fix: read a row-addressed repaint as lines in the preview

Measured against a live Claude pane: an Ink TUI paints by ROW and emits
almost no newlines, so dropping cursor-position sequences collapsed a whole
screen into one unreadable line, and a tail cut mid-sequence printed the
remains of it (";1H") as text. Now a jump to column 1 starts a display line,
a jump inside a row moves the write position (capped, since a stream may
address a column no terminal has), and a severed CSI head is dropped before
parsing.

The preview is readable against a real session as a result: tool calls, the
working line and the composer all land where they belong.

Also drop the repeated session name from a search row, whose snippet opens
with the name the row already shows in its first column.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-08-22 14:13:58 +02:00
parent b51abe2c27
commit ef812236b0
5 changed files with 113 additions and 7 deletions
+3 -1
View File
@@ -332,7 +332,7 @@ describe('search results', () => {
sessionId: 'live-1',
sessionName: 'w1-alpha',
timestamp: NOW,
snippet: '/tmp/alpha',
snippet: 'w1-alpha — /tmp/alpha',
exactMatch: true,
jumpTo: { kind: 'session', sessionId: 'live-1' },
},
@@ -368,6 +368,8 @@ describe('search results', () => {
expect(entries.map((entry) => entry.kind)).toEqual(['header', 'result', 'result', 'header', 'result']);
expect(entries[0].text).toBe('SESSIONS');
expect(entries[1]).toMatchObject({ text: 'w1-alpha', sessionId: 'live-1', live: true });
// The snippet opens with the session name, which the row already shows.
expect(entries[1].detail).toBe('/tmp/alpha');
// A session that is not on the list cannot be selected into.
expect(entries[2]).toMatchObject({ text: 'w9-old', live: false });
expect(entries[4]).toMatchObject({ text: 'docs/notes.md', live: false });