Skip to content

fix(design): the tint that never painted - #543

Merged
haksungjang merged 2 commits into
mainfrom
fix/g07-risk-tint-alpha
Jul 29, 2026
Merged

fix(design): the tint that never painted#543
haksungjang merged 2 commits into
mainfrom
fix/g07-risk-tint-alpha

Conversation

@haksungjang

Copy link
Copy Markdown
Contributor

What was wrong

bg-risk-critical/10 was written at 27 call sites across 13 files, and design-system.md described that spelling as the way to tint a surface. It painted nothing.

Tailwind implements an opacity modifier by splitting the colour into channels and re-emitting it with an alpha. The --risk-* tokens held a bare var() it cannot split, so it did not emit a degraded rule — it emitted no rule at all for the slashed class, and every one of those elements fell back to transparent. No error, no warning, no visible diff in review. The only ways to see it were to compile the stylesheet and grep, or to read a computed style in a browser.

The fix

In the mechanism, not the call sites. Each risk token is now wrapped in a color-mix() carrying Tailwind's <alpha-value>, so the spelling everybody was already writing works — including variants a hand-written class cannot reach (hover:bg-risk-low/20). The severity hex in index.css stays the single source of the hue and charts still read it raw. W16's four .risk-tint-* workaround classes, which covered one caller while twenty-six others kept rendering transparent, are gone.

Consequences of the tints existing for the first time:

  • Alpha scale is three steps/10 surface, /20 emphasis, /40 border. The old call sites mixed six; preserving those distinctions would have meant preserving differences nobody had ever seen.
  • All five --risk-*-foreground tokens are declared. The severity hex measures 3.05:1 on its own tint, so text on a tint needs the darker shade. The three new ones are the hexes design-system.md had already been listing as though they were in the stylesheet — drift resolved toward the code.
  • statusTokenContrast.test.ts re-measures against /20 over --background (the worst case) instead of the /15-over-white surface that never rendered.

riskTintOpacity.test.ts compiles the stylesheet and greps it. Asserting the config's shape would assert our theory of the fix; source text is exactly what looked fine here for as long as the defect lived.

Two gate repairs fell out of the work

  • The a11y scan raced the routed subtree's 250ms fade-in. axe folds an ancestor's opacity into the foreground colour it measures, so a scan landing mid-fade read the muted grey as #7c7c84 instead of #6c6c75 and reported ~120 contrast violations that evaporated once the frame settled — a different set of screens each run. scan() now waits for finite animations to finish (infinite ones — skeleton pulse, spinners — are excluded; waiting on those never returns).
  • Baselines are CI's, not local. Locally the tints appeared to clear projects-list's four colour-contrast violations; on CI they are still there, because the local seed renders fewer rows. The frozen budget stays at 12. Third occurrence of a locally-derived baseline claiming an improvement that did not exist.

Verification

  • vitest run — 1584 passed, design suite 75 passed
  • tsc -b --noEmit clean (tailwind.config.ts added to tsconfig.spec.json's include, since the new test imports it)
  • npm run lint 0 errors · token:lint 130 → 123 · ko-style --fail-on S2 0 findings
  • a11y gate 8/8, narrow-viewport gate 8/8
  • Visual baselines refreshed via ui-gates.yml -f update_baselines=true (run 30455666100). Seven screens move; login is unchanged, having no risk surface.

`bg-risk-critical/10` was written at 27 call sites across 13 files, and the
design-system doc described that spelling as the way to tint a surface. It
painted nothing. Tailwind implements an opacity modifier by splitting the
colour into channels and re-emitting it with an alpha; the `--risk-*` tokens
held a bare `var()` it cannot split, so it emitted no rule at all for the
slashed class and every one of those elements fell back to transparent.

The fix is in the mechanism, not the call sites: each risk token is now
wrapped in a `color-mix()` carrying Tailwind's `<alpha-value>`, so the
spelling everybody was already writing works — including variants a
hand-written class cannot reach (`hover:bg-risk-low/20`). The severity hex
in `index.css` stays the single source of the hue, charts still read it raw,
and the four `.risk-tint-*` classes W16 added as a workaround for one caller
are gone.

Consequences of the tints existing for the first time:

- The alpha scale is three steps (/10 surface, /20 emphasis, /40 border).
  The old call sites mixed six; preserving those distinctions would have
  been preserving differences nobody had ever seen.
- Text on a tint needs a darker shade of the same hue — the severity hex
  measures 3.05:1 on its own tint. All five `--risk-*-foreground` tokens are
  declared now, taking the hexes `design-system.md` had already been listing
  as though they were in the stylesheet.
- `statusTokenContrast.test.ts` re-measures against /20 over `--background`,
  the worst case, rather than the /15-over-white surface that never rendered.

`riskTintOpacity.test.ts` compiles the stylesheet and greps it, because
asserting the config's shape would only assert our theory of the fix, and
source text is exactly what looked fine here for as long as the defect lived.

Two gate repairs fell out of the work:

- The a11y scan raced the routed subtree's 250ms fade-in. axe folds an
  ancestor's opacity into the foreground colour it measures, so a scan
  landing mid-fade read the muted grey as #7c7c84 and reported ~120 contrast
  violations that evaporated once the frame settled — a different set of
  screens each run. It now waits for finite animations to finish.
- With that settled, the tints' arrival drops `projects-list` from 4
  colour-contrast violations to 0. Frozen budget: 12 -> 8.
Seven of the eight representative screens move pixels now that the risk
tints paint: the governance band's risk-score and build-gate tiles, the
severity chips, the scan-log lines. Login is unchanged — it has no risk
surface. Captured by `ui-gates.yml` with `update_baselines=true`, never
locally: the runner's fonts and seed are what the gate compares against.

The a11y baseline comes from the same run, and it disagrees with the local
one. Locally `projects-list` reported its four colour-contrast violations
gone; on CI they are still there. The local seed simply renders fewer rows
than the CI seed, so the offending cells were not on screen — the frozen
budget stays at 12. This is the third time a locally-derived baseline has
claimed an improvement that did not exist, which is why the handoff says to
take these from CI and why the number here is CI's.
@haksungjang
haksungjang merged commit 25a91af into main Jul 29, 2026
26 checks passed
@haksungjang
haksungjang deleted the fix/g07-risk-tint-alpha branch July 29, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant