feat(tui): simplify conversation detail and quiet chat output - #2193
feat(tui): simplify conversation detail and quiet chat output#2193kevinjosethomas wants to merge 24 commits into
Conversation
Prime Agent performance — completedPR Overall: 0 regressed · 0 improved · 17 no clear change.
Python runtime
Sandbox cost: ~$0.0880 — no inference calls. Methodology and samplesMain resolved at 2026-09-10T23:57:28.483618+00:00. Harness
|
| "toolPanelBg", | ||
| "toolTitle", | ||
| "toolOutput", | ||
| "mdBody", |
There was a problem hiding this comment.
🟠 High theme/theme-schema.json:63
Existing custom themes without mdBody are accepted during the initial load, but rendering an assistant paragraph then calls Theme.fg("mdBody", ...) and throws Unknown theme color: mdBody, breaking the TUI. Make mdBody optional with a fallback to text, or migrate legacy themes before rendering.
Also found in 1 other location(s)
packages/coding-agent/src/modes/interactive/components/assistant-message.ts:301
This unconditionally looks up
mdBody, but existing user theme JSON files do not have that newly required key. On the initial custom-theme load,parseThemeJsondeliberately performs only its minimal structural check before the async validator is ready, so such a theme is accepted and constructed without anmdBodyforeground. Rendering any ordinary assistant paragraph then callsTheme.fg("mdBody", ...), which throwsUnknown theme color: mdBodyand breaks the TUI render. Provide a fallback (for example the previous terminal/default text color) or migrate/default legacy custom themes before using the new token.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/interactive/theme/theme-schema.json around line 63:
Existing custom themes without `mdBody` are accepted during the initial load, but rendering an assistant paragraph then calls `Theme.fg("mdBody", ...)` and throws `Unknown theme color: mdBody`, breaking the TUI. Make `mdBody` optional with a fallback to `text`, or migrate legacy themes before rendering.
Evidence trail:
Reviewed commit 7412daf. packages/coding-agent/src/modes/interactive/theme/theme-schema.json:37-94 requires mdBody; packages/coding-agent/src/modes/interactive/theme/theme.ts:63-68 adds mdBody as required in the runtime schema; theme.ts:672-725 shows the initial minimal validation and later non-mutating validation; theme.ts:760-786 constructs colors only from supplied entries; theme.ts:400-403 throws for missing colors; packages/coding-agent/src/modes/interactive/components/assistant-message.ts:295-302 calls theme.fg("mdBody", ...). Commands: git_diff --base MERGE_BASE --head REVIEWED_COMMIT -- packages/coding-agent/src/modes/interactive/theme/theme-schema.json; git_grep mdBody|Unknown theme color|Theme.fg.
Also found in 1 other location(s):
- packages/coding-agent/src/modes/interactive/components/assistant-message.ts:301 -- This unconditionally looks up `mdBody`, but existing user theme JSON files do not have that newly required key. On the initial custom-theme load, `parseThemeJson` deliberately performs only its minimal structural check before the async validator is ready, so such a theme is accepted and constructed without an `mdBody` foreground. Rendering any ordinary assistant paragraph then calls `Theme.fg("mdBody", ...)`, which throws `Unknown theme color: mdBody` and breaks the TUI render. Provide a fallback (for example the previous terminal/default text color) or migrate/default legacy custom themes before using the new token.
There was a problem hiding this comment.
🟡 Medium
Persisted hideThinkingBlock: false settings are ignored, so existing users start with thinking blocks hidden until they cycle the chat-detail control. Removing the getHideThinkingBlock() assignment leaves the field's default true value in place; restore the assignment during runtime settings application.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/interactive/interactive-mode.ts around line 2563:
Persisted `hideThinkingBlock: false` settings are ignored, so existing users start with thinking blocks hidden until they cycle the chat-detail control. Removing the `getHideThinkingBlock()` assignment leaves the field's default `true` value in place; restore the assignment during runtime settings application.
Evidence trail:
packages/coding-agent/src/modes/interactive/interactive-mode.ts:1043, 1511-1514, 2554-2564, 2846-2856, 6461-6476, 7322-7341, 7353-7366; packages/coding-agent/src/core/settings-manager.ts:961-969. Reviewed commit short hash: 4d3fcdf. Verify with: git diff MERGE_BASE REVIEWED_COMMIT -- packages/coding-agent/src/modes/interactive/interactive-mode.ts
| private editDiffsExpanded = false; | ||
|
|
||
| private hideThinkingBlock = false; | ||
| private hideThinkingBlock = true; |
There was a problem hiding this comment.
Orphaned hideThinkingBlock settings API
Medium Severity
hideThinkingBlock is now a presentation flag on the Ctrl+O cycle, but SettingsManager.getHideThinkingBlock / setHideThinkingBlock and the settings field still exist with no non-test callers. The TUI never reads the setting, so hideThinkingBlock: false is silently ignored while docs/settings.md still documents it as a working option.
Additional Locations (1)
Triggered by project rule: Review rules
Reviewed by Cursor Bugbot for commit 4d3fcdf. Configure here.
| this.dirty = true; | ||
| } | ||
|
|
||
| /** True when the message carries a non-empty thinking block. */ |
There was a problem hiding this comment.
Unused thinking-content helper remains
Low Severity
hasThinkingContent() is still a public method on AssistantMessageComponent, but this change deleted its only caller (toggleThinkingBlockVisibility). It now has no non-test consumers.
Triggered by project rule: Review rules
Reviewed by Cursor Bugbot for commit 4d3fcdf. Configure here.
| .split("\n") | ||
| .filter((line) => !/^(?:from rlm import bash|import rlm)\s*$/.test(line)); | ||
| // Restrict to a single top-level launch, optionally followed by its variable. | ||
| const call = /^(?:([A-Za-z_]\w*)\s*=\s*)?(?:rlm\.)?bash\(\s*(.*)\s*\)\s*$/.exec(launch[0] ?? ""); |
| const launch = code | ||
| .trim() | ||
| .split("\n") | ||
| .filter((line) => !/^(?:from rlm import bash|import rlm)\s*$/.test(line)); |
There was a problem hiding this comment.
🟡 Medium components/shell-completion.ts:74
A blank line after the rlm import makes readBackgroundShellHandle reject an otherwise valid single launch, so the completion remains detached for cells such as from rlm import bash\n\njob = bash('npm test')\njob. The normalization removes import lines but retains empty lines, causing launch[0] to miss the bash(...) call; filter blank lines as well.
| .filter((line) => !/^(?:from rlm import bash|import rlm)\s*$/.test(line)); | |
| .filter((line) => line.trim() !== "" && !/^(?:from rlm import bash|import rlm)\s*$/.test(line)); |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/interactive/components/shell-completion.ts around line 74:
A blank line after the `rlm` import makes `readBackgroundShellHandle` reject an otherwise valid single launch, so the completion remains detached for cells such as `from rlm import bash\n\njob = bash('npm test')\njob`. The normalization removes import lines but retains empty lines, causing `launch[0]` to miss the `bash(...)` call; filter blank lines as well.
Evidence trail:
Commit 783d8d4: packages/coding-agent/src/modes/interactive/components/shell-completion.ts:71-78; packages/coding-agent/src/modes/interactive/components/tool-execution.ts:268-271; packages/coding-agent/src/modes/interactive/components/conversation-components.ts:85-95.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 5 total unresolved issues (including 3 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 783d8d4. Configure here.
| Text, | ||
| truncateToWidth, | ||
| visibleWidth, | ||
| } from "@earendil-works/pi-tui"; |
There was a problem hiding this comment.
Dead thinking-label API remains
Low Severity
Removing the thinking heading left hiddenThinkingLabel and setHiddenThinkingLabel with no effect on output. The constructor argument, option, and signature key still exist and only force rebuilds that render the same dim trace.
Additional Locations (2)
Triggered by project rule: Review rules
Reviewed by Cursor Bugbot for commit 783d8d4. Configure here.
| const hint = detailHint ? `${theme.fg("dim", " · ")}${detailHint}` : ""; | ||
| // Keep the summary path stable across conversation detail modes. | ||
| const expandedHint = diffsExpanded === undefined ? "" : expandCollapseHint("app.tools.expand", true); | ||
| const widestHint = expandedHint ? `${theme.fg("dim", " · ")}${expandedHint}` : ""; |
There was a problem hiding this comment.
Dead file-summary hint logic
Low Severity
formatFileChangeSummaryLine still branches on diffsExpanded and builds hint/widest-hint strings, but expandCollapseHint("app.tools.expand") is always empty, so those strings and the extra width reservation never change the line.
Additional Locations (1)
Triggered by project rule: Review rules
Reviewed by Cursor Bugbot for commit 783d8d4. Configure here.
| ); | ||
| this.addBlank(lines, width); | ||
| this.addPlain(lines, truncateToWidth(agentMessageSummaryLine(label, recipient), Math.max(1, width - 1), "…")); | ||
| lines.push(...agentMessageBodyLines(message.message, width)); |
There was a problem hiding this comment.
🟠 High components/ipython-cell.ts:675
Large sent agent messages make agentMessageBodyLines return enough entries that lines.push(...lines) exceeds JavaScript's argument limit and throws during TUI rendering. Append each body line iteratively instead, as renderFileDiff does for large diffs.
| lines.push(...agentMessageBodyLines(message.message, width)); | |
| for (const line of agentMessageBodyLines(message.message, width)) { | |
| lines.push(line); | |
| } |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/interactive/components/ipython-cell.ts around line 675:
Large sent agent messages make `agentMessageBodyLines` return enough entries that `lines.push(...lines)` exceeds JavaScript's argument limit and throws during TUI rendering. Append each body line iteratively instead, as `renderFileDiff` does for large diffs.
Evidence trail:
Reviewed commit 9723a966e5e2e118f9363a1d29d6700389a09021: packages/coding-agent/src/modes/interactive/components/ipython-cell.ts:668-676; packages/coding-agent/src/modes/interactive/components/ipython-cell.ts:697-734; packages/coding-agent/src/modes/interactive/components/agent-message.ts:22-32; packages/coding-agent/src/core/kernel/shared.ts:181-207. External semantics: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
Keep string bodies out of collapsed previews and restore ANSI continuation state before source-line gutters. fixes #2193
Keep string bodies out of collapsed previews and restore ANSI continuation state before source-line gutters. fixes #2193


Conversations open with assistant text visible, thinking hidden, tools collapsed, and compact per-file change summaries. The configurable Ctrl+O shortcut cycles through overview, thinking plus file diffs, and all output. The selected mode applies to streaming content and restored chats. Thinking renders as dim text without a repeated heading; expanded file diffs start at the normal chat inset while preserving code indentation and line-number gutters. A single status shows “Showing overview”, “Showing details”, or “Showing all output” with the configured expand or collapse shortcut. Unbound shortcuts omit the hint. Transcript rows, including sent and received agent messages, no longer repeat expansion hints.
Background shell completions update an identifiable originating IPython row: a running marker becomes a success check or a failure with its exit code. Matching prefers an exact saved BashHandle PID and command. Assignment-only cells do not save a handle, so a successful simple literal assignment can match by its unique exact full command; repeated commands, conflicting known PIDs, complex launches, and unmatched events retain compact “Background shell command finished” or failure notices. Completion rows share tool-row spacing without an extra gap below. All output preserves the complete notification beneath the command and a timestamped marker at its original transcript position. Cell duration remains explicitly labeled as cell timing because completion events do not contain shell duration or output.
Assistant prose, thinking, collapsed tool previews, and trailing event details use quieter colors and less decorative bold. Sent and received agent messages keep compact notices in overview and details, with their full message bodies shown only in all output. Their separate Ctrl+P action and shortcut are removed. Multiline Python strings preserve their syntax color across source lines and narrow wrapping, including embedded Markdown and code snippets. Collapsed Python previews select statements outside string bodies. These are presentation changes; saved messages, JSONL traces, model/tool execution, and transport behavior are unchanged.
Validation: 34 focused Python preview and rendering tests passed for multiline strings, raw strings, streamed incomplete strings, resumed token colors, and narrow wrapping, plus a read-only reproduction of the reported saved cell. 206 tests across the three affected rendering, status, and live/replay files passed for the compact-notice behavior, plus
npm run check. The broader shortcut and shell regression checks also passed. Faux-provider regressions cover matched and unmatched shell success/failure, assignment-only launches, completion-before-tool-result ordering, duplicate notifications, escaped commands, compact completion adjacency, live/restored agent-message visibility, and unchanged saved trace bytes.Design context: UI proposals.
Note
Medium Risk
Large user-facing TUI and keybinding changes with new shell-completion matching logic, but presentation and settings defaults only—saved session traces and tool execution are unchanged.
Overview
Unifies conversation expansion behind a single Ctrl+O three-step cycle—overview (thinking hidden, tools/diffs/agent bodies compact) → details (thinking + file diffs visible, tool output still collapsed) → all output—and removes the old Ctrl+J, Ctrl+T, and Ctrl+P bindings. Detail mode is presentation-only (tray status shows the current step and shortcut; transcript rows no longer repeat expand hints). Thinking defaults to hidden in overview and renders as dim trace text without a header when revealed.
Visual hierarchy adds theme token
mdBodyfor assistant prose, dims collapsed tool/IPython/bash previews and trailing row metadata, trims decorative bold, and aligns expanded file diffs with the normal chat inset.Background shell completions can attach to matching tool/IPython rows (PID + command, with a narrow fallback for unique assignment-only launches); otherwise compact fallback notices remain, with full notifications in all output. Python previews ignore multiline string bodies; expanded cells keep syntax coloring across wrapped lines.
Settings drops the separate “hide thinking” toggle (behavior follows the detail cycle). Theme schemas require five new color tokens (
mdBody, diff backgrounds/text).Reviewed by Cursor Bugbot for commit efa801a. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Simplify TUI conversation detail cycle and quiet chat output styling
Ctrl+O.hideThinkingBlocksetting and its callback. Thinking blocks now start hidden and their visibility is controlled by the new detail cycle.mdBodytheme color token for assistant markdown text and applies dim styling to quiet output.ShellCompletionComponentfor asynchronous Bash completion messages, attaching them to the matching preceding tool execution.message-expansion,edit-expansion, andthinking-togglekeybindings and their legacy migrations in keybindings.ts; existing configs referencing these may need manual updates.Macroscope summarized efa801a.