# Architecture **Analysis Date:** 2026-04-30 ## System Overview ```text ┌─────────────────────────────────────────────────────────────┐ │ Frontend SPA (React) │ ├──────────────────┬──────────────────┬───────────────────────┤ │ App Shell/Routes │ Shared State │ Feature Pages │ │ `frontend/src/ │ `frontend/src/ │ `frontend/src/pages/` │ │ App.tsx` │ context/` │ │ └────────┬─────────┴────────┬─────────┴──────────┬────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Backend API (Fastify) │ │ `backend/src/index.ts` + `backend/src/routes/` │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ SQLite Persistence + Migration Layer │ │ `backend/src/db/schema.ts` + `backend/src/db/client.ts` │ └─────────────────────────────────────────────────────────────┘ ``` ## Component Responsibilities | Component | Responsibility | File | |-----------|----------------|------| | Frontend bootstrap | Mount providers/router and start app tree | `frontend/src/main.tsx` | | App router/shell | Public share routes, authenticated shell routes, global modal composition | `frontend/src/App.tsx` | | Frontend orchestration | Compose domain hooks and expose app-level state/actions | `frontend/src/context/AppContext.tsx` | | API proxy boundary | Rewrite `/api/*` requests to backend root routes | `frontend/vite.config.ts` | | Backend composition root | Register plugins/routes, await migrations, start schedulers | `backend/src/index.ts` | | Route handlers | HTTP contracts, validation, auth hooks, response shaping | `backend/src/routes/*.ts` | | Domain services | Shared domain logic and scheduler behavior | `backend/src/services/*.ts` | | Persistence | Table definitions + compatibility migration/runtime initialization | `backend/src/db/schema.ts`, `backend/src/db/client.ts` | ## Pattern Overview **Overall:** Layered modular monolith (single frontend SPA + single backend process) **Key Characteristics:** - Frontend uses React Router + context/hook composition (`frontend/src/App.tsx`, `frontend/src/context/AppContext.tsx`). - Backend uses route modules with shared service modules (`backend/src/routes/medications.ts`, `backend/src/services/medications-service.ts`). - Data persistence is centralized in Drizzle schema + startup migrations (`backend/src/db/schema.ts`, `backend/src/db/client.ts`). ## Layers **Frontend Presentation + Orchestration:** - Purpose: Render UI, route navigation, manage client state, invoke API. - Location: `frontend/src/main.tsx`, `frontend/src/App.tsx`, `frontend/src/pages/`, `frontend/src/context/`, `frontend/src/hooks/`. - Contains: pages, modals, app shell, hook-based API callers. - Depends on: backend `/api/*`, i18n, shared frontend utils/types. - Used by: browser clients. **Backend HTTP/API Layer:** - Purpose: Expose REST endpoints, authenticate/authorize requests, validate input, map to service/db logic. - Location: `backend/src/index.ts`, `backend/src/routes/`, `backend/src/plugins/`. - Contains: Fastify app setup, route registration, auth middleware. - Depends on: services, db client/schema, env plugin. - Used by: frontend SPA and API consumers. **Domain Services Layer:** - Purpose: Reusable business logic for scheduling, notifications, stock math, parsing. - Location: `backend/src/services/`, `backend/src/utils/`. - Contains: reminder scheduler, notification builders/delivery, medication helpers. - Depends on: db models and utilities. - Used by: routes and startup process. **Persistence Layer:** - Purpose: Define DB schema and keep existing SQLite instances compatible. - Location: `backend/src/db/schema.ts`, `backend/src/db/client.ts`, `backend/drizzle/`. - Contains: tables, migration execution, backward-compatible alter migrations. - Depends on: Drizzle + libsql client. - Used by: routes/services. ## Data Flow ### Primary Request Path 1. Frontend page triggers API call via `/api/*` fetch (`frontend/src/pages/PlannerPage.tsx:307`). 2. Vite proxy rewrites `/api` prefix to backend route root (`frontend/vite.config.ts:23`, `frontend/vite.config.ts:26`). 3. Fastify route handles request under `/planner/send-email` with auth + validation (`backend/src/routes/planner.ts:141`, `backend/src/routes/planner.ts:158`). 4. Route loads user settings and dispatches channel delivery helpers (`backend/src/routes/planner.ts:221`, `backend/src/routes/planner.ts:432`, `backend/src/routes/planner.ts:829`). ### Public Share Flow 1. Frontend routes public token URL to shared schedule view (`frontend/src/App.tsx:35`). 2. Shared schedule component fetches token payload from `/api/share/:token` (`frontend/src/components/SharedSchedule.tsx:311`). 3. Backend public share route reads token/settings and returns filtered medication schedule (`backend/src/routes/share.ts:125`, `backend/src/routes/share.ts:156`). **State Management:** - Frontend: context-centric state aggregation (`frontend/src/context/AppContext.tsx:248`, `frontend/src/context/AppContext.tsx:1020`). - Backend: DB-backed state with runtime scheduler state persisted through notification state utilities (`backend/src/services/reminder-scheduler.ts:42`). ## Key Abstractions **Auth Context + Guards:** - Purpose: unify session/API-key auth across protected routes. - Examples: `backend/src/plugins/auth.ts`, `backend/src/routes/settings.ts`. - Pattern: route-level `preHandler` guard plus request decoration (`backend/src/routes/settings.ts:138`, `backend/src/plugins/auth.ts:236`). **Notification Delivery Contract:** - Purpose: keep route-triggered and scheduler-triggered notifications consistent. - Examples: `backend/src/routes/planner.ts`, `backend/src/services/reminder-scheduler.ts`, `backend/src/services/notifications/delivery.ts`. - Pattern: shared builders/delivery/state helpers imported into both paths (`backend/src/routes/planner.ts:23`, `backend/src/services/reminder-scheduler.ts:39`). **Frontend App Context Aggregator:** - Purpose: centralize shared medication/settings/dose/share/refill state for page/modal consumers. - Examples: `frontend/src/context/AppContext.tsx`, `frontend/src/context/ShareContext.tsx`. - Pattern: compose domain hooks, expose typed value via provider (`frontend/src/context/AppContext.tsx:248`, `frontend/src/context/AppContext.tsx:1020`). ## Entry Points **Frontend bootstrap:** - Location: `frontend/src/main.tsx` - Triggers: browser loads `index.html`. - Responsibilities: initialize theme/provider stack and router (`frontend/src/main.tsx:12`, `frontend/src/main.tsx:15`). **Backend process entry:** - Location: `backend/src/index.ts` - Triggers: `npm run dev`/`npm start` in backend package. - Responsibilities: await migrations, register routes, start HTTP listener and schedulers (`backend/src/index.ts:231`, `backend/src/index.ts:305`, `backend/src/index.ts:309`, `backend/src/index.ts:334`). ## Architectural Constraints - **Threading:** Single Node.js event loop process with in-process schedulers started at runtime (`backend/src/index.ts:309`, `backend/src/index.ts:323`). - **Global state:** Module/global singletons exist in auth and context layers (`backend/src/plugins/auth.ts:15`, `frontend/src/context/AppContext.tsx:222`). - **Circular imports:** Not detected from sampled route/service/db/frontend orchestration files. - **API boundary:** Frontend network calls must use `/api/*` so proxy rewrite applies (`frontend/vite.config.ts:23`, `frontend/vite.config.ts:26`). ## Anti-Patterns ### Duplicated Backend App Wiring **What happens:** Route/plugin registration appears in both `createApp(...)` and top-level startup path. **Why it's wrong:** Two bootstrap paths increase divergence risk when new routes/plugins are added in one path but not the other. **Do this instead:** Keep a single shared app-construction function used by both test/runtime startup paths (`backend/src/index.ts:133`, `backend/src/index.ts:207`, `backend/src/index.ts:289`). ### Oversized Frontend Orchestration Context **What happens:** `AppContext` aggregates many unrelated concerns (medications, settings, doses, sharing, import/export, modal history) in one large provider. **Why it's wrong:** High coupling and broad rerender surface make safe changes harder and increase regression risk. **Do this instead:** Preserve existing provider contract, but move new domain concerns into focused hooks/providers and re-export through composition only when needed (`frontend/src/context/AppContext.tsx`, file size ~1035 lines). ## Error Handling **Strategy:** Fail fast at route boundary with explicit status codes and schema validation, then log context-rich errors. **Patterns:** - Route validation + immediate 400 responses for invalid input (`backend/src/routes/medications.ts:76`, `backend/src/routes/medications.ts:584`). - Planner routes return explicit channel/config errors (`backend/src/routes/planner.ts:204`, `backend/src/routes/planner.ts:509`). - Frontend captures network errors and maps them to normalized error codes for UI handling (`frontend/src/hooks/useMedications.ts:80`). ## Cross-Cutting Concerns **Logging:** Fastify logger options configured centrally with environment-aware formatting (`backend/src/index.ts:66`, `backend/src/index.ts:161`). **Validation:** Zod validation for medication payloads and explicit OpenAPI schema contracts in routes (`backend/src/routes/medications.ts:76`, `backend/src/routes/planner.ts:157`). **Authentication:** Route-level auth hooks and dual API-key/session handling (`backend/src/routes/planner.ts:141`, `backend/src/plugins/auth.ts:113`, `backend/src/plugins/auth.ts:236`). --- *Architecture analysis: 2026-04-30*