feat(studio): multi-select transitions, group editing, take history - #731
Open
scottdraves wants to merge 6 commits into
Open
scottdraves wants to merge 6 commits into
scottdraves wants to merge 6 commits into
Conversation
Closes #728. Editing a flow's transitions gave no way to tell which one you were changing, only let you change one at a time, and threw the old render away on every regenerate. Selection - selectedTransitionIndex becomes selectedTransitionIndices, in click order; the last click is the "primary" the panel names and the preview plays. Click selects, shift/cmd/ctrl-click toggles. - Selected gaps get a blue slab, from new selected/selectedDim/ selectedGlow tokens. Deliberately not the existing `processing` blue, so "selected" and "rendering" stay tellable apart. - Select all / Clear all sit beside Reset, with a live selection count. - Clicking a transition plays it, every time. This is an explicit request carrying a sequence number, not a derived effect: re-clicking the selected transition changes no state, so there would be nothing to react to. CrossfadeVideo takes a replayToken to restart the segment already on screen. Group editing - Edits apply to the whole selection. When one would flatten a field the selection disagrees about, ForceSettingsDialog asks first. - Gating is per interaction, not per field: choosing a preset writes prompt, negative prompt, LoRA and duration, and gating each separately would raise four dialogs in a row. History - Each run is recorded on the transition and restorable. Snapshots the resolved settings as a full override set, not the overrides: globals drift while you work, so overrides alone would replay a take under today's defaults. - Shown as a right-aligned strip in the settings header, using the filmstrip frame nearest the middle of the clip -- the first frame is the start reference, identical across takes. The header reserves its height so the panel does not resize as takes complete and shove the preview around. Two fixes fell out of this: clearTransitionOverride rebuilt the transition field by field and would have dropped history on "Reset to defaults", and regenerating left uprezDreamUuid pointing at the replaced dream's upscale. Behaviour change: clicking a failed transition selects it rather than retrying immediately; the button reads "Retry". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deploying frontend-alpha with
|
| Latest commit: |
07511ab
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://69a9b91c.frontend-btm.pages.dev |
| Branch Preview URL: | https://feat-728-transition-selectio.frontend-btm.pages.dev |
Deploying frontend-stage with
|
| Latest commit: |
07511ab
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://208c0b9f.frontend-d7v.pages.dev |
| Branch Preview URL: | https://feat-728-transition-selectio.frontend-d7v.pages.dev |
Contributor
Author
|
still need to test the force dialog! do not merge yet |
Two ways the flow could misrepresent itself. Selecting transitions that disagreed about a setting left the panel with no honest value to show, so it showed the last-clicked one's and an edit silently flattened the rest. The disagreement is now settled when the selection is made: a shift-click (or Select all) that would mix settings asks first. OK forces the clashing fields onto all of them, taking the values already on screen; Cancel leaves the selection alone. Only the clashing fields are pinned -- ones the selection already agrees about agree because both sides resolve the same way, and pinning them would freeze values that are free to follow the globals. Return cancels, and both dialog buttons wear the same neutral style: forcing settings is the destructive answer, so nothing should pull the eye toward it. Editing an already-rendered transition left the old video in place with nothing to say so, and Generate skipped it outright -- resolveGeneration- Targets read "processed" as "done". A transition is now stale when its resolved settings differ from the snapshot recorded for the take in the flow. Stale ones carry a dot, and Generate targets them along with the never-rendered ones. With a selection active the selection is the scope and runs unconditionally, so the strip header carries an "N edited" count to keep work outside that scope visible. The mapping from resolved settings to a recorded snapshot now lives in one place, which generation writes through and the staleness check reads through: two copies would let a field be recorded one way and compared another, showing up as a transition that is permanently stale or never stale. Prompt and LoRA resolve through the effective preset on both sides of every comparison, matching what the panel displays -- without it, two transitions on different presets showed different prompts while comparing equal. Duration labels under the gaps are gone; the dot took their place, so a gap keeps its height whether or not it is marked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Conflicts resolved: - flow-preview: adopt stage's shared SegmentPreview/useDreamSegments; keep the selection-follow and play-request effects, threading replayToken through SegmentPreview to CrossfadeVideo. - transition-gap: use stage's DreamCardProgress for queue/processing and uprez progress; keep selection aria labels, mouse handling and StaleDot. - transition-gap.styled: keep both the selection styling and the dream-progress overrides. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RH6gcEKeCTjKG6CS751YQm
…ache [DREAM_QUERY_KEY, uuid] is a single React Query cache entry per dream, and every reader stores the whole ApiResponse in it -- except transition-history, which used getDream and stored an unwrapped Dream. The two polled each other's shape out of the cache: - History read data.filmstrip, undefined whenever useDreamSegments wrote last, so thumbnails fell back to the placeholder and reappeared on the next poll. Its refetchInterval never saw a filmstrip either, so it polled forever and kept the flicker going. - useDreamSegments' select returned undefined whenever history wrote last, so dreamsToSegments dropped the segment. SegmentPreview then clamped the out-of-range index to 0 and the container ratio changed with it, which is the preview visibly jumping around. Use getDreamResponse + select in transition-history so the stored shape matches every other reader, and read the unselected response in refetchInterval. FlowPreview also held a positional index into a list that legitimately grows as renders land, so each new segment could shift what was playing. Track the clip by dream uuid and derive the index instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ynpMfJGqavM4QnereDtUz
Take thumbnails go from 84x48 to 126x72. HISTORY_ROW_HEIGHT grows by the same 24px (88 -> 112) so the settings header still reserves the strip's full height and the preview below doesn't jump as takes complete. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012AB1rUaDPEQCR3Dmhye5mN
… check Replace the gold disc with a check on processed transitions with a gold filmstrip glyph: three frame windows between two rows of sprocket holes. Windows and holes are cut out (evenodd) so the selection slab shows through. The disc and its box-shadow ring are gone; the glow now follows the strip's outline via drop-shadow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012AB1rUaDPEQCR3Dmhye5mN
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #728.
Editing a flow's transitions gave no way to tell which one you were changing, only let you change one at a time, and threw the old render away on every regenerate. Three parts, matching the issue.
1. Selection
selectedTransitionIndexbecomesselectedTransitionIndices, held in click order — the last click is the "primary" that the panel names and the preview plays.selected/selectedDim/selectedGlowtokens. Deliberately not the existingprocessingblue, so "selected" and "rendering" stay tellable apart.That last point is why playback is an explicit request carrying a sequence number rather than a derived effect. Re-clicking the selected transition changes no state, so there is nothing to react to; a bare UUID would be an unchanged value.
CrossfadeVideotakes areplayTokento restart the segment already on screen.2. Group editing
Edits apply to the whole selection. When one would flatten a field the selection disagrees about,
ForceSettingsDialogasks first — "force them all to the same value?".Gating is per interaction, not per field. Choosing a preset writes prompt, negative prompt, LoRA and duration; gating each separately would raise four dialogs in a row. Only the field you touched is gated, and the knock-on clamps follow.
3. History
Every run is recorded on the transition and restorable.
The snapshot stores the resolved settings as a full override set, not the overrides. Globals drift while you keep working, so overrides alone would replay a take under today's defaults rather than the ones that produced it.
Shown as a right-aligned strip in the settings header, using the filmstrip frame nearest the middle of the clip — the first frame is the start reference frame, identical across every take at a position. The header reserves its height via
HISTORY_ROW_HEIGHT, so the panel does not resize as takes complete and shove the preview up and down the page.Fixes that fell out
clearTransitionOverriderebuilt the transition field by field and would have droppedhistoryon "Reset to defaults".uprezDreamUuidpointing at the replaced dream's upscale.useFlowGeneration's tests were passing while silently exercising the failure path — the mock store had norecordTransitionRun, so generation threw and the catch swallowed it. Mock fixed, plus an assertion that nothing rolls back tofailed.Behaviour change
Clicking a failed transition now selects it rather than retrying immediately; the Generate button reads "Retry". Uniform click semantics were the point of part 1, but it is one less one-click path than before.
Testing
tsc --noEmit, eslint, prettier, and production build all clean. 243 tests pass — 24 new, covering selection/toggle/prune, history record-restore-cap, replay sequencing, field mismatch detection, and filmstrip/time formatting.Also driven by hand in the browser against staging: selection and the blue highlight, preview following clicks (including the re-click-while-selected case), history restoring a real take across three generations, and the panel holding its height as takes complete.
🤖 Generated with Claude Code