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