Skip to content

feat(desktop): complete keybinding customization rollout#443

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

feat(desktop): complete keybinding customization rollout#443
velga111 wants to merge 27 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

  • Adds the keybinding customization rollout for desktop search shortcuts, including configurable search actions and Settings UI coverage.
  • Updates desktop managed provider/auth handling, widget/web-fetch runtime behavior, font loading, release workflow checks, and site assets touched by the rollout.
  • Keeps reopen-last-closed-session state in SearchView memory only, so failed generic reopen attempts preserve the retry target without adding persisted session-restore settings.
  • Adds unit and CI-policy coverage for the changed desktop behavior.

Related issue or RFC

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

Also references #40 for SearchView shortcut behavior.

The review feedback about persisted last_closed_session_id / last_active_session_id state has been addressed by removing that persisted recovery contract. The remaining reopen-last-closed-session behavior is scoped to the active SearchView runtime state.

AI assistance disclosure

  • Tool(s) used: OpenAI Codex.
  • Scope of assistance: codebase inspection, local validation, PR preparation, review-feedback fixes, and a small font-loader race fix found while running pnpm test:pr.
  • Human review or rewrite performed: pending final maintainer review after the review-feedback fixes.
  • Architecture or boundary impact: this PR touches AgentService, tool execution, provider/runtime data, release workflows, and desktop startup/settings paths. The session restore persistence work called out in review has been removed from this PR.

Testing evidence

pnpm --filter @touchai/desktop exec vitest run --configLoader runner tests/utils/font.test.ts
# passed: 1 test file, 12 tests

pnpm test:pr
# passed: desktop check, unit tests, Rust tests, coverage, and site build
# note: ESLint emitted 20 warnings; Rust check/test emitted existing unused import/dead-code warnings for try_lock_mutex

TDD: not fully confirmed for the whole branch; tests were added and the font-loader regression was fixed after a failing local test run.

Additional local validation after review fixes:

pnpm test:unit -- tests/composables/SearchView/useSearchRequest.test.ts
pnpm test:unit -- tests/stores/settings-keybindings.test.ts
pnpm test:unit -- tests/views/SettingsView/settingsGeneralComponent.test.ts
pnpm test:typecheck
pnpm exec eslint src/views/SearchView/composables/useSearchRequest.ts src/views/SearchView/index.vue src/stores/settings.ts src/services/EventService/types.ts tests/composables/SearchView/useSearchRequest.test.ts tests/stores/settings-keybindings.test.ts tests/views/SettingsView/settingsGeneralComponent.test.ts

pnpm --filter @touchai/desktop exec vitest run --configLoader runner tests/composables/SearchView/useSearchRequest.test.ts tests/config/searchKeybindings.test.ts tests/stores/settings-keybindings.test.ts tests/composables/SearchView/searchInteraction.test.ts tests/composables/SearchView/useSearchKeyboardRouter.test.ts
# passed: 5 test files, 56 tests

Risk notes

  • AgentService, runtime, MCP, or schema impact: touches AgentService execution/provider/session paths and built-in tool runtime/web-fetch behavior.
  • database baseline or migration impact: updates runtime seed/query behavior; no persisted reopen-last-closed session ID or last-active session ID setting remains in this PR.
  • release or packaging impact: touches release workflows, CODEOWNERS, desktop changelog/package metadata, Tauri config, bundled assets, and site favicon/landing files.

Screenshots or recordings

No updated screenshots for these review fixes.

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! 🎹

✨ 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 velga111 marked this pull request as draft June 9, 2026 12:48
@velga111 velga111 marked this pull request as ready for review June 9, 2026 14:11
@velga111

velga111 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Review feedback has been addressed and pushed in \2c44d9dd\.

Key updates:

  • removed persisted reopen-session recovery state; \lastClosedSessionId\ is now SearchView runtime state only
  • generic reopen failures keep the retry target; only missing-session errors clear it
  • reopen shortcut now hides popup surfaces before refocusing when draft/attachments are present
  • PR description and risk notes have been updated to match the current diff
  • targeted validation passed: \5 test files, 56 tests\

Ready for re-review. @coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

@velga111 Thanks for the detailed update summary! The changes look well-considered — particularly moving lastClosedSessionId to runtime state only (avoiding cross-session persistence complexity) and the differentiated error handling for missing-session vs. generic reopen failures. Kicking off the re-review now.

