---
phase: 260610-ka9
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - apps/pwa/src/sw.ts
  - apps/pwa/src/components/InstructionSheet.tsx
autonomous: true
requirements: [NOTIF-FIX-01, NOTIF-FIX-02]

must_haves:
  truths:
    - 'Android push notifications alert audibly/visibly (heads-up + sound/vibration), not silently, even when the notification tag is reused.'
    - 'iOS push notifications continue to display correctly (added options are ignored by iOS; payload parsing and declarative/legacy logic unchanged).'
    - 'pwa typecheck and build both succeed, emitting dist/sw.js.'
    - 'Re-enable instructions name a browser-agnostic Chromium browser (Chrome or Edge), not Chrome only.'
  artifacts:
    - path: 'apps/pwa/src/sw.ts'
      provides: 'Push handler showNotification call enriched with icon, badge, renotify, vibrate'
      contains: 'renotify: true'
    - path: 'apps/pwa/src/components/InstructionSheet.tsx'
      provides: 'Browser-agnostic ANDROID_STEPS first step'
      contains: 'Chrome or Edge'
  key_links:
    - from: 'apps/pwa/src/sw.ts push handler'
      to: 'self.registration.showNotification options object'
      via: 'NotificationOptions with renotify+vibrate+icon+badge'
      pattern: "showNotification\\(title"
---

<objective>
Fix silent Android push notifications. The SW push handler calls `showNotification(title, { body, tag, data })` with minimal options; on Android Chromium (Chrome and Edge) a reused `tag` without `renotify:true` causes the existing notification to be UPDATED silently (no heads-up, sound, or lock-screen alert). Add `icon`, `badge`, `renotify`, and `vibrate` to restore audible/visible alerting. Separately, generalize the Android re-enable copy from Chrome-only to browser-agnostic (Chrome or Edge).

Purpose: Android UAT confirmed delivery (FCM 201) but silent presentation; this is the user-facing fix. iOS is unaffected and must stay working.
Output: Edited `apps/pwa/src/sw.ts` and `apps/pwa/src/components/InstructionSheet.tsx`, with passing typecheck + build.
</objective>

<execution_context>
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
</execution_context>

<context>
@./CLAUDE.md
@apps/pwa/src/sw.ts
@apps/pwa/src/components/InstructionSheet.tsx
</context>

<tasks>

<task type="auto">
  <name>Task 1: Enrich showNotification options to fix silent Android push</name>
  <files>apps/pwa/src/sw.ts</files>
  <action>
In the `push` event handler, modify ONLY the `self.registration.showNotification(title, {...})` options object (the call wrapped in `event.waitUntil(...)`, currently `{ body, tag, data: { url } }`). Add four keys alongside the existing ones, keeping `body`, `tag`, and `data: { url }` exactly as they are:
- `icon: '/icon-192.png'` (asset exists at apps/pwa/public/icon-192.png)
- `badge: '/icon-192.png'` (asset exists; Android masks it to a monochrome status-bar glyph)
- `renotify: true` (re-alert even when the tag is reused — the key fix for silent updates; requires a non-empty tag, which is always set here: default 'familysync-notification' or payload-derived)
- `vibrate: [200, 100, 200]` (ensures vibration/heads-up on Android)

Do NOT set `silent` (it must stay falsy/absent). Do NOT touch the payload parsing, the iOS-18.4+ declarative-vs-legacy branch, the try/catch fallback, `event.waitUntil`, or the `notificationclick` handler. iOS ignores these added options, so its path is unchanged.

TS caveat: `renotify` and `vibrate` are valid NotificationOptions members but some `lib.dom` TS versions omit/deprecate them on `ServiceWorkerRegistration.showNotification`. If typecheck errors on these keys, resolve it MINIMALLY: declare a `const options: NotificationOptions = { ... }` and pass it (with a narrow `as NotificationOptions` cast only if still required). Do NOT broaden suppression or silence unrelated errors. Build success is the load-bearing gate.
</action>
<verify>
<automated>pnpm --filter @familysync/pwa typecheck && pnpm --filter @familysync/pwa build && test -f apps/pwa/dist/sw.js && grep -q "renotify" apps/pwa/dist/sw.js</automated>
</verify>
<done>showNotification options include icon, badge, renotify:true, and vibrate; body/tag/data unchanged; `silent` absent; typecheck passes; vite build succeeds and emits apps/pwa/dist/sw.js containing the renotify option.</done>
</task>

<task type="auto">
  <name>Task 2: Make Android re-enable instructions browser-agnostic</name>
  <files>apps/pwa/src/components/InstructionSheet.tsx</files>
  <action>
In the `ANDROID_STEPS` array, change the first step from `'Open Chrome on your phone'` to a browser-agnostic equivalent that names both Chromium browsers, e.g. `'Open your browser (Chrome or Edge) on your phone'`. Leave the remaining three Android steps unchanged (three-dot menu → Settings → Site Settings → Notifications → Allow — identical across Chromium browsers). Leave `IOS_STEPS` and all component markup/styles untouched. This is a copy-only change; the existing InstructionSheet test does not assert on this wording and must stay green.
  </action>
  <verify>
    <automated>grep -q "Chrome or Edge" apps/pwa/src/components/InstructionSheet.tsx && pnpm --filter @familysync/pwa test</automated>
  </verify>
  <done>ANDROID_STEPS first step names both Chrome and Edge; remaining Android steps and IOS_STEPS unchanged; pwa vitest suite (including InstructionSheet.test.tsx) passes.</done>
</task>

</tasks>

<verification>
- `pnpm --filter @familysync/pwa typecheck` passes (no new TS errors).
- `pnpm --filter @familysync/pwa build` (tsc && vite build) succeeds and emits `apps/pwa/dist/sw.js`.
- Built `dist/sw.js` contains the `renotify` option (regression guard that the SW edit landed in the bundle).
- `pnpm --filter @familysync/pwa test` passes (InstructionSheet test stays green; no sw.ts unit test exists).
- Manual/device confirmation (out of plan scope, for UAT): Android push now produces a heads-up + sound/vibration alert; iOS push still displays correctly.
</verification>

<success_criteria>

- sw.ts showNotification call includes icon, badge, renotify:true, vibrate; body/tag/data and all parsing/waitUntil logic unchanged; no `silent` key.
- InstructionSheet ANDROID_STEPS is browser-agnostic (Chrome or Edge).
- typecheck + build + test all pass; dist/sw.js emitted with the fix.
  </success_criteria>

<output>
Create `.planning/quick/260610-ka9-fix-silent-android-push-notifications-en/260610-ka9-SUMMARY.md` when done.
</output>
