6.6 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-calendar-display | 04 | pwa-calendar-shell |
|
|
|
|
|
|
Phase 02 Plan 04: CalendarShell — Schedule-X Mounted, Wired to Data Pipeline Summary
Schedule-X CalendarShell component wired to TanStack Query windowed fetch, hydrateEvents Temporal conversion, and Zustand range management; all four views available; CAL-03 render smoke test with Temporal hydration guards.
What Was Built
Task 1: CalendarShell + App.tsx
apps/pwa/src/components/CalendarShell.tsx (186 lines):
useCalendarApp(config, [eventsService, eventModal])with all four view factories:createViewDay,createViewWeek,createViewMonthGrid,createViewMonthAgendadefaultViewfrom Zustand persisted view (D-05 defaults: phone→month-agenda, tablet-desktop→month-grid already encoded in store)firstDayOfWeek: SX_FIRST_DAY_OF_WEEK(7 = Sunday, Temporal convention) — Pitfall 1 guardcalendarsconfig frombuildCalendarConfig(members)keyed byString(userId)and'shared'config.callbacks.onRangeUpdateconvertsDateRange.start/end(Temporal.ZonedDateTime) to'YYYY-MM-DD'strings for Zustand via.toPlainDate().toString(), triggering TanStack Query refetcheventsService.set(hydrateEvents(...))inuseEffectkeyed oneventsQuery.data— Pitfall 4 guard- Token-only styling (
var(--color-*),var(--space-*),var(--font-family-base)) — no hardcoded hex/px - Sign-in required error state; slim loading indicator bar
apps/pwa/src/App.tsx: Replaced EventProof landing + health probe + MemberBadge with <CalendarShell /> single render.
Critical API finding (Deviation 1): onRangeUpdate and onEventClick are NOT top-level fields on CalendarConfigExternal. They live under config.callbacks (CalendarCallbacks type). The research pattern sketched them at the top level — the actual type required nesting.
Task 2: CalendarShell Render Smoke Test (CAL-03)
apps/pwa/src/components/CalendarShell.test.tsx (6 tests):
- Render-without-throw smoke (validates
@schedule-x/react@4.1.0↔@schedule-x/calendar@4.6.0import compatibility — Pitfall 6) ScheduleXCalendarmounts with non-nullcalendarApphydrateEventscalled with both timed + all-day occurrences;eventsService.set()called with hydrated events- All-day occurrence →
Temporal.PlainDate(Pitfall 4/all-day date shift guard) - Timed occurrence →
Temporal.ZonedDateTime(Pitfall 4 guard) - Error state test for
/api/merejection
apps/pwa/src/test-setup.ts: window.matchMedia polyfill. Zustand's create() runs at module load time and calls window.matchMedia to derive the D-05 default view. This must be defined before any module importing calendarStore.ts is loaded — a Vitest setupFiles entry is the only reliable placement.
apps/pwa/vitest.config.ts: Added setupFiles: ['./src/test-setup.ts'].
Verification Results
Test Files 4 passed (4)
Tests 24 passed (24)
tsc --noEmit: clean (0 errors)
vite build: clean (474.27 kB, built in 395ms)
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] CalendarCallbacks nested under config.callbacks — not top-level
- Found during: Task 1 — tsc reported
onRangeUpdatenot inCalendarConfigExternal - Issue: Research pattern (RESEARCH.md Pattern 4) showed
onRangeUpdateat the top level of the config object. The actual type (CalendarConfigExternal extends Partial<ReducedCalendarConfigInternal>) carriescallbacks?: CalendarCallbackswhereCalendarCallbackscontainsonRangeUpdateandonEventClick. They must be nested underconfig.callbacks. - Fix: Moved
onRangeUpdateandonEventClickintocallbacks: { ... }in theuseCalendarAppconfig - Files modified:
apps/pwa/src/components/CalendarShell.tsx - Commit:
b79f649
2. [Rule 3 - Blocking] window.matchMedia not defined in jsdom
- Found during: Task 2 — test run crashed at Zustand store initialisation
- Issue:
calendarStore.tscallswindow.matchMediainsidereadPersistedView()which runs atcreate()time — i.e. at module load, before any test-file-level polyfill runs. InlineObject.definePropertyin the test file is too late. - Fix: Created
src/test-setup.tswith the polyfill; addedsetupFiles: ['./src/test-setup.ts']tovitest.config.ts - Files modified:
apps/pwa/src/test-setup.ts(new),apps/pwa/vitest.config.ts - Commit:
f0af43c
Known Stubs
None — CalendarShell fetches real windowed data from /api/events, hydrates to Temporal, and renders via Schedule-X. The Plan 05 popover slot (setOpenEventId in onEventClick) is wired but the popover UI itself is Plan 05.
Threat Flags
No new threat surface beyond the plan's threat model.
- T-02d-01 (XSS): CalendarShell uses React JSX default escaping for all event field rendering — no
dangerouslySetInnerHTML. Carried to Plan 05 popover.
Self-Check: PASSED
Files created:
- apps/pwa/src/components/CalendarShell.tsx
- apps/pwa/src/components/CalendarShell.test.tsx
- apps/pwa/src/test-setup.ts
Commits: