Skip to content

Commit 14b8a12

Browse files
committed
feat(term): 修复终端滚轮与输入法位置问题并完善回归测试
- 实现终端滚轮与输入法位置专项修复,包括 wheel 事件冒泡阶段处理和 IME 锚点跟随光标行列 - 添加多终端焦点与真实事件路径回归测试,验证 split-pane 场景下输入框错位和滚轮行为 - 将 Wheel/IME 修复收口到 xterm 官方扩展点,确保多终端场景中输入框不串位 - 停用终端历史缓存恢复链路,改为只保留当前会话实时追加和 heldData 顺序回放 - 更新 package 版本号至 2026.4.21-1 并调整构建配置 - 记录终端问题两阶段闭环决策过程和后续任务规划
1 parent 28bc7c6 commit 14b8a12

13 files changed

Lines changed: 1750 additions & 363 deletions

File tree

.harness/decisions.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,34 @@
3030
- 原因:用户确认“小眼睛”图标已经满足关闭需求,不再需要额外的文字隐藏按钮
3131
- 收益:界面更干净,同时保留现有 block header 的统一交互
3232
- 范围:移除额外文字按钮,不影响 `Feishu Web` 的网页容器能力
33+
34+
## 2026-04-21
35+
36+
# ADR-20260421-001: 终端问题改为“两阶段闭环”推进
37+
38+
## Context
39+
- 当前终端已停用历史恢复链路,并已有单 terminal smoke
40+
- 用户最新截图显示:真实多 terminal split-pane 场景下,输入框错位和滚轮回归仍会发生
41+
- 现有 smoke 通过 `window.term` 与内部 `.xterm-scrollable-element` 直派发事件,不能代表真实用户路径
42+
43+
## Options
44+
- option A:继续在现有 `termwrap.ts` 上直接 patch
45+
- option B:先补多 terminal / 真实焦点 / 真实 wheel 路径 smoke,再改业务逻辑
46+
- option C:先清理后端历史缓存死代码
47+
48+
## Decision
49+
- chosen option:B
50+
- why it was chosen:当前最大不确定性不是“补丁怎么写”,而是“真实失败路径是否已被自动化覆盖”;先补复现场景,再把业务逻辑收口到 xterm 官方扩展点,风险最低
51+
52+
## Consequences
53+
- positive effects
54+
- 避免再次出现“单测和单 terminal smoke 通过,但用户真实场景仍失败”
55+
- 后续 wheel/IME 重构有更稳定的回归闭环
56+
- negative effects
57+
- 比直接 patch 多一个前置任务包,短期交付稍慢
58+
- follow-up work
59+
- `TASK-TERM-003`
60+
- `TASK-TERM-004`
61+
62+
## Review Date
63+
- 2026-04-22

