Skip to content

feat([issue-4188]): persist a universe's linked mood board on the record - #4192

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

feat([issue-4188]): persist a universe's linked mood board on the record#4192
atomantic merged 3 commits into
mainfrom
next/issue-4188

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

Phase 1 of #4188 (mood boards as a universe style-building tool): the universe record gains a persisted moodBoardId link, replacing the localStorage-only mood-board reference in the Universe Builder.

  • ServersanitizeTemplate accepts an optional moodBoardId pointer (mb-<uuid>, capped at MOOD_BOARD_ID_MAX), persisted only when set so existing records keep their on-disk shape. Accepted on create and PATCH (PATCHABLE_SCALARS); key-present with ''/null clears, key-absent preserves.
  • Sync gateuniverses schema version bumped 8 → 9. The pointer deliberately rides the wire (mood boards federate, so the id resolves on peers), and per the Structured character relationships + opposing-force tagging #1287/Object↔character emotional attachment / significance model #1288 precedent an additive wire-riding field must be version-gated or an older peer re-sanitizing the record would strip-then-LWW the link away. (The issue body guessed "no bump needed" — the codebase precedent says otherwise, so this deviates from the issue text there.) Hardcoded version fixtures in the sync/manifest tests updated. The field is hashed for conflict detection but not restorable — same treatment as mediaCollection.universeId (structural link).
  • ClientMoodBoardReferenceStrip gains a controlled mode (value/onChange): selection is owned by the caller, never falls back to the first board (a deleted board shows as unset rather than silently drifting), and offers one-click create-and-link named after the universe (newBoardName). The Universe Bible tab drives it from the draft (draft.moodBoardId), persisted through the normal dirty-gated Save. Pipeline.jsx keeps the uncontrolled localStorage mode unchanged.

A codex review pass surfaced two P1s, fixed in the second commit:

  • Behind-peer LWW strip — the version gate only rejects ahead senders, so a pre-v9 peer's edit is applied and its moodBoardId-unaware sanitized record would erase the local link. mergeUniversesFromSync now disambiguates omitted-vs-cleared by the sender's universes schema version (senderSchemaVersions, threaded from both the snapshot path in dataSync.applyRemote and the per-record push path in peerSyncReceive): a pre-v9/no-meta sender's omission preserves the local link; a v9-aware sender's omission is an explicit clear.
  • Expand/refine auto-saveuseUniverseExpand's create/update payloads omitted moodBoardId, so "Generate From Idea" on a new universe created it unlinked and rehydration silently dropped the pick.

Remaining phases of #4188 (gallery/upload pickers + video items, per-item prompt-from-media analysis, board→style synthesis) are not in this PR — see the reconciliation comment on the issue.

Refs #4188

Test plan

  • server/services/universeBuilder.test.js — new moodBoardId suite: persists through create, absent when never set, PATCH set/preserve-on-absent/clear-on-''-and-null, non-string sanitized to absent on read; merge tests for behind-peer preserve, v9 explicit clear, and v9 apply-over-local.
  • server/services/dataSync.pipelineUniverse.test.js — route-through test: a v8 sender's snapshot edit cannot strip moodBoardId, a v9 sender's omission clears it. peerSync.test.js call-args assertions extended with the threaded senderSchemaVersions.
  • server/lib/schemaVersions.test.js + sync fixtures (dataSync.pipelineUniverse, peerSync, integration) updated for v9; ahead/behind gating asserted at v10-vs-v9.
  • client/src/components/moodBoard/MoodBoardReferenceStrip.test.jsx — new: uncontrolled fallback + localStorage persistence unchanged; controlled mode no-fallback, onChange routing, no localStorage writes, deleted-board-shows-unset, create-and-link (with and without existing boards).
  • Full server suite (Vitest, node) and full client suite (Vitest, jsdom) pass locally; biome lint clean.

Phase 1 of #4188 (mood boards as a universe style tool). Adds an optional
moodBoardId to the universe record — sanitized, PATCHable, cleared by ''/null,
persisted only when set — and bumps the universes sync gate to v9 so an older
peer can't strip-then-LWW the link away (the pointer rides the wire since
mood boards federate). The Universe Bible tab's reference strip becomes a
controlled, persisted picker (with one-click create-and-link named after the
universe) instead of a per-browser localStorage pick; Pipeline keeps the
localStorage mode.
…s and expand auto-saves

Two P1s from the codex pass: (1) a pre-v9 peer's edit passes the version
gate (only AHEAD rejects) and its moodBoardId-unaware sanitized record
would LWW-strip the local link — mergeUniversesFromSync now disambiguates
omitted-vs-cleared by the sender's universes schema version, threaded from
both the snapshot and per-record push receive paths; (2) the expand/refine
auto-save payloads omitted moodBoardId, so Generate From Idea on a new
universe created it unlinked and dropped the pick on rehydration.
@atomantic
atomantic merged commit 658a08c into main Aug 14, 2026
7 checks passed
@atomantic
atomantic deleted the next/issue-4188 branch August 14, 2026 21:55
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