Files
Lucas Berger 982438dc10 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.
2026-06-11 20:35:18 -04:00

283 lines
19 KiB
Markdown

# Codebase Structure
**Analysis Date:** 2026-06-09
## Directory Layout
```
familysync/
├── apps/
│ ├── api/
│ │ ├── src/
│ │ │ ├── index.ts # Hono app + HTTP server + broker startup
│ │ │ ├── auth/
│ │ │ │ ├── middleware.ts # OIDC guard via @hono/oidc-auth
│ │ │ │ ├── user.ts # Identity upsert + color assignment
│ │ │ │ └── devBypass.ts # DEV_AUTH_BYPASS middleware (local dev)
│ │ │ ├── db/
│ │ │ │ ├── client.ts # mysql2 + Drizzle instance
│ │ │ │ ├── schema.ts # Drizzle table definitions
│ │ │ │ └── migrations/ # drizzle-kit migration files
│ │ │ ├── routes/
│ │ │ │ ├── events.ts # GET /api/events (windowed), POST/PATCH/DELETE (enqueue)
│ │ │ │ ├── me.ts # GET /api/me (current user)
│ │ │ │ ├── health.ts # GET /health (unauthenticated)
│ │ │ │ └── sse.ts # GET /api/sse/heartbeat (SSE test)
│ │ │ └── broker/
│ │ │ ├── poller.ts # 5-min cron: PROPFIND → ctag detect
│ │ │ ├── sync.ts # REPORT → ical.js → upsert (per-calendar)
│ │ │ ├── outboxWorker.ts # 15-sec cron: drain pending writes to Fastmail
│ │ │ ├── write.ts # PUT/DELETE builders for tsdav
│ │ │ ├── expand.ts # Server-side RRULE expansion
│ │ │ ├── vevent.ts # VEVENT builder + RRULE extraction
│ │ │ ├── client.ts # tsdav client factory
│ │ │ ├── crypto.ts # AES-256-GCM encrypt/decrypt
│ │ │ └── spike.ts # Proof-of-concept (unused, historical)
│ │ ├── tests/
│ │ │ ├── routes/ # Unit tests for route handlers
│ │ │ ├── broker/ # Unit tests for broker modules
│ │ │ ├── auth/ # Unit tests for auth
│ │ │ ├── fixtures/ # Test data factories
│ │ │ └── helpers/ # Test utilities (mock db, etc.)
│ │ ├── package.json # Backend dependencies
│ │ ├── tsconfig.json # TypeScript config (strict mode)
│ │ └── dist/ # Compiled JavaScript (gitignored)
│ └── pwa/
│ ├── src/
│ │ ├── main.tsx # Vite entry point
│ │ ├── App.tsx # Root component (CalendarShell)
│ │ ├── components/
│ │ │ ├── CalendarShell.tsx # Schedule-X wiring + TanStack Query + Zustand
│ │ │ ├── AppNav.tsx # Header/sidebar navigation
│ │ │ ├── EventDetailPopover.tsx # Event detail display + edit/delete actions
│ │ │ ├── EventForm.tsx # Create/edit event modal
│ │ │ ├── DeleteConfirmationDialog.tsx # Delete confirm modal
│ │ │ ├── SyncStateToast.tsx # Write-back status toast
│ │ │ ├── ColorLegend.tsx # Calendar color legend
│ │ │ ├── InstallPrompt.tsx # PWA install prompt
│ │ │ ├── SkeletonCalendar.tsx # Loading skeleton
│ │ │ ├── ErrorBoundary.tsx # Error boundary wrapper
│ │ │ └── *.test.tsx # Component tests
│ │ ├── api/
│ │ │ ├── client.ts # Typed fetch wrappers (fetchMe, fetchEvents, fetchCreateEvent, etc.)
│ │ │ └── client.test.ts # API client tests
│ │ ├── store/
│ │ │ └── calendarStore.ts # Zustand UI-state store
│ │ ├── lib/
│ │ │ ├── hydrateEvents.ts # Occurrence[] → Schedule-X CalendarType[]
│ │ │ ├── calendarConfig.ts # Schedule-X config builder
│ │ │ ├── colorUtils.ts # Hex color utilities
│ │ │ ├── eventDateTime.ts # Date/time formatting + parsing
│ │ │ ├── loginRedirect.ts # OIDC redirect handler (maybeRedirectToLogin)
│ │ │ └── *.test.ts # Utility tests
│ │ └── styles/
│ │ └── tokens.ts # CSS-in-JS design tokens (colors, spacing)
│ ├── public/
│ │ ├── index.html # PWA shell HTML
│ │ ├── manifest.webmanifest # PWA metadata
│ │ ├── sw.js # Service worker entry (generated by vite-plugin-pwa)
│ │ ├── icon-192.png # PWA icon (192x192)
│ │ └── icon-512.png # PWA icon (512x512)
│ ├── package.json # Frontend dependencies
│ ├── tsconfig.json # TypeScript config
│ ├── vite.config.ts # Vite + vite-plugin-pwa configuration
│ └── dist/ # Built PWA (gitignored)
├── packages/
│ └── shared/ # Shared types (currently placeholder)
├── package.json # Monorepo root (pnpm workspaces)
├── pnpm-lock.yaml # Dependency lock file
└── .planning/
└── codebase/ # This document
```
## Directory Purposes
**`apps/api/src/`** — Backend HTTP server and background broker
- **Routes** respond to client requests (GET reads cache only; POST/PATCH/DELETE enqueue outbox)
- **Broker** runs background jobs (poller syncs with Fastmail; outbox worker drains writes)
- **Auth** handles OIDC session + user identity upsert
- **DB** defines schema and provides Drizzle ORM client
**`apps/pwa/src/`** — React PWA frontend
- **Components** render UI and handle user interactions
- **API** wraps typed fetch calls to backend endpoints
- **Store** owns UI-only state (view selection, modal open/close) via Zustand
- **Lib** provides utilities for date handling, color assignment, event hydration, login redirect
- **Public** contains PWA manifest, service worker config, and static assets
- **Styles** defines design tokens (colors, spacing, typography)
**`apps/api/tests/`** — Unit tests for backend
- **Routes** test endpoint validation, authorization, DB queries
- **Broker** test CalDAV sync logic, RRULE expansion, outbox draining
- **Auth** test user upsert, color assignment, OIDC claim handling
- **Fixtures** provide test data factories (mock users, credentials, events)
- **Helpers** provide test utilities (mock Drizzle, mock tsdav clients)
**`packages/shared/`** — Shared types (future expansion for N-member)
- Currently a placeholder; will contain cross-app TypeScript interfaces when multi-member features need shared definitions
## Key File Locations
**Entry Points:**
| File | Purpose |
| ----------------------- | ------------------------------------------------------------------------- |
| `apps/api/src/index.ts` | Hono app definition, middleware stack, route registration, broker startup |
| `apps/pwa/src/main.tsx` | Vite entry point; React.createRoot, hydrate App |
| `apps/pwa/src/App.tsx` | Root component; renders CalendarShell |
**Configuration:**
| File | Purpose |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `apps/api/package.json` | Backend dependencies (Hono, Drizzle, tsdav, ical.js, rrule, node-cron, zod, @hono/zod-validator, @hono/oidc-auth, mysql2) |
| `apps/pwa/package.json` | Frontend dependencies (React 19, Vite, @tanstack/react-query, Zustand, @schedule-x/react, lucide-react, etc.) |
| `apps/api/tsconfig.json` | strict: true; lib: es2022; module: es2022 |
| `apps/pwa/tsconfig.json` | strict: true; jsx: react-jsx; lib: es2022, dom |
| `apps/pwa/vite.config.ts` | Vite plugins (react, VitePWA); dev proxy to :3000; PWA manifest config |
**Core Logic:**
| File | Purpose |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `apps/api/src/db/schema.ts` | Drizzle table definitions (users, member_credentials, calendars, calendar_events, calendar_outbox) |
| `apps/api/src/routes/events.ts` | GET /api/events (windowed + expanded), write endpoints (POST/PATCH/DELETE), sync-status polling, writable-calendars |
| `apps/api/src/broker/poller.ts` | 5-min background job; PROPFIND → ctag change detection |
| `apps/api/src/broker/sync.ts` | REPORT → ical.js parse → calendar_events upsert; prune deletes |
| `apps/api/src/broker/outboxWorker.ts` | 15-sec drain pending outbox rows; PUT/DELETE to Fastmail; exponential backoff |
| `apps/api/src/broker/expand.ts` | ical.js RecurExpansion; emit concrete occurrences (with VTIMEZONE + RRULE handled) |
| `apps/pwa/src/components/CalendarShell.tsx` | TanStack Query (events, me), Zustand (range, view), Schedule-X wiring |
| `apps/pwa/src/store/calendarStore.ts` | Zustand store; selectedView, openEventId, calendarRange, eventFormOpen, deleteDialogOpen |
| `apps/pwa/src/api/client.ts` | Typed fetch wrappers; MeResponse, CalendarOccurrence, CreateEventPayload interfaces |
| `apps/pwa/src/lib/hydrateEvents.ts` | Occurrence[] → Schedule-X CalendarEvent[] with Temporal.ZonedDateTime conversion |
**Testing:**
| File | Purpose |
| ------------------------------------------------ | ----------------------------------------------------------------------------- |
| `apps/api/tests/routes/events.test.ts` | Unit tests for route handlers (validation, ownership checks, SQL correctness) |
| `apps/api/tests/broker/expand.test.ts` | Unit tests for RRULE expansion (VTIMEZONE, EXDATE, DST) |
| `apps/pwa/src/components/CalendarShell.test.tsx` | Component integration test; mocked React Query + Zustand |
| `apps/pwa/src/lib/hydrateEvents.test.ts` | Unit tests for Temporal conversion logic |
## Naming Conventions
**Files:**
| Pattern | Example | Where |
| ---------------------------------- | --------------------------------------------------------------------------- | -------------------------- |
| Kebab-case for route/route groups | `events.ts`, `health.ts` | `apps/api/src/routes/` |
| Kebab-case for modules | `poller.ts`, `sync.ts`, `outbox-worker.ts` (or camelCase `outboxWorker.ts`) | `apps/api/src/broker/` |
| PascalCase for React components | `CalendarShell.tsx`, `EventDetailPopover.tsx` | `apps/pwa/src/components/` |
| Kebab-case for utility functions | `hydrateEvents.ts`, `colorUtils.ts` | `apps/pwa/src/lib/` |
| `.test.ts` / `.test.tsx` for tests | `events.test.ts`, `CalendarShell.test.tsx` | Colocated with source |
**Functions:**
| Pattern | Example |
| --------------------------------------- | ---------------------------------------------------------------- |
| camelCase for functions | `fetchEvents`, `expandOccurrences`, `upsertUser`, `syncCalendar` |
| PascalCase for React components | `CalendarShell`, `EventForm`, `SyncStateToast` |
| UPPER_CASE for module-level constants | `MAX_WINDOW_DAYS`, `COLOR_PALETTE`, `TRANSIENT_STATUSES` |
| Leading `$` for Drizzle special methods | `.$returningId()`, `.onDuplicateKeyUpdate()` |
**Variables:**
| Pattern | Example |
| ------------------------------ | ----------------------------------------------- |
| camelCase for variables | `currentUserId`, `calendarRange`, `eventsQuery` |
| `is`/`has` prefix for booleans | `isShared`, `hasRrule`, `eventFormOpen` |
| Trailing `Id` for foreign keys | `userId`, `calendarId`, `groupId` |
| Descriptive names for arrays | `seenUids`, `usedColors`, `occurrences` |
**Types:**
| Pattern | Example |
| ------------------------------------- | -------------------------------------------------------- |
| PascalCase for interfaces | `CalendarOccurrence`, `MeResponse`, `CreateEventPayload` |
| PascalCase for type aliases | `RecurrencePreset`, `BreakpointGroup` |
| Trailing `Schema` for Zod/validation | `eventsQuerySchema`, `eventFieldsSchema` |
| Trailing `Response` for API responses | `MeResponse`, `OccurrencesResponse` |
## Where to Add New Code
**New Feature:**
| Feature Type | Primary Code | Tests | Configuration |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------- |
| Calendar event operation (read-only) | `apps/api/src/routes/events.ts` (new GET endpoint) | `apps/api/tests/routes/events.test.ts` | `apps/pwa/src/api/client.ts` (new fetchFn) |
| Calendar event operation (write) | `apps/api/src/routes/events.ts` (new POST/PATCH/DELETE) + `apps/api/src/broker/write.ts` (new builder) | Route tests + outbox drain tests | `apps/pwa/src/components/EventForm.tsx` (new field) |
| Recurring event handling | `apps/api/src/broker/expand.ts` (expansion logic) | `apps/api/tests/broker/expand.test.ts` | N/A (no UI change needed) |
| Shared list sync | `apps/api/src/routes/lists.ts` (new router) + `apps/api/src/broker/listsSync.ts` (if background job needed) | `apps/api/tests/routes/lists.test.ts` | `apps/pwa/src/api/client.ts` (new interfaces) |
| UI component (calendar display) | `apps/pwa/src/components/` | `apps/pwa/src/components/*.test.tsx` | N/A |
| UI component (modal/dialog) | `apps/pwa/src/components/` + `apps/pwa/src/store/calendarStore.ts` (add state if needed) | Component test | N/A |
**New Endpoint:**
1. Create router file in `apps/api/src/routes/` (or add to existing)
2. Define Zod schema for input validation
3. Implement handler(s): call resolveUserId, validate input, check authorization, query DB or enqueue outbox
4. Mount in `apps/api/src/index.ts` via `app.route('/api/...', newRouter)`
5. Export typed fetch function from `apps/pwa/src/api/client.ts`
6. Call from CalendarShell or component via useQuery/useMutation
7. Write unit tests in `apps/api/tests/routes/`
**New Component:**
1. Create `.tsx` file in `apps/pwa/src/components/`
2. Use TanStack Query for server state (via useQuery hook)
3. Use Zustand selectors for UI state (via useCalendarStore)
4. Export from CalendarShell or parent component
5. Add `.test.tsx` file with Vitest + React Testing Library
6. Mock useQuery and useCalendarStore in tests
**New Utility:**
1. Create `.ts` file in `apps/pwa/src/lib/` (frontend) or `apps/api/src/broker/` (backend)
2. Export functions with clear names and JSDoc comments
3. Add `.test.ts` file with test cases
4. Import where needed (no circular dependencies)
## Special Directories
**`apps/api/src/db/migrations/`:**
- Purpose: drizzle-kit-generated SQL migration files
- Generated: Yes (via `drizzle-kit generate:mysql`)
- Committed: Yes (must be version-controlled for reproducibility)
- How to add: Run `drizzle-kit generate:mysql` after modifying `schema.ts`; commit the `.sql` file
- How to apply: Run `drizzle-kit migrate:mysql` to execute pending migrations against MariaDB
**`apps/pwa/public/`:**
- Purpose: PWA static assets served at root (manifest.webmanifest, service worker, icons, index.html)
- Generated: `sw.js` and `registerSW.js` are generated by vite-plugin-pwa; others are committed
- Committed: Yes (except dist/ and generated service worker code — PWA plugin handles registration)
- How to add: Place assets here; vite build copies to dist/ and serves at /
**`apps/api/dist/` and `apps/pwa/dist/`:**
- Purpose: Compiled output (JavaScript, CSS, bundled PWA)
- Generated: Yes (via build scripts)
- Committed: No (gitignored)
**`node_modules/`:**
- Purpose: pnpm-installed dependencies
- Generated: Yes (via `pnpm install`)
- Committed: No (gitignored; use `pnpm-lock.yaml` for reproducibility)
**`.planning/codebase/`:**
- Purpose: Auto-generated codebase analysis documents (this file, ARCHITECTURE.md, TESTING.md, etc.)
- Generated: Yes (by `/gsd-map-codebase` orchestrator)
- Committed: Yes (reference documentation for future phases)
---
_Structure analysis: 2026-06-09_