The system end to end: what it is made of, how one topic becomes a page, how a change gets in, and what stops a bad build reaching readers.
21 issues, 685 topics across 45 categories and 362 commands, rendered into 880 static pages by 31 build stages and published to one origin. No framework, no client-side rendering, no runtime dependencies.
The shape matters more than the stack. Four data files describe the subject; everything a reader sees is computed from them at build time. That is why the count on the homepage, the count on a category hub and the count in the repository's README cannot drift apart — they are the same expression evaluated three times, not three numbers maintained in three places.
Follow a single topic through the system. Every hop is a real file boundary, and the label on each edge is the operation that crosses it.
784 of the pages are honest placeholders for checklist items not yet written, marked noindex,follow. They are real pages with real internal links, so they belong in the site — but a sitemap is a request to crawl, and asking a crawler to fetch 784 URLs that then decline to be indexed wastes the crawl budget of a site that has very little. So the sitemap carries the 95 indexable ones and the rest stay linked but unsubmitted.
31 stages in a fixed order. The order is not stylistic: a stage that counts or classifies against the filesystem must run after the stages that write what it counts.
Before the output directory existed, this ordering problem was invisible. The build wrote into the repository, so every counting stage read the previous build's output and was quietly one build behind. Wiping dist/ on every run exposed it immediately: 33 checklist items dropped from Live to Planned because the pages proving they were covered had not been generated yet.
What happens to a change between a fork and the live site — and, deliberately, what does not happen.
The deploy workflow triggers on push to main and workflow_dispatch only. There is no pull_request trigger and — the one that matters — no pull_request_target. That is the difference between a safe public CI pipeline and the attack that has drained plenty of real projects: a stranger's code never executes in a job holding the Cloudflare token.
PR validation therefore runs as its own workflow with no secrets at all. It can build, verify and render — it cannot deploy, and it has nothing to leak.
There is no application server. A request resolves to a file, and the only computation at request time is header attachment.
Push is the only action. Everything between it and production is automated, and every gate can stop it.
| Gate | Catches | What it looked like when it did not exist |
|---|---|---|
| all checks passed | broken links, unbalanced tags, wrong canonicals, pages that disagree | All 878 canonicals named a host being retired |
| empty-build gate | a build that produced nothing | A deploy published 0 files; the whole site 404'd |
| render tests | horizontal overflow, duplicate fixed headers | Every page family scrolled sideways between 761px and 950px |
| origin audit | a page fetching from an unaccounted-for host | Two LinkedIn iframes loaded on every homepage visit, unnoticed |
| wrangler rollback | anything the gates missed | Used once, in anger, and it worked |
The repository is public and the site is public. What is not public is exactly two values, and neither has ever been in the repository — a full-history scan confirms it.
One gap is left open deliberately rather than filled badly. The policy declares no script-src, because allowing the site's inline handlers would require 'unsafe-inline' — a directive that scores well on a header scanner and stops approximately no XSS. There is no live vector to close: the search box escapes every interpolated value, nothing reads a query parameter, and there is no backend. A policy that overstates its own strength is worse than an honest gap.