···11+# MM-143: Tauri Mobile Project Scaffolding Design
22+33+## Summary
44+55+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.
66+77+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.
88+99+## Definition of Done
1010+1111+1. `apps/identity-wallet/` exists with a SvelteKit 2 + Svelte 5 frontend (Vite, `@sveltejs/adapter-static`) and a Tauri v2 Rust backend (`src-tauri/`).
1212+2. `apps/identity-wallet/src-tauri` is a member of the root Cargo workspace; `cargo build` at workspace root succeeds cleanly.
1313+3. `cargo tauri ios dev` launches the app in an iOS simulator displaying a placeholder screen.
1414+4. IPC bridge works: the SvelteKit frontend calls a `greet(name)` Rust command and displays the response.
1515+5. `devenv.nix` includes `cargo-tauri`, Node.js, and pnpm; developer setup for Xcode, iOS simulators, and Cocoapods is documented.
1616+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.
1717+1818+## Acceptance Criteria
1919+2020+### MM-143.AC1: Project directory structure exists
2121+- **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`
2222+- **MM-143.AC1.2 Success:** `apps/identity-wallet/src-tauri/` contains `Cargo.toml`, `tauri.conf.json`, `build.rs`, `src/lib.rs`, `src/main.rs`
2323+- **MM-143.AC1.3 Success:** SvelteKit version is 2.x and Svelte version is 5.x (verified in `package.json`)
2424+- **MM-143.AC1.4 Success:** Tauri version is 2.x (verified in `src-tauri/Cargo.toml`)
2525+2626+### MM-143.AC2: Cargo workspace build succeeds
2727+- **MM-143.AC2.1 Success:** `apps/identity-wallet/src-tauri` appears in root `Cargo.toml` `[workspace] members`
2828+- **MM-143.AC2.2 Success:** `cargo build` at workspace root completes without errors
2929+- **MM-143.AC2.3 Success:** `cargo clippy --workspace -- -D warnings` passes
3030+- **MM-143.AC2.4 Success:** `cargo fmt --all --check` passes
3131+- **MM-143.AC2.5 Failure:** Adding `src-tauri` to workspace members does not introduce errors in existing crates (`relay`, `repo-engine`, `crypto`, `common`)
3232+3333+### MM-143.AC3: App launches in iOS simulator
3434+- **MM-143.AC3.1 Success:** `cargo tauri ios dev` completes and the app appears in the iOS simulator
3535+- **MM-143.AC3.2 Success:** App displays a visible placeholder screen (not a blank white screen or error)
3636+- **MM-143.AC3.3 Failure:** App does not crash on launch (no crash dialog in simulator)
3737+3838+### MM-143.AC4: IPC bridge functions correctly
3939+- **MM-143.AC4.1 Success:** Pressing the Greet button triggers the Rust `greet` command via `invoke()`
4040+- **MM-143.AC4.2 Success:** The Rust response (e.g., "Hello, World!") is displayed in the UI
4141+- **MM-143.AC4.3 Failure:** No JavaScript console errors appear in the WebView inspector during IPC invocation
4242+- **MM-143.AC4.4 Edge:** Greet button and response text are visible without scrolling on a standard iPhone 15 screen
4343+4444+### MM-143.AC5: Dev environment and documentation
4545+- **MM-143.AC5.1 Success:** `cargo-tauri` is available in PATH after `nix develop --impure --accept-flake-config`
4646+- **MM-143.AC5.2 Success:** `node` (22.x) is available in PATH after `nix develop`
4747+- **MM-143.AC5.3 Success:** `pnpm` is available in PATH after `nix develop`
4848+- **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
4949+- **MM-143.AC5.5 Success:** Root `CLAUDE.md` contains a pointer to `apps/identity-wallet/CLAUDE.md`
5050+- **MM-143.AC5.6 Success:** `apps/identity-wallet/src-tauri/gen/` is listed in `.gitignore`
5151+5252+### MM-143.AC6: CI pipeline documented
5353+- **MM-143.AC6.1 Success:** Design document specifies a `rust-check` job covering `cargo fmt --check`, `cargo clippy --workspace`, and `cargo build --workspace`
5454+- **MM-143.AC6.2 Success:** Design document specifies a `frontend-check` job covering `pnpm install` and `pnpm build` in `apps/identity-wallet/`
5555+- **MM-143.AC6.3 Success:** Document notes that mobile simulator testing is excluded from automated CI
5656+5757+## Glossary
5858+5959+- **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.
6060+- **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.
6161+- **`invoke()`**: The TypeScript function from `@tauri-apps/api/core` used to call Rust commands across the IPC bridge.
6262+- **`#[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.
6363+- **`generate_handler!`**: A Tauri macro that produces the dispatch table mapping command name strings (received over IPC) to their Rust handler functions.
6464+- **SvelteKit 2**: A full-stack web framework built on Svelte. Used here only as a frontend build system; SSR features are disabled.
6565+- **Svelte 5**: The Svelte component compiler version. Introduces "runes" — a reactivity primitive syntax (`$state`, `$derived`) that replaces the implicit reactive declarations of Svelte 4.
6666+- **Runes**: Svelte 5's explicit reactivity model. `$state()` declares reactive variables; `$derived()` declares computed values. Used in `+page.svelte` for the Greet button demo.
6767+- **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.
6868+- **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.
6969+- **WKWebView**: Apple's iOS/macOS WebView component. Tauri uses it to render the frontend HTML/JS inside the native app shell.
7070+- **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`).
7171+- **`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.
7272+- **`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`).
7373+- **`build.rs`**: A Cargo build script. Tauri requires one that calls `tauri_build::build()` to perform code generation steps before the main crate compiles.
7474+- **`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.
7575+- **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.
7676+- **`devenv.nix`**: The Nix devenv configuration file for this repository. Adding packages here makes them available in the developer shell after running `nix develop`.
7777+- **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.
7878+- **`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.
7979+- **`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.
8080+- **`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.
8181+- **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.
8282+- **pnpm**: A Node.js package manager used for the frontend. Chosen here as the package manager for the `apps/identity-wallet/` project.
8383+8484+## Architecture
8585+8686+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.
8787+8888+### Project Structure
8989+9090+```
9191+apps/
9292+ identity-wallet/
9393+ package.json # pnpm project root
9494+ pnpm-lock.yaml
9595+ svelte.config.js # adapter-static (SPA mode, fallback: index.html)
9696+ vite.config.ts # clearScreen, host, port, hmr, envPrefix
9797+ tsconfig.json
9898+ src/
9999+ routes/
100100+ +layout.ts # ssr = false; prerender = false
101101+ +layout.svelte # app shell
102102+ +page.svelte # splash/placeholder + IPC demo
103103+ lib/
104104+ ipc.ts # typed invoke() wrappers
105105+ src-tauri/
106106+ Cargo.toml # workspace member; Tauri deps declared locally
107107+ tauri.conf.json # bundle id, window config, devUrl, frontendDist
108108+ build.rs
109109+ src/
110110+ lib.rs # #[tauri::command] greet + pub fn run()
111111+ main.rs # entry point
112112+ gen/ # gitignored — Tauri-generated Xcode project
113113+ apple/
114114+```
115115+116116+### SvelteKit + Tauri Configuration
117117+118118+**`svelte.config.js`** — SPA mode via adapter-static:
119119+120120+```js
121121+// fallback: 'index.html' routes 404s to index for client-side navigation
122122+adapter: adapter({ fallback: 'index.html' })
123123+```
124124+125125+**`src/routes/+layout.ts`** — disable SSR globally (WebView has no server):
126126+127127+```ts
128128+export const ssr = false;
129129+export const prerender = false;
130130+```
131131+132132+**`vite.config.ts`** — Tauri-required server settings:
133133+134134+- `clearScreen: false` — surfaces Rust compiler errors
135135+- `server.host: '0.0.0.0'`, `port: 5173`, `strictPort: true`
136136+- `hmr.host: await internalIpV4()` — iOS simulator connects via LAN IP, not localhost
137137+- `envPrefix: ['VITE_', 'TAURI_']`
138138+139139+### IPC Bridge
140140+141141+**Rust side (`src-tauri/src/lib.rs`):**
142142+143143+```rust
144144+// Contract only — no function body in design plans
145145+#[tauri::command]
146146+fn greet(name: String) -> String
147147+148148+pub fn run() // configures builder, registers greet via generate_handler!
149149+```
150150+151151+**TypeScript side (`src/lib/ipc.ts`):**
152152+153153+```ts
154154+import { invoke } from '@tauri-apps/api/core';
155155+156156+export const greet = (name: string): Promise<string> =>
157157+ invoke('greet', { name });
158158+```
159159+160160+### Cargo Workspace Integration
161161+162162+Root `Cargo.toml` gains one `members` entry:
163163+164164+```toml
165165+"apps/identity-wallet/src-tauri"
166166+```
167167+168168+`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]`.
169169+170170+```toml
171171+[lib]
172172+crate-type = ["staticlib", "cdylib", "rlib"]
173173+# staticlib → iOS static binary
174174+# cdylib → Android shared library
175175+# rlib → normal cargo build / integration tests
176176+```
177177+178178+`rust-toolchain.toml` gains two iOS targets:
179179+- `aarch64-apple-ios` — physical iOS device
180180+- `aarch64-apple-ios-sim` — Apple Silicon simulator
181181+182182+### Nix Dev Environment
183183+184184+`devenv.nix` gains three packages:
185185+186186+```nix
187187+pkgs.cargo-tauri # tauri-cli (available in nixpkgs)
188188+pkgs.nodejs_22 # Node.js runtime for frontend tooling
189189+pkgs.nodePackages.pnpm # package manager
190190+```
191191+192192+No new devenv processes or environment variables. `cargo tauri ios dev` is run manually by the developer (requires Xcode, cannot be a background process).
193193+194194+`src-tauri/gen/` is added to `.gitignore` — Tauri regenerates this directory on `cargo tauri ios init` and it is machine-specific.
195195+196196+### Developer Setup Documentation
197197+198198+**`apps/identity-wallet/CLAUDE.md`** (new) — comprehensive iOS developer workstation guide:
199199+200200+- 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)
201201+- Dev shell: `nix develop --impure --accept-flake-config`
202202+- First-time frontend setup: `pnpm install` in `apps/identity-wallet/`
203203+- First-time iOS setup: `cargo tauri ios init` (run once; generates `src-tauri/gen/apple/`)
204204+- Development: launch Xcode first (license acceptance), then `cargo tauri ios dev`
205205+- Workspace build without Xcode: `cargo build` from repo root works on any machine in the dev shell
206206+207207+Root `CLAUDE.md` gains a pointer to `apps/identity-wallet/CLAUDE.md` under a "Mobile" section.
208208+209209+### Suggested CI Pipeline
210210+211211+Not implemented — developer will wire up in tangled.org CI. Two parallel jobs:
212212+213213+**`rust-check`:**
214214+1. Install Rust stable with `rustfmt` + `clippy` + iOS targets (`aarch64-apple-ios`, `aarch64-apple-ios-sim`)
215215+2. Cache Cargo registry + `target/` (key: `Cargo.lock`)
216216+3. `cargo fmt --all --check`
217217+4. `cargo clippy --workspace -- -D warnings`
218218+5. `cargo build --workspace` (compile check without mobile target; no Xcode required)
219219+220220+**`frontend-check`:**
221221+1. Install Node.js 22 + pnpm
222222+2. Cache pnpm store (key: `apps/identity-wallet/pnpm-lock.yaml`)
223223+3. `pnpm install` in `apps/identity-wallet/`
224224+4. `pnpm build` (SvelteKit static build via adapter-static)
225225+226226+Mobile simulator testing (`cargo tauri ios dev`) is excluded from automated CI — it requires a macOS runner with Xcode and is run manually.
227227+228228+## Existing Patterns
229229+230230+**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]`.
231231+232232+**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.
233233+234234+**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.
235235+236236+**rust-toolchain.toml:** Extends the existing file (no new file) by appending iOS targets alongside the existing macOS and Linux targets.
237237+238238+No existing mobile or frontend patterns to follow — this is the first frontend code in the repository.
239239+240240+## Implementation Phases
241241+242242+<!-- START_PHASE_1 -->
243243+### Phase 1: Project Scaffolding + Workspace Integration
244244+245245+**Goal:** Establish the directory structure, SvelteKit project, and Cargo workspace membership so `cargo build` succeeds at the workspace root.
246246+247247+**Components:**
248248+- `apps/identity-wallet/package.json`, `pnpm-lock.yaml` — pnpm project root
249249+- `apps/identity-wallet/svelte.config.js` — adapter-static, SPA mode
250250+- `apps/identity-wallet/vite.config.ts` — Tauri server config (no Tauri plugin yet; frontend only)
251251+- `apps/identity-wallet/tsconfig.json`
252252+- `apps/identity-wallet/src/routes/+layout.ts` — `ssr = false`, `prerender = false`
253253+- `apps/identity-wallet/src/routes/+layout.svelte` — empty app shell
254254+- `apps/identity-wallet/src/routes/+page.svelte` — static placeholder (no IPC yet)
255255+- `apps/identity-wallet/src-tauri/Cargo.toml` — workspace member, minimal deps only
256256+- `apps/identity-wallet/src-tauri/src/main.rs`, `src/lib.rs` — empty stubs
257257+- Root `Cargo.toml` — add `apps/identity-wallet/src-tauri` to `[workspace] members`
258258+- `rust-toolchain.toml` — add `aarch64-apple-ios`, `aarch64-apple-ios-sim`
259259+260260+**Dependencies:** None (first phase).
261261+262262+**Done when:** `cargo build` at workspace root succeeds cleanly. `pnpm install && pnpm build` in `apps/identity-wallet/` succeeds.
263263+<!-- END_PHASE_1 -->
264264+265265+<!-- START_PHASE_2 -->
266266+### Phase 2: Tauri Configuration + IPC Bridge
267267+268268+**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.
269269+270270+**Components:**
271271+- `apps/identity-wallet/src-tauri/tauri.conf.json` — bundle id, `devUrl: http://localhost:5173`, `frontendDist: ../dist`
272272+- `apps/identity-wallet/src-tauri/build.rs` — `tauri_build::build()` invocation
273273+- `apps/identity-wallet/src-tauri/src/lib.rs` — `greet` command + `run()` with `generate_handler!`
274274+- `apps/identity-wallet/src-tauri/src/main.rs` — calls `lib::run()`
275275+- `apps/identity-wallet/vite.config.ts` — updated with `hmr.host` (`internal-ip` package), `envPrefix`
276276+- `apps/identity-wallet/src/lib/ipc.ts` — typed `greet()` wrapper around `invoke()`
277277+- `apps/identity-wallet/src/routes/+page.svelte` — Svelte 5 runes, Greet button, response display
278278+279279+**Dependencies:** Phase 1.
280280+281281+**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.
282282+<!-- END_PHASE_2 -->
283283+284284+<!-- START_PHASE_3 -->
285285+### Phase 3: Nix, Gitignore, and Documentation
286286+287287+**Goal:** Add mobile tooling to the dev shell, exclude generated files from git, and document the iOS developer setup.
288288+289289+**Components:**
290290+- `devenv.nix` — add `cargo-tauri`, `nodejs_22`, `nodePackages.pnpm`
291291+- `.gitignore` — add `apps/identity-wallet/src-tauri/gen/`
292292+- `apps/identity-wallet/CLAUDE.md` (new) — comprehensive iOS workstation setup guide
293293+- Root `CLAUDE.md` — add "Mobile" section pointing to `apps/identity-wallet/CLAUDE.md`
294294+295295+**Dependencies:** Phase 1 (workspace structure must exist for CLAUDE.md paths to be accurate).
296296+297297+**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.
298298+<!-- END_PHASE_3 -->
299299+300300+## Additional Considerations
301301+302302+**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/`.
303303+304304+**`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.
305305+306306+**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.
307307+308308+**`[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.