hermes-agent/ui-tui/src
Brooklyn Nicholson 3b4dd68326 fix(tui): align composer cursorLayout with wrap-ansi to kill multiline cursor drift
The composer's `cursorLayout` (in `ui-tui/src/lib/inputMetrics.ts`) used a
hand-rolled word-wrap algorithm to decide where `useDeclaredCursor`
should park the hardware cursor. But Ink's `<Text wrap="wrap">` renders
the same text via `wrap-ansi`. The two algorithms disagreed on common
real-world inputs — `"branch investigate"` at cols=20, `"hello world"`
at cols=8, exact-fill strings like `"abcdefgh"` at cols=8 — so the
hardware cursor parked several cells past where Ink actually rendered
the last character. Users saw a multi-cell blank gap between their
last-typed letter and the cursor block, especially on narrow terminals
(the Cursor IDE built-in terminal was the worst offender).

Three previous PRs (#26717, #25860, #22197) chased fast-echo
displayCursor/cursorDeclaration drift and in-band-vs-native cursor
heuristics. None of them touched the underlying wrap-algorithm
mismatch, which is why the bug kept resurfacing.

Fix: source cursorLayout's line breaks from wrap-ansi directly. Walk
its emitted string char-by-char, tracking original-string offsets, push
a VisualLine at each '\n'. Also drop the buggy `column >= w` overflow
rule in cursorLayout — that's what pushed exact-fill text onto a
phantom next row.

canFastBackspaceShape now detects the wrap boundary in BOTH coordinate
conventions (column === 0 OR column >= columns), since exact-fill now
reports as (0, columns) instead of the previous (1, 0). The physical
state is identical — the terminal auto-wraps at column N either way —
but the layout function reports the position more honestly.

Tests:
- ui-tui/src/__tests__/textInputWrap.test.ts: 3 tests that pinned the
  BUGGY behavior were updated to assert wrap-ansi parity (the real
  invariant). Added a typing-prefix invariant: cursorLayout must agree
  with wrap-ansi at every character of a long input.
- ui-tui/src/__tests__/cursorDriftRegression.test.ts: new file. Walks
  the user-reported bug message char-by-char at 7 widths and asserts
  agreement with wrap-ansi at every prefix.

Verification:
- 791/791 vitest tests pass.
- 84/84 tui-gateway pytest tests pass via scripts/run_tests.sh.
- PTY repro (typing into a real `hermes --tui` PTY at cols=50/55/60):
  cursor lands exactly 1 cell past the last typed char in every case
  the bug previously drifted.
2026-05-17 11:10:06 -05:00
..
__tests__ fix(tui): align composer cursorLayout with wrap-ansi to kill multiline cursor drift 2026-05-17 11:10:06 -05:00
app fix(tui): allow transcript scroll + Esc during approval/clarify/confirm prompts (#26414) 2026-05-15 21:59:28 -05:00
components fix(tui): align composer cursorLayout with wrap-ansi to kill multiline cursor drift 2026-05-17 11:10:06 -05:00
config fix(tui): close slash parity gaps with CLI (#20339) 2026-05-05 15:42:39 -05:00
content fix(tui): copilot review on #16707 — naming, label consistency, esc priority 2026-04-27 15:37:54 -05:00
domain fix(tui): stabilize sticky prompt tracking 2026-04-28 22:10:40 -05:00
hooks fix(tui): refresh virtual offsets after row resize (#20898) 2026-05-06 13:54:46 -07:00
lib fix(tui): align composer cursorLayout with wrap-ansi to kill multiline cursor drift 2026-05-17 11:10:06 -05:00
protocol refactor(tui): /clean pass across ui-tui — 49 files, −217 LOC 2026-04-16 22:32:53 -05:00
types fix(tui): keep Ink displayCursor in sync with fast-echo writes so cursor stops drifting (#26717) 2026-05-16 00:28:12 -05:00
app.tsx fix(tui): apply ui-tui fix pass and restore type-check 2026-04-25 14:08:54 -05:00
banner.ts fix(tui): restore macOS copy behavior and theme polish (#17131) 2026-04-28 18:47:14 -05:00
entry.tsx tui: make URLs clickable + hover-highlight in any terminal (#25071) 2026-05-13 13:52:10 -07:00
gatewayClient.ts feat(tui): support attaching to an existing gateway (#21978) 2026-05-08 12:12:38 -07:00
gatewayTypes.ts fix(tui): handle timeout/error subagent statuses in /agents (#26687) 2026-05-15 20:19:02 -05:00
theme.ts fix(tui): honor skin highlight colors (#20895) 2026-05-06 14:01:56 -07:00
types.ts fix(tui): handle timeout/error subagent statuses in /agents (#26687) 2026-05-15 20:19:02 -05:00