{ "_meta": { "updated_at": "2026-06-09T18:56:37.326Z", "commit": "99f59c3999f4ef992f01ef8f8a38c1d86d2f2a0f", "version": 3 }, "entries": { "apps/api/src/index.ts": { "exports": ["app"], "imports": [ "@hono/node-server", "@hono/node-server/serve-static", "hono", "./routes/health.js", "./routes/me.js", "./routes/events.js", "./routes/lists.js", "./routes/sse.js", "./auth/middleware.js", "./auth/devBypass.js", "./broker/poller.js", "./broker/outboxWorker.js" ], "type": "entry-point", "notes": "Hono app factory + HTTP server; mounts routes, OIDC guard, static PWA assets. Broker workers started only when isMainModule()." }, "apps/api/src/routes/events.ts": { "exports": ["eventsRouter"], "imports": [ "node:crypto", "hono", "@hono/zod-validator", "zod", "drizzle-orm", "../db/client.js", "../db/schema.js", "../broker/expand.js", "../broker/vevent.js", "../auth/middleware.js", "../auth/user.js", "../auth/devBypass.js" ], "type": "module", "notes": "GET /api/events (windowed), POST /api/events/create, PATCH /api/events/:uid/edit, DELETE /api/events/:uid, GET /api/events/sync-status, GET /api/events/writable-calendars. Writes enqueue to calendarOutbox only — never calls Fastmail directly." }, "apps/api/src/routes/lists.ts": { "exports": ["listsRouter", "listItemsRouter"], "imports": [ "hono", "@hono/zod-validator", "zod", "drizzle-orm", "../db/client.js", "../db/schema.js", "../auth/middleware.js", "../auth/user.js", "../auth/devBypass.js", "../lib/rank.js", "../lib/listEmitter.js" ], "type": "module", "notes": "listsRouter: GET/POST /api/lists, PATCH/DELETE /api/lists/:id, POST/GET /api/lists/:id/items. listItemsRouter: PATCH/DELETE /api/list-items/:itemId. Owner-guard on isShared mutations (T-04-07/T-04-08). Auto-populates list_shares on isShared=true creation (D-01/D-02). publishListEvent fan-out after every mutation." }, "apps/api/src/routes/sse.ts": { "exports": ["sseRouter"], "imports": [ "hono", "hono/streaming", "../auth/middleware.js", "../auth/user.js", "../auth/devBypass.js", "../lib/listEmitter.js", "../lib/listAccess.js" ], "type": "module", "notes": "GET /api/sse/heartbeat — 10s interval smoke-test. GET /api/sse/lists — scoped live-list fan-out (LIST-04, D-04); subscribes per-accessible-list via subscribeListEvents; 30s keepalive heartbeat." }, "apps/api/src/routes/me.ts": { "exports": ["meRouter"], "imports": ["hono", "../auth/middleware.js", "../auth/user.js", "../auth/devBypass.js"], "type": "module", "notes": "GET /api/me — returns { user: { id, displayName, color } }. Upserts user on first login." }, "apps/api/src/routes/health.ts": { "exports": ["healthRouter"], "imports": ["hono", "../db/client.js", "drizzle-orm"], "type": "module", "notes": "GET /health — unauthenticated. Runs SELECT 1 against DB; returns { ok, db }." }, "apps/api/src/db/schema.ts": { "exports": [ "users", "memberCredentials", "calendars", "calendarEvents", "calendarOutbox", "lists", "listShares", "listItems" ], "imports": ["drizzle-orm/mysql-core"], "type": "config", "notes": "Drizzle schema for all 8 MariaDB tables. Phase 4 adds lists, list_shares, list_items. list_items.rank uses varcharBin (COLLATE utf8mb4_bin) for fractional-indexing sort correctness. calendarOutbox status enum: pending|done|failed|dead." }, "apps/api/src/db/client.ts": { "exports": ["db"], "imports": ["drizzle-orm/mysql2", "mysql2/promise"], "type": "module", "notes": "Drizzle client bound to mysql2 pool. Reads DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME from env." }, "apps/api/src/lib/listEmitter.ts": { "exports": ["publishListEvent", "subscribeListEvents", "ListEvent"], "imports": ["node:events"], "type": "module", "notes": "In-process singleton EventEmitter for list change fan-out (D-18). Per-list channels keyed as list:${listId}. publishListEvent broadcasts; subscribeListEvents returns an unsubscribe fn. Max 200 listeners (T-04-04). Redis swap seam: abstraction boundary is inside this module." }, "apps/api/src/lib/listAccess.ts": { "exports": ["getAccessibleListIds"], "imports": ["drizzle-orm", "../db/client.js", "../db/schema.js"], "type": "module", "notes": "getAccessibleListIds(userId): returns deduped list IDs the user owns OR has a list_shares row for. Gate used by SSE endpoint to scope subscriptions (D-04, T-04-02, T-04-03)." }, "apps/api/src/lib/rank.ts": { "exports": ["rankForAppend", "rankBetween"], "imports": ["fractional-indexing"], "type": "module", "notes": "Pure helpers wrapping fractional-indexing generateKeyBetween. rankForAppend(lastRank) → rank after last active item. rankBetween(prev, next) → rank between two items. No DB access." }, "apps/api/src/auth/middleware.ts": { "exports": ["oidcAuthMiddleware", "processOAuthCallback", "getAuth"], "imports": ["@hono/oidc-auth", "hono"], "type": "module", "notes": "OIDC middleware for Hono. Reads OIDC_AUTH_EXTERNAL_URL (mandatory behind Pangolin), OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_ISSUER from env." }, "apps/api/src/auth/devBypass.ts": { "exports": ["devAuthBypass", "DEV_USER"], "imports": ["hono"], "type": "module", "notes": "Dev-only auth bypass middleware. Active only when DEV_AUTH_BYPASS=true AND NODE_ENV!=production. Augments Hono ContextVariableMap with 'user' key." }, "apps/api/src/auth/user.ts": { "exports": ["upsertUser", "deriveDisplayName"], "imports": ["../db/client.js", "../db/schema.js", "drizzle-orm"], "type": "module", "notes": "User upsert keyed on oidc_iss + oidc_sub. deriveDisplayName: name → preferred_username → email → sub." }, "apps/api/src/broker/poller.ts": { "exports": ["startBrokerPoller"], "imports": ["node-cron", "./sync.js", "../db/client.js", "../db/schema.js"], "type": "module", "notes": "5-minute cron that polls Fastmail CalDAV for each member credential. ctag change-detection (D-13)." }, "apps/api/src/broker/outboxWorker.ts": { "exports": ["startOutboxWorker"], "imports": ["node-cron", "./write.js", "../db/client.js", "../db/schema.js"], "type": "module", "notes": "15-second cron that drains pending calendarOutbox rows. Dispatches create/update/delete to Fastmail. Status machine: pending → done|failed|dead." }, "apps/api/src/broker/sync.ts": { "exports": ["syncCalendarsForCredential"], "imports": ["./client.js", "./expand.js", "../db/client.js", "../db/schema.js", "ical.js"], "type": "module", "notes": "CalDAV PROPFIND + REPORT → upserts calendars and calendarEvents rows." }, "apps/api/src/broker/write.ts": { "exports": ["executeOutboxRow"], "imports": ["./client.js", "./vevent.js", "../db/client.js", "../db/schema.js"], "type": "module", "notes": "Executes a single outbox row: builds VEVENT, calls tsdav PUT/DELETE with If-Match etag." }, "apps/api/src/broker/client.ts": { "exports": ["createFastmailClient"], "imports": ["tsdav", "./crypto.js", "../db/client.js", "../db/schema.js"], "type": "module", "notes": "Creates a tsdav DAVClient per member credential (decrypted AES-256-GCM)." }, "apps/api/src/broker/crypto.ts": { "exports": ["encrypt", "decrypt"], "imports": ["node:crypto"], "type": "module", "notes": "AES-256-GCM encrypt/decrypt for Fastmail app passwords stored in memberCredentials." }, "apps/api/src/broker/expand.ts": { "exports": ["expandOccurrences"], "imports": ["ical.js", "temporal-polyfill"], "type": "module", "notes": "Expands raw VCALENDAR string into CalendarOccurrence[] for a [start, end) window. Handles RRULE, EXDATE, DST via ical.js + Temporal." }, "apps/api/src/broker/vevent.ts": { "exports": ["buildVevent", "extractRruleString"], "imports": ["ical.js"], "type": "module", "notes": "Builds VCALENDAR/VEVENT strings from CreateEventPayload. extractRruleString preserves RRULE on calendar-move edits." }, "apps/pwa/src/main.tsx": { "exports": [], "imports": ["react-dom/client", "./App.tsx"], "type": "entry-point", "notes": "React root mount." }, "apps/pwa/src/App.tsx": { "exports": ["default"], "imports": [ "react-router", "./components/CalendarShell.js", "./routes/ListsIndex.js", "./routes/ListDetail.js", "./components/BottomTabBar.js" ], "type": "entry-point", "notes": "BrowserRouter shell. Routes: / → /calendar redirect, /calendar → CalendarShell, /lists → ListsIndex, /lists/:listId → ListDetail. BottomTabBar rendered as persistent sibling of Routes." }, "apps/pwa/src/routes/ListsIndex.tsx": { "exports": ["ListsIndex"], "imports": ["react", "@tanstack/react-query", "../api/listsClient.js", "../components/"], "type": "module", "notes": "Lists overview route (/lists). TanStack Query ['lists'] → fetchLists. Renders ListCard per list, ListsEmptyState when empty, CreateListSheet for new list, ListDeleteDialog for delete confirmation. Optimistic delete with rollback." }, "apps/pwa/src/routes/ListDetail.tsx": { "exports": ["ListDetail"], "imports": [ "react", "@tanstack/react-query", "fractional-indexing", "@dnd-kit/core", "@dnd-kit/sortable", "../api/listsClient.js", "../hooks/useListSSE.js", "../components/" ], "type": "module", "notes": "Single list view (/lists/:listId). Splits items into active (!checked, rank ASC) and completed sections. dnd-kit drag-to-reorder with PATCH { position }. useListSSE for live sync (D-10/D-11). 30s polling fallback (D-12). Optimistic check/uncheck + add + delete." }, "apps/pwa/src/api/listsClient.ts": { "exports": [ "fetchLists", "createList", "patchList", "deleteList", "fetchListItems", "addItem", "patchListItem", "deleteItem", "List", "ListItem", "ListsResponse", "ListItemsResponse" ], "imports": [], "type": "module", "notes": "Typed fetch wrappers for all lists API endpoints. credentials: 'include' for OIDC session cookie. Same opaqueredirect pattern as client.ts." }, "apps/pwa/src/api/client.ts": { "exports": [ "fetchMe", "fetchEvents", "createEvent", "updateEvent", "deleteEvent", "fetchSyncStatus", "fetchWritableCalendars" ], "imports": [], "type": "module", "notes": "Typed fetch wrappers for all calendar API endpoints. Uses credentials: 'include' + redirect: 'manual' for OIDC opaqueredirect detection." }, "apps/pwa/src/hooks/useListSSE.ts": { "exports": ["useListSSE"], "imports": ["react", "@tanstack/react-query"], "type": "module", "notes": "Bounded-backoff EventSource hook for /api/sse/lists (D-11). Backoff: 250ms→500ms→1s→2s→4s→cap 8s; stops after MAX_ATTEMPTS. withCredentials: true (T-04-01). On open: invalidates ['list', listId] for full refetch (D-10). On event: invalidates relevant query. Polling fallback (D-12) lives in ListDetail." }, "apps/pwa/src/components/CalendarShell.tsx": { "exports": ["CalendarShell"], "imports": [ "react", "@tanstack/react-query", "@schedule-x/react", "../api/client.ts", "../lib/calendarConfig.ts", "../lib/hydrateEvents.ts", "../lib/loginRedirect.ts", "../store/calendarStore.ts", "./EventDetailPopover.tsx", "./EventForm.tsx", "./SyncStateToast.tsx", "./ColorLegend.tsx", "./SkeletonCalendar.tsx", "./ErrorBoundary.tsx" ], "type": "module", "notes": "Top-level calendar view. Orchestrates TanStack Query fetches, Schedule-X calendar, event create/edit/delete flows, sync toasts." }, "apps/pwa/src/components/BottomTabBar.tsx": { "exports": ["BottomTabBar"], "imports": ["react", "react-router", "../store/listsStore.js"], "type": "module", "notes": "Phone-only bottom navigation tab bar. Tabs: Calendar (/calendar) and Lists (/lists). Persistent across route changes (rendered outside ). Visibility controlled by CSS at ≥768px." }, "apps/pwa/src/store/listsStore.ts": { "exports": ["useListsStore"], "imports": ["zustand"], "type": "module", "notes": "Zustand UI-only state for lists surface: activeTab, createListSheetOpen. No server data. Follows calendarStore.ts pattern — no persist, no immer." }, "apps/pwa/src/store/calendarStore.ts": { "exports": ["useCalendarStore"], "imports": ["zustand"], "type": "module", "notes": "Zustand store for UI-only state: selectedDateRange, calendarId→color map, drawer open/closed. No server state." }, "apps/pwa/src/lib/calendarConfig.ts": { "exports": ["buildCalendarConfig"], "imports": [], "type": "module", "notes": "Builds Schedule-X calendar config from member color map and MeUser." }, "apps/pwa/src/lib/hydrateEvents.ts": { "exports": ["hydrateEvents"], "imports": ["../api/client.ts"], "type": "module", "notes": "Maps CalendarOccurrence[] → Schedule-X event objects. Routes by isShared/ownerUserId (never calendarId)." }, "apps/pwa/src/lib/eventDateTime.ts": { "exports": ["formatEventDateTime", "toScheduleXDateTime"], "imports": ["temporal-polyfill"], "type": "module", "notes": "Date/time formatting helpers for Schedule-X event start/end fields." }, "apps/pwa/src/lib/loginRedirect.ts": { "exports": ["maybeRedirectToLogin"], "imports": [], "type": "module", "notes": "Top-level navigation to /api/login when OIDC 302/opaqueredirect detected. CORS-bypass strategy." }, "apps/pwa/src/lib/colorUtils.ts": { "exports": ["assignMemberColors"], "imports": [], "type": "module", "notes": "Assigns hex colors from palette to members deterministically." } } }