Skip to content

fix(virtual-core): stop viewport-spanning item growth from drifting scroll#1236

Merged
piecyk merged 2 commits into
TanStack:mainfrom
piecyk:fix/spanning-item-scroll-drift
Jul 20, 2026
Merged

fix(virtual-core): stop viewport-spanning item growth from drifting scroll#1236
piecyk merged 2 commits into
TanStack:mainfrom
piecyk:fix/spanning-item-scroll-drift

Conversation

@piecyk

@piecyk piecyk commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #1218.

When a streaming chat message that spans the fold (top scrolled above the viewport, bottom still below it) keeps growing at its bottom, the viewport drifts downward on its own — scrollTop creeps up token by token while the distance to the bottom stays constant.

Root cause

The default scroll-adjustment heuristic in resizeItem compensated a size change for any item whose top sat above the fold:

itemStart < this.getScrollOffset() + this.scrollAdjustments

That condition can't distinguish an item that is entirely above the fold from one that merely spans it. A streaming message grows at its bottom — i.e. below the anchor point — so compensating by the full delta shifts scrollTop and drags the viewport.

Fix

Split the two cases in the default predicate:

  • Re-measurement (the streaming case): only compensate when the item is entirely above the fold — itemStart + itemSize <= scrollOffset. Growth below the anchor point now leaves the scroll position untouched.
  • First measurement (estimate→actual): unchanged — still compensates any above-fold item, preserving initial-render correctness.
  • A custom shouldAdjustScrollPositionOnItemSizeChange still overrides the default.

Testing

  • @tanstack/virtual-core: 125/125 pass, including 3 new regression tests covering: spanning-item growth (no drift), entirely-above growth (still anchors), and first-measurement compensation.
  • @tanstack/react-virtual: 7/7 pass.
  • tsc --noEmit clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Prevented unwanted scroll-position drift when items spanning the viewport grow during remeasurement.
    • Preserved scroll adjustments for items fully above the viewport.
    • Maintained expected compensation during an item’s first measurement.
    • Custom scroll-adjustment behavior continues to be supported.

…croll

The default scroll-adjustment heuristic compensated any item whose top sat
above the fold (`itemStart < scrollOffset`) on every re-measure. A streaming
chat message that spans the fold (top scrolled above it, bottom below) grows
at its bottom — below the anchor point — so compensating dragged scrollTop
downward token by token, drifting the viewport (TanStack#1218).

Re-measurements now only compensate items that are entirely above the fold
(`itemStart + itemSize <= scrollOffset`); growth below the anchor point leaves
the scroll position untouched. First measurements (estimate→actual) still
compensate any above-fold item, and a custom
shouldAdjustScrollPositionOnItemSizeChange still overrides the default.

Fixes TanStack#1218.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cf2fb783-5b52-42ae-ae1f-79e453043863

📥 Commits

Reviewing files that changed from the base of the PR and between 87f689a and 4f5ccc1.

📒 Files selected for processing (3)
  • .changeset/fix-spanning-item-scroll-drift.md
  • packages/virtual-core/src/index.ts
  • packages/virtual-core/tests/index.test.ts

📝 Walkthrough

Walkthrough

The default resize compensation now skips adjustments when a re-measured item spans the viewport fold, while retaining compensation for fully above-fold items and first measurements. Regression tests cover these cases, and a patch changeset documents the behavior.

Changes

Scroll adjustment behavior

Layer / File(s) Summary
Resize adjustment predicate
packages/virtual-core/src/index.ts, .changeset/fix-spanning-item-scroll-drift.md
Virtualizer.resizeItem distinguishes first measurements from re-measurements, skips compensation for viewport-spanning items, preserves backward-scroll handling and custom callback overrides, and documents the patch release.
Scroll anchoring regression coverage
packages/virtual-core/tests/index.test.ts
Tests verify that spanning-item growth does not move the viewport, fully above-fold growth adjusts the offset, and first measurement compensation remains active.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

  • #1227 — Directly concerns Virtualizer.resizeItem scroll-compensation logic and callback handling.

Possibly related PRs

Suggested reviewers: 2wheeh, mds-ant

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: preventing viewport-spanning item growth from causing scroll drift.
Description check ✅ Passed The description explains the fix, motivation, and testing, but it omits the template's checklist and release-impact sections.
Linked Issues check ✅ Passed The code and tests align with #1218 by preventing re-measurement drift for spanning items while preserving first-measurement compensation.
Out of Scope Changes check ✅ Passed The changes are limited to the fix, regression tests, and a changeset, with no unrelated scope added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nx-cloud

nx-cloud Bot commented Jul 20, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 4f5ccc1

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 2m 55s View ↗
nx run-many --target=build --exclude=examples/** ✅ Succeeded 13s View ↗

☁️ Nx Cloud last updated this comment at 2026-07-20 10:46:41 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jul 20, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-virtual

npm i https://pkg.pr.new/@tanstack/angular-virtual@1236

@tanstack/lit-virtual

npm i https://pkg.pr.new/@tanstack/lit-virtual@1236

@tanstack/marko-virtual

npm i https://pkg.pr.new/@tanstack/marko-virtual@1236

@tanstack/react-virtual

npm i https://pkg.pr.new/@tanstack/react-virtual@1236

@tanstack/solid-virtual

npm i https://pkg.pr.new/@tanstack/solid-virtual@1236

@tanstack/svelte-virtual

npm i https://pkg.pr.new/@tanstack/svelte-virtual@1236

@tanstack/virtual-core

npm i https://pkg.pr.new/@tanstack/virtual-core@1236

@tanstack/vue-virtual

npm i https://pkg.pr.new/@tanstack/vue-virtual@1236

commit: 4f5ccc1

@piecyk
piecyk merged commit 7ae32b5 into TanStack:main Jul 20, 2026
10 checks passed
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.

applyScrollAdjustment causes chat stream viewport to drift downward when a visible streaming item keeps growing

1 participant