Skip to content

fix: resolve computed GSAP timelines + drag improvements in Studio#1506

Merged
miguel-heygen merged 12 commits into
mainfrom
fix/gsap-computed-timelines
Jun 16, 2026
Merged

fix: resolve computed GSAP timelines + drag improvements in Studio#1506
miguel-heygen merged 12 commits into
mainfrom
fix/gsap-computed-timelines

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

Summary

  • Computed timelines now display correctly in Studio. Helper-built (addCycle(1.0, ...)) and bounded-loop timelines resolve at their true positions with MotionPath arcs recognized — the keyframe diamonds spread across the full timeline instead of collapsing to 0:00
  • Runtime-authoritative display for genuinely dynamic timelines: the live window.__timelines feeds the cache when the static parser can't resolve, with correct clip-relative conversion and arcPath extraction
  • Unroll-to-edit lets users convert helper/loop tweens into explicit literal tweens (visual no-op) for direct editing
  • Drag intercept fix: picks the position tween closest to the playhead (not the one with the most keyframes), and dragging outside all tweens' ranges creates a new keyframed tween instead of destructively replacing the nearest one
  • Lint parity: the GSAP lint rule now uses the acorn parser (which inlines computed timelines), so overlapping_gsap_tweens reflects true positions

What changed

Layer What
Static eval (core) AST inlining pre-pass: expand helper calls + bounded loops with param substitution, carrying provenance
Runtime display (studio) Fixed clip-relative conversion, motionPath→arcPath extraction, cache precedence
Editability (studio) Provenance→editability classifier, "Unroll to edit" button + mutation, ComputedTweenNotice
Drag fix (studio) pickClosestToPlayhead, outside-range creates new tween via add-with-keyframes, runtime resolvedFromValues for correct interpolation
Lint (core) Lint rule switched from recast to acorn parser

Test plan

  • Core: 1639 passed (86 files)
  • Studio: 867 passed (77 files)
  • Real-file e2e: add-to-basket-arc parses to 24 animations at correct times, 2 arcs, all helper-tagged
  • Unroll round-trip: 24→24, visual no-op, arcs preserved, helper removed
  • Browser e2e (agent-browser): diamonds render across full timeline, parse endpoint returns correct data
  • Drag test: outside-range drag creates new keyframe without destroying existing tweens
  • Both builds pass (core runtime bundle + studio vite)

U1: clone + shadow-aware identifier substitution over acorn ESTree, plus
provenance tagging and a GsapProvenance type. Foundation for resolving
helper/loop-built timelines in the read parser.
U2: expansion pre-pass that rewrites the analysis AST so a helper called N
times, a literal-bounds for-loop, a for-of, or a forEach over an inline array
each become concrete per-call/per-iteration tl.* statements with substituted
positions and provenance tags. Transitive timeline-building detection, safe
declaration dropping, depth/iteration caps; unresolvable constructs untouched.
U3: parseGsapScriptAcorn runs the inlining pre-pass before analysis, so
helper-built and bounded-loop timelines resolve at true positions with
motionPath arcs recognized; each tween carries provenance. Expansion order is
stamped so cloned tweens (sharing source loc) sort correctly. Read path only —
parseGsapScriptAcornForWrite is untouched, degrades to current behavior on
failure. The add-to-basket addCycle case now yields 7 resolved animations.
Phase 2 (U4-U6): the live-runtime scanner returns tween-relative keyframes
with per-tween timing and converts them to clip-relative when given clip dims,
fixing the timeline-vs-clip-relative bug; it extracts motionPath into arcPath
(shared buildArcPath) so the Arc Motion panel activates for data-driven arcs;
the cache leaves statically-unresolvable tweens to the runtime scan. Exempts
the pre-existing large useGsapTweenCache effects from fallow health (file-level,
like files.ts) rather than suppression comments.
U9: editabilityForProvenance(provenance) -> direct|unroll|override (core,
re-exported from the acorn subpath). A ComputedTweenNotice component shows an
unroll affordance for helper/loop tweens (wired in U10) and an overrides note
for dynamic ones. Extracts the shared GsapAnimationEditCallbacks interface to
remove section/card prop duplication.
U7: the GSAP lint rule now loads parseGsapScriptAcorn (which inlines helpers
and bounded loops) instead of the recast parser, so overlapping_gsap_tweens and
related findings reflect true resolved positions for computed timelines — and
keeps recast out of the lint graph entirely. Literal compositions are
unchanged (parity), all 182 lint tests pass.
U8: keyframes.mdx explains that helper/loop/data-built timelines display
correctly, and how each is edited — literal (direct), helper/loop (unroll to
edit), dynamic (composition overrides). Nothing is permanently locked.
Adds unrollComputedTimeline (core): serializes a parsed timeline's resolved
animations back to literal tl.* statements (arc/keyframe-aware) and surgically
replaces the top-level helper-call/loop statements that produced them via
magic-string, dropping dead helper declarations — a verified visual no-op.
Wires an unroll-timeline studio-api mutation and threads onUnroll to the
AnimationCard 'Unroll to edit' button. Exempts panel files whose inherited
fingerprints shifted from the prop threading.
… (U11)

Adds applyKeyframeOverrides: fetches a gsap-overrides.json sidecar and applies
explicit per-tween value overrides to the live timeline (keyed by selector +
tween ordinal), invalidating so GSAP re-reads them — the deterministic,
render-safe mechanism (preview + headless) for persisting edits to dynamic
tweens that can't be unrolled. Mirrors the shipped caption-overrides pattern;
wired into runtime init alongside applyCaptionOverrides.
Removes the gsap-overrides.json sidecar (runtime apply + init wiring + tests):
it solved a near-nonexistent case (HyperFrames is deterministic, so genuinely
unresolvable dynamic tweens barely exist) and introduced a parallel
persistence path outside the composition. The real cases are covered without
it — const/variable values resolve statically, helper/loop tweens unroll to
literals and then edit in-script (single source of truth). Renames the
editability strategy 'override' -> 'source' (edit in the Code tab) and updates
the notice + docs accordingly.
…rest tween

Fixes the GSAP drag intercept to pick the position tween closest to the
playhead (not the one with the most keyframes), and when dragging outside all
tweens' ranges, creates a brand-new keyframed tween instead of destructively
extending/replacing the nearest one. Reads the runtime position at the tween's
start time (via iframe seek) so convert-to-keyframes produces correct 0%
keyframes that preserve the interpolation from preceding tweens.
…rest tween

Also reverts all fallow health.ignore additions — pre-existing complexity in
touched files is accepted as inherited, not suppressed.
@miguel-heygen miguel-heygen force-pushed the fix/gsap-computed-timelines branch from 8eb6673 to 980a655 Compare June 16, 2026 17:02
@miguel-heygen miguel-heygen merged commit b9bd9ed into main Jun 16, 2026
47 checks passed
@miguel-heygen miguel-heygen deleted the fix/gsap-computed-timelines branch June 16, 2026 17:14
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