Files
Live_RC/ROADMAP.md
larssand 643902ff15 Add end-of-practice summary to the driver's mobile profile
When a driver's personal 5-minute practice timer finishes, a "Pass
avslutat" card now appears immediately: how this pass's pace and
consistency compared to their recent sessions, any goal or lap-count
milestone crossed during the pass with its reward text made prominent,
and one suggested next goal (reusing the existing pace recommendation,
falling back to the next milestone when there isn't enough data yet).
Scoped to the driver's current class, and explicit about insufficient
sample sizes instead of showing a misleading number.

Adds a per-session consistency calculation to
buildPublicTrainingWindowStats server-side; everything else needed
(stats, goals, recommendation, milestone) was already sent to the
client, so the summary itself is computed there from data already in
hand - no raw laps or driver IDs added to what the public endpoint
exposes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 15:05:49 +02:00

150 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RC RaceCore Roadmap
Track work here with checkboxes. Check off a task only after implementation and verification. Record user-visible changes under `Unreleased` in [CHANGELOG.md](CHANGELOG.md). Priorities below are a working order, not release-date commitments.
## Existing foundation
- [x] Race Day, preflight checks and heat queue.
- [x] Automatic SQLite backups and local restore command.
- [x] Isolated session replay.
- [x] Training statistics, personal mobile profiles and car-change markers.
- [x] Interactive list sorting and OBS display modes.
- [x] Visible application version and changelog.
Verified 2026-09-08: 122 logic/API tests and the register/offline browser smoke suite passed on Linux with Chrome. Checked items below describe that coverage; other platforms and workflows remain open.
Fixed 2026-09-09: a fresh Windows install reported the server failing around backups. Root cause: `createBackupService` created the `backups` subdirectory once, synchronously, at server startup with no error handling - if that failed for any reason (no write permission, a path beyond Windows' MAX_PATH, a OneDrive/cloud-sync folder locking it, antivirus interference), the whole Node process crashed before the HTTP server even started listening, taking down timing and everything else with it, not just backups. The directory creation is now non-fatal and retried lazily on each backup attempt, with a clear "Backup directory is unavailable" error surfaced only to the backup action itself; a file rename right after `db.backup()` also now retries briefly on `EBUSY`/`EPERM`, since a freshly written file is sometimes still momentarily held by antivirus/cloud-sync scanning on Windows. Verified with a new unit test (`tests/backup_service.test.js`) that blocks directory creation via a read-only parent directory and asserts the service still constructs and lists successfully, and with a real spawned-server integration check (`RC_TIMING_DATA_DIR` pointed at a data dir where the `backups` path is pre-occupied by a plain file) confirming `/api/health` stays up and only the backup request itself returns a clear error. The exact filesystem condition on the reporting machine is still unconfirmed - this closes the crash-the-whole-server failure mode regardless of which specific cause it turns out to be.
## 1. Reliability and browser regression tests
Acceptance: real create/edit/save flows persist after reload; background updates do not discard edits. Tests use isolated data, never the installation's database.
- [x] Add a repeatable browser smoke-test command with temporary database/profile.
- [x] Cover create/edit/save for classes, drivers and cars, including reload verification.
- [x] Verify register form drafts remain intact during background version polling.
- [x] Cover sponsor-event creation/editing and endurance race wizard/name edits with reload verification.
- [x] Cover team creation, membership changes, editing and deletion.
- [x] Cover Open Practice to team-race conversion, duration/follow-up edits and persisted lap limits.
- [x] Cover Race Day start/pause/resume/finish controls and switching to the next queued heat, with a simulated decoder socket (no real AMMC hardware).
- [ ] Cover session-type-specific start sequences (heat/qualification/race/team_race countdown and audio) and other transitions beyond Open Practice.
- [x] Verify leaderboard/TV/team sorting heading alignment at 390/900/1600 px and preserved race positions.
- [x] Verify OBS views have no interactive sorting controls.
- [x] Cover actual popup launching and compact OBS layout automatically.
- [ ] Add Windows and Linux CI runs for supported runtimes.
Verified 2026-09-09: the OBS overlay popup opens via a real click on the admin "Open OBS overlay" button (`window.open`, not a direct URL navigation), renders the compact race-info/standings split layout, and its clock advances live and freezes on pause without a manual reload - polling independently of the admin page (`tests/browser_obs_popup.js`). Found and fixed a real bug in the process: `window.open(..., "noopener,noreferrer,...")` always returns `null` in modern browsers even when the window opens successfully (WHATWG spec), which made the blocked-popup check in `openPrintWindow()` and `openOverlayWindow()` always fire - print/export (start lists, results, heat sheets, team reports) showed a false "popup blocked" error and never wrote any content, and the overlay popup's `.focus()` call was unreachable. Both now open without those flags (same-origin, first-party content, so no security loss).
Verified 2026-09-08: Race Day start/pause/resume/finish and next-heat selection pass end to end (`tests/browser_race_day.js`) against a simulated decoder socket, with native-dialog auto-accept added to the browser harness so an unexpected `alert()`/`confirm()` fails the test instead of hanging it. Found and fixed two real bugs in the process: Open Practice sessions on a track-mode (sponsor) event showed a ready/green preflight but were silently blocked from starting by a stricter assignment check; and the Race Day view never refreshed when the decoder connected, disconnected, or errored, so its Start button and preflight checklist could stay stale after a page load.
## 2. Connection and save status
Acceptance: users can distinguish server contact, pending edits, an active save and a failed save; a health check never falsely confirms a failed write.
- [x] Show pending, saving, saved and failed state in the main interface.
- [x] Show last successful data load/save times.
- [x] Warn when the server connection is lost or its status becomes stale.
- [x] Offer manual retry for unsaved changes without discarding local edits.
- [x] Verify save failure/reconnection in a real browser.
- [x] Extend stale-data feedback to the personal mobile profile: last applied retrieval, 30-second stale warning, timeout and Retry without discarding loaded data.
- [x] Detect and explain a Backend URL/page-origin mismatch, the real cause behind an otherwise unexplained "Failed to fetch" on every sync and backup.
- [x] Review concurrent writes with two admin browsers open at once; protect live race results from being overwritten, and shrink how stale a browser's data can get before its next save. Reported 2026-09-09, fixed the same day.
- [ ] Full per-field/optimistic-locking protection so no concurrent edit (not just live results) can silently overwrite another admin's - deferred, see the report below for why.
Fixed 2026-09-09: a real Windows install showed "Backup failed: Sync failed: Failed to fetch" and every other backend call failing the same way, while the server itself was healthy (confirmed via `Invoke-WebRequest` returning 200 for `/api/health`, `/api/state` and `/api/backups`) - only the browser's own `fetch()` calls failed. Root cause: the page was opened via the LAN IP (`http://192.168.1.100:8081`) while the persisted Settings "Backend URL" still said `http://127.0.0.1:8081` from an earlier session. Browsers treat those as different origins even on the same machine, so the server's CORS check (`server.js`, comparing the request's `Origin` header against its own `Host` header) correctly rejected it - but nothing told the user why, since a CORS-rejected response surfaces to `fetch()` as a generic "Failed to fetch" with no further detail, and a command-line tool like `Invoke-WebRequest` doesn't send an `Origin` header at all, so it never reproduced the symptom. The Settings page now compares `window.location.origin` against the configured Backend URL on every render and, when they differ, shows a plain-language warning naming the page's actual address plus a one-click "Use `<address>`" button that fixes it immediately. Verified with a headless-browser check (seeding a deliberately mismatched Backend URL, confirming the warning text names the real origin, and confirming the fix button both clears the warning and persists the corrected value to the backend) against an isolated temp SQLite instance.
Reported and fixed 2026-09-09: investigated whether two admin browsers open at once can overwrite each other's changes, per an explicit request to report the risk before making bigger changes. Findings: `POST /api/state` ([server.js](server.js)) always replaces the *entire* stored state with whatever the client's in-memory copy was - no version check, no per-field merge. A browser only ever pushes; it never re-fetches server state after its initial page load, except the Overlay view, which already refreshes every 800ms. So the longer any other view sits open without a save, the staler its copy gets, and its *next* save - even one editing something unrelated, like a driver's name - pushes that entire stale snapshot, silently reverting any changes another browser made in the meantime to drivers, classes, cars, events, sessions, settings, training data, `activeSessionId` and `resultsBySession` alike. The worst case: an idle browser's save reverting another browser's live, in-progress race results. Two things are already safe from this: the raw lap-crossing log (the separate, append-only `passings` SQLite table) can't be lost this way, and the mobile driver self-service endpoints (markers, "Jämna varv", "Stegträning") already read-modify-write just their own slice server-side rather than accepting a client's full blob.
Fixed with two scoped, low-risk changes rather than the bigger optimistic-locking rework (deferred - it needs real UX decisions about what a rejected/conflicting save should look like to the user, across every `saveState()` call site):
1. **Protect live results specifically** ([server.js](server.js)): the server now remembers, in memory, which `resultsBySession[sessionId]` entries were just updated by a real live decoder passing (`/api/passings`, the only route driven by actual timing hardware). For 15 seconds after that, an incoming `POST /api/state` whose body disagrees with that session's just-written result has that one key overwritten back to the server's copy before being saved - every other field in the same save still goes through normally. Verified with an integration test (`tests/server_persistence.integration.test.js`) simulating exactly the idle-browser-reverts-live-race scenario, using a shortened protection window via `RC_RESULT_PROTECTION_WINDOW_MS` to keep the test fast, plus a check that the protection expires and a later legitimate change to that same session still takes effect.
2. **Shrink the staleness window everywhere else** ([src/runtime_services.js](src/runtime_services.js)): every non-Overlay view now silently re-fetches server state in the background every 15 seconds (skipped while there's an unsynced local edit pending, same guard Overlay already uses) - without ever calling `renderView()`, so it can never discard an open, unsaved edit form's draft. This doesn't touch Overlay's own faster, render-driven refresh at all. Verified with a headless-browser check: an open, unsaved driver-name edit survives a full 17-second background-hydration cycle untouched, and a *different*, unrelated save from that same browser afterward is confirmed to carry a driver-name change a "second browser" made in the meantime, instead of the original stale page-load copy.
This closes the highest-impact case (losing live race data) and cuts the realistic exposure window for everything else from potentially hours to about 15 seconds. It does not prevent two admins from overwriting the exact same non-race field within that same ~15-second window - that needs the full optimistic-locking approach, tracked as a separate, deliberately deferred item above.
### Implemented: decoder/socket diagnostics
Acceptance: each configured channel distinguishes an open socket from actual received data and successfully parsed passing messages. Inspecting diagnostics must not interrupt timing or rerender an active settings form.
- [x] Show Main, Split, Pit entry and Pit exit separately, with the configured WebSocket URL.
- [x] Show socket state, last connection, last received message and elapsed time since reception.
- [x] Count received frames, recognized passing messages (not accepted laps), ignored messages and parse errors per channel.
- [x] Add a bounded live message sample/log with pause, clear and copy controls.
- [x] Show why an incoming passing was ignored or rejected without changing lap validation.
- [x] Add simulated-socket tests for receiving, parsing, disconnecting and open status.
- [x] Verify automatic reconnect against a simulated decoder/network interruption (raw socket drop, not a clean close), checking for duplicate connections and correct reconnect-timer behavior.
- [x] Document IP/port/channel setup and the difference between decoder connection and incoming data.
Verified 2026-09-08: 128 logic/API tests and browser smoke tests passed, including mobile profile retention during network loss and recovery via Retry.
Verified 2026-09-09: a browser test (`tests/browser_decoder_reconnect.js`) drops a real TCP connection to a simulated decoder socket and confirms the frontend detects it, waits for the ~2s reconnect timer in `src/decoder_runtime.js`, and settles on exactly one live connection afterward - never two at once. Disabling auto-reconnect through the real Settings form (not a raw state patch, which never reaches an already-loaded page) correctly stops further retries after a drop. Reconnecting against physically real AMMC hardware remains a separate hardware-in-the-loop check this suite cannot cover.
## 3. Clearer practice follow-up
Acceptance: a driver receives a short, encouraging summary based on their own class-specific data, with one useful next goal. Implemented 2026-09-09.
- [x] Add an end-of-practice summary: pace, consistency, progress and achieved goals. Triggered the moment the personal 5-minute timer on the mobile profile finishes ("Passet är klart") - a "Pass avslutat" card appears immediately alongside the existing finish cue.
- [x] Suggest one next training goal using existing calculations. Reuses the existing pace-based recommendation (`buildTrainingRecommendation`/`getPublicTrainingRecommendation`); falls back to the next lap-count milestone when there isn't yet enough data for a pace recommendation.
- [x] Keep summaries separate by class and explain insufficient sample sizes. Scoped to the driver's currently selected class, same as the rest of the page; explicitly explains when there aren't enough laps (this pass, or overall) to judge pace or consistency, rather than showing a misleading number.
- [x] Include the summary in the personal mobile profile. It only exists there - the admin Training page remains the always-current dashboard it already was.
- [x] Add coach/parent reward follow-up without turning practice into a public ranking. Decided 2026-09-09 - rather than a new notification mechanism, when a goal is reached during the pass its existing reward text is surfaced prominently in the summary ("Du nådde X - be din coach/förälder om: [belöning]"), still private to the driver's own share link, never a cross-driver comparison.
Verified 2026-09-09: unit tests (`tests/public_training_profile.test.js`) cover the new per-session consistency calculation added to `buildPublicTrainingWindowStats` (enough-laps vs. too-few-laps cases, with a hand-verified spread value). A real headless-browser check (`tests/browser_*` scratch pattern, isolated temp SQLite) seeded a driver mid-pass with a goal and a lap-count milestone both crossing exactly during the session, confirmed the summary reports the faster pace, the tight consistency, the goal, its reward text, and the milestone all correctly, confirmed a "Stäng" click dismisses it and the dismissal survives a reload of the same session, and confirmed a later, different session shows its own summary again. Found and fixed a real bug in the process: triggering the summary immediately (rather than waiting for the next background poll) called `renderProfile()` from inside `updateTimerDisplay()`, which itself calls back into `updateTimerDisplay()` - the reentrancy guard was updated *after* that call instead of before, so the stale guard let the finished-phase branch fire again on every reentry, recursing until the browser's call stack overflowed. A second headless check with a near-empty driver history confirmed the "not enough data" messaging renders cleanly instead.
### Guided practice exercises
Acceptance: two structured, configurable exercises run inside an already-active Open Practice session, per driver and class, without stopping other drivers or creating a new session. Named for what they help a driver do, not for who supposedly likes them - "Jämna varv" ("Even laps", formerly "Konsistens-jakten") and "Stegträning" ("Step training", formerly "Pyramiden") - and focused on succeeding rather than performing perfectly. Available both from the admin Training page and from the driver's own personal mobile profile link, so the driver can configure, start and stop their own exercise independently of whether an admin session has it open.
**Jämna varv (even laps):** implemented 2026-09-09.
- [x] Driver configures target lap time, tolerance and lap count; suggested default 5 laps / ±0.5s - 15 laps / 0.2s is frustrating for a beginner, but stays adjustable for anyone who wants it stricter.
- [x] Offer a suggested target derived from the driver's own personal best (best lap × 1.05) when enough data exists, shown as the form's placeholder and a hint, never forced.
- [x] Tolerance is ± around the target lap time: every lap in the streak must land within `target ± tolerance`, not a spread between the streak's fastest and slowest lap. Decided 2026-09-09 - simpler to understand and to show live ("hit the target"), and matches how a beginner already thinks about it.
- [x] Show the current streak and today's best streak live, e.g. "4/5 even laps - Best streak today: 7".
- [x] A lap outside tolerance (or one excluded from pace, e.g. a pit stop) ends the current streak but never erases prior progress; messaged as "New streak - try to beat your best", not as a failure.
Verified 2026-09-09: `buildEvenLapsProgress` in `src/training_logic.js` (unit-tested for streak tracking, tolerance, and exclusion handling) plus a manual browser check of the live card, config form and streak reset messaging inside an active Open Practice session. Found and fixed two real bugs in the process: `syncTrainingLedger` rebuilt `state.training` on every save without carrying the new `evenLaps` field through, silently discarding every saved configuration; and the Training page crashed for any driver with fewer than 3 laps in every stint (a very common state - e.g. anyone in their first couple of laps), because a dedup filter read `.id` on a `null` "best stint" entry.
**Stegträning (step training):** implemented 2026-09-09.
- [x] Configurable steps: lap count plus an instruction per step (calm / controlled / fast). The form offers the classic pyramid (calm/controlled/fast/controlled/calm, 5 laps each) as an editable starting point; a step's lap count can be set to 0 to skip it, so shorter or longer sequences are both possible.
- [x] Show the current step and remaining laps in that step, plus a pill per step showing progress and highlighting the current one.
- [x] Treat calm/controlled/fast as perceived effort shown to the driver, not an exact lap-time target the system enforces - there is no reliable way to measure how hard a driver is pushing.
- [x] Compare the two controlled-pace steps afterward without automatically labeling a slower second step as fatigue: shown as a plain pace difference ("Controlled step 1: 0:21.100 · Controlled step 2: 0:22.000 (+0.90s)"), generalized to the first and last "controlled" step in the driver's own configuration rather than assuming exactly the pyramid's two.
Verified 2026-09-09: `buildStepTrainingProgress` in `src/training_logic.js` (unit-tested for step advancement, pace-exclusion handling, and the controlled-step comparison) plus a manual browser check covering the default pyramid, mid-exercise progress, reconfiguring to a shorter sequence, and the completed-sequence comparison text.
**Mobile profile availability:** implemented 2026-09-09.
- [x] Both exercises can be configured, started and stopped directly from the driver's own personal mobile profile link (`public-training.html`), the same way the existing self-service practice timer already works there - independent of whether an admin has the Training page open.
- [x] Progress (current streak/step, today's best, tolerance-based lap coloring, step pills) is shown live on the mobile page itself, computed server-side from the driver's own share token so raw lap rows and driver IDs never reach the client - only sanitized aggregates, matching the existing public-profile security boundary.
- [x] The exercise's own elapsed time is tracked independently of the general 5-minute practice timer and of each other (separate `evenLapsStartedAgoMs`/`stepTrainingStartedAgoMs`, mirroring the existing `startedAgoMs` clock-skew-safe pattern) so starting/stopping one does not affect the other or the general timer.
Verified 2026-09-09: unit tests in `tests/public_training_profile.test.js` cover `savePublicEvenLapsConfig`/`savePublicStepTrainingConfig` (auth via share token, class-membership checks, invalid-input rejection) and the sanitized progress builders, including an explicit assertion that driver IDs never appear in the serialized response. A full end-to-end headless-browser run against an isolated temp SQLite instance (never the installation database) exercised the real mobile page: saving a config via the new `POST /public-training/api/:shareToken/even-laps` and `/step-training` endpoints, starting/stopping via the UI buttons, and confirming live progress (streak count, tolerance-based lap coloring, step pills) renders correctly from seeded lap data - with zero uncaught JS errors and zero raw driverId/transponder leakage in the API response.
## 4. Results archive
- [ ] Search completed sessions by date, class and driver without Race Setup.
- [ ] Show standings, lap history and penalties together.
- [ ] Reuse existing PDF/export functions from the archive.
- [ ] Define what remains available after deleting a race.
## 5. More natural race speech
- [ ] Optional backend-generated neural speech.
- [ ] Cache announcements and driver names before race day.
- [ ] Keep credentials on the backend, outside exports and public responses.
- [ ] Invalidate cached speech when language, voice or pronunciation changes.
- [ ] Provide provider status and a pre-generate action.
- [ ] Verify offline fallback and that playback never delays timing.
## 6. Permissions and mobile Race Control
- [ ] Define Admin, Race Director, Speaker and Read-only capabilities.
- [ ] Enforce permissions on server endpoints, not only in the UI.
- [ ] Keep public overlays and training links restricted.
- [ ] Build compact mobile controls for start/stop, track call, flags and next heat.
- [ ] Test concurrent control and accidental repeated commands.
## Documentation maintenance
- [ ] Update the architecture's module/dependency inventory as modules change.
- [ ] Complete state/settings/SQLite contracts and critical form-to-controller mappings.
- [x] Consolidate older future-work notes so this file remains the primary roadmap.
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.