forked from imbytecat/fullstack-starter
194 lines
5.6 KiB
Markdown
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
|