Skip to content

feat: add "By Key" shortcut reverse-lookup filter#852

Merged
pedramamini merged 5 commits intoRunMaestro:rcfrom
scriptease:shortcut-search
Apr 23, 2026
Merged

feat: add "By Key" shortcut reverse-lookup filter#852
pedramamini merged 5 commits intoRunMaestro:rcfrom
scriptease:shortcut-search

Conversation

@scriptease
Copy link
Copy Markdown

@scriptease scriptease commented Apr 17, 2026

Summary

Adds a "By Key" filter button next to the existing text filter in both the
keyboard shortcuts overview modal (⌘/) and the Settings → Shortcuts tab.
Users can press any key combination to find which action it's mapped to,
making it easy to discover conflicts or recall forgotten bindings.

  • Reverse-lookup filter: click "By Key", press a combo, and the list narrows
    to shortcuts matching those exact keys (sorted-set comparison)
  • Works alongside the existing text filter — entering text clears the key
    filter and vice versa
  • Polish: height-aligned the filter input, button, and count badge; pulled
    the scrollbar flush to the modal's right edge; widened row spacing so key
    badges don't crowd labels

Screenshots

Shortcuts overview modal — By Key button inactive:

Bildschirmfoto 2026-04-17 um 08 33 52

Settings → Shortcuts — By Key filter active (recorded combo shown):

Bildschirmfoto 2026-04-17 um 08 04 13

Test plan

  • Open shortcuts help (⌘/), click "By Key", press ⌘K → list narrows to Clear Terminal
  • Type in search box → key filter clears, text filter takes over
  • Same flow in Settings → Shortcuts tab
  • Press Esc while recording → cancels without filtering
  • Verify scrollbar sits flush with modal's right edge, no overlap with content
  • Confirm "By Key" recording does NOT count toward keyboard mastery stats

Summary by CodeRabbit

  • New Features

    • "By Key" filter to find shortcuts by pressing key combinations (toggle in Shortcuts tab and Help modal); captures and displays "Press keys…" or the formatted keys and updates results count when active.
    • Improved, more consistent key capture/normalization when recording shortcuts.
  • Bug Fixes

    • Modal prevents accidental close while capturing; Escape or losing focus cancels capture.
  • Chores

    • Removed the "New Terminal Tab" shortcut.

noreply and others added 2 commits April 17, 2026 08:17
Adds a key-capture button next to the filter input in both the Shortcuts
settings tab and the keyboard shortcuts help modal. Users can press a key
combination to find which action it maps to, rather than searching by name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…badges

Pulls the scrollable list's right edge out by the modal's p-6 padding so
the scrollbar sits flush with the modal's right border. Adds gap-4 between
each row's label and key badge for better visual separation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8d8b2932-9012-4a74-b5fd-d810f882bc82

📥 Commits

Reviewing files that changed from the base of the PR and between 10c796b and a8eb8fd.

📒 Files selected for processing (2)
  • src/renderer/hooks/keyboard/useKeyboardShortcutHelpers.ts
  • src/renderer/utils/shortcutRecorder.ts
✅ Files skipped from review due to trivial changes (1)
  • src/renderer/hooks/keyboard/useKeyboardShortcutHelpers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/utils/shortcutRecorder.ts

📝 Walkthrough

Walkthrough

Shortcuts UI gains a “By Key” filter mode that captures key sequences, centralized key normalization via new buildKeysFromEvent, and removal of the exported TERMINAL_SHORTCUTS constant. Modal and tab UIs now support recording/cancelling key-capture and exact-key matching for filtering.

Changes

Cohort / File(s) Summary
ShortcutsTab component
src/renderer/components/Settings/tabs/ShortcutsTab.tsx
Added key-capture state and ref (recordingFilterShortcut, filterShortcutKeys, filterShortcutButtonRef), handleFilterRecord, updated parent onRecordingChange to reflect filter capture, switched per-shortcut recording to buildKeysFromEvent, added UI toggle/button for key capture, and changed filtering to exact normalized-key equality when key-filter active.
ShortcutsHelpModal component
src/renderer/components/ShortcutsHelpModal.tsx
Applied the same key-capture/filtering changes as ShortcutsTab: recording/filter state, handleFilterRecord, handleBeforeClose to cancel recording on close, combined search + “By Key” toggle, onBlur cancel, exact-key matching, and updated header badge/count and layout.
Shortcut constants
src/renderer/constants/shortcuts.ts
Removed exported TERMINAL_SHORTCUTS (deleted newTerminalTab shortcut). No replacement added.
Shortcut recorder util
src/renderer/utils/shortcutRecorder.ts
Added exported `buildKeysFromEvent(e: React.KeyboardEvent): string[]
Keyboard helper docs
src/renderer/hooks/keyboard/useKeyboardShortcutHelpers.ts
Updated comments/rationale about Alt-key/code handling and noted symmetry requirement with buildKeysFromEvent (no API changes).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

ready to merge

Poem

🐰 I twitch my whiskers, tap a key,

I catch the chords you press for me.
By text or press, the list grows small,
A hop, a blink—I filter all.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: adding a 'By Key' reverse-lookup filter for keyboard shortcuts across the UI components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 17, 2026

Greptile Summary

This PR adds a "By Key" reverse-lookup filter to both the shortcuts help modal (⌘/) and the Settings → Shortcuts tab, letting users press a key combo to find which action it's bound to. The sorted-set comparison logic and mutual exclusion with the text filter are implemented correctly.

  • P1 (both files): The text input's onChange handler resets filterShortcutKeys but never sets recordingFilterShortcut to false. If a user clicks "By Key" and then clicks back to the text input to type, the button is visually stuck in "Press keys…" mode indefinitely — there is no obvious way to dismiss it.

Confidence Score: 4/5

Safe to merge after fixing the stuck recording-state bug in both files' text input onChange handlers.

One P1 defect is present in both changed files: the "By Key" button can get visually stuck in recording mode when the user clicks away to the text input. The one-line fix is straightforward (setRecordingFilterShortcut(false) in each onChange), but it should be applied before merging to avoid a confusing UX regression. No security issues or data-loss risk.

Both files need the one-line fix in their text input onChange handlers.

Important Files Changed

Filename Overview
src/renderer/components/Settings/tabs/ShortcutsTab.tsx Adds "By Key" reverse-lookup filter with recording state, filter button, and sorted-set key comparison; has a P1 bug where recordingFilterShortcut is not reset when the text input regains focus, plus duplicated key-building logic shared with ShortcutsHelpModal.
src/renderer/components/ShortcutsHelpModal.tsx Adds the same "By Key" filter button to the help modal; carries the identical stuck-recording-state bug in the search input's onChange, and duplicates handleFilterRecord verbatim from ShortcutsTab.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([User opens Shortcuts UI]) --> B{Click 'By Key' button}
    B --> C[recordingFilterShortcut = true\nButton focused]
    C --> D{User action?}
    D -->|Press modifier+key| E[handleFilterRecord captures keys\nfilterShortcutKeys set\nrecordingFilterShortcut = false]
    D -->|Press Escape| F[filterShortcutKeys cleared\nrecordingFilterShortcut = false]
    D -->|Click text input| G[Button loses focus\nrecordingFilterShortcut stays TRUE ⚠️]
    G --> H[User types in text input\nonChange: filterShortcutKeys cleared\nbut recordingFilterShortcut still TRUE]
    H --> I[Button stuck showing 'Press keys...'\nno key capture happening]
    E --> J[List filters to matching shortcuts]
    F --> K[Recording cancelled, list unfiltered]
    I --> L{User clicks 'By Key' again}
    L --> C
Loading

Reviews (1): Last reviewed commit: "fix(shortcuts-help): align scrollbar to ..." | Re-trigger Greptile

Comment thread src/renderer/components/Settings/tabs/ShortcutsTab.tsx
Comment thread src/renderer/components/ShortcutsHelpModal.tsx
Comment thread src/renderer/components/Settings/tabs/ShortcutsTab.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/renderer/components/Settings/tabs/ShortcutsTab.tsx (2)

93-124: Extract the key-normalization logic into a shared helper.

Lines 103-120 duplicate the modifier-capture + Alt/letter+digit normalization block from handleRecord (lines 56-77), and the entire handleFilterRecord is duplicated verbatim in ShortcutsHelpModal.tsx (lines 60-91). That's three copies of the same normalization. A future fix (e.g. adding a key that needs special handling) would have to be applied in all three places. Consider lifting a pure helper such as captureKeyFromEvent(e): string[] | null into src/renderer/utils/shortcutFormatter.ts (or a new shortcutCapture.ts) and having both handlers call it.

♻️ Sketch of shared helper
// src/renderer/utils/shortcutCapture.ts
export function captureKeyFromEvent(e: React.KeyboardEvent): string[] | null {
	if (['Meta', 'Control', 'Alt', 'Shift'].includes(e.key)) return null;
	const keys: string[] = [];
	if (e.metaKey) keys.push('Meta');
	if (e.ctrlKey) keys.push('Ctrl');
	if (e.altKey) keys.push('Alt');
	if (e.shiftKey) keys.push('Shift');
	let mainKey = e.key;
	if (e.altKey && e.code) {
		if (e.code.startsWith('Key')) mainKey = e.code.replace('Key', '').toLowerCase();
		else if (e.code.startsWith('Digit')) mainKey = e.code.replace('Digit', '');
	}
	keys.push(mainKey);
	return keys;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/Settings/tabs/ShortcutsTab.tsx` around lines 93 -
124, handleFilterRecord duplicates modifier-capture and Alt/letter+digit
normalization logic found in handleRecord and ShortcutsHelpModal; extract that
pure logic into a shared helper (e.g. captureKeyFromEvent) and call it from
handleFilterRecord, handleRecord and the ShortcutsHelpModal handler. Implement
captureKeyFromEvent(e: React.KeyboardEvent): string[] | null to return null for
lone modifier keys, build the modifiers array from
e.metaKey/e.ctrlKey/e.altKey/e.shiftKey, normalize mainKey using e.code for
Alt+Key/Digit cases (matching the sketch in the review), and replace the inline
blocks in handleFilterRecord, handleRecord and the modal with a single call that
sets state based on the helper return value.

208-253: Exit recording on blur to avoid a stuck "Press keys…" state.

Because key capture lives on onKeyDownCapture, it only fires while the button is focused. If the user enters recording mode and then clicks anywhere else in the settings modal, the button stays visually in "Press keys…" (accent ring) state with no way to cancel short of clicking it again. Consider adding an onBlur that exits recording mode.

♻️ Proposed change
 				onKeyDownCapture={(e) => {
 					if (recordingFilterShortcut) {
 						handleFilterRecord(e);
 					}
 				}}
+				onBlur={() => {
+					if (recordingFilterShortcut) setRecordingFilterShortcut(false);
+				}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/components/Settings/tabs/ShortcutsTab.tsx` around lines 208 -
253, The button can remain stuck in recording state because key capture uses
onKeyDownCapture and only fires while focused; add an onBlur handler on the same
button to call setRecordingFilterShortcut(false) (and optionally clear
filterShortcutKeys if desired) so recordingFilterShortcut is cleared when focus
leaves; update the element that uses filterShortcutButtonRef,
recordingFilterShortcut, setRecordingFilterShortcut and handleFilterRecord to
ensure onBlur exits recording mode and removes the visual ring.
🤖 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/components/Settings/tabs/ShortcutsTab.tsx`:
- Around line 131-138: filteredShortcuts fails to match shortcuts because
captured modifier names (in handleFilterRecord) use "Ctrl" while
TERMINAL_SHORTCUTS uses "Control"; update handleFilterRecord to normalize
captured keys to the canonical names used in definitions (or vice‑versa) before
building filterShortcutKeys so the equality check in filteredShortcuts (which
sorts and joins keys) can succeed—ensure the normalization mapping (e.g., "Ctrl"
<-> "Control", "Alt"/"Option", etc.) is applied consistently to keys produced by
handleFilterRecord and any stored shortcut keys referenced by
TERMINAL_SHORTCUTS.

In `@src/renderer/components/ShortcutsHelpModal.tsx`:
- Around line 159-163: The modal is closing on Escape before the button's
onKeyDownCapture/handleFilterRecord can run because the window-level capture
stops propagation; to fix, add an onBeforeClose handler in the Modal's
layerOptions that prevents closure when recordingFilterShortcut is true (i.e.,
return false or call e.preventDefault) so the layer stack won't close the modal
while the ShortcutsHelpModal is recording; locate the Modal usage in
ShortcutsHelpModal.tsx and update its layerOptions to include onBeforeClose that
checks the recordingFilterShortcut flag and blocks close when active.

---

Nitpick comments:
In `@src/renderer/components/Settings/tabs/ShortcutsTab.tsx`:
- Around line 93-124: handleFilterRecord duplicates modifier-capture and
Alt/letter+digit normalization logic found in handleRecord and
ShortcutsHelpModal; extract that pure logic into a shared helper (e.g.
captureKeyFromEvent) and call it from handleFilterRecord, handleRecord and the
ShortcutsHelpModal handler. Implement captureKeyFromEvent(e:
React.KeyboardEvent): string[] | null to return null for lone modifier keys,
build the modifiers array from e.metaKey/e.ctrlKey/e.altKey/e.shiftKey,
normalize mainKey using e.code for Alt+Key/Digit cases (matching the sketch in
the review), and replace the inline blocks in handleFilterRecord, handleRecord
and the modal with a single call that sets state based on the helper return
value.
- Around line 208-253: The button can remain stuck in recording state because
key capture uses onKeyDownCapture and only fires while focused; add an onBlur
handler on the same button to call setRecordingFilterShortcut(false) (and
optionally clear filterShortcutKeys if desired) so recordingFilterShortcut is
cleared when focus leaves; update the element that uses filterShortcutButtonRef,
recordingFilterShortcut, setRecordingFilterShortcut and handleFilterRecord to
ensure onBlur exits recording mode and removes the visual ring.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b52b2aaf-99ef-405d-8dcd-685c4ea82b2f

📥 Commits

Reviewing files that changed from the base of the PR and between fb74e52 and 3fa1ff1.

📒 Files selected for processing (2)
  • src/renderer/components/Settings/tabs/ShortcutsTab.tsx
  • src/renderer/components/ShortcutsHelpModal.tsx

Comment thread src/renderer/components/Settings/tabs/ShortcutsTab.tsx
Comment thread src/renderer/components/ShortcutsHelpModal.tsx
noreply and others added 2 commits April 17, 2026 08:52
…ing edge cases

- Extract buildKeysFromEvent() into src/renderer/utils/shortcutRecorder.ts
  and reuse across handleRecord/handleFilterRecord in both ShortcutsTab and
  ShortcutsHelpModal (previously three near-identical copies).
- Cancel "By Key" recording on button blur so it doesn't get stuck when focus
  moves to the search input.
- In ShortcutsHelpModal, add onBeforeClose guard so Escape while recording
  cancels the recording instead of closing the modal (the window-level layer
  stack capture-phase listener was preempting the button's onKeyDownCapture.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
EOF
)
Commit f0cf7c7 already moved `clearTerminal` out of the "never-rendered
TERMINAL_SHORTCUTS group" into DEFAULT_SHORTCUTS but left `newTerminalTab`
behind. The remaining export is unused — its one entry isn't merged into
`allShortcuts` by the help modal or Settings, so it was never displayed
or rebindable. The actual Ctrl+Shift+` handler in useMainKeyboardHandler
uses a hardcoded e.code check and doesn't reference this definition.

Also eliminates the only 'Control' modifier literal (vs 'Ctrl' used
everywhere else), removing a latent filter-mismatch trap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/utils/shortcutRecorder.ts`:
- Around line 7-26: The alt-key e.code fallback currently in buildKeysFromEvent
runs on all platforms but the comment says it's macOS-only; change the
conditional so the e.code remapping only runs on macOS (e.g., guard with an
isMac() helper or check navigator.platform) so AltGr behavior on Windows/Linux
isn't overridden, and update the JSDoc if you intentionally want cross-platform
behavior; specifically modify the block that checks "if (e.altKey && e.code) {
... }" to "if (isMac() && e.altKey && e.code) { ... }" (or conversely revise the
comment to reflect cross-platform semantics).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c6454dfa-95eb-4c20-8192-9806675cb62f

📥 Commits

Reviewing files that changed from the base of the PR and between 3fa1ff1 and 10c796b.

📒 Files selected for processing (4)
  • src/renderer/components/Settings/tabs/ShortcutsTab.tsx
  • src/renderer/components/ShortcutsHelpModal.tsx
  • src/renderer/constants/shortcuts.ts
  • src/renderer/utils/shortcutRecorder.ts
💤 Files with no reviewable changes (1)
  • src/renderer/constants/shortcuts.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/renderer/components/Settings/tabs/ShortcutsTab.tsx
  • src/renderer/components/ShortcutsHelpModal.tsx

Comment thread src/renderer/utils/shortcutRecorder.ts Outdated
…acOS-only

The fallback runs unconditionally on every platform — it recovers the
physical key whenever Alt rewrites e.key, which happens on macOS Alt-chars
(π, ¬, †) and Windows/Linux AltGr layouts alike. Recording and matching
must stay symmetric, so comments now describe the actual behavior and call
out the symmetry constraint between buildKeysFromEvent and isShortcut.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@pedramamini
Copy link
Copy Markdown
Collaborator

@scriptease thanks for the contribution — nice UX addition! I walked through the recent review round and everything looks buttoned up:

  • Stuck recordingFilterShortcut state is now resolved via the button's onBlur reset, which also covers the case where the user clicks back into the text input.
  • Duplication between the modal and settings tab is collapsed into the shared buildKeysFromEvent helper in src/renderer/utils/shortcutRecorder.ts.
  • Modal Escape during recording is properly handled via layerOptions.onBeforeClose — good use of the layer stack pattern.
  • TERMINAL_SHORTCUTS Control/Ctrl concern confirmed inert (dead code).
  • JSDoc on buildKeysFromEvent accurately documents the intentional cross-platform e.code fallback.

Mergeable is CLEAN, CI is green. Approving — thanks again!

pedramamini added a commit that referenced this pull request Apr 23, 2026
- scriptease's PR adds a "By Key" filter button in the Shortcuts Help
  modal and the Settings → Shortcuts tab, letting users press a combo
  to find which action it maps to. Also extracts a shared
  buildKeysFromEvent helper, retires the dead TERMINAL_SHORTCUTS
  export, and polishes scrollbar/spacing in the help modal.
- Follow-up commit extracts the duplicated "By Key" button UI into a
  shared ui/ShortcutFilterButton component, removes an orphan
  trackShortcut('newTerminalTab') call in the main keyboard handler,
  and adds unit coverage for buildKeysFromEvent.
@pedramamini pedramamini merged commit a8eb8fd into RunMaestro:rc Apr 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants