# @fusion/mobile ## Native Shell Onboarding & Remote Connections Mobile uses a shell-level onboarding flow for first-run connection setup before dashboard onboarding. - **Remote-first flow:** mobile onboarding goes directly to remote server connection. - **Connection setup options:** QR scan (`startQrScan`) or manual server URL entry, with optional auth token. - **Saved profiles:** multiple remote profiles are persisted in shell-local storage and can be added via QR/manual entry, edited, switched, and deleted later from dashboard connection management. - **Active-profile fallback:** deleting the active profile automatically promotes the first remaining profile; deleting the last profile resets to an empty state (`activeProfileId: null`, `profiles: []`) so onboarding/manager recovery can reopen cleanly. - **Storage boundary:** profile/mode state is stored only in mobile shell-local storage (via native plugin wrappers), not in Fusion project settings/local dashboard project storage. - **Bridge contract:** mobile exposes `window.fusionShell` (`getState`, `listProfiles`, `saveProfile`, `deleteProfile`, `setActiveProfile`, `startQrScan`, `openConnectionManager`, `subscribe`) so shared dashboard code can run host-neutrally. - **Dashboard-safe capability contract:** shared dashboard helpers should consume the typed `MobileShellDashboardBridge` subset (`getState?`, `openConnectionManager?`). If either function is missing at runtime, treat connection-management as unsupported instead of throwing. Native wrappers are isolated under `src/plugins/native-shell.ts`, `src/plugins/connection-profiles.ts`, and `src/plugins/qr-scanner.ts` so dashboard code never calls vendor-specific APIs directly. ### Regression coverage locked by tests `packages/mobile/src/__tests__/connection-profiles.test.ts`, `native-shell.test.ts`, and `qr-scanner.test.ts` now lock these contracts: - first-run remote setup via QR/manual payloads (including optional auth token handling) - saved-profile edit, active-profile switching, and persisted-state restore across module reinit/relaunch - bridge reads (`getState`, `listProfiles`) plus connection-manager event dispatch - malformed/empty QR payload handling and unavailable-scanner fallback behavior ## Push Notifications `PushNotificationManager` supports two complementary notification channels: 1. **Native push notifications** via Capacitor Push Notifications (`@capacitor/push-notifications`) for FCM/APNs token registration and notification tap handling. 2. **ntfy.sh streaming subscription** via polling-driven topic management, so the app can receive in-app notifications without server-side FCM/APNs setup. ### Initialization ```ts import { PushNotificationManager } from "@fusion/mobile"; const manager = new PushNotificationManager({ settingsFetcher: fetchGlobalSettings, }); await manager.start(); ``` You can also initialize through `initializePlugins({ pushNotifications: { ... } })` if you want plugin bootstrapping from a single entrypoint. ### Event API ```ts manager.on("notification:tapped", ({ taskId }) => { if (taskId) { navigateToTask(taskId); } }); manager.on("notification:received", ({ title, body }) => { console.log("Foreground notification", title, body); }); manager.on("ntfy:message", ({ taskId, message }) => { console.log("ntfy message", taskId, message); }); ``` ### ntfy.sh Integration Behavior When `settingsFetcher()` returns: - `ntfyEnabled: true` - `ntfyTopic: ""` …the manager starts (or switches) a live subscription to `{ntfyBaseUrl}/{topic}/json`. If settings disable ntfy or clear the topic, the subscription is automatically stopped. ### Device Token Access Use `manager.getDeviceToken()` after registration to retrieve the native device token for future server-side FCM/APNs integration work. ### Out of Scope This package currently handles **receiving** push notifications and in-app routing events only. Server-side FCM/APNs delivery infrastructure (token storage, provider credentials, push sending services) is intentionally out of scope for this feature. ## Native Sharing & Deep Links ### ShareManager `ShareManager` opens platform-native sharing when available and always includes a Fusion deep link in the shared payload. ```ts import { ShareManager } from "@fusion/mobile"; const manager = new ShareManager(); await manager.initialize(); await manager.shareTask({ id: "FN-1118", title: "Mobile Plugins - Native Sharing & Deep Links", description: "Implements native share sheet support and deep link parsing.", }); ``` #### Share behavior + fallbacks - Builds a payload with: - `title`: `task.title` or fallback `Task {id}` - `text`: task description (truncated to 200 chars with `...` when needed) - `url`: `${deepLinkBaseUrl}{task.id}` (default base: `fusion://task/`) - **Native (Capacitor)**: uses `@capacitor/share` - **Web fallback**: uses `navigator.share(...)` when available - **Final fallback**: copies the deep-link URL to `navigator.clipboard.writeText(...)` #### Share events - `share:success` → `{ taskId }` - `share:cancelled` → `{ taskId }` - `share:error` → `{ taskId, error }` ### DeepLinkManager `DeepLinkManager` handles incoming links and emits parsed payloads for app-level navigation. ```ts import { DeepLinkManager } from "@fusion/mobile"; const deepLinks = new DeepLinkManager({ scheme: "fusion://", universalLinkHosts: ["app.fusion.dev"], }); await deepLinks.initialize(); deepLinks.on("deeplink:received", (payload) => { // route to screen/task/project in app UI console.log(payload); }); ``` #### Supported URL patterns - `fusion://task/{taskId}` - `fusion://project/{projectId}` - `fusion://project/{projectId}/task/{taskId}` - `fusion://settings` - `fusion://agents` - Query params are preserved in `payload.params` for custom-scheme links Universal links are supported when the host is allowed in `universalLinkHosts`, e.g.: - `https://app.fusion.dev/?task=FN-123` - `https://app.fusion.dev/?project=my-project&task=FN-123&target=task` #### Deep link events - `deeplink:received` → parsed `DeepLinkPayload` - `deeplink:error` → `{ url, error }` Use `handleUrl(url)` for programmatic handling (for example, push-notification tap flows that already provide a URL string). ### Integration flow: share -> open -> navigate A common flow is: 1. Use `ShareManager.shareTask(...)` to share a task link like `fusion://task/FN-123` 2. Recipient opens that link on mobile 3. `DeepLinkManager` receives/parses the URL 4. Your UI listens to `deeplink:received` and navigates to the matching task view ### Capacitor deep-link scheme registration The Fusion mobile app registers the custom URL scheme in `packages/dashboard/capacitor.config.ts`: - `server.iosScheme = "fusion"` - `server.androidScheme = "fusion"` ### Browser hash listener (development/testing) On non-native platforms, `DeepLinkManager` listens for hash changes in the form: - `#deeplink=` This hash-based behavior is intended for development/testing only and is not a production universal-link replacement.