An easy-to-host PDS on the ATProtocol, iPhone and MacOS. Maintain control of your keys and data, always.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: add MM-143 Tauri mobile scaffolding design plan

Covers SvelteKit 2 + Svelte 5 frontend, Tauri v2 Rust backend, Cargo
workspace integration, Nix devenv additions, and suggested CI pipeline.
3 implementation phases.

authored by

Malpercio and committed by
Tangled
0e9e17e3 835b9767

+308
+308
docs/design-plans/2026-03-14-MM-143.md
··· 1 + # MM-143: Tauri Mobile Project Scaffolding Design 2 + 3 + ## Summary 4 + 5 + This document specifies the scaffolding for `apps/identity-wallet/`, the first mobile application in this repository. The app is built with Tauri v2 — a framework that packages a Rust binary and a web frontend together into a native mobile app — using SvelteKit 2 with Svelte 5 as the frontend and a Rust crate as the backend. On iOS, the frontend runs inside a native WKWebView and communicates with the Rust backend exclusively through Tauri's IPC bridge, which lets TypeScript code call Rust functions by name via `invoke()`. The frontend is built as a fully static bundle (no server-side rendering) so it can be embedded in the native app without a web server. 6 + 7 + The work is structured in three phases: first, establishing the project directory structure and integrating the new `src-tauri` crate into the existing Cargo workspace so the entire repo continues to build cleanly; second, wiring up Tauri's configuration, the `greet` IPC command, and the SvelteKit page that exercises it end-to-end in an iOS simulator; and third, adding the new mobile tooling (`cargo-tauri`, Node.js, pnpm) to the Nix dev shell and documenting the iOS workstation setup for developers. Automated CI covers Rust and frontend checks but excludes simulator testing, which requires a macOS machine with Xcode and is run manually. 8 + 9 + ## Definition of Done 10 + 11 + 1. `apps/identity-wallet/` exists with a SvelteKit 2 + Svelte 5 frontend (Vite, `@sveltejs/adapter-static`) and a Tauri v2 Rust backend (`src-tauri/`). 12 + 2. `apps/identity-wallet/src-tauri` is a member of the root Cargo workspace; `cargo build` at workspace root succeeds cleanly. 13 + 3. `cargo tauri ios dev` launches the app in an iOS simulator displaying a placeholder screen. 14 + 4. IPC bridge works: the SvelteKit frontend calls a `greet(name)` Rust command and displays the response. 15 + 5. `devenv.nix` includes `cargo-tauri`, Node.js, and pnpm; developer setup for Xcode, iOS simulators, and Cocoapods is documented. 16 + 6. A suggested CI pipeline spec is documented (two parallel jobs: `rust-check` and `frontend-check`); implementation in tangled.org CI is left to the developer. 17 + 18 + ## Acceptance Criteria 19 + 20 + ### MM-143.AC1: Project directory structure exists 21 + - **MM-143.AC1.1 Success:** `apps/identity-wallet/` contains `package.json`, `svelte.config.js`, `vite.config.ts`, `src/routes/+layout.ts`, `src/routes/+layout.svelte`, `src/routes/+page.svelte`, `src/lib/ipc.ts` 22 + - **MM-143.AC1.2 Success:** `apps/identity-wallet/src-tauri/` contains `Cargo.toml`, `tauri.conf.json`, `build.rs`, `src/lib.rs`, `src/main.rs` 23 + - **MM-143.AC1.3 Success:** SvelteKit version is 2.x and Svelte version is 5.x (verified in `package.json`) 24 + - **MM-143.AC1.4 Success:** Tauri version is 2.x (verified in `src-tauri/Cargo.toml`) 25 + 26 + ### MM-143.AC2: Cargo workspace build succeeds 27 + - **MM-143.AC2.1 Success:** `apps/identity-wallet/src-tauri` appears in root `Cargo.toml` `[workspace] members` 28 + - **MM-143.AC2.2 Success:** `cargo build` at workspace root completes without errors 29 + - **MM-143.AC2.3 Success:** `cargo clippy --workspace -- -D warnings` passes 30 + - **MM-143.AC2.4 Success:** `cargo fmt --all --check` passes 31 + - **MM-143.AC2.5 Failure:** Adding `src-tauri` to workspace members does not introduce errors in existing crates (`relay`, `repo-engine`, `crypto`, `common`) 32 + 33 + ### MM-143.AC3: App launches in iOS simulator 34 + - **MM-143.AC3.1 Success:** `cargo tauri ios dev` completes and the app appears in the iOS simulator 35 + - **MM-143.AC3.2 Success:** App displays a visible placeholder screen (not a blank white screen or error) 36 + - **MM-143.AC3.3 Failure:** App does not crash on launch (no crash dialog in simulator) 37 + 38 + ### MM-143.AC4: IPC bridge functions correctly 39 + - **MM-143.AC4.1 Success:** Pressing the Greet button triggers the Rust `greet` command via `invoke()` 40 + - **MM-143.AC4.2 Success:** The Rust response (e.g., "Hello, World!") is displayed in the UI 41 + - **MM-143.AC4.3 Failure:** No JavaScript console errors appear in the WebView inspector during IPC invocation 42 + - **MM-143.AC4.4 Edge:** Greet button and response text are visible without scrolling on a standard iPhone 15 screen 43 + 44 + ### MM-143.AC5: Dev environment and documentation 45 + - **MM-143.AC5.1 Success:** `cargo-tauri` is available in PATH after `nix develop --impure --accept-flake-config` 46 + - **MM-143.AC5.2 Success:** `node` (22.x) is available in PATH after `nix develop` 47 + - **MM-143.AC5.3 Success:** `pnpm` is available in PATH after `nix develop` 48 + - **MM-143.AC5.4 Success:** `apps/identity-wallet/CLAUDE.md` exists and covers: macOS/Xcode prerequisites, Cocoapods installation, `pnpm install` first-time setup, `cargo tauri ios init` first-time setup, and `cargo tauri ios dev` development workflow 49 + - **MM-143.AC5.5 Success:** Root `CLAUDE.md` contains a pointer to `apps/identity-wallet/CLAUDE.md` 50 + - **MM-143.AC5.6 Success:** `apps/identity-wallet/src-tauri/gen/` is listed in `.gitignore` 51 + 52 + ### MM-143.AC6: CI pipeline documented 53 + - **MM-143.AC6.1 Success:** Design document specifies a `rust-check` job covering `cargo fmt --check`, `cargo clippy --workspace`, and `cargo build --workspace` 54 + - **MM-143.AC6.2 Success:** Design document specifies a `frontend-check` job covering `pnpm install` and `pnpm build` in `apps/identity-wallet/` 55 + - **MM-143.AC6.3 Success:** Document notes that mobile simulator testing is excluded from automated CI 56 + 57 + ## Glossary 58 + 59 + - **Tauri v2**: A framework for building desktop and mobile applications with a Rust backend and a web frontend. The Rust side exposes commands; the frontend runs in a native WebView and calls those commands over an IPC bridge. 60 + - **IPC bridge**: Inter-process communication channel provided by Tauri. The frontend calls `invoke('command_name', args)` in TypeScript; Tauri routes the call to the matching `#[tauri::command]` Rust function and returns the result. 61 + - **`invoke()`**: The TypeScript function from `@tauri-apps/api/core` used to call Rust commands across the IPC bridge. 62 + - **`#[tauri::command]`**: A Rust attribute macro that marks a function as callable from the frontend via `invoke()`. The function must be registered with `generate_handler!` in the Tauri builder. 63 + - **`generate_handler!`**: A Tauri macro that produces the dispatch table mapping command name strings (received over IPC) to their Rust handler functions. 64 + - **SvelteKit 2**: A full-stack web framework built on Svelte. Used here only as a frontend build system; SSR features are disabled. 65 + - **Svelte 5**: The Svelte component compiler version. Introduces "runes" — a reactivity primitive syntax (`$state`, `$derived`) that replaces the implicit reactive declarations of Svelte 4. 66 + - **Runes**: Svelte 5's explicit reactivity model. `$state()` declares reactive variables; `$derived()` declares computed values. Used in `+page.svelte` for the Greet button demo. 67 + - **adapter-static**: A SvelteKit build adapter that outputs a fully static HTML/JS/CSS bundle with no server component. Required for Tauri because the WebView loads files from disk, not from a running server. 68 + - **SSR (Server-Side Rendering)**: Generating HTML on a server before sending it to the client. Disabled here (`ssr = false`) because Tauri apps have no web server — the frontend runs entirely in the native WebView. 69 + - **WKWebView**: Apple's iOS/macOS WebView component. Tauri uses it to render the frontend HTML/JS inside the native app shell. 70 + - **HMR (Hot Module Replacement)**: Vite's development feature that pushes code changes to the browser without a full page reload. In Tauri iOS dev, the simulator connects to the Vite dev server over LAN, so `hmr.host` must be set to the machine's LAN IP (not `localhost`). 71 + - **`vite.config.ts`**: Configuration file for Vite, the build tool and dev server used by SvelteKit. The Tauri-specific settings here (host, port, HMR config) are required for the iOS simulator to connect to the dev server. 72 + - **`tauri.conf.json`**: The primary Tauri configuration file. Specifies the app bundle ID, window settings, the URL Tauri loads during development (`devUrl`), and the directory of the production static build (`frontendDist`). 73 + - **`build.rs`**: A Cargo build script. Tauri requires one that calls `tauri_build::build()` to perform code generation steps before the main crate compiles. 74 + - **`crate-type = ["staticlib", "cdylib", "rlib"]`**: A Cargo setting that compiles the crate in multiple link formats. `staticlib` is required for iOS, `cdylib` for Android, and `rlib` enables normal `cargo build` and tests without a mobile target. 75 + - **Cargo workspace**: Cargo's mechanism for managing multiple Rust crates in one repository with a shared `Cargo.lock` and shared dependency versions. The new `src-tauri` crate is added as a workspace member so it builds alongside the existing relay, repo-engine, crypto, and common crates. 76 + - **`devenv.nix`**: The Nix devenv configuration file for this repository. Adding packages here makes them available in the developer shell after running `nix develop`. 77 + - **Cocoapods**: A dependency manager for Apple platform libraries. Tauri's iOS build process uses it to link native Apple frameworks; developers must install it separately from Xcode. 78 + - **`cargo tauri ios init`**: A one-time CLI command that generates the Xcode project in `src-tauri/gen/apple/`. Must be re-run per developer because the output is machine-specific and is gitignored. 79 + - **`cargo tauri ios dev`**: The CLI command that starts the Vite dev server, compiles the Rust backend, and launches the app in the iOS simulator with live reload. 80 + - **`aarch64-apple-ios` / `aarch64-apple-ios-sim`**: Rust compilation targets for physical iOS devices and Apple Silicon iOS simulators, respectively. Added to `rust-toolchain.toml` so Cargo can cross-compile for iOS. 81 + - **tangled.org CI**: The CI system used by this project. Wiring up the documented pipeline jobs is left to the developer and is outside the scope of this ticket. 82 + - **pnpm**: A Node.js package manager used for the frontend. Chosen here as the package manager for the `apps/identity-wallet/` project. 83 + 84 + ## Architecture 85 + 86 + Tauri v2 mobile app at `apps/identity-wallet/` with a SvelteKit 2 + Svelte 5 frontend and a Rust backend crate. The frontend runs inside a native WebView (WKWebView on iOS); it communicates with the Rust backend exclusively through Tauri's IPC bridge via `invoke()`. No SSR — adapter-static outputs a static bundle the WebView loads. 87 + 88 + ### Project Structure 89 + 90 + ``` 91 + apps/ 92 + identity-wallet/ 93 + package.json # pnpm project root 94 + pnpm-lock.yaml 95 + svelte.config.js # adapter-static (SPA mode, fallback: index.html) 96 + vite.config.ts # clearScreen, host, port, hmr, envPrefix 97 + tsconfig.json 98 + src/ 99 + routes/ 100 + +layout.ts # ssr = false; prerender = false 101 + +layout.svelte # app shell 102 + +page.svelte # splash/placeholder + IPC demo 103 + lib/ 104 + ipc.ts # typed invoke() wrappers 105 + src-tauri/ 106 + Cargo.toml # workspace member; Tauri deps declared locally 107 + tauri.conf.json # bundle id, window config, devUrl, frontendDist 108 + build.rs 109 + src/ 110 + lib.rs # #[tauri::command] greet + pub fn run() 111 + main.rs # entry point 112 + gen/ # gitignored — Tauri-generated Xcode project 113 + apple/ 114 + ``` 115 + 116 + ### SvelteKit + Tauri Configuration 117 + 118 + **`svelte.config.js`** — SPA mode via adapter-static: 119 + 120 + ```js 121 + // fallback: 'index.html' routes 404s to index for client-side navigation 122 + adapter: adapter({ fallback: 'index.html' }) 123 + ``` 124 + 125 + **`src/routes/+layout.ts`** — disable SSR globally (WebView has no server): 126 + 127 + ```ts 128 + export const ssr = false; 129 + export const prerender = false; 130 + ``` 131 + 132 + **`vite.config.ts`** — Tauri-required server settings: 133 + 134 + - `clearScreen: false` — surfaces Rust compiler errors 135 + - `server.host: '0.0.0.0'`, `port: 5173`, `strictPort: true` 136 + - `hmr.host: await internalIpV4()` — iOS simulator connects via LAN IP, not localhost 137 + - `envPrefix: ['VITE_', 'TAURI_']` 138 + 139 + ### IPC Bridge 140 + 141 + **Rust side (`src-tauri/src/lib.rs`):** 142 + 143 + ```rust 144 + // Contract only — no function body in design plans 145 + #[tauri::command] 146 + fn greet(name: String) -> String 147 + 148 + pub fn run() // configures builder, registers greet via generate_handler! 149 + ``` 150 + 151 + **TypeScript side (`src/lib/ipc.ts`):** 152 + 153 + ```ts 154 + import { invoke } from '@tauri-apps/api/core'; 155 + 156 + export const greet = (name: string): Promise<string> => 157 + invoke('greet', { name }); 158 + ``` 159 + 160 + ### Cargo Workspace Integration 161 + 162 + Root `Cargo.toml` gains one `members` entry: 163 + 164 + ```toml 165 + "apps/identity-wallet/src-tauri" 166 + ``` 167 + 168 + `src-tauri/Cargo.toml` inherits `version`, `edition`, `publish` from the workspace root. All Tauri-specific dependencies (`tauri = "2"`, `tauri-build = "2"`) are declared locally — no other workspace crate needs them, so they stay out of `[workspace.dependencies]`. 169 + 170 + ```toml 171 + [lib] 172 + crate-type = ["staticlib", "cdylib", "rlib"] 173 + # staticlib → iOS static binary 174 + # cdylib → Android shared library 175 + # rlib → normal cargo build / integration tests 176 + ``` 177 + 178 + `rust-toolchain.toml` gains two iOS targets: 179 + - `aarch64-apple-ios` — physical iOS device 180 + - `aarch64-apple-ios-sim` — Apple Silicon simulator 181 + 182 + ### Nix Dev Environment 183 + 184 + `devenv.nix` gains three packages: 185 + 186 + ```nix 187 + pkgs.cargo-tauri # tauri-cli (available in nixpkgs) 188 + pkgs.nodejs_22 # Node.js runtime for frontend tooling 189 + pkgs.nodePackages.pnpm # package manager 190 + ``` 191 + 192 + No new devenv processes or environment variables. `cargo tauri ios dev` is run manually by the developer (requires Xcode, cannot be a background process). 193 + 194 + `src-tauri/gen/` is added to `.gitignore` — Tauri regenerates this directory on `cargo tauri ios init` and it is machine-specific. 195 + 196 + ### Developer Setup Documentation 197 + 198 + **`apps/identity-wallet/CLAUDE.md`** (new) — comprehensive iOS developer workstation guide: 199 + 200 + - Prerequisites: macOS Ventura+, Xcode (latest, from App Store), iOS Simulator platform (Xcode → Settings → Platforms), Cocoapods (`sudo gem install cocoapods`), Apple Developer account (optional for simulator, required for device) 201 + - Dev shell: `nix develop --impure --accept-flake-config` 202 + - First-time frontend setup: `pnpm install` in `apps/identity-wallet/` 203 + - First-time iOS setup: `cargo tauri ios init` (run once; generates `src-tauri/gen/apple/`) 204 + - Development: launch Xcode first (license acceptance), then `cargo tauri ios dev` 205 + - Workspace build without Xcode: `cargo build` from repo root works on any machine in the dev shell 206 + 207 + Root `CLAUDE.md` gains a pointer to `apps/identity-wallet/CLAUDE.md` under a "Mobile" section. 208 + 209 + ### Suggested CI Pipeline 210 + 211 + Not implemented — developer will wire up in tangled.org CI. Two parallel jobs: 212 + 213 + **`rust-check`:** 214 + 1. Install Rust stable with `rustfmt` + `clippy` + iOS targets (`aarch64-apple-ios`, `aarch64-apple-ios-sim`) 215 + 2. Cache Cargo registry + `target/` (key: `Cargo.lock`) 216 + 3. `cargo fmt --all --check` 217 + 4. `cargo clippy --workspace -- -D warnings` 218 + 5. `cargo build --workspace` (compile check without mobile target; no Xcode required) 219 + 220 + **`frontend-check`:** 221 + 1. Install Node.js 22 + pnpm 222 + 2. Cache pnpm store (key: `apps/identity-wallet/pnpm-lock.yaml`) 223 + 3. `pnpm install` in `apps/identity-wallet/` 224 + 4. `pnpm build` (SvelteKit static build via adapter-static) 225 + 226 + Mobile simulator testing (`cargo tauri ios dev`) is excluded from automated CI — it requires a macOS runner with Xcode and is run manually. 227 + 228 + ## Existing Patterns 229 + 230 + **Cargo workspace:** Follows the existing workspace resolver v2 pattern. `src-tauri` inherits `version`, `edition`, and `publish` from `[workspace.package]`, matching how all four existing crates are configured. Tauri-specific deps stay local — consistent with how relay-specific deps (axum, clap) are not in `[workspace.dependencies]`. 231 + 232 + **devenv.nix:** Follows the existing pattern of adding Nix-provided tools to `packages`. No new patterns introduced — `cargo-tauri`, `nodejs_22`, and `pnpm` are added the same way `just` and `cargo-audit` are. 233 + 234 + **CLAUDE.md hierarchy:** Follows the existing `nix/CLAUDE.md` pattern of domain-level CLAUDE.md files for subsystem-specific developer guidance. `apps/identity-wallet/CLAUDE.md` is the mobile equivalent. 235 + 236 + **rust-toolchain.toml:** Extends the existing file (no new file) by appending iOS targets alongside the existing macOS and Linux targets. 237 + 238 + No existing mobile or frontend patterns to follow — this is the first frontend code in the repository. 239 + 240 + ## Implementation Phases 241 + 242 + <!-- START_PHASE_1 --> 243 + ### Phase 1: Project Scaffolding + Workspace Integration 244 + 245 + **Goal:** Establish the directory structure, SvelteKit project, and Cargo workspace membership so `cargo build` succeeds at the workspace root. 246 + 247 + **Components:** 248 + - `apps/identity-wallet/package.json`, `pnpm-lock.yaml` — pnpm project root 249 + - `apps/identity-wallet/svelte.config.js` — adapter-static, SPA mode 250 + - `apps/identity-wallet/vite.config.ts` — Tauri server config (no Tauri plugin yet; frontend only) 251 + - `apps/identity-wallet/tsconfig.json` 252 + - `apps/identity-wallet/src/routes/+layout.ts` — `ssr = false`, `prerender = false` 253 + - `apps/identity-wallet/src/routes/+layout.svelte` — empty app shell 254 + - `apps/identity-wallet/src/routes/+page.svelte` — static placeholder (no IPC yet) 255 + - `apps/identity-wallet/src-tauri/Cargo.toml` — workspace member, minimal deps only 256 + - `apps/identity-wallet/src-tauri/src/main.rs`, `src/lib.rs` — empty stubs 257 + - Root `Cargo.toml` — add `apps/identity-wallet/src-tauri` to `[workspace] members` 258 + - `rust-toolchain.toml` — add `aarch64-apple-ios`, `aarch64-apple-ios-sim` 259 + 260 + **Dependencies:** None (first phase). 261 + 262 + **Done when:** `cargo build` at workspace root succeeds cleanly. `pnpm install && pnpm build` in `apps/identity-wallet/` succeeds. 263 + <!-- END_PHASE_1 --> 264 + 265 + <!-- START_PHASE_2 --> 266 + ### Phase 2: Tauri Configuration + IPC Bridge 267 + 268 + **Goal:** Wire up Tauri fully — `tauri.conf.json`, the `greet` command, and the SvelteKit frontend page that calls it — so `cargo tauri ios dev` works end-to-end. 269 + 270 + **Components:** 271 + - `apps/identity-wallet/src-tauri/tauri.conf.json` — bundle id, `devUrl: http://localhost:5173`, `frontendDist: ../dist` 272 + - `apps/identity-wallet/src-tauri/build.rs` — `tauri_build::build()` invocation 273 + - `apps/identity-wallet/src-tauri/src/lib.rs` — `greet` command + `run()` with `generate_handler!` 274 + - `apps/identity-wallet/src-tauri/src/main.rs` — calls `lib::run()` 275 + - `apps/identity-wallet/vite.config.ts` — updated with `hmr.host` (`internal-ip` package), `envPrefix` 276 + - `apps/identity-wallet/src/lib/ipc.ts` — typed `greet()` wrapper around `invoke()` 277 + - `apps/identity-wallet/src/routes/+page.svelte` — Svelte 5 runes, Greet button, response display 278 + 279 + **Dependencies:** Phase 1. 280 + 281 + **Done when:** `cargo tauri ios dev` launches the app in iOS simulator. Pressing Greet displays "Hello, World!" from the Rust backend. No console errors in the WebView. 282 + <!-- END_PHASE_2 --> 283 + 284 + <!-- START_PHASE_3 --> 285 + ### Phase 3: Nix, Gitignore, and Documentation 286 + 287 + **Goal:** Add mobile tooling to the dev shell, exclude generated files from git, and document the iOS developer setup. 288 + 289 + **Components:** 290 + - `devenv.nix` — add `cargo-tauri`, `nodejs_22`, `nodePackages.pnpm` 291 + - `.gitignore` — add `apps/identity-wallet/src-tauri/gen/` 292 + - `apps/identity-wallet/CLAUDE.md` (new) — comprehensive iOS workstation setup guide 293 + - Root `CLAUDE.md` — add "Mobile" section pointing to `apps/identity-wallet/CLAUDE.md` 294 + 295 + **Dependencies:** Phase 1 (workspace structure must exist for CLAUDE.md paths to be accurate). 296 + 297 + **Done when:** `nix develop --impure --accept-flake-config` provides `cargo-tauri`, `node`, and `pnpm` in PATH. `apps/identity-wallet/CLAUDE.md` covers all iOS prerequisites and setup steps. 298 + <!-- END_PHASE_3 --> 299 + 300 + ## Additional Considerations 301 + 302 + **Cargo.lock placement:** Tauri tooling expects `Cargo.lock` at the workspace root (where `Cargo.toml` is). This is already the case in this repo. Do not create a second `Cargo.lock` in `src-tauri/`. 303 + 304 + **`src-tauri/gen/` is gitignored:** `cargo tauri ios init` generates a full Xcode project in `src-tauri/gen/apple/`. This is machine-specific and must be regenerated per developer. Committing it causes merge conflicts and bloats the repo. 305 + 306 + **Android target:** Excluded from this scaffolding. When Android support is added, `aarch64-linux-android` and `armv7-linux-androideabi` targets plus Android SDK configuration will be needed. 307 + 308 + **`[profile.release]` for mobile:** Add `strip = true`, `lto = true`, `opt-level = "z"` to `src-tauri/Cargo.toml` — Tauri's recommendation for reducing iOS binary size. Has no effect on `cargo build --workspace` debug builds.