# CLAUDE.md

Instructions for Claude Code working in this repository.

---

## Agent Behavior

**Always use `pnpm`** — never use `npm`, `yarn`, or `npx`:

- Use `pnpm exec` to run local binaries (e.g., `pnpm exec prisma migrate status`)
- Use `pnpm dlx` for one-off package execution (e.g., `pnpm dlx ts-prune`)

**Do NOT** run long-running processes without explicit permission:

- `pnpm dev`, `pnpm dev:start`, or any dev server commands
- Any command that starts servers, watchers, or blocking processes

**Always ask first** before:

- Starting development servers
- Running Docker containers
- Executing build watchers
- Any process that won't terminate on its own

**Before coding**: Read relevant docs. Never guess endpoint behavior or payload shapes.

---

## Quick Reference

```bash
# Build domain (required after domain changes)
pnpm domain:build

# Type check & lint
pnpm type-check
pnpm lint

# Database
pnpm db:migrate
pnpm db:generate

# Tests
pnpm test
pnpm --filter @customer-portal/bff test
```

**Ports**: Frontend :3000 | Backend :4000/api | Prisma Studio :5555

---

## Architecture

### Monorepo

```
apps/
├── portal/     # Next.js 15 (React 19, Tailwind, shadcn/ui)
└── bff/        # NestJS 11 (Prisma, BullMQ, Zod)

packages/
└── domain/     # Shared contracts, schemas, provider mappers
```

### Three-Layer Boundary

| Layer  | Location           | Purpose                                            |
| ------ | ------------------ | -------------------------------------------------- |
| Domain | `packages/domain/` | Contracts, Zod schemas, provider mappers           |
| BFF    | `apps/bff/`        | HTTP boundary, orchestration, integrations         |
| Portal | `apps/portal/`     | UI layer, thin route wrappers over feature modules |

### Systems of Record

- **WHMCS**: Billing, subscriptions, invoices, authoritative addresses
- **Salesforce**: CRM (Accounts, Contacts, Cases), order snapshots
- **Portal**: UI + BFF orchestration

---

## Import Rules (ESLint Enforced)

```typescript
// Allowed (Portal + BFF)
import type { Invoice } from "@customer-portal/domain/billing";
import { invoiceSchema } from "@customer-portal/domain/billing";
import { Formatting } from "@customer-portal/domain/toolkit";

// Allowed (BFF only)
import { Whmcs } from "@customer-portal/domain/billing/providers";

// Forbidden everywhere
import { Billing } from "@customer-portal/domain"; // root import
import { Invoice } from "@customer-portal/domain/billing/contract"; // deep import

// Forbidden in Portal
import { Whmcs } from "@customer-portal/domain/billing/providers"; // provider adapters
```

---

## Domain Package

Each module follows:

```
packages/domain/<module>/
├── contract.ts       # Normalized types (provider-agnostic)
├── schema.ts         # Zod validation
├── index.ts          # Public exports
└── providers/        # Provider-specific (BFF-only)
    └── whmcs/
        ├── raw.types.ts   # Raw API response types
        └── mapper.ts      # Transform raw → domain
```

**Key principle**: Map once in domain mappers, use domain types everywhere.

---

## Portal Structure

```
apps/portal/src/
├── app/           # Next.js App Router (thin shells, NO API calls)
├── components/    # Atomic: atoms/ molecules/ organisms/ templates/
├── core/          # Infrastructure: api/, logger/, providers/
├── features/      # Feature modules (api/, stores/, hooks/, views/)
└── shared/        # Cross-feature: hooks/, utils/, constants/
```

### Feature Module Pattern

```
features/<name>/
├── api/           # Data fetching (uses core/api/apiClient)
├── stores/        # Zustand state
├── hooks/         # React Query hooks
├── components/    # Feature UI
├── views/         # Page-level views
└── index.ts       # Public exports (barrel)
```

**Rules**:

- Pages are thin wrappers importing views from features
- No API calls in `app/` directory
- No business logic in frontend; use services and APIs

---

## BFF Patterns

### Integration Layer

```
apps/bff/src/integrations/{provider}/
├── services/
│   ├── {provider}-connection.service.ts
│   └── {provider}-{entity}.service.ts
└── utils/
    └── {entity}-query-builder.ts
```

### Data Flow

```
External API → Integration Service → Domain Mapper → Domain Type → Use Directly
              (fetch + query)        (transform once)  (return)
```

**Integration services**:

1. Build queries (SOQL, API params)
2. Execute API calls
3. Use domain mappers to transform
4. Return domain types
5. NO additional mapping or business logic

### Controllers

- Thin: no business logic, no Zod imports
- Use `createZodDto(schema)` + global `ZodValidationPipe`

---

## Validation (Zod-First)

- Schemas in domain: `packages/domain/<module>/schema.ts`
- Derive types: `export type X = z.infer<typeof xSchema>`
- Query params: use `z.coerce.*` for URL strings

---

## Code Standards

- No `any` in public APIs
- No `console.log` (use logger: `nestjs-pino` for BFF, `@/core/logger` for Portal)
- Avoid `V2` suffix in service names
- No unsafe assertions
- Reuse existing types and functions; extend when needed

---

## Documentation

Read before implementing:

| Topic               | Location                                       |
| ------------------- | ---------------------------------------------- |
| Overview            | `docs/README.md`                               |
| BFF patterns        | `docs/development/bff/integration-patterns.md` |
| Portal architecture | `docs/development/portal/architecture.md`      |
| Domain structure    | `docs/development/domain/structure.md`         |
| Salesforce          | `docs/integrations/salesforce/`                |
| WHMCS               | `docs/integrations/whmcs/`                     |
