Milestone v1 (v2.0.0): Mokosh — Session Capture #1

Merged
strategy155 merged 297 commits from gsd/phase-04-harden-clean-up-optional into main 2026-05-31 15:34:17 +00:00
7 changed files with 390 additions and 3 deletions
Showing only changes of commit d48a715da5 - Show all commits

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

18
globals.d.ts vendored
View File

@@ -17,3 +17,21 @@
// https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html // https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html
declare const __MOKOSH_UAT__: boolean; declare const __MOKOSH_UAT__: boolean;
// Plan 01-10 must_have #9 path-A swap-in (landed 2026-05-20):
// ambient declaration for Vite `?url` asset imports. The `?url`
// suffix instructs Vite to emit the asset to `dist/assets/<hash>.<ext>`
// and replace the import with the hashed asset URL as a string at
// build time. We declare the wildcard module so TypeScript accepts
// the import without spreading per-file `vite/client` triple-slash
// directives.
//
// References:
// - Vite explicit URL imports:
// https://vite.dev/guide/assets.html#explicit-url-imports
// - TypeScript ambient module declarations:
// https://www.typescriptlang.org/docs/handbook/modules/reference.html#ambient-modules
declare module '*.svg?url' {
const url: string;
export default url;
}

View File

@@ -50,6 +50,13 @@ export const WELCOME_HERO_EN_FALLBACK =
export const COPY: Readonly<Record<string, string>> = Object.freeze({ export const COPY: Readonly<Record<string, string>> = Object.freeze({
'welcome.page.title': 'Добро пожаловать в Mokosh', 'welcome.page.title': 'Добро пожаловать в Mokosh',
'welcome.hero.title': 'Mokosh', 'welcome.hero.title': 'Mokosh',
// Plan 01-10 must_have #9 path-A swap-in (landed 2026-05-20):
// alt text for the canonical mark <img> populated by welcome.ts
// populateMark(). The mark is decoratively presented (aria-hidden
// is set on the img), so this alt is primarily for screen-reader
// landmark identification when aria-hidden is overridden by future
// accessibility work. Russian phrasing per D-03 Sober voice.
'welcome.hero.mark.alt': 'Знак Mokosh',
'welcome.body.explainer.line1': 'welcome.body.explainer.line1':
'Mokosh непрерывно записывает последние 30 секунд экрана и 10 минут ' 'Mokosh непрерывно записывает последние 30 секунд экрана и 10 минут '
+ 'логов вашего браузера.', + 'логов вашего браузера.',

View File

@@ -79,6 +79,21 @@ body {
letter-spacing: var(--mks-tracking-tight); letter-spacing: var(--mks-tracking-tight);
} }
/* Plan 01-10 must_have #9 path-A swap-in mark image (landed 2026-05-20).
* Replaces .welcome-hero__mark-placeholder at DOMContentLoaded via
* welcome.ts populateMark(). The img inherits the wrapper's rec-bg
* circle (background var(--mks-rec)); we size the img to 60% of the
* wrapper for visual breathing room (the canonical mark is 32×32 with
* stroke-width 2.25 — at full-wrapper size it would touch the circle
* edge unpleasantly). The dark ink stroke (canonical ink token from
* the SVG source) renders on the rec-orange BG with strong contrast
* per D-04 Loom palette intent. */
.welcome-hero__mark-img {
width: 60%;
height: 60%;
display: block;
}
.welcome-hero__title { .welcome-hero__title {
font-family: var(--mks-font-display); font-family: var(--mks-font-display);
font-size: var(--mks-text-3xl); font-size: var(--mks-text-3xl);

View File

@@ -32,6 +32,20 @@
<body> <body>
<main class="welcome"> <main class="welcome">
<section class="welcome-hero" aria-labelledby="welcome-title"> <section class="welcome-hero" aria-labelledby="welcome-title">
<!--
Plan 01-10 must_have #9 path-A swap-in (landed 2026-05-20):
the placeholder <span> inside this wrapper is replaced at
DOMContentLoaded by welcome.ts populateMark() with an <img>
referencing the canonical Mokosh mark SVG (bundled via Vite
?url asset import). The placeholder Russian text serves as
graceful-degradation if welcome.ts fails to load (mirrors
the popup precedent's <title> fallback pattern).
The data-mokosh-slot='mark' attribute on this div is the
design-swap-in-ready slot per Plan 01-10 must_have #9; it
remains in the markup for forward-compat (future plans can
locate the slot via this attribute).
-->
<div class="welcome-hero__mark" role="presentation" data-mokosh-slot="mark"> <div class="welcome-hero__mark" role="presentation" data-mokosh-slot="mark">
<span class="welcome-hero__mark-placeholder" aria-hidden="true">Mokosh</span> <span class="welcome-hero__mark-placeholder" aria-hidden="true">Mokosh</span>
</div> </div>

View File

@@ -33,6 +33,17 @@ import {
WELCOME_HERO_RU_FALLBACK, WELCOME_HERO_RU_FALLBACK,
WELCOME_HERO_EN_FALLBACK, WELCOME_HERO_EN_FALLBACK,
} from './copy'; } from './copy';
// Plan 01-10 must_have #9 path-A swap-in (landed 2026-05-20 per debug
// session 01-10-welcome-page-missing-mark): import the canonical mark
// SVG as a Vite-bundled asset URL. The `?url` suffix instructs Vite to
// emit the SVG verbatim to `dist/assets/<hash>.svg` and replace the
// import with the hashed asset URL at build time. The @crxjs/vite-plugin
// ^2.0.0-beta.25 in this project auto-generates web_accessible_resources
// entries for resources transitively reachable from extension pages
// (welcome.html → welcome.ts → markUrl) — confirmed by Plan 01-12
// RESEARCH §155 and the existing dist/assets/*.woff2 precedent.
// Reference: https://vite.dev/guide/assets.html#explicit-url-imports
import markUrl from '../shared/brand/mokosh-mark.svg?url';
const logger = new Logger('Welcome'); const logger = new Logger('Welcome');
@@ -121,10 +132,60 @@ function populateI18n(): void {
} }
/** /**
* Initialize the welcome page. populateCopy first (non-tagline strings), * Walk every [data-mokosh-slot='mark'] wrapper and replace its inner
* then populateI18n (the two D-08 tagline strings via chrome.i18n). * placeholder content with an <img> referencing the bundled canonical
* mark SVG. Plan 01-10 must_have #9 path-A swap-in (landed 2026-05-20).
*
* The wrapper itself (.welcome-hero__mark) keeps its CSS-driven circle
* background + sizing (welcome.css:64-73) — the SVG renders INSIDE the
* styled wrapper at the existing dimensions. We preserve the
* data-mokosh-slot='mark' attribute on the wrapper for forward-compat
* (future plans can locate the slot generically).
*
* Filter-pipeline form per project rule "no `continue` statements".
* Missing-slot count is logged once via logger.warn for visibility.
*
* Img dimensions: width/height attributes match the wrapper inner
* dimensions implicit from --mks-space-20 minus negligible padding;
* we use percentage sizing so the SVG fills the wrapper responsively
* without coupling to the exact px value of the space token.
*
* Alt text resolves from COPY['welcome.hero.mark.alt'] when present,
* else falls back to a literal Russian default ('Знак Mokosh'). The
* alt is presentational (the mark is a decorative brand element, not
* a structural content element), but a non-empty alt aids screen-
* reader landmark identification.
*/
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',
);
}
}
/**
* Initialize the welcome page. populateMark first (replace the mark
* slot with the bundled SVG so the hero never shows the text
* placeholder), then populateCopy (non-tagline strings), then
* populateI18n (the two D-08 tagline strings via chrome.i18n).
*/ */
function init(): void { function init(): void {
populateMark();
populateCopy(); populateCopy();
populateI18n(); populateI18n();
logger.log('welcome page ready'); logger.log('welcome page ready');

View File

@@ -2090,7 +2090,7 @@ const A17_CANONICAL_REC_RGB = 'rgb(178, 84, 61)';
async function assertA17(): Promise<AssertionResult> { async function assertA17(): Promise<AssertionResult> {
const result: AssertionResult = { const result: AssertionResult = {
passed: false, passed: false,
name: 'A17 — design-swap-readiness: welcome.html parses; .welcome-hero exists; ≥7 mokosh-keyed attrs; welcome.css canonical @import + var(--mks-*) + (no hex OR canonical inlined); bundled JS has COPY[ or chrome.i18n.getMessage(welcomeHero; --mks-rec resolves', name: 'A17 — design-swap-readiness: welcome.html parses; .welcome-hero exists; ≥7 mokosh-keyed attrs; welcome.css canonical @import + var(--mks-*) + (no hex OR canonical inlined); bundled JS has COPY[ or chrome.i18n.getMessage(welcomeHero; --mks-rec resolves; canonical mark SVG bundled + fetchable (A17.8)',
checks: [], checks: [],
diagnostics: [], diagnostics: [],
}; };
@@ -2246,6 +2246,57 @@ async function assertA17(): Promise<AssertionResult> {
passed: resolvedNonDefault, passed: resolvedNonDefault,
}); });
// A17.8: Plan 01-10 must_have #9 path-A swap-in invariant (landed
// 2026-05-20 per debug session 01-10-welcome-page-missing-mark).
// Verifies the canonical Mokosh mark SVG is bundled into the
// welcome chunk so populateMark() can assign it as the <img src>.
//
// Vite's default behaviour (build.assetsInlineLimit = 4096 bytes,
// confirmed via vite.dev/config/build-options.html#build-assetsinlinelimit)
// inlines assets smaller than the limit as data: URLs. The
// canonical mokosh-mark.svg is ~600 bytes, so it's INLINED as a
// `data:image/svg+xml,...` literal inside the welcome JS chunk
// (NOT emitted as a separate `dist/assets/<hash>.svg` file).
//
// We accept BOTH bundling shapes — either is correct from a "the
// mark is reachable from the welcome page" standpoint:
// (a) data URL: `data:image/svg+xml,...` substring in jsText
// (Vite inlined small asset path; default behaviour).
// (b) file URL: a `.svg` filename string in jsText (Vite emitted
// separate asset path; would activate if SVG grew past
// 4096 bytes OR assetsInlineLimit was lowered).
//
// If neither shape is present, populateMark() would assign
// `img.src = undefined` and the welcome hero would render an
// empty/broken image — exactly the regression the operator
// reported in the 2026-05-20 UAT.
const hasInlineDataUrl = jsText.includes('data:image/svg+xml');
const svgFileUrlMatches = jsText.match(/["'][^"']*\.svg["']/g) ?? [];
const hasSvgFileUrl = svgFileUrlMatches.length > 0;
const hasBundledMark = hasInlineDataUrl || hasSvgFileUrl;
diag(result, `Step 7: bundled JS contains inlineDataUrl=${hasInlineDataUrl}, svgFileUrlCount=${svgFileUrlMatches.length}`);
// Cross-witness: the canonical mark's source SVG includes the
// viewBox="0 0 32 32" literal (32×32 woven-square mark per
// src/shared/brand/mokosh-mark.svg). The data URL inlining
// path preserves this verbatim (URL-percent-encoded:
// viewBox='0%200%2032%2032'). For the file URL path the
// substring lives at the fetched asset, not the chunk JS.
// Either way, the chunk JS string is sufficient to prove the
// mark survives the bundle.
const hasCanonicalViewBox =
jsText.includes('viewBox=\'0 0 32 32\'')
|| jsText.includes('viewBox="0 0 32 32"')
|| jsText.includes('viewBox=%270%200%2032%2032%27')
|| jsText.includes("viewBox='0%200%2032%2032'");
result.checks.push({
name: 'A17.8: welcome chunk JS bundles the canonical mark SVG (data URL OR file URL) AND canonical viewBox preserved (Plan 01-10 must_have #9 path-A swap-in)',
expected: 'data:image/svg+xml OR .svg URL in bundle; canonical viewBox=\'0 0 32 32\' preserved',
actual: `inlineDataUrl=${hasInlineDataUrl}, svgFileUrl=${hasSvgFileUrl}, canonicalViewBox=${hasCanonicalViewBox}`,
passed: hasBundledMark && hasCanonicalViewBox,
});
result.passed = result.checks.every((c) => c.passed); result.passed = result.checks.every((c) => c.passed);
diag(result, `A17: ${result.checks.filter((c) => c.passed).length}/${result.checks.length} subchecks passed`); diag(result, `A17: ${result.checks.filter((c) => c.passed).length}/${result.checks.length} subchecks passed`);
} catch (err) { } catch (err) {