Skip to content

feat(web-terminal): mobile UX pass — input bar, font sizing, card terminal deep-link - #884

Open
maxliux5 wants to merge 7 commits into
deepcoldy:masterfrom
maxliux5:feat/mobile-terminal-woof-ux
Open

feat(web-terminal): mobile UX pass — input bar, font sizing, card terminal deep-link#884
maxliux5 wants to merge 7 commits into
deepcoldy:masterfrom
maxliux5:feat/mobile-terminal-woof-ux

Conversation

@maxliux5

Copy link
Copy Markdown

背景

手机上 botmux 终端页体验差:只有浮动快捷键按钮,没有能打长文本的输入框;字号写死 14px 不随视口调整;v3 进度卡片只能跳 SPA(手机上不可用)。参考 woof 的手机终端体验做三步改造。

改动

第一步:手机输入条(worker.ts `getTerminalHtml`)

  • 底部常驻输入条:快捷键行(Ctrl+C / Esc / Tab / Ctrl+D)+ 输入行(模式切换 / textarea / ↑ / ⌫ / ↓ / ↵ / 发送)
  • 双模式:缓冲(整段发送)和实时(逐键 diff 成 ANSI 序列,支持中文输入法,移植 woof `mobile_live_input` 的 grapheme 感知镜像算法)
  • 接入现有 `ws {type:'input'}` 通道,只读会话不显示
  • 修复:模板字符串里 `\n` / `\x1b` 等单反斜杠转义被 TS 模板吞掉,导致浏览器 JS 语法错误、整个 script 块失效——已全部双写

第二步:字号自适应 + 飞书 webview

  • `_fontSizeForViewport()`:≤600px 触屏视口用 16px,其余 14px;resize 时联动 FitAddon 重算列数
  • `lk-config` meta 隐藏飞书 webview 底部导航栏;`format-detection` 防电话号码识别

第三步:v3 卡片终端直达

  • `V3ProgressView` 新增可选 `terminal{sessionId,webPort}`,从最新 `nodeSessionReady` 事件提取(仅外层节点)
  • 卡片新增「终端(手机可看)」按钮:中心 HTTPS 走 `/s/{id}` 代理,LAN 直连 worker 端口;无可达 URL 时不渲染死链

验证

  • `tsc --noEmit` 通过
  • 127 个相关单测通过(含新增 6 个:投影终端提取 3 个 + 卡片终端按钮 3 个)
  • 390×844 headless Chromium 实测:页面无 JS 错误,输入条可见、模式切换/输入/发送正常,底部定位正确

截图

390×844 实测:底部输入条(Ctrl+C/Esc/Tab/Ctrl+D + 缓冲/输入/↑/⌫/↓/↵/发送),与 woof 手机端一致。

🤖 Generated with Claude Code

liuhuadong.hans and others added 3 commits August 15, 2026 00:41
Ports woof's mobile terminal UX to botmux's live terminal page
(worker.ts getTerminalHtml):

- persistent bottom input bar with buffer/live modes, grapheme-aware
  edit-sequence mirror for Chinese IME, shortcut keys (Ctrl+C/Esc/Tab/
  Ctrl+D), arrows/backspace/enter/send — wired to the existing
  ws {type:'input'} channel
- viewport-aware font sizing (16px on <=600px touch viewports, 14px
  elsewhere) re-applied on resize alongside fit
- Feishu webview lk-config meta (hide bottom nav bar) and
  telephone=no format-detection

Co-Authored-By: Claude <noreply@anthropic.com>
The mobile input bar JS lives inside a TS template literal, so single
backslashes (\n, \r, \t, \x1b, …) were interpreted by the template
and emitted as raw control characters / newlines in the rendered HTML —
a 'Invalid or unexpected token' SyntaxError that killed the whole
<script> block (isTouch/hasToken undefined, bar hidden).

Verified at 390x844 in headless Chromium: page loads with no errors,
body gets 'touch has-token', the bar renders at the bottom, mode toggle
(buffer/live) and input work.

Co-Authored-By: Claude <noreply@anthropic.com>
Step 3 of the mobile UX pass: the v3 progress card now surfaces a
「终端(手机可看)」button that opens the live terminal page directly,
skipping the SPA round-trip that was unusable on a phone.

- projection: V3ProgressView gains optional terminal{sessionId,webPort},
  populated from the latest nodeSessionReady event (outer nodes only)
- dashboard-url: buildV3TerminalUrl() — /s/<id> proxy on the central
  HTTPS platform, direct worker webPort on LAN
- card: renders the terminal button ahead of Web 详情 when a live
  session exists; no dead link when there is no reachable URL

Co-Authored-By: Claude <noreply@anthropic.com>
@maxliux5
maxliux5 requested a review from deepcoldy as a code owner August 14, 2026 17:05
liuhuadong.hans and others added 4 commits August 15, 2026 08:00
…ith reference

- Hide the right-edge floating shortcut toolbar on touch devices — the
  bottom input bar replaces it (user request: 右边悬浮不需要)
- Align the bottom bar with the reference app: pill-shaped keys row
  (Paste / Ctrl+C / Esc / Tab / Enter / Shift+Tab) + rounded input row
  with 'Type a command...' placeholder, mode toggle, arrows, backspace,
  send
- Add Paste (clipboard read), Enter (\r), Shift+Tab (\x1b[Z) handlers;
  drop Ctrl+D and the standalone Enter key (Enter is in the keys row now)
- Add a 10s WS open timeout so a hung connect retries instead of leaving
  the user on 'connecting' forever

Co-Authored-By: Claude <noreply@anthropic.com>
The live-mode hint (实时输入 · 点击显示键盘) was absolutely positioned
against the whole input row, so it rendered over the mode toggle and
outside the textarea. Wrap the textarea + hint in a relative-positioned
container so inset:0 covers only the input field.

Co-Authored-By: Claude <noreply@anthropic.com>
In live mode the textarea text is transparent (keystrokes go straight
to the terminal), but the ::placeholder stayed visible and overlapped
the 实时输入 hint. Make the placeholder transparent in live mode too.

Co-Authored-By: Claude <noreply@anthropic.com>
User feedback: 16-19px pill / 50% circle radius is too round. Use 8-10px
subtle rounding matching the reference app.

Co-Authored-By: Claude <noreply@anthropic.com>
@maxliux5

Copy link
Copy Markdown
Author

手机端实测截图(390×844):

mobile terminal

  • 底部操作栏:Paste / Ctrl+C / Esc / Tab / Enter / Shift+Tab + 输入框 + 方向键 + 发送
  • 右侧悬浮工具栏已隐藏
  • 已连接(connected),终端正常渲染

@deepcoldy

Copy link
Copy Markdown
Owner

感谢这个 PR,手机端终端体验的改造方向很好 —— 对标 woof 补齐移动输入条、只读会话不显示输入条、模板字面量转义的修复都做得很到位。经过一轮代码审查 + 真机运行态验证(390×844 触屏视口、实际 WS),发现几个需要在合并前处理的问题,按严重程度列一下:

🔴 P0(阻塞):v3 卡片「终端(手机可看)」按钮在两条部署路径下都打不开

  • 远程 / 中心平台buildV3TerminalUrl() 会无条件生成 /s/<sessionId>,但 v3 ephemeral worker 是直接 fork 的、其 synthetic sessionId 从不注册进 activeSessions / dashboard aggregator 的 session 表,代理 resolvePort 查不到端口 → 最终 404。
  • LAN 自建:生成的是裸 http://host:<webPort>,不带 token / viewToken;而 worker 的读门禁(resolveTerminalAccesshasRead = hasWrite || viewTokenMatches || platformProxied)要求 write token、view token 或平台代理 cookie。飞书 webview 首次打开这三者都没有 → 403(现有 worker-terminal-read-auth.integration.test.ts 也断言裸 / = 403)。
  • 根因还更深一层:nodeSessionReady 事件契约里只持久化 {sessionId, webPort}shared-node-runtime.ts 出于安全考虑主动丢掉了 write token,且契约中根本没有 viewToken 字段 —— 所以构造 URL 时手里没有任何可用的读凭证。

因此仅"把 ephemeral session 注册进 activeSessions"只能修远程 404,LAN 侧还需要一个安全的只读 capability(派生 viewToken),或统一走已认证的前门。建议为 v3 ephemeral 终端设计一个完整的「可路由 + 只读鉴权 + 存活期」契约,并用真实 HTTP 200/403/404 集成测试验证,而不是只断言 URL 字符串。

🔴 P1(阻塞):节点结束后卡片仍保留陈旧死链

progress-projection.ts 会永久取最后一次 nodeSessionReady,不随 nodeSucceeded / nodeFailed / run 终态清理;卡片也只看 URL 是否存在、不看节点状态。ephemeral worker 跑完即 teardown,但完成后的卡片仍然展示这个按钮 —— 而进度卡往往是完成后才回看去点。建议节点进入终态时立即清掉 terminal capability,卡片仅在 running 且 worker 存活时才展示入口。

🟠 P2(建议修):移动输入条遮住 xterm 底部活动区

输入条是 position:fixed、不透明、脱离布局流,但 #terminal 仍按整视口高度 fit。注释里提到的 --mobile-bar-h 让位机制全仓只有注释、从未写入或消费。390×844 实测:bar 高 104px、与 xterm 重叠 96px,遮住约 5 行,活动 prompt 正好落在被盖住的区域。建议让 mobile bar 参与布局(给 #terminalpadding-bottom),或测量其真实高度后再调用 FitAddon,并补一条 390×844 的几何断言。

🟡 P3(低优先级):WS 断开时发送会静默吞掉输入

sendInput() 在 socket 非 OPEN 时返回 false 什么都没发,但 sendBuffered() / sendLiveCommit() 无视返回值、仍然清空 textarea。运行态复现:readyState=0、status=disconnected 时点发送,完整命令会变成空串且并未发出。建议 sendInput() 失败时保留 textarea,并给一个断线提示或临时禁用发送。

其它

  • 建议测试里显式 mock / 隔离 remotePublicBase(),否则开发机的平台绑定配置会污染断言(新增的 card/projection 测试在带平台绑定的环境下会有 2 条失败,隔离后是绿的)。
  • PR 描述里的「截图」小节目前没有实际图片附件,修复后建议补上真机截图。

影响面:P0/P1 局限在 v3 workflow 卡片 + ephemeral session;P2/P3 位于共用的 worker.ts Web 终端,会影响所有 CLI / 后端的可写触屏会话,改动时请留意回归面。

方向没问题,主要是完成度和 v3 终端契约这块。以上问题处理后我们会再看一轮。辛苦了,期待更新 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants