Skip to main content

Graph primitive reuse — non-obvious findings

The portability promise from TASKSET 3 paid off: zero changes to the primitive were required to make it serve a second consumer.

What changed

  • cairnet/src/components/charts/graph-primitive.tsx — verbatim copy of lore/src/components/charts/graph-primitive.tsx. Copy, not workspace import — sdk-js workspace linking still pending.
  • cairnet/src/components/cairn/stone-thread-graph.tsx — new adapter mapping (root: Stone, replies: Stone[]) to GraphPrimitive’s { nodes, edges } shape. Click handler navigates to /stones/:id.
  • cairnet/src/components/cairn/thread-graph-panel.tsx — small client-component disclosure wrapping the graph; default-closed, null-renders when replies.length === 0 to avoid an inert single-node canvas.
  • cairnet/src/app/stones/[id]/page.tsx — renders <ThreadGraphPanel> between the root StoneCard and the reply list.

Decisions that ran against the spec, and why

1. Skipped the /explore graph. The campaign mentioned surfacing the primitive on /explore for “reaction-weighted clusters.” On inspection that surface has no meaningful edges — /api/cairn/explore returns flat top-N lists for trending types and trending agents, not relations. A graph there would have been decorative theatre, violating the project’s “no features beyond what the task requires” rule. If we later add agent-to-agent connection signal (co-reaction, mention edges), revisit. 2. Adapter-only navigation, primitive stays route-free. The adapter calls useRouter() and pushes; the primitive only knows onNodeClick(id). This reproduces the LORE pattern (causality-graph) and means a third app could adopt the primitive with its own router without touching either app. 3. No type-color override. The primitive colors nodes by group (center/upstream/downstream/neutral). It would be tempting to extend node groups with stone-types so a “hypothesis” reply renders regolith and a “question” reply renders dust. Tempting, then declined: extending the group enum would be a primitive-level change, defeating the portability claim. Stone type stays in meta and surfaces in the hover tooltip only. If demand for type-tinted nodes proves out, the right path is making the primitive accept a getNodeColor(node) callback — a backwards-compatible extension, not a breaking enum. 4. Single-node graphs hidden. When a root has no replies, the panel renders nothing rather than a lone center dot. Confirms the rule: visualisation is signal, not chrome.

Footguns surfaced

  • useRouter in adapter, not primitive. The cairn adapter is a client component because of the router hook. The primitive is also a client component (interactive SVG). Both are correctly marked "use client". Forgetting either marker would surface as a build error rather than a runtime one — Next 14 is good about this.
  • Dual-source-of-truth risk for the primitive. With the file now copied into both lore/ and cairnet/, drift is inevitable. The campaign already accepted this cost (“if reuse breaks, CAIRN forks a copy — acceptable cost”). The mitigation is the next item in the sdk-js workspace backlog: extract @devarno/sdk-charts so both apps depend on a single artifact.

Verification done

  • grep of cairnet primitive imports: only react. Zero LORE, cairnet-app, or api-client references.
  • npx tsc --noEmit clean.
  • npx next build succeeded; /stones/[id] route bundle grew 202 B → 2.8 kB, accounting for the primitive (~1.5 kB gzipped) + adapter + panel disclosure. All other route bundles unchanged.
  • Click navigation: tapping a reply node pushes /stones/<reply-id>; tapping the root is a no-op (root === current).

What does NOT exist yet

  • No /explore graph (intentional, see decision #1).
  • No nested-thread support — the adapter takes a flat replies: Stone[], mirroring v0 pebble shape. Recursive nesting is a 5-line change in the adapter when pebble starts returning a tree.
  • No agent-graph visualisation — different problem (would need edges from co-reaction or follow signal that pebble doesn’t compute yet).

Verification checklist (campaign §“TASKSET 6”)

  • Same primitive serves both LORE (causality graph) and CAIRN (thread map) — zero copy-paste of renderer logic; zero edits to the primitive in either repo.
  • Adapter is the only file that knows about cairn types and cairn routes.
  • Build green; bundle delta accounted for.