[READ-ONLY] Mirror of https://github.com/bombshell-dev/tty. Platform independent 2D layout engine for terminal applications based on Clay bomb.sh/docs/tty
tty specs input-spec.md
8.7 kB

Clayterm Input Specification #

Version: 0.1 (draft) Status: Current-state specification. Descriptive for the input parsing surface.


1. Purpose #

This specification describes Clayterm's terminal input parsing surface: the API for decoding raw terminal byte sequences into structured events.

Input parsing is architecturally independent from rendering (see Renderer Specification, INV-7). The two concerns share a compiled WASM binary for loading efficiency, but neither depends on the other's state, types, or API surface. Both consume the shared capability layer defined in the Terminfo Specification; the input parser is additionally that layer's runtime write path (see Section 6).

This specification is currently non-normative except where noted. The input API has clear design intent but has undergone more revision than the rendering core and faces known upcoming forces that will reshape it (Kitty progressive enhancement field surfacing). It is written to document the current surface and guide future stabilization.


2. Scope #

In scope (descriptive) #

  • The input parser creation and lifecycle
  • The scan API and its return type
  • The InputEvent discriminated union and its variants
  • The ESC timeout resolution model
  • Terminfo integration: key sequence loading and capability query response recognition (Section 6, normative)

Out of scope #

  • Rendering (see Renderer Specification)
  • Pointer hit detection (owned by the render loop; see Renderer Specification, Section 12.4)
  • Higher-level event routing, focus management, or keybinding systems

3. Terminology #

Input parser. A WASM-backed instance that accepts raw terminal bytes and produces structured events. Each parser maintains its own internal state for multi-byte sequence buffering and ESC timeout tracking.

Scan. A single invocation of the parser. The caller provides raw bytes (or no bytes, for timeout resolution), and the parser returns any events it can produce along with pending-timeout information.

InputEvent. A discriminated union representing a single parsed terminal event. Discriminated on a type field.


4. Input Parser API #

4.1 Parser creation #

createInput(options?): Promise<Input>

Creates an input parser instance. The returned promise resolves when the WASM module is ready.

Options:

  • escLatency — Milliseconds to wait before resolving a lone ESC byte as the Escape key. Default: 25ms. This controls the tradeoff between responsiveness (lower values) and correct disambiguation of ESC-prefixed sequences (higher values).

  • terminfo — A TerminalInfo value from detectTerminal() (see Terminfo Specification §10.1). Terminal-specific key sequences from terminfo.keys are loaded into the parser's escape sequence trie at initialization (Section 6.1). When omitted, the parser uses built-in xterm default sequences.

4.2 Scan #

input.scan(bytes?: Uint8Array): ScanResult

Feeds raw terminal bytes into the parser and returns parsed events. The bytes parameter is optional; calling without arguments triggers a rescan for ESC timeout resolution.

The parser is synchronous: it processes all provided bytes in a single call and returns immediately.

4.3 ScanResult #

{ events: InputEvent[], pending?: { delay: number, deadline: number } }
  • events — An array of parsed events produced from the provided bytes (and any previously buffered bytes that could now be resolved).

  • pending — When present, indicates that an ambiguous ESC byte is buffered and the parser cannot yet determine whether it begins an escape sequence or is a standalone Escape keypress. The caller SHOULD schedule a rescan (calling scan() with no arguments) after the indicated delay. The delay field is a relative duration in milliseconds. The deadline field is an absolute timestamp (milliseconds since epoch) for the same point in time.

An ESC followed by exactly one introducer byte — [, O, ], P, or _ — and nothing else is ambiguous in the same way: it is either an Alt-modified key or the start of an escape sequence or probe response. The parser MUST treat it as pending and report pending. If no further bytes arrive within escLatency, the rescan MUST resolve it as a key event for the introducer character with alt: true.


5. InputEvent Types #

The InputEvent discriminated union is discriminated on a type field. The current variants are:

  • KeyEvent (type: "keydown" | "keyup" | "keyrepeat") — A keyboard event for special keys, control sequences, and modifier combinations. Fields include key (logical key name), code (physical key identifier), and modifier flags (shift, ctrl, alt, meta).

  • MouseEvent (type: "mousedown" | "mouseup") — A mouse button press or release. Fields include x, y (cell coordinates), button, and modifier flags.

  • WheelEvent (type: "wheel") — A scroll event. Fields include x, y, and scroll direction.

  • ResizeEvent (type: "resize") — A terminal resize notification. Fields include columns and rows.

The discriminant values and the type splits are deliberate design decisions. However, the field sets within each variant are expected to grow when Kitty progressive enhancement types are surfaced in the TypeScript layer (the C struct has already been extended with fields that are not yet mapped to the TS types).


6. Terminfo Integration #

This section is normative. It defines the input parser's two roles in the capability layer specified by the Terminfo Specification.

6.1 Key sequences from terminfo #

When given a terminfo value whose keys field is present, the parser MUST load the terminal's key_* string capabilities into its escape sequence trie at initialization, before any scan. Terminfo-supplied sequences take precedence over the built-in xterm defaults when they conflict; defaults remain registered for sequences the terminfo entry does not define.

The key capabilities consumed are the key_* string range mapped to existing KEY_* codes: arrows (kcuu1, kcud1, kcub1, kcuf1), function keys (kf1–kf12), editing keys (khome, kend, kich1, kdch1, kpp, knp), and backtab (kcbt). Key capabilities with no corresponding KEY_* code are ignored. Terminfo key sequences longer than 16 bytes are ignored.

6.2 Query response recognition #

During a normal scan — with responses potentially interleaved with user input — the parser MUST recognize and consume the probe responses listed in Terminfo Specification §9.1: OSC 10/11/12 theme color reports, OSC 21 kitty color reports, OSC 22 pointer shape reports, XTGETTCAP DCS replies, DECRPM mode-2026 reports, kitty keyboard flag reports, kitty graphics APC replies, and the DA1 device attributes report.

For each recognized response the parser MUST emit a CapabilityEvent in the scan() return value, per Terminfo Specification §6.3 and TINV-6. Responses are consumed silently: they MUST NOT surface as InputEvents, and bytes belonging to a recognized response MUST NOT leak into adjacent events.

When the parser is standalone (no terminfo), responses are still recognized and consumed so stray replies never corrupt the event stream.

6.3 Response termination #

OSC, DCS, and APC responses are terminated by BEL (0x07) or ST (ESC \). Once the parser has recognized a response header — ESC ] Ps ; with Ps one of 10, 11, 12, 21, or 22; ESC P 0 + r or ESC P 1 + r; or ESC _ G — the following bytes end the response early instead:

  • any other C0 control: 0x00–0x06, 0x08–0x1A, 0x1C–0x1F;
  • DEL (0x7F);
  • ESC followed by any byte other than \.

When a response ends early, the parser MUST discard the bytes received for it and MUST NOT emit a CapabilityEvent for it. Parsing resumes at the byte that ended the response, so the key or sequence it begins is delivered normally.


7. Deferred / Future Areas #

These topics are explicitly excluded from this specification. Their omission is intentional, not an oversight.

Full Kitty progressive enhancement event types. The C-side input parser struct has been extended for progressive enhancement fields. The TypeScript event types have not been updated to surface them.

Whether input parsing should be a separate package. Architecturally independent from the renderer but currently co-located. The distribution decision is open.