Milestone v1.0: FamilySync MVP #1
@@ -108,7 +108,7 @@ Plans:
|
||||
|
||||
- [ ] 03-04-PLAN.md — TDD: outbox worker state machine (D-05/06/07/08 retry/backoff/dead/conflict, edit-as-move ordering) + index.ts wiring
|
||||
- [ ] 03-05-PLAN.md — Frontend create/edit slice: write client calls + Zustand keys + EventForm (D-01/02/11) + New Event FAB
|
||||
- [ ] 03-07-PLAN.md — PWA install: VitePWA manifest + auth-safe SW denylist + icons + InstallPrompt (iOS walkthrough + Android prompt) (PWA-01/02)
|
||||
- [x] 03-07-PLAN.md — PWA install: VitePWA manifest + auth-safe SW denylist + icons + InstallPrompt (iOS walkthrough + Android prompt) (PWA-01/02)
|
||||
|
||||
**Wave 4** *(blocked on Wave 3)*
|
||||
|
||||
@@ -161,7 +161,7 @@ Note: Phase 4 depends only on Phase 1 and can begin as soon as Phase 1 is comple
|
||||
|-------|----------------|--------|-----------|
|
||||
| 1. Foundation + Broker Spike | 4/4 | Complete | 2026-06-04 |
|
||||
| 2. Calendar Display | 5/5 | Complete | 2026-06-05 |
|
||||
| 3. Event Write-Back + PWA Install | 3/8 | In Progress| |
|
||||
| 3. Event Write-Back + PWA Install | 4/8 | In Progress| |
|
||||
| 4. Shared Lists + Live Sync | 0/? | Not started | - |
|
||||
| 5. Web Push Notifications | 0/? | Not started | - |
|
||||
|
||||
@@ -171,7 +171,7 @@ Note: Phase 4 depends only on Phase 1 and can begin as soon as Phase 1 is comple
|
||||
|
||||
**Goal:** [Captured for future planning] Abstract the calendar backend behind a provider interface so Fastmail/CalDAV is one implementation among potentially many. Shipping with a single provider is fine, but the broker, sync, and event-expansion layers should be structured so additional providers (e.g. other CalDAV hosts, Google Calendar, generic ICS feeds) can be added without rework. Captures the "provider" seam as an explicit architectural concern.
|
||||
**Requirements:** TBD
|
||||
**Plans:** 3/8 plans executed
|
||||
**Plans:** 4/8 plans executed
|
||||
|
||||
Plans:
|
||||
|
||||
|
||||
+9
-6
@@ -3,14 +3,14 @@ gsd_state_version: 1.0
|
||||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: executing
|
||||
stopped_at: Completed 03-03-PLAN.md
|
||||
last_updated: "2026-06-05T21:59:40.013Z"
|
||||
stopped_at: Completed 03-07-PLAN.md
|
||||
last_updated: "2026-06-05T22:07:48.985Z"
|
||||
last_activity: 2026-06-05
|
||||
progress:
|
||||
total_phases: 6
|
||||
completed_phases: 2
|
||||
total_plans: 17
|
||||
completed_plans: 12
|
||||
completed_plans: 13
|
||||
percent: 33
|
||||
---
|
||||
|
||||
@@ -26,7 +26,7 @@ See: .planning/PROJECT.md (updated 2026-06-03)
|
||||
## Current Position
|
||||
|
||||
Phase: 03 (event-write-back-pwa-install) — EXECUTING
|
||||
Plan: 4 of 8
|
||||
Plan: 5 of 8
|
||||
Status: Ready to execute
|
||||
Last activity: 2026-06-05
|
||||
|
||||
@@ -55,6 +55,7 @@ Progress: [███████░░░] 65%
|
||||
| Phase 03 P01 | 45 | 5 tasks | 8 files |
|
||||
| Phase 03 P03-02 | 4 | 2 tasks | 2 files |
|
||||
| Phase 03 P03-03 | 7 | 3 tasks | 2 files |
|
||||
| Phase 03 P03-07 | 5 | 2 tasks | 7 files |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
@@ -75,6 +76,8 @@ Recent decisions affecting current work:
|
||||
- [Phase ?]: resolveUserId helper uses any type to avoid Hono context generic complexity — internal helper only
|
||||
- [Phase ?]: Write endpoints enqueue-only (calendarOutbox) with 202 optimistic-accept; no Fastmail call in routes (D-12/D-05)
|
||||
- [Phase ?]: GET /writable-calendars is authoritative D-03 writable-set endpoint: WHERE userId=currentUser.id OR isShared=1
|
||||
- [Phase ?]: D-03-07-icons: PWA icons generated via pure Node.js (zlib/Buffer) when ImageMagick/sharp unavailable; placeholder art; operator can replace in public/ without code changes
|
||||
- [Phase ?]: D-03-07-layout: InstallPrompt desktop layout wraps CalendarContent in flex-column div so banner appears above calendar grid
|
||||
|
||||
### Pending Todos
|
||||
|
||||
@@ -103,6 +106,6 @@ Recent decisions affecting current work:
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-06-05T21:59:40.006Z
|
||||
Stopped at: Completed 03-03-PLAN.md
|
||||
Last session: 2026-06-05T22:07:48.979Z
|
||||
Stopped at: Completed 03-07-PLAN.md
|
||||
Resume file: None
|
||||
|
||||
@@ -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