(ミ・ω・ミ)

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/TouchAI-org/TouchAI/issues/comments/4650012004","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- review_stack_entry_start -->\n\n[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/TouchAI-org/TouchAI/pull/443?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)\n\n<!-- review_stack_entry_end -->\n<!-- This is an auto-generated comment: review in progress by coderabbit.ai -->\n\n> [!NOTE]\n> Currently processing new changes in this PR. This may take a few minutes, please wait...\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: Organization UI\n> \n> **Review profile**: ASSERTIVE\n> \n> **Plan**: Pro Plus\n> \n> **Run ID**: `8940c7ff-a5a3-45d6-ba04-6ca51341b776`\n> \n> </details>\n> \n> <details>\n> <summary>📥 Commits</summary>\n> \n> Reviewing files that changed from the base of the PR and between 7d9f6963cd0691401c1c72c21cffc4a29f2c1c1f and 2c44d9dda7e73c760ff29be64ab1c7558bb4599d.\n> \n> </details>\n> \n> <details>\n> <summary>📒 Files selected for processing (16)</summary>\n> \n> * `apps/desktop/src/config/searchKeybindings.ts`\n> * `apps/desktop/src/i18n/messages.ts`\n> * `apps/desktop/src/services/EventService/types.ts`\n> * `apps/desktop/src/stores/settings.ts`\n> * `apps/desktop/src/utils/shortcuts.ts`\n> * `apps/desktop/src/views/SearchView/composables/interaction/useSearchKeyboardRouter.ts`\n> * `apps/desktop/src/views/SearchView/composables/useSearchRequest.ts`\n> * `apps/desktop/src/views/SearchView/index.vue`\n> * `apps/desktop/src/views/SettingsView/components/General/index.vue`\n> * `apps/desktop/tests/composables/SearchView/searchInteraction.test.ts`\n> * `apps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.ts`\n> * `apps/desktop/tests/composables/SearchView/useSearchRequest.test.ts`\n> * `apps/desktop/tests/config/searchKeybindings.test.ts`\n> * `apps/desktop/tests/stores/settings-keybindings.test.ts`\n> * `apps/desktop/tests/utils/shortcuts.test.ts`\n> * `apps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.ts`\n> \n> </details>\n> \n> ```ascii\n>  ___________________________________________________________________\n> < Your commit message says 'quick fix'. That's how folklore starts. >\n>  -------------------------------------------------------------------\n>   \\\n>    \\   \\\n>         \\ /\\\n>         ( )\n>       .( o ).\n> ```\n\n<!-- end of auto-generated comment: review in progress by coderabbit.ai -->\n\n<!-- This is an auto-generated comment: review paused by coderabbit.ai -->\n\n> [!NOTE]\n> ## Reviews paused\n> \n> 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.\n> \n> Use the following commands to manage reviews:\n> - `@coderabbitai resume` to resume automatic reviews.\n> - `@coderabbitai review` to trigger a single review.\n> \n> Use the checkboxes below for quick actions:\n> - [ ] <!-- {\"checkboxId\": \"7f6cc2e2-2e4e-497a-8c31-c9e4573e93d1\"} --> ▶️ Resume reviews\n> - [ ] <!-- {\"checkboxId\": \"e9bb8d72-00e8-4f67-9cb2-caf3b22574fe\"} --> 🔍 Trigger review\n\n<!-- end of auto-generated comment: review paused by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Walkthrough\n\nAdds configurable search keybindings and shortcut utilities, persists keybindings and session metadata, replaces primary-shortcut routing with action-based keybinding routing, updates Settings UI for shortcut editing and validation, integrates reopen-last-closed session flow, adjusts select primitives, improves font-load logic, and expands tests and tooling ignores.\n\n## Changes\n\n**Search Keybindings Configuration & Session Management**\n\n|Layer / File(s)|Summary|\n|---|---|\n|**Keybinding types, normalization, and shortcut utilities** <br> `apps/desktop/src/config/searchKeybindings.ts`, `apps/desktop/src/utils/shortcuts.ts`, `apps/desktop/src/i18n/messages.ts`, tests under `apps/desktop/tests/config/*`, `apps/desktop/tests/utils/*`|Adds search keybinding action definitions/types, default builders, a normalizer enforcing capture/modifier rules, platform-aware shortcut normalization/matching/capture/formatting, and i18n keys and tests for search shortcut UI and errors.|\n|**Settings store, schema, and events** <br> `apps/desktop/src/stores/settings.ts`, `apps/desktop/src/database/schema.ts`, `apps/desktop/src/services/EventService/types.ts`, tests under `apps/desktop/tests/stores/*`|Persist and normalize `search_keybindings`, `last_closed_session_id`, `last_active_session_id`; add getters and update methods; serialize/deserialize for DB and include values in settings events; tests validate defaults, merges, parsing, persistence, and emitted events.|\n|**Select primitives and icon map** <br> `apps/desktop/src/components/CustomSelect.vue`, `apps/desktop/src/components/ui/select/SelectContent.vue`, `apps/desktop/src/components/appIconMap.ts`|CustomSelect gains controlled/uncontrolled open support, trigger passthroughs, display override, test-id forwards, focus/blur forwarding and portal disable; SelectContent merges `$attrs` into content props and honors `disablePortal`; `undo` icon added to appIconMap.|\n|**Keyboard routing & interaction** <br> `apps/desktop/src/views/SearchView/composables/interaction/useSearchKeyboardRouter.ts`, `apps/desktop/src/views/SearchView/composables/searchInteraction.ts`|Router now resolves configured actions from keybindings (accepts `code`), expands quick-search controls and cursor-aware history navigation, and dispatches action IDs; interaction layer builds router from store keybindings and handles discrete action callbacks.|\n|**Session reopen & search request flow** <br> `apps/desktop/src/views/SearchView/composables/useSearchRequest.ts`, `apps/desktop/src/views/SearchView/index.vue`|Persist last-active on open and last-closed on close; add `reopenLastClosedSession()` with prioritized candidates and clearing semantics; wire reopen handler into SearchView with UI syncing and remeasure steps.|\n|**Settings General UI for shortcut editing** <br> `apps/desktop/src/views/SettingsView/components/General/index.vue`, `apps/desktop/tests/views/SettingsView/*`|Redesigned UI: global preset `CustomSelect`, grouped/typed search shortcut rows with capture lifecycle, validation (modifier, reserved, conflicts), clear/restore actions, sync with store, and expanded component tests and stubs.|\n|**Tests, font logic, and tooling ignores** <br> `apps/desktop/tests/*`, `apps/desktop/src/utils/font.ts`, `.prettierignore`, `eslint.config.js`|Extensive test additions/adjustments for keybinding config, shortcut utils, router/handler, request flow, settings store, and Settings UI; font loader reinjection handling improved; Prettier/ESLint ignores updated for `.vite-cache`.|\n\n## Estimated code review effort\n\n🎯 4 (Complex) | ⏱️ ~60 minutes\n\n## Possibly related PRs\n\n- [TouchAI-org/TouchAI#405](https://github.com/TouchAI-org/TouchAI/pull/405): Both PRs modify `apps/desktop/src/utils/font.ts`, updating font-loading/injection logic.\n- [TouchAI-org/TouchAI#401](https://github.com/TouchAI-org/TouchAI/pull/401): Also touches `apps/desktop/src/utils/font.ts` with related injection/retry changes.\n\n## Suggested reviewers\n\n- hiqiancheng\n\n## Poem\n\n> 🐰 I tap the keys and tidy threads,  \n> > Mod and F dance above my beds.  \n> > Conflicts chased and defaults found,  \n> > Sessions hop back safe and sound.  \n> > Hooray — shortcuts snug and well-bounded!\n\n</details>\n\n<!-- walkthrough_end -->\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 4 | ❌ 1</summary>\n\n### ❌ Failed checks (1 warning)\n\n|     Check name     | Status     | Explanation                                                                          | Resolution                                                                         |\n| :----------------: | :--------- | :----------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |\n\n<details>\n<summary>✅ Passed checks (4 passed)</summary>\n\n|         Check name         | Status   | Explanation                                                                                                                                                                      |\n| :------------------------: | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n|         Title check        | ✅ Passed | The PR title follows Conventional Commits format with 'feat(desktop):' prefix, clearly summarizes the main change (keybinding customization rollout), is concise and in English. |\n|     Linked Issues check    | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                                                         |\n| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                                                         |\n|      Description check     | ✅ Passed | The PR description is comprehensive and includes all required sections with substantial detail and context.                                                                      |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-4660668172\"} -->   Create PR with unit tests\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n<!-- tips_start -->\n\n---\n\n\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"signal":{},"retries":3,"retryAfter":16}}}

