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>
This commit is contained in:
larssand
2026-09-10 17:41:10 +02:00
parent 15b574a37d
commit f0838bc5db
14 changed files with 464 additions and 36 deletions

View File

@@ -112,7 +112,7 @@ const buildTeamStintLog = (session, row) => buildTeamStintLogLogic(session, row,
const getSessionGridEntries = (session) => getSessionGridEntriesLogic(session, { state, t, getEventTeams, getDriverDisplayById, getSessionGridOrder });
const getSessionGridOrder = (session) => getSessionGridOrderLogic(session);
const ensureSessionDriverOrder = (session) => ensureSessionDriverOrderLogic(session, { getSessionEntrants });
const buildFinalStandings = (event) => buildFinalStandingsLogic(event, { getSessionsForEvent, buildLeaderboard });
const buildFinalStandings = (event) => buildFinalStandingsLogic(event, { getSessionsForEvent, buildLeaderboard, getSessionEntrants, t });
const getManualCorrectionSummary = (row) => getManualCorrectionSummaryLogic(row, { formatLap });
const renderTeamStintLog = (session, rows) => renderTeamStintLogHelper(session, rows, { t, escapeHtml, buildTeamStintLog, formatTeamActiveMemberLabel, renderTable, formatRaceClock });
@@ -923,6 +923,8 @@ const TRANSLATIONS = {
"events.reseed_done": "Kommande kval heat reseedade från aktuell ranking.",
"events.no_reseed_done": "Inga kommande kval kunde reseedas.",
"events.reseed_locked": "{count} heat hoppades över eftersom manuell grid är låst.",
"events.reseed_mismatched": "Antal deltagare matchar inte längre antal genererade heat i {count} omgång(ar) - generera om kvalet istället för att lita på en delvis reseed.",
"events.dns": "Ej start (DNS)",
"events.reserve_bump_slots": "Reservera bump-platser i finaler",
"events.bump_reserved_note": "Om bump används kan finalgeneratorn reservera platser i högre finaler redan från start.",
"events.actions": "Åtgärder",
@@ -2429,6 +2431,8 @@ const TRANSLATIONS = {
"events.reseed_done": "Upcoming qualifying heats reseeded from current standings.",
"events.no_reseed_done": "No upcoming qualifying heats could be reseeded.",
"events.reseed_locked": "{count} heats were skipped because manual grid is locked.",
"events.reseed_mismatched": "The number of participants no longer matches the generated heats in {count} round(s) - regenerate qualifying instead of relying on a partial reseed.",
"events.dns": "Did not start (DNS)",
"events.reserve_bump_slots": "Reserve bump slots in finals",
"events.bump_reserved_note": "If bump-up is used, finals can reserve slots in upper mains from the start.",
"events.actions": "Actions",