Files
fullstack-starter/AGENTS.md

194 lines
5.6 KiB
Markdown

# 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 <div>{title}</div>
}
// 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<T>` 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