From 4fc12ca7909a40b49f6e5da795f0381911e5684e Mon Sep 17 00:00:00 2001 From: Alan Wizemann Date: Sat, 25 Apr 2026 07:51:43 +0200 Subject: [PATCH] fix(ios-notifications): feature-gate Approve/Deny stub actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Push Notifications capability is disabled in the iOS target, so the APNS code path can't fire today — but the `SCARF_PENDING_PERMISSION` category was registered unconditionally, exposing the stub-only `APPROVE_PERMISSION` / `DENY_PERMISSION` action handlers as a route iOS could surface action buttons on if a notification ever slipped through. Add `NotificationRouter.apnsEnabled` (=`false`) and gate `registerCategories()` behind it. While `false`, the category is explicitly cleared so iOS has no path to route a tap to the stubs. The gate is the single switch — flipping it requires the capability + sender + real handler implementations to all land together. Verified: iOS build succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Notifications/NotificationRouter.swift | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/scarf/Scarf iOS/Notifications/NotificationRouter.swift b/scarf/Scarf iOS/Notifications/NotificationRouter.swift index 13379e2..9b4e549 100644 --- a/scarf/Scarf iOS/Notifications/NotificationRouter.swift +++ b/scarf/Scarf iOS/Notifications/NotificationRouter.swift @@ -27,6 +27,23 @@ import ScarfIOS final class NotificationRouter: NSObject, UNUserNotificationCenterDelegate { static let shared = NotificationRouter() + /// Master gate for the APNs / push pipeline. While `false`: + /// + /// - The `SCARF_PENDING_PERMISSION` category (Approve / Deny actions) + /// is NOT registered, so even if a notification with that + /// `categoryIdentifier` slipped through somehow, iOS would render + /// it without action buttons rather than route the tap to the + /// stub-only `APPROVE_PERMISSION` / `DENY_PERMISSION` handlers. + /// - `registerForRemoteNotifications()` stays uncalled. + /// + /// Flip to `true` only when (a) the Push Notifications capability is + /// enabled in the Xcode target, (b) Hermes ships a push sender, and + /// (c) `APPROVE_PERMISSION` / `DENY_PERMISSION` cases below have real + /// implementations (not just `logger.info` stubs). This gate is the + /// single switch — flipping it in isolation should not cause the + /// stub handlers to silently swallow real user intent. + static let apnsEnabled = false + private let logger = Logger(subsystem: "com.scarf", category: "NotificationRouter") /// Coordinator reference set by ScarfGoTabRoot on appear so the @@ -95,7 +112,19 @@ final class NotificationRouter: NSObject, UNUserNotificationCenterDelegate { /// Install the notification category that exposes Approve / Deny /// action buttons on the lock screen. Safe to call multiple times /// — registerCategories replaces. + /// + /// **Gated on `apnsEnabled`.** Until Hermes ships a real push sender + /// and the `APPROVE_PERMISSION` / `DENY_PERMISSION` handlers have + /// real implementations, register the empty set so iOS has no + /// category by which to route action-tapped notifications to the + /// stub handlers. When `apnsEnabled` flips to `true`, the category + /// is installed and the handlers are simultaneously expected to be + /// real. func registerCategories() { + guard Self.apnsEnabled else { + UNUserNotificationCenter.current().setNotificationCategories([]) + return + } let approve = UNNotificationAction( identifier: "APPROVE_PERMISSION", title: "Approve",