Skip to content

feat: Universe Builder relationship graph, timeline scrubber, gaps panel and poster export - #6679

Merged
atomantic merged 1 commit into
mainfrom
cos/task-mttin1b2/agent-b2c5ce09
Sep 9, 2026
Merged

feat: Universe Builder relationship graph, timeline scrubber, gaps panel and poster export#6679
atomantic merged 1 commit into
mainfrom
cos/task-mttin1b2/agent-b2c5ce09

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

Adds a Graph tab to the Universe Builder that draws a whole universe as one picture, from records the user has already authored.

  • Canvas — canon characters / places / objects, the typed relationshipLinks and object attachments on them, the series + issues they appear in (via the existing canon-usage cross-reference), and their rendered references. Force, radial, and timeline layouts; filter by node kind or link family; search; drag to rearrange; double-click to focus a neighbourhood.
  • Timeline scrubber — replays the universe issue by issue. Entries not yet introduced draw as ghosts, so you can see the world as it stood at any point in the run.
  • Inspector — a node's dossier (Three Sliders, the Ghost→Wound→Lie→Need→Want chain, the five-stage evolution lens, relationships, attachments, appearances), or a Gaps & enrichment list: isolated canon, places whose issues hold no cast, one-directional relationships, entries with no render, plus suggestions where two characters share several issues without a typed link or a well-used character has no lens. Every finding is a pure derivation — no LLM call.
  • Poster builder — exports a print-ready 2× PNG in five layouts (cast roster, character dossier, place atlas, timeline strip, relationship web), three sizes, light or dark paper.

The selected node lives in the URL (?node=<id>), so a specific character's neighbourhood is shareable, bookmarkable and reload-safe; a stale id is dropped once the graph loads.

Design fidelity

Implemented from the Claude Design canvas Universe Graph.dc.html. Four places where the mock assumed data PortOS records don't carry, resolved toward what is actually derivable:

  • Node kinds — the mock's faction / species / lore came from category buckets with no structured entity or link. Dropped rather than invented; the eight real kinds are character, place, object, series, issue, image, composite sheet, mood board.
  • Prose mentions — canon-usage doesn't distinguish a mention from an appearance, so the dashed mention edge type folds into appearance.
  • Evolution stage anchors — a stored lens has no issue anchor, so stages render authored / not-authored instead of pinned to an issue.
  • Faction chart poster — replaced with a relationship web (the whole cast on a circle, typed links as chords), which the records do support.

Server

GET /api/universe-builder/:id/graph (new server/services/universeGraph.js + routes/universeBuilder/graph.js). Read-only aggregation over the universe record and getUniverseCanonUsage — no writes, no new stored state, no LLM calls, so nothing here can fire a cold-bootstrap provider call. Node ids are namespaced by kind (character:<entryId>), so a place and a character sharing a canon id can't collide.

Client modules

Three new pure libs, all registered in the client/src/lib barrel + catalog:

  • universeGraphModel.js — palette, indexGraph, computeUniverseGaps
  • universeGraphLayout.js — force sim with per-kind collision, radial/timeline anchors, fit, hit-test. Deliberately distinct from graphSimulation.js (3D, no anchors/collision/fit — different problem, no shared parameters); the header says so.
  • universeGraphPoster.js — one renderPoster drives both the preview and the 2× download, so they can't diverge

Test plan

  • server/services/universeGraph.test.js — 15 tests: id namespacing (a place and a character sharing a canon id; every id in the payload resolving against the node list), framework/slider projection (absent vs empty), image-node cap, one-directional relationships preserved, dangling link targets dropped, composite→canon matching, global issue ordering across series, firstIssue anchoring, 404 mapping
  • client/src/lib/universeGraphModel.test.js — 13 tests over the index and each gap category, including the thresholds that keep a suggestion quiet
  • client/src/lib/universeGraphLayout.test.js — 12 tests: determinism, coincident-node separation, spring convergence, anchor lanes, totalIssues: 0, empty-graph fit, hit-test
  • client/src/lib/universeGraphPoster.test.js — 13 tests: every layout × size × theme renders, 2× scaling, an empty universe doesn't divide by zero
  • client/src/components/universeBuilder/graph/UniverseGraphTab.test.jsx — 11 tests: overview counts, error/empty/unsaved states, URL selection round-trip, stale ?node= cleanup, gap filtering, timeline scrub, kind filter

Full suites green: server 2115 files / 42274 tests, client 913 files / 11229 tests, biome lint clean, vite build clean.

The rebase onto current main picked up three new tree-wide guards; the diff complies with all of them (44px touch targets on the two icon-only Close buttons, dirtyRef no longer seeded true, and the timeline's playback interval registered in pollingConventions.test.js's allowlist with a reason — it advances a frame and does no I/O).

Universes now carry a Graph tab that draws the whole world as one picture:
canon characters, places and objects, the typed relationships and object
attachments authored on them, the series and issues they appear in, and their
rendered references. Force, radial and timeline layouts; filter by node kind or
link family; search; double-click to focus a neighbourhood.

A timeline scrubber replays the universe issue by issue, so you can see the
world as it stood at any point in the run, with entries not yet introduced
drawn as ghosts.

The inspector doubles as a "gaps & enrichment" panel: isolated canon, places
whose issues hold no cast, one-directional relationships, entries with no
render, and suggestions where two characters share several issues without a
typed link, or a well-used character has no evolution lens. Every finding is
derived from records the user already authored — no LLM call.

A poster builder exports the graph as a print-ready PNG in five layouts (cast
roster, character dossier, place atlas, timeline strip, relationship web), in
three sizes and a light or dark paper.

Server side, GET /api/universe-builder/:id/graph aggregates the universe record
with the existing canon-usage cross-reference. Read-only: no writes, no LLM
calls, no new stored state.
@atomantic
atomantic merged commit 18d0d96 into main Sep 9, 2026
12 checks passed
@atomantic
atomantic deleted the cos/task-mttin1b2/agent-b2c5ce09 branch September 9, 2026 03:46
@atomantic
atomantic restored the cos/task-mttin1b2/agent-b2c5ce09 branch September 9, 2026 03:46
@atomantic
atomantic deleted the cos/task-mttin1b2/agent-b2c5ce09 branch September 9, 2026 03:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant