From e0f7770d45f3f83935747df99338e5d8910e91a7 Mon Sep 17 00:00:00 2001 From: Steven Vo Date: Mon, 15 Dec 2025 11:02:38 +0700 Subject: [PATCH] Prevent AI panel auto-scroll when user scrolls up to read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the AI is streaming responses, the panel would continuously auto-scroll to the bottom, making it impossible to read earlier parts of the response. This change detects when the user has manually scrolled up (more than 50px from bottom) and stops auto-scrolling until they scroll back near the bottom. - Add scroll event listener to detect manual user scrolling - Track userHasScrolledUp state - Only auto-scroll if user is at/near bottom - Improves readability of long AI responses 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/app/aipanel/aipanelmessages.tsx | 40 ++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/frontend/app/aipanel/aipanelmessages.tsx b/frontend/app/aipanel/aipanelmessages.tsx index a0284153d..8e32f2035 100644 --- a/frontend/app/aipanel/aipanelmessages.tsx +++ b/frontend/app/aipanel/aipanelmessages.tsx @@ -20,25 +20,59 @@ export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPane const messagesEndRef = useRef(null); const messagesContainerRef = useRef(null); const prevStatusRef = useRef(status); + const userHasScrolledUp = useRef(false); + const isAutoScrolling = useRef(false); const scrollToBottom = () => { const container = messagesContainerRef.current; if (container) { + isAutoScrolling.current = true; container.scrollTop = container.scrollHeight; container.scrollLeft = 0; + userHasScrolledUp.current = false; + setTimeout(() => { + isAutoScrolling.current = false; + }, 100); } }; + // Detect if user has manually scrolled up + useEffect(() => { + const container = messagesContainerRef.current; + if (!container) return; + + const handleScroll = () => { + // Ignore scroll events triggered by our auto-scroll + if (isAutoScrolling.current) return; + + const { scrollTop, scrollHeight, clientHeight } = container; + const distanceFromBottom = scrollHeight - scrollTop - clientHeight; + + // If user is more than 50px from the bottom, they've scrolled up + if (distanceFromBottom > 50) { + userHasScrolledUp.current = true; + } else { + userHasScrolledUp.current = false; + } + }; + + container.addEventListener("scroll", handleScroll); + return () => container.removeEventListener("scroll", handleScroll); + }, []); + useEffect(() => { model.registerScrollToBottom(scrollToBottom); }, [model]); useEffect(() => { - scrollToBottom(); + // Only auto-scroll if user hasn't manually scrolled up + if (!userHasScrolledUp.current) { + scrollToBottom(); + } }, [messages]); useEffect(() => { - if (isPanelOpen) { + if (isPanelOpen && !userHasScrolledUp.current) { scrollToBottom(); } }, [isPanelOpen]); @@ -47,7 +81,7 @@ export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPane const wasStreaming = prevStatusRef.current === "streaming"; const isNowNotStreaming = status !== "streaming"; - if (wasStreaming && isNowNotStreaming) { + if (wasStreaming && isNowNotStreaming && !userHasScrolledUp.current) { requestAnimationFrame(() => { scrollToBottom(); });