Patch library
A patch in Stardust is never inline. It’s always a reference to a library entry. There is one data model, one code path, one UX flow.
Why one model wins
In most live-performance hosts a patch is a struct that lives in the show file. If you want the same patch in two songs you copy-paste. Fix a bug in one copy, the other diverges. “Save to library” is a separate concept; aliasing is a separate code path; sharing has to decide which patches to bundle.
Stardust takes a different approach: every patch lives in a library entry, every patch in a song is a reference to a library entry, and library entries carry a scope: "show" | "global" field. There is no inline patch type.
What that buys us:
- One data type, one code path, one UX flow
- “New patch” creates both a library entry (show-scope,
Untitled Patch) and a reference at the same time - “Save to global” flips
scope: "show"→scope: "global"— no restructure - “Duplicate” creates a new library entry + reference (no shared history)
- Aliasing is just “a reference to the same library id”
- Sharing always bundles the entries the show references
- CRDT sync (post-v1.0) becomes trivial because the data is already normalized
Storage
~/.stardust/library/ # global library (per-user)├── patches/│ ├── 8e3f-9c2a.json # one file per library entry, named by uuid│ ├── 4a1c-7d8e.json│ └── ...├── rig-components/│ └── 2b9d-1f4e.json└── blocks/ └── ...my-show.stardustshow/ # the show bundle├── show.json # references library entries by id├── libraries/│ ├── patches.json # show-scoped library entries│ ├── rig-components.json│ └── blocks.json├── assets/│ ├── audio/ # backing track stems│ ├── images/ # logos, conductor cam overlays│ └── samples/ # SFZ files, recorded samples└── thumbnails/ # cached widget previewsLibrary entry shape
LibraryPatch { id: "8e3f-9c2a", // uuid, unique scope: "show" | "global", name: "Full Strings", graph: PatchGraph, defaults: { tempo?: number, transpose?: number, trim?: number, // dB notes?: string // markdown }}Reference shape
PatchRef { refId: "ref-12", // unique within show libraryId: "8e3f-9c2a", // points at library entry overrides: { // Basic overrides (shown by default in inspector) name?: string, notes?: string, tempo?: number, transpose?: number, trim?: number, color?: string, // outline visual differentiation tags?: string[], // Advanced overrides (collapsed by default) midiChannelOffset?: number, busRoutingOverride?: BusRouting, pluginParamOverrides?: Record<string, number>, fxBypassOverrides?: string[], onEnterTrigger?: TransportTrigger, onExitTrigger?: TransportTrigger, customCss?: string }, orphan?: { snapshot: PatchGraph, lostAt: timestamp }}Resolution
When a show opens:
- Walk the show’s
libraries/first, then the user’s global~/.stardust/library/ - For each
PatchRef, look uplibraryIdin the resolved library - If found: render with overrides applied —
displayName = ref.overrides.name ?? library.name, etc. - If not found: check
orphan.snapshot; render from snapshot with the orphan banner; if absent (defensive), render error state
Patch instance UX
- Outline — aliased patches show “Instance of ‘Full Strings’” label and a small chain icon
- Patch editor with shared patch open — banner: “Shared — N instances. Graph edits trigger merge UI.”
- Inspector — Basic overrides visible by default; Advanced collapsed but expandable
- All override fields pre-populated from library defaults; user edits override only that field
- No explicit “detach” command — simpler UX. Save-as-new gets you a fresh entry if you really want to fork.
- Outline rename of a reference updates only the
nameoverride (e.g., library is “Full Strings”, instance is “b42. Full Strings”)
Graph edit propagation (the merge UI)
Editing a globally-shared patch’s graph is where most live-performance hosts get scary. Stardust handles it explicitly.
When you save changes to a globally-shared patch’s graph:
- Modal: “Save changes to global ‘Full Strings’?”
- Lists every other use of this library entry with a per-instance checkbox
- Per instance, a dropdown: Update this instance / Keep current / Three-way merge
- Three-way merge view — per-node diff. Shows base / source-edit / instance-overrides side-by-side. User picks per change.
- “Always update silently” checkbox at the bottom — becomes a per-show preference for future edits
The dialog defaults to “Update” on every instance with no conflicting overrides, “Three-way merge” on any instance that has its own override on a changed node. The user can override the default per row.
Orphan handling
When a library entry is deleted, every reference in every open show becomes an orphan:
- System sweeps all open shows and shows known to it
- For each reference, the
orphan.snapshotfield is populated with the last-known graph - The reference is now self-contained (functional but disconnected)
- Outline icon — broken-chain marker on the patch
- Patch editor banner — “This patch references ‘Full Strings’, which was deleted. Frozen at last edit.”
- Action buttons:
- Re-link to existing → pick another library entry, copy graph from there
- Save as new global → create new global library entry from snapshot, re-point reference
- Save as show-local → create new show-scoped library entry, re-point reference
- Keep as orphan → no action; ref keeps its snapshot indefinitely
Lazy orphaning
For shows opened months later: the open-flow detects a missing libraryId and populates orphan.snapshot on open. No data loss. Cross-show detection is bounded to the open shows; orphaning is on-demand.
Sharing model
When you save/export a show:
- Walk the show’s references
- Find every global library entry used
- Snapshot them into the show’s
libraries/patches.jsonwithscope: "show"and aderivedFrom: "global-id"marker - The show is now self-contained — anyone receiving it gets working patches without needing your global library
When a recipient opens the show, they can:
- Use as-is — snapshots stay show-local
- Import to global library — copies the snapshots into their
~/.stardust/library/, re-points the show’s references to the (now imported) global ids
This is the same model the v2.0+ marketplace uses for distribution — a marketplace patch install becomes a new global library entry the recipient’s shows can reference.
Scope promotion / demotion
- Promote
show→global— flips thescopefield; the entry moves fromlibraries/patches.jsoninto the user’s~/.stardust/library/; the show’s references stay pointed at the same id, but now resolve to global instead of show-local - Demote
global→show— flips thescopefield; copies the entry into the show file; the references update to point at the new show-local id
Both are explicit user actions in the library panel — no implicit promotion.
What ships in v0.10.0
- All-patches-as-references data model
show/globalscope on every entry- Basic + Advanced override fields on
PatchRef - Graph-edit merge UI with three-way merge view
- Orphan snapshot + banner + Re-link / Save-as-new / Keep flow
- Promote / demote actions in the library panel
- Library panel UI on the left side of the Program-mode outline
- Sharing snapshot bundling on export
What’s deferred to v1.x or v2.0+
- Marketplace integration — v2.0+
- Community share hub — v1.x bridge step (free anonymous distribution)
- Cross-show search for library entries — v1.x
- CRDT-backed real-time collab on a shared library entry — v2.0+ (the data model is already CRDT-friendly because it’s normalized + reference-based)
- Plugin-bundle install that auto-creates library entries — v2.0+