Universe Library — Plan
The web front-end for a demarkus universe: a reading room over a broker-fronted
knowledge system. Renders the library metaphor demarkus already has (bookshelf
server, librarian agent, LOOKUP card catalog, worlds-as-collections) into a
human-facing web app, so a newcomer can browse a universe and have the
architecture explain itself. Lives in its own repo on its own release cadence,
like the Obsidian plugin.
Why
Every demarkus surface today is agent- or terminal-facing (CLI, TUI, MCP,
Obsidian, the Claude Code plugins); the one third-party GUI (Caztor) is view-only
FETCH. There is no human-facing web view — the real barrier to understanding
demarkus. A universe library lowers that barrier by being self-explanatory: walk
in, browse the stacks, search the catalog, watch the librarian work.
Non-goals (hard)
- Not a Notion clone. No block model, no relational databases/views, no live
multiplayer co-editing — each fights the dumb-server / whole-doc-immutable-version core.
- Editing is a cataloging desk: whole-doc PUBLISH to a new edition (version).
Concurrency via the existing conflict-aware merge, not OT/CRDT.
- Metaphor is information architecture, not skeuomorphic chrome. No bookshelf
textures, no page-flip animation.
Architecture
A browser cannot speak QUIC/Mark. Go through the broker, which already terminates
HTTPS, owns identity (OIDC + auth-code/PKCE/DCR), and proxies the 14-tool MCP
surface to worlds.
The front-end is a server-rendered Go web app with htmx — not a SPA. The app
is fundamentally hypermedia (documents, links, navigation, a catalog you click
through), which is htmx's sweet spot and a poor fit for SPA client-state. This
collapses a tier: no separate REST/JSON projection consumed by a JS framework —
the Go server renders HTML (and htmx fragments) directly and is itself an MCP
client to the broker.
Browser (HTML + htmx) --HTTP / cookie--> Go app --MCP / HTTPS--> Broker --QUIC--> Worlds
~14kb htmx, JS islands html/template + goldmark identity, bookshelves
only where earned OAuth session, MCP client world routing
- Go app: html/template for pages + htmx fragments; goldmark renders markdown
to (sanitized) HTML server-side — same ecosystem as the rest of the project (the
TUI uses Glamour for terminals, web uses goldmark for HTML). Owns the human OAuth
session (tokens never reach the browser — the XSS boundary). As an MCP client to
the broker it reuses the broker's existing session model — the same path Claude
Code's MCP SDK takes; no new session design. A thin adapter, not a second brain
— the librarian principle holds (intelligence stays at the edge/agent).
- JS islands only where earned: a CodeMirror island for the editor (Phase 3),
SSE for the streaming librarian (Phase 4), a canvas graph later. Everything else
is htmx attributes.
- Single binary, no Node build — matches demarkus's single-Go-binary ethos.
SSR is the default, so Phase 5's shareable-links-that-render come free rather
than as bolted-on SPA work.
- Security: rendering org-authored markdown to HTML requires output
sanitization (bluemonday) — markdown-to-HTML XSS is a real path.
Identity model
Access is organization-scoped: the library card is your org identity via the
broker's OIDC (e.g. GCP org). No anonymous public tier for now — you need a card
to enter your org's universe. The exact card-issuance UX is deliberately left
open; the firm decision is the scope (per-organization), not the mechanism.
Consequence: the org login is the turnstile, so it lands in Phase 1, not later.
Phases
Phase 0 — Foundation spike
Repo, stack, and one doc on screen end-to-end. De-risks transport + server-side
markdown rendering + MCP session handling before any UI investment. Stays
auth-free by pointing at a local/dev world (or a public doc if one exists), so
OAuth is deferred to Phase 1.
- New repo
latebit-io/demarkus-library; CI, license, README.
- Go app skeleton: one route that FETCHes a doc, renders it with goldmark
(sanitized), serves HTML.
- Exit: a real demarkus doc renders in a browser, server-rendered.
Phase 1 — Library card + reading room
Org login is the turnstile; then browse and read the org's universe. Read-only.
- Library card: broker OAuth (auth-code + PKCE + DCR), org-scoped; cookie session;
tokens server-side only. Login/logout.
- World list / floor directory from the
root hub.
- Stacks: per-world LIST path tree, navigable (htmx fragment swaps).
- Document view: goldmark-rendered GFM; rewrite
mark://<world>/<path> links to
in-app navigation.
- Card catalog: LOOKUP search (debounced
hx-get, results as an HTML table to click-through).
- Editions: VERSIONS panel; view a historical version (fragment swap).
- Citations: backlinks panel (
mark_backlinks); a lightweight "cited by" list
(defer force-directed graph viz).
- Exit: an org member logs in, browses, searches, reads, and traces citations
across the org's universe.
Phase 2 — Restricted collections (within-org)
Finer-grained access on top of "you're in the org."
- Per-path read-auth respected: locked collections shown, unlocked when the
token permits.
- Per-user surface: home, recently read, bookmarks.
- Exit: a user sees only the collections their token permits within their org.
Phase 3 — The cataloging desk (writes)
- Markdown editor (CodeMirror island); live preview via
hx-post to a server
render. Save is a form POST to PUBLISH, creating a new edition.
- Conflict-aware merge surfaced in the UI (existing diff3) on concurrent edits.
- Create new document; archive (deaccession) soft-delete.
- Metadata/cataloging editor: title + tags + importance (feeds LOOKUP —
cataloging literally improves the catalog).
- Exit: a librarian creates, edits, re-catalogs, and archives docs from the
browser; whole-doc, version-per-save.
Phase 4 — The AI librarian (the differentiator)
- Embedded assistant driving the same MCP tools (LOOKUP/FETCH/graph/VERSIONS) to
answer natural-language questions, summarize a wing, trace citations.
- Streaming via SSE (
hx-ext="sse"): token stream + a live, visible tool-call
trace — watch the librarian work the catalog. Spike the SSE pattern before
committing the design.
- Claude via the API with the broker MCP surface wired as tools (MCP connector / tool use).
- Lands here because it needs a furnished building to work in.
- Exit: "where's our deploy runbook?" returns the doc with its catalog reasoning shown.
Phase 5 — Public face & polish
- Universe home as front-of-house: featured collections, recent acquisitions
(recent publishes), most-cited.
- Shareable public doc URLs with OpenGraph cards — already server-rendered, so
this is polish, not new infra. The adoption payoff: a shared link explains
demarkus on open.
- Catalog caching, accessibility, mobile reading, theming.
Cross-cutting decisions
- Front-end: server-rendered Go app + htmx + goldmark; JS islands only where
earned. (Alt considered: React/TS SPA — rejected; the app is hypermedia, the
SPA adds a JSON tier and a Node build for little gain.)
- Transport: the Go app is an MCP client over the broker MCP gateway. (Alt:
browser-direct MCP — rejected for token exposure.)
- Auth: organization-scoped OIDC (e.g. GCP org); no anonymous public tier for now.
Tokens server-side only; the browser holds a cookie session.
- Markdown: goldmark render + bluemonday sanitize.
- Graph viz: backlinks list first; force-directed graph (canvas island) deferred past v1.
- Repo: standalone
latebit-io/demarkus-library, own cadence (mirrors obsidian-demarkus).
Build model strategy
Build the project on Opus 4.8 by default — it is the most capable GA model,
state-of-the-art on long-horizon agentic coding, half the cost of Fable 5
($5/$25 vs $10/$50 per MTok), and this is a conventional Go + htmx web app where
its ceiling is more than enough. Keep Fable 5 as the escalation valve for the
few genuinely hard, high-leverage moments: the broker/MCP transport + auth-session
design, a concurrency or token-refresh bug Opus stalls on, or a one-shot decision
where being wrong is expensive. Pattern: plan and implement on Opus 4.8, escalate
a specific stuck or high-stakes sub-problem to Fable 5, drop back. Don't run the
whole build at the premium tier.
Resolved
- Front-end (2026-06-10): server-rendered Go + htmx, not a SPA. The app is
hypermedia; htmx collapses the REST/JSON tier, keeps one Go binary with no Node
build, and makes SSR (shareable links) the default. JS islands only for the
editor (CodeMirror), the streaming librarian (SSE), and the eventual graph.
- Library card (2026-06-10): scope to organizations (per-org OIDC, e.g.
GCP org). Issuance UX deferred; scope is the firm part. No anonymous public tier
— auth is the Phase 1 turnstile.
- World enumeration (2026-06-10): the
root hub is the world directory
for v1. Caveat: hand-maintained, so an unlisted world is invisible; add a broker
enumeration endpoint later only if the hub drifts.
- MCP session model (2026-06-10): same as the broker — the Go app is an
MCP client over the broker's existing session path (the route Claude Code's SDK
uses). No new session design.
- Build model (2026-06-10): Opus 4.8 by default, Fable 5 as the escalation
valve. See Build model strategy.
Open / still to confirm
- Whether the broker exposes any anonymous-readable surface at all (only affects
whether the Phase 0 spike can hit the broker directly vs a local dev world).
Naming
Repo demarkus-library (working). Product-name candidates: Athenaeum, Stacks, the
Reading Room, or literal "demarkus Library". TBD — not load-bearing.
First session in the new repo (handoff)
When the latebit-io/demarkus-library repo exists and a fresh Claude Code session
boots there:
- Fetch this plan:
mark_fetch /plans/universe-library.md from demarkus-soul —
it is the source of truth, not a local copy.
- Start Phase 0: scaffold the Go module, a single route that FETCHes one demarkus
doc and renders it through goldmark + bluemonday, and serve the HTML. Point at a
local dev world (or a known public doc) to stay auth-free.
- Resolve the one open question in passing — does the broker serve any
anonymous reads — since it decides whether Phase 0 can hit the broker directly.
- Exit Phase 0 when a real demarkus doc renders in a browser, server-rendered.
Then move to Phase 1 (library card + reading room).
Resolved (2026-06-18) — anonymous-read question SETTLED
The "Open / still to confirm" item — whether the broker exposes any
anonymous-readable surface at all — is settled. Recommendation: yes, a narrow,
default-closed, operator+author opt-in public carve-out; the org-scoped default
is unchanged. This refines (does not discard) the 2026-06-10 "no anonymous
public tier" resolution: org-only stays the default; public is a per-resource
exception both operator (broker WorldConfig) and author (visibility:public
metadata axis) must choose.
Key finding: the world read layer is already identity-blind (reads dispatch with
an empty bearer; tokens.toml is write-only), so the broker's requireAuth edge
is the sole org gate — anonymous-public is a fail-closed bypass for explicitly-
public resources, and is the inverse end of Phase 2's read-authz axis (build
on one shared evaluator). Full design, decisions D1–D7, leak-matrix tests, and
build order:
/demarkus-library/plans/phase-5-public-face.md.
Consequence for Phase 0/spike note below: the broker has no anonymous read
surface today, so a Phase 0 spike must still point at a local/dev world (or
build the public path first) — anonymous broker reads are a Phase 5 deliverable,
not an existing capability.
Related documents