diff --git a/specs/input-spec.md b/specs/input-spec.md new file mode 100644 index 0000000..3416ebd --- /dev/null +++ b/specs/input-spec.md @@ -0,0 +1,168 @@ +# 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](renderer-spec.md), INV-8). The two concerns share a +compiled WASM binary for loading efficiency, but neither depends on the other's +state, types, or API surface. + +This specification is currently non-normative. 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, terminfo binary parsing). 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 + +### Out of scope + +- Rendering (see [Renderer Specification](renderer-spec.md)) +- 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 +``` + +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 `Uint8Array` of raw terminfo binary. Accepted but C-side + parsing is not yet implemented. + +### 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. + +--- + +## 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. 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. + +**Terminfo binary parsing.** The input API accepts a `terminfo` option, but +C-side parsing is not implemented. + +**Whether input parsing should be a separate package.** Architecturally +independent from the renderer but currently co-located. The distribution +decision is open. + +--- + +## Open Decisions + +1. **What are the normative Kitty progressive enhancement event types?** The + C-side struct has been extended. The TypeScript types have not been updated. + This specification does not attempt to predict the final shapes. + +2. **Should the input API be a separate package?** It is architecturally + independent from the renderer (INV-8) but currently co-located in the same + module. + +3. **Is the input API ready for normative specification?** The API has clear + design ownership but has undergone more revision than the rendering core. + This specification documents the current surface without freezing it. diff --git a/specs/renderer-spec.md b/specs/renderer-spec.md new file mode 100644 index 0000000..74a6d49 --- /dev/null +++ b/specs/renderer-spec.md @@ -0,0 +1,814 @@ +# Clayterm Renderer Specification + +**Version:** 0.1 (draft) **Status:** Current-state specification. Normative for +the rendering contract. Descriptive for settling surfaces. + +--- + +## 1. Purpose + +Clayterm is a terminal rendering engine. It accepts a declarative description of +a terminal UI layout, performs layout computation and cell-level diffing +internally, and returns ANSI escape byte sequences suitable for direct write to +a terminal output stream. + +This specification defines Clayterm's current-state rendering contract: its +architectural model, its invariants, its stable public API surface, and its +intentional boundaries. It is written to allow future feature work to extend the +project without destabilizing the core. + +This specification does not attempt to define areas of Clayterm that are still +settling. Where the project has working but evolving surfaces — including the +pointer event model and certain wrapper types — those are described in Section +12 as current implementation rather than normative contract. + +Input parsing is specified separately in the +[Clayterm Input Specification](input-spec.md). + +--- + +## 2. Scope + +### In scope (normative) + +- The rendering pipeline and its architectural commitments +- The frame-snapshot rendering model +- The stable public rendering API +- The directive model and core helpers +- Element identity and frame semantics +- Boundary responsibilities (what Clayterm owns and what it does not) + +### In scope (non-normative, descriptive) + +- Current implementation surfaces that are settling but not yet stable enough to + freeze (Section 12) +- Implementation notes that aid understanding but do not define contract + (Section 13) + +### Out of scope + +- Internal C code organization, function names, or file structure +- WASM memory layout or compilation details beyond behavioral requirements +- Performance targets or benchmark methodology +- Packaging, CI, or distribution workflow details +- Higher-level UI framework concerns (e.g., component lifecycle, reconciliation) +- Demo applications +- The crankterm project or any specific framework built on Clayterm +- Input parsing (see [Clayterm Input Specification](input-spec.md)) + +--- + +## 3. Terminology + +**Frame.** A single, complete rendering pass. Each frame begins with the caller +providing directives and ends with the renderer returning ANSI bytes. Frames are +independent; the renderer carries no UI tree state between them. + +**Directive (op).** A plain object that declares one element of the UI tree for +a single frame. Directives are typed by an identifier field and carry layout, +styling, and content properties. The set of directives for a frame is ordered +and forms an implicit tree via open/close pairing. + +**Directive array.** An ordered array of directives constituting a complete +frame description. The array is the input to the rendering transaction. + +**Render transaction.** The process of accepting a directive array, performing +layout, walking render commands, diffing against the previous frame's cell +buffer, and producing ANSI byte output. A render transaction is a single, +synchronous operation from the caller's perspective. + +**ANSI bytes.** A byte sequence of UTF-8–encoded ANSI escape codes and text +content that, when written to a terminal file descriptor, produces the visual +output described by the frame's directives. ANSI bytes include +cursor-positioning sequences, SGR (Select Graphic Rendition) attribute +sequences, and UTF-8 text. + +**Renderer core.** The WASM module and its TS entry points that together +implement the render transaction. The renderer core owns layout computation, +render-command walking, cell-buffer diffing, and ANSI byte generation. + +**Caller.** Any code that invokes Clayterm's public API to produce terminal +output. The caller owns terminal setup, IO, input handling, and application +lifecycle. + +**Higher-level framework.** A component model, reconciler, or application +framework built on top of Clayterm. Examples include crankterm. Clayterm has no +dependency on any higher-level framework, and this specification does not +constrain their design. + +**Term.** An instance of the Clayterm renderer, bound to specific terminal +dimensions. A Term is the object through which the caller performs render +transactions. + +--- + +## 4. Architectural Model + +_This section is normative._ + +### 4.1 Pipeline + +Clayterm implements a rendering pipeline with the following stages: + +1. **Directive acceptance.** The caller provides a complete directive array + representing the desired UI state for a single frame. + +2. **Transfer.** The renderer transfers the frame description into the WASM + module. The transfer mechanism is an implementation detail. The normative + requirement is that the transfer occurs as part of a single render + transaction; the caller does not interact with the transfer mechanism + directly. + +3. **Render transaction.** The WASM module processes the frame description. + Internally, it drives a layout engine to compute element positions and sizes, + walks the resulting render commands to populate a cell buffer, and diffs the + cell buffer against the previous frame. + +4. **Output generation.** For each cell that differs from the previous frame, + the renderer emits ANSI escape sequences (cursor positioning, color + attributes, and text) into an output buffer. + +5. **Output retrieval.** The caller reads the ANSI byte output. + +### 4.2 Single-transaction rendering + +A frame MUST be rendered in a single transaction that crosses the TS→WASM +boundary once. The caller provides the complete directive array, invokes the +render transaction, and reads the output. There are no intermediate callbacks, +yields, or partial results. + +### 4.3 Frame-snapshot model + +Each render transaction operates on a complete, self-contained snapshot of the +UI. The renderer MUST NOT maintain an internal component tree or UI state across +frames. The only state the renderer retains between frames is the cell buffer +used for diffing, which is an implementation detail of output minimization and +not observable to the caller except through reduced output size. + +### 4.4 Double-buffered diffing + +The renderer maintains two cell buffers: a front buffer (the previously rendered +frame) and a back buffer (the frame being rendered). After populating the back +buffer from the current frame's render commands, the renderer compares it +against the front buffer and emits ANSI bytes only for cells that differ. +Changed cells are then copied from the back buffer to the front buffer so that +both buffers are identical at the end of the transaction. This mechanism is +internal to the renderer and not directly observable to the caller. + +--- + +## 5. Contract Layer Boundary + +_This section is normative._ + +This specification defines the **architectural rendering contract**: the +commitments that make Clayterm what it is and that callers and framework authors +can depend on. + +This specification **does not** define the following as normative: + +- **The internal transfer encoding.** The mechanism by which directives are + serialized for the WASM module — its byte format, opcode structure, and field + encoding — is an implementation detail. The normative commitment is that the + transfer happens within a single render transaction; the encoding is described + in Section 12.1 as current implementation surface. + +- **Validation or error semantics.** How the renderer responds to invalid input + (malformed directive arrays, unbalanced open/close pairs) is not yet specified + as contract. Section 9.1 defines what constitutes valid input. Behavior for + invalid input is currently unspecified. + +- **The complete set of directive properties.** The existence of the core + directive constructors (`open`, `close`, `text`) and the core sizing helpers + (`grow`, `fixed`, `fit`) is normative. The full set of properties accepted by + these constructors — which layout fields, which styling options, which + configuration groups are available — is current implementation surface + described in Section 12.2. New property groups have been added over time and + more may follow. + +- **The return type wrapper of `render()`.** The commitment that `render()` + produces ANSI bytes accessible as a `Uint8Array` is normative. The wrapper + type around those bytes is current implementation surface described in Section + 12.3. + +Future readers should not treat current implementation surface as identical to +the contract boundary. + +--- + +## 6. Core Invariants + +_This section is normative._ + +**INV-1. Zero IO.** The renderer MUST NOT perform any terminal input or output. +It MUST NOT write to stdout, read from stdin, open file descriptors, or interact +with the terminal device. The renderer produces bytes; the caller writes them. + +**INV-2. Single transaction per frame.** Each frame MUST be rendered in a single +transaction that crosses the TS→WASM boundary once. The caller provides the +complete frame as a directive array and receives ANSI bytes in return. + +**INV-3. Frame-snapshot independence.** The renderer MUST NOT require the caller +to maintain or provide state across frames beyond calling `render()` on the same +Term instance. Each directive array fully describes its frame. + +**INV-4. ANSI byte output.** The output of a render transaction MUST be a byte +sequence of valid UTF-8–encoded ANSI escape codes that is directly writable to a +terminal output stream without further transformation or encoding. + +**INV-5. Layout/render/diff ownership.** The renderer owns the layout +computation, render-command walk, cell-buffer diffing, and ANSI byte generation +stages. The caller MUST NOT need to perform any of these operations. + +**INV-6. Internal lifecycle symmetry.** The renderer's internal layout lifecycle +(begin-layout and end-layout calls to the underlying layout engine) MUST be +symmetric: both calls occur within the same render transaction, in the same +function scope. + +**INV-7. Element identity disambiguation.** When multiple elements within a +frame share the same id, the renderer MUST disambiguate their identities so that +the layout engine does not conflate them. The disambiguation mechanism is an +implementation detail, but the guarantee is normative: identical ids MUST NOT +cause layout corruption or element conflation. + +**INV-8. Separation of concerns.** The rendering concern and the input-parsing +concern MUST remain independent. Neither MUST depend on the other's state, +types, or API surface. They MAY share a compiled WASM binary for loading +efficiency, but this is an implementation convenience, not an architectural +coupling. + +--- + +## 7. Rendering Contract + +_This section is normative._ + +### 7.1 Inputs + +The rendering transaction accepts: + +- A **directive array**: an ordered array of directive objects constituting a + complete frame. The array MUST contain balanced open/close pairs forming a + valid tree structure. + +The directive array is the sole required input to a render transaction. + +### 7.2 Rendering transaction + +When the caller invokes a render transaction: + +1. The renderer accepts the directive array and transfers the frame description + into the WASM module. +2. The WASM module processes the frame: it computes layout, walks render + commands, populates the cell buffer, diffs against the previous frame, and + writes ANSI bytes for changed cells. +3. Control returns to the caller with the ANSI byte output available. + +The render transaction is synchronous from the caller's perspective once +invoked. It MUST NOT yield, suspend, or require callbacks during execution. + +### 7.3 Output + +The render transaction produces ANSI bytes as a `Uint8Array`. These bytes: + +- MUST be valid UTF-8 +- MUST consist of ANSI escape sequences (CSI, SGR) and text content +- MUST be directly writable to a terminal file descriptor to produce the + described visual output +- In cursor update mode, MUST represent only the cells that changed since the + previous frame (on a Term instance that has rendered at least one prior frame) +- In line mode, MUST represent all cells in the frame as newline-separated rows + +The output reflects the complete visual state of the frame. The caller SHOULD +write the output to the terminal without modification. + +The output `Uint8Array` is a view over renderer-owned memory. It is valid until +the next `render()` call on the same Term instance, at which point the buffer +may be reused. Callers who need to retain the output beyond the next render MUST +copy it. + +### 7.4 Lifecycle + +A Term instance is created for specific terminal dimensions. The caller provides +width and height at creation time. + +To handle terminal resize, the caller creates a new Term with the new +dimensions. The previous Term instance becomes stale and SHOULD NOT be used for +further rendering. + +Creation of a Term is asynchronous because it may involve WASM module +preparation. A Term instance MAY be used for any number of render transactions. +The Term retains its cell buffers across frames for diffing purposes. + +--- + +## 8. Public Rendering API + +_This section is normative. Only items with high confidence of stability are +included. See Section 5 for what this section does and does not freeze._ + +### 8.1 Term creation + +``` +createTerm(options: { width: number; height: number }): Promise +``` + +Creates a new Term instance bound to the specified terminal dimensions. The +returned promise resolves when the renderer is ready. The `width` and `height` +parameters specify the terminal dimensions in character cells. + +### 8.2 Render invocation + +``` +term.render(ops: Op[], options?: RenderOptions): +``` + +Accepts an ordered array of directive objects and performs a render transaction +as defined in Section 7. Returns the ANSI byte output as a `Uint8Array`. + +The optional `options` parameter controls the rendering mode. See Section 8.2.1 +and 8.2.2 for the two available modes. + +The return type is specified here only to the extent that the ANSI bytes MUST be +accessible as a `Uint8Array`. The precise shape of the return value — whether it +is a bare `Uint8Array`, a wrapper object, or a structure carrying additional +data — is part of the current implementation surface described in Section 12.3 +and is not locked down by this specification. + +#### 8.2.1 Cursor update mode (default) + +When `mode` is omitted, the renderer operates in cursor update mode. Output +consists of ANSI bytes with absolute CUP (`\x1b[row;colH`) cursor positioning +for each changed cell. Only cells that differ from the previous frame are +emitted, making this efficient for full-screen UIs where most of the screen is +static between frames. + +The optional `row` parameter specifies a 1-based row offset for CUP positioning. +This allows the caller to render into a region of the terminal starting at a row +other than the top. The offset is applied to all emitted cursor positions. When +omitted, it defaults to 1. + +#### 8.2.2 Line mode + +When `mode` is `"line"`, the renderer emits all cells as newline-separated rows +without CUP positioning. Every cell is written regardless of whether it changed +since the previous frame. The front buffer is updated so that a subsequent +cursor update mode render can diff efficiently. + +Line mode is intended for inline region rendering where the caller manages +cursor positioning externally and the output must work in pipes or non-alternate +screen contexts. + +### 8.3 Directive constructors + +Directives are created using constructor functions that return plain objects. +The caller assembles these into an array. This pattern — functions returning +plain directives, composed into arrays — is normative. A builder, fluent, or +mutation-based API is explicitly rejected. + +#### 8.3.1 open + +``` +open(id: string, props?): OpenElement +``` + +Creates an element-open directive. The `id` parameter provides an identity for +the element within the frame, used by the underlying layout engine for element +tracking and hit-testing. The optional `props` parameter carries configuration +for layout, styling, and behavior. + +Elements opened with `open()` MUST be closed with a corresponding `close()` +directive later in the same directive array. + +The set of properties accepted by `props` is part of the current implementation +surface described in Section 12.2. This specification defines the existence and +signature of `open()` normatively but does not freeze the complete property +surface, which has been extended incrementally and may continue to grow. + +#### 8.3.2 close + +``` +close(): CloseElement +``` + +Creates an element-close directive. Each `close()` MUST correspond to a +preceding `open()`. + +#### 8.3.3 text + +``` +text(content: string, props?): Text +``` + +Creates a text directive. The `content` parameter provides the text string to +render. The optional `props` parameter carries text styling configuration. + +Text directives MUST appear between a matching open/close pair. + +The set of styling properties accepted by `props` is part of the current +implementation surface and may be extended. + +### 8.4 Sizing helpers + +These functions produce sizing-axis values for use in element layout +configuration: + +``` +grow(): SizingAxis +``` + +The element expands to fill available space in the parent along this axis. + +``` +fixed(value: number): SizingAxis +``` + +The element has a fixed size of `value` cells along this axis. + +``` +fit(min?: number, max?: number): SizingAxis +``` + +The element sizes to fit its content, optionally constrained by minimum and +maximum bounds. + +### 8.5 Color helper + +``` +rgba(r: number, g: number, b: number, a?: number): number +``` + +Packs color channel values (each 0–255) into a single 32-bit integer in ARGB +format. Alpha defaults to 255 (fully opaque). The returned value is used +wherever the directive model expects a color. + +--- + +## 9. Directive Model + +_This section is normative._ + +### 9.1 Directive-array pattern + +The rendering input is an ordered array of directive objects. Each directive is +a plain JavaScript/TypeScript object created by a constructor function (Section +8.3). Directives are not classes, do not carry methods, and do not participate +in a prototype chain. They MAY be spread, composed, stored, or inspected as +ordinary objects. + +The array is processed in order. Open and close directives form an implicit +tree. The renderer processes them sequentially. + +A directive array with unbalanced open/close pairs, or with close directives +that do not match a preceding open, is invalid input. Callers SHOULD validate +directive arrays before rendering. The renderer's behavior when given an invalid +directive array is unspecified by this specification. + +### 9.2 Transfer to the WASM module + +As part of the render transaction, the directive array is transferred into a +form that the WASM module can process. This transfer is handled internally by +the renderer and is not an operation the caller performs or observes. The +transfer mechanism is an implementation detail described in Section 12.1. + +### 9.3 Directive identity + +Each element directive is assigned an identity within the frame for use by the +underlying layout engine. When multiple elements share the same id (the `id` +parameter to `open()`), the renderer MUST disambiguate their identities +automatically. The disambiguation mechanism is an implementation detail. The +normative requirement is that the caller MUST NOT need to provide globally +unique ids; the renderer handles uniqueness internally. + +--- + +## 10. Identity and Frame Semantics + +_This section is normative._ + +### 10.1 Frame completeness + +A directive array provided to `render()` MUST represent a complete frame. The +renderer does not support incremental updates, partial frames, or delta +descriptions. Every frame fully specifies the desired UI state. + +### 10.2 Directive ordering + +Directives MUST be provided in depth-first tree order. An `open()` directive +begins an element; its children (including nested open/close pairs and text +directives) follow in order; a `close()` directive ends the element. The +renderer processes directives in the order they appear in the array. + +### 10.3 Element identity within a frame + +Within a single frame, each element MUST have an unambiguous identity for the +layout engine. As specified in Section 9.3, the renderer handles disambiguation. +Two elements with the same id in the same frame MUST NOT cause layout +corruption, hash collision, or identity conflation. + +### 10.4 No cross-frame identity + +The renderer does not track element identity across frames. An element with id +"sidebar" in frame N and an element with id "sidebar" in frame N+1 are not +related from the renderer's perspective. Cross-frame identity, if needed, is the +responsibility of a higher-level framework. + +--- + +## 11. Boundaries and Non-Responsibilities + +_This section is normative._ + +### 11.1 The renderer does not perform IO + +The renderer MUST NOT write to any output stream. The renderer MUST NOT read +from any input stream. The renderer produces bytes; the caller decides when and +how to write them. This enables the renderer to operate in any environment where +WebAssembly is available, including browsers, server-side runtimes, and embedded +contexts. + +### 11.2 The renderer does not manage terminal state + +The renderer MUST NOT emit escape sequences for any of the following +terminal-management operations: + +- Entering or leaving the alternate screen buffer +- Hiding or showing the cursor +- Setting the cursor shape or blink state +- Enabling or disabling mouse reporting +- Enabling or disabling keyboard protocol modes (e.g., Kitty progressive + enhancement) +- Enabling or disabling raw mode or similar terminal disciplines + +These are the caller's responsibility. The renderer's output contains only the +escape sequences needed to render the frame content (cursor positioning for cell +writes, SGR attributes for styling, and UTF-8 text). + +### 11.3 The renderer does not own application lifecycle + +The renderer MUST NOT maintain a run loop, event loop, timer, or subscription +mechanism. It does not schedule frames. It does not manage component state. It +renders when asked and returns. The decision of when to render is entirely the +caller's. + +### 11.4 The renderer does not own input parsing + +Input parsing (keyboard events, mouse events, escape sequence decoding) is an +independent concern specified separately in the +[Clayterm Input Specification](input-spec.md). The renderer MUST NOT depend on +input-parsing state, types, or API. + +However, pointer hit detection does require the render loop to participate. The +caller may pass the current pointer position as part of render options, and the +renderer returns the ids of every element the pointer is over. This is how the +`PointerEvent[]` array in the render result is populated. See Section 12.4 for +the current pointer event surface. + +### 11.5 The renderer does not own higher-level framework concerns + +The renderer MUST NOT implement or depend on: + +- Component models or component lifecycles +- Reconciliation or diffing of directive trees (the renderer diffs _cells_, not + _trees_) +- State management or reactivity +- Event propagation through a component hierarchy + +These are the domain of higher-level frameworks built on Clayterm. + +--- + +## 12. Current Surface That Remains Elastic + +_This entire section is non-normative. It describes the current implementation +surface to aid consumers and future spec authors. The shapes described here are +real, working, and in many cases deliberately designed, but they do not yet meet +the stability threshold for normative specification. They MAY change in future +versions without constituting a breaking change to the normative core defined +above._ + +### 12.1 Transfer encoding (command protocol) + +The renderer currently serializes directives into a flat byte buffer using a +command protocol based on fixed-width `Uint32` words. Each directive is encoded +as an opcode word followed by directive-specific data. Element-open directives +use a property mask to indicate which optional field groups (layout, border, +corner radius, clip, floating, scroll) are present, followed by the data for +each indicated group. Strings are encoded as length-prefixed UTF-8 byte +sequences within the word stream. Floats are stored as bit-reinterpreted +`Uint32` values. + +This encoding has been extended incrementally (floating, clip, and scroll groups +were added after the initial protocol) but has never been restructured. It is +likely to remain stable in structure while continuing to grow. However, specific +opcode values, mask definitions, and field layouts are implementation details +and are not locked down by this specification. + +### 12.2 Directive property groups + +The `open()` constructor currently accepts the following property groups in its +`props` parameter: + +- **`layout`** — sizing (width and height, specified via sizing helpers), + padding (per-side), alignment (currently numeric enum values, with a planned + transition to string literals), direction (top-to-bottom or left-to-right), + and gap +- **`border`** — per-side border widths and border color +- **`cornerRadius`** — per-corner radius values, producing rounded box-drawing + characters +- **`clip`** — clip region configuration for scroll containers +- **`floating`** — floating-element configuration (offset, parent reference, + attach points, z-index) +- **`scroll`** — scroll container configuration + +The `text()` constructor currently accepts: `color`, `fontSize`, +`letterSpacing`, `lineHeight`, and attribute flags (`bold`, `italic`, +`underline`, `strikethrough`). + +These property groups represent the current implementation surface. New groups +and fields have been added incrementally and more may follow. Alignment values +are expected to transition from numeric to string-literal form. + +**Border width and layout interaction.** In the underlying layout engine (Clay), +border configuration does not affect layout computation. This is Clay's intended +behavior. Borders are drawn as visual overlays within the element's bounding +box. A bordered element with zero padding will have its borders drawn over its +content. Callers must add padding equal to or greater than the border width to +prevent overlap. + +### 12.3 Render return type + +The `render()` method currently returns a `RenderResult` object shaped as +`{ output: Uint8Array, events: PointerEvent[] }`. + +The `output` field is the ANSI byte output specified normatively in Section 7.3 +and Section 8.2. + +The `events` field contains pointer events (enter, leave, click) derived from +the underlying layout engine's element hit-testing. This field was added during +a pointer-events feature implementation. The pointer event model is functional +but has acknowledged gaps (no modifier keys on click events) and its interaction +protocol (passing pointer state via render options, then reading events from the +return value) was arrived at through iteration rather than upfront design. + +The return type of `render()` has changed twice since the project's inception +(string, then `Uint8Array`, then `RenderResult`). While the ANSI bytes +commitment (Section 7.3) is stable, the wrapper shape around those bytes is not. +Future versions may restructure the return type. + +### 12.4 Pointer event model + +Clayterm currently supports pointer hit-testing via the underlying layout +engine's element-identification mechanism. The caller passes pointer state +(`{ x, y, down }`) as part of render options, and the renderer returns pointer +events as part of the render result: + +- `pointerenter` — the pointer has entered an element's bounding box +- `pointerleave` — the pointer has left an element's bounding box +- `pointerclick` — a pointer-up occurred over an element that was also under the + pointer at pointer-down + +This surface is functional but should not be treated as stable contract. The +calling convention was discovered through iteration, the event model has +acknowledged gaps, and the approach may evolve. + +### 12.5 Validation and packing + +**`validate(ops)`** — A public API function that checks a directive array for +structural errors (unbalanced open/close pairs, invalid field types). Exported +and used in tests. + +**`pack(ops, mem, offset)`** — An internal function that serializes a directive +array into the transfer encoding described in Section 12.1. Currently exported +but not public API; its exposure is incidental to the module structure. + +--- + +## 13. Implementation Notes + +_This section is non-normative. These notes describe current implementation +details that aid understanding but do not define contract. They may change +without notice._ + +**WASM module structure.** The renderer is implemented in C and compiled to +WebAssembly as a single module. The module contains both rendering and +input-parsing functionality; they share a binary but maintain independent state. + +**WASM loading.** The WASM binary is inlined as a base64-encoded string in a +generated module and instantiated per Term or Input with fresh memory. + +**Memory layout.** WASM linear memory is initialized with 256 pages (16MB). The +renderer state struct and the transfer buffer are allocated in WASM linear +memory. The specific layout is an implementation detail. + +**Layout engine.** The underlying layout engine is Clay, included as a +dependency. Clay provides flexbox-like layout computation with support for +fixed, grow, and fit sizing; padding; alignment; direction; gap; floating +elements; clip regions; and scroll containers. + +**Text measurement.** Text width measurement uses `wcwidth`-based character +width computation, supporting ASCII, CJK wide characters, and other Unicode +codepoints. + +**Cell representation.** Each cell in the buffer stores a Unicode codepoint, a +foreground color (packed ARGB with attribute flags in the high byte), and a +background color. + +**Border junction resolution.** When bordered elements share edges, the renderer +accumulates per-cell direction bitmasks and resolves them to correct box-drawing +junction glyphs in a post-render pass. + +--- + +## 14. Deferred / Future Areas + +_This section is non-normative. These topics are explicitly excluded from this +specification. Their omission is intentional, not an oversight._ + +**Scroll container API.** The underlying layout engine supports scroll +containers. No TypeScript-side API exists for providing scroll state to the +renderer. + +**CSI helper for terminal setup.** A helper for generating paired apply/rollback +byte arrays for terminal mode configuration was discussed but not implemented. + +**Browser-specific adapter.** The renderer's zero-IO architecture makes browser +portability possible. No adapter exists. + +**`betweenChildren` border support.** The underlying layout engine supports +this. It is not exposed in the directive model. + +--- + +## Appendix A. Confidence Notes + +### Why the rendering core is specified more aggressively than other surfaces + +The rendering architecture — `createTerm`, `render(ops)`, the directive +constructors, the bytes-output commitment, and the core invariants — was +designed at the project's inception and has been stable since. It has survived +the addition of pointer events, border junction resolution, and the crankterm +integration without revision to its fundamental shapes. Its key abstractions +(flat directive arrays, single render transaction, ANSI byte output) were chosen +over explicitly rejected alternatives (per-element FFI, protobuf, builder +pattern, string output). This level of stability and intentionality justifies +normative specification. + +The pointer event model and render return wrapper are the least settled of the +currently shipping features. Both were introduced during feature implementation +rather than designed as part of the core architecture. The return type of +`render()` has changed twice. The pointer calling convention was discovered +through iteration. These are working and useful, but they carry the lowest +confidence of any feature currently in the codebase. + +### How to interpret "currently exported" + +Several symbols are currently accessible from Clayterm's module exports — +including `pack()`, `validate()`, and numerous input-related types — without +clear evidence that they were intended as stable public contract. Being exported +may mean "needed by internal modules" or "not yet audited for public/internal +boundary." + +This specification does not treat the export list as a contract boundary. +Instead, it uses stability over time, design ownership, survival of corrections, +and absence of known reshaping forces as the criteria for normative inclusion. + +--- + +## Open Decisions Intentionally Left Out of This Spec + +The following decisions are open. This specification omits them deliberately. +Future readers should not interpret their absence as oversight or implicit +resolution. + +1. **What is the normative return type of `render()`?** This specification + commits to ANSI bytes as `Uint8Array` but does not lock down the wrapper + type. The current `RenderResult` shape may evolve. + +2. **Is pointer event detection part of the rendering contract?** The current + implementation returns pointer events from `render()`. This specification + does not include pointer events in the normative core. Whether pointer + detection is intrinsic to the renderer or should be a separate concern is + unresolved. + +3. **Is `pack()` public API?** `pack()` is currently exported but is an internal + implementation detail, not public API. `validate()` is public API. + +4. **How should border widths interact with layout?** The current behavior + (borders do not affect layout) is inherited from the underlying layout + engine. The project has questioned whether this is the right design. This + specification describes the current behavior in Section 12.2 without + committing to it. + +5. **What are the specific transfer encoding details?** The encoding structure + is described in Section 12.1 as current implementation surface. Locking down + opcode values would constrain future extensions unnecessarily. + +6. **What is the complete set of directive properties?** The property groups + available in `open()` and `text()` are described in Section 12.2 as current + implementation surface. They have been extended incrementally and will + continue to grow. + +7. **What are the validation and error semantics?** How the renderer responds to + invalid input is unspecified. Callers SHOULD validate, but the validation + model is not yet settled enough to define normatively.