fix(studio): drop dead keyframe menu wiring and close the R1 review items

The diamond context menu still declared `onChangeEase` and `onCopyProperties`
props, and `TimelineOverlays` still threaded `onChangeKeyframeEase` plus a
`keyframeCache` it never read. Nothing on any timeline branch calls them, so
they are removed along with the `onChangeKeyframeEase` callback implementation.

Also from review:
- `deleteSelectedKeyframes` only falls back to the sole keyframed animation when
  there is exactly one. A collapsed selection key carries no animation id, so
  taking the first of several deleted an arbitrary tween's keyframe.
- The duration-less retime test asserts the real 87.601% instead of
  `expect.any(Number)`, so a wrong timing basis fails it.
- `Timeline` wires the keyframe handlers' `onSelectSegment` through to the
  diamonds; it was built and then dropped, so segment ease selection never fired.
- The flat text section arms auto-focus in state rather than reading and
  clearing a ref during render, which Strict Mode's double render swallowed.
This commit is contained in:
Miguel Angel Simon Sierra
2026-07-28 00:37:31 +02:00
parent eddc5e9224
commit 6b58678d94
10 changed files with 27 additions and 47 deletions
@@ -21,8 +21,6 @@ interface KeyframeDiamondContextMenuProps {
onClose: () => void;
onDelete: (elementId: string, target: TimelineKeyframeTarget) => void;
onDeleteAll: (element: TimelineElement) => void;
onChangeEase?: (elementId: string, percentage: number, ease: string) => void;
onCopyProperties?: (elementId: string, percentage: number) => void;
/** Retime the keyframe to the current playhead, preserving its value + ease. */
onMoveToPlayhead?: (element: TimelineElement, target: TimelineKeyframeTarget) => void;
}
@@ -75,7 +75,6 @@ export const Timeline = memo(function Timeline({
onRazorSplitAll,
onDeleteKeyframe,
onDeleteAllKeyframes,
onChangeKeyframeEase,
onMoveKeyframeToPlayhead,
onMoveKeyframe,
} = useResolvedTimelineEditCallbacks({
@@ -263,7 +262,7 @@ export const Timeline = memo(function Timeline({
const selectedKeyframes = usePlayerStore((s) => s.selectedKeyframes);
const toggleSelectedKeyframe = usePlayerStore((s) => s.toggleSelectedKeyframe);
const { onClickKeyframe, onShiftClickKeyframe, onContextMenuKeyframe } =
const { onClickKeyframe, onSelectSegment, onShiftClickKeyframe, onContextMenuKeyframe } =
useTimelineKeyframeHandlers({
expandedElements,
keyframeCache,
@@ -509,6 +508,7 @@ export const Timeline = memo(function Timeline({
currentTime={currentTime}
beatAnalysis={adjustedBeatAnalysis}
onClickKeyframe={onClickKeyframe}
onSelectSegment={onSelectSegment}
onShiftClickKeyframe={onShiftClickKeyframe}
onMoveKeyframe={onMoveKeyframe}
onContextMenuKeyframe={onContextMenuKeyframe}
@@ -547,9 +547,7 @@ export const Timeline = memo(function Timeline({
setKfContextMenu={setKfContextMenu}
onDeleteKeyframe={onDeleteKeyframe}
onDeleteAllKeyframes={onDeleteAllKeyframes}
onChangeKeyframeEase={onChangeKeyframeEase}
onMoveKeyframeToPlayhead={onMoveKeyframeToPlayhead}
keyframeCache={keyframeCache}
clipContextMenu={clipContextMenu}
setClipContextMenu={setClipContextMenu}
currentTime={currentTime}
@@ -74,6 +74,8 @@ export interface TimelineLaneBaseProps {
currentTime: number;
onClickKeyframe?: (element: TimelineElement, keyframe: TimelineKeyframeTarget) => void;
onShiftClickKeyframe?: (elementId: string, keyframe: TimelineKeyframeTarget) => void;
/** Click on the segment BETWEEN two diamonds: selects it to edit its ease. */
onSelectSegment?: (elementId: string, keyframe: TimelineKeyframeTarget) => void;
onContextMenuKeyframe?: (
e: React.MouseEvent,
elementId: string,
@@ -141,6 +143,7 @@ export function TimelineLanes({
currentTime,
onClickKeyframe,
onShiftClickKeyframe,
onSelectSegment,
onContextMenuKeyframe,
onMoveKeyframe,
onContextMenuClip,
@@ -482,6 +485,7 @@ export function TimelineLanes({
onClickKeyframe?.(previewElement, keyframe)
}
onShiftClickKeyframe={onShiftClickKeyframe}
onSelectSegment={onSelectSegment}
onContextMenuKeyframe={onContextMenuKeyframe}
onMoveKeyframe={onMoveKeyframe}
suppressClickRef={suppressClickRef}
@@ -1,4 +1,4 @@
import type { KeyframeCacheEntry, TimelineElement } from "../store/playerStore";
import type { TimelineElement } from "../store/playerStore";
import type { TimelineTheme } from "./timelineTheme";
import type { TimelineRangeSelection } from "./timelineEditing";
import type { TimelineEditCallbacks } from "./timelineCallbacks";
@@ -38,9 +38,7 @@ interface TimelineOverlaysProps {
setKfContextMenu: (value: KeyframeDiamondContextMenuState | null) => void;
onDeleteKeyframe: TimelineEditCallbacks["onDeleteKeyframe"];
onDeleteAllKeyframes: TimelineEditCallbacks["onDeleteAllKeyframes"];
onChangeKeyframeEase: TimelineEditCallbacks["onChangeKeyframeEase"];
onMoveKeyframeToPlayhead: TimelineEditCallbacks["onMoveKeyframeToPlayhead"];
keyframeCache: Map<string, KeyframeCacheEntry>;
clipContextMenu: ClipContextMenuState | null;
setClipContextMenu: (value: ClipContextMenuState | null) => void;
currentTime: number;
@@ -68,9 +66,7 @@ export function TimelineOverlays({
setKfContextMenu,
onDeleteKeyframe,
onDeleteAllKeyframes,
onChangeKeyframeEase,
onMoveKeyframeToPlayhead,
keyframeCache,
clipContextMenu,
setClipContextMenu,
currentTime,
@@ -108,19 +104,11 @@ export function TimelineOverlays({
onClose={() => setKfContextMenu(null)}
onDelete={(elId, target) => onDeleteKeyframe?.(elId, target)}
onDeleteAll={(elId) => onDeleteAllKeyframes?.(elId)}
onChangeEase={(elId, pct, ease) => onChangeKeyframeEase?.(elId, pct, ease)}
onMoveToPlayhead={
onMoveKeyframeToPlayhead
? (element, target) => onMoveKeyframeToPlayhead(element, target)
: undefined
}
onCopyProperties={(elId, pct) => {
const kfData = keyframeCache.get(elId);
const kf = kfData?.keyframes.find((k) => k.percentage === pct);
if (kf) {
void navigator.clipboard.writeText(JSON.stringify(kf.properties, null, 2));
}
}}
/>
)}
@@ -74,7 +74,6 @@ export interface TimelineEditCallbacks {
onRazorSplitAll?: (splitTime: number) => Promise<void> | void;
onDeleteKeyframe?: (elementId: string, keyframe: TimelineKeyframeTarget) => void;
onDeleteAllKeyframes?: (element: TimelineElement) => void;
onChangeKeyframeEase?: (elementId: string, percentage: number, ease: string) => void;
onMoveKeyframeToPlayhead?: (element: TimelineElement, keyframe: TimelineKeyframeTarget) => void;
/** Drag-to-retime: `keyframe` identifies the dragged keyframe (its percentage
* is clip-relative), `toClipPercentage` is the neighbour-clamped drop. */