feat(studio): keyframe system — parser, runtime, timeline UI, design panel, gesture recording#1311
Merged
Merged
Conversation
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).
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 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>
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.
Combined keyframe system stack (U2-U10). Includes:
Feature-gated behind STUDIO_KEYFRAMES_ENABLED (default false).