渲染/tmux 修复(e2e 复现+回归,模拟器 idb 点击+截图取证): - 终端底部残影:ghostty iOS 释放 surface 不摘 IOSurfaceLayer(上游缺陷), platformSetup 清现存子层 + TerminalSurfaceCoordinator.onSurfaceFreed 钩子摘孤儿层 - tmux 切 tab 切回旧 tab 内容消失:改所有窗口常驻挂载(ZStack+opacity+hitTesting), surface 不释放、内容保留、后台窗口持续收 %output - tmux pane 内容钉底/顶部残行:pane grid 由容器几何定(tab 条吃高度),75 行快照喂 73 行 溢出上滚;几何测量上移 TmuxTabbedView + attach kickoff 双条件(attachAcked∧containerKnown) + capture-pane 铺快照前剔尾空行 UI 产品化(对标 Moshi getmoshi.app:深蓝黑 + 终端绿 + Catppuccin): - 设计系统 Theme.swift:TXPalette(色角色 struct)+TXThemeManager(环境注入),颜色封装供主题系统 - 统一主题:抽离 4 套 Catppuccin(Mocha/Macchiato/Frappé/Latte),一套同时驱动 app+终端配色, 设置里选主题 app 与终端一起变色 + UserDefaults 持久化 - 主屏/主机管理页 HomeView:HostCard 主机卡 + FAB 加主机(AddHostSheet) + 设置(主题选择器), HostStore 本地 JSON 持久化(密码暂存/TODO Keychain),点卡片 connect(to:) - 终端外壳 chrome TerminalTopBar:圆形返回钮 + 会话标题 + SSH/mosh 传输徽章 + 状态提示, tmux tab 绿 pill 化,底部工具栏深色自适应,终端默认 Catppuccin Mocha iPad mini(A17 Pro) live 端到端验证:加主机→连接→终端→切 tab→分屏点焦点→主题切换同步。 详见 docs/HANDOFF.md §10。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
119 lines
19 KiB
Markdown
119 lines
19 KiB
Markdown
# terminalX 交接文档(HANDOFF)
|
||
|
||
> 目的:让**没有上下文的新会话**读完即可掌握项目全貌并继续工作。配合 `../CLAUDE.md`(操作规范/命令)与项目记忆(`~/.claude/projects/-Users-kid-...-terminalX/memory/`,本项目会话会自动注入)一起看。
|
||
> 最后更新:2026-07-24。
|
||
|
||
---
|
||
|
||
## 0. 一句话现状
|
||
一个**原生 iPad SSH 终端**已可用并真机验证:libghostty 渲染 + 键盘输入 + SSH 直连 + **SSH-over-tsnet(Tailscale 用户态)** + **tmux -CC 原生 tab + 多 pane 分屏** + **mosh(LAN + over-tsnet 均已端到端验证)** + 断线自动重连 + **mosh 挂起→恢复<3s(M3)**。四大核心需求(libghostty/tailscale/tmux/**mosh 均已验证**)。仅在 iOS 模拟器验证(未上真机/未签名)。
|
||
> **2026-07-24 本轮**:修复 3 个渲染/tmux 显示 bug + 完成 UI 产品化第一波(对标 Moshi)。详见 **§10**。UI 已从 demo 态变为深色 Catppuccin 主题 + 主机管理页 + 终端 chrome + 统一主题切换。
|
||
> **mosh 状态(2026-07-24 全部验证通过)**:mosh(C++,blinksh/ios)+protobuf-lite 交叉编译成 `MoshCore.xcframework`(CommonCrypto 后端,含 arm64 模拟器 slice);tsnet 桥加 UDP relay(`StartMoshRelay`);`MoshSession`(pipe↔FILE*/pthread/SIGWINCH)+`MoshConnectScanner`(TXCore)+`SSHTerminalModel` 编排(SSH 引导 mosh-server→解析 MOSH CONNECT→接管)。
|
||
> - **LAN 直连端到端验证**(192.168.9.199):mosh-server 存活 104s≫60s 无客户端超时+子 shell+心跳=真连。
|
||
> - **mosh over tsnet 端到端验证**(vohive-vm 100.69.201.101):app 日志 `parsed MOSH CONNECT port=60002`→`relay up localPort=63542`→`activateMosh started`;屏幕渲染远端 shell 且无 mosh 断连 overlay=relay UDP 双向流通。**R1(tsnet UDP 数据报语义) 证伪**。
|
||
> - 复现 tsnet:模拟器已持久化 tsnet 节点状态(`~/Library/.../tsnet-main`),`-txTsnetKey` 传任意非空串即可凭存量身份重连(节点未被 tailnet 清理时);节点被清理则需新 ephemeral key。
|
||
|
||
## 1. 项目愿景与硬约束
|
||
- 目标:iPad/iPhone 远程轻量运维 macOS/Linux + vibe coding。平台优先级 **iPadOS > iOS > macOS(Apple Silicon)**。
|
||
- iOS/iPadOS **只远程**(沙盒禁 fork/exec,不能跑本地 shell);macOS 才支持本地 PTY。
|
||
- 分发目标 App Store(M5,已暂缓)。
|
||
- 四大核心:**libghostty**(终端引擎) · **mosh** · **Tailscale tsnet(用户态,非 NetworkExtension)** · **tmux control-mode(-CC) hook**。
|
||
|
||
## 2. 关键架构决策(已定,勿推翻)
|
||
1. 终端引擎用 **libghostty**,经社区 **GhosttyKit**(`Lakr233/libghostty-spm`,已 vendor 到 `vendor/libghostty-spm`)。`TerminalViewState` 是 `ObservableObject`;喂字节入口 `InMemoryTerminalSession.receive(Data)`;SwiftUI 视图 `TerminalSurfaceView(context:)`。
|
||
2. Tailscale 走 **tsnet 用户态**(Go,gomobile bind → `TsnetBridge.xcframework`),**不用 NetworkExtension**(避开 VPN entitlement + 50MB 内存墙)。
|
||
3. SSH 用 **libssh2 + mbedTLS**(`CSSHCore.xcframework`),支持传入外部 fd。
|
||
4. **SSH-over-tsnet = fd 桥**:`tsnet.Dial()` 得 net.Conn(无真实 fd)→ `socketpair(AF_UNIX)` + 双向 io.Copy 泵 → 把另一端 fd 交给 libssh2。见 `vendor/tsnet-bridge/tsnetbridge/bridge.go: DialTCPFD`。
|
||
5. 一进程只一个 Go runtime、只一次 gomobile bind;C/C++ 库符号隐藏只导出 C API。
|
||
6. 多账号(未实现):多 tsnet 节点并存;每 host 绑 egress profile(Direct/tailnet);**禁止多 tailnet 并发盲探**(重叠 100.x/同名会误连)。
|
||
|
||
## 3. 环境(决定构建方式,极重要)
|
||
- **带认证的 HTTPS 代理**(`HTTPS_PROXY=...@...:38128`)。`github.com` release blob 能 curl;**go.dev / ghcr.io 被代理 abort**;**SwiftPM 不支持带认证代理** → 所有 SPM 依赖 vendor 本地化。Go 模块用 `goproxy.cn`。
|
||
- **Go 1.26.5 在 `~/.local/go`**(不在默认 PATH;经 aliyun 镜像 curl 装),**gomobile/gobind 在 `~/go/bin`**,`go env -w GOPROXY=https://goproxy.cn GOSUMDB=off` 已固化。
|
||
- 工具链:Xcode 26.6 / Swift 6.3 / xcodegen 2.46 / zig 0.15.2 / cmake 4.4 / ninja 1.13 / git。
|
||
|
||
## 4. 里程碑状态
|
||
|
||
| 里程碑 | 状态 | 真机实证 |
|
||
|---|---|---|
|
||
| **M0** SSH 直连终端 | ✅ 完成 | libghostty 渲染(SGR/CJK)、键盘输入、连接;**白屏 bug 修复**(OutputGate);会话状态机+自动重连(TXCore 单测总数见 §5=50) |
|
||
| **M1** tsnet + SSH-over-tsnet | ✅ 完成 | app 内 tsnet 加入 tailnet(IP 100.74.60.108);对端枚举;**SSH-over-tsnet 登录 Ubuntu shell**(服务端 `Last login from 100.x` 证明走 tailnet) |
|
||
| **M1.5** tmux -CC 原生 tab | ✅ MVP | 直连跑 `tmux -CC attach` → 渲染 fish/tmux 两原生 tab + 活动窗口内容 |
|
||
| dial 重试打磨 | ✅ | `TsnetManager.dialFD` 6 次退避重试,解决 tsnet 首连路径预热超时 |
|
||
| **R5** Go+gvisor+C+++Swift 同进程 | ✅ retire | 全程真机无崩溃 |
|
||
| **M2** mosh | ✅ 完成(LAN + tsnet 均验证) | libmoshios(C++)+protobuf-lite→`MoshCore.xcframework`;UDP relay over tsnet;**LAN 直连 + mosh-over-tsnet 均端到端验证通过**(R1 证伪) |
|
||
| **M3** 生命周期深化 | ✅ mosh 快恢复(真机深挂起待补) | scenePhase FSM + **mosh 挂起→恢复<3s 已验证**(模拟器:15/30s 冻结→前台,wake pulse→1.3s 收到服务器包,"已恢复");冷启动重连(SSP 序号+key 持久化)划入 M4/M5 |
|
||
| **M4** 安全 | ✅ 完成(真机 SE 硬件待补) | **host key TOFU pin**(首次信任+不符告警,指纹对齐 ssh-keygen,MITM 关闭)+ **publickey 认证**(P-256 sign_callback→SecKeyCreateSignature→DER→SSH mpint,免密登录 192.168.9.199 验证通过;SE 优先/未签名回退软件+文件,SE 生成在模拟器 hw=true 可达,真机 SE 硬件签名+keychain 持久化待补) |
|
||
| **M5** 合规提审 | ⏸️ 暂缓 | 用户指示暂缓 |
|
||
|
||
## 5. 代码地图与数据流
|
||
- `packages/TXCore/Sources/TXCore/`
|
||
- `Tmux/TmuxControlParser.swift` `TmuxEvent.swift` `TmuxIDs.swift` `TmuxOutputDecoder.swift` `TmuxLayout.swift` `TmuxControlSequence.swift`(DCS 检测)—— 纯解析。TXCore 全部单测共 **50**(40 swift-testing + 10 XCTest:tmux 解析/`SessionMachine`含mosh三相/`MoshConnect`/`SSHWire`),`swift test --package-path packages/TXCore` 全绿。
|
||
- `Session/SessionMachine.swift` —— `reduce(event)->[Effect]` 会话状态机(指数退避重连/后台冻结/前台恢复)。**M3 加 mosh 三相** `moshActive/moshParked/moshResuming` + 事件 `moshEstablished/moshHealthy/moshExited/resumeWatchdogFired` + 效果 `nudgeResume/scheduleResumeWatchdog/teardownMosh`:mosh 接管即拆 SSH、后台冻结不重建、前台打唤醒脉冲等 SSP 续、两次脉冲无效兜底全量重建。
|
||
- `packages/TXTransport/Sources/`
|
||
- `TXTransport/Transport.swift`(协议+SSHConfig+SSHError)、`SSHSession.swift`(libssh2;`preconnectedFD` 支持外部 fd;事件循环 poll+非阻塞)。
|
||
- `TXTransport/MoshSession.swift`(M2):驱动 `mosh_main`;pipe↔FILE* 桥(in/out)、Foundation.Thread 跑阻塞主循环+`pthread_self()` 供 `pthread_kill(SIGWINCH)` resize、忽略 SIGPIPE、setenv UTF-8 locale。
|
||
- `CSSH/`(C target:libssh2 头 + modulemap,供 `import CSSH`)、`CSSHCore`(binaryTarget,library-only)。
|
||
- `CMosh/`(C target:`moshiosbridge.h`(mosh_main)+modulemap+占位 shim.c,供 `import CMosh`)、`MoshCore`(binaryTarget,library-only);TXTransport 链 `libc++`/`libz`(mosh 是 C++ 且 compressor 用 zlib;CommonCrypto 属 libSystem 自动解析)。
|
||
- `TXCore/Mosh/MoshConnect.swift`:`MoshConnectScanner` 流式扫描 `MOSH CONNECT <port> <key>`(注意 Swift 视 `\r\n` 为单 Character,行尾判定须用 `isNewline`)。
|
||
- `TXCore/SSH/`(M4):`SSHWire`(string/mpint 编码)/`ECDSAConv`(DER→SSH 签名·P256 blob)/`HostKey`(opensshFingerprint·evaluate·HostTriple·KnownHostsStore 协议),SSHWireTests 10 单测(真实指纹向量+高位 DER)。
|
||
- **M4 安全**:`TXTransport` `SSHConfig.hostKeyVerifier`(闭包)+`SSHError.hostKeyMismatch`+`SSHSigner` 协议+`Authentication.publicKeyCallback`;`SSHSession` 握手后 `libssh2_session_hostkey` 校验、`sshSignCallback`(@convention(c)+malloc 出参)接 `libssh2_userauth_publickey`。app:`KeychainKnownHostsStore`(文件后端 TOFU pin)、`SigningKeyProvider`(SE 优先/软件回退+文件持久化,`SecKeyCreateSignature`)、mismatch alert(ContentView)。
|
||
- **M3 快恢复链**:`bridge.go` 加 `Node.WakeUp()`(tsnet `InjectEvent`+`MagicSock.Rebind/ReSTUN`) 与 `MoshRelay.Rebind()`(同端口重开 loopback、泵按 socket 生成容错);`iosclient.cc` 加 `g_mosh_last_heard`(收到服务器包即更新,导出 `mosh_last_heard_ms()`) 并恢复 SIGCONT 全屏重绘;`MoshSession.nudge()`(SIGCONT) + `lastHeardMs()`;`SSHTerminalModel` 前台执行"唤醒脉冲三连"(WakeUp+Rebind+nudge)+healthy 轮询(探针增长→`moshHealthy`)+恢复看门狗+`beginBackgroundTask`。
|
||
- `apps/TerminalX/iOS/`
|
||
- `SSHTerminalModel.swift` —— 核心编排:`OutputGate`(surface-ready 缓冲)、`TransportHolder`、`TsnetManager`(tsnet 节点 up/dial+重试)、`TsnetError`、`TmuxRouter`(raw/gateway 字节分流)、状态机驱动 startSession(直连/tsnet 分支)、`autoConnectIfConfigured`(启动参数)。
|
||
- `ContentView.swift` —— 分支:`tmuxController != nil`→`TmuxTabbedView`(tab 条+活动窗口);否则 `RawTerminalView`;`TsnetProbeView`(纯自检);`ConnectionForm`。
|
||
- `TmuxController.swift` —— tmux 网关:消费解析事件;**pane-per-surface**(`TmuxPaneSurface`=session+state+gate/pane);`TmuxWindow` 持 panes 字典 + `visibleLayout`(渲染)/`fullLayout`(diff);`surfaceByPane` O(1) 路由 %output;`applyLayout` reconcile(增删留);attach: `refresh-client -C WxH`→`list-windows`(带 layout)→每 pane `capture-pane`(`awaitingCapture` 丢弃快照前 %output);输入绑 pane→`send-keys -t %self`;tap→`select-pane`。字节经 `SSHTerminalModel` 的 `TmuxByteChannel`(AsyncStream 单消费者)保序入网关。
|
||
- `TsnetProbe.swift` —— M1 自检视图(加入 tailnet + 对端列表)。
|
||
- `TerminalXApp.swift` —— @main。
|
||
- **数据流(直连)**:transport.onBytes → `TmuxRouter.feed` → (raw) `OutputGate.deliver`→`session.receive`→libghostty;(检测到 DCS) → `TmuxController.feed`→解析→各 window。输入:libghostty→session write 闭包→transport.send(tmux 模式→send-keys)。
|
||
- **数据流(tsnet)**:`TsnetManager.ensureUp`(auth key)→`dialFD`(socketpair fd)→`SSHSession(preconnectedFD:)`。
|
||
|
||
## 6. 已知问题 / TODO(继续工作的入口)
|
||
- **M4 安全(已完成,剩真机 SE)**:host key TOFU pin(`SSHConfig.hostKeyVerifier`→app `KeychainKnownHostsStore` 文件后端) + publickey 认证(`Authentication.publicKeyCallback(SSHSigner)`→`SSHSession` C `sshSignCallback`→`SigningKeyProvider`)。**剩**:真机验证 SE 硬件密钥生成+签名+keychain 持久化(未签名/模拟器回退软件+文件);`libssh2_session_method_pref` 锁定 pinned hostkey 类型防算法漂移误报(fable 建议,未做);M4DBG NSLog 埋点可清理。
|
||
- **tmux 多 pane 分屏 + 动态 resize**:✅ 已做(pane-per-surface + layout rect 绝对定位渲染 + 按 pane %output 路由 + tap 切焦点/select-pane + attach `refresh-client -C` 设尺寸 + **iPadOS 拖拽窗口/旋转 → `onChange(geo.size)`→`containerResized`(点×displayScale÷cell像素)→debounce→refresh-client -C→%layout-change→更新 frame**,验证:容器 1032x1280@2x cell16x35→129x73,tmux 精确采纳)。**剩余**:完整 scrollback(仍靠 capture-pane 抓当前屏);pause 流控(`%pause/%continue`,防单 pane 刷屏);split/kill pane 手势;tmux<3.1 降级路径未做。
|
||
- **tsnet 首连**:已加 dial 重试;更优是等 peer online 再 dial(读 PeersJSON online 状态)。
|
||
- **签名/真机**:`CODE_SIGNING_ALLOWED=NO`,仅模拟器;上真机/TestFlight 需配置签名 + entitlements(tsnet 用户态**无需** NE entitlement,利好审核)。
|
||
- **git**:main 分支已有 6 次提交(初始 + M2/M3/tmux多pane/tmux resize/M4a/M4b),工作区干净,**未 push 远端**(无 remote)。commit/push 只在用户明确要求时。`artifacts/*.xcframework`(`CSSHCore`/`TsnetBridge`/`MoshCore`)+ `vendor/mosh`+`vendor/protobuf` 源码**均有意提交**(克隆即可构建,无需网络);构建中间产物(`vendor/build/mosh-out`、`vendor/protobuf/build-*`)已 gitignore。
|
||
- **多平台**:iOS target 已跑;macOS target(本地 PTY, `TXPTY`)未建。
|
||
|
||
## 7. 下一步建议(M0~M4 + tmux 多pane/resize 均已完成并验证;以下按价值)
|
||
1. **真机化(最大缺口,多项验证只在模拟器)**:配置签名 + entitlements(tsnet 用户态**无需** NE,利好审核)→ 上真机/TestFlight。真机上补验三件模拟器测不了的:①**SE 硬件密钥**生成+签名+keychain 持久化(M4b 现回退软件+文件;`SigningKeyProvider` 已备 SE 路径);②**mosh 深挂起恢复**(锁屏数分钟过 WG 180s 过期线 / socket defunct,验 `MoshRelay.Rebind`+`hop_port`);③签名后 **Keychain 可用**(当前未签名 SecItem 全 -34018,host key pin 才回退到文件)。
|
||
2. **产品化**:主机列表持久化(签名后用 Keychain 存凭据/host 配置)、多账号多 tsnet 节点(架构决策 §2.6:每 host 绑 egress,禁多 tailnet 盲探)、软键盘运维工具栏(GhosttyKit 内建,接线即可)、多标签(非 tmux)。
|
||
3. **打磨(可选,不阻塞)**:
|
||
- 清理诊断埋点:`SSHTerminalModel`/`TmuxController`/`iosclient.cc` 的 `NSLog("MOSHDBG/TXM3/TMUXDBG/M4DBG …")` + moshiosbridge.cc 的 `fwrite("Hello from the Bridge!")`。
|
||
- mosh:`activateMosh` 写死 80x24 初值(靠首次 resize 纠正);mosh 接管后 idle SSH 未关;`MoshSession.close()` 后 mosh 线程可能滞留;远端累积 detached mosh-server(每次 `mosh-server new`)需清理策略。
|
||
- tmux:pause 流控(`%pause/%continue` 防单 pane 刷屏);split/kill pane 手势;外接键盘 `Cmd+Opt+方向` 导航 pane;完整 scrollback(现靠 capture-pane 抓当前屏);tmux<3.1 降级路径。
|
||
- M4:`libssh2_session_method_pref` 锁定 pinned hostkey 类型防算法漂移误报;host key pin 的 UI 管理(查看/删除已信任)。
|
||
- tsnet 首连:更优是等 peer online 再 dial(读 PeersJSON online 状态)。
|
||
4. **冷启动 mosh 重连(大活)**:mosh 进程被杀后接原 detached 会话——受 SSP 序号防重放限制,需持久化 MOSH_KEY+port 且改 mosh 序列化客户端状态(Blink 式)。价值中等,成本高。
|
||
5. **macOS target**:本地 PTY(`TXPTY`)未建。
|
||
6. **M5 合规提审**:用户指示暂缓。
|
||
|
||
## 8. 测试资源(用户提供,**凭据勿写入仓库/勿硬编码**,每次向用户索取)
|
||
- 一台局域网 macOS(有 tmux + **mosh-server**,如 MacBook-Air-M1;fish 为默认 shell,`mosh-server` 在 `/opt/homebrew/bin` 且交互 shell PATH 可见)、一台 tailnet 内 online 的 Ubuntu VM(vohive-vm)、一个 tailnet auth key(ephemeral,可能过期,用户会给新的)。用于直连 SSH / SSH-over-tsnet / tmux / M2 mosh 端到端验证。凭据每次向用户即时索取。
|
||
- tailnet 是 `tail3b5057.ts.net`,已见节点:vohive-vm(linux)、iphone-air、ipad-pro、macbook-air-m1、macbook-air。
|
||
|
||
## 9. 工作纪律(务必遵守)
|
||
- **绝不臆想工具结果**:调用后等真实返回再推理,绝不编造 BUILD/日志/截图。曾因连续编造工具输出(含假 BUILD SUCCEEDED、错误归因 GhosttyKit)而误诊——真相全靠老实 `Read` 完整 log。此规则已写入全局 `~/.claude/CLAUDE.md`。
|
||
- 每步用真实证据验证:`swift test` 看真实计数、`xcodebuild` 看真实 BUILD 结果、模拟器 `screenshot` + `Read` 看真实画面。
|
||
- 与用户用**简体中文**;commit/push 只在明确要求时。
|
||
|
||
## 10. UI 产品化 + 渲染修复(2026-07-24 本轮)
|
||
|
||
### 10.1 修复的 3 个渲染/tmux 显示 bug(e2e 复现+修复+回归,均模拟器 idb 点击+截图取证)
|
||
1. **终端底部残影**(旧帧钉屏底):ghostty 在 iOS 用 `addSublayer:` 挂 IOSurfaceLayer,但 `Metal.deinit` 从不 `removeFromSuperlayer`(上游 iOS-only 缺陷),SwiftUI attach 抖动导致同一 view 多次建 surface 时旧层泄漏、冻结小尺寸首帧,被 CA `topLeft` gravity 钉在屏底。修:vendor `UITerminalView.platformSetup` 建 surface 前清现存子层 + `TerminalSurfaceCoordinator.onSurfaceFreed` 钩子(`tearDownSurface` 后回调)在 `commonInit` 里摘子层。(诊断经 Fable 顾问在 ghostty 源码层锚定。)
|
||
2. **tmux 切 tab 切回旧 tab 内容消失**:`TmuxTabbedView` 原只渲 activeWindow 且 `.id(win.id)`,切走销毁 pane 视图→释放 surface→内容丢失。修:改成**所有窗口常驻挂载、仅激活可见**(`ZStack`+`opacity`+`allowsHitTesting`),surface 不释放、内容保留、后台窗口持续收 %output。
|
||
3. **tmux pane 内容钉底/顶部残行**:pane 的 ghostty grid 由容器像素几何决定(tab 条吃高度→73 行),而 attach 用 raw 全屏 75 行 capture,75 行快照喂 73 行 grid 溢出上滚。修:(A) 容器几何测量上移到 `TmuxTabbedView` 内容槽(破鸡生蛋死锁);(B) `TmuxController` attach kickoff 改 `attachAcked ∧ containerKnown` 双条件(`kickoffIfReady`);(C) `capture-pane` 铺快照前剔尾部空行。
|
||
|
||
### 10.2 UI 产品化(对标 Moshi getmoshi.app;深蓝黑底 + 终端绿 + 圆角卡片)
|
||
- **设计系统(颜色封装,为主题系统)**:`apps/TerminalX/iOS/Theme.swift`=`TXPalette`(所有色角色 struct)+`TXThemeManager:ObservableObject`(环境注入);结构量走 `enum TX`(圆角/间距/等宽字体)。
|
||
- **统一主题**:从 GhosttyTheme 抽离 4 套 Catppuccin(Mocha/Macchiato/Frappé/Latte),每套同时定义 app 配色+终端配色(`ghosttyName`→`terminalTheme`)。设置里选主题 → **app 与终端一起变色 + 持久化**(`UserDefaults(txThemeStorageKey)`);`ContentView` `.onChange(palette.id)→model.applyTerminalTheme()`;新终端/pane 经 `TerminalTheme.txDefault` 读持久化。
|
||
- **主屏/主机管理页**(`ContentView.HomeView`):主机卡(`HostCard`)+FAB 加主机(`AddHostSheet`)+设置(`SettingsStubView` 含主题选择器);`HostStore`(Application Support/hosts.json 持久化,密码暂存本地/TODO Keychain);点卡片 `SSHTerminalModel.connect(to:)`。
|
||
- **终端外壳 chrome**(`ContentView.TerminalScreen`+`TerminalTopBar`):顶栏(圆形返回钮+会话标题`model.connectionTitle`+传输徽章`model.moshEngaged`?mosh青:SSH绿+连接态点+状态提示);`TmuxTabChip` 绿 pill 化;底部 GhosttyKit 工具栏深色自适应;终端默认 Catppuccin Mocha。
|
||
- **验证**:iPad mini(A17 Pro) live 验证全流程(加主机→连接→终端→切 tab→分屏点焦点→主题切换 app+终端同步)。
|
||
|
||
### 10.3 待续(UI)
|
||
- 会话缩略卡区(Moshi 首屏实时终端预览横滑,需会话管理);设置页字体/工具栏配置;导航壳打磨;终端工具栏 accent tint(`inputAccessoryStyle` SwiftUI 未暴露需桥接)。
|
||
- 无头 UI 测试:`idb ui tap x y`(Python3.14 需事件循环 shim `/tmp/idbrun.py`;坐标=屏幕像素×0.5,注意设备朝向)。诊断埋点(GKDBG/txDbg*)已清理;MOSHDBG/TMUXDBG/M4DBG 仍在(旧 cleanup-todo)。
|
||
</content>
|