ultra11y 4.0.1 → 4.2.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 +28 -0
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +1685 -344
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +1685 -344
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/references/ci.md +109 -0
- package/skills/ultra11y/references/pages.md +76 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +1685 -344
- package/src/annotate.ts +180 -15
- package/src/auditor.ts +115 -43
- package/src/cli.ts +187 -9
- package/src/dev.ts +23 -75
- package/src/evidence.ts +72 -37
- package/src/html-emit.ts +327 -0
- package/src/html-report.ts +482 -0
- package/src/html.ts +261 -0
- package/src/mcp/handlers.ts +11 -1
- package/src/mcp/tools.ts +10 -1
- package/src/pages-report.ts +40 -21
- package/src/pages.ts +28 -4
- package/src/report.ts +84 -31
- package/src/tickets/providers/github.ts +9 -4
- package/src/types.ts +1 -1
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: 4.0
|
|
7
|
+
version: 4.2.0
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# ultra11y — audit WCAG 2.2 AA and write accessible markup
|
|
@@ -262,6 +262,115 @@ Two surfaces come out of it:
|
|
|
262
262
|
|
|
263
263
|
A run with no page in scope is not a failure: the report step says so and the job carries on.
|
|
264
264
|
|
|
265
|
+
## What the artifact looks like when you open it
|
|
266
|
+
|
|
267
|
+
`audits/` used to hold JSON, SARIF and Markdown: everything a machine needs and nothing that
|
|
268
|
+
opens. `html: 'true'` and `evidence: 'true'` are on by default, so it now has a front door.
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
audits/
|
|
272
|
+
├── index.html ← the entry point: rate, synthesis, page scoreboard, links
|
|
273
|
+
├── ultra11y-<std>-<date>.html ← the whole audit in ONE file, printable to PDF
|
|
274
|
+
├── <std>-<date>.md ← the Markdown report, illustrated by the same crops
|
|
275
|
+
├── audit-latest.json ← the machine-readable audit (unchanged)
|
|
276
|
+
├── assets/
|
|
277
|
+
│ ├── <page-id>.png ← the page screenshot
|
|
278
|
+
│ └── <page-id>/<hash>.png ← one annotated crop per distinct defect on that page
|
|
279
|
+
└── pages/
|
|
280
|
+
├── index.md + page-<id>.md ← the per-page dossiers (unchanged)
|
|
281
|
+
└── index.html + page-<id>.html ← the same, navigable
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Two conventions share `assets/` and that is deliberate: `<page-id>.png` is the whole page,
|
|
285
|
+
`<page-id>/` holds the crops OF that page. Renaming either would break the Markdown sheets
|
|
286
|
+
already published by earlier versions.
|
|
287
|
+
|
|
288
|
+
**One artifact, not two.** The HTML is written into the same `audits/` the Markdown already
|
|
289
|
+
travels in, and the upload now fires when EITHER producer ran. A separate artifact would mean
|
|
290
|
+
a second name to keep unique, a second 409 to hit, and a reviewer choosing between two
|
|
291
|
+
downloads.
|
|
292
|
+
|
|
293
|
+
**Everything is self-contained.** No script (an artifact viewer will not run one), no external
|
|
294
|
+
stylesheet or font, and no `src`/`href` that leaves `audits/` — the artifact is read after
|
|
295
|
+
being unzipped somewhere else, so a `../..` climb is a broken image for every reader. `ci.yml`
|
|
296
|
+
asserts this on a real `download-artifact` round trip, because the HTML step itself degrades
|
|
297
|
+
to a `::warning::` and cannot catch its own regression.
|
|
298
|
+
|
|
299
|
+
### The crops
|
|
300
|
+
|
|
301
|
+
An occurrence gains a sub-bullet showing the element, ringed:
|
|
302
|
+
|
|
303
|
+
```md
|
|
304
|
+
- [ ] `.ultra11y/pages/accueil/dom.html:412` (`div.card`) — <img> without an alt attribute…
|
|
305
|
+
- 
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Every document that shows a non-conformity shows its crop**: the Markdown conformance
|
|
309
|
+
report, the per-page sheets, and the single-file composite. They all reference one set of
|
|
310
|
+
files in `assets/`; only the composite pays the base64 tax, because only it has to travel
|
|
311
|
+
alone. `report --evidence` therefore works with or without `--html` — the images are not an
|
|
312
|
+
HTML-only tier. `prd`, `gh` and `jira` deliberately stay text: their output is read on
|
|
313
|
+
github.com, where a path relative to `audits/` resolves to nothing, and a broken image in an
|
|
314
|
+
issue is worse than no image.
|
|
315
|
+
|
|
316
|
+
Crops are derived AT RENDER TIME from the page snapshot, never stamped on the finding: a
|
|
317
|
+
rectangle in pixels is a property of the image, and a stamped box goes silently wrong the
|
|
318
|
+
moment the audit is re-rendered against a different capture. So `evidence` needs snapshots —
|
|
319
|
+
`snapshot: 'true'` (the default), the E2E plugins, or `ultra11y dev`. Audited from source
|
|
320
|
+
alone, the tier is simply inactive and says nothing: a source finding never had pixels, so it
|
|
321
|
+
is not an occurrence that failed to be illustrated.
|
|
322
|
+
|
|
323
|
+
The mark is **not monochrome**: a white halo, a red ring and corner brackets whose *shape*
|
|
324
|
+
says "here". A tool that reports 1.4.1 failures cannot ship a deliverable that commits one.
|
|
325
|
+
|
|
326
|
+
When a crop cannot be drawn, the report says so per page and per criterion, with the reason —
|
|
327
|
+
`no-screenshot`, `below-the-fold`, `unknown-scale`, and nine more. **An occurrence without a
|
|
328
|
+
picture must never read as an occurrence without a defect.**
|
|
329
|
+
|
|
330
|
+
Two of those reasons look alike and are not:
|
|
331
|
+
|
|
332
|
+
| Reason | What it means | Costs the reader |
|
|
333
|
+
|---|---|---|
|
|
334
|
+
| `deduplicated` | the same defect on the same element, already shown by another occurrence's picture | nothing |
|
|
335
|
+
| `capped` | a **distinct** defect left undrawn because a limit ran out | a picture — raise `evidence-max` |
|
|
336
|
+
|
|
337
|
+
`evidence-max` (200 by default) is the run-wide fuse and the only one that is a setting: it
|
|
338
|
+
guards the upload's size. The per-rule (6) and per-page (12) limits are fixed, because they
|
|
339
|
+
are what makes one picture stand for a repeated defect — one design-system failure multiplied
|
|
340
|
+
by 38 routes is one defect, not 472 pictures.
|
|
341
|
+
|
|
342
|
+
### Printing it
|
|
343
|
+
|
|
344
|
+
The single file is the deliverable an auditor hands to a client: open
|
|
345
|
+
`ultra11y-<std>-<date>.html`, print, *Save as PDF*. The print sheet keeps each criterion and
|
|
346
|
+
its evidence on one sheet rather than splitting them across a page break.
|
|
347
|
+
|
|
348
|
+
Its images travel inside it as `data:` URIs, and base64 costs a third more than the bytes it
|
|
349
|
+
encodes — so there is a budget (the `inline-budget` input, `--inline-budget` on the CLI, 12 MB
|
|
350
|
+
by default) and a ladder. Over budget, page screenshots go first, then all but one crop per
|
|
351
|
+
criterion, then the images entirely. Every rung is written **into the document** and onto
|
|
352
|
+
stderr, and the other documents still reference every crop as a file. Images degrade; the
|
|
353
|
+
non-conformities
|
|
354
|
+
never do.
|
|
355
|
+
|
|
356
|
+
### Publishing it as a page (optional, and it costs something)
|
|
357
|
+
|
|
358
|
+
An artifact needs a download and a login. If the report should be a URL, GitHub Pages will
|
|
359
|
+
serve `audits/` as-is — it is already self-contained:
|
|
360
|
+
|
|
361
|
+
```yaml
|
|
362
|
+
- uses: actions/upload-pages-artifact@v3
|
|
363
|
+
with: { path: audits }
|
|
364
|
+
- uses: actions/deploy-pages@v4
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Know the trade before you take it: **a Pages site is public**, and this one names your
|
|
368
|
+
non-conformities, your file paths and your routes, with screenshots. On a private repository
|
|
369
|
+
that is a disclosure, not a convenience. It also needs `pages: write` and `id-token: write`,
|
|
370
|
+
and it overwrites the previous deployment — there is no per-run history the way artifacts have
|
|
371
|
+
one. Left alone, the artifact is the safer default, which is why this is a recipe here and not
|
|
372
|
+
an input on the action.
|
|
373
|
+
|
|
265
374
|
## The action is executed in ITS OWN CI, not just parsed
|
|
266
375
|
|
|
267
376
|
`tests/action.test.ts` reads `action.yml` and gates its shape. That cannot prove the thing
|
|
@@ -288,6 +288,82 @@ both a CSSOM measurement and a screenshot measurement, and presenting those as o
|
|
|
288
288
|
be a lie. It is off everywhere else: the backlog, `prd --split criterion` and every tracker issue
|
|
289
289
|
body are byte-identical to what they were.
|
|
290
290
|
|
|
291
|
+
### Showing the defect, not only citing it (`--evidence`)
|
|
292
|
+
|
|
293
|
+
`selectorHint` is lossy by construction — first class only, `href` truncated — and every
|
|
294
|
+
rendered-tier finding on a client-rendered page is anchored at `dom.html:2`, the `<html>`
|
|
295
|
+
element. What a non-technical reviewer receives is `` `.ultra11y/pages/accueil/dom.html:412`
|
|
296
|
+
(`div.card`) ``, which locates nothing.
|
|
297
|
+
|
|
298
|
+
```sh
|
|
299
|
+
node scripts/ultra11y.mjs pages --in audits/audit-latest.json --format report \
|
|
300
|
+
--split page --evidence --out audits/pages
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Each occurrence gains an inert sub-bullet with an annotated crop of the offending element,
|
|
304
|
+
written to `assets/<page-id>/<hash>.png`:
|
|
305
|
+
|
|
306
|
+
```md
|
|
307
|
+
- [ ] `.ultra11y/pages/accueil/dom.html:412` (`div.card`) — <img> without an alt attribute…
|
|
308
|
+
- 
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Derived at render time, never stamped on the finding.** A rectangle in pixels is a property
|
|
312
|
+
of the image: a box written onto a `Finding` becomes silently wrong the moment the audit is
|
|
313
|
+
re-rendered against another capture. The join goes `sourceStart` → document-order ordinal →
|
|
314
|
+
`boxes.json`, through the same `alignedBoxes` the rendered tier uses — including its refusal
|
|
315
|
+
to align a digest whose tags disagree. `Finding` gains no field.
|
|
316
|
+
|
|
317
|
+
The mark is not monochrome: a white halo, a red ring, corner brackets whose shape says "here",
|
|
318
|
+
and a dashed inner line that survives a monochrome print. This tool reports 1.4.1 failures; its
|
|
319
|
+
own deliverable cannot commit one.
|
|
320
|
+
|
|
321
|
+
**Nothing is cut in silence.** Thirteen refusal reasons, each with its own sentence, counted
|
|
322
|
+
per page and per criterion in the sheet itself:
|
|
323
|
+
|
|
324
|
+
| Reason | What happened |
|
|
325
|
+
|---|---|
|
|
326
|
+
| `no-snapshot` | the capture this finding's own path names is not on this disk |
|
|
327
|
+
| `no-screenshot` | the producer captured no image for that page |
|
|
328
|
+
| `unreadable-image` · `no-boxes` · `truncated` | the capture or the box digest cannot be trusted |
|
|
329
|
+
| `no-offsets` · `unjoinable` | the finding has no byte range, or it resolves to no element |
|
|
330
|
+
| `page-scope` | the finding is about the document, not an element you can frame |
|
|
331
|
+
| `zero-area` · `below-the-fold` | the element is invisible, or outside a viewport-only capture |
|
|
332
|
+
| `unknown-scale` | the device pixel ratio could not be derived — a crop would be off-target |
|
|
333
|
+
| `deduplicated` | the same defect on the same element — another occurrence's picture shows it |
|
|
334
|
+
| `capped` | a **distinct** defect went undrawn: 6 per rule, 12 per page, `--evidence-max` per run |
|
|
335
|
+
|
|
336
|
+
The last two are counted apart on purpose. `deduplicated` costs the reader nothing — the
|
|
337
|
+
defect is on screen, under another occurrence. `capped` is a real gap, and it is the only one
|
|
338
|
+
a setting can close.
|
|
339
|
+
|
|
340
|
+
A finding raised on SOURCE is in neither column: it never had pixels, so it is not an
|
|
341
|
+
occurrence that failed to be illustrated, and a repository audited from source alone gets no
|
|
342
|
+
notice at all rather than one line per finding.
|
|
343
|
+
|
|
344
|
+
An occurrence with no picture must never read as an occurrence with no defect, so the sheet
|
|
345
|
+
says how many were not illustrated and why. So does the combined document, the HTML sheets and
|
|
346
|
+
the composite — every surface that can show a crop can say what it did not show.
|
|
347
|
+
|
|
348
|
+
Without `--evidence` the Markdown is **byte-identical** to what it was. The crop bullet carries
|
|
349
|
+
no `[ ]`, so `verify` builds the same worklist with or without it — indentation alone would not
|
|
350
|
+
be enough, since `AUDITOR_OCCURRENCE` is anchored `^\s*-\s\[ \]` and tolerates leading space.
|
|
351
|
+
|
|
352
|
+
### The same, as a page (`--html`)
|
|
353
|
+
|
|
354
|
+
`--html` writes `index.html` and one `page-<id>.html` beside the Markdown they mirror.
|
|
355
|
+
Self-contained: no script, no external asset, nothing pointing outside the output directory.
|
|
356
|
+
|
|
357
|
+
**The printable single file comes from `report --html`, not from here.** One composite per
|
|
358
|
+
artifact: emitting one from both commands would put a second copy of every inlined crop into
|
|
359
|
+
the same upload, so `pages --html` deliberately writes none.
|
|
360
|
+
|
|
361
|
+
```sh
|
|
362
|
+
node scripts/ultra11y.mjs report --in audits/audit-latest.json --html --evidence --out audits
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
See `references/ci.md` for the artifact layout and the inline-size budget.
|
|
366
|
+
|
|
291
367
|
## Holding an external audit against the grid
|
|
292
368
|
|
|
293
369
|
A human auditor's verdict is evidence, not a measurement this engine can redo. `import` reads one
|