Skip to content

feat(settings): improve shortcut editor ux#267

Merged
walterlow merged 4 commits into
stagingfrom
feat/shortcut-editor-ux
Jun 1, 2026
Merged

feat(settings): improve shortcut editor ux#267
walterlow merged 4 commits into
stagingfrom
feat/shortcut-editor-ux

Conversation

@walterlow
Copy link
Copy Markdown
Owner

@walterlow walterlow commented Jun 1, 2026

Summary

  • add shortcut search with explicit unassigned display
  • add reset-all confirmation for destructive shortcut reset flow
  • add per-conflict overwrite actions, direct unbind polish, search result counts, and explicit-unassigned import/export coverage

Verification

  • npm run test:run -- src/features/settings/components/hotkey-editor-reset-dialog.test.tsx src/features/settings/components/hotkey-editor-search.test.ts src/features/settings/components/hotkey-editor.test.ts src/features/settings/stores/settings-store.test.ts src/config/hotkeys.test.ts
  • npm run lint
  • npx vp fmt --check src/features/settings/components/hotkey-editor.tsx src/features/settings/components/hotkey-editor-reset-dialog.test.tsx src/config/hotkeys.test.ts src/i18n/locales/partials/projects.json
  • npm run build

Summary by CodeRabbit

  • New Features

    • Added searchable hotkey command sidebar with filtering and result count display
    • Added confirmation dialog before resetting all hotkeys to defaults
  • Improvements

    • Enhanced hotkey binding display labels to clearly indicate unassigned states
    • Improved hotkey conflict resolution UI with per-conflict actions

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
freecut Ready Ready Preview, Comment Jun 1, 2026 8:34am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds searchable sidebar for hotkey commands, reset-all confirmation dialog, and refactored capture lifecycle to preserve partial override state during conflict resolution. Updates binding display logic and UI refinements throughout the editor, plus comprehensive test coverage and i18n strings.

Changes

Hotkey Editor Search and Reset Confirmation

Layer / File(s) Summary
Search contract and utilities
src/features/settings/components/hotkey-editor-sections.ts, src/features/settings/components/hotkey-editor-search.test.ts
New HotkeyEditorSearchResult type, getHotkeyBindingDisplayLabel (handles empty/"Unassigned" and formatted bindings), and getHotkeyEditorSearchResults (filters hotkey sections by normalized query against translated labels and binding strings). Tests verify search filtering and binding display format.
Capture lifecycle and partial override snapshots
src/features/settings/components/hotkey-editor.tsx
Introduce partialConflictOverrideSnapshotRef to preserve partial overrides during conflict resolution. Refactor stopCapture into memoized callback with optional snapshot restoration; update Escape handler, unmount cleanup, and capture start to use refactored stopCapture; update save/conflict handlers to commit without restoring snapshots.
Search UI sidebar and reset confirmation dialog
src/features/settings/components/hotkey-editor.tsx
Add search input and filtered results sidebar rendering; introduce searchQuery state and selectSearchResult helper. Replace direct reset-all button with AlertDialog confirmation flow controlled by isResetAllDialogOpen state.
UI refinements and action wiring
src/features/settings/components/hotkey-editor.tsx
Update unbind button to treat empty binding as unassigned; refactor conflict UI to show per-conflict overwrite actions and conditional overwrite-all button; update action handlers to call refactored stopCapture appropriately; wire cancel button to stopCapture().
Integration test suite
src/features/settings/components/hotkey-editor-reset-dialog.test.tsx
Test reset-all confirmation/cancellation, conflict overwrite/restore, unbind behavior, and search result display. Includes helpers for DOM interaction, event dispatch, and store manipulation.
Export document test
src/config/hotkeys.test.ts
Verify that explicitly unassigned command overrides (empty binding) export as custom commands with empty binding and default-binding fallback.
Internationalization
src/i18n/locales/partials/projects.json
Add localized settings.hotkeys strings across 9 locales (en, es, fr, de, pt-BR, ja, ko, zh, tr) for search placeholder, result counts, reset confirmation, and success toast.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • walterlow/freecut#139: New createHotkeyExportDocument unit test directly extends the hotkey export schema tested in this PR's export test.
  • walterlow/freecut#266: Both PRs refine handling of explicit empty-string bindings ('') as real overrides in import/export and UI state management.

Poem

🐰 Search through hotkeys, find them fast,
Confirm before you reset the past.
Partial snapshots catch halfway through,
Unbind, overwrite, see what's new!
The sidebar hops with every query—
No more keyboard hurry-scurry! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 change: improving the shortcut editor UX through search functionality, reset confirmation dialog, and related polish to the hotkey editor component.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/shortcut-editor-ux

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
Contributor

greptile-apps Bot commented Jun 1, 2026

Greptile Summary

This PR improves the shortcut editor UX with three main additions: a live search bar in the sidebar, a confirmation dialog before resetting all shortcuts, and per-conflict "Overwrite" buttons replacing the previous single "Overwrite All" action. Explicit unassigned bindings (empty string) are now surfaced in the UI and correctly round-tripped through import/export.

  • Search: getHotkeyEditorSearchResults filters by translated label, section name, key ID, or normalized binding; results show section context and binding display, and a localized count is shown above the list.
  • Reset-all guard: The destructive reset button now opens an AlertDialog confirmation step before calling resetHotkeys, preventing accidental data loss.
  • Per-conflict overwrite: Each conflict row gets its own "Overwrite" button; resolving the last remaining conflict auto-saves and stops capture, while "Overwrite All" is still shown when more than one conflict exists.

Confidence Score: 3/5

Safe to merge if single-conflict overwrite is the dominant path; multi-conflict partial-overwrite-then-cancel will silently drop an existing binding.

The per-conflict overwrite path writes unbinds to the store immediately and incrementally. If a user resolves one of two conflicts then presses Escape, the first binding is permanently lost even though the new assignment was never saved. All other changes — search, reset confirmation, i18n, unassigned display, export coverage — are solid and well-tested.

src/features/settings/components/hotkey-editor.tsx — specifically the overwriteConflictingHotkey function and how stopCapture interacts with partial store mutations when multiple conflicts are present.

Important Files Changed

Filename Overview
src/features/settings/components/hotkey-editor.tsx Adds search, reset-all confirmation dialog, and per-conflict overwrite buttons; per-conflict unbinds are not rolled back on cancel, causing unintended binding loss when users abort mid-overwrite.
src/features/settings/components/hotkey-editor-sections.ts Adds getHotkeyEditorSearchResults and getHotkeyBindingDisplayLabel helpers; logic is correct, handles blank bindings and normalized binding queries cleanly.
src/features/settings/components/hotkey-editor-reset-dialog.test.tsx New integration test covering reset confirmation, per-conflict overwrite, unbind state, and search result count; thorough and well-structured.
src/features/settings/components/hotkey-editor-search.test.ts Unit tests for the new search helpers; covers label filtering, binding filtering, and blank-binding display label. All cases look correct.
src/config/hotkeys.test.ts Adds test asserting that explicitly unassigned (empty-string) overrides round-trip correctly through export; straightforward and correct.
src/i18n/locales/partials/projects.json Adds nine new i18n keys across all nine supported locales; translations are complete and plural forms are consistent.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User presses key combo] --> B{Conflicts detected?}
    B -- No --> C[canSaveCapture = true\nSave button enabled]
    B -- Yes --> D[Show per-conflict Overwrite buttons]
    D --> E{Multiple conflicts?}
    E -- Yes --> F[Also show Overwrite All button]
    E -- No --> G[Only per-item Overwrite]
    F --> H[overwriteAllConflictingHotkeys\nUnbind all, save binding, stopCapture]
    G --> I[overwriteConflictingHotkey key]
    D --> I
    I --> J[unbindHotkeyBinding key\ncompute remainingConflicts]
    J --> K{remainingConflicts == 0?}
    K -- Yes --> L[setHotkeyBinding + stopCapture]
    K -- No --> M[Store updated, re-render\ncaptureConflicts shrinks by 1\n⚠️ partial unbind NOT rolled back on Escape]
    M --> D
    C --> N[User clicks Save]
    N --> O[setHotkeyBinding + stopCapture]
Loading

Comments Outside Diff (1)

  1. src/features/settings/components/hotkey-editor.tsx, line 569-589 (link)

    P1 Partial conflict overwrites persist when capture is cancelled

    When there are multiple conflicts (e.g. A and B), clicking "Overwrite" on A immediately calls unbindHotkeyBinding(A), writing the unbind to the store. If the user then presses Escape (or navigates away), stopCapture clears the in-flight draft but does not roll back the store change — A remains permanently unbound even though the overall assignment was never completed. A user who resolves one conflict and then changes their mind loses A's binding without having intended to.

    The old overwriteAllConflictingHotkeys was atomic: nothing was persisted until the user committed every conflict in one shot. The new per-conflict path introduces partial mutations that are not rolled back on cancel. Consider snapshotting the pre-conflict overrides before any individual unbind and restoring that snapshot inside stopCapture (or on Escape) if the binding was never ultimately saved.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/features/settings/components/hotkey-editor.tsx
    Line: 569-589
    
    Comment:
    **Partial conflict overwrites persist when capture is cancelled**
    
    When there are multiple conflicts (e.g. A and B), clicking "Overwrite" on A immediately calls `unbindHotkeyBinding(A)`, writing the unbind to the store. If the user then presses Escape (or navigates away), `stopCapture` clears the in-flight draft but does **not** roll back the store change — A remains permanently unbound even though the overall assignment was never completed. A user who resolves one conflict and then changes their mind loses A's binding without having intended to.
    
    The old `overwriteAllConflictingHotkeys` was atomic: nothing was persisted until the user committed every conflict in one shot. The new per-conflict path introduces partial mutations that are not rolled back on cancel. Consider snapshotting the pre-conflict overrides before any individual unbind and restoring that snapshot inside `stopCapture` (or on Escape) if the binding was never ultimately saved.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code Fix in Codex

Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/features/settings/components/hotkey-editor.tsx:569-589
**Partial conflict overwrites persist when capture is cancelled**

When there are multiple conflicts (e.g. A and B), clicking "Overwrite" on A immediately calls `unbindHotkeyBinding(A)`, writing the unbind to the store. If the user then presses Escape (or navigates away), `stopCapture` clears the in-flight draft but does **not** roll back the store change — A remains permanently unbound even though the overall assignment was never completed. A user who resolves one conflict and then changes their mind loses A's binding without having intended to.

The old `overwriteAllConflictingHotkeys` was atomic: nothing was persisted until the user committed every conflict in one shot. The new per-conflict path introduces partial mutations that are not rolled back on cancel. Consider snapshotting the pre-conflict overrides before any individual unbind and restoring that snapshot inside `stopCapture` (or on Escape) if the binding was never ultimately saved.

Reviews (1): Last reviewed commit: "feat(settings): polish shortcut editor u..." | Re-trigger Greptile

@walterlow walterlow changed the base branch from develop to staging June 1, 2026 08:19
Copy link
Copy Markdown
Contributor

@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: 3

🧹 Nitpick comments (1)
src/features/settings/components/hotkey-editor-reset-dialog.test.tsx (1)

39-65: ⚡ Quick win

Replace fixed retry loops with waitFor/findBy* to reduce CI flakiness.

