Skip to content

Fix: Scrollbar jumps to middle of conversation due to array index shifting in long chats #7052

@daniel-lxs

Description

@daniel-lxs

Problem Description

The chat view scrollbar jumps to the middle of the conversation during long streaming sessions, making it impossible to follow the latest messages. This occurs when conversations exceed 500 messages and is caused by array index shifting in the virtual scrolling implementation.

Root Cause Analysis

The Index Shifting Bug

The issue stems from the message filtering logic in webview-ui/src/components/chat/ChatView.tsx at lines 899-902:

const visibleMessages = useMemo(() => {
    const currentMessageCount = modifiedMessages.length
    const startIndex = Math.max(0, currentMessageCount - 500)
    const recentMessages = modifiedMessages.slice(startIndex)
    // ...

When a conversation exceeds 500 messages, each new message causes:

  1. The startIndex to increment by 1
  2. The oldest message to be dropped from the array
  3. All remaining messages to shift down by one index position
  4. Virtuoso (the virtual scrolling library) to lose track of the current position

The Sequence of Events

  1. Initial State: User is viewing message at index 499 (bottom of 500-message window)
  2. New Message Arrives: Total messages become 501
  3. Array Mutation:
    • startIndex changes from 0 to 1
    • Message at index 0 is dropped
    • Messages 1-500 become indices 0-499
    • New message is added at index 499
  4. Position Lost: Virtuoso was tracking index 499, but that index now contains different content
  5. View Jumps: The viewport jumps to approximately the middle of the conversation (around index 250)

Why Current Fixes Don't Work

  • Number.MAX_SAFE_INTEGER scrolling (lines 1337, 1351): This correctly scrolls to bottom, but array mutations happen between scroll commands
  • Auto-scroll timing (line 1395): Auto-scroll fires every 50ms, but array shifts happen immediately when messages arrive
  • PR fix: improve auto-scroll behavior during API activities and streaming #6989: Helps by keeping auto-scroll active during streaming, but doesn't address the underlying array mutation issue

Reproduction Steps

  1. Start a new conversation with Roo Code
  2. Continue the conversation until it exceeds 500 messages
  3. Observe that during streaming, the view jumps to the middle of the conversation
  4. Scrolling down manually doesn't help as it keeps jumping back up

Technical Details

Affected Components

  • webview-ui/src/components/chat/ChatView.tsx
  • React Virtuoso virtual scrolling library
  • Message array management (lines 899-963)

Related Code Sections

  • Message filtering: Lines 899-902
  • Virtuoso configuration: Lines 1869-1885
  • Auto-scroll logic: Lines 1390-1400
  • Scroll methods: Lines 1335-1356

Proposed Solutions

Option 1: Remove Message Limit (Recommended)

Remove the 500-message slice entirely and let Virtuoso handle virtualization of all messages:

const visibleMessages = useMemo(() => {
    // Remove the slicing logic
    const newVisibleMessages = modifiedMessages.filter((message: ClineMessage) => {
        // Keep existing filter logic
    })
    // ...

Pros:

  • Completely eliminates index shifting
  • Virtuoso is designed to handle large lists efficiently
  • Simplest fix

Cons:

  • Slightly higher memory usage (but still bounded by Virtuoso's virtualization)

Option 2: Track by Stable Keys

Configure Virtuoso to track items by message timestamp (ts) instead of array index:

<Virtuoso
    ref={virtuosoRef}
    key={task.ts}
    className="scrollable grow overflow-y-scroll mb-1"
    increaseViewportBy={{ top: 3_000, bottom: 1000 }}
    data={groupedMessages}
    itemContent={itemContent}
    computeItemKey={(index, item) => Array.isArray(item) ? item[0].ts : item.ts}
    // ...
/>

Pros:

  • Maintains current memory optimization
  • Virtuoso can track position even with array mutations

Cons:

  • Requires testing to ensure Virtuoso handles key-based tracking correctly

Option 3: Force Re-scroll on Array Mutation

Detect when startIndex changes and immediately scroll to bottom:

const prevStartIndexRef = useRef(0)

useEffect(() => {
    const currentMessageCount = modifiedMessages.length
    const startIndex = Math.max(0, currentMessageCount - 500)
    
    if (startIndex !== prevStartIndexRef.current && !disableAutoScrollRef.current) {
        scrollToBottomAuto()
        prevStartIndexRef.current = startIndex
    }
}, [modifiedMessages.length])

Pros:

  • Minimal change to existing code
  • Preserves memory optimization

Cons:

  • May cause visible jumps when re-scrolling
  • Doesn't fix the root cause, just mitigates symptoms

Impact

Testing Requirements

  1. Create a conversation with 600+ messages
  2. Verify scrolling stays at bottom during streaming
  3. Verify manual scroll-up is respected
  4. Verify memory usage remains reasonable
  5. Test with various message types (code blocks, images, etc.)

Related Issues and PRs

Additional Context

This investigation revealed that while PR #6989 helps with auto-scroll behavior, it doesn't address the root cause of array index shifting. A proper fix requires preventing the array indices from changing when new messages arrive in long conversations.

The issue is particularly problematic because:

  1. It makes long conversations effectively unusable
  2. Users cannot follow along with streaming responses
  3. Manual intervention (scrolling) doesn't help as the view keeps jumping

@roomote-agent This issue needs immediate attention as it significantly impacts user experience in long conversations.

Metadata

Metadata

Assignees

Labels

Issue - Needs InfoMissing details or unclear. Waiting on author to provide more context.bugSomething isn't working

Type

No type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions