Commit Graph

11 Commits

Author SHA1 Message Date
larssand
1565baab2a Add staggered-start car call-outs and a per-driver done indicator
Staggered-start qualifying heats now announce each car's transponder
number in grid order (~3s apart) right after the official start,
matching what a track announcer does when releasing cars one at a
time; mass/position starts are unaffected. The Timing leaderboard and
every overlay variant now also show a green "Done" flag next to a
driver's name once their own personal timing window (which already
started at their own first crossing) has elapsed, even while the
session is still running for other drivers.

Also confirmed two things already worked correctly: finals already
always start everyone together with the shared countdown/lights/tone
sequence, and practice's time-up announcement already says "finish
the lap you are on" - only a "2 minutes remaining" cue is genuinely
missing from the existing 1min/30s/10s ladder, left as an open
question for the user.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 21:31:01 +02:00
larssand
b1b65a9cf9 Add moving a driver between qualifying heats
The user asked whether a lone driver left in the last heat (e.g. 7
drivers at 3 per heat) could be moved into another heat manually.
Investigated first: neither "Reseed" (redistributes every not-yet-run
heat by current standings, not a targeted move) nor the grid editor
(reorders start position within one heat, never moves a driver
between heats) could do this - there was genuinely no path to it.

moveDriversIntoQualifyingHeat (src/event_race_logic.js) adds the same
"checking them elsewhere is the move" pattern already used for team
rosters: a new driver checklist on the qualification session edit
form pulls a checked driver out of whichever other heat they're
currently on, scoped to the same round only ("Q2 H2" never touches
"Q1"/"Q3", since heat composition can legitimately differ round to
round after a reseed). The checklist names the driver's current heat
before saving, mirroring the team-roster warning.

Also confirmed, per the user's earlier questions, already correct
with no changes needed: practice's per-driver "best 3 laps"/"best
5-minute result" stats, and that heats are already seeded fastest-
first from practice results. Flagged one real, deliberately unbuilt
gap while investigating: a true per-driver rolling "5 minutes from
your own first crossing" qualifying *heat* clock isn't supported -
only the practice stats work that way today.

Verified with new unit tests for moveDriversIntoQualifyingHeat and
the extracted parseQualifyingHeatName helper, and a real headless-
browser run reproducing the exact reported scenario end to end
through the actual UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 18:18:01 +02:00
larssand
e9883188fe Test the DNS fix against the other qualifying points systems too
The user asked whether the DNS fix had actually been tested against
the different points systems, not just the default one. It hadn't -
buildQualifyingStandings had never been exercised end to end with
qualifyingPointsTable "field_desc"/"ifmar" (high-points-win scoring)
or qualifyingScoring "best" (ranked by time, not points) at all,
DNS or otherwise.

