Lock snapshots (#37) #87

Merged
crowmaki merged 9 commits from issue-37-lock-snapshots into main 2026-10-08 03:47:45 +00:00
Owner

Closes #37. Part of #24.

What

  • Migration V9 (the number may need bumping on merge, if #34 or #36 also add one): lock_snapshot, lock_snapshot_pick, tile.retired_at, lighting.phrase and location_lighting.
    • A Lock snapshot is a numbered, immutable pick of one Reference version per Tile. Triggers refuse edits, and the picks' foreign keys refuse deleting a Tile or version a snapshot uses.
    • The tile_label index now ignores retired Tiles, so a retired Tile frees its label.
  • studio-core (snapshots.rs, references.rs, location_sets.rs):
    • lock_reference_set picks each live Tile's newest version. The current snapshot is the highest number. A pick identical to the current one does nothing (created: false, no change event). A Tile with no version is left out, which is what changes the Parity. A set with nothing to pick is refused.
    • roll_back_reference_set(set, number) locks a new snapshot that copies an older pick, or does nothing if that is the current one.
    • Locking and rolling back are append-only: no Edit, reported as ChangeKind::Reference, and the undo history is left alone.
    • remove_tile (undoable): a Tile that a snapshot picks from is retired, so it drops out of the set but stays readable in the snapshot. Any other Tile is deleted with its versions. Undoing a labelled Tile's creation follows the same rule, and undo or redo restores either state.
    • Deleting a set's owner (ADR-0004, no Take consumes anything yet) hard-deletes its snapshots with its Tiles and versions. Undo restores them exactly.
    • Parity is the roles a snapshot has a pick for (LockSnapshotView.parity). Versions list the snapshots that pick them (locked_in).
    • Voice sets: import_voice_clip stores an audio clip (WAV, FLAC, Ogg or MP3, checked by its leading bytes) as a version of a labelled Tile. The same label adds a version, and the same bytes again add nothing. Clips lock, roll back and retire like Tiles. ReferenceVersionView gains media_type, and width / height are optional.
    • Location sets: location_reference_sets returns one set per Lighting in Project order and makes missing ones (first use, or after a Lighting is added). set_location_set_note and set_lighting_phrase are undoable. The effective note is the Location's note, else the Lighting's phrase. Deleting a Location takes its sets with it, undoable. Removing a Lighting is refused while one of its Location sets has Tiles or a note, and otherwise drops the empty sets.
  • Adapter: lock_reference_set, roll_back_reference_set, remove_tile, import_voice_clip, location_reference_sets, set_location_set_note, set_lighting_phrase, plus the new DTO fields. bindings.ts is regenerated.
  • UI: the Reference set panel has a Lock button (the next number, disabled when nothing changed), the Parity line (locked, and "if locked now" when it differs), a lock history with "Roll back to this", lock markers on versions, a remove button on labelled Tiles, and clip import and playback for voice sets. Location inspectors show one panel per Lighting with its note. Characters get a Voice block. Settings > Lighting edits the phrases. Pure display rules are in src/lib/references/lock.ts.

Tests

  • crates/studio-core/tests/snapshots.rs (21): numbering, per-set numbers, older pick kept, no-op, empty set refused, rollback and rollback no-op, rollback over a retired Tile, Parity, not undoable, retirement and un-retirement, label reuse, owner delete with undo and redo, persistence and trigger/FK refusals.
  • crates/studio-core/tests/voice_clips.rs (9) and location_sets.rs (11).
  • src-tauri/tests/lock_snapshots.rs (2) through the adapter, and src/lib/references/lock.test.ts (7).

Checks

  • cargo test --workspace --all-features, bun run test and bun run check pass. cargo clippy -p studio-core --all-targets --all-features -- -D warnings passes.
  • cargo clippy --workspace fails on src-tauri/src/api.rs (type_complexity on Hooks::open_window), and cargo fmt --check flags src-tauri/src/lib.rs and tests/smoke.rs. Both are already on main and left alone.
  • The Svelte UI is type-checked only. It has not been run in the app. Audio playback through the studio: scheme in the webview is untested.

Open questions

  • Locking always picks each Tile's newest version. Picking an older version for one Tile is only possible by rolling back.
  • Merging two Locations leaves the merged-away Location's sets on its tombstone, and the survivor makes its own.
  • Location sets are not in the Source sheet editor's set picker yet. They can be cropped from the Location inspector.
  • Once Takes exist, ADR-0004 makes Lock snapshots consumed rows, so deleting an owner must soft-delete them. Today they are hard-deleted with the owner, per the amendment.
Closes #37. Part of #24. ## What - Migration V9 (**the number may need bumping on merge**, if #34 or #36 also add one): `lock_snapshot`, `lock_snapshot_pick`, `tile.retired_at`, `lighting.phrase` and `location_lighting`. - A Lock snapshot is a numbered, immutable pick of one Reference version per Tile. Triggers refuse edits, and the picks' foreign keys refuse deleting a Tile or version a snapshot uses. - The `tile_label` index now ignores retired Tiles, so a retired Tile frees its label. - `studio-core` (`snapshots.rs`, `references.rs`, `location_sets.rs`): - `lock_reference_set` picks each live Tile's newest version. The current snapshot is the highest number. A pick identical to the current one does nothing (`created: false`, no change event). A Tile with no version is left out, which is what changes the Parity. A set with nothing to pick is refused. - `roll_back_reference_set(set, number)` locks a new snapshot that copies an older pick, or does nothing if that is the current one. - Locking and rolling back are append-only: no `Edit`, reported as `ChangeKind::Reference`, and the undo history is left alone. - `remove_tile` (undoable): a Tile that a snapshot picks from is retired, so it drops out of the set but stays readable in the snapshot. Any other Tile is deleted with its versions. Undoing a labelled Tile's creation follows the same rule, and undo or redo restores either state. - Deleting a set's owner (ADR-0004, no Take consumes anything yet) hard-deletes its snapshots with its Tiles and versions. Undo restores them exactly. - Parity is the roles a snapshot has a pick for (`LockSnapshotView.parity`). Versions list the snapshots that pick them (`locked_in`). - Voice sets: `import_voice_clip` stores an audio clip (WAV, FLAC, Ogg or MP3, checked by its leading bytes) as a version of a labelled Tile. The same label adds a version, and the same bytes again add nothing. Clips lock, roll back and retire like Tiles. `ReferenceVersionView` gains `media_type`, and `width` / `height` are optional. - Location sets: `location_reference_sets` returns one set per Lighting in Project order and makes missing ones (first use, or after a Lighting is added). `set_location_set_note` and `set_lighting_phrase` are undoable. The effective note is the Location's note, else the Lighting's phrase. Deleting a Location takes its sets with it, undoable. Removing a Lighting is refused while one of its Location sets has Tiles or a note, and otherwise drops the empty sets. - Adapter: `lock_reference_set`, `roll_back_reference_set`, `remove_tile`, `import_voice_clip`, `location_reference_sets`, `set_location_set_note`, `set_lighting_phrase`, plus the new DTO fields. `bindings.ts` is regenerated. - UI: the Reference set panel has a Lock button (the next number, disabled when nothing changed), the Parity line (locked, and "if locked now" when it differs), a lock history with "Roll back to this", lock markers on versions, a remove button on labelled Tiles, and clip import and playback for voice sets. Location inspectors show one panel per Lighting with its note. Characters get a Voice block. Settings > Lighting edits the phrases. Pure display rules are in `src/lib/references/lock.ts`. ## Tests - `crates/studio-core/tests/snapshots.rs` (21): numbering, per-set numbers, older pick kept, no-op, empty set refused, rollback and rollback no-op, rollback over a retired Tile, Parity, not undoable, retirement and un-retirement, label reuse, owner delete with undo and redo, persistence and trigger/FK refusals. - `crates/studio-core/tests/voice_clips.rs` (9) and `location_sets.rs` (11). - `src-tauri/tests/lock_snapshots.rs` (2) through the adapter, and `src/lib/references/lock.test.ts` (7). ## Checks - `cargo test --workspace --all-features`, `bun run test` and `bun run check` pass. `cargo clippy -p studio-core --all-targets --all-features -- -D warnings` passes. - `cargo clippy --workspace` fails on `src-tauri/src/api.rs` (`type_complexity` on `Hooks::open_window`), and `cargo fmt --check` flags `src-tauri/src/lib.rs` and `tests/smoke.rs`. Both are already on `main` and left alone. - The Svelte UI is type-checked only. It has not been run in the app. Audio playback through the `studio:` scheme in the webview is untested. ## Open questions - Locking always picks each Tile's newest version. Picking an older version for one Tile is only possible by rolling back. - Merging two Locations leaves the merged-away Location's sets on its tombstone, and the survivor makes its own. - Location sets are not in the Source sheet editor's set picker yet. They can be cropped from the Location inspector. - Once Takes exist, ADR-0004 makes Lock snapshots consumed rows, so deleting an owner must soft-delete them. Today they are hard-deleted with the owner, per the amendment.
# Conflicts:
#	src-tauri/src/api.rs
#	src-tauri/src/dto.rs
#	src/lib/bindings.ts
# Conflicts:
#	src-tauri/src/api.rs
#	src-tauri/src/dto.rs
#	src/lib/bindings.ts
crowmaki deleted branch issue-37-lock-snapshots 2026-10-08 03:47:45 +00:00
Sign in to join this conversation.
No description provided.