diff --git a/frontend/src/components/MessageList.jsx b/frontend/src/components/MessageList.jsx index 6219e1f..d868466 100644 --- a/frontend/src/components/MessageList.jsx +++ b/frontend/src/components/MessageList.jsx @@ -182,6 +182,42 @@ export const MessageList = memo(function MessageList({ messages, isProcessing, o const [newMessageCount, setNewMessageCount] = useState(0); const prevMessageCount = useRef(messages.length); const userScrolledAway = useRef(false); + const prevHostId = useRef(hostId); + + // Cache for incremental updates - avoid full rebuild on streaming content changes + // Moved up so it's available for the reset effect + const processedCacheRef = useRef({ messages: [], result: [], toolResultMap: new Map() }); + + // Reset scroll state when switching sessions (hostId changes or messages array replaced) + // This fixes the "scroll to bottom" button appearing incorrectly after switching from split view + useLayoutEffect(() => { + // Detect session change by checking if hostId changed or if messages were reset + const hostChanged = prevHostId.current !== hostId; + const messagesReset = messages.length === 0 || + (prevMessageCount.current > 0 && messages.length > 0 && + messages[0]?.timestamp !== processedCacheRef.current.messages[0]?.timestamp); + + if (hostChanged || messagesReset) { + // Reset all scroll-related state + userScrolledAway.current = false; + setShowScrollButton(false); + setNewMessageCount(0); + prevMessageCount.current = messages.length; + prevHostId.current = hostId; + + // Clear processed cache to force rebuild + processedCacheRef.current = { messages: [], result: [], toolResultMap: new Map() }; + + // Scroll to bottom after session switch + if (containerRef.current) { + requestAnimationFrame(() => { + if (containerRef.current) { + containerRef.current.scrollTop = containerRef.current.scrollHeight; + } + }); + } + } + }, [hostId, messages]); // Check if scrolled to bottom const checkIfAtBottom = useCallback(() => { @@ -203,9 +239,6 @@ export const MessageList = memo(function MessageList({ messages, isProcessing, o } }, [checkIfAtBottom]); - // Cache for incremental updates - avoid full rebuild on streaming content changes - const processedCacheRef = useRef({ messages: [], result: [], toolResultMap: new Map() }); - // Preprocess messages to pair tool_use with tool_result // Optimized: only rebuild when structure changes, not content const processedMessages = useMemo(() => {