# 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). - [x] 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. Verified 2026-09-10 (`tests/browser_race_day_start_sequences.js`): unlike Open Practice, heat/qualification/final/team_race sessions run the real countdown-light-audio sequence in `src/race_audio.js` before a session is allowed to transition to "running" - confirmed a heat session stays `status: "ready"` immediately after clicking Start, that the Start button visibly ticks down through real countdown digits, and that it only becomes `running` once the full sequence (countdown, 5-light step, start delay) completes. For all four types, confirmed the announced-event log shows `drivers_ready` then `start_sequence` (plus `qualifying_start` in between for qualification only, nowhere else), and that pause/resume/finish behave the same as they already do for Open Practice. Every effect/voice was silenced via `raceAudio.events` so the test depends only on the sequence's own timers, not on real audio/TTS playback completing in a headless browser with no sound hardware - kept the test both fast and independent of any audio asset actually loading. ## 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 `