Skip to content

feat(studio): keyframe system — parser, runtime, timeline UI, design panel, gesture recording#1311

Merged
miguel-heygen merged 8 commits into
mainfrom
refactor/keyframe-stack-u10
Jun 9, 2026
Merged

feat(studio): keyframe system — parser, runtime, timeline UI, design panel, gesture recording#1311
miguel-heygen merged 8 commits into
mainfrom
refactor/keyframe-stack-u10

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

Combined keyframe system stack (U2-U10). Includes:

  • GSAP parser — arc path mutations + keyframe CRUD
  • Runtime hooks — global time compiler + keyframe runtime
  • Keyframe cache + commit hooks
  • Timeline UI — dopesheet diamonds + keyboard nav
  • Design panel — arc controls + ease curve + stagger
  • Gesture recording core (RAF sampling, RDP simplification)
  • Bug bash — 21 drag/recording/cache fixes
  • Integration wiring + docs

Feature-gated behind STUDIO_KEYFRAMES_ENABLED (default false).

Add the runtime bridge layer: global time compilation (tween % → clip %),
soft reload after mutations, runtime keyframe preview, and keyframe
commit helper.
Add the runtime bridge layer: global time compilation (tween % → clip %),
soft reload after mutations, runtime keyframe preview, and keyframe
commit helper.
Add hooks for keyframe cache population (tween → clip-relative %),
mutation dispatch, keyframe snapping, and audio beat detection.
Add dopesheet strip with diamond keyframe indicators, timeline property
rows, keyboard navigation (J/Shift+J/Delete/K), and feature gate
(STUDIO_KEYFRAMES_ENABLED defaults to false).
Add arc path controls (curviness slider, auto-rotate), motion path SVG
overlay, ease curve visualization, stagger controls, and expanded
animation card. Includes border-radius editor dependency from #1217.
Add gesture recording engine with RAF sampling, modifier key property
mapping (Shift→rotationXY, Alt→rotation, Cmd→opacity),
Ramer-Douglas-Peucker simplification, and ghost trail SVG overlay.
21 fixes: capture GSAP base at drag start, translate:none before
gsap.set, skip reapplyPathOffsets for GSAP elements, clamp recording
seek, _auto flag for 100% keyframes, overlay flash fix, block edits
during recording.
Wire App.tsx recording orchestration, TimelineToolbar K/R buttons,
PropertyPanel per-property diamonds, shortcuts panel, toast
notifications, and keyframes guide documentation. All gated on
STUDIO_KEYFRAMES_ENABLED (default false).
@miguel-heygen miguel-heygen merged commit a468550 into main Jun 9, 2026
13 of 15 checks passed
@miguel-heygen miguel-heygen deleted the refactor/keyframe-stack-u10 branch June 9, 2026 22:30
@mintlify

mintlify Bot commented Jun 9, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
hyperframes 🔴 Failed Jun 9, 2026, 10:32 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

jrusso1020 added a commit that referenced this pull request Jun 10, 2026
…est (#1311 follow-up)

The 'hydrates seek first, preserves the initial url state, then restores
selection' test was failing because PR #1311 (keyframes feat) changed
useStudioUrlState to read currentTime from the player store via
usePlayerStore((s) => s.currentTime), removing it from the hook's prop
shape. The test was still trying to drive currentTime via the harness
prop, which is now a no-op — so the selection-hydration useEffect's
time-stability guard

  Math.abs(currentTime - stableTimeRef.current!) > 0.05

never passed (store currentTime stayed at 0 while stableTimeRef caught
the 4.2 seek target). buildDomSelectionFromTarget was never reached,
applyDomSelection was never called, and the assertion got 0 calls.

Fix: setState the store's currentTime to 4.2 ahead of the rerender so
the hook's selector picks it up and the time-stability guard passes.
Harness prop kept as-is — it's a no-op but doesn't hurt.

Pre-existing failure on main HEAD 81416ab; surfaced as CI gate on the
unrelated docs/readme-hero-motion-update PR.
jrusso1020 added a commit that referenced this pull request Jun 10, 2026
* docs(readme): swap hero media to hyperframes-logo-motion

Replaces the prior hfgif-1280.webp hero with a new logo-motion clip
Bin trimmed for the launch. Converted the source MP4 to animated webp
(the existing hero's format) so it auto-plays in the GitHub README the
same way the old one did - MP4 sources don't render inline or autoplay
in <img> tags.

- New asset: static.heygen.ai/hyperframes-oss/docs/images/
  hyperframes-logo-motion-1280.webp (1280x720, 85 frames, 199KB)
- ffmpeg conversion: scale=1280, libwebp_anim, q=80, loop=0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(studio): format 5 hooks files (oxfmt)

* style: remove unused imports in studio hooks (pre-existing lint failures)

CI Lint on main was already failing with 5 unused-import errors in
packages/studio/src/hooks/. Removed the unused symbols to unblock the
README hero PR's CI:

- gsapRuntimeBridge.ts: resolveTweenStart, resolveTweenDuration
- useGsapScriptCommits.ts: usePlayerStore
- useTimelineEditing.ts: PatchTarget (type-only)
- gsapDragCommit.ts: readGsapProperty

Bundled into the README PR per James's request to fix CI in-place.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(studio): add childRects: [] to DomEditOverlay test mock

useDomEditOverlayRects' return type added a childRects: OverlayRect[]
field; the DomEditOverlay test's mock didn't get updated and was
returning an object without it, so DomEditOverlay.tsx's
'childRects.length > 0' check threw TypeError on undefined.

One-line mock-vs-hook contract realignment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(studio): drive player-store currentTime in selection-hydration test (#1311 follow-up)

The 'hydrates seek first, preserves the initial url state, then restores
selection' test was failing because PR #1311 (keyframes feat) changed
useStudioUrlState to read currentTime from the player store via
usePlayerStore((s) => s.currentTime), removing it from the hook's prop
shape. The test was still trying to drive currentTime via the harness
prop, which is now a no-op — so the selection-hydration useEffect's
time-stability guard

  Math.abs(currentTime - stableTimeRef.current!) > 0.05

never passed (store currentTime stayed at 0 while stableTimeRef caught
the 4.2 seek target). buildDomSelectionFromTarget was never reached,
applyDomSelection was never called, and the assertion got 0 calls.

Fix: setState the store's currentTime to 4.2 ahead of the rerender so
the hook's selector picks it up and the time-stability guard passes.
Harness prop kept as-is — it's a no-op but doesn't hurt.

Pre-existing failure on main HEAD 81416ab; surfaced as CI gate on the
unrelated docs/readme-hero-motion-update PR.

* test(studio): stub getBoundingClientRect + flush RAF in DomEditOverlay test

The 'renders selected bounds right after clicking a movable selection'
test asserts the selection box appears after pointerdown, but happy-dom
returns 0 for newly-created elements' getBoundingClientRect. The
overlay's compRect updates via a RAF loop that early-returns when iframe
width is 0; the keyframes PR a468550 added a compRect.width > 0 guard
to the selection-box render path, so compRect=0 silently gates the box
off and the assertion fails.

Stub Element.prototype.getBoundingClientRect to return 800x450 for the
test, and flush two RAFs after render so the compRect state update lands
before the pointerdown assertion. Restore the prototype at test end.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Miguel Sierra <miguel.sierra@heygen.com>
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