chore: add .planning/codebase map (7 documents, gsd-map-codebase)
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
<!-- refreshed: 2026-04-30 -->
|
||||
# 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*
|
||||
@@ -0,0 +1,122 @@
|
||||
# Codebase Concerns
|
||||
|
||||
**Analysis Date:** 2026-04-30
|
||||
|
||||
## Tech Debt
|
||||
|
||||
**Backend startup duplication and config drift:**
|
||||
- Issue: `backend/src/index.ts` contains two parallel server setup paths (the exported `createApp(...)` flow and the top-level runtime bootstrap). Plugin/route registration and rate-limit defaults are duplicated in both branches.
|
||||
- Files: `backend/src/index.ts`
|
||||
- Impact: Configuration behavior can diverge between test/programmatic app construction and production startup (for example, `createApp` uses fixed `rateLimit` max `300`, while runtime startup uses `process.env.RATE_LIMIT_MAX` fallback `100`).
|
||||
- Fix approach: Extract one canonical app-construction function and let both runtime startup and tests consume it; remove duplicated registration blocks.
|
||||
|
||||
**Notification architecture leakage and duplicated composition logic:**
|
||||
- Issue: Notification delivery service code imports a route-layer helper (`sendShoutrrrNotification`) from settings routes, and large HTML/text reminder composition blocks are duplicated across manual and automatic reminder paths.
|
||||
- Files: `backend/src/services/notifications/delivery.ts`, `backend/src/routes/settings.ts`, `backend/src/routes/planner.ts`, `backend/src/services/reminder-scheduler.ts`
|
||||
- Impact: Layer boundary violations increase coupling, and duplicated notification formatting logic makes behavior regressions likely when changing message content or channel behavior.
|
||||
- Fix approach: Move `sendShoutrrrNotification` to a service-layer module, make routes call service APIs only, and centralize email/push payload builders for planner + scheduler flows.
|
||||
|
||||
**Migration artifact ambiguity in drizzle numbering:**
|
||||
- Issue: There are two migration files with `0008_` prefix, but the journal tracks only one `0008` tag and then jumps to `0009`.
|
||||
- Files: `backend/drizzle/0008_add_obsolete_medications.sql`, `backend/drizzle/0008_add_prescription_tracking.sql`, `backend/drizzle/meta/_journal.json`
|
||||
- Impact: Developer confusion and higher risk of migration-order mistakes during future schema changes.
|
||||
- Fix approach: Align migration file names and journal tags so each migration number is unique and journal order is obvious.
|
||||
|
||||
**Monolithic UI/editor and route modules with broad lint suppressions:**
|
||||
- Issue: Core interaction files are very large and rely on file-level `biome-ignore-all` suppressions for multiple rule categories.
|
||||
- Files: `frontend/src/pages/MedicationsPage.tsx`, `frontend/src/components/MobileEditModal.tsx`, `frontend/src/components/SharedSchedule.tsx`, `frontend/src/components/MedDetailModal.tsx`, `backend/src/routes/medications.ts`
|
||||
- Impact: Refactors become high-risk; local regressions are harder to isolate; suppressed rule categories hide legitimate quality issues in future edits.
|
||||
- Fix approach: Split by domain slices (state orchestration vs rendering vs helper transforms), then replace file-level suppressions with narrow, local exceptions only where justified.
|
||||
|
||||
## Known Bugs
|
||||
|
||||
**Environment-dependent behavior mismatch between test app factory and runtime app:**
|
||||
- Symptoms: Programmatic app creation and runtime startup can apply different operational defaults (rate limiting and selected config pathways).
|
||||
- Files: `backend/src/index.ts`
|
||||
- Trigger: Using `createApp(...)` in tests/integration contexts while production startup uses the top-level runtime branch.
|
||||
- Workaround: Explicitly pass runtime-equivalent options into `createApp(...)` in tests until startup construction is unified.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
**Server-side outbound notification surface is broad and sensitive to parser correctness:**
|
||||
- Risk: The app performs server-side HTTP requests to user-configurable notification URLs, including multiple protocol handlers (`pushover://`, `telegram://`, `gotify://`, generic webhook URLs).
|
||||
- Files: `backend/src/routes/settings.ts`
|
||||
- Current mitigation: URL sanitation/validation and hostname checks are present (`sanitizeNotificationUrl`, `validateNotificationHostname` usage in route logic).
|
||||
- Recommendations: Add focused security regression tests for sanitizer bypasses and callback URL edge cases, and keep all outbound request execution in a dedicated service layer.
|
||||
|
||||
**Auth-off bootstrap path creates implicit default user state:**
|
||||
- Risk: In auth-disabled mode, startup creates/relies on a default user path automatically.
|
||||
- Files: `backend/src/db/client.ts`
|
||||
- Current mitigation: Controlled by `AUTH_ENABLED` environment setting.
|
||||
- Recommendations: Add startup log warnings when running without auth outside development and enforce explicit environment confirmation in deployment templates.
|
||||
|
||||
## Performance Bottlenecks
|
||||
|
||||
**Reminder scheduling uses repeated full scans over users and medication/dose datasets:**
|
||||
- Problem: Reminder checks iterate all user settings and compute stock/prescription reminders with repeated in-memory loops over medication and dose collections.
|
||||
- Files: `backend/src/services/reminder-scheduler.ts`, `backend/src/utils/scheduler-utils.ts`
|
||||
- Cause: Polling/check strategy prioritizes correctness and compatibility over incremental indexing.
|
||||
- Improvement path: Introduce incremental candidate selection (changed-medication windows, per-user next-check indices) and reduce repeated whole-set scans.
|
||||
|
||||
**Intake reminder scheduler polls every minute and may scale linearly with active schedules:**
|
||||
- Problem: Intake reminder check loop runs continuously at 60s interval and processes all due reminders/users each tick.
|
||||
- Files: `backend/src/services/intake-reminder-scheduler.ts`
|
||||
- Cause: Fixed-interval scheduler (`CHECK_INTERVAL_MS = 60 * 1000`) with loop-driven due-item selection.
|
||||
- Improvement path: Move toward next-due-time scheduling or bucketing strategy; keep minute polling as fallback only.
|
||||
|
||||
## Fragile Areas
|
||||
|
||||
**Reminder state persistence and lock handling mix sync file IO with best-effort catches:**
|
||||
- Files: `backend/src/services/notifications/state.ts`, `backend/src/services/reminder-scheduler.ts`
|
||||
- Why fragile: Reminder state writes are synchronous file writes and some read paths swallow errors (`catch {}`), while lock/state files are local filesystem coordination primitives.
|
||||
- Safe modification: Keep file format backward-compatible, add explicit error telemetry, and add tests for concurrent/failed write scenarios before changing scheduler state logic.
|
||||
- Test coverage: No direct tests detected for `notifications/delivery.ts` and only limited direct state-function assertions.
|
||||
|
||||
**Desktop/mobile medication edit parity depends on two large independent UI paths:**
|
||||
- Files: `frontend/src/pages/MedicationsPage.tsx`, `frontend/src/components/MobileEditModal.tsx`, `frontend/src/components/medications/MedicationEditCoordinator.tsx`
|
||||
- Why fragile: The same editing domain is implemented in separate surfaces, each with dense UI logic and custom interaction handling.
|
||||
- Safe modification: Apply shared form-section components first, then update desktop and mobile in the same change; validate both paths with targeted tests.
|
||||
- Test coverage: Coverage exists (`MedicationEditCoordinator`, `MobileEditModal`, `MedicationDialogs` tests), but parity regressions remain a recurring risk due to file size/complexity.
|
||||
|
||||
## Scaling Limits
|
||||
|
||||
**Current reminder architecture is single-node/local-state oriented:**
|
||||
- Current capacity: Scheduler state and lock coordination are local files under data directory (`reminder-state.json`, `scheduler-locks/*`).
|
||||
- Limit: Horizontal multi-instance scaling can duplicate work or require externalized coordination.
|
||||
- Scaling path: Move reminder state/locks to DB or distributed lock backend and make scheduler execution leader-aware.
|
||||
|
||||
**SQLite file-backed persistence constrains concurrent write scaling:**
|
||||
- Current capacity: Single SQLite file with local filesystem path resolution.
|
||||
- Limit: Higher write concurrency and distributed deployments will hit filesystem/database locking and throughput limits.
|
||||
- Scaling path: Keep SQLite for local/small deployments; define migration path to managed DB for larger multi-user workloads.
|
||||
|
||||
## Dependencies at Risk
|
||||
|
||||
**Route-to-service coupling in notification stack:**
|
||||
- Risk: Service-layer delivery module depends on route-layer helper import.
|
||||
- Impact: Refactors of route modules can break unrelated notification infrastructure and complicate testing boundaries.
|
||||
- Migration plan: Move shared notification send helpers into `backend/src/services/notifications/*` and keep route modules thin.
|
||||
|
||||
## Missing Critical Features
|
||||
|
||||
**Risk-driven scheduler stress/integration test suite for state-lock edge cases:**
|
||||
- Problem: Complex scheduler/state code paths rely on file coordination and mixed channel delivery outcomes, but dedicated stress/chaos-style verification is limited.
|
||||
- Blocks: High-confidence scaling and reliability changes in reminder subsystems.
|
||||
|
||||
## Test Coverage Gaps
|
||||
|
||||
**Notification delivery abstraction lacks direct unit tests:**
|
||||
- What's not tested: Direct behavior of SMTP transport creation/result validation and push delivery helpers in the dedicated delivery module.
|
||||
- Files: `backend/src/services/notifications/delivery.ts`
|
||||
- Risk: Regressions in recipient validation, SMTP response handling, or provider fallback can ship unnoticed.
|
||||
- Priority: High
|
||||
|
||||
**Reminder state persistence/locking has limited direct verification:**
|
||||
- What's not tested: Corrupted file recovery, concurrent state writes, and lock stale-file behavior under failure modes.
|
||||
- Files: `backend/src/services/notifications/state.ts`, `backend/src/services/reminder-scheduler.ts`
|
||||
- Risk: Duplicate sends or missed sends after crashes/restarts are difficult to detect early.
|
||||
- Priority: High
|
||||
|
||||
---
|
||||
|
||||
*Concerns audit: 2026-04-30*
|
||||
@@ -0,0 +1,116 @@
|
||||
# Coding Conventions
|
||||
|
||||
**Analysis Date:** 2026-04-30
|
||||
|
||||
## Naming Patterns
|
||||
|
||||
**Files:**
|
||||
- Frontend React components and pages use PascalCase file names (for example `frontend/src/components/MobileEditModal.tsx`, `frontend/src/pages/MedicationsPage.tsx`).
|
||||
- Hooks use `useX` camelCase naming in files and symbols (for example `frontend/src/hooks/useMedications.ts`, `frontend/src/hooks/useScheduleController.ts`).
|
||||
- Backend routes/services use kebab-case file names with domain suffixes (for example `backend/src/routes/medications.ts`, `backend/src/services/medications-service.ts`).
|
||||
- Test files use `*.test.ts` or `*.test.tsx` in dedicated test folders (for example `backend/src/test/planner.test.ts`, `frontend/src/test/components/MobileEditModal.test.tsx`).
|
||||
|
||||
**Functions:**
|
||||
- Use camelCase names for functions and methods (for example `parseIntakesWithUnits` in `backend/src/services/medications-service.ts`, `loadMeds` in `frontend/src/hooks/useMedications.ts`).
|
||||
- Use verb-first names for side-effect operations (`loadMeds`, `deleteMed`, `uploadMedImage` in `frontend/src/hooks/useMedications.ts`).
|
||||
|
||||
**Variables:**
|
||||
- Use camelCase for local variables and state (`refillHistoryExpanded`, `scheduleDays`, `showFutureDays` in `frontend/src/context/AppContext.tsx`).
|
||||
- Constant maps and singleton keys use UPPER_SNAKE_CASE (`LOG_LEVELS` in `backend/src/utils/logger.ts`, `APP_CONTEXT_SINGLETON_KEY` in `frontend/src/context/AppContext.tsx`).
|
||||
|
||||
**Types:**
|
||||
- Type aliases and interfaces use PascalCase (`AppContextValue` in `frontend/src/context/AppContext.tsx`, `TestContext` in `backend/src/test/setup.ts`).
|
||||
- Return-shape interfaces use `UseXReturn` convention for hooks (`UseMedicationsReturn` in `frontend/src/hooks/useMedications.ts`).
|
||||
|
||||
## Code Style
|
||||
|
||||
**Formatting:**
|
||||
- Tool used: Biome (`biome.json`, scripts in `frontend/package.json`, `backend/package.json`, `package.json`).
|
||||
- Key settings from `biome.json`:
|
||||
- `indentStyle: tab`
|
||||
- `indentWidth: 2`
|
||||
- `lineWidth: 120`
|
||||
- JavaScript quote style is double quotes, semicolons enabled, trailing commas `es5`.
|
||||
|
||||
**Linting:**
|
||||
- Tool used: Biome linter (`biome.json`).
|
||||
- Key rules enforced/relevant:
|
||||
- `style.useConst: error`
|
||||
- `style.noNestedTernary: warn`
|
||||
- `correctness.noUnusedVariables: warn`
|
||||
- `suspicious.noExplicitAny: warn`
|
||||
- Project governance in `AGENTS.md` reinforces readable code, early returns, and no nested ternaries.
|
||||
|
||||
## Import Organization
|
||||
|
||||
**Order:**
|
||||
1. Node built-ins first in backend modules (for example `node:path` in `backend/src/routes/medications.ts`, `node:crypto` in `backend/src/index.ts`).
|
||||
2. External packages second (`fastify`, `zod`, `drizzle-orm` in backend; `react`, `@testing-library/*` in frontend).
|
||||
3. Internal modules last with relative paths (`../db/client.js`, `../../types`).
|
||||
|
||||
**Path Aliases:**
|
||||
- Not detected in TypeScript configs (`frontend/tsconfig.json`, `backend/tsconfig.json` do not define `paths`).
|
||||
- Relative imports are the standard.
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Patterns:**
|
||||
- Backend validates request data with Zod schemas and `.refine(...)` constraints before route logic (`backend/src/routes/medications.ts`).
|
||||
- Backend route tests assert explicit status codes and body shape (`backend/src/test/routes-real.test.ts`, `backend/src/test/planner.test.ts`).
|
||||
- Frontend hooks often normalize recoverable API errors into UI-safe states (`frontend/src/hooks/useMedications.ts` converts network failures into `NETWORK_ERROR`).
|
||||
- Some frontend fetch flows still use tolerant fallbacks (`catch(() => setMeds([]))` in `frontend/src/hooks/useMedications.ts`), so future changes should prefer explicit user-facing error channels per `AGENTS.md` fail-clear guidance.
|
||||
|
||||
## Logging
|
||||
|
||||
**Framework:**
|
||||
- Backend startup logger wrapper over console with level filtering in `backend/src/utils/logger.ts`.
|
||||
- Runtime HTTP logging via Fastify logger options in `backend/src/index.ts` (`buildLoggerOptions`, request correlation IDs).
|
||||
- Frontend logging utility mirrors backend level semantics (`frontend/src/utils/logger.ts`).
|
||||
|
||||
**Patterns:**
|
||||
- Central log-level maps (`LOG_LEVELS`) and `shouldLog` gating are standard in both frontend and backend logger modules.
|
||||
- Correlation ID propagation is enforced at request boundaries (`backend/src/index.ts` onRequest hook setting `x-correlation-id`).
|
||||
|
||||
## Comments
|
||||
|
||||
**When to Comment:**
|
||||
- Comments are used for rationale and test setup intent, not line-by-line narration.
|
||||
- Typical examples:
|
||||
- Migration/setup intent in `backend/src/test/setup.ts`
|
||||
- E2E stability rationale in `frontend/e2e/fixtures/index.ts`
|
||||
- Timeout/determinism notes in `frontend/vitest.config.ts` and `frontend/playwright.base.config.ts`
|
||||
|
||||
**JSDoc/TSDoc:**
|
||||
- Used selectively for exported utilities and test helpers (`backend/src/test/setup.ts`, `frontend/e2e/fixtures/index.ts`, `frontend/src/utils/logger.ts`).
|
||||
- Not mandatory for every function; concise type annotations plus targeted comments are preferred.
|
||||
|
||||
## Function Design
|
||||
|
||||
**Size:**
|
||||
- Small-to-medium focused functions are common in services/hooks (`parseRawIntakeUnits`, `normalizeDateTime` in `backend/src/services/medications-service.ts`).
|
||||
- Larger orchestrator modules exist where domain aggregation is required (`frontend/src/context/AppContext.tsx`).
|
||||
|
||||
**Parameters:**
|
||||
- Object parameters are used for extensibility in test factories and route payload shapes (`CreateMedicationOptions` in `backend/src/test/setup.ts`).
|
||||
- Explicit primitive parameters used for concise helpers (`clickEditMed(page, medName)` in `frontend/e2e/medication-edit.spec.ts`).
|
||||
|
||||
**Return Values:**
|
||||
- Explicit return types are common on exported functions (`Promise<TestContext>`, `UseMedicationsReturn`).
|
||||
- Guard-clause returns are common for invalid input or unavailable state (`if (!intakesJson) return [];` in `backend/src/services/medications-service.ts`).
|
||||
|
||||
## Module Design
|
||||
|
||||
**Exports:**
|
||||
- Named exports are preferred for utilities, hooks, and service functions (`backend/src/services/notifications/index.ts`, `frontend/src/hooks/index.ts`).
|
||||
- Mixed export style is used where legacy/default exports remain practical (`default` exports in component barrel `frontend/src/components/index.ts`).
|
||||
|
||||
**Barrel Files:**
|
||||
- Barrel files are actively used for stable import surfaces:
|
||||
- `frontend/src/components/index.ts`
|
||||
- `frontend/src/hooks/index.ts`
|
||||
- `backend/src/services/notifications/index.ts`
|
||||
- Practical rule for new code: export domain-level public APIs through local barrels, keep deep internal helpers imported directly.
|
||||
|
||||
---
|
||||
|
||||
*Convention analysis: 2026-04-30*
|
||||
@@ -0,0 +1,111 @@
|
||||
# External Integrations
|
||||
|
||||
**Analysis Date:** 2026-04-30
|
||||
|
||||
## APIs & External Services
|
||||
|
||||
**Medication Data APIs:**
|
||||
- European Medicines Agency (EMA) JSON catalog - medication lookup seed and periodic catalog refresh
|
||||
- SDK/Client: native `fetch` in `backend/src/services/medication-enrichment.ts` (`EMA_MEDICINES_URL`)
|
||||
- Auth: none detected in code
|
||||
- RxNorm (NLM RxNav REST) - normalized name/search enrichment and strength/form hints
|
||||
- SDK/Client: native `fetch` in `backend/src/services/medication-enrichment.ts` (`RXNORM_BASE_URL`)
|
||||
- Auth: none detected in code
|
||||
- openFDA NDC API - product/package metadata enrichment
|
||||
- SDK/Client: native `fetch` in `backend/src/services/medication-enrichment.ts` (`OPENFDA_NDC_URL`)
|
||||
- Auth: none detected in code
|
||||
|
||||
**Authentication/Identity Provider Integration:**
|
||||
- OIDC providers (Authelia, Authentik, Pocket ID, Keycloak documented) - SSO login/callback flow
|
||||
- SDK/Client: `openid-client` used in `backend/src/routes/oidc.ts`
|
||||
- Auth: `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_REDIRECT_URI` validated in `backend/src/plugins/env.ts`
|
||||
|
||||
**Messaging/Notifications:**
|
||||
- SMTP providers - transactional reminder/test emails
|
||||
- SDK/Client: `nodemailer` in `backend/src/services/notifications/delivery.ts`
|
||||
- Auth: `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS` or `SMTP_TOKEN`, `SMTP_FROM`, `SMTP_SECURE`
|
||||
- Push endpoints via Shoutrrr-compatible URL parsing
|
||||
- SDK/Client: native `fetch` in `backend/src/routes/settings.ts` (`sendShoutrrrNotification`)
|
||||
- Auth: URL-embedded creds/token per provider and optional basic auth extracted/sanitized in code
|
||||
- Explicit external push provider endpoints used directly:
|
||||
- `https://api.pushover.net/1/messages.json` in `backend/src/routes/settings.ts`
|
||||
- `https://api.telegram.org` in `backend/src/routes/settings.ts`
|
||||
|
||||
## Data Storage
|
||||
|
||||
**Databases:**
|
||||
- SQLite (file-based, local persistent volume)
|
||||
- Connection: `DATA_DIR` (path resolution), optional `DOTENV_PATH` for env source
|
||||
- Client: `@libsql/client` + `drizzle-orm` in `backend/src/db/client.ts`
|
||||
- Migration pipeline:
|
||||
- SQL migration artifacts in `backend/drizzle/*.sql`
|
||||
- Runtime migration/alter execution in `backend/src/db/client.ts` and `backend/src/db/migration-utils.ts`
|
||||
|
||||
**File Storage:**
|
||||
- Local filesystem only
|
||||
- Backend data root resolved by `backend/src/db/path-utils.ts`
|
||||
- Image/static user files served from `/images` in `backend/src/index.ts`
|
||||
- Compose bind mount `./data:/app/data` in `docker-compose.yml`
|
||||
|
||||
**Caching:**
|
||||
- In-process memory cache only for selected integration data
|
||||
- OIDC discovery config cache in `backend/src/routes/oidc.ts` (`oidcConfig`)
|
||||
- EMA catalog snapshot + refresh promise in `backend/src/services/medication-enrichment.ts`
|
||||
- No external cache service detected (no Redis/Memcached dependency in package manifests)
|
||||
|
||||
## Authentication & Identity
|
||||
|
||||
**Auth Provider:**
|
||||
- Custom session/JWT auth with optional OIDC SSO extension
|
||||
- Implementation: Fastify cookie + JWT plugin, refresh token table, API key hashing in `backend/src/plugins/auth.ts`, `backend/src/routes/auth.ts`, `backend/src/plugins/jwt.ts`, `backend/src/routes/oidc.ts`
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
**Error Tracking:**
|
||||
- None detected for third-party SaaS error tracking (no Sentry/Rollbar/etc. dependencies)
|
||||
|
||||
**Logs:**
|
||||
- Structured app logging via Fastify/Pino in `backend/src/index.ts`
|
||||
- Pretty logging in dev through `pino-pretty` (`backend/package.json`, logger setup in `backend/src/index.ts`)
|
||||
- Frontend/nginx log behavior controlled through env and `frontend/nginx-entrypoint.sh` (documented in `.env.example`)
|
||||
|
||||
## CI/CD & Deployment
|
||||
|
||||
**Hosting:**
|
||||
- Container image publishing to GitHub Container Registry (`ghcr.io`) in `.github/workflows/docker-build.yml`
|
||||
- Runtime deployment model is self-hosted Docker Compose stack (`docker-compose.yml`)
|
||||
|
||||
**CI Pipeline:**
|
||||
- GitHub Actions for lint/type/test (`.github/workflows/test.yml`)
|
||||
- Playwright E2E job (`.github/workflows/e2e.yml`)
|
||||
- Docker build/push and optional release automation (`.github/workflows/docker-build.yml`)
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
**Required env vars:**
|
||||
- Core runtime: `PORT`, `CORS_ORIGINS`, `LOG_LEVEL`, `TZ` (`backend/src/plugins/env.ts`, `.env.example`)
|
||||
- Auth when enabled: `AUTH_ENABLED=true` with `JWT_SECRET`, `REFRESH_SECRET`, `COOKIE_SECRET` (`backend/src/plugins/env.ts`)
|
||||
- OIDC when enabled: `OIDC_ENABLED=true` with issuer/client/redirect vars (`backend/src/plugins/env.ts`)
|
||||
- Email notifications: `SMTP_HOST`, `SMTP_USER`, plus pass/token and sender config (`backend/src/services/notifications/delivery.ts`, `.env.example`)
|
||||
- Data location: `DATA_DIR` used by DB path resolver (`backend/src/db/path-utils.ts`)
|
||||
|
||||
**Secrets location:**
|
||||
- Local runtime env file `.env` (present in repository root; values not inspected)
|
||||
- CI secrets managed by GitHub Actions secret store (e.g., `${{ secrets.GITHUB_TOKEN }}` in `.github/workflows/docker-build.yml`)
|
||||
|
||||
## Webhooks & Callbacks
|
||||
|
||||
**Incoming:**
|
||||
- OIDC callback endpoint: `/auth/oidc/callback` in `backend/src/routes/oidc.ts`
|
||||
- No inbound third-party webhook receiver route detected in backend routes
|
||||
|
||||
**Outgoing:**
|
||||
- Outbound HTTP notifications to webhook-style targets from `sendShoutrrrNotification` in `backend/src/routes/settings.ts`
|
||||
- Provider-specific outgoing callbacks/APIs:
|
||||
- Pushover API endpoint
|
||||
- Telegram Bot API endpoint
|
||||
- Outbound SMTP delivery through configured mail host (`backend/src/services/notifications/delivery.ts`)
|
||||
|
||||
---
|
||||
|
||||
*Integration audit: 2026-04-30*
|
||||
@@ -0,0 +1,86 @@
|
||||
# Technology Stack
|
||||
|
||||
**Analysis Date:** 2026-04-30
|
||||
|
||||
## Languages
|
||||
|
||||
**Primary:**
|
||||
- TypeScript (ESM) - Backend and frontend application code in `backend/src/**/*.ts` and `frontend/src/**/*.{ts,tsx}`
|
||||
- SQL (SQLite migrations) - Schema evolution files in `backend/drizzle/*.sql`
|
||||
|
||||
**Secondary:**
|
||||
- CSS - UI styling in `frontend/src/**/*.css` and CSS modules such as `frontend/src/features/schedule/TimelineSurface.module.css`
|
||||
- YAML - CI/CD and compose configuration in `.github/workflows/*.yml`, `docker-compose.yml`, `docker-compose.dev.yml`
|
||||
- Shell - Container/runtime entrypoints in `backend/docker-entrypoint.sh`, `frontend/nginx-entrypoint.sh`
|
||||
|
||||
## Runtime
|
||||
|
||||
**Environment:**
|
||||
- Node.js 22 runtime baseline (`node:22-slim` in `backend/Dockerfile`, `frontend/Dockerfile`; `actions/setup-node@v6` with `node-version: '22'` in `.github/workflows/test.yml` and `.github/workflows/e2e.yml`)
|
||||
|
||||
**Package Manager:**
|
||||
- npm (scripts in root `package.json`, `backend/package.json`, `frontend/package.json`)
|
||||
- Lockfile: present (`backend/package-lock.json`, `frontend/package-lock.json` referenced by workflow cache in `.github/workflows/test.yml`)
|
||||
|
||||
## Frameworks
|
||||
|
||||
**Core:**
|
||||
- Fastify 5 (`fastify`, `@fastify/*` in `backend/package.json`; app bootstrap in `backend/src/index.ts`)
|
||||
- React 19 (`react`, `react-dom` in `frontend/package.json`; app entry in `frontend/src/main.tsx`)
|
||||
- Vite 8 (`vite` and `@vitejs/plugin-react` in `frontend/package.json`; config in `frontend/vite.config.ts`)
|
||||
- Drizzle ORM + libSQL client (`drizzle-orm`, `@libsql/client` in `backend/package.json`; DB init in `backend/src/db/client.ts`)
|
||||
- Mantine 8 UI system (`@mantine/*` in `frontend/package.json`; provider in `frontend/src/ui/providers/AppUiProvider.tsx`)
|
||||
|
||||
**Testing:**
|
||||
- Vitest 4 (`vitest`, `@vitest/coverage-v8` in backend/frontend package manifests; configs in `backend/vitest.config.ts`, `frontend/vitest.config.ts`)
|
||||
- Playwright (`@playwright/test` in `frontend/package.json`; configs in `frontend/playwright*.config.ts`; CI run in `.github/workflows/e2e.yml`)
|
||||
- Testing Library (`@testing-library/*` in `frontend/package.json`)
|
||||
|
||||
**Build/Dev:**
|
||||
- TypeScript compiler (`tsc` scripts in `backend/package.json` and frontend type-check via `frontend/package.json`)
|
||||
- TSX watcher for backend dev (`tsx watch src/index.ts` in `backend/package.json`)
|
||||
- Biome for lint/format (`biome.json`, lint/check scripts across package manifests)
|
||||
- Drizzle Kit for DB migration generation (`drizzle-kit` in `backend/package.json`, config in `backend/drizzle.config.ts`)
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
**Critical:**
|
||||
- `fastify` and `@fastify/*` - HTTP API runtime, security middleware, docs middleware (`backend/src/index.ts`)
|
||||
- `drizzle-orm` + `@libsql/client` - SQLite data access and migration execution (`backend/src/db/client.ts`)
|
||||
- `openid-client` + `jose` - OIDC SSO and token operations (`backend/src/routes/oidc.ts`, `backend/package.json`)
|
||||
- `nodemailer` - SMTP notification delivery (`backend/src/services/notifications/delivery.ts`)
|
||||
- `react`, `react-router-dom`, `@mantine/*` - SPA UI shell, routing, and component system (`frontend/src/main.tsx`, `frontend/src/App.tsx`)
|
||||
- `i18next` + `react-i18next` - Localization runtime (`frontend/src/i18n/index.ts`)
|
||||
|
||||
**Infrastructure:**
|
||||
- `dotenv` + `zod` - env loading/validation (`backend/src/plugins/env.ts`)
|
||||
- `sharp` - image processing pipeline support (`backend/package.json`, image route usage in medication flows)
|
||||
- `@fastify/swagger` + `@fastify/swagger-ui` - OpenAPI docs on `/docs` (`backend/src/index.ts`)
|
||||
|
||||
## Configuration
|
||||
|
||||
**Environment:**
|
||||
- Runtime env schema and validation in `backend/src/plugins/env.ts`
|
||||
- Example variable inventory in `.env.example`
|
||||
- Frontend proxy target via `BACKEND_URL` in `frontend/vite.config.ts` and compose files
|
||||
|
||||
**Build:**
|
||||
- Backend TS build config: `backend/tsconfig.json`
|
||||
- Frontend TS + Vite config: `frontend/tsconfig.json`, `frontend/tsconfig.node.json`, `frontend/vite.config.ts`
|
||||
- DB migration tooling config: `backend/drizzle.config.ts`
|
||||
- Quality tooling config: `biome.json`
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
**Development:**
|
||||
- Node.js 22 with npm for local runs (`backend/package.json`, `frontend/package.json` scripts)
|
||||
- Optional Docker Compose local stack (`docker-compose.dev.yml`)
|
||||
- Browser runtime for frontend and Playwright browser binaries for E2E (`frontend/package.json`, `.github/workflows/e2e.yml`)
|
||||
|
||||
**Production:**
|
||||
- Containerized deployment using prebuilt images from GHCR (`docker-compose.yml` references `ghcr.io/danielvolz/medassist-ng-backend:latest` and `ghcr.io/danielvolz/medassist-ng-frontend:latest`)
|
||||
- Backend persistent filesystem for SQLite/data in mounted `./data` (`docker-compose.yml`, DB path resolver in `backend/src/db/path-utils.ts`)
|
||||
|
||||
---
|
||||
|
||||
*Stack analysis: 2026-04-30*
|
||||
@@ -0,0 +1,138 @@
|
||||
# Codebase Structure
|
||||
|
||||
**Analysis Date:** 2026-04-30
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```
|
||||
medassist/
|
||||
├── frontend/ # React + Vite SPA, UI, hooks, page routes, frontend tests
|
||||
├── backend/ # Fastify API, domain services, DB schema/migrations, backend tests
|
||||
├── backend/drizzle/ # SQL migration files + drizzle meta journal
|
||||
├── docs/ # Product/ops docs and screenshots
|
||||
├── doku/ # Local-only working notes and reports (ignored)
|
||||
├── .github/ # CI workflows, agents, local skill/runtime metadata
|
||||
├── .planning/codebase/ # Generated codebase mapping documents
|
||||
├── data/ # Runtime/local SQLite backups and scheduler files
|
||||
└── package.json # Root workspace scripts for lint orchestration
|
||||
```
|
||||
|
||||
## Directory Purposes
|
||||
|
||||
**frontend/src:**
|
||||
- Purpose: Product UI and client-side app logic.
|
||||
- Contains: `pages/`, `components/`, `context/`, `hooks/`, `ui/`, `utils/`, `i18n/`, `test/`.
|
||||
- Key files: `frontend/src/main.tsx`, `frontend/src/App.tsx`, `frontend/src/context/AppContext.tsx`.
|
||||
|
||||
**backend/src:**
|
||||
- Purpose: HTTP API, auth, domain services, and persistence access.
|
||||
- Contains: `routes/`, `services/`, `plugins/`, `db/`, `utils/`, `test/`.
|
||||
- Key files: `backend/src/index.ts`, `backend/src/routes/medications.ts`, `backend/src/routes/planner.ts`, `backend/src/db/client.ts`.
|
||||
|
||||
**backend/drizzle:**
|
||||
- Purpose: SQL migration history for SQLite compatibility.
|
||||
- Contains: numbered migration files and `meta/_journal.json`.
|
||||
- Key files: `backend/drizzle/0000_init.sql`, `backend/drizzle/0014_add_user_settings_timezone.sql`.
|
||||
|
||||
**frontend/e2e:**
|
||||
- Purpose: Playwright end-to-end scenarios and fixtures.
|
||||
- Contains: browser tests + auth fixtures.
|
||||
- Key files: `frontend/e2e/fixtures/` and spec files under `frontend/e2e/`.
|
||||
|
||||
**docs + doku:**
|
||||
- Purpose: formal docs (`docs/`) and local-only work tracking (`doku/`).
|
||||
- Contains: behavior/spec docs, screenshots, local report/memory logs.
|
||||
- Key files: `docs/TECH_STACK.md`, `doku/memory_notes.md`, `doku/report.md`.
|
||||
|
||||
## Key File Locations
|
||||
|
||||
**Entry Points:**
|
||||
- `frontend/src/main.tsx`: Browser bootstrap; mounts providers and router.
|
||||
- `frontend/src/App.tsx`: Route graph and global modal/shell orchestration.
|
||||
- `backend/src/index.ts`: Fastify app setup + startup runtime.
|
||||
|
||||
**Configuration:**
|
||||
- `frontend/vite.config.ts`: Dev server, `/api` proxy rewrite, build-time constants.
|
||||
- `frontend/vitest.config.ts`: Frontend unit test config.
|
||||
- `backend/vitest.config.ts`: Backend unit/integration test config.
|
||||
- `backend/drizzle.config.ts`: Drizzle migration configuration.
|
||||
- `.gitignore`: Local-only/generated path policy (including `.planning/`, `doku/`, `data/`, coverage/test artifacts).
|
||||
|
||||
**Core Logic:**
|
||||
- `backend/src/routes/`: API contracts and request handlers.
|
||||
- `backend/src/services/`: Scheduler, notifications, medication helpers.
|
||||
- `backend/src/db/schema.ts`: Source-of-truth table definitions.
|
||||
- `frontend/src/context/`: Shared app orchestration state.
|
||||
- `frontend/src/pages/`: Screen-level composition.
|
||||
|
||||
**Testing:**
|
||||
- `frontend/src/test/`: Frontend unit/component tests.
|
||||
- `frontend/e2e/`: Playwright E2E tests.
|
||||
- `backend/src/test/`: Backend route/service/db tests.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
**Files:**
|
||||
- React components/pages use PascalCase: `frontend/src/pages/MedicationsPage.tsx`, `frontend/src/components/MedDetailModal.tsx`.
|
||||
- Hooks use `use*` naming: `frontend/src/hooks/useMedications.ts`, `frontend/src/hooks/useSettings.ts`.
|
||||
- Backend routes/services use kebab-case: `backend/src/routes/medication-enrichment.ts`, `backend/src/services/reminder-scheduler.ts`.
|
||||
- Migrations use numbered descriptive names: `backend/drizzle/0012_add_api_keys_and_package_amount_columns.sql`.
|
||||
|
||||
**Directories:**
|
||||
- Feature/layer folders are lowercase: `frontend/src/context`, `backend/src/services`.
|
||||
- Test directories stay colocated by runtime side (`frontend/src/test`, `backend/src/test`).
|
||||
|
||||
## Where to Add New Code
|
||||
|
||||
**New Feature:**
|
||||
- Primary code:
|
||||
- Frontend UI route/screen: `frontend/src/pages/` (compose from existing `components/`, `hooks/`, `ui/`).
|
||||
- Backend endpoint: `backend/src/routes/` + matching domain logic in `backend/src/services/`.
|
||||
- Persistence additions: `backend/src/db/schema.ts` plus migration updates in `backend/src/db/client.ts` and `backend/drizzle/`.
|
||||
- Tests:
|
||||
- Frontend unit/component: `frontend/src/test/`.
|
||||
- Backend unit/integration: `backend/src/test/`.
|
||||
- E2E flow: `frontend/e2e/`.
|
||||
|
||||
**New Component/Module:**
|
||||
- Implementation:
|
||||
- Shared UI primitive/layout: `frontend/src/ui/`.
|
||||
- Domain-specific UI component: `frontend/src/components/` (or nested feature folder).
|
||||
- Backend reusable domain behavior: `backend/src/services/`.
|
||||
|
||||
**Utilities:**
|
||||
- Shared helpers:
|
||||
- Frontend: `frontend/src/utils/`.
|
||||
- Backend: `backend/src/utils/`.
|
||||
- DB-specific helpers: `backend/src/db/` focused utility modules.
|
||||
|
||||
## Special Directories
|
||||
|
||||
**frontend/dist, backend/dist:**
|
||||
- Purpose: build output artifacts.
|
||||
- Generated: Yes.
|
||||
- Committed: No (`dist/` ignored in `.gitignore`).
|
||||
|
||||
**frontend/playwright-report, frontend/test-results, frontend/coverage, backend/coverage:**
|
||||
- Purpose: test artifacts/reports.
|
||||
- Generated: Yes.
|
||||
- Committed: No (ignored in `.gitignore`).
|
||||
|
||||
**data/:**
|
||||
- Purpose: runtime/local DB, reminder state, scheduler locks.
|
||||
- Generated: Yes.
|
||||
- Committed: No (`data/` ignored in `.gitignore`).
|
||||
|
||||
**doku/:**
|
||||
- Purpose: local work memory/reporting and internal notes.
|
||||
- Generated: Mixed (manual local notes + artifacts).
|
||||
- Committed: No (`doku/` ignored in `.gitignore`).
|
||||
|
||||
**.planning/codebase/:**
|
||||
- Purpose: generated architecture/stack/convention/concern maps for GSD planning/execution.
|
||||
- Generated: Yes.
|
||||
- Committed: No (`.planning/` ignored by policy in this workspace).
|
||||
|
||||
---
|
||||
|
||||
*Structure analysis: 2026-04-30*
|
||||
@@ -0,0 +1,203 @@
|
||||
# Testing Patterns
|
||||
|
||||
**Analysis Date:** 2026-04-30
|
||||
|
||||
## Test Framework
|
||||
|
||||
**Runner:**
|
||||
- Vitest 4.x for unit/integration tests in both packages:
|
||||
- Frontend config: `frontend/vitest.config.ts`
|
||||
- Backend config: `backend/vitest.config.ts`
|
||||
- Config evidence:
|
||||
- Frontend uses `environment: 'jsdom'` with React setup file `frontend/src/test/setup.ts`.
|
||||
- Backend uses `environment: 'node'` with setup file `backend/src/test/setup.ts`.
|
||||
|
||||
**Assertion Library:**
|
||||
- Vitest `expect`.
|
||||
- Frontend extends DOM assertions via `@testing-library/jest-dom` in `frontend/src/test/setup.ts`.
|
||||
|
||||
**Run Commands:**
|
||||
```bash
|
||||
cd frontend && npm test # Watch/unit tests
|
||||
cd frontend && npm run test:run # CI-style frontend run
|
||||
cd frontend && npm run test:coverage # Frontend coverage
|
||||
cd backend && npm test # Watch/unit tests
|
||||
cd backend && npm run test:run # CI-style backend run
|
||||
cd backend && npm run test:coverage # Backend coverage
|
||||
cd frontend && npm run test:e2e # Stable Playwright suite
|
||||
cd frontend && npm run test:e2e:all # Cross-browser Playwright suite
|
||||
```
|
||||
|
||||
## Test File Organization
|
||||
|
||||
**Location:**
|
||||
- Backend unit/integration tests are in `backend/src/test/*.test.ts`.
|
||||
- Frontend unit/component/hook/context tests are in `frontend/src/test/**`.
|
||||
- Browser E2E tests are in `frontend/e2e/*.spec.ts`.
|
||||
|
||||
**Naming:**
|
||||
- Unit/integration: `*.test.ts` or `*.test.tsx` (for example `backend/src/test/routes-real.test.ts`, `frontend/src/test/components/MedicationDialogs.test.tsx`).
|
||||
- E2E: `*.spec.ts` (for example `frontend/e2e/medication-edit.spec.ts`).
|
||||
|
||||
**Structure:**
|
||||
```
|
||||
backend/src/test/
|
||||
setup.ts
|
||||
*.test.ts
|
||||
|
||||
frontend/src/test/
|
||||
setup.ts
|
||||
App.test.tsx
|
||||
components/*.test.tsx
|
||||
context/*.test.tsx
|
||||
hooks/*.test.ts
|
||||
pages/*.test.tsx
|
||||
utils/*.test.ts
|
||||
|
||||
frontend/e2e/
|
||||
auth.setup.ts
|
||||
fixtures/index.ts
|
||||
*.spec.ts
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
**Suite Organization:**
|
||||
```typescript
|
||||
describe("Feature Area", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("handles expected behavior", async () => {
|
||||
// arrange
|
||||
// act
|
||||
// assert
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
```
|
||||
Pattern evidence: `frontend/src/test/components/MobileEditModal.test.tsx`, `backend/src/test/planner.test.ts`.
|
||||
|
||||
**Patterns:**
|
||||
- Setup pattern:
|
||||
- Frontend centralizes browser mocks in `frontend/src/test/setup.ts` (fetch, localStorage, clipboard, history, i18n).
|
||||
- Backend provides reusable app/database factories in `backend/src/test/setup.ts` (`buildTestApp`, `createTestUser`, `createTestMedication`).
|
||||
- Teardown pattern:
|
||||
- `afterAll` closes Fastify app and DB clients (`backend/src/test/planner.test.ts`, `backend/src/test/integration.test.ts`).
|
||||
- Assertion pattern:
|
||||
- Route tests assert both HTTP status and response body (`backend/src/test/routes-real.test.ts`).
|
||||
- UI tests assert presence and behavior via Testing Library role/test-id queries (`frontend/src/test/components/MedicationDialogs.test.tsx`).
|
||||
|
||||
## Mocking
|
||||
|
||||
**Framework:**
|
||||
- Vitest mocks (`vi.mock`, `vi.fn`, `vi.hoisted`, `vi.stubGlobal`).
|
||||
|
||||
**Patterns:**
|
||||
```typescript
|
||||
const { testClient, testDb } = vi.hoisted(() => {
|
||||
const client = createClient({ url: ":memory:" });
|
||||
const db = drizzle(client);
|
||||
return { testClient: client, testDb: db };
|
||||
});
|
||||
|
||||
vi.mock("../db/client.js", () => ({
|
||||
db: testDb,
|
||||
migrationsReady: Promise.resolve(),
|
||||
}));
|
||||
```
|
||||
Pattern evidence: `backend/src/test/integration.test.ts`, `backend/src/test/routes-real.test.ts`.
|
||||
|
||||
```typescript
|
||||
vi.mock("../../components/ConfirmModal", () => ({
|
||||
ConfirmModal: ({ onConfirm }) => <button onClick={onConfirm}>confirm</button>,
|
||||
}));
|
||||
```
|
||||
Pattern evidence: `frontend/src/test/components/MedicationDialogs.test.tsx`.
|
||||
|
||||
**What to Mock:**
|
||||
- External side effects and infrastructure boundaries: SMTP/nodemailer, fetch network calls, auth/plugin env modules, browser APIs.
|
||||
- Component dependencies in focused unit tests (replace heavy children with stubs).
|
||||
|
||||
**What NOT to Mock:**
|
||||
- Core business behavior under direct test (route handlers in route tests, hook logic in hook tests, E2E API + UI flow in Playwright).
|
||||
|
||||
## Fixtures and Factories
|
||||
|
||||
**Test Data:**
|
||||
```typescript
|
||||
const userId = await createTestUser(client, { username: "testuser" });
|
||||
const medId = await createTestMedication(client, { userId, name: "Test Medication" });
|
||||
```
|
||||
Pattern evidence: `backend/src/test/setup.ts`, used by `backend/src/test/medications.test.ts`.
|
||||
|
||||
```typescript
|
||||
export const test = base.extend({
|
||||
page: async ({ page }, use) => {
|
||||
await applyVideoSafetyMode(page);
|
||||
await setupAuthMeMock(page);
|
||||
await use(page);
|
||||
},
|
||||
});
|
||||
```
|
||||
Pattern evidence: `frontend/e2e/fixtures/index.ts`.
|
||||
|
||||
**Location:**
|
||||
- Backend factories/utilities: `backend/src/test/setup.ts`.
|
||||
- Frontend E2E shared fixtures and API helpers: `frontend/e2e/fixtures/index.ts`.
|
||||
|
||||
## Coverage
|
||||
|
||||
**Requirements:**
|
||||
- Frontend global thresholds in `frontend/vitest.config.ts`: lines/functions/branches/statements = 75.
|
||||
- Backend global thresholds in `backend/vitest.config.ts`: lines 60, functions 65, branches 50, statements 60.
|
||||
|
||||
**View Coverage:**
|
||||
```bash
|
||||
cd frontend && npm run test:coverage
|
||||
cd backend && npm run test:coverage
|
||||
```
|
||||
|
||||
## Test Types
|
||||
|
||||
**Unit Tests:**
|
||||
- Component/hook/utils tests in `frontend/src/test/**`.
|
||||
- Utility/service route-unit style tests in `backend/src/test/*.test.ts`.
|
||||
|
||||
**Integration Tests:**
|
||||
- Backend route interaction and multi-route behavior tests in files like:
|
||||
- `backend/src/test/integration.test.ts`
|
||||
- `backend/src/test/routes-real.test.ts`
|
||||
|
||||
**E2E Tests:**
|
||||
- Playwright used with setup project and browser projects (`frontend/playwright.base.config.ts`).
|
||||
- Auth/session and API seeding helpers in `frontend/e2e/fixtures/index.ts`.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
**Async Testing:**
|
||||
```typescript
|
||||
await waitFor(() => {
|
||||
expect(mockFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
```
|
||||
Pattern evidence: `frontend/src/test/context/AppContext.test.tsx`.
|
||||
|
||||
```typescript
|
||||
const response = await app.inject({ method: "GET", url: "/settings" });
|
||||
expect(response.statusCode).toBe(200);
|
||||
```
|
||||
Pattern evidence: `backend/src/test/routes-real.test.ts`.
|
||||
|
||||
**Error Testing:**
|
||||
```typescript
|
||||
const response = await app.inject({ method: "POST", url: "/planner/send-email", payload: { rows: [] } });
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.json()).toEqual({ error: "Missing planner data" });
|
||||
```
|
||||
Pattern evidence: `backend/src/test/planner.test.ts`.
|
||||
|
||||
---
|
||||
|
||||
*Testing analysis: 2026-04-30*
|
||||
Reference in New Issue
Block a user