5 min read
The guards that defend your judgment when nobody is looking
A principle that does not break the build erodes. Never all at once: it goes through reasonable exceptions, on a Tuesday, in a hurry, with somebody waiting. So the standards of this site do not live in a conventions document. They live in scripts that fail loudly and leave me unable to deploy.
A principle that does not break the build erodes
Every team has a best-practices document and every team breaks it the same way: nobody does it on purpose. Somebody hits an odd, genuinely urgent case, solves it outside the rule, and that exception stays in the repository as precedent. The next person does not argue, they copy. A year later the rule exists in prose and no longer exists in the code. The difference between a standard and an opinion is whether something turns red when you break it.
This site has its own and they are boringly concrete. check-rhythm keeps the home page from placing two bursts back to back. check-dry forbids a class recipe from living anywhere but the single source that defines it. check-design-tokens breaks the build when a raw color shows up instead of a token. check-translations demands that both languages have exactly the same shape. check-max-lines cuts long files down. check-clean-architecture keeps pages pure server components.
A guard gets written at the moment of deciding, never afterward. At that moment I still know why the decision matters and which concrete case I want to prevent; three months later all that is left is habit, and habit cannot be programmed. It is also why a guard joins the lint chain the day the thing it defends exists: until then it is invoked by hand, and that fact is written in its header so nobody believes it is watching something it is not watching yet.
How you pick the number
The line ceiling per file here is 800. That is a number, and numbers have to be defensible. I did not pick it as an aspiration; I measured the repository first. A threshold that fails the day you turn it on is not a guard, it is a backlog in disguise, and the first thing any team does with a backlog like that is mute it. The number belongs where everything passes today and where splitting the neglected file hurts tomorrow.
A guard is code, so it has bugs, and its bugs are worse than ordinary ones. check-max-lines used to count 801 lines in an 800-line file, because the trailing newline left a phantom empty element when the text was split: the effective ceiling was 799 and it failed when it should not have. A guard that errs against you teaches people to distrust every guard, and that distrust is exactly what a repository cannot afford.
Every exemption is a crack with a name
check-design-tokens exempts four files, and each one carries its reason written beside it. The global stylesheet, because that is where the tokens are defined. The manifest and the site config, because the platform demands a literal color there. And the shared layout shell, because bilingual routing splits the root in two and the themeColor that Next's Metadata API requires has to live in exactly one place. None of the four is an exception. They are the edge of the rule, said out loud.
The dangerous exemption is the one with no reason attached, because it cannot be reviewed. When an exempt list grows faster than the code it watches, the code stopped being the problem: the rule is wrong and needs rewriting, not another pardon. A guard with too many exemptions still prints green in the build log, and that is its most expensive way of failing, because it looks like it is still working.
A false positive is visible; a false negative is not
check-rhythm started out wanting to compare two lists: the order of the home page in a JSON file and the same order in a TypeScript file. Reading TypeScript with regular expressions always loses —one similar word inside a type shifts the matching and invents discrepancies, or worse, hides them— so the guard changed the question. Instead of comparing two sources, it demands there be only one: the config file has to import the JSON. By construction they can no longer drift.
That is the underlying choice. A guard can fail in two directions and they do not cost the same. A false positive interrupts me, makes me look, and at worst costs two minutes of irritation. A false negative costs nothing today and costs the whole standard six months from now, when twenty files already break it and fixing it is a project instead of a line.
So the guards here would rather be strict and explain themselves: every error names the file, the line, and what to do instead. check-clean-architecture accepts neither a hook outside a client component nor a page that declares itself a client. check-translations loads both dictionaries and compares their entire shape. None of them has an opinion about whether the code is pretty. They only hold up decisions I already made, on the days I cannot remember why I made them.