Files
ProdDash/CLAUDE.md
T
Waters ChurchandClaude Fable 5.1 459d8b9c38 Overflow Tap: speak Overflow's Dashboard API, as its own helper app does
The first cut guessed a write route on Overflow's public API, which documents
only reads for Tap. With the helper app's bundle in hand (Overflow ProPresenter
Integration 1.2.3, read 2026-10-11) the module now makes exactly the calls the
helper makes, against the Dashboard's own backend:

  POST /nonprofit-dashboard/authentication/login/jwt      a Dashboard sign-in → JWT
  GET  /nonprofit-dashboard/propresenter/nonprofits/{id}/keywords
                                                          the Keyword Mappings
  POST …/nonprofits/{id}/keywords?syncNonprofit=true      { keywords } = the switch

So the keyword → destination → Tap groups mapping stays where Overflow keeps
it (Dashboard › Integrations › ProPresenter › Keyword Mappings) and is read
from there: on start, every five minutes, on the tile's refresh button, and
from every trigger's answer. Admin config shrinks to the ProPresenter endpoint,
the Overflow Dashboard login (plus a Nonprofit ID only for an @overflow.co
staff login) and the alert switch; the API-key, Tap-groups, keyword-text and
match-mode fields are gone.

Behaviour follows the helper: both the presentation layer (/v1/status/slide)
and the announcement layer (/v1/announcement/slide_index + /active) are
watched; when a layer's set of matched keywords changes to a non-empty set it
is sent; matching is per word, case-insensitive, punctuation around the word
ignored. The session token is cached across re-inits for the same login so an
admin saving config repeatedly cannot trip Overflow's "too many attempts";
a 401 signs in again once; a refused sign-in is not retried within 20 s.

tools/overflow-tap-mock.js now plays the Dashboard API (any email/password
signs in; --fail-switch, --expire=<s>, --login=<email>:<password>) and gives
the mock ProPresenter an announcement layer (--no-announcements for an older
one). Verified against it: sign-in, keyword load, triggers on both layers,
token expiry → re-login, 500 → NOT SWITCHED + alert + retries, wrong login →
the reason on the tile and in the health line, and recovery. keywords.selftest.js
pins the matching rule (8 cases).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 10:25:02 -04:00

5.2 KiB

ProdDash

Modular production dashboard web app for Waters Church, developed in this repo. The app lives at the repo root and is built: zero-dependency Node server (server.js, Node 18+), dashboard shell client in public/, admin page at /admin, modules in modules/ (the repo's copies — a packaged install ships none and installs them from the admin page's catalog), and a native macOS menu-bar launcher in launcher/macos/ (Swift, no dependencies — the shipped macOS app: it carries the server and its own Node runtime, supervises them, and owns the macOS permissions modules declare; see its README). Runtime state (module config, named layouts) is written to a per-machine data directory outside the checkout (PRODDASH_DATA_DIR overrides; see README "Where settings are kept") — never add code that writes runtime state into the repo folder. SPEC.md was the authority for the initial build; README.md has run/configure/use instructions.

Building a new module — read docs/MODULE-GUIDE.md first

docs/MODULE-GUIDE.md is the authority on the module contract (manifest, client factory + moduleApi, optional server entry, title-bar controls). The rules that matter most:

  • A module is one self-contained folder in modules/ — never modify the shell (server.js, public/) to add one. Installation = the admin page's Available modules (downloads from the repo into the data directory), or drop the folder and restart; configure in /admin.
  • All settings in the admin page (configSchema / instanceSchema) — never a separate setup page, hand-edited file or env var. Dependencies ship inside the module folder (zero deps is the norm). Everything the module does runs inside its server entry — no helper scripts, companion programs or daemons the operator starts by hand; a listener or decoder is started in init() and stopped in stop(). A native helper ships prebuilt inside the module folder for Apple silicon macOS and 64-bit Windows (bin/<name>-macos-arm64, bin/<name>-win-x64.exe, source and build script under native/); nothing is ever compiled on the machine running ProdDash, and a missing binary means the feature reports itself off, not a broken module. Declare "proddash": ">=x.y.z" and bump the module's version on every change.
  • Browsers never call upstream services directly; the module's server routes proxy everything (no CORS, secrets stay server-side).
  • Keep the interface clean (guide rules 5-11): no help and no label over three words in a tile's gear; server-side settings in configSchema only; a tile setting changes that tile alone; hide-toggles ungrouped when there are four or fewer, in a folded Show group when there are five or more; title-bar controls are icons with tooltips, never labels, and never repeat something the gear already offers.
  • Style with the shell's CSS variables only, scoped under [data-module="<id>"]; survive upstream loss (reconnect + setStatus).
  • Verify against the guide's testing section and resilience checklist. Mocks for the existing upstreams: node tools/prodcom-mock.js (ProdCom) and node band-lineup-display/tmp/pp-mock.js 1599 (ProPresenter). node tools/slack-mock.js 24716 (Slack; run ProdDash with SLACK_API_BASE=http://127.0.0.1:24716/api). node tools/pco-band-mock.js 24710 (Planning Center for Band Lineup; run ProdDash with BAND_LINEUP_API_BASE=http://127.0.0.1:24710). node tools/ableset-mock.js 39071 (AbleSet over OSC/UDP; point the AbleSet module at 127.0.0.1:39071; --stopped, --no-live, --speed 4). node tools/overflow-tap-mock.js (ProPresenter slide notes on 127.0.0.1:1602 + the Overflow Dashboard API on 127.0.0.1:24720; run ProdDash with OVERFLOW_TAP_API_BASE=http://127.0.0.1:24720, any email/password signs in; --fail-switch, --expire=20, --login=<email>:<password>).

Reference code (read-only forks — don't develop these here)

  • prodcom-listener/ — live ProdCom transcript web app. Zero-dependency Node server.js that serves public/ and proxies /prodcom/* (including the SSE transcript stream) to the ProdCom API at config.json → prodcomUrl (set per site; each campus runs its own). Good reference for: SSE handling, reconnect logic, channel colors/filtering, PWA install.
  • band-lineup-display/ — backstage Electron app (stage lineup photos with mic/pack labels) plus ProPresenter Now/Next:
    • band-lineup-display/propresenter-core/ — shared ProPresenter 7/20 client, no deps, no Electron. All ProPresenter quirks live here (PP20 runtime route discovery, arrangement-expanded slide totals, uuid-then-name live-item matching). Reuse this rather than re-implementing ProPresenter access.
    • band-lineup-display/propresenter-app/ — standalone Now/Next web display.
    • band-lineup-display/tmp/pp-mock.js — mock ProPresenter API for testing without the real thing: node tmp/pp-mock.js 1599, then point at 127.0.0.1:1599.

Upstream sources: ProdCom Listener lives at github.com/michaelmcgary2008/prodcom-listener; BandLineupDisplay exists only on the production machine. These folders are snapshots, not submodules.