Skip to content

feat(studio): GSAP keyframe + motion-path editing#1567

Open
miguel-heygen wants to merge 2 commits into
feat/core-motion-path-routefrom
feat/studio-gsap-keyframe-editing
Open

feat(studio): GSAP keyframe + motion-path editing#1567
miguel-heygen wants to merge 2 commits into
feat/core-motion-path-routefrom
feat/studio-gsap-keyframe-editing

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Consolidates the studio side of the GSAP keyframe/motion-path work into a single reviewable PR. The combined diff is byte-identical to the previously-reviewed studio stack.

What's here

  • Runtime read layer + shared helpers — read GSAP tween/keyframe state from the preview iframe.
  • Drag/commit/bridge editing infra — runtime bridge for drag edits + commit pipeline.
  • Motion-path geometry + commit helpers — path math and write helpers.
  • On-canvas motion-path overlay — node/handle overlay UI.
  • Keyframes flag + gesture recording + timeline/selection refinements.

Context

Supersedes the separately-reviewed (and already-approved) studio PRs #1557, #1558, #1559, #1560, #1561, which were consolidated here so the studio work has a single mergeable path to main. The keyframes CLI command + skill remain isolated in their own PR (#1556) on top of this one.


Single-source manual offset + rotation + subcomposition editing (added)

Dragging or rotating an element writes into the GSAP timeline (single source of truth) instead of a parallel --hf-studio-offset / --hf-studio-rotation CSS var: static elements commit a tl.set (idempotent on re-edit), tweened elements edit keyframes, live preview moves via gsap.set. What you see = what is written = what renders. Removes the dual-channel reconciliation behind the fling / disappear / runaway / double-stack / wrong-start bugs — for both position and rotation.

Editing elements inside subcompositions works the same way, which surfaced and fixes:

  • Source resolution — resolve a subcomp element's source file via the composition-id map (the runtime drops the linkage when inlining a subcomp).
  • Overlay visibility — the selection box and motion path use basic visibility, not the occlusion heuristic (a background-less opacity:1 scene above an element is not an opaque cover).
  • Scoped soft reload — a soft reload rebuilds only the committed composition's timeline; editing one composition no longer wipes the others' (the cross-composition revert).
  • Keyframe read — read keyframes from the element's own composition timeline (scan all timelines, not the first unstable key).
  • Reload UX — drag / rotate / gesture / delete-all all use a soft reload (no hard iframe remount).

@miguel-heygen miguel-heygen force-pushed the feat/studio-gsap-keyframe-editing branch 5 times, most recently from e347d3a to 07a6321 Compare June 18, 2026 23:36
@miguel-heygen miguel-heygen force-pushed the feat/core-motion-path-route branch from e450a92 to eb1c51f Compare June 19, 2026 00:38
@miguel-heygen miguel-heygen force-pushed the feat/studio-gsap-keyframe-editing branch 3 times, most recently from cd33fda to 12707a3 Compare June 19, 2026 17:58
@miguel-heygen miguel-heygen force-pushed the feat/core-motion-path-route branch from a01743d to 1dbedb6 Compare June 19, 2026 19:36
@miguel-heygen miguel-heygen force-pushed the feat/studio-gsap-keyframe-editing branch from 12707a3 to 8bf425e Compare June 19, 2026 19:36
@miguel-heygen miguel-heygen force-pushed the feat/core-motion-path-route branch from 1dbedb6 to 6aafc8a Compare June 19, 2026 19:52
@miguel-heygen miguel-heygen force-pushed the feat/studio-gsap-keyframe-editing branch from 8bf425e to 231bf67 Compare June 19, 2026 19:52
@miguel-heygen miguel-heygen force-pushed the feat/core-motion-path-route branch from 6aafc8a to d133735 Compare June 19, 2026 20:23
@miguel-heygen miguel-heygen force-pushed the feat/studio-gsap-keyframe-editing branch from 231bf67 to 6b3aa33 Compare June 19, 2026 20:23
Consolidates the studio side of the GSAP keyframe/motion-path work into one
PR: runtime read layer + shared helpers, drag/commit/bridge editing infra,
motion-path geometry + commit helpers, on-canvas motion-path overlay, and the
keyframes flag with gesture recording + timeline/selection refinements.

Makes "Add keyframe at playhead" do the right thing for every GSAP animation
shape, never disabling or silently no-oping:
- Array-form keyframe tweens (keyframes: [{x,y},…]): readElementPosition now
  derives the captured props from the keyframe stops (top-level properties is
  empty for array form), and the percentage uses the tween range, not the clip
  range — so the add lands at the right spot instead of no-oping.
- Out of the tween range, the action extends the tween to reach the playhead
  and adds a hold there, rescaling existing keyframes to keep their absolute
  timing (was: disabled / destructive).
- Flat tweens (to/from/fromTo) convert to their natural keyframes, then take
  the same add/extend path.
- set() is promoted to a two-stop tween from the set's time to the playhead.
- motionPath/arc tweens add a waypoint at the on-path position (matching
  segment, so the curve is preserved) instead of being linearized; outside the
  range they extend their duration, with a merge threshold against duplicates.

Also fixes deep-link hydration. A URL with ?t=…&selId=… restored neither the
playhead nor the selection on a fresh load: useStudioUrlState requests the seek
before the player runtime mounts its requestedSeekTime subscription, so the
request never reached pendingSeekRef, and initializeAdapter (which drained only
pendingSeekRef when the adapter became ready) started at 0 — which also blocked
selection hydration (gated on the seek settling). Fixed at the source:
initializeAdapter now reconciles the store's requestedSeekTime as well, so a seek
requested any time before the adapter is ready lands deterministically.

Supersedes the separately-reviewed studio PRs #1557, #1558, #1559, #1560, #1561.
…eline

Dragging or rotating an element writes into the GSAP timeline (the single source of
truth) instead of a parallel --hf-studio-offset / --hf-studio-rotation CSS var: static
elements commit a tl.set (idempotent on re-edit), tweened elements edit keyframes, and
the live preview moves via gsap.set so what you see equals what is written and renders.
Removes the dual-channel CSS-var/transform reconciliation behind the
fling / disappear / runaway / double-stack / wrong-start bug class — for BOTH position
and rotation (gesture base read from the gsap transform, gsap.set live preview, tl.set/
keyframe commit, dropped the handleDom*Commit CSS fallbacks).

Subcompositions edit the same single-source way, which surfaced and fixes:
- resolve a subcomp element's source file via the composition-id map (the runtime drops
  the source linkage when inlining the subcomposition);
- a selected element's selection box AND motion path use basic visibility, not the
  occlusion heuristic (a backgroundless opacity-1 scene above it is not an opaque cover);
- soft reload rebuilds ONLY the committed composition's timeline, leaving other
  compositions' timelines intact (no cross-composition revert);
- read keyframes from the element's OWN composition timeline (scan all timelines, not
  the first unstable key);
- delete-all uses a soft reload too, so editing no longer hard-reloads the iframe.
@miguel-heygen miguel-heygen force-pushed the feat/core-motion-path-route branch from d133735 to b79f553 Compare June 19, 2026 22:33
@miguel-heygen miguel-heygen force-pushed the feat/studio-gsap-keyframe-editing branch from 6b3aa33 to b2b2e98 Compare June 19, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant