Commit Graph
9 Commits
Author SHA1 Message Date
ekklesiaandClaude Opus 5.5 518fd101a2 test(display): a display recovers from every failed request without a reload
Pins what a kiosk needs from each network path, on a virtual clock: a
registration that fails once is retried with backoff until it works, a
state refresh that fails is retried without a realtime nudge, and a
realtime stream the host reports closed is subscribed again. Nothing is
scheduled after stop.

Fails today on all three paths: each is tried once. The first matches
the production screen exactly: mounted, showing "Offline — continuing
from the last update", with no heartbeat and no retry until someone
reloads it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 21:31:49 -05:00
ekklesiaandClaude Opus 5.5 f1b7d67aa5 fix(display): define the display-server messages once, in src/shared
CI / lint (pull_request) Successful in 13s
CI / typecheck (pull_request) Successful in 15s
CI / test (pull_request) Successful in 21s
CI / build (pull_request) Successful in 18s
CI / integration (pull_request) Successful in 3m32s
The display controller and the viewer routes each described the
heartbeat their own way and disagreed about one thing: the display sent
null for "nothing to report", and the route accepted only a missing
field. Every heartbeat from a screen showing a native image was refused
with a 400. The roster therefore marked working screens Offline 90 s
after they opened, and never recorded offlineReady or cachedAssetCount.

src/shared/display-protocol.ts now defines every message between a
display and the server:
- viewer session request and response;
- heartbeat request and response;
- channel state;
- the realtime display payload.

Both halves import it. Its rules: a nullable field accepts null
explicitly, an absent field means "not reported", and an unknown field
is ignored, so a newer display is never refused by an older server.

- The routes parse with the shared parsers, and a refusal names the
  field with Signage's sanitized code.
- The server's channel state extends the wire type, so a field the
  display relies on cannot drift without a compile error.
- The display builds its heartbeat as the shared type, and reads its
  registration with the shared parser instead of casting it.
- Test stubs that described server responses more loosely than the
  server sends them (no viewerName, no stale, partial media) now match
  the real responses and are typed as the protocol.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 21:25:21 -05:00
ekklesiaandClaude Opus 5.5 458043280a test(display): run the real display controller against the real viewer routes
Every display test answered the controller with a hand-written stub, and
every viewer test called the service with a hand-written object, so
neither side was ever tested against the other. In production every
heartbeat was refused with a 400.

This suite answers the real controller's requests with the real viewer
route handlers over the in-memory store. The routes gain an optional
store factory for that; production still uses the module database.

Fails today: POST /viewers/:viewerId/heartbeat -> 400
SIGNAGE_INVALID_REQUEST, because the display sends
reportedContributionId: null and the route accepts only a missing field.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-05 21:25:21 -05:00
ekklesia 1bf89a3942 feat: align Signage with platform display and design 2026-09-30 18:21:42 -05:00
ekklesia af0f0adadc test: align signage release expectations 2026-09-29 20:50:02 -05:00
ekklesia 36c1a72363 feat: make display setup actionable 2026-09-29 20:49:51 -05:00
ekklesia aa8cb1c96f fix: complete display and channel group management 2026-09-29 18:15:26 -05:00
ekklesia 5760059ee3 fix: make timeline activation durable and visible 2026-09-29 18:15:08 -05:00
ekklesia 92bef4c7aa feat: initial Signage 0.1.0 2026-08-28 16:41:54 -05:00