ultra11y 2.22.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/README.md +223 -0
- package/docker/Dockerfile +7 -0
- package/docker/package.json +9 -0
- package/docker/runner.mjs +23 -0
- package/package.json +73 -0
- package/scripts/bench.mjs +151 -0
- package/scripts/build-act-corpus.mjs +143 -0
- package/scripts/build-adjudication.mjs +503 -0
- package/scripts/build-pack-rgaa.mjs +397 -0
- package/scripts/build-standards.mjs +548 -0
- package/scripts/copy-bundle.mjs +39 -0
- package/scripts/import-pack.mjs +107 -0
- package/scripts/sync-engine.mjs +84 -0
- package/scripts/sync-version.mjs +43 -0
- package/scripts/ultra11y.mjs +52739 -0
- package/scripts/vendor/act-testcases.json +12297 -0
- package/scripts/vendor/rgaa/SOURCE.json +8 -0
- package/scripts/vendor/rgaa/criteres.json +4144 -0
- package/scripts/vendor/rgaa/glossaire.json +480 -0
- package/scripts/vendor/wcag-2.2-fr.json +82 -0
- package/scripts/vendor/wcag-2.2-sc-universe.json +950 -0
- package/scripts/vendor/wcag-2.2-sc.json +574 -0
- package/scripts/verify-rgaa-source.mjs +126 -0
- package/scripts/verify-skill-bundle.mjs +91 -0
- package/skills/review-a11y/SKILL.md +123 -0
- package/skills/review-a11y/scripts/ultra11y.mjs +52739 -0
- package/skills/ultra11y/SKILL.md +292 -0
- package/skills/ultra11y/references/act.md +163 -0
- package/skills/ultra11y/references/adjudication.md +599 -0
- package/skills/ultra11y/references/audit.md +100 -0
- package/skills/ultra11y/references/authoring.md +37 -0
- package/skills/ultra11y/references/automation.md +113 -0
- package/skills/ultra11y/references/correction.md +41 -0
- package/skills/ultra11y/references/criteria.md +129 -0
- package/skills/ultra11y/references/cross-file.md +82 -0
- package/skills/ultra11y/references/dynamic.md +152 -0
- package/skills/ultra11y/references/false-positives.md +81 -0
- package/skills/ultra11y/references/fix.md +46 -0
- package/skills/ultra11y/references/focus-and-logic.md +64 -0
- package/skills/ultra11y/references/forbidden-patterns.md +61 -0
- package/skills/ultra11y/references/forms-and-errors.md +147 -0
- package/skills/ultra11y/references/guidance.md +55 -0
- package/skills/ultra11y/references/judgment.md +90 -0
- package/skills/ultra11y/references/media-and-motion.md +116 -0
- package/skills/ultra11y/references/methodology.md +72 -0
- package/skills/ultra11y/references/naming.md +117 -0
- package/skills/ultra11y/references/orchestration.md +44 -0
- package/skills/ultra11y/references/packs.md +193 -0
- package/skills/ultra11y/references/prd.md +104 -0
- package/skills/ultra11y/references/rendered.md +110 -0
- package/skills/ultra11y/references/scale.md +49 -0
- package/skills/ultra11y/references/standards.md +97 -0
- package/skills/ultra11y/references/structure.md +138 -0
- package/skills/ultra11y/references/verify.md +88 -0
- package/skills/ultra11y/references/widgets.md +106 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +52739 -0
- package/src/act.ts +441 -0
- package/src/adjudicate.ts +630 -0
- package/src/aria.ts +230 -0
- package/src/audit.ts +396 -0
- package/src/auditor.ts +399 -0
- package/src/autofill.ts +102 -0
- package/src/axe-map.ts +294 -0
- package/src/baseline.ts +75 -0
- package/src/capture.ts +182 -0
- package/src/check.ts +243 -0
- package/src/cli.ts +1927 -0
- package/src/color.ts +102 -0
- package/src/config.ts +182 -0
- package/src/crawl.ts +75 -0
- package/src/criteria.ts +183 -0
- package/src/data/adjudication.json +770 -0
- package/src/data/guidance/rgaa.json +2395 -0
- package/src/data/standards/rgaa.glossary.json +478 -0
- package/src/data/standards/rgaa.json +3454 -0
- package/src/data/wcag-universe.json +530 -0
- package/src/data/wcag.json +1178 -0
- package/src/discover.ts +203 -0
- package/src/fix/codemods.ts +76 -0
- package/src/fix/diff.ts +64 -0
- package/src/fix/edits.ts +126 -0
- package/src/fix.ts +215 -0
- package/src/gh.ts +214 -0
- package/src/glob.ts +206 -0
- package/src/graph/build.ts +114 -0
- package/src/graph/engine-resolve.ts +0 -0
- package/src/graph/graph.ts +126 -0
- package/src/graph/imports.ts +359 -0
- package/src/graph/resolve.ts +73 -0
- package/src/graph/tsconfig.ts +113 -0
- package/src/grounding.ts +119 -0
- package/src/guidance/index.ts +45 -0
- package/src/guidance/types.ts +36 -0
- package/src/init.ts +113 -0
- package/src/mcp/handlers.ts +388 -0
- package/src/mcp/http.ts +260 -0
- package/src/mcp/prompts.ts +183 -0
- package/src/mcp/protocol.ts +187 -0
- package/src/mcp/resources.ts +136 -0
- package/src/mcp/server.ts +228 -0
- package/src/mcp/stdio.ts +119 -0
- package/src/mcp/tools.ts +347 -0
- package/src/messages.ts +1011 -0
- package/src/name.ts +174 -0
- package/src/orchestrate-templates.ts +231 -0
- package/src/orchestrate.ts +186 -0
- package/src/output.ts +108 -0
- package/src/pack.ts +187 -0
- package/src/parse/html.ts +258 -0
- package/src/parse/jsx-ast.ts +67 -0
- package/src/parse/jsx-bridge.ts +247 -0
- package/src/parse/jsx.ts +19 -0
- package/src/parse/source.ts +75 -0
- package/src/prd.ts +406 -0
- package/src/project-lock.ts +42 -0
- package/src/render.ts +307 -0
- package/src/report.ts +399 -0
- package/src/rules/colors.ts +125 -0
- package/src/rules/cross-registry.ts +264 -0
- package/src/rules/cross-rule.ts +83 -0
- package/src/rules/forms.ts +590 -0
- package/src/rules/frames.ts +33 -0
- package/src/rules/headings.ts +227 -0
- package/src/rules/images.ts +200 -0
- package/src/rules/links.ts +193 -0
- package/src/rules/mandatory.ts +173 -0
- package/src/rules/multimedia.ts +77 -0
- package/src/rules/navigation.ts +190 -0
- package/src/rules/presentation.ts +75 -0
- package/src/rules/registry.ts +57 -0
- package/src/rules/rule.ts +122 -0
- package/src/rules/scripts-aria.ts +636 -0
- package/src/rules/tables.ts +264 -0
- package/src/rules/timing.ts +55 -0
- package/src/sample.ts +176 -0
- package/src/scan-local.ts +866 -0
- package/src/scan.ts +538 -0
- package/src/standards/derive.ts +195 -0
- package/src/standards/index.ts +31 -0
- package/src/standards/pack-rules.ts +111 -0
- package/src/standards/pack.ts +81 -0
- package/src/standards/registry.ts +100 -0
- package/src/standards/types.ts +202 -0
- package/src/standards/validate.ts +544 -0
- package/src/standards/vocabulary.ts +75 -0
- package/src/types.ts +355 -0
- package/src/util.ts +32 -0
- package/src/vendor/codeindex-engine.d.mts +1009 -0
- package/src/vendor/codeindex-engine.mjs +13701 -0
- package/src/vendor/engine.meta.json +9 -0
- package/src/verify.ts +275 -0
- package/src/wcag.ts +128 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Audit existing code → WCAG report
|
|
2
|
+
|
|
3
|
+
Goal: produce a dated, reliable WCAG 2.2 AA compliance report. The engine decides the
|
|
4
|
+
automatable subset; the **AI agent** adjudicates the judgment criteria (`verify --manual`,
|
|
5
|
+
gated) and routes the rendering criteria to `scan`; the gates stop any hallucinated
|
|
6
|
+
non-conformity.
|
|
7
|
+
|
|
8
|
+
## The loop
|
|
9
|
+
|
|
10
|
+
1. **Scope it.** Which files / components? HTML, JSX/TSX and Vue/Svelte/Astro components
|
|
11
|
+
(`.vue`/`.svelte`/`.astro`) are walked by default; add server templates (Twig, ERB,
|
|
12
|
+
Handlebars…) with `--ext .twig,.erb`. **Test/spec/story markup** (`*.test.*`,
|
|
13
|
+
`*.spec.*`, `*.stories.*`, `__tests__/`) is excluded by default — it is bad-by-design
|
|
14
|
+
and never shipped; pass `--no-default-excludes` (or name the file) to audit it anyway.
|
|
15
|
+
**`.vue`/`.svelte`/`.astro` are audited as SOURCE templates**: slots, snippets and
|
|
16
|
+
dynamic bindings are invisible, so their findings are flagged `preliminary` and the run
|
|
17
|
+
carries a `scope.sourceTemplate` caveat — audit the rendered output to confirm (step 3,
|
|
18
|
+
and `references/rendered.md`).
|
|
19
|
+
2. **Run the engine:**
|
|
20
|
+
```
|
|
21
|
+
node scripts/ultra11y.mjs audit "src/**/*.html" --json > audit.json
|
|
22
|
+
```
|
|
23
|
+
or on a snippet: `node scripts/ultra11y.mjs audit - < page.html --json`.
|
|
24
|
+
The `AuditResult` classes each success criterion as `C` / `NC` / `NA` (static) or
|
|
25
|
+
`manual` (rendering / judgment, listed in `residualRisks`). It also records the repo's
|
|
26
|
+
declared language(s) as `scope.langs` (the primary `<html lang>` subtags seen, most
|
|
27
|
+
frequent first, e.g. `["fr"]`) — the repo signal `--lang auto` resolves from downstream.
|
|
28
|
+
3. **Triage the results:**
|
|
29
|
+
- engine `NC` = confirmed candidates (each finding cites `file:line`);
|
|
30
|
+
- `manual` *needs-rendering* criteria (contrast 1.4.3, focus visible 2.4.7, reflow
|
|
31
|
+
1.4.10…) → route to `scan`, **never** silently `C`. Note: contrast on
|
|
32
|
+
**inline literal colours** is now decided statically (1.4.3 turns `NC` for that
|
|
33
|
+
subset); contrast via external CSS or variables stays residual (→ dynamic tier);
|
|
34
|
+
- `manual` *judgment* criteria (alt relevance under 1.1.1, link purpose 2.4.4, reading
|
|
35
|
+
/ tab order…) → the AI agent adjudicates them from context via `verify --manual` (gated).
|
|
36
|
+
4. **Decide each applicable criterion**: `C`, `NC` or `NA` (with a justification). For a
|
|
37
|
+
criterion's detail and grounding: `node scripts/ultra11y.mjs criteria 1.1.1`.
|
|
38
|
+
5. **Render the report:**
|
|
39
|
+
```
|
|
40
|
+
node scripts/ultra11y.mjs report --in audit.json --out audits
|
|
41
|
+
```
|
|
42
|
+
→ `audits/wcag-YYYY-MM-DD.md` (5 sections, see `references/methodology.md`); section 2
|
|
43
|
+
renders one **auditor conformance block** per NC criterion (theme, criterion + official
|
|
44
|
+
wording, test(s), WCAG mapping + level, finding, expected state, verification,
|
|
45
|
+
`file:line` occurrences), grouped by severity — the SAME block `prd` and `--gh-issues`
|
|
46
|
+
emit (see `references/prd.md`), so report and backlog never drift. For a country
|
|
47
|
+
standard, add `--standard rgaa` (see `references/standards.md`). Pass `--lang` to match
|
|
48
|
+
your conversation; without it, `auto` resolves the audit's `scope.langs` → the
|
|
49
|
+
standard's default locale → English. Add `--json` to `report` for a machine summary
|
|
50
|
+
(`{path, conformancePct, date, standard}`) instead of just the path.
|
|
51
|
+
6. **Check integrity:**
|
|
52
|
+
```
|
|
53
|
+
node scripts/ultra11y.mjs check --report audits/wcag-YYYY-MM-DD.md
|
|
54
|
+
```
|
|
55
|
+
Fails if a section is missing, a cited criterion does not exist, or an `NA` is unjustified.
|
|
56
|
+
7. **High assurance (optional)**: `references/verify.md` — prove every non-conformity is
|
|
57
|
+
real before you ship.
|
|
58
|
+
|
|
59
|
+
## The normative page sample (échantillon) — country-standard audits
|
|
60
|
+
|
|
61
|
+
A file-tree audit answers "is this markup conformant?"; a **country-standard** audit (RGAA,
|
|
62
|
+
Section 508…) is normatively defined over a **representative page sample**, not the whole
|
|
63
|
+
repo. Declare that sample once in `.ultra11yrc.json`:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{ "standard": "rgaa",
|
|
67
|
+
"sample": {
|
|
68
|
+
"pages": [
|
|
69
|
+
{ "id": "accueil", "name": "Page d'accueil", "url": "http://localhost:3000/" },
|
|
70
|
+
{ "id": "contact", "name": "Contact", "url": "http://localhost:3000/contact" },
|
|
71
|
+
{ "id": "compte", "name": "Mon compte", "url": "http://localhost:3000/compte", "auth": true, "storageState": ".auth/user.json", "notes": "connecté" }
|
|
72
|
+
],
|
|
73
|
+
"transverse": ["header", "navigation principale", "pied de page"]
|
|
74
|
+
} }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- `sample.pages[]` are the audited URLs (each may sit behind `auth` with a per-page
|
|
78
|
+
`storageState`); `transverse` names the elements audited on **every** page (header, nav,
|
|
79
|
+
footer, modals…). The `storageState` **path** is used but its content is never read into
|
|
80
|
+
any output.
|
|
81
|
+
- **Lint the coverage**: `node scripts/ultra11y.mjs sample check` reports which **required
|
|
82
|
+
page kinds** the active standard's `sampleMethodology` expects but the sample lacks (RGAA:
|
|
83
|
+
accueil, contact, mentions légales, déclaration d'accessibilité, plan du site, aide,
|
|
84
|
+
authentification…). Advisory (exit 0) unless the `sample` block is malformed (exit 2).
|
|
85
|
+
- **Scan the sample**: `scan --sample --merge audits/audit-latest.json` iterates every page,
|
|
86
|
+
keeps each finding's page name + auth flag as provenance, and merges the rendered verdicts
|
|
87
|
+
in (see `references/dynamic.md`).
|
|
88
|
+
- **Partial-audit honesty**: a `--standard rgaa` report produced **without** a merged sample
|
|
89
|
+
scan is flagged **partial** — a CLI warning + a report banner naming the needs-rendering
|
|
90
|
+
criteria that were not tested. Say the audit is partial rather than implying full coverage.
|
|
91
|
+
|
|
92
|
+
## Golden rules
|
|
93
|
+
|
|
94
|
+
- **Never invent a non-conformity**: every `NC` must cite a real, resolvable element
|
|
95
|
+
(`check` verifies it).
|
|
96
|
+
- **Residual is explicit, never silently `C`**: an unproven *needs-rendering* criterion goes to
|
|
97
|
+
`scan`; a *judgment* criterion the AI agent adjudicates via `verify --manual` (gated). Any
|
|
98
|
+
criterion still undecided stays in the "to assess manually" section with a recorded reason.
|
|
99
|
+
- The report's **automatic static-check pass rate** covers only the machine-decidable
|
|
100
|
+
subset; full WCAG conformance requires your manual review (it is not a conformance rate).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Write / review accessible HTML/CSS (without regressions)
|
|
2
|
+
|
|
3
|
+
Doctrine: **semantic native HTML first, ARIA last** (native-first, ARIA last). Add ARIA
|
|
4
|
+
only when no native element fits, and never duplicate implicit semantics (see
|
|
5
|
+
`references/forbidden-patterns.md`).
|
|
6
|
+
|
|
7
|
+
## Review loop
|
|
8
|
+
|
|
9
|
+
1. **Structure & landmarks** — a single `<main>`, `<header>/<nav>/<footer>`, `h1…h6`
|
|
10
|
+
headings without level skips, real lists (`ul/ol/dl`).
|
|
11
|
+
2. **Accessible names** — every link/button has a label; every field has an associated
|
|
12
|
+
`<label>` (not just a `placeholder`).
|
|
13
|
+
3. **Images & icons** — relevant `alt` if informative, `alt=""` if decorative, a name on
|
|
14
|
+
icon-only controls.
|
|
15
|
+
4. **Tables** — `<caption>`, `<th scope>`, cell↔header association.
|
|
16
|
+
5. **Keyboard** — everything reachable and operable by keyboard; no positive `tabindex`;
|
|
17
|
+
visible focus (verify at render); a working skip link.
|
|
18
|
+
6. **Language & title** — `<html lang>`, a relevant `<title>`, language changes marked.
|
|
19
|
+
|
|
20
|
+
## Catch regressions early
|
|
21
|
+
|
|
22
|
+
Before shipping the code, audit the snippet:
|
|
23
|
+
```
|
|
24
|
+
node scripts/ultra11y.mjs audit - < component.html
|
|
25
|
+
node scripts/ultra11y.mjs audit "src/components/**/*.tsx" --jsx
|
|
26
|
+
```
|
|
27
|
+
Fix by priority: native semantics → names/labels → keyboard → focus → meaning not carried
|
|
28
|
+
by colour/shape alone → media.
|
|
29
|
+
|
|
30
|
+
## Definition of Done
|
|
31
|
+
|
|
32
|
+
A change is "done" when: native semantics are correct, accessible names are present,
|
|
33
|
+
keyboard access is preserved, no regression is introduced, and the points not statically
|
|
34
|
+
verifiable (contrast, visible focus, zoom) are **flagged as residual risks** rather than
|
|
35
|
+
assumed OK.
|
|
36
|
+
|
|
37
|
+
For the exact meaning of a criterion: `node scripts/ultra11y.mjs criteria <sc>`.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Automate in the repo — `init` (hook / CI)
|
|
2
|
+
|
|
3
|
+
ultra11y can stay an **on-demand** skill, or become a **gate** that runs on its own.
|
|
4
|
+
`init` wires it (zero-dependency, no husky):
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
node scripts/ultra11y.mjs init --hook # .git/hooks/pre-commit — strict STAGED gate (default)
|
|
8
|
+
node scripts/ultra11y.mjs init # same as --hook
|
|
9
|
+
node scripts/ultra11y.mjs init --ci # .github/workflows/a11y.yml (PR diff vs base)
|
|
10
|
+
node scripts/ultra11y.mjs init --baseline … # opt into the legacy regression gate (below)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Default: strict staged snapshot + safe auto-fix
|
|
14
|
+
|
|
15
|
+
The default pre-commit hook operates on **exactly what is about to be committed** — the
|
|
16
|
+
**staged index blobs**, read with `git show :./<path>`, *not* the working-tree copy.
|
|
17
|
+
It runs (fix first, then gate — so safe fixes are always applied, and only the judgment
|
|
18
|
+
issues that remain can block):
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
node scripts/ultra11y.mjs fix --staged --write --safe # auto-apply SAFE fixes to the staged snapshot, re-stage
|
|
22
|
+
node scripts/ultra11y.mjs audit --staged --fail-on blocking # gate: block only if issues remain (need judgment)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Why fix-first: every auto-fixable rule (positive `tabindex`, redundant `role`,
|
|
26
|
+
zoom-blocking viewport) is `major`/`minor`, while the `blocking` findings (missing `alt`,
|
|
27
|
+
missing label) are exactly the ones a codemod must **not** guess. So the safe pass cleans
|
|
28
|
+
up and re-stages what it can, and the gate blocks only on the judgment remainder for the AI
|
|
29
|
+
agent to adjudicate.
|
|
30
|
+
|
|
31
|
+
- **`--staged`** scopes to staged adds/mods only (not untracked, not unstaged-only edits)
|
|
32
|
+
and audits the exact bytes a commit would record — including `.gitattributes` clean
|
|
33
|
+
filters. `--staged` wins over `--changed`/`--since`.
|
|
34
|
+
- **`--safe`** applies only genuinely-**automatic** codemods. Placeholder inserts
|
|
35
|
+
(`alt="TODO"`, `lang="und"`, `title="TODO"`) are **not** applied: a stub would clear the
|
|
36
|
+
finding mechanically while still needing a real value, so those (and judgment proposals)
|
|
37
|
+
are left in place and keep the gate blocking.
|
|
38
|
+
- **Auto-fix + re-stage** happens only for **fully-staged** files (working tree == index).
|
|
39
|
+
A **partially-staged** file (unstaged edits present) is left untouched — writing
|
|
40
|
+
index-derived output over it and `git add`-ing would silently stage those edits — so its
|
|
41
|
+
findings keep the gate blocking with a message to stage/fix it manually.
|
|
42
|
+
- The commit proceeds only when no blocking issue remains after the safe pass. One-off
|
|
43
|
+
bypass: `SKIP_A11Y=1 git commit …`. Engine path override: `ULTRA11Y=/path/to/ultra11y.mjs`.
|
|
44
|
+
|
|
45
|
+
**When the hook blocks on judgment issues** (alt relevance, link purpose, labels,
|
|
46
|
+
heading structure) the agent should: open the cited **staged** code, apply the *real*
|
|
47
|
+
fix (not a placeholder), `git add` the file, and re-commit — or run `fix --staged
|
|
48
|
+
--write` to lay down placeholders and then fill them in.
|
|
49
|
+
|
|
50
|
+
## MUST: the FINAL, rendered semantic HTML has to be correct
|
|
51
|
+
|
|
52
|
+
The static engine only sees **source**. When markup comes from a **component library**
|
|
53
|
+
(DSFR, MUI, Chakra…) or a `.vue`/`.svelte`/`.astro` SFC, the real **semantic HTML** only
|
|
54
|
+
exists **after rendering** — the source `<Button/>` tells you nothing about the `<button>`
|
|
55
|
+
(or `<div role>`) it emits. A green staged gate on opaque component source is **not**
|
|
56
|
+
proof of an accessible result.
|
|
57
|
+
|
|
58
|
+
**So make the produced semantic HTML the thing you gate — with rendered CAPTURES.** A
|
|
59
|
+
capture is the *rendered* DOM serialized to `.html` and committed, so the gate audits the
|
|
60
|
+
true markup, not the component call. Zero-touch setup (your tests do the rendering):
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
node scripts/ultra11y.mjs render --setup # install the capture harvester into your test runner
|
|
64
|
+
npm test # every rendered component → .ultra11y/captures/*.html (provenance-tagged)
|
|
65
|
+
node scripts/ultra11y.mjs audit --require-captures # gate: every opaque/control component must have a capture
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **Attribution + coverage.** A finding on a capture is reported against the SOURCE
|
|
69
|
+
component (via its `<!-- ultra11y:capture … -->` provenance), not the capture file.
|
|
70
|
+
`render --coverage` shows covered vs blind-spot components; `--require-captures` turns any
|
|
71
|
+
remaining blind spot into a failure. `.vue`/`.svelte`/`.astro` components now count in this
|
|
72
|
+
coverage too, so a previously-green gate can newly report blind spots on an SFC-heavy repo —
|
|
73
|
+
intended (a named blind spot beats invisibility); capture those components or scope the gate.
|
|
74
|
+
- **The gate sees the captures for the diffed components automatically.** In
|
|
75
|
+
`--changed`/`--staged`/`--since` mode the audit pulls in the committed captures whose
|
|
76
|
+
provenance points at a diffed source file (a capture is rarely itself part of the diff —
|
|
77
|
+
the SOURCE changed, its capture usually didn't), so the pre-commit gate audits the real
|
|
78
|
+
`<button>`/`<nav>`/heading structure/labels for every touched component, catching
|
|
79
|
+
regressions the opaque source hides. After a component change, re-run your tests so its
|
|
80
|
+
capture refreshes, and stage the refreshed capture with the source. `fix` never rewrites
|
|
81
|
+
captures (generated output). Storybook/build HTML and manual Testing-Library dumps also
|
|
82
|
+
work — anything under `.ultra11y/captures`, or point `--captures <dir>` (`--no-captures`
|
|
83
|
+
opts out entirely).
|
|
84
|
+
- In CI, audit the **built** output (`audit "dist/**/*.html"`), and use `scan` for the
|
|
85
|
+
computed rendering criteria (contrast, focus, zoom). See `references/rendered.md`.
|
|
86
|
+
|
|
87
|
+
Treat this as a hard rule: **do not sign off on a component/SFC change from the source
|
|
88
|
+
audit alone — verify the rendered semantic HTML, and prefer committed captures so the
|
|
89
|
+
gate keeps verifying it.**
|
|
90
|
+
|
|
91
|
+
## Opt-in: baseline regression gate (legacy)
|
|
92
|
+
|
|
93
|
+
For large existing backlogs, keep the original "only block NEW regressions" gate. It is
|
|
94
|
+
opt-in via `--baseline` (or an explicit `--fail-on`), and is what `--ci` uses:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
node scripts/ultra11y.mjs init --baseline # writes audits/baseline.json (commit it)
|
|
98
|
+
node scripts/ultra11y.mjs init --hook --baseline
|
|
99
|
+
node scripts/ultra11y.mjs init --ci # audit --since <base> --baseline … in Actions
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The hook/CI then run `audit --changed --baseline audits/baseline.json --fail-on blocking`:
|
|
103
|
+
the audit is restricted to the **diff**, and only **NEW** non-conformities at/above the
|
|
104
|
+
threshold fail — the existing backlog never blocks. Finding identity `(rule, criterion,
|
|
105
|
+
file, source range)` is robust to line drift. **Commit `audits/baseline.json`** (add a
|
|
106
|
+
`!audits/baseline.json` negation if `audits/` is git-ignored) and refresh it as you burn
|
|
107
|
+
down backlog. `--fail-on blocking|major|minor` sets the threshold (French aliases
|
|
108
|
+
`bloquant|majeur|mineur` accepted).
|
|
109
|
+
|
|
110
|
+
> Either gate relies on the **static** engine. The **rendering** (contrast, focus, zoom) criteria
|
|
111
|
+
> go to `scan` and the **judgment** criteria are adjudicated by the AI agent (`verify --manual`,
|
|
112
|
+
> gated) in the full audit — the gate stops mechanical regressions, it is not the full audit
|
|
113
|
+
> (a human is at most optional oversight).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Correction phase (put the fixes in place, by priority)
|
|
2
|
+
|
|
3
|
+
Fix without breaking or inventing anything. Doctrine (native HTML first, smallest possible
|
|
4
|
+
change, no regressions) applied in priority order.
|
|
5
|
+
|
|
6
|
+
## Processing order
|
|
7
|
+
|
|
8
|
+
1. **🔴 Blocking first**, then 🟠 major, then 🟡 minor — the order of the `prd` backlog and the
|
|
9
|
+
report. On a large repo, handle the shared templates first (layouts, design system): a fix
|
|
10
|
+
there covers the whole site.
|
|
11
|
+
2. **Three classes of fix** (`fix`):
|
|
12
|
+
- **auto** — deterministic codemods (positive tabindex, redundant ARIA, blocked zoom); safe
|
|
13
|
+
to apply unsupervised.
|
|
14
|
+
- **placeholder** — inserts a valid attribute with a `TODO` (alt/lang/title/aria-label) that
|
|
15
|
+
you replace with a real, in-context value.
|
|
16
|
+
- **proposal** — judgment only; the engine never invents content (alt text, link purpose,
|
|
17
|
+
table structure) — the AI agent writes it and adjudicates the criterion (`verify --manual`,
|
|
18
|
+
gated).
|
|
19
|
+
3. **Conforming / non-conforming examples**: for each pattern, start from the smallest change
|
|
20
|
+
that makes the **rendered** HTML conforming (not just the source). If a library component
|
|
21
|
+
does not instantiate correctly (e.g. an icon-only DSFR button with no `title`), fix at the
|
|
22
|
+
usage site and re-verify on the render.
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
node scripts/ultra11y.mjs fix "src/**/*.html" # dry-run: proposed diff
|
|
28
|
+
node scripts/ultra11y.mjs fix "src/**/*.html" --write # apply (auto+placeholder)
|
|
29
|
+
node scripts/ultra11y.mjs fix "src/**/*.tsx" --write # real JSX: jsxSafe codemods only
|
|
30
|
+
node scripts/ultra11y.mjs fix "src/**" --write --iterate # re-audit + re-apply to a fixpoint
|
|
31
|
+
node scripts/ultra11y.mjs fix "src/**" --only img-alt-missing,positive-tabindex
|
|
32
|
+
node scripts/ultra11y.mjs prd --in audits/audit-latest.json # prioritized backlog of remaining fixes
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- **Anti-regression gate**: `--write` applies only if a re-audit proves no new non-conformity
|
|
36
|
+
is introduced (including no new finding type). An invalid placeholder is thus blocked.
|
|
37
|
+
- **JSX/TSX**: `fix --write` applies only the safe codemods (remove ARIA, insert valid React
|
|
38
|
+
attributes); it never rewrites an attribute name (no `tabIndex={5}` → `tabindex="0"`). The
|
|
39
|
+
rest stays a proposal.
|
|
40
|
+
- After fixing, **loop**: re-audit, re-judge the `proposal`s, until `check`/`verify` are green
|
|
41
|
+
again (see the SKILL's "loop" section).
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<!-- GENERATED from src/data/wcag.json by `ultra11y criteria --generate` — do not edit by hand. -->
|
|
2
|
+
|
|
3
|
+
# WCAG 2.2 Level AA — success-criteria reference
|
|
4
|
+
|
|
5
|
+
The 55 Level A + AA success criteria across the 4 principles / 13 guidelines, with each
|
|
6
|
+
SC's level, the ultra11y automatability class (automatable / needs rendering / judgment),
|
|
7
|
+
the engine rules that cover it, and — one column per registered standards pack — the pack
|
|
8
|
+
criteria that map to it. SC ids, titles and levels are derived from the W3C source
|
|
9
|
+
(https://github.com/w3c/wcag); WCAG 2.2 © W3C.
|
|
10
|
+
|
|
11
|
+
## 1.1 Text Alternatives
|
|
12
|
+
|
|
13
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
14
|
+
|---|---|---|---|---|---|
|
|
15
|
+
| 1.1.1 | Non-text Content | A | judgment | img-alt-missing, canvas-fallback-missing, decorative-alt-misuse, input-image-alt-missing, object-embed-no-name, chart-no-accessible-name | 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.9, 4.7, 4.8, 4.9, 6.1, 6.2, 7.2, 10.2, 13.3, 13.4, 13.5, 13.6 |
|
|
16
|
+
|
|
17
|
+
## 1.2 Time-based Media
|
|
18
|
+
|
|
19
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
20
|
+
|---|---|---|---|---|---|
|
|
21
|
+
| 1.2.1 | Audio-only and Video-only (Prerecorded) | A | judgment | — | 4.1, 4.2 |
|
|
22
|
+
| 1.2.2 | Captions (Prerecorded) | A | judgment | media-no-track | 4.3, 4.4 |
|
|
23
|
+
| 1.2.3 | Audio Description or Media Alternative (Prerecorded) | A | judgment | — | 4.1, 4.2 |
|
|
24
|
+
| 1.2.4 | Captions (Live) | AA | judgment | — | — |
|
|
25
|
+
| 1.2.5 | Audio Description (Prerecorded) | AA | judgment | — | 4.5, 4.6 |
|
|
26
|
+
|
|
27
|
+
## 1.3 Adaptable
|
|
28
|
+
|
|
29
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
30
|
+
|---|---|---|---|---|---|
|
|
31
|
+
| 1.3.1 | Info and Relationships | A | judgment | fieldset-legend-missing, data-table-no-headers, table-caption-missing, layout-table-data-markup, heading-order-skip, h1-missing, h1-multiple, list-structure, dl-structure, empty-heading, label-for-dangling, missing-main-landmark, multiple-main-landmark, sortable-header-no-aria-sort, nav-landmark-missing, nav-landmark-unnamed, aria-required-parent, headers-attr-dangling, th-no-data-cells, radio-checkbox-group-ungrouped, table-empty-data-cell, css-generated-content-informative | 3.1, 5.1, 5.2, 5.4, 5.5, 5.6, 5.7, 5.8, 7.3, 8.9, 9.1, 9.2, 9.3, 9.4, 10.1, 10.2, 11.1, 11.5, 11.6, 11.7, 11.8, 12.6, 13.3, 13.4 |
|
|
32
|
+
| 1.3.2 | Meaningful Sequence | A | judgment | — | 5.3, 8.10, 10.1, 10.3, 10.8, 13.3, 13.4 |
|
|
33
|
+
| 1.3.3 | Sensory Characteristics | A | judgment | — | 10.9, 10.10 |
|
|
34
|
+
| 1.3.4 | Orientation | AA | needs-rendering | — | 13.9 |
|
|
35
|
+
| 1.3.5 | Identify Input Purpose | AA | judgment | field-purpose-incomplete, autocomplete-token-invalid | 11.13 |
|
|
36
|
+
|
|
37
|
+
## 1.4 Distinguishable
|
|
38
|
+
|
|
39
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
40
|
+
|---|---|---|---|---|---|
|
|
41
|
+
| 1.4.1 | Use of Color | A | needs-rendering | — | 3.1, 10.6, 10.7, 10.9, 10.10 |
|
|
42
|
+
| 1.4.2 | Audio Control | A | static | autoplay-media | 4.10 |
|
|
43
|
+
| 1.4.3 | Contrast (Minimum) | AA | needs-rendering | contrast-literal | 3.2, 10.5 |
|
|
44
|
+
| 1.4.4 | Resize Text | AA | needs-rendering | meta-viewport-zoom-block | 10.4 |
|
|
45
|
+
| 1.4.5 | Images of Text | AA | needs-rendering | — | 1.8 |
|
|
46
|
+
| 1.4.10 | Reflow | AA | needs-rendering | — | 10.11 |
|
|
47
|
+
| 1.4.11 | Non-text Contrast | AA | needs-rendering | — | 3.3 |
|
|
48
|
+
| 1.4.12 | Text Spacing | AA | needs-rendering | — | 10.12 |
|
|
49
|
+
| 1.4.13 | Content on Hover or Focus | AA | needs-rendering | — | 10.13 |
|
|
50
|
+
|
|
51
|
+
## 2.1 Keyboard Accessible
|
|
52
|
+
|
|
53
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
54
|
+
|---|---|---|---|---|---|
|
|
55
|
+
| 2.1.1 | Keyboard | A | judgment | clickable-noninteractive | 4.11, 4.12, 7.3, 10.14, 12.9, 12.11 |
|
|
56
|
+
| 2.1.2 | No Keyboard Trap | A | needs-rendering | — | 4.11, 4.12, 12.9 |
|
|
57
|
+
| 2.1.4 | Character Key Shortcuts | A | judgment | — | 12.10 |
|
|
58
|
+
|
|
59
|
+
## 2.2 Enough Time
|
|
60
|
+
|
|
61
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
62
|
+
|---|---|---|---|---|---|
|
|
63
|
+
| 2.2.1 | Timing Adjustable | A | judgment | meta-refresh-redirect | 13.1, 13.8 |
|
|
64
|
+
| 2.2.2 | Pause, Stop, Hide | A | judgment | autoplay-media, blink-marquee | 13.1, 13.8 |
|
|
65
|
+
|
|
66
|
+
## 2.3 Seizures and Physical Reactions
|
|
67
|
+
|
|
68
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
69
|
+
|---|---|---|---|---|---|
|
|
70
|
+
| 2.3.1 | Three Flashes or Below Threshold | A | needs-rendering | — | 13.7 |
|
|
71
|
+
|
|
72
|
+
## 2.4 Navigable
|
|
73
|
+
|
|
74
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
75
|
+
|---|---|---|---|---|---|
|
|
76
|
+
| 2.4.1 | Bypass Blocks | A | judgment | skip-link-target-missing | 9.1, 12.6, 12.7, 13.3, 13.4 |
|
|
77
|
+
| 2.4.2 | Page Titled | A | static | title-missing-empty | 8.5, 8.6 |
|
|
78
|
+
| 2.4.3 | Focus Order | A | judgment | positive-tabindex | 10.3, 12.7, 12.8, 13.3, 13.4 |
|
|
79
|
+
| 2.4.4 | Link Purpose (In Context) | A | judgment | link-empty-name, icon-only-control-unnamed | 6.1, 6.2 |
|
|
80
|
+
| 2.4.5 | Multiple Ways | AA | judgment | — | 12.1, 12.3, 12.4 |
|
|
81
|
+
| 2.4.6 | Headings and Labels | AA | judgment | — | 9.1, 11.1, 11.2 |
|
|
82
|
+
| 2.4.7 | Focus Visible | AA | needs-rendering | — | 7.3, 10.7 |
|
|
83
|
+
| 2.4.11 | Focus Not Obscured (Minimum) | AA | needs-rendering | — | — |
|
|
84
|
+
|
|
85
|
+
## 2.5 Input Modalities
|
|
86
|
+
|
|
87
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
88
|
+
|---|---|---|---|---|---|
|
|
89
|
+
| 2.5.1 | Pointer Gestures | A | judgment | — | 13.10 |
|
|
90
|
+
| 2.5.2 | Pointer Cancellation | A | judgment | — | 13.11 |
|
|
91
|
+
| 2.5.3 | Label in Name | A | judgment | label-in-name-mismatch | 6.1, 7.1, 11.2, 11.9 |
|
|
92
|
+
| 2.5.4 | Motion Actuation | A | judgment | — | 13.12 |
|
|
93
|
+
| 2.5.7 | Dragging Movements | AA | judgment | — | — |
|
|
94
|
+
| 2.5.8 | Target Size (Minimum) | AA | needs-rendering | — | — |
|
|
95
|
+
|
|
96
|
+
## 3.1 Readable
|
|
97
|
+
|
|
98
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
99
|
+
|---|---|---|---|---|---|
|
|
100
|
+
| 3.1.1 | Language of Page | A | static | html-lang-missing, lang-invalid | 8.3, 8.4, 13.3, 13.4 |
|
|
101
|
+
| 3.1.2 | Language of Parts | AA | judgment | inline-lang-change-missing, lang-invalid | 8.7, 8.8 |
|
|
102
|
+
|
|
103
|
+
## 3.2 Predictable
|
|
104
|
+
|
|
105
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
106
|
+
|---|---|---|---|---|---|
|
|
107
|
+
| 3.2.1 | On Focus | A | judgment | — | 7.4, 13.2 |
|
|
108
|
+
| 3.2.2 | On Input | A | judgment | on-input-context-change | 7.4 |
|
|
109
|
+
| 3.2.3 | Consistent Navigation | AA | judgment | — | 12.2, 12.4, 12.5, 12.7 |
|
|
110
|
+
| 3.2.4 | Consistent Identification | AA | judgment | — | 11.3 |
|
|
111
|
+
| 3.2.6 | Consistent Help | A | judgment | — | — |
|
|
112
|
+
|
|
113
|
+
## 3.3 Input Assistance
|
|
114
|
+
|
|
115
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
116
|
+
|---|---|---|---|---|---|
|
|
117
|
+
| 3.3.1 | Error Identification | A | judgment | aria-invalid-no-description, error-not-associated | 11.10 |
|
|
118
|
+
| 3.3.2 | Labels or Instructions | A | judgment | radio-checkbox-group-ungrouped, date-fields-ungrouped | 11.1, 11.2, 11.4, 11.5, 11.6, 11.7, 11.10 |
|
|
119
|
+
| 3.3.3 | Error Suggestion | AA | judgment | — | 11.11 |
|
|
120
|
+
| 3.3.4 | Error Prevention (Legal, Financial, Data) | AA | judgment | — | 11.12 |
|
|
121
|
+
| 3.3.7 | Redundant Entry | A | judgment | — | — |
|
|
122
|
+
| 3.3.8 | Accessible Authentication (Minimum) | AA | judgment | credential-entry-blocked | — |
|
|
123
|
+
|
|
124
|
+
## 4.1 Compatible
|
|
125
|
+
|
|
126
|
+
| SC | Title | Level | Automatability | Rules | RGAA |
|
|
127
|
+
|---|---|---|---|---|---|
|
|
128
|
+
| 4.1.2 | Name, Role, Value | A | judgment | iframe-title-missing, invalid-aria-role, aria-ref-missing-id, redundant-aria, clickable-noninteractive, aria-required-children, aria-hidden-focusable, nested-interactive, invalid-aria-attr, invalid-aria-value, aria-required-attr, aria-prohibited-attr, duplicate-id, control-label-missing, placeholder-as-label, form-field-multiple-labels, select-has-option, button-empty-name, icon-only-control-unnamed, control-name-title-only, field-purpose-incomplete, disabled-context-content | 1.2, 1.3, 1.9, 2.1, 2.2, 4.13, 5.3, 7.1, 7.2, 8.2, 9.1, 10.8, 11.1, 11.9, 12.6, 13.3, 13.4 |
|
|
129
|
+
| 4.1.3 | Status Messages | AA | judgment | live-region-conflict, status-message-not-assertive | 7.5 |
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Cross-file analysis (`audit --graph`)
|
|
2
|
+
|
|
3
|
+
By default the engine audits **each file in isolation**. `--graph` (alias `--cross-file`)
|
|
4
|
+
adds a pass that **resolves imports between files**, builds a dependency + component graph,
|
|
5
|
+
and applies rules only the cross-file context makes visible — **without a browser** (no
|
|
6
|
+
Playwright). It complements the per-file static engine and the optional dynamic tier
|
|
7
|
+
(`scan`): all three feed the **same** `AuditResult`.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
node scripts/ultra11y.mjs audit "src/**/*.tsx" --graph --json > audit.json
|
|
11
|
+
node scripts/ultra11y.mjs audit --changed --graph # git diff, graph over the whole scope
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## How it works (and why it scales)
|
|
15
|
+
|
|
16
|
+
- **Real AST**: `.jsx/.tsx` are parsed by a real JS/TS/JSX parser (`@babel/parser`, embedded
|
|
17
|
+
in the bundle — still "no install"), not regex normalization. `PascalCase` components keep
|
|
18
|
+
their case (per-file rules ignore them; only cross-file rules resolve them); native
|
|
19
|
+
elements stay lowercase.
|
|
20
|
+
- **Two passes, bounded memory**: pass 1 — read each file, extract a small *graph node*
|
|
21
|
+
(imports, components + render-control signals, ids, `<html lang>`), then **drop** the
|
|
22
|
+
AST/Doc. Pass 2 — the usual audit loop, which also runs the cross-file rules with the graph
|
|
23
|
+
in hand. The whole repo is never held in memory: O(number of files) small nodes.
|
|
24
|
+
- **`--changed --graph`**: the graph indexes the **whole** scope (to resolve a reference into
|
|
25
|
+
an unchanged definition), but only the diffed files are audited.
|
|
26
|
+
- **Plain `.ts`/`.js` modules are graphed, never audited.** The graph's own discovery widens
|
|
27
|
+
to `.ts`/`.js`/`.mjs`/`.cjs` on top of the markup allowlist: a **barrel**
|
|
28
|
+
(`components/index.ts` re-exporting `Button.tsx`) or a plain-JS component definition is
|
|
29
|
+
real cross-file structure that imports resolve through — chained re-exports
|
|
30
|
+
(`export { Button } from "./Button"`, barrels of barrels) are followed to the real
|
|
31
|
+
definition. Those files feed imports/re-exports/definitions/ids into the graph but never
|
|
32
|
+
enter the audit loop (no markup rule ever runs on them).
|
|
33
|
+
- **`.vue`/`.svelte`/`.astro` SFCs are first-class graph nodes.** Parsed with component case
|
|
34
|
+
preserved (same as the audit); the file's own `<script>` block — or an `.astro` file's
|
|
35
|
+
`---…---` **frontmatter** — is parsed separately as a script AST for its imports; and each
|
|
36
|
+
SFC synthesizes a self component definition (PascalCase basename), so a `.tsx` importing
|
|
37
|
+
`./Widget.vue` resolves cross-file and the SFC counts in capture coverage. Astro
|
|
38
|
+
frontmatter is also stripped (offset-preserving) before the template parse, so frontmatter
|
|
39
|
+
TypeScript (`Array<string>`) never produces phantom elements or findings.
|
|
40
|
+
- **Import resolution**: relative specifiers (`./IconButton`), **tsconfig-paths aliases**
|
|
41
|
+
(`@/components/Icon`, read from the nearest `tsconfig.json`'s `baseUrl`/`paths`), and
|
|
42
|
+
**namespace members** (`<UI.Button/>` from `import * as UI`) all resolve to a discovered
|
|
43
|
+
file. *Bounds (documented, never a silent false "conforming"):* bare `node_modules`
|
|
44
|
+
specifiers (`react`) are out of scope; alias bases are resolved relative to the working
|
|
45
|
+
dir (run the audit from the project root); React **context** value flow and dynamic
|
|
46
|
+
`import()` are not traced.
|
|
47
|
+
|
|
48
|
+
## The cross-file rules
|
|
49
|
+
|
|
50
|
+
- **`cross-icon-only-unnamed`** (WCAG 2.4.4/4.1.2, *flag*): a component that renders an
|
|
51
|
+
**icon-only** control and *can* receive a name (`{...props}` / `aria-label={…}` /
|
|
52
|
+
`{children}`) is used **without a name**. The flag is placed **at the usage site**, with the
|
|
53
|
+
component **definition** in `related`.
|
|
54
|
+
- **`cross-prop-drilled-name-lost`** (WCAG 4.1.2, *flag*): a usage passes an accessible-name
|
|
55
|
+
prop (`aria-label`/`label`/…) to a component that renders a control but **neither spreads
|
|
56
|
+
`{...props}` nor forwards a name** to it → the name is silently dropped. Flagged at the
|
|
57
|
+
usage, with the control **definition** in `related`.
|
|
58
|
+
- **`cross-aria-ref-cross-file`** (WCAG 4.1.2, *suppression*): an `aria-labelledby`/
|
|
59
|
+
`aria-describedby`/`aria-controls` whose target id lives in **another** file →
|
|
60
|
+
suppresses the false `aria-ref-missing-id`. Suppressed only when **every** in-page-missing
|
|
61
|
+
id resolves elsewhere, so a genuinely-dangling reference is never hidden.
|
|
62
|
+
- **`cross-aria-forwarding`** (WCAG 4.1.2, *suppression*): a native control that forwards
|
|
63
|
+
`{...props}` is nameable by its parent → it **suppresses** the false
|
|
64
|
+
`button-empty-name`/`icon-only-control-unnamed` on the **definition**.
|
|
65
|
+
- **`cross-skip-link-target`** (WCAG 2.4.1, *suppression*): an `href="#id"` anchor whose
|
|
66
|
+
target lives in **another** file (imported layout/component) → suppresses the false "broken
|
|
67
|
+
anchor". A target missing everywhere stays a true positive.
|
|
68
|
+
- **`cross-page-lang`** (WCAG 3.1.1, *suppression*): an `<html>` without `lang` whose imported
|
|
69
|
+
layout/wrapper declares the language → suppresses the false positive.
|
|
70
|
+
- **`cross-name-ref-cross-file`** (WCAG 1.3.1, *suppression*): a `<label for="id">` or a
|
|
71
|
+
heading/`aria-labelledby` whose target id is **defined in another** file → suppresses the
|
|
72
|
+
false `label-for-dangling` / empty-heading. Suppressed only when the id resolves elsewhere.
|
|
73
|
+
|
|
74
|
+
## Benefit for `fix`
|
|
75
|
+
|
|
76
|
+
Because the AST indexes the **real file** (exact offsets, non-*lossy* `Doc`), `fix` can apply
|
|
77
|
+
its *safe* codemods on JSX/TSX (remove redundant ARIA, insert `alt`/`lang`/`title`/
|
|
78
|
+
`aria-label`), always behind the anti-regression gate. Codemods that **rewrite** an attribute
|
|
79
|
+
name stay disabled on JSX (so `tabIndex={5}` is never turned into `tabindex="0"`).
|
|
80
|
+
|
|
81
|
+
> Cross-file flags merge into the same `AuditResult`: `report`, `prd`, `check` and `verify`
|
|
82
|
+
> consume them unchanged.
|