feat(studio): razor/blade tool for GSAP-aware timeline clip splitting#1266
Closed
miguel-heygen wants to merge 1 commit into
Closed
feat(studio): razor/blade tool for GSAP-aware timeline clip splitting#1266miguel-heygen wants to merge 1 commit into
miguel-heygen wants to merge 1 commit into
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Fallow audit reportFound 47 findings. Dead code (1)
Duplication (10)
Health (36)
Generated by fallow. |
Comment on lines
+42
to
+49
| const response = await fetch( | ||
| `/api/projects/${projectId}/file-mutations/split-element/${encodeURIComponent(targetPath)}`, | ||
| { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ target: patchTarget, splitTime, newId }), | ||
| }, | ||
| ); |
Comment on lines
+63
to
+77
| const response = await fetch( | ||
| `/api/projects/${projectId}/gsap-mutations/${encodeURIComponent(targetPath)}`, | ||
| { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| type: "split-animations", | ||
| originalId, | ||
| newId, | ||
| splitTime, | ||
| elementStart, | ||
| elementDuration, | ||
| }), | ||
| }, | ||
| ); |
Comment on lines
+32
to
+34
| const response = await fetch( | ||
| `/api/projects/${projectId}/files/${encodeURIComponent(targetPath)}`, | ||
| ); |
d70ab5c to
2516745
Compare
2516745 to
b86a1c0
Compare
b86a1c0 to
004d384
Compare
004d384 to
c71c46f
Compare
c71c46f to
f11ac4d
Compare
f11ac4d to
ff908ff
Compare
ff908ff to
0e9f84f
Compare
0e9f84f to
8626a2b
Compare
8626a2b to
8bdb625
Compare
8bdb625 to
efc9f32
Compare
Add a razor/blade tool to Studio's timeline — the standard NLE workflow for splitting clips at arbitrary positions. - B enters razor mode (crosshair cursor + red vertical guide line) - Click any clip to split it at the click position - Shift+click splits all clips across every track at that time - V or Escape exits razor mode GSAP animations are correctly re-timed for both halves: animations before the split stay on the original, animations after are retargeted, and spanning animations are trimmed with a continuation on the new element. Keyframes animations are classified by total per-keyframe duration and retargeted when entirely after the split point. Extracted shared utilities (canSplitElement, PlayheadIndicator, useContextMenuDismiss, TimelineCallbacks) to reduce duplication across timeline components.
efc9f32 to
7697895
Compare
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.
Summary
Adds a razor/blade tool to Studio's timeline — the standard non-linear editing workflow for splitting clips at arbitrary positions. Inspired by feedback from an After Effects editor who expected this as a fundamental editing primitive.
Why this matters
Timeline editing without a razor tool forces editors into a playhead-dependent workflow: position the playhead, then use the S shortcut. This is slow and imprecise for the most common editing operation. Every professional NLE (Premiere, DaVinci, After Effects) has a razor/blade tool that splits at the mouse position, and editors expect it.
Technical approach
GSAP-aware splitting
The razor tool doesn't just split HTML elements — it correctly re-times GSAP animations for both halves of a split:
tl.set(inserted before other tweens so GSAP records the correct initial state)fromandtovalues preserved correctlyCSS rule duplication
When splitting an element with ID-based CSS (e.g.,
#box { background: red }), the new element gets a different ID (#box-split). The splitter uses PostCSS to parse stylesheets and duplicate matching rules for the new ID, correctly handling media queries, compound selectors, and nested rules.Server-side ID deduplication
When splitting an already-split element, the server checks if the requested ID already exists in the document and auto-increments (
box-split-2,box-split-3, etc.) to prevent duplicate IDs.Preview reload after timeline edits
Timeline move and resize operations now trigger a preview reload so the composition re-renders with the updated timing.
Architecture
useRazorSplit.ts— extracted hook withexecuteSplit()pure orchestration,handleRazorSplit(single clip),handleRazorSplitAll(multi-track)timelineElementSplit.ts— shared utilities (canSplitElement,buildPatchTarget,readFileContent) to break circular dependenciesplayerStore.ts—activeTool: "select" | "razor"state withsetActiveToolsetterTimelineCanvas.tsx— click handler intercepts razor mode, converts pixel position to split timeTimeline.tsx— red guide line overlay, crosshair cursor, shift+click multi-track routingTimelineToolbar.tsx— selection/scissors toggle buttonsgsapParser.ts—splitAnimationsInScript(),updateAnimationSelector(),computeKeyframesTotalDuration(),insertInheritedStateSet()sourceMutation.ts—splitElementInHtml()enhanced with CSS duplication (PostCSS), ID deduplication, and inherited state viatl.setCode quality improvements
Cleaned up 20+ pre-existing clone groups across touched files:
PlayheadIndicatorshared componentuseContextMenuDismisshookTimelineCallbacksinterfacesuseTimelineZoomhookgsapParser.test-helpers.tsfiles.tsTest plan
Unit tests (1208 passing)
splitAnimationsInScript: first-half, second-half, spanning, no match, multiple animations, fromTo, round-trip, keyframes spanning/retarget/before, set tween retarget, same-position dedup, inherited state orderingsplitElementInHtml: basic split, CSS duplication, ID deduplication, clip class preservation, out-of-range rejection, media playback-start adjustmentManual browser testing
Tested against a complex composition with 10 elements across 9 tracks:
data-timeline-locked) rejects split