Session-2 (/gsd-debug continuation) empirically refuted the SUMMARY's
original 'architecture broken → IndexedDB plan-fix needed' interpretation:
- Pre-kill probe: segments.length=3 (segments accumulated correctly during 5-min idle)
- Post-kill probe: segments.length=3 (offscreen-RAM survives SW kill structurally)
- Step C (no worker.close, just 5-min idle): identical 8505 bytes (CDP not the cause)
- Remux logs: each segment trackInfo=320x180 but 0 frames per segment
- 7/7 spike runs deterministic at 8505 bytes (canvas-captureStream throttling)
Root cause: installFakeDisplayMedia() at src/test-hooks/offscreen-hooks.ts:139-264
mints canvas.captureStream(30) on hidden -9999px-offset canvas; headless-Chromium
throttles MediaRecorder on invisible-canvas (Chrome bug 653548). Segments exist
but contain zero VP9 frames over 5-min idle.
Routing: Plan 04-08 inserted (user-authorized ceremony 2026-05-22) — video-file
MediaStream methodology reframe (Option 2 from session-2). IndexedDB plan-fix
recommendation REJECTED — would not close SC#1 because frames are the problem,
not segments.
stopServiceWorker helper + spike script + launch.ts:225 race-tolerant fix all
remain valid persisting artifacts for Plan 04-08.
Coherent 5-edit Wave 1 GREEN landing per Plan 04-02 Task 2; RED gate from
Task 1 (`tests/build/no-new-function-in-sw-chunk.test.ts` 1-hit assertion)
flips GREEN with 0 hits of `new Function` in any SW chunk
(`dist/assets/index.ts-*.js` glob).
## Threat T-04-02-01 mitigation (Elevation of Privilege — `new Function` literal)
Three layered mechanisms cooperate to drop the CSP-unsafe `new Function`
literal from the SW chunk while preserving JSZip's zip-assembly correctness
end-to-end (REVISION iter-2 WARNING 1 empirically pinned at UAT harness 33/33):
1. **Runtime polyfill prelude** at top-of-module of `src/background/index.ts`
(BEFORE the first `import`): an inline `queueMicrotask`-based polyfill
installs `globalThis.setImmediate` at SW boot. JSZip's pre-bundled
`dist/jszip.min.js` IIFE guards its internal setimmediate polyfill behind
`if(!s.setImmediate){...}`, so the upstream offending body never executes
at runtime once our prelude has installed the safe fast-path.
2. **`vite-plugin-node-polyfills` `exclude: ['setimmediate']`** in vite.config.ts:
prevents the plugin from injecting its node-stdlib-browser-aliased
setimmediate polyfill into the chunk. NOTE: this alone is insufficient
because JSZip's `dist/jszip.min.js` ships its OWN bundled-in setimmediate
(via the package.json `"browser"` field that maps `./lib/index` →
`./dist/jszip.min.js`); the plugin's `exclude` only filters the plugin's
own contributions.
3. **`resolve.alias.setimmediate`** redirects bare-specifier `setimmediate`
requires to `src/shared/setimmediate-stub.ts` (a 22-LOC TS module that
installs the same `queueMicrotask`-based polyfill via side-effect import).
This catches any future direct `import 'setimmediate'` consumer that
bypasses the prelude.
4. **`stripSetimmediateNewFunction()` Rollup post-transform plugin** in
vite.config.ts: surgically replaces the single occurrence of
`(I=new Function(""+I))` with `(I=function(){})` in any output chunk
that contains the JSZip-bundled setimmediate IIFE. The replacement is
observably equivalent in our codepath (the parent `typeof I!="function"&&`
guard means the body never runs when I is already a function — which is
the only form JSZip ever uses — AND the runtime prelude makes the entire
IIFE body unreachable regardless). Without this plugin, JSZip's
pre-bundled distribution embeds the upstream setimmediate package's
`setImmediate.js` verbatim inside its internal CJS module registry
(slot 54), unreachable by Vite's resolve.alias or the polyfill plugin's
exclude.
## Architecture decision log
**Option α (force JSZip unbundled `lib/index.js` via `resolve.alias.jszip`)
was attempted and reverted 2026-05-21** (between commits 630d40c and this).
Empirically broke UAT harness A30+ because the unbundled entry's transitive
readable-stream-browser browser-field mapping did not propagate correctly
through Vite's resolver — the async zip-write pipeline silently produced
an empty events.json. The post-transform plugin (Option β) is the
minimum-surface fix that preserves JSZip's runtime behavior verbatim while
satisfying the textual `new Function` count = 0 invariant.
## Verification
**Build / static gates:**
- `npm run build` exits 0; SW chunk `dist/assets/index.ts-DfBxWCT9.js`
(378.92 kB) contains 0 occurrences of `new Function` (was 1 in pre-fix
`index.ts-8LkXuqac.js`).
- `npx tsc --noEmit` exits 0.
- `grep -rn 'permissions.request' src/` returns 0 hits (Plan 04-02 ROADMAP
SC #4 regression pin GREEN).
- `node generate-icons.cjs` exits 0; old `generate-icons.js` no longer
exists (rename via `git mv` preserves history).
- `grep -c "exclude: \\['setimmediate'\\]" vite.config.ts` returns 1.
- `grep -c "queueMicrotask" src/background/index.ts` returns ≥1.
- `grep -c "Resolved in Phase 4 Plan 04-02" .planning/phases/01-stabilize-video-pipeline/deferred-items.md` returns ≥1.
**Test gates:**
- Focused: `npm test -- tests/build/no-new-function-in-sw-chunk.test.ts tests/build/dead-code-grep.test.ts --run` → 3/3 GREEN (Task 1's RED gate flipped GREEN).
- Full vitest: 183/183 GREEN on the clean run (180 baseline + 3 net new
from Plan 04-02 Task 1's two new files). Pre-existing intermittent flakes
per 04-01-SUMMARY Issues Encountered (blob-url-download / webm-remux /
webm-playback ffmpeg dry-run) persist across SUMMARY runs and are owned
by Plan 04-03.
**Pre-checkpoint bundle gates (per saved memory feedback-pre-checkpoint-bundle-gates.md):**
1. Tier-1 FORBIDDEN_HOOK_STRINGS: 13/13 tests GREEN; inventory unchanged at
12 strings (Plan 04-02 added no harness hooks).
2. SW CSP-safety grep: `grep -rn 'new Function\\|eval(' dist/assets/` returns
0 hits — polarity flipped from the pre-existing 1 documented exception
(the setimmediate literal). T-04-02-01 mitigation pin lands.
3. Node-globals: `Buffer.copy / .isView / .length / .push / .shift / .slice
/ .write` in SW chunk (pre-existing JSZip internals; unchanged from
04-01-SUMMARY).
4. DOM-globals: `document.createElement / .createTextNode / .documentElement
/ .F` + `window.Math / .console / .localStorage / .process` (pre-existing
JSZip text encoder fallback paths; unchanged from 04-01-SUMMARY).
5. manifest.json: present, MV3, `name: __MSG_extName__` (chrome.i18n intact).
**Empirical UAT harness (REVISION iter-2 WARNING 1):**
- `HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat` → 33/33 assertions
passed (verbatim `UAT harness: 33/33 assertions passed` in stdout).
Confirms JSZip's full SAVE → zip pipeline (A24-A32 inclusive, exercising
the in-memory MediaRecorder segments + base64 port wire + remux + zip
assembly + chrome.downloads + events.json + meta.json + screenshot)
operates correctly under the new bundle. The setimmediate polyfill
replacement preserves zip-write behavior end-to-end at the empirical
layer.
## Files
- **vite.config.ts**: imports `node:url` (fileURLToPath/URL) + `Plugin`
type from vite; adds `nodePolyfills.exclude: ['setimmediate']`;
adds `resolve.alias.setimmediate` → `src/shared/setimmediate-stub.ts`;
adds `stripSetimmediateNewFunction()` Rollup post-transform plugin
with full rationale comment.
- **src/background/index.ts**: 17-line top-of-module prelude inserted
BEFORE the first `import { Logger } ...` line. Inline `queueMicrotask`-based
setimmediate polyfill with typed widening cast (no `as any` per
CLAUDE.md). Reversible by `git revert`.
- **src/shared/setimmediate-stub.ts** (NEW): 50-LOC TS module providing
the same `queueMicrotask`-based polyfill via side-effect import.
Documented as the resolve.alias target.
- **generate-icons.js → generate-icons.cjs**: `git mv` preserving history.
Node 14+ treats `.cjs` as CJS regardless of `package.json` "type":
"module" per https://nodejs.org/api/packages.html#determining-module-system.
No code change; `require('fs')` + `require('path')` resolve cleanly.
No other references to the old `.js` path elsewhere in the codebase
outside the `.planning/` audit trail.
- **.planning/phases/01-stabilize-video-pipeline/deferred-items.md**:
appended "Resolved in Phase 4 Plan 04-02" closure block citing this
commit; details the 4-mechanism layered mitigation; documents the
Option α attempt + reversion.
References:
- .planning/phases/04-harden-clean-up-optional/04-RESEARCH.md §Q1
- .planning/phases/04-harden-clean-up-optional/04-PATTERNS.md
§vite.config.ts + §src/background/index.ts
- Plan 04-02 threat model T-04-02-01 (Elevation of Privilege) +
T-04-02-02 (DoS — JSZip fallback compatibility; verified by UAT 33/33)
- node_modules/jszip/lib/utils.js:7 (upstream `require("setimmediate")`)
- node_modules/setimmediate/setImmediate.js (upstream polyfill source)
- Plan 01-12 Wave 7 deferred-items.md disclosure (Phase 5 → Phase 4 target)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 carries one genuine designer-side decision: dark-surface logo contrast
strategy. Recommends Option A — `currentColor` SVG + CSS color driven via the
existing `.dark, [data-theme="dark"]` block in tokens.css (lines 234-251). Post-
research amendment: welcome.ts must swap `?url` (data URL → <img>) for `?raw`
(inline <svg> via DOMParser) because <img>-rendered SVGs do not inherit parent
CSS color — `currentColor` only resolves on inline DOM SVG.
Cursor visibility constraint (Plan 01-07 obs 2026-05-15) is listed as
behavioral-only inheritance, not a design surface — 1-line change in
src/offscreen/recorder.ts per Chrome CursorCaptureConstraint enum.
Inherits Phase 1 design system as read-only (Lora display + IBM Plex Sans UI
+ Loom palette + Mokosh mark + canonical tokens.css + 17-key i18n matrix).
Zero new tokens, zero new copy, zero new colors. PNG icons unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User invoked /gsd-plan-phase 4 and answered both gate questions before the
workflow correctly exited at the UI Design Contract gate (per workflow rule
that manual invocations cannot nested-Skill-spawn /gsd-ui-phase due to
AskUserQuestion-in-subcontext issue #1009).
Preferences saved at .plan-phase-preferences.md for the next plan-phase
invocation (after /gsd-ui-phase 4 produces UI-SPEC.md):
- UI gate: generate UI-SPEC.md first — unlike Phase 3 (false positive),
Phase 4 has genuine dark-logo work; UI-SPEC should be thin-but-real
(dark-logo design only; cursor visibility listed as inherited behavioral
change, not a design surface)
- Research gate: research first (light, ~10-20 min) — scope-limited to:
setimmediate polyfill replacement strategy + SW state persistence 5min
idle test patterns + chrome.scripting.executeScript world:'ISOLATED'
best practices for A29 cs-injection-world fix. Researcher NOT to
investigate already-deferred items (rrweb v2, SW-RAM, masking).
File auto-deletes when /gsd-plan-phase 4 honors these preferences.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents the single-task Plan 03-04 closure end-to-end:
- A32 ships ~90 lines of best-effort RAM scaffolding per D-P3-04 +
RESEARCH Open Question 3 (host-side puppeteer.Page.metrics; no page-
side counterpart; no SAVE; no archive parse)
- Pitfall 2 mandatory diagnostic leads diagnostics array (T-03-04-01
Repudiation mitigation; three layers of operator-visible signal so
automation GREEN ≠ §10 #9 closure)
- UAT 32/32 → 33/33 GREEN; vitest 171/171 preserved; Tier-1
FORBIDDEN_HOOK_STRINGS unchanged at 12 (host-side API has no
production-bundle impact)
- Phase 4 inheritance path documented (per-target enumeration via
browser.targets() + createCDPSession + Performance.getMetrics for
SW + offscreen + harness page aggregate)
- Pre-existing parallel-vitest Tier-1-build-step race recurred once
(1/171); verified pre-existing across 03-02 + 03-03; not caused by
A32 changes; isolated re-run 13/13 GREEN
- Plan 03-05 wave dependency: VERIFICATION.md aggregator; will record
§10 #9 as `human_verification` regardless of A32 status
- Zero deviations: plan-spec verbatim implementation; the cleanest of
the four Wave-2/3/4 plans in Phase 3 by deviation count
Plan-checker iter-1 VERIFICATION PASSED with 1 cosmetic WARNING (Dimension 11
Research Resolution: Open Questions section heading lacked (RESOLVED) suffix
convention). Fixed inline: heading now reads "## Open Questions (RESOLVED)".
.plan-phase-preferences.md (created mid-/gsd-plan-phase first invocation to
preserve gate answers across the UI-SPEC detour) DELETED — purpose served;
this plan-phase invocation honored the saved research-first-light scope
brief.
state.record-session CLI bug recurred (status flipped to "completed" because
18/23 known plans done). Restored: status=ready_to_execute. percent: 78 is
correct now (5 Phase 3 plans counted; was 18/18=100 stale).
Phase 3 ready for execution: 5 plans validated, infrastructure inherited,
test baselines preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 is verification-only; /gsd-ui-phase 3 trigger on "page" keyword
is a false positive. UI-SPEC.md confirms no new user-facing UI surface
in scope; locks the Phase 1 design system (Lora + IBM Plex Sans + Loom
palette + Mokosh mark + tokens.css + 17 i18n keys) as read-only
inherited context; declares minimal probe-page conventions for
internal Puppeteer test fixtures (Plans 03-01..03-05 per D-P3-01).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User invoked /gsd-plan-phase 3 and answered both gate questions before the
workflow correctly exited at the UI Design Contract gate (per workflow rule
that manual invocations cannot nested-Skill-spawn /gsd-ui-phase due to
AskUserQuestion-in-subcontext issue #1009).
Preferences saved at .plan-phase-preferences.md for the next plan-phase
invocation (after /gsd-ui-phase 3 produces UI-SPEC.md):
- UI gate: generate UI-SPEC.md first (chosen — most canonical; verification
caveat noted for /gsd-ui-phase to consider)
- Research gate: research first (light) — scope-limited to puppeteer.Page.metrics
+ rrweb alpha-pin status (NOT rrweb v2 upgrade implementation, NOT masking)
File auto-deletes when /gsd-plan-phase 3 honors these preferences.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verifier returned human_needed with 4/5 truths VERIFIED (T1-T4) + T5 UNCERTAIN
because Plan 02-04 Task 4 contract literally typed checkpoint:human-verify gate=blocking
and the operator empirical "approved" ack wasn't on record.
T5 (operator clicks SAVE → ZIP produced in <5s with correct layout + Blob URL)
is OVERRIDDEN to VERIFIED based on:
1. User explicit delegation 2026-05-20: "why do i need to do all of this? It's on
you to test..." — established that automation covers what automation can cover.
2. New saved memory feedback-trust-harness-over-manual-uat.md (same session):
reserve operator empirical UAT for surfaces automation genuinely cannot verify
(brand judgment, ergonomics). For deep-pipeline Phase 2 work, every operator-
checklist surface IS harness-covered.
3. Harness assertion coverage of every step:
- (a) <5s latency → A25 empirical via Puppeteer
- (b) 5-entry archive layout → A28 set-equality
- (c) 8-field meta.json schema → A26 + tests/build/strict-meta-json-validation.test.ts
- (d) video playback → Phase 1 VERIFICATION.md empirical (D-13 unchanged)
- (e) blob: URL pattern → A24 empirical
4. Alpha distribution build covers real-world OS-archive-manager layer outside
in-session verification scope.
Plan 02-04 Task 4 was authored before the saved-memory principle was established;
the checkpoint contract reflects an older operating mode.
Status: passed (with 1 override applied; override_notes captured in frontmatter)
Score: 5/5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SUMMARY.md documents:
- 3 RED tests in tests/background/blob-url-download.test.ts flipped GREEN
(wire-format polarity guard, 6 MB latency + wire-format, revoke lifecycle).
- 6 files modified (3 prod source + 3 test files; +518 / -35 lines).
- Wire-format extension: 3 new PortMessageType variants on keepalivePort.
- Operator-facing improvement: archives >2 MB now download successfully
(was: silent failure with data:URL Network error).
- Rule 3 deviation: extended Plan 02-01 test helpers with the offscreen-side
CREATE_DOWNLOAD_URL → DOWNLOAD_URL → REVOKE_DOWNLOAD_URL round-trip
simulation pattern + capturedArchiveBytes bytes capture. This pattern
is reusable by Plan 02-03 and was anticipated in Plan 02-01 SUMMARY.
- Forward link: Plan 02-03 (meta.urls + tab-url-tracker) is unblocked;
Plan 02-04 (UAT harness A24+) is unblocked.
Verification:
- npx tsc --noEmit: clean
- npm run build: clean
- npm run build:test: clean
- tests/background/blob-url-download.test.ts: 3/3 GREEN
- Tier-1 FORBIDDEN_HOOK_STRINGS: 13/13 GREEN (unchanged)
- Full vitest: 163 passed / 8 failed (was 159 passed / 12 failed); +4 GREEN
net delta. 8 remaining RED are exactly Plan 02-03 territory.
Plan 02-01 Wave 0 RED gate closed. Three failing test files (16 it()
blocks total: 11 RED + 5 GREEN regression guards) pin the locked
decisions for Phase 2 ahead of Plans 02-02 + 02-03 implementation:
- blob-url-download.test.ts (3 RED) — D-P2-01 offscreen Blob URL
pipeline (closes audit P0-6: base64 data: URL → blob: URL).
- meta-json-urls-schema.test.ts (5 RED) — D-P2-02 meta.url → meta.urls
migration + F2 empty-tracker → urls:[] resolution.
- strict-meta-json-validation.test.ts (3 RED + 5 GREEN) — D-P2-03
strict 8-field schema validation with EXPECTED_KEYS pin including
planner-suggested `schemaVersion` 8th field.
Test count delta: 155 GREEN → 159 GREEN + 11 RED (+4 GREEN regression
guards, +11 RED test contracts). Vitest reporter:
Test Files 4 failed | 27 passed (31)
Tests 12 failed | 159 passed (171)
(12 failed = 3 + 5 + 3 RED from this plan + 1 pre-existing flaky
ffprobe test in webm-remux.test.ts — out of scope; documented in
SUMMARY.md Deferred Issues.)
Tier-1 grep gate: 13/13 GREEN preserved (this plan touches no
production code).
Planner-resolved tensions carried forward in SUMMARY.md:
- D-P2-03 'non-empty urls[]' vs CONTEXT.md permissive empty-array →
F2 resolved in favor of permissive (Test 3 of Task 3 relaxed).
- 8th field name `schemaVersion` → tentative planner pick;
Plan 02-03 implementer commits to schemaVersion: '2' const.
- tab-url-tracker module seam → planner-suggested name
`src/background/tab-url-tracker.ts` with getTabUrlsSeen() export.
- Plan claim 'ALL 8 fail' reconciled honestly: 3 RED + 5 GREEN
regression guards (timestamp/semver/totalEvents/buffer-seconds/
duration-minutes already match current 7-field shape).
Plan suggestions reconciled with reality:
- vitest env: 'node' not 'jsdom' (Node 24 has URL/Blob/performance
globals; jsdom not in devDeps). FileReader polyfill inline.
- Task 2 Test 1 source-text scan instead of tsc-compile-failure
(vitest.config.ts typecheck:{enabled:false}).
Per worktree-mode constraint: STATE.md, ROADMAP.md, REQUIREMENTS.md
NOT modified. The orchestrator owns those writes after all worktree
agents in Wave 0 complete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan 01-12 closure SUMMARY landed at .planning/phases/01-stabilize-
video-pipeline/01-12-SUMMARY.md per the plan's <output> block + 01-13/
01-14 closure cadence. Mirrors the 01-13-SUMMARY frontmatter shape +
body sections (One-Liner / What Landed by Wave / Test Counts /
Deviations / Architectural Notes / Self-Check / Known Limitations /
Bridge to Phase 1 Closure).
Plan 01-12 design integration in one sentence: Lora self-hosted via
R2 designer substitution (Newsreader → Lora for Cyrillic coverage,
2026-05-19); src/shared/tokens.css canonical with 8 local @font-face
rules and zero remote URLs; 16 i18n keys across en + ru with parity;
branded Loom-mark icons replace Bug A placeholders; src/popup +
src/background migrated to chrome.i18n.getMessage with || <const>
fallback; UAT harness extended with A18-A22; pre-checkpoint bundle
gates established per feedback-pre-checkpoint-bundle-gates.md;
operator brand-fit ack received 2026-05-20 verbatim "all good".
Gate evidence (per Wave 7 pre-checkpoint 865d394 + this closure):
- vitest: 147/147 GREEN (re-verified on closure day; 26 test files)
- npm run test:uat: 21/21 GREEN (A0-A14 + A18-A22 + A23)
- npx tsc --noEmit: clean
- npm run build + npm run build:test: both clean
- MV3 CSP self-host: 0 googleapis / 0 https://fonts in dist/
- Tier-1 forbidden-strings: 13/13 GREEN (no new test-mode symbols)
- Operator brand-fit empirical ack 2026-05-20: "all good"
Closure linkage:
- Plan 01-12 functionally CLOSED (10/10 tasks; 7/7 waves)
- Plan 01-13 Task 9 (operator brand/design ack on loaded extension)
functionally CLOSED via this checkpoint (same operator + same
empirical surface coverage)
- Phase 1 design/brand contract CLOSED; only Plan 01-10 (welcome tab)
remains as the last Phase 1 functional plan
- Phase 2 inherits tokens.css + chrome.i18n patterns + OFL self-host
recipe + pre-checkpoint bundle gates as production conventions
Out-of-scope discovery (Wave 7 pre-checkpoint, logged for Phase 5
hardening, NOT a Plan 01-12 regression): setimmediate polyfill
`new Function` in SW chunk via vite-plugin-node-polyfills. Pre-existing
across Phase 1 history; logged at deferred-items.md. Suggested
follow-up: switch to a minimal Buffer shim or inline Buffer primitives
to drop the polyfill entirely.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave 7 pre-checkpoint bundle gates per feedback-pre-checkpoint-bundle-gates.md
revealed a pre-existing benign concern in the SW production bundle:
`vite-plugin-node-polyfills` (configured for Buffer in vite.config.ts)
bundles the upstream `setimmediate` package which contains a fallback
`new Function("" + I)` evaluated when setImmediate is called with a
non-function argument. Production source code does NOT call
setImmediate(string); the construct is dead at the runtime call-graph
level but Rollup conservatively preserves it (behind a runtime
typeof check, not a static dead branch).
Verified pre-existing across Phase 1 history via `git checkout main --
src/background/index.ts vite.config.ts && npm run build` — same
`new Function` count. Plan 01-12 made NO changes to the polyfill
configuration; this is logged for future tightening (Phase 5
hardening or a dedicated MV3 CSP audit plan), NOT for fix in this
plan per the deviation-rule SCOPE BOUNDARY.
All other pre-checkpoint bundle gates PASS:
- Tier-1 forbidden-strings: 13/13 GREEN (no new test-mode symbols)
- SW-bundle-import: 15/15 GREEN
- Node-globals (Buffer.*) in SW chunk: 0
- DOM-globals direct SW calls: none
- Manifest validation: PASS (__MSG_*__ + default_locale='en' +
16 i18n keys per locale; en+ru parity verified)
- Tokens.css MV3 CSP self-host: 0 googleapis / 0 https://fonts in dist/
- Icons rasterized: 8-bit RGBA at 406/784/1952 B
- vitest: 147/147 GREEN
- npm run test:uat: 21/21 GREEN (A1..A14 regression-free + A18..A22
new + A23 from 01-14)
- npx tsc --noEmit: clean
- npm run build + npm run build:test: clean
Surfacing Wave 7 operator brand-fit checkpoint to orchestrator next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan-checker BLOCKER B-01-14-01: original plan's must_haves truth #5 understated
baseline regression risk. Adding `monitorTypeSurfaces: 'include'` as a sibling
constraint in src/offscreen/recorder.ts would have dropped vitest from 98/98
GREEN to 97/98 RED because tests/offscreen/display-surface-constraint.test.ts
Test 1 (line 223-226) uses strict deep-equality (toHaveBeenCalledWith, NOT
expect.objectContaining) on the constraints object — the test author's intent
(comment at line 221-222) is to catch future drops of ANY field.
Surgical revision per references/planner-revision.md (surgeon-not-architect):
- Frontmatter: add tests/offscreen/display-surface-constraint.test.ts to
files_modified list.
- must_haves truth #5: replace the "no existing unit test references the
constraints object" claim with a positive statement that the strict-deep-
equality assertion at lines 223-226 is updated in lockstep; preserves the
test author's "no objectContaining" discipline; explicit no-transient-RED
guarantee across commit boundaries.
- must_haves artifacts: new entry for the test file documenting the in-place
edit shape and the preserved test author comment.
- must_haves key_links: new link entry pairing the test assertion with the
source call site under the lockstep contract.
- Interfaces block: add the explicit "test-expectation lockstep update" code
fragment with the chosen key ordering (video → monitorTypeSurfaces → audio)
so the executor lands the source change and the test update with matching
shapes.
- Task 1 <files>: add tests/offscreen/display-surface-constraint.test.ts.
- Task 1 <action>: insert new Step 1b between Step 1 (source change) and
Step 2 (offscreen-hooks bridge) — full single-line edit spec at lines
223-226, preserve toHaveBeenCalledWith contract, preserve comment block,
same-commit guarantee.
- Task 1 verify-block expected outputs: explicitly call out that 98/98 GREEN
is preserved BECAUSE Step 1b lands (without it, 97/98 RED on the strict-
deep-equality assertion).
- Task 1 <done>: add line covering the lockstep test update + the no-transient-
RED guarantee.
- <verification> phase gate: add new check #2 (test-expectation lockstep)
between source-line correctness and A23 round-trip.
- <success_criteria>: add bullet for the lockstep test-expectation update.
- <output> SUMMARY contract: add "Revision linkage" bullet documenting that
the plan was revised once after the plan-checker flagged B-01-14-01.
Untouched (per checker's preserve-verbatim list):
- Source-line target (src/offscreen/recorder.ts:270)
- Harness wiring references (assertA3 686, driveA14 987, __mokoshHarness
1922+1942, drivers array 289-312, Total comment 354)
- FORBIDDEN_HOOK_STRINGS lockstep contract (both inventories)
- `_constraints` capture path
- Scope discipline (still 1 task, autonomous, no checkpoint)
- Research traceability (Plan 01-10 RESEARCH §5 + §Pitfall-5 + W3C §6.1)
- Threat model (T-01-14-04 mirrors Plan 01-13)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan 01-14 ships W3C Screen Capture monitorTypeSurfaces: 'include' (Chrome
119+) on the offscreen getDisplayMedia call, plus an A23 harness regression
assertion that verifies the constraint reaches the call site via the
existing offscreen-hooks bridge.
Scope: 1 source line + A23 wiring + Tier-1 grep gate inventory update
(lockstep extension of unit-gate + UAT A0 FORBIDDEN_HOOK_STRINGS).
Autonomous, single executor; no operator empirical checkpoint (UAT 16/16
harness coverage suffices per feedback-pre-checkpoint-bundle-gates.md).
Canonical sources:
- Plan 01-10 RESEARCH section 5 ('monitorTypeSurfaces: include' recommendation)
- Plan 01-10 RESEARCH section Pitfall-5 ('Misinterpreting displaySurface
as a hard constraint' — monitorTypeSurfaces is the picker-UI complement
to D-15's post-grant validation)
- W3C Screen Capture spec section 6.1 DisplayMediaStreamOptions
- developer.chrome.com/docs/web-platform/screen-sharing-controls
Decisions honored:
- D-01 (whole-desktop only via getDisplayMedia; reject window/tab) — the
new constraint is the picker-UI realization of D-01's intent.
- D-15 (post-grant displaySurface validation) — UNCHANGED; remains the
enforcement (this plan is belt-and-suspenders at the picker UI level).
Ceremony note: this plan replaces the prior AMENDMENT-A.md improvisation
path retired per 01-11-SUMMARY Architectural Notes. Canonical GSD ceremony
(plan -> checker -> executor -> SUMMARY).
Validations:
- gsd-sdk frontmatter.validate -> valid: true (8/8 required fields).
- gsd-sdk verify.plan-structure -> valid: true (1 task; hasFiles/hasAction
/hasVerify/hasDone all true).
- ROADMAP.md Phase 1 plans list extended with 01-14 entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>