# 变更提案: terminal-tab-scroll-restore ## 元信息 ```yaml 类型: 缺陷修复 方案类型: implementation 优先级: P1 状态: 已完成 状态说明: 已完成 Terminal.vue 视口恢复修复与构建验证,待归档 创建: 2026-03-25 ``` --- ## 1. 需求 ### 背景 在 `/workspace` 切换不同终端标签时,xterm 视口偶发没有停留在底部,导致最新输出不在可见区域底部。用户期望保留“看历史”和“追最新输出”两种不同滚动意图。 ### 目标 - 修复终端标签切换后的视口恢复行为。 - 如果终端切换前原本就在底部附近,则重新激活后自动贴底。 - 如果用户切换前主动上翻查看历史,则重新激活后保留原滚动位置。 ### 约束条件 ```yaml 范围约束: 优先限制在前端终端组件与现有会话终端实例管理链路 实现约束: 不改动 SSH 数据流、标签切换语义和后端协议 体验约束: 不得把所有终端切换都强制滚到底部 兼容约束: 兼容现有 keep-alive + v-show 的终端挂载方式 ``` ### 验收标准 - [ ] 切换终端标签时,原本贴底的终端重新显示后仍贴底 - [ ] 切换终端标签时,原本已上翻查看历史的终端重新显示后保留原滚动位置 - [ ] 终端 `fit`、重激活与滚动恢复逻辑不引入明显回归 - [ ] 前端构建通过 --- ## 2. 方案 ### 技术方案 在 `Terminal.vue` 内为每个终端实例记录当前视口行号和“是否应贴底”的本地状态。使用 xterm 的 `buffer.active.ydisp` / `baseY` 判断当前距离底部的行数,并定义一个“小于等于阈值即视为贴底”的规则。终端重新激活或执行 `fit` 时,不再只做尺寸重算,而是按切换前快照恢复:贴底终端调用 `scrollToBottom()`,上翻终端调用 `scrollToLine(savedYdisp)` 恢复到原位置。 ### 影响范围 ```yaml 涉及模块: - frontend: Terminal.vue 终端视口恢复逻辑 预计变更文件: 1 ``` ### 风险评估 | 风险 | 等级 | 应对 | |------|------|------| | `fit()` 后再次滚动导致闪动 | 低 | 将滚动恢复整合到同一恢复函数中,减少多次跳转 | | 输出写入与隐藏标签状态交织时覆盖用户历史位置 | 低 | 切换前冻结快照,重新激活后按快照恢复 | | 阈值过大导致“已上翻少量行”也被当成贴底 | 低 | 使用保守的小阈值,仅认定底部附近少量行差 | ### 实施结果 - `Terminal.vue` 新增终端视口快照逻辑,使用 `buffer.active.viewportY` 与 `baseY` 判断当前是否处于底部附近。 - 终端在失活时会冻结“视口行号 + 是否贴底”状态,重新激活时按该状态恢复,而不是无条件停在任意位置。 - `fit()` 和 `ResizeObserver` 路径都纳入了同一套恢复逻辑,避免尺寸重算后把视口意图覆盖掉。 - `npm run build --workspace @nexus-terminal/frontend` 通过。 - 受本地登录态与后端 SSH 会话现场限制,本轮未在浏览器里真实完成“多终端切换 + 输出滚动”交互验收。