style: format frontend, docs and skills with prettier; add .prettierignore

One-time prettier --write so the new format:check CI gate starts green.
.prettierignore excludes generated (api-client.ts, documentation.json),
vendored (public/cibg-huisstijl), and backend (dotnet format owns it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-03 13:39:31 +02:00
co-authored by Claude Opus 4.8
parent 546097434d
commit e82309786d
176 changed files with 5067 additions and 1469 deletions
+219 -50
View File
@@ -14,7 +14,13 @@ import { parsePostcode } from '@registratie/domain/value-objects/postcode';
/** Minimal fake Resource so <app-async> can be driven through every state without HTTP. */
function fakeResource<T>(status: string, value?: T, error?: Error): Resource<T> {
return { value: () => value as T, status: () => status, error: () => error, hasValue: () => value !== undefined, reload: () => {} } as unknown as Resource<T>;
return {
value: () => value as T,
status: () => status,
error: () => error,
hasValue: () => value !== undefined,
reload: () => {},
} as unknown as Resource<T>;
}
/** Teaching showcase: each section pairs the impossible-state-permitting"before"
@@ -22,38 +28,151 @@ function fakeResource<T>(status: string, value?: T, error?: Error): Resource<T>
@Component({
selector: 'app-concepts-page',
imports: [
FormsModule, PageShellComponent, HeadingComponent, TextInputComponent,
...ASYNC, SkeletonComponent, RegistrationSummaryComponent, HerregistratieWizardComponent, IntakeWizardComponent,
FormsModule,
PageShellComponent,
HeadingComponent,
TextInputComponent,
...ASYNC,
SkeletonComponent,
RegistrationSummaryComponent,
HerregistratieWizardComponent,
IntakeWizardComponent,
],
styles: [
`
.section {
margin: 0 0 3rem;
}
.lead {
color: var(--rhc-color-grijs-700);
max-width: 46rem;
margin: 0.25rem 0 1.25rem;
}
.cols {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
gap: 1.5rem;
align-items: start;
}
.card {
border: 1px solid var(--rhc-color-grijs-200, #e5e5e5);
border-radius: 10px;
padding: 1.25rem;
background: #fff;
}
.card--bad {
border-color: var(--rhc-color-rood-300, #f0b4b4);
}
.card--good {
border-color: var(--rhc-color-groen-300, #b4e0b4);
}
.tag {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-weight: 700;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0 0 0.75rem;
}
.tag::before {
content: '';
width: 0.6rem;
height: 0.6rem;
border-radius: 50%;
}
.tag.bad {
color: var(--rhc-color-rood-600, #a30000);
}
.tag.bad::before {
background: var(--rhc-color-rood-500, #d52b1e);
}
.tag.good {
color: var(--rhc-color-groen-700, #277337);
}
.tag.good::before {
background: var(--rhc-color-groen-500, #39870c);
}
.tag.plain {
color: var(--rhc-color-grijs-700);
}
.tag.plain::before {
display: none;
}
pre {
background: #1e2430;
color: #e6e9ef;
padding: 1rem;
border-radius: 8px;
overflow: auto;
font-size: 0.82rem;
line-height: 1.55;
margin: 0;
}
pre .k {
color: #c792ea;
}
pre .s {
color: #c3e88d;
}
pre .c {
color: #7e8aa0;
font-style: italic;
}
.note {
font-size: 0.9rem;
color: var(--rhc-color-grijs-700);
margin: 0.75rem 0 0;
}
/* live state diagram */
.machine {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0 0 1rem;
}
.node {
padding: 0.4rem 0.8rem;
border-radius: 999px;
border: 1px solid var(--rhc-color-grijs-300, #ccc);
font-size: 0.82rem;
color: var(--rhc-color-grijs-700);
transition: all 0.15s;
}
.node.on {
background: var(--rhc-color-hemelblauw-100, #e5f1fb);
border-color: var(--rhc-color-hemelblauw-500, #007bc7);
color: var(--rhc-color-hemelblauw-700, #00567d);
font-weight: 700;
}
.steplist {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
align-items: center;
margin: 0 0 1rem;
}
.pill {
padding: 0.3rem 0.7rem;
border-radius: 8px;
background: var(--rhc-color-grijs-100, #f3f3f3);
font-size: 0.8rem;
}
.pill.extra {
background: var(--rhc-color-geel-100, #fff6d6);
border: 1px dashed var(--rhc-color-geel-600, #c79a00);
}
.arrow {
color: var(--rhc-color-grijs-400, #999);
}
`,
],
styles: [`
.section { margin: 0 0 3rem }
.lead { color: var(--rhc-color-grijs-700); max-width: 46rem; margin: 0.25rem 0 1.25rem }
.cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 1.5rem; align-items: start }
.card { border: 1px solid var(--rhc-color-grijs-200, #e5e5e5); border-radius: 10px; padding: 1.25rem; background: #fff }
.card--bad { border-color: var(--rhc-color-rood-300, #f0b4b4) }
.card--good { border-color: var(--rhc-color-groen-300, #b4e0b4) }
.tag { display: inline-flex; align-items: center; gap: 0.4rem; font-weight: 700; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 0.75rem }
.tag::before { content: ''; width: 0.6rem; height: 0.6rem; border-radius: 50% }
.tag.bad { color: var(--rhc-color-rood-600, #a30000) } .tag.bad::before { background: var(--rhc-color-rood-500, #d52b1e) }
.tag.good { color: var(--rhc-color-groen-700, #277337) } .tag.good::before { background: var(--rhc-color-groen-500, #39870c) }
.tag.plain { color: var(--rhc-color-grijs-700) } .tag.plain::before { display: none }
pre { background: #1e2430; color: #e6e9ef; padding: 1rem; border-radius: 8px; overflow: auto; font-size: 0.82rem; line-height: 1.55; margin: 0 }
pre .k { color: #c792ea } pre .s { color: #c3e88d } pre .c { color: #7e8aa0; font-style: italic }
.note { font-size: 0.9rem; color: var(--rhc-color-grijs-700); margin: 0.75rem 0 0 }
/* live state diagram */
.machine { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1rem }
.node { padding: 0.4rem 0.8rem; border-radius: 999px; border: 1px solid var(--rhc-color-grijs-300, #ccc); font-size: 0.82rem; color: var(--rhc-color-grijs-700); transition: all .15s }
.node.on { background: var(--rhc-color-hemelblauw-100, #e5f1fb); border-color: var(--rhc-color-hemelblauw-500, #007bc7); color: var(--rhc-color-hemelblauw-700, #00567d); font-weight: 700 }
.steplist { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin: 0 0 1rem }
.pill { padding: 0.3rem 0.7rem; border-radius: 8px; background: var(--rhc-color-grijs-100, #f3f3f3); font-size: 0.8rem }
.pill.extra { background: var(--rhc-color-geel-100, #fff6d6); border: 1px dashed var(--rhc-color-geel-600, #c79a00) }
.arrow { color: var(--rhc-color-grijs-400, #999) }
`],
template: `
<app-page-shell heading="Onmogelijke toestanden onmogelijk maken" backLink="/dashboard">
<p class="lead">
Vijf functionele patronen die atomic design makkelijker maakt om te tonen — telkens
"fout" (de oude vorm liet het toe) naast"goed" (het type maakt het onmogelijk).
Vijf functionele patronen die atomic design makkelijker maakt om te tonen — telkens "fout"
(de oude vorm liet het toe) naast"goed" (het type maakt het onmogelijk).
</p>
<!-- 1. Discriminated unions -->
@@ -64,12 +183,17 @@ function fakeResource<T>(status: string, value?: T, error?: Error): Resource<T>
<div class="card card--bad">
<p class="tag bad">Fout — vlakke interface</p>
<pre [innerHTML]="unionBad"></pre>
<p class="note">Een doorgehaalde registratie houdt tóch een herregistratiedatum: onmogelijke toestand.</p>
<p class="note">
Een doorgehaalde registratie houdt tóch een herregistratiedatum: onmogelijke toestand.
</p>
</div>
<div class="card card--good">
<p class="tag good">Goed — sum type</p>
<app-registration-summary [reg]="doorgehaald" />
<p class="note">De variant <code>Doorgehaald</code> kent geen herregistratiedatum, dus de rij bestaat simpelweg niet.</p>
<p class="note">
De variant <code>Doorgehaald</code> kent geen herregistratiedatum, dus de rij bestaat
simpelweg niet.
</p>
</div>
</div>
</section>
@@ -77,23 +201,44 @@ function fakeResource<T>(status: string, value?: T, error?: Error): Resource<T>
<!-- 2. RemoteData fold -->
<section class="section">
<app-heading [level]="2">2 · RemoteData fold</app-heading>
<p class="lead">Eén waarde met vier elkaar uitsluitende toestanden in plaats van drie losse booleans.</p>
<p class="lead">
Eén waarde met vier elkaar uitsluitende toestanden in plaats van drie losse booleans.
</p>
<div class="cols">
<div class="card card--good">
<p class="tag good">Vier toestanden, één molecuul</p>
<p class="tag plain">Loading</p>
<app-async [resource]="loadingRes"><ng-template appAsyncLoaded let-v>{{ v }}</ng-template><ng-template appAsyncLoading><app-skeleton [count]="2" height="1.2rem" [delay]="0" /></ng-template></app-async>
<app-async [resource]="loadingRes"
><ng-template appAsyncLoaded let-v>{{ v }}</ng-template
><ng-template appAsyncLoading
><app-skeleton [count]="2" height="1.2rem" [delay]="0" /></ng-template
></app-async>
<p class="tag plain">Empty</p>
<app-async [resource]="emptyRes" [isEmpty]="isEmpty"><ng-template appAsyncLoaded let-v>{{ v }}</ng-template></app-async>
<app-async [resource]="emptyRes" [isEmpty]="isEmpty"
><ng-template appAsyncLoaded let-v>{{ v }}</ng-template></app-async
>
<p class="tag plain">Failure</p>
<app-async [resource]="errorRes"><ng-template appAsyncLoaded let-v>{{ v }}</ng-template></app-async>
<app-async [resource]="errorRes"
><ng-template appAsyncLoaded let-v>{{ v }}</ng-template></app-async
>
<p class="tag plain">Success</p>
<app-async [resource]="successRes" [isEmpty]="isEmpty"><ng-template appAsyncLoaded let-v><ul>@for (i of v; track i) {<li>{{ i }}</li>}</ul></ng-template></app-async>
<app-async [resource]="successRes" [isEmpty]="isEmpty"
><ng-template appAsyncLoaded let-v
><ul>
@for (i of v; track i) {
<li>{{ i }}</li>
}
</ul></ng-template
></app-async
>
</div>
<div class="card card--good">
<p class="tag good">De exhaustieve fold</p>
<pre [innerHTML]="foldCode"></pre>
<p class="note">Een nieuwe variant toevoegen breekt de compile via <code>assertNever</code> tot je hem afhandelt.</p>
<p class="note">
Een nieuwe variant toevoegen breekt de compile via <code>assertNever</code> tot je hem
afhandelt.
</p>
</div>
</div>
</section>
@@ -105,13 +250,21 @@ function fakeResource<T>(status: string, value?: T, error?: Error): Resource<T>
<div class="cols">
<div class="card">
<p class="tag good">Smart constructor → Result</p>
<app-text-input inputId="pc" [ngModel]="raw()" (ngModelChange)="raw.set($event)" name="pc" placeholder="Typ een postcode, bijv. 1234 AB" />
<app-text-input
inputId="pc"
[ngModel]="raw()"
(ngModelChange)="raw.set($event)"
name="pc"
placeholder="Typ een postcode, bijv. 1234 AB"
/>
</div>
<div class="card" [class.card--good]="parsed().ok" [class.card--bad]="!parsed().ok">
@if (parsed().ok) {
<p class="tag good">ok</p>
<pre>Postcode ="{{ $any(parsed()).value }}"</pre>
<p class="note">Een gevalideerde <code>Postcode</code> is een ander type dan een ruwe string.</p>
<p class="note">
Een gevalideerde <code>Postcode</code> is een ander type dan een ruwe string.
</p>
} @else {
<p class="tag bad">err</p>
<pre>{{ $any(parsed()).error }}</pre>
@@ -123,17 +276,22 @@ function fakeResource<T>(status: string, value?: T, error?: Error): Resource<T>
<!-- 4. State machine / wizard (live state diagram) -->
<section class="section">
<app-heading [level]="2">4 · Form als state machine</app-heading>
<p class="lead">Eén tagged union stuurt de UI. Speel met de wizard — de gemarkeerde toestand is de huidige.</p>
<p class="lead">
Eén tagged union stuurt de UI. Speel met de wizard — de gemarkeerde toestand is de
huidige.
</p>
<div class="cols">
<div class="card card--bad">
<p class="tag bad">Fout — losse booleans</p>
<pre [innerHTML]="machineBad"></pre>
<p class="note">Niets verhindert"submitting" mét validatiefouten of een successcherm met errors.</p>
<p class="note">
Niets verhindert"submitting" mét validatiefouten of een successcherm met errors.
</p>
</div>
<div class="card card--good">
<p class="tag good">Goed — één tagged union</p>
<div class="machine">
@for (n of ['Editing','Submitting','Submitted','Failed']; track n) {
@for (n of ['Editing', 'Submitting', 'Submitted', 'Failed']; track n) {
<span class="node" [class.on]="w.state().tag === n">{{ n }}</span>
}
</div>
@@ -144,11 +302,14 @@ function fakeResource<T>(status: string, value?: T, error?: Error): Resource<T>
<!-- 5. Fixed steps, questions revealed inline -->
<section class="section">
<app-heading [level]="2">5 · Vragenlijst met vaste stappen —"vragen tonen, niet stappen toevoegen"</app-heading>
<app-heading [level]="2"
>5 · Vragenlijst met vaste stappen —"vragen tonen, niet stappen toevoegen"</app-heading
>
<p class="lead">
Het aantal stappen ligt vast (<code>STEPS</code>); vervolgvragen verschijnen <em>binnen</em> een stap
op basis van eerdere antwoorden. Antwoord"ja" op buitenland of vul weinig uren in, en er komt een
extra vraag bij in dezelfde stap — de voortgang"van N" blijft gelijk.
Het aantal stappen ligt vast (<code>STEPS</code>); vervolgvragen verschijnen
<em>binnen</em> een stap op basis van eerdere antwoorden. Antwoord"ja" op buitenland of
vul weinig uren in, en er komt een extra vraag bij in dezelfde stap — de voortgang"van N"
blijft gelijk.
</p>
<div class="cols">
<div class="card card--good">
@@ -156,10 +317,15 @@ function fakeResource<T>(status: string, value?: T, error?: Error): Resource<T>
<div class="steplist">
@for (s of iw.steps; track s; let last = $last) {
<span class="pill">{{ s }}</span>
@if (!last) { <span class="arrow">→</span> }
@if (!last) {
<span class="arrow">→</span>
}
}
</div>
<p class="note">De stappen zijn altijd dezelfde; alleen de vragen <em>binnen</em> een stap verschijnen of verdwijnen.</p>
<p class="note">
De stappen zijn altijd dezelfde; alleen de vragen <em>binnen</em> een stap verschijnen
of verdwijnen.
</p>
</div>
<div class="card card--good">
<p class="tag good">De wizard</p>
@@ -174,8 +340,11 @@ export class ConceptsPage {
isEmpty = (v: string[]) => !v || v.length === 0;
doorgehaald: Registration = {
bigNummer: '19012345601', naam: 'Dr. A. (Anna) de Vries', beroep: 'Arts',
registratiedatum: '2012-09-01', geboortedatum: '1985-03-14',
bigNummer: '19012345601',
naam: 'Dr. A. (Anna) de Vries',
beroep: 'Arts',
registratiedatum: '2012-09-01',
geboortedatum: '1985-03-14',
status: { tag: 'Doorgehaald', doorgehaaldOp: '2024-05-01', reden: 'Op eigen verzoek' },
};