- Adds an iOS client for OpenHuman: device pairing via QR code, mascot chat screen, and push-to-talk voice input.
- No Rust core ships on device; the iOS app connects to the desktop core via LAN HTTP, an E2E-encrypted socket.io tunnel, or cloud HTTP fallback.
- All changes are cfg-gated or platform-guarded; the desktop build is unaffected.
- Adds the
tauri-plugin-pttSwift plugin (packages/tauri-plugin-ptt/) for AVAudioEngine + SFSpeechRecognizer on iOS. - Adds CI sanity-check workflow, build scripts, capability catalog entries, and full docs.
Users with iOS devices had no way to interact with their OpenHuman assistant on the go. The desktop app required a local machine. This PR adds the client-side scaffolding and transport layer needed to bridge iOS to an existing desktop core.
The iOS app is a subset of the existing React/TypeScript UI, compiled by Tauri v2 into an iOS bundle. A TransportManager selects the best transport at runtime. Pairing is secured by an X25519 key agreement; all tunnel traffic uses XChaCha20-Poly1305 encryption. The backend is a blind socket.io forwarder -- it never sees plaintext.
| Commit | Layer | Summary |
|---|---|---|
a99537f3 |
Layer 1 | Rust devices domain -- pairing store, RPC handlers, event bus, crypto (src/openhuman/devices/) |
4ea14b78 |
Layer 2 | TS transport refactor -- TransportManager, LanHttpTransport, TunnelTransport, CloudHttpTransport, tunnel crypto (app/src/services/transport/, app/src/lib/tunnel/) |
ba651705 |
Layer 3 | Desktop /settings/devices UI -- DevicesPanel, PairPhoneModal with QR generation and 2-second poll |
3e0e2a67 |
Layer 4 | Tauri shell cfg-gating -- #[cfg(target_os = "ios")] guards on CEF-specific code |
621fec98 |
Layer 5 | iOS app shell -- PairScreen (QR scan via AVCaptureSession), MascotScreen (chat UI) |
5ca6cf21 |
Layer 6 | tauri-plugin-ptt -- Swift PTT plugin (AVAudioEngine, SFSpeechRecognizer, AVSpeechSynthesizer) |
41a6a895 |
Layer 6 fix | PTT Swift fix -- latest transcript tracking + @unchecked Sendable on PTTSpeaker |
| (this PR) | Layers 7+8 | Build scripts, CI, Info.plist, capability catalog, docs, quality pass |
- Vitest: 1957 passed, 3 skipped, 1 todo across 218 test files (includes transport, tunnel, devices, iOS, PTT suites).
- Rust (about_app): 20 passed -- validates catalog uniqueness, Mobile category, and new capability entries.
- cargo check (all three Cargo.toml files): clean (warnings only, pre-existing).
The following only activates on cfg(target_os = "ios") or when explicitly called from iOS screens:
- CEF exclusions in
app/src-tauri/(accounts webviews, etc.) tauri-plugin-pttcommands (start_listening,stop_listening,speak,cancel_speech,list_voices) -- returnNotSupportedon non-iOS targets.packages/tauri-plugin-ptt/ios/Swift sources -- not compiled for desktop.
Desktop users see no change.
- Keychain migration: iOS symmetric session key is in-memory only; persist to Keychain so the app reconnects after restart without re-pairing.
- Event-driven pairing detection:
PairPhoneModalpollsdevices_listevery 2 s. Switch to a socket event subscription when the SSE/socket bridge forDomainEvent::DevicePairedlands. - Full Xcode CI:
cargo check --target aarch64-apple-iosruns withcontinue-on-error: truein the new CI workflow because third-party C deps (cef-dll-sys) may fail without full Xcode on the runner. A follow-up should pin an Xcode-enabled runner and harden this to a hard gate. - APNs push notifications: real-time delivery requires the app to be foregrounded.
- Multi-region tunnel: single backend instance only; no failover.
- Info.plist automation: developer must manually copy
Info.ios.plistkeys into the generated Xcode project aftertauri ios init. Should automate viabundle.iOS.templateonce Tauri v2 stabilises the iOS template pipeline.
tinyhumansai/backend#709 must be merged and deployed before end-to-end pairing works. The devices_create_pairing RPC will return a tunnel registration error until the tunnel:register / tunnel:connect / tunnel:frame socket.io contract is live.
(Requires a physical iPhone or iOS 17+ simulator paired with the desktop app.)
From packages/tauri-plugin-ptt/README.md:
- Permissions dialog appears on first
startListeningcall. - Partial transcripts update while speaking; final transcript matches.
- Hold button to record, release to stop, chat message is sent with transcript.
- TTS plays through speaker by default when iPhone is held away from ear.
- BT headset routes audio correctly; disconnecting mid-recording stops gracefully.
- App backgrounded mid-record produces a final transcript and stops cleanly.
- Phone call interruption emits
ptt://errorwithcode: interrupted. -
cancelSpeechduring TTS emitstts-endedwithfinished: false. -
listVoicesreturns non-empty list ofAVSpeechSynthesisVoiceentries.
Additional pairing flow checks:
- Desktop: Settings > Devices > "Pair iPhone" shows QR code.
- iOS app: PairScreen scans QR and transitions to MascotScreen after handshake.
- Desktop: Devices panel lists the paired device with correct label.
- Desktop: Revoke device removes it from the list; iOS app shows reconnect prompt.
- QR code expiry: code expires after TTL, "Generate new code" creates a fresh session.
PLACEHOLDER: Before opening the PR, attach screenshots of:
- Desktop
/settings/devicespanel with a paired device.- iOS mascot screen showing a conversation.
These require a device with Xcode signing configured and
tinyhumansai/backend#709deployed.
- Tests added or updated (transport, tunnel, devices, iOS, PTT suites -- see coverage statement above).
- Diff coverage note: new Rust code in
src/openhuman/devices/was covered in Layer 1 tests; new TS code inapp/src/services/transport/andapp/src/lib/tunnel/covered by Vitest suites. PTT Swift layer cannot be unit-tested without iOS toolchain (noted in README). - Coverage matrix: N/A for this layer (build scripts, CI, docs, catalog).
- No new external network dependencies (all transport calls use existing mock backend or real backend behind feature flag).
- Manual smoke checklist: iOS path not in
docs/RELEASE-MANUAL-SMOKE.mdyet -- tracked as follow-up. - Linked issue: N/A (tracked via Linear).
- Desktop runtime: no change.
- iOS target: new experimental app bundle (not in release pipeline yet).
packages/tauri-plugin-ptt/is a new crate workspace member; adds to build time only when targeting iOS.- Capability catalog adds three new
mobile.*entries and a newMobilecategory.
- Closes: N/A (new feature)
- Follow-up PR(s): Keychain migration, event-driven pairing, full Xcode CI, APNs.
- Backend: tinyhumansai/backend#709
- Key: N/A
- URL: N/A
- Branch:
feat/ios-client - Commit SHA: (set after final commit)
-
pnpm --filter openhuman-app format:check-- clean -
pnpm typecheck-- clean - Focused tests: Vitest 1957 passed; cargo about_app 20 passed
- Rust fmt/check:
cargo fmt --all+cargo checkon all three Cargo.toml -- clean - Tauri fmt/check: included above
- command:
cargo check --target aarch64-apple-ios - error: May fail on cef-dll-sys C deps without full Xcode; guarded with
continue-on-error: truein CI. - impact: Soft gate only; does not block merge.
- Intended behavior change: Desktop users see new Settings > Devices panel. iOS users can pair and chat.
- User-visible effect: Desktop gains device management UI. iOS app becomes available for sideloading/TestFlight.
- Legacy behavior preserved: All existing desktop flows unaffected. No CEF injection added. No new JS injection in webview accounts.
- Guard/fallback/dispatch parity: PTT commands return
NotSupportedon non-iOS. Transport falls back gracefully.
- Duplicate PR(s): None
- Canonical PR: This PR
- Resolution: N/A