From 44f9b6261051b42cc87dc2ecf8d146c69464c13c Mon Sep 17 00:00:00 2001 From: larssand Date: Sat, 12 Sep 2026 17:40:58 +0200 Subject: [PATCH] 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 --- CHANGELOG.md | 6 + ROADMAP.md | 21 ++- docs/javascript-architecture.md | 228 +++++++++++++++++++++++++++----- package-lock.json | 4 +- package.json | 2 +- 5 files changed, 225 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11e5a1a..7b6b83c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ The application version is maintained in `package.json` and displayed in the adm No changes recorded yet. +## 1.20.2 - 2026-09-12 + +### Changed + +- Brought `docs/javascript-architecture.md` back up to date with this session's changes: documented `src/track_layouts_logic.js`, `buildEventSchedule`, the Settings/Event Manager tab layouts, and the new `Event.raceConfig.scheduleStartAt`/`scheduleGapMin` and `Session.trackLayoutId` fields; added a real SQLite table contract section and a full field-by-field `Settings` shape (both previously just TODO notes); documented several previously-undocumented backend routes (`/api/update-status`, `/api/update/run`, `/api/network/public-address`, `/api/passings/purge`, the public-training self-service actions); and listed every file `tests/browser_smoke.js` actually invokes. + ## 1.20.1 - 2026-09-12 ### Changed diff --git a/ROADMAP.md b/ROADMAP.md index 75aabd0..a9b8ad9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -496,9 +496,24 @@ Verified 2026-09-11: confirmed all 7 files are served with `200` and the correct ## Documentation maintenance -- [ ] Update the architecture's module/dependency inventory as modules change. -- [ ] Complete state/settings/SQLite contracts and critical form-to-controller mappings. +- [x] Update the architecture's module/dependency inventory as modules change. +- [x] Complete state/settings/SQLite contracts and critical form-to-controller mappings. - [x] Consolidate older future-work notes so this file remains the primary roadmap. - [x] Update the in-app Guide page for this session's shipped features. Added three new "Träningsstatistik" steps (guided-exercise config, mobile self-service, the end-of-practice summary) and a new "Resultatarkiv" section (overview card + 5-step walkthrough); corrected `guide.sqlite_2`/`_6`, which claimed raw SQLite passings are always retained - no longer true now that deletion/clear-data purges them. Verified 2026-09-09 with a headless-browser check confirming the new content renders as real translated text (sv), not raw `guide.*` keys. -Consolidated 2026-09-09: the raw "Träningsstat" brainstorm notes (Pyramiden, Konsistens-jakten) were folded into section 3's "Guided practice exercises" as a proper specification and removed from here. \ No newline at end of file +Consolidated 2026-09-09: the raw "Träningsstat" brainstorm notes (Pyramiden, Konsistens-jakten) were folded into section 3's "Guided practice exercises" as a proper specification and removed from here. + +Closed out 2026-09-12, requested by the user alongside the screenshot refresh above: `docs/javascript-architecture.md` (~2850 lines) hadn't kept pace with this whole session's work. Used a research-only Explore pass first to get a concrete diff (doc location vs. actual code) rather than guessing what was stale, then applied it: + +- [x] Section 2.2 (file inventory): added `src/track_layouts_logic.js` (previously undocumented anywhere - zero mentions), expanded `src/settings.js`'s and `src/event_manager_view.js`/`event_manager_controller.js`'s entries to describe their tab layouts (`activeSettingsTab`/`activeManageTab`, `MANAGE_TAB_TARGETS`) instead of the pre-tab flat panel lists. Section 2.1: added an entry for `scripts/record-video-guides.mjs` and `scripts/capture-screenshots.mjs`. +- [x] Section 4.1 (`app.js` wrapper functions): added `buildEventSchedule`, previously absent from every part of the doc despite being wired into both Event Manager and the printed start lists. +- [x] Section 9 (Dependency/Ownership Matrix): added rows for `driver_profile_logic.js` and `track_layouts_logic.js`. +- [x] Section 15 (Frontend State Schema): added the missing `trackLayouts`/`audioEventLog` top-level keys and a new `TrackLayout` type (15.1a); added `Event.raceConfig.scheduleStartAt`/`scheduleGapMin` and `Session.trackLayoutId`, both previously undocumented despite driving real features (the race day schedule, track-layout-scoped lap comparisons). Rewrote 15.9 `Settings` from a loose "contains at least" prose list into an actual field-by-field shape grouped by which Settings tab owns each field. +- [x] New section 15.10, SQLite Table Contract: the two actual tables (`app_state`, `passings`) straight from `initSchema()` in `server.js` - this literally didn't exist before, despite being one of the two explicit roadmap TODOs being closed here. +- [x] Section 16 (Backend API Contract): added the update-checker routes (`GET /api/update-status`, `POST /api/update/run`), `GET /api/network/public-address`, `POST /api/passings/purge`, and the three public-training self-service action routes (even-laps, step-training, exercise-sessions) plus the QR route - all previously live in `server.js` with zero documentation. +- [x] Section 18 (Automated Regression Tests): added `tests/driver_profile_logic.test.js` and `tests/track_layouts_logic.test.js` to the unit-test coverage list; rewrote the browser-suite appendix to list all 10 scenario files `tests/browser_smoke.js` actually invokes (it previously named only 2) and fixed a stale line claiming "session/team flows... remain tracked in ROADMAP.md" when both have had real coverage for a while. +- [x] Section 19 (Recommended Future Additions): removed the 3 items now addressed (resultsBySession schema, settings schema, SQLite contract) and the "browser-level smoke tests" item (long since done), keeping only the genuinely still-open items - neural TTS (deferred pending a provider) and permissions/auth (deferred pending its own design conversation), both already tracked in this file. + +Deliberately left as pre-existing, lower-priority gaps rather than closing everything in one pass: several older modules (`race_audio.js`, `sync_status.js`, `html_escape.js`, `list_sorting.js`, `results_archive_view.js`, `decoder_diagnostics.js`/`decoder_diagnostics_view.js`) are documented only in later unnumbered appendix sections, not in section 2.2's main per-file inventory or section 3's import graph - real inconsistencies, but not something this session's changes touched, so fixing them was out of scope for "update as modules change." + +Verified 2026-09-12: re-read every edited section back against the actual source (`grep` for each newly-documented symbol/field/route) to confirm nothing was invented; new subsections inserted between existing numbered ones (`15.1a`, `16.2a`, `16.6a`) use a letter suffix rather than renumbering everything after them. \ No newline at end of file diff --git a/docs/javascript-architecture.md b/docs/javascript-architecture.md index c5fa4a6..7f2ddd6 100644 --- a/docs/javascript-architecture.md +++ b/docs/javascript-architecture.md @@ -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 `
` 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, 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: }`. 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) diff --git a/package-lock.json b/package-lock.json index c3d42cc..d154e93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rc-racecore", - "version": "1.20.1", + "version": "1.20.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rc-racecore", - "version": "1.20.1", + "version": "1.20.2", "hasInstallScript": true, "dependencies": { "better-sqlite3": "^12.11.1", diff --git a/package.json b/package.json index 7a63234..9a100af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-racecore", - "version": "1.20.1", + "version": "1.20.2", "private": true, "description": "RC RaceCore live timing and race control with AMMC ingest and SQLite persistence", "engines": {