Skip to content

Commit 6c9ff49

Browse files
SannidhyaSahSannidhya
andauthored
fix: cancel backend auto-approval timeout when auto-approve is toggled off mid-countdown (#11439)
Co-authored-by: Sannidhya <sann@Sannidhyas-MacBook-Pro.local>
1 parent cdf481c commit 6c9ff49

2 files changed

Lines changed: 106 additions & 0 deletions

File tree

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,6 +1442,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14421442
vscode.postMessage({ type: "askResponse", askResponse: "objectResponse", text: JSON.stringify(response) })
14431443
}, [])
14441444

1445+
// Cancel backend auto-approval timeout when FollowUpSuggest's countdown effect cleans up.
1446+
// This is called when auto-approve is toggled off, a suggestion is clicked, or the component unmounts.
1447+
const handleFollowUpUnmount = useCallback(() => {
1448+
vscode.postMessage({ type: "cancelAutoApproval" })
1449+
}, [])
1450+
14451451
const itemContent = useCallback(
14461452
(index: number, messageOrGroup: ClineMessage) => {
14471453
const hasCheckpoint = modifiedMessages.some((message) => message.say === "checkpoint_saved")
@@ -1459,6 +1465,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14591465
isStreaming={isStreaming}
14601466
onSuggestionClick={handleSuggestionClickInRow} // This was already stabilized
14611467
onBatchFileResponse={handleBatchFileResponse}
1468+
onFollowUpUnmount={handleFollowUpUnmount}
14621469
isFollowUpAnswered={messageOrGroup.isAnswered === true || messageOrGroup.ts === currentFollowUpTs}
14631470
isFollowUpAutoApprovalPaused={isFollowUpAutoApprovalPaused}
14641471
editable={
@@ -1489,6 +1496,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14891496
isStreaming,
14901497
handleSuggestionClickInRow,
14911498
handleBatchFileResponse,
1499+
handleFollowUpUnmount,
14921500
currentFollowUpTs,
14931501
isFollowUpAutoApprovalPaused,
14941502
enableButtons,

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

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,4 +592,102 @@ describe("FollowUpSuggest", () => {
592592
expect(screen.getByText(/3s/)).toBeInTheDocument()
593593
})
594594
})
595+
596+
describe("auto-approve toggle off mid-countdown", () => {
597+
it("should call onCancelAutoApproval when autoApprovalEnabled changes to false during countdown", async () => {
598+
const { rerender } = renderWithTestProviders(
599+
<FollowUpSuggest
600+
suggestions={mockSuggestions}
601+
onSuggestionClick={mockOnSuggestionClick}
602+
ts={123}
603+
onCancelAutoApproval={mockOnCancelAutoApproval}
604+
isAnswered={false}
605+
/>,
606+
defaultTestState,
607+
)
608+
609+
// Should show countdown initially
610+
expect(screen.getByText(/3s/)).toBeInTheDocument()
611+
612+
// Advance timer partially
613+
await act(async () => {
614+
vi.advanceTimersByTime(1000)
615+
})
616+
617+
// Countdown should be at 2s
618+
expect(screen.getByText(/2s/)).toBeInTheDocument()
619+
620+
// Clear mock to track calls from the toggle-off
621+
mockOnCancelAutoApproval.mockClear()
622+
623+
// User toggles auto-approve off
624+
rerender(
625+
<TestExtensionStateProvider value={{ ...defaultTestState, autoApprovalEnabled: false }}>
626+
<TooltipProvider>
627+
<FollowUpSuggest
628+
suggestions={mockSuggestions}
629+
onSuggestionClick={mockOnSuggestionClick}
630+
ts={123}
631+
onCancelAutoApproval={mockOnCancelAutoApproval}
632+
isAnswered={false}
633+
/>
634+
</TooltipProvider>
635+
</TestExtensionStateProvider>,
636+
)
637+
638+
// Countdown should disappear
639+
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument()
640+
641+
// onCancelAutoApproval should have been called to cancel the backend timeout
642+
expect(mockOnCancelAutoApproval).toHaveBeenCalled()
643+
644+
// Advance timer past original timeout - nothing should happen
645+
await act(async () => {
646+
vi.advanceTimersByTime(5000)
647+
})
648+
649+
// onSuggestionClick should NOT have been called
650+
expect(mockOnSuggestionClick).not.toHaveBeenCalled()
651+
})
652+
653+
it("should call onCancelAutoApproval when alwaysAllowFollowupQuestions changes to false during countdown", async () => {
654+
const { rerender } = renderWithTestProviders(
655+
<FollowUpSuggest
656+
suggestions={mockSuggestions}
657+
onSuggestionClick={mockOnSuggestionClick}
658+
ts={123}
659+
onCancelAutoApproval={mockOnCancelAutoApproval}
660+
isAnswered={false}
661+
/>,
662+
defaultTestState,
663+
)
664+
665+
// Should show countdown initially
666+
expect(screen.getByText(/3s/)).toBeInTheDocument()
667+
668+
// Clear mock to track calls from the toggle-off
669+
mockOnCancelAutoApproval.mockClear()
670+
671+
// User disables follow-up question auto-approval
672+
rerender(
673+
<TestExtensionStateProvider value={{ ...defaultTestState, alwaysAllowFollowupQuestions: false }}>
674+
<TooltipProvider>
675+
<FollowUpSuggest
676+
suggestions={mockSuggestions}
677+
onSuggestionClick={mockOnSuggestionClick}
678+
ts={123}
679+
onCancelAutoApproval={mockOnCancelAutoApproval}
680+
isAnswered={false}
681+
/>
682+
</TooltipProvider>
683+
</TestExtensionStateProvider>,
684+
)
685+
686+
// Countdown should disappear
687+
expect(screen.queryByText(/\d+s/)).not.toBeInTheDocument()
688+
689+
// onCancelAutoApproval should have been called to cancel the backend timeout
690+
expect(mockOnCancelAutoApproval).toHaveBeenCalled()
691+
})
692+
})
595693
})

0 commit comments

Comments
 (0)