Stage 0 (Signage production readiness): the platform half. Signage's side follows in a separate PR that pins this one.
Defects fixed
Defect (reproduced on main with the display-device harness)
Root cause
Fix
A browser online event tore down and remounted a running display (connectivity-blip: 1 remount + offline badge per blip)
DisplayApp bumped attempt, which reset state to loading and re-keyed the frame
Re-resolve the descriptor in the background; remount only if descriptor or principal changed; keep the frame on transient errors (ADR 0043 requires re-resolution, not a remount)
A device revoked while its display was open kept playing and polled 403s forever (revoke scenario failed)
DisplayUiFrame never wired onAuthorizationResponse; nothing re-checked authorization
A refused (401/403) module request triggers a throttled revalidation; a denied descriptor returns the screen to pairing (ADR 0036)
A kiosk that boots before its server stops on "could not be reached" until someone presses Retry
No automatic retry when the network is up but the API is not
Backoff retry from failed/unavailable states (2 s → 60 s)
A module upgraded on the server is never picked up by a running display
Nothing re-resolved the descriptor while running
Background re-resolution every 5 min; the same answer disturbs nothing
Offline indicator flashed for any blip and said "waiting for Ekklesia" on a lobby wall
No grace period; hard-coded platform branding
10 s grace; neutral wording
Revoke acted on one click
No confirmation
Shared in-product Dialog explaining the consequence (DESIGN §21.4)
The harness's revoke scenario left the kiosk revoked, so every later display check failed by construction (why Signage could never run it)
Scenario stopped at the pairing screen
It now re-pairs the screen and asserts it resumes and keeps its Signage roster entry
Also
CLAUDE.md updated: separate module repositories, parent ROADMAP.md/DESIGN.md authoritative, ownership/branding/token/dark-mode/no-native-dialog rules, displays transitional until Stage 1, branch-per-PR, no push to main, no release tags. It previously described in-repo modules/* and manifest v7.
deploy/display-kiosk.md: reproducible kiosk setup (single launcher, persistent profile, systemd unit, pairing and the fixed 180-day credential, outage behaviour, offline cold-start limit).
No ADR is changed or superseded: the behaviour now matches ADR 0036 and ADR 0043 as written. No contract, schema, migration, or credential format changes; existing paired displays are unaffected.
New tests: surfaces/shell/tests/display-app.test.tsx (8 continuity/revocation/retry cases, all failing on main), surfaces/shell/tests/device-panel.test.tsx.
PR CI on the runner: all 8 jobs green at bae4ff3 (verify, infrastructure/migration smoke, package release/publication, image build, both deploy topologies).
Display-device harness, split topology, with the Signage Stage 0 package: pair, restart, two-pages, connectivity-blip, session-blip, stream-failure, caching-probe, stream-recycle, revoke (re-pair) all pass, then API restart, container recreation, disable/enable, and remove/reinstall: Zayin/Signage#18 run 274.
Known: the publish-packages.yml workflow and docs/development/releases.md / docs/module-development/README.md still name the old git.riverofglorymn.org/.../River_of_Glory_Church registry, which now answers 502. The packages live at git.zayin.net/api/packages/Zayin/npm/. Not changed here (infrastructure; flagged for the maintainer before the next package release).
Deferred to Stage 1
Settings → Displays, global display names, the hard-coded platform name in the Dashboard shell header/login, responsive shell, theme editor.
Stage 0 (Signage production readiness): the platform half. Signage's side follows in a separate PR that pins this one.
## Defects fixed
| Defect (reproduced on `main` with the display-device harness) | Root cause | Fix |
|---|---|---|
| A browser `online` event tore down and remounted a running display (`connectivity-blip`: 1 remount + offline badge per blip) | `DisplayApp` bumped `attempt`, which reset state to `loading` and re-keyed the frame | Re-resolve the descriptor in the background; remount only if descriptor or principal changed; keep the frame on transient errors (ADR 0043 requires re-resolution, not a remount) |
| A device revoked while its display was open kept playing and polled 403s forever (`revoke` scenario failed) | `DisplayUiFrame` never wired `onAuthorizationResponse`; nothing re-checked authorization | A refused (401/403) module request triggers a throttled revalidation; a denied descriptor returns the screen to pairing (ADR 0036) |
| A kiosk that boots before its server stops on "could not be reached" until someone presses Retry | No automatic retry when the network is up but the API is not | Backoff retry from failed/unavailable states (2 s → 60 s) |
| A module upgraded on the server is never picked up by a running display | Nothing re-resolved the descriptor while running | Background re-resolution every 5 min; the same answer disturbs nothing |
| Offline indicator flashed for any blip and said "waiting for Ekklesia" on a lobby wall | No grace period; hard-coded platform branding | 10 s grace; neutral wording |
| Revoke acted on one click | No confirmation | Shared in-product `Dialog` explaining the consequence (DESIGN §21.4) |
| The harness's `revoke` scenario left the kiosk revoked, so every later display check failed by construction (why Signage could never run it) | Scenario stopped at the pairing screen | It now re-pairs the screen and asserts it resumes and keeps its Signage roster entry |
## Also
- `CLAUDE.md` updated: separate module repositories, parent `ROADMAP.md`/`DESIGN.md` authoritative, ownership/branding/token/dark-mode/no-native-dialog rules, displays transitional until Stage 1, branch-per-PR, no push to `main`, no release tags. It previously described in-repo `modules/*` and manifest v7.
- `deploy/display-kiosk.md`: reproducible kiosk setup (single launcher, persistent profile, systemd unit, pairing and the fixed 180-day credential, outage behaviour, offline cold-start limit).
No ADR is changed or superseded: the behaviour now matches ADR 0036 and ADR 0043 as written. No contract, schema, migration, or credential format changes; existing paired displays are unaffected.
## Verification
- `pnpm ci:verify` passes (shell 229 tests, API 511, root 152); compose/deploy config valid.
- New tests: `surfaces/shell/tests/display-app.test.tsx` (8 continuity/revocation/retry cases, all failing on `main`), `surfaces/shell/tests/device-panel.test.tsx`.
- PR CI on the runner: all 8 jobs green at `bae4ff3` (verify, infrastructure/migration smoke, package release/publication, image build, both deploy topologies).
- Display-device harness, split topology, with the Signage Stage 0 package: `pair`, `restart`, `two-pages`, `connectivity-blip`, `session-blip`, `stream-failure`, `caching-probe`, `stream-recycle`, `revoke` (re-pair) all pass, then API restart, container recreation, disable/enable, and remove/reinstall: Zayin/Signage#18 run 274.
- Known: the `publish-packages.yml` workflow and `docs/development/releases.md` / `docs/module-development/README.md` still name the old `git.riverofglorymn.org/.../River_of_Glory_Church` registry, which now answers 502. The packages live at `git.zayin.net/api/packages/Zayin/npm/`. Not changed here (infrastructure; flagged for the maintainer before the next package release).
## Deferred to Stage 1
Settings → Displays, global display names, the hard-coded platform name in the Dashboard shell header/login, responsive shell, theme editor.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Core's agent guidance still described modules living in this monorepo and
manifest v7, and said nothing about the product rules that now govern every
change. Record that first-party modules live in their own Zayin repositories
and install as Module Packages, that the parent ROADMAP.md and DESIGN.md are
authoritative for product direction, the ownership, branding, token, dark-mode
and no-native-dialog rules, that display pairing is transitional until Stage 1,
and the branch-per-PR, no-push-to-main, and no-release-tag workflow.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every browser 'online' event reset the display host to its loading state and
mounted the module again: a fresh viewer session, a fresh stage, and every
cached object URL released, on each Wi-Fi blip. And a device revoked while its
display was open never noticed; it kept playing cached content and polling
403s until someone reloaded the page.
The host now re-resolves its descriptor in the background (ADR 0043) and only
replaces the frame when the answer actually changed: a new version or
principal remounts, a refusal returns the screen to pairing (ADR 0036), and
any other failure keeps the running display. A module request refused with
401/403 prompts the same revalidation, throttled, so revocation is noticed
within one heartbeat. The offline indicator waits ten seconds before
appearing, so a momentary blip shows nothing, and no longer names the
platform on a lobby wall.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Revoke acted on a single click, yet it strands the screen until someone
re-pairs it in person. Explain the consequence in the shared in-product
Dialog first (DESIGN 21.4), with an obvious Cancel.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Stage 0 requires that kiosk setup can be reproduced from documentation, and
there was none. Cover the address, a single-launcher Chromium command with a
persistent profile, a systemd user unit, pairing and the fixed 180-day device
credential, what the screen does through each kind of outage, the known offline
cold-start limit, and why a second launcher produces duplicate windows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A kiosk that booted before its server, as after a power cut, stopped on 'This
display could not be reached' until someone pressed Retry: with the network
up, no online event ever came. It now retries on its own, from two seconds
doubling to a minute. A running display also re-resolves its descriptor every
five minutes in the background, so a module upgraded on the server is picked
up without a reload, and the same answer disturbs nothing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The revoke scenario left the kiosk revoked, so any display check after it -
the post-recreation restart check in particular - failed by construction.
That is why Signage could never run it. Finish the scenario the way the
roadmap states the requirement: a revoked screen shows a fresh code, is paired
again, resumes without a reload, and for Signage keeps its existing roster
entry rather than appearing as a new screen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ADR 0056 (Zayin/Ekklesia#94) replaces the fixed 180 days from pairing with
180 days from last use, renewed while the screen is in use; the guide no
longer tells operators to track pairing dates.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Stage 0 (Signage production readiness): the platform half. Signage's side follows in a separate PR that pins this one.
Defects fixed
mainwith the display-device harness)onlineevent tore down and remounted a running display (connectivity-blip: 1 remount + offline badge per blip)DisplayAppbumpedattempt, which reset state toloadingand re-keyed the framerevokescenario failed)DisplayUiFramenever wiredonAuthorizationResponse; nothing re-checked authorizationDialogexplaining the consequence (DESIGN §21.4)revokescenario left the kiosk revoked, so every later display check failed by construction (why Signage could never run it)Also
CLAUDE.mdupdated: separate module repositories, parentROADMAP.md/DESIGN.mdauthoritative, ownership/branding/token/dark-mode/no-native-dialog rules, displays transitional until Stage 1, branch-per-PR, no push tomain, no release tags. It previously described in-repomodules/*and manifest v7.deploy/display-kiosk.md: reproducible kiosk setup (single launcher, persistent profile, systemd unit, pairing and the fixed 180-day credential, outage behaviour, offline cold-start limit).No ADR is changed or superseded: the behaviour now matches ADR 0036 and ADR 0043 as written. No contract, schema, migration, or credential format changes; existing paired displays are unaffected.
Verification
pnpm ci:verifypasses (shell 229 tests, API 511, root 152); compose/deploy config valid.surfaces/shell/tests/display-app.test.tsx(8 continuity/revocation/retry cases, all failing onmain),surfaces/shell/tests/device-panel.test.tsx.bae4ff3(verify, infrastructure/migration smoke, package release/publication, image build, both deploy topologies).pair,restart,two-pages,connectivity-blip,session-blip,stream-failure,caching-probe,stream-recycle,revoke(re-pair) all pass, then API restart, container recreation, disable/enable, and remove/reinstall: Zayin/Signage#18 run 274.publish-packages.ymlworkflow anddocs/development/releases.md/docs/module-development/README.mdstill name the oldgit.riverofglorymn.org/.../River_of_Glory_Churchregistry, which now answers 502. The packages live atgit.zayin.net/api/packages/Zayin/npm/. Not changed here (infrastructure; flagged for the maintainer before the next package release).Deferred to Stage 1
Settings → Displays, global display names, the hard-coded platform name in the Dashboard shell header/login, responsive shell, theme editor.
🤖 Generated with Claude Code
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.