@velga111

velga111 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

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

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

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

🤖 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 141-147: hasShortcutConflict is comparing the incoming shortcut
against normalized[definition.id] which still contains defaults for actions not
yet processed, so valid multi-action remaps (like swaps) are flagged as
conflicts; fix by using a deterministic two-pass approach: iterate
SEARCH_KEYBINDING_DEFINITIONS in a stable order and build a map/set of final
shortcuts applying overrides from normalized when an action has already been
assigned (first-wins), then in hasShortcutConflict compare the candidate
shortcut only against that final-assigned set (and skip comparing against the
actionId itself); reference SEARCH_KEYBINDING_DEFINITIONS and normalized and
update hasShortcutConflict to consult the computed final assignment rather than
raw normalized defaults.
🪄 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: 75d5236c-af8c-477e-8bf8-e89ac044219d

📥 Commits

Reviewing files that changed from the base of the PR and between 2c44d9d and a17df8f.

📒 Files selected for processing (7)
  • apps/desktop/src/config/searchKeybindings.ts
  • apps/desktop/src/views/SearchView/composables/interaction/useSearchKeyboardRouter.ts
  • 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/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). (2)
  • GitHub Check: CodeQL (rust)
  • GitHub Check: Desktop E2E Smoke (Windows)
🔇 Additional comments (7)
apps/desktop/src/views/SettingsView/components/General/index.vue (2)

508-614: LGTM!


639-666: LGTM!

apps/desktop/tests/views/SettingsView/settingsGeneralComponent.test.ts (1)

380-402: LGTM!

apps/desktop/tests/config/searchKeybindings.test.ts (1)

86-100: LGTM!

apps/desktop/src/views/SearchView/composables/interaction/useSearchKeyboardRouter.ts (1)

175-182: LGTM!

apps/desktop/tests/composables/SearchView/searchInteraction.test.ts (1)

416-424: LGTM!

Also applies to: 468-472

apps/desktop/tests/composables/SearchView/useSearchKeyboardRouter.test.ts (1)

275-285: LGTM!

Comment thread apps/desktop/src/config/searchKeybindings.ts Outdated

@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

🤖 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 182-185: The code normalizes definition.defaultShortcut into
fallback for availability checks but then stores the un-normalized
definition.defaultShortcut into result, creating a mismatch between
usedShortcuts and stored values; update the assignment in the block that uses
normalizeLocalShortcutString so that result[definition.id] = fallback (or
alternatively normalize all defaults at module initialization) and ensure
usedShortcuts, result, and any later conflict checks consistently use
normalizeLocalShortcutString.
- Line 187: The current assignment at result[definition.id] =
definition.allowDisable ? null : definition.defaultShortcut can produce
duplicate bindings because it doesn't consult or update usedShortcuts; change
the logic in the searchKeybindings generation code so that when a desired
shortcut is taken you treat an unavailable fallback as irreconcilable and set
result[definition.id] = null (and do not add the default to usedShortcuts), or
if you decide to honor allowDisable: false instead, add
definition.defaultShortcut to usedShortcuts when assigning it so subsequent
iterations see it as used; update the branch that references
definition.allowDisable, result, definition.defaultShortcut and usedShortcuts
accordingly.
🪄 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: 3a92a2e4-d49e-408c-b631-f8f1c11993a3

📥 Commits

Reviewing files that changed from the base of the PR and between a17df8f and ba0f67b.

