ワンクリックで
wterm-mobile-dev
wterm-mobile Android 终端应用开发工作流 - 基于 Capacitor + @wterm/react,含完整开发闭环
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
wterm-mobile Android 终端应用开发工作流 - 基于 Capacitor + @wterm/react,含完整开发闭环
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
terminal buffer / render / daemon mirror 真源与门禁
zterm Android 客户端开发工作流 - 基于 Capacitor + @jsonstudio/wtermmod-react,含完整开发闭环
zterm Windows Electron 客户端与 WezTerm daemon 开发闭环,覆盖共享核心边界、packaged preload、真实 Windows CDP/source-to-DOM gate 和精确资源清理。
zterm Mac 客户端开发工作流 - Electron 壳、terminal renderer、local/remote tmux、自闭环验证
| name | wterm-mobile-dev |
| description | wterm-mobile Android 终端应用开发工作流 - 基于 Capacitor + @wterm/react,含完整开发闭环 |
每次开发前必须按顺序阅读:
1. ~/.codex/AGENTS.md → 全局入口、硬护栏
2. ~/.codex/USER.md → 用户偏好(称呼 Jason)
3. coding-principals/SKILL.md → 开发方法论
4. examples/mobile/docs/spec.md → 项目范围与验收
5. examples/mobile/docs/architecture.md → 模块边界与数据流
6. examples/mobile/docs/dev-workflow.md → 执行门禁与验证
7. examples/mobile/task.md → 当前任务板
8. examples/mobile/docs/ui-slices.md → 页面切片与 ownership
9. 本 SKILL.md → 项目约束、可复用门禁
@wterm/core、@wterm/dom、@wterm/react 源码examples/ 下其他项目代码spec.md:产品范围与验收architecture.md:模块边界、数据流、ownershipdev-workflow.md:执行顺序、验证门禁、证据要求ui-slices.md:页面级切片与文件 ownershiptask.md:当前任务状态CACHE.md:本轮短期上下文MEMORY.md:长期可复用经验evidence/:截图、日志、APK、真机证据
examples/mobile/evidence/ 是本地证据仓,默认不进 Git 主线;Git 中只保留目录说明文件examples/mobile/note.md 是 agent 自己看的工作笔记,不是主真源note.md 追加流程说明Connections 页 + 终端页,不是网页式主机列表页docs/ui-slices.mdbridgeHost / bridgePort / sessionName;禁止再用 host/username 混装 server 与 tmux session 语义server + session 组合,否则多 server / 多 tmux session 场景会失真0.0.0.0:3333)bridgePort / daemon 端口 / daemon tmux session 名必须共用同一配置真源;不要在 UI、server、shell script、文案里散落硬编码cols / rows) 并渲染镜像,不能在 keyboard 显隐 / pinch / rotate 时自行 replay bufferwterm daemon start/restart/install-service 不能只看 launchd loaded;必须至少等到 daemon 端口真正监听,再允许回报 ready,避免手机首连撞启动窗口pong timeout -> close -> reconnect,server 需要 protocol ping/pong 回收僵尸 socket;不能让失联 tab 长时间占住 sessionws.onopen 必须同步发送 stream-mode,否则 active tab 会暂时退化成 idle/backfill 频率,表现为“秒级延迟”startScrollTop,横向手势再切 tab;否则会出现“不是从当前底部开始滚”的跳变bufferUpdateKind 去重算滚动锚点;inactive tab 应冻结 scroll/layout 推导,切回后只按“贴底/保留原 scrollTop”恢复CellData 真源必须包含 width(0/1/2):client 只能按远程 cell 宽度/continuation 渲染 cursor,不能再按本地字符宽度猜位置outputHistory replay;每个 session 必须常驻自己的 terminal 实例和本地 buffersendInitialSnapshot() 不能让 tmux capture-pane 异常冒泡到进程级;最多只允许日志告警 + fallback snapshotNew connection 入口必须先进入 session picker:先列历史连接,再列当前 tmux sessions,最后才是 clean session / full form+ 的长按必须复用同一个 session picker,用于 quick new tab;普通点击再回 Connections~/.wterm/config.json -> mobile.daemon.authToken,WTERM_MOBILE_AUTH_TOKEN 只作为显式 overrideauthToken,并在 websocket 连接阶段透传┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 1.规划 │───▶│ 2.开发 │───▶│ 3.测试 │───▶│ 4.提交 │───▶│ 5.沉淀 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
更新task/CACHE 读skill 运行验证 Git commit 更新skill
定义成功 最小切片 证据记录 检查清单 经验沉淀
目标: 明确任务范围、定义成功标准、冻结边界
每次任务开始前,必须先更新 examples/mobile/task.md 和 examples/mobile/CACHE.md:
## Task-XXX
- 目标:
- 成功标准:
- 验证入口:
- 范围:
- 不在范围:
- 风险:
- 证据输出位置:
| 检查项 | 必须确认 |
|---|---|
| 成功标准是否可验证? | ✅ |
| 验证入口是否明确? | ✅ |
| 是否有唯一真源? | ✅ |
| 是否只改本轮切片? | ✅ |
目标: 最小切片实现,每步可验证
1. Config Module → Host/Session 类型定义、存储方式
2. Provider/Adapter → WebSocket 消息协议
3. Minimal Debug Entry → 最小 UI 可见
4. Observability → 状态变更 event
5. Testing/Harness → 验证入口
6. Build/Install → Capacitor 配置
最小切片 = 1个文件 + 1个功能 + 1次验证
示例:
- 切片1: 创建 src/lib/types.ts → 定义 Host 类型 → tsc 编译通过
- 切片2: 创建 src/hooks/useHostStorage.ts → localStorage 存取 → 浏览器验证
- 切片3: 创建 src/components/HostList.tsx → 显示主机列表 → 浏览器查看
| 禁止 | 原因 |
|---|---|
| 一次改多个文件 | 违反最小切片原则 |
| 添加未请求的功能 | 违反 Simplicity First |
| 重构未要求的代码 | 违反 Surgical Changes |
| 修改 @wterm 核心包 | 项目约束 |
目标: 四层验证,证据记录
| 层级 | 验证内容 | 验证方式 |
|---|---|---|
| L1: Unit | 纯函数、类型、状态机 | tsc --noEmit + vitest |
| L2: Function | 模块主路径功能 | 浏览器手动验证 |
| L3: Orchestration | 跨模块推进、多 Tab | 多场景手动验证 |
| L4: Runtime | Android 运行态 | 模拟器/真机验证 |
# L1: Unit 验证
pnpm --filter @wterm/mobile type-check
pnpm --filter @wterm/mobile test # vitest 单元测试(如脚本存在)
# L2: Function 验证(本地开发)
pnpm --filter @wterm/mobile dev
# 结构验证:浏览器访问 portless 输出的 *.localhost 地址
# 真连通验证:pnpm --filter @wterm/mobile preview -- --host 127.0.0.1 --port 4173
# 手动操作:添加主机 → 连接 bridge → 验证终端显示
# L3: Orchestration 验证
# 多 Tab 操作:新建 Tab1 → 新建 Tab2 → 切换 → 关闭
# L4: Runtime Smoke(Android)
pnpm --filter @wterm/mobile build
npx cap sync android # 同步到 Android
npx cap run android # 启动模拟器/真机
每次验证后在 examples/mobile/evidence/<date-task>/ 保存:
目标: 清晰的 commit,检查清单
# Commit message 格式
<type>: <subject>
<body>
# type 范围
feat: 新功能
fix: 修复
refactor: 重构(仅限请求的重构)
docs: 文档更新
test: 测试添加/修改
chore: 配置/构建变更
# 示例
feat: 添加 HostList 组件和 useHostStorage hook
- 创建 src/lib/types.ts 定义 Host 类型
- 创建 src/hooks/useHostStorage.ts 实现本地存储
- 创建 src/components/HostList.tsx 显示主机列表
验证: pnpm --filter @wterm/mobile dev → 浏览器访问 → 添加主机成功
| 检查项 | 命令 |
|---|---|
| 类型检查通过 | pnpm --filter @wterm/mobile type-check |
| 无未使用代码 | 手动检查 |
| task.md 已更新 | git diff examples/mobile/task.md |
| CACHE/MEMORY 是否需要更新 | 检查是否有新约束 |
| SKILL.md 是否需要更新 | 检查是否有新门禁 |
目标: 新约束/经验写入 Skill
| 触发条件 | 更新内容 |
|---|---|
| 发现新的项目约束 | 写入 "禁止事项" |
| 发现新的验证入口 | 写入 "验证入口" |
| 发现反模式/坑 | 写入 "常见问题" |
| 发现可复用模式 | 写入 "最佳实践" |
每次开发前执行:
# 1. 检查上次提交状态
git log --oneline -5
# 2. 运行基础验证
pnpm --filter @wterm/mobile type-check
# 3. 本地启动验证
pnpm --filter @wterm/mobile dev
# 4. 如有 Android 项目
cd examples/mobile && npx cap run android --livereload
| 字段 | 说明 |
|---|---|
| id | UUID |
| name | 显示名称 |
| bridgeHost | IP 或 Tailscale 域名 |
| bridgePort | bridge 端口(默认由统一配置决定,当前 3333) |
| sessionName | tmux session 名 |
| authType | password / key |
| password/privateKey | 凭据(暂不加密) |
| tags | 分组标签(数组) |
| pinned | 是否置顶首页 |
| lastConnected | 最后连接时间戳 |
| autoCommand | 连接后自动执行的命令 |
| 功能 | 说明 |
|---|---|
| 位置 | 底部,手机键盘上方 |
| 基础按键 | Ctrl, Alt, Tab, ESC, 方向键 |
| 扩展按键 | F1-F12(电脑键盘模式全显示) |
| 自定义组合键 | 支持(如 Ctrl+C, Ctrl+D),可增删 |
| 预设模板 | 默认提供 Ctrl+C/D/Z |
| 拖拽排序 | 支持 |
| 存储 | 用户配置文件 + WebDAV 导入导出 |
| 功能 | 说明 |
|---|---|
| 自动连接 | 启动时自动连接上次活跃 Session |
| Tab 状态恢复 | 保存上次关闭时的 Tab 状态 |
| 快速重连 | 一键连接最近 3 个主机 |
| 自动命令 | 主机级别默认 + 连接时可临时覆盖 |
| 命令历史 | 每个 Tab 保存 host+autoCommand,WebDAV 同步 |
| 功能 | 说明 |
|---|---|
| 位置 | 顶部 |
| 显示内容 | 动态标题(来自 tmux / shell 标题),可手动重命名 |
| 重命名持久化 | 支持 |
| 最大 Tab 数 | 10 |
| 功能 | 说明 |
|---|---|
| 通知栏 | 显示每个 Tab 连接状态 |
| 自动重连 | 网络恢复后自动重连 |
| 重连次数 | 可配置,默认 3 次 |
| 心跳间隔 | 30 秒 |
| 功能 | 说明 |
|---|---|
| Tab 状态保存 | 上次关闭时的 Tab 配置 |
| Session 快照 | 保存完整终端输出历史 |
| 功能 | 说明 |
|---|---|
| 断开提示 | Toast 提示网络断开 |
| 错误详情 | 显示具体错误(认证失败、超时、网络不可达) |
| 功能 | 说明 |
|---|---|
| 横屏模式 | 支持,终端尺寸自动调整 |
| 外接键盘 | 支持 USB/蓝牙键盘 |
| 分享功能 | 分享终端输出/命令 |
| 功能 | 说明 |
|---|---|
| 配置导入导出 | WebDAV 支持 |
| 快捷键配置 | WebDAV 同步 |
| 命令历史 | WebDAV 同步 |
type ClientMessage =
| { type: 'connect', payload: HostConfig }
| { type: 'input', payload: string }
| { type: 'resize', payload: { cols: number, rows: number } }
| { type: 'ping' }
| { type: 'close' }
type ServerMessage =
| { type: 'connected', payload: { sessionId: string } }
| { type: 'data', payload: string }
| { type: 'error', payload: { message: string } }
| { type: 'title', payload: string }
| { type: 'closed', payload: { reason: string } }
| { type: 'pong' }
idle → connecting → connected → closed
↓ ↓
error reconnecting → connected
interface Host {
id: string;
name: string;
bridgeHost: string;
bridgePort: number;
sessionName: string;
authType: 'password' | 'key';
password?: string;
privateKey?: string;
tags: string[];
pinned: boolean;
lastConnected?: number;
autoCommand?: string;
}
interface Session {
id: string;
hostId: string;
connectionName: string;
bridgeHost: string;
bridgePort: number;
sessionName: string;
title: string;
ws: WebSocket | null;
state: 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'error' | 'closed';
hasUnread: boolean;
customName?: string; // 用户重命名的名称
}
examples/mobile/
├── docs/ # spec / architecture / workflow
├── evidence/ # 截图 / 日志 / APK / 真机证据
├── task.md # 当前任务板
├── CACHE.md # 短期上下文
├── MEMORY.md # 长期经验
├── android/ # npx cap add android 生成
├── src/
│ ├── components/
│ │ ├── TerminalTabs.tsx # 顶部 Tab 栏
│ │ ├── TerminalView.tsx # 单个终端视图
│ │ ├── HostList.tsx # 主机列表页
│ │ ├── HostForm.tsx # 添加/编辑主机表单
│ │ ├── QuickActions.tsx # 快捷键工具栏
│ │ └── ConnectionBar.tsx # 连接状态栏
│ ├── hooks/
│ │ ├── useSession... # session / bridge 状态管理
│ │ ├── useHostStorage.ts # 主机配置存储
│ │ ├── useKeepAlive.ts # 后台保活
│ │ └── useQuickActions.ts # 快捷键管理
│ ├── contexts/
│ │ └── SessionContext.tsx # 多会话状态管理
│ ├── lib/
│ │ ├── types.ts # Host, Session 类型
│ │ ├── websocket.ts # WebSocket 协议
│ │ ├── storage.ts # localStorage 封装
│ │ └── webdav.ts # WebDAV 同步
│ ├── server/
│ │ └── server.ts # WebSocket → tmux 桥接
│ ├── App.tsx
│ └── main.tsx
├── capacitor.config.ts
├── package.json
└── note.md # 历史记录(非主真源)
| 需求 | 来源 | 复用方式 |
|---|---|---|
| WebSocket tmux 桥接 | examples/mobile/src/server/server.ts | 当前真源 |
| PTY 本地连接 | examples/local/server.ts | 参考 resize 协议 |
| 终端渲染 | @wterm/react | npm install |
| WebSocket Transport | @wterm/core | npm install |
ws://,Capacitor WebView 运行在 httpsandroidScheme=https 会把移动端带到 secure context,ws:// bridge 会被 mixed-content / cleartext 规则卡住capacitor.config.ts 使用 androidScheme=http,AndroidManifest 打开 usesCleartextTraffic=trueConnected,bridge 日志出现 session create/closeImeTracker ... onCancelledEditText anchor 在抢 input focus;只调用 showSoftInput() 不够ImeAnchor;必要时先 clear WebView focus,再由原生 EditText 请求焦点并 showSoftInputImeAnchor show()/showSoftInput() 命中,点击键盘按钮后系统 IME 实际弹出且中文输入可提交到 tmuxhtml/body/#root 固定为 overflow:hidden + overscroll-behavior:none,只让 terminal buffer 容器滚动;MainActivity 再把 WebView 设为 OVER_SCROLL_NEVERcat -v,然后点击方向键 / Esc / 自定义快捷键^[[A、^[、^A 或自定义文本,证明字节序列真实进入 tmuxTerminalView 只做 axis lock 与横向手势 delta 上报;TerminalCanvas 统一负责相邻 tab 预览、跟手位移、半屏阈值、回弹/完成动画与最终切 tabadb shell dumpsys cpuinfo、top -H -p <pid>、dumpsys gfxinfo;重点看 Chrome_IOThread / RenderThread / Slow issue draw commandscursor.visible 当变化条件导致每 96ms 发包) 2) client 端每帧 localStorage.setItem(JSON.stringify(buffer/snapshot)) 3) 全量 scrollback DOM + 常驻 blurInspired by coding-principals skill.
pnpm config set registry https://registry.npmmirror.compkill -9 -f pnpm; pnpm install --no-frozen-lockfilepnpm config set registry https://registry.npmjs.org