fix(studio): a row move restacks only the clip you moved, so nothing else goes behind - #5057
Conversation
Edit accuracy: accurate 2040 (base branch 2040), smooth 1648 of thoseThe gate passes. Quarantined, measured but not gated (0) |
…else goes behind Moved up, the clip rises above the clips it overlaps on the rows now below it; moved down, it sinks below the ones now above it, never under z-index 0. Neighbours keep their z, so a full-frame scene on the top row no longer jumps over a caption moved up a row beneath it.
Drops the stale cascade wording, checks the time overlap before the paint-scope string compare, and every test case names its move direction.
b0ed462 to
400bbe1
Compare
…n never hides it Selected clips that only shifted in time keep their z-index. A move down stops at the clips on lower rows it paints over, instead of dropping under them when z-index 0 cannot get it behind the clip above.
jrusso1020
left a comment
There was a problem hiding this comment.
Review at 4b40d5b3. Approving.
The fix. With raisedZ/loweredZ, the moved clip is the only one patched, so a clip nobody touched can't change z. The scene-on-track-0 case writes nothing. A move up returns null when the clip already paints above every clip that's now below it. A move down is clamped at 0, and it stops at the lower-row clips it draws over. Both match the description. Restacking only dragKey in a multi-selection drag is right: the other selected clips only shift in time, so they keep their rows and their z.
Tests. I ran src/player plus timelineStackingSyncExport.test.tsx three times, and 2209 tests passed each time. I then made one change at a time to the logic and ran the three touched test files (96 tests). 8 of the 9 changes are caught:
- the up no-op check
- the down no-op check
- the z 0 floor
- dropping
keepOver - the
z < clip.zIndexgate - the order of edited clips
- flipping the direction
- always passing
"up"
The one that survives is the equal-z tie branch in the floor (? o.zIndex : o.zIndex + 1), which the PR already lists as untested.
CI has 37 checks passing and 0 failing, with 21 still running when I posted.
Reuse / simplification. This deletes the neighbour cascade and reuses spansOverlap and samePaintScope, so net it's −205 lines. One more cut is available: the only caller now passes one key, so computeStackingPatches could take a single key instead of Iterable<string>. That drops the direction sort and the multi-clip ordering test, which no production path reaches any more. It's optional.
Non-blocking, a latent fragility:
syncStackingForEditgets the direction fromaimedLane < currentLane.- On the track-insert path,
aimedLaneisdrag.insertRow, a boundary index intotrackOrder, whilecurrentLaneis a track value. - They agree today only because
normalizeToZonespacks the visual lanes to 0..n-1. - With a row whose track isn't its index, they disagree.
I checked this with a legacy expanded sub-composition row (trackOrder: [0, 0.25, 1, 2]). A caption on track 2 (z 2) was drawn over a scene on track 0 (z 1). I dragged the caption into a new row directly above its own row. That gesture was classified as "down" and wrote {a: 0}, which put the caption under the scene. The same gesture without the extra row writes nothing.
Current rows never set expandedHostKey, so I don't think a user can hit this today. Even so, comparing the candidate's own target track would remove the dependency:
const target = candidate.find((e) => keyOf(e) === dragKey)?.track;
// direction: target < currentLane ? "up" : "down"That also lets the aimedLane === currentLane guard compare like with like.
— Rames
What
Moving a clip to another timeline row now restacks only that clip:
Why
Until now a row move made the whole overlapping stack follow row order, and lifted clips the person never touched. Films often put a full-frame scene or background on the top row, with captions and overlays on lower rows. In such a film, dragging a caption up one row (still under the scene's row) lifted the scene over it, and the caption disappeared.
Repro on main:
z-index: 1, overlapping in time.Moving a clip to the top row still brings it in front of everything it overlaps.
Related work
Refs #4937, which already records a group move as one undo step until the following edit, so the row change and this restack undo with one Cmd+Z.
How
computeStackingPatches(elements, editedKeys, direction)takes the move's direction.resolveEditedZ,trySingleZ,liftAboveand two helpers) is deleted.syncStackingForEditpassesaimedLane < currentLane ? "up" : "down". It already received both lanes.Test plan
timelineStackingSync.test.tsgives every case a direction:timelineStackingSyncExport.test.tsxnow seeds z-index values a downward move can express. It still proves that a row move restacks the host's preview.Review
Independent adversarial review, at head 4b40d5b:
computeStackingPatchesstill accepts several edited keys, and its only caller now passes one.Before
main: the caption, dragged up from the fourth row to the third (still under the scene's top row), is no longer drawn. The file now gives the scene
z-index: 1and the captionz-index: 0.After
This branch: the same drag leaves both z-index values as they were, and the caption stays in front of the scene.