Originally published on Hearso Writing on 2026-09-20. Hearso is the team trivia game I build and run; these posts are the engineering notes from building it.
Local review before pushing left one outside finding on 70 files: a status page framed twice. Every check asked what it said; none asked how it was framed.
Everything I could think of ran before I asked anyone else to look. It still missed the thing a fresh reader saw in under eight minutes.
On 2026-09-20 we were splitting Hearso across two hostnames: marketing on www, the application on app, one deployment answering to both. The same batch added a new public status page at /health-status.
Two days earlier I had written up eleven checks whose "nothing happened" case read as success. That post made me careful about checks that pass by not looking, which is partly why this one stung: every check here did look.
I wanted to know whether reviewing a branch thoroughly on my own laptop, before pushing it, would leave an outside reader with nothing to find.
So I ran four local reviewers, 18 hand-applied mutants, 67 live HTTP checks against a production build, two accessibility scans and a real browser, then opened one pull request of 70 files instead of five.
Short answer: almost. The first automated reviewer left exactly one comment, 7 minutes 50 seconds after the pull request opened, and it was right. The new status page was drawn inside two frames: two navigation bars and two footers under the platform shell.
None of my checks could have seen it. Every one of them asked the page what it said, and none asked how it was framed. The fix was one entry in a list; the guard that stops it coming back had to read the source tree instead.
Reviewer capacity was the thing we were short of
One of the two automated reviewers we use draws on an allowance shared by the whole organisation. We measured eight reads between 22:24Z and 02:27Z, about one every half hour, and after a 48-file read it refused every request for more than forty minutes. Eight pull requests wanted a turn.
So the rule for the night was: review locally first, batch five branches into one pull request, and try to make the outside reader's first read find nothing.
Everything that ran before I pushed
Four local reviewers read the unpushed branch, one brief each: correctness, security, test quality and repo conventions. Between them, 34 findings. Thirty-three are fixed on the branch. One is accepted with its reason written down, because it is about the cache headers of a 404 on a hosting platform and cannot be judged without deploying.
One more was mine, found while reading after them. A layout renders even when its page refuses, because the not-found boundary is drawn inside that segment's layouts. So a future layout under our operations area could paint data beside the 404 meant to hide it. I proved it with three planted files, then made the gate scan refuse that shape.
Then the mutants: break one thing in the code on purpose and check that a test notices. Eighteen applied by hand, eighteen killed. Each one counted only after asserting that the file's bytes had actually changed, because an unmutated file passing its tests reads exactly like a mutant that survived.
Then the live checks. One production build in split mode, a server on loopback, and every request sent with the Host header a browser would send: 49 of 49 under the classic shell and 18 of 18 under the platform shell. In a real browser, the opt-in hostname run passed 7 of 7. Both accessibility scans pass with the new page added to them.
I got two of my own expectations wrong first
The first live run was 29 of 31, and both failures were mine, not the code's.
An unknown path asked of www is a 308 across to app by design: whatever is not on marketing's list belongs to the application, and the application answers the 404. My check had asked www for a missing page and wanted a 404. The fixed version asks both halves, and both are in the output.
The second is the one worth keeping. I checked that /changelog carried a footer link to the status page; it did not, so the check failed. Under the classic shell /changelog draws no footer at all, so "no link there" was true of the page and said nothing about the link. The check moved to /board, which does draw the classic footer, and passed.
I measured a redirect instead of arguing about it
One reviewer raised GET //library in split mode. The helper that builds a cross-host link throws on a path whose second character is a slash, and nothing caught it, so our interceptor would answer 500.
Fixing that raised a second question. A path like //evil.example/x names a different authority to a browser, so where would a redirect actually send someone? Rather than reason about it, I asked the running server. Next.js's own router normalises that path to Location: /evil.example/x (one slash, our own host) before our code runs at all.
The point is how it was judged. The script prints the Location and passes it to a small function that decides whether a browser following it stays on our two hostnames, and that function has ten control cases of its own. A status code alone would have said nothing: a 308 to somewhere else is still a 308.
I got the control habit wrong elsewhere the same night. I wrote that mail to one of our support addresses "bounced", when I had measured missing DNS records and nothing else. And my port-25 probe was worthless, because its control, a real mail exchanger, read closed too.
That was everything I could think of. The words doing the work in that sentence are "I could think of", and the next section is what they left out.
Seventy files, one comment, seven minutes fifty seconds
Web PR #447 opened at 06:37:19Z: 70 files, five branches of work, with a read requested the same second.
At 06:45:09Z, seven minutes and fifty seconds later, the first automated reviewer left exactly one comment, on line 68 of the status page:
app/layout.tsxalways mountsSiteShell, but/health-statusis in neitherPLATFORM_ONLY_PREFIXESnorDUAL_SHELL_PATHS, so the root shell does not step aside here. ThisViewertherefore renders inside the classic shell: platform mode gets duplicate nav/footer, while classic mode gets the classic nav/footer around the page's bare wordmark.
It was right.
A page that frames itself must tell the root layout to step aside
Think of a framed print. Our app hangs every page in the same frame: a navigation bar above, a footer below. A few pages arrive already framed, and hanging one of those inside the house frame gives you two.
Precisely: the root layout in Next.js wraps every route in the app, and ours mounts the classic site shell. A short list of paths tells that shell to stand down, for the handful of pages that mount a frame themselves.
/roadmap is on that list and draws its own bare wordmark. /health-status was modelled on /roadmap, mounts the same component, and was not on the list. So under the platform shell it came back with two navigations and two footers, and under the classic shell with the classic bar and footer wrapped around a bare wordmark. Figure 1 shows both versions of the route.
Figure 1. The same route twice, before and after commit 703aee91 (2026-09-20). The list controls exactly one thing at runtime: whether the root layout steps aside.
Every check asked what the page said, and none asked how it was framed
Every check I wrote asked that page two kinds of question: what status code does it return, and what words are in it. The accessibility scans add a third kind, and a duplicate landmark is not a blocking finding for them, so they stayed green as well.
None of them asked how many frames it had. Not the 67 live checks, not the four reviewers, not the 8,347 unit tests on that head (Figure 2).
Figure 2. Findings about the page's frame, by who was looking, on 2026-09-20 (UTC). The local layers raised 35 findings between them; none was this one.
This is not the shape from that earlier post. Every check here ran, reached a real running page and reported truthfully. They examined a different dimension from the one that was broken.
Same sentence, different edge: a check says what it examined. Mine examined the content and not the frame.
A hand-written list can't notice a page nobody wrote down
Registering the route is the small half. It joins the list, the footer guard's fixture joins with it, and because that list controls one thing at runtime, nothing else about the page changes.
The class is the harder half. There was already a guard here: a hand-written map of path to page, compared against the list in both directions. It notices a path added to one and not the other. It can never notice a page added to neither, which is exactly what a new page is.
So the new guard reads the source tree instead. Every page.tsx outside the platform route group that mounts the frame component must be a path the root shell steps aside for, and the reverse. Eleven paths today.
expect({
pagesExamined: pages.length > 10,
selfFramed,
}).toEqual({
pagesExamined: true,
selfFramed: [...DUAL_SHELL_PATHS].sort(),
});
pagesExamined is in that assertion because a scan that finds no pages passes every claim it makes about them. Comments are stripped before the scan, so a sentence mentioning the component is not counted as mounting it. And the guard was checked the way any test should be: with the route taken back out of the list, it fails and names it.
The live checks count frames now
Each new block in the live scripts carries a control. /board is a classic page and must be inside the classic shell. /roadmap, the page /health-status was copied from, must not be, and must have exactly one footer.
| Run | Build | Checks | Of which count frames |
|---|---|---|---|
| classic shell, before | 76a2f0ae | 49 of 49 | 0 |
| classic shell, after | 703aee91 | 54 of 54 | 5 |
| platform shell, before | 76a2f0ae | 18 of 18 | 0 |
| platform shell, after | 703aee91 | 22 of 22 | 4 |
Figure 3. The same live scripts before and after, on 2026-09-20 (UTC). Every check passed in all four runs; the bars that changed are the ones that ask a question nobody had asked.
Every check passed in all four runs (Figure 3). What changed is the question. On the fixed head the whole suite is 447 files and 8,354 tests, with the type checker and linter clean.
Both halves are true
Local review first worked. Seventy files came back with one finding. The two pull requests before it each needed a second read: one brought back a real bug that would have been an endless redirect, the other two smaller ones.
Local review first also missed something a fresh reader saw in under eight minutes. Both of those sentences are true, and I would rather write them down together than pick the flattering one.
The lesson I take is not "review harder". It is that a verification examines a dimension, and the honest artifact is the list of dimensions you did not examine. For a page, ours now reads: status code, headers, cookies, words, landmark count, and frames.
Predictions vs outcome
| What I expected | What happened | Right? |
|---|---|---|
| Local review first would leave the outside reader nothing to find | One finding on 70 files, and it was real | Half |
| One batched pull request would need fewer outside rounds than branch by branch | One finding; the two pull requests before it each needed a second read | Yes |
www asked for a missing page answers 404 | A 308 to app by design; app answers the 404 | No |
A missing footer link on /changelog means the link is missing | The classic shell draws no footer there; /board passed | No |
//evil.example/x could redirect a visitor off our hosts | Next.js normalises it to /evil.example/x before our code runs | No |
| Mail to a support address had bounced | I had measured missing DNS records only, and my port-25 control read closed too | No |
| The hand-written path map would catch an unregistered page | It can only compare two lists; a page added to neither passes | No |
Key takeaways
- Write down which dimensions a check did not examine, next to the ones it did. A check examines a dimension, not a page.
- Review locally before pushing, and still ask a fresh reader. Ours cost one finding on 70 files instead of a round trip per branch.
- Build a guard from the tree, not from a hand-written list. A list can only notice things somebody already wrote down.
- Make a check state how much it examined. A scan that finds nothing passes every assertion it makes.
- Put a positive control in the same command as any negative result: a page that must be inside the shell, a mail exchanger that must accept mail.
- Judge the thing you care about, not a proxy for it. A redirect's status code is not its destination.
What I'd do next
Two habits, both cheap. A new public route has at least three registrations to answer for: the shell list, the sitemap and the accessibility scans. Only the first is now guarded by something that reads the tree. The status page was missing from both accessibility scans too, and I only caught that by walking the repository's own checklist, which is a habit rather than a check.
Then the reviewing habit. Before asking anyone for a read, write the two or three questions that nothing in the diff's own checks would answer. On this branch that list would have started with "what does this page look like", which is the one question 67 HTTP checks cannot ask.
When I wrote this, one thing was honestly unfinished: /health-status had never rendered a real payload. It was verified against a stand-in, because the monitoring status page it reads had not been published yet, and the split-mode runs in this post only ever saw its unconfigured "not available yet" state.
Update, 2026-09-28: that changed today. status.hearso.com now renders its monitors from a public Uptime Kuma on our VPS, with all four reading "Operational", and /health-status on the marketing host redirects there. A new version with 90-day uptime bars per service and live refresh merged as web PR #532 at 18:37:58Z, and it waits for the next release.
Evidence
All times UTC, all 2026-09-20 unless stated. Paths are relative to the web repository unless stated.
- The review allowance: eight reads at 22:24, 22:55, 23:26, 23:59, 00:27, 00:58, 01:58 and 02:27, one refusal streak of over forty minutes after a 48-file read, and eight pull requests waiting:
artifacts/plan/2026-09-20-overnight-handoff.md, section 0 and section 1 item 2. - 34 findings from four local reviewers, 33 fixed and 1 accepted:
artifacts/plan/2026-09-20-local-review-ledger.md, section A. Correctness C1-C6, security S1-S10, test quality T1-T12, conventions V1-V6; the accepted one is S7, the cache headers of the operations area's 404. - The finding I raised myself (a layout renders when its page refuses): the same ledger, entry T13, closed in
ecc69059, proven with three planted files. - 18 mutants applied, 18 killed, each after asserting the file's bytes changed:
artifacts/plan/2026-09-20-combined-domain-pr-body.md, verification line. - 49 of 49 and 18 of 18 before the finding (build
76a2f0ae):live-run-2-classic-49-of-49-before-the-finding.out,live-run-platform-1-18-of-18-before-the-finding.out. - 29 of 31 on the first attempt, and the two wrong expectations:
live-run-1-first-attempt-two-wrong-expectations.out; the corrected checks are inlive-check-classic.sh, with the reason in a comment above each. - The
//libraryfinding (the link helper throws, so the interceptor answers 500): the ledger, security finding S2, fixed in67db6ac5. - No open redirect, measured, and the ten-control
safe_locationfunction that judged eachLocation:live-check-classic.sh; the ledger's end-to-end evidence section; the eight redirect rows inlive-run-2-...outandlive-run-3-...out. - The port-25 probe whose control also read closed:
artifacts/plan/2026-09-20-overnight-handoff.md, section 5. - 7 of 7 in a real browser, and both accessibility scans passing with the new page: the ledger's end-to-end evidence section (opt-in hostname run, split build, platform shell).
- 8,347 tests across 446 files at the head the pull request opened on: the same section. On the fixed head
703aee91: 447 files, 8,354 tests,tsc0,eslint0, recorded in the commit message. - Web PR #447: 70 files, opened with a read requested at 06:37:19Z, one comment at 06:45:09Z, 7 min 50 s later: the hosting service's own
createdAtfor the pull request and its timeline'sreview_requestedandreviewedevents, read on 2026-09-20; the comment itself is quoted fromapp/health-status/page.tsx:68. (The ledger first said 06:36Z, which was written from memory and was wrong; it is corrected there.) - The fix: commit
703aee91, 2026-09-20T06:57:12Z.lib/platform/routes.ts,app/site-shell.test.tsx,lib/platform/dual-shell-footer.test.tsand the newlib/platform/self-framed-pages-are-registered.test.ts(115 lines added across four files). - The guard, its eleven paths and its
pagesExaminedassertion:lib/platform/self-framed-pages-are-registered.test.ts; with the route removed from the list it fails and names it. - 54 of 54 and 22 of 22 after (build
703aee91), with/boardand/roadmapas controls:live-run-3-classic-54-of-54-counting-frames.out,live-run-platform-2-22-of-22-counting-frames.out. - The two pull requests before it:
artifacts/plan/2026-09-20-overnight-handoff.md, section 3. Web PR #445 (one major finding, two origins accepted on one hostname, which would have been an endless redirect) and web PR #446 (two findings, both fixed). - The status page had never seen a real payload on 2026-09-20: the same handoff, section 1, item 4.
- The status page live with real data, 2026-09-28:
artifacts/plan/2026-09-26-overnight-handoff.md, the 2026-09-28 entries: status.hearso.com renders the four monitors "Operational", the "not available yet" text is gone, and/health-statusonwwwredirects there. The 90-day bars with live refresh: web PR #532, merged 2026-09-28T18:37:58Z into the development branch, not yet released.
Nothing above contains a credential or an internal hostname; the live runs use www.localhost and app.localhost, and evil.example is a reserved example name.