---
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