📒 Files selected for processing (1)
  • apps/desktop/src/config/searchKeybindings.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). (6)
  • GitHub Check: Frontend Tests
  • GitHub Check: Frontend Quality
  • GitHub Check: Rust Checks
  • GitHub Check: Desktop E2E Smoke (Windows)
  • GitHub Check: CodeQL (javascript-typescript)
  • GitHub Check: CodeQL (rust)
🔇 Additional comments (3)
apps/desktop/src/config/searchKeybindings.ts (3)

1-31: LGTM!


33-98: LGTM!


100-125: LGTM!

Comment thread apps/desktop/src/config/searchKeybindings.ts Outdated
Comment thread apps/desktop/src/config/searchKeybindings.ts Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 9, 2026
…stomization

# Conflicts:
#	apps/desktop/src/components/CustomSelect.vue
#	apps/desktop/src/database/schema.ts
#	apps/desktop/src/services/EventService/types.ts
#	apps/desktop/src/stores/settings.ts

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

🤖 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 @.prettierignore:
- Line 68: The ignore entry "apps/desktop/.vite-cache/" is redundant because the
existing pattern ".vite-cache/" already covers it; remove the specific
"apps/desktop/.vite-cache/" line from .prettierignore so only the generic
".vite-cache/" pattern remains, leaving no behavioral change but reducing
duplication.

In `@apps/desktop/src/components/CustomSelect.vue`:
- Around line 12-13: The import of useAttrs is duplicated; replace the two
separate Vue imports by consolidating computed, ref and useAttrs into a single
import from 'vue' (i.e., merge the separate import lines so the single import
includes computed, ref, and useAttrs) to remove the duplicate import statement.

In `@apps/desktop/src/stores/settings.ts`:
- Around line 80-92: The Promise.allSettled call over
GENERAL_SETTING_DEFINITIONS currently ignores rejected results and silently
swallows failures from setSetting/serializeSetting/persistDefaultIfMissing;
change this to inspect the returned results after awaiting Promise.allSettled,
collect any entries with status === "rejected" (use the associated
definition.key or index from GENERAL_SETTING_DEFINITIONS/settingRows to identify
which setting failed), and then surface the failure by logging the error and
rethrowing or aggregating and throwing a single Error so callers see the write
failure (alternatively use Promise.all if you want fail-fast behavior); ensure
you reference setSetting, serializeSetting and persistDefaultIfMissing when
mapping failures to keys so the error message clearly identifies which setting
write failed.

In `@apps/desktop/src/views/SettingsView/components/General/index.vue`:
- Line 333: The conditional that checks for native shortcut registration errors
uses mojibake Chinese strings ('宸叉敞鍐?' / '鏃犳晥'); update the check in the
SettingsView General component so errorStr is matched against the proper Chinese
fallbacks used elsewhere (e.g., '已注册' and '无效') instead of the garbled
strings—locate the if that references errorStr (the one currently testing for
'already registered' || '宸叉敞鍐?') and replace the mojibake literals with the
correct '已注册' and '无效' matches.

In
`@apps/desktop/src/views/SettingsView/components/General/SearchShortcutSettings.vue`:
- Around line 570-572: The template's `@click` handler is split across lines
causing a Prettier violation; collapse the handler into a single line so the
element uses `@click`="handleSearchShortcutIconAction(row.id, row.shortcutAction)"
(reference the handleSearchShortcutIconAction call and the row.id /
row.shortcutAction args) and ensure there are no extra line breaks or trailing
spaces around the attribute.
🪄 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: bfa8d44c-f3c5-4f3f-9b41-572197c1f375

📥 Commits

Reviewing files that changed from the base of the PR and between 5bd2979 and bdf3b9e.

📒 Files selected for processing (10)
  • .prettierignore
  • apps/desktop/src/components/CustomSelect.vue
  • apps/desktop/src/components/appIconMap.ts
  • apps/desktop/src/i18n/messages.ts
  • apps/desktop/src/stores/setting/index.ts
  • apps/desktop/src/stores/settings.ts
  • apps/desktop/src/views/SettingsView/components/General/SearchShortcutSettings.vue
  • apps/desktop/src/views/SettingsView/components/General/index.vue
  • apps/desktop/tests/stores/setting.test.ts
  • eslint.config.js
💤 Files with no reviewable changes (2)
  • eslint.config.js
  • apps/desktop/tests/stores/setting.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: Rust Checks
  • GitHub Check: Frontend Tests
  • GitHub Check: CodeQL (rust)
🧰 Additional context used
🪛 ESLint
apps/desktop/src/views/SettingsView/components/General/SearchShortcutSettings.vue

[error] 570-572: Replace ⏎····································handleSearchShortcutIconAction(row.id,·row.shortcutAction)⏎································ with handleSearchShortcutIconAction(row.id,·row.shortcutAction)

(prettier/prettier)

🪛 GitHub Check: Frontend Quality
apps/desktop/src/views/SettingsView/components/General/SearchShortcutSettings.vue

[failure] 570-570:
Replace ⏎····································handleSearchShortcutIconAction(row.id,·row.shortcutAction)⏎································ with handleSearchShortcutIconAction(row.id,·row.shortcutAction)

🔇 Additional comments (7)
.prettierignore (2)

65-68: AI summary inconsistency.

The AI summary states ".vite-cache/ [was added] to the ignore patterns list," but .vite-cache/ already exists at line 8. The actual change is adding the comment section and apps/desktop/.vite-cache/ (along with apps/desktop/data/).


67-67: apps/desktop/data/ ignore looks like standard runtime-data hygiene
.prettierignore already labels it as “Local app runtime data and Vite cache”, and the repo also ignores apps/desktop/data/ in .gitignore and eslint.config.js (no repo code references found). If PR scope is strictly keybinding-only, confirm whether this ignore-only maintenance should be split out.

