CraftApp.swift gives a page a badge setter — setBadgeCount, reached as craft.badge — which calls UNUserNotificationCenter.setBadgeCount and is careful not to request a badge-only authorization:
private func setBadgeCount(_ count: Int, callbackId: String?) {
// Changing a badge must not ask for a badge-only authorization …
UNUserNotificationCenter.current().setBadgeCount(count) { … }
}CraftNativeActions.swift — the bridge a native screen uses — has no equivalent. Its Notifications module answers only cancel, cancelAll, pending and schedule, and is gated behind enableLocalNotifications besides. There is no badge module in the case ("…", _) list at all:
Auth, Background, Biometrics, Brightness, Browser, Clipboard, Database, DeepLinks,
Device, Dialog, Files, Haptics, Health, HeartRate, KeepAwake, Lifecycle, Linking,
Location, Music, Network, Notifications, Push, SecureStorage, Share, Speech,
StatusBar, StorageWhy this now matters more than it did. With renderer: "web" an app could always reach the page's bridge for this. With renderer: "native" there is no page, so the capability is unreachable from anywhere in the app.
What it costs. An app cannot show or clear its own unread badge. Today the only way a badge appears is inside an APNs payload, which means:
- the badge cannot be set at launch from a count the app already knows
- it cannot be cleared when the person reads those notifications somewhere else, or in the app itself — it stays until a later push happens to carry a lower number
- an app with notifications turned off, or before push is configured, can never badge at all
What would be enough. A Badge module on the native bridge — set(count) and clear() — mapping to the same setBadgeCount the page path already uses, with the same care about not requesting a badge-only authorization. A badge capability flag alongside it would let an app declare it, as push and health already do.
More generally this looks like a parity gap rather than a design decision: the page bridge and the native bridge should offer the same capabilities, or the difference should be documented. Found in stacksjs/hq.training while trying to show an unread count on the app icon.