.harness/feature-list.json

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{
33
"id": "TASK-001",
44
"title": "飞书入口增强与 Harness 初始化",
5-
"status": "blocked",
5+
"status": "passing",
66
"priority": "P1",
77
"scope": [
88
"emain/emain-feishu.ts",
@@ -32,5 +32,80 @@
3232
"仓库具备最小可续跑的 Harness 工件"
3333
],
3434
"notes": "代码实现与 verify 已完成;剩余阻塞是运行态 smoke 依赖账号态,且本地启动环境还缺少可用的 WCLOUD_ENDPOINT。"
35+
},
36+
{
37+
"id": "TASK-TERM-001",
38+
"title": "终端滚轮与输入法位置专项修复",
39+
"status": "passing",
40+
"priority": "P1",
41+
"scope": [
42+
"frontend/app/view/term/termwrap.ts",
43+
"frontend/app/view/term/termutil.ts",
44+
"frontend/app/view/term/fitaddon.ts",
45+
"frontend/app/view/term/osc-handlers.ts",
46+
".harness/*"
47+
],
48+
"acceptance": [
49+
"普通终端历史可以用鼠标滚轮上下滚动",
50+
"Codex/Agent 会话中 normal buffer 与 alternate buffer 的滚轮行为符合预期",
51+
"中文输入法候选框/组合文本不再出现在左上角或历史 viewport 位置",
52+
"调整窗口大小或从历史恢复后,当前输入位置和可视 viewport 不错位",
53+
"vitest、scripts/verify.ps1 与 electron-builder 验证完成或明确记录阻塞"
54+
],
55+
"notes": "已回到 upstream/main termwrap 官方主线,只保留 termsize 强制同步、Codex/Agent IME 锚点和 normal buffer 滚轮兜底。最新修正后,wheel 兜底改为 bubble 阶段,避免抢占 xterm 内部 xterm-scrollable-element;IME 改为跟随当前 cursor 行列,而不是固定中线。按用户最新要求,前端已彻底停用 terminal 历史缓存/恢复链路:不再读取 cache:term:full、不再调用 SaveTerminalState,只保留当前会话 term blockfile 的实时 append;为避免初始化阶段丢数据,新增 heldData 顺序回放。"
56+
},
57+
{
58+
"id": "TASK-TERM-002",
59+
"title": "终端回归 Smoke 自动化闭环",
60+
"status": "passing",
61+
"priority": "P1",
62+
"scope": [
63+
"scripts/smoke-terminal.ps1",
64+
".harness/*"
65+
],
66+
"acceptance": [
67+
"脚本可启动最新 make\\win-unpacked\\Wave.exe 并连接 Electron CDP",
68+
"脚本可静态确认 termwrap.ts 不再包含历史缓存/恢复入口",
69+
"脚本可运行态确认 window.term 可达、历史方法为空、serializeAddon 不存在",
70+
"脚本可验证 wheel 改变 viewportY,IME textarea 与 cursor 对齐",
71+
"脚本输出 JSON 和截图,失败时给出明确原因"
72+
],
73+
"notes": "已新增 scripts/smoke-terminal.ps1。首次 smoke 抓到旧 win-unpacked bundle 仍暴露历史方法;重跑 scripts/verify.ps1 与 electron-builder --win dir 后通过。最新结果 JSON 为 D:\\files\\AI_output\\waveterm-terminal-smoke\\terminal-smoke-20260421-162451.json,截图为 D:\\files\\AI_output\\waveterm-terminal-smoke\\terminal-smoke-20260421-162451.png;wheel viewportY 127->87,IME topDelta/leftDelta 均为 0。"
74+
},
75+
{
76+
"id": "TASK-TERM-003",
77+
"title": "多终端焦点与真实事件路径 Smoke 补强",
78+
"status": "in_progress",
79+
"priority": "P1",
80+
"scope": [
81+
"scripts/smoke-terminal.ps1",
82+
".harness/*"
83+
],
84+
"acceptance": [
85+
"脚本可识别多 terminal block,而不是只验证 window.term",
86+
"脚本可断言 active/focused terminal 与 IME helper 所属 terminal 一致",
87+
"脚本可区分真实外层 wheel 路径与内部 scrollableElement 路径结果",
88+
"split-pane 场景失败时可明确标出焦点归属问题或 wheel 路由问题"
89+
],
90+
"notes": "来自批准的方向 A。该任务只扩展 smoke 覆盖范围,不改业务逻辑,目标是稳定复现用户最新截图中的多终端输入框错位与滚轮回归。"
91+
},
92+
{
93+
"id": "TASK-TERM-004",
94+
"title": "将 Wheel / IME 修复收口到 xterm 官方扩展点与焦点归属",
95+
"status": "in_progress",
96+
"priority": "P1",
97+
"scope": [
98+
"frontend/app/view/term/termwrap.ts",
99+
"frontend/app/view/term/termutil.ts",
100+
"frontend/app/view/term/termutil.test.ts",
101+
".harness/*"
102+
],
103+
"acceptance": [
104+
"使用 xterm 官方 wheel hook 处理 normal buffer 滚轮兜底",
105+
"只有 active terminal 可重定位 IME helper",
106+
"多 terminal split-pane 场景中输入框不串位,滚轮不串 terminal",
107+
"vitest、verify、smoke、electron-builder 验证通过"
108+
],
109+
"notes": "这是批准方向 A 的第二个闭环任务;需在 TASK-TERM-003 给出更真实复现证据后再改业务逻辑。"
35110
}
36111
]

.harness/opportunities.json

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
{
2+
"opportunities": [
3+
{
4+
"id": "OPP-TERM-002",
5+
"title": "把滚轮与 IME 兜底收敛到 xterm 官方扩展点",
6+
"problem": "当前终端滚轮与 IME 修复仍在 `termwrap.ts` 里通过外层 DOM wheel listener、正则识别 Agent TUI、直接改 textarea/composition-view style 实现,容易与 xterm 内部 viewport、mouse tracking、composition helper 生命周期冲突。",
7+
"evidence": [
8+
{
9+
"source": "xterm.js Terminal API",
10+
"url": "https://xtermjs.org/docs/api/terminal/classes/terminal/#attachcustomwheeleventhandler",
11+
"strength": "strong",
12+
"note": "xterm.js 已提供 `attachCustomWheelEventHandler`,用于让 embedder 决定是否继续处理 terminal wheel event。"
13+
},
14+
{
15+
"source": "xterm.js 6.0.0 release",
16+
"url": "https://github.com/xtermjs/xterm.js/releases/tag/6.0.0",
17+
"strength": "strong",
18+
"note": "xterm.js 6.0.0 集成 VS Code scrollbar,明确说明 viewport/scrollbar 行为有重大变化。"
19+
},
20+
{
21+
"source": "xterm.js issue #5734",
22+
"url": "https://github.com/xtermjs/xterm.js/issues/5734",
23+
"strength": "strong",
24+
"note": "xterm.js 6.0.0 + Electron + Claude/AI CLI 下中文 IME 候选窗定位错误,与当前问题高度相似。"
25+
},
26+
{
27+
"source": "xterm.js PR #5759",
28+
"url": "https://github.com/xtermjs/xterm.js/pull/5759",
29+
"strength": "strong",
30+
"note": "上游已合并 IME 方向修复:compositionstart 前同步 textarea,compositionstart 后立即更新 composition element。"
31+
},
32+
{
33+
"source": "本地代码审查",
34+
"url": "frontend/app/view/term/termwrap.ts",
35+
"strength": "strong",
36+
"note": "`installNormalBufferWheelScrollback()` 当前在 bubble 阶段先判断 `event.defaultPrevented`;如果 xterm 内部已先消费 wheel,Wave 兜底不会运行。"
37+
},
38+
{
39+
"source": "用户 2026-04-21 截图反馈",
40+
"url": ".harness/progress.md",
41+
"strength": "strong",
42+
"note": "最新多终端截图显示真实 split-pane 场景里输入框仍错位、滚轮又失效,说明当前修复在真实焦点切换场景下仍不稳定。"
43+
}
44+
],
45+
"candidate_solutions": [
46+
"用 `terminal.attachCustomWheelEventHandler` 替代外层 DOM wheel listener,在 xterm 默认处理前决定 normal-buffer wheel 是否转为 scrollback",
47+
"给 IME 兜底增加 focused terminal ownership,只允许当前真实焦点 terminal 改 helper textarea/composition-view 位置",
48+
"在 xterm compositionstart / focus 生命周期附近做最小 IME 同步,参考 xterm PR #5759,而不是持续 onRender 改 style",
49+
"把 Agent/Codex 检测从可见文本正则降级为 fallback,只在 xterm 原生同步失败时启用"
50+
],
51+
"reach": 5,
52+
"impact": 5,
53+
"confidence": "high",
54+
"effort": 3,
55+
"architecture_fit": "high",
56+
"strategic_fit": "high",
57+
"risk_penalty": "medium",
58+
"maintenance_penalty": "low",
59+
"status": "approved"
60+
},
61+
{
62+
"id": "OPP-TERM-003",
63+
"title": "建立终端回归 smoke 自动化闭环",
64+
"problem": "多轮修复反复出现“代码已改但用户测到旧包/旧实例/无法确认真实滚轮和 IME”的问题,当前验证主要靠人工和临时 CDP 命令,无法稳定防止回归。",
65+
"evidence": [
66+
{
67+
"source": "本地 .harness/progress.md",
68+
"url": ".harness/progress.md",
69+
"strength": "strong",
70+
"note": "已记录多次产物未刷新、CDP 截图不稳定、真实系统 IME 难自动化的问题。"
71+
},
72+
{
73+
"source": "Microsoft IME guidance",
74+
"url": "https://learn.microsoft.com/en-us/windows/apps/develop/input/input-method-editors",
75+
"strength": "medium",
76+
"note": "Microsoft 建议有文本输入的应用对 IME 端到端体验做测试,并修复候选窗遮挡等问题。"
77+
},
78+
{
79+
"source": "TextBox DesiredCandidateWindowAlignment",
80+
"url": "https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.textbox.desiredcandidatewindowalignment?view=windows-app-sdk-1.8",
81+
"strength": "medium",
82+
"note": "Windows 输入体验默认硬件键盘下 IME 跟随 cursor;这可作为 Wave 的 smoke 断言目标。"
83+
}
84+
],
85+
"candidate_solutions": [
86+
"新增 `scripts/smoke-terminal.ps1`:关闭旧进程、启动最新 win-unpacked、连接 CDP、断言运行路径/版本/行列/无历史恢复",
87+
"用 DOM/JS 断言 xterm `viewportY` 变化、helper textarea 与 cursor 对齐,而不是依赖截图",
88+
"把完整分发包时间戳、SHA256、运行态 `location.href` 写入 smoke 输出,避免误测旧包"
89+
],
90+
"reach": 5,
91+
"impact": 4,
92+
"confidence": "high",
93+
"effort": 2,
94+
"architecture_fit": "high",
95+
"strategic_fit": "high",
96+
"risk_penalty": "low",
97+
"maintenance_penalty": "low",
98+
"status": "implemented"
99+
},
100+
{
101+
"id": "OPP-TERM-005",
102+
"title": "补强多终端焦点与真实事件路径 smoke",
103+
"problem": "当前 smoke 已能证明最新包、历史链路移除、单终端 DOM 级 wheel/IME 断言可通过,但它仍通过 `window.term` 单实例、强制 `shouldAnchorImeForAgentTui=()=>true` 和直接向 `.xterm-scrollable-element` 派发 `WheelEvent` 的方式验证,无法覆盖真实多终端 split-pane、焦点切换和 OS 事件路径。",
104+
"evidence": [
105+
{
106+
"source": "本地 smoke 脚本",
107+
"url": "scripts/smoke-terminal.ps1",
108+
"strength": "strong",
109+
"note": "当前 smoke 只验证单个 `window.term`,并强制调用 `syncImePositionForAgentTui()`,没有验证真实 focus owner。"
110+
},
111+
{
112+
"source": "用户 2026-04-21 截图反馈",
113+
"url": ".harness/progress.md",
114+
"strength": "strong",
115+
"note": "用户最新截图显示脚本通过后,真实 UI 中滚轮和输入框问题仍会复现,说明 smoke 覆盖范围不足。"
116+
}
117+
],
118+
"candidate_solutions": [
119+
"让 smoke 先枚举页面上的多个 terminal block,选择当前可见且聚焦的 terminal,而不是默认 `window.term`",
120+
"增加 split-pane 场景断言:上方 Codex 终端与下方 PowerShell 终端同时存在时,只有 active terminal 的 textarea/composition-view 允许被改位置",
121+
"把 wheel 断言从内部 scrollableElement 直派发升级为对 terminal connectElem/外层容器派发,尽量贴近真实用户路径"
122+
],
123+
"reach": 4,
124+
"impact": 4,
125+
"confidence": "high",
126+
"effort": 2,
127+
"architecture_fit": "high",
128+
"strategic_fit": "high",
129+
"risk_penalty": "low",
130+
"maintenance_penalty": "low",
131+
"status": "approved"
132+
},
133+
{
134+
"id": "OPP-TERM-004",
135+
"title": "清理后端未使用的终端历史缓存入口",
136+
"problem": "前端已经停用 terminal 历史缓存/恢复,但后端仍保留 `SaveTerminalState`、`BlockFile_Cache` 等入口;未来维护者可能误以为历史缓存仍是受支持能力并重新接回。",
137+
"evidence": [
138+
{
139+
"source": "Wave upstream termwrap",
140+
"url": "https://raw.githubusercontent.com/wavetermdev/waveterm/main/frontend/app/view/term/termwrap.ts",
141+
"strength": "medium",
142+
"note": "上游当前仍包含 `cache:term:full` 与 `SaveTerminalState` 链路;本 fork 已按用户要求偏离上游。"
143+
},
144+
{
145+
"source": "本地代码检索",
146+
"url": "frontend/app/view/term/termwrap.ts",
147+
"strength": "strong",
148+
"note": "本地前端已不存在 `cache:term:full`、`SaveTerminalState`、`SerializeAddon` 引用。"
149+
}
150+
],
151+
"candidate_solutions": [
152+
"删除或标记废弃后端 `SaveTerminalState` 与 `BlockFile_Cache`,并更新生成类型",
153+
"保留 API 但改名/注释为 deprecated,避免误接回前端",
154+
"把历史缓存作为显式 feature flag,默认关闭"
155+
],
156+
"reach": 3,
157+
"impact": 3,
158+
"confidence": "medium",
159+
"effort": 3,
160+
"architecture_fit": "medium",
161+
"strategic_fit": "medium",
162+
"risk_penalty": "medium",
163+
"maintenance_penalty": "medium",
164+
"status": "candidate"
165+
}
166+
]
167+
}

0 commit comments

Comments
 (0)