423056e885aabc4fc2efc1e058976a6266438319
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
423056e885 |
Implement Phase 6A visual management dashboard
Build the desktop management surface at / while leaving the verified playback engine and display renderer untouched. Opening the dashboard goes straight to the playlist that is actually active, with slide imagery dominating rather than an admin table. Playlist selection and activation are deliberately separate. Choosing a playlist only changes what is being edited; an inactive playlist offers an explicit Activate Playlist action. Activation remains the server's decision: when playback queues the request to the current slide boundary the header shows STARTING SOON and keeps the old playlist marked active until authoritative state promotes it, so the UI never fakes activation timing. Entries render as large tiles in a responsive grid where position is the sequence, so no ordinal numbers are stamped on slides. Names drop file extensions and are uppercased in Roboto for presentation only; stored display names keep their case. Status rings are green, gray, amber, or red and are always accompanied by the same status as text, so nothing depends on colour alone. Clicking a tile toggles that playlist entry only, never the library asset or its use in other playlists. Tiles reorder by drag through dnd-kit, persisting through the existing playlist API and snapping back to the authoritative order if the server rejects the change. React identity is always the stable PlaylistEntry UUID, never an index. Uploads accept PNG, JPEG, and WebP from the picker or a drop anywhere on the page, run sequentially so appended order matches selection order, and report per-file state without blocking the dashboard. Content duplicates and assets already in the playlist are surfaced rather than duplicated. The Library panel browses and reuses existing assets, and permanent assets are never shown as stale. The live preview reuses the display's own presentation resolver and scene stylesheet, which are extracted to a shared module so the miniature and the signage output cannot drift apart. It takes on none of the kiosk's authority: no worker, no offline rotation, no heartbeat, no identity. Management is desktop-only below 768px, a restriction /display never inherits. Compose now attaches only the web service to the external tabernet network as announcements-web, keeping the API and object storage private. Verified with 200/200 backend, 20 client unit, and 44 Playwright tests. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
ba678d08d5 |
Complete Phase 5B offline display resilience
Make the /display kiosk survive losing its infrastructure, then surrender authority cleanly when it returns. The Phase 5A renderer is unchanged: this builds on its two permanent scene layers, explicit Cache Storage, IndexedDB, Service Worker, timing anchors, and structured errors. While connected the server is authoritative. While disconnected the display becomes a deterministic local continuation of the last authoritative state it persisted, and the moment the server speaks again the local cursor is abandoned rather than reconciled or reported back. A small client evaluator resolves offline playback from the persisted manifest and clock anchor. Catch-up is arithmetic rather than iterative: whole rotations are skipped between eligibility boundaries, so a month offline resolves in milliseconds. Mixed durations, wraparound, [start, end) schedules, and expirations are evaluated locally, and a null expiration stays permanent. Mode off keeps the hidden cursor advancing, pause holds its slide and remainder, static holds until the cached metadata says the asset genuinely expired, and an activation queued for an unknown playlist holds the last good frame instead of continuing into a playlist that is being replaced. Contract tests run this evaluator and the real Phase 3B engine over identical scenarios and assert the same slide, start, and deadline, so the client tracks the server without refactoring it. Cold start restores signage from the Service Worker shell, IndexedDB, and Cache Storage with the entire stack down, keeping the same persistent display identity. Validated display error codes now become Home Assistant incidents through the Phase 4 machinery, keyed per display and condition so a persisting error reports once and recovers once. Arbitrary browser text stays telemetry. Corrects the deployment assumption: the kiosk runs on a separate machine, so production requires a trusted HTTPS origin and localhost is development only. Verifying on a real trusted certificate exposed two production bugs that localhost had hidden — behind a reverse proxy an outage arrives as a 502 rather than a refused connection, and an unbounded shell fetch left a restarting kiosk blank for the proxy's whole connect timeout. The worker now treats a gateway error as an outage and bounds that wait. Verified with 200/200 backend, 20 client unit, and 30 Playwright tests on a trusted HTTPS origin with no browser security flags weakened. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
147e375702 |
Implement Phase 5A resilient display renderer
Build the real /display Chromium signage client as a React route on the existing single-page build. The server remains authoritative; the display only consumes the verified Phase 3B snapshot and reports telemetry. The renderer owns two permanent scene layers that are never recreated. A transition prepares the hidden layer, decodes the incoming image on the element that will actually be composited, then raises that layer above the current one at opacity 0 and fades it in while the outgoing layer stays fully opaque underneath. This removes the v1 defect where two simultaneous fades let the black root through the composite. Every asynchronous load and transition carries a generation token, so work finishing after authoritative state moved on is discarded rather than shown. A true 16:9 slide fills the frame with no backdrop or shadow; anything else is contained, centred, and uncropped over a blurred, solid, or black background with a subtle shadow. Blur uses the global setting and is overscanned; boolean darkening applies to the background only. Static targets use the same renderer with the snapshot's global presentation defaults, and mode off is a topmost gate while the renderer keeps tracking underneath. Slide bytes live in an explicit Cache Storage cache with reference counted object URLs and conservative pruning, never in ordinary HTTP caching. A new manifest is staged before promotion, so one unfetchable asset marks only itself unavailable instead of rejecting a playlist. A small IndexedDB store with explicit version handling holds the persistent client ID, last known good manifest, timing anchor, and last structured error, and discards malformed records on read. A narrow service worker caches only the /display app shell, network-first for navigations, and explicitly bypasses media, API, and socket traffic. The display sends a real display:heartbeat every 12 seconds with its actual state, and reports structured errors as bounded JSON that the server's existing string validation already accepts; /api/displays parses them back into an object, so no migration was required. Verified against an isolated stack through Nginx with 20 Playwright Chromium tests plus 9 client unit tests, and 187/187 backend tests. Co-Authored-By: Claude Opus 5 <[email protected]> |