Skip to content

feat(shortcuts): add configurable number-key navigation#2838

Open
luisKisters wants to merge 9 commits into
generalaction:mainfrom
luisKisters:feat/number-key-switching
Open

feat(shortcuts): add configurable number-key navigation#2838
luisKisters wants to merge 9 commits into
generalaction:mainfrom
luisKisters:feat/number-key-switching

Conversation

@luisKisters

@luisKisters luisKisters commented Jul 12, 2026

Copy link
Copy Markdown

Description

Adds configurable number-key navigation for tasks and pane tabs.

  • Uses Command+1-9 for pane and conversation tabs on macOS, preserving the familiar tab
    navigation behavior.
  • Uses Control+1-9 for sidebar tasks on macOS. Windows and Linux use Mod+1-9 for tabs and
    Alt+1-9 for tasks to avoid conflicts.
  • Makes every task and tab position individually rebindable or disableable in Settings.
  • Forwards configured bindings through terminals and embedded browser webviews, including
    arbitrary non-digit rebindings.
  • Shows delayed keycap hints while the family modifier is held, then removes the hint overlay as
    soon as a shortcut is used so the gradient does not linger during navigation.
  • Keeps task numbering aligned with the rendered sidebar order.
  • Debounces each action independently and adds focused regression coverage.

Related issues

No issue linked.

Testing

  • pnpm --filter @emdash/emdash-desktop exec vitest run --project node src/shared/shortcuts.test.ts src/main/core/browser/browser-webcontents-registry.test.ts src/renderer/lib/hooks/use-number-hotkeys.test.ts (25 tests passed)
  • focused oxlint across all changed TypeScript files
  • focused oxfmt --check across all changed TypeScript files
  • pnpm --filter @emdash/emdash-desktop exec tsgo --noEmit
  • git diff --check origin/main...HEAD
  • manual verification in an isolated development database

Screenshot/Recording

Task & Conversation switching

Screen.Recording.2026-07-12.at.16.36.58.mov

Configurable task and tab bindings

image
Checklist
  • I kept this PR small and focused
  • I ran a self-review before opening this PR
  • I ran the relevant local checks or explained why not
  • I updated documentation and PR evidence where behavior changed
  • I added or updated tests when behavior changed
  • I only added comments where the logic is not obvious
  • I used Conventional Commits for commit messages and the PR title

luisKisters and others added 6 commits July 12, 2026 02:36
Cmd+1-9 jumps to the Nth task in sidebar order (pinned first, then the
project tree top to bottom). Control+1-9 jumps to the Nth tab in the
focused pane, left to right, replacing the previously hardcoded Mod+1-9
tab bindings. Both families are debounced (200ms leading edge),
configurable and disableable in Settings via new tabByNumber and
taskByNumber shortcut entries, and suppressed in Monaco when flagged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… add hold hints

Digit shortcuts died once focus moved into a browser webview or an xterm
terminal (the 'Ctrl+1-2 work, Ctrl+3+ dead' symptom): webviews swallow
keys before the renderer sees them and xterm maps Ctrl+2-8 to control
characters. Forward matching digit presses from browser webContents via
a new numberShortcutChannel (mirroring the Control+Tab path) and route
terminal digit chords through dispatchMatchingHotkeys like tab
navigation. The debounce claim is shared module-wide and measured on
event timestamps so key-repeat is swallowed even under render load.

Holding the shortcut modifier (default Cmd for tasks, Ctrl for tabs) now
reveals 1-9 hint badges on sidebar tasks and pane tabs for discoverability.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…inds

