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.
8.1 KiB
8.1 KiB
phase, slug, status, nyquist_compliant, wave_0_complete, created
| phase | slug | status | nyquist_compliant | wave_0_complete | created |
|---|---|---|---|---|---|
| 3 | event-write-back-pwa-install | draft | true | false | 2026-06-05 |
Phase 3 — Validation Strategy
Per-phase validation contract for feedback sampling during execution. Sourced from 03-RESEARCH.md §Validation Architecture.
Test Infrastructure
| Property | Value |
|---|---|
| Framework (API) | Vitest 4.x, environment: node |
| Framework (PWA) | Vitest 4.x + jsdom + @testing-library/react |
| Config (API) | apps/api/vitest.config.ts |
| Config (PWA) | apps/pwa/vitest.config.ts |
| Quick run (API) | pnpm --filter @familysync/api test |
| Quick run (PWA) | pnpm --filter @familysync/pwa test |
| Full suite | pnpm test (from repo root — runs both apps) |
| Estimated runtime | ~20-40 seconds (mocked DB + CalDAV; no network) |
Sampling Rate
- After every task commit: Run the filtered quick command for the app touched
(
pnpm --filter @familysync/api test -- <path>orpnpm --filter @familysync/pwa test -- <name>). - After every plan wave: Run
pnpm test(full suite, both apps). - Before
/gsd-verify-work: Full suite must be green. - Max feedback latency: ~40 seconds (full suite).
Per-Task Verification Map
| Req ID | Behavior | Plan | Wave | Test Type | Automated Command | File Exists | Status |
|---|---|---|---|---|---|---|---|
| CAL-04 | buildVeventString → VCALENDAR for a timed event (DTSTART UTC) |
02 | 2 | unit | pnpm --filter @familysync/api test -- broker/vevent |
❌ W0 (03-01) | ⬜ pending |
| CAL-04 | buildVeventString → all-day event uses DATE not DATETIME (D-13) |
02 | 2 | unit | same | ❌ W0 (03-01) | ⬜ pending |
| CAL-04 | POST /api/events/create → 202 + inserts pending outbox row | 03 | 2 | unit (mocked DB) | pnpm --filter @familysync/api test -- routes/events |
❌ W0 (03-01) | ⬜ pending |
| CAL-05 | PATCH /api/events/:uid/edit → 202 + inserts row with etag | 03 | 2 | unit | same | ❌ W0 (03-01) | ⬜ pending |
| CAL-06 | DELETE /api/events/:uid → 202 + inserts delete row | 03 | 2 | unit | same | ❌ W0 (03-01) | ⬜ pending |
| CAL-07 | buildVeventString with rruleString → RRULE property |
02 | 2 | unit | pnpm --filter @familysync/api test -- broker/vevent |
❌ W0 (03-01) | ⬜ pending |
| CAL-04/05/06 | Outbox worker: pending→done (204), pending→failed (412), pending→backoff (500), pending→dead (max attempts) | 02/04 | 2 | unit | pnpm --filter @familysync/api test -- broker/outboxWorker |
❌ W0 (03-01) | ⬜ pending |
| CAL-04/05/06 | GET /api/events/sync-status returns outbox status (D-09) | 03 | 2 | unit | pnpm --filter @familysync/api test -- routes/events |
❌ W0 (03-01) | ⬜ pending |
| CAL-04/05/07 | GET /api/events/writable-calendars returns D-03 set; never another member's read-only personal (V4) | 03 | 2 | unit | pnpm --filter @familysync/api test -- routes/events |
❌ W0 (03-01) | ⬜ pending |
| D-08 | 412 → conflict (not retry), mark failed, trigger re-sync | 04 | 2 | unit | pnpm --filter @familysync/api test -- broker/outboxWorker |
❌ W0 (03-01) | ⬜ pending |
| D-04 | Edit-as-move emits DELETE+CREATE pair; create runs first | 04 | 2 | unit | same | ❌ W0 (03-01) | ⬜ pending |
| D-03/V4 | create rejects write to non-owned/non-shared calendar (403) | 03 | 2 | unit | pnpm --filter @familysync/api test -- routes/events |
❌ W0 (03-01) | ⬜ pending |
| PWA-01 | vite.config.ts produces valid manifest.webmanifest with required fields |
06 | 3 | smoke (build output) | pnpm --filter @familysync/pwa build + manifest field check |
❌ W0 (03-01) | ⬜ pending |
| PWA-02 | isIOSSafariNonStandalone() true on mock iOS Safari non-standalone UA |
06 | 3 | unit | pnpm --filter @familysync/pwa test -- InstallPrompt |
❌ W0 (03-01) | ⬜ pending |
| PWA-02 | useAndroidInstallPrompt sets canInstall=true on beforeinstallprompt |
06 | 3 | unit (mock event) | same | ❌ W0 (03-01) | ⬜ pending |
Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky
Wave 0 Requirements
The Wave 0 RED test scaffold is created in plan 03-01 Task 4 (these files import not-yet-existing modules so they fail RED until later waves implement them):
apps/api/tests/broker/vevent.test.ts— CAL-04, CAL-07 (VEVENT builder, DATE/DATETIME split, RRULE)apps/api/tests/broker/write.test.ts— tsdav call shapes, etag/If-Match, response interpretationapps/api/tests/broker/outboxWorker.test.ts— outbox state machine (done/failed/backoff/dead), edit-as-move ordering (D-04/D-07/D-08)apps/api/tests/routes/events.test.ts— EXTEND existing: POST /create, PATCH /edit, DELETE /:uid, GET /sync-status, GET /writable-calendars, 403 ownership (preserve existing GET /api/events block)apps/pwa/src/components/InstallPrompt.test.tsx— iOS detection, Androidbeforeinstallpromptcapture (PWA-02)
Existing test files (broker/sync, routes/events GET block, auth/devBypass) remain in place.
Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|---|---|---|---|
SW navigateFallbackDenylist excludes /callback |
PWA-01 | Requires a real production build + SW registration over HTTPS | Verify against prod build; confirm /callback not intercepted by SW |
| iOS standalone PWA login completes without leaving standalone | Gate 2 | Requires a physical iPhone, installed PWA, Authelia OIDC round-trip | Follow docs/deployment.md Gate 2 checklist (Plan 07) |
Validation Sign-Off
- All tasks have
<automated>verify or a Wave 0 RED dependency (created in 03-01 Task 4) - Sampling continuity: no 3 consecutive tasks without automated verify
- Wave 0 covers all MISSING references (five RED files in 03-01)
- No watch-mode flags
- Feedback latency < 40s
nyquist_compliant: trueset in frontmatter
Approval: approved 2026-06-05