feat(studio): timeline lane-zone model#2199
Open
ukimsanov wants to merge 1 commit into
Open
Conversation
This was referenced Jul 11, 2026
Collaborator
Author
This was referenced Jul 11, 2026
a3c734a to
e90bfcc
Compare
95e9ca4 to
d0aa2e0
Compare
e90bfcc to
af7dd22
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) — the keystone of the two-way agreement
The most consequential math in this batch, and the tests reflect it: a whole section is devoted to the qa-clean regression that motivated the rewrite (whole-track ordering by MAX z carried a low-z clip above a covering higher-z clip), a section to reload stability, and a section to the round-trip with #2198's computeStackingPatches. Round-trip convergence is not asserted merely by construction — it is exercised end-to-end.
The pointed queries
- Correctness of
normalizeToZones— no zone identity violations.packages/studio/src/player/components/timelineZones.ts:164–:198splits elements intovisualandaudioarrays (:170–:174 byclassifyZone), packs each independently, and never reassigns across zones. A clip in zone X remains in zone X after re-packing — the identity property.#2199's tests at :76–:87 (drops audio below the visual lanes even when sharing a track index,groups multiple audio tracks at the bottom) verify this end-to-end. - Empty and single-lane edges. :165 short-circuits an empty input (
return elements— same reference, no reallocation). Single-element inputs pack into lane 0 (verified implicitly by all one-clip contexts). No off-by-one at zero. - Stacking invariants preserved. The per-clip pack at :100–:117 sorts by
zOf(b) - zOf(a) || b.domIndex - a.domIndex || key— deterministic, stable, and (critically) using the INPUT domIndex, not the mutated lane, as the tie-break. The comment at :86–:88 flags this correction from the historical oscillation bug (tie-breaking on the mutated output caused non-termination). Test at :187–:195 (tie-breaks equal-z overlapping clips on the STABLE id, not the mutated lane) pins the invariant. - Fixed point / idempotence. :99–:107, :197–:206, :252–:256, :308–:315 all pass. The claim
normalizeToZones(normalizeToZones(x)) === normalizeToZones(x)is asserted structurally (lane per id equal after two passes) across multiple shapes. - Round-trip with
#2198— the cross-PR invariant. :332–:405 is the money test. Two scenarios: (1) authored order a(z=1)/b(z=5), user drags a above b,computeStackingPatchesemitsa→6, re-normalize returns a to lane 0 — convergent. (2) qa-clean drag video below ralu → cascade patches → re-normalize places video below ralu. Both scenarios also verifycomputeStackingPatches(renormalized) === []— the fixed-point signature. This is the invariant Batch-A pivots on and it is nailed. - Reload stability. :208–:219, :258–:262 verify that fresh objects with the same z produce identical lanes across two independent normalizations. Discovery re-runs are deterministic.
- Half-open overlap. :25–:27 mirrors
#2198with the sameEPS = 1e-6— consistent within the stacking half of this batch.
Nits (non-blocking)
- The zone-identity property (visual stays visual, audio stays audio) is guaranteed by construction (:170–:174) but never asserted directly. A one-line invariant test on a mixed input would document what the split ensures — nice-to-have, not needed.
- The
EPS = 1e-6epsilon convention is now in two files (timelineStackingSync.tsand here). Consider a sharedTIMELINE_EPSILONconstant at some point to avoid drift if one is ever tuned — cosmetic.
R1 by Via
af7dd22 to
bf91abd
Compare
d0aa2e0 to
b3f0ed0
Compare
bf91abd to
0691107
Compare
b3f0ed0 to
41f7e1d
Compare
0691107 to
9e58745
Compare
41f7e1d to
0c56eb0
Compare
What: new pure module timelineZones — visual/audio track-zone classification (classifyZone) and normalizeToZones, which re-packs lanes into zone-consistent rows; tests cover the stacking/zones interaction. Why: completes the z-model started in the stacking-sync PR. How: new files; consumes isAudioTimelineElement (leaf-helpers PR) and computeStackingPatches (stacking-sync PR); type-only playerStore imports. Test plan: bunx vitest run timelineZones.test.ts; tsc --noEmit; fallow audit clean.
0c56eb0 to
69f9285
Compare
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 timelineZones — visual/audio track-zone classification (classifyZone) and normalizeToZones, which re-packs lanes into zone-consistent rows; tests cover the stacking/zones interaction.
Why
completes the z-model started in the stacking-sync PR.
How
new files; consumes isAudioTimelineElement (leaf-helpers PR) and computeStackingPatches (stacking-sync PR); type-only playerStore imports.
Test plan
bunx vitest run timelineZones.test.ts; tsc --noEmit; fallow audit clean.
Stack position 8/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.