feat: add task navigation with Ctrl+# shortcuts#1395
feat: add task navigation with Ctrl+# shortcuts#1395drochag wants to merge 4 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
Resolved conflicts by integrating upstream changes: - Added KeyboardShortcutBinding type that supports null (disabled shortcuts) - Added openInEditor shortcut - Added changelog notification feature - Added ProjectItem component in LeftSidebar While preserving feature changes: - numberShortcutBehavior setting for task navigation shortcuts - Task shortcut indices display (Ctrl+1-9) - handleSelectTaskByIndex function Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds configurable number-key task selection (1–9) with a new Changes
Sequence DiagramsequenceDiagram
participant User
participant Hook as useKeyboardShortcuts
participant Palette as CommandPalette
participant TaskMgmt as useTaskManagement
participant SidebarUI as LeftSidebar/TaskItem
User->>Hook: press modifier+N (1-9)
Hook->>Hook: getTaskSelectionIndex(event, useCmdForTasks)
alt matches task index
Hook->>Palette: request close (if open)
Palette-->>Hook: closed
Hook->>TaskMgmt: handlers.onSelectTask(index)
TaskMgmt->>TaskMgmt: resolve task from sidebar-ordered list
TaskMgmt->>SidebarUI: update selected task
SidebarUI->>User: task highlighted
else no task match
Hook->>Hook: getAgentTabSelectionIndex(event, useCtrlForAgents)
alt matches agent index
Hook->>Palette: request close (if open)
Palette-->>Hook: closed
Hook->>Hook: select agent tab
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/renderer/hooks/useTaskManagement.ts (1)
460-497: Extract the sidebar ordering into a shared helper.
handleSelectTaskByIndexnow re-implements the samesidebarProjectOrder+ pinned-first flattening thatsrc/renderer/components/sidebar/LeftSidebar.tsxuses for badge numbering. That contract already drifted once in this PR; keeping two copies means the displayed badge order and the selected task can silently diverge again the next time one side changes.Also applies to: 1151-1151
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/hooks/useTaskManagement.ts` around lines 460 - 497, handleSelectTaskByIndex duplicates the sidebar ordering + pinned-first flattening logic that LeftSidebar.tsx uses, causing potential drift; extract that logic into a shared helper (e.g., getSidebarOrderedTasks or getSidebarProjectOrder + flattenTasksByPinned) and replace the local implementation in useTaskManagement's handleSelectTaskByIndex as well as the logic in LeftSidebar.tsx to call the common helper; the helper should read 'sidebarProjectOrder' from localStorage, return projects in the stored order (falling back safely on errors), and provide the flattened task list that sorts pinned tasks first within each project so both badge numbering and task selection use identical ordering.src/renderer/components/KeyboardSettingsCard.tsx (1)
148-151: Consider keeping binding settings homogeneous.This cast is only needed because
KeyboardSettingsnow mixesnumberShortcutBehaviorin with theShortcutBinding | nullentries. The same mixed shape now leaks into generic consumers likeassertNoKeyboardShortcutConflictsinsrc/main/settings.ts. Splitting non-binding prefs out of the binding map would keep this code fully typed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/components/KeyboardSettingsCard.tsx` around lines 148 - 151, The keyboard settings map (KeyboardSettings) currently mixes non-binding prefs like numberShortcutBehavior into entries that should be only ShortcutBinding, forcing casts in KeyboardSettingsCard (the keyboard lookup using keyboard?.[shortcut.settingsKey as ShortcutSettingsKey]) and leaking the mixed shape into services such as assertNoKeyboardShortcutConflicts; split non-binding prefs out of the binding map so the binding map contains only ShortcutBinding | null, update the type of keyboard and ShortcutSettingsKey consumers accordingly, remove the ad-hoc cast in KeyboardSettingsCard, and update callers like assertNoKeyboardShortcutConflicts to read non-binding prefs (e.g. numberShortcutBehavior) from the separate config area rather than from the binding map.src/renderer/hooks/useKeyboardShortcuts.ts (1)
643-644: Consider adding a comment to clarify the parameter mapping.The call passes
useCmdForTasksto a parameter nameduseCtrlForAgents. While logically equivalent (when tasks use Cmd, agents use Ctrl), this reads confusingly at the call site. A brief comment would improve maintainability.💡 Suggested clarification
- // Check for agent tab selection (Cmd+1-9 by default, or Ctrl+1-9 if tasks use Cmd) - const agentTabIndex = getAgentTabSelectionIndex(event, useCmdForTasks); + // Check for agent tab selection (Cmd+1-9 by default, or Ctrl+1-9 if tasks use Cmd) + // Note: useCmdForTasks === useCtrlForAgents (when tasks use Cmd, agents use Ctrl) + const agentTabIndex = getAgentTabSelectionIndex(event, useCmdForTasks);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/hooks/useKeyboardShortcuts.ts` around lines 643 - 644, The call to getAgentTabSelectionIndex(event, useCmdForTasks) is confusing because the second argument is named useCtrlForAgents while you're passing useCmdForTasks; add a concise inline comment at this callsite in useKeyboardShortcuts.ts explaining the mapping (e.g., "pass useCmdForTasks here because when tasks use Cmd, agents use Ctrl") so readers understand why the flipped flag is provided to getAgentTabSelectionIndex; reference getAgentTabSelectionIndex and the useCmdForTasks variable in the comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/test/renderer/useKeyboardShortcuts.test.ts`:
- Around line 166-195: The test suite is missing the non-macOS case for
useCmdForTasks; update the helper and tests so Ctrl acts as the
Command-equivalent on non-Mac. In getTaskSelectionIndex, when useCmdForTasks is
true, change the modifier check to use (isMac ? event.metaKey : event.ctrlKey)
instead of always event.metaKey so Windows/Linux treat Ctrl as "Cmd"; then add
assertions in useKeyboardShortcuts.test.ts mirroring the mac case but with
isMac=false: expect a KeyboardEvent with ctrlKey true to return 0 and expect
metaKey true / ctrlKey false to return null.
---
Nitpick comments:
In `@src/renderer/components/KeyboardSettingsCard.tsx`:
- Around line 148-151: The keyboard settings map (KeyboardSettings) currently
mixes non-binding prefs like numberShortcutBehavior into entries that should be
only ShortcutBinding, forcing casts in KeyboardSettingsCard (the keyboard lookup
using keyboard?.[shortcut.settingsKey as ShortcutSettingsKey]) and leaking the
mixed shape into services such as assertNoKeyboardShortcutConflicts; split
non-binding prefs out of the binding map so the binding map contains only
ShortcutBinding | null, update the type of keyboard and ShortcutSettingsKey
consumers accordingly, remove the ad-hoc cast in KeyboardSettingsCard, and
update callers like assertNoKeyboardShortcutConflicts to read non-binding prefs
(e.g. numberShortcutBehavior) from the separate config area rather than from the
binding map.
In `@src/renderer/hooks/useKeyboardShortcuts.ts`:
- Around line 643-644: The call to getAgentTabSelectionIndex(event,
useCmdForTasks) is confusing because the second argument is named
useCtrlForAgents while you're passing useCmdForTasks; add a concise inline
comment at this callsite in useKeyboardShortcuts.ts explaining the mapping
(e.g., "pass useCmdForTasks here because when tasks use Cmd, agents use Ctrl")
so readers understand why the flipped flag is provided to
getAgentTabSelectionIndex; reference getAgentTabSelectionIndex and the
useCmdForTasks variable in the comment.
In `@src/renderer/hooks/useTaskManagement.ts`:
- Around line 460-497: handleSelectTaskByIndex duplicates the sidebar ordering +
pinned-first flattening logic that LeftSidebar.tsx uses, causing potential
drift; extract that logic into a shared helper (e.g., getSidebarOrderedTasks or
getSidebarProjectOrder + flattenTasksByPinned) and replace the local
implementation in useTaskManagement's handleSelectTaskByIndex as well as the
logic in LeftSidebar.tsx to call the common helper; the helper should read
'sidebarProjectOrder' from localStorage, return projects in the stored order
(falling back safely on errors), and provide the flattened task list that sorts
pinned tasks first within each project so both badge numbering and task
selection use identical ordering.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 18b4a176-40b8-420c-8f4a-9a0306c2ef15
📒 Files selected for processing (9)
src/main/settings.tssrc/renderer/components/AppKeyboardShortcuts.tsxsrc/renderer/components/KeyboardSettingsCard.tsxsrc/renderer/components/TaskItem.tsxsrc/renderer/components/sidebar/LeftSidebar.tsxsrc/renderer/hooks/useKeyboardShortcuts.tssrc/renderer/hooks/useTaskManagement.tssrc/renderer/types/shortcuts.tssrc/test/renderer/useKeyboardShortcuts.test.ts
| it('uses Cmd for tasks when useCmdForTasks is true', () => { | ||
| // When tasks use Cmd | ||
| expect( | ||
| getTaskSelectionIndex( | ||
| { | ||
| key: '1', | ||
| metaKey: true, | ||
| ctrlKey: false, | ||
| altKey: false, | ||
| shiftKey: false, | ||
| } as KeyboardEvent, | ||
| true, // useCmdForTasks = true | ||
| true // isMac | ||
| ) | ||
| ).toBe(0); | ||
|
|
||
| // Ctrl should NOT work when tasks are set to use Cmd | ||
| expect( | ||
| getTaskSelectionIndex( | ||
| { | ||
| key: '1', | ||
| metaKey: false, | ||
| ctrlKey: true, | ||
| altKey: false, | ||
| shiftKey: false, | ||
| } as KeyboardEvent, | ||
| true // useCmdForTasks = true | ||
| ) | ||
| ).toBeNull(); | ||
| }); |
There was a problem hiding this comment.
Add the swapped-modifier non-macOS case here.
This suite only covers useCmdForTasks = true on macOS. In src/renderer/hooks/useKeyboardShortcuts.ts, getTaskSelectionIndex(..., true, false) currently checks event.metaKey on non-Mac, while getAgentTabSelectionIndex treats Ctrl as the Command equivalent there. That leaves the "⌘N for tasks, ⌃N for agents" setting effectively unreachable on Windows/Linux. Please add the non-Mac assertion here and align the helper with the existing Ctrl-as-Command behavior.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/test/renderer/useKeyboardShortcuts.test.ts` around lines 166 - 195, The
test suite is missing the non-macOS case for useCmdForTasks; update the helper
and tests so Ctrl acts as the Command-equivalent on non-Mac. In
getTaskSelectionIndex, when useCmdForTasks is true, change the modifier check to
use (isMac ? event.metaKey : event.ctrlKey) instead of always event.metaKey so
Windows/Linux treat Ctrl as "Cmd"; then add assertions in
useKeyboardShortcuts.test.ts mirroring the mac case but with isMac=false: expect
a KeyboardEvent with ctrlKey true to return 0 and expect metaKey true / ctrlKey
false to return null.
When useCmdForTasks is true on non-Mac platforms, Ctrl should act as the Command equivalent since the Meta/Windows key isn't commonly used for keyboard shortcuts. This aligns with how getAgentTabSelectionIndex already handles the inverse case. Added test coverage for the non-Mac useCmdForTasks=true scenario. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/renderer/hooks/useKeyboardShortcuts.ts`:
- Around line 294-296: The cmd-tasks branch makes Ctrl+1-9 always go to task
selection off macOS; update the logic in useKeyboardShortcuts where
useCmdForTasks/hasTaskModifier and numberShortcutBehavior are evaluated so
agent-number shortcuts remain reachable on non-Mac. Specifically, change the
non-Mac fallback for hasTaskModifier (and the checks around lines referencing
numberShortcutBehavior === 'cmd-tasks') to use a distinct modifier (e.g., Alt or
Shift) or gate swapping to isMac before remapping, and ensure the handler order
still allows onSelectAgentTab to run when the agent modifier is pressed; adjust
the conditions that call onSelectTask vs onSelectAgentTab so they check the
corrected modifier flags and numberShortcutBehavior consistently.
- Around line 302-311: The digit-row keyboard shortcut code in
getTaskTabSelectionIndex and getAgentTabSelectionIndex is layout-dependent
because it uses event.key and rejects event.shiftKey; replace that logic to use
event.code for layout-independent matching (check event.code matches
/^Digit[1-9]$/), remove or relax the event.shiftKey rejection so AZERTY/shifted
digit keys work, and convert the matched code (e.g., "Digit1") to a zero-based
index (DigitN -> N-1) to return the correct tab index.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 19cc6be5-f9af-4f7e-a4b7-dfcbc4eaa562
📒 Files selected for processing (2)
src/renderer/hooks/useKeyboardShortcuts.tssrc/test/renderer/useKeyboardShortcuts.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/test/renderer/useKeyboardShortcuts.test.ts
| if (useCmdForTasks) { | ||
| // User wants Cmd/Meta for tasks (on non-Mac, Ctrl acts as Command equivalent) | ||
| hasTaskModifier = isMac ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey; |
There was a problem hiding this comment.
cmd-tasks makes agent-number shortcuts unreachable off macOS.
On Windows/Linux, Line 296 still maps the task path back to Ctrl, and Lines 325-327 also put agents on Ctrl when numberShortcutBehavior === 'cmd-tasks'. Because Line 629 handles task selection first, Ctrl+1-9 always returns through onSelectTask, so onSelectAgentTab can never fire in that mode. This needs a distinct non-macOS fallback, or the swap option should be gated to macOS.
Also applies to: 325-327, 628-645
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/hooks/useKeyboardShortcuts.ts` around lines 294 - 296, The
cmd-tasks branch makes Ctrl+1-9 always go to task selection off macOS; update
the logic in useKeyboardShortcuts where useCmdForTasks/hasTaskModifier and
numberShortcutBehavior are evaluated so agent-number shortcuts remain reachable
on non-Mac. Specifically, change the non-Mac fallback for hasTaskModifier (and
the checks around lines referencing numberShortcutBehavior === 'cmd-tasks') to
use a distinct modifier (e.g., Alt or Shift) or gate swapping to isMac before
remapping, and ensure the handler order still allows onSelectAgentTab to run
when the agent modifier is pressed; adjust the conditions that call onSelectTask
vs onSelectAgentTab so they check the corrected modifier flags and
numberShortcutBehavior consistently.
| if (!hasTaskModifier || event.altKey || event.shiftKey) { | ||
| return null; | ||
| } | ||
|
|
||
| const key = normalizeShortcutKey(event.key); | ||
| if (!/^[1-9]$/.test(key)) { | ||
| return null; | ||
| } | ||
|
|
||
| return Number(key) - 1; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
On the web/Electron KeyboardEvent API, is KeyboardEvent.keylayout-dependent whileKeyboardEvent.codeforDigit1-Digit9 is layout-independent? How do shortcuts behave on AZERTY keyboards where digits require Shift?
💡 Result:
Yes.
KeyboardEvent.keyis layout- (and modifier-) dependent: it reports the key value/character produced given the active layout and modifiers (e.g., Shift). (developer.mozilla.org)KeyboardEvent.codeis (mostly) layout-independent: it reports the physical key position (a stable identifier “unaffected by the current keyboard state”). (w3c.github.io)- For the top number row, codes like
"Digit1"…"Digit9"refer to those physical keys (named after the US layout positions), even though the characters they produce vary by layout (the spec explicitly notes e.g. the"Digit2"key can produce"2"on US but"é"on French). (w3.org)
What happens on AZERTY (where digits typically require Shift)?
On (traditional) French AZERTY, pressing the physical "Digit1" key without Shift yields a symbol like &, and with Shift yields 1 (digits require Shift). (en.wikipedia.org)
So shortcuts differ depending on what you match:
- If your shortcut logic is character-based (e.g.
e.key === "1"), an AZERTY user generally must press Ctrl+Shift+Digit1 to makekeybe"1". - If your shortcut logic is physical-key-based (e.g.
e.code === "Digit1"), an AZERTY user can press Ctrl+Digit1 (physically the same key as&/1), and it will match without requiring Shift, becausecodestays"Digit1"regardless of layout/modifiers. (w3c.github.io)
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key?utm_source=openai
- 2: https://w3c.github.io/uievents/split/keyboard-events.html
- 3: https://www.w3.org/TR/DOM-Level-3-Events-code/
- 4: https://en.wikipedia.org/wiki/AZERTY?utm_source=openai
- 5: https://w3c.github.io/uievents/split/keyboard-events.html
🏁 Script executed:
cat -n src/renderer/hooks/useKeyboardShortcuts.ts | sed -n '300,345p'Repository: generalaction/emdash
Length of output: 1683
🏁 Script executed:
# Search for the normalizeShortcutKey function
rg "normalizeShortcutKey" src/renderer/hooks/useKeyboardShortcuts.ts -A 5Repository: generalaction/emdash
Length of output: 1531
Use KeyboardEvent.code instead of event.key for layout-independent digit key matching.
Both getTaskTabSelectionIndex (lines 302–311) and getAgentTabSelectionIndex (lines 333–342) reject event.shiftKey while matching on event.key. On keyboard layouts like AZERTY where digits require Shift (e.g., physical Digit1 produces & without Shift, 1 with Shift), these shortcuts become inaccessible from the number row. Use event.code === "Digit1" through event.code === "Digit9" instead to match the physical key position, which is layout-independent.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/hooks/useKeyboardShortcuts.ts` around lines 302 - 311, The
digit-row keyboard shortcut code in getTaskTabSelectionIndex and
getAgentTabSelectionIndex is layout-dependent because it uses event.key and
rejects event.shiftKey; replace that logic to use event.code for
layout-independent matching (check event.code matches /^Digit[1-9]$/), remove or
relax the event.shiftKey rejection so AZERTY/shifted digit keys work, and
convert the matched code (e.g., "Digit1") to a zero-based index (DigitN -> N-1)
to return the correct tab index.
Summary
Add direct task navigation using Ctrl+1-9 shortcuts, solving #1389.
Changes
numberShortcutBehaviorsetting type and defaultsonSelectTaskhandlergetTaskSelectionIndexfunction and update handlerhandleSelectTaskByIndexfunctionTest plan
Summary by CodeRabbit