Persistence
The scene-builder auto-saves your work to the browser's IndexedDB and localStorage. When you reopen the page, everything is restored exactly as you left it.
What Gets Saved
IndexedDB (debounced 500ms)
The sajou-scene-builder database has 9 object stores:
| Store | Content |
|---|---|
scene | Scene dimensions, background, layers, positions, routes, zones |
entities | Entity definitions (sprites, spritesheets, GIFs) |
choreographies | Choreography definitions, steps, timing |
wires | Patch bay wire connections |
bindings | Entity bindings to choreography commands |
timeline | Signal timeline state |
shaders | Shader definitions, sources, uniforms |
assets | Image files as ArrayBuffer (incremental save) |
p5 | Sketch definitions (p5.js + Three.js), source code, params, mode |
DB_VERSION history: 1 (initial) → 2 (added shaders store) → 3 (added p5 store).
Each store wraps its data in a versioned envelope { version: 1, data: ... }.
localStorage (debounced 300ms)
| Key | Content |
|---|---|
sajou:remote-sources | Remote signal source configs (URL, protocol, API key, model) |
sajou:editor-prefs | Panel layouts, grid settings, view mode, pipeline layout |
sajou:server-url | Server URL override (empty = Vite proxy default). Set via the connection popover. |
Not Persisted
These are rebuilt or re-discovered on each session:
- Undo/redo stack
- Local signal sources (re-discovered via local discovery)
- Signal source connection status (all sources start
"disconnected") - Server connection status (re-probed on startup, tracked by
server-connection.ts) - Active selections
- Compositor state
Save Flow
State change (any store)
│
├── IndexedDB stores ──→ debounce 500ms ──→ dbPut("store", "current", { version: 1, data })
│
└── localStorage stores ──→ debounce 300ms ──→ localStorage.setItem(key, JSON.stringify(...))Multiple rapid changes within the debounce window collapse into a single write.
On page unload (beforeunload), all pending debounced saves are flushed immediately.
Restore Flow
restoreState() runs before the workspace is initialized:
- Check if
scenestore has data -- if not, this is a first launch - Restore scene state (dimensions, background, layers, etc.)
- Restore entity definitions
- Restore choreography state (clear selection)
- Restore wiring state (clear drag state)
- Restore binding state
- Restore signal timeline (clear selection)
- Restore shader state (clear selection, reset
playing: true) - Restore assets:
ArrayBuffer→File→URL.createObjectURL()→objectUrl - Restore remote sources from localStorage
- Restore editor preferences from localStorage
- Clear undo stack (restored state has no history)
Asset Persistence
Assets are saved incrementally -- only new paths not already in IndexedDB are written. Each asset is stored as:
{
name: string; // original filename
path: string; // unique asset path (key)
category: string; // "sprites" | "spritesheets" | "gifs"
format: AssetFormat; // "png" | "svg" | "webp" | "gif" | "jpeg"
buffer: ArrayBuffer; // raw file data
naturalWidth?: number;
naturalHeight?: number;
frameCount?: number;
detectedFps?: number;
}On restore, ArrayBuffer is reconstructed into a File object and a fresh objectUrl is created.
Force Persist
forcePersistAll() bypasses debouncing and writes all stores immediately. It's called after import to ensure the imported scene is fully saved.
Selective Import & Export
Scene files use the .sajou extension (ZIP archive internally). Both import and export show a section picker dialog — you choose which sections to include. Older .zip files are also accepted on import.
| Section | Stores affected |
|---|---|
| Visual layout | Scene state (placements, positions, routes, lighting, particles) |
| Entities & Assets | Entity definitions, asset files |
| Choreographies & Wiring | Choreography definitions, wire connections, bindings |
| Shaders | Shader definitions |
| Sketches | Sketch definitions (p5.js + Three.js), source code, params |
Sections with no content are grayed out. Unchecked sections keep their current state on import (or are omitted from the export).
On import, the dialog shows summary counts for each section and contextual warnings (e.g. "Visual layout without Entities may produce invisible meshes"). After import, autoWireConnectedSources() creates signal -> signal-type wires for any connected sources, so imported choreographies work immediately.
New Scene
The "New" button (Ctrl+N) triggers newScene():
- Clear all IndexedDB stores (
dbClearAll()) - Remove localStorage keys
- Reset all in-memory stores to defaults
- Clear undo stack
- Re-discover local sources (
scanAndSyncLocal())
Key Files
| File | Role |
|---|---|
state/persistence-db.ts | IndexedDB wrapper (singleton connection, CRUD helpers) |
state/persistence.ts | Auto-save/restore orchestrator |
io/export-scene.ts | Export to .sajou archive (selective) |
io/export-dialog.ts | Export section picker dialog |
io/import-scene.ts | Import from .sajou / .zip (4-phase: pick, parse, dialog, apply) |
io/import-dialog.ts | Import section picker dialog |
state/auto-wire.ts | Auto-wire connected sources on import/connect |