Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
11 KiB
11 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-260610-jlp | 01 | execute | 1 |
|
true |
|
|
Purpose: A browser-blocked user cannot re-enable notifications from inside the app (correct), so the ONLY recovery is the OS-specific step-by-step guidance. That guidance was unreachable from Settings — the documented recovery path was dead.
Output: New shared InstructionSheet.tsx; PermissionDeniedBanner refactored to import it (behaviour identical); SettingsSheet wired to open it; a focused test proving the wiring.
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>
@CLAUDE.md @apps/pwa/src/components/PermissionDeniedBanner.tsx @apps/pwa/src/components/SettingsSheet.tsx @apps/pwa/src/components/InstallPrompt.test.tsx Task 1: Extract InstructionSheet into a shared component and rewire PermissionDeniedBanner apps/pwa/src/components/InstructionSheet.tsx, apps/pwa/src/components/PermissionDeniedBanner.tsx Create `apps/pwa/src/components/InstructionSheet.tsx`. Move VERBATIM from PermissionDeniedBanner.tsx (lines ~25-202): the `isIOS()` function, the `IOS_STEPS` and `ANDROID_STEPS` constants, the `InstructionSheetProps` interface, and the `InstructionSheet` component. Keep markup, inline styles, CSS-var design tokens, role="dialog"/aria-modal/aria-label, the X close button, the numbered steps, and the Done button BYTE-FOR-BYTE identical — this is a behaviour-preserving extraction, NOT a redesign (UI-SPEC §Surface; T-05-24: copy stays plain-text JSX children, no dangerouslySetInnerHTML). Change `function InstructionSheet` to `export function InstructionSheet` (named export). Carry the `import { X } from 'lucide-react'` into the new file (only X is needed there). `isIOS()` MUST move with the component since it selects the step list + platform label.Edit `apps/pwa/src/components/PermissionDeniedBanner.tsx`: delete the now-moved `isIOS()`, `IOS_STEPS`, `ANDROID_STEPS`, `InstructionSheetProps`, and local `InstructionSheet` (the "OS detection", "Instruction steps", and "Instruction sheet" sections). Add `import { InstructionSheet } from './InstructionSheet.js'` (match the existing `.js`-specifier convention used by `usePushSubscription.js`). The remaining import from `lucide-react` keeps `AlertCircle`; drop `X` from that import ONLY if no longer used in this file (grep confirms — X was only used inside the moved InstructionSheet). Leave the `PermissionDeniedBanner` function body unchanged — it already references `InstructionSheet` and `setInstructionsOpen`, now satisfied by the import. Do NOT touch the banner's render/visibility logic.
Create `apps/pwa/src/components/InstructionSheet.test.tsx` following the existing harness style (vitest + @testing-library/react + `.js` import specifiers, see InstallPrompt.test.tsx). Write the test FIRST and watch it fail against the un-wired SettingsSheet (RED), then make it pass (GREEN). Mock the `usePushSubscription` hook (`vi.mock('../hooks/usePushSubscription.js', ...)`) to return `{ permission: 'denied', isSubscribed: false, subscribe: vi.fn(), setEnabled: vi.fn() }` so the permission-denied hint renders. Test: render `<SettingsSheet isOpen={true} onClose={onCloseSpy} />`, assert no `role="dialog"` named "How to enable notifications" exists yet, click the "How to enable" button (`screen.getByText('How to enable')`), then assert (a) a dialog with the "How to enable notifications" heading is now visible and (b) `onCloseSpy` was NOT called. Keep it pragmatic — if jsdom lacks `Notification`, define a minimal `globalThis.Notification = { permission: 'denied' }` stub in the test before render. Do not over-engineer; one wiring assertion is sufficient.
<success_criteria>
- A browser-blocked user opening Settings and tapping "How to enable" sees the OS-specific instruction dialog (the original UAT-05-T4 failure is fixed).
- PermissionDeniedBanner behaves identically to before (behaviour-preserving extraction).
- InstructionSheet markup/copy/styles unchanged; no new dependencies; no dangerouslySetInnerHTML. </success_criteria>