8.6 KiB
8.6 KiB
问题闭环记录(Question Solved)
本文档记录已确认问题的现象、根因、修复与回归要点,目标是避免重复踩坑。
目录
- 2026-03-02:第三方输入法导致输入重复
- 2026-03-02:点击粘贴触发 BAD_REQUEST / invalid_union
- 2026-03-02:多行粘贴被当作回车执行
- 2026-03-03:回车后首字母重复显示(
aasdf) - 通用回归清单
2026-03-02:第三方输入法导致输入重复(jk -> jkjk,测试 -> 测试测试)
现象
- 第三方输入法下输入
jk显示jkjk,输入测试显示测试测试。 - 原生输入法或英文输入模式正常。
根因
- 同一文本同时走了
keydown和input两条发送路径,导致双发。 - 第三方输入法会在
keydown阶段给出整段文本(key="测试"/key="jk"),旧逻辑误判后直接发送。
修复
- 文件:
apps/web/src/terminal/components/TerminalInputBar.vue - 策略:
keydown只处理控制键/功能键。- 文本输入统一走
input通道,保证单通道发送。 KEYDOWN_DIRECT_KEYS作为白名单,非白名单文本直接跳过。
验证
ls、jk、测试均不重复。Enter、Ctrl+C等控制键行为保持正常。
自动化测试
- 文件:
apps/web/src/terminal/input/inputPolicy.test.ts - 覆盖文本键、功能键、组合键判定。
2026-03-02:点击粘贴触发 BAD_REQUEST / invalid_union
现象
- 点击工具栏“粘贴”后出现 error,连接异常或重连。
- 日志出现
ws:error_frame code=BAD_REQUEST,消息含invalid_union。
根因
- 网关
stdin入参 schema 在现网存在差异;paste携带meta会在部分环境触发 union 校验失败。
修复
- 文件:
apps/web/src/terminal/stores/useTerminalStore.ts - 策略:
paste分片发送时不再携带meta。keyboard/assist保持原有meta.source。- 粘贴分片大小从
512调整为256,降低单帧风险。
验证
- 粘贴日志出现
wireMetaSource: "none"。 - 不再出现
BAD_REQUEST invalid_union。
2026-03-02:多行粘贴被当作回车执行
现象
- 复制多行文本后粘贴进 shell,会被当作多次回车,导致命令提前执行。
根因
- 旧粘贴逻辑将换行统一转为
\r(回车语义),不符合“多行粘贴应作为文本块输入”的预期。
修复
- 文件:
packages/terminal-core/src/input/inputBridge.tsapps/web/src/terminal/components/TerminalInputBar.vueapps/web/src/terminal/TerminalPage.vue
- 策略:
mapPaste将换行统一归一为\n,不再转成\r。- Web 两条粘贴链路(原生
paste、工具栏“粘贴”)统一走InputBridge.mapPaste()。 - 开启 bracketed paste(
\x1b[200~... \x1b[201~),避免被解释为逐行回车执行。
验证
- 粘贴多行文本默认不自动逐行执行。
- 两条粘贴入口行为一致。
自动化测试
- 文件:
packages/terminal-core/src/input/inputBridge.test.ts - 覆盖:
CRLF/CR/LF统一归一为LF。- bracketed paste 包裹序列正确。
2026-03-02:软键盘弹出后光标行定位不准与收回后页面跳动问题
现象
- 终端在 iOS 移动设备上点击输入区弹出软键盘时,期望将光标(提示符行)定位到屏幕的距顶 1/4 处,但实际经常定位到键盘顶部或在屏幕中部乱跳。
- 软键盘收回时,期望恢复到键盘弹出前的状态,但页面内容经常回跳或者复位到屏幕底部。
- 终端输入文字(
autoFollow=true)时,滚动条经常突然跳到屏幕中部,无法锁定在最底部。
根因
- 行高计算不准确: 在
textareaRenderer的旧版计算方式中,利用scrollHeight / totalLines试图估算出单行的lineHeight。但这在遇到换行文字(如超宽命令占据多行 HTML)或者终端末尾包含空白补齐的 Null Lines 时,按比例换算的offsetPx数值偏差严重。 - 滚动到 scrollHeight 的陷阱:
renderSnapshot及followBottom在自动跟随终端内容时,直接触发了this.outputEl.scrollTop = this.outputEl.scrollHeight。但是xterm的snapshot.lines通常包含底部的很多未使用的纯空行(通常是为了填满预设高度而预留的。例如在你的日志中底部留有二十几行空行 Padding)。直接将scrollTop置为最大,会导致包含真实光标的行被视觉上挤到了屏幕上方,呈现出"跳到了屏幕中部"的怪异现象。 - iOS 异步布局时序缺陷:
KeyboardAdjustController中,iOS 上软键盘收回(visualViewport.height恢复)早于实际的 DOMclientHeight恢复更新,用固定的时间延迟去读取和操作会导致布局错误。同时 iOS 原生的scroll行为是异步派发的,和程序的滚动重置产生了干扰覆盖(程序刚设回了snapshot高度,又被键盘底层收回的scroll冲成了底部)。
修复
- 文件:
apps/web/src/terminal/renderer/textareaRenderer.tsapps/web/src/terminal/input/keyboardAdjustController.ts
- 策略:
- 放弃比例估算,直接获取光标 DOM 节点: 在
textareaRenderer渲染的光标附带有专门的类名tc-cursor-cell,此时我们设置外部包裹元素为position: relative,并直接借助const cursorSpan = this.outputEl.querySelector('.tc-cursor-cell')拿到精确的offsetTop来计算光标此时刻在纵向的确切位置,确保毫无误差。 - 将光标置于可视区底部,拒绝 scrollHeight: 摒弃粗暴地拉向极限
scrollHeight的做法,改为使用光标偏移量算出安全的底端视区:let targetScrollTop = cursorBottom - clientH + 20并将其作为真实的自动跟随坐标,将跟随对象从"整个 Buffer 的底部"变更为"真正光标所处的区域"。 - Polling 等待基线恢复,长短结合阻塞 scroll 事件干扰: 在
keyboardAdjustController.ts中针对键盘关闭的 Layout 响应,引入requestAnimationFrame及定时检测(Polling),不断比对clientHeight是否已经长回键盘未弹出前的高度,只有确认恢复或者超市才恢复预存前的scrollTop快照,借此解决 iOS 下因为布局慢导致的跳闪。并通过定时锁去屏蔽由于视差调整造成的自动autofollow开关变动。
- 放弃比例估算,直接获取光标 DOM 节点: 在
验证
- 点击 iOS 输入区弹出软键盘,终端精确将当前焦点处于 1/4 屏幕处。
- 收起键盘后,稳定跳掉之前的阅读点(快照复原准确)。
- 长按或者多次键盘输入不再跳动到屏幕中部。
2026-03-03:回车后首字母重复显示(aasdf)
现象
- 输入英文命令(如
asdf)后回车,终端提示符行显示aasdf,但服务端实际执行的是asdf(例如返回zsh: command not found: asdf)。 - 问题具备偶发性,通常出现在回显分片较细时。
根因
- 回显数据分帧到达,典型序列为:
"a"、"\b as"、"df..."。 - 旧实现按帧即时渲染,第一帧字符已经落盘;第二帧开头的
\b(Backspace)无法回退上一帧已写入内容,导致视觉上保留多余首字母。 - 同时存在
\r\r\n与 ANSI 控制序列交织,如果不按终端语义处理,也会放大错位和空行现象。
修复
- 文件:
demo/main.js - 策略:
- 在
renderAnsiToHtml中补齐控制字符语义:处理\x08(退格)、\r\n(归一为换行)和裸\r(回到当前行首覆盖)。 - 增加短窗口流式合帧(
STREAM_BATCH_MS):将连续stdout/stderr分片合并后再渲染,确保跨帧\b/\r能作用于同一文本缓冲。 - 保留原有“本地回显清理”策略(
clearLast()),避免与远端回显叠加。
- 在
验证
- 复现输入
asdf回车,不再出现aasdf。 - 日志可见发送仍为
asdf\n,错误回显保持command not found: asdf,与服务端行为一致。 - 其它输出路径(
stdout/stderr、ANSI 颜色)不回归。
通用回归清单(每次改输入/粘贴链路都执行)
- 英文输入
ls不重复。 - 第三方输入法输入
jk、测试不重复。 Enter、Ctrl+C、方向键功能正常。- 工具栏粘贴与原生粘贴行为一致。
- 长文本粘贴不触发网关
BAD_REQUEST invalid_union。 - 多行粘贴不应被当作多次回车直接执行。