REPOSITORY REVIEW · 12 SEPTEMBER 2026 · c61fe1a

Accuracy first. Measured performance. Explicit uncertainty.

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.

Repairs and verification

Accuracy and performance repairs

12 September 2026. Working-tree changes against c61fe1a. 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, HANDOVER.md, records the state before the last checks ran.

A plain-language account of every rule change, with the Beta Block count explained, is in rule-changes.html.

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.

Implemented

Verification on the final source

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.

CheckResultEvidence
Engine regression cases805 of 805 pass; 687 at baseline, 118 addedhandover-cases.log
Site and extension buildPassed; site output isolated from the running preview watcherhandover-build.log
Editor package and static lanePassed; the package is 5,721 KBhandover-vscode-package.log, handover-vscode.log
Real extension frame audit, Show and hoverPassedhandover-frames.log
ARIA tables and site factsPassedhandover-aria-tables.log, handover-facts.log
Code independence scanCleanhandover-cleanroom.log, report
Full lab gate, run 2026-09-12-08-41-06Passed; figures belowhandover-lab.log
Four-surface smokeFirst 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 passhandover-smoke-failed-launch.log, final-smoke.log
Frame boundary scenariosSix of six pass: private results, slow frame, shadow frame, unavailable frame, spoofed descendant, 16,000-control childfinal-frame-boundaries.log
Runtime contractsElement and shadow-host roots, mutable paths, cancellation in 56 msfinal-runtime.log
Beta Block scoringIdentical to the earlier run in fix-accuracy.log; figures belowfinal-accuracy.log, final-accuracy.json
Firefox testbedsNot verified: the headless renderer could not start in this environment. The Firefox package buildsfix-firefox-launch-error.log

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.

Contrast gate

The final gate sampled live pages, so its counts differ from the earlier gate in fix-lab-gate.log (321 samples) without any source change between the two contrast runs. Do not read the difference as an algorithm change.

MeasureFinal gate
Samples, pages312, 22
Confirmed failures51
Likely false positives0
Potential missed failures0
Confirmed passes135
Skipped28
Reviews with a justified varied background8
Reviews that measure as passing66
Reviews that measure as failing8
Borderline, within antialiasing noise3
Mean, median ratio error0.003, 0

Eleven testbeds clean. Coverage 88 rules, 39 criteria partially assisted, 47 manual. This is sampled evidence, not proof that no contrast defect remains.

Beta Block, against the shipped engine

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.

MeasureShipped engineThis sourceComparator
Planted violations asserted, of 38383228
Planted violations caught including review383831
Planted reviews caught, of 8880
Assertions credited, unmatched39, 039, 030, 0

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 e-mail), 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 scripts/lib/facts.js 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.

Checked against W3C's published tests on 12 September: the ACT rules for a meta refresh delay, a viewport that blocks zoom, orientation locked by CSS rotation and autocomplete values 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. F41 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.

Controlled workload timings

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 target timings and selector timings.

Corpus differential on the final source

differential.mjs builds c61fe1a 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 (fix-differential-0.json, 1, 2) and skipped in the final one, since it did not change.

MeasureFinal snapshot
Pages attempted138
Completed in three concurrent shards, 08:48 UTC130
Completed on retry alone, 08:59 UTC (MDN, HSBC inclusion)2
Unavailable in every run6
Pages whose DOM changed during the run18
Verdict changes, all completed pages454

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: 0, 1, 2; retries: MDN, HSBC inclusion, John Lewis, log. The three shards ran concurrently for accuracy, so their timings are not performance evidence.

The 454 verdict changes fall into these classes. Half of them are on one page.

ClassRowsWhereStatus
Contrast assertions removed by corrected multiply arithmetic225ECMAScript specificationMeasured: glyph pixels read 4.57:1 where the old arithmetic gave 4.03:1. adjudication-visual.md
Contrast reviews removed by actual partial-alpha compositing8Social Security Administration 7, TypeScript 1Measured. adjudication-visual.md
Text or control contrast now under review because a filter or blend cannot be resolved14315 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 ArchiveConservative 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. final-filter-blend-census.json, final-filter-blend-avoidable.json
Contrast now under review because an image or gradient paints the backdrop and its pixels could not be settled2212 pages; Japanese Wikipedia 10, ETH Zurich 2, one each on ten othersConservative by design; not individually measured
Field boundary reviews added9Age UK 3, Ant Design 2, BBC Good Food, Scope, Stanford, MUINot individually measured
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 114ECMAScript, GDS audit page, Beta Block, UW demo, Excalidraw, IKEA, Hacker NewsPolicy; the Beta Block rows are the six above. Marquee reasoning in adjudication-interaction.md
Assertions removed with evidence: target size 3, blink 2, button name 27Ant Design, GDS audit page, Mozilla, PostgreSQLEquivalent controls hit-tested; no animation on the blink elements; the browser exposes the glyph names. adjudication-interaction.md, adjudication-semantics.md
Generic-link reviews removed6GDS, BBC Good Food, Sense, UserWay, BBC News, AppleThe controls carry button or tab roles. adjudication-semantics.md
New assertion: menubar without exposed menu items1Python.orgConfirmed against the Chromium accessibility tree. adjudication-semantics.md
New contrast assertions on a page that changed during the run2RubyPage state, not code: in four settled alternating runs both builds assert both links identically, 4.46:1 and 3.84:1. final-france24-ruby-probe.json, final-ruby-try-link.png
Reading-order reviews moved between sibling elements8France 24Page 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
Other new reviews: unsupported aria-selected on list items 5, unnamed SVG 2, text-spacing budget disclosure 1; one SVG review removed9World Bank, Cambridge, ECMAScript, TypeScriptReview direction; not individually measured

The 4,809 word-joiner reviews seen on the ECMAScript page before the final correction are absent from the final snapshot.

Isolated performance, with caveats

Source: performance-after.mjs, data fix-performance-isolated.json. 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.

PageElementsBaselineThis sourceComparator
ECMAScript specification, initial run194,10313,784.5 ms6,112.1 ms188,737 ms, one run
ECMAScript specification, median of three warm runs194,10313,416.2 ms6,180.7 msnot repeated
nerdy.dev, rounds 2 and 318,6599,308.5 and 9,263.6 ms10,303.8 and 10,284.7 ms5,608.4 and 5,646.9 ms
Astro, median of three warm runs1,85539.7 ms42.6 ms114.5 ms
React, median of three warm runs1,84947.2 ms50.9 ms163.0 ms

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.

