From 5c40bb0dae41a747e17e3edce6e8aa082d467db3 Mon Sep 17 00:00:00 2001 From: Tony Giorgio Date: Fri, 15 Aug 2025 10:59:41 -0500 Subject: [PATCH 1/2] fix: improve auto-scroll behavior in chat interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove auto-scroll when assistant finishes streaming - Keep auto-scroll when user sends a message - Keep auto-scroll when assistant starts streaming - Allows users to read and scroll at their own pace without interruption 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- frontend/src/routes/_auth.chat.$chatId.tsx | 54 ++++++++++++++-------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/frontend/src/routes/_auth.chat.$chatId.tsx b/frontend/src/routes/_auth.chat.$chatId.tsx index 3ca03827e..bf427119f 100644 --- a/frontend/src/routes/_auth.chat.$chatId.tsx +++ b/frontend/src/routes/_auth.chat.$chatId.tsx @@ -327,17 +327,17 @@ function ChatComponent() { const isLoading = phase === "streaming"; const isPersisting = phase === "persisting"; - // Auto-scroll when new messages appear (user message or start of streaming) - const prevMessageCountRef = useRef(localChat.messages.length); - const prevStreamingRef = useRef(false); + // Auto-scroll when user sends message (new user message appears) + const prevUserMessageCountRef = useRef( + localChat.messages.filter((m) => m.role === "user").length + ); useEffect(() => { - const messageCount = localChat.messages.length; - const hasNewMessage = messageCount > prevMessageCountRef.current; - const justStartedStreaming = isLoading && !prevStreamingRef.current; + const userMessageCount = localChat.messages.filter((m) => m.role === "user").length; + const hasNewUserMessage = userMessageCount > prevUserMessageCountRef.current; - if (hasNewMessage || justStartedStreaming) { - // Always scroll for new user messages or when streaming starts + if (hasNewUserMessage) { + // Scroll when user sends a message const container = chatContainerRef.current; if (container) { requestAnimationFrame(() => { @@ -349,9 +349,32 @@ function ChatComponent() { } } - prevMessageCountRef.current = messageCount; - prevStreamingRef.current = isLoading; - }, [localChat.messages.length, isLoading]); + prevUserMessageCountRef.current = userMessageCount; + }, [localChat.messages]); + + // Auto-scroll when assistant starts streaming (currentStreamingMessage appears) + const prevHadStreamingMessage = useRef(false); + + useEffect(() => { + const hasStreamingMessage = !!currentStreamingMessage; + const justStartedStreaming = hasStreamingMessage && !prevHadStreamingMessage.current; + + if (justStartedStreaming) { + // Scroll when assistant starts streaming + const container = chatContainerRef.current; + if (container) { + // Small delay to ensure the streaming message box is rendered + setTimeout(() => { + container.scrollTo({ + top: container.scrollHeight, + behavior: "smooth" + }); + }, 100); + } + } + + prevHadStreamingMessage.current = hasStreamingMessage; + }, [currentStreamingMessage]); const sendMessage = useCallback( async ( @@ -363,14 +386,7 @@ function ChatComponent() { ) => { // Use the appendUserMessage from the hook with system prompt as separate parameter await appendUserMessage(input, images, documentText, documentMetadata, systemPrompt); - - // Scroll to bottom after sending - requestAnimationFrame(() => { - chatContainerRef.current?.scrollTo({ - top: chatContainerRef.current.scrollHeight, - behavior: "smooth" - }); - }); + // Note: Auto-scrolling is handled by the effect that watches for streaming start }, [appendUserMessage] ); From f275c5c8a0f1521e92427eac6a35f29ea4e880b4 Mon Sep 17 00:00:00 2001 From: Tony Giorgio Date: Fri, 15 Aug 2025 11:05:26 -0500 Subject: [PATCH 2/2] fix: disable auto-focus on mobile to prevent keyboard popup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Skip auto-focus logic when on mobile devices - Prevents keyboard from popping up after assistant finishes streaming - Improves mobile user experience by not disrupting reading flow - Desktop behavior remains unchanged with auto-focus still active 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- frontend/src/components/ChatBox.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/ChatBox.tsx b/frontend/src/components/ChatBox.tsx index f257d0c55..aa1eb17a0 100644 --- a/frontend/src/components/ChatBox.tsx +++ b/frontend/src/components/ChatBox.tsx @@ -563,10 +563,12 @@ export default function Component({ setDocumentError(null); setImageError(null); - // Re-focus input after submitting - setTimeout(() => { - inputRef.current?.focus(); - }, 0); + // Re-focus input after submitting (desktop only) + if (!isMobile) { + setTimeout(() => { + inputRef.current?.focus(); + }, 0); + } }; const handleKeyDown = (e: React.KeyboardEvent) => { @@ -678,6 +680,11 @@ export default function Component({ // Auto-focus effect - runs on mount, when chat ID changes, and after streaming completes useEffect(() => { + // Skip auto-focus on mobile to prevent keyboard popup + if (isMobile) { + return; + } + // Skip if user is already focused on an input elsewhere if (document.activeElement?.matches("input, textarea")) { return; @@ -692,7 +699,7 @@ export default function Component({ }, 100); return () => clearTimeout(timer); - }, [chatId, isStreaming, isInputDisabled]); // Re-run when chat ID changes, streaming completes, or input state changes + }, [chatId, isStreaming, isInputDisabled, isMobile]); // Re-run when chat ID changes, streaming completes, or input state changes // Cleanup effect for object URLs useEffect(() => {