Files
mirror/.agents/phases/complete/02_sharp_capture/00_phase.md
T
ducoterra 1da466a412
Build and Push Containers / test (push) Successful in 46s
Build and Push Containers / build-and-push-app (push) Successful in 8s
phase: 02_sharp_capture
**Phase 02 final verification pass — complete, 100% green.**

**Verified (all 4 tasks in `complete/02_sharp_capture/`):**
- Task 1: `pickSurface()` requests `screen × devicePixelRatio` (capped 3840×2160) instead of window size
- Task 2: `degradationPreference: "maintain-resolution"` + per-leg `maxBitrate`, re-applied after `setLocalDescription` *and* on `connected` (both call sites asserted in `test_app.py`)
- Task 3: default cap raised to 25 Mbit/s — `config.py` constant, `net.js` offline fallback, `.env.example`, README, and the drift-guard test all agree
- Task 4: realdisplay sharpness floor (viewer decoded width ≥ 1152px = 90% of Xvfb 1280px) in `test_sharp_picture_reaches_the_wall`

**Gates (all green):**
- `uv run ruff check . && uv run ruff format --check .` → clean
- `uv run pyright` → 0 errors
- `uv run pytest` → 231 passed, 3 skipped (realdisplay), coverage 95%
- `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` → 3 passed on a real Xvfb + headed Chromium

**Completion criteria:** (1) 90% width floor asserted on viewer's decoded pixels — PASS · (2) `/api/config` `maxBitrateBps: 25000000` default, net.js fallback agrees — PASS · (3) `#stat-resolution` matches viewer-decoded size incl. documented window downscale (1100×620, 1280×720 fake-device tests) — PASS · (4) both suites green, aspect-ratio guard (`1280/800` ± 0.06 + `≠ 1280×720`) still distinguishes real from fake capture — PASS · (5) README Configuration documents `25000000` — PASS; `PLAN.md` §6 "default 12 Mbit/s" left untouched as instructed — **flag for the user to amend**.

**Docs:** README — fixed suite timing `~20s → ~50s` (measured 49s); all other claims verified (CLI flags, dev extra, config table, kiosk key table vs. `watch.js`/`share.js` routers, kiosk commands vs. `docs/kiosk-silverblue.md`). AGENTS.md — same stale timing fixed, nothing else stale (all referenced test names/files exist).

**Notable:** `docs/kiosk-silverblue.md` + its scripts use `mirror.reeselink.com` while README/SECURITY.md/Containerfile use `mirror.reeseapps.com` — a pre-existing cross-file mismatch I could not resolve from the repo (both in same-day commits; live-infra fact). Left untouched; user should confirm the live kiosk URL. No live changes made.

**Next pending phase:** `03_audio_share`.
2026-11-01 21:41:28 -05:00

3.9 KiB
Raw Blame History

Phase 02 — A sharp picture on the wall

Source: TODO.md L4 — "The screen sharing should be sharper, it's a little fuzzy when sharing my display." Story: n/a Context: .agents/PLAN.md §5 (the browser-harness finding "Chromium scales a monitor capture to the capturing window", and the rule that media is asserted on the viewer's <video>), §6 ("Mesh upload = N × bitrate per sharer … per-leg maxBitrate cap"); src/mirror/static/share.js (pickSurface, addPeer), src/mirror/config.py, src/mirror/static/net.js

Objective

Text on a shared screen is legible on a full-HD wall. Three things make today's picture fuzzy, and each gets its own task:

  1. The capture is smaller than the screen. pickSurface() requests { frameRate: { ideal: 30, max: 60 } } and no dimensions, so Chromium captures the monitor scaled down to the size of the sharing window (PLAN §5 records exactly this: 1280×800 → 1200×750). A window that is half the screen ships a quarter of the pixels, stretched back up on the wall.
  2. The encoder is allowed to throw resolution away. Nothing sets degradationPreference, so the default "balanced" trades pixels for frame rate the moment the LAN hiccups — the opposite of what a screen of text needs.
  3. 12 Mbit/s is not enough for the pixels we now send. A 1440p text-heavy surface at 12 Mbit/s is where macroblocking comes from.

Dependencies

  • 01_keyboard_kiosk_control (todo) — no functional dependency, but it runs first and its E2E suites (tests/e2e/test_keyboard_wall.py) must still pass here; this phase changes the same two files (share.js, watch.js untouched).

Tasks

  1. 01_capture_resolution_constraints.md — ask the picker for the screen's real pixels (screen × devicePixelRatio) instead of the window's.
  2. 02_sender_fidelity.md — per-leg RTCRtpSender parameters: maintain-resolution, re-applied after every renegotiation.
  3. 03_bitrate_default_and_surface.md — raise the per-leg cap default to 25 Mbit/s and keep server, client fallback, .env.example and README honest about it.
  4. 04_sharpness_assertion_e2e.md — a real-desktop assertion on the viewer's decoded size, so "sharper" stops being a feeling.

Testing & Quality

  • The media rule from AGENTS.md §5 / PLAN §5 governs this whole phase: assert on the viewer's <video> (videoWidth, videoHeight, framesDecoded, inbound bitrate), never on the sharer's preview.
  • The realdisplay suite is where sharpness is actually provable (MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay) because it captures a real desktop with a real encoder; the fake-device suite proves the constraints do not break the deterministic 1280×720 path.
  • Python side (config.py default) is unit + API tested: tests/test_config.py, tests/test_app.py::TestJsonApi.
  • No new external asset, no new dependency, no framework (PLAN A9/A10).
  • Coverage >90% on new/modified code; full gate per task: uv run ruff check . && uv run ruff format --check . && uv run pyright && uv run pytest.

Completion Criteria

  • MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay asserts the viewer's decoded width is ≥ 90% of the Xvfb desktop width (today it only asserts the aspect ratio).
  • /api/config reports maxBitrateBps: 25000000 by default and net.js's offline fallback agrees.
  • The share page's own stats (#stat-resolution) match what the viewer decodes, within the documented downscale the browser applies.
  • uv run pytest and MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay both green; test_…aspect ratio… guard still distinguishes real capture from the fake device.
  • README "Configuration" documents the new default; the commit message flags that PLAN.md §6's "default 12 Mbit/s" figure is now stale for the user to amend (this phase must not edit PLAN.md itself).