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

@@ -44,11 +44,21 @@ struct DesignSystemTests {
#expect(!approxEqual(waiting, stuck))
}
@Test("semantic status colors match the desktop/web status palette")
/// T-iOS-34 **** token "" scheme-adaptive
/// = web = `Tokens.swift`
/// `UIColor(color).getRed(...)` ** trait**
/// " web
/// " ** trait**
/// `AppThemeTests`
@Test("semantic status colors match the desktop/web status palette (dark scheme)")
func statusColorsMatchDirection() {
assertColor(StatusStyle.style(for: DisplayStatus.working).color, r: 70, g: 208, b: 127) // #46D07F web --green
assertColor(StatusStyle.style(for: DisplayStatus.waiting).color, r: 245, g: 177, b: 76) // #F5B14C web --amber
assertColor(StatusStyle.style(for: DisplayStatus.stuck).color, r: 255, g: 107, b: 107) // #FF6B6B web --red
let dark = UITraitCollection(userInterfaceStyle: .dark)
assertColor(StatusStyle.style(for: DisplayStatus.working).color, in: dark,
r: 70, g: 208, b: 127) // #46D07F web --green
assertColor(StatusStyle.style(for: DisplayStatus.waiting).color, in: dark,
r: 245, g: 177, b: 76) // #F5B14C web --amber
assertColor(StatusStyle.style(for: DisplayStatus.stuck).color, in: dark,
r: 255, g: 107, b: 107) // #FF6B6B web --red
}
@Test("the wire ClaudeStatus bridge covers all five cases")
@@ -150,6 +160,13 @@ struct DesignSystemTests {
assertRGBA(rgba(color), r: r, g: g, b: b)
}
/// Same, but resolving an adaptive token in an explicit scheme.
private func assertColor(
_ color: Color, in traits: UITraitCollection, r: Int, g: Int, b: Int
) {
assertRGBA(rgba(UIColor(color).resolvedColor(with: traits)), r: r, g: g, b: b)
}
private func assertRGBA(_ value: RGBA, r: Int, g: Int, b: Int, tolerance: CGFloat = 0.02) {
#expect(abs(value.r - CGFloat(r) / 255) < tolerance)
#expect(abs(value.g - CGFloat(g) / 255) < tolerance)