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
This commit is contained in:
yinjianm
2026-03-25 05:54:43 +08:00
parent 654295e0c8
commit d74e84c87b
8 changed files with 190 additions and 6 deletions
+2
View File
@@ -10,3 +10,5 @@
### 修复
- **[frontend]**: 统一 `QuickCommandsView.vue` 的按钮主题适配,移除残留硬编码 hover 色值并切回主题变量体系 — by yinjianm
- 方案: [202603250532_quickcommands-theme-alignment](archive/2026-03/202603250532_quickcommands-theme-alignment/)
- **[frontend]**: 修复终端标签切换后的视口恢复逻辑,贴底终端重新激活后自动贴底,上翻终端保留历史位置 — by yinjianm
- 方案: [202603250547_terminal-tab-scroll-restore](archive/2026-03/202603250547_terminal-tab-scroll-restore/)
+1 -1
View File
@@ -31,7 +31,7 @@
```yaml
kb_version: 2.3.7
最后更新: 2026-03-25 05:39
最后更新: 2026-03-25 05:52
模块数量: 4
待执行方案: 0
```
@@ -0,0 +1 @@
{"status":"completed","completed":5,"failed":0,"pending":0,"total":5,"done":5,"percent":100,"current":"已完成 Terminal.vue 视口恢复修复与验证,等待归档","updated_at":"2026-03-25 05:52:11"}
@@ -0,0 +1,65 @@
# 变更提案: 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 会话现场限制,本轮未在浏览器里真实完成“多终端切换 + 输出滚动”交互验收。
@@ -0,0 +1,51 @@
# 任务清单: terminal-tab-scroll-restore
```yaml
@feature: terminal-tab-scroll-restore
@created: 2026-03-25
@status: completed
@mode: R2
```
## 进度概览
| 完成 | 失败 | 跳过 | 总数 |
|------|------|------|------|
| 5 | 0 | 0 | 5 |
---
## 任务列表
### 1. 方案与范围确认
- [√] 1.1 创建终端切换滚动恢复方案包并锁定前端终端组件范围 | depends_on: []
### 2. 终端滚动恢复修复
- [√] 2.1 盘点终端切换、fit 与 xterm 视口状态的现有实现 | depends_on: [1.1]
- [√] 2.2 在 `Terminal.vue` 中实现“贴底优先、上翻保留”的视口恢复逻辑 | depends_on: [2.1]
### 3. 验证与同步
- [√] 3.1 运行前端最小验证并记录结果 | depends_on: [2.2]
- [√] 3.2 更新 `.helloagents` 文档与变更记录 | depends_on: [3.1]
---
## 执行日志
| 时间 | 任务 | 状态 | 备注 |
|------|------|------|------|
| 2026-03-25 05:47 | 1.1 | 完成 | 创建 implementation 方案包,范围锁定为终端标签切换后的 xterm 视口恢复 |
| 2026-03-25 05:49 | 2.1 / 2.2 | 完成 | 在 Terminal.vue 中加入视口快照、贴底判断与重激活恢复逻辑,并将 fit/ResizeObserver 路径统一纳入恢复流程 |
| 2026-03-25 05:51 | 3.1 | 完成 | `npm run build --workspace @nexus-terminal/frontend` 通过 |
| 2026-03-25 05:52 | 3.2 | 完成 | 更新 frontend 模块文档并准备归档 |
---
## 执行备注
- 本次修复目标是恢复滚动意图,不是简单强制滚到底部。
- 当前最可能的落点是 `Terminal.vue` 的激活与 `fit()` 逻辑,而非 `TerminalTabBar.vue` 本身。
- 运行态真实验收仍依赖本地可用的多 SSH 会话现场;本轮以构建验证和代码路径审查为主。
+2
View File
@@ -9,6 +9,7 @@
|--------|------|------|---------|------|------|
| 202603250317 | ghcr-docker-publish | implementation | workspace-root | ghcr-docker-publish#D001 | ✅完成 |
| 202603250532 | quickcommands-theme-alignment | implementation | frontend | - | ✅完成 |
| 202603250547 | terminal-tab-scroll-restore | implementation | frontend | - | ✅完成 |
| 202603251200 | workspace-workbench-monitor | implementation | frontend, backend | workspace-workbench-monitor#D001 | ✅完成 |
## 按月归档
@@ -16,6 +17,7 @@
### 2026-03
- [202603250317_ghcr-docker-publish](./2026-03/202603250317_ghcr-docker-publish/) - 新增 GHCR 镜像发布 workflow 并切换 compose 镜像来源
- [202603250532_quickcommands-theme-alignment](./2026-03/202603250532_quickcommands-theme-alignment/) - 统一快捷指令视图按钮主题适配,移除残留硬编码 hover 色值
- [202603250547_terminal-tab-scroll-restore](./2026-03/202603250547_terminal-tab-scroll-restore/) - 修复终端标签切换后的贴底/历史滚动恢复逻辑
- [202603251200_workspace-workbench-monitor](./2026-03/202603251200_workspace-workbench-monitor/) - `/workspace` 改为三栏 Workbench 布局,并新增开机累计流量监控
## 结果状态说明
+1 -1
View File
@@ -36,7 +36,7 @@
### 工作区交互
**条件**: 用户进入 `/workspace` 或相关管理页面。
**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 以 tab 容器整合快捷指令、命令历史、文件管理和编辑器,默认激活快捷指令。`QuickCommandsView.vue` 内的新增按钮、空状态按钮和列表操作按钮统一复用 `bg-button``text-button-text``hover:bg-button-hover``hover:bg-border` 等主题变量类,避免写死黑白 hover 色值。
**行为**: 通过组件、Pinia 与 composable 协同管理终端、文件管理、命令历史、布局配置、主题和状态监控;当前 `/workspace` 默认主布局为“左侧 Workbench、中央终端、右侧状态监控”,其中 Workbench 以 tab 容器整合快捷指令、命令历史、文件管理和编辑器,默认激活快捷指令。`QuickCommandsView.vue` 内的新增按钮、空状态按钮和列表操作按钮统一复用 `bg-button``text-button-text``hover:bg-button-hover``hover:bg-border` 等主题变量类,避免写死黑白 hover 色值`Terminal.vue` 会跟踪 xterm 的视口行号与贴底状态,在终端标签切换、重新激活和 `fit()` 后按原滚动意图恢复
**结果**: 页面逻辑分散在 `views/``components/``stores/``composables/`,其中布局与交互微调优先落在 `layout.store.ts``LayoutRenderer.vue``WorkspaceWorkbench.vue``QuickCommandsView.vue``Terminal.vue`
## 依赖关系