Clinical registry web app: patient passport, disease-phase matrix (pharmacotherapy, remission, mental status, rating scales), roles, audit and authentication.
Domain note: the field registry this project is built on originates from the author's PhD (candidate of sciences) research on depressive disorders — domain expertise + engineering, a combination medtech teams value well above clean code alone.
Docs:
- Еntry point →
docs/en/architecture-overview.md - Русская версия этого файла →
README.ru.md
This registry's data model is grounded in real clinical research: the author holds a PhD in Psychiatry (Bekhterev Institute, St. Petersburg, 2015) for a longitudinal study on recurrent depressive disorder in late-life patients.
The phase-based tracking structure, remission-quality calculations, and clinical scale integration (HAM-D, etc.) in this codebase directly reflect the data collection methodology used in that research — this isn't a synthetic domain model, it's informed by actually running a clinical dataset.
- Next.js 16 (App Router, React 19, Server Actions) + TypeScript (strict)
- Cloudflare Pages/Workers (
@opennextjs/cloudflare), DB — Cloudflare D1 (SQLite) - Tailwind CSS 4, zustand, @tanstack/react-virtual, zod
- FSD architecture:
app/(pages) +src/{app,widgets,features,entities,shared} - Tests — Vitest (unit + integration against local D1)
npm ci
npm run db:restart # recreate local D1 from registry + manual migrations
npm run user:create # first user automatically becomes admin
npm run dev # http://localhost:3000Login: /login (email + password from user:create). Protected pages redirect
unauthenticated users to /login (middleware + requireUser()).
| Command | Purpose |
|---|---|
npm run dev |
Next dev server (local D1; bindings via initOpenNextCloudflareForDev) |
npm run dev:cf |
dev in Cloudflare runtime (worker.js) |
npm run build |
production build |
npm run gen:d1 |
generate D1 migrations from field registry (src/shared/config/registry) |
npm run db:restart |
full local DB reset (baseline + manual migrations 0002–0007) |
npm run db:restart:remote |
full prod D1 reset: dump → DROP tables → migrations; :seed variant adds demo data |
npm run db:migrate:* |
apply migrations (--local / --remote) |
npm run user:create |
create a user (interactive/flags; first one is admin) |
npm run user:delete:remote |
delete users from the prod D1 (--list, --all --keep, --detach; confirm with "prod") |
npm run user:delete:remote:all |
wipe every user, admins included (then re-create an admin via user:create:remote) |
npm run seed:demo |
demo data (local; seed:demo:remote — remote D1, confirm with "prod") |
npm run test |
unit tests (Vitest) |
npm run test:db |
integration tests against local D1 |
npm run lint |
ESLint |
npm run steiger |
FSD layer check |
npm run deploy |
deploy to Cloudflare |
- Own D1-backed sessions (PBKDF2, token only in HttpOnly cookie), 12 h TTL (sliding), absolute 7-day cap from login.
- Roles:
admin(user management,/admin/users),clinician(read/write),readonly(read-only). - Row-level access: per-user
data_scopeswitch — "sees all" / "own site" / "only assigned patients" (seedocs/ru/auth.md). - Password change at
/change-password; rate-limit: 5 wrong passwords → 15 min lock. - Invites: admin issues a one-time
/invite/<token>link (7 days).
Docs are kept as two mirrored sets: docs/en/ (English) and docs/ru/ (Russian
originals). Start with the entry point.
docs/en/architecture-overview.md— entry point: key architecture decisions, security summary, demo boundariesdocs/diagrams/c4-overview.en.svg— C4 diagram (System Context + Container)docs/en/rdd-v1.md— core: architecture, field registry, data schemadocs/en/auth.md— authentication, roles, row-level access (data_scope)docs/en/matrix.md— Matrix widget: virtualization, CAS conflicts, auditdocs/en/export.md— de-identified export (csv/json/xlsx)docs/en/data-dictionary.md— autogenerated dictionary (page/data-dictionary)docs/en/schema-evolution.md— schema evolution without migrations: protocol versioning viaregistry_versions+ per-recordregistry_versionin the generated baseline0001_init.sql; schema changes only via DB reset (npm run db:restart); export carries per-rowregistry_version+meta.registryVersions;/data-dictionaryflagsdeprecated_sincefieldsdocs/en/roadmap.md— stage plan (spec-stage-1..4.md, all implemented)docs/en/threat-model.md— STRIDE threat model mapped to the security summarydocs/en/nfr.md— non-functional requirements: Availability, RTO/RPO, performance budgetsdocs/en/deployment.md— deployment and operations: Cloudflare Workers/D1, manual deploy, the domain (Custom Domain), production DB (migrations, reset, dumps), GitHub Actions, rollback and troubleshootingdocs/en/i18n.md— localization strategy (problem → options → decision → escalation threshold)docs/en/consent.md— patient consent: date/version fixed at inclusion, consent text out of scope, escalation thresholdsdocs/ru/— the same set as Russian originals (i18n.mdexists only in EN)
- UI chrome (buttons, menus, auth forms) — switchable RU/EN via cookie
rdd_locale<LocaleSwitcher />in the user menu (seedocs/en/i18n.md).
RegistryField.label—{ ru, en }everywhere (was partially RU-only); resolved viafieldLabel(field, locale)withDEFAULT_LOCALE(en) fallback.RegistryOption.label—{ ru, en } | string; resolved viaoptionLabel(opt, locale).- Deliberately out of scope (documented in
docs/en/i18n.md): translating historical DB values (codes, not text), clinically validated translation of rating scales, RTL, plural rules.
- CI: GitHub Actions — lint + tsc + steiger + unit/integration tests.
- Manual deploy:
npm run deploy(ornpm run previewfor local CF runtime). - Cloudflare targets (
wrangler.jsonc): workerrdd, custom domainrdd.ux42.studio, D1 databaserdd(bindingDB); no secrets required. - Full walkthrough of deployment, CI, the domain and production DB work:
docs/en/deployment.md.