Copyable TUI transcript without gutter bars
无 gutter bar 的可复制 TUI transcript
The TUI grouped user prompts and tool cards behind a colored left-gutter bar (`▌ `) prepended to every body line, and indented assistant and system blocks by one column. Both are per-line prefixes: a terminal mouse drag-select over the scrollback captures the leading `▌ ` or the leading space on each line, so copy-paste of a message, a tool's output, or a code block pulls in decoration the user must strip by hand. Th
English
Problem
The TUI grouped user prompts and tool cards behind a colored left-gutter bar (▌ ) prepended to every body line, and indented assistant and system blocks by one column. Both are per-line prefixes: a terminal mouse drag-select over the scrollback captures the leading ▌ or the leading space on each line, so copy-paste of a message, a tool's output, or a code block pulls in decoration the user must strip by hand. The bar was the transcript's only per-message separator, so it could not simply be dropped without another way to tell messages apart.
Decision
The scrollback carries no per-line prefix. Messages are separated only by a bold, underlined role header in the role color and blank-line spacing, both of which the terminal already inserts around each block. The underline gives each role a distinct visual band without a background fill, so it reads on any terminal theme and never enters the clipboard:
- User and steering prompts (
UserMessageComponent) are a plainContainer: a bold, underlined accentYou/Steeringheader line (via the sharedmessageHeaderhelper), then the prompt body at column 0. - Assistant blocks render a bold, underlined
Assistantheader, then reasoning and text at column 0, with the timing line at the end of the block (the formerpaddingX = 1indent is gone). - Tool cards drop the
GutterBoxwrapper. The card status (pending / error / success) colors the whole title line — the status glyph (◌/✕/✓) plus the title text share one color, bold and underlined to match the role headers — instead of a colored bar beside an uncolored title. The body renders unprefixed; body lines still pass throughTextat the terminal width so overlong raw tool output wraps rather than overflowing. - The
GutterBoxclass is deleted; nothing else used it.
A drag-select over any of these regions now copies exactly the message text.
Alternatives considered
- Keep the bar only on user messages, drop it on tool cards — leaves tool output, the most-copied region, still polluted. Rejected: the goal is a wholly copyable transcript.
- A single top rule or bar on the header line only — the body copies clean, but selecting the header still captures a glyph, and it reintroduces a decoration character for no distinguishing gain over the underlined role header.
- Indent grouped bodies instead of a bar — leading spaces still enter the clipboard, so it does not solve the copy problem; explicitly ruled out.
- A filled background band on the header (reverse video, or a 256-color muted background) — gives each role a strong color block, but the saturated ANSI fill reads as too heavy and the 256-color shades are fixed rather than theme-remapped. The underline gives per-role distinction with a far lighter footprint.
Consequences
- Copy-paste from the scrollback is clean with no user post-processing. This was the motivating win.
- The transcript is flatter than the gutter-bar layout, but each role's bold, underlined header in the role color plus blank-line spacing keeps message boundaries clear without any left-edge fill. Tool-card status stays legible through the colored, underlined glyph and title.
- Box-drawing borders (
│) on transient overlays — status panel, model selector, resume list — are untouched. They are not scrollback message content and are rarely copied. - The affected keyless TUI
*.expected.txtsnapshots were re-recorded by fixture replay (no API key needed; the recorded LLM sessions are unchanged, only the render differs). Interactive boot and a round-trip prompt were verified in tmux.
中文
Problem
TUI 此前把用户提示词和工具卡片分组在一条彩色左侧 gutter bar(▌ )之后,该竖条被逐行加在每一行正文前面,并把 assistant 与系统块整体缩进一列。两者都是逐行前缀:在 transcript 上用鼠标框选时,每一行开头的 ▌ 或前导空格都会被一并选中,因此复制一条消息、一段工具输出或一个代码块时都会带上装饰字符,用户必须手动清理。该竖条又是 transcript 中唯一的逐条消息分隔标记,所以不能在没有其他区分方式的情况下直接删掉。
Decision
transcript 不再带任何逐行前缀。消息仅通过以角色色渲染的粗体带下划线角色标题和空行分隔,而这两者本就由终端在每个块前后自动插入。下划线让每个角色获得清晰的视觉分带,且无需背景填充,因此在任何终端配色下都可读,也绝不会进入剪贴板:
- 用户提示词与 steering 提示词(
UserMessageComponent)改为普通Container:一行粗体带下划线的强调色You/Steering标题(经共享的messageHeader辅助函数生成),随后是位于第 0 列的提示词正文。 - Assistant 块渲染一行粗体带下划线的
Assistant标题,随后 reasoning 与文本均在第 0 列渲染,timing 行位于块末尾(原先的paddingX = 1缩进已移除)。 - 工具卡片去掉
GutterBox包装层。卡片状态(进行中 / 错误 / 成功)对整行标题着色——状态字形(◌/✕/✓)与标题文本共用一种颜色,并同角色标题一样加粗且带下划线——而不再是未着色标题旁的一条彩色竖条。正文无前缀渲染;正文行仍按终端宽度经Text处理,使过长的原始工具输出换行而非溢出。 GutterBox类被删除;没有其他地方使用它。
现在对上述任一区域框选,复制得到的正是消息文本本身。
Alternatives considered
- 仅在用户消息上保留竖条、在工具卡片上去掉 —— 会让最常被复制的工具输出仍然带有污染。已否决:目标是让整个 transcript 都可复制。
- 仅在标题行上加一条顶部横线或竖条 —— 正文复制干净,但选中标题时仍会带上一个字形,且相比带下划线的角色标题并未带来额外的区分收益,却重新引入了装饰字符。
- 用缩进代替竖条对分组正文缩进 —— 前导空格仍会进入剪贴板,无法解决复制问题;已明确排除。
- 在标题上使用填充背景带(反色,或 256 色柔和背景)—— 能给每个角色一块强烈的色块,但饱和的 ANSI 填充观感过重,且 256 色是固定色而非随主题重映射。下划线以远更轻的方式提供了同样的逐角色区分。
Consequences
- 从 transcript 复制粘贴无需用户做任何后处理。这正是本次改动的核心收益。
- transcript 比 gutter bar 布局更扁平,但每个角色以角色色渲染的粗体带下划线标题加空行分隔,无需任何左缘填充即可让消息边界保持清晰。工具卡片状态仍通过彩色带下划线的字形与标题保持可读。
- 临时浮层(状态面板、模型选择器、恢复列表)上的制表符边框(
│)保持不变。它们不属于 transcript 消息内容,且很少被复制。 - 受影响的 keyless TUI
*.expected.txt快照均通过 fixture 回放重新记录(无需 API 密钥;所记录的 LLM 会话未变,仅渲染不同)。交互式启动与一次往返提示已在 tmux 中验证。