---
phase: quick-260611-tfc
plan: "01"
subsystem: pwa-sw
tags: [service-worker, push-notifications, bug-fix, wr-01]
dependency_graph:
  requires: []
  provides: [reachable-openWindow-fallback-notificationclick]
  affects: [apps/pwa/src/sw.ts]
tech_stack:
  added: []
  patterns: [promise-chain-catch-fallback]
key_files:
  created: []
  modified:
    - apps/pwa/src/sw.ts
decisions:
  - "Canonical fix shape: .then(navigated => openWindow if null) + .catch(() => openWindow) on the focus/navigate chain — keeps the returned promise intact for no-floating-promises"
metrics:
  duration: ~6 min
  completed: "2026-06-12"
---

# Quick Task 260611-tfc: Fix WR-01 sw.ts notificationclick openWindow Fallback

**One-liner:** notificationclick handler extended with `.then(navigated===null → openWindow)` + `.catch(→ openWindow)` so the new-window fallback is reachable on both focus rejection and null navigate.

## What Was Done

Fixed WR-01 from `.planning/phases/13-real-lint-gate-eslint/13-REVIEW.md`.

The focusable-client `return` inside the `for (const client of clientList)` loop was:

```ts
return client.focus().then(() => client.navigate(url));
```

Two failure modes made the `openWindow` fallback unreachable:
1. `navigate()` resolving `null` (browser rejected the navigation) — the chain resolved successfully, so the post-loop fallback was never reached.
2. `focus()` or `navigate()` rejecting (window closed between `matchAll` and `focus`) — the rejection propagated into `event.waitUntil`, marking the click as handled without opening a new window.

Applied the canonical replacement per the review:

```ts
return client
  .focus()
  .then(() => client.navigate(url))
  .then((navigated) => {
    if (navigated === null && self.clients.openWindow)
      return self.clients.openWindow(url);
  })
  .catch(() => {
    if (self.clients.openWindow) return self.clients.openWindow(url);
  });
```

All surrounding behaviour preserved: `event.notification.close()`, `url` extraction + eslint-disable comments, `matchAll` options, and the post-loop no-focusable-client fallback.

## Verification — Exit Codes

| Command | Exit |
|---------|------|
| `pnpm lint` | 0 |
| `pnpm typecheck` | 0 |
| `pnpm --filter @familysync/pwa test` | 0 (191/191 pass) |
| `pnpm --filter @familysync/pwa build` | 0 (dist/sw.js emitted) |

## Deviations from Plan

None — plan executed exactly as written.

## Self-Check: PASSED

- `apps/pwa/src/sw.ts` modified: confirmed
- Commit `af78ccc` exists: confirmed
- `git diff --diff-filter=D HEAD~1 HEAD`: no deletions
- Only `apps/pwa/src/sw.ts` changed (`git diff --stat` = 1 file, 10 insertions, 1 deletion)
