One standardfor every layer.

Visual Thinking’s full-stack decisions, one ADR per layer: the option we chose, the ones we didn’t, and the trade-offs we accepted.

36 decisions34 acceptedlast decided November 13, 2026

The stack, top to bottom

36 layers in three bands. Point at one to read its decision and light up the decisions it builds on; open it for the reasoning.

Build the app22

  1. UI frameworkReactADR-0001
  2. App frameworkTanStack StartADR-0002
  3. API layerHono at /api/*ADR-0004
  4. Input validationZodADR-0005
  5. StylingTailwind CSS v4 + shadcn/ui on Base UIADR-0011
  6. Data fetching & stateTanStack Query + loaders; URL + React stateADR-0012
  7. FormsTanStack Form + Zod + shadcn/ui FieldADR-0013
  8. TablesTanStack Table v9 + shadcn/ui TableADR-0018
  9. Virtual listsTanStack VirtualADR-0019
  10. ChartsTanStack ChartsADR-0020
  11. MarkdownTanStack MarkdownADR-0021
  12. Syntax highlightingTanStack HighlightADR-0022
  13. Debounce & throttleTanStack Pacer, pinnedADR-0023
  14. Keyboard shortcutsTanStack Hotkeys, pinnedADR-0024
  15. AI SDKTanStack AI, pinned; provider per productADR-0025
  16. Project structureBun workspace; layer folders; src/server/ fencedADR-0026
  17. Engineering principlesKISS, YAGNI, SOLID, in that orderADR-0027
  18. Background jobs & schedulingBullMQ on ValkeyADR-0028
  19. Transactional emailReact Email + Nodemailer over SMTPADR-0029
  20. InternationalizationParaglide JSADR-0031
  21. Dates, time zones & moneydate-fns 4 + Intl; money in minor unitsADR-0034
  22. API layerStart server routes at /api/*ADR-0036

Store & protect7

  1. DatabasePostgreSQLADR-0007
  2. ORM / query layerDrizzle ORM v1 + Drizzle Kit on Bun SQLADR-0008
  3. AuthBetter Auth 1.7ADR-0009
  4. Rate limitingrate-limiter-flexible + ValkeyADR-0010
  5. File & object storageS3 API on Ceph or any S3-compatible storeADR-0030
  6. Postgres backup & restoreDefined per deploymentADR-0032
  7. AuthorizationBetter Auth access controlADR-0033

Ship & run7

  1. Deploy targetDocker, self-hosted, on BunADR-0003
  2. Runtime & toolchainNode LTS + pnpm + Vite+ADR-0006
  3. Testingbun test + Testcontainers; Playwright E2EADR-0014
  4. ObservabilityOpenTelemetry + GlitchTipADR-0015
  5. CI/CDOne workflow for GitHub + Forgejo; Nodus deploysADR-0016
  6. Reverse proxy & TLSTraefik 3.7, no CDN for nowADR-0017
  7. Runtime & toolchainBun, built-ins firstADR-0035
ADR-0001Accepted
Open ADR-0001

How a decision gets made

  1. 1

    One topic at a time

    Layers are decided in dependency order: the UI framework first, the AI SDK most recently.

  2. 2

    Options, then a call

    Realistic candidates are weighed against our constraints, with their pros and cons written down.

  3. 3

    A dated ADR

    Context, options considered, the decision, and its consequences: what gets easier, what gets harder, what we give up.

  4. 4

    Superseded, never deleted

    A later ADR can replace a decision. The old page stays published, marked Superseded.

In discussion now: none open.The ADR format

The shape of every ADR, as ADR-0036 lays it out
ADR-0036: API layerStatus AcceptedDate ContextOptions considered1. Keep Hono (ADR-0004)2. Start server routes3. tRPCDecisionConsequences

Every decision, by band

The logo’s three bands are the stack’s three layers: the app on top, its data underneath, and everything that ships and runs it at the bottom.

Build the app

What every screen, route, form, and request handler is written with.

  1. ADR-0001UI frameworkReactCompany standard UI library for all web apps — React.
  2. ADR-0002App frameworkTanStack StartCompany standard full-stack app framework — TanStack Start.
  3. ADR-0004API layerHono at /api/*Company standard HTTP API layer — Hono, mounted at /api inside the TanStack Start app.
  4. ADR-0005Input validationZodCompany standard validation library — Zod, at every system boundary.
  5. ADR-0011StylingTailwind CSS v4 + shadcn/ui on Base UICompany standard styling — Tailwind CSS v4, with shadcn/ui components on Base UI primitives.
  6. ADR-0012Data fetching & stateTanStack Query + loaders; URL + React stateCompany standard data fetching and state — TanStack Query behind Router loaders for server data, the URL and React for client state, Zustand only when needed.
  7. ADR-0013FormsTanStack Form + Zod + shadcn/ui FieldCompany standard forms — TanStack Form with shared Zod schemas, shadcn/ui Field components, and POST server functions; JavaScript required by default.
  8. ADR-0018TablesTanStack Table v9 + shadcn/ui TableCompany standard tables — TanStack Table v9, headless, rendered with shadcn/ui table markup; lists that grow are paged, sorted, and filtered on the server through the URL and Query.
  9. ADR-0019Virtual listsTanStack VirtualCompany standard virtual lists — TanStack Virtual, headless, for long lists, feeds, and large grids once pagination and CSS cannot do the job.
  10. ADR-0020ChartsTanStack ChartsCompany standard charts — TanStack Charts as server-rendered SVG behind our own thin wrappers, not shadcn/ui's Recharts-based charts, with a table twin for every chart.
  11. ADR-0021MarkdownTanStack MarkdownCompany standard Markdown rendering — TanStack Markdown behind one shared component per app, raw HTML off, and one link and image policy for model and user text.
  12. ADR-0022Syntax highlightingTanStack HighlightCompany standard syntax highlighting — TanStack Highlight with an explicit language list, escaped class-based HTML, and colors from our own CSS variables in light and dark.
  13. ADR-0023Debounce & throttleTanStack Pacer, pinnedCompany standard debounce and throttle — TanStack Pacer's React hooks, pinned to exact versions while pre-1.0; server-side limits stay with ADR-0010.
  14. ADR-0024Keyboard shortcutsTanStack Hotkeys, pinnedCompany standard keyboard shortcuts — TanStack Hotkeys, pinned to exact versions while pre-1.0, with WCAG 2.1.4 and discoverability rules for every shortcut.
  15. ADR-0025AI SDKTanStack AI, pinned; provider per productCompany standard AI SDK — TanStack AI, pinned to exact pre-1.0 versions and provider-agnostic; each product picks its model provider in its own ADR.
  16. ADR-0026Project structureBun workspace; layer folders; src/server/ fencedCompany standard repository layout — a Bun workspace with each app under apps/, layer folders inside every app, server-only code fenced in src/server/, environment variables parsed with T3 Env, one naming scheme for files, and squash-merged pull requests titled as Conventional Commits.
  17. ADR-0027Engineering principlesKISS, YAGNI, SOLID, in that orderCompany standard for how we shape code — KISS first, YAGNI second, SOLID where code has a real reason to change.
  18. ADR-0028Background jobs & schedulingBullMQ on ValkeyCompany standard for work outside the request — BullMQ queues and job schedulers on the Valkey we already run, processed by a worker container from the same image.
  19. ADR-0029Transactional emailReact Email + Nodemailer over SMTPCompany standard for app email — React Email templates sent with Nodemailer over SMTP, from a background job; the SMTP provider is chosen per project.
  20. ADR-0031InternationalizationParaglide JSCompany standard app i18n — Paraglide JS, compiled to typed message functions, with the locale in the URL and resolved per request on the server.
  21. ADR-0034Dates, time zones & moneydate-fns 4 + Intl; money in minor unitsCompany standard for time and money — date-fns 4 with @date-fns/tz for date math, Intl for formatting, timestamptz in Postgres, and money as integer minor units with a currency code.
  22. ADR-0036API layerStart server routes at /api/*Company standard HTTP API layer — TanStack Start server routes under /api/*, with handlers in src/server/api/ and token-only authentication. Hono only when a product publishes an API contract.

Store & protect

Where data lives, who can reach it, and how hard anyone can push.

  1. ADR-0007DatabasePostgreSQLCompany standard database engine — PostgreSQL.
  2. ADR-0008ORM / query layerDrizzle ORM v1 + Drizzle Kit on Bun SQLCompany standard database access — Drizzle ORM v1 for queries, Drizzle Kit for migrations.
  3. ADR-0009AuthBetter Auth 1.7Company standard authentication — Better Auth, self-hosted in the app, sessions in our Postgres.
  4. ADR-0010Rate limitingrate-limiter-flexible + ValkeyCompany standard app-level rate limiting — rate-limiter-flexible in Start's global request middleware, counters in Valkey.
  5. ADR-0030File & object storageS3 API on Ceph or any S3-compatible storeCompany standard for files — the S3 API against an S3-compatible store (Ceph RADOS Gateway by default), private buckets, and presigned URLs for browser uploads and downloads.
  6. ADR-0032Postgres backup & restoreDefined per deploymentCompany standard for database backups — defined per deployment, never by the app, with a written record and a tested restore before go-live.
  7. ADR-0033AuthorizationBetter Auth access controlCompany standard for what a signed-in user may do — Better Auth access control for roles and permissions, checked on the server at every entry point, with row scope in the query.

Ship & run

How code is built, tested, delivered, served, and watched in production.

  1. ADR-0003Deploy targetDocker, self-hosted, on BunCompany standard deploy target — Docker containers running Bun, self-hosted.
  2. ADR-0006Runtime & toolchainNode LTS + pnpm + Vite+Company standard — Node LTS runtime, pnpm package manager, Vite+ (vp) unified toolchain.
  3. ADR-0014Testingbun test + Testcontainers; Playwright E2ECompany standard testing — bun test for unit, component (happy-dom) and integration tests (real Postgres and Valkey in Testcontainers); Playwright for interactions and end-to-end.
  4. ADR-0015ObservabilityOpenTelemetry + GlitchTipCompany standard observability — OpenTelemetry in every app, exported over OTLP with sensitive data filtered before it leaves the process, and GlitchTip for browser errors. Where telemetry is stored, viewed and alerted on is outside the app.
  5. ADR-0016CI/CDOne workflow for GitHub + Forgejo; Nodus deploysCompany standard CI/CD — one portable workflow that runs on GitHub Actions and Forgejo Actions (one ephemeral Firecracker microVM per job), one immutable image per commit, deployed by Nodus.
  6. ADR-0017Reverse proxy & TLSTraefik 3.7, no CDN for nowCompany standard edge — Traefik on each host terminates TLS, sets the trusted client IP, gates on /readyz, and limits floods; no CDN for now, with the volumetric-flood risk accepted.
  7. ADR-0035Runtime & toolchainBun, built-ins firstCompany standard runtime and toolchain — Bun for the runtime, package manager, workspaces and test runner, with Bun's built-in APIs preferred over packages; Oxlint, Oxfmt and Vite stay for what Bun does not do.

One standard per layer.Anything else needs its own ADR.

These are the defaults for every new project. A product can deviate, but it writes its own ADR explaining why, so the exception sits on the record next to the rule it breaks.

Rules of the game

Start at the topof the stack.