Skip to content

perf(studio): a long timeline no longer lays itself out on every render while it settles - #6855

Merged
miguel-heygen merged 2 commits into
mainfrom
fix/timeline-no-layout-read-per-commit
Oct 30, 2026
Merged

miguel-heygen merged 2 commits into
mainfrom
fix/timeline-no-layout-read-per-commit

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

What

A long timeline no longer lays itself out on every React render while its project settles. Two reads forced a synchronous layout of the whole timeline on every commit:

  • The ruler's settled scroll position (useSettledScrollLeft) read scrollLeft inside its useSyncExternalStore snapshot, which React calls on every render and after every commit. The settled value is now kept when a scroll settles (and when the hook subscribes) and the snapshot returns it.
  • The rows' geometry effect (useTimelineRowVirtualization) read scrollTop whenever rowGeometry changed identity, even when the new geometry put every row at the same top. It now skips the read when the rows' keys, offsets and top padding are unchanged (the existing sameData); rows are tracks, so the comparison is a handful of numbers.

Why

In a renderer CPU profile of a 600-clip, 23-minute project playing right after it opens, these two reads were the top self-time functions (0.4 to 0.9 s each in 6 s of play), each a forced layout of every clip. While the project settles (each clip's waveform arriving is a commit), playback dropped to a few frames a second.

Related work

None.

How

  • useSettledScrollLeft.ts: a settledRef set on subscribe (with an onChange() so the first render's value is replaced) and when a scroll settles; the snapshot returns it.
  • useTimelineRowVirtualization.ts: the geometry branch runs only when sameData says the rows' keys, offsets or top padding changed (a geometry carries per-instance closures, so the whole objects never compare equal).

No visible change

The timeline looks and scrolls the same; it stops forcing layouts that changed nothing on screen.

Test plan

fails without the fix: with the two source files restored to main, "reads scrollLeft when a scroll settles, never on a render" (useSettledScrollLeft.test.tsx) reads 9 times on renders, and "reads no scroll position for a new geometry that places every row where it was" (useTimelineRowVirtualization.test.tsx) reads 6 times; both read 0 with the fix.

  • Both files 3/3, three runs in a row (vitest, happy-dom).
  • The existing "leaves the viewport still when a live preview lane is removed" case still passes: a geometry with a row added or removed still reads scrollTop and keeps the view anchored.
  • Not yet run: the playback number on a release build of this branch; it follows in a comment.

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 89 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

Unstable (3)

  • seqplainkeys-none-center-r0-nested-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true
  • seqplainkeys-none-center-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true
  • seqplainkeys-none-pct-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES · 3 blocker · 0 important · 0 nits · head e71656bb

I found no problem with the code itself. Locally, both new tests fail on main (9 and 6 reads, matching the body), pass here, and src/player passes (2433 tests). Skipping the read when rows are unchanged gives the same result as before: getTimelineScrollTopForGeometryChange returns scrollTop unchanged when keys, offsets and top padding all match. The blockers are all failing CI checks:

  • CI → Format: oxfmt --check flags packages/studio/src/player/components/useTimelineRowVirtualization.test.tsx. Run oxfmt on it.
  • Comments ratchet: comment share went up in timelineViewportGeometry.ts (0/55 → 2/67) and useSettledScrollLeft.ts (3/39 → 5/44). Cut the added lines or move what they say into names.
  • PR captures: the body needs ## Before / ## After captures. The source diff is over the 20-line limit, so ## No visible change won't pass either unless the gate's rules allow it.
Scope

Read end to end: all 5 files, plus the consumers of useSettledScrollLeft (TimelineRuler, TimelinePlayheadLayer) and how setScrollRef gets attached. Both consumers render inside the viewport div, so the ref is set before the subscribe effect runs. No prior reviews.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE — 3 resolved, 0 open, 0 new · e71656b → 531d716

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 9b0df61 Oct 30, 2026
122 of 123 checks passed
@miguel-heygen
miguel-heygen deleted the fix/timeline-no-layout-read-per-commit branch October 30, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant