Skip to content

fix(sessions): smooth conversation scroll-up jank#2960

Merged
richardsolomou merged 7 commits into
mainfrom
posthog-code/fix-virtualized-scroll-up-jank
Jun 28, 2026
Merged

fix(sessions): smooth conversation scroll-up jank#2960
richardsolomou merged 7 commits into
mainfrom
posthog-code/fix-virtualized-scroll-up-jank

Conversation

@richardsolomou

@richardsolomou richardsolomou commented Jun 27, 2026

Copy link
Copy Markdown
Member

Problem

Scrolling up through a conversation was janky — heavy stutter and content jumping — past stretches of tall rows (long markdown messages with code blocks, and file-edit diffs). Scrolling down, or back up a second time over the same rows, was smooth.

Profiled over CDP: the cause is tall, expensive, async-rendered rows combined with a shallow overscan. With overscan: 6, each such row was first rendered and measured as it entered the viewport, which does two bad things on the visible frame:

  • Layout shift — its true height replaces the pre-measurement estimate; across the rows above the viewport this accumulated to ~300px of shift per scroll-up.
  • Frame stutter — its expensive first paint lands on the visible frame (~140ms worst gap, ~25% dropped frames).

Second pass was smooth because measured heights and syntax highlighting were already cached.

Changes

Deepen the overscan, 6 → 12. Rendering rows ~12 ahead moves the measure + paint work off the visible critical path: by the time a row scrolls into view it's already measured and painted. Measured before/after, same thread, same scroll:

metric (scroll-up) overscan 6 overscan 12
accumulated layout shift ~300px 0px
worst frame gap ~140ms ~55ms
dropped frames ~25% ~5–10%

That's the whole change. An earlier version of this PR also added content-aware row estimates, a diff height-reservation, and a scroll-compensation predicate; profiling showed the overscan bump erases the shift on its own and those added nothing measurable on top (overscan renders rows ahead, so they never enter view unmeasured), so they were dropped to keep the fix minimal.

How did you test this?

  • Profiled scroll-up in the running dev app over CDP, before/after, capturing per-frame gaps and scrollHeight deltas (numbers above). Also A/B'd overscan-12 with vs without the estimate machinery — identical 0px shift, confirming overscan is what matters.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Created with PostHog Code

The transcript virtualizer seeded every row with a flat 80px estimate
while real rows range from ~32px chips to 1000px diffs. A thread opens
scrolled to the bottom, so only the last few rows ever get measured; the
first scroll-up corrects every row above from estimate to true height at
once, and tanstack skips scroll compensation during backward scroll, so
the uncompensated growth shoves the viewport.

Add a content-aware per-row estimate to shrink each correction, and
compensate scrollTop for above-viewport resizes regardless of scroll
direction so visible content stays put. Bottom-follow is unaffected.

Generated-By: PostHog Code
Task-Id: f1ca2a14-42ac-49de-8ccd-b1f2de1e10e0
@github-actions

github-actions Bot commented Jun 27, 2026

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit ae4f48f.

@greptile-apps

greptile-apps Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(sessions): smooth conversation scrol..." | Re-trigger Greptile

Comment thread packages/ui/src/features/sessions/components/new-thread/estimateThreadRow.test.ts Outdated
Comment thread packages/ui/src/features/sessions/components/VirtualizedList.tsx Outdated
Profiling the scroll-up showed the jank comes from tall, async-rendered
rows (long markdown with code blocks, diffs): with overscan 6 each one was
first measured and painted as it entered the viewport, so its true height
replaced the estimate (≈300px of accumulated shift) and its paint landed on
the visible frame (≈140ms hitches, ~25% dropped frames). Rendering 12 rows
ahead moves that work off the visible critical path — measured shift drops
to 0, worst frame to ~56ms, dropped frames to ~5%.

Also size edit-diff rows by their changed lines (the unified diff collapses
unchanged context, so the old whole-file guess over-estimated) and reserve a
diff's measured height across remounts so it can't collapse to zero while its
highlight worker re-initializes.

Generated-By: PostHog Code
Task-Id: f1ca2a14-42ac-49de-8ccd-b1f2de1e10e0
…gnment

Address review: cover every chip-style row arm with it.each (exact px),
and assign the scroll-compensation predicate once via a ref guard instead
of on every render. Keep it in render, not a mount effect, so it precedes
the virtualizer's first measurement pass.

Generated-By: PostHog Code
Task-Id: f1ca2a14-42ac-49de-8ccd-b1f2de1e10e0
@richardsolomou richardsolomou requested a review from a team June 27, 2026 14:49
@richardsolomou richardsolomou marked this pull request as ready for review June 27, 2026 14:49
@greptile-apps

greptile-apps Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Reviews (2): Last reviewed commit: "test(sessions): parameterize chip estima..." | Re-trigger Greptile

Trim verbose comments to their load-bearing facts.

Generated-By: PostHog Code
Task-Id: f1ca2a14-42ac-49de-8ccd-b1f2de1e10e0
@adboio

adboio commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

thank you!!

still reviewing but my gut doesn't love the estimated height values at first glance haha - what are the perf gains like with just the overscan adjustment?

Profiling showed the deeper overscan (6→12) erases the scroll-up layout
shift on its own — rows render and measure ahead of the viewport, so they
enter view already sized and painted. The content-aware estimates, diff
height-reservation, and scroll-compensation predicate were chasing the same
jank before that landed and add nothing measurable on top, so drop them.

Generated-By: PostHog Code
Task-Id: f1ca2a14-42ac-49de-8ccd-b1f2de1e10e0
@richardsolomou

Copy link
Copy Markdown
Member Author

@adboio good catch, profiled it again with just the overscan bump, yeah its the one doing most of the work <3

@richardsolomou richardsolomou enabled auto-merge (squash) June 27, 2026 18:11
@richardsolomou richardsolomou merged commit cb0ca68 into main Jun 28, 2026
23 checks passed
@richardsolomou richardsolomou deleted the posthog-code/fix-virtualized-scroll-up-jank branch June 28, 2026 00:32
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.

2 participants