# 变更提案: terminal-scroll-viewport-restore-fix ## 元信息 ```yaml 类型: 缺陷修复 方案类型: implementation 优先级: P1 状态: 实施中 创建: 2026-04-12 ``` --- ## 1. 需求 ### 背景 当前工作区会为每个 SSH 会话保留一个独立的 `xterm` 终端实例,并在标签切换、尺寸变化和重新激活时恢复 viewport。现有实现使用“绝对行号”保存滚动位置:当终端持续输出日志、用户又切换到其他服务器后,隐藏期间 `buffer.baseY` 会继续增长,但保存的绝对行号不会随之更新。重新激活该终端时,组件会把 viewport 恢复到一个已经过时的绝对位置,导致用户向下滚动时很难再追到底部,表现为“鼠标滚轮先向上滚过一次,后面向下滚也到不了最底部”。 ### 目标 - 修复终端在持续输出日志时切换服务器后出现的滚动恢复异常。 - 保持现有“在底部时继续贴底、离底部阅读时不强制自动跟随”的总体交互策略不变。 - 将影响范围控制在前端终端组件,不修改后端会话、WebSocket 协议或工作区布局逻辑。 ### 约束条件 ```yaml 范围约束: 仅修改 packages/frontend 中与 xterm viewport 跟踪和恢复有关的代码与文档 行为约束: 不新增切换服务器后自动跳底的强制行为,不改变用户主动离开底部阅读时的现有语义 兼容性约束: 兼容 keep-alive + v-show 的多终端实例结构,兼容现有 ResizeObserver 和 fit() 调整流程 验证约束: 以 packages/frontend 的 TypeScript 构建通过作为静态验收基线 ``` ### 验收标准 - [ ] 终端处于持续输出日志状态时,切换到其他服务器再切回,若此前贴底则仍能恢复到最底部。 - [ ] 终端离开底部后切换服务器再切回,viewport 恢复应基于“距离底部的偏移”而不是过时的绝对行号,用户继续向下滚动能够重新到达底部。 - [ ] `packages/frontend` 构建校验通过。 --- ## 2. 方案 ### 技术方案 将 `Terminal.vue` 中的 viewport 快照从“绝对 viewport 行号”改为“距底部的偏移量 + 是否贴底”: - 采集快照时记录 `distanceFromBottom = baseY - viewportY`,而不是仅记录 `viewportY`。 - 恢复快照时,如果此前处于贴底状态则继续调用 `scrollToBottom()`;如果此前离底部,则按当前最新 `baseY - distanceFromBottom` 计算目标行并恢复。 - 保留现有 `onScroll`、`ResizeObserver`、`fitAndEmitResizeNow()` 和激活切换逻辑,仅修正快照语义,避免把过期的绝对行号反复应用到不断增长的缓冲区上。 ### 影响范围 ```yaml 涉及模块: - frontend: Terminal.vue 的 viewport 跟踪与恢复逻辑 - frontend: frontend 模块知识库文档与 CHANGELOG 记录 预计变更文件: 4-5 ``` ### 风险评估 | 风险 | 等级 | 应对 | |------|------|------| | 恢复公式错误导致离底部阅读位置跳动 | 中 | 仅替换快照字段语义,复用现有 `scrollToBottom`/`scrollToLine` 恢复链路 | | 修改后影响普通贴底场景 | 低 | 保留 `shouldStickToBottom` 判断,贴底路径不变 | | 构建期间暴露出与本次改动无关的旧错误 | 低 | 先执行前端构建验证,若失败则区分是本次回归还是仓库既有问题 | --- ## 3. 核心场景 ### 场景: 持续日志输出时切换会话后恢复终端 **模块**: frontend / Terminal.vue **条件**: 某个 SSH 终端持续输出日志,用户切换到其他服务器后再切回该终端 **行为**: 组件在重新激活与 `fit()` 后恢复 viewport **结果**: 若此前贴底则保持贴底;若此前离底部则按距离底部的相对偏移恢复,不会因为隐藏期间日志追加而把终端固定在越来越早的历史位置 ### 场景: 用户手动滚离底部后再向下滚动 **模块**: frontend / Terminal.vue **条件**: 用户曾向上滚动查看历史内容,终端仍在持续输出 **行为**: 用户向下滚动尝试回到最新输出 **结果**: viewport 不会被过期快照拖回旧位置,用户可以继续下滚直到重新抵达底部 --- ## 4. 技术决策 ### terminal-scroll-viewport-restore-fix#D001: viewport 快照改为记录距底部偏移而非绝对行号 **日期**: 2026-04-12 **状态**: 已采纳 **背景**: 当前问题本质是日志持续输出时 `baseY` 持续增长,而组件记录的 `viewportLine` 是静态绝对值。重新激活后按旧绝对值恢复,会让 viewport 相对最新输出越来越靠前。 **选项分析**: | 选项 | 优点 | 缺点 | |------|------|------| | A: 保留现有恢复链路,只把快照字段改为“距底部偏移” | 改动小,符合现有交互语义,能直接修复会话切换后的滚动异常 | 仍需依赖现有 `fit()`/`ResizeObserver` 链路的稳定性 | | B: 切换会话后统一强制 `scrollToBottom()` | 实现最简单,用户总能看到最新输出 | 会改变现有交互语义,破坏用户离底部阅读历史内容的能力 | **决策**: 选择方案 A **理由**: 用户已明确要求“只修复当前异常,不额外改动自动跟随策略”。使用相对底部偏移恢复既能消除过时绝对行号带来的问题,又不改变贴底/离底部两种状态的既有语义。 **影响**: 影响 `packages/frontend/src/components/Terminal.vue` 的 viewport 快照结构和恢复计算方式,并同步 frontend 模块知识库与变更日志。 --- ## 5. 成果设计 ### 设计方向 - **美学基调**: N/A,本次为交互缺陷修复,不涉及视觉样式调整 - **记忆点**: N/A - **参考**: 现有终端交互保持不变 ### 视觉要素 - **配色**: N/A - **字体**: N/A - **布局**: N/A - **动效**: N/A - **氛围**: N/A ### 技术约束 - **可访问性**: 不改动现有 DOM 结构与键鼠交互入口 - **响应式**: 保持桌面端与移动端现有 `fit()`/`ResizeObserver` 适配流程