# 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_