From bed0dfd9f23875a035f55f2e80fab1bb9255c81d Mon Sep 17 00:00:00 2001 From: larssand Date: Wed, 9 Sep 2026 18:31:03 +0200 Subject: [PATCH] Update in-app Guide for this session's features, extend passing purge to Clear data The in-app Guide page hadn't been updated for the guided practice exercises, their mobile self-service controls, the end-of-practice summary, or the new Resultatarkiv view - none of it was documented anywhere a user would find it. Added three new Training walkthrough steps and a new Resultatarkiv section (overview card + 5-step guide). While correcting guide text that claimed raw SQLite passing rows are always retained, found the same gap the results-archive work already fixed for deleting a single event/session also applies to "Rensa data" (Settings -> Clear data): the drivers/cars/races/all-competition-data scopes all reset resultsBySession for every current session, not just ones tied to the cleared scope, so every session's raw rows became orphaned at once. clearDataScope now purges those sessions' rows too, through the same /api/passings/purge endpoint. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 10 ++++++++++ ROADMAP.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- src/app.js | 33 +++++++++++++++++++++++++++++---- src/misc_views.js | 7 ++++++- 6 files changed, 55 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b2130b..85758bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ The application version is maintained in `package.json` and displayed in the adm No changes recorded yet. +## 1.6.1 - 2026-09-09 + +### Fixed + +- "Rensa data" (Settings -> Clear data) for the drivers/cars/races/all-competition-data scopes left every affected session's raw timing rows behind in the database, the same gap already fixed for deleting a single event/session - now purged there too. + +### Documentation + +- The in-app Guide page now covers this session's new features: the two guided practice exercises and their mobile self-service controls, the end-of-practice summary, and the new Resultatarkiv view. Corrected outdated text claiming raw timing rows are always retained. + ## 1.6.0 - 2026-09-09 ### Added diff --git a/ROADMAP.md b/ROADMAP.md index 3a4e2f0..4455d1d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -128,8 +128,12 @@ Implemented 2026-09-09. Verified 2026-09-09: unit tests (`tests/server_persistence.integration.test.js`) cover the purge endpoint (scoped deletion, an unrelated session's rows untouched, empty-input no-op) and (`tests/race_timing_logic.test.js`) a real bug found while building this: `ensureSessionResult` guarded `adjustments`/`splits`/`pitEvents`/`pitStops` against being missing on an already-existing result (e.g. an older/imported race package) but not `passings` itself, so opening a session's lap-history detail crashed outright for any such result - now guarded the same way as the other four. A full headless-browser run against an isolated temp SQLite instance seeded a finished session with two competitors and a penalty, confirmed all four filters (search, class, driver, date) correctly include/exclude it, confirmed the detail panel shows standings/penalties/lap-history correctly, and confirmed deleting the event through the real Events UI actually purges its session's raw passing rows from SQLite end to end. +Extended 2026-09-09: while updating the in-app Guide page to document this session's new features (see Documentation maintenance below), found that "Rensa data" (Settings -> Clear data) for the `drivers`/`cars`/`races`/`all` scopes has the exact same gap the "define what remains after deleting a race" bullet above already fixed for individual event/session deletion - all four scopes reset `resultsBySession` for every current session, not just ones tied to the cleared scope, so every session's raw passing rows became orphaned at once. `clearDataScope` now purges those same sessions' rows too, using the same `/api/passings/purge` endpoint. Verified with a headless-browser run: clearing race data through the real Settings UI empties `resultsBySession` and confirms the raw passing rows for the affected session are gone from SQLite afterward. + ## 5. More natural race speech +Deferred 2026-09-09 at the user's request - requires a paid third-party neural TTS provider (API account/credentials) the user hasn't chosen yet, unlike everything else on this list. Revisit once a provider is picked. + - [ ] Optional backend-generated neural speech. - [ ] Cache announcements and driver names before race day. - [ ] Keep credentials on the backend, outside exports and public responses. @@ -139,6 +143,8 @@ Verified 2026-09-09: unit tests (`tests/server_persistence.integration.test.js`) ## 6. Permissions and mobile Race Control +Deferred 2026-09-09 at the user's request - the largest remaining item, on par with or bigger than the full optimistic-locking rework already deferred under section 2. Today there is no login or authorization at all: anyone reaching the server on the network has full admin access. Building this needs an identity system that doesn't exist yet (no user accounts, no sessions), server-side authorization on every mutating endpoint (not just hiding UI controls), and a genuinely new "compact mobile Race Control" surface (start/stop, track call, flags, next heat) - plus a real decision on how login should work in practice on a shared trackside laptop (PIN codes per role? named accounts? offline/LAN-only requirement?) before any of it can be designed, let alone built. Revisit as its own dedicated design conversation. + - [ ] 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. @@ -150,5 +156,6 @@ Verified 2026-09-09: unit tests (`tests/server_persistence.integration.test.js`) - [ ] 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. +- [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 diff --git a/package-lock.json b/package-lock.json index f16e144..ef12f10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rc-racecore", - "version": "1.6.0", + "version": "1.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rc-racecore", - "version": "1.6.0", + "version": "1.6.1", "hasInstallScript": true, "dependencies": { "better-sqlite3": "^12.11.1", diff --git a/package.json b/package.json index 0ea63bf..904c032 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-racecore", - "version": "1.6.0", + "version": "1.6.1", "private": true, "description": "RC RaceCore live timing and race control with AMMC ingest and SQLite persistence", "engines": { diff --git a/src/app.js b/src/app.js index 16acd65..97fa43c 100644 --- a/src/app.js +++ b/src/app.js @@ -1729,6 +1729,16 @@ const TRANSLATIONS = { "guide.training_24": "24. Ljudhändelsen training stable pace ger valbar personlig röstfeedback efter fem kontrollerade varv i samma practice-stint. Den använder förarens uppläsningsnamn, har två minuters cooldown och aktiveras separat under Raceljud och utrop.", "guide.training_25": "25. Kontrollzonen går från personbästa till 8 procent långsammare. Flytserier bryts vid avvikande varv, ny stint eller ny session. Stark avslutning, rena femminuterspass och förbättrade mellantider ger positiv feedback men påverkar aldrig race-resultat.", "guide.training_26": "26. Under Mål och mobilprofil väljer du vilka publika sektioner som visas. Stints/prestation innehåller även biländring och tempo per aktivt 30-minutersblock. Öppna sektioner ligger kvar vid live-uppdatering.", + "guide.training_27": "27. Under Träning kan du för vald förare/klass ställa in de två guidade övningarna \"Jämna varv\" (måltid, tolerans och antal varv i följd) och \"Stegträning\" (lugn/kontrollerad/snabb-sekvens). Båda körs inuti den redan aktiva Open Practice-sessionen utan att störa andra förare eller starta om timingen.", + "guide.training_28": "28. Samma två övningar går även att ställa in, starta och stoppa direkt från förarens egna mobillänk, oberoende av om adminsidan har Träning öppen. Livestatus (aktuell serie eller steg) visas där föraren själv kör.", + "guide.training_29": "29. När förarens 5-minuterstimer i mobillänken tar slut visas automatiskt en sammanfattning: tempo och jämnhet jämfört med tidigare pass, uppnådda mål eller varvmilstolpar med belöningstext, och ett förslag på nästa mål. Sammanfattningen döljs med Stäng och dyker upp igen efter nästa avslutade pass.", + "guide.archive_title": "Resultatarkiv", + "guide.card_archive_blurb": "Sök och öppna avslutade race på datum, klass eller förare utan att gå via Race Setup.", + "guide.archive_1": "1. Öppna Resultatarkiv i menyn för att se alla avslutade sessioner samlade, oavsett vilket event de tillhör.", + "guide.archive_2": "2. Sök på event- eller sessionsnamn, eller filtrera på klass, förare och datumintervall.", + "guide.archive_3": "3. Klicka Visa på en rad för att se resultat, straff/korrigeringar och varvhistorik per förare för den sessionen.", + "guide.archive_4": "4. Exportera samma PDF- och racepaket (JSON)-filer som redan finns i Manage Event, direkt från arkivet.", + "guide.archive_5": "5. Att radera ett event eller en session tar bort racets data överallt, inklusive de råa varvraderna i databasen - men påverkar aldrig förarens beständiga träningsstatistik, som redan sparas separat.", "overlay.title": "Overlay", "overlay.subtitle": "Extern leaderboard-skärm", "overlay.no_active": "Ingen aktiv session vald.", @@ -1806,11 +1816,11 @@ const TRANSLATIONS = { "guide.linux_3": "3. Kör `npm install`, `npm start`. Servern lyssnar på 0.0.0.0:8081. `ammc-sim` kan användas som fake decoder för test med egna transponder-ID:n; se decoder-guiden.", "guide.sqlite_title": "SQLite-lagring", "guide.sqlite_1": "Databasfil: data/rc_timing.sqlite", - "guide.sqlite_2": "API: /api/state och /api/passings", + "guide.sqlite_2": "API: /api/state, /api/passings och /api/passings/purge", "guide.sqlite_3": "I Inställningar -> Lagring kan du exportera hela racepaket för backup eller flytt till annan server/laptop.", "guide.sqlite_4": "Du kan också exportera eller importera bara klasser, förare och bilar om du vill börja om med race men behålla registret.", "guide.sqlite_5": "CSV-filer för förare och bilar kan både exporteras och importeras. För förare räcker name/namn, transponder och class/klass; endast namnet är obligatoriskt. Egna exporter bevarar ID:n och externa listor slås ihop på transponder eller namn och klass.", - "guide.sqlite_6": "Rensa data kan tömma förare, bilar, klasser, race eller träningsstatistik separat. Rensa all tävlingsdata behåller decoder-, tema-, branding- och serverinställningar. Råa SQLite-passeringar behålls som revisionslogg. Exportera backup först.", + "guide.sqlite_6": "Rensa data kan tömma förare, bilar, klasser, race eller träningsstatistik separat. Rensa all tävlingsdata behåller decoder-, tema-, branding- och serverinställningar. Att radera ett race, en session eller rensa förare/bilar/all tävlingsdata tar även bort de råa SQLite-passeringarna för berörda sessioner - förarens beständiga träningsstatistik påverkas aldrig. Exportera backup först.", "guide.ammc_ref": "AMMC referens: https://www.ammconverter.eu/docs/intro/quick-start/", "error.backend_offline": "Backend offline: {msg}", "error.sync_failed": "Synk misslyckades: {msg}", @@ -3176,6 +3186,16 @@ const TRANSLATIONS = { "guide.training_24": "24. The training stable pace audio event provides optional personal voice feedback after five controlled laps in one practice stint. It uses the driver spoken name, has a two-minute cooldown, and can be enabled separately under Race sounds and announcements.", "guide.training_25": "25. The control zone runs from the personal best to 8 percent slower. Flow streaks reset on an outlier lap, a new stint, or a new session. Strong finishes, clean five-minute runs, and improved split times provide positive feedback but never affect race results.", "guide.training_26": "26. Under Goals and mobile profile, choose which public sections are visible. Stints/performance also contains car-change marking and pace per active 30-minute block. Open sections remain open during live updates.", + "guide.training_27": "27. Under Training, for the selected driver/class, you can configure the two guided exercises \"Jämna varv\" (even laps: target lap time, tolerance and lap count) and \"Stegträning\" (step training: calm/controlled/fast sequence). Both run inside the already-active Open Practice session without disturbing other drivers or restarting timing.", + "guide.training_28": "28. Both exercises can also be configured, started and stopped directly from the driver's own mobile link, independent of whether the admin side has Training open. Live status (current streak or step) is shown where the driver is actually running.", + "guide.training_29": "29. When the driver's 5-minute timer on the mobile link finishes, a summary appears automatically: pace and consistency compared to recent sessions, any goal or lap milestone reached with its reward text, and one suggested next goal. The summary is dismissed with Close and reappears after the next finished pass.", + "guide.archive_title": "Results archive", + "guide.card_archive_blurb": "Search and open completed races by date, class or driver without going through Race Setup.", + "guide.archive_1": "1. Open Results archive in the menu to see every completed session in one place, regardless of which event it belongs to.", + "guide.archive_2": "2. Search by event or session name, or filter by class, driver and date range.", + "guide.archive_3": "3. Click View on a row to see that session's results, penalties/corrections and per-driver lap history.", + "guide.archive_4": "4. Export the same PDF and race-package (JSON) files already available in Manage Event, directly from the archive.", + "guide.archive_5": "5. Deleting an event or session removes the race's data everywhere, including the raw lap rows in the database - but never affects a driver's durable training statistics, which are already stored separately.", "overlay.title": "Overlay", "overlay.subtitle": "External leaderboard screen", "overlay.no_active": "No active session selected.", @@ -3253,11 +3273,11 @@ const TRANSLATIONS = { "guide.linux_3": "3. Run `npm install`, `npm start`. Server listens on 0.0.0.0:8081. `ammc-sim` can act as a fake decoder with selected transponder IDs; see the decoder guide.", "guide.sqlite_title": "SQLite storage", "guide.sqlite_1": "Database file: data/rc_timing.sqlite", - "guide.sqlite_2": "API endpoints: /api/state and /api/passings", + "guide.sqlite_2": "API endpoints: /api/state, /api/passings and /api/passings/purge", "guide.sqlite_3": "In Settings -> Storage you can export a full race package for backup or moving to another server or laptop.", "guide.sqlite_4": "You can also export or import only classes, drivers and cars if you want to reset race data but keep the directory.", "guide.sqlite_5": "Driver and car CSV files can both be exported and imported. Drivers only need name, transponder, and class; only name is required. Native exports preserve IDs and external lists merge by transponder or by name and class.", - "guide.sqlite_6": "Clear data can remove drivers, cars, classes, races, or training statistics separately. Clear all competition data retains decoder, theme, branding, and server settings. Raw SQLite passings remain as an audit log. Export a backup first.", + "guide.sqlite_6": "Clear data can remove drivers, cars, classes, races, or training statistics separately. Clear all competition data retains decoder, theme, branding, and server settings. Deleting a race or session, or clearing drivers/cars/all competition data, also removes the raw SQLite passing rows for the affected sessions - a driver's durable training statistics are never affected. Export a backup first.", "guide.ammc_ref": "AMMC reference: https://www.ammconverter.eu/docs/intro/quick-start/", "error.backend_offline": "Backend offline: {msg}", "error.sync_failed": "Sync failed: {msg}", @@ -4198,6 +4218,10 @@ function importDirectoryCsv(text, type) { } function clearDataScope(scope) { + // drivers/cars/races/all all reset resultsBySession entirely below (not scoped to just the + // affected driver/car), so the raw SQLite passing rows behind every current session become + // orphaned the same way a single deleted event/session's rows would - purge them all here too. + const sessionIdsBeforeClear = state.sessions.map((session) => session.id); const clearDriverReferences = () => { state.events = state.events.map((event) => ({ ...event, @@ -4265,6 +4289,7 @@ function clearDataScope(scope) { settingsStorageNotice = t("settings.clear_success"); settingsStorageNoticeIsError = false; saveState(); + if (["drivers", "cars", "races", "all"].includes(scope)) void purgeSessionPassings(sessionIdsBeforeClear); renderNav(); renderView(); } diff --git a/src/misc_views.js b/src/misc_views.js index 45db418..ae428fb 100644 --- a/src/misc_views.js +++ b/src/misc_views.js @@ -33,6 +33,7 @@ export function renderGuideView({ dom, t }) { ${renderGuideOverviewCard("guide.race_day_title", "guide.card_race_day_blurb", { t })} ${renderGuideOverviewCard("guide.team_title", "guide.card_team_blurb", { t })} ${renderGuideOverviewCard("guide.training_title", "guide.card_training_blurb", { t })} + ${renderGuideOverviewCard("guide.archive_title", "guide.card_archive_blurb", { t })} ${renderGuideOverviewCard("guide.windows_title", "guide.card_decoder_blurb", { t })} @@ -69,7 +70,11 @@ export function renderGuideView({ dom, t }) {
${renderGuidePanel("guide.dashboard_title", ["guide.dashboard_1", "guide.dashboard_2", "guide.dashboard_3", "guide.dashboard_4"], "", { t })} - ${renderGuidePanel("guide.training_title", ["guide.training_1", "guide.training_2", "guide.training_3", "guide.training_4", "guide.training_5", "guide.training_6", "guide.training_7", "guide.training_8", "guide.training_9", "guide.training_10", "guide.training_11", "guide.training_12", "guide.training_13", "guide.training_14", "guide.training_15", "guide.training_16", "guide.training_17", "guide.training_18", "guide.training_19", "guide.training_20", "guide.training_21", "guide.training_22", "guide.training_23", "guide.training_24", "guide.training_25", "guide.training_26"], "", { t })} + ${renderGuidePanel("guide.training_title", ["guide.training_1", "guide.training_2", "guide.training_3", "guide.training_4", "guide.training_5", "guide.training_6", "guide.training_7", "guide.training_8", "guide.training_9", "guide.training_10", "guide.training_11", "guide.training_12", "guide.training_13", "guide.training_14", "guide.training_15", "guide.training_16", "guide.training_17", "guide.training_18", "guide.training_19", "guide.training_20", "guide.training_21", "guide.training_22", "guide.training_23", "guide.training_24", "guide.training_25", "guide.training_26", "guide.training_27", "guide.training_28", "guide.training_29"], "", { t })} +
+ +
+ ${renderGuidePanel("guide.archive_title", ["guide.archive_1", "guide.archive_2", "guide.archive_3", "guide.archive_4", "guide.archive_5"], "", { t })}