waitForText/waitForBodyText use only 10 zero-delay ticks; async UI updates can exceed this, causing nondeterministic failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/settings/components/hotkey-editor-reset-dialog.test.tsx` around
lines 39 - 65, Replace the custom retry loops in waitForText and waitForBodyText
with React Testing Library utilities to avoid flaky timing; specifically remove
the 10-iteration zero-delay loops in the functions and instead use waitFor (or
the appropriate findBy* queries) to await the DOM updates, e.g. use waitFor(()
=> expect(screen.getByText(...)).toBeInTheDocument()) or screen.findByText for
waitForText and waitFor(() => expect(document.body).toHaveTextContent(...)) or
screen.findByText scoped to body for waitForBodyText; update any tests that call
waitForText/waitForBodyText to use the new waitFor/findBy* behavior so
asynchronous UI updates are awaited reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/settings/components/hotkey-editor-reset-dialog.test.tsx`:
- Around line 1-7: The test currently uses vite-plus/test plus manual DOM setup
(createRoot) and brittle polling helpers; migrate it to Vitest + jsdom +
`@testing-library/react` by replacing imports from 'vite-plus/test' with vitest's
test helpers and using `@testing-library/react`'s render, screen, fireEvent (or
userEvent) and waitFor/findBy* for assertions; remove manual createRoot and the
custom getButton/click/keyDown/polling loops and instead render <HotkeyEditor>
wrapped with <TooltipProvider> and, if necessary, mock useSettingsStore via
vi.mock to control state, then use screen.findByText/screen.getByRole and
waitFor to simulate keyboard events and assert dialog open/close and text
changes (reference identifiers: HotkeyEditor, TooltipProvider, useSettingsStore,
any test helpers like getButton/getDialog used in the diff).

In `@src/features/settings/components/hotkey-editor-sections.ts`:
- Around line 58-67: bindings currently maps raw shortcut strings so empty
bindings ('') never match the translated "Unassigned" label; update the search
to treat empty keys as the UI label by mapping item.keys (or bindings) such that
when a key === '' you replace it with the translated/unassigned label
(lowercased) before comparing, and ensure both the item.keys.some(...) check and
the bindings.some(...) comparisons compare against normalizedBindingQuery using
the same transformed value; reference symbols: bindings, item.keys, hotkeys,
normalizedBindingQuery, normalizedQuery, itemLabel, sectionLabel.

In `@src/features/settings/components/hotkey-editor.tsx`:
- Around line 665-668: selectSearchResult currently stops capture and sets only
setSelectedKey(item.keys[0]), which leaves the UI scoped to the previous
section; update selectSearchResult to also sync the active section by calling
the state updater that controls the UI scope (e.g., setActiveLayer or
setActiveSection) with the item's section/layer (item.section or item.layer)
right after stopCapture, and make the same change in the other selection helper
at the other occurrence so the keyboard overlay, command list and details panel
all focus the chosen search result.

---

Nitpick comments:
In `@src/features/settings/components/hotkey-editor-reset-dialog.test.tsx`:
- Around line 39-65: Replace the custom retry loops in waitForText and
waitForBodyText with React Testing Library utilities to avoid flaky timing;
specifically remove the 10-iteration zero-delay loops in the functions and
instead use waitFor (or the appropriate findBy* queries) to await the DOM
updates, e.g. use waitFor(() =>
expect(screen.getByText(...)).toBeInTheDocument()) or screen.findByText for
waitForText and waitFor(() => expect(document.body).toHaveTextContent(...)) or
screen.findByText scoped to body for waitForBodyText; update any tests that call
waitForText/waitForBodyText to use the new waitFor/findBy* behavior so
asynchronous UI updates are awaited reliably.
🪄 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: 1835b62b-0678-48ed-95bf-05d10859b2a8

📥 Commits

Reviewing files that changed from the base of the PR and between 24465b0 and 2435d62.

📒 Files selected for processing (6)
  • src/config/hotkeys.test.ts
  • src/features/settings/components/hotkey-editor-reset-dialog.test.tsx
  • src/features/settings/components/hotkey-editor-search.test.ts
  • src/features/settings/components/hotkey-editor-sections.ts
  • src/features/settings/components/hotkey-editor.tsx
  • src/i18n/locales/partials/projects.json

Comment on lines +1 to +7
import { createElement } from 'react'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it } from 'vite-plus/test'
import { HotkeyEditor } from './hotkey-editor'
import { TooltipProvider } from '@/components/ui/tooltip'
import { useSettingsStore } from '../stores/settings-store'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/features/settings/components/hotkey-editor-reset-dialog.test.tsx"

echo "---- File exists and basic stats ----"
ls -l "$FILE" || true
wc -l "$FILE" || true

echo
echo "---- Imports section (first ~80 lines) ----"
sed -n '1,120p' "$FILE"

echo
echo "---- Check for forbidden patterns in this file ----"
rg -n "console\." "$FILE" || true
rg -n "\bany\b" "$FILE" || true

echo
echo "---- Check for `@testing-library/react` usage ----"
rg -n "`@testing-library/react`" "$FILE" || true
rg -n "user-event" "$FILE" || true

echo
echo "---- Check for vite-plus/test usage ----"
rg -n "vite-plus/test" "$FILE" || true

echo
echo "---- Inspect polling helpers (grep waitForText/waitForBodyText) ----"
rg -n "waitFor(Text|BodyText)|setTimeout\\(0\\)|attempt" "$FILE" || true

echo
echo "---- Inspect for manual query/click/keyDown/changeInput helpers ----"
rg -n "getButton|click\\(|keyDown\\(|changeInput\\(|document\\.querySelector|querySelectorAll" "$FILE" || true

Repository: walterlow/freecut

Length of output: 6299


Migrate this hotkey-editor-reset-dialog test to the required Vitest + jsdom + @testing-library/react`` stack.

  • The test uses vite-plus/test and manual DOM/event plumbing (createRoot + getButton/click/keyDown and waitForText/waitForBodyText) instead of @testing-library/react-based testing (render, screen, findBy*/waitFor).
  • The custom polling helpers use fixed 10-attempt loops with setTimeout(0), which is brittle and can cause CI flakiness.
Suggested direction (migration shape)
-import { createElement } from 'react'
-import { createRoot, type Root } from 'react-dom/client'
-import { afterEach, beforeEach, describe, expect, it } from 'vite-plus/test'
+import { describe, it, beforeEach, afterEach, expect } from 'vitest'
+import { render, screen, waitFor } from '`@testing-library/react`'
+import userEvent from '`@testing-library/user-event`'
...
-beforeEach(async () => {
-  ...
-  root = createRoot(container)
-  root.render(...)
-  await waitForText('Reset All')
-})
+beforeEach(async () => {
+  ...
+  render(
+    <TooltipProvider>
+      <DialogPrimitive.Root open>
+        <HotkeyEditor />
+      </DialogPrimitive.Root>
+    </TooltipProvider>,
+  )
+  await screen.findByRole('button', { name: 'Reset All' })
+})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/settings/components/hotkey-editor-reset-dialog.test.tsx` around
lines 1 - 7, The test currently uses vite-plus/test plus manual DOM setup
(createRoot) and brittle polling helpers; migrate it to Vitest + jsdom +
`@testing-library/react` by replacing imports from 'vite-plus/test' with vitest's
test helpers and using `@testing-library/react`'s render, screen, fireEvent (or
userEvent) and waitFor/findBy* for assertions; remove manual createRoot and the
custom getButton/click/keyDown/polling loops and instead render <HotkeyEditor>
wrapped with <TooltipProvider> and, if necessary, mock useSettingsStore via
vi.mock to control state, then use screen.findByText/screen.getByRole and
waitFor to simulate keyboard events and assert dialog open/close and text
changes (reference identifiers: HotkeyEditor, TooltipProvider, useSettingsStore,
any test helpers like getButton/getDialog used in the diff).

Comment on lines +58 to +67
const bindings = item.keys.map((key) => hotkeys[key].toLowerCase())

return (
itemLabel.includes(normalizedQuery) ||
sectionLabel.includes(normalizedQuery) ||
item.keys.some((key) => key.toLowerCase().includes(normalizedQuery)) ||
bindings.some(
(binding) =>
binding.includes(normalizedQuery) ||
(normalizedBindingQuery.length > 0 && binding === normalizedBindingQuery),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make explicit unassigned bindings searchable.

bindings only contains raw shortcut strings, so commands with '' bindings can never match the translated “Unassigned” label shown in the UI. A search for unassigned shortcuts will always return no results.

Suggested fix
       .filter((item) => {
         const itemLabel = translate(item.labelKey).toLowerCase()
-        const bindings = item.keys.map((key) => hotkeys[key].toLowerCase())
+        const rawBindings = item.keys.map((key) => hotkeys[key].toLowerCase())
+        const displayBindings = item.keys.map((key) =>
+          getHotkeyBindingDisplayLabel(
+            hotkeys[key],
+            translate('projects.settings.hotkeys.unassigned'),
+          ).toLowerCase(),
+        )
 
         return (
           itemLabel.includes(normalizedQuery) ||
           sectionLabel.includes(normalizedQuery) ||
           item.keys.some((key) => key.toLowerCase().includes(normalizedQuery)) ||
-          bindings.some(
+          rawBindings.some(
             (binding) =>
               binding.includes(normalizedQuery) ||
               (normalizedBindingQuery.length > 0 && binding === normalizedBindingQuery),
-          )
+          ) ||
+          displayBindings.some((binding) => binding.includes(normalizedQuery))
         )
       })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const bindings = item.keys.map((key) => hotkeys[key].toLowerCase())
return (
itemLabel.includes(normalizedQuery) ||
sectionLabel.includes(normalizedQuery) ||
item.keys.some((key) => key.toLowerCase().includes(normalizedQuery)) ||
bindings.some(
(binding) =>
binding.includes(normalizedQuery) ||
(normalizedBindingQuery.length > 0 && binding === normalizedBindingQuery),
const rawBindings = item.keys.map((key) => hotkeys[key].toLowerCase())
const displayBindings = item.keys.map((key) =>
getHotkeyBindingDisplayLabel(
hotkeys[key],
translate('projects.settings.hotkeys.unassigned'),
).toLowerCase(),
)
return (
itemLabel.includes(normalizedQuery) ||
sectionLabel.includes(normalizedQuery) ||
item.keys.some((key) => key.toLowerCase().includes(normalizedQuery)) ||
rawBindings.some(
(binding) =>
binding.includes(normalizedQuery) ||
(normalizedBindingQuery.length > 0 && binding === normalizedBindingQuery),
) ||
displayBindings.some((binding) => binding.includes(normalizedQuery))
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/settings/components/hotkey-editor-sections.ts` around lines 58 -
67, bindings currently maps raw shortcut strings so empty bindings ('') never
match the translated "Unassigned" label; update the search to treat empty keys
as the UI label by mapping item.keys (or bindings) such that when a key === ''
you replace it with the translated/unassigned label (lowercased) before
comparing, and ensure both the item.keys.some(...) check and the
bindings.some(...) comparisons compare against normalizedBindingQuery using the
same transformed value; reference symbols: bindings, item.keys, hotkeys,
normalizedBindingQuery, normalizedQuery, itemLabel, sectionLabel.

