From e96a20f8cb16bd14cee5c62f6286c1e19edbcd96 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Wed, 15 Apr 2026 13:26:53 -0700 Subject: [PATCH 1/3] Add task search and sidebar context menu integration to command center --- .../src/main/services/context-menu/schemas.ts | 3 + .../src/main/services/context-menu/service.ts | 19 ++++- .../components/TaskSelector.tsx | 71 +++++++++++-------- .../sidebar/components/SidebarMenu.tsx | 13 ++++ .../src/renderer/hooks/useTaskContextMenu.ts | 11 +++ 5 files changed, 87 insertions(+), 30 deletions(-) diff --git a/apps/code/src/main/services/context-menu/schemas.ts b/apps/code/src/main/services/context-menu/schemas.ts index d11d80eb4..65120b4a3 100644 --- a/apps/code/src/main/services/context-menu/schemas.ts +++ b/apps/code/src/main/services/context-menu/schemas.ts @@ -6,6 +6,8 @@ export const taskContextMenuInput = z.object({ folderPath: z.string().optional(), isPinned: z.boolean().optional(), isSuspended: z.boolean().optional(), + isInCommandCenter: z.boolean().optional(), + hasEmptyCommandCenterCell: z.boolean().optional(), }); export const archivedTaskContextMenuInput = z.object({ @@ -40,6 +42,7 @@ const taskAction = z.discriminatedUnion("type", [ z.object({ type: z.literal("archive") }), z.object({ type: z.literal("archive-prior") }), z.object({ type: z.literal("delete") }), + z.object({ type: z.literal("add-to-command-center") }), z.object({ type: z.literal("external-app"), action: externalAppAction }), ]); diff --git a/apps/code/src/main/services/context-menu/service.ts b/apps/code/src/main/services/context-menu/service.ts index adb49f1da..1308e2a22 100644 --- a/apps/code/src/main/services/context-menu/service.ts +++ b/apps/code/src/main/services/context-menu/service.ts @@ -104,7 +104,14 @@ export class ContextMenuService { async showTaskContextMenu( input: TaskContextMenuInput, ): Promise { - const { worktreePath, folderPath, isPinned, isSuspended } = input; + const { + worktreePath, + folderPath, + isPinned, + isSuspended, + isInCommandCenter, + hasEmptyCommandCenterCell, + } = input; const { apps, lastUsedAppId } = await this.getExternalAppsData(); const hasPath = worktreePath || folderPath; @@ -126,6 +133,16 @@ export class ContextMenuService { ...this.externalAppItems(apps, lastUsedAppId), ] : []), + ...(!isInCommandCenter + ? [ + this.separator(), + this.item( + "Add to Command Center", + { type: "add-to-command-center" as const }, + { enabled: hasEmptyCommandCenterCell ?? true }, + ), + ] + : []), this.separator(), this.item("Archive", { type: "archive" }), this.item( diff --git a/apps/code/src/renderer/features/command-center/components/TaskSelector.tsx b/apps/code/src/renderer/features/command-center/components/TaskSelector.tsx index c12113f3a..d44a56d5b 100644 --- a/apps/code/src/renderer/features/command-center/components/TaskSelector.tsx +++ b/apps/code/src/renderer/features/command-center/components/TaskSelector.tsx @@ -1,5 +1,6 @@ +import { Combobox } from "@components/ui/combobox/Combobox"; import { Plus } from "@phosphor-icons/react"; -import { Popover, Separator } from "@radix-ui/themes"; +import { Popover } from "@radix-ui/themes"; import { useNavigationStore } from "@stores/navigationStore"; import { type ReactNode, useCallback } from "react"; import { useAvailableTasks } from "../hooks/useAvailableTasks"; @@ -42,42 +43,54 @@ export function TaskSelector({ }, [onOpenChange, onNewTask, navigateToTaskInput]); return ( - + {children} - task.title} side="bottom" align="center" sideOffset={4} - style={{ padding: 4, minWidth: 240, maxHeight: 300 }} + style={{ minWidth: 240 }} > - - -
- {availableTasks.length === 0 ? ( -
- No available tasks -
- ) : ( - availableTasks.map((task) => ( - - )) - )} -
-
-
+ + + )} + + ); } diff --git a/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx b/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx index f56771b46..f76f6a57b 100644 --- a/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx +++ b/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx @@ -77,6 +77,7 @@ function SidebarMenuComponent() { } const commandCenterCells = useCommandCenterStore((s) => s.cells); + const assignTaskToCommandCenter = useCommandCenterStore((s) => s.assignTask); const commandCenterActiveCount = commandCenterCells.filter( (taskId) => taskId != null && taskMap.has(taskId), ).length; @@ -138,13 +139,25 @@ function SidebarMenuComponent() { if (task) { const workspace = workspaces[taskId]; const taskData = allSidebarTasks.find((t) => t.id === taskId); + const isInCommandCenter = commandCenterCells.includes(taskId); + const firstEmptyIndex = commandCenterCells.indexOf(null); + const hasEmptyCommandCenterCell = firstEmptyIndex !== -1; + showContextMenu(task, e, { worktreePath: workspace?.worktreePath ?? undefined, folderPath: workspace?.folderPath ?? undefined, isPinned, isSuspended: taskData?.isSuspended, + isInCommandCenter, + hasEmptyCommandCenterCell, onTogglePin: () => togglePin(taskId), onArchivePrior: handleArchivePrior, + onAddToCommandCenter: () => { + if (firstEmptyIndex !== -1) { + assignTaskToCommandCenter(firstEmptyIndex, taskId); + navigateToCommandCenter(); + } + }, }); } }; diff --git a/apps/code/src/renderer/hooks/useTaskContextMenu.ts b/apps/code/src/renderer/hooks/useTaskContextMenu.ts index 63dac5268..b3ccff57f 100644 --- a/apps/code/src/renderer/hooks/useTaskContextMenu.ts +++ b/apps/code/src/renderer/hooks/useTaskContextMenu.ts @@ -28,8 +28,11 @@ export function useTaskContextMenu() { folderPath?: string; isPinned?: boolean; isSuspended?: boolean; + isInCommandCenter?: boolean; + hasEmptyCommandCenterCell?: boolean; onTogglePin?: () => void; onArchivePrior?: (taskId: string) => void; + onAddToCommandCenter?: () => void; }, ) => { event.preventDefault(); @@ -40,8 +43,11 @@ export function useTaskContextMenu() { folderPath, isPinned, isSuspended, + isInCommandCenter, + hasEmptyCommandCenterCell, onTogglePin, onArchivePrior, + onAddToCommandCenter, } = options ?? {}; try { @@ -51,6 +57,8 @@ export function useTaskContextMenu() { folderPath, isPinned, isSuspended, + isInCommandCenter, + hasEmptyCommandCenterCell, }); if (!result.action) return; @@ -86,6 +94,9 @@ export function useTaskContextMenu() { hasWorktree: !!worktreePath, }); break; + case "add-to-command-center": + onAddToCommandCenter?.(); + break; case "external-app": { const effectivePath = worktreePath ?? folderPath; if (effectivePath) { From 16d05f0ce82f7e0dec1145f29905b8bf46b1d2b6 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Wed, 15 Apr 2026 14:32:24 -0700 Subject: [PATCH 2/3] Fix command center menu item disabled for stale cell references --- .../features/command-center/components/TaskSelector.tsx | 2 +- .../renderer/features/sidebar/components/SidebarMenu.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/code/src/renderer/features/command-center/components/TaskSelector.tsx b/apps/code/src/renderer/features/command-center/components/TaskSelector.tsx index d44a56d5b..83b82d422 100644 --- a/apps/code/src/renderer/features/command-center/components/TaskSelector.tsx +++ b/apps/code/src/renderer/features/command-center/components/TaskSelector.tsx @@ -74,7 +74,7 @@ export function TaskSelector({ ))} {hasMore && (
- {moreCount} more {moreCount === 1 ? "task" : "tasks"} — type to + {moreCount} more {moreCount === 1 ? "task" : "tasks"}; type to filter
)} diff --git a/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx b/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx index f76f6a57b..f1c3d47b6 100644 --- a/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx +++ b/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx @@ -140,7 +140,9 @@ function SidebarMenuComponent() { const workspace = workspaces[taskId]; const taskData = allSidebarTasks.find((t) => t.id === taskId); const isInCommandCenter = commandCenterCells.includes(taskId); - const firstEmptyIndex = commandCenterCells.indexOf(null); + const firstEmptyIndex = commandCenterCells.findIndex( + (id) => id == null || !taskMap.has(id), + ); const hasEmptyCommandCenterCell = firstEmptyIndex !== -1; showContextMenu(task, e, { @@ -156,6 +158,8 @@ function SidebarMenuComponent() { if (firstEmptyIndex !== -1) { assignTaskToCommandCenter(firstEmptyIndex, taskId); navigateToCommandCenter(); + } else { + toast.info("Command center is full"); } }, }); From c4d5bdde783ae118d29ecd38fc7e00b59631e156 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Wed, 15 Apr 2026 14:34:36 -0700 Subject: [PATCH 3/3] Use fresh store state for command center cell assignment --- .../features/sidebar/components/SidebarMenu.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx b/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx index f1c3d47b6..819425ddd 100644 --- a/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx +++ b/apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx @@ -139,11 +139,12 @@ function SidebarMenuComponent() { if (task) { const workspace = workspaces[taskId]; const taskData = allSidebarTasks.find((t) => t.id === taskId); - const isInCommandCenter = commandCenterCells.includes(taskId); - const firstEmptyIndex = commandCenterCells.findIndex( + const isInCommandCenter = commandCenterCells.some( + (id) => id === taskId && taskMap.has(id), + ); + const hasEmptyCommandCenterCell = commandCenterCells.some( (id) => id == null || !taskMap.has(id), ); - const hasEmptyCommandCenterCell = firstEmptyIndex !== -1; showContextMenu(task, e, { worktreePath: workspace?.worktreePath ?? undefined, @@ -155,8 +156,10 @@ function SidebarMenuComponent() { onTogglePin: () => togglePin(taskId), onArchivePrior: handleArchivePrior, onAddToCommandCenter: () => { - if (firstEmptyIndex !== -1) { - assignTaskToCommandCenter(firstEmptyIndex, taskId); + const cells = useCommandCenterStore.getState().cells; + const idx = cells.findIndex((id) => id == null || !taskMap.has(id)); + if (idx !== -1) { + assignTaskToCommandCenter(idx, taskId); navigateToCommandCenter(); } else { toast.info("Command center is full");