feat: Universe Builder relationship graph, timeline scrubber, gaps panel and poster export - #6679
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Graph tab to the Universe Builder that draws a whole universe as one picture, from records the user has already authored.
relationshipLinksand objectattachmentson 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.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:faction/species/lorecame 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.mentionedge type folds intoappearance.Server
GET /api/universe-builder/:id/graph(newserver/services/universeGraph.js+routes/universeBuilder/graph.js). Read-only aggregation over the universe record andgetUniverseCanonUsage— 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/libbarrel + catalog:universeGraphModel.js— palette,indexGraph,computeUniverseGapsuniverseGraphLayout.js— force sim with per-kind collision, radial/timeline anchors, fit, hit-test. Deliberately distinct fromgraphSimulation.js(3D, no anchors/collision/fit — different problem, no shared parameters); the header says so.universeGraphPoster.js— onerenderPosterdrives both the preview and the 2× download, so they can't divergeTest 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,firstIssueanchoring, 404 mappingclient/src/lib/universeGraphModel.test.js— 13 tests over the index and each gap category, including the thresholds that keep a suggestion quietclient/src/lib/universeGraphLayout.test.js— 12 tests: determinism, coincident-node separation, spring convergence, anchor lanes,totalIssues: 0, empty-graph fit, hit-testclient/src/lib/universeGraphPoster.test.js— 13 tests: every layout × size × theme renders, 2× scaling, an empty universe doesn't divide by zeroclient/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 filterFull suites green: server 2115 files / 42274 tests, client 913 files / 11229 tests,
biome lintclean,vite buildclean.The rebase onto current
mainpicked up three new tree-wide guards; the diff complies with all of them (44px touch targets on the two icon-only Close buttons,dirtyRefno longer seededtrue, and the timeline's playback interval registered inpollingConventions.test.js's allowlist with a reason — it advances a frame and does no I/O).