From e8f77a445205bfb5bf7a8bed3ec1ab44b99f0478 Mon Sep 17 00:00:00 2001 From: Anton Timmermans Date: Thu, 14 May 2026 18:05:31 +0200 Subject: [PATCH] Add shortcut to archive a thread --- apps/web/src/components/CommandPalette.tsx | 28 ++++++++++++++++++++++ apps/web/src/routes/_chat.tsx | 21 ++++++++++++++++ packages/contracts/src/keybindings.ts | 1 + packages/shared/src/keybindings.ts | 1 + 4 files changed, 51 insertions(+) diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index 7862191d40b..d2642589a29 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -18,6 +18,7 @@ import { ArrowDownIcon, ArrowLeftIcon, ArrowUpIcon, + ArchiveIcon, CornerLeftUpIcon, FolderIcon, FolderPlusIcon, @@ -47,6 +48,7 @@ import { } from "../environments/runtime"; import { useHandleNewThread } from "../hooks/useHandleNewThread"; import { useSettings } from "../hooks/useSettings"; +import { useThreadActions } from "../hooks/useThreadActions"; import { readLocalApi } from "../localApi"; import { getSourceControlDiscoverySnapshot, @@ -404,6 +406,7 @@ function OpenCommandPaletteDialog() { const settings = useSettings(); const { activeDraftThread, activeThread, defaultProjectRef, handleNewThread } = useHandleNewThread(); + const { archiveThread } = useThreadActions(); const projects = useStore(useShallow(selectProjectsAcrossEnvironments)); const threads = useStore(useShallow(selectSidebarThreadsAcrossEnvironments)); const keybindings = useServerKeybindings(); @@ -1021,6 +1024,31 @@ function OpenCommandPaletteDialog() { }); } + if (activeThread) { + actionItems.push({ + kind: "action", + value: "action:archive-current-thread", + searchTerms: ["archive", "current thread", "hide thread"], + title: "Archive current thread", + description: activeThread.title, + icon: , + shortcutCommand: "thread.archiveCurrent", + run: async () => { + await archiveThread(scopeThreadRef(activeThread.environmentId, activeThread.id)).catch( + (error: unknown) => { + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to archive thread", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); + }, + ); + }, + }); + } + actionItems.push({ kind: "action", value: "action:add-project", diff --git a/apps/web/src/routes/_chat.tsx b/apps/web/src/routes/_chat.tsx index da22d7e6028..8a5692d4237 100644 --- a/apps/web/src/routes/_chat.tsx +++ b/apps/web/src/routes/_chat.tsx @@ -3,6 +3,7 @@ import { useEffect } from "react"; import { useCommandPaletteStore } from "../commandPaletteStore"; import { useHandleNewThread } from "../hooks/useHandleNewThread"; +import { useThreadActions } from "../hooks/useThreadActions"; import { startNewLocalThreadFromContext, startNewThreadFromContext, @@ -11,6 +12,7 @@ import { isTerminalFocused } from "../lib/terminalFocus"; import { resolveShortcutCommand } from "../keybindings"; import { selectThreadTerminalState, useTerminalStateStore } from "../terminalStateStore"; import { useThreadSelectionStore } from "../threadSelectionStore"; +import { stackedThreadToast, toastManager } from "~/components/ui/toast"; import { resolveSidebarNewThreadEnvMode } from "~/components/Sidebar.logic"; import { useSettings } from "~/hooks/useSettings"; import { useServerKeybindings } from "~/rpc/serverState"; @@ -20,6 +22,7 @@ function ChatRouteGlobalShortcuts() { const selectedThreadKeysSize = useThreadSelectionStore((state) => state.selectedThreadKeys.size); const { activeDraftThread, activeThread, defaultProjectRef, handleNewThread, routeThreadRef } = useHandleNewThread(); + const { archiveThread } = useThreadActions(); const keybindings = useServerKeybindings(); const terminalOpen = useTerminalStateStore((state) => routeThreadRef @@ -75,6 +78,22 @@ function ChatRouteGlobalShortcuts() { }), handleNewThread, }); + return; + } + + if (command === "thread.archiveCurrent") { + if (!routeThreadRef) return; + event.preventDefault(); + event.stopPropagation(); + void archiveThread(routeThreadRef).catch((error: unknown) => { + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to archive thread", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); + }); } }; @@ -85,10 +104,12 @@ function ChatRouteGlobalShortcuts() { }, [ activeDraftThread, activeThread, + archiveThread, clearSelection, handleNewThread, keybindings, defaultProjectRef, + routeThreadRef, selectedThreadKeysSize, terminalOpen, appSettings.defaultThreadEnvMode, diff --git a/packages/contracts/src/keybindings.ts b/packages/contracts/src/keybindings.ts index 502e564fb82..eff3f67d4f0 100644 --- a/packages/contracts/src/keybindings.ts +++ b/packages/contracts/src/keybindings.ts @@ -37,6 +37,7 @@ export type ModelPickerJumpKeybindingCommand = export const THREAD_KEYBINDING_COMMANDS = [ "thread.previous", "thread.next", + "thread.archiveCurrent", ...THREAD_JUMP_KEYBINDING_COMMANDS, ] as const; export type ThreadKeybindingCommand = (typeof THREAD_KEYBINDING_COMMANDS)[number]; diff --git a/packages/shared/src/keybindings.ts b/packages/shared/src/keybindings.ts index 3cc2e913621..6f8d075b8f2 100644 --- a/packages/shared/src/keybindings.ts +++ b/packages/shared/src/keybindings.ts @@ -32,6 +32,7 @@ export const DEFAULT_KEYBINDINGS: ReadonlyArray = [ { key: "mod+o", command: "editor.openFavorite" }, { key: "mod+shift+[", command: "thread.previous" }, { key: "mod+shift+]", command: "thread.next" }, + { key: "mod+shift+a", command: "thread.archiveCurrent", when: "!terminalFocus" }, ...THREAD_JUMP_KEYBINDING_COMMANDS.map((command, index) => ({ key: `mod+${index + 1}`, command,