The Reading Room — design notes (draft)
Reading-first frontend for the universe library. Desktop only. Markdown-native, no hosted media, agent-maintained content, navigation by tag and link rather than search. Status: draft — under discussion 2026-06-12 (Fritz + Fable). Will be locked as ADR 0005 once the open decisions below settle.
Thesis
Most knowledge management feels exhausting because every click is a context amputation. Back/forward is a one-dimensional history: a single cursor on a line. Reading knowledge is not linear — it branches. You are on RFC A, it references B and C, and you want to return to A's argument with B still in view. A linear history cannot hold a branch, so the reader rebuilds context in their head on every pop. That reconstruction is the tax.
The Reading Room makes context spatial instead of temporal. The trail is laid out on screen; the reader never reconstructs where they were — they look left.
Governing principle: never make the reader hold in their head what the layout could hold for them. Panes hold the path. The margin holds the document's trust signals and the author's asides. The body holds only the argument.
Influences
- Andy Matuschak's working notes — stacked panes; a link opens a column to the right rather than navigating away.
- Tufte CSS — ~60ch reading column, wide working margin, sidenotes not footnotes, off-white/off-black, booktabs tables.
- Gwern "Sidenotes In Web Design" — margin-note rationale + implementation survey.
- adoc-studio Tufte layout — CSS-variable two-part layout (~650px body, ~250px margin).
- matt.roam.garden — hover link previews, backlinks, sliding layered notes.
- Maggie Appleton's garden — legible note maturity; status as first-class signal.
Pane mechanics
The invariant: the stack is always a single path, root → focus. Not a browsing history — the reasoning chain currently in view. Every rule defends this.
Link resolution (core rule). Click a link in pane N:
- Truncate the stack to N (discard everything right of it).
- Append the target as pane N+1.
- Exception: target already in the stack → don't duplicate; scroll it into view and focus it (handles circular references for free).
Truncation is the counterintuitive half: at A→B→C, returning to B to follow a different link abandons C — it's off the path, so it doesn't survive. Keeping it would turn the stack back into temporal history. C is one click away in B.
Active-link highlighting. Each pane marks which outbound link is open to its right. The stack is self-describing — the highlighted links ARE the breadcrumb, spatial and in-context.
Overflow. Panes are ~950px (60ch + margin); 1.5–2 full panes per screen. Overflow is the normal state, not an edge case. Never evict:
- Left panes collapse to a thin vertical spine (title + status badge).
- Focused pane + immediate parent render full; older panes are spines.
- Clicking a spine expands + focuses it — navigation, not a new branch; no truncation.
- New panes open at the right; canvas auto-scrolls to them.
- Only the focused pane shows its full margin; parents render body-only. The margin is an attribute of attention. (This is also the width math: 950 focused + ~700 body-only parent = 1650 fits 1920.)
Locked decisions:
- Canvas scrolls to reveal panes; panes do NOT resize to fit (resize produces unreadable columns past three hops). Shapes the layout engine.
- External (non-demarkus) links leave the graph — browser navigation, never a pane. The stack stays pure to the knowledge graph.
overscroll-behavior-x: containon the canvas — macOS two-finger swipe at the scroll edge must not trigger history-back mid-trail.
Trail sharing: the URL encodes the whole stack (ordered doc ids + focus), so sharing a link shares the reasoning chain, reconstructed exactly.
The margin
Two content kinds with opposite behaviors — don't mix:
Position-anchored (flows with text): sidenotes — author/agent written, anchored to a body point, vertically aligned to the referent. The Tufte core.
Document-level (pinned, position: sticky at margin top):
- Status — the trust signal. Unmissable badge; draft vs accepted look different at a glance (possibly muted body for drafts). The thing enterprise KM never does.
- Tags — clickable, the lateral-nav exit.
- Provenance — last touched, by which agent, when. Answers "can I trust this is current" for agent-written content.
- Backlinks ("referenced by") — the link graph made visible, each with a hover preview card (title + opening lines). The same preview component serves outbound body links — build once, use both directions.
Discipline: an empty margin is correct. Status, tags, provenance, backlinks, and the sidenotes the author actually wrote. Nothing else earns the space. A stuffed margin is a sidebar in costume.
What NOT to build
- No search box. Navigation is tags, links, backlinks. (Capability survives:
a tag click is
mark_lookupwith a tag filter rendered as a tag page — same backend, right affordance.) - No persistent sidebar file tree.
- No comments/reactions/collab cruft. Reading room, not workspace.
- No rich-text editing. Markdown in, rendered out (Phase 3 cataloging desk is markdown-source editing, not WYSIWYG).
Visual baseline
Body ~650px / 60ch · margin ~250px · off-white/off-black, color reserved for signal (status) · booktabs tables (three horizontal rules, no verticals) · mermaid + KaTeX (already shipped, v0.1.2 islands).
Feasibility (htmx-hard, ADR 0003) — assessed 2026-06-12, VIABLE
The unlock: the trail is server state encoded in the URL (/t/...), so
ADR 0003 rule 3 (no client state) holds. The server renders every link in
pane N already carrying its post-click trail URL (prefix-to-N + target) —
truncation, dedup, spine-expand, and active-link highlight all cost zero
client logic; the link IS the state transition. Browser back/forward becomes
temporal history OVER spatial states for free.
- Panes/spines/focus/pinned margin: server-rendered + CSS only.
- Hover previews: pure htmx (
hx-trigger="mouseenter delay:300ms once"→ server fragment). - Sidenotes: standard footnote syntax in markdown; goldmark AST transform inlines each note at its reference as a margin span; Tufte CSS floats it. Degrades to footnotes. Agent-writable.
- Only new JS in the entire design: ~5 lines of scroll-focused-pane-into-view
after htmx settle (htmx
show:is vertical-biased). Rides islands.js.
Hard requirement discovered: rendered-HTML cache. Naive full-canvas re-render = N world fetches per click; a 6-pane trail against the broker's 30/min budget = ~2 clicks/min then 429s. The design therefore REQUIRES a server-side rendered-HTML cache keyed (world, path, version) — a trail click fetches only the NEW document; the rest re-renders from cache (active-link highlight is a cheap post-process). demarkus versioning gives invalidation. With it, a click costs one read — same as today.
Status/maturity channel decision: body frontmatter is what we STRIP;
demarkus carries metadata out of band. Maturity rides metadata tags as an
axis (status:draft, status:accepted) — matches knowledge-system policy
axes, write-gate enforceable. Provenance comes from response metadata
(modified/version/agent) we already receive.
Backlinks dependency: the one margin item without a data source — library gateway has no backlinks verb; broker graph store is ephemeral (per-pod, crawl-populated). Phase behind the rest.
Phasing
- R1 — the Tufte pane (single doc): column + margin, pinned metadata block (status badge, tags, provenance), sidenote transform, booktabs styling, remove search box, add tag pages. Ships alone — the look + trust model.
- R2 — the trail engine: stack URL scheme, canvas, truncate/dedup/focus, spines, active-link highlight, rendered-HTML cache. Layout-shaping decisions lock here.
- R3 — previews + backlinks: hover cards both directions; backlinks when the data source lands.
- Universe view (mark_worlds floor) = the natural trail root: pane zero.
Open decisions (to settle before ADR 0005)
- Confirm killing the nav search box in R1 (capability survives as tag pages).
- Status-axis tag vocabulary (
status:draft|wip|accepted|archived?) and whether the write gate should require the axis. - Trail URL shape: path segments vs query param; stack depth cap (~10?).
- R1-before-R2 phasing vs straight to the trail engine.
- Backlinks data source: wait for broker graph persistence, or library-side crawl of the home world?
One-line synthesis
A Tufte-styled reading column with a working margin (sidenotes, backlinks, provenance), stacked panes for trail preservation, status as trust signal, lateral movement by tag. No search, no tree, no media. Everything in service of reading and trusting, nothing in service of the CMS. Most KM is the inverse.
Decisions settled 2026-06-12 (→ ADR 0005)
All five open decisions resolved; ADR 0005 locks the invariants. Key syntheses from the hash-out:
- Catalog-as-pane (decision 1): no global search box, but the card catalog joins the trail as a pane — tag clicks open it, and a filter input INSIDE the catalog pane keeps lookup easy without a bar to stare at. Search results live on the path.
- Frontmatter rendered friendly (decision 2): parse the stripped fence
and render keys into the margin's document-properties block. Metadata
channel stays authoritative; status badge reads metadata tag axis first,
frontmatter
status:fallback, absent ⇒ draft. - Graph-as-pane (decision 5, Fritz's "add into context, not break out"): click/hotkey in the margin opens the document's graph neighborhood AS A PANE; clicking a node continues the trail. Walking the graph IS building a trail — one model. The universe view is the same component at maximum zoom: the whole-universe graph as pane zero / the floor.
- Universe view needs research before building — what a special universe-scale graph view looks like (rendering approach, zoom levels, what aggregates at scale). Likely exercises ADR 0003's pre-agreed canvas island. Run research alongside R1/R2.