All notes

The contrast gate was checking a color I don't ship

The accent color on this site is a yellow-green. My contrast gate had been checking a mint green, confidently, on every run, for weeks.

The accent exists twice. There is the one you see — oklch(0.84 0.18 118), a sharp yellow-green — and there is an easter egg. Triple-click the line under my name and the hue rotates forty degrees for the rest of the session. Nothing announces it. It is the sort of thing you build at midnight.

Both live in the same token file. The shifted one is scoped to an attribute:

:root { --acid: oklch(0.84 0.18 118); }

:root[data-accent='shifted'] { --acid: oklch(0.84 0.18 158); }

@media (prefers-color-scheme: light) { ... }

How the gate read it

The contrast gate does something I still think is right: it parses the actual stylesheet rather than a hand-copied table of values, resolves the var() chains, and computes real WCAG ratios from the OKLCH. A gate that checks a duplicate of your tokens is a gate that goes stale the first time somebody edits one and not the other.

To separate the dark scheme from the light one, it split the file at the media query:

const LIGHT_AT = css.indexOf('@media (prefers-color-scheme: light)');
const dark = declarations(css.slice(0, LIGHT_AT));

The easter-egg block sits above that media query. So it landed in the same map as :root — and because it came later in the file, its values won. Every run of the gate resolved --acid to hue 158 and reported on it. Both schemes. Every time.

The output even said so, in a line I had read dozens of times without seeing:

ok  --acid  #3ded9c  oklch(0.84 0.18 158)

#3ded9c is mint. The site renders #c1d934. I had been looking at the wrong color printed in my own terminal, in a gate I wrote, and reading it as confirmation.

What that cost

The shipped accent had never been contrast-checked. Not once. It had also never been gamut-checked, which matters because an out-of-gamut OKLCH color is silently clipped by the browser to something that is not the color you specified.

I wanted to know how bad a false pass this was, so I broke it on purpose. In a scratch copy I set the three hue-118 primitives to nearly white on the near-black background — a combination no sighted person could read. The gate printed:

PASS  --accent on --bg  4.62:1  (needs 4.5)
All checks passed.

The real ratio for that pair was 1.06:1. The gate exited zero. CI would have been green. It was not that the gate was lenient — it was answering a question about a color that was not on the page.

The fix was to lift the shifted block out before parsing and evaluate it as its own pair of schemes, so all four palettes get checked: dark, light, and each with the easter egg active. The egg is not allowed to break the contract either. That is four times the coverage from a change that made the file shorter.

The same mistake, from the other direction

The part that made me write this happened a few hours later, and it is the reason I think this generalizes.

A review of the site came back with three reported defects. One was real — my canonical URL named the bare host while the server was redirecting to the www subdomain, so the page was declaring a canonical address that immediately bounced. Worth fixing, and I fixed it.

The other two did not exist. The report said my theme-color was light-only. It is not; there are two, one per color scheme. And it said an internal link was hardcoded to an absolute www URL. It is not; there are no absolute URLs in the source at all.

Both came from reading the served page instead of the source. A scrape had flattened two theme-color tags into one value, and the www in the report was the serving host doing exactly what I had told it to do.

That is my contrast gate again, wearing different clothes. My version examined the wrong copy of the tokens. That version examined the wrong copy of the page. Neither was careless — both were precise measurements, correctly performed, on an artifact that was not the one under discussion.

What I do now

I ask one question of any check before I trust it: which copy of the thing did this actually read?

Source or build output. Authored value or computed value. The file on disk or the bytes on the wire. These diverge constantly and for good reasons — minifiers, cascade layers, redirects, scrapers, server headers — and every one of those divergences is a place a correct measurement can be taken of something nobody cares about.

It also means I now break a gate on purpose before I believe it. If I have not watched a check fail, all I know is that it prints the word PASS.

verso