Skip to content

feat(desktop): complete keybinding customization rollout#443

Open
velga111 wants to merge 61 commits into
TouchAI-org:mainfrom
velga111:feat/keybindings-customization
Open

feat(desktop): complete keybinding customization rollout#443
velga111 wants to merge 61 commits into
TouchAI-org:mainfrom
velga111:feat/keybindings-customization

Conversation

@velga111

@velga111 velga111 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

First external contributors may need to complete the CLA Assistant check before merge.
For code changes, this PR must link the related issue or RFC in the section below.

Summary

  • Makes existing SearchView shortcuts configurable in Settings, including history, input focus, new session, model toggle, window pin, and maximize.
  • Adds configurable shortcuts for reopening the last closed session and opening Settings from SearchView.
  • Keeps fixed SearchView actions such as submit and cancel visible but non-configurable.
  • Adds shortcut validation for duplicates, reserved navigation/editing keys, malformed persisted values, modifier policy, and conflicts with the global activation shortcut.
  • Tightens global activation shortcut capture and registration so Alt+Space / Ctrl+Space can be set from either the input field or preset menu, with safer native re-registration behavior.
  • Adds focused unit coverage for shortcut normalization, Settings shortcut editing, SearchView keyboard routing, and settings persistence.

Related issue or RFC

Related: #145 - make model-switching and other shortcuts configurable.

Also references #40 for SearchView shortcut behavior.

AI assistance disclosure

  • Tool(s) used: OpenAI Codex.
  • Scope of assistance: codebase inspection, implementation, local validation, PR preparation, and review-feedback fixes.
  • Human review or rewrite performed: changes were reviewed locally before pushing; final maintainer review is pending.
  • Architecture or boundary impact: this PR touches desktop Settings, SearchView keyboard routing, shortcut persistence, and native global-shortcut registration. It does not add a new persisted session-restore contract.

Testing evidence

Earlier broad branch validation:

pnpm test:pr
# passed: desktop check, unit tests, Rust tests, coverage, and site build
# note: ESLint emitted existing warnings

Latest local validation after review fixes and shortcut-capture updates:

pnpm --filter @touchai/desktop test:run -- tests/views/SettingsView/settingsGeneralComponent.test.ts tests/utils/shortcuts.test.ts
# passed: 2 test files, 31 tests

pnpm --filter @touchai/desktop test:run -- tests/utils/shortcuts.test.ts tests/views/SettingsView/settingsGeneralComponent.test.ts tests/config/searchKeybindings.test.ts
# passed: 3 test files, 37 tests

pnpm --filter @touchai/desktop type:check
pnpm --filter @touchai/desktop test:typecheck
pnpm --filter @touchai/desktop check:rust
pnpm --filter @touchai/desktop format:check
pnpm --filter @touchai/desktop lint:check
# lint:check passed with existing warnings only

Risk notes

  • SearchView keyboard behavior changes are user-facing. The branch keeps typing, navigation, submit, and cancel flows protected with reserved-key handling and fixed shortcut rows.
  • Global shortcut registration now surfaces failed unregister/register transitions instead of reporting success while keeping the old shortcut active.
  • Shortcut settings are persisted as normalized JSON under search_keybindings; malformed or conflicting persisted values fall back to safe defaults.
  • No database migration is added.

Screenshots or recordings

No updated screenshots. The UI changes are in Settings > General > Shortcuts.

Checklist

  • The PR title follows Conventional Commits and is valid for squash merge.
  • This PR is either ready for review or explicitly marked as a Draft PR.
  • I did not use [WIP] or similar title prefixes.
  • If AI materially assisted this PR, I disclosed the tools and scope and I personally reviewed every affected change.
  • I can explain the why, what, and how of this change without relying on an AI tool.
  • If this touches AgentService, runtime, MCP, or schema boundaries, there is an accepted RFC.
  • If this changes architecture or adds a new cross-boundary abstraction, there is an accepted RFC.
  • I ran pnpm test:pr for this code PR, or this is a docs-only change.
  • If I changed Rust behavior or tests, I reviewed pnpm test:coverage:rust or relied on CI coverage evidence.
  • If I changed desktop startup/window/search/popup/settings/E2E paths, I ran pnpm test:e2e locally or documented why CI is the first valid proof.
  • I added tests or explained why tests are not appropriate.
  • I updated docs when behavior changed.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This 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.

Changes

Search keybindings end-to-end integration

Layer / File(s) Summary
Select primitives and icon wiring
apps/desktop/src/components/CustomSelect.vue, apps/desktop/src/components/ui/select/SelectContent.vue, apps/desktop/src/components/appIconMap.ts
CustomSelect expands to support controlled open prop with update:open emit, displayLabel override, test-id props (triggerTestId, contentTestId, optionTestIdPrefix), disablePortal pass-through, and triggerAs for trigger element customization. SelectContent disables attribute inheritance and merges $attrs into content props while respecting disablePortal. Icon map adds IconUndo entry under 'undo' key.
Shortcut contracts, normalization engine, and locale keys
apps/desktop/src/utils/shortcuts.ts, apps/desktop/src/config/searchKeybindings.ts, apps/desktop/src/i18n/messages.ts, apps/desktop/tests/utils/shortcuts.test.ts, apps/desktop/tests/config/searchKeybindings.test.ts
New shortcuts module provides platform-aware shortcut utilities: canonicalization, keyboard-event matching, capture, conflict detection, and modifier/function-key classification. New searchKeybindings config defines action catalog with definitions (labels/descriptions, defaults, capability flags) and exports validation/normalization functions. Locale dictionaries replace global-shortcut description with more specific activation text and add search-window action labels/descriptions and error keys. Tests verify normalization rules, platform-specific behavior, default synthesis, and conflict handling.
Settings schema, store persistence, and events
apps/desktop/src/stores/setting/index.ts, apps/desktop/src/stores/settings.ts, apps/desktop/tests/stores/settings-keybindings.test.ts, apps/desktop/tests/stores/setting.test.ts
General settings schema adds search_keybindings field with computed/updater accessors. Store layer defines parse/normalize/rewrite logic with persistence predicates. Settings loader conditionally rewrites persisted values when shouldRewritePersisted matches and aggregates persistence errors. Tests verify initialize/merge behavior and updateSearchKeybindings event emission.
Action-based keyboard router behavior
apps/desktop/src/views/SearchView/composables/interaction/useSearchKeyboardRouter.ts, apps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.ts
Router switches from fixed primary-shortcut system to configurable SearchKeybindingActionId resolution via matchShortcut. New cursor-boundary predicates and input-history callback replace prior routing heuristics. Quick Search key handling expands to PageUp/PageDown/ContextMenu/F10+Shift and Ctrl/Cmd+G toggle. Escape collapses quick search when highlighted before other surface closing. Tests cover pending-approval bypass, remapped search commands, placeholder shortcut non-routing, rejected promise handling, popup-focus key consumption, and escape fallback ordering.
Search interaction wiring and keydown handling
apps/desktop/src/views/SearchView/composables/searchInteraction.ts, apps/desktop/tests/composables/SearchView/searchInteraction.test.ts
Keyboard options accept configurable searchKeybindings and action-callback handlers (reopen session, pin/maximize toggles, submit, cancel, clear, settings open). Keydown handler passes both event.key and event.code to router; default/propagation prevention is conditional on handler result. Backspace logic is simplified by removing pending-request and double-backspace cancellation. New test suite validates shortcut routing via KeyboardEvent objects.
Session reopen flow in request and view layers
apps/desktop/src/views/SearchView/composables/useSearchRequest.ts, apps/desktop/src/views/SearchView/index.vue, apps/desktop/tests/composables/SearchView/useSearchRequest.test.ts
Request flow adds lastClosedSessionId tracking; when a session is cleared, its id is saved before state cleanup. New reopenLastClosedSession() method detects missing/current sessions, attempts reopen via openSession, clears metadata on success/not-found, and rethrows other errors. openSession returns immediate descriptor when reopening current session and defers pending-request/draft cleanup until after session load. SearchView wires reopenLastClosedSession handler into keyboard actions; when draft exists focuses input, otherwise closes quick search and attempts to reopen. Tests verify session edge cases and reopen semantics.
Settings General shortcut editor and UI tests
apps/desktop/src/views/SettingsView/components/General/index.vue, apps/desktop/src/views/SettingsView/components/General/SearchShortcutSettings.vue, apps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.ts, apps/desktop/tests/SettingsView/general-language.test.ts
General settings refactors global shortcut UI from suggestions to preset-driven CustomSelect dropdown; opening menu starts capture, selection/completion saves via async save path. New findGlobalShortcutSearchConflict() validates global shortcut does not duplicate search keybindings; registration blocks and errors on conflict. Registration error handling recognizes platform-specific variants. New SearchShortcutSettings component renders grouped search shortcuts with configurable capture/blur save, validation (modifiers/reserved/conflicts), clear/restore icon actions, and fixed shortcut read-only rows. Tests cover preset saving, global/search conflict detection, key/code capture, validation failures, and action buttons.
Font reinjection guard and cache ignore updates
apps/desktop/src/utils/font.ts, eslint.config.js
Font loading tracks whether non-refresh injection occurred; when loadFontFace finds an existing promise but the injected style is missing, it reinjects instead of reusing the stale promise. ESLint ignores expand to include .vite-cache and **/.vite-cache patterns.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • hiqiancheng

Poem

🐰 With shortcuts configured from search to the screen,
Each key finds its action, a keybinding serene,
Sessions reopen, windows dance at command,
Validation and conflicts held firmly in hand—
A complete refactor, from router to store,
Keyboard control like never before! 🎹

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.62% 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
Title check ✅ Passed The PR title 'feat(desktop): complete keybinding customization rollout' follows Conventional Commits standards, includes the 'feat' type with 'desktop' scope, and clearly summarizes the main change.
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.
Description check ✅ Passed The PR description is comprehensive and follows the template structure, including summary, related issues, AI disclosure, testing evidence, risk notes, and a completed checklist.

✏️ 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.

