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.
19 KiB
19 KiB
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:
- Create router file in
apps/api/src/routes/(or add to existing) - Define Zod schema for input validation
- Implement handler(s): call resolveUserId, validate input, check authorization, query DB or enqueue outbox
- Mount in
apps/api/src/index.tsviaapp.route('/api/...', newRouter) - Export typed fetch function from
apps/pwa/src/api/client.ts - Call from CalendarShell or component via useQuery/useMutation
- Write unit tests in
apps/api/tests/routes/
New Component:
- Create
.tsxfile inapps/pwa/src/components/ - Use TanStack Query for server state (via useQuery hook)
- Use Zustand selectors for UI state (via useCalendarStore)
- Export from CalendarShell or parent component
- Add
.test.tsxfile with Vitest + React Testing Library - Mock useQuery and useCalendarStore in tests
New Utility:
- Create
.tsfile inapps/pwa/src/lib/(frontend) orapps/api/src/broker/(backend) - Export functions with clear names and JSDoc comments
- Add
.test.tsfile with test cases - 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:mysqlafter modifyingschema.ts; commit the.sqlfile - How to apply: Run
drizzle-kit migrate:mysqlto execute pending migrations against MariaDB
apps/pwa/public/:
- Purpose: PWA static assets served at root (manifest.webmanifest, service worker, icons, index.html)
- Generated:
sw.jsandregisterSW.jsare 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.yamlfor reproducibility)
.planning/codebase/:
- Purpose: Auto-generated codebase analysis documents (this file, ARCHITECTURE.md, TESTING.md, etc.)
- Generated: Yes (by
/gsd-map-codebaseorchestrator) - Committed: Yes (reference documentation for future phases)
Structure analysis: 2026-06-09