feat(layouts): switch track layout mid-practice and view stats per layout

A club can run one Open Practice all season and re-lay the track now and then.
Previously a session's layout was frozen at start and every training lap inherited
it, so a season-long practice stayed on its first layout forever, and changing layout
was hidden under Settings -> Appearance.

- Each practice lap now counts toward the layout active when it was driven; a running
  practice session moves onto a new layout when one is added
- "New layout" strip on Training stats, Race Day and the Dashboard, offered only around
  practice; a running race session blocks layout changes everywhere
- "Stats for" selector (current / older layout / before layouts / all layouts) on
  Training stats and in the driver's mobile profile (?layout= on the public API)
- A layout that has practice laps on it can no longer be deleted
- Fix: the buildDriverTrainingStats wrapper in app.js dropped extra arguments

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
larssand
2026-09-27 21:24:44 +02:00
parent 196437d25b
commit 627179b95a
22 changed files with 454 additions and 44 deletions

View File

@@ -569,6 +569,13 @@ Owns:
- `piper_speech.js`: `speakWithPiper` fetches the server's WAV and plays it; on any error or after 2.5 s it calls the browser-voice fallback instead. `speakTextHelper` (`runtime_services.js`) routes through it when `settings.ttsEngine === "piper"`, so legacy speaker cues and the race-audio engine both use it
- `tts_settings_view.js`: the "Voice engine" panel in Settings -> Audio (engine choice, sv/en install status, install / pre-generate / test buttons)
### `src/track_layout_bar.js`
Type:
- shared UI strip
Owns:
- "Track layout: <label> - since <date> [New layout]" on Training stats, Race Day and the Dashboard; the button prompts for a name and calls the injected `addTrackLayout` (which refuses while a race session is running - `findRaceSessionBlockingLayoutChange` in `track_layouts_logic.js`)
### `src/misc_views.js`
Type:
- misc page renderers
@@ -1899,10 +1906,12 @@ 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
trackLayoutId: string, // "" if no track layout is configured; stamped at
// commitStart() to whichever layout is active then, and
// moved to a new layout if one is added while a practice
// session is running. Training laps don't inherit it - each
// lap is tagged with the layout active when it was driven
// (syncTrainingLedger)
...additional race/session fields
}
```