Files
nexus-terminal/.helloagents/archive/2026-03/202603250547_terminal-tab-scroll-restore/proposal.md
T
yinjianm d74e84c87b fix(frontend): restore terminal tab scroll position
preserve xterm viewport intent when switching terminal tabs so
bottom-pinned sessions stay pinned and manually scrolled sessions
keep their history position

unify viewport restoration across activation, fit, and resize
paths to avoid losing scroll state after terminal reflow
2026-03-25 05:54:43 +08:00

3.0 KiB

变更提案: terminal-tab-scroll-restore

元信息

类型: 缺陷修复
方案类型: implementation
优先级: P1
状态: 已完成
状态说明: 已完成 Terminal.vue 视口恢复修复与构建验证,待归档
创建: 2026-03-25

1. 需求

背景

/workspace 切换不同终端标签时,xterm 视口偶发没有停留在底部,导致最新输出不在可见区域底部。用户期望保留“看历史”和“追最新输出”两种不同滚动意图。

目标

  • 修复终端标签切换后的视口恢复行为。
  • 如果终端切换前原本就在底部附近,则重新激活后自动贴底。
  • 如果用户切换前主动上翻查看历史,则重新激活后保留原滚动位置。

约束条件

范围约束: 优先限制在前端终端组件与现有会话终端实例管理链路
实现约束: 不改动 SSH 数据流、标签切换语义和后端协议
体验约束: 不得把所有终端切换都强制滚到底部
兼容约束: 兼容现有 keep-alive + v-show 的终端挂载方式

验收标准

  • 切换终端标签时,原本贴底的终端重新显示后仍贴底
  • 切换终端标签时,原本已上翻查看历史的终端重新显示后保留原滚动位置
  • 终端 fit、重激活与滚动恢复逻辑不引入明显回归
  • 前端构建通过

2. 方案

技术方案

Terminal.vue 内为每个终端实例记录当前视口行号和“是否应贴底”的本地状态。使用 xterm 的 buffer.active.ydisp / baseY 判断当前距离底部的行数,并定义一个“小于等于阈值即视为贴底”的规则。终端重新激活或执行 fit 时,不再只做尺寸重算,而是按切换前快照恢复:贴底终端调用 scrollToBottom(),上翻终端调用 scrollToLine(savedYdisp) 恢复到原位置。

影响范围

涉及模块:
  - frontend: Terminal.vue 终端视口恢复逻辑
预计变更文件: 1

风险评估

风险 等级 应对
fit() 后再次滚动导致闪动 将滚动恢复整合到同一恢复函数中,减少多次跳转
输出写入与隐藏标签状态交织时覆盖用户历史位置 切换前冻结快照,重新激活后按快照恢复
阈值过大导致“已上翻少量行”也被当成贴底 使用保守的小阈值,仅认定底部附近少量行差

实施结果

  • Terminal.vue 新增终端视口快照逻辑,使用 buffer.active.viewportYbaseY 判断当前是否处于底部附近。
  • 终端在失活时会冻结“视口行号 + 是否贴底”状态,重新激活时按该状态恢复,而不是无条件停在任意位置。
  • fit()ResizeObserver 路径都纳入了同一套恢复逻辑,避免尺寸重算后把视口意图覆盖掉。
  • npm run build --workspace @nexus-terminal/frontend 通过。
  • 受本地登录态与后端 SSH 会话现场限制,本轮未在浏览器里真实完成“多终端切换 + 输出滚动”交互验收。