---
phase: 04
slug: harden-clean-up-optional
plan: 06
type: execute
wave: 5
depends_on:
  - 01
  - 02
  - 03
  - 04
  - 05
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
  - tests/build/cursor-visibility.test.ts
  - .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md
autonomous: false
requirements: []
tags:
  - visual-polish
  - dark-logo-contrast
  - ui-spec
  - currentColor
  - inline-svg
  - cursor-visibility-verification
  - charter-d-p4-03
  - operator-empirical
user_setup: []
must_haves:
  truths:
    - "src/shared/brand/mokosh-mark.svg root <svg> element carries `stroke=\"currentColor\"` (was `stroke=\"#181b2a\"`); the 12 <line> + 1 <rect> 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 <svg> into the slot (NOT <img>)"
    - "Injected inline <svg> 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 <svg>, 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 <svg> 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')"
    - "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"
      provides: "Single-attribute change on root <svg>: stroke='#181b2a' -> stroke='currentColor'. 13 child elements unchanged."
      contains: "stroke=\"currentColor\""
    - path: "src/welcome/welcome.ts"
      provides: "?raw import + DOMParser-based inline SVG injection in populateMark (replaces <img> 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"
    - path: "globals.d.ts"
      provides: "Ambient module declaration for *.svg?raw imports (4-line block mirror of existing *.svg?url)"
      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"
      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"
      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 <svg> injection (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)"
      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"
      pattern: "DOMParser"
    - from: "Injected inline <svg> 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\\)"
    - 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 <svg>"
      pattern: "A17\\.8"
---

<objective>
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:

1. SVG attribute change: `stroke="#181b2a"` → `stroke="currentColor"` on the root `<svg>` of `src/shared/brand/mokosh-mark.svg`.
2. Inline-SVG injection in welcome.ts: `<img>` 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.

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).

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.

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".

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).
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-harden-clean-up-optional/04-CONTEXT.md
@.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/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
@src/welcome/welcome.ts
@src/welcome/welcome.css
@globals.d.ts
@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
@tests/i18n/manifest-i18n.test.ts
@tests/build/no-remote-fonts.test.ts

<interfaces>
<!-- Key shapes the executor consumes directly. Extracted from codebase 2026-05-21. -->

From src/shared/brand/mokosh-mark.svg (current — 13 elements; ~25 lines):
```svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" stroke="#181b2a" stroke-width="2.25" stroke-linecap="square" stroke-linejoin="miter">
  <rect x="2.5" y="2.5" width="27" height="27" rx="3.5" ry="3.5"></rect>
  <line x1="12" y1="2.5" x2="12" y2="11.5"></line>
  ... (11 more line + rect children)
</svg>
```

After Plan 04-06 (1-character semantic change on root <svg>):
```svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="square" stroke-linejoin="miter">
  ... (13 child elements UNCHANGED)
</svg>
```

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 — <img> injection):
```typescript
function populateMark(): void {
  const slots = Array.from(
    document.querySelectorAll<HTMLElement>('[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');
  }
}
```

After Plan 04-06 (DOMParser inline-SVG injection):
```typescript
function populateMark(): void {
  const slots = Array.from(
    document.querySelectorAll<HTMLElement>('[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');
  }
}
```

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).

From globals.d.ts (current ambient decl for ?url at lines 34-37):
```typescript
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;
}

// 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: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 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 `<img class="welcome-hero__mark-img">` and `<svg class="welcome-hero__mark-img">`. Per UI-SPEC: optional explicit dual-selector for diff-clarity is allowed but not required.

From tests/uat/extension-page-harness.ts:2249-2294 (current A17.8 — Plan 01-10 mark-bundling invariant):
- Current: asserts welcome chunk JS contains `data:image/svg+xml,...` data URL + canonical viewBox.
- Post-Plan-04-06: assert welcome chunk JS contains raw SVG source as string literal with `currentColor` + canonical viewBox. May split into A17.8a (raw bundling) + A17.8b (runtime inline <svg> injection check via DOM query).

SW chunk canonical glob (REVISION iter-2 — plan-checker BLOCKER 1):
The Vite SW chunk is `dist/assets/index.ts-<hash>.js` (verified empirically — `dist/assets/index.ts-8LkXuqac.js` confirmed on disk; also pinned by RESEARCH Q1 finding lines 142/180/196). All Phase 4 bundle gates MUST use the `dist/assets/index.ts-*.js` glob, NOT `dist/assets/index*-bg.js` (which matches no files and would silently return 0 on grep — spurious PASS). Task 4 pre-checkpoint gates use this canonical glob for Gates 2/3/4.
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Wave 0 RED — inline-SVG injection unit test + cursor-visibility defensive pin</name>
  <files>tests/welcome/inline-svg.test.ts, tests/build/cursor-visibility.test.ts</files>
  <read_first>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"</read_first>
  <action>
    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 `<div data-mokosh-slot="mark"></div>`.
       - `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'`.

    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'");`

    Filter-pipeline form. Absolute imports. TypeScript-strict.

    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 `<img>` not inline `<svg>`).
    - tests/build/cursor-visibility.test.ts: 1 GREEN test (the literal exists; regression pin).
  </action>
  <verify>
    <automated>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</automated>
  </verify>
  <acceptance_criteria>
    - 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 `<img>`).
    - 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.
  </acceptance_criteria>
  <done>2 test files committed; 3 RED + 1 GREEN-on-arrival. Atomic commit: `test(04-06): Wave 0 — inline-SVG RED + cursor-visibility regression pin`.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Wave 1 GREEN — SVG stroke recolor + welcome.ts ?raw import + populateMark inline injection + globals.d.ts ambient decl</name>
  <files>src/shared/brand/mokosh-mark.svg, src/welcome/welcome.ts, globals.d.ts</files>
  <read_first>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"</read_first>
  <action>
    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 `<svg>` element (the 12 `<line>` + 1 `<rect>` 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 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 `<interfaces>` 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).

    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 `<interfaces>` above.

    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).
  </action>
  <verify>
    <automated>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</automated>
  </verify>
  <acceptance_criteria>
    - `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.
  </acceptance_criteria>
  <done>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`.</done>
</task>

<task type="auto">
  <name>Task 3: A17.8 harness sub-check update + 01-07-SUMMARY.md back-patch</name>
  <files>tests/uat/extension-page-harness.ts, .planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md</files>
  <read_first>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"</read_first>
  <action>
    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 <svg> 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.

    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.

    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).
  </action>
  <verify>
    <automated>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</automated>
  </verify>
  <acceptance_criteria>
    - `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.
  </acceptance_criteria>
  <done>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`.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 4: Operator empirical UAT — dark-mode aesthetic judgment on welcome hero</name>
  <files>(no files modified — verification-only checkpoint; gates against the existing dist/ + a real Chrome instance)</files>
  <action>
    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.

    **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.

    Step 2 — Operator-driven empirical UAT cycle (manual; ~3-5 min): See `<how-to-verify>` block below for the operator-facing instructions.

    Step 3 — Resume signal handling: orchestrator waits for operator response per `<resume-signal>` block below.
  </action>
  <verify>
    <automated>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</automated>
  </verify>
  <done>Operator returns "approved" verbatim OR describes a specific issue; if issue, route via `/gsd-debug` per `feedback-gsd-ceremony-for-fixes.md`.</done>
  <what-built>
    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.

    Auto-confirms:
    - mokosh-mark.svg now uses `stroke="currentColor"` (1-line semantic change)
    - welcome.ts inline-injects the SVG via DOMParser (no <img>, 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).
  </what-built>
  <how-to-verify>
    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://&lt;ID&gt;/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).

    **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).
  </how-to-verify>
  <resume-signal>Type "approved" or describe issues (e.g., "stroke too thin", "need heavier weight on dark", "want a different accent").</resume-signal>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| 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 <svg> 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 |

## 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 |
</threat_model>

<verification>
- `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).
- PNG toolbar icons byte-identical (no change to scripts/rasterize-icons.sh or icons/*.png).
</verification>

<success_criteria>
- 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.
- 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).
</success_criteria>

<output>
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)
</output>
</content>
