7.3 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260611-tfc | 01 | execute | 1 |
|
true |
|
|
Purpose: Guarantee D-14 deep-link behaviour holds on every notification tap, including the window-closed and navigation-rejected edge cases — important on iOS where push reliability is load-bearing.
Output: Edited apps/pwa/src/sw.ts notificationclick handler; single atomic commit.
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>
@.planning/STATE.md @apps/pwa/src/sw.ts @.planning/phases/13-real-lint-gate-eslint/13-REVIEW.md Task 1: Make the openWindow fallback reachable in the notificationclick handler apps/pwa/src/sw.ts First Read `apps/pwa/src/sw.ts` to confirm the current notificationclick handler text (the `event.waitUntil(self.clients.matchAll(...).then((clientList) => { for (const client of clientList) { if ('focus' in client) { return client.focus().then(() => client.navigate(url)); } } ... })` block, around lines 167-182). Match the real variable names and the existing `'focus' in client` guard exactly — do not rename anything.Apply the WR-01 fix per `.planning/phases/13-real-lint-gate-eslint/13-REVIEW.md`. Inside the
`for (const client of clientList)` loop, replace the focusable-client `return` so the
chain (a) checks the `navigate()` result and opens a new window when it resolves `null`,
and (b) carries a `.catch()` that opens a new window when `focus()` or `navigate()` rejects.
Both new-window branches must be guarded by `if (self.clients.openWindow)` and pass the same
`url`. The chain stays a single `return`ed promise so it is correctly returned (not floating)
inside the `.then((clientList) => ...)` callback. The reviewer's exact replacement is the
canonical shape (`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); })`).
Preserve ALL other behaviour: the `event.notification.close()` call, the `url` extraction +
its `typeof` guard and existing `eslint-disable-next-line` comments (lines ~160-165), the
`matchAll({ type: 'window', includeUncontrolled: true })` options, and the final
`if (self.clients.openWindow) { return self.clients.openWindow(url); }` no-focusable-client
fallback after the loop. Touch only the focusable-client `return` expression.
Scope discipline: do NOT address IN-01 (eslint.config.js spike.ts) or IN-02 (redundant
`as string` cast) from the same review — out of scope for WR-01. Do NOT run a repo-wide
Prettier pass. If the edited lines need formatting, run
`pnpm exec prettier --write apps/pwa/src/sw.ts` on that single file only and confirm the
diff is limited to the handler.
The new code must satisfy the Phase 13 type-aware ESLint gate: the returned promise chain is
correctly returned (no `@typescript-eslint/no-floating-promises`), and no
`@typescript-eslint/no-misused-promises` is introduced. `client.focus()`, `client.navigate()`,
and `self.clients.openWindow()` are all typed by the WebWorker/ServiceWorker lib already
referenced via `/// <reference lib="webworker" />` and `declare const self:
ServiceWorkerGlobalScope;` at the top of the file — no new casts or eslint-disable comments
should be required for the fix.
<success_criteria>
The notificationclick openWindow fallback is reachable on both failure modes (focus rejection
and null navigate), the deep-link happy path is preserved, all four gate commands pass, and the
change lands as a single atomic commit on branch gsd/phase-13-real-lint-gate-eslint touching
only apps/pwa/src/sw.ts.
</success_criteria>