ultra11y 5.6.1 → 5.7.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/dist/cypress-plugin.d.ts +1 -1
- package/dist/cypress.d.ts +2 -2
- package/dist/payload-D8dvqLcc.d.ts +77 -0
- package/dist/playwright.d.ts +1 -1
- package/dist/playwright.mjs +30 -11
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +41 -20
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +41 -20
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/scripts/ultra11y.mjs +41 -20
- package/src/adjudicate.ts +65 -12
- package/src/config.ts +34 -0
- package/src/integrations/payload.ts +6 -2
- package/src/integrations/playwright.ts +33 -1
- package/src/probes.ts +26 -11
- package/src/types.ts +1 -1
|
@@ -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.7.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.7.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.7.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.7.0";
|
|
31
31
|
|
|
32
32
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
|
package/dist/cypress-plugin.d.ts
CHANGED
package/dist/cypress.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CheckOptions } from './payload-
|
|
2
|
-
export { s as slugify } from './payload-
|
|
1
|
+
import { C as CheckOptions } from './payload-D8dvqLcc.js';
|
|
2
|
+
export { s as slugify } from './payload-D8dvqLcc.js';
|
|
3
3
|
|
|
4
4
|
interface CypressCheckOptions extends CheckOptions {
|
|
5
5
|
/** Capture a viewport screenshot so the pixel tier can run. On by default — the plugin
|
|
@@ -0,0 +1,77 @@
|
|
|
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[] | {
|
|
70
|
+
only?: string[];
|
|
71
|
+
reflowWidth?: number;
|
|
72
|
+
maxFocusables?: number;
|
|
73
|
+
maxHits?: number;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { type AuditLike as A, type CheckOptions as C, type FindingLike as F, type SnapshotPayload as S, slugify as s };
|
package/dist/playwright.d.ts
CHANGED
package/dist/playwright.mjs
CHANGED
|
@@ -154,6 +154,7 @@ const __vis = (e) => {
|
|
|
154
154
|
};
|
|
155
155
|
const __html = (e) => (e.outerHTML || '').slice(0, 160);
|
|
156
156
|
`;
|
|
157
|
+
var PROBE_DEFAULTS = { reflowWidth: 320, maxFocusables: 120, maxHits: 20 };
|
|
157
158
|
var REFLOW_PROBE = `(() => {
|
|
158
159
|
const el = document.scrollingElement || document.documentElement;
|
|
159
160
|
return { horizontalScroll: el.scrollWidth > el.clientWidth + 2 };
|
|
@@ -193,7 +194,7 @@ var TEXT_SPACING_PROBE = `(() => { ${PRELUDE}
|
|
|
193
194
|
}
|
|
194
195
|
return hits;
|
|
195
196
|
})()`;
|
|
196
|
-
function focusSetupExpr(scope = "") {
|
|
197
|
+
function focusSetupExpr(scope = "", maxFocusables = PROBE_DEFAULTS.maxFocusables) {
|
|
197
198
|
const rootExpr = scope ? `document.querySelectorAll(${JSON.stringify(scope)})` : `[document.documentElement]`;
|
|
198
199
|
return `(() => { ${PRELUDE}
|
|
199
200
|
const sel = 'a[href],button:not([disabled]),input:not([type=hidden]):not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"]),[role=button]:not([disabled])';
|
|
@@ -227,7 +228,7 @@ function focusSetupExpr(scope = "") {
|
|
|
227
228
|
proxy.setAttribute('data-u11y-fp', key);
|
|
228
229
|
window.__u11yF[key] = { rest: snap(proxy), sel: __sel(proxy), html: __html(proxy) };
|
|
229
230
|
n++;
|
|
230
|
-
if (n >=
|
|
231
|
+
if (n >= ${maxFocusables}) break;
|
|
231
232
|
}
|
|
232
233
|
return n;
|
|
233
234
|
})()`;
|
|
@@ -266,12 +267,12 @@ function hoverVisibleExpr(id, wantHidden = false) {
|
|
|
266
267
|
const j = JSON.stringify(id);
|
|
267
268
|
return `(() => { const t = document.getElementById(${j}); if (!t) return ${wantHidden ? "true" : "false"}; const s = getComputedStyle(t); const shown = s.display !== 'none' && s.visibility !== 'hidden' && t.getBoundingClientRect().height > 0; return ${wantHidden ? "!shown" : "shown"}; })()`;
|
|
268
269
|
}
|
|
269
|
-
async function probeFocusVisible(page, scope = "") {
|
|
270
|
-
const count = await page.evaluate(focusSetupExpr(scope));
|
|
270
|
+
async function probeFocusVisible(page, scope = "", limits = PROBE_DEFAULTS) {
|
|
271
|
+
const count = await page.evaluate(focusSetupExpr(scope, limits.maxFocusables));
|
|
271
272
|
if (!count) return [];
|
|
272
273
|
const hits = [];
|
|
273
274
|
const seen = /* @__PURE__ */ new Set();
|
|
274
|
-
const limit = Math.min(count + 2,
|
|
275
|
+
const limit = Math.min(count + 2, limits.maxFocusables + 10);
|
|
275
276
|
for (let i = 0; i < limit; i++) {
|
|
276
277
|
await page.keyboard.press("Tab");
|
|
277
278
|
const r = await page.evaluate(FOCUS_CHECK_PROBE);
|
|
@@ -289,7 +290,7 @@ async function probeFocusVisible(page, scope = "") {
|
|
|
289
290
|
}
|
|
290
291
|
return hits;
|
|
291
292
|
}
|
|
292
|
-
async function probeHover(page) {
|
|
293
|
+
async function probeHover(page, limits = PROBE_DEFAULTS) {
|
|
293
294
|
const triggers = await page.evaluate(HOVER_SETUP_PROBE);
|
|
294
295
|
const hits = [];
|
|
295
296
|
for (const tr of triggers) {
|
|
@@ -313,11 +314,12 @@ async function probeHover(page) {
|
|
|
313
314
|
detail: `Le contenu r\xE9v\xE9l\xE9 au survol (aria-describedby #${tr.target}) ne se masque pas avec \xC9chap \u2014 Contenu au survol ou au focus (1.4.13).`
|
|
314
315
|
});
|
|
315
316
|
}
|
|
316
|
-
if (hits.length >= 8) break;
|
|
317
|
+
if (hits.length >= Math.min(limits.maxHits, 8)) break;
|
|
317
318
|
}
|
|
318
319
|
return hits;
|
|
319
320
|
}
|
|
320
321
|
async function runLiveProbes(page, opts = {}) {
|
|
322
|
+
const limits = { ...PROBE_DEFAULTS, ...opts.limits };
|
|
321
323
|
const only = opts.only?.length ? new Set(opts.only) : null;
|
|
322
324
|
const want = (id) => only === null || only.has(id);
|
|
323
325
|
const canResize = typeof page.setViewportSize === "function" && typeof page.viewportSize === "function";
|
|
@@ -335,7 +337,7 @@ async function runLiveProbes(page, opts = {}) {
|
|
|
335
337
|
if (!canHover) skip("1.4.13", "the page object cannot hover");
|
|
336
338
|
if (!canStyle) skip("1.4.12", "the page object cannot inject a stylesheet");
|
|
337
339
|
if (want("2.4.7") && canType) {
|
|
338
|
-
const r = await probeFocusVisible(page).catch((e) => {
|
|
340
|
+
const r = await probeFocusVisible(page, "", limits).catch((e) => {
|
|
339
341
|
skip("2.4.7", String(e?.message ?? e).slice(0, 160));
|
|
340
342
|
return null;
|
|
341
343
|
});
|
|
@@ -345,7 +347,7 @@ async function runLiveProbes(page, opts = {}) {
|
|
|
345
347
|
}
|
|
346
348
|
}
|
|
347
349
|
if (want("1.4.13") && canHover && canType) {
|
|
348
|
-
const r = await probeHover(page).catch((e) => {
|
|
350
|
+
const r = await probeHover(page, limits).catch((e) => {
|
|
349
351
|
skip("1.4.13", String(e?.message ?? e).slice(0, 160));
|
|
350
352
|
return null;
|
|
351
353
|
});
|
|
@@ -360,7 +362,7 @@ async function runLiveProbes(page, opts = {}) {
|
|
|
360
362
|
}
|
|
361
363
|
if (want("1.4.10") && canResize) {
|
|
362
364
|
let narrowed = true;
|
|
363
|
-
await page.setViewportSize({ width:
|
|
365
|
+
await page.setViewportSize({ width: limits.reflowWidth, height: restore.height }).catch((e) => {
|
|
364
366
|
narrowed = false;
|
|
365
367
|
skip("1.4.10", String(e?.message ?? e).slice(0, 160));
|
|
366
368
|
});
|
|
@@ -525,6 +527,23 @@ function writePagesReport(opts = {}, engine = enginePath()) {
|
|
|
525
527
|
}
|
|
526
528
|
|
|
527
529
|
// src/integrations/playwright.ts
|
|
530
|
+
function probeOptions(p) {
|
|
531
|
+
if (p === true || p === void 0) return fromConfig();
|
|
532
|
+
if (p === false) return {};
|
|
533
|
+
if (Array.isArray(p)) return { only: p, ...fromConfig() };
|
|
534
|
+
const { only, ...limits } = p;
|
|
535
|
+
return { ...only ? { only } : {}, limits };
|
|
536
|
+
}
|
|
537
|
+
function fromConfig() {
|
|
538
|
+
try {
|
|
539
|
+
const cfg = JSON.parse(readFileSync(join(process.cwd(), ".ultra11yrc.json"), "utf8"));
|
|
540
|
+
if (!cfg.probes) return {};
|
|
541
|
+
const { only, ...limits } = cfg.probes;
|
|
542
|
+
return { ...only ? { only } : {}, limits };
|
|
543
|
+
} catch {
|
|
544
|
+
return {};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
528
547
|
async function checkA11y(page, opts = {}) {
|
|
529
548
|
const collected = await page.evaluate(COLLECT_SNAPSHOT);
|
|
530
549
|
let shot;
|
|
@@ -543,7 +562,7 @@ async function checkA11y(page, opts = {}) {
|
|
|
543
562
|
let probes;
|
|
544
563
|
if (opts.probes) {
|
|
545
564
|
try {
|
|
546
|
-
probes = await runLiveProbes(page,
|
|
565
|
+
probes = await runLiveProbes(page, probeOptions(opts.probes));
|
|
547
566
|
} catch (e) {
|
|
548
567
|
console.warn(
|
|
549
568
|
`ultra11y: the live probes failed on this page \u2014 ${e instanceof Error ? e.message : String(e)}. The snapshot was still recorded; the criteria they decide stay to assess.`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.7.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.7.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -57421,6 +57421,7 @@ function adjudicationText(sc, lang) {
|
|
|
57421
57421
|
// src/adjudicate.ts
|
|
57422
57422
|
var ADJUDICATE_MAX_EVIDENCE_CLASSES = 1200;
|
|
57423
57423
|
var ALSO_AT_SHOWN = 8;
|
|
57424
|
+
var CITE_DRIFT_DEFAULT = 10;
|
|
57424
57425
|
function adjudicationContract() {
|
|
57425
57426
|
return {
|
|
57426
57427
|
verdicts: [...VERDICTS],
|
|
@@ -57448,7 +57449,23 @@ function docsForAudit(audit2, cwd) {
|
|
|
57448
57449
|
}
|
|
57449
57450
|
return docs;
|
|
57450
57451
|
}
|
|
57451
|
-
|
|
57452
|
+
var ADJUDICATION_DEFAULTS = {
|
|
57453
|
+
maxClasses: ADJUDICATE_MAX_EVIDENCE_CLASSES,
|
|
57454
|
+
showAlsoAt: ALSO_AT_SHOWN,
|
|
57455
|
+
citationDrift: CITE_DRIFT_DEFAULT
|
|
57456
|
+
};
|
|
57457
|
+
function adjudicationLimits(cwd) {
|
|
57458
|
+
const cfg = loadConfig(cwd ?? ".")?.adjudication;
|
|
57459
|
+
if (!cfg) return ADJUDICATION_DEFAULTS;
|
|
57460
|
+
const positive2 = (v, fallback) => typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.floor(v) : fallback;
|
|
57461
|
+
return {
|
|
57462
|
+
maxClasses: positive2(cfg.maxClasses, ADJUDICATION_DEFAULTS.maxClasses),
|
|
57463
|
+
showAlsoAt: positive2(cfg.showAlsoAt, ADJUDICATION_DEFAULTS.showAlsoAt),
|
|
57464
|
+
// 0 is meaningful here (exact-anchor matching), so it is allowed through.
|
|
57465
|
+
citationDrift: typeof cfg.citationDrift === "number" && Number.isFinite(cfg.citationDrift) && cfg.citationDrift >= 0 ? Math.floor(cfg.citationDrift) : ADJUDICATION_DEFAULTS.citationDrift
|
|
57466
|
+
};
|
|
57467
|
+
}
|
|
57468
|
+
function collapse2(harvested, limits) {
|
|
57452
57469
|
const byClass = /* @__PURE__ */ new Map();
|
|
57453
57470
|
for (const item of harvested) {
|
|
57454
57471
|
const g = byClass.get(item.cls);
|
|
@@ -57456,7 +57473,7 @@ function collapse2(harvested) {
|
|
|
57456
57473
|
else byClass.set(item.cls, [item]);
|
|
57457
57474
|
}
|
|
57458
57475
|
const groups = [...byClass.values()];
|
|
57459
|
-
const evidence = groups.slice(0,
|
|
57476
|
+
const evidence = groups.slice(0, limits.maxClasses).map((group) => {
|
|
57460
57477
|
const rep = group.find((x) => isSnapshotFile(x.ev.file)) ?? group[0];
|
|
57461
57478
|
const others = group.filter((x) => x !== rep);
|
|
57462
57479
|
const pages = [...new Set(group.map((x) => pageOfDoc(x.ev.file)).filter((x) => x !== void 0))];
|
|
@@ -57475,11 +57492,11 @@ function collapse2(harvested) {
|
|
|
57475
57492
|
files: new Set(harvested.map((x) => x.ev.file)).size,
|
|
57476
57493
|
pages: new Set(harvested.map((x) => pageOfDoc(x.ev.file)).filter((x) => x !== void 0)).size
|
|
57477
57494
|
},
|
|
57478
|
-
complete: groups.length <=
|
|
57495
|
+
complete: groups.length <= limits.maxClasses
|
|
57479
57496
|
};
|
|
57480
57497
|
}
|
|
57481
|
-
function blankItem(criteriaId, automatability2, title2, harvested) {
|
|
57482
|
-
const { evidence, population, complete } = collapse2(harvested);
|
|
57498
|
+
function blankItem(criteriaId, automatability2, title2, harvested, limits) {
|
|
57499
|
+
const { evidence, population, complete } = collapse2(harvested, limits);
|
|
57483
57500
|
return {
|
|
57484
57501
|
criteriaId,
|
|
57485
57502
|
automatability: automatability2,
|
|
@@ -57507,6 +57524,7 @@ function subjectsForPackCriterion(standard, id, scs) {
|
|
|
57507
57524
|
function buildAdjudicationWorklist(audit2, opts = {}) {
|
|
57508
57525
|
const docs = docsForAudit(audit2, opts.cwd);
|
|
57509
57526
|
const standard = opts.standard;
|
|
57527
|
+
const limits = adjudicationLimits(opts.cwd);
|
|
57510
57528
|
if (standard !== void 0 && !isCore(standard)) {
|
|
57511
57529
|
const pack = loadPack(standard);
|
|
57512
57530
|
return derivePackResults(audit2, standard).filter((pc) => pc.status === "manual").map((pc) => {
|
|
@@ -57518,12 +57536,13 @@ function buildAdjudicationWorklist(audit2, opts = {}) {
|
|
|
57518
57536
|
pc.id,
|
|
57519
57537
|
automatability2,
|
|
57520
57538
|
crit ? titlePlain(pack, crit, "fr") : void 0,
|
|
57521
|
-
harvestSubjects(subjectsForPackCriterion(standard, pc.id, scs), docs)
|
|
57539
|
+
harvestSubjects(subjectsForPackCriterion(standard, pc.id, scs), docs),
|
|
57540
|
+
limits
|
|
57522
57541
|
);
|
|
57523
57542
|
});
|
|
57524
57543
|
}
|
|
57525
57544
|
return audit2.residualRisks.map(
|
|
57526
|
-
(r) => blankItem(r.criteriaId, r.automatability, scTitle(r.criteriaId) ?? void 0, harvestSubjects(subjectsForSc(r.criteriaId), docs))
|
|
57545
|
+
(r) => blankItem(r.criteriaId, r.automatability, scTitle(r.criteriaId) ?? void 0, harvestSubjects(subjectsForSc(r.criteriaId), docs), limits)
|
|
57527
57546
|
);
|
|
57528
57547
|
}
|
|
57529
57548
|
function readCitation(c2) {
|
|
@@ -57544,14 +57563,13 @@ function auditFiles(audit2, cwd) {
|
|
|
57544
57563
|
return /* @__PURE__ */ new Set();
|
|
57545
57564
|
}
|
|
57546
57565
|
}
|
|
57547
|
-
|
|
57548
|
-
function cites0(anchors, c2) {
|
|
57566
|
+
function cites0(anchors, c2, drift) {
|
|
57549
57567
|
for (const a of anchors) {
|
|
57550
57568
|
const at = a.lastIndexOf(":");
|
|
57551
57569
|
if (at < 0) continue;
|
|
57552
57570
|
if (a.slice(0, at) !== c2.file) continue;
|
|
57553
57571
|
const line = Number.parseInt(a.slice(at + 1), 10);
|
|
57554
|
-
if (Number.isFinite(line) && Math.abs(line - c2.line) <=
|
|
57572
|
+
if (Number.isFinite(line) && Math.abs(line - c2.line) <= drift) return true;
|
|
57555
57573
|
}
|
|
57556
57574
|
return false;
|
|
57557
57575
|
}
|
|
@@ -57642,6 +57660,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
57642
57660
|
}
|
|
57643
57661
|
const groundInputs = /* @__PURE__ */ new Map();
|
|
57644
57662
|
let scopeCache;
|
|
57663
|
+
const { citationDrift } = adjudicationLimits(opts.cwd);
|
|
57645
57664
|
const scopeFiles = () => scopeCache ??= auditFiles(audit2, opts.cwd);
|
|
57646
57665
|
const toGround = (criteriaId, g) => {
|
|
57647
57666
|
const list = groundInputs.get(criteriaId);
|
|
@@ -57676,7 +57695,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
57676
57695
|
} else if (cites.length > 0) {
|
|
57677
57696
|
const anchors = it.evidence.flatMap((e) => [`${e.file}:${e.line}`, ...e.alsoAt ?? []]);
|
|
57678
57697
|
for (const c2 of cites) {
|
|
57679
|
-
if (!cites0(anchors, c2) && !scopeFiles().has(c2.file)) {
|
|
57698
|
+
if (!cites0(anchors, c2, citationDrift) && !scopeFiles().has(c2.file)) {
|
|
57680
57699
|
blame(it.criteriaId, `criterion ${it.criteriaId}: citation ${c2.file}:${c2.line} is not among this criterion's harvested evidence (fabricated?)`);
|
|
57681
57700
|
}
|
|
57682
57701
|
toGround(it.criteriaId, { file: c2.file, line: c2.line, selector: c2.selector, snippet: c2.snippet });
|
|
@@ -57934,6 +57953,7 @@ function plainTest(s) {
|
|
|
57934
57953
|
}
|
|
57935
57954
|
function formatAdjudication(items, lang = "en", standard = CORE2, opts = {}) {
|
|
57936
57955
|
const s = T2[lang];
|
|
57956
|
+
const { showAlsoAt: shown } = adjudicationLimits(opts.cwd);
|
|
57937
57957
|
const pack = isCore(standard) ? void 0 : loadPack(standard);
|
|
57938
57958
|
const out2 = opts.preamble === false ? [] : [s.title, "", s.intro, "", ...s.verdicts, "", s.rule, "", s.then, ""];
|
|
57939
57959
|
if (pack && opts.preamble !== false) out2.push(`> ${s.packIntro(pack.name)}`, "");
|
|
@@ -57948,7 +57968,7 @@ function formatAdjudication(items, lang = "en", standard = CORE2, opts = {}) {
|
|
|
57948
57968
|
const extra = [
|
|
57949
57969
|
e.occurrences && e.occurrences > 1 ? `\xD7${e.occurrences}` : "",
|
|
57950
57970
|
e.pages?.length ? `${s.on} ${e.pages.slice(0, 4).join(", ")}${e.pages.length > 4 ? "\u2026" : ""}` : "",
|
|
57951
|
-
e.alsoAt?.length ? `${s.alsoAt} ${e.alsoAt.slice(0,
|
|
57971
|
+
e.alsoAt?.length ? `${s.alsoAt} ${e.alsoAt.slice(0, shown).join(", ")}${e.alsoAt.length > shown ? "\u2026" : ""}` : ""
|
|
57952
57972
|
].filter(Boolean).join(" ");
|
|
57953
57973
|
out2.push(`- \`${e.file}:${e.line}\` (\`${e.selector}\`)${extra ? ` [${extra}]` : ""}${e.note ? ` \u2014 ${e.note}` : ""}`);
|
|
57954
57974
|
}
|
|
@@ -59079,6 +59099,7 @@ const __vis = (e) => {
|
|
|
59079
59099
|
};
|
|
59080
59100
|
const __html = (e) => (e.outerHTML || '').slice(0, 160);
|
|
59081
59101
|
`;
|
|
59102
|
+
var PROBE_DEFAULTS = { reflowWidth: 320, maxFocusables: 120, maxHits: 20 };
|
|
59082
59103
|
var REFLOW_PROBE = `(() => {
|
|
59083
59104
|
const el = document.scrollingElement || document.documentElement;
|
|
59084
59105
|
return { horizontalScroll: el.scrollWidth > el.clientWidth + 2 };
|
|
@@ -59118,7 +59139,7 @@ var TEXT_SPACING_PROBE = `(() => { ${PRELUDE}
|
|
|
59118
59139
|
}
|
|
59119
59140
|
return hits;
|
|
59120
59141
|
})()`;
|
|
59121
|
-
function focusSetupExpr(scope = "") {
|
|
59142
|
+
function focusSetupExpr(scope = "", maxFocusables = PROBE_DEFAULTS.maxFocusables) {
|
|
59122
59143
|
const rootExpr = scope ? `document.querySelectorAll(${JSON.stringify(scope)})` : `[document.documentElement]`;
|
|
59123
59144
|
return `(() => { ${PRELUDE}
|
|
59124
59145
|
const sel = 'a[href],button:not([disabled]),input:not([type=hidden]):not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"]),[role=button]:not([disabled])';
|
|
@@ -59152,7 +59173,7 @@ function focusSetupExpr(scope = "") {
|
|
|
59152
59173
|
proxy.setAttribute('data-u11y-fp', key);
|
|
59153
59174
|
window.__u11yF[key] = { rest: snap(proxy), sel: __sel(proxy), html: __html(proxy) };
|
|
59154
59175
|
n++;
|
|
59155
|
-
if (n >=
|
|
59176
|
+
if (n >= ${maxFocusables}) break;
|
|
59156
59177
|
}
|
|
59157
59178
|
return n;
|
|
59158
59179
|
})()`;
|
|
@@ -59191,12 +59212,12 @@ function hoverVisibleExpr(id, wantHidden = false) {
|
|
|
59191
59212
|
const j = JSON.stringify(id);
|
|
59192
59213
|
return `(() => { const t = document.getElementById(${j}); if (!t) return ${wantHidden ? "true" : "false"}; const s = getComputedStyle(t); const shown = s.display !== 'none' && s.visibility !== 'hidden' && t.getBoundingClientRect().height > 0; return ${wantHidden ? "!shown" : "shown"}; })()`;
|
|
59193
59214
|
}
|
|
59194
|
-
async function probeFocusVisible(page, scope = "") {
|
|
59195
|
-
const count = await page.evaluate(focusSetupExpr(scope));
|
|
59215
|
+
async function probeFocusVisible(page, scope = "", limits = PROBE_DEFAULTS) {
|
|
59216
|
+
const count = await page.evaluate(focusSetupExpr(scope, limits.maxFocusables));
|
|
59196
59217
|
if (!count) return [];
|
|
59197
59218
|
const hits = [];
|
|
59198
59219
|
const seen = /* @__PURE__ */ new Set();
|
|
59199
|
-
const limit = Math.min(count + 2,
|
|
59220
|
+
const limit = Math.min(count + 2, limits.maxFocusables + 10);
|
|
59200
59221
|
for (let i2 = 0; i2 < limit; i2++) {
|
|
59201
59222
|
await page.keyboard.press("Tab");
|
|
59202
59223
|
const r = await page.evaluate(FOCUS_CHECK_PROBE);
|
|
@@ -59214,7 +59235,7 @@ async function probeFocusVisible(page, scope = "") {
|
|
|
59214
59235
|
}
|
|
59215
59236
|
return hits;
|
|
59216
59237
|
}
|
|
59217
|
-
async function probeHover(page) {
|
|
59238
|
+
async function probeHover(page, limits = PROBE_DEFAULTS) {
|
|
59218
59239
|
const triggers = await page.evaluate(HOVER_SETUP_PROBE);
|
|
59219
59240
|
const hits = [];
|
|
59220
59241
|
for (const tr of triggers) {
|
|
@@ -59238,7 +59259,7 @@ async function probeHover(page) {
|
|
|
59238
59259
|
detail: `Le contenu r\xE9v\xE9l\xE9 au survol (aria-describedby #${tr.target}) ne se masque pas avec \xC9chap \u2014 Contenu au survol ou au focus (1.4.13).`
|
|
59239
59260
|
});
|
|
59240
59261
|
}
|
|
59241
|
-
if (hits.length >= 8) break;
|
|
59262
|
+
if (hits.length >= Math.min(limits.maxHits, 8)) break;
|
|
59242
59263
|
}
|
|
59243
59264
|
return hits;
|
|
59244
59265
|
}
|
|
@@ -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.7.0
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# review-a11y — accessibility review of changed code
|