Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 7a3632e

Browse files
committed
fix: show stop button in chat input during follow-up questions
When the agent asks a follow-up question, the normal stop button in the chat input area was not visible because isStreaming is false during asks. This left users with no way to abort the task during follow-up questions. Added a dedicated stop button that appears in the chat text area when there is an active follow-up question (canStopTask prop). This button is separate from the morphing send/stop button, so users can both send a response AND stop the task. Fixes #12268
1 parent ad25634 commit 7a3632e

3 files changed

Lines changed: 82 additions & 0 deletions

File tree

webview-ui/src/components/chat/ChatTextArea.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ interface ChatTextAreaProps {
5454
onCancel?: () => void
5555
// Stop/Queue functionality
5656
isStreaming?: boolean
57+
canStopTask?: boolean
5758
onStop?: () => void
5859
onEnqueueMessage?: () => void
5960
}
@@ -77,6 +78,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
7778
isEditMode = false,
7879
onCancel,
7980
isStreaming = false,
81+
canStopTask = false,
8082
onStop,
8183
onEnqueueMessage,
8284
},
@@ -1219,6 +1221,30 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
12191221
</button>
12201222
</StandardTooltip>
12211223
)}
1224+
{/* Stop button - shown when task is active (e.g. during follow-up questions) but not streaming */}
1225+
{!isEditMode && !isStreaming && canStopTask && (
1226+
<StandardTooltip content={t("chat:stop.title")}>
1227+
<button
1228+
aria-label={t("chat:stop.title")}
1229+
disabled={false}
1230+
onClick={onStop}
1231+
className={cn(
1232+
"relative inline-flex items-center justify-center",
1233+
"bg-transparent border-none p-1.5",
1234+
"rounded-full min-w-[28px] min-h-[28px]",
1235+
"text-vscode-descriptionForeground hover:text-vscode-foreground",
1236+
"transition-all duration-200",
1237+
"opacity-100 hover:opacity-100 pointer-events-auto",
1238+
"hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]",
1239+
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
1240+
"active:bg-[rgba(255,255,255,0.1)]",
1241+
"cursor-pointer",
1242+
"bg-vscode-button-background hover:bg-vscode-button-background",
1243+
)}>
1244+
<Square className="size-4 stroke-none fill-vscode-button-foreground" />
1245+
</button>
1246+
</StandardTooltip>
1247+
)}
12221248
{/* Send/Stop button - morphs based on streaming state, always visible in edit mode */}
12231249
<StandardTooltip
12241250
content={

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,6 +1814,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
18141814
setMode={setMode}
18151815
modeShortcutText={modeShortcutText}
18161816
isStreaming={isStreaming}
1817+
canStopTask={!!task && !isStreaming && clineAsk === "followup"}
18171818
onStop={handleStopTask}
18181819
onEnqueueMessage={handleEnqueueCurrentMessage}
18191820
/>

webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,4 +1205,59 @@ describe("ChatTextArea", () => {
12051205
expect(sendButton).toHaveClass("pointer-events-auto")
12061206
})
12071207
})
1208+
1209+
describe("stop button during follow-up questions", () => {
1210+
it("should show a stop button when canStopTask is true and not streaming", () => {
1211+
const onStop = vi.fn()
1212+
const { container } = render(
1213+
<ChatTextArea {...defaultProps} canStopTask={true} isStreaming={false} onStop={onStop} />,
1214+
)
1215+
1216+
// Find the stop button by looking for the button with the Square icon (fill style)
1217+
const buttons = container.querySelectorAll("button")
1218+
const stopButton = Array.from(buttons).find((button) => button.querySelector(".lucide-square") !== null)
1219+
1220+
expect(stopButton).toBeInTheDocument()
1221+
})
1222+
1223+
it("should not show the separate stop button when canStopTask is false", () => {
1224+
const onStop = vi.fn()
1225+
const { container } = render(
1226+
<ChatTextArea {...defaultProps} canStopTask={false} isStreaming={false} onStop={onStop} />,
1227+
)
1228+
1229+
// Should not find any button with Square icon when not streaming and canStopTask is false
1230+
const buttons = container.querySelectorAll("button")
1231+
const stopButton = Array.from(buttons).find((button) => button.querySelector(".lucide-square") !== null)
1232+
1233+
expect(stopButton).not.toBeDefined()
1234+
})
1235+
1236+
it("should not show the separate stop button when isStreaming is true (morphed button handles it)", () => {
1237+
const onStop = vi.fn()
1238+
const { container } = render(
1239+
<ChatTextArea {...defaultProps} canStopTask={true} isStreaming={true} onStop={onStop} />,
1240+
)
1241+
1242+
// When streaming, the morphed send/stop button shows the Square icon
1243+
// but the separate stop button should NOT be rendered
1244+
const buttons = container.querySelectorAll("button")
1245+
const squareButtons = Array.from(buttons).filter(
1246+
(button) => button.querySelector(".lucide-square") !== null,
1247+
)
1248+
1249+
// Only 1 square button (the morphed send/stop), not 2
1250+
expect(squareButtons.length).toBe(1)
1251+
})
1252+
1253+
it("should call onStop when the stop button is clicked during a follow-up question", () => {
1254+
const onStop = vi.fn()
1255+
render(<ChatTextArea {...defaultProps} canStopTask={true} isStreaming={false} onStop={onStop} />)
1256+
1257+
const stopButton = screen.getByRole("button", { name: /stop/i })
1258+
fireEvent.click(stopButton)
1259+
1260+
expect(onStop).toHaveBeenCalledTimes(1)
1261+
})
1262+
})
12081263
})

0 commit comments

Comments
 (0)