feat: 修复 3 个渲染/tmux 显示 bug + UI 产品化第一波(对标 Moshi)
渲染/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>
This commit is contained in:
@@ -167,6 +167,10 @@ final class SSHTerminalModel: ObservableObject {
|
||||
@Published var tmuxController: TmuxController?
|
||||
/// M4:host key 与 pin 不符 → 非 nil 触发 SwiftUI alert(信任新密钥 / 取消)。
|
||||
@Published var hostKeyMismatch: HostKeyMismatchInfo?
|
||||
/// 终端顶部状态行:当前连接标题(主机名/地址)。
|
||||
@Published var connectionTitle: String = ""
|
||||
/// mosh 是否已接管(传输徽章 SSH/mosh)。
|
||||
@Published var moshEngaged: Bool = false
|
||||
|
||||
private let knownHosts = KeychainKnownHostsStore()
|
||||
|
||||
@@ -219,6 +223,7 @@ final class SSHTerminalModel: ObservableObject {
|
||||
)
|
||||
let state = TerminalViewState()
|
||||
state.configuration = TerminalSurfaceOptions(backend: .inMemory(session))
|
||||
_ = state.controller.setTheme(.txDefault) // Catppuccin Mocha 终端配色
|
||||
self.holder = holder
|
||||
self.moshHolder = moshHolder
|
||||
self.session = session
|
||||
@@ -243,7 +248,16 @@ final class SSHTerminalModel: ObservableObject {
|
||||
/// M4b:非 nil 时用 publickey(SE/软件签名器) 认证而非密码。
|
||||
private var pubkeySigner: SigningKeyProvider?
|
||||
|
||||
/// 从主机列表连接(设置 mosh 模式后走标准连接)。
|
||||
func connect(to h: SavedHost) {
|
||||
moshMode = h.useMosh
|
||||
connect(host: h.host, port: h.port, username: h.username, password: h.password)
|
||||
connectionTitle = h.name
|
||||
}
|
||||
|
||||
func connect(host: String, port: Int, username: String, password: String) {
|
||||
connectionTitle = host
|
||||
moshEngaged = false
|
||||
let egress = tsnetAuthKey != nil ? "tsnet" : "direct"
|
||||
let auth: SSHConfig.Authentication = pubkeySigner.map { .publicKeyCallback($0) } ?? .password(password)
|
||||
config = SSHConfig(host: host, port: port, username: username,
|
||||
@@ -289,6 +303,12 @@ final class SSHTerminalModel: ObservableObject {
|
||||
/// 用户取消 → 保持断开。
|
||||
func dismissHostKeyMismatch() { hostKeyMismatch = nil }
|
||||
|
||||
/// 切换主题时推送新终端配色到当前 raw surface 与所有 tmux pane(新建面板另经 TerminalTheme.txDefault 读取)。
|
||||
func applyTerminalTheme(_ t: TerminalTheme) {
|
||||
_ = state.controller.setTheme(t)
|
||||
tmuxController?.applyTerminalTheme(t)
|
||||
}
|
||||
|
||||
func close() { run(machine.reduce(.closeRequested)) }
|
||||
func enterBackground() {
|
||||
if moshMode { beginBgTask() } // 争取 ~30s 后台执行,mosh 每 3s ack 保活 NAT/WG/DERP
|
||||
@@ -499,6 +519,7 @@ final class SSHTerminalModel: ObservableObject {
|
||||
moshSession = mosh
|
||||
moshHolder.session = mosh // 输入/resize 从此路由到 mosh
|
||||
mosh.start()
|
||||
moshEngaged = true
|
||||
NSLog("MOSHDBG activateMosh ip=\(ip) port=\(port) started")
|
||||
// mosh 接管成功 → 状态机拆掉底层 idle SSH(moshActive 之后忽略其 transportClosed)。
|
||||
run(machine.reduce(.moshEstablished))
|
||||
@@ -557,6 +578,7 @@ final class SSHTerminalModel: ObservableObject {
|
||||
/// 拆除 mosh 会话(兜底全量重建 / 关闭时)。重置引导状态,允许重新引导 mosh-server。
|
||||
private func teardownMosh() {
|
||||
cancelResumeWatchdog()
|
||||
moshEngaged = false
|
||||
moshHolder.session = nil
|
||||
moshSession?.close(); moshSession = nil
|
||||
try? moshRelay?.close(); moshRelay = nil
|
||||
|
||||
Reference in New Issue
Block a user