[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.

๐Ÿ“ address PR feedback on spec

- Replace "descriptor" terminology with "directive" throughout
- Rename open() `name` parameter to `id` in spec
- Fix buffer diffing description: cells are copied, not swapped
- Add note that pointer hit detection requires render loop participation
- Reframe border-layout interaction as Clay's intended behavior
- Update WASM loading to reflect inlined base64 binary
- Move output buffer lifetime guarantee to normative section
- Clarify pack() is internal, validate() is public API

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

+527 -161
+527 -161
specs/clayterm-spec.md
··· 1 1 # Clayterm Current-State Specification 2 2 3 - **Version:** 0.1 (draft) 4 - **Status:** Current-state specification. Normative for the rendering contract. Descriptive for settling surfaces. 3 + **Version:** 0.1 (draft) **Status:** Current-state specification. Normative for 4 + the rendering contract. Descriptive for settling surfaces. 5 5 6 6 --- 7 7 8 8 ## 1. Purpose 9 9 10 - 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. 10 + Clayterm is a terminal rendering engine. It accepts a declarative description of 11 + a terminal UI layout, performs layout computation and cell-level diffing 12 + internally, and returns ANSI escape byte sequences suitable for direct write to 13 + a terminal output stream. 11 14 12 - 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. 15 + This specification defines Clayterm's current-state rendering contract: its 16 + architectural model, its invariants, its stable public API surface, and its 17 + intentional boundaries. It is written to allow future feature work to extend the 18 + project without destabilizing the core. 13 19 14 - This specification does not attempt to define areas of Clayterm that are still settling. Where the project has working but evolving surfaces โ€” including the input parsing API, pointer event model, and certain wrapper types โ€” those are described in Section 12 as current implementation rather than normative contract. 20 + This specification does not attempt to define areas of Clayterm that are still 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. 15 25 16 26 --- 17 27 ··· 22 32 - The rendering pipeline and its architectural commitments 23 33 - The frame-snapshot rendering model 24 34 - The stable public rendering API 25 - - The descriptor model and core helpers 35 + - The directive model and core helpers 26 36 - Element identity and frame semantics 27 37 - Boundary responsibilities (what Clayterm owns and what it does not) 28 38 29 39 ### In scope (non-normative, descriptive) 30 40 31 - - Current implementation surfaces that are settling but not yet stable enough to freeze (Section 12) 32 - - Implementation notes that aid understanding but do not define contract (Section 14) 41 + - Current implementation surfaces that are settling but not yet stable enough to 42 + freeze (Section 12) 43 + - Implementation notes that aid understanding but do not define contract 44 + (Section 14) 33 45 34 46 ### Out of scope 35 47 ··· 45 57 46 58 ## 3. Terminology 47 59 48 - **Frame.** A single, complete rendering pass. Each frame begins with the caller providing descriptors and ends with the renderer returning ANSI bytes. Frames are independent; the renderer carries no UI tree state between them. 60 + **Frame.** A single, complete rendering pass. Each frame begins with the caller 61 + providing directives and ends with the renderer returning ANSI bytes. Frames are 62 + independent; the renderer carries no UI tree state between them. 49 63 50 - **Descriptor (op).** A plain object that declares one element of the UI tree for a single frame. Descriptors are typed by an identifier field and carry layout, styling, and content properties. The set of descriptors for a frame is ordered and forms an implicit tree via open/close pairing. 64 + **Directive (op).** A plain object that declares one element of the UI tree for 65 + a single frame. Directives are typed by an identifier field and carry layout, 66 + styling, and content properties. The set of directives for a frame is ordered 67 + and forms an implicit tree via open/close pairing. 51 68 52 - **Descriptor array.** An ordered array of descriptors constituting a complete frame description. The array is the input to the rendering transaction. 69 + **Directive array.** An ordered array of directives constituting a complete 70 + frame description. The array is the input to the rendering transaction. 53 71 54 - **Render transaction.** The process of accepting a descriptor 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. 72 + **Render transaction.** The process of accepting a directive array, performing 73 + layout, walking render commands, diffing against the previous frame's cell 74 + buffer, and producing ANSI byte output. A render transaction is a single, 75 + synchronous operation from the caller's perspective. 55 76 56 - **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 descriptors. ANSI bytes include cursor-positioning sequences, SGR (Select Graphic Rendition) attribute sequences, and UTF-8 text. 77 + **ANSI bytes.** A byte sequence of UTF-8โ€“encoded ANSI escape codes and text 78 + content that, when written to a terminal file descriptor, produces the visual 79 + output described by the frame's directives. ANSI bytes include 80 + cursor-positioning sequences, SGR (Select Graphic Rendition) attribute 81 + sequences, and UTF-8 text. 57 82 58 - **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. 83 + **Renderer core.** The WASM module and its TS entry points that together 84 + implement the render transaction. The renderer core owns layout computation, 85 + render-command walking, cell-buffer diffing, and ANSI byte generation. 59 86 60 - **Caller.** Any code that invokes Clayterm's public API to produce terminal output. The caller owns terminal setup, IO, input handling, and application lifecycle. 87 + **Caller.** Any code that invokes Clayterm's public API to produce terminal 88 + output. The caller owns terminal setup, IO, input handling, and application 89 + lifecycle. 61 90 62 - **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. 91 + **Higher-level framework.** A component model, reconciler, or application 92 + framework built on top of Clayterm. Examples include crankterm. Clayterm has no 93 + dependency on any higher-level framework, and this specification does not 94 + constrain their design. 63 95 64 - **Term.** An instance of the Clayterm renderer, bound to specific terminal dimensions. A Term is the object through which the caller performs render transactions. 96 + **Term.** An instance of the Clayterm renderer, bound to specific terminal 97 + dimensions. A Term is the object through which the caller performs render 98 + transactions. 65 99 66 100 --- 67 101 68 102 ## 4. Architectural Model 69 103 70 - *This section is normative.* 104 + _This section is normative._ 71 105 72 106 ### 4.1 Pipeline 73 107 74 108 Clayterm implements a rendering pipeline with the following stages: 75 109 76 - 1. **Descriptor acceptance.** The caller provides a complete descriptor array representing the desired UI state for a single frame. 110 + 1. **Directive acceptance.** The caller provides a complete directive array 111 + representing the desired UI state for a single frame. 77 112 78 - 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. 113 + 2. **Transfer.** The renderer transfers the frame description into the WASM 114 + module. The transfer mechanism is an implementation detail. The normative 115 + requirement is that the transfer occurs as part of a single render 116 + transaction; the caller does not interact with the transfer mechanism 117 + directly. 79 118 80 - 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. 119 + 3. **Render transaction.** The WASM module processes the frame description. 120 + Internally, it drives a layout engine to compute element positions and sizes, 121 + walks the resulting render commands to populate a cell buffer, and diffs the 122 + cell buffer against the previous frame. 81 123 82 - 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. 124 + 4. **Output generation.** For each cell that differs from the previous frame, 125 + the renderer emits ANSI escape sequences (cursor positioning, color 126 + attributes, and text) into an output buffer. 83 127 84 128 5. **Output retrieval.** The caller reads the ANSI byte output. 85 129 86 130 ### 4.2 Single-transaction rendering 87 131 88 - A frame MUST be rendered in a single transaction that crosses the TSโ†’WASM boundary once. The caller provides the complete descriptor array, invokes the render transaction, and reads the output. There are no intermediate callbacks, yields, or partial results. 132 + A frame MUST be rendered in a single transaction that crosses the TSโ†’WASM 133 + boundary once. The caller provides the complete directive array, invokes the 134 + render transaction, and reads the output. There are no intermediate callbacks, 135 + yields, or partial results. 89 136 90 137 ### 4.3 Frame-snapshot model 91 138 92 - 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. 139 + Each render transaction operates on a complete, self-contained snapshot of the 140 + UI. The renderer MUST NOT maintain an internal component tree or UI state across 141 + frames. The only state the renderer retains between frames is the cell buffer 142 + used for diffing, which is an implementation detail of output minimization and 143 + not observable to the caller except through reduced output size. 93 144 94 145 ### 4.4 Double-buffered diffing 95 146 96 - 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. The buffers are then swapped. This mechanism is internal to the renderer and not directly observable to the caller. 147 + The renderer maintains two cell buffers: a front buffer (the previously rendered 148 + frame) and a back buffer (the frame being rendered). After populating the back 149 + buffer from the current frame's render commands, the renderer compares it 150 + against the front buffer and emits ANSI bytes only for cells that differ. 151 + Changed cells are then copied from the back buffer to the front buffer so that 152 + both buffers are identical at the end of the transaction. This mechanism is 153 + internal to the renderer and not directly observable to the caller. 97 154 98 155 --- 99 156 100 157 ## 5. Contract Layer Boundary 101 158 102 - *This section is normative.* 159 + _This section is normative._ 103 160 104 - This specification defines the **architectural rendering contract**: the commitments that make Clayterm what it is and that callers and framework authors can depend on. 161 + This specification defines the **architectural rendering contract**: the 162 + commitments that make Clayterm what it is and that callers and framework authors 163 + can depend on. 105 164 106 165 This specification **does not** define the following as normative: 107 166 108 - - **The internal transfer encoding.** The mechanism by which descriptors 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. 167 + - **The internal transfer encoding.** The mechanism by which directives are 168 + serialized for the WASM module โ€” its byte format, opcode structure, and field 169 + encoding โ€” is an implementation detail. The normative commitment is that the 170 + transfer happens within a single render transaction; the encoding is described 171 + in Section 12.1 as current implementation surface. 109 172 110 - - **Validation or error semantics.** How the renderer responds to invalid input (malformed descriptor 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. 173 + - **Validation or error semantics.** How the renderer responds to invalid input 174 + (malformed directive arrays, unbalanced open/close pairs) is not yet specified 175 + as contract. Section 9.1 defines what constitutes valid input. Behavior for 176 + invalid input is currently unspecified. 111 177 112 - - **The complete set of descriptor properties.** The existence of the core descriptor 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. 178 + - **The complete set of directive properties.** The existence of the core 179 + directive constructors (`open`, `close`, `text`) and the core sizing helpers 180 + (`grow`, `fixed`, `fit`) is normative. The full set of properties accepted by 181 + these constructors โ€” which layout fields, which styling options, which 182 + configuration groups are available โ€” is current implementation surface 183 + described in Section 12.2. New property groups have been added over time and 184 + more may follow. 113 185 114 - - **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. 186 + - **The return type wrapper of `render()`.** The commitment that `render()` 187 + produces ANSI bytes accessible as a `Uint8Array` is normative. The wrapper 188 + type around those bytes is current implementation surface described in Section 189 + 12.3. 115 190 116 - Future readers should not treat current implementation surface as identical to the contract boundary. 191 + Future readers should not treat current implementation surface as identical to 192 + the contract boundary. 117 193 118 194 --- 119 195 120 196 ## 6. Core Invariants 121 197 122 - *This section is normative.* 198 + _This section is normative._ 123 199 124 - **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. 200 + **INV-1. Zero IO.** The renderer MUST NOT perform any terminal input or output. 201 + It MUST NOT write to stdout, read from stdin, open file descriptors, or interact 202 + with the terminal device. The renderer produces bytes; the caller writes them. 125 203 126 - **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 descriptor array and receives ANSI bytes in return. 204 + **INV-2. Single transaction per frame.** Each frame MUST be rendered in a single 205 + transaction that crosses the TSโ†’WASM boundary once. The caller provides the 206 + complete frame as a directive array and receives ANSI bytes in return. 127 207 128 - **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 descriptor array fully describes its frame. 208 + **INV-3. Frame-snapshot independence.** The renderer MUST NOT require the caller 209 + to maintain or provide state across frames beyond calling `render()` on the same 210 + Term instance. Each directive array fully describes its frame. 129 211 130 - **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. 212 + **INV-4. ANSI byte output.** The output of a render transaction MUST be a byte 213 + sequence of valid UTF-8โ€“encoded ANSI escape codes that is directly writable to a 214 + terminal output stream without further transformation or encoding. 131 215 132 - **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. 216 + **INV-5. Layout/render/diff ownership.** The renderer owns the layout 217 + computation, render-command walk, cell-buffer diffing, and ANSI byte generation 218 + stages. The caller MUST NOT need to perform any of these operations. 133 219 134 - **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. 220 + **INV-6. Internal lifecycle symmetry.** The renderer's internal layout lifecycle 221 + (begin-layout and end-layout calls to the underlying layout engine) MUST be 222 + symmetric: both calls occur within the same render transaction, in the same 223 + function scope. 135 224 136 - **INV-7. Element identity disambiguation.** When multiple elements within a frame share the same tag name, 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 tag names MUST NOT cause layout corruption or element conflation. 225 + **INV-7. Element identity disambiguation.** When multiple elements within a 226 + frame share the same id, the renderer MUST disambiguate their identities so that 227 + the layout engine does not conflate them. The disambiguation mechanism is an 228 + implementation detail, but the guarantee is normative: identical ids MUST NOT 229 + cause layout corruption or element conflation. 137 230 138 - **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. 231 + **INV-8. Separation of concerns.** The rendering concern and the input-parsing 232 + concern MUST remain independent. Neither MUST depend on the other's state, 233 + types, or API surface. They MAY share a compiled WASM binary for loading 234 + efficiency, but this is an implementation convenience, not an architectural 235 + coupling. 139 236 140 237 --- 141 238 142 239 ## 7. Rendering Contract 143 240 144 - *This section is normative.* 241 + _This section is normative._ 145 242 146 243 ### 7.1 Inputs 147 244 148 245 The rendering transaction accepts: 149 246 150 - - A **descriptor array**: an ordered array of descriptor objects constituting a complete frame. The array MUST contain balanced open/close pairs forming a valid tree structure. 247 + - A **directive array**: an ordered array of directive objects constituting a 248 + complete frame. The array MUST contain balanced open/close pairs forming a 249 + valid tree structure. 151 250 152 - The descriptor array is the sole required input to a render transaction. 251 + The directive array is the sole required input to a render transaction. 153 252 154 253 ### 7.2 Rendering transaction 155 254 156 255 When the caller invokes a render transaction: 157 256 158 - 1. The renderer accepts the descriptor array and transfers the frame description into the WASM module. 159 - 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. 257 + 1. The renderer accepts the directive array and transfers the frame description 258 + into the WASM module. 259 + 2. The WASM module processes the frame: it computes layout, walks render 260 + commands, populates the cell buffer, diffs against the previous frame, and 261 + writes ANSI bytes for changed cells. 160 262 3. Control returns to the caller with the ANSI byte output available. 161 263 162 - The render transaction is synchronous from the caller's perspective once invoked. It MUST NOT yield, suspend, or require callbacks during execution. 264 + The render transaction is synchronous from the caller's perspective once 265 + invoked. It MUST NOT yield, suspend, or require callbacks during execution. 163 266 164 267 ### 7.3 Output 165 268 ··· 167 270 168 271 - MUST be valid UTF-8 169 272 - MUST consist of ANSI escape sequences (CSI, SGR) and text content 170 - - MUST be directly writable to a terminal file descriptor to produce the described visual output 171 - - MUST represent only the cells that changed since the previous frame (on a Term instance that has rendered at least one prior frame) 273 + - MUST be directly writable to a terminal file descriptor to produce the 274 + described visual output 275 + - In cursor update mode, MUST represent only the cells that changed since the 276 + previous frame (on a Term instance that has rendered at least one prior frame) 277 + - In line mode, MUST represent all cells in the frame as newline-separated rows 278 + 279 + The output reflects the complete visual state of the frame. The caller SHOULD 280 + write the output to the terminal without modification. 172 281 173 - The output reflects the complete visual state of the frame. The caller SHOULD write the output to the terminal without modification. 282 + The output `Uint8Array` is a view over renderer-owned memory. It is valid until 283 + the next `render()` call on the same Term instance, at which point the buffer 284 + may be reused. Callers who need to retain the output beyond the next render MUST 285 + copy it. 174 286 175 287 ### 7.4 Lifecycle 176 288 177 - A Term instance is created for specific terminal dimensions. The caller provides width and height at creation time. 289 + A Term instance is created for specific terminal dimensions. The caller provides 290 + width and height at creation time. 178 291 179 - 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. 292 + To handle terminal resize, the caller creates a new Term with the new 293 + dimensions. The previous Term instance becomes stale and SHOULD NOT be used for 294 + further rendering. 180 295 181 - 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. 296 + Creation of a Term is asynchronous because it may involve WASM module 297 + preparation. A Term instance MAY be used for any number of render transactions. 298 + The Term retains its cell buffers across frames for diffing purposes. 182 299 183 300 --- 184 301 185 302 ## 8. Public Rendering API 186 303 187 - *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.* 304 + _This section is normative. Only items with high confidence of stability are 305 + included. See Section 5 for what this section does and does not freeze._ 188 306 189 307 ### 8.1 Term creation 190 308 ··· 192 310 createTerm(options: { width: number; height: number }): Promise<Term> 193 311 ``` 194 312 195 - 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. 313 + Creates a new Term instance bound to the specified terminal dimensions. The 314 + returned promise resolves when the renderer is ready. The `width` and `height` 315 + parameters specify the terminal dimensions in character cells. 196 316 197 317 ### 8.2 Render invocation 198 318 199 319 ``` 200 - term.render(ops: Op[]): <result containing ANSI bytes as Uint8Array> 320 + term.render(ops: Op[], options?: RenderOptions): <result containing ANSI bytes as Uint8Array> 201 321 ``` 202 322 203 - Accepts an ordered array of descriptor objects and performs a render transaction as defined in Section 7. Returns the ANSI byte output as a `Uint8Array`. 323 + Accepts an ordered array of directive objects and performs a render transaction 324 + as defined in Section 7. Returns the ANSI byte output as a `Uint8Array`. 325 + 326 + The optional `options` parameter controls the rendering mode. See Section 8.2.1 327 + and 8.2.2 for the two available modes. 328 + 329 + The return type is specified here only to the extent that the ANSI bytes MUST be 330 + accessible as a `Uint8Array`. The precise shape of the return value โ€” whether it 331 + is a bare `Uint8Array`, a wrapper object, or a structure carrying additional 332 + data โ€” is part of the current implementation surface described in Section 12.3 333 + and is not locked down by this specification. 204 334 205 - 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. 335 + #### 8.2.1 Cursor update mode (default) 206 336 207 - ### 8.3 Descriptor constructors 337 + When `mode` is omitted, the renderer operates in cursor update mode. Output 338 + consists of ANSI bytes with absolute CUP (`\x1b[row;colH`) cursor positioning 339 + for each changed cell. Only cells that differ from the previous frame are 340 + emitted, making this efficient for full-screen UIs where most of the screen is 341 + static between frames. 208 342 209 - Descriptors are created using constructor functions that return plain objects. The caller assembles these into an array. This pattern โ€” functions returning plain descriptors, composed into arrays โ€” is normative. A builder, fluent, or mutation-based API is explicitly rejected. 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. 347 + 348 + #### 8.2.2 Line mode 349 + 350 + When `mode` is `"line"`, the renderer emits all cells as newline-separated rows 351 + without CUP positioning. Every cell is written regardless of whether it changed 352 + since the previous frame. The front buffer is updated so that a subsequent 353 + cursor update mode render can diff efficiently. 354 + 355 + Line mode is intended for inline region rendering where the caller manages 356 + cursor positioning externally and the output must work in pipes or non-alternate 357 + screen contexts. 358 + 359 + ### 8.3 Directive constructors 360 + 361 + Directives are created using constructor functions that return plain objects. 362 + The caller assembles these into an array. This pattern โ€” functions returning 363 + plain directives, composed into arrays โ€” is normative. A builder, fluent, or 364 + mutation-based API is explicitly rejected. 210 365 211 366 #### 8.3.1 open 212 367 213 368 ``` 214 - open(name: string, props?): OpenElement 369 + open(id: string, props?): OpenElement 215 370 ``` 216 371 217 - Creates an element-open descriptor. The `name` parameter provides a tag name for the element. The optional `props` parameter carries configuration for layout, styling, and behavior. 372 + Creates an element-open directive. The `id` parameter provides an identity for 373 + the element within the frame, used by the underlying layout engine for element 374 + tracking and hit-testing. The optional `props` parameter carries configuration 375 + for layout, styling, and behavior. 218 376 219 - Elements opened with `open()` MUST be closed with a corresponding `close()` descriptor later in the same descriptor array. 377 + Elements opened with `open()` MUST be closed with a corresponding `close()` 378 + directive later in the same directive array. 220 379 221 - 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. 380 + The set of properties accepted by `props` is part of the current implementation 381 + surface described in Section 12.2. This specification defines the existence and 382 + signature of `open()` normatively but does not freeze the complete property 383 + surface, which has been extended incrementally and may continue to grow. 222 384 223 385 #### 8.3.2 close 224 386 ··· 226 388 close(): CloseElement 227 389 ``` 228 390 229 - Creates an element-close descriptor. Each `close()` MUST correspond to a preceding `open()`. 391 + Creates an element-close directive. Each `close()` MUST correspond to a 392 + preceding `open()`. 230 393 231 394 #### 8.3.3 text 232 395 ··· 234 397 text(content: string, props?): Text 235 398 ``` 236 399 237 - Creates a text descriptor. The `content` parameter provides the text string to render. The optional `props` parameter carries text styling configuration. 400 + Creates a text directive. The `content` parameter provides the text string to 401 + render. The optional `props` parameter carries text styling configuration. 238 402 239 - Text descriptors MUST appear between a matching open/close pair. 403 + Text directives MUST appear between a matching open/close pair. 240 404 241 - The set of styling properties accepted by `props` is part of the current implementation surface and may be extended. 405 + The set of styling properties accepted by `props` is part of the current 406 + implementation surface and may be extended. 242 407 243 408 ### 8.4 Sizing helpers 244 409 245 - These functions produce sizing-axis values for use in element layout configuration: 410 + These functions produce sizing-axis values for use in element layout 411 + configuration: 246 412 247 413 ``` 248 414 grow(): SizingAxis 249 415 ``` 416 + 250 417 The element expands to fill available space in the parent along this axis. 251 418 252 419 ``` 253 420 fixed(value: number): SizingAxis 254 421 ``` 422 + 255 423 The element has a fixed size of `value` cells along this axis. 256 424 257 425 ``` 258 426 fit(min?: number, max?: number): SizingAxis 259 427 ``` 260 - The element sizes to fit its content, optionally constrained by minimum and maximum bounds. 428 + 429 + The element sizes to fit its content, optionally constrained by minimum and 430 + maximum bounds. 261 431 262 432 ### 8.5 Color helper 263 433 ··· 265 435 rgba(r: number, g: number, b: number, a?: number): number 266 436 ``` 267 437 268 - 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 descriptor model expects a color. 438 + Packs color channel values (each 0โ€“255) into a single 32-bit integer in ARGB 439 + format. Alpha defaults to 255 (fully opaque). The returned value is used 440 + wherever the directive model expects a color. 269 441 270 442 --- 271 443 272 - ## 9. Descriptor Model 444 + ## 9. Directive Model 273 445 274 - *This section is normative.* 446 + _This section is normative._ 275 447 276 - ### 9.1 Descriptor-array pattern 448 + ### 9.1 Directive-array pattern 277 449 278 - The rendering input is an ordered array of descriptor objects. Each descriptor is a plain JavaScript/TypeScript object created by a constructor function (Section 8.3). Descriptors 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. 450 + The rendering input is an ordered array of directive objects. Each directive is 451 + a plain JavaScript/TypeScript object created by a constructor function (Section 452 + 8.3). Directives are not classes, do not carry methods, and do not participate 453 + in a prototype chain. They MAY be spread, composed, stored, or inspected as 454 + ordinary objects. 279 455 280 - The array is processed in order. Open and close descriptors form an implicit tree. The renderer processes them sequentially. 456 + The array is processed in order. Open and close directives form an implicit 457 + tree. The renderer processes them sequentially. 281 458 282 - A descriptor array with unbalanced open/close pairs, or with close descriptors that do not match a preceding open, is invalid input. Callers SHOULD validate descriptor arrays before rendering. The renderer's behavior when given an invalid descriptor array is unspecified by this specification. 459 + A directive array with unbalanced open/close pairs, or with close directives 460 + that do not match a preceding open, is invalid input. Callers SHOULD validate 461 + directive arrays before rendering. The renderer's behavior when given an invalid 462 + directive array is unspecified by this specification. 283 463 284 464 ### 9.2 Transfer to the WASM module 285 465 286 - As part of the render transaction, the descriptor 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. 466 + As part of the render transaction, the directive array is transferred into a 467 + form that the WASM module can process. This transfer is handled internally by 468 + the renderer and is not an operation the caller performs or observes. The 469 + transfer mechanism is an implementation detail described in Section 12.1. 287 470 288 - ### 9.3 Descriptor identity 471 + ### 9.3 Directive identity 289 472 290 - Each element descriptor is assigned an identity within the frame for use by the underlying layout engine. When multiple elements share the same tag name (the `name` 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 names; the renderer handles uniqueness internally. 473 + Each element directive is assigned an identity within the frame for use by the 474 + underlying layout engine. When multiple elements share the same id (the `id` 475 + parameter to `open()`), the renderer MUST disambiguate their identities 476 + automatically. The disambiguation mechanism is an implementation detail. The 477 + normative requirement is that the caller MUST NOT need to provide globally 478 + unique ids; the renderer handles uniqueness internally. 291 479 292 480 --- 293 481 294 482 ## 10. Identity and Frame Semantics 295 483 296 - *This section is normative.* 484 + _This section is normative._ 297 485 298 486 ### 10.1 Frame completeness 299 487 300 - A descriptor 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. 488 + A directive array provided to `render()` MUST represent a complete frame. The 489 + renderer does not support incremental updates, partial frames, or delta 490 + descriptions. Every frame fully specifies the desired UI state. 301 491 302 - ### 10.2 Descriptor ordering 492 + ### 10.2 Directive ordering 303 493 304 - Descriptors MUST be provided in depth-first tree order. An `open()` descriptor begins an element; its children (including nested open/close pairs and text descriptors) follow in order; a `close()` descriptor ends the element. The renderer processes descriptors in the order they appear in the array. 494 + Directives MUST be provided in depth-first tree order. An `open()` directive 495 + begins an element; its children (including nested open/close pairs and text 496 + directives) follow in order; a `close()` directive ends the element. The 497 + renderer processes directives in the order they appear in the array. 305 498 306 499 ### 10.3 Element identity within a frame 307 500 308 - 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 tag name in the same frame MUST NOT cause layout corruption, hash collision, or identity conflation. 501 + Within a single frame, each element MUST have an unambiguous identity for the 502 + layout engine. As specified in Section 9.3, the renderer handles disambiguation. 503 + Two elements with the same id in the same frame MUST NOT cause layout 504 + corruption, hash collision, or identity conflation. 309 505 310 506 ### 10.4 No cross-frame identity 311 507 312 - The renderer does not track element identity across frames. An element named "sidebar" in frame N and an element named "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. 508 + The renderer does not track element identity across frames. An element with id 509 + "sidebar" in frame N and an element with id "sidebar" in frame N+1 are not 510 + related from the renderer's perspective. Cross-frame identity, if needed, is the 511 + responsibility of a higher-level framework. 313 512 314 513 --- 315 514 316 515 ## 11. Boundaries and Non-Responsibilities 317 516 318 - *This section is normative.* 517 + _This section is normative._ 319 518 320 519 ### 11.1 The renderer does not perform IO 321 520 322 - 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. 521 + The renderer MUST NOT write to any output stream. The renderer MUST NOT read 522 + from any input stream. The renderer produces bytes; the caller decides when and 523 + how to write them. This enables the renderer to operate in any environment where 524 + WebAssembly is available, including browsers, server-side runtimes, and embedded 525 + contexts. 323 526 324 527 ### 11.2 The renderer does not manage terminal state 325 528 326 - The renderer MUST NOT emit escape sequences for any of the following terminal-management operations: 529 + The renderer MUST NOT emit escape sequences for any of the following 530 + terminal-management operations: 327 531 328 532 - Entering or leaving the alternate screen buffer 329 533 - Hiding or showing the cursor 330 534 - Setting the cursor shape or blink state 331 535 - Enabling or disabling mouse reporting 332 - - Enabling or disabling keyboard protocol modes (e.g., Kitty progressive enhancement) 536 + - Enabling or disabling keyboard protocol modes (e.g., Kitty progressive 537 + enhancement) 333 538 - Enabling or disabling raw mode or similar terminal disciplines 334 539 335 - 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). 540 + These are the caller's responsibility. The renderer's output contains only the 541 + escape sequences needed to render the frame content (cursor positioning for cell 542 + writes, SGR attributes for styling, and UTF-8 text). 336 543 337 544 ### 11.3 The renderer does not own application lifecycle 338 545 339 - 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. 546 + The renderer MUST NOT maintain a run loop, event loop, timer, or subscription 547 + mechanism. It does not schedule frames. It does not manage component state. It 548 + renders when asked and returns. The decision of when to render is entirely the 549 + caller's. 340 550 341 551 ### 11.4 The renderer does not own input parsing 342 552 343 - Input parsing (keyboard events, mouse events, escape sequence decoding) is an independent concern. It is not part of the rendering contract defined by this specification. The renderer MUST NOT depend on input-parsing state, types, or API. 553 + 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. 344 557 345 - Clayterm currently provides input-parsing functionality alongside the renderer in the same package. This co-location is an implementation detail, not an architectural coupling. Section 12.4 describes the current input surface. 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. 561 + 562 + However, pointer hit detection does require the render loop to participate. The 563 + caller may pass the current pointer position as part of render options, and the 564 + 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 566 + the current pointer event surface. 346 567 347 568 ### 11.5 The renderer does not own higher-level framework concerns 348 569 349 570 The renderer MUST NOT implement or depend on: 350 571 351 572 - Component models or component lifecycles 352 - - Reconciliation or diffing of descriptor trees (the renderer diffs *cells*, not *trees*) 573 + - Reconciliation or diffing of directive trees (the renderer diffs _cells_, not 574 + _trees_) 353 575 - State management or reactivity 354 576 - Event propagation through a component hierarchy 355 577 ··· 359 581 360 582 ## 12. Current Surface That Remains Elastic 361 583 362 - *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.* 584 + _This entire section is non-normative. It describes the current implementation 585 + surface to aid consumers and future spec authors. The shapes described here are 586 + real, working, and in many cases deliberately designed, but they do not yet meet 587 + the stability threshold for normative specification. They MAY change in future 588 + versions without constituting a breaking change to the normative core defined 589 + above._ 363 590 364 591 ### 12.1 Transfer encoding (command protocol) 365 592 366 - The renderer currently serializes descriptors into a flat byte buffer using a command protocol based on fixed-width `Uint32` words. Each descriptor is encoded as an opcode word followed by descriptor-specific data. Element-open descriptors 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. 593 + The renderer currently serializes directives into a flat byte buffer using a 594 + command protocol based on fixed-width `Uint32` words. Each directive is encoded 595 + as an opcode word followed by directive-specific data. Element-open directives 596 + use a property mask to indicate which optional field groups (layout, border, 597 + corner radius, clip, floating, scroll) are present, followed by the data for 598 + each indicated group. Strings are encoded as length-prefixed UTF-8 byte 599 + sequences within the word stream. Floats are stored as bit-reinterpreted 600 + `Uint32` values. 367 601 368 - 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. 602 + This encoding has been extended incrementally (floating, clip, and scroll groups 603 + were added after the initial protocol) but has never been restructured. It is 604 + likely to remain stable in structure while continuing to grow. However, specific 605 + opcode values, mask definitions, and field layouts are implementation details 606 + and are not locked down by this specification. 369 607 370 - ### 12.2 Descriptor property groups 608 + ### 12.2 Directive property groups 371 609 372 - The `open()` constructor currently accepts the following property groups in its `props` parameter: 610 + The `open()` constructor currently accepts the following property groups in its 611 + `props` parameter: 373 612 374 - - **`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 613 + - **`layout`** โ€” sizing (width and height, specified via sizing helpers), 614 + padding (per-side), alignment (currently numeric enum values, with a planned 615 + transition to string literals), direction (top-to-bottom or left-to-right), 616 + and gap 375 617 - **`border`** โ€” per-side border widths and border color 376 - - **`cornerRadius`** โ€” per-corner radius values, producing rounded box-drawing characters 618 + - **`cornerRadius`** โ€” per-corner radius values, producing rounded box-drawing 619 + characters 377 620 - **`clip`** โ€” clip region configuration for scroll containers 378 - - **`floating`** โ€” floating-element configuration (offset, parent reference, attach points, z-index) 621 + - **`floating`** โ€” floating-element configuration (offset, parent reference, 622 + attach points, z-index) 379 623 - **`scroll`** โ€” scroll container configuration 380 624 381 - The `text()` constructor currently accepts: `color`, `fontSize`, `letterSpacing`, `lineHeight`, and attribute flags (`bold`, `italic`, `underline`, `strikethrough`). 625 + The `text()` constructor currently accepts: `color`, `fontSize`, 626 + `letterSpacing`, `lineHeight`, and attribute flags (`bold`, `italic`, 627 + `underline`, `strikethrough`). 382 628 383 - 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. 629 + These property groups represent the current implementation surface. New groups 630 + and fields have been added incrementally and more may follow. Alignment values 631 + are expected to transition from numeric to string-literal form. 384 632 385 - **Border width and layout interaction.** In the current underlying layout engine (Clay), border configuration does not affect layout computation. 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. This behavior has been explicitly discussed by the project; the current position is to document it rather than auto-compensate, but this decision may be revisited. 633 + **Border width and layout interaction.** In the underlying layout engine (Clay), 634 + border configuration does not affect layout computation. This is Clay's intended 635 + behavior. Borders are drawn as visual overlays within the element's bounding 636 + box. A bordered element with zero padding will have its borders drawn over its 637 + content. Callers must add padding equal to or greater than the border width to 638 + prevent overlap. 386 639 387 640 ### 12.3 Render return type 388 641 389 - The `render()` method currently returns a `RenderResult` object shaped as `{ output: Uint8Array, events: PointerEvent[] }`. 642 + The `render()` method currently returns a `RenderResult` object shaped as 643 + `{ output: Uint8Array, events: PointerEvent[] }`. 390 644 391 - The `output` field is the ANSI byte output specified normatively in Section 7.3 and Section 8.2. 645 + The `output` field is the ANSI byte output specified normatively in Section 7.3 646 + and Section 8.2. 392 647 393 - 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 (calling `setPointer(x, y, down)` before rendering, then reading events from the return value) was arrived at through iteration rather than upfront design. 648 + The `events` field contains pointer events (enter, leave, click) derived from 649 + the underlying layout engine's element hit-testing. This field was added during 650 + a pointer-events feature implementation. The pointer event model is functional 651 + 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. 394 655 395 - 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. 656 + The return type of `render()` has changed twice since the project's inception 657 + (string, then `Uint8Array`, then `RenderResult`). While the ANSI bytes 658 + commitment (Section 7.3) is stable, the wrapper shape around those bytes is not. 659 + Future versions may restructure the return type. 396 660 397 661 ### 12.4 Input parsing surface 398 662 399 - Clayterm currently provides terminal input parsing alongside the renderer. The input API was designed by the project lead and has clear design intent, but it 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, possible package separation). 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). 400 668 401 669 The current input surface includes: 402 670 403 - **`createInput(options?): Promise<Input>`** โ€” Creates an input parser instance. Options currently include `escLatency` (milliseconds to wait before resolving a lone ESC byte as the Escape key, default 25ms) and `terminfo` (a `Uint8Array` of raw terminfo binary, accepted but with C-side parsing not yet implemented). 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). 404 675 405 - **`input.scan(bytes?): 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. 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. 406 679 407 - **`ScanResult`** โ€” Currently shaped as `{ events: InputEvent[], pending?: { delay: number, deadline: number } }`. The `events` array contains parsed events. The `pending` field, when present, indicates that an ambiguous ESC byte is buffered and provides both a relative delay and an absolute deadline for the caller to schedule a rescan. 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. 408 685 409 - **`InputEvent` discriminated union** โ€” Currently discriminated on a `type` field with these variants: `CharEvent` (insertable character), `KeyEvent` (special/control key), `MouseEvent` (button press/release), `DragEvent` (motion with button held), `WheelEvent` (scroll tick), `ResizeEvent`. 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). 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). 410 694 411 - The input API is architecturally independent from the renderer (see INV-8). Whether it remains in the same package or becomes a separate module is an open question. 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. 412 698 413 699 ### 12.5 Pointer event model 414 700 415 - Clayterm currently supports pointer hit-testing via the underlying layout engine's element-identification mechanism. The current surface includes: 701 + Clayterm currently supports pointer hit-testing via the underlying layout 702 + engine's element-identification mechanism. The current surface includes: 416 703 417 - - `setPointer(x, y, down)` โ€” sets the pointer position and button state for the next render 418 - - Pointer events returned as part of `RenderResult.events`: `pointerenter`, `pointerleave`, `pointerclick` 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` 419 708 420 - 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. 709 + This surface is functional but should not be treated as stable contract. The 710 + calling convention was discovered through iteration, the event model has 711 + acknowledged gaps, and the approach may evolve. 421 712 422 713 ### 12.6 Validation and packing 423 714 424 - **`validate(ops)`** โ€” A function that checks a descriptor array for structural errors (unbalanced open/close pairs, invalid field types). Currently exported and used in tests. Its intended status as public API versus internal utility is not established. 715 + **`validate(ops)`** โ€” A public API function that checks a directive array for 716 + structural errors (unbalanced open/close pairs, invalid field types). Exported 717 + and used in tests. 425 718 426 - **`pack(ops, mem, offset)`** โ€” A function that serializes a descriptor array into the transfer encoding described in Section 12.1. Currently exported and used internally by `render()`. Its exposure as public API is incidental; it was not explicitly designated as caller-facing. 719 + **`pack(ops, mem, offset)`** โ€” An internal function that serializes a directive 720 + array into the transfer encoding described in Section 12.1. Currently exported 721 + but not public API; its exposure is incidental to the module structure. 427 722 428 723 --- 429 724 430 725 ## 13. Deferred / Future Areas 431 726 432 - *This section is non-normative. These topics are explicitly excluded from this specification. Their omission is intentional, not an oversight.* 727 + _This section is non-normative. These topics are explicitly excluded from this 728 + specification. Their omission is intentional, not an oversight._ 433 729 434 - **Section / region rendering mode.** Rendering into a portion of the normal screen buffer rather than the alternate screen. Partially prototyped but not landed. 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. 435 733 436 - **Scroll container API.** The underlying layout engine supports scroll containers. No TypeScript-side API exists for providing scroll state to the renderer. 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. 437 737 438 - **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. 439 - 440 - **Terminfo binary parsing.** The input API accepts a `terminfo` option, but C-side parsing is not implemented. 738 + **Terminfo binary parsing.** The input API accepts a `terminfo` option, but 739 + C-side parsing is not implemented. 441 740 442 - **CSI helper for terminal setup.** A helper for generating paired apply/rollback byte arrays for terminal mode configuration was discussed but not implemented. 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. 443 743 444 - **Browser-specific adapter.** The renderer's zero-IO architecture makes browser portability possible. No adapter exists. 744 + **Browser-specific adapter.** The renderer's zero-IO architecture makes browser 745 + portability possible. No adapter exists. 445 746 446 - **`betweenChildren` border support.** The underlying layout engine supports this. It is not exposed in the descriptor model. 747 + **`betweenChildren` border support.** The underlying layout engine supports 748 + this. It is not exposed in the directive model. 447 749 448 - **Whether input parsing should be a separate package.** Architecturally independent (INV-8) but currently co-located. The distribution decision is open. 750 + **Whether input parsing should be a separate package.** Architecturally 751 + independent (INV-8) but currently co-located. The distribution decision is open. 449 752 450 753 --- 451 754 452 755 ## 14. Implementation Notes 453 756 454 - *This section is non-normative. These notes describe current implementation details that aid understanding but do not define contract. They may change without notice.* 757 + _This section is non-normative. These notes describe current implementation 758 + details that aid understanding but do not define contract. They may change 759 + without notice._ 455 760 456 - **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. 761 + **WASM module structure.** The renderer is implemented in C and compiled to 762 + WebAssembly as a single module. The module contains both rendering and 763 + input-parsing functionality; they share a binary but maintain independent state. 457 764 458 - **WASM loading.** The current implementation loads the WASM binary relative to the module's location, compiles it once, and instantiates it per Term or Input with fresh memory. The loading mechanism has changed and may change again. 765 + **WASM loading.** The WASM binary is inlined as a base64-encoded string in a 766 + generated module and instantiated per Term or Input with fresh memory. 459 767 460 - **WASM co-location.** The WASM binary file is expected to be co-located with the JavaScript module files. Both JSR and npm package builds include the artifact. 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. 461 770 462 - **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. 771 + **Memory layout.** WASM linear memory is initialized with 256 pages (16MB). The 772 + renderer state struct and the transfer buffer are allocated in WASM linear 773 + memory. The specific layout is an implementation detail. 463 774 464 - **Output buffer lifetime.** The ANSI byte output resides in WASM linear memory. The `Uint8Array` returned by `render()` is a view over this memory. The output 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 SHOULD copy it. 775 + **Layout engine.** The underlying layout engine is Clay, included as a 776 + dependency. Clay provides flexbox-like layout computation with support for 777 + fixed, grow, and fit sizing; padding; alignment; direction; gap; floating 778 + elements; clip regions; and scroll containers. 465 779 466 - **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. 780 + **Text measurement.** Text width measurement uses `wcwidth`-based character 781 + width computation, supporting ASCII, CJK wide characters, and other Unicode 782 + codepoints. 467 783 468 - **Text measurement.** Text width measurement uses `wcwidth`-based character width computation, supporting ASCII, CJK wide characters, and other Unicode codepoints. 469 - 470 - **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. 784 + **Cell representation.** Each cell in the buffer stores a Unicode codepoint, a 785 + foreground color (packed ARGB with attribute flags in the high byte), and a 786 + background color. 471 787 472 - **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. 788 + **Border junction resolution.** When bordered elements share edges, the renderer 789 + accumulates per-cell direction bitmasks and resolves them to correct box-drawing 790 + junction glyphs in a post-render pass. 473 791 474 792 --- 475 793 ··· 477 795 478 796 ### Why the rendering core is specified more aggressively than other surfaces 479 797 480 - The rendering architecture โ€” `createTerm`, `render(ops)`, the descriptor 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 descriptor 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. 798 + The rendering architecture โ€” `createTerm`, `render(ops)`, the directive 799 + constructors, the bytes-output commitment, and the core invariants โ€” was 800 + designed at the project's inception and has been stable since. It has survived 801 + the addition of pointer events, border junction resolution, and the crankterm 802 + integration without revision to its fundamental shapes. Its key abstractions 803 + (flat directive arrays, single render transaction, ANSI byte output) were chosen 804 + over explicitly rejected alternatives (per-element FFI, protobuf, builder 805 + pattern, string output). This level of stability and intentionality justifies 806 + normative specification. 481 807 482 - The input API arrived later, has been through significantly more design churn (rejected first draft, iterative event type splits, naming changes, ongoing Kitty progressive enhancement design), and faces known upcoming forces that will reshape it. It has clear design ownership from the project lead, which distinguishes it from purely implementation-driven features like the pointer event model, but design ownership is not the same as contract readiness. 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. 483 814 484 - 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. 815 + The pointer event model and render return wrapper are the least settled of the 816 + currently shipping features. Both were introduced during feature implementation 817 + rather than designed as part of the core architecture. The return type of 818 + `render()` has changed twice. The pointer calling convention was discovered 819 + through iteration. These are working and useful, but they carry the lowest 820 + confidence of any feature currently in the codebase. 485 821 486 822 ### How to interpret "currently exported" 487 823 488 - 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." 824 + Several symbols are currently accessible from Clayterm's module exports โ€” 825 + including `pack()`, `validate()`, and numerous input-related types โ€” without 826 + clear evidence that they were intended as stable public contract. Being exported 827 + may mean "needed by internal modules" or "not yet audited for public/internal 828 + boundary." 489 829 490 - 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. 830 + This specification does not treat the export list as a contract boundary. 831 + Instead, it uses stability over time, design ownership, survival of corrections, 832 + and absence of known reshaping forces as the criteria for normative inclusion. 491 833 492 834 --- 493 835 494 836 ## Open Decisions Intentionally Left Out of This Spec 495 837 496 - The following decisions are open. This specification omits them deliberately. Future readers should not interpret their absence as oversight or implicit resolution. 497 - 498 - 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. 838 + The following decisions are open. This specification omits them deliberately. 839 + Future readers should not interpret their absence as oversight or implicit 840 + resolution. 499 841 500 - 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. 842 + 1. **What is the normative return type of `render()`?** This specification 843 + commits to ANSI bytes as `Uint8Array` but does not lock down the wrapper 844 + type. The current `RenderResult` shape may evolve. 501 845 502 - 3. **Is the input API part of the Clayterm specification?** This specification describes it in Section 12.4 but does not specify it normatively. The input API may become a separate package or specification. 846 + 2. **Is pointer event detection part of the rendering contract?** The current 847 + implementation returns pointer events from `render()`. This specification 848 + does not include pointer events in the normative core. Whether pointer 849 + detection is intrinsic to the renderer or should be a separate concern is 850 + unresolved. 503 851 504 - 4. **Are `pack()` and `validate()` public API?** Both are currently exported. Neither is specified normatively here. 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. 505 855 506 - 5. **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. 856 + 4. **Is `pack()` public API?** `pack()` is currently exported but is an internal 857 + implementation detail, not public API. `validate()` is public API. 507 858 508 - 6. **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. 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. 509 862 510 - 7. **Should the rendering and input concerns be distributed as separate packages?** They are architecturally independent (INV-8) but currently co-located. 863 + 6. **How should border widths interact with layout?** The current behavior 864 + (borders do not affect layout) is inherited from the underlying layout 865 + engine. The project has questioned whether this is the right design. This 866 + specification describes the current behavior in Section 12.2 without 867 + committing to it. 511 868 512 - 8. **What is the specification for section / region rendering mode?** Partially prototyped but not ready for specification. 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. 513 872 514 - 9. **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. 873 + 8. **What are the specific transfer encoding details?** The encoding structure 874 + is described in Section 12.1 as current implementation surface. Locking down 875 + opcode values would constrain future extensions unnecessarily. 515 876 516 - 10. **What is the complete set of descriptor 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. 877 + 9. **What is the complete set of directive properties?** The property groups 878 + available in `open()` and `text()` are described in Section 12.2 as current 879 + implementation surface. They have been extended incrementally and will 880 + continue to grow. 517 881 518 - 11. **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. 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.