--- phase: 260610-ka9 plan: 01 subsystem: pwa/push tags: [push, android, notifications, service-worker, ux] dependency_graph: requires: [] provides: [audible-android-push-notifications] affects: [apps/pwa/src/sw.ts, apps/pwa/src/components/InstructionSheet.tsx] tech_stack: added: [] patterns: [NotificationOptions narrow cast for missing lib.dom members] key_files: modified: - apps/pwa/src/sw.ts - apps/pwa/src/components/InstructionSheet.tsx decisions: - "Used `as NotificationOptions` narrow cast (not type suppression) to handle renotify/vibrate absent from this lib.dom; cast is scoped to the single options object" metrics: duration: ~5 min completed: 2026-06-10 --- # Phase 260610-ka9 Plan 01: Fix Silent Android Push Notifications Summary **One-liner:** SW showNotification enriched with icon/badge/renotify/vibrate to restore audible Android heads-up alerting; ANDROID_STEPS copy generalized to Chrome or Edge. ## Tasks Completed | # | Task | Commit | Files | |---|------|--------|-------| | 1 | Enrich showNotification options to fix silent Android push | c864fc4 | apps/pwa/src/sw.ts | | 2 | Make Android re-enable instructions browser-agnostic | c864fc4 | apps/pwa/src/components/InstructionSheet.tsx | ## Changes Made ### apps/pwa/src/sw.ts The `showNotification` call previously passed only `{ body, tag, data: { url } }`. On Android Chromium, when the same `tag` is reused the existing notification is silently updated (no heads-up, sound, or vibration). Added four keys to the options object: - `icon: '/icon-192.png'` — notification icon (asset exists in public/) - `badge: '/icon-192.png'` — Android status-bar badge (masked to monochrome) - `renotify: true` — forces a re-alert even when tag is reused (the primary fix) - `vibrate: [200, 100, 200]` — ensures vibration/heads-up on Android The `body`, `tag`, and `data: { url }` keys are unchanged. `silent` is absent. Payload parsing, the iOS-18.4+ declarative branch, try/catch, and `event.waitUntil` are untouched. iOS ignores the added options. **TypeScript note:** `renotify` and `vibrate` are absent from this project's `lib.dom` version of `NotificationOptions`. Resolved with a minimal `as NotificationOptions` cast on the object literal — scoped to these two keys only, no blanket suppression. ### apps/pwa/src/components/InstructionSheet.tsx Changed `ANDROID_STEPS[0]` from `'Open Chrome on your phone'` to `'Open your browser (Chrome or Edge) on your phone'`. All other steps (three-dot menu, Site Settings, Notifications, Allow) are unchanged. IOS_STEPS untouched. ## Verification Results ``` pnpm --filter @familysync/pwa typecheck → tsc --noEmit: PASS (no errors) pnpm --filter @familysync/pwa build → tsc && vite build: PASS → dist/sw.js emitted: YES grep -q "renotify" dist/sw.js → FOUND: renotify in dist/sw.js pnpm --filter @familysync/pwa test → vitest run: 15 test files, 187 tests — all PASSED ``` ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] TypeScript error: renotify absent from NotificationOptions in this lib.dom** - **Found during:** Task 1 typecheck - **Issue:** `tsc --noEmit` reported `error TS2353: Object literal may only specify known properties, and 'renotify' does not exist in type 'NotificationOptions'` (same for `vibrate`). - **Fix:** Changed `const options: NotificationOptions = { ... }` to `const options = { ... } as NotificationOptions`. The cast is minimal and scoped to the single options object; no other errors suppressed. - **Files modified:** apps/pwa/src/sw.ts - **Commit:** c864fc4 ## Known Stubs None. ## Threat Flags None. Changes are confined to client-side notification display options and UI copy; no new network endpoints, auth paths, or trust boundaries introduced. ## Self-Check: PASSED - `apps/pwa/src/sw.ts` exists and contains `renotify: true` and `as NotificationOptions` - `apps/pwa/src/components/InstructionSheet.tsx` contains `'Open your browser (Chrome or Edge) on your phone'` - Commit c864fc4 exists in git log - `dist/sw.js` emitted and contains `renotify` - All 187 tests pass