fix(report): correct run timestamps, scoping and recovery

Review of the run history feature turned up four defects, all in the
code that feature introduced.

- Runs were dated with a time of day rather than a wall clock instant.
  `clock` and `rundown.actualStart` are millis since midnight, so every
  run in the history list rendered as 1 January 1970, and getLatestRun
  ordered runs incorrectly across days: a run at 09:00 today sorted below
  one at 20:00 last week, so the "last run" chip and /runs/latest could
  return an older run. Runs now take `_startEpoch`, the instant the show
  began, and the type documents the invariant.
- A stop arriving with no run open is ignored. Loading a project stops
  playback and reinitialises reporting in the same tick, and the trailing
  stop was attributing the previous project's event to the new one.
- Run summaries counted planned events from the loaded rundown rather
  than the rundown the run belongs to, so switching rundowns mid-run gave
  the summary a denominator from a different rundown. A run whose rundown
  has since been deleted now keeps its last known count instead of
  dropping to zero.
- Recovery of runs left open by a crash only closed the first one. Any
  others stayed dangling forever: permanently shown as ongoing and never
  eligible as the latest run.

Also replaces the raw ISO default run label with a readable local date
and time, and adds a batching upsertRuns to the store so recovery writes
the sidecar once rather than racing one write per run.

Each fix has a regression test, verified to fail without the change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nr3FbLbM8gB8Jm771YgTV
This commit is contained in:
Claude
2026-08-08 16:21:00 +00:00
parent 3c2875a956
commit 8551c161f1
5 changed files with 302 additions and 27 deletions
@@ -41,9 +41,17 @@ export type ShowRun = {
* is renamed or deleted.
*/
rundownTitle: string;
/** user editable, defaults to a formatted timestamp */
/** user editable, defaults to a formatted local date and time */
label: string;
/**
* Wall clock instant (milliseconds from epoch) the run began.
* Not a time of day: runs must be datable and orderable across days.
*/
startedAt: number;
/**
* Time of day the last event in the run finished, or null while the run is
* open. Only ever compared against the per event times in the same run.
*/
endedAt: MaybeNumber;
report: OntimeReport;
summary: RunSummary;