Skip to content

feat(studio): marquee selection, AE presets, per-keyframe ease + velocity fitting#1663

Closed
miguel-heygen wants to merge 1 commit into
mainfrom
feat/marquee-selection
Closed

feat(studio): marquee selection, AE presets, per-keyframe ease + velocity fitting#1663
miguel-heygen wants to merge 1 commit into
mainfrom
feat/marquee-selection

Conversation

@miguel-heygen

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

Copy link
Copy Markdown
Collaborator

Summary

  • Marquee multi-selection: click+drag on empty canvas draws a dashed selection rectangle; all intersecting elements are group-selected via SAT OBB intersection (handles rotated/scaled/skewed elements). Shift+marquee adds to selection. Click-on-empty deselects.
  • Per-keyframe easing: each keyframe segment has its own ease, editable via an expandable bezier curve editor per segment in the Animation panel. Parser preserves per-keyframe ease through round-trips.
  • AE Easy Ease presets: After Effects' correct bezier values (0.333, 0, 0.667, 1) in the preset grid — softer deceleration than GSAP's power2.inOut.
  • Velocity-based curve fitting: gesture recordings analyze the velocity profile and assign per-keyframe eases automatically — decelerating segments get Easy Ease In, accelerating segments get Easy Ease Out, constant speed stays linear.

Architecture

  • marqueeGeometry.ts — SAT intersection + OBB corner computation via DOMMatrix
  • marqueeCommit.tsuseMarqueeGestures hook (pointer lifecycle, capture, cancel)
  • KeyframeEaseList.tsx — per-keyframe ease rows with expandable EaseCurveSection
  • velocityEaseFitter.ts — velocity analysis + AE-correct ease assignment
  • Parser: ease-only keyframe update preserves existing properties

Test plan

  • Click+drag on empty canvas draws dashed teal rectangle, selects intersecting elements
  • Shift+marquee adds to existing selection
  • Click on empty deselects
  • Rotated elements: only selected when visual OBB intersects
  • Record a gesture → Animation panel shows per-keyframe ease rows
  • Click a segment row → expands bezier curve editor for that segment
  • Change ease on one segment → only that segment updates, others unchanged
  • Record gesture with deliberate slowdown at end → last segment gets Easy Ease In automatically
  • AE Easy Ease preset in grid with correct curve shape
  • 16 geometry + 4 velocity fitter unit tests pass

@miguel-heygen miguel-heygen force-pushed the feat/marquee-selection branch 2 times, most recently from afdfdc6 to b5d05c4 Compare June 23, 2026 16:24
@miguel-heygen miguel-heygen changed the title feat(studio): add marquee multi-selection to preview canvas feat(studio): marquee multi-selection + AE Easy Ease presets Jun 23, 2026
@miguel-heygen miguel-heygen force-pushed the feat/marquee-selection branch from b5d05c4 to 35bf415 Compare June 23, 2026 17:34
@miguel-heygen miguel-heygen changed the title feat(studio): marquee multi-selection + AE Easy Ease presets feat(studio): marquee selection, AE presets, per-keyframe ease + velocity fitting Jun 23, 2026
@miguel-heygen miguel-heygen force-pushed the feat/marquee-selection branch from 35bf415 to 5c52c3a Compare June 23, 2026 19:17
@mintlify

mintlify Bot commented Jun 23, 2026

Copy link
Copy Markdown

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

Project Status Preview Updated (UTC)
hyperframes 🟢 Ready View Preview Jun 23, 2026, 7:48 PM

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

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown

Fallow audit report

Found 104 findings.

