Skip to content

Bump @surea11y/core from 1.6.0 to 1.7.0 - #5

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/surea11y/core-1.7.0
Open

dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/surea11y/core-1.7.0

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Sep 1, 2026

Copy link
Copy Markdown
Contributor

Bumps @surea11y/core from 1.6.0 to 1.7.0.

Release notes

Sourced from @​surea11y/core's releases.

v1.7.0 — Security fix, EARL output & a stability contract

Minor release. A security fix in the cross-frame responder that affects every earlier version, EARL as a first-class output format, a written contract for the extension points and finding identity, and a package a third smaller. Additive to the output — engine.schemaVersion stays 1.0.0.

Security — upgrade if you use the frame responder

a11yCoreEnableFrameResponder() answered a scan request from any window that could reach it, not only the frame embedding it. A sibling frame obtained through parent.frames[i], or an opener, could ask for a scan and receive occurrences[].html — DOM content the same-origin policy gives it no way to read. A run command is now answered only when its sender is the direct parent, and a reply is accepted only from the window the request was actually sent to.

Affects every published version before 1.7.0, and only consumers that call a11yCoreEnableFrameResponder() in a framed page. The automation-driver patterns — @surea11y/playwright, @surea11y/puppeteer, the CLI — never use the responder and were never exposed. GHSA-ph4m-g9wf-96h6, medium.

Check your CI gate

Several rules that reported fail now report cantTell. A gate on fail stops gating on them, so a build that was red may go green. Nothing new fails: all three new rules are capped at cantTell.

  • Six ARIA rules where the violation leaves the exposed name, role and value intact — aria-valid-attr, aria-allowed-role, aria-braille-equivalent, aria-conditional-attr, plus per-finding grading in aria-required-attr and aria-roles-valid. ACT maps five of them to WAI-ARIA author requirements rather than to WCAG, and the engine was asserting a Level A failure on all of them. Every finding is still reported, with the same occurrences.
  • aria-required-children no longer fails a container for being empty. An empty role="list" is announced as a list with no items, which is what it is. Whether the content a container does own is valid stays aria-prohibited-children's decision, and that rule still fails.
  • duplicate-id under the default 2.2 target, since SC 4.1.1 was dropped in WCAG 2.2. It still runs and still reports every duplicate — one breaks <label for> and fragment navigation whatever the standard says — but comes back cantTell with a wcagVersionScope field. Target 2.0 or 2.1 for the real failure.

aria-allowed-role also gives up its WCAG mapping: ARIA-in-HTML's permitted-roles table is an author requirement of that specification, so claiming SC 4.1.2 at Level A overstated every finding. It carries best-practice now, and a run filtered on tags: ['wcag2a'] no longer selects it.

EARL output

@surea11y/core/earl renders results as an EARL 1.0 report in JSON-LD — the W3C vocabulary for stating what a tool tested and what it found, and the format the ACT Rules community group accepts as an implementation report. Unlike the SARIF and HTML reporters, which carry violations only, every rule that ran becomes an assertion: pass and inapplicable are what distinguish "checked and found nothing to check" from "does not implement that rule". Output is deterministic, so a diff between two engine versions means something. See docs/EARL.md.

What is now under semver

The public surface was previously whatever the build happened to emit — 19 symbols, one of which handed out an engine internal. docs/API_STABILITY.md now names the supported set and lists the rest as exported-but-internal, with a test that fails when a new export appears unclassified.

Also newly contractual: the customRules descriptor, the one real plugin API, which was documented in full but promised nothing; overriddenBuiltinIds; and rule ids and reason codes, which feed the baseline fingerprint and the SARIF partialFingerprints GitHub Code Scanning tracks alerts by — either changing silently unsuppresses every baselined finding.

A cantTell occurrence can now say why: uncertainty.code from a closed vocabulary, with needed naming what would settle the question and evidence recording what the rule did establish.

New rules

  • password-paste-enabled — an authentication field carrying an inline paste handler. First coverage of WCAG 3.3.8; a password manager, or the clipboard for a one-time code, is the mechanism the criterion asks for.
  • identical-iframes-same-purpose — frames sharing an accessible name that embed different resources, implementing ACT 4b1c6c. Reported cantTell, never fail: differently worded copies of a page are exactly what an equivalent case looks like.
  • landmark-complementary-is-top-level — completes the family that already covered banner, contentinfo and main.

