Skip to content

iOS Foundation — Next Steps (device validation, RPC wiring, Keychain, TestFlight) #858

Description

@jamesbrink

iOS Foundation — Next Steps

What just landed (foundation)

Five PRs are now on main covering the Tauri-Mobile-on-iOS foundation:

PR What
#840 iOS foundation — pair / browse / chat / approve flow over WSS; new claudette-mobile crate + src/ui-mobile/ frontend
#843 get_chat_snapshot RPC on claudette-server for missed-event recovery
#844 macOS CI matrix for claudette-mobile (aarch64-apple-darwin clippy + tests)
#845 restore_workspace RPC on claudette-server so headless / mobile clients can resume Stopped workspaces
#853 CI fix scoping tauri-action to src-tauri/ and stamping src-mobile in nightly

Everything compiles, all CI jobs are green, no warnings under RUSTFLAGS="-Dwarnings". Nothing has been validated on a real iPhone yet.

What's still open

The current state has three classes of gap:

  1. Validation gate — physical device testing. Until this happens, all subsequent polish is speculative.
  2. Wire the merged RPCsget_chat_snapshot and restore_workspace shipped server-side but the mobile UI doesn't call them yet.
  3. Polish / distribution — Keychain, push notifications, TestFlight, mDNS, theme tokens.

Recommended order: validate first, then close the RPC consumption gap, then polish.


Phase A — Device validation (interactive, BLOCKER)

This is the highest-information-density next step. Tauri Mobile has enough platform-specific behavior (WKWebView CSS, iOS sandbox path resolution, TLS-self-signed-cert handling in WKWebView, background→foreground WSS reconnect, cellular MTU) that the real punch list only emerges from running on hardware.

Setup tasks

  • Xcode + iOS Rust targets installed: rustup target add aarch64-apple-ios aarch64-apple-ios-sim
  • Apple Developer account configured in Xcode: free account works for 7-day ad-hoc on-device install
  • Generate the Xcode project: cd src-mobile && cargo tauri ios init (currently src-mobile/gen/ is gitignored — produces the Swift wrapper + signing capability config)
  • Simulator smoke first: cargo tauri ios dev — boots empty webview. Camera doesn't work in simulator so use the "Paste connection string" Advanced fallback on ConnectScreen (src/ui-mobile/src/screens/ConnectScreen.tsx).
  • Physical device install: cargo tauri ios dev --device <udid> — requires signing identity.

End-to-end exercise (against your local dev desktop)

  • Open desktop ./scripts/dev.sh, click Share this machine.
  • On phone, tap Scan QR code → point at the QR in the desktop modal. Confirm pair succeeds, fingerprint is captured, session token persists.
  • Force-quit + reopen the app → confirm the saved-server row reconnects via connect_saved (src-mobile/src/commands.rs::connect_saved). The new transport.close() cleanup landed in feat(mobile): iOS foundation — pair, browse, chat, approve over WSS #840 should mean no socket lingers on the server side; eyeball the desktop logs to confirm.
  • Open a workspace, open a chat session, send a prompt. Watch the streaming-draft bubble fill in.
  • Force-quit mid-stream, reopen — this is the recovery scenario. Today the mobile UI does NOT call get_chat_snapshot so the streaming draft will be lost. Note what UX this produces. (This validates the need for the Phase B wiring below.)
  • Send a prompt that requires AskUserQuestion (e.g. "What's our deployment target — staging or prod?"). Confirm bottom sheet appears, options render, submit answer.
  • Send a prompt with --plan mode to trigger ExitPlanMode. Confirm plan card renders, Approve / Deny both work, Deny-with-feedback routes the feedback string back to the agent.
  • Dismiss-without-responding the AskUserQuestion sheet → confirm the auto-deny actually fires (ChatScreen.tsx::handleDismissAsk). Without it the CLI subprocess would hang. The fix-commit 474b2e1a in PR feat(mobile): iOS foundation — pair, browse, chat, approve over WSS #840 wired this; this validates it works on hardware.

Triage outputs

Track whatever surfaces in this issue's comments — typical hits:

  • Safe-area padding around the composer / sheet (Dynamic Island / home indicator)
  • Reconnect-on-foreground UX after iOS backgrounded the WSS
  • Plan card width on smaller iPhones (SE / 12 mini)
  • Any TLS handshake quirks WKWebView introduces vs the desktop's tokio-tungstenite direct path

Phase B — Wire the merged RPCs

Both shipped server-side in PRs #843 / #845. The mobile UI doesn't consume them yet. These are agent-driveable the moment Phase A's punch list is triaged (or in parallel if the punch list is small).

B.1 — get_chat_snapshot recovery

Current behavior: ChatScreen consumes agent-stream and agent-permission-prompt events only. If a broadcast RecvError::Lagged(n) drops events (Copilot finding #3253825789 on PR #840, declined as a tracked follow-up), the UI silently loses them — most painfully an agent-permission-prompt for an AskUserQuestion that's now orphaning the server's pending_permissions entry.

Target behavior: On ChatScreen mount and after any unlisten/relisten cycle (e.g. background→foreground), call get_chat_snapshot once. Reconcile pending_controls against the local pendingQueue state — append any that are server-side-pending but not locally-known.

Files:

  • Add getChatSnapshot(connectionId, chatSessionId, opts) typed wrapper in src/ui-mobile/src/services/rpc.ts (mirror the existing loadChatHistory).
  • Add pending_controls to the snapshot's TS type definition in src/ui-mobile/src/types.ts.
  • Call from ChatScreen.tsx::refreshHistory (also rename to refreshSnapshot since it does more now), reconciling against pendingQueue.

Acceptance:

  • Cold-start a chat with an active server-side pending_permissions entry → the prompt UI renders without needing a fresh agent-permission-prompt event.
  • Existing tests still pass; add a ChatScreen test exercising the snapshot-reconcile path.

B.2 — restore_workspace UI surface

Current behavior: WorkspacesScreen.tsx line 41 filters to status === "Active", so Stopped workspaces are invisible. Users with Stopped workspaces have no path to resume them from the phone. (Copilot finding #3253825838 on PR #840, declined as a tracked follow-up.)

Target behavior: Render a "Stopped" section below "Active" with a Resume button per row that calls restore_workspace via sendRpc. On success, refresh the workspace list (the workspace moves back to Active).

Files:

  • Add restoreWorkspace(connectionId, workspaceId) typed wrapper in src/ui-mobile/src/services/rpc.ts.
  • Update src/ui-mobile/src/screens/WorkspacesScreen.tsx to render Active + Stopped sections.
  • Style the Stopped section subtly (dimmer than Active) so it's clearly secondary.

Acceptance:

  • Stop a workspace from the desktop, observe it surface in the Stopped section on phone.
  • Tap Resume → workspace re-creates worktree, moves to Active section.
  • Existing tests still pass; add a WorkspacesScreen test exercising the stopped-section render path.

B.3 — iOS Keychain plugin (replaces app-sandbox JSON storage)

Current behavior: src-mobile/src/storage.rs persists SavedConnection[] as a plaintext JSON file under app_local_data_dir(). iOS Data Protection encrypts the app sandbox at rest, so this is reasonable for v1, but the security model section of site/src/content/docs/features/mobile-app.mdx explicitly flags Keychain as the long-term home.

Target behavior: Replace file-based storage with iOS Keychain via a small Swift Tauri plugin. The session_token + fingerprint move to Keychain; non-secret metadata (host, port, name, created_at) can stay in the file or migrate too.

Files:

  • New src-mobile/ios-plugins/keychain/ Swift plugin (Tauri 2 mobile plugin pattern — see Tauri's mobile plugin docs).
  • Update src-mobile/src/storage.rs to dispatch through the plugin via #[cfg(mobile)] with the existing file storage retained for the desktop-fallback build (cargo tauri dev --bin claudette-mobile).
  • Update site/src/content/docs/features/mobile-app.mdx Security model section to remove the "Keychain is a follow-up" caveat.

Acceptance:

  • Pair flow stores session token in Keychain on iOS.
  • Force-uninstall + reinstall → saved server list survives (Keychain's value-add over file).
  • Desktop fallback build still works (file storage path kept under #[cfg(not(mobile))]).

Phase C — Distribution

C.1 — TestFlight setup

  • Create a TestFlight app record in App Store Connect with bundle id dev.claudette.mobile.
  • Wire cargo tauri ios build --release into a CI workflow that produces a signed .ipa (requires an Apple Developer signing certificate stored in GitHub Actions secrets — AppleCertificate, AppleProvisioningProfile, etc.).
  • Upload the .ipa to TestFlight on each tagged release (or on main push, gated by a manual approval).
  • Document the install flow in mobile-app.mdx.

C.2 — App Store submission

Way bigger scope; defer until TestFlight has been used by a small group and feedback has cycled.


Phase D — Polish

D.1 — mDNS Nearby discovery on iOS

The server already advertises _claudette._tcp.local. on the LAN; the desktop's sidebar already populates a "Nearby" list from this. The iOS app could do the same.

Files:

  • New src-mobile/src/mdns.rs listening on _claudette._tcp.local. (the existing mdns-sd crate is already in the workspace).
  • Surface discovered servers in ConnectScreen.tsx under a new "Nearby" section above the existing Saved / Scan / Paste flow.

D.2 — Push notifications (APNs)

Most useful for: agent-finished and AskUserQuestion arrival while app is backgrounded.

Requires:

  • Apple Push Notification cert in App Store Connect
  • Server-side push channel — the WSS server would have to track an APNs device token per session and call out to Apple's push gateway when emitting an agent-permission-prompt event for an offline subscriber
  • iOS-side notification permission prompt + payload handler

This is the heaviest item on the list — leave for after TestFlight.

D.3 — Mobile theme tokens

Copilot finding #3253825823 on PR #840 noted that src/ui-mobile/src/styles.css uses raw hex literals rather than pulling from desktop's src/ui/src/styles/theme.css. The right shape is a shared src/ui-theme/ package both UIs depend on.

Deferred as a follow-up once we know whether mobile actually wants the same palette (currently mobile uses deeper blacks for OLED).

D.4 — Android verification

claudette-mobile is a Tauri 2 mobile build and Android falls out for free. Nobody has tried it. Validation is its own milestone.


Recommended sequencing

  1. Phase A this week. Block on it. The triage output of one device session is more valuable than two days of agent polish.
  2. Phase B.1 + B.2 immediately after (parallel agents, both well-scoped). Probably one PR each.
  3. Phase B.3 (Keychain) after the file-based storage's UX has been observed on device — you might find the file storage is fine in practice and Keychain becomes a v1.1 nice-to-have.
  4. Phase C.1 (TestFlight) once B is done so the first external testers see a usable app.
  5. Phase D in whatever order surfaces from real usage.

Done definition for this issue

Issue stays open until:

  • Phase A is complete with a triage list documented in comments
  • Phase B.1 + B.2 have shipped (PRs merged)
  • Phase B.3 has shipped OR has been explicitly deferred to a v1.1 issue with rationale
  • Phase C.1 has shipped OR a separate TestFlight issue is open and owned

Phase D items get their own issues as they're scoped.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority: highShould be resolved soon — significant impact on user experience

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions