Skip to content

feat([issue-4188]): synthesize a universe style guide from its linked mood board - #4247

Merged
atomantic merged 3 commits into
mainfrom
next/issue-4188
Aug 15, 2026
Merged

feat([issue-4188]): synthesize a universe style guide from its linked mood board#4247
atomantic merged 3 commits into
mainfrom
next/issue-4188

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

Phase 4 of #4188 — the final phase: a universe's linked mood board can now be distilled into its style guide.

  • POST /api/mood-boards/:id/synthesize-style (new, stateless) — feeds the board's collected content (text notes, captions, and the per-item prompt-from-media analyses Phase 3 persists) plus the universe's current style context to a user-picked API LLM, and returns { proposed: { styleNotes, influences }, diff, rationale, llm } — the same reviewable diff shape the art-reference analyzer produces (buildStyleReferenceDiff). Context is bounded by fragment count and an aggregate character budget, with the dropped-item count reported. Locked fields keep their current values at proposal time.
  • POST /api/universe-builder/:id/adopt-style (new) + adoptStyleGuide in crud.js — the reference-less sibling of addStyleReference's { adopt } half: one server-side queued write of styleNotes + influences that re-checks field locks against the freshest persisted record (mergeInfluencesWithLocks). Never a client wholesale influences PATCH.
  • "Synthesize style" in the Universe Builder — next to the mood-board picker (Bible tab): pick an API provider/model, synthesize, review the before/after diff, Adopt. Adoption routes through the draft hook's applyStyleReferenceResult bookkeeping (new adoptStyleGuideFromBoard), so the saved-style snapshot and update watermark advance and styleProbeDirty clears exactly as an art-reference adopt does.
  • StyleDiffPreview extracted from UniverseStyleReferences into a shared component so both adopt flows render the identical diff UI.

All LLM calls are user-triggered with a visible provider/model picker (AI Provider Usage Policy); no batch auto-analysis.

A codex review pass surfaced one P1 and three P2s, fixed in the second commit: the synthesis modal is remounted (keyed by universe+board) so a stale proposal can never be adopted into a different universe; adoption goes through the hook's saved-state bookkeeping instead of a bare draft merge; the context collector caps aggregate characters, not just item count; and modal backdrop/Escape dismissal is gated while a run is in flight.

This completes the issue: Phase 1 (persisted universe↔board link, PR #4192), Phase 2 (gallery/upload pickers + video items, PR #4203), Phase 3 (per-item analysis, PR #4234), Phase 4 (this PR).

Closes #4188

Test plan

  • server/services/moodBoardStyleSynthesis.test.js — context collection (notes/captions/analyses, skip empty media items, fragment cap, aggregate character budget), empty-board 400, happy-path proposal + diff + prompt content, locked-field preservation, bad-JSON 502, no-API-provider 503.
  • server/routes/moodBoard.test.js — synthesize route: 404 on missing board, validated pass-through to the service, strict-schema 400.
  • server/services/universeBuilder.test.jsadoptStyleGuide persists the pair with no reference attached; locks re-checked against the freshest record (locked notes/embrace kept, unlocked avoid adopts).
  • client/src/components/universeBuilder/MoodBoardStyleSynthesis.test.jsx — hidden without a board / disabled until saved; synthesize sends the draft style context; diff preview; adopt hands the proposal to the caller and closes; Adopt disabled on a no-change proposal; stale proposal discarded when the target universe changes.
  • client/src/components/universeBuilder/UniverseStyleReferences.test.jsx — unchanged and green after the StyleDiffPreview extraction.
  • Full server route sweep + touched services green (3,048 tests, bounded workers); universeBuilder client suites + draft hook green (102 tests); biome check clean.

…oard (Phase 4)

New stateless POST /api/mood-boards/:id/synthesize-style runs the board's
collected content (text notes, captions, and the per-item prompt-from-media
analyses) through a user-picked API LLM and returns a proposed
styleNotes/influences guide plus the same reviewable diff the art-reference
analyzer produces. The Universe Builder gains a Synthesize style flow next to
the mood-board picker: preview the diff, then Adopt persists via a new
server-side queued write (adoptStyleGuide / POST /:id/adopt-style) that
re-checks field locks against the freshest persisted record — never a client
wholesale influences PATCH. The StyleDiff preview is extracted from
UniverseStyleReferences into a shared component so both adopt flows render
identically.
…ping, context budget, busy-modal dismissal

The synthesis body now remounts (keyed by universe+board) when the target
changes, so a proposal generated for one universe can never be adopted into
another; a mounted-ref guard drops in-flight resolutions after unmount.
Adoption routes through the draft hook's applyStyleReferenceResult
bookkeeping (new adoptStyleGuideFromBoard) instead of a bare updateDraft, so
the saved-style snapshot and update watermark advance and styleProbeDirty
clears — exactly as an art-reference adopt. The board context collector now
bounds the AGGREGATE character budget (24k chars), not just the fragment
count, so a large board degrades to fewer items instead of a truncated or
rejected request. Modal backdrop/Escape dismissal is gated on the run state
(with a force path for the post-adopt close, whose busy flag clears in the
same tick).
@atomantic
atomantic merged commit e62c876 into main Aug 15, 2026
7 checks passed
@atomantic
atomantic deleted the next/issue-4188 branch August 15, 2026 03:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant