Skip to content

fix: prevent scroll position jump when user scrolls up during content updates#28405

Open
PiedPiper911 wants to merge 2 commits into
google-gemini:mainfrom
PiedPiper911:fix/scroll-position-jump-5009
Open

fix: prevent scroll position jump when user scrolls up during content updates#28405
PiedPiper911 wants to merge 2 commits into
google-gemini:mainfrom
PiedPiper911:fix/scroll-position-jump-5009

Conversation

@PiedPiper911

Copy link
Copy Markdown

Summary

Fixes #5009 — scroll position jumps to top/bottom when user scrolls up to review changes (e.g., after Ctrl+S) and new content arrives.

Root Cause

In VirtualizedList.tsx, the useLayoutEffect that manages auto-scroll has a condition that re-enables isStickingToBottom too aggressively:

const contentPreviouslyFit = prevTotalHeight.current <= prevContainerHeight.current;
const wasScrolledToBottomPixels = prevScrollTop.current >= prevTotalHeight.current - prevContainerHeight.current - 1;
const wasAtBottom = contentPreviouslyFit || wasScrolledToBottomPixels;

if (wasAtBottom && actualScrollTop >= prevScrollTop.current) {
    if (!isStickingToBottom) {
        setIsStickingToBottom(true);  // <-- re-enabled incorrectly
    }
}

The wasAtBottom variable includes contentPreviouslyFit, which is true when content fit in the container on the previous render. When new content arrives and causes the first overflow, contentPreviouslyFit is still true from the previous render — even if the user has explicitly scrolled up. This causes isStickingToBottom to be re-enabled, snapping the scroll to Number.MAX_SAFE_INTEGER (the bottom).

This manifests as the scroll "jumping to top" (actually jumping to bottom, then the terminal re-renders from top) when:

  1. User presses Ctrl+S to review code changes
  2. User scrolls up to read the changes
  3. New content arrives (agent response, authorization prompt, etc.)
  4. isStickingToBottom is incorrectly re-enabled → scroll jumps

Fix

Change the condition for re-enabling isStickingToBottom from wasAtBottom to wasScrolledToBottomPixels. This ensures isStickingToBottom is only re-enabled when the user's scroll position is genuinely at the bottom, not just when content previously fit.

// Only re-enable isStickingToBottom when the user is genuinely at the
// bottom (not just when content previously fit). This prevents the scroll
// from jumping back to the bottom when the user has scrolled up to review
// changes (e.g., after Ctrl+S) and new content arrives. (#5009)
if (wasScrolledToBottomPixels && actualScrollTop >= prevScrollTop.current) {
    if (!isStickingToBottom) {
        setIsStickingToBottom(true);
    }
}

The auto-scroll-to-bottom logic (lines 371-388) still uses wasAtBottom for deciding whether to scroll to the new bottom when the list grows. This is correct — we want to auto-scroll when the user was at the bottom. The fix only affects the isStickingToBottom flag re-enablement.

Changes

File Change
packages/cli/src/ui/components/shared/VirtualizedList.tsx Changed condition for re-enabling isStickingToBottom from wasAtBottom to wasScrolledToBottomPixels (1 line + comment)

Test plan

  • Start a conversation that generates enough output to overflow the viewport
  • Press Ctrl+S to enter mouse mode for reviewing changes
  • Scroll up to review the changes
  • Wait for new content to arrive (agent response, authorization prompt)
  • Verify scroll position is preserved (does not jump)
  • Verify that when at the bottom, new content still auto-scrolls correctly
  • Verify that when content transitions from "fits" to "overflows", scroll stays at bottom

Closes #5009

@PiedPiper911
PiedPiper911 requested a review from a team as a code owner July 15, 2026 04:51
@google-cla

google-cla Bot commented Jul 15, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where the virtualized list component would incorrectly force a scroll-to-bottom behavior during content updates. By refining the condition that triggers the auto-scroll state, the component now correctly respects the user's current scroll position, ensuring a smoother experience when reviewing terminal output or agent responses.

