The project has six guard scripts. They all share a trait: they came after the fact, each after one specific slip.
check-lazy-config.mjs config read lazily
check-engine-strings.mjs no reader-facing string sits in the code
check-ui-strings.mjs the instance declares every known string
check-dates.mjs no entry has a bare date
check-header-classes.mjs the header does not push past the screen
check-header-layout.mjs header layout after adding an elementStrings in the code
The engine is meant to be language-independent. That sounds obvious until someone types the word “Czytaj” (“Read”) straight into a component — then the English version of the site gets a Polish button, and nobody sees it until a reader opens it.
Searching for such strings by Polish diacritics does not work. “Czytaj”, “minuty czytania” (“minutes of reading”) and typographic quotation marks do not have a single diacritic between them. So the guard searches by place, not by content: every piece of text rendered for a reader has to come from the dictionary, not from a literal.
A second guard watches the other side: the instance has to declare every string the engine knows. Deliberate gaps — plural forms that English does not have — sit on an exceptions list and are named there one by one.
Config read lazily
This one is the most instructive. The site had a setting that switched off the engine credit in the footer. It worked: the generated HTML had not a word about the engine.
But the constant with the name travelled to the browser in the code bundle anyway, because the condition is checked at run time and the bundler has no way to cut out the dead branch. Anyone who opened the developer tools would have found a name that was meant to be hidden.
The guard now fails the build when the config is read eagerly — even in a documentation comment, because it can end up in the bundle from there too.
A date in the future
The simplest, and the one that saves the day most often. An entry with a date that has not yet come does not appear on the site — that is a feature, scheduled publishing. But the same feature turns a typo in the hour into an invisible entry.
A separate trap: a date on its own, without a time, is read as midnight UTC, that is two in the morning here. An entry with a full timestamp added in the evening then lands below an entry with just the date from the following day.
I tested it on myself only today: publishing a text about deployment traps, I set the date five minutes into the future and got four hundred and four.
What follows
All these scripts together are a few hundred lines. None is a unit test and none checks logic — they check architectural assumptions the compiler cannot see.
And that is probably the right conclusion. Tests make sure the code does what it is meant to do. Guards make sure nobody crosses a line that exists only in the team's heads — and after six months, not even there.