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

linting & formatting

Charles Lowell (Jul 2, 2026, 6:29 PM +0300) 9d238339 ddd8e9df

+52 -55
+3 -3
AGENTS.md
··· 31 31 - The renderer MUST NOT perform IO. It produces bytes; the caller writes them. 32 32 33 33 - The renderer MUST NOT manage terminal state (alternate buffer, mouse 34 - reporting, keyboard protocol modes). Cursor visibility and positioning 35 - are renderer-managed only when a `caret` declaration is present on 36 - a `text()` directive (see renderer-spec.md §7.6). 34 + reporting, keyboard protocol modes). Cursor visibility and positioning are 35 + renderer-managed only when a `caret` declaration is present on a `text()` 36 + directive (see renderer-spec.md §7.6). 37 37 38 38 - Each frame is a complete snapshot. The renderer carries no UI tree state 39 39 between frames — only cell buffers for diffing.
+39 -45
specs/renderer-spec.md
··· 337 337 ### 7.6 Hardware cursor visibility and positioning 338 338 339 339 A `text()` directive MAY declare a `caret` property whose value is a 340 - non-negative integer code-point offset into the directive's `content`. 341 - `0` means "before the first code point"; `[...content].length` means 342 - "after the last." Offsets in between sit immediately before the cell 343 - where the corresponding code point would render. 340 + non-negative integer code-point offset into the directive's `content`. `0` means 341 + "before the first code point"; `[...content].length` means "after the last." 342 + Offsets in between sit immediately before the cell where the corresponding code 343 + point would render. 344 344 345 - The cell where a declared caret sits is the cell at which the code 346 - point at the caret's offset would be drawn given the layout engine's 347 - text wrapping. For an offset `N`: 345 + The cell where a declared caret sits is the cell at which the code point at the 346 + caret's offset would be drawn given the layout engine's text wrapping. For an 347 + offset `N`: 348 348 349 - - If `N < [...content].length`, the caret's cell is the display 350 - position of the `N`-th code point (zero-indexed) within the rendered 351 - text. 352 - - If `N == [...content].length`, the caret's cell is one display 353 - position past the last rendered code point: on the same wrapped line 354 - if there is room, or at the start of the next line if the layout 355 - wraps at the end. 356 - - If `N > [...content].length` or `N < 0`, behavior is unspecified; 357 - callers must keep offsets within bounds. 349 + - If `N < [...content].length`, the caret's cell is the display position of the 350 + `N`-th code point (zero-indexed) within the rendered text. 351 + - If `N == [...content].length`, the caret's cell is one display position past 352 + the last rendered code point: on the same wrapped line if there is room, or at 353 + the start of the next line if the layout wraps at the end. 354 + - If `N > [...content].length` or `N < 0`, behavior is unspecified; callers must 355 + keep offsets within bounds. 358 356 359 - Display position accounts for code points wider than one cell (CJK, 360 - fullwidth forms, some emoji): such code points occupy two cells, and 361 - the caret sits at the first cell of the pair. Cell widths are 362 - determined by the same measurement the renderer uses to lay out the 363 - text itself. 357 + Display position accounts for code points wider than one cell (CJK, fullwidth 358 + forms, some emoji): such code points occupy two cells, and the caret sits at the 359 + first cell of the pair. Cell widths are determined by the same measurement the 360 + renderer uses to lay out the text itself. 364 361 365 - The renderer manages the terminal's hardware cursor based on the 366 - presence of `caret:` declarations: 362 + The renderer manages the terminal's hardware cursor based on the presence of 363 + `caret:` declarations: 367 364 368 - - When the current frame contains one or more `caret:` declarations, 369 - the rendered `output` MUST, when written to the terminal, leave the 370 - hardware cursor visible at the cell where the first declared caret 371 - (in directive order) sits. 365 + - When the current frame contains one or more `caret:` declarations, the 366 + rendered `output` MUST, when written to the terminal, leave the hardware 367 + cursor visible at the cell where the first declared caret (in directive order) 368 + sits. 372 369 373 370 - When the current frame contains no `caret:` declarations: 374 - - If the previous frame contained one, the rendered `output` MUST 375 - leave the hardware cursor hidden. 376 - - Otherwise, the rendered `output` MUST NOT include 377 - cursor-positioning or cursor-visibility bytes; the caller's 378 - cursor state is preserved. 371 + - If the previous frame contained one, the rendered `output` MUST leave the 372 + hardware cursor hidden. 373 + - Otherwise, the rendered `output` MUST NOT include cursor-positioning or 374 + cursor-visibility bytes; the caller's cursor state is preserved. 379 375 380 376 The byte-level path the renderer takes to satisfy these outcomes is 381 - implementation-defined. The renderer MAY hide the cursor before cell 382 - writes to prevent flicker, MAY omit such a hide for in-place edits 383 - where flicker is acceptable, MAY emit only a CUP when the caret has 384 - moved within an already-visible state, MAY use whatever escape-sequence 385 - path it judges appropriate. Only the post-frame cursor state above is 386 - normative. 377 + implementation-defined. The renderer MAY hide the cursor before cell writes to 378 + prevent flicker, MAY omit such a hide for in-place edits where flicker is 379 + acceptable, MAY emit only a CUP when the caret has moved within an 380 + already-visible state, MAY use whatever escape-sequence path it judges 381 + appropriate. Only the post-frame cursor state above is normative. 387 382 388 - If more than one `caret:` declaration is present in a frame, the 389 - renderer SHOULD use the first in directive order for the hardware 390 - cursor. Behavior for additional declarations is intentionally 391 - unspecified, leaving room for a future multi-cursor extension without 392 - breaking this contract. 383 + If more than one `caret:` declaration is present in a frame, the renderer SHOULD 384 + use the first in directive order for the hardware cursor. Behavior for 385 + additional declarations is intentionally unspecified, leaving room for a future 386 + multi-cursor extension without breaking this contract. 393 387 394 - This responsibility is limited to the hardware cursor's position and 395 - visibility. Cursor shape and blink rate remain caller-managed. 388 + This responsibility is limited to the hardware cursor's position and visibility. 389 + Cursor shape and blink rate remain caller-managed. 396 390 397 391 --- 398 392
+8 -6
src/clayterm.c
··· 88 88 * first text node carrying a caret declaration per frame, then locates 89 89 * the corresponding cell after Clay layout. had_caret_last_frame is the 90 90 * only cross-frame bit retained. */ 91 - const char *caret_text_chars; /* start of caret-bearing text node's bytes, or NULL */ 92 - int caret_text_length; /* byte length of that text node */ 93 - uint32_t caret_offset; /* code-point offset within that text node */ 94 - int caret_x, caret_y; /* resolved cell (column, row), valid only when caret_text_chars != NULL */ 95 - int has_caret; /* 1 when the current frame placed a caret */ 96 - int had_caret_last_frame; /* 1 when the previous frame placed a caret */ 91 + const char 92 + *caret_text_chars; /* start of caret-bearing text node's bytes, or NULL */ 93 + int caret_text_length; /* byte length of that text node */ 94 + uint32_t caret_offset; /* code-point offset within that text node */ 95 + int caret_x, caret_y; /* resolved cell (column, row), valid only when 96 + caret_text_chars != NULL */ 97 + int has_caret; /* 1 when the current frame placed a caret */ 98 + int had_caret_last_frame; /* 1 when the previous frame placed a caret */ 97 99 }; 98 100 99 101 /* Memory layout inside the arena provided by the host:
+2 -1
test/term.test.ts
··· 1 + // deno-lint-ignore-file no-control-regex 1 2 import { beforeEach, describe, expect, it } from "./suite.ts"; 2 3 import { createTerm, type Term } from "../term.ts"; 3 4 import { ··· 130 131 let out = decode( 131 132 term.render(box("hello world"), { mode: "line" }).output, 132 133 ); 133 - // deno-lint-ignore no-control-regex 134 + 134 135 expect(out).not.toMatch(/\x1b\[\d+;\d+H/); 135 136 expect(out.split("\n").length).toBe(5); 136 137 expect(trim(print(out, 20, 5))).toEqual(`