style(13-03): apply Prettier formatting across repo

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.
This commit is contained in:
Lucas Berger
2026-06-11 20:35:18 -04:00
parent 4bc0445173
commit 982438dc10
398 changed files with 19050 additions and 16382 deletions
@@ -8,6 +8,7 @@ created: 2026-06-04
---
# Phase 2 — UI Design Contract
## Calendar Display
> Visual and interaction contract for Phase 2. Generated by gsd-ui-researcher.
@@ -17,13 +18,13 @@ created: 2026-06-04
## Design System
| Property | Value |
|----------|-------|
| Tool | none (shadcn not yet initialized) |
| Preset | not applicable |
| Component library | none — custom components against token layer |
| Icon library | lucide-react (lightweight, tree-shakeable, first-party React SVGs; consistent stroke style) |
| Font | system-ui stack: `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif` |
| Property | Value |
| ----------------- | ------------------------------------------------------------------------------------------- |
| Tool | none (shadcn not yet initialized) |
| Preset | not applicable |
| Component library | none — custom components against token layer |
| Icon library | lucide-react (lightweight, tree-shakeable, first-party React SVGs; consistent stroke style) |
| Font | system-ui stack: `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif` |
**Note:** No `components.json` exists in the PWA app. The Phase 1 shell uses inline styles.
Phase 2 introduces a CSS custom-property token layer (see § Token Layer below) as the primary
@@ -51,7 +52,7 @@ props; they use `var(--token-name)` in CSS Modules or `className` strings.
```ts
// apps/pwa/src/lib/calendarConfig.ts
export const WEEK_START_DAY = 0 // 0 = Sunday; flip to 1 = Monday with one edit
export const WEEK_START_DAY = 0; // 0 = Sunday; flip to 1 = Monday with one edit
```
Pass to Schedule-X's `firstDayOfWeek` option. Do not hardcode 0 anywhere else.
@@ -62,33 +63,33 @@ Pass to Schedule-X's `firstDayOfWeek` option. Do not hardcode 0 anywhere else.
### Base palette
| Token | Hex | Role |
|-------|-----|------|
| `--color-surface` | `#FFFFFF` | Page background, calendar grid cells |
| `--color-surface-dim` | `#F7F7F8` | Off-white wash: week/day off-hours bands, modal backdrop |
| `--color-surface-raised` | `#FFFFFF` | Cards, popovers (shadow provides elevation) |
| `--color-border` | `#E2E4E9` | Grid lines, dividers, input borders |
| `--color-border-subtle` | `#ECEEF2` | Secondary separators |
| `--color-text-primary` | `#111318` | Body text, event titles |
| `--color-text-secondary` | `#6B7280` | Meta text: times, locations, legend labels |
| `--color-text-muted` | `#9CA3AF` | Placeholder, empty-state body, disabled |
| `--color-focus-ring` | `#4A90D9` | Keyboard focus outline (3px, 2px offset) |
| `--color-overlay` | `rgba(0,0,0,0.32)` | Popover backdrop scrim |
| Token | Hex | Role |
| ------------------------ | ------------------ | -------------------------------------------------------- |
| `--color-surface` | `#FFFFFF` | Page background, calendar grid cells |
| `--color-surface-dim` | `#F7F7F8` | Off-white wash: week/day off-hours bands, modal backdrop |
| `--color-surface-raised` | `#FFFFFF` | Cards, popovers (shadow provides elevation) |
| `--color-border` | `#E2E4E9` | Grid lines, dividers, input borders |
| `--color-border-subtle` | `#ECEEF2` | Secondary separators |
| `--color-text-primary` | `#111318` | Body text, event titles |
| `--color-text-secondary` | `#6B7280` | Meta text: times, locations, legend labels |
| `--color-text-muted` | `#9CA3AF` | Placeholder, empty-state body, disabled |
| `--color-focus-ring` | `#4A90D9` | Keyboard focus outline (3px, 2px offset) |
| `--color-overlay` | `rgba(0,0,0,0.32)` | Popover backdrop scrim |
### Semantic calendar colors
These are the ONLY colors used for event fills. All are derived from member records
(`users.color`) or the reserved shared-family constant.
| Token | Hex | Assigned to | Source |
|-------|-----|-------------|--------|
| `--color-member-0` | `#4A90D9` | Lucas (member 1) | Phase-1 `users.color` |
| `--color-member-1` | `#50C878` | Wife (member 2) | Phase-1 `users.color` |
| `--color-member-2` | `#F5A623` | Slot 3 (future) | Phase-1 palette |
| `--color-member-3` | `#9B59B6` | Slot 4 (future) | Phase-1 palette |
| `--color-member-4` | `#E67E22` | Slot 5 (future) | Phase-1 palette |
| `--color-member-5` | `#1ABC9C` | Slot 6 (future) | Phase-1 palette |
| `--color-shared-family` | `#F25C7A` | Shared-family calendar (ALL members) | Confirmed by user |
| Token | Hex | Assigned to | Source |
| ----------------------- | --------- | ------------------------------------ | --------------------- |
| `--color-member-0` | `#4A90D9` | Lucas (member 1) | Phase-1 `users.color` |
| `--color-member-1` | `#50C878` | Wife (member 2) | Phase-1 `users.color` |
| `--color-member-2` | `#F5A623` | Slot 3 (future) | Phase-1 palette |
| `--color-member-3` | `#9B59B6` | Slot 4 (future) | Phase-1 palette |
| `--color-member-4` | `#E67E22` | Slot 5 (future) | Phase-1 palette |
| `--color-member-5` | `#1ABC9C` | Slot 6 (future) | Phase-1 palette |
| `--color-shared-family` | `#F25C7A` | Shared-family calendar (ALL members) | Confirmed by user |
**Implementation note:** The `calendars` configuration object passed to Schedule-X is built
dynamically at runtime by mapping `users.color` values to Schedule-X `lightColors.main`. The
@@ -99,29 +100,29 @@ The shared-family calendar always uses `#F25C7A` regardless of any user row.
For each member color `MAIN`, derive:
| Sub-token suffix | Derivation | Usage |
|------------------|------------|-------|
| `container` | `MAIN` at 15% opacity over white | Event chip background |
| `onContainer` | `MAIN` darkened 40% | Event chip text, passed to Schedule-X |
| Sub-token suffix | Derivation | Usage |
| ---------------- | -------------------------------- | ------------------------------------- |
| `container` | `MAIN` at 15% opacity over white | Event chip background |
| `onContainer` | `MAIN` darkened 40% | Event chip text, passed to Schedule-X |
These need not be pre-declared for every slot — compute them with a small utility function
(`colorTokens.ts`) at runtime using CSS Color Level 4 or a tiny LCH/hex math helper.
### 60 / 30 / 10 split
| Band | Tokens | Approximate coverage |
|------|--------|----------------------|
| 60% dominant (surface) | `--color-surface`, `--color-surface-dim` | Calendar grid, page background |
| Band | Tokens | Approximate coverage |
| ------------------------- | ------------------------------------------------------------------- | -------------------------------------- |
| 60% dominant (surface) | `--color-surface`, `--color-surface-dim` | Calendar grid, page background |
| 30% secondary (structure) | `--color-surface-raised`, `--color-border`, `--color-border-subtle` | Cards, nav bar, header, popover shells |
| 10% accent | `--color-shared-family` + per-member fills | Event chips only |
| 10% accent | `--color-shared-family` + per-member fills | Event chips only |
**Accent reserved for:** event chip fills and the color legend swatches. Accent colors MUST NOT
appear on buttons, nav items, headings, or any chrome element.
### Destructive
| Token | Hex | Usage |
|-------|-----|-------|
| Token | Hex | Usage |
| --------------------- | --------- | ------------------------------------------------------------------ |
| `--color-destructive` | `#DC2626` | Not used in Phase 2 (read-only). Token declared for Phase 3 reuse. |
---
@@ -130,17 +131,18 @@ appear on buttons, nav items, headings, or any chrome element.
All values are multiples of 4px. Use tokens; never write raw `px` values in components.
| Token | Value | CSS var | Usage |
|-------|-------|---------|-------|
| `space-1` | 4px | `--space-1` | Icon gap, badge dot, tight inline padding |
| `space-2` | 8px | `--space-2` | Event chip inner padding (vertical), color legend row gap |
| `space-3` | 12px | `--space-3` | Event chip inner padding (horizontal), compact cell padding |
| `space-4` | 16px | `--space-4` | Default element spacing, popover section gap |
| `space-6` | 24px | `--space-6` | Section padding, nav bar height rhythm |
| `space-8` | 32px | `--space-8` | Layout gaps, popover width gutter |
| `space-12` | 48px | `--space-12` | Major section breaks |
| Token | Value | CSS var | Usage |
| ---------- | ----- | ------------ | ----------------------------------------------------------- |
| `space-1` | 4px | `--space-1` | Icon gap, badge dot, tight inline padding |
| `space-2` | 8px | `--space-2` | Event chip inner padding (vertical), color legend row gap |
| `space-3` | 12px | `--space-3` | Event chip inner padding (horizontal), compact cell padding |
| `space-4` | 16px | `--space-4` | Default element spacing, popover section gap |
| `space-6` | 24px | `--space-6` | Section padding, nav bar height rhythm |
| `space-8` | 32px | `--space-8` | Layout gaps, popover width gutter |
| `space-12` | 48px | `--space-12` | Major section breaks |
**Exceptions:**
- Touch targets: minimum 44px height/width on interactive elements (iOS HIG). This is a layout
constraint, not a spacing token. Apply via `min-height: 44px`.
- Calendar header row height: 48px (`--space-12` used as a layout constant).
@@ -152,12 +154,12 @@ All values are multiples of 4px. Use tokens; never write raw `px` values in comp
Font family token: `--font-family-base: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`
| Role | Token | Size | Weight | Line Height | Usage |
|------|-------|------|--------|-------------|-------|
| Body | `--text-body` | 15px | 400 | 1.5 | Popover description, agenda location lines |
| Label | `--text-label` | 13px | 400 | 1.4 | Event chip text, time labels, legend labels, secondary meta |
| Heading | `--text-heading` | 18px | 600 | 1.25 | Popover title, view section headers (month name + year) |
| Display | `--text-display` | 24px | 600 | 1.2 | App name in nav bar (desktop), day number in day-view header |
| Role | Token | Size | Weight | Line Height | Usage |
| ------- | ---------------- | ---- | ------ | ----------- | ------------------------------------------------------------ |
| Body | `--text-body` | 15px | 400 | 1.5 | Popover description, agenda location lines |
| Label | `--text-label` | 13px | 400 | 1.4 | Event chip text, time labels, legend labels, secondary meta |
| Heading | `--text-heading` | 18px | 600 | 1.25 | Popover title, view section headers (month name + year) |
| Display | `--text-display` | 24px | 600 | 1.2 | App name in nav bar (desktop), day number in day-view header |
**Weights declared:** 400 (regular) and 600 (semibold). No other weights permitted.
@@ -169,17 +171,19 @@ Font family token: `--font-family-base: system-ui, -apple-system, BlinkMacSystem
These are the only three breakpoints. Reference them by name in code, never by raw px value.
| Name | Token | Min width | Default view | Notes |
|------|-------|-----------|--------------|-------|
| `phone` | `--bp-phone` | 0px | Agenda | Stacked single-column layout |
| `tablet` | `--bp-tablet` | 768px | Month | Two-column possible; nav becomes persistent sidebar |
| `desktop` | `--bp-desktop` | 1280px | Month | Full grid width |
| Name | Token | Min width | Default view | Notes |
| --------- | -------------- | --------- | ------------ | --------------------------------------------------- |
| `phone` | `--bp-phone` | 0px | Agenda | Stacked single-column layout |
| `tablet` | `--bp-tablet` | 768px | Month | Two-column possible; nav becomes persistent sidebar |
| `desktop` | `--bp-desktop` | 1280px | Month | Full grid width |
**View default logic (D-05):**
```ts
const isMobile = window.matchMedia('(max-width: 767px)').matches
const defaultView = isMobile ? 'month-agenda' : 'month-grid'
const isMobile = window.matchMedia('(max-width: 767px)').matches;
const defaultView = isMobile ? 'month-agenda' : 'month-grid';
```
Last-used view is persisted in Zustand and localStorage, keyed by breakpoint group
(`'phone' | 'tablet-desktop'`).
@@ -190,12 +194,13 @@ Last-used view is persisted in Zustand and localStorage, keyed by breakpoint gro
**Selected: Schedule-X** (`@schedule-x/react` + `@schedule-x/calendar`)
**Rationale:**
- Supports all four required views natively: `createViewDay`, `createViewWeek`,
`createViewMonthGrid`, `createViewMonthAgenda` (agenda).
- Theming via CSS custom properties — its `--sx-color-*` vars are overridden by mapping to
this spec's token values in `tokens.css`. No Schedule-X default stylesheet bleeds through.
- Per-calendar color is first-class (`calendars` config with `lightColors.main / container /
onContainer`) — maps directly to per-member `users.color` and the shared-family rose.
onContainer`) — maps directly to per-member `users.color` and the shared-family rose.
- `onRangeUpdate` callback fires when the user navigates, enabling TanStack Query to fetch
only the visible window from `/api/events?start=&end=`.
- Custom `eventModal` component via `customComponents` prop — the read-only detail popover
@@ -204,6 +209,7 @@ Last-used view is persisted in Zustand and localStorage, keyed by breakpoint gro
- Active maintenance; Temporal-polyfill based (aligns with modern date handling).
**Rejected alternatives:**
- `react-big-calendar`: opinionated CSS (hard to token-ify without !important fights);
unmaintained `moment` / `date-fns` localization coupling; weak agenda view.
- `FullCalendar`: commercial license for premium features; React package adds ~140 KB gzip.
@@ -220,17 +226,21 @@ Result: Schedule-X internal layout engine works; all colors come from this spec'
## Component Inventory
### CalendarShell
Top-level layout wrapper.
- `<AppNav>` (top bar on phone; left sidebar 240px on tablet/desktop)
- `<ViewToolbar>` (Today button, prev/next arrows, date label, view switcher)
- `<ColorLegend>` (member → color; always visible on tablet/desktop; collapsible on phone)
- `<ScheduleXCalendar>` (fills remaining space)
### AppNav
- Phone: top bar, 48px height, app name left, user avatar/color swatch right
- Tablet/Desktop: left sidebar, 240px width; app name + color legend + (future) nav items
### ViewToolbar
- Buttons: Today | < | > | [Day] [Week] [Month] [Agenda]
- Font: 13px label weight
- Active view button: `--color-member-0` (Lucas, current user) background at 12% opacity,
@@ -238,12 +248,14 @@ Top-level layout wrapper.
- Touch targets: 44px minimum height
### ColorLegend
- One row per member: color swatch (12px circle) + display name
- Shared-family row: rose swatch + "Family" label
- Font: 13px label weight, `--color-text-secondary`
- Always rendered; never interactive in Phase 2 (show/hide filter deferred)
### EventChip (month grid)
- Rounded pill, 4px radius
- Background: member `container` color (15% opacity)
- Text: member `onContainer` color, 13px, weight 400, single line, truncated with ellipsis
@@ -251,18 +263,21 @@ Top-level layout wrapper.
- Minimum height: 20px; minimum tap target area: 44px via transparent padding
### EventBlock (week / day view)
- Rectangular block, 4px radius
- Same fill/border as EventChip
- Displays: title (13px, weight 600) + start time (13px, weight 400) stacked
- Overflow clips; no ellipsis in short blocks (too short = just color)
### AgendaRow
- Date group header: heading weight (18px/600), `--color-text-primary`
- Event row: time (13px, muted) | title (15px, primary) | location (13px, secondary, italic)
- Left 4px border strip: member color
- Tap target: full row, min 44px height
### EventDetailPopover (read-only in Phase 2; reused as edit surface in Phase 3)
- Modal-style overlay on phone (full bottom sheet, slides up)
- Popover anchored to event on tablet/desktop (max-width 360px, 8px radius, shadow)
- Sections:
@@ -275,6 +290,7 @@ Top-level layout wrapper.
- Phase 3 note: add edit/delete actions in the footer area (reserved but empty in Phase 2)
### SkeletonCalendar
- Month skeleton: 6×7 grid of rounded rect placeholders, animated shimmer
(`background: linear-gradient(90deg, --color-surface-dim, --color-border-subtle, --color-surface-dim)`)
- Agenda skeleton: 4 date-group blocks, 23 rows each, varying widths (6090% of row)
@@ -282,6 +298,7 @@ Top-level layout wrapper.
- No spinner; shimmer only (matches Fantastical-style)
### EmptyState (no events in range)
- Centered in the calendar viewport
- Icon: lucide-react `CalendarDays` (32px, `--color-text-muted`)
- Heading + body copy (see § Copywriting)
@@ -292,6 +309,7 @@ Top-level layout wrapper.
## View Layout Specification
### Month view (default: tablet/desktop)
- 7-column grid, column headers: SunSat (3-letter, label weight)
- Day cells: 4px border, corner shows day number (13px label)
- Today's cell: `--color-surface-dim` background; day number has filled dot indicator
@@ -300,6 +318,7 @@ Top-level layout wrapper.
- Off-month days: day number in `--color-text-muted`; cells at 60% opacity
### Week view
- Time column 48px wide; columns for each day
- Current time indicator: 2px `--color-member-0` (current user's color) horizontal line
- All-day banner row at top, above time grid: full-width event blocks
@@ -307,10 +326,12 @@ Top-level layout wrapper.
- Event blocks overlap-handled by Schedule-X internals
### Day view
- Same layout as week, single day column (full width minus time column)
- Date in header: `--text-display` (24px/600)
### Agenda view (default: phone)
- Chronological list, grouped by date
- Infinite scroll or paginated by month (Schedule-X `createViewMonthAgenda`)
- Past events: not shown; starts at today
@@ -320,20 +341,20 @@ Top-level layout wrapper.
## Copywriting Contract
| Element | Copy |
|---------|------|
| Primary CTA (Phase 2) | None — read-only phase; no create action |
| Empty state heading | "Nothing here" |
| Empty state body | "No events in this period. Try a different date or switch views." |
| Loading state | (No text — skeleton shimmer only) |
| Error state heading | "Couldn't load events" |
| Error state body | "Check your connection and try again." |
| Error action | "Retry" (taps `queryClient.refetchQueries(['events'])`) |
| "+N more" label | "+{N} more" (month grid overflow) |
| Popover close | "×" (aria-label="Close") |
| Today button | "Today" |
| Color legend — shared | "Family" |
| Nav bar — app name | "FamilySync" |
| Element | Copy |
| --------------------- | ----------------------------------------------------------------- |
| Primary CTA (Phase 2) | None — read-only phase; no create action |
| Empty state heading | "Nothing here" |
| Empty state body | "No events in this period. Try a different date or switch views." |
| Loading state | (No text — skeleton shimmer only) |
| Error state heading | "Couldn't load events" |
| Error state body | "Check your connection and try again." |
| Error action | "Retry" (taps `queryClient.refetchQueries(['events'])`) |
| "+N more" label | "+{N} more" (month grid overflow) |
| Popover close | "×" (aria-label="Close") |
| Today button | "Today" |
| Color legend — shared | "Family" |
| Nav bar — app name | "FamilySync" |
**Destructive actions in Phase 2:** None. Phase 2 is read-only.
@@ -342,12 +363,14 @@ Top-level layout wrapper.
## Interaction Contract
### Navigation
- Prev/next: advance by one unit of current view (day/week/month)
- Today: jump to today's date, preserve current view
- View switch: instant; no animation (avoid jank on low-end Android WebViews)
- All transitions: no slide animations; content replaces in-place
### Touch (iOS PWA)
- All tap targets: minimum 44×44px (enforced via `min-height` / `padding`)
- No hover states on touch devices (use `:focus-visible` only)
- Swipe left/right on calendar grid: advance/retreat by one unit (Schedule-X built-in)
@@ -355,6 +378,7 @@ Top-level layout wrapper.
- Tap backdrop / swipe down: close EventDetailPopover (bottom sheet on phone)
### Keyboard / accessibility
- View toolbar buttons: focusable, `role="button"`, keyboard activated with Enter/Space
- Event chips: `role="button"`, `aria-label="{title}, {date}, {time}"`
- Popover: focus trap while open; Escape closes; focus returns to triggering element
@@ -363,6 +387,7 @@ Top-level layout wrapper.
- Skeleton: `aria-busy="true"` on calendar root during loading
### Error / retry
- TanStack Query `retry: 2` for events query; after exhaustion show error state
- Error state replaces calendar grid (not a toast); "Retry" button triggers manual refetch
@@ -370,14 +395,14 @@ Top-level layout wrapper.
## State Management Contract
| State | Owner | Key | Notes |
|-------|-------|-----|-------|
| Visible event list | TanStack Query | `['events', start, end]` | Invalidated on range change |
| Current user (`/api/me`) | TanStack Query | `['me']` | Used for color derivation |
| Selected view | Zustand + localStorage | `calendarView.{breakpointGroup}` | Persisted per device category |
| Selected date (nav) | Zustand | `calendarSelectedDate` | ISO string; not persisted |
| Open popover event ID | Zustand | `openEventId` | `null` when closed |
| Visible range | Zustand | `calendarRange` | `{ start: string, end: string }` — drives Query key |
| State | Owner | Key | Notes |
| ------------------------ | ---------------------- | -------------------------------- | --------------------------------------------------- |
| Visible event list | TanStack Query | `['events', start, end]` | Invalidated on range change |
| Current user (`/api/me`) | TanStack Query | `['me']` | Used for color derivation |
| Selected view | Zustand + localStorage | `calendarView.{breakpointGroup}` | Persisted per device category |
| Selected date (nav) | Zustand | `calendarSelectedDate` | ISO string; not persisted |
| Open popover event ID | Zustand | `openEventId` | `null` when closed |
| Visible range | Zustand | `calendarRange` | `{ start: string, end: string }` — drives Query key |
Server events NEVER enter Zustand. Zustand holds only UI-shape state.
@@ -385,11 +410,11 @@ Server events NEVER enter Zustand. Zustand holds only UI-shape state.
## Registry Safety
| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| shadcn official | none — shadcn not initialized in Phase 2 | not applicable |
| schedule-x (npm) | `@schedule-x/react`, `@schedule-x/calendar`, `@schedule-x/theme-default` | npm package — no registry vetting gate required; standard npm supply chain |
| lucide-react (npm) | icon components | npm package — standard |
| Registry | Blocks Used | Safety Gate |
| ------------------ | ------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| shadcn official | none — shadcn not initialized in Phase 2 | not applicable |
| schedule-x (npm) | `@schedule-x/react`, `@schedule-x/calendar`, `@schedule-x/theme-default` | npm package — no registry vetting gate required; standard npm supply chain |
| lucide-react (npm) | icon components | npm package — standard |
No third-party shadcn registries in Phase 2.
@@ -397,23 +422,23 @@ No third-party shadcn registries in Phase 2.
## Pre-Population Sources
| Decision | Source |
|----------|--------|
| Shared-family color = `#F25C7A` | User-confirmed in phase prompt |
| `WEEK_START_DAY = 0` (Sunday) | User-confirmed in phase prompt |
| Token-layer architecture (D-01/D-02) | CONTEXT.md §Theming |
| Clean theme only (D-02) | CONTEXT.md §Theming |
| All four views (D-04) | CONTEXT.md §Views, REQUIREMENTS.md CAL-03 |
| Phone→Agenda / tablet→Month default (D-05) | CONTEXT.md §Views |
| Per-member color from `users.color` (D-06) | CONTEXT.md §Color, CLAUDE.md schema |
| Color legend, no show/hide filter (D-07) | CONTEXT.md §Color |
| Informational density + tap-to-expand (D-08) | CONTEXT.md §Event detail |
| Server-side recurrence expansion (D-09) | CONTEXT.md §Recurrence, CLAUDE.md |
| Single local timezone, no shift for all-day (D-10) | CONTEXT.md §Recurrence |
| TanStack Query = server state, Zustand = UI state | CLAUDE.md, CONTEXT.md §Code patterns |
| React 19 + Vite stack | CLAUDE.md §Recommended Stack |
| No shadcn yet (Phase 3 adoption) | Codebase scan (no components.json) |
| Schedule-X as rendering library | Researcher decision (D-Claude); see §Rendering Library |
| Decision | Source |
| -------------------------------------------------- | ------------------------------------------------------ |
| Shared-family color = `#F25C7A` | User-confirmed in phase prompt |
| `WEEK_START_DAY = 0` (Sunday) | User-confirmed in phase prompt |
| Token-layer architecture (D-01/D-02) | CONTEXT.md §Theming |
| Clean theme only (D-02) | CONTEXT.md §Theming |
| All four views (D-04) | CONTEXT.md §Views, REQUIREMENTS.md CAL-03 |
| Phone→Agenda / tablet→Month default (D-05) | CONTEXT.md §Views |
| Per-member color from `users.color` (D-06) | CONTEXT.md §Color, CLAUDE.md schema |
| Color legend, no show/hide filter (D-07) | CONTEXT.md §Color |
| Informational density + tap-to-expand (D-08) | CONTEXT.md §Event detail |
| Server-side recurrence expansion (D-09) | CONTEXT.md §Recurrence, CLAUDE.md |
| Single local timezone, no shift for all-day (D-10) | CONTEXT.md §Recurrence |
| TanStack Query = server state, Zustand = UI state | CLAUDE.md, CONTEXT.md §Code patterns |
| React 19 + Vite stack | CLAUDE.md §Recommended Stack |
| No shadcn yet (Phase 3 adoption) | Codebase scan (no components.json) |
| Schedule-X as rendering library | Researcher decision (D-Claude); see §Rendering Library |
---