Files
fusion/MOBILE.md
gsxdsm da36094d74 FN-7586: add iOS edge-swipe-back gesture parity via AppDelegate patch
Enables the WKWebView interactive-pop (edge-swipe-back) gesture on iOS, matching the Android predictive-back parity already in place, by patching the generated AppDelegate.swift during cap sync.

- Add packages/mobile/scripts/patch-ios-webview.ts: patches AppDelegate.swift to cast the root view controller to CAPBridgeViewController and set webView?.allowsBackForwardNavigationGestures = true before the didFinishLaunchingWithOptions return, is idempotent, and no-ops safely when no ios/ project exists yet
- Wire capacitor:sync:after to run both patch-android-manifest.ts and patch-ios-webview.ts so cap sync keeps both native back-gesture opt-ins in sync; add patch:ios-webview script
- Add unit tests covering patch, idempotency, already-patched, missing-project, and source-preservation cases for the new iOS webview patch, alongside the existing Android manifest patch tests
- Add TaskDetail.swipe-back.test.tsx coverage proving the shared popstate-driven nav-history dismissal stack (no iOS-specific native-back emitter needed) already satisfies the gesture's dismissal contract
- Add mobile-scripts.test.ts dashboard coverage and update MOBILE.md / packages/mobile/README.md documenting the new iOS gesture opt-in

Files changed:
 MOBILE.md                                          |  12 ++
 .../dashboard/app/__tests__/mobile-scripts.test.ts |  31 +++++
 .../__tests__/TaskDetail.swipe-back.test.tsx       | 131 +++++++++++++++++++++
 packages/mobile/README.md                          |  36 ++++++
 packages/mobile/package.json                       |   3 +-
 packages/mobile/scripts/patch-ios-webview.ts       | 119 +++++++++++++++++++
 packages/mobile/src/__tests__/native-shell.test.ts | 122 +++++++++++++++++++
 7 files changed, 453 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-7586

Fusion-Task-Lineage: 248092a1-de1b-49b6-94ef-70675a7b93a1

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-05 12:24:12 -07:00

201 lines
8.6 KiB
Markdown

