ultra11y 2.22.0 → 2.24.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/README.md +93 -9
- package/package.json +6 -2
- package/scripts/build-pack-rgaa.mjs +95 -1
- package/scripts/build-standards.mjs +5 -1
- package/scripts/ultra11y.mjs +2920 -320
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +2920 -320
- package/skills/ultra11y/SKILL.md +38 -2
- package/skills/ultra11y/references/ci.md +165 -0
- package/skills/ultra11y/references/criteria.md +5 -5
- package/skills/ultra11y/references/devtools.md +67 -0
- package/skills/ultra11y/references/e2e.md +112 -0
- package/skills/ultra11y/references/judgment.md +25 -0
- package/skills/ultra11y/references/pages.md +205 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +2920 -320
- package/src/adjudicate.ts +270 -45
- package/src/annotate.ts +158 -0
- package/src/audit.ts +8 -1
- package/src/capture.ts +4 -0
- package/src/cli.ts +485 -14
- package/src/criteria.ts +58 -0
- package/src/data/standards/rgaa.json +155 -30
- package/src/data/wcag.json +5 -5
- package/src/dev.ts +427 -0
- package/src/e2e.ts +303 -0
- package/src/gh.ts +72 -0
- package/src/init.ts +39 -7
- package/src/messages.ts +87 -0
- package/src/orchestrate-templates.ts +12 -4
- package/src/pages.ts +283 -0
- package/src/parse/html.ts +12 -0
- package/src/pixel.ts +217 -0
- package/src/prd.ts +37 -0
- package/src/report.ts +28 -2
- package/src/rules/registry.ts +2 -0
- package/src/rules/rendered.ts +379 -0
- package/src/rules/rule.ts +3 -0
- package/src/sarif.ts +196 -0
- package/src/scan-local.ts +4 -0
- package/src/snapshot.ts +442 -0
- package/src/standards/derive.ts +32 -1
- package/src/types.ts +161 -1
- package/src/util.ts +18 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Audit HTML/CSS/JSX against **WCAG 2.2 AA** accessibility and produce a dated compliance report — or author/review accessible markup without regressions. A [skills.sh](https://skills.sh) agent skill: a deterministic, zero-dependency static engine **plus** the agent's judgment, with `check`/`verify` gates against hallucinated non-conformities. **The central deliverable is the auditor conformance block** — theme, criterion + official wording, test(s), WCAG mapping + level, finding, expected state, verification, `file:line` occurrences — rendered identically by the `report` (compliance doc), the `prd` backlog and the GitHub issues, in the active standard's vocabulary and **in your language** (`--lang auto` follows the conversation/repo). **WCAG is the worldwide core; country standards (RGAA, …) are pluggable in-repo packs.**
|
|
4
4
|
|
|
5
|
-
ultra11y is built around an honest **division of labour**. Automated tools only catch a fraction of accessibility problems, so the engine does the *mechanical* work —
|
|
5
|
+
ultra11y is built around an honest **division of labour**. Automated tools only catch a fraction of accessibility problems, so the engine does the *mechanical* work — 78 machine-detectable static checks tied to the WCAG 2.2 success criteria — and is explicit about everything it can't decide statically. What it can't, the **AI agent adjudicates** (statically, from the evidence, gated by `verify`/`check`) — not a deferral to a human:
|
|
6
6
|
|
|
7
7
|
- **Automatable (engine):** missing `alt`/`lang`/`title`, unlabeled fields, empty links/buttons, icon-only controls, iframes without title, tables without headers, heading-level skips, empty/dangling headings & labels, duplicate ids, invalid/broken ARIA, positive `tabindex`, autoplay/timed-refresh/`blink`/`marquee` media…
|
|
8
8
|
- **Agent judgment (gated):** alt-text relevance, link purpose in context, reading/tab order, caption accuracy — the agent rules on these via `verify --manual` → `--apply`, each verdict carrying a justification (or a groundable NC), never a silent "conforming".
|
|
@@ -28,17 +28,37 @@ Rules needing a rendered page (computed contrast, keyboard traps) or a human cal
|
|
|
28
28
|
|
|
29
29
|
## Install
|
|
30
30
|
|
|
31
|
+
**As a CLI** — on npm, zero runtime dependencies (Node ≥ 22.18):
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npx ultra11y --help # no install
|
|
35
|
+
npm i -D ultra11y # or as a dev dependency
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**As an agent skill** — the repository ships **two** (pick at the prompt, or pass the name):
|
|
39
|
+
`ultra11y` for full audits, compliance reports, PRD backlogs and standards packs, and
|
|
40
|
+
`review-a11y` for the fast loop that audits **exactly the code under change** (staged files,
|
|
41
|
+
working diff, or branch vs merge-base) and reports like a code reviewer. Both bundle the same
|
|
42
|
+
install-free engine.
|
|
43
|
+
|
|
31
44
|
```sh
|
|
32
45
|
npx skills add maxgfr/ultra11y
|
|
33
46
|
```
|
|
34
47
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
**As an MCP server** — so an agent drives the engine as tools instead of shelling out:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
claude mcp add ultra11y -- npx -y ultra11y mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**As a GitHub Action** — audits the PR diff and, optionally, the served pages:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
- uses: maxgfr/ultra11y@v2
|
|
58
|
+
with: { since: auto, standard: rgaa, fail-on: blocking }
|
|
59
|
+
```
|
|
40
60
|
|
|
41
|
-
Or clone and run the bundled engine
|
|
61
|
+
Or clone and run the bundled engine straight from the repo:
|
|
42
62
|
|
|
43
63
|
```sh
|
|
44
64
|
node scripts/ultra11y.mjs --help
|
|
@@ -50,9 +70,14 @@ node scripts/ultra11y.mjs --help
|
|
|
50
70
|
ultra11y audit <globs… | -> [--out <dir>] [--include <glob>] [--exclude <glob>] [--ext <list>] [--jsx] [--graph] [--json] [--lang auto|en|fr] [--no-default-excludes]
|
|
51
71
|
ultra11y audit [--changed | --since <ref> | --staged] [--max-files <n>] [--dedup exact|normalized|off] [--baseline <file>] [--fail-on blocking|major|minor]
|
|
52
72
|
ultra11y audit [--captures <dir>] [--no-captures] [--require-captures] # audit rendered-DOM captures alongside source
|
|
53
|
-
ultra11y
|
|
73
|
+
ultra11y audit [--format sarif|github] # CI: code-scanning SARIF, or inline annotations + job summary
|
|
74
|
+
ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
|
|
54
75
|
ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--standard <pack>] [--gh-issues | --gh-single] [--lang auto|en|fr]
|
|
55
|
-
ultra11y render [<dir>] [--scaffold | --setup | --coverage | --storybook] [--captures <dir>] [--out <file>] [--json]
|
|
76
|
+
ultra11y render [<dir>] [--scaffold | --setup | --e2e | --coverage | --storybook] [--runner playwright|cypress] [--captures <dir>] [--out <file>] [--json]
|
|
77
|
+
ultra11y snapshot write [--root <dir>] [--fail-on blocking|major|minor] [--json] # payload on stdin → .ultra11y/pages/<id>/ + audit
|
|
78
|
+
ultra11y snapshot list [--root <dir>] [--json]
|
|
79
|
+
ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] # the per-page criterion grid
|
|
80
|
+
ultra11y dev [--port <n>] [--standard <pack>] | dev --next # live overlay while you build + dashboard
|
|
56
81
|
ultra11y criteria [<sc>] [--list] [--standard <pack> [--theme <N>]] [--generate] [--json] [--lang auto|en|fr]
|
|
57
82
|
ultra11y check --report <md> [--standard <pack>] [--quiet] [--json]
|
|
58
83
|
ultra11y verify --report <md> [--standard <pack>] [--semantic] [--apply <verdicts.json>] [--max-verify <n>] [--json]
|
|
@@ -172,6 +197,65 @@ a test). See [`CONTRIBUTING.md`](CONTRIBUTING.md) and `skills/ultra11y/reference
|
|
|
172
197
|
auto-codemods, fill-in `TODO` placeholders for the agent to complete, and judgment-only
|
|
173
198
|
proposals. `--dry-run` is the default; `--write` applies but only after a re-audit proves
|
|
174
199
|
no new non-conformity, and never on lossy JSX/TSX. See `references/fix.md`.
|
|
200
|
+
- **During your E2E run** — `render --e2e` writes Playwright/Cypress fixtures that audit a
|
|
201
|
+
targeted page **inside the test run you already have**, so the page is checked in the state
|
|
202
|
+
your test built (logged in, form filled, modal open) — state a separate `scan` run does not
|
|
203
|
+
have. Each checked page is persisted as a **snapshot** (`.ultra11y/pages/<id>/`: the whole
|
|
204
|
+
rendered document + computed styles + boxes), which is the durable half: the same page
|
|
205
|
+
re-audits **offline, with no browser**, so CI and the per-page report work from it. Because
|
|
206
|
+
a snapshot is a full document, the page-scoped rules finally run — RGAA **8.3** (`lang`),
|
|
207
|
+
**8.5/8.6** (`title`) — and every finding carries both its page and the source file that
|
|
208
|
+
rendered it. See `references/e2e.md` and `references/pages.md`.
|
|
209
|
+
- **While you build** — `dev --next` writes a one-line Next overlay component and `dev` starts
|
|
210
|
+
a loopback side-car: a floating panel lists the current page's non-conformities as you
|
|
211
|
+
browse, each linking to its `file:line` in your editor, and `http://127.0.0.1:4111` shows
|
|
212
|
+
the per-page grid accumulating. The overlay detaches itself before collecting — otherwise it
|
|
213
|
+
would audit itself and shift every element index — and renders nothing outside development.
|
|
214
|
+
See `references/devtools.md`.
|
|
215
|
+
- **A country standard is adjudicated at its OWN granularity** — 99 of RGAA's 106 criteria can
|
|
216
|
+
only ever derive `manual`, so ~93% of an RGAA audit is settled by the agent. `verify --manual
|
|
217
|
+
--standard rgaa` now keys the worklist by **RGAA criteria**, each carrying its numbered tests
|
|
218
|
+
in full, its technical note, its particular cases, its guidance and the glossary definitions
|
|
219
|
+
its tests cite (119 entries that previously had no reader). A `normativeRef` must cite one of
|
|
220
|
+
the item's **own** tests — the laxer check accepted a WCAG id that happened to collide with
|
|
221
|
+
an unrelated RGAA test number. Verdicts fold into a separate `packAdjudication` layer so a
|
|
222
|
+
pack decision never rewrites the WCAG core. See `references/judgment.md`.
|
|
223
|
+
- **Rendered tier (offline)** — six rules read a snapshot's browser-only signals inside the
|
|
224
|
+
ordinary `audit`: **no browser, no Docker, no running server**. `rendered-contrast` measures
|
|
225
|
+
contrast on the *computed* styles (the inline-literal rule could only see colours written in
|
|
226
|
+
the markup); `rendered-contrast-pixel` measures it **on the screenshot** for text over a
|
|
227
|
+
gradient or image, where `background-color` is transparent and every CSSOM-based checker —
|
|
228
|
+
axe-core included — is blind; `rendered-nontext-contrast` decides whether a form control's
|
|
229
|
+
boundary is perceivable (**RGAA 3.3**); and two more read the *stylesheet itself*, which is
|
|
230
|
+
the only place they exist: a focus indicator removed with nothing put back (**10.7**, until
|
|
231
|
+
now reachable only by a live browser probe) and an orientation lock (**13.9**).
|
|
232
|
+
`rendered-link-colour-only` makes **RGAA 10.6** decidable at all.
|
|
233
|
+
Each can say *"I don't know"*: an unresolvable backdrop, a varied region, a cross-origin
|
|
234
|
+
stylesheet, a `box-shadow` that might be the boundary — all leave the criterion undecided
|
|
235
|
+
rather than guessed, and without signals the rules do not fire, so no pre-existing verdict
|
|
236
|
+
changes. `tests/rgaa-coverage.test.ts` ratchets the result: **48 of 106** RGAA criteria now
|
|
237
|
+
map onto an engine rule, up from 43, and the number can only go up.
|
|
238
|
+
- **Page by page** — RGAA is a per-page norm; the engine's verdict is scope-wide. `pages`
|
|
239
|
+
bridges the two: one row per criterion, one column per page, embedded in `report` and
|
|
240
|
+
rebuilt from a committed `audit.json` alone. The per-page status is not recomputed — a
|
|
241
|
+
per-page *view* of the audit runs through the very same projection the report uses, so grid
|
|
242
|
+
and report agree by construction. Two rules hold the line: a finding is attributed to a page
|
|
243
|
+
only when something **says** so (else it is counted as unattributed, never spread across
|
|
244
|
+
pages), and a criterion is conforming *by silence* only on a page whose real rendered DOM
|
|
245
|
+
was audited. See `references/pages.md`.
|
|
246
|
+
- **A shipped GitHub Action** — `uses: maxgfr/ultra11y@main` audits the **code** (PR diff) and,
|
|
247
|
+
optionally, the **pages** (it can start your app, wait for it, then scan real URLs or your
|
|
248
|
+
declared sample). The engine ships inside the action, so there is nothing to install and no
|
|
249
|
+
`setup-node`. The gate runs **last**, after SARIF, annotations, the summary, the sticky PR
|
|
250
|
+
comment and the report — a red job is never a dead end. `ultra11y init --ci` writes a
|
|
251
|
+
workflow using it. See `references/ci.md`.
|
|
252
|
+
- **CI surfaces** — a red job says *that* the build broke, not *where*. `--format sarif` emits
|
|
253
|
+
SARIF 2.1.0 for GitHub code scanning, so each finding lands as an **inline annotation on the
|
|
254
|
+
causing line** of the PR; `--format github` does the same via `::error::` workflow commands
|
|
255
|
+
plus a `$GITHUB_STEP_SUMMARY` table, for plans without code scanning. Alert identity reuses
|
|
256
|
+
the baseline's `findingId`, so an alert survives line drift; a URL-keyed finding gets **no**
|
|
257
|
+
location rather than a guessed one. Run it from `report --standard rgaa` for pack-keyed
|
|
258
|
+
output. See `references/ci.md`.
|
|
175
259
|
- **Automation** — `init --hook` (default) wires a zero-dependency git pre-commit gate over the
|
|
176
260
|
**strict staged snapshot**: it audits the exact index blobs, auto-applies the safe fixes and
|
|
177
261
|
re-stages them, and blocks only on judgment issues. `init --baseline`/`--ci` is the opt-in
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.24.0",
|
|
4
4
|
"description": "Audit HTML/CSS/JSX against WCAG 2.2 AA and produce a dated compliance report, or author/review accessible markup (native-HTML-first). A skills.sh agent skill: an install-free static engine (bundled, no npm install) plus the agent's judgment, with check/verify gates against hallucinated non-conformities, and pluggable in-repo standards packs (RGAA France first; Section 508 & EN 301 549 next).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.33.0",
|
|
@@ -56,10 +56,11 @@
|
|
|
56
56
|
"@semantic-release/changelog": "^6.0.3",
|
|
57
57
|
"@semantic-release/exec": "^7.1.0",
|
|
58
58
|
"@semantic-release/git": "^10.0.1",
|
|
59
|
+
"@semantic-release/npm": "^13.1.5",
|
|
59
60
|
"@types/node": "^26.0.1",
|
|
60
61
|
"@vitest/coverage-v8": "^4.1.9",
|
|
61
62
|
"jsdom": "^29.1.1",
|
|
62
|
-
"semantic-release": "^25.0.
|
|
63
|
+
"semantic-release": "^25.0.8",
|
|
63
64
|
"tsup": "^8.5.1",
|
|
64
65
|
"typescript": "^6.0.3",
|
|
65
66
|
"vitest": "^4.1.9",
|
|
@@ -69,5 +70,8 @@
|
|
|
69
70
|
"onlyBuiltDependencies": [
|
|
70
71
|
"esbuild"
|
|
71
72
|
]
|
|
73
|
+
},
|
|
74
|
+
"publishConfig": {
|
|
75
|
+
"access": "public"
|
|
72
76
|
}
|
|
73
77
|
}
|
|
@@ -58,7 +58,23 @@ const slug = (s) =>
|
|
|
58
58
|
|
|
59
59
|
// [term](#anchor) -> term
|
|
60
60
|
const plain = (s) => s.replace(/\[([^\]]+)\]\(#[^)]*\)/g, "$1");
|
|
61
|
-
|
|
61
|
+
// DINUM nests a bullet list inside a note as `{ ul: [...] }` (17 places across
|
|
62
|
+
// technicalNote / particularCases). A plain `String(v)` on those produced a literal
|
|
63
|
+
// "[object Object]" in the shipped pack, silently deleting the normative sub-conditions an
|
|
64
|
+
// auditor needs — e.g. every exception under RGAA 1.2's images-of-text rule. Flatten the
|
|
65
|
+
// node instead of stringifying it, and refuse anything still unrecognised rather than
|
|
66
|
+
// emitting placeholder text (see the assertion at the end of this file).
|
|
67
|
+
const flattenNode = (v) => {
|
|
68
|
+
if (v == null) return [];
|
|
69
|
+
if (typeof v === "string") return [v];
|
|
70
|
+
if (Array.isArray(v)) return v.flatMap(flattenNode);
|
|
71
|
+
if (typeof v === "object") {
|
|
72
|
+
const list = v.ul ?? v.ol ?? v.li ?? v.p ?? v.text;
|
|
73
|
+
if (list !== undefined) return flattenNode(list);
|
|
74
|
+
}
|
|
75
|
+
return [];
|
|
76
|
+
};
|
|
77
|
+
const toArr = (v) => flattenNode(v);
|
|
62
78
|
|
|
63
79
|
// crude HTML -> plaintext for glossary bodies
|
|
64
80
|
const deHtml = (s) =>
|
|
@@ -94,6 +110,8 @@ const RULE_SC = {
|
|
|
94
110
|
"aria-required-children": ["4.1.2"], "autoplay-media": ["1.4.2", "2.2.2"], "blink-marquee": ["2.2.2"],
|
|
95
111
|
"button-empty-name": ["4.1.2"], "canvas-fallback-missing": ["1.1.1"], "chart-no-accessible-name": ["1.1.1"],
|
|
96
112
|
"clickable-noninteractive": ["4.1.2", "2.1.1"], "contrast-literal": ["1.4.3"], "control-label-missing": ["4.1.2"],
|
|
113
|
+
"rendered-contrast": ["1.4.3"], "rendered-contrast-pixel": ["1.4.3"], "rendered-link-colour-only": ["1.4.1"],
|
|
114
|
+
"rendered-nontext-contrast": ["1.4.11"], "rendered-focus-not-visible": ["2.4.7"], "rendered-orientation-lock": ["1.3.4"],
|
|
97
115
|
"css-generated-content-informative": ["1.3.1"], "date-fields-ungrouped": ["3.3.2"], "disabled-context-content": ["4.1.2"],
|
|
98
116
|
"control-name-title-only": ["4.1.2"], "cross-icon-only-unnamed": ["4.1.2"],
|
|
99
117
|
"cross-prop-drilled-name-lost": ["4.1.2"], "data-table-no-headers": ["1.3.1"],
|
|
@@ -125,6 +143,22 @@ const RULE_TO_CRITERIA = {
|
|
|
125
143
|
"iframe-title-missing": ["2.1"], "axe:frame-title": ["2.1"], "axe:frame-title-unique": ["2.1"],
|
|
126
144
|
// Theme 3 / 10.5 — colour contrast (3.2 text/bg, 10.5 CSS declarations)
|
|
127
145
|
"contrast-literal": ["3.2", "10.5"], "axe:color-contrast": ["3.2", "10.5"], "axe:color-contrast-enhanced": ["3.2", "10.5"],
|
|
146
|
+
// Rendered tier: contrast measured on the real page (computed styles) and on the
|
|
147
|
+
// screenshot, for the gradient/background-image case the CSSOM cannot express.
|
|
148
|
+
"rendered-contrast": ["3.2", "10.5"], "rendered-contrast-pixel": ["3.2", "10.5"],
|
|
149
|
+
// 10.6 — a link in running text identified by colour alone. Deliberately NOT also
|
|
150
|
+
// mapped to 3.1 (the general "information by colour alone"): 10.6 is RGAA's specific
|
|
151
|
+
// criterion for links, and claiming both would count one defect twice.
|
|
152
|
+
"rendered-link-colour-only": ["10.6"],
|
|
153
|
+
// 3.3 — the boundary of a form control, measured on the computed borders/fill. The
|
|
154
|
+
// only WCAG SC RGAA 3.3 maps to (1.4.11) had no rule at all before.
|
|
155
|
+
"rendered-nontext-contrast": ["3.3"],
|
|
156
|
+
// 10.7 — a focus indicator removed by the stylesheet with nothing put back. The live
|
|
157
|
+
// `scan` probe (dyn-focus-visible) already evidenced this; the CSSOM route decides it
|
|
158
|
+
// OFFLINE, with no browser and no running app.
|
|
159
|
+
"rendered-focus-not-visible": ["10.7"],
|
|
160
|
+
// 13.9 — orientation lock. Its SC (1.3.4) had no rule.
|
|
161
|
+
"rendered-orientation-lock": ["13.9"],
|
|
128
162
|
// Theme 4 — multimedia (4.3 captions)
|
|
129
163
|
"media-no-track": ["4.3"], "axe:audio-caption": ["4.3"], "axe:video-caption": ["4.3"],
|
|
130
164
|
// Theme 5 — tables (5.4 title, 5.6/5.7 headers, 5.8 layout-table markup)
|
|
@@ -267,6 +301,51 @@ async function main() {
|
|
|
267
301
|
fr: "Indiquez le format et le poids du fichier dans l’intitulé du lien, par exemple « Rapport annuel (PDF, 2 Mo) ».",
|
|
268
302
|
},
|
|
269
303
|
},
|
|
304
|
+
// RGAA 11.8.2 — "Dans chaque balise <select>, chaque balise <optgroup> possède-t-elle un
|
|
305
|
+
// attribut label ?" Fully mechanical, and the criterion had no rule at all. Its siblings
|
|
306
|
+
// 11.8.1 ("si nécessaire") and 11.8.3 ("le label est-il pertinent ?") stay the agent's:
|
|
307
|
+
// this rule decides the one test of the three that is decidable from markup, which is
|
|
308
|
+
// exactly what the DSL is for. NORMATIVE — a missing `label` is a plain failure.
|
|
309
|
+
{
|
|
310
|
+
id: "optgroup-without-label",
|
|
311
|
+
criterion: "11.8",
|
|
312
|
+
wcag: ["1.3.1"],
|
|
313
|
+
severity: "majeur",
|
|
314
|
+
match: { tag: "optgroup", attrs: [{ name: "label", op: "absent" }] },
|
|
315
|
+
message: {
|
|
316
|
+
en: "<optgroup> without a label attribute — the group of options is unnamed (RGAA test 11.8.2).",
|
|
317
|
+
fr: "<optgroup> sans attribut label — le regroupement d’options n’est pas nommé (test RGAA 11.8.2).",
|
|
318
|
+
},
|
|
319
|
+
remediation: {
|
|
320
|
+
en: 'Add label="…" naming what the options in this group have in common, e.g. <optgroup label="Europe">.',
|
|
321
|
+
fr: 'Ajoutez label="…" nommant ce que les options du groupe ont en commun, par ex. <optgroup label="Europe">.',
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
// RGAA 8.10.2 — the FIRST of its two sub-conditions: "La valeur de l'attribut dir est
|
|
325
|
+
// conforme (rtl ou ltr)". Mechanical. The second ("la valeur est pertinente") and 8.10.1
|
|
326
|
+
// (is a reading-direction change signalled at all?) need language detection and stay
|
|
327
|
+
// manual. Anchored negative lookahead so only a genuinely invalid value matches; `auto`
|
|
328
|
+
// is valid HTML and is accepted.
|
|
329
|
+
{
|
|
330
|
+
id: "dir-value-invalid",
|
|
331
|
+
criterion: "8.10",
|
|
332
|
+
wcag: ["1.3.2"],
|
|
333
|
+
severity: "majeur",
|
|
334
|
+
match: {
|
|
335
|
+
attrs: [
|
|
336
|
+
{ name: "dir", op: "present" },
|
|
337
|
+
{ name: "dir", op: "matches", value: "^(?!(rtl|ltr|auto)$).+$" },
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
message: {
|
|
341
|
+
en: "dir attribute with a value that is not rtl, ltr or auto — the reading direction change is not declared conformantly (RGAA test 8.10.2).",
|
|
342
|
+
fr: "Attribut dir dont la valeur n’est ni rtl, ni ltr, ni auto — le changement de sens de lecture n’est pas déclaré conformément (test RGAA 8.10.2).",
|
|
343
|
+
},
|
|
344
|
+
remediation: {
|
|
345
|
+
en: 'Use dir="rtl" or dir="ltr" (or dir="auto"), matching the reading direction of the text it carries.',
|
|
346
|
+
fr: 'Utilisez dir="rtl" ou dir="ltr" (ou dir="auto"), en accord avec le sens de lecture du texte porté.',
|
|
347
|
+
},
|
|
348
|
+
},
|
|
270
349
|
];
|
|
271
350
|
// Wire each rule's namespaced id (`pack:rgaa:<id>`) into the criterion it reports under,
|
|
272
351
|
// so derivePackResults routes its finding onto that criterion through the same
|
|
@@ -385,6 +464,21 @@ async function main() {
|
|
|
385
464
|
writeFileSync(join(OUT, "rgaa.json"), packText);
|
|
386
465
|
writeFileSync(join(OUT, "rgaa.glossary.json"), glossaryText);
|
|
387
466
|
|
|
467
|
+
// A normative sentence that stringified to "[object Object]" is a DELETED sub-condition an
|
|
468
|
+
// auditor would have had to apply. Fail the build rather than ship placeholder text.
|
|
469
|
+
const stringified = [];
|
|
470
|
+
for (const c of criteria) {
|
|
471
|
+
for (const field of ["technicalNote", "particularCases"]) {
|
|
472
|
+
for (const line of c[field] ?? []) if (String(line).includes("[object Object]")) stringified.push(`${c.id}.${field}`);
|
|
473
|
+
}
|
|
474
|
+
for (const [k, lines] of Object.entries(c.tests ?? {})) {
|
|
475
|
+
for (const line of lines) if (String(line).includes("[object Object]")) stringified.push(`${c.id} test ${k}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (stringified.length) {
|
|
479
|
+
throw new Error(`build-pack-rgaa: ${stringified.length} normative text(s) stringified to "[object Object]" — extend flattenNode: ${stringified.join(", ")}`);
|
|
480
|
+
}
|
|
481
|
+
|
|
388
482
|
const noWcag = criteria.filter((c) => c.wcag.length === 0).map((c) => c.id);
|
|
389
483
|
console.log(`build-pack-rgaa: ${themes.length} themes, ${criteria.length} criteria → src/data/standards/rgaa.json`);
|
|
390
484
|
console.log(`build-pack-rgaa: glossary ${Object.keys(glossary).length} entries → src/data/standards/rgaa.glossary.json`);
|
|
@@ -341,7 +341,10 @@ const RULE_SC_COVERAGE = {
|
|
|
341
341
|
],
|
|
342
342
|
"1.3.5": ["field-purpose-incomplete", "autocomplete-token-invalid"],
|
|
343
343
|
"1.4.2": ["autoplay-media"],
|
|
344
|
-
"1.4
|
|
344
|
+
"1.3.4": ["rendered-orientation-lock"],
|
|
345
|
+
"1.4.1": ["rendered-link-colour-only"],
|
|
346
|
+
"1.4.3": ["contrast-literal", "rendered-contrast", "rendered-contrast-pixel"],
|
|
347
|
+
"1.4.11": ["rendered-nontext-contrast"],
|
|
345
348
|
"1.4.4": ["meta-viewport-zoom-block"],
|
|
346
349
|
"2.1.1": ["clickable-noninteractive"],
|
|
347
350
|
"2.2.1": ["meta-refresh-redirect"],
|
|
@@ -349,6 +352,7 @@ const RULE_SC_COVERAGE = {
|
|
|
349
352
|
"2.4.1": ["skip-link-target-missing"],
|
|
350
353
|
"2.4.2": ["title-missing-empty"],
|
|
351
354
|
"2.4.3": ["positive-tabindex"],
|
|
355
|
+
"2.4.7": ["rendered-focus-not-visible"],
|
|
352
356
|
"2.4.4": ["link-empty-name", "icon-only-control-unnamed"],
|
|
353
357
|
"2.5.3": ["label-in-name-mismatch"],
|
|
354
358
|
"3.2.2": ["on-input-context-change"],
|