Skip to content

TUI: long tool-result content bleeds into right sidebar gutter (visual corruption) #36

Description

@Hmbown

Symptom

When a long tool result (e.g. a todo_write echoing back its full JSON payload) renders in the transcript, character fragments of the content (heli, hel, h`li, ent, etc.) appear as a vertical column of garbage inside the right sidebar, in the gutter regions between the Plan / Todos / Tasks / Agents widget borders.

Where a sidebar widget is drawn, the leak is hidden (the widget overdraws). Where there is no widget on a row (the seams between widgets, or rows where a widget has scrolled past its content), the fragments leak through.

Reproduce: open the TUI, run a turn that emits a long todo_write tool result whose content field is something like a long string (e.g. "hello world hello world ..."). The raw result: line is then long enough that wrapped continuations end up rendering past the chat pane's right edge.

Suspect

Layout itself is correct — crates/tui/src/tui/ui.rs:2761-2768 splits chunks[1] horizontally into chat_area + sidebar_area, and ChatWidget::render (crates/tui/src/tui/widgets/mod.rs:136-160) renders a Paragraph to self.content_area. ratatui clips a Paragraph to the area it's given, so the bleed should not be possible from that path alone.

That leaves three plausible paths:

  1. Direct buf.set_string writes outside content_area. The streaming / live-update path may write cells using absolute coordinates without re-clipping to chat_area. Worth auditing crates/tui/src/tui/streaming.rs, crates/tui/src/tui/transcript.rs, and crates/tui/src/tui/markdown_render.rs for any cell writes that take a Buffer and an x derived from something other than area.x.
  2. Transcript cache wrapped to the wrong width. app.transcript_cache.ensure(..., content_area.width.max(1), ...) (mod.rs:65) wraps lines to the chat width — but a single wide tool-result chunk that's pre-wrapped by the renderer (e.g. JSON pretty-printed at a fixed width) might not get re-wrapped. The Paragraph would then truncate, but if anything else writes those overflowed graphemes (a hover/selection layer, scrollbar, status overlay), they'd land in the sidebar.
  3. Stale buffer cells from a prior frame. Less likely under normal ratatui usage (full redraw each frame), but worth confirming the frame is being fully cleared and that no app.view_stack overlay paints partial rows that get torn when chat content shrinks.

Repro environment

  • deepseek-tui 0.5.0
  • deepseek-v4-pro, YOLO mode, max reasoning effort
  • Terminal width ~165 columns

Screenshot

(attached in the issue thread that filed this — long line result: Todo list updated (2 items, 0% complete){ "items": [ { "id": 1, "content": " hel... followed by a vertical column of heli / hel / h`li fragments inside the sidebar gutter)

Acceptance

  • A long single-line tool result no longer produces character fragments inside the sidebar area at any terminal width.
  • A test that renders ChatWidget with a 5,000-char single-line tool result + a sidebar split asserts the sidebar's Buffer cells are untouched outside the widget regions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions