Skip to content

Commit 62fb900

Browse files
wwwillchenclaude
authored andcommitted
Fix scroll behavior when switching between chats (dyad-sh#2643)
## Summary - Fix scroll behavior when switching between chats with existing messages - Use double `requestAnimationFrame` to wait for Virtuoso to render before scrolling to bottom when switching chats - Distinguish between chat switches and new message sends to handle scrolling appropriately - Avoid premature scrolling when switching to chats where messages haven't been fetched yet ## Test plan 1. Open Dyad and start a chat with some messages 2. Start another chat with messages 3. Switch between chats and verify scroll position stays at the bottom 4. Send a new message and verify it scrolls to show the new message 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2643" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes chat auto-scroll to keep the view anchored at the bottom when switching chats or sending messages. Prevents flicker and jumps before messages render. - **Bug Fixes** - Distinguish chat switches vs new sends; adjust scroll timing accordingly. - On chat switch with existing messages, wait for Virtuoso to render (double requestAnimationFrame), then scroll to bottom instantly. - On new message send, wait for the placeholder to render (double requestAnimationFrame) before scrolling; skip auto-scroll when switching to a chat with no messages yet. <sup>Written for commit 8814a60. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Scoped to `ChatPanel` scroll timing logic; risk is limited to possible UI regressions (missed/extra scroll) when switching chats or starting streams. > > **Overview** > Fixes `ChatPanel` auto-scroll behavior by **distinguishing chat switches from new stream starts** and delaying the scroll until after Virtuoso has rendered. > > On chat switch, it now scrolls to bottom *only after messages exist* (avoiding premature scroll before fetch/render) and uses a double `requestAnimationFrame` with `instant` scrolling; on new message sends (`streamCount` changes), it similarly waits for render before scrolling with the default behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 8814a60. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 06997f9 commit 62fb900

1 file changed

Lines changed: 29 additions & 3 deletions

File tree

src/components/ChatPanel.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,38 @@ export function ChatPanel({
7979

8080
// Scroll to bottom when a new stream starts (user sent a message)
8181
const streamCount = chatId ? (streamCountById.get(chatId) ?? 0) : 0;
82+
const messages = chatId ? (messagesById.get(chatId) ?? []) : [];
83+
84+
// Track previous chatId to detect chat switches
85+
const prevChatIdRef = useRef<number | undefined>(undefined);
86+
8287
useEffect(() => {
88+
const isChatSwitch = prevChatIdRef.current !== chatId;
89+
prevChatIdRef.current = chatId;
90+
8391
isAtBottomRef.current = true;
8492
setShowScrollButton(false);
85-
scrollToBottom();
86-
}, [chatId, streamCount, scrollToBottom]);
93+
94+
if (isChatSwitch && messages.length > 0) {
95+
// When switching chats with existing messages, wait for Virtuoso to render
96+
// then scroll to ensure we're at the bottom
97+
requestAnimationFrame(() => {
98+
requestAnimationFrame(() => {
99+
scrollToBottom("instant");
100+
});
101+
});
102+
} else if (!isChatSwitch) {
103+
// For stream count changes (new message sent), wait for Virtuoso to render
104+
// the placeholder message before scrolling
105+
requestAnimationFrame(() => {
106+
requestAnimationFrame(() => {
107+
scrollToBottom();
108+
});
109+
});
110+
}
111+
// Note: if isChatSwitch && messages.length === 0, we don't scroll yet.
112+
// The messages will be fetched and this effect will re-run with messages.length > 0.
113+
}, [chatId, streamCount, messages.length, scrollToBottom]);
87114

88115
const fetchChatMessages = useCallback(async () => {
89116
if (!chatId) {
@@ -102,7 +129,6 @@ export function ChatPanel({
102129
fetchChatMessages();
103130
}, [fetchChatMessages]);
104131

105-
const messages = chatId ? (messagesById.get(chatId) ?? []) : [];
106132
const isStreaming = chatId ? (isStreamingById.get(chatId) ?? false) : false;
107133

108134
// Scroll to bottom when streaming completes to ensure footer content is visible,

0 commit comments

Comments
 (0)