Kit Langton aef16edbac fix(core): preserve keyboard layouts in embedded terminals (#1477)
## Why

In OpenCode's embedded terminal, Dvorak Ctrl+U and Ctrl+D can reach
Neovim as Ctrl+F and Ctrl+H when the child uses Kitty keyboard encoding.
Paging up instead pages down, and paging down moves the cursor left.

The incoming Kitty event correctly separates the active-layout character
from its base-layout alternative. `EmbeddedTerminalRenderable` currently
passes the alternative as the native encoder's unshifted character,
changing the key's identity.

## What Changes

Keep the active-layout character as the primary key, and use the
base-layout alternative only to infer the physical key when an explicit
physical code is unavailable.

| Incoming Dvorak input | Before | After |
| --- | --- | --- |
| Plain u, base-layout F | u (117), byte `75` hex | Unchanged |
| Ctrl+U, base-layout F | Ctrl+F | Ctrl+U |
| Ctrl+D, base-layout H | Ctrl+H | Ctrl+D |
| Ctrl+Shift+U, base-layout F | Ctrl+Shift+F | Ctrl+Shift+U |

If the child requests alternate-key reporting, the original base-layout
alternative remains available separately. Legacy control-byte output
stays unchanged.

This explains why ordinary typing can work while control shortcuts fail:
in the reproduced Kitty mode, plain letters use the translated text,
while control shortcuts use the incorrectly selected key codepoint.
Ctrl+U becomes `ESC[102;5u` instead of `ESC[117;5u`; Ctrl+D becomes
`ESC[104;5u` instead of `ESC[100;5u`.

Regression coverage parses real Kitty sequences and exercises the native
encoder. One table covers plain Dvorak u/d, control shortcuts, Shift,
Cyrillic input, a QWERTY control case, legacy child mode, and
alternate-key reporting. A focused-delivery test covers
press/repeat/release events.

## Demo

Real Neovim 0.12.4 running in the existing `embedded-terminal-demo.ts`
example with the production `EmbeddedTerminalRenderable`. The inspector
shows:

1. The received key, active-layout and base-layout codepoints, and raw
input sequence.
2. The actual `onData` output forwarded unchanged to the PTY, in escaped
and hexadecimal form. The inspector does not re-encode the key.
3. Neovim's independently observed key via `vim.on_key`, plus its
resulting cursor position.

Each case starts at line 80, column 8, with `scroll=8`. Plain u is typed
in Insert mode; Ctrl+D/U are pressed in Normal mode. The clean Neovim
fixture explicitly enables Kitty mode with `CSI > 1 u`; Dvorak Kitty
input sequences are injected at the outer terminal boundary rather than
generated by an OS keyboard layout.

Before uses the unchanged `EmbeddedTerminal.ts` from `fe547ebe`, loaded
through a test-only module override. After uses `03b84c9a`. The three
independent cases are reordered to show plain typing first, slowed to
0.1×, and held for four seconds each; setup and resets are omitted. Both
processes returned cleanly after the check.

**Before:** Plain u stays 117. Ctrl+D changes 100 → 104 and Neovim sees
`<C-H>`; Ctrl+U changes 117 → 102 and Neovim sees `<C-F>`.


https://github.com/user-attachments/assets/6e1b469f-e4d2-4298-a095-8f422e10a8d0

**After:** Plain u still works. Ctrl+D stays 100 and Neovim sees
`<C-D>`; Ctrl+U stays 117 and Neovim sees `<C-U>`.


https://github.com/user-attachments/assets/b825e1f0-b7e6-4f1b-a112-e780a9f34fe8

## Scope

This fixes OpenTUI's embedded-terminal key adapter, without
application-specific remapping or a public API change. OpenCode will
need to adopt an OpenTUI release containing the fix.

## Verification

```sh
cd packages/core
bun run test src/renderables/EmbeddedTerminal.test.ts
bun run typecheck
bun run test:js:node
bun run build:lib
bun run test
cd ../..
bun run fmt:check
bun run lint
```

- Focused suite: seven regressions fail with the baseline encoder, while
plain typing passes; all 31 tests pass afterward. Rechecked after
consolidating the repeated test setup.
- Full Bun suite after building the parser assets: 5,587 passed, 26
skipped, zero failures. The initial unbuilt-worktree run failed only
because `parser.worker.js` was absent.
- Node suite: 4,842 passed, seven skipped, zero failures.
- Typechecking, formatting, and lint pass.
- Real PTY before/after verification shown above.
2026-09-03 20:37:56 -04:00
2025-07-21 17:47:38 +02:00
2026-03-23 15:59:52 +01:00
2026-08-07 12:40:13 +02:00
2026-09-01 15:38:50 +00:00
2025-08-12 12:01:55 +02:00

OpenTUI logo

npm version Core build status

OpenTUI is a library to build terminal user interfaces.

  • It is written in Zig.
  • You write TypeScript directly or through React and Solid.
  • You arrange boxes and text with flexbox.
  • You add selects, inputs, and scroll boxes with keyboard and mouse controls.
  • You can play sounds, show images, and render 3D graphics.

OpenCode uses OpenTUI in production for millions of users.

Website | Documentation | Packages

Packages

  • @opentui/core - Native terminal UI library with TypeScript bindings.
  • @opentui/react - React renderer for building terminal user interfaces with OpenTUI Core.
  • @opentui/solid - Solid renderer for building terminal user interfaces with OpenTUI Core.
  • @opentui/keymap - Key binding and command routing library for terminal and browser applications.
  • @opentui/qrcode - QR code encoder and renderable with React and Solid surfaces.
  • @opentui/three - Three.js WebGPU renderer for OpenTUI.
  • @opentui/ssh - SSH server integration for OpenTUI applications.

Private packages

  • @opentui/native - Private workspace with the Zig implementation, native tests, and benchmarks.
  • @opentui/examples - Private workspace with example applications and a standalone examples executable.
  • @opentui/web - Private workspace with the documentation website and the AI agent skill.

AI agent skill

Install the OpenTUI documentation as a skill for your AI coding assistant with npx skills:

npx skills add anomalyco/opentui --skill opentui

Add -g to install the skill globally.

Development

Development requires Bun 1.3.0 or later and Zig 0.16.0.

See AGENTS.md for engineering, tooling, and verification conventions.

bun install

Run package scripts from their package directory. Run bun run build from the repository root after you change native code or cross-package build outputs. Ordinary TypeScript changes do not require a root build.

Run repository tests and static checks from the repository root:

bun run test
bun run fmt:check
bun run lint

To link the source packages into a Bun-managed project, install that project's dependencies, then run ./scripts/link-opentui-dev.sh /path/to/project from the repository root. Add --react or --solid when needed.

Contributing

OpenTUI is open for contributions.

License

OpenTUI is licensed under the MIT License.

S
Description
Build terminal UIs with OpenTUI. Covers Core, frameworks, components, application APIs, testing, extensions, integrations, deployment, and public API lookup.
Readme MIT 109 MiB
Languages
TypeScript 67.7%
Zig 24.3%
MDX 5.9%
Astro 0.7%
JavaScript 0.6%
Other 0.6%