Integration changes

The extension adds webNavigation 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 Chrome's permission documentation and the site's security page, which documents the use.

Generated audit reports and the Beta Block fixture remain standalone. No existing published benchmark is replaced or relabelled as a measurement of these changes.

Limits and follow-up

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.

Corpus adjudication evidence: semantic findings, paint and lenient changes, target and motion exceptions. 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.

Afternoon follow-up: assertions restored, abstention narrowed

Done on 12 September between 09:30 and 11:10 UTC after the recommendation below was accepted. Same working tree, still against c61fe1a, still uncommitted.

Assertions restored. 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 @supports 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.

Abstention narrowed. 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.

Verified on the result. Every check below ran on the final source of this follow-up.

CheckResultEvidence
Engine regression cases817 of 817 pass; 12 addedfinal-restore-engine.log has the testbeds; the case log is in the session record
Testbeds, ARIA tablesEleven testbeds clean; every table matches the specfinal-restore-engine.log, final-restore-aria.log
Full lab gate, run 2026-09-12-09-46-06Passed: 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 0final-restore-gate.log
Smoke, frames, boundaries, runtimeAll passfinal-restore-smoke.log, final-restore-frames.log, final-restore-boundaries.log, final-restore-runtime.log
Editor package and static lane, facts, cleanroomAll pass; cleanroom cleanfinal-restore-vscode.log, final-restore-facts.log, cleanroom report
Beta Block38 of 38 asserted, 38 caught, 8 of 8 reviews, 44 assertions credited, 0 unmatchedfinal-restore-accuracy.log

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.

Corpus differential, restored source against the shipped engine. 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: 0, 1, 2. The four pages with headerless tables were rerun one at a time after the table change: Beta Block, GDS, UW, ECMAScript.

ClassBefore the follow-upAfter
Assertions demoted to review under the unprovable-claim policy144 (scrollable region 2, marquee 1, bypass blocks 1)
Text or control contrast under review for a filter or blend14362
Contrast under review for an image or gradient backdrop2223
Field boundary reviews99
Contrast assertions removed by corrected multiply arithmetic225225
Contrast reviews removed by measured compositing88
Other verdict changes (targets, blink, names, generic links, Python, ARIA attributes, SVG names, text spacing, the two page-state pages)3331
All verdict changes454 on 132 pages371 on 131 pages

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 (final-userway-probe.json). Ruby's page-state pair and France 24's sibling move recur as before.

Recommendation

12 September 2026. Baseline: commit c61fe1a, 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 Accuracy and performance repairs. Baseline findings and rule ratings below must not be read as the current status of repaired code.

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.

Scope and method

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.

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.

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: WCAG 2.2, WCAG overview, catalog comparison.

Confirmed priorities

P1: Frame audits silently lose findings

src/extension/content.js:118,149,231 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 ok: true 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.

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: frame probes, late reply probe, observations.

P1: Cross-origin audit snippets are exposed to page scripts

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.

Move audit results through extension-owned runtime messaging with frame IDs and verified request ownership. Keep snippets out of page-visible messages. Local proof, result.

P1: Name and visibility helpers give wrong semantic verdicts

src/engine/lib/accessible-name.js:67,154,189 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.

src/engine/lib/dom.js:29,39,47 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. src/engine/index.js:133 also omits a passed Element context itself, because querySelectorAll only visits descendants.

Correct the shared composed-tree and accessible-name model before adding individual rule exceptions. Keep visual rendering separate from accessibility-tree exposure. Evidence: independent full-engine semantics, core probes. Primary references: Accessible Name Computation, HTML inert subtrees.

P1: Contrast can use colours that are not painted

1.4.3-control-contrast.js:62,88,97 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.

The reverse error also exists: lib/contrast.js:1270,1284 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: full-engine visual checks, readable field, missed low contrast. Source: Contrast Minimum.

P1: Target geometry and exceptions overstate failures

2.5.8-target-size.js:431-454,567 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.

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. Independent evidence, W3C size requirement and exceptions.

P1: Syntax and heuristics are being promoted into WCAG violations

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.

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. Semantic evidence, interaction evidence, ARIA error handling, ARIA current.

P2: Untested content is sometimes recorded as passing

1.4.12-text-spacing.js:47 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.

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. Spacing evidence, Text Spacing.

Performance measurements

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.

PageDOM elements at samplingInitial runMedian of next three
Astro1,85382 ms42 ms
Dementia UK1,97253 ms37 ms
ECMAScript specification, isolated repeat194,10114,365 ms14,764 ms

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.

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.

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. lib/dom.js:69 repeatedly walks siblings to construct nth-of-type paths. A per-parent sibling index can reduce this repeated work, subject to mutation invalidation.

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.

Raw data: three-page profile, isolated large-page repeat, reproducible profiler.

Repository and test infrastructure

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.

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.

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.

Recommended repair order

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 WCAG conformance requirements.

Baseline rating of every active rule

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.

28 OVER-FLAGS28 MINOR-GAPS24 UNDER-TESTS6 SOLID2 WEAK
area-altOVER-FLAGS

wcag111, wcag244 · critical

1.1.1-area-alt.js:9. Unused maps still fail (measured); must find associated rendered image and exposed active area.

Primary reference

canvas-altMINOR-GAPS

wcag111 · moderate

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.

Primary reference

embed-altUNDER-TESTS

wcag111 · serious

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.

Primary reference

image-altUNDER-TESTS

wcag111 · critical

1.1.1-image-alt.js:24-25. Focusable role=presentation img passes although browser exposes unnamed image (measured). Generic/filename alternatives appropriately reviewed.

Primary reference

input-image-altUNDER-TESTS

wcag111, wcag412 · critical

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.

Primary reference

object-altUNDER-TESTS

wcag111 · serious

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.

Primary reference

svg-img-altUNDER-TESTS

wcag111 · serious

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.

Primary reference

audio-transcriptMINOR-GAPS

wcag121 · serious

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.

Primary reference

media-captionsMINOR-GAPS

wcag122 · critical

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.

Primary reference

aria-required-childrenUNDER-TESTS

wcag131 · critical

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.

Primary reference

aria-required-parentOVER-FLAGS

wcag131 · critical

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.

Primary reference

