Skip to content

Commit 49c0b50

Browse files
authored
fix(command-center): scope session hotkeys to active task (#1309)
1 parent 4bb8490 commit 49c0b50

File tree

6 files changed

+84
-13
lines changed

6 files changed

+84
-13
lines changed

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,32 @@ function GridCell({
4646
cell,
4747
zoom,
4848
isDragActive,
49+
activeTaskId,
4950
}: {
5051
cell: CommandCenterCellData;
5152
zoom: number;
5253
isDragActive: boolean;
54+
activeTaskId: string | null;
5355
}) {
5456
const cellRef = useRef<HTMLDivElement>(null);
5557
const [isDragOver, setIsDragOver] = useState(false);
58+
const setActiveTask = useCommandCenterStore((s) => s.setActiveTask);
59+
const isActive = !!cell.taskId && activeTaskId === cell.taskId;
5660

5761
const handleCellClick = useCallback(() => {
62+
setActiveTask(cell.taskId);
5863
const actionSelector =
5964
cellRef.current?.querySelector<HTMLElement>("[tabindex='0']");
6065
actionSelector?.focus();
61-
}, []);
66+
}, [cell.taskId, setActiveTask]);
67+
68+
const handleCellPointerDownCapture = useCallback(() => {
69+
setActiveTask(cell.taskId);
70+
}, [cell.taskId, setActiveTask]);
71+
72+
const handleCellFocusCapture = useCallback(() => {
73+
setActiveTask(cell.taskId);
74+
}, [cell.taskId, setActiveTask]);
6275

