fix: delete associated audio, memories, and tasks when deleting conversation#5573
fix: delete associated audio, memories, and tasks when deleting conversation#5573
Conversation
… conversation Audio files are always deleted. Memories and action items are optionally deleted via query params, with vectors cleaned up in background tasks. Closes #4868
New dialog shows a checkbox to optionally delete associated memories and tasks when deleting a conversation. Defaults to unchecked.
Greptile SummaryThis PR extends the conversation-delete flow across the full stack: audio files are always cleaned up on the backend, and a new optional checkbox lets users also purge associated memories and tasks. The changes touch the FastAPI delete endpoint, Flutter's conversation provider, the swipe-to-delete list item, the detail page, a new dialog widget, and 34 locale files. Key concerns:
Confidence Score: 2/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant U as User
participant FL as Flutter UI
participant CP as ConversationProvider
participant API as Backend API
participant FS as Firestore
participant GCS as GCS Audio Storage
participant PC as Pinecone
U->>FL: Swipe-to-delete / Delete button
FL->>FL: showDeleteConversationDialog()
U->>FL: Confirm (± deleteAssociatedData checkbox)
FL->>CP: deleteConversationLocally(conversation, deleteAssociatedData)
CP->>CP: Store _deleteAssociatedDataOptions[id]
CP->>CP: Remove from local list, notifyListeners()
Note over CP: 3-second undo window
CP->>API: DELETE /v1/conversations/{id}?delete_memories=&delete_action_items=
API->>FS: delete_conversation(uid, id)
API->>PC: delete_vector(uid, id)
API-->>API: background: delete_conversation_audio_files
API-->>GCS: Delete chunks/ & audio/ blobs
opt delete_memories=true
API->>FS: get_memory_ids_for_conversation(uid, id)
API->>FS: delete_memories_for_conversation(uid, id)
API-->>PC: background: delete_memory_vector(uid, memory_id) ×N
end
opt delete_action_items=true
API->>FS: delete_action_items_for_conversation(uid, id)
end
API-->>FL: 204 No Content
|
| confirmDismiss: (direction) async { | ||
| HapticFeedback.mediumImpact(); | ||
| bool showDeleteConfirmation = SharedPreferencesUtil().showConversationDeleteConfirmation; | ||
|
|
||
| if (!showDeleteConfirmation) return Future.value(true); | ||
|
|
There was a problem hiding this comment.
showConversationDeleteConfirmation preference silently dropped
The previous implementation respected a user preference (SharedPreferencesUtil().showConversationDeleteConfirmation) that allowed skipping the confirmation dialog on swipe-to-delete. That preference still exists in preferences.dart but is no longer read here, meaning users who had set it to false (no confirmation) will now always see the dialog — a silent behavioral regression.
If the intent is to always require confirmation for the new checkbox UX, the preference should either be migrated/removed from SharedPreferencesUtil or the code should explicitly document that the preference is intentionally ignored, rather than leaving a dead settings key.
| confirmDismiss: (direction) async { | |
| HapticFeedback.mediumImpact(); | |
| bool showDeleteConfirmation = SharedPreferencesUtil().showConversationDeleteConfirmation; | |
| if (!showDeleteConfirmation) return Future.value(true); | |
| confirmDismiss: (direction) async { | |
| HapticFeedback.mediumImpact(); |
| @@ -221,13 +206,27 @@ class _ConversationListItemState extends State<ConversationListItem> { | |||
| ), | |||
| context: context, | |||
| ); | |||
| return false; | |||
| } | |||
There was a problem hiding this comment.
Offline error dialog not awaited — Dismissible snaps back immediately
showDialog is called without await, so return false executes synchronously and the Dismissible animation snaps the item back while the error dialog appears simultaneously. In the previous code the confirmDismiss future was the dialog future itself, meaning the item stayed in its swipe position until the dialog was dismissed. Consider awaiting the dialog so the two events are sequenced:
| if (!connectivityProvider.isConnected) { | |
| await showDialog( | |
| builder: (c) => getDialog( | |
| context, | |
| () => Navigator.pop(context), | |
| () => Navigator.pop(context), | |
| context.l10n.unableToDeleteConversation, | |
| context.l10n.pleaseCheckInternetConnectionAndTryAgain, | |
| singleButton: true, | |
| okButtonText: context.l10n.ok, | |
| ), | |
| context: context, | |
| ); | |
| return false; | |
| } |
|
|
||
| return {"status": "Ok"} | ||
|
|
||
|
|
There was a problem hiding this comment.
204 No Content response must not return a body
The endpoint is decorated with status_code=204 but the function returns {"status": "Ok"}. HTTP 204 (No Content) must have no response body. FastAPI/Starlette typically strips the body, but the return type is misleading and some proxy layers may surface this. The response body should either be removed or the status code changed to 200.
| return |
|
|
||
| if delete_memories: | ||
| memory_ids = memories_db.get_memory_ids_for_conversation(uid, conversation_id) | ||
| memories_db.delete_memories_for_conversation(uid, conversation_id) | ||
| for memory_id in memory_ids: | ||
| background_tasks.add_task(delete_memory_vector, uid, memory_id) | ||
|
|
There was a problem hiding this comment.
Memory vectors deleted after Firestore records — orphan risk on crash
delete_memories_for_conversation is called synchronously (removing Firestore records), and the Pinecone vector deletes are then scheduled as background tasks. If the process dies after the Firestore commit but before the background tasks complete, the Pinecone vectors for those memories will be permanently orphaned because the document IDs are now gone and can't be re-fetched.
Consider either:
- Fetching and passing the memory IDs into background tasks that handle both Firestore deletion and vector deletion atomically, or
- Moving the Firestore deletion itself into a background task alongside the vector deletion so both fail or succeed together.
Summary
delete_memoriesanddelete_action_itemsquery params; audio cleanup runs unconditionally as a background taskdeleteAssociatedDatal10n key with translations for all 34 localesCloses #4868
Test plan
🤖 Generated with Claude Code