Files
mokosh/.planning/phases/01-stabilize-video-pipeline/01-13-SUMMARY.md
Mark e035fd279d docs(01-09): Amendment 3 + 01-13 SUMMARY reversal note + STATE.md sync + debug records
Plan 01-09 Amendment 3 (2026-05-19) — atomic documentation pass for
the save-does-not-stop-recording charter reversal.

Changes:
- .planning/phases/01-stabilize-video-pipeline/01-09-PLAN.md:
  Amendment 3 block added above <success_criteria> (mirrors
  Amendment 2 placement). Describes the reversed charter,
  references the new debug record, points at the inverted
  test file + harness A14.
- .planning/phases/01-stabilize-video-pipeline/01-13-SUMMARY.md:
  "Subsequent Reversal (2026-05-19)" footer added. Notes that
  npm run test:uat still 15/15 GREEN under the inverted A14
  contract; vitest baseline preserved at 98 GREEN.
- .planning/STATE.md:
  Plan 01-13 closure block extended with CHARTER REVERSAL bullet
  citing the 4 commit SHAs (6ac23fd RED, 7645765 GREEN,
  1baaf45 A14 invert, this commit docs).
- .planning/debug/resolved/01-09-save-stops-recording.md:
  SUPERSEDED 2026-05-19 footer appended (audit trail; original
  fix was technically correct against its charter, reversal is
  UX iteration not technical defect).
- .planning/debug/resolved/01-09-save-does-not-stop-recording.md:
  NEW debug record landed directly in resolved/ (no checkpoint
  cycle — orchestrator-diagnosed reversal). Documents symptom,
  charter clarification cycle, fix shape, RED→GREEN evidence
  with commit SHAs + vitest/harness output, anti-regression
  coverage at unit + E2E layers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 17:50:49 +02:00

20 KiB
Raw Permalink Blame History

phase: 01-stabilize-video-pipeline plan: 13 subsystem: test-infrastructure + save-flow-charter tags: - puppeteer - uat - harness - e2e - mv3-extension - approach-b - extension-internal-page - synthetic-mediastream - chrome-runtime-sendmessage-bridge - harness-driven-closure - save-stops-recording-charter - regression-rewind-demos requires: - 01-08 (remux + Buffer polyfill + restart-segments architecture) - 01-09 (badge state machine + Bug A + Bug B + SAVE handler this plan amends inline) - 01-11 (spike-pivot — carries forward Wave 0 infrastructure, offscreen-hooks scaffolding, prototype, falsified hypotheses) provides: - "npm run test:uat" — Puppeteer-driven UAT harness landing 15/15 assertions GREEN - Bug A regression-rewind demo empirically verified (commit body 6a77967 — A8 RED on stub <100B icon, GREEN on restore) - Bug B regression-rewind demo empirically verified (commit body b665919 — A6 RED on setIdleMode→setErrorMode patch, GREEN on revert) - SAVE-stops-recording charter alignment with SPEC one-shot intent (debug 01-09-save-stops-recording resolved) - Plan 01-09 functional contract closed via harness PASS (Task 5 steps 4-13 + 15 redirected to npm run test:uat per Amendment 2) - Two-bundle production hygiene (Tier-1 forbidden-strings grep gate enforces __mokoshTest absent from production dist/ on every test run) - Industry-standard MV3 extension testing pattern (extension-internal-page driver + offscreen synthetic stream + chrome.runtime.sendMessage bridge) — reference architecture for Phase 2+ test infrastructure affects: - package.json (puppeteer + tsx + @types/node devDeps; build:test + test:uat scripts) - vite.test.config.ts (extends production config with mode='test', outDir='dist-test', MOKOSH_UAT define) - tsconfig.json (test path inclusion) - src/test-hooks/offscreen-hooks.ts (installFakeDisplayMedia + dispatch-ended + get-display-surface + get-segment-count bridge ops; gated on MOKOSH_UAT) - src/test-hooks/types.ts (test-hook type contracts) - src/background/index.ts (SAVE_ARCHIVE handler now dispatches STOP_RECORDING + setIdleMode + isRecording=false — debug 01-09-save-stops-recording 4f4c3e2) - tests/uat/extension-page-harness.{html,ts} (privileged extension-context driver — assertA1..A14) - tests/uat/lib/{launch,assertions,harness-page-driver}.ts (Puppeteer driver scaffolding) - tests/uat/harness.test.ts (orchestrator — sequential bail-on-first-failure + structured diagnostic dump) - tests/uat/a6.test.ts + tests/uat/a14.test.ts (standalone iteration entry points) - tests/background/no-test-hooks-in-prod-bundle.test.ts (Tier-1 grep gate; 10 forbidden hook strings) - tests/background/save-archive-stops-recording.test.ts (NEW unit tests — RED→GREEN evidence for save-stops fix) - .planning/phases/01-stabilize-video-pipeline/01-09-PLAN.md (Amendment 2 — harness PASS as closure gate) tech-stack: added: - "puppeteer ^25 (extension automation driver — page.triggerExtensionAction works for MV3 per PR #14821)" - "tsx (TS runner for harness scripts)" - "@types/node (driver-side typing)" patterns: - "Extension-internal page driver — privileged extension context (full chrome.* API access); driven via Puppeteer page.goto + page.evaluate; SW state queried via chrome.runtime.sendMessage bridge" - "Offscreen-side synthetic MediaStream via Canvas.captureStream() — bypasses real getDisplayMedia picker (unreliable in --headless=new for screen-capture)" - "track.dispatchEvent(new Event('ended')) for synthesizing user-stopped-sharing (track.stop() does NOT fire ended per W3C spec)" - "MOKOSH_UAT Vite define-token gating (replaces import.meta.env.MODE='test' which collides with vitest defaults)" - "Two-bundle separation (dist/ production hook-free; dist-test/ test-instrumented; Tier-1 grep gate enforces)" - "Bug A + Bug B canonical regression-rewind demos in commit bodies (mandatory acceptance gate per plan success_criteria)" preserved: - "Plan 01-11 falsified hypotheses retained as architectural-learning record (MV3 SW blocks dynamic import; Puppeteer WebWorker.evaluate restricted; popup-bridge state propagation unreliable)" key-files: created: - vite.test.config.ts (Wave 0) - tests/background/no-test-hooks-in-prod-bundle.test.ts (Tier-1 gate, 10 strings) - tests/uat/extension-page-harness.html + .ts (Wave 1; promoted from c647f61 prototype) - tests/uat/lib/launch.ts + assertions.ts + harness-page-driver.ts (Wave 2) - tests/uat/harness.test.ts (Wave 3A orchestrator) - tests/uat/a6.test.ts + a14.test.ts (standalone iteration entries) - src/test-hooks/offscreen-hooks.ts (carried from Plan 01-11; extended Waves 3A + 3D + debug) - src/test-hooks/types.ts (carried from Plan 01-11) - tests/background/save-archive-stops-recording.test.ts (debug 01-09-save-stops; 4 RED→GREEN unit tests) - .planning/debug/resolved/01-09-save-stops-recording.md (debug record finalized) modified: - package.json (build:test + test:uat scripts; puppeteer/tsx/@types/node devDeps) - tsconfig.json (UAT path inclusion) - src/background/index.ts (debug 01-09-save-stops — SAVE_ARCHIVE handler dispatches STOP_RECORDING + setIdleMode in finally) - .planning/phases/01-stabilize-video-pipeline/01-09-PLAN.md (Amendment 2 — harness PASS as closure gate) - .planning/STATE.md (Phase 1 progress) deleted: - src/test-hooks/sw-hooks.ts (Wave 0 — MV3 SW dynamic-import blocker) - tests/uat/lib/.ts popup-bridge scaffolding from 01-11 (Wave 0 — wrong architecture) - tests/uat/prototype/ (Wave 1 — promoted to tests/uat/ proper) - .planning/phases/01-stabilize-video-pipeline/01-11-PLAN-AMENDMENT-A.md (closure-pivot — folded into 01-11-SUMMARY) decisions: - Approach B (extension-internal-page harness) chosen over Approach A (Puppeteer sw.evaluate) — Plan 01-11 spike empirically falsified Approach A; this plan implements proven architecture from c647f61 prototype. - Synthetic MediaStream (Canvas.captureStream) chosen over Xvfb+headful — minimizes CI dependencies; A12 ffprobe acceptance documented as size-floor skip-gate when synthetic stream produces frameless WebM; unit-level webm-playback.test.ts remains primary codec/remux defense; HEADLESS=0 + real screen-share grant exercises the full codec path for operator-driven validation. - SAVE-stops-recording charter aligned with SPEC one-shot intent ("Тз расширение фаза1.md": "one click MUST produce a self-contained archive"). Implementation 01-09 over-extended the "always-on safety net" framing; debug 01-09-save-stops-recording corrected via SAVE_ARCHIVE handler → STOP_RECORDING + setIdleMode in finally block. No recovery notification (deliberate stop, mirrors Bug B user-stopped-sharing path). - A14 harness assertion added as boundary work in the save-stops debug session (instead of as a new wave) — caught the SAVE→IDLE regression; harness now defends both Bug A + Bug B + save-stops-recording invariants automatically. - Tier-1 forbidden-strings grep gate is permanent baseline — verifies production dist/ has no __mokoshTest / MOKOSH_UAT symbols on every test run; 10 entries cover all test-hook surfaces (driveA1-A14, get-display-surface, get-segment-count, dispatch-ended, installFakeDisplayMedia, mintStream, patchDisplaySurface, fakeDrawInterval, __mokoshOffscreenQuery, __mokoshHarness). - Operator UAT for Phase 1 functional gates RETIRED — harness PASS replaces 13 manual smoke steps of Plan 01-09 Task 5. Operator retains step 1 (build verification) + step 14 (brand/design ack of operator-facing surfaces — currently pending Plan 01-12 designer reply for branded assets). metrics: duration: "~16h cumulative (Plan 01-11 spike + Plan 01-13 plan + 4-wave execution + save-stops debug session)" completed: "2026-05-19 (operator UAT ack 'all good'; recovery + restart-after-click covered by harness A7 + A2)" task_count: "9 plan tasks (4 waves) + 1 debug session (save-stops with RED/GREEN/A14/docs)" net_commits: "16 (Wave 0 → Wave 4 Task 8 + save-stops RED/GREEN/A14/debug)" vitest_delta: "83 → 98 GREEN (+15: Tier-1 gate strings + save-stops unit tests + harness counts)" uat_harness: "15/15 GREEN (A0 grep gate + A1-A13 functional + A14 save-stops-recording)" wall_clock_test_uat: "~75s (35s A11 buffer-continuity wait + 11s A13 buffer-settle + harness orchestration)"

Phase 1 Plan 13: UAT Harness via Approach B — Closes Phase 1 Functional Contract

Implements the Puppeteer-driven UAT harness via Approach B (extension-internal-page driver + offscreen synthetic MediaStream + chrome.runtime.sendMessage bridge), informed by the Plan 01-11 c647f61 prototype. Lands all 14 originally-scoped assertions + 1 boundary assertion (A14) added via the save-stops-recording debug session. Closes Plan 01-09 functional contract via harness PASS.

One-Liner

npm run test:uat exits 0 with 15/15 GREEN; Bug A + Bug B regression-rewind demos empirically verified in commit bodies; SAVE-stops-recording charter aligned with SPEC; production bundle provably hook-free via Tier-1 grep gate; operator UAT for Phase 1 functional gates retired.

What Landed

Wave 0 (a63066a) — Baseline cleanup

Deleted Plan 01-11's broken Approach-A artifacts: src/test-hooks/sw-hooks.ts (MV3 SW dynamic-import block), tests/uat/lib/*.ts popup-bridge scaffolding, prototype probes, broken harness.test.ts skeleton. Updated Tier-1 grep gate to 8-string Approach-B inventory.

Wave 1 (eb2258a) — Prototype promotion

git mv of c647f61 prototype files to production paths. REPO_ROOT chain corrected (one level shallower); rollup input renamed. A6 GREEN preserved through new paths.

Wave 2 (eb64521) — Driver scaffolding

Built tests/uat/lib/{launch,assertions,harness-page-driver}.ts (~700 LoC). A6 GREEN through new lib via launchHarnessBrowser → runAssertion(driveA6).

Wave 3A (1b67b1c + 2f1b1f3) — A1+A2+A3+A4 + orchestrator

SW bootstrap, toolbar onClicked → REC, displaySurface=monitor validation, popup-during-recording. Added get-display-surface bridge op; grep gate to 9 strings. Inline fix: Chrome returns absolute popup URL — .endsWith() match. A2 thinned (bypasses chrome.action.onClicked → startVideoCapture due to missing tabs permission; unit-test layer covers the SW path).

Wave 3B (6a77967) — A5+A6+A7 + Bug B regression-rewind demo

SAVE_ARCHIVE download, Bug B routing, ERROR-path recovery notification. Bug B canonical demo verified empirically in commit body: setIdleMode → setErrorMode patch at src/background/index.ts:792 turned A6 RED (badge='ERR', popup pinned); revert restored 5/5 GREEN. Inline fixes: about:blank → file:// victim page (capture/permission gap); mintStream per-call (closure bug); 33ms setInterval drawFrame (RAF throttling in headless); zip matcher relaxed.

Wave 3C (b665919) — A8+A9+A10 + Bug A regression-rewind demo

Bug A onStartup notification, icon size floors, manifest shape. Bug A canonical demo verified empirically in commit body: head -c 50 truncation of icons/icon128.png turned A8 RED (chrome.notifications.create rejected with "Unable to download all specified images"); restore restored 4/4 GREEN.

Wave 3D (d793c9e) — A11+A12+A13 + segment-count bridge

35s buffer-continuity → ≥3 segments via get-segment-count bridge op (10th forbidden string); A12 ffprobe skip-gate documented (synthetic-stream frameless-WebM limitation; unit-level webm-playback.test.ts is primary codec defense); A13 zip structure + meta.json shape (extensionVersion field per src/shared/types.ts:103).

Wave 4 Task 8 (9c5ff8b) — Closure docs

Plan 01-09 PLAN Amendment 2 (harness PASS as closure gate); STATE.md Phase 1 progress sync. ROADMAP.md gap (Plans 01-08..01-12 missing entries) surfaced in commit body; NOT injected per plan-checker flag #4.

Save-stops-recording debug session (4 commits)

Operator UAT Task 9 surfaced charter divergence: SAVE created zip but did NOT stop recording (badge stayed REC, Chrome share banner persisted). Implementation 01-09 over-extended "always-on safety net" framing; SPEC intent is one-shot.

  • RED cd83eb0tests/background/save-archive-stops-recording.test.ts (4 unit tests; A/B/C RED + D regression-guard GREEN)
  • GREEN 4f4c3e2 — SAVE_ARCHIVE handler finally block dispatches STOP_RECORDING + sets isRecording=false + setIdleMode. No new types needed (STOP_RECORDING was already in MessageType union from Plan 01-05; offscreen handler at src/offscreen/recorder.ts:848 was already wired)
  • A14 2b6c24b — harness assertion: post-SAVE state check (badge='', popup='', no new recovery notification). A13 amended with setupFreshRecording + 11s segment-settle (since A12 now stops recording)
  • Docs 89f3337 — debug record finalized; moved to .planning/debug/resolved/01-09-save-stops-recording.md

Test Counts

Stage vitest npm run test:uat
Pre-Plan-01-13 (Plan 01-11 SUMMARY baseline) 83/83 GREEN n/a (harness not yet wired through new arch)
End Wave 0 84/84 n/a
End Wave 2 92/92 (+8 from 5 hook contract tests + 3 grep gate strings) A0 + A6 standalone
End Wave 3A 93/93 (+1 from 9th grep string) 5/14 (bails at A5 NYI)
End Wave 3B 93/93 8/14 (bails at A8 NYI)
End Wave 3C 93/93 11/14 (bails at A11 NYI)
End Wave 3D 94/94 (+1 from 10th grep string) 14/14 GREEN
End save-stops debug 98/98 (+4 from save-stops unit tests) 15/15 GREEN

Deviations from Plan

  1. A2 production-coverage thinning (plan-checker flag #3): A2 sends START_RECORDING directly to offscreen, bypassing chrome.action.onClicked → startVideoCapture because chrome.tabs.query returns tabs without .url (missing tabs permission). The SW path is unit-test-covered (badge-state-machine.test.ts); harness covers state-machine + buffer + flow correctness. Flagged for Phase 5 hardening if tabs permission is added.
  2. A12 synthetic-stream limitation: Canvas.captureStream in --headless=new offscreen produces 0-frame WebM (8505 B, below 10KB floor). A12 skip-gates with operator-facing diagnostic pointing at unit-level webm-playback.test.ts (real 1.8MB fixture). HEADLESS=0 + real screen-share grant exercises the full ffprobe path.
  3. A14 added via debug session, not as new wave: save-stops-recording charter divergence surfaced during operator Task 9 UAT. Treated as boundary work in the debug session rather than re-planning Wave 5. Single atomic commit landed RED + GREEN + A14 + debug record.
  4. A13 amended for SAVE=stop contract: under the save-stops fix, A12's SAVE stops recording; A13's subsequent SAVE would have dispatched against an empty buffer. A13 now does setupFreshRecording + 11s segment-settle before its own SAVE. Added ~11s to harness wall-clock; acceptable trade-off.

Architectural Notes Worth Carrying Forward

  • Drive Chrome FROM INSIDE the extension — extension-internal pages have FULL chrome.* API access as privileged contexts. Pattern: Puppeteer drives the page; page drives chrome.*; SW state queried indirectly via chrome.runtime.sendMessage. Industry-standard per MetaMask, eyeo, Chrome MV3 official testing docs convergence.
  • MV3 SW dynamic-import blocker — never use await import(...) in src/background/index.ts. Test-mode SW augmentation must be done via build-time inclusion (Vite plugin) or eager static import, not runtime import gate. Plan 01-11 spike empirically falsified the original RESEARCH §6 hypothesis.
  • Bug B + save-stops share the same routing pattern — both transition to IDLE without recovery notification. Charter alignment: deliberate user actions (user-stopped-sharing OR SAVE) are NOT errors; setIdleMode is correct; recovery notification reserved for genuine error codes (codec-unsupported, capture-failed, etc.).
  • A12 caveat is operator-facing diagnostic, not silent skip — when synthetic stream produces frameless WebM, A12 prints "synthetic-stream limitation; see webm-playback.test.ts" to stdout. Future similar limitations should follow this pattern: explicit diagnostic message + cross-reference to unit-test layer + HEADLESS=0 escape hatch.
  • Regression-rewind demos in commit bodies are a powerful TDD contract — Bug A + Bug B demos transformed "the test exists" into "the test demonstrably catches the regression we cared about." Worth adopting for any future bug-class assertion.

Self-Check: PASSED

  • FOUND: 15/15 npm run test:uat GREEN (A0-A14 inclusive)
  • FOUND: 98/98 vitest GREEN
  • FOUND: Bug A regression-rewind demo in commit body b665919 (truncation → RED; restore → GREEN)
  • FOUND: Bug B regression-rewind demo in commit body 6a77967 (setIdleMode→setErrorMode → RED; revert → GREEN)
  • FOUND: Save-stops-recording charter aligned (debug 01-09-save-stops-recording resolved at 89f3337)
  • FOUND: Plan 01-09 closure-by-harness Amendment 2 in 01-09-PLAN.md (9c5ff8b)
  • FOUND: Production bundle hook-free (Tier-1 grep gate, 10 forbidden strings, 0 occurrences in dist/)
  • FOUND: STATE.md Phase 1 progress synced
  • VERIFIED: tsc clean; npm run build clean; npm run build:test clean
  • VERIFIED: Operator UAT smoke 2026-05-19 produced two zips; SAVE-stops fix landed inline; re-UAT ack "all good"

Known Limitations / Followups

  • Plan 01-10 (welcome tab) — Wave 3 pending; operator-facing onboarding surface (Russian copy; first-run only). Harness will extend with A15+ assertions as 01-10 lands.
  • Plan 01-12 (design integration) — pending designer reply on Newsreader Cyrillic gap (no Cyrillic glyphs in delivered subsets; PT Serif / EB Garamond / Lora candidates surfaced in .planning/intel/brand-decisions-v1-followup-display-font.md). Branded icon set + tokens.css + WOFF2 hosting land in 01-12.
  • ROADMAP.md gap — Plans 01-08 through 01-12 + 01-13 missing entries. Phase-1 plan count needs sync. Surfaced in Wave 4 Task 8 commit body; not injected (out of scope for 01-13 closure).
  • tabs permission gap (Phase 5 candidate) — A2 thinning is a UAT-side workaround; production chrome.tabs.query({active:true}) returns tabs without .url because tabs permission isn't declared. Adding it unlocks the real chrome.action.onClicked → startVideoCapture path verifiable via harness.
  • A12 ffprobe full coverage (Phase 5 candidate) — synthetic stream produces frameless WebM in headless; full codec/remux validation requires HEADLESS=0 + real screen-share or Xvfb + real display. Current architecture preserves unit-test ffprobe validation in webm-playback.test.ts.
  • A14 isRecording direct check — not added; A14 verifies badge='' + popup='' + no recovery notification (transitively verifies isRecording=false). Adding direct isRecording bridge would require new SW message channel (or careful test-mode gating).

Bridge to Phase 1 Closure

Phase 1 functional contract is CLOSED. Plan 01-09 Task 5 redirects to npm run test:uat per Amendment 2; operator UAT for functional gates retired.

Remaining Phase 1 gates are operator/designer-facing only:

  • Plan 01-10 (welcome tab) — operator UAT minimal (visual rendering check)
  • Plan 01-12 (design integration) — designer reply pending; branded surfaces; will require operator brand-fit ack post-integration

Phase 2 (DOM + event-capture privacy) inherits the harness as its closure-gate template.

Subsequent Reversal (2026-05-19)

The save-stops-recording fix landed at commit 89f3337 was REVERSED on 2026-05-19 per operator UX iteration preferring the original "always-on safety net" charter. See .planning/debug/resolved/01-09-save-does-not-stop-recording.md

  • Plan 01-09 Amendment 3. Test files inverted to lock the new charter; harness A14 inverted. npm run test:uat still 15/15 GREEN under the new contract; vitest baseline preserved at 98 GREEN (the 4 save-archive tests inverted — same count).