feat: add task navigation with Ctrl+# shortcuts#1395
feat: add task navigation with Ctrl+# shortcuts#1395drochag wants to merge 2 commits intogeneralaction:mainfrom
Conversation
Add direct task navigation using Ctrl+1-9 shortcuts. Tasks display number badges (⌃1, ⌃2, etc.) and pressing Ctrl+N selects the corresponding task. Cmd+1-9 remains for agent tab switching. - Add getTaskSelectionIndex function for Ctrl+1-9 task selection - Add shortcut badges to TaskItem component (first 9 tasks globally) - Add handleSelectTaskByIndex to useTaskManagement hook - Add numberShortcutBehavior setting to swap Ctrl/Cmd behavior - Add settings UI dropdown to configure number shortcut behavior - Update tests for new keyboard shortcut functions The behavior is configurable: users can swap modifiers in Settings so that Cmd+# selects tasks and Ctrl+# switches agent tabs. Closes generalaction#1389
|
@drochag is attempting to deploy a commit to the General Action Team on Vercel. A member of the Team first needs to authorize it. |
Greptile SummaryThis PR adds direct task navigation via
Confidence Score: 2/5
|
| Filename | Overview |
|---|---|
| src/renderer/hooks/useTaskManagement.ts | Adds handleSelectTaskByIndex but iterates projects (raw DB order) instead of sortedProjects (user's custom order), causing the wrong task to be selected when projects have been reordered. |
| src/renderer/hooks/useKeyboardShortcuts.ts | Adds getTaskSelectionIndex and integrates both task/agent number-key handlers, but the number-key section is not guarded by the isEditableTarget check, so shortcuts fire unexpectedly while typing in inputs. |
| src/renderer/components/sidebar/LeftSidebar.tsx | Correctly computes global shortcut indices using sortedProjects and passes them to TaskItem; however the ordering used here diverges from useTaskManagement.ts's resolution logic. |
| src/renderer/components/AppKeyboardShortcuts.tsx | Cleanly wires handleSelectTaskByIndex from context into useKeyboardShortcuts via the onSelectTask handler; no issues. |
| src/renderer/components/KeyboardSettingsCard.tsx | Adds a Select dropdown for the new setting and includes a defensive type-guard fix to prevent non-ShortcutBinding values (like numberShortcutBehavior) from being treated as bindings. |
| src/renderer/components/TaskItem.tsx | Adds optional shortcutIndex / shortcutModifier props and renders a small <kbd> badge; straightforward and well-isolated. |
| src/main/settings.ts | Adds NumberShortcutBehavior type, default value, and normalization logic; normalization correctly whitelists only the non-default value. |
| src/renderer/types/shortcuts.ts | Adds NumberShortcutBehavior type, extends KeyboardSettings and GlobalShortcutHandlers; clean, no issues. |
| src/test/renderer/useKeyboardShortcuts.test.ts | Good test coverage for both getTaskSelectionIndex and the updated getAgentTabSelectionIndex, including modifier-swap scenarios; no issues. |
Sequence Diagram
sequenceDiagram
participant User
participant KeyboardShortcuts as useKeyboardShortcuts
participant TaskMgmt as useTaskManagement
participant Sidebar as LeftSidebar
participant TaskItem
Note over Sidebar: On render: sortedProjects (custom order)
Sidebar->>Sidebar: compute taskShortcutIndices (1-9) from sortedProjects
Sidebar->>TaskItem: shortcutIndex={1..9}, shortcutModifier={'ctrl'|'cmd'}
TaskItem-->>User: renders ⌃1 / ⌘1 badge
User->>KeyboardShortcuts: keydown Ctrl+1
KeyboardShortcuts->>KeyboardShortcuts: getTaskSelectionIndex(event, useCmdForTasks)
KeyboardShortcuts->>TaskMgmt: onSelectTask(index=0)
Note over TaskMgmt: Iterates raw `projects` (DB order) ⚠️
TaskMgmt->>TaskMgmt: build allTasksInOrder from projects (not sortedProjects)
TaskMgmt-->>User: activates allTasksInOrder[0] (may differ from badge ⌃1)
Comments Outside Diff (1)
-
src/renderer/hooks/useKeyboardShortcuts.ts, line 604-638 (link)Number shortcuts fire inside editable fields
The
isEditableTargetguard (line 540-541) is applied only inside thefor (const shortcut of shortcuts)loop. The new task-selection and existing agent-tab-selection blocks that follow the loop have no such guard. As a result, pressingCtrl+1(orCmd+1) while the cursor is in a text<input>,<textarea>, orcontentEditableelement will unexpectedly switch the active task (or agent tab).Consider applying the same guard here:
// After the shortcut loop, before number-key handling: if (isEditableTarget) return; const useCmdForTasks = ...;
This matches the pattern used for every other non-palette shortcut and prevents surprising task switches while the user is actively typing.
Last reviewed commit: 2e51aef
| const handleSelectTaskByIndex = useCallback( | ||
| (index: number) => { | ||
| // Build a flat list of all tasks in sidebar order (by project, then pinned first within each) | ||
| const allTasksInOrder: Task[] = []; | ||
| for (const project of projects) { | ||
| const projectTasks = (tasksByProjectId[project.id] ?? []) | ||
| .slice() | ||
| .sort((a, b) => (b.metadata?.isPinned ? 1 : 0) - (a.metadata?.isPinned ? 1 : 0)); | ||
| allTasksInOrder.push(...projectTasks); | ||
| } | ||
|
|
||
| if (index >= 0 && index < allTasksInOrder.length) { | ||
| handleSelectTask(allTasksInOrder[index]); | ||
| } | ||
| }, | ||
| [projects, tasksByProjectId, handleSelectTask] |
There was a problem hiding this comment.
Stale project order causes wrong task selection
handleSelectTaskByIndex iterates projects (raw DB order from context), but the sidebar badge numbers are computed using sortedProjects (which respects the user-configurable projectOrder stored in localStorage — see LeftSidebar.tsx lines 110-120).
If the user has reordered their projects via drag-and-drop, the two orderings diverge. Pressing Ctrl+1 will select the task ranked first in DB order, but the ⌃1 badge will be shown on a different task (the first in the user's custom order). This means the shortcut silently selects the wrong task.
useTaskManagement doesn't have access to sortedProjects directly, so the fix should be to either:
- Accept the sorted project list as a parameter to the callback, or
- Move the index-to-task resolution into
LeftSidebarwheresortedProjectsis available and pass the resolvedTaskup instead of a numeric index.
// e.g. accept a pre-computed ordered list
const handleSelectTaskByIndex = useCallback(
(index: number, orderedTasks: Task[]) => {
if (index >= 0 && index < orderedTasks.length) {
handleSelectTask(orderedTasks[index]);
}
},
[handleSelectTask]
);
- Add isEditableTarget guard before number-key shortcuts to prevent task switching while typing in input fields - Use same project ordering (from localStorage) in handleSelectTaskByIndex as LeftSidebar uses for badge display, fixing mismatch when projects are reordered
|
Addressed the review feedback in e20b80c:
|
yashdev9274
left a comment
There was a problem hiding this comment.
hey @drochag
this task change shortcut feature is not smooth.
it's needing to click on project root to change the task every time - the sidebar/main content doesn't update properly on keyboard-triggered task selection.
> possible root cause
The keyboard shortcut flow (Ctrl+1 → handleSelectTaskByIndex(index) → handleSelectTask(task)) should call the same handleSelectTask as clicking on a task. However, there may be:
- Focus handling - keyboard events may not trigger proper focus/context updates
- State sync - React state batching differs between keyboard vs click flows
- Sidebar refresh - sidebar may not re-render to show active task highlight
> i would like you to address couple of things:
Investigate and fix the keyboard-triggered task selection to match click behavior:
- Add debug logging to verify:
handleSelectTaskByIndexis called correctly- Same task is resolved as clicking would produce
- Ensure proper focus/context updates after keyboard task selection
- Verify sidebar properly reflects
activeTaskchanges when triggered via keyboard
and push the new changes
Summary
Add direct task navigation using Ctrl+1-9 shortcuts, solving #1389.
Changes
numberShortcutBehaviorsetting type and defaultsonSelectTaskhandlergetTaskSelectionIndexfunction and update handlerhandleSelectTaskByIndexfunctionTest plan