fix(chat): remove confirmation modal when closing chat tab#1263
fix(chat): remove confirmation modal when closing chat tab#1263arnestrickmann merged 1 commit intomainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR removes the
Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| src/renderer/components/ChatInterface.tsx | Inlines deletion logic from the removed modal into handleCloseChat; however, lacks a busy-state guard (chat can be deleted while actively running) and has no error handling around the async DB operations, which can leave state inconsistent on failure. |
| src/renderer/components/DeleteChatModal.tsx | File deleted entirely as part of the modal removal — clean deletion with no dangling references. |
Sequence Diagram
sequenceDiagram
actor User
participant Tab as Chat Tab (UI)
participant HC as handleCloseChat
participant TR as terminalSessionRegistry
participant DB as rpc.db
participant State as React State
User->>Tab: Click ✕ (close button)
Tab->>HC: handleCloseChat(conversationId)
alt conversations.length <= 1
HC-->>User: toast "Cannot Close"
else
Note over HC: ⚠️ No busy-state check here
HC->>TR: dispose(terminalId)
HC->>DB: deleteConversation(conversationId)
HC->>DB: getConversations(taskId)
DB-->>HC: updatedConversations
HC->>State: setConversations(updatedConversations)
alt deleted chat was active
HC->>DB: setActiveConversation(newActive)
HC->>State: setActiveConversationId / setAgent
end
HC->>Window: dispatchEvent('emdash:conversations-changed')
end
Last reviewed commit: 9cdd14c
| @@ -528,52 +525,39 @@ const ChatInterface: React.FC<Props> = ({ | |||
| return; | |||
| } | |||
There was a problem hiding this comment.
No guard against closing a busy/running chat
The handleCloseChat function now immediately disposes the terminal and deletes the conversation without checking whether the chat is actively processing a request. When busyByConversationId[conversationId] === true, clicking the ✕ button will:
- Dispose the running terminal process mid-generation
- Permanently delete all conversation messages from the DB
Before this PR, the confirmation modal at least surfaced the warning "This will permanently delete this chat and all its messages. This action cannot be undone." — giving the user a chance to cancel. Now, there is no protection at all for a busy chat.
Consider adding a busy-state guard at the top of the function:
| const handleCloseChat = useCallback( | |
| async (conversationId: string) => { | |
| if (conversations.length <= 1) { | |
| toast({ | |
| title: 'Cannot Close', | |
| description: 'Cannot close the last chat', | |
| variant: 'destructive', | |
| }); | |
| return; | |
| } | |
| if (busyByConversationId[conversationId]) { | |
| toast({ | |
| title: 'Cannot Close', | |
| description: 'Cannot close a chat while it is running', | |
| variant: 'destructive', | |
| }); | |
| return; | |
| } |
| // Dispose the terminal for this chat | ||
| const convToDelete = conversations.find((c) => c.id === conversationId); | ||
| const convAgent = (convToDelete?.provider || agent) as Agent; | ||
| const terminalToDispose = makePtyId(convAgent, 'chat', conversationId); | ||
| terminalSessionRegistry.dispose(terminalToDispose); | ||
|
|
||
| await rpc.db.deleteConversation(conversationId); | ||
|
|
||
| // Reload conversations | ||
| const updatedConversations = await rpc.db.getConversations(task.id); | ||
| setConversations(updatedConversations); | ||
| // Switch to another chat if we deleted the active one | ||
| if (conversationId === activeConversationId && updatedConversations.length > 0) { | ||
| const newActive = updatedConversations[0]; | ||
| await rpc.db.setActiveConversation({ | ||
| taskId: task.id, | ||
| conversationId: newActive.id, | ||
| }); | ||
| setActiveConversationId(newActive.id); | ||
| // Update provider if needed | ||
| if (newActive.provider) { | ||
| setAgent(newActive.provider as Agent); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| try { | ||
| window.dispatchEvent( | ||
| new CustomEvent('emdash:conversations-changed', { detail: { taskId: task.id } }) | ||
| ); | ||
| } catch {} | ||
|
|
||
| // Clear the state | ||
| setChatToDelete(null); | ||
| setShowDeleteChatModal(false); | ||
| }, [chatToDelete, conversations, agent, task.id, activeConversationId]); | ||
| try { | ||
| window.dispatchEvent( | ||
| new CustomEvent('emdash:conversations-changed', { detail: { taskId: task.id } }) | ||
| ); | ||
| } catch {} |
There was a problem hiding this comment.
No error handling around async DB operations
If rpc.db.deleteConversation or rpc.db.getConversations throws, the function exits after the terminal has already been disposed — leaving the conversation still in the DB while its terminal session is gone. Additionally, since handleCloseChat is now async and is called from click/keyboard event handlers and a window event listener without .catch(), any rejection becomes an unhandled promise rejection.
Wrapping the body in a try/catch (and reverting the terminal dispose on failure) would keep state consistent:
const terminalToDispose = makePtyId(convAgent, 'chat', conversationId);
terminalSessionRegistry.dispose(terminalToDispose);
try {
await rpc.db.deleteConversation(conversationId);
// ... rest of logic
} catch (err) {
// Re-register or notify; at minimum surface the error
toast({ title: 'Error', description: 'Failed to close chat', variant: 'destructive' });
}
Summary
DeleteChatModalconfirmation dialog that appeared when closing a chat tabhandleCloseChat, making tab close instantDeleteChatModalcomponentMotivation
The confirmation modal added unnecessary friction when closing chat tabs. Since chats can be easily recreated and this is a lightweight action, the extra confirmation step was more disruptive than helpful.