What changed, and why
The 23 Glassbox lessons shipped as islands. Their prose was full of sibling references โ bloom-filters' coda names HyperLogLog and cuckoo, torrents leans on SHA-256 and Merkle trees, saga rests on ACID โ yet not one of them was a link. This change adds the connective tissue and the loop that keeps it honest.
Two new pieces carry the weight, and the rest is them applied. **LessonLink** is the cross-lesson primitive: a real, crawlable ?lesson=<id> anchor that soft-navigates inside the SPA. **constellation** is a new Many Hands Engineering loop โ but the odd one out: it launches no agent. It is a deterministic scan that prints RESULT: PASS/FAIL, the way a test does, and you drive it to green by wiring links. Then ~35 links were wired across the collection, and the mentions that should stay plain were recorded with reasons.
Read the loop first (it defines the contract), then the primitive, then one real link, then the test that pins it.
constellation: a loop you run like a test
Every other loop in agents/ reasons with a model. This one only measures. It builds a vocabulary of distinctive sibling terms from the catalog, scans each lesson's prose for a term that names another lesson, and reports every one that isn't already inside a <LessonLink>. The whole thing is pure Node.
The vocabulary is curated by hand, case-sensitive, matched on alnum boundaries โ so "trie" ignores "tries", and short ambiguous ids lean on the ignore file rather than firing on every common word.
The term vocabulary, the masking + boundary regex, the scan, and the PASS/FAIL report.
agents/constellation.ts ยท 177 lines
โฏ 34 lines hidden (lines 1โ34)
โฏ 21 lines hidden (lines 59โ79)
โฏ 1 line hidden (lines 102โ102)
โฏ 1 line hidden (lines 145โ145)
โฏ 1 line hidden (lines 177โ177)
The intentionally-plain record
Not every sibling mention should be a link. Repeats clutter, homonyms mislead, and some prose can't host a link at all. Those decisions live in a committed JSON file, keyed by lesson then term, each with a reason โ the persistent, auditable version of "keep this plain". Drive the loop to green by either wiring a link or adding an entry here.
The honest cases: a dangerouslySetInnerHTML block, a flood-not-BitTorrent homonym, the SHA construction.
agents/constellation-ignore.json ยท 75 lines
โฏ 24 lines hidden (lines 7โ30)
โฏ 16 lines hidden (lines 34โ49)
โฏ 23 lines hidden (lines 53โ75)
LessonLink: the cross-lesson primitive
The whole component is 42 lines. It renders a real ?lesson=<id> anchor (so it's crawlable and a build step could harvest the graph from hrefs), and consumes a NavContext to soft-navigate without a reload. Modifier and middle clicks fall through to the browser, so open-in-new-tab still works.
src/shared/LessonLink.jsx ยท 42 lines
src/shared/NavContext.js ยท 8 lines
Wiring it into the shell
App.jsx already owned the navigation: selectPage(id) sets state and pushes the ?lesson= URL. The change provides that function through NavContext so a LessonLink deep inside a section can reach it without prop-drilling, and exposes the active lesson's accent as --lesson-link-color on <main>.
Provider around the active page; the accent var on <main>.
src/App.jsx ยท 111 lines
โฏ 68 lines hidden (lines 1โ68)
โฏ 25 lines hidden (lines 87โ111)
Ink text, accent-tinted underline, a visible focus ring.
src/shared/utilities.css ยท 243 lines
โฏ 185 lines hidden (lines 1โ185)
โฏ 31 lines hidden (lines 213โ243)
A link in practice: the bloom-filters coda
Most links were a one-line wrap of existing JSX text. The hard case is prose that lives in a plain string array. The bloom-filters coda renders a NEXT list of [title, body] string tuples โ you can't drop a <LessonLink> into a string. So the relevant entries became JSX fragments, preserving the exact words.
String tuples became fragments; the SSTable link sits in clean inline prose.
src/lessons/bloom-filters/sections/Coda.jsx ยท 73 lines
โฏ 9 lines hidden (lines 1โ9)
โฏ 26 lines hidden (lines 40โ65)
Pinning it, and the docs
A jsdom spec pins the three behaviors a reviewer would worry about: the anchor's href and default text, the soft-navigation through context, and the fail-soft on an unknown id. The loop's own contract is documented where the other loops are โ agents/README.md and AGENTS.md โ flagged as the deterministic, no-agent member you drive to green.