Skip to content

Graph Core Refactors & Architecture Doctrine

Doctrine: Graph as exposure layer, not universal store

The graph is an exposure/adaptor layer — it does not own the objects it exposes. Domain data lives wherever it naturally belongs: entity data in entity stores (entities/string/, entities/code/, etc.), activity-owned world surface state in activity sessions, LLM conversations in the LLM store (llm/), filesystem nodes on the real filesystem, kernel instances in the kernel system. The graph creates lightweight adapter nodes that project these domain objects into a uniform navigable topology.

Graph core persists only user-materialized topology — which visible node keys exist and which visible edge connections exist (graph:capture-state / graph:restore-state). Owning systems persist the actual object data. There is no "full graph state backup" that captures domain data; capture-state stores node keys and edge source/target keys only.

Related objects become graph-visible only through explicit preview, view, search-row, or action controls. A node may offer actions such as Show Code, Show Details, Start, or a searchable picker that loads a selected key into the current graph map and adds any display edge needed for that user-visible relationship. Graph code should not rely on hidden relationship expansion hooks to bulk-materialize neighboring records.

Canonical terminology

UseAvoid
graph-exposed object, graph-visible objectgraph-backed object, graph object
graph node adapter, projectionfirst-class graph object
entity-store-backed node, world-state-backed node, LLM-store-backed nodebacked by the graph
graph topology state (node keys + edge keys)full graph state, graph data, graph node data
exposed through the graph, visible in the graphlives in the graph, stored in the graph
graph as universal interface / exposure layergraph as universal model
domain objectgraph-native object

Key architectural facts

  • graph/core.fnl: nodes are lightweight records (key, label, color, view ref, graph ref). capture-state stores node keys and edge source/target keys only — no domain data.
  • graph/key-loaders.fnl: each loader adapts its owning store/system into a graph node on demand via load-by-key. Entity loaders adapt entity stores; LLM loaders adapt the LLM store; world activity and surface loaders adapt world-manager and WorldData.
  • graph/map.fnl: graph maps hold the visible topology a user has materialized in that interaction context. Preview/search/action code loads keys through the active GraphMap and inserts explicit display edges when the user asks to reveal related records.
  • graph/world-data.fnl: activity-owned scene/HUD/canvas state is resolved from world.state.activity.sessions.<activity-id> through WorldData helpers. Activity-owned graph keys include both world-id and activity-id (for example activity-scene:<world-id>:<activity-id>, activity-background:<world-id>:<activity-id>, and activity-terrain:<world-id>:<activity-id>:<terrain-id>). Updates mutate the owning activity surface state, then sync to the active surface and persist world. Graph nodes are projections, not the source of truth.
  • Activity hierarchy keys expose world:<world-id>world-activities:<world-id>world-activity:<world-id>:<activity-id>activity-surfaces:<world-id>:<activity-id> before reaching concrete surface nodes such as scene, HUD, or canvas.
  • graph/nodes/*.fnl: node constructors receive stores/world-manager, resolve domain records from them, and emit signals when underlying data changes.
  • graph/view/: owns visual/interactive systems (ForceLayout, points, labels, selection, movables, persistence metadata). Graph nodes do not track view instances.

Extracted

  • Graph model: graph/core owns nodes/edges, add/remove/replace, and emits signals (node-added, node-removed, node-replaced, edge-added, edge-removed).
  • Graph view: graph/view (GraphView) owns ForceLayout, points, labels, selection, movables, persistence, and node dialogs. It listens to graph signals and can be dropped/recreated without touching the graph model.
  • Persistence: graph/view/persistence handles metadata load/save and scheduled writes.
  • Labels and LOD: graph/view/labels (GraphViewLabels) manages label creation, LOD switching, camera-distance debounce, positioning, and cleanup.
  • Selection: graph/view/selection (GraphViewSelection) owns selector wiring, selected-node bookkeeping, pruning, and signals.
  • Node view wiring: graph/view/node-views (GraphViewNodeViews) builds/drops node dialogs or HUD embeds.
  • Movables: graph/view/movables (GraphViewMovables) builds drag targets, registers/unregisters with app.movables, forwards position updates, and schedules persistence on drag end.
  • Layout and edges: graph/view/layout (GraphViewLayout) wraps ForceLayout, line updates, node positioning, and rebuilds.
  • View registry: graph/view/registry (GraphViewRegistry) centralizes view-only bookkeeping (indices, node/point maps, edge map), replacement, and deduping.
  • Utils split: graph/core/utils holds core-only helpers (glm vec coercion). graph/view/utils adds view helpers (label text truncation/wrapping) and re-exports the core glm helpers.

Label and LOD handling

  • Core now forwards node→point maps to graph/view/labels to build spans and reposition them based on camera distance; tune LOD thresholds and label styling there instead of in core.
  • Benefits: keeps HUD text concerns and camera logic out of core; makes it easier to tune LOD thresholds separately.

Selection and node views

  • Selection is owned by graph/view + graph/view/selection; update behavior there when changing selector policies or selection logging.
  • Node dialogs are handled by graph/view/node-views; tweak dialog builders or HUD embedding there without touching the graph model.
  • Graph nodes do not track view instances. Nodes expose a plain :view constructor (e.g. :view HackerNewsStoryView) and emit state via signals (rows-changed, items-changed, targets-changed, etc.). Views subscribe to those signals (or call node methods like fetch, open-entry, add-target) and stay UI-only so multiple views can hang off the same node.

What stays in core

  • Graph model: adding/removing/replacing visible nodes/edges and lifecycle (drop).
  • No visual state: layout, labels, selection, movables, points, or persistence belong in graph/view.

See also