From 4b65298afe45dd697c00bdd8acc9ae77434d6860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 23 May 2026 12:45:11 +0200 Subject: [PATCH] fix(notifications): apns self-test fires MEDICATION_REMINDER for lock-screen parity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-channel "Send test" button on the Settings notification card hit `/api/notifications/apns/test`, which forwarded `eventType: "SYSTEM_ALERT"` to the dispatcher. The dispatcher's `isTimeSensitive` gate keys off MEDICATION_REMINDER specifically, so the test push went out at the default `active` interruption-level. iOS then summarises those into the Notification Center rather than presenting a lock-screen banner, which leaves the user with no signal whether a real dose reminder — which DOES use the time-sensitive path — will surface visibly. Switching the test event-type to MEDICATION_REMINDER exercises the same dispatcher branch the production reminder cron runs: time-sensitive interruption-level, priority 10, the MEDICATION_REMINDER action category, collapse-id. A successful self-test now demonstrates the exact lock-screen behaviour the user can expect from a real dose reminder; an unsuccessful self-test (sound-only, Notification-Center-only) points the user at their own iOS Settings → HealthLog → "Allow Time Sensitive Notifications" toggle or active Focus mode rather than appearing to be a server-side bug. Title and body strings remain the existing "Test notification" copy so the push cannot be confused with an actual scheduled dose. --- CHANGELOG.md | 1 + src/app/api/notifications/apns/test/route.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7916a4422..c7cfc36a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ v1.4.47 closed the H1 + Mediums punch list. v1.4.49 bundles the deferred v1.4.47 - Workout-batch integration tests (`workout-batch-create`, `workout-batch-race`) updated to match the v1.4.42 cross-source dedup semantics. The integration suite had been red on `main` since v1.4.45 (three release commits shipped through it). Suite is green again. - Twelve stale "migration 0076" references in code comments corrected. `consecutiveFailures` sites read 0077; `disable-coach` sites read 0078. Comment-only sweep, no behavioural change. - `docs/ops/attempt-number-semantics.md` referenced migration 0076 for the dropped `consecutiveFailures` column; the actual migration is 0077. +- Per-channel APNs "Send test" button (Settings → notification channels) now fires with `eventType: "MEDICATION_REMINDER"` so the dispatcher's `time-sensitive` interruption-level + `apns-priority: 10` branch runs. The previous `SYSTEM_ALERT` event-type used the dispatcher's default `active` level, which iOS may summarise into the Notification Center rather than presenting a lock-screen banner — leaving the user with no signal whether a real dose reminder will surface visibly. A successful test now exercises the exact path production reminders take. Title and body strings still read "Test notification" so the push cannot be mistaken for an actual scheduled dose. ### Performance diff --git a/src/app/api/notifications/apns/test/route.ts b/src/app/api/notifications/apns/test/route.ts index 23a940667..ec024804b 100644 --- a/src/app/api/notifications/apns/test/route.ts +++ b/src/app/api/notifications/apns/test/route.ts @@ -57,10 +57,18 @@ export const POST = apiHandler(async (request: NextRequest) => { : defaultLocale; const t = getServerTranslator(locale).t; + // Send as MEDICATION_REMINDER so the dispatcher's time-sensitive / + // priority-10 branch fires. Otherwise the test runs at the default + // `active` interruption-level and iOS may summarise it into the + // Notification Center rather than presenting a lock-screen banner — + // leaving the user with no signal whether real medication reminders + // (which DO use time-sensitive) will surface on the lock screen. + // The title/body strings still read "Test notification" so the user + // doesn't mistake this for an actual scheduled dose. const result = await sendViaApns(user.id, { title: t("notifications.admin.testNotificationTitle"), message: t("notifications.admin.testNotificationBody"), - eventType: "SYSTEM_ALERT", + eventType: "MEDICATION_REMINDER", }); annotate({