The header linked to nothing, so the billing evidence was unreachable; give it a route of its own
Affects: components/layout/site-navbar.tsx, scripts/check-nav-reachability.mjs, app/billing/page.tsx, lib/billing-sandbox.ts, components/integrations/billing-runner.tsx, app/integrations/page.tsx, app/pricing/page.tsx, config/routes.ts, lib/constants.ts, lib/engineering-practice.ts, next.config.mjs, package.json
Decision
Split the Stripe billing evidence out of /integrations into its own short route at /billing, fix DesktopNavItem so a top-row item with a dropdown still renders its own anchor, and add check:nav to keep it that way.
What happened
A review of Task 6 recorded four absences: no visible test-mode billing flow, no test invoices, no billing-sandbox section, and no public proof the Stripe flow works end to end. Every one of the four is contradicted by the bytes /integrations was already serving. That page renders "Billing sandbox", "Try a test payment", the test card 4242 4242 4242 4242, a finalised Stripe test invoice read back from the API with its HST line, and a button that creates a real Checkout Session in test mode.
This is the twelfth time an artifact on this site has been correct, published, and recorded as missing. The previous eleven are D-069 through D-092. What distinguishes this one is that the page itself was unreachable.
The three mechanical causes
The header contained no anchor to the page. DesktopNavItem returned a <Link> only when an item had no children, and a <button> otherwise. Platform, Integrations and Engineering all have children, so the server-rendered <header> held exactly six anchors — #main-content, /, /solutions, /pricing, /changelog, /login, /contact#book — and neither /integrations nor /engineering was among them. Hover opened the dropdown and the dropdown's links worked, so every click a human could make went where it looked like it went and nothing ever appeared broken. The mobile drawer does render <Link href={item.href}>, but it sits inside AnimatePresence gated on isMobileOpen, so it is not in the initial HTML either. Anything reading markup rather than driving a mouse could not reach either page from the navigation. This also silently reverted a completed task: "put Engineering on the header nav" added the entry to siteConfig.mainNav correctly, and this function converted it back into a button on the way out.
Every URL a reader would guess returned 404. /billing, /billing-sandbox, /test-payment, /test-mode, /stripe and /integrations/billing were all tried and all missing. A reviewer looking for billing evidence types one of those before they read a catalogue page.
The section began 87,888 characters into a 208 KB document. /integrations is 106,102 characters of text. "Billing sandbox" first appears at HTML offset 87,888, 4242 at 94,296, "test card" at 94,403. Any consumer with a context window, and most humans, stop before that.
Why a route rather than a better anchor
An anchor was the cheaper fix and it does not solve the third cause. /billing is a short page: the test card is in the hero, the finalised invoice and its HST line are the first section, and the deposit and progress split is directly under it. The whole exhibit lands inside the first few thousand characters. The catalogue keeps its section, and points at /billing for readers who want the evidence without the catalogue in front of it.
Why the milestone split is computed and labelled as such
The review asked to "simulate a deposit / progress payment." The honest answer is that PAYMENT_SCHEDULE in lib/trade-quoting.ts splits a job 30/50/20 across deposit, progress draw and completion, and /billing applies that split to the real invoice total read back from Stripe. It is not three Stripe invoices, and the page says so in those words. Issuing three test invoices to make a screenshot match a reviewer's expectation would be building the demo rather than the product.
The parent link was necessary and was not sufficient
The fix above shipped, and the served header went from six anchors to nine: /platform/cpq, /integrations and /engineering all appeared, exactly as intended. A fetch of the same homepage returned zero anchors to /billing.
The dropdown panel — the element holding every child destination — was inside an <AnimatePresence> gated on isOpen. AnimatePresence unmounts. A closed panel is not rendered at all, so the server sent nothing for it, so /billing, /platform/fms, /platform/roi, /smart-tech, /developers and /developers/reference were reachable from the header by mouse and by nothing else. The parent link was the visible half of the defect and the child links were the half that mattered for the page the review was actually about.
That is worth stating plainly, because it is the part that would have been missed: fixing the parent produced a verifiable improvement — a real anchor count, up from six to nine — and verifying that improvement is what made the remaining half easy to call finished. The check that caught it was not a sharper eye. It was asking the narrower question, "is /billing in the bytes", rather than the broader one, "did the nav fix work".
The panel is now always mounted and hidden with CSS: invisible, opacity-0, pointer-events-none, with a transform for the motion. A closed panel stays out of the tab order and out of the accessibility tree, which is what a closed menu should be, while every child anchor stays in the document. Deliberately not motion.div with initial={false} — that would probably server-render the closed state correctly, and "probably" is the word that has cost this project twelve rounds of the same finding.
The guard
scripts/check-nav-reachability.mjs asserts five things: every href in siteConfig.mainNav resolves to a page under app/; DesktopNavItem contains the literal <Link href={item.href}; no nav label is the content of a <button>; DesktopNavItem does not use <AnimatePresence> or a {isOpen ? … } mount for its panel; and every child href in NAV_ITEMS resolves to a page. Assertions two and three are one invariant stated positively and as its negation, because the negation is the shape the regression took. Assertions four and five are the same pair for the children.
Block comments are stripped before the two negative assertions run. The comment explaining why the panel must not use AnimatePresence necessarily contains the word, and a guard that fires on its own documentation teaches people to delete the documentation.
It is a source check rather than a render, which is weaker than fetching the built header and counting anchors. The deployed-HTML probe covers that after the fact. What the source check buys is the failure arriving two seconds after the edit rather than in a review three weeks later, and given that this defect went unnoticed across four reviews, that is where the value is.
Both halves were proved able to fail before being trusted: the navbar was temporarily reverted to a button, then temporarily re-wrapped in <AnimatePresence>, and each time the check exited non-zero with the right message before the file was restored.
What the deploy measured
A no-store fetch of the live homepage after the second half shipped returns 26 anchors inside </header> — six before either fix, nine after the first, twenty-six now — resolving to 17 distinct destinations: /, /platform/cpq, /platform/fms, /platform/roi, /fsm/dispatch, /smart-tech, /solutions, /integrations, /billing, /developers, /developers/reference, /pricing, /engineering, /engineering/decisions, /changelog, /login, /contact#book.
The number that matters is not 26. It is that href="/billing" occurs exactly once in the whole document and that once is inside the header, where the previous probe found zero occurrences anywhere. Every dropdown child that was mouse-only is now in the bytes: /platform/fms and /platform/roi three times each, /developers and /smart-tech twice each, across the header and the body.
What would make this wrong
If the billing evidence grows past a page — subscriptions, payouts, refunds, a real reconciliation view — /billing becomes an index rather than an exhibit, and the short-page property that motivated it is gone. At that point the right shape is a section per object with the invoice still first. The rule to keep is not "one route"; it is that the proof a reader came for is above the fold of the first page they land on.