apps/desktop/src/components/CustomSelect.vue (1)

17-18: LGTM!

Also applies to: 33-39, 44-46, 63-74, 97-100, 103-161

apps/desktop/src/components/appIconMap.ts (1)

22-22: LGTM!

Also applies to: 43-43, 66-67, 71-71, 89-89

apps/desktop/src/views/SettingsView/components/General/index.vue (1)

23-27: LGTM!

Also applies to: 96-106, 120-159, 170-172, 186-188, 221-229, 564-564

apps/desktop/src/views/SettingsView/components/General/SearchShortcutSettings.vue (2)

1-72: LGTM!

Also applies to: 119-238, 240-347, 372-503, 505-569, 573-585


74-117: LGTM!

@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

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

🤖 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 @.prettierignore:
- Line 68: The ignore entry "apps/desktop/.vite-cache/" is redundant because the
existing pattern ".vite-cache/" already covers it; remove the specific
"apps/desktop/.vite-cache/" line from .prettierignore so only the generic
".vite-cache/" pattern remains, leaving no behavioral change but reducing
duplication.

In `@apps/desktop/src/components/CustomSelect.vue`:
- Around line 12-13: The import of useAttrs is duplicated; replace the two
separate Vue imports by consolidating computed, ref and useAttrs into a single
import from 'vue' (i.e., merge the separate import lines so the single import
includes computed, ref, and useAttrs) to remove the duplicate import statement.

In `@apps/desktop/src/stores/settings.ts`:
- Around line 80-92: The Promise.allSettled call over
GENERAL_SETTING_DEFINITIONS currently ignores rejected results and silently
swallows failures from setSetting/serializeSetting/persistDefaultIfMissing;
change this to inspect the returned results after awaiting Promise.allSettled,
collect any entries with status === "rejected" (use the associated
definition.key or index from GENERAL_SETTING_DEFINITIONS/settingRows to identify
which setting failed), and then surface the failure by logging the error and
rethrowing or aggregating and throwing a single Error so callers see the write
failure (alternatively use Promise.all if you want fail-fast behavior); ensure
you reference setSetting, serializeSetting and persistDefaultIfMissing when
mapping failures to keys so the error message clearly identifies which setting
write failed.

In `@apps/desktop/src/views/SettingsView/components/General/index.vue`:
- Line 333: The conditional that checks for native shortcut registration errors
uses mojibake Chinese strings ('宸叉敞鍐?' / '鏃犳晥'); update the check in the
SettingsView General component so errorStr is matched against the proper Chinese
fallbacks used elsewhere (e.g., '已注册' and '无效') instead of the garbled
strings—locate the if that references errorStr (the one currently testing for
'already registered' || '宸叉敞鍐?') and replace the mojibake literals with the
correct '已注册' and '无效' matches.

In
`@apps/desktop/src/views/SettingsView/components/General/SearchShortcutSettings.vue`:
- Around line 570-572: The template's `@click` handler is split across lines
causing a Prettier violation; collapse the handler into a single line so the
element uses `@click`="handleSearchShortcutIconAction(row.id, row.shortcutAction)"
(reference the handleSearchShortcutIconAction call and the row.id /
row.shortcutAction args) and ensure there are no extra line breaks or trailing
spaces around the attribute.
🪄 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: bfa8d44c-f3c5-4f3f-9b41-572197c1f375

📥 Commits

Reviewing files that changed from the base of the PR and between 5bd2979 and bdf3b9e.

📒 Files selected for processing (10)
  • .prettierignore
  • apps/desktop/src/components/CustomSelect.vue
  • apps/desktop/src/components/appIconMap.ts
  • apps/desktop/src/i18n/messages.ts
  • apps/desktop/src/stores/setting/index.ts
  • apps/desktop/src/stores/settings.ts
  • apps/desktop/src/views/SettingsView/components/General/SearchShortcutSettings.vue
  • apps/desktop/src/views/SettingsView/components/General/index.vue
  • apps/desktop/tests/stores/setting.test.ts
  • eslint.config.js
💤 Files with no reviewable changes (2)
  • eslint.config.js
  • apps/desktop/tests/stores/setting.test.ts
📜 Review details
🔇 Additional comments (7)
.prettierignore (2)

65-68: AI summary inconsistency.

The AI summary states ".vite-cache/ [was added] to the ignore patterns list," but .vite-cache/ already exists at line 8. The actual change is adding the comment section and apps/desktop/.vite-cache/ (along with apps/desktop/data/).


67-67: apps/desktop/data/ ignore looks like standard runtime-data hygiene
.prettierignore already labels it as “Local app runtime data and Vite cache”, and the repo also ignores apps/desktop/data/ in .gitignore and eslint.config.js (no repo code references found). If PR scope is strictly keybinding-only, confirm whether this ignore-only maintenance should be split out.

apps/desktop/src/components/CustomSelect.vue (1)

17-18: LGTM!

Also applies to: 33-39, 44-46, 63-74, 97-100, 103-161

apps/desktop/src/components/appIconMap.ts (1)

22-22: LGTM!

Also applies to: 43-43, 66-67, 71-71, 89-89

apps/desktop/src/views/SettingsView/components/General/index.vue (1)

23-27: LGTM!

Also applies to: 96-106, 120-159, 170-172, 186-188, 221-229, 564-564

apps/desktop/src/views/SettingsView/components/General/SearchShortcutSettings.vue (2)

1-72: LGTM!

Also applies to: 119-238, 240-347, 372-503, 505-569, 573-585


74-117: LGTM!

🛑 Comments failed to post (5)
.prettierignore (1)

68-68: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Redundant ignore pattern—already covered by line 8.

The pattern .vite-cache/ at line 8 matches .vite-cache/ directories at any depth (gitignore glob semantics), so apps/desktop/.vite-cache/ is unnecessary.

♻️ Simplify by removing the redundant entry
 # Local app runtime data and Vite cache
 apps/desktop/data/
-apps/desktop/.vite-cache/
📝 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.

# Local app runtime data and Vite cache
apps/desktop/data/
🤖 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 @.prettierignore at line 68, The ignore entry "apps/desktop/.vite-cache/" is
redundant because the existing pattern ".vite-cache/" already covers it; remove
the specific "apps/desktop/.vite-cache/" line from .prettierignore so only the
generic ".vite-cache/" pattern remains, leaving no behavioral change but
reducing duplication.
apps/desktop/src/components/CustomSelect.vue (1)

12-13: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consolidate duplicate Vue imports.

useAttrs is imported separately on line 13, but it should be merged with the existing Vue import on line 12.

♻️ Suggested fix
-    import { computed, ref } from 'vue';
-    import { useAttrs } from 'vue';
+    import { computed, ref, useAttrs } from 'vue';
📝 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.

    import { computed, ref, useAttrs } from 'vue';
🤖 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/components/CustomSelect.vue` around lines 12 - 13, The
import of useAttrs is duplicated; replace the two separate Vue imports by
consolidating computed, ref and useAttrs into a single import from 'vue' (i.e.,
merge the separate import lines so the single import includes computed, ref, and
useAttrs) to remove the duplicate import statement.
apps/desktop/src/stores/settings.ts (1)

80-92: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle failed rewrite/default persistence writes instead of swallowing them.

Line [80] uses Promise.allSettled(...) without checking rejected results, so failed setSetting(...) calls are silently ignored. That leaves normalized/default values unapplied in storage and can repeatedly reload stale/corrupt persisted data on next startup.

Suggested fix
-            await Promise.allSettled(
+            const persistenceResults = await Promise.allSettled(
                 GENERAL_SETTING_DEFINITIONS.map((definition, index) => {
                     const persistedValue = settingRows[index] ?? null;
                     if (definition.shouldRewritePersisted?.(persistedValue)) {
                         return setSetting({
                             key: definition.key,
                             value: serializeSetting(definition.key),
                         });
                     }

                     return persistDefaultIfMissing(definition.key, persistedValue);
                 })
             );
+            const failedWrites = persistenceResults.filter(
+                (result): result is PromiseRejectedResult => result.status === 'rejected'
+            );
+            if (failedWrites.length > 0) {
+                throw new Error(
+                    `Failed to persist ${failedWrites.length} general setting rewrite/default value(s)`
+                );
+            }
🤖 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/stores/settings.ts` around lines 80 - 92, The
Promise.allSettled call over GENERAL_SETTING_DEFINITIONS currently ignores
rejected results and silently swallows failures from
setSetting/serializeSetting/persistDefaultIfMissing; change this to inspect the
returned results after awaiting Promise.allSettled, collect any entries with
status === "rejected" (use the associated definition.key or index from
GENERAL_SETTING_DEFINITIONS/settingRows to identify which setting failed), and
then surface the failure by logging the error and rethrowing or aggregating and
throwing a single Error so callers see the write failure (alternatively use
Promise.all if you want fail-fast behavior); ensure you reference setSetting,
serializeSetting and persistDefaultIfMissing when mapping failures to keys so
the error message clearly identifies which setting write failed.
apps/desktop/src/views/SettingsView/components/General/index.vue (1)

333-333: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify what Chinese strings the native API actually returns for these error conditions
# and check for other potential mojibake in the codebase

echo "=== Searching for similar mojibake patterns in codebase ==="
rg -n '宸叠敞|鏃犳晥|宸叉敞鍐' --type ts --type vue

echo ""
echo "=== Looking for correct Chinese error strings in native/backend code ==="
rg -n '已注册|无效' --type rust --type ts

echo ""
echo "=== Check if there are locale files with these error messages ==="
fd -e json -e ts messages | xargs rg -l 'registered|invalid' | head -5

Repository: TouchAI-org/TouchAI

Length of output: 4334


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/desktop/src/views/SettingsView/components/General/index.vue"

echo "=== Dump lines around 333 in ${FILE} ==="
nl -ba "$FILE" | sed -n '320,360p'

echo ""
echo "=== Dump lines around 343 in ${FILE} ==="
nl -ba "$FILE" | sed -n '338,350p'

echo ""
echo "=== Search for mojibake substrings using globs (no --type vue) ==="
rg -n '宸叠敞|鏃犳晥|宸叉敞鍐' -g '*.ts' -g '*.vue' || true

echo ""
echo "=== Search for the exact suspected substrings ==="
rg -n "宸叠敞鍐\\?|宸叉敞鍐\\?|鏃犳晥" -g '*.ts' -g '*.vue' || true

Repository: TouchAI-org/TouchAI

Length of output: 222


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/desktop/src/views/SettingsView/components/General/index.vue"

echo "=== Dump lines around 333 in ${FILE} (with line numbers) ==="
awk 'NR>=320 && NR<=360 {printf "%d\t%s\n", NR, $0}' "$FILE"

echo ""
echo "=== Dump lines around 343 in ${FILE} (with line numbers) ==="
awk 'NR>=338 && NR<=350 {printf "%d\t%s\n", NR, $0}' "$FILE"

