# Terminal Handoff 本文档用于把 `base-path-demo` 当前浏览器终端实现的状态、已验证结论、未解决问题和下一步建议整理清楚,方便后续继续开发。 ## 1. 当前目标 项目目标不是简单接一个现成 JS 终端,而是在 `base-path-demo` 中逐步沉淀一套 Rust 终端能力: - 后端:`Axum + portable-pty` - 前端:`Leptos + Rust/WASM` - 长期方向:抽出可复用 Rust 终端组件库 当前已经完成: - PTY 启动与 shell 会话 - WebSocket 双向通信 - Rust/WASM 终端渲染 MVP - ANSI 样式分段渲染 - 宽高测量与 PTY resize - 基础 scrollback/history 浏览能力 当前仍未稳定完成: - 长历史滚动后的视口一致性 - 从 history 模式返回 live 模式时的可靠定位 - 超长输出下的 scrollback / DOM / parser 三者完全一致 ## 2. 当前实现位置 ### 后端 - `server/src/terminal/pty_session.rs` - `server/src/terminal/ws.rs` - `server/src/main.rs` ### 前端 - `app/src/terminal/core.rs` - `app/src/terminal/component.rs` - `app/src/terminal/scrollback.rs` - `app/src/terminal/protocol.rs` - `app/src/pages/terminal.rs` - `style/tailwind.css` ## 3. 当前前端结构 当前前端已经从“组件里直接推导滚动”改成三层: 1. `TerminalCore` - 基于 `vt100::Parser` - 负责终端字节流解析 - 负责从 parser 生成 `TerminalSnapshot` - 暴露 `snapshot_live()` 与 `snapshot_for_top_row()` 2. `ScrollbackModel` - 位于 `app/src/terminal/scrollback.rs` - 负责管理: - `live/history` 状态 - `top_row` - `ScrollMetrics` - `scrollTop <-> top_row` 映射 - spacer 渲染计划 3. `TerminalPanel` - 负责 DOM 事件和 WebSocket - 把滚动、输入、resize 转换为 model 更新 - 再根据 model 请求 `TerminalCore` 产出 snapshot 这是一次结构性重构,不再依赖前面几轮“scrollback_offset / scrollTop / spacer”互相修补的逻辑。 ## 4. 已确认有效的部分 这些能力在当前实现里基本是成立的: - WebSocket 通信链路稳定 - shell 启动和 PTY 读写稳定 - 普通命令行输入输出稳定 - `btm` 这种全屏 TUI 在 live 模式下效果已经明显优于最初版本 - 宽度和高度测量相比初版更合理 - 终端样式、ANSI 颜色和 cursor 基本可用 ## 5. 当前未解决问题 用户最新反馈:滚动相关问题仍然存在。 典型症状: 1. 滚动到高处后,再滚回底部,视图仍可能错位。 2. 认为已经回到底部,但看不到当前 prompt 或新输出。 3. 某些情况下顶部会出现大块空白。 4. history 模式和 live 模式切换仍然不完全符合真实终端体验。 ## 6. 为什么当前结构仍可能不稳 虽然已经抽出了 `ScrollbackModel`,但当前方案本质上仍然依赖一个前提: > 用 DOM 滚动条 + spacer 高度,去模拟“完整终端历史缓冲区”。 这条路的优势是实现快,但仍有天然风险: ### 6.1 `vt100` 只给出“当前视口” `vt100` 当前更适合表达: - 当前可见 screen - 当前 scrollback offset 它不是为浏览器里的“无限历史虚拟滚动列表”直接设计的 UI 数据结构。 也就是说,浏览器里看到的“长历史列表”不是 parser 原生给出来的,而是我们通过 spacer + 视口切换模拟出来的。 ### 6.2 DOM 滚动不是终端滚动 浏览器滚动条是像素系统,终端历史是行系统。 即使我们做了: - 行高量化 - near-bottom 阈值 - top row model - 程序滚动忽略回流 仍然可能在以下场景出现边界问题: - resize 后 scrollHeight 改变 - 某些行因字符宽度或字体 fallback 产生高度/宽度偏差 - cursor / prompt / wrapped line 在 parser 里和 DOM 里对应关系不完全一致 ### 6.3 spacer 模型仍然是“UI 侧估计” 现在的 spacer 高度来自: - `scrollback_rows` - `viewport_rows` - `top_row` 但这仍然是假设“每一逻辑行高度固定、所有历史行都可用同一高度表达”。 对于普通命令输出通常够用,但当用户期待系统终端级别的一致性时,仍可能暴露问题。 ## 7. 建议的下一步方向 不建议继续围绕当前 spacer 方案做大量微调。 ### 推荐方向 A:显式历史行缓冲 最推荐。 思路: 1. 在前端维护一份显式的“历史行缓冲”。 2. 每次收到 PTY 输出时,不只保留当前 snapshot,还要把可确认滚出屏幕的行纳入历史缓冲。 3. 浏览器滚动条直接绑定: - `history_rows` - `visible_rows` 4. 当前视图渲染为: - 历史区真实行 - 当前 screen 行 这样浏览器滚动就不再依赖 spacer 去“伪装总高度”,而是真正拥有一份历史数据模型。 更具体地说,应当抽成类似: ```text terminal-core parser screen scrollback_store viewport_state terminal-leptos renderer dom scroll adapter input adapter ``` 建议引入一个新的模型,例如: ```rust pub struct TerminalBufferModel { pub history_rows: Vec, pub screen_rows: Vec, pub viewport_rows: usize, pub mode: ViewMode, } ``` 其中: - `history_rows` 是真实可滚动历史 - `screen_rows` 是当前 parser screen - `ViewMode` 表示 live/history 然后滚动行为只是在这份显式数据上切片,不再通过“scrollback offset + spacer”来推导。 ### 推荐方向 B:虚拟列表化 如果历史很多,后续要避免一次性渲染全部历史行。 那就基于显式历史行缓冲再做: - 虚拟滚动窗口 - 仅渲染当前可见行切片 - 上下填充高度由真实行数计算 这个方向适合在方向 A 稳定后再做。 ### 不推荐继续投入的方向 除非只是临时验证,否则不建议继续在下面这些点上做大量时间投入: - 单纯继续调 `scrollTop -> topRow` 的换算公式 - 继续加更多“忽略下一次 scroll 事件”之类的回流保护 - 继续依赖 spacer 修正 live/history 错位 这些都只能减轻问题,不太可能从结构上彻底解决。 ## 8. 一个更稳的实施顺序 建议下一位接手的人按这个顺序推进: 1. 先保留当前 PTY / WS / 输入输出链路,不动后端。 2. 在前端新增显式历史缓冲层,不急着删除现有 `ScrollbackModel`。 3. 把当前渲染改成: - `history_rows + screen_rows` - 不再通过 `snapshot.scrollback_offset` 去制造大块 spacer 4. 先让滚动逻辑只在显式数据上成立。 5. 确认: - 滚到顶部没有空白 - 滚回底部一定能恢复 prompt - history 模式继续输入一定回 live 6. 再考虑是否保留 `vt100` 的 scrollback offset 接口,还是让它只负责 screen,而历史完全交给自有 buffer model。 ## 9. 当前代码是否值得保留 值得保留的部分: - `portable-pty` 后端 - Axum WebSocket 协议与会话层 - `TerminalCore` 的 ANSI 分段渲染能力 - 宽高测量和 PTY resize - `ScrollbackModel` 里关于 live/history 的概念划分 可以重做的部分: - 终端历史滚动的底层表现方式 - spacer 方案本身 - DOM scroll 与 parser scrollback 的耦合方式 ## 10. 建议的 handoff 结论 当前项目已经证明: - Rust 后端 PTY 是可行的 - Rust/WASM 终端组件是可行的 - `btm` 和普通 shell 已经能跑 但要让它真正成为一个稳定的 Rust 终端组件库,下一步不应继续在“spacer + 当前视口推导”上做小修小补,而应转向: > 显式历史缓冲模型 + 真实终端行数据结构 + 可选虚拟滚动 这会更接近真正终端组件库内部的设计,也更利于后续抽成 crate。 ## 11. 当前验证命令 建议继续用下面这些命令回归: - `pwd` - `ls` - `cat ` - `btm` - `top` / `htop`(如果环境可用) - 连续回车制造长历史 重点检查: - 滚到顶部是否有空白 - 滚回底部是否恢复 prompt - history 模式输入后是否回到 live - resize 后历史视图是否稳定