Files
fusion/packages/mobile
gsxdsm 8bc3d7b0a5 FN-6042: raise dependency security floors
Harden dependency floors and update the Vitest toolchain to patched releases.

- upgrade workspace vitest and @vitest/coverage-v8 dependencies to the 4.1 line across packages and plugins
- pin transitive protobufjs via pnpm overrides and lockfile updates to patched versions
- adapt Vitest configs, engine test helpers, and security-floor coverage for the new dependency baselines
- add the published CLI changeset and related workspace/package metadata updates included in the task branch

Files changed:
 .changeset/fn-6042-security-dependencies.md        |   5 +
 AGENTS.md                                          |   4 +
 Dockerfile                                         |   3 +
 docs/PLUGIN_AUTHORING.md                           |   2 -
 package.json                                       |   3 +-
 packages/cli/package.json                          |   4 +-
 packages/cli/vitest.config.ts                      |   2 +-
 packages/core/package.json                         |   4 +-
 packages/core/vitest.config.ts                     |   2 +-
 packages/dashboard/app/test/mockApi.ts             |   4 +-
 packages/dashboard/package.json                    |   4 +-
 packages/dashboard/vitest.config.ts                |   2 +-
 packages/desktop/package.json                      |   4 +-
 packages/desktop/vitest.config.ts                  |   2 +-
 packages/droid-cli/package.json                    |   2 +-
 packages/droid-cli/vitest.config.ts                |   2 +-
 packages/engine/package.json                       |   4 +-
 .../engine/src/__tests__/executor-test-helpers.ts  |  29 +-
 .../engine/src/__tests__/gridlock-detector.test.ts |   5 +-
 .../src/__tests__/heartbeat-scheduler.test.ts      |   3 +-
 packages/engine/src/__tests__/scheduler.test.ts    |  24 +-
 packages/engine/src/__tests__/self-healing.test.ts |   5 +
 packages/engine/tsconfig.json                      |   3 +-
 packages/engine/vitest.config.ts                   |  10 +-
 packages/i18n/package.json                         |   2 +-
 packages/i18n/vitest.config.ts                     |   7 +
 packages/mobile/package.json                       |   2 +-
 packages/mobile/vitest.config.ts                   |   2 +-
 packages/pi-claude-cli/package.json                |   2 +-
 packages/pi-claude-cli/vitest.config.ts            |   2 +-
 packages/pi-llama-cpp/package.json                 |   2 +-
 packages/pi-llama-cpp/vitest.config.ts             |   2 +-
 packages/plugin-sdk/package.json                   |   2 +-
 packages/plugin-sdk/vitest.config.ts               |   2 +-
 .../examples/fusion-plugin-auto-label/package.json |   2 +-
 .../fusion-plugin-auto-label/vitest.config.ts      |   2 +-
 .../examples/fusion-plugin-ci-status/package.json  |   2 +-
 .../fusion-plugin-ci-status/vitest.config.ts       |   2 +-
 .../fusion-plugin-notification/package.json        |   2 +-
 .../fusion-plugin-notification/vitest.config.ts    |   2 +-
 .../fusion-plugin-settings-demo/package.json       |   2 +-
 .../fusion-plugin-settings-demo/vitest.config.ts   |   2 +-
 plugins/fusion-plugin-acp-runtime/package.json     |   2 +-
 plugins/fusion-plugin-acp-runtime/vitest.config.ts |   2 +-
 plugins/fusion-plugin-agent-browser/package.json   |   2 +-
 .../fusion-plugin-agent-browser/vitest.config.ts   |   2 +-
 .../fusion-plugin-cli-printing-press/package.json  |   2 +-
 .../vitest.config.ts                               |   2 +-
 .../package.json                                   |   2 +-
 .../src/__tests__/orchestrator-live-output.test.ts |   4 +-
 .../vitest.config.ts                               |   2 +-
 plugins/fusion-plugin-cursor-runtime/package.json  |   4 +-
 .../fusion-plugin-dependency-graph/package.json    |   4 +-
 .../vitest.config.ts                               |   2 +-
 plugins/fusion-plugin-droid-runtime/package.json   |   2 +-
 .../fusion-plugin-droid-runtime/vitest.config.ts   |   2 +-
 plugins/fusion-plugin-even-cards/package.json      |   2 +-
 plugins/fusion-plugin-even-cards/vitest.config.ts  |   2 +-
 .../package.json                                   |   2 +-
 .../vitest.config.ts                               |   2 +-
 plugins/fusion-plugin-hermes-runtime/package.json  |   2 +-
 .../fusion-plugin-hermes-runtime/vitest.config.ts  |   2 +-
 .../fusion-plugin-openclaw-runtime/package.json    |   2 +-
 .../vitest.config.ts                               |   2 +-
 .../fusion-plugin-paperclip-runtime/package.json   |   2 +-
 .../vitest.config.ts                               |   2 +-
 plugins/fusion-plugin-reports/package.json         |   2 +-
 .../src/__tests__/review-panel.test.ts             |   6 +-
 plugins/fusion-plugin-reports/vitest.config.ts     |   2 +-
 plugins/fusion-plugin-roadmap/package.json         |   2 +-
 plugins/fusion-plugin-roadmap/vitest.config.ts     |   2 +-
 plugins/fusion-plugin-whatsapp-chat/package.json   |   2 +-
 .../fusion-plugin-whatsapp-chat/vitest.config.ts   |   2 +-
 pnpm-lock.yaml                                     | 626 ++++++++-------------
 .../__tests__/dependency-security-floor.test.mjs   |  95 ++++
 75 files changed, 475 insertions(+), 491 deletions(-)

Fusion-Task-Id: FN-6042

Fusion-Task-Lineage: fff6a1cb-8937-435c-9a91-b7c7a59cc80e
2026-06-08 15:19:27 -07:00
..
2026-05-31 20:05:32 -07:00

@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

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

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: "<topic>"

…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.

ShareManager

ShareManager opens platform-native sharing when available and always includes a Fusion deep link in the shared payload.

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.

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
  • 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

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=<encoded-url>

This hash-based behavior is intended for development/testing only and is not a production universal-link replacement.