phase: 04_reachable_share_cta
Build and Push Containers / test (push) Successful in 47s
Build and Push Containers / build-and-push-app (push) Successful in 10s

All verification is complete. The pending working-tree changes are exactly the phase 04 work (CSS, tests, the README narrow-screen line, the AGENTS.md "Two→Three pages" fix) — all accurate.

## Final report

**Verified (final pass — all 4 task files present in `complete/`):**
- Gate 1: `uv run ruff check . && uv run ruff format --check .` → all checks passed, 29 files formatted
- Gate 2: `uv run pyright` → 0 errors
- Gate 3: `uv run pytest` → **269 passed, 4 skipped** (realdisplay-gated), coverage 95%
- Gate 4: `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` → **3 passed, 1 skipped** (host delivers no audio source — documented in module docstring)

**Completion criteria:**
1. `#start` elementFromPoint at the 11 required sizes — ✅ `TestIdleShareButtonReachable` sweeps 12 viewports (superset); 12/12 passed
2. `#stop` clickable while sharing — ✅ `TestLiveStopButtonReachable` (320×568, 390×844, 1280×450) passed, incl. placeholder-hidden + livetag assertions
3. Single-cell stage guard — ✅ `test_the_share_stage_is_one_cell` + `test_the_stage_has_a_floor_and_a_ceiling` passed; CSS has `grid-template: 1fr / 1fr`, `.stage > * { grid-area: 1 / 1 }`, `min-height: 264px`, `max-height: min(70svh, 640px)` + vh fallback, `.placeholder { max-height: 100%; overflow: auto }`, `align-content: safe center`
4. Live video fills stage, rounded corners, `#livetag` top-left — ✅ verified by live-browser probe: video fills stage (decoded), stage radius 14px, livetag top-left, placeholder hidden
5. README accurate, AGENTS.md current, suite 100% green — ✅
6. Phases 01–03 unchanged — ✅ `test_keyboard_wall` + `test_audio_share` + `test_sender_fidelity`: 48 passed

**Documentation:** README.md reviewed in full (page table, quickstart, all 12 config defaults vs `config.py`, CLI flags, secure-context/wayland/audio sections, kiosk key table vs `watch.js`/`share.js`, dev commands) — **consistent, no fixes needed**. AGENTS.md verified statement-by-statement (commands, routes, test names, browser/realdisplay flags) — **no stale statements; no changes needed** (the pending "Two→Three pages" fix is correct).

**Decisions:** criterion 4 had no dedicated test, so verified directly with a throwaway probe reusing the e2e harness (script in /tmp, not committed). No live infrastructure touched. No defects found in prior work.

**Next pending phase:** none — `todo/` is empty of other phases.
This commit is contained in:
2026-11-01 23:26:36 -05:00
parent d43eb407f8
commit bf027672b5
34 changed files with 670 additions and 9 deletions
@@ -0,0 +1,23 @@
All verification is complete. The pending working-tree changes are exactly the phase 04 work (CSS, tests, the README narrow-screen line, the AGENTS.md "Two→Three pages" fix) — all accurate.
## Final report
**Verified (final pass — all 4 task files present in `complete/`):**
- Gate 1: `uv run ruff check . && uv run ruff format --check .` → all checks passed, 29 files formatted
- Gate 2: `uv run pyright` → 0 errors
- Gate 3: `uv run pytest` → **269 passed, 4 skipped** (realdisplay-gated), coverage 95%
- Gate 4: `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` → **3 passed, 1 skipped** (host delivers no audio source — documented in module docstring)
**Completion criteria:**
1. `#start` elementFromPoint at the 11 required sizes — ✅ `TestIdleShareButtonReachable` sweeps 12 viewports (superset); 12/12 passed
2. `#stop` clickable while sharing — ✅ `TestLiveStopButtonReachable` (320×568, 390×844, 1280×450) passed, incl. placeholder-hidden + livetag assertions
3. Single-cell stage guard — ✅ `test_the_share_stage_is_one_cell` + `test_the_stage_has_a_floor_and_a_ceiling` passed; CSS has `grid-template: 1fr / 1fr`, `.stage > * { grid-area: 1 / 1 }`, `min-height: 264px`, `max-height: min(70svh, 640px)` + vh fallback, `.placeholder { max-height: 100%; overflow: auto }`, `align-content: safe center`
4. Live video fills stage, rounded corners, `#livetag` top-left — ✅ verified by live-browser probe: video fills stage (decoded), stage radius 14px, livetag top-left, placeholder hidden
5. README accurate, AGENTS.md current, suite 100% green — ✅
6. Phases 01–03 unchanged — ✅ `test_keyboard_wall` + `test_audio_share` + `test_sender_fidelity`: 48 passed
**Documentation:** README.md reviewed in full (page table, quickstart, all 12 config defaults vs `config.py`, CLI flags, secure-context/wayland/audio sections, kiosk key table vs `watch.js`/`share.js`, dev commands) — **consistent, no fixes needed**. AGENTS.md verified statement-by-statement (commands, routes, test names, browser/realdisplay flags) — **no stale statements; no changes needed** (the pending "Two→Three pages" fix is correct).
**Decisions:** criterion 4 had no dedicated test, so verified directly with a throwaway probe reusing the e2e harness (script in /tmp, not committed). No live infrastructure touched. No defects found in prior work.
**Next pending phase:** none — `todo/` is empty of other phases.
@@ -0,0 +1,34 @@
........................................................................ [ 26%]
.................ssss................................................... [ 52%]
........................................................................ [ 79%]
......................................................... [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 185 11 94% 108, 113, 117, 182-183, 212-216, 245
src/mirror/config.py 68 5 93% 110-111, 118-120
src/mirror/debug.py 15 10 33% 19-28
src/mirror/protocol.py 59 0 100%
src/mirror/registry.py 191 3 98% 166, 180, 355
------------------------------------------------------
TOTAL 653 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
269 passed, 4 skipped, 1 warning in 68.73s (0:01:08)
All checks passed!
29 files already formatted
0 errors, 0 warnings, 0 informations
@@ -0,0 +1,10 @@
Everything is verified and green. Final report:
**Phase 04 / Task 01 — one-cell stage: complete.**
- **Implemented**: `src/mirror/static/app.css` `.stage` now declares `grid-template: 1fr / 1fr` (one cell) plus `.stage > * { grid-area: 1 / 1; }`, so `#preview`, `#placeholder`, `#livetag` overlap in one cell; invariant comment added at the rule. `.livetag` stays absolute, `.stage video` untouched, `share.js`/`mirror.html` untouched. Diff confined to the `/* share */` block.
- **Instrumented browser check** (Chromium `channel="chromium"`, `http://127.0.0.1`): idle `#placeholder` topOffset now 1 @320×568 and 187.4 @1280×800 (centred, no longer the constant 151 px row); `#start` bottom 37 px below stage at 320 (was 187) and fully inside at 1280 with `elementFromPoint` = `BUTTON#start`; live state A/B-probed against old CSS — geometry byte-identical at both sizes (placeholder hidden, `#stop` clickable, video + `#livetag` unchanged → no visual regression).
- **Screenshots**: `docs/screenshots/phase-04/` — `idle-1280x800.png`, `idle-390x844.png`, `live-1280x800.png` (visually verified: CTA centred, live video fills stage, LIVE top-left).
- **Gates**: `uv run ruff check . && uv run ruff format --check .` clean · `uv run pyright` 0 errors · `uv run pytest` **245 passed, 4 skipped** (realdisplay-only) · `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` **3 passed, 1 skipped** (pre-existing documented host-audio skip). Coverage 95% (floor >90%).
- **Docs**: no README/AGENTS.md changes — README makes no claim about the share-page layout that this fix alters (no stale statement; the one pre-existing warning is a Starlette dependency deprecation, untouched).
- **Next pending task**: `02_size_the_stage_for_real_viewports.md`.
@@ -0,0 +1,34 @@
...................................................................ssss. [ 28%]
........................................................................ [ 57%]
........................................................................ [ 86%]
................................. [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 185 11 94% 108, 113, 117, 182-183, 212-216, 245
src/mirror/config.py 68 5 93% 110-111, 118-120
src/mirror/debug.py 15 10 33% 19-28
src/mirror/protocol.py 59 0 100%
src/mirror/registry.py 191 3 98% 166, 180, 355
------------------------------------------------------
TOTAL 653 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
245 passed, 4 skipped, 1 warning in 66.61s (0:01:06)
All checks passed!
28 files already formatted
0 errors, 0 warnings, 0 informations
@@ -0,0 +1,16 @@
All green. Final state verified — everything complete:
**Task 02 complete.** Report:
**Implemented**
- `app.css` (entirely inside the `/* share */` block): `.stage` floor `min-height: 264px`, ceiling `max-height: min(70vh, 640px)` + `min(70svh, 640px)` (vh fallback), `align-content: safe center`; `.stage .placeholder` escape hatch `max-height: 100%; overflow: auto`; `aspect-ratio: 4/3` at ≤900px, `auto` + 16px padding at ≤480px
- Two fixes beyond the literal task: (1) media queries placed *after* the base rules — before that, equal-specificity source order made them lose; (2) `width: 100%` on `.stage` — a stretched grid item with aspect-ratio gets its *width* recomputed from a max-height-clamped height (stage shrank to 1024px, 534px gap beside the panel); a definite width keeps the full column and the video letterboxes, per the task's ASSUMPTION
- `tests/e2e/test_live_mirror.py::TestStageSizing`: the 6 measured failure viewports (button inside stage + inside viewport, floor ≥263, stage bottom on-screen) + 1920×1080 ceiling (≤641px, Session panel reachable) — in `test_live_mirror.py` since `test_share_layout.py` belongs to task 03
**Before → after** (measured, Chromium over 127.0.0.1): 320×568 stage 180→264, `#start` clipped→263–315 inside; 900×480 542→336 (on-screen); 1280×450 564→315; 1920×1080 964→640, panel bottom 499 < 1080. Test verified **red before** the fix (6/7 viewports + ceiling fail) and green after.
**Gates**: `uv run ruff check . && uv run ruff format --check .` clean · `uv run pyright` 0 errors · `uv run pytest` 252 passed, 4 skipped (realdisplay-gated) · `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` 3 passed, 1 skipped (documented env-gated audio) · `pytest tests/test_app.py -m "not e2e"` 68 passed · coverage 95% (no new src code; phase is CSS-only per 00_phase.md)
**Docs**: README.md — added the sharer-facing narrow-screen line (320px viewport, CTA always on screen). AGENTS.md — no stale statements, no edit.
**Next pending task**: `03_reachable_cta_e2e` — the 12-viewport `elementFromPoint` sweep in `tests/e2e/test_share_layout.py`, idle + live `#stop`.
@@ -0,0 +1,34 @@
........................................................................ [ 28%]
..ssss.................................................................. [ 56%]
........................................................................ [ 84%]
........................................ [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 185 12 94% 108, 113, 116-117, 182-183, 212-216, 245
src/mirror/config.py 68 5 93% 110-111, 118-120
src/mirror/debug.py 15 10 33% 19-28
src/mirror/protocol.py 59 0 100%
src/mirror/registry.py 191 3 98% 166, 180, 355
------------------------------------------------------
TOTAL 653 33 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
252 passed, 4 skipped, 1 warning in 66.55s (0:01:06)
All checks passed!
28 files already formatted
0 errors, 0 warnings, 0 informations
@@ -0,0 +1,17 @@
All green. Report:
**Implemented**
- `tests/e2e/test_share_layout.py`: `#start` hit-test sweep over the 12 task viewports (verbatim `elementFromPoint` probe; asserts `disabled`, `hit_is_button` with `hit` named in the failure message, `in_stage`, `in_viewport`, `no_h_scroll`) plus live-state sweep at 320×568 / 390×844 / 1280×450 asserting `#stop` hit-testable, `#placeholder` hidden, `#livetag` visible inside the stage — reusing `base_url`/`browser`/`start_sharing` from `tests/e2e/conftest.py`.
- Pre-fix verification (temporary revert to HEAD CSS, then restored): **7/12 idle viewports fail** — 320×568 (`hit: H2`), 360×640 (`hit: #name`), 390×844 (`hit: LABEL`), 430×932 (`hit: H2`), 480×800 (`hit: MAIN`), 900×480 (`hit: null`), 1280×450 (`hit: null`); 700×900, 900×700, 1024×600, 1280×800, 1920×1080 pass. Matches the 00_phase diagnosis (480×800's hit landed on `MAIN` this run vs `#share-link` in the original table — same clip, culprit named in output). Please fold these failing widths into the phase commit message.
**Gates (all green)**
- `uv run ruff check . && uv run ruff format --check .` — All checks passed; 29 files formatted
- `uv run pyright` — 0 errors
- `uv run pytest` — 267 passed, 4 skipped (realdisplay gate), 95% coverage
- `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` — 3 passed, 1 skipped (host offers no audio source; pre-existing, documented in the test)
**Docs** — no doc impact: README already documents the 320px phone behaviour (tasks 01–02); this task is test-only, no stale AGENTS.md facts.
**Decisions** — git stash unavailable per harness rules, so pre-fix check used a file swap of `app.css` (restored immediately); live probe parameterised on selector since `#stop` lives in the Session panel, not the stage (`in_stage` asserted for `#start` only).
**Next pending task** — `04_css_guard_and_docs` (`.agents/phases/todo/04_reachable_share_cta/04_css_guard_and_docs.md`).
@@ -0,0 +1,34 @@
........................................................................ [ 26%]
.................ssss................................................... [ 53%]
........................................................................ [ 79%]
....................................................... [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 185 11 94% 108, 113, 117, 182-183, 212-216, 245
src/mirror/config.py 68 5 93% 110-111, 118-120
src/mirror/debug.py 15 10 33% 19-28
src/mirror/protocol.py 59 0 100%
src/mirror/registry.py 191 3 98% 166, 180, 355
------------------------------------------------------
TOTAL 653 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
267 passed, 4 skipped, 1 warning in 69.49s (0:01:09)
All checks passed!
29 files already formatted
0 errors, 0 warnings, 0 informations
@@ -0,0 +1,13 @@
Everything is verified and green. Final report:
**Phase 04, Task 04 — complete. All gates green.**
- **`TestStylesheet::test_the_share_stage_is_one_cell`** — asserts `.stage` declares `grid-template: 1fr / 1fr` (or explicit rows/columns of 1fr) *and* `.stage > *` carries `grid-area: 1 / 1`; docstring records the 300×150 idle-video / 151px-stack / `overflow: hidden` clip reason.
- **`TestStylesheet::test_the_stage_has_a_floor_and_a_ceiling`** — `.stage` must keep `min-height` and `max-height`; any rule combining `overflow: hidden` with `aspect-ratio` needs `min-height` in the same block (the general statement of the bug).
- **`test_hidden_always_means_gone`** — `.placeholder` added to the `[hidden]`-toggled component list, with the inline comment corrected so it stays accurate.
- **Revert verification (by hand)**: deleting the grid lines fails the one-cell guard; deleting `min-height: 264px` fails the floor check; deleting both `max-height` lines fails the ceiling check. CSS restored; all 5 `TestStylesheet` tests green.
- **README.md L10** — added the `/share` alias (served by `app.py`, asserted by `test_pages_render`, previously undocumented). The narrow-screen user-facing line (L39) was already present from an earlier task; full-file consistency pass found no other gaps (ports, config defaults, key tables, kiosk commands all verified against code/docs).
- **AGENTS.md L44** — fixed the one stale fact: "Two pages" → "Three pages and one WebSocket" (the index page exists and is tested). No narrative added; "coverage ~95%" re-measured at 95%, still accurate.
- **PLAN.md** — untouched, but L204's "per-leg `maxBitrate` cap (default 12 Mbit/s)" is contradicted by reality: phase 02 raised the default to 25 Mbit/s (`config.py` L15, README L72). Reported here for the user to amend; I did not edit it.
- **Gates**: `uv run ruff check . && uv run ruff format --check .` clean · `uv run pyright` 0 errors · `uv run pytest` 269 passed / 4 skipped (realdisplay flag-gated) · `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` 3 passed / 1 skipped (host has no audio source — documented platform limitation). Phases 01–03 e2e suites (keyboard, sharpness, audio) all still green.
- **Next pending task**: none — task 04 was the last task of phase 04; the phase is ready for its final validation pass.
@@ -0,0 +1,34 @@
........................................................................ [ 26%]
.................ssss................................................... [ 52%]
........................................................................ [ 79%]
......................................................... [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 185 12 94% 108, 113, 116-117, 182-183, 212-216, 245
src/mirror/config.py 68 5 93% 110-111, 118-120
src/mirror/debug.py 15 10 33% 19-28
src/mirror/protocol.py 59 0 100%
src/mirror/registry.py 191 3 98% 166, 180, 355
------------------------------------------------------
TOTAL 653 33 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
269 passed, 4 skipped, 1 warning in 68.64s (0:01:08)
All checks passed!
29 files already formatted
0 errors, 0 warnings, 0 informations