Commit Graph

162 Commits

Author SHA1 Message Date
1f2eb2e818 fix(04-08): revise plan per iter-1 BLOCKER fixes — bundling strategy + sync monkey-patch + async closure
iter-2 revision of Plan 04-08 (video-file MediaStream methodology reframe)
addressing the 2 BLOCKERs + 5 WARNINGs + 3 advisories from plan-checker
iter-1 (commit 051813e, .planning/phases/04-harden-clean-up-optional/04-08-CHECKER-iter-1.md).

BLOCKER 1 (Vite ?url asset emission for >=1 MB WebM): pre-decide the
bundling strategy. The 1.9 MB WebM is three orders of magnitude above
Vite's assetsInlineLimit (4096); it follows the extracted-asset path
(dist-test/assets/<hash>.webm), not the data-URI-inline path the
Plan 01-10 SVG precedent uses. The @crxjs/vite-plugin auto-WAR
behavior for extracted media assets in offscreen-document context is
empirically untested in this codebase. Resolution: add an explicit
web_accessible_resources entry for assets/*.webm in manifest.json
alongside the existing src/welcome/welcome.html entry. Production
dist/ has zero *.webm assets so the entry is inert; test dist-test/
has the hashed asset and the entry authorizes chrome-extension://<id>/
assets/<hash>.webm URL access from the offscreen document context.
No executor improvisation; the bundling strategy is locked-in BEFORE
Task 1 begins.

BLOCKER 2 (installFakeDisplayMedia async conversion breaks eager-install
contract): preserve the SYNCHRONOUS function signature. The existing
eager call at src/test-hooks/offscreen-hooks.ts:528-537 + the top-
level await at src/offscreen/recorder.ts:46-48 establish a contract
that navigator.mediaDevices.getDisplayMedia is monkey-patched BEFORE
recorder.bootstrap runs. Converting installFakeDisplayMedia() to
async would create a race window where recorder.startRecording calls
the REAL getDisplayMedia (Chrome screen-share picker hangs in
headless). Resolution: SYNC install (videoEl creation + DOM append +
monkey-patch assignment) + LAZY first-frame closure (await readyState
HAVE_FUTURE_DATA + .play() deferred INTO fakeGetDisplayMedia body).
First getDisplayMedia call may block ~50-500ms while video decodes;
subsequent calls observe the resolved readiness Promise + proceed
immediately. Bridge handler + eager-install try/catch remain sync.

WARNING 1 (autoplay reliability): explicit error class identifier
('autoplay-blocked or codec-unsupported in headless context') in the
.play() reject path; spike surface root cause instead of mysterious
0-frames.

WARNING 2 (patchDisplaySurface compatibility): new sub-gate in Task 1
verify that mints a stream + asserts track.getSettings().displaySurface
=== 'monitor'. Optional executor implementation as a --check-display-
surface-only mode on the spike script; spike re-run is the fallback
high-latency catch.

WARNING 3 (spike probe-value asserts): surfaced as explicit grep gates
in Task 2 verify block. POST-PRIME=0, PRE-KILL>=3, POST-KILL>=3 per
debug session-2 baseline.

WARNING 4 (ROADMAP.md edit): pre-specified exact pre-edit string +
replacement + grep gate (CLOSED via Plan 04-08 must appear; STATUS
2026-05-21: OPEN must disappear).

WARNING 5 (synthetic-display-source filename leak): new Tier-2 sub-
invariant in tests/background/no-test-hooks-in-prod-bundle.test.ts;
catches accidental test-hook inlining into production chunk. Tier-1
inventory at 12 entries unchanged.

advisory 1: commit message corrected to reference Task 1 + Task 2
only (not Task 3, which doesn't exist).

advisory 2: src/offscreen/recorder.ts:91 segments invariant added as
grep gate in Task 1 verify block.

advisory 3: dual-location fixture note added to Task 1 Step 1 (the
original tests/fixtures/last_30sec.webm remains in place; the new
tests/uat/fixtures/synthetic-display-source.webm is a SECOND copy
under the UAT subtree).

Plan validates via gsd-sdk frontmatter.validate --schema plan (valid:
true, no missing fields) AND gsd-sdk verify.plan-structure (valid:
true, 0 errors, 0 warnings, 2 tasks with full 4-element shapes).
files_modified updated to include tests/background/no-test-hooks-in-
prod-bundle.test.ts (Tier-2 gate location).

Iter-2 architectural thesis unchanged: HTMLVideoElement.captureStream
bypasses the canvas-throttling root cause per debug session-2 verdict.
The revision is methodology-tightening, not re-architecture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:17:33 +02:00
051813ee6e docs(04-08): plan-checker iter-1 — ITERATE-NEEDED (2 BLOCKER + 5 WARNING + 3 advisory)
Plan 04-08's core thesis (HTMLVideoElement.captureStream bypasses canvas
throttling per debug session-2 verdict) IS the correct path to close
ROADMAP SC #1. But two blocking issues prevent reliable delivery:

BLOCKER 1: Vite `?url` asset-emission analog mis-applied — mokosh-mark.svg
is 877 bytes (inlined as data:image/svg+xml URI) so the Plan 01-10 "?url
+ crxjs auto-WAR" precedent is NOT a direct analog for the 1.9 MB WebM
which will emit as a separate dist-test/assets/<hash>.webm file. WAR
auto-generation for extracted assets is unverified in this codebase.
Remediation: probe-then-decide OR Blob URL from ?raw ArrayBuffer.

BLOCKER 2: installFakeDisplayMedia()'s eager-install-at-module-load
contract is silently broken by the proposed async conversion. The race
window opens because recorder.ts:48 resolves before the async install
completes; recorder.startRecording → real getDisplayMedia → headless
hang. Remediation: keep sync monkey-patch; defer the canplay wait into
fakeGetDisplayMedia closure (lazy first-frame).

WARNINGS surface unverified headless autoplay reliability, displaySurface
monkey-patch portability to HTMLVideoElement tracks, spike probe-value
gates not surfaced as automated verify, and ROADMAP.md flip without grep
enforcement.

Architectural alignment confirmed (segments: Blob[] preserved; IDB
correctly rejected; D-P4-01 honored). iter-2 is a methodology-tightening
pass, not re-architecture. Estimated ~150-300 lines of plan edits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:03:08 +02:00
504d9dccf3 docs(04-08): create plan — video-file MediaStream methodology reframe + A33 revival
Inserts Plan 04-08 between Plans 04-06 and 04-07 (Wave 5.5) per
debug session-2 verdict (REFUTED-architecture; canvas-captureStream
issue). Scope: replace canvas.captureStream(30) source in
installFakeDisplayMedia() at src/test-hooks/offscreen-hooks.ts:139-264
with HTMLVideoElement.captureStream backed by a bundled VP9 WebM at
tests/uat/fixtures/synthetic-display-source.webm. Bundled via Vite ?url
import per Plan 01-10 mokosh-mark precedent. Revives the A33 harness
assertion (Plan 04-04 Pattern 4 verbatim) under valid methodology;
stopServiceWorker helper from Plan 04-04 reused. Closes ROADMAP SC #1
within v1. Architecture (offscreen-RAM segments: Blob[]) UNCHANGED
per debug session-2 segment-count probe evidence.

2 tasks atomic: (1) bundle fixture + rewrite installFakeDisplayMedia
+ ambient *.webm?url decl; (2) re-run spike + land driveA33 +
orchestrator wiring + SKIP_LONG_UAT env-gate + SUMMARY + STATE/ROADMAP
markers. UAT 33 -> 34 GREEN target. FORBIDDEN_HOOK_STRINGS unchanged
at 12. Pre-checkpoint bundle gates 6/6 PASS preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 08:41:51 +02:00
c1501e7a81 docs(04-04): amend SUMMARY post-debug session-2 — REFUTED-architecture verdict
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.
2026-05-22 08:14:44 +02:00
4ea1bbb7a8 docs(debug): SC#1 sw-offscreen-persistence investigation session 2 — REFUTED-architecture (canvas-captureStream issue)
Session-2 (continuation of d614462 INCONCLUSIVE) executed disambiguation
plan and converged on a definitive verdict. Three independent observations
ruled out ALL architectural-failure hypotheses:

  Step A: race-tolerant offscreen target attach (committed separately;
  enabled visibility into the offscreen recorder + remux pipeline).

  Step B: pre-kill and post-kill segment-count probes via the existing
  `__mokoshOffscreenQuery 'get-segment-count'` bridge op (no new
  test-only symbols introduced; FORBIDDEN_HOOK_STRINGS inventory
  unchanged at 12 entries). Observed segments.length transition:
    POST-PRIME=0 → PRE-KILL=3 → POST-KILL=3
  Segments structurally survive the SW kill (offscreen still responds
  to bridge query post-kill). Hypothesis A (architectural RAM loss
  across SW termination) REFUTED.

  Step C: SPIKE_SKIP_SW_KILL=1 env-var mode skips worker.close(). The
  resulting videoSize is IDENTICAL to the canonical run (8505 bytes).
  Hypothesis C (CDP-induced offscreen collateral teardown) REFUTED.
  Since SW was not killed, its console listener stayed connected,
  exposing the full Remux pipeline output:
    [SW:Remux] Segment ts=1: 0 frames, duration=0ms, trackInfo=320x180
    [SW:Remux] Segment ts=2: 0 frames, duration=0ms, trackInfo=320x180
    [SW:Remux] Segment ts=3: 0 frames, duration=0ms, trackInfo=320x180
    [SW:Remux] Remux complete: 0 frames, total timeline=0ms, output=8505 bytes
  Each segment Blob has a valid track header (PixelWidth/Height parsed
  successfully) but ZERO VP9 frames. Hypothesis B (canvas-captureStream
  throttling in headless idle) CONFIRMED.

VERDICT: REFUTED-architecture (canvas-captureStream issue).

The architecture (offscreen-RAM `segments: Blob[] = []`) works
correctly; the spike's test methodology is invalid. The
`installFakeDisplayMedia` synthetic stream (canvas.captureStream(30)
on a hidden -9999px-offset 320x180 canvas) cannot sustain frame
production during a 5-min headless idle window despite the
`setInterval(drawFrame, 33ms)` belt-and-suspenders mitigation. This
matches the documented Chromium throttling of MediaRecorder on
invisible-canvas sources (Chrome bug 653548; auto-throttled-screen-capture
design doc; sendrec.eu blog "Why Canvas Breaks Your Screen Recorder").

ROUTING RECOMMENDATION (out of scope for this debug session):
  - Do NOT proceed with the IndexedDB persistence plan-fix proposed by
    Plan 04-04 SUMMARY. The plan-fix would NOT close SC #1 because the
    spike would STILL produce 8505 bytes after IDB lands — the failure
    is in the test's fake stream, not in segment persistence.
  - Open a new plan slot (likely Plan 04-08 or a Phase 5 plan) that
    reframes SC #1 verification methodology. Options:
      (a) real getDisplayMedia in non-headless Puppeteer with
          --auto-select-desktop-capture-source;
      (b) video-file-backed MediaStream source (HTMLVideoElement
          playing a bundled WebM) — bypasses canvas-captureStream
          throttling entirely;
      (c) reduce SC #1 wall-clock idle threshold to a value short
          enough that canvas-captureStream survives (e.g., 30s) AND
          add a separate manual operator-empirical test for 5-min.

ROADMAP SC #1 status: REMAINS OPEN. The architecture is sound; the
empirical verification gate is broken. Plan 04-04 SUMMARY's
characterization ("spike FAILED → architectural plan-fix needed") is
TECHNICALLY CORRECT on the first clause but INCORRECT on the second —
the spike's failure mode is in test infrastructure, not in production
code.

Files in this commit:
  - tests/uat/spike-a33-sw-persistence.ts: added probeSegmentCount
    helper using existing __mokoshOffscreenQuery bridge op; 3
    checkpoints (POST-PRIME / PRE-KILL / POST-KILL); SPIKE_SKIP_SW_KILL=1
    env-var skips worker.close() for Step C disambiguation.
  - .planning/debug/sw-offscreen-persistence-investigation-session-2.md:
    NEW session-2 debug note documenting full evidence trail + verdict
    derivation + routing recommendation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 08:02:24 +02:00
d614462694 docs(debug): SC#1 sw-offscreen-persistence investigation — INCONCLUSIVE
Pre-commit-ceremony verification of Plan 04-04 Wave 0 SPIKE finding
(videoSize=8505 bytes after 5-min SW idle + Puppeteer worker.close()).

Reproducibility: 4/4 runs (incl. prior 3726eee) produced identical
8505-byte WebM. Deterministic.

Chrome docs research: chrome.offscreen DISPLAY_MEDIA reason has NO
lifetime limit; offscreen "may outlive" its SW; Puppeteer #9995 +
crbug 1371432 document CDP attach distorting SW lifecycle; chromium
auto-throttled-screen-capture + Chrome Bug 653548 document canvas-
captureStream throttling on invisible/background tabs.

Verdict: INCONCLUSIVE — the spike's 8505-byte result is consistent
with THREE competing root causes (test-invalid headless throttling;
CDP-artifact collateral teardown; architectural offscreen-RAM-loss)
and the spike cannot disambiguate between them. Observability gaps:
launch.ts:225 filters offscreen console on background_page (MV2)
when MV3 offscreen is type 'page' → zero offscreen logs in all spike
runs.

Recommendation: PAUSE the ~2-4h IndexedDB plan-fix. Three cheap
disambiguation steps (~75 min total) can isolate the actual root
cause before committing. Detailed in the debug note's
routing_recommendation block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:12:46 +02:00
e8a2e7696d docs(04-04): complete harden-clean-up-optional plan 04-04 — SW persistence spike FAILED, plan-fix ceremony required
Plan 04-04 (spike→auto) closes at Task 1 (Wave 0 SPIKE) with an empirical
NO on the RESEARCH Q2 MEDIUM-confidence hypothesis A3 (offscreen-document
independent lifecycle anchored by active MediaRecorder). Task 2 (Wave 1
A33 verification-only harness assertion) BLOCKED by the plan's explicit
gating condition (videoSize > 100_000); ROADMAP SC #1 remains OPEN.

Spike empirical numbers (one HEADLESS=1 run; 308.7s wall-clock; full log
at /tmp/04-04-spike.log; reproducible via the committed spike script):
  - assertA2 prime:        PASSED (REC state established)
  - 5-min wall-clock idle: elapsed cleanly
  - stopServiceWorker CDP: succeeded (worker.close() returned)
  - SAVE_ARCHIVE ack:      {success: true} (event-driven SW respawn worked)
  - video/last_30sec.webm: 8505 bytes (sanity floor 100 KB; healthy 1-3 MB)
  - ffprobe on extracted:  'End of file' + 'Duplicate element' (no clusters)
  - rrweb/session.json:    [] (empty)
  - logs/events.json:      [] (empty)
  - meta.urls:             chrome-extension://* only (real-page URLs LOST)

Conclusion: src/offscreen/recorder.ts:91 `let segments: Blob[] = []` RAM-
only architecture does NOT survive 5-min SW idle + Puppeteer CDP worker.
close(). Architectural change required to close ROADMAP SC #1 (canonical
recommendation per 04-RESEARCH.md Q2 sub-question b Option C: IndexedDB
persistence in offscreen — Blobs serialize cleanly via structured-clone;
per-segment write ~3 MB; ~3 writes per 30s window). Per saved memory
`feedback-gsd-ceremony-for-fixes.md` the architectural fix routes through
/gsd-plan-phase rewrite OR /gsd-debug ceremony — NOT improvised inline
inside Plan 04-04.

Task 1 persisting artifacts (committed at 3726eee):
  - tests/uat/lib/harness-page-driver.ts: +43/-6 lines
    - Browser type added to puppeteer import
    - stopServiceWorker(browser, extensionId) helper (Chrome devrel
      canonical pattern; Puppeteer >=22.1.0 worker.close())
    - findLatestZip exported (was module-internal)
  - tests/uat/spike-a33-sw-persistence.ts NEW +202 lines
    - One-shot reproducible empirical investigation script
    - Reusable for future SW-lifecycle regression (the eventual plan-fix
      re-runs this script as its A33-verification gate)
    - Committed (not deleted) per the spike-FAILED forensic-evidence pattern

Task 2 was NOT committed (BLOCKED by gating condition); UAT count stays
33/33; Tier-1 FORBIDDEN_HOOK_STRINGS inventory unchanged at 12; A33 not
introduced.

Pre-checkpoint bundle gates (per saved memory feedback-pre-checkpoint-
bundle-gates.md): 6/6 GREEN unchanged from Plan 04-03 baseline (zero
production source changes in Plan 04-04).
  - SW chunk new Function: 0 (Plan 04-02 polarity preserved)
  - SW chunk eval:         0 (preserved)
  - SW chunk Buffer.:      1 (pre-existing JSZip polyfill; logged deferred)
  - SW chunk window./doc.: 0/0 (preserved)
  - dist/ grep × 12 hooks: 0 matches (Tier-1 inventory invariant held)
  - Manifest:              validates clean

vitest baseline: 183 tests total. Sequential `npm test` showed 180/183 with
3 pre-existing flakes in tests/background/blob-url-download.test.ts +
tests/background/webm-remux.test.ts + tests/offscreen/webm-playback.test.ts;
all 3 PASS in isolation. Per 04-CONTEXT.md items 9-10 these are documented
pre-existing issues (parallel-vitest Tier-1-build-step race + 2 ffprobe/
ffmpeg flakes pre-dating Phase 3) — NOT a Plan 04-04 regression (Plan 04-04
made zero source-code changes that could possibly affect them).

Files committed:
  - .planning/phases/04-harden-clean-up-optional/04-04-SUMMARY.md NEW
  - .planning/STATE.md: position advanced 4→5 / 7; progress 87% → 90%;
    2 decision entries logged; session metadata updated
  - .planning/ROADMAP.md: Phase 4 row count 2/7 → 4/7; Plan 04-04
    checklist box ticked with full SPIKE FAILED annotation; SC #1 marked
    OPEN with empirical evidence inline

Next step (out of Plan 04-04 scope; routed per spike-first contract):
plan-fix ceremony for IndexedDB persistence layer at src/offscreen/*.
The plan-checker/planner owns whether to (a) rewrite Plan 04-04 in-place,
(b) insert a new plan slot (e.g., 04-08), or (c) close Plan 04-04 as
spike-findings + open a fresh follow-up plan. Recommendation in SUMMARY:
option (b) or (c) — keep Plan 04-04 as the canonical spike-findings record.
2026-05-21 20:02:42 +02:00
303644f8cc docs(04-03): complete harden-clean-up-optional plan 04-03 — A29 flake fix
A29 (rrweb DOM verification) rewritten in-place via the canonical cs-
injection-world pattern + strict-sentinel filter. Closes ~2/3 flake
documented in Plans 03-02 + 03-03 SUMMARYs (A29 was "passing" by
reading iana.org leftover DOM events from A27/A28's still-open probe
tabs; a real rrweb regression at src/content/index.ts:284 would have
been masked).

Plan 04-03 task commits (atomic; sequential foreground mode):
- 73eb9b6: Task 1 — A29 page-side cs-injection-world skeleton +
  sentinel-bearing <div> injection
- b341a71: Task 2 — A29 host-side strict-sentinel filter (RESEARCH Q3
  Code Example Pattern 3); IncrementalSource added to @rrweb/types
  import binding; A29.2 PASS × 5/5 consecutive UAT runs

Empirical evidence:
- vitest 183/183 GREEN preserved (Plan 04-02 baseline)
- UAT harness 33/33 GREEN × 5 consecutive runs
- A29 strict-sentinel: mutationEvents=1, sentinelEvents=1 in ALL 5 runs
- Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12
- SW chunk: 0 new Function, 0 eval (Plan 04-02 baseline held)
- Pre-checkpoint bundle gates 5/5 PASS

STATE.md + ROADMAP.md updated per sequential workflow:
- Plan counter advanced 3 → 4 of 7
- Progress 83% → 87% (26/30 plans complete)
- Decision log entry added for Plan 04-03
- ROADMAP Phase 4 04-03 row flipped to [x]
2026-05-21 17:01:58 +02:00
6a1fc32826 docs(04-02): complete harden-clean-up-optional plan 04-02 — build hygiene
Plan 04-02 closes three independent build-hygiene fixes consolidated into
one plan because they share the build-gate-grep test-scaffold pattern:

1. **setimmediate polyfill replacement** — layered 4-mechanism CSP-hardening
   eliminates the `new Function` literal from the SW chunk (grep -c flips
   1→0 across all three SW chunks). Runtime guard + nodePolyfills exclude
   + resolve.alias + Rollup post-transform plugin. Option α (force JSZip
   unbundled lib/index.js) attempted + reverted because it broke
   readable-stream-browser propagation causing UAT A30+ regressions;
   Option β (post-transform plugin) preserves JSZip's pre-bundled
   distribution verbatim while excising the offending literal.

2. **ROADMAP SC #3** (generate-icons ESM/CJS) — `git mv generate-icons.js
   generate-icons.cjs` resolves the `require('fs')` under
   `package.json type: module` via Node's `.cjs`-as-CJS rule.

3. **ROADMAP SC #4** (dead-code grep) — `tests/build/dead-code-grep.test.ts`
   regression-pins `permissions.request` absence in `src/`.

Plus closure of Plan 01-12 Wave 7's setimmediate deferred-items entry.

Task commits:
  - 630d40c test(04-02): Wave 0 RED — no-new-function + dead-code-grep
  - f251297 feat(04-02): Wave 1 GREEN — setimmediate replacement + CJS rename + closure

Verification:
  - vitest 180/180 → 183/183 GREEN on clean run (+3 net new tests)
  - UAT harness 33/33 GREEN preserved (REVISION iter-2 WARNING 1 empirical pin)
  - Pre-checkpoint bundle gates 5/5 PASS; SW CSP-safety polarity flipped 1→0
  - tsc-clean preserved; npm run build exit 0; node generate-icons.cjs exit 0

STATE.md: Plan 3/7 (Plan 04-02 complete); 25/30 total plans; 83% progress.
ROADMAP.md: Phase 4 progress 2/7 plans complete (04-01 + 04-02).
deferred-items.md: Plan 01-12 Wave 7 setimmediate entry CLOSED end-to-end.

SUMMARY at `.planning/phases/04-harden-clean-up-optional/04-02-SUMMARY.md`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 15:41:54 +02:00
f251297256 feat(04-02): Wave 1 — setimmediate polyfill replaced + generate-icons.cjs + deferred-items closure
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>
2026-05-21 15:16:44 +02:00
f72bca5c46 docs(04-01): complete audit-p1-polish-content-script plan
Plan 04-01 closure marker — 04-01-SUMMARY.md + STATE.md position advance
(Plan 1 of 7 -> Plan 2 of 7; Plan 04-02 build hygiene queued NEXT in Wave 1)
+ ROADMAP plan-progress table flip ([ ] -> [x] 04-01-PLAN.md row).

Plan delivered (per SUMMARY):
- Audit P1 #11 fetch URL extraction fix (TWO sites; instanceof Request narrow)
- Audit P1 #14 navigation URL tracking fix (module-level previousUrl)
- Audit P1 #15 rrweb emit timestamp normalization (Date.now() Unix epoch)
- 9 new vitest tests under tests/content/; baseline 171 -> 180/180 GREEN
- tsc-clean preserved; Tier-1 hook-strings inventory unchanged at 12
- Audit P1 polish backlog CLOSED 3/3

Per-task commits (TDD pair):
- 3dbc51c test(04-01): Wave 0 RED — content-script test scaffolds
- 7da30af feat(04-01): Wave 1 GREEN — 3 surgical edits in src/content/index.ts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:34:03 +02:00
3dbc51cdcd test(04-01): Wave 0 RED — audit P1 #11/#14/#15 content-script test scaffolds
Three new test files at tests/content/ (NEW directory mirroring src/content/)
pin the canonical Plan 04-01 contracts; 7 of 9 tests are RED today and flip
GREEN once src/content/index.ts gains the three surgical edits in Task 2.

* tests/content/fetch-interception.test.ts (4 tests; A+C pass today via the
  identity String(string)===string coincidence, B+D RED — they fetch a
  `new Request(url)` and assert target === request.url under the canonical
  `args[0] instanceof Request ? args[0].url : String(args[0])` narrow).
* tests/content/navigation-tracking.test.ts (3 tests; all 3 RED — popstate
  + hashchange + history.pushState wrap all read meta.previousUrl which is
  permanently 'unknown' under today's `history.state?.url || 'unknown'`
  emit; GREEN after module-level `let previousUrl` lands).
* tests/content/rrweb-timestamps.test.ts (2 tests; both RED — Test A asserts
  rrweb-emit normalizes timestamps to Date.now()-class >1e12 instead of the
  rrweb-internal page-load-relative small int; Test B regresses
  cleanupOldEvents arithmetic correctness when both sides are Unix-epoch).

Scaffold mirrors tests/background/start-video-capture-no-tab.test.ts (Plan
01-09): vi.resetModules() in beforeEach, minimal chrome.* + window/document/
history/Request stubs installed on globalThis before
`await import('../../src/content/index')`. rrweb is mocked via vi.mock so the
content-script's `import { record } from 'rrweb'` short-circuits to a no-op
factory (avoids the rrweb-lib ESM-in-CJS transform crash). userEvents and
rrwebEvents are read back through the canonical GET_RRWEB_EVENTS chrome.
runtime.onMessage path the production archive pipeline uses.

Also folds in the .planning/config.json `use_worktrees: false` flip the
orchestrator staged before respawning this executor in foreground mode.

Plan: 04-01 Wave 0
Files:
- tests/content/fetch-interception.test.ts
- tests/content/navigation-tracking.test.ts
- tests/content/rrweb-timestamps.test.ts
- .planning/config.json (worktree mode disabled)

Verification (RED gate):
- npm test -- tests/content/ --run → 7 failed | 2 passed (9)
- grep -c "instanceof Request" tests/content/fetch-interception.test.ts → 5
- grep -c "previousUrl" tests/content/navigation-tracking.test.ts → 24
- grep -cE "Date\.now\(\)" tests/content/rrweb-timestamps.test.ts → 9

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:19:39 +02:00
9ad5069f70 docs(state): mark phase 04 executing — begin-phase tick
Set status=executing, current focus=Phase 04, position=Plan 1 of 7.
2026-05-21 13:32:53 +02:00
3ceca875e5 docs(state): resume-work — HANDOFF.json consumed; user routed to /gsd-execute-phase 4
Session continuity updated; .continue-here.md preserved as secondary fallback.
2026-05-21 10:31:20 +02:00
dbcf4827f6 wip: phase-04 paused — 7 plans validated iter-2 PASSED, ready for execution .planning/phases/04-harden-clean-up-optional/.continue-here.md .planning/HANDOFF.json 2026-05-21 10:25:29 +02:00
55369b80ad docs(state): record phase 4 plan-phase closure — iter-2 PASSED + 3 cosmetic advisories fixed
state.record-session CLI bug recurred (status: completed because 23/30 partial
match triggered; restored: status=ready_to_execute). total_plans correctly
bumped 23 → 30 (Phase 1: 14 + Phase 2: 4 + Phase 3: 5 + Phase 4: 7).

Phase 4 plan-phase ceremony complete:
- 7 plans across 6 waves (Wave 1: 04-01+04-02 parallel; Waves 2-6 single-plan)
- Plan-checker iter-1: 2 BLOCKER + 4 WARNING (fixed)
- Plan-checker iter-2: VERIFICATION PASSED with 3 cosmetic advisories
- 3 cosmetic advisories now fixed (commit 3c1280e):
  * Dim 11 RESEARCH.md Open Questions (RESOLVED) suffix
  * Dim 12 PATTERNS.md:886 DEPRECATED banner citing Plan 04-04 Option B
  * VALIDATION.md frontmatter revision count 4 → 5

Phase 4 ready for execution: 7 plans (~30 anticipated files; 7 new test files;
A33+A34 harness extensions; setimmediate polyfill replacement; dead-code grep;
SW state persistence spike+impl; dark-logo currentColor; cursor verification
+ stale-note correction; closure aggregator + ROADMAP backfill).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:21:44 +02:00
3c1280ed2d docs(04): plan-phase closure — 3 cosmetic advisories from checker iter-2 resolved
Plan-checker iter-2 returned VERIFICATION PASSED with 3 cosmetic advisories:
- Dim 11: RESEARCH.md "## Open Questions" missing "(RESOLVED)" suffix → fixed
- Dim 12: PATTERNS.md:886 stale dispatchSaveArchiveForA33 example → added
  DEPRECATED banner citing Plan 04-04 REVISION iter-2 Option B canonical pattern
- VALIDATION.md frontmatter "4 revised tasks" mismatched per-task map (5 rows) → fixed

All 4 BLOCKER+WARNING issues from iter-1 verified resolved by iter-2 plan-checker
(VERIFICATION PASSED). 3 cosmetic items now resolved as well. 2 advisory items
left as-is per iter-1 (W2 scope-sanity at 04-06; W3 conservative 04-03 dep).

Phase 4 plans cleared for execution:
- 7 plans across 6 waves (Wave 1: 04-01+04-02 parallel; Waves 2-6 single-plan)
- Plan-checker iter-2 VERIFICATION PASSED
- Test baselines preserved: vitest 171/171 · UAT harness 33/33 · Tier-1 12

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:21:03 +02:00
76fffb35b9 fix(04): revise plans per checker iter-1 — 2 BLOCKERS + 2 WARNINGS fixed
Plan-checker iter-1 found 2 BLOCKERS + 4 WARNINGS. Iter-2 revision applies
surgical fixes to 4 plans + VALIDATION:

BLOCKER 1 (Plan 04-06 Task 4): wrong SW chunk glob `dist/assets/index*-bg.js`
matched zero files → Gates 2/3/4 silently PASSED. Replaced with canonical
`dist/assets/index.ts-*.js` (verified empirically: index.ts-8LkXuqac.js
on disk; RESEARCH Q1). Added glob-existence pre-gate `ls | wc -l >= 1`
to fail-loudly on future Vite chunk-naming shift.

BLOCKER 2 (Plan 04-04 Task 1): spike called non-existent
__mokoshHarness.dispatchSaveArchive (verified: harness surface is
assertA1..A31 + getManifestVersion only). Applied Option B — spike
+ driveA33 now dispatch SAVE_ARCHIVE via chrome.runtime.sendMessage
inline in page.evaluate (matches 9 existing assertA* methods:
A5/A11/A12/A13/A26/A28/A29/A30/A31). No new harness helper introduced.

WARNING 1 (Plan 04-02 Task 2): verify omitted UAT harness run. Added
`HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat 2>&1 | grep -c 'UAT
harness: 33/33 assertions passed'` to verify command (stdout format
confirmed at tests/uat/harness.test.ts:537).

WARNING 4 (Plan 04-07 Task 1): weak operator-ack gate (placeholder would
pass). Added `grep -cE 'approved|All good|APPROVED|approved by|operator
ack|all good' 04-VERIFICATION.md` to verify command. Covers both
canonical Plan 04-06 resume-signal ("approved" lowercase) AND prior-art
Plan 01-10 cycle-2 ack ("All good" titlecase).

WARNINGS 2 + 3 left as-is (truly advisory: scope-sanity threshold +
conservative dependency without file overlap).

04-VALIDATION.md per-task map rows updated for the 5 revised task entries
(04-02 T2 + 04-04 T1 + 04-04 T2 + 04-06 T4 + 04-07 T1). Frontmatter
adds `revised: 2026-05-21` + iter-2 notes block.

3 plans unchanged on disk (04-01, 04-03, 04-05).

Empirical confirmations used in revision:
- Harness surface: grep extension-page-harness.ts:4018 confirms
  __mokoshHarness.{assertA1..A31, getManifestVersion}; no dispatchSaveArchive
- SW chunk filename: ls dist/assets/ shows index.ts-8LkXuqac.js;
  no index*-bg.js matches
- SAVE_ARCHIVE precedent count: 9 existing assertA* methods use the
  chrome.runtime.sendMessage pattern
- UAT harness stdout format: harness.test.ts:537 emits canonical
  "UAT harness: N/N assertions passed"

Ready for plan-checker iter-3 re-verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:00:07 +02:00
526ac78046 docs(04): create phase plan — 7 plans for Phase 4 hardening (audit P1 polish + flake stabilization + SW persistence + visual polish + closure)
Wave structure:
- W1 (parallel): 04-01 (Audit P1 polish #11/#14/#15 TDD) + 04-02 (build/CSP hygiene: setimmediate polyfill + dead-code + generate-icons.cjs)
- W2: 04-03 (A29 cs-injection-world rewrite; closes flake)
- W3: 04-04 (A33 SW state persistence; spike-first + CDP worker.close())
- W4: 04-05 (A34 fetch+XHR network_error; ROADMAP SC #2 + validates Plan 04-01 P1 #11 end-to-end)
- W5: 04-06 (dark-logo currentColor + cursor verification + 01-07-SUMMARY back-patch; operator empirical)
- W6: 04-07 (04-VERIFICATION.md aggregator + ROADMAP backfill + v1 close prep)

Honors locked decisions D-P4-01..05 (full Phase 4 + all 3 P1 polish + both visual items + alpha-independent + ROADMAP backfill).
Implements RESEARCH Q1 (setimmediate option a), Q2 (spike-first SW persistence), Q3 (A29 cs-injection-world), Finding 4 (cursor already shipped — verification only).
UI-SPEC dark-logo currentColor strategy with inline-SVG injection landed per UI-SPEC §"Implementation amendment".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 09:30:49 +02:00
f012c8c103 docs(04): pattern map — ~30 anticipated files mapped (21 exact + 8 role-match + 1 NEW pattern stopServiceWorker CDP helper) .planning/phases/04-harden-clean-up-optional/04-PATTERNS.md 2026-05-21 08:29:19 +02:00
7178d14154 docs(phase-04): add validation strategy — Wave 0 anticipates 6 new unit test files per RESEARCH .planning/phases/04-harden-clean-up-optional/04-VALIDATION.md 2026-05-21 08:03:10 +02:00
d1f676707e docs(04): research phase domain — setimmediate, SW persistence, A29 race fix + cursor finding .planning/phases/04-harden-clean-up-optional/04-RESEARCH.md 2026-05-21 08:01:27 +02:00
61caf04273 docs(state): record phase 4 UI-SPEC session — APPROVED 5/6 + 1 FLAG non-blocking
state.record-session CLI bug recurred (status: completed because 23/23 known
plans done). Restored: status=ready_to_plan.

UI-SPEC.md at:
  .planning/phases/04-harden-clean-up-optional/04-UI-SPEC.md
  status: approved · reviewer: gsd-ui-checker · reviewed_at: 2026-05-20

Verdict: 5/6 dimensions PASS + 1 FLAG (Dim 4 inherited type scale exceeds
standard thresholds but is locked from Phase 1 operator brand-fit ack
2026-05-20; Phase 4 adds zero new sizes/weights) — non-blocking.

Next: /gsd-plan-phase 4 (preferences preserved at .plan-phase-preferences.md
auto-deletes when consumed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 07:47:10 +02:00
266aa95235 docs(04): UI-SPEC.md status approved — 5/6 PASS + 1 FLAG non-blocking (dim 4 inherited type scale)
UI-checker verdict: APPROVED. Dimension breakdown:
- 1 Copywriting: PASS (17-key matrix inherited + locked; zero new copy)
- 2 Visuals: PASS (no new screen; dark-logo is stroke binding change)
- 3 Color: PASS (Loom palette inherited; semantic accents declared)
- 4 Typography: FLAG (8 sizes / 4 weights exceed standard thresholds but
  correctly captured as Phase 1-locked inherited from operator brand-fit
  ack 2026-05-20; Phase 4 adds zero new sizes/weights) — non-blocking
- 5 Spacing: PASS (all multiples of 4; locked; no new values)
- 6 Registry Safety: PASS (vanilla DOM + DOMParser; no shadcn; no third-party)

Three checker observations addressed:
1. `?url` → `?raw` bundling: correctly preserves @crxjs auto-WAR (SVG
   content stays in JS bundle as string literal vs base64 data URL)
2. A17.8 sub-check update: concrete enough (raw-SVG-source string-search
   for `currentColor` + `viewBox='0 0 32 32'`); optional A17.8a/A17.8b split
   well-described
3. Dark-mode contrast: deep-indigo stroke on madder-orange wrapper is
   readable; operator empirical checkpoint (acceptance criterion #6) is
   the designated gate for WCAG ratio judgment

Implementation contract = 5 file edits + 6 acceptance criteria. Planner can
now use UI-SPEC as design context for the visual-polish Phase 4 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 07:46:34 +02:00
55cefbaa32 docs(04): UI design contract — thin scope (dark-logo currentColor strategy)
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>
2026-05-20 22:48:06 +02:00
74ac8ac342 docs(04): preserve plan-phase preferences captured pre-UI-SPEC exit
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>
2026-05-20 22:42:13 +02:00
f14c605dcd docs(state): record phase 4 context session — Phase 4 discuss-phase complete
state.record-session CLI bug recurred (status flipped completed because
23/23 known plans done). Restored: status=ready_to_plan.

Phase 4 CONTEXT.md at:
  .planning/phases/04-harden-clean-up-optional/04-CONTEXT.md

5 D-P4-* decisions locked:
- D-P4-01 Full Phase 4 scope (all 4 ROADMAP SC + ~10 of 12 deferred items;
  excludes rrweb v2 + programmatic SW-RAM)
- D-P4-02 Audit P1 all three (#11 fetch + #14 nav URL + #15 rrweb timestamps)
- D-P4-03 Both visual polish items (cursor visibility + dark-logo)
- D-P4-04 Alpha tester integration user-handled out-of-band (proceed independently)
- D-P4-05 ROADMAP backfill in scope (docs hygiene)

Next: /gsd-plan-phase 4 (note: UI-SPEC gate may trip on dark-logo work;
suggest --skip-ui or generate thin UI-SPEC for visual polish surface).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:17:59 +02:00
8b31fbe3de docs(04): capture phase context — discuss-phase complete (5 D-P4-* locked decisions; full Phase 4 scope) .planning/phases/04-harden-clean-up-optional/04-CONTEXT.md .planning/phases/04-harden-clean-up-optional/04-DISCUSSION-LOG.md 2026-05-20 22:16:57 +02:00
d02b41b7c7 docs(phase-03): VERIFICATION + Phase 3 closure markers — verdict PASSED (5/5 ROADMAP + 9/9 SPEC §10)
Phase 3 verifier returned human_needed with 2 verification items:
1. §10 #9 RAM ceiling — genuinely non-automatable (Page.metrics page-realm only
   per RESEARCH Pitfall 2; SW heap unreachable in MV3 without research budget)
2. ROADMAP/STATE marker flips — orchestrator-owned per worktree protocol

Item 2 resolved automatically via gsd-sdk phase.complete:
- ROADMAP Phase 3 marker flipped [x]
- STATE.md completed_phases: 2 → 3; percent recalculated 75
- REQUIREMENTS.md REQ-rrweb-dom-buffer + REQ-user-event-log flipped Complete

Item 1 OVERRIDDEN to VERIFIED based on user explicit ack 2026-05-20:
- A32 best-effort scaffolding (page-realm Page.metrics with explicit "page-realm
  only" diagnostic; ~1.82 MB at harness baseline; harness self-leak detector)
- chrome://memory-internals operator instructions preserved in human_verification
  for spot-checks
- Alpha distribution build covers real-world cross-profile RAM observation
- D-P3-04 charter explicitly authorized best-effort + operator/alpha path
- Analogous to Phase 2 T5 override per saved memory
  feedback-trust-harness-over-manual-uat.md
- Programmatic SW-context measurement via chrome.devtools Memory API deferred
  to Phase 4 hardening (in 03-VERIFICATION.md Forward-Looking Deferred Items)

VERIFICATION.md frontmatter: status flipped human_needed → passed.
overrides_applied: 4 (3 from executor 03-05 + 1 for §10 #9 closure).

STATE.md body refreshed: Phase 3 marked COMPLETE with citations + Phase 4
character clarified (optional; milestone v1 may close at Phase 3); body
completed_plans bumped 18 → 23 (CLI bug auto-fix; 14 + 4 + 5 = 23).

PROJECT.md Validated section evolved: Phase 3 section added with REQ-rrweb-dom-buffer
+ REQ-user-event-log + §10 sweep entries; Active section restructured to show
Phase 4 backlog (12 deferred items) with milestone v1 close option called out.

Phase 3 closure: 5/5 plans landed; UAT harness 29→33 GREEN (A29 A30 A31 A32);
vitest 171/171 GREEN preserved; Tier-1 FORBIDDEN_HOOK_STRINGS 12; bundle gates
6/6 PASS; src/content/index.ts UNMODIFIED per D-P3-02 charter literal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 21:36:06 +02:00
4de6b3b9ae docs(phase-03): update tracking after wave 5 — 03-05 GREEN (§10 sweep VERIFICATION + REQ marker flips) .planning/ROADMAP.md 2026-05-20 21:25:04 +02:00
1642a73067 docs(03-05): SUMMARY — Phase 3 closure aggregator (9/9 SPEC §10 sweep + REQUIREMENTS markers + 12 Phase 4 deferred items)
- Phase 3 Wave 5 plan: pure documentation synthesis + verification gate execution
- 3/3 tasks complete: Task 1 pre-checkpoint bundle gates 6/6 PASS; Task 2 03-VERIFICATION.md
  (204 lines; 9-criterion §10 scorecard + 3 T5 overrides + 1 human_verification + 12-row deferred
  items table); Task 3 REQUIREMENTS.md REQ-rrweb-dom-buffer + REQ-user-event-log flipped Complete
- STATE.md + ROADMAP.md NOT modified per parallel-executor worktree protocol (auto-strip rule;
  orchestrator owns those writes post Wave 5 merge per Phase 1 + Phase 2 closure precedent)
- 1 Rule-3 deviation documented: STATE.md/ROADMAP.md scope adapted to worktree mode
- vitest 171/171 GREEN preserved; UAT 33/33 GREEN on second consecutive run (first hit
  pre-existing A29 zip-mtime race-condition flake documented in 03-02 + 03-03 SUMMARYs;
  routed to Phase 4 hardening as deferred item row 1)
- Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12 entries; pre-checkpoint bundle gates 6/6 PASS
- Saved memories cited first-class: feedback-trust-harness-over-manual-uat.md (3 T5 overrides),
  feedback-pre-checkpoint-bundle-gates.md (Cross-Cutting Gates row), feedback-no-unilateral-
  scope-reduction.md (worktree protocol honored)
- Phase 4 backlog seeded: 12-row Forward-Looking Deferred Items in 03-VERIFICATION.md
  including A29 cs-injection-world re-target + parallel-vitest flake + rrweb v2 upgrade +
  programmatic per-target RAM + REQ-password-confidentiality v2 candidate (conditional) +
  audit P1 polish + 5 minor backlog items

Self-Check: PASSED — all files exist; both task commits found (a63a821 + 71081aa);
worktree base verified at 041c4d4258.
2026-05-20 21:24:08 +02:00
71081aa8c2 docs(03-05): Task 3 — REQUIREMENTS.md REQ marker flips (Phase 3 closure)
- REQ-rrweb-dom-buffer: [ ] → [x] with Plan 03-01 A29 GREEN closure block (4 EventType-enum
  checks; T5 override per saved memory feedback-trust-harness-over-manual-uat.md)
- REQ-user-event-log: [ ] → [x] with Plan 03-02 A30 GREEN closure block (5 UserEvent.type
  presence checks via cs-injection-world pattern; T5 override)
- Traceability table: both REQs flipped Pending → Complete 2026-05-20 with citation
- Closure footer appended with Phase 3 sweep summary (3 T5 overrides for §10 #4/#5/#8 PARTIAL;
  1 human_verification for §10 #9 RAM per D-P3-04; UAT 29 → 33 GREEN)
- REQ-password-confidentiality unchanged (Out of Scope v1 per D-P3-02 charter; PARTIAL
  via A31 covers existing minimum at src/content/index.ts:82)

Per parallel-executor protocol: STATE.md + ROADMAP.md NOT modified (auto-strip on merge;
orchestrator owns those writes post Wave 5 merge). REQUIREMENTS.md propagates via merge.
2026-05-20 21:19:38 +02:00
a63a821172 docs(03-05): Task 2 — 03-VERIFICATION.md (9/9 SPEC §10 sweep; 3 T5 overrides + 1 human_verification)
- Aggregates Plan 03-01..04 empirical evidence + Phase 1 + Phase 2 closure citations
- Frontmatter: status=passed, score=9/9, overrides_applied=3, human_verification=1 entry
- 9-criterion scorecard with Phase + Plan + commit citations
- T5 overrides applied for §10 #4 (A29 cc13f31), #5 (A30 116432a), #8 PARTIAL (A31 34b36fb)
  per saved memory feedback-trust-harness-over-manual-uat.md + D-P3-02 charter
- human_verification entry for §10 #9 RAM per D-P3-04 + RESEARCH Pitfall 2 (Page.metrics
  page-realm only); operator chrome://memory-internals instructions verbatim;
  A32 informational scaffolding GREEN (commit 8c94bd5; page-realm 1.82 MB observed)
- Cross-Cutting Gates table: 6 rows incl. vitest 171/171 + UAT 33/33 + Tier-1 grep 12
  + pre-checkpoint bundle gates 6/6 (Task 1 results) + tsc + Phase-3-surface as-any/ts-ignore
- 12 Forward-Looking Deferred Items incl. A29 zip-mtime race-condition flake (Phase 4 candidate)
- Pre-checkpoint bundle gates (Task 1) PASSED 6/6 standard inventory:
  Gate 1 build=0; Gate 2 SW CSP=1 setimmediate exception; Gate 3 SW Node-globals=0;
  Gate 4 DOM-globals typeof-guarded; Gate 5 sw-bundle-import=2/2 GREEN;
  Gate 6 FORBIDDEN_HOOK_STRINGS=13/13 GREEN; Gate 7 i18n+build=57/57 GREEN

Per parallel-executor protocol: STATE.md + ROADMAP.md NOT modified (orchestrator owns).
REQUIREMENTS.md REQ marker flips ship in Task 3 (next commit; propagates via merge).
2026-05-20 21:18:09 +02:00
041c4d4258 docs(phase-03): update tracking after wave 4 — 03-04 GREEN (A32 §10 #9 RAM scaffolding; UAT 33/33) .planning/ROADMAP.md 2026-05-20 21:07:09 +02:00
c508a91af2 docs(03-04): Plan 04 SUMMARY — A32 RAM scaffolding (33/33 GREEN; host-side Page.metrics; D-P3-04 best-effort)
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
2026-05-20 21:06:18 +02:00
450f43ebf0 docs(phase-03): update tracking after wave 3 — 03-03 GREEN (A31 §10 #8 PARTIAL; UAT 32/32) .planning/ROADMAP.md 2026-05-20 20:49:25 +02:00
773e0350ad docs(03-03): Plan 03 SUMMARY — A31 password-filter PARTIAL (32/32 GREEN; cs-injection-world + A31.4 defense-in-depth)
Plan 03-03 closure SUMMARY documenting A31 GREEN end-to-end with 5/5
checks under the cs-injection-world pattern + A31.4 defense-in-depth
control-sentinel-PRESENT orthogonal-channel check (Rule 2 critical
addition).

Empirical contract literally satisfied:
- userEvents.length=1
- sentinel-containing count=0 (proves src/content/index.ts:82 fired)
- password-targeting count=0 (same filter via orthogonal path)
- control-containing count=1 (proves the listener IS alive — A31.2/A31.3
  absences are NOT vacuously satisfied)

vitest 171/171 GREEN preserved; Tier-1 FORBIDDEN_HOOK_STRINGS unchanged
at 12 entries; src/content/index.ts UNMODIFIED (verification-only
charter literally honored); UAT count 31 → 32 GREEN.

Deviations documented inline:
- Rule 3 (blocking architectural misassumption): cs-injection-world
  adaptation — plan's document.querySelector on harness page would
  have been tautological (chrome-extension:// has no content script
  per Plan 03-02 finding)
- Rule 2 (critical functionality addition): A31.4 defense-in-depth
  control-sentinel-PRESENT check (T-03-03-04 strict mitigation)

Pre-existing A29 zip-mtime race-condition flake disclosed (per
Plan 03-02 SUMMARY) — 3 base runs showed 2/3 PASS, 1/3 FAIL with
no Plan 03-03 changes applied; deferred to Plan 03-05 + Phase 4
hardening per CLAUDE.md SCOPE BOUNDARY rule.
2026-05-20 20:48:07 +02:00
de398347e0 docs(phase-03): update tracking after wave 2 — 03-02 GREEN (A30 event-log; UAT 31/31) .planning/ROADMAP.md 2026-05-20 20:00:37 +02:00
66678798f1 docs(03-02): Plan 02 SUMMARY — A30 event-log verification (31/31 GREEN; cs-injection-world fix)
- 7-check A30 contract empirically verified end-to-end across all 5
  UserEvent.type literal values (click, input, navigation, js_error,
  network_error); userEvents.length=5; type counts all = 1.
- UAT 30 -> 31 GREEN; vitest 171/171 preserved; Tier-1
  FORBIDDEN_HOOK_STRINGS unchanged at 12 (13/13 unit-gate sub-tests).
- 2 deviations documented:
  - Rule 3 — Blocking — chrome-extension:// URLs not covered by
    `<all_urls>` (MV3 match-pattern spec); page-world fetch never
    reaches the ISOLATED-world window.fetch wrapper. Fixed by opening
    a fresh https://example.com probe tab + chrome.scripting.execute
    Script(world:'ISOLATED'). Rides production surfaces only;
    FORBIDDEN_HOOK_STRINGS impact = 0.
  - Rule 1 — Bug — history.pushState destroys Puppeteer CDP execution
    context. Fixed by popstate dispatch (functionally equivalent for
    the production wiring at src/content/index.ts:111).
- One latent A29 issue surfaced (A29 "passed" via iana.org leftover
  data, not the harness page) — flagged for Plan 03-05 deferred-items
  + Phase 4 hardening; not in scope for Plan 03-02.
- cs-injection-world pattern reusable for Plan 03-03 (password sentinel)
  and any future page-world-event-log verification.
2026-05-20 19:59:39 +02:00
72bbb8044b docs(phase-03): update tracking after wave 1 — 03-01 GREEN (A29 rrweb DOM verification; UAT 30/30) .planning/ROADMAP.md 2026-05-20 19:21:42 +02:00
dc57f5cfc0 docs(03-01): complete A29 rrweb DOM verification plan — SUMMARY
- 2/2 plan tasks completed (c02914d + cc13f31).
- UAT harness 29 → 30 GREEN; vitest 171/171 preserved.
- Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12.
- REQ-rrweb-dom-buffer empirically verified through real Chrome +
  rrweb's already-shipped record() wiring + GET_RRWEB_EVENTS bridge +
  the assembled zip's rrweb/session.json content.
- A29 events.length=4; event types {2, 3, 4} (Meta + FullSnapshot +
  IncrementalSnapshot — all 3 required surfaces empirically present).
- Worktree mode: STATE.md / ROADMAP.md NOT modified per parallel-
  executor protocol (orchestrator owns those writes after all
  worktree agents in the wave complete).
2026-05-20 19:20:39 +02:00
5892371eae chore(03): state.begin-phase — mark Phase 3 executing
- Status: ready_to_execute → Executing Phase 03
- Current focus → Phase 03 (spec-10-smoke-verification-dom-event-log-verification)
- Current Position → Phase: 03 / Plan: 1 of 5
- Branch created: gsd/phase-03-spec-10-smoke-verification-dom-event-log-verification

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:07:34 +02:00
de3f14722f docs(03): plan-phase closure — checker WARNING resolved + preferences consumed + state synced
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>
2026-05-20 19:06:00 +02:00
b3bfbf4a8d feat(03): plans 01-05 — Phase 3 SPEC §10 smoke + DOM/event-log verification
5 plans across 5 waves (Wave 2 sequential per RESEARCH Pitfall 6 file overlap):
- 03-01 Wave 1: rrweb DOM verification harness extension (A29; REQ-rrweb-dom-buffer; §10 #4)
- 03-02 Wave 2: event-log verification harness extension (A30; REQ-user-event-log; §10 #5)
- 03-03 Wave 3: §10 #8 password-filter PARTIAL verification (A31; D-P3-02 charter)
- 03-04 Wave 4: §10 #9 RAM ceiling best-effort + Page.metrics scaffolding (A32; D-P3-04)
- 03-05 Wave 5: §10 sweep VERIFICATION.md + REQUIREMENTS/ROADMAP/STATE marker flips
  (REQ-install-clean + REQ-rrweb-dom-buffer + REQ-user-event-log)

Each plan has:
- frontmatter (wave + depends_on + files_modified + autonomous + requirements + tags + must_haves)
- tasks with mandatory <read_first> + <acceptance_criteria> + concrete <action>
- <threat_model> block per security gate
- Validation map row(s) added to 03-VALIDATION.md (10 tasks total)

Expected UAT growth: 29/29 → 33/33 GREEN (A29-A32 + 03-05 docs).
Expected vitest baseline preserved: 171/171.
Expected Tier-1 FORBIDDEN_HOOK_STRINGS: 12 (A29+ ride production surfaces only).

ROADMAP.md Phase 3 entry replaces "Plans: TBD" with full 5-plan list.
VALIDATION.md status: planner_filled (nyquist_compliant: true; wave_0_complete: true).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 19:01:21 +02:00
6af952700b docs(03): pattern map — 4 exact analogs from Plan 02-04 + Phase 1+2 VERIFICATION precedents .planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-PATTERNS.md 2026-05-20 18:27:57 +02:00
ab8b0eec37 docs(phase-03): add validation strategy — verification-only phase; infra inherited from Phase 1+2 .planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-VALIDATION.md 2026-05-20 18:21:18 +02:00
2c477c3f6d docs(03): research phase domain — Approach B verification + 4 scoped questions resolved
Phase 3 RESEARCH.md addresses the 4 user-scoped research questions:

  Q1 puppeteer.Page.metrics() reliable for SW context?  →  NO (page-realm only;
     SW is a separate Puppeteer target). Scaffolding viable per D-P3-04 with
     explicit diagnostic copy; not authoritative for §10 #9. (Verified via
     pptr.dev/api/puppeteer.page.metrics + Puppeteer issue #7536.)

  Q2 rrweb 2.0.0-alpha.4 testing patterns?  →  Structural EventType enum grep
     (FullSnapshot=2 + IncrementalSnapshot=3 + Meta=4) on rrweb/session.json
     from latest archive. Matches "records without errors" charter literally;
     simpler than rrweb's own assertDomSnapshot MHTML diff. (Verified via
     node_modules/@rrweb/types/dist/index.d.ts grep.)

  Q3 rrweb v2 stable release status / alpha.4 safety?  →  Stable v2 has NOT
     shipped; npm `latest` tag still points at 2.0.0-alpha.4 (2023). Newest
     alpha is 2.0.0-alpha.20 (2026-02-03) with breaking NodeType import-site
     change. alpha.4 pin is safe for Phase 3 verification (9 closed plans +
     29/29 UAT GREEN). Phase 4 upgrade research correctly deferred per
     D-P3-03. (Verified via `npm view rrweb dist-tags`.)

  Q4 New chrome.* patterns for §10?  →  None required. Existing 29-assertion
     harness already covers all §10 surfaces: A24 (blob: URL via
     chrome.downloads.onCreated), A28 (screenshot.png set-equality), A26 +
     A27 (meta.json + multi-tab urls strict). Operator chrome://memory-internals
     remains §10 #9 canonical per D-P3-04.

Plan structure (D-P3-01: 5 atomic plans):
  - 03-01: rrweb DOM verification (assertA29 structural; probe HTML form+table+modal)
  - 03-02: event-log verification (assertA30; Puppeteer page.click/type + grep)
  - 03-03: §10 #8 PARTIAL via password-filter sentinel-absence (D-P3-02)
  - 03-04: §10 #9 best-effort + optional Page.metrics scaffolding (D-P3-04)
  - 03-05: §10 #1-#9 sweep VERIFICATION.md aggregator (Phase 2 frontmatter template)

Tier-1 FORBIDDEN_HOOK_STRINGS expected to stay at 12 entries (A29+ ride
production surfaces only). No new dependencies. Approach B template from
Plan 02-04 is direct precedent.
2026-05-20 18:19:27 +02:00
0d2bc74dae docs(state): record phase 3 UI-SPEC session — null-spec approved 6/6 dimensions
state.record-session CLI bug (same as previous turn): flipped status:completed
+ percent:100 since 18/18 currently-known plans are done. Restored:
status:ready_to_plan, percent:50 (2/4 phases truly complete).

UI-SPEC.md at:
  .planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-UI-SPEC.md
Verifier APPROVED with 6/6 dimensions PASS (null-spec correctly applied —
inherited Phase 1 design system locked read-only; probe-page conventions
scoped to internal Puppeteer fixtures).

Next: /gsd-plan-phase 3 (preferences preserved at .plan-phase-preferences.md
auto-deletes when consumed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 18:06:09 +02:00
03d4b3343c docs(03): UI design contract — null-spec for verification-only phase
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>
2026-05-20 18:04:00 +02:00
6b52d111f8 docs(03): preserve plan-phase preferences captured pre-UI-SPEC exit
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>
2026-05-20 18:00:08 +02:00