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>
ProdDash
A modular production dashboard for Waters Church. Any browser on the
building network opens one page and composes its own dashboard out of
modules — independent tiles that each show one aspect of a live service.
Ships with the live ProdCom transcript, the ProPresenter Now / Next
display, and a booth clock; new modules drop into modules/ without
touching the shell (see docs/MODULE-GUIDE.md).
How it works
server.js (zero dependencies, just Node.js) does four things:
- Serves the dashboard shell (
public/) and the admin page (/admin). - Discovers modules in
modules/and serves each one's client at/modules/<id>/. - Mounts each enabled module's server routes under
/api/modules/<id>/— that's where module servers proxy ProdCom, poll ProPresenter, and stream live updates over SSE, so browsers never talk to production gear directly (no CORS, and API keys never leave the server). - Stores server-wide module config and named layouts under
config/.
Each browser arranges its own tiles (add, drag, resize, remove, per-tile
settings); the layout autosaves to that browser's localStorage. Named
layouts can also be saved to the server and loaded from any other browser.
Every module survives its upstream disappearing: tiles show a red status dot
and a clear degraded state, then recover on their own.
Run it
Requires Node.js 18 or newer.
node server.js
Or double-click Start ProdDash.command (macOS) / start.bat (Windows).
Then open the URL it prints — http://localhost:24500 on this machine, or
the http://<this-machine's-IP>:24500 address from any other device on the
network.
Configure
Server-wide module settings (ProdCom URL, ProPresenter host, API keys,
enable/disable) live in the admin page — open Admin from the
dashboard's top bar, or go to /admin. Saving re-initialises the module
immediately; open tiles reconnect on their own.
Shell settings live in config/proddash.json:
| Key | Meaning |
|---|---|
port |
Port ProdDash serves on (default 24500) |
adminPasscode |
If set, the admin page asks for this shared passcode. Leave "" for none — it's a plain-HTTP LAN tool. |
Environment variables PORT and PRODDASH_PASSCODE override the file.
config/modules.json (module config) and config/layouts/ (named layouts)
are written by the app itself.
Using the dashboard
- + — add any enabled module; multiple tiles of the same module are fine (e.g. two transcripts filtered to different channels).
- Drag a tile by its title bar to move it; drag the corner handle to resize; ✕ removes it. Tiles sit flush — when two share an edge, hover the seam and a small grab pill appears; drag it to trade width between the two tiles (macOS split-view style).
- A tile's actions (menus, text size, clear…) live in its title bar; the small notch on the bar's bottom edge hides/shows the whole bar for a clean wall-display look.
- The gear on a tile holds per-tile settings (filters, text size, display options) — these belong to this browser's layout, not the server.
- The grid icon — save the current arrangement to the server under a name, load a named layout (it becomes a local copy — browsers are never linked), or reset this browser's dashboard.
- Every tile has a status dot: green = live, amber = connecting, red = upstream unreachable (hover it for details). Tiles reconnect automatically.
Included modules
-
ProdCom — live transcript stream with in-place updates of in-progress speech, channel colors, per-tile channel visibility toggles and group presets, timestamps, text size and jump-to-latest in the title bar. Admin config: ProdCom server (host : port), API key.
-
ProPresenter — what's live and what's next, with slide progress, driven by the shared
propresenter-coreclient (ProPresenter 7/20). Admin config: ProPresenter endpoint (host : port), password. -
Timers — live ProPresenter timers (countdown, countdown-to-time, elapsed) and LTC timecode as auto-sized cards. The + picker lists every discovered timer (and the LTC card) as its own tile, or add "All timers" and pick a selection via the tile's "Timers ▾" menu; overruns are unmistakably red. Read-only — no start/stop controls. Admin config: ProPresenter endpoint (host : port), API password, the LTC listener switch + audio device + channel, LTC reader token, stage display password.
LTC lives behind the admin LTC listener switch — off means no LTC tile in the picker at all. On, the sources, best first (ProPresenter's HTTP API has no timecode route):
- Built-in listener — the module captures the admin-selected audio
input device/channel on the ProdDash machine (native
ltc-capturehelper, compiled on first use) and decodes the SMPTE frames in-process: running/stopped/no-signal, timecode, frame rate (incl. 29.97 drop-frame). macOS note: the ProdDash server needs microphone permission — launch it from a logged-in session (Start ProdDash.command) and approve the one-time prompt; a server started over SSH or by bare launchd silently captures zeros. - Remote reader — when a different machine hears the LTC, run
tools/ltc-reader.jsthere; it decodes the same way from a PCM pipe (ltc-capture, ffmpeg, or sox;--channels 18 --ch 5picks one input of a multichannel interface — never downmix LTC into program audio) and POSTs to the module's/ltcroute.--selftestchecks the decoder;--demofeeds synthetic LTC end-to-end with no audio hardware. - A stage-display layout field labeled "LTC", read over ProPresenter's stage websocket — value and freshness only, no frame rate.
- Built-in listener — the module captures the admin-selected audio
input device/channel on the ProdDash machine (native
-
Clock — a big booth clock; also the smallest possible module and the reference for module authors.
To develop modules against mocks instead of real gear:
node tools/prodcom-mock.js (ProdCom on 127.0.0.1:24480),
node band-lineup-display/tmp/pp-mock.js 1599 (ProPresenter slides on
127.0.0.1:1599) and node tools/pp-timers-mock.js 1600 (ProPresenter
timers + timecode on 127.0.0.1:1600). See the testing section of the
module guide.
Install as a desktop web app
Open the page in a browser first, then:
- macOS — Safari: File → Add to Dock…
- macOS/Windows — Chrome: click the install icon in the address bar, or ⋮ menu → Cast, save and share → Install page as app…
- Windows — Edge: ⋯ menu → Apps → Install this site as an app
Note: Chrome only offers the one-click PWA install on http://localhost (a
"secure context"). When opening from another machine over plain HTTP, use
Edge's "Install this site as an app" or Chrome's ⋮ → Cast, save and share →
"Create shortcut…" (check "Open as window") — same result: a dock/taskbar
app in its own window.
Repository layout
ProdDash/
├── server.js shell server: static files, module discovery/mounting,
│ admin + layouts APIs
├── public/ dashboard shell client (index.html, shell.js, style.css)
│ └── admin/ admin page client
├── modules/ installed modules, one folder each
│ ├── clock/
│ ├── prodcom-transcript/
│ └── propresenter-now-next/ (carries its own copy of propresenter-core)
├── config/ proddash.json + state written by the app
├── docs/MODULE-GUIDE.md how to build and install a module
├── tools/prodcom-mock.js mock ProdCom API for development
├── prodcom-listener/ reference fork — read-only, not served
└── band-lineup-display/ reference fork — read-only, not served
The two reference folders are snapshots of the apps ProdDash grew out of;
they are not wired into anything (but band-lineup-display/tmp/pp-mock.js
is handy for testing).