Skip to content

Timed speaker notes on the stage display - #5

Merged
KyleJamesWalker merged 4 commits into
mainfrom
feat/timed-speaker-notes
Sep 3, 2026
Merged

KyleJamesWalker merged 4 commits into
mainfrom
feat/timed-speaker-notes

Conversation

@KyleJamesWalker

Copy link
Copy Markdown
Owner

A cue's notes become a list, each one starting at a point inside the cue. The
stage display shows the note that is due, under the timer, where the speaker
can read it.

What changed

  • room: a cue carries notes as a list of {at_ms, text}, sorted by time.
    Ten notes of 500 characters each. The single string older rundowns and
    snapshots carry still loads as one note at the cue start.
  • rundown_io: JSON carries notes as [{at, text}], where at reads
    5:00 or a bare 5. The CSV column carries the same list as text, one note
    per line opening with its time, an indented line continuing the one above.
    An import over 100,000 characters of notes gets a refusal.
  • viewer: the note sits under the timer, wraps over as many as five lines,
    and changes as the time left reaches each note.
  • console: the cue editor holds one row per note. Each rundown row says how
    many notes a cue holds.

Why

A note was one clipped line in the footer, and the only way to write one was
the Edit form. The toggle for it read as an unfinished feature.

Authoring in time-in and reading in time-remaining is the point. An operator
who buys the speaker two more minutes moves every later note with them, and no
stored note changes. Per-change rationale is in the commit bodies.

Breaking: cues[].notes is a list in the state frame, where it was a
string. Anything reading a cue note over the API needs the new shape.

How it was tested

make lint and make test are clean: 360 Rust tests and 115 JavaScript
assertions, including the picker at every threshold and the added-time case.

Drove a real browser against a live binary: the note at 30:00, 25:00 and 5:00
remaining and into overtime, a CSV round trip carrying a multi-line note, the
row editor adding and removing notes, and both the room toggle and ?notes=0.

A cue carried one note. It now carries a list, and each note starts at a
point inside the cue.

`at_ms` is time into the cue, which is how a running order reads. The
screen converts to time remaining, against the cue's planned length. So
an operator who gives the speaker two more minutes moves every later
note with them. Nothing stored changes.

An import takes a list of `{at, text}`. `at` reads `5:00`, or a bare `5`
for minutes, the way `duration` does. The one string older rundowns and
snapshots carry still loads. It becomes a single note that starts with
the cue. An error names the note that failed.

The CSV column holds the same list as text, one note per line opening
with its time. An indented line continues the note above it, so a note
keeps its line breaks. A line opening with a bare number stays text.
"3 things to cover" is prose, not a cue point three minutes in.

A cue holds 10 notes of 500 characters. An import carrying more than
100,000 characters of notes gets a refusal. All of it rides in every
state frame to every client, and a slow client holds a backlog of them.

BREAKING CHANGE: `cues[].notes` is a list of `{at_ms, text}` in the
state frame, where it was a string. A controller that reads a cue note
needs the new shape.
The note moved from the footer to under the timer, where the speaker
can read it. It wraps, keeps the operator's line breaks, and gives up
after five lines. The old style was one clipped line at 40vw, so a 34
character note already lost its end.

`currentNote` picks the note that is due. It runs with the readout
rather than on a state frame, because the answer changes with the time
left. It clamps remaining to the cue's planned length. Otherwise time
added before the start would delay the opening note. A cue with no
length shows the opening note, which covers count-up and time-of-day.

Picking the note is display work, so it lives in the browser. No server
tick has to know about it.
The cue editor held one Note field. It now holds a row for each note.
Each row carries the time into the cue, the text, and a remove button.

A new row lands five minutes after the last, which is where an author
is working. A time that does not parse keeps the form open and says so.
It does not drop the note.

Each rundown row now says how many notes a cue holds. The notes sat
behind Edit with nothing pointing at them. That is how the toggle for
them came to look like an unfinished feature.
operations.md gains a section for the notes. How the time reads, what
the screen does with it, and both document shapes. The ceilings entry
carries the new numbers.

architecture.md records the decision worth keeping. A note carries
time-in and reads as time-remaining. `currentNote` holds that rule in
the browser, because no server tick needs it.
@KyleJamesWalker
KyleJamesWalker merged commit 1d02f3b into main Sep 3, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant