cloneEntryData relies on a spread plus a hand-written list of the nested
containers to copy. Nothing tied that list to the real types, so adding a
reference-typed field to an entry - or a new entry type - would silently
produce a clone that aliases the new field back to the source, which is the
exact bug the custom clone exists to avoid.
Two compile-time guards, both verified by temporarily mutating the shared
types:
- ClonedReferenceFields declares, per entry type, the reference-typed fields
the switch copies. UnclonedReferenceFields diffs that against the fields
the types actually have, computed via ReferenceKeys. Adding
`attachments: string[]` to OntimeEvent now fails with
`Type '"attachments"' does not satisfy the constraint 'never'`, naming the
offending field. Adding a primitive field stays silent, since the spread
already copies it by value and no action is needed.
- A default branch in the switch asserts the entry is never. Adding a
SupportedEntry member fails with `Type 'OntimeMarker' is not assignable to
type 'never'`, and separately at the ClonedReferenceFields index, which is
no longer total.
Branded primitives such as Day (number & Brand<'day'>) correctly classify as
primitives, so they are not flagged.
Typecheck, lint, format and the full suite (695 tests) pass unchanged.
Overload createTransaction() on the literal mutableRundown option: with
mutableRundown: true it returns rundown: Rundown as before; otherwise it
returns rundown: DeepReadonly<Rundown> (ts-essentials, already a convention
in this codebase for read-only snapshots).
Previously a non-mutable transaction's rundown was typed as plain Rundown
even though it's the live cachedRundown reference itself (or a background
rundown read straight from disk) - nothing stopped a future mutation
function from writing into it outside the commit() flow, since
Readonly<T> (used elsewhere for the same purpose) only blocks top-level
reassignment, not nested writes like array.push() or entry.field = x.
Scoped to rundown.dao.ts only: every existing call site in rundown.service.ts
passes a literal mutableRundown: true, so this changes no call-site types.
The one mutableRundown: false site doesn't destructure rundown at all.
Verified with a throwaway probe file (removed) that mutating a non-mutable
transaction's rundown is now a compile error, and that a mutable one still
compiles as before. Typecheck, lint and full test suite (695 tests) pass
unchanged.
structuredClone's generic serialization algorithm does far more work than
plain object spreads need for these known shapes. Adds cloneEntryData()
and cloneRundown() as drop-in replacements (same "independent copy" contract,
same call sites) and swaps them in everywhere a rundown or a single entry
was being deep-cloned via structuredClone:
- createTransaction()/init() in rundown.dao.ts - the main per-mutation clone
- DataProvider.setRundown() - was re-cloning the whole rundown a second time
on every single commit
- rundown.service.ts background-rundown clones (custom field rename/remove,
duplicateExistingRundown)
- the per-entry clone in processRundown's non-mutating path (rundown.parser.ts)
- mergeRundownPreservingFields's per-entry clone
Also:
- safeMerge() (DataProvider.utils.ts) was deep-cloning the entire DatabaseModel,
including all rundowns, just to read a handful of small config properties
that never touch rundowns - it now only clones the properties it actually
merges.
- sheets.service.ts's per-row clone before building a (read-only) Google
Sheets cell request was unnecessary and is removed.
- runtime.service.ts's previous-state snapshot for eventNow/eventNext/
eventFlag/groupNow now uses cloneEntryData.
Benchmarked on a synthetic 1000-event rundown: a cue-only edit (no
reprocessing needed) went from ~5ms to ~1.2ms end to end, including the
DataProvider clone. Verified against the existing test suite (695 passing)
plus typecheck and lint.
* 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