# AGENTS.md - AI Coding Agent Guidelines ## Project Overview TanStack Start fullstack app with Tauri desktop shell. | Layer | Tech | |-------|------| | Framework | TanStack Start (React SSR, file-based routing) | | Runtime | Bun | | Language | TypeScript (strict mode) | | Styling | Tailwind CSS v4 | | Database | PostgreSQL + Drizzle ORM | | RPC | ORPC (contract-first, type-safe) | | Build | Vite + Turbo | | Linting | Biome | | Desktop | Tauri v2 (optional, see `src-tauri/AGENTS.md`) | ## Commands ```bash # Development bun dev # Start Tauri + Vite via Turbo bun dev:vite # Vite only (localhost:3000) bun db:studio # Drizzle Studio # Build bun build # Full build (Vite → compile → Tauri) bun build:vite # Vite only (outputs to .output/) # Code Quality bun typecheck # TypeScript check (tsc -b) bun fix # Biome auto-fix (format + lint) # Database bun db:generate # Generate migrations from schema bun db:migrate # Run migrations bun db:push # Push schema changes (dev only) # Testing (not configured yet) # When adding tests, use Vitest or Bun test runner: # bun test path/to/test.ts # Single file # bun test -t "pattern" # By test name ``` ## Code Style ### Formatting (Biome enforced) - **Indent**: 2 spaces - **Line endings**: LF - **Quotes**: Single `'string'` - **Semicolons**: As needed (ASI) - **Arrow parens**: Always `(x) => x` ### Imports Biome auto-organizes. Order: external → internal (`@/*`) → type-only imports. ```typescript import { createFileRoute } from '@tanstack/react-router' import { z } from 'zod' import { db } from '@/db' import type { ReactNode } from 'react' ``` ### TypeScript Strict mode with extra checks: - `noUncheckedIndexedAccess: true` - array/object index returns `T | undefined` - `noImplicitOverride: true` - `verbatimModuleSyntax: true` Path alias: `@/*` → `src/*` ### Naming | Entity | Convention | Example | |--------|------------|---------| | Files (utils) | kebab-case | `utils.ts`, `db-provider.ts` | | Files (components) | PascalCase | `NotFound.tsx` | | Routes | TanStack conventions | `routes/index.tsx`, `routes/__root.tsx` | | Components | PascalCase arrow functions | `const MyComponent = () => {}` | | Functions | camelCase | `handleSubmit` | | Constants | UPPER_SNAKE_CASE | `MAX_RETRIES` | | Types/Interfaces | PascalCase | `TodoItem`, `RouterContext` | ### React Patterns ```typescript // Components: arrow functions (Biome enforces) const MyComponent = ({ title }: { title: string }) => { return
{title}
} // Routes: createFileRoute export const Route = createFileRoute('/')({ component: Home, loader: async ({ context }) => { await context.queryClient.ensureQueryData(orpc.todo.list.queryOptions()) }, }) // Data fetching: TanStack Query const query = useSuspenseQuery(orpc.todo.list.queryOptions()) const mutation = useMutation(orpc.todo.create.mutationOptions()) ``` ### ORPC Pattern (Contract-First RPC) 1. **Define contract** (`src/orpc/contracts/my-feature.ts`): ```typescript import { oc } from '@orpc/contract' import { z } from 'zod' export const get = oc.input(z.object({ id: z.uuid() })).output(schema) export const create = oc.input(insertSchema).output(schema) ``` 2. **Implement handler** (`src/orpc/handlers/my-feature.ts`): ```typescript import { os } from '@/orpc/server' import { dbProvider } from '@/orpc/middlewares' export const get = os.myFeature.get .use(dbProvider) .handler(async ({ context, input }) => { return await context.db.query.myTable.findFirst(...) }) ``` 3. **Register** in `contract.ts` and `router.ts` 4. **Use** in components via `orpc.myFeature.get.queryOptions()` ### Drizzle Schema ```typescript import { sql } from 'drizzle-orm' import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core' export const myTable = pgTable('my_table', { id: uuid('id').primaryKey().default(sql`uuidv7()`), name: text('name').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow().$onUpdateFn(() => new Date()), }) ``` ### Environment Variables - Server: no prefix (e.g., `DATABASE_URL`) - Client: `VITE_` prefix required - Validated via `@t3-oss/env-core` in `src/env.ts` ## Directory Structure ``` src/ ├── components/ # Reusable React components ├── db/ │ ├── schema/ # Drizzle schema definitions │ └── index.ts # Database instance ├── integrations/ # TanStack Query/Router setup ├── lib/ # Utility functions ├── orpc/ │ ├── contracts/ # Input/output schemas │ ├── handlers/ # Server procedure implementations │ ├── middlewares/ # Middleware (e.g., dbProvider) │ ├── contract.ts # Contract aggregation │ ├── router.ts # Router composition │ └── client.ts # Isomorphic client ├── routes/ # File-based routes │ ├── __root.tsx # Root layout │ └── api/rpc.$.ts # ORPC HTTP endpoint └── env.ts # Environment validation ``` ## Critical Rules - **DO NOT** edit `src/routeTree.gen.ts` (auto-generated) - **DO NOT** commit `.env` files - **MUST** run `bun fix` before commits - **MUST** use `@/*` path alias (not relative imports) - **MUST** use React Compiler (no manual memoization needed) - **MUST** use `Readonly` for immutable props ## Git Workflow 1. Make changes following style guide 2. Run `bun fix` (format + lint) 3. Run `bun typecheck` (type safety) 4. Test with `bun dev` 5. Commit with descriptive message