Skip to content

Bug: Shift+Enter doesn't insert newline in xterm.js terminal — Kitty protocol not supported #963

@jrf0110

Description

@jrf0110

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

  • Shift+Enter inserts a newline in the Kilo TUI prompt when running in the Gastown dashboard terminal
  • Works in all three terminal contexts: Mayor terminal, agent terminal, and the PTY hook terminal
  • Does not break plain Enter (submit) behavior

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Should fix before soft launchbugSomething isn't workinggt:uiDashboard, settings, terminal, drawerskilo-auto-fixAuto-generated label by Kilokilo-triagedAuto-generated label by Kilo

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions