Skip to content

feat: add task navigation with Ctrl+# shortcuts#1395

Open
drochag wants to merge 2 commits intogeneralaction:mainfrom
drochag:feat/task-navigation-shortcuts
Open

feat: add task navigation with Ctrl+# shortcuts#1395
drochag wants to merge 2 commits intogeneralaction:mainfrom
drochag:feat/task-navigation-shortcuts

Conversation

@drochag
Copy link

@drochag drochag commented Mar 10, 2026

Summary

Add direct task navigation using Ctrl+1-9 shortcuts, solving #1389.

  • Tasks display number badges (⌃1, ⌃2, etc.) for the first 9 tasks globally
  • Pressing Ctrl+N selects the corresponding task
  • Cmd+1-9 remains for agent tab switching (no regression)
  • Configurable: users can swap modifiers in Settings → Interface → "Number shortcuts (1-9)"

Changes

  • src/main/settings.ts: Add numberShortcutBehavior setting type and defaults
  • src/renderer/types/shortcuts.ts: Add types for the new setting and onSelectTask handler
  • src/renderer/hooks/useKeyboardShortcuts.ts: Add getTaskSelectionIndex function and update handler
  • src/renderer/hooks/useTaskManagement.ts: Add handleSelectTaskByIndex function
  • src/renderer/components/AppKeyboardShortcuts.tsx: Wire up the task selection handler
  • src/renderer/components/TaskItem.tsx: Add shortcut badge display (⌃1, ⌘2, etc.)
  • src/renderer/components/sidebar/LeftSidebar.tsx: Compute global task indices and pass to TaskItem
  • src/renderer/components/KeyboardSettingsCard.tsx: Add dropdown to configure modifier behavior
  • src/test/renderer/useKeyboardShortcuts.test.ts: Add tests for new functions

Test plan

  • Open app with multiple tasks across multiple projects
  • Verify tasks show ⌃1, ⌃2, etc. badges (first 9 globally)
  • Press Ctrl+1 → should select first task
  • Press Ctrl+5 → should select fifth task (if exists)
  • With 3 tasks, press Ctrl+5 → nothing should happen
  • Cmd+1-9 still switches agent tabs (no regression)
  • Change setting to "⌘N for tasks, ⌃N for agents" in Settings
  • Verify badges now show ⌘1, ⌘2, etc.
  • Cmd+1 should now select tasks, Ctrl+1 should switch agents
Screenshot 2026-03-10 at 12 14 43 p m Screenshot 2026-03-10 at 12 14 30 p m

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
@vercel
Copy link

vercel bot commented Mar 10, 2026

@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-apps
Copy link
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR adds direct task navigation via Ctrl+1-9 shortcuts (configurable to Cmd+1-9), with matching ⌃N/⌘N badges rendered on the first 9 tasks in the sidebar. The overall architecture is sound — settings plumbing, badge rendering, and the new getTaskSelectionIndex helper are all cleanly implemented — but two logic bugs affect correctness.

  • Wrong task selected when projects are reordered: LeftSidebar computes badge indices using sortedProjects (the user's custom drag-and-drop order from localStorage), while handleSelectTaskByIndex in useTaskManagement builds its flat task list from projects (raw DB order). Any user who has reordered their projects will see ⌃1 on one task but have Ctrl+1 activate a different task.
  • Shortcuts fire inside editable fields: The isEditableTarget guard that prevents other shortcuts from firing inside <input> / <textarea> / contenteditable elements is not applied to the number-key block. Pressing Ctrl+1 while typing in any text field will unexpectedly switch the active task.
  • The KeyboardSettingsCard type-guard fix (checking 'key' in saved && 'modifier' in saved before using a keyboard setting as a ShortcutBinding) is a nice defensive improvement.
  • Test coverage for getTaskSelectionIndex and the updated getAgentTabSelectionIndex is thorough.

Confidence Score: 2/5

  • Not safe to merge — the shortcut selects the wrong task for any user who has reordered their projects.
  • Two logic bugs are present: (1) the project-ordering mismatch between badge display and task resolution will silently select the wrong task for users with custom project order, and (2) number shortcuts are not blocked inside editable fields, causing unintended task switches while typing.
  • src/renderer/hooks/useTaskManagement.ts (ordering mismatch) and src/renderer/hooks/useKeyboardShortcuts.ts (missing editable-field guard) need fixes before merge.

Important Files Changed

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)
Loading

Comments Outside Diff (1)

  1. src/renderer/hooks/useKeyboardShortcuts.ts, line 604-638 (link)

    Number shortcuts fire inside editable fields

    The isEditableTarget guard (line 540-541) is applied only inside the for (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, pressing Ctrl+1 (or Cmd+1) while the cursor is in a text <input>, <textarea>, or contentEditable element 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

Comment on lines +392 to +407
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]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 LeftSidebar where sortedProjects is available and pass the resolved Task up 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
@drochag
Copy link
Author

drochag commented Mar 11, 2026

Addressed the review feedback in e20b80c:

  1. Fixed shortcuts firing in editable fields - Added isEditableTarget guard before the number-key handling block, so Ctrl+1-9 won't trigger while typing in input fields.

  2. Fixed project ordering mismatch - handleSelectTaskByIndex now reads from the same localStorage key (sidebarProjectOrder) that LeftSidebar uses to sort projects. This ensures the task selected by Ctrl+N matches the badge displayed.

Copy link
Contributor

@yashdev9274 yashdev9274 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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+1handleSelectTaskByIndex(index)handleSelectTask(task)) should call the same handleSelectTask as clicking on a task. However, there may be:

  1. Focus handling - keyboard events may not trigger proper focus/context updates
  2. State sync - React state batching differs between keyboard vs click flows
  3. 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:

  1. Add debug logging to verify:
    • handleSelectTaskByIndex is called correctly
    • Same task is resolved as clicking would produce
  2. Ensure proper focus/context updates after keyboard task selection
  3. Verify sidebar properly reflects activeTask changes when triggered via keyboard

and push the new changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants