1784 lines
39 KiB
Markdown
1784 lines
39 KiB
Markdown
# 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<string, SessionResult>,
|
|
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
|