Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FOCUSABLE_SELECTOR } from "@utils/overlay";
import { useCallback, useEffect, useRef, useState } from "react";
import type { CommandCenterCellData } from "../hooks/useCommandCenterData";
import {
Expand Down Expand Up @@ -46,34 +47,43 @@ function GridCell({
cell,
zoom,
isDragActive,
activeTaskId,
isActive,
}: {
cell: CommandCenterCellData;
zoom: number;
isDragActive: boolean;
activeTaskId: string | null;
isActive: boolean;
}) {
const cellRef = useRef<HTMLDivElement>(null);
const [isDragOver, setIsDragOver] = useState(false);
const setActiveTask = useCommandCenterStore((s) => s.setActiveTask);
const isActive = !!cell.taskId && activeTaskId === cell.taskId;
const setActiveCell = useCommandCenterStore((s) => s.setActiveCell);

const handleCellClick = useCallback(() => {
const markActive = useCallback(() => {
setActiveCell(cell.cellIndex);
setActiveTask(cell.taskId);
const selection = window.getSelection();
if (selection && !selection.isCollapsed) return;
const actionSelector =
cellRef.current?.querySelector<HTMLElement>("[tabindex='0']");
actionSelector?.focus();
}, [cell.taskId, setActiveTask]);
}, [cell.cellIndex, cell.taskId, setActiveCell, setActiveTask]);

const handleCellPointerDownCapture = useCallback(() => {
setActiveTask(cell.taskId);
}, [cell.taskId, setActiveTask]);

const handleCellFocusCapture = useCallback(() => {
setActiveTask(cell.taskId);
}, [cell.taskId, setActiveTask]);
const handleCellClick = useCallback(
(e: React.MouseEvent) => {
markActive();
const target = e.target as HTMLElement;
// Don't redirect focus when the click already lands on a real control,
// or when it bubbled in from a portaled popover whose DOM target is
// outside this cell. Either way the click is targeting something that
// owns its own focus.
if (
!e.currentTarget.contains(target) ||
target.closest(FOCUSABLE_SELECTOR)
) {
return;
}
const selection = window.getSelection();
if (selection && !selection.isCollapsed) return;
cellRef.current?.querySelector<HTMLElement>("[tabindex='0']")?.focus();
},
[markActive],
);

const handleDragOver = useCallback((e: React.DragEvent) => {
if (e.dataTransfer.types.includes("text/x-task-id")) {
Expand Down Expand Up @@ -106,8 +116,8 @@ function GridCell({
data-grid-cell
className="relative overflow-hidden bg-gray-1"
onClick={handleCellClick}
onPointerDownCapture={handleCellPointerDownCapture}
onFocusCapture={handleCellFocusCapture}
onPointerDownCapture={markActive}
onFocusCapture={markActive}
>
<div
className="h-full w-full origin-top-left"
Expand Down Expand Up @@ -140,7 +150,7 @@ function GridCell({
export function CommandCenterGrid({ layout, cells }: CommandCenterGridProps) {
const { cols, rows } = getGridDimensions(layout);
const zoom = useCommandCenterStore((s) => s.zoom);
const activeTaskId = useCommandCenterStore((s) => s.activeTaskId);
const activeCellIndex = useCommandCenterStore((s) => s.activeCellIndex);
const isDragActive = useTaskDragActive();

return (
Expand All @@ -157,7 +167,7 @@ export function CommandCenterGrid({ layout, cells }: CommandCenterGridProps) {
cell={cell}
zoom={zoom}
isDragActive={isDragActive}
activeTaskId={activeTaskId}
isActive={activeCellIndex === cell.cellIndex}
/>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ interface CommandCenterStoreState {
layout: LayoutPreset;
cells: (string | null)[];
activeTaskId: string | null;
activeCellIndex: number | null;
zoom: number;
creatingCells: number[];
}

interface CommandCenterStoreActions {
setLayout: (preset: LayoutPreset) => void;
setActiveTask: (taskId: string | null) => void;
setActiveCell: (cellIndex: number | null) => void;
assignTask: (cellIndex: number, taskId: string) => void;
removeTask: (cellIndex: number) => void;
removeTaskById: (taskId: string) => void;
Expand Down Expand Up @@ -70,6 +72,7 @@ export const useCommandCenterStore = create<CommandCenterStore>()(
layout: "2x2",
cells: [null, null, null, null],
activeTaskId: null,
activeCellIndex: null,
zoom: 1,
creatingCells: [],

Expand All @@ -82,6 +85,10 @@ export const useCommandCenterStore = create<CommandCenterStore>()(
)
? state.activeTaskId
: null,
activeCellIndex:
state.activeCellIndex !== null && state.activeCellIndex < newCount
? state.activeCellIndex
: null,
layout: preset,
cells: resizeCells(state.cells, newCount),
creatingCells: state.creatingCells.filter((i) => i < newCount),
Expand All @@ -90,6 +97,8 @@ export const useCommandCenterStore = create<CommandCenterStore>()(

setActiveTask: (taskId) => set({ activeTaskId: taskId }),

setActiveCell: (cellIndex) => set({ activeCellIndex: cellIndex }),

assignTask: (cellIndex, taskId) =>
set((state) => {
if (cellIndex < 0 || cellIndex >= state.cells.length) return state;
Expand Down Expand Up @@ -136,6 +145,7 @@ export const useCommandCenterStore = create<CommandCenterStore>()(
clearAll: () =>
set((state) => ({
activeTaskId: null,
activeCellIndex: null,
cells: state.cells.map(() => null),
creatingCells: [],
})),
Expand Down Expand Up @@ -165,6 +175,7 @@ export const useCommandCenterStore = create<CommandCenterStore>()(
layout: state.layout,
cells: state.cells,
activeTaskId: state.activeTaskId,
activeCellIndex: state.activeCellIndex,
zoom: state.zoom,
creatingCells: state.creatingCells,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { toast } from "@renderer/utils/toast";
import { useNavigationStore } from "@stores/navigationStore";
import { useQuery } from "@tanstack/react-query";
import { getFilePath } from "@utils/getFilePath";
import { FOCUSABLE_SELECTOR } from "@utils/overlay";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { usePreviewConfig } from "../hooks/usePreviewConfig";
import { useTaskCreation } from "../hooks/useTaskCreation";
Expand Down Expand Up @@ -480,10 +481,18 @@ export function TaskInput({
editorRef.current?.focus();
}, []);

const handleContainerClick = useCallback((e: React.MouseEvent) => {
if (!e.currentTarget.contains(e.target as Node)) return;
if ((e.target as HTMLElement).closest(FOCUSABLE_SELECTOR)) return;
editorRef.current?.focus();
}, []);

return (
// biome-ignore lint/a11y/noStaticElementInteractions: drag-and-drop container
// biome-ignore lint/a11y/useKeyWithClickEvents: click delegates focus to the editor; keyboard users tab into it directly
<div
ref={containerRef}
onClick={handleContainerClick}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
Expand Down
3 changes: 3 additions & 0 deletions apps/code/src/renderer/utils/overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ const OVERLAY_SELECTORS = [
export function hasOpenOverlay(): boolean {
return document.querySelector(OVERLAY_SELECTORS) !== null;
}

export const FOCUSABLE_SELECTOR =
'button, a, input, textarea, select, [role="button"], [role="link"], [role="combobox"], [role="menuitem"], [contenteditable="true"], [data-interactive]';
Loading