Files
terminal-lab/terminal/question-solved.md
douboer@gmail.com 3b7c1d558a first commit
2026-03-03 13:23:14 +08:00

8.6 KiB
Raw Permalink Blame History

问题闭环记录Question Solved

本文档记录已确认问题的现象、根因、修复与回归要点,目标是避免重复踩坑。

目录

  1. 2026-03-02第三方输入法导致输入重复
  2. 2026-03-02点击粘贴触发 BAD_REQUEST / invalid_union
  3. 2026-03-02多行粘贴被当作回车执行
  4. 2026-03-03回车后首字母重复显示aasdf
  5. 通用回归清单

2026-03-02第三方输入法导致输入重复jk -> jkjk测试 -> 测试测试

现象

  • 第三方输入法下输入 jk 显示 jkjk,输入 测试 显示 测试测试
  • 原生输入法或英文输入模式正常。

根因

  • 同一文本同时走了 keydowninput 两条发送路径,导致双发。
  • 第三方输入法会在 keydown 阶段给出整段文本(key="测试" / key="jk"),旧逻辑误判后直接发送。

修复

  • 文件:apps/web/src/terminal/components/TerminalInputBar.vue
  • 策略:
    1. keydown 只处理控制键/功能键。
    2. 文本输入统一走 input 通道,保证单通道发送。
    3. KEYDOWN_DIRECT_KEYS 作为白名单,非白名单文本直接跳过。

验证

  • lsjk测试 均不重复。
  • EnterCtrl+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
  • 策略:
    1. paste 分片发送时不再携带 meta
    2. keyboard/assist 保持原有 meta.source
    3. 粘贴分片大小从 512 调整为 256,降低单帧风险。

验证

  • 粘贴日志出现 wireMetaSource: "none"
  • 不再出现 BAD_REQUEST invalid_union

2026-03-02多行粘贴被当作回车执行

现象

  • 复制多行文本后粘贴进 shell会被当作多次回车导致命令提前执行。

根因

  • 旧粘贴逻辑将换行统一转为 \r(回车语义),不符合“多行粘贴应作为文本块输入”的预期。

修复

  • 文件:
    • packages/terminal-core/src/input/inputBridge.ts
    • apps/web/src/terminal/components/TerminalInputBar.vue
    • apps/web/src/terminal/TerminalPage.vue
  • 策略:
    1. mapPaste 将换行统一归一为 \n,不再转成 \r
    2. Web 两条粘贴链路(原生 paste、工具栏“粘贴”)统一走 InputBridge.mapPaste()
    3. 开启 bracketed paste\x1b[200~... \x1b[201~),避免被解释为逐行回车执行。

验证

  • 粘贴多行文本默认不自动逐行执行。
  • 两条粘贴入口行为一致。

自动化测试

  • 文件:packages/terminal-core/src/input/inputBridge.test.ts
  • 覆盖:
    1. CRLF/CR/LF 统一归一为 LF
    2. bracketed paste 包裹序列正确。

2026-03-02软键盘弹出后光标行定位不准与收回后页面跳动问题

现象

  1. 终端在 iOS 移动设备上点击输入区弹出软键盘时,期望将光标(提示符行)定位到屏幕的距顶 1/4 处,但实际经常定位到键盘顶部或在屏幕中部乱跳。
  2. 软键盘收回时,期望恢复到键盘弹出前的状态,但页面内容经常回跳或者复位到屏幕底部。
  3. 终端输入文字(autoFollow=true)时,滚动条经常突然跳到屏幕中部,无法锁定在最底部。

根因

  1. 行高计算不准确:textareaRenderer 的旧版计算方式中,利用 scrollHeight / totalLines 试图估算出单行的 lineHeight。但这在遇到换行文字(如超宽命令占据多行 HTML或者终端末尾包含空白补齐的 Null Lines 时,按比例换算的 offsetPx 数值偏差严重。
  2. 滚动到 scrollHeight 的陷阱: renderSnapshotfollowBottom 在自动跟随终端内容时,直接触发了 this.outputEl.scrollTop = this.outputEl.scrollHeight。但是 xtermsnapshot.lines 通常包含底部的很多未使用的纯空行(通常是为了填满预设高度而预留的。例如在你的日志中底部留有二十几行空行 Padding。直接将 scrollTop 置为最大,会导致包含真实光标的行被视觉上挤到了屏幕上方,呈现出"跳到了屏幕中部"的怪异现象。
  3. iOS 异步布局时序缺陷: KeyboardAdjustControlleriOS 上软键盘收回(visualViewport.height 恢复)早于实际的 DOM clientHeight 恢复更新,用固定的时间延迟去读取和操作会导致布局错误。同时 iOS 原生的 scroll 行为是异步派发的,和程序的滚动重置产生了干扰覆盖(程序刚设回了 snapshot 高度,又被键盘底层收回的 scroll 冲成了底部)。

修复

  • 文件:
    • apps/web/src/terminal/renderer/textareaRenderer.ts
    • apps/web/src/terminal/input/keyboardAdjustController.ts
  • 策略:
    1. 放弃比例估算,直接获取光标 DOM 节点:textareaRenderer 渲染的光标附带有专门的类名 tc-cursor-cell,此时我们设置外部包裹元素为 position: relative,并直接借助 const cursorSpan = this.outputEl.querySelector('.tc-cursor-cell') 拿到精确的 offsetTop 来计算光标此时刻在纵向的确切位置,确保毫无误差。
    2. 将光标置于可视区底部,拒绝 scrollHeight 摒弃粗暴地拉向极限 scrollHeight 的做法,改为使用光标偏移量算出安全的底端视区:let targetScrollTop = cursorBottom - clientH + 20 并将其作为真实的自动跟随坐标,将跟随对象从"整个 Buffer 的底部"变更为"真正光标所处的区域"。
    3. Polling 等待基线恢复,长短结合阻塞 scroll 事件干扰:keyboardAdjustController.ts 中针对键盘关闭的 Layout 响应,引入 requestAnimationFrame 及定时检测Polling不断比对 clientHeight 是否已经长回键盘未弹出前的高度,只有确认恢复或者超市才恢复预存前的 scrollTop 快照,借此解决 iOS 下因为布局慢导致的跳闪。并通过定时锁去屏蔽由于视差调整造成的自动 autofollow 开关变动。

验证

  • 点击 iOS 输入区弹出软键盘,终端精确将当前焦点处于 1/4 屏幕处。
  • 收起键盘后,稳定跳掉之前的阅读点(快照复原准确)。
  • 长按或者多次键盘输入不再跳动到屏幕中部。

2026-03-03回车后首字母重复显示aasdf

现象

  • 输入英文命令(如 asdf)后回车,终端提示符行显示 aasdf,但服务端实际执行的是 asdf(例如返回 zsh: command not found: asdf)。
  • 问题具备偶发性,通常出现在回显分片较细时。

根因

  • 回显数据分帧到达,典型序列为:"a""\b as""df..."
  • 旧实现按帧即时渲染,第一帧字符已经落盘;第二帧开头的 \bBackspace无法回退上一帧已写入内容导致视觉上保留多余首字母。
  • 同时存在 \r\r\n 与 ANSI 控制序列交织,如果不按终端语义处理,也会放大错位和空行现象。

修复

  • 文件:demo/main.js
  • 策略:
    1. renderAnsiToHtml 中补齐控制字符语义:处理 \x08(退格)、\r\n(归一为换行)和裸 \r(回到当前行首覆盖)。
    2. 增加短窗口流式合帧(STREAM_BATCH_MS):将连续 stdout/stderr 分片合并后再渲染,确保跨帧 \b/\r 能作用于同一文本缓冲。
    3. 保留原有“本地回显清理”策略(clearLast()),避免与远端回显叠加。

验证

  • 复现输入 asdf 回车,不再出现 aasdf
  • 日志可见发送仍为 asdf\n,错误回显保持 command not found: asdf,与服务端行为一致。
  • 其它输出路径(stdout/stderr、ANSI 颜色)不回归。

通用回归清单(每次改输入/粘贴链路都执行)

  1. 英文输入 ls 不重复。
  2. 第三方输入法输入 jk测试 不重复。
  3. EnterCtrl+C、方向键功能正常。
  4. 工具栏粘贴与原生粘贴行为一致。
  5. 长文本粘贴不触发网关 BAD_REQUEST invalid_union
  6. 多行粘贴不应被当作多次回车直接执行。