--- name: bff-endpoint description: Add a screen-shaped read — backend endpoint returning a decision-enriched DTO, regenerated typed client, contracts DTO, adapter with parse boundary, application store, in the page. Use for any new data a page needs. --- # BFF-lite read endpoint (decision DTO) One screen = one endpoint returning everything that screen needs, **decisions included**. The FE renders the server's decisions; it never recomputes business rules (ADR-0001). Per rule choose: **decision flag** (server computes the boolean) or **config value** (server sends the threshold, FE applies it for instant feedback, server re-validates as authority). ## Steps 1. **Backend** (`backend/src/BigRegister.Api/`): minimal-API endpoint under `/api/v1`, wire DTO in `Contracts/`, rule in `Domain/` with a test in `backend/tests/BigRegister.Tests/`. Rejections are ProblemDetails (RFC 7807, 422). 2. **Regenerate the client**: `npm run gen:api` — commits `backend/swagger.json` + `src/app/shared/infrastructure/api-client.ts` (CI has a drift check; never edit the generated file). 3. **DTO** in `/contracts/.dto.ts` — plain interfaces/string-literal unions. Contracts import **nothing** (lint-enforced): no Angular, no aliases, no relative imports. 4. **Adapter** in `/infrastructure/.adapter.ts` — the only place HTTP lives: ```ts @Injectable({ providedIn: 'root' }) export class DashboardViewAdapter { private client = inject(ApiClient); viewResource() { return resource({ loader: () => this.client.dashboardView() }); } } // The trust boundary: validate the untrusted shape AND map DTO → domain. export function parseDashboardView(json: unknown): Result { … } ``` The mapping may read as an identity copy today — the point is the **types** differ, so the compiler forces a real mapping the moment the wire diverges. 5. **Application store** exposes the resource as `RemoteData` (`fromResource`, combine sources with `map`/`map2`/`andThen` from `@shared/application/remote-data`). UI never imports infrastructure (lint-enforced). 6. **Page** renders through `` (`shared/ui/async`) — one of loading/empty/error/loaded by construction. Check all four states with the dev-only `?scenario=slow|loading|empty|error` toggle. ## Worked example (trace one name through all layers) `dashboard-view`: `backend/src/BigRegister.Api` endpoint → `src/app/registratie/contracts/dashboard-view.dto.ts` → `src/app/registratie/infrastructure/dashboard-view.adapter.ts` (+ spec on `parseDashboardView`) → `src/app/registratie/application/big-profile.store.ts` → dashboard page. ## Verify ```bash cd backend && dotnet test && cd .. npm run gen:api && git diff --exit-code src/app/shared/infrastructure/api-client.ts npm test && npm run lint && npm run build ```