phase: 08_kiosk_page
**Phase 08 final verification pass — complete, all green.**
**Verified (all 5 tasks already implemented):**
- `static/wall.js` (`createWall` + `installWallKeys`), `kiosk.html`/`kiosk.js`, `GET /kiosk` route — guards: no `$("#…")` in wall.js, one keydown listener, kiosk ids no-collision across 3 pages, no audio field in `arrange`, no header/share link in markup, address gate covers `/kiosk`
- Cross-page e2e: arrival-arrangement, keypress-on-one-display moves the other (asserted on the second display's decoded moving pixels), dead mirror → `data-layout="wall"` on both, `followers` count, pointer-free meta-test
**Gates (exact outcomes):**
- `uv run ruff check . && uv run ruff format --check .` — passed
- `uv run pyright` — 0 errors
- `uv run pytest` — 341 passed, 5 skipped (realdisplay-gated), 96s
- `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` — 4 passed, 1 skipped (documented: host has no audio source for real capture)
- Coverage 96% (>90%); only warning is a starlette/httpx third-party deprecation
**Completion criteria:** all 9 ✅ — e2e green incl. `test_kiosk_layout.py` (the one e2e diff is the phase-mandated address-gate extension to `/kiosk`; assertion logic untouched), full suite 100% passing, README documents `/kiosk`, PLAN.md untouched.
**Documentation:**
- README: fixed the Kiosk-client section that implied the display box is permanently pointed at `/watch` — now states the display page is `/kiosk` with the runbook's one-line re-point (installer default still `/watch` this phase, per docs). Rest of README verified against code (routes, config table, CLI flag, presets, key table) — consistent.
- AGENTS.md: fixed 2 stale statements — CDN test moved to `TestPages` (was `TestClientScripts`), suite runtime ~50s → ~95s. No content added.
**Notable:** PLAN.md §6 says "per-leg `maxBitrate` cap (default 12 Mbit/s)" but code/README default is 25 Mbit/s — pre-existing, left untouched (locked file; flagged for a user decision).
**Next pending phase:** `09_wall_remote_page`.
This commit is contained in:
@@ -64,6 +64,56 @@ so the fleet is auditable.
|
||||
|
||||
---
|
||||
|
||||
## Two endpoints: `/watch` and `/kiosk`
|
||||
|
||||
The wall has two client pages, and they are not the same page twice:
|
||||
|
||||
* **`/watch` is a wall a person operates.** The operator's page: the grid,
|
||||
the keys, and the chrome a person standing in front of it needs — header,
|
||||
share link, counts.
|
||||
* **`/kiosk` is a display that obeys a wall.** No chrome at all: no header,
|
||||
no counts, no share button — the net pill is the only thing on screen that
|
||||
is not a mirror. It subscribes to every mirror like `/watch` does and then
|
||||
does what the shared layout state says — on arrival *and* on every change —
|
||||
with no input from anyone standing at the box. Point the box at it and it
|
||||
arranges itself: whichever mirror is on stage, fullscreen or not, is
|
||||
applied before the first tile finishes painting, so a person who points a
|
||||
kiosk at `/kiosk` never has to touch it. When a staged mirror goes away,
|
||||
every display falls back to the wall in the same frame set; no display sits
|
||||
on a dead tile.
|
||||
|
||||
The state is the boss, locally too: **the wall keys still work on `/kiosk`**
|
||||
for the moment a keyboard is plugged in (see [the key table](#operating-the-wall-keys)),
|
||||
and a keypress there performs the change *and* publishes the same `arrange`
|
||||
intent, so a controller and a display converge on one state instead of
|
||||
disagreeing (`m` is local-only — a display never becomes audible because
|
||||
another machine asked). The **controller side** — a page a person on another
|
||||
machine drives the wall from — arrives in phase 09 (`/remote`); until then a
|
||||
controller is any client that can send an `arrange` frame on `/ws`, `websocat`
|
||||
included. `GET /api/kiosk` is the state's read surface: what is on stage,
|
||||
whether it is fullscreen, and how many displays (`followers`) are obeying it.
|
||||
|
||||
**The migration rule, stated honestly.** Existing clients keep their own
|
||||
`MIRROR_KIOSK_URL`: an existing `kiosk.conf` URL wins over the built-in
|
||||
default, and the installer's default still points at `/watch` in this phase.
|
||||
So **nothing changes under a box until its `kiosk.conf` is re-pointed and
|
||||
`gdm` restarted**:
|
||||
|
||||
```bash
|
||||
sudo sed -i 's|^MIRROR_KIOSK_URL=.*|MIRROR_KIOSK_URL="https://…/kiosk"|' \
|
||||
/var/home/kiosk/.config/mirror/kiosk.conf
|
||||
sudo systemctl restart gdm
|
||||
```
|
||||
|
||||
(`pkill -u kiosk firefox` is the shorter path — the loop re-sources
|
||||
`kiosk.conf` on every relaunch, see [the section below](#the-two-facts-that-make-this-work) —
|
||||
`gdm` is the clean restart when you want the whole session back.) The kiosk
|
||||
page serves `<title>Wall · mirror`; `check-kiosk.sh`, untouched in this
|
||||
phase, still greps for `<title>Watch · mirror`, and the checker is widened to
|
||||
accept the kiosk title in phase 09, when the default URL moves with it.
|
||||
|
||||
---
|
||||
|
||||
## The two facts that make this work
|
||||
|
||||
**Silverblue's mutable seams.** `/usr` is read-only and belongs to the OSTree
|
||||
|
||||
Reference in New Issue
Block a user