Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
13 KiB
13 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-calendar-display | 04 | execute | 3 |
|
|
true |
|
|
Purpose: This is where CAL-02, CAL-03, and the CAL-07 display path become observable to the user. It consumes the Plan 02 endpoint and the Plan 03 token layer / config / hydration / store. Output: CalendarShell.tsx wired to the full data pipeline; App.tsx renders it; a render smoke test.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02-calendar-display/02-UI-SPEC.md @.planning/phases/02-calendar-display/02-RESEARCH.md @.planning/phases/02-calendar-display/02-PATTERNS.md @.planning/phases/02-calendar-display/02-03-SUMMARY.md @.planning/phases/02-calendar-display/02-02-SUMMARY.md Task 1: CalendarShell — Schedule-X mounted + wired to TanStack Query + hydrate + Zustand range apps/pwa/src/components/CalendarShell.tsx, apps/pwa/src/App.tsx - apps/pwa/src/App.tsx (current root: MemberBadge + EventProof + meQuery useQuery pattern lines 58–92; 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 + 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" 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}) } })`.
Fetch events with `useQuery({ queryKey: ['events', calendarRange.start, calendarRange.end], queryFn: () => fetchEvents(calendarRange.start, calendarRange.end), retry: 2, staleTime: 5*60*1000 })`. The initial calendarRange comes from the Zustand default (current month ± 1 week) — do NOT rely on onRangeUpdate firing on mount (A4 / Open Q2). In a `useEffect` keyed on `eventsQuery.data`, call `eventsService.set(hydrateEvents(eventsQuery.data.occurrences))` (Pitfall 4: must hydrate to Temporal before set). Wire event click to `setOpenEventId` (popover is built in Plan 05; here just store the id — keep the customComponents.eventModal slot reserved for Plan 05).
Render `<ScheduleXCalendar calendarApp={calendar} />` filling the available space. Use token-based styling only (className/var(--token)) — no hard-coded hex/px (Phase 2 rule). The AppNav/ViewToolbar/ColorLegend/popover chrome is Plan 05; CalendarShell here may render a minimal toolbar placeholder or rely on Schedule-X's built-in controls so the four views are switchable and verifiable now.
Update `apps/pwa/src/App.tsx`: replace the EventProof landing content with `<CalendarShell />` as the app root. Migrate any remaining hard-coded hex/px in App.tsx to tokens (Phase 2 rule). Leave the meQuery sign-in-required error branch intact for unauthenticated state.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| /api/events JSON → calendar render | server occurrences rendered into the DOM via React |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-02d-01 | Tampering (XSS) | event title/location/description in render | mitigate | React JSX default escaping; never dangerouslySetInnerHTML for event fields (carried into Plan 05 popover) |
| T-02d-02 | Information disclosure | events from another member's account | accept | API already enforces auth + per-credential scoping (Plan 02 / CAL-08-DECISION); client renders only what the authed endpoint returns |
</threat_model>
- `pnpm --filter @familysync/pwa test` green (CalendarShell smoke) - `tsc --noEmit` clean in apps/pwa - Manual (dev-auth bypass): app shows real color-coded events; all four views switch and render<success_criteria>
- Real Fastmail occurrences render color-coded across day/week/month/agenda (CAL-02, CAL-03)
- Recurring + all-day occurrences render correctly in-window (CAL-07 display)
- Window navigation refetches via TanStack Query </success_criteria>
<artifacts_produced>
Artifacts this phase produces (Plan 04)
CalendarShell(React component) — apps/pwa/src/components/CalendarShell.tsx- App.tsx now renders CalendarShell as root (EventProof landing removed from render path)
- CalendarShell.test.tsx (CAL-03 render smoke)
- Schedule-X eventsService + eventModal plugin instances + useCalendarApp config in CalendarShell </artifacts_produced>