@verndale/accessibility-standards 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/OPERATING.md +5 -0
- package/README.md +22 -0
- package/bin/accessibility-standards.mjs +32 -0
- package/dist/manifest.json +202 -0
- package/dist/profiles/ai-orchestration/coverage-manifest.json +202 -0
- package/dist/profiles/ai-orchestration/implementation/accessibility.md +329 -0
- package/dist/profiles/ai-orchestration/pattern-contracts.json +697 -0
- package/dist/profiles/ai-orchestration/patterns/accordion.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/alert.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/carousel.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/combobox.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/consequential-submission.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/dialog.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/disclosure.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/dynamic-status.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/field.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/image.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/listbox.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/media-player.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/menu-button.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/pagination.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/popover.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/progress.md +11 -0
- package/dist/profiles/ai-orchestration/patterns/tabs.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/tooltip.md +12 -0
- package/dist/profiles/ai-orchestration/patterns/validation.md +11 -0
- package/dist/profiles/ai-orchestration/review/accessibility.md +67 -0
- package/dist/profiles/ai-orchestration/semantics/accessible-name.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/alert.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/consequential-action.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/data-table.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/expanded-state.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/focus.initial.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/focus.return.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/focus.visible.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/form-error.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/form-label.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/form-required.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/headings.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/keyboard.escape.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/keyboard.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/landmarks.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/live-status.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/media-alternatives.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/motion-control.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/name-role-value.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/native-dialog.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/native-elements.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/non-text-alternative.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/roles-states-properties.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/selected-state.md +8 -0
- package/dist/profiles/ai-orchestration/semantics/target-size.md +8 -0
- package/dist/profiles/ai-orchestration/semantics.json +289 -0
- package/dist/profiles/ai-orchestration/testing/accessibility.md +138 -0
- package/dist/profiles/conductor/a11y-semantics.md +152 -0
- package/dist/profiles/conductor/accessibility-quality.md +179 -0
- package/dist/profiles/conductor/applicability-matrix.json +428 -0
- package/dist/profiles/conductor/coverage-manifest.json +202 -0
- package/dist/profiles/conductor/pattern-contracts.json +697 -0
- package/dist/profiles/conductor/patterns/accordion.json +44 -0
- package/dist/profiles/conductor/patterns/alert.json +34 -0
- package/dist/profiles/conductor/patterns/carousel.json +45 -0
- package/dist/profiles/conductor/patterns/combobox.json +44 -0
- package/dist/profiles/conductor/patterns/consequential-submission.json +38 -0
- package/dist/profiles/conductor/patterns/dialog.json +47 -0
- package/dist/profiles/conductor/patterns/disclosure.json +39 -0
- package/dist/profiles/conductor/patterns/dynamic-status.json +36 -0
- package/dist/profiles/conductor/patterns/field.json +39 -0
- package/dist/profiles/conductor/patterns/image.json +35 -0
- package/dist/profiles/conductor/patterns/listbox.json +43 -0
- package/dist/profiles/conductor/patterns/media-player.json +45 -0
- package/dist/profiles/conductor/patterns/menu-button.json +41 -0
- package/dist/profiles/conductor/patterns/pagination.json +39 -0
- package/dist/profiles/conductor/patterns/popover.json +40 -0
- package/dist/profiles/conductor/patterns/progress.json +37 -0
- package/dist/profiles/conductor/patterns/tabs.json +42 -0
- package/dist/profiles/conductor/patterns/tooltip.json +37 -0
- package/dist/profiles/conductor/patterns/validation.json +42 -0
- package/dist/profiles/conductor/semantics.json +289 -0
- package/dist/projection-manifest.json +90 -0
- package/dist/requirements-to-implementation.md +21 -0
- package/lib/build-manifest.mjs +20 -0
- package/lib/build.mjs +31 -0
- package/lib/compile-applicability.mjs +56 -0
- package/lib/digest.mjs +11 -0
- package/lib/index.mjs +4 -0
- package/lib/load.mjs +40 -0
- package/lib/render.mjs +71 -0
- package/lib/sync.mjs +94 -0
- package/lib/validate.mjs +31 -0
- package/package.json +46 -0
- package/profiles/ai-orchestration/profile.json +1 -0
- package/profiles/ai-orchestration/templates/implementation/README.md +3 -0
- package/profiles/ai-orchestration/templates/patterns/README.md +3 -0
- package/profiles/ai-orchestration/templates/review/README.md +3 -0
- package/profiles/ai-orchestration/templates/semantics/README.md +3 -0
- package/profiles/ai-orchestration/templates/testing/README.md +3 -0
- package/profiles/conductor/profile.json +1 -0
- package/profiles/conductor/templates/applicability/README.md +3 -0
- package/profiles/conductor/templates/evidence/README.md +3 -0
- package/profiles/conductor/templates/patterns/README.md +3 -0
- package/profiles/conductor/templates/semantics/README.md +3 -0
- package/schemas/applicability-matrix.schema.json +1 -0
- package/schemas/consumer-config.schema.json +1 -0
- package/schemas/consumer-routes.schema.json +1 -0
- package/schemas/manifest.schema.json +1 -0
- package/schemas/pattern.schema.json +1 -0
- package/schemas/profile.schema.json +1 -0
- package/schemas/projection-manifest.schema.json +1 -0
- package/schemas/record.schema.json +1 -0
- package/schemas/semantic.schema.json +1 -0
- package/src/applicability/facts.yml +1 -0
- package/src/applicability/matrix.yml +20 -0
- package/src/evidence/proof-routing.yml +1 -0
- package/src/foundations/conformance-boundary.yml +1 -0
- package/src/foundations/standards-hierarchy.yml +1 -0
- package/src/foundations/wcag-2.2.yml +1 -0
- package/src/patterns/disclosure/accordion.yml +1 -0
- package/src/patterns/disclosure/disclosure.yml +1 -0
- package/src/patterns/feedback/alert.yml +1 -0
- package/src/patterns/feedback/dynamic-status.yml +1 -0
- package/src/patterns/feedback/progress.yml +1 -0
- package/src/patterns/forms/consequential-submission.yml +1 -0
- package/src/patterns/forms/field.yml +1 -0
- package/src/patterns/forms/validation.yml +1 -0
- package/src/patterns/media/carousel.yml +1 -0
- package/src/patterns/media/image.yml +1 -0
- package/src/patterns/media/media-player.yml +1 -0
- package/src/patterns/navigation/menu-button.yml +1 -0
- package/src/patterns/navigation/pagination.yml +1 -0
- package/src/patterns/navigation/tabs.yml +1 -0
- package/src/patterns/overlays/dialog.yml +1 -0
- package/src/patterns/overlays/popover.yml +1 -0
- package/src/patterns/overlays/tooltip.yml +1 -0
- package/src/patterns/selection/combobox.yml +1 -0
- package/src/patterns/selection/listbox.yml +1 -0
- package/src/policies/enhanced-quality.yml +1 -0
- package/src/policies/target-size.yml +1 -0
- package/src/semantics/accessible-names.yml +1 -0
- package/src/semantics/forms.yml +1 -0
- package/src/semantics/headings-landmarks.yml +1 -0
- package/src/semantics/keyboard-focus.yml +1 -0
- package/src/semantics/live-regions.yml +1 -0
- package/src/semantics/media.yml +1 -0
- package/src/semantics/native-elements.yml +1 -0
- package/src/semantics/roles-states-properties.yml +1 -0
- package/src/semantics/tables.yml +1 -0
- package/src/sources.yml +1 -0
package/lib/load.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
export const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
6
|
+
|
|
7
|
+
export async function readJsonYaml(path) {
|
|
8
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function filesUnder(root) {
|
|
12
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
13
|
+
const nested = await Promise.all(entries.sort((a, b) => a.name.localeCompare(b.name)).map(async (entry) => {
|
|
14
|
+
const path = join(root, entry.name);
|
|
15
|
+
return entry.isDirectory() ? filesUnder(path) : path.endsWith('.yml') ? [path] : [];
|
|
16
|
+
}));
|
|
17
|
+
return nested.flat();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function loadStandards(root = packageRoot) {
|
|
21
|
+
const semantics = [];
|
|
22
|
+
for (const file of await filesUnder(join(root, 'src', 'semantics'))) {
|
|
23
|
+
const parsed = await readJsonYaml(file);
|
|
24
|
+
semantics.push(...(Array.isArray(parsed) ? parsed : [parsed]));
|
|
25
|
+
}
|
|
26
|
+
const patterns = [];
|
|
27
|
+
for (const file of await filesUnder(join(root, 'src', 'patterns'))) patterns.push(await readJsonYaml(file));
|
|
28
|
+
const foundations = [];
|
|
29
|
+
for (const file of await filesUnder(join(root, 'src', 'foundations'))) foundations.push(await readJsonYaml(file));
|
|
30
|
+
const policies = [];
|
|
31
|
+
for (const file of await filesUnder(join(root, 'src', 'policies'))) policies.push(await readJsonYaml(file));
|
|
32
|
+
return {
|
|
33
|
+
package: JSON.parse(await readFile(join(root, 'package.json'), 'utf8')),
|
|
34
|
+
sources: await readJsonYaml(join(root, 'src', 'sources.yml')),
|
|
35
|
+
facts: await readJsonYaml(join(root, 'src', 'applicability', 'facts.yml')),
|
|
36
|
+
matrix: await readJsonYaml(join(root, 'src', 'applicability', 'matrix.yml')),
|
|
37
|
+
evidence: await readJsonYaml(join(root, 'src', 'evidence', 'proof-routing.yml')),
|
|
38
|
+
foundations, semantics, patterns, policies
|
|
39
|
+
};
|
|
40
|
+
}
|
package/lib/render.mjs
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { compileApplicability, resolutionStates, triggerStates } from './compile-applicability.mjs';
|
|
2
|
+
import { digest } from './digest.mjs';
|
|
3
|
+
|
|
4
|
+
export const marker = 'GENERATED by @verndale/accessibility-standards; DO NOT EDIT';
|
|
5
|
+
|
|
6
|
+
function json(payload) {
|
|
7
|
+
return `${JSON.stringify({ _generated: marker, ...payload }, null, 2)}\n`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function markdown(title, records, body) {
|
|
11
|
+
return `<!-- ${marker} -->\n# ${title}\n\n${records.map(body).join('\n\n')}\n`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function semanticSection(item) {
|
|
15
|
+
return `## ${item.id}\n\n${item.requirement}\n\nProof: ${item.proof.join(', ')}.`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function patternSection(item) {
|
|
19
|
+
return `## ${item.id}\n\nRequires: ${item.requires.join(', ')}.\n\nBehavior:\n${item.behavior.map((line) => `- ${line}`).join('\n')}\n\nEvidence: ${item.evidence_routes.join(', ')}.`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildArtifacts(data, profile) {
|
|
23
|
+
const semantics = [...data.semantics].sort(byId);
|
|
24
|
+
const patterns = [...data.patterns].sort(byId);
|
|
25
|
+
const compiled = compileApplicability(data.matrix, data.facts);
|
|
26
|
+
const ids = [...semantics.map(({ id }) => id), ...patterns.map(({ id }) => id), ...data.matrix.rows.map(({ id }) => id)].sort();
|
|
27
|
+
const laneCoverage = Object.fromEntries(Object.keys(data.evidence.proof_kinds).sort().map((lane) => [lane, idsForLane(data, lane)]));
|
|
28
|
+
const artifacts = new Map();
|
|
29
|
+
artifacts.set('semanticsJson', json({ schema_version: 1, semantics }));
|
|
30
|
+
artifacts.set('patternContracts', json({ schema_version: 1, patterns }));
|
|
31
|
+
artifacts.set('coverageManifest', json({ schema_version: 1, projected_ids: ids, lane_coverage: laneCoverage }));
|
|
32
|
+
if (profile === 'conductor') {
|
|
33
|
+
artifacts.set('semanticsMarkdown', markdown('Accessibility semantics', semantics, semanticSection));
|
|
34
|
+
artifacts.set('qualityMarkdown', markdown('Accessibility quality contract', patterns, patternSection));
|
|
35
|
+
artifacts.set('applicabilityMatrix', json({ schema_version: 1, trigger_states: triggerStates, resolution_states: resolutionStates, facts: data.facts.facts, rows: compiled }));
|
|
36
|
+
for (const pattern of patterns) artifacts.set(`pattern:${pattern.id}`, json({ schema_version: 1, pattern }));
|
|
37
|
+
} else if (profile === 'ai-orchestration') {
|
|
38
|
+
for (const semantic of semantics) artifacts.set(`semantic:${semantic.id}`, markdown(semantic.id, [semantic], semanticSection));
|
|
39
|
+
for (const pattern of patterns) artifacts.set(`pattern:${pattern.id}`, markdown(pattern.id, [pattern], patternSection));
|
|
40
|
+
artifacts.set('implementation', markdown('Accessibility implementation contract', [...semantics, ...patterns], (item) => item.kind === 'semantic' ? semanticSection(item) : patternSection(item)));
|
|
41
|
+
artifacts.set('testing', `<!-- ${marker} -->\n# Accessibility testing contract\n\n${Object.entries(laneCoverage).map(([lane, laneIds]) => `## ${lane}\n\n${laneIds.map((id) => `- ${id}`).join('\n')}`).join('\n\n')}\n`);
|
|
42
|
+
artifacts.set('review', `<!-- ${marker} -->\n# Accessibility review contract\n\nReview in this order: source compatibility, semantic dependencies, pattern behavior, implementation outcomes, test lanes, human evidence.\n\n${ids.map((id) => `- ${id}`).join('\n')}\n`);
|
|
43
|
+
} else throw new Error(`Unknown profile: ${profile}`);
|
|
44
|
+
return artifacts;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function defaultPath(key, profile) {
|
|
48
|
+
const fixed = profile === 'conductor' ? {
|
|
49
|
+
semanticsMarkdown: 'a11y-semantics.md', qualityMarkdown: 'accessibility-quality.md', semanticsJson: 'semantics.json', patternContracts: 'pattern-contracts.json', applicabilityMatrix: 'applicability-matrix.json', coverageManifest: 'coverage-manifest.json'
|
|
50
|
+
} : {
|
|
51
|
+
semanticsJson: 'semantics.json', patternContracts: 'pattern-contracts.json', coverageManifest: 'coverage-manifest.json', implementation: 'implementation/accessibility.md', testing: 'testing/accessibility.md', review: 'review/accessibility.md'
|
|
52
|
+
};
|
|
53
|
+
if (fixed[key]) return fixed[key];
|
|
54
|
+
if (key.startsWith('pattern:')) return `patterns/${key.slice(8).replace('pattern.', '')}.${profile === 'conductor' ? 'json' : 'md'}`;
|
|
55
|
+
if (key.startsWith('semantic:')) return `semantics/${key.slice(9).replace('semantics.', '')}.md`;
|
|
56
|
+
throw new Error(`No output path for ${key}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function artifactDigestMap(artifacts) {
|
|
60
|
+
return Object.fromEntries([...artifacts].sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => [key, digest(value)]));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function idsForLane(data, lane) {
|
|
64
|
+
return [...new Set([
|
|
65
|
+
...data.semantics.filter((item) => item.proof.includes(lane)).map(({ id }) => id),
|
|
66
|
+
...data.patterns.filter((item) => item.evidence_routes.includes(lane)).map(({ id }) => id),
|
|
67
|
+
...data.matrix.rows.filter((item) => item.evidence.includes(lane)).flatMap((item) => item.outcomes)
|
|
68
|
+
])].sort();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const byId = (a, b) => a.id.localeCompare(b.id);
|
package/lib/sync.mjs
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { access, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { buildProjectionManifest } from './build-manifest.mjs';
|
|
5
|
+
import { digest } from './digest.mjs';
|
|
6
|
+
import { loadStandards, packageRoot } from './load.mjs';
|
|
7
|
+
import { buildArtifacts, defaultPath, marker } from './render.mjs';
|
|
8
|
+
import { validateStandards } from './validate.mjs';
|
|
9
|
+
|
|
10
|
+
const exists = async (path) => access(path).then(() => true, () => false);
|
|
11
|
+
|
|
12
|
+
export async function projection({ configPath, check = false, ifNeeded = false }) {
|
|
13
|
+
const absoluteConfig = resolve(configPath);
|
|
14
|
+
const consumerRoot = dirname(absoluteConfig);
|
|
15
|
+
const config = JSON.parse(await readFile(absoluteConfig, 'utf8'));
|
|
16
|
+
const expectedPackage = '@verndale/accessibility-standards@1.0.0';
|
|
17
|
+
if (config.package !== expectedPackage) throw new Error(`Exact package pin required: ${expectedPackage}`);
|
|
18
|
+
const routesPath = resolve(consumerRoot, config.routes);
|
|
19
|
+
const routes = JSON.parse(await readFile(routesPath, 'utf8'));
|
|
20
|
+
const data = await loadStandards(packageRoot);
|
|
21
|
+
validateStandards(data);
|
|
22
|
+
const outputRoot = resolve(consumerRoot, config.outputRoot ?? '.');
|
|
23
|
+
const artifacts = buildArtifacts(data, config.profile);
|
|
24
|
+
const sourceKey = 'source';
|
|
25
|
+
const sourcePath = resolveOutput(outputRoot, routes.outputs[sourceKey] ?? 'accessibility.source.json');
|
|
26
|
+
const manifest = buildProjectionManifest({ data, profile: config.profile, config, routes, artifacts });
|
|
27
|
+
artifacts.set(sourceKey, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
28
|
+
const paths = new Map([...artifacts].map(([key]) => [key, resolveOutput(outputRoot, routeFor(key, routes, config.profile))]));
|
|
29
|
+
if (check) return checkProjection(artifacts, paths);
|
|
30
|
+
if (ifNeeded && await projectionMatches(artifacts, paths)) return { changed: false, files: paths.size };
|
|
31
|
+
await protectManagedFiles(paths, sourcePath);
|
|
32
|
+
const stage = join(tmpdir(), `a11y-sync-${process.pid}-${Date.now()}`);
|
|
33
|
+
await mkdir(stage, { recursive: true });
|
|
34
|
+
try {
|
|
35
|
+
let index = 0;
|
|
36
|
+
const staged = [];
|
|
37
|
+
for (const [key, content] of artifacts) {
|
|
38
|
+
const temp = join(stage, String(index++));
|
|
39
|
+
await writeFile(temp, content);
|
|
40
|
+
staged.push([temp, paths.get(key)]);
|
|
41
|
+
}
|
|
42
|
+
for (const [temp, target] of staged) {
|
|
43
|
+
await mkdir(dirname(target), { recursive: true });
|
|
44
|
+
await rename(temp, target);
|
|
45
|
+
}
|
|
46
|
+
} finally { await rm(stage, { recursive: true, force: true }); }
|
|
47
|
+
await checkProjection(artifacts, paths);
|
|
48
|
+
return { changed: true, files: paths.size };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function protectManagedFiles(paths, sourcePath) {
|
|
52
|
+
let prior = null;
|
|
53
|
+
if (await exists(sourcePath)) {
|
|
54
|
+
const raw = await readFile(sourcePath, 'utf8');
|
|
55
|
+
if (!raw.includes(marker)) throw new Error(`Refusing to overwrite unmarked provenance: ${sourcePath}`);
|
|
56
|
+
prior = JSON.parse(raw);
|
|
57
|
+
}
|
|
58
|
+
for (const [key, path] of paths) {
|
|
59
|
+
if (!await exists(path)) continue;
|
|
60
|
+
const raw = await readFile(path, 'utf8');
|
|
61
|
+
if (!raw.includes(marker)) throw new Error(`Refusing to overwrite unmarked generated target: ${path}`);
|
|
62
|
+
const expected = prior?.output_digests?.[key];
|
|
63
|
+
if (expected && digest(raw) !== expected) throw new Error(`Refusing to overwrite locally modified generated target: ${path}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function projectionMatches(artifacts, paths) {
|
|
68
|
+
try { await checkProjection(artifacts, paths); return true; } catch { return false; }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function checkProjection(artifacts, paths) {
|
|
72
|
+
const errors = [];
|
|
73
|
+
for (const [key, content] of artifacts) {
|
|
74
|
+
const path = paths.get(key);
|
|
75
|
+
if (!await exists(path)) { errors.push(`Missing generated output: ${path}`); continue; }
|
|
76
|
+
const actual = await readFile(path, 'utf8');
|
|
77
|
+
if (!actual.includes(marker)) errors.push(`Missing generated marker: ${path}`);
|
|
78
|
+
if (actual !== content) errors.push(`Stale generated output: ${path}`);
|
|
79
|
+
}
|
|
80
|
+
if (errors.length) throw new Error(errors.join('\n'));
|
|
81
|
+
return { changed: false, files: paths.size };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function routeFor(key, routes, profile) {
|
|
85
|
+
if (routes.outputs[key]) return routes.outputs[key];
|
|
86
|
+
if (key.startsWith('pattern:') && routes.outputs.patterns) return join(routes.outputs.patterns, defaultPath(key, profile).split('/').pop());
|
|
87
|
+
if (key.startsWith('semantic:') && routes.outputs.semantics) return join(routes.outputs.semantics, defaultPath(key, profile).split('/').pop());
|
|
88
|
+
return defaultPath(key, profile);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function resolveOutput(root, path) {
|
|
92
|
+
const target = isAbsolute(path) ? path : resolve(root, path);
|
|
93
|
+
return target;
|
|
94
|
+
}
|
package/lib/validate.mjs
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { compileApplicability } from './compile-applicability.mjs';
|
|
2
|
+
|
|
3
|
+
export function validateStandards(data) {
|
|
4
|
+
const errors = [];
|
|
5
|
+
const records = [...data.foundations, ...data.semantics, ...data.patterns, ...data.policies];
|
|
6
|
+
const ids = new Set();
|
|
7
|
+
for (const record of records) {
|
|
8
|
+
if (!record.id || !record.version || !record.title) errors.push(`Malformed record: ${JSON.stringify(record)}`);
|
|
9
|
+
if (ids.has(record.id)) errors.push(`Duplicate authority: ${record.id}`);
|
|
10
|
+
ids.add(record.id);
|
|
11
|
+
}
|
|
12
|
+
const semanticIds = new Set(data.semantics.map(({ id }) => id));
|
|
13
|
+
for (const pattern of data.patterns) {
|
|
14
|
+
if (!pattern.requires?.length) errors.push(`Orphan pattern: ${pattern.id}`);
|
|
15
|
+
for (const dependency of pattern.requires ?? []) if (!semanticIds.has(dependency)) errors.push(`${pattern.id} has missing semantic dependency ${dependency}`);
|
|
16
|
+
if (!pattern.activation || !pattern.scope || !pattern.behavior?.length || !pattern.functional_spec_bindings?.length || !pattern.implementation_outcomes?.length || !pattern.evidence_routes?.length) errors.push(`Incomplete pattern contract: ${pattern.id}`);
|
|
17
|
+
}
|
|
18
|
+
const proofKinds = new Set(Object.keys(data.evidence.proof_kinds));
|
|
19
|
+
for (const semantic of data.semantics) for (const route of semantic.proof ?? []) if (!proofKinds.has(route)) errors.push(`${semantic.id} uses unsupported proof route ${route}`);
|
|
20
|
+
for (const pattern of data.patterns) for (const route of pattern.evidence_routes ?? []) if (!proofKinds.has(route)) errors.push(`${pattern.id} uses unsupported proof route ${route}`);
|
|
21
|
+
for (const row of data.matrix.rows) {
|
|
22
|
+
try { compileApplicability({ rows: [row] }, data.facts); } catch (error) { errors.push(`${row.id}: ${error.message}`); }
|
|
23
|
+
for (const outcome of row.outcomes) if (!ids.has(outcome)) errors.push(`${row.id} references unknown outcome ${outcome}`);
|
|
24
|
+
for (const route of row.evidence) if (!proofKinds.has(route)) errors.push(`${row.id} uses unsupported proof route ${route}`);
|
|
25
|
+
}
|
|
26
|
+
const lanes = new Set([...data.semantics.flatMap((item) => item.proof), ...data.patterns.flatMap((item) => item.evidence_routes), ...data.matrix.rows.flatMap((item) => item.evidence)]);
|
|
27
|
+
for (const lane of proofKinds) if (!lanes.has(lane)) errors.push(`Dead evidence lane: ${lane}`);
|
|
28
|
+
if (data.matrix.rows.length !== 18) errors.push(`Expected 18 applicability rows, found ${data.matrix.rows.length}`);
|
|
29
|
+
if (errors.length) throw new Error(errors.join('\n'));
|
|
30
|
+
return { records: records.length, semantics: data.semantics.length, patterns: data.patterns.length, applicability: data.matrix.rows.length };
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@verndale/accessibility-standards",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Deterministic accessibility semantics, patterns, applicability, and consumer projections",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=24.14.0"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"accessibility-standards": "bin/accessibility-standards.mjs"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"lib",
|
|
17
|
+
"schemas",
|
|
18
|
+
"src",
|
|
19
|
+
"profiles",
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md",
|
|
22
|
+
"OPERATING.md"
|
|
23
|
+
],
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@semantic-release/changelog": "6.0.3",
|
|
26
|
+
"@semantic-release/git": "10.0.1",
|
|
27
|
+
"@semantic-release/github": "12.0.8",
|
|
28
|
+
"@semantic-release/npm": "13.1.5",
|
|
29
|
+
"@verndale/ai-commit": "2.7.0",
|
|
30
|
+
"@verndale/ai-pr": "1.3.5",
|
|
31
|
+
"semantic-release": "25.0.3"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"provenance": true
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "node bin/accessibility-standards.mjs build",
|
|
39
|
+
"check": "node bin/accessibility-standards.mjs validate",
|
|
40
|
+
"test": "node --test tests/unit/*.test.mjs",
|
|
41
|
+
"verify:ci": "pnpm check && pnpm test && pnpm build && git diff --exit-code -- . ':!dist'",
|
|
42
|
+
"graph:build": "node scripts/wiki/build-graph.cjs",
|
|
43
|
+
"graph:check": "node scripts/wiki/build-graph.cjs --check",
|
|
44
|
+
"wiki:check": "node scripts/wiki/check.cjs"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"name":"ai-orchestration","version":"1.0.0","channels":["semantics","patterns","implementation","testing","review"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"name":"conductor","version":"1.0.0","channels":["semantics","patterns","applicability","evidence"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"applicability-matrix.schema.json","type":"object","required":["version","rows"],"properties":{"version":{"type":"integer","const":1},"rows":{"type":"array","minItems":1,"items":{"type":"object","required":["id","when","outcomes","evidence"],"properties":{"id":{"type":"string","pattern":"^applicability\\."},"when":{"type":"object"},"outcomes":{"type":"array","items":{"type":"string"},"minItems":1},"evidence":{"type":"array","items":{"enum":["unit","axe","e2e","human"]},"minItems":1}}}}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"consumer-config.schema.json","type":"object","required":["package","profile","routes"],"properties":{"package":{"const":"@verndale/accessibility-standards@1.0.0"},"profile":{"enum":["conductor","ai-orchestration"]},"routes":{"type":"string"},"outputRoot":{"type":"string"}},"additionalProperties":false}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"consumer-routes.schema.json","type":"object","required":["version","outputs"],"properties":{"version":{"const":1},"outputs":{"type":"object","additionalProperties":{"type":"string"}}},"additionalProperties":false}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"manifest.schema.json","type":"object","required":["package","schema_version","records"],"properties":{"package":{"type":"string"},"schema_version":{"type":"integer"},"records":{"type":"array","items":{"type":"object","required":["id","digest"]}}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"pattern.schema.json","allOf":[{"$ref":"record.schema.json"},{"type":"object","required":["kind","requires","activation","scope","behavior","product_decisions","functional_spec_bindings","implementation_outcomes","evidence_routes"],"properties":{"kind":{"const":"pattern"},"requires":{"type":"array","items":{"type":"string","pattern":"^semantics\\."},"minItems":1,"uniqueItems":true}}}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"profile.schema.json","type":"object","required":["name","version","channels"],"properties":{"name":{"enum":["conductor","ai-orchestration"]},"version":{"type":"string"},"channels":{"type":"array","items":{"type":"string"},"uniqueItems":true}},"additionalProperties":false}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"projection-manifest.schema.json","type":"object","required":["package","profile","digests","projected_ids","lane_coverage"],"properties":{"package":{"type":"string"},"profile":{"type":"string"},"digests":{"type":"object"},"projected_ids":{"type":"array","items":{"type":"string"}},"lane_coverage":{"type":"object"}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"record.schema.json","type":"object","required":["id","version","title"],"properties":{"id":{"type":"string","pattern":"^[a-z][a-z0-9.-]+$"},"version":{"type":"string","pattern":"^\\d+\\.\\d+\\.\\d+$"},"title":{"type":"string","minLength":1}},"additionalProperties":true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"semantic.schema.json","allOf":[{"$ref":"record.schema.json"},{"type":"object","required":["kind","requirement","proof"],"properties":{"kind":{"const":"semantic"},"requirement":{"type":"string"},"proof":{"type":"array","items":{"enum":["unit","axe","e2e","human"]},"minItems":1,"uniqueItems":true}}}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":1,"facts":{"artifact.page_context":{"type":"boolean"},"content.has_non_text":{"type":"boolean"},"content.has_media":{"type":"boolean"},"component.has_form_fields":{"type":"boolean"},"component.submission_consequence":{"type":"array","items":"string"},"component.has_dynamic_status":{"type":"boolean"},"component.pattern":{"type":"string"},"interaction.has_motion_or_timing":{"type":"boolean"},"interaction.has_complex_pointer":{"type":"boolean"},"interaction.has_character_shortcuts":{"type":"boolean"},"content.uses_orientation_or_sensory_cues":{"type":"boolean"},"interaction.has_pointer_targets":{"type":"boolean"},"interaction.can_obscure_focus":{"type":"boolean"},"flow.has_authentication":{"type":"boolean"},"flow.repeats_entry":{"type":"boolean"},"artifact.has_help":{"type":"boolean"},"content.has_locale_or_bidi":{"type":"boolean"},"component.has_data_table_or_grid":{"type":"boolean"}}}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{"version":1,"rows":[
|
|
2
|
+
{"id":"applicability.page-context","when":{"equals":{"fact":"artifact.page_context","value":true}},"outcomes":["semantics.headings","semantics.landmarks"],"evidence":["axe","human"]},
|
|
3
|
+
{"id":"applicability.non-text-content","when":{"equals":{"fact":"content.has_non_text","value":true}},"outcomes":["pattern.image"],"evidence":["axe","human"]},
|
|
4
|
+
{"id":"applicability.media","when":{"equals":{"fact":"content.has_media","value":true}},"outcomes":["pattern.media-player"],"evidence":["e2e","human"]},
|
|
5
|
+
{"id":"applicability.forms","when":{"equals":{"fact":"component.has_form_fields","value":true}},"outcomes":["pattern.field","pattern.validation"],"evidence":["unit","axe","e2e"]},
|
|
6
|
+
{"id":"applicability.consequential-submission","when":{"contains":{"fact":"component.submission_consequence","value":"consequential"}},"outcomes":["pattern.consequential-submission"],"evidence":["e2e","human"]},
|
|
7
|
+
{"id":"applicability.dynamic-status","when":{"equals":{"fact":"component.has_dynamic_status","value":true}},"outcomes":["pattern.dynamic-status"],"evidence":["unit","e2e","human"]},
|
|
8
|
+
{"id":"applicability.overlays","when":{"contains":{"fact":"component.pattern","value":["dialog","popover","tooltip"]}},"outcomes":["semantics.keyboard.escape","semantics.focus.return"],"evidence":["e2e"]},
|
|
9
|
+
{"id":"applicability.motion-timing","when":{"equals":{"fact":"interaction.has_motion_or_timing","value":true}},"outcomes":["semantics.motion-control"],"evidence":["e2e","human"]},
|
|
10
|
+
{"id":"applicability.complex-pointer","when":{"equals":{"fact":"interaction.has_complex_pointer","value":true}},"outcomes":["semantics.keyboard"],"evidence":["e2e","human"]},
|
|
11
|
+
{"id":"applicability.character-shortcuts","when":{"equals":{"fact":"interaction.has_character_shortcuts","value":true}},"outcomes":["semantics.keyboard"],"evidence":["unit","e2e"]},
|
|
12
|
+
{"id":"applicability.orientation-sensory","when":{"equals":{"fact":"content.uses_orientation_or_sensory_cues","value":true}},"outcomes":["semantics.name-role-value"],"evidence":["human"]},
|
|
13
|
+
{"id":"applicability.target-size","when":{"equals":{"fact":"interaction.has_pointer_targets","value":true}},"outcomes":["semantics.target-size"],"evidence":["e2e","human"]},
|
|
14
|
+
{"id":"applicability.focus-obstruction","when":{"equals":{"fact":"interaction.can_obscure_focus","value":true}},"outcomes":["semantics.focus.visible"],"evidence":["e2e","human"]},
|
|
15
|
+
{"id":"applicability.authentication-captcha","when":{"equals":{"fact":"flow.has_authentication","value":true}},"outcomes":["semantics.form-label","semantics.form-error"],"evidence":["axe","e2e","human"]},
|
|
16
|
+
{"id":"applicability.redundant-entry","when":{"equals":{"fact":"flow.repeats_entry","value":true}},"outcomes":["semantics.form-label"],"evidence":["e2e","human"]},
|
|
17
|
+
{"id":"applicability.consistent-help","when":{"equals":{"fact":"artifact.has_help","value":true}},"outcomes":["semantics.landmarks"],"evidence":["human"]},
|
|
18
|
+
{"id":"applicability.locale-bidi","when":{"equals":{"fact":"content.has_locale_or_bidi","value":true}},"outcomes":["semantics.native-elements"],"evidence":["unit","human"]},
|
|
19
|
+
{"id":"applicability.data-table-grid","when":{"equals":{"fact":"component.has_data_table_or_grid","value":true}},"outcomes":["semantics.data-table"],"evidence":["axe","e2e","human"]}
|
|
20
|
+
]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":1,"proof_kinds":{"unit":{"purpose":"semantic state and deterministic resolver behavior","required_capability":"unit"},"axe":{"purpose":"automated rule evidence","required_capability":"axe"},"e2e":{"purpose":"keyboard, focus, and integrated behavior","required_capability":"e2e"},"human":{"purpose":"judgment, assistive technology, content, and visual evidence","required_capability":"human"}},"rule":"Tool availability changes the evidence route, never requirement applicability. A missing required capability is a blocker."}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"foundation.conformance-boundary","version":"1.0.0","title":"Conformance boundary","kind":"foundation","requirement":"Automated checks provide evidence but do not establish conformance; every applicable outcome needs its declared proof routes."}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"foundation.standards-hierarchy","version":"1.0.0","title":"Standards hierarchy","kind":"foundation","requirement":"Prefer native platform semantics; apply WCAG conformance, ARIA only where needed, APG interaction guidance, then non-conflicting product policy."}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"foundation.wcag-2.2","version":"1.0.0","title":"WCAG 2.2 AA baseline","kind":"foundation","requirement":"Meet WCAG 2.2 Level A and AA. Target size minimum is 24 by 24 CSS pixels or an allowed exception; 44 by 44 is enhanced guidance only."}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.accordion","version":"1.0.0","title":"Accordion","kind":"pattern","requires":["semantics.native-elements","semantics.accessible-name","semantics.expanded-state","semantics.keyboard","semantics.focus.visible"],"activation":{"fact":"component.pattern","equals":"accordion"},"scope":"Grouped disclosure sections","behavior":["Toggle only the controlled panel","Preserve DOM and focus order"],"product_decisions":["single_or_multiple_open"],"functional_spec_bindings":["behavior","states","keyboard"],"implementation_outcomes":["accordion-control","accordion-panel"],"evidence_routes":["unit","axe","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.disclosure","version":"1.0.0","title":"Disclosure","kind":"pattern","requires":["semantics.native-elements","semantics.accessible-name","semantics.expanded-state","semantics.keyboard"],"activation":{"fact":"component.pattern","equals":"disclosure"},"scope":"Show and hide supplementary content","behavior":["Control toggles one region"],"product_decisions":["initial_state"],"functional_spec_bindings":["behavior","states"],"implementation_outcomes":["disclosure-control"],"evidence_routes":["unit","axe","e2e"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.alert","version":"1.0.0","title":"Alert","kind":"pattern","requires":["semantics.alert"],"activation":{"fact":"component.pattern","equals":"alert"},"scope":"Urgent message","behavior":["Announce once without moving focus"],"product_decisions":["urgency"],"functional_spec_bindings":["messaging"],"implementation_outcomes":["alert-announcement"],"evidence_routes":["unit","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.dynamic-status","version":"1.0.0","title":"Dynamic status","kind":"pattern","requires":["semantics.live-status"],"activation":{"fact":"component.has_dynamic_status","equals":true},"scope":"Asynchronous status message","behavior":["Announce meaningful changes once"],"product_decisions":["announcement_copy"],"functional_spec_bindings":["messaging","states"],"implementation_outcomes":["status-live-region"],"evidence_routes":["unit","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.progress","version":"1.0.0","title":"Progress","kind":"pattern","requires":["semantics.name-role-value","semantics.live-status"],"activation":{"fact":"component.pattern","equals":"progress"},"scope":"Determinate or indeterminate progress","behavior":["Expose value and completion changes without announcement noise"],"product_decisions":["determinate"],"functional_spec_bindings":["states","messaging"],"implementation_outcomes":["progress-value"],"evidence_routes":["unit","axe","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.consequential-submission","version":"1.0.0","title":"Consequential submission","kind":"pattern","requires":["semantics.consequential-action","semantics.form-error","semantics.keyboard"],"activation":{"fact":"component.submission_consequence","contains":"consequential"},"scope":"Legal, financial, destructive, or irreversible submission","behavior":["Support review, correction, confirmation, or reversal"],"product_decisions":["safeguard","reversal_window"],"functional_spec_bindings":["submission","confirmation"],"implementation_outcomes":["submission-safeguard"],"evidence_routes":["e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.field","version":"1.0.0","title":"Form field","kind":"pattern","requires":["semantics.form-label","semantics.form-required","semantics.focus.visible"],"activation":{"fact":"component.has_form_fields","equals":true},"scope":"Labeled form input","behavior":["Expose label, instructions, and requiredness before interaction"],"product_decisions":["required","instructions"],"functional_spec_bindings":["fields","requiredness"],"implementation_outcomes":["field-associations"],"evidence_routes":["unit","axe","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.validation","version":"1.0.0","title":"Form validation","kind":"pattern","requires":["semantics.form-error","semantics.live-status","semantics.focus.initial"],"activation":{"fact":"component.has_validation","equals":true},"scope":"Client or server validation","behavior":["Identify, associate, summarize, and focus errors deterministically"],"product_decisions":["validation_timing","error_summary"],"functional_spec_bindings":["validation","messaging","focus"],"implementation_outcomes":["validation-associations","error-summary"],"evidence_routes":["unit","axe","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.carousel","version":"1.0.0","title":"Carousel","kind":"pattern","requires":["semantics.accessible-name","semantics.keyboard","semantics.focus.visible","semantics.motion-control","semantics.live-status"],"activation":{"fact":"component.pattern","equals":"carousel"},"scope":"Rotating content set","behavior":["Pause rotation on interaction","Provide named controls and current position"],"product_decisions":["auto_rotation","looping"],"functional_spec_bindings":["behavior","states","controls"],"implementation_outcomes":["carousel-controls","carousel-rotation"],"evidence_routes":["unit","axe","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.image","version":"1.0.0","title":"Image","kind":"pattern","requires":["semantics.non-text-alternative"],"activation":{"fact":"content.has_non_text","equals":true},"scope":"Informative, functional, complex, or decorative image","behavior":["Alternative follows image purpose"],"product_decisions":["purpose","long_description"],"functional_spec_bindings":["content"],"implementation_outcomes":["image-alternative"],"evidence_routes":["axe","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.media-player","version":"1.0.0","title":"Media player","kind":"pattern","requires":["semantics.media-alternatives","semantics.accessible-name","semantics.keyboard","semantics.focus.visible","semantics.motion-control"],"activation":{"fact":"content.has_media","equals":true},"scope":"Audio or video playback","behavior":["Provide keyboard-operable named controls and required alternatives"],"product_decisions":["captions","transcript","audio_description","autoplay"],"functional_spec_bindings":["content","controls","behavior"],"implementation_outcomes":["media-alternatives","media-controls"],"evidence_routes":["axe","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.menu-button","version":"1.0.0","title":"Menu button","kind":"pattern","requires":["semantics.accessible-name","semantics.expanded-state","semantics.keyboard","semantics.focus.initial","semantics.focus.return"],"activation":{"fact":"component.pattern","equals":"menu-button"},"scope":"Button opening a command menu","behavior":["Open and navigate menu by keyboard"],"product_decisions":["focus_strategy"],"functional_spec_bindings":["behavior","keyboard","focus"],"implementation_outcomes":["menu-button-keyboard"],"evidence_routes":["unit","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.pagination","version":"1.0.0","title":"Pagination","kind":"pattern","requires":["semantics.native-elements","semantics.accessible-name","semantics.landmarks","semantics.focus.visible"],"activation":{"fact":"component.pattern","equals":"pagination"},"scope":"Paged result navigation","behavior":["Expose current page and named navigation"],"product_decisions":["url_behavior"],"functional_spec_bindings":["states","navigation"],"implementation_outcomes":["pagination-current-page"],"evidence_routes":["unit","axe","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.tabs","version":"1.0.0","title":"Tabs","kind":"pattern","requires":["semantics.accessible-name","semantics.selected-state","semantics.keyboard","semantics.focus.visible"],"activation":{"fact":"component.pattern","equals":"tabs"},"scope":"Tabbed interfaces","behavior":["Arrow keys move between tabs","Selection and activation follow the documented model"],"product_decisions":["automatic_or_manual_activation"],"functional_spec_bindings":["behavior","states","keyboard"],"implementation_outcomes":["tabs-keyboard-model"],"evidence_routes":["unit","axe","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.dialog","version":"1.0.0","title":"Dialog","kind":"pattern","requires":["semantics.native-dialog","semantics.accessible-name","semantics.keyboard.escape","semantics.focus.initial","semantics.focus.return"],"activation":{"fact":"component.pattern","equals":"dialog"},"scope":"Modal and non-modal dialogs","behavior":["Manage initial, contained, and return focus","Dismiss topmost dialog with Escape"],"product_decisions":["modal","initial_focus","dismissal"],"functional_spec_bindings":["behavior","states","focus","keyboard"],"implementation_outcomes":["dialog-semantics","dialog-focus-lifecycle"],"evidence_routes":["unit","axe","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.popover","version":"1.0.0","title":"Popover","kind":"pattern","requires":["semantics.native-elements","semantics.accessible-name","semantics.keyboard.escape","semantics.focus.return"],"activation":{"fact":"component.pattern","equals":"popover"},"scope":"Non-modal transient surface","behavior":["Dismiss without trapping focus"],"product_decisions":["dismissal","focus_entry"],"functional_spec_bindings":["behavior","focus"],"implementation_outcomes":["popover-dismissal"],"evidence_routes":["unit","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.tooltip","version":"1.0.0","title":"Tooltip","kind":"pattern","requires":["semantics.accessible-name","semantics.keyboard.escape"],"activation":{"fact":"component.pattern","equals":"tooltip"},"scope":"Supplementary description","behavior":["Available on hover and focus","Never contains interactive content"],"product_decisions":["delay"],"functional_spec_bindings":["behavior"],"implementation_outcomes":["tooltip-description"],"evidence_routes":["unit","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.combobox","version":"1.0.0","title":"Combobox","kind":"pattern","requires":["semantics.accessible-name","semantics.expanded-state","semantics.selected-state","semantics.keyboard","semantics.live-status"],"activation":{"fact":"component.pattern","equals":"combobox"},"scope":"Input with selectable popup","behavior":["Keyboard controls popup, option, and input independently"],"product_decisions":["editable","autocomplete","selection_commit"],"functional_spec_bindings":["behavior","states","keyboard"],"implementation_outcomes":["combobox-state-machine"],"evidence_routes":["unit","axe","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"pattern.listbox","version":"1.0.0","title":"Listbox","kind":"pattern","requires":["semantics.accessible-name","semantics.selected-state","semantics.keyboard","semantics.focus.visible"],"activation":{"fact":"component.pattern","equals":"listbox"},"scope":"Single or multi-select option list","behavior":["Arrow keys move active option","Selection follows declared model"],"product_decisions":["single_or_multi","selection_follows_focus"],"functional_spec_bindings":["behavior","states","keyboard"],"implementation_outcomes":["listbox-keyboard-model"],"evidence_routes":["unit","axe","e2e","human"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"policy.enhanced-quality","version":"1.0.0","title":"Enhanced quality","kind":"policy","requirement":"Enhanced guidance is advisory and cannot be represented as a conformance failure unless the consumer explicitly adopts it as product policy."}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"policy.target-size","version":"1.0.0","title":"Target size policy","kind":"policy","requirement":"Enforce WCAG 2.5.8 at 24 by 24 CSS pixels or a documented exception; report 44 by 44 as enhanced guidance, never as the AA minimum."}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"semantics.accessible-name","version":"1.0.0","title":"Accessible name","kind":"semantic","requirement":"Every interactive or meaning-bearing control has a concise programmatic name derived from visible text when available.","proof":["unit","axe","human"]},{"id":"semantics.name-role-value","version":"1.0.0","title":"Name role value","kind":"semantic","requirement":"Expose the correct accessible name, role, value, and state through the platform accessibility API.","proof":["unit","axe","human"]}]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"semantics.form-label","version":"1.0.0","title":"Form label","kind":"semantic","requirement":"Every form control has a persistent programmatically associated label.","proof":["unit","axe"]},{"id":"semantics.form-error","version":"1.0.0","title":"Form error","kind":"semantic","requirement":"Identify errors in text, associate them with fields, and provide correction guidance.","proof":["unit","axe","e2e","human"]},{"id":"semantics.form-required","version":"1.0.0","title":"Required field","kind":"semantic","requirement":"Requiredness is communicated visually and programmatically before submission.","proof":["unit","axe"]},{"id":"semantics.consequential-action","version":"1.0.0","title":"Consequential action","kind":"semantic","requirement":"Legal, financial, destructive, or data-submitting actions support review, confirmation, or reversal as required.","proof":["e2e","human"]}]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"semantics.headings","version":"1.0.0","title":"Heading structure","kind":"semantic","requirement":"Headings describe document structure without choosing levels for visual appearance.","proof":["axe","human"]},{"id":"semantics.landmarks","version":"1.0.0","title":"Landmarks","kind":"semantic","requirement":"Use native landmarks and name repeated landmarks only when a name is required to distinguish them.","proof":["axe","human"]}]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"semantics.keyboard","version":"1.0.0","title":"Keyboard operation","kind":"semantic","requirement":"All functionality is operable with a keyboard without traps.","proof":["e2e","human"]},{"id":"semantics.keyboard.escape","version":"1.0.0","title":"Escape dismissal","kind":"semantic","requirement":"Escape closes the topmost dismissible overlay without activating its trigger.","proof":["unit","e2e"]},{"id":"semantics.focus.visible","version":"1.0.0","title":"Visible focus","kind":"semantic","requirement":"Keyboard focus is visible, sufficiently contrasted, and not fully obscured.","proof":["e2e","human"]},{"id":"semantics.focus.initial","version":"1.0.0","title":"Initial focus","kind":"semantic","requirement":"On activation, focus moves to the deterministic target appropriate to the pattern.","proof":["unit","e2e"]},{"id":"semantics.focus.return","version":"1.0.0","title":"Focus return","kind":"semantic","requirement":"On dismissal, focus returns to the invoking control or a documented logical successor.","proof":["unit","e2e"]},{"id":"semantics.target-size","version":"1.0.0","title":"Target size","kind":"semantic","requirement":"Pointer targets meet 24 by 24 CSS pixels or a WCAG 2.5.8 exception; 44 by 44 is an enhanced recommendation.","proof":["e2e","human"]}]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"semantics.live-status","version":"1.0.0","title":"Status announcements","kind":"semantic","requirement":"Important asynchronous status updates are exposed through a correctly scoped, non-duplicative live region.","proof":["unit","e2e","human"]},{"id":"semantics.alert","version":"1.0.0","title":"Urgent alert","kind":"semantic","requirement":"Use alert semantics only for urgent, time-sensitive messages that require immediate announcement.","proof":["unit","human"]}]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"semantics.non-text-alternative","version":"1.0.0","title":"Non-text alternative","kind":"semantic","requirement":"Meaningful non-text content has an equivalent alternative and decorative content is ignored by assistive technology.","proof":["axe","human"]},{"id":"semantics.media-alternatives","version":"1.0.0","title":"Media alternatives","kind":"semantic","requirement":"Prerecorded media provides applicable captions, transcripts, and audio description.","proof":["human"]},{"id":"semantics.motion-control","version":"1.0.0","title":"Motion control","kind":"semantic","requirement":"Respect reduced-motion preferences and provide controls for moving or time-limited content where required.","proof":["e2e","human"]}]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"semantics.native-elements","version":"1.0.0","title":"Native elements","kind":"semantic","requirement":"Use the native HTML element whose semantics and behavior match the control before adding ARIA.","proof":["axe","human"]},{"id":"semantics.native-dialog","version":"1.0.0","title":"Native dialog semantics","kind":"semantic","requirement":"Use dialog semantics with a programmatic name and modal state only when interaction is modal.","proof":["axe","e2e","human"]}]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"semantics.roles-states-properties","version":"1.0.0","title":"Roles states and properties","kind":"semantic","requirement":"Apply only valid ARIA roles, states, and properties and keep exposed state synchronized with rendered behavior.","proof":["unit","axe","e2e"]},{"id":"semantics.expanded-state","version":"1.0.0","title":"Expanded state","kind":"semantic","requirement":"Expandable controls expose aria-expanded and identify the controlled region when needed.","proof":["unit","axe","e2e"]},{"id":"semantics.selected-state","version":"1.0.0","title":"Selected state","kind":"semantic","requirement":"Selection widgets expose current selection with the state required by their role.","proof":["unit","e2e"]}]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"semantics.data-table","version":"1.0.0","title":"Data table","kind":"semantic","requirement":"Tabular relationships use table markup with associated headers; interactive grids implement their full keyboard model.","proof":["axe","e2e","human"]}]
|
package/src/sources.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":1,"authorities":[{"id":"wcag-2.2","url":"https://www.w3.org/TR/WCAG22/","normative":true},{"id":"wai-aria-1.2","url":"https://www.w3.org/TR/wai-aria-1.2/","normative":true},{"id":"aria-apg","url":"https://www.w3.org/WAI/ARIA/apg/","normative":false}],"precedence":["platform-semantics","wcag-2.2","wai-aria-1.2","aria-apg","product-policy"]}
|