mirror of
https://github.com/cline/cline.git
synced 2026-08-30 17:20:20 +08:00
Fix queued prompt row alignment and auto-scroll when queueing a message (#12767)
* Fix queued prompt row alignment and auto-scroll on queue Center the dot, badges, and cancel button on the first text line of each queued prompt row (the X previously sat ~3px below the text), and re-pin the chat view to the bottom when a prompt is queued so the queue banner doesn't cover the end of the conversation. * Don't treat task switches as queue growth for auto-scroll Guard the queued-prompt auto-scroll effect on the displayed task's ts: switching to a task that already has queued prompts grows the count without a send from this webview, and should not hijack the newly opened conversation's scroll position.
This commit is contained in:
@@ -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..."
|
||||
|
||||
+10
-5
@@ -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 (
|
||||
<div
|
||||
className="flex items-start gap-2 rounded-[3px] bg-input-background/40 px-2 py-1.5 text-xs leading-snug"
|
||||
className="flex items-start gap-2 rounded-[3px] bg-input-background/40 px-2 py-1.5 text-xs"
|
||||
key={item.id}>
|
||||
<span aria-hidden="true" className="mt-[5px] size-1.5 shrink-0 rounded-full bg-description/70" />
|
||||
<span aria-hidden="true" className="mt-1.75 size-1.5 shrink-0 rounded-full bg-description/70" />
|
||||
<span className="min-w-0 flex-1 break-words text-foreground">{truncatePrompt(item.prompt)}</span>
|
||||
{isSteer && (
|
||||
<span className="shrink-0 rounded-[3px] border border-editor-group-border px-1.5 py-[1px] text-[10px] leading-4 text-description">
|
||||
<span className="flex h-5 shrink-0 items-center rounded-[3px] border border-editor-group-border px-1.5 text-[10px] leading-none text-description">
|
||||
Steer
|
||||
</span>
|
||||
)}
|
||||
{attachments && (
|
||||
<span className="shrink-0 rounded-[3px] border border-editor-group-border px-1.5 py-[1px] text-[10px] leading-4 text-description">
|
||||
<span className="flex h-5 shrink-0 items-center rounded-[3px] border border-editor-group-border px-1.5 text-[10px] leading-none text-description">
|
||||
{attachments}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
aria-label="Cancel queued message"
|
||||
className="mt-[-2px] flex size-5 shrink-0 items-center justify-center rounded-[3px] text-description hover:bg-toolbar-hover-background hover:text-foreground disabled:pointer-events-none disabled:opacity-50"
|
||||
className="-my-1.5 flex size-5 shrink-0 items-center justify-center rounded-[3px] text-description hover:bg-toolbar-hover-background hover:text-foreground disabled:pointer-events-none disabled:opacity-50"
|
||||
disabled={isCancelling}
|
||||
onClick={() => cancelQueuedPrompt(item.id)}
|
||||
title="Cancel queued message"
|
||||
|
||||
Reference in New Issue
Block a user