Commit Graph
18 Commits
Author SHA1 Message Date
Waters ChurchandClaude Fable 5.1 a7e40ee1a8 Layouts keep every tile they are given, and show what is on offer
The bug behind this: for seventeen days the server rebuilt each saved tile
from the fields it knew and dropped the rest — including `variant`, so a
layout of five different timers came back from the server as five "All
timers". The client had carried `variant` since multi-tile modules arrived;
the server was the one that lagged, and it is the part of ProdDash that
always will (a booth updates weeks after the dev machine). So the server
stops deciding which tile fields exist: it checks the shape of the ones it
knows, keeps any other small plain-JSON field a tile carries, drops only
retired ones, stamps the file `v: 2`, and refuses a layout over a hundred
tiles instead of quietly saving the first hundred. A test round-trips a
fully-populated tile plus one the server has never heard of, and fails if
anything is lost.

Then the rule the arrangement is loaded by. A layout keeps every tile it is
given; the board shows the ones whose module is installed and enabled and —
for a tile added from a picker entry — whose entry is offered right now. The
rest keep their place and draw nothing, and appear the moment the registry
offers them again: a Service timer waits for PCO Plan, a transcript for its
channel. Never the module's default in their place. Because a module's entry
list can change with no event to mark it (PCO Plan comes back and, seconds
later, the timers module is offering its timers again), the registry is
re-read every few seconds while anything is folded away, and not at all
otherwise. The timers module's placeholder card for a missing solo timer
goes with this: nothing is drawn, the dot says why.

Loading says what it did — "Loaded 'FOH': 9 tiles, 2 not shown — Service,
#ops aren't offered right now" — with Undo in the message. Saving over a
name asks first, and the server keeps the copy it replaced one back; Admin →
Saved layouts gets Restore previous, which swaps the two so a restore is one
restore from undone. The browser keeps one good copy of its own layout
behind the live one and falls back to it when the live one will not parse.

Shell 1.8.0, timers 1.4.1. The layouts already saved on the booth machines
had their variants stripped before this and have to be saved again from a
browser that still holds the right tiles — nothing here recovers what the
files no longer contain.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 12:02:34 -04:00
Waters ChurchandClaude Opus 5 12f0b20e5f Rules for a clean interface, and the modules brought to them
Seven rules join the four in the module guide, as rules 5 to 11: no
explanatory text in a tile's gear and no label over three words; server-side
settings in configSchema only; a tile setting changes that tile alone;
hide-toggles ungrouped when there are four or fewer and in a folded "Show"
group when there are five or more; title-bar controls are icons with
tooltips, never names; anything needing words belongs in the gear; and one
control per action, either place but not both.

Two passages said the opposite and now say the same thing — "keep labels
short and put the explanation there" is scoped to configSchema, and "icon
buttons, short labels" becomes icons with no labels at all. CLAUDE.md
carries the summary.

Then the modules. Nothing here changes a setting key or a default, so saved
layouts, named layouts and every tile already on a wall keep working; group
names, labels, help and fold state are presentation and are not stored.

- pco-plan: sixteen labels cut to three words or fewer ("Tint items by type
  (song / media / other)" is now "Tint by type"), "Show / hide" renamed
  "Show" and folded. Its one remaining help line stays by request: the top
  bar's template needs its separator syntax spelled out, which the variable
  chips beside it cannot convey, so rule 5 carves out template fields.
- propresenter-timers: the "Timers ▾" menu was the only named control left
  in any title bar. It is a stopwatch icon now, with the count riding
  alongside it while some timers are hidden — setLabel() writes over the
  button's contents, so the count needed ProdCom's badge treatment rather
  than a label. "Show" folds; the Colours help goes.
- propresenter-now-next: "Show" folds, in the manifest and in the Now
  entry's own copy.
- prodcom-transcript: "Show channel icons" loses the redundant verb.

slack and clock were already compliant and are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:22:06 -04:00
Waters ChurchandClaude Fable 5.1 f29e43373b Timers: cards keep their text when grouped by source
The sizing budget counted rows as if every card sat in one grid unless
headings were showing, so a grouped tile without headings got digits
sized for fewer rows than it had and the lines below were cut off. Rows
now follow the grouping itself; each group's share of the height is the
exact height its rows, gaps and heading take, so every row is the same
height; and after the layout is applied each card's real height is
measured and digits that would still spill are shrunk to fit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 13:25:37 -04:00
Waters ChurchandClaude Fable 5.1 dde9a00ee1 Timers: "Group by source" on the All timers tile, off by default
Off, every timer sits in one grid filled row by row — a wrapping flex row
rather than a CSS grid, so the last row's cards widen to use the whole
width and nothing is left empty; a source in trouble is named in one line
above the cards, and its own cards dim. On, the sections per source come
back, and "Source headings" appears (showWhen) to hide their titles.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 12:56:22 -04:00
Waters ChurchandClaude Fable 5.1 818def822d Shell: tooltips at once, one or two words; text-size buttons are drawn − and +
Every header and top-bar button's title becomes a tip the shell shows the
moment the pointer arrives (the browser's own waits a second and looks
different everywhere); it moves from `title` to data-tip so the native one
never appears, and a module that sets `title` again is picked up on the
next hover. The shell's own labels are "Settings" and "Remove"; the top
bar's are "Fullscreen", "Add tile", "Layouts", "Admin"; Slack, PCO Plan,
ProdCom and Timers shorten theirs to match. A− / A+ are a drawn − and +
in Slack, PCO Plan and ProdCom, sized like every other header icon.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 10:49:58 -04:00
Waters ChurchandClaude Fable 5.1 efbcf960dd Timers: the gear chooses what a card shows, not which timers; a 24-hour switch; a date that fits
Which timers appear is the header's Timers ▾ menu's job, so the gear's
source switches (ProPresenter / LTC / module timers, the Clock on/off)
are gone. In their place the Show group offers the card's elements —
Names, Status lines, Detail lines, Seconds, Leading zeros, Source
headings; without seconds a countdown reads in whole minutes, rounded
up. The clock's format select is a 24-hour switch (an older layout's
"12h" choice is honoured); the clock card has no CLOCK label; Short day
and Short month appear once Date is on, and the date fits its card — one
line when it can, else a break after the day and a size that fits the
longer half. Solo tiles get the same element switches.

Shell: the settings window's grab bar is the accent colour and the gear
that opened it wears the same colour while it is open; showWhen works in
a tile's gear as it does on the admin page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 09:53:41 -04:00
Waters ChurchandClaude Fable 5.1 1db6c2c89f Timers: "to 9:30a" under a countdown-to-time, smaller status lines, titles that fit
A ProPresenter count_down_to_time timer's detail line reads "to 9:30a"
(or "to 21:30" on a 24-hour clock) instead of a sentence. The status and
detail lines under the digits are drawn at 0.8 of the title's size, and
fit() budgets them at that share. A title that would be cut off shrinks
to fit its card (measured off-screen, down to 8 px) instead of ending in
an ellipsis; a changed label re-fits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 09:05:56 -04:00
Waters ChurchandClaude Fable 5.1 b817e230d4 Timers: each picker entry carries the gear settings that fit its tile
An "All timers" tile keeps the full set — sources, headings, status lines,
the clock's format (now behind the Clock group's title-row switch), the
colours. A solo tile's entry carries only what its one card can use: a
ProPresenter or module timer gets Status lines and the four colours; the
LTC timecode Status lines and the three tones it takes (running, no
signal, stopped); the time-of-day clock its format, seconds and date
alone; a clock another module offers its format plus the idle tone.

Every field is still defined once, in module.json; server.js composes the
subsets from it. The client resolves an absent setting from its own
DEFAULTS rather than from whatever schema the tile has, so a slimmer
schema changes nothing about what a tile shows. Entry descriptions are
one-sentence picker tooltips now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 15:36:41 -04:00
Waters ChurchandClaude Fable 5.1 555bc482ce Timers: steadier menu, ellipsised labels, saner warning window
- The Timers ▾ menu's rows are rebuilt only when the row set or a switch
  changes, not on every snapshot — rows no longer vanish from under a
  click. Their on-state fill uses the shell's accent: the menu hangs off the
  title bar, outside the tile root, where the tile's colour properties
  don't reach.
- A long timer label ends in an ellipsis (the label is its own box inside
  the flex name row).
- The warning window is capped at half a timer's length, so a 20 s
  countdown isn't amber for its whole run (it warns under 10 s; a 25 min
  one under 2:30).
- Server: the change key that gates SSE frames ignores startedAt (derived
  from a whole-second value, it jittered ±500 ms per poll and doubled the
  frame rate); ProPresenter errors name the cause (ECONNREFUSED) rather
  than fetch's "fetch failed".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 13:24:46 -04:00
Waters ChurchandClaude Fable 5.1 bf1cabbc6b Timers: every timer and clock in the room, from any source
The Timers module becomes the one place a production tech looks for every
timer and clock: ProPresenter's timers as before, the LTC timecode, a
time-of-day clock (the Clock module's format / seconds / date, folded in as
a sub-part — modules/clock itself is untouched), and timers other modules
offer over the module guide's provider contract: PCO Plan's service /
current item / countdown, or anything else declaring provides: ["timers"].

Server: a small `sources` abstraction. Each source turns its own world into
the guide's standard timer objects and reports health — ProPresenter (the
existing poller, wrapped, behind ppEnabled), LTC (the existing listener,
untouched, as a kind:"clock" timer whose status.text is the timecode, with
fps/df/ageMs in an `ltc` extension), Clock, and one module source per
provider found in the shell's /api/modules over loopback (shell.port):
/timers/stream when it answers as SSE, else /timers polled every 2 s;
re-discovered every 30 s; backoff 3 s → 15 s; a provider's timers survive a
10 s grace and then go. PCO Plan sits behind pcoEnabled, the rest behind
moduleTimersEnabled. The module is itself a provider: GET /timers and
GET /timers/stream serve the merged set. The header comment says how to
add a platform in a dozen lines.

Client: one tile, every timer, grouped under source headings — label, the
big time derived locally from the snapshot and a server-offset-corrected
clock (so digits tick smoothly between snapshots), a status line, a detail
line. Countdowns count down, elapsed count up, clocks show the time; the
LTC card still counts frames. Feedback colours (running / warning near the
end / overrun / idle) are per-tile colour settings applied as custom
properties on the tile root, so they win over dashboard themes; left at
their defaults they follow the theme. Gear settings: Clock, ProPresenter
timers, LTC timecode, Module timers, Source headings, Status lines, the
clock's format / seconds / date; a "Timers ▾" header menu hides any single
timer (source:id, saved with the layout; the old hiddenItems and
timer:<uuid> / ltc picker variants keep working).

Manifest: 1.3.0, proddash >=1.4.0, provides timers, configGroups with a
toggle switch per integration (ProPresenter / LTC Timecode / PCO Plan /
Other modules) and group help; switch labels are nouns.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 13:03:30 -04:00
Waters ChurchandClaude Opus 4.8 5fe39539fc Timers: admin "Timer font" option with a monospace choice
A new Display-group select (Default / Monospace) sets the font for the
timer and LTC digits. Monospace keeps the numbers from shifting sideways
as they change — easier to read on a fast-moving countdown or timecode.
Flows through server state → client applies a `tm-mono` class and re-fits
the digit sizing; both timer and LTC values (.tm-time) honor it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 15:23:07 -04:00
Waters ChurchandClaude Opus 4.8 1d46657e06 Timers: LTC is listener-only; admin switch, device/channel, grouped settings
Simplify LTC to its one working source — the built-in audio listener — and
make the admin UI reflect it.

Module:
- Remove the stage-display websocket source (stage-ws.js, stagePassword)
  and the /v1/timecode/status API probe — both dead on ProPresenter 21.4.
- Remove the remote-reader ingest (POST /ltc, ingestToken, tools/
  ltc-reader.js): the module owns LTC end to end now. The decoder selftest
  moves to modules/propresenter-timers/ltc.selftest.js so the engine stays
  covered.
- composeLtc collapses to the listener: running/stopped/nosignal, or a
  clear note when disabled / no device / capture failed / starting.
- Manifest: ltcEnabled is now a `switch`; ltcDevice and ltcChannel carry
  showWhen:"ltcEnabled"; ProPresenter and LTC fields carry `group` labels.

Shell (admin) — backward-compatible additions, coordinate with the shell lane:
- New `switch` field type (boolean shown as the enable-toggle control).
- `group`: consecutive fields render under a labeled subsection.
- `showWhen: "<key>"`: a field is shown only while that boolean/switch is
  on (and actually collapses — .field[hidden] override, since .field's
  display:flex outranks the [hidden] UA default).
- applyConfigPatch coerces `switch` as boolean. Documented in MODULE-GUIDE.

Verified: engine selftest (29 checks); listener decodes BlackHole loopback
(running 30fps, ageMs); device picker lists inputs; POST /ltc now 404s;
admin shows two labeled groups, the switch, and device/channel appearing
only when the switch is on (computed display none↔flex confirmed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 13:29:06 -04:00
Waters ChurchandClaude Opus 4.8 7e75146838 Timers: real-time frame-accurate LTC display
The LTC card repainted only on server anchors (~4x/s), so the frame
counter jumped in ~7-frame steps. Now the client counts every frame
locally between anchors and re-syncs on each one — frame-accurate and
smooth, with no extra network traffic.

- Server anchors carry ageMs (how old the decoded timecode is) so clients
  can pin the anchor to their own clock. ltc-listener reports it while
  running; composeLtc passes it through (listener) or derives it (reader).
- Client does exact SMPTE frame<->timecode math (parseTc/tcToFrames/
  framesToTc, incl. 29.97 drop-frame minute counting) and advances the
  display in a requestAnimationFrame loop at the LTC's own rate.
- Robust when rAF is throttled (backgrounded tab): render() paints the
  extrapolated frame at the 4x/s anchor rate too, so the display never
  freezes — the rAF loop only refines it to every frame when visible.
- Anchor jitter can't tick the counter backwards (small regressions
  clamped; genuine seeks/re-strikes pass); freshness cap stops freewheel
  into phantom frames when anchors stop arriving.

Verified: SMPTE math against the encoder over 200k drop-frame frames plus
round-trips; the extrapolation pipeline simulated at 30/25/29.97DF showing
strict +1-per-frame advance and correct DF boundary crossing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 13:12:52 -04:00
Waters ChurchandClaude Fable 5 b95aa7dc34 Timers: built-in LTC listener, configured in the admin page
The LTC listener now lives inside the module: it spawns the native
ltc-capture helper (moved into the module folder, compiled on first use,
exact-name device matching) for the admin-selected input device, extracts
the configured channel, and decodes SMPTE LTC in-process (ltc.js — the
engine, shared with tools/ltc-reader.js, which remains the remote-machine
option posting to /ltc).

Admin config: an "LTC listener" switch, an audio-device select populated
live from the new GET /ltc/devices route, and a 1-based channel number.
The switch off means LTC is gone entirely: no LTC entry in the Add-tile
picker, no LTC row in the Timers menu or All-timers grid, no stage/API
probes, and POST /ltc answers 409. Source precedence with it on:
listener-with-data, fresh remote reader, listener's own no-signal verdict,
then the legacy API/stage chain.

Shell (admin page): `select` fields learn `optionsRoute` — options fetched
from a module route at render, saved value kept selectable when the route
or device is gone. Documented in MODULE-GUIDE; backward compatible.
Coordinate with the shell lane before its next admin.js change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 17:21:48 -04:00
Waters ChurchandClaude Fable 5 cb77030eec Timers: LTC from the audio signal itself via an external reader
ProPresenter 21.4 exposes no timecode through any API, so read the LTC
where it actually lives — the audio feed:

- tools/ltc-reader.js: zero-dependency SMPTE LTC decoder (biphase-mark,
  adaptive bit clock, envelope-scaled comparator) fed s16le PCM on stdin
  from ffmpeg/sox; POSTs running/stopped/nosignal + timecode + frame rate
  (incl. 29.97 drop-frame) to the module. --selftest runs an encoder→
  decoder suite (24/25/30 fps, DF minute counting, low level + noise + DC,
  dropout recovery); --demo synthesizes live LTC through the real decoder
  and ingest path with no audio hardware.
- Module: POST /ltc ingest route (optional bearer token, validated body,
  6 s staleness with automatic fallback to the API probe / stage source),
  richer LTC state (status, fps, df, reader source), health messages.
- Client: LTC card shows Running · <rate> / Stopped (held frame) /
  No signal; works LTC-only with no ProPresenter host configured; the
  reader-fed card no longer grays out when only ProPresenter is down.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 14:52:20 -04:00
Waters ChurchandClaude Fable 5 444f03938d Timers: LTC timecode via the stage-display websocket
ProPresenter's HTTP API has no timecode route (verified: 21.4 returns
404), but its classic stage-display websocket still answers — so LTC now
comes from a stage layout field labeled "LTC": a zero-dependency RFC6455
client (stage-ws.js) authenticates (new stagePassword admin config),
resolves the LTC-labeled frame's uid from the layout list, and treats any
message carrying that uid as the timecode text. "Receiving" is a 2.5s
staleness heuristic; frozen or placeholder text reads as no-signal. The
HTTP probe stays authoritative when it ever answers (a 404 verdict is
retried once a minute), and the LTC card surfaces the reason when
timecode is unavailable (bad stage password, no LTC field). pp-timers-mock
grows a /stagedisplay websocket with --stage-pwd and --no-ltc-field to
exercise every path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 17:05:23 -04:00
Waters ChurchandClaude Fable 5 12245cba93 Timers: present each timer and LTC in the Add-tile picker
Adopt main's new multi-tile contract: the module's server exports
tiles() (answered from the poller's held state, never a fresh upstream
request) listing All timers, one entry per discovered timer, and LTC
timecode. The client branches on moduleApi.variant — solo tiles render
exactly one card with no selection menu, and a solo timer that vanishes
from ProPresenter shows a labeled "Not in ProPresenter" card instead of
erroring (visible even in the compact layout). pp-timers-mock gains
--drop=<uuid,...> to simulate a timer deleted in ProPresenter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 14:34:59 -04:00
Waters ChurchandClaude Fable 5 a8a382c171 Timers module: live ProPresenter timers and LTC timecode
New propresenter-timers module: polls /v1/timers + /v1/timers/current
(shapes verified against openapi.propresenter.com), probes the
undocumented /v1/timecode/status for LTC and honestly reports
"not available on this ProPresenter" when absent. Per-tile timer/LTC
selection via a Timers ▾ header menu (never-seen timers default on),
responsive auto-sized cards with state-derived colors (the API exposes
no per-timer color), unmistakable overrun. Includes tools/pp-timers-mock.js
for developing without a real ProPresenter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 14:31:04 -04:00