Turns the reporter from a live-only, single-run curiosity into a
persistent, per-project, per-rundown history of runs.
- Extend OntimeEventReport with a schedule snapshot (scheduledStart,
scheduledDuration) taken when an event starts, plus playCount. Reports
are now a record that survives later rundown edits, rather than a live
join against the current rundown.
- Add a run lifecycle: a run opens on the first event start and closes on
a full stop, archiving the previous run to history so the next start
begins fresh.
- Persist runs to a per-project sidecar file (services/report-service),
patterned on the existing restore-service, so a crash or restart no
longer loses the whole show's record. Runs are scoped by rundownId for
multi-rundown projects, cascade-deleted with their rundown or project,
renamed alongside a project rename, and deliberately not copied when a
project is duplicated.
- Extract the over/under/on-time variance and run summary maths shared by
the rundown chip, the report settings panel, and the server into
ontime-utils (getEventVariance, getRunSummary, countPlannedEvents).
- Extend the report API with run history endpoints (list, get, latest,
rename, delete) while keeping GET /report's existing shape so Companion
and HTTP automations are unaffected.
- Replace the settings report table with a run browser: a list of runs
with a rundown filter, inline rename, delete, and a detail view with
per-run summary stats and CSV export.
- Add a third, muted state to the rundown event chip: an event with
nothing in the current run yet previews how it went last time.
Fixes a real bug found while testing the new store: emptyStore() was a
shared object, so its runs array leaked mutations across project loads.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nr3FbLbM8gB8Jm771YgTV
* fix: issue where a count-to-end would lead to incorrect expected times
* fix: include add time in overtime when countToEnd
* fix: ui and server use same calculation for expected end
The server serves html/timer-legacy.html and html/login.html from disk
at runtime, relative to the bundled server. The Docker image copies
these files but the electron packaging did not, so the view 404ed in
all desktop distributions.
Additionally, AppImages mount at /tmp/.mount_*, a hidden directory.
Express sendFile refuses paths containing dot-segments by default
(returns 404 without touching disk), so the view failed on Linux even
with the file packaged. Allow dotfiles for this route; the request
path is fixed so no user input is affected.
The Stage Timer and PiP Timer views relied on `align-content: center` to
vertically center the timer digits inside `.timer-container`, which is a
plain block element. Aligning children of a block container via
`align-content` only works in Chromium 123+ (Firefox 125+, Safari 17.4+).
Embedded browsers such as vMix Browser Input (CEF V115) and other older
CEF/Chromium-based production tools ignore the property, so the digits
fall back to the top of the container while every other element renders
correctly.
Make `.timer-container` a real flex column and center with
`justify-content: center`, which is universally supported and matches the
centering approach used elsewhere in the codebase. The removed
`justify-self`/`align-self` were no-ops on a full-width flex item.
Fixes#2126
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T6ENZ3r6JXZb1fpYw2oNY
Surface {{groupNow.*}} template variables (title, note, colour, times,
custom fields) in the automation template autocomplete so events inside a
group can reference their group. The runtime store already carries
groupNow, so substitution and filters worked already; this makes it
discoverable.
Extend the message-secondary action with an optional text field so an
automation can set the secondary message content, not just its source.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZYPZVdZLWowU7DyzGkWyy