## 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.
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
OpenTUI Core
@opentui/core supplies the renderer and TypeScript API. You use imperative renderables and events directly. The
TypeScript packages call native Zig through an internal foreign function interface (FFI) boundary.
import { TextRenderable, createCliRenderer } from "@opentui/core"
const renderer = await createCliRenderer({ exitOnCtrlC: true })
renderer.root.add(new TextRenderable(renderer, { content: "Hello, OpenTUI!" }))
exitOnCtrlC: true calls renderer.destroy() when the user presses Ctrl+C. The code that creates the renderer must
call renderer.destroy() on every other shutdown path.
Install the package:
bun add @opentui/core
Then build your first app with the quickstart.
Runtime and platform support
@opentui/core runs on Bun 1.3.0 or later, or on Node.js 26.4.0 or later with ECMAScript modules (ESM) and
--experimental-ffi.
The native ABI and the generated platform packages, such as @opentui/core-linux-x64, are internal distribution
surfaces, not application APIs.
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.
License
OpenTUI is licensed under the MIT License.