From 8c385b9ac2e2fb73fce4243402c7c0eb11d4e453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 18 Jul 2026 03:48:11 +0200 Subject: [PATCH] fix(coach): delete-with-undo replaces the arm-forever confirm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The history rail + /coach/conversations armed a row on the first tap and only disarmed on a successful delete — no timeout, no outside-click reset — so a row armed minutes earlier deleted on a later stray tap, and there was no way back once it fired. Replace it with the same delayed-commit delete-with-undo the Documents surface already uses: one tap hides the row and schedules the real DELETE a few seconds out; the toast's Undo cancels it. Centralised in useDeleteCoachConversationWithUndo so both surfaces, which carried byte-identical arm/confirm logic, share one implementation. --- messages/de.json | 2 +- messages/en.json | 2 +- messages/es.json | 2 +- messages/fr.json | 2 +- messages/it.json | 2 +- messages/pl.json | 2 +- src/app/coach/conversations/page.tsx | 44 +++++----- .../insights/coach-panel/history-rail.tsx | 58 ++++++------- .../insights/coach-panel/use-coach.ts | 81 +++++++++++++++++++ 9 files changed, 136 insertions(+), 59 deletions(-) diff --git a/messages/de.json b/messages/de.json index 0db2acf0a..96a0d94df 100644 --- a/messages/de.json +++ b/messages/de.json @@ -2262,7 +2262,7 @@ "historySearchPlaceholder": "Unterhaltungen durchsuchen…", "historyEmpty": "Deine Unterhaltungen erscheinen hier. Stell eine Frage, um zu starten.", "historyDeleteAria": "Unterhaltung löschen", - "historyDeleteConfirm": "Diese Unterhaltung löschen? Das lässt sich nicht rückgängig machen.", + "historyDeleted": "Unterhaltung gelöscht", "sourcesTitle": "Worauf ich zugreife", "sourcesFresh": "Aktuell", "sourcesStale": "Veraltet", diff --git a/messages/en.json b/messages/en.json index 85ae8a4ea..ae8c7ee80 100644 --- a/messages/en.json +++ b/messages/en.json @@ -2262,7 +2262,7 @@ "historySearchPlaceholder": "Search conversations…", "historyEmpty": "Your conversations will appear here. Ask anything to start.", "historyDeleteAria": "Delete conversation", - "historyDeleteConfirm": "Delete this conversation? This cannot be undone.", + "historyDeleted": "Conversation deleted", "sourcesTitle": "What I can see", "sourcesFresh": "Fresh", "sourcesStale": "Stale", diff --git a/messages/es.json b/messages/es.json index 1f8693a3c..a011182c3 100644 --- a/messages/es.json +++ b/messages/es.json @@ -2262,7 +2262,7 @@ "historySearchPlaceholder": "Buscar en las conversaciones…", "historyEmpty": "Tus conversaciones aparecen aquí. Lanza una pregunta para empezar.", "historyDeleteAria": "Eliminar conversación", - "historyDeleteConfirm": "¿Eliminar esta conversación? No se puede deshacer.", + "historyDeleted": "Conversación eliminada", "sourcesTitle": "A qué accedo", "sourcesFresh": "Actuales", "sourcesStale": "Desactualizados", diff --git a/messages/fr.json b/messages/fr.json index 943c80cc9..3c684a35b 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -2262,7 +2262,7 @@ "historySearchPlaceholder": "Rechercher dans les conversations…", "historyEmpty": "Tes conversations s’affichent ici. Lance une question pour commencer.", "historyDeleteAria": "Supprimer la conversation", - "historyDeleteConfirm": "Supprimer cette conversation ? Action irréversible.", + "historyDeleted": "Conversation supprimée", "sourcesTitle": "Ce à quoi j’accède", "sourcesFresh": "À jour", "sourcesStale": "Obsolètes", diff --git a/messages/it.json b/messages/it.json index 6e947266a..03bbf720a 100644 --- a/messages/it.json +++ b/messages/it.json @@ -2262,7 +2262,7 @@ "historySearchPlaceholder": "Cerca nelle conversazioni…", "historyEmpty": "Le tue conversazioni compaiono qui. Fai una domanda per iniziare.", "historyDeleteAria": "Elimina conversazione", - "historyDeleteConfirm": "Eliminare questa conversazione? Non si può annullare.", + "historyDeleted": "Conversazione eliminata", "sourcesTitle": "A cosa accedo", "sourcesFresh": "Aggiornate", "sourcesStale": "Datate", diff --git a/messages/pl.json b/messages/pl.json index e5fa6918b..c1e3ad683 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -2262,7 +2262,7 @@ "historySearchPlaceholder": "Szukaj w rozmowach…", "historyEmpty": "Twoje rozmowy pojawią się tutaj. Zadaj pytanie, aby zacząć.", "historyDeleteAria": "Usuń rozmowę", - "historyDeleteConfirm": "Usunąć tę rozmowę? Tego nie da się cofnąć.", + "historyDeleted": "Rozmowa usunięta", "sourcesTitle": "Do czego mam dostęp", "sourcesFresh": "Aktualne", "sourcesStale": "Nieaktualne", diff --git a/src/app/coach/conversations/page.tsx b/src/app/coach/conversations/page.tsx index a80a3d776..7001046ce 100644 --- a/src/app/coach/conversations/page.tsx +++ b/src/app/coach/conversations/page.tsx @@ -10,6 +10,7 @@ import { Target, Trash2, } from "lucide-react"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -18,7 +19,7 @@ import { QueryErrorCard } from "@/components/ui/query-error-card"; import { COACH_SCROLLBAR } from "@/components/insights/coach-panel/message-thread"; import { useCoachConversations, - useDeleteCoachConversation, + useDeleteCoachConversationWithUndo, } from "@/components/insights/coach-panel/use-coach"; import type { CoachConversationDTO } from "@/lib/ai/coach/types"; import { useCoachLaunch } from "@/lib/insights/coach-launch-context"; @@ -102,16 +103,17 @@ function CoachConversationsBody() { const router = useRouter(); const { conversations, isLoading, isError, refetch } = useCoachConversations(); - const deleteMutation = useDeleteCoachConversation(); + const { pendingDeleteIds, requestDelete, undoDelete } = + useDeleteCoachConversationWithUndo(); const [filter, setFilter] = useState(""); - const [confirmId, setConfirmId] = useState(null); // Mirror the rail's title substring filter. const filtered = useMemo(() => { const q = filter.trim().toLowerCase(); - if (!q) return conversations; - return conversations.filter((c) => c.title.toLowerCase().includes(q)); - }, [conversations, filter]); + const visible = conversations.filter((c) => !pendingDeleteIds.has(c.id)); + if (!q) return visible; + return visible.filter((c) => c.title.toLowerCase().includes(q)); + }, [conversations, filter, pendingDeleteIds]); const groups = useMemo(() => groupByRecency(filtered), [filtered]); @@ -120,13 +122,17 @@ function CoachConversationsBody() { router.push(`/coach?c=${id}`); } + // v1.30.1 M5 — one tap deletes (with an Undo toast); see + // `use-coach.ts`'s `useDeleteCoachConversationWithUndo` for the + // rationale — this mirrors the rail's identical fix. function handleDeleteRequest(id: string) { - if (confirmId === id) { - deleteMutation.mutate(id); - setConfirmId(null); - } else { - setConfirmId(id); - } + requestDelete(id); + toast.success(t("insights.coach.historyDeleted"), { + action: { + label: t("common.undo"), + onClick: () => undoDelete(id), + }, + }); } return ( @@ -212,7 +218,6 @@ function CoachConversationsBody() {
    {group.conversations.map((c) => { - const isConfirming = confirmId === c.id; return (
  • handleDeleteRequest(c.id)} - aria-label={ - isConfirming - ? t("insights.coach.historyDeleteConfirm") - : t("insights.coach.historyDeleteAria") - } + aria-label={t("insights.coach.historyDeleteAria")} data-slot="coach-conversations-delete" - data-confirming={isConfirming ? "true" : undefined} - className={cn( - "shrink-0", - isConfirming && - "text-destructive hover:text-destructive", - )} + className="shrink-0" >