接受终端的约束而不是绕过它:"coding agent 本质是聊天界面,一切可线性化。"
终端 UI 的两条路各有致命伤:全屏 TUI(opencode 一派)独占视口,丢掉终端原生的滚回缓冲和搜索;朴素追加打印又会闪烁,而且没法原地更新流式生成中的 markdown。
走 Claude Code 一派的"流式 TUI",但引入保留模式组件树(pi-tui 包):
- 每个 Component 实现
render(width): string[],Container 垂直排列;组件缓存渲染输出——流式完成的消息不必每帧重新解析 markdown; - 差分算法:首帧全量输出;之后找第一个差异行,从那行重绘到底;
- 终端宽度变化(软换行全变)→ 清屏全量重绘;
- 关键坑:差异行已滚出可见视口上方时只能全量重绘——终端不允许写入滚回缓冲区,这是协议级约束,绕不过去;
- 用同步输出转义序列
CSI ?2026h/l把一帧的输出原子化提交,Ghostty/iTerm2 下完全无闪烁; - 代价算过账:内存里保存完整滚回缓冲(几百 KB 字符串),作者实测 V8 下可接受。
这是"约束逼出简洁"的一个完整案例:不能随意定位光标、不能写滚回区——接受这两条终端铁律之后,剩下的唯一可行架构(线性组件树 + 从差异行重绘到底)反而比全屏 TUI 的自由布局简单得多。作者的架构论点值得记住:coding agent 本质是聊天界面,一切可线性化——需要二维布局的那一刻,往往是功能本身该被质疑的一刻(呼应 p02)。
notes 没有 TUI 章节(Reina 是桌面 GUI,渲染走 DOM)。仅参考——但"组件缓存 + 首个差异行重绘"的思路对任何流式渲染面(包括 GUI 里的消息列表虚拟化)都成立。

