From 2c21e4659ffdfd60e1d824a3197d6576238287e3 Mon Sep 17 00:00:00 2001 From: mptyl Date: Mon, 29 Jun 2026 13:30:46 +0200 Subject: [PATCH] fix(frontend): surface session mutation failures via toast; doResume only activates on success Co-Authored-By: Claude Sonnet 4.6 --- .../src/shell/AppShell.session-mgmt.test.tsx | 10 ++++ frontend/src/shell/AppShell.tsx | 60 +++++++++++++++---- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/frontend/src/shell/AppShell.session-mgmt.test.tsx b/frontend/src/shell/AppShell.session-mgmt.test.tsx index 1c8b3699..19adf332 100644 --- a/frontend/src/shell/AppShell.session-mgmt.test.tsx +++ b/frontend/src/shell/AppShell.session-mgmt.test.tsx @@ -62,3 +62,13 @@ test("Resume from the panel activates the session and closes the panel", async ( await waitFor(() => expect(resumed).toBe("s1")); await waitFor(() => expect(screen.queryByText("Domanda originale")).not.toBeInTheDocument()); // panel closed }); + +test("a failed resume keeps the panel open and does not activate the session", async () => { + server.use(http.post("http://localhost:8787/sessions/:id/resume", () => new HttpResponse(null, { status: 409 }))); + wrap(); + await userEvent.click(await screen.findByText("Attiva uno")); + await screen.findByText("Domanda originale"); // panel open + await userEvent.click(screen.getByRole("button", { name: /resume/i })); + // panel stays open (document still visible) after the failed resume + await waitFor(() => expect(screen.getByText("Domanda originale")).toBeInTheDocument()); +}); diff --git a/frontend/src/shell/AppShell.tsx b/frontend/src/shell/AppShell.tsx index a543d6b8..499229e1 100644 --- a/frontend/src/shell/AppShell.tsx +++ b/frontend/src/shell/AppShell.tsx @@ -11,6 +11,7 @@ import { SteerInput, ComposerFooter } from "./SteerInput"; import { WorkflowBar } from "./WorkflowBar"; import { Button } from "../components/ui/button"; import { Toaster } from "../components/ui/sonner"; +import { toast } from "sonner"; import { closeSession, listSessions, resumeSession, renameSession, setSessionGroup, archiveSession, unarchiveSession, deleteSession, @@ -52,19 +53,39 @@ export function AppShell() { if (s) setPanelSession(s); } async function doResume(id: string) { - await resumeSession(id); - setPanelSession(null); - setActiveSessionId(id); + try { + await resumeSession(id); + setPanelSession(null); + setActiveSessionId(id); + } catch { + toast.error("Failed to resume session."); + } + } + async function move(s: SessionSummary, group: string) { + try { + await setSessionGroup(s.id, group); refresh(); + } catch { + toast.error("Failed to move session."); + } } - async function move(s: SessionSummary, group: string) { await setSessionGroup(s.id, group); refresh(); } async function newGroup(s: SessionSummary) { const name = window.prompt("New group:"); - if (name && name.trim()) { await setSessionGroup(s.id, name.trim()); refresh(); } + if (name && name.trim()) { + try { + await setSessionGroup(s.id, name.trim()); refresh(); + } catch { + toast.error("Failed to update group."); + } + } } async function toggleArchive(s: SessionSummary) { - await (s.archived ? unarchiveSession(s.id) : archiveSession(s.id)); - if (panelSession?.id === s.id) setPanelSession(null); - refresh(); + try { + await (s.archived ? unarchiveSession(s.id) : archiveSession(s.id)); + if (panelSession?.id === s.id) setPanelSession(null); + refresh(); + } catch { + toast.error(s.archived ? "Failed to restore session." : "Failed to archive session."); + } } function menuFor(s: SessionSummary) { @@ -244,7 +265,15 @@ export function AppShell() { open initial={renameTarget.name ?? ""} onOpenChange={(o) => { if (!o) setRenameTarget(null); }} - onSubmit={async (name) => { await renameSession(renameTarget.id, name); setRenameTarget(null); refresh(); }} + onSubmit={async (name) => { + try { + await renameSession(renameTarget.id, name); + setRenameTarget(null); + refresh(); + } catch { + toast.error("Failed to rename session."); + } + }} /> )} {deleteTarget && ( @@ -253,10 +282,15 @@ export function AppShell() { label={deleteTarget.name || deleteTarget.question} onOpenChange={(o) => { if (!o) setDeleteTarget(null); }} onConfirm={async () => { - await deleteSession(deleteTarget.id); - if (panelSession?.id === deleteTarget.id) setPanelSession(null); - if (activeSessionId === deleteTarget.id) { resetSession(); setActiveSessionId(null); } - setDeleteTarget(null); refresh(); + try { + await deleteSession(deleteTarget.id); + if (panelSession?.id === deleteTarget.id) setPanelSession(null); + if (activeSessionId === deleteTarget.id) { resetSession(); setActiveSessionId(null); } + setDeleteTarget(null); + refresh(); + } catch { + toast.error("Failed to delete session."); + } }} /> )}