fix(studio): freeze timeline zoom during extend-drag so clips don't jump

Dragging a clip past the video end fed back on itself: the growing preview
length shrank the fit-to-width zoom, which remapped the pointer, moved the
clip, and grew the length again (visible jumping). Split the committed
basis duration (drives zoom) from the displayed effective duration (adds
the live preview for ruler + width). Zoom holds fixed through the gesture
and the extra length scrolls; it re-fits once on drop. Duration math
extracted to pure tested helpers in timelineLayout.
This commit is contained in:
Miguel Angel Simon Sierra
2026-07-08 23:46:28 -04:00
parent d7e7fca8ec
commit 580676bbf4
3 changed files with 89 additions and 16 deletions
@@ -0,0 +1,34 @@
import { describe, expect, it } from "vitest";
import { computeTimelineBasisDuration, computeTimelineEffectiveDuration } from "./timelineLayout";
describe("computeTimelineBasisDuration", () => {
it("uses the root duration when it exceeds every clip end", () => {
expect(computeTimelineBasisDuration(12, [4, 6, 9])).toBe(12);
});
it("grows to the furthest committed clip end past the root duration", () => {
expect(computeTimelineBasisDuration(12, [4, 18, 9])).toBe(18);
});
it("falls back to the root duration with no clips / non-finite ends", () => {
expect(computeTimelineBasisDuration(10, [])).toBe(10);
expect(computeTimelineBasisDuration(Number.NaN, [])).toBe(0);
});
});
describe("computeTimelineEffectiveDuration", () => {
it("returns the basis when there is no active preview", () => {
expect(computeTimelineEffectiveDuration(12, [null, null])).toBe(12);
});
it("extends to a drag/resize preview end beyond the basis", () => {
expect(computeTimelineEffectiveDuration(12, [20, null])).toBe(20);
expect(computeTimelineEffectiveDuration(12, [null, 16])).toBe(16);
});
it("never shrinks below the basis for a preview inside the current length", () => {
// The invariant behind the jump fix: the basis (which drives zoom) is
// independent of the preview, and a smaller preview end can't reduce it.
expect(computeTimelineEffectiveDuration(12, [8])).toBe(12);
});
});