Highlights

  • Scroll Behavior Fix: Modified the logic in VirtualizedList.tsx to prevent the scroll position from snapping to the bottom when new content arrives while the user is reviewing previous output.
  • Condition Refinement: Updated the re-enablement condition for the isStickingToBottom flag to rely strictly on actual scroll position rather than content fit status.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added the size/xs An extra small PR label Jul 15, 2026
@github-actions

Copy link
Copy Markdown

📊 PR Size: size/XS

  • Lines changed: 6
  • Additions: +5
  • Deletions: -1
  • Files changed: 1

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request modifies the scroll-sticking logic in VirtualizedList.tsx to prevent the scroll position from jumping to the bottom when new content arrives. However, the reviewer identified a logical issue where wasScrolledToBottomPixels evaluates to true when the content fits the container, which would cause unintended scroll jumps. A code suggestion was provided to explicitly check that the content did not previously fit.

Comment on lines +357 to +361
// Only re-enable isStickingToBottom when the user is genuinely at the
// bottom (not just when content previously fit). This prevents the scroll
// from jumping back to the bottom when the user has scrolled up to review
// changes (e.g., after Ctrl+S) and new content arrives. (#5009)
if (wasScrolledToBottomPixels && actualScrollTop >= prevScrollTop.current) {

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.

high

There is a subtle logical issue with using wasScrolledToBottomPixels directly here.

When the content fits within the container (prevTotalHeight.current <= prevContainerHeight.current), prevTotalHeight.current - prevContainerHeight.current - 1 becomes a negative number (e.g., -1 or less). Since prevScrollTop.current is always >= 0 (or at least >= prevTotalHeight.current - prevContainerHeight.current), the condition prevScrollTop.current >= prevTotalHeight.current - prevContainerHeight.current - 1 will always evaluate to true.

This means wasScrolledToBottomPixels is always true when the content previously fit. As a result, if the user scrolled up while the content fit (which sets isStickingToBottom to false), the moment new content arrives and causes an overflow, wasScrolledToBottomPixels and actualScrollTop >= prevScrollTop.current will both be true, incorrectly re-enabling isStickingToBottom and causing the scroll position to jump to the bottom anyway.

To fix this, we should explicitly ensure that the content did not previously fit (i.e., !contentPreviouslyFit) when re-enabling isStickingToBottom based on wasScrolledToBottomPixels.

Suggested change
// Only re-enable isStickingToBottom when the user is genuinely at the
// bottom (not just when content previously fit). This prevents the scroll
// from jumping back to the bottom when the user has scrolled up to review
// changes (e.g., after Ctrl+S) and new content arrives. (#5009)
if (wasScrolledToBottomPixels && actualScrollTop >= prevScrollTop.current) {
// Only re-enable isStickingToBottom when the user is genuinely at the
// bottom (not just when content previously fit). This prevents the scroll
// from jumping back to the bottom when the user has scrolled up to review
// changes (e.g., after Ctrl+S) and new content arrives. (#5009)
if (!contentPreviouslyFit && wasScrolledToBottomPixels && actualScrollTop >= prevScrollTop.current) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Great catch! You are right — when content fits within the container, wasScrolledToBottomPixels is always true because the threshold goes negative. I have adopted your suggestion and added the !contentPreviouslyFit guard to the condition. The updated check now reads:

if (!contentPreviouslyFit && wasScrolledToBottomPixels && actualScrollTop >= prevScrollTop.current)

This ensures we only stick to the bottom when there was actual scrollable content and the user was at the bottom. Thanks for the precise analysis!

@gemini-cli gemini-cli Bot added priority/p1 Important and should be addressed in the near term. priority/p2 Important but can be addressed in a future release. area/core Issues related to User Interface, OS Support, Core Functionality 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item. labels Jul 15, 2026
…ent fits

When content fits within the container, wasScrolledToBottomPixels evaluates
to true (threshold goes negative). Adding !contentPreviouslyFit explicitly
prevents re-enabling isStickingToBottom in that edge case.

Addresses review feedback from gemini-code-assist.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Issues related to User Interface, OS Support, Core Functionality 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item. priority/p1 Important and should be addressed in the near term. priority/p2 Important but can be addressed in a future release. size/xs An extra small PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scroll position jumps to top on new message arrival

1 participant