Josh Bruce Online

School Route Mapper - API Reference & Technical Documentation

Table of Contents

  1. Event System
  2. Internal APIs
  3. Data Schema Reference
  4. Configuration Options
  5. Extension Points
  6. Error Handling

Event System

The School Route Mapper uses a comprehensive event-driven architecture for inter-component communication. All components emit and listen for events to maintain loose coupling.

Global Events

Drawing Tool Events

// Feature lifecycle events
document.addEventListener('drawingTool:featureAdded', (event) => {
  // event.detail: { feature, tool }
});

document.addEventListener('drawingTool:featureModified', (event) => {
  // event.detail: { feature, changes, tool }
});

document.addEventListener('drawingTool:featureDeleted', (event) => {
  // event.detail: { featureId, feature, tool }
});

document.addEventListener('drawingTool:selectionChanged', (event) => {
  // event.detail: { selectedFeatures, previousSelection }
});

// Tool state events
document.addEventListener('drawingTool:toolChanged', (event) => {
  // event.detail: { tool, previousTool }
});

document.addEventListener('drawingTool:categoryChanged', (event) => {
  // event.detail: { categoryId, category }
});

POI Events

// POI lifecycle events
document.addEventListener('poi:poiAdded', (event) => {
  // event.detail: { poi }
});

document.addEventListener('poi:poiUpdated', (event) => {
  // event.detail: { poi, changes }
});

document.addEventListener('poi:poiDeleted', (event) => {
  // event.detail: { poiId, poi }
});

// POI interaction events
document.addEventListener('poi:poiSelected', (event) => {
  // event.detail: { poi }
});

document.addEventListener('poi:dialogOpened', (event) => {
  // event.detail: { poi, mode } // mode: 'create' | 'edit'
});

document.addEventListener('poi:dialogClosed', (event) => {
  // event.detail: { saved, poi }
});

Storage Events

// Project operations
document.addEventListener('storage:projectSaved', (event) => {
  // event.detail: { project, timestamp }
});

document.addEventListener('storage:projectLoaded', (event) => {
  // event.detail: { project, source } // source: 'localStorage' | 'import'
});

document.addEventListener('storage:projectImported', (event) => {
  // event.detail: { project, filename, warnings }
});

document.addEventListener('storage:projectExported', (event) => {
  // event.detail: { project, filename, format }
});

// Autosave events
document.addEventListener('storage:autosaveTriggered', (event) => {
  // event.detail: { project, timestamp }
});

document.addEventListener('storage:autosaveCompleted', (event) => {
  // event.detail: { success, project, timestamp }
});

// Error events
document.addEventListener('storage:error', (event) => {
  // event.detail: { message, error, operation }
});

Canvas Events

// Viewport changes
document.addEventListener('canvas:viewportChanged', (event) => {
  // event.detail: { viewport, zoom, center }
});

// Mouse interactions
document.addEventListener('canvas:click', (event) => {
  // event.detail: { world, screen, button, ctrlKey, shiftKey }
});

document.addEventListener('canvas:mousemove', (event) => {
  // event.detail: { world, screen, deltaX, deltaY }
});

// Grid events
document.addEventListener('canvas:gridChanged', (event) => {
  // event.detail: { spacing, origin, visible }
});

Component-Specific Event Emitters

Base EventEmitter Pattern

class BaseComponent {
  constructor() {
    this.eventListeners = new Map();
  }
  
  on(eventType, listener) {
    if (!this.eventListeners.has(eventType)) {
      this.eventListeners.set(eventType, []);
    }
    this.eventListeners.get(eventType).push(listener);
  }
  
  off(eventType, listener) {
    const listeners = this.eventListeners.get(eventType);
    if (listeners) {
      const index = listeners.indexOf(listener);
      if (index > -1) {
        listeners.splice(index, 1);
      }
    }
  }
  
  emit(eventType, data) {
    const listeners = this.eventListeners.get(eventType);
    if (listeners) {
      listeners.forEach(listener => listener(data));
    }
    
    // Also emit as custom DOM event
    const event = new CustomEvent(`${this.constructor.name.toLowerCase()}:${eventType}`, {
      detail: data
    });
    document.dispatchEvent(event);
  }
}

Internal APIs

Core Application API

SchoolRouteMapperApp

class SchoolRouteMapperApp {
  constructor() {
    this.version = '1.0.0';
    this.project = null;
    this.canvasGrid = null;
    this.drawingTools = null;
    this.poiManager = null;
    this.storage = null;
    this.uiControls = null;
    this.isInitialized = false;
    this.currentMode = 'edit'; // 'edit' | 'navigate'
  }

  // Lifecycle Methods
  async initialize() { /* ... */ }
  destroy() { /* ... */ }
  
  // Project Management
  newProject(title = 'New School Map') { /* ... */ }
  async loadProject(project) { /* ... */ }
  saveProject() { /* ... */ }
  exportProject(filename = null) { /* ... */ }
  loadDemo() { /* ... */ }
  
  // State Management
  getState() {
    return {
      version: this.version,
      initialized: this.isInitialized,
      mode: this.currentMode,
      project: this.project,
      hasUnsavedChanges: this.storage?.isDirty() || false
    };
  }
  
  // Utility Methods
  getContentBounds() { /* ... */ }
  render() { /* ... */ }
  showError(message) { /* ... */ }
  checkBrowserCompatibility() { /* ... */ }
}

Data Model API

Project Creation and Validation

// Project Factory Functions
createEmptyProject(title = "New School Map") -> Project
createDemoProject() -> Project

// Validation Functions
validateProject(data) -> {
  valid: boolean,
  errors: string[],
  warnings: string[]
}

migrateProject(data) -> Project

// Feature Factory Functions
createPolyline(categoryId, points, properties = {}) -> PolylineFeature
createPOI(name, type, position, properties = {}) -> POIFeature

// Utility Functions
generateId(prefix = '') -> string
deepClone(obj) -> any
calculateBounds(features) -> BoundingBox | null
findFeaturesNear(features, point, tolerance = 0.5) -> FoundFeature[]
snapToGrid(point, spacing, origin = [0, 0]) -> [number, number]
getVisibleGridPoints(viewport, spacing, origin = [0, 0]) -> [number, number][]
updateProjectMeta(project, updates) -> Project
normalizeColor(color) -> string
normalizeDashPattern(dash) -> number[]

CategoryManager API

class CategoryManager {
  constructor(categories = []) {
    this.categories = [...categories];
  }
  
  // CRUD Operations
  getCategory(id) -> Category | undefined
  addCategory(category) -> Category
  updateCategory(id, updates) -> Category | null
  removeCategory(id) -> Category | null
  
  // Query Methods
  getAllCategories() -> Category[]
  getWalkableCategories() -> Category[]
  getBlockedCategories() -> Category[]
  
  // Validation
  validateCategory(category) -> ValidationResult
}

Canvas Grid API

CanvasGrid Class

class CanvasGrid extends BaseComponent {
  constructor(canvas) {
    super();
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.viewport = { x: 0, y: 0, zoom: 20, width: 0, height: 0 };
    this.grid = { spacing: 0.5, origin: [0, 0], visible: true };
    this.mouse = { world: [0, 0], screen: [0, 0] };
    this.background = { image: null, transform: {...}, opacity: 0.35 };
  }
  
  // Coordinate Transformation
  screenToWorld(screenX, screenY) -> [number, number]
  worldToScreen(worldX, worldY) -> [number, number]
  
  // Viewport Management
  setViewport(x, y, zoom) -> void
  fitToContent(bounds, padding = 50) -> void
  centerOn(worldX, worldY) -> void
  
  // Grid Management
  setGridSpacing(spacing) -> void
  setGridOrigin(origin) -> void
  setGridVisible(visible) -> void
  getSnapPosition(worldPos = this.mouse.world) -> [number, number]
  
  // Background Management
  setBackgroundImage(image) -> Promise<void>
  setBackgroundTransform(transform) -> void
  setBackgroundOpacity(opacity) -> void
  clearBackground() -> void
  
  // Rendering
  draw() -> void
  clear() -> void
  resize() -> void
  
  // Event Handling
  setupEventListeners() -> void
  handleMouseMove(event) -> void
  handleMouseDown(event) -> void
  handleMouseUp(event) -> void
  handleWheel(event) -> void
  handleKeyDown(event) -> void
}

Drawing Tools API

DrawingTools Class

class DrawingTools extends BaseComponent {
  constructor(canvasGrid, project) {
    super();
    this.canvasGrid = canvasGrid;
    this.project = project;
    this.categoryManager = new CategoryManager(project.categories);
    this.currentTool = 'select';
    this.currentCategory = null;
    this.selectedFeatures = [];
    this.isDrawing = false;
  }
  
  // Tool Management
  setTool(tool) -> void // 'select' | 'polyline' | 'erase' | 'poi' | 'pan'
  getCurrentTool() -> string
  
  // Category Management
  setCategory(categoryId) -> void
  getCurrentCategory() -> Category | null
  
  // Selection Management
  selectFeature(feature, addToSelection = false) -> void
  deselectFeature(feature) -> void
  clearSelection() -> void
  getSelectedFeatures() -> Feature[]
  
  // Feature Operations
  deleteSelected() -> void
  moveSelected(deltaX, deltaY) -> void
  duplicateSelected() -> Feature[]
  
  // Drawing Operations
  startDrawing(worldPos) -> void
  continueDrawing(worldPos) -> void
  finishDrawing() -> Feature | null
  cancelDrawing() -> void
  
  // Hit Testing
  hitTest(worldPos, tolerance = 0.5) -> Feature | null
  getFeatureAtPosition(worldPos, tolerance = 0.5) -> Feature | null
  
  // Rendering Integration
  render(ctx) -> void
  renderSelection(ctx) -> void
  renderPreview(ctx) -> void
}

POI Manager API

POIManager Class

class POIManager extends BaseComponent {
  constructor(canvasGrid, project) {
    super();
    this.canvasGrid = canvasGrid;
    this.project = project;
    this.selectedPOI = null;
    this.hoveredPOI = null;
    this.editingPOI = null;
  }
  
  // POI Operations
  placePOI(position, name = 'New POI', type = 'other', properties = {}) -> POI
  updatePOI(poiId, updates) -> POI | null
  deletePOI(poiId) -> boolean
  
  // Query Methods
  getAllPOIs() -> POI[]
  getPOIsByType(type) -> POI[]
  getPOIsByLevel(level) -> POI[]
  findPOINear(position, tolerance = 1.0) -> POI[]
  
  // UI Integration
  showPOIDialog(poi = null) -> void
  hidePOIDialog() -> void
  
  // Selection Management
  selectPOI(poi) -> void
  deselectPOI() -> void
  getSelectedPOI() -> POI | null
  
  // Rendering
  render(ctx) -> void
  renderPOI(ctx, poi) -> void
  getPOIColor(type) -> string
  getPOIIcon(type) -> string
}

Storage Manager API

StorageManager Class

class StorageManager extends BaseComponent {
  constructor() {
    super();
    this.storageKey = 'schoolRouteMapper';
    this.autosaveKey = `${this.storageKey}_autosave`;
    this.historyKey = `${this.storageKey}_history`;
    this.settingsKey = `${this.storageKey}_settings`;
    this.maxHistoryItems = 10;
    this.autosaveInterval = 30000;
    this.currentProject = null;
    this.hasUnsavedChanges = false;
  }
  
  // Project Persistence
  saveProject(project, updateMeta = true) -> boolean
  loadProject() -> Project | null
  
  // File Operations
  exportProject(project, filename = null) -> boolean
  importProject(file) -> Promise<ImportResult>
  setupFileInput(input) -> void
  setupDragAndDrop(element) -> void
  
  // Autosave Management
  startAutosave(project) -> void
  stopAutosave() -> void
  markDirty() -> void
  isDirty() -> boolean
  
  // History Management
  saveToHistory(project) -> void
  getHistory() -> HistoryEntry[]
  loadFromHistory(historyId) -> Project | null
  clearHistory() -> void
  
  // Settings Management
  getSettings() -> Settings
  saveSettings(settings) -> void
  resetSettings() -> void
  
  // Utility Methods
  getStorageUsage() -> StorageInfo
  clearAllData() -> void
}

Data Schema Reference

Core Types

Project Schema

interface Project {
  schemaVersion: number;
  meta: ProjectMetadata;
  background: BackgroundConfig;
  categories: Category[];
  features: Features;
}

interface ProjectMetadata {
  title: string;
  created: string; // ISO 8601 timestamp
  updated: string; // ISO 8601 timestamp
  unit: 'm' | 'ft'; // measurement unit
  grid: GridConfig;
  render: RenderConfig;
  description?: string;
  author?: string;
  version?: string;
}

interface GridConfig {
  spacing: number; // grid spacing in units
  origin: [number, number]; // grid origin point
}

interface RenderConfig {
  theme: 'light' | 'dark';
  showGrid?: boolean;
  showCoordinates?: boolean;
  highQuality?: boolean;
}

Background Configuration

interface BackgroundConfig {
  source: string | null; // data URL or null
  transform: Transform;
  opacity: number; // 0-1
}

interface Transform {
  x: number; // translation X
  y: number; // translation Y
  scale: number; // scale factor
  rotation: number; // rotation in degrees
}

Category Schema

interface Category {
  id: string; // unique identifier
  name: string; // display name
  color: string; // hex color code
  dash: number[]; // line dash pattern
  walkability: WalkabilityType;
  
  // Optional routing properties
  buffer?: number; // minimum clearance
  speedFactor?: number; // movement speed multiplier
  costFactor?: number; // pathfinding cost multiplier
  gateWidth?: number; // passage width for gates
}

type WalkabilityType = 
  | 'blocked' 
  | 'gate' 
  | 'normal' 
  | 'fast' 
  | 'slow' 
  | 'slower' 
  | 'discouraged';

Feature Schema

interface Features {
  polylines: PolylineFeature[];
  pois: POIFeature[];
}

interface PolylineFeature {
  id: string;
  category: string; // category ID
  points: [number, number][]; // world coordinates
  properties: PolylineProperties;
}

interface PolylineProperties {
  level: number; // floor level
  description?: string;
  tags?: string[];
  [key: string]: any; // extensible properties
}

interface POIFeature {
  id: string;
  name: string;
  type: POIType;
  pos: [number, number]; // world coordinates
  properties: POIProperties;
}

interface POIProperties {
  level: number; // floor level
  tags: string[];
  description?: string;
  capacity?: number;
  accessible?: boolean;
  [key: string]: any; // extensible properties
}

type POIType = 
  | 'room' 
  | 'entrance' 
  | 'stairs' 
  | 'elevator' 
  | 'restroom' 
  | 'office' 
  | 'exit' 
  | 'emergency-exit' 
  | 'cafeteria' 
  | 'library' 
  | 'gym' 
  | 'auditorium' 
  | 'lab' 
  | 'computer-lab' 
  | 'nurse' 
  | 'admin' 
  | 'parking' 
  | 'bike-rack' 
  | 'bus-stop' 
  | 'other';

Utility Types

Geometric Types

interface BoundingBox {
  minX: number;
  minY: number;
  maxX: number;
  maxY: number;
  width: number;
  height: number;
  centerX: number;
  centerY: number;
}

interface Viewport {
  x: number; // world X offset
  y: number; // world Y offset
  zoom: number; // zoom level (pixels per unit)
  width: number; // viewport width in pixels
  height: number; // viewport height in pixels
}

Storage Types

interface Settings {
  theme: 'light' | 'dark';
  gridSpacing: number;
  autosaveEnabled: boolean;
  routingResolution: number;
  defaultBuffer: number;
  turnPenalty: number;
  splineTension: number;
  animationSpeed: number;
  highQualityRendering: boolean;
}

interface HistoryEntry {
  id: string;
  timestamp: string;
  title: string;
  project: Project;
  size: number; // serialized size in bytes
}

interface ImportResult {
  project: Project;
  warnings: string[];
  migrated: boolean;
}

interface StorageInfo {
  used: number; // bytes used
  available: number; // bytes available
  quota: number; // total quota
  projects: number; // number of projects
  history: number; // number of history entries
}

Validation Schema

Validation Rules

interface ValidationRule {
  field: string;
  type: 'required' | 'type' | 'range' | 'format' | 'custom';
  message: string;
  validator?: (value: any) => boolean;
}

// Example validation rules
const PROJECT_VALIDATION_RULES: ValidationRule[] = [
  {
    field: 'schemaVersion',
    type: 'required',
    message: 'Schema version is required'
  },
  {
    field: 'meta.title',
    type: 'required',
    message: 'Project title is required'
  },
  {
    field: 'categories',
    type: 'type',
    message: 'Categories must be an array',
    validator: (value) => Array.isArray(value)
  }
];

Configuration Options

Application Configuration

Runtime Settings

const DEFAULT_SETTINGS = {
  // Visual preferences
  theme: 'light', // 'light' | 'dark'
  highQualityRendering: true, // enable anti-aliasing
  
  // Grid configuration
  gridSpacing: 0.5, // meters
  showGrid: true, // show grid by default
  snapToGrid: true, // enable grid snapping
  
  // Storage settings
  autosaveEnabled: true, // enable automatic saves
  autosaveInterval: 30000, // milliseconds
  maxHistoryItems: 10, // autosave history limit
  
  // Routing parameters
  routingResolution: 0.25, // pathfinding grid resolution (meters)
  defaultBuffer: 0.3, // obstacle clearance (meters)
  turnPenalty: 0.3, // cost for direction changes
  
  // Animation settings
  animationSpeed: 1.0, // route animation speed multiplier
  
  // Spline settings
  splineTension: 0.5, // curve tension (0-1)
  splineResolution: 0.1, // curve point density
  
  // Performance settings
  maxFeatures: 10000, // feature count warning threshold
  maxPolylinePoints: 1000, // polyline complexity limit
  renderDistance: 1000 // meters beyond viewport to render
};

Theme Configuration

const THEME_DEFINITIONS = {
  light: {
    // Background colors
    '--bg-primary': '#ffffff',
    '--bg-secondary': '#f8fafc',
    '--bg-tertiary': '#f1f5f9',
    
    // Text colors
    '--text-primary': '#1e293b',
    '--text-secondary': '#64748b',
    '--text-muted': '#94a3b8',
    
    // Border colors
    '--border-color': '#e2e8f0',
    
    // Accent colors
    '--accent-primary': '#3b82f6',
    '--accent-hover': '#2563eb',
    '--success': '#10b981',
    '--warning': '#f59e0b',
    '--danger': '#ef4444',
    
    // Grid colors
    '--grid-color': '#94a3b8',
    '--grid-alpha': 0.5
  },
  
  dark: {
    // Background colors
    '--bg-primary': '#0f172a',
    '--bg-secondary': '#1e293b',
    '--bg-tertiary': '#334155',
    
    // Text colors
    '--text-primary': '#f1f5f9',
    '--text-secondary': '#cbd5e1',
    '--text-muted': '#64748b',
    
    // Border colors
    '--border-color': '#475569',
    
    // Accent colors
    '--accent-primary': '#60a5fa',
    '--accent-hover': '#3b82f6',
    '--success': '#34d399',
    '--warning': '#fbbf24',
    '--danger': '#f87171',
    
    // Grid colors
    '--grid-color': '#64748b',
    '--grid-alpha': 0.3
  }
};

Pathfinding Configuration

