docs(03-07): complete PWA install plan — manifest, InstallPrompt, icons
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
---
|
||||
phase: 03-event-write-back-pwa-install
|
||||
plan: 07
|
||||
subsystem: pwa, frontend
|
||||
tags: [vite-plugin-pwa, service-worker, install-prompt, ios, android, workbox]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 03-event-write-back-pwa-install
|
||||
plan: 01
|
||||
provides: vite-plugin-pwa installed in apps/pwa
|
||||
|
||||
provides:
|
||||
- VitePWA manifest + service worker with auth-safe navigateFallbackDenylist (T-03-20)
|
||||
- PWA icon assets (192x192, 512x512, 180x180 apple-touch-icon)
|
||||
- iOS head meta entries for A2HS install
|
||||
- InstallPrompt component: iOS walkthrough banner/sheet + Android beforeinstallprompt banner
|
||||
|
||||
affects: [03-08]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- VitePWA navigateFallbackDenylist to exclude /callback, /api/, /health from SW interception
|
||||
- runtimeCaching: [] — no API response caching (T-03-21)
|
||||
- isIOSSafariNonStandalone() — iOS UA + navigator.standalone detection
|
||||
- useAndroidInstallPrompt() — captures beforeinstallprompt, deferred prompt pattern
|
||||
- localStorage.installPromptDismissed — persist banner dismissal cross-session
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- apps/pwa/src/components/InstallPrompt.tsx
|
||||
- apps/pwa/public/icon-192.png
|
||||
- apps/pwa/public/icon-512.png
|
||||
- apps/pwa/public/apple-touch-icon.png
|
||||
modified:
|
||||
- apps/pwa/vite.config.ts
|
||||
- apps/pwa/index.html
|
||||
- apps/pwa/src/components/CalendarShell.tsx
|
||||
|
||||
key-decisions:
|
||||
- "Icons generated programmatically via pure Node.js (zlib/Buffer) — ImageMagick and sharp not available in the environment; minimal valid PNGs at exact pixel dimensions are functionally equivalent for PWA install purposes. Real branded icons can be dropped into public/ without any code change."
|
||||
- "Desktop InstallPrompt placement: wrapped CalendarContent in a flex-column div on desktop to allow InstallPrompt to appear as a top bar above the calendar grid without disrupting the row sidebar layout."
|
||||
|
||||
# Metrics
|
||||
duration: ~5min
|
||||
completed: 2026-06-05
|
||||
---
|
||||
|
||||
# Phase 03 Plan 07: PWA Install — VitePWA Manifest + InstallPrompt Summary
|
||||
|
||||
**VitePWA manifest + auth-safe service worker + iOS icons/meta + InstallPrompt (iOS guided walkthrough + Android beforeinstallprompt) wired into CalendarShell**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** ~5 min
|
||||
- **Started:** 2026-06-05T22:01Z
|
||||
- **Completed:** 2026-06-05T22:06Z
|
||||
- **Tasks:** 2
|
||||
- **Files modified:** 7
|
||||
|
||||
## Accomplishments
|
||||
|
||||
### Task 1: VitePWA manifest + service worker + iOS head/icons
|
||||
|
||||
- Added `VitePWA` plugin to `apps/pwa/vite.config.ts` with `registerType: 'autoUpdate'`
|
||||
- Configured `workbox.navigateFallbackDenylist: [/^\/callback/, /^\/api\//, /^\/health/]` — OIDC /callback is never SW-intercepted (T-03-20 Gate 2 prerequisite)
|
||||
- `runtimeCaching: []` — no authenticated API responses cached (T-03-21)
|
||||
- Manifest: name/short_name FamilySync, description, theme_color #4A90D9, background_color #ffffff, display:standalone, scope:/, start_url:/, 3 icons (192, 512, 512-maskable)
|
||||
- Existing `server.proxy` block preserved verbatim (including `/callback` proxy to localhost:3000)
|
||||
- Generated three PNG icon files via pure Node.js (zlib/Buffer): `icon-192.png` (192×192), `icon-512.png` (512×512), `apple-touch-icon.png` (180×180) — solid #4A90D9 background with white "F" glyph
|
||||
- Added five iOS `<head>` entries to `index.html`: apple-touch-icon link, theme-color meta (was already present, supplemented), apple-mobile-web-app-capable, apple-mobile-web-app-status-bar-style, apple-mobile-web-app-title
|
||||
- Production build verified: `dist/manifest.webmanifest` emitted with display:standalone, scope:/, 3 icons; `dist/sw.js` and `dist/workbox-*.js` emitted
|
||||
|
||||
### Task 2: InstallPrompt (TDD GREEN — RED scaffold from Plan 01)
|
||||
|
||||
- Implemented `isIOSSafariNonStandalone()`: UA regex for iPad/iPhone/iPod + `navigator.standalone !== true`
|
||||
- Implemented `useAndroidInstallPrompt()`: captures `beforeinstallprompt`, resets on `appinstalled`; returns `{ canInstall, triggerInstall }`
|
||||
- `InstallPrompt` renders nothing when `display-mode: standalone` or `navigator.standalone` (already installed)
|
||||
- iOS branch: dismissible banner (Smartphone icon, "Install FamilySync" heading, body + "How to install" link) gated by `localStorage.installPromptDismissed`; "How to install" opens `WalkthroughSheet` — full-screen bottom sheet with 5 annotated steps per UI-SPEC copy; orange (#F5A623) step number circles
|
||||
- Android branch: banner with "Install" CTA shown only when `canInstall === true`; triggers native prompt, then dismisses
|
||||
- `role="banner"`, `aria-label="Dismiss install prompt"`, 44px touch targets throughout
|
||||
- Mounted `<InstallPrompt />` in `CalendarShell` for both phone and tablet/desktop layouts
|
||||
- `InstallPrompt.test.tsx` GREEN: 5 tests (iOS UA detection, standalone false, Android UA false, canInstall=true on beforeinstallprompt, canInstall=false on appinstalled)
|
||||
- Full PWA suite: 44 tests across 6 files — all green; `tsc --noEmit` clean
|
||||
|
||||
## Task Commits
|
||||
|
||||
1. **Task 1: VitePWA manifest + auth-safe SW denylist + iOS head/icons** — `bd82837`
|
||||
2. **Task 2: InstallPrompt — iOS walkthrough banner + Android beforeinstallprompt** — `e0fb34b`
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `apps/pwa/vite.config.ts` — added VitePWA plugin with manifest, workbox config, auth-safe denylist
|
||||
- `apps/pwa/index.html` — added 4 iOS head entries (theme-color was already present)
|
||||
- `apps/pwa/public/icon-192.png` — 192×192 PNG icon (solid #4A90D9 + white "F")
|
||||
- `apps/pwa/public/icon-512.png` — 512×512 PNG icon (solid #4A90D9 + white "F")
|
||||
- `apps/pwa/public/apple-touch-icon.png` — 180×180 PNG icon (solid #4A90D9 + white "F")
|
||||
- `apps/pwa/src/components/InstallPrompt.tsx` — iOS walkthrough + Android install prompt component (476 lines)
|
||||
- `apps/pwa/src/components/CalendarShell.tsx` — import + mount InstallPrompt
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- **Icon generation via pure Node.js:** ImageMagick was not available in the environment and `sharp` is not an installed project dependency. Generated minimal valid PNGs programmatically using Node.js `zlib.deflateSync` + PNG chunk encoding. Icons are structurally correct at exact pixel dimensions and pass `file` dimension checks. Placeholder visuals (solid #4A90D9 background with white "F") are sufficient for PWA installability; the operator can drop in final branded icons at any time without code changes.
|
||||
- **Desktop layout wrapper:** On tablet/desktop, wrapped `<CalendarContent />` in a new `flex-column` div so that `<InstallPrompt />` can appear as a top bar above the calendar grid without disrupting the outer `flex-row` sidebar/content layout.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written. Icon generation method (pure Node.js vs ImageMagick/sharp) was anticipated by the plan's fallback note and is not a deviation.
|
||||
|
||||
## TDD Gate Compliance
|
||||
|
||||
- Task 2 had `tdd="true"` with a pre-existing RED scaffold (Plan 03-01 Task 4).
|
||||
- RED gate: `InstallPrompt.test.tsx` confirmed failing before implementation (module not found error).
|
||||
- GREEN gate: commit `e0fb34b` implements the component; all 5 test behaviors pass.
|
||||
- No separate RED commit needed (RED scaffold existed from Plan 01, committed as `bbfccda`).
|
||||
|
||||
## Known Stubs
|
||||
|
||||
- **Icon visuals:** `icon-192.png`, `icon-512.png`, `apple-touch-icon.png` use a solid #4A90D9 fill with a simple white "F" glyph. These are functional for PWA installability (manifest validation, iOS A2HS icon display) but are placeholder art. Final branded icons can replace these files in `apps/pwa/public/` without any code change.
|
||||
- **iOS walkthrough screenshots:** The walkthrough sheet renders 5 annotated step-text items. Actual iOS screenshots with annotation overlays (referenced by the plan as "annotated screenshot images may be placeholder assets") are not included — the component structure, copy, and annotation color (#F5A623) are complete; real screenshots can be added as `<img>` elements within the steps in a future pass.
|
||||
|
||||
## Threat Surface Scan
|
||||
|
||||
No new security-relevant surface introduced beyond what was in the threat model:
|
||||
- T-03-20 (SW intercepts /callback): **mitigated** — `navigateFallbackDenylist` confirmed in vite.config.ts
|
||||
- T-03-21 (SW caches API responses): **mitigated** — `runtimeCaching: []`
|
||||
- T-03-22 (icons/manifest leak secrets): **accepted** — static public assets only
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- `apps/pwa/vite.config.ts` — exists, contains `navigateFallbackDenylist`
|
||||
- `apps/pwa/index.html` — exists, contains `apple-touch-icon`
|
||||
- `apps/pwa/public/icon-192.png` — 192×192 PNG verified
|
||||
- `apps/pwa/public/icon-512.png` — 512×512 PNG verified
|
||||
- `apps/pwa/public/apple-touch-icon.png` — 180×180 PNG verified
|
||||
- `apps/pwa/src/components/InstallPrompt.tsx` — exists, 476 lines
|
||||
- `apps/pwa/src/components/CalendarShell.tsx` — contains InstallPrompt import + mount
|
||||
- `dist/manifest.webmanifest` — display:standalone, scope:/, 3 icons verified
|
||||
- Commit `bd82837` — verified in git log
|
||||
- Commit `e0fb34b` — verified in git log
|
||||
|
||||
---
|
||||
*Phase: 03-event-write-back-pwa-install*
|
||||
*Completed: 2026-06-05*
|
||||
Reference in New Issue
Block a user