test(05): UAT — iOS push delivery PROVEN on-device (test 2 pass); reminder scheduling issue (test 1)
Delivery chain verified end-to-end on a real iPhone (subscribe -> VAPID sign -> Apple 201 -> SW showNotification). Test 1 reminder did not fire on schedule: node-cron missed the window tick + the scan has no catch-up, so a missed tick drops the reminder permanently. Gap + fix direction recorded.
This commit is contained in:
@@ -3,17 +3,16 @@ status: testing
|
|||||||
phase: 05-web-push-notifications
|
phase: 05-web-push-notifications
|
||||||
source: [05-VERIFICATION.md]
|
source: [05-VERIFICATION.md]
|
||||||
started: 2026-06-10T02:46:43Z
|
started: 2026-06-10T02:46:43Z
|
||||||
updated: 2026-06-10T02:46:43Z
|
updated: 2026-06-10T16:25:00Z
|
||||||
---
|
---
|
||||||
|
|
||||||
## Current Test
|
## Current Test
|
||||||
|
|
||||||
number: 1
|
number: 3
|
||||||
name: iOS PWA install → push subscription → 15-min reminder receipt
|
name: iOS subscription health-check keeps subscription alive after 1+ week of inactivity
|
||||||
expected: |
|
expected: |
|
||||||
After adding FamilySync to the Home Screen on an iOS 16.4+ device and tapping
|
After a week without opening the app, opening it again silently re-subscribes
|
||||||
"Enable Notifications", a push notification appears on the lock screen ~15
|
(if permission still granted) and notifications continue to be delivered.
|
||||||
minutes before a shared Family-calendar timed event starts.
|
|
||||||
awaiting: user response
|
awaiting: user response
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
@@ -21,12 +20,16 @@ awaiting: user response
|
|||||||
### 1. iOS PWA install → push subscription → 15-min reminder receipt
|
### 1. iOS PWA install → push subscription → 15-min reminder receipt
|
||||||
expected: After adding FamilySync to the Home Screen on an iOS 16.4+ device and tapping "Enable Notifications", a push notification appears on the lock screen ~15 minutes before a shared Family-calendar timed event starts.
|
expected: After adding FamilySync to the Home Screen on an iOS 16.4+ device and tapping "Enable Notifications", a push notification appears on the lock screen ~15 minutes before a shared Family-calendar timed event starts.
|
||||||
why_human: iOS-Safari standalone push delivery cannot be driven by playwright-cli per CLAUDE.md — requires a physical iOS device + Home Screen install.
|
why_human: iOS-Safari standalone push delivery cannot be driven by playwright-cli per CLAUDE.md — requires a physical iOS device + Home Screen install.
|
||||||
result: [pending]
|
result: issue
|
||||||
|
reported: "Scheduled 15-min reminder did NOT arrive at the window. node-cron logged 'missed execution' at 15:59/16:00/16:01 UTC (exactly the [now+14,now+16] reminder window). A manual scan with injected time found the event and dispatched to the same subscription → Apple 201 → the notification arrived on the iPhone. So delivery works; the scheduled trigger was dropped."
|
||||||
|
severity: major
|
||||||
|
root_cause: "Two factors. (1) node-cron missed the every-minute reminder tick during the window (intermittent — measured setInterval drift was negligible afterward and host/container clocks were in sync, so not sustained clock drift; likely transient tick starvation). (2) DESIGN: reminderScheduler scans only the fixed [now+14,now+16] window keyed to one exact tick, with no catch-up — a single missed/late tick drops the reminder permanently. Dispatch chain (VAPID sign → Apple → SW showNotification on iOS) is proven working."
|
||||||
|
|
||||||
### 2. iOS push subscription does not receive NotAllowedError
|
### 2. iOS push subscription does not receive NotAllowedError
|
||||||
expected: Tapping "Enable Notifications" on iOS in the installed PWA (or the Settings toggle) successfully calls pushManager.subscribe() without throwing NotAllowedError. Both vapidKey and swRegistration are pre-resolved in state before the tap.
|
expected: Tapping "Enable Notifications" on iOS in the installed PWA (or the Settings toggle) successfully calls pushManager.subscribe() without throwing NotAllowedError. Both vapidKey and swRegistration are pre-resolved in state before the tap.
|
||||||
why_human: NEW-CR-01 fix is verified in code (zero awaits between tap and subscribe()), but runtime confirmation on a physical iOS device is the only way to close this.
|
why_human: NEW-CR-01 fix is verified in code (zero awaits between tap and subscribe()), but runtime confirmation on a physical iOS device is the only way to close this.
|
||||||
result: [pending]
|
result: pass
|
||||||
|
note: "Confirmed on a real iPhone (installed standalone PWA). Enable Notifications succeeded with no NotAllowedError; subscription persisted (push_subscriptions id 176, user 3, web.push.apple.com endpoint). Required first fixing a truncated VAPID private key in .env (was 30 bytes → restored to a valid matched 32-byte pair)."
|
||||||
|
|
||||||
### 3. iOS subscription health-check keeps subscription alive after 1+ week of inactivity
|
### 3. iOS subscription health-check keeps subscription alive after 1+ week of inactivity
|
||||||
expected: After a week without opening the app, opening it again silently re-subscribes (if permission still granted) and notifications continue to be delivered.
|
expected: After a week without opening the app, opening it again silently re-subscribes (if permission still granted) and notifications continue to be delivered.
|
||||||
@@ -37,6 +40,7 @@ result: [pending]
|
|||||||
expected: When member A modifies a shared event title/time/location, member B receives a push notification on Android within the next 5-minute poll cycle, showing "A updated an event" with the event title.
|
expected: When member A modifies a shared event title/time/location, member B receives a push notification on Android within the next 5-minute poll cycle, showing "A updated an event" with the event title.
|
||||||
why_human: End-to-end push delivery through FCM to a real Android device with a subscribed session cannot be driven by playwright-cli.
|
why_human: End-to-end push delivery through FCM to a real Android device with a subscribed session cannot be driven by playwright-cli.
|
||||||
result: [pending]
|
result: [pending]
|
||||||
|
note: "Delivery chain to a real device (VAPID → push service → SW showNotification) is now proven via the iOS path; the Android-specific leg (FCM endpoint + a subscribed Android session) is still untested."
|
||||||
|
|
||||||
### 5. List-change push coalescing is observable
|
### 5. List-change push coalescing is observable
|
||||||
expected: Member B making 5 rapid grocery-list edits results in a SINGLE push notification to member A (not 5), naming the actor and the list, arriving after the 45-second coalesce window.
|
expected: Member B making 5 rapid grocery-list edits results in a SINGLE push notification to member A (not 5), naming the actor and the list, arriving after the 45-second coalesce window.
|
||||||
@@ -46,10 +50,26 @@ result: [pending]
|
|||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
total: 5
|
total: 5
|
||||||
passed: 0
|
passed: 1
|
||||||
issues: 0
|
issues: 1
|
||||||
pending: 5
|
pending: 3
|
||||||
skipped: 0
|
skipped: 0
|
||||||
blocked: 0
|
blocked: 0
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **Delivery pipeline PROVEN on a real iOS device (2026-06-10):** iOS standalone-PWA subscribe → `push_subscriptions` row → VAPID-signed `web-push` send → `web.push.apple.com` 201 → service-worker `push` handler `showNotification()` → notification on the iPhone lock screen. This is the core of Phase 5 and de-risks tests 4 and 5 (same chain, different trigger/endpoint).
|
||||||
|
- Prereq fix applied: VAPID `.env` private key was truncated (30 bytes); restored to a valid 32-byte key that pairs with the public key (verified via ECDH derivation).
|
||||||
|
- Shared calendar wired: operator's "FamilySync" Fastmail calendar synced as `calendars.id=10`, marked `is_shared=1` (D-16), giving reminders a real target.
|
||||||
|
|
||||||
## Gaps
|
## Gaps
|
||||||
|
|
||||||
|
- truth: "A shared-calendar timed event triggers a push reminder ~15 min before start, delivered to subscribed devices"
|
||||||
|
status: failed
|
||||||
|
reason: "Scheduled reminder did not fire. node-cron missed the every-minute tick during the [now+14,now+16] window; the scan has NO catch-up, so the reminder was dropped permanently. Underlying dispatch+delivery verified working (manual trigger → Apple 201 → notification on the iPhone)."
|
||||||
|
severity: major
|
||||||
|
test: 1
|
||||||
|
artifacts: [apps/api/src/broker/reminderScheduler.ts, apps/api/src/index.ts]
|
||||||
|
missing:
|
||||||
|
- "Resilient reminder scan: persist a last-scanned-time watermark and scan [lastScanned, now+16min] each run (keep the (uid, minuteBucket) dedup) so a missed/late cron tick is recovered on the next run instead of being lost."
|
||||||
|
- "Confirm node-cron tick reliability on the real Unraid host (the misses were observed on the WSL2 dev box; need to know if prod is affected)."
|
||||||
|
|||||||
Reference in New Issue
Block a user