fix(app): restore Alt+letter shortcuts on macOS

The key matcher only consulted event.code when a binding explicitly
opted in via codeFallback. macOS rewrites event.key whenever Option
is held (Option+T -> "†", Option+[ -> "“", Option+Shift+W -> "„",
etc.), so the comparison against combo.key always failed and every
Alt-bound letter / bracket shortcut silently stopped firing on Mac:
Cmd+Alt+T (cycle theme), Alt+Shift+[/], Alt+[/], Alt+Shift+W.

Fall back to event.code when combo.alt is set. Keep the existing
key-first behaviour for non-Alt bindings so Dvorak users still get
logical-character matching (Cmd+V on physical Period keeps pasting
instead of triggering Cmd+. for sidebar toggle).
This commit is contained in:
Mohamed Boudra
2026-05-22 17:55:48 +07:00
parent 5f11f602fb
commit de7bf2fb01
2 changed files with 70 additions and 12 deletions
@@ -300,6 +300,49 @@ describe("keyboard-shortcuts", () => {
preventDefault: false,
stopPropagation: false,
},
// macOS rewrites event.key when Option is held (Option+T -> "†",
// Option+[ -> "“", Option+Shift+W -> "„", etc.). Every Alt-bound
// letter / bracket shortcut must still resolve.
{
name: "matches Cmd+Alt+T to cycle theme on macOS when Option substitutes event.key",
event: { key: "\u2020", code: "KeyT", metaKey: true, altKey: true },
context: { isMac: true },
action: "theme.cycle",
},
{
name: "matches Alt+Shift+[ to previous tab on macOS when Option substitutes event.key",
event: { key: "\u201D", code: "BracketLeft", altKey: true, shiftKey: true },
context: { isMac: true },
action: "workspace.tab.navigate.relative",
payload: { delta: -1 },
},
{
name: "matches Alt+Shift+] to next tab on macOS when Option substitutes event.key",
event: { key: "\u2019", code: "BracketRight", altKey: true, shiftKey: true },
context: { isMac: true },
action: "workspace.tab.navigate.relative",
payload: { delta: 1 },
},
{
name: "matches Alt+[ to previous workspace on macOS web when Option substitutes event.key",
event: { key: "\u201C", code: "BracketLeft", altKey: true },
context: { isMac: true, isDesktop: false },
action: "workspace.navigate.relative",
payload: { delta: -1 },
},
{
name: "matches Alt+] to next workspace on macOS web when Option substitutes event.key",
event: { key: "\u2018", code: "BracketRight", altKey: true },
context: { isMac: true, isDesktop: false },
action: "workspace.navigate.relative",
payload: { delta: 1 },
},
{
name: "matches Alt+Shift+W to close current tab on macOS web when Option substitutes event.key",
event: { key: "\u201E", code: "KeyW", altKey: true, shiftKey: true },
context: { isMac: true, isDesktop: false },
action: "workspace.tab.close.current",
},
];
it.each(matchingCases)(
@@ -401,6 +444,14 @@ describe("keyboard-shortcuts", () => {
event: { key: "v", code: "Period", metaKey: true },
context: { isMac: true, isDesktop: true, focusScope: "message-input" },
},
// Sanity: the macOS Option-substitution fallback must still respect
// modifier checks — pressing Option+T alone (no Cmd) must not trigger
// the Cmd+Alt+T theme-cycle binding.
{
name: "does not cycle theme on macOS when Cmd is missing (Alt+T alone)",
event: { key: "\u2020", code: "KeyT", altKey: true },
context: { isMac: true },
},
];
it.each(nonMatchingCases)("$name", ({ event, context }) => {