Skip to content

fix(virtual-core): reconcile index scrolls after pending measurements - #1294

Open
minwookshin wants to merge 1 commit into
TanStack:mainfrom
minwookshin:codex/resize-scroll-reconciliation
Open

minwookshin wants to merge 1 commit into
TanStack:mainfrom
minwookshin:codex/resize-scroll-reconciliation

Conversation

@minwookshin

@minwookshin minwookshin commented Sep 29, 2026 •

Copy link
Copy Markdown

🎯 Changes

Fixes #1290. Keep scrollToIndex reconciliation active long enough for pending ResizeObserver measurements to update the target, including rAF-deferred measurements. If an external scroll leaves a reached target during that wait, retire reconciliation so it respects the new position; measurement compensation still reconciles.

Added core and browser regressions for a cached row growing while re-pinning to the end, plus coverage for scrolling away and regular/clamped measurement compensation. Both observer modes previously stopped 30px short and now land at the end.

Validation passed: pnpm test:pr --base=origin/main --parallel=1 (69 affected projects), plus pnpm --filter @tanstack/react-virtual test:e2e cached-measurements.spec.ts --browser=all --workers=1 (9 tests across Chromium, Firefox, and WebKit). The core suite passes 173 tests, and the existing Marko chat reading/prepend cases also pass five consecutive runs each.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Bug Fixes
    • Scrolling to an item now stays aligned as its size measurements settle, including when measurements are deferred to an animation frame.
    • If you scroll away after the target is reached, the virtual scroller stops correcting toward the old target.
    • Scrolling to the end remains pinned as item sizes change, including when the end position is clamped.

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 3d27bd45-fbf5-413c-9de9-d46b6fe774c7

📥 Commits

Reviewing files that changed from the base of the PR and between 78371e8 and 1679287.

📒 Files selected for processing (5)
  • .changeset/tidy-scroll-measurements.md
  • packages/react-virtual/e2e/app/cached-measurements/main.tsx
  • packages/react-virtual/e2e/app/test/cached-measurements.spec.ts
  • packages/virtual-core/src/index.ts
  • packages/virtual-core/tests/index.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The virtualizer now tracks when a scroll reaches its target and continues index-based reconciliation while measurements are pending. Tests cover deferred ResizeObserver delivery, external scrolls, and end-clamp adjustments. The cached-measurements demo and Playwright test exercise end pinning with both observer scheduling modes.

Changes

Scroll reconciliation

Layer / File(s) Summary
Track and reconcile scroll targets
packages/virtual-core/src/index.ts, packages/virtual-core/tests/index.test.ts, .changeset/tidy-scroll-measurements.md
Scroll state tracks target arrival. Reconciliation waits for stable frames based on scroll type and ResizeObserver scheduling, and tests cover measurement retargeting, external scroll cancellation, short-of-target events, and end-clamp adjustments.
Exercise end pinning with cached measurements
packages/react-virtual/e2e/app/cached-measurements/main.tsx, packages/react-virtual/e2e/app/test/cached-measurements.spec.ts
The demo can expand an item before scrolling to the end. The test checks that the scroller remains pinned with deferred ResizeObserver delivery enabled or disabled.

Priority: ➖ Normal

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

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: piecyk

Merge Risk: ⚪ Minimal · up to 16792

No actionable issue remains from this review; the change is mergeable after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 16792

The change affects scroll positioning and cancellation, but the reviewed paths do not show a new security boundary or sensitive operation. Some delayed-event ordering remains unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — Effective exposure in the inspected change is the virtualizer instance’s scroll position and its consumer-supplied scroll adapter. No new tenant, credential, network, or data-store authority was identified in that path.

Trust Boundaries and Controls

  • observed — Offset events resembling the intended self-write or a pending clamped write are distinguished from movement that cancels a reached index target.

Resilience and Maintainability Implications

  • inferred — Cleanup cancels reconciliation and clears its state, but does not reset the existing self-write offset token or explicitly cancel an already queued measurement callback. The impact of late delivery across a replacement remains unverified; these omissions also predate this PR.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: keeping virtual-core index scroll reconciliation active after pending measurements.
Description check ✅ Passed The description explains the change and motivation, references issue #1290, documents test coverage and results, completes the checklist, and identifies the release impact with a changeset.
Linked Issues check ✅ Passed The implementation addresses #1290. ScrollState keeps index-scroll reconciliation active across pending ResizeObserver measurements, including animation-frame-deferred callbacks. The implementation …
Out of Scope Changes check ✅ Passed The changes stay within #1290. The core changes implement pending-measurement reconciliation. The core tests and cached-measurements browser demo and test provide regression coverage for the reported …
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 4…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Warning

Some tools did not complete. Review the errors below.

🔧 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.

packages/virtual-core/tests/index.test.ts

Parsing error: "parserOptions.project" has been provided for @typescript-eslint/parser.
The file was not found in any of the provided project(s): packages/virtual-core/tests/index.test.ts


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.

This branch has not been deployed

No deployments
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.

scrollToIndex(last, { align: 'end' }) stays short of the end after a measured row grows (virtual-core ≥ 3.17.0)

1 participant