definition-listUNDER-TESTS

wcag131 · serious

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.

Primary reference

dlitem-parentMINOR-GAPS

wcag131 · serious

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.

Primary reference

form-labelOVER-FLAGS

wcag131, wcag332, wcag412 · critical

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.

Primary reference

list-structureOVER-FLAGS

wcag131 · serious

1.3.1-list-structure.js:63-68. Measured focusable role=none LI retains listitem but fails. Uses raw role rather than effective role.

Primary reference

listitem-parentUNDER-TESTS

wcag131 · serious

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.

Primary reference

p-as-headingSOLID

wcag131 · serious

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.

Primary reference

table-headersWEAK

wcag131 · serious

1.3.1-table-headers.js:68,79,92. Measured layout-table assertion and plain TD header-reference pass.

Primary reference

reading-order-divergenceMINOR-GAPS

wcag132 · moderate

1.3.2-reading-order-divergence.js:32-70. Review-only preserves verdict discipline. Grid placement, vertical writing and nested wrappers outside scope.

Primary reference

orientation-lockOVER-FLAGS

wcag134 · serious

1.3.4-orientation.js:40,84. Two measured ineffective CSS declarations still fail.

Primary reference

autocomplete-validOVER-FLAGS

wcag135 · serious

1.3.5-autocomplete-valid.js:99-110. Measured non-personal search wrongly included in criterion scope. No missing-purpose branch; grammar only.

Primary reference

link-in-text-blockOVER-FLAGS

wcag141 · serious

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

Primary reference

audio-controlSOLID

wcag142 · critical

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.

Primary reference

color-contrastOVER-FLAGS

wcag143 · serious

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

Primary reference

control-contrastOVER-FLAGS

wcag143 · serious

Three independent measured false violations: background image, group opacity, text fill. src/engine/rules/wcag/1.4.3-control-contrast.js:62,88,97

Primary reference

meta-viewportOVER-FLAGS

wcag144 · critical

Definitive failure ignores an available G178 text-resize mechanism. src/engine/rules/wcag/1.4.4-meta-viewport.js:19

Primary reference

color-contrast-enhancedOVER-FLAGS

wcag146 · serious

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.

Primary reference

reflowMINOR-GAPS

wcag1410 · moderate

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.

Primary reference

non-text-contrastUNDER-TESTS

wcag1411 · serious

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.

Primary reference

text-spacingUNDER-TESTS

wcag1412 · serious

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.

Primary reference

scrollable-region-focusableOVER-FLAGS

wcag211, wcag213 · serious

Source: src/engine/rules/wcag/2.1.1-scrollable-region-focusable.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html

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.

Primary reference

meta-refreshOVER-FLAGS

wcag221 · critical

Source: src/engine/rules/wcag/2.2.1-meta-refresh.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/timing-adjustable.html

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.

Primary reference

pause-stop-hideOVER-FLAGS

wcag222 · serious

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

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.

Primary reference

video-loop-motionMINOR-GAPS

wcag222 · serious

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

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.

Primary reference

reduced-motionMINOR-GAPS

wcag233 · moderate

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

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.

Primary reference

bypass-blocksOVER-FLAGS

wcag241 · serious

Source: src/engine/rules/wcag/2.4.1-bypass-blocks.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html

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.

Primary reference

document-titleOVER-FLAGS

wcag242 · serious

Source: src/engine/rules/wcag/2.4.2-document-title.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/page-titled.html

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.

Primary reference

visual-order-divergenceMINOR-GAPS

wcag243 · moderate

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

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.

Primary reference

link-nameSOLID

wcag244, wcag412 · serious

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

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.

Primary reference

link-text-genericMINOR-GAPS

wcag244 · moderate

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

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.

Primary reference

focus-visibleMINOR-GAPS

wcag247 · serious

Source: src/engine/rules/wcag/2.4.7-focus-visible.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html

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.

Primary reference

link-text-generic-onlyOVER-FLAGS

wcag249 · moderate

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

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.

Primary reference

focus-not-obscuredUNDER-TESTS

wcag2411 · serious

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

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.

Primary reference

target-size-enhancedOVER-FLAGS

wcag255 · serious

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

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.

Primary reference

drag-alternativeMINOR-GAPS

wcag257 · moderate

Source: src/engine/rules/wcag/2.5.7-drag-alternative.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html

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.

Primary reference

target-sizeOVER-FLAGS

wcag258 · serious

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

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.

Primary reference

html-langMINOR-GAPS

wcag311 · serious

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

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.

Primary reference

valid-lang-partsUNDER-TESTS

wcag312 · serious

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

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.

Primary reference

on-input-navigationSOLID

wcag322, wcag321 · serious

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

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.

Primary reference

error-message-linkageSOLID

wcag331 · serious

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

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.

Primary reference

redundant-entryMINOR-GAPS

wcag337 · moderate

Source: src/engine/rules/wcag/3.3.7-redundant-entry.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/redundant-entry.html

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.

Primary reference

auth-field-obstructionOVER-FLAGS

wcag338 · serious

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

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.

Primary reference

aria-allowed-attrMINOR-GAPS

wcag412 · moderate

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.

Primary reference

aria-attr-validOVER-FLAGS

wcag412 · critical

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.

Primary reference

aria-field-nameOVER-FLAGS

wcag412 · serious

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.

Primary reference

aria-hidden-focusUNDER-TESTS

wcag412 · serious

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.

Primary reference

aria-label-misuseMINOR-GAPS

wcag412 · moderate

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.

Primary reference

aria-valid-refsOVER-FLAGS

wcag412 · moderate

4.1.2-aria-valid-refs.js:129-132. Measured dormant error reference assertion. Broader broken-ref policy needs distinction from syntax errors.

Primary reference

button-nameWEAK

wcag412 · critical

4.1.2-button-name.js:11,22. Shared name computation gives three confirmed false asserts, one false pass; exact fallback selectors miss controls.

Primary reference

composite-widget-nameUNDER-TESTS

wcag412 · serious

4.1.2-composite-widget-name.js:22. Correct role ownership, but exact selectors and shared name defects apply.

Primary reference

dialog-nameUNDER-TESTS

wcag412 · serious

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.

Primary reference

frame-titleMINOR-GAPS

wcag412 · serious

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.

Primary reference

invoker-targetOVER-FLAGS

wcag412 · serious

4.1.2-invoker-target.js:31,66. Measured static paragraph blamed as button. Event interception, disabled controls and reflection-target cases need coverage.

Primary reference

label-for-validOVER-FLAGS

wcag412 · serious

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.

Primary reference

nested-interactiveUNDER-TESTS

wcag412 · serious

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.

Primary reference

role-required-ariaUNDER-TESTS

wcag412 · serious

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.

Primary reference

summary-nameUNDER-TESTS

wcag412 · serious

4.1.2-summary-name.js:22-25. Correctly isolates first summary; shared descendant-generated/name faults still apply.

Primary reference

valid-roleOVER-FLAGS

wcag412 · critical

4.1.2-valid-role.js:119. Native named button typo wrongly asserted. Prefix acceptance admits arbitrary doc-* and graphics-* roles as valid.

Primary reference

accesskeysUNDER-TESTS

Best practice · serious

Source: src/engine/rules/best-practice/accesskeys.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/character-key-shortcuts.html

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.

Primary reference

button-typeMINOR-GAPS

Best practice · minor

Source: src/engine/rules/best-practice/button-type.js:1. Primary reference: https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type

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.

Primary reference

empty-headingUNDER-TESTS

Best practice · minor

Source: src/engine/rules/best-practice/empty-heading.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/

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.

Primary reference

fieldset-legendOVER-FLAGS

Best practice · moderate

Source: src/engine/rules/best-practice/fieldset-legend.js:1. Primary reference: https://www.w3.org/WAI/tutorials/forms/grouping/

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.

Primary reference

heading-orderMINOR-GAPS

Best practice · moderate

Source: src/engine/rules/best-practice/heading-order.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/

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.

Primary reference

landmark-one-mainMINOR-GAPS

Best practice · moderate

Source: src/engine/rules/best-practice/landmark-one-main.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/

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.

Primary reference

landmark-top-levelMINOR-GAPS

Best practice · moderate

Source: src/engine/rules/best-practice/landmark-top-level.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/

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.

Primary reference

landmark-uniqueOVER-FLAGS

Best practice · moderate

Source: src/engine/rules/best-practice/landmark-unique.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/

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.

Primary reference

multiple-labelsUNDER-TESTS

Best practice · minor

Source: src/engine/rules/best-practice/multiple-labels.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html

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.

Primary reference

new-window-linkMINOR-GAPS

Best practice · minor

Source: src/engine/rules/best-practice/new-window-link.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Techniques/general/G201

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.

Primary reference

no-autofocusSOLID

Best practice · minor

Source: src/engine/rules/best-practice/no-autofocus.js:1. Primary reference: https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute

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.

Primary reference

page-heading-oneMINOR-GAPS

Best practice · moderate

Source: src/engine/rules/best-practice/page-heading-one.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/

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.

Primary reference

positive-tabindexUNDER-TESTS

Best practice · serious

Source: src/engine/rules/best-practice/positive-tabindex.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html

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.

Primary reference

redundant-alt-phraseMINOR-GAPS

Best practice · minor

Source: src/engine/rules/best-practice/redundant-alt-phrase.js:1. Primary reference: https://www.w3.org/WAI/tutorials/images/tips/

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.

Primary reference

redundant-ariaUNDER-TESTS

Best practice · minor

Source: src/engine/rules/best-practice/redundant-aria.js:1. Primary reference: https://www.w3.org/TR/using-aria/#rule2

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.

Primary reference

redundant-aria-labelMINOR-GAPS

Best practice · minor

Source: src/engine/rules/best-practice/redundant-aria-label.js:1. Primary reference: https://www.w3.org/TR/using-aria/#rule2

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.

Primary reference

redundant-image-altMINOR-GAPS

Best practice · minor

Source: src/engine/rules/best-practice/redundant-image-alt.js:1. Primary reference: https://www.w3.org/WAI/tutorials/images/functional/

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.

Primary reference

redundant-roleMINOR-GAPS

Best practice · minor

Source: src/engine/rules/best-practice/redundant-role.js:1. Primary reference: https://www.w3.org/TR/using-aria/#firstrule

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.

Primary reference

redundant-tabindexUNDER-TESTS

Best practice · minor

Source: src/engine/rules/best-practice/redundant-tabindex.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html

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.

Primary reference

regionMINOR-GAPS

Best practice · moderate

Source: src/engine/rules/best-practice/region.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/

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.

Primary reference

Parked rule

label-in-name · not registered

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

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.

Manual scope and safe opportunities

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.

SCCriterionLevelPrinciple
1.2.3Audio Description or Media Alternative (Prerecorded)APerceivable
1.2.4Captions (Live)AAPerceivable
1.2.5Audio Description (Prerecorded)AAPerceivable
1.2.6Sign Language (Prerecorded)AAAPerceivable
1.2.7Extended Audio Description (Prerecorded)AAAPerceivable
1.2.8Media Alternative (Prerecorded)AAAPerceivable
1.2.9Audio-only (Live)AAAPerceivable
1.3.3Sensory CharacteristicsAPerceivable
1.3.6Identify PurposeAAAPerceivable
1.4.5Images of TextAAPerceivable
1.4.7Low or No Background AudioAAAPerceivable
1.4.8Visual PresentationAAAPerceivable
1.4.9Images of Text (No Exception)AAAPerceivable
1.4.13Content on Hover or FocusAAPerceivable
2.1.2No Keyboard TrapAOperable
2.1.4Character Key ShortcutsAOperable
2.2.3No TimingAAAOperable
2.2.4InterruptionsAAAOperable
2.2.5Re-authenticatingAAAOperable
2.2.6TimeoutsAAAOperable
2.3.1Three Flashes or Below ThresholdAOperable
2.3.2Three FlashesAAAOperable
2.4.5Multiple WaysAAOperable
2.4.6Headings and LabelsAAOperable
2.4.8LocationAAAOperable
2.4.10Section HeadingsAAAOperable
2.4.12Focus Not Obscured (Enhanced)AAAOperable
2.4.13Focus AppearanceAAAOperable
2.5.1Pointer GesturesAOperable
2.5.2Pointer CancellationAOperable
2.5.3Label in NameAOperable
2.5.4Motion ActuationAOperable
2.5.6Concurrent Input MechanismsAAAOperable
3.1.3Unusual WordsAAAUnderstandable
3.1.4AbbreviationsAAAUnderstandable
3.1.5Reading LevelAAAUnderstandable
3.1.6PronunciationAAAUnderstandable
3.2.3Consistent NavigationAAUnderstandable
3.2.4Consistent IdentificationAAUnderstandable
3.2.5Change on RequestAAAUnderstandable
3.2.6Consistent HelpAUnderstandable
3.3.3Error SuggestionAAUnderstandable
3.3.4Error Prevention (Legal, Financial, Data)AAUnderstandable
3.3.5HelpAAAUnderstandable
3.3.6Error Prevention (All)AAAUnderstandable
3.3.9Accessible Authentication (Enhanced)AAAUnderstandable
4.1.3Status MessagesAARobust