Performance and size

  • excludeSelectors cost a multiple of the whole scan: the ancestor walk ran once per selector per rule, for all 130. Excluding 25 elements — an ordinary list for a real site — turned a 2.3s scan into 43s. Results are memoized per element now, and the same page takes 3.1s.
  • The browser bundle is minified: 1430 KB to 706 KB, and 294 KB to 165 KB over the wire.
  • runa11yCoreAcrossFrames no longer carries a second copy of the rule catalog. The published package drops from 7.7 MB to 5.3 MB unpacked.

Also in this release

engineOptions.wcagVersion sets which version of WCAG a scan tests against. @surea11y/core/i18n/<locale> resolves locale side files by path, which a binding injecting the standalone bundle needs. Rules that could not evaluate a candidate no longer report pass for it. link-in-text-block reads the author stylesheets rather than a computed text-decoration jsdom does not cascade. Contrast rules now find text assigned straight to a shadow root. SARIF carries "could not check" as an execution notice rather than dropping it.

avoid-inline-spacing no longer fails text that cannot wrap — the engine's one false positive across the 798 ACT test cases, and a false positive is the one thing that blocks an ACT implementation report.

Full details in CHANGELOG.md.

Changelog

Sourced from @​surea11y/core's changelog.

[1.7.0] - 2026-08-29

Added

  • @surea11y/core/earl renders scan results as an EARL 1.0 report in JSON-LD — the W3C vocabulary for stating what a tool tested and what it found, and the format the ACT Rules community group accepts as an implementation report. renderEarlReport(results, { assertor, mode }) takes an array as readily as one result, groups the graph by TestSubject as the ACT context requires, and sorts subjects by source and assertions by rule id so the same inputs give byte-identical output and a diff between engine versions means something. Unlike the SARIF and HTML reporters, which carry violations only, every rule that ran becomes an assertion: pass and inapplicable are what distinguish "checked and found nothing to check" from "does not implement that rule". Success Criteria come out as WCAG2:<criterion-id> derived from the criterion's own title, reading only the mappings that state a conformance level, since normativeMappings also carries Understanding-document references and other standards under the same standard: "WCAG". A rule mapping to no criterion omits isPartOf rather than asserting an empty list. See docs/EARL.md.
  • The engine's extension boundary is declared. src/index.js re-exports the generated core verbatim, so the public surface was whatever the build happened to emit — 19 symbols, of which the six first-party consumers use two, and one of which (__internal) hands out an engine internal. docs/API_STABILITY.md now names the supported set (runa11yCoreInPage, runDomRulesInPage, runa11yCoreAcrossFrames, a11yCoreEnableFrameResponder, getChecksCatalog, getRulesCatalog) and lists the rest as exported-but-internal, free to change in a minor. The classification lives in scripts/data/public-api.json and tests/public-api.test.js fails when a new export appears unclassified, so a leak has to be a decision. Nothing is removed: that is a candidate for the next major, and no consumer needs it yet.
  • The custom-rule descriptor is covered by semver. engineOptions.customRules was documented in full but appeared nowhere in the stability contract, so the one real plugin API carried no promise. id, meta, runInPage(ctx), the optional applicability(ctx) and data, the ctx.helpers a rule receives, the result it returns, and the function-or-source-string form a binding needs to cross a realm boundary are all stable now. engineOptions.policyContract/policy and the reporter entry points are documented as the other two extension points.
  • overriddenBuiltinIds joins the stable top-level result fields. It is always emitted and fully documented in OUTPUT_SCHEMA.md, but was missing from the stable list despite being how a consumer detects a customRules entry shadowing a built-in id.
  • Rule ids and reason codes are now a documented contract, inventoried in scripts/data/finding-ids.json and enforced by tests/finding-ids.test.js. Both feed the finding fingerprint — computeBaselineKey(ruleId, reasonCode, html), which backs both stored baselines and the SARIF partialFingerprints GitHub Code Scanning tracks alerts by — so either one changing silently unsuppresses every baselined finding and makes every open alert close and reopen as new. data.details.reasonCode moves out of API_STABILITY.md's explicitly-unstable list into the stable set, as a deliberate exception to the rest of data.details: a rule may gain a code in a minor release, but a shipped one does not change, and a published rule id is removed or renamed only through a deprecated/replacedBy entry. Regenerate with npm run finding-ids. The build already rejected a renamed rule that a composite references; the inventory covers the rules no composite mentions, which it did not. See docs/API_STABILITY.md.
  • A cantTell occurrence can now say why it could not be decided. occurrences[i].uncertainty carries a code from a closed vocabulary — not-computable, runtime-dependent, spec-only, equivalence-unknown, judgement-required, out-of-scope — alongside needed, one sentence naming what would settle the question, and evidence, what the rule did establish so a reviewer starts from the engine's work rather than repeating it. The reason for a cantTell was previously only in data.details.reasonCode, which is per-rule, free-form and documented as not a stable contract, so nothing could branch on it. The field is present only on a cantTell-tier occurrence: on a fail-tier one it would claim the rule both decided and did not, and the engine drops it. Every automatic rule that can report cantTell carries it — the ARIA family that this release regraded reports spec-only, the contrast and CSS rules that cannot read their inputs report not-computable, and the target-size and label rules report judgement-required or equivalence-unknown. The engine attaches out-of-scope itself to the occurrences behind a wcagVersionScope coercion. A test holds the line so a new rule cannot report cantTell without saying why, and validate:rules rejects a code outside the vocabulary. Manual rules do not carry it, since judgement-required is what type: "manual" already means. Purely additive, so no schemaVersion bump. See docs/OUTPUT_SCHEMA.md.
  • occurrences[i].occurrenceOutcome is documented. It has been in the output since rules began grading findings into a confident fail tier and a needs-review cantTell tier, but OUTPUT_SCHEMA.md never described it, so the reason a fail result can carry cantTell occurrences was undocumented. No behaviour change.
  • engineOptions.wcagVersion ('2.0', '2.1' or '2.2') sets which version of WCAG a scan is conformance-testing against. It defaults to whatever your version-origin tags imply, and to '2.2' when they imply nothing, and the resolved target comes back on every result as engine.wcagVersion. See docs/ENGINE_OPTIONS.md.
  • docs/RULE_HELPERS.md: reference for every function on ctx.helpers available to a rule's runInPage — around 35 flat helpers plus the contrast.*/aria.* namespaces. docs/RULE_AUTHORING.md §6 previously named only a dozen of them inline; the rest were discoverable only by reading src/core/dom-helpers.js directly. §6 now points here instead.
  • password-paste-enabled raises an authentication field carrying an inline paste handler for review, the first coverage of WCAG 3.3.8. A password manager, or the clipboard for a one-time code, is the mechanism the criterion asks for, and blocking paste removes it. Advisory and capped at cantTell: whether a handler really stops the user depends on script the markup does not carry, so the two cases are reported apart rather than decided — one that only cancels, and one that goes further and may be re-inserting the text. In scope are current-password, new-password and one-time-code fields, plus <input type="password"> unless its autocomplete names another purpose.
  • landmark-complementary-is-top-level reports a complementary landmark nested inside another landmark, completing a family that already covered banner, contentinfo and main. Advisory and capped at cantTell, like its siblings. An unnamed <aside> inside sectioning content is not reported, since HTML-AAM leaves it no complementary role to nest.
  • @surea11y/core/i18n/<locale> resolves each locale side file by path, alongside the existing @surea11y/core/browser. A binding that injects the standalone bundle into a page needs the matching dictionary to honour engineOptions.locale, and the exports map previously put both out of reach. An unshipped locale fails to resolve rather than resolving to nothing, so a caller can tell the difference and fall back. See docs/BINDING_AUTHORS_GUIDE.md.
  • identical-iframes-same-purpose checks that <iframe>/<frame> elements sharing an accessible name embed the same resource, implementing ACT rule 4b1c6c. It sits alongside iframe-title-unique, which asks the stricter and different question of whether the title attribute repeats at all: this one keys on the computed accessible name, counts only frames included in the accessibility tree, and judges the resource behind the name. src values are compared as resolved absolute URLs with the fragment removed and a trailing slash normalised away, so one directory written both ways is a single resource. Frames resolving to different URLs are reported cantTell, never fail: different resources can still be equivalent — differently worded copies of a page, or two adverts serving the same purpose — and neither the markup nor the embedded documents settle it, since content differing is exactly what those equivalent cases look like.

