Buttons that go nowhere
Every interactive element has to be declared. An action with no destination fails the build — which is how the two new links in this site's own navigation were caught the first time it ran.
A site here is not a folder of HTML someone edits. It is a set of typed documents, compiled to a finished site by a program that runs sixty-two checks on its own output and refuses to ship if one of them fails. This page went through it.
The failure a small site actually suffers is not a bad design. It is drift: a fix applied to four pages and forgotten on the fifth, a colour pasted in by hand that does not match, an image that lost its alt text in an edit, a heading level that skipped a rung and quietly broke the page for anyone using a screen reader. None of that announces itself, and all of it accumulates.
If the pages are data and the markup is generated, that class of problem stops being a matter of care. A page cannot use a colour that is not defined. An unknown key in a page document is a hard error rather than a section that silently vanishes. And the audit runs on the generated output every single time, so the check is not something you remember to do before a deploy.
Delete the built site entirely, run the build again, and the bytes come back identical to what the server was serving a minute ago.
That is worth stating as a test rather than a principle, so I ran it while writing this page. Copy this site's content to a scratch directory, delete the whole built tree, and rebuild: sixty-two phases, no findings, a little over a second. The regenerated files match the committed ones one for one, and the generated home page is byte-for-byte what the live server was handing out at that moment. It is a claim anyone with the repository can re-run, which is the only kind worth making.
$ rm -rf static/ && forge build --root .
strict findings: 0
duration: 1161ms
forge build OK
# # 62 phases; output byte-identical to what the server was serving
Running the same comparison against a client site turned up a difference of exactly one line: the integrity hash pinning the stylesheet. The committed build had been generated against a version of the design system that had since moved on.
That is the mechanism working rather than failing. Because every page pins the exact bytes of the stylesheet it was built against, a design system that drifts underneath a site turns into a visible one-line difference instead of a site that renders with the wrong palette and nobody notices for a week. The deploy script for that site carries a note about the incident that led to the pin: a stale build tree shipped against fresh stylesheets, and the client's brand colours disappeared.
Some defects only exist at runtime. Whether text actually has enough contrast against what ends up behind it, whether a control can be reached and seen by keyboard, whether a tap target is big enough for a thumb, whether a button does anything when clicked — none of that is decidable by reading the source.
So a hundred detectors exist to drive a real browser through a finished site and produce a typed report rather than a page of prose: forty documents, one per axis, with screenshots and text snapshots of what a screen reader would announce at three points in the journey. The useful part is the last file, which diffs a run against the previous one, so "is this worse than it was?" is a question a build can answer instead of a person.
Every interactive element has to be declared. An action with no destination fails the build — which is how the two new links in this site's own navigation were caught the first time it ran.
Content policy, transport security and subresource integrity are checked on the generated output, not assumed from the server configuration.
There is a check against thin content and one against a page that is the same block repeated — which failed this project's own index page while it was being written, and was right to.
This is infrastructure for one person's sites. It is public and it builds, but it is not packaged for anyone else: there is no release, nothing on a package registry, and no install that does not begin with cloning three repositories. The robot browser in particular is the least portable piece — it needs a working browser runtime, and it failed to complete a run on one of my own machines the day I wrote this.