phase: 09_wall_remote_page
**Phase 09 — final verification pass (all 3 tasks already in `complete/`; phase work was in the working tree, verified, nothing needed fixing)**
**Gates** — `uv run ruff check . && uv run ruff format --check .` → clean; `uv run pyright` → 0 errors; `uv run pytest` → **360 passed, 5 skipped** (only the realdisplay rung), coverage 96% total / 95% app.py; `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` → 4 passed, 1 skipped (documented host limitation: no audio source on the real X11 path); `uv run pytest tests/e2e/test_remote_control.py` → 8/8 passed; `node --check` on every `static/*.js` → all pass.
**Completion criteria** — all met: (1) "Show on wall" stages on the display, asserted on display DOM + `/api/kiosk` ✓; (2) Back/Fullscreen + untouched second controller converge (TestConvergence) ✓; (3) `/api/streams` reports `viewers: 0` / `watching: 0` with only `/remote` open (TestACostsNothing) ✓; (4) followers count displays only — 0/1/1 with a second controller (TestFollowers) ✓; (5) controller works with no display: `followers: 0`, state stored (protocol test + e2e) ✓; (6) e2e + full `-m e2e` + realdisplay all green ✓; (7) README + SECURITY.md updated, Silverblue default now `/kiosk` (installer, conf, gnome script, checker) ✓; (8) suite 100% green, coverage >90% on new code ✓; (9) PLAN.md untouched; AGENTS.md changed only for a stale statement ✓.
**Documentation** — README.md reviewed in full against code (page table, quickstart, env-var table vs `config.py`/`debug.py`, presets, keys, deployment, kiosk section): no inconsistencies found. AGENTS.md: fixed the one stale statement introduced by this phase ("Four pages" → "Five pages"); no other staleness, nothing added.
**Decisions** — no repo changes were required; the one realdisplay skip is a pre-documented environment fact, not a failure.
**Next pending phase** — none; `todo/` held only phase 09.
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
**Phase 09 — final verification pass (all 3 tasks already in `complete/`; phase work was in the working tree, verified, nothing needed fixing)**
|
||||
|
||||
**Gates** — `uv run ruff check . && uv run ruff format --check .` → clean; `uv run pyright` → 0 errors; `uv run pytest` → **360 passed, 5 skipped** (only the realdisplay rung), coverage 96% total / 95% app.py; `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` → 4 passed, 1 skipped (documented host limitation: no audio source on the real X11 path); `uv run pytest tests/e2e/test_remote_control.py` → 8/8 passed; `node --check` on every `static/*.js` → all pass.
|
||||
|
||||
**Completion criteria** — all met: (1) "Show on wall" stages on the display, asserted on display DOM + `/api/kiosk` ✓; (2) Back/Fullscreen + untouched second controller converge (TestConvergence) ✓; (3) `/api/streams` reports `viewers: 0` / `watching: 0` with only `/remote` open (TestACostsNothing) ✓; (4) followers count displays only — 0/1/1 with a second controller (TestFollowers) ✓; (5) controller works with no display: `followers: 0`, state stored (protocol test + e2e) ✓; (6) e2e + full `-m e2e` + realdisplay all green ✓; (7) README + SECURITY.md updated, Silverblue default now `/kiosk` (installer, conf, gnome script, checker) ✓; (8) suite 100% green, coverage >90% on new code ✓; (9) PLAN.md untouched; AGENTS.md changed only for a stale statement ✓.
|
||||
|
||||
**Documentation** — README.md reviewed in full against code (page table, quickstart, env-var table vs `config.py`/`debug.py`, presets, keys, deployment, kiosk section): no inconsistencies found. AGENTS.md: fixed the one stale statement introduced by this phase ("Four pages" → "Five pages"); no other staleness, nothing added.
|
||||
|
||||
**Decisions** — no repo changes were required; the one realdisplay skip is a pre-documented environment fact, not a failure.
|
||||
|
||||
**Next pending phase** — none; `todo/` held only phase 09.
|
||||
@@ -0,0 +1 @@
|
||||
ok
|
||||
@@ -0,0 +1,37 @@
|
||||
........................................................................ [ 19%]
|
||||
..............................................sssss..................... [ 39%]
|
||||
........................................................................ [ 59%]
|
||||
........................................................................ [ 78%]
|
||||
........................................................................ [ 98%]
|
||||
..... [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib64/python3.15/site-packages/fastapi/testclient.py:1
|
||||
/home/ducoterra/Projects/Personal/mirror/.venv/lib64/python3.15/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.15.0-final-0 ________________
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
------------------------------------------------------
|
||||
src/mirror/__init__.py 3 0 100%
|
||||
src/mirror/__main__.py 72 3 96% 129, 145, 156
|
||||
src/mirror/access.py 60 0 100%
|
||||
src/mirror/app.py 197 10 95% 108, 113, 117, 182-183, 212-216
|
||||
src/mirror/config.py 68 5 93% 110-111, 118-120
|
||||
src/mirror/debug.py 15 10 33% 19-28
|
||||
src/mirror/protocol.py 66 0 100%
|
||||
src/mirror/registry.py 228 3 99% 208, 226, 460
|
||||
------------------------------------------------------
|
||||
TOTAL 709 31 96%
|
||||
=========================== short test summary info ============================
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:281: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:321: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:364: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:446: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:545: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
360 passed, 5 skipped, 1 warning in 102.18s (0:01:42)
|
||||
All checks passed!
|
||||
34 files already formatted
|
||||
0 errors, 0 warnings, 0 informations
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
All gates green. Task complete.
|
||||
|
||||
**Report**
|
||||
|
||||
- **Implemented:** `GET /remote` route in `src/mirror/app.py` (+ `PAGES` no-store); `static/remote.html` (bar + net pill, readout `#remote-state` with stable hooks, `#remote-back`/`#remote-fullscreen` controls, `#remote-list` + row template, empty state with share URL); `static/remote.js` stub (config, refusal notice, socket hello as plain `role:"viewer"` with no `kiosk` flag, net pill to online); `app.css` `.remote` styles (mono readout strip, wrapping control row, 320 px-safe rows, `#remote-fullscreen[aria-pressed="true"]` accent = tile audio button treatment); entry links in `watch.html` (`#remote-link`) and `index.html` (4th card); `/kiosk` untouched; `test_app.py` guards extended to the fourth page (render/no-store/asset/node-check/element-exists/stylesheet-module/id-collision over 5 pages with `{"net","notice","toast"}` as the only overlap, address gate, plus new `test_a_controller_is_media_free` and `test_the_remote_page_declares_its_control_hooks`).
|
||||
- **Results:** `uv run ruff check . && uv run ruff format --check .` — clean; `uv run pyright` — 0 errors; `uv run pytest` — 349 passed, 5 skipped (realdisplay opt-in), 100% green; `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` — 4 passed, 1 documented platform skip; `uv run pytest tests/test_app.py` — 104 passed; coverage 95% overall, new route fully covered. Live checks: `curl -s/-I localhost:8055/remote` serves the page with `cache-control: no-store`; browser at 320 px: net pill online, empty state + share URL, zero console errors, both buttons reachable, no horizontal scroll.
|
||||
- **Docs:** README — `/remote` row in the page table + quickstart URL line (SECURITY.md/full controller docs left to task 03 per the phase plan). AGENTS.md — one stale fact only: "Four pages" → "Five pages".
|
||||
- **Deviation:** the task text names both the readout marker span and the Full screen button `remote-fullscreen` (a duplicate id that would fail the page's own uniqueness guard). The button keeps `#remote-fullscreen` (the `aria-pressed` CSS rule and the task's guard target it); the readout marker is `#remote-fullscreen-mark`, noted in the markup and asserted by the new hook guard.
|
||||
- **Next pending task:** `.agents/phases/todo/09_wall_remote_page/02_remote_controller_logic.md`
|
||||
+1
@@ -0,0 +1 @@
|
||||
ok
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
........................................................................ [ 20%]
|
||||
......................................sssss............................. [ 40%]
|
||||
........................................................................ [ 61%]
|
||||
........................................................................ [ 81%]
|
||||
.................................................................. [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib64/python3.15/site-packages/fastapi/testclient.py:1
|
||||
/home/ducoterra/Projects/Personal/mirror/.venv/lib64/python3.15/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.15.0-final-0 ________________
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
------------------------------------------------------
|
||||
src/mirror/__init__.py 3 0 100%
|
||||
src/mirror/__main__.py 72 3 96% 129, 145, 156
|
||||
src/mirror/access.py 60 0 100%
|
||||
src/mirror/app.py 197 11 94% 108, 113, 116-117, 182-183, 212-216
|
||||
src/mirror/config.py 68 5 93% 110-111, 118-120
|
||||
src/mirror/debug.py 15 10 33% 19-28
|
||||
src/mirror/protocol.py 66 0 100%
|
||||
src/mirror/registry.py 228 3 99% 208, 226, 460
|
||||
------------------------------------------------------
|
||||
TOTAL 709 32 95%
|
||||
=========================== short test summary info ============================
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:281: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:321: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:364: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:446: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:545: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
349 passed, 5 skipped, 1 warning in 95.96s (0:01:35)
|
||||
All checks passed!
|
||||
33 files already formatted
|
||||
0 errors, 0 warnings, 0 informations
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
All green. Final report:
|
||||
|
||||
**Implemented (task 02 — `remote.js` controller logic):**
|
||||
- `src/mirror/static/remote.js`: `mirrors` Map + `layout` state; roster frames (`welcome` seeds, added/updated upsert, removed deletes, `signal` ignored); `renderMirrors()` (template rows, `data-on`/`aria-current`/`On the wall`, empty state); `renderState()` (staged title/`(gone)`/`no mirror`, fullscreen on/off on marker + button `aria-pressed`/label, followers plural handling); one-line hint (`no display following` / `offline`); `arrange()` writers with event delegation on `#remote-list`; one-time "…ended — back to the wall" toast; stale readout on socket close, re-sync on `welcome`.
|
||||
- `remote.html`: added `#remote-hint`; `app.css`: `.remote-hint` + dimmed `[data-stale]` readout.
|
||||
- `tests/test_app.py`: media-free guard extended (`MediaStream`, `navigator.mediaDevices`), new `test_a_controller_sends_exactly_one_frame_type_besides_hello`, hook list gains `remote-hint`, 2 protocol tests (controller drives layout, not counted in `followers`; `arrange` changes no subscriptions/audience).
|
||||
|
||||
**Gates (all 4, exact commands):** ruff check+format clean · pyright 0 errors · `uv run pytest` **352 passed, 5 skipped** (documented realdisplay skips) · `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` **4 passed, 1 documented skip** · coverage 95%, byte-identical to verified baseline (no `src/` Python touched).
|
||||
|
||||
**Docs:** no impact — README already describes `/remote` accurately (task 01); deep docs are task 03's scope; no stale AGENTS.md statements.
|
||||
|
||||
**Notable:** spec's "three spans" named `#remote-fullscreen`, but task 01's built HTML (locked contract, asserted in tests) uses `#remote-fullscreen-mark` for the readout span and `#remote-fullscreen` for the button — followed the built HTML (marker carries on/off, button carries label/aria). Pre-existing, verified in a reconstructed baseline: the non-gate combined run `MIRROR_REALDISPLAY=1 uv run pytest` errors 5 x11 fixture setups (Playwright sync-API double context); both official invocations are green, left untouched.
|
||||
|
||||
**Next pending task:** `03_remote_e2e_docs_and_kiosk_url.md`.
|
||||
+1
@@ -0,0 +1 @@
|
||||
ok
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
........................................................................ [ 20%]
|
||||
......................................sssss............................. [ 40%]
|
||||
........................................................................ [ 60%]
|
||||
........................................................................ [ 80%]
|
||||
..................................................................... [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib64/python3.15/site-packages/fastapi/testclient.py:1
|
||||
/home/ducoterra/Projects/Personal/mirror/.venv/lib64/python3.15/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.15.0-final-0 ________________
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
------------------------------------------------------
|
||||
src/mirror/__init__.py 3 0 100%
|
||||
src/mirror/__main__.py 72 3 96% 129, 145, 156
|
||||
src/mirror/access.py 60 0 100%
|
||||
src/mirror/app.py 197 11 94% 108, 113, 116-117, 182-183, 212-216
|
||||
src/mirror/config.py 68 5 93% 110-111, 118-120
|
||||
src/mirror/debug.py 15 10 33% 19-28
|
||||
src/mirror/protocol.py 66 0 100%
|
||||
src/mirror/registry.py 228 3 99% 208, 226, 460
|
||||
------------------------------------------------------
|
||||
TOTAL 709 32 95%
|
||||
=========================== short test summary info ============================
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:281: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:321: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:364: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:446: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:545: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
352 passed, 5 skipped, 1 warning in 96.76s (0:01:36)
|
||||
All checks passed!
|
||||
33 files already formatted
|
||||
0 errors, 0 warnings, 0 informations
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
All changes verified. The AGENTS.md diff ("Five pages") was from task 01 — I left AGENTS.md untouched (no stale statement in it). Everything is complete and green.
|
||||
|
||||
**Report**
|
||||
|
||||
- **New `tests/e2e/test_remote_control.py`** (8 tests, `e2e` + `timeout(180)`, same `device()`/`open_device` setup as the kiosk suite): the click loop (`.show` → display `data-layout="stage"` + staged tile + `/api/kiosk`, `#remote-back` → wall), pixels-not-classes (`frames_matching` width>0, distinct≥2 on the display), fullscreen (server state + `body[data-fullscreen]`, `document.fullscreenElement` never asserted, comment says why), two-controller convergence, controller-costs-nothing (stream `viewers: 0` / `stats.watching: 0` with only `/remote`, moves when `/watch` joins), followers agreement (0/1/1 — a controller is not a display), kiosk `n` keypress moving the remote readout, teardown (display unstages, row/readout clear), `errors == []` in every case.
|
||||
- **Fix found while testing**: the kiosk suite leaves `fullscreen: true` on the shared session server (teardown resets `staged` only), which broke the toggle-based fullscreen test; the test now normalizes the shared state through the product at start (documented in the test).
|
||||
- **SECURITY.md**: §2 gains the "rearrange anybody's wall display" bullet (names `/remote`, same trust decision as watching, bounded by `MIRROR_ALLOW_CIDRS`) and the not-possible audio bullet; §3 gains the "No remote audio" row naming the real enforcing tests.
|
||||
- **README**: new "Driving an unattended wall" section (keys on the box visible to controllers, fullscreen requested-not-promised, in-memory state); Kiosk-client paragraph updated for the new default. Page table/Quickstart already had both endpoints.
|
||||
- **Silverblue tree → `/kiosk` in one change**: `install-kiosk.sh` default + comment, `kiosk.conf` URL + comment, `gnome-kiosk-script` fallback, `check-kiosk.sh` (accepts both walls, warning kept for the third case), `kiosk-silverblue.md` (top description, `KIOSK_URL` example, controller/migration/title paragraphs de-staled). `sh -n` clean on all scripts; checker logic verified via harness: `/kiosk` and `/watch` → ok, other → warn.
|
||||
- **Gates**: `uv run pytest tests/e2e/test_remote_control.py` → 8 passed; `uv run pytest -m e2e` → 118 passed; `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` → 4 passed (1 documented platform skip); full `uv run pytest` → **360 passed, 5 skipped, 0 failed**; `ruff check`/`ruff format --check`/`pyright` all clean.
|
||||
- **Note**: one one-off `test_kiosk_layout.py` tile-id flake in a single full run (not reproduced in two subsequent full runs; my suite runs after it in file order, so it cannot be the cause). No doc impact on AGENTS.md.
|
||||
- **Next pending task**: none — task 03 is the last task of phase 09.
|
||||
+1
@@ -0,0 +1 @@
|
||||
ok
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
........................................................................ [ 19%]
|
||||
..............................................sssss..................... [ 39%]
|
||||
........................................................................ [ 59%]
|
||||
........................................................................ [ 78%]
|
||||
........................................................................ [ 98%]
|
||||
..... [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib64/python3.15/site-packages/fastapi/testclient.py:1
|
||||
/home/ducoterra/Projects/Personal/mirror/.venv/lib64/python3.15/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.15.0-final-0 ________________
|
||||
|
||||
Name Stmts Miss Cover Missing
|
||||
------------------------------------------------------
|
||||
src/mirror/__init__.py 3 0 100%
|
||||
src/mirror/__main__.py 72 3 96% 129, 145, 156
|
||||
src/mirror/access.py 60 0 100%
|
||||
src/mirror/app.py 197 10 95% 108, 113, 117, 182-183, 212-216
|
||||
src/mirror/config.py 68 5 93% 110-111, 118-120
|
||||
src/mirror/debug.py 15 10 33% 19-28
|
||||
src/mirror/protocol.py 66 0 100%
|
||||
src/mirror/registry.py 228 3 99% 208, 226, 460
|
||||
------------------------------------------------------
|
||||
TOTAL 709 31 96%
|
||||
=========================== short test summary info ============================
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:281: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:321: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:364: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:446: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
SKIPPED [1] tests/e2e/test_x11_capture.py:545: set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend
|
||||
360 passed, 5 skipped, 1 warning in 102.09s (0:01:42)
|
||||
All checks passed!
|
||||
34 files already formatted
|
||||
0 errors, 0 warnings, 0 informations
|
||||
Reference in New Issue
Block a user