Kitty keyboard protocol + macOS editing keybinds over the web terminal - #79
Kitty keyboard protocol + macOS editing keybinds over the web terminal#79dkzlv wants to merge 8 commits into
Conversation
The Cmd+K command palette search input lacked autocomplete-suppression attributes, so the browser could show its native autocomplete/history dropdown over the palette. Add autocomplete/autocorrect/autocapitalize=off and spellcheck=false, matching the sibling overlays (Palette, Font, Remotes).
The Paste button fired from onPointerDown with e.preventDefault(), which suppresses the synthesized click. iOS Safari (WebKit) only authorises navigator.clipboard.readText() inside a genuine click/touch gesture, so the read rejected silently and pasting was a no-op. Route the Paste button through a real click via a new clickToActivate flag (other buttons keep their snappy pointerdown behaviour), and refocus the surface afterwards to keep the on-screen keyboard up.
Add a shared scrollbarStyle(theme) helper and apply it to every scrollable overlay/list: the Cmd+K switcher, theme/font pickers, remotes list, the generic OverlayPanel body, and the off-screen terminal rail. Uses CSS scrollbar-width/scrollbar-color so the scrollbar matches the active palette instead of the chunky native one.
The browser terminal only captured paste as text via the hidden textarea's input(insertFromPaste) event, and Ctrl+V's preventDefault suppressed the paste event entirely — so pasted images were silently dropped and TUIs like Claude Code read an empty clipboard on ^V. Intercept Ctrl+V to defer ^V, add a paste listener that forwards any clipboard image via sendClipboard(mime, bytes) to the compositor clipboard, then send ^V so the app reads a populated selection (transport messages are ordered). A fallback timer preserves ^V quoted-insert when no image/paste is present; Cmd+V / Ctrl+Shift+V text paths are unchanged.
The hidden capture textarea is kept empty, but iOS only auto-repeats Backspace while the field still has content to delete, so a held Backspace deleted a single character and stopped. Seed the textarea with an NBSP filler buffer on iOS so iOS's own key-repeat streams a deleteContentBackward per repeat; forward one DEL each and top the buffer back up on idle (or before it runs dry mid-hold). Filler is stripped from the typed-text and Ctrl/Alt modifier paths so it never reaches the shell.
The BLIT_EXPORT_SOCK row widened the Purpose column without realigning the rest of the table, tripping prettier --check in CI.
…erminal Modifier+key combos (Shift+Enter, Cmd+Backspace, Ctrl+Enter, ...) now reach TUIs like Claude Code instead of collapsing to their unmodified key, and macOS "natural text editing" chords work at a bare shell prompt. Kitty keyboard protocol (CSI-u), end-to-end: - alacritty-driver: enable kitty_keyboard; capture the CSI ? u query reply (Event::PtyWrite, previously dropped) and drain it back to the PTY; surface negotiated flags from TermMode in the frame snapshot. - remote: carry kitty_flags as a trailing S2C_UPDATE byte (old clients ignore it), force a frame on flag-only changes, parse defensively. - server: drain pty writes after driver.process(); reorder query replies so the CSI ? u answer isn't beaten by DA1. - browser: expose Terminal.kitty_flags() to JS. - js/core: new kitty.ts CSI-u encoder; keyboard.ts delegates to it when flags != 0 and stays byte-identical to the legacy path when flags == 0. macOS natural text editing keybinds (browser-side, gated on kitty == 0): - macEditingKeybind() maps Cmd/Option editing chords to the legacy control bytes readline/ZLE already bind (Cmd+Backspace -> Ctrl+U, Cmd+arrows -> Ctrl+A/E, Opt+Backspace -> Meta-DEL, Opt+arrows -> Meta-b/f), so they work at a plain shell without shell config, while a kitty-aware app still receives the real combo as CSI-u. Also: add pkgs.git to the dev shell and a virtual:blit-wasm module declaration for the website's typecheck.
Coverage
|
|
🔗 Preview: https://blit-k73dskpc4-indent.vercel.app |
Hmmm? |
|
@indent explain the why and how of the frame forcing on flag-only changes. |
|
Why. The web client can only emit CSI-u encoding for a keystroke if it knows the app has negotiated kitty mode — The catch is that It also isn't caught by the existing How. One clause is added to the if !title_changed
&& current.cursor_row == previous.cursor_row
&& current.cursor_col == previous.cursor_col
&& current.mode == previous.mode
&& current.kitty_flags == previous.kitty_flags // <-- new
{
return None;
}So a flags-only delta now skips the |
|
@indent could we leave less responsibility to the client by moving some of the logic server-side? |
What
Two related fixes so keyboard input over blit's web terminal behaves like a native terminal.
1. Kitty keyboard protocol (CSI-u), end-to-end
Modifier+key combos (Shift+Enter, Cmd+Backspace, Ctrl+Enter, …) previously collapsed to their unmodified key because a PTY is a raw byte stream and the legacy xterm encoding has no sequence for them. This implements the modern kitty keyboard protocol, which encodes every key as
CSI <codepoint>;<modifier-bitmask> uand only activates for apps that negotiate it — so combos now reach TUIs like Claude Code without regressing other apps.kitty_keyboard; capture theCSI ? ucapability-query reply (Event::PtyWrite, which we were dropping) and drain it back to the PTY; surface negotiated flags fromTermModein the frame snapshot.kitty_flagsas a trailingS2C_UPDATEbyte (old clients ignore it), force a frame on flag-only changes, parse defensively by payload length.driver.process(); reorder query replies so theCSI ? uanswer isn't beaten by DA1 (which would read as "no kitty").Terminal.kitty_flags()to JS.kitty.tsCSI-u encoder;keyboard.tsdelegates to it when flags != 0 and stays byte-identical to the legacy path when flags == 0.2. macOS "natural text editing" keybinds
At a bare shell prompt the kitty protocol is inactive (by design), so Cmd/Option editing chords did nothing. Mirroring Ghostty's default macOS keybinds,
macEditingKeybind()translates them to the legacy control bytes readline/ZLE already bind — so they work at a plain shell with no shell config, while a kitty-aware app still receives the real combo as CSI-u:Ctrl+UCtrl+A/Ctrl+EMeta-DELMeta-b/Meta-fGated on macOS and on
kittyFlags === 0.Testing
Local CI green:
./bin/lint— cargo fmt, prettier, clippy (-D warnings) all clean./bin/tests— Rust workspace tests pass; JS typecheck; core 388 / react 11 / solid 11 vitest; Python + Bun fd-channelNotes
quality-of-life-fixeshad a pre-existingprettier --checkfailure ondocs/server.md(unrelated table drift); fixed directly on that branch and this is rebased on top.pkgs.gitin the dev shell and avirtual:blit-wasmmodule declaration for the website typecheck.