Files
ProdDash/SPEC.md
T
Waters ChurchandClaude Opus 5 a3e1282354 No campus's address ships in the repo
prodcom-transcript's endpoint defaulted to 10.3.11.152:24480 — Apollo
Beach's ProdCom. A fresh install anywhere therefore came up reading AB's
comms and reported itself healthy doing it: that is exactly what the new
North Attleboro install did. The default is now blank, so a new install
says "No ProdCom server configured" (in red, as the ProPresenter modules
already do for a missing host) until someone points it at the ProdCom for
that location. Setting a host still connects as before.

The same address is out of SPEC.md's worked example, CLAUDE.md's note
about the reference app, and the prodcom-listener snapshot's config,
README and fallback — that fallback now exits with a line telling the
operator what to set, rather than throwing on an empty URL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 10:37:56 -04:00

246 lines
11 KiB
Markdown

# ProdDash — Build Instructions
ProdDash is a flexible **production dashboard** web app for Waters Church. A
browser anywhere on the building network loads one page and composes its own
dashboard out of **modules** — independent tiles that each show one aspect of a
live service. Ships with two modules ported from the reference forks in this
repo: the live **ProdCom transcript** and the **ProPresenter Now/Next** display.
This document is the authority for the initial build. Where it is silent, match
the conventions of the reference code (`prodcom-listener/`,
`band-lineup-display/`).
## Goals
1. A dashboard shell where each browser independently enables, arranges, and
resizes module tiles. Layouts persist in that browser's `localStorage` by
default; named layouts can also be saved to the server and loaded from any
browser.
2. An **admin page** where installed modules are configured (connection
settings, credentials, enable/disable) once, server-side, for everyone.
3. A **module system** with a documented contract, so future modules can be
developed and installed without touching the shell. Producing that guide
(`docs/MODULE-GUIDE.md`) is part of this build, not an afterthought.
## Constraints and house style
- **Runtime:** Node.js server + browser client, plain HTTP on the LAN. No
build step required to run (`node server.js` and it works), matching
`prodcom-listener`. Windows + macOS.
- **Dependencies:** keep the zero/low-dependency ethos. Prefer no npm deps on
the server. On the client, vanilla JS/CSS is preferred; if a grid library
genuinely earns its place for drag/resize (e.g. gridstack), **vendor it into
the repo** — no CDN loads, the app must work on a network with no internet.
- **Resilience first:** this runs during live services. Every module must
survive its upstream (ProdCom, ProPresenter) disappearing and reconnect
automatically, showing a clear degraded state — copy the reconnect patterns
in `prodcom-listener/public/app.js` and `propresenter-core`.
- **Look:** dark, high-contrast, production-booth friendly. Reuse the visual
language of `prodcom-listener/public/style.css` (its CSS variables, themed
scrollbars). All module styling via shared CSS variables so modules look
native.
- Installable as a PWA like prodcom-listener (manifest + service worker),
nice-to-have, last.
## Architecture
```
ProdDash/
├── server.js shell server: static files, config store, layouts API,
│ module discovery, mounts each module's server routes
├── config/
│ ├── proddash.json port, admin passcode (optional), etc.
│ └── modules.json per-module config + enabled flags (written by admin page)
├── public/ dashboard shell client (index.html, shell.js, style.css)
│ └── admin/ admin page client
├── modules/ installed modules, one folder each (see contract)
│ ├── prodcom-transcript/
│ └── propresenter-now-next/
├── docs/MODULE-GUIDE.md how to build and install a module (deliverable)
├── prodcom-listener/ reference fork — read-only, not served
└── band-lineup-display/ reference fork — read-only, not served
```
Server responsibilities:
- Discover modules by scanning `modules/*/module.json` at startup.
- Serve each module's client assets at `/modules/<id>/...`.
- Call each enabled module's server entry (if it has one) and mount its routes
under `/api/modules/<id>/...`, passing that module's config object.
- Shell API: `GET /api/modules` (installed+enabled manifests for the client),
layouts CRUD (`GET/PUT /api/layouts`, named layouts as JSON on disk under
`config/layouts/`), admin config API.
- Config changes from the admin page take effect without a manual restart
where practical (re-init the affected module); a clean restart is acceptable
for v1 if flagged in the admin UI.
Client shell responsibilities:
- Grid of tiles: add module (from enabled list), drag to move, drag to resize,
remove. Multiple instances of the same module are allowed (e.g. two
transcript tiles filtered to different channels).
- **Per-instance settings** (e.g. transcript channel filter, font size) are
chosen in the tile and stored with the layout — distinct from admin config,
which is server-wide (URLs, ports, API keys).
- Layout (tiles, positions, sizes, per-instance settings) autosaves to
`localStorage`. A layout menu offers: Save as named layout (to server), Load
named layout, Reset. Loading a named layout copies it into local state; it
does not live-link browsers together.
- Each browser is fully independent — no shared runtime state between viewers.
## Admin page
At `/admin` (link from the shell's menu):
- Lists every installed module with name, version, description, and status
(enabled/disabled, connected/erroring if the module reports health).
- Per-module config form **generated from the `configSchema` in its manifest**
(text, number, boolean, select, password fields). Saving writes
`config/modules.json` and re-inits the module.
- Enable/disable toggle per module — disabled modules don't appear in the
shell's picker and their server routes are not mounted.
- Manage named layouts (list, rename, delete).
- Protect with a single shared passcode if `adminPasscode` is set in
`config/proddash.json`; no accounts, no sessions beyond a cookie. Plain HTTP
LAN tool — keep it simple.
## Module contract (v1)
A module is a folder in `modules/` containing:
```
modules/<id>/
├── module.json manifest (required)
├── client.js ES module, client entry (required)
├── server.js server entry (optional)
└── ... assets (css, images) served statically at /modules/<id>/
```
`module.json`:
```json
{
"id": "prodcom-transcript",
"name": "ProdCom Transcript",
"version": "1.0.0",
"description": "Live ProdCom transcript stream",
"client": "client.js",
"server": "server.js",
"minSize": { "w": 2, "h": 2 },
"defaultSize": { "w": 4, "h": 3 },
"configSchema": {
"prodcomUrl": { "type": "string", "label": "ProdCom URL", "default": "" },
"apiKey": { "type": "password", "label": "API key (if PSK auth enabled)", "default": "" }
},
"instanceSchema": {
"channel": { "type": "string", "label": "Channel filter", "default": "" }
}
}
```
Client entry (`client.js`) default-exports a factory:
```js
export default function create({ root, moduleApi }) {
// root: the tile's DOM element (module owns everything inside it)
// moduleApi: {
// id, instanceId,
// config, // admin (server-wide) config, read-only
// instanceSettings, saveInstanceSettings(patch), // per-tile, persisted in layout
// fetch(path, opts), // fetch scoped to /api/modules/<id>/
// sse(path, handlers), // EventSource scoped the same way, with auto-reconnect
// setStatus('ok'|'connecting'|'error', msg) // drives the tile's status dot
// }
return {
start() {}, // begin rendering / connecting
stop() {}, // tear down everything (timers, streams, listeners)
onResize(w, h) {}, // optional; CSS should handle most of it
onConfigChange(cfg) {} // optional; admin config was updated
};
}
```
Server entry (`server.js`, optional) exports:
```js
module.exports = {
init({ config, log }) { /* return handle with stop() */ },
routes({ config, log }) {
// return { 'GET /state': handler, 'GET /stream': sseHandler, ... }
// mounted under /api/modules/<id>/
}
};
```
Rules for modules (enforce these in the guide):
- Never touch DOM outside `root`; never define global CSS (scope with a
`[data-module="<id>"]` prefix or shadow DOM).
- Style with the shell's CSS variables; no hardcoded colors.
- No external network calls from the client — anything upstream goes through
the module's server routes (same proxy reasoning as prodcom-listener: no
CORS, browsers never need to reach ProdCom/ProPresenter directly).
- Handle upstream loss: reconnect with backoff, report via `setStatus`.
- Installation = drop the folder into `modules/`, restart ProdDash, configure
in `/admin`. No shell code changes.
`docs/MODULE-GUIDE.md` must document all of the above from a module author's
point of view, with a complete minimal example module (e.g. a clock) that can
be copy-pasted as a starter, and a testing section.
## Initial modules
### 1. `prodcom-transcript`
Port `prodcom-listener/` into the module system:
- Server part: the `/prodcom/*` proxy from `prodcom-listener/server.js`
(including SSE passthrough), driven by admin config instead of its own
`config.json`.
- Client part: the transcript UI from `prodcom-listener/public/` — history
load, live SSE, in-place updates of in-progress speech, channel colors,
auto-scroll with "↓ New messages", channel visibility toggles.
- Admin config: `prodcomUrl`, `apiKey`. Instance settings: channel filter,
text size.
### 2. `propresenter-now-next`
Port the Now/Next feature using `band-lineup-display/propresenter-core/`
**as-is** (copy it into the module; it has no deps and holds every
ProPresenter 7/20 quirk — do not re-implement it):
- Server part: connect/poll ProPresenter via propresenter-core, expose current
state at `GET /state` and push changes over `GET /stream` (SSE).
- Client part: the Now/Next display, based on the page in
`band-lineup-display/propresenter-app/public/`.
- Admin config: ProPresenter host, port. Instance settings: display options
the existing page already supports.
- Must work against the mock: `node band-lineup-display/tmp/pp-mock.js 1599`
with host `127.0.0.1`, port `1599` — including the arrangement-repeat case
it simulates.
## Milestones (build in this order, each independently verifiable)
1. **Shell:** server + dashboard grid with a placeholder tile; add/move/
resize/remove; layout survives reload via localStorage.
2. **Module system:** discovery, client loader, module API; placeholder
becomes a real trivial module (clock) proving the contract end-to-end.
3. **prodcom-transcript** module, verified against a live or mocked ProdCom.
4. **propresenter-now-next** module, verified against `pp-mock.js`.
5. **Admin page:** schema-driven config forms, enable/disable, persisted to
`config/modules.json`.
6. **Named layouts:** save to server, load from another browser.
7. **Docs:** `docs/MODULE-GUIDE.md` + README rewrite (run instructions in the
style of the prodcom-listener README).
## Definition of done
- `node server.js` on a clean clone starts everything; the printed URL works
from another device on the LAN.
- Two different browsers hold different layouts simultaneously; a named layout
saved in one loads in the other.
- Kill and restart ProdCom / the ProPresenter mock while tiles are open: tiles
show degraded status and recover on their own.
- A new module can be added by following MODULE-GUIDE.md alone, without
reading shell source.