# jcode iOS App > Status: v2 rebuild. Pure Swift. This replaces the earlier prototype and the > Rust-mobile-core/simulator direction, both removed in the `ios-app-restart` > branch history. ## Product definition A native iOS remote control for jcode servers running on your own machines. The phone renders conversations and drives sessions; all heavy lifting (LLM calls, tools, git, files, MCP) stays on the server. Reachability is assumed to be Tailscale (or LAN); the app never talks to LLM providers directly. Design identity: dark, calm, terminal-native without being retro. Mint accent (`#4DD9A6`) for live/connected state. Dense information in touchable cards. ## Architecture decision: pure Swift The app is a thin client over an existing, server-owned protocol. Behavior worth sharing lives server-side already. A shared Rust core would duplicate the server protocol a third time and require an FFI bridge, custom renderer work, and a parallel simulator to stay honest. Instead: - **Swift owns the client.** SwiftUI for UI, Swift 6 concurrency, `@Observable`. - **The server protocol is the single source of truth.** The Swift codec is validated by fixture tests against real wire JSON; drift fails tests. - **Testability without devices** comes from layering, not a simulator: everything below the view layer builds and tests on macOS via `swift test`. ## Layering ``` ios/ Package.swift SPM package, builds on macOS + iOS Sources/JCodeKit/ platform-free client core (no UIKit) Gateway.swift endpoints: /health, /pair, /ws Pairing.swift pairing code -> auth token Wire.swift Request/ServerEvent codecs (NDJSON over WS) Transport.swift WebSocketTransport protocol + URLSession impl Connection.swift actor: connect/auth/reconnect, AsyncStream SessionReducer.swift pure state machine: events -> transcript/app state CredentialStore.swift Keychain-backed server credentials (protocol + impl) Sources/JCodeMobile/ SwiftUI app shell (iOS only) JCodeMobileApp.swift entry AppModel.swift @Observable glue: Connection + Reducer -> views Views/ Pairing, Chat, Transcript, Sessions, Settings QRScannerView.swift camera pairing Theme.swift colors/typography tokens Tests/JCodeKitTests/ swift test on macOS: codec fixtures, reducer, pairing project.yml XcodeGen spec for the app target ``` Rules: - `JCodeKit` never imports UIKit/SwiftUI. It must compile for macOS so the whole behavior layer is testable headlessly by agents on this machine. - Views contain no protocol or state-transition logic. `AppModel` only forwards actions and publishes reducer output. - `SessionReducer` is a pure function `(State, ServerEvent) -> State` (plus local user intents). All streaming/tool/session edge cases are unit tested there, replacing the old Rust simulator's role. ## Protocol Server side (already shipped, unchanged): - `jcode pair` CLI generates a 6-digit code (5 min TTL) and QR with `jcode://pair?host=H&port=P&code=C`. - `POST http://host:7643/pair` with `{code, device_id, device_name}` returns `{token, server_name, server_version}`. Token is stored hashed server-side. - `GET /health` for reachability checks. - `ws://host:7643/ws?token=...` upgrades to a WebSocket carrying the same newline-delimited JSON protocol as Unix-socket TUI clients (`crates/jcode-protocol/src/wire.rs`, `#[serde(tag = "type")]`). Client v1 requests: `subscribe`, `message`, `cancel`, `soft_interrupt`, `ping`, `get_history`, `resume_session`, `set_model`, `rename_session`, `clear`. Client v1 events consumed: `ack`, `text_delta`, `reasoning_delta`, `reasoning_done`, `text_replace`, `tool_start`, `tool_input`, `tool_exec`, `tool_done`, `message_end`, `done`, `error`, `pong`, `state`, `session`, `session_renamed`, `history`, `model_changed`, `available_models_updated`, `tokens`, `interrupted`, `status_detail`, `notification`, `compaction`. Unknown event types are ignored by design (forward compatibility). ## Feature scope v1 (this rebuild): - Pair via QR scan or manual host/port/code; multiple saved servers in Keychain - Connect/disconnect lifecycle with automatic reconnect + backoff - Chat: send, streamed assistant text, markdown rendering, reasoning indicator - Tool calls rendered as collapsible cards with live status - Interrupt (cancel) and soft-interrupt (queue a message mid-run) - Session list (from history payload), switch via `resume_session`, rename - Model picker from `available_models` - Token usage + connection status surfaces - Error and disconnect banners that never silently drop user input Later (explicitly out of v1): push notifications (APNs), Live Activities, voice input, image attachments, tool approval UX, ambient/swarm dashboards, widgets, Mac Catalyst polish. ## Testing strategy 1. `swift test` (macOS, no Xcode project needed): - codec round-trips against fixture JSON captured from the real server - `SessionReducer` streaming scenarios (deltas, tool lifecycle, history replay, interrupts, errors, reconnect resubscribe) - pairing client against a stubbed URLProtocol - connection actor against a fake `WebSocketTransport` 2. `xcodebuild build` of the app target (XcodeGen) keeps the UI compiling. 3. Manual/automated device pass via `xcrun simctl` once a simulator runtime is installed; not a CI gate. ## CI A macOS job runs `swift test` in `ios/` plus `xcodegen` + `xcodebuild build` for the app target. TestFlight delivery can be re-added later (the previous Codemagic pipeline was removed with the prototype).