From 4740922b8de82d9634155cc0593fc94216f83884 Mon Sep 17 00:00:00 2001 From: teernisse Date: Wed, 25 Feb 2026 16:32:00 -0500 Subject: [PATCH] refactor(dashboard): simplify chat rendering and add scroll behavior Removes unnecessary complexity from ChatMessages while adding proper scroll management to SessionCard: ChatMessages.js: - Remove scroll position tracking refs and effects (wasAtBottomRef, prevMessagesLenRef, containerRef) - Remove spinner display logic (moved to parent components) - Simplify to pure message filtering and rendering - Add display limit (last 20 messages) with offset tracking for keys SessionCard.js: - Add chatPaneRef for scroll container - Add useEffect to scroll to bottom when conversation updates - Provides natural "follow" behavior for new messages The refactor moves scroll responsibility to the component that owns the scroll container, reducing prop drilling and effect complexity. Co-Authored-By: Claude Opus 4.5 --- dashboard/components/ChatMessages.js | 77 +++++----------------------- dashboard/components/SessionCard.js | 14 +++-- 2 files changed, 25 insertions(+), 66 deletions(-) diff --git a/dashboard/components/ChatMessages.js b/dashboard/components/ChatMessages.js index 6a75935..16f03b2 100644 --- a/dashboard/components/ChatMessages.js +++ b/dashboard/components/ChatMessages.js @@ -1,50 +1,9 @@ -import { html, useRef, useEffect } from '../lib/preact.js'; -import { getUserMessageBg, getStatusMeta } from '../utils/status.js'; +import { html } from '../lib/preact.js'; +import { getUserMessageBg } from '../utils/status.js'; import { MessageBubble, filterDisplayMessages } from './MessageBubble.js'; export function ChatMessages({ messages, status }) { - const statusMeta = getStatusMeta(status); - const containerRef = useRef(null); const userBgClass = getUserMessageBg(status); - const wasAtBottomRef = useRef(true); - const prevMessagesLenRef = useRef(0); - - // Scroll to bottom on initial mount - useEffect(() => { - const container = containerRef.current; - if (!container) return; - - // Always scroll to bottom on first render - container.scrollTop = container.scrollHeight; - }, []); - - // Check if scrolled to bottom before render - useEffect(() => { - const container = containerRef.current; - if (!container) return; - - const checkScroll = () => { - const threshold = 50; - const isAtBottom = container.scrollHeight - container.scrollTop - container.clientHeight < threshold; - wasAtBottomRef.current = isAtBottom; - }; - - container.addEventListener('scroll', checkScroll); - return () => container.removeEventListener('scroll', checkScroll); - }, []); - - // Scroll to bottom on new messages if user was at bottom - useEffect(() => { - const container = containerRef.current; - if (!container || !messages) return; - - const hasNewMessages = messages.length > prevMessagesLenRef.current; - prevMessagesLenRef.current = messages.length; - - if (hasNewMessages && wasAtBottomRef.current) { - container.scrollTop = container.scrollHeight; - } - }, [messages]); if (!messages || messages.length === 0) { return html` @@ -54,28 +13,20 @@ export function ChatMessages({ messages, status }) { `; } - const displayMessages = filterDisplayMessages(messages).slice(-20); + const allDisplayMessages = filterDisplayMessages(messages); + const displayMessages = allDisplayMessages.slice(-20); + const offset = allDisplayMessages.length - displayMessages.length; return html` -
-
- ${displayMessages.map((msg, i) => html` - <${MessageBubble} - key=${i} - msg=${msg} - userBg=${userBgClass} - compact=${true} - /> - `)} -
- ${statusMeta.spinning && html` -
- - - ... - -
- `} +
+ ${displayMessages.map((msg, i) => html` + <${MessageBubble} + key=${`${msg.role}-${msg.timestamp || (offset + i)}`} + msg=${msg} + userBg=${userBgClass} + compact=${true} + /> + `)}
`; } diff --git a/dashboard/components/SessionCard.js b/dashboard/components/SessionCard.js index 69061a2..676110f 100644 --- a/dashboard/components/SessionCard.js +++ b/dashboard/components/SessionCard.js @@ -1,4 +1,4 @@ -import { html, useEffect } from '../lib/preact.js'; +import { html, useEffect, useRef } from '../lib/preact.js'; import { getStatusMeta } from '../utils/status.js'; import { formatDuration, getContextUsageSummary } from '../utils/formatting.js'; import { ChatMessages } from './ChatMessages.js'; @@ -19,6 +19,14 @@ export function SessionCard({ session, onClick, conversation, onFetchConversatio } }, [session.session_id, session.project_dir, agent, conversation, onFetchConversation]); + const chatPaneRef = useRef(null); + + // Scroll chat pane to bottom when conversation loads or updates + useEffect(() => { + const el = chatPaneRef.current; + if (el) el.scrollTop = el.scrollHeight; + }, [conversation]); + const handleDismissClick = (e) => { e.stopPropagation(); onDismiss(session.session_id); @@ -26,7 +34,7 @@ export function SessionCard({ session, onClick, conversation, onFetchConversatio return html`
onClick(session)} > @@ -77,7 +85,7 @@ export function SessionCard({ session, onClick, conversation, onFetchConversatio
-
+
<${ChatMessages} messages=${conversation || []} status=${session.status} />