You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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).
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.
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
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
Phase A this week. Block on it. The triage output of one device session is more valuable than two days of agent polish.
Phase B.1 + B.2 immediately after (parallel agents, both well-scoped). Probably one PR each.
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.
Phase C.1 (TestFlight) once B is done so the first external testers see a usable app.
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.
iOS Foundation — Next Steps
What just landed (foundation)
Five PRs are now on
maincovering the Tauri-Mobile-on-iOS foundation:claudette-mobilecrate +src/ui-mobile/frontendget_chat_snapshotRPC onclaudette-serverfor missed-event recoveryclaudette-mobile(aarch64-apple-darwinclippy + tests)restore_workspaceRPC onclaudette-serverso headless / mobile clients can resume Stopped workspacestauri-actiontosrc-tauri/and stampingsrc-mobilein nightlyEverything 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:
get_chat_snapshotandrestore_workspaceshipped server-side but the mobile UI doesn't call them yet.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
rustup target add aarch64-apple-ios aarch64-apple-ios-simcd src-mobile && cargo tauri ios init(currentlysrc-mobile/gen/is gitignored — produces the Swift wrapper + signing capability config)cargo tauri ios dev— boots empty webview. Camera doesn't work in simulator so use the "Paste connection string" Advanced fallback onConnectScreen(src/ui-mobile/src/screens/ConnectScreen.tsx).cargo tauri ios dev --device <udid>— requires signing identity.End-to-end exercise (against your local dev desktop)
./scripts/dev.sh, click Share this machine.connect_saved(src-mobile/src/commands.rs::connect_saved). The newtransport.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.get_chat_snapshotso the streaming draft will be lost. Note what UX this produces. (This validates the need for the Phase B wiring below.)AskUserQuestion(e.g. "What's our deployment target — staging or prod?"). Confirm bottom sheet appears, options render, submit answer.--planmode to triggerExitPlanMode. Confirm plan card renders, Approve / Deny both work, Deny-with-feedback routes the feedback string back to the agent.ChatScreen.tsx::handleDismissAsk). Without it the CLI subprocess would hang. The fix-commit474b2e1ain 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:
tokio-tungstenitedirect pathPhase 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_snapshotrecoveryCurrent behavior:
ChatScreenconsumesagent-streamandagent-permission-promptevents only. If a broadcastRecvError::Lagged(n)drops events (Copilot finding #3253825789 on PR #840, declined as a tracked follow-up), the UI silently loses them — most painfully anagent-permission-promptfor an AskUserQuestion that's now orphaning the server'spending_permissionsentry.Target behavior: On
ChatScreenmount and after any unlisten/relisten cycle (e.g. background→foreground), callget_chat_snapshotonce. Reconcilepending_controlsagainst the localpendingQueuestate — append any that are server-side-pending but not locally-known.Files:
getChatSnapshot(connectionId, chatSessionId, opts)typed wrapper insrc/ui-mobile/src/services/rpc.ts(mirror the existingloadChatHistory).pending_controlsto the snapshot's TS type definition insrc/ui-mobile/src/types.ts.ChatScreen.tsx::refreshHistory(also rename torefreshSnapshotsince it does more now), reconciling againstpendingQueue.Acceptance:
pending_permissionsentry → the prompt UI renders without needing a freshagent-permission-promptevent.ChatScreentest exercising the snapshot-reconcile path.B.2 —
restore_workspaceUI surfaceCurrent behavior:
WorkspacesScreen.tsxline 41 filters tostatus === "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_workspaceviasendRpc. On success, refresh the workspace list (the workspace moves back to Active).Files:
restoreWorkspace(connectionId, workspaceId)typed wrapper insrc/ui-mobile/src/services/rpc.ts.src/ui-mobile/src/screens/WorkspacesScreen.tsxto render Active + Stopped sections.Acceptance:
WorkspacesScreentest exercising the stopped-section render path.B.3 — iOS Keychain plugin (replaces app-sandbox JSON storage)
Current behavior:
src-mobile/src/storage.rspersistsSavedConnection[]as a plaintext JSON file underapp_local_data_dir(). iOS Data Protection encrypts the app sandbox at rest, so this is reasonable for v1, but the security model section ofsite/src/content/docs/features/mobile-app.mdxexplicitly 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:
src-mobile/ios-plugins/keychain/Swift plugin (Tauri 2 mobile plugin pattern — see Tauri's mobile plugin docs).src-mobile/src/storage.rsto dispatch through the plugin via#[cfg(mobile)]with the existing file storage retained for the desktop-fallback build (cargo tauri dev --bin claudette-mobile).site/src/content/docs/features/mobile-app.mdxSecurity model section to remove the "Keychain is a follow-up" caveat.Acceptance:
#[cfg(not(mobile))]).Phase C — Distribution
C.1 — TestFlight setup
dev.claudette.mobile.cargo tauri ios build --releaseinto a CI workflow that produces a signed.ipa(requires an Apple Developer signing certificate stored in GitHub Actions secrets —AppleCertificate,AppleProvisioningProfile, etc.)..ipato TestFlight on each tagged release (or onmainpush, gated by a manual approval).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:
src-mobile/src/mdns.rslistening on_claudette._tcp.local.(the existingmdns-sdcrate is already in the workspace).ConnectScreen.tsxunder a new "Nearby" section above the existing Saved / Scan / Paste flow.D.2 — Push notifications (APNs)
Most useful for: agent-finished and
AskUserQuestionarrival while app is backgrounded.Requires:
agent-permission-promptevent for an offline subscriberThis 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.cssuses raw hex literals rather than pulling from desktop'ssrc/ui/src/styles/theme.css. The right shape is a sharedsrc/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-mobileis a Tauri 2 mobile build and Android falls out for free. Nobody has tried it. Validation is its own milestone.Recommended sequencing
Done definition for this issue
Issue stays open until:
Phase D items get their own issues as they're scoped.
References
site/src/content/docs/features/mobile-app.mdxsrc-mobile/README.mdCLAUDE.md— Crate structure