跳到主要内容

13. U2 总结

前面十二篇把 U2 阶段的每个模块都逐函数拆了一遍。这一篇做个收束:U2 整体交付了什么、对应哪些计划单元、做了哪些关键技术决策,以及刻意留到后面的部分。

U2 对应主屏计划 2026-06-25-003 的 U2(静态主屏组件)与 U3(输入框输入与换行),以及主题计划 2026-06-29-001 的 U2(半行背景块原语)与 U3(接入背景块与主题)。它由两个 commit 叠加完成:dd15b678(构建交互式主屏)和 5432e018(迁移到 Jotai 并拆分模块)。

到目前为止完成了什么

U1 的 App 还只是一个静态空壳。U2 把它变成了一个真正能交互的主屏:

  • 可组合的主屏布局:Header(logo + 版本)、正文转录区、cwd 行、输入框、状态栏五块,由 HomeScreenView 纵向组合。
  • 底部吸附布局:cwd、输入框、状态栏在任意终端尺寸下都吸在底部,正文与 cwd 之间恰好一个空行,靠 homeScreenAtoms 里的布局与 spacer 计算实现。
  • 正文转录与滚动bodyRows 把类型化条目编译成渲染行,BodyPane 处理换行、滚动窗口和滚动条,支持滚轮与 PageUp/PageDown。
  • 可输入可提交的输入框composerAtoms 持有状态,PromptComposer/ 下的几个聚焦模块分别负责输入、可视文本、光标、渲染;支持换行、Unicode 光标、64 KiB 限长、提交快照。
  • Git 感知的 cwdgitStatus 解析 porcelain 输出,cwd 行显示 ~ 相对路径加分支/改动标记。
  • 内部主题与半行背景块themeConfig 集中语义令牌,半行 / glyph 给用户消息块和输入框做无缝背景。

关键技术决策

  • 共享状态用 Jotai,局部编辑状态仍可内聚。主屏有十几个被多组件读取的派生值,全用 props 会让叶子组件参数爆炸。重构把它们收进 atoms,组件按需订阅。这也是第二个 commit 的主线。

  • 纯函数 helper 与展示组件分离bodyRowslayoutcwdLinebackgroundBlock 等放在 src/libs/tui/ 下,src/components/ 只留 .tsx。布局、换行、Git 解析这些容易出错的逻辑被隔离成可单测的纯函数。

  • 按 200 行约定拆分文件HomeScreenPromptComposer 都从单文件拆成了 colocated 文件夹(index + 子模块),符合 tui/AGENTS.md 的文件大小约定。输入框尤其明显:状态、输入、可视文本、光标、帧渲染各自成文件。

  • 背景渲染是内部原语,且 fallback-aware。半行块只用在消息块和输入框这两个局部,没有去刷整屏 canvas(早期尝试会露行缝)。shouldRenderBackground 被保留成变量而非写死,给将来按终端能力降级留了接口。所有“含义”都用非颜色标记(ERROR:*/+/%、滚动条字形)承载,去色后依然可读。

  • 手动光标定位必须随布局同步验证。光标用 Ink cursor API 手动算,y 要叠加 composerTop、背景 padding 和 Ink 行原点偏移。任何改动 body 高度、spacer、换行或背景行的改动,都要重新确认光标落在当前文本行上。

  • 鼠标与 Git 都要稳健降级。Git 状态读取整段 try/catch 加超时,失败就不显示而不是崩溃;鼠标上报只在 TTY 下开关,非 TTY 跳过。

刻意留到后面的部分

U2 只搭了可交互的前端外壳,没有接任何后端行为。提交 prompt 目前只是把它追加进正文,onPromptSubmit 默认是 noop。主题也没有任何用户可见配置(没有 /theme、没有持久化、没有自定义主题文件)。这些都按计划留给后续单元:

  • 主屏计划的 U4–U8:Rust JSON-RPC stdio 后端、协议、受控启动器、后端客户端、提交/ACK 接线。
  • 之后的 U9–U13:SQLite 会话存储与 /resume、独立可执行文件、跨平台分发与 Release 流水线。

换句话说,U2 把“看得见、摸得着”的主屏先做扎实——布局稳、输入顺、转录清楚——为后面接入真正的 agent 循环打好 UI 地基。