Skip to content
PolyFish CSS

The checker that says no

A build script that refuses to ship is the cheapest code review you will ever get. Every rule in ours exists because something broke first.

· ~4 min read · 0.14.0 in the changelog

There's a script in this repo whose entire job is to tell me I've done something wrong. It has grown a rule every time we've shipped a bug worth not repeating, and at this point I trust it more than I trust myself at 1am.

It isn't clever. It parses the CSS with tinycss2, reads the HTML with regular expressions like a barbarian, and prints a list. What makes it useful is not sophistication, it's that the rules are specific — each one encodes a mistake somebody actually made rather than a style opinion somebody had in a meeting.

A rule per scar

A few of the current ones, and what they came from:

  • Components read role tokens, never palette values. Because five of six themes hovered to the default green, and none of us noticed for weeks.
  • A class is declared in exactly one module. Because a duplicated declaration means a page that loads one module and not the other gets half a component.
  • No physical properties. Because margin-left is invisible until somebody reads the site in Arabic, and by then it is everywhere.
  • Every token that is read must be declared somewhere. Because one of them was only declared in an optional file, so any page that skipped that file quietly lost a text colour.

Written down like that they look obvious. They were not obvious. Each one is a morning I'm not going to spend again.

What a static check cannot see

Worth being honest about the ceiling here. The checker can tell you a rule exists, is well-formed and sits in the right file. It cannot tell you whether that rule won. Cascade layers, later modules, a stylesheet that was never linked on the page at all — the CSS is perfect and the browser ignores it.

That's why the same repo also runs the Nu validator over every page and a browser suite over a few of them. Different layers catch different classes of wrong, and the expensive layer only earns its keep because the cheap one has already removed the noise.

Make it refuse, not warn

The one design decision I'd repeat everywhere: the script exits non-zero and the release script refuses to finish. A warning is a thing you scroll past. I have scrolled past hundreds of warnings. I have never once scrolled past a build that would not build.

If you take one idea from this: next time you fix a bug that a computer could have caught, spend the extra twenty minutes teaching a script to catch it. The rule is usually five lines, and it will outlive your memory of why it mattered.

What this post does not cover

  • CI configuration. The script runs the same way locally and on a server.
  • Testing strategy in general — this is one cheap layer under a browser suite, not a pyramid.

Checked against tinycss2, Nu validator, logical properties. Figures are measured from the build they describe.

What this doesn’t cover

A static check cannot tell you whether a rule won, whether a page looks right, or whether a component is comprehensible to a screen reader. It is the cheap layer. Everything it cannot see needs a rendering engine or a person, and the rules it does enforce are ours — they encode our mistakes, not a general standard.

Sources: tinycss2 and the Nu HTML validator. The rule list is reproduced from tools/verify.py as it stands; no code blocks in this post.

Tooling Testing Build Quality CSS Python