tevra-design-kit 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/PROVENANCE.md +18 -0
- package/README.md +251 -2
- package/SKILL.md +20 -0
- package/THIRD_PARTY_NOTICES.md +23 -0
- package/adapters/claude-code/CLAUDE.md +14 -0
- package/adapters/codex/AGENTS.md +5 -0
- package/adapters/cursor/README.md +5 -0
- package/adapters/gemini-cli/README.md +5 -0
- package/adapters/github-copilot/README.md +5 -0
- package/adapters/kiro/README.md +5 -0
- package/adapters/opencode/README.md +5 -0
- package/adapters/windsurf/README.md +5 -0
- package/dist/packages/cli/src/arguments.d.ts +9 -0
- package/dist/packages/cli/src/arguments.js +58 -0
- package/dist/packages/cli/src/arguments.js.map +1 -0
- package/dist/packages/cli/src/index.d.ts +2 -0
- package/dist/packages/cli/src/index.js +132 -0
- package/dist/packages/cli/src/index.js.map +1 -0
- package/dist/packages/cli/src/project-commands.d.ts +4 -0
- package/dist/packages/cli/src/project-commands.js +137 -0
- package/dist/packages/cli/src/project-commands.js.map +1 -0
- package/dist/packages/cli/src/run-command.d.ts +2 -0
- package/dist/packages/cli/src/run-command.js +35 -0
- package/dist/packages/cli/src/run-command.js.map +1 -0
- package/dist/packages/cli/src/visual-commands.d.ts +5 -0
- package/dist/packages/cli/src/visual-commands.js +73 -0
- package/dist/packages/cli/src/visual-commands.js.map +1 -0
- package/dist/packages/cli/src/workflow-commands.d.ts +2 -0
- package/dist/packages/cli/src/workflow-commands.js +73 -0
- package/dist/packages/cli/src/workflow-commands.js.map +1 -0
- package/dist/packages/contracts/src/index.d.ts +181 -0
- package/dist/packages/contracts/src/index.js +3 -0
- package/dist/packages/contracts/src/index.js.map +1 -0
- package/dist/packages/core/src/analyze.d.ts +6 -0
- package/dist/packages/core/src/analyze.js +46 -0
- package/dist/packages/core/src/analyze.js.map +1 -0
- package/dist/packages/core/src/config.d.ts +4 -0
- package/dist/packages/core/src/config.js +39 -0
- package/dist/packages/core/src/config.js.map +1 -0
- package/dist/packages/core/src/install.d.ts +13 -0
- package/dist/packages/core/src/install.js +69 -0
- package/dist/packages/core/src/install.js.map +1 -0
- package/dist/packages/core/src/router.d.ts +12 -0
- package/dist/packages/core/src/router.js +90 -0
- package/dist/packages/core/src/router.js.map +1 -0
- package/dist/packages/core/src/search.d.ts +12 -0
- package/dist/packages/core/src/search.js +31 -0
- package/dist/packages/core/src/search.js.map +1 -0
- package/dist/packages/core/src/skills.d.ts +7 -0
- package/dist/packages/core/src/skills.js +36 -0
- package/dist/packages/core/src/skills.js.map +1 -0
- package/dist/packages/core/src/workflow.d.ts +3 -0
- package/dist/packages/core/src/workflow.js +73 -0
- package/dist/packages/core/src/workflow.js.map +1 -0
- package/dist/packages/design-context/src/index.d.ts +5 -0
- package/dist/packages/design-context/src/index.js +41 -0
- package/dist/packages/design-context/src/index.js.map +1 -0
- package/dist/packages/design-context/src/migrate.d.ts +2 -0
- package/dist/packages/design-context/src/migrate.js +27 -0
- package/dist/packages/design-context/src/migrate.js.map +1 -0
- package/dist/packages/design-context/src/tokens.d.ts +8 -0
- package/dist/packages/design-context/src/tokens.js +65 -0
- package/dist/packages/design-context/src/tokens.js.map +1 -0
- package/dist/packages/design-context/src/validation.d.ts +1 -0
- package/dist/packages/design-context/src/validation.js +22 -0
- package/dist/packages/design-context/src/validation.js.map +1 -0
- package/dist/packages/design-memory/src/index.d.ts +16 -0
- package/dist/packages/design-memory/src/index.js +47 -0
- package/dist/packages/design-memory/src/index.js.map +1 -0
- package/dist/packages/filesystem/src/index.d.ts +8 -0
- package/dist/packages/filesystem/src/index.js +83 -0
- package/dist/packages/filesystem/src/index.js.map +1 -0
- package/dist/packages/filesystem/src/resources.d.ts +1 -0
- package/dist/packages/filesystem/src/resources.js +13 -0
- package/dist/packages/filesystem/src/resources.js.map +1 -0
- package/dist/packages/performance-qa/src/index.d.ts +24 -0
- package/dist/packages/performance-qa/src/index.js +77 -0
- package/dist/packages/performance-qa/src/index.js.map +1 -0
- package/dist/packages/quality-engine/src/html.d.ts +2 -0
- package/dist/packages/quality-engine/src/html.js +15 -0
- package/dist/packages/quality-engine/src/html.js.map +1 -0
- package/dist/packages/quality-engine/src/index.d.ts +3 -0
- package/dist/packages/quality-engine/src/index.js +45 -0
- package/dist/packages/quality-engine/src/index.js.map +1 -0
- package/dist/packages/registry/src/index.d.ts +49 -0
- package/dist/packages/registry/src/index.js +132 -0
- package/dist/packages/registry/src/index.js.map +1 -0
- package/dist/packages/visual-testing/src/capture.d.ts +12 -0
- package/dist/packages/visual-testing/src/capture.js +174 -0
- package/dist/packages/visual-testing/src/capture.js.map +1 -0
- package/dist/packages/visual-testing/src/checks.d.ts +3 -0
- package/dist/packages/visual-testing/src/checks.js +17 -0
- package/dist/packages/visual-testing/src/checks.js.map +1 -0
- package/dist/packages/visual-testing/src/compare.d.ts +12 -0
- package/dist/packages/visual-testing/src/compare.js +85 -0
- package/dist/packages/visual-testing/src/compare.js.map +1 -0
- package/dist/packages/visual-testing/src/types.d.ts +49 -0
- package/dist/packages/visual-testing/src/types.js +2 -0
- package/dist/packages/visual-testing/src/types.js.map +1 -0
- package/dist/packages/workflow-engine/src/index.d.ts +11 -0
- package/dist/packages/workflow-engine/src/index.js +51 -0
- package/dist/packages/workflow-engine/src/index.js.map +1 -0
- package/docs/agents.md +36 -0
- package/docs/architecture.md +86 -0
- package/docs/configuration.md +109 -0
- package/docs/contributing.md +75 -0
- package/docs/security.md +42 -0
- package/docs/usage.md +142 -0
- package/package.json +85 -4
- package/references/accessibility/manual-checklist.md +3 -0
- package/references/color-systems/semantic-color.md +3 -0
- package/references/design-principles/decision-making.md +3 -0
- package/references/frameworks/nextjs.md +3 -0
- package/references/frameworks/react-native-expo.md +3 -0
- package/references/frameworks/react.md +3 -0
- package/references/frameworks/sveltekit.md +3 -0
- package/references/frameworks/vue.md +3 -0
- package/references/typography/scales.md +3 -0
- package/references/ux-patterns/README.md +7 -0
- package/references/ux-patterns/UPSTREAM-LICENSE.txt +21 -0
- package/references/ux-patterns/catalog.json +221 -0
- package/registry/capability-ownership.json +191 -0
- package/registry/skills.json +764 -0
- package/registry/sources.json +159 -0
- package/rubrics/quality-rubric.md +24 -0
- package/schemas/capability-ownership.schema.json +69 -0
- package/schemas/capture-manifest.schema.json +209 -0
- package/schemas/checks.schema.json +83 -0
- package/schemas/comparison.schema.json +154 -0
- package/schemas/config.schema.json +297 -0
- package/schemas/design-context.schema.json +651 -0
- package/schemas/design-context.v2.schema.json +395 -0
- package/schemas/handoff.schema.json +135 -0
- package/schemas/legacy/design-context.master-v1.schema.json +167 -0
- package/schemas/legacy/design-context.v1.schema.json +36 -0
- package/schemas/memory.schema.json +69 -0
- package/schemas/plan.schema.json +139 -0
- package/schemas/quality-report.schema.json +151 -0
- package/schemas/skill-registry.schema.json +222 -0
- package/schemas/sources.schema.json +129 -0
- package/skills/accessibility-qa/LICENSE +21 -0
- package/skills/accessibility-qa/SKILL.md +56 -0
- package/skills/accessibility-qa/references/guide.md +3 -0
- package/skills/browser-testing/LICENSE +21 -0
- package/skills/browser-testing/SKILL.md +35 -0
- package/skills/browser-testing/references/guide.md +11 -0
- package/skills/design-direction/LICENSE +21 -0
- package/skills/design-direction/SKILL.md +56 -0
- package/skills/design-direction/references/guide.md +3 -0
- package/skills/design-research/LICENSE +21 -0
- package/skills/design-research/SKILL.md +56 -0
- package/skills/design-research/references/guide.md +3 -0
- package/skills/design-system/LICENSE +21 -0
- package/skills/design-system/SKILL.md +56 -0
- package/skills/design-system/references/guide.md +3 -0
- package/skills/frontend-review/LICENSE +21 -0
- package/skills/frontend-review/SKILL.md +35 -0
- package/skills/frontend-review/references/guide.md +11 -0
- package/skills/interaction-motion/LICENSE +21 -0
- package/skills/interaction-motion/SKILL.md +56 -0
- package/skills/interaction-motion/references/guide.md +3 -0
- package/skills/performance-qa/LICENSE +21 -0
- package/skills/performance-qa/SKILL.md +35 -0
- package/skills/performance-qa/references/guide.md +9 -0
- package/skills/responsive-design/LICENSE +21 -0
- package/skills/responsive-design/SKILL.md +56 -0
- package/skills/responsive-design/references/guide.md +3 -0
- package/skills/ui-builder/LICENSE +21 -0
- package/skills/ui-builder/SKILL.md +58 -0
- package/skills/ui-builder/references/guide.md +3 -0
- package/skills/ui-builder/references/nextjs.md +3 -0
- package/skills/ui-builder/references/react-native-expo.md +3 -0
- package/skills/ui-builder/references/react.md +3 -0
- package/skills/ui-builder/references/sveltekit.md +3 -0
- package/skills/ui-builder/references/vue.md +3 -0
- package/skills/ui-redesign/LICENSE +21 -0
- package/skills/ui-redesign/SKILL.md +56 -0
- package/skills/ui-redesign/references/guide.md +3 -0
- package/skills/visual-review/LICENSE +21 -0
- package/skills/visual-review/SKILL.md +56 -0
- package/skills/visual-review/references/guide.md +7 -0
- package/templates/direction-decision.md +45 -0
- package/templates/research-brief.md +56 -0
- package/templates/visual-review.md +46 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { validate } from '../../design-context/src/validation.js';
|
|
4
|
+
import { kitRoot } from '../../filesystem/src/resources.js';
|
|
5
|
+
export function validateRegistry(input) {
|
|
6
|
+
const registry = validate('skill-registry.schema.json', input);
|
|
7
|
+
const identities = new Map();
|
|
8
|
+
const names = new Set();
|
|
9
|
+
for (const skill of registry.skills) {
|
|
10
|
+
if (identities.has(skill.id) || names.has(skill.name))
|
|
11
|
+
throw new Error(`Duplicate skill identity: ${skill.id}`);
|
|
12
|
+
if (/^(?:[a-z]:|[\\/])/iu.test(skill.entrypoint) || skill.entrypoint.split(/[\\/]/u).some(part => part === '..' || part === '.') || skill.entrypoint.includes('\0'))
|
|
13
|
+
throw new Error(`Unsafe entrypoint: ${skill.id}`);
|
|
14
|
+
identities.set(skill.id, skill);
|
|
15
|
+
names.add(skill.name);
|
|
16
|
+
}
|
|
17
|
+
for (const skill of registry.skills) {
|
|
18
|
+
for (const id of [...skill.dependencies, ...skill.conflicts]) {
|
|
19
|
+
if (!identities.has(id))
|
|
20
|
+
throw new Error(`Missing dependency or conflict ${id} for ${skill.id}`);
|
|
21
|
+
if (id === skill.id)
|
|
22
|
+
throw new Error(`Self dependency or conflict: ${id}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const visited = new Set();
|
|
26
|
+
const active = new Set();
|
|
27
|
+
function visit(skill) {
|
|
28
|
+
if (active.has(skill.id))
|
|
29
|
+
throw new Error(`Dependency cycle: ${skill.id}`);
|
|
30
|
+
if (visited.has(skill.id))
|
|
31
|
+
return;
|
|
32
|
+
active.add(skill.id);
|
|
33
|
+
for (const id of skill.dependencies) {
|
|
34
|
+
const dependency = identities.get(id);
|
|
35
|
+
if (!dependency)
|
|
36
|
+
throw new Error(`Missing dependency: ${id}`);
|
|
37
|
+
visit(dependency);
|
|
38
|
+
}
|
|
39
|
+
active.delete(skill.id);
|
|
40
|
+
visited.add(skill.id);
|
|
41
|
+
}
|
|
42
|
+
for (const skill of registry.skills)
|
|
43
|
+
visit(skill);
|
|
44
|
+
return registry;
|
|
45
|
+
}
|
|
46
|
+
export async function loadRegistry(extensionFiles = []) {
|
|
47
|
+
const files = [resolve(kitRoot(), 'registry/skills.json'), ...extensionFiles];
|
|
48
|
+
const skills = [];
|
|
49
|
+
for (const path of files) {
|
|
50
|
+
const extension = validate('skill-registry.schema.json', JSON.parse(await readFile(path, 'utf8')));
|
|
51
|
+
skills.push(...extension.skills);
|
|
52
|
+
}
|
|
53
|
+
return validateRegistry({ version: 1, skills });
|
|
54
|
+
}
|
|
55
|
+
export function selectCapabilities(registryInput, capabilities, options = {}) {
|
|
56
|
+
const registry = validateRegistry(registryInput);
|
|
57
|
+
const owners = {};
|
|
58
|
+
const duplicates = {};
|
|
59
|
+
const selected = new Map();
|
|
60
|
+
const compatible = (skill) => (!options.framework || skill.frameworks.includes(options.framework)) &&
|
|
61
|
+
(!options.platform || skill.platforms.includes(options.platform)) &&
|
|
62
|
+
(!options.agent || skill.agents.includes(options.agent));
|
|
63
|
+
for (const capability of [...new Set(capabilities)].sort()) {
|
|
64
|
+
const candidates = registry.skills.filter(skill => skill.capabilities.includes(capability) && compatible(skill))
|
|
65
|
+
.sort((a, b) => b.priority - a.priority || a.id.localeCompare(b.id, 'en'));
|
|
66
|
+
const override = options.overrides?.[capability];
|
|
67
|
+
const winner = override ? candidates.find(skill => skill.id === override) : candidates[0];
|
|
68
|
+
if (!winner)
|
|
69
|
+
throw new Error(`No compatible owner for ${capability}${override ? ` (override ${override})` : ''}`);
|
|
70
|
+
owners[capability] = winner.id;
|
|
71
|
+
selected.set(winner.id, winner);
|
|
72
|
+
if (candidates.length > 1)
|
|
73
|
+
duplicates[capability] = candidates.map(skill => skill.id);
|
|
74
|
+
}
|
|
75
|
+
const ordered = [];
|
|
76
|
+
const added = new Set();
|
|
77
|
+
function add(skill) {
|
|
78
|
+
if (added.has(skill.id))
|
|
79
|
+
return;
|
|
80
|
+
if (!compatible(skill))
|
|
81
|
+
throw new Error(`Incompatible dependency: ${skill.id}`);
|
|
82
|
+
for (const id of skill.dependencies) {
|
|
83
|
+
const dependency = registry.skills.find(candidate => candidate.id === id);
|
|
84
|
+
if (!dependency)
|
|
85
|
+
throw new Error(`Missing dependency: ${id}`);
|
|
86
|
+
add(dependency);
|
|
87
|
+
}
|
|
88
|
+
added.add(skill.id);
|
|
89
|
+
ordered.push(skill);
|
|
90
|
+
}
|
|
91
|
+
for (const skill of selected.values())
|
|
92
|
+
add(skill);
|
|
93
|
+
for (const skill of ordered) {
|
|
94
|
+
const conflict = skill.conflicts.find(id => added.has(id));
|
|
95
|
+
if (conflict)
|
|
96
|
+
throw new Error(`Capability conflict: ${skill.id} conflicts with ${conflict}`);
|
|
97
|
+
}
|
|
98
|
+
return { skills: ordered, owners, duplicates };
|
|
99
|
+
}
|
|
100
|
+
export function registryGraph(registry) {
|
|
101
|
+
validateRegistry(registry);
|
|
102
|
+
const lines = ['graph TD'];
|
|
103
|
+
const nodes = new Map(registry.skills.map((skill, index) => [skill.id, `s${index}`]));
|
|
104
|
+
for (const skill of registry.skills) {
|
|
105
|
+
lines.push(` ${nodes.get(skill.id)}["${skill.id}"]`);
|
|
106
|
+
for (const dependency of skill.dependencies)
|
|
107
|
+
lines.push(` ${nodes.get(dependency)} --> ${nodes.get(skill.id)}`);
|
|
108
|
+
for (const conflict of skill.conflicts)
|
|
109
|
+
lines.push(` ${nodes.get(skill.id)} -. conflict .-> ${nodes.get(conflict)}`);
|
|
110
|
+
}
|
|
111
|
+
return lines.join('\n');
|
|
112
|
+
}
|
|
113
|
+
export async function validateBundledProvenance(registry) {
|
|
114
|
+
const sources = validate('sources.schema.json', JSON.parse(await readFile(resolve(kitRoot(), 'registry/sources.json'), 'utf8')));
|
|
115
|
+
if (new Set(sources.sources.map(source => source.id)).size !== sources.sources.length)
|
|
116
|
+
throw new Error('Duplicate source IDs');
|
|
117
|
+
for (const skill of registry.skills.filter(skill => skill.id.startsWith('tevra.')))
|
|
118
|
+
for (const id of skill.source.references)
|
|
119
|
+
if (!sources.sources.some(source => source.id === id))
|
|
120
|
+
throw new Error(`Missing source provenance: ${id}`);
|
|
121
|
+
const ownership = validate('capability-ownership.schema.json', JSON.parse(await readFile(resolve(kitRoot(), 'registry/capability-ownership.json'), 'utf8')));
|
|
122
|
+
if (new Set(ownership.capabilities.map(capability => capability.id)).size !== ownership.capabilities.length)
|
|
123
|
+
throw new Error('Duplicate capability ownership');
|
|
124
|
+
for (const capability of ownership.capabilities) {
|
|
125
|
+
if (capability.primary && !registry.skills.some(skill => skill.id === capability.primary && skill.capabilities.includes(capability.id)))
|
|
126
|
+
throw new Error(`Invalid capability owner: ${capability.id}`);
|
|
127
|
+
for (const supporting of capability.supporting)
|
|
128
|
+
if (!registry.skills.some(skill => skill.id === supporting))
|
|
129
|
+
throw new Error(`Unknown supporting skill: ${supporting}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/registry/src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import axe from 'axe-core';
|
|
2
|
+
import type { Config } from '../../contracts/src/index.js';
|
|
3
|
+
import type { CaptureManifest } from './types.js';
|
|
4
|
+
declare global {
|
|
5
|
+
interface Window {
|
|
6
|
+
axe: typeof axe;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export declare function configHash(config: Config): string;
|
|
10
|
+
export declare function manifestHash(manifest: CaptureManifest): string;
|
|
11
|
+
export declare function comparisonSettingsHash(config: Config): string;
|
|
12
|
+
export declare function capture(root: string, config: Config, kind: CaptureManifest['kind'], revision: number, approveBaseline?: boolean, allowCustomBrowser?: boolean): Promise<CaptureManifest>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
import axe from 'axe-core';
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
import { mkdir, stat } from 'node:fs/promises';
|
|
5
|
+
import { isMissing, redact, safePath, withLock, writeJSON } from '../../filesystem/src/index.js';
|
|
6
|
+
export function configHash(config) {
|
|
7
|
+
return createHash('sha256').update(JSON.stringify({ baseURL: config.baseURL, origins: config.permittedOrigins, browser: config.browser, viewports: config.viewports, routes: config.routes, readySelector: config.readySelector, colorScheme: config.colorScheme ?? 'light', reducedMotion: config.reducedMotion ?? 'reduce' })).digest('hex');
|
|
8
|
+
}
|
|
9
|
+
export function manifestHash(manifest) { return createHash('sha256').update(JSON.stringify(manifest)).digest('hex'); }
|
|
10
|
+
export function comparisonSettingsHash(config) { return createHash('sha256').update(JSON.stringify({ diffRatio: config.thresholds.diffRatio, pixelSensitivity: config.thresholds.pixelSensitivity })).digest('hex'); }
|
|
11
|
+
export async function capture(root, config, kind, revision, approveBaseline = false, allowCustomBrowser = false) {
|
|
12
|
+
if (config.browser.executablePath && !allowCustomBrowser)
|
|
13
|
+
throw new Error('Custom browser executable requires explicit --allow-custom-browser authorization');
|
|
14
|
+
if (kind === 'baseline' && !approveBaseline)
|
|
15
|
+
throw new Error('Baseline capture requires explicit --approve-baseline authorization');
|
|
16
|
+
return withLock(root, '.tevra/visual.lock', async () => {
|
|
17
|
+
if (kind === 'baseline') {
|
|
18
|
+
try {
|
|
19
|
+
await stat(await safePath(root, '.tevra/artifacts/baseline/manifest.json'));
|
|
20
|
+
throw new Error('Baseline already exists; archive it explicitly before establishing a new baseline');
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (!isMissing(error))
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const runId = randomUUID();
|
|
28
|
+
await writeJSON(root, `.tevra/artifacts/${kind}/attempt.json`, { status: 'running', runId });
|
|
29
|
+
try {
|
|
30
|
+
const browser = await chromium.launch({ ...config.browser, headless: true, timeout: config.timeoutMs });
|
|
31
|
+
try {
|
|
32
|
+
const manifest = { version: 1, kind, runId, createdAt: new Date().toISOString(), configHash: configHash(config), contextRevision: revision, browserVersion: browser.version(), entries: [] };
|
|
33
|
+
for (const route of config.routes)
|
|
34
|
+
for (const state of route.states)
|
|
35
|
+
for (const viewport of config.viewports) {
|
|
36
|
+
const id = `${route.name}--${state.name}--${viewport.name}`;
|
|
37
|
+
const file = `.tevra/artifacts/${kind}/${runId}/${id}.png`;
|
|
38
|
+
const path = await safePath(root, file);
|
|
39
|
+
await mkdir(await safePath(root, `.tevra/artifacts/${kind}/${runId}`), { recursive: true });
|
|
40
|
+
const context = await browser.newContext({ viewport: { width: viewport.width, height: viewport.height }, reducedMotion: config.reducedMotion ?? 'reduce', colorScheme: config.colorScheme ?? 'light', locale: 'en-US', timezoneId: 'UTC', serviceWorkers: 'block', acceptDownloads: false });
|
|
41
|
+
try {
|
|
42
|
+
await context.routeWebSocket('**/*', socket => {
|
|
43
|
+
const url = new URL(socket.url());
|
|
44
|
+
url.protocol = url.protocol === 'wss:' ? 'https:' : 'http:';
|
|
45
|
+
if (config.permittedOrigins.includes(url.origin))
|
|
46
|
+
socket.connectToServer();
|
|
47
|
+
else
|
|
48
|
+
socket.close();
|
|
49
|
+
});
|
|
50
|
+
await context.route('**/*', async (request) => {
|
|
51
|
+
const url = new URL(request.request().url());
|
|
52
|
+
if (['data:', 'blob:', 'about:'].includes(url.protocol) || config.permittedOrigins.includes(url.origin))
|
|
53
|
+
await request.continue();
|
|
54
|
+
else
|
|
55
|
+
await request.abort('blockedbyclient');
|
|
56
|
+
});
|
|
57
|
+
const page = await context.newPage();
|
|
58
|
+
page.setDefaultTimeout(config.timeoutMs);
|
|
59
|
+
const consoleErrors = [];
|
|
60
|
+
const networkErrors = [];
|
|
61
|
+
let assertions = 0;
|
|
62
|
+
page.on('console', message => { if (message.type() === 'error')
|
|
63
|
+
consoleErrors.push(redact(message.text())); });
|
|
64
|
+
page.on('requestfailed', request => networkErrors.push(redact(`${request.method()} ${request.url()}: ${request.failure()?.errorText ?? 'failed'}`)));
|
|
65
|
+
page.on('response', response => { if (response.status() >= 400)
|
|
66
|
+
networkErrors.push(redact(`HTTP ${response.status()} ${response.url()}`)); });
|
|
67
|
+
const pageErrors = [];
|
|
68
|
+
page.on('pageerror', error => pageErrors.push(redact(error.message)));
|
|
69
|
+
const response = await page.goto(new URL(route.path, config.baseURL).href, { waitUntil: 'load', timeout: config.timeoutMs });
|
|
70
|
+
if (!response?.ok())
|
|
71
|
+
throw new Error(`Route failed with HTTP ${response?.status() ?? 'no response'}: ${route.name}`);
|
|
72
|
+
if (!config.permittedOrigins.includes(new URL(page.url()).origin))
|
|
73
|
+
throw new Error(`Redirect outside permitted origins: ${route.name}`);
|
|
74
|
+
if (config.readySelector)
|
|
75
|
+
await page.locator(config.readySelector).waitFor({ state: 'attached' });
|
|
76
|
+
for (const action of state.actions) {
|
|
77
|
+
const control = page.locator(action.selector);
|
|
78
|
+
if (action.type === 'click')
|
|
79
|
+
await control.click();
|
|
80
|
+
else if (action.type === 'check')
|
|
81
|
+
await control.check();
|
|
82
|
+
else if (action.type === 'fill') {
|
|
83
|
+
if (action.value === undefined)
|
|
84
|
+
throw new Error('fill requires a value');
|
|
85
|
+
await control.fill(action.value);
|
|
86
|
+
}
|
|
87
|
+
else if (action.type === 'press') {
|
|
88
|
+
if (action.value === undefined)
|
|
89
|
+
throw new Error('press requires a value');
|
|
90
|
+
await control.press(action.value);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
if (action.type === 'expect-visible')
|
|
94
|
+
await control.waitFor({ state: 'visible' });
|
|
95
|
+
else if (action.type === 'expect-hidden')
|
|
96
|
+
await control.waitFor({ state: 'hidden' });
|
|
97
|
+
else if (action.type === 'expect-checked') {
|
|
98
|
+
if (!await control.isChecked())
|
|
99
|
+
throw new Error(`Expected checked control: ${action.selector}`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
if (action.value === undefined)
|
|
103
|
+
throw new Error('expect-text requires a value');
|
|
104
|
+
await control.filter({ hasText: action.value }).waitFor({ state: 'visible' });
|
|
105
|
+
}
|
|
106
|
+
assertions += 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (state.waitFor)
|
|
110
|
+
await page.locator(state.waitFor).waitFor({ state: 'visible' });
|
|
111
|
+
await page.waitForFunction(() => document.fonts.status === 'loaded');
|
|
112
|
+
for (const selector of state.masks)
|
|
113
|
+
if (!await page.locator(selector).count())
|
|
114
|
+
throw new Error(`Mask matches no elements: ${selector}`);
|
|
115
|
+
await page.addScriptTag({ content: axe.source });
|
|
116
|
+
const accessibility = await page.evaluate(async () => {
|
|
117
|
+
return window.axe.run(document, { runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'] } });
|
|
118
|
+
});
|
|
119
|
+
const geometry = await page.evaluate(() => {
|
|
120
|
+
const overflow = [];
|
|
121
|
+
const smallTargets = [];
|
|
122
|
+
for (const element of document.querySelectorAll('body *')) {
|
|
123
|
+
const box = element.getBoundingClientRect();
|
|
124
|
+
const style = getComputedStyle(element);
|
|
125
|
+
if (!box.width || !box.height || style.visibility === 'hidden' || style.display === 'none')
|
|
126
|
+
continue;
|
|
127
|
+
if (box.right > innerWidth + 1 || box.left < -1) {
|
|
128
|
+
let ancestor = element.parentElement;
|
|
129
|
+
let scrollable = false;
|
|
130
|
+
while (ancestor && ancestor !== document.body) {
|
|
131
|
+
if (['auto', 'scroll'].includes(getComputedStyle(ancestor).overflowX)) {
|
|
132
|
+
scrollable = true;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
ancestor = ancestor.parentElement;
|
|
136
|
+
}
|
|
137
|
+
if (!scrollable)
|
|
138
|
+
overflow.push(`${element.tagName.toLowerCase()}${element.id ? `#${element.id}` : ''}`);
|
|
139
|
+
}
|
|
140
|
+
if (element.matches('button,input:not([type=hidden]),select,[role=button]') && (box.width < 24 || box.height < 24))
|
|
141
|
+
smallTargets.push(`${element.tagName.toLowerCase()}${element.id ? `#${element.id}` : ''}`);
|
|
142
|
+
}
|
|
143
|
+
if (document.documentElement.scrollWidth > innerWidth + 1)
|
|
144
|
+
overflow.push('document-horizontal-overflow');
|
|
145
|
+
return { overflow: [...new Set(overflow)].slice(0, 100), smallTargets: [...new Set(smallTargets)].slice(0, 100) };
|
|
146
|
+
});
|
|
147
|
+
await safePath(root, file);
|
|
148
|
+
const pixelArea = await page.evaluate(() => innerWidth * Math.max(document.documentElement.scrollHeight, innerHeight));
|
|
149
|
+
if (pixelArea > 40_000_000)
|
|
150
|
+
throw new Error('Rendered page exceeds the 40 million pixel capture limit');
|
|
151
|
+
const image = await page.screenshot({ path, fullPage: true, animations: 'disabled', mask: state.masks.map(selector => page.locator(selector)), maskColor: '#777777' });
|
|
152
|
+
manifest.entries.push({ id, route: route.name, state: state.name, viewport, file, imageHash: createHash('sha256').update(image).digest('hex'), masks: state.masks, ...geometry,
|
|
153
|
+
axeViolations: accessibility.violations.map(violation => ({ id: violation.id, impact: violation.impact ?? 'unknown', targets: violation.nodes.map(node => node.target.join(' ')), help: violation.help })),
|
|
154
|
+
incompleteRules: accessibility.incomplete.map(rule => rule.id), pageErrors, consoleErrors, networkErrors, assertions });
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
await context.close();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
await writeJSON(root, `.tevra/artifacts/${kind}/manifest.json`, manifest);
|
|
161
|
+
await writeJSON(root, `.tevra/artifacts/${kind}/attempt.json`, { status: 'passed', runId });
|
|
162
|
+
return manifest;
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
await browser.close();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
await writeJSON(root, `.tevra/artifacts/${kind}/attempt.json`, { status: 'failed', runId, error: redact(error instanceof Error ? error.message : String(error)) });
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../../../packages/visual-testing/src/capture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function captureChecks(manifest) {
|
|
2
|
+
const evidence = [`.tevra/artifacts/${manifest.kind}/manifest.json`];
|
|
3
|
+
const violations = manifest.entries.flatMap(entry => entry.axeViolations.map(violation => `${entry.id}: ${violation.id} (${violation.impact})`));
|
|
4
|
+
const overflow = manifest.entries.flatMap(entry => entry.overflow.map(selector => `${entry.id}: ${selector}`));
|
|
5
|
+
const errors = manifest.entries.flatMap(entry => entry.pageErrors.map(error => `${entry.id}: ${error}`));
|
|
6
|
+
const consoleErrors = manifest.entries.flatMap(entry => (entry.consoleErrors ?? []).map(error => `${entry.id}: ${error}`));
|
|
7
|
+
const networkErrors = manifest.entries.flatMap(entry => (entry.networkErrors ?? []).map(error => `${entry.id}: ${error}`));
|
|
8
|
+
const smallTargets = manifest.entries.flatMap(entry => entry.smallTargets.map(selector => `${entry.id}: ${selector}`));
|
|
9
|
+
const check = (id, category, kind, issues, severity, correction) => ({ id, category, kind, status: issues.length ? 'failed' : 'passed', severity, evidence, issue: issues.length ? issues.join('; ') : `No ${id} findings in captured states.`, component: 'configured viewport/state matrix', correction });
|
|
10
|
+
return [
|
|
11
|
+
{ id: 'functional-tests', category: 'functional', kind: 'deterministic', status: manifest.entries.every(entry => (entry.assertions ?? 0) > 0) ? 'passed' : 'unverified', severity: 'critical', evidence, issue: 'Only explicitly configured assertions establish behavior coverage.', component: 'configured browser flows', correction: 'Add relevant expect-visible/text/hidden/checked actions to every configured state.' },
|
|
12
|
+
check('console-errors', 'functional', 'deterministic', consoleErrors, 'critical', 'Resolve unexpected console errors.'),
|
|
13
|
+
check('network-errors', 'functional', 'deterministic', networkErrors, 'major', 'Inspect failed or blocked network requests.'),
|
|
14
|
+
check('axe', 'accessibility', 'deterministic', violations, 'critical', 'Resolve axe barriers and perform manual keyboard/screen-reader checks.'), check('overflow', 'responsive', 'heuristic', overflow, 'major', 'Inspect clipping and unintended horizontal overflow.'), check('runtime-errors', 'functional', 'deterministic', errors, 'critical', 'Fix browser runtime errors before delivery.'), check('touch-targets', 'accessibility', 'heuristic', smallTargets, 'minor', 'Inspect 24px target-size or spacing exceptions; increase targets where needed.')
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../../../packages/visual-testing/src/checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Config } from '../../contracts/src/index.js';
|
|
2
|
+
import type { CaptureManifest, ComparisonResult } from './types.js';
|
|
3
|
+
export declare function validateCaptureAttempt(root: string, manifest: CaptureManifest): Promise<void>;
|
|
4
|
+
export declare function assertCaptureMatchesConfig(manifest: CaptureManifest, config: Config): void;
|
|
5
|
+
export declare function compareImages(baseline: Buffer, current: Buffer, sensitivity: number, limit: number): {
|
|
6
|
+
png: Buffer;
|
|
7
|
+
differentPixels: number;
|
|
8
|
+
ratio: number;
|
|
9
|
+
sizeMismatch: boolean;
|
|
10
|
+
passed: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare function compare(root: string, config: Config): Promise<ComparisonResult>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import pixelmatch from 'pixelmatch';
|
|
3
|
+
import { PNG } from 'pngjs';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { readJSON, safePath, withLock, writeAtomic, writeJSON } from '../../filesystem/src/index.js';
|
|
6
|
+
import { validate } from '../../design-context/src/validation.js';
|
|
7
|
+
import { configHash, manifestHash, comparisonSettingsHash } from './capture.js';
|
|
8
|
+
export async function validateCaptureAttempt(root, manifest) {
|
|
9
|
+
const attempt = await readJSON(root, `.tevra/artifacts/${manifest.kind}/attempt.json`);
|
|
10
|
+
if (!attempt || typeof attempt !== 'object' || !('status' in attempt) || attempt.status !== 'passed' || !('runId' in attempt) || attempt.runId !== manifest.runId)
|
|
11
|
+
throw new Error(`Latest ${manifest.kind} capture did not pass; recapture before comparison/reporting`);
|
|
12
|
+
for (const entry of manifest.entries) {
|
|
13
|
+
const digest = createHash('sha256').update(await readFile(await safePath(root, entry.file))).digest('hex');
|
|
14
|
+
if (digest !== entry.imageHash)
|
|
15
|
+
throw new Error(`Screenshot changed after capture: ${entry.id}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function assertCaptureMatchesConfig(manifest, config) {
|
|
19
|
+
if (manifest.configHash !== configHash(config))
|
|
20
|
+
throw new Error('Capture does not match the active configuration');
|
|
21
|
+
const expected = config.routes.flatMap(route => route.states.flatMap(state => config.viewports.map(viewport => `${route.name}--${state.name}--${viewport.name}`)));
|
|
22
|
+
if (manifest.entries.length !== expected.length || new Set(manifest.entries.map(entry => entry.id)).size !== expected.length || expected.some(id => !manifest.entries.some(entry => entry.id === id)))
|
|
23
|
+
throw new Error('Capture artifact sets differ');
|
|
24
|
+
for (const entry of manifest.entries) {
|
|
25
|
+
const viewport = config.viewports.find(profile => profile.name === entry.viewport.name);
|
|
26
|
+
if (!viewport || viewport.width !== entry.viewport.width || viewport.height !== entry.viewport.height)
|
|
27
|
+
throw new Error('Capture viewport dimensions differ from configuration');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function compareImages(baseline, current, sensitivity, limit) {
|
|
31
|
+
for (const buffer of [baseline, current]) {
|
|
32
|
+
if (buffer.length < 24 || buffer.subarray(0, 8).toString('hex') !== '89504e470d0a1a0a' || buffer.subarray(12, 16).toString() !== 'IHDR')
|
|
33
|
+
throw new Error('Invalid PNG header');
|
|
34
|
+
if (buffer.readUInt32BE(16) * buffer.readUInt32BE(20) > 40_000_000)
|
|
35
|
+
throw new Error('Screenshot exceeds 40 million pixel comparison limit');
|
|
36
|
+
}
|
|
37
|
+
const left = PNG.sync.read(baseline);
|
|
38
|
+
const right = PNG.sync.read(current);
|
|
39
|
+
const width = Math.max(left.width, right.width);
|
|
40
|
+
const height = Math.max(left.height, right.height);
|
|
41
|
+
if (width * height > 40_000_000)
|
|
42
|
+
throw new Error('Screenshot exceeds 40 million pixel comparison limit');
|
|
43
|
+
const paddedLeft = new PNG({ width, height });
|
|
44
|
+
const paddedRight = new PNG({ width, height });
|
|
45
|
+
const diff = new PNG({ width, height });
|
|
46
|
+
PNG.bitblt(left, paddedLeft, 0, 0, left.width, left.height, 0, 0);
|
|
47
|
+
PNG.bitblt(right, paddedRight, 0, 0, right.width, right.height, 0, 0);
|
|
48
|
+
const differentPixels = pixelmatch(paddedLeft.data, paddedRight.data, diff.data, width, height, { threshold: sensitivity, includeAA: false });
|
|
49
|
+
const ratio = differentPixels / (width * height);
|
|
50
|
+
const sizeMismatch = left.width !== right.width || left.height !== right.height;
|
|
51
|
+
return { png: PNG.sync.write(diff), differentPixels, ratio, sizeMismatch, passed: !sizeMismatch && ratio <= limit };
|
|
52
|
+
}
|
|
53
|
+
export async function compare(root, config) {
|
|
54
|
+
return withLock(root, '.tevra/visual.lock', async () => {
|
|
55
|
+
const baseline = validate('capture-manifest.schema.json', await readJSON(root, '.tevra/artifacts/baseline/manifest.json'));
|
|
56
|
+
const current = validate('capture-manifest.schema.json', await readJSON(root, '.tevra/artifacts/current/manifest.json'));
|
|
57
|
+
if (baseline.kind !== 'baseline' || current.kind !== 'current')
|
|
58
|
+
throw new Error('Invalid capture kinds');
|
|
59
|
+
await validateCaptureAttempt(root, baseline);
|
|
60
|
+
await validateCaptureAttempt(root, current);
|
|
61
|
+
assertCaptureMatchesConfig(baseline, config);
|
|
62
|
+
assertCaptureMatchesConfig(current, config);
|
|
63
|
+
if (baseline.configHash !== current.configHash)
|
|
64
|
+
throw new Error('Capture configurations differ; establish matching evidence rather than hiding the difference');
|
|
65
|
+
if (baseline.contextRevision !== current.contextRevision)
|
|
66
|
+
throw new Error('Capture context revisions differ');
|
|
67
|
+
if (baseline.browserVersion !== current.browserVersion)
|
|
68
|
+
throw new Error('Browser versions differ; compare in a consistent environment');
|
|
69
|
+
const comparisons = [];
|
|
70
|
+
for (const entry of current.entries) {
|
|
71
|
+
const original = baseline.entries.find(candidate => candidate.id === entry.id);
|
|
72
|
+
if (!original)
|
|
73
|
+
throw new Error(`Missing baseline: ${entry.id}`);
|
|
74
|
+
const result = compareImages(await readFile(await safePath(root, original.file)), await readFile(await safePath(root, entry.file)), config.thresholds.pixelSensitivity, config.thresholds.diffRatio);
|
|
75
|
+
const diff = `.tevra/artifacts/diff/${entry.id}.png`;
|
|
76
|
+
await writeAtomic(root, diff, result.png);
|
|
77
|
+
comparisons.push({ id: entry.id, ratio: result.ratio, differentPixels: result.differentPixels, sizeMismatch: result.sizeMismatch, passed: result.passed, diff });
|
|
78
|
+
}
|
|
79
|
+
const evidence = comparisons.map(comparison => comparison.diff);
|
|
80
|
+
const result = { version: 1, createdAt: new Date().toISOString(), settingsHash: comparisonSettingsHash(config), currentManifestHash: manifestHash(current), comparisons, checks: [{ id: 'visual-diff', category: 'consistency', kind: 'deterministic', status: comparisons.every(value => value.passed) ? 'passed' : 'failed', severity: 'major', evidence, component: 'configured visual matrix', issue: 'Changed-pixel ratio and screenshot dimensions compared to baseline.', correction: 'Inspect generated differences; obtain approval for intended changes before replacing baselines.' }] };
|
|
81
|
+
await writeJSON(root, '.tevra/artifacts/comparison.json', result);
|
|
82
|
+
return result;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../../packages/visual-testing/src/compare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Check, Viewport } from '../../contracts/src/index.js';
|
|
2
|
+
export interface CaptureEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
route: string;
|
|
5
|
+
state: string;
|
|
6
|
+
viewport: Viewport;
|
|
7
|
+
file: string;
|
|
8
|
+
imageHash: string;
|
|
9
|
+
masks: string[];
|
|
10
|
+
overflow: string[];
|
|
11
|
+
smallTargets: string[];
|
|
12
|
+
axeViolations: {
|
|
13
|
+
id: string;
|
|
14
|
+
impact: string;
|
|
15
|
+
targets: string[];
|
|
16
|
+
help: string;
|
|
17
|
+
}[];
|
|
18
|
+
incompleteRules: string[];
|
|
19
|
+
pageErrors: string[];
|
|
20
|
+
consoleErrors?: string[];
|
|
21
|
+
networkErrors?: string[];
|
|
22
|
+
assertions?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface CaptureManifest {
|
|
25
|
+
version: 1;
|
|
26
|
+
kind: 'baseline' | 'current';
|
|
27
|
+
runId: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
configHash: string;
|
|
30
|
+
contextRevision: number;
|
|
31
|
+
browserVersion: string;
|
|
32
|
+
entries: CaptureEntry[];
|
|
33
|
+
}
|
|
34
|
+
export interface Comparison {
|
|
35
|
+
id: string;
|
|
36
|
+
ratio: number;
|
|
37
|
+
differentPixels: number;
|
|
38
|
+
sizeMismatch: boolean;
|
|
39
|
+
diff: string;
|
|
40
|
+
passed: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface ComparisonResult {
|
|
43
|
+
version: 1;
|
|
44
|
+
createdAt: string;
|
|
45
|
+
settingsHash: string;
|
|
46
|
+
currentManifestHash: string;
|
|
47
|
+
comparisons: Comparison[];
|
|
48
|
+
checks: Check[];
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../packages/visual-testing/src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Plan } from '../../contracts/src/index.js';
|
|
2
|
+
export interface StageResult {
|
|
3
|
+
status: 'passed' | 'failed' | 'blocked' | 'skipped';
|
|
4
|
+
evidence: string[];
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
export type StageAdapter = () => Promise<StageResult>;
|
|
8
|
+
export declare function runWorkflow(root: string, path: string, adapters: Record<string, StageAdapter>): Promise<{
|
|
9
|
+
status: 'passed' | 'failed' | 'blocked';
|
|
10
|
+
plan: Plan;
|
|
11
|
+
}>;
|