Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/substrate/tasks/message-bubble-overflow.md
Fimeg e480809c70 docs: rescue the agent-substrate tree out of a gitignored directory
219 files, 2.0 MB, untracked in souveraine/docs and existing nowhere else.
The volume is at 100% with no snapshots.
2026-07-26 12:11:50 -04:00

4.1 KiB

task_id title status assignee priority phase
ui-bubble-overflow-001 Fix Message Bubble Overflow — Content Breaking Out of Bubble Borders pending medium 3.0

Task: Fix Message Bubble Overflow

Objective

When agent messages contain long lines (code blocks, URLs, or wide text), the rendered markdown lines can exceed the bubble's computed width, breaking the box-drawing characters (│ │ border) and spilling content into the chat margin.

Current State

The bubble rendering in src/ui/chat.rs has a tension between two approaches:

  1. bubble() (plain text, line 1044) — Uses wrap_words() to wrap body text to max_inner, then sets bubble width based on the widest wrapped line. This works correctly because wrapped lines are guaranteed to fit.

  2. bubble_rendered() (markdown, line 1107) — Takes pre-rendered Vec<Line<'static>> from markdown::render() and places them inside the bubble. The bubble width is set to widest_line_width.min(max_inner). But: if widest_line_width > max_inner, the lines are clipped to max_inner while the bubble border uses max_inner + 4 — so the content overflows past the │ │ borders.

The root cause is in bubble_rendered() at line 1121:

let widest = body_lines.iter().map(|l| l.width()).max().unwrap_or(0).max(...);
let inner = widest.min(max_inner);  // clips widest to max_inner
let outer = inner + 4;

When widest > max_inner, inner = max_inner but the rendered body_lines still contain lines wider than max_inner. The border is drawn at inner + 4 while content extends past it.

Additional Cases

  • Surfacing bubbles (line 987): These also use bubble() which handles wrapping, but surfacing content could still overflow if it contains code.
  • Tool cards (line 1209): These use bubble_rendered() with the same overflow risk.
  • User bubbles (line 959): Use bubble() with word wrapping — less risk but long URLs without spaces can still break out.

Implementation Options

Option A: Constrain markdown rendering width

Pass max_inner to markdown::render() so it wraps lines before they reach the bubble. The markdown renderer at src/ui/markdown.rs:74 currently has no width parameter — it splits on \n only. Adding a max_width parameter lets it wrap inline code and long text.

Pros: Fixes the root cause. All callers benefit. Cons: Requires changes to markdown::render() API. More work.

Option B: Post-hoc wrapping in bubble_rendered

After rendering markdown, re-wrap any lines that exceed max_inner using the same wrap_words() logic from bubble(). Insert the re-wrapped lines and skip originals that exceed width.

Pros: Minimal changes, contained in bubble_rendered(). Cons: Code-style markers (bold, code spans) on long lines would need wrapping aware of Ratatui spans.

Option C: Auto-expand bubble width

Don't cap inner at max_inner. Let the bubble grow to the widest rendered line. Center-align or use scroll when the terminal is too narrow. This is how desktop chat apps work — the bubble grows, overflow is handled by viewport scroll.

Pros: No wrapping needed. Content is never truncated. Cons: Very long lines (1000+ char) would make the bubble span the entire terminal width. Feels wrong for a TUI.

Recommendation: Option A — constrain at the markdown renderer. Cleanest architecture, fixes all callers.

Investigation Needed

  1. Look at src/ui/markdown.rs:74 — understand the current renderer API and where a max_width param would fit.
  2. Look at the four callers of bubble_rendered() — which are most affected by overflow?
  3. Check if the same issue exists in bubble() for user messages with long URLs.

Files to Modify

File Change
src/ui/markdown.rs Add max_width parameter to render(), implement word-wrapping for code blocks and long lines
src/ui/chat.rs Pass max_inner to markdown::render() calls in bubble_rendered() and tool card rendering
src/ui/chat.rsbubble_rendered() Lines 1121-1123: remove the widest.min(max_inner) truncation since wrapping is now upstream