---
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: []
