[READ-ONLY] Mirror of https://github.com/bombshell-dev/tty. Platform independent 2D layout engine for terminal applications based on Clay
5

Configure Feed

Select the types of activity you want to include in your feed.

♻️ split spec into renderer and input specifications

Separate the combined spec into two documents aligned with INV-8
(architectural independence of rendering and input parsing):

- renderer-spec.md: rendering contract, directive model, rendering
modes (cursor update + line mode), pointer hit-testing
- input-spec.md: input parser API, scan model, InputEvent types

Also adds normative specification of cursor update mode (default)
and line mode with row offset to the renderer spec.

Charles Lowell (Apr 17, 2026, 1:30 PM -0500) e75b49ac 04d45172

+223 -125
+55 -125
specs/clayterm-spec.md specs/renderer-spec.md
··· 1 - # Clayterm Current-State Specification 1 + # Clayterm Renderer Specification 2 2 3 3 **Version:** 0.1 (draft) **Status:** Current-state specification. Normative for 4 4 the rendering contract. Descriptive for settling surfaces. ··· 19 19 20 20 This specification does not attempt to define areas of Clayterm that are still 21 21 settling. Where the project has working but evolving surfaces — including the 22 - input parsing API, pointer event model, and certain wrapper types — those are 23 - described in Section 12 as current implementation rather than normative 24 - contract. 22 + pointer event model and certain wrapper types — those are described in Section 23 + 12 as current implementation rather than normative contract. 24 + 25 + Input parsing is specified separately in the 26 + [Clayterm Input Specification](input-spec.md). 25 27 26 28 --- 27 29 ··· 41 43 - Current implementation surfaces that are settling but not yet stable enough to 42 44 freeze (Section 12) 43 45 - Implementation notes that aid understanding but do not define contract 44 - (Section 14) 46 + (Section 13) 45 47 46 48 ### Out of scope 47 49 ··· 52 54 - Higher-level UI framework concerns (e.g., component lifecycle, reconciliation) 53 55 - Demo applications 54 56 - The crankterm project or any specific framework built on Clayterm 57 + - Input parsing (see [Clayterm Input Specification](input-spec.md)) 55 58 56 59 --- 57 60 ··· 340 343 emitted, making this efficient for full-screen UIs where most of the screen is 341 344 static between frames. 342 345 343 - The optional `row` parameter specifies a 1-based row offset for CUP 344 - positioning. This allows the caller to render into a region of the terminal 345 - starting at a row other than the top. The offset is applied to all emitted 346 - cursor positions. When omitted, it defaults to 1. 346 + The optional `row` parameter specifies a 1-based row offset for CUP positioning. 347 + This allows the caller to render into a region of the terminal starting at a row 348 + other than the top. The offset is applied to all emitted cursor positions. When 349 + omitted, it defaults to 1. 347 350 348 351 #### 8.2.2 Line mode 349 352 ··· 551 554 ### 11.4 The renderer does not own input parsing 552 555 553 556 Input parsing (keyboard events, mouse events, escape sequence decoding) is an 554 - independent concern. It is not part of the rendering contract defined by this 555 - specification. The renderer MUST NOT depend on input-parsing state, types, or 556 - API. 557 - 558 - Clayterm currently provides input-parsing functionality alongside the renderer 559 - in the same package. This co-location is an implementation detail, not an 560 - architectural coupling. Section 12.4 describes the current input surface. 557 + independent concern specified separately in the 558 + [Clayterm Input Specification](input-spec.md). The renderer MUST NOT depend on 559 + input-parsing state, types, or API. 561 560 562 561 However, pointer hit detection does require the render loop to participate. The 563 562 caller may pass the current pointer position as part of render options, and the 564 563 renderer returns the ids of every element the pointer is over. This is how the 565 - `PointerEvent[]` array in the render result is populated. See Section 12.5 for 564 + `PointerEvent[]` array in the render result is populated. See Section 12.4 for 566 565 the current pointer event surface. 567 566 568 567 ### 11.5 The renderer does not own higher-level framework concerns ··· 649 648 the underlying layout engine's element hit-testing. This field was added during 650 649 a pointer-events feature implementation. The pointer event model is functional 651 650 but has acknowledged gaps (no modifier keys on click events) and its interaction 652 - protocol (calling `setPointer(x, y, down)` before rendering, then reading events 653 - from the return value) was arrived at through iteration rather than upfront 654 - design. 651 + protocol (passing pointer state via render options, then reading events from the 652 + return value) was arrived at through iteration rather than upfront design. 655 653 656 654 The return type of `render()` has changed twice since the project's inception 657 655 (string, then `Uint8Array`, then `RenderResult`). While the ANSI bytes 658 656 commitment (Section 7.3) is stable, the wrapper shape around those bytes is not. 659 657 Future versions may restructure the return type. 660 658 661 - ### 12.4 Input parsing surface 662 - 663 - Clayterm currently provides terminal input parsing alongside the renderer. The 664 - input API was designed by the project lead and has clear design intent, but it 665 - has undergone more revision than the rendering core and faces known upcoming 666 - forces that will reshape it (Kitty progressive enhancement field surfacing, 667 - terminfo binary parsing, possible package separation). 668 - 669 - The current input surface includes: 670 - 671 - **`createInput(options?): Promise<Input>`** — Creates an input parser instance. 672 - Options currently include `escLatency` (milliseconds to wait before resolving a 673 - lone ESC byte as the Escape key, default 25ms) and `terminfo` (a `Uint8Array` of 674 - raw terminfo binary, accepted but with C-side parsing not yet implemented). 675 - 676 - **`input.scan(bytes?): ScanResult`** — Feeds raw terminal bytes into the parser 677 - and returns parsed events. The `bytes` parameter is optional; calling without 678 - arguments triggers a rescan for ESC timeout resolution. 679 - 680 - **`ScanResult`** — Currently shaped as 681 - `{ events: InputEvent[], pending?: { delay: number, deadline: number } }`. The 682 - `events` array contains parsed events. The `pending` field, when present, 683 - indicates that an ambiguous ESC byte is buffered and provides both a relative 684 - delay and an absolute deadline for the caller to schedule a rescan. 685 - 686 - **`InputEvent` discriminated union** — Currently discriminated on a `type` field 687 - with these variants: `CharEvent` (insertable character), `KeyEvent` 688 - (special/control key), `MouseEvent` (button press/release), `DragEvent` (motion 689 - with button held), `WheelEvent` (scroll tick), `ResizeEvent`. The discriminant 690 - values and the type splits are deliberate design decisions. However, the field 691 - sets within each variant are expected to grow when Kitty progressive enhancement 692 - types are surfaced in the TypeScript layer (the C struct has already been 693 - extended with fields that are not yet mapped to the TS types). 694 - 695 - The input API is architecturally independent from the renderer (see INV-8). 696 - Whether it remains in the same package or becomes a separate module is an open 697 - question. 698 - 699 - ### 12.5 Pointer event model 659 + ### 12.4 Pointer event model 700 660 701 661 Clayterm currently supports pointer hit-testing via the underlying layout 702 - engine's element-identification mechanism. The current surface includes: 662 + engine's element-identification mechanism. The caller passes pointer state 663 + (`{ x, y, down }`) as part of render options, and the renderer returns pointer 664 + events as part of the render result: 703 665 704 - - `setPointer(x, y, down)` — sets the pointer position and button state for the 705 - next render 706 - - Pointer events returned as part of `RenderResult.events`: `pointerenter`, 707 - `pointerleave`, `pointerclick` 666 + - `pointerenter` — the pointer has entered an element's bounding box 667 + - `pointerleave` — the pointer has left an element's bounding box 668 + - `pointerclick` — a pointer-up occurred over an element that was also under the 669 + pointer at pointer-down 708 670 709 671 This surface is functional but should not be treated as stable contract. The 710 672 calling convention was discovered through iteration, the event model has 711 673 acknowledged gaps, and the approach may evolve. 712 674 713 - ### 12.6 Validation and packing 675 + ### 12.5 Validation and packing 714 676 715 677 **`validate(ops)`** — A public API function that checks a directive array for 716 678 structural errors (unbalanced open/close pairs, invalid field types). Exported ··· 722 684 723 685 --- 724 686 725 - ## 13. Deferred / Future Areas 726 - 727 - _This section is non-normative. These topics are explicitly excluded from this 728 - specification. Their omission is intentional, not an oversight._ 729 - 730 - **Scroll container API.** The underlying layout engine supports scroll 731 - containers. No TypeScript-side API exists for providing scroll state to the 732 - renderer. 733 - 734 - **Full Kitty progressive enhancement event types.** The C-side input parser 735 - struct has been extended for progressive enhancement fields. The TypeScript 736 - event types have not been updated to surface them. 737 - 738 - **Terminfo binary parsing.** The input API accepts a `terminfo` option, but 739 - C-side parsing is not implemented. 740 - 741 - **CSI helper for terminal setup.** A helper for generating paired apply/rollback 742 - byte arrays for terminal mode configuration was discussed but not implemented. 743 - 744 - **Browser-specific adapter.** The renderer's zero-IO architecture makes browser 745 - portability possible. No adapter exists. 746 - 747 - **`betweenChildren` border support.** The underlying layout engine supports 748 - this. It is not exposed in the directive model. 749 - 750 - **Whether input parsing should be a separate package.** Architecturally 751 - independent (INV-8) but currently co-located. The distribution decision is open. 752 - 753 - --- 754 - 755 - ## 14. Implementation Notes 687 + ## 13. Implementation Notes 756 688 757 689 _This section is non-normative. These notes describe current implementation 758 690 details that aid understanding but do not define contract. They may change ··· 764 696 765 697 **WASM loading.** The WASM binary is inlined as a base64-encoded string in a 766 698 generated module and instantiated per Term or Input with fresh memory. 767 - 768 - **WASM co-location.** The WASM binary file is expected to be co-located with the 769 - JavaScript module files. Both JSR and npm package builds include the artifact. 770 699 771 700 **Memory layout.** WASM linear memory is initialized with 256 pages (16MB). The 772 701 renderer state struct and the transfer buffer are allocated in WASM linear ··· 791 720 792 721 --- 793 722 723 + ## 14. Deferred / Future Areas 724 + 725 + _This section is non-normative. These topics are explicitly excluded from this 726 + specification. Their omission is intentional, not an oversight._ 727 + 728 + **Scroll container API.** The underlying layout engine supports scroll 729 + containers. No TypeScript-side API exists for providing scroll state to the 730 + renderer. 731 + 732 + **CSI helper for terminal setup.** A helper for generating paired apply/rollback 733 + byte arrays for terminal mode configuration was discussed but not implemented. 734 + 735 + **Browser-specific adapter.** The renderer's zero-IO architecture makes browser 736 + portability possible. No adapter exists. 737 + 738 + **`betweenChildren` border support.** The underlying layout engine supports 739 + this. It is not exposed in the directive model. 740 + 741 + --- 742 + 794 743 ## Appendix A. Confidence Notes 795 744 796 745 ### Why the rendering core is specified more aggressively than other surfaces ··· 804 753 over explicitly rejected alternatives (per-element FFI, protobuf, builder 805 754 pattern, string output). This level of stability and intentionality justifies 806 755 normative specification. 807 - 808 - The input API arrived later, has been through significantly more design churn 809 - (rejected first draft, iterative event type splits, naming changes, ongoing 810 - Kitty progressive enhancement design), and faces known upcoming forces that will 811 - reshape it. It has clear design ownership from the project lead, which 812 - distinguishes it from purely implementation-driven features like the pointer 813 - event model, but design ownership is not the same as contract readiness. 814 756 815 757 The pointer event model and render return wrapper are the least settled of the 816 758 currently shipping features. Both were introduced during feature implementation ··· 849 791 detection is intrinsic to the renderer or should be a separate concern is 850 792 unresolved. 851 793 852 - 3. **Is the input API part of the Clayterm specification?** This specification 853 - describes it in Section 12.4 but does not specify it normatively. The input 854 - API may become a separate package or specification. 855 - 856 - 4. **Is `pack()` public API?** `pack()` is currently exported but is an internal 794 + 3. **Is `pack()` public API?** `pack()` is currently exported but is an internal 857 795 implementation detail, not public API. `validate()` is public API. 858 796 859 - 5. **What are the normative Kitty progressive enhancement event types?** The 860 - C-side struct has been extended. The TypeScript types have not been updated. 861 - This specification does not attempt to predict the final shapes. 862 - 863 - 6. **How should border widths interact with layout?** The current behavior 797 + 4. **How should border widths interact with layout?** The current behavior 864 798 (borders do not affect layout) is inherited from the underlying layout 865 799 engine. The project has questioned whether this is the right design. This 866 800 specification describes the current behavior in Section 12.2 without 867 801 committing to it. 868 802 869 - 7. **Should the rendering and input concerns be distributed as separate 870 - packages?** They are architecturally independent (INV-8) but currently 871 - co-located. 872 - 873 - 8. **What are the specific transfer encoding details?** The encoding structure 803 + 5. **What are the specific transfer encoding details?** The encoding structure 874 804 is described in Section 12.1 as current implementation surface. Locking down 875 805 opcode values would constrain future extensions unnecessarily. 876 806 877 - 9. **What is the complete set of directive properties?** The property groups 807 + 6. **What is the complete set of directive properties?** The property groups 878 808 available in `open()` and `text()` are described in Section 12.2 as current 879 809 implementation surface. They have been extended incrementally and will 880 810 continue to grow. 881 811 882 - 10. **What are the validation and error semantics?** How the renderer responds 883 - to invalid input is unspecified. Callers SHOULD validate, but the validation 884 - model is not yet settled enough to define normatively. 812 + 7. **What are the validation and error semantics?** How the renderer responds to 813 + invalid input is unspecified. Callers SHOULD validate, but the validation 814 + model is not yet settled enough to define normatively.
+168
specs/input-spec.md
··· 1 + # Clayterm Input Specification 2 + 3 + **Version:** 0.1 (draft) **Status:** Current-state specification. Descriptive 4 + for the input parsing surface. 5 + 6 + --- 7 + 8 + ## 1. Purpose 9 + 10 + This specification describes Clayterm's terminal input parsing surface: the API 11 + for decoding raw terminal byte sequences into structured events. 12 + 13 + Input parsing is architecturally independent from rendering (see 14 + [Renderer Specification](renderer-spec.md), INV-8). The two concerns share a 15 + compiled WASM binary for loading efficiency, but neither depends on the other's 16 + state, types, or API surface. 17 + 18 + This specification is currently non-normative. The input API has clear design 19 + intent but has undergone more revision than the rendering core and faces known 20 + upcoming forces that will reshape it (Kitty progressive enhancement field 21 + surfacing, terminfo binary parsing). It is written to document the current 22 + surface and guide future stabilization. 23 + 24 + --- 25 + 26 + ## 2. Scope 27 + 28 + ### In scope (descriptive) 29 + 30 + - The input parser creation and lifecycle 31 + - The scan API and its return type 32 + - The `InputEvent` discriminated union and its variants 33 + - The ESC timeout resolution model 34 + 35 + ### Out of scope 36 + 37 + - Rendering (see [Renderer Specification](renderer-spec.md)) 38 + - Pointer hit detection (owned by the render loop; see Renderer Specification, 39 + Section 12.4) 40 + - Higher-level event routing, focus management, or keybinding systems 41 + 42 + --- 43 + 44 + ## 3. Terminology 45 + 46 + **Input parser.** A WASM-backed instance that accepts raw terminal bytes and 47 + produces structured events. Each parser maintains its own internal state for 48 + multi-byte sequence buffering and ESC timeout tracking. 49 + 50 + **Scan.** A single invocation of the parser. The caller provides raw bytes (or 51 + no bytes, for timeout resolution), and the parser returns any events it can 52 + produce along with pending-timeout information. 53 + 54 + **InputEvent.** A discriminated union representing a single parsed terminal 55 + event. Discriminated on a `type` field. 56 + 57 + --- 58 + 59 + ## 4. Input Parser API 60 + 61 + ### 4.1 Parser creation 62 + 63 + ``` 64 + createInput(options?): Promise<Input> 65 + ``` 66 + 67 + Creates an input parser instance. The returned promise resolves when the WASM 68 + module is ready. 69 + 70 + Options: 71 + 72 + - **`escLatency`** — Milliseconds to wait before resolving a lone ESC byte as 73 + the Escape key. Default: 25ms. This controls the tradeoff between 74 + responsiveness (lower values) and correct disambiguation of ESC-prefixed 75 + sequences (higher values). 76 + 77 + - **`terminfo`** — A `Uint8Array` of raw terminfo binary. Accepted but C-side 78 + parsing is not yet implemented. 79 + 80 + ### 4.2 Scan 81 + 82 + ``` 83 + input.scan(bytes?: Uint8Array): ScanResult 84 + ``` 85 + 86 + Feeds raw terminal bytes into the parser and returns parsed events. The `bytes` 87 + parameter is optional; calling without arguments triggers a rescan for ESC 88 + timeout resolution. 89 + 90 + The parser is synchronous: it processes all provided bytes in a single call and 91 + returns immediately. 92 + 93 + ### 4.3 ScanResult 94 + 95 + ``` 96 + { events: InputEvent[], pending?: { delay: number, deadline: number } } 97 + ``` 98 + 99 + - **`events`** — An array of parsed events produced from the provided bytes (and 100 + any previously buffered bytes that could now be resolved). 101 + 102 + - **`pending`** — When present, indicates that an ambiguous ESC byte is buffered 103 + and the parser cannot yet determine whether it begins an escape sequence or is 104 + a standalone Escape keypress. The caller SHOULD schedule a rescan (calling 105 + `scan()` with no arguments) after the indicated delay. The `delay` field is a 106 + relative duration in milliseconds. The `deadline` field is an absolute 107 + timestamp (milliseconds since epoch) for the same point in time. 108 + 109 + --- 110 + 111 + ## 5. InputEvent Types 112 + 113 + The `InputEvent` discriminated union is discriminated on a `type` field. The 114 + current variants are: 115 + 116 + - **`KeyEvent`** (`type: "keydown" | "keyup" | "keyrepeat"`) — A keyboard event 117 + for special keys, control sequences, and modifier combinations. Fields include 118 + `key` (logical key name), `code` (physical key identifier), and modifier flags 119 + (`shift`, `ctrl`, `alt`, `meta`). 120 + 121 + - **`MouseEvent`** (`type: "mousedown" | "mouseup"`) — A mouse button press or 122 + release. Fields include `x`, `y` (cell coordinates), `button`, and modifier 123 + flags. 124 + 125 + - **`WheelEvent`** (`type: "wheel"`) — A scroll event. Fields include `x`, `y`, 126 + and scroll direction. 127 + 128 + - **`ResizeEvent`** (`type: "resize"`) — A terminal resize notification. Fields 129 + include `columns` and `rows`. 130 + 131 + The discriminant values and the type splits are deliberate design decisions. 132 + However, the field sets within each variant are expected to grow when Kitty 133 + progressive enhancement types are surfaced in the TypeScript layer (the C struct 134 + has already been extended with fields that are not yet mapped to the TS types). 135 + 136 + --- 137 + 138 + ## 6. Deferred / Future Areas 139 + 140 + _These topics are explicitly excluded from this specification. Their omission is 141 + intentional, not an oversight._ 142 + 143 + **Full Kitty progressive enhancement event types.** The C-side input parser 144 + struct has been extended for progressive enhancement fields. The TypeScript 145 + event types have not been updated to surface them. 146 + 147 + **Terminfo binary parsing.** The input API accepts a `terminfo` option, but 148 + C-side parsing is not implemented. 149 + 150 + **Whether input parsing should be a separate package.** Architecturally 151 + independent from the renderer but currently co-located. The distribution 152 + decision is open. 153 + 154 + --- 155 + 156 + ## Open Decisions 157 + 158 + 1. **What are the normative Kitty progressive enhancement event types?** The 159 + C-side struct has been extended. The TypeScript types have not been updated. 160 + This specification does not attempt to predict the final shapes. 161 + 162 + 2. **Should the input API be a separate package?** It is architecturally 163 + independent from the renderer (INV-8) but currently co-located in the same 164 + module. 165 + 166 + 3. **Is the input API ready for normative specification?** The API has clear 167 + design ownership but has undergone more revision than the rendering core. 168 + This specification documents the current surface without freezing it.