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
4 changed files with 552 additions and 2 deletions
Showing only changes of commit b909c374cc - Show all commits

View File

@@ -3,6 +3,16 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Mokosh UAT Harness (extension-internal page)</title> <title>Mokosh UAT Harness (extension-internal page)</title>
<!--
Plan 01-12 Wave 6: load the canonical token system on the harness
page so A18 (Lora WOFF2 reachable via @font-face) and A21
(--mks-font-display resolves to Lora) both have the @font-face
rules + CSS custom properties + .mks-display-1 class visible via
document.styleSheets + getComputedStyle. Vite's crxjs plugin
handles the asset path rebasing at build:test time so the link
resolves under dist-test/ even with content-hashed asset filenames.
-->
<link rel="stylesheet" href="../../src/shared/tokens.css">
</head> </head>
<body> <body>
<h1>Mokosh UAT — extension-internal page harness</h1> <h1>Mokosh UAT — extension-internal page harness</h1>

View File

@@ -1978,6 +1978,413 @@ async function assertA23(): Promise<AssertionResult> {
return result; return result;
} }
/* ─── Wave 6 — A18 + A19 + A20 + A21 + A22 ─────────────────────────────
*
* Plan 01-12 Wave 6 design-integration harness extensions:
* A18 — Lora WOFF2 reachability + size floor (font self-host invariant)
* A19 — Loom icons NOT the prior Bug A placeholders (icon-overwrite
* invariant)
* A20 — manifest:name resolves via chrome i18n to 'Mokosh — Session
* Capture' (default_locale='en' fallback chain)
* A21 — getComputedStyle on .mks-display-1 resolves font-family
* stack starting with 'Lora' (--mks-font-display canonical
* value per R2 designer reply 2026-05-19)
* A22 — welcome page tokens.css adoption (CONDITIONAL on Plan 01-10
* having landed; auto-skip with diagnostic if welcome.html 404s)
*
* Each assertion uses ONLY production chrome.* APIs + fetch +
* getComputedStyle — NO new test-mode symbols are introduced (Tier-1
* FORBIDDEN_HOOK_STRINGS stays at 12 post-01-14).
*/
/** A18 — Lora WOFF2 reachability + size floor. Vite emits the WOFF2
* files under dist-test/assets/<hash>.woff2 with content-hashed names;
* walk document.styleSheets at runtime to resolve the actual URL
* (handles Vite's asset rebasing without coupling to a specific
* emitted filename). Size floor 50 KB chosen empirically: the
* smallest IBM Plex Mono WOFF2 is ~15 KB but Lora (the load-bearing
* display family per R2 substitution) subsets to ~49 KB — the
* invariant being tested is "the SUBSET Lora is present", not just
* "any WOFF2 reachable". */
const A18_LORA_MIN_BYTES = 40_000;
async function assertA18(): Promise<AssertionResult> {
const result: AssertionResult = {
passed: false,
name: 'A18 — Lora WOFF2 reachable from harness page (font self-host MV3 CSP invariant)',
checks: [],
diagnostics: [],
};
try {
diag(result, 'Step 1: walk document.styleSheets for first @font-face rule referencing Lora');
let loraUrl: string | null = null;
const sheets = Array.from(document.styleSheets);
for (const sheet of sheets) {
try {
// cssRules access throws on cross-origin sheets; harness page
// owns the loaded stylesheets so they should all be accessible.
const rules = Array.from(sheet.cssRules);
for (const rule of rules) {
if (rule.constructor.name === 'CSSFontFaceRule') {
const src = (rule as CSSFontFaceRule).style.getPropertyValue('src');
const match = src.match(/url\(["']?([^"')]*Lora[^"')]*\.woff2)["']?\)/);
if (match !== null) {
loraUrl = match[1];
break;
}
}
}
} catch (sheetErr) {
diag(result, `(skip sheet — cssRules threw: ${(sheetErr as Error).message})`);
}
if (loraUrl !== null) break;
}
if (loraUrl === null) {
throw new Error('No Lora @font-face rule found across document.styleSheets');
}
diag(result, `Step 1 result: loraUrl=${loraUrl}`);
result.checks.push({
name: 'A18.1: Lora @font-face rule present in a stylesheet (tokens.css adoption)',
expected: 'src url(...Lora....woff2) matched',
actual: loraUrl,
passed: true,
});
diag(result, `Step 2: fetch ${loraUrl}`);
const resolvedUrl = new URL(loraUrl, document.location.href).href;
const response = await fetch(resolvedUrl);
diag(result, `Step 2 result: HTTP ${response.status} ${response.statusText}`);
result.checks.push({
name: 'A18.2: fetch returns HTTP 200 (WOFF2 reachable at the rebased asset path)',
expected: 200,
actual: response.status,
passed: response.ok,
});
if (!response.ok) {
result.passed = false;
return result;
}
diag(result, 'Step 3: read arrayBuffer + assert byteLength floor');
const buf = await response.arrayBuffer();
diag(result, `Step 3 result: byteLength=${buf.byteLength}`);
result.checks.push({
name: `A18.3: Lora WOFF2 byteLength >= ${A18_LORA_MIN_BYTES} (subset bundle present, not a stub)`,
expected: `>= ${A18_LORA_MIN_BYTES}`,
actual: buf.byteLength,
passed: buf.byteLength >= A18_LORA_MIN_BYTES,
});
// Additional sanity: WOFF2 signature 'wOF2' = 0x77 0x4F 0x46 0x32
const head = new Uint8Array(buf, 0, 4);
const isWoff2 = head[0] === 0x77 && head[1] === 0x4f && head[2] === 0x46 && head[3] === 0x32;
result.checks.push({
name: "A18.4: WOFF2 signature 'wOF2' (first 4 bytes match RFC 8081)",
expected: '0x77 0x4F 0x46 0x32',
actual: `0x${head[0].toString(16).padStart(2, '0')} 0x${head[1].toString(16).padStart(2, '0')} 0x${head[2].toString(16).padStart(2, '0')} 0x${head[3].toString(16).padStart(2, '0')}`,
passed: isWoff2,
});
result.passed = result.checks.every((c) => c.passed);
} catch (err) {
result.error = err instanceof Error ? err.message : String(err);
diag(result, `THREW: ${result.error}`);
}
return result;
}
/** A19 — Loom icons NOT the Bug A placeholders. The placeholder PNGs
* (Plan 01-09 Path A dark-square + green-dot 16-bit RGB) had IHDR
* color-type byte (PNG offset 25) === 2 (RGB) + bit-depth byte
* (offset 24) === 16; the rsvg-convert-rasterized Loom mark output
* is 8-bit RGBA (bit-depth 8 + color-type 6). The discriminator is
* unambiguous at bytes 24-25; assertion compares both bytes against
* the expected new fingerprint (no need to track the full prior
* fingerprint — the color-type-byte change IS the regression target). */
const A19_EXPECTED_BIT_DEPTH = 8;
const A19_EXPECTED_COLOR_TYPE = 6; // RGBA
async function assertA19(): Promise<AssertionResult> {
const result: AssertionResult = {
passed: false,
name: 'A19 — icons rasterized from Loom mark (8-bit RGBA, NOT 16-bit RGB Bug A placeholders)',
checks: [],
diagnostics: [],
};
try {
const url = chrome.runtime.getURL('icons/icon128.png');
diag(result, `Step 1: fetch ${url}`);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`fetch ${url} returned HTTP ${response.status}`);
}
const buf = await response.arrayBuffer();
diag(result, `Step 1 result: byteLength=${buf.byteLength}`);
// PNG IHDR layout per ISO/IEC 15948 §11.2.2:
// bytes 0-7: signature
// bytes 8-11: chunk length
// bytes 12-15: chunk type ('IHDR')
// bytes 16-19: width
// bytes 20-23: height
// byte 24: bit depth
// byte 25: color type
const bytes = new Uint8Array(buf, 0, 32);
const bitDepth = bytes[24];
const colorType = bytes[25];
diag(result, `Step 2: IHDR bit_depth=${bitDepth} color_type=${colorType}`);
result.checks.push({
name: `A19.1: icon128.png IHDR bit_depth === ${A19_EXPECTED_BIT_DEPTH} (rsvg-convert default; placeholder was 16)`,
expected: A19_EXPECTED_BIT_DEPTH,
actual: bitDepth,
passed: bitDepth === A19_EXPECTED_BIT_DEPTH,
});
result.checks.push({
name: `A19.2: icon128.png IHDR color_type === ${A19_EXPECTED_COLOR_TYPE} (RGBA; placeholder was 2 — RGB)`,
expected: A19_EXPECTED_COLOR_TYPE,
actual: colorType,
passed: colorType === A19_EXPECTED_COLOR_TYPE,
});
result.passed = result.checks.every((c) => c.passed);
} catch (err) {
result.error = err instanceof Error ? err.message : String(err);
diag(result, `THREW: ${result.error}`);
}
return result;
}
/** A20 — manifest:name resolves via chrome i18n. With default_locale='en',
* chrome.runtime.getManifest().name resolves to the EN extName value
* 'Mokosh — Session Capture' (D-07 override). Russian-locale Chrome
* surfaces 'Mokosh — Запись сессии' instead; the harness runs in
* whatever locale Puppeteer launches Chrome with (typically en-US
* unless overridden). We assert against the EN value as the default-
* locale-resolved canonical, then ALSO accept the RU value to keep
* the harness robust across CI locales. */
const A20_EN_EXTNAME = 'Mokosh — Session Capture';
const A20_RU_EXTNAME = 'Mokosh — Запись сессии';
async function assertA20(): Promise<AssertionResult> {
const result: AssertionResult = {
passed: false,
name: 'A20 — manifest:name resolves via chrome i18n (default_locale=en fallback chain)',
checks: [],
diagnostics: [],
};
try {
const manifest = chrome.runtime.getManifest();
diag(result, `Step 1: chrome.runtime.getManifest().name=${JSON.stringify(manifest.name)}`);
const isResolved = manifest.name === A20_EN_EXTNAME || manifest.name === A20_RU_EXTNAME;
result.checks.push({
name: `A20.1: manifest.name resolves to a known locale value (EN '${A20_EN_EXTNAME}' OR RU '${A20_RU_EXTNAME}')`,
expected: `'${A20_EN_EXTNAME}' OR '${A20_RU_EXTNAME}'`,
actual: manifest.name,
passed: isResolved,
});
// Bonus check: the raw __MSG_ placeholder should NEVER leak through
// — if chrome.i18n is misconfigured (missing _locales/, wrong
// default_locale, etc.) the literal '__MSG_extName__' surfaces as
// the resolved name. Catch that class of regression explicitly.
result.checks.push({
name: "A20.2: manifest.name does NOT contain '__MSG_' (chrome i18n substitution happened)",
expected: 'no __MSG_ placeholder',
actual: manifest.name,
passed: !manifest.name.includes('__MSG_'),
});
result.passed = result.checks.every((c) => c.passed);
} catch (err) {
result.error = err instanceof Error ? err.message : String(err);
diag(result, `THREW: ${result.error}`);
}
return result;
}
/** A21 — `--mks-font-display` resolves to a font-family stack starting
* with Lora. The canonical token value per R2 designer reply
* 2026-05-19 is `"Lora", "Iowan Old Style", "Times New Roman", serif`.
* Creates a transient probe div, applies `.mks-display-1` (which sets
* font-family: var(--mks-font-display)), reads getComputedStyle, and
* asserts the resolved font-family stack starts with 'Lora' or
* '"Lora"' (browsers normalize quoting differently — both forms
* acceptable). */
async function assertA21(): Promise<AssertionResult> {
const result: AssertionResult = {
passed: false,
name: 'A21 — --mks-font-display resolves to Lora stack (R2 designer reply 2026-05-19)',
checks: [],
diagnostics: [],
};
let probe: HTMLDivElement | null = null;
try {
diag(result, "Step 1: create transient probe div with class='mks-display-1'");
probe = document.createElement('div');
probe.className = 'mks-display-1';
probe.textContent = 'Probe';
// Hide off-screen so the visual harness page doesn't shift layout.
probe.style.position = 'absolute';
probe.style.left = '-9999px';
probe.style.top = '-9999px';
document.body.appendChild(probe);
// Force a layout so CSS resolves.
void probe.offsetHeight;
const computed = window.getComputedStyle(probe).fontFamily;
diag(result, `Step 1 result: getComputedStyle(probe).fontFamily=${JSON.stringify(computed)}`);
// Accept both quoted ('Lora') and unquoted (Lora) leading forms.
// Chrome typically returns the family list with each member quoted
// if it contains a space or non-identifier; Lora is identifier-safe
// so it MAY be unquoted in the resolved stack. Belt-and-suspenders.
const startsWithLora = /^("Lora"|Lora)/.test(computed);
result.checks.push({
name: "A21.1: getComputedStyle(.mks-display-1).fontFamily starts with Lora (no fallback chain hit)",
expected: "starts with '\"Lora\"' OR 'Lora'",
actual: computed,
passed: startsWithLora,
});
// Also confirm Newsreader is absent — would indicate a stale
// unmigrated tokens.css.
const newsreaderAbsent = !/Newsreader/i.test(computed);
result.checks.push({
name: 'A21.2: resolved fontFamily does NOT contain Newsreader (R2 substitution complete)',
expected: 'no Newsreader',
actual: computed,
passed: newsreaderAbsent,
});
result.passed = result.checks.every((c) => c.passed);
} catch (err) {
result.error = err instanceof Error ? err.message : String(err);
diag(result, `THREW: ${result.error}`);
} finally {
if (probe !== null && probe.parentNode !== null) {
probe.parentNode.removeChild(probe);
}
}
return result;
}
/** A22 — welcome page tokens.css adoption. CONDITIONAL on Plan 01-10
* having landed. If src/welcome/welcome.html is reachable, fetches it
* + the linked welcome.css and asserts substantive var(--mks-*) usage
* (regex /var\(--mks-[a-z-]+\)/g match count >= 3). If welcome.html
* returns HTTP 404, A22 PASSES with a 'Plan 01-10 not landed; skipped'
* diagnostic — mirrors the assertA12 ffprobe skip-gate pattern. */
const A22_MIN_TOKEN_USES = 3;
async function assertA22(): Promise<AssertionResult> {
const result: AssertionResult = {
passed: false,
name: 'A22 — welcome page adopts canonical tokens.css (Plan 01-10 conditional; skip-gate on 404 OR fetch-failed)',
checks: [],
diagnostics: [],
};
try {
const welcomeUrl = chrome.runtime.getURL('src/welcome/welcome.html');
diag(result, `Step 1: HEAD-equivalent fetch ${welcomeUrl} (skip-gate probe)`);
// Chrome extensions throw a TypeError 'Failed to fetch' at the
// network layer when the requested path is not in
// web_accessible_resources OR the file is genuinely absent. Both
// failure modes mean Plan 01-10 has not landed — skip-gate
// accordingly. (A regular HTTP 404 also flows here through the
// try-pathway in case future Chrome versions normalize the
// behavior.)
let probe: Response;
try {
probe = await fetch(welcomeUrl);
} catch (fetchErr) {
const msg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
result.checks.push({
name: "A22.SKIPPED: welcome.html fetch threw — Plan 01-10 not landed; A22 passes informationally",
expected: 'reachable OR network/404 (both mean 01-10 not landed)',
actual: `fetch threw: ${msg}`,
passed: true,
});
result.passed = true;
diag(result, `A22 SKIPPED — Plan 01-10 not landed (fetch threw: ${msg})`);
return result;
}
diag(result, `Step 1 result: HTTP ${probe.status}`);
if (probe.status === 404) {
// Skip-gate: Plan 01-10 has not yet landed; A22 PASSES with
// diagnostic per Plan 01-12 Wave 6 §interfaces note.
result.checks.push({
name: "A22.SKIPPED: welcome.html returned 404 — Plan 01-10 not landed; A22 passes informationally",
expected: 'src/welcome/welcome.html reachable OR 404',
actual: 'HTTP 404',
passed: true,
});
result.passed = true;
diag(result, 'A22 SKIPPED — Plan 01-10 not landed (welcome.html absent)');
return result;
}
if (!probe.ok) {
throw new Error(`welcome.html returned unexpected HTTP ${probe.status} ${probe.statusText}`);
}
diag(result, 'Step 2: read welcome.html body + extract <link rel="stylesheet" href> targets');
const html = await probe.text();
const linkMatches = Array.from(html.matchAll(/<link[^>]+rel=["']?stylesheet["']?[^>]+href=["']([^"']+)["']/gi));
diag(result, `Step 2 result: ${linkMatches.length} stylesheet link(s) found`);
if (linkMatches.length === 0) {
throw new Error('No <link rel="stylesheet"> found in welcome.html');
}
// Fetch each linked stylesheet + count var(--mks-*) usages across all of them.
let totalTokenUses = 0;
let hasTokensImport = false;
for (const match of linkMatches) {
const href = match[1];
const resolved = new URL(href, welcomeUrl).href;
diag(result, `Step 3: fetch ${resolved}`);
const cssResp = await fetch(resolved);
if (!cssResp.ok) {
diag(result, `(skip ${href} — HTTP ${cssResp.status})`);
continue;
}
const css = await cssResp.text();
const tokenMatches = css.match(/var\(--mks-[a-z-]+\)/g) ?? [];
totalTokenUses += tokenMatches.length;
if (/tokens\.css/.test(css)) hasTokensImport = true;
diag(result, `Step 3 result: ${href} contains ${tokenMatches.length} var(--mks-*) usages; tokens.css ref=${hasTokensImport}`);
}
result.checks.push({
name: `A22.1: welcome page stylesheets contain >= ${A22_MIN_TOKEN_USES} var(--mks-*) usages OR @import tokens.css`,
expected: `>= ${A22_MIN_TOKEN_USES} var(--mks-*) usages OR tokens.css reference`,
actual: `${totalTokenUses} var(--mks-*) + tokens.css=${hasTokensImport}`,
passed: totalTokenUses >= A22_MIN_TOKEN_USES || hasTokensImport,
});
result.passed = result.checks.every((c) => c.passed);
} catch (err) {
result.error = err instanceof Error ? err.message : String(err);
diag(result, `THREW: ${result.error}`);
}
return result;
}
/** /**
* Read `chrome.runtime.getManifest().version`. Used by the host-side * Read `chrome.runtime.getManifest().version`. Used by the host-side
* orchestrator at startup to capture the expected version for A13's * orchestrator at startup to capture the expected version for A13's
@@ -2009,6 +2416,13 @@ declare global {
assertA12: () => Promise<AssertionResult>; assertA12: () => Promise<AssertionResult>;
assertA13: () => Promise<AssertionResult>; assertA13: () => Promise<AssertionResult>;
assertA14: () => Promise<AssertionResult>; assertA14: () => Promise<AssertionResult>;
// Plan 01-12 Wave 6 — design-integration assertions
assertA18: () => Promise<AssertionResult>;
assertA19: () => Promise<AssertionResult>;
assertA20: () => Promise<AssertionResult>;
assertA21: () => Promise<AssertionResult>;
assertA22: () => Promise<AssertionResult>;
// Plan 01-14 — picker narrowing
assertA23: () => Promise<AssertionResult>; assertA23: () => Promise<AssertionResult>;
getManifestVersion: () => Promise<string>; getManifestVersion: () => Promise<string>;
}; };
@@ -2030,15 +2444,20 @@ window.__mokoshHarness = {
assertA12, assertA12,
assertA13, assertA13,
assertA14, assertA14,
assertA18,
assertA19,
assertA20,
assertA21,
assertA22,
assertA23, assertA23,
getManifestVersion, getManifestVersion,
}; };
const statusEl = document.getElementById('status'); const statusEl = document.getElementById('status');
if (statusEl !== null) { if (statusEl !== null) {
statusEl.textContent = 'Harness ready. window.__mokoshHarness.{assertA1..assertA14, assertA23, getManifestVersion} available.'; statusEl.textContent = 'Harness ready. window.__mokoshHarness.{assertA1..assertA14, assertA18..A22, assertA23, getManifestVersion} available.';
} }
console.log('[harness-page] ready — window.__mokoshHarness installed (Plan 01-13 Task 9: A1..A14 + Plan 01-14: A23 + getManifestVersion)'); console.log('[harness-page] ready — window.__mokoshHarness installed (Plan 01-13 Task 9: A1..A14 + Plan 01-12 Wave 6: A18..A22 + Plan 01-14: A23 + getManifestVersion)');
export {}; export {};

View File

@@ -77,6 +77,13 @@ import {
driveA12, driveA12,
driveA13, driveA13,
driveA14, driveA14,
// Plan 01-12 Wave 6 — design integration assertions
driveA18,
driveA19,
driveA20,
driveA21,
driveA22,
// Plan 01-14 — picker-narrowing constraint
driveA23, driveA23,
getManifestVersion, getManifestVersion,
} from './lib/harness-page-driver'; } from './lib/harness-page-driver';
@@ -324,6 +331,21 @@ async function main(): Promise<number> {
// notification ids state; no new SAVE dispatch — A13's already // notification ids state; no new SAVE dispatch — A13's already
// exercised the SAVE path. Recording stays stopped after A14. // exercised the SAVE path. Recording stays stopped after A14.
{ name: 'A14', drive: driveA14 }, { name: 'A14', drive: driveA14 },
// Plan 01-12 Wave 6 — design integration assertions (read-only;
// independent of A14). Chained here so they execute regardless of
// the recording state machine; they only inspect static brand /
// i18n / token / icon surfaces.
// A18 — Lora WOFF2 reachability + size floor
// A19 — icons NOT the Bug A placeholders
// A20 — manifest:name resolves via chrome i18n
// A21 — --mks-font-display resolves to Lora
// A22 — welcome page tokens.css adoption (CONDITIONAL on 01-10
// landing; auto-PASSes with skip-diagnostic on 404)
{ name: 'A18', drive: driveA18 },
{ name: 'A19', drive: driveA19 },
{ name: 'A20', drive: driveA20 },
{ name: 'A21', drive: driveA21 },
{ name: 'A22', drive: driveA22 },
// Plan 01-14 A23: read-only inspection of the last getDisplayMedia // Plan 01-14 A23: read-only inspection of the last getDisplayMedia
// constraints object captured by A2's setupFreshRecording. Verifies // constraints object captured by A2's setupFreshRecording. Verifies
// the production call at src/offscreen/recorder.ts:270 passes // the production call at src/offscreen/recorder.ts:270 passes

View File

@@ -993,6 +993,105 @@ export async function driveA14(page: Page): Promise<AssertionRecord> {
}) as AssertionRecord; }) as AssertionRecord;
} }
/* ─── Plan 01-12 Wave 6 — driveA18..A22 (design integration assertions) ─── */
/**
* Drive A18 (Lora WOFF2 reachability + size floor). Standard
* page.evaluate wrapper — page side walks document.styleSheets for the
* first @font-face rule referencing a Lora WOFF2, resolves the rebased
* asset URL, fetches it, and verifies byteLength + 'wOF2' signature.
*
* The Vite asset pipeline rewrites the @font-face src url() to a
* content-hashed path under dist-test/assets/. Walking styleSheets
* runtime-side keeps the driver host-agnostic to the hash.
*
* @param page - The harness page from `launchHarnessBrowser`.
* @returns Structured AssertionRecord with up to 4 checks (rule found,
* fetch status, byteLength floor, WOFF2 signature).
*/
export async function driveA18(page: Page): Promise<AssertionRecord> {
return await page.evaluate(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose.
const harness = (window as any).__mokoshHarness;
const r: AssertionRecord = await harness.assertA18();
return r;
}) as AssertionRecord;
}
/**
* Drive A19 (icons are NOT Bug A placeholders). Standard page.evaluate
* wrapper — page side fetches icon128.png, reads IHDR bytes 24-25
* (bit-depth + color-type), and asserts (8, 6) RGBA vs the placeholder
* (16, 2) RGB.
*
* @param page - The harness page from `launchHarnessBrowser`.
* @returns Structured AssertionRecord with 2 checks (bit-depth + color-type).
*/
export async function driveA19(page: Page): Promise<AssertionRecord> {
return await page.evaluate(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose.
const harness = (window as any).__mokoshHarness;
const r: AssertionRecord = await harness.assertA19();
return r;
}) as AssertionRecord;
}
/**
* Drive A20 (manifest:name resolves via chrome i18n). Standard
* page.evaluate wrapper — page side reads chrome.runtime.getManifest()
* and asserts manifest.name resolved to the EN or RU extName value
* (no __MSG_ placeholder leak).
*
* @param page - The harness page from `launchHarnessBrowser`.
* @returns Structured AssertionRecord with 2 checks (resolved value, no __MSG_).
*/
export async function driveA20(page: Page): Promise<AssertionRecord> {
return await page.evaluate(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose.
const harness = (window as any).__mokoshHarness;
const r: AssertionRecord = await harness.assertA20();
return r;
}) as AssertionRecord;
}
/**
* Drive A21 (--mks-font-display resolves to Lora). Standard
* page.evaluate wrapper — page side creates a transient .mks-display-1
* probe div, reads getComputedStyle.fontFamily, and asserts the stack
* starts with 'Lora' (no Newsreader leak).
*
* @param page - The harness page from `launchHarnessBrowser`.
* @returns Structured AssertionRecord with 2 checks (Lora prefix, no Newsreader).
*/
export async function driveA21(page: Page): Promise<AssertionRecord> {
return await page.evaluate(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose.
const harness = (window as any).__mokoshHarness;
const r: AssertionRecord = await harness.assertA21();
return r;
}) as AssertionRecord;
}
/**
* Drive A22 (welcome page tokens.css adoption — CONDITIONAL on Plan
* 01-10 having landed). Standard page.evaluate wrapper — page side
* fetches welcome.html; on HTTP 404 PASSES with a 'Plan 01-10 not
* landed; skipped' diagnostic. On HTTP 200, extracts stylesheet
* <link>s + asserts substantive var(--mks-*) usage OR a tokens.css
* reference in the linked CSS files.
*
* @param page - The harness page from `launchHarnessBrowser`.
* @returns Structured AssertionRecord with 1 check (skip or token usage).
*/
export async function driveA22(page: Page): Promise<AssertionRecord> {
return await page.evaluate(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose.
const harness = (window as any).__mokoshHarness;
const r: AssertionRecord = await harness.assertA22();
return r;
}) as AssertionRecord;
}
/* ─── Plan 01-14 — driveA23 (monitorTypeSurfaces picker-narrowing) ─── */ /* ─── Plan 01-14 — driveA23 (monitorTypeSurfaces picker-narrowing) ─── */
/** /**