# 移动端终端自定义虚拟按键会重新唤起系统软键盘

## 标题

`fix(web): 移动端终端自定义虚拟按键不应重新唤起系统软键盘`

## 问题描述

在 `coder-studio` 的移动端 terminal 场景下，用户先让终端进入可输入状态，再手动收起系统软键盘，随后点击终端底部我们自己实现的虚拟按键行，例如 `Ctrl`、`Shift`、`Esc`、`Tab`、方向键或 `Enter`，系统软键盘会再次被唤起。

这排按键的设计目标是补足移动端终端缺失的控制键，不应该在用户已经手动收起系统软键盘后，又把键盘重新拉起。

## 复现步骤

1. 在手机或平板上打开 `coder-studio` 的 terminal。
2. 点击 terminal，让系统软键盘弹出。
3. 手动收起系统软键盘。
4. 点击 terminal 底部自定义虚拟按键行中的任意键，例如 `Ctrl`、`Shift`、`Esc` 或方向键。
5. 观察系统软键盘重新弹出。

## 预期行为

- 当系统软键盘已经打开时，点击自定义虚拟按键不应强制把它收掉。
- 当系统软键盘已经被用户手动收起时，点击自定义虚拟按键也不应再次把它唤起。
- 自定义虚拟按键应只向终端发送控制输入，而不改变当前系统软键盘的可见状态。

## 实际行为

用户手动收起系统软键盘后，点击自定义虚拟按键仍会导致系统软键盘再次弹出。

## 已确认事实

- 该问题涉及的是 terminal 内部我们自己实现的移动端虚拟按键行，不是系统软键盘本身。
- 自定义虚拟按键的数据链路是：
  - `MobileTerminalInputBar`
  - `handleSoftKeyPress`
  - `handleInput`
  - `wsClient.sendTerminalInput`
- 自定义虚拟按键并不是通过 `xterm.onData(...)` 触发输入；它们不会先走 xterm 的浏览器键盘事件采集路径。
- `xterm` 在浏览器里依赖隐藏的 `textarea`（`.xterm-helper-textarea`）承接键盘、IME 与移动端输入。
- `xterm` 自身维护这个隐藏 `textarea` 的焦点状态，并通过 `focus()` / `blur()` 管理终端输入上下文。
- 在移动端浏览器里，用户“收起软键盘”不一定等价于输入元素真正 `blur`；隐藏 `textarea` 可能仍保持焦点。
- 当前自定义虚拟按键在 `pointerdown` 上使用了 `preventDefault()`，因此按钮本身不会接管焦点。
- 当按钮不接管焦点，而 `xterm` 的隐藏 `textarea` 仍处于焦点上下文时，浏览器可能在下一次触摸交互时重新恢复系统软键盘。

## 当前判断

问题更像是：

- `xterm` 的隐藏 `textarea` 在用户收起软键盘后仍保持焦点或可恢复的输入上下文
- 自定义虚拟按键由于 `preventDefault()` 没有改变当前焦点归属
- 浏览器在后续触摸时将该焦点中的文本输入上下文重新激活，于是系统软键盘再次弹出

也就是说，根因不在“自定义虚拟按键错误地走进了 xterm 的 `onData`”，而在 `xterm` 隐藏输入层的焦点生命周期与移动端浏览器软键盘策略之间的耦合。

## 已排除方向

- 不是自定义虚拟按键通过 `xterm.onData(...)` 间接触发的输入问题。
- 不是 PTY / WebSocket 数据发送链路导致系统软键盘弹出。
- 直接对 `document.activeElement` 做全局 `blur()` 虽然能验证问题与焦点有关，但方案过粗，可能引入键盘闪烁、误伤其他输入元素等副作用，不适合作为最终修复。

## 后续排查方向

- 在真实移动端浏览器上记录以下事件时序：
  - 用户手动收起软键盘前后
  - 点击自定义虚拟按键前后
  - `document.activeElement`
  - `xterm.textarea` 的 `focus` / `blur`
  - `visualViewport.height` 变化
- 确认“软键盘收起但 `xterm.textarea` 仍保持焦点”是否稳定可复现，以及在不同浏览器上的差异。
- 评估是否需要在移动端引入更精确的终端输入状态机，例如区分：
  - 系统键盘打开
  - 系统键盘已被用户收起
  - 仅使用自定义虚拟按键输入
- 如果需要修复，优先考虑：
  - 仅围绕 `xterm.textarea` 做精确的 `focus` / `blur` 管理
  - 避免对全局 `activeElement` 做无条件处理
  - 保证“键盘开着时不强制收掉，键盘关着时不重新拉起”
