From f3c68c9d4308ae31c937acb39be9d212bc019aed Mon Sep 17 00:00:00 2001 From: Vojta Bartos Date: Tue, 28 Apr 2026 18:28:23 +0200 Subject: [PATCH 1/2] fix(code): keep focus in popovers opened from command center cells GridCell's onClick redirected focus to the cell's first tab stop on every click, including ones that bubbled in through React's synthetic event system from portaled popovers (BranchSelector, GitHubRepoPicker, etc.). Skip the redirect when the click already lands on a focusable control or bubbled from a target outside the cell's DOM. Generated-By: PostHog Code Task-Id: c9fad25f-633d-4125-af8b-2a6da16e859f --- .../components/CommandCenterGrid.tsx | 29 ++++++++++++++----- .../task-detail/components/TaskInput.tsx | 9 ++++++ apps/code/src/renderer/utils/overlay.ts | 3 ++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/code/src/renderer/features/command-center/components/CommandCenterGrid.tsx b/apps/code/src/renderer/features/command-center/components/CommandCenterGrid.tsx index 5140bf2119..9034216505 100644 --- a/apps/code/src/renderer/features/command-center/components/CommandCenterGrid.tsx +++ b/apps/code/src/renderer/features/command-center/components/CommandCenterGrid.tsx @@ -1,3 +1,4 @@ +import { FOCUSABLE_SELECTOR } from "@utils/overlay"; import { useCallback, useEffect, useRef, useState } from "react"; import type { CommandCenterCellData } from "../hooks/useCommandCenterData"; import { @@ -58,14 +59,26 @@ function GridCell({ const setActiveTask = useCommandCenterStore((s) => s.setActiveTask); const isActive = !!cell.taskId && activeTaskId === cell.taskId; - const handleCellClick = useCallback(() => { - setActiveTask(cell.taskId); - const selection = window.getSelection(); - if (selection && !selection.isCollapsed) return; - const actionSelector = - cellRef.current?.querySelector("[tabindex='0']"); - actionSelector?.focus(); - }, [cell.taskId, setActiveTask]); + const handleCellClick = useCallback( + (e: React.MouseEvent) => { + setActiveTask(cell.taskId); + 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("[tabindex='0']")?.focus(); + }, + [cell.taskId, setActiveTask], + ); const handleCellPointerDownCapture = useCallback(() => { setActiveTask(cell.taskId); diff --git a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx index 22f6dcf38a..c75b7df024 100644 --- a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx +++ b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx @@ -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"; @@ -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
Date: Tue, 28 Apr 2026 18:50:52 +0200 Subject: [PATCH 2/2] fix(code): show active cell indicator for empty and creating cells Active cell tracking previously keyed off activeTaskId, so cells without a task (empty or in the new-task flow) never lit up the red border. Track the active cell by index instead, and show the indicator whenever a cell is the active one regardless of whether it has a task. Generated-By: PostHog Code Task-Id: c9fad25f-633d-4125-af8b-2a6da16e859f --- .../components/CommandCenterGrid.tsx | 31 +++++++++---------- .../stores/commandCenterStore.ts | 11 +++++++ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/apps/code/src/renderer/features/command-center/components/CommandCenterGrid.tsx b/apps/code/src/renderer/features/command-center/components/CommandCenterGrid.tsx index 9034216505..ad61b6daf3 100644 --- a/apps/code/src/renderer/features/command-center/components/CommandCenterGrid.tsx +++ b/apps/code/src/renderer/features/command-center/components/CommandCenterGrid.tsx @@ -47,21 +47,26 @@ function GridCell({ cell, zoom, isDragActive, - activeTaskId, + isActive, }: { cell: CommandCenterCellData; zoom: number; isDragActive: boolean; - activeTaskId: string | null; + isActive: boolean; }) { const cellRef = useRef(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 markActive = useCallback(() => { + setActiveCell(cell.cellIndex); + setActiveTask(cell.taskId); + }, [cell.cellIndex, cell.taskId, setActiveCell, setActiveTask]); const handleCellClick = useCallback( (e: React.MouseEvent) => { - setActiveTask(cell.taskId); + 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 @@ -77,17 +82,9 @@ function GridCell({ if (selection && !selection.isCollapsed) return; cellRef.current?.querySelector("[tabindex='0']")?.focus(); }, - [cell.taskId, setActiveTask], + [markActive], ); - const handleCellPointerDownCapture = useCallback(() => { - setActiveTask(cell.taskId); - }, [cell.taskId, setActiveTask]); - - const handleCellFocusCapture = useCallback(() => { - setActiveTask(cell.taskId); - }, [cell.taskId, setActiveTask]); - const handleDragOver = useCallback((e: React.DragEvent) => { if (e.dataTransfer.types.includes("text/x-task-id")) { e.preventDefault(); @@ -119,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} >
s.zoom); - const activeTaskId = useCommandCenterStore((s) => s.activeTaskId); + const activeCellIndex = useCommandCenterStore((s) => s.activeCellIndex); const isDragActive = useTaskDragActive(); return ( @@ -170,7 +167,7 @@ export function CommandCenterGrid({ layout, cells }: CommandCenterGridProps) { cell={cell} zoom={zoom} isDragActive={isDragActive} - activeTaskId={activeTaskId} + isActive={activeCellIndex === cell.cellIndex} /> ))}
diff --git a/apps/code/src/renderer/features/command-center/stores/commandCenterStore.ts b/apps/code/src/renderer/features/command-center/stores/commandCenterStore.ts index 7e8b354ce2..77c36fc619 100644 --- a/apps/code/src/renderer/features/command-center/stores/commandCenterStore.ts +++ b/apps/code/src/renderer/features/command-center/stores/commandCenterStore.ts @@ -23,6 +23,7 @@ interface CommandCenterStoreState { layout: LayoutPreset; cells: (string | null)[]; activeTaskId: string | null; + activeCellIndex: number | null; zoom: number; creatingCells: number[]; } @@ -30,6 +31,7 @@ interface CommandCenterStoreState { 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; @@ -70,6 +72,7 @@ export const useCommandCenterStore = create()( layout: "2x2", cells: [null, null, null, null], activeTaskId: null, + activeCellIndex: null, zoom: 1, creatingCells: [], @@ -82,6 +85,10 @@ export const useCommandCenterStore = create()( ) ? 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), @@ -90,6 +97,8 @@ export const useCommandCenterStore = create()( setActiveTask: (taskId) => set({ activeTaskId: taskId }), + setActiveCell: (cellIndex) => set({ activeCellIndex: cellIndex }), + assignTask: (cellIndex, taskId) => set((state) => { if (cellIndex < 0 || cellIndex >= state.cells.length) return state; @@ -136,6 +145,7 @@ export const useCommandCenterStore = create()( clearAll: () => set((state) => ({ activeTaskId: null, + activeCellIndex: null, cells: state.cells.map(() => null), creatingCells: [], })), @@ -165,6 +175,7 @@ export const useCommandCenterStore = create()( layout: state.layout, cells: state.cells, activeTaskId: state.activeTaskId, + activeCellIndex: state.activeCellIndex, zoom: state.zoom, creatingCells: state.creatingCells, }),