Clients
Every client renders the same core. Apple and Android link the Rust engine through UniFFI; the web client runs the protocol and MLS client compiled to WebAssembly. No client keeps its own copy of the messaging logic, its own database schema or a mock messaging service.
flowchart LR
Core["Rust core"] -->|"UniFFI → Swift"| Apple["apple/<br/>SwiftUI · iOS 26 · macOS 26"]
Core -->|"UniFFI → Kotlin + JNI"| Android["android/<br/>Kotlin · Jetpack Compose · Material 3"]
WebCore["crates/zoen-web-core"] -->|"wasm-bindgen"| Web["packages/zoen-ui/web<br/>React 19"]
Design principles shared by all clients
Section titled “Design principles shared by all clients”- Native first: platform navigation, typography, accessibility settings and gestures; the web follows the iOS themes and conversation hierarchy.
- One Chats list for direct chats, groups and communities, with optional filters.
- Cards for things that need you: approvals, notifications and plans are cards; you can switch to a list and back.
- Plain language: no technical jargon in the UI.
- Accessibility: Reduce Motion, Reduce Transparency and Increase Contrast are honoured and checked (HIG matrix, motion).
| Path | apple/ — Shared/ (features, design system, navigation, sync, widgets), iOS/, Mac/, Tests/, UITests/ |
| Project | Generated by XcodeGen from apple/project.yml (targets ZoeniOS, ZoenMac, ZoenUITests, Zoen-Device) |
| Language | Swift 6, SwiftUI; deployment targets iOS 26 and macOS 26 |
| Core | XCFramework + Swift bindings built by scripts/build-core.sh into apple/Packages/ |
| Look | Glass cards and buttons from a custom Metal shader (apple/Shared/DesignSystem/ZoenGlass.metal) on main; a move to system Liquid Glass is on the ui/native-glass branch |
| On-device model | Foundation Models chooses mini-apps when available, with keyword fallback |
Run it:
scripts/run.sh # builds the core if missing, generates the Xcode project, opens iOS simulator + Macscripts/run.sh ios # iOS onlyscripts/run.sh mac # Mac onlySIM="iPhone 17 Pro" scripts/run.sh iosscripts/build-core.sh # rebuild the XCFramework and Swift bindings after any core API changeMore: watch mode rebuilds and relaunches on every change; installing on an iPhone (scripts/device.sh); the iPhone app on a Mac (scripts/mac-iphone.sh).

Android
Section titled “Android”| Path | android/ (Gradle, app/ module, package xyz.tironi.zoen) |
| Language | Kotlin, Jetpack Compose, Material 3 |
| Requirements | JDK 17, Android SDK 36, build-tools 36.0.0, NDK 27.1.12297006, cargo-ndk; runs on Android 9+ (ARM64 phones, x86_64 emulators) |
| Core | A Gradle task compiles the Rust library and generates matching Kotlin bindings before Kotlin compilation |
| Navigation | Bottom bar on compact windows, rail from 600 dp, list/detail panes from 840 dp; predictive Back |
| Keys | AES-256-GCM wrapped secrets in no-backup storage under a non-exportable Android Keystore key |
| Mini-apps | Native mini-apps on every supported version; the HTML MCP host needs a WebView with WEB_MESSAGE_LISTENER and MULTI_PROFILE |
Run it:
cargo install cargo-ndk --lockedrustup target add aarch64-linux-android x86_64-linux-androidexport ANDROID_HOME=/path/to/android/sdkscripts/run-android.sh -PandroidAbis=arm64-v8a # build, install, launch on a connected deviceOr from android/: ./gradlew :app:assembleDebug, ./gradlew :app:testDebugUnitTest :app:lintDebug, ./gradlew :app:connectedDebugAndroidTest. Debug builds have an explicit Explore the demo entry with clearly labelled simulated peers. Full details: android/README.md, parity record.
Two entry points live in packages/zoen-ui:
| Entry | What it is |
|---|---|
Shell preview (preview/) |
The reusable React application shell with sample data — for layout and interaction review. No message delivery. |
Authenticated browser client (web/) |
Real client: paired by the phone, native protocol 4, OpenMLS in a dedicated worker, signing through a non-exportable WebCrypto key, encrypted IndexedDB session. |
# Shell preview (sample data) → http://127.0.0.1:4173npm ci --prefix packages/zoen-ui --ignore-scriptsnpm run preview --prefix packages/zoen-ui
# Authenticated client → http://127.0.0.1:4174cd packages/zoen-uinpm cinpm run typecheck:web && npm run test:webZOEN_WASM_BINDGEN=/absolute/path/to/wasm-bindgen npm run build:webnpm run webThe build needs the wasm32-unknown-unknown target and wasm-bindgen CLI 0.2.129. Output is pinned with SRI hashes; the server sets COOP/COEP isolation. Real native/browser pairing and reload/reconnect journeys are still open. Details: web client README, ADR 0029.
The zoen CLI
Section titled “The zoen CLI”crates/zoen-cli builds the zoen binary: a person at a terminal, using the exact core the apps use. Every command is a fresh process — open the database, load the key, connect, catch up, act, flush, exit. It powers the end-to-end journeys in CI and is the fastest way to explore the system (tutorial).
