- Every customer-facing API endpoint MUST have
@UseGuards(HybridAuthGuard, PermissionGuard)and@RequirePermission('resource', 'action') @Public()is only acceptable for webhooks and unauthenticated endpoints (e.g., trust portal public pages)- No manual role string parsing (
role.includes('admin')) — always use permission check utilities - Frontend mutation buttons must be gated with
hasPermission(permissions, 'resource', 'action') - Raw
fetch()calls to the API must includecredentials: 'include' - Database queries must be scoped by
organizationIdfor multi-tenancy - Error messages must not leak internal details (stack traces, DB structure, internal IDs)
- No new server actions — client components should call the NestJS API directly via
apiClient/apior SWR hooks - No
useActionfromnext-safe-actionin new code - No direct
@dbimports in the Next.js app for mutations — all mutations go through the NestJS API - Server actions are acceptable ONLY for server-side-only operations like encryption/decryption that need access to server env vars
- Multi-step orchestration should use Next.js API routes (
apps/app/src/app/api/...), not server actions
- New UI must use
@trycompai/design-systemcomponents, not@trycompai/ui(legacy, being phased out) - Icons must come from
@trycompai/design-system/icons(Carbon icons), notlucide-react - DS components
Text,Stack,HStack,Badge,Buttondo not acceptclassName— wrap in a<div>for custom styling - Use DS
Buttonprops likeloading,iconLeft,iconRightinstead of manually rendering spinners/icons inside buttons
- No
as anycasts — use proper types, generics, orunknownwith type guards - No
@ts-ignoreor@ts-expect-error— fix the underlying type issue - Files must not exceed 300 lines — split into focused modules
- Client components should use
useSWRwithapiClientor custom hooks - Server components should fetch with
serverApiand pass asfallbackData mutate()optimistic update functions must guard againstundefinedinput- Use
Array.isArray()checks when consuming SWR data that could be stale
- New IDs must use prefixed CUIDs:
@default(dbgenerated("generate_prefixed_cuid('prefix'::text)")) - Operations modifying multiple records must use transactions
- Migrations must be backward-compatible
- Controllers must use
@Controller({ path: 'name', version: '1' }), NOT@Controller('v1/name')(causes double prefix bug) - API list endpoints return
{ data: [...], count }— single resource endpoints return the entity flat
- Forms must use React Hook Form + Zod validation
- No
useStatefor form field values — use the form's state management
- Pre-existing
@trycompai/uiusage in files not touched by the PR - Pre-existing
lucide-reactusage in files not touched by the PR - Pre-existing server actions in files not touched by the PR
- Test files using simplified mock types
- Generated files under
packages/db/prisma/generated/ - OpenAPI spec file
packages/docs/openapi.json