feat(ios,android): P2 wave, git panel, token UX, per-host WS token, docs

App layer, four sequential slices (a shared .xcodeproj means adding files
regenerates it, so these could not run in parallel):

- token UX end to end: pairing prompts for a token when a host 401s, POST /auth
  validates it, and 204-without-Set-Cookie is correctly read as "this server has
  auth disabled" rather than "authenticated". A host paired before the token was
  turned on recovers by re-pairing in place. Remove-host now exists and finally
  gives PushRegistrar.handleHostRemoved a caller.
- project git panel + worktree lifecycle (T-iOS-32) + claude --resume history —
  the parity gap with Android and the web front end.
- terminal search (T-iOS-33) and voice PTT (T-iOS-31) with an epoch guard so a
  session switch between dictation and confirm cannot inject into the wrong
  session.
- theme + Dynamic Type (T-iOS-34) and web ?join= interop (T-iOS-35). RootView no
  longer hard-locks .preferredColorScheme(.dark).

Also unpins SwiftTerm to 1.15.0 by dropping the local hasActiveSelection that
collided with the upstream one, verified green from a fresh derivedDataPath.

Includes the two HIGH fixes the security review found:
- iOS resolved the WS token host-independently, so a token-gated host sitting
  next to an open one could never open a terminal and no on-screen remedy could
  fix it. Now one transport per host; cross-host leakage is structurally
  impossible since both read paths return only that host's own value.
- Android reported the host's own git-credential 401 (git-ops.ts:108, "Push
  authentication required on the host.") as "your access token is wrong", because
  a blanket 401 mapping ran ahead of the per-route one. Git-write routes are now
  ROUTE_DEFINED and keep the server's message.

And the doc sync: README/ios README no longer claim the client is unmerged on
feat/ios-client, the Clients section finally lists Android, and the plan
checkboxes reflect what is actually built.

iOS 534 app tests + 452 package tests; Android 687 tests.
This commit is contained in:
Yaojia Wang
2026-07-30 15:57:41 +02:00
parent 9114630c3a
commit 284cfd193a
70 changed files with 10271 additions and 358 deletions

View File

@@ -0,0 +1,98 @@
import SwiftUI
import UIKit
/// T-iOS-34 · ****
///
/// ""
///
///
/// - = `#100F0D` / `#ECE9E3` web `--bg` / `--text`****
/// - = `#F6F7F9` / `#1A1D24` web `public/settings.ts`
/// `THEMES.light`iOS
///
/// caret / selection **** accent `#E3A64A` /
/// `#C9892F`
///
/// `SwiftTerm.TerminalView` `nativeBackgroundColor` setter
/// UIColor `terminal.backgroundColor``iOSTerminalView.swift:1207`
/// `traitCollectionDidChange` ** UIColor "
/// "** `apply`
/// `colors(for:)` `dynamic*` UIColor
/// trait
enum TerminalPalette {
/// ****
/// 便 SwiftTerm便
struct Colors: Equatable {
let background: UIColor
let foreground: UIColor
let caret: UIColor
let selection: UIColor
}
///
private enum Dark {
static let background: UInt32 = 0x100F_0D
static let foreground: UInt32 = 0xECE9_E3
}
/// web `THEMES.light`
private enum Light {
static let background: UInt32 = 0xF6F7_F9
static let foreground: UInt32 = 0x1A1D_24
}
static func colors(for scheme: ColorScheme) -> Colors {
let style = scheme.userInterfaceStyle
let accent = DS.Palette.accentUIColor()
.resolvedColor(with: UITraitCollection(userInterfaceStyle: style))
let isDark = style == .dark
return Colors(
background: UIColor(hex: isDark ? Dark.background : Light.background),
foreground: UIColor(hex: isDark ? Dark.foreground : Light.foreground),
caret: accent,
selection: accent
)
}
/// SwiftUI/UIKit trait
static func dynamicBackground() -> UIColor {
UIColor { trait in colors(for: trait.colorScheme).background }
}
///
static func dynamicForeground() -> UIColor {
UIColor { trait in colors(for: trait.colorScheme).foreground }
}
}
// MARK: - ColorScheme UIUserInterfaceStyle
extension ColorScheme {
/// SwiftUI UIKit`ColorScheme` light/dark case
var userInterfaceStyle: UIUserInterfaceStyle {
self == .dark ? .dark : .light
}
}
extension UITraitCollection {
/// UIKit SwiftUI`.unspecified` iOS
var colorScheme: ColorScheme {
userInterfaceStyle == .dark ? .dark : .light
}
}
// MARK: - Hex
extension UIColor {
/// `0xRRGGBB` sRGB token /CSS
/// 01
convenience init(hex: UInt32, alpha: CGFloat = 1) {
self.init(
red: CGFloat((hex >> 16) & 0xFF) / 255,
green: CGFloat((hex >> 8) & 0xFF) / 255,
blue: CGFloat(hex & 0xFF) / 255,
alpha: alpha
)
}
}