# fix\(web\): right panel launcher letters work on non-Latin keyboard layouts · PR \#21508 · pingdotgg/t3code

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

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

Visibility: public

State: open

Source repository: Unavailable fork

Source branch: fix/launcher-letters-non-latin

Target branch: main

Head commit: 638cdead41fb86094cdbae6c931e846da8624744

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/21508)

[Commits](https://git.cafe/pingdotgg/t3code/pull/21508/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/21508/diffs?format=markdown)

## Description

---

## Problem

The right panel's empty launcher and its add-surface menu open a surface from a bare letter, B for Browser, T for Terminal, F for Files and so on. `surfaceShortcutActionForKey` compares `event.key` with that letter and nothing else. On a Ukrainian or Russian layout the T key reports the Cyrillic `е` with `code` `KeyT`, and a Greek layout types Greek letters on the same keys, so no action matches. Nothing claims the letter, and type-to-focus sends it into the composer instead. Modified shortcuts already fall back to the physical key in this case through `resolveEventKeys`. That is why Cmd+Opt+B still toggles the right panel on those layouts while a bare T lands in the composer. In #13427, artile reported this and also opened the first PR for it.

## Change

`SurfaceShortcutEvent` gains an optional `code` and `getModifierState`. `surfaceShortcutActionForKey` lowercases `event.key` and, when that has only letters or combining marks from non-Latin scripts such as Cyrillic, Greek, Thai or Arabic, also takes the letter from a `KeyA` to `KeyZ` code. An available action matches on either. When the typed key is already a Latin letter, the matcher ignores the physical key, so Dvorak and Colemak keep matching what they type. Both launcher call sites share this one function.

Its guard is narrower than the one in `resolveEventKeys`, which falls back for any key outside `a` to `z`. A modified shortcut types nothing, so that wider fallback costs nothing there. A bare key does type, and on a Latin layout it types text the user wants, like Bépo `è` on the T key, the French AZERTY comma on the M key or a `Dead` key starting an accent. Those keys match only what they type. Latin layouts can type the launcher letters themselves, so nothing is lost.

The guard accepts a run of characters because a key can type several. The B key on the [Windows Arabic 101 layout](https://learn.microsoft.com/en-us/globalization/keyboards/kbda1) types lam and alef together. Reading the Chromium, Firefox and WebKit sources, I found that Firefox reports both in one `event.key`, and so does Safari on the macOS Arabic PC layout, which has the same key. Chromium reports at most one typed character. On Windows it reports `Unidentified` for this key, which matches nothing, as on main. On macOS it reports only the alef, which already falls back.

The same sources show that on Linux, and on Windows with a layout that has an AltGr key, Chromium and Firefox report AltGr as AltGraph, with `ctrlKey` and `altKey` false, so the early return on those keys misses it. A German layout types the micro sign `µ` on AltGr+M, a letter outside the Latin script, so the fallback alone would match the M key and open Device. The matcher now returns early when AltGraph is held and `event.key` is not an ASCII letter or digit, the test `matchesKeybindingShortcut` uses outside macOS. A Finnish layout on Linux types `b` on AltGr+B and still opens Browser, as on main. On macOS, Option sets `altKey` in Chromium, WebKit and Firefox, so the early return already covers it.

`shouldTypeToFocusComposer` needs no change. The `data-surface-launcher-keys` check quoted in the triage comment is already gone from it, and it skips a key event the launcher has already handled.

## Scope and approval

This fixes the shortcut half of [#13427](https://github.com/pingdotgg/t3code/issues/13427). The [triage comment](https://github.com/pingdotgg/t3code/issues/13427#issuecomment-5815297641) prescribes the guard from `resolveEventKeys` and adds "A Dvorak or Colemak layout that types a different Latin letter must keep matching `event.key` alone." This PR keeps that rule but narrows the guard to non-Latin letters and marks, for the reason under Change. The [closing comment](https://github.com/pingdotgg/t3code/pull/13428#issuecomment-5935848900) on the reporter's combined PR #13428 says "Submit the focused shortcut repair separately", and this is that submission. This PR leaves the dialog half of the issue alone.

#13409 landed the `resolveEventKeys` fallback for Mod+B. #18132 is open in the same launcher area but fixes a different bug, launcher letters blocked by a closed menu that stays mounted. It does not change the matcher, so the two do not overlap.

## Verification

I ran these from `apps/web` on Node 26 after the repo's typecheck patch step. The repo pins Node 24.

- `vp test run src/components/RightPanelTabs.test.tsx` has 17 tests. With the new cases and the original matcher, 1 fails, the non-Latin case. The PR's first commit fails the accented letter, punctuation, dead key and AltGr cases, its second fails the lam alef and AltGr cases, and its third fails the AltGr case. With the change, all 17 pass.
- The cases cover `и`, `β`, the Thai vowel sign `ิ` and the Arabic lam alef `لا` on the B key opening Browser, Dvorak `x` and `X` on the B key opening nothing, `b` on the N key opening Browser, Bépo `è` and `Dead` on the T key leaving Terminal shut, the French AZERTY comma on the M key leaving Device shut, `в` on the D key leaving an unavailable Diff shut, German AltGr+M typing `µ` leaving Device shut, and Finnish AltGr+B typing `b` still opening Browser.
- Wrong versions of the guard each fail at least one of those cases. Matching the physical key whenever a code is present fails the Dvorak case, and so does letting the physical letter win over the typed one. Using the physical key only when the typed key is longer than one character, skipping it for any Unicode letter, or not lowercasing the physical letter fails the Ukrainian case. A Cyrillic-only fallback fails the Greek case. Checking availability only for the typed letter fails the Diff case. The first commit's `a` to `z` check fails the `è`, comma and `Dead` cases, and falling back for any letter outside `a` to `z` fails the `è` case. Falling back for anything but a single Latin letter fails the comma and `Dead` cases, and dropping the single-letter check fails the comma case. Leaving combining marks out of the guard fails the Thai case. Accepting only a single letter or mark fails the lam alef case. Accepting any run of letters and marks without the Latin check fails the Dvorak, `è` and `Dead` cases, and excluding only `a` to `z` from that run fails the `è` case. Dropping the AltGraph check, or asking for a modifier named AltGr, fails the `µ` case. Ignoring every AltGraph key fails the Finnish `b` case, and applying the ASCII check to every key fails the Ukrainian case.
- Typecheck for `apps/web`, lint with unused directive reporting, the format check on the three changed files and `git diff --check` all pass.
- `RightPanelTabs.keyboard.browser.test.tsx` gets one case. It mounts the launcher with Terminal available, presses `е` with `code` `KeyT` through the file's own `press` helper, and checks that the event was prevented and Terminal opened once. In headless Chromium the file reported 9 passed and 1 failed with the original matcher, the new case failing on `defaultPrevented`, and 10 passed with the change.

I also ran the local web app in Chrome on a new-thread draft in an empty project folder, with the right panel launcher open. I dispatched `keydown` events on `window`. Before the change, `е` with `code` `KeyT` left the launcher open and put `е` into the composer. After it, the same event opened Terminal 1 and the composer stayed empty. A Dvorak `x` with `code` `KeyB` did not open Browser on either build and went into the composer, and a QWERTY `t` opened Terminal on both builds. That app run used the PR's first commit. The later matcher changes give the same result for those three events, and I did not rerun the app.

On the fixed build, the first QWERTY `t` after I closed a terminal and reopened the right panel went into the composer, and the same event opened Terminal after a reload. On main in the app, with trusted key presses from browser automation, not dispatched events, opening the empty launcher with its toolbar button after a reload and pressing T put `t` in the composer 4 of 4 times. I filed it as #21541, and #21556 is the open fix PR for it. A jsdom test with the real type-to-focus handler and main's matcher puts `t` in the composer when the launcher mounts after the chat view. The launcher adds its key listener after type-to-focus, which then takes the letter first. For a Latin letter the new matcher behaves exactly like the old one, so this change does not cause it.

**Before**

<img src="https://github.com/user-attachments/assets/d11217b5-94f2-44a1-acd4-e2c3f927c8fd" width="720" alt="Before the change, the launcher stays open and е is in the composer">

**After**

<img src="https://github.com/user-attachments/assets/518e1a55-e2d8-4e26-a8f3-91d6e658596d" width="720" alt="After the change, Terminal 1 is open and the composer is empty">

The app events were synthetic, and I did not press a key on a real Ukrainian, Greek, Thai, Arabic, Bépo, AZERTY, German or Finnish input source. The app run used only the Cyrillic event, so the other layouts are covered by the unit tests alone. What the engines report for lam alef and for AltGr comes from reading the Chromium, Firefox and WebKit sources, not from a run. I ran the web client only and did not run desktop, which uses the same launcher.
