Files
familysync/.planning/phases/06-ux-polish/06-VALIDATION.md
T

98 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 6
slug: ux-polish
status: draft
nyquist_compliant: false
wave_0_complete: false
created: 2026-06-10
---
# Phase 6 — Validation Strategy
> Per-phase validation contract for feedback sampling during execution.
> Source: `06-RESEARCH.md` § Validation Architecture. Planner fills the Per-Task Verification Map below as plans are written.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework** | vitest (both `apps/pwa` and `apps/api`) |
| **Config file** | `apps/pwa/vitest.config.ts`, `apps/api/vitest.config.ts` |
| **Quick run command** | `pnpm --filter @familysync/pwa test` (PWA-side fixes) · `pnpm --filter @familysync/api test` (recurrence write/expand) |
| **Full suite command** | `pnpm -r test` |
| **Browser verification** | `playwright-cli` skill (desktop Chromium) for visual/behavioral items — per CLAUDE.md convention |
| **Estimated runtime** | ~3060 seconds (unit); browser checks additive |
---
## Sampling Rate
- **After every task commit:** Run the quick run command for the touched app
- **After every plan wave:** Run `pnpm -r test`
- **Before `/gsd-verify-work`:** Full suite green + playwright-cli checks pass
- **Max feedback latency:** 60 seconds (unit)
---
## Per-Task Verification Map
> Planner: populate one row per task as plans are authored. Test-type guidance from RESEARCH § Validation Architecture:
> - **unit** — duration-preservation math (`eventDateTime.ts`), RRULE UNTIL/COUNT serialization (`vevent.ts`), inclusive↔exclusive DTEND round-trip, FREQ-persistence regression (D-07), opaqueredirect/401 detection (`client.ts`), `hasRrule` population in `expandOccurrences()`.
> - **integration** — PWA→API→expand recurrence round-trip (bounded series renders correct occurrence count, per-occurrence duration = start→end delta).
> - **browser (playwright-cli, desktop Chromium)** — all-day visual distinctness (999.6), spinner/pulse actually animating (D-13), no calendar/"sign-in required" flash on cold load (999.2), clean session-expiry redirect (999.3), whole-series edit prompt (999.9).
| Task ID | Plan | Wave | Decision | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|----------|------------|-----------------|-----------|-------------------|-------------|--------|
| 06-01-01 | 06-01 | 1 | D-04 | T-06-01 | N/A | unit | `cd apps/pwa && pnpm test -- run lib/eventDateTime` | ✅ | ⬜ pending |
| 06-01-02 | 06-01 | 1 | D-04 | T-06-01 | N/A | unit | `cd apps/pwa && pnpm test -- run lib/eventDateTime` | ✅ | ⬜ pending |
| 06-02-01 | 06-02 | 1 | D-06, D-07 | T-06-02 | RRULE UNTIL/COUNT value-type-matched; no raw passthrough to ICS | unit | `cd apps/api && pnpm test -- run broker/vevent broker/outboxWorker` | ✅ | ⬜ pending |
| 06-02-02 | 06-02 | 1 | D-06, D-07 | T-06-02 / T-06-02b | Zod max-10 until + int≥1 count at route boundary; ical.js re-parse rejects malformed RRULE | unit | `cd apps/api && pnpm test -- run broker/vevent broker/outboxWorker` | ✅ | ⬜ pending |
| 06-03-01 | 06-03 | 1 | D-08, D-06 | T-06-03 | N/A | unit | `cd apps/api && pnpm test -- run broker/expand` | ✅ | ⬜ pending |
| 06-03-02 | 06-03 | 1 | D-08, D-06 | T-06-03 | hasRrule derived only from already-access-scoped data | unit | `cd apps/api && pnpm test -- run broker/expand` | ✅ | ⬜ pending |
| 06-04-01 | 06-04 | 1 | D-13 | T-06-04 | N/A | unit | `grep -v '^#' apps/pwa/src/styles/tokens.css \| grep -c '@keyframes pulse' \| grep -qx 1 && cd apps/pwa && pnpm test -- run` | ✅ | ⬜ pending |
| 06-04-02 | 06-04 | 1 | D-13 | T-06-04 | N/A | browser | playwright-cli (desktop Chromium) | ✅ | ⬜ pending |
| 06-05-01 | 06-05 | 1 | D-11, D-06, D-08 | T-06-05-session | SessionExpiredError from 401/opaqueredirect only; never trusts response body; no token stored client-side | unit | `cd apps/pwa && pnpm test -- run api/client` | ✅ | ⬜ pending |
| 06-05-02 | 06-05 | 1 | D-10 | T-06-05-info | Render gated on meQuery.isSuccess; no app data painted while auth unknown | integration | `cd apps/pwa && pnpm test -- run components/CalendarShell` | ✅ | ⬜ pending |
| 06-05-03 | 06-05 | 1 | D-11 | T-06-05-redirect | Fixed internal /api/login target (no returnTo); one-shot guard bounds re-auth attempts | integration | `cd apps/pwa && pnpm test -- run` | ✅ | ⬜ pending |
| 06-05-04 | 06-05 | 1 | D-10, D-11 | T-06-05-info / T-06-05-redirect | No pre-auth flash; clean mid-use 401 redirect within ~2s | browser | playwright-cli (desktop Chromium) | ✅ | ⬜ pending |
| 06-06-01 | 06-06 | 2 | D-04, D-06, D-07, D-05 | T-06-06-input | Client-side count≥1 / until≥start validation (UX layer; server Zod is enforcement) | unit | `cd apps/pwa && pnpm test -- run components/EventForm` | ✅ | ⬜ pending |
| 06-06-02 | 06-06 | 2 | D-08, D-09 | T-06-06-series | Whole-series PUT reuses existing per-user ownership/etag scope; prompt adds no privilege | integration | `cd apps/pwa && pnpm test -- run` | ✅ | ⬜ pending |
| 06-06-03 | 06-06 | 2 | D-12 | T-06-06-xss | All-day label is plain-text JSX; CSS override is presentation-only, no injection surface | unit | `grep -v '^#' apps/pwa/src/styles/index.css \| grep -c 'sx__all-day-event' \| grep -qx 1 && cd apps/pwa && pnpm test -- run` | ✅ | ⬜ pending |
| 06-06-04 | 06-06 | 2 | D-04, D-06, D-08, D-12 | T-06-06-input / T-06-06-series | End-tracking floor; bounded recurrence; series-edit gated; all-day distinct | browser | playwright-cli (desktop Chromium) | ✅ | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
---
## Wave 0 Requirements
- [ ] Confirm existing vitest infra covers new test files (no framework install needed — vitest already configured in both apps)
- [ ] Test stubs for: duration-preservation, RRULE UNTIL/COUNT, FREQ round-trip, session-error detection
*Existing infrastructure (vitest) covers all phase test types; Wave 0 is stub creation only.*
---
## Manual-Only Verifications
| Behavior | Decision | Why Manual | Test Instructions |
|----------|----------|------------|-------------------|
| iOS-Safari standalone behavior (only if a fix regresses install/standalone) | D-10/D-11 | Cannot be driven by playwright-cli (per CLAUDE.md exception) | Add to Home Screen, cold-load, confirm splash + redirect on real iOS device |
*All other phase behaviors have automated verification (vitest) or desktop-Chromium browser verification (playwright-cli).*
---
## Validation Sign-Off
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags (`vitest run`, not `vitest --watch`)
- [ ] Feedback latency < 60s
- [ ] `nyquist_compliant: true` set in frontmatter
**Approval:** pending