Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/scripts/verify-ui-mock-visual.ts
T

1632 lines
76 KiB
TypeScript

#!/usr/bin/env tsx
import assert from "node:assert/strict";
import fs from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";
// Use a relative path so vitest's Vite-based resolver can find the workspace package
// even when it has not been hoisted into the top-level node_modules/@hivegrid directory.
import {
createAutomationEditorFixturePage,
createChatDetailInterimFixturePage,
createChatIndexInterimFixturePage,
createCrmInterimFixturePage,
createDashboardInterimFixturePage,
createEmailComposeInterimFixturePage,
createEmailDetailInterimFixturePage,
createEmailInboxInterimFixturePage,
createHotSwapDrillPage,
createLeadInterimFixturePage,
createP20InterimFixturePage,
createPluginCatalogInterimFixturePage,
createProjektBoardInterimFixturePage,
createSpotlightInterimFixturePage,
createTelephonyInterimFixturePage,
createVaP24aInterimFixturePage,
createVaP26aInterimFixturePage,
createVaP26bInterimFixturePage,
createVaP26cInterimFixturePage,
generateMockArtifacts,
type P20InterimViewModels,
} from "../packages/ui-mock/src/index.js";
const require = createRequire(import.meta.url);
const { JSDOM } = require("jsdom") as {
JSDOM: new (html: string) => { window: { document: Document } };
};
const BASELINE_DIR = "packages/ui-mock/visual-baseline";
const BASELINE_FILES = {
module: "hot-swap.mock.ts",
html: "hot-swap.html",
snapshot: "hot-swap.snapshot.json",
} as const;
// R-P11b dashboard interim screen (AD-28) — interim baseline, re-baselined on the D-DASH design-nachzug.
const R_P11B_BASELINE_FILES = {
module: "r-p11b-interim.mock.ts",
html: "r-p11b-interim.html",
snapshot: "r-p11b-interim.snapshot.json",
} as const;
// R-P11a Spotlight interim screen (AD-28) - re-baseline on the final D-DASH design follow-up.
const R_P11A_BASELINE_FILES = {
module: "r-p11a-spotlight-interim.mock.ts",
html: "r-p11a-spotlight-interim.html",
snapshot: "r-p11a-spotlight-interim.snapshot.json",
} as const;
// R-P6 typed CRM contact list (AD-28) — interim baseline, re-baselined on the D-CRM design-nachzug.
const CRM_INTERIM_BASELINE_FILES = {
module: "r-p6-crm-interim.mock.ts",
html: "r-p6-crm-interim.html",
snapshot: "r-p6-crm-interim.snapshot.json",
} as const;
// R-P7 lead-inbox interim screen (AD-28) — interim baseline, re-baselined on the D-P7 design-nachzug.
const LEADS_INTERIM_BASELINE_FILES = {
module: "r-p7-leads-interim.mock.ts",
html: "r-p7-leads-interim.html",
snapshot: "r-p7-leads-interim.snapshot.json",
} as const;
// R-P8 pipeline-kanban interim screen (AD-28) — interim baseline, re-baselined on the D-P8/D-KANBAN design-nachzug.
const PROJEKT_INTERIM_BASELINE_FILES = {
module: "r-p8-projects-interim.mock.ts",
html: "r-p8-projects-interim.html",
snapshot: "r-p8-projects-interim.snapshot.json",
} as const;
const P20_INTERIM_BASELINE_FILES = {
module: "ak-p20-interim.mock.ts",
html: "ak-p20-interim.html",
snapshot: "ak-p20-interim.snapshot.json",
} as const;
// VA-P24a self-service subscription builder (AD-28) — re-baseline on the VD-P24 design-nachzug.
const VA_P24A_INTERIM_BASELINE_FILES = {
module: "va-p24a-interim.mock.ts",
html: "va-p24a-interim.html",
snapshot: "va-p24a-interim.snapshot.json",
} as const;
// VA-P26a customer portal (AD-28) — re-baseline on the VD-P26 design-nachzug.
const VA_P26A_INTERIM_BASELINE_FILES = {
module: "va-p26a-customer-portal-interim.mock.ts",
html: "va-p26a-customer-portal-interim.html",
snapshot: "va-p26a-customer-portal-interim.snapshot.json",
} as const;
// VA-P26b customer-portal interaction, admin and lifecycle screens (AD-28).
const VA_P26B_INTERIM_BASELINE_FILES = {
module: "va-p26b-customer-portal-interim.mock.ts",
html: "va-p26b-customer-portal-interim.html",
snapshot: "va-p26b-customer-portal-interim.snapshot.json",
} as const;
// VA-P26c customer-portal custom-domain admin screen (AD-28).
const VA_P26C_INTERIM_BASELINE_FILES = {
module: "va-p26c-customer-portal-domain-interim.mock.ts",
html: "va-p26c-customer-portal-domain-interim.html",
snapshot: "va-p26c-customer-portal-domain-interim.snapshot.json",
} as const;
// AK-P15a Aircall telephony interim screens (AD-28) — re-baseline on the D-P15 design-nachzug.
const AK_P15A_TELEPHONY_BASELINE_FILES = {
module: "ak-p15a-telephony-interim.mock.ts",
html: "ak-p15a-telephony-interim.html",
snapshot: "ak-p15a-telephony-interim.snapshot.json",
} as const;
// AK-P15b team-chat interim index (AD-28) — re-baseline on the D-CHAT design-nachzug.
const AK_P15B_CHAT_INDEX_BASELINE_FILES = {
module: "ak-p15b-chat-index-interim.mock.ts",
html: "ak-p15b-chat-index-interim.html",
snapshot: "ak-p15b-chat-index-interim.snapshot.json",
} as const;
// AK-P15b team-chat interim detail (AD-28) — re-baseline on the D-CHAT design-nachzug.
const AK_P15B_CHAT_DETAIL_BASELINE_FILES = {
module: "ak-p15b-chat-detail-interim.mock.ts",
html: "ak-p15b-chat-detail-interim.html",
snapshot: "ak-p15b-chat-detail-interim.snapshot.json",
} as const;
// R-P12b flow editor (Subframe delta) — contract snapshot of picker/context-menu/node-config/reverse-recipient surfaces.
const R_P12B_BASELINE_FILES = {
module: "r-p12b-editor.mock.ts",
html: "r-p12b-editor.html",
snapshot: "r-p12b-editor.snapshot.json",
} as const;
// AK-UI-01 email inbox interim screen (AD-28) — interim baseline, re-baselined on the design-nachzug.
const AK_UI_01_BASELINE_FILES = {
module: "ak-ui-01-email-inbox.mock.ts",
html: "ak-ui-01-email-inbox.html",
snapshot: "ak-ui-01-email-inbox.snapshot.json",
} as const;
// AK-UI-02 email message detail interim screen (AD-28) — interim baseline, re-baselined on the design-nachzug.
const AK_UI_02_BASELINE_FILES = {
module: "ak-ui-02-email-detail.mock.ts",
html: "ak-ui-02-email-detail.html",
snapshot: "ak-ui-02-email-detail.snapshot.json",
} as const;
// AK-UI-03 email compose interim screen (AD-28) — interim baseline, re-baselined on the design-nachzug.
const AK_UI_03_BASELINE_FILES = {
module: "ak-ui-03-email-compose.mock.ts",
html: "ak-ui-03-email-compose.html",
snapshot: "ak-ui-03-email-compose.snapshot.json",
} as const;
// AK-UI-05 tenant plugin catalog interim screen (AD-28) — interim baseline, re-baselined on the D-P5 design-nachzug.
const AK_UI_05_BASELINE_FILES = {
module: "ak-ui-05-plugins.mock.ts",
html: "ak-ui-05-plugins.html",
snapshot: "ak-ui-05-plugins.snapshot.json",
} as const;
const P20_INTERIM_VIEW_MODELS = {
subscription: { status: "empty" },
usage: { status: "empty" },
billing: { status: "empty" },
demoData: { status: "empty" },
checklist: { status: "empty" },
} as const satisfies P20InterimViewModels;
export type UiMockVisualResult = {
readonly passes: readonly string[];
readonly failures: readonly string[];
readonly observations: readonly string[];
};
export function buildUiMockVisualDrill() {
const page = createHotSwapDrillPage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildDashboardInterimVisualDrill() {
const page = createDashboardInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildSpotlightInterimVisualDrill() {
const page = createSpotlightInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildAutomationEditorVisualDrill() {
const page = createAutomationEditorFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildEmailInboxInterimVisualDrill() {
const page = createEmailInboxInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildEmailDetailInterimVisualDrill() {
const page = createEmailDetailInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildEmailComposeInterimVisualDrill() {
const page = createEmailComposeInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildPluginCatalogInterimVisualDrill() {
const page = createPluginCatalogInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildP20InterimVisualDrill() {
const page = createP20InterimFixturePage(P20_INTERIM_VIEW_MODELS);
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildVaP24aInterimVisualDrill() {
const page = createVaP24aInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildCrmInterimVisualDrill() {
const page = createCrmInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildLeadInterimVisualDrill() {
const page = createLeadInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildProjektBoardInterimVisualDrill() {
const page = createProjektBoardInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildVaP26aInterimVisualDrill() {
const page = createVaP26aInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildVaP26bInterimVisualDrill() {
const page = createVaP26bInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildVaP26cInterimVisualDrill() {
const page = createVaP26cInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildTelephonyInterimVisualDrill() {
const page = createTelephonyInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildChatIndexInterimVisualDrill() {
const page = createChatIndexInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function buildChatDetailInterimVisualDrill() {
const page = createChatDetailInterimFixturePage();
const artifacts = generateMockArtifacts(page);
return {
module: artifacts.module,
html: artifacts.html,
snapshot: `${JSON.stringify(artifacts.snapshot, null, 2)}\n`,
};
}
export function writeUiMockVisualBaseline(rootDir = process.cwd()) {
const drill = buildUiMockVisualDrill();
const crmInterimDrill = buildCrmInterimVisualDrill();
const leadInterimDrill = buildLeadInterimVisualDrill();
const projektBoardInterimDrill = buildProjektBoardInterimVisualDrill();
const p20InterimDrill = buildP20InterimVisualDrill();
const vaP24aInterimDrill = buildVaP24aInterimVisualDrill();
const vaP26aInterimDrill = buildVaP26aInterimVisualDrill();
const vaP26bInterimDrill = buildVaP26bInterimVisualDrill();
const vaP26cInterimDrill = buildVaP26cInterimVisualDrill();
const telephonyInterimDrill = buildTelephonyInterimVisualDrill();
const chatIndexInterimDrill = buildChatIndexInterimVisualDrill();
const chatDetailInterimDrill = buildChatDetailInterimVisualDrill();
const baselineDir = path.join(rootDir, BASELINE_DIR);
fs.mkdirSync(baselineDir, { recursive: true });
for (const [files, artifacts] of [
[BASELINE_FILES, drill],
[CRM_INTERIM_BASELINE_FILES, crmInterimDrill],
[LEADS_INTERIM_BASELINE_FILES, leadInterimDrill],
[PROJEKT_INTERIM_BASELINE_FILES, projektBoardInterimDrill],
[P20_INTERIM_BASELINE_FILES, p20InterimDrill],
[VA_P24A_INTERIM_BASELINE_FILES, vaP24aInterimDrill],
[VA_P26A_INTERIM_BASELINE_FILES, vaP26aInterimDrill],
[VA_P26B_INTERIM_BASELINE_FILES, vaP26bInterimDrill],
[VA_P26C_INTERIM_BASELINE_FILES, vaP26cInterimDrill],
[AK_P15A_TELEPHONY_BASELINE_FILES, telephonyInterimDrill],
[AK_P15B_CHAT_INDEX_BASELINE_FILES, chatIndexInterimDrill],
[AK_P15B_CHAT_DETAIL_BASELINE_FILES, chatDetailInterimDrill],
] as const) {
for (const [artifactName, fileName] of Object.entries(files)) {
fs.writeFileSync(path.join(baselineDir, fileName), artifacts[artifactName as keyof typeof artifacts], "utf8");
}
}
const dashboardDrill = buildDashboardInterimVisualDrill();
for (const [artifactName, fileName] of Object.entries(R_P11B_BASELINE_FILES)) {
fs.writeFileSync(path.join(baselineDir, fileName), dashboardDrill[artifactName as keyof typeof dashboardDrill], "utf8");
}
const spotlightDrill = buildSpotlightInterimVisualDrill();
for (const [artifactName, fileName] of Object.entries(R_P11A_BASELINE_FILES)) {
fs.writeFileSync(path.join(baselineDir, fileName), spotlightDrill[artifactName as keyof typeof spotlightDrill], "utf8");
}
const automationEditorDrill = buildAutomationEditorVisualDrill();
for (const [artifactName, fileName] of Object.entries(R_P12B_BASELINE_FILES)) {
fs.writeFileSync(path.join(baselineDir, fileName), automationEditorDrill[artifactName as keyof typeof automationEditorDrill], "utf8");
}
const emailInboxDrill = buildEmailInboxInterimVisualDrill();
for (const [artifactName, fileName] of Object.entries(AK_UI_01_BASELINE_FILES)) {
fs.writeFileSync(path.join(baselineDir, fileName), emailInboxDrill[artifactName as keyof typeof emailInboxDrill], "utf8");
}
const emailDetailDrill = buildEmailDetailInterimVisualDrill();
for (const [artifactName, fileName] of Object.entries(AK_UI_02_BASELINE_FILES)) {
fs.writeFileSync(path.join(baselineDir, fileName), emailDetailDrill[artifactName as keyof typeof emailDetailDrill], "utf8");
}
const emailComposeDrill = buildEmailComposeInterimVisualDrill();
for (const [artifactName, fileName] of Object.entries(AK_UI_03_BASELINE_FILES)) {
fs.writeFileSync(path.join(baselineDir, fileName), emailComposeDrill[artifactName as keyof typeof emailComposeDrill], "utf8");
}
const pluginCatalogDrill = buildPluginCatalogInterimVisualDrill();
for (const [artifactName, fileName] of Object.entries(AK_UI_05_BASELINE_FILES)) {
fs.writeFileSync(path.join(baselineDir, fileName), pluginCatalogDrill[artifactName as keyof typeof pluginCatalogDrill], "utf8");
}
}
export function verifyUiMockVisualBaseline(rootDir = process.cwd()): UiMockVisualResult {
const drill = buildUiMockVisualDrill();
const crmInterimDrill = buildCrmInterimVisualDrill();
const leadInterimDrill = buildLeadInterimVisualDrill();
const projektBoardInterimDrill = buildProjektBoardInterimVisualDrill();
const p20InterimDrill = buildP20InterimVisualDrill();
const vaP24aInterimDrill = buildVaP24aInterimVisualDrill();
const vaP26aInterimDrill = buildVaP26aInterimVisualDrill();
const vaP26bInterimDrill = buildVaP26bInterimVisualDrill();
const vaP26cInterimDrill = buildVaP26cInterimVisualDrill();
const telephonyInterimDrill = buildTelephonyInterimVisualDrill();
const chatIndexInterimDrill = buildChatIndexInterimVisualDrill();
const chatDetailInterimDrill = buildChatDetailInterimVisualDrill();
const passes: string[] = [];
const failures: string[] = [];
const observations: string[] = [];
assert.equal(drill.module.includes("export const mockPage"), true);
assert.equal(drill.html.includes('data-ui-mock-route="/mock/phase-c/hot-swap"'), true);
const domSmoke = verifyRenderedHotSwapDrill(drill.html);
passes.push(...domSmoke.passes);
failures.push(...domSmoke.failures);
assert.equal(crmInterimDrill.module.includes('"designStatus": "interim"'), true);
assert.equal(crmInterimDrill.html.includes('data-ui-mock-page="r-p6-crm-interim"'), true);
assert.equal(leadInterimDrill.module.includes('"designStatus": "interim"'), true);
assert.equal(leadInterimDrill.html.includes('data-ui-mock-page="r-p7-leads-interim"'), true);
assert.equal(projektBoardInterimDrill.module.includes('"designStatus": "interim"'), true);
assert.equal(projektBoardInterimDrill.html.includes('data-ui-mock-page="r-p8-projects-interim"'), true);
assert.equal(p20InterimDrill.module.includes('"designStatus": "interim"'), true);
assert.equal(p20InterimDrill.html.includes('data-ui-mock-page="ak-p20-interim"'), true);
const p20DomSmoke = verifyRenderedP20InterimDrill(p20InterimDrill.html);
passes.push(...p20DomSmoke.passes);
failures.push(...p20DomSmoke.failures);
assert.equal(vaP24aInterimDrill.module.includes('"designStatus": "interim"'), true);
assert.equal(vaP24aInterimDrill.html.includes('data-ui-mock-page="va-p24a-interim"'), true);
const vaP24aDomSmoke = verifyRenderedVaP24aInterimDrill(vaP24aInterimDrill.html);
passes.push(...vaP24aDomSmoke.passes);
failures.push(...vaP24aDomSmoke.failures);
assert.equal(vaP26aInterimDrill.module.includes('"designStatus": "interim"'), true);
assert.equal(vaP26aInterimDrill.html.includes('data-ui-mock-page="va-p26a-customer-portal-interim"'), true);
const vaP26aDomSmoke = verifyRenderedVaP26aInterimDrill(vaP26aInterimDrill.html);
passes.push(...vaP26aDomSmoke.passes);
failures.push(...vaP26aDomSmoke.failures);
assert.equal(vaP26bInterimDrill.module.includes('"designStatus": "interim"'), true);
assert.equal(vaP26bInterimDrill.html.includes('data-ui-mock-page="va-p26b-customer-portal-interim"'), true);
const vaP26bDomSmoke = verifyRenderedVaP26bInterimDrill(vaP26bInterimDrill.html);
passes.push(...vaP26bDomSmoke.passes);
failures.push(...vaP26bDomSmoke.failures);
assert.equal(vaP26cInterimDrill.module.includes('"designStatus": "interim"'), true);
assert.equal(vaP26cInterimDrill.html.includes('data-ui-mock-page="va-p26c-customer-portal-domain-interim"'), true);
const vaP26cDomSmoke = verifyRenderedVaP26cInterimDrill(vaP26cInterimDrill.html);
passes.push(...vaP26cDomSmoke.passes);
failures.push(...vaP26cDomSmoke.failures);
assert.equal(telephonyInterimDrill.module.includes('"designStatus": "interim"'), true);
assert.equal(telephonyInterimDrill.html.includes('data-ui-mock-page="ak-p15a-telephony-interim"'), true);
const telephonyDomSmoke = verifyRenderedTelephonyInterimDrill(telephonyInterimDrill.html);
passes.push(...telephonyDomSmoke.passes);
failures.push(...telephonyDomSmoke.failures);
assert.equal(chatIndexInterimDrill.module.includes('"designStatus": "interim"'), true);
assert.equal(chatIndexInterimDrill.html.includes('data-ui-mock-page="ak-p15b-chat-index-interim"'), true);
const chatIndexDomSmoke = verifyRenderedChatIndexInterimDrill(chatIndexInterimDrill.html);
passes.push(...chatIndexDomSmoke.passes);
failures.push(...chatIndexDomSmoke.failures);
assert.equal(chatDetailInterimDrill.module.includes('"source": "http_ack"'), true);
assert.equal(chatDetailInterimDrill.module.includes('"source": "local_echo"'), false);
assert.equal(chatDetailInterimDrill.html.includes('data-ui-mock-page="ak-p15b-chat-detail-interim"'), true);
const chatDetailDomSmoke = verifyRenderedChatDetailInterimDrill(chatDetailInterimDrill.html);
passes.push(...chatDetailDomSmoke.passes);
failures.push(...chatDetailDomSmoke.failures);
for (const [files, artifacts] of [
[BASELINE_FILES, drill],
[CRM_INTERIM_BASELINE_FILES, crmInterimDrill],
[LEADS_INTERIM_BASELINE_FILES, leadInterimDrill],
[PROJEKT_INTERIM_BASELINE_FILES, projektBoardInterimDrill],
[P20_INTERIM_BASELINE_FILES, p20InterimDrill],
[VA_P24A_INTERIM_BASELINE_FILES, vaP24aInterimDrill],
[VA_P26A_INTERIM_BASELINE_FILES, vaP26aInterimDrill],
[VA_P26B_INTERIM_BASELINE_FILES, vaP26bInterimDrill],
[VA_P26C_INTERIM_BASELINE_FILES, vaP26cInterimDrill],
[AK_P15A_TELEPHONY_BASELINE_FILES, telephonyInterimDrill],
[AK_P15B_CHAT_INDEX_BASELINE_FILES, chatIndexInterimDrill],
[AK_P15B_CHAT_DETAIL_BASELINE_FILES, chatDetailInterimDrill],
] as const) {
for (const [artifactName, fileName] of Object.entries(files)) {
const relativePath = `${BASELINE_DIR}/${fileName}`;
const baselinePath = path.join(rootDir, relativePath);
if (!fs.existsSync(baselinePath)) {
failures.push(`${relativePath}: missing UI mock static artifact baseline`);
continue;
}
const expected = normalizeNewlines(fs.readFileSync(baselinePath, "utf8"));
const actual = normalizeNewlines(artifacts[artifactName as keyof typeof artifacts]);
if (actual === expected) {
passes.push(`${relativePath}: matches generated hot-swap drill artifact`);
} else {
failures.push(`${relativePath}: static artifact baseline drifted; run pnpm run verify:ui-visual -- --update after reviewing the change`);
}
}
}
const dashboardDrill = buildDashboardInterimVisualDrill();
assert.equal(dashboardDrill.module.includes("export const mockPage"), true);
assert.equal(dashboardDrill.html.includes('data-ui-mock-route="/mock/r-p11b/dashboard"'), true);
const dashboardDomSmoke = verifyRenderedDashboardInterimDrill(dashboardDrill.html);
passes.push(...dashboardDomSmoke.passes);
failures.push(...dashboardDomSmoke.failures);
for (const [artifactName, fileName] of Object.entries(R_P11B_BASELINE_FILES)) {
const relativePath = `${BASELINE_DIR}/${fileName}`;
const baselinePath = path.join(rootDir, relativePath);
if (!fs.existsSync(baselinePath)) {
failures.push(`${relativePath}: missing R-P11b dashboard interim baseline`);
continue;
}
const expected = normalizeNewlines(fs.readFileSync(baselinePath, "utf8"));
const actual = normalizeNewlines(dashboardDrill[artifactName as keyof typeof dashboardDrill]);
if (actual === expected) {
passes.push(`${relativePath}: matches generated R-P11b dashboard interim artifact`);
} else {
failures.push(`${relativePath}: interim baseline drifted; run pnpm run verify:ui-visual -- --update after reviewing the change`);
}
}
const spotlightDrill = buildSpotlightInterimVisualDrill();
assert.equal(spotlightDrill.module.includes("export const mockPage"), true);
assert.equal(spotlightDrill.html.includes('data-ui-mock-route="/mock/r-p11a/spotlight"'), true);
const spotlightDomSmoke = verifyRenderedSpotlightInterimDrill(spotlightDrill.html);
passes.push(...spotlightDomSmoke.passes);
failures.push(...spotlightDomSmoke.failures);
for (const [artifactName, fileName] of Object.entries(R_P11A_BASELINE_FILES)) {
const relativePath = `${BASELINE_DIR}/${fileName}`;
const baselinePath = path.join(rootDir, relativePath);
if (!fs.existsSync(baselinePath)) {
failures.push(`${relativePath}: missing R-P11a Spotlight interim baseline`);
continue;
}
const expected = normalizeNewlines(fs.readFileSync(baselinePath, "utf8"));
const actual = normalizeNewlines(spotlightDrill[artifactName as keyof typeof spotlightDrill]);
if (actual === expected) {
passes.push(`${relativePath}: matches generated R-P11a Spotlight interim artifact`);
} else {
failures.push(`${relativePath}: interim baseline drifted; run pnpm run verify:ui-visual -- --update after reviewing the change`);
}
}
const automationEditorDrill = buildAutomationEditorVisualDrill();
assert.equal(automationEditorDrill.module.includes("export const mockPage"), true);
assert.equal(automationEditorDrill.html.includes('data-ui-mock-route="/mock/r-p12b/editor"'), true);
const automationEditorDomSmoke = verifyRenderedAutomationEditorDrill(automationEditorDrill.html);
passes.push(...automationEditorDomSmoke.passes);
failures.push(...automationEditorDomSmoke.failures);
for (const [artifactName, fileName] of Object.entries(R_P12B_BASELINE_FILES)) {
const relativePath = `${BASELINE_DIR}/${fileName}`;
const baselinePath = path.join(rootDir, relativePath);
if (!fs.existsSync(baselinePath)) {
failures.push(`${relativePath}: missing R-P12b flow editor baseline`);
continue;
}
const expected = normalizeNewlines(fs.readFileSync(baselinePath, "utf8"));
const actual = normalizeNewlines(automationEditorDrill[artifactName as keyof typeof automationEditorDrill]);
if (actual === expected) {
passes.push(`${relativePath}: matches generated R-P12b flow editor artifact`);
} else {
failures.push(`${relativePath}: interim baseline drifted; run pnpm run verify:ui-visual -- --update after reviewing the change`);
}
}
const emailInboxDrill = buildEmailInboxInterimVisualDrill();
assert.equal(emailInboxDrill.module.includes("export const mockPage"), true);
assert.equal(emailInboxDrill.html.includes('data-ui-mock-route="/mock/ak-ui-01/emails"'), true);
const emailInboxDomSmoke = verifyRenderedEmailInboxInterimDrill(emailInboxDrill.html);
passes.push(...emailInboxDomSmoke.passes);
failures.push(...emailInboxDomSmoke.failures);
for (const [artifactName, fileName] of Object.entries(AK_UI_01_BASELINE_FILES)) {
const relativePath = `${BASELINE_DIR}/${fileName}`;
const baselinePath = path.join(rootDir, relativePath);
if (!fs.existsSync(baselinePath)) {
failures.push(`${relativePath}: missing AK-UI-01 email inbox interim baseline`);
continue;
}
const expected = normalizeNewlines(fs.readFileSync(baselinePath, "utf8"));
const actual = normalizeNewlines(emailInboxDrill[artifactName as keyof typeof emailInboxDrill]);
if (actual === expected) {
passes.push(`${relativePath}: matches generated AK-UI-01 email inbox interim artifact`);
} else {
failures.push(`${relativePath}: interim baseline drifted; run pnpm run verify:ui-visual -- --update after reviewing the change`);
}
}
const emailDetailDrill = buildEmailDetailInterimVisualDrill();
assert.equal(emailDetailDrill.module.includes("export const mockPage"), true);
assert.equal(emailDetailDrill.html.includes('data-ui-mock-route="/mock/ak-ui-02/emails/msg-1"'), true);
const emailDetailDomSmoke = verifyRenderedEmailDetailInterimDrill(emailDetailDrill.html);
passes.push(...emailDetailDomSmoke.passes);
failures.push(...emailDetailDomSmoke.failures);
for (const [artifactName, fileName] of Object.entries(AK_UI_02_BASELINE_FILES)) {
const relativePath = `${BASELINE_DIR}/${fileName}`;
const baselinePath = path.join(rootDir, relativePath);
if (!fs.existsSync(baselinePath)) {
failures.push(`${relativePath}: missing AK-UI-02 email detail interim baseline`);
continue;
}
const expected = normalizeNewlines(fs.readFileSync(baselinePath, "utf8"));
const actual = normalizeNewlines(emailDetailDrill[artifactName as keyof typeof emailDetailDrill]);
if (actual === expected) {
passes.push(`${relativePath}: matches generated AK-UI-02 email detail interim artifact`);
} else {
failures.push(`${relativePath}: interim baseline drifted; run pnpm run verify:ui-visual -- --update after reviewing the change`);
}
}
const emailComposeDrill = buildEmailComposeInterimVisualDrill();
assert.equal(emailComposeDrill.module.includes("export const mockPage"), true);
assert.equal(emailComposeDrill.html.includes('data-ui-mock-route="/mock/ak-ui-03/emails/new"'), true);
const emailComposeDomSmoke = verifyRenderedEmailComposeInterimDrill(emailComposeDrill.html);
passes.push(...emailComposeDomSmoke.passes);
failures.push(...emailComposeDomSmoke.failures);
for (const [artifactName, fileName] of Object.entries(AK_UI_03_BASELINE_FILES)) {
const relativePath = `${BASELINE_DIR}/${fileName}`;
const baselinePath = path.join(rootDir, relativePath);
if (!fs.existsSync(baselinePath)) {
failures.push(`${relativePath}: missing AK-UI-03 email compose interim baseline`);
continue;
}
const expected = normalizeNewlines(fs.readFileSync(baselinePath, "utf8"));
const actual = normalizeNewlines(emailComposeDrill[artifactName as keyof typeof emailComposeDrill]);
if (actual === expected) {
passes.push(`${relativePath}: matches generated AK-UI-03 email compose interim artifact`);
} else {
failures.push(`${relativePath}: interim baseline drifted; run pnpm run verify:ui-visual -- --update after reviewing the change`);
}
}
const pluginCatalogDrill = buildPluginCatalogInterimVisualDrill();
assert.equal(pluginCatalogDrill.module.includes("export const mockPage"), true);
assert.equal(pluginCatalogDrill.html.includes('data-ui-mock-route="/mock/ak-ui-05/plugins"'), true);
const pluginCatalogDomSmoke = verifyRenderedPluginCatalogInterimDrill(pluginCatalogDrill.html);
passes.push(...pluginCatalogDomSmoke.passes);
failures.push(...pluginCatalogDomSmoke.failures);
for (const [artifactName, fileName] of Object.entries(AK_UI_05_BASELINE_FILES)) {
const relativePath = `${BASELINE_DIR}/${fileName}`;
const baselinePath = path.join(rootDir, relativePath);
if (!fs.existsSync(baselinePath)) {
failures.push(`${relativePath}: missing AK-UI-05 plugin catalog interim baseline`);
continue;
}
const expected = normalizeNewlines(fs.readFileSync(baselinePath, "utf8"));
const actual = normalizeNewlines(pluginCatalogDrill[artifactName as keyof typeof pluginCatalogDrill]);
if (actual === expected) {
passes.push(`${relativePath}: matches generated AK-UI-05 plugin catalog interim artifact`);
} else {
failures.push(`${relativePath}: interim baseline drifted; run pnpm run verify:ui-visual -- --update after reviewing the change`);
}
}
observations.push(
"The UI mock static drill suite, including the AK-P20 and AK-P15a interim fixtures, renders through JSDOM in CI; browser screenshot and E2E commands remain intentionally outside this gate."
);
observations.push("R-P11b dashboard baseline is an AD-28 interim baseline; re-baseline when the final D-DASH optics land.");
observations.push("R-P11a Spotlight baseline is an AD-28 interim baseline; re-baseline when the final D-DASH optics land.");
observations.push("R-P12b flow editor baseline pins the Subframe-delta surface contract (picker, context menu, node config, reverse recipients).");
observations.push("AK-UI-01 email inbox baseline is an AD-28 interim baseline; re-baseline when the final email inbox optics land (design-nachzug).");
observations.push("AK-UI-02 email detail baseline is an AD-28 interim baseline; re-baseline when the final email detail optics land (design-nachzug).");
observations.push("AK-UI-03 email compose baseline is an AD-28 interim baseline; re-baseline when the final compose optics land (design-nachzug).");
observations.push("AK-UI-05 plugin catalog baseline is an AD-28 interim baseline; re-baseline when the final D-P5 optics land (design-nachzug).");
observations.push("AK-P15a telephony baseline is an AD-28 interim baseline; re-baseline when the final D-P15 optics land (design-nachzug).");
observations.push("AK-P15b chat index and detail baselines are AD-28 interim baselines; re-baseline when the final D-CHAT optics land (design-nachzug).");
observations.push("VA-P24a self-service billing baseline is an AD-28 interim baseline; re-baseline when the final VD-P24 optics land (design-nachzug).");
observations.push("VA-P26a customer portal baseline is an AD-28 interim baseline; re-baseline when the final VD-P26 optics land (design-nachzug).");
observations.push(
"VA-P26b customer portal interaction, admin and lifecycle baseline is an AD-28 interim baseline; re-baseline when the final VD-P26 optics land (design-nachzug)."
);
observations.push(
"VA-P26c customer portal domain-admin baseline is an AD-28 interim baseline; re-baseline when the final VD-P26 optics land (design-nachzug)."
);
return { passes, failures, observations };
}
function verifyRenderedVaP26aInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const activeLinks = [...document.querySelectorAll(".mock-link")].filter((item) => item.getAttribute("data-active") === "true");
const stateScript = document.querySelector("#mock-page-state");
if (body?.getAttribute("data-ui-mock-route") === "/:tenant" && shell?.getAttribute("data-ui-mock-page") === "va-p26a-customer-portal-interim") {
passes.push("rendered VA-P26a interim HTML exposes the canonical portal route and page id");
} else {
failures.push("rendered VA-P26a interim HTML is missing its canonical route or page id");
}
if (activeLinks.length === 1) {
passes.push("rendered VA-P26a interim navigation has exactly one active route");
} else {
failures.push(`rendered VA-P26a interim navigation expected one active route but found ${activeLinks.length}`);
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
if (state?.status === "ready" && state?.data?.designStatus === "interim" && state?.data?.viewModel?.data?.portalName === "Acme Kundenportal") {
passes.push("rendered VA-P26a interim state contains the access-scoped portal dashboard VM");
} else {
failures.push("rendered VA-P26a interim state is missing the portal dashboard VM");
}
} catch {
failures.push("rendered VA-P26a interim state payload is missing or invalid");
}
if (document.querySelectorAll(".mock-grid .mock-card").length === 3) {
passes.push("rendered VA-P26a interim HTML includes all three contract metric cards");
} else {
failures.push("rendered VA-P26a interim HTML is missing contract metric cards");
}
return { passes, failures };
}
function verifyRenderedVaP26bInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const activeLinks = [...document.querySelectorAll(".mock-link")].filter((item) => item.getAttribute("data-active") === "true");
const stateScript = document.querySelector("#mock-page-state");
if (body?.getAttribute("data-ui-mock-route") === "/:tenant/upload" && shell?.getAttribute("data-ui-mock-page") === "va-p26b-customer-portal-interim") {
passes.push("rendered VA-P26b interim HTML exposes the canonical upload route and page id");
} else {
failures.push("rendered VA-P26b interim HTML is missing its canonical route or page id");
}
if (activeLinks.length === 1) {
passes.push("rendered VA-P26b interim navigation has exactly one active route");
} else {
failures.push(`rendered VA-P26b interim navigation expected one active route but found ${activeLinks.length}`);
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
const data = state?.data;
const interactions = data?.viewModels?.interactions?.data;
const lifecycle = data?.viewModels?.lifecycle?.data;
const admin = data?.viewModels?.admin?.data;
if (state?.status === "ready" && data?.designStatus === "interim" && data?.screenRoutes?.length === 5 && data?.surfaces?.length === 3) {
passes.push("rendered VA-P26b interim state preserves all five real routes and three VM surfaces");
} else {
failures.push("rendered VA-P26b interim state is missing routes or VM surfaces");
}
if (interactions?.maxFiles === 10 && interactions?.complaintForm?.formSlug === "reklamation" && lifecycle?.actions?.canRequestDeletion === true) {
passes.push("rendered VA-P26b portal VMs preserve upload, complaint and lifecycle contracts");
} else {
failures.push("rendered VA-P26b portal VMs lost upload, complaint or lifecycle contracts");
}
if (admin?.users?.[0]?.kind === "subcontractor" && admin?.inbox?.[0]?.kind === "complaint" && admin?.inbox?.[1]?.kind === "upload") {
passes.push("rendered VA-P26b admin VM preserves subcontractor isolation and inbox priority");
} else {
failures.push("rendered VA-P26b admin VM lost subcontractor or service-inbox contracts");
}
} catch {
failures.push("rendered VA-P26b interim state payload is missing or invalid");
}
if (document.querySelectorAll(".mock-grid .mock-card").length === 3) {
passes.push("rendered VA-P26b interim HTML includes all three contract metric cards");
} else {
failures.push("rendered VA-P26b interim HTML is missing contract metric cards");
}
return { passes, failures };
}
function verifyRenderedVaP26cInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const activeLinks = [...document.querySelectorAll(".mock-link")].filter((item) => item.getAttribute("data-active") === "true");
const stateScript = document.querySelector("#mock-page-state");
const registriesScript = document.querySelector("#mock-page-registries");
if (
body?.getAttribute("data-ui-mock-route") === "/:tenant/settings/customer-portal/domain" &&
shell?.getAttribute("data-ui-mock-page") === "va-p26c-customer-portal-domain-interim"
) {
passes.push("rendered VA-P26c domain-admin HTML exposes its canonical route and page id");
} else {
failures.push("rendered VA-P26c domain-admin HTML is missing its canonical route or page id");
}
if (activeLinks.length === 1) passes.push("rendered VA-P26c domain-admin navigation has exactly one active route");
else failures.push(`rendered VA-P26c domain-admin navigation expected one active route but found ${activeLinks.length}`);
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
const data = state?.data;
const vm = data?.viewModel?.data;
if (state?.status === "ready" && data?.designStatus === "interim" && data?.screenRoute === "/:tenant/settings/customer-portal/domain") {
passes.push("rendered VA-P26c state preserves its AD-28 marker and canonical screen route");
} else {
failures.push("rendered VA-P26c state lost its interim marker or screen route");
}
if (vm?.portalEntitlementSource === "base_plan" && vm?.domain?.status === "cooldown" && vm?.pendingSwitch?.status === "dns_verified") {
passes.push("rendered VA-P26c VM preserves entitlement, safe fallback lifecycle and pending-switch state");
} else {
failures.push("rendered VA-P26c VM lost entitlement, domain lifecycle or pending-switch state");
}
if (vm?.pendingSwitch?.txtRecordValue === "hivegrid-verification-proof-once" && vm?.actions?.canActivateSwitch === true) {
passes.push("rendered VA-P26c VM preserves the one-time claim-bound DNS proof and activation action");
} else {
failures.push("rendered VA-P26c VM lost the claim-bound DNS proof or activation action");
}
} catch {
failures.push("rendered VA-P26c state payload is missing or invalid");
}
try {
const registries = registriesScript?.textContent ? JSON.parse(decodeHtmlEntities(registriesScript.textContent)) : null;
if (registries?.sidebar?.[0]?.permission === "portal.admin" && registries?.breadcrumbs?.length === 3) {
passes.push("rendered VA-P26c registry preserves the portal.admin gate and domain breadcrumb");
} else {
failures.push("rendered VA-P26c registry lost its permission gate or breadcrumb contract");
}
} catch {
failures.push("rendered VA-P26c registry payload is missing or invalid");
}
return { passes, failures };
}
function verifyRenderedVaP24aInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const activeLinks = [...document.querySelectorAll(".mock-link")].filter((item) => item.getAttribute("data-active") === "true");
const breadcrumbs = [...document.querySelectorAll(".mock-breadcrumbs span")].filter((item) => item.textContent !== "/");
const stateScript = document.querySelector("#mock-page-state");
if (body?.getAttribute("data-ui-mock-route") === "/:tenant/settings/billing" && shell?.getAttribute("data-ui-mock-page") === "va-p24a-interim") {
passes.push("rendered VA-P24a interim HTML exposes the canonical subscription route and page id");
} else {
failures.push("rendered VA-P24a interim HTML is missing its canonical route or page id");
}
if (activeLinks.length === 1 && breadcrumbs.length === 2) {
passes.push("rendered VA-P24a interim registries expose one active route and two breadcrumbs");
} else {
failures.push(`rendered VA-P24a interim registries expected one active route and two breadcrumbs but found ${activeLinks.length}/${breadcrumbs.length}`);
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
const viewModel = state?.data?.viewModel;
if (state?.status === "ready" && state?.data?.designStatus === "interim" && state?.data?.screenRoute === "/:tenant/settings/billing") {
passes.push("rendered VA-P24a state preserves its ready AD-28 interim route contract");
} else {
failures.push("rendered VA-P24a state lost its ready/interim route contract");
}
const plans: readonly { readonly prices?: readonly unknown[] }[] = viewModel?.catalog?.plans ?? [];
const priceCount = plans.reduce((total, plan) => total + (plan.prices?.length ?? 0), 0);
if (
viewModel?.overview?.status === "ready" &&
plans.length === 2 &&
priceCount === 4 &&
viewModel?.minimumSeatQuantity === 3 &&
viewModel?.canManage === true &&
viewModel?.trialPolicy?.requiresManualActivation === false &&
viewModel?.trialPolicy?.requiresPaymentMethod === true
) {
passes.push("rendered VA-P24a state carries the serializable overview, catalog, seats and trial policy VM");
} else {
failures.push("rendered VA-P24a state lost its self-service subscription view-model contract");
}
} catch {
failures.push("rendered VA-P24a interim state payload is missing or invalid");
}
return { passes, failures };
}
function verifyRenderedTelephonyInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const activeLinks = [...document.querySelectorAll(".mock-link")].filter((item) => item.getAttribute("data-active") === "true");
const stateScript = document.querySelector("#mock-page-state");
const registriesScript = document.querySelector("#mock-page-registries");
if (body?.getAttribute("data-ui-mock-route") === "/:tenant/calls" && shell?.getAttribute("data-ui-mock-page") === "ak-p15a-telephony-interim") {
passes.push("rendered AK-P15a telephony interim HTML exposes the calls route and interim page id");
} else {
failures.push("rendered AK-P15a telephony interim HTML is missing its route or page id");
}
if (activeLinks.length === 1) {
passes.push("rendered AK-P15a telephony registry has exactly one active route");
} else {
failures.push(`rendered AK-P15a telephony registry expected one active route but found ${activeLinks.length}`);
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
const surfaces: readonly string[] = state?.data?.surfaces ?? [];
const viewModels = state?.data?.viewModels;
if (state?.status === "ready" && state?.data?.designStatus === "interim" && surfaces.join(",") === "settings,calls,callDetail,clickToCall") {
passes.push("rendered AK-P15a telephony state preserves all four AD-28 interim surfaces");
} else {
failures.push("rendered AK-P15a telephony state lost its interim marker or surface contract");
}
if (
viewModels?.settings?.data?.connection?.provider === "aircall" &&
viewModels?.calls?.data?.rows?.length === 2 &&
viewModels?.callDetail?.data?.id === "call-1" &&
viewModels?.clickToCall?.mode === "provider"
) {
passes.push("rendered AK-P15a telephony state carries the serializable settings, list, detail, and click-to-call VMs");
} else {
failures.push("rendered AK-P15a telephony state lost one or more serializable view models");
}
} catch {
failures.push("rendered AK-P15a telephony state payload is missing or invalid");
}
try {
const registries = registriesScript?.textContent ? JSON.parse(decodeHtmlEntities(registriesScript.textContent)) : null;
const permissions = (registries?.sidebar ?? []).map((item: { permission?: string }) => item.permission);
if (permissions.join(",") === "telephony.read_own,telephony.manage" && registries?.breadcrumbs?.length === 2) {
passes.push("rendered AK-P15a telephony registry preserves permission gates and call-detail breadcrumbs");
} else {
failures.push("rendered AK-P15a telephony registry lost its permission gates or breadcrumbs");
}
} catch {
failures.push("rendered AK-P15a telephony registry payload is missing or invalid");
}
return { passes, failures };
}
function verifyRenderedChatIndexInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const activeLinks = [...document.querySelectorAll(".mock-link")].filter((item) => item.getAttribute("data-active") === "true");
const stateScript = document.querySelector("#mock-page-state");
const registriesScript = document.querySelector("#mock-page-registries");
if (body?.getAttribute("data-ui-mock-route") === "/:tenant/chat" && shell?.getAttribute("data-ui-mock-page") === "ak-p15b-chat-index-interim") {
passes.push("rendered AK-P15b chat index interim HTML exposes the channel index route and page id");
} else {
failures.push("rendered AK-P15b chat index interim HTML is missing its route or page id");
}
if (activeLinks.length === 1) {
passes.push("rendered AK-P15b chat index registry has exactly one active route");
} else {
failures.push(`rendered AK-P15b chat index registry expected one active route but found ${activeLinks.length}`);
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
if (state?.status === "ready" && state?.data?.designStatus === "interim" && state?.data?.screenRoute === "/chat") {
passes.push("rendered AK-P15b chat index state preserves its AD-28 interim marker and screen route");
} else {
failures.push("rendered AK-P15b chat index state lost its interim marker or screen route");
}
const viewModel = state?.data?.viewModel;
const rows: readonly { id?: string; hasUnread?: boolean; hasMention?: boolean; isMuted?: boolean }[] = viewModel?.data?.rows ?? [];
const discoverRows: readonly { id?: string; joinHref?: string | null; detailHref?: string | null; canJoin?: boolean }[] =
viewModel?.data?.discoverRows ?? [];
if (
viewModel?.status === "ready" &&
rows.map((row) => row.id).join(",") === "chat-group-montage,chat-dm-mara" &&
rows[0]?.hasUnread === true &&
rows[0]?.hasMention === true &&
rows[1]?.isMuted === true &&
viewModel?.data?.totals?.unread === 3 &&
viewModel?.data?.permissions?.canDiscover === true &&
discoverRows.length === 1 &&
discoverRows[0]?.id === "chat-public-service" &&
discoverRows[0]?.joinHref === "/:tenant/chat" &&
discoverRows[0]?.detailHref === null &&
discoverRows[0]?.canJoin === true
) {
passes.push("rendered AK-P15b chat index state keeps member channels and join-only public discovery available");
} else {
failures.push("rendered AK-P15b chat index state lost its member-channel list contract");
}
} catch {
failures.push("rendered AK-P15b chat index state payload is missing or invalid");
}
try {
const registries = registriesScript?.textContent ? JSON.parse(decodeHtmlEntities(registriesScript.textContent)) : null;
if (
registries?.sidebar?.length === 1 &&
registries.sidebar[0]?.permission === "chat.read_own_channels" &&
registries?.breadcrumbs?.length === 1 &&
registries.breadcrumbs[0]?.permission === "chat.read_own_channels"
) {
passes.push("rendered AK-P15b chat index registry preserves its read permission and breadcrumb contract");
} else {
failures.push("rendered AK-P15b chat index registry lost its read permission or breadcrumb contract");
}
} catch {
failures.push("rendered AK-P15b chat index registry payload is missing or invalid");
}
return { passes, failures };
}
function verifyRenderedChatDetailInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const activeLinks = [...document.querySelectorAll(".mock-link")].filter((item) => item.getAttribute("data-active") === "true");
const stateScript = document.querySelector("#mock-page-state");
const registriesScript = document.querySelector("#mock-page-registries");
if (body?.getAttribute("data-ui-mock-route") === "/:tenant/chat/:id" && shell?.getAttribute("data-ui-mock-page") === "ak-p15b-chat-detail-interim") {
passes.push("rendered AK-P15b chat detail interim HTML exposes the channel detail route and page id");
} else {
failures.push("rendered AK-P15b chat detail interim HTML is missing its route or page id");
}
if (activeLinks.length === 1) {
passes.push("rendered AK-P15b chat detail registry has exactly one active route");
} else {
failures.push(`rendered AK-P15b chat detail registry expected one active route but found ${activeLinks.length}`);
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
if (state?.status === "ready" && state?.data?.designStatus === "interim" && state?.data?.screenRoute === "/chat/:id") {
passes.push("rendered AK-P15b chat detail state preserves its AD-28 interim marker and screen route");
} else {
failures.push("rendered AK-P15b chat detail state lost its interim marker or screen route");
}
const viewModel = state?.data?.viewModel;
const messages: readonly { id?: string; clientMessageId?: string; source?: string; deliveryState?: string }[] = viewModel?.data?.messages ?? [];
if (
viewModel?.status === "ready" &&
messages.length === 2 &&
messages.map((message) => message.source).join(",") === "persisted,http_ack" &&
messages[1]?.id === "message-raffael-1" &&
messages[1]?.clientMessageId === "client-raffael-1" &&
messages[1]?.deliveryState === "sent"
) {
passes.push("rendered AK-P15b chat detail state reconciles local echo and HTTP acknowledgement into one sent message");
} else {
failures.push("rendered AK-P15b chat detail state lost its optimistic-send reconciliation contract");
}
if (
viewModel?.data?.composer?.enabled === true &&
viewModel?.data?.composer?.canAttachFiles === false &&
viewModel?.data?.connection?.state === "online" &&
viewModel?.data?.history?.hasOlder === true &&
viewModel?.data?.history?.olderCursor === "cursor-message-older" &&
viewModel?.data?.typingLabels?.join(",") === "Mara Klein"
) {
passes.push("rendered AK-P15b chat detail state keeps the live composer, connection, typing, and cursor-history contract");
} else {
failures.push("rendered AK-P15b chat detail state lost its live composer, connection, typing, or history contract");
}
} catch {
failures.push("rendered AK-P15b chat detail state payload is missing or invalid");
}
try {
const registries = registriesScript?.textContent ? JSON.parse(decodeHtmlEntities(registriesScript.textContent)) : null;
if (
registries?.sidebar?.length === 1 &&
registries.sidebar[0]?.permission === "chat.read_own_channels" &&
registries?.breadcrumbs?.length === 2 &&
registries.breadcrumbs.every((item: { permission?: string }) => item.permission === "chat.read_own_channels")
) {
passes.push("rendered AK-P15b chat detail registry preserves its read permission and detail breadcrumbs");
} else {
failures.push("rendered AK-P15b chat detail registry lost its read permission or detail breadcrumbs");
}
} catch {
failures.push("rendered AK-P15b chat detail registry payload is missing or invalid");
}
return { passes, failures };
}
function verifyRenderedEmailDetailInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const stateScript = document.querySelector("#mock-page-state");
if (
body?.getAttribute("data-ui-mock-route") === "/mock/ak-ui-02/emails/msg-1" &&
shell?.getAttribute("data-ui-mock-page") === "ak-ui-02-email-detail-interim"
) {
passes.push("rendered AK-UI-02 email detail interim HTML exposes its canonical mock route and page id");
} else {
failures.push("rendered AK-UI-02 email detail interim HTML is missing its canonical mock route or page id");
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
if (state?.status === "ready" && state?.data?.designStatus === "interim" && state?.data?.screenRoute === "/emails/:id") {
passes.push("rendered AK-UI-02 email detail state preserves its AD-28 interim marker and screen route");
} else {
failures.push("rendered AK-UI-02 email detail state is missing its interim marker or screen route");
}
const viewModel = state?.data?.viewModel;
const thread: readonly { id?: string; isFocused?: boolean }[] = viewModel?.data?.thread ?? [];
if (
viewModel?.status === "ready" &&
thread.map((entry) => entry.id).join(",") === "msg-0,msg-1" &&
thread.filter((entry) => entry.isFocused).length === 1
) {
passes.push("rendered AK-UI-02 email detail state carries the serializable thread VM with exactly one focused message");
} else {
failures.push("rendered AK-UI-02 email detail state lost the thread VM contract");
}
// The sanitized bodies must survive serialization and external images must stay blocked by default.
if (viewModel?.data?.allowExternalImages === false && String(viewModel?.data?.thread?.[1]?.safeBodyHtml ?? "").includes("<p>")) {
passes.push("rendered AK-UI-02 email detail state keeps the sanitized bodies and the blocked-image default");
} else {
failures.push("rendered AK-UI-02 email detail state lost the sanitized bodies or the blocked-image default");
}
} catch {
failures.push("rendered AK-UI-02 email detail state payload is not parseable JSON");
}
return { passes, failures };
}
function verifyRenderedEmailComposeInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const stateScript = document.querySelector("#mock-page-state");
if (
body?.getAttribute("data-ui-mock-route") === "/mock/ak-ui-03/emails/new" &&
shell?.getAttribute("data-ui-mock-page") === "ak-ui-03-email-compose-interim"
) {
passes.push("rendered AK-UI-03 email compose interim HTML exposes its canonical mock route and page id");
} else {
failures.push("rendered AK-UI-03 email compose interim HTML is missing its canonical mock route or page id");
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
if (state?.status === "ready" && state?.data?.designStatus === "interim" && state?.data?.screenRoute === "/emails/new") {
passes.push("rendered AK-UI-03 email compose state preserves its AD-28 interim marker and screen route");
} else {
failures.push("rendered AK-UI-03 email compose state is missing its interim marker or screen route");
}
const viewModel = state?.data?.viewModel;
const mailboxes: readonly { id?: string }[] = viewModel?.data?.mailboxes ?? [];
if (viewModel?.status === "ready" && mailboxes.map((mailbox) => mailbox.id).join(",") === "mailbox-1,mailbox-2") {
passes.push("rendered AK-UI-03 email compose state carries the serializable compose VM with its mailbox options");
} else {
failures.push("rendered AK-UI-03 email compose state lost the compose VM contract");
}
if (viewModel?.data?.noMailboxKey === "emailInbox.compose.noMailbox" && String(viewModel?.data?.prefill?.quotedHtml ?? "").includes("<blockquote")) {
passes.push("rendered AK-UI-03 email compose state keeps the Klartext no-mailbox key and the quoted reply prefill");
} else {
failures.push("rendered AK-UI-03 email compose state lost the Klartext no-mailbox key or the quoted reply prefill");
}
} catch {
failures.push("rendered AK-UI-03 email compose state payload is not parseable JSON");
}
return { passes, failures };
}
function verifyRenderedPluginCatalogInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const stateScript = document.querySelector("#mock-page-state");
if (body?.getAttribute("data-ui-mock-route") === "/mock/ak-ui-05/plugins" && shell?.getAttribute("data-ui-mock-page") === "ak-ui-05-plugins-interim") {
passes.push("rendered AK-UI-05 plugin catalog interim HTML exposes its canonical mock route and page id");
} else {
failures.push("rendered AK-UI-05 plugin catalog interim HTML is missing its canonical mock route or page id");
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
if (state?.status === "ready" && state?.data?.designStatus === "interim" && state?.data?.screenRoute === "/settings/integrations/plugins") {
passes.push("rendered AK-UI-05 plugin catalog state preserves its AD-28 interim marker and screen route");
} else {
failures.push("rendered AK-UI-05 plugin catalog state is missing its interim marker or screen route");
}
const viewModel = state?.data?.viewModel;
const rows: readonly {
pluginId?: string;
statusKey?: string;
grantOp?: string;
canEnable?: boolean;
missingCapabilities?: readonly string[];
}[] = viewModel?.data?.rows ?? [];
if (viewModel?.status === "ready" && rows.map((row) => row.pluginId).join(",") === "com.demo.outbound,com.demo.workflow-hook") {
passes.push("rendered AK-UI-05 plugin catalog state carries the serializable plugin catalog VM with both fixture plugins");
} else {
failures.push("rendered AK-UI-05 plugin catalog state lost the plugin catalog VM contract");
}
// The consent gate is the security-relevant part of this screen: a plugin
// with a missing grant must never be directly enable-able, and its approval
// has to fold the enable into the grant op.
const pending = rows[1];
if (pending?.statusKey === "shared.disabled" && pending?.canEnable === false && pending?.grantOp === "grantAndEnable") {
passes.push("rendered AK-UI-05 plugin catalog state keeps the consent gate: no enable without an approved capability grant");
} else {
failures.push("rendered AK-UI-05 plugin catalog state lost the capability consent gate");
}
} catch {
failures.push("rendered AK-UI-05 plugin catalog state payload is not parseable JSON");
}
return { passes, failures };
}
function verifyRenderedEmailInboxInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const stateScript = document.querySelector("#mock-page-state");
if (body?.getAttribute("data-ui-mock-route") === "/mock/ak-ui-01/emails" && shell?.getAttribute("data-ui-mock-page") === "ak-ui-01-email-inbox-interim") {
passes.push("rendered AK-UI-01 email inbox interim HTML exposes its canonical mock route and page id");
} else {
failures.push("rendered AK-UI-01 email inbox interim HTML is missing its canonical mock route or page id");
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
if (state?.status === "ready" && state?.data?.designStatus === "interim" && state?.data?.screenRoute === "/emails") {
passes.push("rendered AK-UI-01 email inbox state preserves its AD-28 interim marker and screen route");
} else {
failures.push("rendered AK-UI-01 email inbox state is missing its interim marker or screen route");
}
const viewModel = state?.data?.viewModel;
const rows: readonly { id?: string }[] = viewModel?.data?.rows ?? [];
if (viewModel?.status === "ready" && rows.map((row) => row.id).join(",") === "msg-1,msg-2" && viewModel?.data?.nextCursor) {
passes.push("rendered AK-UI-01 email inbox state carries the serializable inbox list VM with rows and cursor");
} else {
failures.push("rendered AK-UI-01 email inbox state lost the inbox list VM contract");
}
if (
viewModel?.data?.emptyHeadlineKey === "emailInbox.list.empty.headline" &&
viewModel?.data?.noMailboxHeadlineKey === "emailInbox.list.noMailbox.headline"
) {
passes.push("rendered AK-UI-01 email inbox state keeps the Klartext empty/no-mailbox keys");
} else {
failures.push("rendered AK-UI-01 email inbox state lost the Klartext empty/no-mailbox keys");
}
} catch {
failures.push("rendered AK-UI-01 email inbox state payload is not parseable JSON");
}
return { passes, failures };
}
function verifyRenderedAutomationEditorDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const stateScript = document.querySelector("#mock-page-state");
if (body?.getAttribute("data-ui-mock-route") === "/mock/r-p12b/editor" && shell?.getAttribute("data-ui-mock-page") === "r-p12b-editor") {
passes.push("rendered R-P12b flow editor HTML exposes its canonical mock route and page id");
} else {
failures.push("rendered R-P12b flow editor HTML is missing its canonical mock route or page id");
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
if (state?.status === "ready" && state?.data?.designStatus === "subframe" && state?.data?.editor?.steps?.length === 3) {
passes.push("rendered R-P12b flow editor state pins the Subframe design marker and the three-step fixture flow");
} else {
failures.push("rendered R-P12b flow editor state is missing its subframe marker or fixture steps");
}
const recipients: readonly unknown[] = state?.data?.reverseExample?.recipients ?? [];
if (state?.data?.reverseExample?.reversible === false && recipients.length === 2) {
passes.push("rendered R-P12b flow editor state preserves the non-retractable reverse recipient contract");
} else {
failures.push("rendered R-P12b flow editor state lost the reverse recipient contract");
}
} catch {
failures.push("rendered R-P12b flow editor state payload is not parseable JSON");
}
return { passes, failures };
}
function verifyRenderedSpotlightInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const activeLinks = [...document.querySelectorAll(".mock-link")].filter((item) => item.getAttribute("data-active") === "true");
const stateScript = document.querySelector("#mock-page-state");
const registriesScript = document.querySelector("#mock-page-registries");
if (body?.getAttribute("data-ui-mock-route") === "/mock/r-p11a/spotlight" && shell?.getAttribute("data-ui-mock-page") === "r-p11a-spotlight-interim") {
passes.push("rendered R-P11a Spotlight interim HTML exposes only its canonical mock route and page id");
} else {
failures.push("rendered R-P11a Spotlight interim HTML is missing its canonical mock route or page id");
}
if (activeLinks.length === 0) {
passes.push("rendered R-P11a Spotlight interim navigation has no user-facing route entry");
} else {
failures.push(`rendered R-P11a Spotlight interim expected no active navigation route but found ${activeLinks.length}`);
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
const groups: readonly { entityType?: string; items?: readonly unknown[] }[] = state?.data?.groups ?? [];
if (state?.status === "ready" && state?.data?.designStatus === "interim") {
passes.push("rendered R-P11a Spotlight state preserves its AD-28 interim marker");
} else {
failures.push("rendered R-P11a Spotlight state is missing its ready/interim marker");
}
if (state?.data?.surfaces?.desktop === "modal" && state?.data?.surfaces?.mobile === "viewport") {
passes.push("rendered R-P11a Spotlight state preserves desktop modal and mobile viewport surfaces");
} else {
failures.push("rendered R-P11a Spotlight state lost its desktop/mobile surface contract");
}
if (groups.map((group) => group.entityType).join(",") === "contact,project" && groups.every((group) => (group.items?.length ?? 0) === 1)) {
passes.push("rendered R-P11a Spotlight state preserves stable grouped search results");
} else {
failures.push("rendered R-P11a Spotlight state lost its grouped search results");
}
if (state?.data?.initialResultLimit === 20 && state?.data?.maxResultLimit === 50 && state?.data?.recentSearches?.length === 1) {
passes.push("rendered R-P11a Spotlight state preserves result limits and RecentSearch fixture data");
} else {
failures.push("rendered R-P11a Spotlight state lost result limits or RecentSearch fixture data");
}
} catch {
failures.push("rendered R-P11a Spotlight state payload is missing or invalid");
}
try {
const registries = registriesScript?.textContent ? JSON.parse(decodeHtmlEntities(registriesScript.textContent)) : null;
if (
registries?.portals?.length === 1 &&
registries.portals[0]?.slot === "shell.commandPalette" &&
registries.portals[0]?.permission === "search.use" &&
!registries?.sidebar &&
!registries?.mobile
) {
passes.push("rendered R-P11a Spotlight registry is permission-gated and portal-only");
} else {
failures.push("rendered R-P11a Spotlight registry is not permission-gated portal-only chrome");
}
} catch {
failures.push("rendered R-P11a Spotlight registry payload is missing or invalid");
}
return { passes, failures };
}
function verifyRenderedDashboardInterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const stateScript = document.querySelector("#mock-page-state");
if (body?.getAttribute("data-ui-mock-route") === "/mock/r-p11b/dashboard" && body?.getAttribute("data-ui-mock-status") === "ready") {
passes.push("rendered R-P11b dashboard interim HTML exposes canonical mock route and ready status");
} else {
failures.push("rendered R-P11b dashboard interim HTML is missing canonical mock route or ready status");
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
// The payload ships exactly the layout the server derived from the member's
// seat — the other fixed layouts are neither rendered nor selectable.
const layout: { role?: string; mobileFirst?: boolean; widgets?: readonly unknown[] } | undefined = state?.data?.layout;
if (state?.status === "ready" && state?.data?.activeRole === "gf" && layout?.role === "gf") {
passes.push("rendered R-P11b dashboard state payload carries the derived role layout");
} else {
failures.push("rendered R-P11b dashboard state payload is missing the derived role layout");
}
if (state?.data?.roles === undefined && (layout?.widgets?.length ?? 0) > 0) {
passes.push("rendered R-P11b dashboard state payload ships only the active layout");
} else {
failures.push("rendered R-P11b dashboard state payload still carries the non-active role layouts");
}
} catch {
failures.push("rendered R-P11b dashboard state payload is missing or invalid");
}
return { passes, failures };
}
function verifyRenderedP20InterimDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const activeLinks = [...document.querySelectorAll(".mock-link")].filter((item) => item.getAttribute("data-active") === "true");
const cards = [...document.querySelectorAll(".mock-grid .mock-card")];
const activity = [...document.querySelectorAll(".mock-activity li")];
const stateScript = document.querySelector("#mock-page-state");
if (body?.getAttribute("data-ui-mock-route") === "/:tenant/settings/billing" && shell?.getAttribute("data-ui-mock-page") === "ak-p20-interim") {
passes.push("rendered AK-P20 interim HTML exposes the subscription route and interim page id");
} else {
failures.push("rendered AK-P20 interim HTML is missing its route or page id");
}
if (activeLinks.length === 1 && activity.length === 5) {
passes.push("rendered AK-P20 interim navigation has one active route and covers five screens");
} else {
failures.push(`rendered AK-P20 interim fixture expected one active route and five screens but found ${activeLinks.length}/${activity.length}`);
}
if (cards.length === 3 && document.body.textContent?.includes("AK-P20 interim UI")) {
passes.push("rendered AK-P20 interim page includes coverage cards and its interim title");
} else {
failures.push("rendered AK-P20 interim page is missing coverage cards or its interim title");
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
const viewModelCount = state?.data?.viewModels ? Object.keys(state.data.viewModels).length : 0;
if (state?.status === "ready" && state?.data?.designStatus === "interim" && viewModelCount === 5) {
passes.push("rendered AK-P20 interim state preserves all five serializable view models");
} else {
failures.push("rendered AK-P20 interim state is missing its interim marker or view models");
}
} catch {
failures.push("rendered AK-P20 interim state payload is missing or invalid");
}
return { passes, failures };
}
function verifyRenderedHotSwapDrill(html: string): { passes: string[]; failures: string[] } {
const passes: string[] = [];
const failures: string[] = [];
const document = new JSDOM(html).window.document;
const body = document.body;
const shell = document.querySelector(".mock-shell");
const activeLinks = [...document.querySelectorAll(".mock-link")].filter((item) => item.getAttribute("data-active") === "true");
const cards = [...document.querySelectorAll('section[aria-label="Mock metrics"] .mock-card')];
const stateScript = document.querySelector("#mock-page-state");
if (body?.getAttribute("data-ui-mock-route") === "/mock/phase-c/hot-swap" && shell?.getAttribute("data-ui-mock-page") === "phase-c-hot-swap") {
passes.push("rendered hot-swap HTML exposes canonical mock route and page id");
} else {
failures.push("rendered hot-swap HTML is missing canonical mock route or page id");
}
if (activeLinks.length === 1) {
passes.push("rendered hot-swap navigation has exactly one active route");
} else {
failures.push(`rendered hot-swap navigation expected one active route but found ${activeLinks.length}`);
}
if (cards.length >= 3 && document.body.textContent?.includes("Serialized state")) {
passes.push("rendered hot-swap page includes metric cards and serialized state");
} else {
failures.push("rendered hot-swap page is missing metric cards or serialized state");
}
try {
const state = stateScript?.textContent ? JSON.parse(decodeHtmlEntities(stateScript.textContent)) : null;
if (state?.status === "ready") {
passes.push("rendered hot-swap state payload parses as ready");
} else {
failures.push("rendered hot-swap state payload is missing or invalid");
}
} catch {
failures.push("rendered hot-swap state payload is missing or invalid");
}
return { passes, failures };
}
function decodeHtmlEntities(value: string) {
return value
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&amp;/g, "&");
}
export function formatUiMockVisualResult(result: UiMockVisualResult): string {
const status = result.failures.length > 0 ? "FAIL" : "PASS";
return [
`${status} UI mock static artifact baseline verification`,
...result.passes.map((pass) => `PASS ${pass}`),
...result.observations.map((observation) => `OBSERVE ${observation}`),
...result.failures.map((failure) => `FAIL ${failure}`),
`Summary: ${result.passes.length} passed, ${result.observations.length} observed, ${result.failures.length} failed`,
"",
].join("\n");
}
function normalizeNewlines(value: string): string {
return value.replace(/\r\n/g, "\n");
}
export function assertVisualBaselineUpdateAllowed(env: NodeJS.ProcessEnv = process.env): void {
if (env.CI) {
throw new Error("Refusing to update UI mock static artifact baselines while CI is set.");
}
}
const isCli = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
if (isCli) {
if (process.argv.includes("--update")) {
assertVisualBaselineUpdateAllowed();
writeUiMockVisualBaseline(process.cwd());
}
const result = verifyUiMockVisualBaseline(process.cwd());
const output = formatUiMockVisualResult(result);
if (result.failures.length > 0) {
process.stderr.write(output);
process.exitCode = 1;
} else {
process.stdout.write(output);
}
}