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>
222 lines
18 KiB
Markdown
222 lines
18 KiB
Markdown
---
|
||
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: []
|