feat(studio): add ripple, roll, slip and slide trim tools - #3341
Open
miguel-heygen wants to merge 1 commit into
Open
feat(studio): add ripple, roll, slip and slide trim tools#3341miguel-heygen wants to merge 1 commit into
miguel-heygen wants to merge 1 commit into
Conversation
The timeline could only trim one clip's own in/out, so every re-edit of a multi-clip track left a gap or an overlap to close by hand. Four tools now own the standard NLE edit operations, each committing as one atomic, single-undo batch through the existing group-resize persist. - Ripple (T): trim an edge, every later clip on the track follows. - Roll (Shift+T): move the cut between two clips; nothing downstream moves. - Slip (Y): change which part of the source plays; nothing moves. - Slide (Shift+Y): move a clip; its neighbours absorb the move. All four are lane-scoped, clamped by available source media and the minimum clip duration, snap the edge that actually moves, and refuse up front when the tool cannot act on the clip (a roll with no clip across the cut, a slip on generated pixels) with a message that says which. Two adjacent cleanups came with it: the blocked-edit toast now reads the intent it was already being handed instead of ignoring it, and the duplicated remove-element POST behind the two delete paths moved into one helper.
Comment on lines
+157
to
+164
| return fetch( | ||
| `/api/projects/${projectId}/file-mutations/remove-element/${encodeURIComponent(targetPath)}`, | ||
| { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json", ...studioWriteHeaders() }, | ||
| body: JSON.stringify({ target }), | ||
| }, | ||
| ); |
This was referenced Aug 19, 2026
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.
What
Four dedicated trim tools for the Studio timeline, alongside Select and Razor:
T⇧TY⇧YCloses #3285 (except the optional FCP-style precision view — see "Not covered").
Why
Timeline trim only ever changed the grabbed clip's own in/out. On a track with
more than one clip that means every trim opens a gap or an overlap that has to
be closed by hand, and there is no single action for "trim this and shift the
rest to match". Roll, slip and slide had no equivalent at all.
Demo
Recorded against a real Studio on a track of
A 0-8, B 8-12, C 12-16(plus asub-composition on a second lane). Every clip below is the actual pointer
gesture, not a mock-up.
Ripple — drag A's out point right; B and C follow, and the second lane is
untouched.
Roll — drag the A|B cut; A grows by exactly what B gives up and C never
moves.
Slide — drag B along the track; A stretches and C shortens to absorb it, so
the track keeps its total length.
Slip has no motion to record — that is the whole point of it. On the
sub-composition clip it moved the in point
3s → 4.5swhilestartanddurationstayed at4 + 6. On a clip with no source at all it refuses upfront and says why, instead of starting a gesture that does nothing:
How
timelineTrimOps.tsis pure math with no store or DOM in it:resolveTrimPlandecides which clips an operation touches (and returns nullwhen the gesture is impossible),
resolveTrimDeltaBoundssays how far it maytravel,
applyTrimDeltaproduces the per-clip patches. Semantics follow FCP /Premiere: a head ripple keeps the clip's start and pulls the lane in
behind it, which is what makes it a ripple rather than a plain trim.
timelineTrimSession.tsis the gesture layer. A trim session is aTimelineGroupResizeSession, so the whole existing pipeline downstream —projection rendering, escape-cancel, and the atomic single-undo commit through
commitTimelineGroupResize— is reused unchanged. Only the preview mathdiffers. No new persistence plumbing.
available source media and the minimum clip duration, and snap the edge that
actually moves, with every clip that rides along excluded from the snap grid.
reusing the existing blocked-edit report — which was already being handed an
intent it threw away.
the keyboard handler and the shortcuts panel.
Two adjacent cleanups came along because the repo gates flagged them once these
files were in scope: the duplicated
remove-elementPOST behind the two deletepaths moved into one helper, and the timeline shell's inline style moved into
the theme module (both files were at the 600-line cap).
Test plan
Unit tests added/updated — 43 new tests: the trim math (bounds, clamps,
playback-rate scaling, adjacency invariants for every op), the session
layer (lane scoping, capability refusal, snapping), and six end-to-end
gesture tests through the drag hook asserting exactly what gets persisted.
Manual testing performed — drove a real Chrome against Studio and read
back the project file after each gesture rather than trusting the UI.
Each gesture dragged +2s:
A 0+10 B 10+4 C 14+4— second lane untouchedA 0+8 B 8+2 C 10+4— B's start pinned, lane closed behindA 0+10 B 10+2 C 12+4— C never movedA 0+10 B 10+4 C 14+2— track length unchanged4+6unchanged, in point3 → 4.5One
⌘Zafter a ripple restored the whole lane, confirming the batch is asingle undo step. All four shortcuts (
T,⇧T,Y,⇧Y),Vback toSelect, and the toolbar buttons were driven through the real UI.
Full
packages/studiosuite green (4308 tests), typecheck, oxlint, oxfmt.Not covered
is not in this PR; it is a separate component and follows on its own branch.
clip's own lane, matching the existing "close gap" tooling.
data-playback-startonto clips that have no source at all (adivgetsin=0on an out-trim,in=2on a head-trim). The trim tools here do not dothat — they only write an in point on a clip that has one — but fixing the
shared plain-resize path changes what every head-trim writes and wants its own
change with its own regression tests. The root is that the runtime clip
manifest reports
playbackStart: 0for every element, so nothing downstreamcan tell "no in point" from "in point at zero". Happy to pick it up next.
Size
~1.6k changed lines against the 1k guideline, about 900 of which are the new
pure-math and gesture modules and ~450 are tests. The four operations share one
plan/bounds/apply module and one session type, so splitting them into two PRs
would cut that module in half and leave the first half untestable on its own.
Flagging rather than hiding it.