Skip to content

ADR-003: Incremental TypeScript + Tailwind CSS Migration

ADR-003: Incremental TypeScript + Tailwind CSS Migration

Section titled “ADR-003: Incremental TypeScript + Tailwind CSS Migration”

Status: accepted
Date: 2026-06-14
Deciders: @ErnestNuma


ClinicFlow started as an all-JavaScript project with inline CSS styles throughout the frontend. By June 2026, the codebase had grown to:

  • Backend: ~30 route/service files, ~40 test files, ~458 tests
  • Frontend: ~18 page components, ~20+ shared components, all with inline style={{}} objects

Two pain points had become acute:

  1. TypeScript: No static type checking. Refactoring was risky — changing a function signature meant manually finding every call site. The backend’s JWT payload, Supabase query results, and API request/response shapes were all implicitly typed, causing runtime errors that tsc would have caught.
  2. Tailwind CSS: Inline styles are verbose, hard to maintain for responsive design, and cause unnecessary re-renders (new object references on every render). Dark mode support required manual style overrides per component. The landing page and LoginPage already used Tailwind and looked better than the inline-styled dashboard pages.

Adopt an incremental, opt-in migration to TypeScript + Tailwind CSS. No big-bang rewrite.

Phase Scope Status
CI Install TypeScript, configure tsconfig.json with allowJs: true, add tsc --noEmit to CI pipeline ✅ Shipped (PR #78)
A Backend: type-check clean on key modules (auth, middleware, db) ✅ Shipped (PR #79, all green)
B Backend: remaining routes + services 🔜 Next
C Frontend: type-check clean on API client, context, hooks
D Frontend: page/component migration
Tailwind pages Migrate inline-styled pages to Tailwind (utility classes, no CSS modules)
  • allowJs: true during transition — .js and .ts files coexist
  • Node native type-stripping (Node ≥22.18) runs .ts files directly — no build step for backend
  • Vite handles frontend .ts transformation automatically
  • Tailwind config has the brand green (#059669) as the brand token
  • New components in frontend/src/ should use Tailwind by default unless touching a file that’s entirely inline-styled (match the surrounding code)
  • No CSS modules — Tailwind utility classes or inline styles only (reduces API surface; one less thing to migrate later)
  • Safer refactoring (type-check catches missed call sites)
  • Better dark mode support (Tailwind’s dark: variant vs. manual overrides)
  • Eliminates inline-style re-render perf tax
  • Single Tailwind config for all brand tokens

Positive:

  • Type errors caught in CI before they reach production
  • Tailwind’s utility classes make responsive design and dark mode trivial
  • Incremental approach means no blocking migration — ship anytime
  • Node’s native type-stripping means zero build overhead for backend .ts files

Negative:

  • Two syntaxes coexist for months — JS and TS, inline and Tailwind. Developers must context-switch per file.
  • allowJs weakens strictness — a fully .ts codebase would catch more errors
  • Tailwind’s compile step adds ~2s to frontend build time
  • Inline styles being migrated means git history has more churn than a greenfield approach

Risks:

  • Migration stalls and the project lives with mixed syntax forever (mitigation: phased, no-regression approach; each phase ships independently)
  • New hires find the hybrid state confusing (mitigation: this ADR serves as the canonical explanation)
  • tsc --noEmit must pass in both backend/ and frontend/ before any PR merges
  • ESLint rules prevent any type where possible
  • New frontend components default to Tailwind; inline styles require justification in PR