Skip to content

Commit 8c6efdc

Browse files
kartikyeKartikye
andauthored
feat: Add a button to archive sessions from the sidebar (#570)
- add an `Archive` action to sidebar session rows - reuse a shared archive dialog between the sidebar and the session page - add `archiveSession` as the shared archive helper - keep sidebar state in sync after archiving - add/update focused sidebar coverage for the archive flow <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Archive sessions directly from the session sidebar. * Confirmation dialog when archiving sessions. * Mobile-optimized archive workflow via long-press. * Archived sessions are removed from the sidebar immediately; archive action is disabled while processing. * **Tests** * Added coverage for the mobile archive flow and confirmation/workflow behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Kartikye <kartikye@dots.dev>
1 parent 352bda0 commit 8c6efdc

7 files changed

Lines changed: 298 additions & 139 deletions

File tree

packages/web/src/app/(app)/session/[id]/page.tsx

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { Group as PanelGroup, Panel, Separator as PanelResizeHandle } from "reac
2828
import { TerminalPanel } from "@/components/terminal-panel";
2929
import { ActionBar } from "@/components/action-bar";
3030
import { copyToClipboard, formatModelNameLower } from "@/lib/format";
31+
import { archiveSession } from "@/lib/archive-session";
3132
import { SHORTCUT_LABELS } from "@/lib/keyboard-shortcuts";
3233
import { SIDEBAR_SESSIONS_KEY } from "@/lib/session-list";
3334
import { useMediaQuery } from "@/hooks/use-media-query";
@@ -210,21 +211,6 @@ function SessionPageContent() {
210211
[searchParams]
211212
);
212213

213-
const { trigger: triggerArchive } = useSWRMutation(
214-
`/api/sessions/${sessionId}/archive`,
215-
(url: string) =>
216-
fetch(url, { method: "POST" }).then((r) => {
217-
if (r.ok) {
218-
mutate(SIDEBAR_SESSIONS_KEY);
219-
return true;
220-
}
221-
222-
console.error("Failed to archive session");
223-
return false;
224-
}),
225-
{ throwOnError: false }
226-
);
227-
228214
const { trigger: triggerRename } = useSWRMutation(
229215
`/api/sessions/${sessionId}/title`,
230216
(url: string, { arg }: { arg: { title: string } }) =>
@@ -241,11 +227,11 @@ function SessionPageContent() {
241227
);
242228

243229
const handleArchive = useCallback(async () => {
244-
const didArchive = await triggerArchive();
230+
const didArchive = await archiveSession(sessionId);
245231
if (didArchive) {
246232
router.push("/");
247233
}
248-
}, [router, triggerArchive]);
234+
}, [router, sessionId]);
249235

