20 KiB
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, andmemberships productshas been migrated to Route Handlers + Drizzleusershas been migrated to Route Handlers + Drizzle- RBAC now uses a global
users.system_roleplus organization-scopedmemberships.role - self-service sign-up is disabled; accounts must be created by an admin
- some legacy pages still exist as placeholders or transitional seams
Governance Layer
This repository now treats governance documentation as implementation input, not optional background reading.
Before any task that changes behavior, architecture, permissions, reporting, exports, PDFs, approvals, or CRM workflow, agents must review the relevant standards and foundations first.
Primary governance documents:
AGENTS.mddocs/standards/task-contract-template.mddocs/standards/task-catalog.mddocs/standards/project-foundations.mddocs/standards/architecture-rules.mddocs/standards/ui-ux-rules.mddocs/standards/task-review-checklist.mddocs/adr/**docs/business/**docs/security/**docs/implementation/**for the related feature lineage
Task Execution Rules
All tasks follow these rules:
- review first; implementation without review is prohibited
- reuse existing foundations before creating new modules, helpers, or flows
- extend existing services before creating duplicate services
- extend existing route groups before creating duplicate APIs
- extend existing permission models before creating duplicate permission systems
- extend existing export/report foundations before creating duplicate export flows
- keep business logic in feature service layers, not in route handlers or client components
- document any exception when an existing foundation cannot be reused
Required review order for non-trivial work:
AGENTS.mddocs/standards/**- relevant
docs/adr/** - relevant
docs/business/** - related completed tasks from
docs/standards/task-catalog.mdanddocs/implementation/** - existing foundations under
src/features/foundation/** - existing feature implementations under
src/features/** - existing APIs under
src/app/api/** - permission and access enforcement under
src/lib/auth/**,src/features/crm/security/**, anddocs/security/** - existing audit patterns under
src/features/foundation/audit-log/**
Historical Knowledge Rule
Before implementing any feature:
- review related foundations
- review related ADRs
- review related completed tasks from
docs/standards/task-catalog.md - reuse before creating
Implementation without historical review is prohibited.
Required Foundations
The following reusable foundations are mandatory whenever the task touches their area:
- Audit Foundation:
src/features/foundation/audit-log/** - Approval Foundation:
src/features/foundation/approval/** - Storage Foundation:
src/features/foundation/storage/**andsrc/features/foundation/document-artifact/** - PDF Foundation:
src/features/foundation/pdf-generator/**andsrc/features/crm/quotations/document/** - Report Foundation:
src/features/crm/reports/**,docs/adr/0017-report-foundation.md - CRM Authorization Foundation:
src/lib/auth/crm-access.ts,src/features/crm/security/**,docs/security/** - Customer Ownership Foundation:
docs/adr/0015-customer-ownership-contact-sharing.md,src/features/crm/customers/** - Contact Sharing Foundation:
docs/adr/0015-customer-ownership-contact-sharing.md,src/app/api/crm/customers/[id]/contacts/[contactId]/shares/**
If a task touches CRM leads, enquiries, quotations, approvals, reports, exports, storage, or PDFs, agents must explicitly check whether one of these foundations already solves part of the problem.
Architecture Constraints
Frontend constraints:
- Next.js App Router only
- server components first
PageContainerfor dashboard page headers- shadcn/ui primitives and existing app UI wrappers
- TanStack React Query with server prefetch +
HydrationBoundary+ clientuseSuspenseQuery()for data-heavy app pages - TanStack Form + Zod for forms
nuqsfor URL state- existing CRM terminology helpers and Thai labels for business-facing CRM UI
Backend constraints:
- Route Handlers are the main HTTP boundary
- feature service layer owns business logic
- Drizzle ORM owns database access
- auth and organization access go through
@/authandsrc/lib/auth/session.ts - CRM scope resolution goes through resolved-access helpers, not direct role-string branching
- audit logging goes through
src/features/foundation/audit-log/service.ts
Forbidden patterns:
- Redux for new application state
- SWR for new server-state fetching
- React Hook Form for new forms in this repo
- direct database access from client components
- business logic inside route handlers
- new Clerk usage
- direct role-string authorization such as
if (role === 'sales') - report-specific export systems that bypass
src/features/crm/reports/server/exports/service.ts
Mandatory Task Review
Before implementation, every non-trivial task must review:
- reusable foundations
- relevant ADRs
- related completed tasks from
docs/standards/task-catalog.md - related existing APIs
- permission and scope enforcement
- audit events and existing entity/action naming
- duplication risk across services, routes, exports, datasets, and UI shells
Implementation without this review is prohibited.
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
organizationsandmembershipsfor 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:
usersorganizationsmemberships
Important files:
src/auth.ts- Auth.js config and exported helperssrc/lib/auth/session.ts- shared server-side auth helperssrc/proxy.ts- route protectionsrc/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_rolewithsuper_admin | user - organization RBAC uses
memberships.rolewithadmin | 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
/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.mdplans/implementation-plan.mddocs/themes.mddocs/nav-rbac.mddocs/clerk_setup.mdis legacy historical reference only
Build & Development Commands
# 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
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
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:dbon 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
interfacefor object contracts - use
@/*imports fromsrc
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
useAppFormfrom@/components/ui/tanstack-form - use Zod for submit validation
- avoid
useStateinsideAppFieldrender props
Buttons
- use
<Button isLoading={isPending}>for loading states
Navigation & RBAC
Navigation is configured in src/config/nav-config.ts.
Current access properties:
systemRolerequireOrgpermissionroleplanfeature
Notes:
systemRoleis for global access such assuper_adminrequireOrgremains a compatibility seam and means the user must have an active organization- nav filtering in
src/hooks/use-nav.tsuses Auth.js session data - server routes and server pages must enforce real access independently
Authentication Patterns
Protected Routes
Dashboard routes are protected through src/proxy.ts.
Example protected page:
import { auth } from '@/auth';
import { redirect } from 'next/navigation';
export default async function Page() {
const session = await auth();
if (!session?.user) {
redirect('/auth/sign-in');
}
if (!session.user.activeOrganizationId) {
redirect('/dashboard/workspaces');
}
return <div>Protected</div>;
}
Shared Server Helpers
Prefer:
requireSession()requireSystemRole()requireOrganizationAccess(options?)
Plan / Feature Gating
Do not use vendor-specific gating components.
Read plan or feature state from app-owned organization data and enforce it server-side.
Data Fetching Patterns
Service Layer Architecture
Each feature should use:
src/features/<name>/api/
types.ts
service.ts
queries.ts
mutations.ts # when mutations exist
Rules:
- components import types from
types.ts - components call query options from
queries.ts - services call local route handlers through
apiClient - route handlers talk to Drizzle / database
Backend Patterns
Preferred patterns in this repo:
| Pattern | Guidance |
|---|---|
| Route Handlers + ORM | Preferred for app-owned features |
| Server Actions + ORM | Acceptable when it fits the feature well |
| BFF | Acceptable when proxying to an external backend |
| Mock | Legacy only, not the preferred path |
React Query
Preferred pattern for new pages:
- define query options in
queries.ts - prefetch in the server page
- hydrate with
HydrationBoundary - read with
useSuspenseQuery()
Required pattern for CRUD mutations:
- every feature must define centralized query key factories in
queries.ts - use grouped keys such as
all,lists(),list(filters),details(),detail(id), plus child-resource keys likecontacts(id)orfollowups(id)when needed - after create, explicitly invalidate the list-level key
- after update, explicitly invalidate both list and detail keys
- after delete, explicitly invalidate the list key and remove stale detail queries with
removeQueries - invalidate related queries affected by the mutation, such as counts, child tabs, document previews, approval panels, and cross-feature related lists
- do not rely on
staleTimealone to refresh production CRUD data - do not rely on
router.refresh()alone for CRUD freshness; React Query invalidation is required first, androuter.refresh()is optional only as a supplement
Mutation callback safety rule:
- if a shared mutation object from
api/mutations.tsis spread intouseMutation({ ...sharedMutation, ... }), do not overrideonSuccessoronSettledin a way that drops cache invalidation - prefer keeping cache invalidation inside the shared mutation definition
- if component-level success behavior is needed, either:
- put invalidation in shared
onSettledand keep UI concerns like toast and closing dialogs in componentonSuccess, or - call the shared invalidate helper explicitly from the component before closing the sheet/dialog
- put invalidation in shared
UI completion rule after successful mutation:
- close sheet/dialog only after the mutation promise resolves successfully
- table/detail views must show fresh data immediately without a full browser refresh
- related tabs and counts must refresh in the same interaction when their backing data changed
URL State
Use nuqs for table search params and list filters.
Tables
Use:
- TanStack Table
- React Query
useDataTable- column definitions under the feature component tree
Current Migration Guidance
Use the migrated products feature as the main reference for new CRUD work.
Important current status:
productsis migrated to Auth.js + Route Handlers + Drizzleusersis migrated to Auth.js + Route Handlers + Drizzle- new features should follow the
productsanduserspattern, not the old mock pattern
Legacy seams still present:
src/constants/mock-api.tssrc/constants/mock-api-users.ts- some placeholder pages under workspaces, billing, profile, and exclusive sections
Treat those as migration seams, not target architecture.
Deployment Notes
Production Requirements
Ensure these are set:
AUTH_SECRETDATABASE_URLSUPER_ADMIN_EMAILSUPER_ADMIN_PASSWORD- relevant
SENTRY_*variables if using Sentry
Build Considerations
- app builds as standalone
- database migrations must be applied before runtime
- production secrets must not be exposed as
NEXT_PUBLIC_*
Troubleshooting
Auth fails in development
- verify
AUTH_SECRET - verify
DATABASE_URL - verify migrations are applied
- verify the super admin seed has run and seeded users have valid
password_hash
Theme not applying
- verify theme CSS import in
src/styles/theme.css - verify theme registration in
theme.config.ts
Navigation items not showing
- check
accessinnav-config.ts - verify session has active organization, role, or permissions as needed
Feature data looks stale
- verify mutation invalidates the feature query key factory
- verify filters are included in the query key
External Documentation
- Next.js App Router
- Auth.js
- Drizzle ORM
- shadcn/ui
- Tailwind CSS v4
- TanStack Query
- TanStack Table
- TanStack Form
- Sentry Next.js
Notes for AI Agents
- Always use
cn()for className merging. - Respect the feature-based structure and keep new work inside
src/features/. - Server components are the default.
- Avoid
any; keep contracts explicit. - Use
PageContainerfor page headers instead of importingHeadingdirectly in pages. - Use TanStack Form + Zod via the existing project wrappers.
- Use icons only through
@/components/icons. - For new app-owned features, prefer Route Handlers + Drizzle + React Query.
- Never import from
@/constants/mock-api*directly in components. - Use
@/authandsrc/lib/auth/session.tsfor auth and organization checks. - Do not introduce new Clerk usage.
- Treat legacy mock-backed modules as migration seams.
- Follow the migrated
productsandusersfeature patterns when building new CRUD modules. - Do not re-enable self-service sign-up unless the product requirement changes explicitly.
- For every CRUD mutation, explicitly invalidate React Query caches for list/detail/related data; never rely on passive staleness recovery.
- Centralize query keys in each feature's
api/queries.ts; do not scatter manual string query keys in components. - When deleting entities, remove stale detail queries with
removeQueriesin addition to invalidating lists. - If a component overrides
useMutationcallbacks on top of a shared mutation config, preserve or explicitly call the shared invalidation behavior before closing UI. - After successful create/update/delete, sheets, dialogs, and destructive modals must close only after cache invalidation has been triggered and the UI can refresh from fresh data.
- Review
docs/standards/**, relevantdocs/adr/**,docs/business/**, anddocs/security/**before implementing non-trivial changes. - Review related completed task notes in
docs/standards/task-catalog.mdanddocs/implementation/**before implementing non-trivial changes. - Reuse foundations under
src/features/foundation/**andsrc/features/crm/**before introducing new services, routes, permissions, exports, or datasets. - Do not create duplicate report/export/PDF/approval/security plumbing when a project foundation already exists.
- CRM authorization must flow through resolved access helpers such as
resolveCrmMembershipAccess(),buildCrmSecurityContext(), or report-context builders; do not authorize with raw role strings alone.
Before implementing any task, review:
- AGENTS.md
- docs/standards/*
- docs/standards/task-catalog.md
- related ADRs
- related completed task documents
Implementation without review is prohibited.