ultra11y 5.16.0 → 5.17.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.opencode/plugins/ultra11y.js +1 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +36 -16
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +36 -16
- package/skills/ultra11y/SKILL.md +3 -3
- package/skills/ultra11y/references/dynamic.md +14 -9
- package/skills/ultra11y/scripts/ultra11y.mjs +36 -16
- package/src/cli.ts +18 -1
- package/src/html-report.ts +13 -6
- package/src/init.ts +1 -1
- package/src/pages.ts +28 -2
- package/src/scan.ts +25 -7
- package/src/types.ts +1 -1
- package/dist/payload-DzQb84Kw.d.ts +0 -87
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "ultra11y",
|
|
11
11
|
"source": "./",
|
|
12
12
|
"description": "Automatic accessibility review of the code under change before every commit, push and pull request, plus full WCAG 2.2 AA / RGAA audits and conformance deliverables on demand.",
|
|
13
|
-
"version": "5.
|
|
13
|
+
"version": "5.17.0",
|
|
14
14
|
"author": { "name": "maxgfr" },
|
|
15
15
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
16
16
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
3
|
"description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.17.0",
|
|
5
5
|
"author": { "name": "maxgfr" },
|
|
6
6
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
7
7
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
3
|
"description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.17.0",
|
|
5
5
|
"author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
|
|
6
6
|
"homepage": "https://github.com/maxgfr/ultra11y",
|
|
7
7
|
"repository": "https://github.com/maxgfr/ultra11y",
|
|
@@ -27,7 +27,7 @@ import { fileURLToPath } from "node:url";
|
|
|
27
27
|
// Ownership marker: `ultra11y install --opencode` refuses to overwrite a plugin file that
|
|
28
28
|
// does not carry it, so a user's own ultra11y.js is never clobbered.
|
|
29
29
|
const ULTRA11Y_OPENCODE_PLUGIN = "ULTRA11Y_OPENCODE_PLUGIN";
|
|
30
|
-
const ULTRA11Y_PLUGIN_VERSION = "5.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.17.0";
|
|
31
31
|
|
|
32
32
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
|
package/README.md
CHANGED
|
@@ -442,7 +442,7 @@ node scripts/ultra11y.mjs scan http://localhost:3000 --runtime local --cwd . --m
|
|
|
442
442
|
node scripts/ultra11y.mjs report --in audits/audit-latest.json --out audits
|
|
443
443
|
```
|
|
444
444
|
|
|
445
|
-
Only the Docker runtime needs Docker. `--runtime local` needs a Chromium binary (`npx playwright install chromium`)
|
|
445
|
+
Only the Docker runtime needs Docker. `--runtime local` needs a Chromium binary (`npx playwright install chromium`) plus `@playwright/test` and `@axe-core/playwright` — which **come with the npm package**, since they are its own dependencies; the audited project's own copies take precedence when it has them. Running the engine as a **standalone skill bundle** is the exception: an installed skill has no `node_modules` beside it, so there the two packages must be in the audited project, reachable from `--cwd`. The engine bundle itself stays dependency-free either way. The Docker runner + Dockerfile are embedded in the engine and mirrored under `docker/` (with a `docker-compose.yml`). See `skills/ultra11y/references/dynamic.md`.
|
|
446
446
|
|
|
447
447
|
Typical audit flow:
|
|
448
448
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.17.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",
|
package/scripts/ultra11y.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "5.
|
|
9
|
+
var VERSION = "5.17.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
var INAPPLICABLE_STATUS = "C";
|
|
12
12
|
|
|
@@ -54623,6 +54623,17 @@ function renderRedirected(redirected, lang = "en") {
|
|
|
54623
54623
|
}
|
|
54624
54624
|
return out2;
|
|
54625
54625
|
}
|
|
54626
|
+
function gridRate(rows, status, pageId) {
|
|
54627
|
+
let c2 = 0;
|
|
54628
|
+
let nc = 0;
|
|
54629
|
+
for (const row of rows) {
|
|
54630
|
+
const st = status.get(row.id)?.get(pageId);
|
|
54631
|
+
if (st === "C") c2++;
|
|
54632
|
+
else if (st === "NC") nc++;
|
|
54633
|
+
}
|
|
54634
|
+
const decided = c2 + nc;
|
|
54635
|
+
return [decided === 0 ? null : Math.round(c2 / decided * 100), decided, rows.length];
|
|
54636
|
+
}
|
|
54626
54637
|
function renderPageGrid(result, pages, standard = CORE2, lang = "en") {
|
|
54627
54638
|
const s = L3[lang];
|
|
54628
54639
|
const out2 = [];
|
|
@@ -54636,9 +54647,9 @@ function renderPageGrid(result, pages, standard = CORE2, lang = "en") {
|
|
|
54636
54647
|
if (derived.some((p) => p.basis === "attributed")) out2.push(`> \u26A0\uFE0F ${s.basisNote}`, "");
|
|
54637
54648
|
const head = [isCore(standard) ? s.criterion : s.criterion, ...derived.map((p) => `${p.name}${p.auth ? " \u{1F512}" : ""}`)];
|
|
54638
54649
|
out2.push(`| ${head.join(" | ")} |`, `| ${head.map(() => "---").join(" | ")} |`);
|
|
54639
|
-
out2.push(`| **${s.rate}** | ${derived.map((p) => `**${formatRate(p.conformancePct, p.decided, p.total)}**`).join(" | ")} |`);
|
|
54640
|
-
out2.push(`| _${s.snapshot}?_ | ${derived.map((p) => `_${basisLabel(p.basis, lang)}_`).join(" | ")} |`);
|
|
54641
54650
|
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
54651
|
+
out2.push(`| **${s.rate}** | ${derived.map((p) => `**${formatRate(...gridRate(rows, status, p.id))}**`).join(" | ")} |`);
|
|
54652
|
+
out2.push(`| _${s.snapshot}?_ | ${derived.map((p) => `_${basisLabel(p.basis, lang)}_`).join(" | ")} |`);
|
|
54642
54653
|
let group = "";
|
|
54643
54654
|
for (const row of rows) {
|
|
54644
54655
|
if (row.group !== group) {
|
|
@@ -59669,11 +59680,13 @@ function mergeSnapshotAudit(base, snap) {
|
|
|
59669
59680
|
if (snap.packFindings?.length) merged.packFindings = [...merged.packFindings ?? [], ...snap.packFindings];
|
|
59670
59681
|
for (const c2 of merged.criteria) {
|
|
59671
59682
|
if (!c2.inapplicable) continue;
|
|
59672
|
-
|
|
59673
|
-
|
|
59674
|
-
|
|
59675
|
-
|
|
59676
|
-
|
|
59683
|
+
const measured = snapById.get(c2.id);
|
|
59684
|
+
if (!measured || measured.status !== "C" || measured.inapplicable) continue;
|
|
59685
|
+
c2.status = "C";
|
|
59686
|
+
delete c2.inapplicable;
|
|
59687
|
+
if (measured.justification) c2.justification = measured.justification;
|
|
59688
|
+
else delete c2.justification;
|
|
59689
|
+
if (measured.decidedBy) c2.decidedBy = measured.decidedBy;
|
|
59677
59690
|
}
|
|
59678
59691
|
const nowNc = new Set(snap.findings.filter((f) => !f.advisory).map((f) => f.criteriaId));
|
|
59679
59692
|
merged.residualRisks = merged.residualRisks.filter((r) => !nowNc.has(r.criteriaId));
|
|
@@ -61016,7 +61029,7 @@ jobs:
|
|
|
61016
61029
|
ultra11y:
|
|
61017
61030
|
runs-on: ubuntu-latest
|
|
61018
61031
|
steps:
|
|
61019
|
-
- uses: actions/checkout@
|
|
61032
|
+
- uses: actions/checkout@v7
|
|
61020
61033
|
with:
|
|
61021
61034
|
fetch-depth: 0 # the diff gate needs the base ref
|
|
61022
61035
|
- uses: maxgfr/ultra11y@v${VERSION}
|
|
@@ -62975,12 +62988,16 @@ function scoreboardBlocks(result, standard, lang, sheetHref) {
|
|
|
62975
62988
|
const scope = pagesOf(result);
|
|
62976
62989
|
if (!scope.length) return [];
|
|
62977
62990
|
const derived = derivePages(result, scope);
|
|
62978
|
-
const rows = derived.map((p) =>
|
|
62979
|
-
|
|
62980
|
-
|
|
62981
|
-
|
|
62982
|
-
|
|
62983
|
-
|
|
62991
|
+
const rows = derived.map((p) => {
|
|
62992
|
+
const criteria = pageCriterionRows(result, p, standard, lang);
|
|
62993
|
+
const cov = pageCoverage(criteria);
|
|
62994
|
+
return [
|
|
62995
|
+
{ text: `${p.name}${p.auth ? " \u{1F512}" : ""}`, ...sheetHref ? { href: sheetHref(p.id) } : {} },
|
|
62996
|
+
{ text: p.url, mono: true },
|
|
62997
|
+
{ text: basisLabel(p.basis, lang) },
|
|
62998
|
+
{ text: formatRate(pageRatePct(criteria), cov.decided, cov.total), align: "end" }
|
|
62999
|
+
];
|
|
63000
|
+
});
|
|
62984
63001
|
const out2 = [
|
|
62985
63002
|
{ kind: "heading", level: 2, text: t3.perPage, id: "pages" },
|
|
62986
63003
|
{
|
|
@@ -67062,6 +67079,9 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
67062
67079
|
]);
|
|
67063
67080
|
var KNOWN_FLAGS = /* @__PURE__ */ new Set([...VALUE_FLAGS2, ...BOOLEAN_FLAGS]);
|
|
67064
67081
|
var LIST_FLAGS = /* @__PURE__ */ new Set(["include", "exclude", "ext"]);
|
|
67082
|
+
function looksLikeFlag(token) {
|
|
67083
|
+
return token?.startsWith("--") ?? false;
|
|
67084
|
+
}
|
|
67065
67085
|
function parseArgs(argv) {
|
|
67066
67086
|
const [command, ...rest] = argv;
|
|
67067
67087
|
const valueFlags = valueFlagsFor(command ?? "");
|
|
@@ -67076,7 +67096,7 @@ function parseArgs(argv) {
|
|
|
67076
67096
|
const inlineVal = eq === -1 ? void 0 : a.slice(eq + 1);
|
|
67077
67097
|
let val;
|
|
67078
67098
|
if (inlineVal !== void 0) val = inlineVal;
|
|
67079
|
-
else if (valueFlags.has(key)) val = rest[++i2] ?? "";
|
|
67099
|
+
else if (valueFlags.has(key) && !looksLikeFlag(rest[i2 + 1])) val = rest[++i2] ?? "";
|
|
67080
67100
|
else val = true;
|
|
67081
67101
|
const prev = flags2[key];
|
|
67082
67102
|
if (LIST_FLAGS.has(key) && typeof prev === "string" && typeof val === "string") {
|
|
@@ -5,7 +5,7 @@ when_to_use: "Invoke WITHOUT being asked whenever changed front-end code should
|
|
|
5
5
|
allowed-tools: Bash(node ${CLAUDE_SKILL_DIR}/scripts/ultra11y.mjs *), Read, Grep, Glob
|
|
6
6
|
license: MIT
|
|
7
7
|
metadata:
|
|
8
|
-
version: 5.
|
|
8
|
+
version: 5.17.0
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# review-a11y — accessibility review of changed code
|
|
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "5.
|
|
9
|
+
var VERSION = "5.17.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
var INAPPLICABLE_STATUS = "C";
|
|
12
12
|
|
|
@@ -54623,6 +54623,17 @@ function renderRedirected(redirected, lang = "en") {
|
|
|
54623
54623
|
}
|
|
54624
54624
|
return out2;
|
|
54625
54625
|
}
|
|
54626
|
+
function gridRate(rows, status, pageId) {
|
|
54627
|
+
let c2 = 0;
|
|
54628
|
+
let nc = 0;
|
|
54629
|
+
for (const row of rows) {
|
|
54630
|
+
const st = status.get(row.id)?.get(pageId);
|
|
54631
|
+
if (st === "C") c2++;
|
|
54632
|
+
else if (st === "NC") nc++;
|
|
54633
|
+
}
|
|
54634
|
+
const decided = c2 + nc;
|
|
54635
|
+
return [decided === 0 ? null : Math.round(c2 / decided * 100), decided, rows.length];
|
|
54636
|
+
}
|
|
54626
54637
|
function renderPageGrid(result, pages, standard = CORE2, lang = "en") {
|
|
54627
54638
|
const s = L3[lang];
|
|
54628
54639
|
const out2 = [];
|
|
@@ -54636,9 +54647,9 @@ function renderPageGrid(result, pages, standard = CORE2, lang = "en") {
|
|
|
54636
54647
|
if (derived.some((p) => p.basis === "attributed")) out2.push(`> \u26A0\uFE0F ${s.basisNote}`, "");
|
|
54637
54648
|
const head = [isCore(standard) ? s.criterion : s.criterion, ...derived.map((p) => `${p.name}${p.auth ? " \u{1F512}" : ""}`)];
|
|
54638
54649
|
out2.push(`| ${head.join(" | ")} |`, `| ${head.map(() => "---").join(" | ")} |`);
|
|
54639
|
-
out2.push(`| **${s.rate}** | ${derived.map((p) => `**${formatRate(p.conformancePct, p.decided, p.total)}**`).join(" | ")} |`);
|
|
54640
|
-
out2.push(`| _${s.snapshot}?_ | ${derived.map((p) => `_${basisLabel(p.basis, lang)}_`).join(" | ")} |`);
|
|
54641
54650
|
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
54651
|
+
out2.push(`| **${s.rate}** | ${derived.map((p) => `**${formatRate(...gridRate(rows, status, p.id))}**`).join(" | ")} |`);
|
|
54652
|
+
out2.push(`| _${s.snapshot}?_ | ${derived.map((p) => `_${basisLabel(p.basis, lang)}_`).join(" | ")} |`);
|
|
54642
54653
|
let group = "";
|
|
54643
54654
|
for (const row of rows) {
|
|
54644
54655
|
if (row.group !== group) {
|
|
@@ -59669,11 +59680,13 @@ function mergeSnapshotAudit(base, snap) {
|
|
|
59669
59680
|
if (snap.packFindings?.length) merged.packFindings = [...merged.packFindings ?? [], ...snap.packFindings];
|
|
59670
59681
|
for (const c2 of merged.criteria) {
|
|
59671
59682
|
if (!c2.inapplicable) continue;
|
|
59672
|
-
|
|
59673
|
-
|
|
59674
|
-
|
|
59675
|
-
|
|
59676
|
-
|
|
59683
|
+
const measured = snapById.get(c2.id);
|
|
59684
|
+
if (!measured || measured.status !== "C" || measured.inapplicable) continue;
|
|
59685
|
+
c2.status = "C";
|
|
59686
|
+
delete c2.inapplicable;
|
|
59687
|
+
if (measured.justification) c2.justification = measured.justification;
|
|
59688
|
+
else delete c2.justification;
|
|
59689
|
+
if (measured.decidedBy) c2.decidedBy = measured.decidedBy;
|
|
59677
59690
|
}
|
|
59678
59691
|
const nowNc = new Set(snap.findings.filter((f) => !f.advisory).map((f) => f.criteriaId));
|
|
59679
59692
|
merged.residualRisks = merged.residualRisks.filter((r) => !nowNc.has(r.criteriaId));
|
|
@@ -61016,7 +61029,7 @@ jobs:
|
|
|
61016
61029
|
ultra11y:
|
|
61017
61030
|
runs-on: ubuntu-latest
|
|
61018
61031
|
steps:
|
|
61019
|
-
- uses: actions/checkout@
|
|
61032
|
+
- uses: actions/checkout@v7
|
|
61020
61033
|
with:
|
|
61021
61034
|
fetch-depth: 0 # the diff gate needs the base ref
|
|
61022
61035
|
- uses: maxgfr/ultra11y@v${VERSION}
|
|
@@ -62975,12 +62988,16 @@ function scoreboardBlocks(result, standard, lang, sheetHref) {
|
|
|
62975
62988
|
const scope = pagesOf(result);
|
|
62976
62989
|
if (!scope.length) return [];
|
|
62977
62990
|
const derived = derivePages(result, scope);
|
|
62978
|
-
const rows = derived.map((p) =>
|
|
62979
|
-
|
|
62980
|
-
|
|
62981
|
-
|
|
62982
|
-
|
|
62983
|
-
|
|
62991
|
+
const rows = derived.map((p) => {
|
|
62992
|
+
const criteria = pageCriterionRows(result, p, standard, lang);
|
|
62993
|
+
const cov = pageCoverage(criteria);
|
|
62994
|
+
return [
|
|
62995
|
+
{ text: `${p.name}${p.auth ? " \u{1F512}" : ""}`, ...sheetHref ? { href: sheetHref(p.id) } : {} },
|
|
62996
|
+
{ text: p.url, mono: true },
|
|
62997
|
+
{ text: basisLabel(p.basis, lang) },
|
|
62998
|
+
{ text: formatRate(pageRatePct(criteria), cov.decided, cov.total), align: "end" }
|
|
62999
|
+
];
|
|
63000
|
+
});
|
|
62984
63001
|
const out2 = [
|
|
62985
63002
|
{ kind: "heading", level: 2, text: t3.perPage, id: "pages" },
|
|
62986
63003
|
{
|
|
@@ -67062,6 +67079,9 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
67062
67079
|
]);
|
|
67063
67080
|
var KNOWN_FLAGS = /* @__PURE__ */ new Set([...VALUE_FLAGS2, ...BOOLEAN_FLAGS]);
|
|
67064
67081
|
var LIST_FLAGS = /* @__PURE__ */ new Set(["include", "exclude", "ext"]);
|
|
67082
|
+
function looksLikeFlag(token) {
|
|
67083
|
+
return token?.startsWith("--") ?? false;
|
|
67084
|
+
}
|
|
67065
67085
|
function parseArgs(argv) {
|
|
67066
67086
|
const [command, ...rest] = argv;
|
|
67067
67087
|
const valueFlags = valueFlagsFor(command ?? "");
|
|
@@ -67076,7 +67096,7 @@ function parseArgs(argv) {
|
|
|
67076
67096
|
const inlineVal = eq === -1 ? void 0 : a.slice(eq + 1);
|
|
67077
67097
|
let val;
|
|
67078
67098
|
if (inlineVal !== void 0) val = inlineVal;
|
|
67079
|
-
else if (valueFlags.has(key)) val = rest[++i2] ?? "";
|
|
67099
|
+
else if (valueFlags.has(key) && !looksLikeFlag(rest[i2 + 1])) val = rest[++i2] ?? "";
|
|
67080
67100
|
else val = true;
|
|
67081
67101
|
const prev = flags2[key];
|
|
67082
67102
|
if (LIST_FLAGS.has(key) && typeof prev === "string" && typeof val === "string") {
|
package/skills/ultra11y/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: "Use to AUDIT existing HTML/CSS/JSX against WCAG 2.2 AA accessibili
|
|
|
4
4
|
when_to_use: "Invoke when the user asks for an accessibility AUDIT or a formal deliverable: audit a repo, site or page against WCAG 2.2 AA or a country standard (RGAA, Section 508, EN 301 549); produce a dated conformance report, a criterion grid, a PRD backlog, or tickets (GitHub/GitLab/Jira); author accessible markup from scratch; wire the repo gate (init --hook/--ci) or a rendered-DOM capture pipeline. For a review of the code UNDER CHANGE (diff, branch, PR), use the `review-a11y` skill instead."
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
7
|
-
version: 5.
|
|
7
|
+
version: 5.17.0
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# ultra11y — audit WCAG 2.2 AA and write accessible markup
|
|
@@ -314,7 +314,7 @@ node scripts/ultra11y.mjs pack check ./packs/section508.json #
|
|
|
314
314
|
```
|
|
315
315
|
Add `--standard rgaa` to `report`/`prd`/`criteria`/`check`/`verify` for a country standard,
|
|
316
316
|
`--pack ./pack.json` to load one at runtime, and `--json` anywhere for machine output.
|
|
317
|
-
`--lang` follows the conversation (pass it explicitly — Core rule
|
|
317
|
+
`--lang` follows the conversation (pass it explicitly — Core rule 7).
|
|
318
318
|
|
|
319
319
|
## The loop: audit → render → judge → fix → re-audit
|
|
320
320
|
|
|
@@ -347,7 +347,7 @@ drive the judgment and content stages:
|
|
|
347
347
|
per NC criterion) and `prd` (the same blocks as an actionable backlog; `tickets`
|
|
348
348
|
filing one GitHub issue per criterion with that identical block) are two views of the
|
|
349
349
|
ONE building block, in the language of this conversation (pass `--lang` explicitly —
|
|
350
|
-
Core rule
|
|
350
|
+
Core rule 7).
|
|
351
351
|
|
|
352
352
|
6. **Hand the change over to `review-a11y`, in a subagent.** Once the deliverable is out,
|
|
353
353
|
the code you just fixed still has to be reviewed as a *change*, and that is the other
|
|
@@ -7,21 +7,26 @@ content on hover (1.4.13), target size (2.5.8). The dynamic tier decides them by
|
|
|
7
7
|
|
|
8
8
|
- **`--runtime local`** (recommended): resolves Playwright + `@axe-core/playwright`
|
|
9
9
|
**at runtime**, from `--cwd` first and from ultra11y's own install second (no Docker, no
|
|
10
|
-
global install).
|
|
11
|
-
|
|
12
|
-
is tried first. Adds the residual-criteria **probes** (below).
|
|
10
|
+
global install). A project that pins its own Playwright keeps it, because `--cwd` is tried
|
|
11
|
+
first. Adds the residual-criteria **probes** (below).
|
|
13
12
|
- **`--runtime docker`**: runs axe-core in a Docker image auto-built on first use (runner +
|
|
14
13
|
Dockerfile embedded in the bundle). No host deps beyond Docker. Axe + 320px reflow only.
|
|
15
14
|
- **`--runtime auto`** (default): local if Playwright resolves **and** a browser binary is on
|
|
16
|
-
disk, else Docker, else an actionable error.
|
|
17
|
-
decides `auto` in practice is the browser, not the modules.
|
|
15
|
+
disk, else Docker, else an actionable error.
|
|
18
16
|
|
|
19
17
|
## Prerequisites
|
|
20
18
|
|
|
21
|
-
- **local**: a Chromium browser
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
- **local**: a Chromium browser (`npx playwright install chromium`), plus the two npm packages
|
|
20
|
+
— and **where they have to live depends on how you run the engine**:
|
|
21
|
+
|
|
22
|
+
| Channel | `@playwright/test` + `@axe-core/playwright` |
|
|
23
|
+
|---|---|
|
|
24
|
+
| ultra11y as an npm dependency (`ultra11y/playwright`, `pnpm exec ultra11y`) | **come with it** — they are its own `dependencies`. Nothing to install. |
|
|
25
|
+
| the standalone skill bundle (`node scripts/ultra11y.mjs` from an installed skill) | **must be in the audited project**, reachable from `--cwd`. An installed skill is a directory of files with no `node_modules` beside it, so the second anchor finds nothing there. |
|
|
26
|
+
|
|
27
|
+
Either way, a project that installs them itself wins: `--cwd` is tried first, which is what a
|
|
28
|
+
repository with its own pinned Playwright wants. When neither anchor resolves, `auto` degrades
|
|
29
|
+
to Docker and says which package it could not find.
|
|
25
30
|
- **docker**: Docker running.
|
|
26
31
|
|
|
27
32
|
The rest of the skill (static audit) needs neither.
|
|
@@ -6,7 +6,7 @@ import { join as join51, relative as relative5, resolve as resolve16, sep as sep
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "5.
|
|
9
|
+
var VERSION = "5.17.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
var INAPPLICABLE_STATUS = "C";
|
|
12
12
|
|
|
@@ -54623,6 +54623,17 @@ function renderRedirected(redirected, lang = "en") {
|
|
|
54623
54623
|
}
|
|
54624
54624
|
return out2;
|
|
54625
54625
|
}
|
|
54626
|
+
function gridRate(rows, status, pageId) {
|
|
54627
|
+
let c2 = 0;
|
|
54628
|
+
let nc = 0;
|
|
54629
|
+
for (const row of rows) {
|
|
54630
|
+
const st = status.get(row.id)?.get(pageId);
|
|
54631
|
+
if (st === "C") c2++;
|
|
54632
|
+
else if (st === "NC") nc++;
|
|
54633
|
+
}
|
|
54634
|
+
const decided = c2 + nc;
|
|
54635
|
+
return [decided === 0 ? null : Math.round(c2 / decided * 100), decided, rows.length];
|
|
54636
|
+
}
|
|
54626
54637
|
function renderPageGrid(result, pages, standard = CORE2, lang = "en") {
|
|
54627
54638
|
const s = L3[lang];
|
|
54628
54639
|
const out2 = [];
|
|
@@ -54636,9 +54647,9 @@ function renderPageGrid(result, pages, standard = CORE2, lang = "en") {
|
|
|
54636
54647
|
if (derived.some((p) => p.basis === "attributed")) out2.push(`> \u26A0\uFE0F ${s.basisNote}`, "");
|
|
54637
54648
|
const head = [isCore(standard) ? s.criterion : s.criterion, ...derived.map((p) => `${p.name}${p.auth ? " \u{1F512}" : ""}`)];
|
|
54638
54649
|
out2.push(`| ${head.join(" | ")} |`, `| ${head.map(() => "---").join(" | ")} |`);
|
|
54639
|
-
out2.push(`| **${s.rate}** | ${derived.map((p) => `**${formatRate(p.conformancePct, p.decided, p.total)}**`).join(" | ")} |`);
|
|
54640
|
-
out2.push(`| _${s.snapshot}?_ | ${derived.map((p) => `_${basisLabel(p.basis, lang)}_`).join(" | ")} |`);
|
|
54641
54650
|
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
54651
|
+
out2.push(`| **${s.rate}** | ${derived.map((p) => `**${formatRate(...gridRate(rows, status, p.id))}**`).join(" | ")} |`);
|
|
54652
|
+
out2.push(`| _${s.snapshot}?_ | ${derived.map((p) => `_${basisLabel(p.basis, lang)}_`).join(" | ")} |`);
|
|
54642
54653
|
let group = "";
|
|
54643
54654
|
for (const row of rows) {
|
|
54644
54655
|
if (row.group !== group) {
|
|
@@ -59669,11 +59680,13 @@ function mergeSnapshotAudit(base, snap) {
|
|
|
59669
59680
|
if (snap.packFindings?.length) merged.packFindings = [...merged.packFindings ?? [], ...snap.packFindings];
|
|
59670
59681
|
for (const c2 of merged.criteria) {
|
|
59671
59682
|
if (!c2.inapplicable) continue;
|
|
59672
|
-
|
|
59673
|
-
|
|
59674
|
-
|
|
59675
|
-
|
|
59676
|
-
|
|
59683
|
+
const measured = snapById.get(c2.id);
|
|
59684
|
+
if (!measured || measured.status !== "C" || measured.inapplicable) continue;
|
|
59685
|
+
c2.status = "C";
|
|
59686
|
+
delete c2.inapplicable;
|
|
59687
|
+
if (measured.justification) c2.justification = measured.justification;
|
|
59688
|
+
else delete c2.justification;
|
|
59689
|
+
if (measured.decidedBy) c2.decidedBy = measured.decidedBy;
|
|
59677
59690
|
}
|
|
59678
59691
|
const nowNc = new Set(snap.findings.filter((f) => !f.advisory).map((f) => f.criteriaId));
|
|
59679
59692
|
merged.residualRisks = merged.residualRisks.filter((r) => !nowNc.has(r.criteriaId));
|
|
@@ -61016,7 +61029,7 @@ jobs:
|
|
|
61016
61029
|
ultra11y:
|
|
61017
61030
|
runs-on: ubuntu-latest
|
|
61018
61031
|
steps:
|
|
61019
|
-
- uses: actions/checkout@
|
|
61032
|
+
- uses: actions/checkout@v7
|
|
61020
61033
|
with:
|
|
61021
61034
|
fetch-depth: 0 # the diff gate needs the base ref
|
|
61022
61035
|
- uses: maxgfr/ultra11y@v${VERSION}
|
|
@@ -62975,12 +62988,16 @@ function scoreboardBlocks(result, standard, lang, sheetHref) {
|
|
|
62975
62988
|
const scope = pagesOf(result);
|
|
62976
62989
|
if (!scope.length) return [];
|
|
62977
62990
|
const derived = derivePages(result, scope);
|
|
62978
|
-
const rows = derived.map((p) =>
|
|
62979
|
-
|
|
62980
|
-
|
|
62981
|
-
|
|
62982
|
-
|
|
62983
|
-
|
|
62991
|
+
const rows = derived.map((p) => {
|
|
62992
|
+
const criteria = pageCriterionRows(result, p, standard, lang);
|
|
62993
|
+
const cov = pageCoverage(criteria);
|
|
62994
|
+
return [
|
|
62995
|
+
{ text: `${p.name}${p.auth ? " \u{1F512}" : ""}`, ...sheetHref ? { href: sheetHref(p.id) } : {} },
|
|
62996
|
+
{ text: p.url, mono: true },
|
|
62997
|
+
{ text: basisLabel(p.basis, lang) },
|
|
62998
|
+
{ text: formatRate(pageRatePct(criteria), cov.decided, cov.total), align: "end" }
|
|
62999
|
+
];
|
|
63000
|
+
});
|
|
62984
63001
|
const out2 = [
|
|
62985
63002
|
{ kind: "heading", level: 2, text: t3.perPage, id: "pages" },
|
|
62986
63003
|
{
|
|
@@ -67062,6 +67079,9 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
67062
67079
|
]);
|
|
67063
67080
|
var KNOWN_FLAGS = /* @__PURE__ */ new Set([...VALUE_FLAGS2, ...BOOLEAN_FLAGS]);
|
|
67064
67081
|
var LIST_FLAGS = /* @__PURE__ */ new Set(["include", "exclude", "ext"]);
|
|
67082
|
+
function looksLikeFlag(token) {
|
|
67083
|
+
return token?.startsWith("--") ?? false;
|
|
67084
|
+
}
|
|
67065
67085
|
function parseArgs(argv) {
|
|
67066
67086
|
const [command, ...rest] = argv;
|
|
67067
67087
|
const valueFlags = valueFlagsFor(command ?? "");
|
|
@@ -67076,7 +67096,7 @@ function parseArgs(argv) {
|
|
|
67076
67096
|
const inlineVal = eq === -1 ? void 0 : a.slice(eq + 1);
|
|
67077
67097
|
let val;
|
|
67078
67098
|
if (inlineVal !== void 0) val = inlineVal;
|
|
67079
|
-
else if (valueFlags.has(key)) val = rest[++i2] ?? "";
|
|
67099
|
+
else if (valueFlags.has(key) && !looksLikeFlag(rest[i2 + 1])) val = rest[++i2] ?? "";
|
|
67080
67100
|
else val = true;
|
|
67081
67101
|
const prev = flags2[key];
|
|
67082
67102
|
if (LIST_FLAGS.has(key) && typeof prev === "string" && typeof val === "string") {
|
package/src/cli.ts
CHANGED
|
@@ -691,6 +691,23 @@ export interface ParsedArgs {
|
|
|
691
691
|
unknown: string[];
|
|
692
692
|
}
|
|
693
693
|
|
|
694
|
+
/** Is this token the NEXT FLAG rather than the previous flag's value?
|
|
695
|
+
*
|
|
696
|
+
* Several value flags take an OPTIONAL value — `--ledger` bare means the standard's default
|
|
697
|
+
* path, `--require-decided` bare means the run's grid — and the bare form is almost always
|
|
698
|
+
* followed by another flag. Consumed blindly, the next flag became the value:
|
|
699
|
+
* `verify --apply … --ledger --lang fr` wrote a 33 KB verdict ledger to a file literally
|
|
700
|
+
* named `--lang`, never wrote `.ultra11y/verdicts/rgaa.json`, and — `--lang` having been
|
|
701
|
+
* eaten — printed the run in the wrong language. Nothing failed and nothing warned; the next
|
|
702
|
+
* run simply paid for the whole adjudication again, which is the cost the ledger exists to
|
|
703
|
+
* remove.
|
|
704
|
+
*
|
|
705
|
+
* Only `--` counts. A single dash is the stdin positional, and a value may legitimately be
|
|
706
|
+
* negative or start with one. */
|
|
707
|
+
function looksLikeFlag(token: string | undefined): boolean {
|
|
708
|
+
return token?.startsWith("--") ?? false;
|
|
709
|
+
}
|
|
710
|
+
|
|
694
711
|
export function parseArgs(argv: string[]): ParsedArgs {
|
|
695
712
|
const [command, ...rest] = argv;
|
|
696
713
|
const valueFlags = valueFlagsFor(command ?? "");
|
|
@@ -708,7 +725,7 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
708
725
|
const inlineVal = eq === -1 ? undefined : a.slice(eq + 1);
|
|
709
726
|
let val: string | boolean;
|
|
710
727
|
if (inlineVal !== undefined) val = inlineVal;
|
|
711
|
-
else if (valueFlags.has(key)) val = rest[++i] ?? "";
|
|
728
|
+
else if (valueFlags.has(key) && !looksLikeFlag(rest[i + 1])) val = rest[++i] ?? "";
|
|
712
729
|
else val = true;
|
|
713
730
|
const prev = flags[key];
|
|
714
731
|
if (LIST_FLAGS.has(key) && typeof prev === "string" && typeof val === "string") {
|
package/src/html-report.ts
CHANGED
|
@@ -301,12 +301,19 @@ export function scoreboardBlocks(result: AuditResult, standard: StandardId, lang
|
|
|
301
301
|
const scope = pagesOf(result);
|
|
302
302
|
if (!scope.length) return [];
|
|
303
303
|
const derived = derivePages(result, scope);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
304
|
+
// Through the standard-aware helpers, exactly like `renderPageRates` in src/report.ts:
|
|
305
|
+
// `PageResult.conformancePct` is the WCAG core projection, so under a pack this cell used to
|
|
306
|
+
// quote a rate out of 55 beside a document counting 106.
|
|
307
|
+
const rows = derived.map((p) => {
|
|
308
|
+
const criteria = pageCriterionRows(result, p, standard, lang);
|
|
309
|
+
const cov = pageCoverage(criteria);
|
|
310
|
+
return [
|
|
311
|
+
{ text: `${p.name}${p.auth ? " 🔒" : ""}`, ...(sheetHref ? { href: sheetHref(p.id) } : {}) },
|
|
312
|
+
{ text: p.url, mono: true },
|
|
313
|
+
{ text: basisLabel(p.basis, lang) },
|
|
314
|
+
{ text: formatRate(pageRatePct(criteria), cov.decided, cov.total), align: "end" as const },
|
|
315
|
+
];
|
|
316
|
+
});
|
|
310
317
|
const out: Block[] = [
|
|
311
318
|
{ kind: "heading", level: 2, text: t.perPage, id: "pages" },
|
|
312
319
|
{
|
package/src/init.ts
CHANGED
package/src/pages.ts
CHANGED
|
@@ -489,6 +489,25 @@ export function renderRedirected(redirected: ScanRedirect[], lang: Lang = "en"):
|
|
|
489
489
|
return out;
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
+
/** One page's standing over the rows of the grid it is printed on — same C ÷ (C + NC) basis
|
|
493
|
+
* as everywhere else, and the same NULL for "nothing decided". Taking it from the grid model
|
|
494
|
+
* rather than from `PageResult` is what makes the header a summary of the body under a pack
|
|
495
|
+
* as well as under the core: `PageResult` only ever carries the WCAG projection.
|
|
496
|
+
*
|
|
497
|
+
* Returned as the argument tuple `formatRate` takes, so no caller can pair a rate with
|
|
498
|
+
* somebody else's denominator. */
|
|
499
|
+
export function gridRate(rows: { id: string }[], status: Map<string, Map<string, Status>>, pageId: string): [number | null, number, number] {
|
|
500
|
+
let c = 0;
|
|
501
|
+
let nc = 0;
|
|
502
|
+
for (const row of rows) {
|
|
503
|
+
const st = status.get(row.id)?.get(pageId);
|
|
504
|
+
if (st === "C") c++;
|
|
505
|
+
else if (st === "NC") nc++;
|
|
506
|
+
}
|
|
507
|
+
const decided = c + nc;
|
|
508
|
+
return [decided === 0 ? null : Math.round((c / decided) * 100), decided, rows.length];
|
|
509
|
+
}
|
|
510
|
+
|
|
492
511
|
/** The Markdown grid: one row per criterion, one column per page. */
|
|
493
512
|
export function renderPageGrid(result: AuditResult, pages: PageScope[], standard: StandardId = CORE, lang: Lang = "en"): string {
|
|
494
513
|
const s = L[lang];
|
|
@@ -504,10 +523,17 @@ export function renderPageGrid(result: AuditResult, pages: PageScope[], standard
|
|
|
504
523
|
|
|
505
524
|
const head = [isCore(standard) ? s.criterion : s.criterion, ...derived.map((p) => `${p.name}${p.auth ? " 🔒" : ""}`)];
|
|
506
525
|
out.push(`| ${head.join(" | ")} |`, `| ${head.map(() => "---").join(" | ")} |`);
|
|
507
|
-
|
|
526
|
+
// THE MODEL FIRST, because the rate is a summary OF THIS GRID and has to be computed from
|
|
527
|
+
// the rows it is printed above. It was read off `PageResult` instead — which is always the
|
|
528
|
+
// WCAG core projection — so under a pack the header and the body answered different
|
|
529
|
+
// questions: measured on a two-page RGAA run, the grid listed all 106 criteria and then
|
|
530
|
+
// announced « 89 % (9/55) », while the same page's own dossier said 47/106. Same command,
|
|
531
|
+
// same page, two denominators; and this grid is what `comment-kind: pages` pastes onto a
|
|
532
|
+
// pull request, so the wrong one was the one people read.
|
|
533
|
+
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
534
|
+
out.push(`| **${s.rate}** | ${derived.map((p) => `**${formatRate(...gridRate(rows, status, p.id))}**`).join(" | ")} |`);
|
|
508
535
|
out.push(`| _${s.snapshot}?_ | ${derived.map((p) => `_${basisLabel(p.basis, lang)}_`).join(" | ")} |`);
|
|
509
536
|
|
|
510
|
-
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
511
537
|
let group = "";
|
|
512
538
|
for (const row of rows) {
|
|
513
539
|
if (row.group !== group) {
|
package/src/scan.ts
CHANGED
|
@@ -741,15 +741,33 @@ export function mergeSnapshotAudit(base: AuditResult, snap: AuditResult): AuditR
|
|
|
741
741
|
if (snap.packFindings?.length) merged.packFindings = [...(merged.packFindings ?? []), ...snap.packFindings];
|
|
742
742
|
|
|
743
743
|
// A criterion the static pass closed for want of a subject, which the snapshot then MEASURED.
|
|
744
|
-
// Both read `C`, so the discriminator is the
|
|
745
|
-
//
|
|
744
|
+
// Both read `C`, so the discriminator is the FLAG, not the status — and testing the status
|
|
745
|
+
// is exactly what this loop used to do. The snapshot pages have no time-based media either,
|
|
746
|
+
// so they closed 1.2.1 the same way the source did, `C` + `inapplicable`; reading only the
|
|
747
|
+
// status, the merge took that for a measurement and stripped the flag from every criterion
|
|
748
|
+
// both halves had closed for want of a subject.
|
|
749
|
+
//
|
|
750
|
+
// Two things then broke, both silently. The `C` lost the justification that made it
|
|
751
|
+
// falsifiable, becoming the bare uncited conformity this engine refuses everywhere else. And
|
|
752
|
+
// `pageStatus` reads `inapplicable` to hold "a conformity reached for want of a subject
|
|
753
|
+
// holds on every page" — without the flag those criteria fell through to the honesty rule
|
|
754
|
+
// that only `static` criteria earn a verdict by silence, and came back « à évaluer » on
|
|
755
|
+
// EVERY page. Measured on a two-page RGAA crawl: the run reported a complete 106/106 grid
|
|
756
|
+
// while each page carried 10 criteria nobody could ever adjudicate, because there was
|
|
757
|
+
// nothing there to adjudicate.
|
|
746
758
|
for (const c of merged.criteria) {
|
|
747
759
|
if (!c.inapplicable) continue;
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
760
|
+
const measured = snapById.get(c.id);
|
|
761
|
+
// Only a snapshot that reached `C` WITHOUT the flag has falsified "nothing of that kind
|
|
762
|
+
// here" — that one looked at the rendered page and found the subject.
|
|
763
|
+
if (!measured || measured.status !== "C" || measured.inapplicable) continue;
|
|
764
|
+
c.status = "C";
|
|
765
|
+
delete c.inapplicable;
|
|
766
|
+
// Keep the claim citable: adopt what the measuring half said, rather than leaving a `C`
|
|
767
|
+
// with nothing behind it.
|
|
768
|
+
if (measured.justification) c.justification = measured.justification;
|
|
769
|
+
else delete c.justification;
|
|
770
|
+
if (measured.decidedBy) c.decidedBy = measured.decidedBy;
|
|
753
771
|
}
|
|
754
772
|
|
|
755
773
|
const nowNc = new Set(snap.findings.filter((f) => !f.advisory).map((f) => f.criteriaId));
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Single source of truth for shared types + the embedded version. sync-version.mjs
|
|
2
2
|
// rewrites VERSION at release time (kept in lockstep with package.json + SKILL.md).
|
|
3
|
-
export const VERSION = "5.
|
|
3
|
+
export const VERSION = "5.17.0";
|
|
4
4
|
export const SCHEMA_VERSION = 2;
|
|
5
5
|
|
|
6
6
|
// Lang = the UI FRAME's language — the `L` localization tables each module (report,
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
interface FindingLike {
|
|
2
|
-
ruleId: string;
|
|
3
|
-
criteriaId: string;
|
|
4
|
-
file: string;
|
|
5
|
-
line: number;
|
|
6
|
-
severity: string;
|
|
7
|
-
message: string;
|
|
8
|
-
advisory?: boolean;
|
|
9
|
-
origin?: {
|
|
10
|
-
sourceFile?: string;
|
|
11
|
-
sourceLine?: number;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
interface AuditLike {
|
|
15
|
-
findings?: FindingLike[];
|
|
16
|
-
}
|
|
17
|
-
interface SnapshotPayload {
|
|
18
|
-
meta: Record<string, unknown>;
|
|
19
|
-
dom: string;
|
|
20
|
-
styles?: unknown;
|
|
21
|
-
boxes?: unknown;
|
|
22
|
-
css?: unknown;
|
|
23
|
-
screenshot?: string;
|
|
24
|
-
/** What the live probes measured, when the caller ran them. */
|
|
25
|
-
probes?: unknown;
|
|
26
|
-
}
|
|
27
|
-
/** Same slug rule as the engine (src/snapshot.ts slugifyPageId): accent-folded URL path. */
|
|
28
|
-
declare function slugify(url: string): string;
|
|
29
|
-
interface CheckOptions {
|
|
30
|
-
/** The page id (directory name). Defaults to the slugified URL path. */
|
|
31
|
-
as?: string;
|
|
32
|
-
/** Human page name for the report. Defaults to the document title. */
|
|
33
|
-
name?: string;
|
|
34
|
-
/** Severity at which the check fails, or `false` to record without ever failing. */
|
|
35
|
-
failOn?: "blocking" | "major" | "minor" | false;
|
|
36
|
-
/** Mark the page as sitting behind authentication. */
|
|
37
|
-
auth?: boolean;
|
|
38
|
-
/** Source files that rendered it — findings are then reported against YOUR code. */
|
|
39
|
-
sources?: string[];
|
|
40
|
-
/** Reproduction notes carried into the auditor ticket. */
|
|
41
|
-
notes?: string;
|
|
42
|
-
/** Capture a viewport screenshot for the pixel tier (Playwright: on by default). */
|
|
43
|
-
screenshot?: boolean;
|
|
44
|
-
/** The path you NAVIGATED to. Given it, the fixture refuses to record a page the browser
|
|
45
|
-
* did not stay on — a guarded route that redirected, a session that expired — instead of
|
|
46
|
-
* filing that other screen under this page's `as`/`name`. Nothing about such a snapshot
|
|
47
|
-
* looks wrong, which is what makes it the worst thing an accessibility report can carry.
|
|
48
|
-
* Compared by path (a query or fragment the app appends to its own route is the same
|
|
49
|
-
* page); a full URL works too. `scan --sample` applies the same rule. */
|
|
50
|
-
expectPath?: string;
|
|
51
|
-
/** Run the LIVE PROBES on this page after collecting it: 200% zoom, 320px reflow, the
|
|
52
|
-
* text-spacing override, focus visibility and content-on-hover.
|
|
53
|
-
*
|
|
54
|
-
* These are the measurements a recorded snapshot can never settle, because they are
|
|
55
|
-
* properties of a page being acted on rather than of a page as it stands. `scan` runs them
|
|
56
|
-
* in its own browser, which is exactly why they were undecidable on any screen behind a
|
|
57
|
-
* login or a state machine — the scanner arrives cold and gets a redirect. Your suite has
|
|
58
|
-
* the page in the right state; this is how it says so.
|
|
59
|
-
*
|
|
60
|
-
* Off by default: the probes press Tab, hover, resize the viewport and inject a stylesheet,
|
|
61
|
-
* and a suite should opt into that. Everything is restored before it returns, and the
|
|
62
|
-
* snapshot has already been collected, so what they measure cannot alter what was recorded.
|
|
63
|
-
* Pass an array to run only some of them (by criterion: "1.4.4", "1.4.10", "1.4.12",
|
|
64
|
-
* "1.4.13", "2.4.7"), or an object to also set the bounds — how wide the reflow probe
|
|
65
|
-
* narrows to, how many focusables are worth tabbing through, how many hits are worth
|
|
66
|
-
* recording. 320px is normative and stays the default; the other two are judgements about
|
|
67
|
-
* YOUR pages, and a screen with 400 focusables should be able to say so rather than being
|
|
68
|
-
* quietly cut off at 120. */
|
|
69
|
-
probes?: boolean | string[] | ProbeTuning;
|
|
70
|
-
}
|
|
71
|
-
/** The bounds a repository may put on the live probes. Stated once, here, because the same
|
|
72
|
-
* list is read from `.ultra11yrc.json`, accepted at the call site and handed to the engine —
|
|
73
|
-
* three copies of a field list is three chances for one of them to fall behind.
|
|
74
|
-
*
|
|
75
|
-
* `budgetMs` and `actionTimeoutMs` are not tuning in the same sense as the others: they are
|
|
76
|
-
* what stops a probe from spending SOMEBODY ELSE'S test timeout. See ProbeLimits. */
|
|
77
|
-
interface ProbeTuning {
|
|
78
|
-
only?: string[];
|
|
79
|
-
reflowWidth?: number;
|
|
80
|
-
maxFocusables?: number;
|
|
81
|
-
maxHits?: number;
|
|
82
|
-
maxTriggers?: number;
|
|
83
|
-
actionTimeoutMs?: number;
|
|
84
|
-
budgetMs?: number;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export { type AuditLike as A, type CheckOptions as C, type FindingLike as F, type SnapshotPayload as S, slugify as s };
|