feat(desktop): settings phase 3a - keyboard layout strategy - #6289
Conversation
Schema field for the future user-facing radio. Default 'hybrid' will preserve the dispatch refactor's behavior, and the current code path is unaffected. Zod's parse-time defaults handle existing users automatically, so no explicit migration is needed.
Refactors getPressedKey to read the keyboard layout strategy from a small web-safe holder, replacing the single event.code path with a per-strategy dispatch. The hybrid strategy prefers event.key when it produces a Latin glyph and falls back to event.code for non-Latin layouts, which is the recommended default. The key and code strategies are escape valves. Letter and digit branches are the only ones that dispatch. Arrow keys, brackets, and the ?-to-/ mapping are layout stable and apply unchanged regardless. The desktop settings composable updates the holder on initial load, on store watch events, and eagerly inside update() so changes take effect on the next keypress. Skip shortcut handling during IME composition (CJK input) and when AltGr is the modifier, so a Ctrl+key combo that's actually part of typing doesn't fire a shortcut. Adds vitest coverage for all three strategies across QWERTY, AZERTY, QWERTZ, Cyrillic, Dvorak, and Mac Option dead key fixtures, plus layout-stable keys and numpad cases.
The capture-phase keydown listener in selfhost-web/main.ts hardcoded each shortcut against event.code, so on AZERTY a user pressing the keycap labelled A (physical KeyQ position) fired Ctrl+Q and quit the app instead of selecting all (#6090, #6120). Routes the listener through the shared resolvePressedKey + active strategy so the same dispatch the in-page handler uses applies to capture-phase too. The hardcoded e.code === "KeyQ" checks become key === "q" after resolution, which respects the user's strategy choice and matches what the user typed. Also adds the same IME composition and AltGraph guards as the in-page handler. The AltGraph guard closes #5787: on QWERTZ the user typing [ via AltGr+8 no longer triggers the Ctrl+Alt+[ tab shortcut. Closes #5787, #6090, #6120.
Adds a Keyboard group to the Desktop
section of the settings page with three
radios: Smart (recommended), Typed
letter, and Physical key position.
Each option carries a one-line
description so users can pick without
trial and error.
The group label completes the choice
("Match shortcuts by typed letter or
physical position") and a helper
paragraph names the situation in plain
terms so users on AZERTY, QWERTZ, or
Cyrillic see why the choice exists.
Selection writes to the
keyboardLayoutStrategy field through
the desktop settings composable.
Changes apply on the next keypress.
Updates the section description so a
new user reading the page header sees
what's actually configurable here:
update behavior and keyboard handling.
i18n keys for the group title, the
strategy label and helper, and per-
option labels and descriptions.
Greptile SummaryThis PR introduces a
Confidence Score: 5/5Safe to merge — the strategy dispatch is well-isolated, the singleton pattern is consistent with existing composables, and all three resolution paths are covered by 38 new tests. The refactor correctly addresses the AZERTY quit-instead-of-select-all regression, restores the synthetic-event fallback that was flagged in a previous review, and consolidates both shortcut handlers through the same resolver. The empty-code fallback for the "code" strategy is explicitly tested, the AltGr and IME guards are in the right order, and the transactional rollback in the composable includes the strategy holder, preventing in-memory drift after a failed persist. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
EV[Keyboard event] --> IME{IME composing?}
IME -->|yes| SKIP1[Ignored]
IME -->|no| ALTGR{AltGr modifier?}
ALTGR -->|yes| SKIP2[Ignored]
ALTGR -->|no| RESOLVE[resolvePressedKey with active strategy]
RESOLVE --> STRAT{Strategy}
STRAT -->|typed-letter| LK[letterFromKey]
STRAT -->|physical-code| LC[letterFromCode or empty-code fallback]
STRAT -->|smart-hybrid| LH[letterFromKey then letterFromCode]
LK --> MATCH{Match found?}
LC --> MATCH
LH --> MATCH
MATCH -->|yes| LETTER[Return letter]
MATCH -->|no| STABLE[Layout-stable keys: arrows, tab, enter, slash, dot]
STABLE --> BKEY{Bracket from typed char?}
BKEY -->|yes| RBKEY[Return bracket]
BKEY -->|no| BCODE{Bracket from physical code?}
BCODE -->|yes| RBCODE[Return bracket - strategy-independent]
BCODE -->|no| DIGIT[Digit dispatch by strategy]
DIGIT --> NUMPAD{Numpad plus NumLock?}
NUMPAD -->|yes| RNUMPAD[Return numpad digit]
NUMPAD -->|no| NULL[null - no match]
LETTER --> CAPTURE{Capture phase shortcut?}
RBKEY --> CAPTURE
RBCODE --> CAPTURE
RNUMPAD --> CAPTURE
CAPTURE -->|matched| EMIT[preventDefault, emit Tauri event]
CAPTURE -->|no match| INPAGE[In-page handler invokes action]
Reviews (3): Last reviewed commit: "fix(common): tone down keyboard-strategy..." | Re-trigger Greptile |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Implements phase 3a of desktop keyboard shortcut layout handling by introducing a keyboardLayoutStrategy setting and routing both in-page and capture-phase shortcut detection through a shared resolver.
Changes:
- Added
keyboardLayoutStrategy: "key" | "code" | "hybrid"to desktop settings (default:"hybrid"), plus a module-level holder getter/setter. - Refactored shortcut key resolution into
resolvePressedKeyand updated both keydown handlers to honor the active strategy while skipping IME composition and AltGr. - Added desktop settings UI radios for selecting the strategy and introduced Vitest coverage for resolver behavior.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/hoppscotch-selfhost-web/src/main.ts | Capture-phase shortcut handler now resolves keys via shared strategy-aware resolver and adds IME/AltGr guards. |
| packages/hoppscotch-common/src/platform/desktop-settings.ts | Adds keyboardLayoutStrategy to the desktop settings schema with a default. |
| packages/hoppscotch-common/src/helpers/keyboard-strategy.ts | Introduces a web-safe module-level holder for the active layout strategy. |
| packages/hoppscotch-common/src/helpers/keybindings.ts | Adds IME/AltGr guards and introduces resolvePressedKey strategy-based resolution. |
| packages/hoppscotch-common/src/helpers/tests/keybindings.spec.ts | Adds Vitest cases covering key resolution for multiple layouts/strategies. |
| packages/hoppscotch-common/src/composables/desktop-settings.ts | Mirrors settings changes into the strategy holder eagerly and on external updates. |
| packages/hoppscotch-common/src/components/settings/Desktop.vue | Adds settings UI (radio group) for selecting keyboard layout strategy. |
| packages/hoppscotch-common/locales/en.json | Adds new i18n strings for the keyboard strategy UI and updates desktop description. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The resolver's bracket branch only matched
event.key === "[" / "]". On Russian Cyrillic
the physical [ keycap types "х" and the ]
keycap types "ъ", so users on Cyrillic
couldn't fire ctrl-alt-[ or ctrl-alt-] from
their keyboard. Adds the BracketLeft and
BracketRight code fallback so the shortcut
resolves regardless of what the active layout
types from those keys.
Also restores the pre-strategy resolver's
synthetic-event fallback for the "code"
branch. When event.code is empty (synthetic
events, certain older environments),
letterFromCode is null and the function used
to return null under "code" strategy,
silently dropping the shortcut. The "code"
branch now falls back to event.key for ASCII
letters, matching what the resolver did
before strategy dispatch.
Exports KeyboardEventLike alongside the
function since it appears in the public
signature, and updates the JSDoc to name
both call sites (getPressedKey and the
capture-phase listener in selfhost-web/main.ts)
instead of "exported for tests".
Tests cover the new bracket fallback under
all three strategies (Cyrillic { key: "х",
code: "BracketLeft" } resolves to "[", same
for "ъ"/"]") and the synthetic-event fallback
under each strategy.
The keyboard-strategy holder is written by
the desktop settings composable's loadInitial.
The composable's only caller was Desktop.vue's
setup, so the holder stayed at its module-
level default ("hybrid") until the user opened
the settings page. A persisted "key" or
"code" choice was ignored on every restart
until then.
Calls useDesktopSettings() inside
setupDesktopUI() in selfhost-web/main.ts so
loadInitial fires at desktop boot and the
holder picks up the persisted choice before
any keypress fires.
…ention The strategy description paragraph used `text-secondaryDark`, giving it visual weight close to the `<h4>` "Keyboard" title above. Other settings panels use `text-secondaryLight` for description paragraphs under their subsection title (see `settings.vue` General/Theme/Kernel Interceptor descriptions and `Agent.vue`/`Native.vue` proxy labels).
* chore: bump version to `2026.3.1` * chore: patch axios CVEs and bump related dependencies (hoppscotch#6131) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * Delete .github/workflows/codeql-analysis.yml * refactor(cli): match test-result helper name to documented contract (hoppscotch#6122) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * feat(backend): use stateless OAuth2 state store (hoppscotch#6098) * fix(common): handle non-string values in Postman collection import (hoppscotch#6137) Co-authored-by: atharvasingh7007 <singhatharva7007@gmail.com> Co-authored-by: XHamzaX <hamzaswitch1221@gmail.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * chore(common): remove unused `flow` import * fix(common): correct environment locale wording (hoppscotch#6117) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * fix(data): make `$randomUUID` predefined variable RFC 4122 compliant (hoppscotch#6125) Co-authored-by: hconsulting987654321-blip <hconsulting987654321-blip@users.noreply.github.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * chore(common): complete missing Spanish translations (hoppscotch#6109) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * chore(common): complete missing Turkish translations (hoppscotch#6071) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore(common): modify Chinese translation of words (hoppscotch#5996) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * fix(common): add missing aria-labels to icon-only sidenav links (hoppscotch#6160) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * fix(common): variable hover tooltip was not clickable (disappeared) (hoppscotch#6155) * feat: add SMTP OAuth2 authentication support (hoppscotch#6141) Co-authored-by: nivedin <nivedinp@gmail.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * fix: improve environment validation in published docs (hoppscotch#5962) * feat(common): improve API documentation publishing UX (hoppscotch#6116) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * fix: remediate `quinn-proto` vulnerability across native packages (hoppscotch#6174) Co-authored-by: orbisai0security <242526317+orbisai0security@users.noreply.github.com> * docs: security threat model and policy update (hoppscotch#6158) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * fix(common): apply platform default proxy URL on load and reset (hoppscotch#6142) * feat: add collection-level pre-request and test scripts (hoppscotch#5745) Co-authored-by: nivedin <nivedinp@gmail.com> Co-authored-by: “mirarifhasan” <arif.ishan05@gmail.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * fix(common): support `id_token` in Authorization Code OAuth flow (hoppscotch#6144) * feat(desktop): settings phase 0 - infra and update check (hoppscotch#6172) Co-authored-by: VicenzoMF <81040684+VicenzoMF@users.noreply.github.com> * feat(selfhost-web): make webapp-server timeouts configurable (hoppscotch#6147) Signed-off-by: Rodrigo Kellermann <kellermann@gmail.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * chore: bump version to `2026.4.0` * chore: bump CLI version * chore: formatting updates * fix(common): restore magic-link sign-in flow on cloud for orgs (hoppscotch#6237) * fix(desktop): unified store scope and migration reroute (hoppscotch#6238) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * chore: security patch for the dependency chain `v2026.4.0` (hoppscotch#6191) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * chore(agent): bump version to `v0.1.17` * chore(common): add token_refresh auth event and harden no-sync flag * fix(backend): harden onboarding config endpoint (hoppscotch#6240) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * fix(common): set domain url as mockserver environment (hoppscotch#6185) * fix(common): subfolder add-new in team collections respects write access (hoppscotch#6243) * fix(common): preserve string contract for GQL history responses (hoppscotch#6244) * fix(cli): ship `semver` as a runtime dependency (hoppscotch#6257) * fix(security): prevent mass assignment in onboarding (hoppscotch#6171) * fix(backend): prevent mass assignment in onboarding config endpoint The unauthenticated POST /v1/onboarding/config endpoint mapped the request body directly to InfraConfigEnum keys, allowing an attacker on a fresh install to inject sensitive values such as JWT_SECRET and SESSION_SECRET, enabling forged admin JWTs and full takeover. Four independent weaknesses combined to make this exploit possible. This commit addresses each in layers so the fix holds even if any single layer regresses: - main.ts: enable `whitelist: true` on the global ValidationPipe so properties not declared on any DTO are stripped before reaching any controller / service. This is the primary mitigation described in the advisory. - onboarding.controller.ts: scope an additional ValidationPipe (`whitelist` + `forbidNonWhitelisted`) on the onboarding POST body so requests containing unknown fields are explicitly rejected with 400 instead of silently dropped. - infra-config.service.ts (updateOnboardingConfig): introduce an `ONBOARDING_ALLOWED_KEYS` allowlist so any `InfraConfigEnum` key not part of the documented onboarding surface (OAuth, SMTP) is dropped server-side before being persisted, even if earlier layers regress. - infra-config.service.ts (validateEnvValues): explicitly reject `JWT_SECRET`, `SESSION_SECRET` and `ALLOW_SECURE_COOKIES` so these keys can never be written through any infra-config code path, replacing the prior `default: break` behaviour that silently accepted them. Fixes GHSA-j542-4rch-8hwf * fix(backend): harden onboarding config validation and add sensitive infra-config tests * chore: cleanup * chore: class validator implemented in dto layer * fix: arguments * fix: api feedback --------- Co-authored-by: “mirarifhasan” <arif.ishan05@gmail.com> * fix: class validator decorator usages (hoppscotch#6293) * fix: class validator decorator usages * fix: feedback * fix: preserve script imports and avoid WebKit lookbehind (hoppscotch#6306) * feat(desktop): settings phase 3a - keyboard layout strategy (hoppscotch#6289) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * chore: bump version to `2026.4.1` * chore(cli): bump version to `0.31.1` * chore(cli): bump version to `0.31.2` * chore: security patch for the dependency chain `v2026.5.0` (hoppscotch#6338) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: stop secret variable values from leaking to backend (hoppscotch#6279) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * fix(common): wait for proxy settings before issuing requests (hoppscotch#6333) * feat: make proxy URL configurable from env and admin dashboard (hoppscotch#6336) Co-authored-by: nivedin <nivedinp@gmail.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * feat(desktop): zoom level control in settings (hoppscotch#6358) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * feat(common): add OpenAPI 3.1 collection export (hoppscotch#5880) Co-authored-by: gavin mcdonough <mcdgavin@users.noreply.github.com> Co-authored-by: nivedin <nivedinp@gmail.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * fix(desktop): align appload types and resolve shell import alias (hoppscotch#6369) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * chore: bump version to `2026.5.0` * chore: enforce `minimumReleaseAge` for supply chain hardening * fix: class validation issue for updateRESTUserRequest (hoppscotch#6373) * fix: class validation issue for updateRESTUserRequest * test: modified user-req unit test cases * feat: add Mongolian translation (hoppscotch#6344) Co-authored-by: cf3901646 <cf3901646@users.noreply.github.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> * fix(common): preserve collection tree on OpenAPI re-import (hoppscotch#6376) * fix(test-runner): restore missing closing braces in runTestsWithIterations after merge conflict resolution * fix(test-runner): fix Run Collection - rename runTestsWithIterations to runTestCollection and wire inherited scripts through runTestsInCustomOrder --------- Signed-off-by: Rodrigo Kellermann <kellermann@gmail.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Liyas Thomas <liyascthomas@gmail.com> Co-authored-by: Basavaraj_m_n <162813862+Basavaraj8143@users.noreply.github.com> Co-authored-by: Mir Arif Hasan <arif.ishan05@gmail.com> Co-authored-by: okxint <130782884+okxint@users.noreply.github.com> Co-authored-by: atharvasingh7007 <singhatharva7007@gmail.com> Co-authored-by: XHamzaX <hamzaswitch1221@gmail.com> Co-authored-by: Pallav Sarkar <pallav2005sarkar@gmail.com> Co-authored-by: BUNGHUNTER2026ILOVEYOUECHO <hconsulting987654321@gmail.com> Co-authored-by: hconsulting987654321-blip <hconsulting987654321-blip@users.noreply.github.com> Co-authored-by: Franco Ortiz <fr.dv.ortiz@gmail.com> Co-authored-by: Serhat <49079271+onwp@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Denny Jiang <1632856+jiangzm@users.noreply.github.com> Co-authored-by: Rishu ray <rayrishu19@gmail.com> Co-authored-by: Aaron Fort Garcia <aaronfortg@gmail.com> Co-authored-by: nivedin <nivedinp@gmail.com> Co-authored-by: sahilkhan09k <Msahilkhan05052005@gmail.com> Co-authored-by: Nivedin <53208152+nivedin@users.noreply.github.com> Co-authored-by: Shreyas <CuriousCorrelation@gmail.com> Co-authored-by: orbisai0security <242526317+orbisai0security@users.noreply.github.com> Co-authored-by: John An <junghyunan0319@gmail.com> Co-authored-by: Anwarul Islam <anwaarulislaam@gmail.com> Co-authored-by: VicenzoMF <81040684+VicenzoMF@users.noreply.github.com> Co-authored-by: Rodrigo Kellermann <rkferreira@gmail.com> Co-authored-by: Nahid Hasan <52489202+nahidhasan94@users.noreply.github.com> Co-authored-by: Gavin McDonough <gavincmcd@gmail.com> Co-authored-by: gavin mcdonough <mcdgavin@users.noreply.github.com> Co-authored-by: Charlie Freeman <cf3901646@gmail.com> Co-authored-by: cf3901646 <cf3901646@users.noreply.github.com> Co-authored-by: arunkumarjeevanantham <aj34@ford.com>
Phase 3a of the keyboard work in the Desktop App Settings &
Customization epic. Adds a
keyboardLayoutStrategysetting toDesktopSettings, refactors the in-page and capture-phase shortcutdispatch to honor it, and exposes a Keyboard group with a three-option
radio in the Desktop section of the settings page. The default
("Smart") prefers the typed letter and falls back to the physical key
position for non-Latin layouts, so AZERTY's "A" keycap fires Ctrl+A
and Cyrillic users still get Ctrl+Q from the physical Q position.
Closes FE-1232
Closes FE-1233
Closes FE-1234
Closes FE-1241
Closes #5787
Closes #6090
Closes #6120
Refs #6192
Follow-up on #5944
What it does
The settings store gains
keyboardLayoutStrategy: "key" | "code" | "hybrid"with"hybrid"asthe default. Zod's parse-time defaults handle existing users
automatically, so no explicit migration is needed.
getPressedKeyinhelpers/keybindings.tsnow reads the activestrategy from a small holder (
helpers/keyboard-strategy.ts) anddispatches letter and digit lookups through it. The hybrid strategy
prefers
event.keywhen it produces a Latin glyph and falls back toevent.codeotherwise. Thekeyandcodestrategies are escapevalves for users on layouts where the heuristic guesses wrong. Arrow
keys, Tab, brackets, and the "?" → "/" mapping are layout-stable and
apply unchanged regardless.
The capture-phase keydown listener in
selfhost-web/main.ts(whichintercepts desktop-shell shortcuts like Cmd+Q before the in-page
handler sees them) routes through the same resolver. Previously each
shortcut hardcoded its
event.codecheck, so an AZERTY user pressingthe keycap labelled A (physical KeyQ position) fired Cmd+Q and quit
the app instead of selecting all (#6090, #6120). The capture path now
respects the strategy, so the user's choice in settings takes effect
everywhere a shortcut might fire.
Both handlers also skip:
ev.isComposing || ev.keyCode === 229), so a CJKuser's keystrokes during composition don't trigger shortcuts.
ev.getModifierState("AltGraph")), so QWERTZ userstyping
[via AltGr+8 don't get hijacked into the Ctrl+Alt+[ MRUshortcut ([bug]: Cannot make [ ] in query parameters #5787).
AltGraphis true only for AltGr, never forgenuine Ctrl+Alt presses.
The settings UI gains a Keyboard group with a three-option radio:
Smart (recommended), Typed letter, Physical key position. Each option
carries a one-line description so users can pick without trial and
error. Selection writes to
keyboardLayoutStrategythrough thedesktop settings composable, which mirrors it into the holder eagerly
so the next keypress respects the change without waiting for the
store-watch round-trip.
Backwards compatibility
Existing users with no setting saved get the new "hybrid" default on
upgrade. Behaviour shifts from #6009's pure-
event.coderesolutionto hybrid, which closes the AZERTY bug (#6090) and keeps the Cyrillic
fix (#5944) intact. Users who want the prior physical-position
behaviour can pick "Physical key position" in the new radio.
Fresh installs land on "hybrid" the same way.
The web build keeps the holder at its module-level default ("hybrid")
because the desktop settings composable (the only writer) doesn't run
on web. Web users get the same recommended behaviour as desktop users
who haven't touched the radio.
Tests
Adds 38 vitest cases for
resolvePressedKeycovering all threestrategies across QWERTY, AZERTY, QWERTZ, Cyrillic, Dvorak, and Mac
Option dead key fixtures, plus layout-stable keys (arrows, brackets,
Tab, Enter) and numpad cases.
End-to-end testing on the Tauri shell against each layout is done
on Windows, Linux and MacOS - FE-1244.
Notes for reviewers
Bracket detection in
resolvePressedKeyruns before the digit branchdeliberately. AZERTY's
[via AltGr+5 hasevent.code === "Digit5"but
event.key === "[". Without the early bracket check, the digitbranch would resolve to "5" and a
ctrl-alt-5binding would fireinstead of letting
[through.The capture-phase listener and the in-page handler share the same
resolver, so the user's strategy choice takes effect everywhere.
getKeyboardLayoutStrategyis a plain getter in a helpers file sothe hot path doesn't pay reactivity cost on every keystroke. The
composable updates the holder eagerly inside
update()and from thestore-watch callback so the reactive UI and the holder stay coherent.
The architecture is a strategy dispatch rather than a single
heuristic. The same
event.code-vs-event.keyquestion keepsreappearing in the issue tracker (#3058, #3332, #5944, #6009, #6090, #6120, #6192).
Letting users pick once retires that question instead
of picking the next lost.
VS Code patterns: hybrid dispatch (key first, code fallback)
as the recommended default, AltGraph detection for distinguishing
AltGr-typed symbols from Ctrl+Alt shortcuts, and IME composition
guard for CJK input.
Future work
Per-shortcut rebinding (Phase 3b, FE-1235 through FE-1240) lets
users rebind any registered action through a UI table. The schema
supports it now (the holder pattern extends to a per-action lookup),
but the recorder, bindings table, and conflict detection ship in a
follow-up.
Native menu accelerator integration (FE-1242) brings Tauri's native
Edit menu (added in #5920) under the same strat.
Component-level keydown handlers in spotlight, EnvInput, SchemaSearch,
and the AI modals (FE-1246, audit) currently bypass
helpers/keybindings.ts. Routing them through the registry comesafter too.
Summary by cubic
Adds a desktop
keyboardLayoutStrategysetting to match shortcuts by typed letter or by physical key, with a Smart default. Both in‑page and capture‑phase handlers use the same resolver, and saved choices now load at desktop boot. Implements phase 3a of the Desktop Settings & Customization epic (FE-1232, FE-1233, FE-1234, FE-1241).New Features
Bug Fixes
Written for commit 1969d66. Summary will update on new commits.