180 lines
8.1 KiB
Markdown
180 lines
8.1 KiB
Markdown
---
|
|
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
|