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..83b82d422 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..819425ddd 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,32 @@ function SidebarMenuComponent() { if (task) { const workspace = workspaces[taskId]; const taskData = allSidebarTasks.find((t) => t.id === taskId); + const isInCommandCenter = commandCenterCells.some( + (id) => id === taskId && taskMap.has(id), + ); + const hasEmptyCommandCenterCell = commandCenterCells.some( + (id) => id == null || !taskMap.has(id), + ); + showContextMenu(task, e, { worktreePath: workspace?.worktreePath ?? undefined, folderPath: workspace?.folderPath ?? undefined, isPinned, isSuspended: taskData?.isSuspended, + isInCommandCenter, + hasEmptyCommandCenterCell, onTogglePin: () => togglePin(taskId), onArchivePrior: handleArchivePrior, + onAddToCommandCenter: () => { + 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"); + } + }, }); } }; 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) {