What happened
When a queued message's first non-empty line exceeds 200 characters, its pending-bar preview contains an embedded newline inside what must be a single terminal row, and the TUI frame misaligns until the queue drains.
The chain:
limitText (packages/cli/src/pi-transcript-format.ts:183-186) returns `${text.slice(0, maxChars)}\n... ${n} chars truncated` — note the embedded \n
firstLinePreview (packages/cli/src/pi-transcript.ts:1506-1513) returns limitText(line, 200) raw, despite its own contract: "trimmed for a one-line preview"
- the result is placed into a single pending-bar row via
fitLine (pi-transcript.ts:1493, :1497)
Each element returned by a layout component must be exactly one terminal row; the viewport math counts pending-bar lines for its frame accounting. fitLine cannot catch this because pi-tui's visibleWidth treats control characters as zero-width, and pi-tui writes each array element between explicit \r\n separators — so the embedded \n physically shifts every subsequent row down one line while the diff accounting still believes one row was written. The corruption persists frame-to-frame until the queued message is consumed.
Sibling call sites already defend against this: pi-transcript-tools.ts:98 wraps the same limitText output in collapseToSingleLine(...). firstLinePreview is the path that forgot.
How to reproduce
- Open
maka in a terminal of roughly ≥210 columns (narrower terminals truncate before the newline lands)
- During a turn, steer with a single-line message longer than 200 characters
- On the next render, the editor/status block drops one physical row; misalignment persists until the queue drains
Suggested fix
return collapseToSingleLine(limitText(line, 200)); in firstLinePreview (or truncate by display width with '…' here instead of limitText's multiline suffix). Defense-in-depth: strip \r?\n from composed lines in the layout component's render.
What happened
When a queued message's first non-empty line exceeds 200 characters, its pending-bar preview contains an embedded newline inside what must be a single terminal row, and the TUI frame misaligns until the queue drains.
The chain:
limitText(packages/cli/src/pi-transcript-format.ts:183-186) returns`${text.slice(0, maxChars)}\n... ${n} chars truncated`— note the embedded\nfirstLinePreview(packages/cli/src/pi-transcript.ts:1506-1513) returnslimitText(line, 200)raw, despite its own contract: "trimmed for a one-line preview"fitLine(pi-transcript.ts:1493,:1497)Each element returned by a layout component must be exactly one terminal row; the viewport math counts pending-bar lines for its frame accounting.
fitLinecannot catch this because pi-tui'svisibleWidthtreats control characters as zero-width, and pi-tui writes each array element between explicit\r\nseparators — so the embedded\nphysically shifts every subsequent row down one line while the diff accounting still believes one row was written. The corruption persists frame-to-frame until the queued message is consumed.Sibling call sites already defend against this:
pi-transcript-tools.ts:98wraps the samelimitTextoutput incollapseToSingleLine(...).firstLinePreviewis the path that forgot.How to reproduce
makain a terminal of roughly ≥210 columns (narrower terminals truncate before the newline lands)Suggested fix
return collapseToSingleLine(limitText(line, 200));infirstLinePreview(or truncate by display width with'…'here instead oflimitText's multiline suffix). Defense-in-depth: strip\r?\nfrom composed lines in the layout component's render.