fix(studio): Enable keyframes works (and auto-tracks) on elements with a global gsap.set#1728
Merged
Merged
Conversation
…ot the comp end resolveTimelinePositions walks anims in document order advancing a cursor; a global `gsap.set(...)` carries no position arg, so it fell through to the cursor fallback and inherited the comp-end time (every prior tween's duration summed) as its resolvedStart. A global set is a load-time hold — its start is 0. This silently broke 'Enable keyframes' on any element whose only animation is a global gsap.set (e.g. a statically-positioned card): promoteSetToKeyframes bails when `playhead <= setStart`, and setStart was the comp end, so any playhead before the end was a no-op. Pin a global set to resolvedStart 0 in both the recast and acorn parsers; don't let it advance the cursor/prevStart.
…-tracking When 'Enable keyframes' promotes a static set to a two-stop tween, the 0% (the held start the user didn't choose) is now marked `auto: true` → serialized as the `_auto: 1` marker. The parser's endpoint-sync then keeps it tracking the nearest keyframe until the user edits it directly; the 100% (the real keyframe placed at the playhead) stays fixed. This re-wires the auto-endpoint behavior that was silently dropped in #1605 (the sync logic stayed, but no flow produced an `_auto` endpoint anymore, so an untouched 0% never tracked). Adds a guard test so it can't be lost again.
The resolveTimelinePositions guard added a branch (pushes it over threshold), and changed-file scope re-surfaces pre-existing complex functions (readElementPosition, applyArcWaypointAtPlayhead, the useEnableKeyframes callback). Bare directives only.
Dragging a static element positioned via the legacy --hf-studio-offset CSS var (e.g. dot-a) flew off-screen — three independent failure modes, all fixed: 1. Live drag integrated: the per-move draft read its base from the live transform it set last frame (gsap.getProperty), so base+delta accumulated frame-over-frame. Fix: carry a stable baseGsap on the in-memory drag member (immune to mid-drag re-renders that wipe the data-hf-drag-* attrs) and use it as the fallback. 2. Commit re-added the delta: the source commit re-read the wiped attrs / live transform. Fix: re-stamp the stable base/initial attrs in applyManualOffsetDragCommit before the commit reads them. 3. Drop left it offset: the committed source was correct, but the LIVE element kept its --hf-studio-offset var + translate:var(...), which composed with the GSAP transform (rendered at dropped + offset) until a full reload. Fix: on cleanup, when GSAP owns the position, clearStudioPathOffset() migrates the element off the legacy CSS channel (leaving transform untouched) — matching the stripped source. Adds a regression suite covering all three layers.
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
Two related fixes for Enable keyframes on an element whose only animation is a global `gsap.set(...)` (e.g. a statically-positioned/3D-transformed element).
1. Enable keyframes was a silent no-op
resolveTimelinePositionswalks animations in document order advancing acursor. A globalgsap.setcarries no position arg, so it fell through to the cursor fallback and inherited the comp-end time (sum of every prior tween's duration) as itsresolvedStart. But a global set is an off-timeline load hold — its start is0.promoteSetToKeyframesbails whenplayhead <= setStart, so withsetStart= comp end, clicking the diamond at any playhead before the end did nothing. Fixed at the root in both parsers: a global set pins toresolvedStart = 0and doesn't advance the cursor.2. The generated 0% endpoint didn't auto-track
The
_autoendpoint-sync (an unmodified0%/100%follows its nearest neighbor until edited) was still in the parser, but no flow produced an_autoendpoint anymore — it was silently dropped in #1605. So a 0% the user never touched behaved as a fixed keyframe.Now
promoteSetToKeyframesmarks the generated0%(the held start the user didn't choose) asauto: true→_auto: 1; the100%(the real keyframe placed at the playhead) stays fixed.How it was found
Added temporary strategic logging to the Enable-keyframes flow and reproduced in the studio — the log showed
setStart: 4, blocked: true. Logging removed after the root cause was confirmed.Tests
gsap.setresolves toresolvedStart: 0, not the cursor (recast + acorn).promoteSetToKeyframesmarks the0%endpointauto, leaves100%fixed.gsap.setcorrectly becomestl.to("#card", { keyframes: {...}, duration }, 0)with_auto: 1on the0%.