# AGENTS.md - AI Coding Agent Reference This file provides project-specific guidance for AI coding agents working in this repository. It reflects the current architecture and migration status of the codebase. --- ## Project Overview This project is a Next.js admin dashboard starter that is being migrated from template scaffolding to an app-owned architecture. Current core stack: - **Framework**: Next.js 16 (App Router) - **Language**: TypeScript 5.7 - **UI**: shadcn/ui + Tailwind CSS v4 - **Authentication**: Auth.js - **Persistence**: PostgreSQL + Drizzle ORM - **Data Fetching**: TanStack React Query - **URL State**: nuqs - **Forms**: TanStack Form + Zod - **Charts**: Recharts - **Monitoring**: Sentry - **Package Manager**: Bun preferred, npm acceptable Current migration status: - Auth has been moved to **Auth.js** - Multi-tenant data now uses app-owned **`users`**, **`organizations`**, and **`memberships`** - **`products`** has been migrated to Route Handlers + Drizzle - **`users`** has been migrated to Route Handlers + Drizzle - RBAC now uses a global `users.system_role` plus organization-scoped `memberships.role` - self-service sign-up is disabled; accounts must be created by an admin - some legacy pages still exist as placeholders or transitional seams --- ## Technology Stack Details ### Core Runtime - Next.js 16.x with App Router - React 19.x - TypeScript strict mode ### Styling & UI - Tailwind CSS v4 - shadcn/ui components - CSS custom properties and theme registry ### State Management - Zustand for local UI state - nuqs for URL search params - TanStack Form + Zod for forms ### Data Fetching - TanStack React Query for server state - server prefetch with `HydrationBoundary` + `dehydrate` - `useSuspenseQuery()` for client consumption of prefetched data ### Authentication & Authorization - Auth.js for authentication - app-owned `organizations` and `memberships` for multi-tenant access - global system role stored on `users.system_role` - organization role and permissions stored on `memberships` - client-side nav filtering is UX-only; real protection must be server-side ### Persistence & APIs - PostgreSQL + Drizzle ORM - Route Handlers under `src/app/api/**` are the main server boundary - feature services call those route handlers through `src/lib/api-client.ts` - mock APIs remain only as **legacy migration seams** --- ## Current Auth Model The current auth model is app-owned and centered around: - `users` - `organizations` - `memberships` Important files: - `src/auth.ts` - Auth.js config and exported helpers - `src/lib/auth/session.ts` - shared server-side auth helpers - `src/proxy.ts` - route protection - `src/db/schema.ts` - current database schema Current behavior: - authentication uses an Auth.js **Credentials** provider - passwords are verified against `users.password_hash` - global RBAC uses `users.system_role` with `super_admin | user` - organization RBAC uses `memberships.role` with `admin | user` - the user row stores `active_organization_id` - session enrichment loads organization and membership data on each request - self-service sign-up is disabled - initial super admin bootstrap is done with the seed script, not through the UI Preferred server-side helpers: - `requireSession()` - `requireSystemRole()` - `requireOrganizationAccess(options?)` Do not introduce new Clerk usage. --- ## Project Structure ```text /src ├── app/ # Next.js App Router │ ├── auth/ # Auth pages │ ├── dashboard/ # Dashboard routes │ ├── api/ # Route handlers │ ├── layout.tsx # Root layout │ └── page.tsx # Landing / redirect entry │ ├── components/ │ ├── ui/ # shadcn/ui and app UI primitives │ ├── layout/ # Sidebar, header, providers │ ├── forms/ # Form field wrappers │ ├── themes/ # Theme system │ └── icons.tsx # Single icon registry │ ├── db/ │ └── schema.ts # Drizzle schema │ ├── features/ │ ├── auth/ │ ├── overview/ │ ├── products/ # Migrated: React Query + Route Handlers + Drizzle │ ├── users/ # Migrated: React Query + Route Handlers + Drizzle + RBAC │ ├── react-query-demo/ │ ├── kanban/ │ ├── chat/ │ ├── notifications/ │ └── profile/ │ ├── config/ │ └── nav-config.ts │ ├── hooks/ │ ├── use-nav.ts │ └── use-data-table.ts │ ├── lib/ │ ├── api-client.ts │ ├── auth/ │ ├── db.ts │ ├── query-client.ts │ └── searchparams.ts │ ├── styles/ └── types/ ``` Relevant docs/plans: - `plans/req.md` - `plans/implementation-plan.md` - `docs/themes.md` - `docs/nav-rbac.md` - `docs/clerk_setup.md` is legacy historical reference only --- ## Build & Development Commands ```bash # Install dependencies bun install # or npm install # Development server bun run dev # or npm run dev # Build bun run build # or npm run build # Start bun run start # or npm run start # Lint bun run lint # or npm run lint # Format bun run format # or npm run format ``` --- ## Environment Configuration Copy `env.example.txt` to `.env.local` and configure: ### Required ```env AUTH_SECRET=your-long-random-secret DATABASE_URL=postgres://postgres:postgres@localhost:5432/training_system SUPER_ADMIN_EMAIL=admin@example.com SUPER_ADMIN_PASSWORD=change-this-password ``` ### Optional for Sentry ```env NEXT_PUBLIC_SENTRY_DSN=https://...@....ingest.sentry.io/... NEXT_PUBLIC_SENTRY_ORG=your-org NEXT_PUBLIC_SENTRY_PROJECT=your-project SENTRY_AUTH_TOKEN=sntrys_... NEXT_PUBLIC_SENTRY_DISABLED="false" ``` Notes: - keep auth and database secrets server-only - OAuth providers can be added later through Auth.js - billing is currently modeled as app-owned organization plan data, not a live provider integration - run `npm run setup:db` on first setup to apply migrations and seed the initial super admin --- ## Code Style Guidelines ### TypeScript - strict mode enabled - use explicit return types for public functions when useful - prefer `interface` for object contracts - use `@/*` imports from `src` ### Component Conventions - use function declarations for components - server components by default - add `'use client'` only when needed - use `cn()` for className merging ### Forms - use TanStack Form via `useAppForm` from `@/components/ui/tanstack-form` - use Zod for submit validation - avoid `useState` inside `AppField` render props ### Buttons - use `