# AGENTS.md - AI Coding Agent Guidelines This document provides comprehensive guidelines for AI coding agents working in this TanStack Start fullstack starter codebase. ## Project Overview - **Framework**: TanStack Start (React SSR framework with file-based routing) - **Runtime**: Bun - **Language**: TypeScript (strict mode, ESNext) - **Styling**: Tailwind CSS v4 - **Database**: PostgreSQL with Drizzle ORM - **State Management**: TanStack Query - **Routing**: TanStack Router (file-based) - **RPC**: ORPC (type-safe RPC with contract-first design) - **Build Tool**: Vite - **Linter/Formatter**: Biome ## Build, Lint, and Test Commands ### Development ```bash bun dev # Start development server bun db:studio # Open Drizzle Studio for database management ``` ### Building ```bash bun build # Build for production (outputs to .output/) bun compile # Compile to standalone executable (out/server) bun serve # Preview production build ``` ### Code Quality ```bash bun typecheck # Run TypeScript compiler (tsc -b) bun fix # Run Biome linter and formatter (auto-fix issues) biome check . # Check without auto-fix biome format --write . # Format code only ``` ### Database ```bash bun db:generate # Generate migration files from schema bun db:migrate # Run migrations bun db:push # Push schema changes directly (dev only) ``` ### Testing **Note**: No test framework is currently configured. When adding tests: - Use Vitest or Bun's built-in test runner - Run single test file: `bun test path/to/test.ts` - Run specific test: `bun test -t "test name pattern"` ## Code Style Guidelines ### Formatting (Biome) **Indentation**: 2 spaces (not tabs) **Line Endings**: LF (Unix-style) **Quotes**: Single quotes for strings **Semicolons**: As needed (ASI - automatic semicolon insertion) **Arrow Parens**: Always use parentheses `(x) => x` Example: ```typescript const myFunc = (value: string) => { return value.toUpperCase() } ``` ### Import Organization Imports are auto-organized by Biome. Order: 1. External dependencies 2. Internal imports using `@/*` alias 3. Type imports (use `type` keyword when importing only types) Example: ```typescript import { createFileRoute } from '@tanstack/react-router' import { createServerFn } from '@tanstack/react-start' import { db } from '@/db' import type { ReactNode } from 'react' ``` ### TypeScript **Strict Mode**: Enabled with additional strictness flags - `strict: true` - `noUncheckedIndexedAccess: true` - Array/object indexing returns `T | undefined` - `noImplicitOverride: true` - `noFallthroughCasesInSwitch: true` **Module Resolution**: `bundler` mode with `verbatimModuleSyntax` - Always use `.ts`/`.tsx` extensions in imports - Use `@/*` path alias for `src/*` **Type Annotations**: - Always annotate function parameters and return types for public APIs - Prefer explicit types over `any` - Use `type` for object shapes, `interface` for extendable contracts - Use `Readonly` for immutable props Example: ```typescript function getTodos(): Promise { return db.query.todoTable.findMany() } type Props = Readonly<{ children: ReactNode }> ``` ### Naming Conventions - **Files**: kebab-case for utilities, PascalCase for components - `utils.ts`, `todo.tsx`, `NotFound.tsx` - **Routes**: Use TanStack Router conventions - `routes/index.tsx` → `/` - `routes/todo.tsx` → `/todo` - `routes/__root.tsx` → Root layout - **Components**: PascalCase function declarations - **Functions**: camelCase - **Constants**: UPPER_SNAKE_CASE for true constants, camelCase for config objects - **Types/Interfaces**: PascalCase ### React Patterns **Components**: Use arrow functions (enforced by Biome rule `useArrowFunction`) ```typescript const MyComponent = ({ title }: { title: string }) => { return
{title}
} ``` **Server Functions**: Use TanStack Start's `createServerFn` ```typescript const getTodos = createServerFn({ method: 'GET' }).handler(async () => { const todos = await db.query.todoTable.findMany() return todos }) ``` **Routing**: Use `createFileRoute` for route definitions ```typescript export const Route = createFileRoute('/todo')({ component: Todo, }) ``` **Data Fetching**: Use TanStack Query hooks - `useSuspenseQuery` for guaranteed data - `useQuery` when data might be optional **Props**: No direct prop mutations (enforced by `noReactPropAssignments`) ### Database Schema (Drizzle) - Define schemas in `src/db/schema/*.ts` - Export from `src/db/schema/index.ts` - Use PostgreSQL types from `drizzle-orm/pg-core` - Use `uuidv7()` for primary keys (requires PostgreSQL extension) - Always include `createdAt` and `updatedAt` timestamps Example: ```typescript import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core' import { sql } from 'drizzle-orm' 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 - Use `@t3-oss/env-core` for type-safe env validation - Define schema in `src/env.ts` - Server vars: No prefix - Client vars: Must have `VITE_` prefix - Always validate with Zod schemas ### Error Handling - Use try-catch for async operations - Throw errors with descriptive messages - Prefer Result types or error boundaries for user-facing errors - Log errors appropriately (avoid logging sensitive data) ### Styling (Tailwind CSS) - Use Tailwind v4 utility classes - Import styles via `@/styles.css?url` - Prefer composition over custom CSS - Use responsive modifiers: `sm:`, `md:`, `lg:` - Use Chinese text for UI when appropriate (as seen in codebase) ## File Structure ``` src/ ├── components/ # Reusable React components ├── db/ │ ├── schema/ # Drizzle schema definitions │ └── index.ts # Database instance ├── lib/ # Utility functions ├── orpc/ # ORPC (RPC layer) │ ├── contracts/ # Contract definitions (input/output schemas) │ ├── handlers/ # Server-side procedure implementations │ ├── middlewares/ # Middleware (e.g., DB provider) │ ├── contract.ts # Contract aggregation │ ├── router.ts # Router composition │ ├── server.ts # Server instance │ ├── client.ts # Isomorphic client │ └── types.ts # Type utilities ├── routes/ # TanStack Router file-based routes │ ├── __root.tsx # Root layout │ ├── index.tsx # Home page │ └── api/rpc.$.ts # ORPC HTTP endpoint ├── env.ts # Environment variable validation ├── index.ts # Application entry point ├── router.tsx # Router configuration ├── routeTree.gen.ts # Auto-generated (DO NOT EDIT) └── styles.css # Global styles ``` ## Important Notes - **DO NOT** edit `src/routeTree.gen.ts` - it's auto-generated - **DO NOT** commit `.env` files - use `.env.example` for templates - **DO** run `bun fix` before committing to ensure code quality - **DO** use the `@/*` path alias instead of relative imports - **DO** leverage React Compiler (babel-plugin-react-compiler) - avoid manual memoization ## Git Workflow 1. Make changes following the style guidelines above 2. Run `bun fix` to auto-format and lint 3. Run `bun typecheck` to ensure type safety 4. Test changes locally with `bun dev` 5. Commit with clear, descriptive messages ## Common Patterns ### Adding a New Route 1. Create `src/routes/my-route.tsx` 2. Export route with `createFileRoute` 3. Route tree auto-updates on save ### Adding Database Table 1. Create schema in `src/db/schema/my-table.ts` 2. Export from `src/db/schema/index.ts` 3. Run `bun db:generate` to create migration 4. Run `bun db:migrate` to apply migration ### Creating Server Function ```typescript const myServerFn = createServerFn({ method: 'POST' }) .validator((data) => mySchema.parse(data)) .handler(async ({ data }) => { // Server-side logic here return result }) ``` ### Creating ORPC Procedures **Step 1: Define Contract** (`src/orpc/contracts/my-feature.ts`) ```typescript import { oc } from '@orpc/contract' import { z } from 'zod' export const myContract = { get: oc.input(z.object({ id: z.uuid() })).output(mySchema), create: oc.input(createSchema).output(mySchema), } ``` **Step 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 }) => { const item = await context.db.query.myTable.findFirst(...) return item }) ``` **Step 3: Register in Contract & Router** ```typescript // src/orpc/contract.ts export const contract = { myFeature: myContract, } // src/orpc/router.ts import * as myFeature from './handlers/my-feature' export const router = os.router({ myFeature }) ``` **Step 4: Use in Component** ```typescript import { orpc } from '@/orpc' const query = useSuspenseQuery(orpc.myFeature.get.queryOptions({ id })) const mutation = useMutation(orpc.myFeature.create.mutationOptions()) ``` --- **Last Updated**: 2026-01-18 **Project Version**: Based on package.json dependencies