A ground-up rebuild of the CircleHealth application, focusing on a clean codebase with strong conventions and modern tooling.
| Layer | Technology |
|---|---|
| Monorepo | Turborepo |
| Language | TypeScript 5.9 |
| Framework | Next.js 16 (App Router) |
| UI | React 19 + Chakra UI v3 |
| Database | PostgreSQL via Supabase |
| Schema | Drizzle ORM |
| Queries | Kysely |
| API | tRPC v11 |
| Formatting & Linting | Biome |
| Tool | Version | Notes |
|---|---|---|
| Node.js | >= 20 | See Node version management below |
| pnpm | 10.23.0 | corepack enable will install the correct version |
| Supabase CLI | latest | Required for local DB and E2E tests |
| Docker | latest | Required by Supabase CLI |
We recommend using a Node version manager so you can easily switch between projects. Either of these works:
Using nvm:
# Install nvm (macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# Install and use the correct Node version
nvm install 20
nvm use 20
Using fnm (faster alternative):
# Install fnm (macOS)
brew install fnm
# Or via the install script (all platforms)
curl -fsSL https://fnm.vercel.app/install | bash
# Install and use the correct Node version
fnm install 20
fnm use 20
The repo includes a .node-version file, so both nvm and fnm will auto-switch when you cd into the project (if you have shell integration enabled).
# Clone the repo
git clone <repo-url> circle-v2
cd circle-v2
# Enable corepack for pnpm
corepack enable
# Install dependencies
pnpm install
# Set up environment variables (see docs/02-getting-started.md for details)
cp apps/web/.env.example apps/web/.env # or create manually
# Start development
pnpm dev
All commands are run from the repo root.
| Command | Description |
|---|---|
pnpm dev |
Start the web app and all dependencies in dev mode |
pnpm build |
Build all packages and apps |
pnpm test |
Run all unit tests via Turbo |
pnpm test:watch |
Run tests in watch mode |
pnpm lint |
Run Biome + ESLint across the monorepo |
pnpm format |
Auto-fix formatting via biome check --write |
pnpm check-types |
Type-check all packages and apps |
pnpm --filter storybook dev # Start Storybook on port 6006
pnpm --filter storybook build # Build static Storybook
pnpm --filter @repo/db generate # Generate a new Drizzle migration
pnpm --filter @repo/db migrate # Apply pending migrations
pnpm --filter @repo/db test # Run DB integration tests
subgraph packages [Packages] UI["@repo/ui"] TRPC["@repo/trpc"] AuthNext["@repo/auth-next"] Auth["@repo/auth"] DB["@repo/db"] Services["@repo/services"] Legacy["@repo/legacy"] Utils["@repo/utils"] Safe["@repo/safe"] Errors["@repo/errors"] Logger["@repo/logger"] BaseService["@repo/base-service"] RouteKit["@repo/route-kit"] RoutesKitNext["@repo/routes-kit-next"] UseForm["@repo/use-form"] UseRecordAudio["@repo/use-record-audio"] AnalyticsCore["@repo/analytics-core"] AnalyticsReact["@repo/analytics-react"] ReactQuery["@repo/react-query"] UtilsReact["@repo/utils-react"] end
subgraph infra [Infrastructure] Supabase["Supabase (Postgres + Auth)"] end
Web --> UI Web --> TRPC Web --> AuthNext Web --> Legacy Web --> RoutesKitNext Web --> UseRecordAudio Web --> AnalyticsReact SB --> UI SB --> UseForm
TRPC --> Services TRPC --> DB TRPC --> Auth Services --> BaseService Services --> DB Services --> Safe AuthNext --> Auth AuthNext --> DB Auth --> DB Auth --> Safe Legacy --> DB Legacy --> Utils RoutesKitNext --> RouteKit UI --> Utils UI --> UseForm UseRecordAudio --> Safe AnalyticsReact --> AnalyticsCore Utils --> DB Utils --> Errors Safe --> Errors DB --> Errors DB --> Logger DB --> Safe
DB --> Supabase
subgraph packages [Packages] UI["@repo/ui"] TRPC["@repo/trpc"] AuthNext["@repo/auth-next"] Auth["@repo/auth"] DB["@repo/db"] Services["@repo/services"] Legacy["@repo/legacy"] Utils["@repo/utils"] Safe["@repo/safe"] Errors["@repo/errors"] Logger["@repo/logger"] BaseService["@repo/base-service"] RouteKit["@repo/route-kit"] RoutesKitNext["@repo/routes-kit-next"] UseForm["@repo/use-form"] UseRecordAudio["@repo/use-record-audio"] AnalyticsCore["@repo/analytics-core"] AnalyticsReact["@repo/analytics-react"] ReactQuery["@repo/react-query"] UtilsReact["@repo/utils-react"] end
subgraph infra [Infrastructure] Supabase["Supabase (Postgres + Auth)"] end
Web --> UI Web --> TRPC Web --> AuthNext Web --> Legacy Web --> RoutesKitNext Web --> UseRecordAudio Web --> AnalyticsReact SB --> UI SB --> UseForm
TRPC --> Services TRPC --> DB TRPC --> Auth Services --> BaseService Services --> DB Services --> Safe AuthNext --> Auth AuthNext --> DB Auth --> DB Auth --> Safe Legacy --> DB Legacy --> Utils RoutesKitNext --> RouteKit UI --> Utils UI --> UseForm UseRecordAudio --> Safe AnalyticsReact --> AnalyticsCore Utils --> DB Utils --> Errors Safe --> Errors DB --> Errors DB --> Logger DB --> Safe
DB --> Supabase
graph TD
subgraph apps [Apps]
Web["apps/web (Next.js)"]
SB["apps/storybook"]
end
subgraph packages [Packages]
UI["@repo/ui"]
TRPC["@repo/trpc"]
AuthNext["@repo/auth-next"]
Auth["@repo/auth"]
DB["@repo/db"]
Services["@repo/services"]
Legacy["@repo/legacy"]
Utils["@repo/utils"]
Safe["@repo/safe"]
Errors["@repo/errors"]
Logger["@repo/logger"]
BaseService["@repo/base-service"]
RouteKit["@repo/route-kit"]
RoutesKitNext["@repo/routes-kit-next"]
UseForm["@repo/use-form"]
UseRecordAudio["@repo/use-record-audio"]
AnalyticsCore["@repo/analytics-core"]
AnalyticsReact["@repo/analytics-react"]
ReactQuery["@repo/react-query"]
UtilsReact["@repo/utils-react"]
end
subgraph infra [Infrastructure]
Supabase["Supabase (Postgres + Auth)"]
end
Web --> UI
Web --> TRPC
Web --> AuthNext
Web --> Legacy
Web --> RoutesKitNext
Web --> UseRecordAudio
Web --> AnalyticsReact
SB --> UI
SB --> UseForm
TRPC --> Services
TRPC --> DB
TRPC --> Auth
Services --> BaseService
Services --> DB
Services --> Safe
AuthNext --> Auth
AuthNext --> DB
Auth --> DB
Auth --> Safe
Legacy --> DB
Legacy --> Utils
RoutesKitNext --> RouteKit
UI --> Utils
UI --> UseForm
UseRecordAudio --> Safe
AnalyticsReact --> AnalyticsCore
Utils --> DB
Utils --> Errors
Safe --> Errors
DB --> Errors
DB --> Logger
DB --> Safe
DB --> Supabase
Detailed documentation lives in the docs/ folder: