Summary
On Windows with a Russian (ЙЦУКЕН) keyboard layout active, none of BB's web-layer keyboard shortcuts fire. Pressing Ctrl+K does not open the command palette, Ctrl+Shift+[ / ] do not switch threads, and plugin shortcuts are dead. Switching the OS layout back to English makes every one of them work again. I expected shortcuts to be bound to the physical key position, as they are in Chrome, VS Code and the six shortcuts BB routes through its native Electron menu.
The cause is that normalizeAppShortcutInputKey compares the layout-dependent KeyboardEvent.key against the binding's latin shortcut.key. On a Cyrillic layout the K position reports "л", and "л" !== "k".
Versions and environment
- bb 0.43.3, desktop app built from source at
main. The cited code is unchanged at current upstream main (370029d), and the unit repro below fails on a clean main checkout.
- Windows 11 Pro 10.0.26200
- Node v24.18.0
- Keyboard layout: Russian (ЙЦУКЕН), the stock Windows layout. No provider involved — this is entirely in the renderer's key matching.
Steps to reproduce
In the app:
- On Windows, add the Russian keyboard layout and make it active (
Win+Space).
- Open the BB desktop app, focus the thread view.
- Press
Ctrl+K — nothing happens.
- Press
Ctrl+Shift+[ — nothing happens.
- Switch the layout back to English and repeat steps 3–4 — both work.
As a unit test (fails on main at 370029d, no app or layout needed):
// packages/domain/test/layout-repro.test.ts
import { describe, expect, it } from "vitest";
import { matchesAppShortcut } from "../src/app-keybindings";
describe("cyrillic layout", () => {
it("matches Ctrl+K pressed on a Russian layout", () => {
expect(
matchesAppShortcut(
// Windows/Chromium reports the layout character in `key`
{ key: "л", code: "KeyK", metaKey: false, ctrlKey: true, altKey: false, shiftKey: false },
{ key: "k", mod: true, meta: false, control: false, alt: false, shift: false },
false,
),
).toBe(true);
});
});
pnpm exec turbo run test --filter=@bb/domain -- layout-repro
Did NOT reproduce — these keep working on the Russian layout, which is what narrowed it down:
Ctrl+N, Ctrl+T, Ctrl+W, Ctrl+Shift+N, Ctrl+Shift+T, Ctrl+, — the six commands routed through the native Electron application menu. Chromium matches those accelerators by VK code, so they are layout-independent.
Ctrl+\ (sidebar.toggle) — the Backslash position emits \ on both layouts.
Ctrl+1…Ctrl+9 (thread jumps) — the digit row is identical on both layouts.
That split is the evidence that this is a BB bug in the web layer rather than an Electron or Chromium limitation: the same modifier chord works when Electron matches it and fails when BB matches it.
Expected vs actual
Physical key: K position, Ctrl held, Russian layout active
Actual — no command runs. The KeyboardEvent is:
{ key: "л", code: "KeyK", ctrlKey: true, altKey: false, shiftKey: false }
normalizeAppShortcutInputKey() returns "л"
matchesAppShortcut() compares "л" === "k" -> false
Expected — palette.open fires, exactly as it does on the English layout,
where the same physical key reports { key: "k", code: "KeyK" }.
The unit test above fails with:
AssertionError: expected false to be true
Evidence
Permalinks at upstream main 370029d7280d2f0a46c0c13272ecdc7e64d5f13e:
normalizeAppShortcutInputKey — the code fallback is gated behind input.altKey, which was written for macOS Option compositions. Without Alt held, the raw layout key is returned.
matchesAppShortcut — compares that value to the binding key.
baseKeyFromCode — knows only Key* and Digit* positions, so punctuation bindings stay unreachable even once the fallback does run.
Everything downstream of that comparison is affected:
There is a second, quieter consequence. appShortcutFromInput normalizes through the same function when recording a custom shortcut, and the result is persisted. Rebinding a command while a Cyrillic layout is active stores { key: "л", mod: true } — the override then works only on that layout, renders as "Ctrl + Л" in settings, and the conflict detector cannot tell it apart from a binding on the same physical key. appShortcutSchema accepts any string, so nothing rejects it.
Also unreachable because baseKeyFromCode returns null for their positions: the [ / ] thread-navigation defaults at app-keybindings.ts#L154-L180.
What you ruled out
Suggested priority and effort
Hits every user whose daily driver is a non-latin layout (Russian, Ukrainian, Greek, Hebrew, Arabic, …) — they lose essentially the whole shortcut surface, and the only workaround is switching layout before every chord. No data loss. The fix is small and contained to one function plus a lookup table.
A fix is ready
A fix branched off main is linked below as a prototype per CONTRIBUTING.md; happy to open a PR if a maintainer would like one.
https://github.com/irium/bb/tree/fix/non-latin-keyboard-shortcuts — single commit d340b06, based on main at c1a64f4.
What it does:
- Falls back to
KeyboardEvent.code whenever the reported key is a single non-ASCII character, not only when Alt is held.
- Teaches
baseKeyFromCode the punctuation positions (Comma, Period, Slash, Semicolon, Quote, BracketLeft/Right, Backslash, Backquote, Minus, Equal) so the [ / ] and , bindings resolve too.
The non-ASCII condition is deliberate: layouts that report ASCII keep matching by character, so AZERTY still binds its own letters rather than the QWERTY positions. The existing test at app-keybindings.test.ts#L175 pins that behavior and still passes.
Because shortcut recording shares the same normalization, the persisted-override problem described above is fixed by the same change. No schema validation or migration was added for already-stored non-latin overrides — happy to add either if preferred.
Tests: 6 added (Cyrillic Ctrl+K/Ctrl+P/Ctrl+F, Ctrl+Shift+[, Ctrl+,, a negative case, and one for shortcut recording). @bb/domain 215/215, @bb/app keybinding suites 28/28; typecheck and lint clean on domain, app, desktop, server.
One behavior change worth flagging in review: on a non-latin layout the settings UI keeps displaying the latin key (for example "Ctrl + ,") while the key that physically fires it prints something else. Matching by position but labelling by the latin base seemed the right trade-off versus wiring up navigator.keyboard.getLayoutMap(), but it is a product call.
AGENT GENERATED
Summary
On Windows with a Russian (ЙЦУКЕН) keyboard layout active, none of BB's web-layer keyboard shortcuts fire. Pressing Ctrl+K does not open the command palette, Ctrl+Shift+[ / ] do not switch threads, and plugin shortcuts are dead. Switching the OS layout back to English makes every one of them work again. I expected shortcuts to be bound to the physical key position, as they are in Chrome, VS Code and the six shortcuts BB routes through its native Electron menu.
The cause is that
normalizeAppShortcutInputKeycompares the layout-dependentKeyboardEvent.keyagainst the binding's latinshortcut.key. On a Cyrillic layout the K position reports"л", and"л" !== "k".Versions and environment
main. The cited code is unchanged at current upstreammain(370029d), and the unit repro below fails on a cleanmaincheckout.Steps to reproduce
In the app:
Win+Space).Ctrl+K— nothing happens.Ctrl+Shift+[— nothing happens.As a unit test (fails on
mainat370029d, no app or layout needed):Did NOT reproduce — these keep working on the Russian layout, which is what narrowed it down:
Ctrl+N,Ctrl+T,Ctrl+W,Ctrl+Shift+N,Ctrl+Shift+T,Ctrl+,— the six commands routed through the native Electron application menu. Chromium matches those accelerators by VK code, so they are layout-independent.Ctrl+\(sidebar.toggle) — theBackslashposition emits\on both layouts.Ctrl+1…Ctrl+9(thread jumps) — the digit row is identical on both layouts.That split is the evidence that this is a BB bug in the web layer rather than an Electron or Chromium limitation: the same modifier chord works when Electron matches it and fails when BB matches it.
Expected vs actual
The unit test above fails with:
Evidence
Permalinks at upstream
main370029d7280d2f0a46c0c13272ecdc7e64d5f13e:normalizeAppShortcutInputKey— thecodefallback is gated behindinput.altKey, which was written for macOS Option compositions. Without Alt held, the raw layoutkeyis returned.matchesAppShortcut— compares that value to the binding key.baseKeyFromCode— knows onlyKey*andDigit*positions, so punctuation bindings stay unreachable even once the fallback does run.Everything downstream of that comparison is affected:
AppCommandProvider.tsx#L313and#L339— every app command.plugin-command-keybindings.ts#L18— plugin shortcuts.desktop-browser-shortcuts.ts#L23— shortcuts inside the embedded browser.There is a second, quieter consequence.
appShortcutFromInputnormalizes through the same function when recording a custom shortcut, and the result is persisted. Rebinding a command while a Cyrillic layout is active stores{ key: "л", mod: true }— the override then works only on that layout, renders as "Ctrl + Л" in settings, and the conflict detector cannot tell it apart from a binding on the same physical key.appShortcutSchemaaccepts any string, so nothing rejects it.Also unreachable because
baseKeyFromCodereturnsnullfor their positions: the[/]thread-navigation defaults atapp-keybindings.ts#L154-L180.What you ruled out
matchesAppShortcutpath fails. Electron's known non-latin accelerator bugs are Linux-side and do not apply here.mainat370029d— I fetchedpackages/domain/src/app-keybindings.tsat that sha; the gating condition andbaseKeyFromCodeare unchanged from what is cited above.Suggested priority and effort
Hits every user whose daily driver is a non-latin layout (Russian, Ukrainian, Greek, Hebrew, Arabic, …) — they lose essentially the whole shortcut surface, and the only workaround is switching layout before every chord. No data loss. The fix is small and contained to one function plus a lookup table.
A fix is ready
A fix branched off
mainis linked below as a prototype per CONTRIBUTING.md; happy to open a PR if a maintainer would like one.https://github.com/irium/bb/tree/fix/non-latin-keyboard-shortcuts — single commit
d340b06, based onmainatc1a64f4.What it does:
KeyboardEvent.codewhenever the reportedkeyis a single non-ASCII character, not only when Alt is held.baseKeyFromCodethe punctuation positions (Comma,Period,Slash,Semicolon,Quote,BracketLeft/Right,Backslash,Backquote,Minus,Equal) so the[/]and,bindings resolve too.The non-ASCII condition is deliberate: layouts that report ASCII keep matching by character, so AZERTY still binds its own letters rather than the QWERTY positions. The existing test at
app-keybindings.test.ts#L175pins that behavior and still passes.Because shortcut recording shares the same normalization, the persisted-override problem described above is fixed by the same change. No schema validation or migration was added for already-stored non-latin overrides — happy to add either if preferred.
Tests: 6 added (Cyrillic
Ctrl+K/Ctrl+P/Ctrl+F,Ctrl+Shift+[,Ctrl+,, a negative case, and one for shortcut recording).@bb/domain215/215,@bb/appkeybinding suites 28/28; typecheck and lint clean ondomain,app,desktop,server.One behavior change worth flagging in review: on a non-latin layout the settings UI keeps displaying the latin key (for example "Ctrl + ,") while the key that physically fires it prints something else. Matching by position but labelling by the latin base seemed the right trade-off versus wiring up
navigator.keyboard.getLayoutMap(), but it is a product call.