6376
const handleDragOver = useCallback((e: React.DragEvent) => {
6477
if (e.dataTransfer.types.includes("text/x-task-id")) {
@@ -88,16 +101,20 @@ function GridCell({
88101
// biome-ignore lint/a11y/useKeyWithClickEvents lint/a11y/noStaticElementInteractions: click delegates focus to ActionSelector within
89102
<div
90103
ref={cellRef}
91-
className="relative overflow-hidden bg-gray-1 focus-within:ring-2 focus-within:ring-accent-9 focus-within:ring-inset"
104+
className={`relative overflow-hidden bg-gray-1 focus-within:ring-2 focus-within:ring-accent-9 focus-within:ring-inset ${
105+
isActive ? "ring-2 ring-accent-9 ring-inset" : ""
106+
}`}
92107
onClick={handleCellClick}
108+
onPointerDownCapture={handleCellPointerDownCapture}
109+
onFocusCapture={handleCellFocusCapture}
93110
>
94111
<div
95112
className="h-full w-full origin-top-left"
96113
style={{
97114
zoom: zoom !== 1 ? zoom : undefined,
98115
}}
99116
>
100-
<CommandCenterPanel cell={cell} />
117+
<CommandCenterPanel cell={cell} isActiveSession={isActive} />
101118
</div>
102119
{isDragActive && (
103120
// biome-ignore lint/a11y/noStaticElementInteractions: transparent overlay to capture drag events over session content
@@ -119,6 +136,7 @@ function GridCell({
119136
export function CommandCenterGrid({ layout, cells }: CommandCenterGridProps) {
120137
const { cols, rows } = getGridDimensions(layout);
121138
const zoom = useCommandCenterStore((s) => s.zoom);
139+
const activeTaskId = useCommandCenterStore((s) => s.activeTaskId);
122140
const isDragActive = useTaskDragActive();
123141

124142
return (
@@ -137,6 +155,7 @@ export function CommandCenterGrid({ layout, cells }: CommandCenterGridProps) {
137155
cell={cell}
138156
zoom={zoom}
139157
isDragActive={isDragActive}
158+
activeTaskId={activeTaskId}
140159
/>
141160
))}
142161
</div>

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { TaskSelector } from "./TaskSelector";
1111

1212
interface CommandCenterPanelProps {
1313
cell: CommandCenterCellData;
14+
isActiveSession: boolean;
1415
}
1516

1617
function EmptyCell({ cellIndex }: { cellIndex: number }) {
@@ -43,8 +44,10 @@ function EmptyCell({ cellIndex }: { cellIndex: number }) {
4344

4445
function PopulatedCell({
4546
cell,
47+
isActiveSession,
4648
}: {
4749
cell: CommandCenterCellData & { task: Task };
50+
isActiveSession: boolean;
4851
}) {
4952
const navigateToTask = useNavigationStore((s) => s.navigateToTask);
5053
const removeTask = useCommandCenterStore((s) => s.removeTask);
@@ -101,18 +104,28 @@ function PopulatedCell({
101104
</Flex>
102105

103106
<Flex direction="column" className="min-h-0 flex-1">
104-
<CommandCenterSessionView taskId={cell.task.id} task={cell.task} />
107+
<CommandCenterSessionView
108+
taskId={cell.task.id}
109+
task={cell.task}
110+
isActiveSession={isActiveSession}
111+
/>
105112
</Flex>
106113
</Flex>
107114
);
108115
}
109116

110-
export function CommandCenterPanel({ cell }: CommandCenterPanelProps) {
117+
export function CommandCenterPanel({
118+
cell,
119+
isActiveSession,
120+
}: CommandCenterPanelProps) {
111121
if (!cell.taskId || !cell.task) {
112122
return <EmptyCell cellIndex={cell.cellIndex} />;
113123
}
114124

115125
return (
116-
<PopulatedCell cell={cell as CommandCenterCellData & { task: Task }} />
126+
<PopulatedCell
127+
cell={cell as CommandCenterCellData & { task: Task }}
128+
isActiveSession={isActiveSession}
129+
/>
117130
);
118131
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import { useEffect } from "react";
1010
interface CommandCenterSessionViewProps {
1111
taskId: string;
1212
task: Task;
13+
isActiveSession: boolean;
1314
}
1415

1516
export function CommandCenterSessionView({
1617
taskId,
1718
task,
19+
isActiveSession,
1820
}: CommandCenterSessionViewProps) {
1921
const { requestFocus } = useDraftStore((s) => s.actions);
2022

@@ -67,6 +69,7 @@ export function CommandCenterSessionView({
6769
onNewSession={isCloud ? undefined : handleNewSession}
6870
isInitializing={isInitializing}
6971
compact
72+
isActiveSession={isActiveSession}
7073
/>
7174
</Flex>
7275
);

apps/code/src/renderer/features/command-center/stores/commandCenterStore.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ function getCellCount(preset: LayoutPreset): number {
2222
interface CommandCenterStoreState {
2323
layout: LayoutPreset;
2424
cells: (string | null)[];
25+
activeTaskId: string | null;
2526
zoom: number;
2627
}
2728

2829
interface CommandCenterStoreActions {
2930
setLayout: (preset: LayoutPreset) => void;
31+
setActiveTask: (taskId: string | null) => void;
3032
assignTask: (cellIndex: number, taskId: string) => void;
3133
removeTask: (cellIndex: number) => void;
3234
removeTaskById: (taskId: string) => void;
@@ -60,14 +62,22 @@ export const useCommandCenterStore = create<CommandCenterStore>()(
6062
(set) => ({
6163
layout: "2x2",
6264
cells: [null, null, null, null],
65+
activeTaskId: null,
6366
zoom: 1,
6467

6568
setLayout: (preset) =>
6669
set((state) => ({
70+
activeTaskId: resizeCells(state.cells, getCellCount(preset)).includes(
71+
state.activeTaskId,
72+
)
73+
? state.activeTaskId
74+
: null,
6775
layout: preset,
6876
cells: resizeCells(state.cells, getCellCount(preset)),
6977
})),
7078

79+
setActiveTask: (taskId) => set({ activeTaskId: taskId }),
80+
7181
assignTask: (cellIndex, taskId) =>
7282
set((state) => {
7383
if (cellIndex < 0 || cellIndex >= state.cells.length) return state;
@@ -77,14 +87,21 @@ export const useCommandCenterStore = create<CommandCenterStore>()(
7787
cells[existingIndex] = null;
7888
}
7989
cells[cellIndex] = taskId;
80-
return { cells };
90+
return { cells, activeTaskId: taskId };
8191
}),
8292

8393
removeTask: (cellIndex) =>
8494
set((state) => {
8595
const cells = [...state.cells];
96+
const removedTaskId = cells[cellIndex];
8697
cells[cellIndex] = null;
87-
return { cells };
98+
return {
99+
cells,
100+
activeTaskId:
101+
removedTaskId && state.activeTaskId === removedTaskId
102+
? null
103+
: state.activeTaskId,
104+
};
88105
}),
89106

90107
removeTaskById: (taskId) =>
@@ -93,11 +110,16 @@ export const useCommandCenterStore = create<CommandCenterStore>()(
93110
if (index === -1) return state;
94111
const cells = [...state.cells];
95112
cells[index] = null;
96-
return { cells };
113+
return {
114+
cells,
115+
activeTaskId:
116+
state.activeTaskId === taskId ? null : state.activeTaskId,
117+
};
97118
}),
98119

99120
clearAll: () =>
100121
set((state) => ({
122+
activeTaskId: null,
101123
cells: state.cells.map(() => null),
102124
})),
103125

@@ -113,6 +135,7 @@ export const useCommandCenterStore = create<CommandCenterStore>()(
113135
partialize: (state) => ({
114136
layout: state.layout,
115137
cells: state.cells,
138+
activeTaskId: state.activeTaskId,
116139
zoom: state.zoom,
117140
}),
118141
},

apps/code/src/renderer/features/message-editor/components/MessageEditor.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ interface MessageEditorProps {
133133
onModeChange?: () => void;
134134
onFocus?: () => void;
135135
onBlur?: () => void;
136+
isActiveSession?: boolean;
136137
}
137138

138139
export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
@@ -150,6 +151,7 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
150151
onModeChange,
151152
onFocus,
152153
onBlur,
154+
isActiveSession = true,
153155
},
154156
ref,
155157
) => {
@@ -235,6 +237,7 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
235237
"escape",
236238
(e) => {
237239
if (hasOpenOverlay()) return;
240+
if (!isActiveSession) return;
238241
if (isLoading && onCancel) {
239242
e.preventDefault();
240243
onCancel();
@@ -245,7 +248,7 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
245248
enableOnContentEditable: true,
246249
enabled: isLoading && !!onCancel,
247250
},
248-
[isLoading, onCancel],
251+
[isActiveSession, isLoading, onCancel],
249252
);
250253

251254
const handleContainerClick = (e: React.MouseEvent) => {

apps/code/src/renderer/features/sessions/components/SessionView.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ interface SessionViewProps {
6060
isInitializing?: boolean;
6161
slackThreadUrl?: string;
6262
compact?: boolean;
63+
isActiveSession?: boolean;
6364
}
6465

6566
const DEFAULT_ERROR_MESSAGE =
@@ -88,6 +89,7 @@ export function SessionView({
8889
isInitializing = false,
8990
slackThreadUrl,
9091
compact = false,
92+
isActiveSession = true,
9193
}: SessionViewProps) {
9294
const showRawLogs = useShowRawLogs();
9395
const { setShowRawLogs } = useSessionViewActions();
@@ -161,9 +163,16 @@ export function SessionView({
161163
{
162164
enableOnFormTags: true,
163165
enableOnContentEditable: true,
164-
enabled: isRunning && !!modeOption,
166+
enabled: isRunning && !!modeOption && isActiveSession,
165167
},
166-
[taskId, currentModeId, isRunning, modeOption, allowBypassPermissions],
168+
[
169+
taskId,
170+
currentModeId,
171+
isRunning,
172+
modeOption,
173+
allowBypassPermissions,
174+
isActiveSession,
175+
],
167176
);
168177

169178
const latestPlan = useMemo((): Plan | null => {
@@ -352,7 +361,7 @@ export function SessionView({
352361
editorRef.current?.focus();
353362
}, []);
354363

355-
useAutoFocusOnTyping(editorRef);
364+
useAutoFocusOnTyping(editorRef, !isActiveSession);
356365

357366
return (
358367
<ContextMenu.Root>
@@ -538,6 +547,7 @@ export function SessionView({
538547
onCancel={onCancelPrompt}
539548
modeOption={modeOption}
540549
onModeChange={modeOption ? handleModeChange : undefined}
550+
isActiveSession={isActiveSession}
541551
/>
542552
</Box>
543553
</Box>

0 commit comments

Comments
 (0)