feat(desktop): complete keybinding customization rollout#443
Conversation
Make SearchView command shortcuts configurable via persisted keybindings. Add settings UI and tests for shortcut routing and validation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Assisted-by: OpenAI Codex
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR introduces configurable search keybindings across the desktop app. It adds a new keyboard shortcut system for search window actions (like maximize, toggle pin, reopen last session), integrates those bindings into keyboard routing and settings persistence, updates the global shortcut UI to prevent conflicts, and provides search-specific shortcut editing with validation and persistence. ChangesSearch keybindings end-to-end integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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 `@apps/desktop/src/config/searchKeybindings.ts`:
- Around line 130-133: normalizeSearchKeybindings currently accepts any
normalized string (via normalizeLocalShortcutString) including modifierless
single-letter keys like "H", allowing invalid persisted payloads; update the
normalization step to enforce the shortcut policy (require at least one modifier
or allow only function keys when modifierless) before writing to
normalized[definition.id]. Specifically, after calling
normalizeLocalShortcutString(candidate) validate the returned shortcut with the
policy (e.g., check for presence of Ctrl/Alt/Meta/Shift modifiers or that the
key is a function key) and only assign to normalized[definition.id] when it
passes; otherwise skip (or clear) the entry so invalid persisted shortcuts
cannot bypass UI validation. Ensure you use the existing helpers
(normalizeLocalShortcutString and the normalizeSearchKeybindings function) and
centralize the policy check so all persisted normalization follows the same
rule.
In `@apps/desktop/src/stores/settings.ts`:
- Around line 187-199: The code currently sets
settings.value.lastClosedSessionId and settings.value.lastActiveSessionId
directly from Number(value) and only checks Number.isNaN; change this to
validate that the persisted value is a positive integer (>0) before applying:
trim the input, ensure it matches /^\d+$/ (no decimals/whitespace/signs),
convert to a Number and accept only if Number.isInteger(parsed) && parsed > 0,
otherwise set the field to null; factor this into a small helper (e.g.,
validateSessionId(value)) and reuse it for settings.value.lastClosedSessionId,
settings.value.lastActiveSessionId and the other similar assignments mentioned
so all persisted session IDs are sanitized consistently.
In `@apps/desktop/src/utils/shortcuts.ts`:
- Around line 154-161: The loop that parses tokens currently overwrites
non-modifier keys (variables: parts, SUPPORTED_CAPTURE_MODIFIERS, modifierSet,
key) allowing malformed multi-key shortcuts to be silently rewritten; update the
parsing in the function that normalizes shortcuts so that when iterating parts,
if you encounter a non-modifier token and key is already set, the function fails
normalization (e.g., return an error/invalid result or throw) instead of
assigning the new token—this rejects multi-key inputs rather than keeping the
last token. Ensure the failure path is used by whatever callers expect
(normalize/parseShortcut) and add a test for two non-modifier tokens to verify
rejection.
In `@apps/desktop/src/views/SearchView/composables/searchInteraction.ts`:
- Around line 776-804: The onSearchKeybindingAction handler currently swallows
any actionId not explicitly matched, causing declared shortcuts to appear
broken; update the switch in onSearchKeybindingAction to either (A) add explicit
cases for the remaining declared search keybinding IDs and implement their
behavior (or delegate to existing helpers like controller.focusSearchInput,
openHistoryDialog, startNewSession, etc.), or (B) change the default branch to
not silently consume the action—return false (or propagate/forward the action to
a higher-level handler) and optionally log a debug/warn with the actionId—so
unhandled actions are not swallowed.
In `@apps/desktop/src/views/SearchView/composables/useSearchRequest.ts`:
- Around line 436-447: The persistence calls to
settingsStore.updateLastClosedSessionId(null) can throw and should not abort
reopen flow; wrap both calls (the one after successfully opening the session
where currentSessionId.value === settingsStore.lastClosedSessionId, and the one
in the catch block where sessionId === settingsStore.lastClosedSessionId) in
their own try/catch that swallows or logs errors so failures to write metadata
do not propagate out of reopenLastClosedSession (refer to
settingsStore.updateLastClosedSessionId, currentSessionId, sessionId,
lastOpenError and reopenLastClosedSession in useSearchRequest.ts).
In `@apps/desktop/src/views/SettingsView/components/General/index.vue`:
- Around line 152-155: The function formatSearchShortcutForSettings currently
returns the hardcoded Chinese string '无' for empty shortcuts; change it to
return a localized string using the app's i18n translator instead (e.g., call
the same i18n key used elsewhere in settings like t('settings.noShortcut') or
the existing settings copy key). Update formatSearchShortcutForSettings to
obtain the translator (e.g., via useI18n() or the project's i18n helper) and
return t(...) when normalizedShortcut is falsy so the fallback respects the
active locale.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 1f43909e-e466-498f-9eaa-82de665e7cce
📒 Files selected for processing (25)
.prettierignoreapps/desktop/src/components/CustomSelect.vueapps/desktop/src/components/appIconMap.tsapps/desktop/src/components/ui/select/SelectContent.vueapps/desktop/src/config/searchKeybindings.tsapps/desktop/src/database/schema.tsapps/desktop/src/i18n/messages.tsapps/desktop/src/services/EventService/types.tsapps/desktop/src/stores/settings.tsapps/desktop/src/utils/font.tsapps/desktop/src/utils/shortcuts.tsapps/desktop/src/views/SearchView/composables/interaction/useSearchKeyboardRouter.tsapps/desktop/src/views/SearchView/composables/searchInteraction.tsapps/desktop/src/views/SearchView/composables/useSearchRequest.tsapps/desktop/src/views/SearchView/index.vueapps/desktop/src/views/SettingsView/components/General/index.vueapps/desktop/tests/SettingsView/general-language.test.tsapps/desktop/tests/composables/SearchView/searchInteraction.test.tsapps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.tsapps/desktop/tests/composables/SearchView/useSearchRequest.test.tsapps/desktop/tests/config/searchKeybindings.test.tsapps/desktop/tests/stores/settings-keybindings.test.tsapps/desktop/tests/utils/shortcuts.test.tsapps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.tseslint.config.js
📜 Review details
🧰 Additional context used
🪛 OpenGrep (1.22.0)
apps/desktop/src/utils/shortcuts.ts
[ERROR] 318-318: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
🔇 Additional comments (19)
apps/desktop/src/components/CustomSelect.vue (1)
11-67: LGTM!Also applies to: 68-71, 93-96, 105-106, 116-126, 127-150, 154-156, 162-164
apps/desktop/src/components/ui/select/SelectContent.vue (1)
11-17: LGTM!Also applies to: 21-22, 31-32, 35-36, 40-48, 52-55
apps/desktop/src/components/appIconMap.ts (1)
42-43: LGTM!Also applies to: 86-87
apps/desktop/tests/SettingsView/general-language.test.ts (1)
169-172: LGTM!apps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.ts (1)
7-47: LGTM!Also applies to: 105-126, 188-189, 191-474
apps/desktop/src/utils/font.ts (1)
19-19: LGTM!Also applies to: 161-163, 177-179, 184-186
.prettierignore (1)
8-8: LGTM!eslint.config.js (1)
63-66: LGTM!apps/desktop/src/database/schema.ts (1)
29-29: LGTM!apps/desktop/src/services/EventService/types.ts (1)
14-14: LGTM!Also applies to: 75-79, 93-93
apps/desktop/tests/config/searchKeybindings.test.ts (1)
13-80: LGTM!apps/desktop/tests/stores/settings-keybindings.test.ts (1)
55-166: LGTM!apps/desktop/tests/utils/shortcuts.test.ts (1)
25-153: LGTM!apps/desktop/src/i18n/messages.ts (1)
80-128: LGTM!Also applies to: 855-915
apps/desktop/src/views/SearchView/composables/interaction/useSearchKeyboardRouter.ts (1)
1-8: LGTM!Also applies to: 15-17, 25-25, 35-37, 49-56, 63-63, 86-98, 108-108, 118-119, 132-137, 144-144, 171-174, 182-185, 225-243, 279-306
apps/desktop/tests/composables/SearchView/searchInteraction.test.ts (1)
2-3: LGTM!Also applies to: 6-6, 12-12, 36-38, 297-474
apps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.ts (1)
3-3: LGTM!Also applies to: 14-57, 60-62, 103-483
apps/desktop/src/views/SearchView/index.vue (1)
119-119: LGTM!Also applies to: 237-237, 513-513, 541-541, 817-846
apps/desktop/tests/composables/SearchView/useSearchRequest.test.ts (1)
18-18: LGTM!Also applies to: 36-45, 54-54, 70-72, 149-153, 352-352, 357-403, 405-443
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/desktop/tests/composables/SearchView/searchInteraction.test.ts (1)
297-477: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winAdd a keydown-handler test for the new settings action.
This suite doesn’t yet assert the new
'search.settings.open'branch increateSearchKeydownHandler(Line 804 inapps/desktop/src/views/SearchView/composables/searchInteraction.ts). Add aCtrl+,/Cmd+,case that verifiesopenSettingsWindowis called and the event is consumed.🤖 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 `@apps/desktop/tests/composables/SearchView/searchInteraction.test.ts` around lines 297 - 477, Add a test to cover the new "search.settings.open" branch in createSearchKeydownHandler by asserting openSettingsWindow is invoked and the event is consumed; create a handler via createSearchKeydownHandler (reuse createControllerStub) with openSettingsWindow mocked (vi.fn().mockResolvedValue(undefined)), dispatch a KeyboardEvent('keydown', { key: ',', ctrlKey: true, cancelable: true }) and await the handler, then expect openSettingsWindow toHaveBeenCalledTimes(1) and event.defaultPrevented toBe(true); also add a separate case for Cmd (metaKey: true) if you want both modifier behaviours covered.
🤖 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.
Outside diff comments:
In `@apps/desktop/tests/composables/SearchView/searchInteraction.test.ts`:
- Around line 297-477: Add a test to cover the new "search.settings.open" branch
in createSearchKeydownHandler by asserting openSettingsWindow is invoked and the
event is consumed; create a handler via createSearchKeydownHandler (reuse
createControllerStub) with openSettingsWindow mocked
(vi.fn().mockResolvedValue(undefined)), dispatch a KeyboardEvent('keydown', {
key: ',', ctrlKey: true, cancelable: true }) and await the handler, then expect
openSettingsWindow toHaveBeenCalledTimes(1) and event.defaultPrevented
toBe(true); also add a separate case for Cmd (metaKey: true) if you want both
modifier behaviours covered.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 51ca1c1b-0722-4638-891f-de5819401f32
📒 Files selected for processing (11)
apps/desktop/src/config/searchKeybindings.tsapps/desktop/src/i18n/messages.tsapps/desktop/src/utils/shortcuts.tsapps/desktop/src/views/SearchView/composables/searchInteraction.tsapps/desktop/src/views/SearchView/index.vueapps/desktop/src/views/SettingsView/components/General/index.vueapps/desktop/tests/composables/SearchView/searchInteraction.test.tsapps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.tsapps/desktop/tests/config/searchKeybindings.test.tsapps/desktop/tests/stores/settings-keybindings.test.tsapps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Desktop E2E Smoke (Windows)
- GitHub Check: Frontend Tests
- GitHub Check: Rust Checks
- GitHub Check: CodeQL (rust)
🔇 Additional comments (13)
apps/desktop/tests/stores/settings-keybindings.test.ts (1)
71-74: LGTM!apps/desktop/src/config/searchKeybindings.ts (1)
16-17: LGTM!Also applies to: 89-96, 143-151
apps/desktop/src/i18n/messages.ts (1)
104-105: LGTM!Also applies to: 119-120, 885-886, 903-904
apps/desktop/src/utils/shortcuts.ts (1)
40-40: LGTM!apps/desktop/tests/config/searchKeybindings.test.ts (1)
46-47: LGTM!Also applies to: 57-58, 65-66
apps/desktop/src/views/SearchView/composables/searchInteraction.ts (1)
139-140: LGTM!Also applies to: 677-678, 804-806
apps/desktop/src/views/SearchView/index.vue (1)
544-545: LGTM!apps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.ts (1)
189-198: LGTM!apps/desktop/src/views/SettingsView/components/General/index.vue (1)
222-222: LGTM!apps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.ts (4)
19-19: LGTM!
223-224: LGTM!
231-236: LGTM!
273-273: LGTM!
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.ts (1)
9-20: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winBuild the mocked search keybindings from the shared defaults.
This fixture duplicates the production map, so any change to the real keybinding defaults has to be mirrored manually here and these restore/default assertions can silently test the wrong contract. Seed
searchKeybindingsfromcreateDefaultSearchKeybindings()and override only the bindings a given test cares about.🤖 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 `@apps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.ts` around lines 9 - 20, The test fixture createGeneralSettingsMock duplicates production keybinding defaults; replace the hardcoded searchKeybindings with a seeded map from the shared helper createDefaultSearchKeybindings() and only override entries needed for specific tests. Update createGeneralSettingsMock to call createDefaultSearchKeybindings(), spread that result into the searchKeybindings value, and apply per-test overrides (e.g., for 'search.history.open' etc.) so the test uses the canonical defaults and only customizes what each test requires.apps/desktop/src/views/SettingsView/components/General/index.vue (1)
532-542:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReserved-shortcut validation now blocks a supported reopen binding.
This unconditional check rejects every reserved chord, but the search keyboard router still supports
search.session.reopenLastClosedwithMod+Up. BecauseresetSearchShortcut()also flows throughsaveSearchShortcut(), users can no longer save or restore that action from Settings.🛠️ Proposed fix
- if (isReservedLocalShortcut(normalizedShortcut)) { + const allowsReservedShortcut = + actionId === 'search.session.reopenLastClosed' && + normalizedShortcut === 'Mod+Up'; + + if (isReservedLocalShortcut(normalizedShortcut) && !allowsReservedShortcut) { reportSearchShortcutError( actionId, 'settings.general.searchShortcuts.errors.reserved' ); updateSearchShortcutDisplay(🤖 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 `@apps/desktop/src/views/SettingsView/components/General/index.vue` around lines 532 - 542, The reserved-shortcut check in the save path (isReservedLocalShortcut) is too strict and blocks the supported reopen binding (search.session.reopenLastClosed with Mod+Up); update the logic in the block around isReservedLocalShortcut to allow that supported binding instead of unconditionally rejecting it — either skip the reserved validation when actionId === 'search.session.reopenLastClosed' (or when resetSearchShortcut/saveSearchShortcut flow needs to restore it) or replace the check with a router-aware allowance (e.g., call a helper that returns true if the shortcut is allowed for this action by the search keyboard router) before calling reportSearchShortcutError/updateSearchShortcutDisplay/formatSearchShortcutForSettings so users can save and restore the reopenLastClosed binding.
🤖 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 `@apps/desktop/src/views/SearchView/composables/useSearchRequest.ts`:
- Around line 428-443: The catch block in reopenLastClosedSession clears
settingsStore.lastClosedSessionId on any error, losing the only retry candidate;
change it so clearLastClosedSessionIdSafely is NOT called for generic/transient
errors. Specifically, remove the await clearLastClosedSessionIdSafely() from the
catch in reopenLastClosedSession(), and only clear the stored id when you can
deterministically detect the session is gone (e.g., catch and check for a
specific SessionNotFoundError or an error code/message indicating the session is
missing) — otherwise rethrow the error so the user can retry; keep the existing
success-path clearing logic that checks currentSessionId.value === sessionId
after openSession returns.
In `@apps/desktop/src/views/SearchView/index.vue`:
- Around line 818-822: In handleReopenLastClosedSession: before the early return
that preserves the draft (the branch that checks queryText.value.trim() ||
attachments.value.length > 0), ensure you call hideAllPopups() so any open popup
surfaces are dismissed, then await controller.focusSearchInput() and return;
this mirrors the existing search.input.focus path behavior and keeps the draft
while hiding popups.
---
Outside diff comments:
In `@apps/desktop/src/views/SettingsView/components/General/index.vue`:
- Around line 532-542: The reserved-shortcut check in the save path
(isReservedLocalShortcut) is too strict and blocks the supported reopen binding
(search.session.reopenLastClosed with Mod+Up); update the logic in the block
around isReservedLocalShortcut to allow that supported binding instead of
unconditionally rejecting it — either skip the reserved validation when actionId
=== 'search.session.reopenLastClosed' (or when
resetSearchShortcut/saveSearchShortcut flow needs to restore it) or replace the
check with a router-aware allowance (e.g., call a helper that returns true if
the shortcut is allowed for this action by the search keyboard router) before
calling
reportSearchShortcutError/updateSearchShortcutDisplay/formatSearchShortcutForSettings
so users can save and restore the reopenLastClosed binding.
In `@apps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.ts`:
- Around line 9-20: The test fixture createGeneralSettingsMock duplicates
production keybinding defaults; replace the hardcoded searchKeybindings with a
seeded map from the shared helper createDefaultSearchKeybindings() and only
override entries needed for specific tests. Update createGeneralSettingsMock to
call createDefaultSearchKeybindings(), spread that result into the
searchKeybindings value, and apply per-test overrides (e.g., for
'search.history.open' etc.) so the test uses the canonical defaults and only
customizes what each test requires.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: ff5f7c16-e57d-44e4-814a-8451f0559450
📒 Files selected for processing (12)
apps/desktop/src/config/searchKeybindings.tsapps/desktop/src/utils/shortcuts.tsapps/desktop/src/views/SearchView/composables/interaction/useSearchKeyboardRouter.tsapps/desktop/src/views/SearchView/composables/useSearchRequest.tsapps/desktop/src/views/SearchView/index.vueapps/desktop/src/views/SettingsView/components/General/index.vueapps/desktop/tests/composables/SearchView/searchInteraction.test.tsapps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.tsapps/desktop/tests/composables/SearchView/useSearchRequest.test.tsapps/desktop/tests/config/searchKeybindings.test.tsapps/desktop/tests/utils/shortcuts.test.tsapps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: CodeQL (rust)
- GitHub Check: Desktop E2E Smoke (Windows)
- GitHub Check: Rust Checks
- GitHub Check: Frontend Tests
🔇 Additional comments (6)
apps/desktop/src/config/searchKeybindings.ts (1)
5-5: LGTM!Also applies to: 62-62, 152-154
apps/desktop/src/utils/shortcuts.ts (1)
17-31: LGTM!Also applies to: 173-177
apps/desktop/tests/config/searchKeybindings.test.ts (1)
42-42: LGTM!Also applies to: 56-57, 64-64, 67-67, 80-80
apps/desktop/tests/utils/shortcuts.test.ts (1)
36-36: LGTM!Also applies to: 43-46, 73-73, 134-139
apps/desktop/src/views/SearchView/composables/interaction/useSearchKeyboardRouter.ts (1)
86-89: LGTM!Also applies to: 284-286, 296-298
apps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.ts (1)
165-175: LGTM!
Alt+Space opened the Win32 window system menu instead of being captured as a shortcut, because the WM_SYSKEYDOWN/WM_SYSCHAR is handled by the WebView2 child window (Chrome_WidgetWin_*), not the top-level window. Subclass the top-level window and all descendant windows after the webview is ready, swallowing WM_SYSKEYDOWN/WM_SYSCHAR for Space and WM_SYSCOMMAND(SC_KEYMENU) at the source so the keydown still reaches the DOM for shortcut capture. Subclass install failures degrade to a warning rather than aborting window creation.
Alt+Space could only be set from the dropdown preset list, not by typing it into the capture input. WebView2 classifies Alt+Space as a system accelerator and calls SetHandled implicitly, so the keydown never reaches the DOM and the capture handler never fires. Bridge the key from the host: in the WebView2 AcceleratorKeyPressed handler emit a 'shortcut-capture-system-key' Tauri event with the modifier state, and have the global shortcut capture input subscribe to it during capture mode and reuse the existing save path. The HWND subclass continues to swallow WM_SYSCHAR / SC_KEYMENU so the system menu stays suppressed. This mirrors the existing search-surface accelerator bridge.
The global shortcut input rejected Cmd on Mac because it duplicated the capture logic and treated metaKey unconditionally as the Win/Super key. The Rust parser also only recognised Ctrl/Alt/Shift, so Cmd-based global shortcuts could never be registered. Reuse captureShortcutFromKeyboardEvent in General/index.vue so Mac Cmd flows through correctly while Win/Super is still rejected with a now platform-neutral message. Recognise cmd/command/meta/super/win/windows and a Mod alias in parse_shortcut, mapping to global-hotkey's SUPER as HotKey::new normalises META to SUPER. Cover the new aliases with unit tests.
Summary
Alt+Space/Ctrl+Spacecan be set from either the input field or preset menu, with safer native re-registration behavior.Related issue or RFC
Related: #145 - make model-switching and other shortcuts configurable.
Also references #40 for SearchView shortcut behavior.
AI assistance disclosure
Testing evidence
Earlier broad branch validation:
Latest local validation after review fixes and shortcut-capture updates:
Risk notes
search_keybindings; malformed or conflicting persisted values fall back to safe defaults.Screenshots or recordings
No updated screenshots. The UI changes are in Settings > General > Shortcuts.
Checklist
[WIP]or similar title prefixes.AgentService, runtime, MCP, or schema boundaries, there is an accepted RFC.pnpm test:prfor this code PR, or this is a docs-only change.pnpm test:coverage:rustor relied on CI coverage evidence.pnpm test:e2elocally or documented why CI is the first valid proof.