pour-engine 1.21.0 → 1.23.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/config/project.config.js
CHANGED
package/engine/AUDIT.html
CHANGED
|
@@ -684,7 +684,7 @@
|
|
|
684
684
|
<p>What to trust and how much: the ten headline defects were re-verified line by line in the source by a second pass and are safe to act on. Per-rule ratings are judgments, not measurements; the boundary between "minor gaps" and "under-tests" is taste. Over-violation claims describe constructible scenarios, not measured frequency on real pages: an over-flag that fires on rare markup matters less in practice than the rating implies, and the lab benchmark, not this audit, is the source of truth for how often each fires. Two flagged behaviours (subtitles-as-captions, the auth-field both-attributes fail) are pinned by spec cases, meaning they are standing policy: this report flags them as spec deviations, and changing them is a product decision, not a bug fix.</p>
|
|
685
685
|
<p>Timeline: the audit ran against a clean tree with 140 pinned cases, changing nothing. The same day, the proposed batch was implemented (eight rules, four catalog flips), adversarially audited, corrected, and re-pinned: 176 cases now pass, along with all testbeds, frame checks, and the four-surface smoke; the mass lab gate was not run. Coverage was regenerated and site plus extension bundles rebuilt with the new engine. The pre-existing confirmed defects in the findings above are deliberately untouched: fixing them is the proposed next batch, and a separate decision from adding rules.</p>
|
|
686
686
|
|
|
687
|
-
<p class="foot">Pour engine spec sweep, 10 August 2026. Catalog verified against the W3C Recommendation of 12 December 2024 and its errata. This report lives at src/engine/AUDIT.html and is regenerated by running the spec-sweep skill; it
|
|
687
|
+
<p class="foot">Pour engine spec sweep, 10 August 2026. Catalog verified against the W3C Recommendation of 12 December 2024 and its errata. This report lives at src/engine/AUDIT.html and is regenerated by running the spec-sweep skill; it ships alongside the engine source (the engine mirror repo includes it), so it is written to be read.</p>
|
|
688
688
|
</main>
|
|
689
689
|
</body>
|
|
690
690
|
</html>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// WCAG SC 1.1.1 Non-text Content (Level A)
|
|
2
|
+
import { labelledByName } from '../../lib/accessible-name.js';
|
|
3
|
+
|
|
2
4
|
export default {
|
|
3
5
|
id: 'svg-img-alt',
|
|
4
6
|
impact: 'serious',
|
|
@@ -10,6 +12,21 @@ export default {
|
|
|
10
12
|
// because browsers expose it to AT as a graphic even without a role.
|
|
11
13
|
selector: 'svg[role="img"], svg[role="graphics-document"], svg[role="graphics-symbol"], [role="img"]:not(img):not(svg), svg:not([role])',
|
|
12
14
|
evaluate(element, { accessibleName }) {
|
|
15
|
+
// A non-svg role="img" host names from author only, and the role makes
|
|
16
|
+
// its subtree presentational (ARIA: children presentational) — so a
|
|
17
|
+
// descendant <svg><title> or text content never reaches AT as a name,
|
|
18
|
+
// even though a generic contents walk would find it.
|
|
19
|
+
if (element.tagName.toLowerCase() !== 'svg' && element.getAttribute('role') === 'img') {
|
|
20
|
+
const authorName = labelledByName(element)
|
|
21
|
+
|| element.getAttribute('aria-label')?.trim()
|
|
22
|
+
|| element.getAttribute('title')?.trim();
|
|
23
|
+
if (authorName) return { status: 'pass' };
|
|
24
|
+
return {
|
|
25
|
+
status: 'fail',
|
|
26
|
+
message: 'This element is marked as an image but has no accessible name — for role="img", content inside it (including an inner SVG’s <title>) does not count as a name.',
|
|
27
|
+
fix: 'Add aria-label="…" or aria-labelledby pointing at visible text.',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
13
30
|
if (accessibleName(element)) return { status: 'pass' };
|
|
14
31
|
if (!element.getAttribute('role') && element.tagName.toLowerCase() === 'svg') {
|
|
15
32
|
if (element.parentNode instanceof SVGElement) return { status: 'pass' }; // nested svg: part of the outer graphic
|
|
@@ -38,7 +38,19 @@ export default {
|
|
|
38
38
|
?? IMPLICIT_CONTAINER[owner.tagName.toLowerCase()]);
|
|
39
39
|
if (ownerRole && containers.includes(ownerRole)) return { status: 'pass' };
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
// FLAT-TREE ancestry, not light-DOM ancestry: assistive technology sees
|
|
42
|
+
// the composed tree, so a slotted role="listitem" whose role="list"
|
|
43
|
+
// container lives inside the host component's shadow root IS inside a
|
|
44
|
+
// list (Adobe Spectrum's sidenav is exactly this — a light-DOM walk was
|
|
45
|
+
// 11 false asserts on one page). An element assigned to a slot continues
|
|
46
|
+
// through the slot's shadow-side ancestors; an element at a shadow root's
|
|
47
|
+
// top continues through the host.
|
|
48
|
+
const flatParent = (node) => node.assignedSlot
|
|
49
|
+
?? node.parentElement
|
|
50
|
+
?? (node.getRootNode() instanceof ShadowRoot ? node.getRootNode().host : null);
|
|
51
|
+
for (let parent = flatParent(element); parent; parent = flatParent(parent)) {
|
|
52
|
+
// A <slot> is rendering plumbing with no tree presence of its own.
|
|
53
|
+
if (parent.tagName === 'SLOT' && !parent.hasAttribute('role')) continue;
|
|
42
54
|
const parentRole =
|
|
43
55
|
parent.getAttribute('role')?.trim().split(/\s+/)[0]?.toLowerCase() ??
|
|
44
56
|
IMPLICIT_CONTAINER[parent.tagName.toLowerCase()];
|
|
@@ -24,12 +24,15 @@
|
|
|
24
24
|
// already fixed, and must not be flagged. That also means an aria-label
|
|
25
25
|
// which is itself generic is caught, which is the more common mistake.
|
|
26
26
|
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// than bad ones.
|
|
27
|
+
// The list grows one language at a time, and only with a speaker of that
|
|
28
|
+
// language checking every entry — a wrong entry would fail correct links.
|
|
29
|
+
// English and French are covered; pages in other languages get no findings
|
|
30
|
+
// here rather than bad ones. Entries are stored in NORMALIZED form (see
|
|
31
|
+
// normalizeName below): apostrophes become spaces, so "plus d informations"
|
|
32
|
+
// is how "plus d'informations" — straight or typographic — must be listed.
|
|
31
33
|
|
|
32
34
|
const GENERIC = new Set([
|
|
35
|
+
// English
|
|
33
36
|
'read more', 'read more about this', 'more', 'much more', 'learn more',
|
|
34
37
|
'click here', 'click', 'click this', 'tap here', 'press here',
|
|
35
38
|
'here', 'this', 'this link', 'link', 'this page', 'go', 'go here',
|
|
@@ -37,6 +40,13 @@ const GENERIC = new Set([
|
|
|
37
40
|
'see more', 'view more', 'show more', 'find out more', 'discover more',
|
|
38
41
|
'continue', 'continue reading', 'keep reading', 'full story', 'full article',
|
|
39
42
|
'details', 'see details', 'view details', 'more details',
|
|
43
|
+
// French — contributed and checked by a native speaker
|
|
44
|
+
'en savoir plus', 'savoir plus', 'lire la suite', 'lire plus', 'plus',
|
|
45
|
+
'cliquez ici', 'cliquer ici', 'clique ici', 'ici', 'ce lien', 'lien',
|
|
46
|
+
'cette page', 'voir plus', 'en voir plus', 'afficher plus', 'afficher',
|
|
47
|
+
'voir', 'voir le détail', 'voir les détails', 'plus de détails',
|
|
48
|
+
'plus d informations', 'plus d infos',
|
|
49
|
+
'en savoir davantage', 'découvrir', 'découvrez', 'continuer', 'suite',
|
|
40
50
|
]);
|
|
41
51
|
|
|
42
52
|
/** Lowercased, punctuation and arrows dropped, spaces collapsed, so
|
package/package.json
CHANGED