@github-actions github-actions Bot added area:frontend Frontend UI or view-layer changes area:database Schema, persistence, or migration changes labels Jun 8, 2026
@velga111 velga111 marked this pull request as ready for review June 8, 2026 15:34
@coderabbitai coderabbitai Bot requested a review from hiqiancheng June 8, 2026 15:36

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 55d11ac and 82e32ed.

📒 Files selected for processing (25)
  • .prettierignore
  • apps/desktop/src/components/CustomSelect.vue
  • apps/desktop/src/components/appIconMap.ts
  • apps/desktop/src/components/ui/select/SelectContent.vue
  • apps/desktop/src/config/searchKeybindings.ts
  • apps/desktop/src/database/schema.ts
  • apps/desktop/src/i18n/messages.ts
  • apps/desktop/src/services/EventService/types.ts
  • apps/desktop/src/stores/settings.ts
  • apps/desktop/src/utils/font.ts
  • apps/desktop/src/utils/shortcuts.ts
  • apps/desktop/src/views/SearchView/composables/interaction/useSearchKeyboardRouter.ts
  • apps/desktop/src/views/SearchView/composables/searchInteraction.ts
  • apps/desktop/src/views/SearchView/composables/useSearchRequest.ts
  • apps/desktop/src/views/SearchView/index.vue
  • apps/desktop/src/views/SettingsView/components/General/index.vue
  • apps/desktop/tests/SettingsView/general-language.test.ts
  • apps/desktop/tests/composables/SearchView/searchInteraction.test.ts
  • apps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.ts
  • apps/desktop/tests/composables/SearchView/useSearchRequest.test.ts
  • apps/desktop/tests/config/searchKeybindings.test.ts
  • apps/desktop/tests/stores/settings-keybindings.test.ts
  • apps/desktop/tests/utils/shortcuts.test.ts
  • apps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.ts
  • eslint.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

Comment thread apps/desktop/src/config/searchKeybindings.ts Outdated
Comment thread apps/desktop/src/stores/settings.ts Outdated
Comment thread apps/desktop/src/utils/shortcuts.ts
Comment thread apps/desktop/src/views/SearchView/composables/searchInteraction.ts
Comment thread apps/desktop/src/views/SearchView/composables/useSearchRequest.ts Outdated
Comment thread apps/desktop/src/views/SettingsView/components/General/index.vue Outdated
@coderabbitai coderabbitai Bot added area:ci CI, automation, or repository workflow changes area:agent-service AgentService and conversation runtime changes labels Jun 8, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 win

Add a keydown-handler test for the new settings action.

This suite doesn’t yet assert the new 'search.settings.open' branch in createSearchKeydownHandler (Line 804 in apps/desktop/src/views/SearchView/composables/searchInteraction.ts). Add a Ctrl+,/Cmd+, case that verifies openSettingsWindow is 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

📥 Commits

Reviewing files that changed from the base of the PR and between b62fd54 and 7d9f696.

📒 Files selected for processing (11)
  • apps/desktop/src/config/searchKeybindings.ts
  • apps/desktop/src/i18n/messages.ts
  • apps/desktop/src/utils/shortcuts.ts
  • apps/desktop/src/views/SearchView/composables/searchInteraction.ts
  • apps/desktop/src/views/SearchView/index.vue
  • apps/desktop/src/views/SettingsView/components/General/index.vue
  • apps/desktop/tests/composables/SearchView/searchInteraction.test.ts
  • apps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.ts
  • apps/desktop/tests/config/searchKeybindings.test.ts
  • apps/desktop/tests/stores/settings-keybindings.test.ts
  • apps/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!

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 9, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 win

Build 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 searchKeybindings from createDefaultSearchKeybindings() 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 win

Reserved-shortcut validation now blocks a supported reopen binding.

This unconditional check rejects every reserved chord, but the search keyboard router still supports search.session.reopenLastClosed with Mod+Up. Because resetSearchShortcut() also flows through saveSearchShortcut(), 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7d9f696 and 557bf54.

📒 Files selected for processing (12)
  • apps/desktop/src/config/searchKeybindings.ts
  • apps/desktop/src/utils/shortcuts.ts
  • apps/desktop/src/views/SearchView/composables/interaction/useSearchKeyboardRouter.ts
  • apps/desktop/src/views/SearchView/composables/useSearchRequest.ts
  • apps/desktop/src/views/SearchView/index.vue
  • apps/desktop/src/views/SettingsView/components/General/index.vue
  • apps/desktop/tests/composables/SearchView/searchInteraction.test.ts
  • apps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.ts
  • apps/desktop/tests/composables/SearchView/useSearchRequest.test.ts
  • apps/desktop/tests/config/searchKeybindings.test.ts
  • apps/desktop/tests/utils/shortcuts.test.ts
  • apps/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!

Comment thread apps/desktop/src/views/SearchView/composables/useSearchRequest.ts
Comment thread apps/desktop/src/views/SearchView/index.vue
velga111 and others added 30 commits June 12, 2026 00:53
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent-service AgentService and conversation runtime changes area:ci CI, automation, or repository workflow changes area:database Schema, persistence, or migration changes area:frontend Frontend UI or view-layer changes area:tauri Tauri shell or desktop runtime changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants