From b3a626d83c9fbd4e0d2eabdb541ab00cd2e6c420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 5 Jun 2026 15:21:18 +0200 Subject: [PATCH] docs: add navigational READMEs to key code directories Add a README to src, src/lib, src/app, src/components, prisma, scripts, and e2e so a technical visitor browsing the tree gets oriented: what lives in each directory, the most important files, the local conventions, and pointers to related dirs and docs. Co-Authored-By: Claude Opus 4.8 (1M context) --- e2e/README.md | 21 +++++++++++++++++++++ prisma/README.md | 28 ++++++++++++++++++++++++++++ scripts/README.md | 31 +++++++++++++++++++++++++++++++ src/README.md | 26 ++++++++++++++++++++++++++ src/app/README.md | 25 +++++++++++++++++++++++++ src/components/README.md | 24 ++++++++++++++++++++++++ src/lib/README.md | 33 +++++++++++++++++++++++++++++++++ 7 files changed, 188 insertions(+) create mode 100644 e2e/README.md create mode 100644 prisma/README.md create mode 100644 scripts/README.md create mode 100644 src/README.md create mode 100644 src/app/README.md create mode 100644 src/components/README.md create mode 100644 src/lib/README.md diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 000000000..fa2e35d96 --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,21 @@ +# `e2e/` + +End-to-end browser tests. Playwright 1.60 drives a production build of the app against a real PostgreSQL, with `@axe-core/playwright` for accessibility assertions. This is the heaviest test tier; CI runs it on every PR, and locally it is worth running when a change touches a UI flow. + +## Running + +```bash +pnpm e2e +``` + +The Playwright config (`../playwright.config.ts`) builds the app (`next build`) and brings up Postgres before the specs run; `setup/global-setup.ts` seeds the auth + fixture state. The suite is not part of the fast local gate (`typecheck` / `lint` / `test`) — it surfaces at PR CI, so expect responsive/wizard/nav reworks to flag here first. + +## Layout + +- **`setup/`** — `global-setup.ts` (auth + DB seeding), `test-helpers.ts` (shared login + navigation helpers). +- **`utils/`** — fixtures such as `mock-dashboard-snapshot.ts`. +- **`*.spec.ts`** — one spec per flow: auth/login/redirect, dashboard, charts, the medication-wizard cadences (`medications-wizard-*`), insights generation + scroll restoration, doctor report, settings, mobile-viewport consistency, locale switch, accessibility (`a11y.spec.ts`), and the `v1427-*` regression set. + +## Convention + +Assert against stable `data-*` attributes (`data-slot`, `data-display-step`, root display attrs), not viewport-dependent visible text. Responsive `sm:hidden` UI changes break `getByText` on desktop while the underlying element is still present — anchoring on data attributes keeps the specs stable across breakpoints. See [`../CLAUDE.md`](../CLAUDE.md). diff --git a/prisma/README.md b/prisma/README.md new file mode 100644 index 000000000..584bebcd6 --- /dev/null +++ b/prisma/README.md @@ -0,0 +1,28 @@ +# `prisma/` + +The database layer. HealthLog runs PostgreSQL 16 via Prisma 7. This directory holds the single schema file and the forward-only migration history; the generated client is emitted elsewhere. + +## Contents + +- **`schema.prisma`** — 61 models, one file. `cuid()` primary keys, `snake_case` columns via `@map(...)`, encrypted columns mostly suffixed `*Encrypted` (search the file; there are 24 encrypted column references). The `generator client` block emits to `../src/generated/prisma` (imported as `@/generated/prisma`). +- **`migrations/`** — numbered, forward-only SQL migrations (`0001_…` upward). `migration_lock.toml` pins the provider to `postgresql`. + +## Migration workflow + +```bash +# author a migration from a schema edit +pnpm dlx prisma migrate dev --name + +# regenerate the client after any schema change +pnpm dlx prisma generate +``` + +Number a new migration with the next free prefix. The production entrypoint runs `prisma migrate deploy` on boot, so every migration must apply cleanly forward — there is no down-migration path. Always include the migration file in the same change as the schema edit. + +## Conventions + +- Encryption is fail-closed: `*Encrypted` columns are written through `@/lib/crypto` (AES-256-GCM, versioned key ids); never store plaintext in an encrypted column. Key rotation is `scripts/rotate-encryption-key.ts` + `docs/ops/encryption-key-rotation.md`. +- The generated client under `src/generated/prisma` is build output — do not edit it. +- Schema-touching changes that affect the API surface must regenerate `docs/api/openapi.yaml` (see [`../CLAUDE.md`](../CLAUDE.md)). + +Related: [`../src/lib/README.md`](../src/lib/README.md) (`db.ts`, `rollups/`), [`../docs/ops/`](../docs/ops/) (migrations + backup runbooks). diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 000000000..5fcadab54 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,31 @@ +# `scripts/` + +One-shot operator and build scripts. These are not part of the running application — they are run by hand during a deploy, a backfill, or a release, or wired into CI. Recurring work belongs on pg-boss (`src/lib/jobs/`), not here. + +## How to run them + +```bash +pnpm dlx tsx scripts/.ts +``` + +Use `pnpm dlx tsx`, not bare `pnpm tsx`. The production standalone image strips `tsx`, so the bare invocation fails inside the container. `scripts/` is excluded from the project typecheck. + +## What lives here + +- **`generate-openapi.ts`** — emits `docs/api/openapi.yaml` from the registered Zod schemas (`pnpm openapi:generate`). +- **`check-openapi.ts`** — compares a freshly generated spec against the committed YAML; CI fails on drift (`pnpm openapi:check`). +- **`check-env.ts`** — pre-deploy env-var sanity check against `env-manifest.json` (`pnpm check-env`); wired into CI. +- **`assert-deploy.ts`** — post-deploy `/api/version` assertion. +- **`rotate-encryption-key.ts`** — AES-256-GCM key rotation; see `docs/ops/encryption-key-rotation.md`. +- **`restore-backup.ts`** — restore an off-host AES-GCM backup from an S3-compatible bucket. +- **`backfill-rollups.ts`**, **`drain-per-sample-cumulative.ts`**, **`backfill-mood-note-column.ts`** — historical data backfills (note: the boot-time `rollup-full-backfill` queue handles new accounts automatically; the CLI is the manual fallback). +- **`seed-demo.ts`** + **`seed-demo-v15.sql`** — populate a demo tenant. +- **`test-notifications.ts`** — exercise the notification dispatcher + reminder check. +- **`fetch-geolite2.sh`** — download the MaxMind GeoLite2 MMDBs (needs a licence key) before `docker build`. +- **`install-hooks.sh`** — install the local git hooks. +- **`generate-sw-version.mjs`**, **`print-bundle-report.mjs`** — service-worker version stamp and bundle-size report. +- **`i18n/`** — locale tooling; **`__tests__/`** — tests for the scripts above. + +## Conventions + +See [`../CLAUDE.md`](../CLAUDE.md) "Tests and commands" + "Self-hosting gotchas". Operator runbooks that drive these scripts live in [`../docs/ops/`](../docs/ops/). diff --git a/src/README.md b/src/README.md new file mode 100644 index 000000000..1f460f2e5 --- /dev/null +++ b/src/README.md @@ -0,0 +1,26 @@ +# `src/` + +Application source. HealthLog is a Next.js 16 App Router project (`output: "standalone"`, React 19, TypeScript strict). The path alias `@/*` resolves to this directory. Everything a request touches lives under here; the layout below follows the request flow rather than alphabetical order. + +## Request flow + +``` +client → src/proxy.ts → src/app//route.ts → src/lib/api-handler.ts → handler body → src/lib/api-response.ts → client +``` + +- **`proxy.ts`** — the HTTP edge (Next.js 16's renamed `middleware`). Public-path allowlist, demo-mode mutation block, server-side onboarding redirect, worker-only refusal, `x-request-id` + CSP nonce generation, every security header. +- **`app/`** — App Router pages (RSC by default) and API routes. Every API route wraps its handler in `apiHandler`. See [`app/README.md`](app/README.md). +- **`lib/`** — the server/runtime toolkit: `apiHandler`, auth, crypto, rollups, AI, notifications, pg-boss jobs, integrations, rate limiting. See [`lib/README.md`](lib/README.md). +- **`components/`** — React components (shadcn/ui new-york, kebab-case files). See [`components/README.md`](components/README.md). + +## Other directories + +- **`hooks/`** — shared React hooks (`use-` prefix, kebab-case). +- **`types/`** — shared TypeScript declarations. +- **`generated/prisma/`** — the generated Prisma client (do not edit; regenerated by `prisma generate`). Imported as `@/generated/prisma`. +- **`instrumentation.ts`** — Next.js startup hook; boots the pg-boss worker registry and boot-time backfill queues. +- **`__tests__/`** — cross-cutting unit tests; most tests live in `__tests__/` folders next to the code they cover. + +## Conventions + +See [`../CLAUDE.md`](../CLAUDE.md) for the full set. The load-bearing ones: RSC by default (`"use client"` only when a hook/state/browser API needs it); every API route wraps `apiHandler` and parses its body with Zod `safeParse`; `userId` is always narrowed from session or Bearer, never accepted as a body field; TanStack Query keys come only from `src/lib/query-keys.ts`; outbound HTTP goes through `src/lib/safe-fetch.ts`. diff --git a/src/app/README.md b/src/app/README.md new file mode 100644 index 000000000..889bc66c4 --- /dev/null +++ b/src/app/README.md @@ -0,0 +1,25 @@ +# `src/app/` + +The Next.js App Router tree: the page surfaces a logged-in user sees and the REST API under `api/`. Server components are the default; `"use client"` is added only where a hook, state, or browser API needs it. Routing is file-based — a `page.tsx` is a page, a `route.ts` is an API endpoint. + +## API routes (`api/`) + +Every endpoint wraps its handler in `apiHandler` (`@/lib/api-handler`), parses any body with Zod `safeParse`, narrows `userId` from the session or Bearer token (never from the body), and returns the `{ data, error, meta? }` envelope. `requireAdmin()` is cookie-only, so `api/admin/` is unreachable by Bearer token. Notable groups: + +- **`auth/`** — login, register, password, passkey (WebAuthn), refresh, Codex device-OAuth. +- **`measurements/`, `mood-entries/`, `medications/`, `sleep/`** — the core health-data CRUD + batch/bulk ingest. +- **`ingest/`** — external Bearer-token ingest surfaces. +- **`insights/`, `analytics/`, `dashboard/`** — AI insights, the Coach SSE stream, trend summaries, the first-paint dashboard snapshot. +- **`withings/`, `whoop/`, `fitbit/`, `integrations/`, `sync/`** — OAuth connect/sync/webhook surfaces and the offline delta feed. +- **`fhir/`, `share-links/`, `doctor-report/`, `export/`, `import/`** — clinician-facing FHIR R4 read API, shareable records, report + data export. +- **`notifications/`, `devices/`, `send/`, `telegram/`** — channel registration, device tokens, dispatch, webhook intake. +- **`admin/`** — operator panel APIs (cookie-only). +- **`health/`, `version/`, `meta/`, `internal/`** — health check, build-version probe, runtime metadata, worker-internal routes. + +## Page surfaces + +`measurements/`, `medications/`, `mood/`, `insights/`, `achievements/`, `notifications/`, `settings/`, `onboarding/`, `admin/`, plus the public `about/`, `privacy/`, `bugreport/`, the share-record route `c/`, and the root dashboard (`page.tsx`). Shared shell + chrome lives in `layout.tsx`; `error.tsx` / `global-error.tsx` / `not-found.tsx` / `loading.tsx` cover the framework boundaries. `layout.tsx` holds the one nonce-bound `dangerouslySetInnerHTML` (theme bootstrap). + +## Conventions + +See [`../../CLAUDE.md`](../../CLAUDE.md). The request-flow walk in `.planning/codebase/arch.md` annotates each layer with file:line citations. Related dirs: handlers compose [`../lib/`](../lib/README.md); pages render [`../components/`](../components/README.md). diff --git a/src/components/README.md b/src/components/README.md new file mode 100644 index 000000000..6adf81477 --- /dev/null +++ b/src/components/README.md @@ -0,0 +1,24 @@ +# `src/components/` + +React components. The UI primitives are shadcn/ui (new-york style, zinc base) over Radix + Tailwind 4 + Lucide; everything above the primitives is feature-grouped by surface. Components are server components unless they need a hook, state, or browser API, in which case they open with `"use client"`. Charts use Recharts 3 — replacing the chart library requires explicit approval. + +## Primitives and shell + +- **`ui/`** — shadcn/ui primitives (button, dialog, sheet, table, …). Generated by the shadcn CLI; keep edits minimal and on-style. +- **`layout/`** — the app shell: sidebar, topbar, bottom nav. +- **`providers.tsx`, `app-settings-provider.tsx`, `version-poller.tsx`** — top-level context providers and the self-healing stale-shell poller. +- **`i18n/`** — translation context + locale switcher. + +## Feature groups + +- **`insights/`** — insight status, assessment cards, Coach surface, advisor cards (the largest group). +- **`medications/`** — medication cards, the schedule wizard, intake timeline, detail history. +- **`settings/`** — the section components behind each settings page. +- **`measurements/`, `mood/`, `dashboard/`** — entry forms, lists, dashboard tiles. +- **`charts/`** — Recharts wrappers. +- **`monitoring/`** — Umami / GlitchTip client bootstrap. +- **`gamification/`, `targets/`, `comparison/`, `doctor-report/`, `clinician/`, `onboarding/`, `admin/`** — the remaining per-surface groups. + +## Conventions + +Kebab-case filenames; React component exports stay PascalCase regardless. (A handful of PascalCase-filename outliers under `medications/` + `onboarding/` are pre-existing drift, noted in [`../../CLAUDE.md`](../../CLAUDE.md).) Every `t()` call needs a matching key in `messages/en.json` — guarded by `i18n-call-site-coverage.test.ts`. Data reads go through TanStack Query with keys from `@/lib/query-keys` and unwrap `(await res.json()).data`. Tests live in [`__tests__/`](__tests__/). diff --git a/src/lib/README.md b/src/lib/README.md new file mode 100644 index 000000000..f46fde331 --- /dev/null +++ b/src/lib/README.md @@ -0,0 +1,33 @@ +# `src/lib/` + +The server and runtime toolkit. Everything that is not a page, component, or route handler lives here: the API kit, auth, crypto, the rollup tier, AI providers, notifications, background jobs, and the integration clients. Route handlers and RSC pages compose these modules; the modules never reach back into `app/`. + +## Core request kit + +- **`api-handler.ts`** — the `apiHandler` wrapper every route uses. Auth resolver (cookie OR Bearer), `requireAuth` / `requireAdmin` (admin is cookie-only by construction), idempotency plumbing, Wide-Event builder, GlitchTip forwarder. +- **`api-response.ts`** — the `{ data, error, meta? }` envelope: `apiSuccess` / `apiError`, `safeJson`, trusted-proxy IP resolver, `returnAllZodIssues` + `sanitiseZodIssues`. +- **`rate-limit.ts`** — Postgres-backed sliding-window limiter (single atomic upsert); multi-instance correct. +- **`idempotency.ts`** — user-scoped idempotency keys that refuse to cache secret-shaped responses. +- **`query-keys.ts`** — the only legal source of TanStack Query `queryKey` / `mutationKey` arrays (enforced by an ESLint rule). +- **`safe-fetch.ts`** — the one documented outbound-egress entry (`redirect: "manual"` + timeout; optional connect-time DNS-rebinding pin). Raw `fetch(` under `lib/` + `app/` is banned by ESLint. +- **`db.ts`** — the Prisma client singleton. +- **`crypto.ts`** — AES-256-GCM at rest with versioned key ids, fail-closed loader, rotation primitives. + +## Subsystems + +- **`auth/`** — Postgres sessions, secure-cookie flag policy, passkey (WebAuthn) + Argon2id password, per-device refresh-token rotation, HMAC Bearer tokens. +- **`ai/`** — multi-provider insight + Coach engine: `provider-chain.ts`, hand-rolled `{openai,anthropic,local,codex,mock}-client.ts`, `coach/`, `prompts/`, citation + confidence helpers. +- **`rollups/`** — DAY/WEEK/MONTH/YEAR pre-aggregations for measurements, mood, and medication compliance. Read-swap pattern: probe the rollup, fall back to live SQL on a coverage miss. +- **`notifications/`** — the dispatcher cascade (APNs → Telegram → ntfy → Web Push), per-channel `senders/`, retry policy, VAPID config. +- **`jobs/`** — pg-boss workers: reminders, insight pre-generation, backups, retention sweeps, integration backfills. Recurring work belongs here, not in `scripts/`. +- **`integrations/`**, **`withings/`**, **`whoop/`**, **`fitbit/`** — OAuth clients and sync services for the device providers; `sync/` holds the shared delta-feed plumbing. +- **`analytics/`** — trend calculations, cadence-aware medication `compliance.ts`, correlations. +- **`insights/`**, **`fhir/`**, **`doctor-report-*.ts`** — insight pipeline, FHIR R4 export, client-side doctor-report PDF. +- **`openapi/`** — Zod registry that is the source of truth for `docs/api/openapi.yaml`. +- **`logging/`**, **`observability/`**, **`monitoring/`** — Wide-Event transports and the central redaction denylists (redact at the egress boundary, not the call site). +- **`validations/`** — shared Zod schemas, including the SSRF `isPublicUrl` floor. +- **`i18n/`**, **`tz/`** + **`timezone.ts`**, **`format*.ts`** — translations, timezone helpers (Berlin for display, UTC in DB), locale formatting. + +## Conventions + +Kebab-case filenames. See [`../../CLAUDE.md`](../../CLAUDE.md) "Code conventions" and "Security-relevant patterns"; the critical-files map there cites the exact entry points. Most modules carry tests in a sibling `__tests__/`.