Spec: AI Studio end-to-end slice #24

Closed
opened 2026-10-07 07:24:15 +00:00 by crowmaki · 3 comments
Owner

Spec for the end-to-end slice charted in #1. Every decision below comes from a closed child ticket of #1 (linked inline). Terms follow CONTEXT.md. ADRs 0001–0005 apply.

Problem Statement

A filmmaker who works alone with a local video model (MiniMax H3 through ComfyUI on one RTX 5090 / 32 GB) has no tool that connects a screenplay to an editor's timeline. Today they hand-build references, hand-write long six-field H3 prompts, keep track in their head of which reference version fed which clip, and rename and sort hundreds of generated files before an editor can cut them. Nothing records why a clip looks the way it does, nothing flags clips made from references that have since changed, and nothing turns "the takes I like" into something DaVinci Resolve understands. The work is slow and error-prone, and it can't be reproduced.

Solution

AI Studio is a local desktop app (Tauri 2 + SvelteKit + Svelte 5, with a Rust studio-core that owns the domain). It takes a screenplay through a pipeline organized like real production:

OSF script → Entities → locked References → Atlases → Setups / Shots with Boards and Keyframes → Takes (drafts, Promotion, Single-pass final) → Animatic → Resolve Edit package.

The filmmaker imports an OSF script. The app creates Scenes, Lines, Characters and Locations. The filmmaker builds Reference sets by importing and cropping sheets or by generating Tiles, and locks them as Lock snapshots. They plan Setups and Shots, draw Boards with a structured Notation, and queue Takes. The app composes the H3 prompt, fills the reference slots, builds Atlases, and runs a render queue that it owns against the user's own ComfyUI. Takes come back to Dailies, where the filmmaker screens, circles, notes and promotes them. The Animatic plays the Scene's Cut, and circled Takes replace Boards until it is a rough cut. An optional Performance locks dialogue timing. Export writes an Edit package: binned 12A-3.mov clips, FCPXML 1.10 and .otio timelines, and a Resolve metadata CSV with circled status and notes. Every Take keeps full provenance, so it can be explained, judged stale, and reproduced.

User Stories

Project and environment

  1. As a filmmaker, I want each film to be a single .studio file with a sibling media folder, so that I can move, back up and double-click a Project like a document.
  2. As a filmmaker, I want the app to refuse to open a Project that another running instance has open, so that two writers never corrupt one file.
  3. As a filmmaker, I want the app to back up a Project file before it migrates it to a newer schema, so that an upgrade can never lose my work.
  4. As a filmmaker, I want the app to refuse a Project made by a newer version of the app, so that an old build never damages a file it doesn't understand.
  5. As a filmmaker, I want the Project file to be a single file at rest (no stray journal files after a clean close), so that copying it is safe.
  6. As a filmmaker, I want app-wide defaults (Lighting list, slot order, Notation set, Generation profile) copied into each new Project, so that later changes to my defaults never silently change an existing film.
  7. As a filmmaker, I want to set the Project's aspect ratio from a fixed list and change it later, so that I can decide on format without starting over.
  8. As a filmmaker, I want the app to connect to my own ComfyUI and run a preflight check of nodes, models and version, so that I know what is missing before I queue anything.
  9. As a filmmaker, I want preflight to report missing node packs and model files by name and download URL, without installing anything, so that I stay in control of my ComfyUI install.
  10. As a filmmaker, I want each shipped workflow shown as Ready or Not ready with reasons in a ComfyUI status panel, so that I know which kinds of work I can queue.
  11. As a filmmaker, I want app-level undo for my authored edits (merges, Shot and Setup edits, circling, Board saves, Cut overrides, marks), so that I can experiment safely.
  12. As a filmmaker, I want locking, queuing, rendering and importing to be append-only and not undoable, so that provenance can never be rewritten by accident.
  13. As a filmmaker, I want to open several windows on one Project that all stay in sync, so that I can keep Dailies and the Animatic open side by side.

Script import

  1. As a filmmaker, I want to import my OSF 4.x script (custom urn:drafter:1 dialect), so that Scenes, Lines, Characters and Locations are created for me.
  2. As a filmmaker, I want each action paragraph and each whole dialogue block to become one Line, so that Shots and Performances point at units that match how I write.
  3. As a filmmaker, I want transitions and shot directions kept as read-only Lines that never count toward Characters, Shots or Performances, so that the script reads as written without polluting production.
  4. As a filmmaker, I want Scenes to keep their d:id and Lines to get stable opaque IDs, so that my production data survives for the life of the Project.
  5. As a filmmaker, I want missing or duplicate d:ids to get new IDs with a warning, so that a messy script still imports.
  6. As a filmmaker, I want locked scene numbers (such as 12A) used as written, and other Scenes numbered in script order and frozen at import, so that Take names never change.
  7. As a filmmaker, I want omitted Scenes kept as placeholders with their number and hidden from production views, so that numbering stays honest.
  8. As a filmmaker, I want Characters created from dialogue cues (normalized, with extensions removed), so that the speaking cast appears without manual entry.
  9. As a filmmaker, I want cue extensions (V.O., O.S./O.C., SUBTITLE, other) stored on the Line and CONT'D dropped, so that the prompt can describe off-screen and voice-over delivery.
  10. As a filmmaker, I want SUBTITLE Lines to have hand-entered spoken language and spoken text, and to be flagged while those are empty, so that H3 speaks the right language.
  11. As a filmmaker, I want dual dialogue imported as two Lines with a shared simultaneous marker, so that overlapping speech is timed and prompted as one unit.
  12. As a filmmaker, I want each scene heading split into a Location (INT and EXT kept separate) and a time slug, so that Locations match real places.
  13. As a filmmaker, I want Locations with the same base name grouped into a Location folder automatically, so that a house's rooms sit together without affecting anything else.
  14. As a filmmaker, I want to create, rename and delete Location folders and move Locations between them, so that I can organize Locations my way.
  15. As a filmmaker, I want each Scene's Lighting filled in from a per-Project slug→Lighting table, with relative slugs inheriting from the previous Scene, so that day and night references are picked correctly.
  16. As a filmmaker, I want Scenes with unknown slugs flagged with empty Lighting, so that I fix them rather than get a wrong guess.
  17. As a filmmaker, I want edits to the slug table to leave already-imported Scenes alone, so that my fixes are never overwritten.
  18. As a filmmaker, I want page numbers kept as text per Line (so 2A works) and Scene page ranges derived from them, so that I can still find things by page.
  19. As a filmmaker, I want the title page to fill the Project's title and author, so that the export carries them.
  20. As a filmmaker, I want notes and synopses kept as read-only text, so that my writer's notes travel with the script.
  21. As a filmmaker, I want import to be lenient and end with a summary of counts, warnings and flagged Scenes and Lines, so that I fix problems afterwards in the normal editors.
  22. As a filmmaker, I want OSF import to run with progress and not freeze the UI, so that long scripts don't feel broken.

Entities

  1. As a filmmaker, I want to create Characters, Locations and Props by hand as equals of parsed ones, so that the script never limits who and what I can reference.
  2. As a filmmaker, I want to create a non-speaking Character or a Prop from a text selection in the script, so that tagging is quick.
  3. As a filmmaker, I want to merge near-duplicate Characters and Locations, with Lines and mentions moved to the survivor and the old name kept as an alias, so that parsing mistakes are cheap to fix.
  4. As a filmmaker, I want each Character to hold default identity fields (age and build, face, hair, eyes, distinctive features), so that every prompt describes them the same way.
  5. As a filmmaker, I want each Look to inherit the Character's identity and override only what differs, plus default-wardrobe text, so that variants stay consistent.
  6. As a filmmaker, I want Wardrobe items with their own wardrobe text and mannequin references, so that I can change an outfit without a new turnaround.
  7. As a filmmaker, I want each Location to hold a description and an Ambience, so that environment and sound bed are described once.
  8. As a filmmaker, I want one Reference set per Location per Lighting, each with a lighting note that defaults from a per-Project phrase per Lighting, so that day and night versions of a place stay separate.
  9. As a filmmaker, I want each Prop to hold a description and its own Reference set, so that key objects look the same in every Shot.
  10. As a filmmaker, I want a Voice reference set per Character, so that their voice timbre is consistent.
  11. As a filmmaker, I want to set per Scene which Look and Wardrobe item each Character wears, with speaking Characters added automatically on their default Look, so that continuity is explicit.

References

  1. As a filmmaker, I want to import a generated turnaround sheet as a Source sheet that belongs to the Project, so that one sheet can feed several Reference sets.
  2. As a filmmaker, I want to crop a sheet into a turnaround's four Tiles on one stage, with boxes auto-fitted to detected figures in reading order, so that cropping takes seconds.
  3. As a filmmaker, I want 1×4 and 2×2 presets, shrink-to-content, snapping to detected figure edges and a padding control, so that crops are clean without fiddling.
  4. As a filmmaker, I want free-form crop boxes with a live 4:9 Atlas cell preview, so that I can judge exactly what H3 will see.
  5. As a filmmaker, I want a side panel that compares each new crop with the Tile's current version, and lets me skip Tiles, so that I only make the versions I mean to.
  6. As a filmmaker, I want a new Reference version only when a crop differs from the Tile's current one, so that version history isn't cluttered.
  7. As a filmmaker, I want each Tile card to show its version strip and the provenance of each version (Source sheet with crop box highlighted), so that I know where every image came from.
  8. As a filmmaker, I want to re-crop a version from its sheet, or crop from any other sheet, so that I can fix a Tile without importing again.
  9. As a filmmaker, I want to generate a Tile with the shipped text-to-image workflow at the right shape (4:9 for turnaround and mannequin Tiles, Project ratio for location angles, 1:1 for Props), so that I don't need outside tools for every reference.
  10. As a filmmaker, I want imported and generated versions to be the same kind of object, so that locking and staleness treat them alike.
  11. As a filmmaker, I want to lock a Reference set as a numbered, immutable Lock snapshot that picks one version per Tile, so that only approved images feed production.
  12. As a filmmaker, I want to roll back by locking a new snapshot that copies an older pick, and locking an identical pick to do nothing, so that snapshot numbers always mean something.
  13. As a filmmaker, I want Tiles that a snapshot uses to be retired rather than deleted, so that history stays readable.
  14. As a filmmaker, I want location and Prop sets to have open, user-labelled Tile lists, so that I can give a place as many angles as it needs.
  15. As a filmmaker, I want an empty turnaround Tile to change the Look's Parity, so that Atlases never mix views.

Atlases

  1. As a filmmaker, I want the app to build Atlases from Lock snapshots automatically (one row per Look, face → front → side → back, uniform 4:9 cells, mid-grey padding, wider row gaps, no burned-in text), so that up to three Looks share one image slot.
  2. As a filmmaker, I want Atlases scaled to a megapixel budget (1.0 MP by default, never upscaled) or at native size, set per Project and overridable per Setup, so that close-up-heavy Setups can get more detail.
  3. As a filmmaker, I want Looks grouped into Atlases in subject order by default, and the option to regroup per Setup (within Parity), so that I can split Looks whose clothes bleed into each other.
  4. As a filmmaker, I want Setups that need the same Looks to share one Atlas, so that nothing is built twice.
  5. As a filmmaker, I want Atlas pixels to be byte-stable for the same inputs, so that a rebuilt Atlas never causes false staleness.

