From b59bd24354dd07012f50cb302b302302163e409a Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 22 May 2026 16:22:04 +0200 Subject: [PATCH] =?UTF-8?q?docs(04-06):=20re-plan=20=E2=80=94=20correct=20?= =?UTF-8?q?false=20jsdom=20premise=20+=20stale=20back-patch=20lines=20+=20?= =?UTF-8?q?baseline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full re-plan via /gsd-plan-phase ceremony. The prior 04-06-PLAN.md hit a blocking checkpoint (plan-assumption defect). Three defects corrected; thesis preserved (dark-logo currentColor Option A + cursor verification-only + A17.8 + operator-empirical Task 4). DEFECT 1 — false jsdom premise: prior Task 1 assumed vitest configures a jsdom environment. FALSE — vitest.config.ts:18 sets environment:'node' and no DOM-emulation library is in node_modules. Resolution: STRATEGY (a) — reframe tests/welcome/inline-svg.test.ts as a node-env source-contract test (the canonical tests/i18n/manifest-i18n.test.ts file-read + string-assert pattern); delegate live-DOM injection + currentColor cascade verification to the A17.8 harness sub-check in real Chrome. Rejected (b) jsdom devDependency (deviates from a twice-reaffirmed no-DOM-library stance) and (c) manual DOMParser stub (fragile for SVG-namespace fidelity). DEFECT 2 — stale back-patch line numbers: verified the genuine stale 'deferred to Phase 5' lines in 01-07-SUMMARY.md are 22/47/82/135/205; historical commit-description lines 40/89/109/110 left unchanged. DEFECT 3 — wrong vitest baseline: real baseline is 183 GREEN / 1 pre-existing RED (strict-meta-json-validation.test.ts, logged to deferred-items.md, routed to /gsd-debug). Test-count target reframed to 187 GREEN / 1 pre-existing RED. revision_history block added. files_modified updated (welcome.css dropped — the bare class selector matches identically; no CSS edit needed). must_haves truths/artifacts/key_links updated to match the corrected plan. frontmatter.validate + verify.plan-structure both green. Co-Authored-By: Claude Opus 4.7 --- .../04-harden-clean-up-optional/04-06-PLAN.md | 576 ++++++++---------- 1 file changed, 269 insertions(+), 307 deletions(-) diff --git a/.planning/phases/04-harden-clean-up-optional/04-06-PLAN.md b/.planning/phases/04-harden-clean-up-optional/04-06-PLAN.md index 5c635ba..4219578 100644 --- a/.planning/phases/04-harden-clean-up-optional/04-06-PLAN.md +++ b/.planning/phases/04-harden-clean-up-optional/04-06-PLAN.md @@ -13,7 +13,6 @@ depends_on: files_modified: - src/shared/brand/mokosh-mark.svg - src/welcome/welcome.ts - - src/welcome/welcome.css - globals.d.ts - tests/uat/extension-page-harness.ts - tests/welcome/inline-svg.test.ts @@ -31,17 +30,30 @@ tags: - charter-d-p4-03 - operator-empirical user_setup: [] +revision_history: + - revised: 2026-05-22 + reason: "Full re-plan via /gsd-plan-phase ceremony. The prior 04-06-PLAN.md hit a blocking checkpoint (plan-assumption defect). Three defects corrected; thesis preserved (dark-logo currentColor Option A + cursor verification-only + A17.8 + operator-empirical Task 4)." + defects_resolved: + - id: "DEFECT 1 — false jsdom premise" + was: "Prior Task 1 stated 'vitest jsdom environment (vitest.config.ts already configures this; verify by grep environment.*jsdom vitest.config.ts)'. FALSE — vitest.config.ts line 18 sets environment:'node'; no DOM-emulation library (jsdom/happy-dom/linkedom/@happy-dom/global-registrator) is in node_modules; no vitest unit test has ever used DOMParser. The prior plan required tests/welcome/inline-svg.test.ts to exercise populateMark()'s live DOMParser + document.querySelector path inside a jsdom that does not exist." + now: "Test strategy decided — STRATEGY (a): reframe inline-svg.test.ts for node-env. The test reads src/shared/brand/mokosh-mark.svg + src/welcome/welcome.ts + globals.d.ts from disk (the canonical tests/i18n/manifest-i18n.test.ts file-read + string-assert pattern) and pins the SOURCE-LEVEL contract that populateMark's ?raw import + DOMParser injection depends on. Full live-DOM injection + currentColor CSS cascade verification is delegated to the A17.8 harness sub-check in real Chrome (Task 3). Rationale: the project DELIBERATELY ships no DOM library (UI-SPEC scope_kind:thin-design-surface; vitest.config.ts environment:'node' is a deliberate Plan 01-11 choice); two precedents (tests/background/toolbar-action.test.ts:279, tests/offscreen/display-surface-constraint.test.ts:41) explicitly 'manually stub' rather than install jsdom; CLAUDE.md mandates 'consistency with existing patterns > brevity'; feedback-trust-harness-over-manual-uat reserves real-browser behavior for the Puppeteer harness. Strategy (b) jsdom devDependency rejected — deviates from a twice-reaffirmed no-DOM-library stance to faithfully test what the harness already covers in real Chrome. Strategy (c) manual DOMParser stub rejected — the executor correctly assessed a faithful SVG-namespace + currentColor DOMParser stub as substantial + fragile; toolbar-action.test.ts only stubs simple element-getter surfaces, never DOMParser." + - id: "DEFECT 2 — stale back-patch line numbers" + was: "Prior Task 3 + PATTERNS.md cited 01-07-SUMMARY.md back-patch lines 47/82/109/135/205." + now: "Verified against the live file (grep 2026-05-22): the genuine stale 'deferred to Phase 5' framing — the cursor-visibility deferral that is FALSE because cursor:'always' actually shipped in Plan 01-09 — is at lines 22, 47, 82, 135, 205. Lines 40, 89, 109, 110 are historical descriptions of what the Plan 01-07-closure ROADMAP/STATE commits DID (they record commit content + the [Phase 01-07-deferred-to-5] decision-log tag) and are LEFT unchanged. Task 3 flips 22/47/82/135/205 only." + - id: "DEFECT 3 — wrong vitest baseline" + was: "Prior plan assumed a 184/184 GREEN vitest baseline and targeted '184 -> ~188 GREEN'." + now: "Verified real baseline: 183 passed / 1 failed (184 total). tests/build/strict-meta-json-validation.test.ts fails on a clean tree (getMetaOrFail line 491 — meta.json not parsed from the SAVE_ARCHIVE flow). This is a pre-existing red, NOT caused by Plan 04-06 (whose surface is SVG recolor + welcome.ts + globals.d.ts + A17.8 + docs); logged to .planning/phases/04-harden-clean-up-optional/deferred-items.md at commit 6a989e8 and routed to /gsd-debug separately (consolidates with the A33 SAVE-ack flake). The test-count target is reframed below as '+4 new tests on top of the 183-GREEN/1-pre-existing-RED baseline' = 187 GREEN / 1 pre-existing RED." must_haves: truths: - "src/shared/brand/mokosh-mark.svg root element carries `stroke=\"currentColor\"` (was `stroke=\"#181b2a\"`); the 12 + 1 children are unchanged" - - "src/welcome/welcome.ts imports the mark via `import markSvg from '../shared/brand/mokosh-mark.svg?raw';` (was `?url`); populateMark uses DOMParser to inject inline into the slot (NOT )" - - "Injected inline inherits color from the parent .welcome-hero__mark wrapper (color: var(--mks-fg-inverse)) so the stroke resolves via CSS currentColor cascade" - - "globals.d.ts contains a `declare module '*.svg?raw'` ambient module declaration alongside the existing `*.svg?url`" - - "tests/welcome/inline-svg.test.ts pins the contract: populateMark injects inline , stroke='currentColor', aria-hidden/role/aria-label preserved" - - "A17.8 harness sub-check updated: welcome chunk JS bundles raw SVG source with viewBox='0 0 32 32' + stroke='currentColor' AND DOM-querySelector inline after populateMark" - - "Cursor visibility verified ALREADY SHIPPED at src/offscreen/recorder.ts:285 (`cursor: 'always'`) per RESEARCH Finding 4; tests/build/cursor-visibility.test.ts regression-pins the literal" - - ".planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md back-patched: stale 'deferred to Phase 5' lines flipped to 'shipped Plan 01-09; verified Phase 4 Plan 04-06'" - - "Operator empirical checkpoint: dark-mode visual aesthetic judgment on welcome-hero (the ONE genuine operator-empirical case in Phase 4 per UI-SPEC §'Manual-Only Verifications')" + - "src/welcome/welcome.ts imports the mark via `import markSvg from '../shared/brand/mokosh-mark.svg?raw';` (was `?url`); populateMark uses DOMParser to inject an inline into the slot (NOT , NEVER innerHTML)" + - "Injected inline inherits color from the parent .welcome-hero__mark wrapper (color: var(--mks-fg-inverse)) so the stroke resolves via the CSS currentColor cascade — verified in real Chrome by the A17.8 harness sub-check" + - "globals.d.ts contains a `declare module '*.svg?raw'` ambient module declaration alongside the existing `*.svg?url` and `*.webm?url`" + - "tests/welcome/inline-svg.test.ts (node-env, source-level) pins the contract: mokosh-mark.svg source carries stroke='currentColor' + canonical viewBox; welcome.ts source uses the ?raw import + DOMParser + replaceChildren + aria attributes and does NOT use innerHTML; globals.d.ts carries the *.svg?raw ambient decl" + - "A17.8 harness sub-check updated to assert the welcome chunk JS bundles the raw SVG source (stroke='currentColor' + canonical viewBox) AND, in real Chrome, populateMark injects an inline whose stroke resolves to currentColor via parent CSS color" + - "Cursor visibility verified ALREADY SHIPPED at src/offscreen/recorder.ts:285 (`cursor: 'always'`) per RESEARCH Finding 4; tests/build/cursor-visibility.test.ts (node-env file-grep) regression-pins the literal" + - ".planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md back-patched: the stale 'deferred to Phase 5' lines 22, 47, 82, 135, 205 flipped to 'shipped opportunistically Plan 01-09; verified Phase 4 Plan 04-06'; the historical commit-description lines 40, 89, 109, 110 left unchanged" + - "Operator empirical checkpoint: dark-mode visual aesthetic judgment on welcome-hero from dark + light Puppeteer screenshots (the ONE genuine operator-empirical case in Phase 4 per UI-SPEC §'Acceptance Criteria #6')" - "PNG toolbar icons untouched (Chrome auto-inverts on dark toolbars; icons/icon{16,48,128}.png byte-identical)" artifacts: - path: "src/shared/brand/mokosh-mark.svg" @@ -49,57 +61,58 @@ must_haves: contains: "stroke=\"currentColor\"" - path: "src/welcome/welcome.ts" provides: "?raw import + DOMParser-based inline SVG injection in populateMark (replaces injection)" - contains: "import markSvg from '../shared/brand/mokosh-mark.svg?raw'" - - path: "src/welcome/welcome.css" - provides: "(Optional) selector broadening for .welcome-hero__mark-img to match both img.* AND svg.* — bare class selector works for both; explicit selectors documented for transition clarity" - contains: "welcome-hero__mark-img" + contains: "mokosh-mark.svg?raw" - path: "globals.d.ts" - provides: "Ambient module declaration for *.svg?raw imports (4-line block mirror of existing *.svg?url)" + provides: "Ambient module declaration for *.svg?raw imports (mirror of the existing *.svg?url block)" contains: "*.svg?raw" - path: "tests/welcome/inline-svg.test.ts" - provides: "Wave 0 RED -> GREEN — 3-test contract pinning inline SVG injection + currentColor + aria preservation" + provides: "Wave 0 RED -> GREEN — node-env source-level contract: SVG source recolor + welcome.ts ?raw/DOMParser/no-innerHTML + globals.d.ts ambient decl" contains: "currentColor" min_lines: 50 - path: "tests/build/cursor-visibility.test.ts" - provides: "Wave 0 GREEN-on-arrival — defensive pin for src/offscreen/recorder.ts:285 cursor: 'always' literal" + provides: "Wave 0 GREEN-on-arrival — node-env file-grep defensive pin for src/offscreen/recorder.ts:285 cursor: 'always' literal" contains: "cursor: 'always'" min_lines: 15 - path: "tests/uat/extension-page-harness.ts" - provides: "A17.8 sub-check updated to assert raw SVG source bundling + inline injection (NOT data URL)" + provides: "A17.8 sub-check updated to assert raw SVG source bundling + inline injection in real Chrome (NOT data URL)" contains: "A17.8" - path: ".planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md" - provides: "Surgical back-patch — stale 'Phase 5' lines flipped (lines 47, 82, 109, 135, 205 per PATTERNS.md)" + provides: "Surgical back-patch — stale 'deferred to Phase 5' lines 22/47/82/135/205 flipped; historical commit-description lines 40/89/109/110 left" contains: "Phase 4 Plan 04-06" key_links: - from: "src/welcome/welcome.ts populateMark" to: "src/shared/brand/mokosh-mark.svg?raw" - via: "Vite ?raw import returns SVG source as string; DOMParser parses; appendChild injects" + via: "Vite ?raw import returns SVG source as a string; DOMParser parses; replaceChildren injects" pattern: "DOMParser" - from: "Injected inline stroke='currentColor'" to: ".welcome-hero__mark wrapper color: var(--mks-fg-inverse)" - via: "CSS color cascade (W3C SVG2 §13.3)" - pattern: "color: var\\(--mks-fg-inverse\\)" + via: "CSS color cascade (W3C SVG2 §13.3) — verified in real Chrome by A17.8" + pattern: "stroke=\"currentColor\"" - from: "tests/uat/extension-page-harness.ts A17.8" - to: "welcome chunk JS bundle string-grep" - via: "post-build grep for raw SVG source + querySelector inline " - pattern: "A17\\.8" + to: "welcome chunk JS bundle string-grep + real-Chrome DOM query" + via: "post-build grep for raw SVG source + querySelector inline after populateMark" + pattern: "A17" --- -Land the UI-SPEC dark-surface logo contrast strategy (Option A — currentColor SVG + CSS color via existing `.dark` block in tokens.css). Per the UI-SPEC §"Implementation amendment", this requires a 2-part technique: +Land the UI-SPEC dark-surface logo contrast strategy (Option A — `currentColor` SVG + CSS `color` via the existing `.dark` block in `src/shared/tokens.css`). Per the UI-SPEC §"Implementation amendment", this requires a 2-part technique: -1. SVG attribute change: `stroke="#181b2a"` → `stroke="currentColor"` on the root `` of `src/shared/brand/mokosh-mark.svg`. -2. Inline-SVG injection in welcome.ts: `` rendering of SVG runs in an isolated SVG document context where `currentColor` resolves to the SVG's own root color (defaults to `canvastext` ≈ black). To get the desired CSS cascade, the SVG must be inlined into the welcome page's DOM. Switch `import markUrl from '.../*.svg?url'` to `import markSvg from '.../*.svg?raw'` + rewrite populateMark to use DOMParser + appendChild. +1. **SVG attribute change:** `stroke="#181b2a"` to `stroke="currentColor"` on the root `` of `src/shared/brand/mokosh-mark.svg`. +2. **Inline-SVG injection in welcome.ts:** an ``-rendered SVG runs in an isolated SVG document context where `currentColor` resolves to the SVG's own root color (defaults to `canvastext`, near-black). To get the desired CSS cascade, the SVG must be inlined into the welcome page's DOM. Switch the `import markUrl from '.../*.svg?url'` to `import markSvg from '.../*.svg?raw'` plus rewrite `populateMark` to use DOMParser + `replaceChildren`. Two additional polish items co-land: -3. **Cursor visibility verification** (RESEARCH Finding 4): `cursor: 'always'` is ALREADY SHIPPED at `src/offscreen/recorder.ts:285` (Plan 01-09 opportunistically lifted the original Phase 5 deferral). Plan 04-06 confirms via a defensive grep test (`tests/build/cursor-visibility.test.ts`) + back-patches the stale "deferred to Phase 5" lines in `.planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md` (5 occurrences per PATTERNS.md mapping). +3. **Cursor visibility verification** (RESEARCH Finding 4): `cursor: 'always'` is ALREADY SHIPPED at `src/offscreen/recorder.ts:285` (Plan 01-09 opportunistically lifted the original Phase 5 deferral). Plan 04-06 confirms via a defensive grep test (`tests/build/cursor-visibility.test.ts`) and back-patches the stale "deferred to Phase 5" lines in `.planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md`. -4. **Operator empirical checkpoint** (UI-SPEC Acceptance Criterion #6): the ONE Phase 4 genuine operator-empirical gate — dark-mode visual aesthetic judgment of the welcome hero. The harness covers `currentColor` resolution + inline-SVG injection automatically (Tasks 1-3); only the dark-OS aesthetic judgment is non-automatable. Per `feedback-trust-harness-over-manual-uat.md`, operator empirical is reserved for genuinely non-automatable cases; this is one. +4. **Operator empirical checkpoint** (UI-SPEC Acceptance Criterion #6): the ONE Phase 4 genuine operator-empirical gate — dark-mode visual aesthetic judgment of the welcome hero. The harness covers `currentColor` resolution + inline-SVG injection automatically (Tasks 1-3); only the dark-OS aesthetic judgment is non-automatable. Per `feedback-trust-harness-over-manual-uat.md`, the operator judges from dark + light Puppeteer screenshots produced as the verification artifact, NOT a manual Chrome session. -Purpose: Closes UI-SPEC dark-logo contrast strategy + closes RESEARCH Finding 4 cursor verification + closes ROADMAP cursor visibility item + closes Plan 01-10 cycle-2 operator observation "also on dark surfaces probably either we need to place the logo on the light background or dunno". +Purpose: Closes the UI-SPEC dark-logo contrast strategy, RESEARCH Finding 4 cursor verification, the ROADMAP cursor visibility item, and the Plan 01-10 cycle-2 operator observation "also on dark surfaces probably either we need to place the logo on the light background or dunno". -Output: 5 source/test/spec file edits (per UI-SPEC §"Net file-change set") + 2 new test files at `tests/welcome/inline-svg.test.ts` + `tests/build/cursor-visibility.test.ts` + 1 docs back-patch + 1 operator empirical UAT cycle. The plan is `autonomous: false` because of the operator-empirical checkpoint (the ONLY autonomous: false plan in Phase 4). +Output: 3 source/test/spec file edits (SVG recolor + welcome.ts ?raw/DOMParser + globals.d.ts ambient decl) + 1 harness sub-check update (A17.8) + 2 new test files (`tests/welcome/inline-svg.test.ts` + `tests/build/cursor-visibility.test.ts`) + 1 docs back-patch + 1 operator empirical UAT cycle. The plan is `autonomous: false` because of the operator-empirical checkpoint (the ONLY autonomous: false plan in Phase 4). + +**Test-strategy note (re-plan DEFECT 1 resolution):** `tests/welcome/inline-svg.test.ts` runs in vitest's `environment: 'node'` (the deliberate project default — `vitest.config.ts:18`). It pins the SOURCE-LEVEL contract — the SVG source carries `currentColor`, `populateMark` is rewritten correctly with a `?raw` import + DOMParser, and no `innerHTML` is used. It does NOT exercise `populateMark`'s live DOMParser + `document.querySelector` path, because the project deliberately ships no DOM-emulation library. Full live-DOM injection + `currentColor` CSS cascade verification is delegated to the **A17.8 harness sub-check (Task 3) in real Chrome** — the canonical home for real-browser behavior per `feedback-trust-harness-over-manual-uat.md`. + +**Baseline note (re-plan DEFECT 3 resolution):** the vitest baseline is **183 passed / 1 failed (184 total)**. The 1 failure — `tests/build/strict-meta-json-validation.test.ts` — is a pre-existing red on a clean tree (NOT caused by this plan), logged to `deferred-items.md` and routed to `/gsd-debug` separately. Plan 04-06 adds 2 new test files (4 new tests: 3 in `inline-svg.test.ts` + 1 in `cursor-visibility.test.ts`). After this plan the expected state is **187 passed / 1 pre-existing fail (188 total)**. `strict-meta-json-validation.test.ts` is OUT OF SCOPE for Plan 04-06 — Task 2's vitest gate and Task 3's UAT gate explicitly tolerate that one pre-existing red and FAIL ONLY if a different test breaks. @@ -115,8 +128,8 @@ Output: 5 source/test/spec file edits (per UI-SPEC §"Net file-change set") + 2 @.planning/phases/04-harden-clean-up-optional/04-RESEARCH.md @.planning/phases/04-harden-clean-up-optional/04-PATTERNS.md @.planning/phases/04-harden-clean-up-optional/04-UI-SPEC.md +@.planning/phases/04-harden-clean-up-optional/deferred-items.md @.planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md -@.planning/phases/01-stabilize-video-pipeline/01-10-SUMMARY.md # Source files — locus of the visual polish edits @src/shared/brand/mokosh-mark.svg @@ -126,328 +139,274 @@ Output: 5 source/test/spec file edits (per UI-SPEC §"Net file-change set") + 2 @src/offscreen/recorder.ts @tests/uat/extension-page-harness.ts -# Analog test scaffolds — welcome-page side-effect assertion + build-grep -@tests/background/onboarding.test.ts +# Analog test scaffolds — node-env file-read + string-assertion (NO DOM library) @tests/i18n/manifest-i18n.test.ts @tests/build/no-remote-fonts.test.ts +@vitest.config.ts - + -From src/shared/brand/mokosh-mark.svg (current — 13 elements; ~25 lines): -```svg - - - - ... (11 more line + rect children) - -``` +PROJECT TEST ENVIRONMENT (re-plan DEFECT 1 — verified, NON-NEGOTIABLE): +- vitest.config.ts line 18 sets environment:'node'. There is NO jsdom / happy-dom / linkedom / @happy-dom/global-registrator in node_modules. No vitest unit test uses DOMParser. Do NOT grep for environment.*jsdom and do NOT assume a jsdom environment — there is none. +- When a vitest test needs DOM surfaces, the project pattern is to MANUALLY STUB the minimal surface touched (precedent: tests/background/toolbar-action.test.ts:279 "jsdom is not installed; instead we manually stub"; tests/offscreen/display-surface-constraint.test.ts:41). Those precedents stub only simple element-getter surfaces (getElementById / querySelector returning plain-object stubs) — they do NOT stub DOMParser. +- tests/welcome/inline-svg.test.ts therefore uses the file-read + string-assert pattern (precedent: tests/i18n/manifest-i18n.test.ts). It does NOT instantiate a DOM, does NOT call populateMark, does NOT use DOMParser at runtime. -After Plan 04-06 (1-character semantic change on root ): -```svg - - ... (13 child elements UNCHANGED) - -``` +Canonical node-env file-read + string-assert scaffold (from tests/i18n/manifest-i18n.test.ts:29-46): +- import { describe, expect, it } from 'vitest'; +- import { existsSync, readFileSync } from 'node:fs'; +- import { resolve as resolvePath } from 'node:path'; +- const SOME_PATH = resolvePath(process.cwd(), 'relative/path'); +- inside it(): const text = readFileSync(SOME_PATH, 'utf8'); expect(text).toContain('...'); + +From src/shared/brand/mokosh-mark.svg (current — root + 13 children; line 2 is the root element): +- root line 2: +- children: 1 + 12 elements; they inherit stroke from the root. + +After Plan 04-06 (1 attribute changed on the root ; 13 children UNCHANGED): +- root line 2: stroke="#181b2a" becomes stroke="currentColor". Nothing else changes. From src/welcome/welcome.ts:46 (current ?url import): -```typescript import markUrl from '../shared/brand/mokosh-mark.svg?url'; -``` After Plan 04-06 (swap to ?raw): -```typescript import markSvg from '../shared/brand/mokosh-mark.svg?raw'; -``` -From src/welcome/welcome.ts:159-179 (current populateMark — injection): -```typescript -function populateMark(): void { - const slots = Array.from( - document.querySelectorAll('[data-mokosh-slot="mark"]'), - ); - const altText = COPY['welcome.hero.mark.alt'] ?? 'Знак Mokosh'; - for (const slot of slots) { - const img = document.createElement('img'); - img.src = markUrl; - img.alt = altText; - img.width = 64; - img.height = 64; - img.className = 'welcome-hero__mark-img'; - img.setAttribute('aria-hidden', 'true'); - slot.replaceChildren(img); - } - if (slots.length === 0) { - logger.warn('populateMark: no [data-mokosh-slot="mark"] element found in DOM'); - } -} -``` +From src/welcome/welcome.ts:159-179 (current populateMark — injection): builds an HTMLImageElement, sets img.src = markUrl, img.alt, img.width/height = 64, img.className = 'welcome-hero__mark-img', img.setAttribute('aria-hidden','true'), then slot.replaceChildren(img). Filter-pipeline form; empty-slot logger.warn fallback at the end. -After Plan 04-06 (DOMParser inline-SVG injection): -```typescript -function populateMark(): void { - const slots = Array.from( - document.querySelectorAll('[data-mokosh-slot="mark"]'), - ); - const parser = new DOMParser(); - const altText = COPY['welcome.hero.mark.alt'] ?? 'Знак Mokosh'; - for (const slot of slots) { - const doc = parser.parseFromString(markSvg, 'image/svg+xml'); - const svg = doc.documentElement; - svg.classList.add('welcome-hero__mark-img'); - svg.setAttribute('aria-hidden', 'true'); - svg.setAttribute('role', 'img'); - svg.setAttribute('aria-label', altText); - slot.replaceChildren(svg); - } - if (slots.length === 0) { - logger.warn('populateMark: no [data-mokosh-slot="mark"] element found in DOM'); - } -} -``` +After Plan 04-06 (DOMParser inline-SVG injection — the populateMark body becomes): +- const parser = new DOMParser(); +- for each slot: const doc = parser.parseFromString(markSvg, 'image/svg+xml'); const svg = doc.documentElement; +- svg.classList.add('welcome-hero__mark-img'); svg.setAttribute('aria-hidden','true'); svg.setAttribute('role','img'); svg.setAttribute('aria-label', altText); +- slot.replaceChildren(svg); +- preserve the function signature, the slots query, altText = COPY['welcome.hero.mark.alt'] ?? 'Знак Mokosh', the filter-pipeline form, and the empty-slot logger.warn fallback. -NEVER use `innerHTML` — DOMParser + appendChild ONLY (MV3 CSP discipline). The DOMParser parse is safe because the input is a Vite-bundled compile-time literal (no runtime untrusted input). +NEVER use innerHTML — DOMParser + replaceChildren ONLY (MV3 CSP discipline; T-04-06-01 mitigation). The DOMParser parse is safe because the input is a Vite-bundled compile-time literal (no runtime untrusted input). -From globals.d.ts (current ambient decl for ?url at lines 34-37): -```typescript -declare module '*.svg?url' { - const url: string; - export default url; -} -``` +From globals.d.ts (current — *.svg?url block at lines 34-37; a *.webm?url block follows at 47-50): +declare module '*.svg?url' { const url: string; export default url; } -After Plan 04-06 (append): -```typescript -declare module '*.svg?url' { - const url: string; - export default url; -} +After Plan 04-06 (append a *.svg?raw block immediately after the *.svg?url block): +declare module '*.svg?raw' { const raw: string; export default raw; } +(with an explanatory comment block per project docstring convention; cite the UI-SPEC dark-logo strategy + Vite ?raw asset-as-string semantics). -// Plan 04-06 UI-SPEC dark-logo `currentColor` strategy: ambient module -// declaration for Vite `?raw` asset imports. -declare module '*.svg?raw' { - const raw: string; - export default raw; -} -``` +From src/offscreen/recorder.ts:284-291 (verification target — ALREADY shipped; do NOT modify): the getDisplayMedia constraints object carries video: { displaySurface: 'monitor', cursor: 'always' }. The cursor: 'always' literal is at line 285. -From src/offscreen/recorder.ts:285 (verification target — ALREADY shipped): -```typescript -const stream = await navigator.mediaDevices.getDisplayMedia({ - video: { displaySurface: 'monitor', cursor: 'always' }, // ← Plan 01-09 opportunistic; Plan 04-06 verifies - monitorTypeSurfaces: 'include', - audio: false, -} as ...); -``` +From src/welcome/welcome.css:91-95 (existing rule — bare class selector matches both and ; NO change required): .welcome-hero__mark-img { width: 60%; height: 60%; display: block; }. welcome.css is NOT in files_modified — the bare class selector .welcome-hero__mark-img matches identically to . The inline inherits color: var(--mks-fg-inverse) from the .welcome-hero__mark wrapper automatically (CSS color is an inherited property). No CSS edit is needed; the prior plan's "optional welcome.css broadening" is dropped. -From src/welcome/welcome.css:91-95 (existing rule — bare class selector works for both img and svg): -```css -.welcome-hero__mark-img { - width: 60%; - height: 60%; - display: block; -} -``` -No change strictly required; the bare class selector matches both `` and ``. Per UI-SPEC: optional explicit dual-selector for diff-clarity is allowed but not required. +From tests/uat/extension-page-harness.ts:2249-2298 (current A17.8 — Plan 01-10 mark-bundling invariant): the A17 driver locates the welcome JS chunk via the parsed - Task 1: Wave 0 RED — inline-SVG injection unit test + cursor-visibility defensive pin + Task 1: Wave 0 RED — inline-SVG source-contract unit test + cursor-visibility defensive pin tests/welcome/inline-svg.test.ts, tests/build/cursor-visibility.test.ts - tests/background/onboarding.test.ts (welcome-page side-effect assertion shape), tests/i18n/manifest-i18n.test.ts (file-read + string-assertion shape), tests/build/no-remote-fonts.test.ts (single-file grep scaffold), .planning/phases/04-harden-clean-up-optional/04-PATTERNS.md sections "tests/welcome/inline-svg.test.ts" + "tests/build/cursor-visibility.test.ts" + tests/i18n/manifest-i18n.test.ts (node-env file-read + string-assert scaffold — THE canonical analog), tests/build/no-remote-fonts.test.ts (single-file grep scaffold), vitest.config.ts (CONFIRM environment:'node' at line 18 — there is NO jsdom), src/shared/brand/mokosh-mark.svg (current state — stroke='#181b2a'), src/welcome/welcome.ts (current state — ?url import + populateMark), globals.d.ts (current state — no ?raw decl), .planning/phases/04-harden-clean-up-optional/04-PATTERNS.md sections "tests/welcome/inline-svg.test.ts" and "tests/build/cursor-visibility.test.ts" + + tests/welcome/inline-svg.test.ts — node-env, source-level, 3 tests in describe('UI-SPEC dark-logo currentColor strategy — source-level contract'): + - Test A (mokosh-mark.svg recolor): the SVG source contains stroke="currentColor" AND viewBox="0 0 32 32" AND does NOT contain stroke="#181b2a". RED today (SVG still has #181b2a). + - Test B (welcome.ts ?raw import + DOMParser injection): the welcome.ts source contains mokosh-mark.svg?raw AND DOMParser AND replaceChildren AND does NOT contain mokosh-mark.svg?url AND does NOT contain innerHTML. RED today (welcome.ts still uses ?url + ). + - Test C (globals.d.ts ambient decl): the globals.d.ts source contains the *.svg?raw ambient module declaration. RED today (only *.svg?url + *.webm?url declared). + tests/build/cursor-visibility.test.ts — node-env, 1 test: the src/offscreen/recorder.ts source contains cursor: 'always'. GREEN today (already shipped at recorder.ts:285). + - 1. Create `tests/welcome/` directory (NEW — mirrors src/welcome/ to source-tree convention). - 2. Create `tests/welcome/inline-svg.test.ts` per the 3-test contract (PATTERNS.md): - - Setup: vitest jsdom environment (vitest.config.ts already configures this; verify by `grep environment.*jsdom vitest.config.ts`). vi.mock chrome.* + chrome.i18n + chrome.runtime + chrome.storage minimal stubs (welcome page calls chrome.i18n.getMessage in populateI18n; needs a stub). - - Build a minimal welcome.html-like DOM with `
`. - - `await import('../../src/welcome/welcome.ts')` to trigger populateMark. - - Test A: `document.querySelector('.welcome-hero__mark svg')` is non-null AND `.welcome-hero__mark img` is null (DOM is now inline SVG, not img). - - Test B: `document.querySelector('svg.welcome-hero__mark-img')?.getAttribute('stroke') === 'currentColor'` AND `getAttribute('viewBox') === '0 0 32 32'`. - - Test C: `svg.getAttribute('aria-hidden') === 'true'` AND `svg.classList.contains('welcome-hero__mark-img')` AND `svg.getAttribute('role') === 'img'`. + 1. Create the tests/welcome/ directory (NEW — mirrors src/welcome/ per the source-tree convention used by tests/background/, tests/i18n/). - 3. Create `tests/build/cursor-visibility.test.ts` per the single-it scaffold (PATTERNS.md, inverted polarity): - - Imports node:fs + node:path + vitest. - - `const RECORDER_PATH = resolvePath(process.cwd(), 'src/offscreen/recorder.ts');` - - Single `describe('cursor visibility constraint shipped (Plan 01-09 -> verified Plan 04-06)', () => { ... })`. - - Single `it("src/offscreen/recorder.ts contains \\`cursor: 'always'\\` in the getDisplayMedia constraints block", () => { ... })`: - - `const text = readFileSync(RECORDER_PATH, 'utf8');` - - `expect(text).toContain("cursor: 'always'");` + 2. Create tests/welcome/inline-svg.test.ts as a node-env source-contract test (NO DOM, NO runtime DOMParser, NO jsdom — this is the re-plan DEFECT 1 resolution). Mirror the tests/i18n/manifest-i18n.test.ts scaffold: + - import { describe, expect, it } from 'vitest'; + - import { readFileSync } from 'node:fs'; and import { resolve as resolvePath } from 'node:path'; + - Define path constants via resolvePath(process.cwd(), ...): MARK_SVG_PATH for src/shared/brand/mokosh-mark.svg, WELCOME_TS_PATH for src/welcome/welcome.ts, GLOBALS_DTS_PATH for globals.d.ts. + - describe('UI-SPEC dark-logo currentColor strategy — source-level contract', ...) with 3 it() blocks: + - Test A: read MARK_SVG_PATH; assert the text contains stroke="currentColor"; assert it contains viewBox="0 0 32 32"; assert it does NOT contain stroke="#181b2a". + - Test B: read WELCOME_TS_PATH; assert the text contains the substring mokosh-mark.svg?raw; assert it contains DOMParser; assert it contains replaceChildren; assert it does NOT contain mokosh-mark.svg?url; assert it does NOT contain innerHTML (the MV3-CSP discipline pin and the T-04-06-01 mitigation). + - Test C: read GLOBALS_DTS_PATH; assert the text contains the *.svg?raw ambient module declaration string. + - Add a header comment block (per the project docstring convention) explaining: this is a node-env source-contract test; live-DOM injection + currentColor cascade is delegated to the A17.8 harness sub-check in real Chrome; cite the re-plan DEFECT 1 resolution and the deliberate no-DOM-library project stance. - Filter-pipeline form. Absolute imports. TypeScript-strict. + 3. Create tests/build/cursor-visibility.test.ts per the PATTERNS.md cursor-visibility scaffold (node-env, single-it, file-grep): + - import { describe, expect, it } from 'vitest'; import { readFileSync } from 'node:fs'; import { resolve as resolvePath } from 'node:path'; + - Define RECORDER_PATH via resolvePath(process.cwd(), 'src/offscreen/recorder.ts'). + - describe('cursor visibility constraint shipped (Plan 01-09 -> verified Plan 04-06)', ...). + - One it() block: read RECORDER_PATH; assert the text contains the cursor: 'always' literal. - RED gate: `npm test -- tests/welcome/ tests/build/cursor-visibility.test.ts --run`: - - tests/welcome/inline-svg.test.ts: 3 RED tests (current populateMark uses `` not inline ``). - - tests/build/cursor-visibility.test.ts: 1 GREEN test (the literal exists; regression pin). + Filter-pipeline form where loops appear. Absolute imports per project rule. TypeScript-strict. + + RED gate: run npm test against tests/welcome/ and tests/build/cursor-visibility.test.ts. inline-svg.test.ts shows 3 RED (SVG still #181b2a; welcome.ts still ?url/; globals.d.ts has no ?raw decl). cursor-visibility.test.ts shows 1 GREEN (the literal already exists at recorder.ts:285).
- npm test -- tests/welcome/ tests/build/cursor-visibility.test.ts --run 2>&1 | tee /tmp/04-06-task-1-red.log; grep -cE 'FAIL|✗' /tmp/04-06-task-1-red.log; grep -cE 'PASS|✓' /tmp/04-06-task-1-red.log + npm test -- tests/welcome/ tests/build/cursor-visibility.test.ts --run 2>&1 | tail -25 - - File `tests/welcome/inline-svg.test.ts` exists with 3 it() blocks in a `describe('UI-SPEC dark-logo currentColor strategy ...')` block. - - File `tests/build/cursor-visibility.test.ts` exists with 1 it() block. - - Inline-SVG tests: 3 RED today (current welcome.ts uses ``). - - Cursor-visibility test: 1 GREEN today (regression pin for already-shipped literal). - - `grep -c 'currentColor' tests/welcome/inline-svg.test.ts` returns >=2. - - `grep -c "cursor: 'always'" tests/build/cursor-visibility.test.ts` returns >=1. + - File tests/welcome/inline-svg.test.ts exists with 3 it() blocks in a describe('UI-SPEC dark-logo currentColor strategy ...') block. + - File tests/build/cursor-visibility.test.ts exists with 1 it() block. + - Inline-SVG tests: 3 RED today (SVG still #181b2a; welcome.ts still ?url/; globals.d.ts no ?raw). + - Cursor-visibility test: 1 GREEN today (regression pin for the already-shipped literal). + - grep -c 'currentColor' tests/welcome/inline-svg.test.ts returns at least 2. + - grep -c "cursor: 'always'" tests/build/cursor-visibility.test.ts returns at least 1. + - grep -c 'jsdom' tests/welcome/inline-svg.test.ts returns 0 (no jsdom dependency — DEFECT 1 resolution). - 2 test files committed; 3 RED + 1 GREEN-on-arrival. Atomic commit: `test(04-06): Wave 0 — inline-SVG RED + cursor-visibility regression pin`. + 2 test files committed; 3 RED + 1 GREEN-on-arrival. Atomic commit: test(04-06): Wave 0 — inline-SVG source-contract RED + cursor-visibility regression pin.
Task 2: Wave 1 GREEN — SVG stroke recolor + welcome.ts ?raw import + populateMark inline injection + globals.d.ts ambient decl src/shared/brand/mokosh-mark.svg, src/welcome/welcome.ts, globals.d.ts - src/shared/brand/mokosh-mark.svg, src/welcome/welcome.ts (full; ~199 lines), globals.d.ts, .planning/phases/04-harden-clean-up-optional/04-UI-SPEC.md §"Implementation amendment" + src/shared/brand/mokosh-mark.svg, src/welcome/welcome.ts (full; ~199 lines), globals.d.ts (full; ~51 lines), .planning/phases/04-harden-clean-up-optional/04-UI-SPEC.md section "Implementation amendment", .planning/phases/04-harden-clean-up-optional/04-PATTERNS.md section "src/welcome/welcome.ts:46 + 159-179" - Edit 1 — src/shared/brand/mokosh-mark.svg (1-attribute change on root): - - Read the file (~25 lines, single read). - - Use Edit tool to replace `stroke="#181b2a"` with `stroke="currentColor"` ONLY on the root `` element (the 12 `` + 1 `` children inherit stroke from root; they are unchanged). - - Verify: `grep -c 'stroke="#181b2a"' src/shared/brand/mokosh-mark.svg` returns 0 (was 1); `grep -c 'stroke="currentColor"' src/shared/brand/mokosh-mark.svg` returns 1. + Edit 1 — src/shared/brand/mokosh-mark.svg (1-attribute change on the root ): + - Use the Edit tool to replace stroke="#181b2a" with stroke="currentColor" on the root element (line 2). The 12 + 1 children inherit stroke from the root and are UNCHANGED — do not touch them. + - Verify: grep -c 'stroke="#181b2a"' src/shared/brand/mokosh-mark.svg returns 0 (was 1); grep -c 'stroke="currentColor"' src/shared/brand/mokosh-mark.svg returns 1. Edit 2 — src/welcome/welcome.ts (?raw import + populateMark rewrite): - - Read the file (~199 lines, single read). - - Use Edit tool to replace the line 46 import `import markUrl from '../shared/brand/mokosh-mark.svg?url';` with `import markSvg from '../shared/brand/mokosh-mark.svg?raw';`. - - Use Edit tool to replace the entire `populateMark` function body (lines 159-179) with the DOMParser-based inline injection per the `` block above. Preserve the function signature, the `slots` query, the `altText` resolution, the empty-slot warn fallback. The new body inlines the SVG via `parser.parseFromString(markSvg, 'image/svg+xml')` + `slot.replaceChildren(doc.documentElement)`. - - Verify TypeScript-strict: the new code references `markSvg` (was `markUrl`); the IDE/tsc should accept this once globals.d.ts has the ambient decl (Edit 3). + - Use the Edit tool to replace the line-46 import (import markUrl from '../shared/brand/mokosh-mark.svg?url';) with import markSvg from '../shared/brand/mokosh-mark.svg?raw';. Update the surrounding comment block (lines 36-45) to describe the ?raw strategy — cite the UI-SPEC implementation amendment and the currentColor cascade requirement (inline inherits parent CSS color; does not) — instead of the ?url asset-URL strategy. + - Use the Edit tool to replace the populateMark function body (lines 159-179) with the DOMParser-based inline injection per the block. Preserve 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. The new body parses markSvg via parser.parseFromString(markSvg, 'image/svg+xml'), takes doc.documentElement, applies classList.add('welcome-hero__mark-img') + setAttribute('aria-hidden','true') + setAttribute('role','img') + setAttribute('aria-label', altText), then slot.replaceChildren(svg). + - Update the populateMark docstring (lines 134-158) to describe inline- injection (was injection). NEVER use innerHTML. Edit 3 — globals.d.ts (append ambient decl): - - Read the file (~38 lines, single read). - - Use Edit tool to APPEND the `declare module '*.svg?raw'` block after the existing `*.svg?url` block (lines 34-37). Include the explanatory comment block per the `` above. + - Use the Edit tool to APPEND the declare module '*.svg?raw' block immediately after the existing *.svg?url block (lines 34-37), with the explanatory comment per the block. The existing *.webm?url block (lines 47-50) is unrelated and stays. Run gates: - - `npx tsc --noEmit` exits 0 (the ?raw ambient decl makes welcome.ts tsc-clean). - - `npm test -- tests/welcome/inline-svg.test.ts --run` — Wave 0 RED flips to 3/3 GREEN. - - Run full vitest: `npm test -- --run` exits 0 (Plan 04-05 baseline + 4 new tests = >=185 GREEN). - - Run `npm run build` and verify the welcome chunk JS contains the raw SVG source: `grep -c 'viewBox="0 0 32 32"' dist/assets/*.js | head -3` — at least one file should match (the welcome chunk has the raw SVG source as a string literal post-?raw-import). + - npx tsc --noEmit exits 0 (the ?raw ambient decl makes welcome.ts tsc-clean). + - npm test against tests/welcome/inline-svg.test.ts — Wave 0 RED flips to 3/3 GREEN. + - npm test against tests/build/cursor-visibility.test.ts — stays 1/1 GREEN. + - Full vitest (npm test --run). Expected: 187 passed / 1 failed (188 total). The 1 failure is the pre-existing tests/build/strict-meta-json-validation.test.ts red (re-plan DEFECT 3 — logged to deferred-items.md, routed to /gsd-debug, OUT OF SCOPE for Plan 04-06). Verify the failure set is EXACTLY {strict-meta-json-validation.test.ts}. If any OTHER test fails, Plan 04-06 caused a regression and must be fixed before commit. + - npm run build exits 0; verify the welcome chunk JS bundles the raw SVG source — at least one file under dist/assets/*.js contains the currentColor signature from the inlined SVG source. - npx tsc --noEmit && npm test -- tests/welcome/inline-svg.test.ts tests/build/cursor-visibility.test.ts --run 2>&1 | tail -10 | tee /tmp/04-06-task-2.log; grep -c 'currentColor' src/shared/brand/mokosh-mark.svg + npx tsc --noEmit && npm test -- tests/welcome/inline-svg.test.ts tests/build/cursor-visibility.test.ts --run 2>&1 | tail -20 - - `npx tsc --noEmit` exits 0. - - `grep -c 'stroke="currentColor"' src/shared/brand/mokosh-mark.svg` returns 1. - - `grep -c 'stroke="#181b2a"' src/shared/brand/mokosh-mark.svg` returns 0. - - `grep -c 'svg?raw' src/welcome/welcome.ts` returns >=1. - - `grep -c 'svg?url' src/welcome/welcome.ts` returns 0 (post-edit; the old import is gone). - - `grep -c 'DOMParser' src/welcome/welcome.ts` returns >=1. - - `grep -c "declare module '\\*\\.svg\\?raw'" globals.d.ts` returns >=1. - - `npm test -- tests/welcome/inline-svg.test.ts --run` exits 0 with 3/3 GREEN (was 3-RED). - - `npm test -- tests/build/cursor-visibility.test.ts --run` exits 0 with 1/1 GREEN (preserved). - - Full vitest passes: `npm test -- --run` exits 0 with >= 185 GREEN. + - npx tsc --noEmit exits 0. + - grep -c 'stroke="currentColor"' src/shared/brand/mokosh-mark.svg returns 1. + - grep -c 'stroke="#181b2a"' src/shared/brand/mokosh-mark.svg returns 0. + - grep -c 'svg?raw' src/welcome/welcome.ts returns at least 1. + - grep -c 'svg?url' src/welcome/welcome.ts returns 0 (the old import is gone). + - grep -c 'DOMParser' src/welcome/welcome.ts returns at least 1. + - grep -c 'innerHTML' src/welcome/welcome.ts returns 0 (MV3 CSP discipline; T-04-06-01). + - The *.svg?raw ambient module declaration is present in globals.d.ts. + - npm test against tests/welcome/inline-svg.test.ts exits 0 with 3/3 GREEN (was 3-RED). + - npm test against tests/build/cursor-visibility.test.ts exits 0 with 1/1 GREEN (preserved). + - Full vitest: 187 passed / 1 failed; the single failure is exactly tests/build/strict-meta-json-validation.test.ts (pre-existing; out of scope). - SVG + welcome.ts + globals.d.ts edits landed; inline-SVG tests flip 3-RED -> 3-GREEN. Atomic commit: `feat(04-06): Wave 1 — dark-logo currentColor strategy + inline-SVG injection`. + SVG + welcome.ts + globals.d.ts edits landed; inline-SVG tests flip 3-RED to 3-GREEN; only the pre-existing strict-meta-json red remains. Atomic commit: feat(04-06): Wave 1 — dark-logo currentColor strategy + inline-SVG injection. - Task 3: A17.8 harness sub-check update + 01-07-SUMMARY.md back-patch + Task 3: A17.8 harness sub-check update + 01-07-SUMMARY.md back-patch (corrected line set) tests/uat/extension-page-harness.ts, .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md - tests/uat/extension-page-harness.ts:2249-2310 (existing A17.8 region), .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md (full; ~250 lines; line ranges 47/82/109/135/205 per PATTERNS.md), .planning/phases/04-harden-clean-up-optional/04-PATTERNS.md §"A17.8 sub-check update" + §"01-07-SUMMARY.md back-patch" + tests/uat/extension-page-harness.ts:2244-2310 (existing A17.8 region — the A17 driver's Step 7 block), .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md (full; ~244 lines), .planning/phases/04-harden-clean-up-optional/04-PATTERNS.md section "tests/uat/extension-page-harness.ts A17.8 sub-check update" and section "01-07-SUMMARY.md back-patch" - Edit 1 — A17.8 sub-check (tests/uat/extension-page-harness.ts): - - Read the existing A17.8 block (~line 2294 region; ~20-30 lines around it). - - Update the check description and the substring grep targets: - - Current: checks for `data:image/svg+xml,...` data URL OR file URL + `viewBox='0 0 32 32'`. - - New: checks for raw SVG source string literal (`stroke="currentColor"` + `viewBox="0 0 32 32"`) in the welcome chunk JS. - - Either: - (a) Update A17.8 in-place with a single new check description. - (b) Split into A17.8a (raw SVG source bundled) + A17.8b (inline injected at populateMark runtime; checks via DOM querySelector that `.welcome-hero__mark svg` is non-null AND its stroke attribute is 'currentColor'). - - Either approach is acceptable per UI-SPEC §"Acceptance Criteria #3". Pick (a) for minimum-surface; pick (b) if defense-in-depth justifies the extra check. + Edit 1 — A17.8 sub-check (tests/uat/extension-page-harness.ts, the A17 driver Step 7 block at lines 2249-2298): + - Read the existing A17.8 block. + - The ?raw import bundles the SVG SOURCE as a string literal into the welcome chunk (no more data:image/svg+xml data URL). Update the sub-check: + (a) Replace the hasInlineDataUrl computation (jsText.includes('data:image/svg+xml')) and the svgFileUrl regex with a raw-source check: assert jsText contains the canonical mark signature stroke="currentColor" AND viewBox="0 0 32 32" (the inlined raw SVG source). Keep the hasCanonicalViewBox check; it remains valid (the raw source carries the literal viewBox="0 0 32 32"). Update the diag() line and the result.checks.push({ name, expected, actual, passed }) object so the A17.8 name + expected describe the raw-source bundling. + (b) RECOMMENDED defense-in-depth (pick this unless the harness page cannot reach the welcome DOM in this driver): add an A17.8b check that, in the already-loaded welcome page DOM, document.querySelector('.welcome-hero__mark svg') is non-null AND that element's getAttribute('stroke') === 'currentColor' AND document.querySelector('.welcome-hero__mark img') is null. This is the real-Chrome verification of the inline-SVG injection + the currentColor attribute that the node-env unit test deliberately cannot cover (re-plan DEFECT 1 — the unit test pins source; the harness pins real DOM). If A17.8b is added, push it as a second result.checks entry; if the driver structure makes a second DOM query infeasible, document why in a code comment and ship A17.8a only — A17.8a alone still satisfies UI-SPEC Acceptance Criterion #3. + - Note the line range: A17.8 is at ~line 2294 — disjoint from the Plan 04-04/04-05 harness appends at ~line 3970+, which is what lets Plan 04-06 land parallel-safe. - Note line range: A17.8 is at ~line 2294 — disjoint from Plan 04-04/05 appends at ~line 3970+. This is what allows Plan 04-06 to land parallel-safe. - - Edit 2 — 01-07-SUMMARY.md back-patch (5 stale "Phase 5" lines): - - Read 01-07-SUMMARY.md once. - - Locate the 5 stale lines per PATTERNS.md (lines 47, 82, 109, 135, 205 — line numbers approximate; grep for each): - - `grep -nE 'Phase 5|deferred to Phase 5|cursor.*Phase 5' .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md` - - For each match, decide: - - Flip to "Shipped opportunistically in Plan 01-09 (`recorder.ts:285 cursor: 'always'`); verified Phase 4 Plan 04-06" per the back-patch text in PATTERNS.md. - - OR remove the stale framing entirely if the line was forward-pointing TODO. - - Make each edit surgical with surrounding context; do NOT rewrite the whole SUMMARY. + Edit 2 — 01-07-SUMMARY.md back-patch (CORRECTED line set per re-plan DEFECT 2): + - Read .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md (full). + - Re-confirm the line set with: grep -nE 'Phase 5|deferred to Phase 5' .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md (the executor MUST verify the live file rather than trusting cited numbers). + - FLIP these 5 lines — the genuine stale "deferred to Phase 5" framing (cursor:'always' actually shipped opportunistically in Plan 01-09, so the deferral framing is false): + - Line 22 (the affects: dependency-graph entry: "Phase 5 (P1/P2 hardening) — new entry: getDisplayMedia cursor visibility constraint"). Flip to note the constraint shipped in Plan 01-09 and was verified in Phase 4 Plan 04-06. + - Line 47 (key-decisions: "Cursor visibility ... deferred to Phase 5, not back-patched into Phase 1 ..."). Flip to "Cursor visibility (cursor: 'always') was opportunistically shipped in Plan 01-09 at recorder.ts:285 and verified in Phase 4 Plan 04-06; the Phase 1 closure correctly did not back-patch it into Phase 1." + - Line 82 (accomplishments: "Cursor-visibility refinement deferred to Phase 5 ..."). Flip per the PATTERNS.md back-patch text: "Cursor-visibility refinement opportunistically shipped in Plan 01-09 (recorder.ts:285 cursor: 'always'); verified in Phase 4 Plan 04-06 via tests/build/cursor-visibility.test.ts + operator-empirical SAVE flow showing the pointer visible in last_30sec.webm." + - Line 135 (Decisions Made #5: "Cursor visibility refinement deferred to Phase 5 ..."). Flip the framing to reflect the Plan 01-09 opportunistic shipping while keeping the historical point that Phase 1 closure did not widen its own diff. + - Line 205 (User Setup Required: "The cursor visibility refinement (Phase 5) will, when activated, add a cursor: 'always' constraint ..."). Flip to past tense: the constraint was added in Plan 01-09; no user action was required; Phase 4 Plan 04-06 verified it. + - LEAVE these 4 lines UNCHANGED — they are historical descriptions of what specific Plan 01-07-closure commits DID, not forward-looking deferrals: + - Line 40 (key-files modified: describes the ROADMAP.md commit content "cursor-visibility refinement appended to Phase 5"). + - Line 89 (Task Commits: describes the 7df72aa commit "Phase 5 cursor-visibility appended"). + - Line 109 (Files Created/Modified: describes the ROADMAP.md modification "Phase 5 P1/P2 list appended ..."). + - Line 110 (Files Created/Modified: describes the STATE.md modification, including the [Phase 01-07-deferred-to-5] decision-log tag — a historical commit record). + - Each flip is surgical (single line or single bullet, with surrounding context). Do NOT rewrite the whole SUMMARY. After the flips, the SUMMARY narrative is internally consistent: the 5 flipped lines acknowledge Plan 01-09 + Phase 4; the 4 historical lines still accurately describe what the Phase-1-closure commits recorded at the time. Verify gates: - - `npm run build:test` exits 0. - - Run UAT harness: `HEADLESS=1 SKIP_PROD_REBUILD=0 SKIP_LONG_UAT=1 npm run test:uat` exits 0 with 35/35 GREEN (A17.8 updated; the dark-logo strategy land hasn't regressed any other assertion). - - `grep -c 'Phase 5' .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md` returns 0 (or only references that are not the stale-deferral; if 0 is too aggressive due to legitimate "Phase 5" mentions, accept >=1 but verify each remaining mention is contextually correct). - - `grep -c 'Phase 4 Plan 04-06' .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md` returns >=1 (the new back-patch citation). + - npm run build:test exits 0. + - Run the UAT harness (HEADLESS=1 SKIP_PROD_REBUILD=0 SKIP_LONG_UAT=1 npm run test:uat). Expected: the full harness driver set GREEN with the updated A17.8. The dark-logo strategy land must not regress any other assertion. (npm run test:uat performs its own build:test internally; SKIP_PROD_REBUILD/SKIP_LONG_UAT are consumed inside the harness.) + - grep -c 'Phase 4 Plan 04-06' .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md returns at least 1 (the new back-patch citation appears in the flipped lines). + - The 5 flipped lines no longer carry "deferred to Phase 5" framing; the 4 historical lines still mention Phase 5 as a record of what the closure commits did — that is correct and expected. - npm run build:test && HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat 2>&1 | tail -10 | tee /tmp/04-06-task-3.log; grep -c '35/35' /tmp/04-06-task-3.log + npm run build:test 2>&1 | tail -5 && HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat 2>&1 | tail -15 - - `npm run build:test` exits 0. - - UAT harness 35/35 GREEN with the updated A17.8. - - `grep -c 'stroke=\"currentColor\"' tests/uat/extension-page-harness.ts | grep -v '^#'` returns >=1 (new A17.8 grep target). - - `grep -c 'data:image/svg+xml' tests/uat/extension-page-harness.ts | grep -v '^#'` returns 0 (old A17.8 grep target removed; verify by counting lines not in comments). - - 01-07-SUMMARY.md back-patched: stale "deferred to Phase 5" lines flipped. - - `grep -c 'Phase 4 Plan 04-06' .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md` returns >=1. + - npm run build:test exits 0. + - The UAT harness driver set is GREEN with the updated A17.8 (no harness regression from the dark-logo land). + - grep -c 'stroke="currentColor"' tests/uat/extension-page-harness.ts returns at least 1 (the new A17.8 raw-source grep target). + - The data:image/svg+xml grep target is removed from the A17.8 block (the ?raw import no longer produces a data URL). + - 01-07-SUMMARY.md lines 22, 47, 82, 135, 205 no longer carry the stale "deferred to Phase 5" framing. + - 01-07-SUMMARY.md lines 40, 89, 109, 110 are unchanged (historical commit-description lines). + - grep -c 'Phase 4 Plan 04-06' .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md returns at least 1. - A17.8 updated + 01-07-SUMMARY.md back-patched. Atomic commit: `feat(04-06): A17.8 inline-SVG check + back-patch 01-07-SUMMARY Phase-5 references`. + A17.8 updated to assert raw-source bundling (+ optional real-Chrome A17.8b); 01-07-SUMMARY.md back-patched with the corrected 22/47/82/135/205 line set. Atomic commit: feat(04-06): A17.8 inline-SVG check + back-patch 01-07-SUMMARY stale Phase-5 lines. - Task 4: Operator empirical UAT — dark-mode aesthetic judgment on welcome hero - (no files modified — verification-only checkpoint; gates against the existing dist/ + a real Chrome instance) + Task 4: Operator empirical UAT — dark-mode aesthetic judgment on welcome hero (from screenshots) + (no source files modified — verification-only checkpoint; gates against the existing dist/ + Puppeteer screenshots) - Step 1 — Pre-checkpoint bundle gates (orchestrator-driven; per saved memory `feedback-pre-checkpoint-bundle-gates.md`; MUST PASS before surfacing Step 2 to operator): - 1. `npm run build` -> exit 0; dist/ populated. - 2. SW CSP-safety grep (REVISION iter-2 — canonical SW chunk glob per RESEARCH Q1; was `index*-bg.js` which matches no files): `grep -cE 'new Function\(|eval\(' dist/assets/index.ts-*.js` -> 0 hits (Plan 04-02 effect preserved). The pipe-counted form ensures a SPURIOUS-PASS-PROOF gate — if the glob ever matches zero files, the gate registers as missing rather than silent 0. - 3. SW Node-globals grep (REVISION iter-2 — canonical SW chunk glob): `grep -cE 'Buffer\.from|Buffer\.alloc|process\.|require\(' dist/assets/index.ts-*.js` -> 0 hits. - 4. DOM-globals grep (REVISION iter-2 — canonical SW chunk glob): `grep -E '(window\.|document\.)' dist/assets/index.ts-*.js | grep -vE '^//|globalThis|^$' | wc -l` -> 0 hits in SW chunk. - 5. Tier-1 SW-bundle-import gate: `npx vitest run tests/background/sw-bundle-import.test.ts` -> GREEN. - 6. Tier-1 FORBIDDEN_HOOK_STRINGS gate: `npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts` -> 12 strings, 0 hits each -> GREEN. + Step 1 — Pre-checkpoint bundle gates (orchestrator-driven; per saved memory feedback-pre-checkpoint-bundle-gates.md; ALL MUST PASS before surfacing Step 3 to the operator): - **REVISION iter-2 — Glob existence pre-gate (BLOCKER 1 mitigation):** before running Gates 2/3/4, verify the glob actually matches at least one file: `ls dist/assets/index.ts-*.js 2>/dev/null | wc -l` MUST return >= 1. If 0, the build is broken (SW chunk missing) OR the canonical filename pattern shifted — fail loudly rather than silently 0-grep. + Glob-existence pre-gate (run FIRST — guards against silent 0-hit passes on a mis-glob): ls dist/assets/index.ts-*.js must list at least one file. If 0, the build is broken (SW chunk missing) OR the canonical filename pattern shifted — fail loudly rather than 0-grep silently. - Step 2 — Operator-driven empirical UAT cycle (manual; ~3-5 min): See `` block below for the operator-facing instructions. + 1. npm run build exits 0; dist/ populated. + 2. SW CSP-safety grep (canonical SW chunk glob): grep for new Function( and eval( in dist/assets/index.ts-*.js returns 0 hits (Plan 04-02 effect preserved). + 3. SW Node-globals grep (canonical SW chunk glob): grep for Buffer.from / Buffer.alloc / process. / require( in dist/assets/index.ts-*.js returns 0 hits. + 4. DOM-globals grep (canonical SW chunk glob): grep for window. / document. in dist/assets/index.ts-*.js, excluding comment lines and globalThis idioms, returns 0 hits. + 5. Tier-1 SW-bundle-import gate: vitest run tests/background/sw-bundle-import.test.ts is GREEN. + 6. Tier-1 FORBIDDEN_HOOK_STRINGS gate: vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts is GREEN (12 strings, 0 hits each — Plan 04-06 adds no new __MOKOSH_UAT__-gated symbols, so the inventory stays at 12). - Step 3 — Resume signal handling: orchestrator waits for operator response per `` block below. + Also confirm the vitest baseline: full vitest is 187 passed / 1 failed, where the single failure is exactly tests/build/strict-meta-json-validation.test.ts (pre-existing; re-plan DEFECT 3; out of scope). And the UAT harness driver set is GREEN. + + Step 2 — Produce the verification artifact (light + dark Puppeteer screenshots; per feedback-trust-harness-over-manual-uat.md the operator judges from screenshots, NOT a manual Chrome session): + - Load dist/ as an unpacked extension and open the welcome page in a Puppeteer-driven Chrome. + - Capture a LIGHT-surface screenshot of the welcome hero (default OS appearance — the regression-baseline shot). + - Capture a DARK-surface screenshot of the welcome hero. Force dark via Puppeteer's emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]) (or the equivalent CDP Emulation.setEmulatedMedia) so the .dark / [data-theme="dark"] token overrides in src/shared/tokens.css lines 234-251 apply, then re-screenshot the welcome hero. + - Save both screenshots to a stable path (e.g. /tmp/04-06-welcome-hero-light.png and /tmp/04-06-welcome-hero-dark.png) and surface both paths to the operator in the checkpoint message. + + Step 3 — Operator-driven empirical judgment: see the block below. + + Step 4 — Resume signal handling: the orchestrator waits for the operator response per the block below. - npm run build && ls dist/assets/index.ts-*.js | wc -l; grep -cE 'new Function\(|eval\(' dist/assets/index.ts-*.js; HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat 2>&1 | tail -3 + npm run build 2>&1 | tail -3 && ls dist/assets/index.ts-*.js - Operator returns "approved" verbatim OR describes a specific issue; if issue, route via `/gsd-debug` per `feedback-gsd-ceremony-for-fixes.md`. - UI-SPEC dark-logo contrast strategy: SVG stroke recolor + inline-SVG injection + ambient module decl + harness update + cursor-visibility regression pin + docs back-patch. Pre-checkpoint bundle gates 6/6 PASS: - - Gate 1: `npm run build` exit 0 - - Gate 2: `grep -c 'new Function' dist/assets/index.ts-*.js` returns 0 (Plan 04-02 effect preserved; canonical SW chunk glob) - - Gate 3: SW Node-globals grep clean against `dist/assets/index.ts-*.js` - - Gate 4: DOM-globals bundled-lib idiom (typeof-guarded) against `dist/assets/index.ts-*.js` - - Gate 5: Tier-1 SW-bundle-import unit gate GREEN - - Gate 6: FORBIDDEN_HOOK_STRINGS at 12 (no change) - - Glob-existence pre-gate: `ls dist/assets/index.ts-*.js | wc -l` returns >= 1 (prevents silent 0-hit pass on mis-glob) - - Plus pre-checkpoint vitest baseline >=185 GREEN; UAT harness 35/35 GREEN. + UI-SPEC dark-logo contrast strategy: SVG stroke recolor (stroke="currentColor") + inline-SVG injection in welcome.ts (DOMParser + replaceChildren; no , no innerHTML, no eval) + globals.d.ts *.svg?raw ambient decl + A17.8 harness raw-source assertion + cursor-visibility node-env regression pin + 01-07-SUMMARY.md back-patch (5 stale Phase-5 lines flipped). Pre-checkpoint bundle gates 6/6 PASS: + - Gate 1: npm run build exit 0. + - Gate 2: new Function / eval count in dist/assets/index.ts-*.js returns 0 (Plan 04-02 effect preserved; canonical SW chunk glob). + - Gate 3: SW Node-globals grep clean against dist/assets/index.ts-*.js. + - Gate 4: DOM-globals grep clean against dist/assets/index.ts-*.js. + - Gate 5: Tier-1 SW-bundle-import unit gate GREEN. + - Gate 6: FORBIDDEN_HOOK_STRINGS at 12 (no change — Plan 04-06 adds no test-mode symbols). + - Glob-existence pre-gate: ls dist/assets/index.ts-*.js lists at least one file (prevents silent 0-hit pass on a mis-glob). + - Plus: vitest 187 passed / 1 pre-existing fail (strict-meta-json-validation.test.ts, out of scope); UAT harness driver set GREEN. - Auto-confirms: - - mokosh-mark.svg now uses `stroke="currentColor"` (1-line semantic change) - - welcome.ts inline-injects the SVG via DOMParser (no , no innerHTML, no eval) - - On the light surface (current welcome page rendering), the mark appears identical to Plan 01-10 cycle-2 ack — `color: var(--mks-fg-inverse)` at `.welcome-hero__mark` cascades to the inline SVG's `currentColor`, resolving to `--mks-linen-50` (off-white) stroke on the madder-orange BG. - - PNG toolbar icons unchanged (Chrome auto-inverts on dark toolbars). - - cursor: 'always' already at recorder.ts:285 (Plan 01-09 opportunistic; verified by tests/build/cursor-visibility.test.ts). + Auto-confirmed (no operator action needed): + - mokosh-mark.svg now uses stroke="currentColor" (1-attribute semantic change; 13 children unchanged). + - welcome.ts inline-injects the SVG via DOMParser + replaceChildren (no , no innerHTML, no eval). + - On the LIGHT surface the mark appears identical to the Plan 01-10 cycle-2 ack — color: var(--mks-fg-inverse) at .welcome-hero__mark cascades to the inline SVG's currentColor, resolving to linen-50 (off-white) stroke on the madder-orange wrapper background. + - PNG toolbar icons unchanged (Chrome auto-inverts dark icons on dark toolbars). + - cursor: 'always' already at recorder.ts:285 (Plan 01-09 opportunistic); pinned by tests/build/cursor-visibility.test.ts. - 1. Build the production bundle: `npm run build` exit 0. - 2. Load `dist/` as unpacked extension into Chrome (chrome://extensions/ -> Developer mode -> Load unpacked -> select dist/). - 3. Open the welcome page (it should auto-open on first install; if not, navigate to chrome-extension://<ID>/src/welcome/welcome.html). - 4. **Light-surface verification (regression baseline):** the welcome hero mark renders inside the madder-600 circle with linen-50 (off-white) stroke. Visually identical to Plan 01-10 cycle-2 ack 2026-05-20 "All good". Expected appearance unchanged. - 5. **Dark-surface aesthetic judgment (the operator-empirical decision):** enable your OS dark mode (macOS: System Settings -> Appearance -> Dark; Windows: Settings -> Personalization -> Colors -> Dark; Linux/GNOME: Settings -> Appearance -> Dark; OR Chrome's own setting at chrome://settings/appearance -> Theme: Dark). - 6. Reload the welcome page. The mark inherits the `.dark` class behavior from `src/shared/tokens.css` lines 234-251 (which overrides `--mks-fg-inverse` to `--mks-ink-900` deep indigo on dark surfaces). The wrapper background stays madder; the stroke darkens to deep indigo on dark surfaces. - 7. Judge the visual contrast: does the mark remain legible on the dark wrapper-on-dark-OS combination? The strategy aims to keep the mark crisp on EITHER light or dark surfaces by inverting the stroke when the surface flips. - 8. (Optional) Test a captured recording: start recording, click around, SAVE -> open `video/last_30sec.webm` -> verify the operator's cursor is visible in playback (the cursor: 'always' verification per ROADMAP cursor visibility item). + The orchestrator has produced two screenshots of the welcome hero: + - /tmp/04-06-welcome-hero-light.png — LIGHT surface (default OS appearance). + - /tmp/04-06-welcome-hero-dark.png — DARK surface (prefers-color-scheme: dark emulated). - **Resume signals:** - - "approved" — dark-mode aesthetic acceptable; Phase 4 dark-logo strategy closed. - - Describe issues if any (e.g., "stroke too thin on dark; needs heavier stroke-width" or "wrapper madder clashes with dark BG; want a different accent"). Plan execution will route via `/gsd-debug` per `feedback-gsd-ceremony-for-fixes.md` (no hot-edits to the UI-SPEC or canonical design system). + 1. Open /tmp/04-06-welcome-hero-light.png. LIGHT-surface regression check: the welcome hero mark renders inside the madder-600 circle with a linen-50 (off-white) stroke — visually identical to the Plan 01-10 cycle-2 operator ack 2026-05-20 "All good". Expected appearance unchanged. + 2. Open /tmp/04-06-welcome-hero-dark.png. DARK-surface aesthetic judgment (the operator-empirical decision): on dark mode the .dark token block in src/shared/tokens.css lines 234-251 overrides --mks-fg-inverse to --mks-ink-900 (deep indigo); the wrapper background stays madder; the inline SVG's currentColor therefore resolves to deep indigo. + 3. Judge the visual contrast: does the mark remain legible and aesthetically acceptable on the dark surface? The strategy aims to keep the mark crisp on EITHER a light or a dark surface by inverting the stroke when the surface flips. + 4. (Optional, only if you want to also exercise the cursor path) Load dist/ as an unpacked extension, start a recording, click around, SAVE, then open video/last_30sec.webm and confirm the operator cursor is visible in playback (the cursor: 'always' verification for the ROADMAP cursor visibility item — this is already pinned automatically by tests/build/cursor-visibility.test.ts, so it is optional here). + + Resume signals: + - "approved" — the dark-mode aesthetic is acceptable; the Phase 4 dark-logo strategy is closed. + - Otherwise describe the issue specifically (e.g. "stroke too thin on dark; needs heavier stroke-width", or "wrapper madder clashes with the dark BG; want a different accent"). Plan execution routes the issue via /gsd-debug per feedback-gsd-ceremony-for-fixes.md — no hot-edits to the UI-SPEC or the canonical design system. - Type "approved" or describe issues (e.g., "stroke too thin", "need heavier weight on dark", "want a different accent"). + Type "approved" or describe the issue (e.g. "stroke too thin", "need heavier weight on dark", "want a different accent"). + Operator returns "approved" verbatim OR describes a specific issue; if an issue, route via /gsd-debug per feedback-gsd-ceremony-for-fixes.md.
@@ -457,58 +416,61 @@ The Vite SW chunk is `dist/assets/index.ts-.js` (verified empirically — | Boundary | Description | |----------|-------------| -| Vite ?raw import -> compile-time string literal | SVG source is bundled as a compile-time string; NO runtime untrusted input flows into DOMParser; the parser's input is statically known at build time | -| DOMParser parse -> live DOM | parseFromString('image/svg+xml') is a CSP-safe operation per MDN (does NOT execute scripts in the parsed SVG; runs in a sandboxed parser context); no innerHTML; no eval | -| Inline CSS color inheritance | inline SVG (parsed + appended via DOMParser) inherits parent CSS color per W3C SVG2 §13.3; `currentColor` on stroke resolves to the wrapper's `color: var(--mks-fg-inverse)` cascade | +| Vite ?raw import to compile-time string literal | SVG source is bundled as a compile-time string; NO runtime untrusted input flows into DOMParser; the parser input is statically known at build time | +| DOMParser parse to live DOM | parseFromString('image/svg+xml') is a CSP-safe operation per MDN — it does NOT execute scripts in the parsed SVG and runs in a sandboxed parser context; no innerHTML; no eval | +| Inline CSS color inheritance | inline SVG (parsed + appended via DOMParser + replaceChildren) inherits parent CSS color per W3C SVG2 §13.3; currentColor on stroke resolves to the wrapper's color: var(--mks-fg-inverse) cascade | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| -| T-04-06-01 | Tampering | a future developer might switch DOMParser to innerHTML for "simplicity" — innerHTML in a content script context is unsafe (CSP risk + script-execution surface) | mitigate | Inline code comment + executor-pattern in the action: "NEVER use innerHTML — DOMParser + appendChild only (MV3 CSP discipline)"; A17.8 harness check verifies the inline-SVG DOM shape (defense in depth) | -| T-04-06-02 | Information Disclosure | the inline SVG has no PII or secret; it's the canonical Mokosh brand mark | accept | Static brand asset; out-of-tree threat surface | -| T-04-06-03 | Spoofing | cursor visibility in captured frames could leak sensitive UI overlay state (e.g., 2FA OTP digit operator was about to type) — but this is a known and intended diagnostic feature per Plan 01-07 obs; out of scope for password masking per D-P3-02 charter | accept | Operator-side responsibility per CONTEXT charter; v2 candidate per CONTEXT Deferred Ideas | -| T-04-06-04 | Repudiation (REVISION iter-2 — BLOCKER 1) | A mis-globbed `dist/assets/index*-bg.js` matches no files; `grep -c new Function` returns 0; Gate 2 spuriously PASSES even if the setimmediate polyfill never landed | mitigate | Canonical SW chunk glob `dist/assets/index.ts-*.js` verified empirically (file `index.ts-8LkXuqac.js` exists on disk; pinned by RESEARCH Q1 lines 142/180/196); pre-gate `ls dist/assets/index.ts-*.js \| wc -l >= 1` validates the glob matches BEFORE running the grep gates | +| T-04-06-01 | Tampering | a future developer might switch DOMParser to innerHTML for "simplicity" — innerHTML in an extension-page context is unsafe (CSP risk + script-execution surface) | mitigate | Inline code comment in populateMark + the executor instruction "NEVER use innerHTML — DOMParser + replaceChildren only (MV3 CSP discipline)"; tests/welcome/inline-svg.test.ts Test B asserts the welcome.ts source does NOT contain innerHTML; the A17.8 harness check verifies the inline-SVG DOM shape in real Chrome (defense in depth) | +| T-04-06-02 | Information Disclosure | the inline SVG carries no PII or secret — it is the canonical Mokosh brand mark | accept | Static brand asset; out-of-tree threat surface | +| T-04-06-03 | Spoofing | cursor visibility in captured frames could leak transient UI overlay state (e.g. a 2FA OTP digit the operator was about to type) — but this is a known and intended diagnostic feature per the Plan 01-07 observation; password masking is out of scope per the D-P3-02 charter | accept | Operator-side responsibility per the CONTEXT charter; v2 candidate per CONTEXT Deferred Ideas | +| T-04-06-04 | Repudiation | a mis-globbed dist/assets/index*-bg.js matches no files; grep -c new Function returns 0; Gate 2 spuriously PASSES even if a CSP regression slipped in | mitigate | Canonical SW chunk glob dist/assets/index.ts-*.js (verified empirically; pinned by RESEARCH Q1); the Task 4 glob-existence pre-gate (ls dist/assets/index.ts-*.js lists at least one file) runs BEFORE the grep gates and fails loudly if the glob matches nothing | +| T-04-06-SC | Tampering | npm/pip/cargo installs introducing a supply-chain risk | accept | Plan 04-06 introduces NO new package-manager installs — files_modified contains only existing source/test/spec/docs files; no devDependency is added (the DEFECT 1 resolution explicitly rejected adding jsdom). Package legitimacy gate is therefore not triggered. | -- `npx tsc --noEmit` exits 0. -- `npm run build` exits 0. -- vitest baseline +4 to >=185 GREEN (Task 1's 4 tests + Task 2 flips 3-RED -> 3-GREEN). -- `grep -c 'stroke="currentColor"' src/shared/brand/mokosh-mark.svg` returns 1. -- `grep -c "svg?raw" src/welcome/welcome.ts` returns 1. -- `grep -c "declare module '\\*\\.svg\\?raw'" globals.d.ts` returns 1. -- UAT harness 35/35 GREEN with updated A17.8 (no harness count change; assertion quality changes). -- 01-07-SUMMARY.md back-patched. -- Operator empirical ack received (Task 4 resume signal "approved"). -- Pre-checkpoint bundle gates 6/6 PASS (REVISION iter-2 — canonical SW chunk glob `dist/assets/index.ts-*.js` used across Gates 2/3/4; glob-existence pre-gate `ls dist/assets/index.ts-*.js | wc -l >= 1` validates). +- npx tsc --noEmit exits 0. +- npm run build exits 0. +- Full vitest: 187 passed / 1 failed (188 total); the single failure is exactly tests/build/strict-meta-json-validation.test.ts (pre-existing; re-plan DEFECT 3; out of scope, routed to /gsd-debug). +4 new tests over the 183-GREEN baseline (Task 1's 3 RED inline-svg tests flip GREEN in Task 2; Task 1's 1 cursor-visibility test is GREEN throughout). +- grep -c 'stroke="currentColor"' src/shared/brand/mokosh-mark.svg returns 1. +- grep -c 'svg?raw' src/welcome/welcome.ts returns at least 1; grep -c 'svg?url' src/welcome/welcome.ts returns 0. +- grep -c 'innerHTML' src/welcome/welcome.ts returns 0. +- The *.svg?raw ambient module declaration is present in globals.d.ts. +- UAT harness driver set GREEN with the updated A17.8 (assertion quality changes; driver count unchanged). +- 01-07-SUMMARY.md back-patched: lines 22/47/82/135/205 flipped; lines 40/89/109/110 unchanged. +- Operator empirical ack received (Task 4 resume signal "approved") from the dark + light screenshot artifact. +- Pre-checkpoint bundle gates 6/6 PASS (canonical SW chunk glob dist/assets/index.ts-*.js across Gates 2/3/4; glob-existence pre-gate validates the glob first). - PNG toolbar icons byte-identical (no change to scripts/rasterize-icons.sh or icons/*.png). - UI-SPEC dark-logo strategy landed end-to-end (Tasks 1-3 automation + Task 4 operator ack). -- Cursor visibility VERIFIED at recorder.ts:285 via regression-pin test + 01-07-SUMMARY back-patch (RESEARCH Finding 4 closure). -- Inline-SVG injection contract pinned by 3 unit tests + 1 harness assertion update. +- Cursor visibility VERIFIED at recorder.ts:285 via the node-env regression-pin test + the 01-07-SUMMARY back-patch (RESEARCH Finding 4 closure). +- Inline-SVG injection contract pinned by 3 node-env source-level unit tests (DEFECT 1 strategy) + the A17.8 real-Chrome harness assertion. - ROADMAP cursor visibility item GREEN. - ROADMAP dark-surface logo contrast item GREEN. -- Operator empirical ack received on dark-mode visual aesthetic. -- Pre-checkpoint bundle gates 6/6 PASS preserved (REVISION iter-2 — canonical glob). -- UAT harness 35/35 GREEN preserved. -- vitest baseline +4 (Plan 04-05 baseline 181 -> Plan 04-06 baseline >= 185). +- Operator empirical ack received on the dark-mode visual aesthetic, judged from the Puppeteer screenshot artifact. +- Pre-checkpoint bundle gates 6/6 PASS preserved (canonical glob). +- UAT harness driver set GREEN preserved. +- Vitest: 187 GREEN / 1 pre-existing RED (strict-meta-json-validation.test.ts is out of scope and explicitly tolerated). -After completion, create `.planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md` capturing: -- mokosh-mark.svg diff (1-char change documented) -- welcome.ts diff (?raw + DOMParser + populateMark rewrite) -- globals.d.ts diff (ambient decl append) -- A17.8 harness sub-check diff (old data-URL grep -> new raw-source grep) -- 01-07-SUMMARY.md back-patch (5 stale Phase-5 lines flipped) -- 2 new test files (inline-svg + cursor-visibility) with RED->GREEN cycle commits -- Operator empirical UAT verbatim ack (e.g., "approved", "all good", or specific issues) -- Pre-checkpoint bundle gates 6/6 PASS evidence (grep outputs against canonical `dist/assets/index.ts-*.js` glob per REVISION iter-2) -- vitest before/after (181 -> >=185) -- UAT 35/35 GREEN preservation -- Commit refs (Tasks 1 + 2 + 3 + 4 ack) +After completion, create .planning/phases/04-harden-clean-up-optional/04-06-SUMMARY.md capturing: +- mokosh-mark.svg diff (1-attribute change documented). +- welcome.ts diff (?raw import + DOMParser + populateMark rewrite). +- globals.d.ts diff (*.svg?raw ambient decl append). +- A17.8 harness sub-check diff (old data-URL grep to new raw-source grep; note whether A17.8b real-Chrome DOM check was added). +- 01-07-SUMMARY.md back-patch (5 stale Phase-5 lines flipped — 22/47/82/135/205; 4 historical lines left — 40/89/109/110). +- 2 new test files (inline-svg source-contract + cursor-visibility) with the RED-to-GREEN cycle commits. +- The DEFECT 1 test-strategy decision recorded (node-env source-contract, no jsdom) so future plans understand why inline-svg.test.ts does not exercise live DOM. +- Operator empirical UAT verbatim ack (e.g. "approved", or the specific issue raised). +- Pre-checkpoint bundle gates 6/6 PASS evidence (grep outputs against the canonical dist/assets/index.ts-*.js glob). +- vitest before/after (183 GREEN + 1 pre-existing RED to 187 GREEN + 1 pre-existing RED). +- UAT harness driver-set GREEN preservation. +- Note that tests/build/strict-meta-json-validation.test.ts remains a pre-existing RED out of Plan 04-06 scope (routed to /gsd-debug per deferred-items.md). +- Commit refs (Tasks 1 + 2 + 3 + 4 ack). -