Parent
Part of #204 (Phase 4: Hardening)
Problem
Pressing Shift+Enter in the Gastown dashboard's xterm.js terminal submits the prompt instead of inserting a newline. It works correctly in native terminals (Ghostty, Kitty, WezTerm, iTerm2).
Root Cause
The Kilo TUI uses the Kitty keyboard protocol (useKittyKeyboard: {} at packages/opencode/src/cli/cmd/tui/app.tsx:175). When activated, a capable terminal encodes Shift+Enter as \x1b[13;2u (CSI u with shift modifier), which OpenTUI parses as { name: "return", shift: true } and maps to the input_newline action.
xterm.js 6.0.0 does not support the Kitty keyboard protocol. It ignores the CSI > u activation sequence sent by the TUI. When the user presses Shift+Enter, xterm.js sends \r — identical to plain Enter. The TUI receives { name: "return", shift: false } and triggers "submit" instead of "newline."
The WebSocket proxy chain is fully transparent (verified: every hop is byte-for-byte passthrough). The problem is at the source — xterm.js encodes the wrong byte.
xterm.js version
6.0.0 — confirmed in cloud/node_modules/@xterm/xterm/package.json. Kitty protocol support was added in xterm.js 6.1.0-beta via PR #5600.
Comparison with the Kilo desktop/web app
The Kilo SolidJS app (packages/app/src/components/terminal.tsx) uses ghostty-web, not xterm.js. Ghostty implements the Kitty keyboard protocol natively — Shift+Enter encodes correctly as \x1b[13;2u.
Possible Fixes
Option A — attachCustomKeyEventHandler (quick fix): Intercept Shift+Enter at the DOM KeyboardEvent level and manually inject the CSI u sequence. The browser's KeyboardEvent has shiftKey available even though xterm.js doesn't encode it:
term.attachCustomKeyEventHandler((ev) => {
if (ev.type === 'keydown' && ev.key === 'Enter' && ev.shiftKey) {
term.input('\x1b[13;2u');
return false;
}
return true;
});
This needs to be added to all three terminal instances: useXtermPty.ts, MayorTerminalPane in TerminalBar.tsx, and AgentTerminalPane in TerminalBar.tsx.
Downside: this only fixes Shift+Enter. Other Kitty-protocol-dependent keybindings (Ctrl+Enter, Super+key, etc.) would need the same treatment one by one.
Option B — Upgrade xterm.js to 6.1+ or 7.0: Kitty keyboard protocol support landed in 6.1.0-beta (PR #5600). Upgrading would fix Shift+Enter and all other modified key combinations at once.
Option C — Replace xterm.js with ghostty-web: Match the Kilo desktop app's terminal stack. Ghostty natively supports the Kitty keyboard protocol. This is the most comprehensive fix but a larger migration.
Acceptance Criteria
Notes
- The Kilo TUI's newline keybindings are configurable (
config.ts:912-916): shift+return, ctrl+return, alt+return, ctrl+j. Ideally all of these should work, but Shift+Enter is the primary user expectation.
- The PTY is created with
TERM: "xterm-256color" (pty/index.ts:124), which is a legacy terminal type. If upgrading xterm.js, it may be worth considering whether TERM should be set to something that advertises Kitty protocol support (though the Kitty protocol is opt-in via escape sequences, not TERM-based).
Parent
Part of #204 (Phase 4: Hardening)
Problem
Pressing Shift+Enter in the Gastown dashboard's xterm.js terminal submits the prompt instead of inserting a newline. It works correctly in native terminals (Ghostty, Kitty, WezTerm, iTerm2).
Root Cause
The Kilo TUI uses the Kitty keyboard protocol (
useKittyKeyboard: {}atpackages/opencode/src/cli/cmd/tui/app.tsx:175). When activated, a capable terminal encodes Shift+Enter as\x1b[13;2u(CSI u with shift modifier), which OpenTUI parses as{ name: "return", shift: true }and maps to theinput_newlineaction.xterm.js 6.0.0 does not support the Kitty keyboard protocol. It ignores the CSI > u activation sequence sent by the TUI. When the user presses Shift+Enter, xterm.js sends
\r— identical to plain Enter. The TUI receives{ name: "return", shift: false }and triggers "submit" instead of "newline."The WebSocket proxy chain is fully transparent (verified: every hop is byte-for-byte passthrough). The problem is at the source — xterm.js encodes the wrong byte.
xterm.js version
6.0.0 — confirmed in
cloud/node_modules/@xterm/xterm/package.json. Kitty protocol support was added in xterm.js 6.1.0-beta via PR #5600.Comparison with the Kilo desktop/web app
The Kilo SolidJS app (
packages/app/src/components/terminal.tsx) uses ghostty-web, not xterm.js. Ghostty implements the Kitty keyboard protocol natively — Shift+Enter encodes correctly as\x1b[13;2u.Possible Fixes
Option A —
attachCustomKeyEventHandler(quick fix): Intercept Shift+Enter at the DOMKeyboardEventlevel and manually inject the CSI u sequence. The browser'sKeyboardEventhasshiftKeyavailable even though xterm.js doesn't encode it:This needs to be added to all three terminal instances:
useXtermPty.ts,MayorTerminalPaneinTerminalBar.tsx, andAgentTerminalPaneinTerminalBar.tsx.Downside: this only fixes Shift+Enter. Other Kitty-protocol-dependent keybindings (Ctrl+Enter, Super+key, etc.) would need the same treatment one by one.
Option B — Upgrade xterm.js to 6.1+ or 7.0: Kitty keyboard protocol support landed in 6.1.0-beta (PR #5600). Upgrading would fix Shift+Enter and all other modified key combinations at once.
Option C — Replace xterm.js with ghostty-web: Match the Kilo desktop app's terminal stack. Ghostty natively supports the Kitty keyboard protocol. This is the most comprehensive fix but a larger migration.
Acceptance Criteria
Notes
config.ts:912-916):shift+return,ctrl+return,alt+return,ctrl+j. Ideally all of these should work, but Shift+Enter is the primary user expectation.TERM: "xterm-256color"(pty/index.ts:124), which is a legacy terminal type. If upgrading xterm.js, it may be worth considering whetherTERMshould be set to something that advertises Kitty protocol support (though the Kitty protocol is opt-in via escape sequences, not TERM-based).