Files
alla-allaos-fullstack/AGENTS.md
phaichayon 56683ee7b9 Hotfix Rule: Force Fresh Data After CRUD Mutation
Task-h not implements
2026-06-16 11:59:41 +07:00

519 lines
14 KiB
Markdown

# 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 `<Button isLoading={isPending}>` for loading states
---
## Navigation & RBAC
Navigation is configured in `src/config/nav-config.ts`.
Current access properties:
- `systemRole`
- `requireOrg`
- `permission`
- `role`
- `plan`
- `feature`
Notes:
- `systemRole` is for global access such as `super_admin`
- `requireOrg` remains a compatibility seam and means the user must have an active organization
- nav filtering in `src/hooks/use-nav.ts` uses 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:
```tsx
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:
```text
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:
1. define query options in `queries.ts`
2. prefetch in the server page
3. hydrate with `HydrationBoundary`
4. read with `useSuspenseQuery()`
Required pattern for CRUD mutations:
1. every feature must define centralized query key factories in `queries.ts`
2. use grouped keys such as `all`, `lists()`, `list(filters)`, `details()`, `detail(id)`, plus child-resource keys like `contacts(id)` or `followups(id)` when needed
3. after create, explicitly invalidate the list-level key
4. after update, explicitly invalidate both list and detail keys
5. after delete, explicitly invalidate the list key and remove stale detail queries with `removeQueries`
6. invalidate related queries affected by the mutation, such as counts, child tabs, document previews, approval panels, and cross-feature related lists
7. do not rely on `staleTime` alone to refresh production CRUD data
8. do not rely on `router.refresh()` alone for CRUD freshness; React Query invalidation is required first, and `router.refresh()` is optional only as a supplement
Mutation callback safety rule:
- if a shared mutation object from `api/mutations.ts` is spread into `useMutation({ ...sharedMutation, ... })`, do not override `onSuccess` or `onSettled` in 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 `onSettled` and keep UI concerns like toast and closing dialogs in component `onSuccess`, or
- call the shared invalidate helper explicitly from the component before closing the sheet/dialog
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:
- `products` is migrated to Auth.js + Route Handlers + Drizzle
- `users` is migrated to Auth.js + Route Handlers + Drizzle
- new features should follow the `products` and `users` pattern, not the old mock pattern
Legacy seams still present:
- `src/constants/mock-api.ts`
- `src/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_SECRET`
- `DATABASE_URL`
- `SUPER_ADMIN_EMAIL`
- `SUPER_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 `access` in `nav-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](https://nextjs.org/docs/app)
- [Auth.js](https://authjs.dev)
- [Drizzle ORM](https://orm.drizzle.team)
- [shadcn/ui](https://ui.shadcn.com/docs)
- [Tailwind CSS v4](https://tailwindcss.com/docs)
- [TanStack Query](https://tanstack.com/query)
- [TanStack Table](https://tanstack.com/table/latest)
- [TanStack Form](https://tanstack.com/form)
- [Sentry Next.js](https://docs.sentry.io/platforms/javascript/guides/nextjs/)
---
## Notes for AI Agents
1. Always use `cn()` for className merging.
2. Respect the feature-based structure and keep new work inside `src/features/`.
3. Server components are the default.
4. Avoid `any`; keep contracts explicit.
5. Use `PageContainer` for page headers instead of importing `Heading` directly in pages.
6. Use TanStack Form + Zod via the existing project wrappers.
7. Use icons only through `@/components/icons`.
8. For new app-owned features, prefer Route Handlers + Drizzle + React Query.
9. Never import from `@/constants/mock-api*` directly in components.
10. Use `@/auth` and `src/lib/auth/session.ts` for auth and organization checks.
11. Do not introduce new Clerk usage.
12. Treat legacy mock-backed modules as migration seams.
13. Follow the migrated `products` and `users` feature patterns when building new CRUD modules.
14. Do not re-enable self-service sign-up unless the product requirement changes explicitly.
15. For every CRUD mutation, explicitly invalidate React Query caches for list/detail/related data; never rely on passive staleness recovery.
16. Centralize query keys in each feature's `api/queries.ts`; do not scatter manual string query keys in components.
17. When deleting entities, remove stale detail queries with `removeQueries` in addition to invalidating lists.
18. If a component overrides `useMutation` callbacks on top of a shared mutation config, preserve or explicitly call the shared invalidation behavior before closing UI.
19. 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.