Adds docker-compose.yml (Postgres on host port 5433, to avoid clashing with another project's container already using 5432 on this machine) and .env.example so `docker compose up -d` + `cp .env.example .env.local` gets a working local DB. Adds a db:seed script alongside the existing db:* scripts. Rewrites README's "Adding a Database" section, which still described Better Auth's DB as optional/hypothetical even though this project already requires Postgres for both its own data and Better Auth's tables. Fills in CLAUDE.md's Data layer connection-details placeholder and fixes two more stale pnpm references and a wrong migrations path (src/db/migrations/ instead of the actual drizzle/) found nearby. Also fixes a real bug the new docs surfaced: `npm run db:seed` didn't load .env.local the way drizzle-kit does, so DATABASE_URL was silently unset when run as documented. seed.ts now loads env the same way drizzle.config.ts already does. Verified the full documented flow end-to-end against a fresh container: migrate, better-auth migrate, seed, dev server, real page render. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.4 KiB
7.4 KiB
CLAUDE.md
Guidance for Claude Code when working in this repository.
Project overview
[1-2 sentences: what this app does, who it's for]
Stack:
- TanStack Start (React, file-based routing via TanStack Router)
- TypeScript (strict mode)
- Vite
- SCSS Modules
- Drizzle ORM + PostgreSQL
- Better Auth
- npm
Commands
npm run dev # start dev server (http://localhost:3000)
npm run build # production build
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm test # vitest run
Always run typecheck and lint before considering a task done. If build
fails with TypeScript errors, run typecheck first to isolate them.
Project structure
src/
├── routes/ # file-based routes (TanStack Router)
│ ├── __root.tsx # root layout, wraps every page
│ └── index.tsx # home page
├── routeTree.gen.ts # AUTO-GENERATED — never edit by hand
├── router.tsx # router instance/config
├── components/
│ ├── ui/ # base primitives (buttons, inputs, etc.)
│ └── <domain>/ # feature-specific components, e.g. posts/, auth/
├── lib/ # shared utilities, API clients, helpers
├── db/
│ ├── schema.ts # Drizzle schema definitions
│ ├── index.ts # Drizzle client instance
│ └── seed.ts # sample data for local dev / tests
├── styles/
│ ├── globals.scss # global styles, resets
│ └── _variables.scss # shared SCSS variables/mixins (partial)
public/ # static assets
drizzle.config.ts # Drizzle Kit config (schema path, out dir, dialect)
drizzle/ # generated SQL migrations — never hand-edit
Routing conventions
- Routes live under
src/routes/and map to URLs by file name/path. __root.tsxis the root layout every route renders inside.routeTree.gen.tsis generated by TanStack Router — never edit it directly; it regenerates ondev/buildfrom the files inroutes/.- Use route
loaders for data fetching tied to a route, notuseEffect. - Keep server-only code (secrets, DB access) out of files that ship to the
client; be careful with
verbatimModuleSyntaxso server code doesn't leak into client bundles.
Styling (SCSS Modules)
- Every component that needs styles gets a co-located
Component.module.scssnext toComponent.tsx— not a shared stylesheet. - Import as
import styles from './Component.module.scss'and apply classes viaclassName={styles.foo}; never reference a module class by string outside that import. - Class names in
.module.scssfiles are camelCase (e.g..cardHeader, not.card-header) so they read naturally asstyles.cardHeader. - Shared tokens (colors, spacing, breakpoints) live in
src/styles/_variables.scssand are pulled in with@use '~/styles/variables' as *;— don't hardcode colors/spacing that already exist as a variable. src/styles/globals.scssis for resets and truly global rules only (imported once in__root.tsx); component-specific styles always go in a module, never in globals.- Avoid deep nesting (max ~3 levels) and avoid
!important.
Code style
- TypeScript strict mode; avoid
any. Prefer inferred types everywhere possible — let TypeScript infer return types and variable types rather than annotating them. Only add explicit types where inference can't reach: function parameters (always type these), exported values whose inferred type would be unclear to a caller, or when inference produces something wider than intended (e.g. useas constor a literal type instead of annotating). - Functional React components only, no class components.
- Use arrow functions everywhere (components, handlers, utilities) instead of
the
functionkeyword — e.g.const getUser = async (id: string) => {...}andexport const Button = (props: ButtonProps) => {...}. - [Naming conventions: PascalCase components, camelCase functions/vars, etc.]
- [Import order / path aliases, e.g.
~/or@/forsrc/] - Co-locate a component's test file next to it unless it's a shared
primitive in
components/ui/(see "Styling" for.module.scssplacement). - No comments unless they're unavoidable for understanding — e.g. explaining why a non-obvious workaround exists, documenting a constraint imposed by an external system, or flagging a case that genuinely can't be made self-evident through naming/structure. Never comment what the code already says; prefer clearer names and smaller functions over a comment explaining an unclear one.
Data layer (Drizzle + Postgres)
- Schema lives in
src/db/schema.ts; the Drizzle client is created once insrc/db/index.tsand imported from there — don't instantiate new clients elsewhere. - Only import
db/*from server-only code (routeloaders/actions, server functions) — never from client components, to avoid leaking DB credentials into the client bundle. - Query with Drizzle's query builder (
db.select()...,db.query.table...) rather than raw SQL, unless a query genuinely needs it. - After changing
schema.ts, generate a migration rather than editing the database by hand:npm run db:generate # create a migration from schema changes npm run db:migrate # apply pending migrations - Never hand-edit files under
drizzle/(the migrations output directory, configured indrizzle.config.ts) — they're generated and should be treated as a history, not touched retroactively. - Connection string comes from
DATABASE_URL(see.env.example), read viadrizzle-orm/node-postgres. Local dev DB is adocker-compose.ymlPostgres container — see the README's "Local development database" section..env.localis picked up automatically by both the dev server anddrizzle-kit; no manual sourcing needed.
Testing
- [Test runner and conventions, e.g. Vitest + Testing Library]
- [Where tests live: colocated
*.test.ts(x)vs a__tests__dir] - Run the relevant test file after changes, not just the full suite, unless asked to verify broadly.
Things to avoid
- Don't hand-edit
routeTree.gen.tsor files underdrizzle/. - Don't run destructive Drizzle commands (
drizzle-kit pushagainst a populated DB, or anything that drops columns/tables) without confirming first — prefergenerate+migrateso changes are reviewable. - Don't introduce a new state-management library without asking — prefer route loaders / TanStack Query cache / local state first.
- Don't add new dependencies without checking
package.jsonfor an existing one that already covers the need. - [Any other project-specific guardrails]
Git / PR conventions
- [Commit message style, branch naming, whether Claude should commit automatically or leave changes staged for review]
Agent skills
Issue tracker
Issues live as Gitea issues on the self-hosted instance at git.lukas-harat.com (lukas/Ghost_Kitchen), accessed via the REST API with curl (no tea CLI installed). See docs/agents/issue-tracker.md.
Triage labels
Default canonical label strings (needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix) used as-is. See docs/agents/triage-labels.md.
Domain docs
Single-context: CONTEXT.md + docs/adr/ at the repo root. See docs/agents/domain.md.