Changed

  • A rule mapped only to a Success Criterion the target WCAG version removed can no longer report fail. Under the default 2.2 target that means duplicate-id: it still runs and still reports every duplicate it finds, but comes back cantTell with a wcagVersionScope field naming the criterion 2.2 dropped, so a default scan is never gated by SC 4.1.1. Coercing rather than excluding keeps the defect visible, since a duplicate id still breaks <label for>, fragment navigation and getElementById whatever the standard says. Target 2.0 or 2.1 for the real failure, or keep excluding the rule outright with excludeTags: ['wcag22-removed'].
  • aria-controls pointing at an id no element has is no longer a failure of aria-valid-attr-value. The menu, listbox or panel it names is routinely built when the widget opens, so a static scan that cannot find it has not established a defect. A collapsed element (aria-expanded="false" or aria-selected="false") passes outright, since the absence is what that state means; anything else is reported as cantTell for review. Every other ID-reference attribute is unchanged: a dangling aria-labelledby or aria-owns names content that was supposed to be there already.
  • The standalone browser bundle and its locale side files are minified. surea11y.browser.js goes from 1430 KB to 706 KB, and from 294 KB to 165 KB over the wire. Most of a page's download was the 130 inlined rule bodies, and most of those were their own comments. The global, the API and the result are unchanged; the readable form of every rule remains its own module under src/checks.
  • runa11yCoreAcrossFrames scans its own frame through runa11yCoreInPage instead of carrying a second copy of the rule catalog and the shared runner block. The generated src/core.js drops from 4.34 MB to 2.67 MB, and the published package from 7.7 MB to 5.3 MB unpacked. Both functions stay usable the bundler-free way they always were — raw source injected into a page — since runa11yCoreInPage is itself self-contained and free of require().
  • The composite rollup moved out of runCore into its own function, rollupCompositeResults, in src/core/dom-runner.js. Results are unchanged. It was a long inline block nothing else could reach; splitting it keeps runCore readable and lets the rollup be called on its own. Internal only, not part of the package's public exports.
  • aria-required-children, aria-prohibited-children and aria-required-parent map to SC 1.3.1 Info and Relationships instead of 4.1.2 Name, Role, Value, and carry wcag131 in place of wcag412. The ACT rules these three implement, bc4a75 and ff89c9, name 1.3.1 as their only requirement, and it is the criterion the finding actually describes: a role="listitem" outside any list, or a role="list" owning no item, misstates the structure exposed to assistive technology rather than the name, role or value of a control. It also puts them beside the native-HTML checks that ask the same question, listitem-parent-valid and list-children-valid, which were already 1.3.1. Level A either way, and all three still run on a default scan and report exactly what they reported before; what moves is which composite the verdict lands in, wcag-1.3.1-info-and-relationships gaining the three and wcag-4.1.2-aria-validity losing them, and what a tag-filtered run selects, since tags: ['wcag412'] no longer picks them up. Their coverage facets move to 1.3.1 with them.
  • aria-hidden-body and aria-role-name-present carry the aria tag the rest of the family already had. Both are entirely about ARIA usage — aria-hidden on the document body, and the roles WAI-ARIA requires an accessible name for — so a run filtered on tags: ['aria'] was silently missing two of them. No other tag changes, and no rule changes what it evaluates or reports.
  • aria-required-parent honours aria-busy="true" on an ancestor, WAI-ARIA's own escape hatch for a widget script has not finished assembling. aria-required-children and aria-prohibited-children already read it off the container they check; from the item's side the marked element is an ancestor, so the walk looks up rather than at the element itself, and only the exact string "true" counts. It also outranks the roleless-generic-parent rule, since aria-busy is a global ARIA attribute and would otherwise block the context search and fail the very markup the spec says to mark. A role="option" inside a container still loading its role="listbox" wrapper is notApplicable now, not a failure.
  • docs/OUTPUT_SCHEMA.md no longer defines fail as a high-confidence outcome. Eight automatic rules ship fail at confidence: "medium", and that was never a contradiction: the outcome describes the decision procedure, which guesses at nothing, while confidence describes the model it decides against — curated WAI-ARIA tables, native-role mappings, an accessibility tree inferred from static markup. The outcome table, the type: "manual" note, POLICY.md, SARIF.md and LIMITATIONS.md all said "deterministic, high-confidence"; they say "deterministic" now, and the confidence section names the rules and explains what medium on a fail means. Documentation only, no behaviour change.
  • aria-required-children no longer fails a container for being empty; it reports cantTell for every finding. The rule asks only whether the required content is present, and a container that owns nothing conveys nothing false — an empty role="list" is announced as a list with no items, which is what it is. Whether the content a container does own is valid is aria-prohibited-children's decision, and that rule still fails, so a role="button" among list items or a role="tablist" of plain buttons is caught exactly as before, at the same criterion. This also settles an inconsistency with the native-HTML rules, which already judge only the children that exist: <ul></ul> passed while <div role="list"></div> failed. One shape loses its failure and is now reported for review instead: a container whose items never got their role, <div role="list"><div>Item</div></div>. Applicability, the aria-busy exemption and aria-owns resolution are unchanged.
  • Six ARIA rules report cantTell where the violation leaves the exposed name, role and value intact, instead of fail. ACT maps five of them to WAI-ARIA author requirements rather than to WCAG, and lists 1.3.1/4.1.2 as "less strict" secondary requirements that an implicit role or a spec-supplied default can still satisfy; the engine was asserting a Level A failure on all of them. Two grade per finding: aria-required-attr fails where ARIA supplies no stand-in (aria-checked on checkbox/radio/switch/menuitem*, aria-valuenow on slider/scrollbar/meter and a focusable separator) and reports cantTell where it does (aria-expanded on combobox, aria-level on heading), the table generated from aria-query's requiredProps by scripts/generate-aria-tables.js so the two tiers cannot drift from the spec; aria-roles-valid fails on a roleless host left exposed as generic and reports cantTell where a native role survives the bad token. Four report cantTell throughout: aria-valid-attr (an undefined attribute is inert), aria-allowed-role (an ARIA-in-HTML author requirement with no ACT rule and no WCAG mapping anywhere), aria-braille-equivalent and aria-conditional-attr, the last two also dropping from serious to moderate. Every finding is still reported with the same occurrences; what changes is that a page whose only ARIA defects are of this kind comes back cantTell on wcag-4.1.2-aria-validity rather than fail, so a CI gate on fail stops gating on them. ACT 674b10, 4e8ab6 and 5f99a7 still run clean (25 cases, 0 mismatches). Reasoning in docs/DESIGN_CHALLENGES.md.
  • aria-allowed-role no longer claims a WCAG Success Criterion. ARIA-in-HTML's permitted-roles table is an author conformance requirement of that specification: no ACT rule covers it, and no source maps it to a criterion, so declaring SC 4.1.2 at Level A overstated every finding it made. It now carries best-practice in place of wcag2a/wcag412, with wcagSc: [], no normativeMappings and no coverage facet, and it has left the wcag-4.1.2-aria-validity composite (13 contributors) and the 4.1.2 facet registry. It still runs on a default scan and still reports the same findings at cantTell; what changes is that a run filtered on tags: ['wcag2a'] or ['wcag412'] no longer selects it, and a page whose only ARIA-in-HTML nit is this one now reaches pass on that composite instead of cantTell. This is the first automatic rule in the engine with no WCAG mapping, alongside the 25 manual best-practice rules that already had none.
  • docs/RULE_TAXONOMY.md §1.1 no longer says an automatic rule may use cantTell only as a defensive fallback, "never as its primary intended path". Six rules now lead with it. The dividing line between automatic and manual is whether a rule can decide, not which outcome it reports: an automatic rule reporting cantTell has decided and is saying what it found, while a manual rule reports cantTell because the question is not decidable from markup. The three cases where cantTell is an automatic rule's primary path are listed there.

Fixed

  • A frame responder answered any window that could reach it, not just the frame embedding it. a11yCoreEnableFrameResponder() is documented as opting a frame in to being scanned from above, but the listener ran a scan for any sender: a sibling frame can obtain a reference through parent.frames[i] and postMessage across origins, and the reply carries occurrences[].html — DOM content the same-origin policy gives that sibling no way to read. An opener could do the same. A run command is now answered only when its sender is the direct parent, which the hop-by-hop relay makes the only legitimate case, and a window nothing embeds answers nobody. Affects every published version before 1.7.0, and only consumers that call a11yCoreEnableFrameResponder() in a framed page — the automation-driver patterns (@surea11y/playwright, @surea11y/puppeteer, the CLI) never use the responder and were never exposed.
  • A reply could be accepted from a window the request never went to. Any window naming an in-flight requestId could settle it, forging a frame's scan result or its failure. Each pending request now records the window it was sent to and ignores answers from anywhere else, pings included — reachability is the addressed frame's verdict to give.
  • engineOptions.excludeSelectors cost a multiple of the whole scan. Every rule queries through isExcluded, which walked an element's ancestor chain once per selector with nothing remembered between rules, so the work was repeated for all 130 of them: excluding a cookie banner and four ad slots turned a 2.3s scan of a 3574-element page into 10.5s, and 25 exclusions — an ordinary list for a real site — into 43s. Exclusion results are now memoized per element for the run, partitioned by the effective exclude list exactly as the selector cache already is, and the self-test uses matches against the element rather than closest against the chain, since a parent's answer already settles its descendants. The same page with 25 exclusions takes 3.1s, and the raw matching floor is 18ms against the 5588ms an equivalent scan used to spend. Rule-scoped rules[ruleId].excludeSelectors keep their own cache partition, and a malformed selector still excludes nothing without disabling the usable selectors beside it. perfStats gains excluded.hit/excluded.miss.
  • A rule that could not check anything said so, and SARIF dropped it. The contrast rules attach an occurrence to their notApplicable result naming the eligible text count and pointing at contrast-computable; the HTML report showed it, SARIF did not, so a CI pipeline reading only SARIF saw no contrast alerts and had no way to tell a clean page from one where contrast was never computable. Those occurrences now reach SARIF as note-level entries in runs[0].invocations[0].toolExecutionNotices, each carrying associatedRule.id. They are deliberately not results: every SARIF result renders as an alert, and "not evaluated" is not one, so nothing new gates a build or appears in Code Scanning. The block is emitted only when there is something to say.
  • OUTPUT_SCHEMA.md, SARIF.md and EARL.md all stated that a notApplicable result carries no occurrences. It can: a rule that had nothing to judge may attach one occurrence saying why, and the contrast rules do exactly that when no text had a computable background — the message names the eligible text count and points at contrast-computable. A consumer that took the documented shape at face value read occurrences.length as a violation count and got one for a rule that flagged nothing. The behaviour is deliberate and unchanged; the three documents now describe it, OUTPUT_SCHEMA.md notes that such an occurrence carries an empty selector because it describes the scan rather than an element, and SARIF.md states that SARIF omits it — a SARIF consumer treats every result as an alert, so a pipeline reading only SARIF cannot tell "checked, nothing to flag" from "could not check" and needs checksResults or the HTML report for that distinction.
  • OUTPUT_SCHEMA.md claimed without qualification that the engine verifies a reported selector resolves to the element it names. page-title-present is the exception, reporting head > title and an html of <title>(missing)</title> for a page that has neither, since its finding is the absence itself. Both are constants and the baseline fingerprint they feed stays stable; the field now says so rather than promising a resolvable selector.
  • LIMITATIONS.md now records that scan time under jsdom grows with the square of DOM depth. jsdom resolves inherited CSS by walking the ancestor chain on every getComputedStyle call, so 4000 elements in one chain cost 8.3s of getComputedStyle alone against 0.25s for the same 4000 as siblings, with no engine code involved. The engine's own walks are capped and stay linear. A real browser computes inherited style natively and does not have this shape.
  • buildStructuralPath was the one ancestor walk here with no bound. A consistent tree ends it when an element is not found among its parent's children, but a parent chain that cycles while still reporting itself as each other's child never terminates. It now gives up past a depth no real document reaches and returns null, matching what the field already documents for a path it cannot determine.
  • avoid-inline-spacing no longer fails text that cannot wrap. ACT 78fd32/24afc2/9e45ec apply only to text containing a soft wrap break, and running the official corpus turned this up as the engine's one false positive across 798 cases: a fixed-width paragraph inside a horizontally scrolling container, which never wraps however the viewport changes, was reported as a violation. Layout would settle whether text wraps and a static scan cannot, but two shapes do establish that no wrap is possible — text not allowed to wrap, and a fixed-width element inside a horizontally scrolling ancestor — and those now report cantTell with the not-computable uncertainty code and a new INLINE_SPACING_NO_SOFT_WRAP reason code. Everything else is still treated as wrapping, so an ordinary forced value below the metric fails exactly as before. A false positive is the one thing that blocks an ACT implementation report, which is why this is graded rather than left as the documented gap it had been.
  • link-in-text-block, avoid-inline-spacing and css-orientation-lock reported pass for candidates they never evaluated. Each counted an element as applicable, met a condition it could not resolve, skipped it, and fell through to pass — the same clean result whether the element was checked and found sound or never checked at all. They report cantTell for those candidates now, the computability gate RULE_TAXONOMY.md §1.1 already allows automatic rules. A proven failure still outranks an undecided candidate, so a page with both fails as before. Concretely: link-in-text-block skipped a link whose contrast against the surrounding text was not computable, avoid-inline-spacing an !important spacing value that resolved to no ratio, and css-orientation-lock every cross-origin stylesheet — so a page with one readable stylesheet and three unreadable ones asserted no orientation lock existed.
  • link-in-text-block treated every link as underlined outside a real browser. It read text-decoration-line and the text-decoration shorthand as one set of tokens, which only holds where the two agree. jsdom does not cascade the property: the shorthand reads back as the user agent's underline for every <a> whatever the author stylesheet says, and the longhand as none unless the author wrote the longhand themselves, so text-decoration: underline and text-decoration: none are indistinguishable in the computed style. Reading the longhand alone inverts the error into a false failure on correctly underlined links. A conforming CSSOM serialises the shorthand with the line value first, so disagreement between the two is now the signal to distrust both, and the rule resolves the declaration from the author stylesheets instead, as css-orientation-lock and css-focus-indicator-suppressed already read the CSSOM. :hover and other state rules are excluded, since they do not describe the link's resting appearance, and inline style outranks the stylesheets.
  • link-in-text-block now tests the cues that do not depend on text-decoration first, so a link distinguished by font weight, font style or sufficient contrast is decided even where decoration cannot be read.
  • contrast-minimum, contrast-enhanced and contrast-computable skipped text assigned straight to a shadow root. A text node whose parent is the shadow root itself has no parent element, and the scan resolved colors from the parent element alone, so shadowRoot.textContent = 'Some text' was walked and then dropped with no candidate recorded — a component rendering its text that way was reported notApplicable rather than checked. The host carries the inherited color and background that text renders with, so it is what the scan attributes the text to now. Text inside an element within a shadow root was always found and is unchanged.
Commits
  • 0c2b85e Run lint as part of npm test
  • 23e4289 Drop a redundant initializer in the frame responder's embedder lookup
  • 88bdba5 Release 1.7.0
  • b5c435d Note the versions the frame responder issue affects
  • 8a4be0f Carry a rule's "could not check" into SARIF as an execution notice
  • bf6adf9 Answer only the frame that embeds this one
  • 6fe88dc Correct what the output contract says about occurrences and selectors
  • 0a98143 Memoize element exclusion and bound the structural-path walk
  • 0af46d9 Cover the no-soft-wrap branch in the avoid-inline-spacing fixture
  • fbf6993 Stop failing text that cannot take a soft wrap break
  • Additional commits viewable in compare view

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Bumps [@surea11y/core](https://github.com/SureA11y/core) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/SureA11y/core/releases)
- [Changelog](https://github.com/SureA11y/core/blob/main/CHANGELOG.md)
- [Commits](SureA11y/core@v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: "@surea11y/core"
  dependency-version: 1.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Sep 1, 2026
@dependabot
dependabot Bot requested a review from rumoroso as a code owner September 1, 2026 17:50
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants