#3967 · Layout characters bypass shortcut fallback
Verdict: REPRODUCED · Root-cause confidence: high
1. TL;DR
The shared shortcut matcher rejects a Latin binding when a keyboard event reports a Cyrillic character at the corresponding physical key. The normalizer uses the physical code only with Alt held, and its physical mapping omits punctuation. Shortcut recording uses the same normalizer, so a recorded Cyrillic character remains layout-specific. This report reproduces the shared function behavior; it does not claim a Windows desktop or native Electron menu verification.
2. Claims vs findings
| Claim | Finding | Evidence |
|---|---|---|
| Non-Latin characters fail Latin shortcut matching | Verified at shared function boundary | Three failing assertions in each clean run |
| Punctuation positions also fail | Verified | Semicolon and BracketLeft cases; no physical punctuation table |
| Recording preserves layout-specific characters | Verified normalization; recording consumer traced statically | Saved-binding control passes; recording calls this same function |
| All Windows shortcuts fail while native menu accelerators work | Unverified as a blanket statement | No Windows machine or native menu exercised; ASCII control succeeds |
3. Environment
Trusted get-bb/bb origin/main commit b8866c6e1dbb1e9b043df3d4474178459a5b4f9a. macOS, Darwin arm64, Node 22.22.3, pnpm 9.15.0 through Corepack. Two separate temporary Git worktrees at the same commit. No provider, browser, server, ports, runtime data, or user settings used.
4. Minimal reproduction
- Check out the trusted base commit shown above.
- Install dependencies with
corepack pnpm install --frozen-lockfile --prefer-offline. - Save the test below as
packages/domain/test/issue-3967.test.ts. - Run
corepack pnpm exec turbo run test --filter=@bb/domain -- issue-3967.
The first three cases expect the physical base binding to match. The two controls pin existing ASCII layout matching and accepted saved Cyrillic bindings.
import { describe, expect, it } from "vitest";
import { matchesAppShortcut, normalizeAppShortcutInputKey, type AppShortcutInput } from "../src/app-keybindings";
const chord = { mod: true, meta: false, control: false, alt: false, shift: false };
function input(key: string, code: string): AppShortcutInput {
return { key, code, ctrlKey: true, metaKey: false, altKey: false, shiftKey: false };
}
describe("issue 3967 shared shortcut path", () => {
it.each([
["в", "KeyD", "d"],
["ж", "Semicolon", ";"],
["х", "BracketLeft", "["],
])("resolves %s at %s to %s", (character, code, base) => {
const event = input(character, code);
expect(matchesAppShortcut(event, { ...chord, key: base }, false)).toBe(true);
expect(normalizeAppShortcutInputKey(event)).toBe(base);
});
it("retains ASCII layout character matching", () => {
expect(matchesAppShortcut(input("a", "KeyQ"), { ...chord, key: "a" }, false)).toBe(true);
expect(matchesAppShortcut(input("a", "KeyQ"), { ...chord, key: "q" }, false)).toBe(false);
});
it("currently accepts a saved non-Latin character binding", () => {
const event = input("в", "KeyD");
const saved = { ...chord, key: normalizeAppShortcutInputKey(event) };
expect(saved.key).toBe("в");
expect(matchesAppShortcut(event, saved, false)).toBe(true);
expect(matchesAppShortcut(input("d", "KeyD"), saved, false)).toBe(false);
});
});
5. Root cause
Physical-key normalization is gated by input.altKey. Without Alt, it returns the reported character (with a limited shifted-symbol adjustment). baseKeyFromCode supports only Key and Digit codes. Matching compares the lowercased normalized character with the saved shortcut string. Modifiers are checked separately, so the character mismatch alone rejects the chord.
if (input.altKey && !isAsciiAlphanumeric(input.key)) {
const fromCode = baseKeyFromCode(input.code);
if (fromCode !== null) return fromCode;
}Recording uses that normalizer. App commands, shortcut conflict detection and embedded-browser shortcuts use the matcher. This explains the shared failure, without requiring focus-routing assumptions.
6. Proposed fix and automatic-fix decision
Add a fallback for non-Latin printable input and map punctuation codes, while preserving character matching for ASCII layouts. Before changing shared normalization, decide how existing character-based custom shortcuts remain valid and how physical-key conflicts are represented. The current schema accepts arbitrary key strings; changing recording normalization alone does not update saved overrides. A matcher-only fallback could preserve saved character bindings but allow two distinct saved keys to resolve to the same physical chord, which the existing conflict detector must handle. That detector supplies an empty physical code and compares saved key strings, so it cannot currently identify such physical aliases.
No automatic PR: choosing compatibility and conflict behavior for saved non-Latin shortcuts is a product decision, outside the rule’s simple-fix gate. No production code was changed or pushed. No linked open PR was found through the issue timeline or open-PR search.
7. Related issues
Issue #3184 concerns embedded-browser focus routing. This reproduction calls the shared matcher directly and does not require a focused browser.
8. Verification
The same agent ran the test in two fresh detached worktrees at the recorded base commit. Both commands executed (neither test was a cache hit) and exited 1 with the same three failed assertions and two passing controls. No production changes were present in either checkout. No ports or data directories were needed. The second run supports the original root cause; no correction was required.
corepack pnpm exec turbo run test --filter=@bb/domain -- issue-3967
FAIL resolves в at KeyD to d
FAIL resolves ж at Semicolon to ;
FAIL resolves х at BracketLeft to [
AssertionError: expected false to be true // Object.is equality
Test Files 1 failed (1)
Tests 3 failed | 2 passed (5)
First run: Duration 3.71s
Second run: Duration 4.37sThe host pnpm shim initially referenced a missing installation. The test commands used a temporary pnpm wrapper that delegates to Corepack and the repository-pinned pnpm 9.15.0. No dependency was added.
9. Appendix and trust boundary
The issue and its linked prototype were treated as untrusted claims. No issue script, patch, branch, or linked external URL was fetched or executed. The test was authored from trusted repository functions. The same agent repeated the reproduction; this is not independent verification. Reproduction code and output are inline under the reports repository’s publication rules; local raw artifacts were retained but not published.