Skip to content

bug(tool-card): expanded args render as ~1.5 unreadable lines — newlines collapsed, word-break:break-all destroys code #1484

@nesquena-hermes

Description

@nesquena-hermes

Summary

When a tool card (execute_code, terminal, write_file, etc.) is unfolded, the arguments are unreadable — the user sees roughly a line and a half of wrapped, indentation-stripped, word-broken garbage instead of the actual code. Reported by @cygnus on Discord (May 02 2026):

"wish list item — when things like execute-code are folded, and I unfold them, I do it because I want to see the code, but I can only see a line and a half of it"

Same bug surfaced a second time the same morning when Cygnus tried to debug a recurring macOS Terminal permission prompt and couldn't read the executing command:

"I'm trying to figure out what code keeps on giving me this several times in a row, but I can't expand and see the code it's running when it does"

Both reports are the same root cause.

Root cause (verified)

Bug 1 — .tool-arg-val has no white-space: pre-wrap

static/ui.js:3974-3977 renders args like this:

${tc.args&&Object.keys(tc.args).length?`<div class="tool-card-args">${
  Object.entries(tc.args).map(([k,v])=>`<div><span class="tool-arg-key">${esc(k)}</span> <span class="tool-arg-val">${esc(String(v))}</span></div>`).join('')
}</div>`:''}

The val is wrapped in an inline <span> inside a <div>. Current CSS (static/style.css:1700-1701):

.tool-arg-key{color:var(--blue);font-family:'SF Mono',ui-monospace,monospace;font-size:var(--font-size-xs);}
.tool-arg-val{color:var(--muted);font-family:'SF Mono',ui-monospace,monospace;font-size:var(--font-size-xs);word-break:break-all;}

Default white-space: normal means every \n collapses to a single space, every run of whitespace collapses to one space, and indentation is destroyed. Then word-break: break-all chops the resulting prose-ified blob at arbitrary character boundaries to fit the card width.

For a 30-line Python script passed as the code argument to execute_code, this renders as one wrapped run-on string of ~1.5 visible lines (depending on viewport width and font size). The user sees something like:

code import os, sys sys.path.insert(0, '/home/...') from foo import bar def main(): try: ...

Instead of the actual indented multi-line code.

Bug 2 — .tool-card-result pre caps too tight

static/style.css:1702:

.tool-card-result pre{...max-height:240px;overflow-y:auto;...}

The parent .tool-card-detail opens to max-height: 600px (line 1697). The 240px cap on pre means tool output can also feel cramped even though the outer card has room. Less severe than Bug 1 because the inner pre does have its own scrollbar, but still a paper cut.

Fix

Two CSS-only changes. ~10 LOC.

Patch (static/style.css:1698-1702)

.tool-card-args{margin-bottom:6px;}
.tool-card-args > div{font-size:var(--font-size-xs);line-height:1.6;display:flex;gap:6px;align-items:flex-start;}
.tool-arg-key{color:var(--blue);font-family:'SF Mono',ui-monospace,monospace;font-size:var(--font-size-xs);flex-shrink:0;}
.tool-arg-val{
  color:var(--muted);
  font-family:'SF Mono',ui-monospace,monospace;
  font-size:var(--font-size-xs);
  white-space:pre-wrap;       /* preserve newlines + leading whitespace */
  word-break:break-word;      /* wrap on word boundaries, NOT character */
  flex:1;
  min-width:0;                /* allow flex item to shrink below content width */
  max-height:360px;
  overflow-y:auto;
  display:block;
  margin:0;
}
.tool-card-result pre{
  font-size:var(--font-size-xs);
  color:var(--muted);
  font-family:'SF Mono',ui-monospace,monospace;
  white-space:pre-wrap;
  word-break:break-word;      /* was: break-all — see Bug 1 reasoning */
  max-height:480px;           /* was: 240px — better fit inside 600px parent */
  overflow-y:auto;
  margin:0;
  line-height:1.55;
}

Why these specific values

  • white-space: pre-wrap on .tool-arg-val — preserves every newline and leading space, exactly what users want when reading code. Doesn't affect short single-line args (they still render as one line).
  • word-break: break-word instead of break-all — only breaks long unbroken tokens (URLs, base64) at character boundaries, not normal code. break-all was actively harmful: it'd wrap import os mid-word at imp\nort os.
  • max-height: 360px on val + overflow-y: auto — for genuinely huge args (a 200-line code blob), card stays bounded but val gets its own scrollbar inside. Card-detail outer cap is 600px, so 360 leaves room for key, output, and chrome.
  • max-height: 480px on result pre — bumps from 240px so long outputs are more useful inside the 600px card. Still capped to keep the card itself manageable.
  • display: flex on the row + flex-shrink: 0 on key + flex: 1; min-width: 0 on val — keeps the key column from being compressed when val wraps.

What this is NOT

  • Not a JS change. The renderer is fine; it's the CSS that misbehaves.
  • Not a "verbosity setting" — Cygnus speculated this might be configurable; it isn't, and shouldn't need to be. The default should just work.
  • Not removing the height cap entirely — at 600px parent + scroll-when-needed, the user can read what they want without the page becoming an unbounded code dump.

Reproducer

  1. Run any session that calls execute_code with a multi-line script (most agent sessions hit this within 1-2 turns).
  2. Tool card collapses by default.
  3. Click chevron to expand.
  4. Observe args region: code value is one wrapped run-on line of ~1.5 visible lines, indentation gone, breaks at arbitrary characters.

After fix:

  1. Click chevron to expand.
  2. Observe args region: code value is a properly indented, line-preserved code listing inside its own scrollable region (capped at 360px).

Tests

Behavioral test in tests/test_tool_card_args_rendering.py:

  • Render a tool card with a multi-line code arg, assert the rendered HTML contains the literal \n characters intact (escaped properly, but present in the DOM text).
  • Computed-style smoke test (jsdom or Playwright): getComputedStyle(.tool-arg-val).whiteSpace === 'pre-wrap', wordBreak === 'break-word'.
  • Visual regression check: render with a 30-line code arg, snapshot the bounding-box height of .tool-card-detail is reasonable (between ~200px and 600px), not overflowing the parent.

~50 LOC of tests.

Files

  • static/style.css — the patch above (~15 LOC)
  • tests/test_tool_card_args_rendering.py — new (~60 LOC)

Estimated effort

~75 LOC total. Single PR. P1 — UX paper cut on the most common interaction (looking at a tool call) that affects every user every session.

Reporter / context

  • @cygnus, Discord #testers-talk, May 02 2026
  • Two reports same morning (the wishlist phrasing + the recurring-permission-prompt frustration both have the same fix)
  • No prior issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions