feat(reminders): leave alarm via AlarmKit on iOS 26+#64
Open
JamieRuderman wants to merge 9 commits into
Open
Conversation
Schedule the focused-trip leave reminder as a real Apple AlarmKit alarm on iOS 26+ so it breaks through Silent Mode / Focus, instead of a local notification that's easy to miss. Falls back to the existing notification everywhere else (Android, web, AlarmKit unavailable/denied, or any scheduling error). The alarm replaces the notification — never both. Stacked on the go-focused-trip refactor: the AlarmKit preference lives in the focused-trip orchestration (useFocusedTrip) plus a thin iOS-only wrapper; notificationScheduler.ts stays a pure primitive. - Add @capgo/capacitor-alarm + src/lib/native/leaveAlarm.ts wrapper (availability/auth/schedule/cancel + pure decideReminderChannel). - armAndPersistReminder prefers a Leave Alarm, scheduling the new channel before retiring the old one so a failed (re)schedule never leaves the user with no reminder; persist the alarm id on FocusedTripReminder. - Cancel/clear/disarm retire both channels (notification + alarm). - Label the active pill "Leave alarm" when AlarmKit-backed (en + es). - Add NSAlarmKitUsageDescription to Info.plist. - Vitest coverage for the channel decision and alarm scheduling/fallback. Android keeps the notification: the plugin's Android path only hands off to the system Clock app (uncancellable), and true break-through alarms require USE_FULL_SCREEN_INTENT, which Android 14+ won't grant a transit app by default.
- Date-safety (high): @Capgo's createAlarm only takes a clock time and fires at the NEXT occurrence of that HH:MM — it can't target a calendar date. A weekend trip focused on a weekday (serviceDate = nextServiceDate) or a "tomorrow" departure whose time recurs earlier today would have fired days early. scheduleLeaveAlarm now bails (→ dated notification fallback) unless fireAt is the next occurrence of its own clock time (alarmFiresOnIntendedDay). - Stale-focus race: a permission prompt can block long enough for the user to Stop / switch trains / the trip to auto-clear. armAndPersistReminder now re-reads the focus after scheduling and, if it changed, rolls back the freshly scheduled alarm/notification instead of resurrecting the stale trip. - Permission gate: notification permission is now requested only on the notification fallback path, so an iOS user who denied notifications but authorized AlarmKit can still set a Leave Alarm. Adds unit coverage for the day-safety helper and the off-day fallback.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Schedules the focused-trip leave reminder as a real Apple AlarmKit alarm on iOS 26+ so it breaks through Silent Mode / Focus, instead of a local notification that's easy to miss. Everywhere else (Android, web, AlarmKit unavailable/denied, or any scheduling failure) it falls back to the existing local notification. The alarm replaces the notification — never both, so the user gets a single alert.
How it works
src/lib/native/leaveAlarm.ts(new) — thin, mockable wrapper over@capgo/capacitor-alarm: availability/auth checks,scheduleLeaveAlarm,cancelLeaveAlarm, and the puredecideReminderChannel. AlarmKit is scoped to iOS only on purpose — the plugin's Android path just hands off to the system Clock app (uncancellable) and doesn't fit our auto-scheduled, per-trip, cancellable model.useFocusedTrip.ts—armAndPersistReminderprefers a Leave Alarm and persists its id onFocusedTripReminder; cancel/clear/disarm retire both channels.notificationScheduler.tsstays a pure primitive (untouched).NSAlarmKitUsageDescriptionadded toInfo.plist; en + es copy added.Why @Capgo (not a hand-rolled Swift plugin)
This is a Capacitor 8 hybrid app; AlarmKit is native-only.
@capgo/capacitor-alarm(v8.1.2, Capacitor‑8 aligned, MPL‑2.0, actively maintained) gives AlarmKit +cancelAlarm+ permissions with no custom Swift to maintain or verify, and sidesteps the AlarmKit widget-extension question.Review fixes already applied
createAlarmonly takes a clock time and fires at the next occurrence of that HH:MM — it can't target a calendar date.scheduleLeaveAlarmnow bails to the dated notification unlessfireAtis the next occurrence of its own clock time (alarmFiresOnIntendedDay), so a weekend trip focused on a weekday no longer fires days early.Android
Keeps the existing notification. A true break-through alarm there needs
USE_FULL_SCREEN_INTENT, which Android 14+ won't grant a transit app by default.Test plan
npm run typecheck,npm run lint,npm run buildnpm run test:unit— 99 passing (new coverage: channel decision, day-safety helper, alarm scheduling + off-day/denied/unavailable fallbacks)npx cap sync ios; verify the wrapper against the installed plugin API; on an iOS 26 device confirm grant → alarm fires at the leave time labeled "Leave for SMART train" → cancel/replace; on iOS 15–25 confirm fallback to a notification with no crash; on Android confirm the notification still schedules/cancels.Known limitation
For trips not departing within the next-occurrence window, iOS uses the dated notification rather than an alarm (the "Leave alarm" label only appears when a true alarm was scheduled). Supporting alarms for arbitrary future dates would need a custom AlarmKit bridge taking an absolute
Date(the plugin can't) — possible follow-up.https://claude.ai/code/session_015t5VmBqUDZ77s7WHZdvukq
Generated by Claude Code