docs(02): create phase plan

This commit is contained in:
Lucas Berger
2026-06-04 15:09:55 -04:00
parent b842955612
commit fc4cc2ccf8
7 changed files with 134 additions and 76 deletions
@@ -15,8 +15,8 @@ user_setup: []
must_haves:
truths:
- "Opening the app renders a Schedule-X calendar populated with REAL windowed Fastmail occurrences fetched from /api/events"
- "Events render in their owner's member color; shared-family events render in the reserved rose color"
- "The user can switch between day, week, month, and agenda views and events render in each"
- "Events render in their owner's member color; shared-family events render in the reserved rose color (D-06)"
- "The user can switch between day, week, month, and agenda views and events render in each (D-04)"
- "Recurring events show all in-window occurrences; all-day events appear as full-day banners on the correct date with no shift"
- "The visible window drives the TanStack Query key; navigating to a new window refetches"
- "Default view is agenda on phone and month on tablet/desktop (D-05)"
@@ -78,15 +78,15 @@ Output: CalendarShell.tsx wired to the full data pipeline; App.tsx renders it; a
<read_first>
- apps/pwa/src/App.tsx (current root: MemberBadge + EventProof + meQuery useQuery pattern lines 5892; this becomes the CalendarShell host)
- apps/pwa/src/api/client.ts (fetchEvents(start,end), fetchMe — from Plan 03)
- apps/pwa/src/lib/hydrateEvents.ts (hydrateEvents signature — Plan 03)
- apps/pwa/src/lib/calendarConfig.ts (buildCalendarConfig, SX_FIRST_DAY_OF_WEEK, view factories — Plan 03)
- apps/pwa/src/lib/hydrateEvents.ts (hydrateEvents signature + its calendarId routing: 'shared' | String(ownerUserId) — Plan 03)
- apps/pwa/src/lib/calendarConfig.ts (buildCalendarConfig keyed by String(userId) + 'shared', SX_FIRST_DAY_OF_WEEK, view factories — Plan 03)
- apps/pwa/src/store/calendarStore.ts (useCalendarStore: calendarRange, selectedView, setCalendarRange, setOpenEventId — Plan 03)
- .planning/phases/02-calendar-display/02-RESEARCH.md §"Pattern 4: TanStack Query + onRangeUpdate Wiring" + §"Pitfall 4" + §"A4 note" (do not depend on onRangeUpdate for first fetch)
- .planning/phases/02-calendar-display/02-UI-SPEC.md §"Component Inventory: CalendarShell" + §"View default logic (D-05)" + §"View Layout Specification"
- .planning/phases/02-calendar-display/02-PATTERNS.md §"apps/pwa/src/components/CalendarShell.tsx"
</read_first>
<action>
Create `apps/pwa/src/components/CalendarShell.tsx`. Read the current user via `useQuery(['me'], fetchMe)` to source member colors; build the Schedule-X calendars config with `buildCalendarConfig(members)` where members come from /api/me (current user) — for Phase 2 the per-calendar color comes from users.color via the API occurrences, so the calendars config keys must match the `calendarId` (String(userId) or 'shared') that hydrateEvents stamps on each event.
Create `apps/pwa/src/components/CalendarShell.tsx`. Read the current user via `useQuery(['me'], fetchMe)` to source member colors, then build the Schedule-X calendars config with `buildCalendarConfig(members)` where members come from /api/me (current user). CRITICAL — the calendars config keys are `String(userId)` for each member plus the reserved `'shared'` entry; these MUST match the `calendarId` that hydrateEvents stamps on each event, which is `occ.isShared ? 'shared' : String(occ.ownerUserId)` (Plan 03 routing fix). Do NOT key the config by the DB calendar-row id (`occ.calendarId`) — events from a member who owns multiple calendars would then render with no color.
Create the events service and event-modal plugins ONCE via `useState(() => createEventsServicePlugin())[0]` / `useState(() => createEventModalPlugin())[0]` (stable across renders). Build the app with `useCalendarApp({ views: [createViewDay(), createViewWeek(), createViewMonthGrid(), createViewMonthAgenda()], defaultView: <month-agenda on phone, month-grid on tablet/desktop per D-05>, firstDayOfWeek: SX_FIRST_DAY_OF_WEEK, calendars, plugins: [eventsService, eventModal], onRangeUpdate(range){ setCalendarRange({start, end}) } })`.
@@ -104,12 +104,13 @@ Output: CalendarShell.tsx wired to the full data pipeline; App.tsx renders it; a
<acceptance_criteria>
- CalendarShell.tsx mounts ScheduleXCalendar with all four view factories (day/week/month-grid/month-agenda)
- CalendarShell uses useQuery(['events', start, end]) → fetchEvents and calls eventsService.set(hydrateEvents(...)) in a data-keyed effect
- The calendars config is keyed by String(userId) + 'shared' (matching hydrateEvents' calendarId routing), NOT by the DB calendar-row id
- defaultView resolves to month-agenda on phone and month-grid on tablet/desktop (D-05)
- firstDayOfWeek passed as SX_FIRST_DAY_OF_WEEK (=7), not 0
- App.tsx renders CalendarShell as root; EventProof landing content removed from the render path
- No hard-coded hex/px in CalendarShell.tsx (token vars only); tsc --noEmit clean
</acceptance_criteria>
<done>Schedule-X renders real windowed Fastmail occurrences (color-coded, all four views switchable) as the app root; PWA typechecks.</done>
<done>Schedule-X renders real windowed Fastmail occurrences (color-coded via userId/shared-keyed config, all four views switchable) as the app root; PWA typechecks.</done>
</task>
<task type="auto" tdd="true">