Files
wehub-resource-sync a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

5.6 KiB

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<ServerEvent>
    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).