Skip to content

feat(universe-builder): Phase B — enrich expand contract with canon + plumb canon into arc prompts#267

Merged
atomantic merged 17 commits into
mainfrom
feat/ub-redesign-phase-b
May 17, 2026
Merged

feat(universe-builder): Phase B — enrich expand contract with canon + plumb canon into arc prompts#267
atomantic merged 17 commits into
mainfrom
feat/ub-redesign-phase-b

Conversation

@atomantic
Copy link
Copy Markdown
Owner

Summary

Phase B of the Universe Builder redesign (PLAN.md Next Up #1). Builds on the Phase A schema (#264) by enriching the LLM "Generate From Idea" contract to return first-class named canon and plumbing that canon into downstream prompts.

  • Server expand contract now returns characters[] / settings[] / objects[] with rich metadata (physicalDescription, palette, slugline, recurringDetails, etc.) alongside the existing categories + composite sheets. Each category also gets a kind tag so the Phase C UI knows which canon trunk to render it under.
  • Client handleExpand merges returned canon into the draft's universe.characters/.settings/.objects arrays. Existing entries always win on name/slugline collision so a re-expand can't clobber hand-authored or series-extracted records.
  • arcPlanner picks up a new worldCanonText context field via renderCanonForPrompt(world) — the LLM now sees named cast/places/objects alongside the existing worldCategoriesText exploratory variations. Folds in the "arcPlanner prompt context — include canon" backlog item.
  • Migration 019 auto-updates the pipeline-arc-resolve.md + pipeline-volume-verify.md templates to include the new {{worldCanonText}} block when unmodified; customized prompts get a manual-merge warning (mirrors the migration-003 pattern).

Files changed

  • server/services/universeBuilderExpand.jsbuildExpansionPrompt asks for canon + kind; isExpansionShape recognizes canon arrays; normalizeCanonArray(raw, kind) runs LLM output through sanitizeBibleList with BIBLE_SOURCE.UNIVERSE_EXPAND stamped pre-sanitize.
  • server/lib/universePromptRenderers.js — new renderCanonForPrompt(world), table-driven per-kind formatting.
  • server/services/pipeline/arcPlanner.js — wires worldCanonText into loadWorldContext + EMPTY_WORLD_CONTEXT.
  • client/src/pages/UniverseBuilder.jsxmergeCanonByName helper (dedupe by name + slugline, identity-preserving on empty input); handleExpand merges canon via pickCanon; toast dedupe via expandToast helper.
  • data.sample/prompts/stages/pipeline-{arc-resolve,volume-verify}.md — new "World canon" block above the categories block.
  • scripts/migrations/019-arc-verify-resolve-canon-context.js — hash-driven one-shot template upgrade.
  • scripts/setup-data.js — bumped shipped-MD5 entries (OLD becomes array of two hashes; NEW gets the post-019 hash).
  • server/routes/universeBuilder.js — JSDoc update to advertise the new expand return shape.

Test plan

  • cd server && npm test — 5108 passing, 5 skipped (was 5102 in Phase A, +6 new)
  • cd client && npm run build — clean
  • New tests:
    • EXPANSION_PROMPT contains characters/settings/objects + physicalDescription + slugline + significance (prompt contract)
    • EXPANSION_PROMPT teaches kind enum on categories (LLM hint matches Zod enum)
    • normalizeCanonArray x4 (non-array → []; character → bible shape; setting requires name OR slugline; object requires name)
    • arcPlanner: worldCanonText contains Mira Holt + field detective + The Tongue (canon context flows into prompt); placeholder branch asserts none
  • /simplify review applied: HIGH ×1 (BIBLE_SOURCE constant); MED ×4 (toast dedupe, table-driven renderer, mergeCanonByName short-circuit, pickCanon helper); 1 MED deferred → PLAN.md (mergeExpandIntoDraft extraction)
  • Manual smoke: open a universe, click "Generate From Idea", confirm canon entries appear in the canon section after expand
  • Manual smoke: trigger arc planning on a series with a linked universe, verify the prompt context (logged at data/runs/<id>/output.txt) includes the World canon block

Migration safety

Migration 019 is unmodified-only — data/prompts/stages/pipeline-arc-resolve.md and pipeline-volume-verify.md are replaced ONLY when their on-disk hash matches one of the two prior shipped hashes (pre-005 or current). Customized prompts get a warning + manual-merge instructions and stay unchanged. Idempotent: re-runs against the new hash are no-ops.

Known deferred

  • mergeExpandIntoDraft(draft, result) extraction from the now ~155-line handleExpand. Pure merge logic can be lifted out + unit-tested. Captured in PLAN.md under "Code quality / dedup".
  • Sweep for other prompt builders that read world.categories without world.canon (PLAN.md backlog item).

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 17, 2026 12:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enriches Universe Builder expansion output with first-class canon entities and makes linked-world canon available to downstream arc/volume prompt contexts.

Changes:

  • Expands the server/client “Generate From Idea” contract to include characters, settings, objects, and category kind.
  • Adds canon prompt rendering and wires worldCanonText into arc planner context/templates.
  • Adds migration/setup-data hash handling and tests/documentation for the new contract.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
server/services/universeBuilderExpand.js Adds canon arrays and category kind guidance to expansion output.
server/services/universeBuilderExpand.test.js Adds tests for canon normalization and prompt contract text.
server/lib/universePromptRenderers.js Adds canon-to-prompt rendering helper.
server/services/pipeline/arcPlanner.js Adds worldCanonText to linked-world context.
server/services/pipeline/arcPlanner.test.js Verifies canon context flows into arc planner calls.
server/routes/universeBuilder.js Updates route docs for expand response shape.
client/src/pages/UniverseBuilder.jsx Merges returned canon into the draft and auto-save payload.
data.sample/prompts/stages/pipeline-arc-resolve.md Adds World canon block to resolve prompt.
data.sample/prompts/stages/pipeline-volume-verify.md Adds World canon block to volume verification prompt.
scripts/migrations/019-arc-verify-resolve-canon-context.js Adds hash-gated prompt template migration.
scripts/setup-data.js Updates shipped prompt hashes for drift detection.
PLAN.md Marks Phase B canon context work as folded in and records follow-ups.
.changelog/NEXT.md Adds user-facing release notes for expansion canon and prompt canon context.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread client/src/pages/UniverseBuilder.jsx
Comment thread server/services/universeBuilderExpand.js
Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Comment thread scripts/migrations/019-arc-verify-resolve-canon-context.js Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.

Comment thread client/src/pages/UniverseBuilder.jsx
Comment thread server/services/pipeline/arcPlanner.js
Comment thread scripts/migrations/019-arc-resolve-volume-verify-canon-context.js Outdated
Comment thread server/services/universeBuilderExpand.js Outdated
Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Comment thread server/services/universeBuilderExpand.js Outdated
Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Comment thread .changelog/NEXT.md Outdated
Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Comment thread scripts/migrations/019-arc-volume-prompts-canon-context.js Outdated
Comment thread client/src/pages/UniverseBuilder.jsx
Comment thread server/services/universeBuilderExpand.js Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Comment thread server/services/universeBuilderExpand.js
Comment thread data.sample/prompts/stages/pipeline-arc-overview.md
Comment thread data.sample/prompts/stages/pipeline-arc-verify.md
Comment thread scripts/migrations/019-arc-volume-prompts-canon-context.js Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

Comment thread server/services/universeBuilderExpand.js Outdated
Comment thread scripts/migrations/019-arc-volume-prompts-canon-context.test.js Outdated
Comment thread scripts/migrations/019-arc-volume-prompts-canon-context.test.js
Comment thread server/lib/universePromptRenderers.js
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Comment thread server/lib/universePromptRenderers.js Outdated
Comment thread server/lib/universePromptRenderers.js Outdated
Comment thread PLAN.md Outdated
Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Comment thread client/src/pages/UniverseBuilder.jsx
Comment thread scripts/migrations/019-arc-volume-prompts-canon-context.test.js
Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.

Comment thread client/src/pages/UniverseBuilder.jsx
Comment thread server/services/universeBuilder.js Outdated
Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

client/src/pages/UniverseBuilder.jsx:713

  • This auto-save path has the same stale-merge problem as manual save: it refetches the server canon, then merges the draft's full canon arrays back in. Any concurrent delete or rename that happened while the LLM call was running can be undone because the old draft entry is treated as a new non-colliding addition. Merge only the entries introduced by this expand result, rather than the whole expandedDraft canon.
            characters: mergeCanonByName(fresh.characters || [], expandedDraft.characters || [], 'character'),
            settings: mergeCanonByName(fresh.settings || [], expandedDraft.settings || [], 'setting'),
            objects: mergeCanonByName(fresh.objects || [], expandedDraft.objects || [], 'object'),

client/src/pages/UniverseBuilder.jsx:946

  • While preserving pending expand additions, this also merges the draft's entire stale canon back over the server response. If the server response intentionally removed or renamed an entry (for example from another tab) while canonDirty is true, the old draft entry can be appended again because it no longer collides. Keep a list of the pending expand-created entries and merge only those into updated.
          characters: mergeCanonByName(updated.characters || [], d.characters || [], 'character'),
          settings: mergeCanonByName(updated.settings || [], d.settings || [], 'setting'),
          objects: mergeCanonByName(updated.objects || [], d.objects || [], 'object'),

Comment thread client/src/pages/UniverseBuilder.jsx
Comment thread client/src/pages/UniverseBuilder.jsx Outdated
atomantic added 15 commits May 17, 2026 08:32
…rrays + plumb canon into arc prompts

Universe Builder "Generate From Idea" now returns rich first-class canon
alongside categories:
  - characters[]: name, physicalDescription, personality, background, prompt, tags
  - settings[]:   name, slugline, description, palette, recurringDetails, ...
  - objects[]:    name, description, significance, prompt, tags

The expand LLM is also taught to tag each category with `kind` so the Phase A
data model lands them under the right canon trunk in the upcoming UI. Client
handleExpand merges canon into draft (existing entries always win on name/
slugline collision — server-side dedupe via sanitizeBibleList).

arcPlanner now feeds named canon into LLM prompts via a new worldCanonText
context field (sibling to worldCategoriesText). Migration 019 auto-updates
the pipeline-arc-resolve and pipeline-volume-verify templates on launch for
unmodified installs; customized prompts get a manual-merge warning.
Also forward LLM-returned category `kind` through normalizeCategories so
custom buckets land under the right canon trunk; use normalizeSlugline for
settings dedupe so dash/punct-variant sluglines collide; rename migration
019 to reflect the files it actually updates (pipeline-arc-resolve.md +
pipeline-volume-verify.md, not arc-verify).
… in all arc/volume prompts

Five Copilot follow-ups:

- handleExpand: build mergedCategories with both 'kind' (existing draft wins,
  LLM-returned kind as fallback) and 'variations', so the LLM/user-tagged
  bucket trunk survives the round-trip through ensureDraftCategories.
- arc-overview.md + arc-verify.md: render {{worldCanonText}} so the canon
  context fed by arcPlanner actually reaches the prompt (previously only
  arc-resolve + volume-verify referenced it).
- Migration 019 now updates all four arc/volume templates, renamed to
  019-arc-volume-prompts-canon-context.js; setup-data.js hash bumps mirror.
- normalizeCanonArray strips LLM-supplied id/createdAt/updatedAt before
  sanitize so a hallucinated/example id can't introduce duplicate canon ids.
- mergeCanonByName is kind-aware — settings use normalizeSlugline for both
  name and slugline (matches storyBible MERGE_CONFIG.setting.keyFields), so
  dash/punct-variant identifiers collide instead of duplicating.
…oast + complete changelog

Four Copilot follow-ups:

- normalizeCanonArray strips locked/sourceSeriesId/imageRefs/primaryImageRef
  alongside the previously-stripped id/timestamps. Without this, a
  hallucinated 'locked: true' from the LLM would silently lock new canon
  entries (blocking user edits via the lock UI), and stale sourceSeriesId/
  imageRefs would falsely attribute provenance + pin visuals.
- expandToast now reports NEW canon entries added by this expand (post-merge
  minus pre-existing), not the draft's running total. Re-expanding on a
  populated universe no longer claims credit for entries the user authored.
- mergeCanonByName is alias-aware for character/object kinds, matching the
  server's MERGE_CONFIG keyFields. Existing 'Ashley' with alias 'Ash'
  collides with an LLM-returned 'Ash' instead of duplicating.
- Changelog NEXT.md names all four templates updated by migration 019
  (arc-overview, arc-verify, arc-resolve, volume-verify), not just the
  two originally listed.
… boundary; gate Linked World prompt block on hasLinkedWorld; add migration 019 test
…rompt rendering, drift-catch + OLD→NEW migration test via applyMigration helper
… enrich canon renderer with personality/slugline/recurringDetails, fix mergeCanonByName within-batch dedupe, correct PLAN doc
…e on setSaving to prevent double-submit, exclude *.test.js from migration runner, refresh stale comment
…e payload, drop premature cap in characters-bucket fold, capture canon-clobber risk in PLAN
…plus manual Save still persists merged canon
…ory kind on updateCategory + handleGenerateInCategory
… edits (other tabs, NounsStage) aren't clobbered
…pand; defer canon-deletion-revert edge case to PLAN
Copilot AI review requested due to automatic review settings May 17, 2026 15:35
@atomantic atomantic force-pushed the feat/ub-redesign-phase-b branch from 4eb7602 to 3003b88 Compare May 17, 2026 15:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (2)

client/src/pages/UniverseBuilder.jsx:717

  • The refetch protects concurrent additions, but this merge still replays the entire stale draft canon onto the fresh server canon. If another tab deleted or renamed an existing canon entry while the expand request was running, expandedDraft.characters/settings/objects still contains the old entry and mergeCanonByName(fresh, expandedDraft, ...) will re-add it in the auto-save payload. This should merge only the entries newly returned by this expand (or track an additions ledger), not the full draft arrays.
          canonForPayload = {
            characters: mergeCanonByName(fresh.characters || [], expandedDraft.characters || [], 'character'),
            settings: mergeCanonByName(fresh.settings || [], expandedDraft.settings || [], 'setting'),
            objects: mergeCanonByName(fresh.objects || [], expandedDraft.objects || [], 'object'),
          };

client/src/pages/UniverseBuilder.jsx:950

  • While canonDirty is set, every canon-section update is merged with the entire previous draft canon. A delete/rename performed in the canon UI returns updated without the old entry, but d.characters/settings/objects still includes it, so this local merge can immediately put the deleted/renamed entry back into the draft and a later save can persist it again. This should preserve only pending expand additions rather than merging the full stale draft arrays.
          characters: mergeCanonByName(updated.characters || [], d.characters || [], 'character'),
          settings: mergeCanonByName(updated.settings || [], d.settings || [], 'setting'),
          objects: mergeCanonByName(updated.objects || [], d.objects || [], 'object'),

Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Comment thread server/services/universeBuilderExpand.js Outdated
Comment thread server/services/universeBuilder.js Outdated
Comment thread scripts/migrations/019-arc-volume-prompts-canon-context.test.js Outdated
Comment thread scripts/run-migrations.js Outdated
… reverted; normalize keys when folding retired characters bucket; fileURLToPath for path safety; corrected vitest glob comment
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Comments suppressed due to low confidence (1)

client/src/pages/UniverseBuilder.jsx:764

  • If this refetch fails, the code falls back to expandedDraft's full canon arrays and still sends them in the PATCH. Because canon arrays are replaced wholesale server-side, a transient GET failure can turn the auto-save into a stale-canon write that clobbers concurrent canon edits/deletions. For existing worlds, either abort the auto-save (leaving the user to Save) or omit canon unless the current server canon was successfully fetched and merged with the pending additions.
      if (selectedId) {
        const fresh = await getUniverse(selectedId).catch(() => null);
        if (fresh) {
          canonForPayload = {
            // Merge ONLY pending additions onto refetched server canon
            // (not the full stale draft). Without this, concurrent canon
            // deletions in other tabs/surfaces get resurrected because the
            // deleted entry is still present in the stale draft.
            characters: mergeCanonByName(fresh.characters || [], pendingCanonAdditionsRef.current.characters, 'character'),
            settings: mergeCanonByName(fresh.settings || [], pendingCanonAdditionsRef.current.settings, 'setting'),
            objects: mergeCanonByName(fresh.objects || [], pendingCanonAdditionsRef.current.objects, 'object'),
          };
        }
      }

Comment thread server/services/universeBuilderExpand.js Outdated
Comment thread server/services/universeBuilder.js Outdated
Comment thread client/src/pages/UniverseBuilder.jsx Outdated
Comment thread scripts/migrations/019-arc-volume-prompts-canon-context.test.js Outdated
Comment thread client/src/pages/UniverseBuilder.jsx
…e from other; abort save on refetch fail; import migration hash tables in test

- universeBuilderExpand.js + universeBuilder.js: fix isCharactersBucket to match
  variant keys like "character variations" / "Character_Variations" via
  /^characters?(_|$)/i — exact equality only caught "characters", not multi-word
  normalized forms (e.g. "character_variations")
- UniverseBuilder.jsx kind merge: allow a fresh LLM kind to supersede an existing
  'other' default so pre-Phase-B buckets saved as 'other' can be promoted on
  re-expand; user-curated non-'other' kinds are still preserved
- UniverseBuilder.jsx handleSave + handleExpand: abort the save when the server
  refetch fails for an existing universe instead of falling back to the stale
  draft canon, which would clobber concurrent canon deletions/edits
- 019 migration: export ACCEPTED_OLD_MD5 + NEW_SHIPPED_MD5; test now imports them
  directly so hash-table drift fails loudly; idempotency test asserts
  alreadyCurrent count via applyMigration with real tables
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Comment thread server/services/universeBuilder.js
Comment thread client/src/pages/UniverseBuilder.jsx
Comment thread server/services/universeBuilder.js
@atomantic atomantic merged commit 42a3b3f into main May 17, 2026
6 checks passed
@atomantic atomantic deleted the feat/ub-redesign-phase-b branch May 17, 2026 16:21
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.

2 participants