9.2 KiB
phase, slug, status, nyquist_compliant, wave_0_complete, created
| phase | slug | status | nyquist_compliant | wave_0_complete | created |
|---|---|---|---|---|---|
| 02 | calendar-display | ready | true | false | 2026-06-04 |
Phase 02 — Validation Strategy
Per-phase validation contract for feedback sampling during execution.
Test Infrastructure
| Property | Value |
|---|---|
| Framework | vitest |
| Config (API) | apps/api/vitest.config.ts (environment: node — exists, Phase 1) |
| Config (PWA) | apps/pwa/vitest.config.ts (environment: jsdom — created in Plan 01 Task 1) |
| Quick run command (API) | cd apps/api && pnpm test -- <test-file> |
| Quick run command (PWA) | cd apps/pwa && pnpm test -- <test-file> |
| Full suite command | pnpm -r test (runs both workspaces) |
| Type gate | pnpm exec tsc --noEmit per workspace |
| Estimated runtime | ~25 seconds full suite (no live network; broker mocked) |
PWA harness (vitest + jsdom + @testing-library/react + @testing-library/jest-dom) is installed in Plan 01 Task 1 — until that task completes, all PWA test rows are blocked on the harness (❌ W0).
Sampling Rate
- After every task commit: Run the task's quick run command (the
<automated>in that task) - After every plan wave: Run
pnpm -r test - Before
/gsd-verify-work:pnpm -r testgreen +tsc --noEmitclean in both workspaces - Max feedback latency: 30 seconds
Per-Task Verification Map
| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---|---|---|---|---|---|---|---|---|---|
| 02-01-01 | 01 | 1 | CAL-07 | T-02-SC | schema + RED stubs + fixtures (no behavior yet) | unit/grep | grep -q has_rrule + ICAL.parse fixtures (3 grep/node checks) |
✅ creates RED stubs | ⬜ pending |
| 02-01-02 | 01 | 1 | CAL-02 | T-02-01 | dev-bypass hard-disabled when NODE_ENV=production | unit | cd apps/api && pnpm test -- tests/auth/devBypass.test.ts |
❌ W0 (this task creates it) | ⬜ pending |
| 02-01-03 | 01 | 1 | CAL-07 | T-02-02 | live MariaDB has has_rrule + is_shared (no false-green) | integration | node SHOW COLUMNS calendar_events/calendars |
N/A (DB assertion) | ⬜ pending |
| 02-02-01 | 02 | 2 | CAL-07 | T-02b-04 | DST wall-clock preserved; all-day no shift; EXDATE excluded | unit | cd apps/api && pnpm test -- tests/broker/expand.test.ts |
✅ RED stub from 02-01 | ⬜ pending |
| 02-02-02 | 02 | 2 | CAL-02, CAL-07 | T-02b-01/02 | start/end zod-validated before SQL; 90-day cap; color+isShared join | unit | cd apps/api && pnpm test -- tests/routes/events.test.ts |
✅ RED stub from 02-01 | ⬜ pending |
| 02-02-03 | 02 | 2 | CAL-02 | — | operator marks shared calendar (is_shared=1) | manual | human-verify checkpoint (see Manual-Only) | N/A | ⬜ pending |
| 02-03-01 | 03 | 2 | CAL-02 | T-02c-SC | token layer + Schedule-X var overrides; Temporal-first import | grep/node | grep --color-shared-family + node require deps |
N/A (style/deps) | ⬜ pending |
| 02-03-02 | 03 | 2 | CAL-02 | — | firstDayOfWeek 0→7; per-member + 'shared' config | unit | cd apps/pwa && pnpm test -- src/lib/colorUtils.test.ts src/lib/calendarConfig.test.ts |
✅ RED stub (calendarConfig) from 02-01 | ⬜ pending |
| 02-03-03 | 03 | 2 | CAL-07 | T-02c-02 | all-day→PlainDate guard; calendarId routed by isShared/ownerUserId | unit | cd apps/pwa && pnpm test -- src/lib/hydrateEvents.test.ts |
✅ RED stub from 02-01 | ⬜ pending |
| 02-04-01 | 04 | 3 | CAL-02, CAL-03 | T-02d-01 | Schedule-X renders real windowed occurrences; 4 views; token-only | grep/type | grep ScheduleXCalendar/hydrateEvents + tsc --noEmit |
N/A (wired in 04) | ⬜ pending |
| 02-04-02 | 04 | 3 | CAL-03 | T-02d-01 | render smoke: 4 views + timed+all-day through hydrate→eventsService | unit | cd apps/pwa && pnpm test -- src/components/CalendarShell.test.tsx |
❌ W0 (this task creates it) | ⬜ pending |
| 02-05-01 | 05 | 4 | CAL-03 | T-02e-01 | popover renders fields as text (no dangerouslySetInnerHTML); Escape closes | unit | cd apps/pwa && pnpm test -- src/components/EventDetailPopover.test.tsx |
❌ W0 (this task creates it) | ⬜ pending |
| 02-05-02 | 05 | 4 | CAL-02, CAL-03 | T-02e-02 | legend/nav/toolbar + skeleton/empty/error; EventProof removed | grep/type | grep SkeletonCalendar/EmptyState + tsc --noEmit |
N/A (wired in 05) | ⬜ pending |
| 02-05-03 | 05 | 4 | CAL-02, CAL-03, CAL-07 | — | visual + functional verification of 4 success criteria | manual | human-verify checkpoint (see Manual-Only) | N/A | ⬜ pending |
Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky
Tasks without a unit-test <automated> (02-01-01 grep/fixture, 02-01-03 DB, 02-03-01 grep/deps, 02-04-01 grep/type, 02-05-02 grep/type) each carry an automated grep/node/tsc check, and none of them appear in 3-consecutive sequence without a unit test between them: the expand/events/colorUtils/hydrateEvents/popover/smoke unit tests interleave every wave.
Wave 0 Requirements
Wave 0 = Plan 01 Task 1, which creates the failing-but-present test stubs and fixtures the later waves turn green:
apps/api/tests/broker/expand.test.ts— RED stub forexpandOccurrences(CAL-07). Contract: weeklyAmerica/New_YorkRRULE across the March 2026 DST boundary keeps 10:00 local wall-clock on both sides; all-day birthday →allDay:true+'YYYY-MM-DD'start; EXDATE-excluded occurrence absent. Fails at import (../../src/broker/expand.jsnot yet built).apps/api/tests/routes/events.test.ts— RED stub for windowed/api/events(CAL-02). Contract: occurrences carrycolor+isShared; bad/oversized params → 400.apps/pwa/src/lib/hydrateEvents.test.ts— RED stub (CAL-07). Contract: all-day→Temporal.PlainDate; timed→Temporal.ZonedDateTime; Schedule-XcalendarId=occ.isShared ? 'shared' : String(occ.ownerUserId)(shared occurrence →'shared'; personal occurrence →String(ownerUserId)).apps/pwa/src/lib/calendarConfig.test.ts— RED stub (CAL-02). Contract:WEEK_START_DAY=0→ Schedule-XfirstDayOfWeek=7.apps/api/tests/fixtures/{weekly-dst,allday-birthday,exdate-series}.ics— fixture corpus;weekly-dst.icscarries a full VTIMEZONE (STANDARD + DAYLIGHT) for the DST assertion.apps/api/tests/auth/devBypass.test.ts— created and made green within Plan 01 Task 2 (not a cross-wave RED stub).apps/pwa/vitest.config.ts+ PWA test deps (vitest, @testing-library/react, @testing-library/jest-dom, jsdom) — installed in Plan 01 Task 1; without this the PWA RED stubs cannot run.apps/pwa/src/components/CalendarShell.test.tsxandapps/pwa/src/components/EventDetailPopover.test.tsx— created in-wave by Plan 04/05 (not Wave 0 stubs; depend on components built in the same plan).
Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|---|---|---|---|
Designate the shared-family calendar (calendars.is_shared=1) |
CAL-02 | Which calendar is "shared-family" cannot be derived deterministically from data (broker exposes "Calendar" + "USA Holidays"; members' calendars arrive under their own credential) — operator must designate it. A different identification rule (e.g. displayName-pattern matching) is a plan revision, not a resume-from-checkpoint. | Plan 02 Task 3: list calendars, UPDATE calendars SET is_shared=1 WHERE id=<chosen>, re-list, curl /api/events confirms isShared:true + color #F25C7A on marked rows. |
| DST boundary correctness in the rendered UI | CAL-07 | Automated expand.test.ts asserts the wall-clock contract, but visual confirmation that Schedule-X paints the occurrence at the right hour across March 2026 requires a human eye on the grid. | Plan 05 Task 3 step 4: find a recurring event, navigate across the March 2026 DST boundary, confirm time does not jump ±1 hour. |
| All-day event renders as a full-day banner with no date shift | CAL-07 | PlainDate guard is unit-tested, but the actual Schedule-X all-day banner placement (correct date, no off-by-one) is a render-path visual check. | Plan 05 Task 3 step 5: find an all-day event (birthday/holiday), confirm it appears as a full-day banner on the correct date, not a day early/late. |
| Color-coded ownership legible at a glance + legend decode | CAL-02 | "Reads at a glance" is a subjective slick-constraint judgment. | Plan 05 Task 3 steps 2–3: confirm each member's events render in their color, shared in rose, legend decodes ownership. |
| Phone bottom-sheet popover + agenda default view | CAL-03 | Responsive breakpoint behavior (D-05) needs a real phone-width render. | Plan 05 Task 3 step 7: resize to phone width, confirm default view is Agenda and popover is a bottom sheet. |
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 (expand, events, hydrateEvents, calendarConfig stubs + PWA harness)
- No watch-mode flags (
vitest run/pnpm test -- <file>, never--watch) - Feedback latency < 30s
nyquist_compliant: trueset in frontmatter
Approval: approved 2026-06-04