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 -2
View File
@@ -50,7 +50,7 @@ import chalk from 'chalk';
import { palette, table, tint, type Tone } from '../cli-style.js';
import { CODEMAN_INSTANCE, resolveTmuxSocketName } from '../config/instance.js';
import { getErrorMessage } from '../types/api.js';
import { toDisplayLines } from './tui-ansi.js';
import { dropSeveredEscape, toDisplayLines } from './tui-ansi.js';
import { approvalAnswerForKey, newApprovalIds } from './tui-approvals.js';
import { composerScroll, composerStep, composerText, createComposer, type TuiComposerState } from './tui-composer.js';
import { formatAwayDigest } from './tui-digest.js';
@@ -895,7 +895,8 @@ class TuiApp {
try {
const raw = await this.client.fetchTerminalTail(sessionId, PREVIEW_TAIL_BYTES);
if (this.previewSessionId !== sessionId) return;
this.applyPreview({ sessionId, lines: toDisplayLines(raw).slice(-PREVIEW_MAX_LINES) });
const lines = toDisplayLines(dropSeveredEscape(raw)).slice(-PREVIEW_MAX_LINES);
this.applyPreview({ sessionId, lines });
} catch {
// A tail that cannot be read is a pane-level fact, not a connection one:
// the list stays exactly as it is and only this pane says so.