Files
seastem-electronjs/AGENTS.md
T

11 KiB

AGENTS.md - AI Coding Agent Guidelines

Guidelines for AI agents working in this project.

Project Overview

This project uses Bun exclusively as both the JavaScript runtime and package manager. Do NOT use Node.js / npm / yarn / pnpm. All commands start with bun — use bun install for dependencies and bun run <script> for scripts. Always prefer bun run <script> over bun <script> to avoid conflicts with Bun built-in subcommands (e.g. bun build invokes Bun's bundler, NOT your package.json script). Never use npm, npx, or node.

  • Framework: TanStack Start (React 19 SSR, file-based routing)
  • Runtime: Bun (see mise.toml for version) — NOT Node.js
  • Package Manager: Bun — NOT npm / yarn / pnpm
  • Language: TypeScript (strict mode)
  • Styling: Tailwind CSS v4
  • Database: PostgreSQL + Drizzle ORM v1 beta (drizzle-orm/postgres-js, RQBv2)
  • State: TanStack Query v5
  • RPC: ORPC (contract-first, type-safe)
  • Build: Vite + Nitro

Build / Lint / Test Commands

# Development
bun run dev                    # Vite dev server (localhost:3000)
bun run db:studio              # Drizzle Studio GUI

# Build
bun run build                  # Production build → .output/
bun run compile                # Compile to standalone binary (current platform, depends on build)
bun run compile:darwin         # Compile for macOS (arm64 + x64)
bun run compile:darwin:arm64   # Compile for macOS arm64
bun run compile:darwin:x64     # Compile for macOS x64
bun run compile:linux          # Compile for Linux (x64 + arm64)
bun run compile:linux:arm64    # Compile for Linux arm64
bun run compile:linux:x64      # Compile for Linux x64
bun run compile:windows        # Compile for Windows (default: x64)
bun run compile:windows:x64    # Compile for Windows x64

# Code Quality
bun run fix                    # Biome auto-fix
bun run typecheck              # TypeScript check

# Database (Drizzle)
bun run db:generate            # Generate migrations from schema
bun run db:migrate             # Run migrations
bun run db:push                # Push schema directly (dev only)

# Testing (not yet configured)
bun test path/to/test.ts       # Run single test
bun test -t "pattern"          # Run tests matching pattern

Code Style (TypeScript)

Formatting (Biome)

  • Indent: 2 spaces | Line endings: LF
  • Quotes: Single ' | Semicolons: Omit (ASI)
  • Arrow parentheses: Always (x) => x

Imports

Biome auto-organizes. Order: 1) External packages → 2) Internal @/* aliases → 3) Type imports (import type { ... })

import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
import { db } from '@/server/db'
import type { ReactNode } from 'react'

TypeScript Strictness

  • strict: true, noUncheckedIndexedAccess: true, noImplicitOverride: true, verbatimModuleSyntax: true
  • Use @/* path aliases (maps to src/*)

Naming Conventions

Type Convention Example
Files (utils) kebab-case auth-utils.ts
Files (components) PascalCase UserProfile.tsx
Components PascalCase arrow const Button = () => {}
Functions camelCase getUserById
Constants UPPER_SNAKE MAX_RETRIES
Types/Interfaces PascalCase UserProfile

React Patterns

  • Components: arrow functions (enforced by Biome)
  • Routes: TanStack Router file conventions (export const Route = createFileRoute(...))
  • Data fetching: useSuspenseQuery(orpc.feature.list.queryOptions())

Error Handling

  • Use try-catch for async operations; throw descriptive errors
  • ORPC: Use ORPCError with proper codes (NOT_FOUND, INPUT_VALIDATION_FAILED)
  • Never use empty catch blocks

ORPC Pattern

1. Define Contract (src/server/api/contracts/feature.contract.ts)

import { oc } from '@orpc/contract'
import { createSelectSchema } from 'drizzle-orm/zod'
import { z } from 'zod'
import { featureTable } from '@/server/db/schema'

const selectSchema = createSelectSchema(featureTable)

export const list = oc.input(z.void()).output(z.array(selectSchema))
export const create = oc.input(insertSchema).output(selectSchema)

2. Implement Router (src/server/api/routers/feature.router.ts)

import { ORPCError } from '@orpc/server'
import { db } from '../middlewares'
import { os } from '../server'

export const list = os.feature.list.use(db).handler(async ({ context }) => {
  return await context.db.query.featureTable.findMany({
    orderBy: { createdAt: 'desc' },
  })
})

3. Register in Index Files

// src/server/api/contracts/index.ts
import * as feature from './feature.contract'
export const contract = { feature }

// src/server/api/routers/index.ts
import * as feature from './feature.router'
export const router = os.router({ feature })

4. Use in Components

import { useSuspenseQuery, useMutation } from '@tanstack/react-query'
import { orpc } from '@/client/orpc'

const { data } = useSuspenseQuery(orpc.feature.list.queryOptions())
const mutation = useMutation(orpc.feature.create.mutationOptions())

Database (Drizzle ORM v1 beta + postgres-js)

  • ORM: Drizzle ORM 1.0.0-beta (RQBv2)
  • Driver: drizzle-orm/postgres-js (NOT bun-sql)
  • Validation: drizzle-orm/zod (built-in, NOT separate drizzle-zod package)
  • Relations: Defined via defineRelations() in src/server/db/relations.ts (contains schema info, so drizzle() only needs { relations })
  • Query style: RQBv2 object syntax (orderBy: { createdAt: 'desc' }, where: { id: 1 })

Schema Definition

import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
import { sql } from 'drizzle-orm'

export const myTable = pgTable('my_table', {
  id: uuid().primaryKey().default(sql`uuidv7()`),
  name: text().notNull(),
  createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
  updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow().$onUpdateFn(() => new Date()),
})

Relations (RQBv2)

// src/server/db/relations.ts
import { defineRelations } from 'drizzle-orm'
import * as schema from './schema'

export const relations = defineRelations(schema, (r) => ({
  // Define relations here using r.one / r.many / r.through
}))

DB Instance

// src/server/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js'
import { relations } from '@/server/db/relations'
// In RQBv2, relations already contain schema info — no separate schema import needed

const db = drizzle({
  connection: env.DATABASE_URL,
  relations,
})

RQBv2 Query Examples

// Object-style orderBy (NOT callback style)
const todos = await db.query.todoTable.findMany({
  orderBy: { createdAt: 'desc' },
})

// Object-style where
const todo = await db.query.todoTable.findFirst({
  where: { id: someId },
})

Environment Variables

  • Use @t3-oss/env-core with Zod validation in src/env.ts
  • Server vars: no prefix | Client vars: VITE_ prefix required
  • Never commit .env files
// src/env.ts
import { createEnv } from '@t3-oss/env-core'
import { z } from 'zod'

export const env = createEnv({
  server: {
    DATABASE_URL: z.string().url(),
  },
  clientPrefix: 'VITE_',
  client: {
    VITE_API_URL: z.string().optional(),
  },
})

Development Principles

These principles apply to ALL code changes. Agents MUST follow them on every task.

  1. No backward compatibility — This project is in rapid iteration. Always use the latest API and patterns. Never keep deprecated code paths or old API fallbacks "just in case".
  2. Always sync documentation — When code changes, immediately update all related documentation (AGENTS.md, README.md, inline code examples). Code and docs must never drift apart. This includes updating code snippets in docs when imports, APIs, or patterns change.
  3. Forward-only migration — When upgrading dependencies, fully adopt the new API. Don't mix old and new patterns in the same codebase.

Critical Rules

DO:

  • Run bun run fix before committing
  • Use @/* path aliases (not relative imports)
  • Include createdAt/updatedAt on all tables
  • Use ORPCError with proper codes
  • Use drizzle-orm/zod (NOT drizzle-zod) for schema validation
  • Use RQBv2 object syntax for orderBy and where
  • Update AGENTS.md and other docs whenever code patterns change

DON'T:

  • Use npm, npx, node, yarn, pnpm — always use bun / bunx
  • Edit src/routeTree.gen.ts (auto-generated)
  • Use as any, @ts-ignore, @ts-expect-error
  • Commit .env files
  • Use empty catch blocks catch(e) {}
  • Import from drizzle-zod (use drizzle-orm/zod instead)
  • Use RQBv1 callback-style orderBy / old relations() API
  • Use drizzle-orm/bun-sql driver (use drizzle-orm/postgres-js)
  • Pass schema to drizzle() constructor (only relations is needed in RQBv2)
  • Import os from @orpc/server in middleware — use @/server/api/server (the local typed instance)
  • Leave docs out of sync with code changes

Git Workflow

  1. Make changes following style guide
  2. bun run fix - auto-format and lint
  3. bun run typecheck - verify types
  4. bun run dev - test locally
  5. Commit with descriptive message

Directory Structure

.
├── src/
│   ├── client/                # Client-side code
│   │   └── orpc.ts            # ORPC client + TanStack Query utils (single entry point)
│   ├── components/            # React components
│   ├── routes/                # TanStack Router file routes
│   │   ├── __root.tsx         # Root layout
│   │   ├── index.tsx          # Home page
│   │   └── api/
│   │       ├── $.ts           # OpenAPI handler + Scalar docs
│   │       ├── health.ts      # Health check endpoint
│   │       └── rpc.$.ts       # ORPC RPC handler
│   ├── server/                # Server-side code
│   │   ├── api/               # ORPC layer
│   │   │   ├── contracts/     # Input/output schemas (Zod)
│   │   │   ├── middlewares/   # Middleware (db provider, auth)
│   │   │   ├── routers/       # Handler implementations
│   │   │   ├── interceptors.ts # Shared error interceptors
│   │   │   ├── context.ts     # Request context
│   │   │   ├── server.ts      # ORPC server instance
│   │   │   └── types.ts       # Type exports
│   │   └── db/
│   │       ├── schema/        # Drizzle table definitions
│   │       ├── fields.ts      # Shared field builders (id, createdAt, updatedAt)
│   │       ├── relations.ts   # Drizzle relations (defineRelations, RQBv2)
│   │       └── index.ts       # Database instance (postgres-js driver)
│   ├── env.ts                 # Environment variable validation
│   ├── router.tsx             # Router configuration
│   ├── routeTree.gen.ts       # Auto-generated (DO NOT EDIT)
│   └── styles.css             # Tailwind entry
├── biome.json                 # Linting/formatting config
├── compile.ts                 # Cross-platform binary compilation script
├── drizzle.config.ts          # Drizzle Kit config
├── vite.config.ts             # Vite + TanStack Start + Nitro config
├── tsconfig.json              # TypeScript config
└── package.json               # Dependencies and scripts