import assert from 'node:assert/strict'; import { execFileSync, spawn } from 'node:child_process'; import { once } from 'node:events'; import fs from 'node:fs'; import net from 'node:net'; import os from 'node:os'; import path from 'node:path'; import { pathToFileURL, fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const skillRoot = path.resolve(__dirname, '..'); const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-webm-artifact-')); const externalReachSource = process.env.ARCHIFY_REACH_CARD_SOURCE ? path.resolve(process.env.ARCHIFY_REACH_CARD_SOURCE) : ''; const externalReachOutput = process.env.ARCHIFY_REACH_CARD_OUTPUT ? path.resolve(process.env.ARCHIFY_REACH_CARD_OUTPUT) : ''; assert.equal(Boolean(externalReachSource), Boolean(externalReachOutput), 'ARCHIFY_REACH_CARD_SOURCE and ARCHIFY_REACH_CARD_OUTPUT must be set together'); function executable(candidates) { for (const candidate of candidates) { if (!candidate) continue; if (candidate.includes(path.sep)) { if (fs.existsSync(candidate)) return candidate; continue; } try { return execFileSync('sh', ['-c', `command -v "$1"`, 'archify-which', candidate], { encoding: 'utf8' }).trim(); } catch (_) { // Try the next platform-specific name. } } return ''; } const chrome = executable([ process.env.ARCHIFY_CHROME, 'google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser', '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', ]); const ffmpeg = executable([process.env.ARCHIFY_FFMPEG, 'ffmpeg']); assert.ok(chrome, 'Chrome/Chromium is required for the WebM artifact smoke test (or set ARCHIFY_CHROME)'); assert.ok(ffmpeg, 'ffmpeg is required for the WebM artifact smoke test (or set ARCHIFY_FFMPEG)'); const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8')); source.meta.animation = 'trace'; source.meta.visual_preset = 'signal-flow'; const input = path.join(tmp, 'motion.architecture.json'); const output = path.join(tmp, 'motion.html'); fs.writeFileSync(input, JSON.stringify(source)); execFileSync(process.execPath, [ path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'), input, output, ], { stdio: ['ignore', 'ignore', 'pipe'] }); const sequenceOutput = path.join(tmp, 'sequence.html'); execFileSync(process.execPath, [ path.join(skillRoot, 'renderers/sequence/render-sequence.mjs'), path.join(skillRoot, 'examples/cache-miss-request.sequence.json'), sequenceOutput, ], { stdio: ['ignore', 'ignore', 'pipe'] }); const routeOutputs = { architecture: output, sequence: sequenceOutput, }; for (const [mode, example] of Object.entries({ workflow: 'agent-tool-call.workflow.json', dataflow: 'product-analytics.dataflow.json', lifecycle: 'agent-run.lifecycle.json', })) { const rendered = path.join(tmp, `${mode}.html`); execFileSync(process.execPath, [ path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), path.join(skillRoot, 'examples', example), rendered, ], { stdio: ['ignore', 'ignore', 'pipe'] }); routeOutputs[mode] = rendered; } function renderLegendFixture(mode, name, document) { const inputPath = path.join(tmp, `${name}.${mode}.json`); const outputPath = path.join(tmp, `${name}.${mode}.html`); fs.writeFileSync(inputPath, JSON.stringify(document)); execFileSync(process.execPath, [ path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), inputPath, outputPath, ], { stdio: ['ignore', 'ignore', 'pipe'] }); return outputPath; } const legendOutputs = { dataflow: renderLegendFixture('dataflow', 'issue-52-default-flow', { schema_version: 1, diagram_type: 'dataflow', meta: { title: 'Default Flow With Store' }, stages: [{ label: 'Input' }, { label: 'Output' }], nodes: [ { id: 'input', type: 'backend', label: 'Input', stage: 0, row: 0 }, { id: 'output', type: 'database', label: 'Output Store', stage: 1, row: 0 }, ], flows: [{ from: 'input', to: 'output', label: 'request', route: 'straight' }], }), lifecycle: renderLegendFixture('lifecycle', 'issue-52-no-waiting', { schema_version: 1, diagram_type: 'lifecycle', meta: { title: 'No Waiting or Failure', viewBox: [720, 566] }, lanes: [{ id: 'main', label: 'Lifecycle' }], states: [ { id: 'started', type: 'start', label: 'Started', lane: 'main', col: 0 }, { id: 'running', type: 'active', label: 'Running', lane: 'main', col: 1 }, { id: 'completed', type: 'success', label: 'Completed', lane: 'main', col: 2 }, ], transitions: [{ from: 'started', to: 'running' }, { from: 'running', to: 'completed' }], }), custom: renderLegendFixture('architecture', 'issue-52-custom-label', { schema_version: 1, diagram_type: 'architecture', meta: { title: 'Custom Legend Label', viewBox: [720, 420], legend: { entries: { frontend: { label: 'Reader & ops' }, external: { label: 'Future integration', visible: true }, }, }, views: [{ id: 'main', label: 'Main', focus: ['ui', 'store'] }], }, components: [ { id: 'ui', type: 'frontend', label: 'UI', pos: [60, 90] }, { id: 'store', type: 'database', label: 'Store', pos: [300, 90] }, ], connections: [], }), hidden: renderLegendFixture('dataflow', 'issue-52-hidden', { schema_version: 1, diagram_type: 'dataflow', meta: { title: 'Hidden Legend', legend: { mode: 'hidden', entries: { database: { visible: true } } } }, stages: [{ label: 'Input' }, { label: 'Output' }], nodes: [ { id: 'input', type: 'backend', label: 'Input', stage: 0, row: 0 }, { id: 'output', type: 'backend', label: 'Output', stage: 1, row: 0 }, ], flows: [{ from: 'input', to: 'output', label: 'request', route: 'straight' }], }), }; const parallelSource = JSON.parse(JSON.stringify(source)); parallelSource.meta.title = 'Parallel Route Identity'; parallelSource.connections[0].label = ''; parallelSource.connections.splice(1, 0, { id: 'users-to-cdn-alternate', from: 'users', to: 'cdn', label: '', }); const parallelInput = path.join(tmp, 'parallel.architecture.json'); const parallelOutput = path.join(tmp, 'parallel.html'); fs.writeFileSync(parallelInput, JSON.stringify(parallelSource)); execFileSync(process.execPath, [ path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'), parallelInput, parallelOutput, ], { stdio: ['ignore', 'ignore', 'pipe'] }); const specialSourceLabel = '入口 汉字 🚀 with an intentionally very long endpoint label'; const specialTargetLabel = '终点服务 ✅ emoji + CJK + a second deliberately long endpoint label for header fitting'; const specialComponents = Array.from({ length: 11 }, (_, index) => ({ id: index === 0 ? 'Route_Source-01' : index === 10 ? 'Route_Target-10' : `route_step-${index}`, type: index === 0 ? 'external' : index === 10 ? 'database' : 'backend', label: index === 0 ? specialSourceLabel : index === 10 ? specialTargetLabel : `步骤 ${index} · service_${index} ⚙️`, sublabel: `hop ${index}`, pos: [40 + index * 820, 280], size: [index === 0 || index === 10 ? 780 : 220, 72], })); const specialRouteSource = { schema_version: 1, diagram_type: 'architecture', meta: { title: '多语言 Route Share Card 🚀 with a deliberately long original diagram title that must fit safely', subtitle: 'Ten exact authored hops', animation: 'trace', }, components: specialComponents, boundaries: [], connections: specialComponents.slice(0, -1).map((component, index) => ({ id: `route_edge-${index + 1}`, from: component.id, to: specialComponents[index + 1].id, label: '', })), cards: [], }; const specialRouteInput = path.join(tmp, 'special-route.architecture.json'); const specialRouteOutput = path.join(tmp, 'special-route.html'); fs.writeFileSync(specialRouteInput, JSON.stringify(specialRouteSource)); execFileSync(process.execPath, [ path.join(skillRoot, 'renderers/architecture/render-architecture.mjs'), specialRouteInput, specialRouteOutput, ], { stdio: ['ignore', 'ignore', 'pipe'] }); assert.equal(typeof WebSocket, 'function', 'Node.js 22+ is required for the Chrome DevTools smoke harness'); function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function waitForExit(child, timeoutMs) { if (child.exitCode !== null || child.signalCode !== null) return true; return Promise.race([ once(child, 'exit').then(() => true), delay(timeoutMs).then(() => false), ]); } async function removeTempTree(directory) { const transientCodes = new Set(['EBUSY', 'ENOTEMPTY', 'EPERM']); let lastError; for (let attempt = 0; attempt < 8; attempt += 1) { try { fs.rmSync(directory, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); return; } catch (error) { if (!transientCodes.has(error?.code)) throw error; lastError = error; await delay(100 * (attempt + 1)); } } console.warn(`warning: temporary Chrome profile cleanup deferred (${lastError?.code || 'unknown'}): ${directory}`); } async function withTimeout(promise, ms, label) { let timer; try { return await Promise.race([ promise, new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms); }), ]); } finally { clearTimeout(timer); } } async function freePort() { const server = net.createServer(); await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); }); const port = server.address().port; await new Promise((resolve) => server.close(resolve)); return port; } async function devtoolsEndpoint(port, chromeProcess, diagnostics) { for (let attempt = 0; attempt < 100; attempt += 1) { try { const response = await fetch(`http://127.0.0.1:${port}/json/version`); if (response.ok) return (await response.json()).webSocketDebuggerUrl; } catch (_) { // Chrome may need a moment to bind the debugging port. } if (chromeProcess.exitCode !== null) break; await delay(50); } const stderr = diagnostics().trim(); const exit = chromeProcess.exitCode === null ? 'still running' : `exited with code ${chromeProcess.exitCode}`; throw new Error(`Chrome did not expose a DevTools endpoint (${exit})${stderr ? `:\n${stderr}` : ''}`); } async function connectCdp(webSocketUrl) { const socket = new WebSocket(webSocketUrl); await new Promise((resolve, reject) => { socket.addEventListener('open', resolve, { once: true }); socket.addEventListener('error', reject, { once: true }); }); let nextId = 0; const pending = new Map(); socket.addEventListener('message', (event) => { const message = JSON.parse(String(event.data)); if (!message.id || !pending.has(message.id)) return; const request = pending.get(message.id); pending.delete(message.id); if (message.error) request.reject(new Error(message.error.message)); else request.resolve(message.result); }); socket.addEventListener('close', () => { for (const request of pending.values()) request.reject(new Error('Chrome DevTools connection closed')); pending.clear(); }); return { socket, send(method, params = {}, sessionId) { const id = ++nextId; return new Promise((resolve, reject) => { pending.set(id, { resolve, reject }); socket.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) })); }); }, }; } async function evaluate(cdp, sessionId, expression, awaitPromise = false) { const response = await cdp.send('Runtime.evaluate', { expression, awaitPromise, returnByValue: true, }, sessionId); if (response.exceptionDetails) { throw new Error(response.exceptionDetails.exception?.description || response.exceptionDetails.text || 'browser evaluation failed'); } return response.result?.value; } const port = await freePort(); let chromeStderr = ''; const chromeProcess = spawn(chrome, [ '--headless=new', '--disable-gpu', '--disable-dev-shm-usage', '--no-sandbox', '--allow-file-access-from-files', '--autoplay-policy=no-user-gesture-required', '--log-level=3', `--user-data-dir=${path.join(tmp, 'chrome-profile')}`, '--remote-debugging-address=127.0.0.1', `--remote-debugging-port=${port}`, 'about:blank', ], { stdio: ['ignore', 'ignore', 'pipe'] }); chromeProcess.stderr.setEncoding('utf8'); chromeProcess.stderr.on('data', (chunk) => { chromeStderr = `${chromeStderr}${chunk}`.slice(-64 * 1024); }); let cdp; let targetId; try { cdp = await connectCdp(await devtoolsEndpoint(port, chromeProcess, () => chromeStderr)); ({ targetId } = await cdp.send('Target.createTarget', { url: 'about:blank' })); await cdp.send('Target.activateTarget', { targetId }); const { sessionId } = await cdp.send('Target.attachToTarget', { targetId, flatten: true }); await cdp.send('Page.enable', {}, sessionId); await cdp.send('Runtime.enable', {}, sessionId); async function navigateReady(file, condition, label) { const url = file instanceof URL ? file.href : pathToFileURL(file).href; await cdp.send('Page.navigate', { url }, sessionId); await cdp.send('Page.bringToFront', {}, sessionId); await cdp.send('Emulation.setFocusEmulationEnabled', { enabled: true }, sessionId); let ready = false; for (let attempt = 0; attempt < 100 && !ready; attempt += 1) { ready = await evaluate(cdp, sessionId, `document.readyState === "complete" && (${condition})`); if (!ready) await delay(50); } assert.equal(ready, true, `${label} did not expose its browser export surface`); } async function verifyResolvedLegendContract(outputs) { async function inspectKinds(file, expectedKinds, theme) { const url = new URL(pathToFileURL(file).href); url.searchParams.set('theme', theme); await navigateReady(url, '!!(window.Archify && Archify.semanticLens)', `legend ${theme}`); const result = await evaluate(cdp, sessionId, `(() => { var svg = document.querySelector('.diagram-container > svg'); var entries = Array.from(svg.querySelectorAll('[data-legend-semantic-kind]')); var vb = svg.viewBox.baseVal; return { theme: document.documentElement.getAttribute('data-theme'), kinds: entries.map(function (entry) { return entry.getAttribute('data-legend-semantic-kind'); }), bridge: !!svg.querySelector('[data-legend-bridge]'), roles: entries.map(function (entry) { return entry.getAttribute('role'); }), aria: entries.map(function (entry) { return entry.getAttribute('aria-label'); }), counts: entries.map(function (entry) { return entry.getAttribute('data-legend-count'); }), tabStops: entries.filter(function (entry) { return entry.getAttribute('tabindex') === '0'; }).length, inside: entries.every(function (entry) { var box = entry.getBBox(); return box.x >= vb.x && box.y >= vb.y && box.x + box.width <= vb.x + vb.width && box.y + box.height <= vb.y + vb.height; }) }; })()`); assert.equal(result.theme, theme); assert.deepEqual(result.kinds, expectedKinds); assert.equal(result.inside, true); return result; } for (const theme of ['dark', 'light']) { const dataflow = await inspectKinds(outputs.dataflow, ['database', 'default'], theme); assert.equal(dataflow.bridge, true); assert.deepEqual(dataflow.roles, ['button', null]); assert.deepEqual(dataflow.aria, ['Inspect data store, 1 node', null]); assert.deepEqual(dataflow.counts, ['1', null]); assert.equal(dataflow.tabStops, 1); const lifecycle = await inspectKinds(outputs.lifecycle, ['start', 'active', 'success'], theme); assert.equal(lifecycle.bridge, true); } await navigateReady(outputs.dataflow, '!!(window.Archify && Archify.semanticLens && Archify.exportMenu)', 'Dataflow database legend runtime'); const databaseRuntime = await evaluate(cdp, sessionId, String.raw`(async function () { var entry = document.querySelector('[data-legend-kind="database"]'); entry.focus(); entry.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); var originalCreateObjectURL = URL.createObjectURL; var originalAnchorClick = HTMLAnchorElement.prototype.click; var captured; URL.createObjectURL = function (blob) { captured = blob.text(); return 'blob:archify-dataflow-legend-smoke'; }; HTMLAnchorElement.prototype.click = function () {}; try { await Archify.exportMenu.run('svg'); var exportedText = await captured; var exported = new DOMParser().parseFromString(exportedText, 'image/svg+xml').documentElement; return { selected: Archify.semanticLens.active(), lensOpen: Archify.semanticLens.isOpen(), exportedKinds: Array.from(exported.querySelectorAll('[data-legend-semantic-kind]')).map(function (item) { return item.getAttribute('data-legend-semantic-kind'); }), exportedBridgeResidue: exported.querySelectorAll('[data-legend-bridge], [data-legend-kind], [data-legend-label], [data-legend-count], [data-legend-bridge-runtime]').length }; } finally { URL.createObjectURL = originalCreateObjectURL; HTMLAnchorElement.prototype.click = originalAnchorClick; } })()`, true); assert.deepEqual(databaseRuntime.selected, ['database']); assert.equal(databaseRuntime.lensOpen, true); assert.deepEqual(databaseRuntime.exportedKinds, ['database', 'default']); assert.equal(databaseRuntime.exportedBridgeResidue, 0); await navigateReady(outputs.custom, '!!(window.Archify && Archify.semanticLens && Archify.exportMenu)', 'custom legend runtime'); const runtime = await evaluate(cdp, sessionId, String.raw`(async function () { var svg = document.querySelector('.diagram-container > svg'); var entries = Array.from(svg.querySelectorAll('[data-legend-semantic-kind]')); var interactive = entries.filter(function (entry) { return entry.hasAttribute('data-legend-kind'); }); var first = interactive[0]; var second = interactive[1]; first.focus(); first.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })); var arrowMoved = document.activeElement === second; second.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); var selected = Archify.semanticLens.active(); var guidedActivated = Archify.guidedViews.activate('main', { updateUrl: false }); var guidedActive = Archify.guidedViews.active(); var visualMatrix = []; for (var preset of ['classic', 'signal-flow', 'blueprint', 'editorial']) { if (!Archify.preset.apply(preset)) throw new Error('could not apply preset ' + preset); for (var theme of ['dark', 'light']) { document.documentElement.setAttribute('data-theme', theme); visualMatrix.push({ preset: preset, theme: theme, kinds: entries.map(function (entry) { return entry.getAttribute('data-legend-semantic-kind'); }), labels: entries.map(function (entry) { return entry.querySelector('text').textContent; }) }); } } var originalCreateObjectURL = URL.createObjectURL; var originalAnchorClick = HTMLAnchorElement.prototype.click; var captured; URL.createObjectURL = function (blob) { captured = blob.text(); return 'blob:archify-legend-smoke'; }; HTMLAnchorElement.prototype.click = function () {}; try { await Archify.exportMenu.run('svg'); var exportedText = await captured; var exported = new DOMParser().parseFromString(exportedText, 'image/svg+xml').documentElement; return { labels: interactive.map(function (entry) { return entry.getAttribute('aria-label'); }), roles: entries.map(function (entry) { return entry.getAttribute('role'); }), counts: interactive.map(function (entry) { return entry.getAttribute('data-legend-count'); }), tabStops: interactive.filter(function (entry) { return entry.getAttribute('tabindex') === '0'; }).length, arrowMoved: arrowMoved, selected: selected, lensOpen: Archify.semanticLens.isOpen(), guidedActivated: guidedActivated, guidedActive: guidedActive, visualMatrix: visualMatrix, forcedUnusedInteractive: entries.at(-1).hasAttribute('data-legend-kind'), exportedKinds: Array.from(exported.querySelectorAll('[data-legend-semantic-kind]')).map(function (entry) { return entry.getAttribute('data-legend-semantic-kind'); }), exportedBridgeResidue: exported.querySelectorAll('[data-legend-bridge], [data-legend-kind], [data-legend-label], [data-legend-count], [data-legend-bridge-runtime]').length, exportedLabels: Array.from(exported.querySelectorAll('[data-legend-semantic-kind] text')).map(function (text) { return text.textContent; }) }; } finally { URL.createObjectURL = originalCreateObjectURL; HTMLAnchorElement.prototype.click = originalAnchorClick; } })()`, true); assert.deepEqual(runtime.labels, ['Inspect Reader & ops, 1 node', 'Inspect Database, 1 node']); assert.deepEqual(runtime.roles, ['button', 'button', null]); assert.deepEqual(runtime.counts, ['1', '1']); assert.equal(runtime.tabStops, 1); assert.equal(runtime.arrowMoved, true); assert.deepEqual(runtime.selected, ['database']); assert.equal(runtime.lensOpen, false); assert.equal(runtime.guidedActivated, true); assert.equal(runtime.guidedActive, 'main'); assert.equal(runtime.visualMatrix.length, 8); for (const entry of runtime.visualMatrix) { assert.deepEqual(entry.kinds, ['frontend', 'database', 'external']); assert.deepEqual(entry.labels, ['Reader & ops', 'Database', 'Future integration']); } assert.equal(runtime.forcedUnusedInteractive, false); assert.deepEqual(runtime.exportedKinds, ['frontend', 'database', 'external']); assert.equal(runtime.exportedBridgeResidue, 0); assert.deepEqual(runtime.exportedLabels, ['Reader & ops', 'Database', 'Future integration']); await cdp.send('Emulation.setEmulatedMedia', { media: 'print' }, sessionId); const printState = await evaluate(cdp, sessionId, `(() => ({ legendDisplay: getComputedStyle(document.querySelector('[data-legend]')).display, runtimeBadgesHidden: Array.from(document.querySelectorAll('[data-legend-bridge-runtime]')).every(function (entry) { return getComputedStyle(entry).display === 'none'; }) }))()`); assert.notEqual(printState.legendDisplay, 'none'); assert.equal(printState.runtimeBadgesHidden, true); await cdp.send('Emulation.setEmulatedMedia', { media: '' }, sessionId); const embedUrl = new URL(pathToFileURL(outputs.custom).href); embedUrl.searchParams.set('embed', '1'); await navigateReady(embedUrl, '!!(window.Archify && Archify.semanticLens)', 'embedded legend'); const embed = await evaluate(cdp, sessionId, `(() => ({ roles: document.querySelectorAll('[data-legend-kind][role]').length, runtime: document.querySelectorAll('[data-legend-bridge-runtime]').length, kinds: Array.from(document.querySelectorAll('[data-legend-semantic-kind]')).map(function (entry) { return entry.getAttribute('data-legend-semantic-kind'); }) }))()`); assert.deepEqual(embed, { roles: 0, runtime: 0, kinds: ['frontend', 'database', 'external'] }); await navigateReady(outputs.hidden, '!!(window.Archify && Archify.semanticLens)', 'hidden legend'); const hidden = await evaluate(cdp, sessionId, `(() => ({ root: !!document.querySelector('[data-legend]'), bridge: !!document.querySelector('[data-legend-bridge]'), title: Array.from(document.querySelectorAll('.diagram-container svg text')).some(function (text) { return text.textContent.trim() === 'Legend'; }) }))()`); assert.deepEqual(hidden, { root: false, bridge: false, title: false }); console.log('ok legend runtime: labels, counts, keyboard, export, print, embed, hidden, and dual themes'); } async function verifySemanticPassportDismissal(file) { await navigateReady(file, '!!(window.Archify && Archify.focus && document.querySelector("#btn-focus-clear"))', 'Semantic Passport dismissal'); const result = await evaluate(cdp, sessionId, `(() => { var chip = document.querySelector('#focus-chip'); var close = document.querySelector('#btn-focus-clear'); var container = document.querySelector('.diagram-container'); var svg = container.querySelector(':scope > svg'); var origin = svg.querySelector('[data-node-id="clients"]'); var neighbor = svg.querySelector('[data-node-id]:not([data-node-id="clients"])'); if (!origin || !neighbor) return { ok: false, error: 'missing smoke-test nodes' }; function state() { return { hidden: chip.hidden, active: Archify.focus.active() }; } Archify.focus.set('clients', { toggle: false, updateUrl: false }); var cardRect = chip.getBoundingClientRect(); var closeRect = close.getBoundingClientRect(); var layout = { topGap: Math.round(closeRect.top - cardRect.top), rightGap: Math.round(cardRect.right - closeRect.right), label: close.getAttribute('aria-label'), title: close.getAttribute('title'), text: close.textContent.trim() }; close.click(); var afterClose = state(); var restoredFocus = document.activeElement && document.activeElement.getAttribute('data-node-id'); Archify.focus.set('clients', { toggle: false, updateUrl: false }); chip.querySelector('.relationship-lens-head').dispatchEvent(new MouseEvent('click', { bubbles: true })); var afterInside = state(); container.dispatchEvent(new MouseEvent('click', { bubbles: true })); var afterOutside = state(); Archify.focus.set('clients', { toggle: false, updateUrl: false }); neighbor.dispatchEvent(new MouseEvent('click', { bubbles: true })); var afterNode = state(); return { ok: true, layout: layout, afterClose: afterClose, restoredFocus: restoredFocus, afterInside: afterInside, afterOutside: afterOutside, afterNode: afterNode, neighborId: neighbor.getAttribute('data-node-id') }; })()`); assert.equal(result?.ok, true, result?.error || 'Semantic Passport smoke failed'); assert.equal(result.layout.label, 'Close semantic passport'); assert.equal(result.layout.title, 'Close'); assert.equal(result.layout.text, '×'); assert.ok(result.layout.topGap >= 0 && result.layout.topGap <= 16, `Semantic Passport close top gap ${result.layout.topGap}px`); assert.ok(result.layout.rightGap >= 0 && result.layout.rightGap <= 16, `Semantic Passport close right gap ${result.layout.rightGap}px`); assert.deepEqual(result.afterClose, { hidden: true, active: null }); assert.equal(result.restoredFocus, 'clients'); assert.deepEqual(result.afterInside, { hidden: false, active: 'clients' }); assert.deepEqual(result.afterOutside, { hidden: true, active: null }); assert.deepEqual(result.afterNode, { hidden: false, active: result.neighborId }); console.log('ok Semantic Passport: close control, focus return, inside preservation, and outside dismissal'); } async function verifyArchitectureDeltaNavigator(file) { await cdp.send('Emulation.setEmulatedMedia', { media: 'screen', features: [{ name: 'prefers-reduced-motion', value: 'no-preference' }], }, sessionId); const readyCondition = '!!document.querySelector("#review-play") && !document.querySelector("#review-play").disabled'; async function waitForSelected(changeKey, label) { for (let attempt = 0; attempt < 80; attempt += 1) { if (await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey === ${JSON.stringify(changeKey)}`)) return; await delay(50); } const observed = await evaluate(cdp, sessionId, `({ selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey || null, pressed: document.querySelector('#review-play')?.getAttribute('aria-pressed'), label: document.querySelector('#review-play')?.textContent, status: document.querySelector('#review-status')?.textContent, reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches, hidden: document.hidden })`); assert.fail(`${label} did not select ${changeKey} within the bounded wait: ${JSON.stringify(observed)}`); } async function waitForReviewFinished(label) { for (let attempt = 0; attempt < 400; attempt += 1) { const done = await evaluate(cdp, sessionId, `document.querySelector('#review-play').getAttribute('aria-pressed') === 'false' && document.querySelector('#review-play').textContent === 'Replay'`); if (done) return; await delay(50); } assert.fail(`${label} did not finish within the bounded wait`); } await navigateReady(file, readyCondition, 'architecture-delta navigator'); const initial = await evaluate(cdp, sessionId, `({ status: document.querySelector('#review-status').textContent, selected: document.querySelectorAll('.change-row[aria-current="step"]').length, current: document.querySelectorAll('[data-delta-review-current]').length, rows: document.querySelectorAll('.change-row').length })`); assert.deepEqual(initial, { status: 'Overview · 11 authored changes', selected: 0, current: 0, rows: 11 }); await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`); await waitForSelected('relationship:fraud-check', 'architecture-delta initial playback'); const advanced = await evaluate(cdp, sessionId, `({ selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey, pressed: document.querySelector('#review-play').getAttribute('aria-pressed') })`); assert.deepEqual(advanced, { selected: 'relationship:fraud-check', pressed: 'true' }); await evaluate(cdp, sessionId, `document.querySelector('[role="tab"][data-target="base"]').click()`); const pausedKey = await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey`); await delay(1550); assert.equal(await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey`), pausedKey); assert.equal(await evaluate(cdp, sessionId, `document.querySelector('#review-play').getAttribute('aria-pressed')`), 'false'); const overview = await evaluate(cdp, sessionId, `(() => { document.querySelector('[role="tab"][data-target="delta"]').click(); document.querySelector('#review-overview').click(); return { active: document.querySelector('[data-view="delta"]').hasAttribute('data-delta-review-active'), current: document.querySelectorAll('[data-delta-review-current]').length, selected: document.querySelectorAll('.change-row[aria-current="step"]').length }; })()`); assert.deepEqual(overview, { active: false, current: 0, selected: 0 }); await navigateReady(file, readyCondition, 'architecture-delta manual lifecycle navigator'); await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`); await waitForSelected('relationship:fraud-check', 'architecture-delta previous control'); const previousPause = await evaluate(cdp, sessionId, `(() => { document.querySelector('#review-previous').click(); return { selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey, pressed: document.querySelector('#review-play').getAttribute('aria-pressed') }; })()`); assert.deepEqual(previousPause, { selected: 'component:fraud', pressed: 'false' }); await delay(1450); assert.equal(await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey`), 'component:fraud'); await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`); const nextPause = await evaluate(cdp, sessionId, `(() => { document.querySelector('#review-next').click(); return { selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey, pressed: document.querySelector('#review-play').getAttribute('aria-pressed') }; })()`); assert.deepEqual(nextPause, { selected: 'relationship:fraud-check', pressed: 'false' }); await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`); const rowPause = await evaluate(cdp, sessionId, `(() => { document.querySelector('.change-row[data-change-key="component:queue"]').click(); return { selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey, pressed: document.querySelector('#review-play').getAttribute('aria-pressed') }; })()`); assert.deepEqual(rowPause, { selected: 'component:queue', pressed: 'false' }); await navigateReady(file, readyCondition, 'architecture-delta focus lifecycle navigator'); const focusedPause = await evaluate(cdp, sessionId, `(() => { document.querySelector('#review-play').click(); document.querySelector('#review-next').focus(); return { selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey, pressed: document.querySelector('#review-play').getAttribute('aria-pressed') }; })()`); assert.deepEqual(focusedPause, { selected: 'component:fraud', pressed: 'false' }); await delay(1450); assert.equal(await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey`), 'component:fraud'); await navigateReady(file, readyCondition, 'architecture-delta hidden lifecycle navigator'); const hiddenPause = await evaluate(cdp, sessionId, `(() => { document.querySelector('#review-play').click(); Object.defineProperty(document, 'hidden', { configurable: true, get: function () { return true; } }); document.dispatchEvent(new Event('visibilitychange')); return { hidden: document.hidden, selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey, pressed: document.querySelector('#review-play').getAttribute('aria-pressed') }; })()`); assert.deepEqual(hiddenPause, { hidden: true, selected: 'component:fraud', pressed: 'false' }); await delay(1450); assert.equal(await evaluate(cdp, sessionId, `document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey`), 'component:fraud'); await navigateReady(file, readyCondition, 'architecture-delta print lifecycle navigator'); const printPause = await evaluate(cdp, sessionId, `(() => { document.querySelector('#review-play').click(); window.dispatchEvent(new Event('beforeprint')); return { status: document.querySelector('#review-status').textContent, selected: document.querySelectorAll('.change-row[aria-current="step"]').length, pressed: document.querySelector('#review-play').getAttribute('aria-pressed') }; })()`); assert.deepEqual(printPause, { status: 'Overview · 11 authored changes', selected: 0, pressed: 'false' }); await delay(1450); assert.equal(await evaluate(cdp, sessionId, `document.querySelectorAll('.change-row[aria-current="step"]').length`), 0); const keyboard = await evaluate(cdp, sessionId, `(() => { document.querySelector('details').open = true; var first = document.querySelector('.change-row'); first.click(); first.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })); var focused = document.activeElement.dataset.changeKey; document.activeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); return { focused: focused, selected: document.querySelector('.change-row[aria-current="step"]').dataset.changeKey }; })()`); assert.deepEqual(keyboard, { focused: 'relationship:fraud-check', selected: 'relationship:fraud-check' }); await cdp.send('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'reduce' }] }, sessionId); await navigateReady(file, readyCondition, 'architecture-delta reduced-motion navigator'); await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`); await delay(1550); const reduced = await evaluate(cdp, sessionId, `({ selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey, pressed: document.querySelector('#review-play').getAttribute('aria-pressed'), nextDisabled: document.querySelector('#review-next').disabled })`); assert.deepEqual(reduced, { selected: 'component:fraud', pressed: 'false', nextDisabled: false }); await evaluate(cdp, sessionId, `document.querySelector('#review-next').click()`); await evaluate(cdp, sessionId, `document.querySelector('.change-row').click()`); await cdp.send('Emulation.setEmulatedMedia', { media: 'print' }, sessionId); await delay(200); const printState = await evaluate(cdp, sessionId, `({ strip: getComputedStyle(document.querySelector('.review-strip')).display, base: getComputedStyle(document.querySelector('[data-view="base"]')).display, delta: getComputedStyle(document.querySelector('[data-view="delta"]')).display, head: getComputedStyle(document.querySelector('[data-view="head"]')).display, current: getComputedStyle(document.querySelector('[data-delta-review-current]')).opacity, same: getComputedStyle(document.querySelector('[data-view="delta"] [data-delta-state="same"]')).opacity })`); assert.deepEqual(printState, { strip: 'none', base: 'none', delta: 'block', head: 'none', current: '1', same: '1' }); await cdp.send('Emulation.setEmulatedMedia', { media: 'screen', features: [{ name: 'prefers-reduced-motion', value: 'no-preference' }], }, sessionId); await navigateReady(file, readyCondition, 'architecture-delta tamper navigator'); const tampered = await evaluate(cdp, sessionId, `(() => { var companion = document.querySelector('[data-view="delta"] g[data-edge-id="fraud-check"]'); companion.parentNode.insertBefore(companion.cloneNode(true), companion.nextSibling); document.querySelector('.change-row[data-change-key="relationship:fraud-check"]').click(); return { status: document.querySelector('#review-status').textContent, disabled: Array.from(document.querySelectorAll('.review-step,.change-row')).every(function (button) { return button.disabled; }), canvases: document.querySelectorAll('[data-view]').length }; })()`); assert.deepEqual(tampered, { status: 'Review unavailable · compare identity mismatch', disabled: true, canvases: 3 }); await navigateReady(file, readyCondition, 'architecture-delta finite navigator'); await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`); await waitForReviewFinished('architecture-delta finite playback'); const finished = await evaluate(cdp, sessionId, `({ selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey, label: document.querySelector('#review-play').textContent, pressed: document.querySelector('#review-play').getAttribute('aria-pressed') })`); assert.deepEqual(finished, { selected: 'relationship:publish-order', label: 'Replay', pressed: 'false' }); await evaluate(cdp, sessionId, `document.querySelector('#review-play').click()`); const replayStarted = await evaluate(cdp, sessionId, `({ selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey, label: document.querySelector('#review-play').textContent, pressed: document.querySelector('#review-play').getAttribute('aria-pressed') })`); assert.deepEqual(replayStarted, { selected: 'component:fraud', label: 'Pause', pressed: 'true' }); await waitForReviewFinished('architecture-delta replay playback'); const replayFinished = await evaluate(cdp, sessionId, `({ selected: document.querySelector('.change-row[aria-current="step"]')?.dataset.changeKey, label: document.querySelector('#review-play').textContent, pressed: document.querySelector('#review-play').getAttribute('aria-pressed') })`); assert.deepEqual(replayFinished, { selected: 'relationship:publish-order', label: 'Replay', pressed: 'false' }); const exportProof = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () { var frames = Array.from(document.querySelectorAll('.snapshot-frame')); var explorers = frames.map(function (frame) { var child = frame.contentWindow; return Boolean(child && child.Archify && child.Archify.focus && child.Archify.routeProbe && child.document.querySelector('#btn-node-finder') && child.document.querySelector('#guided-view-play')); }); var svgA = Archify.deltaExport.canonicalSvg(); document.querySelector('#theme').click(); document.querySelector('#preset').click(); document.querySelector('.change-row').click(); var svgB = Archify.deltaExport.canonicalSvg(); var parsed = new DOMParser().parseFromString(svgB, 'image/svg+xml'); var exportStyle = parsed.querySelector('style')?.textContent || ''; var blob = await Archify.deltaExport.shareCard(); var bytes = new Uint8Array(await blob.arrayBuffer()); return { explorers: explorers, stable: svgA === svgB, reviewResidue: parsed.querySelectorAll('[data-delta-review-current]').length, boundaryStyle: exportStyle.includes('text[data-delta-boundary-state="added"]{fill:#34d399!important}'), markerStyle: exportStyle.includes('.delta-edge-marker[data-delta-state],.delta-boundary-marker[data-delta-state]{color:var(--delta)}'), frameStyle: exportStyle.includes('rect[data-graph-role="structural-frame"]'), boundaryMarkers: Array.from(parsed.querySelectorAll('.delta-boundary-marker')).map(function (marker) { return marker.textContent; }), type: blob.type, size: blob.size, signature: Array.from(bytes.slice(0, 8)).map(function (byte) { return byte.toString(16).padStart(2, '0'); }).join('') }; })()`, true), 15_000, 'Architecture Delta export'); assert.deepEqual(exportProof.explorers, [true, true]); assert.equal(exportProof.stable, true); assert.equal(exportProof.reviewResidue, 0); assert.equal(exportProof.boundaryStyle, true); assert.equal(exportProof.markerStyle, true); assert.equal(exportProof.frameStyle, true); assert.deepEqual(exportProof.boundaryMarkers, ['~', '~']); assert.equal(exportProof.type, 'image/png'); assert.ok(exportProof.size > 20_000, `Architecture Delta Share Card is unexpectedly small (${exportProof.size} bytes)`); assert.equal(exportProof.signature, '89504e470d0a1a0a'); console.log(`ok Architecture Delta navigator + export: exact identity, complete explorers, static SVG, and ${exportProof.size}-byte Share Card`); } async function captureShareCard(file, label) { await navigateReady(file, '!!(window.Archify && Archify.exportMenu && Archify.exportMenu.shareCard)', label); const sharePayload = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () { try { var blob = await Archify.exportMenu.shareCard(); var bytes = new Uint8Array(await blob.arrayBuffer()); var binary = ''; for (var offset = 0; offset < bytes.length; offset += 32768) { binary += String.fromCharCode.apply(null, bytes.subarray(offset, offset + 32768)); } return { ok: true, type: blob.type, size: blob.size, base64: btoa(binary) }; } catch (error) { return { ok: false, error: String(error && error.message || error) }; } })()`, true), 10_000, `${label} Share Card export`); assert.equal(sharePayload?.ok, true, sharePayload?.error || `${label} Share Card export failed`); assert.equal(sharePayload.type, 'image/png'); assert.ok(sharePayload.size > 20_000, `${label} Share Card is unexpectedly small (${sharePayload.size} bytes)`); const png = Buffer.from(sharePayload.base64, 'base64'); assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a', `${label} output is not a PNG`); assert.equal(png.readUInt32BE(16), 1200, `${label} Share Card width`); assert.equal(png.readUInt32BE(20), 630, `${label} Share Card height`); const pngPath = path.join(tmp, `${label}.share-card.png`); fs.writeFileSync(pngPath, png); const pixels = execFileSync(ffmpeg, [ '-v', 'error', '-i', pngPath, '-vf', 'scale=120:63', '-frames:v', '1', '-f', 'rawvideo', '-pix_fmt', 'rgb24', '-', ], { maxBuffer: 4 * 1024 * 1024 }); const colors = new Set(); const counts = new Map(); for (let offset = 0; offset < pixels.length; offset += 3) { const color = pixels.subarray(offset, offset + 3).toString('hex'); colors.add(color); counts.set(color, (counts.get(color) || 0) + 1); } const largestColorShare = Math.max(...counts.values()) / (pixels.length / 3); assert.ok(colors.size >= 24, `${label} Share Card has only ${colors.size} sampled colors`); assert.ok(largestColorShare < 0.96, `${label} Share Card is visually near-blank (${Math.round(largestColorShare * 100)}% one color)`); console.log(`ok ${label} Share Card: ${sharePayload.size} bytes, 1200x630, ${colors.size} sampled colors`); } async function captureCopiedShareCard(file, label) { await navigateReady(file, '!!(window.Archify && Archify.exportMenu && Archify.exportMenu.copyShareCard)', label); const copiedPayload = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () { try { Object.defineProperty(window, 'ClipboardItem', { configurable: true, value: function ClipboardItem(items) { this.items = items; } }); Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { write: async function (items) { window.__archifyCopiedShareCard = await Promise.resolve(items[0].items['image/png']); } } }); window.alert = function (message) { window.__archifyCopyAlert = message; }; await Archify.exportMenu.copyShareCard(); var blob = window.__archifyCopiedShareCard; if (!blob) throw new Error(window.__archifyCopyAlert || 'clipboard received no blob'); var bytes = new Uint8Array(await blob.arrayBuffer()); var binary = ''; for (var offset = 0; offset < bytes.length; offset += 32768) { binary += String.fromCharCode.apply(null, bytes.subarray(offset, offset + 32768)); } return { ok: true, type: blob.type, size: blob.size, base64: btoa(binary), receipt: { format: document.documentElement.getAttribute('data-last-export-format'), width: document.documentElement.getAttribute('data-last-export-width'), height: document.documentElement.getAttribute('data-last-export-height'), canonical: document.documentElement.getAttribute('data-last-export-canonical'), error: document.documentElement.getAttribute('data-last-export-error') } }; } catch (error) { return { ok: false, error: String(error && error.message || error) }; } })()`, true), 10_000, `${label} Copy Share Card`); assert.equal(copiedPayload?.ok, true, copiedPayload?.error || `${label} Copy Share Card failed`); assert.equal(copiedPayload.type, 'image/png'); assert.ok(copiedPayload.size > 20_000, `${label} copied Share Card is unexpectedly small`); const png = Buffer.from(copiedPayload.base64, 'base64'); assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a', `${label} copied output is not a PNG`); assert.equal(png.readUInt32BE(16), 1200, `${label} copied Share Card width`); assert.equal(png.readUInt32BE(20), 630, `${label} copied Share Card height`); assert.deepEqual(copiedPayload.receipt, { format: 'share-card', width: '1200', height: '630', canonical: 'true', error: null, }); console.log(`ok ${label} Copy Share Card: ${copiedPayload.size} bytes, image/png, truthful receipt`); } async function captureRouteShareCard(file, label, sourceId, targetId, options = {}) { await navigateReady(file, '!!(window.Archify && Archify.routeProbe && Archify.exportMenu && Archify.exportMenu.downloadRouteShareCard)', label); const routePayload = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () { try { window.alert = function (message) { window.__archifyRouteAlert = message; }; Archify.routeProbe.begin({ source: ${JSON.stringify(sourceId)}, focusNode: false }); if (!Archify.routeProbe.choose(${JSON.stringify(targetId)}, { updateUrl: false })) { throw new Error('route did not resolve'); } var snapshot = Archify.routeProbe.exportSnapshot(); if (!snapshot) throw new Error('resolved route exposed no export snapshot'); Archify.exportMenu.syncRouteShare(); var routeMenuItem = document.querySelector('[data-action="route-share-card"]'); var menuResolved = !!routeMenuItem && !routeMenuItem.hidden && !routeMenuItem.disabled; var svg = document.querySelector('.diagram-container svg'); var firstEdgeKey = snapshot.edges[0].key; var primaryCarrier = Array.from(svg.querySelectorAll('[data-edge-key]')).find(function (element) { return element.getAttribute('data-edge-key') === firstEdgeKey && hasDrawableGeometry(element); }); if (!primaryCarrier) throw new Error('route exposed no primary geometry carrier'); var duplicateCarrier = primaryCarrier.cloneNode(true); var duplicateGeometry = /^(path|line|polyline)$/i.test(duplicateCarrier.tagName) ? duplicateCarrier : duplicateCarrier.querySelector('path, line, polyline'); if (duplicateGeometry.tagName.toLowerCase() === 'path') { duplicateGeometry.setAttribute('d', duplicateGeometry.getAttribute('d') + ' M 0 0 L 1 1'); } svg.appendChild(duplicateCarrier); var duplicateGeometryRejected = Archify.routeProbe.exportSnapshot() === null; var duplicateExportError = ''; try { await Archify.exportMenu.shareCard({ variant: 'route' }); } catch (error) { duplicateExportError = String(error && error.message || error); } duplicateCarrier.remove(); var primaryGeometry = /^(path|line|polyline)$/i.test(primaryCarrier.tagName) ? primaryCarrier : primaryCarrier.querySelector('path, line, polyline'); var geometryAttribute = primaryGeometry.tagName.toLowerCase() === 'path' ? 'd' : primaryGeometry.tagName.toLowerCase() === 'polyline' ? 'points' : 'x2'; var originalGeometry = primaryGeometry.getAttribute(geometryAttribute); primaryGeometry.setAttribute(geometryAttribute, ''); var emptyGeometryRejected = Archify.routeProbe.exportSnapshot() === null; var emptyGeometryExportError = ''; try { await Archify.exportMenu.shareCard({ variant: 'route' }); } catch (error) { emptyGeometryExportError = String(error && error.message || error); } primaryGeometry.setAttribute(geometryAttribute, originalGeometry); await new Promise(function (resolve) { requestAnimationFrame(function () { requestAnimationFrame(resolve); }); }); function stableLiveSnapshot() { var clone = svg.cloneNode(true); clone.style.removeProperty('transform'); clone.style.removeProperty('clip-path'); clone.removeAttribute('data-view-scale'); Array.from(clone.querySelectorAll('[data-legend-bridge-runtime]')).forEach(function (element) { element.remove(); }); return clone.outerHTML; } var liveBefore = stableLiveSnapshot(); var captured = []; var downloads = []; var originalCreateObjectURL = URL.createObjectURL.bind(URL); var originalAnchorClick = HTMLAnchorElement.prototype.click; var originalFillText = CanvasRenderingContext2D.prototype.fillText; var headerMetrics = []; URL.createObjectURL = function (blob) { if (blob && blob.type && blob.type.indexOf('image/svg+xml') === 0) { captured.push(blob.text()); } return originalCreateObjectURL(blob); }; HTMLAnchorElement.prototype.click = function () { downloads.push(this.download); }; CanvasRenderingContext2D.prototype.fillText = function (text, x, y) { if (headerMetrics.length < 2 && (y === 62 || y === 87)) { headerMetrics.push({ y: y, text: String(text), width: this.measureText(String(text)).width, maxWidth: y === 62 ? 798 : 848 }); } return originalFillText.apply(this, arguments); }; var blob; var canonicalBlob; var routeReceipt; var ordinaryReceipt; var routeFingerprints = []; try { blob = await Archify.exportMenu.downloadRouteShareCard(); routeReceipt = { format: document.documentElement.getAttribute('data-last-export-format'), variant: document.documentElement.getAttribute('data-last-export-variant'), width: document.documentElement.getAttribute('data-last-export-width'), height: document.documentElement.getAttribute('data-last-export-height'), canonical: document.documentElement.getAttribute('data-last-export-canonical'), routeStateClean: document.documentElement.getAttribute('data-last-export-route-state-clean'), error: document.documentElement.getAttribute('data-last-export-error') }; var routeSvgText = captured[0] ? await captured[0] : ''; function fingerprint(text) { var hash = 2166136261; for (var index = 0; index < text.length; index++) { hash ^= text.charCodeAt(index); hash = Math.imul(hash, 16777619); } return (hash >>> 0).toString(16); } routeFingerprints.push(fingerprint(routeSvgText)); if (${JSON.stringify(options.journeyInvariance === true)}) { async function captureJourneySource(action) { action(); var captureIndex = captured.length; await Archify.exportMenu.shareCard({ variant: 'route' }); routeFingerprints.push(fingerprint(await captured[captureIndex])); } await captureJourneySource(function () { Archify.routeProbe.selectJourneyIndex(0); }); await captureJourneySource(function () { Archify.routeProbe.selectJourneyIndex(Math.floor(snapshot.nodeIds.length / 2)); }); await captureJourneySource(function () { Archify.routeProbe.selectJourneyIndex(snapshot.nodeIds.length - 1); }); Archify.routeProbe.showOverview({ reveal: false }); var started = Archify.routeProbe.playJourney(); if (!started) throw new Error('Route Journey could not enter playing state for invariance check'); await captureJourneySource(function () {}); Archify.routeProbe.pauseJourney({ preserveElapsed: false }); await captureJourneySource(function () {}); document.documentElement.setAttribute('data-motion', 'still'); await captureJourneySource(function () {}); document.documentElement.setAttribute('data-motion', 'live'); Archify.routeProbe.showOverview({ reveal: false }); } var toBlobError = ''; var originalToBlob = HTMLCanvasElement.prototype.toBlob; HTMLCanvasElement.prototype.toBlob = function (callback) { callback(null); }; try { await Archify.exportMenu.shareCard({ variant: 'route' }); } catch (error) { toBlobError = String(error && error.message || error); } finally { HTMLCanvasElement.prototype.toBlob = originalToBlob; } var missingToBlobError = ''; HTMLCanvasElement.prototype.toBlob = undefined; try { await Archify.exportMenu.shareCard({ variant: 'route' }); } catch (error) { missingToBlobError = String(error && error.message || error); } finally { HTMLCanvasElement.prototype.toBlob = originalToBlob; } var missingContextError = ''; var originalGetContext = HTMLCanvasElement.prototype.getContext; HTMLCanvasElement.prototype.getContext = function () { return null; }; try { await Archify.exportMenu.shareCard({ variant: 'route' }); } catch (error) { missingContextError = String(error && error.message || error); } finally { HTMLCanvasElement.prototype.getContext = originalGetContext; } var imageDecodeError = ''; var OriginalImage = window.Image; function FailingImage() {} Object.defineProperty(FailingImage.prototype, 'src', { set: function () { var instance = this; queueMicrotask(function () { if (typeof instance.onerror === 'function') instance.onerror(new Event('error')); }); } }); window.Image = FailingImage; try { await Archify.exportMenu.shareCard({ variant: 'route' }); } catch (error) { imageDecodeError = String(error && error.message || error); } finally { window.Image = OriginalImage; } var unknownVariantError = ''; try { await Archify.exportMenu.shareCard({ variant: 'unknown' }); } catch (error) { unknownVariantError = String(error && error.message || error); } var canonicalIndex = captured.length; canonicalBlob = await Archify.exportMenu.shareCard(); var canonicalSvgText = captured[canonicalIndex] ? await captured[canonicalIndex] : ''; await Archify.exportMenu.run('share-card'); ordinaryReceipt = { format: document.documentElement.getAttribute('data-last-export-format'), variant: document.documentElement.getAttribute('data-last-export-variant'), width: document.documentElement.getAttribute('data-last-export-width'), height: document.documentElement.getAttribute('data-last-export-height'), canonical: document.documentElement.getAttribute('data-last-export-canonical'), routeStateClean: document.documentElement.getAttribute('data-last-export-route-state-clean'), error: document.documentElement.getAttribute('data-last-export-error') }; var svgDownloadIndex = captured.length; await Archify.exportMenu.run('svg'); var exportedSvgText = captured[svgDownloadIndex] ? await captured[svgDownloadIndex] : ''; var svgReceipt = { format: document.documentElement.getAttribute('data-last-export-format'), variant: document.documentElement.getAttribute('data-last-export-variant'), canonical: document.documentElement.getAttribute('data-last-export-canonical') }; await Archify.exportMenu.run('png'); var pngReceipt = { format: document.documentElement.getAttribute('data-last-export-format'), variant: document.documentElement.getAttribute('data-last-export-variant'), canonical: document.documentElement.getAttribute('data-last-export-canonical') }; var parser = new DOMParser(); var routeSvg = parser.parseFromString(routeSvgText, 'image/svg+xml').documentElement; var canonicalSvg = parser.parseFromString(canonicalSvgText, 'image/svg+xml').documentElement; var exportedSvg = parser.parseFromString(exportedSvgText, 'image/svg+xml').documentElement; var matchedNodeIds = Array.from(routeSvg.querySelectorAll('[data-node-id][data-share-route-match]')).map(function (node) { return { id: node.getAttribute('data-node-id'), step: Number(node.getAttribute('data-share-route-step')) }; }).sort(function (a, b) { return a.step - b.step; }).map(function (item) { return item.id; }); var edgeSteps = new Map(); Array.from(routeSvg.querySelectorAll('[data-edge-key][data-share-route-match]')).forEach(function (edge) { edgeSteps.set(edge.getAttribute('data-edge-key'), Number(edge.getAttribute('data-share-route-step'))); }); var matchedEdgeKeys = Array.from(edgeSteps).sort(function (a, b) { return a[1] - b[1]; }).map(function (entry) { return entry[0]; }); var expectedEdgeKeys = snapshot.edges.map(function (edge) { return edge.key; }); var routeNodeIds = Array.from(routeSvg.querySelectorAll('[data-node-id]')).map(function (node) { return node.getAttribute('data-node-id'); }).sort(); var liveNodeIds = Array.from(svg.querySelectorAll('[data-node-id]')).map(function (node) { return node.getAttribute('data-node-id'); }).sort(); var routeEdgeKeys = Array.from(new Set(Array.from(routeSvg.querySelectorAll('[data-edge-key]')).map(function (edge) { return edge.getAttribute('data-edge-key'); }))).sort(); var liveEdgeKeys = Array.from(new Set(Array.from(svg.querySelectorAll('[data-edge-key]')).map(function (edge) { return edge.getAttribute('data-edge-key'); }))).sort(); var bytes = new Uint8Array(await blob.arrayBuffer()); var binary = ''; for (var offset = 0; offset < bytes.length; offset += 32768) { binary += String.fromCharCode.apply(null, bytes.subarray(offset, offset + 32768)); } await new Promise(function (resolve) { requestAnimationFrame(function () { requestAnimationFrame(resolve); }); }); var liveAfter = stableLiveSnapshot(); var liveUnchanged = liveBefore === liveAfter; var liveDiff = ''; if (!liveUnchanged) { var diffIndex = 0; while (diffIndex < liveBefore.length && diffIndex < liveAfter.length && liveBefore[diffIndex] === liveAfter[diffIndex]) diffIndex++; liveDiff = 'at ' + diffIndex + ': before=' + liveBefore.slice(Math.max(0, diffIndex - 80), diffIndex + 160) + ' after=' + liveAfter.slice(Math.max(0, diffIndex - 80), diffIndex + 160); } var asyncClearIndex = captured.length; var asyncClearPromise = Archify.exportMenu.shareCard({ variant: 'route' }); Archify.routeProbe.clear({ updateUrl: false, preserveView: true }); var asyncClearBlob = await asyncClearPromise; var asyncClearFingerprint = fingerprint(await captured[asyncClearIndex]); var hiddenAfterClear = routeMenuItem.hidden && routeMenuItem.disabled && getComputedStyle(routeMenuItem).display === 'none'; var staleError = ''; try { await Archify.exportMenu.shareCard({ variant: 'route' }); } catch (error) { staleError = String(error && error.message || error); } await Archify.exportMenu.downloadRouteShareCard(); var failedReceipt = { format: document.documentElement.getAttribute('data-last-export-format'), variant: document.documentElement.getAttribute('data-last-export-variant'), errorFormat: document.documentElement.getAttribute('data-last-export-error-format'), error: document.documentElement.getAttribute('data-last-export-error') }; return { ok: true, type: blob.type, size: blob.size, base64: btoa(binary), snapshot: snapshot, matchedNodeIds: matchedNodeIds, matchedEdgeKeys: matchedEdgeKeys, expectedEdgeKeys: expectedEdgeKeys, routeShare: routeSvg.hasAttribute('data-share-route'), routeLiveResidue: routeSvg.querySelectorAll('[data-route-match], [data-route-step], [data-route-start], [data-route-end], [data-route-journey-state], [data-route-journey-current], [data-route-journey-overlay]').length, routeMotionResidue: (routeSvg.hasAttribute('data-animation') ? 1 : 0) + routeSvg.querySelectorAll('[data-animate]').length, routeNodeIds: routeNodeIds, liveNodeIds: liveNodeIds, routeEdgeKeys: routeEdgeKeys, liveEdgeKeys: liveEdgeKeys, canonicalRouteResidue: canonicalSvg.querySelectorAll('[data-route-match], [data-route-step], [data-route-start], [data-route-end], [data-share-route-match], [data-share-route-step], [data-share-route-start], [data-share-route-end], [data-share-route-middle]').length, canonicalRouteActive: canonicalSvg.hasAttribute('data-route-active') || canonicalSvg.hasAttribute('data-share-route'), canonicalSize: canonicalBlob.size, liveUnchanged: liveUnchanged, liveDiff: liveDiff, menuResolved: menuResolved, duplicateGeometryRejected: duplicateGeometryRejected, duplicateExportError: duplicateExportError, emptyGeometryRejected: emptyGeometryRejected, emptyGeometryExportError: emptyGeometryExportError, toBlobError: toBlobError, missingToBlobError: missingToBlobError, missingContextError: missingContextError, imageDecodeError: imageDecodeError, unknownVariantError: unknownVariantError, headerMetrics: headerMetrics, routeLabelExecuted: !!window.__routeLabelExecuted, svgReceipt: svgReceipt, pngReceipt: pngReceipt, exportedSvgRouteResidue: exportedSvg.hasAttribute('data-route-active') || exportedSvg.hasAttribute('data-share-route') || exportedSvg.querySelectorAll('[data-route-match], [data-route-step], [data-route-start], [data-route-end], [data-share-route-match], [data-share-route-step], [data-share-route-start], [data-share-route-end], [data-share-route-middle]').length > 0, exportedSvgDualTheme: /prefers-color-scheme:\s*light/.test(Array.from(exportedSvg.querySelectorAll('style')).map(function (style) { return style.textContent; }).join('\n')), asyncClearStable: asyncClearBlob && asyncClearBlob.type === 'image/png' && asyncClearFingerprint === routeFingerprints[0], hiddenAfterClear: hiddenAfterClear, staleSnapshot: Archify.routeProbe.exportSnapshot(), staleError: staleError, routeFingerprints: routeFingerprints, downloads: downloads, routeReceipt: routeReceipt, ordinaryReceipt: ordinaryReceipt, failedReceipt: failedReceipt }; } finally { URL.createObjectURL = originalCreateObjectURL; HTMLAnchorElement.prototype.click = originalAnchorClick; CanvasRenderingContext2D.prototype.fillText = originalFillText; } } catch (error) { return { ok: false, error: String(error && error.message || error) }; } })()`, true), 25_000, `${label} Route Card export`); assert.equal(routePayload?.ok, true, routePayload?.error || `${label} Route Card export failed`); assert.equal(routePayload.type, 'image/png'); assert.ok(routePayload.size > 20_000, `${label} Route Card is unexpectedly small (${routePayload.size} bytes)`); const png = Buffer.from(routePayload.base64, 'base64'); assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a', `${label} Route Card is not a PNG`); assert.equal(png.readUInt32BE(16), 1200, `${label} Route Card width`); assert.equal(png.readUInt32BE(20), 630, `${label} Route Card height`); const pngPath = path.join(tmp, `${label}.route-share-card.png`); fs.writeFileSync(pngPath, png); const sampledPixels = execFileSync(ffmpeg, [ '-v', 'error', '-i', pngPath, '-vf', 'scale=120:63', '-frames:v', '1', '-f', 'rawvideo', '-pix_fmt', 'rgb24', '-', ], { maxBuffer: 4 * 1024 * 1024 }); const sampledColors = new Set(); for (let offset = 0; offset < sampledPixels.length; offset += 3) { sampledColors.add(sampledPixels.subarray(offset, offset + 3).toString('hex')); } assert.ok(sampledColors.size >= 20, `${label} Route Card has only ${sampledColors.size} sampled colors`); assert.equal(routePayload.routeShare, true); assert.deepEqual(routePayload.matchedNodeIds, routePayload.snapshot.nodeIds); assert.deepEqual(routePayload.matchedEdgeKeys, routePayload.expectedEdgeKeys); assert.equal(routePayload.routeLiveResidue, 0); assert.equal(routePayload.routeMotionResidue, 0); assert.deepEqual(routePayload.routeNodeIds, routePayload.liveNodeIds); assert.deepEqual(routePayload.routeEdgeKeys, routePayload.liveEdgeKeys); assert.equal(routePayload.canonicalRouteResidue, 0); assert.equal(routePayload.canonicalRouteActive, false); assert.equal(routePayload.liveUnchanged, true, routePayload.liveDiff); assert.equal(routePayload.menuResolved, true); assert.equal(routePayload.duplicateGeometryRejected, true); assert.match(routePayload.duplicateExportError, /Trace a route before exporting a Route Share Card/); assert.equal(routePayload.emptyGeometryRejected, true); assert.match(routePayload.emptyGeometryExportError, /Trace a route before exporting a Route Share Card/); assert.match(routePayload.toBlobError, /canvas\.toBlob returned no data for Share Card/); assert.match(routePayload.missingToBlobError, /canvas\.toBlob unavailable for Share Card/); assert.match(routePayload.missingContextError, /2D canvas context unavailable for Share Card/); assert.ok(routePayload.imageDecodeError); assert.match(routePayload.unknownVariantError, /Unknown Share Card variant: unknown/); assert.equal(routePayload.routeLabelExecuted, false); assert.deepEqual(routePayload.headerMetrics.map((metric) => metric.y), [62, 87]); assert.ok(routePayload.headerMetrics.every((metric) => metric.width <= metric.maxWidth + 0.5), `${label} title/subtitle overflowed the Share Card header`); assert.deepEqual(routePayload.svgReceipt, { format: 'svg', variant: null, canonical: 'true' }); assert.deepEqual(routePayload.pngReceipt, { format: 'png', variant: null, canonical: 'true' }); assert.equal(routePayload.exportedSvgRouteResidue, false); assert.equal(routePayload.exportedSvgDualTheme, true); assert.equal(routePayload.asyncClearStable, true); assert.equal(routePayload.hiddenAfterClear, true); assert.equal(routePayload.staleSnapshot, null); assert.match(routePayload.staleError, /Trace a route before exporting a Route Share Card/); assert.ok(routePayload.routeFingerprints.every((fingerprint) => fingerprint === routePayload.routeFingerprints[0]), `${label} Route source changed across Journey state`); assert.match(routePayload.downloads[0], /-route-share-card\.png$/); assert.doesNotMatch(routePayload.downloads[0], /-route-.+-to-.+\.png$/); assert.deepEqual(routePayload.routeReceipt, { format: 'share-card', variant: 'route', width: '1200', height: '630', canonical: 'false', routeStateClean: 'true', error: null, }); assert.deepEqual(routePayload.ordinaryReceipt, { format: 'share-card', variant: null, width: '1200', height: '630', canonical: 'true', routeStateClean: null, error: null, }); assert.equal(routePayload.failedReceipt.format, null); assert.equal(routePayload.failedReceipt.variant, null); assert.equal(routePayload.failedReceipt.errorFormat, 'share-card'); assert.match(routePayload.failedReceipt.error, /Trace a route before exporting a Route Share Card/); if (options.expectedHops !== undefined) assert.equal(routePayload.snapshot.hops, options.expectedHops); if (options.expectedSourceLabel !== undefined) assert.equal(routePayload.snapshot.source.label, options.expectedSourceLabel); if (options.expectedTargetLabel !== undefined) assert.equal(routePayload.snapshot.target.label, options.expectedTargetLabel); console.log(`ok ${label} Route Card: ${routePayload.size} bytes, ${routePayload.snapshot.nodeIds.length} nodes, ${routePayload.snapshot.edges.length} exact hops`); } async function verifyDynamicReducedMotionRoute(file, label, sourceId, targetId) { await cdp.send('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'no-preference' }], }, sessionId); await navigateReady(file, '!!(window.Archify && Archify.routeProbe && Archify.exportMenu && Archify.motionGovernor)', label); async function sourceFingerprint() { return withTimeout(evaluate(cdp, sessionId, String.raw`(async function () { var originalCreateObjectURL = URL.createObjectURL.bind(URL); var sourcePromise = null; URL.createObjectURL = function (blob) { if (!sourcePromise && blob && blob.type && blob.type.indexOf('image/svg+xml') === 0) sourcePromise = blob.text(); return originalCreateObjectURL(blob); }; try { await Archify.exportMenu.shareCard({ variant: 'route' }); var source = await sourcePromise; var hash = 2166136261; for (var index = 0; index < source.length; index++) { hash ^= source.charCodeAt(index); hash = Math.imul(hash, 16777619); } return (hash >>> 0).toString(16); } finally { URL.createObjectURL = originalCreateObjectURL; } })()`, true), 10_000, `${label} source fingerprint`); } const setup = await evaluate(cdp, sessionId, `(function () { Archify.routeProbe.begin({ source: ${JSON.stringify(sourceId)}, focusNode: false }); if (!Archify.routeProbe.choose(${JSON.stringify(targetId)}, { updateUrl: false })) return { resolved: false }; Archify.routeProbe.showOverview({ reveal: false }); return { resolved: true, started: Archify.routeProbe.playJourney(), playing: Archify.routeProbe.isJourneyPlaying(), motion: document.documentElement.getAttribute('data-motion') }; })()`); assert.deepEqual(setup, { resolved: true, started: true, playing: true, motion: 'live' }); const before = await sourceFingerprint(); await cdp.send('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'reduce' }], }, sessionId); let reduced = null; for (let attempt = 0; attempt < 50; attempt += 1) { reduced = await evaluate(cdp, sessionId, `({ matches: matchMedia('(prefers-reduced-motion: reduce)').matches, motion: document.documentElement.getAttribute('data-motion'), playing: Archify.routeProbe.isJourneyPlaying() })`); if (reduced.matches && reduced.motion === 'still' && !reduced.playing) break; await delay(20); } assert.deepEqual(reduced, { matches: true, motion: 'still', playing: false }); const after = await sourceFingerprint(); assert.equal(after, before, `${label} source changed after dynamic reduced-motion paused Journey`); await cdp.send('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'no-preference' }], }, sessionId); console.log(`ok ${label}: dynamic reduced-motion paused Journey without changing Route Card source`); } async function captureRouteVisualMatrix(file, label, sourceId, targetId) { await navigateReady(file, '!!(window.Archify && Archify.preset && Archify.routeProbe && Archify.exportMenu)', label); const matrix = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () { Archify.routeProbe.begin({ source: ${JSON.stringify(sourceId)}, focusNode: false }); if (!Archify.routeProbe.choose(${JSON.stringify(targetId)}, { updateUrl: false })) { throw new Error('route did not resolve'); } var identity = JSON.stringify(Archify.routeProbe.exportSnapshot()); var results = []; for (var preset of ['classic', 'signal-flow', 'blueprint', 'editorial']) { if (!Archify.preset.apply(preset)) throw new Error('could not apply preset ' + preset); for (var theme of ['dark', 'light']) { document.documentElement.setAttribute('data-theme', theme); var blob = await Archify.exportMenu.shareCard({ variant: 'route' }); var bytes = new Uint8Array(await blob.arrayBuffer()); var hash = 2166136261; for (var index = 0; index < bytes.length; index++) { hash ^= bytes[index]; hash = Math.imul(hash, 16777619); } var view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); results.push({ preset: preset, theme: theme, type: blob.type, size: blob.size, width: view.getUint32(16), height: view.getUint32(20), hash: (hash >>> 0).toString(16), identity: JSON.stringify(Archify.routeProbe.exportSnapshot()) }); } } return { identity: identity, results: results }; })()`, true), 20_000, `${label} Route visual matrix`); assert.equal(matrix.results.length, 8); for (const result of matrix.results) { assert.equal(result.type, 'image/png', `${label} ${result.preset}/${result.theme} MIME`); assert.equal(result.width, 1200, `${label} ${result.preset}/${result.theme} width`); assert.equal(result.height, 630, `${label} ${result.preset}/${result.theme} height`); assert.ok(result.size > 20_000, `${label} ${result.preset}/${result.theme} is unexpectedly small`); assert.equal(result.identity, matrix.identity, `${label} ${result.preset}/${result.theme} changed route identity`); } assert.equal(new Set(matrix.results.map((result) => result.hash)).size, 8, `${label} presets/themes should produce eight distinct PNGs`); console.log(`ok ${label} Route visual matrix: Classic/Flow/Blueprint/Editorial x dark/light`); } async function captureReachShareCard(file, label, originId, direction, options = {}) { await navigateReady(file, '!!(window.Archify && Archify.focus && Archify.focus.reachabilitySnapshot && Archify.exportMenu && Archify.exportMenu.downloadReachShareCard)', label); const reachPayload = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () { try { window.alert = function (message) { window.__archifyReachAlert = String(message); }; if (!Archify.focus.set(${JSON.stringify(originId)}, { toggle: false, updateUrl: false })) { throw new Error('focus origin did not resolve'); } if (!Archify.focus.reach(${JSON.stringify(direction)}, { toggle: false, updateUrl: false, reveal: false })) { throw new Error('authored reach did not resolve'); } var snapshot = Archify.focus.reachabilitySnapshot(); if (!snapshot) throw new Error('active authored reach exposed no export snapshot'); Archify.exportMenu.syncReachShare(); var reachMenuItem = document.querySelector('[data-action="reach-share-card"]'); var menuResolved = !!reachMenuItem && !reachMenuItem.hidden && !reachMenuItem.disabled && getComputedStyle(reachMenuItem).display !== 'none'; var svg = document.querySelector('.diagram-container svg'); var firstEdge = snapshot.edges[0]; var primaryCarrier = Array.from(svg.querySelectorAll('[data-edge-key]')).find(function (element) { return element.getAttribute('data-edge-key') === firstEdge.key && hasDrawableGeometry(element); }); if (!primaryCarrier) throw new Error('reach exposed no primary geometry carrier'); var duplicateCarrier = primaryCarrier.cloneNode(true); var duplicateGeometry = /^(path|line|polyline)$/i.test(duplicateCarrier.tagName) ? duplicateCarrier : duplicateCarrier.querySelector('path, line, polyline'); if (duplicateGeometry.tagName.toLowerCase() === 'path') { duplicateGeometry.setAttribute('d', duplicateGeometry.getAttribute('d') + ' M 0 0 L 1 1'); } svg.appendChild(duplicateCarrier); var duplicateGeometryRejected = Archify.focus.reachabilitySnapshot() === null; var duplicateExportError = ''; try { await Archify.exportMenu.shareCard({ variant: 'reach' }); } catch (error) { duplicateExportError = String(error && error.message || error); } duplicateCarrier.remove(); snapshot = Archify.focus.reachabilitySnapshot(); if (!snapshot) throw new Error('reach snapshot did not recover after geometry restoration'); function stableLiveSnapshot() { var clone = svg.cloneNode(true); clone.style.removeProperty('transform'); clone.style.removeProperty('clip-path'); clone.removeAttribute('data-view-scale'); Array.from(clone.querySelectorAll('[data-legend-bridge-runtime]')).forEach(function (element) { element.remove(); }); return clone.outerHTML; } function fingerprintBytes(bytes) { var hash = 2166136261; for (var index = 0; index < bytes.length; index++) { hash ^= bytes[index]; hash = Math.imul(hash, 16777619); } return (hash >>> 0).toString(16); } var liveBefore = stableLiveSnapshot(); var captured = []; var downloads = []; var originalCreateObjectURL = URL.createObjectURL.bind(URL); var originalAnchorClick = HTMLAnchorElement.prototype.click; URL.createObjectURL = function (blob) { if (blob && blob.type && blob.type.indexOf('image/svg+xml') === 0) captured.push(blob.text()); return originalCreateObjectURL(blob); }; HTMLAnchorElement.prototype.click = function () { downloads.push(this.download); }; try { var blob = await Archify.exportMenu.shareCard({ variant: 'reach' }); var reachSvgText = captured[0] ? await captured[0] : ''; var downloadedBlob = await Archify.exportMenu.downloadReachShareCard(); var reachReceipt = { format: document.documentElement.getAttribute('data-last-export-format'), variant: document.documentElement.getAttribute('data-last-export-variant'), width: document.documentElement.getAttribute('data-last-export-width'), height: document.documentElement.getAttribute('data-last-export-height'), canonical: document.documentElement.getAttribute('data-last-export-canonical'), routeStateClean: document.documentElement.getAttribute('data-last-export-route-state-clean'), reachStateClean: document.documentElement.getAttribute('data-last-export-reach-state-clean'), error: document.documentElement.getAttribute('data-last-export-error') }; var parser = new DOMParser(); var reachSvg = parser.parseFromString(reachSvgText, 'image/svg+xml').documentElement; var matchedNodeIds = Array.from(reachSvg.querySelectorAll('[data-node-id][data-share-reach-match]')).map(function (node) { return node.getAttribute('data-node-id'); }); var matchedEdgeKeys = Array.from(new Set(Array.from(reachSvg.querySelectorAll('[data-edge-key][data-share-reach-match]')).map(function (edge) { return edge.getAttribute('data-edge-key'); }))); var reachNodeIds = Array.from(reachSvg.querySelectorAll('[data-node-id]')).map(function (node) { return node.getAttribute('data-node-id'); }).sort(); var liveNodeIds = Array.from(svg.querySelectorAll('[data-node-id]')).map(function (node) { return node.getAttribute('data-node-id'); }).sort(); var reachEdgeKeys = Array.from(new Set(Array.from(reachSvg.querySelectorAll('[data-edge-key]')).map(function (edge) { return edge.getAttribute('data-edge-key'); }))).sort(); var liveEdgeKeys = Array.from(new Set(Array.from(svg.querySelectorAll('[data-edge-key]')).map(function (edge) { return edge.getAttribute('data-edge-key'); }))).sort(); var reachStyles = Array.from(reachSvg.querySelectorAll('style')).map(function (style) { return style.textContent; }).join('\n'); await new Promise(function (resolve) { requestAnimationFrame(function () { requestAnimationFrame(resolve); }); }); var liveAfter = stableLiveSnapshot(); var matrix = []; if (${JSON.stringify(options.matrix === true)}) { var identity = JSON.stringify(snapshot); for (var preset of ['classic', 'signal-flow', 'blueprint', 'editorial']) { if (!Archify.preset.apply(preset)) throw new Error('could not apply preset ' + preset); for (var theme of ['dark', 'light']) { document.documentElement.setAttribute('data-theme', theme); var matrixBlob = await Archify.exportMenu.shareCard({ variant: 'reach' }); var matrixBytes = new Uint8Array(await matrixBlob.arrayBuffer()); var matrixView = new DataView(matrixBytes.buffer, matrixBytes.byteOffset, matrixBytes.byteLength); matrix.push({ preset: preset, theme: theme, type: matrixBlob.type, size: matrixBlob.size, width: matrixView.getUint32(16), height: matrixView.getUint32(20), hash: fingerprintBytes(matrixBytes), identity: JSON.stringify(Archify.focus.reachabilitySnapshot()) }); } } if (matrix.some(function (entry) { return entry.identity !== identity; })) { throw new Error('visual matrix changed authored reach identity'); } } Archify.focus.clearReach({ updateUrl: false }); Archify.exportMenu.syncReachShare(); var hiddenAfterClear = reachMenuItem.hidden && reachMenuItem.disabled && getComputedStyle(reachMenuItem).display === 'none'; var staleError = ''; try { await Archify.exportMenu.shareCard({ variant: 'reach' }); } catch (error) { staleError = String(error && error.message || error); } await Archify.exportMenu.downloadReachShareCard(); var failedReceipt = { format: document.documentElement.getAttribute('data-last-export-format'), variant: document.documentElement.getAttribute('data-last-export-variant'), errorFormat: document.documentElement.getAttribute('data-last-export-error-format'), error: document.documentElement.getAttribute('data-last-export-error') }; var canonicalIndex = captured.length; var canonicalBlob = await Archify.exportMenu.shareCard(); var canonicalSvgText = captured[canonicalIndex] ? await captured[canonicalIndex] : ''; var canonicalSvg = parser.parseFromString(canonicalSvgText, 'image/svg+xml').documentElement; var canonicalReachResidue = canonicalSvg.hasAttribute('data-share-reach') || canonicalSvg.hasAttribute('data-reach-active') || canonicalSvg.querySelectorAll('[data-share-reach-match], [data-share-reach-origin], [data-share-reach-depth], [data-reach-match], [data-reach-origin], [data-reach-depth]').length > 0; var bytes = new Uint8Array(await blob.arrayBuffer()); var binary = ''; for (var offset = 0; offset < bytes.length; offset += 32768) { binary += String.fromCharCode.apply(null, bytes.subarray(offset, offset + 32768)); } return { ok: true, type: blob.type, size: blob.size, base64: btoa(binary), snapshot: snapshot, matchedNodeIds: matchedNodeIds, matchedEdgeKeys: matchedEdgeKeys, expectedEdgeKeys: snapshot.edges.map(function (edge) { return edge.key; }), reachDirection: reachSvg.getAttribute('data-share-reach'), originCount: reachSvg.querySelectorAll('[data-node-id][data-share-reach-origin]').length, reachLiveResidue: reachSvg.hasAttribute('data-reach-active') || reachSvg.querySelectorAll('[data-reach-match], [data-reach-origin], [data-reach-depth]').length > 0, routeResidue: reachSvg.hasAttribute('data-share-route') || reachSvg.querySelectorAll('[data-share-route-match], [data-share-route-step], [data-route-match], [data-route-step]').length > 0, motionResidue: reachSvg.hasAttribute('data-animation') || reachSvg.querySelectorAll('[data-animate]').length > 0, blueprintStaticRule: /data-preset=\"blueprint\"\]\[data-share-reach\][^}]*filter:\s*none/.test(reachStyles), reachNodeIds: reachNodeIds, liveNodeIds: liveNodeIds, reachEdgeKeys: reachEdgeKeys, liveEdgeKeys: liveEdgeKeys, liveUnchanged: liveBefore === liveAfter, menuResolved: menuResolved, duplicateGeometryRejected: duplicateGeometryRejected, duplicateExportError: duplicateExportError, downloadStable: downloadedBlob && downloadedBlob.type === 'image/png', downloads: downloads, reachReceipt: reachReceipt, hiddenAfterClear: hiddenAfterClear, staleSnapshot: Archify.focus.reachabilitySnapshot(), staleError: staleError, failedReceipt: failedReceipt, canonicalSize: canonicalBlob.size, canonicalReachResidue: canonicalReachResidue, matrix: matrix }; } finally { URL.createObjectURL = originalCreateObjectURL; HTMLAnchorElement.prototype.click = originalAnchorClick; } } catch (error) { return { ok: false, error: String(error && error.message || error) }; } })()`, true), options.matrix ? 35_000 : 15_000, `${label} Reach Card export`); assert.equal(reachPayload?.ok, true, reachPayload?.error || `${label} Reach Card export failed`); assert.equal(reachPayload.type, 'image/png'); assert.ok(reachPayload.size > 20_000, `${label} Reach Card is unexpectedly small (${reachPayload.size} bytes)`); const png = Buffer.from(reachPayload.base64, 'base64'); assert.equal(png.subarray(0, 8).toString('hex'), '89504e470d0a1a0a', `${label} Reach Card is not a PNG`); assert.equal(png.readUInt32BE(16), 1200, `${label} Reach Card width`); assert.equal(png.readUInt32BE(20), 630, `${label} Reach Card height`); if (options.outputPath) { fs.mkdirSync(path.dirname(options.outputPath), { recursive: true }); fs.writeFileSync(options.outputPath, png); } assert.equal(reachPayload.reachDirection, direction); assert.deepEqual(reachPayload.matchedNodeIds.slice().sort(), reachPayload.snapshot.nodeIds.slice().sort()); assert.deepEqual(reachPayload.matchedEdgeKeys.slice().sort(), reachPayload.expectedEdgeKeys.slice().sort()); assert.equal(reachPayload.originCount, 1); assert.equal(reachPayload.reachLiveResidue, false); assert.equal(reachPayload.routeResidue, false); assert.equal(reachPayload.motionResidue, false); assert.equal(reachPayload.blueprintStaticRule, true); assert.deepEqual(reachPayload.reachNodeIds, reachPayload.liveNodeIds); assert.deepEqual(reachPayload.reachEdgeKeys, reachPayload.liveEdgeKeys); assert.equal(reachPayload.liveUnchanged, true); assert.equal(reachPayload.menuResolved, true); assert.equal(reachPayload.duplicateGeometryRejected, true); assert.match(reachPayload.duplicateExportError, /Trace authored reach before exporting a Reach Share Card/); assert.equal(reachPayload.downloadStable, true); assert.match(reachPayload.downloads[0], new RegExp(`-${direction}-reach-share-card\\.png$`)); assert.deepEqual(reachPayload.reachReceipt, { format: 'share-card', variant: 'reach', width: '1200', height: '630', canonical: 'false', routeStateClean: null, reachStateClean: 'true', error: null, }); assert.equal(reachPayload.hiddenAfterClear, true); assert.equal(reachPayload.staleSnapshot, null); assert.match(reachPayload.staleError, /Trace authored reach before exporting a Reach Share Card/); assert.equal(reachPayload.failedReceipt.format, null); assert.equal(reachPayload.failedReceipt.variant, null); assert.equal(reachPayload.failedReceipt.errorFormat, 'share-card'); assert.match(reachPayload.failedReceipt.error, /Trace authored reach before exporting a Reach Share Card/); assert.ok(reachPayload.canonicalSize > 20_000); assert.equal(reachPayload.canonicalReachResidue, false); if (options.matrix) { assert.equal(reachPayload.matrix.length, 8); assert.equal(new Set(reachPayload.matrix.map((entry) => entry.hash)).size, 8, `${label} Reach presets/themes should produce eight distinct PNGs`); for (const entry of reachPayload.matrix) { assert.equal(entry.type, 'image/png'); assert.equal(entry.width, 1200); assert.equal(entry.height, 630); assert.ok(entry.size > 20_000); } } console.log(`ok ${label} Reach Card: ${reachPayload.size} bytes, ${reachPayload.snapshot.nodeIds.length} nodes, ${reachPayload.snapshot.edges.length} authored links`); } await verifyResolvedLegendContract(legendOutputs); await verifySemanticPassportDismissal(path.resolve(skillRoot, '../docs/gallery/artifacts/production-deployment.architecture.html')); await verifyArchitectureDeltaNavigator(path.resolve(skillRoot, '../examples/checkout-platform-delta.html')); await captureShareCard(output, 'architecture-wide'); await captureShareCard(sequenceOutput, 'sequence-tall'); await captureCopiedShareCard(output, 'architecture-wide'); await captureRouteShareCard(routeOutputs.architecture, 'architecture-route', 'users', 'api', { journeyInvariance: true }); await captureRouteShareCard(routeOutputs.workflow, 'workflow-route', 'user', 'approval'); await captureRouteShareCard(routeOutputs.sequence, 'sequence-route', 'web', 'db'); await captureRouteShareCard(routeOutputs.dataflow, 'dataflow-route', 'web', 'dashboard'); await captureRouteShareCard(routeOutputs.lifecycle, 'lifecycle-route', 'executing', 'cancelled'); await captureRouteShareCard(parallelOutput, 'parallel-route', 'users', 'cdn'); await captureRouteShareCard(specialRouteOutput, 'special-10-hop-route', 'Route_Source-01', 'Route_Target-10', { expectedHops: 10, expectedSourceLabel: specialSourceLabel, expectedTargetLabel: specialTargetLabel, }); await captureRouteVisualMatrix(routeOutputs.architecture, 'architecture-route', 'users', 'api'); await captureRouteVisualMatrix(routeOutputs.sequence, 'sequence-route', 'web', 'db'); await captureReachShareCard(routeOutputs.architecture, 'architecture-reach', 'users', 'downstream', { matrix: true }); await captureReachShareCard(routeOutputs.workflow, 'workflow-reach', 'user', 'downstream'); await captureReachShareCard(routeOutputs.sequence, 'sequence-reach', 'web', 'downstream'); await captureReachShareCard(routeOutputs.dataflow, 'dataflow-reach', 'web', 'downstream'); await captureReachShareCard(routeOutputs.lifecycle, 'lifecycle-reach', 'executing', 'downstream'); if (externalReachSource) { assert.ok(fs.existsSync(externalReachSource), `external Reach Card source does not exist: ${externalReachSource}`); await captureReachShareCard( externalReachSource, 'external-reach', process.env.ARCHIFY_REACH_CARD_ORIGIN || 'router', process.env.ARCHIFY_REACH_CARD_DIRECTION || 'downstream', { outputPath: externalReachOutput }, ); console.log(`ok external Reach Card artifact: ${externalReachOutput}`); } await verifyDynamicReducedMotionRoute(routeOutputs.architecture, 'architecture-route reduced motion', 'users', 'api'); await navigateReady(output, '!!(window.Archify && Archify.motion && Archify.motion.canRecord())', 'motion artifact'); const payload = await withTimeout(evaluate(cdp, sessionId, String.raw`(async function () { try { var blob = await Archify.motion.recordWebm({ duration: 1400, fps: 12 }); var bytes = new Uint8Array(await blob.arrayBuffer()); var binary = ''; for (var offset = 0; offset < bytes.length; offset += 32768) { binary += String.fromCharCode.apply(null, bytes.subarray(offset, offset + 32768)); } return { ok: true, type: blob.type, size: blob.size, base64: btoa(binary) }; } catch (error) { return { ok: false, error: String(error && error.message || error) }; } })()`, true), 20_000, 'WebM recording'); assert.equal(payload?.ok, true, payload?.error || 'WebM recording failed'); assert.match(payload.type, /^video\/webm/); assert.ok(payload.size > 10_000, `WebM is unexpectedly small (${payload.size} bytes)`); const webm = path.join(tmp, 'motion.webm'); fs.writeFileSync(webm, Buffer.from(payload.base64, 'base64')); const frameMd5 = execFileSync(ffmpeg, [ '-v', 'error', '-i', webm, '-vf', 'fps=6,scale=320:-2', '-f', 'framemd5', '-', ], { encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 }); const hashes = frameMd5 .split('\n') .filter((line) => line && !line.startsWith('#')) .map((line) => line.split(',').at(-1).trim()); const uniqueFrames = new Set(hashes); assert.ok(hashes.length >= 4, `decoded only ${hashes.length} WebM frames`); assert.ok(uniqueFrames.size >= 2, 'decoded WebM frames are static'); console.log(`ok WebM artifact: ${payload.size} bytes, ${hashes.length} sampled frames, ${uniqueFrames.size} unique`); } finally { if (cdp && targetId) await withTimeout(cdp.send('Target.closeTarget', { targetId }), 500, 'target close').catch(() => {}); if (cdp) cdp.socket.close(); chromeProcess.kill('SIGTERM'); if (!(await waitForExit(chromeProcess, 1000))) { chromeProcess.kill('SIGKILL'); await waitForExit(chromeProcess, 1000); } await removeTempTree(tmp); }