Comment on lines +665 to +668
const selectSearchResult = (item: HotkeyEditorItem) => {
stopCapture()
setSelectedKey(item.keys[0]!)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Sync the active section when selecting a search result.

This only updates selectedKey. If another activeLayer was already selected, the details panel jumps to the found command but the keyboard overlay and command list stay scoped to the old section, so the selected result is not actually focused.

Suggested fix
-  const selectSearchResult = (item: HotkeyEditorItem) => {
+  const selectSearchResult = (section: HotkeyEditorSection, item: HotkeyEditorItem) => {
     stopCapture()
+    setActiveLayer(section)
     setSelectedKey(item.keys[0]!)
   }
-                            onClick={() => selectSearchResult(item)}
+                            onClick={() => selectSearchResult(section, item)}

Also applies to: 793-794

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/settings/components/hotkey-editor.tsx` around lines 665 - 668,
selectSearchResult currently stops capture and sets only
setSelectedKey(item.keys[0]), which leaves the UI scoped to the previous
section; update selectSearchResult to also sync the active section by calling
the state updater that controls the UI scope (e.g., setActiveLayer or
setActiveSection) with the item's section/layer (item.section or item.layer)
right after stopCapture, and make the same change in the other selection helper
at the other occurrence so the keyboard overlay, command list and details panel
all focus the chosen search result.

@walterlow walterlow merged commit dddcbe8 into staging Jun 1, 2026
5 of 6 checks passed
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