# Live_RC Architecture Reference This is the technical architecture reference for the `Live_RC` codebase. It is intended for debugging, maintenance, and future refactoring work. This document covers: - runtime components - backend/frontend boundaries - every JavaScript file in the repo - direct import dependencies - injected dependencies from `app.js` - state ownership - save/rerender rules - end-to-end data flows - where to debug specific failures ## 1. Runtime Architecture ## 1.1 Components The system has four main runtime parts: 1. Browser UI - `index.html` - `src/styles.css` - `src/app.js` - the rest of `src/*.js` 2. Node backend - `server.js` - Express API - static file serving - AMMC process control - PDF export endpoint - app version endpoint 3. Local persistence - SQLite via `better-sqlite3` - main file: `data/rc_timing.sqlite` - AMMC config JSON: `data/ammc_config.json` 4. Decoder / AMMC runtime - AMMC process started by backend or manually elsewhere - browser decoder socket handled in frontend runtime ## 1.2 High-level diagram ```mermaid flowchart LR Browser[Browser UI\nindex.html + src/*.js] -->|HTTP GET / static files| Server[server.js / Express] Browser -->|GET/POST /api/state| Server Browser -->|GET/POST /api/ammc/*| Server Browser -->|POST /api/passings| Server Browser -->|GET /api/app-version| Server Server -->|persist app state| SQLite[(SQLite)] Server -->|persist passings| SQLite Server -->|read/write config| AMMCConfig[(ammc_config.json)] Server -->|spawn/stop| AMMC[AMMC process] Browser -->|WebSocket| DecoderWS[AMMC websocket / decoder feed] ``` ## 1.3 Ownership split Frontend owns: - UI rendering - in-memory app state while page is active - decoder websocket client - leaderboard and standings calculations - judging/corrections in current page state Backend owns: - static asset serving - persisted app state snapshot - passings table persistence - AMMC process management - PDF generation endpoint - file-watch based app version bumping ## 2. Repository JavaScript Inventory ## 2.1 Root/runtime files ### `package.json` Role: - package metadata - runtime dependencies - npm scripts Dependencies: - `express` - `better-sqlite3` Scripts: - `npm start` -> `node scripts/serverctl.js start` - `npm run start:fg` -> `node server.js` - `npm stop` - `npm run status` - `npm restart` ### `server.js` Role: - backend runtime Uses: - `fs` - `path` - `os` - `child_process.spawn` - `express` - `better-sqlite3` Owns: - HTTP server - API routes - SQLite schema/init - static serving - file watching for app version - AMMC config + process state - public overlay route serving via same frontend shell - PDF export endpoint Main API groups: - `/api/health` - `/api/app-version` - `/api/state` - `/api/passings` - `/api/ammc/config` - `/api/ammc/status` - `/api/ammc/start` - `/api/ammc/stop` - `/api/export/pdf` Static routes of interest: - `/` - `/public-overlay` - `/public-overlay/obs` ### `scripts/serverctl.js` Role: - local process manager for `server.js` Owns: - background start/stop/restart/status - pid file management - log file paths Uses: - `data/server.pid` - `logs/server.out.log` - `logs/server.err.log` ## 2.2 Frontend files ### `src/app.js` Type: - frontend composition root / app shell Owns: - global `state` - bootstrapping - route/view selection - local persistence/bootstrap - top-level DOM rendering - utility/formatting helpers - all dependency injection into extracted modules This is the only file that should know about almost every other frontend module. ### `src/runtime_services.js` Type: - frontend runtime helpers Owns: - backend hydrate/sync/version polling - overlay polling/rotation/live refresh - AMMC config/status/start/stop helpers - audio/speech helpers - session timer ticks ### `src/decoder_runtime.js` Type: - frontend decoder websocket runtime Owns: - connect/disconnect decoder - process incoming decoder messages - resolve incoming passings into app state ### `src/timing_logic.js` Type: - timing domain logic Owns: - active session and timing helpers - lap validation labels and lap window logic - leaderboard generation - practice/qual/final standings - team race standings and stint logs - grid helpers ### `src/judging_logic.js` Type: - judging domain logic Owns: - manual corrections - invalidate/restore lap - undo handling - judging filters ### `src/event_race_logic.js` Type: - event/race domain logic Owns: - event normalization - branding normalization - team normalization - race presets - race wizard defaults and session generation - sponsor round generation - event drivers / teams / team driver pool - qualifying/finals generation and bumps - race summary/manage status logic ### `src/race_render_helpers.js` Type: - race-specific render helpers Owns: - team standings block - team stint log block - final matrix block - grid render block - print/export HTML builders ### `src/race_setup_ui.js` Type: - race setup shared UI fragments Owns: - race format field cards - context cards - summary items/warnings - wizard steps/content - standings table UI ### `src/event_common.js` Type: - shared event/session utility module Owns: - sessions for event - mode/start-mode labels - class/event name lookup - assignment list render - sessions table render ### `src/event_views.js` Type: - outer event/race workspace markup ### `src/event_workspace_controller.js` Type: - outer event/race workspace controller Owns: - create/edit/delete event or race - race wizard step flow - list actions - opening manager view ### `src/event_manager_view.js` Type: - `Hantera` markup Owns: - session forms - race format panel - branding panel - team section - sponsor tools - assignments section - summary/actions panels - team/session modals ### `src/event_manager_controller.js` Type: - `Hantera` controller Owns: - session create/edit/delete - race format save/apply/delete preset - branding save/logo save - team create/edit/delete - generation actions - assignment actions - print/export/grid actions ### `src/overlays.js` Type: - overlay renderers Owns: - normal overlay - team overlay - OBS/public overlay - side panels - overlay leaderboard rows ### `src/timing_views.js` Type: - timing/judging renderers Owns: - timing page UI - judging page UI - leaderboard modal - recent passings UI - quick-add UI ### `src/core_views.js` Type: - overview/admin renderers Owns: - Dashboard - Classes - Drivers - Cars ### `src/misc_views.js` Type: - misc page renderers Owns: - Guide page - Overlay page inside admin UI ### `src/settings.js` Type: - settings page renderer Owns: - backend/decoder settings UI - OBS settings UI - branding/audio settings UI - import/export settings UI ## 3. Direct Import Dependencies ## 3.1 Static import graph ### `src/app.js` imports - `src/overlays.js` - `src/settings.js` - `src/race_setup_ui.js` - `src/event_race_logic.js` - `src/timing_logic.js` - `src/core_views.js` - `src/misc_views.js` - `src/event_common.js` - `src/event_workspace_controller.js` - `src/event_manager_view.js` - `src/event_manager_controller.js` - `src/runtime_services.js` - `src/decoder_runtime.js` - `src/timing_views.js` - `src/race_render_helpers.js` - `src/judging_logic.js` ### `src/event_workspace_controller.js` imports - `src/event_views.js` ### Backend/root imports - `server.js` imports Node core + `express` + `better-sqlite3` - `scripts/serverctl.js` imports Node core only ## 3.2 Injected dependency graph This project uses dependency injection heavily. These relationships do not appear as `import` statements, but they are architecturally real. ```mermaid flowchart TD App[src/app.js] TRL[src/timing_logic.js] ERL[src/event_race_logic.js] EMC[src/event_manager_controller.js] TV[src/timing_views.js] OV[src/overlays.js] MISC[src/misc_views.js] RT[src/runtime_services.js] DEC[src/decoder_runtime.js] EW[src/event_workspace_controller.js] EView[src/event_views.js] EMV[src/event_manager_view.js] App --> TRL App --> ERL App --> EMC App --> TV App --> OV App --> MISC App --> RT App --> DEC App --> EW EW --> EView EMC --> EMV ``` ## 4. `app.js` Wrapper Layer `app.js` defines thin wrappers around extracted module exports so they receive the current state, current language, formatters, and other app-level helpers. This wrapper layer is one of the most important parts of the architecture. ## 4.1 Event/race wrappers - `normalizeRaceTeam` - `normalizeStoredRacePreset` - `getRaceFormatPresets` - `applyRaceFormatPreset` - `buildRaceFormatConfigFromForm` - `normalizeBrandingConfig` - `normalizeEvent` - `resolveEventBranding` - `buildDefaultRaceWizardDraft` - `getRaceWizardPreset` - `applyRaceWizardPresetDefaults` - `ensureRaceParticipantsConfigured` - `buildRaceSession` - `buildTrackSession` - `createSponsorRounds` - `buildRaceSessionsFromWizard` - `getRaceWizardSessionPlan` - `getEventDrivers` - `getEventTeams` - `getTeamDriverPool` - `findEventTeamForPassing` - `generateQualifyingForRace` - `reseedUpcomingQualifying` - `generateFinalsForRace` - `applyBumpsForRace` ## 4.2 Timing wrappers - `getSessionTypeLabel` - `getStatusLabel` - `isUntimedSession` - `getActiveSession` - `getSessionTargetMs` - `getSessionLapWindow` - `isCountedPassing` - `getVisiblePassings` - `getPassingValidationLabel` - `getSessionTiming` - `ensureSessionResult` - `formatLapDelta` - `formatLeaderboardGap` - `getCompetitorElapsedMs` - `getCompetitorPassings` - `getCompetitorSeedMetric` - `getSessionEntrants` - `buildPracticeStandings` - `getQualifyingPointsValue` - `isHighPointsTable` - `compareNumberSet` - `buildQualifyingTieBreakNote` - `hasQualifyingPrimaryTie` - `buildQualifyingStandings` - `formatTeamActiveMemberLabel` - `buildTeamRaceStandings` - `buildTeamStintLog` - `getSessionGridEntries` - `getSessionGridOrder` - `ensureSessionDriverOrder` - `buildFinalStandings` ## 4.3 Judging wrappers - `getManualCorrectionSummary` - `applyCompetitorCorrection` - `recalculateCompetitorFromPassings` - `invalidateCompetitorLastLap` - `restoreCompetitorLastInvalidLap` - `findPassingByUndoMarker` - `undoJudgingAdjustment` - `getJudgeFilteredRows` - `getJudgeFilteredLog` ## 4.4 Event/session shared wrappers - `getSessionsForEvent` - `getModeLabel` - `normalizeStartMode` - `getStartModeLabel` - `getClassName` - `getEventName` - `renderAssignmentList` - `renderSessionsTable` ## 4.5 Runtime wrappers - `createDefaultAmmcConfig` - `getManagedWsUrl` - `loadAmmcConfigFromBackend` - `saveAmmcConfigToBackend` - `refreshAmmcStatus` - `startManagedAmmc` - `stopManagedAmmc` - `applyPersistedState` - `hydrateFromBackend` - `scheduleBackendSync` - `syncStateToBackend` - `pingBackend` - `checkAppVersion` - `startAppVersionPolling` - `startOverlaySync` - `startOverlayRotation` - `startOverlayLiveRefresh` - `ensureAudioContext` - `playPassingBeep` - `playFinishSiren` - `playLeaderCue` - `playStartCue` - `playBestLapCue` - `pushOverlayEvent` - `speakText` - `announcePassing` - `announceRaceFinished` - `handleSessionTimerTick` - `tickClock` ## 4.6 Decoder wrappers - `connectDecoder` - `disconnectDecoder` - `processDecoderMessage` ## 5. State Model Global state owner: - `src/app.js` Primary state buckets: - `state.events` - `state.sessions` - `state.drivers` - `state.cars` - `state.classes` - `state.resultsBySession` - `state.settings` - `state.passings` UI and runtime state in or near `app.js`: - selected event/session/team/grid IDs - current view - overlay mode/view mode - backend dirty/synced version flags - version polling flags - audio context - decoder connection reference ## 5.1 Persistence split Frontend-local / backend-mirrored state: - most of `state` - synced through `/api/state` SQLite persisted backend data: - app state snapshot - passings history Separate config file: - `data/ammc_config.json` ## 6. Save / Rerender Rules ## 6.1 Rule If a change must survive rerender, update the canonical state array. Good: ```js state.events = state.events.map((item) => item.id === eventId ? normalizeEvent({ ...item, raceConfig: nextConfig }) : item ); saveState(); renderView(); ``` Risky: ```js event.raceConfig = nextConfig; saveState(); rerenderEventManager(eventId); ``` ## 6.2 Why this matters After the code split, many bugs came from: - changing a local `event` reference - but not replacing the stored object in `state.events` - then rerender reading from canonical state and showing the old value again This pattern is especially important in: - `src/event_manager_controller.js` - `src/event_workspace_controller.js` ## 7. Screen Ownership ### Overview Render chain: - `app.js` -> `renderView()` -> `renderDashboardView()` in `src/core_views.js` ### Classes / Drivers / Cars Render chain: - `app.js` -> `renderView()` -> `renderClassesView()` / `renderDriversView()` / `renderCarsView()` ### Event / Race Setup outer page Render chain: - `app.js` -> `renderEventWorkspace(mode)` - `event_workspace_controller.js` -> `renderEventWorkspaceMarkup()` from `event_views.js` ### Event / Race Setup manager (`Hantera`) Render chain: - `app.js` -> `renderEventManager(eventId)` - `event_manager_controller.js` -> `renderEventManagerMarkup()` from `event_manager_view.js` ### Timing / Judging Render chain: - `app.js` -> `renderView()` -> `renderTimingView()` / `renderJudgingView()` from `timing_views.js` ### Overlay admin page Render chain: - `app.js` -> `renderOverlay()` -> `renderOverlayPageView()` from `misc_views.js` ### Public overlays Render chain: - `app.js` -> `renderOverlay()` -> helpers in `overlays.js` ### Settings Render chain: - `app.js` -> `renderView()` -> `renderSettings()` ## 8. End-to-End Flows ## 8.1 Page load ```mermaid sequenceDiagram participant B as Browser participant A as app.js participant R as runtime_services.js participant S as server.js participant DB as SQLite B->>A: load app.js A->>A: loadState() A->>R: hydrateFromBackend() R->>S: GET /api/state S->>DB: read app_state DB-->>S: stored JSON S-->>R: state payload R-->>A: applyPersistedState() A->>A: renderNav()/renderView() ``` ## 8.2 Event manager save flow ```mermaid sequenceDiagram participant UI as event_manager_view.js participant C as event_manager_controller.js participant A as app.js participant R as runtime_services.js participant S as server.js UI->>C: submit form C->>C: build normalized object/config C->>A: update canonical state array C->>A: saveState() A->>R: schedule/sync backend state R->>S: POST /api/state C->>A: renderView()/rerenderEventManager() ``` ## 8.3 Decoder passing flow ```mermaid sequenceDiagram participant D as Decoder/AMMC WS participant DR as decoder_runtime.js participant TL as timing_logic.js participant A as app.js participant S as server.js participant DB as SQLite D->>DR: websocket message DR->>A: resolve session/driver/car/team DR->>TL: lap validation + result mutation inputs DR->>A: update resultsBySession A->>S: POST /api/passings S->>DB: insert passing A->>A: rerender timing/overlay ``` ## 8.4 Public overlay flow ```mermaid sequenceDiagram participant O as Public overlay page participant A as app.js participant R as runtime_services.js participant S as server.js participant DB as SQLite O->>A: load public overlay route A->>R: hydrateFromBackend() R->>S: GET /api/state S->>DB: read app_state DB-->>S: stored JSON S-->>R: state snapshot A->>A: render overlay view A->>R: overlay sync polling ``` ## 9. Dependency/Ownership Matrix | File | Type | Direct imports | Imported by | Owns DOM? | Owns state? | Mutates canonical state? | |---|---|---|---|---|---|---| | `src/app.js` | shell | many | browser entry | yes | yes | yes | | `src/runtime_services.js` | runtime helpers | none | `app.js` | no | no | indirectly via injected setters | | `src/decoder_runtime.js` | runtime helpers | none | `app.js` | no | no | yes, through injected state access | | `src/timing_logic.js` | logic | none | `app.js` | no | no | yes, on session results through injected access | | `src/judging_logic.js` | logic | none | `app.js` | no | no | yes, on session results through injected access | | `src/event_race_logic.js` | logic | none | `app.js` | no | no | no, returns normalized/generated data | | `src/race_render_helpers.js` | render helpers | none | `app.js` | yes | no | no | | `src/race_setup_ui.js` | render helpers | none | `app.js` | yes | no | no | | `src/event_common.js` | shared helpers | none | `app.js` | mixed | no | no | | `src/event_views.js` | view | none | `event_workspace_controller.js` | yes | no | no | | `src/event_workspace_controller.js` | controller | `event_views.js` | `app.js` | yes | no | yes | | `src/event_manager_view.js` | view | none | `app.js` | yes | no | no | | `src/event_manager_controller.js` | controller | none | `app.js` | yes | no | yes | | `src/overlays.js` | view/render | none | `app.js` | yes | no | no | | `src/timing_views.js` | view/render | none | `app.js` | yes | no | no | | `src/core_views.js` | view/render | none | `app.js` | yes | no | no | | `src/misc_views.js` | view/render | none | `app.js` | yes | no | no | | `src/settings.js` | view/render | none | `app.js` | yes | no | no | | `server.js` | backend runtime | node core + deps | npm/node | n/a | backend process state | yes | | `scripts/serverctl.js` | process control | node core | npm scripts | n/a | no | pid/log files only | ## 10. Symptom-Based Entry Points ### Value reverts after save Open first: - `src/event_manager_controller.js` - `src/event_race_logic.js` - `src/app.js` ### Button click does nothing Open first: - matching `*_view.js` - matching `*_controller.js` ### Overlay/timing crashes with missing helper Open first: - `src/app.js` - target module signature / deps object ### Wrong drivers in race/team flow Open first: - `src/event_race_logic.js` - `src/event_manager_controller.js` - `src/event_manager_view.js` ### Decoder online but no laps Open first: - `src/decoder_runtime.js` - `src/timing_logic.js` - `src/runtime_services.js` ### Public overlay works differently from admin overlay Open first: - `src/overlays.js` - `src/misc_views.js` - `src/runtime_services.js` - `server.js` ## 11. Frontend vs Backend Boundary Start in frontend when the problem is: - form save/revert - button binding - modal behavior - wrong driver/team/session visibility - leaderboard/standings math - overlay layout or missing values Start in backend when the problem is: - persisted backend state endpoint - AMMC process control - public overlay route resolution - SQLite schema/data corruption - PDF export endpoint - app version/file-watch reload ## 12. Maintenance Discipline Whenever a new module is added or a major responsibility moves: - update this file - add direct imports - note who calls the module - note whether it mutates canonical state - note whether the module owns DOM or only logic This file should stay strict and technical. It is a debugging map, not user documentation. ## 13. Known Fragile Paths These are the parts of the codebase that have historically been most likely to regress after refactors. ### 13.1 `src/event_manager_controller.js` Why fragile: - owns many independent save paths in one controller - mixes DOM binding, normalization, state updates, and rerender logic - uses both `state.events` and `state.sessions` - depends on a large injected dependency set from `app.js` High-risk operations: - race format save - preset apply/save/delete - session create/edit/delete - team create/edit/delete - assignment actions - grid actions Typical failures: - button click works but value reverts after rerender - wrong scope for team drivers vs race participants - session type saves wrong value - modal save button stops submitting after markup change Debug rule: - first verify the handler fires - then verify canonical array update - then verify rerender reads the updated object ### 13.2 `src/event_race_logic.js` Why fragile: - contains defaults and normalization logic - owns race presets and wizard defaults - decides driver/team visibility in race flows High-risk functions: - `normalizeEvent(...)` - `buildRaceFormatConfigFromForm(...)` - `getEventDrivers(...)` - `getTeamDriverPool(...)` - `generateQualifyingForRace(...)` - `generateFinalsForRace(...)` Typical failures: - saved values snap back to defaults - wrong driver lists in race/team setup - preset application overwrites custom changes Debug rule: - check whether normalization is reintroducing fallback values - check whether the controller passes the correct event object into these helpers ### 13.3 `src/app.js` dependency wiring Why fragile: - almost every extracted module still depends on `app.js` wiring - one missing helper injection can break a whole screen Typical failures: - `... is not defined` - `... is not a function` - only one overlay mode breaks while others work - a view loads but one action silently fails Debug rule: - inspect the wrapper in `app.js` - inspect the called module signature - verify the helper is passed through exactly once ### 13.4 `src/timing_logic.js` Why fragile: - central math for leaderboard, standings, lap validity, and team timing - used by normal race, qualifying, finals, free practice, and team race High-risk functions: - `getSessionLapWindow(...)` - `buildLeaderboard(...)` - `buildQualifyingStandings(...)` - `buildTeamRaceStandings(...)` - `buildTeamStintLog(...)` Typical failures: - invalid laps counted or hidden incorrectly - gap/delta metrics wrong - team standings or stints split wrong Debug rule: - confirm incoming session config first - then inspect row/metric calculation - only after that inspect rendering ### 13.5 `src/runtime_services.js` Why fragile: - multiple background loops interact with UI and backend state - can produce symptoms that look unrelated to runtime logic Typical failures: - decoder online/offline after refresh - page reloads at the wrong time - overlay timer/preview stops moving - stale backend state overwrites local changes Debug rule: - inspect timers and polling before touching screen renderers ## 14. Function-Level Map This section lists the most important functions to know in each critical file. ### 14.1 `src/app.js` #### Bootstrap and persistence - `init()` - application startup - runs initial render/bootstrap flow - `seedDefaultData()` - seeds initial local data when needed - `loadState()` - reads persisted frontend state - `saveState(options)` - persists frontend state and schedules backend sync - `buildPersistableState()` - selects what is written to backend/local persistence #### Routing and rendering - `renderNav()` - main side navigation render - `renderView()` - main route switch for current page - `renderEventWorkspace(mode)` - thin wrapper into event workspace controller - `renderEventManager(eventId)` - thin wrapper into event manager controller - `renderOverlay()` - overlay/admin overlay dispatch point #### Normalization and formatting - `normalizeDriver(...)` - `normalizeCar(...)` - `normalizeSession(...)` - `formatLap(...)` - `formatPredictedLapDelta(...)` - `formatCountdown(...)` - `formatElapsedClock(...)` - `formatRaceClock(...)` - `formatSeedMetric(...)` #### Export/print helpers - `buildRacePackagePayload(...)` - `importRacePackagePayload(...)` - `openPrintWindow(...)` - `requestPdfExport(...)` - `exportRaceStartListsPdf(...)` - `exportRaceResultsPdf(...)` - `exportTeamRaceResultsPdf(...)` - `exportSessionHeatSheetPdf(...)` #### Decoder/session helpers still local - `persistPassingToBackend(...)` - `validateTrackSessionForStart(...)` - `findDuplicateSessionTransponders(...)` - `autoAssignTrackSession(...)` ### 14.2 `src/event_manager_controller.js` #### Manager setup - `renderEventManagerView(context)` - main controller entry - resolves event/session/team/current manager state - binds all forms and buttons in `Hantera` #### Important internal controller patterns - `refreshManager()` - full rerender helper used after saves - `updateEvent(updater)` - canonical `state.events` update path #### High-risk save sections inside this controller - branding save form submit - race format form submit - preset apply/save/delete handlers - session create form submit - session edit form submit - team create form submit - team edit form submit - team delete action If one of these breaks, inspect this controller first. ### 14.3 `src/event_race_logic.js` #### Normalization - `normalizeEvent(...)` - central race/event normalization - `normalizeBrandingConfig(...)` - `normalizeRaceTeam(...)` - `normalizeStoredRacePreset(...)` #### Race format / preset logic - `getRaceFormatPresets(...)` - `applyRaceFormatPreset(...)` - `buildRaceFormatConfigFromForm(...)` #### Race wizard logic - `buildDefaultRaceWizardDraft(...)` - `getRaceWizardPreset(...)` - `applyRaceWizardPresetDefaults(...)` - `buildRaceSessionsFromWizard(...)` - `getRaceWizardSessionPlan(...)` #### Pool/scoping logic - `getEventDrivers(...)` - `getEventTeams(...)` - `getTeamDriverPool(...)` - `findEventTeamForPassing(...)` #### Race generation logic - `generateQualifyingForRace(...)` - `reseedUpcomingQualifying(...)` - `generateFinalsForRace(...)` - `applyBumpsForRace(...)` ### 14.4 `src/timing_logic.js` #### Session state - `getActiveSession(...)` - `getSessionTargetMs(...)` - `getSessionLapWindow(...)` - `getSessionTiming(...)` #### Leaderboard core - `ensureSessionResult(...)` - `buildLeaderboard(...)` - `getCompetitorPassings(...)` - `getCompetitorSeedMetric(...)` - `formatLeaderboardGap(...)` - `formatLapDelta(...)` #### Standings - `buildPracticeStandings(...)` - `buildQualifyingStandings(...)` - `buildFinalStandings(...)` - `buildTeamRaceStandings(...)` - `buildTeamStintLog(...)` #### Grid - `getSessionGridEntries(...)` - `getSessionGridOrder(...)` - `ensureSessionDriverOrder(...)` ### 14.5 `src/runtime_services.js` #### Backend sync/runtime - `hydrateFromBackendHelper(...)` - `syncStateToBackendHelper(...)` - `scheduleBackendSyncHelper(...)` - `pingBackendHelper(...)` - `checkAppVersionHelper(...)` - `startAppVersionPollingHelper(...)` #### Overlay/runtime refresh - `startOverlaySyncHelper(...)` - `startOverlayRotationHelper(...)` - `startOverlayLiveRefreshHelper(...)` - `tickClockHelper(...)` - `handleSessionTimerTickHelper(...)` #### AMMC/backend control - `loadAmmcConfigFromBackendHelper(...)` - `saveAmmcConfigToBackendHelper(...)` - `refreshAmmcStatusHelper(...)` - `startManagedAmmcHelper(...)` - `stopManagedAmmcHelper(...)` #### Audio/event feed - `pushOverlayEventHelper(...)` - `announcePassingHelper(...)` - `announceRaceFinishedHelper(...)` - `speakTextHelper(...)` ### 14.6 `server.js` #### Backend API and persistence - `initSchema()` - initializes SQLite schema - `watchAppFiles()` - bumps app version on file changes - `loadAmmcConfig()` / `saveAmmcConfig()` - AMMC config file persistence - `normalizeAmmcConfig(...)` - validates/stabilizes AMMC config - `buildAmmcStatus()` - current AMMC process status object - `startAmmcProcess()` / `stopAmmcProcess()` - process lifecycle - PDF export helpers below `/api/export/pdf` #### Critical backend routes - `GET /api/state` - `POST /api/state` - `POST /api/passings` - `GET/POST /api/ammc/*` - `GET /api/app-version` These are the first backend touchpoints to inspect if the frontend bug crosses the API boundary. ## 15. Frontend State Schema This section describes the practical shape of the frontend state as persisted by `buildPersistableState()` in `src/app.js`. ## 15.1 Persisted top-level shape ```js { classes: Class[], drivers: Driver[], cars: Car[], events: Event[], sessions: Session[], resultsBySession: Record, activeSessionId: string, settings: Settings } ``` ## 15.2 `Class` ```js { id: string, name: string } ``` Produced/normalized by: - `normalizeImportedClass()` in `src/app.js` ## 15.3 `Driver` ```js { id: string, name: string, classId: string, transponder: string, brand: string } ``` Normalized by: - `normalizeDriver()` in `src/app.js` Used by: - `state.drivers` - race participant selection - team selection - decoder passing resolution - exports/imports ## 15.4 `Car` ```js { id: string, name: string, transponder: string, brand: string } ``` Normalized by: - `normalizeCar()` in `src/app.js` Used by: - `state.cars` - sponsor event shared cars - team race car selection - decoder passing resolution ## 15.5 `Event` Practical shape: ```js { id: string, name: string, date: string, classId: string, mode: "track" | "race", branding: BrandingConfig, raceConfig: RaceConfig } ``` Normalized by: - `normalizeEvent()` in `src/event_race_logic.js` ### `BrandingConfig` ```js { brandName: string, brandTagline: string, pdfFooter: string, pdfTheme: "" | "classic" | "minimal" | "motorsport", logoDataUrl: string } ``` Normalized by: - `normalizeBrandingConfig()` in `src/event_race_logic.js` ### `RaceConfig` Practical normalized shape: ```js { presetId: string, qualifyingScoring: "points" | "best", qualifyingRounds: number, carsPerHeat: number, qualDurationMin: number, qualStartMode: string, qualSeedLapCount: number, qualSeedMethod: "best_sum" | "average" | "consecutive", countedQualRounds: number, qualifyingPointsTable: "rank_low" | "field_desc" | "ifmar", qualifyingTieBreak: "rounds" | "best_lap" | "best_round", carsPerFinal: number, finalLegs: number, countedFinalLegs: number, finalDurationMin: number, finalStartMode: string, followUpSec: number, minLapMs: number, maxLapMs: number, bumpCount: number, reserveBumpSlots: boolean, driverIds: string[], participantsConfigured: boolean, finalsSource: "practice" | "qualifying", teams: RaceTeam[] } ``` ### `RaceTeam` ```js { id: string, name: string, driverIds: string[], carIds: string[] } ``` Normalized by: - `normalizeRaceTeam()` in `src/event_race_logic.js` ## 15.6 `Session` Practical shape: ```js { id: string, eventId: string, name: string, type: string, durationMin: number, followUpSec: number, startMode: string, status: string, startedAt: number | null, endedAt: number | null, staggerGapSec: number, maxCars: number, seedLapCount: number, seedMethod: string, generated: boolean, driverIds: string[], manualGridIds: string[], gridLocked: boolean, ...additional race/session fields } ``` Normalized by: - `normalizeSession()` in `src/app.js` Used by: - race setup - timing view - team race - overlay - exports/printouts ## 15.7 `resultsBySession` Practical shape: ```js { [sessionId: string]: { competitors: { [rowKey: string]: { key: string, driverId?: string, driverName?: string, carId?: string, carName?: string, teamId?: string, teamName?: string, transponder: string, laps: number, bestLapMs: number, lastLapMs: number, totalMs: number, manualLapAdjustment?: number, manualTimeAdjustmentMs?: number, invalidPending?: boolean, invalidReason?: string, ...runtime-derived leaderboard fields } }, passings: Passing[], adjustments?: Adjustment[] } } ``` This bucket is the most dynamic part of state and is mutated heavily during live timing and judging. ## 15.8 `Settings` Practical shape contains at least: - theme - language - backendUrl - decoderUrl / websocket URL - speaker/audio flags - OBS overlay settings - branding defaults - race preset library - AMMC/managed runtime preferences This shape is broad and partly feature-driven. It is normalized through `applyPersistedState()` and related helpers rather than one single strict schema function. ## 16. Backend API Contract This section describes the practical contract exposed by `server.js`. ## 16.1 `GET /api/health` Response: ```json { "ok": true, "dbPath": "/abs/path/to/data/rc_timing.sqlite" } ``` Use for: - health check - confirming backend is alive ## 16.2 `GET /api/app-version` Response: ```json { "revision": 1, "updatedAt": "2026-03-30T...Z" } ``` Use for: - frontend file-change reload detection - app version polling in runtime services ## 16.3 `GET /api/state` Success response: ```json { "state": { /* persisted frontend state */ }, "updatedAt": "2026-03-30T...Z" } ``` Empty response when no state exists: ```json { "state": null, "updatedAt": null } ``` Error response: ```json { "error": "Stored app state is invalid JSON" } ``` Use for: - frontend hydration - public overlay hydration ## 16.4 `POST /api/state` Request body: - full persistable frontend state object - effectively the output of `buildPersistableState()` Success response: ```json { "ok": true, "updatedAt": "2026-03-30T...Z" } ``` Error response: ```json { "error": "Expected JSON body" } ``` Important note: - this endpoint stores the full frontend state snapshot in `app_state` - it is not a patch endpoint ## 16.5 `POST /api/passings` Request body: ```json { "sessionId": "session_...", "passing": { "timestamp": 1711111111111, "transponder": "1234567", "driverId": "driver_...", "driverName": "Name", "carId": "car_...", "carName": "Car", "strength": -53.2, "loopId": "1", "resend": false, "...": "raw decoder-derived fields may exist" }, "sessionResult": { "...": "optional current resultsBySession[sessionId] snapshot" } } ``` Success response: ```json { "ok": true } ``` Error response: ```json { "error": "Expected { sessionId, passing }" } ``` Important note: - if `sessionResult` is provided, backend also hot-updates `app_state.resultsBySession[sessionId]` - this is important for overlays hydrating from backend instead of direct decoder websocket ## 16.6 `GET /api/passings` Query parameters: - `sessionId` optional - `limit` optional, max `1000` Response: ```json { "rows": [ { "id": 1, "session_id": "session_...", "timestamp_ms": 1711111111111, "transponder": "1234567", "driver_id": "driver_...", "driver_name": "Name", "car_id": "car_...", "car_name": "Car", "strength": -53.2, "loop_id": "1", "resend": 0, "raw_json": "{...}", "created_at": "2026-03-30T...Z" } ] } ``` Use for: - reading persisted passings history - debugging session passings ## 16.7 `GET /api/ammc/config` Response: ```json { "config": { /* normalized AMMC config */ }, "status": { /* buildAmmcStatus() output */ } } ``` ## 16.8 `POST /api/ammc/config` Request body: - config object to normalize and save Success response: ```json { "ok": true, "config": { /* normalized config */ }, "status": { /* buildAmmcStatus() output */ } } ``` Error response: ```json { "error": "...", "status": { /* buildAmmcStatus() output */ } } ``` ## 16.9 `GET /api/ammc/status` Response: - current output of `buildAmmcStatus()` Practical fields include: - whether process is running - pid - timestamps - last exit code/signal - last error - recent log lines - resolved executable path - current normalized config ## 16.10 `POST /api/ammc/start` Success response: ```json { "ok": true, "status": { /* buildAmmcStatus() */ } } ``` Error response: ```json { "error": "...", "status": { /* buildAmmcStatus() */ } } ``` ## 16.11 `POST /api/ammc/stop` Success response: ```json { "ok": true, "status": { /* buildAmmcStatus() */ } } ``` Error response: ```json { "error": "...", "status": { /* buildAmmcStatus() */ } } ``` ## 16.12 `POST /api/export/pdf` Request body: ```json { "title": "...", "subtitle": "...", "brandName": "...", "brandTagline": "...", "footer": "...", "theme": "classic|minimal|motorsport", "logoDataUrl": "data:image/jpeg;base64,...", "filename": "results.pdf", "sections": [ { "title": "...", "headers": ["..."], "rows": [["...", "..."]] } ] } ``` Success response: - binary PDF with `Content-Type: application/pdf` Error response: ```json { "error": "..." } ``` ## 16.13 Public overlay routes Routes: - `/public-overlay` - `/public-overlay/:mode` Behavior: - if `PUBLIC_OVERLAY_TOKEN` is set, query param `token` must match - otherwise returns `403 Forbidden` - on success serves `index.html` Use for: - public OBS/browser-source overlays without exposing admin path directly ## 17. Regression History / Known Gotchas This section captures bugs already seen in this codebase after the modular split. ## 17.1 Missing dependency injection from `app.js` Observed failures: - `DEFAULT_OBS_OVERLAY_SETTINGS` before initialization - `OBS_LAYOUTS` before initialization - `formatLap is not defined` - `formatPredictedLapDelta is not a function` - `getPassingValidationLabel` dependency missing Pattern: - helper/module was extracted or wrapper changed - consumer module signature expected injected helper - `app.js` no longer passed it through correctly Mitigation: - whenever a module crashes after split, compare: - imported function in `app.js` - wrapper function in `app.js` - destructured params in target module ## 17.2 Local mutation vs canonical state update Observed failures: - race format values reverted after save - team create/edit/delete looked successful but UI snapped back - branding changes could be lost after rerender Pattern: - local `event` object mutated - canonical `state.events` was not updated via array replacement Mitigation: - in controllers, prefer `updateEvent(...)` / array replacement pattern - do not rely on mutating local references inside a controller ## 17.3 Event manager save fragility Observed failures: - session type saved as `open practice` instead of selected type - team save buttons did nothing - team save worked for create but not edit - race format save reverted min/max values Pattern: - `Hantera` mixes many forms, rerenders, and local references in one controller Mitigation: - treat `src/event_manager_controller.js` as a high-risk file - verify submit source, selected DOM node, canonical update, then rerender in that order ## 17.4 Team race pool confusion Observed failures: - team race showed class-only drivers when user wanted all drivers - race participant list and team pool affected each other - selecting team members unexpectedly affected participant selection or vice versa Pattern: - using the same pool/list for: - race participants - team selection Mitigation: - keep race participant scope and team pool scope separate - document clearly whether team race uses: - all drivers - or only race participants ## 17.5 Overlay/public route issues Observed failures: - public overlay loaded plain page without JS/CSS behavior - overlay preview timer in main app did not tick - OBS config dropdowns closed immediately - overlay/logo/theme inconsistencies Pattern: - nested routes and overlay runtime had different bootstrap needs Mitigation: - prefer absolute asset paths in `index.html` - keep overlay refresh timers separate from admin rerenders - test: - admin overlay page - popup overlay - public overlay - OBS overlay ## 17.6 Backend hydration overwriting local edits Observed failures: - edits in admin pages could appear to save and then revert Pattern: - local dirty state vs backend hydration/sync race Mitigation: - treat backend hydration and local dirty flags as runtime concerns - inspect `src/runtime_services.js` together with the affected save path ## 18. Recommended Future Additions If the project grows further, the next useful additions to this document are: - exact `resultsBySession` competitor row schema - exact `settings` schema snapshot - SQLite table contract section - controller-to-view ID map for critical forms/buttons - test checklist per module