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

✨ feat(term): resolve caret cell on empty text content

Substitute a single-space slice for empty text nodes carrying a caret
declaration so the layout engine emits one cell of geometry at the text
element's origin. Replaces the v1-limitation test with one asserting
the spec's outcome (CUP to row 1, col 1 + DECTCEM-show).

Charles Lowell (Jul 8, 2026, 2:16 PM +0300) 8d47badc 895565d4

+14 -8
+10
src/clayterm.c
··· 776 776 char *str_chars = (char *)&buf[i]; 777 777 i += str_words; 778 778 779 + /* A caret on empty content is a rendering commitment the layout 780 + * engine cannot satisfy on its own — zero cells give locate_caret 781 + * nothing to attach the cursor to. Substitute a single space so 782 + * the caret lands at the text element's origin, per the spec's 783 + * "as if the content were a single space" outcome. */ 784 + if (caret != 0xFFFFFFFF && str_len == 0) { 785 + str_chars = (char *)" "; 786 + str_len = 1; 787 + } 788 + 779 789 /* Record the FIRST caret declaration per frame for the 780 790 * single-hardware-cursor contract; later declarations are 781 791 * intentionally ignored (multi-cursor is unspecified). */
+4 -8
test/term.test.ts
··· 434 434 }); 435 435 436 436 it("shows the cursor at the text origin when content is empty and caret is 0", () => { 437 - // v1 known limitation: Clay does not emit a text render command for an 438 - // empty string, so locate_caret never finds a matching slice and suppresses 439 - // cursor visibility. The proper fix requires looking up the element's 440 - // bounding box separately (e.g. via get_element_bounds) rather than 441 - // scanning text render commands. Until that is implemented, no cursor 442 - // appears when the caret-bearing text node is empty. 437 + // Declaring a `caret` on empty content is a rendering commitment: the 438 + // renderer places the cursor at the cell the caret would occupy if the 439 + // content were a single space. Origin of the text element is (row 1, col 1). 443 440 let ansi = decode( 444 441 term.render([ 445 442 open("root", { ··· 449 446 close(), 450 447 ]).output, 451 448 ); 452 - expect(ansi).not.toContain("\x1b[?25h"); 453 - expect(ansi).not.toContain("\x1b[?25l"); 449 + expect(ansi).toMatch(/\x1b\[1;1H\x1b\[\?25h$/); 454 450 }); 455 451 456 452 it("hides the cursor when transitioning from caret-present to caret-absent", () => {