ultra11y 5.6.0 → 5.6.1
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/playwright.mjs +68 -22
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +1 -1
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +1 -1
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/scripts/ultra11y.mjs +1 -1
- package/src/integrations/playwright.ts +9 -2
- package/src/probes.ts +74 -20
- 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.6.
|
|
13
|
+
"version": "5.6.1",
|
|
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.6.
|
|
4
|
+
"version": "5.6.1",
|
|
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.6.
|
|
4
|
+
"version": "5.6.1",
|
|
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.6.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.6.1";
|
|
31
31
|
|
|
32
32
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
|
package/dist/playwright.mjs
CHANGED
|
@@ -320,37 +320,80 @@ async function probeHover(page) {
|
|
|
320
320
|
async function runLiveProbes(page, opts = {}) {
|
|
321
321
|
const only = opts.only?.length ? new Set(opts.only) : null;
|
|
322
322
|
const want = (id) => only === null || only.has(id);
|
|
323
|
-
const
|
|
323
|
+
const canResize = typeof page.setViewportSize === "function" && typeof page.viewportSize === "function";
|
|
324
|
+
const canType = !!page.keyboard && typeof page.keyboard.press === "function";
|
|
325
|
+
const canHover = typeof page.hover === "function" && typeof page.waitForTimeout === "function";
|
|
326
|
+
const canStyle = typeof page.addStyleTag === "function";
|
|
327
|
+
const size = canResize ? page.viewportSize() ?? null : null;
|
|
324
328
|
const restore = size ?? { width: 1280, height: 900 };
|
|
325
|
-
const out = { focusVisible: [], hover: [], reflowZoom: [], textSpacing: [], reflow: { horizontalScroll: false }, probed: [] };
|
|
326
|
-
|
|
327
|
-
out.
|
|
328
|
-
|
|
329
|
+
const out = { focusVisible: [], hover: [], reflowZoom: [], textSpacing: [], reflow: { horizontalScroll: false }, probed: [], skipped: [] };
|
|
330
|
+
const skip = (sc, why) => {
|
|
331
|
+
out.skipped?.push({ sc, why });
|
|
332
|
+
};
|
|
333
|
+
if (!canResize) skip("1.4.10", "the page object cannot resize its viewport");
|
|
334
|
+
if (!canType) skip("2.4.7", "the page object exposes no keyboard");
|
|
335
|
+
if (!canHover) skip("1.4.13", "the page object cannot hover");
|
|
336
|
+
if (!canStyle) skip("1.4.12", "the page object cannot inject a stylesheet");
|
|
337
|
+
if (want("2.4.7") && canType) {
|
|
338
|
+
const r = await probeFocusVisible(page).catch((e) => {
|
|
339
|
+
skip("2.4.7", String(e?.message ?? e).slice(0, 160));
|
|
340
|
+
return null;
|
|
341
|
+
});
|
|
342
|
+
if (r) {
|
|
343
|
+
out.focusVisible = r;
|
|
344
|
+
out.probed.push("2.4.7");
|
|
345
|
+
}
|
|
329
346
|
}
|
|
330
|
-
if (want("1.4.13")) {
|
|
331
|
-
|
|
332
|
-
|
|
347
|
+
if (want("1.4.13") && canHover && canType) {
|
|
348
|
+
const r = await probeHover(page).catch((e) => {
|
|
349
|
+
skip("1.4.13", String(e?.message ?? e).slice(0, 160));
|
|
350
|
+
return null;
|
|
351
|
+
});
|
|
352
|
+
if (r) {
|
|
353
|
+
out.hover = r;
|
|
354
|
+
out.probed.push("1.4.13");
|
|
355
|
+
}
|
|
333
356
|
}
|
|
334
357
|
if (want("1.4.4")) {
|
|
335
358
|
out.reflowZoom = await page.evaluate(REFLOW_ZOOM_PROBE).catch(() => []) ?? [];
|
|
336
359
|
out.probed.push("1.4.4");
|
|
337
360
|
}
|
|
338
|
-
if (want("1.4.10")) {
|
|
339
|
-
|
|
361
|
+
if (want("1.4.10") && canResize) {
|
|
362
|
+
let narrowed = true;
|
|
363
|
+
await page.setViewportSize({ width: 320, height: restore.height }).catch((e) => {
|
|
364
|
+
narrowed = false;
|
|
365
|
+
skip("1.4.10", String(e?.message ?? e).slice(0, 160));
|
|
340
366
|
});
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
367
|
+
if (narrowed) {
|
|
368
|
+
const r = await page.evaluate(REFLOW_PROBE).catch((e) => {
|
|
369
|
+
skip("1.4.10", String(e?.message ?? e).slice(0, 160));
|
|
370
|
+
return null;
|
|
371
|
+
});
|
|
372
|
+
await page.setViewportSize(restore).catch(() => {
|
|
373
|
+
});
|
|
374
|
+
if (r) {
|
|
375
|
+
out.reflow = r;
|
|
376
|
+
out.probed.push("1.4.10");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
347
379
|
}
|
|
348
|
-
if (want("1.4.12")) {
|
|
349
|
-
const handle = await page.addStyleTag({ content: TEXT_SPACING_CSS }).catch(() =>
|
|
350
|
-
|
|
351
|
-
|
|
380
|
+
if (want("1.4.12") && canStyle) {
|
|
381
|
+
const handle = await page.addStyleTag({ content: TEXT_SPACING_CSS }).catch((e) => {
|
|
382
|
+
skip("1.4.12", String(e?.message ?? e).slice(0, 160));
|
|
383
|
+
return null;
|
|
352
384
|
});
|
|
353
|
-
|
|
385
|
+
if (handle) {
|
|
386
|
+
const r = await page.evaluate(TEXT_SPACING_PROBE).catch((e) => {
|
|
387
|
+
skip("1.4.12", String(e?.message ?? e).slice(0, 160));
|
|
388
|
+
return null;
|
|
389
|
+
});
|
|
390
|
+
await page.evaluate(REMOVE_TEXT_SPACING_STEP).catch(() => {
|
|
391
|
+
});
|
|
392
|
+
if (r) {
|
|
393
|
+
out.textSpacing = r;
|
|
394
|
+
out.probed.push("1.4.12");
|
|
395
|
+
}
|
|
396
|
+
}
|
|
354
397
|
}
|
|
355
398
|
return out;
|
|
356
399
|
}
|
|
@@ -501,7 +544,10 @@ async function checkA11y(page, opts = {}) {
|
|
|
501
544
|
if (opts.probes) {
|
|
502
545
|
try {
|
|
503
546
|
probes = await runLiveProbes(page, Array.isArray(opts.probes) ? { only: opts.probes } : {});
|
|
504
|
-
} catch {
|
|
547
|
+
} catch (e) {
|
|
548
|
+
console.warn(
|
|
549
|
+
`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.`
|
|
550
|
+
);
|
|
505
551
|
}
|
|
506
552
|
}
|
|
507
553
|
const payload = buildPayload(collected, url, "playwright", opts, shot, probes);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
|
-
"version": "5.6.
|
|
3
|
+
"version": "5.6.1",
|
|
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.6.
|
|
9
|
+
var VERSION = "5.6.1";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -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.6.
|
|
8
|
+
version: 5.6.1
|
|
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.6.
|
|
9
|
+
var VERSION = "5.6.1";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
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.6.
|
|
7
|
+
version: 5.6.1
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# ultra11y — audit WCAG 2.2 AA and write accessible markup
|
|
@@ -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.6.
|
|
9
|
+
var VERSION = "5.6.1";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -85,8 +85,15 @@ export async function checkA11y(page: PlaywrightPage, opts: PlaywrightCheckOptio
|
|
|
85
85
|
if (opts.probes) {
|
|
86
86
|
try {
|
|
87
87
|
probes = await runLiveProbes(page, Array.isArray(opts.probes) ? { only: opts.probes } : {});
|
|
88
|
-
} catch {
|
|
89
|
-
|
|
88
|
+
} catch (e) {
|
|
89
|
+
// LOUD, not silent. A swallowed probe failure is indistinguishable from a page with
|
|
90
|
+
// nothing wrong: the criteria it would have decided simply stay « to assess », run after
|
|
91
|
+
// run, with nobody able to tell whether they were measured and clean or never measured
|
|
92
|
+
// at all. That confusion is the exact failure mode this tool exists to prevent, so it
|
|
93
|
+
// must not be the one it ships. The page is still recorded either way.
|
|
94
|
+
console.warn(
|
|
95
|
+
`ultra11y: the live probes failed on this page — ${e instanceof Error ? e.message : String(e)}. The snapshot was still recorded; the criteria they decide stay to assess.`,
|
|
96
|
+
);
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
|
package/src/probes.ts
CHANGED
|
@@ -260,38 +260,87 @@ export async function probeHover(page: Any): Promise<ProbeHit[]> {
|
|
|
260
260
|
export async function runLiveProbes(page: Any, opts: { only?: string[] } = {}): Promise<LiveProbeResult> {
|
|
261
261
|
const only = opts.only?.length ? new Set(opts.only) : null;
|
|
262
262
|
const want = (id: string): boolean => only === null || only.has(id);
|
|
263
|
-
|
|
263
|
+
// Capability checks, not assumptions. A page object that cannot resize or press a key is a
|
|
264
|
+
// page whose reflow and focus-visibility simply were not measured — which is a fact to
|
|
265
|
+
// record, not a crash to propagate into somebody's test run.
|
|
266
|
+
const canResize = typeof page.setViewportSize === "function" && typeof page.viewportSize === "function";
|
|
267
|
+
const canType = !!page.keyboard && typeof page.keyboard.press === "function";
|
|
268
|
+
const canHover = typeof page.hover === "function" && typeof page.waitForTimeout === "function";
|
|
269
|
+
const canStyle = typeof page.addStyleTag === "function";
|
|
270
|
+
const size = (canResize ? (page.viewportSize() ?? null) : null) as { width: number; height: number } | null;
|
|
264
271
|
const restore = size ?? { width: 1280, height: 900 };
|
|
265
|
-
const out: LiveProbeResult = { focusVisible: [], hover: [], reflowZoom: [], textSpacing: [], reflow: { horizontalScroll: false }, probed: [] };
|
|
272
|
+
const out: LiveProbeResult = { focusVisible: [], hover: [], reflowZoom: [], textSpacing: [], reflow: { horizontalScroll: false }, probed: [], skipped: [] };
|
|
273
|
+
const skip = (sc: string, why: string): void => {
|
|
274
|
+
out.skipped?.push({ sc, why });
|
|
275
|
+
};
|
|
276
|
+
if (!canResize) skip("1.4.10", "the page object cannot resize its viewport");
|
|
277
|
+
if (!canType) skip("2.4.7", "the page object exposes no keyboard");
|
|
278
|
+
if (!canHover) skip("1.4.13", "the page object cannot hover");
|
|
279
|
+
if (!canStyle) skip("1.4.12", "the page object cannot inject a stylesheet");
|
|
266
280
|
// Each probe is guarded on its own: one that throws costs its criterion, never the others
|
|
267
281
|
// and never the caller's test.
|
|
268
|
-
if (want("2.4.7")) {
|
|
269
|
-
|
|
270
|
-
|
|
282
|
+
if (want("2.4.7") && canType) {
|
|
283
|
+
const r = await probeFocusVisible(page).catch((e: unknown) => {
|
|
284
|
+
skip("2.4.7", String((e as Error)?.message ?? e).slice(0, 160));
|
|
285
|
+
return null;
|
|
286
|
+
});
|
|
287
|
+
if (r) {
|
|
288
|
+
out.focusVisible = r;
|
|
289
|
+
out.probed.push("2.4.7");
|
|
290
|
+
}
|
|
271
291
|
}
|
|
272
|
-
if (want("1.4.13")) {
|
|
273
|
-
|
|
274
|
-
|
|
292
|
+
if (want("1.4.13") && canHover && canType) {
|
|
293
|
+
const r = await probeHover(page).catch((e: unknown) => {
|
|
294
|
+
skip("1.4.13", String((e as Error)?.message ?? e).slice(0, 160));
|
|
295
|
+
return null;
|
|
296
|
+
});
|
|
297
|
+
if (r) {
|
|
298
|
+
out.hover = r;
|
|
299
|
+
out.probed.push("1.4.13");
|
|
300
|
+
}
|
|
275
301
|
}
|
|
276
302
|
if (want("1.4.4")) {
|
|
277
303
|
out.reflowZoom = ((await page.evaluate(REFLOW_ZOOM_PROBE).catch(() => [])) as ProbeHit[]) ?? [];
|
|
278
304
|
out.probed.push("1.4.4");
|
|
279
305
|
}
|
|
280
|
-
if (want("1.4.10")) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
306
|
+
if (want("1.4.10") && canResize) {
|
|
307
|
+
let narrowed = true;
|
|
308
|
+
await page.setViewportSize({ width: 320, height: restore.height }).catch((e: unknown) => {
|
|
309
|
+
narrowed = false;
|
|
310
|
+
skip("1.4.10", String((e as Error)?.message ?? e).slice(0, 160));
|
|
311
|
+
});
|
|
312
|
+
if (narrowed) {
|
|
313
|
+
const r = (await page.evaluate(REFLOW_PROBE).catch((e: unknown) => {
|
|
314
|
+
skip("1.4.10", String((e as Error)?.message ?? e).slice(0, 160));
|
|
315
|
+
return null;
|
|
316
|
+
})) as { horizontalScroll: boolean } | null;
|
|
317
|
+
// The viewport goes back whatever the probe did — the caller's next assertion must not
|
|
318
|
+
// be measuring a 320px page.
|
|
319
|
+
await page.setViewportSize(restore).catch(() => {});
|
|
320
|
+
if (r) {
|
|
321
|
+
out.reflow = r;
|
|
322
|
+
out.probed.push("1.4.10");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
287
325
|
}
|
|
288
|
-
if (want("1.4.12")) {
|
|
326
|
+
if (want("1.4.12") && canStyle) {
|
|
289
327
|
// Tagged so it can be removed again — an injected stylesheet left behind would change
|
|
290
328
|
// every measurement the caller makes after this call.
|
|
291
|
-
const handle = await page.addStyleTag({ content: TEXT_SPACING_CSS }).catch(() =>
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
329
|
+
const handle = await page.addStyleTag({ content: TEXT_SPACING_CSS }).catch((e: unknown) => {
|
|
330
|
+
skip("1.4.12", String((e as Error)?.message ?? e).slice(0, 160));
|
|
331
|
+
return null;
|
|
332
|
+
});
|
|
333
|
+
if (handle) {
|
|
334
|
+
const r = (await page.evaluate(TEXT_SPACING_PROBE).catch((e: unknown) => {
|
|
335
|
+
skip("1.4.12", String((e as Error)?.message ?? e).slice(0, 160));
|
|
336
|
+
return null;
|
|
337
|
+
})) as ProbeHit[] | null;
|
|
338
|
+
await page.evaluate(REMOVE_TEXT_SPACING_STEP).catch(() => {});
|
|
339
|
+
if (r) {
|
|
340
|
+
out.textSpacing = r;
|
|
341
|
+
out.probed.push("1.4.12");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
295
344
|
}
|
|
296
345
|
return out;
|
|
297
346
|
}
|
|
@@ -317,4 +366,9 @@ export interface LiveProbeResult {
|
|
|
317
366
|
/** The success criteria actually probed on THIS page. Absence of a hit only means
|
|
318
367
|
* something when the probe ran, which is why this travels with the hits. */
|
|
319
368
|
probed: string[];
|
|
369
|
+
/** Why a probe did not run, when one did not. Recorded rather than dropped: a measurement
|
|
370
|
+
* that vanishes silently is indistinguishable from a page with nothing wrong, and the
|
|
371
|
+
* criteria it would have decided then sit at « to assess » with nobody able to tell which
|
|
372
|
+
* of the two happened. */
|
|
373
|
+
skipped?: { sc: string; why: string }[];
|
|
320
374
|
}
|
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.6.
|
|
3
|
+
export const VERSION = "5.6.1";
|
|
4
4
|
export const SCHEMA_VERSION = 2;
|
|
5
5
|
|
|
6
6
|
// Lang = the UI FRAME's language — the `L` localization tables each module (report,
|