T3 Code 源码拆解
客户端

16 · Expo / React Native 移动端

移动端如何复用连接与领域状态,并用原生模块解决终端、Diff、Markdown 与输入性能。

移动端不是缩小版网页

apps/mobile 使用 Expo、React Native 0.85、React 19.2 与 React Navigation。它与 Web 共享 contracts、client-runtime 和领域语义, 但不共享 DOM UI。原因很实际:导航、键盘、安全存储、后台生命周期、长列表和终端渲染都具有原生约束。

App.tsx 组合 Atom registry、云 auth、connection runtime、 主题与 native navigation。它首先是一台“可能随时休眠、换网络”的远程控制设备。

共享与不共享

共享移动端独有
RPC/contracts 与错误 SchemaReact Navigation screen stack
Environment catalog/registry/supervisorSecure storage、AppState、network bridge
Thread/project/session reducersSwift/Kotlin 原生渲染模块
命令、审批、checkpoint 语义键盘、safe area、手势、触觉反馈
Relay discovery/authPush/前后台与移动网络体验

这是一种“headless 运行时共享”:重用状态机和协议,不强求组件跨平台。

移动连接的额外压力

手机网络在 Wi‑Fi/蜂窝/VPN 间切换,操作系统会冻结 JS,后台 socket 可能被静默回收。移动 platform layer 将 NetInfo/AppState 等信号转成 client-runtime wakeup;supervisor 决定 probe 或重连。

移动特定 probe 窗口更短(本快照约 3 秒),目的是在用户打开 App 时快速识别僵尸连接;但离线仍不消耗 backoff attempt。 App 前台恢复时不能直接创建第二条 socket,必须让 registry/supervisor 串行化环境 lease。

移动端从后台恢复
yes no yes no App background / JS suspended AppState foreground Runtime wakeup Existing environment supervisor Short probe succeeds? Keep session + subscriptions Close stale session Resolve endpoint + new ticket + WS Cursor gap acceptable? Replay bounded gap Reload snapshots Refresh screens

Native navigation 与 environment identity

导航参数需要 environmentId + threadId,和 Web URL 一样显式。Screen mount 获取 detail lease,unmount/blur 释放或降低 retention; 后台通知/深链先解析 environment,再打开 thread。不能用一个全局 currentServer 推断,否则用户切换环境时返回栈会展示错数据。

为什么写原生模块

源码包含 iOS/Android 原生实现,用于 review diff、terminal、markdown、composer 等重负载区域。动机通常是:

  • 大 diff/markdown 在 JS 线程解析布局会阻塞手势;
  • 终端需要高频字节绘制、字体网格和选择;
  • Composer 与系统输入法、自动完成、附件交互耦合;
  • 原生列表/文本能降低 bridge 往返。

原生模块的输入应是稳定的领域/view model,而不是直接理解 Provider 原始事件;否则每次协议升级都要同时发 iOS/Android binary。

移动终端

features/terminal/ 将职责拆为:

  • terminalLaunchContext:从 environment/thread 得到连接上下文;
  • threadTerminalPanelModel:面板状态;
  • terminalBufferReplay:恢复输出基线与增量;
  • NativeTerminalSurface:原生渲染;
  • nativeTerminalModule:JS/native 边界;
  • preferences/theme/menu:纯产品配置。
移动终端数据流
Native keyboard/input Terminal session command Terminal RPC Server PTY Output stream Buffer replay model NativeTerminalSurface React Navigation screen

Buffer replay 很关键:移动 App 恢复时,不能假设在后台收到所有字节;Server/terminal session 要提供可重放 buffer 或新基线。 终端数据不进入 orchestration event log,因为吞吐和保留语义不同。

审批是移动端的核心价值

手机最有价值的场景往往不是编辑长提示,而是查看进度、批准命令、回答一个问题、interrupt 或接收完成状态。 这要求 approval/pending input 已经由 Server 持久化,移动端只是另一个有 scope 的响应者。

双设备同时操作时,第二个响应应收到“已结算/冲突”的领域结果;UI 刷新 projection,不自行覆盖第一个决定。

认证与安全存储

移动端用 Clerk 处理云身份,并在本地 secure storage 保存环境授权材料。Pairing link 的敏感 token 放 fragment,避免被 HTTP server/log 收集; 兑换后保存 session/DPoP key,并为 WebSocket 取得短期 ticket。

日志和 diagnostics 必须使用 client-runtime 的 safe error/trace,避免把 bearer、ticket、pairing proof 或完整 URL 输出到 Expo log。

后台与任务持续性

Agent 运行在开发机 Server,因此手机进入后台不会中止 turn,这是本地执行边界带来的直接收益。移动端不应尝试在后台维持无限 socket; 恢复时基于 Server 事实追上即可。若需要通知,应由可选 relay/notification 控制面发“有更新”信号,而不是把完整代码/消息放进 push payload。

性能与容量

  • thread list 使用 shell/pagination,不装载所有 detail;
  • message/activity 长列表要稳定 key、窗口化与原生文本优化;
  • 大 diff 按需请求,尊重 Server 大小上限;
  • Markdown 高亮/代码块避免在每个 delta 全量解析;
  • foreground 恢复合并 wakeup,防止 AppState/NetInfo 同时触发两次重连;
  • 原生模块失败必须有纯 React fallback 或明确不可用状态。

源码锚点

可迁移经验

  • 跨 Web/Native 最值得共享的是协议与状态机,不是 DOM 组件。
  • 后台恢复应“探测—有界补发—快照重建”,不要依赖后台 socket 永久在线。
  • 原生优化边界接收稳定 view model,避免理解供应商协议。
  • 将长期任务留在 Server,移动端做可丢弃、可重建的控制面。
  • 多设备审批由领域幂等和权威 projection 解决,不靠按钮 disabled 的本地状态。

On this page