feat(studio): razor/blade tool for GSAP-aware timeline clip splitting#1266
Open
miguel-heygen wants to merge 1 commit into
Open
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
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.
2516745 to
b86a1c0
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:
fromandtovalues preserved correctlyThe split pipeline:
useRazorSplit→ HTML split viasplit-elementmutation → GSAP split viasplitAnimationsInScriptingsapParser.ts→ save with undo history viasaveProjectFilesWithHistory.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()Code quality improvements (while-we're-here)
Cleaned up 20+ pre-existing clone groups across touched files:
PlayheadIndicatorshared component (33 lines deduped)useContextMenuDismisshook (21 lines deduped)TimelineCallbacksinterfaces (14 lines deduped)useTimelineZoomhook (4 selectors consolidated)gsapParser.test-helpers.ts(shared test utilities)files.tscollectNumericKeyframePropsandcomputePcthelpers in toolbarTesting performed
Unit tests (1199 passing)
splitAnimationsInScript: first-half only, second-half retarget, spanning split, no matching animations, multiple animations, fromTo preservation, round-trip, keyframes spanning, keyframes retarget, keyframes before splitBrowser testing (agent-browser via Chrome DevTools MCP)
Tested against a complex composition with 12 elements across 11 tracks:
gsap.towith opacity/y/scale (Hero Title)gsap.fromTowith elastic easing (Floating Circle)Verified:
data-start/data-durationfromandtovalues preservedKnown limitations