# Files changed · PR \#21508 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/21508/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[fix\(web\): right panel launcher letters work on non-Latin keyboard layouts](https://git.cafe/pingdotgg/t3code/pull/21508.md)

Version: 14

Head commit: 638cdead41fb86094cdbae6c931e846da8624744

Base commit: d36b7da2485bc889b320a285deea181f637d760a

Comparison base: d36b7da2485bc889b320a285deea181f637d760a

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/web/src/components/RightPanelTabs.keyboard.browser.test.tsx

Status: modified; +11; −0

```diff
@@ -280,6 +280,17 @@
   });
 });
 
+describe("right panel launcher letters", () => {
+  it("opens a surface from the physical key when the layout types a non-Latin letter", async () => {
+    const addTerminal = vi.fn();
+    await renderPanel({ terminalAvailable: true, onAddTerminal: addTerminal });
+    // Ukrainian layout: the T key types "е".
+    const event = await press("\u0435", { code: "KeyT" });
+    expect(event.defaultPrevented).toBe(true);
+    expect(addTerminal).toHaveBeenCalledOnce();
+  });
+});
+
 describe("right panel tab middle click", () => {
   it("cancels middle-button defaults and closes once while preserving other buttons", async () => {
     const onActivate = vi.fn();
```

## apps/web/src/components/RightPanelTabs.test.tsx

Status: modified; +69; −0

```diff
@@ -36,6 +36,8 @@
   const actions = [
     { shortcut: "B", available: true, label: "Browser" },
     { shortcut: "D", available: false, label: "Diff" },
+    { shortcut: "T", available: true, label: "Terminal" },
+    { shortcut: "M", available: true, label: "Device" },
   ] as const;
 
   it("matches available surface shortcuts case-insensitively", () => {
@@ -43,8 +45,60 @@
     expect(surfaceShortcutActionForKey(actions, shortcutEvent("B"))).toBe(actions[0]);
   });
 
+  it("falls back to the physical key when the layout types a non-Latin letter or mark", () => {
+    // Ukrainian layout: the B key types "и".
+    expect(surfaceShortcutActionForKey(actions, shortcutEvent("\u0438", { code: "KeyB" }))).toBe(
+      actions[0],
+    );
+    // Greek layout: the B key types "β".
+    expect(surfaceShortcutActionForKey(actions, shortcutEvent("\u03b2", { code: "KeyB" }))).toBe(
+      actions[0],
+    );
+    // Thai layout: the B key types the vowel sign "\u0e34", a combining mark.
+    expect(surfaceShortcutActionForKey(actions, shortcutEvent("\u0e34", { code: "KeyB" }))).toBe(
+      actions[0],
+    );
+    // Arabic (101) layout: the B key types lam alef "\u0644\u0627", two code points.
+    expect(
+      surfaceShortcutActionForKey(actions, shortcutEvent("\u0644\u0627", { code: "KeyB" })),
+    ).toBe(actions[0]);
+  });
+
+  it("follows the typed letter on Latin layouts that move it", () => {
+    // Dvorak: the key at QWERTY B types "x", and "b" sits on QWERTY N.
+    expect(surfaceShortcutActionForKey(actions, shortcutEvent("x", { code: "KeyB" }))).toBeNull();
+    // The same key with Shift or Caps Lock types "X".
+    expect(surfaceShortcutActionForKey(actions, shortcutEvent("X", { code: "KeyB" }))).toBeNull();
+    expect(surfaceShortcutActionForKey(actions, shortcutEvent("b", { code: "KeyN" }))).toBe(
+      actions[0],
+    );
+  });
+
+  it("keeps accented Latin letters typed on a letter key", () => {
+    // Bépo: the T key types "è".
+    expect(
+      surfaceShortcutActionForKey(actions, shortcutEvent("\u00e8", { code: "KeyT" })),
+    ).toBeNull();
+  });
+
+  it("keeps punctuation typed on a letter key", () => {
+    // French AZERTY: the M key types ",".
+    expect(surfaceShortcutActionForKey(actions, shortcutEvent(",", { code: "KeyM" }))).toBeNull();
+  });
+
+  it("keeps a dead key on a letter key", () => {
+    // A dead key starts an accented letter on a Latin layout.
+    expect(
+      surfaceShortcutActionForKey(actions, shortcutEvent("Dead", { code: "KeyT" })),
+    ).toBeNull();
+  });
+
   it("does not activate unavailable surfaces", () => {
     expect(surfaceShortcutActionForKey(actions, shortcutEvent("d"))).toBeNull();
+    // Ukrainian layout: the D key types "в".
+    expect(
+      surfaceShortcutActionForKey(actions, shortcutEvent("\u0432", { code: "KeyD" })),
+    ).toBeNull();
   });
 
   it("leaves modified, composing, and already-handled key events alone", () => {
@@ -56,6 +110,21 @@
       surfaceShortcutActionForKey(actions, shortcutEvent("b", { defaultPrevented: true })),
     ).toBeNull();
   });
+
+  it("leaves AltGr text alone unless it is a plain letter", () => {
+    const getModifierState = (key: string) => key === "AltGraph";
+    // German layout: AltGr+M types "\u00b5" and sets only AltGraph on Linux and Windows.
+    expect(
+      surfaceShortcutActionForKey(
+        actions,
+        shortcutEvent("\u00b5", { code: "KeyM", getModifierState }),
+      ),
+    ).toBeNull();
+    // Finnish layout: AltGr+B types "b", which still matches, as it does in keybindings.ts.
+    expect(
+      surfaceShortcutActionForKey(actions, shortcutEvent("b", { code: "KeyB", getModifierState })),
+    ).toBe(actions[0]);
+  });
 });
 
 describe("surface shortcut typing contexts", () => {
```

## apps/web/src/components/RightPanelTabs.tsx

Status: modified; +22; −4

```diff
@@ -248,17 +248,35 @@
 type SurfaceShortcutEvent = Pick<
   KeyboardEvent,
   "altKey" | "ctrlKey" | "defaultPrevented" | "isComposing" | "key" | "metaKey"
->;
+> &
+  Partial<Pick<KeyboardEvent, "code" | "getModifierState">>;
 
 export function surfaceShortcutActionForKey<
   const Action extends { available: boolean; shortcut: string },
 >(actions: readonly Action[], event: SurfaceShortcutEvent): Action | null {
   if (event.defaultPrevented || event.isComposing) return null;
   if (event.metaKey || event.ctrlKey || event.altKey) return null;
+  // Linux and Windows report AltGr as AltGraph without Ctrl or Alt, which the
+  // check above misses, while macOS Option always sets altKey. As in
+  // keybindings.ts, AltGr text other than a plain letter or digit, like the
+  // micro sign on German AltGr+M, is not a launcher letter.
+  if (event.getModifierState?.("AltGraph") && !/^[a-z0-9]$/i.test(event.key)) return null;
+  const typed = event.key.toLowerCase();
+  // A layout that types non-Latin letters or combining marks matches by
+  // physical key, like the modified shortcuts in keybindings.ts. One key can
+  // type several, such as the Arabic lam alef ligature. Any other key, such as
+  // an accented Latin letter, punctuation or a dead key, matches only what it
+  // types.
+  const physical =
+    /^[\p{L}\p{M}]+$/u.test(typed) && !/\p{Script=Latin}/u.test(typed)
+      ? event.code?.match(/^Key([A-Z])$/)?.[1]?.toLowerCase()
+      : undefined;
   return (
+    actions.find((action) => {
+      if (!action.available) return false;
-    actions.find(
-      (action) => action.available && action.shortcut.toLowerCase() === event.key.toLowerCase(),
-    ) ?? null
+      const shortcut = action.shortcut.toLowerCase();
+      return shortcut === typed || shortcut === physical;
+    }) ?? null
   );
 }
 
```
