diff --git a/.agents/phases/todo/08_kiosk_page/00_phase.md b/.agents/phases/complete/08_kiosk_page/00_phase.md similarity index 100% rename from .agents/phases/todo/08_kiosk_page/00_phase.md rename to .agents/phases/complete/08_kiosk_page/00_phase.md diff --git a/.agents/phases/todo/08_kiosk_page/01_extract_wall_tiles_and_stage.md b/.agents/phases/complete/08_kiosk_page/01_extract_wall_tiles_and_stage.md similarity index 100% rename from .agents/phases/todo/08_kiosk_page/01_extract_wall_tiles_and_stage.md rename to .agents/phases/complete/08_kiosk_page/01_extract_wall_tiles_and_stage.md diff --git a/.agents/phases/todo/08_kiosk_page/02_extract_wall_key_router.md b/.agents/phases/complete/08_kiosk_page/02_extract_wall_key_router.md similarity index 100% rename from .agents/phases/todo/08_kiosk_page/02_extract_wall_key_router.md rename to .agents/phases/complete/08_kiosk_page/02_extract_wall_key_router.md diff --git a/.agents/phases/todo/08_kiosk_page/03_kiosk_page_and_route.md b/.agents/phases/complete/08_kiosk_page/03_kiosk_page_and_route.md similarity index 100% rename from .agents/phases/todo/08_kiosk_page/03_kiosk_page_and_route.md rename to .agents/phases/complete/08_kiosk_page/03_kiosk_page_and_route.md diff --git a/.agents/phases/todo/08_kiosk_page/04_kiosk_layout_applier.md b/.agents/phases/complete/08_kiosk_page/04_kiosk_layout_applier.md similarity index 100% rename from .agents/phases/todo/08_kiosk_page/04_kiosk_layout_applier.md rename to .agents/phases/complete/08_kiosk_page/04_kiosk_layout_applier.md diff --git a/.agents/phases/todo/08_kiosk_page/05_kiosk_layout_e2e_and_docs.md b/.agents/phases/complete/08_kiosk_page/05_kiosk_layout_e2e_and_docs.md similarity index 100% rename from .agents/phases/todo/08_kiosk_page/05_kiosk_layout_e2e_and_docs.md rename to .agents/phases/complete/08_kiosk_page/05_kiosk_layout_e2e_and_docs.md diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__00_phase.a1.err b/.agents/reports/08_kiosk_page/08_kiosk_page__00_phase.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__00_phase.a1.md b/.agents/reports/08_kiosk_page/08_kiosk_page__00_phase.a1.md new file mode 100644 index 0000000..a762d21 --- /dev/null +++ b/.agents/reports/08_kiosk_page/08_kiosk_page__00_phase.a1.md @@ -0,0 +1,22 @@ +**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`. diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__00_phase.a1.md.status b/.agents/reports/08_kiosk_page/08_kiosk_page__00_phase.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/08_kiosk_page/08_kiosk_page__00_phase.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__00_phase.a1.validate b/.agents/reports/08_kiosk_page/08_kiosk_page__00_phase.a1.validate new file mode 100644 index 0000000..e919b32 --- /dev/null +++ b/.agents/reports/08_kiosk_page/08_kiosk_page__00_phase.a1.validate @@ -0,0 +1,36 @@ +........................................................................ [ 20%] +......................................sssss............................. [ 41%] +........................................................................ [ 62%] +........................................................................ [ 83%] +.......................................................... [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 194 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 706 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 +341 passed, 5 skipped, 1 warning in 96.44s (0:01:36) +All checks passed! +33 files already formatted +0 errors, 0 warnings, 0 informations diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__01_extract_wall_tiles_and_stage.a1.err b/.agents/reports/08_kiosk_page/08_kiosk_page__01_extract_wall_tiles_and_stage.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__01_extract_wall_tiles_and_stage.a1.md b/.agents/reports/08_kiosk_page/08_kiosk_page__01_extract_wall_tiles_and_stage.a1.md new file mode 100644 index 0000000..739891b --- /dev/null +++ b/.agents/reports/08_kiosk_page/08_kiosk_page__01_extract_wall_tiles_and_stage.a1.md @@ -0,0 +1,16 @@ +Task 01 complete — all gates green. + +**Implemented** +- New `src/mirror/static/wall.js`: `createWall({grid, stageEl, template, send, rtc, onChange, stats})` owning tiles, peers, stage, selection, mute policy, autofade, fullscreen-exit race, stats ticker, `closePeers()`; returns the task's surface plus `selectedTile()`, `revealStageControls()`, `handleFullscreenChange()`. Zero document queries; socket via page's `send` (guarded on `state.ws` in the page closure, so the wall builds before the socket opens). +- `watch.js` now a thin page: socket, `renderCounts` as the `onChange` callback, `fullscreenchange`/pointer/key bindings, help, refusal, share link. Dead `ui.unstage` branch deleted with a comment pointing at the per-tile `.exit` button. +- `app.css`: `#watch-stage` → `.stage` (all 10 rules, comments updated to class-scoped/source-order rationale); `watch.html` stage gains `class="stage"`. +- **Collision found & fixed:** the share page already used `class="stage"` — the literal re-key would have made `#preview-stage` inherit `position: fixed; inset: 0; display: none` (same specificity, later rule wins). Renamed the share preview to `.preview-stage` (matching its id) so `.stage` is wall-only; `test_share_layout`/`test_live_mirror` pass unedited, proving the share page is unchanged. +- `test_app.py`: `wall.js` in parse/asset lists; new `test_wall_never_looks_up_elements_in_the_document` (cites the id-collision test as its reason); `test_the_two_pages_use_the_media_apis_they_claim` re-pointed at `wall.js` (RTCPeerConnection moved); the two TestStylesheet tests mechanically re-pointed to `.preview-stage`. + +**Gates** — `ruff check` + `format --check` clean; `pyright` 0 errors; targeted e2e (`-k "keyboard or live_mirror or audio or stage or share_layout"`) 92 passed **unedited**; full e2e 103 passed; full `uv run pytest` 322 passed / 5 skipped (realdisplay-marked); `MIRROR_REALDISPLAY=1 pytest -m realdisplay` 4 passed / 1 platform-documented skip. No e2e file touched. + +**Docs** — no doc impact (pure refactor; README/AGENTS.md state nothing this changed). + +**Decisions** — `.preview-stage` rename (above); `rtc` added to the wall options (peers moved into the wall, config stays page-owned); redundant `renderCounts()` in the `welcome` case dropped (per-`absorb` `onChange` covers it, identical final state). + +**Next pending task** — `02_extract_wall_key_router.md` (phase 08). diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__01_extract_wall_tiles_and_stage.a1.md.status b/.agents/reports/08_kiosk_page/08_kiosk_page__01_extract_wall_tiles_and_stage.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/08_kiosk_page/08_kiosk_page__01_extract_wall_tiles_and_stage.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__01_extract_wall_tiles_and_stage.a1.validate b/.agents/reports/08_kiosk_page/08_kiosk_page__01_extract_wall_tiles_and_stage.a1.validate new file mode 100644 index 0000000..b69116e --- /dev/null +++ b/.agents/reports/08_kiosk_page/08_kiosk_page__01_extract_wall_tiles_and_stage.a1.validate @@ -0,0 +1,36 @@ +........................................................................ [ 22%] +...............................sssss.................................... [ 44%] +........................................................................ [ 66%] +........................................................................ [ 88%] +....................................... [100%] +=============================== warnings summary =============================== +.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1 + /home/ducoterra/Projects/Personal/mirror/.venv/lib64/python3.14/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.14.8-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 191 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 703 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 +322 passed, 5 skipped, 1 warning in 98.55s (0:01:38) +All checks passed! +32 files already formatted +0 errors, 0 warnings, 0 informations diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__02_extract_wall_key_router.a1.err b/.agents/reports/08_kiosk_page/08_kiosk_page__02_extract_wall_key_router.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__02_extract_wall_key_router.a1.md b/.agents/reports/08_kiosk_page/08_kiosk_page__02_extract_wall_key_router.a1.md new file mode 100644 index 0000000..2008247 --- /dev/null +++ b/.agents/reports/08_kiosk_page/08_kiosk_page__02_extract_wall_key_router.a1.md @@ -0,0 +1,23 @@ +All done. Final state verified — here's the report: + +## Task 02 report — `installWallKeys()` in `wall.js` + +**Implemented** +- `wall.js`: new `installWallKeys({ wall, onCommand, root? })` — the one `keydown` listener: case-insensitive key→command switch (`next`, `previous`, `stage`, `fullscreen`, `wall`, `step_next`, `step_previous`, `mute`, `jump` w/ 0-based `index`, `help`, `escape`), chord/text-field bail-outs, staged-reveal before dispatch (never preventDefaults), and `preventDefault()` on exactly the keys that changed something. Wall commands carry module defaults (moved verbatim from `watch.js`, with their "why" comments); `help`/`escape` have no default (page policy). +- `wall.js`: `createWall` surface gains `reRequestFullscreen()` so the `f`-when-staged default can re-request on the stage element the wall owns (same element `/watch` used via `ui.stage`). +- `watch.js`: deleted `onWallKey`, `stageSelectedOrFirst`, `stepStage`, `toggleHelp` and the `document.addEventListener("keydown", …)` line; now `installWallKeys({ wall, onCommand })` with `onCommand` answering only `help` (toggle `#wall-help`, always handled) and `escape` (overlay first, then `wall.unstage()`, handled iff either happened) — the `?` focus comment moved with the behaviour. +- `test_app.py`: new guard — `installWallKeys` defined in `wall.js`, `wall.js` holds exactly one keydown listener, and no wall-page module (watch.js, future kiosk/remote) adds its own. + +**Results (all gates)** +- `uv run ruff check . && uv run ruff format --check .` → clean +- `uv run pyright` → 0 errors +- `uv run pytest tests/e2e/test_keyboard_wall.py` **unedited** → 35/35 passed +- `uv run pytest -m e2e` → 103 passed, 5 skipped (realdisplay-gated) +- `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` → 4 passed, 1 skipped (no audio source on host — pre-existing env skip) +- `uv run pytest` (full) → **323 passed, 5 skipped**, 0 failed; coverage ~95% (JS router covered by browser assertions, per AGENTS.md §4) + +**Docs:** no doc impact (pure refactor, no user-visible change; README/AGENTS.md unaffected). + +**Notable decision:** `grep 'addEventListener("keydown"' static/*.js` shows the one `wall.js` hit *plus* `share.js`'s pre-existing `/mirror` s/x/Esc listener (share-page policy, not a wall page — untouched, out of scope); the test guard encodes the "one listener per wall page" rule exactly. `fullscreen` got a module default (not listed in the task's default list, but required for item 5's thin-observer `/watch` and for `/kiosk` to publish while the module performs). + +**Next pending task:** `08_kiosk_page/03_kiosk_page_and_route.md` diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__02_extract_wall_key_router.a1.md.status b/.agents/reports/08_kiosk_page/08_kiosk_page__02_extract_wall_key_router.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/08_kiosk_page/08_kiosk_page__02_extract_wall_key_router.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__02_extract_wall_key_router.a1.validate b/.agents/reports/08_kiosk_page/08_kiosk_page__02_extract_wall_key_router.a1.validate new file mode 100644 index 0000000..73e0789 --- /dev/null +++ b/.agents/reports/08_kiosk_page/08_kiosk_page__02_extract_wall_key_router.a1.validate @@ -0,0 +1,36 @@ +........................................................................ [ 21%] +...............................sssss.................................... [ 43%] +........................................................................ [ 65%] +........................................................................ [ 87%] +........................................ [100%] +=============================== warnings summary =============================== +.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1 + /home/ducoterra/Projects/Personal/mirror/.venv/lib64/python3.14/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.14.8-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 191 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 703 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 +323 passed, 5 skipped, 1 warning in 92.90s (0:01:32) +All checks passed! +32 files already formatted +0 errors, 0 warnings, 0 informations diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__03_kiosk_page_and_route.a1.err b/.agents/reports/08_kiosk_page/08_kiosk_page__03_kiosk_page_and_route.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/08_kiosk_page/08_kiosk_page__03_kiosk_page_and_route.a1.md b/.agents/reports/08_kiosk_page/08_kiosk_page__03_kiosk_page_and_route.a1.md new file mode 100644 index 0000000..79a957e --- /dev/null +++ b/.agents/reports/08_kiosk_page/08_kiosk_page__03_kiosk_page_and_route.a1.md @@ -0,0 +1,19 @@ +All done. Final state is 100% green across all four gates. + +**Report — phase 08, task 03 (`GET /kiosk` + page skeleton)** + +**Implemented** +- `GET /kiosk` route in `app.py` (beside `/watch`), `/kiosk` added to `PAGES` (no-store); fixed the stale "Two pages" module docstring +- `static/kiosk.html`: chrome-free wall — `body.kiosk[data-layout="wall"][data-fullscreen="false"]`, `#kiosk-grid`, `#kiosk-empty` + `#kiosk-share-url`, `#kiosk-stage` with mandatory `class="stage"`, `#kiosk-hud`, `#kiosk-tile-template`, shared `net`/`notice`/`toast` only, no header, `