Skip to content

Commit 04b7b35

Browse files
committed
code review: refactor & race conditions
1 parent 78f92b7 commit 04b7b35

File tree

1 file changed

+32
-21
lines changed

1 file changed

+32
-21
lines changed

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

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
163163
}),
164164
)
165165
const autoApproveTimeoutRef = useRef<NodeJS.Timeout | null>(null)
166-
const [followUpAnswered, setFollowUpAnswered] = useState<Set<number>>(new Set())
166+
const [currentFollowUpTs, setCurrentFollowUpTs] = useState<number | null>(null)
167167

168168
const clineAskRef = useRef(clineAsk)
169169
useEffect(() => {
@@ -416,7 +416,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
416416
useEffect(() => {
417417
setExpandedRows({})
418418
everVisibleMessagesTsRef.current.clear() // Clear for new task
419-
setFollowUpAnswered(new Set()) // Clear follow-up answered state for new task
419+
setCurrentFollowUpTs(null) // Clear follow-up answered state for new task
420420
}, [task?.ts])
421421

422422
useEffect(() => {
@@ -488,6 +488,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
488488
return false
489489
}, [modifiedMessages, clineAsk, enableButtons, primaryButtonText])
490490

491+
const markFollowUpAsAnswered = useCallback(() => {
492+
const lastFollowUpMessage = messagesRef.current.findLast((msg) => msg.ask === "followup")
493+
if (lastFollowUpMessage) {
494+
setCurrentFollowUpTs(lastFollowUpMessage.ts)
495+
}
496+
}, [])
497+
491498
const handleChatReset = useCallback(() => {
492499
// Clear any pending auto-approval timeout to prevent race conditions
493500
if (autoApproveTimeoutRef.current) {
@@ -515,19 +522,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
515522
if (messagesRef.current.length === 0) {
516523
vscode.postMessage({ type: "newTask", text, images })
517524
} else if (clineAskRef.current) {
518-
// Clear auto-approval timeout when user sends a message for follow-up questions
525+
// Clear auto-approval timeout FIRST to prevent race conditions
519526
// This ensures the countdown timer is properly dismounted when users submit custom responses
520-
if (clineAskRef.current === "followup") {
521-
if (autoApproveTimeoutRef.current) {
522-
clearTimeout(autoApproveTimeoutRef.current)
523-
autoApproveTimeoutRef.current = null
524-
}
527+
if (autoApproveTimeoutRef.current) {
528+
clearTimeout(autoApproveTimeoutRef.current)
529+
autoApproveTimeoutRef.current = null
530+
}
525531

526-
// Mark the current follow-up question as answered
527-
const lastFollowUpMessage = messagesRef.current.findLast((msg) => msg.ask === "followup")
528-
if (lastFollowUpMessage) {
529-
setFollowUpAnswered((prev) => new Set(prev).add(lastFollowUpMessage.ts))
530-
}
532+
if (clineAskRef.current === "followup") {
533+
markFollowUpAsAnswered()
531534
}
532535

533536
// Use clineAskRef.current
@@ -553,7 +556,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
553556
handleChatReset()
554557
}
555558
},
556-
[handleChatReset], // messagesRef and clineAskRef are stable
559+
[handleChatReset, markFollowUpAsAnswered], // messagesRef and clineAskRef are stable
557560
)
558561

559562
const handleSetChatBoxMessage = useCallback(
@@ -1237,12 +1240,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
12371240

12381241
const handleSuggestionClickInRow = useCallback(
12391242
(suggestion: SuggestionItem, event?: React.MouseEvent) => {
1243+
// Clear auto-approval timeout to prevent race conditions when suggestion is clicked
1244+
if (autoApproveTimeoutRef.current) {
1245+
clearTimeout(autoApproveTimeoutRef.current)
1246+
autoApproveTimeoutRef.current = null
1247+
}
1248+
12401249
// Mark the current follow-up question as answered when a suggestion is clicked
12411250
if (clineAsk === "followup" && !event?.shiftKey) {
1242-
const lastFollowUpMessage = messagesRef.current.findLast((msg) => msg.ask === "followup")
1243-
if (lastFollowUpMessage) {
1244-
setFollowUpAnswered((prev) => new Set(prev).add(lastFollowUpMessage.ts))
1245-
}
1251+
markFollowUpAsAnswered()
12461252
}
12471253

12481254
// Check if we need to switch modes
@@ -1264,7 +1270,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
12641270
handleSendMessage(suggestion.answer, [])
12651271
}
12661272
},
1267-
[handleSendMessage, setInputValue, switchToMode, alwaysAllowModeSwitch, clineAsk],
1273+
[handleSendMessage, setInputValue, switchToMode, alwaysAllowModeSwitch, clineAsk, markFollowUpAsAnswered],
12681274
)
12691275

12701276
const handleBatchFileResponse = useCallback((response: { [key: string]: boolean }) => {
@@ -1317,7 +1323,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
13171323
onSuggestionClick={handleSuggestionClickInRow} // This was already stabilized
13181324
onBatchFileResponse={handleBatchFileResponse}
13191325
onFollowUpUnmount={handleFollowUpUnmount}
1320-
isFollowUpAnswered={followUpAnswered.has(messageOrGroup.ts)}
1326+
isFollowUpAnswered={messageOrGroup.ts === currentFollowUpTs}
13211327
/>
13221328
)
13231329
},
@@ -1331,7 +1337,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
13311337
handleSuggestionClickInRow,
13321338
handleBatchFileResponse,
13331339
handleFollowUpUnmount,
1334-
followUpAnswered,
1340+
currentFollowUpTs,
13351341
],
13361342
)
13371343

@@ -1364,6 +1370,11 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
13641370
autoApproveTimeoutRef.current = setTimeout(resolve, followupAutoApproveTimeoutMs)
13651371
})
13661372

1373+
// Check if timeout was cleared (user responded manually)
1374+
if (!autoApproveTimeoutRef.current) {
1375+
return
1376+
}
1377+
13671378
// Get the first suggestion
13681379
const firstSuggestion = followUpData.suggest[0]
13691380

0 commit comments

Comments
 (0)