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
122 lines
5.6 KiB
Markdown
122 lines
5.6 KiB
Markdown
# 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).
|