Motivation
The mobile app can wake on a NIP-PL push and show a banner, but the alert is silent. Operators using Buzz as a human–agent workspace need an audible cue when the phone is locked or the app is backgrounded — same expectation as Slack/iMessage, not a mute reconnect banner.
This is a product request from a multi-workspace operator (Litbox nest). It is not “please add push.” iOS push registration, lease publish, gateway delivery, and the Notification Service Extension already landed in #6269 (feat(mobile): push notifications MVP, merged 2026-08-28). The remaining hole is sound.
Why it is silent today
NIP-PL’s public APNs profile is class-blind: every accepted delivery sends one compiled-in body. That body has no sound key.
Verified at block/buzz bd734904:
// crates/buzz-push-gateway/src/model.rs
pub const APNS_RECONNECT_PAYLOAD: &[u8] =
br#"{"aps":{"alert":{"body":"Reconnect to your relay now"},"mutable-content":1}}"#;
The same exact UTF-8 constant is pinned in:
docs/nips/NIP-PL.md:227
docs/formal/nip-pl/delivery.py (FIXED_BODY)
docs/formal/nip-pl/fixed_payload.py (C)
Under APNs, an alert without sound displays a banner without playing audio. Client permission is already requested (.alert, .badge, .sound in mobile/ios/Runner/AppDelegate.swift). The NSE (mobile/ios/NotificationService/NotificationService.swift) copies the incoming UNMutableNotificationContent and never sets .sound. So neither the gateway payload nor the extension adds a sound — permission alone does not play one.
The gateway cannot vary the body per event. NIP-PL § Wake Delivery requires application_body(a) = C_transport for every send, and DeliveryRequest carries only v, endpoint_grant, request_id, expires_at. silent is a matching preference only; the public APNs profile does not expose relay-selected notification classes. Sound therefore has two legal levers: put it in the constant, or set it on-device in the NSE. The constant is the one that still works when the extension times out or fails.
Proposed solution
v1: system default sound on every public-profile APNs wake. No per-slot custom sounds.
Amend the four pins together to:
{"aps":{"alert":{"body":"New activity in Buzz"},"sound":"default","mutable-content":1}}
Change the body text in the same edit. Without the NSE, this string is what every user sees on every push, and "Reconnect to your relay now" is internal plumbing language. "New activity in Buzz" (or maintainer-chosen copy) is a user-facing placeholder the NSE can still replace when it has relay data.
crates/buzz-push-gateway/src/apns.rs already asserts every sent body equals APNS_RECONNECT_PAYLOAD, so it follows the constant. No Desktop change. No Android work in this issue.
Acceptance:
- A lock-screen / background APNs wake on a physical iOS device plays the system default sound when notification permission includes sound.
- The four pins stay byte-identical to each other.
- Formal noninterference still holds:
application_body() takes no arguments.
- NSE replacement path still runs; it may keep or set
UNNotificationSound.default but must not depend on that for v1 audibility.
Alternatives considered
| Approach |
Why not for v1 |
NSE sets content.sound and leave the constant mute |
Silent whenever the extension is not invoked, times out, or fails closed — the exact fallback path users hit. |
| Per-slot / per-channel custom sounds (Desktop local sounds) |
Desktop’s per-slot sounds are for an online app. Background APNs cannot reuse that path; the public profile is class-blind by spec. |
| Silent data-only wake + local notification |
Fails when the app is killed and iOS does not schedule the local notification. |
| Re-file “mobile never registers for push” |
Already shipped in #6269. #4657 is the registration bug, now stale for iOS. |
Non-goals
- Android / FCM — already #6092 (client) and #3229 (gateway profile).
- Per-channel mute/level UI — #3234 / #3160.
- Self-hosted relays reaching
push.buzz.xyz — #5206.
- Custom sound files or notification categories.
Additional context
#6269 validated lock-screen presentation and NSE replacement on a physical iPhone 12 mini; that write-up does not claim an audible alert. Apple provisioning (.p8, App Attest app id, production vs sandbox) remains an operator/release step and is not this issue.
Duplicate check
Searched 2026-09-01 for push notification, alert sound, APNs, APNS_RECONNECT_PAYLOAD. Closest, none of which ask for sound on the fixed APNs body:
Motivation
The mobile app can wake on a NIP-PL push and show a banner, but the alert is silent. Operators using Buzz as a human–agent workspace need an audible cue when the phone is locked or the app is backgrounded — same expectation as Slack/iMessage, not a mute reconnect banner.
This is a product request from a multi-workspace operator (Litbox nest). It is not “please add push.” iOS push registration, lease publish, gateway delivery, and the Notification Service Extension already landed in #6269 (
feat(mobile): push notifications MVP, merged 2026-08-28). The remaining hole is sound.Why it is silent today
NIP-PL’s public APNs profile is class-blind: every accepted delivery sends one compiled-in body. That body has no
soundkey.Verified at
block/buzzbd734904:The same exact UTF-8 constant is pinned in:
docs/nips/NIP-PL.md:227docs/formal/nip-pl/delivery.py(FIXED_BODY)docs/formal/nip-pl/fixed_payload.py(C)Under APNs, an alert without
sounddisplays a banner without playing audio. Client permission is already requested (.alert, .badge, .soundinmobile/ios/Runner/AppDelegate.swift). The NSE (mobile/ios/NotificationService/NotificationService.swift) copies the incomingUNMutableNotificationContentand never sets.sound. So neither the gateway payload nor the extension adds a sound — permission alone does not play one.The gateway cannot vary the body per event. NIP-PL § Wake Delivery requires
application_body(a) = C_transportfor every send, andDeliveryRequestcarries onlyv,endpoint_grant,request_id,expires_at.silentis a matching preference only; the public APNs profile does not expose relay-selected notification classes. Sound therefore has two legal levers: put it in the constant, or set it on-device in the NSE. The constant is the one that still works when the extension times out or fails.Proposed solution
v1: system default sound on every public-profile APNs wake. No per-slot custom sounds.
Amend the four pins together to:
{"aps":{"alert":{"body":"New activity in Buzz"},"sound":"default","mutable-content":1}}Change the body text in the same edit. Without the NSE, this string is what every user sees on every push, and
"Reconnect to your relay now"is internal plumbing language."New activity in Buzz"(or maintainer-chosen copy) is a user-facing placeholder the NSE can still replace when it has relay data.crates/buzz-push-gateway/src/apns.rsalready asserts every sent body equalsAPNS_RECONNECT_PAYLOAD, so it follows the constant. No Desktop change. No Android work in this issue.Acceptance:
application_body()takes no arguments.UNNotificationSound.defaultbut must not depend on that for v1 audibility.Alternatives considered
content.soundand leave the constant muteNon-goals
push.buzz.xyz— #5206.Additional context
#6269 validated lock-screen presentation and NSE replacement on a physical iPhone 12 mini; that write-up does not claim an audible alert. Apple provisioning (
.p8, App Attest app id, production vs sandbox) remains an operator/release step and is not this issue.Duplicate check
Searched 2026-09-01 for
push notification,alert sound,APNs,APNS_RECONNECT_PAYLOAD. Closest, none of which ask forsoundon the fixed APNs body: