pour-engine 1.40.0 → 1.42.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 +1 -1
- package/engine/AUDIT.html +24 -690
- package/engine/COVERAGE.md +17 -15
- package/engine/README.md +10 -5
- package/engine/index.js +34 -8
- package/engine/lib/accessible-name.js +69 -63
- package/engine/lib/contrast.js +111 -10
- package/engine/lib/dom.js +78 -18
- package/engine/lib/roles.js +5 -2
- package/engine/rules/best-practice/fieldset-legend.js +11 -5
- package/engine/rules/best-practice/region.js +1 -1
- package/engine/rules/index.js +29 -0
- package/engine/rules/wcag/1.1.1-area-alt.js +6 -1
- package/engine/rules/wcag/1.1.1-image-alt.js +2 -1
- package/engine/rules/wcag/1.1.1-svg-img-alt.js +14 -17
- package/engine/rules/wcag/1.2.3-video-audio-description.js +42 -0
- package/engine/rules/wcag/1.3.1-aria-required-children.js +20 -8
- package/engine/rules/wcag/1.3.1-aria-required-parent.js +7 -10
- package/engine/rules/wcag/1.3.1-form-label.js +5 -29
- package/engine/rules/wcag/1.3.1-list-structure.js +4 -3
- package/engine/rules/wcag/1.3.1-table-headers.js +23 -2
- package/engine/rules/wcag/1.3.4-orientation.js +117 -48
- package/engine/rules/wcag/1.3.5-autocomplete-valid.js +21 -8
- package/engine/rules/wcag/1.3.6-region-purpose.js +35 -0
- package/engine/rules/wcag/1.4.1-link-in-text-block.js +19 -3
- package/engine/rules/wcag/1.4.10-reflow.js +3 -3
- package/engine/rules/wcag/1.4.11-non-text-contrast.js +9 -1
- package/engine/rules/wcag/1.4.12-text-spacing.js +15 -2
- package/engine/rules/wcag/1.4.3-color-contrast.js +79 -7
- package/engine/rules/wcag/1.4.3-control-contrast.js +35 -6
- package/engine/rules/wcag/1.4.4-meta-viewport.js +7 -2
- package/engine/rules/wcag/1.4.8-text-justified.js +49 -0
- package/engine/rules/wcag/2.1.1-scrollable-region-focusable.js +5 -5
- package/engine/rules/wcag/2.2.1-meta-refresh.js +10 -4
- package/engine/rules/wcag/2.2.2-pause-stop-hide.js +5 -5
- package/engine/rules/wcag/2.2.4-meta-refresh-no-exceptions.js +50 -0
- package/engine/rules/wcag/2.4.1-bypass-blocks.js +2 -2
- package/engine/rules/wcag/2.4.10-section-heading.js +86 -0
- package/engine/rules/wcag/2.4.11-focus-not-obscured.js +134 -78
- package/engine/rules/wcag/2.4.12-focus-not-obscured-enhanced.js +17 -0
- package/engine/rules/wcag/2.4.2-document-title.js +7 -8
- package/engine/rules/wcag/2.4.4-link-text-generic.js +6 -5
- package/engine/rules/wcag/2.4.6-heading-label-placeholder.js +69 -0
- package/engine/rules/wcag/2.4.9-link-text-generic-only.js +8 -23
- package/engine/rules/wcag/2.5.1-pointer-gesture-alternative.js +98 -0
- package/engine/rules/wcag/2.5.8-target-size.js +161 -45
- package/engine/rules/wcag/3.3.4-financial-form-confirmation.js +32 -0
- package/engine/rules/wcag/3.3.8-auth-field-obstruction.js +10 -13
- package/engine/rules/wcag/3.3.9-captcha-alternative.js +35 -0
- package/engine/rules/wcag/4.1.2-aria-attr-valid.js +4 -0
- package/engine/rules/wcag/4.1.2-aria-field-name.js +6 -5
- package/engine/rules/wcag/4.1.2-aria-valid-refs.js +8 -3
- package/engine/rules/wcag/4.1.2-button-name.js +1 -1
- package/engine/rules/wcag/4.1.2-composite-widget-name.js +1 -1
- package/engine/rules/wcag/4.1.2-invoker-target.js +2 -1
- package/engine/rules/wcag/4.1.2-label-for-valid.js +2 -2
- package/engine/rules/wcag/4.1.2-valid-role.js +6 -1
- package/engine/wcag22.js +19 -19
- package/package.json +1 -1
package/engine/AUDIT.html
CHANGED
|
@@ -1,690 +1,24 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
|
|
3
|
-
<head>
|
|
4
|
-
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<
|
|
7
|
-
<style>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
--over-fg: #EC9377; --over-bg: #3A1E14;
|
|
25
|
-
--under-fg: #9FAEE8; --under-bg: #1D2440;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
:root[data-theme="dark"] {
|
|
29
|
-
--bg: #101613; --surface: #171F1B; --ink: #E7ECE8; --muted: #97A49E;
|
|
30
|
-
--line: #29332D; --accent: #52C2AC; --accent-soft: #17342E;
|
|
31
|
-
--code-bg: #1E2823;
|
|
32
|
-
--solid-fg: #7BD49B; --solid-bg: #16311F;
|
|
33
|
-
--minor-fg: #DCC26A; --minor-bg: #33290F;
|
|
34
|
-
--over-fg: #EC9377; --over-bg: #3A1E14;
|
|
35
|
-
--under-fg: #9FAEE8; --under-bg: #1D2440;
|
|
36
|
-
}
|
|
37
|
-
body {
|
|
38
|
-
background: var(--bg); color: var(--ink);
|
|
39
|
-
font: 16px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
40
|
-
margin: 0; padding: 0 20px 80px;
|
|
41
|
-
}
|
|
42
|
-
main { max-width: 880px; margin: 0 auto; }
|
|
43
|
-
h1, h2, h3 { font-family: Charter, Georgia, "Times New Roman", serif; text-wrap: balance; line-height: 1.2; }
|
|
44
|
-
h1 { font-size: 2.1rem; margin: 48px 0 6px; }
|
|
45
|
-
h2 { font-size: 1.45rem; margin: 52px 0 14px; padding-top: 14px; border-top: 1px solid var(--line); }
|
|
46
|
-
h3 { font-size: 1.1rem; margin: 28px 0 8px; }
|
|
47
|
-
p { max-width: 70ch; }
|
|
48
|
-
.sub { color: var(--muted); margin: 0 0 26px; max-width: 75ch; }
|
|
49
|
-
code { font: 0.86em ui-monospace, "SF Mono", Menlo, monospace; background: var(--code-bg); padding: 1px 5px; border-radius: 4px; }
|
|
50
|
-
a { color: var(--accent); }
|
|
51
|
-
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; margin: 24px 0 8px; }
|
|
52
|
-
.tile { background: var(--surface); border: 1px solid var(--line); border-radius: 8px; padding: 14px 16px; }
|
|
53
|
-
.tile b { display: block; font-size: 1.5rem; font-variant-numeric: tabular-nums; }
|
|
54
|
-
.tile span { color: var(--muted); font-size: 0.85rem; }
|
|
55
|
-
.toc { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 8px; }
|
|
56
|
-
.toc a { text-decoration: none; font-size: 0.85rem; border: 1px solid var(--line); background: var(--surface); color: var(--ink); padding: 5px 11px; border-radius: 999px; }
|
|
57
|
-
.toc a:hover { border-color: var(--accent); color: var(--accent); }
|
|
58
|
-
.pill { display: inline-block; font: 600 0.72rem/1 system-ui, sans-serif; letter-spacing: 0.04em; padding: 4px 8px; border-radius: 999px; vertical-align: 1px; white-space: nowrap; }
|
|
59
|
-
.r-solid { color: var(--solid-fg); background: var(--solid-bg); }
|
|
60
|
-
.r-minor { color: var(--minor-fg); background: var(--minor-bg); }
|
|
61
|
-
.r-over { color: var(--over-fg); background: var(--over-bg); }
|
|
62
|
-
.r-under { color: var(--under-fg); background: var(--under-bg); }
|
|
63
|
-
details.rule { background: var(--surface); border: 1px solid var(--line); border-radius: 8px; margin: 8px 0; }
|
|
64
|
-
details.rule summary { cursor: pointer; padding: 11px 14px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
65
|
-
details.rule summary::marker { color: var(--muted); }
|
|
66
|
-
details.rule summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 8px; }
|
|
67
|
-
details.rule[open] summary { border-bottom: 1px solid var(--line); }
|
|
68
|
-
details.rule .body { padding: 6px 16px 14px; }
|
|
69
|
-
details.rule .body p { margin: 10px 0; max-width: none; }
|
|
70
|
-
.rid { font: 600 0.9rem ui-monospace, "SF Mono", Menlo, monospace; }
|
|
71
|
-
.sc { color: var(--muted); font-size: 0.82rem; }
|
|
72
|
-
.lbl { font-weight: 650; }
|
|
73
|
-
table { border-collapse: collapse; font-size: 0.88rem; min-width: 100%; }
|
|
74
|
-
th, td { text-align: left; padding: 8px 12px 8px 0; border-bottom: 1px solid var(--line); vertical-align: top; }
|
|
75
|
-
th { color: var(--muted); font-weight: 600; }
|
|
76
|
-
.scroll { overflow-x: auto; }
|
|
77
|
-
.callout { background: var(--accent-soft); border-radius: 8px; padding: 14px 18px; margin: 18px 0; }
|
|
78
|
-
.callout p { margin: 6px 0; }
|
|
79
|
-
.defect { background: var(--surface); border: 1px solid var(--line); border-left: 3px solid var(--over-fg); border-radius: 6px; padding: 10px 16px; margin: 10px 0; }
|
|
80
|
-
.defect.gap { border-left-color: var(--under-fg); }
|
|
81
|
-
.defect p { margin: 4px 0; max-width: none; }
|
|
82
|
-
ol, ul { max-width: 72ch; }
|
|
83
|
-
li { margin: 6px 0; }
|
|
84
|
-
.foot { color: var(--muted); font-size: 0.85rem; margin-top: 60px; border-top: 1px solid var(--line); padding-top: 16px; }
|
|
85
|
-
</style>
|
|
86
|
-
</head>
|
|
87
|
-
<body>
|
|
88
|
-
<main>
|
|
89
|
-
<h1>Pour Engine rule audit</h1>
|
|
90
|
-
<p class="sub">A full sweep of every registered rule against the WCAG 2.2 specification, 10 August 2026. Every rule's source was read in full; every claim below comes from that source or from W3C text, and the sharpest defect claims were independently re-verified line by line before being stated. The same sweep implemented its own next batch (eight new rules), which then got a same-day adversarial audit by fresh eyes; the defects that pass found were fixed and re-pinned the same day. This report is deliberately unflinching: it lists what over-flags, what under-tests, and what the engine cannot test at all.</p>
|
|
91
|
-
|
|
92
|
-
<div class="tiles">
|
|
93
|
-
<div class="tile"><b>0</b><span>catalog drift vs the published W3C spec</span></div>
|
|
94
|
-
<div class="tile"><b>176 / 176</b><span>pinned spec cases pass in Chromium</span></div>
|
|
95
|
-
<div class="tile"><b>82</b><span>rules audited (62 WCAG + 20 best practice), plus 1 parked</span></div>
|
|
96
|
-
<div class="tile"><b>29</b><span>rated solid</span></div>
|
|
97
|
-
<div class="tile"><b>38</b><span>minor gaps</span></div>
|
|
98
|
-
<div class="tile"><b>8 + 7</b><span>over-flag risks + under-testers</span></div>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
<nav class="toc" aria-label="Sections">
|
|
102
|
-
<a href="#verdict">Verdict</a>
|
|
103
|
-
<a href="#defects">Top findings</a>
|
|
104
|
-
<a href="#f-text">Text & media</a>
|
|
105
|
-
<a href="#f-structure">Structure & forms</a>
|
|
106
|
-
<a href="#f-contrast">Contrast & visual</a>
|
|
107
|
-
<a href="#f-operable">Operable & targets</a>
|
|
108
|
-
<a href="#f-aria">ARIA & language</a>
|
|
109
|
-
<a href="#f-batch">New batch</a>
|
|
110
|
-
<a href="#f-bp">Best practice</a>
|
|
111
|
-
<a href="#coverage">What the engine cannot test</a>
|
|
112
|
-
<a href="#missing">Missing failure families</a>
|
|
113
|
-
<a href="#next">Proposed next batch</a>
|
|
114
|
-
<a href="#themes">Cross-cutting themes</a>
|
|
115
|
-
<a href="#method">Method & honesty notes</a>
|
|
116
|
-
</nav>
|
|
117
|
-
|
|
118
|
-
<h2 id="verdict">The honest verdict</h2>
|
|
119
|
-
<p>The engine is in good shape where it matters most, and the audit found no catalog problems: all 86 success criteria match the published W3C Recommendation exactly (numbers, names, levels, and version flags), the 2025 errata are editorial only, and no newer WCAG has shipped. The abstain-over-assert discipline is real and mostly enforced: the hardest criteria (focus visibility, focus obscurement, reflow, text spacing, non-text contrast) never assert failures they cannot prove, and the main text-contrast machinery is the strongest single piece of the engine, with every contentious judgment pinned by a measured case.</p>
|
|
120
|
-
<p>The honest bad news comes in four flavours. First, a handful of rules assert violations the spec does not support: four WCAG rules and four best-practice rules over-flag in concrete, reproducible scenarios. Second, six rules are honest about their verdicts but examine a much thinner slice of their criterion than their name suggests. Third, several confirmed silent bugs let the worst offenders pass: the focus-visible replacement check counts <code>box-shadow: none</code> as an indicator, and the non-text-contrast wrapper check passes any border without measuring its contrast. Fourth, the engine's hard-won doctrine is unevenly applied: lessons about inert markup, shadow DOM, and the rendered tree live in some rules and never reached their siblings.</p>
|
|
121
|
-
<p>Coverage-wise, 34 of 86 criteria are now partially automated and 52 need humans. That split is honest, and this audit confirms most manual classifications are genuinely dead ends. The six provable failure families the sweep found with no rule at all are now closed: this sweep implemented its own eight-rule batch (the muted hero video, embed, canvas, summary, composite-widget items, plus error-message linkage for 3.3.1, the inline jump-menu signature for 3.2.2/3.2.1, and CSS visual-order divergence for 2.4.3), taking assisted criteria from 30 to 34.</p>
|
|
122
|
-
<p>The batch then got a same-day adversarial audit by an auditor with no attachment to the implementation. It found one genuine over-assert (error-message-linkage failed broken references that Understanding 3.3.1 says visible text can still satisfy), a double-reporting overlap on <code><button role="tab"></code>, and five narrow-signal gaps. The clear-cut ones were fixed and re-pinned the same day; the per-rule blocks in the New batch section record what was fixed and what remains disclosed.</p>
|
|
123
|
-
|
|
124
|
-
<h2 id="defects">Top findings, verified against source</h2>
|
|
125
|
-
<p>Each of these was claimed by an auditor and then independently re-checked in the rule source before being listed here.</p>
|
|
126
|
-
|
|
127
|
-
<div class="defect"><p><b>1. focus-visible counts indicator removal as indicator provision.</b> The replacement check treats any set property as a replacement, but CSSOM returns <code>"none"</code> and <code>"transparent"</code> as truthy values. So <code>:focus { outline: none; box-shadow: none }</code>, the most aggressive suppression pattern on the web, silently passes as "replaced". Measured in Chromium. <span class="sc">2.4.7-focus-visible.js, replacementIn</span></p></div>
|
|
128
|
-
<div class="defect"><p><b>2. non-text-contrast passes wrapper borders without measuring them.</b> The input-group heuristic passes on any wrapper border with nonzero width and alpha, never checking the 3:1 ratio the sibling wrapper-fill branch checks. A borderless input inside a wrapper with a 1px near-white border on white gets an asserted pass at roughly 1.1:1. <span class="sc">1.4.11-non-text-contrast.js:84-87</span></p></div>
|
|
129
|
-
<div class="defect"><p><b>3. text-spacing asserts pass on pages it never measured.</b> Above 20,000 elements the rule returns <code>pass</code> for every element; the comment says "abstain wholesale" but the code asserts. Should be incomplete or an honest skip. <span class="sc">1.4.12-text-spacing.js:33</span></p></div>
|
|
130
|
-
<div class="defect"><p><b>4. label-for-valid asserts a 4.1.2 failure the spec can pass.</b> A dangling <code>label[for]</code> next to a control that is named via <code>aria-label</code> fully satisfies 4.1.2, and with 4.1.1 Parsing retired the dangling reference has no criterion of its own. Real markup defect, wrong criterion verdict. <span class="sc">4.1.2-label-for-valid.js:16-23</span></p></div>
|
|
131
|
-
<div class="defect"><p><b>5. autocomplete-valid flags fields the criterion does not cover.</b> SC 1.3.5 scopes itself to fields collecting information about the user; the ubiquitous autofill-suppression hacks (<code>autocomplete="nope"</code> on search boxes and coupon fields) are failed anyway. Meanwhile grammatically invalid values built from valid tokens (<code>"on email"</code>, <code>"billing"</code> alone, wrong token order) pass. Both over-flags and under-tests. <span class="sc">1.3.5-autocomplete-valid.js:30-36</span></p></div>
|
|
132
|
-
<div class="defect"><p><b>6. role-required-aria flags a spec-sanctioned pattern and misses six required states.</b> <code><input type="checkbox" role="switch"></code> without aria-checked is failed, but ARIA-in-HTML sanctions exactly that markup and forbids adding aria-checked to it. Against the ARIA 1.2 tables the rule also never checks: combobox and scrollbar need aria-controls, scrollbar and meter and focusable separator need aria-valuenow, option needs aria-selected, heading needs aria-level. <span class="sc">4.1.2-role-required-aria.js:26-28</span></p></div>
|
|
133
|
-
<div class="defect"><p><b>7. aria-attr-valid rejects legal values and skips whole value types.</b> ARIA 1.2 types aria-orientation and aria-hidden with a legal <code>undefined</code> token; the enum tables reject it as a critical fail. Numeric values (<code>aria-level="banana"</code>) and token lists (aria-relevant) are never validated at all. <span class="sc">4.1.2-aria-attr-valid.js:5-22</span></p></div>
|
|
134
|
-
<div class="defect"><p><b>8. audio-control asserts what G170 leaves open.</b> A looping autoplay clip is failed even though the criterion is satisfied by any pause control anywhere on the page, which the rule cannot see. Its sibling pause-stop-hide abstains for precisely this reason, in writing. A muted-by-stripped-audio-track video also fails because only the muted flag is consulted. <span class="sc">1.4.2-audio-control.js:20-38</span></p></div>
|
|
135
|
-
<div class="defect"><p><b>9. object-alt hard-fails embedded text documents.</b> A nameless <code><object></code> embedding an HTML page or tagged PDF is text content, not non-text content, so 1.1.1 may not even apply; the rule asserts a violation regardless and offers no presentation-role escape. The new embed-alt rule implements the type-gated review this rule still needs. <span class="sc">1.1.1-object-alt.js:9-16</span></p></div>
|
|
136
|
-
<div class="defect"><p><b>10. Two best-practice page rules fire inside every iframe.</b> landmark-one-main and page-heading-one lack the embedded-document gate that every page-level WCAG rule has, and the extension injects into all frames, so every ad or widget iframe fails "no main landmark" and "no h1". <span class="sc">landmark-one-main.js, page-heading-one.js</span></p></div>
|
|
137
|
-
<div class="defect gap"><p><b>11. The muted background hero video is nobody's job — CLOSED.</b> <code><video autoplay loop muted></code> is infinite automatic motion beside other content, squarely 2.2.2 subject matter, and it fell between audio-control (passes it: muted) and pause-stop-hide (never looks at video). Closed by video-loop-motion in this sweep's batch.</p></div>
|
|
138
|
-
<div class="defect gap"><p><b>12. media-captions accepts subtitles as captions.</b> WCAG's captions definition explicitly distinguishes captions (sound effects, speaker identification) from dialogue-only subtitles, yet a <code>kind="subtitles"</code> track passes 1.2.2 outright. This one is pinned by spec cases, so it is standing policy rather than an accident, but it remains a real deviation from the spec's own definition.</p></div>
|
|
139
|
-
|
|
140
|
-
<h2 id="f-text">Text alternatives & media <span class="sc">(9 rules)</span></h2>
|
|
141
|
-
|
|
142
|
-
<details class="rule"><summary><span class="rid">image-alt</span> <span class="sc">SC 1.1.1, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
143
|
-
<p><span class="lbl">Checks:</span> every rendered, AT-exposed <code><img></code> has an accessible name, empty alt, or a presentation role. Whitespace-only alt fails; filename-ish or generic alt goes to review.</p>
|
|
144
|
-
<p><span class="lbl">Over:</span> near none. The whitespace-alt hard fail is a defensible judgment call either way. <code>alt="42"</code> on an image depicting "42" goes to review, which is noise but not a violation.</p>
|
|
145
|
-
<p><span class="lbl">Under:</span> <code><img role="presentation" tabindex="0"></code> passes, but the ARIA role-conflict rule means browsers ignore the role on focusable elements, leaving a nameless focusable image. Garbage alt like "asdf" passes; content quality is not automatable.</p>
|
|
146
|
-
<p><span class="lbl">Discipline:</span> very good. Fails only on provable absence, abstains on suspicious-but-possibly-valid text.</p>
|
|
147
|
-
</div></details>
|
|
148
|
-
|
|
149
|
-
<details class="rule"><summary><span class="rid">svg-img-alt</span> <span class="sc">SC 1.1.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
150
|
-
<p><span class="lbl">Checks:</span> SVGs and elements with image roles need an accessible name; explicit image roles without one fail, roleless SVGs go to review.</p>
|
|
151
|
-
<p><span class="lbl">Over:</span> no wrong violations, but review volume: every unlabeled decorative roleless SVG outside the link/button allowlist gets flagged for review, and that allowlist is narrower than the real set of naming containers (tabs, summary, list items with text).</p>
|
|
152
|
-
<p><span class="lbl">Under:</span> the shared accessible-name helper computes name from contents for every role, but the image role is name-from-author only in the accname spec. So <code><div role="img">some text</div></code> passes while AT exposes an unnamed image. The same leak is what makes SVG title children work, so it is accidentally right for SVG and wrong for text-bearing hosts.</p>
|
|
153
|
-
<p><span class="lbl">Discipline:</span> exemplary; fail only when the author claimed image semantics and shipped no name.</p>
|
|
154
|
-
</div></details>
|
|
155
|
-
|
|
156
|
-
<details class="rule"><summary><span class="rid">area-alt</span> <span class="sc">SC 1.1.1 + 2.4.4, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
157
|
-
<p><span class="lbl">Checks:</span> every <code><area href></code> in a map needs an accessible name.</p>
|
|
158
|
-
<p><span class="lbl">Over:</span> areas in an orphaned map that no image references are still failed, though nothing presents them. Harmless edge; the visibility exemption is otherwise necessary since areas have no rendered box.</p>
|
|
159
|
-
<p><span class="lbl">Under:</span> none material.</p>
|
|
160
|
-
<p><span class="lbl">Discipline:</span> correct assert; a nameless actionable hotspot is provable.</p>
|
|
161
|
-
</div></details>
|
|
162
|
-
|
|
163
|
-
<details class="rule"><summary><span class="rid">object-alt</span> <span class="sc">SC 1.1.1, Level A</span> <span class="pill r-over">OVER-FLAGS</span></summary><div class="body">
|
|
164
|
-
<p><span class="lbl">Checks:</span> every rendered <code><object></code> needs an accessible name or non-empty fallback content.</p>
|
|
165
|
-
<p><span class="lbl">Over:</span> confirmed. It asserts fail regardless of what the object embeds. An object embedding an HTML document or tagged PDF is text content, so 1.1.1's applicability is itself unprovable; by the engine's own abstain principle this should be incomplete, or gated on the type and data attributes. There is also no presentation-role escape hatch, unlike image-alt.</p>
|
|
166
|
-
<p><span class="lbl">Under:</span> junk fallback text passes; text quality is not automatable.</p>
|
|
167
|
-
<p><span class="lbl">Discipline:</span> the one weak spot: asserts where applicability is unknowable. No pinned cases protect it.</p>
|
|
168
|
-
</div></details>
|
|
169
|
-
|
|
170
|
-
<details class="rule"><summary><span class="rid">input-image-alt</span> <span class="sc">SC 1.1.1 + 4.1.2, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
171
|
-
<p><span class="lbl">Checks:</span> <code>input[type=image]</code> must have an accessible name.</p>
|
|
172
|
-
<p><span class="lbl">Over:</span> none found; the assert is justified since a UA default like "Submit" does not describe purpose, and F65 lists this exact failure.</p>
|
|
173
|
-
<p><span class="lbl">Under:</span> minor inconsistency: no filename or generic-alt screen like image-alt has, so <code>alt="button.png"</code> passes here but would go to review on an img.</p>
|
|
174
|
-
<p><span class="lbl">Discipline:</span> correct.</p>
|
|
175
|
-
</div></details>
|
|
176
|
-
|
|
177
|
-
<details class="rule"><summary><span class="rid">media-captions</span> <span class="sc">SC 1.2.2, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
178
|
-
<p><span class="lbl">Checks:</span> every video should carry a captions track; without one it abstains, since captions may be burned in or player-provided.</p>
|
|
179
|
-
<p><span class="lbl">Over:</span> no wrong violations (it never fails), but structural review noise: 1.2.2 exempts video-only media, yet every silent background hero video without tracks gets flagged for review. No use of muted state to narrow this. Live streams get the same treatment though they belong to 1.2.4.</p>
|
|
180
|
-
<p><span class="lbl">Under:</span> the notable one: WCAG's captions definition distinguishes captions from dialogue-only subtitles, but a <code>kind="subtitles"</code> track (or a bare track, whose missing-value default is the subtitles state) passes outright. A French translation subtitle track on English audio passes 1.2.2 here. Pinned by two spec cases, so it is standing policy: HTML-correct, WCAG-generous. Broken or empty VTT files also pass on presence alone.</p>
|
|
181
|
-
<p><span class="lbl">Discipline:</span> good on the abstain side; the presence-equals-pass assert is the one place it claims more than it measured.</p>
|
|
182
|
-
</div></details>
|
|
183
|
-
|
|
184
|
-
<details class="rule"><summary><span class="rid">audio-control</span> <span class="sc">SC 1.4.2, Level A</span> <span class="pill r-over">OVER-FLAGS</span></summary><div class="body">
|
|
185
|
-
<p><span class="lbl">Checks:</span> autoplaying media must be muted, zero-volume, carry controls, or play 3 seconds or less; endless loops fail, unknown durations abstain.</p>
|
|
186
|
-
<p><span class="lbl">Over:</span> confirmed, two ways. G170 lets the pause mechanism be any control on the page, so a page with a looping clip plus its own custom pause button is failed anyway; pause-stop-hide abstains on the identical unknowable and says why in its header, so the engine contradicts itself here. And a looping video whose author stripped the audio track fails hard, because only the muted flag and volume are consulted, never whether audio exists or whether autoplay was blocked.</p>
|
|
187
|
-
<p><span class="lbl">Under:</span> script-initiated playback (<code>audio.play()</code> with no autoplay attribute) is invisible to the selector; iframe embeds are genuinely out of reach.</p>
|
|
188
|
-
<p><span class="lbl">Discipline:</span> split personality. The muted-property check and unknown-duration abstention are exemplary; both fail paths assert on unprovables. No pinned cases.</p>
|
|
189
|
-
</div></details>
|
|
190
|
-
|
|
191
|
-
<details class="rule"><summary><span class="rid">meta-refresh</span> <span class="sc">SC 2.2.1, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
192
|
-
<p><span class="lbl">Checks:</span> meta refresh: zero-delay self-reload fails, zero-delay redirect passes, 1 second to 20 hours fails, over 20 hours passes.</p>
|
|
193
|
-
<p><span class="lbl">Over:</span> none found. F40/F41 are unconditional documented failures and the 20-hour boundary is read exactly, pinned to the second (72000 in scope, 72001 exempt).</p>
|
|
194
|
-
<p><span class="lbl">Under:</span> trivial edges: a refresh to a fragment of the same page is still a reload loop but passes; parseInt reads some forms differently than the HTML refresh parser (verdict survives, message can misstate the delay).</p>
|
|
195
|
-
<p><span class="lbl">Discipline:</span> correct throughout. The best spec-boundary work in its family, fully pinned.</p>
|
|
196
|
-
</div></details>
|
|
197
|
-
|
|
198
|
-
<details class="rule"><summary><span class="rid">pause-stop-hide</span> <span class="sc">SC 2.2.2, Level A</span> <span class="pill r-under">UNDER-TESTS</span></summary><div class="body">
|
|
199
|
-
<p><span class="lbl">Checks:</span> marquee and blink fail outright; text-bearing elements moved by infinite CSS animations go to review with the animation named; everything else is silence.</p>
|
|
200
|
-
<p><span class="lbl">Over:</span> edge-only: a single-pass marquee (potentially under 5 seconds) and a page whose only content is the marquee are permitted by the SC's clauses but hard-failed.</p>
|
|
201
|
-
<p><span class="lbl">Under:</span> the real story. The infinity-only gate skips finite-but-long motion (a 12-second slide, or 50 iterations totalling 100 seconds) that the Understanding doc confirms is covered past five seconds. The text-only gate also silences image carousels and canvas motion, core 2.2.2 subject matter, while the header frames the exclusion as just spinners. JS-driven movement, tickers, autoplaying silent video, and animated GIFs are all unexamined.</p>
|
|
202
|
-
<p><span class="lbl">Discipline:</span> the best in the engine. The infinite-ticker abstention exists precisely because the pause mechanism could be any button, and every pass that is really an abstention is documented. The verdicts it emits are impeccable; the problem is what it never examines.</p>
|
|
203
|
-
</div></details>
|
|
204
|
-
|
|
205
|
-
<h2 id="f-structure">Structure & forms <span class="sc">(11 rules)</span></h2>
|
|
206
|
-
|
|
207
|
-
<details class="rule"><summary><span class="rid">aria-required-children</span> <span class="sc">SC 1.3.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
208
|
-
<p><span class="lbl">Checks:</span> composite ARIA containers (list, listbox, menu, tablist, grid) must contain, or aria-own, at least one required child role; populated containers with none fail.</p>
|
|
209
|
-
<p><span class="lbl">Over:</span> shadow DOM blindness: light-DOM queries only, so a list whose items live in a child component's shadow root is falsely failed. A child with <code>role="bogus listitem"</code> does not count because the first token is taken rather than the first valid token. aria-owns resolves in the container's own root only.</p>
|
|
210
|
-
<p><span class="lbl">Under:</span> ARIA 1.2 also gives feed a required article child, and row requires cells; neither is modelled, so an empty feed or cell-less row is never checked. Deliberate lenience: fully empty containers pass as lazy-load placeholders, beyond the spec's aria-busy escape (that part is disclosed and pinned).</p>
|
|
211
|
-
<p><span class="lbl">Discipline:</span> asserts only on provably populated containers, except the shadow case where abstention is owed.</p>
|
|
212
|
-
</div></details>
|
|
213
|
-
|
|
214
|
-
<details class="rule"><summary><span class="rid">aria-required-parent</span> <span class="sc">SC 1.3.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
215
|
-
<p><span class="lbl">Checks:</span> child roles (listitem, option, tab, row) must sit inside or be owned by the required container role, with presentation roles and role-less wrappers transparent.</p>
|
|
216
|
-
<p><span class="lbl">Over:</span> an explicit <code>role="generic"</code> wrapper breaks the walk and fails the child, though explicit generic is exactly as transparent as a role-less div. <code><select multiple></code> is missing from the implicit-container map, so an option with an explicit role inside it fails. The ancestor walk does not cross shadow boundaries, which sibling rule listitem-parent solved.</p>
|
|
217
|
-
<p><span class="lbl">Under:</span> columnheader and rowheader require a row context per ARIA 1.2 and are absent from the map. Multi-token roles escape the selector.</p>
|
|
218
|
-
<p><span class="lbl">Discipline:</span> fails it asserts are provable except the generic-wrapper and shadow cases.</p>
|
|
219
|
-
</div></details>
|
|
220
|
-
|
|
221
|
-
<details class="rule"><summary><span class="rid">definition-list</span> <span class="sc">SC 1.3.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
222
|
-
<p><span class="lbl">Checks:</span> direct children of a role-less <code><dl></code> must be dt, dd, div, or script-supporting elements.</p>
|
|
223
|
-
<p><span class="lbl">Over:</span> a <code><style></code> child fails, though the engine's own list-structure rule learned that CSS-in-JS injects style elements in place and they never reach the accessibility tree; the fix never propagated here. No rendered-tree filtering either, so a hidden stray child fails here while the identical case passes list-structure.</p>
|
|
224
|
-
<p><span class="lbl">Under:</span> the help text promises term/description pairing but only child tags are policed: a dd-before-dt ordering, a dt with no dd, div wrappers with arbitrary contents, and direct text nodes all pass despite violating the HTML content model.</p>
|
|
225
|
-
<p><span class="lbl">Discipline:</span> fine for what it tests. No pinned cases.</p>
|
|
226
|
-
</div></details>
|
|
227
|
-
|
|
228
|
-
<details class="rule"><summary><span class="rid">dlitem-parent</span> <span class="sc">SC 1.3.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
229
|
-
<p><span class="lbl">Checks:</span> role-less dt/dd must have a dl ancestor.</p>
|
|
230
|
-
<p><span class="lbl">Over:</span> DOM-tree closest() only, so slotted or shadow-hosted items are falsely failed; listitem-parent crosses the flat tree and this rule never got the same fix.</p>
|
|
231
|
-
<p><span class="lbl">Under:</span> a dl whose role was replaced (say tablist) still passes its dt children, though the explicit role replaced the list semantics; the sibling rule fails the equivalent ul case.</p>
|
|
232
|
-
<p><span class="lbl">Discipline:</span> fine for the core case.</p>
|
|
233
|
-
</div></details>
|
|
234
|
-
|
|
235
|
-
<details class="rule"><summary><span class="rid">form-label</span> <span class="sc">SC 1.3.1 + 3.3.2 + 4.1.2, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
236
|
-
<p><span class="lbl">Checks:</span> every non-button field needs a perceivable label: visible label element, aria-label, or aria-labelledby pass; title-only goes to review (H65); placeholder-only, hidden-only, empty, or nothing fails.</p>
|
|
237
|
-
<p><span class="lbl">Over:</span> requiring the label to be visible goes beyond 1.3.1 and 4.1.2 alone, but Understanding 3.3.2 states a label is presented to all users, and the rule carries the 3.3.2 tag, so the hidden-only-label fail is spec-grounded. One message inaccuracy when a hidden label coexists with a placeholder (verdict right, description wrong).</p>
|
|
238
|
-
<p><span class="lbl">Under:</span> label text is read with raw textContent, so a label whose only content is aria-hidden or display-none still passes, though the field then has no accessible name at all. That is the one real defect.</p>
|
|
239
|
-
<p><span class="lbl">Discipline:</span> exemplary; the H65 abstention and differentiated fail messages are the model for the tri-criterion judgment. Well pinned.</p>
|
|
240
|
-
</div></details>
|
|
241
|
-
|
|
242
|
-
<details class="rule"><summary><span class="rid">list-structure</span> <span class="sc">SC 1.3.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
243
|
-
<p><span class="lbl">Checks:</span> ul/ol direct children (flat-tree and slot aware) must carry listitem semantics; unrendered children and div/span wrappers are transparent; a list whose every rendered item is neutralised also fails.</p>
|
|
244
|
-
<p><span class="lbl">Over:</span> only role-less div and span count as generic wrappers, so a custom element wrapper (<code><my-row></code>) around list items is falsely failed, though an unknown element with no role is generic in the same way. The all-items-neutralised fail is a policy call beyond any documented failure technique, but it is measured and pinned.</p>
|
|
245
|
-
<p><span class="lbl">Under:</span> direct stray text nodes inside the ul are invisible to the check, though the rule's own wrapper logic treats stray text as corrupting. An empty role attribute skips judgment entirely though browsers discard it.</p>
|
|
246
|
-
<p><span class="lbl">Discipline:</span> good; provable corruption only, policy pinned.</p>
|
|
247
|
-
</div></details>
|
|
248
|
-
|
|
249
|
-
<details class="rule"><summary><span class="rid">listitem-parent</span> <span class="sc">SC 1.3.1, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
250
|
-
<p><span class="lbl">Checks:</span> a role-less li must be owned, in the flat tree, by a list container, walking through presentation roles and generic wrappers.</p>
|
|
251
|
-
<p><span class="lbl">Over:</span> the same custom-element wrapper blind spot as list-structure, otherwise the hard cases (presentational propagation, slotted parents, role-replaced containers, div role="list") are all handled and pinned by four cases.</p>
|
|
252
|
-
<p><span class="lbl">Under:</span> <code>li role=""</code> escapes the selector though browsers discard empty roles. Marginal.</p>
|
|
253
|
-
<p><span class="lbl">Discipline:</span> the strongest of the structure set; everything it fails is provable from the flat tree.</p>
|
|
254
|
-
</div></details>
|
|
255
|
-
|
|
256
|
-
<details class="rule"><summary><span class="rid">p-as-heading</span> <span class="sc">SC 1.3.1, Level A</span> <span class="pill r-under">UNDER-TESTS</span></summary><div class="body">
|
|
257
|
-
<p><span class="lbl">Checks:</span> a short paragraph whose sole child is bold and holds all its text, followed by another paragraph, is flagged as a possible styled heading.</p>
|
|
258
|
-
<p><span class="lbl">Over:</span> none in the failure sense; it only ever asks for review, and the heuristic is tight.</p>
|
|
259
|
-
<p><span class="lbl">Under:</span> F2's actual territory, the CSS-styled fake heading (a paragraph or div styled big and bold with no strong element), is never examined; the rule reads markup only, no computed styles. A bold lead-in with a trailing tail also escapes.</p>
|
|
260
|
-
<p><span class="lbl">Discipline:</span> perfect abstention; very narrow net.</p>
|
|
261
|
-
</div></details>
|
|
262
|
-
|
|
263
|
-
<details class="rule"><summary><span class="rid">table-headers</span> <span class="sc">SC 1.3.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
264
|
-
<p><span class="lbl">Checks:</span> per data-looking table: fail if headers attributes reference ids that are not cells of the same table, or if no header markup exists at all; headerless tables full of nested tables go to review as likely layout.</p>
|
|
265
|
-
<p><span class="lbl">Over:</span> a data table headed with ARIA instead of th (<code><td role="columnheader"></code>) fails "no header cells" though the relationship is programmatically determined, which is all 1.3.1 asks. Document-wide id resolution can also misfire on duplicate ids.</p>
|
|
266
|
-
<p><span class="lbl">Under:</span> wrong or ambiguous scope, empty th cells, and complex association errors pass by design (disclosed); ARIA tables (<code>role="table"</code> on divs) and treegrids never enter the selector though they are the rule's claimed territory.</p>
|
|
267
|
-
<p><span class="lbl">Discipline:</span> good; the ambiguous case abstains.</p>
|
|
268
|
-
</div></details>
|
|
269
|
-
|
|
270
|
-
<details class="rule"><summary><span class="rid">orientation-lock</span> <span class="sc">SC 1.3.4, Level AA</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
271
|
-
<p><span class="lbl">Checks:</span> stylesheet rules inside orientation media queries that hide or rotate the page root (the "please rotate" wall and the forced-orientation hack).</p>
|
|
272
|
-
<p><span class="lbl">Over:</span> print-context orientation queries are not excluded (contrived). The essential exception is acknowledged in the fix text but not in the verdict for the hide-wall branch; the rotate-branch argument that rendered-rotated content disproves essentiality is sound.</p>
|
|
273
|
-
<p><span class="lbl">Under:</span> disclosed honestly: script locks, cross-origin sheets, and region-level hiding are out of scope, and hiding an SPA's app root rides out under the region exemption, which is the likeliest real-world lock it won't see. Rotation via rotateZ or matrix forms escapes the pattern list.</p>
|
|
274
|
-
<p><span class="lbl">Discipline:</span> proves it or stays silent, with in-force pinned cases.</p>
|
|
275
|
-
</div></details>
|
|
276
|
-
|
|
277
|
-
<details class="rule"><summary><span class="rid">autocomplete-valid</span> <span class="sc">SC 1.3.5, Level AA</span> <span class="pill r-over">OVER-FLAGS</span></summary><div class="body">
|
|
278
|
-
<p><span class="lbl">Checks:</span> any autocomplete attribute must use only tokens from the HTML autofill vocabulary; unknown tokens fail.</p>
|
|
279
|
-
<p><span class="lbl">Over:</span> confirmed. The criterion scopes itself to fields collecting information about the user, and its Understanding doc says other fields need not expose a purpose. The rule fails every unknown token regardless, which makes the widespread autofill-suppression hacks (<code>"nope"</code>, <code>"false"</code>, <code>"chrome-off"</code> on search boxes, coupon codes, admin filters) reported violations on fields the criterion does not cover. By the engine's own principle, a token resembling no purpose on an unknowable field argues for review, not fail.</p>
|
|
280
|
-
<p><span class="lbl">Under:</span> the token list is complete against the HTML spec, but only membership is checked, never the grammar: <code>"on email"</code>, <code>"email shipping"</code> in wrong order, <code>"billing"</code> with no field token, and <code>"webauthn email"</code> all pass despite being invalid values browsers may refuse to map.</p>
|
|
281
|
-
<p><span class="lbl">Discipline:</span> weak on both edges. No pinned cases.</p>
|
|
282
|
-
</div></details>
|
|
283
|
-
|
|
284
|
-
<h2 id="f-contrast">Contrast & visual <span class="sc">(8 rules)</span></h2>
|
|
285
|
-
|
|
286
|
-
<details class="rule"><summary><span class="rid">link-in-text-block</span> <span class="sc">SC 1.4.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
287
|
-
<p><span class="lbl">Checks:</span> links in prose without underline, underline substitute, or a 300+ weight difference need 3:1 colour difference from surrounding text (G183). Wrapped links are measured per line fragment.</p>
|
|
288
|
-
<p><span class="lbl">Over:</span> two real ones: font-style is never read, so a link distinguished by italics alone is failed; and a link marked by a visible icon (child image or generated-content glyph) is a non-colour cue the rule cannot see, failed regardless.</p>
|
|
289
|
-
<p><span class="lbl">Under:</span> the own-text gate counts only direct text nodes, so a paragraph whose surrounding words mostly sit inside inline elements (strong, em) escapes entirely. Prose in divs and other containers is out of scope by measured design.</p>
|
|
290
|
-
<p><span class="lbl">Discipline:</span> asserts only after exhausting every measurable cue; the current G183 requires just the ratio, so the 3:1 pass is spec-clean. Six pinned cases.</p>
|
|
291
|
-
</div></details>
|
|
292
|
-
|
|
293
|
-
<details class="rule"><summary><span class="rid">color-contrast</span> <span class="sc">SC 1.4.3, Level AA</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
294
|
-
<p><span class="lbl">Checks:</span> text contrast at 4.5:1 (3:1 large) against the resolved backdrop: ancestor walk, hit-testing, pseudo-element geometry, image and gradient pixel sampling with luminance bracketing, opacity and animation resting states, modal-scrim double checks, and a film bracket for unplaceable translucent paint.</p>
|
|
295
|
-
<p><span class="lbl">Over:</span> the criterion's logotype and pure-decoration exceptions are not modelled (statically undetectable), so a low-contrast HTML wordmark is failed. Image downsampling to 32x32 can narrow the sampled luminance range on high-frequency images, making a bracketed fail wrong in principle. Both are rare; the fail path is otherwise defended on every side.</p>
|
|
296
|
-
<p><span class="lbl">Under:</span> disclosed: SVG and MathML text are excluded (painted by fill) and no other rule covers them. Quieter: a pointer-events-none compact control is treated as inactive and passed, but such a link can still be keyboard-focusable and operable; and an aria-disabled ancestor exempts plain prose inside it, an escape hatch the criterion does not grant for non-component text. Same-colour text goes to review, never fail, by documented choice.</p>
|
|
297
|
-
<p><span class="lbl">Discipline:</span> exemplary. Every unprovable configuration goes to review with a specific reason; the translucent-text bracket is mathematically honest and pinned; the large-text boundary matches the spec's pt-to-px mapping on the lenient side.</p>
|
|
298
|
-
</div></details>
|
|
299
|
-
|
|
300
|
-
<details class="rule"><summary><span class="rid">control-contrast</span> <span class="sc">SC 1.4.3, Level AA</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
301
|
-
<p><span class="lbl">Checks:</span> value and placeholder text contrast inside form fields, honouring fieldset disabling, resting opacity, and abstaining over images.</p>
|
|
302
|
-
<p><span class="lbl">Over:</span> value-text colour is judged even on an empty input with no placeholder, where nothing is presented yet. Preventive but strictly beyond the criterion.</p>
|
|
303
|
-
<p><span class="lbl">Under:</span> with an opaque background colour, the control's own background-image (gradient-styled inputs) is never consulted, so the flat colour is judged while a gradient actually paints under the text. None of the main rule's overlay, pseudo, or veil machinery runs here: a transparent input over a positioned hero panel gets no backdrop-hazard bracket. Per-option colours in selects are unexamined.</p>
|
|
304
|
-
<p><span class="lbl">Discipline:</span> good and pinned on the endemic placeholder case; the gaps are in what it does not bracket.</p>
|
|
305
|
-
</div></details>
|
|
306
|
-
|
|
307
|
-
<details class="rule"><summary><span class="rid">color-contrast-enhanced</span> <span class="sc">SC 1.4.6, Level AAA</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
308
|
-
<p><span class="lbl">Checks:</span> the identical machinery at the AAA thresholds 7:1 and 4.5:1 large, both spec-correct.</p>
|
|
309
|
-
<p><span class="lbl">Over / under:</span> inherits 1.4.3's exactly. One structural caveat: control text has no AAA twin, so in a AAA audit placeholder and value text are only ever held to AA.</p>
|
|
310
|
-
<p><span class="lbl">Discipline:</span> same as the main rule.</p>
|
|
311
|
-
</div></details>
|
|
312
|
-
|
|
313
|
-
<details class="rule"><summary><span class="rid">non-text-contrast</span> <span class="sc">SC 1.4.11, Level AA</span> <span class="pill r-under">UNDER-TESTS</span></summary><div class="body">
|
|
314
|
-
<p><span class="lbl">Checks:</span> whether a form field's fill or border reaches 3:1 against surroundings; verdicts are pass or review only, never fail, because a boundary is only required when it is the identifying visual information.</p>
|
|
315
|
-
<p><span class="lbl">Over:</span> none possible in the violation sense; the review messages state the conditional correctly.</p>
|
|
316
|
-
<p><span class="lbl">Under:</span> one confirmed bug: the input-group wrapper heuristic passes on any wrapper border with nonzero width and alpha, without measuring that border's contrast, while the sibling wrapper-fill branch measures 3:1. A near-invisible wrapper border produces an asserted pass. Beyond the bug, the scope is deliberately narrow and mostly disclosed: focus indicators, states, icons, and graphical objects are manual; checkboxes, radios, and buttons are excluded by selector even though their boundary is usually the sole identifier. The help string overstates by mentioning icons, which are never checked.</p>
|
|
317
|
-
<p><span class="lbl">Discipline:</span> right posture for a mostly-human criterion, except the unmeasured wrapper-border pass asserts on unproven evidence.</p>
|
|
318
|
-
</div></details>
|
|
319
|
-
|
|
320
|
-
<details class="rule"><summary><span class="rid">text-spacing</span> <span class="sc">SC 1.4.12, Level AA</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
321
|
-
<p><span class="lbl">Checks:</span> injects the criterion's exact four overrides in one pass and reports containers that newly overflow their clipped box, as review.</p>
|
|
322
|
-
<p><span class="lbl">Over:</span> none material; overflow findings are review, never fail, pinned even for a provably line-hiding clamp.</p>
|
|
323
|
-
<p><span class="lbl">Under:</span> confirmed: above 20,000 elements the rule returns pass for everything while the comment says abstain; that is an asserted pass on unmeasured pages. The overlap failure mode (fixed-height boxes spilling onto neighbours without clipping) is invisible to the probe. Shadow-root internals are only partially reached.</p>
|
|
324
|
-
<p><span class="lbl">Discipline:</span> rigorous and pinned, except the pass-on-skip.</p>
|
|
325
|
-
</div></details>
|
|
326
|
-
|
|
327
|
-
<details class="rule"><summary><span class="rid">reflow</span> <span class="sc">SC 1.4.10, Level AA</span> <span class="pill r-under">UNDER-TESTS</span></summary><div class="body">
|
|
328
|
-
<p><span class="lbl">Checks:</span> whether the document already scrolls horizontally at the current viewport; if so, flags for verification at 320 CSS px. Never fails.</p>
|
|
329
|
-
<p><span class="lbl">Over:</span> none as verdicts. The message's claim that things "will be worse" at 320px is an unmeasured prediction (a media query may give a clean mobile layout) and should be softened.</p>
|
|
330
|
-
<p><span class="lbl">Under:</span> honestly disclosed in code and message: the criterion's actual test condition (320 CSS px width) is not run, so the dominant real-world failure class, pages fine at desktop that break narrow, is invisible unless the audit itself runs narrow. The 256 px condition for horizontal-scrolling content is not addressed and not mentioned.</p>
|
|
331
|
-
<p><span class="lbl">Discipline:</span> correct: flags, never asserts, and says what a human must verify.</p>
|
|
332
|
-
</div></details>
|
|
333
|
-
|
|
334
|
-
<details class="rule"><summary><span class="rid">meta-viewport</span> <span class="sc">SC 1.4.4, Level AA</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
335
|
-
<p><span class="lbl">Checks:</span> fails a top-document viewport meta with user-scalable=no or maximum-scale below 2; embedded-frame metas pass because browsers ignore them (measured, pinned).</p>
|
|
336
|
-
<p><span class="lbl">Over:</span> strictly, desktop zoom can satisfy 1.4.4 while the meta blocks mobile pinch, but W3C's own published ACT rule maps exactly these conditions to a failed outcome, so the assert has direct spec-ecosystem backing, and the boundary (exactly 2 allowed) is right. Edges: with two viewport metas the browser honours the last but each is judged independently; an invalid negative maximum-scale that browsers drop still fails.</p>
|
|
337
|
-
<p><span class="lbl">Under:</span> the rest of 1.4.4 (clipped text at 200%, controls that do not resize, viewport-unit text) is untested and honestly scoped out.</p>
|
|
338
|
-
<p><span class="lbl">Discipline:</span> good; the embedded-frame carve-out kills a whole class of false positives.</p>
|
|
339
|
-
</div></details>
|
|
340
|
-
|
|
341
|
-
<h2 id="f-operable">Operable, focus & targets <span class="sc">(11 rules + 1 parked)</span></h2>
|
|
342
|
-
|
|
343
|
-
<details class="rule"><summary><span class="rid">scrollable-region-focusable</span> <span class="sc">SC 2.1.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
344
|
-
<p><span class="lbl">Checks:</span> genuinely scrollable regions must be keyboard reachable: tabbable themselves or containing tabbable content (flat-tree aware). Explicit tabindex -1 with nothing tabbable inside fails; the no-tabindex case is browser-dependent and goes to review.</p>
|
|
345
|
-
<p><span class="lbl">Over:</span> the fail only looks for tabbable content inside the scroller. A carousel scrolled by prev/next buttons outside it is fully keyboard operable (2.1.1 asks for operability, not tab-order membership) yet is failed. An invalid tabindex value is also treated as a deliberate opt-out, with a message about a value the author never wrote.</p>
|
|
346
|
-
<p><span class="lbl">Under:</span> the empty-scroller browser split is handled honestly (measured, pinned); JS-only scrolling is out of scope by nature.</p>
|
|
347
|
-
<p><span class="lbl">Discipline:</span> near-exemplary; one assert can hit keyboard-operable carousels.</p>
|
|
348
|
-
</div></details>
|
|
349
|
-
|
|
350
|
-
<details class="rule"><summary><span class="rid">bypass-blocks</span> <span class="sc">SC 2.4.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
351
|
-
<p><span class="lbl">Checks:</span> page passes if any sufficient technique is present: any landmark, any heading, any resolving in-page anchor, all frames titled, or fewer than 4 links.</p>
|
|
352
|
-
<p><span class="lbl">Over:</span> the criterion governs blocks repeated across pages, which one page cannot prove; the small-page guard is the only protection, so a prose article with 5 links, no headings, and no landmarks fails on unprovable repetition. Rare shape.</p>
|
|
353
|
-
<p><span class="lbl">Under:</span> presence is checked, not function: one footer landmark or one hidden heading passes the whole page, and any resolving anchor counts even if it is a back-to-top link at the bottom, which is not what G1 describes.</p>
|
|
354
|
-
<p><span class="lbl">Discipline:</span> passes when uncertain rather than asserting, which is the defensible direction, but a review tier for present-but-plausibly-nonfunctional techniques does not exist.</p>
|
|
355
|
-
</div></details>
|
|
356
|
-
|
|
357
|
-
<details class="rule"><summary><span class="rid">document-title</span> <span class="sc">SC 2.4.2, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
358
|
-
<p><span class="lbl">Checks:</span> non-embedded documents need a non-empty title; known editor placeholders fail; single generic words go to review; embedded frames pass per the spec's definition of a page.</p>
|
|
359
|
-
<p><span class="lbl">Over:</span> none found; the two tiers map exactly to wrong-under-every-reading versus somebody's call, and both are pinned.</p>
|
|
360
|
-
<p><span class="lbl">Under:</span> the placeholder lexicon is English-only and, unlike the link rules, that limit is not stated in the file. Whether a real-looking title matches the page is deliberately not judged.</p>
|
|
361
|
-
<p><span class="lbl">Discipline:</span> model behaviour.</p>
|
|
362
|
-
</div></details>
|
|
363
|
-
|
|
364
|
-
<details class="rule"><summary><span class="rid">link-name</span> <span class="sc">SC 2.4.4, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
365
|
-
<p><span class="lbl">Checks:</span> every link must have a non-empty accessible name via the shared accname helper.</p>
|
|
366
|
-
<p><span class="lbl">Over:</span> hinges on accname fidelity, which held up under measurement: SVG title children are included (verified in Chromium), labelledby recursion, hidden-content exemptions, slots, and shadow trees all behave. Residual risk is exotic accname corners only.</p>
|
|
367
|
-
<p><span class="lbl">Under:</span> none found; nameless links reliably fail. (Image-map areas are covered separately by area-alt.)</p>
|
|
368
|
-
<p><span class="lbl">Discipline:</span> correct; name presence is provable.</p>
|
|
369
|
-
</div></details>
|
|
370
|
-
|
|
371
|
-
<details class="rule"><summary><span class="rid">link-text-generic</span> <span class="sc">SC 2.4.4, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
372
|
-
<p><span class="lbl">Checks:</span> links whose accessible name is a generic phrase ("read more", "click here") go to review, because programmatically determined context can supply the purpose.</p>
|
|
373
|
-
<p><span class="lbl">Over:</span> none as violations; it never asserts. Review noise is possible but the message says exactly what to check. Pinned.</p>
|
|
374
|
-
<p><span class="lbl">Under:</span> English-only, disclosed with reasoning. The finite list misses cousins ("shop now", "view all"), and identical generic names pointing at different destinations are not correlated.</p>
|
|
375
|
-
<p><span class="lbl">Discipline:</span> exactly right for the criterion's context escape.</p>
|
|
376
|
-
</div></details>
|
|
377
|
-
|
|
378
|
-
<details class="rule"><summary><span class="rid">link-text-generic-only</span> <span class="sc">SC 2.4.9, Level AAA</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
379
|
-
<p><span class="lbl">Checks:</span> the same list at AAA, but asserting fail: link text alone must identify the purpose.</p>
|
|
380
|
-
<p><span class="lbl">Over:</span> the criterion says a mechanism must be available to identify purpose from text alone, and F84 conditions failure on the absence of such a mechanism (a preference that expands link texts). The rule asserts fail with no way to see a mechanism. Rare in the wild, but the assert outruns proof. The in-file reasoning about the ambiguous-to-everyone exception also reaches the right verdict by a backwards argument, which would mislead a future editor.</p>
|
|
381
|
-
<p><span class="lbl">Under:</span> same list limits as the AA rule; non-generic-but-unhelpful text is untestable.</p>
|
|
382
|
-
<p><span class="lbl">Discipline:</span> defensible under F84's practical reading, but this is one place abstention is bent.</p>
|
|
383
|
-
</div></details>
|
|
384
|
-
|
|
385
|
-
<details class="rule"><summary><span class="rid">focus-visible</span> <span class="sc">SC 2.4.7, Level AA</span> <span class="pill r-under">UNDER-TESTS</span></summary><div class="body">
|
|
386
|
-
<p><span class="lbl">Checks:</span> scans same-origin stylesheets for focus rules that strip the outline without a replacement in the same rule (F78), exempting the published focus-visible reset idiom; reports review, never fail.</p>
|
|
387
|
-
<p><span class="lbl">Over:</span> none as violations; the review message is self-disclosing.</p>
|
|
388
|
-
<p><span class="lbl">Under:</span> the confirmed bug: the replacement check treats any set property as a replacement, and CSSOM returns "none" and "transparent" as truthy, so <code>:focus { outline: none; box-shadow: none }</code> and friends, the most aggressive suppressors, silently pass as replaced. Also missed: transparent outline colours, adopted stylesheets and shadow-root sheets, and scripted blur (F55). No pinned cases at all.</p>
|
|
389
|
-
<p><span class="lbl">Discipline:</span> right posture (visibility cannot be asserted from static CSS), but the pass is weaker than it looks.</p>
|
|
390
|
-
</div></details>
|
|
391
|
-
|
|
392
|
-
<details class="rule"><summary><span class="rid">focus-not-obscured</span> <span class="sc">SC 2.4.11, Level AA</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
393
|
-
<p><span class="lbl">Checks:</span> hit-tests each focusable's centre; if an opaque fixed or sticky panel fully contains it and no scroll-padding reserves the edge, asks for a keyboard pass. Never fails.</p>
|
|
394
|
-
<p><span class="lbl">Over:</span> none as violations; full containment matches the criterion's "entirely hidden", partial overlap passes, and scroll-padding suppresses the finding as the Understanding doc's approach.</p>
|
|
395
|
-
<p><span class="lbl">Under:</span> two silent holes: any visible dialog anywhere (including persistent chat widgets shipping as role dialog) disables the rule for the whole page with no trace; and overlays opaque via gradient or background-image with transparent background colour are invisible to the opacity test. No pinned cases.</p>
|
|
396
|
-
<p><span class="lbl">Discipline:</span> the header is a model statement of why geometry alone cannot assert this criterion.</p>
|
|
397
|
-
</div></details>
|
|
398
|
-
|
|
399
|
-
<details class="rule"><summary><span class="rid">target-size</span> <span class="sc">SC 2.5.8, Level AA</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
400
|
-
<p><span class="lbl">Checks:</span> targets at 24x24 or the spec's spacing-circle test, with inline and user-agent exceptions, overlap re-measurement, and same-action dismissal; wrapped links judged on line fragments.</p>
|
|
401
|
-
<p><span class="lbl">Over:</span> two concrete: the user-agent exception is implemented only for checkboxes and radios, but the spec excepts any target whose size the agent determines and the author left unmodified, so an unstyled select or small default button crowded by a sibling fails despite the exception provably applying. And the equivalent-target exception is only applied in the overlap path, not the spacing path: a 20px icon link beside a large text link to the same URL fails though the matching hrefs are in hand.</p>
|
|
402
|
-
<p><span class="lbl">Under:</span> any clip-path makes an element pointer-invisible to the rule, so a visible clipped avatar button is neither judged nor counted as a crowder. The selector misses textarea, summary, area, and ARIA widget roles, which also weakens the spacing test. The essential exception needs the human reviewing the finding, which the header discloses but the output does not.</p>
|
|
403
|
-
<p><span class="lbl">Discipline:</span> the spacing geometry is the most spec-faithful implementation in the file set, and every contentious call is pinned with measured provenance.</p>
|
|
404
|
-
</div></details>
|
|
405
|
-
|
|
406
|
-
<details class="rule"><summary><span class="rid">target-size-enhanced</span> <span class="sc">SC 2.5.5, Level AAA</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
407
|
-
<p><span class="lbl">Checks:</span> the same machinery at 44x44, correctly without the spacing exception (verified: 2.5.5's exceptions are equivalent, inline, user agent, essential only).</p>
|
|
408
|
-
<p><span class="lbl">Over:</span> inherits and amplifies the user-agent gap: at 44px every unstyled native control fails a criterion that explicitly excepts unmodified agent sizing; only checkboxes and radios are exempt. Equivalent and essential are unprovable but fail is asserted, and at AAA density that will fire on excepted content.</p>
|
|
409
|
-
<p><span class="lbl">Under:</span> same selector and clip-path holes. No pinned cases of its own; behaviour is locked only transitively through the shared factory.</p>
|
|
410
|
-
<p><span class="lbl">Discipline:</span> usually spec-correct at this threshold, with a wider blast radius for the same tensions.</p>
|
|
411
|
-
</div></details>
|
|
412
|
-
|
|
413
|
-
<details class="rule"><summary><span class="rid">auth-field-obstruction</span> <span class="sc">SC 3.3.8, Level AA</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
414
|
-
<p><span class="lbl">Checks:</span> credential fields with inline paste/drop blockers or autocomplete off. One obstruction goes to review; both together fail.</p>
|
|
415
|
-
<p><span class="lbl">Over:</span> the fail branch is unsound on the rule's own evidence, twice: the criterion is satisfied by an alternative (passkey, SSO, emailed link) which the header admits the rule cannot see, and the premise that autocomplete off removes the password-manager mechanism is contradicted three lines later by its own third branch ("browsers often fill anyway"). The pinned case locks this over-assertion in as policy.</p>
|
|
416
|
-
<p><span class="lbl">Under:</span> honestly scoped: framework-attached blockers are invisible, and the criterion's central subject, cognitive tests themselves (CAPTCHAs, security questions), is outside this rule's remit, as the name admits.</p>
|
|
417
|
-
<p><span class="lbl">Discipline:</span> the two review branches teach the distinction well; the single asserting branch asserts what the file concedes it cannot know and should be a strongly-worded review.</p>
|
|
418
|
-
</div></details>
|
|
419
|
-
|
|
420
|
-
<details class="rule"><summary><span class="rid">label-in-name</span> <span class="sc">SC 2.5.3, Level A, PARKED</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
421
|
-
<p><span class="lbl">Status:</span> deliberately unregistered by product decision (findings volume not wanted); the criterion is classed manual so it lands in the human checklist rather than being silently skipped. The rule stays maintained.</p>
|
|
422
|
-
<p><span class="lbl">If re-enabled:</span> the core containment check is spec-correct, visible-text computation is careful, and the assert is appropriate. Two flaws to fix first: a visually hidden component (sr-only button with aria-label) is still judged though nothing is presented visually; and image alt inside a control is ignored though the criterion covers images of text, so a visible logo-text button with a divergent label is missed. No pinned cases.</p>
|
|
423
|
-
</div></details>
|
|
424
|
-
|
|
425
|
-
<h2 id="f-aria">ARIA, robust & language <span class="sc">(15 rules)</span></h2>
|
|
426
|
-
|
|
427
|
-
<details class="rule"><summary><span class="rid">aria-allowed-attr</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
428
|
-
<p><span class="lbl">Checks:</span> non-global aria attributes on roles that do not support them (an ARIA author MUST). Asks for review, never asserts, because the attribute is inert and no criterion provably fails.</p>
|
|
429
|
-
<p><span class="lbl">Over:</span> one wrong question: the implicit-role table maps td to cell unconditionally, so a td inside a grid (which is really a gridcell, where aria-selected is legal) gets asked about a legitimate attribute. The deprecated foursome is correctly kept legal, verified against the per-role tables.</p>
|
|
430
|
-
<p><span class="lbl">Under:</span> unmodelled roles pass without guessing, honest. Label attributes are routed to aria-label-misuse, which covers fewer elements than the prohibition (see that rule).</p>
|
|
431
|
-
<p><span class="lbl">Discipline:</span> the ask-not-assert stance was verified as spec-honest, and the pinned cases pin exactly the right line, including the ARIA 1.1-to-1.2 narrowing.</p>
|
|
432
|
-
</div></details>
|
|
433
|
-
|
|
434
|
-
<details class="rule"><summary><span class="rid">aria-attr-valid</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
435
|
-
<p><span class="lbl">Checks:</span> aria attribute names must exist in the 1.2 vocabulary and enumerated attributes must carry an allowed value. Asserts fail.</p>
|
|
436
|
-
<p><span class="lbl">Over:</span> confirmed: aria-orientation legally takes undefined (its default token) and aria-hidden is typed true/false/undefined, but both enum entries reject undefined as a critical fail on conforming markup. An unknown attribute name on a non-widget (an analytics hook) is also asserted as critical although it is inert, which is exactly the reasoning that makes the sibling rule abstain; applied inconsistently here.</p>
|
|
437
|
-
<p><span class="lbl">Under:</span> numeric values are never validated and token-list values are unchecked; IDREFs are honestly delegated.</p>
|
|
438
|
-
<p><span class="lbl">Discipline:</span> right to assert on a malformed value for a supported state; the blanket assert on unknown names does not meet the engine's own provability bar.</p>
|
|
439
|
-
</div></details>
|
|
440
|
-
|
|
441
|
-
<details class="rule"><summary><span class="rid">aria-field-name</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
442
|
-
<p><span class="lbl">Checks:</span> ARIA-built fields and value widgets need an accessible name; author-only roles cannot be named by content. All role classifications verified against the 1.2 tables.</p>
|
|
443
|
-
<p><span class="lbl">Over:</span> none found. The ARIA 1.1 combobox escape hatch passes a wrapper the ARIA spec says must be named, because the input users reach is labelled: a deliberate, documented divergence in favour of the user-outcome reading. Defensible, but unpinned; it should get a case.</p>
|
|
444
|
-
<p><span class="lbl">Under:</span> honestly scoped, but engine-wide the other name-required author-only roles (radiogroup, tree, grid, treegrid, tabpanel, region, application) are covered by no rule anywhere, so an unnamed tree passes silently. Backlog item, not this rule's defect.</p>
|
|
445
|
-
<p><span class="lbl">Discipline:</span> asserts only what the spec makes provable; the progressbar and meter additions are pinned with citations.</p>
|
|
446
|
-
</div></details>
|
|
447
|
-
|
|
448
|
-
<details class="rule"><summary><span class="rid">aria-hidden-focus</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
449
|
-
<p><span class="lbl">Checks:</span> focusable elements inside aria-hidden regions, reachable by keyboard but imperceptible to screen readers.</p>
|
|
450
|
-
<p><span class="lbl">Over:</span> a tabindex of -2 or other negative values is counted as focusable because the filter compares the literal string "-1"; such elements are not tab-reachable. Rare. The opacity and inert exclusions kill the big real-world false positives and are pinned.</p>
|
|
451
|
-
<p><span class="lbl">Under:</span> does not pierce shadow roots, so an aria-hidden host with focusable shadow content passes. The open-modal carve-out downgrades every aria-hidden finding on the page to review, over-abstaining in the honest direction.</p>
|
|
452
|
-
<p><span class="lbl">Discipline:</span> exemplary; asserts the provable case, abstains where script-managed focus makes the DOM inconclusive.</p>
|
|
453
|
-
</div></details>
|
|
454
|
-
|
|
455
|
-
<details class="rule"><summary><span class="rid">aria-label-misuse</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
456
|
-
<p><span class="lbl">Checks:</span> aria-label on plain generic containers (div, span, p), which ARIA 1.2 prohibits naming. Asks for review.</p>
|
|
457
|
-
<p><span class="lbl">Over:</span> none found; the name-from-content carve-out is spec-correct and pinned.</p>
|
|
458
|
-
<p><span class="lbl">Under:</span> the prohibition covers eleven roles (including strong, em, code, caption, del, ins, presentation) and the selector covers three elements; the rest breaks the identical MUST and neither this rule nor its sibling ever mentions it. Undocumented gap.</p>
|
|
459
|
-
<p><span class="lbl">Discipline:</span> the ask stance is well grounded, with exposure measured rather than assumed, and pinned into the default audit.</p>
|
|
460
|
-
</div></details>
|
|
461
|
-
|
|
462
|
-
<details class="rule"><summary><span class="rid">aria-valid-refs</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
463
|
-
<p><span class="lbl">Checks:</span> seven IDREF aria attributes must point at existing elements in the same tree; duplicate-id targets go to review.</p>
|
|
464
|
-
<p><span class="lbl">Over:</span> lazily-rendered targets: only aria-controls under a collapsed aria-expanded is excused, so a describedby tooltip created on hover, or a tab's aria-controls to a lazily-mounted panel (tabs use aria-selected), is failed though the pattern works when the user gets there. By the engine's own inertness doctrine a dangling describedby harms no name, role, or value, so the assert proves more than the DOM shows; dangling labelledby and activedescendant asserts are fully justified.</p>
|
|
465
|
-
<p><span class="lbl">Under:</span> aria-flowto omitted; trivial.</p>
|
|
466
|
-
<p><span class="lbl">Discipline:</span> good on duplicates; slightly over-assertive on the describedby class.</p>
|
|
467
|
-
</div></details>
|
|
468
|
-
|
|
469
|
-
<details class="rule"><summary><span class="rid">button-name</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
470
|
-
<p><span class="lbl">Checks:</span> buttons must have a non-empty accessible name.</p>
|
|
471
|
-
<p><span class="lbl">Over:</span> two rare accname edges: a button labelled via labelledby pointing at a select is not credited with the selection text (the helper implements embedded-control values for text inputs only), and the CSS content alternative-text syntax contributes nothing.</p>
|
|
472
|
-
<p><span class="lbl">Under:</span> none found; UA-default submit names, generated content, slots, shadow, and the hidden-subtree rules all match the accname spec under verification.</p>
|
|
473
|
-
<p><span class="lbl">Discipline:</span> correct; nameless buttons are provable.</p>
|
|
474
|
-
</div></details>
|
|
475
|
-
|
|
476
|
-
<details class="rule"><summary><span class="rid">dialog-name</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
477
|
-
<p><span class="lbl">Checks:</span> rendered dialogs must have an author-provided name; body text never names a dialog. Verified: both roles are name-required, name-from-author.</p>
|
|
478
|
-
<p><span class="lbl">Over / under:</span> none found; closed dialogs are correctly filtered, labelledby resolution is pinned.</p>
|
|
479
|
-
<p><span class="lbl">Discipline:</span> correct assert; fully provable.</p>
|
|
480
|
-
</div></details>
|
|
481
|
-
|
|
482
|
-
<details class="rule"><summary><span class="rid">frame-title</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
483
|
-
<p><span class="lbl">Checks:</span> iframes need a title or other accessible name; hidden and aria-hidden frames are excluded.</p>
|
|
484
|
-
<p><span class="lbl">Over:</span> 1x1 utility trackers that are not hidden are flagged; spec-aligned since they sit in the accessibility tree, but the fix text offers only a title when aria-hidden is the right fix for such frames. Wrong prescription, right verdict.</p>
|
|
485
|
-
<p><span class="lbl">Under:</span> none found.</p>
|
|
486
|
-
<p><span class="lbl">Discipline:</span> correct.</p>
|
|
487
|
-
</div></details>
|
|
488
|
-
|
|
489
|
-
<details class="rule"><summary><span class="rid">label-for-valid</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-over">OVER-FLAGS</span></summary><div class="body">
|
|
490
|
-
<p><span class="lbl">Checks:</span> label[for] must reference an existing labelable control in the same tree.</p>
|
|
491
|
-
<p><span class="lbl">Over:</span> confirmed and structural: an orphaned label does not by itself fail 4.1.2. A dangling for attribute next to a control named via aria-label satisfies the criterion completely; with 4.1.1 Parsing retired, the dangling reference has no criterion of its own. The actual failure, a nameless control, is form-label's finding. The rule asserts a serious criterion failure on pages the spec passes.</p>
|
|
492
|
-
<p><span class="lbl">Under:</span> none in scope; shadow-root resolution and the for-kills-implicit-association rule are both correctly implemented.</p>
|
|
493
|
-
<p><span class="lbl">Discipline:</span> should ask, or fail only when the defect leaves a control nameless.</p>
|
|
494
|
-
</div></details>
|
|
495
|
-
|
|
496
|
-
<details class="rule"><summary><span class="rid">nested-interactive</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
497
|
-
<p><span class="lbl">Checks:</span> an interactive element must not contain another user-reachable interactive element; managed-focus patterns are filtered out up front.</p>
|
|
498
|
-
<p><span class="lbl">Over:</span> low; the spec grounding (children-presentational roles, the HTML content model) is strong for most pairs, and the auditor could not construct a conforming page it flags.</p>
|
|
499
|
-
<p><span class="lbl">Under:</span> shadow descendants are not traversed, and a few widget roles (textbox, combobox, gridcell) are not in the interactive list, so a link inside a role-textbox contenteditable passes.</p>
|
|
500
|
-
<p><span class="lbl">Discipline:</span> careful filters do the abstention work up front.</p>
|
|
501
|
-
</div></details>
|
|
502
|
-
|
|
503
|
-
<details class="rule"><summary><span class="rid">role-required-aria</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-under">UNDER-TESTS</span></summary><div class="body">
|
|
504
|
-
<p><span class="lbl">Checks:</span> explicit widget roles that require a state (checked, valuenow, expanded) must carry it.</p>
|
|
505
|
-
<p><span class="lbl">Over:</span> confirmed: a checkbox input re-roled as switch is failed for missing aria-checked, but ARIA-in-HTML sanctions exactly that markup and forbids adding aria-checked to it; the native-state carve-out only covers same-role redundancy, not the sanctioned swaps.</p>
|
|
506
|
-
<p><span class="lbl">Under:</span> measured against the 1.2 required-state tables, six requirements are unchecked and undocumented: combobox and scrollbar need aria-controls, scrollbar and meter and focusable separator need aria-valuenow, option needs aria-selected, heading needs aria-level. The map is roughly a third of the spec's table.</p>
|
|
507
|
-
<p><span class="lbl">Discipline:</span> right to assert on a stateless checkbox role; the coverage is the problem.</p>
|
|
508
|
-
</div></details>
|
|
509
|
-
|
|
510
|
-
<details class="rule"><summary><span class="rid">valid-role</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
511
|
-
<p><span class="lbl">Checks:</span> role tokens must be valid concrete ARIA roles, with prefix vocabularies and fallback-token semantics honoured. The role table was diffed against the spec: all 80 concrete roles present, all 12 abstract roles correctly absent, deliberate draft additions documented.</p>
|
|
512
|
-
<p><span class="lbl">Over:</span> the harmless-fallback escape hatch covers only div, span, and svg, so a bogus role on a paragraph or list is asserted as critical although the role is discarded, the element falls back to its real semantics, and nothing a user receives is wrong; the file's own stated principle (report invalid roles that mask semantics or decorate interactive elements) says these should pass or ask.</p>
|
|
513
|
-
<p><span class="lbl">Under:</span> none material; the escape direction is safe.</p>
|
|
514
|
-
<p><span class="lbl">Discipline:</span> good instincts, but the assert/pass boundary is drawn by tag name instead of by the file's own principle.</p>
|
|
515
|
-
</div></details>
|
|
516
|
-
|
|
517
|
-
<details class="rule"><summary><span class="rid">html-lang</span> <span class="sc">SC 3.1.1, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
518
|
-
<p><span class="lbl">Checks:</span> the html element needs a lang attribute that is a plausible BCP 47 tag.</p>
|
|
519
|
-
<p><span class="lbl">Over:</span> none found; singletons, numeric regions, private-use forms, and case handling all check out, and rejecting long primary subtags like "english" matches the validity reading H57 requires.</p>
|
|
520
|
-
<p><span class="lbl">Under:</span> a wrong-but-valid declaration (lang="en" on a French page) is undetectable statically and universally abstained on; the file never says so, a disclosure nit.</p>
|
|
521
|
-
<p><span class="lbl">Discipline:</span> correct; both failure modes are provable.</p>
|
|
522
|
-
</div></details>
|
|
523
|
-
|
|
524
|
-
<details class="rule"><summary><span class="rid">valid-lang-parts</span> <span class="sc">SC 3.1.2, Level AA</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
525
|
-
<p><span class="lbl">Checks:</span> lang attributes on elements below the root must be valid BCP 47 tags; empty passes.</p>
|
|
526
|
-
<p><span class="lbl">Over:</span> none found.</p>
|
|
527
|
-
<p><span class="lbl">Under:</span> the primary real-world failure, foreign passages with no lang markup at all, is undetectable here, implicitly disclosed by the rule's name. One wrong comment: empty lang means language unknown per HTML, not a reset to the page language (verdict unaffected).</p>
|
|
528
|
-
<p><span class="lbl">Discipline:</span> correct; asserts only the provable syntactic failure.</p>
|
|
529
|
-
</div></details>
|
|
530
|
-
|
|
531
|
-
<h2 id="f-batch">The new batch <span class="sc">(8 rules, shipped and adversarially audited 10 August 2026)</span></h2>
|
|
532
|
-
<p>These eight were implemented by this sweep, then audited the same day by fresh eyes with zero attachment to the implementation. That pass found one genuine over-assert and several narrow-signal gaps; the clear-cut ones were fixed and re-pinned the same day (the batch carries 36 pinned cases). Ratings below are post-fix.</p>
|
|
533
|
-
|
|
534
|
-
<details class="rule"><summary><span class="rid">video-loop-motion</span> <span class="sc">SC 2.2.2, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
535
|
-
<p><span class="lbl">Checks:</span> autoplaying looped videos without controls at meaningful size get a review asking whether a pause mechanism reaches them. Loaded-but-paused passes: either autoplay was refused or a control already worked, and nothing is moving.</p>
|
|
536
|
-
<p><span class="lbl">Fixed same day:</span> the size floor was OR-based, so a 1200x60 animated banner strip passed as icon-scale; now small-on-both-axes only, pinned.</p>
|
|
537
|
-
<p><span class="lbl">Remaining, disclosed:</span> autoplay without loop but longer than 5 seconds is unexamined (duration not consulted); IDL-property autoplay and script-started playback never match the attribute selector.</p>
|
|
538
|
-
</div></details>
|
|
539
|
-
|
|
540
|
-
<details class="rule"><summary><span class="rid">error-message-linkage</span> <span class="sc">SC 3.3.1, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
541
|
-
<p><span class="lbl">Checks:</span> fields captured in an invalid state: a broken, empty, or hidden aria-errormessage target gets a review naming the broken reference; invalid with no linkage at all gets a review; a populated visible target passes; errormessage on a non-invalid field is inert and passes.</p>
|
|
542
|
-
<p><span class="lbl">Fixed same day (the adversarial pass's flagship find):</span> the broken-reference branch originally asserted a 3.3.1 violation, but Understanding 3.3.1 is explicit that programmatic association is not required, so adjacent visible text can satisfy the criterion on its own. Both shapes now review, never assert; invalid now includes the spelling and grammar states per ARIA 1.2; image-of-text error messages now count via the accname walk. All re-pinned.</p>
|
|
543
|
-
<p><span class="lbl">Remaining, disclosed:</span> fields in visual-only error state (no aria-invalid) are invisible, which is the state-dependent design.</p>
|
|
544
|
-
</div></details>
|
|
545
|
-
|
|
546
|
-
<details class="rule"><summary><span class="rid">composite-widget-name</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
547
|
-
<p><span class="lbl">Checks:</span> nameless tabs, menu items, options, and treeitems fail; all six roles verified name-required and name-from-contents in the ARIA 1.2 tables.</p>
|
|
548
|
-
<p><span class="lbl">Fixed same day:</span> <code><button role="tab"></code> and <code><a href role="tab"></code> were double-reported, since button-name and link-name match the tag regardless of the role override; this rule now owns only div/span-style hosts, pinned.</p>
|
|
549
|
-
<p><span class="lbl">Remaining:</span> a private-use icon-font glyph via generated content counts as a name per accname, so a tab announced as garbage passes: a quality gap, not a spec error. Async listboxes rendering empty mid-load draw a snapshot fail.</p>
|
|
550
|
-
</div></details>
|
|
551
|
-
|
|
552
|
-
<details class="rule"><summary><span class="rid">summary-name</span> <span class="sc">SC 4.1.2, Level A</span> <span class="pill r-solid">SOLID</span></summary><div class="body">
|
|
553
|
-
<p><span class="lbl">Checks:</span> the first summary child of a details (the actual disclosure control per the HTML spec) must have an accessible name; other summaries are plain content and owe nothing.</p>
|
|
554
|
-
<p><span class="lbl">Adversarial result:</span> hostile shapes (nested wrappers, hidden first summary, second summaries) all behaved; nothing found. Known asymmetry, accepted: a details with no summary at all passes on the UA-supplied "Details" legend, which is barely more informative than the empty summary the rule fails.</p>
|
|
555
|
-
</div></details>
|
|
556
|
-
|
|
557
|
-
<details class="rule"><summary><span class="rid">embed-alt</span> <span class="sc">SC 1.1.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
558
|
-
<p><span class="lbl">Checks:</span> nameless embeds fail; document formats get a review because embedded text puts 1.1.1's applicability in question.</p>
|
|
559
|
-
<p><span class="lbl">Fixed same day:</span> the document carve-out covered only text types; PDF-via-embed (the common real-world case), XHTML, and typeless embeds with a document-extension src now review instead of fail, pinned.</p>
|
|
560
|
-
<p><span class="lbl">Remaining:</span> no size floor, so a legacy 1x1 tracking embed hard-fails while presenting nothing; aria-hidden remains the decorative escape.</p>
|
|
561
|
-
</div></details>
|
|
562
|
-
|
|
563
|
-
<details class="rule"><summary><span class="rid">canvas-alt</span> <span class="sc">SC 1.1.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
564
|
-
<p><span class="lbl">Checks:</span> roleless chart-sized canvas with no name, no fallback, and no named enclosing control gets a review; decorative versus meaningful is the human call, per the roleless-SVG precedent.</p>
|
|
565
|
-
<p><span class="lbl">Fixed same day:</span> the fallback check used raw textContent, so a chart library's bootstrap script inside the canvas counted as fallback content; script, style, and template text now excluded, pinned.</p>
|
|
566
|
-
<p><span class="lbl">Remaining:</span> a canvas with an invalid role exits this selector and is not picked up by svg-img-alt, vanishing between rules; the enclosing-control allowlist is narrower than the real set of naming containers.</p>
|
|
567
|
-
</div></details>
|
|
568
|
-
|
|
569
|
-
<details class="rule"><summary><span class="rid">on-input-navigation</span> <span class="sc">SC 3.2.2 + 3.2.1, Level A</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body">
|
|
570
|
-
<p><span class="lbl">Checks:</span> inline onchange/oninput on form controls and onfocus anywhere whose handler writes to location, submits, or opens a window: review, since conditional code paths and the advised-beforehand clause are human questions.</p>
|
|
571
|
-
<p><span class="lbl">Fixed same day:</span> the pattern matched reads of location, so analytics handlers drew reviews; now writes and navigating calls only. requestSubmit and oninput added. All pinned, including the location-read pass.</p>
|
|
572
|
-
<p><span class="lbl">Remaining, disclosed:</span> bare submit() without a dot, SPA router navigation, and all addEventListener registrations are invisible.</p>
|
|
573
|
-
</div></details>
|
|
574
|
-
|
|
575
|
-
<details class="rule"><summary><span class="rid">visual-order-divergence</span> <span class="sc">SC 2.4.3, Level A</span> <span class="pill r-under">UNDER-TESTS</span></summary><div class="body">
|
|
576
|
-
<p><span class="lbl">Checks:</span> focusable direct siblings in one flex/grid container where CSS order or a reversed flow makes measured visual order diverge from DOM order: one review per container. Triple-gated so pages that never reorder produce zero findings; RTL-aware; pinned in both directions including the order-agrees-with-DOM pass.</p>
|
|
577
|
-
<p><span class="lbl">Known cost, disclosed in the header:</span> focusables wrapped one level deep (cards: flex container, then a div, then the link), the dominant real-world shape, never meet the container as direct siblings and are not examined; grid's native reordering (explicit placement, template areas, dense flow) reorders without order or reverse and is not treated as evidence. Widening either needs measured cases first.</p>
|
|
578
|
-
</div></details>
|
|
579
|
-
|
|
580
|
-
<h2 id="f-bp">Best practice <span class="sc">(20 opt-in rules, never WCAG-tagged)</span></h2>
|
|
581
|
-
<p>Verified across the whole set: every rule carries exactly the best-practice tag and nothing else, so none of them inflates WCAG coverage. Only 2 of 20 have pinned spec cases, the set's biggest structural weakness.</p>
|
|
582
|
-
|
|
583
|
-
<details class="rule"><summary><span class="rid">region</span> <span class="pill r-solid">SOLID</span></summary><div class="body"><p>All perceivable content must sit inside a landmark. The most defensively engineered rule in the set: per-branch dedupe, a no-landmarks-at-all short-circuit, and a spec-derived skip-link carve-out remove every classic noise mode of this rule class. The skip-link exception is slightly lenient (any pre-header fragment nav passes), which errs in the quiet direction.</p></div></details>
|
|
584
|
-
|
|
585
|
-
<details class="rule"><summary><span class="rid">redundant-aria-label</span> <span class="pill r-solid">SOLID</span></summary><div class="body"><p>An aria-label identical to the visible text, flagged only on name-from-content roles, which kills the classic false positive (a nav labelled "Main" containing a "Main" link). Pinned in both directions; the best-pinned rule in the set. Value-named buttons (input value plus identical aria-label) escape.</p></div></details>
|
|
586
|
-
|
|
587
|
-
<details class="rule"><summary><span class="rid">empty-heading</span> <span class="pill r-solid">SOLID</span></summary><div class="body"><p>Headings need a non-empty accessible name. Careful role demotion and real accname use mean icon and image headings with names pass; no realistic false positive identified.</p></div></details>
|
|
588
|
-
|
|
589
|
-
<details class="rule"><summary><span class="rid">landmark-unique</span> <span class="pill r-solid">SOLID</span></summary><div class="body"><p>Duplicate banner/main/contentinfo and same-role-same-label landmarks. Implicit-role and demotion handling is unusually thorough; the invalid-role-token edges are cosmetic.</p></div></details>
|
|
590
|
-
|
|
591
|
-
<details class="rule"><summary><span class="rid">positive-tabindex</span> <span class="pill r-solid">SOLID</span></summary><div class="body"><p>Any tabindex above zero. Universally supported guidance with essentially zero false-positive surface.</p></div></details>
|
|
592
|
-
|
|
593
|
-
<details class="rule"><summary><span class="rid">redundant-image-alt</span> <span class="pill r-solid">SOLID</span></summary><div class="body"><p>An image inside a control whose alt exactly duplicates the control's text. Conservative by design; partial duplication ("Acme logo" beside "Acme") is a scope choice, not a bug.</p></div></details>
|
|
594
|
-
|
|
595
|
-
<details class="rule"><summary><span class="rid">fieldset-legend</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body"><p>Same-name radio/checkbox groups need a named group. Sound, WAI-tutorial-backed, and pinned in both directions. One defect: the group key merges id-less forms, so two unrelated forms each holding one same-named radio can be fused into a phantom group and flagged.</p></div></details>
|
|
596
|
-
|
|
597
|
-
<details class="rule"><summary><span class="rid">heading-order</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body"><p>No skipping heading levels, walking all headings flat. Inherent noise on sidebar and widget headings that belong to unrelated rails, plus a NaN hole: a garbage aria-level poisons the walk and silently passes surrounding jumps.</p></div></details>
|
|
598
|
-
|
|
599
|
-
<details class="rule"><summary><span class="rid">landmark-one-main</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body"><p>Exactly one visible main landmark. Correct on top-level pages, but it lacks the embedded-document gate every page-level WCAG rule has, and the extension injects into all frames, so every ad and widget iframe fails.</p></div></details>
|
|
600
|
-
|
|
601
|
-
<details class="rule"><summary><span class="rid">page-heading-one</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body"><p>The page needs an h1. Same missing frame gate as landmark-one-main, and the inverse visibility bug: a display-none h1 satisfies the check though no AT user can navigate to it.</p></div></details>
|
|
602
|
-
|
|
603
|
-
<details class="rule"><summary><span class="rid">landmark-top-level</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body"><p>Banner, main, and contentinfo must not nest inside another landmark. One concrete false positive: an unnamed form ancestor counts as a landmark though unnamed forms are not exposed as landmarks (checkout pages wrapping content in a form get flagged). Complementary is missing from the checked set though the guidance includes it.</p></div></details>
|
|
604
|
-
|
|
605
|
-
<details class="rule"><summary><span class="rid">multiple-labels</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body"><p>More than one visible label per field. Correctly demoted from WCAG scope after a previous sweep (the file says why); still flags the deliberate short-label-plus-sr-only-expansion pattern because clip-hidden labels count as visible.</p></div></details>
|
|
606
|
-
|
|
607
|
-
<details class="rule"><summary><span class="rid">new-window-link</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body"><p>New-window links should say so. Correct advisory placement, but the warning regex is English-only (all-noise on non-English sites) and both too strict and too loose ("Opens in Spotify" passes; a describedby warning fails).</p></div></details>
|
|
608
|
-
|
|
609
|
-
<details class="rule"><summary><span class="rid">accesskeys</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body"><p>Duplicate accesskey values. Hidden-element duplicates over-flag (SPA double renders), multi-token values slip past whole-attribute comparison, and the "browser can only honour one" message overstates the HTML spec.</p></div></details>
|
|
610
|
-
|
|
611
|
-
<details class="rule"><summary><span class="rid">redundant-alt-phrase</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body"><p>Alt text starting with "image of" and cousins. Right guidance, but the anchored match bites proper nouns ("Icon of the Seas docked at port") and the suggested fix would amputate the noun.</p></div></details>
|
|
612
|
-
|
|
613
|
-
<details class="rule"><summary><span class="rid">redundant-role</span> <span class="pill r-minor">MINOR GAPS</span></summary><div class="body"><p>Explicit roles equal to the implicit role. Good core with the two famous VoiceOver carve-outs honoured in code, but it keeps a local role table that diverges from the shared one: an aside nested in sectioning content is generic, so role="complementary" there is doing real work and gets flagged; td-in-grid likewise; and the SVG exception tests the URL for ".svg", losing the carve-out for data URIs.</p></div></details>
|
|
614
|
-
|
|
615
|
-
<details class="rule"><summary><span class="rid">button-type</span> <span class="pill r-over">OVER-FLAGS</span></summary><div class="body"><p>Buttons in forms without a type attribute. Flags the single most common valid pattern on the web: a form whose one button is the intended submit, written without a type. No accessibility authority backs the advice (it is a JS-framework lint convention, and the cited HTML spec page deliberately defaults to submit), and nothing about it is AT-specific.</p></div></details>
|
|
616
|
-
|
|
617
|
-
<details class="rule"><summary><span class="rid">no-autofocus</span> <span class="pill r-over">OVER-FLAGS</span></summary><div class="body"><p>Any autofocus attribute fails unconditionally. But autofocus inside a dialog is the HTML spec's recommended way to set initial dialog focus (and the dialog pattern requires moving focus in), and the spec also countenances autofocus on single-purpose pages. With visibility off it even fires while the dialog is closed. Meanwhile the actually harmful modern equivalent, scripted focus on load, is invisible.</p></div></details>
|
|
618
|
-
|
|
619
|
-
<details class="rule"><summary><span class="rid">redundant-tabindex</span> <span class="pill r-over">OVER-FLAGS</span></summary><div class="body"><p>tabindex zero on natively focusable elements. The roving-tabindex pattern puts exactly that token on the current toolbar or tablist item, siblings at -1; the rule flags the current item and its fix would break the pattern's state management. Needs a sibling-at-minus-one or composite-widget exception.</p></div></details>
|
|
620
|
-
|
|
621
|
-
<details class="rule"><summary><span class="rid">redundant-aria</span> <span class="pill r-over">OVER-FLAGS</span></summary><div class="body"><p>ARIA states duplicating native attributes. Presence-only matching gives actively harmful fix advice on custom elements: a framework-reflected stray checked attribute on a non-form element plus aria-checked gets told the native attribute already informs AT, which is false there, and following the fix would break the widget. Contradictions (aria-checked false beside checked) are also mislabelled as duplicates.</p></div></details>
|
|
622
|
-
|
|
623
|
-
<h2 id="coverage">What the engine does not test, and cannot</h2>
|
|
624
|
-
<p>The catalog classes 30 of 86 criteria as partially automated and 56 as manual, and no criterion is claimed as fully automated: every one ends in a question about meaning only a person can settle. This audit re-examined every manual classification to ask whether a browser-measurable heuristic could at least flag candidates. The honest answer for most: no, and faking it would be worse than silence. Four structural walls account for most of the manual list:</p>
|
|
625
|
-
<ul>
|
|
626
|
-
<li><b>Media content judgment</b> (all of 1.2.x beyond track presence, background audio): nothing in the DOM knows what the pixels and samples mean.</li>
|
|
627
|
-
<li><b>Cross-page consistency</b> (2.4.5, 3.2.3, 3.2.4, 3.2.6, and repetition in 2.4.1): a single-page engine cannot see "the same relative order across the set", and detecting a help link's presence on one page proves nothing.</li>
|
|
628
|
-
<li><b>Interaction-dependent behaviour</b> (2.1.2 keyboard traps, 2.1.4 shortcuts, 1.4.13 hover content, 2.5.1/2.5.2/2.5.4/2.5.6, 3.2.5): JS listeners are invisible to a passive scan; these need driven focus, keys, and pointers, which is outside the passive audit model.</li>
|
|
629
|
-
<li><b>Flash analysis</b> (2.3.1, 2.3.2): real analysis needs frame decoding and luminance thresholds. A "video present, please review" heuristic would fire on essentially every media page while proving nothing; explicitly resisted.</li>
|
|
630
|
-
</ul>
|
|
631
|
-
<div class="scroll"><table>
|
|
632
|
-
<tr><th>Manual SC</th><th>Could a heuristic help?</th><th>Honest assessment</th></tr>
|
|
633
|
-
<tr><td>2.4.3 Focus Order</td><td>Yes, narrow</td><td>CSS order and reversed flex/grid where visual order diverges from DOM order among focusable siblings is pure geometry; the general "order preserves meaning" stays human. Proposed below, riskiest of the batch.</td></tr>
|
|
634
|
-
<tr><td>3.3.1 Error Identification</td><td>Yes, state-dependent</td><td>aria-invalid without message linkage, and aria-errormessage pointing at empty or hidden nodes. Fires only when the page is captured mid-error, so zero over-flag by construction. Proposed below.</td></tr>
|
|
635
|
-
<tr><td>3.2.2 On Input</td><td>Yes, narrow</td><td>Inline onchange handlers that navigate (the classic jump menu). High-precision legacy signature; listener-based handlers stay invisible. Proposed below.</td></tr>
|
|
636
|
-
<tr><td>1.3.2 Meaningful Sequence</td><td>Narrow slice</td><td>Same CSS-reordering mechanism as 2.4.3; shares that rule's evidence.</td></tr>
|
|
637
|
-
<tr><td>2.5.7 Dragging</td><td>Marginal</td><td>Explicit draggable attributes could flag for review, but libraries set it on already-operable widgets; weaker signal than the batch above.</td></tr>
|
|
638
|
-
<tr><td>2.4.12 Focus Not Obscured (Enhanced)</td><td>Yes, cheap upgrade</td><td>Same geometry as 2.4.11 with an any-part-covered predicate; AAA, low priority.</td></tr>
|
|
639
|
-
<tr><td>2.4.6 Headings and Labels</td><td>Trivial slice</td><td>Placeholder heading text is real but nearly empty in the wild; descriptiveness is semantic. Not worth a WCAG-tagged rule yet.</td></tr>
|
|
640
|
-
<tr><td>1.4.5 Images of Text</td><td>Not without OCR</td><td>Every proxy (aspect ratio, filename, long alt) over-flags photography; logos are excepted anyway. Dead end until OCR ships.</td></tr>
|
|
641
|
-
<tr><td>4.1.3 Status Messages</td><td>Mostly no</td><td>Live-region absence proves nothing on a snapshot; the failure is about dynamically added messages a single pass never witnesses. Would need a mutation-observation window.</td></tr>
|
|
642
|
-
<tr><td>1.3.3 Sensory Characteristics</td><td>No</td><td>Language-dependent phrase matching; a false-positive disaster across locales. Dead end.</td></tr>
|
|
643
|
-
<tr><td>3.3.7 Redundant Entry</td><td>No</td><td>The tempting confirm-email slice mostly passes the criterion (autofill makes the value available), and the process scope is invisible. Honest dead end.</td></tr>
|
|
644
|
-
<tr><td>2.3.3, 3.2.1, 1.4.8, 2.4.10, others</td><td>Weak slices only</td><td>Each has a computable fragment (reduced-motion queries, inline onfocus, justified text, long prose without headings), but each either over-flags by design or is AAA with author-default caveats. Noted, not proposed.</td></tr>
|
|
645
|
-
<tr><td>All remaining manual SCs</td><td>No</td><td>Media judgment, cross-page structure, process-level requirements, or lexical semantics. Genuinely manual.</td></tr>
|
|
646
|
-
</table></div>
|
|
647
|
-
|
|
648
|
-
<h2 id="missing">Missing failure families</h2>
|
|
649
|
-
<p>The six families this sweep found (nameless embed, roleless canvas charts, the muted hero video, icon-only summary, empty-named composite-widget items, and 3.3.1's broken-linkage state) are <b>all closed</b> by the shipped batch. The open coverage debts are now the smaller gaps the adversarial pass identified:</p>
|
|
650
|
-
<ol>
|
|
651
|
-
<li><b>2.2.2:</b> autoplay without loop but longer than 5 seconds (duration is measurable and unconsulted); IDL-property autoplay and script-started playback.</li>
|
|
652
|
-
<li><b>2.4.3:</b> focusables wrapped one level deep inside reordered containers; grid-native reordering without order or reverse.</li>
|
|
653
|
-
<li><b>4.1.2:</b> the name-required author-only composite containers (radiogroup, tree, grid, treegrid, tabpanel, region, application) still have no rule anywhere.</li>
|
|
654
|
-
<li><b>1.1.1:</b> a canvas with an invalid role falls between canvas-alt and svg-img-alt.</li>
|
|
655
|
-
</ol>
|
|
656
|
-
|
|
657
|
-
<h2 id="next">Next batch (proposed, not implemented)</h2>
|
|
658
|
-
<p>The previous sweep's eight-rule proposal is fully shipped (see <a href="#f-batch">the new batch</a>). The queue now is mostly a quality pass on standing defects rather than new coverage:</p>
|
|
659
|
-
<ol>
|
|
660
|
-
<li><b>composite-container-name</b> (4.1.2, A): nameless <code>role="tree" / "grid" / "treegrid" / "tabpanel" / "radiogroup" / "region"</code>, all name-required and author-only per ARIA 1.2: fail. Binary, provable, closes the last composite gap.</li>
|
|
661
|
-
<li><b>Fix the four standing over-asserters:</b> label-for-valid asks (or gates on the control being nameless); autocomplete-valid reviews unknown tokens and gains a grammar check; audio-control's loop branch reviews, mirroring pause-stop-hide; object-alt adopts embed-alt's document gate.</li>
|
|
662
|
-
<li><b>video-duration upgrade</b> to video-loop-motion: consult duration for non-looping autoplay over 5 seconds, as review.</li>
|
|
663
|
-
<li><b>focus-visible truthiness fix:</b> treat "none", "0", and "transparent" values as removal, not replacement (confirmed defect above).</li>
|
|
664
|
-
<li><b>non-text-contrast wrapper-border measurement</b> (confirmed defect above).</li>
|
|
665
|
-
<li><b>text-spacing skip honesty:</b> return incomplete (or report a skip) above the element ceiling instead of pass.</li>
|
|
666
|
-
<li><b>aria-attr-valid enum corrections</b> plus numeric and token-list validation.</li>
|
|
667
|
-
<li><b>visual-order-divergence widening</b> to one-level wrappers, pinned by measured cases before shipping.</li>
|
|
668
|
-
</ol>
|
|
669
|
-
|
|
670
|
-
<h2 id="themes">Cross-cutting themes</h2>
|
|
671
|
-
<h3>The inertness doctrine is applied unevenly</h3>
|
|
672
|
-
<p>The engine's most distinctive idea, ask instead of asserting when broken markup is inert in browsers, is applied to unsupported attributes and prohibited labels, but not to unknown attribute names (aria-attr-valid asserts), dangling describedby references (aria-valid-refs asserts), orphaned labels (label-for-valid asserts), or bogus roles outside div/span/svg (valid-role asserts). All four assert under the same conditions the doctrine says to ask. Deciding this once and applying it everywhere would resolve four findings in one stroke.</p>
|
|
673
|
-
<h3>Hard-won lessons don't propagate to siblings</h3>
|
|
674
|
-
<p>list-structure learned that injected style elements never reach the accessibility tree; definition-list still fails them. listitem-parent crosses shadow boundaries; dlitem-parent and both aria-required rules don't. The wrapped-link line-fragment lesson reached target-size but the wrapper concept ("what counts as generic") is defined three different ways. A shared-helper pass would fix a class of bugs, not instances.</p>
|
|
675
|
-
<h3>Shadow DOM is the biggest silent-miss surface</h3>
|
|
676
|
-
<p>Confirmed blind spots in aria-required-children, aria-required-parent, dlitem-parent, aria-hidden-focus (focusables in shadow trees), focus-visible (adopted stylesheets), and text-spacing (shadow-internal declarations). Meanwhile the accname helper and listitem-parent handle shadow correctly, so the machinery exists.</p>
|
|
677
|
-
<h3>English-only lexicons</h3>
|
|
678
|
-
<p>document-title's placeholder list (undisclosed), link-text-generic's phrase list (disclosed with reasoning), and new-window-link's warning regex (all-noise on non-English sites). At minimum the disclosure should be uniform.</p>
|
|
679
|
-
<h3>Pinned-case coverage is uneven</h3>
|
|
680
|
-
<p>The spec-case harness is the engine's best defence and its coverage map is lopsided: the five legacy 1.1.1 rules, audio-control, autocomplete-valid, definition-list, focus-visible, focus-not-obscured, target-size-enhanced, the parked label-in-name, and 18 of 20 best-practice rules have no pinned cases at all. Several confirmed bugs above live exactly in the unpinned zone, which is not a coincidence. The new batch is the counterexample: fully pinned from day one (36 cases), and the same-day adversarial fixes were pinned with it.</p>
|
|
681
|
-
|
|
682
|
-
<h2 id="method">Method and honesty notes</h2>
|
|
683
|
-
<p>The sweep followed the repo's spec-sweep protocol: coverage regenerated and validated, the catalog diffed against the published Recommendation, and then every rule file read in full, including the shared helpers its verdicts depend on, by seven parallel auditors judging only against W3C text (the normative spec, Understanding documents, ARIA 1.2, accname, ARIA-in-HTML, HTML). An eighth adversarial auditor covered the newly implemented batch the same day, with fresh eyes and instructions to construct hostile DOM shapes. No other tool's behaviour was consulted or used as a target. A few claims were settled empirically in Chromium rather than by reading (CSSOM truthiness, SVG title display, ARIA table extraction from the downloaded spec).</p>
|
|
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
|
-
<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
|
-
|
|
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
|
-
</main>
|
|
689
|
-
</body>
|
|
690
|
-
</html>
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Pour engine accuracy and performance audit · 12 September 2026</title><style>
|
|
2
|
+
:root{color-scheme:light dark;--bg:#fafaf7;--fg:#202321;--muted:#525a54;--line:#c9cec8;--panel:#fff;--accent:#846000}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:16px/1.6 system-ui,sans-serif}main{max-width:1120px;margin:auto;padding:48px 28px 100px}h1{font-size:clamp(2rem,4vw,3.5rem);line-height:1.1;max-width:23ch;letter-spacing:-.025em}h2{font-size:1.7rem;margin-top:2.5rem}h3{font-size:1.3rem;margin-top:2rem}h4{font-size:1.1rem}p,li{max-width:90ch}a{color:inherit;text-underline-offset:.2em}a:hover{text-decoration-thickness:2px}a:focus-visible,summary:focus-visible,[tabindex]:focus-visible{outline:3px solid var(--accent);outline-offset:4px}.eyebrow,.muted{color:var(--muted)}nav{display:flex;flex-wrap:wrap;gap:12px 24px;padding:24px 0;border-bottom:1px solid var(--line)}section{margin-top:40px;scroll-margin-top:24px}.stats{display:flex;flex-wrap:wrap;gap:10px;margin:24px 0}.stat{background:var(--panel);border:1px solid var(--line);padding:12px 16px}.stat b{font-size:1.5rem;margin-right:.25em}details{border-top:1px solid var(--line);padding:12px 0}summary{cursor:pointer;padding:10px 0;font-weight:650}summary .rating{font-size:.8rem;margin-left:12px;color:var(--muted)}details[open]>summary{margin-bottom:12px}details details{margin-left:12px}code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.9em;overflow-wrap:anywhere}pre{padding:18px;background:var(--panel);border:1px solid var(--line);white-space:pre-wrap;overflow:auto}.table-scroll{overflow:auto;margin:20px 0}table{width:100%;border-collapse:collapse;text-align:left;font-size:.95rem}th,td{padding:12px;border-bottom:1px solid var(--line);vertical-align:top}thead{background:var(--panel)}ul{padding-left:24px}li{margin:8px 0}img{max-width:100%;height:auto;border:1px solid var(--line)}figure{margin:24px 0}figcaption{color:var(--muted);margin-top:8px}footer{margin-top:50px;border-top:1px solid var(--line);padding-top:20px;color:var(--muted)}@media(prefers-color-scheme:dark){:root{--bg:#111412;--fg:#eef1ed;--muted:#b5beb5;--line:#3b453c;--panel:#1b211c;--accent:#eed46a}}@media(max-width:600px){main{padding:24px 18px 64px}th,td{padding:8px}summary .rating{display:block;margin-left:0}}@media print{details{break-inside:avoid}nav{display:none}main{max-width:none}body{font-size:11pt}}
|
|
3
|
+
</style></head><body><main><p class="eyebrow">REPOSITORY REVIEW · 12 SEPTEMBER 2026 · c61fe1a</p><h1>Accuracy first. Measured performance. Explicit uncertainty.</h1><p>Baseline review of 88 active rules and one parked rule, followed by implemented repairs. The verification section records the current batch; detailed rule ratings below describe the original audit snapshot.</p><nav aria-label="Audit sections"><a href="#repairs">Repairs and verification</a><a href="#findings">Baseline findings</a><a href="#rules">Every rule</a><a href="#manual">Manual scope</a><a href="#evidence">Detailed evidence</a></nav><section id="repairs"><h2>Repairs and verification</h2><h2>Accuracy and performance repairs</h2><p>12 September 2026. Working-tree changes against <code>c61fe1a</code>. No version bump, publication, commit or push. Verification below was completed on the frozen final source between 08:41 and 09:05 UTC; the handover written at 08:52 UTC, <a href="../../reports/engine-audit/2026-09-12/HANDOVER.md">HANDOVER.md</a>, records the state before the last checks ran.</p><p>A plain-language account of every rule change, with the Beta Block count explained, is in <a href="../../reports/engine-audit/2026-09-12/rule-changes.html">rule-changes.html</a>.</p><p>The repairs target demonstrated wrong findings and expensive scans. They do not certify WCAG conformance or establish a universal accuracy score. WCAG requirements, their exceptions, browser accessibility trees and measured pixels decide the verdict. Comparator output is a second opinion, not a specification.</p><h3>Implemented</h3><ul><li>Accessible names now include nested references, visible generated descendant text and native image labels, while excluding hidden descendants of visible references and hidden generated content. Semantic visibility follows slotted ancestry and excludes inert content. Boxless semantic containers remain eligible.</li><li>Native role fallback, contextual ARIA values, dormant error references, associated image maps, form labels and table-header relationships have regression coverage. Unprovable table, autocomplete and optional-reference claims request review.</li><li>Text and control contrast account for actual text fill, group opacity, partial-alpha backgrounds and first-line styling. Bounded multiply blending uses the browser compositing formula; an invisible unpainted word joiner does not create spurious uncertainty. Populated controls do not test unpainted placeholders. Unsupported paint effects request review. A shadow-root text-spacing probe is applied inside the affected root; budget exits disclose unchecked candidates.</li><li>Target geometry retains valid usable areas after overlaps, excludes disabled controls and checks equivalent controls before using the exception. Spatial indexing replaces the quadratic spacing scan. Bulk checks cooperate with cancellation.</li><li>CSS orientation declarations are checked against active conditions and computed styles. Overridden declarations are not failures. Other heuristics, including legacy motion, generic titles, time limits and bypass blocks, request review when the required facts cannot be established.</li><li>Element-scoped runs include the element itself and its open shadow root. Finding selectors use mutation-aware indexes. Task channels and audit caches are released.</li><li>Extension frame audits use private runtime transport and browser-verified frame ancestry. Slow frames get an audit deadline rather than the discovery deadline. Missing frames produce explicit review findings. Shadow-root frames are included.</li><li>The editor excludes orientation checks that now require browser rendering rather than silently returning a false pass. Site fixture claims count violations and review findings together.</li></ul><h3>Verification on the final source</h3><p>Every check below ran after the last source edit. The engine bundle hashed during the corpus run matches the working tree at the time of writing.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Check</th><th scope="col">Result</th><th scope="col">Evidence</th></tr></thead><tbody><tr><td>Engine regression cases</td><td>805 of 805 pass; 687 at baseline, 118 added</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/handover-cases.log">handover-cases.log</a></td></tr><tr><td>Site and extension build</td><td>Passed; site output isolated from the running preview watcher</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/handover-build.log">handover-build.log</a></td></tr><tr><td>Editor package and static lane</td><td>Passed; the package is 5,721 KB</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/handover-vscode-package.log">handover-vscode-package.log</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/handover-vscode.log">handover-vscode.log</a></td></tr><tr><td>Real extension frame audit, Show and hover</td><td>Passed</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/handover-frames.log">handover-frames.log</a></td></tr><tr><td>ARIA tables and site facts</td><td>Passed</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/handover-aria-tables.log">handover-aria-tables.log</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/handover-facts.log">handover-facts.log</a></td></tr><tr><td>Code independence scan</td><td>Clean</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/handover-cleanroom.log">handover-cleanroom.log</a>, <a href="../../reports/engine-audit/2026-09-12/../../cleanroom/2026-09-12-0134.html">report</a></td></tr><tr><td>Full lab gate, run 2026-09-12-08-41-06</td><td>Passed; figures below</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/handover-lab.log">handover-lab.log</a></td></tr><tr><td>Four-surface smoke</td><td>First attempt at 08:45 UTC failed to launch Chromium (a 30 s wait for its debugging endpoint, before any surface ran) while the gate and three corpus shards were still running. Rerun at 08:57 UTC on an idle machine: all four surfaces pass</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/handover-smoke-failed-launch.log">handover-smoke-failed-launch.log</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/final-smoke.log">final-smoke.log</a></td></tr><tr><td>Frame boundary scenarios</td><td>Six of six pass: private results, slow frame, shadow frame, unavailable frame, spoofed descendant, 16,000-control child</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/final-frame-boundaries.log">final-frame-boundaries.log</a></td></tr><tr><td>Runtime contracts</td><td>Element and shadow-host roots, mutable paths, cancellation in 56 ms</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/final-runtime.log">final-runtime.log</a></td></tr><tr><td>Beta Block scoring</td><td>Identical to the earlier run in fix-accuracy.log; figures below</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/final-accuracy.log">final-accuracy.log</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/final-accuracy.json">final-accuracy.json</a></td></tr><tr><td>Firefox testbeds</td><td>Not verified: the headless renderer could not start in this environment. The Firefox package builds</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/fix-firefox-launch-error.log">fix-firefox-launch-error.log</a></td></tr></tbody></table></div><p>The smoke failure is not attributed to the source: the same build passed on rerun and nothing changed between the two attempts except machine load.</p><h4>Contrast gate</h4><p>The final gate sampled live pages, so its counts differ from the earlier gate in <a href="../../reports/engine-audit/2026-09-12/evidence/fix-lab-gate.log">fix-lab-gate.log</a> (321 samples) without any source change between the two contrast runs. Do not read the difference as an algorithm change.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Measure</th><th scope="col">Final gate</th></tr></thead><tbody><tr><td>Samples, pages</td><td>312, 22</td></tr><tr><td>Confirmed failures</td><td>51</td></tr><tr><td>Likely false positives</td><td>0</td></tr><tr><td>Potential missed failures</td><td>0</td></tr><tr><td>Confirmed passes</td><td>135</td></tr><tr><td>Skipped</td><td>28</td></tr><tr><td>Reviews with a justified varied background</td><td>8</td></tr><tr><td>Reviews that measure as passing</td><td>66</td></tr><tr><td>Reviews that measure as failing</td><td>8</td></tr><tr><td>Borderline, within antialiasing noise</td><td>3</td></tr><tr><td>Mean, median ratio error</td><td>0.003, 0</td></tr></tbody></table></div><p>Eleven testbeds clean. Coverage 88 rules, 39 criteria partially assisted, 47 manual. This is sampled evidence, not proof that no contrast defect remains.</p><h4>Beta Block, against the shipped engine</h4><p>The pinned benchmark run (2026-09-11-2339) scores the shipped engine on the same fixture, so the two can be set side by side.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Measure</th><th scope="col">Shipped engine</th><th scope="col">This source</th><th scope="col">Comparator</th></tr></thead><tbody><tr><td>Planted violations asserted, of 38</td><td>38</td><td>32</td><td>28</td></tr><tr><td>Planted violations caught including review</td><td>38</td><td>38</td><td>31</td></tr><tr><td>Planted reviews caught, of 8</td><td>8</td><td>8</td><td>0</td></tr><tr><td>Assertions credited, unmatched</td><td>39, 0</td><td>39, 0</td><td>30, 0</td></tr></tbody></table></div><p>Six planted faults moved from assertion to review: V02 (1.4.4, a viewport that blocks zoom), V12 and V15 (1.3.5, the autocomplete token <code>e-mail</code>), V17 (1.3.1, a data table with no header cells), V33 (2.2.1, a timed refresh every 7200 s) and V34 (1.3.4, an orientation lock under a media condition that is not active). Each follows the batch's rule that an assertion needs evidence the engine can establish, and each review message says what a person must check. The comparator asserts four of the six (V02, V12, V15, V33). The homepage copy and the facts guard in <code>scripts/lib/facts.js</code> were changed to count violations and reviews together, which is a weaker claim than the live site makes today. Whether the six stay as reviews is a policy decision to take before any release, because re-pinning the benchmark on this source would change the site's figure from 38 to 32 asserted.</p><p>Checked against W3C's published tests on 12 September: the ACT rules for <a href="https://www.w3.org/WAI/standards-guidelines/act/rules/bc659a/">a meta refresh delay</a>, <a href="https://www.w3.org/WAI/standards-guidelines/act/rules/b4f0c3/">a viewport that blocks zoom</a>, <a href="https://www.w3.org/WAI/standards-guidelines/act/rules/b33eff/">orientation locked by CSS rotation</a> and <a href="https://www.w3.org/WAI/standards-guidelines/act/rules/73f2c2/">autocomplete values</a> each map a failed outcome to "success criterion is not satisfied", under stated assumptions: that the page offers no way to turn the refresh off and it is not essential, that no other mechanism resizes the text, that the orientation is not essential, and that the field collects information about the user. <a href="https://www.w3.org/WAI/WCAG22/Techniques/failures/F41">F41</a> lists checks for those exceptions in its procedure. The shipped engine and the comparator follow the ACT reading and assert; this source follows the procedure literally and asks a person to make the checks. Two details: the orientation ACT rule evaluates both orientations, so an inactive media condition is not a reason to abstain; and it covers rotation only, so the hidden-root case planted on Beta Block was the engine's own extension in both versions. On the fixture itself every exception check comes out against the page, so the reviews there ask a person to confirm what the markup already shows.</p><h4>Controlled workload timings</h4><p>Using the same DOM and helper code with only the target module replaced, 4,000 separated small targets improved from a 3,686 ms median to 135 ms with identical outcomes. Selector serialization of 4,000 sibling findings improved from 157.9 ms to 3.7 ms with matching targets and mutation checks. These are individual workloads, not whole-engine or competitor speed claims. See <a href="../../reports/engine-audit/2026-09-12/evidence/fix-target-performance.json">target timings</a> and <a href="../../reports/engine-audit/2026-09-12/evidence/fix-path-performance.json">selector timings</a>.</p><h3>Corpus differential on the final source</h3><p><a href="../../reports/engine-audit/2026-09-12/differential.mjs">differential.mjs</a> builds <code>c61fe1a</code> and the working tree separately, loads each benchmark page once, runs both on that one document and matches findings by live element identity. The comparator was run in the earlier snapshot (<a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-0.json">fix-differential-0.json</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-1.json">1</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-2.json">2</a>) and skipped in the final one, since it did not change.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Measure</th><th scope="col">Final snapshot</th></tr></thead><tbody><tr><td>Pages attempted</td><td>138</td></tr><tr><td>Completed in three concurrent shards, 08:48 UTC</td><td>130</td></tr><tr><td>Completed on retry alone, 08:59 UTC (MDN, HSBC inclusion)</td><td>2</td></tr><tr><td>Unavailable in every run</td><td>6</td></tr><tr><td>Pages whose DOM changed during the run</td><td>18</td></tr><tr><td>Verdict changes, all completed pages</td><td>454</td></tr></tbody></table></div><p>The six unavailable pages are UK Parliament, United Nations, Bank of England, NSPCC and AP News (HTTP 403 or a challenge page) and John Lewis (an HTTP/2 protocol error on every attempt, including a retry at 09:00 UTC). Shards: <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-0-final.json">0</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-1-final.json">1</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-2-final.json">2</a>; retries: <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-retry-mdn-final.json">MDN</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-retry-hsbc-inclusion-final.json">HSBC inclusion</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-retry-john-lewis-final.json">John Lewis</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/final-differential-retries.log">log</a>. The three shards ran concurrently for accuracy, so their timings are not performance evidence.</p><p>The 454 verdict changes fall into these classes. Half of them are on one page.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Class</th><th scope="col">Rows</th><th scope="col">Where</th><th scope="col">Status</th></tr></thead><tbody><tr><td>Contrast assertions removed by corrected multiply arithmetic</td><td>225</td><td>ECMAScript specification</td><td>Measured: glyph pixels read 4.57:1 where the old arithmetic gave 4.03:1. <a href="../../reports/engine-audit/2026-09-12/adjudication-visual.md">adjudication-visual.md</a></td></tr><tr><td>Contrast reviews removed by actual partial-alpha compositing</td><td>8</td><td>Social Security Administration 7, TypeScript 1</td><td>Measured. <a href="../../reports/engine-audit/2026-09-12/adjudication-visual.md">adjudication-visual.md</a></td></tr><tr><td>Text or control contrast now under review because a filter or blend cannot be resolved</td><td>143</td><td>15 pages; Ant Design 59, GitHub 12, React 11, Astro 10, Tailwind 9, MUI 7, accessiBe 7, Service-Public 6, Stripe 6, Svelte 5, Atlassian 4, BBC Good Food 4, one each on pour.dev, Apple and the Internet Archive</td><td>Conservative by design: 118 were silent passes, 25 were assertions the engine could not prove. This is the batch's largest cost to users, and a census shows part of it is avoidable: of 184 such reviews live on nine of these pages, 77 are text in a translucent container over a filtered backdrop and 35 sit under a colour-changing filter or a group blend (genuine uncertainty), while 65 have opaque paint between the text and the backdrop filter and 7 come from a drop shadow alone, so those 72 could have been judged exactly. <a href="../../reports/engine-audit/2026-09-12/evidence/final-filter-blend-census.json">final-filter-blend-census.json</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/final-filter-blend-avoidable.json">final-filter-blend-avoidable.json</a></td></tr><tr><td>Contrast now under review because an image or gradient paints the backdrop and its pixels could not be settled</td><td>22</td><td>12 pages; Japanese Wikipedia 10, ETH Zurich 2, one each on ten others</td><td>Conservative by design; not individually measured</td></tr><tr><td>Field boundary reviews added</td><td>9</td><td>Age UK 3, Ant Design 2, BBC Good Food, Scope, Stanford, MUI</td><td>Not individually measured</td></tr><tr><td>Assertions demoted to review under the unprovable-claim policy: table headers 5, viewport zoom 2, scrollable region 2, marquee 1, orientation 1, autocomplete 1, refresh 1, bypass blocks 1</td><td>14</td><td>ECMAScript, GDS audit page, Beta Block, UW demo, Excalidraw, IKEA, Hacker News</td><td>Policy; the Beta Block rows are the six above. Marquee reasoning in <a href="../../reports/engine-audit/2026-09-12/adjudication-interaction.md">adjudication-interaction.md</a></td></tr><tr><td>Assertions removed with evidence: target size 3, blink 2, button name 2</td><td>7</td><td>Ant Design, GDS audit page, Mozilla, PostgreSQL</td><td>Equivalent controls hit-tested; no animation on the blink elements; the browser exposes the glyph names. <a href="../../reports/engine-audit/2026-09-12/adjudication-interaction.md">adjudication-interaction.md</a>, <a href="../../reports/engine-audit/2026-09-12/adjudication-semantics.md">adjudication-semantics.md</a></td></tr><tr><td>Generic-link reviews removed</td><td>6</td><td>GDS, BBC Good Food, Sense, UserWay, BBC News, Apple</td><td>The controls carry button or tab roles. <a href="../../reports/engine-audit/2026-09-12/adjudication-semantics.md">adjudication-semantics.md</a></td></tr><tr><td>New assertion: menubar without exposed menu items</td><td>1</td><td>Python.org</td><td>Confirmed against the Chromium accessibility tree. <a href="../../reports/engine-audit/2026-09-12/adjudication-semantics.md">adjudication-semantics.md</a></td></tr><tr><td>New contrast assertions on a page that changed during the run</td><td>2</td><td>Ruby</td><td>Page state, not code: in four settled alternating runs both builds assert both links identically, 4.46:1 and 3.84:1. <a href="../../reports/engine-audit/2026-09-12/evidence/final-france24-ruby-probe.json">final-france24-ruby-probe.json</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/final-ruby-try-link.png">final-ruby-try-link.png</a></td></tr><tr><td>Reading-order reviews moved between sibling elements</td><td>8</td><td>France 24</td><td>Page state, not code: the baseline's first run on a fresh page names one sibling, and every later run of either build names the other. Same probe file</td></tr><tr><td>Other new reviews: unsupported <code>aria-selected</code> on list items 5, unnamed SVG 2, text-spacing budget disclosure 1; one SVG review removed</td><td>9</td><td>World Bank, Cambridge, ECMAScript, TypeScript</td><td>Review direction; not individually measured</td></tr></tbody></table></div><p>The 4,809 word-joiner reviews seen on the ECMAScript page before the final correction are absent from the final snapshot.</p><h3>Isolated performance, with caveats</h3><p>Source: <a href="../../reports/engine-audit/2026-09-12/performance-after.mjs">performance-after.mjs</a>, data <a href="../../reports/engine-audit/2026-09-12/evidence/fix-performance-isolated.json">fix-performance-isolated.json</a>. One loaded page, the six A and AA tags, a 1280 by 800 viewport, engine order rotated each round, page scripts left running. The comparator ran once on the specification because of its cost. The two small pages were re-timed after all other browser work had stopped. This is a diagnostic profile, not a replacement for the published benchmark, whose pin is unchanged.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Page</th><th scope="col">Elements</th><th scope="col">Baseline</th><th scope="col">This source</th><th scope="col">Comparator</th></tr></thead><tbody><tr><td>ECMAScript specification, initial run</td><td>194,103</td><td>13,784.5 ms</td><td>6,112.1 ms</td><td>188,737 ms, one run</td></tr><tr><td>ECMAScript specification, median of three warm runs</td><td>194,103</td><td>13,416.2 ms</td><td>6,180.7 ms</td><td>not repeated</td></tr><tr><td>nerdy.dev, rounds 2 and 3</td><td>18,659</td><td>9,308.5 and 9,263.6 ms</td><td>10,303.8 and 10,284.7 ms</td><td>5,608.4 and 5,646.9 ms</td></tr><tr><td>Astro, median of three warm runs</td><td>1,855</td><td>39.7 ms</td><td>42.6 ms</td><td>114.5 ms</td></tr><tr><td>React, median of three warm runs</td><td>1,849</td><td>47.2 ms</td><td>50.9 ms</td><td>163.0 ms</td></tr></tbody></table></div><p>On the specification this source is about 2.3 times faster than the baseline. The single comparator run there is not a median and must not be quoted as a universal ratio. On nerdy.dev this source is about 11% slower than the baseline and 1.8 times slower than the comparator; rounds 0 and 1 overlapped the tail of another process and are excluded. The added cost is in contrast (6,337.5 ms against 5,695.6 ms), link name (237.7 against 106.8) and generic link (228.4 against 105.1). No page was dropped from this table to improve the picture.</p><h3>Integration changes</h3><p>The extension adds <code>webNavigation</code> solely to authenticate parent and child frame relationships. Chrome lists this permission with the warning "Read your browsing history". The implementation reads frame ancestry in the audited tab; it does not collect history or transmit it. The store update prompt must be reviewed before distributing the build. See <a href="https://developer.chrome.com/docs/extensions/reference/permissions-list#webNavigation">Chrome's permission documentation</a> and the site's <a href="../../reports/engine-audit/2026-09-12/../../../src/site/public/security/index.html">security page</a>, which documents the use.</p><p>Generated audit reports and the Beta Block fixture remain standalone. No existing published benchmark is replaced or relabelled as a measurement of these changes.</p><h3>Limits and follow-up</h3><p>Closed shadow roots, inaccessible embedded documents, dynamic interaction flows and semantic judgments still limit automation. Uncertain findings remain in review. The baseline audit retains other source-level risks and possible enhancements; this batch does not claim every issue is resolved. Firefox runtime behaviour is unverified. The corpus differential covers one viewport, one load and one moment on each live page; 18 of the pages changed while being measured.</p><p>Corpus adjudication evidence: <a href="../../reports/engine-audit/2026-09-12/adjudication-semantics.md">semantic findings</a>, <a href="../../reports/engine-audit/2026-09-12/adjudication-visual.md">paint and lenient changes</a>, <a href="../../reports/engine-audit/2026-09-12/adjudication-interaction.md">target and motion exceptions</a>. The first corpus review exposed a native-option regression in the draft; it was corrected and pinned before the final run. A later naming check found hidden generated text duplicating a link label; that was also corrected and pinned with four cases.</p><h3>Afternoon follow-up: assertions restored, abstention narrowed</h3><p>Done on 12 September between 09:30 and 11:10 UTC after the recommendation below was accepted. Same working tree, still against <code>c61fe1a</code>, still uncommitted.</p><p><strong>Assertions restored.</strong> Meta refresh, viewport zoom, orientation lock and autocomplete values assert again, as the shipped engine and the ACT rules do, and every message now names the assumption the assertion rests on and the exception a reviewer may apply. Orientation keeps the batch's real gains: an active block is checked against computed style, so an overridden or cancelled declaration is not a failure; a dormant block is read in cascade order, so a later declaration undoes an earlier lock; unsupported <code>@supports</code> branches and disabled sheets are skipped. Autocomplete keeps one narrowing: a search box with a bad value is asked about rather than failed. The headerless table was restored too, on the rule's own guards (a grid of at least four short text cells, no nested table, no block content, no layout role), after the demotion proved to throw away a determination the rule made carefully; a table whose every cell is only a link or a button is new and stays a review as a navigation layout.</p><p><strong>Abstention narrowed.</strong> A filter made only of drop shadows changes no pixel of its own element, and a backdrop filter or a drop shadow reaches text only through non-opaque paint, so opaque paint at or below the effect now settles the question. Colour-changing filters and group blends still recolour everything inside them and stay reviews. Twelve original cases pin these behaviours.</p><p><strong>Verified on the result.</strong> Every check below ran on the final source of this follow-up.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Check</th><th scope="col">Result</th><th scope="col">Evidence</th></tr></thead><tbody><tr><td>Engine regression cases</td><td>817 of 817 pass; 12 added</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/final-restore-engine.log">final-restore-engine.log</a> has the testbeds; the case log is in the session record</td></tr><tr><td>Testbeds, ARIA tables</td><td>Eleven testbeds clean; every table matches the spec</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/final-restore-engine.log">final-restore-engine.log</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/final-restore-aria.log">final-restore-aria.log</a></td></tr><tr><td>Full lab gate, run 2026-09-12-09-46-06</td><td>Passed: 320 samples on 22 pages, 51 confirmed failures, 0 likely false positives, 0 potential missed failures, 135 confirmed passes, 29 skipped, 8 justified reviews, 70 reviews measuring pass, 10 measuring fail, 3 borderline; mean ratio error 0.003, median 0</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/final-restore-gate.log">final-restore-gate.log</a></td></tr><tr><td>Smoke, frames, boundaries, runtime</td><td>All pass</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/final-restore-smoke.log">final-restore-smoke.log</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/final-restore-frames.log">final-restore-frames.log</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/final-restore-boundaries.log">final-restore-boundaries.log</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/final-restore-runtime.log">final-restore-runtime.log</a></td></tr><tr><td>Editor package and static lane, facts, cleanroom</td><td>All pass; cleanroom clean</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/final-restore-vscode.log">final-restore-vscode.log</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/final-restore-facts.log">final-restore-facts.log</a>, <a href="../../reports/engine-audit/2026-09-12/../../cleanroom/2026-09-12-1055.html">cleanroom report</a></td></tr><tr><td>Beta Block</td><td>38 of 38 asserted, 38 caught, 8 of 8 reviews, 44 assertions credited, 0 unmatched</td><td><a href="../../reports/engine-audit/2026-09-12/evidence/final-restore-accuracy.log">final-restore-accuracy.log</a></td></tr></tbody></table></div><p>With the six demotions reversed the homepage copy and the facts guard went back to their committed wording, and the extension's Beta Block count is 45 again (44 on the top document plus the untitled blank frame), measured at 1280, 1440 and 1920 wide.</p><p><strong>Corpus differential, restored source against the shipped engine.</strong> Same harness, three concurrent shards at 10:20 UTC, 138 pages attempted, 131 completed, 16 with a DOM change during the run. Unavailable: the six from before plus the Internet Archive, which returned a 503 "temporarily offline" page. Files: <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-0-final-restore.json">0</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-1-final-restore.json">1</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-2-final-restore.json">2</a>. The four pages with headerless tables were rerun one at a time after the table change: <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-retry-tables-demo-betablock.json">Beta Block</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-retry-tables-alphagov-github-io.json">GDS</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-retry-tables-uw-edu.json">UW</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/fix-differential-retry-tables-tc39-es.json">ECMAScript</a>.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Class</th><th scope="col">Before the follow-up</th><th scope="col">After</th></tr></thead><tbody><tr><td>Assertions demoted to review under the unprovable-claim policy</td><td>14</td><td>4 (scrollable region 2, marquee 1, bypass blocks 1)</td></tr><tr><td>Text or control contrast under review for a filter or blend</td><td>143</td><td>62</td></tr><tr><td>Contrast under review for an image or gradient backdrop</td><td>22</td><td>23</td></tr><tr><td>Field boundary reviews</td><td>9</td><td>9</td></tr><tr><td>Contrast assertions removed by corrected multiply arithmetic</td><td>225</td><td>225</td></tr><tr><td>Contrast reviews removed by measured compositing</td><td>8</td><td>8</td></tr><tr><td>Other verdict changes (targets, blink, names, generic links, Python, ARIA attributes, SVG names, text spacing, the two page-state pages)</td><td>33</td><td>31</td></tr><tr><td>All verdict changes</td><td>454 on 132 pages</td><td>371 on 131 pages</td></tr></tbody></table></div><p>The three shards ran before the table change, so they still carry the five headerless-table demotions; the four single-page reruns afterwards show none, and they are excluded from the "after" column. The rows for meta refresh, viewport, orientation, autocomplete and the clear-table case are gone, which is the intended result: those rules match the shipped engine again. Four contrast reviews on UserWay appeared as removed in this run only; a settled probe with the cookie underlay present shows both builds reviewing all four in four alternating runs, so that row is consent-dialog timing, not code (<a href="../../reports/engine-audit/2026-09-12/evidence/final-userway-probe.json">final-userway-probe.json</a>). Ruby's page-state pair and France 24's sibling move recur as before.</p><h3>Recommendation</h3><ul><li>Done above: the five demoted assertions are restored with the assumption named in each message, and the filter-and-blend abstention is narrowed to the cases that are genuinely unresolvable.</li><li>Surface the <code>webNavigation</code> permission and its store warning before distributing an update.</li><li>Profile the nerdy.dev contrast and name paths before release; the slowdown is measured and concentrated.</li><li>Decide whether an empty <code>about:blank</code> frame should be reported for a missing language; the bookmarklet already skips invisible utility frames, the extension does not.</li><li>Nothing here is committed, versioned or published. That needs a separate decision.</li></ul></section><section id="findings" aria-label="Audit findings"><p>12 September 2026. Baseline: commit <code>c61fe1a</code>, engine 1.40.0, browser extension 1.2.114. This section preserves the initial audit snapshot before repairs. Subsequent implementation and verification are recorded in <a href="../../reports/engine-audit/2026-09-12/FIXES.md">Accuracy and performance repairs</a>. Baseline findings and rule ratings below must not be read as the current status of repaired code.</p><p>At the audited baseline, the engine did not meet the requested standard of only provable violation findings. Original browser fixtures demonstrate false violations, missed violations and incomplete frame audits presented as successful. These are specific defects, not a measured false-positive rate across the web. Existing regression tests pass despite them.</p><h3>Scope and method</h3><p>All 88 registered rules and the parked rule were read in full, alongside the four shared engine helpers. The runner, frame aggregation, bookmarklet, CLI audit invocation, result storage and packaging requirements were examined for accuracy and performance implications. Existing site and distribution checks were considered alongside the fresh engine/editor checks. This is not a line-by-line security review of every generated report, asset or dependency in the repository.</p><p>Suspected defects were tested with original local HTML. Independent full-engine checks supplement focused rule probes. Actual Chromium accessibility-tree output was used for semantic claims; Playwright's synthesized ARIA snapshots are retained as supplementary observations only. Pixels and computed styles were checked for paint defects. No other accessibility engine's implementation was read, and matching another tool's finding count was not the acceptance criterion.</p><p>The W3C WCAG 2.2 Recommendation remains the target. Its 86 current criterion names and levels match the catalog exactly. Introduction versions were independently checked against WCAG 2.0 and 2.1: no differences. The catalog accurately records 39 partially assisted criteria and 47 manual criteria, with none fully automated. WCAG 3 remains draft work, not a replacement target. Sources: <a href="https://www.w3.org/TR/WCAG22/">WCAG 2.2</a>, <a href="https://www.w3.org/WAI/standards-guidelines/wcag/">WCAG overview</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/catalog.json">catalog comparison</a>.</p><h3>Confirmed priorities</h3><h4>P1: Frame audits silently lose findings</h4><p><code>src/extension/content.js:118,149,231</code> imposes a three-second reply timeout and drops missing replies without recording a coverage warning. In a real built-extension test, a visible frame with 16,000 unnamed buttons returned its findings after the top audit had returned <code>ok: true</code> with zero violations and no incomplete findings. A separate test placed a frame inside an open shadow root; no child frame was audited because enumeration uses document.querySelectorAll without collecting shadow roots.</p><p>Use extension-owned frame routing, explicit per-frame completion state and an appropriate audit deadline. A timeout must produce a partial result or error, not an apparently complete clean result. Source and executable evidence: <a href="../../reports/engine-audit/2026-09-12/frame-probes.mjs">frame probes</a>, <a href="../../reports/engine-audit/2026-09-12/frame-timeout.mjs">late reply probe</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/frame-timeout.json">observations</a>.</p><h4>P1: Cross-origin audit snippets are exposed to page scripts</h4><p>The frame protocol in src/extension/content.js:130,147-160 sends results with window.postMessage. Page scripts can observe these messages. A local proof used a parent on 127.0.0.1 and a child on localhost: the parent could not access contentDocument, but could read the dummy child input value LOCAL-DEMO-ONLY from the audit result. No external data was used. Reply matching also lacks event.source validation; adding that alone would not make window messages private from scripts in the destination page.</p><p>Move audit results through extension-owned runtime messaging with frame IDs and verified request ownership. Keep snippets out of page-visible messages. <a href="../../reports/engine-audit/2026-09-12/frame-privacy.mjs">Local proof</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/frame-privacy.json">result</a>.</p><h4>P1: Name and visibility helpers give wrong semantic verdicts</h4><p><code>src/engine/lib/accessible-name.js:67,154,189</code> omits image alternatives inside labels, generated content on descendants and nested references, while admitting hidden descendants of a visible referenced label. A button visibly and programmatically named Save is reported as unnamed; a different truly unnamed button passes. These shared helper defects affect several rules.</p><p><code>src/engine/lib/dom.js:29,39,47</code> omits inert and assigned-slot ancestry from semantic visibility. Inert content is reported as an unnamed active control even when absent from the browser accessibility tree. Conversely, a nameless button styled display:contents is skipped despite remaining exposed as a button. <code>src/engine/index.js:133</code> also omits a passed Element context itself, because querySelectorAll only visits descendants.</p><p>Correct the shared composed-tree and accessible-name model before adding individual rule exceptions. Keep visual rendering separate from accessibility-tree exposure. Evidence: <a href="../../reports/engine-audit/2026-09-12/evidence/confirmed-semantics.json">independent full-engine semantics</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/core-probes.json">core probes</a>. Primary references: <a href="https://www.w3.org/TR/accname-1.2/#computation-steps">Accessible Name Computation</a>, <a href="https://html.spec.whatwg.org/multipage/interaction.html#inert-subtrees">HTML inert subtrees</a>.</p><h4>P1: Contrast can use colours that are not painted</h4><p><code>1.4.3-control-contrast.js:62,88,97</code> reports white text on a black gradient as 1:1 rather than 21:1, composites group opacity incorrectly, and ignores text-fill colour. The main text rule mishandles an own-element filter and includes an unpainted base colour when first-line styling covers all the text.</p><p>The reverse error also exists: <code>lib/contrast.js:1270,1284</code> treats a black image at 49% alpha as wholly transparent. Grey text on the resulting grey background passes, although the rendered colour pair is approximately 1.94:1. Shared paint resolution, conservative uncertainty handling and adversarial pixel fixtures are required. Evidence: <a href="../../reports/engine-audit/2026-09-12/evidence/confirmed-more.json">full-engine visual checks</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/gradient-control.png">readable field</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/alpha-image.png">missed low contrast</a>. Source: <a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html">Contrast Minimum</a>.</p><h4>P1: Target geometry and exceptions overstate failures</h4><p><code>2.5.8-target-size.js:431-454,567</code> selects the largest remaining rectangle by area. A 100 by 50 target overlapped at its upper right retains a fully usable 24 by 50 left strip. The engine instead chooses a 100 by 23 bottom strip and asserts a failure. Additional fixtures wrongly flag disabled buttons and small buttons with working equivalent large controls.</p><p>The implementation must test whether a qualifying square exists and honour scope and exceptions. If equivalence or essentiality cannot be established, ask for review rather than asserting their absence. <a href="../../reports/engine-audit/2026-09-12/evidence/confirmed-more.json">Independent evidence</a>, <a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html">W3C size requirement and exceptions</a>.</p><h4>P1: Syntax and heuristics are being promoted into WCAG violations</h4><p>Confirmed examples include a valid fallback ARIA token rejected by an enum, a role typo on a native button whose role remains intact, dormant error references, ineffective orientation declarations, a static blink element, a stopped marquee and a paste handler whose preventDefault call is in an unreachable branch. The paste event was dispatched and remained uncancelled. Four unique controls on a single page are also mistaken for repeated navigation blocks that require bypassing.</p><p>These branches need evidence of the criterion's actual failure. Authoring errors can be useful review findings, but they are not automatically WCAG failures. The per-rule notes distinguish confirmed examples from broader suspected scope problems. <a href="../../reports/engine-audit/2026-09-12/evidence/confirmed-semantics.json">Semantic evidence</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/interaction-probes.json">interaction evidence</a>, <a href="https://www.w3.org/TR/wai-aria-1.2/#document-handling_author-errors">ARIA error handling</a>, <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-current">ARIA current</a>.</p><h4>P2: Untested content is sometimes recorded as passing</h4><p><code>1.4.12-text-spacing.js:47</code> returns passes above its 20,000-element limit. Its document-level stylesheet also fails to apply to shadow text with explicit spacing styles; the engine passes a fixture that clips after the intended overrides are correctly applied in its shadow root. Timed-out image samples remain cached, preventing a later audit from retrying the same resource. The CLI's ordinary run audits only the top document, whereas the extension and bookmarklet aggregate some frames.</p><p>Represent skipped rules, inaccessible roots, timeouts and untested states explicitly. A rule pass must describe a completed applicable test, not a budget exit. The manual checklist is valuable but does not substitute for recording an attempted rule that never ran. <a href="../../reports/engine-audit/2026-09-12/evidence/confirmed-more.json">Spacing evidence</a>, <a href="https://www.w3.org/WAI/WCAG22/Understanding/text-spacing.html">Text Spacing</a>.</p><h3>Performance measurements</h3><p>These are diagnostic profiles, not a replacement for the site's published benchmark. The engine used six cumulative WCAG 2.2 A/AA tags, a 1280 by 800 viewport and one initial run followed by three further runs. Live pages can change during a run. The large page was repeated after other browser probes finished; no competitor engine was run.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Page</th><th scope="col">DOM elements at sampling</th><th scope="col">Initial run</th><th scope="col">Median of next three</th></tr></thead><tbody><tr><td>Astro</td><td>1,853</td><td>82 ms</td><td>42 ms</td></tr><tr><td>Dementia UK</td><td>1,972</td><td>53 ms</td><td>37 ms</td></tr><tr><td>ECMAScript specification, isolated repeat</td><td>194,101</td><td>14,365 ms</td><td>14,764 ms</td></tr></tbody></table></div><p>On the large page, median per-rule cost was 8,422.5 ms for target-size and 4,612.3 ms for text contrast. Together those median costs are approximately 88% of the median audit duration. Target-size evaluates its set synchronously, so the engine's per-element renderer yields do not protect this long operation. Optimise the geometry algorithm and make set-level evaluation cooperative before chasing tiny rule costs.</p><p>Source review identifies an all-target spacing scan for each candidate despite the separate spatial index used for overlap. Confirm exceptions before expensive geometry, reuse a spatial index for spacing, and preserve correctness in nonrectangular and overlapping cases. These are proposals, not measured speedups.</p><p>A separate selector-serialization stress probe took approximately 2.7, 9.7, 40.3 and 147.4 ms for 500, 1,000, 2,000 and 4,000 sibling controls. Eight times the findings cost approximately 55 times as much. <code>lib/dom.js:69</code> repeatedly walks siblings to construct nth-of-type paths. A per-parent sibling index can reduce this repeated work, subject to mutation invalidation.</p><p>Additional source-level lifecycle risks: the runner creates MessageChannel ports without closing them, persistent image caches are unbounded, and image load work continues after the sample timeout. Heap growth and end-user speed improvements were not measured here. Avoid cross-audit caches without explicit invalidation: speed must not preserve stale accessibility results.</p><p>Raw data: <a href="../../reports/engine-audit/2026-09-12/evidence/profile.json">three-page profile</a>, <a href="../../reports/engine-audit/2026-09-12/evidence/profile-isolated.json">isolated large-page repeat</a>, <a href="../../reports/engine-audit/2026-09-12/profile.mjs">reproducible profiler</a>.</p><h3>Repository and test infrastructure</h3><p>The CLI advertises Node >=18 in scripts/sync-cli.js:133 while its direct dependency requirements are newer: installed jsdom 30.0.1 requires ^22.22.2, ^24.15.0 or >=26; @babel/parser 8.0.4 requires ^22.18.0 or >=24.11.0. The root also advertises Node >=18 while its installed Puppeteer requires >=22.12.0. This is a declared-support mismatch, not a tested crash on every older Node version. Align support declarations and CI with the dependency graph; test the minimum declared VS Code extension host separately.</p><p>Fresh baseline checks passed: verify:cases (687 cases), verify:engine, verify:frames, verify:smoke, verify:aria-tables, verify:facts and verify:vscode. The change-aware lab gate passed its 11 testbeds and coverage check. It selected the fast path; the live contrast pixel suite was not rerun. No engine implementation changed, so no before/after corpus differential or release cleanroom scan was performed. Existing tests and no-change gates are not evidence that the newly discovered cases are correct.</p><p>Expand regression coverage around compliant alternatives, unreachable code, effective CSS, real accessibility-tree names, shadow boundaries, partially transparent paint and slow frames. Pair each violation fixture with a minimally changed valid fixture. Keep independent expected outcomes linked to the normative criterion and relevant exceptions. Review expectations as well as implementation; otherwise a mistaken rule can acquire a passing test that protects the mistake.</p><h3>Recommended repair order</h3><ul><li>Remove silent partial-success results and secure frame-result transport.</li><li>Correct shared accessible-name, effective-role and semantic visibility handling.</li><li>Unify contrast paint handling and stop treating nonzero alpha as invisible.</li><li>Fix target-size geometry and exception handling, then optimise and yield within its set evaluation.</li><li>Demote unsupported syntax/heuristic assertions to review until stronger evidence exists.</li><li>Expose budget skips and repair shadow-root probe application.</li><li>Fix runtime support declarations, cache lifecycle and selector serialization.</li><li>Run all engine gates plus a same-DOM before/after corpus differential, adjudicating every newly asserted failure and significant disappearance, before publishing an engine update.</li></ul><p>Do not claim 100% accuracy from finite fixtures. The enforceable objective is narrower and useful: every asserted finding has demonstrable evidence, uncertainty remains visible, and unsupported checks cannot silently manufacture a clean audit. Human judgment is still needed for complete pages and processes under <a href="https://www.w3.org/TR/WCAG22/#conformance-reqs">WCAG conformance requirements</a>.</p></section><section id="rules"><h2>Baseline rating of every active rule</h2><p>Ratings are qualitative judgments within each rule's limited scope. They are not an observed false-positive rate. “Solid” means no new defect identified here; “Under-tests” includes insufficient coverage and source-level risks as well as confirmed misses.</p><div class="stats"><span class="stat"><b>28</b> OVER-FLAGS</span><span class="stat"><b>28</b> MINOR-GAPS</span><span class="stat"><b>24</b> UNDER-TESTS</span><span class="stat"><b>6</b> SOLID</span><span class="stat"><b>2</b> WEAK</span></div><details><summary><span>area-alt</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag111, wcag244 · critical</p><p>1.1.1-area-alt.js:9. Unused maps still fail (measured); must find associated rendered image and exposed active area.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html">Primary reference</a></p></details><details><summary><span>canvas-alt</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag111 · moderate</p><p>1.1.1-canvas-alt.js:23-43. Correctly reviews roleless graphics; tiny canvas and fallback text heuristics can miss meaningful content. Raw fallback text can include hidden text.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html">Primary reference</a></p></details><details><summary><span>embed-alt</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag111 · serious</p><p>1.1.1-embed-alt.js:25-38. Document type demotion sensible. Nameless unknown resource fails without knowing resource type or whether actual non-text content is presented. Need resource/error/decorative cases; no measured defect in this branch.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html">Primary reference</a></p></details><details><summary><span>image-alt</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag111 · critical</p><p>1.1.1-image-alt.js:24-25. Focusable role=presentation img passes although browser exposes unnamed image (measured). Generic/filename alternatives appropriately reviewed.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html">Primary reference</a></p></details><details><summary><span>input-image-alt</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag111, wcag412 · critical</p><p>1.1.1-input-image-alt.js:13. Default Submit name is measurable, so message claiming it cannot describe this action overstates evidence when actual action is submission. Requires content/context test before classifying as false positive.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html">Primary reference</a></p></details><details><summary><span>object-alt</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag111 · serious</p><p>1.1.1-object-alt.js:16. Raw textContent permits hidden or script fallback, and no successful resource check. Document demotion follows embed. Needs render-level tests.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html">Primary reference</a></p></details><details><summary><span>svg-img-alt</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag111 · serious</p><p>1.1.1-svg-img-alt.js:31. svg role=img containing only text returns helper name Chart/pass although CDP image itself is unnamed. Child text is present in AX, so accessible graphic quality requires context; name claim is definitely wrong. role=image/fallback strings missed.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html">Primary reference</a></p></details><details><summary><span>audio-transcript</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag121 · serious</p><p>1.2.1-audio-transcript.js:35-44. Review-only is appropriate. Custom unmute controls can make muted/no-controls assumption false; video-only/live distinctions remain manual.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/audio-only-and-video-only-prerecorded.html">Primary reference</a></p></details><details><summary><span>media-captions</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag122 · critical</p><p>1.2.2-media-captions.js:11,54. Caption-track presence passes even failed/empty resource; custom unmute missed. Missing captions correctly review rather than assertion.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/captions-prerecorded.html">Primary reference</a></p></details><details><summary><span>aria-required-children</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag131 · critical</p><p>1.3.1-aria-required-children.js:109. Hidden listitem satisfies required content while visible stray text remains. Exact selectors miss fallback roles. Ownership is intentionally broad; this does not verify real required ownership.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html">Primary reference</a></p></details><details><summary><span>aria-required-parent</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag131 · critical</p><p>1.3.1-aria-required-parent.js:58,74. Measured valid fallback parent list fails. Flat-tree slots handled better than global visibility but parent semantics are independently reimplemented.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html">Primary reference</a></p></details><details><summary><span>definition-list</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag131 · serious</p><p>1.3.1-definition-list.js:18,43. All role-bearing dl excluded, including empty/invalid role retaining native semantics. Wrapper validity is equated with guaranteed relationship loss without browser evidence for each shape. Slotted dl pairs likely fail wrapper check; unprobed.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html">Primary reference</a></p></details><details><summary><span>dlitem-parent</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag131 · serious</p><p>1.3.1-dlitem-parent.js:9,23. Any ancestor DL passes regardless of replaced role or intervening semantics. Explicit unknown role on orphan item skips. Composed ancestor support is a strength.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html">Primary reference</a></p></details><details><summary><span>form-label</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag131, wcag332, wcag412 · critical</p><p>1.3.1-form-label.js:45-61. Measured CSS-generated visible label missed. Own accessible-text implementation drifts from shared helper and browser algorithm.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html">Primary reference</a></p></details><details><summary><span>list-structure</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag131 · serious</p><p>1.3.1-list-structure.js:63-68. Measured focusable role=none LI retains listitem but fails. Uses raw role rather than effective role.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html">Primary reference</a></p></details><details><summary><span>listitem-parent</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag131 · serious</p><p>1.3.1-listitem-parent.js:11,45. Good role-token fallback on container, inconsistent exclusion for role-bearing LI; unknown role on orphan LI silently skips. Wrapper whitelist needs effective-role / display:contents coverage.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html">Primary reference</a></p></details><details><summary><span>p-as-heading</span><span class="rating">SOLID</span></summary><p class="muted">wcag131 · serious</p><p>1.3.1-p-as-heading.js:11-22. Narrow evidence, review-only with explicit human condition. Misses CSS bold/plain text heading styles by design.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html">Primary reference</a></p></details><details><summary><span>table-headers</span><span class="rating">WEAK</span></summary><p class="muted">wcag131 · serious</p><p>1.3.1-table-headers.js:68,79,92. Measured layout-table assertion and plain TD header-reference pass.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html">Primary reference</a></p></details><details><summary><span>reading-order-divergence</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag132 · moderate</p><p>1.3.2-reading-order-divergence.js:32-70. Review-only preserves verdict discipline. Grid placement, vertical writing and nested wrappers outside scope.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/meaningful-sequence.html">Primary reference</a></p></details><details><summary><span>orientation-lock</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag134 · serious</p><p>1.3.4-orientation.js:40,84. Two measured ineffective CSS declarations still fail.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/orientation.html">Primary reference</a></p></details><details><summary><span>autocomplete-valid</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag135 · serious</p><p>1.3.5-autocomplete-valid.js:99-110. Measured non-personal search wrongly included in criterion scope. No missing-purpose branch; grammar only.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/identify-input-purpose.html">Primary reference</a></p></details><details><summary><span>link-in-text-block</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag141 · serious</p><p>Descendant glyph colour differs from anchor colour: false violation. Invisible background cue: false pass. src/engine/rules/wcag/1.4.1-link-in-text-block.js:208,179</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html">Primary reference</a></p></details><details><summary><span>audio-control</span><span class="rating">SOLID</span></summary><p class="muted">wcag142 · critical</p><p>Correct review-only discipline for custom controls/audio-track uncertainty, <=3s exemption and looping. src/engine/rules/wcag/1.4.2-audio-control.js:25. Script-started media and Web Audio remain outside the declared selector.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/audio-control.html">Primary reference</a></p></details><details><summary><span>color-contrast</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag143 · serious</p><p>Measured own-filter and first-line false violations, translucent-image false pass. src/engine/rules/wcag/1.4.3-color-contrast.js:592,962; lib/contrast.js:901,1270</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html">Primary reference</a></p></details><details><summary><span>control-contrast</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag143 · serious</p><p>Three independent measured false violations: background image, group opacity, text fill. src/engine/rules/wcag/1.4.3-control-contrast.js:62,88,97</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html">Primary reference</a></p></details><details><summary><span>meta-viewport</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag144 · critical</p><p>Definitive failure ignores an available G178 text-resize mechanism. src/engine/rules/wcag/1.4.4-meta-viewport.js:19</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/resize-text.html">Primary reference</a></p></details><details><summary><span>color-contrast-enhanced</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag146 · serious</p><p>Factory shares the main rule's paint and pseudo-candidate defects. src/engine/rules/wcag/1.4.6-contrast-enhanced.js:5. AAA derivation source-verified, not separately browser-run. Control text remains excluded from this AAA rule and has no AAA counterpart.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-enhanced.html">Primary reference</a></p></details><details><summary><span>reflow</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag1410 · moderate</p><p>Review-only geometry is appropriately cautious; wording incorrectly guarantees desktop overflow worsens at 320px. src/engine/rules/wcag/1.4.10-reflow.js:23. No mobile viewport or horizontal-writing-mode test.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/reflow.html">Primary reference</a></p></details><details><summary><span>non-text-contrast</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag1411 · serious</p><p>Snug wrapper passes on presence of any painted border without checking its contrast. src/engine/rules/wcag/1.4.11-non-text-contrast.js:117. Field-only scope is disclosed; graphics and state indicators not examined.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html">Primary reference</a></p></details><details><summary><span>text-spacing</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag1412 · serious</p><p>Shadow styles can defeat document-only probe, yet result passes. 20,000-candidate cutoff also returns pass without probing. src/engine/rules/wcag/1.4.12-text-spacing.js:47,63. Review verdict for uncertain ink clipping is sound.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/text-spacing.html">Primary reference</a></p></details><details><summary><span>scrollable-region-focusable</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag211, wcag213 · serious</p><p>Source: src/engine/rules/wcag/2.1.1-scrollable-region-focusable.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html</p><p>Negative tabindex and no tabbable descendants do not prove keyboard inoperability. External keyboard-operable scroll buttons or scripted focus are valid alternatives. Original full-engine reproduction scroll-with-keyboard-buttons fails despite two working native buttons. The other branch appropriately reviews browser-dependent auto-focus. Sources: 2.1.1 evaluate lines91-109; DOM isInert helper inspected.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html">Primary reference</a></p></details><details><summary><span>meta-refresh</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag221 · critical</p><p>Source: src/engine/rules/wcag/2.2.1-meta-refresh.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/timing-adjustable.html</p><p>Delay parser handles malformed refresh syntax and >20h carefully. But all positive delays <=20h are asserted without observing prior user adjustment, a mechanism or a real-time/essential exception. This is a scope risk, not a confirmed working cancel-control reproduction. Do not use the experimental remove-meta button as proof: removing a scheduled meta directive may not cancel the browser navigation. Missing tests for a time limit explicitly adjusted before entering this page.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/timing-adjustable.html">Primary reference</a></p></details><details><summary><span>pause-stop-hide</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag222 · serious</p><p>Source: src/engine/rules/wcag/2.2.2-pause-stop-hide.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html</p><p>Full-engine reproductions confirm static BLINK and stopped MARQUEE with real Pause button still assert at lines120-130. Motion, duration, parallel content, controls and essentiality are not established in this tag branch. CSS animation branch appropriately reviews; finite animations >5s are a disclosed-or-undisclosed missed family, since only Infinity iterations considered.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html">Primary reference</a></p></details><details><summary><span>video-loop-motion</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag222 · serious</p><p>Source: src/engine/rules/wcag/2.2.2-video-loop-motion.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html</p><p>Correctly asks for review rather than asserting a missing arbitrary script control; native controls and actually paused videos are accepted. An aria-controls button proves only association, not pause behavior, so the pass branch can suppress a genuine missing-control review. Tiny video exception is a noise policy, not a WCAG size threshold.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html">Primary reference</a></p></details><details><summary><span>reduced-motion</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag233 · moderate</p><p>Source: src/engine/rules/wcag/2.3.3-reduced-motion.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html</p><p>Review-only avoids false violations. Any prefers-reduced-motion occurrence anywhere silences all motion, even an empty or unrelated media block. Arbitrary 8px/15% filters and first-five-subject sample intentionally miss some motion; report scope as heuristic. CSSOM scans can repeat queries/style reads.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html">Primary reference</a></p></details><details><summary><span>bypass-blocks</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag241 · serious</p><p>Source: src/engine/rules/wcag/2.4.1-bypass-blocks.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html</p><p>Full-engine four-colour-buttons reproduction fails at lines79-85, although a one-page colour picker contains no repeated blocks. Four controls prove neither repetition across pages nor a header to skip. Existing tests pin this assumption. Also any heading/landmark, including hidden or irrelevant ones, is accepted without proving bypass of the repeated block.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html">Primary reference</a></p></details><details><summary><span>document-title</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag242 · serious</p><p>Source: src/engine/rules/wcag/2.4.2-document-title.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/page-titled.html</p><p>Missing title is demonstrable; placeholder meaning is not. Full engine falsely fails title Document title on documentation whose h1 and topic are Document title. Same risk for artworks literally named Untitled 1. Placeholder assertion at lines62-67 should be review under the requested precision policy.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/page-titled.html">Primary reference</a></p></details><details><summary><span>visual-order-divergence</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag243 · moderate</p><p>Source: src/engine/rules/wcag/2.4.3-visual-order-divergence.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html</p><p>Review-only and narrow direct flex/grid siblings are appropriately cautious. Does not account for CSS reading-flow/new focus ordering or positive tabindex, and disabled-fieldset descendants may enter tabbable() because it reads element.disabled rather than :disabled. Vertical writing modes are not modeled. These affect review accuracy, not certain violations.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html">Primary reference</a></p></details><details><summary><span>link-name</span><span class="rating">SOLID</span></summary><p class="muted">wcag244, wcag412 · serious</p><p>Source: src/engine/rules/wcag/2.4.4-link-name.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html</p><p>Within modeled name-presence scope, role routing and unknown-role implicit fallback are sound. Depends on simplified accessibleName helper, whose coverage is audited separately. Missing names tagged both2.4.4 and4.1.2 are stronger under4.1.2; contextual purpose can satisfy2.4.4, so avoid interpreting each tag as independently demonstrated.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html">Primary reference</a></p></details><details><summary><span>link-text-generic</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag244 · moderate</p><p>Source: src/engine/rules/wcag/2.4.4-link-text-generic.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html</p><p>Review verdict correctly admits surrounding context. Missing effective-role filter means role=button anchors are incorrectly reviewed as links. English/French phrase list is bounded heuristic, not semantic proof. Existing2 cases are thin relative to localization and role routing.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html">Primary reference</a></p></details><details><summary><span>focus-visible</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag247 · serious</p><p>Source: src/engine/rules/wcag/2.4.7-focus-visible.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html</p><p>Review-only declaration scan is conservative about certainty. Inactive media rules can both create suspects and falsely restore them; any box-shadow/background declaration is treated as replacement even none/transparent/unchanged. Shadow/adopted stylesheets not covered. Genuine computed-focus browser checks would add confidence without broadening assertions prematurely.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html">Primary reference</a></p></details><details><summary><span>link-text-generic-only</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag249 · moderate</p><p>Source: src/engine/rules/wcag/2.4.9-link-text-generic-only.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-link-only.html</p><p>Full-engine role=button Show more anchor falsely fails link-purpose at createLinkPurposeRule evaluate lines87-96. Also a working Show descriptive link names button satisfies the SC mechanism allowance but Read more is still failed. Phrase meanings can be proper names in any language; current Go/Plus exceptions are not exhaustive. F84 specifically requires absence of a mechanism, unlike this rule.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-link-only.html">Primary reference</a></p></details><details><summary><span>focus-not-obscured</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag2411 · serious</p><p>Source: src/engine/rules/wcag/2.4.11-focus-not-obscured.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum.html</p><p>Review-only is appropriate for a snapshot. Only2 dedicated cases. Document-wide any visible role=dialog/open dialog suppresses even a nonmodal dialog and all unrelated obstruction findings. Alpha parser assumes opaque for modern colour syntax it cannot parse. Tests needed for nonmodal dialogs, opaque descendants of transparent overlays, shadow focusables and inactive popovers.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum.html">Primary reference</a></p></details><details><summary><span>target-size-enhanced</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag255 · serious</p><p>Source: src/engine/rules/wcag/2.5.5-target-size-enhanced.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html</p><p>Shares target geometry and external-equivalence/essentiality gaps with target-size; disabled targets incorrectly considered. Largest-area uncovered slab can fail despite another adequate square. UA-sized native controls and no-spacing AAA distinction are useful. 8 dedicated cases do not exercise shared adversarial geometry independently at44px.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html">Primary reference</a></p></details><details><summary><span>drag-alternative</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag257 · moderate</p><p>Source: src/engine/rules/wcag/2.5.7-drag-alternative.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html</p><p>Review-only scoped to explicit draggable attributes avoids speculative violations. Blanket exclusion of img and a[href] also misses authored drag/drop interactions built from native-draggable images/links, not merely their browser default. Pointer-event-only interactions are disclosed as unobservable.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html">Primary reference</a></p></details><details><summary><span>target-size</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag258 · serious</p><p>Source: src/engine/rules/wcag/2.5.8-target-size.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html</p><p>Confirmed three independent failure families: disabled controls fail; adequate equivalent native buttons ignored because only matching href proves equivalence; largest-area uncovered slab rejects an intact24x50 strip. Unmeasured essential/legal presentation exceptions still asserted. Shape/clip/opacity/label-union approximations also create false passes. Small-target spacing tests every other target with elements.some(crowds), a quadratic workload despite spatial hash for the separate overlap branch.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html">Primary reference</a></p></details><details><summary><span>html-lang</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag311 · serious</p><p>Source: src/engine/rules/wcag/3.1.1-html-lang.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/language-of-page.html</p><p>Missing/empty language is a sound check; regex is only tag shape and accepts unregistered primaries such as zz and malformed extension structure. Does not verify language matches prose, which is appropriately outside reliable static inference. Only2 dedicated cases for BCP47 parsing.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/language-of-page.html">Primary reference</a></p></details><details><summary><span>valid-lang-parts</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag312 · serious</p><p>Source: src/engine/rules/wcag/3.1.2-valid-lang-parts.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/language-of-parts.html</p><p>Zero dedicated cases. Same loose BCP47 regex; lang empty means language unknown, not reset to page language as comment claims. A malformed tag on a part whose text already matches inherited language does not necessarily prove wrong language exposure. Missing changes in unmarked foreign passages are a disclosed conceptual gap.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/language-of-parts.html">Primary reference</a></p></details><details><summary><span>on-input-navigation</span><span class="rating">SOLID</span></summary><p class="muted">wcag322, wcag321 · serious</p><p>Source: src/engine/rules/wcag/3.2.2-on-input-navigation.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/on-input.html</p><p>Review verdict correctly distinguishes handler source from executed navigation and prior warning. Only inline handlers are observable; regex can match comments/strings or conditional code, but uncertainty is appropriately disclosed. Eight focused cases pin read-vs-write and event selection.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/on-input.html">Primary reference</a></p></details><details><summary><span>error-message-linkage</span><span class="rating">SOLID</span></summary><p class="muted">wcag331 · serious</p><p>Source: src/engine/rules/wcag/3.3.1-error-message-linkage.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html</p><p>Uses review rather than violation for missing programmatic linkage, correctly acknowledging visible adjacent error text can satisfy3.3.1. Empty/hidden/dangling targets and invalid-state inertness handled. Unrecognized nonfalse aria-invalid tokens absent aria-errormessage are outside selector; error text relevance still requires human review.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html">Primary reference</a></p></details><details><summary><span>redundant-entry</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag337 · moderate</p><p>Source: src/engine/rules/wcag/3.3.7-redundant-entry.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/redundant-entry.html</p><p>Review-only correct because confirmation, security, stale values and different people may justify repeated fields. Prefilled editable duplicate fields still get review, although re-entry may not be required; disabled fieldsets not modeled with :disabled. Only within-form descendants, not controls associated via form= or cross-page process.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/redundant-entry.html">Primary reference</a></p></details><details><summary><span>auth-field-obstruction</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag338 · serious</p><p>Source: src/engine/rules/wcag/3.3.8-auth-field-obstruction.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/accessible-authentication-minimum.html</p><p>Full engine fails a password input with autocomplete=off and onpaste=if(false) event.preventDefault(), although paste cancellation does not execute. Regex at41 cannot prove behavior; off does not prove password managers blocked; adjacent email+one password does not establish existing-user login; alternatives are ignored. Assertion at57-63 should require real evidence or remain review.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/accessible-authentication-minimum.html">Primary reference</a></p></details><details><summary><span>aria-allowed-attr</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag412 · moderate</p><p>4.1.2-aria-allowed-attr.js:63-74. Review-only discipline sound, but unknown role and unsupported new role return pass; blanket message browser drops property may oversimplify browser behavior.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>aria-attr-valid</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag412 · critical</p><p>4.1.2-aria-attr-valid.js:29,75-85. Unknown current/invalid tokens and undefined current are rejected; state fallback exists. Numeric and relevant token-list validity not covered.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>aria-field-name</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag412 · serious</p><p>4.1.2-aria-field-name.js:14-18,36. Measured native labelled meter rejected. Old combobox exemption checks mere label/ref presence, not resolved name.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>aria-hidden-focus</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag412 · serious</p><p>4.1.2-aria-hidden-focus.js:30-37. Role/query walk does not cross shadow descendants. Opacity-zero elements intentionally pass even though keyboard/AT mismatch remains possible. Script focus guards unmeasured.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>aria-label-misuse</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag412 · moderate</p><p>4.1.2-aria-label-misuse.js:66-84. Review-only justified. Exact role selectors and light-DOM content ancestor can add noise on slots.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>aria-valid-refs</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag412 · moderate</p><p>4.1.2-aria-valid-refs.js:129-132. Measured dormant error reference assertion. Broader broken-ref policy needs distinction from syntax errors.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>button-name</span><span class="rating">WEAK</span></summary><p class="muted">wcag412 · critical</p><p>4.1.2-button-name.js:11,22. Shared name computation gives three confirmed false asserts, one false pass; exact fallback selectors miss controls.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>composite-widget-name</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag412 · serious</p><p>4.1.2-composite-widget-name.js:22. Correct role ownership, but exact selectors and shared name defects apply.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>dialog-name</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag412 · serious</p><p>4.1.2-dialog-name.js:18-20. Exact roles miss fallback; native dialog with semantic override still judged as dialog. Shared labelledby hidden logic applies.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>frame-title</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">wcag412 · serious</p><p>4.1.2-frame-title.js:11. Direct title/ARIA path sound; global inert and shared labelledby problems apply. No frame-resource distinction in rule.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>invoker-target</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag412 · serious</p><p>4.1.2-invoker-target.js:31,66. Measured static paragraph blamed as button. Event interception, disabled controls and reflection-target cases need coverage.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>label-for-valid</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag412 · serious</p><p>4.1.2-label-for-valid.js:44-46. Measured independently named child falsely asserted as missing relationship/name. Broken for alone does not prove 4.1.2 failure.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>nested-interactive</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag412 · serious</p><p>4.1.2-nested-interactive.js:65. Both native and ARIA outer buttons containing link fail while both names/roles remain in Chromium AX. This does NOT establish cross-AT conformformance; blanket critical claim needs stronger evidence and keyboard/AT study, likely review outside demonstrable lost-role cases.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>role-required-aria</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag412 · serious</p><p>4.1.2-role-required-aria.js:76-87. Missing heading level fails despite measured AX level2; whether intended level is wrong is unknown. Negative tabindex separator treated non-focusable; exact role strings miss fallbacks. Distinguish strict ARIA requirement from actual 4.1.2 harm.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>summary-name</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">wcag412 · serious</p><p>4.1.2-summary-name.js:22-25. Correctly isolates first summary; shared descendant-generated/name faults still apply.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>valid-role</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">wcag412 · critical</p><p>4.1.2-valid-role.js:119. Native named button typo wrongly asserted. Prefix acceptance admits arbitrary doc-* and graphics-* roles as valid.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html">Primary reference</a></p></details><details><summary><span>accesskeys</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">Best practice · serious</p><p>Source: src/engine/rules/best-practice/accesskeys.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/character-key-shortcuts.html</p><p>Zero dedicated cases. Treats whole accesskey string as one key, though HTML defines an ordered set of space-separated candidate keys. Fails duplicate empty values that assign no key; misses overlaps among different lists. visibleOnly false also compares nonusable controls. Advice only, no WCAG tags.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/character-key-shortcuts.html">Primary reference</a></p></details><details><summary><span>button-type</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">Best practice · minor</p><p>Source: src/engine/rules/best-practice/button-type.js:1. Primary reference: https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type</p><p>Correctly accepts sole implicit submit button. Multiple legitimate submit buttons still get style advice; not a WCAG violation. Current HTML Auto state and command/popover invocation exceptions require review because unconditional submitting prose may be wrong. External form-associated controls and named-property clobbering need tests.</p><p><a href="https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type">Primary reference</a></p></details><details><summary><span>empty-heading</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">Best practice · minor</p><p>Source: src/engine/rules/best-practice/empty-heading.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/</p><p>Zero dedicated cases. Checks raw role string, unlike effectiveRole helper: unknown or fallback roles can silently skip real headings. Name helper dependencies apply; advisory heading-name criterion is not taggedWCAG.</p><p><a href="https://www.w3.org/WAI/tutorials/page-structure/headings/">Primary reference</a></p></details><details><summary><span>fieldset-legend</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">Best practice · moderate</p><p>Source: src/engine/rules/best-practice/fieldset-legend.js:1. Primary reference: https://www.w3.org/WAI/tutorials/forms/grouping/</p><p>Confirmed separate idless forms with single same-name checkboxes are merged into one nonexistent group because key uses element.form.id, not form identity, at18. First control's nearest group alone also need not contain remaining controls. Group identity should include tree/form object and actual memberships.</p><p><a href="https://www.w3.org/WAI/tutorials/forms/grouping/">Primary reference</a></p></details><details><summary><span>heading-order</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">Best practice · moderate</p><p>Source: src/engine/rules/best-practice/heading-order.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/</p><p>Sound as best-practice advice, not WCAG assertion. Effective roles modeled, but aria-level parseInt accepts malformed partial numbers and slot/shadow order can differ from registry query order. Skipped levels are not universally usability failures; message is stronger than evidence.</p><p><a href="https://www.w3.org/WAI/tutorials/page-structure/headings/">Primary reference</a></p></details><details><summary><span>landmark-one-main</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">Best practice · moderate</p><p>Source: src/engine/rules/best-practice/landmark-one-main.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/</p><p>Visible shadow-root counting is useful. Native main elements overridden with another role still counted, and nested document/application scope not separated. These are best-practice findings only.</p><p><a href="https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/">Primary reference</a></p></details><details><summary><span>landmark-top-level</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">Best practice · moderate</p><p>Source: src/engine/rules/best-practice/landmark-top-level.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/</p><p>Handles unnamed forms and contextual aside/header/footer cases. Raw role parsing and parentElement walk miss fallback roles and shadow ancestors; overridden native main/header elements can be mislabeled. APG allows main within nested document/application contexts, so absolute direct-body remedy is too broad.</p><p><a href="https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/">Primary reference</a></p></details><details><summary><span>landmark-unique</span><span class="rating">OVER-FLAGS</span></summary><p class="muted">Best practice · moderate</p><p>Source: src/engine/rules/best-practice/landmark-unique.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/</p><p>Full-engine identical labeled pagination navs produce failures, contrary to APG explicit advice to give identical nav links the same label. Singleton grouping also ignores nested document/application contexts. Distinguish duplicates with same purpose/content from genuinely ambiguous landmarks.</p><p><a href="https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/">Primary reference</a></p></details><details><summary><span>multiple-labels</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">Best practice · minor</p><p>Source: src/engine/rules/best-practice/multiple-labels.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html</p><p>Zero dedicated cases. HTML permits multiple labels; this is only advisory and correctly untaggedWCAG. Claim that screen readers differ needs current empirical evidence. Hidden-label accname behavior is more nuanced than visibleLabels filter.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html">Primary reference</a></p></details><details><summary><span>new-window-link</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">Best practice · minor</p><p>Source: src/engine/rules/best-practice/new-window-link.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Techniques/general/G201</p><p>English-only warning regex and target=blank may misstate behavior; named blank context can reuse an existing window, and English wording outside the regex can warn adequately. Description targets included and nonEnglish cases review. Warnings are not a universal WCAG requirement; tags correctly best-practice.</p><p><a href="https://www.w3.org/WAI/WCAG22/Techniques/general/G201">Primary reference</a></p></details><details><summary><span>no-autofocus</span><span class="rating">SOLID</span></summary><p class="muted">Best practice · minor</p><p>Source: src/engine/rules/best-practice/no-autofocus.js:1. Primary reference: https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute</p><p>Review-only and native dialog/popover exemption avoid a false violation. Message says HTML allows only when control is main purpose, which overstates nonnormative caution; soften advisory wording. ARIA dialog presence alone does not create native autofocus scoping.</p><p><a href="https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute">Primary reference</a></p></details><details><summary><span>page-heading-one</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">Best practice · moderate</p><p>Source: src/engine/rules/best-practice/page-heading-one.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/</p><p>Includes open shadow roots and aria-level. However no visibility/effective-role filter means hidden or demoted h1 satisfies the check even with no exposed level1 heading. Only presence is checked though help says start outline.</p><p><a href="https://www.w3.org/WAI/tutorials/page-structure/headings/">Primary reference</a></p></details><details><summary><span>positive-tabindex</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">Best practice · serious</p><p>Source: src/engine/rules/best-practice/positive-tabindex.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html</p><p>Zero dedicated cases. Appropriate as opt-in advice but positive tabindex can preserve meaningful operation; never surface as WCAG failure. parseInt accepts malformed strings, so must follow actual HTML integer parsing if expanded.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html">Primary reference</a></p></details><details><summary><span>redundant-alt-phrase</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">Best practice · minor</p><p>Source: src/engine/rules/best-practice/redundant-alt-phrase.js:1. Primary reference: https://www.w3.org/WAI/tutorials/images/tips/</p><p>Narrowed prefix matching avoids some damaging fixes. An icon's medium or an artwork's title may still legitimately begin Icon/Image of. Best-practice advisory rather than certain bad alt text; avoid automatic replacement.</p><p><a href="https://www.w3.org/WAI/tutorials/images/tips/">Primary reference</a></p></details><details><summary><span>redundant-aria</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">Best practice · minor</p><p>Source: src/engine/rules/best-practice/redundant-aria.js:1. Primary reference: https://www.w3.org/TR/using-aria/#rule2</p><p>Zero dedicated cases. Presence-only comparison treats aria-required=false plus required as duplicate, not conflicting; dynamic native checked attribute vs checked property can diverge. Advice to remove may be fine but claim of equivalence is unproven. State-specific tests needed.</p><p><a href="https://www.w3.org/TR/using-aria/#rule2">Primary reference</a></p></details><details><summary><span>redundant-aria-label</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">Best practice · minor</p><p>Source: src/engine/rules/best-practice/redundant-aria-label.js:1. Primary reference: https://www.w3.org/TR/using-aria/#rule2</p><p>Conservative gates for name-from-content, hidden children, image alts and pseudo text are useful. Raw DOM text vs actual name still differs for nested accessible names and slots. Walk makes2 pseudo-style reads per descendant plus computed style, costly on large matched containers.</p><p><a href="https://www.w3.org/TR/using-aria/#rule2">Primary reference</a></p></details><details><summary><span>redundant-image-alt</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">Best practice · minor</p><p>Source: src/engine/rules/best-practice/redundant-image-alt.js:1. Primary reference: https://www.w3.org/WAI/tutorials/images/functional/</p><p>Correctly keeps sole naming alternative and handles title fallback. Does not inspect overriding aria-label/labelledby on image or parent link; says duplicate is announced when an explicit control name suppresses descendant text. ExposedText duplicates part of accname algorithm and can drift.</p><p><a href="https://www.w3.org/WAI/tutorials/images/functional/">Primary reference</a></p></details><details><summary><span>redundant-role</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">Best practice · minor</p><p>Source: src/engine/rules/best-practice/redundant-role.js:1. Primary reference: https://www.w3.org/TR/using-aria/#firstrule</p><p>Useful explicit exceptions for markerless lists, SVG images and CSS table semantics. Raw first role token and model-dependent native roles leave gaps; browser-dependent semantics claims should stay validated across engines. Advice only, notWCAG.</p><p><a href="https://www.w3.org/TR/using-aria/#firstrule">Primary reference</a></p></details><details><summary><span>redundant-tabindex</span><span class="rating">UNDER-TESTS</span></summary><p class="muted">Best practice · minor</p><p>Source: src/engine/rules/best-practice/redundant-tabindex.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html</p><p>Zero dedicated cases. Summary element is not natively focusable unless first summary in details; standalone summary tabindex=0 creates focus and must not be removed as redundant. Disabled native controls and user-agent focus preferences complicate blanket prose.</p><p><a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html">Primary reference</a></p></details><details><summary><span>region</span><span class="rating">MINOR-GAPS</span></summary><p class="muted">Best practice · moderate</p><p>Source: src/engine/rules/best-practice/region.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/</p><p>Deduplicating one per branch reduces output cost. Landmark selector disagrees with effective-role model, hidden/unlabeled/overridden landmarks and shadow roots; root doc.querySelector can miss shadow-only landmarks. Absolute claim regions-only users never reach stray content is overstated.13cases but cross-root variants still needed.</p><p><a href="https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/">Primary reference</a></p></details><h3>Parked rule</h3><details><summary>label-in-name · not registered</summary><p>Source: src/engine/rules/wcag/2.5.3-label-in-name.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/label-in-name.html</p><p>PARKED by explicit historical product decision, confirmed rules/index.js:20-27; SC2.5.3 remains on manual checklist. Zero dedicated behavior cases for maintained parked implementation. Digits-only visible labels are excluded by HAS_LETTERS, missing mismatched numbered pagination or calculator controls. Visible content ignores images of text, generated text, shadow-root text at the control itself; punctuation folding and concatenation can alter word boundaries. No live-engine behavior conclusion: hidden-label/dynamic-value runs had no registered label-in-name rule. Direct helper/source judgments only; do not reactivate without a deliberate decision.</p></details></section><section id="manual"><h2>Manual scope and safe opportunities</h2><p>All 86 criteria still need human judgment for complete conformance. These 47 have no active automated rule. The other 39 are partially assisted, not certified. The family notes below describe candidates and why they cannot support universal assertions.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">SC</th><th scope="col">Criterion</th><th scope="col">Level</th><th scope="col">Principle</th></tr></thead><tbody><tr><th scope="row">1.2.3</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/audio-description-or-media-alternative-prerecorded.html">Audio Description or Media Alternative (Prerecorded)</a></td><td>A</td><td>Perceivable</td></tr><tr><th scope="row">1.2.4</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/captions-live.html">Captions (Live)</a></td><td>AA</td><td>Perceivable</td></tr><tr><th scope="row">1.2.5</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/audio-description-prerecorded.html">Audio Description (Prerecorded)</a></td><td>AA</td><td>Perceivable</td></tr><tr><th scope="row">1.2.6</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/sign-language-prerecorded.html">Sign Language (Prerecorded)</a></td><td>AAA</td><td>Perceivable</td></tr><tr><th scope="row">1.2.7</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/extended-audio-description-prerecorded.html">Extended Audio Description (Prerecorded)</a></td><td>AAA</td><td>Perceivable</td></tr><tr><th scope="row">1.2.8</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/media-alternative-prerecorded.html">Media Alternative (Prerecorded)</a></td><td>AAA</td><td>Perceivable</td></tr><tr><th scope="row">1.2.9</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/audio-only-live.html">Audio-only (Live)</a></td><td>AAA</td><td>Perceivable</td></tr><tr><th scope="row">1.3.3</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/sensory-characteristics.html">Sensory Characteristics</a></td><td>A</td><td>Perceivable</td></tr><tr><th scope="row">1.3.6</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/identify-purpose.html">Identify Purpose</a></td><td>AAA</td><td>Perceivable</td></tr><tr><th scope="row">1.4.5</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/images-of-text.html">Images of Text</a></td><td>AA</td><td>Perceivable</td></tr><tr><th scope="row">1.4.7</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/low-or-no-background-audio.html">Low or No Background Audio</a></td><td>AAA</td><td>Perceivable</td></tr><tr><th scope="row">1.4.8</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/visual-presentation.html">Visual Presentation</a></td><td>AAA</td><td>Perceivable</td></tr><tr><th scope="row">1.4.9</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/images-of-text-no-exception.html">Images of Text (No Exception)</a></td><td>AAA</td><td>Perceivable</td></tr><tr><th scope="row">1.4.13</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/content-on-hover-or-focus.html">Content on Hover or Focus</a></td><td>AA</td><td>Perceivable</td></tr><tr><th scope="row">2.1.2</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/no-keyboard-trap.html">No Keyboard Trap</a></td><td>A</td><td>Operable</td></tr><tr><th scope="row">2.1.4</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/character-key-shortcuts.html">Character Key Shortcuts</a></td><td>A</td><td>Operable</td></tr><tr><th scope="row">2.2.3</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/no-timing.html">No Timing</a></td><td>AAA</td><td>Operable</td></tr><tr><th scope="row">2.2.4</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/interruptions.html">Interruptions</a></td><td>AAA</td><td>Operable</td></tr><tr><th scope="row">2.2.5</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/re-authenticating.html">Re-authenticating</a></td><td>AAA</td><td>Operable</td></tr><tr><th scope="row">2.2.6</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/timeouts.html">Timeouts</a></td><td>AAA</td><td>Operable</td></tr><tr><th scope="row">2.3.1</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/three-flashes-or-below-threshold.html">Three Flashes or Below Threshold</a></td><td>A</td><td>Operable</td></tr><tr><th scope="row">2.3.2</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/three-flashes.html">Three Flashes</a></td><td>AAA</td><td>Operable</td></tr><tr><th scope="row">2.4.5</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/multiple-ways.html">Multiple Ways</a></td><td>AA</td><td>Operable</td></tr><tr><th scope="row">2.4.6</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/headings-and-labels.html">Headings and Labels</a></td><td>AA</td><td>Operable</td></tr><tr><th scope="row">2.4.8</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/location.html">Location</a></td><td>AAA</td><td>Operable</td></tr><tr><th scope="row">2.4.10</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/section-headings.html">Section Headings</a></td><td>AAA</td><td>Operable</td></tr><tr><th scope="row">2.4.12</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-enhanced.html">Focus Not Obscured (Enhanced)</a></td><td>AAA</td><td>Operable</td></tr><tr><th scope="row">2.4.13</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-appearance.html">Focus Appearance</a></td><td>AAA</td><td>Operable</td></tr><tr><th scope="row">2.5.1</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/pointer-gestures.html">Pointer Gestures</a></td><td>A</td><td>Operable</td></tr><tr><th scope="row">2.5.2</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/pointer-cancellation.html">Pointer Cancellation</a></td><td>A</td><td>Operable</td></tr><tr><th scope="row">2.5.3</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/label-in-name.html">Label in Name</a></td><td>A</td><td>Operable</td></tr><tr><th scope="row">2.5.4</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/motion-actuation.html">Motion Actuation</a></td><td>A</td><td>Operable</td></tr><tr><th scope="row">2.5.6</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/concurrent-input-mechanisms.html">Concurrent Input Mechanisms</a></td><td>AAA</td><td>Operable</td></tr><tr><th scope="row">3.1.3</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/unusual-words.html">Unusual Words</a></td><td>AAA</td><td>Understandable</td></tr><tr><th scope="row">3.1.4</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/abbreviations.html">Abbreviations</a></td><td>AAA</td><td>Understandable</td></tr><tr><th scope="row">3.1.5</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/reading-level.html">Reading Level</a></td><td>AAA</td><td>Understandable</td></tr><tr><th scope="row">3.1.6</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/pronunciation.html">Pronunciation</a></td><td>AAA</td><td>Understandable</td></tr><tr><th scope="row">3.2.3</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/consistent-navigation.html">Consistent Navigation</a></td><td>AA</td><td>Understandable</td></tr><tr><th scope="row">3.2.4</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/consistent-identification.html">Consistent Identification</a></td><td>AA</td><td>Understandable</td></tr><tr><th scope="row">3.2.5</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/change-on-request.html">Change on Request</a></td><td>AAA</td><td>Understandable</td></tr><tr><th scope="row">3.2.6</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/consistent-help.html">Consistent Help</a></td><td>A</td><td>Understandable</td></tr><tr><th scope="row">3.3.3</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/error-suggestion.html">Error Suggestion</a></td><td>AA</td><td>Understandable</td></tr><tr><th scope="row">3.3.4</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/error-prevention-legal-financial-data.html">Error Prevention (Legal, Financial, Data)</a></td><td>AA</td><td>Understandable</td></tr><tr><th scope="row">3.3.5</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/help.html">Help</a></td><td>AAA</td><td>Understandable</td></tr><tr><th scope="row">3.3.6</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/error-prevention-all.html">Error Prevention (All)</a></td><td>AAA</td><td>Understandable</td></tr><tr><th scope="row">3.3.9</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/accessible-authentication-enhanced.html">Accessible Authentication (Enhanced)</a></td><td>AAA</td><td>Understandable</td></tr><tr><th scope="row">4.1.3</th><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html">Status Messages</a></td><td>AA</td><td>Robust</td></tr></tbody></table></div><h3>Coverage additions after correctness repairs</h3><ul><li>Status messages: controlled interactions can collect announcement candidates, but live-region markup alone cannot prove an announcement.</li><li>Hover/focus content: test dismissal, pointer transfer and persistence in an isolated interaction runner.</li><li>Keyboard traps and shortcuts: bounded keyboard journeys can reveal candidates without claiming every workflow was exercised.</li><li>Input purpose: review missing personal-data autocomplete purposes, rather than failing arbitrary search fields for syntax alone.</li><li>Media alternatives: inspect resource availability and nominate transcript/caption review; do not infer equivalent meaning from track presence.</li></ul></section><section id="evidence"><h2>Detailed evidence and reasoning</h2><p>Focused probes and independent full-engine checks are separate evidence sets. Candidate-only and unmeasured observations remain labelled. Unsuccessful experimental fixtures are not classified as confirmed defects.</p><details><summary>Names, semantics and ARIA: full evidence and scope notes</summary><h2>Semantic engine audit, 2026-09-12</h2><p>Scope: all 37 rule files beginning 1.1, 1.2, 1.3 or 4.1, read in full. Also read accessible-name.js, dom.js, roles.js, the runner, visual-order-divergence.js, and cumulativeOpacity's implementation. No competitor implementation was inspected. PROVENANCE.md and spec-sweep instructions were read. This is a read-only audit, with original temporary browser probes; no shipping source changed.</p><p>Evidence: <code>pour-semantics-probe.mjs</code> builds the actual source rules using esbuild, then invokes their evaluate methods in Chromium, with real selectors and the runner's visibility applicability check. <code>pour-semantics-probes.json</code> contains 30 cases, HTML, actual outcome, applicability, and Chromium CDP Accessibility.getFullAXTree output. These are focused rule probes, not the full runner or full regression suite. The extra Playwright ariaSnapshot field is NOT authoritative: it synthesizes roles and incorrectly shows inert content and layout tables. Use axNodes from CDP. Independent full-engine confirmations are recorded in evidence/confirmed-semantics.json; scope cautions below remain applicable.</p><h3>Highest priority findings</h3><h4>1. Name computation reports named controls as nameless, and misses unnamed controls</h4><p><code>src/engine/lib/accessible-name.js:67</code> uses raw label.textContent; <code>:189</code> recurses descendants without their own generated content or aria-labelledby; <code>:154</code> includes all hidden descendants whenever inside any labelledby traversal. Browser probes confirm:</p><ul><li><code>generated-child-name</code>: <code><style>.label::before{content:"Save"}</style><button id="test"><span class="label"></span></button></code> yields button-name fail, CDP button name Save.</li><li><code>descendant-ref-name</code>: <code><span id="label">Save</span><button id="test"><span aria-labelledby="label"></span></button></code> yields fail, CDP name Save.</li><li><code>label-image-name</code>: <code><label for="test"><img alt="Save" src="icon.svg"></label><button id="test"></button></code> yields fail, CDP name Save.</li><li><code>label-hidden-child-ref</code>: <code><span id="label"><span hidden>Save</span></span><button id="test" aria-labelledby="label"></button></code> yields pass, engine name Save, CDP empty name.</li></ul><p>These affect every rule sharing the helper. Repair the recursive traversal, with a visited/context model and browser-backed cases, before tightening individual name rules. <a href="https://www.w3.org/TR/accname-1.2/#computation-steps">Accessible Name Computation</a>.</p><h4>2. AT visibility fails to exclude inert subtrees</h4><p><code>src/engine/lib/dom.js:47</code> checks aria-hidden and rendering, but never inert. Probe <code>inert-name</code> (<code><div inert><button id="test"></button></div></code>) is applicable and fails button-name despite no button in CDP AX. A global semantic visibility correction would improve several rules at once. Keep visual checks separate: inert content remains visible. The existing isInert helper also needs proper assigned-slot traversal. <a href="https://html.spec.whatwg.org/multipage/interaction.html#inert-subtrees">HTML inert subtrees</a>.</p><h4>3. Role handling is inconsistent across rules</h4><p><code>aria-required-parent</code> lines 58 and 74 uses the first written role token, whereas roles.js has a separate effective-role model. Probe <code>required-parent-fallback</code>: <code><div role="bogus list"><div id="test" role="listitem">First</div></div></code> fails despite AX list/listitem. <code>list-structure:63</code> counts literal role=none as neutralised even when presentation is discarded: <code><ul id="test"><li role="none" tabindex="0">One</li></ul></code> fails despite AX listitem. Both confirmed.</p><p>Exact-match selectors also silently miss legal fallback strings: <code><div role="unknown button"></div></code> has an unnamed button in AX but does not match button-name's selector. Probe reports applies=false, not an engine fail. Many ARIA rules share this selector issue. <code>effectiveRole:240</code> itself returns null for wholly unrecognised explicit roles instead of native fallback. <a href="https://www.w3.org/TR/wai-aria-1.2/#document-handling_author-errors">ARIA role error handling</a>.</p><h4>4. Orientation raw CSS is mistaken for effective restriction</h4><p><code>1.3.4-orientation.js:40-56,84</code> scans declarations without cascade or active ancestor condition evaluation. Two measured false assertions:</p><pre tabindex="0"><code><style>@media (orientation:landscape){body{display:none}body{display:block}}</style>
|
|
4
|
+
<p>Visible in either orientation</p></code></pre><pre tabindex="0"><code><style>@supports (display:completely-invalid){@media (orientation:landscape){body{display:none}}}</style>
|
|
5
|
+
<p>Always visible</p></code></pre><p>Both fail; both render and are exposed in AX. Essential-orientation exceptions also cannot be disproven from this selector alone. Use review unless effective restrictions in both orientations are demonstrated. <a href="https://www.w3.org/WAI/WCAG22/Understanding/orientation.html">SC 1.3.4</a>.</p><h4>5. Attribute validation and actual accessibility failure are conflated</h4><ul><li><code>valid-role:119</code>: <code><button id="test" role="buton">Save</button></code> fails; CDP still exposes named, focusable button Save. The native role survives. This is an authoring defect, not established 4.1.2 failure.</li><li><code>aria-attr-valid:75-85</code>: arbitrary <code>aria-current="chapter"</code> and <code>aria-invalid="custom"</code> fail. The ARIA token fallbacks preserve truth states; CDP confirms invalid=true for the latter. <code>aria-current="undefined"</code> is also explicitly false under the spec, but rejected by this enum.</li><li><code>aria-valid-refs:129-132</code>: <code><input aria-label="Email" aria-invalid="false" aria-errormessage="error"></code> fails despite a dormant error reference. AX has named, valid textbox. Missing controls/details/owns similarly need harm-sensitive handling.</li></ul><p><a href="https://www.w3.org/TR/wai-aria-1.2/#aria-current">ARIA current</a>, <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-invalid">ARIA invalid</a>, <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-errormessage">ARIA errormessage</a>.</p><h4>6. Table heuristic asserts on navigation layout, while weak reference checks miss actual defects</h4><p><code>table-headers:92</code> fails a 2x2 table of four independent navigation links; CDP classifies it LayoutTable. No label/value relationship exists. The suggested first-column row-header fix would change the meaning. HTML and source in probe <code>layout-table</code>.</p><p><code>table-headers:68</code> explicitly permits TD reference targets and <code>:79</code> treats mere scope/headers presence as evidence of headers. <code>table-nonheader-reference</code> passes a headers attribute targeting a plain TD. CDP exposes only cells, no headers. HTML header-assignment behavior needs further validation before claiming the exact user-facing loss, but the source contradicts its own message requiring header cells. <a href="https://www.w3.org/WAI/WCAG22/Techniques/failures/F91">F91</a>, <a href="https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html">Info and Relationships</a>.</p><h4>7. Several additional direct false assertions</h4><ul><li><code>aria-field-name:14-18,36</code>: native <code><meter role="meter"></code> with ordinary associated label fails, although CDP says meter Progress. This branch only accepts ARIA/title and ignores native labels.</li><li><code>form-label:45-61</code>: CSS-generated label text is missed. <code><style>label::before{content:"Email"}</style><label for="test"></label><input id="test"></code> fails, CDP textbox Email.</li><li><code>area-alt:9-14</code>: unused map areas are checked despite no associated image or AX presence. Probe <code>map-unused</code> fails an unused <code><map><area href=...></map></code>.</li><li><code>invoker-target:31,66</code>: <code><p popovertarget="missing">Static paragraph</p></code> fails as if it were a button. Scope the rule to invoker-capable controls, then distinguish functional faults from 4.1.2 evidence.</li><li><code>label-for-valid:44-46</code>: <code><label for="missing">Email<input aria-label="Email"></label></code> fails despite the independently named textbox and visible matching caption. A broken association does not prove missing name.</li><li><code>autocomplete-valid:99-110</code>: Search products with autocomplete=disabled fails 1.3.5, although product searches do not collect a listed purpose about the user. Invalid autocomplete is not enough to establish applicability.</li></ul><p><a href="https://www.w3.org/TR/html-aam-1.0/#accessible-name-and-description-computation">HTML naming</a>, <a href="https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element">HTML invokers</a>, <a href="https://www.w3.org/WAI/WCAG22/Understanding/identify-input-purpose.html">SC 1.3.5</a>, <a href="https://www.w3.org/WAI/WCAG22/Techniques/failures/F107">F107</a>.</p><h3>Per-rule inventory and rating</h3><p>Ratings describe the implemented rule and shared dependencies, not complete criterion coverage. SOLID does not mean proof of perfect accuracy. MINOR-GAPS includes narrow disclosed coverage; OVER-FLAGS requires direct or shared demonstrated false-assertion risk; UNDER-TESTS signals a demonstrated/sourced miss; WEAK combines over- and under-reporting in important paths.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Rule</th><th scope="col">Rating</th><th scope="col">Source and assessment</th></tr></thead><tbody><tr><td>area-alt</td><td>OVER-FLAGS</td><td>1.1.1-area-alt.js:9. Unused maps still fail (measured); must find associated rendered image and exposed active area.</td></tr><tr><td>canvas-alt</td><td>MINOR-GAPS</td><td>1.1.1-canvas-alt.js:23-43. Correctly reviews roleless graphics; tiny canvas and fallback text heuristics can miss meaningful content. Raw fallback text can include hidden text.</td></tr><tr><td>embed-alt</td><td>UNDER-TESTS</td><td>1.1.1-embed-alt.js:25-38. Document type demotion sensible. Nameless unknown resource fails without knowing resource type or whether actual non-text content is presented. Need resource/error/decorative cases; no measured defect in this branch.</td></tr><tr><td>image-alt</td><td>UNDER-TESTS</td><td>1.1.1-image-alt.js:24-25. Focusable role=presentation img passes although browser exposes unnamed image (measured). Generic/filename alternatives appropriately reviewed.</td></tr><tr><td>input-image-alt</td><td>UNDER-TESTS</td><td>1.1.1-input-image-alt.js:13. Default Submit name is measurable, so message claiming it cannot describe this action overstates evidence when actual action is submission. Requires content/context test before classifying as false positive.</td></tr><tr><td>object-alt</td><td>UNDER-TESTS</td><td>1.1.1-object-alt.js:16. Raw textContent permits hidden or script fallback, and no successful resource check. Document demotion follows embed. Needs render-level tests.</td></tr><tr><td>svg-img-alt</td><td>UNDER-TESTS</td><td>1.1.1-svg-img-alt.js:31. svg role=img containing only text returns helper name Chart/pass although CDP image itself is unnamed. Child text is present in AX, so accessible graphic quality requires context; name claim is definitely wrong. role=image/fallback strings missed.</td></tr><tr><td>audio-transcript</td><td>MINOR-GAPS</td><td>1.2.1-audio-transcript.js:35-44. Review-only is appropriate. Custom unmute controls can make muted/no-controls assumption false; video-only/live distinctions remain manual.</td></tr><tr><td>media-captions</td><td>MINOR-GAPS</td><td>1.2.2-media-captions.js:11,54. Caption-track presence passes even failed/empty resource; custom unmute missed. Missing captions correctly review rather than assertion.</td></tr><tr><td>aria-required-children</td><td>UNDER-TESTS</td><td>1.3.1-aria-required-children.js:109. Hidden listitem satisfies required content while visible stray text remains. Exact selectors miss fallback roles. Ownership is intentionally broad; this does not verify real required ownership.</td></tr><tr><td>aria-required-parent</td><td>OVER-FLAGS</td><td>1.3.1-aria-required-parent.js:58,74. Measured valid fallback parent list fails. Flat-tree slots handled better than global visibility but parent semantics are independently reimplemented.</td></tr><tr><td>definition-list</td><td>UNDER-TESTS</td><td>1.3.1-definition-list.js:18,43. All role-bearing dl excluded, including empty/invalid role retaining native semantics. Wrapper validity is equated with guaranteed relationship loss without browser evidence for each shape. Slotted dl pairs likely fail wrapper check; unprobed.</td></tr><tr><td>dlitem-parent</td><td>MINOR-GAPS</td><td>1.3.1-dlitem-parent.js:9,23. Any ancestor DL passes regardless of replaced role or intervening semantics. Explicit unknown role on orphan item skips. Composed ancestor support is a strength.</td></tr><tr><td>form-label</td><td>OVER-FLAGS</td><td>1.3.1-form-label.js:45-61. Measured CSS-generated visible label missed. Own accessible-text implementation drifts from shared helper and browser algorithm.</td></tr><tr><td>list-structure</td><td>OVER-FLAGS</td><td>1.3.1-list-structure.js:63-68. Measured focusable role=none LI retains listitem but fails. Uses raw role rather than effective role.</td></tr><tr><td>listitem-parent</td><td>UNDER-TESTS</td><td>1.3.1-listitem-parent.js:11,45. Good role-token fallback on container, inconsistent exclusion for role-bearing LI; unknown role on orphan LI silently skips. Wrapper whitelist needs effective-role / display:contents coverage.</td></tr><tr><td>p-as-heading</td><td>SOLID</td><td>1.3.1-p-as-heading.js:11-22. Narrow evidence, review-only with explicit human condition. Misses CSS bold/plain text heading styles by design.</td></tr><tr><td>table-headers</td><td>WEAK</td><td>1.3.1-table-headers.js:68,79,92. Measured layout-table assertion and plain TD header-reference pass.</td></tr><tr><td>reading-order-divergence</td><td>MINOR-GAPS</td><td>1.3.2-reading-order-divergence.js:32-70. Review-only preserves verdict discipline. Grid placement, vertical writing and nested wrappers outside scope.</td></tr><tr><td>orientation-lock</td><td>OVER-FLAGS</td><td>1.3.4-orientation.js:40,84. Two measured ineffective CSS declarations still fail.</td></tr><tr><td>autocomplete-valid</td><td>OVER-FLAGS</td><td>1.3.5-autocomplete-valid.js:99-110. Measured non-personal search wrongly included in criterion scope. No missing-purpose branch; grammar only.</td></tr><tr><td>aria-allowed-attr</td><td>MINOR-GAPS</td><td>4.1.2-aria-allowed-attr.js:63-74. Review-only discipline sound, but unknown role and unsupported new role return pass; blanket message browser drops property may oversimplify browser behavior.</td></tr><tr><td>aria-attr-valid</td><td>OVER-FLAGS</td><td>4.1.2-aria-attr-valid.js:29,75-85. Unknown current/invalid tokens and undefined current are rejected; state fallback exists. Numeric and relevant token-list validity not covered.</td></tr><tr><td>aria-field-name</td><td>OVER-FLAGS</td><td>4.1.2-aria-field-name.js:14-18,36. Measured native labelled meter rejected. Old combobox exemption checks mere label/ref presence, not resolved name.</td></tr><tr><td>aria-hidden-focus</td><td>UNDER-TESTS</td><td>4.1.2-aria-hidden-focus.js:30-37. Role/query walk does not cross shadow descendants. Opacity-zero elements intentionally pass even though keyboard/AT mismatch remains possible. Script focus guards unmeasured.</td></tr><tr><td>aria-label-misuse</td><td>MINOR-GAPS</td><td>4.1.2-aria-label-misuse.js:66-84. Review-only justified. Exact role selectors and light-DOM content ancestor can add noise on slots.</td></tr><tr><td>aria-valid-refs</td><td>OVER-FLAGS</td><td>4.1.2-aria-valid-refs.js:129-132. Measured dormant error reference assertion. Broader broken-ref policy needs distinction from syntax errors.</td></tr><tr><td>button-name</td><td>WEAK</td><td>4.1.2-button-name.js:11,22. Shared name computation gives three confirmed false asserts, one false pass; exact fallback selectors miss controls.</td></tr><tr><td>composite-widget-name</td><td>UNDER-TESTS</td><td>4.1.2-composite-widget-name.js:22. Correct role ownership, but exact selectors and shared name defects apply.</td></tr><tr><td>dialog-name</td><td>UNDER-TESTS</td><td>4.1.2-dialog-name.js:18-20. Exact roles miss fallback; native dialog with semantic override still judged as dialog. Shared labelledby hidden logic applies.</td></tr><tr><td>frame-title</td><td>MINOR-GAPS</td><td>4.1.2-frame-title.js:11. Direct title/ARIA path sound; global inert and shared labelledby problems apply. No frame-resource distinction in rule.</td></tr><tr><td>invoker-target</td><td>OVER-FLAGS</td><td>4.1.2-invoker-target.js:31,66. Measured static paragraph blamed as button. Event interception, disabled controls and reflection-target cases need coverage.</td></tr><tr><td>label-for-valid</td><td>OVER-FLAGS</td><td>4.1.2-label-for-valid.js:44-46. Measured independently named child falsely asserted as missing relationship/name. Broken for alone does not prove 4.1.2 failure.</td></tr><tr><td>nested-interactive</td><td>UNDER-TESTS</td><td>4.1.2-nested-interactive.js:65. Both native and ARIA outer buttons containing link fail while both names/roles remain in Chromium AX. This does NOT establish cross-AT conformformance; blanket critical claim needs stronger evidence and keyboard/AT study, likely review outside demonstrable lost-role cases.</td></tr><tr><td>role-required-aria</td><td>UNDER-TESTS</td><td>4.1.2-role-required-aria.js:76-87. Missing heading level fails despite measured AX level2; whether intended level is wrong is unknown. Negative tabindex separator treated non-focusable; exact role strings miss fallbacks. Distinguish strict ARIA requirement from actual 4.1.2 harm.</td></tr><tr><td>summary-name</td><td>UNDER-TESTS</td><td>4.1.2-summary-name.js:22-25. Correctly isolates first summary; shared descendant-generated/name faults still apply.</td></tr><tr><td>valid-role</td><td>OVER-FLAGS</td><td>4.1.2-valid-role.js:119. Native named button typo wrongly asserted. Prefix acceptance admits arbitrary doc-* and graphics-* roles as valid.</td></tr></tbody></table></div><h3>Performance opportunities from source, not yet measured</h3><ul><li><code>aria-valid-refs:86</code> does querySelectorAll for every reference to count duplicate IDs. Build a per-root ID index once per audit to remove repeated full-root selector scans and share it with name resolution.</li><li>Required-children traverses entire descendant trees for each matching composite. Deep nested structures can repeat work. Resolve composed ownership once per root rather than per rule/element, while retaining context and hidden state.</li><li>Name recursion is repeated across rules and overlapping ancestors. Correctness must precede caching. Per-audit context-sensitive caching can help, but avoid caching across yielded DOM mutations without invalidation.</li><li>Reading-order checks read textContent for every flex/grid child's complete subtree; nested flex layouts can repeat deep traversal. Profile on a deep original fixture before optimizing.</li><li>Improve role and visibility reuse. Today each rule implements small inconsistent approximations, creating both repeated work and accuracy drift.</li></ul><h3>Manual criteria in assigned families</h3><p>1.1.1 meaningfulness/equivalence cannot be determined by text presence. 1.2 transcript/caption quality, synchronization, live/prerecorded and audio description require listening/viewing; media metadata can safely identify candidates. 1.3.1 real visual relationships and 1.3.2 meaningful order remain context dependent; rendered geometric probes can only nominate review. 1.3.3 sensory instructions can be searched linguistically but false alarms are high without task context. 1.3.4 can be tested in isolated alternate-orientation snapshots, but essential exceptions need a human. 1.3.5 missing personal-purpose candidates can be reviewed without asserting from vague field names. 1.3.6 purpose beyond input autocomplete needs role/content interpretation. 4.1.3 status announcements require observing controlled interaction and live-region changes; mere live-region presence cannot establish announcement success.</p><p>Recommended order: shared name computation and visibility, effective-role routing, false-assertion exceptions, then coverage expansion. Each behavior change needs original adversarial cases and same-DOM differential results. No claim of zero false positives is supported by the present implementation or this finite audit.</p></details><details><summary>Interaction, language and best practices: full evidence and scope notes</summary><h2>Engine interaction and best-practice audit</h2><p>Date: 2026-09-12. Read-only audit of all 23 principle2/3 source files (22 active, label-in-name parked) and all 20 best-practice rules. Each assigned source was read in full, plus dom.js, roles.js, accessible-name.js and shared target-size and generic-link helpers. Fresh source was bundled with esbuild and exercised through the full engine in Chromium. No competitor implementation was read. No shipping code changed.</p><p>Ratings describe current rule scope, not WCAG certification. SOLID means no new confirmed defect within that scope in this review; it does not mean complete criterion automation. Case counts refer only to scripts/spec-cases/cases.js, not all testbeds. All20 best-practice exports were imported and confirmed to carry only best-practice tags.</p><h3>Confirmed browser evidence</h3><p>Runnable original fixtures: pour-audit-interaction-repro.mjs. Full engine JSON: pour-audit-interaction-repro.json. The experimental meta-refresh-adjustable fixture only establishes engine assertion, not a compliant cancellation mechanism; exclude it from confirmed defects. Hidden-label and dynamic-value candidate cases produced no violations because label-in-name is not registered; those runs establish no rule behavior and are not evidence of correctness. Other fixtures establish the noted engine outcomes.</p><p>Priority: target-size uncovered-square geometry, disabled controls, external equivalent controls; conditional paste false failure; role-overridden and expandable link-purpose; stopped legacy motion; repeated-block assumption; semantic title assertion. Best-practice scope: separate form identity and identical navigation labels.</p><h3>Per-rule review</h3><h4>scrollable-region-focusable | OVER-FLAGS | 8 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.1.1-scrollable-region-focusable.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html</p><p>Negative tabindex and no tabbable descendants do not prove keyboard inoperability. External keyboard-operable scroll buttons or scripted focus are valid alternatives. Original full-engine reproduction scroll-with-keyboard-buttons fails despite two working native buttons. The other branch appropriately reviews browser-dependent auto-focus. Sources: 2.1.1 evaluate lines91-109; DOM isInert helper inspected.</p><h4>meta-refresh | OVER-FLAGS | 12 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.2.1-meta-refresh.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/timing-adjustable.html</p><p>Delay parser handles malformed refresh syntax and >20h carefully. But all positive delays <=20h are asserted without observing prior user adjustment, a mechanism or a real-time/essential exception. This is a scope risk, not a confirmed working cancel-control reproduction. Do not use the experimental remove-meta button as proof: removing a scheduled meta directive may not cancel the browser navigation. Missing tests for a time limit explicitly adjusted before entering this page.</p><h4>pause-stop-hide | OVER-FLAGS | 9 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.2.2-pause-stop-hide.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html</p><p>Full-engine reproductions confirm static BLINK and stopped MARQUEE with real Pause button still assert at lines120-130. Motion, duration, parallel content, controls and essentiality are not established in this tag branch. CSS animation branch appropriately reviews; finite animations >5s are a disclosed-or-undisclosed missed family, since only Infinity iterations considered.</p><h4>video-loop-motion | MINOR-GAPS | 8 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.2.2-video-loop-motion.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html</p><p>Correctly asks for review rather than asserting a missing arbitrary script control; native controls and actually paused videos are accepted. An aria-controls button proves only association, not pause behavior, so the pass branch can suppress a genuine missing-control review. Tiny video exception is a noise policy, not a WCAG size threshold.</p><h4>reduced-motion | MINOR-GAPS | 10 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.3.3-reduced-motion.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html</p><p>Review-only avoids false violations. Any prefers-reduced-motion occurrence anywhere silences all motion, even an empty or unrelated media block. Arbitrary 8px/15% filters and first-five-subject sample intentionally miss some motion; report scope as heuristic. CSSOM scans can repeat queries/style reads.</p><h4>bypass-blocks | OVER-FLAGS | 12 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.4.1-bypass-blocks.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html</p><p>Full-engine four-colour-buttons reproduction fails at lines79-85, although a one-page colour picker contains no repeated blocks. Four controls prove neither repetition across pages nor a header to skip. Existing tests pin this assumption. Also any heading/landmark, including hidden or irrelevant ones, is accepted without proving bypass of the repeated block.</p><h4>focus-not-obscured | UNDER-TESTS | 2 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.4.11-focus-not-obscured.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-not-obscured-minimum.html</p><p>Review-only is appropriate for a snapshot. Only 2 dedicated cases. Document-wide any visible role=dialog/open dialog suppresses even a nonmodal dialog and all unrelated obstruction findings. Alpha parser assumes opaque for modern colour syntax it cannot parse. Tests needed for nonmodal dialogs, opaque descendants of transparent overlays, shadow focusables and inactive popovers.</p><h4>document-title | OVER-FLAGS | 10 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.4.2-document-title.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/page-titled.html</p><p>Missing title is demonstrable; placeholder meaning is not. Full engine falsely fails title Document title on documentation whose h1 and topic are Document title. Same risk for artworks literally named Untitled 1. Placeholder assertion at lines62-67 should be review under the requested precision policy.</p><h4>visual-order-divergence | MINOR-GAPS | 7 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.4.3-visual-order-divergence.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html</p><p>Review-only and narrow direct flex/grid siblings are appropriately cautious. Does not account for CSS reading-flow/new focus ordering or positive tabindex, and disabled-fieldset descendants may enter tabbable() because it reads element.disabled rather than :disabled. Vertical writing modes are not modeled. These affect review accuracy, not certain violations.</p><h4>link-name | SOLID | 9 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.4.4-link-name.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html</p><p>Within modeled name-presence scope, role routing and unknown-role implicit fallback are sound. Depends on simplified accessibleName helper, whose coverage is audited separately. Missing names tagged both2.4.4 and4.1.2 are stronger under4.1.2; contextual purpose can satisfy2.4.4, so avoid interpreting each tag as independently demonstrated.</p><h4>link-text-generic | MINOR-GAPS | 2 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.4.4-link-text-generic.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html</p><p>Review verdict correctly admits surrounding context. Missing effective-role filter means role=button anchors are incorrectly reviewed as links. English/French phrase list is bounded heuristic, not semantic proof. Existing2 cases are thin relative to localization and role routing.</p><h4>focus-visible | MINOR-GAPS | 5 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.4.7-focus-visible.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html</p><p>Review-only declaration scan is conservative about certainty. Inactive media rules can both create suspects and falsely restore them; any box-shadow/background declaration is treated as replacement even none/transparent/unchanged. Shadow/adopted stylesheets not covered. Genuine computed-focus browser checks would add confidence without broadening assertions prematurely.</p><h4>link-text-generic-only | OVER-FLAGS | 18 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.4.9-link-text-generic-only.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-link-only.html</p><p>Full-engine role=button Show more anchor falsely fails link-purpose at createLinkPurposeRule evaluate lines87-96. Also a working Show descriptive link names button satisfies the SC mechanism allowance but Read more is still failed. Phrase meanings can be proper names in any language; current Go/Plus exceptions are not exhaustive. F84 specifically requires absence of a mechanism, unlike this rule.</p><h4>label-in-name | UNDER-TESTS | 0 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.5.3-label-in-name.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/label-in-name.html</p><p>PARKED by explicit historical product decision, confirmed rules/index.js:20-27; SC2.5.3 remains on manual checklist. Zero dedicated behavior cases for maintained parked implementation. Digits-only visible labels are excluded by HAS_LETTERS, missing mismatched numbered pagination or calculator controls. Visible content ignores images of text, generated text, shadow-root text at the control itself; punctuation folding and concatenation can alter word boundaries. No live-engine behavior conclusion: hidden-label/dynamic-value runs had no registered label-in-name rule. Direct helper/source judgments only; do not reactivate without a deliberate decision.</p><h4>target-size-enhanced | OVER-FLAGS | 8 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.5.5-target-size-enhanced.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html</p><p>Shares target geometry and external-equivalence/essentiality gaps with target-size; disabled targets incorrectly considered. Largest-area uncovered slab can fail despite another adequate square. UA-sized native controls and no-spacing AAA distinction are useful. 8 dedicated cases do not exercise shared adversarial geometry independently at44px.</p><h4>drag-alternative | MINOR-GAPS | 2 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.5.7-drag-alternative.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html</p><p>Review-only scoped to explicit draggable attributes avoids speculative violations. Blanket exclusion of img and a[href] also misses authored drag/drop interactions built from native-draggable images/links, not merely their browser default. Pointer-event-only interactions are disclosed as unobservable.</p><h4>target-size | OVER-FLAGS | 36 dedicated cases</h4><p>Source: src/engine/rules/wcag/2.5.8-target-size.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html</p><p>Confirmed three independent failure families: disabled controls fail; adequate equivalent native buttons ignored because only matching href proves equivalence; largest-area uncovered slab rejects an intact24x50 strip. Unmeasured essential/legal presentation exceptions still asserted. Shape/clip/opacity/label-union approximations also create false passes. Small-target spacing tests every other target with elements.some(crowds), a quadratic workload despite spatial hash for the separate overlap branch.</p><h4>html-lang | MINOR-GAPS | 2 dedicated cases</h4><p>Source: src/engine/rules/wcag/3.1.1-html-lang.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/language-of-page.html</p><p>Missing/empty language is a sound check; regex is only tag shape and accepts unregistered primaries such as zz and malformed extension structure. Does not verify language matches prose, which is appropriately outside reliable static inference. Only 2 dedicated cases for BCP47 parsing.</p><h4>valid-lang-parts | UNDER-TESTS | 0 dedicated cases</h4><p>Source: src/engine/rules/wcag/3.1.2-valid-lang-parts.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/language-of-parts.html</p><p>Zero dedicated cases. Same loose BCP47 regex; lang empty means language unknown, not reset to page language as comment claims. A malformed tag on a part whose text already matches inherited language does not necessarily prove wrong language exposure. Missing changes in unmarked foreign passages are a disclosed conceptual gap.</p><h4>on-input-navigation | SOLID | 8 dedicated cases</h4><p>Source: src/engine/rules/wcag/3.2.2-on-input-navigation.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/on-input.html</p><p>Review verdict correctly distinguishes handler source from executed navigation and prior warning. Only inline handlers are observable; regex can match comments/strings or conditional code, but uncertainty is appropriately disclosed. Eight focused cases pin read-vs-write and event selection.</p><h4>error-message-linkage | SOLID | 6 dedicated cases</h4><p>Source: src/engine/rules/wcag/3.3.1-error-message-linkage.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html</p><p>Uses review rather than violation for missing programmatic linkage, correctly acknowledging visible adjacent error text can satisfy3.3.1. Empty/hidden/dangling targets and invalid-state inertness handled. Unrecognized nonfalse aria-invalid tokens absent aria-errormessage are outside selector; error text relevance still requires human review.</p><h4>redundant-entry | MINOR-GAPS | 5 dedicated cases</h4><p>Source: src/engine/rules/wcag/3.3.7-redundant-entry.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/redundant-entry.html</p><p>Review-only correct because confirmation, security, stale values and different people may justify repeated fields. Prefilled editable duplicate fields still get review, although re-entry may not be required; disabled fieldsets not modeled with :disabled. Only within-form descendants, not controls associated via form= or cross-page process.</p><h4>auth-field-obstruction | OVER-FLAGS | 7 dedicated cases</h4><p>Source: src/engine/rules/wcag/3.3.8-auth-field-obstruction.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/accessible-authentication-minimum.html</p><p>Full engine fails a password input with autocomplete=off and onpaste=if(false) event.preventDefault(), although paste cancellation does not execute. Regex at41 cannot prove behavior; off does not prove password managers blocked; adjacent email+one password does not establish existing-user login; alternatives are ignored. Assertion at57-63 should require real evidence or remain review.</p><h4>accesskeys | UNDER-TESTS | 0 dedicated cases</h4><p>Source: src/engine/rules/best-practice/accesskeys.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/character-key-shortcuts.html</p><p>Zero dedicated cases. Treats whole accesskey string as one key, though HTML defines an ordered set of space-separated candidate keys. Fails duplicate empty values that assign no key; misses overlaps among different lists. visibleOnly false also compares nonusable controls. Advice only, no WCAG tags.</p><h4>button-type | MINOR-GAPS | 3 dedicated cases</h4><p>Source: src/engine/rules/best-practice/button-type.js:1. Primary reference: https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type</p><p>Correctly accepts sole implicit submit button. Multiple legitimate submit buttons still get style advice; not a WCAG violation. Current HTML Auto state and command/popover invocation exceptions require review because unconditional submitting prose may be wrong. External form-associated controls and named-property clobbering need tests.</p><h4>empty-heading | UNDER-TESTS | 0 dedicated cases</h4><p>Source: src/engine/rules/best-practice/empty-heading.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/</p><p>Zero dedicated cases. Checks raw role string, unlike effectiveRole helper: unknown or fallback roles can silently skip real headings. Name helper dependencies apply; advisory heading-name criterion is not taggedWCAG.</p><h4>fieldset-legend | OVER-FLAGS | 4 dedicated cases</h4><p>Source: src/engine/rules/best-practice/fieldset-legend.js:1. Primary reference: https://www.w3.org/WAI/tutorials/forms/grouping/</p><p>Confirmed separate idless forms with single same-name checkboxes are merged into one nonexistent group because key uses element.form.id, not form identity, at18. First control's nearest group alone also need not contain remaining controls. Group identity should include tree/form object and actual memberships.</p><h4>heading-order | MINOR-GAPS | 6 dedicated cases</h4><p>Source: src/engine/rules/best-practice/heading-order.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/</p><p>Sound as best-practice advice, not WCAG assertion. Effective roles modeled, but aria-level parseInt accepts malformed partial numbers and slot/shadow order can differ from registry query order. Skipped levels are not universally usability failures; message is stronger than evidence.</p><h4>landmark-one-main | MINOR-GAPS | 4 dedicated cases</h4><p>Source: src/engine/rules/best-practice/landmark-one-main.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/</p><p>Visible shadow-root counting is useful. Native main elements overridden with another role still counted, and nested document/application scope not separated. These are best-practice findings only.</p><h4>landmark-top-level | MINOR-GAPS | 8 dedicated cases</h4><p>Source: src/engine/rules/best-practice/landmark-top-level.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/</p><p>Handles unnamed forms and contextual aside/header/footer cases. Raw role parsing and parentElement walk miss fallback roles and shadow ancestors; overridden native main/header elements can be mislabeled. APG allows main within nested document/application contexts, so absolute direct-body remedy is too broad.</p><h4>landmark-unique | OVER-FLAGS | 6 dedicated cases</h4><p>Source: src/engine/rules/best-practice/landmark-unique.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/</p><p>Full-engine identical labeled pagination navs produce failures, contrary to APG explicit advice to give identical nav links the same label. Singleton grouping also ignores nested document/application contexts. Distinguish duplicates with same purpose/content from genuinely ambiguous landmarks.</p><h4>multiple-labels | UNDER-TESTS | 0 dedicated cases</h4><p>Source: src/engine/rules/best-practice/multiple-labels.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html</p><p>Zero dedicated cases. HTML permits multiple labels; this is only advisory and correctly untaggedWCAG. Claim that screen readers differ needs current empirical evidence. Hidden-label accname behavior is more nuanced than visibleLabels filter.</p><h4>new-window-link | MINOR-GAPS | 3 dedicated cases</h4><p>Source: src/engine/rules/best-practice/new-window-link.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Techniques/general/G201</p><p>English-only warning regex and target=blank may misstate behavior; named blank context can reuse an existing window, and English wording outside the regex can warn adequately. Description targets included and nonEnglish cases review. Warnings are not a universal WCAG requirement; tags correctly best-practice.</p><h4>no-autofocus | SOLID | 3 dedicated cases</h4><p>Source: src/engine/rules/best-practice/no-autofocus.js:1. Primary reference: https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute</p><p>Review-only and native dialog/popover exemption avoid a false violation. Message says HTML allows only when control is main purpose, which overstates nonnormative caution; soften advisory wording. ARIA dialog presence alone does not create native autofocus scoping.</p><h4>page-heading-one | MINOR-GAPS | 4 dedicated cases</h4><p>Source: src/engine/rules/best-practice/page-heading-one.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/</p><p>Includes open shadow roots and aria-level. However no visibility/effective-role filter means hidden or demoted h1 satisfies the check even with no exposed level1 heading. Only presence is checked though help says start outline.</p><h4>positive-tabindex | UNDER-TESTS | 0 dedicated cases</h4><p>Source: src/engine/rules/best-practice/positive-tabindex.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html</p><p>Zero dedicated cases. Appropriate as opt-in advice but positive tabindex can preserve meaningful operation; never surface as WCAG failure. parseInt accepts malformed strings, so must follow actual HTML integer parsing if expanded.</p><h4>redundant-alt-phrase | MINOR-GAPS | 4 dedicated cases</h4><p>Source: src/engine/rules/best-practice/redundant-alt-phrase.js:1. Primary reference: https://www.w3.org/WAI/tutorials/images/tips/</p><p>Narrowed prefix matching avoids some damaging fixes. An icon's medium or an artwork's title may still legitimately begin Icon/Image of. Best-practice advisory rather than certain bad alt text; avoid automatic replacement.</p><h4>redundant-aria-label | MINOR-GAPS | 4 dedicated cases</h4><p>Source: src/engine/rules/best-practice/redundant-aria-label.js:1. Primary reference: https://www.w3.org/TR/using-aria/#rule2</p><p>Conservative gates for name-from-content, hidden children, image alts and pseudo text are useful. Raw DOM text vs actual name still differs for nested accessible names and slots. Walk makes2 pseudo-style reads per descendant plus computed style, costly on large matched containers.</p><h4>redundant-aria | UNDER-TESTS | 0 dedicated cases</h4><p>Source: src/engine/rules/best-practice/redundant-aria.js:1. Primary reference: https://www.w3.org/TR/using-aria/#rule2</p><p>Zero dedicated cases. Presence-only comparison treats aria-required=false plus required as duplicate, not conflicting; dynamic native checked attribute vs checked property can diverge. Advice to remove may be fine but claim of equivalence is unproven. State-specific tests needed.</p><h4>redundant-image-alt | MINOR-GAPS | 5 dedicated cases</h4><p>Source: src/engine/rules/best-practice/redundant-image-alt.js:1. Primary reference: https://www.w3.org/WAI/tutorials/images/functional/</p><p>Correctly keeps sole naming alternative and handles title fallback. Does not inspect overriding aria-label/labelledby on image or parent link; says duplicate is announced when an explicit control name suppresses descendant text. ExposedText duplicates part of accname algorithm and can drift.</p><h4>redundant-role | MINOR-GAPS | 6 dedicated cases</h4><p>Source: src/engine/rules/best-practice/redundant-role.js:1. Primary reference: https://www.w3.org/TR/using-aria/#firstrule</p><p>Useful explicit exceptions for markerless lists, SVG images and CSS table semantics. Raw first role token and model-dependent native roles leave gaps; browser-dependent semantics claims should stay validated across engines. Advice only, notWCAG.</p><h4>redundant-tabindex | UNDER-TESTS | 0 dedicated cases</h4><p>Source: src/engine/rules/best-practice/redundant-tabindex.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html</p><p>Zero dedicated cases. Summary element is not natively focusable unless first summary in details; standalone summary tabindex=0 creates focus and must not be removed as redundant. Disabled native controls and user-agent focus preferences complicate blanket prose.</p><h4>region | MINOR-GAPS | 13 dedicated cases</h4><p>Source: src/engine/rules/best-practice/region.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/</p><p>Deduplicating one per branch reduces output cost. Landmark selector disagrees with effective-role model, hidden/unlabeled/overridden landmarks and shadow roots; root doc.querySelector can miss shadow-only landmarks. Absolute claim regions-only users never reach stray content is overstated.13cases but cross-root variants still needed.</p><h3>Additional primary-source checks</h3><ul><li>SC2.5.8 size requirement expressly permits any axis-aligned24x24 square fully inside a target: https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html#size-requirement . The geometry fixture retains24x50 but returns100x23.</li><li>SC2.4.9 allows a mechanism to make names descriptive, not necessarily descriptive names in the initial state: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-link-only.html ; see mechanism explanation and F84.</li><li>APG explicitly recommends same label for identical navigation link sets: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/#navigation .</li><li>SC3.3.8 alternatives and mechanisms: https://www.w3.org/WAI/WCAG22/Understanding/accessible-authentication-minimum.html . A string containing preventDefault is not observed execution.</li><li>SC2.2.2 actual motion, duration and control requirements: https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html .</li><li>HTML accesskey is a space-separated candidate token list: https://html.spec.whatwg.org/multipage/interaction.html#the-accesskey-attribute .</li></ul><h3>Recommendations</h3><ul><li>Put semantic/behavioral uncertainty into review rather than asserting through tag or phrase heuristics.</li><li>Correct measurable geometry and disabled-state mistakes with adversarial cases before performance work.</li><li>Add dedicated language-parts cases, and label-in-name cases if its maintained parked implementation is revisited, plus exception cases above. Existing expected-fail tests can encode incorrect assumptions and must be reviewed against primary text.</li><li>Reuse spatial indexing for spacing as well as overlap. Benchmark many separated undersized links, deeply nested trees and repeated pseudo-name walks. Compare exact outcomes before and after optimization.</li><li>Keep best-practice advice visibly distinct from WCAG violations in consuming UI and counts.</li></ul><h3>Manual criteria: meaningful opportunities and dead ends</h3><p>These32 principle2/3 criteria are currently manual in src/engine/wcag22.js. Manual classification is appropriate for complete determination, but does not mean no useful candidate automation exists. This is a proposed testing roadmap, not authorization to reactivate parked behavior or report speculative violations.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Group and complete criterion list</th><th scope="col">Meaningful bounded automation</th><th scope="col">Why a snapshot cannot certify or safely assert</th></tr></thead><tbody><tr><td>Keyboard and input behavior:2.1.2 No Keyboard Trap,2.1.4 Character Key Shortcuts,2.5.1 Pointer Gestures,2.5.2 Pointer Cancellation,2.5.4 Motion Actuation,2.5.6 Concurrent Input Mechanisms</td><td>Controlled browser journeys can record focus transitions, test escape keys and pointer down/up cancellation, and look for declared aria-keyshortcuts or inline sensor/gesture handlers as review candidates.</td><td>Arbitrary listeners are not enumerable; traps can have documented exit commands; gesture/motion alternatives may live elsewhere. Synthetic events and a short key sequence cannot prove all input paths. Scanning onclick/keydown strings as failures is a dead end.</td></tr><tr><td>Time and process:2.2.3 No Timing,2.2.4 Interruptions,2.2.5 Re-authenticating,2.2.6 Timeouts,3.2.5 Change on Request</td><td>Long-running test sessions with injected form data can observe redirects, unexpected interruptions, timeout warnings and data restoration following session expiry. A meta-refresh directive can be a candidate for review under2.2.3.</td><td>Timeouts depend on server state; restoration and reauthentication are cross-step behavior. Essential/real-time exceptions need context. A timed meta directive cannot by itself settle the exception or a prior configuration.</td></tr><tr><td>Flashing:2.3.1 Three Flashes or Below Threshold,2.3.2 Three Flashes</td><td>Local video/frame capture plus temporal luminance/red-flash analysis can measure an observed interval against primary definitions.</td><td>CSS declarations alone do not establish visual flash frequency, field area, contrast or media content. Unseen intervals and runtime state remain unknown. A keyframe-name or duration heuristic should never assert.</td></tr><tr><td>Navigation/site relationships:2.4.5 Multiple Ways,2.4.8 Location,3.2.3 Consistent Navigation,3.2.4 Consistent Identification,3.2.6 Consistent Help</td><td>A same-site multi-page corpus can identify repeated nav/help blocks, compare relative ordering and collect evidence of search/sitemap/breadcrumbs. Review only until equivalent-function and process context established.</td><td>One loaded page cannot prove a set of pages, repeated functions, repeated help mechanisms or process exceptions. Counting nav links or landmarks is not proof.</td></tr><tr><td>Semantic organization:2.4.6 Headings and Labels,2.4.10 Section Headings</td><td>Presence of headings, empty or repeated generic labels and very long unstructured content can produce targeted review questions with exact examples.</td><td>Meaning, purpose, sections and adequacy require reading. Every empty heading or long paragraph is not automatically a failure of these criteria.</td></tr><tr><td>Visual focus:2.4.12 Focus Not Obscured Enhanced,2.4.13 Focus Appearance</td><td>Controlled keyboard traversal and before/after screenshots can measure partial occlusion, visible focus area and pixel contrast. Start with original rectangular fixtures and known author-controlled focus indicators.</td><td>Focus handlers, scroll state, browser indicators and exceptions prevent static CSS declarations proving pass/fail. Pixel differencing needs isolation from unrelated animations and caret changes.</td></tr><tr><td>Label in Name:2.5.3</td><td>There is already a maintained parked implementation. Pure text visible labels with accurate accessible-name and rendering measurements are feasible. Add adversarial cases and disclose images-of-text/OCR limits before any proposed return.</td><td>Parked intentionally by prior product decision. Full visible label detection, symbolic labels, OCR, slot rendering and speech-input matching need care; this is a product decision as well as an accuracy question.</td></tr><tr><td>Language semantics:3.1.3 Unusual Words,3.1.4 Abbreviations,3.1.5 Reading Level,3.1.6 Pronunciation</td><td>Missing title on abbr and glossary/definition associations can be candidates; language-specific reading metrics can assist an optional reviewer.</td><td>Unusualness, intended audience, required lower-secondary alternatives, definitions earlier in a document and pronunciation-dependent meaning cannot be reliably inferred by simple regex or readability scores. Never turn a complexity score into a WCAG failure.</td></tr><tr><td>Form/error processes:3.3.3 Error Suggestion,3.3.4 Error Prevention Legal Financial Data,3.3.5 Help,3.3.6 Error Prevention All</td><td>Instrument known test forms, deliberately submit invalid input, record correction guidance, confirmation/reversal/edit routes and context-sensitive help.</td><td>Need actual error state, nature of transaction and server behavior. Text in an aria-describedby region is not proof of a suggestion; a missing nearby help button is not proof help is unavailable.</td></tr><tr><td>Authentication enhanced:3.3.9</td><td>Extend controlled authentication review to identify object-recognition or personal-content challenges that are excepted atAA but notAAA, retaining review unless process alternatives are known.</td><td>Available alternatives and mechanisms are process-level facts; password-field attributes and CAPTCHA shapes alone cannot rule them out.</td></tr></tbody></table></div><h3>Precision boundary</h3><p>Confirmed fixture failures demonstrate that these implementation paths can emit false positives. They do not establish their prevalence in live corpus findings or imply that every finding from those rules is wrong. No corpus false-positive percentage was measured by this sub-audit. No production edits, releases or version changes were made.</p></details><details><summary>Visual rendering and contrast: full evidence and scope notes</summary><h2>Visual rule audit: WCAG 1.4.*, 2026-09-12</h2><p>Read-only engine review. Shipping source was not changed. Read PROVENANCE.md, spec-sweep/SKILL.md, all nine 1.4.* rule files, the full 1,705-line contrast helper, and full dom.js. Coverage table reviewed at src/engine/COVERAGE.md:31. All scenarios below were authored during this audit. No other accessibility engine or its source was consulted.</p><h3>Verdict and ratings</h3><p>This family contains substantial original rendering work, including alpha groups, positioned backdrops, pseudo paints, image ranges and shadow-aware background resolution. Its principal correctness weakness is inconsistent paint handling between the main text rule and control rule. Several simple, fully readable fields receive definitive violations. The main rule also mistakes unpainted base text colours for visible text and overlooks filters applied to the element itself. An image alpha threshold silently clears real painted backgrounds.</p><p>Ratings are analyst judgments, not conformance scores. Defect behavior marked <strong>measured</strong> was run in bundled current source in headless Chromium. Independent full-engine confirmations of the main contrast and spacing defects are recorded in evidence/confirmed-more.json. Full-engine runner integration was not exercised here; these are direct rule calls with the real helpers.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">Rule</th><th scope="col">SC</th><th scope="col">Rating</th><th scope="col">Evidence / scope</th></tr></thead><tbody><tr><td>link-in-text-block</td><td>1.4.1</td><td>OVER-FLAGS</td><td>Descendant glyph colour differs from anchor colour: false violation. Invisible background cue: false pass. src/engine/rules/wcag/1.4.1-link-in-text-block.js:208,179</td></tr><tr><td>audio-control</td><td>1.4.2</td><td>SOLID</td><td>Correct review-only discipline for custom controls/audio-track uncertainty, <=3s exemption and looping. src/engine/rules/wcag/1.4.2-audio-control.js:25. Script-started media and Web Audio remain outside the declared selector.</td></tr><tr><td>color-contrast</td><td>1.4.3</td><td>OVER-FLAGS</td><td>Measured own-filter and first-line false violations, translucent-image false pass. src/engine/rules/wcag/1.4.3-color-contrast.js:592,962; lib/contrast.js:901,1270</td></tr><tr><td>control-contrast</td><td>1.4.3</td><td>OVER-FLAGS</td><td>Three independent measured false violations: background image, group opacity, text fill. src/engine/rules/wcag/1.4.3-control-contrast.js:62,88,97</td></tr><tr><td>meta-viewport</td><td>1.4.4</td><td>OVER-FLAGS</td><td>Definitive failure ignores an available G178 text-resize mechanism. src/engine/rules/wcag/1.4.4-meta-viewport.js:19</td></tr><tr><td>color-contrast-enhanced</td><td>1.4.6</td><td>OVER-FLAGS</td><td>Factory shares the main rule's paint and pseudo-candidate defects. src/engine/rules/wcag/1.4.6-contrast-enhanced.js:5. AAA derivation source-verified, not separately browser-run. Control text remains excluded from this AAA rule and has no AAA counterpart.</td></tr><tr><td>reflow</td><td>1.4.10</td><td>MINOR-GAPS</td><td>Review-only geometry is appropriately cautious; wording incorrectly guarantees desktop overflow worsens at 320px. src/engine/rules/wcag/1.4.10-reflow.js:23. No mobile viewport or horizontal-writing-mode test.</td></tr><tr><td>non-text-contrast</td><td>1.4.11</td><td>UNDER-TESTS</td><td>Snug wrapper passes on presence of any painted border without checking its contrast. src/engine/rules/wcag/1.4.11-non-text-contrast.js:117. Field-only scope is disclosed; graphics and state indicators not examined.</td></tr><tr><td>text-spacing</td><td>1.4.12</td><td>UNDER-TESTS</td><td>Shadow styles can defeat document-only probe, yet result passes. 20,000-candidate cutoff also returns pass without probing. src/engine/rules/wcag/1.4.12-text-spacing.js:47,63. Review verdict for uncertain ink clipping is sound.</td></tr></tbody></table></div><p>Tally: OVER-FLAGS 5, SOLID 1, MINOR-GAPS 1, UNDER-TESTS 2, WEAK 0.</p><h3>Measured defects and original reproductions</h3><p>All basic field cases use <code>font:16px Arial; width:400px; height:48px; border:0; padding:10px; box-sizing:border-box</code>. Screenshot pour-visual-repro.png is 900x700. JSON retains exact bounding boxes and styles. pour-visual-repro.mjs reproduces the first three field defects and alpha case. Extended cases live in pour-visual-repro-extra.mjs and pour-visual-repro-final.mjs. Their JSON and PNG files use matching prefixes. Screenshots were visually inspected; pixel reads independently identify the painted backgrounds and glyph interiors. Pixel values establish rendered colours, rather than measuring anti-aliased edge contrast.</p><h4>V1. Opaque field colour hides an actually painted gradient from the control rule</h4><p><strong>Measured false violation.</strong> src/engine/rules/wcag/1.4.3-control-contrast.js:62 selects the opaque background-color without consulting background-image. The background image guard only exists in the translucent branch at :69.</p><pre tabindex="0"><code><input value="A readable field"
|
|
6
|
+
style="font:16px Arial;color:white;background-color:white;background-image:linear-gradient(black,black)"></code></pre><p>Observed verdict: <code>fail</code>, ratio <code>1</code>, required <code>4.5</code>. Computed image is a black-to-black gradient, screenshot background is rgb(0,0,0), glyphs white. Actual pair is 21:1. Measured rect x32 y66 width400 height48. Recommendation: share painted-background resolution or return incomplete for an own image that cannot be sampled, never assume its underlying colour is the painted background. <a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html">SC 1.4.3</a>.</p><h4>V2. Control group opacity composites only the foreground</h4><p><strong>Measured false violation.</strong> src/engine/rules/wcag/1.4.3-control-contrast.js:88. In contrast, main text invokes <code>opacityGroupPaint</code> at 1.4.3-color-contrast.js:620.</p><pre tabindex="0"><code><body style="background:#808080">
|
|
7
|
+
<input value="A readable field" style="font:16px Arial;color:black;background:white;opacity:.5"></code></pre><p>Observed <code>fail</code>, 3.97:1. The actual ideal composited pair is rgb(64,64,64) on rgb(191.5,191.5,191.5), 5.66854:1. Screenshot glyph interiors are rgb(63,63,63), background inside the glyph region rgb(191,191,191), confirming both layers are composited. Recommendation: reuse the existing original opacity-group helper for form control text. Same primary <a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html">contrast criterion</a>.</p><h4>V3. Control text fill is ignored</h4><p><strong>Measured false violation.</strong> src/engine/rules/wcag/1.4.3-control-contrast.js:97 always judges <code>style.color</code>; main text already prefers <code>webkitTextFillColor</code> at 1.4.3-color-contrast.js:537.</p><pre tabindex="0"><code><input value="A readable field" style="font:16px Arial;color:#eee;-webkit-text-fill-color:black;background:white"></code></pre><p>Observed fail 1.16:1. Screenshot contains black glyph interiors on white, 21:1. This is relevant to restyled input values and browser autofill customizations. Resolve the actual glyph fill before calculating the ratio. <a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html">SC 1.4.3</a>.</p><h4>V4. Semi-transparent images are declared wholly invisible</h4><p><strong>Measured false pass.</strong> src/engine/lib/contrast.js:1270 skips alpha bytes below 128. At :1284 a complete absence of pixels *above this threshold* becomes <code>{transparent:true}</code>, and the URL enters the persistent <code>transparentImages</code> set. Yet nonzero alpha paints.</p><pre tabindex="0"><code>const canvas = document.createElement('canvas');
|
|
8
|
+
canvas.width = canvas.height = 64;
|
|
9
|
+
const ctx = canvas.getContext('2d');
|
|
10
|
+
ctx.fillStyle = 'rgba(0,0,0,.49)';
|
|
11
|
+
ctx.fillRect(0,0,64,64);
|
|
12
|
+
p.style.cssText = 'color:#555;background-color:white;font:16px Arial;min-height:64px';
|
|
13
|
+
p.style.backgroundImage = `url("${canvas.toDataURL()}")`;
|
|
14
|
+
p.textContent = 'Grey text on white darkened by a translucent image.';</code></pre><p><code>imageLuminanceRange</code> returns <code>{transparent:true,width:64,height:64}</code>. Main rule returns pass. Screenshot background rgb(130,130,130) with text rgb(85,85,85) yields 1.93996:1. The unsullied white pair would pass at 7.45:1. Only exactly zero alpha can prove no paint; partial alpha needs compositing or uncertainty. Also review mixed images whose below-128 pixels are excluded from a range rather than the whole image. <a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html">Contrast criterion</a>.</p><h4>V5. Own-element CSS filter is absent from the contrast pair</h4><p><strong>Measured false violation.</strong> src/engine/lib/contrast.js:901 immediately returns an element's own opaque background. Later filter checks inspect lower stack layers, and main text has no corresponding own-filter gate.</p><pre tabindex="0"><code><p style="font:16px Arial;color:#777;background:white;filter:invert(1)">
|
|
15
|
+
Visible grey on black after inversion.
|
|
16
|
+
</p></code></pre><p>Observed fail 4.47:1, declared rgb119 on white. Screenshot has glyph interior rgb136 on black, a passing pair (approximately 5.92:1). Recommendation: detect filters affecting the text and return review unless their effects are resolved. Factory sharing carries this into AAA; e.g. #666 on white, inverted, crosses the AAA threshold in the opposite direction (source-derived variant). <a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html">SC 1.4.3</a>.</p><h4>V6. Unpainted base colour is retained when first-line styling covers all text</h4><p><strong>Measured false violation.</strong> src/engine/rules/wcag/1.4.3-color-contrast.js:592 and :962 include the base colour in the worst candidate unconditionally.</p><pre tabindex="0"><code><style>
|
|
17
|
+
p {font:16px Arial;width:450px;color:#aaa;background:white}
|
|
18
|
+
p::first-line {color:black}
|
|
19
|
+
</style>
|
|
20
|
+
<p>A single black first line.</p></code></pre><p>Observed fail 2.32:1 for #aaa on white. Screenshot contains black glyphs on white, 21:1. There is no second line or glyph painted in #aaa. More conservative response: prove that a losing candidate actually paints text, otherwise review. The same issue can occur with a one-character first-letter treatment. <a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html">Contrast criterion</a>.</p><h4>V7. Link colour is read from its wrapper instead of the text-bearing descendant</h4><p><strong>Measured false violation.</strong> src/engine/rules/wcag/1.4.1-link-in-text-block.js:208. The rule collects descendant styles for decoration and weight, but the colour comparison still uses the anchor.</p><pre tabindex="0"><code><p style="color:black;background:white">Read the
|
|
21
|
+
<a href="#" style="color:blue;text-decoration:none"><span style="color:black">account details</span></a>
|
|
22
|
+
before continuing.</p></code></pre><p>Observed fail 2.44:1. Both visible prose and link glyphs are black. The W3C explanation explicitly exempts a link that has no visual differentiation from adjacent text from this particular criterion. Poor discoverability alone is not a 1.4.1 failure. <a href="https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html">Use of Color, scope note</a>.</p><p>Related <strong>measured false pass</strong> at :179: an own background is accepted as a shape even when it exactly matches surrounding white. <code><p style="color:#444;background:white">Read the <a style="color:#555;background:white;text-decoration:none">account details</a> before continuing.</p></code> passes. The white fill paints no distinction; only a sub-3:1 colour difference remains. <a href="https://www.w3.org/WAI/WCAG22/Techniques/general/G183">Current G183</a> supports 3:1 as sufficient without requiring an extra hover treatment. Do not flag the absence of a hover treatment as a defect; the current July 2026 technique has no such requirement.</p><h4>V8. Viewport rule disregards an available text enlargement control</h4><p><strong>Measured verdict and text resize behavior; normative classification follows G178.</strong> src/engine/rules/wcag/1.4.4-meta-viewport.js:19. pour-visual-probes.mjs builds a page with user-scalable=no and two prominent text-size buttons. All button and paragraph text inherit the root font size. Increase adds 4px, capped at 32px from a 16px initial size; decrease reverses it.</p><p>Observed rule fail. Four clicks produce <code>[32px,32px,32px]</code> from <code>[16px,16px,16px]</code>, without horizontal overflow; four decreases restore 16px. A flowing paragraph and both controls remain available. This meets the specific supported enlargement alternative rather than requiring browser zoom. Rule cannot prove the absence of an alternative mechanism from a meta tag; incomplete is the supportable universal classification unless context proves failure. <a href="https://www.w3.org/WAI/WCAG22/Techniques/general/G178">G178 sufficient technique</a>.</p><h4>V9. Wrapper borders bypass non-text contrast measurement</h4><p><strong>Measured false pass relative to the rule's claimed field-boundary review scope.</strong> src/engine/rules/wcag/1.4.11-non-text-contrast.js:117. In pour-visual-repro-final.mjs the snug wrapper has <code>border:1px solid #808080</code> against body #808080; the input has no border or fill, no visible label, no value and no placeholder (only aria-label). The field disappears. The wrapper border is present but contributes 1:1 contrast; result is pass. Presence is not a sufficient contrast measurement. At least preserve the review outcome for an uncontrasted boundary rather than clearing it. Whether the input has other visual identifiers generally remains a human judgment. <a href="https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html">Non-text Contrast</a>.</p><h4>V10. Spacing probe can report pass without applying shadow-root styles</h4><p><strong>Measured false pass.</strong> src/engine/rules/wcag/1.4.12-text-spacing.js:63 appends a stylesheet to the document rather than the target root. In pour-visual-probes.mjs an open shadow root contains:</p><pre tabindex="0"><code><style>p{font:16px/16px Arial;letter-spacing:normal;word-spacing:normal;
|
|
23
|
+
width:180px;height:16px;overflow:hidden;margin:0}</style>
|
|
24
|
+
<p>A short text sample</p></code></pre><p>Engine <code>evaluateAll([p])</code> returns pass. Baseline scroll/client height 16px; applying the same spacing values *inside* the shadow root yields scrollHeight48, clientHeight16 and line-height24. The engine's probe was not applied to the judged glyphs. Some properties can inherit through the host; the explicit resets above make the failed scope boundary unambiguous. <a href="https://www.w3.org/WAI/WCAG22/Understanding/text-spacing.html">Text Spacing</a>.</p><h3>Other source-verified limits and policy distinctions</h3><ul><li>Main bold cutoff is 600 at src/engine/lib/contrast.js:1697. Its comment records an explicit user policy. Do not label this an accidental defect or silently change it to 700. WCAG uses the word bold; equivalence across fonts remains an interpretation.</li><li>Text spacing returns passes above 20,000 candidate elements (:47). This is a performance abstention expressed as success. Better expose not-tested/incomplete at rule level. Existing baseline clipping also clears later increased loss (:84). Containers with overflow:visible can overlap and lose legibility, but are excluded at :53. These are source-confirmed misses; no large stress run was performed here.</li><li>Reflow's <code>at 320px ... will be worse</code> (:23) is an unjustified guarantee. A media query can replace a wide desktop layout with a single-column mobile layout. Current overflow is measured; future overflow is not. The rule should state a review prompt without the prediction. Horizontal writing needs the 256px height branch. No specific reflow reproduction run here.</li><li>Control text does not use its own full text renderer's shadow/outline/filter safeguards. The three measured cases demonstrate the common architectural gap; avoid adding independent approximations that diverge again.</li><li>The current G183 July 2026 revision was checked directly. A 3:1 link distinction alone is sufficient in the current text. An older assumption requiring an extra hover/focus cue would be an incorrect audit finding.</li></ul><h3>Performance findings: suspected costs, not measured bottlenecks</h3><p>No full-site timing or large synthetic performance run was performed here while parent verification was active.</p><ul><li><code>imageRangeCache</code> at lib/contrast.js:1209 and <code>transparentImages</code> at :221 persist across audits; the former keys by URL, overlays and grid and has no bound. Dynamic image URLs and many overlay/grid combinations grow retained entries. Main imageVsText first samples at 32x32 (:154), followed by a possibly larger grid (:714), duplicating image decode/draw work under different keys. Measure before choosing a cache policy.</li><li>The 1,500ms image timeout at lib/contrast.js:1245 resolves null without cancelling image load. A timed-out load can later draw and walk up to a 1024x1024 grid. A failed result also remains cached, preventing later retry even after the image becomes available. Source-confirmed lifecycle behavior, not elapsed-time measurement.</li><li>Pseudo, media, panel and opacity caches are valuable. Nevertheless <code>pseudoBackdropForText</code> scans the ancestor chain per text, <code>backdropHazard</code> scans collected media/panels per candidate, and out-of-flow fallback can style-scan up to 2,000 descendants per text at color-contrast.js:1039. Long deeply nested or many-card pages are natural stress dimensions. Preserve cache correctness across root/document boundaries.</li><li>Text spacing intentionally forces layout twice. Its threshold bounds costs but currently hides the abstention. Prefer transparent budgeting over pass fabrication.</li></ul><h3>Manual 1.4 criteria: useful opportunities and dead ends</h3><p>All five manual entries in COVERAGE.md:35,37,38,39,43 are reasonable manual classifications for complete conformance. None implies that every review candidate must be impossible to collect.</p><div class="table-scroll" tabindex="0"><table><thead><tr><th scope="col">SC</th><th scope="col">Useful optional automation</th><th scope="col">Why it cannot assert full conformance</th></tr></thead><tbody><tr><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/images-of-text.html">1.4.5 Images of Text</a></td><td>OCR local accessible images; identify text-like bitmap candidates for review, compare with visible adjacent text.</td><td>Logos, essential presentations, customizable images and substantial visual context need meaning judgments. Alt text alone is not proof of image-of-text misuse.</td></tr><tr><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/low-or-no-background-audio.html">1.4.7 Low or No Background Audio</a></td><td>Identify prerecorded audio and collect available separate speech/background stems; ask for review.</td><td>Mixed waveform amplitude does not distinguish speech from background or establish exceptions. Reliable separated track evidence is uncommon.</td></tr><tr><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/visual-presentation.html">1.4.8 Visual Presentation</a></td><td>Measure long prose width, justification, line/paragraph spacing and provide targets for reviewing adjustment mechanisms.</td><td>A nonpreferred default does not fail where a suitable user mechanism exists. Avoid reporting every justified paragraph as a violation.</td></tr><tr><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/images-of-text-no-exception.html">1.4.9 Images of Text, No Exception</a></td><td>Same local OCR candidate discovery; separate decorative/essential review questions.</td><td>OCR cannot decide essential or decorative role. A blanket image selector creates mostly noise.</td></tr><tr><td><a href="https://www.w3.org/WAI/WCAG22/Understanding/content-on-hover-or-focus.html">1.4.13 Content on Hover or Focus</a></td><td>In a controlled runner, hover/focus known tooltip triggers, test Escape, pointer transfer and persistence.</td><td>Additional content can be UA-native, non-obscuring or have application-specific interactions. DOM attribute guesses cannot establish the behavior.</td></tr></tbody></table></div><p>Prioritize fixing measured false assertions and reporting skips before extending new heuristic scope. All scratch results remain local for incorporation into the parent report.</p></details><h3>Rendered examples</h3><figure><img alt="White readable field text on a black gradient background" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAAH0CAIAAADqkXSEAAAQAElEQVR4nOzdZcycZdrH4ekuy7JdlkWKBSeElOJWIDjBJRAoFHcL7hYgOAQrGjRAgCABAgR3d9fixV1f3Nr3zNxh8rwPdCi8sP92exwfJqP3XM/0yy/ndc90nBEjRrQAACDhLy0AAAgRowAAxIhRAABixCgAADFiFACAGDEKAECMGAUAIGac1mhjxhlnbDHWGzRo0JFHHtkCAMYOo1GMQpXommuu2QIAxhqjXYwOGzasxdhKiQLA2MY5owAAxIjRmP79+88333zjjTdeCwBgbDX6xugDDzww4v/6/vvvP/roo5122qn1x9l0003ryAcccED3p/1P2y8+9MUXX3z88cet36Jfv351tKFDhz700ENPPPFELWDyySfv8vypppqqnnPjjTf+/KFRXD8AwOhppDF6xBFHTDDBBH369DnnnHM6d958880DBw6sOxdaaKHHHnus9Wf661//Wpc33XTTxW1XXnnlhx9+OPHEEw8ZMmSLLbZo/UHqb6nLv/zl90d5n7bf9JI99tjjX//614svvnjggQc++uijlbM//PBD97cY2SL//+sHAAga6ReY9tprrxrIbbjhhptssslss81WG8p159JLLz1gwIC6/6677hpnnP/El5+q2x5//PHOzSOPPHL33XevGD3jjDNaY6xpppmmLnfYYYfrr7++BQAwFus2Uevbt++CCy5YV1ZaaaX333+/ubM2lKeeeuqeJTp8+PDug70/0AUXXFCXk002WWcxlarfffddraF28NdZZ53OMxdddNHXX3+9dvZrF/vrr7++6qqr/va3vzUPbbvttm+//XbdX3vlq666as/j77zzzrXnPrytHt1///07D9X08d577/2x7amnnpp00kl/vrwu6+m45557Bg0aVFcuv/zymi6fffbZNRmdZJJJmkd32223zz77rNb27bff3nnnneOPP/7Pj9Bl/QAAY5Zf2d5dYYUVDj744CrRwYMHV9i12k027rjjNo9++umn22+/ffXTrLPOWgPUpvyuu+66tdZa6+ijj95nn30qzlZcccWXXnrp3HPPrf39mghedNFFzWtffvnl1VZbre6p1/7i2ZA/V2XWxOh5551XlxWXdZA555yz6u2GG26oje96dI011qiHpp122jvuuKMmuE888cT9999fO/4rr7zyYYcdVg8tueSSJ554Yr9+/Wqdn3/++SqrrNI5fo0qjz322H/84x+33377Cy+8UG9X2+jzzjtv8+g///nPSvNbb731+eefr1Hxc88912t5XdbT03333VefW10ZOnRoXZ9uuunqyM1HesABBxx11FH1vhWsw4YNq55+5plner28y/oBAMY4v36u4d57712T0eqzPffcs9dDQ4YMufbaayvgbrvttnPOOacSqqaG77zzziWXXHLQQQfNMcccNfy7++67F1544Qq4yqyaaO64446t9veBll122WOOOaZisWpsueWWe/XVV3/x3Sslv2yrSWG11yyzzFLX61D1UF1WxtVb15CyorkCse486aST6nLXXXft06fP4YcfPt988y200EIVx3XnYostVpc1iWzOea1Krlp94403Ou/VnIq6yCKLLLXUUv3797/44ovrZlVsz49imWWWGTBgQK1q4okn3nTTTXsutct6eqp2v+WWW+rK1ltvvc0223Tur8qvfK8r9YHUGmoBDzzwQFV1r/Fql/UDAIxxfv28zxor1lyzBn6VnjUmXHfddTsPVRputNFGrfbUsC6rOJdYYolKtM0226yic+211261TzOteeGhhx5a17fbbrt69KuvvjrjjDPGG2+82oZu/bTnXhPHX3z3atAauNawsJkdnn766bWTXkeo6/Vedfnaa6/tvvvuzZO/+eab5mvp9e6VzrVdXguu+WLFbt1ZI8+6nHLKKd97771HHnmkeckJJ5zQpG2ZffbZ+/btWz1da66AbmaiNeDsLOaUU05prtS0+JprrqnDnnXWWZ1Hu6xnVFR91my1hqad+mxOjajovOuuuzpP67J+AIAxzih9CammgFdccUVNGddff/0K0M79lZsVRnvttddbb71VN7/++uvOQ53vd0800USdE0wnnHDCVntz/8EHH6wN+immmKJuDm6beeaZDznkkJ+/9eKLL958gakmrxW+FWo19Wweal6+8cYb93pJ9d+HH35YI9uqw+bL5s0JBqUKuKL2o48+6jz56aef7lyfYYYZamZZl83NXifC1qs6v+5UO+yt9skAPZ/QZT31KbV+zTzzzNNqf0S9/mf2GWecsXO9+/oBAMY4o/qN+BoT1jizNrJXXXXVzjmjtVtdu+2XXnppbV6ff/75v/jC5heaeqlsrUCsvezWKKvIGzhwYKVwjQnnnnvuuqd27avzBg0aVBPZns/84IMPLrjggiWXXLImi+edd96FF174dluFaY0qhw8f3nPY2a9fv871OnJtfFf71gZ9HaHKuwa6naru+ftNU089dV2++eabPd+3y3pao6AJ+hdffHH11VfveX+lZ+cz7L5+AIAxTrdzRn9o69zcfPPNqwh7FthWW221/PLLV49WIbV+i+mnn7725V966aXmZg0y99tvv199VfNGc80115ZbbtlqnxVQl5VuT7e9+uqr99xzz80339z66fTQAQMG7LbbbrWjvd5669XNZkD77rvvVkp2ds87Zx1UdFaJVlBW6R5xxBGvv/56/Wmt9teSmifUeLhp0Fb7fINW+2f5ey6vy3pGRXO0mWaaqT6W5gg1Ca7F9zozdWTrBwAYE3WL0ZrVvfzyyz3vOfnkk+eYY47OzQqjO+6447nnnmtOW6yAe+WVV7799ttWe4bXPKfyrvOzUF9++WXzUA0d68pyyy139dVXX3bZZRVwzbmn3VUHN9+IP+GEE8Yff/ztt99+xIgRtXFfXbvTTjs9+eSTE0wwQQ01K1irAutpl1xySc1xjz/++Gbju2/fvnVZR6gZZ6XeZpttduaZZ3ams/Wqd955p4aO9bfUemqXf9FFF239dGpB46mnntp2221PO+20tdZaq/6WY489tufyuqynNQq++uqrKtdaW33sBx100KmnnrrrrrvWAWv9PZ82svUDAIyJRhqju+yyS8VQ7VZvsskmP/74Y3Nn9Vy1Y+fHL/fdd98KzcUXX7ymhiuttNKQIUPuvPPOemGr/YOg119/fdXSNddcUxG55557PvbYY/WEVvvr5NNNN91xxx1X5brKKqvU8SvjaiLYawFNw/UquRqgVjL+/e9/P//884cNGzZ48ODa8a8d+TpyzTVvueWW5tv6tfI33nij7r/iiivq4FdddVVtdk8zzTS13109XS3773//u9ZWPdeckNq8y8477/zJJ5/U8upvXHrppQ8//PDWT0PWVvtU13r5SSedVHPZjz/+uFK1eVXzX5XWlS7r6aV5fuflneuVzrfddlt9mPVn1tS5Wn+DDTb44osvmuc0l13WDwAwxunTJM5v8sEHH3R+8r0GhFWoNaur43z33XeViaN+nBqR1hRw2mmnbbbCm2/qVNK1fqPatp5iiikefvjhXvdPNNFE/fv3f/DBBzsx3dMCCywwdOjQn/+P85XF44477rPPPvuL71WD4arDyujWb1/PKKpPY+DAgbWAKuMuTxvZ+sdo9Y9Vl/PPP38LABg7/J4Y/ZP87hjlv4YYBYCxza//6D0AAPxJxCgAADFiFACAmFH90fv/jEGDBq255potAADGDqNRjCpRAICxzWj0bXoAAMY2zhkFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGeXO3jgAAA9lJREFUAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCIEaMAAMSIUQAAYsQoAAAxYhQAgBgxCgBAjBgFACBGjAIAECNGAQCI+V8AAAD//1vA7JoAAAAGSURBVAMAdS43E8zpscAAAAAASUVORK5CYII="><figcaption>Baseline engine asserted 1:1. The actual field paints white text on black.</figcaption></figure><figure><img alt="Grey text on a grey background produced by a translucent black image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAAH0CAIAAADqkXSEAAAQAElEQVR4nOzdZ5MbVd7GYRm87Nq7SzTY5JyKz8grPiJFzsFkTM7Bz13613b1ambkMTDc+NnreuGa0Ujdp09PlX5zWpJPX758eQMAAA3XbQAAoESMAgBQI0YBAKgRowAA1IhRAABqxCgAADViFACAGjEKAECNGAUAoEaMAgBQI0YBAKgRowAA1IhRAABqxCgAADViFACAmtNH/eCZZ57ZAHCtefrppzcA1w4rowAA1IhRAABqxCgAADViFACAGjEKAECNGAUAoEaMAgBQI0YBAKgRowAA1IhRAABqxCgAADViFACAGjEKAECNGAUAoEaMAgBQI0YBAKgRowAA1IhRAABqxCgAADViFACAmj8jRn/99dcffvjhl19+2QAAwMrpzYlJg3766adffPHFd999N7dcf/3158+fv+222zYn48cff7x06dKFCxc2v8nly5c/++yzf/zjH2fPnt38Zbzyyis///zzk08+efyHZOafe+65W2655Z577tn8uZ599tmbb7753nvvXd+Yv0Oef/75nPe77rpr87sVj+74Dp0HAOCgk1oZTdi98cYbH3zwQQLx3Llzd9999+23357b39vanIzsMe27+a2++uqrixcv/vTTTxtOQH4lNgAA/+2kVkazJvrtt9/+85//vP/++7MgOjemR7POlx/dcccdp0+f4KIsAADXhJMqws8//zz/5jLlUqKb7WX6O++8MzGa1cfE6EcfffTll18mWD/55JNcHH/wwQc326XTr7/+Ohd2c0uu6uanuSybhD1z5sx9990328lPX3311RtvvDFbWzb+5ptvZhU2X7z44osp4Nz/+++/z0pnmvjUqVPZTq7q/u1vf8uuX3/99Ywku8u/2XjWU3Md/Pz587Nkm4dkVAcvsF66dCkDzsPzqJtuuunChQtzaG+99VaO5brrrssl/gws+82OMviDc5JV2w8//PCHH37I1zfccEP2mCu5B++Wjbz//vsZQ8aWHeXf5Uc5ogzyu+++y43ZaR6eYeToMmPvvvtuVqCzFJ1R7Qw+R5RF3/wlkAvleWBmO6cgh5wt5K+CedVEbn/55Zdzn2wqd84tmd6sZ88x5s4Hz8tsPIeTnWa0uecsfh8lG3nttddyOnLPW2+9NYefke9sPD/KlrP9nKl5VGYsv0s5s7k9Q82E7Gx2fXTZQgaTec4XGX8mOZt99NFHd+Ynvx5nz5499ITmgfllO7c123/nnXdyn4ceemj/FB1/HgCAtRO5TJ8LsgmmpMCSFIs86+d5PcW22b7EM3dLG6WKUiR5Fk9ipiTyqKTAVGPaJZ2XW3J77jMbydd57M4rOxMruWf65t///nc2lThIVeThKYZbbrklAZGSSGpkU7kl+02IbLatk/tkVMtLRTO2pbQWqbdET44rA8sd0jEZ2/wogZhvcxR5+L/+9a9sOXV7cE6SKW+//XYGkA7LADL+VM6E6Y7sKF2braWupsPm9hz++ogymOw099xs+zXTlYTNDORu62nP1jK8bC37nW9zyHMgmfZU4MxDbskW8m3GmdmYCc8G50eHnpfZb77OQ7LxTHtqbHO03C3Hm1DLdj7++OPZeKRQs/E53py43G35Ub7I8GbSMs+ZwPy7M1c7R5dvMwnZVHox8zMzfHB+jjqhMw/LnM/5nZ3umaKrmgcAYO1EVkbnuXyKc7klZiUs8kSeVJ2vUydZlMozfYIgT/Z5Os9qU25PJWSNM8/9jzzySG785ptvUmazXpUCm+hc7zTbyRbyxTw8C5b5N2tgSbfNNlUTf6mT81vJiFmOzb9///vfs0CYTMleUjD5d2f5LUtiGUY66bHHHltWQ1Me2chyzxT2JGyiJEOddcf1RmapOHfL7vJFGitRmHKdbxe5JZtd3vuS7afV5kfZ4xxdSjRfJAfzo+xrOmwmPHN1eWtuyS4yJ5mBbC0zljLLMDLzjz/++GZbVy+99FLus7ylLPd54oknMvLUVSZ/1v/2nJf8KEea22cMOZZsbXO0LFLm1Gf+84dBcjBf5OH5xchhzrL3vDkpB5Wvs9NJ/ExaBpbzm9Gm85YF8p2jy4Rk6vJb8cADD+SnmcNl6g7Oz1En9ODfITuOmqKrmgcAYHEiMToxtH7DSsojYbF8u35j9fRcnuMnQZJKy5uQ8pQ/78SfoEyDpoTyrJ/sSGokH/eMYbaWMazf0jQ35oGJ1Kz25dJtvk277N/UtHU6Y3nJQcacdpkl1Rn8EjEpnuwlpbITo0moueCeI0oUzsri+hL8mONd+jIplgmZu+XGHPU0ZSwHuDx2GczcmBHOSvC02nL42eYyJ8nxWQicvw2ysjvDzpHmlnkv157zMv8uLzbIad0TYdnXLNlmMDmWZGWOIoN56qmnNtv0zAJkNriMfzaeMz6Dn4bOFo46upnS+VWZw8xpPXR+Zrn00BN6xRg9dIquah4AgLUTidFZ7Zs4GHmenhuTkjvP08s15Wm+g5c4Z5UxW8iqXp77J4mWXDvKfKxpLuyub1wuv861+GTWvLJw/6YmONZ3S5Gst7buzvVrZHeOIkuzX3/99Xx7VP5OJ603mPmZG5OkuSg8q7+H7mjnQGYGMvgc5qwiz4A/31rfc4nR9X6XkttzXjKwDGM5lqOOfcykjdnRvNYig8nGD34M7Rz1+iUHM8gZ1cGjm/uv95JTvEz4ZjU/Vzyhexw6RVc1DwDA2onEaBafUntZLloq58zWZvu0vROjy7X7eQp/+OGHd9YU5/bU51RUYnTe5nLFMSQOcln20H19s7XZLrDli/1bW97Es9wyCbJcYV82u0cuBM+CbvJ3OngnlMe017rMltXTLC2nRDONmYpsIaN64YUX9uwxK5FZjs0F8URw1hRz/5nYCxcu7LwOYX887TkvGe36RZz7P7xp/dM5wPxuZB4uXryYTWVUKcJMaQY8hzz7XU/FrJvOb9TBozs4dUf9PwtXPKHroa7vdpSrmgcAYO2kPmd0XoaYS+Hrj+3Mk/S8xu5Q8w70LGXdsJX0eeONN5Zim/xKjaVI5kWTh1rSLWGRFklJzNZyewbz8ccfb7aNMq8oTWBttpm4rpaDJTH1s77cPyuL6xfF7pdtpkSznVxTTgjm0GbF7uC+5k1Uy74y/uW/DJipe/DBBxOjObqDl+l3ZHjzcth5j/nmP7GV7dzwH/nD4LXXXtvfW3vOy4x2OaczpKOsT/0c4MToZvtC2Ntvvz0DnncazUHNfpdHzUcozCsrDj26GcyycpztrNfm1/ac0FndXL9p7DgxelXzAACsndRHO6UX5yOKXnrppax4JSWzJpqgmTY9dCUyOZLFv+RR7pMLr59++mmaIJmyrDtmm/PC06Ou0adW85CPPvoo98xK25tb8/btuRA8D3zvvffy9V133ZWGuPPOOzPI3LJ8ClX2u1m9/m+zvTKbDX722WdZhMsXWQPL1nLjFVdnFzmE7CttlFRKYyWDppkOLt3NEvJ8wkDuuV5FzpxkC9n1jCELipujF//Ws5qRZ3d5VLYwFZtpybfznrCs1E6479nCUeclG0nfZ1rmvU0zpKPkUUnYnIIcfg4kj80x5ncjW14+UWHenD5/UWTGMgPZXU7fvNUsN64/zGvn6LKp3C1f5+H5Yv7wONSeE5oYnT8V8luU/c5HDVzR/nmY91HNXz4AwI4T/OT5c+fOzadvfrW12cbifDTm8kaW/xrK6dMPPfRQeuXSVu6c5dV1d+brFEmWtXbegb7I/bNylrjJpubd36nMWTbLo/JtGiUjSYEl+GbtNv9mhLklA0v9ZMvppHTzzieATgPNPTfbZLz//vuv6nP7c9QXt2Ye7rnnngw10ZO1vfXdMicPPPBAFmtn2JOksziXI8r9Z3I22w7LsSQol8Xgnfmcb3MKcjk7PZSJfeKJJ7KwmmaaM5I7pETX/0Xnoa832HNech6zwbe3NttcXr9Gc0dicd6xvtm+YWimNHOeksuszicrZTy5W+Y568EzyRnt8nLV/EbN584edXQZ53xM7ER2zuN6YX59dHtOaP4syfzPnwG5PQe1LE4fNUX752Hn46gAgLVTR13nfeaZZzZ/kOwiq2LLaxavKO2V4Dj4vqL5MKNkxPKB5Ifuaz47fYmG+Uipg594umfvuf+hL6PMqLK1bOo3v0NlrkFf8S1Tm+2SZxy857z2IGPY/wkAV9x4RpKNX9VGjjovm+0kz5ri/i3k2BOIBydwJvao8cxorzjt2XIKez4BdLb54osv5iHzOVaH2nNCj3lEf8ij4I/19NNPbwCuHX/Gs2ba7qi1zEMdfC5PZ+RpflbI9rxgdPa18/DjlN/+vS/SGYf+10rHd/wmvn7r+Ldfld+2kT0zc8xJztk5dAL3T+wxR5vSzVX+rHTO/4qURfRU7KH/x9Vx9nu1vza/51EA8L/s2ljCyYXXKdHlf1+EHcnKLJmnQV999dW5Zec/jAUA/oKujRjN+laW5c6cOfM7Fyb5/y3peeHChflPB86ePXv8dWgAoOXaiNFUxf6r8zBOnTq1/tx7AOAv7qQ+ZxQAAK5IjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoOXX58uUNAAA0WBkFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1Uvkg3gAABbhJREFUYhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKBGjAIAUCNGAQCoEaMAANSIUQAAasQoAAA1YhQAgBoxCgBAjRgFAKDm/wAAAP//OaVOfAAAAAZJREFUAwAYYPiqgX7+fQAAAABJRU5ErkJggg=="><figcaption>Baseline engine passed. The painted text/background pair is approximately 1.94:1.</figcaption></figure></section><footer>Local audit and working-tree repairs. No version bump, commit, push or publication. Reproduce from the repository root with the scripts under reports/engine-audit/2026-09-12.</footer></main></body></html>
|