feat(studio): timeline z-stacking sync model#2198
Closed
ukimsanov wants to merge 1 commit into
Closed
Conversation
This was referenced Jul 11, 2026
Collaborator
Author
This was referenced Jul 11, 2026
a3c734a to
e90bfcc
Compare
4622bb9 to
d0a6ab1
Compare
e90bfcc to
af7dd22
Compare
d0a6ab1 to
206097c
Compare
miga-heygen
reviewed
Jul 11, 2026
miga-heygen
left a comment
Contributor
There was a problem hiding this comment.
Reviewed as part of the 25-PR NLE overhaul stack. Full stack review on #2205 (the keystone). No blockers on this PR. — Miga
vanceingalls
left a comment
Collaborator
There was a problem hiding this comment.
Verdict: LGTM (green) — permutation-preserving, cascade minimal
The z-stacking resolver's cleverness is in its restraint: try the single-clip patch first (authored z sacred), fall through to cascade only when ties or the z=0 floor make a single value inexpressible. And the cascade lifts only the neighbours that would otherwise no longer paint strictly above. Handsomely done.
The pointed queries
- Output is a valid permutation of inputs.
packages/studio/src/player/components/timelineStackingSync.ts:230–:258— patches are emitted through achanged: Map<key, zIndex>deduplicated byemitted: Set. No key appears twice; no key fromelementsis dropped from consideration (the resolver iterates every edited clip and every overlapping neighbour). By construction the patch set is a subset-permutation of the input keys, one z per key. No drops, no duplicates. Tested throughout — see the multi-clip test at :115–:120 ({ a: 7, b: 6 }is single-emit per key) and the cascade at :166–:180. - Round-trip through
normalizeToZones(cross-PR invariant with #2199). This is verified directly in#2199's test file attimelineZones.test.ts:332–:405—lane-move → z patch → re-discovery orders lanes by that same z → identical lanesandqa-clean: drag video BELOW ralu → z patch → re-pack keeps it below, no oscillation. Both close the loop:computeStackingPatches(#2198)output projected onto TimelineElement.zIndex, fed back throughnormalizeToZones(#2199), yields a fixed point. That is the invariant this batch turns on and it is empirically nailed. - Half-open overlap.
overlapsInTimeat :57–:59 uses<with anEPS = 1e-6guard on the right side (a.start < b.start + b.duration - EPS). Different from#2195's exact strict-<— the epsilon defends against floating-point fuzz creating spurious overlaps in the stacking resolver (a visible artifact) where#2195doesn't need it because a collision decision is boolean and idempotent. Justified divergence. Test at :109–:113:touching-but-not-overlapping intervals do NOT count—a(0..5) andb(5..5+5), no patch. Correct. - Authored z sacred (fast path).
trySingleZat :162–:176 attempts a strictly-between integer first; only whenminAbove - maxBelow < 2or when the only above-neighbour is at z=0 does it fall to the cascade. Test at :50–:54 confirms already-correct edits yield no patch; :91–:101 confirms only-below / only-above bounds. - Cascade minimality.
liftAboveat :183–:196 sorts above-neighbours lowest-first and bumps each only if its current z ≤ the floor. Test at :166–:180 pinsonly the blockers move— a non-overlapping neighbourxis not touched. - DOM-order tie-breaking.
paintsAboveat :78–:82 usesdomIndexwhen zs are equal — the sync sees the actual paint reality and doesn't under-patch on equal-z ties. Test at :145–:153 usesdomIndexon equal-z clips and verifies the cascade fires. - Audio exclusion. :123 filters out
isAudioneighbours before the overlap set is computed.musicneighbour test at :85–:89 pins the invariant.
Nits (non-blocking)
overlapsInTimeuses- EPSwhiletimeRangesOverlapin#2195uses exact<. The divergence is intentional and defensive here, but a one-line comment noting the choice would help a future reader who reads both files back-to-back and wonders whether the two are meant to align.
R1 by Via
af7dd22 to
bf91abd
Compare
1ed5c27 to
f2fbc25
Compare
bf91abd to
0691107
Compare
0691107 to
9e58745
Compare
6730438 to
7f34c4e
Compare
9e58745 to
468cb87
Compare
7f34c4e to
4a2773c
Compare
174d299 to
42fb8a8
Compare
4a46364 to
0cc64a7
Compare
42fb8a8 to
f5f66df
Compare
What: new pure module timelineStackingSync — lane order ↔ z-index reconciliation (laneIsAbove, computeStackingPatches) with tests. Why: the single source of truth for how timeline lane order maps to canvas stacking; the ordering rules and tie-breaks live here. How: new files only; consumed later by timelineZones and the stacking-sync hook. Test plan: bunx vitest run timelineStackingSync.test.ts; tsc --noEmit; fallow audit clean.
0cc64a7 to
03318aa
Compare
f5f66df to
600ef91
Compare
miguel-heygen
approved these changes
Jul 12, 2026
miguel-heygen
left a comment
Collaborator
There was a problem hiding this comment.
Re-reviewed current head and verified the addressed feedback and required behavior. Approval is for this exact head; Graphite mergeability may still be pending.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What
new pure module timelineStackingSync — lane order ↔ z-index reconciliation (laneIsAbove, computeStackingPatches) with tests.
Why
the single source of truth for how timeline lane order maps to canvas stacking; the ordering rules and tie-breaks live here.
How
new files only; consumed later by timelineZones and the stacking-sync hook.
Test plan
bunx vitest run timelineStackingSync.test.ts; tsc --noEmit; fallow audit clean.
Stack position 7/25 — studio NLE overhaul (CapCut-parity timeline + canvas). Graphite manages bases; merge from #2192 upward. Temporary
TEMP(studio-dnd)fallow entries (unwired-component windows) are all removed by #2213 (app-shell swap), whose tree is byte-identical to the fully-verified integration branch.