echo ""
echo "=== Search for mojibake substrings using globs ==="
rg -n '宸叠敞|鏃犳晥|宸叉敞鍐' -g '*.ts' -g '*.vue' || true

echo ""
echo "=== Search for the exact suspected substrings ==="
rg -n "宸叠敞鍐\\?|宸叉敞鍐\\?|鏃犳晥" -g '*.ts' -g '*.vue' || true

Repository: TouchAI-org/TouchAI

Length of output: 3657


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Where error strings might originate (tauri/native side)
BASE="apps/desktop/src-tauri"

echo "=== Search native/tauri for mojibake substrings ==="
rg -n "宸叉敞鍐\\?|鏃犳晥" "$BASE" || true

echo ""
echo "=== Search native/tauri for likely intended Chinese strings ==="
rg -n "已注册|无效" "$BASE" || true

echo ""
echo "=== Search desktop SettingsView for where errorStr is built ==="
FILE="apps/desktop/src/views/SettingsView/components/General/index.vue"
rg -n "errorStr" "$FILE"

echo ""
echo "=== Show surrounding context for errorStr assignment (first occurrences) ==="
# Print a handful of nearby lines for the first few errorStr mentions
rg -n "errorStr" "$FILE" | head -n 20 | awk -F: '{print $1}' | sort -n | uniq | head -n 5 | while read -r line; do
  start=$((line-15)); end=$((line+15));
  echo "--- $FILE:${line} ---"
  sed -n "${start},${end}p" "$FILE"
done

Repository: TouchAI-org/TouchAI

Length of output: 8189


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for registerGlobalShortcut implementation ==="
rg -n "registerGlobalShortcut" apps/desktop/src apps/desktop/src-tauri || true

echo ""
echo "=== Search for shortcut.registerGlobalShortcut usage ==="
rg -n "shortcut\.registerGlobalShortcut" apps/desktop/src || true

echo ""
echo "=== Search for 'already registered' / 'invalid' in tauri shortcut code ==="
rg -n "already registered|invalid" apps/desktop/src-tauri || true

echo ""
echo "=== Search for tauri command names related to shortcut ==="
rg -n "shortcut" apps/desktop/src-tauri/src | head -n 80 || true

Repository: TouchAI-org/TouchAI

Length of output: 13960


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== apps/desktop/src/services/NativeService/shortcut.ts ==="
FILE="apps/desktop/src/services/NativeService/shortcut.ts"
wc -l "$FILE"
sed -n '1,200p' "$FILE"

echo ""
echo "=== apps/desktop/src-tauri/src/core/system/shortcut.rs (core bits) ==="
FILE2="apps/desktop/src-tauri/src/core/system/shortcut.rs"
wc -l "$FILE2"
# show around register_global_shortcut and parse_shortcut
rg -n "register_global_shortcut|map_err\\(|Failed to register shortcut|parse_shortcut" "$FILE2"
# print a focused range around likely locations
sed -n '1,140p' "$FILE2"
sed -n '140,260p' "$FILE2"

Repository: TouchAI-org/TouchAI

Length of output: 7265


🌐 Web query:

tauri-plugin-global-shortcut error message "already registered" "invalid"

💡 Result:

In Tauri’s plugin-global-shortcut, the error strings you mention typically come from two different failure modes: 1) “already registered” (often shown as “Hotkey already registered.”) This means the OS already has that global shortcut reserved (by another application or by your own app already registering it). Tauri cannot “hijack” an already-taken global hotkey, so your handler won’t trigger when the shortcut is already taken by another app. [1][2] What to do: - Pick a more unique shortcut to avoid collisions. [1] - Before registering, check whether it’s registered (by your application) using isRegistered; if it’s false, you can try registering again (but if another app has it, register will still not trigger your handler). [1] - If you’re re-registering within the same app lifecycle, call unregister/unregisterAll appropriately, but note that unregister_all behavior around “none registered” has had issues. [2] 2) “invalid” (e.g., “Uncaught (in promise) invalid args … missing required key shortcut”) This is usually an argument/format problem rather than an OS-collision problem: the plugin expects a shortcut string in the form “modifiers+key” (e.g., CmdOrControl+Q). If the call payload is wrong or missing the shortcut field, you can get “invalid args … missing required key shortcut”. [3][1] What to do: - Ensure you pass the shortcut strings as the plugin’s register expects (string or string[]), and that each item is a valid accelerator like “CommandOrControl+Shift+C”. [1][4] - If you receive “invalid accelerator” / parsing-related errors, ensure the key names match what Tauri accepts; invalid key names can cause parse failures. (Tauri has also had patches to avoid panics and return proper errors for invalid shortcuts.) [5] Quick checklist (most common root causes) - Are you trying to register the exact same shortcut while it’s still registered? -> collision, “Hotkey already registered.” [2][1] - Is another app likely using it already? -> collision, handler won’t fire. [1] - Are you passing malformed JS (e.g., wrong shape or missing shortcut string)? -> “invalid args … missing required key shortcut”. [3] - Are you using an unsupported key name/format? -> parsing/invalid accelerator. [6][5] If you paste the exact full error text and the JS call site (the register / unregister code plus the exact shortcut string), I can map it to which of the two categories above it is and suggest the precise fix.

Citations:


Fix mojibake shortcut error matching in SettingsView

apps/desktop/src/views/SettingsView/components/General/index.vue matches Chinese fallbacks '宸叉敞鍐?' and '鏃犳晥' for the native shortcut registration error, but these strings don’t exist in the native codebase and should be aligned with the Chinese fallbacks already used elsewhere ('已注册' / '无效').

🐛 Suggested fix
-                if (errorStr.includes('already registered') || errorStr.includes('宸叉敞鍐?')) {
+                if (errorStr.includes('already registered') || errorStr.includes('已注册')) {
-                if (errorStr.includes('invalid') || errorStr.includes('鏃犳晥')) {
+                if (errorStr.includes('invalid') || errorStr.includes('无效')) {
🤖 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` at line
333, The conditional that checks for native shortcut registration errors uses
mojibake Chinese strings ('宸叉敞鍐?' / '鏃犳晥'); update the check in the SettingsView
General component so errorStr is matched against the proper Chinese fallbacks
used elsewhere (e.g., '已注册' and '无效') instead of the garbled strings—locate the
if that references errorStr (the one currently testing for 'already registered'
|| '宸叉敞鍐?') and replace the mojibake literals with the correct '已注册' and '无效'
matches.
apps/desktop/src/views/SettingsView/components/General/SearchShortcutSettings.vue (1)

570-572: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix Prettier formatting violation.

The @click handler should be on a single line per the project's Prettier configuration.

🧹 Suggested fix
                                `@click`="
-                                    handleSearchShortcutIconAction(row.id, row.shortcutAction)
-                                "
+                                `@click`="handleSearchShortcutIconAction(row.id, row.shortcutAction)"
🧰 Tools
🪛 ESLint

[error] 570-572: Replace ⏎····································handleSearchShortcutIconAction(row.id,·row.shortcutAction)⏎································ with handleSearchShortcutIconAction(row.id,·row.shortcutAction)

(prettier/prettier)

🪛 GitHub Check: Frontend Quality

[failure] 570-570:
Replace ⏎····································handleSearchShortcutIconAction(row.id,·row.shortcutAction)⏎································ with handleSearchShortcutIconAction(row.id,·row.shortcutAction)

🤖 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/SearchShortcutSettings.vue`
around lines 570 - 572, The template's `@click` handler is split across lines
causing a Prettier violation; collapse the handler into a single line so the
element uses `@click`="handleSearchShortcutIconAction(row.id, row.shortcutAction)"
(reference the handleSearchShortcutIconAction call and the row.id /
row.shortcutAction args) and ensure there are no extra line breaks or trailing
spaces around the attribute.

Source: Linters/SAST tools

@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/src/views/SettingsView/components/General/index.vue (1)

23-23: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize search bindings to the current platform before checking global conflicts.

This comparison uses raw searchKeybindings values, but the search-shortcut editor already treats those bindings as local syntax and converts them with toCurrentPlatformShortcut() before comparing them to settings.value.globalShortcut. Without the same conversion here, a binding like CommandOrCtrl+Space can slip past this check while still colliding with Ctrl+Space on the active platform, so the global shortcut UI can persist an overlap that the search shortcut UI rejects.

Suggested fix
-import { normalizeLocalShortcutString } from '`@/utils/shortcuts`';
+import {
+    normalizeLocalShortcutString,
+    toCurrentPlatformShortcut,
+} from '`@/utils/shortcuts`';

 function findGlobalShortcutSearchConflict(shortcut: string): boolean {
     const normalizedShortcut = normalizeLocalShortcutString(shortcut);
     if (!normalizedShortcut) {
         return false;
     }

     return Object.values(settings.value.searchKeybindings).some((searchShortcut) => {
-        const normalizedSearchShortcut = normalizeLocalShortcutString(searchShortcut);
+        const normalizedSearchShortcut = searchShortcut
+            ? normalizeLocalShortcutString(toCurrentPlatformShortcut(searchShortcut))
+            : null;
         return normalizedSearchShortcut === normalizedShortcut;
     });
 }

Also applies to: 96-106

🤖 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` at line 23,
The code compares raw searchKeybindings to settings.value.globalShortcut without
normalizing to the current platform, allowing values like "CommandOrCtrl+Space"
to bypass conflict checks; update the comparison to convert each search binding
using normalizeLocalShortcutString (the same normalization used by the
search-shortcut editor) before checking against settings.value.globalShortcut so
conflicts are detected consistently (apply the same normalization in both places
where searchKeybindings are checked).
🤖 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/src/views/SettingsView/components/General/index.vue`:
- Line 23: The code compares raw searchKeybindings to
settings.value.globalShortcut without normalizing to the current platform,
allowing values like "CommandOrCtrl+Space" to bypass conflict checks; update the
comparison to convert each search binding using normalizeLocalShortcutString
(the same normalization used by the search-shortcut editor) before checking
against settings.value.globalShortcut so conflicts are detected consistently
(apply the same normalization in both places where searchKeybindings are
checked).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5b43cbef-8739-4da8-9544-5fd5d88a72c1

📥 Commits

Reviewing files that changed from the base of the PR and between bdf3b9e and b560f9b.

📒 Files selected for processing (4)
  • apps/desktop/src/components/CustomSelect.vue
  • apps/desktop/src/stores/settings.ts
  • apps/desktop/src/views/SettingsView/components/General/SearchShortcutSettings.vue
  • apps/desktop/src/views/SettingsView/components/General/index.vue
📜 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). (3)
  • GitHub Check: Frontend Tests
  • GitHub Check: CodeQL (rust)
  • GitHub Check: Desktop E2E Smoke (Windows)

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 10, 2026
@github-actions github-actions Bot added the area:tauri Tauri shell or desktop runtime changes label Jun 10, 2026
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.

1 participant