diff --git a/docs/superpowers/specs/2026-05-27-kitty-keyboard-design.md b/docs/superpowers/specs/2026-05-27-kitty-keyboard-design.md new file mode 100644 index 0000000..cdb043c --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-kitty-keyboard-design.md @@ -0,0 +1,330 @@ +# Kitty Keyboard Protocol Support + +## Summary + +Add full kitty keyboard protocol support to vue-tui, matching Ink's implementation. This covers three layers: protocol lifecycle (enable/disable/auto-detect), useInput Key interface extension, and comprehensive test backfill. + +The kitty keyboard protocol is an opt-in terminal enhancement that provides disambiguated key events, additional modifiers (super, hyper, capsLock, numLock), event types (press/repeat/release), and text-as-codepoints fields. vue-tui already has the parsing layer (parse-keypress.ts) but lacks the terminal handshake and useInput integration. + +Reference: https://sw.kovidgoyal.net/kitty/keyboard-protocol/ + +## Current State + +| Layer | Status | Details | +|-------|--------|---------| +| Parsing (parse-keypress.ts) | Complete | Decodes CSI u sequences, kitty modifiers, event types, text-as-codepoints | +| useInput Key interface | Partial | Exposes ctrl/shift/meta but not super/hyper/capsLock/numLock/eventType | +| Protocol lifecycle | Missing | No enable/disable sequences, no auto-detect, no MountOptions field | +| Tests | 1 of ~85 | Only Ctrl+C via kitty codepoint-3 form exists | + +## Architecture + +### New file: `packages/runtime/src/io/kitty-keyboard.ts` + +Self-contained module with types, constants, query/response matching, and lifecycle controller. + +#### Types & Constants + +```ts +export const kittyFlags = { + disambiguateEscapeCodes: 1, + reportEventTypes: 2, + reportAlternateKeys: 4, + reportAllKeysAsEscapeCodes: 8, + reportAssociatedText: 16, +} as const; + +export type KittyFlagName = keyof typeof kittyFlags; + +export type KittyKeyboardOptions = { + mode?: 'auto' | 'enabled' | 'disabled'; + flags?: KittyFlagName[]; +}; + +export function resolveFlags(flags: KittyFlagName[]): number; +``` + +#### Query/Response Matching + +Functions for detecting terminal responses to the `\x1b[?u` capability query: + +- `matchKittyQueryResponse(buffer, startIndex)` — detects `\x1b[?u` pattern in a byte buffer. Returns `{state: 'complete', endIndex}` or `{state: 'partial'}` or `undefined`. +- `hasCompleteKittyQueryResponse(buffer)` — scans entire buffer for any complete response. +- `stripKittyQueryResponsesAndTrailingPartial(buffer)` — removes complete responses and trailing partial sequences, returns remaining bytes to re-emit to the input pipeline. + +These operate on `number[]` byte buffers because terminal responses can arrive as raw bytes (Uint8Array) and may be interleaved with user input. + +#### Lifecycle Controller + +```ts +export function createKittyKeyboardController( + stdin: NodeJS.ReadStream, + stdout: NodeJS.WriteStream, +): KittyKeyboardController; + +interface KittyKeyboardController { + init(options: KittyKeyboardOptions | undefined, interactive: boolean): void; + dispose(): void; + readonly isEnabled: boolean; +} +``` + +**init(options, interactive):** +1. If options not provided or `mode === 'disabled'` — no-op. +2. Resolve flags (default: `['disambiguateEscapeCodes']`). +3. `mode === 'enabled'` — force-enable if both stdin and stdout are TTYs. Write `\x1b[>${resolvedFlags}u`. +4. `mode === 'auto'` (default) — require `interactive === true` + both TTYs, then call `confirmKittySupport()`. + +**confirmKittySupport():** +1. Create `responseBuffer: number[]`. +2. Attach `data` listener to stdin (before writing query, to catch sync responses). +3. Write `\x1b[?u` to stdout. +4. Set 200ms timeout. +5. On data: push bytes to buffer. If `hasCompleteKittyQueryResponse(buffer)` → cleanup + enable. +6. On timeout: cleanup only (no enable). +7. Cleanup: remove listener, clear timeout, strip query responses from buffer, re-emit remaining bytes via `stdin.unshift(Uint8Array.from(remaining))`. +8. Guard: don't enable if already disposed (handles unmount-during-detection race). + +**dispose():** +1. Cancel in-progress detection (call stored cleanup function). +2. If protocol was enabled, write `\x1b[u` | `\x1b[?1u` — terminal supports disambiguate | +| Enable protocol | `\x1b[>u` | `\x1b[>1u` — enable disambiguateEscapeCodes | +| Disable protocol | `\x1b[1u`) when `mode: 'enabled'` and both streams are TTY +- Writes disable sequence (`\x1b[