250236
const renameSession = useCallback(
251237
async (title: string) => {

packages/web/src/components/action-bar.tsx

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useState } from "react";
44
import { toast } from "sonner";
5+
import { ArchiveSessionDialog } from "@/components/archive-session-dialog";
56
import type { Artifact } from "@/types/session";
67
import {
78
GlobeIcon,
@@ -12,16 +13,6 @@ import {
1213
GitHubIcon,
1314
} from "@/components/ui/icons";
1415
import { Button } from "@/components/ui/button";
15-
import {
16-
AlertDialog,
17-
AlertDialogAction,
18-
AlertDialogCancel,
19-
AlertDialogContent,
20-
AlertDialogDescription,
21-
AlertDialogFooter,
22-
AlertDialogHeader,
23-
AlertDialogTitle,
24-
} from "@/components/ui/alert-dialog";
2516
import {
2617
DropdownMenu,
2718
DropdownMenuContent,
@@ -154,21 +145,11 @@ export function ActionBar({
154145
</DropdownMenu>
155146
</div>
156147

157-
<AlertDialog open={showArchiveDialog} onOpenChange={setShowArchiveDialog}>
158-
<AlertDialogContent>
159-
<AlertDialogHeader>
160-
<AlertDialogTitle>Archive session</AlertDialogTitle>
161-
<AlertDialogDescription>
162-
Archive this session? You can restore archived sessions from Settings &gt; Data
163-
Controls.
164-
</AlertDialogDescription>
165-
</AlertDialogHeader>
166-
<AlertDialogFooter>
167-
<AlertDialogCancel>Cancel</AlertDialogCancel>
168-
<AlertDialogAction onClick={handleConfirmArchive}>Archive</AlertDialogAction>
169-
</AlertDialogFooter>
170-
</AlertDialogContent>
171-
</AlertDialog>
148+
<ArchiveSessionDialog
149+
open={showArchiveDialog}
150+
onOpenChange={setShowArchiveDialog}
151+
onConfirm={handleConfirmArchive}
152+
/>
172153
</>
173154
);
174155
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"use client";
2+
3+
import {
4+
AlertDialog,
5+
AlertDialogAction,
6+
AlertDialogCancel,
7+
AlertDialogContent,
8+
AlertDialogDescription,
9+
AlertDialogFooter,
10+
AlertDialogHeader,
11+
AlertDialogTitle,
12+
} from "@/components/ui/alert-dialog";
13+
14+
interface ArchiveSessionDialogProps {
15+
open: boolean;
16+
onOpenChange: (open: boolean) => void;
17+
onConfirm: () => void | Promise<void>;
18+
}
19+
20+
export function ArchiveSessionDialog({ open, onOpenChange, onConfirm }: ArchiveSessionDialogProps) {
21+
return (
22+
<AlertDialog open={open} onOpenChange={onOpenChange}>
23+
<AlertDialogContent>
24+
<AlertDialogHeader>
25+
<AlertDialogTitle>Archive session</AlertDialogTitle>
26+
<AlertDialogDescription>
27+
Archive this session? You can restore archived sessions from Settings &gt; Data
28+
Controls.
29+
</AlertDialogDescription>
30+
</AlertDialogHeader>
31+
<AlertDialogFooter>
32+
<AlertDialogCancel>Cancel</AlertDialogCancel>
33+
<AlertDialogAction onClick={onConfirm}>Archive</AlertDialogAction>
34+
</AlertDialogFooter>
35+
</AlertDialogContent>
36+
</AlertDialog>
37+
);
38+
}

packages/web/src/components/session-sidebar.test.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ const { mockUseIsMobile } = vi.hoisted(() => ({
1414
mockUseIsMobile: vi.fn(() => false),
1515
}));
1616

17+
const { mockPush } = vi.hoisted(() => ({
18+
mockPush: vi.fn(),
19+
}));
20+
1721
vi.mock("next-auth/react", () => ({
1822
useSession: () => ({
1923
data: {
@@ -28,6 +32,7 @@ vi.mock("next-auth/react", () => ({
2832

2933
vi.mock("next/navigation", () => ({
3034
usePathname: () => "/",
35+
useRouter: () => ({ push: mockPush }),
3136
}));
3237

3338
vi.mock("next/link", () => ({
@@ -47,6 +52,7 @@ afterEach(() => {
4752
vi.restoreAllMocks();
4853
vi.useRealTimers();
4954
mockUseIsMobile.mockReturnValue(false);
55+
mockPush.mockReset();
5056
});
5157

5258
function createSession(index: number) {
@@ -207,5 +213,47 @@ describe("SessionSidebar", () => {
207213
});
208214

209215
expect(screen.getByText("Rename")).toBeInTheDocument();
216+
expect(screen.getByText("Archive")).toBeInTheDocument();
217+
});
218+
219+
it("archives a session from the sidebar actions menu", async () => {
220+
mockUseIsMobile.mockReturnValue(true);
221+
222+
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
223+
if (String(input) === "/api/sessions/session-1/archive" && init?.method === "POST") {
224+
return jsonResponse({ ok: true });
225+
}
226+
227+
throw new Error(`Unexpected fetch for ${String(input)}`);
228+
});
229+
230+
vi.stubGlobal("fetch", fetchMock);
231+
232+
render(
233+
<SWRConfig
234+
value={{
235+
fallback: { [SIDEBAR_SESSIONS_KEY]: { sessions: [createSession(1)], hasMore: false } },
236+
dedupingInterval: 0,
237+
revalidateOnFocus: false,
238+
}}
239+
>
240+
<SessionSidebar />
241+
</SWRConfig>
242+
);
243+
244+
const link = await screen.findByRole("link", { name: /session 1/i });
245+
vi.useFakeTimers();
246+
fireEvent.touchStart(link, { touches: [{ clientX: 20, clientY: 20 }] });
247+
act(() => {
248+
vi.advanceTimersByTime(MOBILE_LONG_PRESS_MS);
249+
});
250+
vi.useRealTimers();
251+
252+
fireEvent.click(screen.getByText("Archive"));
253+
fireEvent.click(await screen.findByRole("button", { name: "Archive" }));
254+
255+
await waitFor(() => {
256+
expect(fetchMock).toHaveBeenCalledWith("/api/sessions/session-1/archive", { method: "POST" });
257+
});
210258
});
211259
});

0 commit comments

Comments
 (0)