docs(04-06): complete visual polish + dark-logo decoupling — D-P4-03 closed (UAT 36/36 GREEN; 188/188 vitest with #9/#10 flake tolerated; operator re-confirmed 2026-05-26)

Plan 04-06 closure — the most ceremony-heavy plan in Phase 4: 3 planner
passes + 2 plan-checker passes + 4 task commits + 1 /gsd-debug fix cycle
+ this closure commit. D-P4-03 (locked, 04-CONTEXT.md) CLOSED — both
visual polish items: (a) cursor visibility verification + (b) dark-surface
logo contrast.

Closure trail:
  6a989e8 mis-diagnosed strict-meta-json deferred-items entry
  b59bd24 re-plan iter-1 — correct false jsdom premise + back-patch lines
  deb68df re-plan-checker iter-1 — ITERATE-NEEDED (2 BLOCKER)
  f3baa3a re-plan iter-2 — real A35 + corrected 184/184 baseline
  48c7053 re-plan-checker iter-2 — PASSED (0B + 0W + 3 cosmetic-advisories)
  f0b88d4 Task 1 — Wave 0 RED inline-SVG source-contract + cursor pin
  c416143 Task 2 — Wave 1 GREEN SVG+welcome.ts+globals.d.ts
  3f8e31a Task 3 — A35 driver + A17.8 narrowed + back-patch + correction
  d66cbf6 Task 4 artifact — operator-empirical screenshot harness
  (Task 4 first operator empirical: TWEAK verdict 2026-05-26)
  a8bcc17 debug-fix — decouple via --mks-mark-stroke + A35.5 sub-check
  (Task 4 re-empirical: CONFIRMED FIXED 2026-05-26)
  THIS    closure (SUMMARY + STATE.md + ROADMAP.md + debug archive)

Key deliverables:
- mokosh-mark.svg stroke="#181b2a" -> stroke="currentColor"
- welcome.ts ?url/<img> -> ?raw/DOMParser/replaceChildren inline-<svg>
- globals.d.ts *.svg?raw ambient decl
- src/shared/tokens.css NEW --mks-mark-stroke = var(--mks-linen-50) in :root
  (NOT overridden in .dark — theme-independent brand-component token)
- src/welcome/welcome.css .welcome-hero__mark rewired to --mks-mark-stroke
- NEW A35 host-side harness (5 sub-checks incl. A35.5 light+dark equality
  decouple-proof) at tests/uat/lib/harness-page-driver.ts
- A17.8 honestly narrowed to SOURCE-BUNDLING only; points to A35
- tests/welcome/inline-svg.test.ts (3 source-contract tests)
- tests/build/cursor-visibility.test.ts (1 regression pin)
- scripts/04-06-welcome-hero-screenshots.mjs (reproducible artifact)
- 01-07-SUMMARY back-patch (5 stale lines flipped; 4 historical left)
- deferred-items.md mis-diagnosis correction

Baselines preserved:
- vitest 188/188 GREEN (most recent 187/188 with 04-CONTEXT #9/#10
  webm-remux flake; passes in isolation; tolerated per Task 2 gate)
- UAT 36/36 GREEN; FORBIDDEN_HOOK_STRINGS unchanged at 12
- Pre-checkpoint bundle gates 6/6 PASS at both checkpoint + re-checkpoint
- All 4 ROADMAP SC CLOSED; D-P4-03 CLOSED

Phase 4 progress: 6/8 -> 7/8 (Plan 04-07 NEXT).

SUMMARY: .planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md
Debug session archived: .planning/debug/resolved/04-06-dark-mode-mark-decouple.md
This commit is contained in:
2026-05-26 13:14:41 +02:00
parent a8bcc17822
commit c790c6a8b3
4 changed files with 407 additions and 14 deletions

View File

@@ -300,7 +300,7 @@ finalized at plan time):
- [x] 04-04-PLAN.md — A33 SW state persistence: **spike-first Wave 0 SPIKE FAILED 2026-05-21** (videoSize=8505 bytes vs 100KB floor; offscreen RAM-only `segments: Blob[]` at src/offscreen/recorder.ts:91 does NOT survive 5-min SW idle + Puppeteer CDP `worker.close()`; corrupt WebM per ffprobe). **REFUTED-architecture 2026-05-22 via debug session-2 (commit `4ea1bbb`):** root cause is canvas.captureStream invisible-canvas throttling (Chrome bug 653548), NOT architectural; segments survived SW kill structurally (POST-KILL probe count=3). Plan 04-04 SUMMARY amended at `c1501e7` with the REFUTED-architecture verdict + Plan 04-08 insertion authorization. ROADMAP SC #1 reframed as test-methodology issue (NOT architectural); IndexedDB persistence plan-fix REJECTED (would not have closed SC #1 because segments are not the problem, frames are). - [x] 04-04-PLAN.md — A33 SW state persistence: **spike-first Wave 0 SPIKE FAILED 2026-05-21** (videoSize=8505 bytes vs 100KB floor; offscreen RAM-only `segments: Blob[]` at src/offscreen/recorder.ts:91 does NOT survive 5-min SW idle + Puppeteer CDP `worker.close()`; corrupt WebM per ffprobe). **REFUTED-architecture 2026-05-22 via debug session-2 (commit `4ea1bbb`):** root cause is canvas.captureStream invisible-canvas throttling (Chrome bug 653548), NOT architectural; segments survived SW kill structurally (POST-KILL probe count=3). Plan 04-04 SUMMARY amended at `c1501e7` with the REFUTED-architecture verdict + Plan 04-08 insertion authorization. ROADMAP SC #1 reframed as test-methodology issue (NOT architectural); IndexedDB persistence plan-fix REJECTED (would not have closed SC #1 because segments are not the problem, frames are).
- [x] 04-08-PLAN.md — A33 methodology reframe + harness assertion: **CLOSED 2026-05-22** via debug session-2 verdict (canvas-captureStream invisible-source throttling root cause); HTMLVideoElement.captureStream replaces canvas.captureStream in installFakeDisplayMedia() with SYNC install + LAZY first-frame contract; spike re-run produces videoSize=1_797_178 bytes (1.8 MB; vs 8505 baseline); A33 lands per original Plan 04-04 Wave 1 spec under SKIP_LONG_UAT env-gate; UAT 33 -> 34 GREEN. **ROADMAP SC #1 CLOSED.** - [x] 04-08-PLAN.md — A33 methodology reframe + harness assertion: **CLOSED 2026-05-22** via debug session-2 verdict (canvas-captureStream invisible-source throttling root cause); HTMLVideoElement.captureStream replaces canvas.captureStream in installFakeDisplayMedia() with SYNC install + LAZY first-frame contract; spike re-run produces videoSize=1_797_178 bytes (1.8 MB; vs 8505 baseline); A33 lands per original Plan 04-04 Wave 1 spec under SKIP_LONG_UAT env-gate; UAT 33 -> 34 GREEN. **ROADMAP SC #1 CLOSED.**
- [x] 04-05-PLAN.md — A34 fetch + XHR network_error empirical: **CLOSED 2026-05-22.** assertA34 + driveA34 + 3-site orchestrator wiring; cs-injection-world `fetch(404)` + `XMLHttpRequest(404)` from a probe tab; host-side asserts 2 `network_error` entries with `meta.status === 404`. Skip-mode UAT 34 -> 35/35 GREEN (A34 real; all 6 checks PASS). Plan 04-01 P1 #11 Request-narrow fix validated end-to-end (fetch `target` = real URL, not `[object Request]`). **ROADMAP SC #2 CLOSED.** Full-mode 35/35 gate observed a pre-existing Plan 04-08 A33 SAVE-ack flake (A33.1 false; video buffer survived at 1.56 MB) — A34 SKIPPED-not-reached in that run but unaffected; A33 flake routed to /gsd-debug. - [x] 04-05-PLAN.md — A34 fetch + XHR network_error empirical: **CLOSED 2026-05-22.** assertA34 + driveA34 + 3-site orchestrator wiring; cs-injection-world `fetch(404)` + `XMLHttpRequest(404)` from a probe tab; host-side asserts 2 `network_error` entries with `meta.status === 404`. Skip-mode UAT 34 -> 35/35 GREEN (A34 real; all 6 checks PASS). Plan 04-01 P1 #11 Request-narrow fix validated end-to-end (fetch `target` = real URL, not `[object Request]`). **ROADMAP SC #2 CLOSED.** Full-mode 35/35 gate observed a pre-existing Plan 04-08 A33 SAVE-ack flake (A33.1 false; video buffer survived at 1.56 MB) — A34 SKIPPED-not-reached in that run but unaffected; A33 flake routed to /gsd-debug.
- [ ] 04-06-PLAN.md — Dark-logo currentColor + cursor visibility verification + 01-07-SUMMARY back-patch (UI-SPEC; operator empirical ack) - [x] 04-06-PLAN.md — Dark-logo currentColor + cursor visibility verification + 01-07-SUMMARY back-patch (UI-SPEC; operator empirical ack): **CLOSED 2026-05-26** via operator re-empirical confirmation "Confirmed fixed — close Plan 04-06". D-P4-03 (both visual polish items) CLOSED. Multi-iteration ceremony: 3 planner passes + 2 checker passes + 1 /gsd-debug fix cycle. Key deliverables: (1) SVG stroke recolor (`stroke="currentColor"`) + welcome.ts `?raw`/DOMParser/replaceChildren inline-SVG injection (no `<img>`, no innerHTML) + globals.d.ts `*.svg?raw` ambient decl; (2) NEW brand-component token `--mks-mark-stroke = var(--mks-linen-50)` in :root (NOT overridden in `.dark`) — decoupled the welcome-hero mark from the theme-flipping semantic `--mks-fg-inverse` token (abstraction error surfaced via Task 4 operator empirical TWEAK; routed via /gsd-debug per `feedback-gsd-ceremony-for-fixes.md`; fix at `a8bcc17`); (3) NEW A35 host-side harness with 5 sub-checks including A35.5 light+dark equality decouple-proof (UAT 35 → 36 GREEN); (4) tests/welcome/inline-svg.test.ts (3 tests) + tests/build/cursor-visibility.test.ts (1 test) — vitest 184 → 188 GREEN; (5) 01-07-SUMMARY back-patch (5 stale 'deferred to Phase 5' framing lines flipped, 4 historical commit-description lines left); (6) deferred-items.md mis-diagnosis correction (04-CONTEXT #9/#10 parallel-vitest flake, NOT strict-meta-json). FORBIDDEN_HOOK_STRINGS unchanged at 12; 6/6 bundle gates PASS. SUMMARY: .planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md.
- [ ] 04-07-PLAN.md — Phase 4 closure aggregator + ROADMAP backfill (D-P4-05) + v1 milestone close prep - [ ] 04-07-PLAN.md — Phase 4 closure aggregator + ROADMAP backfill (D-P4-05) + v1 milestone close prep
## Progress ## Progress
@@ -313,4 +313,4 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5.
| 1. Stabilize video pipeline | 14/14 | **CLOSED 2026-05-20** via gsd-verifier audit GREEN (17/17 must-haves; commit 586836f); all markers flipped | Functional contract closed 2026-05-19 via Plan 01-13 harness PASS; design/brand contract closed 2026-05-20 via Plan 01-12 brand-fit ack; welcome-tab contract closed 2026-05-20 via Plan 01-10 cycle-2 operator ack "All good" + 5 inter-cycle debug fixes | | 1. Stabilize video pipeline | 14/14 | **CLOSED 2026-05-20** via gsd-verifier audit GREEN (17/17 must-haves; commit 586836f); all markers flipped | Functional contract closed 2026-05-19 via Plan 01-13 harness PASS; design/brand contract closed 2026-05-20 via Plan 01-12 brand-fit ack; welcome-tab contract closed 2026-05-20 via Plan 01-10 cycle-2 operator ack "All good" + 5 inter-cycle debug fixes |
| 2. Stabilize export pipeline | 0/4 | Plans landed 2026-05-20 (4 plans: Wave 0 RED → Wave 1 Blob URL + meta.urls parallel → Wave 2 harness + operator checkpoint); execution pending | - | | 2. Stabilize export pipeline | 0/4 | Plans landed 2026-05-20 (4 plans: Wave 0 RED → Wave 1 Blob URL + meta.urls parallel → Wave 2 harness + operator checkpoint); execution pending | - |
| 3. SPEC §10 smoke + DOM/event-log verification | 0/TBD | Not started (absorbed Phase-2 DOM verification per 2026-05-20 re-phasing; ~2-3 plans) | - | | 3. SPEC §10 smoke + DOM/event-log verification | 0/TBD | Not started (absorbed Phase-2 DOM verification per 2026-05-20 re-phasing; ~2-3 plans) | - |
| 4. Harden + clean up (optional) | 5/8 | In Progress| | | 4. Harden + clean up (optional) | 7/8 | In Progress (Plan 04-06 closed D-P4-03 — both visual polish items: cursor visibility verification + dark-surface logo contrast via --mks-mark-stroke brand-component token decoupling; operator re-empirical confirmed 2026-05-26) | |

View File

@@ -3,15 +3,15 @@ gsd_state_version: 1.0
milestone: v2.0.0 milestone: v2.0.0
milestone_name: milestone milestone_name: milestone
status: executing status: executing
stopped_at: "Completed 04-05-PLAN.md (A34 fetch+XHR network_error empirical; ROADMAP SC #2 CLOSED; skip-mode UAT 34->35/35 GREEN; full-mode bailed at pre-existing Plan 04-08 A33 SAVE-ack flake — A34 SKIPPED-not-reached but verified by skip-mode)" stopped_at: "Completed 04-06-PLAN.md (D-P4-03 CLOSED — both visual polish items: cursor visibility verification + dark-surface logo contrast via --mks-mark-stroke brand-component token decoupling; A35 host-side harness with 5 sub-checks including A35.5 light+dark equality decouple-proof; UAT 35->36 GREEN; vitest 184->188; multi-iteration ceremony: 3 planner passes + 2 checker passes + 1 /gsd-debug fix cycle; operator re-empirical confirmed 2026-05-26)"
last_updated: "2026-05-22T10:43:22.622Z" last_updated: "2026-05-26T11:08:13.425Z"
last_activity: 2026-05-22 last_activity: 2026-05-26
progress: progress:
total_phases: 4 total_phases: 4
completed_phases: 3 completed_phases: 3
total_plans: 31 total_plans: 31
completed_plans: 28 completed_plans: 29
percent: 90 percent: 93
--- ---
# Project State # Project State
@@ -28,12 +28,31 @@ no server, no password leaks.
## Current Position ## Current Position
Phase: 04 (harden-clean-up-optional) — EXECUTING Phase: 04 (harden-clean-up-optional) — EXECUTING
Phase 4 of 4 (Hardening — optional) — Plans 04-01..04-05 + 04-08 closed (6/8); 2 plans remain: 04-06 (visual polish; operator empirical) + 04-07 (closure aggregator). ROADMAP SC #1 + SC #2 both CLOSED. Phase 4 of 4 (Hardening — optional) — Plans 04-01..04-06 + 04-08 closed (7/8); 1 plan remains: 04-07 (Phase 4 closure aggregator + v1 milestone close prep). ROADMAP SC #1 + SC #2 both CLOSED; D-P4-03 CLOSED via Plan 04-06.
Plan: 7 of 7 (04-06 NEXT) Plan: 8 of 8 (04-07 NEXT)
Status: Ready to execute Status: Ready to execute
Last activity: 2026-05-22 Last activity: 2026-05-26
Progress: [█████████] 90% Progress: [█████████] 93%
### Plan 04-06 closure (2026-05-26)
- Dark-logo contrast strategy + cursor visibility verification landed end-to-end; D-P4-03 (locked, 04-CONTEXT.md) CLOSED — both visual polish items.
- 5 atomic commits (4 task + 1 debug-fix): `f0b88d4` (Task 1 — Wave 0 RED inline-SVG source-contract + cursor-visibility regression pin) → `c416143` (Task 2 — Wave 1 GREEN: SVG stroke recolor + welcome.ts ?raw/DOMParser/replaceChildren + globals.d.ts ambient decl) → `3f8e31a` (Task 3 — A35 live-DOM inline-SVG harness check + A17.8 raw-source update + 01-07-SUMMARY back-patch + deferred-items correction) → `d66cbf6` (Task 4 artifact — operator-empirical screenshot harness scripts/04-06-welcome-hero-screenshots.mjs) → operator-empirical TWEAK verdict 2026-05-26 → /gsd-debug session → `a8bcc17` (debug-fix — decouple welcome-hero mark stroke via NEW `--mks-mark-stroke` brand-component token in :root + A35.5 light+dark equality decouple-proof sub-check) → operator re-empirical CONFIRMED FIXED 2026-05-26.
- SUMMARY: `.planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md`.
- **Multi-iteration ceremony**: this was the most ceremony-heavy plan in Phase 4 — 3 planner passes (`6a989e8` mis-diagnosis → `b59bd24` re-plan iter-1 → `f3baa3a` re-plan iter-2) + 2 plan-checker passes (`deb68df` iter-1 ITERATE-NEEDED → `48c7053` iter-2 PASSED) + 4 task commits + 1 /gsd-debug fix cycle (debug session at `.planning/debug/resolved/04-06-dark-mode-mark-decouple.md`). Ceremony was a necessary cost — the brand-component vs semantic token abstraction error only surfaced when the operator saw the dark theme. Lesson encoded: when a checkpoint is operator-empirical, the planner should either front-load the brand-component token or accept a /gsd-debug fix cycle as part of the plan budget.
- **Dark-logo currentColor strategy**: src/shared/brand/mokosh-mark.svg root `<svg>` `stroke="#181b2a"``stroke="currentColor"` (1-attribute change; 13 children unchanged). src/welcome/welcome.ts line 46 `import markUrl from '../shared/brand/mokosh-mark.svg?url'``import markSvg from '../shared/brand/mokosh-mark.svg?raw'`; populateMark body rewritten to use DOMParser + replaceChildren inline-`<svg>` injection (no `<img>`, no innerHTML — MV3 CSP discipline T-04-06-01 mitigation). globals.d.ts ambient `declare module '*.svg?raw' { const raw: string; export default raw; }` block appended.
- **Theme decoupling via `--mks-mark-stroke` brand-component token**: NEW token `--mks-mark-stroke: var(--mks-linen-50)` in src/shared/tokens.css universal `:root` block (line ~131) — CRUCIALLY NOT overridden in `.dark, [data-theme="dark"]` block; stays linen-50 on every surface. src/welcome/welcome.css line 72 `.welcome-hero__mark { color: var(--mks-fg-inverse); }``color: var(--mks-mark-stroke);`. SVG remains untouched — stroke="currentColor" cascade plumbing identical; only the wrapper's color source changed. Both light + dark themes now resolve `computedStroke` to `rgb(250, 247, 241)` (linen-50) — crisp linen-on-madder grid icon in both themes.
- **NEW A35 host-side harness assertion** (5 sub-checks): driveA35(page, browser, extensionId) at tests/uat/lib/harness-page-driver.ts opens welcome.html as a real Puppeteer tab via `browser.newPage()` + `page.goto('chrome-extension://${extensionId}/src/welcome/welcome.html', { waitUntil: 'domcontentloaded' })` + `waitForSelector('.welcome-hero__mark svg', ...)`. Extracted `a35Probe(welcomePage, dark)` helper toggles `data-theme="dark"` on documentElement (+ requestAnimationFrame wait) + reads live DOM. 5 sub-checks: A35.1 svg present; A35.2 stroke="currentColor"; A35.3 getComputedStyle().stroke resolved non-default (linen-50); A35.4 no `<img>` in slot; A35.5 (NEW from debug session) light.computedStroke === dark.computedStroke === "rgb(250, 247, 241)" (linen-50 decouple-proof). welcomePage.close() in finally block ensures no tab leak. 3-site orchestrator wiring at tests/uat/harness.test.ts. UAT 35 → 36 GREEN.
- **A17.8 honestly narrowed**: tests/uat/extension-page-harness.ts A17.8 sub-check replaced `data:image/svg+xml` data-URL grep with raw-source grep (`stroke="currentColor"` + `viewBox="0 0 32 32"` in jsText). Explanatory comment block explicitly disavows live-DOM coverage and points to A35 for runtime behavior proof.
- **Source-contract unit tests**: tests/welcome/inline-svg.test.ts (3 it() blocks; node-env file-read + string-assert per tests/i18n/manifest-i18n.test.ts; NO DOM library; NO `@vitest-environment jsdom`). Tests A/B/C pin SVG recolor + welcome.ts ?raw/DOMParser/no-innerHTML + globals.d.ts ambient decl. tests/build/cursor-visibility.test.ts (1 it() block) pins literal `cursor: 'always'` at recorder.ts:285 (Plan 01-09 opportunistic). vitest 184 → 188 GREEN; most recent full-suite run 187/188 with the 04-CONTEXT #9/#10 webm-remux ffprobe-timeout flake tolerated (passes 5/5 in isolation per Task 2 VITEST GATE LOGIC behavior-based rule).
- **01-07-SUMMARY back-patch**: 5 stale 'deferred to Phase 5' framing lines (22, 47, 82, 135, 205) flipped to 'shipped opportunistically Plan 01-09 at recorder.ts:285; verified Phase 4 Plan 04-06'; 4 historical commit-description lines (40, 89, 109, 110) LEFT unchanged. Narrative internally consistent.
- **deferred-items.md mis-diagnosis correction**: the prior 'strict-meta-json fails on a clean tree' entry (commit `6a989e8`) rewritten to describe the real root cause — the 04-CONTEXT #9/#10 parallel-vitest ffprobe-timeout flake family + the true clean baseline of 184/184 GREEN.
- **Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12** (Plan 04-06 adds no `__MOKOSH_UAT__`-gated symbols — DOMParser is standard web platform API; `?raw` is normal production Vite import; `*.svg?raw` ambient decl is type-only at build).
- **Pre-checkpoint bundle gates 6/6 PASS** at BOTH the first Task 4 checkpoint AND the post-debug re-checkpoint. SW chunk byte-identical at the Plan 04-05 boundary (Plan 04-06 modifies only welcome + tokens.css :root + tests + globals.d.ts — none affect SW chunk shape).
- **Cosmetic advisories ADV-2A/B/C** from re-plan-checker iter-2 all addressed: ADV-2A (banner string) LEAVE per decision (auto-count via `total = drivers.length + 1` carries actual count); ADV-2B (SKIP_PROD_REBUILD=0 rationale) corrected prose in SUMMARY; ADV-2C (A35-appended-LAST safety) documented in SUMMARY threat-surface section.
- **Debug session archived**: `.planning/debug/04-06-dark-mode-mark-decouple.md` moved to `.planning/debug/resolved/04-06-dark-mode-mark-decouple.md` at plan closure (resolved end-to-end via `a8bcc17` + operator re-empirical 2026-05-26).
- **Plan 04-07 (Phase 4 closure aggregator + v1 milestone close prep) is now the ONLY remaining Phase 4 plan.** All 4 ROADMAP success criteria CLOSED; D-P4-03 CLOSED via this plan.
### Plan 04-05 closure (2026-05-22) ### Plan 04-05 closure (2026-05-22)
@@ -166,6 +185,7 @@ Progress: [█████████░] 90%
| Phase 04 P03 | 46min | 2 tasks | 2 files | | Phase 04 P03 | 46min | 2 tasks | 2 files |
| Phase 04 P04 | ~25min | - tasks | - files | | Phase 04 P04 | ~25min | - tasks | - files |
| Phase 04 P05 | ~45min | 2 tasks | 3 files | | Phase 04 P05 | ~45min | 2 tasks | 3 files |
| Phase 04 P06 | ~4 days end-to-end (3 planner + 2 checker + 1 /gsd-debug fix cycle; ~6h executor wall-clock across 4 task commits + 1 debug-fix commit) | 4 tasks (3 autonomous + 1 operator empirical with TWEAK→fix→CONFIRMED arc) | 11 modified + 4 created |
## Accumulated Context ## Accumulated Context
@@ -223,6 +243,10 @@ current work:
- [Phase 04-04]: stopServiceWorker(browser, extensionId) helper landed at tests/uat/lib/harness-page-driver.ts (verbatim Chrome devrel canonical pattern — Puppeteer >=22.1.0 worker.close()). Persisting artifact retained even though Task 2 BLOCKED — helper is non-empty positive scaffolding for the eventual IndexedDB-persistence plan-fix verification harness (A33-equivalent reuse). Pattern: spike-FAILED forensic-evidence — commit the spike script (tests/uat/spike-a33-sw-persistence.ts; 202 lines) AND the persisting helpers (not delete) so future plan-fix can re-run the exact reproducible test that revealed the failure. - [Phase 04-04]: stopServiceWorker(browser, extensionId) helper landed at tests/uat/lib/harness-page-driver.ts (verbatim Chrome devrel canonical pattern — Puppeteer >=22.1.0 worker.close()). Persisting artifact retained even though Task 2 BLOCKED — helper is non-empty positive scaffolding for the eventual IndexedDB-persistence plan-fix verification harness (A33-equivalent reuse). Pattern: spike-FAILED forensic-evidence — commit the spike script (tests/uat/spike-a33-sw-persistence.ts; 202 lines) AND the persisting helpers (not delete) so future plan-fix can re-run the exact reproducible test that revealed the failure.
- [Phase ?]: [Phase 04-08]: Methodology reframe — video-file MediaStream replaces canvas.captureStream throttling per debug session-2 verdict; A33 lands; UAT 33->34; ROADMAP SC #1 CLOSED 2026-05-22 (videoSize=1.8MB vs 8505 baseline); architecture UNCHANGED. - [Phase ?]: [Phase 04-08]: Methodology reframe — video-file MediaStream replaces canvas.captureStream throttling per debug session-2 verdict; A33 lands; UAT 33->34; ROADMAP SC #1 CLOSED 2026-05-22 (videoSize=1.8MB vs 8505 baseline); architecture UNCHANGED.
- [Phase ?]: [Phase 04-05]: A34 fetch+XHR network_error empirical lands (ROADMAP SC #2 CLOSED). cs-injection-world fetch(404)+XMLHttpRequest(404) from an example.com probe tab via chrome.scripting.executeScript ISOLATED; driveA34 host-side JSZip-parses logs/events.json + asserts 2 network_error entries (fetch+XHR) with meta.status===404. Plan 04-01 P1 #11 Request-narrow fix validated end-to-end — fetch entry target carries real URL not [object Request]. Skip-mode UAT 34->35/35 GREEN (A34 real, all 6 checks PASS). Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12; bundle gates 6/6 PASS; vitest 184/184 preserved. Full-mode 35/35 gate observed a pre-existing Plan 04-08 A33 SAVE-ack flake (A33.1 false; 1.56MB video buffer survived) — A34 SKIPPED-not-reached in that run, unaffected; A33 flake routed to /gsd-debug. - [Phase ?]: [Phase 04-05]: A34 fetch+XHR network_error empirical lands (ROADMAP SC #2 CLOSED). cs-injection-world fetch(404)+XMLHttpRequest(404) from an example.com probe tab via chrome.scripting.executeScript ISOLATED; driveA34 host-side JSZip-parses logs/events.json + asserts 2 network_error entries (fetch+XHR) with meta.status===404. Plan 04-01 P1 #11 Request-narrow fix validated end-to-end — fetch entry target carries real URL not [object Request]. Skip-mode UAT 34->35/35 GREEN (A34 real, all 6 checks PASS). Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12; bundle gates 6/6 PASS; vitest 184/184 preserved. Full-mode 35/35 gate observed a pre-existing Plan 04-08 A33 SAVE-ack flake (A33.1 false; 1.56MB video buffer survived) — A34 SKIPPED-not-reached in that run, unaffected; A33 flake routed to /gsd-debug.
- [Phase 04-06]: Dark-logo currentColor + inline-SVG injection + `--mks-mark-stroke` brand-component token decoupling + cursor-visibility verification + NEW A35 host-side harness with 5 sub-checks (incl. A35.5 light+dark equality decouple-proof). D-P4-03 (locked, 04-CONTEXT.md) CLOSED — both visual polish items. Multi-iteration ceremony: 3 planner passes + 2 plan-checker passes + 1 /gsd-debug fix cycle (debug session resolved at `a8bcc17`). 5 task commits: `f0b88d4` (Wave 0 RED test+pin) → `c416143` (Wave 1 GREEN SVG+welcome.ts+globals.d.ts) → `3f8e31a` (A35 driver + A17.8 raw-source narrowing + 01-07-SUMMARY back-patch + deferred-items correction) → `d66cbf6` (operator-empirical screenshot harness) → `a8bcc17` (debug-fix --mks-mark-stroke decoupling + A35.5 sub-check). Operator empirical Task 4 TWEAK → /gsd-debug per `feedback-gsd-ceremony-for-fixes.md` → CONFIRMED FIXED 2026-05-26.
- [Phase 04-06]: Brand-component token vs semantic token abstraction pattern established. `--mks-fg-inverse` is for theme-flipping text foreground (where the surface flips); the welcome-mark wrapper sits on theme-INDEPENDENT madder-600, so a theme-coupled stroke was the wrong abstraction. Fix: introduce a NEW brand-component token `--mks-mark-stroke = var(--mks-linen-50)` in the universal `:root` block + do NOT override in `.dark, [data-theme="dark"]` + rewire `.welcome-hero__mark { color: var(--mks-mark-stroke); }`. SVG remains untouched (currentColor cascade plumbing identical; only wrapper's color source changed). Both themes now resolve computedStroke to rgb(250, 247, 241) (linen-50). The `--mks-fg-inverse` token continues serving its proper role (e.g. src/popup/style.css:39 — theme-flipping surface; LEFT untouched). Pattern: brand-component tokens for theme-independent surfaces; semantic tokens for theme-flipping surfaces.
- [Phase 04-06]: Live-DOM host-side harness assertion pattern against welcome.html — NEW A35 driver (driveA35 in tests/uat/lib/harness-page-driver.ts) opens welcome.html as a real Puppeteer tab via browser.newPage() + page.goto(chrome-extension://${extensionId}/src/welcome/welcome.html) + waitForSelector('.welcome-hero__mark svg', ...). Extracted a35Probe(welcomePage, dark) helper toggles documentElement.setAttribute('data-theme', 'dark'|'light') (+ requestAnimationFrame wait) for multi-theme probing. 5 CheckRecords incl. A35.5 light+dark equality decouple-proof. welcomePage.close() in finally block. Pattern reusable for any future welcome.html DOM contract + multi-theme live-DOM check.
- [Phase 04-06]: Operator-empirical screenshot harness pattern — per `feedback-trust-harness-over-manual-uat.md`, the operator only judges aesthetics from /tmp/04-06-welcome-hero-{light,dark}.png produced via Puppeteer's Emulation.setEmulatedMedia (prefers-color-scheme: dark). Automation does the rest. scripts/04-06-welcome-hero-screenshots.mjs (194 lines) is the reproducible artifact. The operator's TWEAK verdict on the dark cascade was the surface that catches the abstraction error a planner-checker cannot — encoded the cost-of-empirical-checkpoints lesson.
### Pending Todos ### Pending Todos
@@ -247,11 +271,13 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity ## Session Continuity
Last session: 2026-05-22T10:43:10.855Z Last session: 2026-05-26T11:08:13Z
Stopped at: Completed 04-05-PLAN.md (A34 fetch+XHR network_error empirical; ROADMAP SC #2 CLOSED; skip-mode UAT 34->35/35 GREEN; full-mode bailed at pre-existing Plan 04-08 A33 SAVE-ack flake — A34 SKIPPED-not-reached but verified by skip-mode) Stopped at: Completed 04-06-PLAN.md (D-P4-03 CLOSED — both visual polish items: cursor visibility verification + dark-surface logo contrast via --mks-mark-stroke brand-component token decoupling; A35 host-side harness with 5 sub-checks including A35.5 light+dark equality decouple-proof; UAT 35->36 GREEN; vitest 184->188; multi-iteration ceremony: 3 planner passes + 2 checker passes + 1 /gsd-debug fix cycle; operator re-empirical confirmed 2026-05-26)
Resume file: None Resume file: None
Prior session: 2026-05-21T08:22:59.958Z — /gsd-pause-work saved Phase 4 execution-ready handoff (dbcf482); Phase 4 plans validated iter-2 PASSED + 3 cosmetic advisories fixed Prior session: 2026-05-22T10:43:10.855Z — Completed 04-05-PLAN.md (A34 fetch+XHR network_error empirical; ROADMAP SC #2 CLOSED; skip-mode UAT 34->35/35 GREEN; full-mode bailed at pre-existing Plan 04-08 A33 SAVE-ack flake — A34 SKIPPED-not-reached but verified by skip-mode)
Earlier session: 2026-05-21T08:22:59.958Z — /gsd-pause-work saved Phase 4 execution-ready handoff (dbcf482); Phase 4 plans validated iter-2 PASSED + 3 cosmetic advisories fixed
Earlier session: 2026-05-20T12:54:42.000Z — /gsd-pause-work saved Phase 2 execution-ready handoff (a440c7d); Phase 1 closed end-to-end via verifier audit GREEN (586836f); alpha distribution shipped (dist-archives/mokosh-build-2026-05-20-6dbed91.zip) Earlier session: 2026-05-20T12:54:42.000Z — /gsd-pause-work saved Phase 2 execution-ready handoff (a440c7d); Phase 1 closed end-to-end via verifier audit GREEN (586836f); alpha distribution shipped (dist-archives/mokosh-build-2026-05-20-6dbed91.zip)
Earlier session: 2026-05-20T12:00:00.000Z — Plan 01-10 closed via cycle-2 operator ack "All good" + 5 inter-cycle debug fixes + brand-rename follow-up Earlier session: 2026-05-20T12:00:00.000Z — Plan 01-10 closed via cycle-2 operator ack "All good" + 5 inter-cycle debug fixes + brand-rename follow-up
Even earlier: 2026-05-20T08:00:00.000Z — Plan 01-12 closed via Wave 7 operator brand-fit ack 2026-05-20 'all good' (SUMMARY f319c7d; 147/147 vitest + 21/21 UAT GREEN) Even earlier: 2026-05-20T08:00:00.000Z — Plan 01-12 closed via Wave 7 operator brand-fit ack 2026-05-20 'all good' (SUMMARY f319c7d; 147/147 vitest + 21/21 UAT GREEN)

View File

@@ -0,0 +1,367 @@
---
phase: 04-harden-clean-up-optional
plan: 06
subsystem: ui
tags:
- visual-polish
- dark-logo-contrast
- ui-spec
- currentColor
- inline-svg
- dom-parser
- cursor-visibility-verification
- charter-d-p4-03
- operator-empirical
- mks-mark-stroke-token
- theme-decoupling
- live-dom-harness
- a35
- a17-8-narrowed
- 01-07-summary-back-patch
- gsd-debug-routed-fix
- multi-iteration-ceremony
requires:
- phase: 01-stabilize-video-pipeline
provides: "Plan 01-09 opportunistically shipped `cursor: 'always'` at src/offscreen/recorder.ts:285 (D-01 getDisplayMedia constraints object). Plan 01-10 bundled `mokosh-mark.svg` via Vite `?url` import + populateMark `<img>` injection (the LIGHT-surface contrast baseline). Plan 01-12 D-04 Loom palette (`--mks-linen-50` = #faf7f1; `--mks-madder-600` = #b2543d; `--mks-ink-900` = #181b2a) + `--mks-fg-inverse` semantic token that flips linen-50 -> ink-900 in `.dark, [data-theme=\"dark\"]`. Plan 04-06 inverts the welcome-mark strategy from `?url`/`<img>` (Plan 01-10) to `?raw`/inline-`<svg>`/currentColor + introduces a NEW brand-component token `--mks-mark-stroke` to decouple from the theme-flipping semantic token."
- plan: 04-03
provides: "A29 cs-injection-world rewrite established the host-side driver pattern (driveA29/30/31/32/33/34) — A35 follows this pattern verbatim (host-side; build CheckRecord[] directly; no page.evaluate(window.__mokoshHarness) wrapper)."
- plan: 04-05
provides: "driveA34 host-side driver template + 3-site orchestrator wiring pattern (import + driveA{N}Wrapped + drivers-array push). A35 inserts immediately after A34 with the same wiring shape."
- plan: 04-08
provides: "A33 SW state persistence baseline (UAT 34/34 GREEN; vitest 184/184 GREEN; Tier-1 FORBIDDEN_HOOK_STRINGS at 12). Plan 04-06 flips UAT 35 -> 36 (A35 + A35.5) and vitest 184 -> 188 (3 inline-svg.test.ts + 1 cursor-visibility.test.ts)."
provides:
- "Dark-logo contrast strategy landed end-to-end: SVG stroke recolor (`stroke=\"currentColor\"`) + welcome.ts `?raw` import + DOMParser-based inline-SVG injection (no `<img>`, no innerHTML, no eval) + globals.d.ts `*.svg?raw` ambient module declaration + theme-INDEPENDENT brand-component token `--mks-mark-stroke = var(--mks-linen-50)` in :root (NOT overridden in `.dark, [data-theme=\"dark\"]`)."
- "Empirical closure of D-P4-03 (locked, 04-CONTEXT.md): BOTH (a) cursor visibility AND (b) dark-surface logo contrast. (a) verified by tests/build/cursor-visibility.test.ts file-grep regression pin (already-shipped Plan 01-09 literal at recorder.ts:285) + 01-07-SUMMARY.md back-patch (5 stale 'deferred to Phase 5' lines flipped). (b) verified by 4-layer coverage: source-contract unit tests (3 RED -> GREEN in inline-svg.test.ts) + A17.8 raw-source bundling check (narrowed honestly) + NEW A35 live-DOM host-side harness assertion (5 sub-checks including the A35.5 light+dark equality decouple-proof) + operator-empirical Puppeteer screenshots (TWEAK -> /gsd-debug fix -> CONFIRMED FIXED 2026-05-26)."
- "NEW host-side A35 harness assertion at tests/uat/lib/harness-page-driver.ts — opens welcome.html as a real Puppeteer tab (`browser.newPage()` + `page.goto('chrome-extension://${extensionId}/src/welcome/welcome.html')`), lets `populateMark()` run at DOMContentLoaded, reads the LIVE injected `.welcome-hero__mark svg` element + `getComputedStyle().stroke` to prove the `currentColor` cascade actually resolves. Now probes BOTH light + dark themes (data-theme=\"dark\" toggle on documentElement) via an extracted `a35Probe(welcomePage, dark)` helper and asserts A35.5: `light.computedStroke === dark.computedStroke === 'rgb(250, 247, 241)'` (linen-50). This is the genuine automated proof of the runtime injection + cascade + theme decoupling — closes iter-2 BLOCKER 1."
- "A17.8 sub-check honestly narrowed at tests/uat/extension-page-harness.ts: replaced the prior `data:image/svg+xml` data-URL grep with a raw-source grep (`stroke=\"currentColor\"` + `viewBox=\"0 0 32 32\"` in jsText). A17.8 is now a SOURCE-BUNDLING check only — disavows live-DOM coverage and explicitly points to A35 for runtime behavior proof. No more fictitious delegation."
- "NEW brand-component token `--mks-mark-stroke = var(--mks-linen-50)` in src/shared/tokens.css universal :root block (line ~131) + `.welcome-hero__mark { color: var(--mks-mark-stroke); }` rewire at src/welcome/welcome.css line 72. CRUCIALLY this token is NOT overridden in the `.dark, [data-theme=\"dark\"]` block — stays linen-50 on every surface. The previous semantic-token coupling (`color: var(--mks-fg-inverse)`) was the wrong abstraction (the welcome-mark wrapper is theme-INDEPENDENT madder-600, not a theme-flipping inverse surface)."
- "Operator-empirical screenshot harness at scripts/04-06-welcome-hero-screenshots.mjs (194 lines) — Puppeteer script that loads dist/ as an unpacked extension, opens welcome.html, and captures `/tmp/04-06-welcome-hero-light.png` + `/tmp/04-06-welcome-hero-dark.png` (dark via `Emulation.setEmulatedMedia` `prefers-color-scheme: dark`). Reproducible artifact for any future re-shoot if the strategy ever needs re-validation."
- "01-07-SUMMARY.md back-patch — 5 stale 'deferred to Phase 5' framing lines (22, 47, 82, 135, 205) flipped to 'shipped opportunistically Plan 01-09; verified Phase 4 Plan 04-06'; 4 historical commit-description lines (40, 89, 109, 110) LEFT unchanged. The Phase 1 closure record is now internally consistent with the reality (cursor:'always' was shipped 2026-05-19 in Plan 01-09 commit `a2dfc8c` co-land, not deferred)."
- "deferred-items.md correction — the prior 'tests/build/strict-meta-json-validation.test.ts fails on a clean tree' mis-diagnosis (commit 6a989e8) rewritten to describe the real root cause: the pre-existing 04-CONTEXT.md in-scope items #9 (parallel-vitest Tier-1-build-step race; ~1/5 runs) + #10 (2 ffprobe/ffmpeg vitest flakes) non-deterministic family. The true clean baseline is 184/184 GREEN (188/188 after Plan 04-06)."
affects:
- "ROADMAP Plan 04-06 row flipped `[ ]` -> `[x]` with closure annotation. Phase 4 progress 6/8 -> 7/8."
- "D-P4-03 (charter, locked) — both visual polish items closed via Plan 04-06."
- "Plan 04-07 (Phase 4 closure aggregator) is now unblocked — only remaining Phase 4 plan."
- "UAT harness driver count 35 -> 36 (A35 appended LAST after A34; A35 itself contains 5 sub-checks now, including A35.5 decouple-proof). Per-plan increments: Plan 04-08 (33->34, A33), Plan 04-05 (34->35, A34), Plan 04-06 (35->36, A35)."
- "vitest baseline 184/184 -> 188/188 (+4: 3 inline-svg.test.ts source-contract tests + 1 cursor-visibility.test.ts regression pin). The pre-existing 04-CONTEXT #9/#10 parallel-vitest flake family remains tolerable in full-suite runs (currently 187/188 in the most recent post-fix run — webm-remux flake fired; passes in isolation; documented)."
- "Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12 — Plan 04-06 introduces no `__MOKOSH_UAT__`-gated symbols (DOMParser is a standard web platform API; `?raw` is a normal production Vite import; `*.svg?raw` ambient decl is type-only)."
- "Pre-checkpoint bundle gates 6/6 PASS unchanged from Plan 04-05 baseline (SW chunk byte-identical at boundary: Plan 04-06 modifies only src/welcome/* + src/shared/brand/* + src/shared/tokens.css :root token + tests/* + globals.d.ts; SW chunk shape unchanged)."
- "Cross-plan provenance: this plan went through 3 planner passes + 2 plan-checker passes + 1 /gsd-debug fix cycle — the most ceremony-heavy plan in Phase 4. Full audit trail: `6a989e8` (mis-diagnosis) -> `b59bd24` (re-plan iter-1) -> `deb68df` (checker iter-1 ITERATE-NEEDED) -> `f3baa3a` (re-plan iter-2) -> `48c7053` (checker iter-2 PASSED) -> `f0b88d4`/`c416143`/`3f8e31a` (Tasks 1-3) -> `d66cbf6` (screenshot harness) -> operator TWEAK -> `a8bcc17` (debug fix) -> operator CONFIRMED FIXED 2026-05-26."
tech-stack:
added: []
patterns:
- "Inline-SVG via Vite `?raw` import + DOMParser + replaceChildren (NEW for Plan 04-06): `import markSvg from '../shared/brand/mokosh-mark.svg?raw';` returns the SVG source as a string at build time; `new DOMParser().parseFromString(markSvg, 'image/svg+xml').documentElement` produces a live `<svg>` element; `slot.replaceChildren(svg)` injects it into the DOM. Inline `<svg>` inherits parent CSS `color` (W3C SVG2 §13.3); `stroke=\"currentColor\"` resolves to that inherited color. NEVER use `innerHTML` (MV3 CSP discipline; T-04-06-01 mitigation). Replaces the Plan 01-10 `?url`/`<img>` pattern for ANY future case where the brand mark must adapt to its surface via CSS cascade."
- "Brand-component token vs semantic token abstraction (NEW for Plan 04-06): when a CSS color needs to be theme-INDEPENDENT on a theme-INDEPENDENT surface, do NOT couple to a semantic token that flips with theme (e.g. `--mks-fg-inverse`). Introduce a dedicated brand-component token (e.g. `--mks-mark-stroke`) in the universal `:root` block and do NOT override it in `.dark, [data-theme=\"dark\"]`. The semantic token is for text-foreground-on-inverse-surface (where the surface flips); the brand-component token is for the canonical brand color on a fixed surface. Established at src/shared/tokens.css :root + src/welcome/welcome.css:72."
- "Live-DOM host-side harness assertion against welcome.html (NEW for Plan 04-06): when a runtime DOM behavior (populateMark injection + currentColor cascade resolution) cannot be verified by a node-env unit test (vitest is `environment: 'node'`; no DOM library) AND cannot be verified by the existing A17 string-grep (detached DOMParser parse of the welcome HTML), open the welcome page as a NEW Puppeteer tab via `browser.newPage()` + `page.goto('chrome-extension://${extensionId}/src/welcome/welcome.html', { waitUntil: 'domcontentloaded' })`. Run `page.waitForSelector(...)` for race-free guard against the readyState branch. Read live DOM via `page.evaluate(...) { ... }` returning a plain-object result. Build CheckRecord[] host-side; close the welcomePage in a finally block. Established at tests/uat/lib/harness-page-driver.ts driveA35; pattern reusable for any future welcome.html DOM contract."
- "Multi-theme live-DOM probe (NEW for Plan 04-06): a single host-side driver probes BOTH light + dark themes by toggling `documentElement.setAttribute('data-theme', 'dark' | 'light')` between two `page.evaluate()` calls (and waiting `requestAnimationFrame()` for the CSS cascade to recompute). Extract the per-theme probe into a helper (e.g. `a35Probe(welcomePage, dark)`) to keep the driver body symmetric. Useful for any future driver that needs to assert theme decoupling or theme symmetry. Established at tests/uat/lib/harness-page-driver.ts driveA35 + a35Probe helper."
- "Operator-empirical screenshot harness pattern (NEW for Plan 04-06): per `feedback-trust-harness-over-manual-uat.md`, when the operator needs to judge aesthetics (e.g. dark-mode contrast), produce light + dark screenshots via Puppeteer's `Emulation.setEmulatedMedia` for `prefers-color-scheme: dark` rather than asking the operator to click through Chrome. Save to `/tmp/<plan>-<scope>-{light,dark}.png` and surface both paths in the checkpoint. Operator only judges the visual artifact; automation does the rest. Established at scripts/04-06-welcome-hero-screenshots.mjs."
key-files:
created:
- "tests/welcome/inline-svg.test.ts (70 lines; 3 it() blocks in `UI-SPEC dark-logo currentColor strategy — source-level contract`; node-env; file-read + string-assert pattern per tests/i18n/manifest-i18n.test.ts; NO DOM library import; NO `@vitest-environment jsdom` directive). Tests: A (mokosh-mark.svg recolor: contains `stroke=\"currentColor\"` + `viewBox=\"0 0 32 32\"`; does NOT contain `stroke=\"#181b2a\"`); B (welcome.ts ?raw + DOMParser + replaceChildren; does NOT contain `?url` or `innerHTML`); C (globals.d.ts ambient `*.svg?raw` decl)."
- "tests/build/cursor-visibility.test.ts (45 lines; 1 it() block; node-env file-grep regression pin for the literal `cursor: 'always'` at src/offscreen/recorder.ts:285 — already shipped opportunistically by Plan 01-09)."
- "scripts/04-06-welcome-hero-screenshots.mjs (194 lines; Puppeteer; loads dist/ as unpacked extension; opens chrome-extension://${extensionId}/src/welcome/welcome.html; captures `/tmp/04-06-welcome-hero-light.png` + `/tmp/04-06-welcome-hero-dark.png` via `Emulation.setEmulatedMedia` `prefers-color-scheme: dark`; prints the resolved `getComputedStyle().stroke` for each theme as diagnostic output). Reproducible verification artifact for the operator-empirical Task 4 checkpoint."
- ".planning/debug/04-06-dark-mode-mark-decouple.md -> RESOLVED, archived to .planning/debug/resolved/ at plan closure. 115 lines; full reasoning checkpoint + symptoms + evidence + resolution; cites the abstraction-error root cause + the --mks-mark-stroke brand-component token fix."
- ".planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md (this file)."
modified:
- "src/shared/brand/mokosh-mark.svg — single-attribute change on root `<svg>` (line 2): `stroke=\"#181b2a\"` -> `stroke=\"currentColor\"`. 12 `<line>` + 1 `<rect>` children inherit stroke from the root and are UNCHANGED (they were verified line-by-line; no other attributes touched). +1 / -1."
- "src/welcome/welcome.ts — line 46 import flipped from `import markUrl from '../shared/brand/mokosh-mark.svg?url';` to `import markSvg from '../shared/brand/mokosh-mark.svg?raw';`. populateMark function body (lines 159-179) rewritten: `<img>` injection REPLACED with DOMParser + replaceChildren inline-SVG injection. Preserves the function signature, the slots query, the altText resolution (`COPY['welcome.hero.mark.alt'] ?? 'Знак Mokosh'`), the filter-pipeline form, and the empty-slot logger.warn fallback. NEVER uses innerHTML (MV3 CSP discipline; T-04-06-01). Docstring (lines 134-158) + comment block (lines 36-45) updated to describe the inline-`<svg>` strategy + cite the UI-SPEC implementation amendment + cite the currentColor cascade requirement. Net +90 / -25 lines."
- "globals.d.ts — appended `declare module '*.svg?raw' { const raw: string; export default raw; }` ambient module declaration block immediately after the existing `*.svg?url` block (lines 34-37). Explanatory comment per project docstring convention cites the UI-SPEC dark-logo strategy + Vite `?raw` asset-as-string semantics. +21 lines."
- "src/shared/tokens.css — added the NEW brand-component token `--mks-mark-stroke: var(--mks-linen-50);` adjacent to `--mks-fg-inverse` in the universal `:root` block (line ~131). CRUCIALLY this token is NOT added to the `.dark, [data-theme=\"dark\"]` block — it stays linen-50 on every surface. Comment in source cites the abstraction-error rationale + the .planning/debug/04-06-dark-mode-mark-decouple.md debug note. SCOPE EXPANSION (authorized by Task 4 TWEAK verdict) — this file was not in Plan 04-06 re-plan iter-2 `files_modified`. +15 lines."
- "src/welcome/welcome.css — line 72 flipped from `color: var(--mks-fg-inverse);` to `color: var(--mks-mark-stroke);` inside the `.welcome-hero__mark` block. The bare `.welcome-hero__mark-img` class selector at line 91 is unchanged (matches `<svg>` and `<img>` identically — no `img.` qualifier). Comment block updated to cite the cascade chain (wrapper color -> inline `<svg>` stroke via currentColor). SCOPE EXPANSION (authorized by Task 4 TWEAK verdict). +11 / -0 lines."
- "tests/uat/extension-page-harness.ts — A17.8 sub-check (lines ~2249-2298) narrowed honestly: replaced `hasInlineDataUrl` (jsText.includes('data:image/svg+xml')) + svgFileUrl regex with a raw-source grep that asserts jsText contains `stroke=\"currentColor\"` AND `viewBox=\"0 0 32 32\"`. Explanatory comment block flipped to state honestly: A17.8 is a SOURCE-BUNDLING check (confirms `?raw` inlined the source string); it does NOT prove live-DOM injection or that the currentColor cascade resolves — that runtime behavior is verified by the NEW host-side assertion A35. Net +25 / -45 lines."
- "tests/uat/lib/harness-page-driver.ts — appended NEW `driveA35` host-side driver function after `driveA34` (+247 lines). Signature: `(page: Page, browser: Browser, extensionId: string) => Promise<AssertionRecord>`. Opens welcome.html via `browser.newPage()` + `page.goto(chrome-extension://${extensionId}/src/welcome/welcome.html, { waitUntil: 'domcontentloaded' })` + `page.waitForSelector('.welcome-hero__mark svg', ...)`. Extracted `a35Probe(welcomePage, dark)` helper that toggles `data-theme=\"dark\"` on documentElement and reads the LIVE injected `.welcome-hero__mark svg` (svgPresent + strokeAttr + computedStroke + imgPresent). 5 CheckRecords: A35.1 svg present (light); A35.2 stroke=\"currentColor\" (light); A35.3 getComputedStyle().stroke is the non-default linen-50 (light); A35.4 no `<img>` in slot; A35.5 (NEW from debug session) light.computedStroke === dark.computedStroke === 'rgb(250, 247, 241)' (linen-50 decouple-proof). welcomePage.close() in finally block ensures no tab leak. Try/catch + result.error mirror driveA33/driveA34."
- "tests/uat/harness.test.ts — 3-site lockstep wiring (+28 lines): (1) `driveA35` added to the import block from './lib/harness-page-driver' (alongside driveA33/driveA34, lines 81-116); (2) `driveA35Wrapped` closure added alongside driveA33Wrapped/driveA34Wrapped (lines 367-372) — captures `handles.browser` + `handles.extensionId`; (3) `{ name: 'A35', drive: driveA35Wrapped }` appended as the LAST entry of the `drivers` array (immediately after the A34 entry, line ~534). Architecture banner string at line 283 left for cosmetic-advisory ADV-2A choice (auto-count via `total = drivers.length + 1` at line 580 carries the actual count). A35 is NOT env-gated."
- ".planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md — surgical back-patch: 5 stale 'deferred to Phase 5' framing lines (22, 47, 82, 135, 205) flipped to 'shipped opportunistically in Plan 01-09 at recorder.ts:285; verified in Phase 4 Plan 04-06'; 4 historical commit-description lines (40, 89, 109, 110) LEFT unchanged (they accurately describe what the Phase-1-closure commits recorded at the time). After the flips, the SUMMARY narrative is internally consistent. Net +10 / -10 lines."
- ".planning/phases/04-harden-clean-up-optional/deferred-items.md — corrected the prior 'strict-meta-json fails on a clean tree' mis-diagnosis (commit 6a989e8). The entry now correctly describes the 04-CONTEXT #9/#10 parallel-vitest ffprobe flake family + states the true clean baseline is 184/184 GREEN (188/188 after Plan 04-06). +2 / -2 lines."
- ".planning/ROADMAP.md — Plan 04-06 row flipped `[ ]` -> `[x]` with closure annotation (D-P4-03 closed; --mks-mark-stroke decoupling + A35.5 light+dark equality; operator-empirical re-confirmed 2026-05-26; UAT 35->36; vitest 184->188). Phase 4 progress table cell updated from `6/8 In Progress (Plan 04-05 closed ROADMAP SC #2)` to `7/8 In Progress (Plan 04-06 closed D-P4-03)`."
- ".planning/STATE.md — Current Position advanced to Plan 7 of 7 (04-07 NEXT). Stopped At updated. Last activity 2026-05-26. Plan 04-06 closure section added. Decisions list appended. Performance metric row added. Progress flipped 90% -> 93% (28/31 -> 29/31)."
decisions:
- "Multi-iteration ceremony — Plan 04-06 went through 3 planner passes + 2 plan-checker passes + 1 /gsd-debug fix cycle. This is the most ceremony-heavy plan in Phase 4 and worth full provenance traceability. Sequence: original plan (BLOCKED at Task 1 on false jsdom premise) -> mis-diagnosis logged at `6a989e8` (`deferred-items.md` entry naming strict-meta-json wrongly; later corrected in this plan's Task 3) -> re-plan iter-1 `b59bd24` (correct false jsdom premise + back-patch lines) -> re-plan-checker iter-1 `deb68df` (ITERATE-NEEDED: 2 BLOCKER — fictitious A17.8 delegation + wrong vitest baseline) -> re-plan iter-2 `f3baa3a` (real A35 + behavior-based vitest gate + 184/184 baseline) -> re-plan-checker iter-2 `48c7053` (PASSED: 0B + 0W + 3 cosmetic-advisories ADV-2A/B/C) -> Tasks 1-3 executed cleanly (`f0b88d4` + `c416143` + `3f8e31a`) -> operator-empirical screenshot harness landed (`d66cbf6`) -> Task 4 operator-empirical checkpoint -> TWEAK verdict (dark cascade flipped icon to ink-900 indigo on madder; lower contrast than light) -> /gsd-debug session (.planning/debug/04-06-dark-mode-mark-decouple.md) -> fix commit `a8bcc17` (decouple via --mks-mark-stroke token + A35.5 light+dark equality sub-check) -> operator re-empirical 2026-05-26: 'Confirmed fixed — close Plan 04-06'. Lesson: when a checkpoint is operator-empirical, the planner should anticipate the abstraction-error vector (semantic vs brand-component tokens) and either (a) front-load the brand-component token into the plan, or (b) accept that a /gsd-debug fix cycle is part of the plan budget."
- "Brand-component token vs semantic token (the debug-session abstraction-error correction) — `--mks-fg-inverse` is a SEMANTIC text-foreground-on-inverse-surface token (`tokens.css :root` line 128 = linen-50; `.dark` line 244 = ink-900). It was the wrong coupling for `.welcome-hero__mark`: the welcome mark sits on a theme-INDEPENDENT madder-600 circle (the wrapper background is fixed; the surface does NOT flip with theme). When the theme flipped, the `currentColor` cascade resolved to ink-900 (indigo) on madder — muddy, low contrast. The fix: introduce a NEW brand-component token `--mks-mark-stroke = var(--mks-linen-50)` in the universal `:root` block + do NOT override it in `.dark, [data-theme=\"dark\"]` + rewire `.welcome-hero__mark` to `color: var(--mks-mark-stroke);`. The SVG remains untouched — `stroke=\"currentColor\"` cascade plumbing identical; only the wrapper's color source changed. Both themes now resolve `computedStroke` to `rgb(250, 247, 241)` (linen-50) — crisp linen-on-madder in both themes. Lesson encoded as a NEW pattern: brand-component tokens for theme-independent surfaces; semantic tokens for theme-flipping surfaces. The `--mks-fg-inverse` token continues to serve its proper role (e.g. src/popup/style.css:39, which IS a theme-flipping surface — LEFT untouched)."
- "A35.5 sub-check (NEW from debug session) — the post-debug A35 driver was strengthened with a 5th sub-check: assert `light.computedStroke === dark.computedStroke === 'rgb(250, 247, 241)'` (linen-50). This is the DECOUPLE-PROOF: a future regression that re-couples the stroke to a theme-flipping token would fail A35.5 even if A35.1-A35.4 all pass (a coupled stroke still injects the inline `<svg>`; it just renders the wrong color in dark). A35.5 is the canonical assertion for any future case where a brand-component must remain theme-independent. The probe pattern (extracted `a35Probe(welcomePage, dark)` helper; toggle data-theme on documentElement; wait requestAnimationFrame; re-read getComputedStyle) is reusable for any future multi-theme live-DOM check."
- "Honest A17.8 narrowing — the iter-1 re-plan claimed 'live-DOM injection is delegated to A17.8 in real Chrome', which iter-1 BLOCKER 1 verified FALSE (A17.8 is 100% string-grep on jsText via a detached DOMParser parse; populateMark() never runs in the harness). The iter-2 fix is the NEW A35 host-side driver (real new harness work); A17.8 is honestly narrowed to a SOURCE-BUNDLING check only. The A17.8 explanatory comment block now disavows live-DOM coverage and explicitly points to A35. No more fictitious delegation. Lesson: when test coverage is split across layers (source/bundling/live-DOM), every assertion's comment block must accurately describe its scope. The 'delegate to X' shorthand is a code smell unless X actually does the work."
- "Behavior-based vitest gate logic (iter-2 BLOCKER 2 resolution) — the iter-1 plan hard-coded 'expected failing test == strict-meta-json-validation.test.ts'. iter-2 BLOCKER 2 verified that test is 8/8 GREEN in isolation AND on a clean tree. The real flake is the 04-CONTEXT #9/#10 parallel-vitest ffprobe-timeout family, which lands non-deterministically on whichever test loses the worker race (observed: tests/background/webm-remux.test.ts). The iter-2 gate logic is behavior-based: 188/188 GREEN -> PASS; 187/1 where the single failing test PASSES in isolation -> tolerated 04-CONTEXT #9/#10 flake, PASS, record the test name + isolation re-run result in SUMMARY; 2+ failures OR a reproducible single failure -> FAIL the gate. The gate hard-codes NO test filename — it distinguishes flake from regression via re-run behavior. Plan 04-06 baseline is 184/184 GREEN; target after this plan is 188/188 (+4 new tests). Lesson: never hard-code a specific failing test name; always gate on behavior."
- "01-07-SUMMARY back-patch — 5 lines flipped (22/47/82/135/205) and 4 lines left (40/89/109/110). The 5 flipped lines carry the genuine stale 'deferred to Phase 5' framing — `cursor: 'always'` actually shipped in Plan 01-09 at recorder.ts:285 (commit `a2dfc8c` co-land 2026-05-19), so the deferral framing was false. The 4 left lines are historical descriptions of what specific Phase-1-closure commits DID (the ROADMAP modification, the 7df72aa commit, the decision-log tag) — those are accurate historical records and must not be rewritten. The classification was verified iter-2 by re-running `grep -nE 'Phase 5|deferred to Phase 5' .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md`. Lesson: when back-patching historical documents, distinguish forward-looking framing (FLIP) from historical commit-content records (LEAVE)."
- "Cosmetic advisory ADV-2A resolution (architecture banner string at harness.test.ts:283) — the plan instructed `Append ', A35' to the architecture banner string`. The banner today reads `...A29, A30, A31, A32)` — A33 and A34 are MISSING from this banner (they were added to the drivers array but the banner was not updated). The auto-count via `total = drivers.length + 1` (line 580) carries the actual count, so the banner is cosmetic display only. Decision: LEAVE the banner alone in Plan 04-06 — adding ', A35' alongside the missing A33/A34 would either understate (', A35' alone is misleading) or scope-creep (', A33, A34, A35' updates 3 plans' worth of cosmetic display). Non-blocking — no gate depends on the banner string. Plan 04-07 (Phase 4 closure aggregator) may opportunistically refresh the banner if it touches that file."
- "Cosmetic advisory ADV-2B resolution (SKIP_PROD_REBUILD=0 rationale) — the plan said `SKIP_PROD_REBUILD=0 is intentional: the harness must rebuild dist-test`. Actually `SKIP_PROD_REBUILD` gates the prod `dist/` build (used by A0 hook-string grep gate); `dist-test/` always rebuilds because `package.json:12` runs `build:test` unconditionally. The actual reason SKIP_PROD_REBUILD=0 is needed is the A0 grep-gate against fresh `dist/`. Decision: keep the command shape `SKIP_PROD_REBUILD=0`, document the corrected rationale here. Executor behavior is unchanged — the command produces the right result. Non-blocking prose-accuracy fix."
- "Cosmetic advisory ADV-2C resolution (race-analysis underspecified in threat model) — the plan's threat model T-04-06 row noted finally-block cleanup but did not explicitly note that A35 is appended LAST in the drivers array. Verified independently safe: drivers run serially (harness.test.ts:542 `for (...)` loop, not Promise.all); A35 is last; A35 does NOT mutate chrome.storage (opening welcome.html does NOT trigger openWelcomeIfFirstInstall — that runs only on chrome.runtime.onInstalled); welcomePage.close() in finally ensures no tab leak. Pollution-of-future-drivers is moot (there are none). Decision: document the safety here in SUMMARY; no design change. Non-blocking documentation clarity."
requirements-completed: [] # Plan 04-06 has no REQ-* requirements (Phase 4 is optional hardening; all v1 REQs covered by Phases 1-3 per ROADMAP). Charter linkage is via the `charter-d-p4-03` tag.
# Metrics
duration: "~4 days end-to-end (3 planner passes + 2 checker passes + 4 task commits + 1 debug fix; sequential foreground with operator empirical pause in the middle)"
completed: 2026-05-26
tasks: 4
files-modified: 11
---
# Phase 04 Plan 06: Dark-Logo Contrast + Inline-SVG Injection + Cursor Visibility Verification + D-P4-03 Closure Summary
**Dark-logo currentColor strategy + theme-independent `--mks-mark-stroke` brand-component token + DOMParser inline-SVG injection (no `<img>`, no innerHTML) + NEW A35 live-DOM host-side harness with 5-sub-check decouple-proof + cursor-visibility regression pin + 01-07-SUMMARY back-patch — closes D-P4-03 (both visual polish items) after 3 planner passes + 2 checker passes + 1 /gsd-debug fix cycle.**
## Performance
- **Duration:** ~4 days end-to-end (executor wall-clock ~6h across 4 task commits + 1 debug-fix commit; operator-empirical pause for the dark-mode aesthetic judgment + debug fix re-confirmation)
- **Started:** 2026-05-22 (executor spawn after re-plan-checker iter-2 PASSED at `48c7053`)
- **Completed:** 2026-05-26 (operator re-empirical confirmation "Confirmed fixed — close Plan 04-06")
- **Tasks:** 4 of 4 plan tasks complete (Tasks 1-3 autonomous + Task 4 operator-empirical UAT with TWEAK -> debug-fix -> CONFIRMED FIXED arc)
- **Files modified:** 11 (3 source files + 4 test files + 2 spec docs + STATE.md + ROADMAP.md) + 1 created script (operator-empirical screenshot harness) + 1 archived debug note
- **Production source changes:** 3 files (src/shared/brand/mokosh-mark.svg + src/welcome/welcome.ts + src/welcome/welcome.css + src/shared/tokens.css — the latter two are SCOPE EXPANSIONS authorized by the Task 4 TWEAK verdict)
## Accomplishments
- **Dark-logo currentColor strategy landed end-to-end** (Task 2 commit `c416143`): SVG stroke recolor (`stroke=\"currentColor\"`) + welcome.ts `?raw` import + DOMParser-based inline-SVG injection (no `<img>`, no innerHTML, no eval) + globals.d.ts `*.svg?raw` ambient module declaration. Inline `<svg>` inherits parent CSS `color` via W3C SVG2 §13.3 cascade; `stroke=\"currentColor\"` resolves to that inherited color. MV3 CSP discipline preserved (T-04-06-01 mitigation).
- **Theme decoupling via `--mks-mark-stroke` brand-component token** (debug-fix commit `a8bcc17`): introduced a NEW brand-component token `--mks-mark-stroke = var(--mks-linen-50)` in the universal `:root` block of src/shared/tokens.css. CRUCIALLY this token is NOT overridden in `.dark, [data-theme=\"dark\"]` — stays linen-50 on every surface. Rewired `.welcome-hero__mark { color: var(--mks-mark-stroke); }` at src/welcome/welcome.css line 72. Both light and dark themes now resolve `computedStroke` to `rgb(250, 247, 241)` (linen-50) — crisp linen-on-madder mark in both themes. The SVG remains untouched.
- **NEW host-side A35 harness assertion** (Task 3 commit `3f8e31a` + debug-fix commit `a8bcc17` strengthening): opens welcome.html as a real Puppeteer tab; lets `populateMark()` run at DOMContentLoaded; reads the LIVE injected `.welcome-hero__mark svg` element + `getComputedStyle().stroke`. 5 CheckRecords: A35.1 svg present; A35.2 stroke=\"currentColor\"; A35.3 getComputedStyle().stroke resolved non-default; A35.4 no `<img>` in slot; A35.5 (NEW from debug) light.computedStroke === dark.computedStroke === 'rgb(250, 247, 241)' (the decouple-proof). welcomePage.close() in finally block; UAT 36/36 GREEN.
- **Honest A17.8 narrowing** (Task 3 commit `3f8e31a`): replaced the `data:image/svg+xml` data-URL grep with a raw-source grep (`stroke=\"currentColor\"` + `viewBox=\"0 0 32 32\"` in jsText). A17.8 is now a SOURCE-BUNDLING check only — the explanatory comment block explicitly disavows live-DOM coverage and points to A35 for runtime behavior proof.
- **Source-contract unit tests** (Task 1 commit `f0b88d4`): tests/welcome/inline-svg.test.ts (3 tests; node-env file-read + string-assert; NO DOM library; NO `@vitest-environment jsdom`). Tests pin source TEXT: SVG recolor + welcome.ts ?raw/DOMParser/replaceChildren/no-innerHTML + globals.d.ts ambient decl. 3 RED today -> 3 GREEN after Task 2.
- **Cursor-visibility regression pin** (Task 1 commit `f0b88d4`): tests/build/cursor-visibility.test.ts (1 test; node-env file-grep) regression-pins the literal `cursor: 'always'` at src/offscreen/recorder.ts:285 (already shipped opportunistically by Plan 01-09). 1 GREEN-on-arrival.
- **01-07-SUMMARY back-patch** (Task 3 commit `3f8e31a`): 5 stale 'deferred to Phase 5' framing lines (22, 47, 82, 135, 205) flipped to 'shipped opportunistically Plan 01-09 at recorder.ts:285; verified Phase 4 Plan 04-06'. 4 historical commit-description lines (40, 89, 109, 110) LEFT unchanged.
- **deferred-items.md mis-diagnosis correction** (Task 3 commit `3f8e31a`): the prior 'strict-meta-json fails on a clean tree' entry (commit 6a989e8) rewritten to describe the real root cause — the 04-CONTEXT #9/#10 parallel-vitest ffprobe flake family. True clean baseline corrected to 184/184 GREEN.
- **Operator-empirical screenshot harness** (commit `d66cbf6`): scripts/04-06-welcome-hero-screenshots.mjs (194 lines) — reproducible Puppeteer script that captures `/tmp/04-06-welcome-hero-{light,dark}.png` via `Emulation.setEmulatedMedia` `prefers-color-scheme: dark`. Re-runnable for any future re-validation.
- **Operator-empirical Task 4 closure**: TWEAK on 2026-05-26 (dark cascade flipped icon to ink-900 indigo on madder; lower contrast than light) -> /gsd-debug session with abstraction-error verdict -> fix commit `a8bcc17` (decouple via --mks-mark-stroke token + A35.5 light+dark equality sub-check) -> operator re-empirical confirmation "Confirmed fixed — close Plan 04-06".
- **D-P4-03 (charter, locked) closed end-to-end**: BOTH (a) cursor visibility AND (b) dark-surface logo contrast. (a) verified by tests/build/cursor-visibility.test.ts + 01-07-SUMMARY back-patch. (b) verified by 4-layer coverage: source-contract unit tests + A17.8 raw-source grep + A35 live-DOM (5 sub-checks including A35.5 decouple-proof) + operator-empirical screenshots.
## Multi-Iteration Ceremony History
Plan 04-06 is the most ceremony-heavy plan in Phase 4 — full provenance traceability:
| Commit | Date | Stage |
|-----------|------------|---------------------------------------------------------------------------------------|
| `6a989e8` | 2026-05-22 | Mis-diagnosed strict-meta-json deferred-items entry (logged out-of-scope from initial executor BLOCK on false jsdom premise) |
| `b59bd24` | 2026-05-22 | Re-plan iter-1 — correct false jsdom premise + stale back-patch lines + baseline |
| `deb68df` | 2026-05-22 | Re-plan-checker iter-1 — ITERATE-NEEDED (2 BLOCKER: fictitious A17.8 delegation + wrong vitest baseline) |
| `f3baa3a` | 2026-05-22 | Re-plan iter-2 — real inline-SVG coverage (NEW A35) + corrected 184/184 baseline |
| `48c7053` | 2026-05-22 | Re-plan-checker iter-2 — PASSED (0 BLOCKER + 0 WARNING + 3 cosmetic-advisories ADV-2A/B/C) |
| `f0b88d4` | 2026-05-26 | Task 1 — Wave 0 RED: inline-SVG source-contract + cursor-visibility regression pin |
| `c416143` | 2026-05-26 | Task 2 — Wave 1 GREEN: dark-logo currentColor + inline-SVG injection |
| `3f8e31a` | 2026-05-26 | Task 3 — A35 live-DOM inline-SVG harness + A17.8 raw-source update + back-patch |
| `d66cbf6` | 2026-05-26 | Operator-empirical screenshot harness (Task 4 artifact) |
| (TWEAK) | 2026-05-26 | Task 4 operator-empirical UAT verdict: dark cascade flipped icon to ink-900 indigo |
| `a8bcc17` | 2026-05-26 | /gsd-debug fix: decouple via `--mks-mark-stroke` token + A35.5 light+dark equality |
| (CONFIRM) | 2026-05-26 | Operator re-empirical: "Confirmed fixed — close Plan 04-06" |
| (THIS) | 2026-05-26 | Closure commit (SUMMARY + STATE.md + ROADMAP.md + debug archive) |
**Provenance lesson:** when a checkpoint is operator-empirical, the planner should either (a) front-load the brand-component token into the plan, or (b) accept that a /gsd-debug fix cycle is part of the plan budget. Plan 04-06's checker iter-2 PASSED verdict was sound on the technical contract (the SVG/welcome.ts/globals.d.ts/A35 strategy was correct); the abstraction-error vector (semantic `--mks-fg-inverse` vs brand-component `--mks-mark-stroke`) only surfaced once the operator saw the dark theme. This is the inherent cost of operator-empirical aesthetics judgments — and is exactly why D-P4-03 was scoped as `autonomous: false`.
## Task Commits
Each plan task was committed atomically with normal git commits + pre-commit hooks (sequential foreground mode; in-line with Plans 04-01 through 04-05 + 04-08 protocol):
1. **Task 1 — Wave 0 RED — inline-SVG source-contract + cursor-visibility regression pin**`f0b88d4` (test). 2 files created: tests/welcome/inline-svg.test.ts (3 it() blocks; 70 lines); tests/build/cursor-visibility.test.ts (1 it() block; 45 lines). 3 RED + 1 GREEN-on-arrival.
2. **Task 2 — Wave 1 GREEN — SVG stroke recolor + welcome.ts ?raw import + populateMark inline injection + globals.d.ts ambient decl**`c416143` (feat). 3 files modified: src/shared/brand/mokosh-mark.svg (+1/-1); src/welcome/welcome.ts (+90/-25); globals.d.ts (+21). 3 source-contract RED flipped to 3 GREEN; vitest 184 -> 188 (full suite 187/188 with the 04-CONTEXT #9/#10 webm-remux flake; isolation 5/5 GREEN — tolerated per Task 2 VITEST GATE LOGIC).
3. **Task 3 — A17.8 raw-source update + NEW A35 live-DOM harness assertion + 01-07-SUMMARY back-patch + deferred-items correction**`3f8e31a` (feat). 5 files modified: tests/uat/extension-page-harness.ts (A17.8 narrowed honestly); tests/uat/lib/harness-page-driver.ts (driveA35 +247 lines; before debug-fix strengthening: 4 sub-checks); tests/uat/harness.test.ts (3-site wiring +28); .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md (5 lines flipped); .planning/phases/04-harden-clean-up-optional/deferred-items.md (mis-diagnosis correction). UAT 35/35 GREEN (A35 added + 4 sub-checks all PASS pre-debug).
4. **Task 4 artifact — Operator-empirical screenshot harness**`d66cbf6` (chore). 1 file created: scripts/04-06-welcome-hero-screenshots.mjs (194 lines). Reproducible Puppeteer script for the operator-empirical checkpoint.
5. **Task 4 operator-empirical UAT — first verdict** — TWEAK (no commit; verdict only). Dark cascade flipped the icon to ink-900 indigo on madder — lower contrast than light. Routed via /gsd-debug per `feedback-gsd-ceremony-for-fixes.md`.
6. **Debug-fix — decouple welcome-hero mark stroke via --mks-mark-stroke**`a8bcc17` (fix). 6 files modified: src/shared/tokens.css (NEW --mks-mark-stroke token in :root; +15); src/welcome/welcome.css (rewire to new token; +11); src/welcome/welcome.ts (comment-only); tests/welcome/inline-svg.test.ts (comment-only); tests/uat/lib/harness-page-driver.ts (A35 strengthening + a35Probe helper extraction + A35.5 light+dark equality decouple-proof); tests/uat/harness.test.ts (comment-only). UAT 36/36 GREEN (A35 now has 5 sub-checks all PASS).
7. **Task 4 operator-empirical UAT — re-verdict** — CONFIRMED FIXED (no commit; verdict only). Operator response 2026-05-26: "Confirmed fixed — close Plan 04-06".
**Plan metadata commit:** to be committed atomically with this SUMMARY + STATE.md + ROADMAP.md + the archived debug session as `docs(04-06): complete dark-logo contrast + cursor-visibility verification + D-P4-03 closure (4 tasks; UAT 36/36 GREEN; operator re-confirmed 2026-05-26)`.
## Files Created/Modified
### Created
- `tests/welcome/inline-svg.test.ts`**CREATED** (70 lines). 3 it() blocks in `describe('UI-SPEC dark-logo currentColor strategy — source-level contract', ...)`. Node-env file-read + string-assert pattern (precedent: tests/i18n/manifest-i18n.test.ts). Tests: A (mokosh-mark.svg recolor: contains `stroke="currentColor"` + `viewBox="0 0 32 32"`; does NOT contain `stroke="#181b2a"`); B (welcome.ts ?raw + DOMParser + replaceChildren; does NOT contain `?url` or `innerHTML`); C (globals.d.ts ambient `*.svg?raw` decl). NO DOM library import; NO `@vitest-environment jsdom` directive (verified by grep -E "^import .*jsdom|@vitest-environment jsdom" returns 0).
- `tests/build/cursor-visibility.test.ts`**CREATED** (45 lines). 1 it() block; node-env file-grep regression pin for `cursor: 'always'` literal at src/offscreen/recorder.ts:285 (shipped opportunistically by Plan 01-09).
- `scripts/04-06-welcome-hero-screenshots.mjs`**CREATED** (194 lines). Puppeteer script; loads dist/ as unpacked extension; opens `chrome-extension://${extensionId}/src/welcome/welcome.html`; captures `/tmp/04-06-welcome-hero-light.png` + `/tmp/04-06-welcome-hero-dark.png` (dark via `Emulation.setEmulatedMedia` `prefers-color-scheme: dark`); prints diagnostic `getComputedStyle().stroke` for each theme.
- `.planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md`**CREATED** (this file).
### Modified
- `src/shared/brand/mokosh-mark.svg`**MODIFIED.** Net +1 / -1. Root `<svg>` (line 2) `stroke="#181b2a"` -> `stroke="currentColor"`. 12 `<line>` + 1 `<rect>` children inherit stroke from the root and are UNCHANGED.
- `src/welcome/welcome.ts`**MODIFIED.** Net +90 / -25. Line 46 import flipped from `?url`/`markUrl` to `?raw`/`markSvg`. populateMark body (lines 159-179) rewritten: `<img>` -> DOMParser + replaceChildren inline-SVG. NEVER uses innerHTML. Preserves function signature, slots query, altText resolution, filter-pipeline form, empty-slot logger.warn fallback. Docstring + comment block updated to describe inline-`<svg>` strategy.
- `globals.d.ts`**MODIFIED.** Net +21. Appended `declare module '*.svg?raw' { const raw: string; export default raw; }` block after the existing `*.svg?url` block. Explanatory comment per project docstring convention.
- `src/shared/tokens.css`**MODIFIED.** Net +15. Added `--mks-mark-stroke: var(--mks-linen-50);` in the universal `:root` block (line ~131), adjacent to `--mks-fg-inverse`. NOT added to `.dark, [data-theme="dark"]` block. Comment cites the abstraction-error rationale + .planning/debug/04-06-dark-mode-mark-decouple.md. SCOPE EXPANSION (authorized by Task 4 TWEAK verdict).
- `src/welcome/welcome.css`**MODIFIED.** Net +11. Line 72 `.welcome-hero__mark { color: var(--mks-fg-inverse); }` -> `color: var(--mks-mark-stroke);`. Comment block updated to cite the cascade chain. SCOPE EXPANSION (authorized by Task 4 TWEAK verdict).
- `tests/uat/extension-page-harness.ts`**MODIFIED.** Net +25 / -45. A17.8 sub-check (lines ~2249-2298) narrowed honestly: replaced `hasInlineDataUrl` + svgFileUrl regex with raw-source grep (`stroke="currentColor"` + `viewBox="0 0 32 32"`). Comment block disavows live-DOM coverage and points to A35.
- `tests/uat/lib/harness-page-driver.ts`**MODIFIED.** Net +247 (Task 3 first land) + strengthening (debug-fix; A35.5 sub-check + a35Probe helper extraction). Appended `driveA35` host-side driver after `driveA34`. Opens welcome.html via `browser.newPage()` + `page.goto(...)` + `waitForSelector('.welcome-hero__mark svg', ...)`. Extracted `a35Probe(welcomePage, dark)` helper toggles `data-theme="dark"` on documentElement + reads live DOM. 5 CheckRecords: A35.1-A35.4 (live-DOM injection + currentColor cascade); A35.5 (light+dark equality decouple-proof). welcomePage.close() in finally; try/catch + result.error mirror driveA33/A34.
- `tests/uat/harness.test.ts`**MODIFIED.** Net +28. 3-site lockstep wiring: import block adds `driveA35,`; wrapped-driver block adds `driveA35Wrapped` closure (capturing `handles.browser` + `handles.extensionId`); drivers-array push appends `{ name: 'A35', drive: driveA35Wrapped }` as the LAST entry. Architecture banner string at line 283 LEFT unchanged (cosmetic-advisory ADV-2A; auto-count via `total = drivers.length + 1` carries actual count).
- `.planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md`**MODIFIED.** Net +10 / -10. 5 stale 'deferred to Phase 5' framing lines (22, 47, 82, 135, 205) flipped to 'shipped opportunistically Plan 01-09 at recorder.ts:285; verified Phase 4 Plan 04-06'. 4 historical commit-description lines (40, 89, 109, 110) LEFT unchanged. After the flips the SUMMARY narrative is internally consistent.
- `.planning/phases/04-harden-clean-up-optional/deferred-items.md`**MODIFIED.** Net +2 / -2. The prior 'strict-meta-json fails on a clean tree' entry rewritten to describe the real 04-CONTEXT #9/#10 parallel-vitest ffprobe flake family + correct the baseline to 184/184 GREEN.
- `.planning/ROADMAP.md`**MODIFIED.** Plan 04-06 row flipped `[ ]` -> `[x]` with closure annotation. Phase 4 progress table cell updated from `6/8 In Progress (Plan 04-05 closed ROADMAP SC #2)` to `7/8 In Progress (Plan 04-06 closed D-P4-03)`.
- `.planning/STATE.md`**MODIFIED.** Current Position advanced. Stopped At updated. Last activity 2026-05-26. Plan 04-06 closure section added. Decisions appended. Performance metric row added. Progress 90% -> 93% (28/31 -> 29/31).
### Archived
- `.planning/debug/04-06-dark-mode-mark-decouple.md` -> `.planning/debug/resolved/04-06-dark-mode-mark-decouple.md` — debug session moved at plan closure (resolved end-to-end via fix commit `a8bcc17` + operator re-empirical 2026-05-26).
## Decisions Made
See `key-decisions` in frontmatter for the canonical list (10 decisions). Highlights:
1. **Multi-iteration ceremony was a necessary cost** — 3 planner passes + 2 checker passes + 1 debug fix cycle. The /gsd-debug fix surfaced the brand-component vs semantic token abstraction error that the planner-checker couldn't catch (requires operator visual judgment). Lesson encoded.
2. **Brand-component token vs semantic token**`--mks-fg-inverse` is for theme-flipping text foreground; `--mks-mark-stroke` is for theme-independent brand stroke. Pattern established and documented.
3. **A35.5 decouple-proof sub-check** — light.computedStroke === dark.computedStroke === 'rgb(250, 247, 241)'. Regression-proof for any future re-coupling.
4. **Honest A17.8 narrowing** — SOURCE-BUNDLING only; live-DOM coverage lives in A35.
5. **Behavior-based vitest gate** — hard-codes no test filename; isolation-passing single flake is tolerated; reproducible failure or 2+ failures FAIL the gate.
6. **01-07-SUMMARY back-patch** — 5 stale framing lines flipped; 4 historical commit-description lines left.
7. **Cosmetic advisories ADV-2A/B/C** — banner string left for Plan 04-07 to opportunistically refresh; SKIP_PROD_REBUILD=0 prose accuracy noted; A35-appended-LAST safety documented in SUMMARY.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 — Blocking] Welcome screenshot script created from scratch (Task 4 artifact)**
- **Found during:** Task 4 (Operator empirical UAT — pre-checkpoint screenshot production)
- **Issue:** The plan instructed "Load dist/ as an unpacked extension and open the welcome page in a Puppeteer-driven Chrome. Capture LIGHT-surface screenshot. Capture DARK-surface screenshot." but did not provide a dedicated script — the operator-empirical checkpoint required a reproducible artifact.
- **Fix:** Created scripts/04-06-welcome-hero-screenshots.mjs (194 lines) — Puppeteer; loads dist/ as unpacked extension; opens welcome.html; captures /tmp/04-06-welcome-hero-{light,dark}.png via `Emulation.setEmulatedMedia` `prefers-color-scheme: dark`; prints diagnostic `getComputedStyle().stroke` for each theme. Reproducible for any future re-shoot.
- **Files modified:** scripts/04-06-welcome-hero-screenshots.mjs (CREATED)
- **Verification:** Both screenshots produced + visually inspected by operator; diagnostic output confirms `computedStroke` resolved for each theme.
- **Committed in:** `d66cbf6` (separate chore commit; Task 4 artifact)
**2. [Rule 1 — Bug] Dark-cascade abstraction error: --mks-fg-inverse couples stroke to theme**
- **Found during:** Task 4 operator-empirical UAT (TWEAK verdict 2026-05-26)
- **Issue:** `.welcome-hero__mark { color: var(--mks-fg-inverse); }` is theme-coupled (linen-50 in light; ink-900 in dark per tokens.css :root vs .dark blocks). The mark sits on a theme-INDEPENDENT madder-600 circle, so a theme-coupled stroke produced muddy ink-on-madder in dark mode. Wrong abstraction (semantic token applied to a non-flipping surface).
- **Fix:** Introduced a NEW brand-component token `--mks-mark-stroke = var(--mks-linen-50)` in the universal :root block of src/shared/tokens.css. CRUCIALLY NOT overridden in `.dark, [data-theme="dark"]` — stays linen-50 on every surface. Rewired `.welcome-hero__mark { color: var(--mks-mark-stroke); }` at src/welcome/welcome.css line 72. SVG remains untouched (stroke="currentColor" cascade plumbing identical; only the wrapper's color source changed). Routed via /gsd-debug per `feedback-gsd-ceremony-for-fixes.md` — no inline hot-edit.
- **Files modified:** src/shared/tokens.css (SCOPE EXPANSION; +15); src/welcome/welcome.css (SCOPE EXPANSION; +11); src/welcome/welcome.ts (comment-only); tests/welcome/inline-svg.test.ts (comment-only); tests/uat/lib/harness-page-driver.ts (A35 strengthening: a35Probe helper + A35.5 light+dark equality); tests/uat/harness.test.ts (comment-only); .planning/debug/04-06-dark-mode-mark-decouple.md (NEW debug note; later archived to resolved/).
- **Verification:** Re-shot screenshots show identical crisp linen-on-madder in both themes. A35.5 live-DOM probe: light.computedStroke === dark.computedStroke === "rgb(250, 247, 241)". UAT 36/36 GREEN. Operator re-empirical 2026-05-26: "Confirmed fixed — close Plan 04-06".
- **Committed in:** `a8bcc17` (debug-fix commit; ceremony-routed per saved memory)
**3. [Rule 1 — Bug] A35 sub-check strengthening: 4 -> 5 sub-checks (A35.5 decouple-proof added)**
- **Found during:** /gsd-debug session for the dark-cascade abstraction error
- **Issue:** The original A35 (Task 3 first land) had 4 sub-checks (A35.1-A35.4) covering live-DOM injection + currentColor cascade resolution in LIGHT theme only. A future regression that re-couples the stroke to a theme-flipping token would still pass A35.1-A35.4 (the inline `<svg>` is injected; the cascade resolves; just to the wrong color in dark).
- **Fix:** Extracted the live-DOM probe into a helper `a35Probe(welcomePage, dark)` that toggles `data-theme="dark"` on documentElement (+ requestAnimationFrame wait for CSS recompute). Probe BOTH light + dark. Add A35.5: assert `light.computedStroke === dark.computedStroke === "rgb(250, 247, 241)"` (linen-50). This is the canonical decouple-proof.
- **Files modified:** tests/uat/lib/harness-page-driver.ts (folded into debug-fix commit `a8bcc17`)
- **Verification:** UAT 36/36 GREEN; A35.5 diagnostics show light + dark both rgb(250, 247, 241).
- **Committed in:** `a8bcc17` (folded into the debug-fix commit; ceremony-routed)
---
**Total deviations:** 3 auto-fixed (1 blocking [screenshot script]; 2 bugs [abstraction error + A35 strengthening]).
**Impact on plan:** All 3 deviations were essential for closure. The screenshot script was the operator-empirical artifact; the abstraction-error fix closed the TWEAK verdict; the A35 strengthening encodes the regression-proof. No scope creep — the SCOPE EXPANSIONS (tokens.css + welcome.css edits) were authorized by the Task 4 TWEAK verdict per the operator-empirical resume-signal contract.
## Issues Encountered
### Operator-empirical TWEAK verdict — abstraction error in dark cascade
Task 4's first operator-empirical verdict (2026-05-26) was TWEAK NEEDED. The light screenshot was crisp linen-on-madder (Plan 01-10 baseline preserved); the dark screenshot showed the mark stroke flipped to ink-900 (deep indigo) on the madder-600 wrapper — visually muddy, low contrast. Root cause: `.welcome-hero__mark { color: var(--mks-fg-inverse); }` couples the cascade to a semantic token that flips with theme. The mark wrapper is theme-independent (madder-600 in both themes), so the coupling was the wrong abstraction.
**Resolution (per saved memory `feedback-gsd-ceremony-for-fixes.md`):** routed via /gsd-debug rather than inline hot-edit. Debug session at .planning/debug/04-06-dark-mode-mark-decouple.md established root cause + fix; fix commit `a8bcc17` decoupled via the NEW brand-component token `--mks-mark-stroke`. A35 strengthened with the A35.5 decouple-proof sub-check. Re-shot screenshots + UAT re-run produced identical crisp linen-on-madder in both themes. Operator re-empirical 2026-05-26: "Confirmed fixed — close Plan 04-06".
### Parallel-vitest #9/#10 flake fired during Task 2 GREEN gate
Task 2's full vitest run produced 187 passed / 1 failed (188 total). The failing test was `tests/background/webm-remux.test.ts > 'ffprobe -count_frames reports between 905 and 912 frames'` (`Error: Test timed out in 5000ms`). Per the Task 2 VITEST GATE LOGIC behavior-based rule: re-run in isolation (`npm test -- tests/background/webm-remux.test.ts --run`) -> 5/5 GREEN. Tolerated as the known 04-CONTEXT #9/#10 parallel-vitest ffprobe-timeout flake family. NOT a Plan 04-06 regression (Plan 04-06 modifies no offscreen recorder code). Documented + tolerated per gate logic.
## Pre-Checkpoint Bundle Gates (6/6 PASS)
Per saved memory `feedback-pre-checkpoint-bundle-gates.md`, all 6 gates run on `npm run build` production output before each operator-empirical checkpoint surface:
| Gate | Check | Result |
|------|----------------------------------------------------------------------|----------|
| 1 | npm run build exit 0; glob-existence ls dist/assets/index.ts-*.js | PASS |
| 2 | SW CSP-safety: `new Function`/`eval` in dist/assets/index.ts-*.js | 0 / 0 — PASS (Plan 04-02 effect preserved) |
| 3 | SW Node-globals: `Buffer.`/`process.`/`require(` in SW chunk | 1 (pre-existing JSZip polyfill `typeof ArrayBuffer<"u"` feature-detection) — PASS (baseline-identical to Plan 04-05 boundary; Plan 04-06 modifies no SW chunk source) |
| 4 | DOM-globals: `window.`/`document.` in SW chunk | third-party `typeof window<"u"`/`typeof document<"u"` guarded feature-detection — PASS (baseline-identical) |
| 5 | Tier-1 SW-bundle-import vitest gate | GREEN |
| 6 | Tier-1 FORBIDDEN_HOOK_STRINGS at 12 + Tier-2 production filename-leak gate (0 hits) | GREEN — PASS |
**6/6 PASS** at both the first Task 4 checkpoint AND the post-debug re-checkpoint. Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12 (Plan 04-06 adds no `__MOKOSH_UAT__`-gated symbols; DOMParser is a standard web platform API; `?raw` is a normal production Vite import; `*.svg?raw` ambient decl is type-only at build time). SW chunk is byte-identical at the Plan 04-05 boundary (Plan 04-06 modifies only src/welcome/* + src/shared/brand/mokosh-mark.svg + src/shared/tokens.css :root token + tests/* + globals.d.ts — none of these affect the SW chunk shape).
## Test Baselines
- **vitest:** **188/188 GREEN** on a fully-clean run (the +4 from Plan 04-06: 3 inline-svg.test.ts + 1 cursor-visibility.test.ts). Most recent full-suite run post-debug-fix: **187/188 with the 04-CONTEXT #9/#10 webm-remux flake fired** (passes 5/5 in isolation; tolerated per Task 2 VITEST GATE LOGIC). Baseline contract held: 184 + 4 = 188.
- **UAT harness:** **36/36 GREEN** (skip-mode `SKIP_LONG_UAT=1 npm run test:uat`). Increments: 33 (post-04-03) -> 34 (04-08 A33) -> 35 (04-05 A34) -> 36 (04-06 A35). A35 itself contains 5 sub-checks all PASS (A35.1-A35.5).
- **Tier-1 FORBIDDEN_HOOK_STRINGS:** **12** (unchanged from Plan 04-02 baseline; verified at `tests/background/no-test-hooks-in-prod-bundle.test.ts:108-126`).
- **Tier-2 production-bundle filename-leak gate:** `synthetic-display-source` = 0 hits in dist/ (Plan 04-08 invariant preserved).
- **Pre-checkpoint bundle gates:** **6/6 PASS** (verified at both Task 4 checkpoint AND post-debug re-checkpoint).
- **`tsc --noEmit`:** exits 0. **`npm run build`:** exits 0. **`npm run build:test`:** exits 0.
## D-P4-03 Closure Evidence
**D-P4-03 (charter, locked, 04-CONTEXT.md):** BOTH (a) cursor visibility AND (b) dark-surface logo contrast.
### (a) Cursor visibility — CLOSED
- **Already shipped:** `cursor: 'always'` at src/offscreen/recorder.ts:285 (Plan 01-09 opportunistic co-land in commit `a2dfc8c` 2026-05-19; the original Phase 5 deferral framing in 01-07-SUMMARY was false — the constraint was shipped concurrently with the no-active-tab cleanup).
- **Verified by:** tests/build/cursor-visibility.test.ts — node-env file-grep regression pin for the literal `cursor: 'always'`. 1 GREEN-on-arrival. Any future regression that removes the literal will fail the gate.
- **01-07-SUMMARY back-patch:** 5 stale 'deferred to Phase 5' framing lines (22, 47, 82, 135, 205) flipped; 4 historical commit-description lines (40, 89, 109, 110) LEFT.
### (b) Dark-surface logo contrast — CLOSED
4-layer coverage:
1. **Source-contract unit tests** (Task 1): tests/welcome/inline-svg.test.ts (3 tests; node-env; file-read + string-assert). Pins SVG recolor + welcome.ts ?raw/DOMParser/replaceChildren/no-innerHTML + globals.d.ts ambient decl. RED -> GREEN after Task 2.
2. **A17.8 raw-source bundling check** (Task 3): tests/uat/extension-page-harness.ts. Asserts welcome JS chunk contains the raw SVG source signature (stroke="currentColor" + viewBox="0 0 32 32"). Honestly narrowed to SOURCE-BUNDLING only; explanatory comment disavows live-DOM coverage and points to A35.
3. **A35 live-DOM host-side harness assertion** (Task 3 + debug-fix strengthening): tests/uat/lib/harness-page-driver.ts driveA35. Opens welcome.html as a real Puppeteer tab; lets populateMark() run at DOMContentLoaded; reads the LIVE injected `.welcome-hero__mark svg` element + getComputedStyle().stroke. **5 sub-checks**: A35.1 svg present; A35.2 stroke=\"currentColor\"; A35.3 getComputedStyle().stroke resolved non-default (linen-50); A35.4 no `<img>` in slot; **A35.5 light.computedStroke === dark.computedStroke === \"rgb(250, 247, 241)\" (linen-50 decouple-proof, NEW from debug-fix)**. UAT 36/36 GREEN — A35 is the genuine automated proof of the runtime injection + cascade + theme decoupling.
4. **Operator-empirical Puppeteer screenshots** (Task 4): scripts/04-06-welcome-hero-screenshots.mjs produces /tmp/04-06-welcome-hero-{light,dark}.png via prefers-color-scheme: dark emulation. First operator verdict: TWEAK (dark cascade flipped to ink-900). /gsd-debug session resolved via --mks-mark-stroke brand-component token. Re-shot screenshots + operator re-empirical 2026-05-26: "Confirmed fixed — close Plan 04-06".
## TDD Gate Compliance
Plan 04-06 was a `type: execute` plan with `tdd="true"` on Tasks 1-2 (the source-contract + implementation pair). Gate sequence verified in git log:
1.**RED gate (test commit):** `f0b88d4` (test(04-06): Wave 0 — inline-SVG source-contract RED + cursor-visibility regression pin). 3 RED + 1 GREEN-on-arrival at commit time; isolated `npm test -- tests/welcome/ --run` showed 3/3 FAILED before Task 2 GREEN.
2.**GREEN gate (feat commit):** `c416143` (feat(04-06): Wave 1 GREEN — dark-logo currentColor strategy + inline-SVG injection) after RED. 3 RED flipped to 3 GREEN; cursor-visibility test preserved 1 GREEN.
3. **REFACTOR gate:** N/A — no separate refactor commit needed; Task 2 implementation was minimal-to-pass.
TDD discipline preserved end-to-end.
## Charter Linkage
Plan 04-06 has no REQ-* requirements (Phase 4 is optional hardening; all v1 REQs covered by Phases 1-3 per ROADMAP). Charter linkage is via the `charter-d-p4-03` tag in frontmatter. D-P4-03 = visual polish (cursor visibility + dark-surface logo contrast) — both items closed by Plan 04-06.
## Cross-Plan Provenance Note for Plan 04-07
Plan 04-07 (Phase 4 closure aggregator) is now the ONLY remaining Phase 4 plan. Inputs for 04-07:
- **D-P4-03 closure:** documented here (cursor + dark-logo both closed; A35 + cursor-visibility unit test pin live).
- **D-P4-04 (alpha tester integration):** honored out-of-band (per ROADMAP entry).
- **ROADMAP success criteria status:** SC #1 CLOSED (04-08); SC #2 CLOSED (04-05); SC #3 CLOSED (04-02); SC #4 CLOSED (04-02). All 4 ROADMAP success criteria are now CLOSED.
- **Cosmetic-advisory ADV-2A** (architecture banner string at harness.test.ts:283 missing A33/A34/A35): non-blocking; Plan 04-07 may opportunistically refresh the banner if it touches that file.
- **04-CONTEXT #9/#10 parallel-vitest flake family:** documented in deferred-items.md (corrected this plan); routed for /gsd-debug or future-plan worker-isolation strategy (`poolOptions.threads.singleThread:true` or `testTimeout` bump) if it becomes a blocker for Phase 4 closure.
Plan 04-07 should produce the Phase 4 aggregate verification (VERIFICATION.md gsd-verifier audit) + REQUIREMENTS/ROADMAP/STATE marker flips + v1 milestone close prep.
## Saved-Memory Compliance Audit
-**`feedback-no-unilateral-scope-reduction.md`** — full plan scope executed (4 tasks; 11 files modified; SCOPE EXPANSIONS authorized by operator TWEAK verdict; no unilateral truncation).
-**`feedback-trust-harness-over-manual-uat.md`** — operator only judged from screenshots (Task 4 artifact via scripts/04-06-welcome-hero-screenshots.mjs); A35.5 light+dark equality decouple-proof landed in the automated harness (not just the screenshot artifact).
-**`feedback-gsd-ceremony-for-fixes.md`** — the dark-cascade abstraction error was routed via /gsd-debug (debug session at .planning/debug/04-06-dark-mode-mark-decouple.md; fix commit `a8bcc17` is a separate atomic fix-commit, NOT a hot-edit folded into a task commit). Ceremony honored.
-**`feedback-pre-checkpoint-bundle-gates.md`** — 6/6 bundle gates run + verified PASS at BOTH the first Task 4 checkpoint AND the post-debug re-checkpoint. Documented above.
## Threat Surface Scan
Plan 04-06 introduces no new security-relevant surface:
- **Inline-SVG injection:** safe because the input is a Vite-bundled compile-time literal (no runtime untrusted input). DOMParser parse + replaceChildren preserves MV3 CSP discipline (T-04-06-01 mitigation; no innerHTML, no eval). The `?raw` import is processed at build time; the SVG source string is statically known.
- **NEW A35 harness tab:** test-only; always closed in finally block (no production surface, no leaked tab); A35 is appended LAST in the drivers array (no pollution-of-future-drivers risk — there are no future drivers); A35 does NOT mutate chrome.storage (opening welcome.html does NOT trigger openWelcomeIfFirstInstall — that runs only on chrome.runtime.onInstalled). Verified safe at design time (cosmetic-advisory ADV-2C).
- **`--mks-mark-stroke` brand-component token:** purely additive CSS custom property in :root; no override anywhere; no consumer surface change.
No `## Threat Flags` section needed.
## Self-Check: PASSED
Acceptance criterion by criterion (cross-referenced against the success_criteria list in the closure-work prompt):
-**04-06-SUMMARY.md written per canonical template + multi-iteration ceremony history**: this file. Frontmatter complete (phase + plan + subsystem + tags + requires + provides + affects + tech-stack + key-files + key-decisions + metrics). Multi-Iteration Ceremony History section provides the 13-row provenance table.
-**All 4 tasks documented**: Tasks 1-3 autonomous (commits `f0b88d4` + `c416143` + `3f8e31a`) + Task 4 operator empirical with TWEAK → fix `a8bcc17` → CONFIRMED arc.
-**All key deliverables enumerated**: currentColor + --mks-mark-stroke decoupling + A35 (5 sub-checks) + back-patch + screenshot script + 01-07-SUMMARY 5-line flip + deferred-items correction.
-**3 re-plan-checker iter-2 cosmetic advisories documented**: ADV-2A (banner: LEAVE per decision), ADV-2B (SKIP_PROD_REBUILD=0 rationale corrected in SUMMARY), ADV-2C (A35-appended-LAST safety noted in Threat Surface Scan).
-**Pre-checkpoint bundle gates 6/6 PASS reported**: dedicated section above with table.
-**Test baselines documented**: vitest 188/188 GREEN (most recent 187/188 with tolerated webm-remux flake); UAT 36/36 GREEN; FORBIDDEN_HOOK_STRINGS 12.
-**D-P4-03 closure evidence**: dedicated section with (a) cursor visibility + (b) dark-surface logo contrast 4-layer coverage.
-**Self-Check section**: this section, criterion by criterion.
**Created files exist:**
- ✅ tests/welcome/inline-svg.test.ts — FOUND
- ✅ tests/build/cursor-visibility.test.ts — FOUND
- ✅ scripts/04-06-welcome-hero-screenshots.mjs — FOUND
- ✅ .planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md — FOUND (this file)
**Commits exist (verified via `git log --oneline`):**
-`f0b88d4` (Task 1 RED) — FOUND
-`c416143` (Task 2 GREEN) — FOUND
-`3f8e31a` (Task 3 A35 + back-patch) — FOUND
-`d66cbf6` (Task 4 screenshot harness) — FOUND
-`a8bcc17` (debug-fix --mks-mark-stroke) — FOUND
No missing items. Plan 04-06 closes cleanly.
---
*Phase: 04-harden-clean-up-optional*
*Plan: 06*
*Completed: 2026-05-26*