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:
@@ -50,6 +50,13 @@ export const WELCOME_HERO_EN_FALLBACK =
|
||||
export const COPY: Readonly<Record<string, string>> = Object.freeze({
|
||||
'welcome.page.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':
|
||||
'Mokosh непрерывно записывает последние 30 секунд экрана и 10 минут '
|
||||
+ 'логов вашего браузера.',
|
||||
|
||||
@@ -79,6 +79,21 @@ body {
|
||||
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 {
|
||||
font-family: var(--mks-font-display);
|
||||
font-size: var(--mks-text-3xl);
|
||||
|
||||
@@ -32,6 +32,20 @@
|
||||
<body>
|
||||
<main class="welcome">
|
||||
<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">
|
||||
<span class="welcome-hero__mark-placeholder" aria-hidden="true">Mokosh</span>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,17 @@ import {
|
||||
WELCOME_HERO_RU_FALLBACK,
|
||||
WELCOME_HERO_EN_FALLBACK,
|
||||
} 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');
|
||||
|
||||
@@ -121,10 +132,60 @@ function populateI18n(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the welcome page. populateCopy first (non-tagline strings),
|
||||
* then populateI18n (the two D-08 tagline strings via chrome.i18n).
|
||||
* Walk every [data-mokosh-slot='mark'] wrapper and replace its inner
|
||||
* 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 {
|
||||
populateMark();
|
||||
populateCopy();
|
||||
populateI18n();
|
||||
logger.log('welcome page ready');
|
||||
|
||||
Reference in New Issue
Block a user