The badge painted the entry colour straight into the background and left
the text colour fixed, so a light entry colour rendered an unreadable
number. Every other place that tints an element by entry colour goes
through getAccessibleColour, which picks the text colour to match.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
The three-variant result union differed only in whether an entry had a
cue, an event index and a parent, and the UI narrowed it with runtime
property checks anyway, so the discrimination bought nothing while
costing three near-identical branches to build a result. One flat type
removes those branches and the checks around them.
Skipping non-searchable entries asked whether an entry was a group or a
milestone, then asked again when building the result. It now asks once
whether it is a delay, which is what the guard actually meant.
The search functions move out of the memo closure to the module, where
they are ordinary pure functions.
Whether to show the matched field compared against its display label, so
a custom field labelled "Title" would have had its match hidden. It now
compares the field key.
Drops the review document, which had served its purpose, and reduces the
finder tests to a single one covering the path a user takes: open from a
focused field, find an entry by its note, scope with a badge, and reveal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
The filter badges wrote a keyword prefix into the search box, which put
syntax in front of the user for something the badge already expresses.
They are now toggles: the input holds only what is being searched for,
the active badge is highlighted, and pressing it again returns to
searching every field. Typing a keyword still works and lights up the
badge it refers to, so the documented syntax is not lost.
The result list said nothing when it had more matches than it rendered.
It now ends with a count of what is hidden, since the footer total is
easy to miss while scanning results.
Also stops the search shortcut toggling the dialog. Toggling on a key
that mounts and unmounts the dialog is unreliable, and browsers treat a
repeated find shortcut as "focus the search again". It now opens, and
selects the existing query when the finder is already up, leaving
Escape to close.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
The close-shortcut test sent its second keystroke as soon as the input
was visible, which is before a dialog with a focus trap has settled.
Waiting for focus instead asserts the same behaviour against a dialog
that is actually ready to receive it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
Marks the correctness and search-depth phases as delivered, notes what
is still outstanding inside them, and records the measured cost of
widening the scan. Frames the review section as the state at the time
of writing, since several findings no longer describe the code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
Searching only matched titles, so an event was unreachable by its cue,
its note, or any custom field value. Custom fields are how teams model
their own show data, which made the most valuable data the least
findable.
A bare query now matches cue, title, note and text custom fields across
events, groups and milestones, and each result names the field it
matched with an excerpt, so a hit in a note is legible. Widening the
scan is free: at 5000 entries a pass over every field measures the same
as the previous title-only pass, both far below the render cost.
The filter syntax was only discoverable through a line of footer text.
It is now a row of badges built from the fixed fields plus the project
custom fields, which scope the search while keeping whatever the user
already typed. The input becomes controlled, which removes the effect
that replayed the last search on rundown changes.
Raises the result cap and reports the total, since matching more fields
means more results than the previous cap could show.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
Two defects surfaced while reviewing the feature.
The search shortcut was dead while any input had focus. The hotkey hook
skips input elements by default, so the shortcut did nothing while
editing an entry title, which is exactly when a user reaches for it.
Opt out of that behaviour and drop the local handler that partially
worked around it, so a single binding both opens and closes.
Milestones were skipped by the cue search even though they carry a cue
and display it in the rundown, so filtering by cue could never find one.
They are already covered by the title search.
Adds e2e coverage for both, and fires the shortcut from a focused input
in the existing keyboard test so the first defect stays fixed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
The unit-test job runs format:check before the tests, so an unformatted
markdown file failed the job while every test passed.
Promote the bold pseudo-headings in the Phase 3 section to real
subheadings. They previously sat directly above numbered list items,
which the formatter reads as one paragraph and collapses into a run-on
line, mangling items 13-27.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
Track the highlighted result by entry ID rather than by list index.
The index was never reset when results changed, so a background
refetch that shrank the list left it pointing past the end and
selecting threw on an undefined entry. Resolving by ID falls back to
the first result instead, and keeps the user's position across
rundown edits.
Submit the entry belonging to the clicked row instead of whichever
row was highlighted. The two only agreed because a mousemove usually
precedes a click, so touch input navigated to the wrong entry.
Ignore pointer moves that do not change the cursor position: scrolling
the list under a stationary pointer fires a move event which pulled
the selection away from the keyboard cursor. Keep the highlighted row
scrolled into view while navigating.
Close the finder on the search shortcut. Mantine ignores hotkeys while
an input is focused, so the global toggle could not close the modal
once the user was typing. The global Escape handler is dropped: the
dialog already dismisses on Escape, and registering it document wide
conflicts with inline field editing.
Drop the bounds check in the index search, which compared an event
ordinal against the count of all entries. The loop below it already
returns no results when no event carries that index.
Add e2e coverage for clicking a result, picking one with the keyboard,
and closing with the shortcut. This needs the finder rows and the
rundown event row to expose test ids and selection state.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
The review stated the finder had no e2e coverage and that
209-rundown-shortcuts.spec.ts never opened it. Both are wrong: a
"Find in rundown" smoke test opens the modal and closes it with
Escape. Describe the coverage that exists and what it leaves unguarded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
Reviews the existing rundown Finder and sets out a phased roadmap.
Findings:
- bare queries search title only; note and custom fields are never
searched, making custom field data unfindable
- selected index is never reset when results change, so a shrinking
result list can dereference undefined
- row clicks read selection from state rather than the clicked element,
so touch input navigates to the wrong entry
- no visible affordance anywhere, leaving the feature unreachable on
touch devices where no keyboard is available
- cuesheet already registers a scroll handler for finder jumps but the
Finder is never mounted there
Roadmap covers correctness, search depth, visibility and polish,
cuesheet reach, and a find-and-replace expansion. Records the
constraints that shape the work: cuesheet URL preset column permissions,
index memoisation on rundown revision, and the absence of any undo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
* 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