Review fixes: the debounce now keys on family+digit so distinct rapid
presses all fire while key-repeat is still swallowed; hint badges render
the full keycap chord (e.g. ⌘1) via the shared Shortcut component and
stay visible across consecutive jumps within one modifier hold; invalid
number-family rebinds fall back to the default binding instead of
silently disabling the family (shared resolveNumberFamilyBase used by
renderer, Monaco bridge, and browser forwarding); sidebar order for
jumps and badges comes from a single numberedTaskEntries source;
webview forwarding emits every matching family; taskByNumber allows
conflicts like its tab sibling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hint badges float on the right edge of task rows and tab chips instead
of reflowing content: an absolutely positioned overlay whose leftward
gradient matches the row background per state (default/hover/active),
so underlying markers fade out beneath the keycap while the modifier is
held.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the two base-plus-derived-digits family entries with eighteen
standalone APP_SHORTCUTS entries (task1-9, tab1-9), so Settings lists
'Jump to 1st Task' through 'Jump to 9th Tab' and each digit can be
rebound or disabled on its own — including to non-digit keys. The
webview forwarding matches each configured binding exactly, hint badges
show each entry's own effective hotkey, the reveal modifier comes from
the first enabled binding of a family, and the Monaco bridge works
through the plain per-key ignore path again. Drops getNumberHotkeys /
resolveNumberFamilyBase / numberFamily.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@luisKisters luisKisters marked this pull request as ready for review July 12, 2026 14:02
@greptile-apps

greptile-apps Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds configurable number-key navigation for tasks and tabs. The main changes are:

  • New per-position shortcut settings for tab and task jumps.
  • Browser webview and terminal forwarding for number shortcuts.
  • Sidebar and tab-bar hint overlays while the family modifier is held.
  • Shared debounce handling and focused tests for shortcut behavior.

Confidence Score: 5/5

The changed flow looks mergeable after small cleanup to hint state and terminal conflict behavior.

  • Core navigation targets use the same task and tab ordering as the displayed hints.
  • Browser forwarding keeps the source browser id and routes tab shortcuts to the owning pane.
  • Terminal dispatch can pick the wrong action when two number shortcuts share one key.
  • Hint visibility can stay true after changing the reveal modifier.

apps/emdash-desktop/src/renderer/lib/hooks/use-number-hotkeys.ts; apps/emdash-desktop/src/renderer/lib/hooks/use-modifier-held.ts

Important Files Changed

Filename Overview
apps/emdash-desktop/src/main/core/browser/browser-webcontents-registry.ts Adds browser webContents forwarding for number shortcuts and modifier state.
apps/emdash-desktop/src/renderer/lib/hooks/use-number-hotkeys.ts Adds shared number shortcut binding, debounce, and terminal dispatch helpers.
apps/emdash-desktop/src/renderer/lib/hooks/use-modifier-held.ts Adds delayed modifier-held tracking for shortcut hint overlays.
apps/emdash-desktop/src/renderer/features/sidebar/sidebar-store.ts Adds a single numbered task order for shortcuts and hint badges.
apps/emdash-desktop/src/shared/shortcuts.ts Adds task and tab number shortcut definitions and ordered key arrays.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/emdash-desktop/src/renderer/lib/hooks/use-number-hotkeys.ts:45
**Terminal Dispatch Uses Global Order**

When a task number shortcut and a tab number shortcut are rebound to the same key, terminal input goes through this `dispatch: 'first'` path instead of the normal focused document handler. The first enabled global registration wins by manager insertion order, so a focused terminal can navigate to a sidebar task instead of switching the focused pane tab, or the reverse.

### Issue 2 of 2
apps/emdash-desktop/src/renderer/lib/hooks/use-modifier-held.ts:97-104
**Held State Survives Rebinding**

When the configured reveal modifier changes while hints are visible, the old effect cleanup removes listeners and clears the timer but leaves `held` as `true`. The next shortcut family can show its hint overlay immediately even though the new modifier was never held.

```suggestion
    return () => {
      window.removeEventListener('keydown', onKeyDown, true);
      window.removeEventListener('keyup', onKeyUp, true);
      window.removeEventListener('blur', cancel);
      stopForwardedModifier();
      stopForwardedShortcut();
      if (timer !== null) window.clearTimeout(timer);
      setHeld(false);
    };
```

Reviews (1): Last reviewed commit: "fix(shortcuts): refine number navigation..." | Re-trigger Greptile

Comment thread apps/emdash-desktop/src/renderer/lib/hooks/use-number-hotkeys.ts
Comment thread apps/emdash-desktop/src/renderer/lib/hooks/use-modifier-held.ts
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.

1 participant