From 8f3bbd5f0a5bdb1584d76e8b3f81f3324b7d0503 Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Tue, 2 Jun 2026 20:23:58 +1000 Subject: [PATCH 1/3] fix(zoom): hold connected zoom before handoff --- .../videoPlayback/zoomAnimation.test.ts | 2 +- .../video-editor/videoPlayback/zoomRegionUtils.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/video-editor/videoPlayback/zoomAnimation.test.ts b/src/components/video-editor/videoPlayback/zoomAnimation.test.ts index 472cf5793..f9fd3e959 100644 --- a/src/components/video-editor/videoPlayback/zoomAnimation.test.ts +++ b/src/components/video-editor/videoPlayback/zoomAnimation.test.ts @@ -400,7 +400,7 @@ describe("findDominantRegion", () => { const result = findDominantRegion(regions, 3100, { connectZooms: true }); expect(result.transition).toBeNull(); expect(result.region?.id).toBe("a"); - expect(result.strength).toBeGreaterThan(0); + expect(result.strength).toBe(1); }); it("keeps the incoming region at full strength after a connected handoff", () => { diff --git a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts index 4566455dc..7b01130c3 100644 --- a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts +++ b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts @@ -124,8 +124,18 @@ function getActiveRegion( const activeRegions = regions .map((region) => { const outgoingPair = connectedPairs.find((pair) => pair.currentRegion.id === region.id); - if (outgoingPair && timeMs >= outgoingPair.transitionStart) { - return { region, strength: 0 }; + if (outgoingPair) { + if (timeMs >= outgoingPair.transitionStart) { + return { region, strength: 0 }; + } + + const zoomOutStart = + outgoingPair.currentRegion.endMs - + ZOOM_OUT_EARLY_START_MS + + ZOOM_ANIMATION_LEAD_MS; + if (timeMs >= zoomOutStart) { + return { region, strength: 1 }; + } } const incomingPair = connectedPairs.find((pair) => pair.nextRegion.id === region.id); From bbe821cea87f35a175303feb6be317ef3436b20b Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Tue, 2 Jun 2026 20:25:34 +1000 Subject: [PATCH 2/3] chore(test): drop PR-only zoom assertion --- src/components/video-editor/videoPlayback/zoomAnimation.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/video-editor/videoPlayback/zoomAnimation.test.ts b/src/components/video-editor/videoPlayback/zoomAnimation.test.ts index f9fd3e959..472cf5793 100644 --- a/src/components/video-editor/videoPlayback/zoomAnimation.test.ts +++ b/src/components/video-editor/videoPlayback/zoomAnimation.test.ts @@ -400,7 +400,7 @@ describe("findDominantRegion", () => { const result = findDominantRegion(regions, 3100, { connectZooms: true }); expect(result.transition).toBeNull(); expect(result.region?.id).toBe("a"); - expect(result.strength).toBe(1); + expect(result.strength).toBeGreaterThan(0); }); it("keeps the incoming region at full strength after a connected handoff", () => { From 37533cdd04836d08e17b06071bf0b78282b82f7c Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Tue, 2 Jun 2026 20:40:10 +1000 Subject: [PATCH 3/3] feat(zoom): use camera physics for handoffs --- .../video-editor/videoPlayback/zoomAnimation.test.ts | 10 +++------- .../video-editor/videoPlayback/zoomRegionUtils.ts | 5 ----- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/components/video-editor/videoPlayback/zoomAnimation.test.ts b/src/components/video-editor/videoPlayback/zoomAnimation.test.ts index 472cf5793..b98988591 100644 --- a/src/components/video-editor/videoPlayback/zoomAnimation.test.ts +++ b/src/components/video-editor/videoPlayback/zoomAnimation.test.ts @@ -380,15 +380,11 @@ describe("findDominantRegion", () => { { id: "b", startMs: 3500, endMs: 6000, depth: 3, focus: { cx: 0.8, cy: 0.8 } }, ]; - // During the connected transition (between a.endMs + 200 and a.endMs + 1200) + // During the connected handoff, the next region becomes the spring target. const result = findDominantRegion(regions, 3200, { connectZooms: true }); expect(result.strength).toBe(1); - expect(result.transition).not.toBeNull(); - - // Focus should be blending between the two - if (result.region) { - expect(result.region.focus.cx).toBeGreaterThan(0.2); - } + expect(result.transition).toBeNull(); + expect(result.region?.id).toBe("b"); }); it("keeps the outgoing region active until the connected transition begins", () => { diff --git a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts index 7b01130c3..8e1cc6bf5 100644 --- a/src/components/video-editor/videoPlayback/zoomRegionUtils.ts +++ b/src/components/video-editor/videoPlayback/zoomRegionUtils.ts @@ -250,11 +250,6 @@ export function findDominantRegion( const connectedPairs = options.connectZooms ? getConnectedRegionPairs(regions) : []; if (options.connectZooms) { - const connectedTransition = getConnectedRegionTransition(connectedPairs, timeMs); - if (connectedTransition) { - return connectedTransition; - } - const connectedHold = getConnectedRegionHold(timeMs, connectedPairs); if (connectedHold) { return { ...connectedHold, transition: null };