bridge_mobile_notifications.zig left this action to the Swift shim and wrote down exactly why. The blocker was never a missing selector:
Both Swift failure paths are rejections, not results …
ios_asynccan only resolve —deliverJsonis the whole asynchronous reply surface. Resolving the string "denied" for a denied prompt would be textbook fabricated success … The fix is anios_async.deliverError(ticket, code, message)sharingdeliverJson's slot,dispatch_async_fhop, and request-id restoration.
| commit | date | |
|---|---|---|
| that comment written | 0a97def | 2026-08-31 |
deliverErrorCode lands, sharing exactly those three things | abfbd8f | 2026-09-01 |
The comment described a limitation that stopped existing the next day, and the shim has been keeping an action Zig could serve. Worth re-reading the other deferrals for the same reason before assuming they still hold — that note is now in the ratchet's own history.
The chain
Authorization, then add: the spec's two stages in the spec's order. Content and request are built inside the authorization completion, where the spec builds them, so the autoreleased request never crosses a pool boundary and nothing needs retaining — which is what the original note expected to be the hard part. What survives is the identifier, owned heap memory parked on the slot and answered in stage two, because that is the value a page needs to cancel with later. Both stages take-and-clear, so a double-fired completion replies to nobody rather than twice.
A crash not reproduced
The spec passes delay / 1000 to UNTimeIntervalNotificationTrigger unguarded, and an interval of zero or less raises an uncatchable Objective-C exception — so {delay: 0} SIGABRTs the app today. A non-positive delay here produces a nil trigger, which is how UserNotifications spells "deliver now". bridge_notification.zig documents the same trap and answers it the same way.
Constants cited, not guessed
UNAuthorizationOptionAlert|Sound|Badge= 7 —UNUserNotificationCenter.h:23-25, independently the same valuebridge_mobile_permissions.zigalready resolved.- Calendar mask = 252 —
NSCalendar.h:61-66defines each unit as the matchingkCFCalendarUnit*;CFCalendar.h:68-73gives year1<<2through second1<<7.
A sibling module once shipped a guessed HealthKit bit that named a different option and crashed the app, so both are pinned by a test that names its source.
Deliberate divergences
A missing notification object rejects. The spec's case has no else, so that call never reaches a callback and the page's promise hangs forever. Refusals carry typed codes rather than the spec's free text, matching every other migrated module.
Also in here
getPendingNotifications abandoned its slot and left the caller hanging when its reply would not shape — the same "ios_async can only resolve" reasoning, recorded in the same file. It rejects now.
Verification
Cold zig build test: 150/150 steps, 2486/2498 passed, 12 skipped, 0 failed. zig fmt --check clean. build-ios-all -Doptimize=ReleaseSafe produces all three archives.