Milestone v1 (v2.0.0): Mokosh — Session Capture #1
221
.planning/debug/resolved/01-10-welcome-page-missing-mark.md
Normal file
221
.planning/debug/resolved/01-10-welcome-page-missing-mark.md
Normal 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
18
globals.d.ts
vendored
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 минут '
|
||||||
+ 'логов вашего браузера.',
|
+ 'логов вашего браузера.',
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user