const PATHFINDING_CONFIG = {
  // Grid settings
  resolution: 0.25, // meters per grid cell
  padding: 5, // meters of padding around content
  
  // Algorithm parameters
  algorithm: 'astar', // 'astar' | 'dijkstra' | 'bfs'
  heuristic: 'euclidean', // 'euclidean' | 'manhattan'
  allowDiagonal: true, // allow diagonal movement
  
  // Cost factors
  defaultCost: 1.0, // base movement cost
  diagonalCost: 1.414, // diagonal movement multiplier
  turnPenalty: 0.3, // cost for direction changes
  
  // Obstacle handling
  defaultBuffer: 0.3, // minimum clearance from obstacles
  bufferResolution: 0.1, // buffer calculation precision
  
  // Performance limits
  maxIterations: 100000, // algorithm iteration limit
  timeout: 5000 // milliseconds before timeout
};

Category Defaults

Standard Categories

const CATEGORY_PRESETS = {
  // Structure categories
  walls: {
    external: {
      color: '#1f2937',
      walkability: 'blocked',
      buffer: 0.5,
      dash: []
    },
    internal: {
      color: '#4b5563',
      walkability: 'blocked',
      buffer: 0.4,
      dash: []
    },
    temporary: {
      color: '#9ca3af',
      walkability: 'blocked',
      buffer: 0.3,
      dash: [4, 4]
    }
  },
  
  // Access categories
  openings: {
    door: {
      color: '#22c55e',
      walkability: 'gate',
      gateWidth: 1.0,
      dash: [2, 3]
    },
    window: {
      color: '#06b6d4',
      walkability: 'blocked',
      buffer: 0.2,
      dash: [1, 2]
    },
    opening: {
      color: '#84cc16',
      walkability: 'normal',
      dash: []
    }
  },
  
  // Circulation categories
  movement: {
    corridor: {
      color: '#e5e7eb',
      walkability: 'normal',
      dash: []
    },
    stairs: {
      color: '#10b981',
      walkability: 'slow',
      speedFactor: 0.6,
      dash: [1, 2]
    },
    elevator: {
      color: '#0ea5e9',
      walkability: 'slow',
      speedFactor: 0.7,
      dash: [4, 2]
    },
    ramp: {
      color: '#14b8a6',
      walkability: 'normal',
      speedFactor: 0.9,
      dash: []
    }
  },
  
  // Outdoor categories
  exterior: {
    path: {
      color: '#2563eb',
      walkability: 'fast',
      speedFactor: 1.05,
      dash: []
    },
    road: {
      color: '#7c3aed',
      walkability: 'discouraged',
      costFactor: 1.8,
      dash: [2, 6]
    },
    grass: {
      color: '#84cc16',
      walkability: 'slower',
      speedFactor: 0.8,
      dash: [1, 5]
    }
  }
};

Extension Points

Plugin Architecture

Plugin Interface

class BasePlugin {
  constructor(app) {
    this.app = app;
    this.name = 'BasePlugin';
    this.version = '1.0.0';
    this.enabled = false;
  }
  
  // Lifecycle methods
  initialize() { /* Override */ }
  activate() { this.enabled = true; }
  deactivate() { this.enabled = false; }
  destroy() { /* Override */ }
  
  // Extension points
  onProjectLoaded(project) { /* Override */ }
  onFeatureAdded(feature) { /* Override */ }
  onRender(ctx) { /* Override */ }
  
  // Utility methods
  registerTool(toolName, toolClass) { /* ... */ }
  registerCategory(categoryDef) { /* ... */ }
  addMenuItem(menu, item) { /* ... */ }
}

Custom Tool Development

class CustomTool extends BaseTool {
  constructor(drawingTools) {
    super(drawingTools);
    this.name = 'custom';
    this.cursor = 'crosshair';
  }
  
  activate() {
    super.activate();
    // Tool-specific setup
  }
  
  deactivate() {
    super.deactivate();
    // Tool-specific cleanup
  }
  
  handleClick(worldPos, event) {
    // Handle click events
  }
  
  handleMouseMove(worldPos, event) {
    // Handle mouse movement
  }
  
  render(ctx) {
    // Custom rendering
  }
}

// Register custom tool
app.drawingTools.registerTool('custom', CustomTool);

Custom POI Types

const CUSTOM_POI_TYPES = {
  'science-lab': {
    name: 'Science Laboratory',
    color: '#8b5cf6',
    icon: '🧪',
    category: 'academic',
    properties: {
      capacity: { type: 'number', required: true },
      equipment: { type: 'array', default: [] },
      safetyLevel: { type: 'string', enum: ['low', 'medium', 'high'] }
    }
  }
};

// Register custom POI type
app.poiManager.registerPOIType('science-lab', CUSTOM_POI_TYPES['science-lab']);

Renderer Extensions

Custom Rendering Layers

class CustomRenderLayer {
  constructor(canvasGrid) {
    this.canvasGrid = canvasGrid;
    this.visible = true;
    this.zIndex = 10;
  }
  
  render(ctx) {
    if (!this.visible) return;
    
    // Custom rendering logic
    ctx.save();
    // ... rendering code ...
    ctx.restore();
  }
  
  hitTest(worldPos) {
    // Return feature at position or null
    return null;
  }
}

// Register custom layer
app.canvasGrid.addRenderLayer(new CustomRenderLayer(app.canvasGrid));

Style Customization

const CUSTOM_STYLES = {
  polyline: {
    selectedStroke: '#ff6b6b',
    selectedWidth: 3,
    hoverStroke: '#4ecdc4',
    hoverWidth: 2
  },
  poi: {
    selectedFill: '#ff6b6b',
    selectedStroke: '#ffffff',
    hoverFill: '#4ecdc4',
    textFont: '12px Arial',
    textColor: '#333333'
  },
  grid: {
    majorColor: '#cccccc',
    minorColor: '#eeeeee',
    majorWidth: 1,
    minorWidth: 0.5,
    majorSpacing: 5 // major lines every 5 grid units
  }
};

// Apply custom styles
app.canvasGrid.setStyles(CUSTOM_STYLES);

Error Handling

Error Types

Application Errors

class SchoolRouteMapperError extends Error {
  constructor(message, code, context = {}) {
    super(message);
    this.name = 'SchoolRouteMapperError';
    this.code = code;
    this.context = context;
    this.timestamp = new Date().toISOString();
  }
}

// Error codes
const ERROR_CODES = {
  // Initialization errors
  INIT_CANVAS_NOT_FOUND: 'INIT_001',
  INIT_BROWSER_INCOMPATIBLE: 'INIT_002',
  INIT_MODULE_LOAD_FAILED: 'INIT_003',
  
  // Data errors
  DATA_VALIDATION_FAILED: 'DATA_001',
  DATA_MIGRATION_FAILED: 'DATA_002',
  DATA_CORRUPTION: 'DATA_003',
  
  // Storage errors
  STORAGE_QUOTA_EXCEEDED: 'STOR_001',
  STORAGE_ACCESS_DENIED: 'STOR_002',
  STORAGE_IMPORT_FAILED: 'STOR_003',
  STORAGE_EXPORT_FAILED: 'STOR_004',
  
  // Rendering errors
  RENDER_CANVAS_ERROR: 'REND_001',
  RENDER_BACKGROUND_LOAD_FAILED: 'REND_002',
  
  // Pathfinding errors
  PATH_NO_SOLUTION: 'PATH_001',
  PATH_INVALID_START: 'PATH_002',
  PATH_INVALID_END: 'PATH_003',
  PATH_TIMEOUT: 'PATH_004'
};

Error Recovery Strategies

class ErrorRecoveryManager {
  constructor(app) {
    this.app = app;
    this.recoveryStrategies = new Map();
    this.setupDefaultStrategies();
  }
  
  setupDefaultStrategies() {
    // Storage quota exceeded
    this.recoveryStrategies.set('STOR_001', async (error) => {
      // Clear old history entries
      await this.app.storage.clearOldHistory();
      
      // Compress current project
      await this.app.storage.compressProject();
      
      // Retry operation
      return { retry: true };
    });
    
    // Canvas rendering error
    this.recoveryStrategies.set('REND_001', async (error) => {
      // Reduce rendering quality
      this.app.canvasGrid.setHighQuality(false);
      
      // Clear and reinitialize canvas
      this.app.canvasGrid.clear();
      this.app.canvasGrid.resize();
      
      return { retry: true };
    });
    
    // Pathfinding timeout
    this.recoveryStrategies.set('PATH_004', async (error) => {
      // Reduce pathfinding resolution
      const newResolution = Math.min(error.context.resolution * 2, 1.0);
      this.app.pathfinder.setResolution(newResolution);
      
      return { retry: true, message: 'Retrying with lower precision' };
    });
  }
  
  async handleError(error) {
    const strategy = this.recoveryStrategies.get(error.code);
    
    if (strategy) {
      try {
        const result = await strategy(error);
        return result;
      } catch (recoveryError) {
        console.error('Error recovery failed:', recoveryError);
        return { retry: false };
      }
    }
    
    return { retry: false };
  }
}

Global Error Handler

class GlobalErrorHandler {
  constructor(app) {
    this.app = app;
    this.errorHistory = [];
    this.maxErrorHistory = 50;
    this.setupErrorHandlers();
  }
  
  setupErrorHandlers() {
    // Unhandled JavaScript errors
    window.addEventListener('error', (event) => {
      this.handleError(new SchoolRouteMapperError(
        event.message,
        'JS_RUNTIME_ERROR',
        {
          filename: event.filename,
          line: event.lineno,
          column: event.colno,
          stack: event.error?.stack
        }
      ));
    });
    
    // Unhandled promise rejections
    window.addEventListener('unhandledrejection', (event) => {
      this.handleError(new SchoolRouteMapperError(
        event.reason?.message || 'Unhandled promise rejection',
        'PROMISE_REJECTION',
        {
          reason: event.reason,
          stack: event.reason?.stack
        }
      ));
    });
    
    // Application-specific errors
    document.addEventListener('schoolRouteMapper:error', (event) => {
      this.handleError(event.detail.error);
    });
  }
  
  handleError(error) {
    // Add to error history
    this.errorHistory.unshift({
      error,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent,
      url: window.location.href,
      appState: this.app.getState()
    });
    
    // Limit history size
    if (this.errorHistory.length > this.maxErrorHistory) {
      this.errorHistory = this.errorHistory.slice(0, this.maxErrorHistory);
    }
    
    // Log error
    console.error('Application error:', error);
    
    // Attempt recovery
    if (this.app.errorRecovery) {
      this.app.errorRecovery.handleError(error);
    }
    
    // Show user notification for critical errors
    if (this.isCriticalError(error)) {
      this.showErrorNotification(error);
    }
  }
  
  isCriticalError(error) {
    const criticalCodes = [
      'INIT_CANVAS_NOT_FOUND',
      'INIT_BROWSER_INCOMPATIBLE',
      'DATA_CORRUPTION',
      'STORAGE_ACCESS_DENIED'
    ];
    
    return criticalCodes.includes(error.code);
  }
  
  showErrorNotification(error) {
    if (this.app.uiControls) {
      this.app.uiControls.showNotification({
        type: 'error',
        title: 'Application Error',
        message: error.message,
        actions: [
          { label: 'Retry', action: () => window.location.reload() },
          { label: 'Report', action: () => this.reportError(error) }
        ]
      });
    }
  }
  
  reportError(error) {
    // Create error report
    const report = {
      error: {
        message: error.message,
        code: error.code,
        context: error.context,
        timestamp: error.timestamp
      },
      environment: {
        userAgent: navigator.userAgent,
        url: window.location.href,
        timestamp: new Date().toISOString()
      },
      application: this.app.getState()
    };
    
    // Generate downloadable report
    const blob = new Blob([JSON.stringify(report, null, 2)], {
      type: 'application/json'
    });
    
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `error-report-${Date.now()}.json`;
    a.click();
    
    URL.revokeObjectURL(url);
  }
}

This API reference provides comprehensive technical documentation for developers working with or extending the School Route Mapper application.