Setups, Shots and the Board

  1. As a filmmaker, I want to add Setups to a Scene, lettered A, B, C and never reused, so that names follow set convention.
  2. As a filmmaker, I want to reorder a Scene's Setups separately from their letters, so that the master can come first in the Cut.
  3. As a filmmaker, I want each Setup to have a hand-picked Location angle, a framing note and the list of subjects in frame, so that coverage matches across cuts.
  4. As a filmmaker, I want a blocking-plan placeholder on the Setup, so that the UI has a place for the later blocking tool.
  5. As a filmmaker, I want each Shot to cover a contiguous run of whole Lines in its Scene, so that coverage maps to the script exactly.
  6. As a filmmaker, I want several Shots (from different Setups) to cover the same Line, so that I can shoot proper coverage.
  7. As a filmmaker, I want a Shot's length derived from its Lines plus head and tail handles, with an override clamped to 2–15 s, so that Takes are long enough to cut.
  8. As a filmmaker, I want queuing blocked past 15 s with an offered split at a Line boundary into numbered Shots (12B·1, 12B·2), so that I never send H3 a length it can't make.
  9. As a filmmaker, I want a single Line or dual-dialogue group longer than 15 s to block queuing with a clear message, so that I know to rewrite or wait for Take extension.
  10. As a filmmaker, I want existing Takes to stay on the first part when a Shot is split, so that nothing is lost.
  11. As a filmmaker, I want a per-Shot aspect ratio override, so that an insert can use a different format.
  12. As a filmmaker, I want to draw a Board for each Shot in a Panel-strip editor over a Scene-wide Board timeline, so that I can storyboard a whole Scene in one place.
  13. As a filmmaker, I want Sketch tools (brush, eraser, fill, line, rectangle, ellipse, filled toggle, size, three tones) on a fixed low-res canvas (short side 180 px at the Shot's ratio), so that sketching stays fast and rough.
  14. As a filmmaker, I want a Sketch/Annotate switch (Tab) that swaps the toolbar, so that the Annotation layer stays separate from the drawing.
  15. As a filmmaker, I want to add, duplicate, reorder and delete Panels, and ghost the previous Panel (also across Shots), so that I can show how a Shot progresses.
  16. As a filmmaker, I want structured Annotations (Notation symbols with a subject and a direction) and a read-back list under the stage ("MARK moves right", "the camera pushes in"), so that I know exactly what H3 will be told.
  17. As a filmmaker, I want freehand arrows flagged as meaningless until converted to a symbol, so that I don't think a scribble is instructing H3.
  18. As a filmmaker, I want a project-wide Notation set built from a fixed glyph library, with editable name, colour, category, key and meaning, so that conventions stay consistent.
  19. As a filmmaker, I want Notation entries in use to be retired rather than deleted, so that old Boards keep their meaning.
  20. As a filmmaker, I want "Describe…" on a Panel to fill its Sketch layer with a rough board image from the board text-to-image workflow, with opt-in "Reduce to 3 tones", so that I can board without drawing.
  21. As a filmmaker, I want Regenerate to reuse the Panel's stored prompt, replace only the Sketch layer, and be undoable, so that my annotations survive.
  22. As a filmmaker, I want multi-Panel Boards sent tiled into one image slot by default, with a per-Shot option for one slot per Panel, so that I can trade slots for clarity.
  23. As a filmmaker, I want a per-Shot toggle to send the clean sketch without the Annotation layer, so that I can work around H3 drawing marks literally.
  24. As a filmmaker, I want a Panel drawn at an old aspect ratio barred at send time and shown with bars until I draw on it again, so that a format change never sends a distorted Board.
  25. As a filmmaker, I want to add Keyframes to a Shot (start, end or a given time), imported, generated at the Shot's final size, or taken from a frame of another Take, so that I can pin exactly how a Shot opens or closes.
  26. As a filmmaker, I want "Use first / last frame as Keyframe for…" in Dailies, so that I can continue across a cut.

Prompt composer

  1. As a filmmaker, I want the app to compose H3's six-field prompt (subject definitions, summary, retention analysis, detailed description, soundscape, music) automatically, so that I never write boilerplate.
  2. As a filmmaker, I want Characters referred to as <Subject N> and Props as <Prop N>, built from structured fields and never named, so that names don't bias the model.
  3. As a filmmaker, I want positional Atlas phrasing ("top row of <Picture 2>") with the views listed per Parity, so that H3 maps subjects to rows correctly.
  4. As a filmmaker, I want Preservation written for me (full for a default-wardrobe Look, partial with an attribute transfer for a Wardrobe item), so that outfit changes just work.
  5. As a filmmaker, I want slot tags numbered after the budget is filled, so that a dropped reference is never named in the prompt.
  6. As a filmmaker, I want the Board described as a weak reference and never named in the description, while its Notation read-backs become plain directions, so that H3 takes staging from it but not style.
  7. As a filmmaker, I want each Shot to have editable action text pre-filled from its action Lines, with @-mentions for Characters and Props, so that I can describe the beat in my own words.
  8. As a filmmaker, I want dialogue Lines shown as chips in the action text in script order, expanding to quoted speech with language and inline parenthetical tags, so that dialogue is exact and can't be reordered by accident.
  9. As a filmmaker, I want parentheticals that name a Character moved into an action sentence and flagged for rewording, so that the quote stays clean.
  10. As a filmmaker, I want off-screen and voice-over Lines phrased as such, and heard-only speakers defined as heard but not seen, so that H3 doesn't put them in frame.
  11. As a filmmaker, I want the soundscape built from the Location's Ambience plus an optional Scene Ambience addition, so that the sound bed matches across cuts.
  12. As a filmmaker, I want an editable Shot summary pre-filled from the action text, so that the prompt opens with my framing of the beat.
  13. As a filmmaker, I want a read-only preview of the composed prompt that I can fork into a hand-edited override, so that I can tune prompts that matter.
  14. As a filmmaker, I want an unobtrusive "composer changed" marker and an on-demand three-way reconcile, rather than prompts on every queue, so that my overrides are never overwritten and I'm never nagged.
  15. As a filmmaker, I want queuing blocked when an override names a slot tag that no longer exists, so that I never send a broken prompt.

Slot budget

  1. As a filmmaker, I want the 9 image and 3 audio slots filled automatically in a per-Project category order (Keyframes → Atlases → Wardrobe mannequins → location angle → Board → Props), so that the right references go first.
  2. As a filmmaker, I want to reorder that category order by drag-and-drop in Project settings (seeded from an app-wide default), so that I can tune priorities per film.
  3. As a filmmaker, I want each Shot's Slot list to show every candidate with a draggable sent / not-sent line, labelled excluded or over budget, so that I always see what was dropped and why.
  4. As a filmmaker, I want an edited Slot list to keep my placements when candidates change, with new items badged and never pushing out my choices, so that my edits are respected.
  5. As a filmmaker, I want "Reset to Project order" on a Shot, so that I can undo my tinkering.
  6. As a filmmaker, I want a one-slot-per-Panel Board to fall back to tiled before anything is dropped, so that slots aren't wasted.
  7. As a filmmaker, I want Voice references only for speaking Characters, one joined clip each, dropped when a Performance is present, so that audio slots go where they help.
  8. As a filmmaker, I want each Prop packed into one slot from its Lock snapshot's Tiles, so that a Prop costs one slot.
  9. As a filmmaker, I want an "over budget" badge on Shots wherever they are listed, for forced drops only, so that I notice lost references.

Generation and the render queue

  1. As a filmmaker, I want a Project default Generation profile (model and encoder files, quantization, LoRA stack with weights, Distilled/Full speed mode, draft/final short sides 768/1120) that each new Take copies, so that I set things up once.
  2. As a filmmaker, I want to change the Generation profile for one Take in the queue dialog, picking files from what my ComfyUI has installed, so that I can try a different model or LoRA.
  3. As a filmmaker, I want changing a profile never to make Takes stale, so that experimenting with models doesn't flood Dailies with warnings.
  4. As a filmmaker, I want to queue a draft Take, or a Single-pass final for the best quality, so that I can choose speed or quality per Shot.
  5. As a filmmaker, I want each Take named 12A-3 with numbers never reused, so that names are stable for the editor.
  6. As a filmmaker, I want a queued Take to have its number, seed and recorded inputs as soon as it is queued, so that I can see if it's already stale before it renders.
  7. As a filmmaker, I want "Rebuild with current inputs" on a waiting Take, keeping its number and seed, so that I can fix a stale queued job.
  8. As a filmmaker, I want image jobs to run before drafts and drafts before finals, FIFO within each class, so that quick work is never stuck behind an overnight batch.
  9. As a filmmaker, I want the worker to prefer jobs that use the model already loaded, within a class and with a skip limit, so that model swaps are rare and nothing waits forever.
  10. As a filmmaker, I want to drag jobs within a class and use Run next to put a selection at the front, so that I can get urgent work done now.
  11. As a filmmaker, I want Pause/Resume, per-job Hold and a sticky Hold finals switch, so that I can save finals for overnight.
  12. As a filmmaker, I want to cancel a queued or running job, with a Take that has no Rendition deleted and its number burned, so that the queue never leaves junk.
  13. As a filmmaker, I want at most one pending final job per Take, so that I never promote the same Take twice by accident.
  14. As a filmmaker, I want the queue to reopen paused with a banner, and an in-flight job reattached, collected or requeued, so that a crash or restart never loses or duplicates work.
  15. As a filmmaker, I want a warning when I close with a job running, and the job left running in ComfyUI if I confirm, so that closing the app doesn't waste GPU time.
  16. As a filmmaker, I want ComfyUI errors to fail the job with no auto-retry, except one retry after freeing memory on OOM (a setting), so that bad jobs don't loop.
  17. As a filmmaker, I want "ComfyUI offline" to be a waiting state, so that restarting ComfyUI never fails my queue.
  18. As a filmmaker, I want jobs for a Not-ready workflow to wait instead of failing, so that installing a missing node fixes things without requeuing.
  19. As a filmmaker, I want preparation (Atlas builds, uploads) overlapped with the current render and post-steps (proxies, posters) run in a CPU lane, so that the GPU never sits idle.
  20. As a filmmaker, I want a dockable Queue panel and a status-bar indicator (running job and %, waiting count, Paused / Finals held / ComfyUI offline), so that I always know what the GPU is doing.
  21. As a filmmaker, I want failed jobs pinned to the top of the Queue panel until I dismiss them or open the Take, so that failures are never missed.
  22. As a filmmaker, I want status badges (Queued / Held / Rendering % / Finishing / Failed) on Take tiles and Shot listings, so that I can see progress where I work.
  23. As a filmmaker, I want live per-job progress, so that I can tell a slow job from a stuck one.
  24. As a filmmaker, I want draft Renditions saved with their split latent, H.264 MP4 and lossless FLAC, so that Promotion can reuse them later.
  25. As a filmmaker, I want final Renditions as a ProRes 422 HQ master with 01:00:00:00 timecode and PCM audio, plus an H.264 proxy for in-app playback, so that the editor gets real masters and I can still play them.
  26. As a filmmaker, I want a "proxy missing" marker and a retry action when a post-step fails, keeping the GPU output, so that a small ffmpeg failure never wastes a render.
  27. As a filmmaker, I want lengths rounded up to H3's frame grid with the extra frames as tail handle, so that Shots are never cut short.

Dailies

  1. As a filmmaker, I want one Dailies screen per Scene, so that I review coverage the way it will be cut.
  2. As a filmmaker, I want a coverage wall with a row of Take tiles per Shot, grouped by Setup, with the Shot's Board, a stale count and a "no circled Take" warning, so that I can see gaps at a glance.
  3. As a filmmaker, I want to tick up to four Takes into a synced side-by-side compare tray, so that I can choose between performances.
  4. As a filmmaker, I want a "+ Take" tile at the end of each Shot row, so that I can roll more coverage in place.
  5. As a filmmaker, I want a sortable, filterable table of every Take (unseen, circled, stale, draft-only, NG) with batch Promote and batch new seed, so that I can manage many Takes at once.
  6. As a filmmaker, I want a keyboard screening mode for unseen Takes (C circle, N note at playhead, P promote, R new seed, J/K next/prev, hold B to flip to the circled Take), so that I can get through dailies fast.
  7. As a filmmaker, I want a details panel with an A/B wipe against another Take and a table of the Take's inputs against the current ones, including the sent slot assignment, so that I can see exactly what changed.
  8. As a filmmaker, I want to circle several Takes per Shot and pin one for the Animatic, with the most recently circled used when none is pinned, so that I can keep options without losing the cut.
  9. As a filmmaker, I want a Take marked seen once it has played through in screening or been opened in details, with "mark all seen" per Shot and Scene, so that unseen means unseen.
  10. As a filmmaker, I want a list of notes per Take, each with an optional timecode, so that I can note a moment precisely.
  11. As a filmmaker, I want to promote a Take (including a stale one, with a warning naming the stale inputs), so that I can finish what I like.
  12. As a filmmaker, I want Promotion to keep the draft's audio exactly and rebuild from the draft's recorded inputs, so that a promoted final matches the draft I circled.
  13. As a filmmaker, I want to re-promote a promoted Take with a new refine seed, replacing its final in place only on success, with a "re-promoting" badge meanwhile, so that I can improve a final without a new Take number.
  14. As a filmmaker, I want a newly rendered final to mark the Take unseen while keeping circles, pin, notes and Line marks, so that I re-check finals.
  15. As a filmmaker, I want "same take, new seed" to roll a new Take with current inputs by default (and original inputs as an option on stale Takes), recording which Take it came from, so that I can explore variations.
  16. As a filmmaker, I want failed Takes to offer Retry (same Take, same seed) and Delete, so that failures are easy to clear.
  17. As a filmmaker, I want to mark a Take NG, hidden from the wall and the reel but kept in the table under an NG filter, so that I clean up review without losing provenance.
  18. As a filmmaker, I want circled Takes not to be markable NG, so that my two judgments never conflict.
  19. As a filmmaker, I want to nudge a Take's Line marks in the details panel, so that the Animatic cuts on the real performance.

Staleness

  1. As a filmmaker, I want a Take shown as stale whenever a re-roll with current inputs would send a different job (ignoring seed and profile), so that I know which clips no longer match my references.
  2. As a filmmaker, I want each difference listed as a named Stale reason grouped by kind (References, Board/Keyframes, Performance, Prompt, Framing, Workflow), so that I can judge whether it matters.
  3. As a filmmaker, I want to accept a Stale reason on one Take until that input changes again, and "Accept all", so that harmless changes stop nagging me without changing provenance.
  4. As a filmmaker, I want workflow updates shown only as information, so that an app update doesn't mark my whole film stale.
  5. As a filmmaker, I want stale badges and a sortable Stale column on every Take in Dailies, but only a "N circled stale" count in Shot and Scene listings and a corner mark in the Animatic, so that staleness is visible where it matters without noise.
  6. As a filmmaker, I want "Re-roll stale circled Takes" per Scene and per Reference set, with a confirm sheet showing counts, estimated time and a draft/final toggle, so that I can refresh coverage after a reference change in one action.
  7. As a filmmaker, I want batch re-roll to skip Shots that already have a current queued Take and never change circles, so that it is safe to run twice.

Animatic and Cut

  1. As a filmmaker, I want an Animatic per Scene that plays the Cut, with each Line from exactly one covering Shot and no dialogue repeated, so that I can watch the Scene before rendering everything.
  2. As a filmmaker, I want each Line assigned by default to the first covering Setup in Setup order, and to override individual Lines in a Line lane, so that I can make cut decisions quickly.
  3. As a filmmaker, I want overrides whose Shot no longer covers the Line to fall back and be flagged "reassigned", so that the Cut never points at nothing.
  4. As a filmmaker, I want Line durations taken from the Performance, else a manual override, else a text estimate from per-Project rates, so that the Animatic is timed sensibly from day one.
  5. As a filmmaker, I want circled Takes to replace Boards and play between their Line marks with their own audio, so that the Animatic becomes a rough cut.
  6. As a filmmaker, I want Board events to play the Performance slice, or silence with dialogue captions, and Panels spread across the Shot's span, so that pacing reads even before any Take exists.
  7. As a filmmaker, I want uncovered Lines shown as a grey "no coverage" slate with captions, so that gaps are obvious.
  8. As a filmmaker, I want transitions and shot directions shown as zero-length markers, so that the script's intent is visible on the timeline.
  9. As a filmmaker, I want "play from here" to run on through later Scenes, skipping omitted ones, so that I can watch the film continuously.
  10. As a filmmaker, I want stale playing Take events marked in a corner, so that I know what to re-roll.
  11. As a filmmaker, I want other-ratio Shots letterboxed into the Project ratio, so that the Animatic stays one format.

Performance

  1. As a filmmaker, I want to Bounce the Scene's Cut audio into a new Performance version, so that a set of circled Takes' dialogue becomes the locked timing for every Setup.
  2. As a filmmaker, I want Lines with no circled Take to carry forward the previous Performance's audio, or become silent holes listed in a warning, so that a Bounce never invents dialogue.
  3. As a filmmaker, I want to import Performance audio for a contiguous run of Lines, with marks auto-placed by silence detection and then corrected by hand, so that I can use recorded dialogue.
  4. As a filmmaker, I want marks to be a contiguous partition (pauses to the earlier Line, action Lines splitting gaps, dual dialogue as one span), so that every moment of audio belongs to a Line.
  5. As a filmmaker, I want a Performance lane on the Animatic with the waveform, draggable boundaries, warning badges, uncovered-Line shading and a ghost lane of the previous version, so that I can mark Lines visually.
  6. As a filmmaker, I want mark edits to be undoable and to make stale only Shots whose slice moved, so that small fixes don't invalidate everything.
  7. As a filmmaker, I want the Performance audio immutable and new audio to be a new version, so that a Take can always be explained.
  8. As a filmmaker, I want each Shot over covered Lines sent its Performance slice as both forced audio and a fully_copy audio slot, so that every Setup matches the locked timing.
  9. As a filmmaker, I want a Take's Line marks snapshotted from the Performance when queued, so that later mark edits never move a Take's cut points.
  10. As a filmmaker, I want a Shot that straddles covered and uncovered Lines to block queuing with "split the Shot or extend the Performance", so that a Take never gets half a Performance.
  11. As a filmmaker, I want a per-Scene switch to turn the Performance off without losing marks, so that I can let H3 generate dialogue again.

Edit package

  1. As a filmmaker, I want to export an Edit package folder for the Project, picking Scenes and an optional Circled only filter, with the folder remembered, so that handing off is one action.
  2. As an editor, I want per-Scene bins with per-Setup sub-bins of 12A-3.mov clips (segmented Shots 12B_1-3.mov), so that the Media Pool reads like real coverage.
  3. As an editor, I want every clip to be a .mov starting at 01:00:00:00 (the ProRes master if final, otherwise the draft remuxed with PCM audio), so that clips behave consistently.
  4. As a filmmaker, I want finals hard-linked into the package when it's on the same volume, so that exporting doesn't double my disk use.
  5. As an editor, I want per-Scene and whole-film timelines as FCPXML 1.10 (one event, one project, no bins or markers) and .otio (with markers), so that I can start from the Animatic in Resolve.
  6. As an editor, I want Take events to reference whole clips with in and out points at the Line marks, so that handles are available.
  7. As an editor, I want Board and slate events as PNG stills at final size with burned-in captions, a sidecar .srt, and per-Scene Performance audio, so that the Animatic is complete in Resolve.
  8. As an editor, I want a UTF-8 takes.csv for Resolve's metadata import (matched by file name and start/end TC) that sets Scene, Shot, Take, Tag=1 for circled, the framing note, keywords (draft/final/stale), notes as Comments, and Seed/Profile/Pinned columns, so that circled status and notes survive into Resolve.
  9. As a filmmaker, I want a manifest.json with each Take's full recorded inputs and the packaged Rendition's id and hash, so that any clip can be explained and regenerated later.
  10. As a filmmaker, I want re-export to sync in place (skip unchanged, overwrite changed Renditions, rewrite timelines and metadata, move dropped clips to _Removed/<date>/), using temp files and renames, so that the editor's links keep working and nothing is deleted.
  11. As a filmmaker, I want a warning before switching Circled only on, so that I know clips will move to _Removed/.
  12. As a filmmaker, I want staleness never to block export, so that I can always hand off.
  13. As a filmmaker, I want export to run with progress, so that large packages don't look frozen.

Storage

  1. As a filmmaker, I want a project-wide Storage view with totals for the project file (and its free space), the media folder and the Edit package, so that I know where my disk went.
  2. As a filmmaker, I want one sortable table of every item that uses space, with what uses it, its real reclaimable size (by hard-link count) and badges, so that I free space safely.
  3. As a filmmaker, I want to Purge Rendition files and latents (keeping the row and its poster frame), blocked for circled Takes and for latents with a pending job, so that I can reclaim space without losing provenance.
  4. As a filmmaker, I want each Purge to show the exact bytes freed and what stops working, and to be irreversible only after that confirmation, so that I never lose work by surprise.
  5. As a filmmaker, I want purged Takes to stay in Dailies as purged tiles with "Re-roll same seed", so that I can bring them back.
  6. As a filmmaker, I want to delete unused Source sheets from Storage, with a "Used by" list and jump links when they're in use, so that I can clean imports.
  7. As a filmmaker, I want suggestion filters (NG Renditions, latents and drafts of finalled Takes, unused sheets, orphan files, _Removed/, Compact) that only select rows, so that nothing runs without me.
  8. As a filmmaker, I want "Empty _Removed" with a warning about Resolve links, so that only I delete things from the package.
  9. As a filmmaker, I want a manual Compact with the queue paused, so that I can shrink the project file.
  10. As a filmmaker, I want files that disappeared outside the app shown as missing and treated like purged, so that the app never crashes on a missing file.
  11. As a filmmaker, I want Dailies shortcuts (free space on NG Takes, delete failed Takes) to use the same rules as Storage, so that behaviour is consistent.

Implementation Decisions

Architecture (ADR-0003, #3)

  • A Cargo workspace with a thick studio-core crate (no Tauri, no specta). It owns the domain model, SQLite, OSF import, Lock snapshot rules, staleness, Atlas composition, the prompt composer, the slot budget, the render queue and worker, the ComfyUI client, ffmpeg calls and export. src-tauri is a thin adapter.
  • Only Rust opens the .studio file. The frontend changes state only through named domain commands (taurpc). DTOs are defined in the adapter and mapped from the core's view-shaped read models. Commands return a typed error enum.
  • Coarse changed { kind, ids } invalidation events are broadcast to all windows, and views refetch. tauri::ipc::Channel carries per-job progress and progress for long operations (OSF import, export).
  • One ProjectSession per process holds the connection, the undo log and the worker. It may be shown in several windows.
  • Undo is an in-memory Rust command log, per Project, that covers authored edits only. Locking, queuing, rendering, generating and importing are not undoable. Cancelling a job is an explicit queue command.
  • One studio: custom scheme (async) serves blob/<id> from SQLite and rendition/<id> with Range/206. URLs carry ids, not paths, and the webview has no file scope.
  • ffmpeg is an unmodified LGPL sidecar that only Rust calls. It makes posters, thumbnails, audio extraction and decoding, remuxing, proxies and timecode stamping. It never encodes Renditions except the H.264 proxy.
  • Rust produces every stored or derived image deterministically. Boards are flattened from the stored sketch and annotation layers, Atlases and packed Prop images are built with the image crate, and Tile crops are cut from the original sheet. The UI only draws.
  • Svelte 5 holds view state and interaction, including the Board paint canvas and its local stroke undo until save.

Project file (ADR-0001, ADR-0004, ADR-0005, #11)

  • .studio is a SQLite database. Renditions and latents live in a sibling <name>.media/ folder under ID-based names (renditions/<rendition-id>.*, latents/<rendition-id>.{video,audio}.latent) with relative paths.
  • ULID primary keys everywhere. Human labels (frozen Scene number, Setup letter, Take number) are plain columns. Scenes keep d:id as a unique column.
  • One content-addressed blob table (BLAKE3, media type, size, pixel dimensions). Unused blobs are swept on open and on demand, with no reference counts.
  • Lifecycle: PRAGMA user_version migrations embedded in the core, applied in one transaction after copying to <name>.studio.bak-v<N>. A newer file is refused. A fixed application_id. WAL while open, then checkpoint and journal_mode=DELETE on clean close. A <name>.studio.lock file holds PID and host.
  • Settings live on a singleton project row. Lists get their own tables (lighting, slug_lighting, reference_category_order, notation_entry). App-wide defaults are a JSON file in the app config directory, copied into a Project at creation and never read again.
  • Deletion follows ADR-0004. Rows that any Take consumed get deleted_at. Unconsumed rows are hard-deleted. Failed Takes may be hard-deleted, and numbers are never reused. Merged Characters and Locations become tombstones with merged_into.
  • Separate character, location and prop tables. Each Reference set owner holds a unique FK (look, wardrobe_item, prop, character.voice_set_id, and location_lighting(location, lighting, reference_set, lighting_note)). reference_set.kind is one of turnaround, mannequin, location, prop or voice. Tiles have role and ordinal (fixed roles for turnaround and mannequin) and are retired, never deleted, once any snapshot includes them.
  • Reference versions are append-only, each with its own blob and, for imported versions, the Source sheet and crop box. A generated version exists as a row with no pixels from the moment it is queued.
  • lock_snapshot(set, number) + snapshot_tile is immutable, and the current snapshot is the highest number.
  • atlas(input_key, blob, built_at) is a cache (ADR-0005). The key hashes the ordered snapshot IDs, the layout version and the size settings. Setups store only their grouping.
  • Lines have an immutable position, and the script is frozen after import. Shots store first_line_id/last_line_id. setup(scene, letter, setup_order). shot(setup, index, next_take_number). scene_cast(scene, character, look, wardrobe_item?) and setup_subject(setup, character, position).
  • Shot action text is a JSON node document (text, mention{kind,id}, dialogue{line_id}) plus a derived shot_mention table rewritten on save. A merge rewrites both in one transaction. The prompt override is plain text plus its fork-base composed text.
  • Slot list: shot_slot_item(shot, position, candidate_key, state, is_new) + shot.slot_line, with logical keys (keyframe:<id>, atlas:<group>, wardrobe:<id>, location_angle:<tile>, board, prop:<id>). No rows means the Shot follows the Project order live.
  • panel(board, position, canvas_w, canvas_h, sketch_blob, annotations JSON) with normalized annotation positions. Boards are edited in place with no history.
  • keyframe(shot, anchor, at_seconds?, image_blob, origin). A take_frame Keyframe records its source Take, Rendition and frame time.
  • performance(scene, number, audio_blob, created_at) is immutable. performance_source is an open edge list of source kinds (Takes, carried-forward Performance, import). performance_mark(performance, line, start) plus an end, a coverage flag per Line per version, and scene.performance_off.
  • Take provenance: take_input has one row per consumed thing, with a real FK and the slot number it filled. Shot and Setup text fields are recorded by value or hash. The exact job graph and prompt text are stored as blobs, plus an overridden flag. take_link(take, other_take, kind) with kinds rerolled_from and frame_from (and extends reserved for Take extension). The Take also records its Performance slice and snapshotted marks, seen, NG, notes with optional timecodes, circled-at, accepted reasons, and the shipped workflow id/version/hash.
  • shot.pinned_take must be circled and is cleared on uncircle.
  • rendition(take, kind, via, state, path?, purged_at, size, hash, poster_blob, refine_seed?), created when queued. Latents have their own purged marker. Missing is computed when Storage opens.
  • job has one row per ComfyUI submission: kind, exactly one target FK (Rendition, Reference version or Panel), state, queue_position, attempt, our own prompt_id, held flag, error and timestamps. The Hold finals switch is persisted on the Project.
  • The aspect ratio is on the Project (fixed list) with a per-Shot override. A Shot's effective ratio feeds staleness. Panels and Keyframes keep their pixels and are barred or flagged at send time.
  • The full DDL is written by the build issue that creates each table.

OSF import (#2)

  • Accepts OSF 4.x only, resolving custom styles through basestyle. Reads only scene_time_separator. Ignores <lists>, <styles>, revision colours and empty paragraphs. Strips inline formatting but keeps soft breaks.
  • The Line rules, ID rules, Character normalization, cue-extension enum, SUBTITLE fields, heading split, auto Location folders, slug→Lighting seed table, dual-dialogue simultaneous group, frozen numbering, omitted placeholders, text page numbers, title/author, and read-only notes are as stated in User Stories 14–34.
  • Import writes straight into the Project and returns a summary of counts, warnings and flagged items. There is no staged review.

References and Atlases (#4, #5, #10)

  • Crop sessions target one Reference set. Auto-fit maps the four largest detected figures in reading order to face → front → side → back. Other figures become faint snap targets. Fewer than four leaves Tiles unboxed. Commit makes versions only for changed crops and never locks.
  • Atlas layout v1: rows per Look (≤ 3) in Setup subject order (or the Setup's regroup), columns face → front → side → back, uniform 4:9 cells, fit-and-pad on neutral mid-grey, wider row gutters, no text. Megapixel budget (1.0 MP default, never upscaled) or native, per Project with a per-Setup override. Missing Tiles collapse their cell, and only same-Parity Looks combine. All of this is part of the content identity.
  • Packed Prop image: the Prop snapshot's Tiles side by side in uniform cells on mid-grey, identified by content. Joined Voice reference: a Character's snapshot clips concatenated with short gaps.

Prompt composer (#9)

  • A pure function from a Shot's current inputs plus the filled slot assignment to H3's six-field text, always in this order and with these names: subject_definitions, summary, retention_analysis, detailed_description, overall_soundscape, non_diegetic_music (always N/A). Exactly one [Shot 1], with no id= header.

  • <Subject N> is numbered by first appearance in the Shot, and <Prop N> by attachment order. <Environment> is the Location. <Picture N>/<Audio N> are numbered in wired order after the budget is filled. No names or pronouns.

  • Retention entries, taken from #9:

    Input Entry
    Look, default wardrobe fully_preserved - face, hair, eyes, physique and clothing.
    Look + Wardrobe item Atlas row partially_preserved - … clothing is not inherited.; mannequin attribute_transfer - the <item> is worn by <Subject N>; the mannequin does not appear.
    Location angle fully_preserved - architecture, layout, fixed objects and lighting; the camera angle may differ.
    Prop fully_preserved - shape, material, wear and markings.
    Board weak_reference - rough storyboard of N panels, staging/composition/camera only, style and figures not reproduced, arrows are directions
    Keyframe fully_preserved - first frame at 00:00.000 … / last frame at <ms>
    Voice reference reference - its vocal timbre guides the vocal delivery of <Subject N> …
    Performance slice fully_copy - the dialogue of this shot …. Voice references are dropped.
    Subject without picture newly_generated - no visual reference …
  • detailed_description = the framing note, then the Notation read-back sentences in panel order (never naming the storyboard), then the action text with dialogue chips expanded as <Subject N> says: <d>[Language] <tag> text</d>. O.S./O.C. becomes "says from off-screen:", V.O. becomes "says in voice-over:", and dual dialogue becomes "… speak at the same time." Character-naming parentheticals move out of the quote.

  • overall_soundscape = the Location's Ambience + the Scene's Ambience addition.

  • An override is forked from the preview and stores its fork base. A composer change shows only a quiet marker, and the three-way reconcile runs on demand. Slot tags in an override are validated, and a broken one blocks queuing. A Take records the exact text and an overridden flag.

Slot budget (#10)

  • Binding limits are 9 image + 3 audio. No video slots in the slice.
  • A per-Project category order (seeded from the app default) resolves into a per-Shot Slot list. Overflow first collapses a per-Panel Board to tiled. Edited lists persist with the rules in User Stories 110–113.
  • Audio: a Performance slice (one slot, Voice references dropped), else Voice references of speaking Characters (dialogue chip present, O.S./V.O. count) ordered by first Line, one slot each.
  • No per-Scene or per-Take override. Forced drops never block queuing.

Shipped workflows and ComfyUI (ADR-0002, #13, #14, #15, #17, #20)

  • Five workflows ship in the repo as API-format JSON templates plus manifests: h3.draft, h3.promotion, h3.final, image.t2i and image.board (both image workflows on Qwen-Image 2.1, and image.board with its own fixed rough-style prompt). The owner's SEEDHUNTER graph is a technique reference only.
  • A manifest names scalar bindings (node_id.input), output nodes, the node pack name/URL for each non-core class_type, and the folder/URL for each fixed model file. Rust makes at most three structural edits: one LoadImage/LoadAudio per filled slot wired into the MiniMaxH3ReferenceToVideo Autogrow ports in slot order; a LoraLoaderModelOnly chain at a named model point (the chain's position relative to the shipped LoRAs doesn't matter); and, for Performance-driven Takes only, the forced-audio branch (VAEEncodeAudio → 0 mask). The filled graph is the job blob.
  • Each workflow has a stable id and a hand-bumped integer version. CI fails if the template hash changes without a bump. Jobs record id, version and hash. Only versions marked as changing output raise the (informational) Workflow reason. Promotion always uses the current h3.promotion. Old versions are not kept.
  • Generation profile: the diffusion model file (FL2VA/Ref2VA, any quantization), the text encoder file, a user LoRA stack (applied to both passes), and a speed mode (Distilled = shipped DMAD 4-step LoRA + steps, Full = shipped higher steps). Short sides default to 768/1120, the long side follows the effective ratio, and Rust rounds both to ×32 (there is no ResolutionSelector in the graphs). Fixed in the workflow: VAEs, upscaler weights, sampler and schedulers, refine denoise, attention and low-VRAM settings.
  • Draft: first pass → LTXVSeparateAVLatent → 2× SaveLatent, plus an H.264/AAC MP4 at 24 fps and a lossless FLAC (core SaveAudio). Saved latent file names are read from history and copied into the media folder.
  • Promotion: 2× LoadLatent "<file> [output]" → MinimaxH3LatentUpscaler3D → LTXVConcatAVLatent → refine pass at final size, with conditioning rebuilt from the draft's recorded prompt, slot assignment and blobs. The draft's audio is always frozen with a 0 SetLatentNoiseMask. Each Promotion gets a new random refine seed (editable, and recorded on the final).
  • Single-pass final: one pass at final size, with no latent saved.
  • Finals: a ProRes 422 HQ .mov master written by the workflow. A CPU post-step then stamps 01:00:00:00 and PCM audio into it and makes an H.264 proxy at short side 768 (h264_nvenc if a startup check finds it, otherwise libopenh264). Every H3 job writes a lossless FLAC.
  • Performance: Rust cuts the Shot's slice (Lines plus handles), pads it to the frame count, and sends it as forced latent audio (VAEEncodeAudio → 0 mask) and as the fully_copy slot.
  • Length: seconds round up to 5 + 17k frames (≤ 362), and the job records both the requested seconds and the frame count.
  • Image sizes are set by the caller: turnaround and mannequin Tiles 4:9 at ~1 MP, location angles at the Project ratio ~1 MP, Prop Tiles 1:1 ~1 MP, Keyframes at the Shot's exact final W×H, Boards at ~1 MP at the Panel ratio, downscaled by Rust to the canvas.
  • ComfyUI client (Rust only, because loopback Origin checks block the webview): WS /ws?clientId, POST /api/upload/image for images and audio (use the returned name), POST /api/prompt with our own UUID prompt_id, where a 200 with non-empty node_errors is a failure. Progress follows the documented event order, and executing{node:null} means done. Results are fetched from /api/history/{id} (walk every output key) and /api/view, and persisted immediately because history is in memory only. Cancel uses POST /api/jobs/{id}/cancel (feature-detected), falling back to interrupt-with-id + queue delete. The ComfyUI queue is shared with the user's own tab.
  • Preflight runs on connect, on demand, and before each submission. It checks reachability and version ≥ 0.39.0 (/system_stats), every class_type (/object_info, both combo encodings), every fixed model file and combo value, and the job's profile files (/api/models/*). The result is Ready or Not ready with reasons. It never installs anything. Pickers read /api/models/*.

Render worker (#18)

  • Jobs are built at queue time. Classes run images → drafts → finals, FIFO within a class. Model affinity applies within a class (checkpoint + quantization, ignoring LoRAs) with a skip limit of ~3 passes or a few minutes. Drag reorders within a class. Run next goes to the global front and is exempt from affinity. No pre-emption.
  • Pause/Resume, per-job Hold and the sticky Hold finals switch. Cancel works on queued and running jobs (interrupt, discarding partial output). Cancelling a Take that has no Rendition deletes it and burns its number. At most one pending or running final job per Take.
  • Always reopens paused, with a banner. The in-flight job is reattached by prompt_id, collected from history, or requeued at the head of its class as an interrupted attempt. Closing with a running job warns and then leaves it running.
  • ComfyUI errors fail with no retry. OOM retries once after /free (a setting). Offline is a waiting state with re-probing. Not-ready workflows' jobs wait. A failed draft or Single-pass final becomes a Failed Take. A failed Promotion leaves the draft plus a "Promotion failed" note with Retry. A post-step failure keeps the GPU output with a "proxy missing" marker and retry.
  • One ComfyUI submission lane. Preparation for the next job overlaps the current render, and a CPU post-step lane runs 1–2 at a time. A Take shows Finishing until its post-steps are done. Poster frames are extracted when a Rendition is collected.

Staleness (#12, #23)

  • Computed, never stored: diff the Take's recorded job inputs (take_input, recorded text and hashes, slot assignment, Performance version and slice) against the job the Shot would build now, ignoring seed and Generation profile. Each difference is a Stale reason of kind References, Board/Keyframes, Performance (version, marks/slice), Prompt, Framing (including effective aspect ratio) or Workflow (informational only).
  • On a Shot with an override, Takes are compared with the override text. A composer change alone affects only the Shot marker.
  • An accepted reason stores the input's current value and lapses when the input changes. Accepting never changes provenance.
  • Queued Takes are judged like any other Take. Batch re-roll and its skip rule follow User Stories 168–169.

Dailies (#7)

  • One screen per Scene, toggling between the coverage wall and the table, plus screening mode and the details panel as described in User Stories 144–162. The prototype on branch prototype/dailies is the visual reference.
  • Circling, pinning, notes, seen, NG and failed-Take rules are as stated in User Stories 151–161.

Animatic and Performance (#8, #23)

  • The Cut is computed per Scene from Setup order with stored per-Line overrides (simultaneous groups as units). Events merge neighbouring Lines on the same Shot. Line durations come from the Performance, else an override, else an estimate (dialogue 2.5 words/s + 0.5 s per parenthetical, action 1.5 words/s with a 1 s minimum; rates per Project).
  • Default Take Line marks: snapshotted Performance marks if there is a Performance, otherwise the Rendition length minus handles spread in proportion to Line durations. Marks are nudgeable. An event plays the final if there is one, else the draft.
  • Shot length = Lines + head/tail handles rounded to the frame grid, with an override clamped to 2–15 s. The split helper splits at Line boundaries in the same Setup.
  • Bounce, import auto-placement (silencedetect), the mark partition rules, the coverage set, the straddle block and the ghost lane are as in User Stories 181–191. Re-run auto-placement is one undoable edit.

Edit package (#16, #19, #21)

  • The folder layout, taken from #19:

    <package>/
      012 MARKS HOUSE - KITCHEN/   per-Scene bin (zero-padded frozen number + ASCII-safe heading)
        12A/  12A-1.mov  12A-3.mov
        12B/  12B_1-2.mov  12B_2-1.mov
      Animatic/                    Board/slate PNG stills, per-Scene Performance.wav
      Timelines/                   <Scene>.fcpxml / .otio / .srt, Animatic.fcpxml / .otio (whole film)
      _Removed/<date>/
      takes.csv
      manifest.json
    
  • FCPXML 1.10 is hand-written with exactly one event holding one project, and no browser clips, bins or markers (otherwise Resolve fails with "No valid sequences"). Timeline: the Project ratio at final size, 24 fps, starting at 01:00:00:00, with drafts scaled to fit. .otio carries timeline markers (transitions, shot directions) and clip markers (timecoded notes).

  • takes.csv headers: File Name, Clip Directory, Start TC, End TC, Scene, Shot, Take, Tag (1 for circled, blank otherwise, never 2), Description, Keywords, Comments, plus custom Seed, Profile, Pinned. UTF-8. Never use the Good Take column.

  • The sync algorithm is driven by the previous manifest, with temp-file writes and renames. Hard links are used on the same volume, otherwise copies.

Storage (#22)

  • Purge applies to media-folder files only (Rendition files and latents), with the guards and confirmations described in User Stories 207–209. In-file items are deleted only from their own editors (ADR-0004), except Source sheets, which Storage can delete when unused.
  • Reclaimable bytes are counted per file from its hard-link count. Orphan files are found by a scan when Storage opens. Compact is a manual VACUUM with the queue paused. In-file deletes are undoable within the session, while Purge and Empty _Removed are not.

Testing Decisions

  • One primary seam: the public API of studio-core's ProjectSession. Tests drive the same named domain commands and view-shaped read models that the taurpc adapter wraps (import OSF, crop and lock, set up Shots, queue, collect, circle, promote, bounce, export, purge…). Each test opens a real .studio file in a temp directory. Tests never read tables or call private modules directly.
  • What a good test looks like: it states a user-visible behaviour, drives it through ProjectSession, and asserts only on what the API returns or on the artifacts the core writes. Examples: the composed prompt text for a Shot; the filled job graph sent to ComfyUI; an Atlas's bytes or hash (golden, because it must be byte-stable); the read model's Stale reasons; the Edit package tree, takes.csv, FCPXML and .otio contents. Tests don't assert on table layout or internal structs, so the schema can change freely under them.
  • The only fake is ComfyUI. A scriptable in-process HTTP + WebSocket server that speaks the real protocol: /api/prompt (recording each submitted graph and prompt_id), /api/upload/image, /ws event sequences, /api/history, /api/view, /api/jobs/{id}/cancel, /api/interrupt, /api/object_info, /api/models/*, /system_stats and /free. It returns canned fixtures (a short MP4, ProRes .mov, FLAC, latent files, PNG). It can be scripted to produce: a 400 validation error, a 200 with node_errors, execution_error, OOM, being offline, a job still running or finished when a Project reopens, history lost after a restart, and missing nodes or models for preflight.
  • Everything else is real: SQLite, the filesystem (including hard links into a temp Edit package), and the ffmpeg sidecar (posters, remux, timecode stamping, proxy via libopenh264, silencedetect).
  • What gets tested through the seam: OSF import (against the owner's sample script and small crafted edge cases), Reference versioning and Lock snapshots, Atlas identity and pixels, slot budget resolution, prompt composition, job building and the two splice points, queue ordering and lifecycle, crash and reopen recovery, staleness and accepted reasons, Dailies state rules (circle, pin, NG, seen, notes, re-roll, re-promotion), Cut and Line durations, Performance Bounce, import auto-placement and marks, Edit package export and re-sync, Storage purge guards and reclaimable bytes, undo of authored edits, and schema migration and backup.
  • Outside the seam:
    • A unit test over the shipped workflow templates checks that every manifest binding resolves against its template. A CI check fails when a template hash changes without a version bump. These guard content, not behaviour.
    • The src-tauri adapter gets only a smoke test (it is thin DTO mapping).
    • Svelte views (crop stage, Board paint tool, Dailies, Animatic lanes, Queue panel, Storage) are checked by hand against the prototype branches (prototype/crop-sheet, prototype/board-paint, prototype/dailies).
    • Real H3 output quality and real Resolve import are checked by hand with the owner's kits (research/h3-latent-reuse, local scratch/resolve-test/), not in CI.
  • Prior art: there are no tests in the repo yet. The closest prior art is the job-splitting script and job graphs on branch research/h3-latent-reuse (research/h3-latent-reuse-run/), which show the exact draft and Promotion graphs that the job builder's output should match, and the Resolve test kit's reference FCPXML, OTIO and CSV files, which are golden references for export.

Out of Scope

These are beyond the slice and come back only as a new effort (from #1):

  • Blender previz (playblast as a video reference). Video slots stay unused.
  • An MCP server and prompt templates. No LLM in the slice: Props come from text selection or manual creation.
  • Lined script export.
  • A blocking plan editor (the slice keeps only a UI placeholder), derived framing, and Board underlays.
  • A Board cleanup pass (ControlNet / edit model).
  • Continuity state.
  • Take extension (seamless continuation with overlap). It is the next thing wanted, so provenance (take_link kinds, chained inputs) must not rule it out.
  • Script re-import with reconciliation. The script is frozen after import.
  • A render daemon that runs while the app is closed.
  • In-app inpaint or edit-model reference editing. Editing happens outside the app.
  • Takes with internal cuts ([Shot 2] blocks, intercuts).
  • SeedVR2, NVIDIA upscaling and RIFE as finishing steps (RIFE may later be an opt-in derived file, never a final).
  • Alternate Performance voice sources (local TTS). performance_source must stay open to new kinds.
  • Forced alignment or ASR auto-marking of imported Performances.
  • Fountain import (only the owner's OSF dialect is supported).
  • Score and music: non_diegetic_music is always N/A, and music is added in Resolve.
  • FCP 7 XML export.

Further Notes

  • Unverified items to watch in the build:
    • Peak VRAM for a Single-pass final at 1120 short side for 15 s has not been measured. Only Promotion was (~283 s, ~31.8 GB).
    • Whether H3 renders composited Annotation marks literally has not been seen. The clean-sketch toggle exists as a hedge.
    • Whether FCPXML keyword/note reach Resolve's Media Pool is unknown, which is why only the CSV is relied on.
    • Attribute bleed in mixed Atlases is still open. Per-Setup regroup is the mitigation.
  • Hardware assumptions: one RTX 5090 / 32 GB, ComfyUI ≥ 0.39.0 managed by the user, and Resolve 18+ (tested on 21.1.1 free).
  • Research notes live on branches research/comfyui-api, research/finishing-pass, research/h3-latent-reuse and research/resolve-fcpxml. UI prototypes are on prototype/crop-sheet, prototype/board-paint and prototype/dailies. Treat prototypes as visual references only, not code to keep.
  • The source plan, scratch/scratch-plan.md, is local and gitignored. Where it conflicts with this spec (for example its LLM prop proposal, Fountain import or RIFE), this spec and the closed tickets win.
  • The next step is to split this spec into build issues, each of which writes the DDL for the tables it creates.
Spec for the end-to-end slice charted in #1. Every decision below comes from a closed child ticket of #1 (linked inline). Terms follow `CONTEXT.md`. ADRs 0001–0005 apply. ## Problem Statement A filmmaker who works alone with a local video model (MiniMax H3 through ComfyUI on one RTX 5090 / 32 GB) has no tool that connects a screenplay to an editor's timeline. Today they hand-build references, hand-write long six-field H3 prompts, keep track in their head of which reference version fed which clip, and rename and sort hundreds of generated files before an editor can cut them. Nothing records why a clip looks the way it does, nothing flags clips made from references that have since changed, and nothing turns "the takes I like" into something DaVinci Resolve understands. The work is slow and error-prone, and it can't be reproduced. ## Solution AI Studio is a local desktop app (Tauri 2 + SvelteKit + Svelte 5, with a Rust `studio-core` that owns the domain). It takes a screenplay through a pipeline organized like real production: **OSF script → Entities → locked References → Atlases → Setups / Shots with Boards and Keyframes → Takes (drafts, Promotion, Single-pass final) → Animatic → Resolve Edit package.** The filmmaker imports an OSF script. The app creates Scenes, Lines, Characters and Locations. The filmmaker builds Reference sets by importing and cropping sheets or by generating Tiles, and locks them as Lock snapshots. They plan Setups and Shots, draw Boards with a structured Notation, and queue Takes. The app composes the H3 prompt, fills the reference slots, builds Atlases, and runs a render queue that it owns against the user's own ComfyUI. Takes come back to Dailies, where the filmmaker screens, circles, notes and promotes them. The Animatic plays the Scene's Cut, and circled Takes replace Boards until it is a rough cut. An optional Performance locks dialogue timing. Export writes an Edit package: binned `12A-3.mov` clips, FCPXML 1.10 and `.otio` timelines, and a Resolve metadata CSV with circled status and notes. Every Take keeps full provenance, so it can be explained, judged stale, and reproduced. ## User Stories ### Project and environment 1. As a filmmaker, I want each film to be a single `.studio` file with a sibling media folder, so that I can move, back up and double-click a Project like a document. 2. As a filmmaker, I want the app to refuse to open a Project that another running instance has open, so that two writers never corrupt one file. 3. As a filmmaker, I want the app to back up a Project file before it migrates it to a newer schema, so that an upgrade can never lose my work. 4. As a filmmaker, I want the app to refuse a Project made by a newer version of the app, so that an old build never damages a file it doesn't understand. 5. As a filmmaker, I want the Project file to be a single file at rest (no stray journal files after a clean close), so that copying it is safe. 6. As a filmmaker, I want app-wide defaults (Lighting list, slot order, Notation set, Generation profile) copied into each new Project, so that later changes to my defaults never silently change an existing film. 7. As a filmmaker, I want to set the Project's aspect ratio from a fixed list and change it later, so that I can decide on format without starting over. 8. As a filmmaker, I want the app to connect to my own ComfyUI and run a preflight check of nodes, models and version, so that I know what is missing before I queue anything. 9. As a filmmaker, I want preflight to report missing node packs and model files by name and download URL, without installing anything, so that I stay in control of my ComfyUI install. 10. As a filmmaker, I want each shipped workflow shown as Ready or Not ready with reasons in a ComfyUI status panel, so that I know which kinds of work I can queue. 11. As a filmmaker, I want app-level undo for my authored edits (merges, Shot and Setup edits, circling, Board saves, Cut overrides, marks), so that I can experiment safely. 12. As a filmmaker, I want locking, queuing, rendering and importing to be append-only and not undoable, so that provenance can never be rewritten by accident. 13. As a filmmaker, I want to open several windows on one Project that all stay in sync, so that I can keep Dailies and the Animatic open side by side. ### Script import 14. As a filmmaker, I want to import my OSF 4.x script (custom `urn:drafter:1` dialect), so that Scenes, Lines, Characters and Locations are created for me. 15. As a filmmaker, I want each action paragraph and each whole dialogue block to become one Line, so that Shots and Performances point at units that match how I write. 16. As a filmmaker, I want transitions and shot directions kept as read-only Lines that never count toward Characters, Shots or Performances, so that the script reads as written without polluting production. 17. As a filmmaker, I want Scenes to keep their `d:id` and Lines to get stable opaque IDs, so that my production data survives for the life of the Project. 18. As a filmmaker, I want missing or duplicate `d:id`s to get new IDs with a warning, so that a messy script still imports. 19. As a filmmaker, I want locked scene numbers (such as `12A`) used as written, and other Scenes numbered in script order and frozen at import, so that Take names never change. 20. As a filmmaker, I want omitted Scenes kept as placeholders with their number and hidden from production views, so that numbering stays honest. 21. As a filmmaker, I want Characters created from dialogue cues (normalized, with extensions removed), so that the speaking cast appears without manual entry. 22. As a filmmaker, I want cue extensions (V.O., O.S./O.C., SUBTITLE, other) stored on the Line and CONT'D dropped, so that the prompt can describe off-screen and voice-over delivery. 23. As a filmmaker, I want SUBTITLE Lines to have hand-entered spoken language and spoken text, and to be flagged while those are empty, so that H3 speaks the right language. 24. As a filmmaker, I want dual dialogue imported as two Lines with a shared simultaneous marker, so that overlapping speech is timed and prompted as one unit. 25. As a filmmaker, I want each scene heading split into a Location (INT and EXT kept separate) and a time slug, so that Locations match real places. 26. As a filmmaker, I want Locations with the same base name grouped into a Location folder automatically, so that a house's rooms sit together without affecting anything else. 27. As a filmmaker, I want to create, rename and delete Location folders and move Locations between them, so that I can organize Locations my way. 28. As a filmmaker, I want each Scene's Lighting filled in from a per-Project slug→Lighting table, with relative slugs inheriting from the previous Scene, so that day and night references are picked correctly. 29. As a filmmaker, I want Scenes with unknown slugs flagged with empty Lighting, so that I fix them rather than get a wrong guess. 30. As a filmmaker, I want edits to the slug table to leave already-imported Scenes alone, so that my fixes are never overwritten. 31. As a filmmaker, I want page numbers kept as text per Line (so `2A` works) and Scene page ranges derived from them, so that I can still find things by page. 32. As a filmmaker, I want the title page to fill the Project's title and author, so that the export carries them. 33. As a filmmaker, I want notes and synopses kept as read-only text, so that my writer's notes travel with the script. 34. As a filmmaker, I want import to be lenient and end with a summary of counts, warnings and flagged Scenes and Lines, so that I fix problems afterwards in the normal editors. 35. As a filmmaker, I want OSF import to run with progress and not freeze the UI, so that long scripts don't feel broken. ### Entities 36. As a filmmaker, I want to create Characters, Locations and Props by hand as equals of parsed ones, so that the script never limits who and what I can reference. 37. As a filmmaker, I want to create a non-speaking Character or a Prop from a text selection in the script, so that tagging is quick. 38. As a filmmaker, I want to merge near-duplicate Characters and Locations, with Lines and mentions moved to the survivor and the old name kept as an alias, so that parsing mistakes are cheap to fix. 39. As a filmmaker, I want each Character to hold default identity fields (age and build, face, hair, eyes, distinctive features), so that every prompt describes them the same way. 40. As a filmmaker, I want each Look to inherit the Character's identity and override only what differs, plus default-wardrobe text, so that variants stay consistent. 41. As a filmmaker, I want Wardrobe items with their own wardrobe text and mannequin references, so that I can change an outfit without a new turnaround. 42. As a filmmaker, I want each Location to hold a description and an Ambience, so that environment and sound bed are described once. 43. As a filmmaker, I want one Reference set per Location per Lighting, each with a lighting note that defaults from a per-Project phrase per Lighting, so that day and night versions of a place stay separate. 44. As a filmmaker, I want each Prop to hold a description and its own Reference set, so that key objects look the same in every Shot. 45. As a filmmaker, I want a Voice reference set per Character, so that their voice timbre is consistent. 46. As a filmmaker, I want to set per Scene which Look and Wardrobe item each Character wears, with speaking Characters added automatically on their default Look, so that continuity is explicit. ### References 47. As a filmmaker, I want to import a generated turnaround sheet as a Source sheet that belongs to the Project, so that one sheet can feed several Reference sets. 48. As a filmmaker, I want to crop a sheet into a turnaround's four Tiles on one stage, with boxes auto-fitted to detected figures in reading order, so that cropping takes seconds. 49. As a filmmaker, I want 1×4 and 2×2 presets, shrink-to-content, snapping to detected figure edges and a padding control, so that crops are clean without fiddling. 50. As a filmmaker, I want free-form crop boxes with a live 4:9 Atlas cell preview, so that I can judge exactly what H3 will see. 51. As a filmmaker, I want a side panel that compares each new crop with the Tile's current version, and lets me skip Tiles, so that I only make the versions I mean to. 52. As a filmmaker, I want a new Reference version only when a crop differs from the Tile's current one, so that version history isn't cluttered. 53. As a filmmaker, I want each Tile card to show its version strip and the provenance of each version (Source sheet with crop box highlighted), so that I know where every image came from. 54. As a filmmaker, I want to re-crop a version from its sheet, or crop from any other sheet, so that I can fix a Tile without importing again. 55. As a filmmaker, I want to generate a Tile with the shipped text-to-image workflow at the right shape (4:9 for turnaround and mannequin Tiles, Project ratio for location angles, 1:1 for Props), so that I don't need outside tools for every reference. 56. As a filmmaker, I want imported and generated versions to be the same kind of object, so that locking and staleness treat them alike. 57. As a filmmaker, I want to lock a Reference set as a numbered, immutable Lock snapshot that picks one version per Tile, so that only approved images feed production. 58. As a filmmaker, I want to roll back by locking a new snapshot that copies an older pick, and locking an identical pick to do nothing, so that snapshot numbers always mean something. 59. As a filmmaker, I want Tiles that a snapshot uses to be retired rather than deleted, so that history stays readable. 60. As a filmmaker, I want location and Prop sets to have open, user-labelled Tile lists, so that I can give a place as many angles as it needs. 61. As a filmmaker, I want an empty turnaround Tile to change the Look's Parity, so that Atlases never mix views. ### Atlases 62. As a filmmaker, I want the app to build Atlases from Lock snapshots automatically (one row per Look, face → front → side → back, uniform 4:9 cells, mid-grey padding, wider row gaps, no burned-in text), so that up to three Looks share one image slot. 63. As a filmmaker, I want Atlases scaled to a megapixel budget (1.0 MP by default, never upscaled) or at native size, set per Project and overridable per Setup, so that close-up-heavy Setups can get more detail. 64. As a filmmaker, I want Looks grouped into Atlases in subject order by default, and the option to regroup per Setup (within Parity), so that I can split Looks whose clothes bleed into each other. 65. As a filmmaker, I want Setups that need the same Looks to share one Atlas, so that nothing is built twice. 66. As a filmmaker, I want Atlas pixels to be byte-stable for the same inputs, so that a rebuilt Atlas never causes false staleness. ### Setups, Shots and the Board 67. As a filmmaker, I want to add Setups to a Scene, lettered A, B, C and never reused, so that names follow set convention. 68. As a filmmaker, I want to reorder a Scene's Setups separately from their letters, so that the master can come first in the Cut. 69. As a filmmaker, I want each Setup to have a hand-picked Location angle, a framing note and the list of subjects in frame, so that coverage matches across cuts. 70. As a filmmaker, I want a blocking-plan placeholder on the Setup, so that the UI has a place for the later blocking tool. 71. As a filmmaker, I want each Shot to cover a contiguous run of whole Lines in its Scene, so that coverage maps to the script exactly. 72. As a filmmaker, I want several Shots (from different Setups) to cover the same Line, so that I can shoot proper coverage. 73. As a filmmaker, I want a Shot's length derived from its Lines plus head and tail handles, with an override clamped to 2–15 s, so that Takes are long enough to cut. 74. As a filmmaker, I want queuing blocked past 15 s with an offered split at a Line boundary into numbered Shots (`12B·1`, `12B·2`), so that I never send H3 a length it can't make. 75. As a filmmaker, I want a single Line or dual-dialogue group longer than 15 s to block queuing with a clear message, so that I know to rewrite or wait for Take extension. 76. As a filmmaker, I want existing Takes to stay on the first part when a Shot is split, so that nothing is lost. 77. As a filmmaker, I want a per-Shot aspect ratio override, so that an insert can use a different format. 78. As a filmmaker, I want to draw a Board for each Shot in a Panel-strip editor over a Scene-wide Board timeline, so that I can storyboard a whole Scene in one place. 79. As a filmmaker, I want Sketch tools (brush, eraser, fill, line, rectangle, ellipse, filled toggle, size, three tones) on a fixed low-res canvas (short side 180 px at the Shot's ratio), so that sketching stays fast and rough. 80. As a filmmaker, I want a Sketch/Annotate switch (Tab) that swaps the toolbar, so that the Annotation layer stays separate from the drawing. 81. As a filmmaker, I want to add, duplicate, reorder and delete Panels, and ghost the previous Panel (also across Shots), so that I can show how a Shot progresses. 82. As a filmmaker, I want structured Annotations (Notation symbols with a subject and a direction) and a read-back list under the stage ("MARK moves right", "the camera pushes in"), so that I know exactly what H3 will be told. 83. As a filmmaker, I want freehand arrows flagged as meaningless until converted to a symbol, so that I don't think a scribble is instructing H3. 84. As a filmmaker, I want a project-wide Notation set built from a fixed glyph library, with editable name, colour, category, key and meaning, so that conventions stay consistent. 85. As a filmmaker, I want Notation entries in use to be retired rather than deleted, so that old Boards keep their meaning. 86. As a filmmaker, I want "Describe…" on a Panel to fill its Sketch layer with a rough board image from the board text-to-image workflow, with opt-in "Reduce to 3 tones", so that I can board without drawing. 87. As a filmmaker, I want Regenerate to reuse the Panel's stored prompt, replace only the Sketch layer, and be undoable, so that my annotations survive. 88. As a filmmaker, I want multi-Panel Boards sent tiled into one image slot by default, with a per-Shot option for one slot per Panel, so that I can trade slots for clarity. 89. As a filmmaker, I want a per-Shot toggle to send the clean sketch without the Annotation layer, so that I can work around H3 drawing marks literally. 90. As a filmmaker, I want a Panel drawn at an old aspect ratio barred at send time and shown with bars until I draw on it again, so that a format change never sends a distorted Board. 91. As a filmmaker, I want to add Keyframes to a Shot (start, end or a given time), imported, generated at the Shot's final size, or taken from a frame of another Take, so that I can pin exactly how a Shot opens or closes. 92. As a filmmaker, I want "Use first / last frame as Keyframe for…" in Dailies, so that I can continue across a cut. ### Prompt composer 93. As a filmmaker, I want the app to compose H3's six-field prompt (subject definitions, summary, retention analysis, detailed description, soundscape, music) automatically, so that I never write boilerplate. 94. As a filmmaker, I want Characters referred to as `<Subject N>` and Props as `<Prop N>`, built from structured fields and never named, so that names don't bias the model. 95. As a filmmaker, I want positional Atlas phrasing ("top row of `<Picture 2>`") with the views listed per Parity, so that H3 maps subjects to rows correctly. 96. As a filmmaker, I want Preservation written for me (full for a default-wardrobe Look, partial with an attribute transfer for a Wardrobe item), so that outfit changes just work. 97. As a filmmaker, I want slot tags numbered after the budget is filled, so that a dropped reference is never named in the prompt. 98. As a filmmaker, I want the Board described as a weak reference and never named in the description, while its Notation read-backs become plain directions, so that H3 takes staging from it but not style. 99. As a filmmaker, I want each Shot to have editable action text pre-filled from its action Lines, with @-mentions for Characters and Props, so that I can describe the beat in my own words. 100. As a filmmaker, I want dialogue Lines shown as chips in the action text in script order, expanding to quoted speech with language and inline parenthetical tags, so that dialogue is exact and can't be reordered by accident. 101. As a filmmaker, I want parentheticals that name a Character moved into an action sentence and flagged for rewording, so that the quote stays clean. 102. As a filmmaker, I want off-screen and voice-over Lines phrased as such, and heard-only speakers defined as heard but not seen, so that H3 doesn't put them in frame. 103. As a filmmaker, I want the soundscape built from the Location's Ambience plus an optional Scene Ambience addition, so that the sound bed matches across cuts. 104. As a filmmaker, I want an editable Shot summary pre-filled from the action text, so that the prompt opens with my framing of the beat. 105. As a filmmaker, I want a read-only preview of the composed prompt that I can fork into a hand-edited override, so that I can tune prompts that matter. 106. As a filmmaker, I want an unobtrusive "composer changed" marker and an on-demand three-way reconcile, rather than prompts on every queue, so that my overrides are never overwritten and I'm never nagged. 107. As a filmmaker, I want queuing blocked when an override names a slot tag that no longer exists, so that I never send a broken prompt. ### Slot budget 108. As a filmmaker, I want the 9 image and 3 audio slots filled automatically in a per-Project category order (Keyframes → Atlases → Wardrobe mannequins → location angle → Board → Props), so that the right references go first. 109. As a filmmaker, I want to reorder that category order by drag-and-drop in Project settings (seeded from an app-wide default), so that I can tune priorities per film. 110. As a filmmaker, I want each Shot's Slot list to show every candidate with a draggable sent / not-sent line, labelled excluded or over budget, so that I always see what was dropped and why. 111. As a filmmaker, I want an edited Slot list to keep my placements when candidates change, with new items badged and never pushing out my choices, so that my edits are respected. 112. As a filmmaker, I want "Reset to Project order" on a Shot, so that I can undo my tinkering. 113. As a filmmaker, I want a one-slot-per-Panel Board to fall back to tiled before anything is dropped, so that slots aren't wasted. 114. As a filmmaker, I want Voice references only for speaking Characters, one joined clip each, dropped when a Performance is present, so that audio slots go where they help. 115. As a filmmaker, I want each Prop packed into one slot from its Lock snapshot's Tiles, so that a Prop costs one slot. 116. As a filmmaker, I want an "over budget" badge on Shots wherever they are listed, for forced drops only, so that I notice lost references. ### Generation and the render queue 117. As a filmmaker, I want a Project default Generation profile (model and encoder files, quantization, LoRA stack with weights, Distilled/Full speed mode, draft/final short sides 768/1120) that each new Take copies, so that I set things up once. 118. As a filmmaker, I want to change the Generation profile for one Take in the queue dialog, picking files from what my ComfyUI has installed, so that I can try a different model or LoRA. 119. As a filmmaker, I want changing a profile never to make Takes stale, so that experimenting with models doesn't flood Dailies with warnings. 120. As a filmmaker, I want to queue a draft Take, or a Single-pass final for the best quality, so that I can choose speed or quality per Shot. 121. As a filmmaker, I want each Take named `12A-3` with numbers never reused, so that names are stable for the editor. 122. As a filmmaker, I want a queued Take to have its number, seed and recorded inputs as soon as it is queued, so that I can see if it's already stale before it renders. 123. As a filmmaker, I want "Rebuild with current inputs" on a waiting Take, keeping its number and seed, so that I can fix a stale queued job. 124. As a filmmaker, I want image jobs to run before drafts and drafts before finals, FIFO within each class, so that quick work is never stuck behind an overnight batch. 125. As a filmmaker, I want the worker to prefer jobs that use the model already loaded, within a class and with a skip limit, so that model swaps are rare and nothing waits forever. 126. As a filmmaker, I want to drag jobs within a class and use Run next to put a selection at the front, so that I can get urgent work done now. 127. As a filmmaker, I want Pause/Resume, per-job Hold and a sticky Hold finals switch, so that I can save finals for overnight. 128. As a filmmaker, I want to cancel a queued or running job, with a Take that has no Rendition deleted and its number burned, so that the queue never leaves junk. 129. As a filmmaker, I want at most one pending final job per Take, so that I never promote the same Take twice by accident. 130. As a filmmaker, I want the queue to reopen paused with a banner, and an in-flight job reattached, collected or requeued, so that a crash or restart never loses or duplicates work. 131. As a filmmaker, I want a warning when I close with a job running, and the job left running in ComfyUI if I confirm, so that closing the app doesn't waste GPU time. 132. As a filmmaker, I want ComfyUI errors to fail the job with no auto-retry, except one retry after freeing memory on OOM (a setting), so that bad jobs don't loop. 133. As a filmmaker, I want "ComfyUI offline" to be a waiting state, so that restarting ComfyUI never fails my queue. 134. As a filmmaker, I want jobs for a Not-ready workflow to wait instead of failing, so that installing a missing node fixes things without requeuing. 135. As a filmmaker, I want preparation (Atlas builds, uploads) overlapped with the current render and post-steps (proxies, posters) run in a CPU lane, so that the GPU never sits idle. 136. As a filmmaker, I want a dockable Queue panel and a status-bar indicator (running job and %, waiting count, Paused / Finals held / ComfyUI offline), so that I always know what the GPU is doing. 137. As a filmmaker, I want failed jobs pinned to the top of the Queue panel until I dismiss them or open the Take, so that failures are never missed. 138. As a filmmaker, I want status badges (Queued / Held / Rendering % / Finishing / Failed) on Take tiles and Shot listings, so that I can see progress where I work. 139. As a filmmaker, I want live per-job progress, so that I can tell a slow job from a stuck one. 140. As a filmmaker, I want draft Renditions saved with their split latent, H.264 MP4 and lossless FLAC, so that Promotion can reuse them later. 141. As a filmmaker, I want final Renditions as a ProRes 422 HQ master with `01:00:00:00` timecode and PCM audio, plus an H.264 proxy for in-app playback, so that the editor gets real masters and I can still play them. 142. As a filmmaker, I want a "proxy missing" marker and a retry action when a post-step fails, keeping the GPU output, so that a small ffmpeg failure never wastes a render. 143. As a filmmaker, I want lengths rounded up to H3's frame grid with the extra frames as tail handle, so that Shots are never cut short. ### Dailies 144. As a filmmaker, I want one Dailies screen per Scene, so that I review coverage the way it will be cut. 145. As a filmmaker, I want a coverage wall with a row of Take tiles per Shot, grouped by Setup, with the Shot's Board, a stale count and a "no circled Take" warning, so that I can see gaps at a glance. 146. As a filmmaker, I want to tick up to four Takes into a synced side-by-side compare tray, so that I can choose between performances. 147. As a filmmaker, I want a "+ Take" tile at the end of each Shot row, so that I can roll more coverage in place. 148. As a filmmaker, I want a sortable, filterable table of every Take (unseen, circled, stale, draft-only, NG) with batch Promote and batch new seed, so that I can manage many Takes at once. 149. As a filmmaker, I want a keyboard screening mode for unseen Takes (C circle, N note at playhead, P promote, R new seed, J/K next/prev, hold B to flip to the circled Take), so that I can get through dailies fast. 150. As a filmmaker, I want a details panel with an A/B wipe against another Take and a table of the Take's inputs against the current ones, including the sent slot assignment, so that I can see exactly what changed. 151. As a filmmaker, I want to circle several Takes per Shot and pin one for the Animatic, with the most recently circled used when none is pinned, so that I can keep options without losing the cut. 152. As a filmmaker, I want a Take marked seen once it has played through in screening or been opened in details, with "mark all seen" per Shot and Scene, so that unseen means unseen. 153. As a filmmaker, I want a list of notes per Take, each with an optional timecode, so that I can note a moment precisely. 154. As a filmmaker, I want to promote a Take (including a stale one, with a warning naming the stale inputs), so that I can finish what I like. 155. As a filmmaker, I want Promotion to keep the draft's audio exactly and rebuild from the draft's recorded inputs, so that a promoted final matches the draft I circled. 156. As a filmmaker, I want to re-promote a promoted Take with a new refine seed, replacing its final in place only on success, with a "re-promoting" badge meanwhile, so that I can improve a final without a new Take number. 157. As a filmmaker, I want a newly rendered final to mark the Take unseen while keeping circles, pin, notes and Line marks, so that I re-check finals. 158. As a filmmaker, I want "same take, new seed" to roll a new Take with current inputs by default (and original inputs as an option on stale Takes), recording which Take it came from, so that I can explore variations. 159. As a filmmaker, I want failed Takes to offer Retry (same Take, same seed) and Delete, so that failures are easy to clear. 160. As a filmmaker, I want to mark a Take NG, hidden from the wall and the reel but kept in the table under an NG filter, so that I clean up review without losing provenance. 161. As a filmmaker, I want circled Takes not to be markable NG, so that my two judgments never conflict. 162. As a filmmaker, I want to nudge a Take's Line marks in the details panel, so that the Animatic cuts on the real performance. ### Staleness 163. As a filmmaker, I want a Take shown as stale whenever a re-roll with current inputs would send a different job (ignoring seed and profile), so that I know which clips no longer match my references. 164. As a filmmaker, I want each difference listed as a named Stale reason grouped by kind (References, Board/Keyframes, Performance, Prompt, Framing, Workflow), so that I can judge whether it matters. 165. As a filmmaker, I want to accept a Stale reason on one Take until that input changes again, and "Accept all", so that harmless changes stop nagging me without changing provenance. 166. As a filmmaker, I want workflow updates shown only as information, so that an app update doesn't mark my whole film stale. 167. As a filmmaker, I want stale badges and a sortable Stale column on every Take in Dailies, but only a "N circled stale" count in Shot and Scene listings and a corner mark in the Animatic, so that staleness is visible where it matters without noise. 168. As a filmmaker, I want "Re-roll stale circled Takes" per Scene and per Reference set, with a confirm sheet showing counts, estimated time and a draft/final toggle, so that I can refresh coverage after a reference change in one action. 169. As a filmmaker, I want batch re-roll to skip Shots that already have a current queued Take and never change circles, so that it is safe to run twice. ### Animatic and Cut 170. As a filmmaker, I want an Animatic per Scene that plays the Cut, with each Line from exactly one covering Shot and no dialogue repeated, so that I can watch the Scene before rendering everything. 171. As a filmmaker, I want each Line assigned by default to the first covering Setup in Setup order, and to override individual Lines in a Line lane, so that I can make cut decisions quickly. 172. As a filmmaker, I want overrides whose Shot no longer covers the Line to fall back and be flagged "reassigned", so that the Cut never points at nothing. 173. As a filmmaker, I want Line durations taken from the Performance, else a manual override, else a text estimate from per-Project rates, so that the Animatic is timed sensibly from day one. 174. As a filmmaker, I want circled Takes to replace Boards and play between their Line marks with their own audio, so that the Animatic becomes a rough cut. 175. As a filmmaker, I want Board events to play the Performance slice, or silence with dialogue captions, and Panels spread across the Shot's span, so that pacing reads even before any Take exists. 176. As a filmmaker, I want uncovered Lines shown as a grey "no coverage" slate with captions, so that gaps are obvious. 177. As a filmmaker, I want transitions and shot directions shown as zero-length markers, so that the script's intent is visible on the timeline. 178. As a filmmaker, I want "play from here" to run on through later Scenes, skipping omitted ones, so that I can watch the film continuously. 179. As a filmmaker, I want stale playing Take events marked in a corner, so that I know what to re-roll. 180. As a filmmaker, I want other-ratio Shots letterboxed into the Project ratio, so that the Animatic stays one format. ### Performance 181. As a filmmaker, I want to Bounce the Scene's Cut audio into a new Performance version, so that a set of circled Takes' dialogue becomes the locked timing for every Setup. 182. As a filmmaker, I want Lines with no circled Take to carry forward the previous Performance's audio, or become silent holes listed in a warning, so that a Bounce never invents dialogue. 183. As a filmmaker, I want to import Performance audio for a contiguous run of Lines, with marks auto-placed by silence detection and then corrected by hand, so that I can use recorded dialogue. 184. As a filmmaker, I want marks to be a contiguous partition (pauses to the earlier Line, action Lines splitting gaps, dual dialogue as one span), so that every moment of audio belongs to a Line. 185. As a filmmaker, I want a Performance lane on the Animatic with the waveform, draggable boundaries, warning badges, uncovered-Line shading and a ghost lane of the previous version, so that I can mark Lines visually. 186. As a filmmaker, I want mark edits to be undoable and to make stale only Shots whose slice moved, so that small fixes don't invalidate everything. 187. As a filmmaker, I want the Performance audio immutable and new audio to be a new version, so that a Take can always be explained. 188. As a filmmaker, I want each Shot over covered Lines sent its Performance slice as both forced audio and a `fully_copy` audio slot, so that every Setup matches the locked timing. 189. As a filmmaker, I want a Take's Line marks snapshotted from the Performance when queued, so that later mark edits never move a Take's cut points. 190. As a filmmaker, I want a Shot that straddles covered and uncovered Lines to block queuing with "split the Shot or extend the Performance", so that a Take never gets half a Performance. 191. As a filmmaker, I want a per-Scene switch to turn the Performance off without losing marks, so that I can let H3 generate dialogue again. ### Edit package 192. As a filmmaker, I want to export an Edit package folder for the Project, picking Scenes and an optional Circled only filter, with the folder remembered, so that handing off is one action. 193. As an editor, I want per-Scene bins with per-Setup sub-bins of `12A-3.mov` clips (segmented Shots `12B_1-3.mov`), so that the Media Pool reads like real coverage. 194. As an editor, I want every clip to be a `.mov` starting at `01:00:00:00` (the ProRes master if final, otherwise the draft remuxed with PCM audio), so that clips behave consistently. 195. As a filmmaker, I want finals hard-linked into the package when it's on the same volume, so that exporting doesn't double my disk use. 196. As an editor, I want per-Scene and whole-film timelines as FCPXML 1.10 (one event, one project, no bins or markers) and `.otio` (with markers), so that I can start from the Animatic in Resolve. 197. As an editor, I want Take events to reference whole clips with in and out points at the Line marks, so that handles are available. 198. As an editor, I want Board and slate events as PNG stills at final size with burned-in captions, a sidecar `.srt`, and per-Scene Performance audio, so that the Animatic is complete in Resolve. 199. As an editor, I want a UTF-8 `takes.csv` for Resolve's metadata import (matched by file name and start/end TC) that sets Scene, Shot, Take, `Tag=1` for circled, the framing note, keywords (draft/final/stale), notes as Comments, and Seed/Profile/Pinned columns, so that circled status and notes survive into Resolve. 200. As a filmmaker, I want a `manifest.json` with each Take's full recorded inputs and the packaged Rendition's id and hash, so that any clip can be explained and regenerated later. 201. As a filmmaker, I want re-export to sync in place (skip unchanged, overwrite changed Renditions, rewrite timelines and metadata, move dropped clips to `_Removed/<date>/`), using temp files and renames, so that the editor's links keep working and nothing is deleted. 202. As a filmmaker, I want a warning before switching Circled only on, so that I know clips will move to `_Removed/`. 203. As a filmmaker, I want staleness never to block export, so that I can always hand off. 204. As a filmmaker, I want export to run with progress, so that large packages don't look frozen. ### Storage 205. As a filmmaker, I want a project-wide Storage view with totals for the project file (and its free space), the media folder and the Edit package, so that I know where my disk went. 206. As a filmmaker, I want one sortable table of every item that uses space, with what uses it, its real reclaimable size (by hard-link count) and badges, so that I free space safely. 207. As a filmmaker, I want to Purge Rendition files and latents (keeping the row and its poster frame), blocked for circled Takes and for latents with a pending job, so that I can reclaim space without losing provenance. 208. As a filmmaker, I want each Purge to show the exact bytes freed and what stops working, and to be irreversible only after that confirmation, so that I never lose work by surprise. 209. As a filmmaker, I want purged Takes to stay in Dailies as purged tiles with "Re-roll same seed", so that I can bring them back. 210. As a filmmaker, I want to delete unused Source sheets from Storage, with a "Used by" list and jump links when they're in use, so that I can clean imports. 211. As a filmmaker, I want suggestion filters (NG Renditions, latents and drafts of finalled Takes, unused sheets, orphan files, `_Removed/`, Compact) that only select rows, so that nothing runs without me. 212. As a filmmaker, I want "Empty _Removed" with a warning about Resolve links, so that only I delete things from the package. 213. As a filmmaker, I want a manual Compact with the queue paused, so that I can shrink the project file. 214. As a filmmaker, I want files that disappeared outside the app shown as missing and treated like purged, so that the app never crashes on a missing file. 215. As a filmmaker, I want Dailies shortcuts (free space on NG Takes, delete failed Takes) to use the same rules as Storage, so that behaviour is consistent. ## Implementation Decisions ### Architecture (ADR-0003, #3) - A Cargo workspace with a thick **`studio-core`** crate (no Tauri, no specta). It owns the domain model, SQLite, OSF import, Lock snapshot rules, staleness, Atlas composition, the prompt composer, the slot budget, the render queue and worker, the ComfyUI client, ffmpeg calls and export. **`src-tauri`** is a thin adapter. - Only Rust opens the `.studio` file. The frontend changes state only through named domain commands (`taurpc`). DTOs are defined in the adapter and mapped from the core's view-shaped read models. Commands return a typed error enum. - Coarse `changed { kind, ids }` invalidation events are broadcast to all windows, and views refetch. `tauri::ipc::Channel` carries per-job progress and progress for long operations (OSF import, export). - One `ProjectSession` per process holds the connection, the undo log and the worker. It may be shown in several windows. - Undo is an in-memory Rust command log, per Project, that covers authored edits only. Locking, queuing, rendering, generating and importing are not undoable. Cancelling a job is an explicit queue command. - One `studio:` custom scheme (async) serves `blob/<id>` from SQLite and `rendition/<id>` with Range/206. URLs carry ids, not paths, and the webview has no file scope. - ffmpeg is an unmodified LGPL sidecar that only Rust calls. It makes posters, thumbnails, audio extraction and decoding, remuxing, proxies and timecode stamping. It never encodes Renditions except the H.264 proxy. - Rust produces every stored or derived image deterministically. Boards are flattened from the stored sketch and annotation layers, Atlases and packed Prop images are built with the `image` crate, and Tile crops are cut from the original sheet. The UI only draws. - Svelte 5 holds view state and interaction, including the Board paint canvas and its local stroke undo until save. ### Project file (ADR-0001, ADR-0004, ADR-0005, #11) - `.studio` is a SQLite database. Renditions and latents live in a sibling `<name>.media/` folder under ID-based names (`renditions/<rendition-id>.*`, `latents/<rendition-id>.{video,audio}.latent`) with relative paths. - ULID primary keys everywhere. Human labels (frozen Scene number, Setup letter, Take number) are plain columns. Scenes keep `d:id` as a unique column. - One content-addressed `blob` table (BLAKE3, media type, size, pixel dimensions). Unused blobs are swept on open and on demand, with no reference counts. - Lifecycle: `PRAGMA user_version` migrations embedded in the core, applied in one transaction after copying to `<name>.studio.bak-v<N>`. A newer file is refused. A fixed `application_id`. WAL while open, then checkpoint and `journal_mode=DELETE` on clean close. A `<name>.studio.lock` file holds PID and host. - Settings live on a singleton `project` row. Lists get their own tables (`lighting`, `slug_lighting`, `reference_category_order`, `notation_entry`). App-wide defaults are a JSON file in the app config directory, copied into a Project at creation and never read again. - Deletion follows ADR-0004. Rows that any Take consumed get `deleted_at`. Unconsumed rows are hard-deleted. Failed Takes may be hard-deleted, and numbers are never reused. Merged Characters and Locations become tombstones with `merged_into`. - Separate `character`, `location` and `prop` tables. Each Reference set owner holds a unique FK (`look`, `wardrobe_item`, `prop`, `character.voice_set_id`, and `location_lighting(location, lighting, reference_set, lighting_note)`). `reference_set.kind` is one of turnaround, mannequin, location, prop or voice. Tiles have role and ordinal (fixed roles for turnaround and mannequin) and are retired, never deleted, once any snapshot includes them. - Reference versions are append-only, each with its own blob and, for imported versions, the Source sheet and crop box. A generated version exists as a row with no pixels from the moment it is queued. - `lock_snapshot(set, number)` + `snapshot_tile` is immutable, and the current snapshot is the highest number. - `atlas(input_key, blob, built_at)` is a cache (ADR-0005). The key hashes the ordered snapshot IDs, the layout version and the size settings. Setups store only their grouping. - Lines have an immutable position, and the script is frozen after import. Shots store `first_line_id`/`last_line_id`. `setup(scene, letter, setup_order)`. `shot(setup, index, next_take_number)`. `scene_cast(scene, character, look, wardrobe_item?)` and `setup_subject(setup, character, position)`. - Shot action text is a JSON node document (`text`, `mention{kind,id}`, `dialogue{line_id}`) plus a derived `shot_mention` table rewritten on save. A merge rewrites both in one transaction. The prompt override is plain text plus its fork-base composed text. - Slot list: `shot_slot_item(shot, position, candidate_key, state, is_new)` + `shot.slot_line`, with logical keys (`keyframe:<id>`, `atlas:<group>`, `wardrobe:<id>`, `location_angle:<tile>`, `board`, `prop:<id>`). No rows means the Shot follows the Project order live. - `panel(board, position, canvas_w, canvas_h, sketch_blob, annotations JSON)` with normalized annotation positions. Boards are edited in place with no history. - `keyframe(shot, anchor, at_seconds?, image_blob, origin)`. A take_frame Keyframe records its source Take, Rendition and frame time. - `performance(scene, number, audio_blob, created_at)` is immutable. `performance_source` is an open edge list of source kinds (Takes, carried-forward Performance, import). `performance_mark(performance, line, start)` plus an end, a coverage flag per Line per version, and `scene.performance_off`. - Take provenance: `take_input` has one row per consumed thing, with a real FK and the slot number it filled. Shot and Setup text fields are recorded by value or hash. The exact job graph and prompt text are stored as blobs, plus an overridden flag. `take_link(take, other_take, kind)` with kinds `rerolled_from` and `frame_from` (and `extends` reserved for Take extension). The Take also records its Performance slice and snapshotted marks, seen, NG, notes with optional timecodes, circled-at, accepted reasons, and the shipped workflow id/version/hash. - `shot.pinned_take` must be circled and is cleared on uncircle. - `rendition(take, kind, via, state, path?, purged_at, size, hash, poster_blob, refine_seed?)`, created when queued. Latents have their own purged marker. Missing is computed when Storage opens. - `job` has one row per ComfyUI submission: kind, exactly one target FK (Rendition, Reference version or Panel), state, `queue_position`, attempt, our own `prompt_id`, held flag, error and timestamps. The Hold finals switch is persisted on the Project. - The aspect ratio is on the Project (fixed list) with a per-Shot override. A Shot's effective ratio feeds staleness. Panels and Keyframes keep their pixels and are barred or flagged at send time. - The full DDL is written by the build issue that creates each table. ### OSF import (#2) - Accepts OSF 4.x only, resolving custom styles through `basestyle`. Reads only `scene_time_separator`. Ignores `<lists>`, `<styles>`, revision colours and empty paragraphs. Strips inline formatting but keeps soft breaks. - The Line rules, ID rules, Character normalization, cue-extension enum, SUBTITLE fields, heading split, auto Location folders, slug→Lighting seed table, dual-dialogue simultaneous group, frozen numbering, omitted placeholders, text page numbers, title/author, and read-only notes are as stated in User Stories 14–34. - Import writes straight into the Project and returns a summary of counts, warnings and flagged items. There is no staged review. ### References and Atlases (#4, #5, #10) - Crop sessions target one Reference set. Auto-fit maps the four largest detected figures in reading order to face → front → side → back. Other figures become faint snap targets. Fewer than four leaves Tiles unboxed. Commit makes versions only for changed crops and never locks. - Atlas layout v1: rows per Look (≤ 3) in Setup subject order (or the Setup's regroup), columns face → front → side → back, uniform 4:9 cells, fit-and-pad on neutral mid-grey, wider row gutters, no text. Megapixel budget (1.0 MP default, never upscaled) or native, per Project with a per-Setup override. Missing Tiles collapse their cell, and only same-Parity Looks combine. All of this is part of the content identity. - Packed Prop image: the Prop snapshot's Tiles side by side in uniform cells on mid-grey, identified by content. Joined Voice reference: a Character's snapshot clips concatenated with short gaps. ### Prompt composer (#9) - A pure function from a Shot's current inputs plus the filled slot assignment to H3's six-field text, always in this order and with these names: `subject_definitions`, `summary`, `retention_analysis`, `detailed_description`, `overall_soundscape`, `non_diegetic_music` (always `N/A`). Exactly one `[Shot 1]`, with no `id=` header. - `<Subject N>` is numbered by first appearance in the Shot, and `<Prop N>` by attachment order. `<Environment>` is the Location. `<Picture N>`/`<Audio N>` are numbered in wired order after the budget is filled. No names or pronouns. - Retention entries, taken from #9: | Input | Entry | |---|---| | Look, default wardrobe | `fully_preserved - face, hair, eyes, physique and clothing.` | | Look + Wardrobe item | Atlas row `partially_preserved - … clothing is not inherited.`; mannequin `attribute_transfer - the <item> is worn by <Subject N>; the mannequin does not appear.` | | Location angle | `fully_preserved - architecture, layout, fixed objects and lighting; the camera angle may differ.` | | Prop | `fully_preserved - shape, material, wear and markings.` | | Board | `weak_reference` - rough storyboard of N panels, staging/composition/camera only, style and figures not reproduced, arrows are directions | | Keyframe | `fully_preserved - first frame at 00:00.000 …` / `last frame at <ms>` | | Voice reference | `reference - its vocal timbre guides the vocal delivery of <Subject N> …` | | Performance slice | `fully_copy - the dialogue of this shot …`. Voice references are dropped. | | Subject without picture | `newly_generated - no visual reference …` | - `detailed_description` = the framing note, then the Notation read-back sentences in panel order (never naming the storyboard), then the action text with dialogue chips expanded as `<Subject N> says: <d>[Language] <tag> text</d>`. O.S./O.C. becomes "says from off-screen:", V.O. becomes "says in voice-over:", and dual dialogue becomes "… speak at the same time." Character-naming parentheticals move out of the quote. - `overall_soundscape` = the Location's Ambience + the Scene's Ambience addition. - An override is forked from the preview and stores its fork base. A composer change shows only a quiet marker, and the three-way reconcile runs on demand. Slot tags in an override are validated, and a broken one blocks queuing. A Take records the exact text and an overridden flag. ### Slot budget (#10) - Binding limits are 9 image + 3 audio. No video slots in the slice. - A per-Project category order (seeded from the app default) resolves into a per-Shot Slot list. Overflow first collapses a per-Panel Board to tiled. Edited lists persist with the rules in User Stories 110–113. - Audio: a Performance slice (one slot, Voice references dropped), else Voice references of speaking Characters (dialogue chip present, O.S./V.O. count) ordered by first Line, one slot each. - No per-Scene or per-Take override. Forced drops never block queuing. ### Shipped workflows and ComfyUI (ADR-0002, #13, #14, #15, #17, #20) - Five workflows ship in the repo as API-format JSON templates plus manifests: `h3.draft`, `h3.promotion`, `h3.final`, `image.t2i` and `image.board` (both image workflows on Qwen-Image 2.1, and `image.board` with its own fixed rough-style prompt). The owner's SEEDHUNTER graph is a technique reference only. - A manifest names scalar bindings (`node_id.input`), output nodes, the node pack name/URL for each non-core class_type, and the folder/URL for each fixed model file. Rust makes at most three structural edits: one `LoadImage`/`LoadAudio` per filled slot wired into the `MiniMaxH3ReferenceToVideo` Autogrow ports in slot order; a `LoraLoaderModelOnly` chain at a named model point (the chain's position relative to the shipped LoRAs doesn't matter); and, for Performance-driven Takes only, the forced-audio branch (`VAEEncodeAudio` → 0 mask). The filled graph is the job blob. - Each workflow has a stable id and a hand-bumped integer version. CI fails if the template hash changes without a bump. Jobs record id, version and hash. Only versions marked as changing output raise the (informational) Workflow reason. Promotion always uses the current `h3.promotion`. Old versions are not kept. - Generation profile: the diffusion model file (FL2VA/Ref2VA, any quantization), the text encoder file, a user LoRA stack (applied to both passes), and a speed mode (Distilled = shipped DMAD 4-step LoRA + steps, Full = shipped higher steps). Short sides default to 768/1120, the long side follows the effective ratio, and Rust rounds both to ×32 (there is no ResolutionSelector in the graphs). Fixed in the workflow: VAEs, upscaler weights, sampler and schedulers, refine denoise, attention and low-VRAM settings. - Draft: first pass → `LTXVSeparateAVLatent` → 2× `SaveLatent`, plus an H.264/AAC MP4 at 24 fps and a lossless FLAC (core `SaveAudio`). Saved latent file names are read from history and copied into the media folder. - Promotion: 2× `LoadLatent "<file> [output]"` → `MinimaxH3LatentUpscaler3D` → `LTXVConcatAVLatent` → refine pass at final size, with conditioning rebuilt from the draft's **recorded** prompt, slot assignment and blobs. The draft's audio is **always** frozen with a 0 `SetLatentNoiseMask`. Each Promotion gets a new random refine seed (editable, and recorded on the final). - Single-pass final: one pass at final size, with no latent saved. - Finals: a ProRes 422 HQ `.mov` master written by the workflow. A CPU post-step then stamps `01:00:00:00` and PCM audio into it and makes an H.264 proxy at short side 768 (`h264_nvenc` if a startup check finds it, otherwise `libopenh264`). Every H3 job writes a lossless FLAC. - Performance: Rust cuts the Shot's slice (Lines plus handles), pads it to the frame count, and sends it as forced latent audio (`VAEEncodeAudio` → 0 mask) and as the `fully_copy` slot. - Length: seconds round up to 5 + 17k frames (≤ 362), and the job records both the requested seconds and the frame count. - Image sizes are set by the caller: turnaround and mannequin Tiles 4:9 at ~1 MP, location angles at the Project ratio ~1 MP, Prop Tiles 1:1 ~1 MP, Keyframes at the Shot's exact final W×H, Boards at ~1 MP at the Panel ratio, downscaled by Rust to the canvas. - ComfyUI client (Rust only, because loopback Origin checks block the webview): `WS /ws?clientId`, `POST /api/upload/image` for images and audio (use the returned name), `POST /api/prompt` with our own UUID `prompt_id`, where a 200 with non-empty `node_errors` is a failure. Progress follows the documented event order, and `executing{node:null}` means done. Results are fetched from `/api/history/{id}` (walk every output key) and `/api/view`, and persisted immediately because history is in memory only. Cancel uses `POST /api/jobs/{id}/cancel` (feature-detected), falling back to interrupt-with-id + queue delete. The ComfyUI queue is shared with the user's own tab. - Preflight runs on connect, on demand, and before each submission. It checks reachability and version ≥ 0.39.0 (`/system_stats`), every class_type (`/object_info`, both combo encodings), every fixed model file and combo value, and the job's profile files (`/api/models/*`). The result is Ready or Not ready with reasons. It never installs anything. Pickers read `/api/models/*`. ### Render worker (#18) - Jobs are built at queue time. Classes run images → drafts → finals, FIFO within a class. Model affinity applies within a class (checkpoint + quantization, ignoring LoRAs) with a skip limit of ~3 passes or a few minutes. Drag reorders within a class. Run next goes to the global front and is exempt from affinity. No pre-emption. - Pause/Resume, per-job Hold and the sticky Hold finals switch. Cancel works on queued and running jobs (interrupt, discarding partial output). Cancelling a Take that has no Rendition deletes it and burns its number. At most one pending or running final job per Take. - Always reopens paused, with a banner. The in-flight job is reattached by `prompt_id`, collected from history, or requeued at the head of its class as an interrupted attempt. Closing with a running job warns and then leaves it running. - ComfyUI errors fail with no retry. OOM retries once after `/free` (a setting). Offline is a waiting state with re-probing. Not-ready workflows' jobs wait. A failed draft or Single-pass final becomes a Failed Take. A failed Promotion leaves the draft plus a "Promotion failed" note with Retry. A post-step failure keeps the GPU output with a "proxy missing" marker and retry. - One ComfyUI submission lane. Preparation for the next job overlaps the current render, and a CPU post-step lane runs 1–2 at a time. A Take shows **Finishing** until its post-steps are done. Poster frames are extracted when a Rendition is collected. ### Staleness (#12, #23) - Computed, never stored: diff the Take's recorded job inputs (`take_input`, recorded text and hashes, slot assignment, Performance version and slice) against the job the Shot would build now, ignoring seed and Generation profile. Each difference is a Stale reason of kind References, Board/Keyframes, Performance (version, marks/slice), Prompt, Framing (including effective aspect ratio) or Workflow (informational only). - On a Shot with an override, Takes are compared with the override text. A composer change alone affects only the Shot marker. - An accepted reason stores the input's current value and lapses when the input changes. Accepting never changes provenance. - Queued Takes are judged like any other Take. Batch re-roll and its skip rule follow User Stories 168–169. ### Dailies (#7) - One screen per Scene, toggling between the coverage wall and the table, plus screening mode and the details panel as described in User Stories 144–162. The prototype on branch `prototype/dailies` is the visual reference. - Circling, pinning, notes, seen, NG and failed-Take rules are as stated in User Stories 151–161. ### Animatic and Performance (#8, #23) - The Cut is computed per Scene from Setup order with stored per-Line overrides (simultaneous groups as units). Events merge neighbouring Lines on the same Shot. Line durations come from the Performance, else an override, else an estimate (dialogue 2.5 words/s + 0.5 s per parenthetical, action 1.5 words/s with a 1 s minimum; rates per Project). - Default Take Line marks: snapshotted Performance marks if there is a Performance, otherwise the Rendition length minus handles spread in proportion to Line durations. Marks are nudgeable. An event plays the final if there is one, else the draft. - Shot length = Lines + head/tail handles rounded to the frame grid, with an override clamped to 2–15 s. The split helper splits at Line boundaries in the same Setup. - Bounce, import auto-placement (`silencedetect`), the mark partition rules, the coverage set, the straddle block and the ghost lane are as in User Stories 181–191. Re-run auto-placement is one undoable edit. ### Edit package (#16, #19, #21) - The folder layout, taken from #19: ``` <package>/ 012 MARKS HOUSE - KITCHEN/ per-Scene bin (zero-padded frozen number + ASCII-safe heading) 12A/ 12A-1.mov 12A-3.mov 12B/ 12B_1-2.mov 12B_2-1.mov Animatic/ Board/slate PNG stills, per-Scene Performance.wav Timelines/ <Scene>.fcpxml / .otio / .srt, Animatic.fcpxml / .otio (whole film) _Removed/<date>/ takes.csv manifest.json ``` - FCPXML 1.10 is hand-written with exactly one event holding one project, and no browser clips, bins or markers (otherwise Resolve fails with "No valid sequences"). Timeline: the Project ratio at final size, 24 fps, starting at `01:00:00:00`, with drafts scaled to fit. `.otio` carries timeline markers (transitions, shot directions) and clip markers (timecoded notes). - `takes.csv` headers: `File Name`, `Clip Directory`, `Start TC`, `End TC`, `Scene`, `Shot`, `Take`, `Tag` (`1` for circled, blank otherwise, never `2`), `Description`, `Keywords`, `Comments`, plus custom `Seed`, `Profile`, `Pinned`. UTF-8. Never use the `Good Take` column. - The sync algorithm is driven by the previous manifest, with temp-file writes and renames. Hard links are used on the same volume, otherwise copies. ### Storage (#22) - Purge applies to media-folder files only (Rendition files and latents), with the guards and confirmations described in User Stories 207–209. In-file items are deleted only from their own editors (ADR-0004), except Source sheets, which Storage can delete when unused. - Reclaimable bytes are counted per file from its hard-link count. Orphan files are found by a scan when Storage opens. Compact is a manual `VACUUM` with the queue paused. In-file deletes are undoable within the session, while Purge and Empty `_Removed` are not. ## Testing Decisions - **One primary seam: the public API of `studio-core`'s `ProjectSession`.** Tests drive the same named domain commands and view-shaped read models that the `taurpc` adapter wraps (import OSF, crop and lock, set up Shots, queue, collect, circle, promote, bounce, export, purge…). Each test opens a real `.studio` file in a temp directory. Tests never read tables or call private modules directly. - **What a good test looks like:** it states a user-visible behaviour, drives it through `ProjectSession`, and asserts only on what the API returns or on the artifacts the core writes. Examples: the composed prompt text for a Shot; the filled job graph sent to ComfyUI; an Atlas's bytes or hash (golden, because it must be byte-stable); the read model's Stale reasons; the Edit package tree, `takes.csv`, FCPXML and `.otio` contents. Tests don't assert on table layout or internal structs, so the schema can change freely under them. - **The only fake is ComfyUI.** A scriptable in-process HTTP + WebSocket server that speaks the real protocol: `/api/prompt` (recording each submitted graph and `prompt_id`), `/api/upload/image`, `/ws` event sequences, `/api/history`, `/api/view`, `/api/jobs/{id}/cancel`, `/api/interrupt`, `/api/object_info`, `/api/models/*`, `/system_stats` and `/free`. It returns canned fixtures (a short MP4, ProRes `.mov`, FLAC, latent files, PNG). It can be scripted to produce: a 400 validation error, a 200 with `node_errors`, `execution_error`, OOM, being offline, a job still running or finished when a Project reopens, history lost after a restart, and missing nodes or models for preflight. - **Everything else is real:** SQLite, the filesystem (including hard links into a temp Edit package), and the ffmpeg sidecar (posters, remux, timecode stamping, proxy via `libopenh264`, `silencedetect`). - **What gets tested through the seam:** OSF import (against the owner's sample script and small crafted edge cases), Reference versioning and Lock snapshots, Atlas identity and pixels, slot budget resolution, prompt composition, job building and the two splice points, queue ordering and lifecycle, crash and reopen recovery, staleness and accepted reasons, Dailies state rules (circle, pin, NG, seen, notes, re-roll, re-promotion), Cut and Line durations, Performance Bounce, import auto-placement and marks, Edit package export and re-sync, Storage purge guards and reclaimable bytes, undo of authored edits, and schema migration and backup. - **Outside the seam:** - A unit test over the shipped workflow templates checks that every manifest binding resolves against its template. A CI check fails when a template hash changes without a version bump. These guard content, not behaviour. - The `src-tauri` adapter gets only a smoke test (it is thin DTO mapping). - Svelte views (crop stage, Board paint tool, Dailies, Animatic lanes, Queue panel, Storage) are checked by hand against the prototype branches (`prototype/crop-sheet`, `prototype/board-paint`, `prototype/dailies`). - Real H3 output quality and real Resolve import are checked by hand with the owner's kits (`research/h3-latent-reuse`, local `scratch/resolve-test/`), not in CI. - **Prior art:** there are no tests in the repo yet. The closest prior art is the job-splitting script and job graphs on branch `research/h3-latent-reuse` (`research/h3-latent-reuse-run/`), which show the exact draft and Promotion graphs that the job builder's output should match, and the Resolve test kit's reference FCPXML, OTIO and CSV files, which are golden references for export. ## Out of Scope These are beyond the slice and come back only as a new effort (from #1): - Blender previz (playblast as a video reference). Video slots stay unused. - An MCP server and prompt templates. No LLM in the slice: Props come from text selection or manual creation. - Lined script export. - A blocking plan editor (the slice keeps only a UI placeholder), derived framing, and Board underlays. - A Board cleanup pass (ControlNet / edit model). - Continuity state. - **Take extension** (seamless continuation with overlap). It is the next thing wanted, so provenance (`take_link` kinds, chained inputs) must not rule it out. - Script re-import with reconciliation. The script is frozen after import. - A render daemon that runs while the app is closed. - In-app inpaint or edit-model reference editing. Editing happens outside the app. - Takes with internal cuts (`[Shot 2]` blocks, intercuts). - SeedVR2, NVIDIA upscaling and RIFE as finishing steps (RIFE may later be an opt-in derived file, never a final). - Alternate Performance voice sources (local TTS). `performance_source` must stay open to new kinds. - Forced alignment or ASR auto-marking of imported Performances. - Fountain import (only the owner's OSF dialect is supported). - Score and music: `non_diegetic_music` is always `N/A`, and music is added in Resolve. - FCP 7 XML export. ## Further Notes - **Unverified items to watch in the build:** - Peak VRAM for a Single-pass final at 1120 short side for 15 s has not been measured. Only Promotion was (~283 s, ~31.8 GB). - Whether H3 renders composited Annotation marks literally has not been seen. The clean-sketch toggle exists as a hedge. - Whether FCPXML `keyword`/`note` reach Resolve's Media Pool is unknown, which is why only the CSV is relied on. - Attribute bleed in mixed Atlases is still open. Per-Setup regroup is the mitigation. - **Hardware assumptions:** one RTX 5090 / 32 GB, ComfyUI ≥ 0.39.0 managed by the user, and Resolve 18+ (tested on 21.1.1 free). - **Research notes** live on branches `research/comfyui-api`, `research/finishing-pass`, `research/h3-latent-reuse` and `research/resolve-fcpxml`. UI prototypes are on `prototype/crop-sheet`, `prototype/board-paint` and `prototype/dailies`. Treat prototypes as visual references only, not code to keep. - The source plan, `scratch/scratch-plan.md`, is local and gitignored. Where it conflicts with this spec (for example its LLM prop proposal, Fountain import or RIFE), this spec and the closed tickets win. - The next step is to split this spec into build issues, each of which writes the DDL for the tables it creates.
Author
Owner

Spec amendments decided while reviewing #79 (#33) and #80 (#35):

  • Structural edits: Rust may make up to three structural edits. The third one is the forced-audio branch (VAEEncodeAudio → 0 mask), and only Performance-driven Takes get it (#65). The position of the user LoraLoaderModelOnly chain relative to the shipped LoRAs doesn't matter.
  • Audio format: H3 jobs write lossless FLAC (core SaveAudio), not WAV. This covers Story 140, the Draft and Finals bullets, and the fake ComfyUI fixtures. The per-Scene Performance.wav in the Edit package doesn't change.
  • Deleting references: ADR-0004 applies to Reference sets. If no Take consumed an owner's versions, deleting a Look, Wardrobe item, Prop or Character hard-deletes its set, Tiles and versions, and the delete can be undone. "Append-only" means a version is never edited, and never deleted while its Tile still exists.

#51 and #65 have been updated to match. The text of this spec body still needs the same edits.

Spec amendments decided while reviewing #79 (#33) and #80 (#35): - **Structural edits:** Rust may make up to three structural edits. The third one is the forced-audio branch (`VAEEncodeAudio` → 0 mask), and only Performance-driven Takes get it (#65). The position of the user `LoraLoaderModelOnly` chain relative to the shipped LoRAs doesn't matter. - **Audio format:** H3 jobs write lossless FLAC (core `SaveAudio`), not WAV. This covers Story 140, the Draft and Finals bullets, and the fake ComfyUI fixtures. The per-Scene `Performance.wav` in the Edit package doesn't change. - **Deleting references:** ADR-0004 applies to Reference sets. If no Take consumed an owner's versions, deleting a Look, Wardrobe item, Prop or Character hard-deletes its set, Tiles and versions, and the delete can be undone. "Append-only" means a version is never edited, and never deleted while its Tile still exists. #51 and #65 have been updated to match. The text of this spec body still needs the same edits.
Author
Owner

Follow-up from #47: the app-wide default Slot order is now its own ticket, #99 (App Settings: default Slot order). A Project copies the default order only when it is created.

Follow-up from #47: the app-wide default Slot order is now its own ticket, #99 (App Settings: default Slot order). A Project copies the default order only when it is created.
Author
Owner

Audited all 215 user stories against main (90a8019). 209 are implemented. Every child ticket is closed. These gaps remain, each with its own ticket:

  • #133: Boards sent to H3 carry their Annotation layer (Stories 88 and 89). board_images sends only the Sketch, so the clean toggle changes only the prompt text.
  • #134: New seed with original inputs on stale Takes (Story 158). #56 left this option out.
  • #135: Over-budget badge on every Shot listing (Story 116). It is missing from the Dailies coverage wall.
  • #136: Open a Project by double-clicking its .studio file (Story 1). There is no file association.
  • #137: Carry the Project author into the Edit package (Story 32). The author is stored but never used.

Smaller notes, not ticketed:

  • Story 6: Only the Slot order can be edited as an app-wide default. The Lighting list, Notation set and Generation profile are seeded from defaults.json by hand.
  • Story 213: Compact holds jobs back by keeping the session mutex locked. It doesn't pause the queue.
  • Story 190: The block message wording differs from the spec.
  • Some behaviours have no test: Workflow reasons being information only (166), Takes staying on the first part of a split Shot (76), and export of a file that went missing before Storage was opened (214).

This spec stays open until #133 to #137 are closed.

Audited all 215 user stories against `main` (90a8019). 209 are implemented. Every child ticket is closed. These gaps remain, each with its own ticket: - #133: Boards sent to H3 carry their Annotation layer (Stories 88 and 89). `board_images` sends only the Sketch, so the clean toggle changes only the prompt text. - #134: New seed with original inputs on stale Takes (Story 158). #56 left this option out. - #135: Over-budget badge on every Shot listing (Story 116). It is missing from the Dailies coverage wall. - #136: Open a Project by double-clicking its .studio file (Story 1). There is no file association. - #137: Carry the Project author into the Edit package (Story 32). The author is stored but never used. Smaller notes, not ticketed: - Story 6: Only the Slot order can be edited as an app-wide default. The Lighting list, Notation set and Generation profile are seeded from `defaults.json` by hand. - Story 213: Compact holds jobs back by keeping the session mutex locked. It doesn't pause the queue. - Story 190: The block message wording differs from the spec. - Some behaviours have no test: Workflow reasons being information only (166), Takes staying on the first part of a split Shot (76), and export of a file that went missing before Storage was opened (214). This spec stays open until #133 to #137 are closed.
Sign in to join this conversation.
No description provided.