Configurable keybindings and consistent terminal link modifiers - #554
Configurable keybindings and consistent terminal link modifiers#554tbrownio wants to merge 7 commits into
Conversation
…ctive-binding dispatch Phase 1 of configurable keybindings: one typed catalog of every registry-backed command, keyboardShortcutOverrides in the global config (sparse: override or null), a chord->candidates hotkey index that runs neither command on an ambiguous chord, terminal release and webview forwarding driven by the configured catalog, a config:updated relay for live propagation, and agent launch presets referencing catalog ids instead of literal hotkeys. Claude-Session: https://claude.ai/code/session_016CuGxyYX4yEZ1dfUZZDm36
…eset
Phase 2 of configurable keybindings. Settings → Shortcuts gains a searchable,
category-grouped key-binding table sourced from the shared catalog plus the
current snippet and custom-command bindings: effective and default chords,
activation scope, customized/unassigned/invalid state, and per-environment
availability. An accessible recorder (window-capture while armed, Escape
cancels without closing Settings, Backspace unassigns, terminal-reserved chords
refused, recording a row's default removes the override) writes a sparse draft;
conflicts are validated globally (no platform gate) and block Apply naming both
owners; per-row Reset deletes the override and Reset all sends {}.
Help renders the same map. The raw override map round-trips untouched
(unknown ids and malformed values are preserved on disk and shown as
"invalid — using default"). ConfigManager validates conflicts globally.
Adds Playwright coverage for the settings map, agent-launch remaps in worktree
and main-repo views (mock now records panel creates/updates/activations and
broadcasts panel:created), plus unit coverage for the map builder and glyphs.
Claude-Session: https://claude.ai/code/session_012BQcLGZB4EmWoxpTTCWrC9
… router Phase 3 of configurable keybindings. Auto-detected URLs, OSC-8 hyperlinks, and git SHA/issue links share one pure classifier and router: Primary+Shift opens a validated, credential-free HTTP(S) URL in the session's Browser panel (reusing the first one or creating one through a single create-or-navigate helper) and falls back to the external browser exactly once where no Browser surface exists (Project/main-repo terminals, Pane Chat, no session context); Primary opens externally; the macOS Control-click alias applies only to unshifted primary button activations; Alt and non-primary buttons never qualify; rejected URLs on the Browser branch open nothing. Hover text names the available gestures per provider and platform. The browser-panel:navigate event path is removed; BrowserPanel navigates from panel state alone, with a monotonic navigationNonce so a repeated same-URL request reloads. The selection popover shows "Open in Browser" only where a Browser surface exists; HTML previews reuse the same helper. Also fixes a pre-existing off-by-one in the file and git link providers: xterm passes a 1-based buffer line to provideLinks, so they read the row below and emitted ranges one row off, which meant their links never matched the pointer. Claude-Session: https://claude.ai/code/session_012BQcLGZB4EmWoxpTTCWrC9
- Every gesture-driven link sink (Pane Browser, external, and the unavailable-surface fallback) now takes only a validated, credential-free HTTP(S) URL; rejected targets open nothing. OSC-8 plain click keeps its pre-existing pass-through. - Alt and non-primary-button activations are classified as rejected so OSC-8 cannot treat them as plain clicks. - Shortcuts settings: the key-binding Apply and the snippet Apply are each blocked while the other draft is unsaved, since conflicts are validated across both drafts but persisted separately. - HTML previews retitle a reused Browser panel again. - Drop three unused type exports flagged by Knip. Claude-Session: https://claude.ai/code/session_012BQcLGZB4EmWoxpTTCWrC9
Recording an unmodified named key (Tab, Enter, Space, arrows, paging…) would hijack focus navigation and typing app-wide, and mod+c / mod+shift+c is the terminal's copy shortcut; both are now refused with live status text. Claude-Session: https://claude.ai/code/session_012BQcLGZB4EmWoxpTTCWrC9
|
React Doctor found 2 new issues in 2 files · 2 warnings · score 69 / 100 (Needs work) · 1 fixed · vs 2 warnings
Reviewed by React Doctor for commit |
| fullWidth | ||
| /> | ||
| <div className="max-h-[28rem] overflow-y-auto rounded-md border border-border-secondary"> | ||
| <div role="table" aria-label="Key bindings" className="min-w-full text-sm"> |
There was a problem hiding this comment.
React Doctor · react-doctor/prefer-tag-over-role (warning)
Screen reader users get more reliable semantics from <table> than role="table", so use <table> instead.
Fix → Use the matching HTML element when one exists so browsers and assistive tech get native semantics.
| }), [overridesDraft, shortcuts, customCommands, projectEnvironment, platform]); | ||
| const conflicted = shortcutMap.conflicts.length > 0; | ||
| const snippetConflicts = new Set( | ||
| shortcutMap.rows.filter((row) => row.origin === 'snippet' && row.conflicts.length > 0).map((row) => row.id), |
There was a problem hiding this comment.
React Doctor · react-doctor/js-combine-iterations (warning)
This loops over your list twice because .filter().map() makes two passes, so do it in one pass with .reduce() or a for...of loop
Fix → Combine .map().filter() style chains into one pass with .reduce() or a for...of loop, so you only loop over the list once
Description
One global, persistent keybinding model for Pane plus consistent modified-click routing for terminal links. Implements
briefs/configurable-keybindings-and-external-link-modifiers.mdin these commits:Shared shortcut catalog + effective-binding dispatch (
a9fe0f2c). Every registry-backed command lives inshared/constants/keyboardShortcuts.ts(id, label, category, activation scope, default chord, platform gate, terminal/TUI/webview interception flags).keyboardShortcutOverridesin the global config is sparse (override ornull); the hotkey store resolvesoverride | unassigned | default, indexeschord → candidates, and runs neither command on an ambiguous chord. TerminalPanel's hard-coded release list and main's webview forward whitelist are replaced by sets built from the effective catalog (with a parity harness against the deleted lists). Agent launch presets reference catalog ids instead of literal hotkeys.Shortcuts settings map (
92865cf6). Settings → Shortcuts shows a searchable, category-grouped table sourced from the catalog plus current snippets/custom commands: effective + default chord, scope, customized/unassigned/invalid state, per-environment availability. Accessible key recorder (window-capture while armed; Escape cancels without closing Settings; Backspace unassigns; terminal-reserved chords refused; recording a row's default removes the override). Conflicts are validated globally (no platform gate — a Windows host can open a WSL project where Cursor is active) and block Apply naming both owners; Reset deletes the override, Reset all sends{}. Help renders the same map. Raw override maps round-trip untouched (unknown ids / malformed values are preserved on disk and shown as "invalid — using default").Terminal HTTP(S) link router (
5ce8ff4b). Auto-detected URLs, OSC-8 hyperlinks, and git SHA/issue links share one classifier + router: Primary+Shift (⇧⌘ / Ctrl+Shift) opens a validated, credential-free HTTP(S) URL in the session's Browser panel via a single create-or-navigate helper, falling back to the external browser exactly once where no Browser surface exists (Project/main-repo terminals, Pane Chat); Primary opens externally; the macOS Control-click alias applies only to unshifted primary-button activations; Alt and non-primary buttons never qualify; rejected URLs open nothing. Hover text names the available gestures per provider/platform. Thebrowser-panel:navigatedouble path is removed — BrowserPanel navigates from state alone, with a monotonicnavigationNonceso a repeated same-URL request reloads.Review hardening (
322f733c). Every gesture-driven link sink (internal, external, and the unavailable-surface fallback) takes only a validated HTTP(S) URL — rejected targets open nothing; OSC-8 plain click keeps its pre-existing pass-through for compatibility. Alt/non-primary activations are rejected outright. The two Shortcuts Apply buttons block each other while the other draft is unsaved (conflicts are validated across both drafts but persisted separately). HTML previews retitle a reused Browser panel. Follow-ups from pass 2: bare navigation keys andmod+crefused by the recorder; reference rows searchable.Pre-existing defect fixed on the way
fileLinkProviderandgitLinkProviderindexed the buffer 0-based, but xterm passes a 1-based line toprovideLinks(the bundled WebLinks addon doesy - 1). They read the row below and emitted ranges one row off, so file-path and git links never matched the pointer. Both now uselineNumber - 1with ranges on the requested row (unit test pins it).Plan deltas (vs
tmp/configurable-keybindings/plan-2.md/plan-3.md)frontend/src/components/terminal/linkRouting.ts) and the navigation primitive isfrontend/src/services/browserPanelNavigation.ts(names differ from the plan; behavior matches).canHostSessionBrowser) is unit-tested for Pane Chat, main-repo, and null sessions; the main-repo Playwright case exercises the same fallback path.addPanel()ed locally:panelManagerbroadcastspanel:createdand SessionView inserts the panel into the split layout only when it is not already in the store (the mock now broadcasts the same event).Type of Change
Checklist
docs/ADDING_NEW_CLI_TOOLS.md)pnpm typecheckandpnpm lintlocallypnpm electron-dev— manual desktop QA items below remain for the ownerCritical Areas Modified
config:updatedrelay for live shortcut propagation;panels:updatestate writes for Browser navigationTesting
pnpm --filter frontend test(354),pnpm --filter main testkeyboard/config subset (othermainsuites fail locally on a pre-existingbetter-sqlite3NODE_MODULE_VERSION mismatch, unrelated).PLAYWRIGHT_PORT=<free> pnpm test): 173 passed. New specs:tests/shortcuts-settings.spec.ts,tests/launch-shortcuts.spec.ts(all three agents × worktree/main-repo views),tests/terminal-links.spec.ts(primary/primary+shift/plain per provider, Browser reuse vs create, main-repo fallback, hover copy on macOS and Linux), plus two cases intests/settings.spec.ts. Known failures unrelated to this branch:settings.spec.ts"rebaselines a completed host setup" (fails onorigin/main,remoteRuntimePresentation.tscrash) andagent-usage.spec.ts:174(fails identically on the Phase 1 baseline);theme-screenshots.spec.ts"amber-crt: high-contrast" flaked once under the full parallel run and passes 2/2 alone. Note: a stale dev server on the default port 4521 (shared across worktrees withreuseExistingServer) serves another checkout's code — run with a dedicatedPLAYWRIGHT_PORT.322f733c,HEAD).Manual QA still owned by a human (desktop)
AltGr+Qstill types@in a terminal;Ctrl+\still delivers SIGQUIT.https://claude.ai/code/session_012BQcLGZB4EmWoxpTTCWrC9