Mobile apps (iOS / Android)¶
The phone apps are the same Tauri shell as the desktop app (apps/shell).
The web build runs in the system webview, keeps its whole workspace locally
(IndexedDB + Loro), and syncs through any of the usual channels: a server, a
LAN peer, or a file pack. Nothing in the study features is desktop-specific.
iOS¶
Prerequisites: Xcode with iOS simulators, and a Rust toolchain with the iOS targets. Two ways to get the toolchain:
- Nix:
nix developgives it to you pinned (the flake includes the iOS targets). Nothing else to install. - Or rustup:
rustup target add aarch64-apple-ios aarch64-apple-ios-sim.
Either way Xcode itself is not in Nix (Apple does not allow it), so the iOS SDK, the simulator and signing always come from your Xcode install.
The Xcode project lives in apps/shell/src-tauri/gen/apple and is committed,
so there is nothing to generate. Avoid re-running pnpm tauri ios init: it
overwrites the project, including the Info.plist local-network keys that LAN
sync needs. Only run it to regenerate after changing plugins, and re-add
those keys afterwards.
Add an Apple account once¶
iOS builds must be signed, even for the simulator in a release build and always for a device. Do this one-time setup or the build fails with "requires a development team" / "No Accounts":
- Open Xcode > Settings > Accounts and add an Apple ID (a free one works for local development). Signing for the simulator and your own devices works with the personal team that gives you.
- Find the team id: Xcode > Settings > Accounts (the team's id), or
security find-identity -v -p codesigning(the code in parentheses after "Apple Development: Your Name"). - Put it in a gitignored
.env.localat the repo root, which the Nix shell loads automatically:
echo 'APPLE_DEVELOPMENT_TEAM=XXXXXXXXXX' > .env.local
Outside the Nix shell, export APPLE_DEVELOPMENT_TEAM=XXXXXXXXXX before
building. Tauri reads it and sets the Xcode development team.
To sign against the Synapse team (for a build distributed as Synapse rather than under your own name), ask to be added to it. For now that team is @dhovart's personal one, so this is a manual invite rather than something you can self-serve.
Build and run¶
Run this from a plain terminal, not nix develop:
cd apps/shell
pnpm ios:sim
It creates/boots a simulator matching the Xcode SDK and runs the app on it.
Simulators need no signing, so it skips the device setup below. iOS uses the
system Xcode toolchain, so it must run outside nix develop (only Android
builds inside the Nix shell).
Distribution goes through the normal Xcode archive flow (TestFlight / App
Store). A device or App Store build must be signed by the Synapse team, so ask
to be added to it (see above); CI signs headlessly from that team's App Store
Connect API key (APPLE_API_ISSUER, APPLE_API_KEY, APPLE_API_KEY_PATH).
Custom Info.plist keys¶
The LAN-sync keys (NSLocalNetworkUsageDescription, NSBonjourServices) live in
apps/shell/src-tauri/Info.ios.plist, pointed at from tauri.conf.json
(bundle > iOS > infoPlist). Tauri merges it at build time. Don't put them in
the generated gen/apple/synapse_iOS/Info.plist; tauri ios init overwrites it.
Android¶
The whole Android toolchain is pinned in the flake, so nix develop is the
only prerequisite: it provides the Rust cross-targets, a JDK, and the Android
SDK/NDK/build-tools (via androidenv), and exports JAVA_HOME,
ANDROID_HOME and NDK_HOME. No Android Studio install is required, and no
ANDROID_HOME to set by hand. (The SDK is unfree; the flake accepts its
licence for this dev shell only.)
The Android project in apps/shell/src-tauri/gen/android is committed, so
there is nothing to generate. Run the Tauri commands inside nix develop:
# run on an emulator or connected device
nix develop --impure -c pnpm --filter @synapse/shell tauri android dev
# build a signed universal APK
nix develop --impure -c pnpm --filter @synapse/shell tauri android build --apk
tauri android dev needs a running emulator (or a connected device); start
one first (emulator -avd <name>), and make sure its disk has room (a failed
install with "not enough space" is a full emulator, not a build error).
Signing¶
tauri android build signs the release APK from
gen/android/app/keystore.properties (gitignored). Point it at a keystore:
storeFile=/absolute/path/to/keystore.jks
storePassword=…
keyAlias=…
keyPassword=…
For local installs the Android debug keystore works
(~/.android/debug.keystore, all three secrets android / alias
androiddebugkey). Without this file the release build is left unsigned, and
Android refuses to install an unsigned APK. The build tool version is pinned
for every module (app and the Tauri plugin subprojects) in
gen/android/build.gradle.kts so the Android Gradle plugin never tries to
download into the read-only Nix store.
The signed APK lands at
gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk;
install it with adb install -r <that path> or by sideloading the file.
What differs from desktop on a phone¶
- The UI switches to its narrow layouts (the viewer replaces the side panels
with a bottom toolbar); the e2e suite covers this with a phone-viewport
pass (
apps/web/e2e/specs/mobile.spec.ts). - LAN sync: the Rust side (mDNS + WebSocket listener) compiles for mobile as
part of the same crate. iOS prompts for the local-network permission
(
NSLocalNetworkUsageDescriptionandNSBonjourServicesare set in the committed Info.plist) and Android needs the app in the foreground while syncing. - File packs: exporting opens the system save picker. On Android that returns a Storage Access Framework location (no filesystem path), so the app confirms the saved file by name rather than a path, and the desktop "show in folder" action is hidden there.