Bring the architecture doc back up to date with this session's changes
Closes both open "Documentation maintenance" roadmap items. Used a research-only Explore pass to get a concrete diff against the actual code before editing, rather than guessing what was stale. Documented: src/track_layouts_logic.js (previously unmentioned anywhere), buildEventSchedule, the Settings/Event Manager tab layouts, new state fields (raceConfig.scheduleStartAt/scheduleGapMin, Session.trackLayoutId, top-level trackLayouts/audioEventLog), a real SQLite table contract section, a full field-by-field Settings shape, several previously-undocumented backend routes, and the complete list of files tests/browser_smoke.js actually invokes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -219,6 +219,15 @@ Uses:
|
||||
- `logs/server.out.log`
|
||||
- `logs/server.err.log`
|
||||
|
||||
### `scripts/record-video-guides.mjs` and `scripts/capture-screenshots.mjs`
|
||||
Role:
|
||||
- documentation tooling - not run in production, not imported by `server.js` or any `src/*.js` module
|
||||
|
||||
Owns:
|
||||
- both spin up an isolated `server.js` (temp `RC_TIMING_DATA_DIR`) plus a headless Chrome instance driven over the DevTools Protocol via a raw `WebSocket`, the same harness pattern `tests/browser_smoke.js` uses - own temp SQLite dir, own browser profile, never touches a real installation
|
||||
- `record-video-guides.mjs`: narrated MKV screen-capture clips for new-user video guides (`docs/videos/*.mkv`, linked from `README.md`/`README.sv.md` and the in-app Guide page). Each clip is a list of `{ text, action }` steps; narration audio is synthesized up front via Piper (`pipx install piper-tts` plus a downloaded voice model - not espeak-ng, which was tried first and found barely intelligible), so each line's real spoken duration is known before recording, and the final audio track is built from the actual measured wall-clock offset each line started at (`ffmpeg`'s `adelay`/`amix`), not an idealized schedule - avoids drifting out of sync with page-load/network latency over a 30+ second clip. A `prepare()` hook lets a clip spin up a live dependency (e.g. a fake decoder socket via `tests/browser_fake_decoder.js`) before the app is seeded.
|
||||
- `capture-screenshots.mjs`: single-frame JPEG captures (`docs/screenshots/*.jpg`, 1600x900 CSS at `deviceScaleFactor: 2`) against one shared seeded demo dataset ("JMK RB" branding, a finished "Klubbmästare Race 4" event) so screenshots taken at different times stay visually consistent with each other.
|
||||
|
||||
### Windows bootstrap
|
||||
|
||||
Files:
|
||||
@@ -366,6 +375,7 @@ Owns:
|
||||
- event drivers / teams / team driver pool
|
||||
- qualifying/finals generation and bumps
|
||||
- race summary/manage status logic
|
||||
- `buildEventSchedule(event, sessions, { getSessionSortWeight })`: cascades each race-mode session's planned clock time from `event.raceConfig.scheduleStartAt`, in running order (reusing `getSessionSortWeight`'s practice→qualifying→finals ordering from `race_render_helpers.js`), each session's own duration/follow-up plus `scheduleGapMin` between sessions. Once a session has actually started/finished, everything after it re-anchors to that real time - a late start or an overrun (a pause's wall-clock time is already baked into a session's real `endedAt`) pushes the rest of the day forward automatically, with no separate "reschedule" step. Returns `[]` when no `scheduleStartAt` is set. Consumed by Event Manager's Schedule tab and by `buildRaceStartListsHtml` (prints the planned time next to each session).
|
||||
|
||||
### `src/race_render_helpers.js`
|
||||
Type:
|
||||
@@ -419,27 +429,32 @@ Type:
|
||||
- `Hantera` markup
|
||||
|
||||
Owns:
|
||||
- for a race-mode event: a 4-tab layout (`.manage-tabs`: Setup/Participants & teams, Format, Live & results, Schedule & print) wrapping the panels below in `<div data-manage-panel="...">` groups - session-plan (create/list sessions) and branding stay always visible above the tabs regardless of the active one, as does the permanent 4-card step-status summary (Setup/Format/Generate/Live)
|
||||
- session forms
|
||||
- race format panel
|
||||
- branding panel
|
||||
- team section
|
||||
- race format panel (Format tab)
|
||||
- branding panel (always visible)
|
||||
- team section (Setup tab)
|
||||
- sponsor tools
|
||||
- assignments section
|
||||
- Schedule panel (Schedule tab): start-time + gap-between-sessions form, and (once set) a table of every session's planned/actual time and a "Delayed" flag, built from `buildEventSchedule`
|
||||
- summary/actions panels
|
||||
- team/session modals
|
||||
- track/sponsor (non-race-mode) events skip the tabs entirely and keep the simple, untabbed layout - they never had enough content to need them
|
||||
|
||||
### `src/event_manager_controller.js`
|
||||
Type:
|
||||
- `Hantera` controller
|
||||
|
||||
Owns:
|
||||
- `activeManageTab` (module-scoped, same pattern as `settings.js`'s `activeSettingsTab`) and `MANAGE_TAB_TARGETS`, a lookup from a quick-jump target id (the step-status cards, the race-summary's warning links) to the tab it now lives inside, so jumping to it switches tabs first - `scrollIntoView` on an element behind an inactive tab does nothing on its own
|
||||
- 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
|
||||
- print/export/grid actions (opening a session's grid editor also switches to the Live tab, since that's where the editor renders)
|
||||
- schedule form save/clear (`scheduleForm`/`scheduleClear`)
|
||||
|
||||
### `src/overlays.js`
|
||||
Type:
|
||||
@@ -502,6 +517,20 @@ Imported by:
|
||||
- `src/training_logic.js` for class-scoped lap aggregation
|
||||
- `src/training_views.js` for person and class selectors
|
||||
|
||||
### `src/track_layouts_logic.js`
|
||||
Type:
|
||||
- pure track-layout identity/lookup helpers
|
||||
|
||||
Owns:
|
||||
- normalizing a single layout (`{ id, label, startedAt }`) and the installation's whole layout list, sorted by `startedAt`
|
||||
- resolving which layout was active at a given timestamp (`getTrackLayoutIdAt`) - layouts are contiguous, so a layout's implicit end is the next one's `startedAt`, with no separate stop field
|
||||
- default/next label generation (`getTrackLayoutLabel`, `nextTrackLayoutLabel` - "Layout N")
|
||||
|
||||
Imported by:
|
||||
- `src/app.js` to stamp `session.trackLayoutId` at the moment a session starts (never recomputed later, so editing the layout list afterwards can't retroactively reclassify old results) and to scope `getTrackRecordMs` (`src/timing_logic.js`) and training pace/consistency stats (`src/training_logic.js`, `server/public_training_profile.js`) to the current layout
|
||||
- `src/settings.js` for the "Banlayouter" panel (add/list/delete, delete blocked while any session still references the layout)
|
||||
- `src/results_archive_view.js` to show which layout a completed session ran on
|
||||
|
||||
### `src/misc_views.js`
|
||||
Type:
|
||||
- misc page renderers
|
||||
@@ -515,7 +544,8 @@ Type:
|
||||
- settings page renderer
|
||||
|
||||
Owns:
|
||||
- backend/decoder settings UI
|
||||
- the page's 4-tab layout (`.settings-tabs`: Connection, Audio, Appearance & overlay, System & backup) - `activeSettingsTab` is module-scoped state, not part of `app.js`'s state object, so the selected tab survives a re-render (e.g. after a save) within the same page load but resets on navigation
|
||||
- backend/decoder settings UI (Connection tab) and the "Banlayouter" track-layout panel (Appearance & overlay tab)
|
||||
- OBS settings UI
|
||||
- branding/audio settings UI
|
||||
- import/export settings UI
|
||||
@@ -766,6 +796,7 @@ This wrapper layer is one of the most important parts of the architecture.
|
||||
- `reseedUpcomingQualifying`
|
||||
- `generateFinalsForRace`
|
||||
- `applyBumpsForRace`
|
||||
- `buildEventSchedule`
|
||||
|
||||
## 4.2 Timing wrappers
|
||||
- `getSessionTypeLabel`
|
||||
@@ -1201,6 +1232,8 @@ sequenceDiagram
|
||||
| `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/driver_profile_logic.js` | pure logic | none | `app.js`, `core_views.js`, `training_logic.js`, `training_views.js` | no | no | no |
|
||||
| `src/track_layouts_logic.js` | pure logic | none | `app.js` | no | no | no |
|
||||
| `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 |
|
||||
@@ -1627,10 +1660,28 @@ This section describes the practical shape of the frontend state as persisted by
|
||||
resultsBySession: Record<string, SessionResult>,
|
||||
training: TrainingState,
|
||||
activeSessionId: string,
|
||||
settings: Settings
|
||||
settings: Settings,
|
||||
trackLayouts: TrackLayout[],
|
||||
audioEventLog: AudioEventLogEntry[]
|
||||
}
|
||||
```
|
||||
|
||||
`trackLayouts` and `audioEventLog` are both append/edit-only lists with no other top-level state referencing them by shape (sessions reference a layout only by `trackLayoutId`, a plain string - see 15.6). `audioEventLog` records every triggered race-audio cue (`{ event, sessionId, ts, ... }`, see section 20) for diagnostics and the "recent audio events" checks in the browser test suite; it is not surfaced in any admin UI.
|
||||
|
||||
## 15.1a `TrackLayout`
|
||||
|
||||
```js
|
||||
{
|
||||
id: string,
|
||||
label: string, // defaults to "Layout N", user-renamable
|
||||
startedAt: number // epoch ms; layouts are contiguous, so a layout's implicit
|
||||
// end is simply the next layout's startedAt - there is no
|
||||
// separate end/stop field to keep in sync
|
||||
}
|
||||
```
|
||||
|
||||
Normalized by `normalizeTrackLayout`/`normalizeTrackLayouts` in `src/track_layouts_logic.js`. Managed from Settings' "Banlayouter" panel; deleting a layout is blocked while any session still references it via `trackLayoutId`. See that module's doc entry in section 2.2 for how lookups/scoping work.
|
||||
|
||||
## 15.2 `Class`
|
||||
|
||||
```js
|
||||
@@ -1762,10 +1813,14 @@ Practical normalized shape:
|
||||
participantsConfigured: boolean,
|
||||
finalsSource: "practice" | "qualifying",
|
||||
teams: RaceTeam[]
|
||||
practiceOnly: boolean
|
||||
practiceOnly: boolean,
|
||||
scheduleStartAt: number | null, // epoch ms; null when no time-of-day schedule is set
|
||||
scheduleGapMin: number // minutes between sessions, defaults to 3
|
||||
}
|
||||
```
|
||||
|
||||
`scheduleStartAt`/`scheduleGapMin` drive `buildEventSchedule()` (section 4.1, and its doc entry under `src/event_race_logic.js` in section 2.2) - the race day Schedule tab's cascading planned-time table. Set/cleared from Event Manager's Schedule tab (`scheduleForm`/`scheduleClear` in `event_manager_controller.js`).
|
||||
|
||||
`practiceOnly: true` identifies the simplified Open Practice flow. It deliberately has no class scope or configured participants, uses an `open_practice` session, ignores `durationMin` in timing logic, and must not require qualifying/finals in race summary warnings.
|
||||
|
||||
### `RaceTeam`
|
||||
@@ -1806,6 +1861,10 @@ Practical shape:
|
||||
driverIds: string[],
|
||||
manualGridIds: string[],
|
||||
gridLocked: boolean,
|
||||
trackLayoutId: string, // "" if no track layout is configured; stamped once at
|
||||
// commitStart() to whichever layout is active at that
|
||||
// moment (see src/track_layouts_logic.js) and never
|
||||
// recomputed afterwards
|
||||
...additional race/session fields
|
||||
}
|
||||
```
|
||||
@@ -1976,21 +2035,88 @@ Migration behavior:
|
||||
|
||||
## 15.9 `Settings`
|
||||
|
||||
Practical shape contains at least:
|
||||
- theme
|
||||
- language
|
||||
- backendUrl
|
||||
- decoderUrl / websocket URL
|
||||
- speaker/audio flags
|
||||
- `audioEnabled`, `passingSoundMode`, `startSoundEnabled`, `finishSoundEnabled`
|
||||
- `finishVoiceEnabled` (legacy property name; controls start/finish voice announcements)
|
||||
- `speechVoiceUri`, `speechRate`, `speechPitch`
|
||||
- OBS overlay settings
|
||||
- branding defaults
|
||||
- race preset library
|
||||
- AMMC/managed runtime preferences
|
||||
Practical shape (field names as actually read/written in `src/settings.js` and `src/app.js` - there is no single strict schema function; it's normalized piecemeal through `applyPersistedState()` and each form's own submit handler):
|
||||
|
||||
This shape is broad and partly feature-driven. It is normalized through `applyPersistedState()` and related helpers rather than one single strict schema function.
|
||||
```js
|
||||
{
|
||||
// Connection tab
|
||||
theme: "dark" | "nord" | "light",
|
||||
language: "sv" | "en",
|
||||
backendUrl: string,
|
||||
wsUrl: string, // main decoder WebSocket URL
|
||||
splitDecoderEnabled: boolean,
|
||||
splitWsUrl: string,
|
||||
pitDecoderEnabled: boolean,
|
||||
pitEntryWsUrl: string,
|
||||
pitExitWsUrl: string,
|
||||
autoReconnect: boolean,
|
||||
publicBaseUrl: string,
|
||||
|
||||
// Audio tab
|
||||
audioEnabled: boolean,
|
||||
passingSoundMode: string,
|
||||
startSoundEnabled: boolean,
|
||||
finishSoundEnabled: boolean,
|
||||
finishVoiceEnabled: boolean, // legacy name; controls start/finish voice announcements
|
||||
speechVoiceUri: string,
|
||||
speechRate: number,
|
||||
speechPitch: number,
|
||||
speakerPassingCueEnabled: boolean,
|
||||
speakerLeaderCueEnabled: boolean,
|
||||
speakerFinishCueEnabled: boolean,
|
||||
speakerBestLapCueEnabled: boolean,
|
||||
speakerTop3CueEnabled: boolean,
|
||||
speakerSessionStartCueEnabled: boolean,
|
||||
raceAudio: RaceAudioSettings, // see section 20.3 - the newer, more complete race-audio config (countdown, lights, per-event effect/voice toggles, staggered-release timing)
|
||||
|
||||
// Appearance & overlay tab
|
||||
clubName: string,
|
||||
clubTagline: string,
|
||||
pdfFooter: string,
|
||||
pdfTheme: "" | "classic" | "minimal" | "motorsport",
|
||||
logoDataUrl: string,
|
||||
obsOverlay: object, // OBS overlay layout/theme/column config
|
||||
|
||||
// System & backup tab
|
||||
racePresets: StoredRacePreset[]
|
||||
}
|
||||
```
|
||||
|
||||
`trackLayouts` (section 15.1a) lives at the top level, not under `Settings`, even though it's managed from the Appearance & overlay tab alongside branding.
|
||||
|
||||
Settings' own 4-tab layout (`activeSettingsTab`, module-scoped - see the `src/settings.js` doc entry in 2.2) groups these fields as marked above; the underlying `state.settings` object itself has no such grouping, it's a flat bag of fields.
|
||||
|
||||
## 15.10 SQLite Table Contract
|
||||
|
||||
Defined in `initSchema()` in `server.js`. There are exactly two tables - almost everything else in the app (classes, drivers, cars, events, sessions, results, settings, training, track layouts) lives in one JSON blob, not relational rows; see "Persistence split" (section 5.1) for why.
|
||||
|
||||
```sql
|
||||
CREATE TABLE app_state (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1), -- always exactly one row
|
||||
state_json TEXT NOT NULL, -- the entire frontend state tree (section 15.1), as JSON
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE passings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
timestamp_ms INTEGER NOT NULL,
|
||||
transponder TEXT NOT NULL,
|
||||
driver_id TEXT,
|
||||
driver_name TEXT,
|
||||
car_id TEXT,
|
||||
car_name TEXT,
|
||||
strength REAL,
|
||||
loop_id TEXT,
|
||||
resend INTEGER NOT NULL DEFAULT 0,
|
||||
raw_json TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX idx_passings_session ON passings(session_id);
|
||||
CREATE INDEX idx_passings_timestamp ON passings(timestamp_ms);
|
||||
```
|
||||
|
||||
`app_state` is a single-row key/value store, not a normalized table - `POST /api/state` (16.4) always replaces the whole `state_json` blob (see the optimistic-locking discussion in ROADMAP.md, section 2). `passings` is the one durable, append-only record of every raw decoder message that isn't reachable through `/api/state` at all - only deletable via `POST /api/passings/purge` (16.6a), never overwritten by a state save. Its columns match `GET /api/passings`'s response shape (16.6) exactly, field for field.
|
||||
|
||||
## 16. Backend API Contract
|
||||
|
||||
@@ -2026,6 +2152,16 @@ Use for:
|
||||
- frontend file-change reload detection
|
||||
- app version polling in runtime services
|
||||
|
||||
## 16.2a `GET /api/update-status`, `POST /api/update/run`
|
||||
|
||||
`GET /api/update-status` (optional `?force=1` to bypass its own cache) runs `checkForUpdate()` and returns whether a newer commit is available on the configured git remote, cached for `UPDATE_CHECK_CACHE_MS`. Backs the in-app update checker (Settings, System & backup tab) and the sidebar "New version - update" link (ROADMAP.md "In-app update checker and one-click updater").
|
||||
|
||||
`POST /api/update/run` responds immediately (`{ ok: true, message: "..." }`) and spawns a **detached** `scripts/serverctl.js update` child process that outlives the request - it stops the currently running server, `git pull`s, runs `npm install`, then starts a fresh server, reusing the same stop/start machinery as `npm run restart`. The HTTP response returns before any of that happens, so the frontend cannot observe success/failure directly; it just expects the server to become briefly unreachable and then come back on the new version.
|
||||
|
||||
## 16.2b `GET /api/network/public-address`
|
||||
|
||||
Calls `https://api.ipify.org` server-side (5s timeout) and returns `{ address, suggestedBaseUrl }`. Used by Settings to suggest a public Backend URL when configuring remote access; the outbound call happens from the backend, not the browser, so it works even when the admin page itself is only reachable over LAN/VPN.
|
||||
|
||||
## 16.3 `GET /api/state`
|
||||
|
||||
Success response:
|
||||
@@ -2164,6 +2300,10 @@ Use for:
|
||||
- reading persisted passings history
|
||||
- debugging session passings
|
||||
|
||||
## 16.6a `POST /api/passings/purge`
|
||||
|
||||
Body: `{ "sessionIds": string[] }`. Deletes every raw `passings` row for the given session ids and responds `{ ok: true, deleted: <count> }`. The raw lap-crossing log is the one piece of session data not reachable at all through `/api/state` (it lives only in the append-only SQLite `passings` table, deliberately never included in the state blob a `POST /api/state` could overwrite - see section 17's "Backend hydration overwriting local edits" gotcha), so deleting an event/session or running the "Rensa data" clear-data flow calls this separately to avoid leaving orphaned rows behind with no UI ever pointing at them again.
|
||||
|
||||
## 16.7 `GET /api/ammc/config`
|
||||
|
||||
Response:
|
||||
@@ -2307,8 +2447,9 @@ Use for:
|
||||
|
||||
Routes:
|
||||
- page: `GET /public-training/:shareToken`
|
||||
- QR code for the page's own URL: `GET /public-training/qr/:shareToken`
|
||||
- data: `GET /public-training/api/:shareToken`
|
||||
- restricted marker action: `POST /public-training/api/:shareToken/markers`
|
||||
- restricted actions (same token authorization as the data route): `POST /public-training/api/:shareToken/markers`, `.../even-laps`, `.../step-training`, `.../exercise-sessions` - the driver-self-service actions (marking a lap, "Jämna varv" lap-evening, step-training logging, and guided exercise session logging) each read-modify-write only their own slice of `state.training` server-side rather than accepting a client's full state blob, so an idle mobile session can't revert unrelated admin edits the way a raw `POST /api/state` could (see section 17.6).
|
||||
|
||||
Authorization:
|
||||
- `shareToken` must match an enabled entry in `state.training.shares`
|
||||
@@ -2489,6 +2630,11 @@ npm test
|
||||
- personal timer aggregate and frozen-window calculations
|
||||
- clock-skew-safe query normalization and bounds
|
||||
|
||||
`tests/driver_profile_logic.test.js` covers `findTransponderConflict`:
|
||||
- flags a transponder shared across two different personal profiles
|
||||
- never flags one person's own multi-class entries sharing a transponder
|
||||
- names the conflicting driver and class in the returned conflict
|
||||
|
||||
`tests/race_timing_logic.test.js` covers:
|
||||
- race-format parsing, defaults, lap limits, participants, and teams
|
||||
- practice-only Open Practice bundle creation and persisted mode markers
|
||||
@@ -2499,6 +2645,15 @@ npm test
|
||||
- race lap windows and best/consecutive seed metrics
|
||||
- qualifying points, tie comparisons, and leaderboard gaps
|
||||
- multi-leg final standings and counted-leg scoring
|
||||
- `getEventDrivers` preserving participant-selection order (not directory creation order) and dropping stale/out-of-class ids
|
||||
- `getSessionSortWeight`'s practice-first ordering (`??` not `||`, since `open_practice`'s own weight is `0`)
|
||||
- `buildEventSchedule`'s cascading planned-time math, running-order sort, track-mode exclusion, and re-anchoring to a session's real start/end (including overtime from a pause)
|
||||
- `personalTimeUp` (staggered-mode personal window elapsed, excluding paused time) and the staggered-mode shared-clock extension
|
||||
|
||||
`tests/track_layouts_logic.test.js` covers:
|
||||
- layout normalization/sorting and label defaulting/renaming
|
||||
- `getTrackLayoutIdAt`'s contiguous "layout active at time X" lookup at its boundaries
|
||||
- `getTrackRecordMs` scoping a live lap's comparison to only same-layout sessions, never conflating a layout-tagged session with an untagged legacy one
|
||||
|
||||
`tests/judging_logic.test.js` covers:
|
||||
- cumulative lap/time corrections, reset, and correction-state undo
|
||||
@@ -2539,18 +2694,17 @@ The suite uses Node's built-in `node:test` runner and does not add a test-framew
|
||||
|
||||
## 19. Recommended Future Additions
|
||||
|
||||
Closed out 2026-09-12: `resultsBySession`'s competitor row schema (15.7), a full `settings` schema snapshot (15.9), and the SQLite table contract (15.10) are now documented; the browser regression suite (section 18) already covers every critical create/edit/save workflow (classes, drivers, cars, sponsor events, the race wizard, team management, Race Day start/pause/resume/finish, session-type-specific start sequences, Settings tabs, Event Manager tabs, the schedule panel, OBS popup, decoder reconnect).
|
||||
|
||||
If the project grows further, the next useful additions to this document are:
|
||||
- optional neural TTS provider integration for natural start, finish, and driver-name announcements
|
||||
- optional neural TTS provider integration for natural start, finish, and driver-name announcements (deferred at the user's request pending a chosen provider - see ROADMAP.md, "More natural race speech")
|
||||
- backend-only provider credentials with no API key material in frontend state, exports, or public overlays
|
||||
- server-side speech generation and content-addressed cache under `data/tts/` so race playback never waits for a cloud request
|
||||
- cached audio invalidation when language, voice, phrase, or a driver's `speechName` changes
|
||||
- offline fallback order: cached neural audio -> uploaded WAV/MP3 -> browser system voice -> passing beep
|
||||
- provider health/status and a pre-generate-all-names action in Settings before race day
|
||||
- exact `resultsBySession` competitor row schema
|
||||
- exact `settings` schema snapshot
|
||||
- SQLite table contract section
|
||||
- controller-to-view ID map for critical forms/buttons
|
||||
- browser-level smoke tests for critical create/edit workflows
|
||||
- a full controller-to-view ID map for every form/button (large - the doc currently documents this per-feature as each area is touched, e.g. the manage-tab targets in 2.2's `event_manager_controller.js` entry, rather than as one exhaustive cross-reference)
|
||||
- permissions/authentication design once that work actually starts (ROADMAP.md, "Permissions and mobile Race Control" - deferred, no identity system exists yet)
|
||||
|
||||
## 20. Race Audio Architecture
|
||||
|
||||
@@ -2660,9 +2814,23 @@ The application currently has no authentication or role model. Race-control soun
|
||||
- Existing app-version polling reports server contact through an optional `onContact` callback. `src/app.js` updates `lastContactAt`/`connectionChecked` and renders status text once per second without rebuilding forms. Contact older than 15 seconds is stale. Decoder connectivity remains separate.
|
||||
- `index.html` owns `syncStatus`, `syncStatusLabel`, `syncStatusDetail`, `syncStatusRetry`, and `syncWarning`. Retry uses the existing full-state POST when local changes or write errors exist. Successful full-state synchronization at the current local version clears the passing error too. Keep the page open when unsaved changes exist; this is not a new durable offline queue or a multi-controller conflict-resolution mechanism.
|
||||
- `tests/sync_status.test.js` verifies status precedence and state-write failure/retry. `tests/browser_smoke.js` drives Chrome/Chromium/Edge over its local debugging protocol with Node's WebSocket API. It launches an isolated server using `RC_TIMING_DATA_DIR` and a temporary browser profile. `CHROME_PATH` overrides discovery. `RC_BROWSER_NO_SANDBOX=1` is an explicit opt-in for restricted test environments; normal runs keep the browser sandbox enabled.
|
||||
- The browser suite covers register CRUD creation/editing (not deletion), drafts through two version polls, reload persistence, failed offline saves and retry. Session/team flows and CI platform coverage remain tracked in ROADMAP.md.
|
||||
- The browser suite covers register CRUD creation/editing (not deletion), drafts through two version polls, reload persistence, failed offline saves and retry, session/team flows, and Race Day's full session lifecycle. CI platform coverage (Windows/Linux runs) remains tracked in ROADMAP.md as the one still-open item under "Reliability and browser regression tests" - everything else in that roadmap section is done.
|
||||
|
||||
Browser scenarios are split into `tests/browser_race_setup.js` (sponsor-event/race/team/session persistence) and `tests/browser_overlays.js` (shared overlay column alignment and sorting isolation). Both are invoked by `tests/browser_smoke.js` using its isolated browser/server harness. Overlay headings and rows use the same `--overlay-columns` grid tracks in `src/styles.css`; narrow interactive lists scroll horizontally as one unit. OBS grid rows retain their compact-screen wrapping because they have no sorting toolbar.
|
||||
`tests/browser_smoke.js` is the entry point (`npm run test:browser`); it wires together one isolated server/browser instance and invokes every scenario file below in sequence:
|
||||
- `tests/browser_race_setup.js` - sponsor-event/race/team/session creation, editing, and reload persistence
|
||||
- `tests/browser_team_membership.js` - moving a driver between teams (the duplicate-membership bug fix), team-scoped driver pool, and team roster cleanup on driver deletion
|
||||
- `tests/browser_event_manager_tabs.js` - Event Manager's 4-tab layout: default tab, per-tab panel visibility, the quick-jump step cards/warning links switching tabs before scrolling, and the Open Grid button jumping to the Live tab
|
||||
- `tests/browser_event_schedule.js` - the Schedule tab: saving/clearing a time-of-day schedule, the cascaded running-order table, and the "Delayed" badge
|
||||
- `tests/browser_settings_tabs.js` - Settings' 4-tab layout: default tab, per-tab panel visibility, and the active tab surviving a save-triggered re-render
|
||||
- `tests/browser_race_day.js` - Race Day start/pause/resume/finish, switching to the next queued heat, against a simulated decoder socket
|
||||
- `tests/browser_race_day_start_sequences.js` - the real countdown/light/audio start sequence for heat/qualification/final/team_race/practice sessions (as opposed to open_practice/free_practice's instant start)
|
||||
- `tests/browser_obs_popup.js` - the OBS overlay popup opening via a real click, compact layout, and independent live-updates
|
||||
- `tests/browser_decoder_reconnect.js` - auto-reconnect after a dropped socket, settling on exactly one connection, and auto-reconnect staying off when disabled
|
||||
- `tests/browser_overlays.js` - shared overlay column alignment and sorting isolation across viewport sizes
|
||||
|
||||
`tests/browser_fake_decoder.js` is a shared helper (not a scenario file itself): it completes just the WebSocket upgrade handshake so any scenario needing a "connected decoder" doesn't need real AMMC hardware, with `dropConnections()` to simulate a network interruption.
|
||||
|
||||
Overlay headings and rows use the same `--overlay-columns` grid tracks in `src/styles.css`; narrow interactive lists scroll horizontally as one unit. OBS grid rows retain their compact-screen wrapping because they have no sorting toolbar.
|
||||
|
||||
## Decoder Diagnostics (Browser-Local)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user