# Mobile Development Guide
Fusion mobile builds package the dashboard web client into Capacitor shells via `packages/mobile/`.
## Prerequisites
- **Node.js** 22+
- **pnpm** 10+
- **Xcode** (iOS builds)
- **Android Studio** (Android SDK + emulator tooling)
- **Java JDK** 21+ (Android Gradle builds)
## Quick Start
```bash
pnpm install
pnpm mobile:build
pnpm mobile:ios # open iOS project in Xcode
# or
pnpm mobile:android # open Android project in Android Studio
```
## Development with Live Reload
Use the live-reload helpers in `packages/mobile/scripts/live-reload.ts`:
```bash
pnpm mobile:dev:ios
pnpm mobile:dev:android
```
These commands automatically set:
- `FUSION_LIVE_RELOAD=true`
- `FUSION_SERVER_URL=http://localhost:5173` (default)
To target a different dev server URL, set `FUSION_SERVER_URL` before running (or pass `--server-url` directly to the script):
```bash
FUSION_SERVER_URL=http://192.168.1.50:5173 pnpm mobile:dev:android
```
## Building for Production
```bash
pnpm mobile:build
```
This runs:
1. `pnpm --filter @fusion/dashboard build`
2. `pnpm --filter @fusion/mobile cap sync`
After sync, open native projects for release signing/distribution:
```bash
pnpm mobile:ios
pnpm mobile:android
```
## PWA Installation
The dashboard includes a PWA manifest (`packages/dashboard/app/public/manifest.json`) and service worker (`packages/dashboard/app/public/sw.js`).
### Standalone iOS home-indicator spacing
- Installed standalone mode sets `--standalone-bottom-gap` via `@media (display-mode: standalone) { :root { ... } }`.
- Bottom spacing must stay scoped to layout/component rules (for example mobile content padding and footer/nav offsets), not global `#root` padding.
- Keep standalone spacing additive with existing safe-area handling (`env(safe-area-inset-bottom, 0px)`).
- The `.project-content` wrapper is the single source of truth for mobile-nav/footer/standalone bottom reservation; inline dashboard tabs (for example Agents and Missions) must only apply their own content padding and must not re-add `--mobile-nav-height` or duplicate footer spacing.
Install from browser:
- **Chrome**: three-dot menu → **Install app**
- **Safari (iOS)**: **Share** → **Add to Home Screen**
> Service workers require **HTTPS** (or `localhost`). PWA install/offline behavior will not work on plain HTTP origins.
## Mobile UX Behavior
### Native shell onboarding and connection profiles
First launch in the mobile shell enters a shell-level remote connection onboarding flow before dashboard model onboarding.
For the canonical flow (QR/manual setup, saved profiles, active-profile behavior, and security caveats), see [Native Shell Connection Guide](./docs/native-shell.md).
Implementation notes:
- Mobile shell profiles are persisted in shell-local storage (Capacitor Preferences), separate from Fusion project/global settings.
- Active-profile deletion fallback is shell-owned: deleting the active profile promotes the first remaining profile, and deleting the final profile resets to a clean empty state.
- The dashboard consumes this through the shared `window.fusionShell` connection APIs.
### Native Back Handling (Android Back + iOS Edge-Swipe-Back)
Task-detail dismissal via native "back" (Android hardware Back / predictive-back gesture,
iOS edge-swipe-back, or plain browser swipe-back) converges on a single shared invariant:
the dashboard's `useNavigationHistory` nav-history stack. See `packages/mobile/README.md`
→ "Native Back Handling" for the full Android (`fusion:native-back`) vs. iOS (`popstate`)
routing details and the tracked post-`cap sync` patch scripts (`scripts/
patch-android-manifest.ts`, `scripts/patch-ios-webview.ts`) that keep each platform's native
gesture delivery enabled across `cap sync` regenerations.
### Planning Mode
Planning Mode opens directly into the composer pane on mobile when no planning sessions exist, avoiding an empty-sidebar dead end. On desktop/tablet the split view is unaffected. Once sessions are saved, mobile shows the session list as usual and the user can navigate between list and detail panes.
### Chat and Quick Chat mobile scroll/readability behavior
- Chat and Quick Chat must keep scrolling container-scoped (`.chat-messages` / `.quick-chat-panel-messages`) and must not switch to page-level scroll APIs (including `scrollIntoView()`) to avoid mobile Safari viewport drift.
- Full Chat direct-thread mobile headers include a title-triggered quick session switcher; preserve one-pane behavior (back-to-list still works) and keep the switcher scoped to direct sessions only (room threads keep existing room header/back behavior).
- Both surfaces now pause live-tail autoscroll when the user scrolls away from bottom, show a temporary **Latest** jump control, and resume tail-follow only after jumping back.
- Mobile bubble widths are intentionally slightly wider for readability, but safe-area padding, full-screen Quick Chat bounds, and compact mobile tool-call summaries must remain intact.
## CI/CD Pipeline
Mobile CI is defined in `.github/workflows/mobile.yml`.
- Trigger manually via **GitHub Actions → Mobile Builds → Run workflow**
- Also runs on push to `main` when files under `packages/mobile/**` or `packages/dashboard/**` change
- Jobs:
- `build-web` (build dashboard and upload `dist/client`)
- `build-ios` (sync/build iOS when `packages/mobile/ios/` exists)
- `build-android` (sync/build Android when `packages/mobile/android/` exists)
Artifacts from the Mobile Builds workflow are retained for 30 days. Tagged binary releases also run the Android build leg in `.github/workflows/release.yml`; `.github/workflows/test-release.yml` mirrors that path in its tag-less rehearsal artifact.
When the repository has Android signing secrets configured (`ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD`), the release pipeline publishes signed `fusion-android-release.apk` and `fusion-android-release.aab` assets plus `.sha256` checksums. Without those secrets, the pipeline preserves the secret-free fallback and publishes the unsigned debug APK as `fusion-android.apk` plus `fusion-android.apk.sha256`.
Install the signed APK by enabling **Install unknown apps** for the transfer source on the device, then sideloading it:
```bash
adb install fusion-android-release.apk
```
Verify the APK signer before distribution when Android SDK build-tools are available:
```bash
apksigner verify --print-certs fusion-android-release.apk
```
The `.aab` file is for Play distribution and is not directly sideloadable with `adb install`. Automated Play Store / Play Console upload remains out of scope for now because it needs a Google service-account JSON secret, a published Play listing, and fastlane or `r0adkll/upload-google-play` wiring; that work is tracked separately in FN-7043 from the sideload-first release assets.
## Replacing PWA Icons
Current icons are placeholders:
- `packages/dashboard/app/public/icons/icon-192.png`
- `packages/dashboard/app/public/icons/icon-512.png`
Generate production icons from `logo.svg` (example with sharp-cli):
```bash
npx sharp-cli -i packages/dashboard/app/public/logo.svg -o packages/dashboard/app/public/icons/icon-192.png resize 192 192
npx sharp-cli -i packages/dashboard/app/public/logo.svg -o packages/dashboard/app/public/icons/icon-512.png resize 512 512
```
You can also use ImageMagick if preferred.
## Troubleshooting
### `cap sync` fails
- Confirm dependencies are installed: `pnpm install`
- Ensure platform projects have been added (`packages/mobile/ios` / `packages/mobile/android`)
- Re-run: `pnpm mobile:sync`
### iOS build fails
- Verify Xcode version/toolchain compatibility
- Open `packages/mobile/ios/App/App.xcworkspace` in Xcode and resolve signing settings
### Android build fails
- Verify Java 21+ (`java -version`)
- Confirm Android SDK and Gradle tooling are installed via Android Studio
### PWA does not install
- Verify HTTPS (or localhost)
- Confirm `manifest.json` and `sw.js` are served from the built app
- Clear old service worker/cache and reload
## Script Reference
Root scripts (`package.json`):
- `mobile:build`
- `mobile:ios`
- `mobile:android`
- `mobile:dev:ios`
- `mobile:dev:android`
- `mobile:sync`
Mobile package scripts (`packages/mobile/package.json`):
- `cap`
- `dev:ios`
- `dev:android`
- `build:mobile`
- `patch:ios-webview` — idempotently enables the iOS WKWebView edge-swipe-back gesture in the generated `ios/App/App/AppDelegate.swift` (safe no-op if `ios/` doesn't exist yet)
- `capacitor:sync:after` — Capacitor's own post-`cap sync` hook; currently runs the iOS webview patch so `cap sync` regeneration can't silently drop the gesture opt-in