fix(notifications): real OS permission gate + delivery for #1152#1247
Conversation
…ai#1152 The "Send test notification" flow reported success but no banner appeared because the bundled tauri-plugin-notification's permission_state() / request_permission() are hardcoded to Granted on desktop, and its .show() spawns notify-rust on a background task and discards the result. So a denied user passed the permission gate, the OS silently dropped the banner, and the UI happily declared "sent." - Move notification IPC into app/src-tauri/src/native_notifications/ and route show_native_notification through UNUserNotificationCenter on macOS: real authorization check, real addNotificationRequest:withCompletionHandler: dispatch, with delivery errors surfaced back to the caller. - Switch the frontend bridge from plugin:notification|* to the dedicated notification_permission_state / notification_permission_request / show_native_notification commands so the UI sees the real OS state. - Cover the new contract with bridge unit tests and small Rust unit tests for the macOS state mapping. Closes tinyhumansai#1152
The capability ACL rejected notification_permission_state / notification_permission_request / show_native_notification with "Command not found" because Tauri 2 requires custom commands to be explicitly listed under permissions/. The previous commit moved these into a dedicated module and switched the frontend bridge to call them directly (the old plugin commands plugin:notification|* were already covered by notification:default), but never updated the allowlist — verified end-to-end on macOS by clicking "Send test notification" and watching the banner appear.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis PR refactors native notification handling by extracting Tauri command handlers from the main library into a dedicated ChangesNative Notifications Refactor
Sequence DiagramsequenceDiagram
participant UI as Frontend<br/>(TypeScript)
participant Bridge as Tauri Bridge<br/>(tauriBridge.ts)
participant TauriRuntime as Tauri Runtime<br/>(invoke)
participant RustBackend as Native Notifications<br/>(Rust)
participant macOSAPI as macOS API<br/>(UNUserNotificationCenter)
UI->>Bridge: getNotificationPermissionState(requestIfNeeded=true)
Bridge->>TauriRuntime: invoke("notification_permission_state")
TauriRuntime->>RustBackend: notification_permission_state()
RustBackend->>macOSAPI: UNNotificationSettings query
macOSAPI-->>RustBackend: authorization status
RustBackend-->>TauriRuntime: "granted"|"denied"|"prompt"
TauriRuntime-->>Bridge: backend state string
Bridge->>Bridge: mapBackendState(raw)
Bridge-->>UI: NotificationPermissionState
UI->>Bridge: showNativeNotification(title, body, tag?)
alt permission not granted
Bridge->>Bridge: ensureNotificationPermission()
Bridge->>TauriRuntime: invoke("notification_permission_request")
RustBackend->>macOSAPI: requestAuthorizationWithOptions
macOSAPI-->>RustBackend: user response
end
Bridge->>TauriRuntime: invoke("show_native_notification", {title, body, tag})
TauriRuntime->>RustBackend: show_native_notification(...)
RustBackend->>macOSAPI: UNMutableNotificationContent + UNNotificationRequest
macOSAPI->>macOSAPI: add notification request
macOSAPI-->>RustBackend: dispatch result (with timeout)
RustBackend-->>TauriRuntime: Ok(()) | Err(NSError message)
TauriRuntime-->>Bridge: result
Bridge-->>UI: {delivered: true} | {delivered: false, reason, error}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Summary
tauri-plugin-notification:permission_state()is hardcoded toGrantedon desktop, and.show()spawnsnotify-ruston a background task and discards the result. So a denied user sailed through the permission gate, the OS silently dropped the banner, and the UI happily declared "sent."UNUserNotificationCenter.add(...)call so the completion handler propagates real errors instead of getting swallowed.Changes
app/src-tauri/src/native_notifications/module:notification_permission_stateandnotification_permission_requestdriveUNUserNotificationCenterdirectly so the bridge sees the real OS authorization state (the plugin's hardcodedGrantedis no longer trusted).show_native_notificationre-checks authorization, builds aUNNotificationRequest, and waits foraddNotificationRequest:withCompletionHandler:so a successful return means the system accepted the request, not just that an async dispatch was scheduled.app/src/lib/nativeNotifications/tauriBridge.ts) switched fromplugin:notification|*to the dedicated commands.app/src-tauri/permissions/allow-core-process.toml(Tauri 2 ACL requires explicit entries; the old plugin commands were covered bynotification:default).lib.rs(was 2100 lines, project rule prefers ≤500).Test plan
pnpm debug unit nativeNotifications— 23 new bridge tests for the new contract (uses dedicated commands, maps backend states correctly, surfaces Rust errors).pnpm debug unit OpenhumanLinkModal.notifications— existing 4 modal-flow tests still pass against the new bridge.cargo test --manifest-path app/src-tauri/Cargo.toml --lib native_notifications— Rust unit tests for the macOS state mapping helpers.cargo check --manifest-path app/src-tauri/Cargo.toml— Tauri shell compiles clean.pnpm dev:app:Note: an unrelated pre-existing test failure in
src/components/walkthrough/__tests__/AppWalkthrough.test.tsx(missingreact-joyrideresolution) exists onmainand is not introduced by this PR.Closes #1152
Summary by CodeRabbit
New Features
Refactor
Tests