Coverage additions after correctness repairs

Detailed evidence and reasoning

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.

Names, semantics and ARIA: full evidence and scope notes

Semantic engine audit, 2026-09-12

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.

Evidence: pour-semantics-probe.mjs builds the actual source rules using esbuild, then invokes their evaluate methods in Chromium, with real selectors and the runner's visibility applicability check. pour-semantics-probes.json 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.

Highest priority findings

1. Name computation reports named controls as nameless, and misses unnamed controls

src/engine/lib/accessible-name.js:67 uses raw label.textContent; :189 recurses descendants without their own generated content or aria-labelledby; :154 includes all hidden descendants whenever inside any labelledby traversal. Browser probes confirm:

  • generated-child-name: <style>.label::before{content:"Save"}</style><button id="test"><span class="label"></span></button> yields button-name fail, CDP button name Save.
  • descendant-ref-name: <span id="label">Save</span><button id="test"><span aria-labelledby="label"></span></button> yields fail, CDP name Save.
  • label-image-name: <label for="test"><img alt="Save" src="icon.svg"></label><button id="test"></button> yields fail, CDP name Save.
  • label-hidden-child-ref: <span id="label"><span hidden>Save</span></span><button id="test" aria-labelledby="label"></button> yields pass, engine name Save, CDP empty name.

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. Accessible Name Computation.

2. AT visibility fails to exclude inert subtrees

src/engine/lib/dom.js:47 checks aria-hidden and rendering, but never inert. Probe inert-name (<div inert><button id="test"></button></div>) 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. HTML inert subtrees.

3. Role handling is inconsistent across rules

aria-required-parent lines 58 and 74 uses the first written role token, whereas roles.js has a separate effective-role model. Probe required-parent-fallback: <div role="bogus list"><div id="test" role="listitem">First</div></div> fails despite AX list/listitem. list-structure:63 counts literal role=none as neutralised even when presentation is discarded: <ul id="test"><li role="none" tabindex="0">One</li></ul> fails despite AX listitem. Both confirmed.

Exact-match selectors also silently miss legal fallback strings: <div role="unknown button"></div> 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. effectiveRole:240 itself returns null for wholly unrecognised explicit roles instead of native fallback. ARIA role error handling.

4. Orientation raw CSS is mistaken for effective restriction

1.3.4-orientation.js:40-56,84 scans declarations without cascade or active ancestor condition evaluation. Two measured false assertions:

<style>@media (orientation:landscape){body{display:none}body{display:block}}</style>
<p>Visible in either orientation</p>
<style>@supports (display:completely-invalid){@media (orientation:landscape){body{display:none}}}</style>
<p>Always visible</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. SC 1.3.4.

5. Attribute validation and actual accessibility failure are conflated

  • valid-role:119: <button id="test" role="buton">Save</button> fails; CDP still exposes named, focusable button Save. The native role survives. This is an authoring defect, not established 4.1.2 failure.
  • aria-attr-valid:75-85: arbitrary aria-current="chapter" and aria-invalid="custom" fail. The ARIA token fallbacks preserve truth states; CDP confirms invalid=true for the latter. aria-current="undefined" is also explicitly false under the spec, but rejected by this enum.
  • aria-valid-refs:129-132: <input aria-label="Email" aria-invalid="false" aria-errormessage="error"> fails despite a dormant error reference. AX has named, valid textbox. Missing controls/details/owns similarly need harm-sensitive handling.

ARIA current, ARIA invalid, ARIA errormessage.

6. Table heuristic asserts on navigation layout, while weak reference checks miss actual defects

table-headers:92 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 layout-table.

table-headers:68 explicitly permits TD reference targets and :79 treats mere scope/headers presence as evidence of headers. table-nonheader-reference 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. F91, Info and Relationships.

7. Several additional direct false assertions

  • aria-field-name:14-18,36: native <meter role="meter"> with ordinary associated label fails, although CDP says meter Progress. This branch only accepts ARIA/title and ignores native labels.
  • form-label:45-61: CSS-generated label text is missed. <style>label::before{content:"Email"}</style><label for="test"></label><input id="test"> fails, CDP textbox Email.
  • area-alt:9-14: unused map areas are checked despite no associated image or AX presence. Probe map-unused fails an unused <map><area href=...></map>.
  • invoker-target:31,66: <p popovertarget="missing">Static paragraph</p> fails as if it were a button. Scope the rule to invoker-capable controls, then distinguish functional faults from 4.1.2 evidence.
  • label-for-valid:44-46: <label for="missing">Email<input aria-label="Email"></label> fails despite the independently named textbox and visible matching caption. A broken association does not prove missing name.
  • autocomplete-valid:99-110: 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.

HTML naming, HTML invokers, SC 1.3.5, F107.

Per-rule inventory and rating

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.

RuleRatingSource and assessment
area-altOVER-FLAGS1.1.1-area-alt.js:9. Unused maps still fail (measured); must find associated rendered image and exposed active area.
canvas-altMINOR-GAPS1.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.
embed-altUNDER-TESTS1.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.
image-altUNDER-TESTS1.1.1-image-alt.js:24-25. Focusable role=presentation img passes although browser exposes unnamed image (measured). Generic/filename alternatives appropriately reviewed.
input-image-altUNDER-TESTS1.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.
object-altUNDER-TESTS1.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.
svg-img-altUNDER-TESTS1.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.
audio-transcriptMINOR-GAPS1.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.
media-captionsMINOR-GAPS1.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.
aria-required-childrenUNDER-TESTS1.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.
aria-required-parentOVER-FLAGS1.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.
definition-listUNDER-TESTS1.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.
dlitem-parentMINOR-GAPS1.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.
form-labelOVER-FLAGS1.3.1-form-label.js:45-61. Measured CSS-generated visible label missed. Own accessible-text implementation drifts from shared helper and browser algorithm.
list-structureOVER-FLAGS1.3.1-list-structure.js:63-68. Measured focusable role=none LI retains listitem but fails. Uses raw role rather than effective role.
listitem-parentUNDER-TESTS1.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-as-headingSOLID1.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.
table-headersWEAK1.3.1-table-headers.js:68,79,92. Measured layout-table assertion and plain TD header-reference pass.
reading-order-divergenceMINOR-GAPS1.3.2-reading-order-divergence.js:32-70. Review-only preserves verdict discipline. Grid placement, vertical writing and nested wrappers outside scope.
orientation-lockOVER-FLAGS1.3.4-orientation.js:40,84. Two measured ineffective CSS declarations still fail.
autocomplete-validOVER-FLAGS1.3.5-autocomplete-valid.js:99-110. Measured non-personal search wrongly included in criterion scope. No missing-purpose branch; grammar only.
aria-allowed-attrMINOR-GAPS4.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.
aria-attr-validOVER-FLAGS4.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.
aria-field-nameOVER-FLAGS4.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.
aria-hidden-focusUNDER-TESTS4.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.
aria-label-misuseMINOR-GAPS4.1.2-aria-label-misuse.js:66-84. Review-only justified. Exact role selectors and light-DOM content ancestor can add noise on slots.
aria-valid-refsOVER-FLAGS4.1.2-aria-valid-refs.js:129-132. Measured dormant error reference assertion. Broader broken-ref policy needs distinction from syntax errors.
button-nameWEAK4.1.2-button-name.js:11,22. Shared name computation gives three confirmed false asserts, one false pass; exact fallback selectors miss controls.
composite-widget-nameUNDER-TESTS4.1.2-composite-widget-name.js:22. Correct role ownership, but exact selectors and shared name defects apply.
dialog-nameUNDER-TESTS4.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.
frame-titleMINOR-GAPS4.1.2-frame-title.js:11. Direct title/ARIA path sound; global inert and shared labelledby problems apply. No frame-resource distinction in rule.
invoker-targetOVER-FLAGS4.1.2-invoker-target.js:31,66. Measured static paragraph blamed as button. Event interception, disabled controls and reflection-target cases need coverage.
label-for-validOVER-FLAGS4.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.
nested-interactiveUNDER-TESTS4.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.
role-required-ariaUNDER-TESTS4.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.
summary-nameUNDER-TESTS4.1.2-summary-name.js:22-25. Correctly isolates first summary; shared descendant-generated/name faults still apply.
valid-roleOVER-FLAGS4.1.2-valid-role.js:119. Native named button typo wrongly asserted. Prefix acceptance admits arbitrary doc-* and graphics-* roles as valid.

Performance opportunities from source, not yet measured

  • aria-valid-refs:86 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.
  • 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.
  • 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.
  • 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.
  • Improve role and visibility reuse. Today each rule implements small inconsistent approximations, creating both repeated work and accuracy drift.

Manual criteria in assigned families

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.

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.

Interaction, language and best practices: full evidence and scope notes

Engine interaction and best-practice audit

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.

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.

Confirmed browser evidence

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.

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.

Per-rule review

scrollable-region-focusable | OVER-FLAGS | 8 dedicated cases

Source: src/engine/rules/wcag/2.1.1-scrollable-region-focusable.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html

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.

meta-refresh | OVER-FLAGS | 12 dedicated cases

Source: src/engine/rules/wcag/2.2.1-meta-refresh.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/timing-adjustable.html

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.

pause-stop-hide | OVER-FLAGS | 9 dedicated cases

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

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.

video-loop-motion | MINOR-GAPS | 8 dedicated cases

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

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.

reduced-motion | MINOR-GAPS | 10 dedicated cases

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

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.

bypass-blocks | OVER-FLAGS | 12 dedicated cases

Source: src/engine/rules/wcag/2.4.1-bypass-blocks.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html

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.

focus-not-obscured | UNDER-TESTS | 2 dedicated cases

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

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.

document-title | OVER-FLAGS | 10 dedicated cases

Source: src/engine/rules/wcag/2.4.2-document-title.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/page-titled.html

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.

visual-order-divergence | MINOR-GAPS | 7 dedicated cases

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

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.

link-name | SOLID | 9 dedicated cases

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

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.

link-text-generic | MINOR-GAPS | 2 dedicated cases

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

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.

focus-visible | MINOR-GAPS | 5 dedicated cases

Source: src/engine/rules/wcag/2.4.7-focus-visible.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html

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.

link-text-generic-only | OVER-FLAGS | 18 dedicated cases

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

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.

label-in-name | UNDER-TESTS | 0 dedicated cases

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

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.

target-size-enhanced | OVER-FLAGS | 8 dedicated cases

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

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.

drag-alternative | MINOR-GAPS | 2 dedicated cases

Source: src/engine/rules/wcag/2.5.7-drag-alternative.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/dragging-movements.html

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.

target-size | OVER-FLAGS | 36 dedicated cases

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

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.

html-lang | MINOR-GAPS | 2 dedicated cases

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

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.

valid-lang-parts | UNDER-TESTS | 0 dedicated cases

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

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.

on-input-navigation | SOLID | 8 dedicated cases

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

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.

error-message-linkage | SOLID | 6 dedicated cases

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

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.

redundant-entry | MINOR-GAPS | 5 dedicated cases

Source: src/engine/rules/wcag/3.3.7-redundant-entry.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/redundant-entry.html

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.

auth-field-obstruction | OVER-FLAGS | 7 dedicated cases

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

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.

accesskeys | UNDER-TESTS | 0 dedicated cases

Source: src/engine/rules/best-practice/accesskeys.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/character-key-shortcuts.html

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.

button-type | MINOR-GAPS | 3 dedicated cases

Source: src/engine/rules/best-practice/button-type.js:1. Primary reference: https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type

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.

empty-heading | UNDER-TESTS | 0 dedicated cases

Source: src/engine/rules/best-practice/empty-heading.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/

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.

fieldset-legend | OVER-FLAGS | 4 dedicated cases

Source: src/engine/rules/best-practice/fieldset-legend.js:1. Primary reference: https://www.w3.org/WAI/tutorials/forms/grouping/

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.

heading-order | MINOR-GAPS | 6 dedicated cases

Source: src/engine/rules/best-practice/heading-order.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/

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.

landmark-one-main | MINOR-GAPS | 4 dedicated cases

Source: src/engine/rules/best-practice/landmark-one-main.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/

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.

landmark-top-level | MINOR-GAPS | 8 dedicated cases

Source: src/engine/rules/best-practice/landmark-top-level.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/

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.

landmark-unique | OVER-FLAGS | 6 dedicated cases

Source: src/engine/rules/best-practice/landmark-unique.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/

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.

multiple-labels | UNDER-TESTS | 0 dedicated cases

Source: src/engine/rules/best-practice/multiple-labels.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/labels-or-instructions.html

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.

new-window-link | MINOR-GAPS | 3 dedicated cases

Source: src/engine/rules/best-practice/new-window-link.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Techniques/general/G201

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.

no-autofocus | SOLID | 3 dedicated cases

Source: src/engine/rules/best-practice/no-autofocus.js:1. Primary reference: https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute

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.

page-heading-one | MINOR-GAPS | 4 dedicated cases

Source: src/engine/rules/best-practice/page-heading-one.js:1. Primary reference: https://www.w3.org/WAI/tutorials/page-structure/headings/

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.

positive-tabindex | UNDER-TESTS | 0 dedicated cases

Source: src/engine/rules/best-practice/positive-tabindex.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html

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.

redundant-alt-phrase | MINOR-GAPS | 4 dedicated cases

Source: src/engine/rules/best-practice/redundant-alt-phrase.js:1. Primary reference: https://www.w3.org/WAI/tutorials/images/tips/

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.

redundant-aria-label | MINOR-GAPS | 4 dedicated cases

Source: src/engine/rules/best-practice/redundant-aria-label.js:1. Primary reference: https://www.w3.org/TR/using-aria/#rule2

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.

redundant-aria | UNDER-TESTS | 0 dedicated cases

Source: src/engine/rules/best-practice/redundant-aria.js:1. Primary reference: https://www.w3.org/TR/using-aria/#rule2

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.

redundant-image-alt | MINOR-GAPS | 5 dedicated cases

Source: src/engine/rules/best-practice/redundant-image-alt.js:1. Primary reference: https://www.w3.org/WAI/tutorials/images/functional/

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.

redundant-role | MINOR-GAPS | 6 dedicated cases

Source: src/engine/rules/best-practice/redundant-role.js:1. Primary reference: https://www.w3.org/TR/using-aria/#firstrule

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.

redundant-tabindex | UNDER-TESTS | 0 dedicated cases

Source: src/engine/rules/best-practice/redundant-tabindex.js:1. Primary reference: https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html

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.

region | MINOR-GAPS | 13 dedicated cases

Source: src/engine/rules/best-practice/region.js:1. Primary reference: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/

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.

Additional primary-source checks

  • 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.
  • 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.
  • APG explicitly recommends same label for identical navigation link sets: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/#navigation .
  • 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.
  • SC2.2.2 actual motion, duration and control requirements: https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html .
  • HTML accesskey is a space-separated candidate token list: https://html.spec.whatwg.org/multipage/interaction.html#the-accesskey-attribute .

Recommendations

  • Put semantic/behavioral uncertainty into review rather than asserting through tag or phrase heuristics.
  • Correct measurable geometry and disabled-state mistakes with adversarial cases before performance work.
  • 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.
  • 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.
  • Keep best-practice advice visibly distinct from WCAG violations in consuming UI and counts.

Manual criteria: meaningful opportunities and dead ends

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.

Group and complete criterion listMeaningful bounded automationWhy a snapshot cannot certify or safely assert
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 MechanismsControlled 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.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.
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 RequestLong-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.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.
Flashing:2.3.1 Three Flashes or Below Threshold,2.3.2 Three FlashesLocal video/frame capture plus temporal luminance/red-flash analysis can measure an observed interval against primary definitions.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.
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 HelpA 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.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.
Semantic organization:2.4.6 Headings and Labels,2.4.10 Section HeadingsPresence of headings, empty or repeated generic labels and very long unstructured content can produce targeted review questions with exact examples.Meaning, purpose, sections and adequacy require reading. Every empty heading or long paragraph is not automatically a failure of these criteria.
Visual focus:2.4.12 Focus Not Obscured Enhanced,2.4.13 Focus AppearanceControlled 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.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.
Label in Name:2.5.3There 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.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.
Language semantics:3.1.3 Unusual Words,3.1.4 Abbreviations,3.1.5 Reading Level,3.1.6 PronunciationMissing title on abbr and glossary/definition associations can be candidates; language-specific reading metrics can assist an optional reviewer.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.
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 AllInstrument known test forms, deliberately submit invalid input, record correction guidance, confirmation/reversal/edit routes and context-sensitive help.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.
Authentication enhanced:3.3.9Extend controlled authentication review to identify object-recognition or personal-content challenges that are excepted atAA but notAAA, retaining review unless process alternatives are known.Available alternatives and mechanisms are process-level facts; password-field attributes and CAPTCHA shapes alone cannot rule them out.

Precision boundary

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.

Visual rendering and contrast: full evidence and scope notes

Visual rule audit: WCAG 1.4.*, 2026-09-12

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.

Verdict and ratings

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.

Ratings are analyst judgments, not conformance scores. Defect behavior marked measured 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.

RuleSCRatingEvidence / scope
link-in-text-block1.4.1OVER-FLAGSDescendant 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
audio-control1.4.2SOLIDCorrect 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.
color-contrast1.4.3OVER-FLAGSMeasured 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
control-contrast1.4.3OVER-FLAGSThree independent measured false violations: background image, group opacity, text fill. src/engine/rules/wcag/1.4.3-control-contrast.js:62,88,97
meta-viewport1.4.4OVER-FLAGSDefinitive failure ignores an available G178 text-resize mechanism. src/engine/rules/wcag/1.4.4-meta-viewport.js:19
color-contrast-enhanced1.4.6OVER-FLAGSFactory 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.
reflow1.4.10MINOR-GAPSReview-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.
non-text-contrast1.4.11UNDER-TESTSSnug 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.
text-spacing1.4.12UNDER-TESTSShadow 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.

Tally: OVER-FLAGS 5, SOLID 1, MINOR-GAPS 1, UNDER-TESTS 2, WEAK 0.

