fix(studio): remove keyframe dragging from the timeline#1763
Conversation
cfbb217 to
e6bd9e4
Compare
9df753a to
2f9ece7
Compare
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Reviewed at 2f9ece73. Clean -448 LOC deletion of the timeline keyframe-drag affordance — diamonds still render, click-to-seek, and context-menu (add / remove / ease) all preserved. Caller-graph audit shows zero external consumers in hyperframes-internal or hyperframes-gemini-agent; the in-repo orphan is small. PR body matches behavior.
🟡 Concerns
-
snapKeyframePctToBeatis now dead code.packages/studio/src/player/components/timelineEditing.ts:120— still exported, zero callers in the repo after this PR (the only call site was theonSnapKeyframePctbinding inTimeline.tsxthat's deleted in this diff). Either delete the function + its unit tests, or keep it but at least drop the export. Same cleanup spirit as removing thekeyframeMovemodule — leftover export is the kind of thing that ages into "why does this exist?" two quarters from now. Not a blocker; trivial follow-up. -
No assertion that the diamonds still respond to click in
Timeline.test.ts. The PR drops theonPointerDownhandler that was the diamond's only event-listener for drag, leavingonClick+onContextMenu. The PR body claims "click-to-seek, and offer the context menu" still work, and the code looks right, but there's no test pinning this — a future "diamonds are pointer-events: none under some condition" refactor would silently regress click-to-seek and CI wouldn't catch it. Cheap addition.
🟢 Questions
- Selection-on-drag-start side effect.
onPickKeyframeElementpreviously selected the element on drag-start, loading its GSAP session as a side effect. Worth confirming no other interaction path (keyboard K toggle, context-menuAdd/Remove) was depending on that selection happening implicitly via a diamond grab. Tests pass + the K-toggle flow goes through its own selection path, so I think this is moot — but tagging in case there's an edge case.
🔵 Nits
-
The commit message + PR body are unusually good — they correctly document the why (intermittent no-op/revert from session lag + the optimistic-hold workaround) which justifies the
fix:prefix on a pure deletion. No action. -
Consider a
CHANGELOG.md/docs/keyboard-shortcuts.mdmention if either exists for the studio — users who learned the drag will look for it. Skip if those docs don't exist.
Verdict
LGTM modulo the dead-export cleanup. No blockers. CI green on all required checks. Happy to stamp once the orphan is addressed (or deferred to a follow-up PR you can name in-thread).
— Rames D Jusso
vanceingalls
left a comment
There was a problem hiding this comment.
Verdict: LGTM with one cleanup nit. Pure removal, surgically clean: gut'd render path still works for the basic display-only case, no cross-package consumers, no orphans in TimelineEditContext / timelineCallbacks / public surface. CI green (player-perf, regression, preview-regression all pass). HEAD 2f9ece73, no prior reviewers.
Findings
🟡 Orphan helper left behind: snapKeyframePctToBeat
packages/studio/src/player/components/timelineEditing.ts:120 still exports snapKeyframePctToBeat. This PR deletes the import in Timeline.tsx (-import { snapKeyframePctToBeat } from "./timelineEditing";) and its single call site (onSnapKeyframePct wiring around the old line 488). I scanned all 430 .ts/.tsx files under packages/studio/src at HEAD 2f9ece73, plus packages/player, packages/sdk, packages/core, packages/producer — the function has zero consumers post-PR. Not a pre-existing orphan: on main and on base eg-05-3d-keyframe-fixes, Timeline.tsx imports and calls it.
Drop the function definition (lines ~113-145 in timelineEditing.ts) in this PR or queue a one-line cleanup. Since the whole point of this stack is removing keyframe drag, leaving the snap helper alive only confuses the next reader who searches "keyframe" in the player.
💭 (Verified clean — for the record)
TimelineEditContextresidue:value.onMoveKeyframecorrectly dropped from the memoization dep array (line 42 in the diff); the surviving deps match the survivingTimelineEditCallbacksfields intimelineCallbacks.ts. No orphan dep.TimelineClipDiamondsgutted path: the +4/-125 leaves a clean display-only render —effPctcollapse tokf.percentageis correct sincedragstate is gone; the connecting-linesx1/x2calc is the same arithmetic minus the drag indirection. No danglinguseEffect/ refs / cleanup.onMoveKeyframecallback type: removed from theTimelineEditCallbacksinterface attimelineCallbacks.ts:42, and theTimeline.tsxdestructure at line 86 drops it cleanly. No external consumers —gh search code(which indexes live HEAD lazily) and a brute-force scan of all 430 studio TS files at the PR SHA both confirm zero external references toonMoveKeyframe,onDragKeyframe,onPickForDrag,onPickKeyframeElement,pickKeyframeTween,computeKeyframeMovePlan,KeyframeMovePlan.- Public surface (
packages/studio/src/index.ts): never re-exportedkeyframeMoveor its symbols — safe. - Sub-comp timeline expansion (#1761): the expansion already merges into the base of this stack and renders via the same
TimelineClipDiamonds— the gut'd display path covers expanded children automatically; no second drag-path to also remove. - Stack context: #1764 (next on top) is about ungrouping, #1765 is telemetry. Neither references the dropped symbols. The cleanup nit above doesn't block the stack.
- "Decorative gate" check (per my memory on dangling state): no surviving
isDragging/dragStateset-without-unset. ThedragRef/pendingRef/dragCleanupRefare all deleted as a block.
CI
All required: regression, preview-regression, player-perf, Preview parity, Preflight (lint + format) — SUCCESS. Graphite / mergeability_check in progress (cosmetic).
Review by Via
2f9ece7 to
c2f6b7d
Compare
e6bd9e4 to
7771520
Compare
c2f6b7d to
c5a984d
Compare
5bf3620 to
be4cfa4
Compare
c5a984d to
a3f0c7c
Compare
vanceingalls
left a comment
There was a problem hiding this comment.
Verdict: COMMENTED — both R1 nits remain unaddressed, but neither is blocking.
R2 HEAD a3f0c7c4 is a rebase-only refresh (single commit, identical PR delta +4/-448, base advanced from e6bd9e44 → be4cfa43 on eg-05-3d-keyframe-fixes). No new commit was added to address either R1 nit, and Timeline.test.ts was not touched in the rebased delta.
Per-finding status
- Via R1 —
snapKeyframePctToBeatorphan atpackages/studio/src/player/components/timelineEditing.ts:120: NOT addressed ata3f0c7c4. The exported helper is still present, and its only prod call site (Timeline.tsx) still has the import removed by this PR. No new references show up inTimeline.tsxat HEAD. Confirmed unchanged in the rebase delta. Still a NIT — happy to land as-is or in a follow-up. - Rames R1 — click-to-seek test gap on
TimelineClipDiamonds: NOT addressed ata3f0c7c4.Timeline.test.tshas nodiamond/keyframe/seekcoverage at HEAD and was not modified in the rebase delta. Still a NIT — the surviving click-to-seek behavior is asserted only via the manual QA path described in the PR body.
CI
All required checks pass at a3f0c7c4 (Preflight, regression, player-perf, Preview parity, preview-regression). Graphite / mergeability_check pending, non-blocking. WIP green.
Prior reviewer state
- james-russo-rames-d-jusso: COMMENTED at
2f9ece73— click-to-seek test gap NIT. - vanceingalls (Via): COMMENTED at
2f9ece73—snapKeyframePctToBeatorphan NIT. - No R2 reviews yet from either reviewer.
LGTM to land as-is at author's discretion; both nits remain landable as follow-ups.
R2 by Via
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
R2 verdict: COMMENTED — both R1 nits unaddressed; PR is rebase-only refresh.
Reviewed at a3f0c7c49817189b02722386df2cdbe72e4a2782. New HEAD is a rebase of the same +4/-448 delta — no new commits added to address R1 nits. Confirmed against Via's R2 (which converged independently 30s before mine).
Per-finding status
| # | R1 source | Finding | Status at a3f0c7c4 |
|---|---|---|---|
| 1 | Via 🟡 | snapKeyframePctToBeat orphan at packages/studio/src/player/components/timelineEditing.ts:120 |
NOT addressed. Still exported with zero consumers across packages/studio, packages/player, packages/sdk, packages/core, packages/producer (re-confirmed at HEAD via grep -rn). No tests touch it either. Trivial cleanup; can land as follow-up. |
| 2 | Rames 🟡 | No click-to-seek / context-menu pin on TimelineClipDiamonds |
NOT addressed. Timeline.test.ts at HEAD has no diamond / keyframe / onClickKeyframe coverage. Rebase delta didn't touch test files. Surviving paths still asserted only via manual QA per PR body. |
| 3 | Rames 💭 | Implicit selection-on-drag-start side effect | N/A — moot. Confirmed at HEAD: surviving onClickKeyframe at Timeline.tsx:468-478 explicitly calls setSelectedElementId(elKey) + onSelectElement?.(el) — selection is now explicit, not implicit-via-drag. Closing this one cleanly. |
R2-NEW scan
Nothing new to flag at a3f0c7c4. Caller-graph for the removed symbols (onMoveKeyframe, onDragKeyframe, onPickForDrag, pickKeyframeTween, computeKeyframeMovePlan, KeyframeMovePlan) is empty across the monorepo. TimelineEditContext memo deps + timelineCallbacks.ts interface drop are clean. No useEffect / useRef survivors from the dragRef family. Public surface (packages/studio/src/index.ts) never re-exported anything from this module — safe.
Convergence with Via
Via's R2 at a3f0c7c4 (4 min before mine) reaches identical conclusions: both nits unaddressed, both non-blocking. No daylight between our findings. I won't re-litigate the snap-helper cleanup — happy to land as-is at the author's discretion or as a one-line follow-up.
CI
Required checks green at a3f0c7c4: regression, preview-regression, Preview parity, player-perf, Preflight. Graphite / mergeability_check in_progress (non-blocking).
Net
LGTM to land. Both nits are landable as follow-ups; neither is blocking. R1 verdict stands.
R2 verification by Rames D Jusso
be4cfa4 to
acc5df1
Compare
a3f0c7c to
072e6f7
Compare
vanceingalls
left a comment
There was a problem hiding this comment.
Verdict: COMMENTED — LGTM-sustained. R3 is a pure rebase; both R1/R2 nits unchanged, neither blocking.
R3 HEAD 072e6f78 is a rebase-only refresh — single commit, identical message, base of eg-05-3d-keyframe-fixes advanced from be4cfa43 → acc5df1d. PR delta unchanged at +4/-448 across the same 8 files. All 8 PR-affected files are byte-identical at a3f0c7c4 vs 072e6f78 (sha256-verified per file). The R2→R3 three-dot compare diff shows additional files only because the base branch itself moved forward; none of those changes are part of this PR's surface.
Per-finding status
- Via R1/R2 —
snapKeyframePctToBeatorphan atpackages/studio/src/player/components/timelineEditing.ts:120: NOT addressed at072e6f78. Exported helper still present; verified via raw-content fetch at the new HEAD. Still a NIT — landable as-is or in a follow-up. - Rames R1/R2 — click-to-seek test gap on
TimelineClipDiamonds: NOT addressed at072e6f78.Timeline.test.tsstill has nodiamond/keyframe/seekcoverage. Surviving click-to-seek behavior remains asserted only via the manual QA path in the PR body. Still a NIT.
CI
At 072e6f78: 13 success, 6 skipped, 6 cancelled (superseded), 1 in-flight (Graphite / mergeability_check, non-blocking). Zero failures. mergeable_state: unstable is from the pending Graphite check; required checks all green.
Prior reviewer state
- james-russo-rames-d-jusso: COMMENTED at
2f9ece73(R1) +a3f0c7c4(R2) — click-to-seek test gap NIT. No R3 review yet. - vanceingalls (Via): COMMENTED at
2f9ece73(R1) +a3f0c7c4(R2) —snapKeyframePctToBeatorphan NIT.
LGTM-sustained to land at author's discretion. Both nits remain landable as follow-ups.
R3 by Via
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
R3 verdict: LGTM with NITs (sustained). Pure rebase — neither R1/R2 nit addressed.
Reviewed at HEAD 072e6f78. R2 SHA a3f0c7c4 → R3 SHA 072e6f78 is a Graphite restack from the parent stack: base eg-05-3d-keyframe-fixes advanced (be4cfa43 → acc5df1d) and the single commit rehashed. PR's incremental diff vs base is unchanged at +4/-448 across the same 8 files. The R2..R3 three-dot diff shows additional domEditingLayers.ts/useDomSelection.ts deletions, but those are base-branch deltas being absorbed into the merge-base, not this PR's surface — confirmed via git diff acc5df1d..072e6f78 showing zero changes to the cited files.
Convergent with Via's R3 posted ~10min ago.
Per-finding status at HEAD
| Finding | R1 | R2 | R3 |
|---|---|---|---|
🟡 snapKeyframePctToBeat orphan at packages/studio/src/player/components/timelineEditing.ts:120 |
Open | Open | Still open. Function still exported; grep -rn snapKeyframePctToBeat returns only the definition line across all packages. Originally orphaned at PR base acc5df1d, but this PR's stated purpose ("remove keyframe dragging") is the natural home for the cleanup — the helper exists only to support keyframe-drag beat-snap. |
🟡 No test pin on surviving click-to-seek path at Timeline.tsx:468-478 |
Open | Open | Still open. Timeline.test.ts (362 LOC) has zero matches for onClickKeyframe / keyframe / seek / click-to-seek. Surviving behavior remains asserted only via manual QA per PR body. |
| 💭 Implicit selection side-effect on click-to-seek | Closed at R2 | n/a | n/a (closed clean — setSelectedElementId called explicitly at Timeline.tsx:471). |
R3-NEW: none
Reviewed Timeline.tsx / TimelineClipDiamonds.tsx / timelineCallbacks.ts / keyframeMove.ts (deletion) / StudioPreviewArea.tsx (deletion) at HEAD. No new findings. Mechanical deletion-only PR; the only delta vs R2 is base-branch absorption.
CI
All required checks success at 072e6f78 (Preflight, regression, preview-regression, Preview parity, player-perf, WIP). Graphite / mergeability_check in_progress, non-blocking.
Net
Same verdict as R1/R2: LGTM with NITs, neither blocking. Both nits remain landable as follow-ups (the orphan cleanup belongs here morally, but base-branch hoist of the keyframe-drag removal makes it defensible to land separately). Ship at author's discretion.
R3 verification by Rames D Jusso
acc5df1 to
8ebf1dc
Compare
072e6f7 to
7af2bdc
Compare
Dragging timeline keyframe diamonds was unreliable — clip<->tween percentage remapping, an optimistic-hold workaround, and an intermittent no-op/revert when the GSAP session lagged the drag (its own comments document the flakiness). Remove the drag interaction entirely: diamonds still display, click-to-seek, and offer the context menu (add/remove/ease) — keyframe timing is edited via the playhead + panel, which are deterministic. Deletes the keyframe-move plan module + its wiring through TimelineClipDiamonds -> TimelineCanvas -> Timeline -> TimelineEditContext.
7af2bdc to
3b1d095
Compare

Remove keyframe dragging from the timeline (6/8)
Deletes the timeline keyframe-drag affordance and its supporting code (−448 LOC). Pure removal; timeline still renders and selects normally.