325 lines
9.6 KiB
Markdown
325 lines
9.6 KiB
Markdown
# 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
|
|
- **Desktop Shell** (Optional): Tauri v2 (see `src-tauri/AGENTS.md` for desktop-specific guidelines)
|
|
|
|
## 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<T>` for immutable props
|
|
|
|
Example:
|
|
```typescript
|
|
function getTodos(): Promise<Todo[]> {
|
|
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 <div>{title}</div>
|
|
}
|
|
```
|
|
|
|
**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
|