Import a Source sheet and crop Tiles (#35) #80

Merged
crowmaki merged 4 commits from issue-35-source-sheet-crop into main 2026-10-07 17:57:31 +00:00
Owner

Closes #35. Part of #24.

What

  • Migration V8: source_sheet, tile and reference_version.
    • A Source sheet is an immutable, content-hashed blob that belongs to the Project. Importing the same bytes again returns the same sheet.
    • Turnaround and mannequin sets have four fixed Tiles (face, front, side, back), created with the set. Sets from earlier files are backfilled. Location and Prop sets take user-labelled Tiles (add_tile, undoable until a version exists). Voice sets have none.
    • A Reference version stores its own PNG blob plus its provenance: Source sheet id and crop box in sheet pixels. Triggers make versions append-only.
    • The blob sweep keeps sheet and version blobs and drops orphaned ones as usual.
    • Deletion follows ADR-0004. Nothing can consume a Reference version yet (no Lock snapshots or Takes), so deleting a Look, Wardrobe item, Prop or Character hard-deletes its set, Tiles and versions. Undo and redo restore them exactly: the Tiles and versions are captured when the owner (or a labelled Tile) is removed, in the same way the cast rows are for a Character delete. A version is only ever deleted together with its Tile: the DELETE trigger refuses a direct delete while the Tile exists, and the UPDATE trigger still blocks edits. Source sheets stay while they exist.
  • studio-core (references.rs, sheet_image.rs):
    • import_source_sheet, source_sheets, reference_set_view, add_tile.
    • A crop session targets one Reference set. preview_crop_session and commit_crop_session share one plan, so the UI's "new / changed / unchanged" comes from the same rule that commit uses.
    • Commit cuts each crop from the original sheet with the image crate (same sheet and box always give the same PNG bytes) and makes a version only for crops that differ from the Tile's current version (different sheet or box). Re-cropping to match an older version still makes the next number. A Tile left out of the session is skipped. A bad session (voice set, unknown set or sheet, Tile from another set or cropped twice, empty or out-of-bounds box) writes nothing. Commit never locks.
    • Importing and committing are append-only: no Edit, reported with the new ChangeKind::Reference. Committing leaves the undo history alone.
  • studio_core::ATLAS_CELL (4, 9) names the Atlas cell shape; the UI's CELL_ASPECT points to it.
  • Adapter: sheet, set view, tile and crop session commands, and the studio: custom scheme (blob/<id>) that serves blobs to the webview.
  • UI (src/lib/references/, ported from prototype/crop-sheet): each Look, Wardrobe item and Prop gets a collapsible references panel.
    • Tile cards show a version strip. For the selected version they show its Source sheet with the crop box highlighted.
    • "Re-crop" reopens a version's sheet with its box loaded. "Crop from sheet…" crops a Tile from any other sheet.
    • The crop session has free-form boxes on one stage and, per Tile, the current version beside the new crop in a 4:9 Atlas cell on mid-grey. Tiles can be included or skipped. The commit button counts only new or changed Tiles.
    • describe moved from +page.svelte to src/lib/commandError.ts so the new components share it.
  • Not built, as they belong to #36: auto-fit, 1×4 / 2×2 presets, shrink-to-content, snapping, the padding control and the compare panel beyond current-vs-new.
  • cargo fmt also reformatted two spots left over in src-tauri/src/lib.rs and src-tauri/tests/smoke.rs.

Tests

  • crates/studio-core/tests/source_sheets.rs (26 tests): import as-is and dedupe, non-images refused, fixed roles for turnaround and mannequin sets, labelled Tiles, version creation only on change (same box, moved box, same box on another sheet, left-out Tiles, re-crop to an older box), provenance, crop pixels checked pixel-for-pixel against the sheet, one sheet feeding two sets, preview matching commit, atomic refusal of bad sessions, change events, the undo history surviving a commit, delete-with-versions with undo and redo for a Look, Wardrobe item, Prop and Character, undo of a labelled Tile that has versions, undo of an owner's creation after a commit, a direct DELETE or UPDATE of a version row being rejected, tile ids stable across undo and redo, and persistence across close and reopen.
  • crates/studio-core/tests/migrations.rs: sets from before V8 get their fixed Tiles.
  • src-tauri/tests/source_sheets.rs: import, preview, commit and studio: blob serving through the adapter.

Checks

  • cargo test --workspace --all-features, cargo clippy --workspace --all-targets --all-features -- -D warnings, cargo fmt --check and bun run check pass.
  • The Svelte UI is type-checked only. It has not been run in the app, so the crop stage, pointer dragging and the studio: image URLs in the webview are untested.

Open questions

  • Importing the same image bytes twice returns the existing sheet (and keeps its first name) instead of making a second one.
  • Source sheets are limited to PNG, JPEG and WebP, and are fully decoded at import so a bad file fails there and not at commit time. EXIF orientation is not applied.
  • Version blobs are always PNG, whatever the sheet's format.
  • Crop boxes are whole sheet pixels and must lie inside the sheet. A Tile with no earlier version from the chosen sheet starts with no box, and "Add box" places a centred 4:9 box.
  • Location Reference sets do not exist yet (they arrive with location_lighting), so labelled Tiles are exercised on Prop sets only. add_tile already accepts location sets.
  • Undoing a labelled Tile's creation, or an owner's creation, removes the versions cut for it. Redo restores them. This follows from hard-deleting what no Take consumed.
  • DTO status and role stay plain strings, like the existing kind fields.
  • No way to delete an unused Source sheet yet. That is the Storage view.
Closes #35. Part of #24. ## What - Migration V8: `source_sheet`, `tile` and `reference_version`. - A Source sheet is an immutable, content-hashed blob that belongs to the Project. Importing the same bytes again returns the same sheet. - Turnaround and mannequin sets have four fixed Tiles (face, front, side, back), created with the set. Sets from earlier files are backfilled. Location and Prop sets take user-labelled Tiles (`add_tile`, undoable until a version exists). Voice sets have none. - A Reference version stores its own PNG blob plus its provenance: Source sheet id and crop box in sheet pixels. Triggers make versions append-only. - The blob sweep keeps sheet and version blobs and drops orphaned ones as usual. - Deletion follows ADR-0004. Nothing can consume a Reference version yet (no Lock snapshots or Takes), so deleting a Look, Wardrobe item, Prop or Character hard-deletes its set, Tiles and versions. Undo and redo restore them exactly: the Tiles and versions are captured when the owner (or a labelled Tile) is removed, in the same way the cast rows are for a Character delete. A version is only ever deleted together with its Tile: the DELETE trigger refuses a direct delete while the Tile exists, and the UPDATE trigger still blocks edits. Source sheets stay while they exist. - `studio-core` (`references.rs`, `sheet_image.rs`): - `import_source_sheet`, `source_sheets`, `reference_set_view`, `add_tile`. - A crop session targets one Reference set. `preview_crop_session` and `commit_crop_session` share one plan, so the UI's "new / changed / unchanged" comes from the same rule that commit uses. - Commit cuts each crop from the original sheet with the `image` crate (same sheet and box always give the same PNG bytes) and makes a version only for crops that differ from the Tile's current version (different sheet or box). Re-cropping to match an older version still makes the next number. A Tile left out of the session is skipped. A bad session (voice set, unknown set or sheet, Tile from another set or cropped twice, empty or out-of-bounds box) writes nothing. Commit never locks. - Importing and committing are append-only: no `Edit`, reported with the new `ChangeKind::Reference`. Committing leaves the undo history alone. - `studio_core::ATLAS_CELL` (4, 9) names the Atlas cell shape; the UI's `CELL_ASPECT` points to it. - Adapter: sheet, set view, tile and crop session commands, and the `studio:` custom scheme (`blob/<id>`) that serves blobs to the webview. - UI (`src/lib/references/`, ported from `prototype/crop-sheet`): each Look, Wardrobe item and Prop gets a collapsible references panel. - Tile cards show a version strip. For the selected version they show its Source sheet with the crop box highlighted. - "Re-crop" reopens a version's sheet with its box loaded. "Crop from sheet…" crops a Tile from any other sheet. - The crop session has free-form boxes on one stage and, per Tile, the current version beside the new crop in a 4:9 Atlas cell on mid-grey. Tiles can be included or skipped. The commit button counts only new or changed Tiles. - `describe` moved from `+page.svelte` to `src/lib/commandError.ts` so the new components share it. - Not built, as they belong to #36: auto-fit, 1×4 / 2×2 presets, shrink-to-content, snapping, the padding control and the compare panel beyond current-vs-new. - `cargo fmt` also reformatted two spots left over in `src-tauri/src/lib.rs` and `src-tauri/tests/smoke.rs`. ## Tests - `crates/studio-core/tests/source_sheets.rs` (26 tests): import as-is and dedupe, non-images refused, fixed roles for turnaround and mannequin sets, labelled Tiles, version creation only on change (same box, moved box, same box on another sheet, left-out Tiles, re-crop to an older box), provenance, crop pixels checked pixel-for-pixel against the sheet, one sheet feeding two sets, preview matching commit, atomic refusal of bad sessions, change events, the undo history surviving a commit, delete-with-versions with undo and redo for a Look, Wardrobe item, Prop and Character, undo of a labelled Tile that has versions, undo of an owner's creation after a commit, a direct DELETE or UPDATE of a version row being rejected, tile ids stable across undo and redo, and persistence across close and reopen. - `crates/studio-core/tests/migrations.rs`: sets from before V8 get their fixed Tiles. - `src-tauri/tests/source_sheets.rs`: import, preview, commit and `studio:` blob serving through the adapter. ## Checks - `cargo test --workspace --all-features`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, `cargo fmt --check` and `bun run check` pass. - The Svelte UI is type-checked only. It has not been run in the app, so the crop stage, pointer dragging and the `studio:` image URLs in the webview are untested. ## Open questions - Importing the same image bytes twice returns the existing sheet (and keeps its first name) instead of making a second one. - Source sheets are limited to PNG, JPEG and WebP, and are fully decoded at import so a bad file fails there and not at commit time. EXIF orientation is not applied. - Version blobs are always PNG, whatever the sheet's format. - Crop boxes are whole sheet pixels and must lie inside the sheet. A Tile with no earlier version from the chosen sheet starts with no box, and "Add box" places a centred 4:9 box. - Location Reference sets do not exist yet (they arrive with `location_lighting`), so labelled Tiles are exercised on Prop sets only. `add_tile` already accepts location sets. - Undoing a labelled Tile's creation, or an owner's creation, removes the versions cut for it. Redo restores them. This follows from hard-deleting what no Take consumed. - DTO `status` and `role` stay plain strings, like the existing `kind` fields. - No way to delete an unused Source sheet yet. That is the Storage view.
Source sheets are imported as-is and belong to the Project. Turnaround and
mannequin sets get fixed face/front/side/back Tiles; location and Prop sets
take labelled Tiles. A crop session targets one Reference set, and committing
it cuts each crop from the original sheet and makes a version only when the
crop differs from the Tile's current version. Versions are append-only and
carry their sheet and crop box.
Adds the sheet, Reference set view and crop session commands, and the
studio: scheme that serves blobs to the webview.
Each Reference set gets Tile cards with a version strip and per-version
provenance, and a crop session with free-form boxes and a live 4:9 Atlas
cell preview.
Per ADR-0004, nothing consumes a version yet, so deleting an owner removes
its Tiles and versions, and undo or redo restores them. A version is only
deleted together with its Tile. Commit no longer clears the undo history.
Also adds CropFocus and currentVersion helpers and the ATLAS_CELL constant.
crowmaki deleted branch issue-35-source-sheet-crop 2026-10-07 17:57:31 +00:00
Sign in to join this conversation.
No description provided.