- T-iOS-34's stated acceptance is "最大字号不破版" and it was failing: the key bar
froze its height at 52pt while an AX5 keycap needs 108.24pt, so caps clipped —
recorded as a withKnownIssue rather than fixed. Height now derives from the
content size category (and tracks live changes via registerForTraitChanges);
the known-issue marker is gone, replaced by positive assertions including a
12-category no-clip sweep and a guard that stays red if anyone writes the
constant back. Honest tradeoff: the keycap font is clamped at .accessibility2,
the same policy the design system already applies to dense content, because an
unclamped AX5 bar would eat the terminal. A test pins the clamp so the two
cannot drift.
- Thumbnails silently 401'd on a token-gated host: the pipeline built its own
transport with no token source, and by design never throws, so every preview
degraded to a placeholder with no signal. Assembled from AppEnvironment now.
- Android had zero CI while the token wave shipped 24 files of secret-handling
code. The instrumented leg is workflow_dispatch-only and says why in the file:
no one has ever seen it green on a runner, and a required leg nobody trusts
just produces a false green.
- Android persisted a validated token before the host probe succeeded, stranding
a secret for a host that never paired.
App bundle 534 -> 550 on both simulators, zero known issues. Android 687 -> 691.
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.
T-iOS-1: ios/ XcodeGen project (iOS 17, Swift 6 strict concurrency, ATS per PLAN §5.2), 5 SPM package shells, CI skeleton
T-iOS-2: Origin spike vs real server — URLSessionWebSocketTask custom Origin CONFIRMED (no Starscream); 16MiB replay + EMSGSIZE(40) errno correction written back to plan
T-iOS-3: WireProtocol frozen contract, 59 tests, 100% line coverage, cross-impl vectors vs src/protocol.ts via tsx
T-iOS-4: FakeTransport/FakeClock/FakeHTTPTransport doubles
Verify: independent agent re-ran all acceptance — 6/6 PASS