Dead code (6)
Severity Rule Location Description
major fallow/unused-export packages/studio/src/components/editor/AnimationCardParts.tsx:35 Export 'adjustedValue' is never imported by other modules
major fallow/unused-export packages/studio/src/components/editor/AnimationCardParts.tsx:44 Export 'RemoveButton' is never imported by other modules
major fallow/unused-export packages/studio/src/components/editor/propertyPanelHelpers.ts:214 Export 'EMPTY_FILTER_VALUE' is never imported by other modules
major fallow/unused-export packages/studio/src/components/editor/propertyPanelHelpers.ts:215 Export 'BOX_SHADOW_PRESETS' is never imported by other modules
major fallow/unused-export packages/studio/src/components/editor/propertyPanelHelpers.ts:275 Export 'clampPanelNumber' is never imported by other modules
major fallow/unused-type packages/studio/src/hooks/useBlockCatalog.ts:9 Type export 'CatalogItem' is never imported by other modules
Duplication (47)
Severity Rule Location Description
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:625 Code clone group 1 (7 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:740 Code clone group 1 (7 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:1342 Code clone group 2 (10 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:1370 Code clone group 2 (10 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:1465 Code clone group 3 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2120 Code clone group 4 (27 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2155 Code clone group 5 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2189 Code clone group 4 (27 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2217 Code clone group 5 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2407 Code clone group 6 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2569 Code clone group 7 (13 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2570 Code clone group 8 (10 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2612 Code clone group 7 (13 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2613 Code clone group 8 (10 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2640 Code clone group 8 (10 lines, 3 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2659 Code clone group 9 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2686 Code clone group 9 (9 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapParser.ts:2744 Code clone group 3 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/parsers/gsapSerialize.ts:578 Code clone group 6 (8 lines, 2 instances)
minor fallow/code-duplication packages/core/src/runtime/captionOverrides.ts:13 Code clone group 10 (16 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/captions/generator.ts:264 Code clone group 11 (23 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/captions/hooks/useCaptionSync.ts:6 Code clone group 10 (16 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/captions/hooks/useCaptionSync.ts:27 Code clone group 11 (23 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/components/editor/manualEditsDom.ts:168 Code clone group 12 (7 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/components/editor/propertyPanelHelpers.ts:323 Code clone group 12 (7 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/gsapDragCommit.ts:356 Code clone group 13 (15 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/gsapDragCommit.ts:371 Code clone group 14 (13 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/gsapDragPositionCommit.ts:176 Code clone group 15 (11 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/gsapDragPositionCommit.ts:204 Code clone group 15 (11 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/gsapDragPositionCommit.ts:234 Code clone group 13 (15 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/gsapDragPositionCommit.ts:243 Code clone group 14 (13 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/gsapRuntimeBridge.ts:361 Code clone group 16 (6 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/gsapRuntimeBridge.ts:513 Code clone group 16 (6 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useDomEditTextCommits.ts:257 Code clone group 17 (9 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/hooks/useDomEditTextCommits.ts:314 Code clone group 17 (9 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/player/lib/timelineDOM.ts:259 Code clone group 18 (8 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/player/lib/timelineIframeHelpers.ts:350 Code clone group 18 (8 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/utils/sdkCutover.ts:520 Code clone group 19 (23 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/utils/sdkCutover.ts:544 Code clone group 19 (23 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/utils/sourcePatcher.ts:160 Code clone group 20 (9 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/utils/sourcePatcher.ts:327 Code clone group 21 (8 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/utils/sourcePatcher.ts:351 Code clone group 22 (8 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/utils/sourcePatcher.ts:353 Code clone group 20 (9 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/utils/sourcePatcher.ts:362 Code clone group 21 (8 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/utils/sourcePatcher.ts:366 Code clone group 23 (9 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/utils/sourcePatcher.ts:468 Code clone group 23 (9 lines, 2 instances)
minor fallow/code-duplication packages/studio/src/utils/sourcePatcher.ts:484 Code clone group 22 (8 lines, 2 instances)
Health (50)
Severity Rule Location Description
major fallow/high-complexity packages/core/src/runtime/media.ts:29 'refreshRuntimeMediaCache' has cyclomatic complexity 27 (threshold: 20) and cognitive complexity 30 (threshold: 15)
critical fallow/high-crap-score packages/studio/src/captions/hooks/useCaptionSync.ts:21 'buildOverrides' has CRAP score 306.0 (threshold: 30.0, cyclomatic 17)
minor fallow/high-crap-score packages/studio/src/captions/hooks/useCaptionSync.ts:69 'save' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
minor fallow/high-crap-score packages/studio/src/captions/hooks/useCaptionSync.ts:88 'unsub' has CRAP score 42.0 (threshold: 30.0, cyclomatic 6)
critical fallow/high-crap-score packages/studio/src/captions/hooks/useCaptionSync.ts:108 'loadOverrides' has CRAP score 812.0 (threshold: 30.0, cyclomatic 28)
major fallow/high-crap-score packages/studio/src/components/editor/BlockParamsPanel.tsx:75 'ParamControl' has CRAP score 90.0 (threshold: 30.0, cyclomatic 9)
minor fallow/high-crap-score packages/studio/src/components/editor/DomEditOverlay.tsx:103 'selectionShapeStyles' has CRAP score 49.5 (threshold: 30.0, cyclomatic 13)
major fallow/high-crap-score packages/studio/src/components/editor/DomEditOverlay.tsx:230 '<arrow>' has CRAP score 63.6 (threshold: 30.0, cyclomatic 15)
major fallow/high-crap-score packages/studio/src/components/editor/PropertyPanel.tsx:250 '<arrow>' has CRAP score 97.0 (threshold: 30.0, cyclomatic 19)
minor fallow/high-cognitive-complexity packages/studio/src/components/editor/manualOffsetDrag.ts:299 'createManualOffsetDragMember' has cognitive complexity 17 (threshold: 15)
minor fallow/high-cognitive-complexity packages/studio/src/components/editor/propertyPanelHelpers.ts:488 'readGsapRuntimeValuesForPanel' has cognitive complexity 24 (threshold: 15)
major fallow/high-crap-score packages/studio/src/hooks/gsapDragPositionCommit.ts:103 'resolveDragRuntime' has CRAP score 56.3 (threshold: 30.0, cyclomatic 14)
minor fallow/high-crap-score packages/studio/src/hooks/gsapRuntimeBridge.ts:76 '<arrow>' has CRAP score 37.1 (threshold: 30.0, cyclomatic 11)
minor fallow/high-crap-score packages/studio/src/hooks/gsapRuntimeBridge.ts:109 '<arrow>' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
minor fallow/high-crap-score packages/studio/src/hooks/gsapRuntimeBridge.ts:137 'resolveGroupTween' has CRAP score 31.6 (threshold: 30.0, cyclomatic 10)
critical fallow/high-crap-score packages/studio/src/hooks/gsapRuntimeBridge.ts:299 'tryGsapResizeIntercept' has CRAP score 46.7 (threshold: 30.0, cyclomatic 41)
minor fallow/high-cognitive-complexity packages/studio/src/hooks/gsapRuntimeBridge.ts:461 'tryGsapRotationIntercept' has cognitive complexity 19 (threshold: 15)
critical fallow/high-crap-score packages/studio/src/hooks/gsapRuntimeReaders.ts:50 'readAllAnimatedProperties' has CRAP score 50.5 (threshold: 30.0, cyclomatic 44)
major fallow/high-crap-score packages/studio/src/hooks/useDomEditTextCommits.ts:88 'handleDomStyleCommit' has CRAP score 71.3 (threshold: 30.0, cyclomatic 16)
major fallow/high-crap-score packages/studio/src/hooks/useDomEditTextCommits.ts:239 'handleDomTextCommit' has CRAP score 63.6 (threshold: 30.0, cyclomatic 15)
major fallow/high-crap-score packages/studio/src/hooks/useDomEditTextCommits.ts:301 'commitDomTextFields' has CRAP score 71.3 (threshold: 30.0, cyclomatic 16)
critical fallow/high-crap-score packages/studio/src/hooks/useFrameCapture.ts:31 'handleCaptureFrameClick' has CRAP score 132.0 (threshold: 30.0, cyclomatic 11)
major fallow/high-crap-score packages/studio/src/hooks/useGestureCommit.ts:33 'partitionKeyframesByGroup' has CRAP score 56.0 (threshold: 30.0, cyclomatic 7)
minor fallow/high-crap-score packages/studio/src/hooks/useGestureCommit.ts:143 'simplified' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
critical fallow/high-crap-score packages/studio/src/hooks/useGestureCommit.ts:288 'handleToggleRecording' has CRAP score 210.0 (threshold: 30.0, cyclomatic 14)
critical fallow/high-crap-score packages/studio/src/hooks/useGestureRecording.ts:55 'readBasePosition' has CRAP score 156.0 (threshold: 30.0, cyclomatic 12)
critical fallow/high-crap-score packages/studio/src/hooks/useGestureRecording.ts:90 'connectGsapRuntime' has CRAP score 210.0 (threshold: 30.0, cyclomatic 14)
minor fallow/high-crap-score packages/studio/src/hooks/useGestureRecording.ts:156 'computeIframeScale' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
major fallow/high-crap-score packages/studio/src/hooks/useGestureRecording.ts:164 'resolveGestureProperties' has CRAP score 56.0 (threshold: 30.0, cyclomatic 7)
minor fallow/high-crap-score packages/studio/src/hooks/useGestureRecording.ts:279 'startRecording' has CRAP score 30.0 (threshold: 30.0, cyclomatic 5)
critical fallow/high-crap-score packages/studio/src/hooks/useGestureRecording.ts:356 'tick' has CRAP score 156.0 (threshold: 30.0, cyclomatic 12)
critical fallow/high-crap-score packages/studio/src/hooks/useGestureRecording.ts:419 'stopRecording' has CRAP score 182.0 (threshold: 30.0, cyclomatic 13)
minor fallow/high-crap-score packages/studio/src/hooks/useGsapTweenCache.ts:27 'synthesizeFlatTweenKeyframes' has CRAP score 43.1 (threshold: 30.0, cyclomatic 12)
major fallow/high-crap-score packages/studio/src/hooks/useGsapTweenCache.ts:206 'animations' has CRAP score 56.3 (threshold: 30.0, cyclomatic 14)
critical fallow/high-crap-score packages/studio/src/hooks/useGsapTweenCache.ts:273 '<arrow>' has CRAP score 116.3 (threshold: 30.0, cyclomatic 21)
major fallow/high-crap-score packages/studio/src/hooks/useGsapTweenCache.ts:376 '<arrow>' has CRAP score 97.0 (threshold: 30.0, cyclomatic 19)
major fallow/high-crap-score packages/studio/src/hooks/useGsapTweenCache.ts:448 'tryRuntimeScan' has CRAP score 71.3 (threshold: 30.0, cyclomatic 16)
critical fallow/high-crap-score packages/studio/src/hooks/useLintModal.ts:34 'runLint' has CRAP score 182.0 (threshold: 30.0, cyclomatic 13)
critical fallow/high-crap-score packages/studio/src/player/hooks/useTimelinePlayer.ts:130 'getAdapter' has CRAP score 184.5 (threshold: 30.0, cyclomatic 27)
minor fallow/high-crap-score packages/studio/src/player/hooks/useTimelinePlayer.ts:212 'tick' has CRAP score 37.1 (threshold: 30.0, cyclomatic 11)
critical fallow/high-crap-score packages/studio/src/player/hooks/useTimelinePlayer.ts:488 'handleMessage' has CRAP score 172.0 (threshold: 30.0, cyclomatic 26)
major fallow/high-crap-score packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts:60 'processTimelineMessage' has CRAP score 88.0 (threshold: 30.0, cyclomatic 18)
critical fallow/high-crap-score packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts:171 'initializeAdapter' has CRAP score 238.6 (threshold: 30.0, cyclomatic 31)
minor fallow/high-crap-score packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts:283 'onMessage' has CRAP score 31.6 (threshold: 30.0, cyclomatic 10)
minor fallow/high-cognitive-complexity packages/studio/src/player/lib/timelineIframeHelpers.ts:39 'autoHealMissingCompositionIds' has cognitive complexity 23 (threshold: 15)
critical fallow/high-crap-score packages/studio/src/player/lib/timelineIframeHelpers.ts:285 '<arrow>' has CRAP score 659.7 (threshold: 30.0, cyclomatic 53)
major fallow/high-crap-score packages/studio/src/utils/sourcePatcher.ts:32 'splitInlineStyleDeclarations' has CRAP score 56.3 (threshold: 30.0, cyclomatic 14)
major fallow/high-crap-score packages/studio/src/utils/sourcePatcher.ts:107 'resolveSourceFile' has CRAP score 71.3 (threshold: 30.0, cyclomatic 16)
minor fallow/high-crap-score packages/studio/src/utils/sourcePatcher.ts:172 'patchInlineStyleInTag' has CRAP score 31.6 (threshold: 30.0, cyclomatic 10)
minor fallow/high-crap-score packages/studio/src/utils/sourcePatcher.ts:440 'patchHtmlAttributeInTag' has CRAP score 37.1 (threshold: 30.0, cyclomatic 11)
Suppressions (1)
Severity Rule Location Description
minor fallow/stale-suppression packages/studio/src/components/editor/snapTargetCollection.ts:1 // fallow-ignore-file unused-file

Generated by fallow.

…city fitting

Marquee selection: click+drag on empty canvas draws a dashed selection
rectangle. All elements whose OBB intersects are group-selected via
SAT. Shift+marquee adds to selection. Click on empty deselects.

Per-keyframe easing: each keyframe segment has its own ease, editable
via expandable bezier curve editor in the Animation panel. Parser
preserves per-keyframe ease through round-trips (ease-only updates
preserve existing properties).

AE Easy Ease presets: correct After Effects bezier values (0.333, 0,
0.667, 1) in the preset grid.

Velocity-based curve fitting: gesture recordings analyze velocity
profile and assign per-keyframe custom eases automatically.

Gesture smoothing: Gaussian-weighted moving average (from PR #1658)
+ easeEach support for keyframed tweens + fetch-cancellation race
fix in useGsapAnimationsForElement.
@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Superseded by Graphite stack: #1691#1692#1693#1694

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