@@ -21,6 +21,7 @@ import ModesView from "./components/modes/ModesView"
2121import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog"
2222import { DeleteMessageDialog , EditMessageDialog } from "./components/chat/MessageModificationConfirmationDialog"
2323import ErrorBoundary from "./components/ErrorBoundary"
24+ import { DialogErrorBoundary } from "./components/ui/DialogErrorBoundary"
2425import { AccountView } from "./components/account/AccountView"
2526import { useAddNonInteractiveClickListener } from "./components/ui/hooks/useNonInteractiveClick"
2627import { TooltipProvider } from "./components/ui/tooltip"
@@ -207,6 +208,57 @@ const App = () => {
207208 console . debug ( "App initialized with source map support" )
208209 } , [ ] )
209210
211+ // Dialog recovery mechanism - detect and close stuck dialogs
212+ useEffect ( ( ) => {
213+ // Set up a timeout to check for stuck dialogs after 30 seconds
214+ const timeoutId = setTimeout ( ( ) => {
215+ // Check if any dialog is open but the app seems unresponsive
216+ const hasStuckDialog =
217+ humanRelayDialogState . isOpen || deleteMessageDialogState . isOpen || editMessageDialogState . isOpen
218+
219+ if ( hasStuckDialog ) {
220+ console . warn ( "Detected potentially stuck dialog, attempting recovery" )
221+
222+ // Reset all dialog states
223+ setHumanRelayDialogState ( { isOpen : false , requestId : "" , promptText : "" } )
224+ setDeleteMessageDialogState ( { isOpen : false , messageTs : 0 } )
225+ setEditMessageDialogState ( { isOpen : false , messageTs : 0 , text : "" , images : [ ] } )
226+
227+ // Log telemetry for debugging
228+ telemetryClient . capture ( "dialog_recovery_triggered" , {
229+ humanRelayOpen : humanRelayDialogState . isOpen ,
230+ deleteMessageOpen : deleteMessageDialogState . isOpen ,
231+ editMessageOpen : editMessageDialogState . isOpen ,
232+ } )
233+ }
234+ } , 30000 ) // 30 seconds timeout
235+
236+ return ( ) => clearTimeout ( timeoutId )
237+ } , [ humanRelayDialogState . isOpen , deleteMessageDialogState . isOpen , editMessageDialogState . isOpen ] )
238+
239+ // Add keyboard shortcut for manual dialog recovery (Escape key)
240+ useEffect ( ( ) => {
241+ const handleKeyDown = ( e : KeyboardEvent ) => {
242+ // Check if Escape key is pressed and any dialog is open
243+ if ( e . key === "Escape" ) {
244+ const hasOpenDialog =
245+ humanRelayDialogState . isOpen || deleteMessageDialogState . isOpen || editMessageDialogState . isOpen
246+
247+ if ( hasOpenDialog ) {
248+ console . log ( "Manual dialog recovery triggered via Escape key" )
249+
250+ // Close all dialogs
251+ setHumanRelayDialogState ( { isOpen : false , requestId : "" , promptText : "" } )
252+ setDeleteMessageDialogState ( { isOpen : false , messageTs : 0 } )
253+ setEditMessageDialogState ( { isOpen : false , messageTs : 0 , text : "" , images : [ ] } )
254+ }
255+ }
256+ }
257+
258+ window . addEventListener ( "keydown" , handleKeyDown )
259+ return ( ) => window . removeEventListener ( "keydown" , handleKeyDown )
260+ } , [ humanRelayDialogState . isOpen , deleteMessageDialogState . isOpen , editMessageDialogState . isOpen ] )
261+
210262 // Focus the WebView when non-interactive content is clicked (only in editor/tab mode)
211263 useAddNonInteractiveClickListener (
212264 useCallback ( ( ) => {
@@ -260,38 +312,44 @@ const App = () => {
260312 showAnnouncement = { showAnnouncement }
261313 hideAnnouncement = { ( ) => setShowAnnouncement ( false ) }
262314 />
263- < MemoizedHumanRelayDialog
264- isOpen = { humanRelayDialogState . isOpen }
265- requestId = { humanRelayDialogState . requestId }
266- promptText = { humanRelayDialogState . promptText }
267- onClose = { ( ) => setHumanRelayDialogState ( ( prev ) => ( { ...prev , isOpen : false } ) ) }
268- onSubmit = { ( requestId , text ) => vscode . postMessage ( { type : "humanRelayResponse" , requestId, text } ) }
269- onCancel = { ( requestId ) => vscode . postMessage ( { type : "humanRelayCancel" , requestId } ) }
270- />
271- < MemoizedDeleteMessageDialog
272- open = { deleteMessageDialogState . isOpen }
273- onOpenChange = { ( open ) => setDeleteMessageDialogState ( ( prev ) => ( { ...prev , isOpen : open } ) ) }
274- onConfirm = { ( ) => {
275- vscode . postMessage ( {
276- type : "deleteMessageConfirm" ,
277- messageTs : deleteMessageDialogState . messageTs ,
278- } )
279- setDeleteMessageDialogState ( ( prev ) => ( { ...prev , isOpen : false } ) )
280- } }
281- />
282- < MemoizedEditMessageDialog
283- open = { editMessageDialogState . isOpen }
284- onOpenChange = { ( open ) => setEditMessageDialogState ( ( prev ) => ( { ...prev , isOpen : open } ) ) }
285- onConfirm = { ( ) => {
286- vscode . postMessage ( {
287- type : "editMessageConfirm" ,
288- messageTs : editMessageDialogState . messageTs ,
289- text : editMessageDialogState . text ,
290- images : editMessageDialogState . images ,
291- } )
292- setEditMessageDialogState ( ( prev ) => ( { ...prev , isOpen : false } ) )
293- } }
294- />
315+ < DialogErrorBoundary onError = { ( ) => setHumanRelayDialogState ( ( prev ) => ( { ...prev , isOpen : false } ) ) } >
316+ < MemoizedHumanRelayDialog
317+ isOpen = { humanRelayDialogState . isOpen }
318+ requestId = { humanRelayDialogState . requestId }
319+ promptText = { humanRelayDialogState . promptText }
320+ onClose = { ( ) => setHumanRelayDialogState ( ( prev ) => ( { ...prev , isOpen : false } ) ) }
321+ onSubmit = { ( requestId , text ) => vscode . postMessage ( { type : "humanRelayResponse" , requestId, text } ) }
322+ onCancel = { ( requestId ) => vscode . postMessage ( { type : "humanRelayCancel" , requestId } ) }
323+ />
324+ </ DialogErrorBoundary >
325+ < DialogErrorBoundary onError = { ( ) => setDeleteMessageDialogState ( ( prev ) => ( { ...prev , isOpen : false } ) ) } >
326+ < MemoizedDeleteMessageDialog
327+ open = { deleteMessageDialogState . isOpen }
328+ onOpenChange = { ( open ) => setDeleteMessageDialogState ( ( prev ) => ( { ...prev , isOpen : open } ) ) }
329+ onConfirm = { ( ) => {
330+ vscode . postMessage ( {
331+ type : "deleteMessageConfirm" ,
332+ messageTs : deleteMessageDialogState . messageTs ,
333+ } )
334+ setDeleteMessageDialogState ( ( prev ) => ( { ...prev , isOpen : false } ) )
335+ } }
336+ />
337+ </ DialogErrorBoundary >
338+ < DialogErrorBoundary onError = { ( ) => setEditMessageDialogState ( ( prev ) => ( { ...prev , isOpen : false } ) ) } >
339+ < MemoizedEditMessageDialog
340+ open = { editMessageDialogState . isOpen }
341+ onOpenChange = { ( open ) => setEditMessageDialogState ( ( prev ) => ( { ...prev , isOpen : open } ) ) }
342+ onConfirm = { ( ) => {
343+ vscode . postMessage ( {
344+ type : "editMessageConfirm" ,
345+ messageTs : editMessageDialogState . messageTs ,
346+ text : editMessageDialogState . text ,
347+ images : editMessageDialogState . images ,
348+ } )
349+ setEditMessageDialogState ( ( prev ) => ( { ...prev , isOpen : false } ) )
350+ } }
351+ />
352+ </ DialogErrorBoundary >
295353 </ >
296354 )
297355}
0 commit comments