diff --git a/packages/studio/src/player/components/timelineTrackOrder.test.ts b/packages/studio/src/player/components/timelineTrackOrder.test.ts index e84cd431d..31d85c45e 100644 --- a/packages/studio/src/player/components/timelineTrackOrder.test.ts +++ b/packages/studio/src/player/components/timelineTrackOrder.test.ts @@ -34,16 +34,29 @@ function rowIds(rows: readonly { elements: readonly TimelineElement[] }[]): stri } describe("buildStackingTimelineLayers", () => { - it("packs non-overlapping clips into the same lane even when their z-index differs", () => { + it("splits non-overlapping clips into separate lanes when their z-index differs", () => { + // A lane is a z-band: differing z must land on different rows even when the + // clips don't overlap in time, so a vertical (z) restack actually moves the + // clip's row. Rows are ordered by descending z. const result = buildStackingTimelineLayers([ rowElement({ id: "back", zIndex: 1, start: 0, duration: 1 }), rowElement({ id: "front", zIndex: 10, start: 1, duration: 1 }), ]); - expect(rowIds(result.visualLayers)).toEqual([["back", "front"]]); + expect(rowIds(result.visualLayers)).toEqual([["front"], ["back"]]); expect(result.visualLayers[0]?.zIndex).toBe(10); }); + it("packs non-overlapping clips into one lane when they share a z-index", () => { + const result = buildStackingTimelineLayers([ + rowElement({ id: "back", zIndex: 5, start: 0, duration: 1 }), + rowElement({ id: "front", zIndex: 5, start: 1, duration: 1 }), + ]); + + expect(rowIds(result.visualLayers)).toEqual([["back", "front"]]); + expect(result.visualLayers[0]?.zIndex).toBe(5); + }); + it("splits clips into separate lanes when they overlap in time", () => { const result = buildStackingTimelineLayers([ rowElement({ id: "front", zIndex: 10, start: 0, duration: 2 }), diff --git a/packages/studio/src/player/components/timelineTrackOrder.ts b/packages/studio/src/player/components/timelineTrackOrder.ts index 43e0014f3..4f3a41d7d 100644 --- a/packages/studio/src/player/components/timelineTrackOrder.ts +++ b/packages/studio/src/player/components/timelineTrackOrder.ts @@ -78,8 +78,13 @@ function getOrderedContextKeys(items: readonly TimelineLayerOrderItem[]): string } function canJoinLayer(layer: BuildLayer, item: TimelineLayerOrderItem): boolean { + // A row IS a z-band: clips only share a lane when they carry the same z-index + // (and don't overlap in time). Without the z-index gate a non-overlapping clip + // greedily packs into the first time-compatible row — the topmost one — so a + // vertical restack changes z but the row never follows it. return ( layer.contextKey === resolveStackingContextKey(item) && + layer.zIndex === item.zIndex && layer.elements.every((element) => !timelineElementsOverlap(element, item.element)) ); }