Found while fixing #258.
The SDK's NotificationOptions says to schedule with scheduleAt (a timestamp), and its own example uses it:
await notifications.schedule({ title: 'Reminder', body: 'Time to take a break', scheduleAt: Date.now() + 3600000 })notifications.schedule hands the options to craft.notifications.schedule, which posts scheduleNotification with them unchanged. The contract layer in CraftApp.swift only rewrites show, which adds its own scheduleAt: Date.now(). Neither iOS scheduler reads that field:
- Swift:
scheduleLocalNotificationbuilds its trigger fromtimestamp(ms) ordelay(ms). - Zig:
triggerFrominbridge_mobile_notifications.zigdoes the same.
So with no timestamp or delay, the trigger is nil, and the reminder meant for an hour from now fires immediately. Only callers who know the bridge's private field names (timestamp, delay) get a scheduled notification.
Fix: map scheduleAt onto the bridge's timestamp, either in the SDK or in the page contract's schedule, or have both schedulers read it. Mapping at one layer keeps the native readers unchanged. Add a test that fails without the fix. The mobile E2E harness's local-notification stage (#258) can check the timing end to end.