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

✨ hardware cursor positioning via text() caret (#103) master

* spec(renderer): add hardware cursor visibility and positioning * 📝 spec(renderer): note §7.6 carve-out in §11.2 output description * feat(term): emit hardware cursor at text() caret offset * 🧪 test(term): cursor hides on caret-present → caret-absent transition * 🧪 test(term): no cursor bytes when no caret ever declared * 🧪 test(term): caret at end-of-content positions one cell past last char * 🧪 test(term): caret lands on correct line after text wrapping * 🧪 test(term): caret accounts for wide-character cell widths * 🧪 test(term): first caret declaration wins when multiple present * ✨ example(term): interactive text input demonstrating caret * 📝 AGENTS.md: reflect §7.6 cursor management exception * 🧪 test(term): document v1 limitation for caret on empty text * 🎨 example(term): simplify layout and fall back to space for empty input * linting & formatting * 📝 spec(renderer): specify caret behavior on empty text content Adds a normative paragraph to §7.6 stating that a `caret` declaration on empty content places the cursor at the text element's origin — the cell where the first code point of `content` would be drawn if it were a single space. Frames the empty-content case as a rendering commitment the renderer must honor, without prescribing mechanism. * ✨ 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). * 🔥 example(term): drop empty-value workaround now handled by renderer The `value || " "` fallback previously worked around the renderer's inability to attach a caret to an empty text node. That case is now handled directly by the renderer, so the example can pass the raw value through and let the caret resolve at the input box's origin. * 📝 spec(renderer): specify caret behavior at wrap boundaries * 🐛 fix(term): resolve caret cell during the render walk Precompute the caret's byte offset within its text node at OP_TEXT decode time, then place the caret cell as a side effect of render_text's existing walk: at the top of each iteration, if the current pointer matches the target byte offset, record the cell. Two edge cases fall out of the same mechanism. A slice whose first byte is already past the target (Clay dropped whitespace at the wrap seam) snaps the caret to the slice's origin — the start of the next wrapped line — rather than orphaning it off the end of the previous line. And the trailing cell of the last walked caret slice is remembered as the end-of-content fallback for offset == content-length. Deletes locate_caret, which walked slices with a code-point accumulator that diverged from the caller's original offset whenever the layout engine's wrap pass either dropped or retained-off-screen the seam whitespace. Adds tests covering both wrap-boundary cases and tightens the previously-lax "correct wrapped line" assertion to an exact cell. * make tests visual


+645 -13
7 changed files