Commit Graph
33 Commits
Author SHA1 Message Date
Waters ChurchandClaude Fable 5.1 48526934bb pco-plan: per-service timing, ProPresenter cues, timers for the Timers module
The service clock now belongs to one service at a time: a plan's services
take turns by window (the next one takes over pre-roll before its start,
once the previous one has ended or was due to end), and moving into the
next window resets the service elapsed clock, the item timing and the
countdown. A service starts at its scheduled time or, under the new
"Service timing" admin group, when a chosen ProPresenter document goes
live (scheduled time as the failsafe); it ends when the chosen end
document goes live, or at the next service / plan end + 60 min.

The pure logic lives in service-timing.js and matching.js (cue documents
are matched like plan items, with a stricter 0.75 bar so the pre-service
loop never ends a service) with service-timing.test.js covering windows,
precedence and failsafes. GET /timers and /timers/stream offer the
service, current-item and countdown timers per the module guide;
GET /documents lists the documents the Now/Next feed has shown or listed
for the new selects. Loopback calls use shell.port, with the old
proddash.json guess only when the shell hands no port.

Manifest: 1.4.0, proddash >=1.4.0, provides ["timers"]; Client ID
wording, shorter labels, group help tightened, field help trimmed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 12:58:12 -04:00
Waters ChurchandClaude Opus 5 adac3a4785 Ship ProdDash as one app: its own Node, its own copy, a .dmg
Installing is dragging ProdDash to Applications. The app carries the
dashboard and the Node that runs it, so nothing has to be installed first and
nothing can go missing underneath it.

  ProdDash.app/Contents/
    MacOS/ProdDash            the launcher
    Resources/node/bin/node   Node 24.21.0 LTS, fetched and checksum-verified
    Resources/app/            server.js, public/, modules/

Everything mutable stays where it already was, in ~/Library/Application
Support/ProdDash — and that now includes app/, the copy of ProdDash that
actually runs. The copy is not ceremony: ProdDash updates itself in place,
and nothing may write inside a signed .app. So the bundle holds a read-only
baseline and the data directory holds the running copy, which means both
routes to a new version keep working and don't fight — installing a newer
.app replaces the copy, while a copy that has already updated itself past the
bundle is left alone.

build.sh now assembles all of it: fetches Node against nodejs.org's published
checksums (cached in .cache/), embeds the server, compiles each module's
native helper so the Mac running ProdDash needs no compiler, signs nested
code before the bundle, and builds the disk image. --dev skips the payload
for launcher work, which still runs against a checkout.

Signing is set up for a Developer ID with the hardened runtime and
notarisation (--notarize), and falls back to ad-hoc when no such certificate
is installed. Entitlements are split by what each binary actually does: JIT
for node, audio input for the launcher and for module helpers that read one.

Built and run end to end: first-run install, bundled node, dashboard and
admin serving, re-seed in both directions (newer app replaces an older copy;
an updated copy survives), disk image mounts and its app verifies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 12:38:46 -04:00
Waters ChurchandClaude Opus 5 e1bceb3e60 Launcher: strip the window back to what's worth reading
A pass for less prose and fewer boxes.

- Header shows the version and node; the port is already in the addresses
  below it, and the module list belonged in the log.
- Settings: "Network" over the port (no explanation under it), "Startup"
  instead of "Starting up", and no node chooser — the path stays visible,
  since which node runs is the first thing worth knowing when it won't.
- Permissions: one line per permission — a status dot, its name, and the one
  thing you can do about it. The standing explanation is gone and the
  modules' own reasons moved to each row's tooltip. Local network results
  still appear under their row after a check, with a short line and a link
  only when nothing answered at all. Re-check sits centred at the bottom,
  outside the group.

The app-level "notify me" switch goes with it: macOS's own per-app
notification setting already does that job, so notifications now follow the
permission alone, asked for by the row's Enable button rather than on first
run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 11:10:22 -04:00
Waters ChurchandClaude Opus 5 1dbd55aa24 macOS menu-bar launcher: run ProdDash, and own its permissions
A native Swift app (launcher/macos, ~2 MB, no dependencies) that starts,
stops and watches over the server from the menu bar — and holds the macOS
permissions its modules need.

The permissions are the point as much as the buttons are. macOS grants
privacy permissions to the application that owns a process, and a child
inherits its parent's identity, so with the server started from here node —
and the Timers module's ltc-capture — belong to this app bundle and the
microphone and local-network prompts are asked, and remembered, as ProdDash.
Started over SSH or from a launchd job there is nobody to ask: the permission
is silently absent rather than refused, which is the "endless zeroes" failure
ltc-listener.js warns about.

The shell was already built for a supervisor: with PRODDASH_LAUNCHER=1 it
exits 75 instead of respawning itself when the admin page applies an update,
which the launcher reads as a restart request rather than a fault. Crashes
get a backed-off restart (1s, 2s, 5s, 10s) and four in two minutes stops the
cycle. The server is spawned into its own process group, so stopping it also
stops anything it started — an orphaned ltc-capture would otherwise hold an
audio device indefinitely — and signals, a logout and Quit all stop it first.

- Menu bar: ProdDash's own 2x2 tile mark, its accent tile lit green while
  serving, amber mid-move, red when something needs a person. Start, stop,
  restart, open the dashboard or admin, open at login, quit.
- Window: status and both addresses, plus Log (also written to
  ~/Library/Logs/ProdDash, kept a week), Settings, and Permissions.
- The port is written to the data directory's proddash.json — ProdDash's own
  file — so the port set here holds however it is next started. Something
  already on the port is reported, and offered as a takeover when it answers
  like a ProdDash started another way.
- Modules declare what they need in their manifests ("permissions": [...],
  documented in docs/MODULE-GUIDE.md and ignored by the shell); the launcher
  lists exactly that, with each module's reason, asks for the microphone,
  and — since macOS reports nothing about local network access — offers a
  check that browses for services and tries every configured upstream.

Timers, ProdCom and ProPresenter modules declare their permissions (patch
bumps). Build with launcher/macos/build.sh; output is gitignored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 10:27:08 -04:00
Waters ChurchandClaude Fable 5.1 6b3e73824c Merge main (PRs #5, #7, #9, #10) into the PCO Plan branch
Resolves the admin form conflicts in favour of this branch's grouped /
collapsible / dependent-select additions (the base hunks were the timers
lane's, ported verbatim), keeps both sides of the README and module-guide
edits, and adopts main's versioning rules: the shell moves to 1.3.0 for the
new schema features (configGroups / instanceGroups, help, color fields,
grouped and dependent selects) and pco-plan 1.3.0 declares
"proddash": ">=1.3.0".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 16:47:06 -04:00
Waters ChurchandClaude Opus 5 5ecc39efe2 ProdCom: direction toggle, icon channels button, leaner tile settings
Three changes to the ProdCom tile, all per-tile and persisted with the
layout (version 1.2.0):

- A title-bar button flips the transcript between top-down (newest at the
  bottom, still the default) and bottom-up. It is CSS only — the entries
  container becomes column-reverse, DOM order is untouched — but everything
  that follows "latest" flips with it: auto-follow, jump-to-latest, the
  near-latest scroll test, and the "New messages" pill, which moves to the
  top of the tile and points up.

- The "Channels ▾" text button is now a funnel icon. It keeps what the
  label carried: while any channel is hidden the icon takes the accent
  color and a compact "3/4" count sits beside it, with the full wording in
  the tooltip.

- Tile settings drop the channel-name filter and text size, both of which
  the title bar already handles, and gain "Show channel icons" for the
  per-entry avatars. Removing the channel filter made its code dead, so
  channelFilter(), filteredOut() and the data-channel-name attribute went
  with it — the menubar's hidden-channel set is now the only filter.

Text size keeps working without a schema entry: the A−/A+ buttons persist
it through saveInstanceSettings, the shell's settings popover only writes
schema keys so it survives an Apply, and the client falls back to 15px when
it is absent. Existing tiles have no saved showAvatars value and the check
is `=== false`, so they keep their icons.

Verified against tools/prodcom-mock.js: both directions render and
auto-follow correctly, the pill appears at the right end and jumps to the
newest entry, the count badge tracks the channel toggles, a tile bumped to
19px kept it across an Apply and a reload, a freshly added tile started at
15px with icons shown, and two tiles held independent state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 16:40:46 -04:00
Waters Church d2c087a256 Merge main (PRs #5, #10) into the timers branch 2026-09-10 16:39:58 -04:00
Waters ChurchandClaude Fable 5.1 d567f5459a pco-plan: edge-to-edge headers, tile colors for headers / current item / key, gradient switch
Section headers lose the bracket look (no accent bar, no rounded corners)
and span the full tile width, with text still aligned to the other rows.
A "Colors" group in the gear menu holds swatch pickers for the song key,
section headers and the current item, a switch for fading the header color
to the right (on by default), and the item-type tint. Colors apply as CSS
custom properties on the tile.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 16:31:46 -04:00
Waters ChurchandClaude Fable 5.1 a71b14d755 Shell: color field type (swatch picker) in tile settings and admin config
Renders <input type="color"> with the label beside it; the stored value is
always a #rrggbb string (3-digit hex widened, anything else falls back to
the schema default). Same styling in the gear popover and the admin form.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 16:31:46 -04:00
Waters ChurchandClaude Fable 5.1 4c5d7956ab pco-plan: display settings are per tile again (gear menu), admin keeps the connection
Top bar (template, item-type tint, key color) and the whole show/hide
checklist move from admin config to instanceSchema, grouped in the gear
popover ("Top bar" open, "Show / hide" folded, three columns). They are
browser-side choices stored with the layout, not server state. Admin now
holds only credentials, service type, plan, poll interval and the
ProPresenter follow switch. noteCategories moves to the tile too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 16:20:46 -04:00
Waters ChurchandClaude Fable 5.1 01eac1bdc4 Shell: grouped, collapsible, multi-column per-tile settings popover
The gear popover learns the admin form's conventions: an instanceSchema
field's `group` starts a labeled subsection, `help` puts a line of guidance
under a control, and a manifest `instanceGroups[name]` adds
{ collapsed, columns, help } for that group (rendered as <details> with a
grid body; container queries drop to two or one column in narrow tiles).
/api/modules now exposes instanceGroups. Lets a module keep a long
show/hide checklist per tile — where display choices belong — without
the popover turning into a scroll of thirty loose checkboxes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 16:20:45 -04:00
Waters ChurchandClaude Fable 5.1 20dc2deb2b pco-plan: everything configured in Admin; key, section and top-bar restyle
- Drops the separate setup page: the service type is a select grouped by
  Planning Center folder path (GET /service-types), the pinned plan a
  select of upcoming/recent plans that follows the chosen service type
  (GET /plans?serviceTypeId=), credentials sit in a group that folds once
  saved.
- Show/hide moves from per-tile settings to a server-wide checklist in
  Admin (three columns, folded by default): top bar, series, part, service
  type, date, service time, total length, running clock, countdown,
  headers, item numbers, start times, length, type badge, key, arrangement,
  author/CCLI, description, notes, pre/post-service items, now indicator,
  item runtime, dim completed, ProPresenter line. Tiles keep only text
  size and auto-scroll.
- Top bar text is a template with placeholders ({series} • {part} |
  {serviceType} • {date} by default; • separates droppable segments, |
  starts the second line).
- Song key: larger, admin-chosen color (blue by default), always right
  after the title and before the type badge. Section headers are
  full-width accent bands. Optional background tint per item type.
- tools/pco-mock.js plan titles are part numbers, as in a real series.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 15:31:12 -04:00
Waters ChurchandClaude Fable 5.1 106ce0c1ce Admin: collapsible / multi-column config groups, help text, grouped and dependent selects
Ports the timers lane's admin schema additions unchanged (field `group`,
`showWhen`, type `switch`, `optionsRoute`) so the two branches merge
cleanly, and extends them for modules whose settings are richer than a
handful of fields:

- manifest `configGroups[name]` = { collapsed: true | false | "whenSet",
  columns: 2–4, help } — a credentials block that folds once it holds a
  saved secret, a show/hide checklist laid out as a compact grid
- `help` on any field: one line of guidance under the control
- select options may carry `group` → rendered as <optgroup>s (a folder
  tree browsable inside the select)
- `optionsDependsOn: "<key>"` refetches a select's options with that
  field's current value as a query parameter, now and on every change
- a save re-runs the card's dynamic option fetches, so a list that needed
  the just-saved credentials fills in without a page reload; a blank
  "— choose —" is no longer added when the module offers its own blank
  option

Documented in docs/MODULE-GUIDE.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 15:31:12 -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 dba10a30fe Merge main via the stage-display branch: data dir, installer, versioning
Adopts the new contract: the manifest declares "proddash": ">=1.2.0"
(this module's admin device picker needs the optionsRoute select support
this branch adds), module version 1.2.0, and the shell version bumps
1.1.0 → 1.2.0 for that admin capability. The optionsRoute paragraph
merged into the guide's new schema section cleanly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-10 09:28:04 -04:00
Waters ChurchandClaude Fable 5 64502261ff Timers: version 1.1.0 (stage-display LTC), per the new versioning rules
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-10 09:26:50 -04:00
Waters Church 6427840223 Merge main: per-machine data dir, admin installer, module version requirements 2026-09-10 09:26:35 -04:00
Waters ChurchandClaude Fable 5.1 50caf02023 pco-plan: learn the shell's port from requests instead of config/proddash.json
Main now keeps a per-machine data directory whose proddash.json can override
the port, so reading the repo's config file was no longer a safe way to find
the ProPresenter module's loopback feed. Guess the port the way the shell
does (PORT, data-dir proddash.json, repo proddash.json, 24500), then confirm
it from the Host header of the first tile request; a learned port that
refuses the connection (a reverse proxy) is blacklisted and the guess kept.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 15:45:55 -04:00
Waters ChurchandClaude Fable 5.1 e7dc235a8a PCO Plan module: Planning Center order of service, following ProPresenter
New module modules/pco-plan: reads the current plan for a chosen Planning
Center Services service type (PAT or OAuth token), streams items with start
times, lengths, keys, descriptions and item notes to tiles (every detail a
per-tile show/hide setting), and follows the propresenter-now-next module's
live item over loopback SSE to highlight the current plan item and track
elapsed vs planned runtime. Ships its own setup page
(/modules/pco-plan/setup.html) to browse folders and pick the service type
through the shell admin API, plus tools/pco-mock.js for development.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 15:42:53 -04:00
Waters ChurchandClaude Fable 5 a68aed7443 Launcher: bundled runtime, and take the port over from a prior instance
Start ProdDash.command now prefers ./runtime/bin/node (production machines
have no system Node) and kills whatever already holds :24500 before
starting — so relaunching from the GUI (which the LTC listener needs for
microphone permission) is one double-click, not a port-conflict hunt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 17:22:59 -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 c4db412014 ltc-reader: corrupt frames must not poison the frame rate
Real Dante capture produced a bit-error frame whose frames field read 35
yet passed the lax range check (f<=59, hours<=39), and max-frame-based
rate detection then reported 36 fps to the dashboard permanently. Now:
every BCD digit is validated and fields respect hard ceilings (f<=29,
h<=23); nominal fps locks only from two agreeing second-rollovers (the
frame number preceding f=0 with seconds+1), so one glitch can't fake it
and a genuine rate change re-locks within two seconds; the drop-frame
flag gets two-frame hysteresis. Selftest reproduces the forged-35 frame
plus PCM glitches and pins fps at 30 throughout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 11:28:37 -04:00
Waters ChurchandClaude Fable 5 c3d8483943 ltc-capture: native CoreAudio input capture, no ffmpeg needed
Production machines rarely have ffmpeg/sox, and the LTC reader only needs
"device N → s16le stdout". This ~200-line Swift CLI (compiles with the
Xcode CLT already on the target: swiftc -O -o tools/ltc-capture
tools/ltc-capture.swift) enumerates input devices (--list), pins an
AVAudioEngine tap to one picked by name substring (--device), and streams
interleaved s16le at the device's own rate/channel count, printing the
matching --sr/--channels reader flags to stderr.

Verified by a real loopback on BlackHole 64ch: synthesized LTC played into
the device, captured back (channel 2 of 64 @ 44.1 kHz), decoded to
running/stopped on live tiles with zero drift over 14 s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 10:49:27 -04:00
Waters ChurchandClaude Fable 5 7ff8b42e90 ltc-reader: pick the LTC channel out of a multichannel capture
LTC usually rides one input of a console bus or Dante/MADI interface, and
downmixing it into program audio (-ac 1) corrupts the bit transitions. New
flags --channels <n> (interleaved count of the stdin stream, no practical
upper bound) and --ch <n> (1-based pick) deinterleave in the reader, so
the capture command stays a plain passthrough. The extractor keeps frame
alignment across arbitrary chunk splits; selftest adds an LTC-on-5-of-8
case fed in odd-sized chunks. Mono remains the default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 16:06:53 -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 d5cb6b6cd9 Timers: heuristically bind LTC when the stage layout omits the field
Verified against ProPresenter 21.4: the legacy stage-display protocol
(ptl 610 — the only version PP21 accepts; newer values get 'out of
date') omits stage objects it predates, so a timecode field labeled LTC
never appears in any layout's frame list, and no timecode messages were
observed while idle. Fallback: an unknown uid streaming timecode-shaped
text (a fourth :FF group; not any configured timer's uuid) is bound as
LTC after two advancing samples, and that binding survives layout
refreshes. pp-timers-mock gains --ltc-unlisted to exercise the path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 17:05:23 -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
Waters ChurchandClaude Fable 5 7ec3637a73 Add build spec: dashboard shell, module system, admin page, initial modules
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:24:13 -04:00
Waters ChurchandClaude Fable 5 150797389e ProdDash: new project with reference forks of ProdCom Listener and BandLineupDisplay
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 10:09:41 -04:00