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.
DEVELOPMENT_TEAM=C738Z66SRW + target-level CODE_SIGN_IDENTITY (XcodeGen's
settingPreset injects the deprecated "iPhone Developer" at target level, so a
project-level override cannot win). Device build now BUILD SUCCEEDED on the free
personal team with an on-the-fly 7-day profile.
aps-environment lives in a new WebTerm.entitlements that is NOT attached by
default: CODE_SIGN_ENTITLEMENTS expands ${WEBTERM_PUSH_ENTITLEMENTS}, empty
unless set at xcodegen time. Proven both ways — attaching it on a free team
fails with "Personal development teams ... do not support the Push
Notifications capability".
Also pins SwiftTerm to exactVersion 1.13.0: the floating "from: 1.13.0" now
resolves to 1.15.0, whose new public hasActiveSelection collides with the local
one in TerminalScreen.swift:328 and broke every generate+build.
Wire the SecureEnclave enroll library into a real flow (login->bearer->CSR->
/device/enroll->keychain identity), presented on the existing mTLS path; add a
rotation scheduler. Atomic keychain replace (add-before-delete); renew body is
{csr}-only; renewal-failing surfaced in the UI. ClientTLS 48 tests pass.
New brand icon (Orbit — a node circling a block-cursor core, amber-gold on
warm-dark, matching the desktop theme):
- iOS: Assets.xcassets/AppIcon.appiconset (opaque 1024, was no icon)
- desktop: resources/icon.png (rounded 1024 → .icns at package time)
- desktop tray: trayTemplate.png/@2x — a proper macOS TEMPLATE (black shapes on
transparent, system-tinted) replacing the old white blob
- web: public/icon.svg (vector favicon/PWA)
Master SVGs rendered with rsvg-convert.
Amber-gold accent is designed for the desktop's warm-dark background; on light
mode gold text/badges wash out. Match the desktop (dark theme by default):
.preferredColorScheme(.dark) at the root. DirtyBadge becomes a soft filled
capsule (amber on amber-18%) instead of amber-on-quaternary, readable in any mode.
Match the desktop/web theme (public/style.css): accent #E3A64A gold (dark) /
#C9892F (light) replacing the indigo, +onAccent #1A1305 dark ink for gold-fill
buttons, +accentSoft. Status colors realigned to the web warm palette
(#46D07F/#F5B14C/#FF6B6B). Terminal canvas fixed to the desktop warm-dark
(#100F0D bg / #ECE9E3 fg) with gold caret/selection. DSButtonStyle primary now
uses onAccent (dark) instead of white for contrast on gold. All via the single
accent token + DS palette — no scattered edits.
Verified: iPhone 16 290 + iPad Pro 11 290 tests green (DesignSystemTests assert
the exact gold + web-status RGB in both schemes).
T-iOS-18 (F-iOS-1..13 walkthrough) + T-iOS-19 (security audit vs built artifacts): both
PASS_WITH_FINDINGS, 0 CRITICAL/HIGH; real-device items DEFERRED with manual checklists.
Fixes (1 MED + 3 LOW, all closed):
- WebTermUITests: the single scripted happy path (manual-entry pair → list → attach →
KeyBar ^L → injected held gate via POST /hook/permission → tap Approve → server-side
behavior==allow assertion). Green twice locally (fresh + already-paired branches).
- ios.yml: ui-test leg (server boot + TEST_RUNNER_ env as PROCESS env) + iOS17-floor leg
(loud-skip if runtime absent, hard-fail if present and red); dropped CODE_SIGNING_ALLOWED=NO
from app-tests leg (unsigned hosts get -34018 from the real keychain — measured)
- URLSessionHTTPTransport defaults to .ephemeral (no disk cache of preview terminal bytes)
- TARGETED_DEVICE_FAMILY moved to target level (built plist now UIDeviceFamily [1] only)
Final: 306 automated checks green (214 pkg + 76 app + 10 integration + XCUITest); server src/
untouched; P0 complete (19/19 tasks)
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