Skip to content

Commit bb5e11f

Browse files
committed
feat(code): allow new task creation in command center
1 parent 4f5ee10 commit bb5e11f

4 files changed

Lines changed: 93 additions & 29 deletions

File tree

apps/code/src/renderer/features/command-center/components/CommandCenterPanel.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { TaskInput } from "@features/task-detail/components/TaskInput";
12
import { ArrowsOut, Plus, X } from "@phosphor-icons/react";
23
import { Flex, Text } from "@radix-ui/themes";
34
import type { Task } from "@shared/types";
@@ -15,6 +16,48 @@ interface CommandCenterPanelProps {
1516

1617
function EmptyCell({ cellIndex }: { cellIndex: number }) {
1718
const [selectorOpen, setSelectorOpen] = useState(false);
19+
const [isCreating, setIsCreating] = useState(false);
20+
const assignTask = useCommandCenterStore((s) => s.assignTask);
21+
22+
const handleTaskCreated = useCallback(
23+
(task: Task) => {
24+
assignTask(cellIndex, task.id);
25+
},
26+
[assignTask, cellIndex],
27+
);
28+
29+
if (isCreating) {
30+
return (
31+
<Flex direction="column" height="100%">
32+
<Flex
33+
align="center"
34+
justify="between"
35+
px="2"
36+
py="1"
37+
className="shrink-0 border-gray-6 border-b"
38+
>
39+
<Text
40+
size="1"
41+
weight="medium"
42+
className="font-mono text-[11px] text-gray-11"
43+
>
44+
New task
45+
</Text>
46+
<button
47+
type="button"
48+
onClick={() => setIsCreating(false)}
49+
className="flex h-5 w-5 items-center justify-center rounded text-gray-10 transition-colors hover:bg-gray-4 hover:text-gray-12"
50+
title="Cancel"
51+
>
52+
<X size={12} />
53+
</button>
54+
</Flex>
55+
<Flex direction="column" className="min-h-0 flex-1">
56+
<TaskInput onTaskCreated={handleTaskCreated} />
57+
</Flex>
58+
</Flex>
59+
);
60+
}
1861

1962
return (
2063
<Flex align="center" justify="center" height="100%">
@@ -23,6 +66,7 @@ function EmptyCell({ cellIndex }: { cellIndex: number }) {
2366
cellIndex={cellIndex}
2467
open={selectorOpen}
2568
onOpenChange={setSelectorOpen}
69+
onNewTask={() => setIsCreating(true)}
2670
>
2771
<button
2872
type="button"

apps/code/src/renderer/features/command-center/components/TaskSelector.tsx

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ interface TaskSelectorProps {
99
cellIndex: number;
1010
open: boolean;
1111
onOpenChange: (open: boolean) => void;
12+
onNewTask?: () => void;
1213
children: ReactNode;
1314
}
1415

1516
export function TaskSelector({
1617
cellIndex,
1718
open,
1819
onOpenChange,
20+
onNewTask,
1921
children,
2022
}: TaskSelectorProps) {
2123
const availableTasks = useAvailableTasks();
@@ -32,8 +34,12 @@ export function TaskSelector({
3234

3335
const handleNewTask = useCallback(() => {
3436
onOpenChange(false);
35-
navigateToTaskInput();
36-
}, [onOpenChange, navigateToTaskInput]);
37+
if (onNewTask) {
38+
onNewTask();
39+
} else {
40+
navigateToTaskInput();
41+
}
42+
}, [onOpenChange, onNewTask, navigateToTaskInput]);
3743

3844
return (
3945
<Popover.Root open={open} onOpenChange={onOpenChange}>
@@ -44,29 +50,31 @@ export function TaskSelector({
4450
sideOffset={4}
4551
style={{ padding: 4, minWidth: 240, maxHeight: 300 }}
4652
>
47-
<div className="overflow-y-auto" style={{ maxHeight: 280 }}>
48-
<button
49-
type="button"
50-
onClick={handleNewTask}
51-
className="flex w-full items-center gap-1.5 rounded-sm px-2 py-1.5 text-left font-mono text-[11px] text-gray-12 transition-colors hover:bg-gray-3"
52-
>
53-
<Plus size={12} className="shrink-0" />
54-
<span>New task</span>
55-
</button>
56-
{availableTasks.length > 0 && (
57-
<>
58-
<Separator size="4" my="1" />
59-
{availableTasks.map((task) => (
60-
<button
61-
key={task.id}
62-
type="button"
63-
onClick={() => handleSelect(task.id)}
64-
className="flex w-full items-center rounded-sm px-2 py-1.5 text-left font-mono text-[11px] text-gray-12 transition-colors hover:bg-gray-3"
65-
>
66-
<span className="min-w-0 flex-1 truncate">{task.title}</span>
67-
</button>
68-
))}
69-
</>
53+
<button
54+
type="button"
55+
onClick={handleNewTask}
56+
className="flex w-full items-center gap-1.5 rounded-sm px-2 py-1.5 text-left font-mono text-[11px] text-gray-12 transition-colors hover:bg-gray-3"
57+
>
58+
<Plus size={12} />
59+
New task
60+
</button>
61+
<Separator size="4" className="my-1" />
62+
<div className="overflow-y-auto" style={{ maxHeight: 240 }}>
63+
{availableTasks.length === 0 ? (
64+
<div className="px-2 py-3 text-center font-mono text-[11px] text-gray-10">
65+
No available tasks
66+
</div>
67+
) : (
68+
availableTasks.map((task) => (
69+
<button
70+
key={task.id}
71+
type="button"
72+
onClick={() => handleSelect(task.id)}
73+
className="flex w-full items-center rounded-sm px-2 py-1.5 text-left font-mono text-[11px] text-gray-12 transition-colors hover:bg-gray-3"
74+
>
75+
<span className="min-w-0 flex-1 truncate">{task.title}</span>
76+
</button>
77+
))
7078
)}
7179
</div>
7280
</Popover.Content>

apps/code/src/renderer/features/task-detail/components/TaskInput.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ import { type WorkspaceMode, WorkspaceModeSelect } from "./WorkspaceModeSelect";
3131

3232
const DOT_FILL = "var(--gray-6)";
3333

34-
export function TaskInput() {
34+
interface TaskInputProps {
35+
onTaskCreated?: (task: import("@shared/types").Task) => void;
36+
}
37+
38+
export function TaskInput({ onTaskCreated }: TaskInputProps = {}) {
3539
const trpcReact = useTRPC();
3640
const { view } = useNavigationStore();
3741
const { data: mostRecentRepo } = useQuery(
@@ -131,6 +135,7 @@ export function TaskInput() {
131135
executionMode: currentExecutionMode,
132136
model: currentModel,
133137
reasoningLevel: currentReasoningLevel,
138+
onTaskCreated,
134139
});
135140

136141
const handleCycleMode = useCallback(() => {

apps/code/src/renderer/features/task-detail/hooks/useTaskCreation.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useConnectivity } from "@hooks/useConnectivity";
99
import type { WorkspaceMode } from "@main/services/workspace/schemas";
1010
import { get } from "@renderer/di/container";
1111
import { RENDERER_TOKENS } from "@renderer/di/tokens";
12-
import type { ExecutionMode } from "@shared/types";
12+
import type { ExecutionMode, Task } from "@shared/types";
1313
import { useNavigationStore } from "@stores/navigationStore";
1414
import { logger } from "@utils/logger";
1515
import { useCallback, useState } from "react";
@@ -30,6 +30,7 @@ interface UseTaskCreationOptions {
3030
adapter?: "claude" | "codex";
3131
model?: string;
3232
reasoningLevel?: string;
33+
onTaskCreated?: (task: Task) => void;
3334
}
3435

3536
interface UseTaskCreationReturn {
@@ -91,6 +92,7 @@ export function useTaskCreation({
9192
adapter,
9293
model,
9394
reasoningLevel,
95+
onTaskCreated,
9496
}: UseTaskCreationOptions): UseTaskCreationReturn {
9597
const [isCreatingTask, setIsCreatingTask] = useState(false);
9698
const { navigateToTask } = useNavigationStore();
@@ -140,8 +142,12 @@ export function useTaskCreation({
140142
// Invalidate tasks query
141143
invalidateTasks(task);
142144

143-
// Navigate to the new task
144-
navigateToTask(task);
145+
// Navigate to the new task or notify caller
146+
if (onTaskCreated) {
147+
onTaskCreated(task);
148+
} else {
149+
navigateToTask(task);
150+
}
145151

146152
// Clear editor
147153
editor.clear();
@@ -176,6 +182,7 @@ export function useTaskCreation({
176182
reasoningLevel,
177183
invalidateTasks,
178184
navigateToTask,
185+
onTaskCreated,
179186
]);
180187

181188
return {

0 commit comments

Comments
 (0)