diff --git a/tests/uat/extension-page-harness.html b/tests/uat/extension-page-harness.html
index 6b6e1db..84a5134 100644
--- a/tests/uat/extension-page-harness.html
+++ b/tests/uat/extension-page-harness.html
@@ -3,6 +3,16 @@
Mokosh UAT Harness (extension-internal page)
+
+
Mokosh UAT — extension-internal page harness
diff --git a/tests/uat/extension-page-harness.ts b/tests/uat/extension-page-harness.ts
index 9717599..14c42cf 100644
--- a/tests/uat/extension-page-harness.ts
+++ b/tests/uat/extension-page-harness.ts
@@ -1978,6 +1978,413 @@ async function assertA23(): Promise {
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/.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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 targets');
+ const html = await probe.text();
+ const linkMatches = Array.from(html.matchAll(/]+rel=["']?stylesheet["']?[^>]+href=["']([^"']+)["']/gi));
+ diag(result, `Step 2 result: ${linkMatches.length} stylesheet link(s) found`);
+
+ if (linkMatches.length === 0) {
+ throw new Error('No 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
* orchestrator at startup to capture the expected version for A13's
@@ -2009,6 +2416,13 @@ declare global {
assertA12: () => Promise;
assertA13: () => Promise;
assertA14: () => Promise;
+ // Plan 01-12 Wave 6 — design-integration assertions
+ assertA18: () => Promise;
+ assertA19: () => Promise;
+ assertA20: () => Promise;
+ assertA21: () => Promise;
+ assertA22: () => Promise;
+ // Plan 01-14 — picker narrowing
assertA23: () => Promise;
getManifestVersion: () => Promise;
};
@@ -2030,15 +2444,20 @@ window.__mokoshHarness = {
assertA12,
assertA13,
assertA14,
+ assertA18,
+ assertA19,
+ assertA20,
+ assertA21,
+ assertA22,
assertA23,
getManifestVersion,
};
const statusEl = document.getElementById('status');
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 {};
diff --git a/tests/uat/harness.test.ts b/tests/uat/harness.test.ts
index d4f15e5..1343d96 100644
--- a/tests/uat/harness.test.ts
+++ b/tests/uat/harness.test.ts
@@ -77,6 +77,13 @@ import {
driveA12,
driveA13,
driveA14,
+ // Plan 01-12 Wave 6 — design integration assertions
+ driveA18,
+ driveA19,
+ driveA20,
+ driveA21,
+ driveA22,
+ // Plan 01-14 — picker-narrowing constraint
driveA23,
getManifestVersion,
} from './lib/harness-page-driver';
@@ -324,6 +331,21 @@ async function main(): Promise {
// notification ids state; no new SAVE dispatch — A13's already
// exercised the SAVE path. Recording stays stopped after A14.
{ 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
// constraints object captured by A2's setupFreshRecording. Verifies
// the production call at src/offscreen/recorder.ts:270 passes
diff --git a/tests/uat/lib/harness-page-driver.ts b/tests/uat/lib/harness-page-driver.ts
index 6876fae..cdd5d4b 100644
--- a/tests/uat/lib/harness-page-driver.ts
+++ b/tests/uat/lib/harness-page-driver.ts
@@ -993,6 +993,105 @@ export async function driveA14(page: Page): Promise {
}) 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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
+ * 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 {
+ 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) ─── */
/**