8.8 KiB
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
VitePWAplugin toapps/pwa/vite.config.tswithregisterType: '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.proxyblock preserved verbatim (including/callbackproxy 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 toindex.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.webmanifestemitted with display:standalone, scope:/, 3 icons;dist/sw.jsanddist/workbox-*.jsemitted
Task 2: InstallPrompt (TDD GREEN — RED scaffold from Plan 01)
- Implemented
isIOSSafariNonStandalone(): UA regex for iPad/iPhone/iPod +navigator.standalone !== true - Implemented
useAndroidInstallPrompt(): capturesbeforeinstallprompt, resets onappinstalled; returns{ canInstall, triggerInstall } InstallPromptrenders nothing whendisplay-mode: standaloneornavigator.standalone(already installed)- iOS branch: dismissible banner (Smartphone icon, "Install FamilySync" heading, body + "How to install" link) gated by
localStorage.installPromptDismissed; "How to install" opensWalkthroughSheet— 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 />inCalendarShellfor both phone and tablet/desktop layouts InstallPrompt.test.tsxGREEN: 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 --noEmitclean
Task Commits
- Task 1: VitePWA manifest + auth-safe SW denylist + iOS head/icons —
bd82837 - 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 denylistapps/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
sharpis not an installed project dependency. Generated minimal valid PNGs programmatically using Node.jszlib.deflateSync+ PNG chunk encoding. Icons are structurally correct at exact pixel dimensions and passfiledimension 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 newflex-columndiv so that<InstallPrompt />can appear as a top bar above the calendar grid without disrupting the outerflex-rowsidebar/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.tsxconfirmed failing before implementation (module not found error). - GREEN gate: commit
e0fb34bimplements 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.pnguse 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 inapps/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 —
navigateFallbackDenylistconfirmed 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, containsnavigateFallbackDenylistapps/pwa/index.html— exists, containsapple-touch-iconapps/pwa/public/icon-192.png— 192×192 PNG verifiedapps/pwa/public/icon-512.png— 512×512 PNG verifiedapps/pwa/public/apple-touch-icon.png— 180×180 PNG verifiedapps/pwa/src/components/InstallPrompt.tsx— exists, 476 linesapps/pwa/src/components/CalendarShell.tsx— contains InstallPrompt import + mountdist/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