Files
familysync/.planning/phases/05-web-push-notifications/05-UAT.md
T
Lucas Berger 1f3c672194 test(05): UAT — Test 5 PASS (coalescing); Test 4 issue (blocked-notif recovery link broken)
SettingsSheet 'How to enable' calls onClose instead of showing instructions;
leaves no recovery path once browser-blocked. Test 4 push delivery still
unverified (needs a subscribed Android session).
2026-06-10 14:06:13 -04:00

87 lines
7.8 KiB
Markdown

---
status: testing
phase: 05-web-push-notifications
source: [05-VERIFICATION.md]
started: 2026-06-10T02:46:43Z
updated: 2026-06-10T17:14:00Z
---
## Current Test
number: 3
name: 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.
awaiting: user response
## Tests
### 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.
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: pass
note: "PASS confirmed on a real iPhone via the SCHEDULED path (no manual trigger). Initially failed; two root causes found and fixed: (1) node-cron 4.2.1 skipped EVERY scheduled execution in the long-running API process ('missed execution' each tick) → replaced node-cron with setInterval in all 3 broker workers (quick 260610-i4x). (2) The reminder scan only checked a fixed [now+14,now+16] window with no catch-up, so a missed/late tick dropped the reminder permanently → added a catch-up window (now, now+16min] + per-uid exactly-once dedup (quick 260610-hbu). After redeploy, a shared-calendar timed event triggered a push reminder on its own (setInterval → catch-up scan → VAPID sign → Apple 201 → SW showNotification on the iPhone). Also required fixing a truncated VAPID private key in .env earlier."
fix_commits: ["260610-hbu (catch-up + per-uid dedup)", "260610-i4x (node-cron→setInterval)"]
### 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.
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: 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
expected: After a week without opening the app, opening it again silently re-subscribes (if permission still granted) and notifications continue to be delivered.
why_human: Requires real elapsed time and a physical iOS device. Cannot be simulated.
result: [pending]
### 4. Android FCM: event-change push arrives after the other member modifies a calendar event
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.
result: issue
reported: "On Android, could not enable notifications. Once the browser has blocked notifications, the SettingsSheet shows a 'Notifications are blocked' hint with a 'How to enable' link, but the link does nothing except close the settings sheet — no instructions shown, leaving the user with no recovery path. Could not get a subscribed Android session, so the event-change push leg itself remains untested."
severity: major
root_cause: "BUG in apps/pwa/src/components/SettingsSheet.tsx: the 'How to enable' button (the denied-permission hint) is wired to onClick={onClose} — it just dismisses the sheet. It should open the OS-specific InstructionSheet (Android/iOS steps) the same way PermissionDeniedBanner.tsx does. The InstructionSheet component exists but is local to PermissionDeniedBanner and unused by SettingsSheet. Note: being unable to RE-enable from within the web app once the browser blocked the site is expected (browsers won't let a page re-prompt after denial) — but the recovery guidance must work; right now it doesn't."
note: "Event-change push delivery itself (the actual Test 4 assertion) is still UNVERIFIED — blocked on getting a subscribed Android session, which requires unblocking notifications in Chrome site settings first."
### 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.
why_human: Requires two devices/sessions, real timing, and real push delivery. Playwright-cli can exercise the API hooks but not multi-device push receipt.
result: pass
note: "Confirmed on-device — 5 rapid list edits produced a single coalesced push (not 5)."
## Summary
total: 5
passed: 3
issues: 1
pending: 1
skipped: 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
- truth: "A shared-calendar timed event triggers a push reminder ~15 min before start, delivered to subscribed devices"
status: RESOLVED 2026-06-10
reason: "Originally failed (scheduled reminder never fired). Two root causes found + fixed: (a) node-cron 4.2.1 skipped EVERY scheduled execution in the long-running API process → replaced with setInterval in all 3 broker workers (quick 260610-i4x, commit d9efbc1); (b) reminder scan had no catch-up so a missed/late tick dropped the reminder → added catch-up window (now, now+16min] + per-uid exactly-once dedup (quick 260610-hbu, commit 19d92c6). After redeploy, a real shared-calendar event triggered a push on the iPhone via the SCHEDULED path with no manual trigger."
severity: major
test: 1
artifacts: [apps/api/src/broker/reminderScheduler.ts, apps/api/src/broker/poller.ts, apps/api/src/broker/outboxWorker.ts]
note: "Side benefit: the node-cron→setInterval fix also restores the CalDAV poller (5-min sync) and outbox drain (15s), which were ALSO being skipped by node-cron in the long-running process."
- truth: "When notifications are browser-blocked, the user is given a working path to re-enable them"
status: failed
reason: "SettingsSheet 'How to enable' link only closes the sheet (onClick={onClose}); shows no instructions. Blocks Test 4 (could not get a subscribed Android session to test event-change push)."
severity: major
test: 4
artifacts: [apps/pwa/src/components/SettingsSheet.tsx, apps/pwa/src/components/PermissionDeniedBanner.tsx]
missing:
- "Wire SettingsSheet 'How to enable' to open the OS-specific InstructionSheet (extract/share it from PermissionDeniedBanner) instead of calling onClose."
- "After unblocking in Chrome site settings, re-run Test 4: modify a shared event as member A, confirm member B's Android device receives the 'updated an event' push within the 5-min poll cycle."