Files
familysync/.planning/milestones/v1.0-phases/02-calendar-display/02-03-SUMMARY.md
T

8.1 KiB


phase: 02-calendar-display plan: "03" subsystem: pwa-foundation tags: [token-layer, schedule-x, temporal, color-utils, calendar-config, hydrate-events, zustand, fetch-client] dependency_graph: requires: ["02-01"] provides: [css-token-layer, sx-color-overrides, colorUtils, calendarConfig, hydrateEvents, calendarStore, windowed-fetchEvents, temporal-polyfill-global] affects: ["02-04", "02-05"] tech_stack: added: - "@schedule-x/calendar@4.6.0" - "@schedule-x/react@4.1.0" - "@schedule-x/theme-default@4.6.0" - "@schedule-x/event-modal@4.6.0" - "@schedule-x/events-service@4.6.0" - "temporal-polyfill@0.3.2" - "lucide-react@1.17.0" patterns: - CSS custom property token layer (clean theme, D-01/D-02) - Schedule-X --sx-color-* override via cascade (imported after theme-default in main.tsx) - Temporal polyfill-first import order in main.tsx - Alpha-blend-over-white for event chip container colors - WEEK_START_DAY=0 (JS) to SX_FIRST_DAY_OF_WEEK=7 (Temporal) translation - hydrateEvents calendarId routing: isShared ? 'shared' : String(ownerUserId) - Zustand UI store with localStorage view persistence per breakpoint group - Windowed fetchEvents(start,end) with credentials:include key_files: created: - apps/pwa/src/styles/tokens.css - apps/pwa/src/styles/tokens.ts - apps/pwa/src/styles/index.css - apps/pwa/src/lib/colorUtils.ts - apps/pwa/src/lib/colorUtils.test.ts - apps/pwa/src/lib/calendarConfig.ts - apps/pwa/src/lib/hydrateEvents.ts - apps/pwa/src/store/calendarStore.ts modified: - apps/pwa/package.json - apps/pwa/src/main.tsx - apps/pwa/src/api/client.ts - apps/pwa/src/lib/hydrateEvents.test.ts - apps/pwa/src/lib/calendarConfig.test.ts - apps/pwa/src/components/EventProof.tsx - pnpm-lock.yaml decisions:

  • "calendarId routing uses isShared/ownerUserId (never String(calendarId)) to match buildCalendarConfig keys"
  • "SX_FIRST_DAY_OF_WEEK = WEEK_START_DAY === 0 ? 7 : WEEK_START_DAY encoded as exported constant"
  • "fetchEventsLegacy() preserves EventProof compilation until Plan 05 removes the component"
  • "calendarStore initializes calendarRange to today's month +-7-day buffer for initial TanStack Query fetch"
  • "tokens.css imported via index.css (not directly in main.tsx) to preserve correct cascade order" metrics: duration: "6m 56s" completed: "2026-06-05" tasks_completed: 3 files_created: 8 files_modified: 7

Phase 02 Plan 03: PWA Foundation — Token Layer, Color Utils, Calendar Config, Hydration, Store Summary

CSS custom-property token layer with Schedule-X overrides, hex-blend color utilities, firstDayOfWeek 0 to 7 translation, Temporal-based event hydration with all-day PlainDate guard and ownership-routed calendarId, Zustand UI store with localStorage view persistence, and windowed fetchEvents.

What Was Built

Task 1: Schedule-X Stack + Token Layer + main.tsx Import Order

Installed packages in apps/pwa:

  • @schedule-x/calendar@4.6.0, @schedule-x/react@4.1.0, @schedule-x/theme-default@4.6.0
  • @schedule-x/event-modal@4.6.0, @schedule-x/events-service@4.6.0
  • temporal-polyfill@0.3.2, lucide-react@1.17.0

apps/pwa/src/styles/tokens.css — CSS custom properties declaring:

  • Surface/border/text palette: --color-surface, --color-surface-dim, --color-surface-raised, --color-border, --color-border-subtle, --color-text-primary/secondary/muted, --color-focus-ring, --color-overlay
  • Calendar colors: --color-member-0..5 + --color-shared-family: #F25C7A + --color-destructive
  • Spacing scale: --space-1..12 (multiples of 4px)
  • Typography: --font-family-base, --text-body/label/heading/display-size/weight/line-height
  • Breakpoints: --bp-phone: 0px, --bp-tablet: 768px, --bp-desktop: 1280px
  • Schedule-X overrides: all --sx-color-* vars mapped to project tokens; --sx-font-family
  • @keyframes shimmer for SkeletonCalendar

apps/pwa/src/styles/tokens.ts — TypeScript mirror of all token values for inline-style props; as const typed.

apps/pwa/src/styles/index.css — imports tokens.css + minimal global reset.

apps/pwa/src/main.tsx — updated with load-bearing import order:

  1. import 'temporal-polyfill/global' (must be first)
  2. import '@schedule-x/theme-default/dist/index.css' (SX layout CSS)
  3. import './styles/index.css' (token overrides win cascade)

Task 2: colorUtils + calendarConfig — RED Stubs Turned GREEN

apps/pwa/src/lib/colorUtils.ts exports:

  • hexToContainer(hex) — alpha blends at 15% opacity over white
  • hexToOnContainer(hex) — darkens 40% (channel multiply by 0.6)
  • deriveScheduleXColors(main) returning { main, container, onContainer }

apps/pwa/src/lib/calendarConfig.ts exports:

  • WEEK_START_DAY = 0 (JS Sunday convention)
  • SX_FIRST_DAY_OF_WEEK = WEEK_START_DAY === 0 ? 7 : WEEK_START_DAY
  • buildCalendarConfig(members) returning { firstDayOfWeek: 7, calendars } with 'shared' (rose) + per-member entries keyed by String(userId)

calendarConfig.test.ts (Plan 01 RED stubs) — all 4 assertions now GREEN.

Task 3: hydrateEvents + calendarStore + windowed fetchEvents — RED Stubs Turned GREEN

apps/pwa/src/lib/hydrateEvents.ts:

  • allDay:true uses Temporal.PlainDate.from(occ.start) (guards all-day date shift)
  • allDay:false uses Temporal.ZonedDateTime.from(occ.start/end)
  • calendarId = occ.isShared ? 'shared' : String(occ.ownerUserId) — never String(occ.calendarId)
  • _familySync: { uid, color, isShared } carried for popover rendering

hydrateEvents.test.ts (Plan 01 RED stubs) — all 4 assertions now GREEN.

apps/pwa/src/store/calendarStore.ts Zustand store:

  • selectedView — from localStorage per breakpoint group; D-05 defaults
  • calendarRange — month ± 7-day buffer for initial TanStack Query key
  • openEventId, selectedDate — UI-only, not persisted

apps/pwa/src/api/client.ts:

  • Added CalendarOccurrence, OccurrencesResponse, fetchEvents(start, end)
  • Kept deprecated CalendarEvent, EventsResponse, fetchEventsLegacy() for EventProof.tsx (removed Plan 05)

Verification Results

Test Files  3 passed (3)
Tests       18 passed (18)

tsc --noEmit: clean (0 errors)

All Wave 1 RED stubs are GREEN:

  • calendarConfig.test.ts — 4/4 pass
  • hydrateEvents.test.ts — 4/4 pass
  • colorUtils.test.ts — 10/10 pass

Deviations from Plan

Auto-fixed Issues

1. [Rule 2 - Missing Critical Functionality] Added temporal-polyfill/global to hydrateEvents.test.ts

  • Found during: Task 3 test run
  • Issue: Plan 01 RED stub lacked import 'temporal-polyfill/global'; jsdom has no native Temporal
  • Fix: Added as first import in hydrateEvents.test.ts
  • Files modified: apps/pwa/src/lib/hydrateEvents.test.ts
  • Commit: f377d7c

2. [Rule 2 - Missing Critical Functionality] Added fetchEventsLegacy() to preserve EventProof

  • Found during: Task 3 — updating client.ts
  • Issue: EventProof.tsx called no-arg fetchEvents() and used CalendarEvent fields not on CalendarOccurrence
  • Fix: Added fetchEventsLegacy() (deprecated) + updated EventProof to use it; plan says it is replaced in Plan 05
  • Files modified: apps/pwa/src/api/client.ts, apps/pwa/src/components/EventProof.tsx
  • Commit: f377d7c

Known Stubs

None — all artifacts are fully wired. Plan 04 mounts Schedule-X and consumes these modules.

Threat Flags

No new threat surface beyond the plan's threat model. All packages pre-approved in RESEARCH.md with no postinstall scripts.

Self-Check: PASSED

Files created:

  • apps/pwa/src/styles/tokens.css
  • apps/pwa/src/styles/tokens.ts
  • apps/pwa/src/styles/index.css
  • apps/pwa/src/lib/colorUtils.ts
  • apps/pwa/src/lib/colorUtils.test.ts
  • apps/pwa/src/lib/calendarConfig.ts
  • apps/pwa/src/lib/hydrateEvents.ts
  • apps/pwa/src/store/calendarStore.ts

Commits:

  • 0911a23 — Task 1: Schedule-X stack + token layer + main.tsx
  • 43554f4 — Task 2: colorUtils + calendarConfig; calendarConfig stubs GREEN
  • f377d7c — Task 3: hydrateEvents + calendarStore + windowed fetchEvents; all stubs GREEN