From 2f1b1f36a774585d15133b02c991894d3f68a452 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 18 May 2026 15:33:35 +0200 Subject: [PATCH] =?UTF-8?q?feat(01-13):=20wave-3A=20=E2=80=94=20add=20get-?= =?UTF-8?q?display-surface=20bridge=20op=20(A3=20prereq)=20+=20extend=20Ti?= =?UTF-8?q?er-1=20grep=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scope: prerequisite step for Wave 3A's A3 assertion (displaySurface=monitor verification). The page→offscreen bridge gains a new op so the harness can query the active stream's `getSettings().displaySurface` without needing direct offscreen.evaluate access (impossible by-construction; the only cross-isolate path is chrome.runtime.sendMessage). Bridge op contract (`src/test-hooks/offscreen-hooks.ts`): - Protocol: { type: '__mokoshOffscreenQuery', op: 'get-display-surface' } - Response: { displaySurface: string|null } • null when no current stream (recording not active) • 'monitor' when installFakeDisplayMedia's monkey-patched getSettings() reports it (production code in src/offscreen/recorder.ts enforces this same value — tears down stream + throws 'wrong-display-surface' otherwise). - Failure: { ok: false, error: } only on getSettings throw. Tier-1 grep gate extension (`tests/background/no-test-hooks-in-prod-bundle.test.ts`): - FORBIDDEN_HOOK_STRINGS: 8 → 9 entries. - Added: 'get-display-surface' (the literal bridge-op string; matches the production-bundle absence invariant — the offscreen-hooks module is tree-shaken in production builds by the Vite mode gate in src/offscreen/recorder.ts top-of-module). Verification: - npx tsc: clean - npm run build: clean (dist/ 4 chunks; no offscreen-hooks artifact) - npm run build:test: clean (dist-test/ adds offscreen-hooks-DfWtG71P.js, 2.38kB) - SKIP_BUILD=1 vitest run no-test-hooks-in-prod-bundle.test.ts → 10/10 GREEN (1 build-sanity + 9 forbidden-string checks; production bundle hook-free) - SKIP_BUILD=1 vitest run (full) → 93/93 GREEN (Wave 0+1+2 baseline 92 + 1 from the 9th grep-gate string) - npx tsx tests/uat/a6.test.ts → A6 5/5 GREEN (lib-driven path preserved; bridge op addition does not interfere) Wave 3A continuation: assertA1/A2/A3/A4 land in the next commit which wires the harness-page surface + driver wrappers + harness.test.ts orchestrator. This commit is the bridge prerequisite — keeping the bridge-op extension atomic + the grep-gate extension atomic so the 'production bundle hook-free' invariant is provable BEFORE the page-side surface lands. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/test-hooks/offscreen-hooks.ts | 33 +++++++++++++++++++ .../no-test-hooks-in-prod-bundle.test.ts | 4 ++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/test-hooks/offscreen-hooks.ts b/src/test-hooks/offscreen-hooks.ts index bf2463b..c0c7a90 100644 --- a/src/test-hooks/offscreen-hooks.ts +++ b/src/test-hooks/offscreen-hooks.ts @@ -300,6 +300,10 @@ globalThis.__mokoshTest = { // op='install-fake-display-media' → { ok: true } OR { ok: false, error } // op='dispatch-ended' → { ok: true } OR { ok: false, error: 'no stream' } // op='has-stream' → { hasStream: boolean } +// op='get-display-surface' → { displaySurface: string|null } OR { ok: false, error } +// — Plan 01-13 Wave 3A A3 contract. Returns the active track's +// `getSettings().displaySurface` value (monkey-patched to 'monitor' +// by `installFakeDisplayMedia`); returns null when no stream is live. // Unknown ops respond { ok: false, error: 'unknown-op' }. // // The bridge handler MUST run BEFORE the production offscreen bridge @@ -349,6 +353,35 @@ chrome.runtime.onMessage.addListener((rawMessage, _sender, sendResponse) => { sendResponse({ hasStream: currentStream !== null }); return false; } + if (op === 'get-display-surface') { + // Plan 01-13 Wave 3A A3 contract — return the active track's + // displaySurface so the harness can verify the offscreen-hooks + // monkey-patched getSettings() correctly reports 'monitor'. + // Production code in src/offscreen/recorder.ts enforces this same + // invariant (tears down + throws 'wrong-display-surface' otherwise), + // so if recording is live the value is guaranteed monitor — the + // harness asserts === 'monitor' for explicit empirical verification. + try { + if (currentStream === null) { + sendResponse({ displaySurface: null }); + return false; + } + const track = currentStream.getVideoTracks()[0]; + if (track === undefined) { + sendResponse({ displaySurface: null }); + return false; + } + const settings = track.getSettings(); + const displaySurface = settings.displaySurface ?? null; + sendResponse({ displaySurface }); + } catch (err) { + sendResponse({ + ok: false, + error: err instanceof Error ? err.message : String(err), + }); + } + return false; + } sendResponse({ ok: false, error: 'unknown-op' }); return false; }); diff --git a/tests/background/no-test-hooks-in-prod-bundle.test.ts b/tests/background/no-test-hooks-in-prod-bundle.test.ts index 4eb41ee..1073b0b 100644 --- a/tests/background/no-test-hooks-in-prod-bundle.test.ts +++ b/tests/background/no-test-hooks-in-prod-bundle.test.ts @@ -58,8 +58,9 @@ // (replaces Approach-A `simulateUserStop`) // - `getSegmentCount` — Plan 01-11 Task 7 segments-count getter (retained) // - `__mokoshOffscreenQuery` — 01-13 page→offscreen bridge message type +// - `get-display-surface` — 01-13 Wave 3A bridge op string (A3 contract) // -// Total: 8 surface strings. Each MUST be absent from EVERY file under +// Total: 9 surface strings. Each MUST be absent from EVERY file under // `dist/` post-build. The list is mirrored by the harness's A0 // assertion (tests/uat/harness.test.ts in Wave 3A) so the same // invariant is enforced at unit-test time (fast, every CI run) AND @@ -109,6 +110,7 @@ const FORBIDDEN_HOOK_STRINGS: ReadonlyArray = [ 'dispatchEndedOnTrack', 'getSegmentCount', '__mokoshOffscreenQuery', + 'get-display-surface', ]; /** How long the build child has to finish (`npm run build` is ~10s).