Measured defects and original reproductions

All basic field cases use font:16px Arial; width:400px; height:48px; border:0; padding:10px; box-sizing:border-box. 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.

V1. Opaque field colour hides an actually painted gradient from the control rule

Measured false violation. 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.

<input value="A readable field"
 style="font:16px Arial;color:white;background-color:white;background-image:linear-gradient(black,black)">

Observed verdict: fail, ratio 1, required 4.5. 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. SC 1.4.3.

V2. Control group opacity composites only the foreground

Measured false violation. src/engine/rules/wcag/1.4.3-control-contrast.js:88. In contrast, main text invokes opacityGroupPaint at 1.4.3-color-contrast.js:620.

<body style="background:#808080">
<input value="A readable field" style="font:16px Arial;color:black;background:white;opacity:.5">

Observed fail, 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 contrast criterion.

V3. Control text fill is ignored

Measured false violation. src/engine/rules/wcag/1.4.3-control-contrast.js:97 always judges style.color; main text already prefers webkitTextFillColor at 1.4.3-color-contrast.js:537.

<input value="A readable field" style="font:16px Arial;color:#eee;-webkit-text-fill-color:black;background:white">

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. SC 1.4.3.

V4. Semi-transparent images are declared wholly invisible

Measured false pass. src/engine/lib/contrast.js:1270 skips alpha bytes below 128. At :1284 a complete absence of pixels *above this threshold* becomes {transparent:true}, and the URL enters the persistent transparentImages set. Yet nonzero alpha paints.

const canvas = document.createElement('canvas');
canvas.width = canvas.height = 64;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgba(0,0,0,.49)';
ctx.fillRect(0,0,64,64);
p.style.cssText = 'color:#555;background-color:white;font:16px Arial;min-height:64px';
p.style.backgroundImage = `url("${canvas.toDataURL()}")`;
p.textContent = 'Grey text on white darkened by a translucent image.';

imageLuminanceRange returns {transparent:true,width:64,height:64}. 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. Contrast criterion.

V5. Own-element CSS filter is absent from the contrast pair

Measured false violation. 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 style="font:16px Arial;color:#777;background:white;filter:invert(1)">
Visible grey on black after inversion.
</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). SC 1.4.3.

V6. Unpainted base colour is retained when first-line styling covers all text

Measured false violation. src/engine/rules/wcag/1.4.3-color-contrast.js:592 and :962 include the base colour in the worst candidate unconditionally.

<style>
p {font:16px Arial;width:450px;color:#aaa;background:white}
p::first-line {color:black}
</style>
<p>A single black first line.</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. Contrast criterion.

V7. Link colour is read from its wrapper instead of the text-bearing descendant

Measured false violation. 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 style="color:black;background:white">Read the
<a href="#" style="color:blue;text-decoration:none"><span style="color:black">account details</span></a>
before continuing.</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. Use of Color, scope note.

Related measured false pass at :179: an own background is accepted as a shape even when it exactly matches surrounding white. <p style="color:#444;background:white">Read the <a style="color:#555;background:white;text-decoration:none">account details</a> before continuing.</p> passes. The white fill paints no distinction; only a sub-3:1 colour difference remains. Current G183 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.

V8. Viewport rule disregards an available text enlargement control

Measured verdict and text resize behavior; normative classification follows G178. 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.

Observed rule fail. Four clicks produce [32px,32px,32px] from [16px,16px,16px], 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. G178 sufficient technique.

V9. Wrapper borders bypass non-text contrast measurement

Measured false pass relative to the rule's claimed field-boundary review scope. src/engine/rules/wcag/1.4.11-non-text-contrast.js:117. In pour-visual-repro-final.mjs the snug wrapper has border:1px solid #808080 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. Non-text Contrast.

V10. Spacing probe can report pass without applying shadow-root styles

Measured false pass. 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:

<style>p{font:16px/16px Arial;letter-spacing:normal;word-spacing:normal;
width:180px;height:16px;overflow:hidden;margin:0}</style>
<p>A short text sample</p>

Engine evaluateAll([p]) 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. Text Spacing.

Other source-verified limits and policy distinctions

  • 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.
  • 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.
  • Reflow's at 320px ... will be worse (: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.
  • 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.
  • 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.

Performance findings: suspected costs, not measured bottlenecks

No full-site timing or large synthetic performance run was performed here while parent verification was active.

  • imageRangeCache at lib/contrast.js:1209 and transparentImages 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.
  • 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.
  • Pseudo, media, panel and opacity caches are valuable. Nevertheless pseudoBackdropForText scans the ancestor chain per text, backdropHazard 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.
  • Text spacing intentionally forces layout twice. Its threshold bounds costs but currently hides the abstention. Prefer transparent budgeting over pass fabrication.

Manual 1.4 criteria: useful opportunities and dead ends

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.

SCUseful optional automationWhy it cannot assert full conformance
1.4.5 Images of TextOCR local accessible images; identify text-like bitmap candidates for review, compare with visible adjacent text.Logos, essential presentations, customizable images and substantial visual context need meaning judgments. Alt text alone is not proof of image-of-text misuse.
1.4.7 Low or No Background AudioIdentify prerecorded audio and collect available separate speech/background stems; ask for review.Mixed waveform amplitude does not distinguish speech from background or establish exceptions. Reliable separated track evidence is uncommon.
1.4.8 Visual PresentationMeasure long prose width, justification, line/paragraph spacing and provide targets for reviewing adjustment mechanisms.A nonpreferred default does not fail where a suitable user mechanism exists. Avoid reporting every justified paragraph as a violation.
1.4.9 Images of Text, No ExceptionSame local OCR candidate discovery; separate decorative/essential review questions.OCR cannot decide essential or decorative role. A blanket image selector creates mostly noise.
1.4.13 Content on Hover or FocusIn a controlled runner, hover/focus known tooltip triggers, test Escape, pointer transfer and persistence.Additional content can be UA-native, non-obscuring or have application-specific interactions. DOM attribute guesses cannot establish the behavior.

Prioritize fixing measured false assertions and reporting skips before extending new heuristic scope. All scratch results remain local for incorporation into the parent report.

Rendered examples

White readable field text on a black gradient background
Baseline engine asserted 1:1. The actual field paints white text on black.
Grey text on a grey background produced by a translucent black image
Baseline engine passed. The painted text/background pair is approximately 1.94:1.