BCFSleuth V2.0 features a production-ready architecture with centralized utilities, optimized methods, and professional code organization. This guide covers the key concepts for developers working with the codebase.
- 95% duplicate code reduction (800+ lines → 40 lines)
- 25+ duplicate methods eliminated
- 18 focused helper methods extracted from large methods
- 73% average reduction in method sizes
- A+ production code quality
Purpose: Centralized coordinate handling for all BCF operations
// Basic coordinate formatting
const formatted = CoordinateUtils.formatCoordinate(123.456789); // "123.457"
// Get primary viewpoint from topic
const viewpoint = CoordinateUtils.getPrimaryViewpoint(topic);
// Extract specific coordinate values
const cameraX = CoordinateUtils.getPrimaryViewpointBCFCoordinate(
topic, 'camera_world_position', 'x'
);
// Check for coordinate availability
if (CoordinateUtils.hasCoordinateData(topic)) {
// Process coordinates
}Key Methods:
formatCoordinate(value)- 3-decimal precision formattinggetPrimaryViewpoint(topic)- Intelligent viewpoint selectiongetPrimaryViewpointBCFCoordinate(topic, type, axis)- BCF coordinate accesshasCoordinateData(topic)- Coordinate availability checking
Purpose: Consistent PDF generation across all report types
// Add standardized cover page
PDFUtils.addStandardizedCoverPage(pdf, 'Detailed', 25, 'Project ABC', stats);
// Wrap text for PDF constraints
const wrappedLines = PDFUtils.wrapTextForPDF(pdf, longText, maxWidth);
// Generate consistent filenames
const filename = PDFUtils.generatePDFFilename('Project ABC', 'Summary', new Date());
// Safe image addition with error handling
const success = PDFUtils.addImageToPDF(pdf, imageData, x, y, width, height);Key Methods:
addStandardizedCoverPage()- Consistent cover page formattingwrapTextForPDF()- Smart text wrapping for PDF constraintsgeneratePDFFilename()- Standardized filename generationaddImageToPDF()- Error-handling image insertion
Purpose: Professional color management with accessibility
// Context-aware color assignment
const statusColors = ColorManager.getDistinctColors(['Open', 'Closed'], 'status');
// Status-specific colors
const colors = ColorManager.getStatusColors(['Open', 'In Progress', 'Closed']);
// Chart.js compatible palette
const chartColors = ColorManager.generateChartPalette(5, 0.8);Key Methods:
getDistinctColors(values, context)- Smart color assignmentgetStatusColors(statuses)- Status-optimized colorsgetPriorityColors(priorities)- Priority-optimized colorsgenerateChartPalette(count, alpha)- Chart.js compatible colors
The BCF parser has been transformed from monolithic methods to focused, testable units:
// parseTopic() - 250 lines of complex logic
function parseTopic(topicData, zip) {
// Extract labels (55 lines)
// Process custom fields (10 lines)
// Handle viewpoints (30 lines)
// Initialize data (80 lines)
// Discover images (20 lines)
// Match viewpoints (70 lines)
// Store image data (15 lines)
}// parseTopic() - 80 lines, focused orchestration
function parseTopic(topicData, zip) {
const topic = initializeTopicData(topicData);
extractTopicLabels(topicData, topic);
processTopicCustomFields(topicData, topic);
await processTopicViewpoints(topic, zip);
return topic;
}
// Individual focused methods (18 total)
function initializeTopicData(topicData) { /* 80 lines → focused initialization */ }
function extractTopicLabels(topicData, topic) { /* 55 lines → label processing */ }
function processTopicCustomFields(topicData, topic) { /* 10 lines → custom fields */ }
// ... 15 more focused helper methods- Testability: Each method can be unit tested independently
- Readability: Clear single responsibility for each method
- Maintainability: Easy to update specific functionality
- Debugging: Easier to isolate and fix issues
- Performance: Optimized execution paths
Similar transformation applied to image processing:
// Before: downloadImagesAsZip() - 80 lines
// After: 25 lines with focused helpers
async function downloadImagesAsZip(selectedImages) {
if (!validateZipDownload(selectedImages)) return;
const zipBlob = await createZipWithImages(selectedImages, updateProgress);
const filename = generateZipFilename();
generateAndDownloadZip(zipBlob, filename);
}
// Supporting focused methods
function validateZipDownload(selectedImages) { /* validation logic */ }
async function createZipWithImages(images, callback) { /* ZIP creation */ }
function generateAndDownloadZip(blob, filename) { /* download handling */ }All user-facing errors use the standardized feedback system:
// Standardized error display
showUserFeedback('Operation completed successfully', 'success', 3000);
showUserFeedback('Warning: Large file detected', 'warning', 5000);
showUserFeedback('Error: Invalid BCF format', 'error', 0); // Persistent error
// Debug logging (conditional)
debugLog('Processing viewpoint', { topicId, viewpointId, coordinates });
// Toggle debug mode
window.bcfApp.toggleDebugMode(); // Enable/disable via console- Conditional Logging: Debug messages only appear when debug mode is enabled
- Persistent Settings: Debug mode state saved in localStorage
- Console Access: Toggle debug mode via browser console
- Performance Tracking: Monitor method execution times
- Data Inspection: Detailed logging of processing steps
When adding new export capabilities, follow these patterns:
// 1. Use shared coordinate utilities
import { CoordinateUtils } from './utils/coordinate-utils.js';
function exportToNewFormat(topics) {
return topics.map(topic => {
// Always use shared coordinate methods
const coordinates = {
x: CoordinateUtils.getPrimaryViewpointBCFCoordinate(topic, 'camera_world_position', 'x'),
y: CoordinateUtils.getPrimaryViewpointBCFCoordinate(topic, 'camera_world_position', 'y'),
z: CoordinateUtils.getPrimaryViewpointBCFCoordinate(topic, 'camera_world_position', 'z')
};
return {
title: topic.title,
coordinates: coordinates,
// ... other fields
};
});
}
// 2. Use standardized error handling
function processExport() {
try {
const result = exportToNewFormat(topics);
showUserFeedback('Export completed successfully', 'success');
return result;
} catch (error) {
debugLog('Export failed', { error: error.message });
showUserFeedback('Export failed: ' + error.message, 'error');
return null;
}
}Follow the ColorManager pattern for consistent visualization:
// Use ColorManager for all chart colors
function createNewChart(data, context) {
const colors = ColorManager.getDistinctColors(data.labels, context);
const chartConfig = {
type: 'newChartType',
data: {
labels: data.labels,
datasets: [{
data: data.values,
backgroundColor: colors,
borderColor: colors.map(color => ColorManager.darkenColor(color, 0.2))
}]
}
};
return new Chart(canvas, chartConfig);
}- Keep methods under 50 lines when possible
- Extract helper methods for complex logic
- Use single responsibility principle - one clear purpose per method
- Prefer composition over large monolithic functions
// Good: Use shared utilities to reduce memory footprint
const coordinates = CoordinateUtils.formatCoordinate(value);
// Avoid: Duplicate coordinate formatting logic
function formatCoordinate(value) { /* duplicate implementation */ }// Use debug logging for development
debugLog('Starting coordinate extraction', { topicCount, hasViewpoints });
// Provide meaningful error context
try {
processCoordinates(topic);
} catch (error) {
debugLog('Coordinate processing failed', {
topicId: topic.id,
error: error.message,
hasViewpoints: topic.viewpoints?.length > 0
});
throw new Error(`Failed to process coordinates for topic ${topic.id}: ${error.message}`);
}The optimized method structure enables comprehensive unit testing:
// Test individual helper methods
describe('CoordinateUtils', () => {
test('formatCoordinate handles decimal precision', () => {
expect(CoordinateUtils.formatCoordinate(123.456789)).toBe('123.457');
expect(CoordinateUtils.formatCoordinate('123.456789')).toBe('123.457');
});
test('hasCoordinateData detects coordinate availability', () => {
const topicWithCoords = { viewpoints: [{ coordinates: { x: 1, y: 2, z: 3 } }] };
const topicWithoutCoords = { viewpoints: [] };
expect(CoordinateUtils.hasCoordinateData(topicWithCoords)).toBe(true);
expect(CoordinateUtils.hasCoordinateData(topicWithoutCoords)).toBe(false);
});
});
// Test integration between modules
describe('BCF Export Integration', () => {
test('CSV export uses shared coordinate utilities', () => {
const topics = [createMockTopic()];
const csvData = generateCSVExport(topics);
// Verify coordinate formatting matches CoordinateUtils.formatCoordinate
expect(csvData).toContain('123.457'); // Not '123.4567' or other format
});
});// Test complete workflows
describe('PDF Generation Workflow', () => {
test('detailed PDF report generation', async () => {
const topics = [createMockTopicWithImages()];
const pdf = await generateDetailedPDFReport(topics);
expect(pdf).toBeDefined();
expect(pdf.internal.pages.length).toBeGreaterThan(1); // Cover + content pages
});
});When adding support for new BCF versions:
- Extend CoordinateUtils for new coordinate fields
- Update bcf-parser.js helper methods for new XML structures
- Maintain backward compatibility with existing coordinate fields
- Add version detection in the parsing logic
- Update export utilities to handle new fields
Follow the established pattern:
// js/utils/new-utility.js
/**
* @fileoverview New utility module description
* @version 2.0.0
*/
const NewUtility = {
/**
* Method description with JSDoc
* @param {type} param - Parameter description
* @returns {type} Return description
*/
methodName(param) {
// Implementation
}
};
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = NewUtility;
}Before adding new code, verify:
- ✅ No duplicate methods - Check if functionality exists in utilities
- ✅ Proper error handling - Use showUserFeedback() for user messages
- ✅ Debug logging - Add debugLog() calls for troubleshooting
- ✅ Method size - Keep methods focused and under 50 lines
- ✅ JSDoc comments - Document all public methods
- ✅ Utility usage - Use shared utilities instead of duplicating logic
- ✅ Testing - Ensure new methods can be unit tested
When working with legacy code:
// Before: Direct coordinate formatting
const formattedX = parseFloat(coordinates.x).toFixed(3);
// After: Use shared utility
const formattedX = CoordinateUtils.formatCoordinate(coordinates.x);
// Before: Custom PDF filename generation
const filename = projectName.replace(/[^a-zA-Z0-9]/g, '_') + '_report.pdf';
// After: Use shared utility
const filename = PDFUtils.generatePDFFilename(projectName, 'Detailed', new Date());Step-by-step approach:
- Identify logical sections in the large method
- Extract helper methods for each section
- Test each helper method independently
- Update main method to orchestrate helpers
- Verify functionality remains identical
Method not found errors:
// Ensure utility modules are properly loaded
if (typeof CoordinateUtils === 'undefined') {
console.error('CoordinateUtils not loaded - check script order in index.html');
}Debug mode not working:
// Check if debug mode is properly initialized
console.log('Debug mode:', window.BCF_DEBUG_MODE);
window.bcfApp.toggleDebugMode(); // Toggle via consoleColor inconsistencies:
// Always use ColorManager for consistency
const colors = ColorManager.getDistinctColors(values, 'status'); // Good
const colors = ['red', 'blue', 'green']; // Avoid hardcoded colorsLarge file processing:
// Use progress callbacks for long operations
async function processLargeDataset(data, progressCallback) {
for (let i = 0; i < data.length; i++) {
await processItem(data[i]);
// Update progress every 10 items
if (i % 10 === 0) {
progressCallback(i / data.length * 100);
}
}
}The BCFSleuth V2.0 architecture provides a solid foundation for professional development with:
- Clean separation of concerns through utility modules
- Optimized method structure for better maintainability
- Standardized patterns for consistent development
- Comprehensive error handling and debugging capabilities
- Performance optimizations throughout the codebase
Follow these guidelines to maintain code quality and extend BCFSleuth capabilities effectively.