# Three changes, with the receipts.

> Three real pull requests behind this site. Each has the plain brief, the steps taken, what review caught, and the real diff stats.

Source: https://customhosted.com/case-files/

№ 01 Case files

Every hosting company claims agents ship changes fast. Here are three that actually shipped on this exact site — the plain-language brief, what the agent did, what human review caught, and the diff behind each one, read straight from this repo's own history.

№ 02 By the numbers

## The whole ledger, not just three rows

Every figure below is read from this repo's own merge history at build time — see the FAQ for how.

- **Changes shipped**: 132
  7 July 2026 → 3 October 2026, across 84 separate shipping days.
- **Pace**: 10.4/wk
  Busiest day was 10 July 2026, with 22 changes shipped.
- **Diff size**: +106160 / −12108
  Across 1984 file changes, every merge counted.
- **Automated guards**: 29
  Merges that added or tightened one of this site's own build-time checks.

№ 03 The case files

## Three changes, followed end to end

Each one started as a plain-language brief and ended as a merged pull request. The spec row under each brief is read from that merge's own commits, not typed by hand.

CF-01

[LG-36 in the ledger](https://customhosted.com/built-in-the-open#lg-36) New section

### The site is blinding at night

“The site is blinding at night — support dark mode.”

19 files · +751 / −258 · shipped 31 July 2026

1. 01 — Add the dark half of the palette — Every colour on this site already lived in one tokens file, so the theme is a single :root[data-theme='dark'] block there — no second copy of the palette behind a media query to drift out of sync. A new --on-accent token was added for text sitting on an accent fill, which flips separately from --ink.
2. 02 — Resolve the theme before first paint — The layout's <head> got an inline script: read localStorage's stored side, else follow the OS's prefers-color-scheme, write data-theme on <html> before the body paints, and keep following the OS live until a visitor picks a side.
3. 03 — Add the DAY/NIGHT toggle — The header grew a labelled rocker, not a moon icon — on brand for a utility that already talks about shifts. It renders hidden until that same inline script un-hides it, so a JS-off visitor never sees a dead control.
4. 04 — Keep the always-dark bars dark — The contact and footer "switch room" bookends, plus a few permanently dark bars elsewhere, were repointed at theme-invariant tokens instead of the ones that flip — so they read as a darker, separate room in both themes rather than inverting.
5. 05 — Teach the accessibility crawler to see both themes — The automated a11y check had only ever scanned the one palette that existed. It now opens every route in a light-preference browser context and a dark-preference one and scans both.

What review changed

Review sent this branch back twice before it merged: two tokens behind the homepage's technical figure had no dark value at all, so its dashed construction lines, dimension rails, and vent strokes all composited to near-invisible on the new background — the figure lost its draughtsmanship and left two captions floating with no rail attached. And the DAY/NIGHT rocker itself picked up a display style that silently defeated its own [hidden] guard, so a JS-off visitor would have seen a control that looked live and did nothing. Both were fixed on the same branch.

A full day/night theme, auto by default and sticky once a visitor picks a side, everywhere on the site.

[See it flip on the homepage →](https://customhosted.com/)

CF-02

[LG-33 in the ledger](https://customhosted.com/built-in-the-open#lg-33) Fix

### Make sure someone on a screen reader can actually use this

“Make sure someone on a low-contrast display or a screen reader can actually use this.”

46 files · +957 / −80 · shipped 28 July 2026

1. 01 — Crawl every route with an accessibility engine — A new check-a11y script drives a real accessibility engine across every route in the sitemap and asserts the WCAG 2.1 A/AA rule set, plus one heading and one main landmark per page.
2. 02 — Fix what the crawl found — Contrast values in the token file and a dozen individual pages were adjusted to clear AA, and a handful of heading levels that skipped a step were corrected.
3. 03 — Add the document-structure checks the crawl misses by default — The engine files heading-order, one-h1, one-main, and landmark-region under its own 'best practice' tag rather than the WCAG tags the crawl otherwise runs — so the guard now runs those four rules explicitly instead of trusting the tag filter alone.
4. 04 — Close the crawl's own blind spot — The engine never simulates :hover, so a failing hover state can ship invisibly. The guard now rewrites every :hover colour rule into an unconditional one at scan time and re-runs the contrast check against that.
5. 05 — Fix the regression the fix itself introduced — A short follow-up branch caught a heading skip that the first pass had relocated rather than removed on /selector, and taught the guard to see that shape of mistake too.

What review changed

Review's own read of the first pass is what surfaced the hover blind spot: a primary call-to-action's hover state was failing contrast and no automated run had ever caught it, because the crawl never triggers :hover. The fix is the guard, not just the one button. The follow-up branch then caught a heading-order regression the original fix had introduced on /selector — the loop catching its own mistake, not just the site's.

Site-wide WCAG 2.1 AA contrast and heading-structure fixes, plus an automated crawl that runs against every future change in both themes.

[Read the accessibility page →](https://customhosted.com/accessibility)

CF-03

[LG-39 in the ledger](https://customhosted.com/built-in-the-open#lg-39) New page

### Answer engines should be able to read the whole site

“Answer engines should be able to read the whole site, not just the pages we remembered to list.”

11 files · +651 / −146 · shipped 2 August 2026

1. 01 — Build one manifest for every route — A single module composes the site's navigation data plus the guide, stack, audience, and legal content modules into one grouped list of every route and its description — throwing at build time if a route is missing one.
2. 02 — Render it for people — A new /directory page renders that manifest as an on-brand, grouped index — the back-of-manual reference for a human visitor.
3. 03 — Render it for machines — A generator writes public/llms.txt in the shape the llms.txt convention defines, from the exact same manifest — one data source, two renderings.
4. 04 — Chain the generator into the build — bun run build now regenerates llms.txt before astro build runs, so what ships is always current with the manifest at the commit being deployed, not a file someone regenerated by hand last month.
5. 05 — Guard the drift after the build — A post-build check reads the built llms.txt and sitemap and fails if any sitemap route is missing from llms.txt, listed twice, or points at a link that doesn't resolve to a real file.

What the audit caught

An automated audit of the site is what surfaced this, not a human reading the diff: the machine-readable index was the one failing check on an otherwise clean homepage, because it listed its URLs as plain text rather than links — so a link-following crawler saw no index at all — and it had already drifted behind the pages that existed. Patching that file would have left it a hand-maintained second copy of the site's structure, so the change deleted the copy instead: the human directory and the machine index are now both rendered from one manifest, and the build fails loudly rather than letting the two drift apart again.

A human directory page and a generated llms.txt, both read from one manifest that cannot fall out of sync with the site.

[Browse the directory →](https://customhosted.com/directory)

№ 04 Where the work went

## Not just the marketing copy

File changes across every merge in this repo's history, grouped by what kind of file they touched.

- **Site components & copy**: 1314
- **Generated assets**: 484
- **Docs & config**: 88
- **Build & check scripts**: 45
- **Infrastructure as code**: 37
- **Deploy pipeline**: 16

№ 05 What this means for your project

## The same loop, on your codebase

- Already hosted somewhere, or about to be? — Host with us and CodeHerder can iterate on your codebase the exact way it iterates on this one — brief a change, review the pull request, ship the merge. — [How the loop works →](https://customhosted.com/loop)
- Want the full ledger, not three examples? — Every one of these three case files is a single row pulled out of a much longer public record of merges to this site. — [Read the full ledger →](https://customhosted.com/built-in-the-open)
- Want to know what it costs? — Hosting and the CodeHerder iteration line are both flat monthly rates. A net-new CustomLabs build, like the one behind each case file here, is quoted separately. — [See how a month is billed →](https://customhosted.com/billing)
- Have a change in mind already? — Describe it in plain language, the way every brief on this page started. One fixed monthly rate, in writing, within a business day. — [Get a quote →](https://customhosted.com/contact)

№ 06 FAQ

## Case file questions

What this page is proof of, and what it deliberately doesn't claim.

1. 01 Are these real pull requests, or a demo built to look like one? Real. Every branch named on this page is an actual merge in this repo's own history — check:ledger fails the build if one isn't.
2. 02 Why only three case files, when built-in-the-open lists dozens more rows? Built in the open is the full ledger, one line per merge. This page is that ledger's slow lane: pull a handful of rows and show the paperwork behind each one — the brief, the steps, what review caught, the outcome.
3. 03 Where do the numbers on this page come from? A small script reads this repo's own git history at build time — the same merge commits the ledger counts — and writes the file counts, line counts, and dates this page interpolates. Nothing here is hand-typed.
4. 04 Why doesn't this page say how fast a change went from brief to live? Because agents in this repo commit once at the end of a session, a measured first-commit-to-merge time would describe that bookkeeping, not how long the work actually took — so we don't publish a number we already know is misleading.

№ 07 See also

## Keep exploring

- [Brief a change Every brief on this page, run through the same five questions, plus a composer to write your own.](https://customhosted.com/brief)
- [Built in the open The full pull-request ledger these case files are pulled from.](https://customhosted.com/built-in-the-open)
- [The Loop The Build → Iterate → Run story told in full, as a product.](https://customhosted.com/loop)
- [Review The worked artifact above, in full — the real checks and review notes behind these three dossiers.](https://customhosted.com/review)
- [In service The same loop, worked through on five other hosting shapes instead of on ourselves.](https://customhosted.com/in-service)
- [Directory Every page on the site, grouped and described — the back-of-manual index.](https://customhosted.com/directory)

## Want this loop running on your project?

Host with us and CodeHerder's agents can iterate on your codebase the same way — brief a change, review the pull request, ship the merge.

[Get a quote →](https://customhosted.com/contact)
