---
name: feature-vertical-slice
description: Build a feature end-to-end — Prisma model → migration → .NET CQRS + controller → Angular feature → i18n → tests. Use when adding a new domain capability.
---

# Skill: Feature Vertical Slice

## When to use

A new domain capability that needs DB + API + UI. Driven by `/create-feature`.

## Order of operations (do NOT reorder)

1. **Schema first.** Add the model to `prisma/schema.prisma` with `id @default(cuid())`, audit fields, `deletedAt`, `tenantId`. Mirror EF entity + `database/migrations/`.
2. **Contracts.** Zod schemas in `packages/api-contracts/src/zod/<domain>.ts`.
3. **Backend.** `dotnet/src/Domain/<Domain>/` entity, `Application/<Domain>/{Commands,Queries}` (MediatR + FluentValidation), `Api/Controllers/<Domain>Controller.cs` returning `ApiResponse<T>`.
4. **Frontend.** `apps/web-angular/src/app/features/<domain>/` — list (`DataTableComponent`) + form (Reactive Forms), via `BaseApiService`.
5. **i18n.** Keys in `en.json` + `th.json`.
6. **Tests.** Validator test + handler test + component test.

## Hard-won gotchas

- EF reads MUST use `AsNoTracking().Select(...)` — projecting to the response DTO directly avoids lazy-load N+1.
- The `(tenantId, email)`-style unique indexes are **filtered** on `deletedAt IS NULL` — don't add a plain unique or soft-deleted rows collide.
- Angular signals: use `input.required<T>()` for required inputs; the template reads them as `name()`.
- Permission strings live in THREE places — keep them in lockstep: `permission.constants.ts`, `PermissionConstants.cs`, `prisma/seed.ts`.
- Run `pnpm db:migrate` only with user confirmation (it's an `ask` permission).

## Validation

`pnpm validate && dotnet build && dotnet test && pnpm --filter web-angular typecheck`
