A module may now present a list of tiles instead of a single picker
entry. A server module exports tiles({config, log}) — sync or async,
called on every picker load so the list can be live data (discovered
ProPresenter timers, admin-configured pages); a client-only module can
declare a static tiles array in module.json. The + picker groups the
entries under the module's name; adding one presets that entry's
instance settings, gives the tile the entry's name as its title, and
honors per-entry default/min sizes, all persisted with the layout.
moduleApi.variant tells the instance which entry it was added as. A
throwing or hung tiles() (2 s deadline) falls back to the classic single
entry so a dead upstream can never break the picker; modules without
tiles are byte-for-byte unaffected. Documented in docs/MODULE-GUIDE.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNYUv9EyrGSFmCnmH7WYKH
Topbar: the add button is now just + and the layout button a 3-segment
grid icon (two squares + one tall rectangle). Modules no longer build
their own toolbars — moduleApi.header.addButton/addMenu put controls in
the tile's title bar next to the gear/close (menus are fixed-positioned
by the shell so a tile's overflow can't clip them, and everything a
module adds is removed automatically on unmount). Each title bar grows a
notch on its bottom edge that hides/shows the bar, ProdCom-Listener
style; the choice persists with the layout. Modules renamed: ProdCom and
ProPresenter. The ProdCom tile regains the original listener tools as
icon buttons in its title bar: timestamps toggle (clock), A−/A+ text
size, jump-to-latest, and a circle-with-cross clear button, plus the
Channels menu. MODULE-GUIDE documents the header API; README updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNYUv9EyrGSFmCnmH7WYKH
Tiles now sit with zero gap (their 1px borders are the only seam) and the
grid runs edge to edge. Wherever two tiles share a vertical edge, an
invisible strip sits on the seam; hovering reveals a small grab pill
(split-view style) and dragging it moves the shared edge, trading width
between the two tiles. The combined span is preserved, both tiles' min
sizes are honored, and a third tile sitting behind part of the seam
blocks the drag instead of being overlapped. Pills hide during tile
drags/resizes and rebuild for the new geometry on release; seam sizes
persist with the layout. Also fixes cellMetrics treating a 0px grid gap
as falsy and falling back to 10px.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNYUv9EyrGSFmCnmH7WYKH
Milestone 7. docs/MODULE-GUIDE.md documents the whole module contract
from an author's point of view — manifest reference, client factory +
moduleApi, server init/routes/health, scoping and resilience rules, a
copy-paste minimal module, and a testing section with the two mocks and a
resilience checklist. README rewritten with run/configure/use
instructions in the prodcom-listener style, plus double-click launchers
for macOS and Windows. PWA: manifest, icons, and a minimal network-first
service worker that never touches /api/*.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNYUv9EyrGSFmCnmH7WYKH
Milestone 6. Layouts CRUD as JSON under config/layouts/ — GET list,
GET/PUT by name (open to every dashboard), DELETE and rename gated by the
admin passcode. Layout menu grows 'Save as named layout…' and a
'Load from server' list; loading copies the layout into the browser's own
local state, never live-links viewers. Admin page lists, renames and
deletes named layouts. Verified with two isolated browser contexts
holding different layouts simultaneously.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNYUv9EyrGSFmCnmH7WYKH
Milestone 5. GET /api/admin/state, PUT /api/admin/modules/<id>/config and
/enabled persist to config/modules.json and re-init the module in place —
no restart. /api/events (SSE) tells open dashboards about admin changes;
the shell then calls onConfigChange(cfg) on instances that implement it
and cleanly remounts the rest, so tiles reconnect on their own. Admin
page renders forms from each configSchema (text/number/boolean/select/
password), shows module health from handle.health(), and never echoes
password values back to a browser. Optional shared passcode
(config/proddash.json adminPasscode or PRODDASH_PASSCODE) gates the admin
API behind a cookie; the dashboard API stays open.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNYUv9EyrGSFmCnmH7WYKH
Milestone 2. Server scans modules/*/module.json, serves module assets at
/modules/<id>/, mounts optional server routes (exact + trailing-/* prefix
patterns) under /api/modules/<id>/ with per-module config from
config/modules.json, and lists enabled manifests at GET /api/modules.
Shell dynamically imports each module's client.js and hands it the
moduleApi (config, instanceSettings, scoped fetch, auto-reconnecting sse,
setStatus). Clock module proves the contract end to end; password-typed
config fields never leave the server.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNYUv9EyrGSFmCnmH7WYKH
Milestone 1 of SPEC.md. Zero-dependency Node server serving public/;
12-column tile grid with add (placeholder tile), drag to move, drag to
resize, remove; collision-free placement; layout autosaves to this
browser's localStorage and survives reload.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNYUv9EyrGSFmCnmH7WYKH