Close the alert-badge AA failure, extend the token gate, restore the brand teal - #7
Merged
Merged
Conversation
…d teal Issue 1 — .al-badge and .al-row-status.hit painted a literal `color: #fff` beside `background: var(--neg)`/`var(--pos)`, which is 3.16:1 / 2.33:1 in dark and invisible to the token-pair gate. Introduced --neg-fill/--pos-fill (identical to --neg/--pos in light, darkened in dark, same idiom as --teal-btn) and pointed the two badges at them. Extended validate-tokens.ts with findCallsiteContrastViolations, which reads each CSS rule block outside global.css and checks a literal `color` against a same-block `background: var(--token)` in both themes at 4.5:1 — it cannot see JS-driven backgrounds (ui-logo's `bg ?? 'var(--x)'`), color-mix()/gradient fills, or a pairing split across two selectors (a :hover rule that only touches `color`); those are named in the function's doc comment. Issue 2 — restored --teal to its literal brand hex (#20808d), reverting P12's accessibility-driven darkening to #20708d. That darkening fixed --teal-soft/--teal-softer text contrast but dragged the literal brand hex (logo, focus ring, chart stroke) along with it. Split off --teal-text (#20708d in light — P12's already-vetted value; unchanged from --teal in dark, which already cleared 4.5:1) for every consumer that renders actual text: the 8 places named plus ~30 more found by auditing every `var(--teal)` call site — hover-link states, kickers on hero gradients, summary/eyebrow labels, and two color-mix() tint chips — several of which sit on --bg-hover/--bg-subtle/--bg-inset or --teal-softer, not just --teal-soft, and would have newly failed AA had they stayed on the restored brand hex. Added --teal to NON_TEXT_INKS (3:1 vs the five neutral surfaces) and swapped SEMANTIC_PAIRS' teal entries from --teal to --teal-text against both --teal-soft and --teal-softer (the latter pairing was previously ungated). Also fixed LogoChip's fallback avatar (bg ?? 'var(--teal)' with a literal `color: #fff` in ui.css) to use --teal-btn instead — the identical white-on-solid-fill shape already solved for .ui-btn.primary, discovered live-failing in dark (2.96:1) while auditing --teal's consumers. Every new/changed gate was watched to fail with the exact reported ratio, then restored to green; a comment-wording bug in the token parser (a "--teal:" substring inside prose broke the regex) was caught the same way. Verified live in the browser in both themes: the alert badge and hit-status colors, the teal-soft/-softer chip pairs, and the reverted brand hex on the logo mark. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review Minor: the gate's JSON output said "callsiteContrast": "PASS" with no signal that PASS means "no literal-colour-beside-a-single-var-background pair failed" rather than "no callsite contrast problems exist" — the exact failure mode this phase kept hitting, a harness reporting success while structurally unable to ask the question it appears to answer. Added a callsiteContrastScope field next to callsiteContrast, naming the three shapes the check cannot see (JS-set backgrounds, color-mix()/gradient backgrounds, base/:hover-split pairs) in the report a CI reader actually sees, not just in a comment ninety lines up at the function. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the two items P12 shipped with, and the gap that let one of them ship.
A live AA failure the P12 gate could not see
alerts.cssput literal white on semantic backgrounds, and both failed WCAG AA in dark on surfaces a user actually reads:.al-badge(#fffon--neg).al-row-status.hit(#fffon--pos)Fixed with
--pos-fill/--neg-fill— identical to the semantic tokens in light, darkened in dark — following the--teal-btnidiom P12 already established rather than bending--pos/--neg, which are tuned for text on neutral surfaces and are checked against their own pairs.P12's
SEMANTIC_PAIRScheck resolves token-to-token pairs declared inglobal.css. It structurally could not see a literal colour beside avar()background in a feature file, so this shipped green.validate-tokens.tsnow carriesfindCallsiteContrastViolations, which reads each rule block, resolves avar()background and a literalcolor, and computes the pair in both themes.The gate now states what it cannot see
This matters more than the check itself. Four times during P12 a harness reported success while structurally unable to ask the question it appeared to answer — a validator blind to a whole property family, a codemod emitting CSS browsers silently drop, a snap that crushed values by 66% with no literal left to flag, and a 24-combination viewport matrix run in an emulator that reports
vh == svh == lvh == dvh. Each was caught only because a person happened to look.A
LogoChip-shaped failure was live in production while this gate would have printedPASSbeside it. A gate whose output implies more than it checks is worse than no gate, because it converts an unknown into a false assurance. So the scope travels with the answer.Brand colour restored by splitting its roles
P12 darkened
--teal#20808d→#20708das a side effect of an accessibility fix, changing the literal brand hex used by the logo mark, focus rings, links and chart strokes.The cause was one token serving two roles with different requirements: brand identity, governed by WCAG 1.4.11 at 3:1 as a non-text indicator, and text on
--teal-soft/--teal-softer, which needs 4.5:1. The stricter requirement dragged the brand hex.--tealis back to#20808d;--teal-textcarries the text role. Verified:--tealclears 3:1 against all five surfaces in both themes (light 4.02–4.63, dark 5.07–6.30), and--teal-textclears 4.5:1 on both tinted surfaces in both themes (light 4.72/5.07, dark 4.56/5.22). Dark never needed the split —#35a4b2already passed at 4.56/5.22 — which is why only:rootchanged.The audit found ~30 text call sites, not the 8 the task named, and two of those named the wrong background token. Every one of the 62
--teal/--teal-textsites was classified by hand and re-classified independently in review; the ones left on--tealare borders, outlines, chart strokes, icon glyphs, the logo SVG and the large 404 digits.Found while auditing, fixed in passing
LogoChip's fallback avatar rendered#fffat 2.96:1 in dark — live, and invisible to the new gate because its background is set via a JSstyleprop. Now 4.55:1 via--teal-btn. Twocolor-mix()sites in the same family were fixed the same way.Verification
npm run checkexit 0 — 63 files / 284 tests, every gate PASS withliterals: 0.Both gates were watched to fail. Reverting
.al-badgeto--negproducescallsite-contrast: .al-badge — #fff on --neg is 3.16 in dark, needs 4.5; collapsing--teal-textback onto the brand hex producescontrast: light --teal-text on --teal-soft is 3.92, needs 4.5. Both restore clean.Every ratio was recomputed independently in review against the WCAG formula, including several not asked for. No discrepancy was found.
Known limits
The callsite check is a textual per-rule-block scan and misses JS-set backgrounds,
color-mix()/gradient fills, and pairings split across a base rule and its:hovervariant — now stated in its own output. A pre-existing typo inPriceChart.tsx's--posfallback (#0d8259vs the real#0d7259) is left alone as out of scope; it only fires ifgetComputedStylefails to resolve the variable.🤖 Generated with Claude Code