From ba5474c54fe41bebc83542f18f4014e44a69ba90 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 18 May 2026 14:02:38 +0200 Subject: [PATCH] =?UTF-8?q?docs(01-11):=20close=20as=20spike-pivot=20?= =?UTF-8?q?=E2=80=94=20SUMMARY=20landed,=20AMENDMENT-A=20deleted,=20pivots?= =?UTF-8?q?=20to=2001-13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes Plan 01-11 honestly per GSD spike-pivot pattern. Original Approach A (Puppeteer sw.evaluate per RESEARCH §1+6) empirically falsified across Wave 3 execution + feasibility research. Approach B (extension-internal-page harness + offscreen synthetic stream) proven via c647f61 prototype; full implementation moves to Plan 01-13. What this commit does: - ADDS 01-11-SUMMARY.md (spike-then-pivot framing per GSD artifact- types.md PLAN→SUMMARY lifecycle; captures retained infrastructure, falsified hypotheses, working prototype, bridge to 01-13) - REVERTS frontmatter amendment block in 01-11-PLAN.md; replaces with closed_as/pivoted_in/closure_note pointing at SUMMARY + 01-13 - DELETES 01-11-PLAN-AMENDMENT-A.md (improvised artifact type — not recognized in GSD artifact-types.md; content folded into SUMMARY) Lesson for orchestrator (captured in SUMMARY §Architectural Notes): when a plan attempts an approach that proves infeasible, the right move is honest SUMMARY + new plan, NOT in-place rewrite + AMENDMENT artifact. The project's own pattern (01-08, 01-09, 01-10, 01-11 added mid-phase as new work surfaced) confirms add-new-plan-when- scope-shifts is the established pattern. Plan 01-09 closure via harness PASS NOT achieved by 01-11; still requires operator UAT pending Plan 01-13 landing. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../01-11-PLAN-AMENDMENT-A.md | 140 ------------- .../01-stabilize-video-pipeline/01-11-PLAN.md | 7 +- .../01-11-SUMMARY.md | 194 ++++++++++++++++++ 3 files changed, 198 insertions(+), 143 deletions(-) delete mode 100644 .planning/phases/01-stabilize-video-pipeline/01-11-PLAN-AMENDMENT-A.md create mode 100644 .planning/phases/01-stabilize-video-pipeline/01-11-SUMMARY.md diff --git a/.planning/phases/01-stabilize-video-pipeline/01-11-PLAN-AMENDMENT-A.md b/.planning/phases/01-stabilize-video-pipeline/01-11-PLAN-AMENDMENT-A.md deleted file mode 100644 index ce3f57d..0000000 --- a/.planning/phases/01-stabilize-video-pipeline/01-11-PLAN-AMENDMENT-A.md +++ /dev/null @@ -1,140 +0,0 @@ -# Plan 01-11 — Amendment A (2026-05-18) - -Architectural pivot triggered by Feasibility Research Prototype (commit `c647f61`). -Supersedes Wave 1 Task 2 + Wave 2 Task 3 + Wave 3 method guidance in -`01-11-PLAN.md` (commit 66e6c4a). Other waves unchanged. - -## Why this amendment exists - -The original Plan 01-11 (66e6c4a) + RESEARCH (969afba) specified driving -assertions via `Puppeteer sw.evaluate(chrome.action.getBadgeText)` against -the MV3 service worker. **This architecture is empirically infeasible** for -two reasons the original research didn't probe: - -1. **MV3 service workers BLOCK dynamic import.** `await import('../test-hooks/sw-hooks')` - in `src/background/index.ts` silently kills the SW — the chunk loads, the - await never resolves, no listeners register. Verified by - feasibility-research subagent against Chrome 148. Citations: - [Chromium `es_modules.md`](https://chromium.googlesource.com/chromium/src/+/HEAD/content/browser/service_worker/es_modules.md) - + [w3c/webextensions#212](https://github.com/w3c/webextensions/issues/212). - The Wave 1 (cb1a729) SW-side hooks are therefore **fundamentally broken - in test builds**. Production unaffected (gated by `__MOKOSH_UAT__`). - -2. **Puppeteer's `WebWorker.evaluate` against MV3 SW only exposes `chrome.{loadTimes, csi}`** — - not the extension chrome.* API. The CDP-level abstraction treats MV3 SW - as a generic service worker, dropping extension privileges in the isolated - world used for `evaluate`. Plus [Puppeteer #9995](https://github.com/puppeteer/puppeteer/issues/9995) - — calling `target.worker()` can put SW into a dead state. - -## What replaces it (Verdict-A architecture) - -Drive assertions FROM INSIDE the extension via an internal harness page — -the pattern MetaMask, eyeo, and Chrome's official MV3 testing docs converge on: - -``` -chrome-extension:///tests/uat/extension-page-harness.html - ↓ Puppeteer page.goto() drives this PAGE - ↓ page calls chrome.* directly (privileged extension page) - ↓ for SW state: chrome.runtime.sendMessage round-trip -SW background context (production code, untouched) - ↓ response -harness page → reads result, asserts, exposes verdict via window.__mokoshHarness - ↓ Puppeteer reads via page.evaluate -``` - -The harness page is an extension page — privileged context with FULL chrome.* -API access. Puppeteer drives it like any normal page. - -## Section overrides - -### Wave 1 Task 2 (test hooks) — REPLACED - -**OLD:** gated SW + offscreen hooks at `src/test-hooks/`; production bundle hook-free. - -**NEW:** gated OFFSCREEN-ONLY hooks at `src/test-hooks/offscreen-hooks.ts`. -**DO NOT add SW-side hooks** (dynamic import blocked in MV3 SW). SW -observability moves to extension-internal harness page using only production -`chrome.*` APIs. - -**Action items:** -- DELETE the `await import('../test-hooks/sw-hooks')` block from - `src/background/index.ts` introduced by cb1a729 -- DELETE `src/test-hooks/sw-hooks.ts` entirely if it exists -- KEEP `src/test-hooks/offscreen-hooks.ts` and EXTEND with `installFakeDisplayMedia()` - (eager-init pattern at module load, gated by `__MOKOSH_UAT__`) -- Production grep gate (A0) still asserts `__mokoshTest` absent from prod `dist/` - -### Wave 2 Task 3 (harness scaffolding) — REPLACED - -**OLD:** `tests/uat/lib/*` + `harness.test.ts` skeleton; popup-bridge architecture. - -**NEW:** Extension-internal harness page architecture: -- `tests/uat/extension-page-harness.html` — page skeleton (loaded by Puppeteer) -- `tests/uat/extension-page-harness.ts` — controller exposing `window.__mokoshHarness.assertN()` per assertion -- `vite.test.config.ts` declares the harness page as a rollup input (so it builds into `dist-test/tests/uat/`) -- `tests/uat/lib/driver.ts` — Puppeteer driver utilities (launches Chrome, loads extension, opens harness page, runs assertions) -- `tests/uat/harness.test.ts` — top-level test runner invoking each `assertN()` - -**Reference files (working pattern from c647f61 prototype):** -- `tests/uat/prototype/extension-page-harness.ts` -- `tests/uat/prototype/extension-page-harness.html` -- `tests/uat/prototype/a6.test.ts` -- `src/test-hooks/offscreen-hooks.ts` (synthetic stream impl) -- `vite.test.config.ts` (rollup input wiring) - -Executor extends prototype files; promotes from `tests/uat/prototype/` to `tests/uat/` proper. - -### Wave 3 Tasks 4-7 (assertions) — METHOD UPDATED - -Each assertion implemented as `__mokoshHarness.assertN()` on the page, not via -Puppeteer `sw.evaluate`. Specific guidance per assertion: - -- **A0 (hook leak grep)** — keep existing Tier-1 vitest unit-test gate (no change) -- **A1 (SW bootstrap)** — harness page reads `chrome.action.getBadgeText({})` + `getPopup({})` after extension load -- **A2 (toolbar click → REC)** — `page.triggerExtensionAction(ext)` (Puppeteer 25 native); harness page polls badge state -- **A3 (displaySurface=monitor)** — offscreen hook patches `getDisplayMedia` to return synthetic stream with `getSettings().displaySurface='monitor'`; harness asserts via offscreen-bridge query -- **A4 (popup during recording)** — `page.bringToFront` on popup target after `triggerExtensionAction` during REC state -- **A5 (SAVE_ARCHIVE → download)** — harness sends SAVE_ARCHIVE via `chrome.runtime.sendMessage`; Puppeteer polls headless Downloads dir -- **A6 (Bug B regression catch)** — ✅ PROVEN by prototype (5/5 GREEN). Extend pattern verbatim. -- **A7 (genuine error → ERR + recovery notif)** — harness sends synthetic `RECORDING_ERROR{error:'codec-unsupported'}` via `chrome.runtime.sendMessage`; asserts badge='ERR' + `chrome.notifications.getAll()` shows recovery notif -- **A8 (Bug A onStartup notification)** — harness manually invokes onStartup listener via offscreen-side bridge → trigger SW listener call via test message; asserts `chrome.notifications.getAll()` succeeds with valid `iconUrl` -- **A9 (icon file sizes)** — harness fetches `chrome.runtime.getURL('icons/icon{16,48,128}.png')`, checks Content-Length ≥ floors -- **A10 (manifest shape)** — harness reads `chrome.runtime.getManifest()` directly -- **A11 (35s buffer → ≥3 segments)** — offscreen hook records 35s via synthetic stream; harness queries segment count via offscreen-bridge -- **A12 (ffprobe-clean WebM)** — harness produces zip via SAVE_ARCHIVE; Puppeteer extracts; ffprobe via Bash from driver -- **A13 (zip structure)** — same as A12; harness/driver inspects zip contents - -### Additional gotchas (researcher empirically discovered) - -- **`tabs` permission missing** → `chrome.tabs.query({active:true})` returns tabs without `.url` → production `startVideoCapture` throws "No active tab found". **Workaround:** bypass `startVideoCapture`; send `START_RECORDING` directly to offscreen via `chrome.runtime.sendMessage`. (For production, this is a separate Plan 5 hardening item — manifest may want `tabs` permission added.) -- **Puppeteer #9995**: `target.worker()` can put SW into dead state. **Avoid `worker()` entirely** — page-driven architecture doesn't need it. -- **vite `modulePreload.polyfill`**: red herring. Harmless in SW (only fires DOM code if `n.length > 0`). Left enabled in prototype config. - -## Effort estimate (revised) - -| Wave | Status from f44ca3a | Action | Estimate | -|---|---|---|---| -| 0 (T1) | COMPLETE (96fa8e8, 0cd50fd) | Keep | 0 h | -| 1 (T2) | PARTIAL (cb1a729) — SW hooks broken | DELETE SW-side; keep offscreen-side; extend with `installFakeDisplayMedia` | 30 min | -| 2 (T3) | PARTIAL (dbd977c) — popup-bridge wrong arch | REPLACE with extension-page architecture per prototype | 1-2 h | -| 3 (T4-T7) | NOT STARTED | Extend prototype to 14 assertions | 4-6 h | -| 4 (T8-T9) | NOT STARTED | Closure ceremony + operator brand checkpoint | 1 h | - -**Total remaining:** ~7-10 hours subagent budget. - -## Acceptance unchanged - -Per original PLAN.md §"Success criteria": -- All 14 assertions GREEN (A0 already GREEN from Wave 0) -- Production bundle hook-free (Tier-1 grep gate enforces) -- Existing vitest baseline preserved (89/89 at amendment time) -- Plan 01-09 closure via harness PASS (Task 9 `checkpoint:human-verify`) - -## Sources - -- [eyeo's MV3 SW testing journey](https://developer.chrome.com/blog/eyeos-journey-to-testing-mv3-service%20worker-suspension) -- [Chrome MV3 E2E testing official guide](https://developer.chrome.com/docs/extensions/mv3/end-to-end-testing/) -- [Chromium ES Modules in Service Workers](https://chromium.googlesource.com/chromium/src/+/HEAD/content/browser/service_worker/es_modules.md) -- [w3c/webextensions#212 — dynamic import in background SW](https://github.com/w3c/webextensions/issues/212) -- [Puppeteer #9995](https://github.com/puppeteer/puppeteer/issues/9995) -- [MetaMask extension driver.js](https://github.com/MetaMask/metamask-extension/blob/develop/test/e2e/webdriver/driver.js) diff --git a/.planning/phases/01-stabilize-video-pipeline/01-11-PLAN.md b/.planning/phases/01-stabilize-video-pipeline/01-11-PLAN.md index 0c40191..1aca92d 100644 --- a/.planning/phases/01-stabilize-video-pipeline/01-11-PLAN.md +++ b/.planning/phases/01-stabilize-video-pipeline/01-11-PLAN.md @@ -1,7 +1,8 @@ --- -amended_at: "2026-05-18" -amendment: "A" -amendment_summary: "Wave 1 T2 + Wave 2 T3 + Wave 3 method guidance superseded. MV3 SW blocks dynamic import (verified empirically); SW-side test hooks DROPPED. Replaced with extension-internal harness page architecture (proven by c647f61 prototype). See 01-11-PLAN-AMENDMENT-A.md." +closed_as: "spike-pivot" +closed_at: "2026-05-18" +pivoted_in: "01-13" +closure_note: "Approach A (Puppeteer sw.evaluate per RESEARCH §1+6) empirically falsified; pivoted to Approach B (extension-internal-page harness, proven by c647f61 prototype) in Plan 01-13. See 01-11-SUMMARY.md for full pivot rationale + retained infrastructure." phase: 01-stabilize-video-pipeline plan: 11 type: tdd diff --git a/.planning/phases/01-stabilize-video-pipeline/01-11-SUMMARY.md b/.planning/phases/01-stabilize-video-pipeline/01-11-SUMMARY.md new file mode 100644 index 0000000..06c5d4b --- /dev/null +++ b/.planning/phases/01-stabilize-video-pipeline/01-11-SUMMARY.md @@ -0,0 +1,194 @@ +--- +phase: 01-stabilize-video-pipeline +plan: 11 +subsystem: test-infrastructure +tags: + - puppeteer + - uat + - harness + - e2e + - mv3-extension + - spike-then-pivot + - architectural-falsification + - test-bundle-separation +requires: + - 01-08 (Plan 01-08 SUMMARY + Tier-1 SW-bundle-import gate GREEN) + - 01-09 (Bug A + Bug B fixes landed; the regression-catch targets) +provides: + - test-build infrastructure (vite.test.config.ts + dist-test/ + npm scripts build:test/test:uat) + - Tier-1 hook-leak grep gate (no-test-hooks-in-prod-bundle.test.ts enforces __mokoshTest absent from production dist/) + - empirical falsification of original RESEARCH §6 (MV3 SW blocks dynamic import — verified) + - working prototype validating Approach B architecture (commit c647f61; A6 5/5 GREEN; Bug-B regression rewind verified) + - offscreen-side test hooks scaffolding (src/test-hooks/offscreen-hooks.ts with installFakeDisplayMedia) + - input for Plan 01-13 (carries the proven architecture forward) +affects: + - package.json (puppeteer + tsx + @types/node devDeps) + - vite.test.config.ts (NEW — extends production config with mode='test', outDir='dist-test') + - tsconfig.json (test path inclusion) + - src/test-hooks/sw-hooks.ts (NEW — BROKEN architecturally; deleted in 01-13) + - src/test-hooks/offscreen-hooks.ts (NEW — works; carried to 01-13) + - src/test-hooks/types.ts (NEW — type contracts; carried to 01-13) + - src/background/index.ts (Wave 1 added dynamic import — silently kills SW per MV3 limit; reverted in 01-13) + - src/offscreen/recorder.ts (Wave 1 added dynamic import — works because offscreen IS a DOM document) + - tests/uat/lib/*.ts (Wave 2 popup-bridge scaffolding — wrong architecture; replaced in 01-13) + - tests/uat/harness.test.ts (Wave 2 skeleton with A0 GREEN — rewired in 01-13) + - tests/uat/prototype/*.ts (NEW — proves Approach B; promoted to production paths in 01-13) + - tests/background/no-test-hooks-in-prod-bundle.test.ts (NEW Tier-1 gate; permanent) +tech-stack: + added: + - puppeteer 25.x (extension automation driver) + - tsx (TS runner for harness scripts) + - "@types/node" + patterns: + - Two-bundle separation (production dist/ vs test dist-test/ via Vite mode flag) + - __MOKOSH_UAT__ Vite define-token gating (replaces import.meta.env.MODE='test' which collides with vitest) + - Tier-1 grep gate as production-bundle hygiene contract + - Synthetic MediaStream via Canvas.captureStream() (bypasses getDisplayMedia picker in test) + - track.dispatchEvent(new Event('ended')) for synthesizing user-stopped-sharing (track.stop() does NOT fire ended per W3C spec) + falsified: + - "MV3 SW supports dynamic import gated by import.meta.env.MODE" — empirically wrong; await import() in SW never resolves; SW silently dies + - "Puppeteer sw.evaluate exposes extension chrome.*" — empirically wrong; only chrome.{loadTimes,csi} exposed (CDP treats MV3 SW as generic worker) + - "Popup-bridge architecture sufficient for SW state queries" — empirically wrong; setPopup state changes don't propagate to popup chrome.action.getPopup queries reliably + - "Headless --auto-select-desktop-capture-source triggers getDisplayMedia resolution" — empirically wrong in --headless=new +key-files: + created: + - vite.test.config.ts + - src/test-hooks/sw-hooks.ts (deleted in 01-13) + - src/test-hooks/offscreen-hooks.ts (carried to 01-13) + - src/test-hooks/types.ts (carried to 01-13) + - tests/uat/lib/launch.ts + extension.ts + sw.ts + offscreen.ts + assertions.ts + zip.ts (Wave 2; popup-bridge — replaced in 01-13) + - tests/uat/harness.test.ts (Wave 2 skeleton; rewired in 01-13) + - tests/uat/prototype/extension-page-harness.html + .ts (PROVEN ARCHITECTURE; promoted in 01-13) + - tests/uat/prototype/a6.test.ts (Puppeteer driver template; promoted in 01-13) + - tests/background/no-test-hooks-in-prod-bundle.test.ts (Tier-1 grep gate; permanent) + modified: + - package.json (puppeteer + tsx + @types/node devDeps; build:test + test:uat scripts) + - tsconfig.json (test paths) + - src/background/index.ts (Wave 1 dynamic import — reverted in 01-13) + - src/offscreen/recorder.ts (Wave 1 dynamic import — preserved in 01-13) +decisions: + - Approach A (Puppeteer sw.evaluate per RESEARCH §1+6) attempted across Waves 1-3; empirically falsified by both Wave 3 execution (commit f44ca3a) + dedicated feasibility research subagent (Verdict-A research, prototype c647f61). + - Approach B (extension-internal-page harness + offscreen-side synthetic MediaStream + chrome.runtime.sendMessage bridge) validated via prototype; industry-standard pattern per MetaMask, eyeo, Chrome MV3 official testing docs convergence. + - Wave 0 infrastructure retained as foundation for 01-13. Waves 1-2 partially retained — offscreen-hooks + types + harness.test.ts skeleton + Tier-1 gate kept; sw-hooks + popup-bridge lib deleted/replaced. + - Spike-then-pivot framing: 01-11 reads retroactively as a SPIKE that delivered useful infrastructure + falsified the original architectural hypothesis + produced a working prototype. Full 14-assertion harness implementation defers to Plan 01-13. + - __MOKOSH_UAT__ Vite define-token chosen over import.meta.env.MODE='test' (per original RESEARCH §6 sketch) because vitest defaults MODE='test' and would have activated hooks during unit tests, clobbering 8 existing chrome.* vi.fn() mocks. + - Tier-1 grep gate (no-test-hooks-in-prod-bundle.test.ts) is permanent baseline — verifies __mokoshTest absent from production dist/ on every test run. +metrics: + duration: "~8h cumulative (planning + research + 2 executor attempts + feasibility research + prototype + pivot)" + completed: "2026-05-18 (closed as spike-then-pivot; full harness deferred to 01-13)" + task_count: "Wave 0 (T1) complete; Waves 1-4 partial or not delivered as originally planned" + files_modified: "18 created + 4 modified (some files will be deleted in 01-13 baseline)" + net_loc: "~1500+ (test infrastructure + prototype + Tier-1 gate)" + pivot: "yes — full harness implementation deferred to Plan 01-13" +--- + +# Phase 1 Plan 11: UAT Harness — Approach A Spike (Pivoted to 01-13) + +Attempted to retire operator UAT for functional gates via Puppeteer-driven MV3 extension automation. Approach A (Puppeteer `sw.evaluate` per original RESEARCH §1+6) empirically falsified across Wave 3 + dedicated feasibility research. Approach B (extension-internal-page harness + offscreen-side synthetic stream) validated via working prototype. **Full harness implementation deferred to Plan 01-13.** + +## One-Liner + +Approach A spike: shipped two-bundle infrastructure + Tier-1 grep gate + offscreen-hooks scaffolding + empirical falsification of "MV3 SW supports dynamic import" hypothesis + working prototype of Approach B (extension-internal-page harness). Full 14-assertion implementation pivots to Plan 01-13. + +## What Landed + +### Infrastructure (Wave 0, commit 96fa8e8) — Retained for 01-13 + +- `vite.test.config.ts` — extends production config with `mode='test'` + `outDir='dist-test'` +- `package.json` scripts `build:test` + `test:uat` (orchestrate build + harness) +- `tests/background/no-test-hooks-in-prod-bundle.test.ts` — Tier-1 grep gate (A0 assertion); permanent in test suite +- Bonus: `0cd50fd` — imported Bug B recovery-flow debug record from prior session (cleanup that was overdue) + +### Hooks scaffolding (Wave 1, commit cb1a729) — Partially Retained + +| File | Status | Reason | +|---|---|---| +| `src/test-hooks/offscreen-hooks.ts` | RETAINED + EXTENDED in 01-13 | Offscreen IS a DOM document; dynamic import works there | +| `src/test-hooks/types.ts` | RETAINED in 01-13 | Type contracts valid regardless of arch | +| `src/test-hooks/sw-hooks.ts` | DELETED in 01-13 | MV3 SW blocks dynamic import — silently kills SW | +| `src/background/index.ts` Wave 1 import block | REVERTED in 01-13 | Same MV3 SW limit | + +### Harness scaffolding (Wave 2, commit dbd977c) — Mostly Replaced + +| File | Status | Reason | +|---|---|---| +| `tests/uat/lib/*.ts` (popup-bridge) | DELETED/REPLACED in 01-13 | Wrong architecture; setPopup state changes don't propagate reliably | +| `tests/uat/harness.test.ts` skeleton | REWIRED in 01-13 | Top-level structure stays; assertions point at new extension-page architecture | +| A0 Tier-1 gate (GREEN here) | RETAINED — permanent | Grep gate is architecture-agnostic | + +### Working prototype (commit c647f61) — PROVES Approach B; Promoted in 01-13 + +- `tests/uat/prototype/extension-page-harness.html + .ts` — extension-internal page (privileged chrome.* access) drives assertions via direct chrome.* calls + `chrome.runtime.sendMessage` bridge +- `tests/uat/prototype/a6.test.ts` — Puppeteer driver template +- **A6 assertion (Bug B regression catch): 5/5 GREEN** +- **Bug-B regression rewind verified**: `if (false)` patch on `src/background/index.ts:759` turns 4/5 RED; restoring turns 5/5 GREEN +- Total prototype runtime: ~7s end-to-end + +### Empirical Falsifications (Research Output) + +These were the original RESEARCH §1 + §6 hypotheses. All falsified by execution + feasibility research: + +1. **MV3 SW blocks dynamic import.** `await import(...)` in `src/background/index.ts` silently never resolves; SW dies. Verified across two independent investigations. Cited: [Chromium `es_modules.md`](https://chromium.googlesource.com/chromium/src/+/HEAD/content/browser/service_worker/es_modules.md) + [w3c/webextensions#212](https://github.com/w3c/webextensions/issues/212). +2. **Puppeteer `WebWorker.evaluate` against MV3 SW** only exposes `chrome.{loadTimes, csi}` — NOT extension chrome.* API. CDP-level abstraction treats MV3 SW as a generic worker. +3. **Popup-bridge `chrome.action.setPopup` state changes** don't propagate to popup-side `chrome.action.getPopup` queries within waitable timeframes. +4. **Headless `--auto-select-desktop-capture-source`** doesn't trigger getDisplayMedia resolution in `--headless=new` for screen-capture sources. + +## Test Counts + +- Before plan: 83/83 vitest GREEN +- After Wave 0 (96fa8e8): 84/84 GREEN (+1 Tier-1 grep gate) +- After Wave 1 (cb1a729): 89/89 GREEN (+5 hook contract tests) +- After Wave 2 (dbd977c): 89/89 GREEN (popup-bridge tests stubbed RED-by-design per plan; A0 GREEN) +- After prototype (c647f61): 89/89 vitest unchanged; prototype runs out-of-band via `npx tsx tests/uat/prototype/a6.test.ts` (5/5 GREEN, ~7s) +- End of plan: **89/89 vitest GREEN; production bundle hook-free (Tier-1 gate enforces)** + +## Deviations from Plan + +The plan as written assumed Approach A architecture would work. Across Wave 3 attempted execution + dedicated feasibility research, Approach A architecturally infeasible. Pivoted to Approach B per prototype. + +Plan 01-09 closure via harness PASS (the original Task 5 redirect target) NOT achieved — Plan 01-09 still requires operator UAT pending Plan 01-13 landing. + +## Architectural Notes Worth Carrying Forward + +- **Drive Chrome FROM INSIDE the extension, not FROM OUTSIDE.** MetaMask/eyeo/Chrome-docs convergence: extension-internal pages have FULL chrome.* API access as privileged contexts. Puppeteer drives the page; page drives chrome.*. SW state queried indirectly via `chrome.runtime.sendMessage`. +- **`track.dispatchEvent(new Event('ended'))` for synthesizing user-stopped-sharing**, NOT `track.stop()` (which does NOT fire `ended` per W3C spec + empirical Chrome 148 verification). +- **`__MOKOSH_UAT__` Vite define-token** over `import.meta.env.MODE='test'` — vitest defaults `MODE='test'`, which would activate hooks during unit tests and clobber chrome.* mocks. +- **MV3 SW blocks dynamic import** — 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. +- **Pages CAN call `chrome.offscreen.createDocument`** — confirmed empirically. Chrome docs don't say this explicitly. Architectural unlock for the prototype. +- **Spike-then-pivot framing is a legitimate plan outcome** — when a plan attempts an approach that proves infeasible, the right move is honest SUMMARY + new plan, not in-place rewrite + AMENDMENT artifact. (Lesson for the orchestrator.) + +## Self-Check: PARTIAL — Closed as Spike-Pivot + +- FOUND: `vite.test.config.ts` (Wave 0) +- FOUND: `tests/background/no-test-hooks-in-prod-bundle.test.ts` (Tier-1 gate, A0 GREEN) +- FOUND: `src/test-hooks/offscreen-hooks.ts` (carries to 01-13) +- FOUND: `src/test-hooks/types.ts` (carries to 01-13) +- FOUND: `tests/uat/prototype/extension-page-harness.{html,ts}` +- FOUND: `tests/uat/prototype/a6.test.ts` (5/5 GREEN) +- FOUND: `tests/uat/lib/*.ts` (wrong arch — to delete/replace in 01-13) +- FOUND: `src/test-hooks/sw-hooks.ts` (broken — to delete in 01-13) +- FOUND: 96fa8e8 (Wave 0 infrastructure) +- FOUND: 0cd50fd (bonus debug record import) +- FOUND: cb1a729 (Wave 1 — partial retained; sw-hooks broken) +- FOUND: dbd977c (Wave 2 — popup-bridge wrong arch) +- FOUND: f44ca3a (Wave 3 partial — abandoned) +- FOUND: c647f61 (prototype — proves Approach B; PROVENANCE for 01-13) +- NOT-DELIVERED: A1-A13 functional assertions (deferred to 01-13) +- NOT-DELIVERED: Plan 01-09 closure via harness PASS (still requires operator UAT pending 01-13) + +## Known Stubs (To Be Resolved in 01-13) + +- `tests/uat/lib/*.ts` — popup-bridge architecture stubs from Wave 2 dbd977c. Wrong architecture; 01-13 deletes or rewrites. +- `src/test-hooks/sw-hooks.ts` + dynamic-import block in `src/background/index.ts` — BROKEN (MV3 SW limit). 01-13 deletes both. +- `tests/uat/prototype/*` — proof-of-concept code, not production-quality. 01-13 promotes to production paths under `tests/uat/` proper. + +## Bridge to Plan 01-13 + +Plan 01-13 implements full UAT harness via Approach B (extension-internal-page architecture). Inputs: +- This SUMMARY (pivot rationale + retained infrastructure + falsified hypotheses) +- Plan 01-11-RESEARCH.md (original research; partially historical now) +- Feasibility research findings (Verdict-A architecture details, embedded in c647f61 commit body + this SUMMARY) +- Prototype files (`tests/uat/prototype/*`, `src/test-hooks/offscreen-hooks.ts`, `vite.test.config.ts`) +- The 14-assertion scope from original Plan 01-11 PLAN.md + +01-13 will land all 14 assertions reliably, close Plan 01-09 functional contract via harness PASS, and become the standard closure gate for future phases.