Added two tests: a DNS driver scored under the high-points field_desc
table (confirms the DNS row still ranks last under an inverted sort
direction, and isn't scored as either zero or the winner's points),
and one under "best time" qualifying (confirms a bad DNS heat can't
contaminate a driver's genuine best result, since bestRank/
bestRoundMetric take the driver's single best heat, not an average).
Both passed immediately, confirming the fix already handled these
correctly - this closes a real, previously-untested gap in confidence
rather than fixing a new bug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 17:48:23 +02:00
larssand
3232dff25a Add regression test: a DNS heat must not drop a driver's other heats
The user asked specifically to confirm this: a driver who DNS'd in one
qualifying heat but raced normally in others must still be fully
counted across all of them, not dropped. Traced through the fix
(buildQualifyingStandings backfills a DNS row per finished heat,
accumulating into the same per-driver entry across every heat they're
in) and confirmed it already handles this correctly - added a test
that exercises exactly this scenario (DNS in heat 1, wins heat 2 and
3) to lock it in, since this specific multi-heat aggregation path had
no prior coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 17:45:53 +02:00
larssand
f0838bc5db Fix qualifying/finals/bump-up/judging/pause-timing bugs
A thorough audit of practice/qualifying/heats/finals, bump-up,
judging and pause/timer code (requested by the user) found several
real, concrete bugs, fixed together:

- Qualifying reseed sorted heats as text ("H10" before "H2"),
  scrambling grids at 10+ heats; now sorts numerically and reports a
  clear warning instead of silently dropping/ghosting heats when the
  participant count changes.
- A driver with zero laps in a finished heat/final (DNS) vanished
  entirely - not just from that round, but silently from every future
  reseeded round and the final grid. Now backfilled as a real,
  last-place "DNS" row.
- Bump-up did nothing at all with "reserve bump slots" off (mains
  were already seeded full, so the old capacity check always
  blocked it), and a promoted driver stayed double-entered in their
  old main's upcoming leg. An oversized bump count could also blow
  the main-letter naming past "Z". All three fixed.
- Judging undo restored an absolute snapshot instead of reversing its
  own delta - undoing two penalties out of order could leave a live
  penalty behind two "undone" log entries. Now always nets correctly
  regardless of undo order.
- A competitor's elapsed time didn't exclude paused time (only the
  session clock did), making a paused heat's results incomparable to
  an unpaused one in qualifying ranking. Finishing while paused also
  dropped the final pause segment, so the displayed elapsed time kept
  growing after finish. Both fixed.

Verified with new unit tests for every fix (including a new
session_control.test.js - that file had zero prior coverage) and a
real headless-browser run confirming the judging-undo and bump-up
fixes end to end through the actual UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 17:41:10 +02:00
larssand
15b574a37d Fix Team Race duplicate-membership bug and two related team-roster gaps
Reported by the user: creating a team, adding the wrong driver, then
moving that driver to another team by ticking them on the new team's
edit form left the driver on both teams at once. Root cause: there
was never a "move" action - the edit form only ever added a driver to
the team being saved, never removed them from wherever they already
were. Live laps from a driver on two teams were then silently
credited to only the first-matching team, with no clear error.

reassignTeamMembers (event_race_logic.js) now strips a driver/car
being saved onto one team out of every other team in the same event,
and the team checkbox list shows which team a driver is already on
before you move them.

Also fixed two related gaps found while investigating, at the user's
request to fix everything: getTeamDriverPool had regressed to
ignoring the event's class/participant scoping entirely (showing
every driver in the club), and deleting a driver or car left them
behind in team rosters forever. Added a duplicate_team_members Race
Day preflight check as defense in depth for any already-corrupted
saved state.

Verified with new unit tests for reassignTeamMembers, getTeamDriverPool
and the new preflight check, plus a new permanent browser test
reproducing the user's exact reported scenario end to end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 17:07:45 +02:00
larssand
94794350e5 Add a results archive and purge orphaned passing rows on delete
Until now there was no way to find a completed session without already
knowing which event it belonged to - navigation was strictly Events ->
one event -> its sessions. A new "Resultatarkiv" view lists every
finished session across all events, searchable by event/session name,
class, driver and date range.

Opening a session reuses the existing single-session leaderboard
builder for standings and the existing per-competitor lap lookup for
lap history - no new results logic - plus a session-wide penalties/
corrections list (resultsBySession[id].adjustments), previously only
visible in the live judging action log. Export buttons reuse the
existing per-event PDF and race-package JSON export functions rather
than adding a new format.

Investigated what should happen to raw timing data when a race is
deleted. Training statistics and session replay already read from
their own independent, deliberately-retained copies that survive
deletion by existing design - unaffected either way. The one thing
that outlived deletion with no purpose was the raw `passings` SQLite
rows (write-only; nothing in the app reads them), growing forever with
driver IDs never purged. A new POST /api/passings/purge endpoint now
removes exactly the deleted session(s)' rows, called as a best-effort
side call from both delete-event and delete-session.

Also fixes a real bug found while building this: ensureSessionResult
guarded adjustments/splits/pitEvents/pitStops against being missing on
an already-existing result, but not passings itself, so viewing lap
history for such a result (e.g. an older/imported race package)
crashed outright.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 15:49:10 +02:00
larssand
5f79228941 Add configurable manifest-driven race audio system 2026-08-28 13:29:27 +02:00
larssand
abf887dccd Split- och depåstatistik är implementerad 2026-08-25 20:00:55 +02:00
larssand
2e6866a013 Add simplified Open Practice flow and reorganize 2026-08-24 19:25:21 +02:00
larssand
0ca1679e2a Add race timing and judging regression tests 2026-08-24 18:58:40 +02:00