-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Fix scroll behavior when switching between chats #2643
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,11 +79,38 @@ export function ChatPanel({ | |
|
|
||
| // Scroll to bottom when a new stream starts (user sent a message) | ||
| const streamCount = chatId ? (streamCountById.get(chatId) ?? 0) : 0; | ||
| const messages = chatId ? (messagesById.get(chatId) ?? []) : []; | ||
|
|
||
| // Track previous chatId to detect chat switches | ||
| const prevChatIdRef = useRef<number | undefined>(undefined); | ||
|
|
||
| useEffect(() => { | ||
| const isChatSwitch = prevChatIdRef.current !== chatId; | ||
| prevChatIdRef.current = chatId; | ||
|
|
||
| isAtBottomRef.current = true; | ||
| setShowScrollButton(false); | ||
| scrollToBottom(); | ||
| }, [chatId, streamCount, scrollToBottom]); | ||
|
|
||
| if (isChatSwitch && messages.length > 0) { | ||
| // When switching chats with existing messages, wait for Virtuoso to render | ||
| // then scroll to ensure we're at the bottom | ||
| requestAnimationFrame(() => { | ||
| requestAnimationFrame(() => { | ||
| scrollToBottom("instant"); | ||
| }); | ||
| }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stale RAF causes cross-chat scroll jumpsMedium Severity The double |
||
| } else if (!isChatSwitch) { | ||
| // For stream count changes (new message sent), wait for Virtuoso to render | ||
| // the placeholder message before scrolling | ||
| requestAnimationFrame(() => { | ||
| requestAnimationFrame(() => { | ||
| scrollToBottom(); | ||
| }); | ||
| }); | ||
|
Comment on lines
+102
to
+109
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MEDIUM: Flagged by 3/3 independent reviewers (MEDIUM, LOW, LOW) The The comment says "For stream count changes (new message sent)" but the code also triggers on message-count changes. This creates additional scroll-to-bottom calls during streaming that may conflict with Virtuoso's Consider guarding this branch with a check that Generated by Dyadbot multi-agent code review |
||
| } | ||
| // Note: if isChatSwitch && messages.length === 0, we don't scroll yet. | ||
| // The messages will be fetched and this effect will re-run with messages.length > 0. | ||
| }, [chatId, streamCount, messages.length, scrollToBottom]); | ||
|
Comment on lines
+82
to
+113
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic has a subtle bug. When switching to a chat where messages aren't cached, they are fetched asynchronously. When they arrive, this To fix this and ensure an instant scroll in all chat-switch scenarios, we can use an additional
Comment on lines
87
to
+113
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Chat switch scroll logic fails when messages are fetched asynchronously (uncached chats) When switching to a chat whose messages are not yet in the Root CauseThe effect at lines 87-108 tracks chat switches via
This means:
The comment at lines 104-105 acknowledges this re-run scenario but incorrectly assumes it will be handled properly. In practice, the Impact: When switching to a chat that hasn't been visited yet in the current session (messages not cached), the user may see a brief flash of content at the wrong scroll position, or a smooth scroll animation instead of an instant jump to the bottom. Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| const fetchChatMessages = useCallback(async () => { | ||
| if (!chatId) { | ||
|
|
@@ -102,7 +129,6 @@ export function ChatPanel({ | |
| fetchChatMessages(); | ||
| }, [fetchChatMessages]); | ||
|
|
||
| const messages = chatId ? (messagesById.get(chatId) ?? []) : []; | ||
| const isStreaming = chatId ? (isStreamingById.get(chatId) ?? false) : false; | ||
|
|
||
| // Scroll to bottom when streaming completes to ensure footer content is visible, | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 HIGH: Unconditionally resetting
isAtBottomRefdefeats user scroll position during streamingFlagged by 2/3 independent reviewers (HIGH, MEDIUM)
Lines 91-92 unconditionally set
isAtBottomRef.current = trueandsetShowScrollButton(false)on every effect run. Sincemessages.lengthis now in the dependency array, this means whenever a new message arrives during streaming, the user's scroll position state is forcibly reset to "at bottom" and the scroll button is hidden — even if the user has scrolled up to read earlier messages.The previous code only did this on
chatIdorstreamCountchanges, which was more intentional. Now withmessages.lengthas a trigger, this reset happens too frequently and can break the user's ability to stay scrolled up during a stream.Move the unconditional reset inside the
if (isChatSwitch)block so it only fires on actual chat switches, not on everymessages.lengthchange during streaming.Generated by Dyadbot multi-agent code review