Source: ddx-documentation/adr/0005-frameworks.md
- **Status:** Accepted (2026-10-23) - **Deciders:** plan `ui-shell-rnd-workspace`, task 003 - **Research:** [../research/rq5-frameworks.md](/en/docs/research/rq5-frameworks) - **Binds:** plan invariant 8, G6.1, AC14.4 ## Context HMS (`dudoxx-ai-hms`, structure reference only) grew two markdown engines, three PDF viewers, five TipTap editors, four table stacks and a `zodResolver` / `standardSchemaResolver` split (`findings/hms-pattern-audit/components.md` §6, `routes.md` I4, I6, I10). Each came from a task that added "one more" library. This workspace runs many tasks in parallel. If each task edits `package.json` and `pnpm-lock.yaml`, the lock file collides. ## Decision 1. One library per concern, as picked in RQ5. Each pick has one recorded rejected option. 2. Task 009 installs the table below **once**, in one commit-ready change. 3. After task 009, **no task adds, removes or bumps a dependency**. A task that needs a package not in this table STOPS and reports. Adding one needs a new ADR (`adr/00NN-*.md`) that amends this table, and one dedicated install task. 4. Versions come from `~/.claude/ddx-topic-flows/VERSIONS.md` (by key) where a key exists. Otherwise from the scaffold baseline, or from `npm view` on 2026-10-23. Ranges are caret (`^`) unless the row says **exact**. 5. Banned packages (never install): `react-markdown` (AC14.4 — `streamdown` is the one markdown engine), `sonner` / any toast library (toast ban), `@heroicons/*` / `@fortawesome/*` (Lucide only), `tailwind-variants`, `inversify`, `reflect-metadata` in ddx-web, `ag-grid-*`, `swiper`, `motion` / `framer-motion`, `livekit-client` (WS14 NOT), `formik`, `dayjs`, `moment`, `axios` (fetch through the BFF factory, ADR 0006). ## Approved dependency table Apps: `ddx-web` (Next.js), `ddx-api` (NestJS), `contracts` (`packages/ddx-contracts`). Kind: `dep` = dependencies, `dev` = devDependencies. ### Stack majors (VERSIONS.md keys) | Package | App | Kind | Version | Version source | |---|---|---|---|---| | `next` | ddx-web | dep | `^16.4.3` | VERSIONS.md `next16` | | `react`, `react-dom` | ddx-web | dep | `^19.3.1` | VERSIONS.md `react19` | | `typescript` | ddx-web, contracts | dev | `^7.0.2` | VERSIONS.md `ts7` | | `typescript` (alias `npm:@typescript/typescript6@^6.0.2`) + `@typescript/native` (`npm:typescript@^7.0.2`) | ddx-api | dev | as shown | VERSIONS.md `ts7` Nest build note | | `tailwindcss`, `@tailwindcss/postcss` | ddx-web | dev | `^4.3.3` | VERSIONS.md `tw4` | | `next-intl` | ddx-web | dep | `^4.14.9` | VERSIONS.md `nextintl4` | | `shadcn` (CLI) | ddx-web | dev | `^4.22.1` | VERSIONS.md `shadcn4` | | `@tanstack/react-query`, `@tanstack/react-query-devtools` | ddx-web | dep | `^5.104.5` | VERSIONS.md `tanstack5` | | `zod` | ddx-web, ddx-api, contracts | dep | `^4.6.5` | VERSIONS.md `zod4` | | `better-auth` | ddx-web (client only), ddx-api (server) | dep | `^1.7.12` | VERSIONS.md `betterauth1` (Q1) | | `@nestjs/common`, `@nestjs/core`, `@nestjs/platform-express`, `@nestjs/testing` (dev) | ddx-api | dep | `^12.1.2` | VERSIONS.md `nest12` (floor 12.1) | | `@prisma/client`, `prisma` (dev) | ddx-api | dep | `^7.10.0` | VERSIONS.md `prisma7` (never 8 RC) | | `oxlint` + `oxlint-tsgolint` | ddx-web, ddx-api | dev | `^1.87.0` + `^7.0.2003` | VERSIONS.md `oxlint` | | `vitest` | ddx-web, ddx-api | dev | `^5.0.3` | VERSIONS.md `vitest5` | | `@playwright/test` | ddx-web | dev | `^1.64.1` | VERSIONS.md `pw1` | | pnpm (tool, not a dep) | root | — | `12` | VERSIONS.md `pnpm12` | ### ddx-web — scaffold baseline kept (create-nextjs template) | Package | Kind | Version | Version source | |---|---|---|---| | `@base-ui/react` | dep | `^1.9.0` | scaffold baseline (shadcn default primitives) | | `@radix-ui/react-menubar`, `-popover`, `-radio-group`, `-switch`, `-visually-hidden` | dep | baseline ranges | scaffold baseline | | `class-variance-authority` | dep | `^0.7.1` | scaffold baseline · RQ5 pick (variant API) | | `clsx` | dep | `^2.1.1` | scaffold baseline | | `tailwind-merge` | dep | `^3.7.0` | scaffold baseline | | `tw-animate-css` | dep | `^1.4.0` | scaffold baseline (durations must map to `--token-motion-*`, invariant 4) | | `lucide-react` | dep | `^1.61.0` | scaffold baseline (only icon set) | | `cmdk` | dep | `^1.1.1` | scaffold baseline (one command palette) | | `next-themes` | dep | `^0.4.6` | scaffold baseline · RQ5 pick (colour mode) | | `react-hook-form` | dep | `^7.90.0` | scaffold baseline · RQ5 pick (forms) | | `@hookform/resolvers` | dep | `^6.0.0` | scaffold baseline — use `standardSchemaResolver` only, never `zodResolver` | | `@standard-schema/spec` | dep | `^1.1.0` | scaffold baseline | | `@tanstack/react-table` | dep | `9.3.1` **exact** | owner-approved major 2026-10-24 (plan ddx-web-speed-ui) · native `useTable` API, never `useLegacyTable` | | `date-fns` | dep | `^4.4.0` | scaffold baseline · RQ5 pick (dates) | | `react-day-picker` | dep | `^10.0.2` | scaffold baseline (shadcn calendar) | | `react-resizable-panels` | dep | `^4.14.8` | scaffold baseline (workspace panels) | | `server-only` | dep | `^0.0.1` | scaffold baseline (AC6.2 marker) | | `@t3-oss/env-nextjs` | dep | `^0.13.11` | scaffold baseline (typed env, server/client split) | | `neverthrow` | dep | `^8.2.0` | scaffold baseline (Result types) | | `@vercel/otel` | dep | `^2.1.3` | owner-approved major 2026-10-24 (plan ddx-web-speed-ui); root `pnpm-workspace.yaml` pins `@opentelemetry/api` 1.9.1 | | dev majors 2026-10-24 (owner-approved, plan ddx-web-speed-ui): `@testing-library/jest-dom` 7, `jsdom` 30, `knip` 6, `@types/node` 26 — rest | dev | latest stable | `pnpm update --latest` | | dev: `@testing-library/react`, `@testing-library/jest-dom`, `jsdom`, `@vitejs/plugin-react`, `babel-plugin-react-compiler`, `knip`, `prettier`, `prettier-plugin-tailwindcss`, `postcss`, `eslint`, `@types/node`, `@types/react`, `@types/react-dom` | dev | baseline ranges | scaffold baseline | ### ddx-web — added by this ADR | Package | Kind | Version | Version source | |---|---|---|---| | `@tanstack/react-virtual` | dep | `^3.14.22` | npm view 2026-10-23 · RQ5 pick (data grid) | | `@babel/parser` | dev | `^8.0.8` | owner order 2026-10-24 · AST for `ddx-web/scripts/i18n-check.mjs` (exact file:line per key) | | `chalk` | dep | `^6.0.1` | owner order 2026-10-24 (plan ddx-web-speed-ui task 010) · server-side coloured dev logs in `src/lib/logger/server-format.ts` only | | `recharts` | dep | `^3.10.1` | npm view 2026-10-23 · RQ5 pick (charts) | | `embla-carousel-react` | dep | `^8.6.0` | npm view 2026-10-23 · RQ5 pick (carousel) | | `@tiptap/react`, `@tiptap/pm`, `@tiptap/starter-kit` | dep | `^3.32.0` (lockstep, same version all three) | npm view 2026-10-23 · RQ5 pick (rich text) | | `streamdown` | dep | `^2.7.1` | npm view 2026-10-23 · RQ5 pick (markdown, AC14.4) | | `pdfjs-dist` | dep | `^6.4.299` | npm view 2026-10-23 · RQ5 pick (PDF view) | | `@react-pdf/renderer` | dep | `^4.9.0` | npm view 2026-10-23 · RQ5 pick (PDF build, server-only modules) | | `@dnd-kit/core` | dep | `^6.3.1` | npm view 2026-10-23 · RQ5 pick (DnD) | | `@dnd-kit/sortable` | dep | `^10.0.0` | npm view 2026-10-23 | | `@dnd-kit/utilities` | dep | `^3.2.2` | npm view 2026-10-23 | | `jose` | dep | `^6.2.13` | npm view 2026-10-23 (BFF verifies the ddx-api JWT against `/auth/jwks`; server-only) | | `@ddx/contracts` | dep | `workspace:*` | workspace package (`packages/ddx-contracts`) | ### ddx-web — removed from the scaffold baseline | Package | Reason | |---|---| | `@prisma/client`, `prisma`, `pg`, `@types/pg` | ddx-web has no DB access (`_index.md` trust table; invariant 1/2 — data only through the BFF). If task 010 finds a template import it cannot drop, it reports; it does not keep the package silently. | ### ddx-api — scaffold baseline kept (create-nestjs template) All packages of `~/.claude/skills-vault/create-nestjs/templates/package.json.tmpl` at their template ranges: `@nestjs/config`, `@nestjs/schedule`, `@nestjs/swagger`, `@nestjs/throttler`, `express`, `helmet`, `nestjs-pino`, `pino` (`^10.4.0`), `pino-http`, `reflect-metadata`, `rxjs`, and dev `@nestjs/cli`, `@nestjs/schematics`, `@swc/core`, `@types/express`, `@types/node`, `@types/supertest`, `@vitest/coverage-v8`, `eslint`, `@eslint/js`, `typescript-eslint`, `pino-pretty`, `supertest`, `unplugin-swc`. Version source: scaffold baseline. ### ddx-api — added by this ADR | Package | Kind | Version | Version source | |---|---|---|---| | `@prisma/adapter-pg` | dep | `^7.10.0` (lockstep with `@prisma/client`) | VERSIONS.md `prisma7` · npm 7.10.1 on 2026-10-23 | | `pg` | dep | `^8.23.1` | npm view 2026-10-23 (driver for the adapter, six DBs) | | `@types/pg` | dev | `^8.23.1` | npm view 2026-10-23 | | `@aws-sdk/client-s3`, `@aws-sdk/s3-request-presigner` | dep | `^3.1156.0` | npm view 2026-10-23 · RQ5 pick (MinIO via S3 API). ADR 0010 (RQ8) may confirm; a different client needs a superseding ADR | | `@ddx/contracts` | dep | `workspace:*` | workspace package | | `dotenv` | dep | `^16.4.7` (scaffold) | create-nestjs scaffold · required by every Prisma 7 `prisma.config.ts` (`import "dotenv/config"`) — lead amendment 2026-10-23 (task 009 gap) | | `@better-auth/api-key`, `@better-auth/passkey` | dep | `^1.7.12` (lockstep with `better-auth`, bumped to `^1.7.12`) | npm view 2026-10-23 · better-auth 1.7.12 moved the apiKey + passkey plugins out of `better-auth/plugins` into these packages; Q1 names both — lead amendment 2026-10-23 (task 013 finding) | | `jose` | dep | `^6.2.12` (scaffold) | create-nestjs scaffold · JWKS guard (`--auth jwks`); better-auth JWT verify path — lead amendment 2026-10-23 (task 009 gap) | | `ioredis`, `bullmq` (+ `redis` Docker image) | dep | see [ADR 0016](/en/docs/adr/0016-sse-bus) | [ADR 0016](/en/docs/adr/0016-sse-bus) — SSE Bus (Redis Streams log, pub/sub fan-out, BullMQ outbound queue); versions live there only | ### contracts (`packages/ddx-contracts`) | Package | Kind | Version | Version source | |---|---|---|---| | `zod` | dep | `^4.6.5` | VERSIONS.md `zod4` | | `typescript` | dev | `^7.0.2` | VERSIONS.md `ts7` | ## Consequences - Good: one engine per concern; parallel waves never touch `package.json` or the lock file. - Good: every pick has a recorded rejected option, so a later "why not X" has an answer. - Cost: a real gap found mid-plan costs one ADR plus one install task. - Risk: npm "latest" moves. Ranges are caret within the major, so a minor bump needs no ADR; a major bump does. ## Validation ```sh rg -q -i 'streamdown' ddx-documentation/adr/0005-frameworks.md ! rg -q '"react-markdown"|"sonner"' ddx-web/package.json # after task 009 ```
ui-shell-rnd-workspace, task 003HMS (dudoxx-ai-hms, structure reference only) grew two markdown engines, three PDF
viewers, five TipTap editors, four table stacks and a zodResolver / standardSchemaResolver
split (findings/hms-pattern-audit/components.md §6, routes.md I4, I6, I10). Each came from a
task that added "one more" library. This workspace runs many tasks in parallel. If each task
edits package.json and pnpm-lock.yaml, the lock file collides.
adr/00NN-*.md) that
amends this table, and one dedicated install task.~/.claude/ddx-topic-flows/VERSIONS.md (by key) where a key exists.
Otherwise from the scaffold baseline, or from npm view on 2026-10-23. Ranges are caret
(^) unless the row says exact.react-markdown (AC14.4 — streamdown is the one
markdown engine), sonner / any toast library (toast ban), @heroicons/* /
@fortawesome/* (Lucide only), tailwind-variants, inversify, reflect-metadata in
ddx-web, ag-grid-*, swiper, motion / framer-motion, livekit-client (WS14 NOT),
formik, dayjs, moment, axios (fetch through the BFF factory, ADR 0006).Apps: ddx-web (Next.js), ddx-api (NestJS), contracts (packages/ddx-contracts).
Kind: dep = dependencies, dev = devDependencies.
All packages of ~/.claude/skills-vault/create-nestjs/templates/package.json.tmpl at their
template ranges: @nestjs/config, @nestjs/schedule, @nestjs/swagger, @nestjs/throttler,
express, helmet, nestjs-pino, pino (^10.4.0), pino-http, reflect-metadata, rxjs,
and dev @nestjs/cli, @nestjs/schematics, @swc/core, @types/express, @types/node,
@types/supertest, @vitest/coverage-v8, eslint, @eslint/js, typescript-eslint,
pino-pretty, supertest, unplugin-swc. Version source: scaffold baseline.
packages/ddx-contracts)package.json or the lock file.adr/
deploy/
research/
rg -q -i 'streamdown' ddx-documentation/adr/0005-frameworks.md! rg -q '"react-markdown"|"sonner"' ddx-web/package.json # after task 009