docs(02-03): complete plan 03 summary — token layer, calendar config, hydration, store

This commit is contained in:
Lucas Berger
2026-06-05 09:49:39 -04:00
parent f377d7c3f8
commit 5435df6bbe
@@ -0,0 +1,179 @@
---
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:
- [x] apps/pwa/src/styles/tokens.css
- [x] apps/pwa/src/styles/tokens.ts
- [x] apps/pwa/src/styles/index.css
- [x] apps/pwa/src/lib/colorUtils.ts
- [x] apps/pwa/src/lib/colorUtils.test.ts
- [x] apps/pwa/src/lib/calendarConfig.ts
- [x] apps/pwa/src/lib/hydrateEvents.ts
- [x] apps/pwa/src/store/calendarStore.ts
Commits:
- [x] 0911a23 — Task 1: Schedule-X stack + token layer + main.tsx
- [x] 43554f4 — Task 2: colorUtils + calendarConfig; calendarConfig stubs GREEN
- [x] f377d7c — Task 3: hydrateEvents + calendarStore + windowed fetchEvents; all stubs GREEN