# @toolctrl/mobile — Capacitor 7 native wrapper This workspace bundles the React Router v7 web app at `apps/web/` as a native iOS + Android shell via [Capacitor 7](https://capacitorjs.com/). It is **not** a separate UI codebase — there is exactly one product UI (the web app). This package only owns: - `capacitor.config.ts` — bundle id, web-dir, plugin config - `ios/` and `android/` native project skeletons (generated by `cap add`) - `src/auth.ts` — mobile session persistence wrapper around `@toolctrl/api-client-ts` - `src/sentry.ts` — Sentry-Capacitor bootstrap (mirrors `apps/web/app/sentry.client.ts`) - `src/pushRegistration.ts` — APNs/FCM token registration against the backend - Generated app icons and splash screens (under `ios/` and `android/`) ## Status **Code scaffold + push backend** is shipped under task #65 (Phase 08). Actual native builds, Xcode signing, App-Store / Play-Store submission are operator-owned tasks and are explicitly deferred — see [`docs/operations/capacitor.md`](../../docs/operations/capacitor.md). ## Local development ```bash # 1. Install workspace deps (from repo root) pnpm install # 2. Build the web bundle that the native wrapper will load pnpm --filter @toolctrl/mobile build:web # 3. Sync web assets + plugins into the native projects pnpm --filter @toolctrl/mobile sync # 4. Open in Xcode / Android Studio pnpm --filter @toolctrl/mobile open:ios pnpm --filter @toolctrl/mobile open:android ``` ### Live reload (dev workflow) For tight iteration loops point the wrapper at a running dev origin instead of the bundled `webDir`: ```bash export MOBILE_LIVE_RELOAD_URL=http://localhost:3000 # iOS simulator # export MOBILE_LIVE_RELOAD_URL=http://10.0.2.2:3000 # Android emulator pnpm --filter @toolctrl/mobile sync ``` This injects the URL into `capacitor.config.ts > server.url`. On the emulator/simulator the app then loads from your dev box every reload. **Never set this for production builds.** ## Plugins | Plugin | Purpose | |---|---| | `@capacitor/app` | App lifecycle + custom URL scheme handling | | `@capacitor/browser` | In-app browser for the login flow | | `@capacitor/status-bar` | Native status-bar theming | | `@capacitor/splash-screen` | Splash on cold start | | `@capacitor/push-notifications` | APNs + FCM token + display | | `@capacitor/local-notifications` | Foreground reminders + UAT smoke | | `@capacitor/preferences` | UserDefaults (iOS) / SharedPreferences (Android) session persistence | `@capacitor/preferences` is not encrypted storage. Keep bearer material short-lived here, or replace the auth storage adapter with a native secure-storage plugin before persisting long-lived refresh tokens. ## Why the native folders look minimal When this scaffold was committed the agent did **not** have a working local Xcode / Android-Studio install, so the iOS and Android projects ship as the minimum subset required for `cap sync` to succeed: - `apps/mobile/ios/App/App/Info.plist`, `App.xcodeproj/project.pbxproj`, AppDelegate stubs - `apps/mobile/android/app/build.gradle`, `AndroidManifest.xml`, `MainActivity.java`, `settings.gradle` An operator with the native tooling installed should run ```bash cd apps/mobile rm -rf ios android npx cap add ios npx cap add android npx cap sync ``` once to overwrite these skeletons with the canonical CLI output. Bundle id, app name and webDir stay identical — Capacitor reads them from `capacitor.config.ts`. ## See also - [`docs/improvement-plan/08-mobile-cli-storybook-sdk/capacitor.md`](../../docs/improvement-plan/08-mobile-cli-storybook-sdk/capacitor.md) full spec + deviation log - [`docs/operations/capacitor.md`](../../docs/operations/capacitor.md) build / signing / submission runbook