diff --git a/apps/vscode/webview-ui/src/components/chat/ChatView.tsx b/apps/vscode/webview-ui/src/components/chat/ChatView.tsx index 4372cdd1f4..53a7bdb22e 100644 --- a/apps/vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/apps/vscode/webview-ui/src/components/chat/ChatView.tsx @@ -339,6 +339,36 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie // Use scroll behavior hook const scrollBehavior = useScrollBehavior(displayMessages, visibleMessages, groupedMessages, expandedRows, setExpandedRows) + const { scrollToBottomSmooth, scrollToBottomAuto, disableAutoScrollRef } = scrollBehavior + + // When a prompt gets queued, the queue banner mounts (or grows) in the footer, which + // shrinks the messages area and visually covers the bottom of the conversation. No new + // chat row is added, so the list-length-based auto-scroll never fires — re-pin to the + // bottom here so the latest content stays visible. + const queuedPromptCount = queuedPrompts?.length ?? 0 + const taskTs = task?.ts + const prevQueuedPromptCountRef = useRef(queuedPromptCount) + const prevQueuedPromptTaskTsRef = useRef(taskTs) + useEffect(() => { + const previousCount = prevQueuedPromptCountRef.current + const previousTaskTs = prevQueuedPromptTaskTsRef.current + prevQueuedPromptCountRef.current = queuedPromptCount + prevQueuedPromptTaskTsRef.current = taskTs + // A task switch can grow the count without a send from this webview (the newly + // displayed task may already have queued prompts) — don't hijack its scroll position. + if (taskTs !== previousTaskTs || queuedPromptCount <= previousCount) { + return + } + // Queueing is a deliberate send, so re-engage bottom pinning like handleSendMessage does. + disableAutoScrollRef.current = false + scrollToBottomSmooth() + // Settle with an instant scroll once the footer's layout change has landed. + setTimeout(() => { + if (!disableAutoScrollRef.current) { + scrollToBottomAuto() + } + }, 50) + }, [queuedPromptCount, taskTs, scrollToBottomSmooth, scrollToBottomAuto, disableAutoScrollRef]) const placeholderText = useMemo(() => { const text = task ? "Type a message..." : "Type your task here..." diff --git a/apps/vscode/webview-ui/src/components/chat/chat-view/components/layout/QueuedPrompts.tsx b/apps/vscode/webview-ui/src/components/chat/chat-view/components/layout/QueuedPrompts.tsx index e60924d02c..120879214c 100644 --- a/apps/vscode/webview-ui/src/components/chat/chat-view/components/layout/QueuedPrompts.tsx +++ b/apps/vscode/webview-ui/src/components/chat/chat-view/components/layout/QueuedPrompts.tsx @@ -64,25 +64,30 @@ export function QueuedPrompts({ items = [] }: QueuedPromptsProps) { const attachments = attachmentLabel(item.attachmentCount) const isSteer = item.delivery === "steer" const isCancelling = cancellingIds.has(item.id) + // The prompt text renders in spans whose line boxes are 5 spacing units tall + // (the global `span { @apply leading-5 }` base style), so every control in the + // row is sized/offset against that same 5-unit first line to stay vertically + // centered with it: the dot ((5 - 1.5) / 2 units), the h-5 badges, and the + // size-5 (26px) cancel button pulled in by -my-1.5 ((5u - size-5) / 2). return (