Feature: Mobile Builds (iOS & Android)
Date: 2026-02-12 Status: M1+M2 IMPLEMENTED — M3 Planned Phase: 3d (Mobile), moved up from Phase 4
1. Context & Motivation
Morphee is currently a desktop app (macOS, Windows, Linux) built on Tauri v2, plus a web frontend. To reach users where they spend most of their time — especially families, teachers, and teams — iOS and Android builds are essential.
Tauri v2 shipped stable mobile support in October 2024. The existing codebase already has partial mobile readiness:
#[cfg_attr(mobile, tauri::mobile_entry_point)]inlib.rscrate-type = ["lib", "cdylib", "staticlib"]inCargo.toml(staticlib needed for iOS)- Frontend runtime detection (
isTauri()) and dual-backend routing (memoryClient,fsClient) - Responsive CSS with mobile breakpoints
The challenge is that 2 of 4 Rust crates (fastembed, lancedb) do not compile for mobile targets, requiring a phased approach.
2. Options Investigated
Option A: Tauri v2 Mobile (Chosen)
Use Tauri v2's built-in iOS/Android support. Same codebase, same React frontend, Rust backend runs natively on device.
- Pros: Single codebase for all 5 platforms. Aligns with offline-first vision. Rust code runs natively. Existing
#[cfg_attr(mobile)]support. Consistent Integration/Interface architecture. - Cons:
fastembedandlancedbdon't compile for mobile — need conditional compilation then alternatives. Some Tauri plugins are desktop-only. iOS build toolchain has rough edges. App store review overhead. - Estimated effort: M (Phase M1) + L (Phase M2) + L (Phase M3)
Option B: Progressive Web App (PWA) — Rejected
Ship the existing React frontend as a PWA.
- Pros: Zero build complexity. Works on all mobile browsers immediately.
- Cons: No Rust backend (no local ML, no LanceDB, no git). No real push notifications on iOS. No offline capability. Contradicts the core vision.
- Why rejected: Morphee's differentiator is local compute and offline operation. A PWA can't access any of that.
Option C: React Native — Rejected
Build a separate mobile app using React Native.
- Pros: Mature mobile ecosystem. Native UX patterns.
- Cons: Completely separate codebase. No Rust integration. Doubles maintenance. Diverges from Tauri/Rust architecture.
- Why rejected: Maintaining two frontends with different tech stacks is not viable for our team size. The Rust backend is core to the product.
Option D: Capacitor — Rejected
Wrap the existing Vite/React app in Capacitor for iOS/Android.
- Pros: Reuses existing React code. Simpler than Tauri mobile.
- Cons: No Rust backend on mobile. Another framework alongside Tauri. No offline ML.
- Why rejected: Same fundamental problem as PWA — no access to Rust. Adding a second hybrid framework creates fragmentation.
3. Decision
Chosen approach: Option A — Tauri v2 Mobile
Reasoning: It's the only option that preserves the Rust backend and offline-first vision. The codebase is already 80% ready (mobile entry point, staticlib, runtime detection). The crate compatibility issues are solvable with conditional compilation (Phase M1) and alternative crates (Phase M2).
Trade-offs accepted:
- Phase M1 mobile will be online-only (Python backend required) — offline comes in Phase M2/M3
fastembedmust be replaced bycandlefor mobile embeddings (pure Rust, no C++ dependency)lancedbmust be replaced by SQLite + vector extension on mobile (until lance fixes Android/iOS targets)- Mobile UX will start as "desktop layout on mobile" and improve iteratively
4. Technical Analysis
Rust Crate Compatibility
| Crate | iOS | Android | Issue | Mobile Alternative |
|---|---|---|---|---|
fastembed v5 (ONNX) | No prebuilt binaries | No prebuilt binaries | ort only ships desktop ONNX Runtime binaries | candle (pure Rust, GGUF/ONNX, no C++ dep) |
lancedb v0.26 | Untested, SIMD fails | Fails — lance#2411 | SIMD CPU detection only handles macOS/Linux | SQLite + sqlite-vec or in-memory HNSW |
git2 v0.20 (vendored) | Works | Works (NDK toolchain) | C library cross-compiles via cc crate | N/A — use as-is |
keyring v3 | Works (apple-native) | Needs alternative | keyring doesn't support Android | android-keyring crate or Kotlin Tauri plugin |
Tauri Plugin Compatibility
| Plugin | Desktop | Mobile | Impact on Morphee |
|---|---|---|---|
| Shell (currently used) | Yes | Android only (limited) | Low — only used for git subprocess (we use git2 instead) |
| Notifications | Yes | Yes | Good — push notifications work |
| Biometric | No | Yes | Opportunity — biometric unlock on mobile |
| Dialog, Clipboard, FS | Yes | Yes | Good — all work |
| HTTP Client | Yes | No | None — we use browser fetch() |
| WebSocket plugin | Yes | No | None — we use browser WebSocket |
Conditional Compilation Strategy
// In Cargo.toml — desktop-only dependencies
[target.'cfg(not(any(target_os = "ios", target_os = "android")))'.dependencies]
fastembed = "5"
lancedb = "0.26"
arrow-array = "57"
arrow-schema = "57"
// In Cargo.toml — all platforms
[dependencies]
git2 = { version = "0.20", features = ["vendored-libgit2"] }
// In Cargo.toml — platform-specific keyring
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
keyring = { version = "3", features = ["apple-native"] }
[target.'cfg(target_os = "windows")'.dependencies]
keyring = { version = "3", features = ["windows-native"] }
[target.'cfg(target_os = "linux")'.dependencies]
keyring = { version = "3", features = ["linux-native"] }
// In embeddings.rs — conditional module
#[cfg(not(any(target_os = "ios", target_os = "android")))]
mod desktop_embeddings { /* fastembed implementation */ }
#[cfg(any(target_os = "ios", target_os = "android"))]
mod mobile_embeddings { /* stub or candle implementation */ }
Mobile-Specific Tauri Configuration
Tauri v2 supports per-platform config. The main tauri.conf.json stays as-is for desktop. Mobile gets its own project files:
- iOS:
src-tauri/gen/apple/(Xcode project, generated bytauri ios init) - Android:
src-tauri/gen/android/(Gradle project, generated bytauri android init)
Key mobile config differences:
- No window chrome, no min width/height, no resizable
- Bundle identifier:
app.morphee.mobile(iOS) /app.morphee.android(Android) - Deep link schemes for SSO callback:
morphee://auth/callback - iOS:
PrivacyInfo.xcprivacyfor App Store requirements
CI/CD: GitHub Actions for Mobile
iOS Build Job:
- Runner:
macos-latest(already used for desktop macOS) - Requires: Xcode (pre-installed), CocoaPods
- Apple signing: certificates already in GitHub Secrets
- Rust targets:
aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios - Output:
.ipafile uploaded to GitHub Release + App Store Connect (viaxcrun altoolor Fastlane)
Android Build Job:
- Runner:
ubuntu-latestormacos-latest - Requires: Android SDK, NDK, JDK
- Rust targets:
aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android - Signing: Android keystore in GitHub Secrets
- Output:
.apk/.aabuploaded to GitHub Release + Google Play Console
5. Implementation Plan
| Step | Description | Effort | Dependencies |
|---|---|---|---|
| M1.1 | Tauri mobile project scaffolding (tauri ios init, tauri android init) | S | None |
| M1.2 | Conditional compilation — gate fastembed + lancedb behind cfg(not(mobile)) | S | M1.1 |
| M1.3 | Mobile Rust stubs — embedding/vector commands return "not available on mobile" | S | M1.2 |
| M1.4 | Frontend graceful degradation — isMobile() detection, skip local ML features | S | M1.2 |
| M1.5 | Mobile-specific tauri.conf.json adjustments (no window chrome, deep links) | S | M1.1 |
| M1.6 | Touch/responsive UI polish (navigation, chat input, gestures) | M | M1.1 |
| M1.7 | CI/CD — GitHub Actions for iOS build + signing | M | M1.1 |
| M1.8 | CI/CD — GitHub Actions for Android build + signing | M | M1.1 |
| M1.9 | Test on physical devices + simulators, fix platform-specific issues | M | M1.1–M1.8 |
| M1.10 | App Store + Google Play initial submission | S | M1.9 |
| M2.1 | Replace fastembed with candle-based embeddings on mobile (pure Rust) | L | M1 complete |
| M2.2 | Replace lancedb with SQLite + vector extension on mobile | L | M1 complete |
| M2.3 | Android keystore integration for keyring | M | M1 complete |
| M2.4 | Push notifications (Tauri notifications plugin) | S | M1 complete |
| M2.5 | Deep linking for SSO callbacks on mobile | S | M1 complete |
| M3.1 | Local LLM on mobile via candle (small quantized models) | L | M2.1 |
| M3.2 | Full offline memory system on mobile | L | M2.1, M2.2 |
| M3.3 | Background sync + queued actions | M | M3.2 |
| M3.4 | Auth token caching for offline → online transition | S | M3.2 |
Phase M1 total: Medium (sum of many S items + a few M items) Phase M2 total: Large Phase M3 total: Extra Large
6. Questions & Answers
| Question | Answer |
|---|---|
| Target both platforms simultaneously? | Yes — same priority for iOS and Android |
| Where in the roadmap? | Moved up to Phase 3d (after SSO, before Channels) |
| Apple Developer + Google Play accounts? | Both available. Apple certs already in GitHub Secrets |
| OK with online-only mobile initially? | Yes for Phase M1, but offline is high priority (M2/M3) |
| Why not just compile the same crates for mobile? | fastembed depends on ONNX Runtime C++ prebuilt binaries (desktop-only). lancedb has a known Android build failure in SIMD detection (lance#2411). Pure-Rust alternatives (candle, SQLite+vec) solve this. |
Will candle work on mobile? | Yes — it's pure Rust (no C++ build chain). It supports Metal on iOS and can use CPU on Android. Same crate planned for local LLM in Phase 3. |
| Impact on desktop? | None. Desktop continues using fastembed + lancedb. Conditional compilation keeps the two paths separate. |
7. Open Items
- Verify
git2vendored cross-compilation for Android NDK — confirmed working (cc crate handles cross-compilation) - Decide on Android keystore approach — SQLite vault chosen (M2 implemented, replaces keyring stub)
- Evaluate
sqlite-vecvs in-memory HNSW for mobile vector search — rusqlite brute-force cosine chosen (M2 implemented, sufficient for mobile-scale data) - Define minimum device requirements (iOS version, Android API level)
- Plan TestFlight (iOS) and Internal Testing (Android) beta distribution
- Research Tauri's mobile background execution model (iOS app lifecycle, Android services)
- Evaluate mobile binary size impact (Rust + WebView)
- Plan model size constraints for on-device ML (mobile storage/memory limits)
8. Risks & Mitigations
| Risk | Impact | Mitigation |
|---|---|---|
| iOS Xcode integration fragility | Build failures in CI | Pin Xcode version, test locally before CI changes |
git2 fails to cross-compile | No local git on mobile | Fall back to Python backend git endpoints |
| Mobile binary size too large | App store rejection (>200MB) | Strip debug symbols, optimize Rust release profile, defer ML models to on-demand download |
| WebView performance on low-end Android | Sluggish UI | Performance testing early, optimize React rendering, consider lighter CSS |
| App Store review rejection | Delayed launch | Follow Apple/Google guidelines from the start, prepare privacy policy + data declarations |
candle on mobile has undiscovered issues | Phase M2 blocked | Test candle cross-compilation early (in M1 as a spike) |
| ONNX Runtime builds from source for mobile | Fallback if candle doesn't cover all models | Very complex, last resort — avoid this path |
9. References
- Tauri v2 Mobile Prerequisites
- Tauri v2 Development Guide
- Tauri v2 Stable Release Blog
- LanceDB Android Build Issue #2411
- ort (ONNX Runtime Rust) Linking Docs
- ONNX Runtime Mobile Build
- candle (Hugging Face Rust ML)
- keyring-rs Platform Support
- Tauri Plugin Directory
- Existing SSO Feature Doc — reference for auth callback deep linking