Repository navigation
low-contrast: a stepper's resting state is advisory, a parked reveal is not at rest - #1148
Conversation
Text dimmed by a stepper's inactive state (a box under 0.5 opacity with an opacity transition beside a sibling of its component at 1) reports as advisory, unless the dimmed box or the text's own box is a control (taste call r13-t1; arc.net). A box a script parked in its style attribute at the start of a reveal is not at rest: opacity with a transform and will-change (framer.com), or under 0.5 with an opacity transition and either a var() opacity (ramp.com) or the same component revealed elsewhere on the page (raycast.com). Co-Authored-By: Claude Code <noreply@anthropic.com>
The reveal arm stands down for a control and needs a set of boxes parked alike before a lit namesake elsewhere counts; the scrubbed arm needs the variable inside a math function or set in a style attribute, so an inline design token keeps its finding. A state needs the bright sibling to carry the dimmed box's classes plus exactly one, and a box that sets cursor: pointer around a short label counts as a control (the capture now records cursor). The review's probes are should-flag rows in the fixture. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Nothing to fix. About this review
Reviewed 1 file on 23 Oct 2026, 22:05 UTC · Run page Reviews (10) · Last reviewed commit: Merge origin/main into corpus/round13-resting |
[Medium impact] The change reaches browser-based low-contrast findings across scanned pages. Fix the shadow-tree control check before merging so slotted button labels keep their contrast warnings.
|
The pixel pass marks a stepper item dimmed by its state as advisory, like the element pass. A box that holds a clickable child with a short label and no role is not parked for a reveal. The inline style reader takes the declaration that wins (last, unless an earlier one is !important) and matches custom property names case-sensitively. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
The colour check now treats text as decorative when it is dimmed by a resting state (this branch) or sits under a box labelled as a screenshot (main); both contract entries kept; bundle regenerated. Co-Authored-By: Claude Code <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 30b0e03. Configure here.
…ire a shown lit peer A disclosure's summary and a menuitemcheckbox keep their verdict like the other controls, before the state and reveal exceptions. A revealed peer on the page counts only when it is painted and no ancestor fades it. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
A stepper item's active sibling that is display: none or hidden shows no state a reader meets, so the dimmed item keeps its warning. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Takes in #1155; bundle regenerated. Co-Authored-By: Claude Code <noreply@anthropic.com>
Main moved again while the previous merge was verified: release 4.7.1 and engine 0.1.17 (#1159, #1158), the row-stripe side tab and its follow-ups (#1133, #1156), the stepper resting state (#1148), the corpus round 11 deferred edge cases (#1155), the contrast grounds work that drops unmeasured handed verdicts (#1147), and the generated provider sync. No source conflicts; the in-page bundle conflicted as a generated file and was regenerated from the merged source with `cargo xtask bundle`. The sweep goldens stay main's as they stand: main carries its newest fixtures' entries in them as accepted deltas in DELTAS.md rather than re-recording, and this branch's sweep entries are covered by its existing DELTAS entries the same way, as in the previous merge. Prepared with AI assistance (Claude Code). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Round 13 of the detector corpus, branch E:
low-contraston text a visitor sees in another state. Under taste call r13-t1, a stepper item dimmed beside the active one is advisory; text a script parked at the start of a reveal is not at rest and is scored at its declared colour.What the row was
On the craft run (1009),
low-contrastscored four shapes of dimmed text as if the page sat that way:article.featuresits at opacity 0.3 by stylesheet withtransition: opacity .3s, and the active one,article.feature.is-active, is at 1. The visitor scrolls the active slot along, so each item is read lit. 21 findings, all marked false positive.opacity: max(.2, var(--card-animation-1)), a value a scroll script scrubs from 0.2 upward. The box is a sole child. 4 findings, all false positive.opacity: 0.2with an opacity transition; the same component further down the page carries one more class (glowing) at 1. 10 findings in the pixel pass.will-change: transform; opacity: 0.2; transform: translateX(-19.99px). 2 findings in the pixel pass.The fix
All in the shared element colour pass,
crates/core/src/browser/element_checks.rs.A state (arc, advisory).
dimmed_by_a_state: a box between the text and its surface below opacity 0.5, with an opacity transition, beside a same-tag sibling at opacity 1 whose class list is the dimmed box's plus exactly one token. The finding keeps its place and becomes advisory, like the decorative path. The pixel pass marks such an item's candidate advisory the same way, so a stepper over a photo stays advisory there too. It stands down when the dimmed box is a control or the text reaches it through one: button, field, tab,a[href],aria-selected, link/option/menu/tree roles, and a short box whose computedcursorispointerwhere its parent's is not.A reveal (ramp, raycast, framer; not at rest).
parked_inline_for_a_reveal, read where the pass decides whether a fade is at rest. The box needs an inlineopacity, and one of:will-changenaming opacity or a transform. All four signals together; a stylesheet value, opacity alone, or no displacement stays at rest.var(insidemax(,min(,clamp(orcalc(, or a custom property set in astyleattribute on the box or an ancestor.The ramp and raycast arms stand down when the box is a control, sits in one or holds one, including a clickable child with a short label and no role. The
styleattribute is read for the declaration that wins (the last, unless an earlier one is!important), and custom property names match case-sensitively.An inline-parked box with a bright sibling is a state, not a reveal: it goes advisory, never dropped.
The snapshot capture now reads computed
cursor(STYLE_PROPS,docs/CLI-CONTRACT.md). Stored corpus captures predate that and read it as empty, so the replay cannot exercise the cursor guard; the fixture and browser test do.The static engine reads no transition, transform or
will-change, so its output does not change. The new fixture's static goldens pin that.Accepted as advisory
Paul accepted two shapes that match arc's exactly and become advisory with it: a checkout stepper's future steps beside the current one, and a footer list whose current item is at 1 and the rest at 0.4 with a hover transition. Nothing in a snapshot separates "the visitor moves the active slot" from "this is how the page sits". The fixture pins the checkout stepper as advisory. Footer links all faded alike, or list items with no class, still warn.
Run 1009 (craft), against main's replay
Only
low-contrastmoves: 1112 on main, 1108 here. 4 removed, 0 added, 21 now advisory.No legit mark is removed or demoted. arc.net's mobile captures do not move: that grey timeline copy is at full opacity.
framer (10133521-22) and raycast (10134381-84, 10134970-75) are pixel-pass findings, which a replay cannot run. On the stored snapshots both are caught mid reveal, so the pixel pass skips them: raycast's parked keyboard (76 of 152 desktop primaries, which holds every finding rect, and 4 of 4 on mobile) and both framer headings. They will show on the next cloud craft run.
Corpus runs, against main's replay
The violations are main's own (replay against recorded), with identical id sets on both sides, so this branch loses or demotes no finding both judges called harmful. The first version demoted partner.co.il's inactive tab buttons on 1006 (10089911, 10089926, both harmful); the control guard keeps them, and 1006 now matches main.
Hook cost
The new tests sit in the shared element colour pass, which the static HTML engine also runs (
color-rules).rule_cost --runs 9over main'stests/fixtures/antipatternsplus the impeccable.style site source (803 inputs, cold per edit), main (c51e529) and this branch built from their own trees, in the order main, branch, branch, main:No difference above run-to-run noise on a loaded machine. On the hook path the reveal test reads the inline
styleattribute and stops at the transition check, which the static engine never passes (it reads no transitions); the state test runs only for an element that already failed contrast. The text engine is untouched.Tests
tests/fixtures/antipatterns/low-contrast-resting-states.html. Should flag: the arc stepper and a checkout stepper (both advisory), footer links faded alike, tabs with and without a transition, an inline number, a lone parked badge with a lit namesake in another section, parked nav links, an inline design token (opacity: var(--muted)), a faint modifier on the dimmed box, a clickable plan option with no role, identical class lists. Should pass (URL engine): the framer, ramp and raycast shapes.element_checks.rsfor each arm and its twins, andcrates/browser/tests/transient_opacity.rs, which asserts on computed colours and the advisory flag only, so it does not depend on host fonts.detect-fixture-json-low-contrast-resting-states-htmlanddetect-fixture-text-low-contrast-resting-states-html(static engine: fourteen warnings). No existing golden moved; the directory sweeps gain the same findings but are accepted deltas and not compared (DELTAS entry).!important, and custom property case. Runs 1009, 1006, 1004 and 47 re-measured on this head: identical to the numbers above.cargo build --release -p impeccable,cargo xtask bundle --check, node oracle,cargo test --workspace,bun run build(no tracked changes),bun run testwith this binary. All green.AI assistance: prepared by Claude Code, under instructions from @pbakaus.
🤖 Generated with Claude Code
Note
Medium Risk
Changes when low-contrast fires and its severity across element and pixel passes; behavior is nuanced but heavily fixture-tested.
Overview
Refines low-contrast so dimmed text is not always scored as if the page permanently sits that way.
Resting UI state (advisory): Text behind a faded ancestor that has an opacity transition and a same-component sibling at full opacity (e.g. inactive stepper/tab beside the active one) still reports low-contrast but is advisory, with the same treatment in the pixel pass. Controls on the path (tabs, links,
cursor: pointerchips, etc.) keep full severity.Parked reveals (not at rest): Fades driven by inline
styleat animation start are excluded from “at rest” blending—Framer-style transform+will-change, scroll-scrubbedopacity: max(.2, var(...)), and groups of keys parked alike when a lit peer exists elsewhere on the page.Snapshot capture now records computed
cursorso pointer-based control detection works in the browser engine. Fixture, unit, and URL-engine tests cover the matrix.Reviewed by Cursor Bugbot for commit 42bd9dc. Bugbot is set up for automated code reviews on this repo. Configure here.