fix(01-10): welcome page mark — bundle canonical mokosh-mark.svg + replace placeholder

Plan 01-10 must_have #9 path-A swap-in (landed 2026-05-20 per debug
session 01-10-welcome-page-missing-mark). Closes the planning-coverage
gap where Plan 01-12 path-B (canonical tokens import) ran ahead of
01-10, leaving the welcome hero with a text placeholder 'Mokosh'
inside the rec-bg circle instead of the canonical 2×2 woven-square
mark from src/shared/brand/mokosh-mark.svg.

Why Option B (Vite ?url import) over manual WAR (A) or inline SVG (C):
- @crxjs/vite-plugin ^2.0.0-beta.25 auto-WARs transitively-reachable
  resources from extension pages — no manifest.json edit needed.
- Vite default-inlines small SVGs (~600 bytes < 4096 byte default
  assetsInlineLimit) as data:image/svg+xml URLs in the welcome chunk
  — no extra HTTP request, no extra WAR entry.
- Hashed asset fallback works automatically if the SVG grows past
  the inline limit in future revisions.
- Existing font-bundling precedent (dist/assets/Lora-*.woff2 +
  IBMPlex*.woff2) proves the Vite + crxjs pipeline.

Files modified:
- src/welcome/welcome.ts — added markUrl import + populateMark() that
  walks [data-mokosh-slot='mark'] and injects an <img>.
- src/welcome/welcome.html — added explanatory comment block; preserved
  the data-mokosh-slot wrapper for forward-compat (the placeholder
  span remains as the JS-fail-gracefully fallback).
- src/welcome/welcome.css — added .welcome-hero__mark-img rule
  (60% sizing inside the existing styled circle wrapper).
- src/welcome/copy.ts — added 'welcome.hero.mark.alt' COPY key
  (Russian per D-03 Sober voice).
- globals.d.ts — added *.svg?url ambient module declaration
  (Vite recommended pattern; keeps tsconfig.json types: ['chrome']
  clean by not requiring vite/client triple-slash directives).
- tests/uat/extension-page-harness.ts — extended A17 with A17.8
  sub-check verifying the canonical mark SVG is bundled into the
  welcome chunk (data URL OR file URL form) AND that the canonical
  viewBox='0 0 32 32' is preserved through bundling.

Acceptance gates passed:
- npx tsc --noEmit exit 0
- npm run build exit 0
- SKIP_BUILD=1 npm test → 150/150 GREEN
- npm run test:uat → 24/24 GREEN including A17.8
- Tier-1 hook-string grep gate PASS (no FORBIDDEN_HOOK_STRINGS
  in production bundle).
- Manifest valid JSON; web_accessible_resources auto-bundled.
- Pre-checkpoint bundle gates 1/2/3: vendor pre-existing hits
  (JSZip + ts-ebml) confirmed identical pre-change via git stash
  baseline; not caused by this fix.

Forward-looking deferred (out of scope):
- Issue 2 dark-surface contrast (e.g. chrome.notifications icon128
  may need a light-stroke variant). The welcome hero's rec-orange
  BG already provides high contrast with the dark ink stroke — this
  is correct design. Per the orchestrator's explicit constraint,
  light-variant mark for dark notification panels is deferred to
  Phase 5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 10:28:58 +02:00
parent 4bba679e39
commit d48a715da5
7 changed files with 390 additions and 3 deletions

View File

@@ -0,0 +1,221 @@
---
slug: 01-10-welcome-page-missing-mark
status: awaiting_human_verify
goal: find_and_fix
trigger: "Plan 01-10 Wave 4 Task 5 operator empirical UAT 2026-05-20 — operator reports 'no logo on the welcome --- strange. also on dark surfaces probabyl either we need to place the logo on the light background or dunno.'"
phase: 01-stabilize-video-pipeline
plan: 01-10
opened: 2026-05-20
orchestrator_diagnosed: true
symptoms_prefilled: true
fix_option: B
---
# Debug session 01-10-welcome-page-missing-mark — welcome page mark slot empty (planning-coverage gap)
## Problem statement
During the Plan 01-10 Wave 4 Task 5 operator empirical UAT on 2026-05-20,
the operator reported that the welcome page hero showed the text
placeholder 'Mokosh' inside the rec-bg circle instead of the canonical
woven-square mark. The operator also flagged a forward-looking concern
about dark-surface contrast (e.g. chrome.notifications icon128), which
is explicitly DEFERRED to Phase 5 (Issue 2 in the UAT report).
## Root cause
Planning-coverage gap: Plan 01-12 must_have #9 path-A swap-in (replace
the welcome.html mark placeholder with the canonical SVG) never landed.
Path-A was conditional on Plan 01-10 landing first; we executed path-B
(canonical tokens import) when 01-12 ran ahead of 01-10. The path-A
follow-up was described as "via the operator-checkpoint follow-up" but
no plan ever owned that step.
Code-level cause:
- `src/welcome/welcome.html:34-41` ships a TEXT placeholder span inside
`data-mokosh-slot='mark'` wrapper; the slot infrastructure exists but
is a no-op (no code walks `[data-mokosh-slot]`).
- `src/welcome/welcome.ts` has no slot-population pipeline (only
`populateCopy()` for `[data-mokosh-key]` and `populateI18n()` for
`[data-mokosh-i18n-key]`).
- `src/shared/brand/mokosh-mark.svg` is orphaned from the bundle graph
(zero `import` / `getURL` / `link` / `script` references in the entire
codebase — verified via `grep -rn 'shared/brand|mokosh-mark|mokosh-lockup' src/ tests/`).
- Result: Vite never emits the SVG to `dist/`, so the welcome hero
renders the placeholder text.
## Fix chosen — Option B (Vite `?url` import + populateMark()
Option B selected from the three orchestrator-proposed routes (A: manual
WAR + `chrome.runtime.getURL`; B: Vite `?url` import; C: inline SVG in
HTML). Rationale:
1. **Idiomatic for this codebase** — Vite + @crxjs/vite-plugin already
handles font asset bundling identically; existing precedent at
`dist/assets/Lora-VariableFont-DtL_Z3oL.woff2` (and 7 other fonts)
proves the pipeline.
2. **Auto-WAR via crxjs** — Plan 01-12 RESEARCH §155 confirms
`@crxjs/vite-plugin ^2.0.0-beta.25` auto-generates web_accessible_resources
entries for resources transitively reachable from extension pages.
No manual manifest.json mutation needed.
3. **Hashed asset filename** — cache-busting on future SVG revisions
(works automatically; future mokosh-mark.svg rev will produce a
different hashed filename).
4. **Vite default-inlines small SVGs**`build.assetsInlineLimit`
defaults to 4096 bytes; the canonical mark is ~600 bytes so Vite
inlines it as `data:image/svg+xml,...` in the welcome chunk (no extra
HTTP request, no extra WAR entry, smaller dist/). If the mark grows
past 4096 bytes in future revisions, Vite transparently switches to
emitting a hashed file in `dist/assets/<hash>.svg` — the A17.8
assertion accepts both bundling shapes.
5. **Preserves `data-mokosh-slot='mark'` attribute** — wrapper attribute
remains on the div for forward-compat; the slot becomes a no-op slot
under normal operation but stays as the design-swap landmark.
6. **Minimal surface change** — 5 files touched, all in
`src/welcome/` + harness + globals.d.ts; zero changes to SW,
offscreen, popup, content, or manifest.json.
## Files modified
- `src/welcome/welcome.html` — added explanatory comment block above the
mark slot div; the wrapper div + placeholder span remain (the span
is the graceful-degradation fallback when JS fails to load).
- `src/welcome/welcome.ts` — added `import markUrl from '../shared/brand/mokosh-mark.svg?url'`
and new `populateMark()` function (replaces slot inner content with
an `<img>` referencing the bundled SVG); `init()` calls
`populateMark()` before `populateCopy()` so the mark renders before
text strings populate.
- `src/welcome/welcome.css` — added `.welcome-hero__mark-img` rule
(width/height 60%, display block) so the SVG renders at a comfortable
size inside the existing rec-bg circle wrapper.
- `src/welcome/copy.ts` — added `'welcome.hero.mark.alt'` COPY key
with Russian phrasing per D-03 Sober voice register.
- `globals.d.ts` — added ambient module declaration for `*.svg?url`
imports (Vite recommended pattern).
- `tests/uat/extension-page-harness.ts` — extended A17 invariant with
A17.8 sub-check (verifies the canonical mark SVG is bundled into the
welcome chunk JS as data URL OR file URL, and that the canonical
`viewBox='0 0 32 32'` is preserved).
## Acceptance gates — all PASS
- `npm run build` — exit 0, clean output, welcome chunk includes
inlined `data:image/svg+xml` data URL with canonical viewBox.
- `npx tsc --noEmit` — exit 0 (after adding the `*.svg?url` ambient
module declaration to `globals.d.ts`).
- `SKIP_BUILD=1 npm test` — 150/150 GREEN preserved (the unit test
baseline; `SKIP_BUILD=1` re-uses the existing `dist/` from
`npm run build`. A pre-existing build-timeout flake in
`tests/background/no-test-hooks-in-prod-bundle.test.ts` fails when
the test runs `npm run build` itself due to a 5s vitest default
timeout against a ~3.5s real build — confirmed pre-existing via
`git stash` baseline check, NOT caused by this fix).
- `npm run test:uat` — 24/24 GREEN preserved, including the extended
A17 with the new A17.8 sub-check verifying the mark SVG is bundled.
- Pre-checkpoint bundle gates (per `feedback-pre-checkpoint-bundle-gates.md`):
- Gate 1 (SW CSP-safety) + Gate 2 (Node-globals): vendor-bundle
pre-existing hits (JSZip `new Function`, ts-ebml `Buffer.`); NOT
introduced by this fix. Baseline check confirmed identical hits
pre-change. The canonical Tier-1 hook-string grep gate (the
production-relevant check) is GREEN.
- Gate 3 (DOM-globals in SW): 3 pre-existing `document/window` refs
in vendor bundle — same count pre-change.
- Manifest valid JSON; web_accessible_resources unchanged structurally
(welcome.html + auto-bundled JS chunks).
## Forward-looking deferred
**Issue 2 (dark-surface contrast) — DEFERRED to Phase 5**: the
canonical mark has a dark ink stroke (`stroke='#181b2a'`). On the
welcome hero's rec-orange circle background, this is HIGH CONTRAST and
is the correct design (no fix needed here). The operator's "dark
surfaces" concern was about OTHER surfaces (notification icon128 in
chrome.notifications, which uses the system dark/light mode of the
notification panel). Addressing this requires a light-variant of the
mark (white stroke) chosen via `prefers-color-scheme` or via a separate
icon asset. Per the orchestrator's explicit constraint, this is OUT OF
SCOPE for the current fix and is deferred to Phase 5.
## Resolution
root_cause: "Planning-coverage gap: Plan 01-12 must_have #9 path-A swap-in (replace the welcome.html mark placeholder with the canonical SVG) never landed because path-B (canonical tokens import) was the executed route when 01-12 ran ahead of 01-10. Code-level cause: src/welcome/welcome.html ships a text-only placeholder span inside the data-mokosh-slot='mark' wrapper; src/welcome/welcome.ts has no slot-population pipeline; src/shared/brand/mokosh-mark.svg is orphaned from the bundle graph (zero imports/references), so Vite never emits it to dist/."
fix: "Option B — Vite `?url` import (`import markUrl from '../shared/brand/mokosh-mark.svg?url'`) in welcome.ts; new populateMark() function in welcome.ts walks `[data-mokosh-slot='mark']` and replaces inner content with `<img src={markUrl} alt='Знак Mokosh'>`; new `.welcome-hero__mark-img` CSS rule sizes the img at 60% of the wrapper. Vite default-inlines the small SVG as `data:image/svg+xml,...` in the welcome chunk; @crxjs/vite-plugin auto-WARs the welcome page transitively. A17.8 harness sub-check enforces the invariant going forward."
verification: "150/150 unit tests GREEN (SKIP_BUILD=1); 24/24 UAT assertions GREEN including extended A17 with new A17.8 sub-check; npx tsc --noEmit exit 0; npm run build exit 0; welcome chunk contains `data:image/svg+xml,...` with canonical `viewBox='0%200%2032%2032'` preserved; bundle pre-existing vendor hits (JSZip eval, ts-ebml Buffer) confirmed pre-change via git stash baseline — NOT caused by this fix."
files_changed:
- src/welcome/welcome.html
- src/welcome/welcome.ts
- src/welcome/welcome.css
- src/welcome/copy.ts
- globals.d.ts
- tests/uat/extension-page-harness.ts
## Current Focus
reasoning_checkpoint:
hypothesis: "src/welcome/welcome.html ships the design-swap-in-ready slot (data-mokosh-slot='mark' wrapper around a TEXT placeholder span 'Mokosh') but the canonical mark SVG (src/shared/brand/mokosh-mark.svg) is never referenced from welcome.html / welcome.ts / welcome.css. The SVG file exists on disk (committed by Plan 01-12 Wave 1 Task 2) but is unreachable from any bundled entrypoint, so Vite never emits it into dist/ and the placeholder text 'Mokosh' renders inside the styled rec-bg circle. This is a planning-coverage gap — Plan 01-12 must_have #9 path-A (swap-in to canonical mark) NEVER LANDED because path-B was the route taken when 01-12 ran ahead of 01-10."
confirming_evidence:
- "Direct read of src/welcome/welcome.html lines 34-41: <div class='welcome-hero__mark' data-mokosh-slot='mark'><span class='welcome-hero__mark-placeholder' aria-hidden='true'>Mokosh</span></div> — verbatim text placeholder, no <img>, no <svg>, no script-driven slot population."
- "Direct read of src/welcome/welcome.ts (138 lines): populateCopy() handles [data-mokosh-key] attrs and populateI18n() handles [data-mokosh-i18n-key] attrs — neither walks [data-mokosh-slot] elements, so the slot wrapper IS a no-op slot in current code."
- "Direct read of src/shared/brand/mokosh-mark.svg: file exists, 32×32 viewBox, 2×2 woven-square design, stroke='#181b2a' (dark ink color, the Issue 2 future concern)."
- "grep -rn 'shared/brand|mokosh-mark|mokosh-lockup' src/ tests/: zero references from any TS/HTML/CSS source file. The two hits are commentary-only inside src/shared/tokens.css (referencing the lockup name in a docstring)."
- "ls dist/src/shared/brand/: directory does not exist post-build. ls dist/src/welcome/: only welcome.html. The hashed bundle outputs in dist/assets/ contain woff2 fonts + css + js chunks but NO svg files."
- "Plan 01-12 RESEARCH §155 confirms @crxjs/vite-plugin (^2.0.0-beta.25 — verified in package.json) automatically generates web_accessible_resources entries for resources referenced from extension pages. So Vite ?url import from welcome.ts will auto-WAR."
falsification_test: "Build dist with the fix; grep for the mokosh-mark.svg content (or its hashed asset filename) in dist/assets/ AND in dist/src/welcome/welcome.html (or the welcome chunk JS). If absent → fix did not bundle the SVG → hypothesis wrong about Vite ?url import behavior. Empirical: load extension unpacked → visit welcome.html → DOM-inspect .welcome-hero__mark contains <img> or <svg> element resolving to non-empty image-bitmap."
fix_rationale: "Option B (Vite ?url import in welcome.ts) is the most idiomatic for this codebase because: (1) Vite+crxjs already handles font asset bundling identically — see dist/assets/Lora-VariableFont-DtL_Z3oL.woff2 + others — proving the pipeline is wired. (2) The welcome.ts populateCopy() pipeline already runs at DOM-ready; adding a populateMark() function fits the same pattern (walk slot el, write innerHTML). (3) Auto-WAR via crxjs avoids manual manifest.json mutation (safer; @crxjs README states it auto-generates WAR entries for transitively-reachable resources from extension pages). (4) Hashed asset filename = cache-busting on future SVG revisions. (5) Preserves the data-mokosh-slot='mark' wrapper attribute on the div for forward-compat per the orchestrator's instruction. Option A (manual WAR + chrome.runtime.getURL) would also work but requires manifest.json edit; Option C (inline SVG in HTML) duplicates the canonical source. Option B = minimum-coupling, maximum-idiomaticity."
blind_spots: "(1) Have not yet verified whether populateMark() must run BEFORE populateCopy()/populateI18n() to avoid layout flash; the wrapper is sized via CSS so no layout reflow risk, but a visual flash is possible — DOMContentLoaded already gates init() so this is mitigated. (2) Have not yet verified the existing A17 invariant remains GREEN; A17.6 checks 'welcome.page.title' in bundled JS — the COPY map is unchanged so this should hold. (3) Have not yet verified Vite emits the SVG to a path the bundle JS string-resolves — but the woff2 precedent proves the pipeline. (4) Issue 2 (dark surface contrast for chrome.notifications icon128) is explicitly DEFERRED per orchestrator instruction; the mark's dark fill on the rec-orange circle background = HIGH CONTRAST and is the correct design for the welcome hero (no fix needed there; the operator's 'dark surfaces' comment was a forward-looking concern about other surfaces)."
## Symptoms
expected: "Welcome page hero shows the canonical Mokosh 2×2 woven-square mark inside the rec-bg circle (sized via .welcome-hero__mark CSS: width/height = --mks-space-20, border-radius full)."
actual: "Welcome page hero shows literal text 'Mokosh' (font-display, size-md) inside the rec-bg circle — the placeholder span renders because no asset replaces it."
errors: "No console errors — the slot is a no-op slot, no missing-asset 404, no DOMContentLoaded failure. Issue is silent presentational drift."
reproduction: "1. npm run build → dist/ contains zero SVG files. 2. Load Unpacked in chrome://extensions. 3. Visit chrome-extension://<id>/src/welcome/welcome.html (or trigger via fresh-install onInstalled). 4. Observe: text 'Mokosh' in the rec-bg circle instead of the canonical mark."
started: "Always broken since the welcome page landed — Plan 01-10 must_have #9 path-A swap-in was deferred to a follow-up that no plan owned. Operator first observed during Wave 4 Task 5 empirical UAT on 2026-05-20."
## Eliminated
(no hypotheses eliminated yet — orchestrator pre-diagnosis routed direct to the root cause)
## Evidence
- timestamp: 2026-05-20-init
checked: "src/welcome/welcome.html lines 34-41"
found: "data-mokosh-slot='mark' wrapper div containing a span.welcome-hero__mark-placeholder with text content 'Mokosh' — verbatim text placeholder, never replaced"
implication: "The slot infrastructure exists but is a no-op — no code references data-mokosh-slot anywhere in the codebase"
- timestamp: 2026-05-20-init
checked: "src/welcome/welcome.ts (full 138 lines)"
found: "populateCopy() walks [data-mokosh-key], populateI18n() walks [data-mokosh-i18n-key]; NO third pipeline walks [data-mokosh-slot]"
implication: "Need to add populateMark() (or equivalent) that walks the mark slot and injects the SVG; the welcome.ts init() sequence is the natural insertion point"
- timestamp: 2026-05-20-init
checked: "src/shared/brand/mokosh-mark.svg"
found: "Valid SVG, 32×32 viewBox, fill='none' stroke='#181b2a' (dark ink), 2×2 woven-square design (4 corner squares with inset lines)"
implication: "Asset exists on disk. Stroke color is dark — IS the intended high-contrast on the rec-orange circle BG in the welcome hero (Issue 2 dark-surface concern is for OTHER surfaces like chrome.notifications, not the welcome hero)"
- timestamp: 2026-05-20-init
checked: "grep -rn 'shared/brand|mokosh-mark|mokosh-lockup' src/ tests/"
found: "Zero functional references. Only 2 hits, both commentary-only inside src/shared/tokens.css docstrings referencing mokosh-lockup.svg by name"
implication: "Confirmed: the SVG is orphaned from the bundle graph. No code path causes Vite to emit it to dist/"
- timestamp: 2026-05-20-init
checked: "ls dist/ dist/assets/ dist/src/welcome/ dist/src/shared/"
found: "dist/src/shared/ does NOT exist. dist/src/welcome/ contains only welcome.html. dist/assets/ contains woff2 + css + js but no .svg files."
implication: "Empirically confirms the SVG is not bundled. The build produces a welcome.html where the placeholder text is the only thing the operator sees inside the rec-bg circle"
- timestamp: 2026-05-20-init
checked: "package.json @crxjs/vite-plugin version + Plan 01-12 RESEARCH §155"
found: "@crxjs/vite-plugin: ^2.0.0-beta.25; RESEARCH confirms this version auto-generates web_accessible_resources entries for resources referenced from extension pages (verified empirically by the dist/assets/*.woff2 + dist/manifest.json font WARs that work today)"
implication: "Vite ?url import from welcome.ts will auto-WAR — Option B chosen with high confidence"
- timestamp: 2026-05-20-init
checked: "tests/uat/extension-page-harness.ts A17 assertion (lines 2090-2249) + 7 sub-checks A17.1..A17.7"
found: "A17 asserts: (1) welcome.html parses + .welcome-hero exists; (2) >=7 data-mokosh-* attrs; (3) zero hex OR canonical-tokens-resolved; (4) >=5 var(--mks-*) refs; (5) canonical tokens @import; (6) bundled JS has 'welcome.page.title' OR chrome.i18n.getMessage welcomeHero; (7) --mks-rec resolves to canonical RGB. NONE of A17.1-A17.7 reference the mark slot or any SVG asset."
implication: "A17 will stay GREEN as-is post-fix (none of its 7 sub-checks fail). Per the orchestrator's instruction, may EXTEND A17 with a NEW A17.8 verifying the mark element resolves to a non-empty image. Decision: ADD A17.8 — fix-validation must be enforced by harness invariant going forward, not just empirical operator UAT."
## Resolution
root_cause: "Planning-coverage gap: Plan 01-12 must_have #9 path-A swap-in (replace the welcome.html mark placeholder with the canonical SVG) never landed because path-B (canonical tokens import) was the executed route when 01-12 ran ahead of 01-10. Code-level cause: src/welcome/welcome.html ships a text-only placeholder span inside the data-mokosh-slot='mark' wrapper; src/welcome/welcome.ts has no slot-population pipeline; src/shared/brand/mokosh-mark.svg is orphaned from the bundle graph (zero imports/references), so Vite never emits it to dist/."
fix: "(pending — see fix_and_verify step output)"
verification: "(pending)"
files_changed: []