The in-chapter TOC needed live JavaScript. It still got a mockup first.

The scratchpad/learning-toc-mockup.html file had an IntersectionObserver in it. Not a static wireframe showing where the TOC panel would sit — a live scroll-spy, watching real headings from actual chapter content, updating active states as you moved down the page. CJ approved it. Then the production component got written.

That detail is the one worth pulling out of the learning hub navigation retrofit.

The programme

The captainrandom learning hub needed a navigation layer. The spec lived at docs/specs/learning-hub/navigation.md, broken into phases: P2 through P5, then a closeout. The hub was already live when the navigation work started. The retrofit was additive — new wayfinding layered onto a working subsystem, not a rebuild. The existing hub content and structure stayed in place; the phases added navigability around it.

Each phase had a scope and a branch. feat/learning-nav-p2-tracks, feat/learning-nav-p3-sidebar, feat/learning-nav-p4-toc, feat/learning-nav-p5-prereqs. Phases shipped in sequence; each merged before the next opened.

The constraint that ran through all of them: no production code until an interactive approval mockup existed and had explicit sign-off.

P2: re-establishing the gate

P2 shipped the hub track sections and S1 JSON-LD — the first visible navigation change in the programme. It was also the first time the full UI gate had run on the learning hub since the original hub build.

That second point matters more than the first. The mockup-first gate had been established for the wider site but hadn’t been applied to the hub since v1. P2 built the interactive approval mockup that re-established it — the first concrete demonstration of what the gate looked like for a navigation change on the hub. Every phase from P3 onwards inherited the same constraint with no negotiation required.

P3: highest impact, no exception

P3 added the persistent course rail to every chapter page. The devlog calls it the biggest browsability lift in the programme. Before P3, a reader on a chapter page had no view of the course structure around them — no sense of where the current chapter sat relative to what came before or after.

It shipped mockup-first. Higher impact didn’t create pressure to skip the gate. The approval object was an interactive layout showing the persistent rail alongside a representative chapter page. CJ approved the layout before the component was written.

P4: the client island

P4 closed N2: the “on this page” right rail. It’s the subsystem’s one client-side component.

The learning hub is a static build. Most navigation is rendered from known data at build time — course structure, chapter order, prerequisite relationships. An in-chapter TOC that tracks the active heading requires the reader’s scroll position, which isn’t available until the page is in the browser. The component has to be a client island. There’s no server-rendered path for scroll-spy behaviour.

The mockup for P4 was scratchpad/learning-toc-mockup.html. It had a live IntersectionObserver watching real headings from actual chapter content. Active state updated correctly as you scrolled. The mockup existed before the React component did.

This is the phase where the gate is most easily argued away. The reasoning is available: a client-side component with real interaction behaviour can’t be meaningfully evaluated in a static HTML file, so ship it in production and approve the real thing. The P4 mockup made that argument false. A standalone HTML file with a live IntersectionObserver is sufficient to validate scroll-spy behaviour — you can see whether the active state updates correctly, whether the TOC panel sits in the right position, whether the heading detection fires at the right threshold. CJ approved all of that in the mockup. The production component was an implementation of something already agreed, not an experiment he saw for the first time at code review.

P5: the highest-leverage fix

P5 shipped the N1 “Read first ↳” prerequisite chips at the chapter head. The conventions.md backlog had flagged these as the single highest-value navigability fix in the programme, tracked under B-037. P5 delivered them.

Prerequisite chips are server-rendered. The complexity was in surfacing the correct prerequisite data per chapter, not in interaction behaviour. Mockup-first still applied. The gate doesn’t vary by implementation complexity.

Closeout

After P5, the programme ran a backlog reconciliation. conventions.md picked up per-item statuses across the M/E/N/S tracking categories — M1, E2, and the rest, each marked against what shipped versus what was scoped. The programme now has a legible endpoint: you can read it as a completed unit rather than inferring completion from the absence of open branches.

A living atlas was created to give the navigation work a navigable document of its own.

Why the constraint held

The UI gate — interactive mockup, explicit approval, then production code — is a commitment device. It forces a visual and behavioural contract to be agreed before implementation begins. The approval object is a working artefact, not a description of one.

The discipline is worth naming precisely because the counter-argument for skipping it is strongest exactly when the gate is most valuable. A client-side scroll-spy feels like behaviour that only makes sense in the browser, in the real component, wired into the real content. That intuition is mostly wrong. The IntersectionObserver’s trigger logic, the TOC panel’s layout at different content lengths, the active-heading update behaviour — all of these are evaluable in a standalone HTML file. The browser doesn’t care whether it’s running a mockup or a React page. The behaviour is the behaviour.

When the constraint applies to every phase without exception, the retrofit stays coherent across five branches. Each phase hands off to the next with an approved visual contract. The backlog reconciliation at the end reads against a spec that was delivered as written. That’s what the gate is for — not to add process overhead, but to ensure that what ships is what was agreed, from start to closeout.

All writing