Lint-enforce two architecture rules that were only documented (ADR-0001), landing the rules with the fixes so the build stays green: - contracts/ imports nothing: dashboard-view.dto.ts is now pure wire shapes (inline string-union enums, no domain imports). The DashboardView FE-view type moves to the adapter, which maps wire → domain (compiler-enforced seam). - ApiClient lives only in infrastructure: change-request-form (UI) no longer injects ApiClient — a new ChangeRequestAdapter owns the client and the submit becomes a createSubmitChangeRequest() command factory (createDraftSync shape). draft-sync's wire-DTO import becomes type-only (allowed via allowTypeImports). - Role type moves to shared/domain/role.ts; the ?role= reader stays in shared/infrastructure/role.ts. - eslint: contracts import-ban + @typescript-eslint/no-restricted-imports on api-client (value-only; type imports permitted; infra + shared/upload exempt). Also fixes a PRE-EXISTING bug found while verifying the flow: change-request-form never imported FormsModule, so (ngSubmit) didn't bind and the submit button did a native form submit (page reload) instead of submitting. Verified end-to-end in the running app: submit → command → adapter → backend → reference, success alert shown. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
80 lines
3.8 KiB
Markdown
80 lines
3.8 KiB
Markdown
# WP-03 — Boundaries I: contracts purity + ApiClient confinement
|
|
|
|
Status: done (pending commit)
|
|
Phase: 0 — enforcement & gates
|
|
|
|
## Why
|
|
|
|
`contracts/` is the FE⇄BE wire seam and must import **nothing** (CLAUDE.md §1,
|
|
ADR-0001) — but `registratie/contracts/dashboard-view.dto.ts` imports domain types, and
|
|
`change-request-form.component.ts` injects `ApiClient` straight into UI. Neither rule is
|
|
lint-enforced. Rule + fixes land together so this WP ends green.
|
|
|
|
## Read first
|
|
|
|
- `CLAUDE.md` §1 + §4 (layers, BFF-lite)
|
|
- `eslint.config.mjs` (existing zone style to extend)
|
|
- `src/app/registratie/contracts/dashboard-view.dto.ts`
|
|
- `src/app/registratie/infrastructure/dashboard-view.adapter.ts`
|
|
- `src/app/registratie/ui/change-request-form/change-request-form.component.ts` (~line 53)
|
|
- `src/app/registratie/application/submit-change-request.ts`
|
|
- `src/app/shared/infrastructure/role.ts` and `src/app/brief/ui/letter-composer/letter-composer.component.ts` (line 2)
|
|
|
|
## Decisions (pre-made, don't relitigate)
|
|
|
|
- Wire DTOs express enums as plain string-literal unions **inlined in the DTO file** —
|
|
they describe the wire, not the domain. The adapter's `parse*` maps them to domain types.
|
|
- The `Role` **type** moves out of infrastructure (to `shared/domain` or
|
|
`shared/application` — pick where its current consumers point most naturally); the
|
|
role-reading mechanism (`role.interceptor.ts` etc.) stays infrastructure.
|
|
|
|
## Files
|
|
|
|
- `eslint.config.mjs` — two new restrictions
|
|
- `src/app/registratie/contracts/dashboard-view.dto.ts` — remove domain imports
|
|
- `src/app/registratie/infrastructure/dashboard-view.adapter.ts` — absorb the mapping
|
|
- `src/app/registratie/ui/change-request-form/change-request-form.component.ts` — stop
|
|
injecting `ApiClient`; the component calls the application command, which owns the
|
|
client dependency (move the `inject` into `submit-change-request.ts` or a thin
|
|
registratie adapter, matching how other commands get the client)
|
|
- `src/app/shared/infrastructure/role.ts` + new home for the `Role` type + the
|
|
`letter-composer.component.ts` / `brief.store.ts` import sites
|
|
|
|
## Steps
|
|
|
|
1. Rewrite `dashboard-view.dto.ts` as pure wire shapes (no imports at all); update the
|
|
adapter's `parse*`/`toDomain` to map wire → domain; run the adapter spec.
|
|
2. Move the `Role` type; update import sites (mechanical; lint will find them).
|
|
3. Refactor change-request-form: UI keeps dispatching to the machine; the submit command
|
|
receives/owns the `ApiClient` (pattern: other `submit-*.ts` commands).
|
|
4. Add eslint zones:
|
|
- `src/app/**/contracts/**` may import nothing from `@angular/*`, any `@*` context
|
|
alias, or relative app code.
|
|
- `@shared/infrastructure/api-client` importable only from `**/infrastructure/**`
|
|
(+ the app config/provider and the generated file itself).
|
|
5. Lint the repo; there must be zero new disables.
|
|
|
|
## Acceptance criteria
|
|
|
|
- [x] `dashboard-view.dto.ts` has no import statements.
|
|
- [x] No value import of `@shared/infrastructure/api-client` outside `infrastructure/`
|
|
(the only remaining non-infra reference is draft-sync's type-only DTO import,
|
|
allowed by `allowTypeImports`). ApiClient confinement is lint-enforced.
|
|
- [x] Both eslint rules active; planted violations fail lint (contracts→domain,
|
|
UI→ApiClient value); type-only DTO import still passes.
|
|
- [x] Change-request flow works — verified end-to-end in the running app (submit →
|
|
command → adapter → backend → reference `BIG-2026-…`, success alert); specs green.
|
|
|
|
## Verification
|
|
|
|
GREEN + `npm run test-storybook:ci`. Manual smoke: `npm start` → Gegevens wijzigen →
|
|
submit a change request.
|
|
|
|
## Out of scope
|
|
|
|
The `ui ↛ infrastructure` rule for adapter injections in wizards — that's WP-04.
|
|
|
|
## Risks
|
|
|
|
Role-type move ripples through brief imports — mechanical; run tests before/after.
|