--- 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 `
` 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 `