Fail a build on taste: wiring a design gate into CI

5 min read

Eduardo Calvo

Eduardo CalvoWrote the skill and the detector this post measures with

Lint catches syntax. Types catch shape. Nothing in a normal pipeline catches a page that is technically fine and visibly generated. That is the gap a design gate fills.

The command

npx ui-craft-detect src --format junit

No config file needed, no model call, no network. It reads source, applies 43 deterministic checks — 38 anti-slop and 5 on accessibility — and exits non-zero when something fails. Same input, same answer, twice. Token discipline and a second accessibility pass live in the score rather than the CLI, which is what takes the whole system to 52 gates.

The GitHub Action

- name: Design gate
  run: npx ui-craft-detect src --format junit --threshold 85

--threshold is the score, 0 to 100. Below it, the job fails.

What to gate, and what not to

Gate: the checks with a right answer. Missing focus states. Contrast under the bar. Tables with no overflow handling. transition: all. Images with no dimensions. Heading levels skipped. These are defects, and a defect that reaches review wastes a human’s attention on something a script should have caught.

Do not gate: the judgement calls. Whether six cards in a row is wrong depends on what the six cards are. Whether a page commits enough colour is a decision about the brand. The detector reports these as warnings on purpose — they belong in the pull request conversation, not in a red X.

The distinction matters more than the tooling. A gate that fails on taste teaches the team to route around it.

Start at your current score

The mistake is setting the threshold at 100 on day one and watching everyone add ignore comments. Run the scan, take the number you have, set the threshold there, and raise it when the number goes up on its own.