Bumps [@capacitor/core](https://github.com/ionic-team/capacitor) from 7.6.1 to 8.5.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/ionic-team/capacitor/releases">@capacitor/core's releases</a>.</em></p> <blockquote> <h2>8.5.0</h2> <h1><a href="https://github.com/ionic-team/capacitor/compare/8.4.2...8.5.0">8.5.0</a> (2026-07-31)</h1> <h3>Bug Fixes</h3> <ul> <li><strong>cli:</strong> support TypeScript 7 when loading capacitor.config.ts (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8534">#8534</a>) (<a href="4c1c870941">4c1c870</a>)</li> </ul> <h3>Features</h3> <ul> <li><strong>cli:</strong> add migrator functionality for adopting UIScene (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8544">#8544</a>) (<a href="984fa85ba0">984fa85</a>)</li> <li><strong>ios:</strong> UIScene Support (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8536">#8536</a>) (<a href="3fa04a357c">3fa04a3</a>)</li> </ul> <h2>8.4.2</h2> <h2><a href="https://github.com/ionic-team/capacitor/compare/8.4.1...8.4.2">8.4.2</a> (2026-07-14)</h2> <h3>Bug Fixes</h3> <ul> <li><strong>android:</strong> explicitly grant URI permissions for image capture intent (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8526">#8526</a>) (<a href="6f2d328389">6f2d328</a>)</li> </ul> <h2>8.4.1</h2> <h2><a href="https://github.com/ionic-team/capacitor/compare/8.4.0...8.4.1">8.4.1</a> (2026-06-19)</h2> <h3>Bug Fixes</h3> <ul> <li><strong>cli:</strong> make SPM dependency patch work on prereleases (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8508">#8508</a>) (<a href="6048e90171">6048e90</a>)</li> <li><strong>cli:</strong> patch Capacitor SPM dependency version in plugins (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8492">#8492</a>) (<a href="28bb2c6870">28bb2c6</a>)</li> </ul> <h2>8.4.0</h2> <h1><a href="https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0">8.4.0</a> (2026-06-02)</h1> <h3>Bug Fixes</h3> <ul> <li><strong>android:</strong> show only the requested system bar (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8480">#8480</a>) (<a href="4c6c3219af">4c6c321</a>)</li> <li><strong>cli:</strong> revert live reload config on failure (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8485">#8485</a>) (<a href="1d031a4abe">1d031a4</a>)</li> <li><strong>SystemBars:</strong> make <code>safe-area-inset-x</code> available on API <= 34 (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8424">#8424</a>) (<a href="e456de083e">e456de0</a>)</li> <li><strong>SystemBars:</strong> respect <code>insetsHandling</code> disable (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8481">#8481</a>) (<a href="d4ad7ffe39">d4ad7ff</a>)</li> </ul> <h3>Features</h3> <ul> <li>add method getDouble to plugin config (<a href="https://redirect.github.com/ionic-team/capacitor/issues/7638">#7638</a>) (<a href="93c72de40a">93c72de</a>)</li> <li><strong>cli:</strong> add experimental packageOptions (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8471">#8471</a>) (<a href="258867b7bf">258867b</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/ionic-team/capacitor/blob/main/CHANGELOG.md">@capacitor/core's changelog</a>.</em></p> <blockquote> <h1><a href="https://github.com/ionic-team/capacitor/compare/8.4.2...8.5.0">8.5.0</a> (2026-07-31)</h1> <h3>Bug Fixes</h3> <ul> <li><strong>cli:</strong> support TypeScript 7 when loading capacitor.config.ts (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8534">#8534</a>) (<a href="4c1c870941">4c1c870</a>)</li> </ul> <h3>Features</h3> <ul> <li><strong>cli:</strong> add migrator functionality for adopting UIScene (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8544">#8544</a>) (<a href="984fa85ba0">984fa85</a>)</li> <li><strong>ios:</strong> UIScene Support (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8536">#8536</a>) (<a href="3fa04a357c">3fa04a3</a>)</li> </ul> <h2><a href="https://github.com/ionic-team/capacitor/compare/8.4.1...8.4.2">8.4.2</a> (2026-07-14)</h2> <h3>Bug Fixes</h3> <ul> <li><strong>android:</strong> explicitly grant URI permissions for image capture intent (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8526">#8526</a>) (<a href="6f2d328389">6f2d328</a>)</li> </ul> <h2><a href="https://github.com/ionic-team/capacitor/compare/8.4.0...8.4.1">8.4.1</a> (2026-06-19)</h2> <h3>Bug Fixes</h3> <ul> <li><strong>cli:</strong> make SPM dependency patch work on prereleases (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8508">#8508</a>) (<a href="6048e90171">6048e90</a>)</li> <li><strong>cli:</strong> patch Capacitor SPM dependency version in plugins (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8492">#8492</a>) (<a href="28bb2c6870">28bb2c6</a>)</li> </ul> <h1><a href="https://github.com/ionic-team/capacitor/compare/8.3.4...8.4.0">8.4.0</a> (2026-06-02)</h1> <h3>Bug Fixes</h3> <ul> <li><strong>android:</strong> show only the requested system bar (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8480">#8480</a>) (<a href="4c6c3219af">4c6c321</a>)</li> <li><strong>cli:</strong> revert live reload config on failure (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8485">#8485</a>) (<a href="1d031a4abe">1d031a4</a>)</li> <li><strong>SystemBars:</strong> make <code>safe-area-inset-x</code> available on API <= 34 (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8424">#8424</a>) (<a href="e456de083e">e456de0</a>)</li> <li><strong>SystemBars:</strong> respect <code>insetsHandling</code> disable (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8481">#8481</a>) (<a href="d4ad7ffe39">d4ad7ff</a>)</li> </ul> <h3>Features</h3> <ul> <li>add method getDouble to plugin config (<a href="https://redirect.github.com/ionic-team/capacitor/issues/7638">#7638</a>) (<a href="93c72de40a">93c72de</a>)</li> <li><strong>cli:</strong> add experimental packageOptions (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8471">#8471</a>) (<a href="258867b7bf">258867b</a>)</li> <li><strong>cli:</strong> capture ios_package_manager in telemetry (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8482">#8482</a>) (<a href="b4b297a52f">b4b297a</a>)</li> </ul> <h2><a href="https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4">8.3.4</a> (2026-05-12)</h2> <p><strong>Note:</strong> Version bump only for package capacitor</p> <h2><a href="https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3">8.3.3</a> (2026-05-08)</h2> <h3>Bug Fixes</h3> <ul> <li><strong>cli:</strong> copy plugin files in CocoaPods projects (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8467">#8467</a>) (<a href="b2d771926a">b2d7719</a>)</li> </ul> <h2><a href="https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2">8.3.2</a> (2026-05-07)</h2> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="3ab4139bd0"><code>3ab4139</code></a> Release 8.5.0</li> <li><a href="984fa85ba0"><code>984fa85</code></a> feat(cli): add migrator functionality for adopting UIScene (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8544">#8544</a>)</li> <li><a href="3fa04a357c"><code>3fa04a3</code></a> feat(ios): UIScene Support (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8536">#8536</a>)</li> <li><a href="4c1c870941"><code>4c1c870</code></a> fix(cli): support TypeScript 7 when loading capacitor.config.ts (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8534">#8534</a>)</li> <li><a href="f368a1b1ec"><code>f368a1b</code></a> chore(android): fix lint issues (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8542">#8542</a>)</li> <li><a href="1834b9740e"><code>1834b97</code></a> Release 8.4.2</li> <li><a href="6f2d328389"><code>6f2d328</code></a> fix(android): explicitly grant URI permissions for image capture intent (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8526">#8526</a>)</li> <li><a href="b789b683f0"><code>b789b68</code></a> chore: run <code>npm run fmt</code> to fix lint errors (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8516">#8516</a>)</li> <li><a href="7217b5215a"><code>7217b52</code></a> Release 8.4.1</li> <li><a href="6048e90171"><code>6048e90</code></a> fix(cli): make SPM dependency patch work on prereleases (<a href="https://redirect.github.com/ionic-team/capacitor/issues/8508">#8508</a>)</li> <li>Additional commits viewable in <a href="https://github.com/ionic-team/capacitor/compare/7.6.1...8.5.0">compare view</a></li> </ul> </details> <br /> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@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
MobileShellDashboardBridgesubset (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.
Android Back: hardware button and predictive-back GESTURE both dismiss task detail
AndroidBackButtonManager (src/plugins/native-shell.ts) subscribes to @capacitor/app's
backButton event and dispatches a single cancelable fusion:native-back browser event.
The dashboard's shared nav-history stack (useNavigationHistory) consumes that event to
dismiss the top task-detail surface (board main-panel, list-mobile, modal, and nested
detail) and return to the board/previous detail — the exact same invariant the browser
popstate/swipe-back path already uses. If nothing handles the event (!defaultPrevented),
Capacitor's own fallback runs unchanged: history.back() when canGoBack, else exitApp().
FN-7583: the Android system back gesture (predictive back / edge swipe, Android
13+, default-prominent at this project's targetSdk 35) previously did not reach that
listener, even though the hardware/legacy Back button did — the app never opted the
generated manifest into Android's predictive-back framework
(android:enableOnBackInvokedCallback="true"), so OnBackPressedDispatcher silently
dropped the gesture-completion callback while still delivering button presses via the
ordinary Window/Activity path. Because the native android/ project is generated by
cap sync and is git-ignored, the fix lives in tracked source:
scripts/patch-android-manifest.ts idempotently adds that manifest attribute to the
generated AndroidManifest.xml, and is wired into Capacitor's own capacitor:sync:after
npm-script hook (see package.json) so it runs automatically on every cap sync —
including build:mobile, dev:android, and cap run android (which syncs first). No
dashboard-side change was needed: once the gesture reaches the native backButton
listener, gesture and button converge on the identical fusion:native-back dispatch.
Manual invocation (e.g. after a bare npx cap sync outside build:mobile):
pnpm --filter @fusion/mobile patch:android-manifest
Mobile task-detail predictive-back transition (FN-7587)
FN-7583 (Android back-gesture parity) and FN-7586 (iOS edge-swipe-back parity) made native
"back" gestures functionally dismiss Fusion's mobile task-detail surfaces (board
main-panel, list-mobile, modal, and nested detail) through the dashboard's shared
nav-history invariant (useNavigationHistory / popstate / fusion:native-back). FN-7587
layers a presentation-only slide/fade transition on top of that unchanged routing:
- Mobile/native-only — the transition is gated to the mobile viewport (
<= 768px, matching the existingisMobile/OVERSIGHT_MENU_MOBILE_BREAKPOINTconvention in the dashboard); desktop task-detail never receives the animation class. - Non-interactive — the transition is a short CSS
@keyframesslide/fade (~200ms) triggered by mount/prop-state change, not by gesture progress. It does not intercept, delay, or reorder when theuseNavigationHistorypop /fusion:native-back/ empty-stack fallback fires; the animation is purely a CSS class applied to the already-real DOM node. - Honors
prefers-reduced-motion: reduce(neutralizes to an instant, transform-free show), mirroring the dashboard's existing reduced-motion convention (WorkflowSwitcher.css,TopProgressBar.css). - Interactive predictive-back is not implemented and is not feasible today from a
Capacitor single-page WebView on either platform: iOS's
allowsBackForwardNavigationGesturesgesture (used by FN-7586) exposes only a discretepopstateon commit, with no interactive-progress callback reachable from JS; Android's OS-owned predictive-back preview animates outside the single-Activity WebView and is not driveable from in-page DOM. A follow-up task is filed to revisit this if/when platform APIs expose gesture-progress callbacks to JS.
Implementation lives entirely in the dashboard package (packages/dashboard/app/styles.css,
packages/dashboard/app/components/TaskDetailModal.css, MainContent.tsx,
TaskDetailModal.tsx) — no mobile-shell-native code changes were required for this task.
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
Native Back Handling (Android Back + iOS Edge-Swipe-Back)
Mobile task-detail dismissal converges on a SINGLE shared invariant regardless of how the
user triggers "back": the dashboard's useNavigationHistory nav-history stack
(packages/dashboard/app/hooks/useNavigationHistory.ts), which board main-panel,
list-mobile, modal, and nested task-detail surfaces already consume for desktop/browser
swipe-back via ordinary popstate.
- Android hardware Back button + gesture (FN-7583):
AndroidBackButtonManagerinsrc/plugins/native-shell.tslistens for@capacitor/app'sbackButtonevent and dispatches a cancelablefusion:native-backcustom event;useNavigationHistoryconsumes it to dismiss the top task-detail surface (falling back to native browser-historyback()or app exit when Fusion owns no nav entry). The Android 13+ predictive-back gesture additionally requires the generatedAndroidManifest.xmlto opt intoandroid:enableOnBackInvokedCallback="true"for AndroidX's dispatcher to route the gesture-completion callback to the samebackButtonlistener — sinceandroid/is generated bycap syncand git-ignored, this opt-in lives in a tracked post-cap syncpatch (scripts/patch-android-manifest.ts), wired into thecapacitor:sync:afternpm hook. - iOS edge-swipe-back (FN-7586): iOS has no Capacitor
backButtonevent, so it does NOT go throughfusion:native-back. Instead, the WKWebView edge-swipe-back gesture (once enabled) drives ordinary back-forward navigation, which the browser delivers as apopstate— the exact same seam desktop/browser swipe-back already uses, and thatuseNavigationHistory.handlePopStatealready consumes correctly. The gesture itself is OFF by default:WKWebView.allowsBackForwardNavigationGesturesdefaults tofalse, Capacitor'sCAPBridgeViewControllernever sets it, and nocapacitor.config.tsiostoggle exists for it. Sinceios/is also generated bycap syncand git-ignored, this is enabled via a tracked post-cap syncpatch (scripts/patch-ios-webview.ts) that idempotently patches the generatedAppDelegate.swiftto setwebView?.allowsBackForwardNavigationGestures = trueonce the storyboard's rootCAPBridgeViewControllerhas loaded — also wired into the samecapacitor:sync:afternpm hook. Empty-stack behavior (no Fusion nav entry) is unaffected: the gesture just performs ordinary WKWebView history navigation with no forced trap/exit.
Both patch scripts are safe no-ops when their respective native platform directory
(android/ / ios/) hasn't been added yet, and are idempotent across repeated cap sync
runs (see src/__tests__/native-shell.test.ts).
Push Notifications
PushNotificationManager supports two complementary notification channels:
- Native push notifications via Capacitor Push Notifications (
@capacitor/push-notifications) for FCM/APNs token registration and notification tap handling. - 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: truentfyTopic: "<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.
Native Sharing & Deep Links
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.titleor fallbackTask {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://settingsfusion://agents- Query params are preserved in
payload.paramsfor custom-scheme links
Universal links are supported when the host is allowed in universalLinkHosts, e.g.:
https://app.fusion.dev/?task=FN-123https://app.fusion.dev/?project=my-project&task=FN-123&target=task
Deep link events
deeplink:received→ parsedDeepLinkPayloaddeeplink: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:
- Use
ShareManager.shareTask(...)to share a task link likefusion://task/FN-123 - Recipient opens that link on mobile
DeepLinkManagerreceives/parses the URL- Your UI listens to
deeplink:receivedand 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=<encoded-url>
This hash-based behavior is intended for development/testing only and is not a production universal-link replacement.