From 6bd6e854c7f7893e3a490748766683c85cfc989b Mon Sep 17 00:00:00 2001 From: Edwin van den Houdt Date: Thu, 25 Jun 2026 16:56:17 +0200 Subject: [PATCH] Regenerate compodoc documentation.json Reflects the new types, components, and pages from the impossible-states work. Co-Authored-By: Claude Opus 4.8 --- documentation.json | 2127 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 1952 insertions(+), 175 deletions(-) diff --git a/documentation.json b/documentation.json index 5417ace..4fbdf98 100644 --- a/documentation.json +++ b/documentation.json @@ -3,12 +3,12 @@ "interfaces": [ { "name": "Aantekening", - "id": "interface-Aantekening-ad224cc8184441fa9923164457d2a4916359232fbcc5bc89a42031573682d3c39f25d7596de394302d8d9e7a7c2157118f79ba2c6dc80ebbf36b836a6d7db8df", + "id": "interface-Aantekening-2ecca36e413e22c1c5967d07b811fa4171ff578eb6b47a7275723bcca44e93a2db27d77a765b82e5f6182492464cc8b9268c9724e13b400718d30da7bc4ba50c", "file": "src/app/core/models.ts", "deprecated": false, "deprecationMessage": "", "type": "interface", - "sourceCode": "export type RegistrationStatus = 'Geregistreerd' | 'Doorgehaald' | 'Geschorst';\n\nexport interface Registration {\n bigNummer: string;\n naam: string;\n beroep: string; // arts, verpleegkundige, apotheker, ...\n status: RegistrationStatus;\n registratiedatum: string; // ISO date\n herregistratieDatum: string;\n geboortedatum: string;\n}\n\nexport interface Aantekening {\n type: string; // specialisme of aantekening\n omschrijving: string;\n datum: string;\n}\n", + "sourceCode": "export type RegistrationStatus =\n | { tag: 'Geregistreerd'; herregistratieDatum: string } // ISO date\n | { tag: 'Geschorst'; geschorstTot: string; reden: string }\n | { tag: 'Doorgehaald'; doorgehaaldOp: string; reden: string };\n\n/** Just the discriminant — for atoms that only need the label/color. */\nexport type StatusTag = RegistrationStatus['tag'];\n\nexport interface Registration {\n bigNummer: string;\n naam: string;\n beroep: string; // arts, verpleegkundige, apotheker, ...\n registratiedatum: string; // ISO date\n geboortedatum: string;\n status: RegistrationStatus;\n}\n\nexport interface Aantekening {\n type: string; // specialisme of aantekening\n omschrijving: string;\n datum: string;\n}\n", "properties": [ { "name": "datum", @@ -18,7 +18,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 16 + "line": 28 }, { "name": "omschrijving", @@ -28,7 +28,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 15 + "line": 27 }, { "name": "type", @@ -38,7 +38,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 14 + "line": 26 } ], "indexSignatures": [], @@ -46,14 +46,98 @@ "methods": [], "extends": [] }, + { + "name": "ChangeRequest", + "id": "interface-ChangeRequest-e366867f9f2a82d4c6cafc6fbdce9749e7775f651192c4de62cefc40a237a6e2c6772537e64a4aeeb6667ffa33d9a69ee6114c69160674807408090b1cbc283a", + "file": "src/app/organisms/change-request-form/change-request-form.component.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "interface", + "sourceCode": "import { Component, output, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { Postcode, parsePostcode } from '../../core/parse';\n\n/** A submitted change request carries a *parsed* postcode (branded Postcode),\n not a raw string — downstream code can't receive an unvalidated one. */\nexport interface ChangeRequest {\n street: string;\n zip: Postcode;\n city: string;\n}\n\n/** Organism: change-request (adreswijziging) form. Reuses the same form-field\n molecule + text-input/button atoms as the login form. Field errors come\n straight from the parser's Result — no parallel \"is it valid\" flag to drift. */\n@Component({\n selector: 'app-change-request-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, HeadingComponent],\n template: `\n Adreswijziging doorgeven\n
\n \n \n \n \n \n \n \n \n \n
\n Wijziging indienen\n
\n
\n `,\n})\nexport class ChangeRequestFormComponent {\n street = '';\n zip = '';\n city = '';\n streetError = signal('');\n zipError = signal('');\n submitted = output();\n\n onSubmit() {\n const street = this.street.trim();\n this.streetError.set(street ? '' : 'Vul straat en huisnummer in.');\n\n const postcode = parsePostcode(this.zip);\n this.zipError.set(postcode.ok ? '' : postcode.error);\n\n if (!street || !postcode.ok) return;\n this.submitted.emit({ street, zip: postcode.value, city: this.city.trim() });\n }\n}\n", + "properties": [ + { + "name": "city", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 14 + }, + { + "name": "street", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 12 + }, + { + "name": "zip", + "deprecated": false, + "deprecationMessage": "", + "type": "Postcode", + "indexKey": "", + "optional": false, + "description": "", + "line": 13 + } + ], + "indexSignatures": [], + "kind": 172, + "description": "

A submitted change request carries a parsed postcode (branded Postcode),\nnot a raw string — downstream code can't receive an unvalidated one.

\n", + "rawdescription": "\nA submitted change request carries a *parsed* postcode (branded Postcode),\nnot a raw string — downstream code can't receive an unvalidated one.", + "methods": [], + "extends": [] + }, + { + "name": "Draft", + "id": "interface-Draft-6fcaabe58c126a645622a7a77942a847877e8d80e60ddd45cb9ba4c247cede3ee197def542b645ef02b0ce5dd0deab6c644edddf2f37dec67a129b27ba53bb23", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "interface", + "sourceCode": "import { Result } from '../../core/fp';\nimport { Uren, parseUren } from '../../core/parse';\n\n/** What the user is typing (raw, possibly invalid). */\nexport interface Draft {\n uren: string;\n punten: string;\n}\n\n/** What we have AFTER parsing — branded/typed, guaranteed valid. */\nexport interface Valid {\n uren: Uren;\n punten: number;\n}\n\n/**\n * The whole wizard as one tagged union. `step` and `errors` exist ONLY while\n * Editing; Submitting/Submitted/Failed carry a `Valid` payload and nothing else.\n * So \"submitting while a field is invalid\" or \"showing the success screen with\n * errors set\" are unrepresentable — the bug class is gone by construction.\n */\nexport type WizardState =\n | { tag: 'Editing'; step: 1 | 2; draft: Draft; errors: Partial> }\n | { tag: 'Submitting'; data: Valid }\n | { tag: 'Submitted'; data: Valid }\n | { tag: 'Failed'; data: Valid; error: string };\n\nexport const initial: WizardState = { tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} };\n\n/** Parse every field; on success hand back a Valid, else the per-field errors. */\nfunction validate(draft: Draft): Result>, Valid> {\n const uren = parseUren(draft.uren);\n const punten = parseUren(draft.punten);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!punten.ok) errors.punten = punten.error;\n if (uren.ok && punten.ok) return { ok: true, value: { uren: uren.value, punten: punten.value } };\n return { ok: false, error: errors };\n}\n\n/** Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op. */\nexport function next(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 1) return s;\n const uren = parseUren(s.draft.uren);\n return uren.ok\n ? { ...s, step: 2, errors: {} }\n : { ...s, errors: { uren: uren.error } };\n}\n\nexport function back(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n return { ...s, step: 1, errors: {} };\n}\n\n/** Step 2 submit: parse everything; move to Submitting only with Valid data. */\nexport function submit(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n const result = validate(s.draft);\n return result.ok ? { tag: 'Submitting', data: result.value } : { ...s, errors: result.error };\n}\n\n/** Resolve the async submit. Only meaningful while Submitting. */\nexport function resolve(s: WizardState, r: Result): WizardState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n", + "properties": [ + { + "name": "punten", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 7 + }, + { + "name": "uren", + "deprecated": false, + "deprecationMessage": "", + "type": "string", + "indexKey": "", + "optional": false, + "description": "", + "line": 6 + } + ], + "indexSignatures": [], + "kind": 172, + "description": "

What the user is typing (raw, possibly invalid).

\n", + "rawdescription": "\nWhat the user is typing (raw, possibly invalid).", + "methods": [], + "extends": [] + }, { "name": "Registration", - "id": "interface-Registration-ad224cc8184441fa9923164457d2a4916359232fbcc5bc89a42031573682d3c39f25d7596de394302d8d9e7a7c2157118f79ba2c6dc80ebbf36b836a6d7db8df", + "id": "interface-Registration-2ecca36e413e22c1c5967d07b811fa4171ff578eb6b47a7275723bcca44e93a2db27d77a765b82e5f6182492464cc8b9268c9724e13b400718d30da7bc4ba50c", "file": "src/app/core/models.ts", "deprecated": false, "deprecationMessage": "", "type": "interface", - "sourceCode": "export type RegistrationStatus = 'Geregistreerd' | 'Doorgehaald' | 'Geschorst';\n\nexport interface Registration {\n bigNummer: string;\n naam: string;\n beroep: string; // arts, verpleegkundige, apotheker, ...\n status: RegistrationStatus;\n registratiedatum: string; // ISO date\n herregistratieDatum: string;\n geboortedatum: string;\n}\n\nexport interface Aantekening {\n type: string; // specialisme of aantekening\n omschrijving: string;\n datum: string;\n}\n", + "sourceCode": "export type RegistrationStatus =\n | { tag: 'Geregistreerd'; herregistratieDatum: string } // ISO date\n | { tag: 'Geschorst'; geschorstTot: string; reden: string }\n | { tag: 'Doorgehaald'; doorgehaaldOp: string; reden: string };\n\n/** Just the discriminant — for atoms that only need the label/color. */\nexport type StatusTag = RegistrationStatus['tag'];\n\nexport interface Registration {\n bigNummer: string;\n naam: string;\n beroep: string; // arts, verpleegkundige, apotheker, ...\n registratiedatum: string; // ISO date\n geboortedatum: string;\n status: RegistrationStatus;\n}\n\nexport interface Aantekening {\n type: string; // specialisme of aantekening\n omschrijving: string;\n datum: string;\n}\n", "properties": [ { "name": "beroep", @@ -63,7 +147,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 6 + "line": 19 }, { "name": "bigNummer", @@ -73,7 +157,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 4 + "line": 17 }, { "name": "geboortedatum", @@ -83,17 +167,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 10 - }, - { - "name": "herregistratieDatum", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "indexKey": "", - "optional": false, - "description": "", - "line": 9 + "line": 21 }, { "name": "naam", @@ -103,7 +177,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 5 + "line": 18 }, { "name": "registratiedatum", @@ -113,7 +187,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 8 + "line": 20 }, { "name": "status", @@ -123,13 +197,50 @@ "indexKey": "", "optional": false, "description": "", - "line": 7 + "line": 22 } ], "indexSignatures": [], "kind": 172, "methods": [], "extends": [] + }, + { + "name": "Valid", + "id": "interface-Valid-6fcaabe58c126a645622a7a77942a847877e8d80e60ddd45cb9ba4c247cede3ee197def542b645ef02b0ce5dd0deab6c644edddf2f37dec67a129b27ba53bb23", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "interface", + "sourceCode": "import { Result } from '../../core/fp';\nimport { Uren, parseUren } from '../../core/parse';\n\n/** What the user is typing (raw, possibly invalid). */\nexport interface Draft {\n uren: string;\n punten: string;\n}\n\n/** What we have AFTER parsing — branded/typed, guaranteed valid. */\nexport interface Valid {\n uren: Uren;\n punten: number;\n}\n\n/**\n * The whole wizard as one tagged union. `step` and `errors` exist ONLY while\n * Editing; Submitting/Submitted/Failed carry a `Valid` payload and nothing else.\n * So \"submitting while a field is invalid\" or \"showing the success screen with\n * errors set\" are unrepresentable — the bug class is gone by construction.\n */\nexport type WizardState =\n | { tag: 'Editing'; step: 1 | 2; draft: Draft; errors: Partial> }\n | { tag: 'Submitting'; data: Valid }\n | { tag: 'Submitted'; data: Valid }\n | { tag: 'Failed'; data: Valid; error: string };\n\nexport const initial: WizardState = { tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} };\n\n/** Parse every field; on success hand back a Valid, else the per-field errors. */\nfunction validate(draft: Draft): Result>, Valid> {\n const uren = parseUren(draft.uren);\n const punten = parseUren(draft.punten);\n const errors: Partial> = {};\n if (!uren.ok) errors.uren = uren.error;\n if (!punten.ok) errors.punten = punten.error;\n if (uren.ok && punten.ok) return { ok: true, value: { uren: uren.value, punten: punten.value } };\n return { ok: false, error: errors };\n}\n\n/** Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op. */\nexport function next(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 1) return s;\n const uren = parseUren(s.draft.uren);\n return uren.ok\n ? { ...s, step: 2, errors: {} }\n : { ...s, errors: { uren: uren.error } };\n}\n\nexport function back(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n return { ...s, step: 1, errors: {} };\n}\n\n/** Step 2 submit: parse everything; move to Submitting only with Valid data. */\nexport function submit(s: WizardState): WizardState {\n if (s.tag !== 'Editing' || s.step !== 2) return s;\n const result = validate(s.draft);\n return result.ok ? { tag: 'Submitting', data: result.value } : { ...s, errors: result.error };\n}\n\n/** Resolve the async submit. Only meaningful while Submitting. */\nexport function resolve(s: WizardState, r: Result): WizardState {\n if (s.tag !== 'Submitting') return s;\n return r.ok ? { tag: 'Submitted', data: s.data } : { tag: 'Failed', data: s.data, error: r.error };\n}\n", + "properties": [ + { + "name": "punten", + "deprecated": false, + "deprecationMessage": "", + "type": "number", + "indexKey": "", + "optional": false, + "description": "", + "line": 13 + }, + { + "name": "uren", + "deprecated": false, + "deprecationMessage": "", + "type": "Uren", + "indexKey": "", + "optional": false, + "description": "", + "line": 12 + } + ], + "indexSignatures": [], + "kind": 172, + "description": "

What we have AFTER parsing — branded/typed, guaranteed valid.

\n", + "rawdescription": "\nWhat we have AFTER parsing — branded/typed, guaranteed valid.", + "methods": [], + "extends": [] } ], "injectables": [ @@ -175,12 +286,12 @@ "directives": [ { "name": "AsyncEmptyDirective", - "id": "directive-AsyncEmptyDirective-2f8f54349a396c3f59c90536339a2de5b28ea23c435f301c585563e8dcd7e5652d6b6dee37cd8065a1f9573e0fbbc44511e14a02310dba1fd9d0a8354991e759", + "id": "directive-AsyncEmptyDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9", "file": "src/app/molecules/async/async.component.ts", "type": "directive", "description": "", "rawdescription": "\n", - "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\ntype State = 'loading' | 'error' | 'empty' | 'loaded';\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). The states are mutually exclusive by\n * construction, so the UI can never show two at once (\"impossible states\").\n * Unprovided slots fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('loaded') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n protected value = computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n });\n\n protected state = computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n });\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", + "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", "selector": "[appAsyncEmpty]", "providers": [], "hostDirectives": [], @@ -200,7 +311,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 19, + "line": 20, "modifierKind": [ 125 ] @@ -223,7 +334,7 @@ "deprecationMessage": "" } ], - "line": 18, + "line": 19, "jsdoctags": [ { "name": "tpl", @@ -241,12 +352,12 @@ }, { "name": "AsyncErrorDirective", - "id": "directive-AsyncErrorDirective-2f8f54349a396c3f59c90536339a2de5b28ea23c435f301c585563e8dcd7e5652d6b6dee37cd8065a1f9573e0fbbc44511e14a02310dba1fd9d0a8354991e759", + "id": "directive-AsyncErrorDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9", "file": "src/app/molecules/async/async.component.ts", "type": "directive", "description": "", "rawdescription": "\n", - "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\ntype State = 'loading' | 'error' | 'empty' | 'loaded';\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). The states are mutually exclusive by\n * construction, so the UI can never show two at once (\"impossible states\").\n * Unprovided slots fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('loaded') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n protected value = computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n });\n\n protected state = computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n });\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", + "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", "selector": "[appAsyncError]", "providers": [], "hostDirectives": [], @@ -266,7 +377,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 23, + "line": 24, "modifierKind": [ 125 ] @@ -289,7 +400,7 @@ "deprecationMessage": "" } ], - "line": 22, + "line": 23, "jsdoctags": [ { "name": "tpl", @@ -307,12 +418,12 @@ }, { "name": "AsyncLoadedDirective", - "id": "directive-AsyncLoadedDirective-2f8f54349a396c3f59c90536339a2de5b28ea23c435f301c585563e8dcd7e5652d6b6dee37cd8065a1f9573e0fbbc44511e14a02310dba1fd9d0a8354991e759", + "id": "directive-AsyncLoadedDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9", "file": "src/app/molecules/async/async.component.ts", "type": "directive", "description": "", "rawdescription": "\n", - "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\ntype State = 'loading' | 'error' | 'empty' | 'loaded';\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). The states are mutually exclusive by\n * construction, so the UI can never show two at once (\"impossible states\").\n * Unprovided slots fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('loaded') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n protected value = computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n });\n\n protected state = computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n });\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", + "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", "selector": "[appAsyncLoaded]", "providers": [], "hostDirectives": [], @@ -332,7 +443,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 11, + "line": 12, "modifierKind": [ 125 ] @@ -355,7 +466,7 @@ "deprecationMessage": "" } ], - "line": 10, + "line": 11, "jsdoctags": [ { "name": "tpl", @@ -373,12 +484,12 @@ }, { "name": "AsyncLoadingDirective", - "id": "directive-AsyncLoadingDirective-2f8f54349a396c3f59c90536339a2de5b28ea23c435f301c585563e8dcd7e5652d6b6dee37cd8065a1f9573e0fbbc44511e14a02310dba1fd9d0a8354991e759", + "id": "directive-AsyncLoadingDirective-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9", "file": "src/app/molecules/async/async.component.ts", "type": "directive", "description": "", "rawdescription": "\n", - "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\ntype State = 'loading' | 'error' | 'empty' | 'loaded';\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). The states are mutually exclusive by\n * construction, so the UI can never show two at once (\"impossible states\").\n * Unprovided slots fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('loaded') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n protected value = computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n });\n\n protected state = computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n });\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", + "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", "selector": "[appAsyncLoading]", "providers": [], "hostDirectives": [], @@ -398,7 +509,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 15, + "line": 16, "modifierKind": [ 125 ] @@ -421,7 +532,7 @@ "deprecationMessage": "" } ], - "line": 14, + "line": 15, "jsdoctags": [ { "name": "tpl", @@ -528,7 +639,7 @@ }, { "name": "AsyncComponent", - "id": "component-AsyncComponent-2f8f54349a396c3f59c90536339a2de5b28ea23c435f301c585563e8dcd7e5652d6b6dee37cd8065a1f9573e0fbbc44511e14a02310dba1fd9d0a8354991e759", + "id": "component-AsyncComponent-2c3bb2a3fee90c1c02ec41aacfd672fcdf754eccc5e8761a7590b96d19724dc55523a26a1eff26c0caeaf28a297042b8fdf7e78d70272079bd5465f72e58dbc9", "file": "src/app/molecules/async/async.component.ts", "encapsulation": [], "entryComponents": [], @@ -538,7 +649,7 @@ "selector": "app-async", "styleUrls": [], "styles": [], - "template": "@switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('loaded') {\n \n }\n}\n", + "template": "@switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n}\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -580,6 +691,20 @@ "description": "", "line": 71 }, + { + "name": "error", + "defaultValue": "computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n )", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 82, + "modifierKind": [ + 124 + ] + }, { "name": "errorTpl", "defaultValue": "contentChild(AsyncErrorDirective)", @@ -613,6 +738,20 @@ "description": "", "line": 70 }, + { + "name": "rd", + "defaultValue": "computed(() => fromResource(this.resource(), this.isEmpty()))", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 75, + "modifierKind": [ + 124 + ] + }, { "name": "retry", "defaultValue": "() => {...}", @@ -622,11 +761,11 @@ "indexKey": "", "optional": false, "description": "", - "line": 87 + "line": 86 }, { - "name": "state", - "defaultValue": "computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n })", + "name": "value", + "defaultValue": "computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n )", "deprecated": false, "deprecationMessage": "", "type": "unknown", @@ -637,20 +776,6 @@ "modifierKind": [ 124 ] - }, - { - "name": "value", - "defaultValue": "computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n })", - "deprecated": false, - "deprecationMessage": "", - "type": "unknown", - "indexKey": "", - "optional": false, - "description": "", - "line": 74, - "modifierKind": [ - 124 - ] } ], "methodsClass": [], @@ -676,10 +801,10 @@ "type": "component" } ], - "description": "

Renders exactly ONE of loading / empty / error / loaded for a signal-based\nresource (e.g. httpResource). The states are mutually exclusive by\nconstruction, so the UI can never show two at once ("impossible states").\nUnprovided slots fall back to sensible defaults.

\n", - "rawdescription": "\n\nRenders exactly ONE of loading / empty / error / loaded for a signal-based\nresource (e.g. httpResource). The states are mutually exclusive by\nconstruction, so the UI can never show two at once (\"impossible states\").\nUnprovided slots fall back to sensible defaults.\n", + "description": "

Renders exactly ONE of loading / empty / error / loaded for a signal-based\nresource (e.g. httpResource). Built on a RemoteData tagged union (see\ncore/remote-data.ts), so the states are mutually exclusive by construction —\nthe UI can never show two at once ("impossible states"). Unprovided slots\nfall back to sensible defaults.

\n", + "rawdescription": "\n\nRenders exactly ONE of loading / empty / error / loaded for a signal-based\nresource (e.g. httpResource). Built on a RemoteData tagged union (see\ncore/remote-data.ts), so the states are mutually exclusive by construction —\nthe UI can never show two at once (\"impossible states\"). Unprovided slots\nfall back to sensible defaults.\n", "type": "component", - "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\ntype State = 'loading' | 'error' | 'empty' | 'loaded';\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). The states are mutually exclusive by\n * construction, so the UI can never show two at once (\"impossible states\").\n * Unprovided slots fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (state()) {\n @case ('loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('error') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('loaded') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n protected value = computed(() => {\n const r = this.resource();\n return r.hasValue() ? r.value() : undefined;\n });\n\n protected state = computed(() => {\n const r = this.resource();\n if (r.status() === 'error') return 'error';\n if (r.status() === 'loading') return 'loading';\n if (r.hasValue()) return this.isEmpty()(r.value()) ? 'empty' : 'loaded';\n return 'loading';\n });\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", + "sourceCode": "import { Component, Directive, TemplateRef, computed, contentChild, input } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport type { Resource } from '@angular/core';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { fromResource, foldRemote } from '../../core/remote-data';\n\n/* Slot markers. Put on children of . */\n@Directive({ selector: '[appAsyncLoaded]' })\nexport class AsyncLoadedDirective {\n constructor(public tpl: TemplateRef<{ $implicit: unknown }>) {}\n}\n@Directive({ selector: '[appAsyncLoading]' })\nexport class AsyncLoadingDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncEmpty]' })\nexport class AsyncEmptyDirective {\n constructor(public tpl: TemplateRef) {}\n}\n@Directive({ selector: '[appAsyncError]' })\nexport class AsyncErrorDirective {\n constructor(public tpl: TemplateRef<{ $implicit: Error | undefined; retry: () => void }>) {}\n}\n\n/**\n * Renders exactly ONE of loading / empty / error / loaded for a signal-based\n * resource (e.g. httpResource). Built on a RemoteData tagged union (see\n * core/remote-data.ts), so the states are mutually exclusive by construction —\n * the UI can never show two at once (\"impossible states\"). Unprovided slots\n * fall back to sensible defaults.\n */\n@Component({\n selector: 'app-async',\n imports: [NgTemplateOutlet, SpinnerComponent, AlertComponent, ButtonComponent],\n template: `\n @switch (rd().tag) {\n @case ('Loading') {\n @if (loadingTpl()) { }\n @else { }\n }\n @case ('Failure') {\n @if (errorTpl()) {\n \n } @else {\n Er ging iets mis bij het laden van de gegevens.\n
\n Opnieuw proberen\n
\n }\n }\n @case ('Empty') {\n @if (emptyTpl()) { }\n @else {

Geen gegevens gevonden.

}\n }\n @case ('Success') {\n \n }\n }\n `,\n})\nexport class AsyncComponent {\n resource = input.required>();\n isEmpty = input<(v: T) => boolean>(() => false);\n\n loadedTpl = contentChild.required(AsyncLoadedDirective);\n loadingTpl = contentChild(AsyncLoadingDirective);\n emptyTpl = contentChild(AsyncEmptyDirective);\n errorTpl = contentChild(AsyncErrorDirective);\n\n // Single source of truth: the resource projected into a RemoteData union.\n protected rd = computed(() => fromResource(this.resource(), this.isEmpty()));\n\n // value/error are pulled out via the exhaustive fold — only Success carries a\n // value, only Failure carries an error, so these can't lie.\n protected value = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: () => undefined, success: (v) => v }),\n );\n protected error = computed(() =>\n foldRemote(this.rd(), { loading: () => undefined, empty: () => undefined, failure: (e) => e, success: () => undefined }),\n );\n\n retry = () => {\n const r = this.resource();\n if ('reload' in r && typeof (r as { reload?: unknown }).reload === 'function') {\n (r as { reload: () => void }).reload();\n }\n };\n}\n\n/** Convenience: import this array to get the wrapper + all slot directives. */\nexport const ASYNC = [\n AsyncComponent, AsyncLoadedDirective, AsyncLoadingDirective,\n AsyncEmptyDirective, AsyncErrorDirective,\n] as const;\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -758,7 +883,7 @@ }, { "name": "ChangeRequestFormComponent", - "id": "component-ChangeRequestFormComponent-81277b5d39f6d8389da0d8313e3b14f50df8058e30513eca8776b01c490aed300ca483ab7ea00363d41e0a98360a792cc030698f4bf3918e14719ce4b08f64fa", + "id": "component-ChangeRequestFormComponent-e366867f9f2a82d4c6cafc6fbdce9749e7775f651192c4de62cefc40a237a6e2c6772537e64a4aeeb6667ffa33d9a69ee6114c69160674807408090b1cbc283a", "file": "src/app/organisms/change-request-form/change-request-form.component.ts", "encapsulation": [], "entryComponents": [], @@ -768,7 +893,7 @@ "selector": "app-change-request-form", "styleUrls": [], "styles": [], - "template": "Adreswijziging doorgeven\n
\n \n \n \n \n \n \n \n \n \n
\n Wijziging indienen\n
\n
\n", + "template": "Adreswijziging doorgeven\n
\n \n \n \n \n \n \n \n \n \n
\n Wijziging indienen\n
\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -778,11 +903,11 @@ "name": "submitted", "deprecated": false, "deprecationMessage": "", - "type": "void", + "type": "ChangeRequest", "indexKey": "", "optional": false, "description": "", - "line": 36, + "line": 47, "required": false } ], @@ -796,7 +921,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 34 + "line": 44 }, { "name": "street", @@ -807,7 +932,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 32 + "line": 42 }, { "name": "streetError", @@ -818,7 +943,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 35 + "line": 45 }, { "name": "zip", @@ -829,7 +954,18 @@ "indexKey": "", "optional": false, "description": "", - "line": 33 + "line": 43 + }, + { + "name": "zipError", + "defaultValue": "signal('')", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 46 } ], "methodsClass": [ @@ -839,7 +975,7 @@ "optional": false, "returnType": "void", "typeParameters": [], - "line": 38, + "line": 49, "deprecated": false, "deprecationMessage": "" } @@ -871,18 +1007,213 @@ "type": "component" } ], - "description": "

Organism: change-request (adreswijziging) form. Reuses the same form-field\nmolecule + text-input/button atoms as the login form.

\n", - "rawdescription": "\nOrganism: change-request (adreswijziging) form. Reuses the same form-field\nmolecule + text-input/button atoms as the login form.", + "description": "

Organism: change-request (adreswijziging) form. Reuses the same form-field\nmolecule + text-input/button atoms as the login form. Field errors come\nstraight from the parser's Result — no parallel "is it valid" flag to drift.

\n", + "rawdescription": "\nOrganism: change-request (adreswijziging) form. Reuses the same form-field\nmolecule + text-input/button atoms as the login form. Field errors come\nstraight from the parser's Result — no parallel \"is it valid\" flag to drift.", "type": "component", - "sourceCode": "import { Component, output, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\n\n/** Organism: change-request (adreswijziging) form. Reuses the same form-field\n molecule + text-input/button atoms as the login form. */\n@Component({\n selector: 'app-change-request-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, HeadingComponent],\n template: `\n Adreswijziging doorgeven\n
\n \n \n \n \n \n \n \n \n \n
\n Wijziging indienen\n
\n
\n `,\n})\nexport class ChangeRequestFormComponent {\n street = '';\n zip = '';\n city = '';\n streetError = signal('');\n submitted = output();\n\n onSubmit() {\n if (!this.street.trim()) { this.streetError.set('Vul straat en huisnummer in.'); return; }\n this.streetError.set('');\n this.submitted.emit();\n }\n}\n", + "sourceCode": "import { Component, output, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { Postcode, parsePostcode } from '../../core/parse';\n\n/** A submitted change request carries a *parsed* postcode (branded Postcode),\n not a raw string — downstream code can't receive an unvalidated one. */\nexport interface ChangeRequest {\n street: string;\n zip: Postcode;\n city: string;\n}\n\n/** Organism: change-request (adreswijziging) form. Reuses the same form-field\n molecule + text-input/button atoms as the login form. Field errors come\n straight from the parser's Result — no parallel \"is it valid\" flag to drift. */\n@Component({\n selector: 'app-change-request-form',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, HeadingComponent],\n template: `\n Adreswijziging doorgeven\n
\n \n \n \n \n \n \n \n \n \n
\n Wijziging indienen\n
\n
\n `,\n})\nexport class ChangeRequestFormComponent {\n street = '';\n zip = '';\n city = '';\n streetError = signal('');\n zipError = signal('');\n submitted = output();\n\n onSubmit() {\n const street = this.street.trim();\n this.streetError.set(street ? '' : 'Vul straat en huisnummer in.');\n\n const postcode = parsePostcode(this.zip);\n this.zipError.set(postcode.ok ? '' : postcode.error);\n\n if (!street || !postcode.ok) return;\n this.submitted.emit({ street, zip: postcode.value, city: this.city.trim() });\n }\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", "extends": [] }, + { + "name": "ConceptsPage", + "id": "component-ConceptsPage-6ebc285de6e31b814c5a5499d296626fc51bce08971ae012bfa0176580e767b207f13bbc91e38ad1d616cb28e386208e7686eef9bdf8f5e0ddf89b7ee67362cb", + "file": "src/app/pages/concepts/concepts.page.ts", + "encapsulation": [], + "entryComponents": [], + "inputs": [], + "outputs": [], + "providers": [], + "selector": "app-concepts-page", + "styleUrls": [], + "styles": [ + "\n .cols { display:flex; flex-wrap:wrap; gap:1.5rem; margin:1rem 0 2.5rem }\n .col { flex:1 1 20rem; min-width:18rem }\n .tag { font-weight:700; font-size:0.8rem; text-transform:uppercase; letter-spacing:0.04em }\n .bad { color:var(--rhc-color-rood-500) }\n .good { color:var(--rhc-color-groen-500) }\n pre { background:var(--rhc-color-grijs-100,#f3f3f3); padding:1rem; border-radius:4px; overflow:auto; font-size:0.85rem }\n " + ], + "template": "\n \n Vier functionele patronen die met atomic design makkelijker te tonen zijn — telkens \"fout\"\n (de oude vorm liet het toe) naast \"goed\" (het type maakt het onmogelijk).\n \n\n \n 1 · Discriminated unions\n
\n
\n

Fout — vlakke interface

\n
{{ unionBad }}
\n

Een doorgehaalde registratie houdt tóch een herregistratiedatum: onmogelijke toestand.

\n
\n
\n

Goed — sum type

\n \n

De variant Doorgehaald kent geen herregistratiedatum, dus de rij bestaat simpelweg niet.

\n
\n
\n\n \n 2 · RemoteData fold\n
\n
\n

Eén molecuul, vier elkaar uitsluitende toestanden

\n

Loading

\n {{ v }}\n

Empty

\n {{ v }}\n

Failure

\n {{ v }}\n

Success

\n
    @for (i of v; track i) {
  • {{ i }}
  • }
\n
\n
\n

De exhaustieve fold

\n
{{ foldCode }}
\n

Een nieuwe variant toevoegen breekt de compile via assertNever tot je hem afhandelt.

\n
\n
\n\n \n 3 · Parse, don't validate\n
\n
\n

Smart constructor → Result

\n \n
\n
\n @if (parsed().ok) {\n

ok

\n
Postcode = \"{{ parsed().ok ? $any(parsed()).value : '' }}\"
\n

Een gevalideerde Postcode is een ander type dan een ruwe string.

\n } @else {\n

err

\n
{{ $any(parsed()).error }}
\n }\n
\n
\n\n \n 4 · Form als state machine\n
\n
\n

Fout — losse booleans

\n
{{ machineBad }}
\n

Niets verhindert \"submitting\" mét validatiefouten of een successcherm met errors.

\n
\n
\n

Goed — één tagged union stuurt de UI

\n \n
\n
\n
\n", + "templateUrl": [], + "viewProviders": [], + "hostDirectives": [], + "inputsClass": [], + "outputsClass": [], + "propertiesClass": [ + { + "name": "doorgehaald", + "defaultValue": "{\n bigNummer: '19012345601', naam: 'Dr. A. (Anna) de Vries', beroep: 'Arts',\n registratiedatum: '2012-09-01', geboortedatum: '1985-03-14',\n status: { tag: 'Doorgehaald', doorgehaaldOp: '2024-05-01', reden: 'Op eigen verzoek' },\n }", + "deprecated": false, + "deprecationMessage": "", + "type": "Registration", + "indexKey": "", + "optional": false, + "description": "", + "line": 117 + }, + { + "name": "emptyRes", + "defaultValue": "fakeResource('resolved', [])", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 124 + }, + { + "name": "errorRes", + "defaultValue": "fakeResource('error', undefined, new Error('Demo'))", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 125 + }, + { + "name": "foldCode", + "defaultValue": "`foldRemote(rd, {\n loading: () => spinner,\n empty: () => 'geen data',\n failure: (e) => alert(e),\n success: (v) => render(v),\n}); // mist er één → compile-fout`", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 136 + }, + { + "name": "isEmpty", + "defaultValue": "() => {...}", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 115 + }, + { + "name": "loadingRes", + "defaultValue": "fakeResource('loading')", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 123 + }, + { + "name": "machineBad", + "defaultValue": "`submitting = signal(false);\nsubmitted = signal(false);\nerrors = signal<...>({});\n// submitting === true && errors.size > 0 ? 🤷`", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 143 + }, + { + "name": "parsed", + "defaultValue": "computed(() => parsePostcode(this.raw()))", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 129 + }, + { + "name": "raw", + "defaultValue": "signal('')", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 128 + }, + { + "name": "successRes", + "defaultValue": "fakeResource('resolved', ['Huisartsgeneeskunde', 'Spoedeisende hulp'])", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 126 + }, + { + "name": "unionBad", + "defaultValue": "`interface Registration {\n status: 'Geregistreerd' | 'Doorgehaald';\n herregistratieDatum: string; // altijd aanwezig 😬\n}`", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 131 + } + ], + "methodsClass": [], + "deprecated": false, + "deprecationMessage": "", + "hostBindings": [], + "hostListeners": [], + "standalone": false, + "imports": [ + { + "name": "FormsModule", + "type": "module" + }, + { + "name": "PageShellComponent", + "type": "component" + }, + { + "name": "HeadingComponent", + "type": "component" + }, + { + "name": "AlertComponent", + "type": "component" + }, + { + "name": "TextInputComponent", + "type": "component" + }, + { + "name": "ASYNC" + }, + { + "name": "SkeletonComponent", + "type": "component" + }, + { + "name": "RegistrationSummaryComponent", + "type": "component" + }, + { + "name": "HerregistratieWizardComponent", + "type": "component" + } + ], + "description": "

Teaching showcase: each section pairs the impossible-state-permitting "before"\nwith the "after" where the type system rules it out. Composition-only.

\n", + "rawdescription": "\nTeaching showcase: each section pairs the impossible-state-permitting \"before\"\nwith the \"after\" where the type system rules it out. Composition-only.", + "type": "component", + "sourceCode": "import { Component, computed, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport type { Resource } from '@angular/core';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ASYNC } from '../../molecules/async/async.component';\nimport { SkeletonComponent } from '../../atoms/skeleton/skeleton.component';\nimport { RegistrationSummaryComponent } from '../../organisms/registration-summary/registration-summary.component';\nimport { HerregistratieWizardComponent } from '../../organisms/herregistratie-wizard/herregistratie-wizard.component';\nimport { Registration } from '../../core/models';\nimport { parsePostcode } from '../../core/parse';\n\n/** Minimal fake Resource so can be driven through every state without HTTP. */\nfunction fakeResource(status: string, value?: T, error?: Error): Resource {\n return { value: () => value as T, status: () => status, error: () => error, hasValue: () => value !== undefined, reload: () => {} } as unknown as Resource;\n}\n\n/** Teaching showcase: each section pairs the impossible-state-permitting \"before\"\n with the \"after\" where the type system rules it out. Composition-only. */\n@Component({\n selector: 'app-concepts-page',\n imports: [\n FormsModule, PageShellComponent, HeadingComponent, AlertComponent, TextInputComponent,\n ...ASYNC, SkeletonComponent, RegistrationSummaryComponent, HerregistratieWizardComponent,\n ],\n styles: [`\n .cols { display:flex; flex-wrap:wrap; gap:1.5rem; margin:1rem 0 2.5rem }\n .col { flex:1 1 20rem; min-width:18rem }\n .tag { font-weight:700; font-size:0.8rem; text-transform:uppercase; letter-spacing:0.04em }\n .bad { color:var(--rhc-color-rood-500) }\n .good { color:var(--rhc-color-groen-500) }\n pre { background:var(--rhc-color-grijs-100,#f3f3f3); padding:1rem; border-radius:4px; overflow:auto; font-size:0.85rem }\n `],\n template: `\n \n \n Vier functionele patronen die met atomic design makkelijker te tonen zijn — telkens \"fout\"\n (de oude vorm liet het toe) naast \"goed\" (het type maakt het onmogelijk).\n \n\n \n 1 · Discriminated unions\n
\n
\n

Fout — vlakke interface

\n
{{ unionBad }}
\n

Een doorgehaalde registratie houdt tóch een herregistratiedatum: onmogelijke toestand.

\n
\n
\n

Goed — sum type

\n \n

De variant Doorgehaald kent geen herregistratiedatum, dus de rij bestaat simpelweg niet.

\n
\n
\n\n \n 2 · RemoteData fold\n
\n
\n

Eén molecuul, vier elkaar uitsluitende toestanden

\n

Loading

\n {{ v }}\n

Empty

\n {{ v }}\n

Failure

\n {{ v }}\n

Success

\n
    @for (i of v; track i) {
  • {{ i }}
  • }
\n
\n
\n

De exhaustieve fold

\n
{{ foldCode }}
\n

Een nieuwe variant toevoegen breekt de compile via assertNever tot je hem afhandelt.

\n
\n
\n\n \n 3 · Parse, don't validate\n
\n
\n

Smart constructor → Result

\n \n
\n
\n @if (parsed().ok) {\n

ok

\n
Postcode = \"{{ parsed().ok ? $any(parsed()).value : '' }}\"
\n

Een gevalideerde Postcode is een ander type dan een ruwe string.

\n } @else {\n

err

\n
{{ $any(parsed()).error }}
\n }\n
\n
\n\n \n 4 · Form als state machine\n
\n
\n

Fout — losse booleans

\n
{{ machineBad }}
\n

Niets verhindert \"submitting\" mét validatiefouten of een successcherm met errors.

\n
\n
\n

Goed — één tagged union stuurt de UI

\n \n
\n
\n
\n `,\n})\nexport class ConceptsPage {\n isEmpty = (v: string[]) => !v || v.length === 0;\n\n doorgehaald: Registration = {\n bigNummer: '19012345601', naam: 'Dr. A. (Anna) de Vries', beroep: 'Arts',\n registratiedatum: '2012-09-01', geboortedatum: '1985-03-14',\n status: { tag: 'Doorgehaald', doorgehaaldOp: '2024-05-01', reden: 'Op eigen verzoek' },\n };\n\n loadingRes = fakeResource('loading');\n emptyRes = fakeResource('resolved', []);\n errorRes = fakeResource('error', undefined, new Error('Demo'));\n successRes = fakeResource('resolved', ['Huisartsgeneeskunde', 'Spoedeisende hulp']);\n\n raw = signal('');\n parsed = computed(() => parsePostcode(this.raw()));\n\n unionBad = `interface Registration {\n status: 'Geregistreerd' | 'Doorgehaald';\n herregistratieDatum: string; // altijd aanwezig 😬\n}`;\n\n foldCode = `foldRemote(rd, {\n loading: () => spinner,\n empty: () => 'geen data',\n failure: (e) => alert(e),\n success: (v) => render(v),\n}); // mist er één → compile-fout`;\n\n machineBad = `submitting = signal(false);\nsubmitted = signal(false);\nerrors = signal<...>({});\n// submitting === true && errors.size > 0 ? 🤷`;\n}\n", + "assetsDirs": [], + "styleUrlsData": "", + "stylesData": "\n .cols { display:flex; flex-wrap:wrap; gap:1.5rem; margin:1rem 0 2.5rem }\n .col { flex:1 1 20rem; min-width:18rem }\n .tag { font-weight:700; font-size:0.8rem; text-transform:uppercase; letter-spacing:0.04em }\n .bad { color:var(--rhc-color-rood-500) }\n .good { color:var(--rhc-color-groen-500) }\n pre { background:var(--rhc-color-grijs-100,#f3f3f3); padding:1rem; border-radius:4px; overflow:auto; font-size:0.85rem }\n \n", + "extends": [] + }, { "name": "DashboardPage", - "id": "component-DashboardPage-36586da5e82d0e8ebdd85752f770845da4b3cc25f67a64c1fbaa2fdc227f11a3686aa7e85d20ba00f27796d75849db0f6e52fc10e1f2c59c8dd10c6c940c6c91", + "id": "component-DashboardPage-f363992c5154b037dc0dc2a461da66f9a297af7bc7ba9dd9f19a1bef213af9b9fb15a92e6d5e1922040acbf5aa3f7ec27db8035944e496c8f97bb519b4652cb3", "file": "src/app/pages/dashboard/dashboard.page.ts", "encapsulation": [], "entryComponents": [], @@ -892,7 +1223,7 @@ "selector": "app-dashboard-page", "styleUrls": [], "styles": [], - "template": "\n \n \n \n \n \n \n \n \n \n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n
\n", + "template": "\n \n \n \n \n \n \n \n \n \n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n

\n Functionele patronen (impossible states) →\n

\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -908,7 +1239,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 57 + "line": 60 }, { "name": "notesEmpty", @@ -919,7 +1250,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 58 + "line": 61 }, { "name": "reg", @@ -930,7 +1261,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 56 + "line": 59 }, { "name": "regEmpty", @@ -941,7 +1272,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 59 + "line": 62 }, { "name": "svc", @@ -952,7 +1283,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 55, + "line": 58, "modifierKind": [ 123 ] @@ -996,7 +1327,7 @@ "description": "", "rawdescription": "\n", "type": "component", - "sourceCode": "import { Component, inject } from '@angular/core';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { LinkComponent } from '../../atoms/link/link.component';\nimport { SkeletonComponent } from '../../atoms/skeleton/skeleton.component';\nimport { ASYNC } from '../../molecules/async/async.component';\nimport { RegistrationSummaryComponent } from '../../organisms/registration-summary/registration-summary.component';\nimport { RegistrationTableComponent } from '../../organisms/registration-table/registration-table.component';\nimport { RegistrationService } from '../../core/registration.service';\nimport { Aantekening } from '../../core/models';\n\n@Component({\n selector: 'app-dashboard-page',\n imports: [\n PageShellComponent, HeadingComponent, LinkComponent, SkeletonComponent, ...ASYNC,\n RegistrationSummaryComponent, RegistrationTableComponent,\n ],\n template: `\n \n \n \n \n \n \n \n \n \n \n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n
\n `,\n})\nexport class DashboardPage {\n private svc = inject(RegistrationService);\n reg = this.svc.registrationResource();\n notes = this.svc.aantekeningenResource();\n notesEmpty = (v: Aantekening[]) => v.length === 0;\n regEmpty = (v: unknown) => !v || Object.keys(v).length === 0;\n}\n", + "sourceCode": "import { Component, inject } from '@angular/core';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { HeadingComponent } from '../../atoms/heading/heading.component';\nimport { LinkComponent } from '../../atoms/link/link.component';\nimport { SkeletonComponent } from '../../atoms/skeleton/skeleton.component';\nimport { ASYNC } from '../../molecules/async/async.component';\nimport { RegistrationSummaryComponent } from '../../organisms/registration-summary/registration-summary.component';\nimport { RegistrationTableComponent } from '../../organisms/registration-table/registration-table.component';\nimport { RegistrationService } from '../../core/registration.service';\nimport { Aantekening } from '../../core/models';\n\n@Component({\n selector: 'app-dashboard-page',\n imports: [\n PageShellComponent, HeadingComponent, LinkComponent, SkeletonComponent, ...ASYNC,\n RegistrationSummaryComponent, RegistrationTableComponent,\n ],\n template: `\n \n \n \n \n \n \n \n \n \n \n\n
\n Specialismen en aantekeningen\n \n \n \n \n \n \n \n \n

U heeft nog geen specialismen of aantekeningen.

\n
\n
\n
\n\n

\n Gegevens bekijken of een wijziging doorgeven →\n

\n

\n Herregistratie aanvragen →\n

\n

\n Functionele patronen (impossible states) →\n

\n
\n `,\n})\nexport class DashboardPage {\n private svc = inject(RegistrationService);\n reg = this.svc.registrationResource();\n notes = this.svc.aantekeningenResource();\n notesEmpty = (v: Aantekening[]) => v.length === 0;\n regEmpty = (v: unknown) => !v || Object.keys(v).length === 0;\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -1195,7 +1526,7 @@ }, { "name": "HerregistratiePage", - "id": "component-HerregistratiePage-ab0497d7957e16f74b4c042109d559a772b5fd5c364d0d3c721504a6467354672c7afef26ddf84aa3d7a9e64ec48213993f5550e57ca65a536d10403b61ec754", + "id": "component-HerregistratiePage-e6eeb0932b0d6c7f4073bda82d95601ef7d31f17ac310061dcf3347c8f30d1444f50e124bfedda286285ebfda9932bb075fdaf20683413b524570e51fa350c3f", "file": "src/app/pages/herregistratie/herregistratie.page.ts", "encapsulation": [], "entryComponents": [], @@ -1205,68 +1536,252 @@ "selector": "app-herregistratie-page", "styleUrls": [], "styles": [], - "template": "\n \n Uw huidige registratie verloopt op 1 september 2027. Vraag tijdig herregistratie aan.\n \n\n @if (submitted()) {\n
\n Uw aanvraag tot herregistratie is ontvangen.\n
\n } @else {\n
\n \n \n \n \n \n \n
\n Herregistratie aanvragen\n
\n
\n }\n
\n", + "template": "\n \n Uw huidige registratie verloopt op 1 september 2027. Vraag tijdig herregistratie aan.\n \n
\n \n
\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], "inputsClass": [], "outputsClass": [], + "propertiesClass": [], + "methodsClass": [], + "deprecated": false, + "deprecationMessage": "", + "hostBindings": [], + "hostListeners": [], + "standalone": false, + "imports": [ + { + "name": "PageShellComponent", + "type": "component" + }, + { + "name": "AlertComponent", + "type": "component" + }, + { + "name": "HerregistratieWizardComponent", + "type": "component" + } + ], + "description": "

A whole new page built from existing building blocks — no new components.\nThis is the atomic-design payoff: a new flow is just composition.

\n", + "rawdescription": "\nA whole new page built from existing building blocks — no new components.\nThis is the atomic-design payoff: a new flow is just composition.", + "type": "component", + "sourceCode": "import { Component } from '@angular/core';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { HerregistratieWizardComponent } from '../../organisms/herregistratie-wizard/herregistratie-wizard.component';\n\n/** A whole new page built from existing building blocks — no new components.\n This is the atomic-design payoff: a new flow is just composition. */\n@Component({\n selector: 'app-herregistratie-page',\n imports: [PageShellComponent, AlertComponent, HerregistratieWizardComponent],\n template: `\n \n \n Uw huidige registratie verloopt op 1 september 2027. Vraag tijdig herregistratie aan.\n \n
\n \n
\n
\n `,\n})\nexport class HerregistratiePage {}\n", + "assetsDirs": [], + "styleUrlsData": "", + "stylesData": "", + "extends": [] + }, + { + "name": "HerregistratieWizardComponent", + "id": "component-HerregistratieWizardComponent-ad587121f38c0c6e6bb4c8525e7b626054b7790ec708c99a0f6e66b3b7703b935b47c6b141072f7f8c9e567974dd162961ef5e17b983e373d6d7a3a7814aaf80", + "file": "src/app/organisms/herregistratie-wizard/herregistratie-wizard.component.ts", + "encapsulation": [], + "entryComponents": [], + "inputs": [], + "outputs": [], + "providers": [], + "selector": "app-herregistratie-wizard", + "styleUrls": [], + "styles": [], + "template": "@switch (state().tag) {\n @case ('Editing') {\n

Stap {{ step() }} van 2

\n
\n @if (step() === 1) {\n \n \n \n Volgende\n } @else {\n \n \n \n
\n Vorige\n Herregistratie aanvragen\n
\n }\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n }\n @case ('Failed') {\n Indienen mislukt. Probeer het opnieuw.\n
\n Opnieuw proberen\n
\n }\n}\n", + "templateUrl": [], + "viewProviders": [], + "hostDirectives": [], + "inputsClass": [ + { + "name": "seed", + "defaultValue": "initial", + "deprecated": false, + "deprecationMessage": "", + "type": "WizardState", + "indexKey": "", + "optional": false, + "description": "

Optional seed so Storybook / the showcase can mount any state directly.

\n", + "line": 58, + "rawdescription": "\nOptional seed so Storybook / the showcase can mount any state directly.", + "required": false + } + ], + "outputsClass": [], "propertiesClass": [ { - "name": "punten", - "defaultValue": "''", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "indexKey": "", - "optional": false, - "description": "", - "line": 45 - }, - { - "name": "submitted", - "defaultValue": "signal(false)", + "name": "back", + "defaultValue": "back", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 47 + "line": 61 }, { - "name": "uren", - "defaultValue": "''", - "deprecated": false, - "deprecationMessage": "", - "type": "string", - "indexKey": "", - "optional": false, - "description": "", - "line": 44 - }, - { - "name": "urenError", - "defaultValue": "signal('')", + "name": "draft", + "defaultValue": "computed(() => this.editing()?.draft ?? { uren: '', punten: '' })", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 46 + "line": 65, + "modifierKind": [ + 124 + ] + }, + { + "name": "editing", + "defaultValue": "computed(() => (this.state().tag === 'Editing' ? (this.state() as Extract) : null))", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 63, + "modifierKind": [ + 123 + ] + }, + { + "name": "errPunten", + "defaultValue": "computed(() => this.editing()?.errors.punten ?? '')", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 67, + "modifierKind": [ + 124 + ] + }, + { + "name": "errUren", + "defaultValue": "computed(() => this.editing()?.errors.uren ?? '')", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 66, + "modifierKind": [ + 124 + ] + }, + { + "name": "state", + "defaultValue": "signal(initial)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 59 + }, + { + "name": "step", + "defaultValue": "computed(() => this.editing()?.step ?? 1)", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "indexKey": "", + "optional": false, + "description": "", + "line": 64, + "modifierKind": [ + 124 + ] } ], "methodsClass": [ { - "name": "onSubmit", + "name": "onPrimary", "args": [], "optional": false, "returnType": "void", "typeParameters": [], - "line": 49, + "line": 79, "deprecated": false, "deprecationMessage": "" + }, + { + "name": "onRetry", + "args": [], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 86, + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "runIfSubmitting", + "args": [], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 94, + "deprecated": false, + "deprecationMessage": "", + "modifierKind": [ + 123 + ] + }, + { + "name": "set", + "args": [ + { + "name": "key", + "type": "unknown", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "value", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "" + } + ], + "optional": false, + "returnType": "void", + "typeParameters": [], + "line": 73, + "deprecated": false, + "deprecationMessage": "", + "jsdoctags": [ + { + "name": "key", + "type": "unknown", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "value", + "type": "string", + "optional": false, + "dotDotDotToken": false, + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] } ], "deprecated": false, @@ -1280,11 +1795,11 @@ "type": "module" }, { - "name": "PageShellComponent", + "name": "FormFieldComponent", "type": "component" }, { - "name": "AlertComponent", + "name": "TextInputComponent", "type": "component" }, { @@ -1292,21 +1807,29 @@ "type": "component" }, { - "name": "FormFieldComponent", + "name": "AlertComponent", "type": "component" }, { - "name": "TextInputComponent", + "name": "SpinnerComponent", "type": "component" } ], - "description": "

A whole new page built from existing building blocks — no new components.\nThis is the atomic-design payoff: a new flow is just composition.

\n", - "rawdescription": "\nA whole new page built from existing building blocks — no new components.\nThis is the atomic-design payoff: a new flow is just composition.", + "description": "

Organism: multi-step herregistratie wizard driven entirely by a state\nmachine (wizard.machine.ts). The signal IS the union, so the UI just folds\nover its tag — no booleans like submitting/submitted that could contradict\neach other. Composition-only: reuses existing form-field/input/button/alert.

\n", + "rawdescription": "\nOrganism: multi-step herregistratie wizard driven entirely by a state\nmachine (wizard.machine.ts). The signal IS the union, so the UI just folds\nover its tag — no booleans like `submitting`/`submitted` that could contradict\neach other. Composition-only: reuses existing form-field/input/button/alert.", "type": "component", - "sourceCode": "import { Component, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { PageShellComponent } from '../../templates/page-shell/page-shell.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\n\n/** A whole new page built from existing building blocks — no new components.\n This is the atomic-design payoff: a new flow is just composition. */\n@Component({\n selector: 'app-herregistratie-page',\n imports: [\n FormsModule, PageShellComponent, AlertComponent,\n ButtonComponent, FormFieldComponent, TextInputComponent,\n ],\n template: `\n \n \n Uw huidige registratie verloopt op 1 september 2027. Vraag tijdig herregistratie aan.\n \n\n @if (submitted()) {\n
\n Uw aanvraag tot herregistratie is ontvangen.\n
\n } @else {\n
\n \n \n \n \n \n \n
\n Herregistratie aanvragen\n
\n
\n }\n
\n `,\n})\nexport class HerregistratiePage {\n uren = '';\n punten = '';\n urenError = signal('');\n submitted = signal(false);\n\n onSubmit() {\n if (!this.uren.trim()) { this.urenError.set('Vul het aantal gewerkte uren in.'); return; }\n this.urenError.set('');\n this.submitted.set(true);\n }\n}\n", + "sourceCode": "import { Component, computed, input, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { FormFieldComponent } from '../../molecules/form-field/form-field.component';\nimport { TextInputComponent } from '../../atoms/text-input/text-input.component';\nimport { ButtonComponent } from '../../atoms/button/button.component';\nimport { AlertComponent } from '../../atoms/alert/alert.component';\nimport { SpinnerComponent } from '../../atoms/spinner/spinner.component';\nimport { ok } from '../../core/fp';\nimport { WizardState, Draft, initial, next, back, submit, resolve } from './wizard.machine';\n\n/** Organism: multi-step herregistratie wizard driven entirely by a state\n machine (wizard.machine.ts). The signal IS the union, so the UI just folds\n over its tag — no booleans like `submitting`/`submitted` that could contradict\n each other. Composition-only: reuses existing form-field/input/button/alert. */\n@Component({\n selector: 'app-herregistratie-wizard',\n imports: [FormsModule, FormFieldComponent, TextInputComponent, ButtonComponent, AlertComponent, SpinnerComponent],\n template: `\n @switch (state().tag) {\n @case ('Editing') {\n

Stap {{ step() }} van 2

\n
\n @if (step() === 1) {\n \n \n \n Volgende\n } @else {\n \n \n \n
\n Vorige\n Herregistratie aanvragen\n
\n }\n
\n }\n @case ('Submitting') {\n Aanvraag wordt verwerkt…\n }\n @case ('Submitted') {\n Uw aanvraag tot herregistratie is ontvangen.\n }\n @case ('Failed') {\n Indienen mislukt. Probeer het opnieuw.\n
\n Opnieuw proberen\n
\n }\n }\n `,\n})\nexport class HerregistratieWizardComponent {\n /** Optional seed so Storybook / the showcase can mount any state directly. */\n seed = input(initial);\n state = signal(initial);\n\n back = back;\n\n private editing = computed(() => (this.state().tag === 'Editing' ? (this.state() as Extract) : null));\n protected step = computed(() => this.editing()?.step ?? 1);\n protected draft = computed(() => this.editing()?.draft ?? { uren: '', punten: '' });\n protected errUren = computed(() => this.editing()?.errors.uren ?? '');\n protected errPunten = computed(() => this.editing()?.errors.punten ?? '');\n\n constructor() {\n queueMicrotask(() => this.state.set(this.seed()));\n }\n\n set(key: keyof Draft, value: string) {\n const s = this.state();\n if (s.tag !== 'Editing') return;\n this.state.set({ ...s, draft: { ...s.draft, [key]: value } });\n }\n\n onPrimary() {\n const s = this.state();\n if (s.tag !== 'Editing') return;\n this.state.set(s.step === 1 ? next(s) : submit(s));\n this.runIfSubmitting();\n }\n\n onRetry() {\n const s = this.state();\n if (s.tag !== 'Failed') return;\n this.state.set({ tag: 'Submitting', data: s.data });\n this.runIfSubmitting();\n }\n\n // ponytail: fake the backend with a timer; swap for a real httpResource call later.\n private runIfSubmitting() {\n if (this.state().tag !== 'Submitting') return;\n setTimeout(() => this.state.set(resolve(this.state(), ok(undefined))), 800);\n }\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", + "constructorObj": { + "name": "constructor", + "description": "", + "deprecated": false, + "deprecationMessage": "", + "args": [], + "line": 67 + }, "extends": [] }, { @@ -1729,7 +2252,7 @@ }, { "name": "RegistrationSummaryComponent", - "id": "component-RegistrationSummaryComponent-eb956e029fecdfc5c3173f0c48515cad94e7bb8023ebcc4d0a37ae311c0b39c215fb46643ac356b4dd5c0f4ead6123e79f4f7aeecc3b631eff0269e9f1a3e914", + "id": "component-RegistrationSummaryComponent-cad630ad4fd52e8743ba17ebf8f1cf8362c4f8677937ac6e41fadc055438d0c35c6756d7f2d39b67239ceb2af09e0a11c141fa9cd398f80e5d18a8e6960d90ee", "file": "src/app/organisms/registration-summary/registration-summary.component.ts", "encapsulation": [], "entryComponents": [], @@ -1739,7 +2262,7 @@ "selector": "app-registration-summary", "styleUrls": [], "styles": [], - "template": "
\n
\n \n \n \n \n \n \n \n \n
\n
\n", + "template": "
\n
\n \n \n \n \n \n \n \n \n @switch (reg().status.tag) {\n @case ('Geregistreerd') {\n \n }\n @case ('Geschorst') {\n \n \n }\n @case ('Doorgehaald') {\n \n \n }\n }\n
\n
\n", "templateUrl": [], "viewProviders": [], "hostDirectives": [], @@ -1752,7 +2275,7 @@ "indexKey": "", "optional": false, "description": "", - "line": 27, + "line": 40, "required": true } ], @@ -1781,7 +2304,7 @@ "description": "

Organism: registration summary card. Composes data-row molecules + status-badge atom.

\n", "rawdescription": "\nOrganism: registration summary card. Composes data-row molecules + status-badge atom.", "type": "component", - "sourceCode": "import { Component, input } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { Registration } from '../../core/models';\nimport { DataRowComponent } from '../../molecules/data-row/data-row.component';\nimport { StatusBadgeComponent } from '../../atoms/status-badge/status-badge.component';\n\n/** Organism: registration summary card. Composes data-row molecules + status-badge atom. */\n@Component({\n selector: 'app-registration-summary',\n imports: [DatePipe, DataRowComponent, StatusBadgeComponent],\n template: `\n
\n
\n \n \n \n \n \n \n \n \n
\n
\n `,\n})\nexport class RegistrationSummaryComponent {\n reg = input.required();\n}\n", + "sourceCode": "import { Component, input } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { Registration } from '../../core/models';\nimport { DataRowComponent } from '../../molecules/data-row/data-row.component';\nimport { StatusBadgeComponent } from '../../atoms/status-badge/status-badge.component';\n\n/** Organism: registration summary card. Composes data-row molecules + status-badge atom. */\n@Component({\n selector: 'app-registration-summary',\n imports: [DatePipe, DataRowComponent, StatusBadgeComponent],\n template: `\n
\n
\n \n \n \n \n \n \n \n \n @switch (reg().status.tag) {\n @case ('Geregistreerd') {\n \n }\n @case ('Geschorst') {\n \n \n }\n @case ('Doorgehaald') {\n \n \n }\n }\n
\n
\n `,\n})\nexport class RegistrationSummaryComponent {\n reg = input.required();\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -2231,7 +2754,7 @@ }, { "name": "StatusBadgeComponent", - "id": "component-StatusBadgeComponent-3374b85f056b7a50e5cce348202733fea8109c0244004ec59ac829d32a629dbd750f1a087eabbdad5fce0aba1cf9495090e7a949f0bec2cdc371f854fc77d365", + "id": "component-StatusBadgeComponent-5cb9f831687f564e86bbc72b1276fd5b10bc1bc877bf15a2dfff580724e2795f7abdc861ad6ccce41846084a022c11d4c570924d3f7ec1388a1cd5e31b9fd8a2", "file": "src/app/atoms/status-badge/status-badge.component.ts", "encapsulation": [], "entryComponents": [], @@ -2250,11 +2773,11 @@ "name": "status", "deprecated": false, "deprecationMessage": "", - "type": "RegistrationStatus", + "type": "StatusTag", "indexKey": "", "optional": false, "description": "", - "line": 16, + "line": 18, "required": true } ], @@ -2262,14 +2785,14 @@ "propertiesClass": [ { "name": "color", - "defaultValue": "computed(() => ({\n Geregistreerd: 'var(--rhc-color-groen-500)',\n Doorgehaald: 'var(--rhc-color-rood-500)',\n Geschorst: 'var(--rhc-color-oranje-500)',\n }[this.status()]))", + "defaultValue": "computed(() => {\n const tag = this.status();\n switch (tag) {\n case 'Geregistreerd':\n return 'var(--rhc-color-groen-500)';\n case 'Doorgehaald':\n return 'var(--rhc-color-rood-500)';\n case 'Geschorst':\n return 'var(--rhc-color-oranje-500)';\n default:\n return assertNever(tag);\n }\n })", "deprecated": false, "deprecationMessage": "", "type": "unknown", "indexKey": "", "optional": false, "description": "", - "line": 17 + "line": 19 } ], "methodsClass": [], @@ -2279,10 +2802,10 @@ "hostListeners": [], "standalone": false, "imports": [], - "description": "

Atom: status badge = colored RHC dot-badge + label. Hand-built to show how\nyou compose a new atom from design tokens (dot color driven per status).

\n", - "rawdescription": "\nAtom: status badge = colored RHC dot-badge + label. Hand-built to show how\nyou compose a new atom from design tokens (dot color driven per status).", + "description": "

Atom: status badge = colored RHC dot-badge + label. The color is a total\nfunction of the status tag — assertNever makes a new status fail to compile\nuntil a color is chosen for it.

\n", + "rawdescription": "\nAtom: status badge = colored RHC dot-badge + label. The color is a total\nfunction of the status tag — assertNever makes a new status fail to compile\nuntil a color is chosen for it.", "type": "component", - "sourceCode": "import { Component, computed, input } from '@angular/core';\nimport { RegistrationStatus } from '../../core/models';\n\n/** Atom: status badge = colored RHC dot-badge + label. Hand-built to show how\n you compose a new atom from design tokens (dot color driven per status). */\n@Component({\n selector: 'app-status-badge',\n template: `\n \n \n {{ status() }}\n \n `,\n})\nexport class StatusBadgeComponent {\n status = input.required();\n color = computed(() => ({\n Geregistreerd: 'var(--rhc-color-groen-500)',\n Doorgehaald: 'var(--rhc-color-rood-500)',\n Geschorst: 'var(--rhc-color-oranje-500)',\n }[this.status()]));\n}\n", + "sourceCode": "import { Component, computed, input } from '@angular/core';\nimport { StatusTag } from '../../core/models';\nimport { assertNever } from '../../core/fp';\n\n/** Atom: status badge = colored RHC dot-badge + label. The color is a total\n function of the status tag — assertNever makes a new status fail to compile\n until a color is chosen for it. */\n@Component({\n selector: 'app-status-badge',\n template: `\n \n \n {{ status() }}\n \n `,\n})\nexport class StatusBadgeComponent {\n status = input.required();\n color = computed(() => {\n const tag = this.status();\n switch (tag) {\n case 'Geregistreerd':\n return 'var(--rhc-color-groen-500)';\n case 'Doorgehaald':\n return 'var(--rhc-color-rood-500)';\n case 'Geschorst':\n return 'var(--rhc-color-oranje-500)';\n default:\n return assertNever(tag);\n }\n });\n}\n", "assetsDirs": [], "styleUrlsData": "", "stylesData": "", @@ -2630,6 +3153,36 @@ "rawdescription": "Convenience: import this array to get the wrapper + all slot directives.", "description": "

Convenience: import this array to get the wrapper + all slot directives.

\n" }, + { + "name": "err", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/core/fp.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "defaultValue": "(error: E): Result => ({ ok: false, error })" + }, + { + "name": "initial", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "WizardState", + "defaultValue": "{ tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} }" + }, + { + "name": "ok", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/core/fp.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "defaultValue": "(value: T): Result => ({ ok: true, value })" + }, { "name": "routes", "ctype": "miscellaneous", @@ -2638,7 +3191,7 @@ "deprecated": false, "deprecationMessage": "", "type": "Routes", - "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" + "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./pages/concepts/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" }, { "name": "scenarioInterceptor", @@ -2664,6 +3217,64 @@ } ], "functions": [ + { + "name": "assertNever", + "file": "src/app/core/fp.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Tiny native-TS functional toolkit. No dependency — this is the whole "library".\nReused by every "impossible states" concept in the POC.

\n", + "args": [ + { + "name": "x", + "type": "never", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "never", + "jsdoctags": [ + { + "name": "x", + "type": "never", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "back", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, { "name": "currentScenario", "file": "src/app/core/scenario.ts", @@ -2674,6 +3285,344 @@ "description": "

Reads ?scenario= from the URL so a demo can force each async state.

\n", "args": [], "returnType": "Scenario" + }, + { + "name": "fakeResource", + "file": "src/app/pages/concepts/concepts.page.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Minimal fake Resource so can be driven through every state without HTTP.

\n", + "args": [ + { + "name": "status", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "value", + "type": "T", + "deprecated": false, + "deprecationMessage": "", + "optional": true + }, + { + "name": "error", + "type": "Error", + "deprecated": false, + "deprecationMessage": "", + "optional": true + } + ], + "returnType": "Resource", + "jsdoctags": [ + { + "name": "status", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "value", + "type": "T", + "deprecated": false, + "deprecationMessage": "", + "optional": true, + "tagName": { + "text": "param" + } + }, + { + "name": "error", + "type": "Error", + "deprecated": false, + "deprecationMessage": "", + "optional": true, + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "foldRemote", + "file": "src/app/core/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Exhaustive fold: you must handle every case, checked at compile time.

\n", + "args": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "h", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "R", + "jsdoctags": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "h", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "fromResource", + "file": "src/app/core/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Project Angular's loosely-typed Resource into a RemoteData value.

\n", + "args": [ + { + "name": "r", + "type": "Resource", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "isEmpty", + "deprecated": false, + "deprecationMessage": "", + "defaultValue": "() => false" + } + ], + "returnType": "RemoteData", + "jsdoctags": [ + { + "name": "r", + "type": "Resource", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "isEmpty", + "deprecated": false, + "deprecationMessage": "", + "defaultValue": "() => false", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "next", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op.

\n", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "parsePostcode", + "file": "src/app/core/parse.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result", + "jsdoctags": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "parseUren", + "file": "src/app/core/parse.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result", + "jsdoctags": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "resolve", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Resolve the async submit. Only meaningful while Submitting.

\n", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "r", + "type": "Result", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "r", + "type": "Result", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "submit", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Step 2 submit: parse everything; move to Submitting only with Valid data.

\n", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "validate", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Parse every field; on success hand back a Valid, else the per-field errors.

\n", + "args": [ + { + "name": "draft", + "type": "Draft", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result>, Valid>", + "jsdoctags": [ + { + "name": "draft", + "type": "Draft", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] } ], "typealiases": [ @@ -2688,15 +3637,59 @@ "description": "", "kind": 193 }, + { + "name": "Brand", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "unknown", + "file": "src/app/core/fp.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Nominal typing: Brand<string, 'Postcode'> is assignable from a plain string\nonly through an explicit cast — so a smart constructor is the only minter.

\n", + "kind": 194 + }, + { + "name": "Postcode", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "Brand", + "file": "src/app/core/parse.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

"Parse, don't validate." A validated value gets its own branded type, and the\nsmart constructor is the ONLY way to mint one. So once you hold a Postcode you\nknow it's well-formed — validity is carried in the type, not re-checked\neverywhere or tracked in a parallel error flag.

\n", + "kind": 184 + }, { "name": "RegistrationStatus", "ctype": "miscellaneous", "subtype": "typealias", - "rawtype": "\"Geregistreerd\" | \"Doorgehaald\" | \"Geschorst\"", + "rawtype": "literal type | literal type | literal type", "file": "src/app/core/models.ts", "deprecated": false, "deprecationMessage": "", - "description": "", + "description": "

Registration status as a discriminated union: each variant owns exactly the\ndata that makes sense for it. Only an active (Geregistreerd) registration has\na herregistratie date; a struck-off (Doorgehaald) one cannot carry one. The\nold flat interface allowed that impossible combination — this makes it\nunrepresentable.

\n", + "kind": 193 + }, + { + "name": "RemoteData", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type | literal type | literal type", + "file": "src/app/core/remote-data.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

The four mutually-exclusive states of an async fetch, as a tagged union.\nCrucially the data lives ON the state: only Failure has an error, only\nSuccess has a value. "Loaded but no value" or "error with stale value"\nare unrepresentable — Richard Feldman's RemoteData.

\n", + "kind": 193 + }, + { + "name": "Result", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type", + "file": "src/app/core/fp.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

A computation that either succeeded with a value or failed with an error.\nPlain objects (no classes) to match the signal/httpResource ergonomics.

\n", "kind": 193 }, { @@ -2711,15 +3704,26 @@ "kind": 193 }, { - "name": "State", + "name": "StatusTag", "ctype": "miscellaneous", "subtype": "typealias", - "rawtype": "\"loading\" | \"error\" | \"empty\" | \"loaded\"", - "file": "src/app/molecules/async/async.component.ts", + "rawtype": "RegistrationStatus", + "file": "src/app/core/models.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Just the discriminant — for atoms that only need the label/color.

\n", + "kind": 200 + }, + { + "name": "Uren", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "Brand", + "file": "src/app/core/parse.ts", "deprecated": false, "deprecationMessage": "", "description": "", - "kind": 193 + "kind": 184 }, { "name": "Variant", @@ -2731,6 +3735,17 @@ "deprecationMessage": "", "description": "", "kind": 193 + }, + { + "name": "WizardState", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type | literal type | literal type", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

The whole wizard as one tagged union. step and errors exist ONLY while\nEditing; Submitting/Submitted/Failed carry a Valid payload and nothing else.\nSo "submitting while a field is invalid" or "showing the success screen with\nerrors set" are unrepresentable — the bug class is gone by construction.

\n", + "kind": 193 } ], "enumerations": [], @@ -2761,6 +3776,40 @@ "description": "

Convenience: import this array to get the wrapper + all slot directives.

\n" } ], + "src/app/core/fp.ts": [ + { + "name": "err", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/core/fp.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "defaultValue": "(error: E): Result => ({ ok: false, error })" + }, + { + "name": "ok", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/core/fp.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "unknown", + "defaultValue": "(value: T): Result => ({ ok: true, value })" + } + ], + "src/app/organisms/herregistratie-wizard/wizard.machine.ts": [ + { + "name": "initial", + "ctype": "miscellaneous", + "subtype": "variable", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "type": "WizardState", + "defaultValue": "{ tag: 'Editing', step: 1, draft: { uren: '', punten: '' }, errors: {} }" + } + ], "src/app/app.routes.ts": [ { "name": "routes", @@ -2770,7 +3819,7 @@ "deprecated": false, "deprecationMessage": "", "type": "Routes", - "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" + "defaultValue": "[\n {\n path: '',\n component: ShellComponent, // persistent header/footer; only children swap\n children: [\n { path: '', pathMatch: 'full', redirectTo: 'login' },\n { path: 'login', loadComponent: () => \"import('./pages/login/login.page').then(m => m.LoginPage)\" },\n { path: 'dashboard', loadComponent: () => \"import('./pages/dashboard/dashboard.page').then(m => m.DashboardPage)\" },\n { path: 'registratie', loadComponent: () => \"import('./pages/registration-detail/registration-detail.page').then(m => m.RegistrationDetailPage)\" },\n { path: 'herregistratie', loadComponent: () => \"import('./pages/herregistratie/herregistratie.page').then(m => m.HerregistratiePage)\" },\n { path: 'concepts', loadComponent: () => \"import('./pages/concepts/concepts.page').then(m => m.ConceptsPage)\" },\n { path: '**', redirectTo: 'login' },\n ],\n },\n]" } ], "src/app/core/scenario.interceptor.ts": [ @@ -2801,6 +3850,199 @@ ] }, "groupedFunctions": { + "src/app/core/fp.ts": [ + { + "name": "assertNever", + "file": "src/app/core/fp.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Tiny native-TS functional toolkit. No dependency — this is the whole "library".\nReused by every "impossible states" concept in the POC.

\n", + "args": [ + { + "name": "x", + "type": "never", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "never", + "jsdoctags": [ + { + "name": "x", + "type": "never", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } + ], + "src/app/organisms/herregistratie-wizard/wizard.machine.ts": [ + { + "name": "back", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "next", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Step 1 → 2: only advance if the uren field parses. Illegal elsewhere = no-op.

\n", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "resolve", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Resolve the async submit. Only meaningful while Submitting.

\n", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "r", + "type": "Result", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "r", + "type": "Result", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "submit", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Step 2 submit: parse everything; move to Submitting only with Valid data.

\n", + "args": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "WizardState", + "jsdoctags": [ + { + "name": "s", + "type": "WizardState", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "validate", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Parse every field; on success hand back a Valid, else the per-field errors.

\n", + "args": [ + { + "name": "draft", + "type": "Draft", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result>, Valid>", + "jsdoctags": [ + { + "name": "draft", + "type": "Draft", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } + ], "src/app/core/scenario.ts": [ { "name": "currentScenario", @@ -2813,6 +4055,219 @@ "args": [], "returnType": "Scenario" } + ], + "src/app/pages/concepts/concepts.page.ts": [ + { + "name": "fakeResource", + "file": "src/app/pages/concepts/concepts.page.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Minimal fake Resource so can be driven through every state without HTTP.

\n", + "args": [ + { + "name": "status", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "value", + "type": "T", + "deprecated": false, + "deprecationMessage": "", + "optional": true + }, + { + "name": "error", + "type": "Error", + "deprecated": false, + "deprecationMessage": "", + "optional": true + } + ], + "returnType": "Resource", + "jsdoctags": [ + { + "name": "status", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "value", + "type": "T", + "deprecated": false, + "deprecationMessage": "", + "optional": true, + "tagName": { + "text": "param" + } + }, + { + "name": "error", + "type": "Error", + "deprecated": false, + "deprecationMessage": "", + "optional": true, + "tagName": { + "text": "param" + } + } + ] + } + ], + "src/app/core/remote-data.ts": [ + { + "name": "foldRemote", + "file": "src/app/core/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Exhaustive fold: you must handle every case, checked at compile time.

\n", + "args": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "h", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "R", + "jsdoctags": [ + { + "name": "rd", + "type": "RemoteData", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "h", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "fromResource", + "file": "src/app/core/remote-data.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "

Project Angular's loosely-typed Resource into a RemoteData value.

\n", + "args": [ + { + "name": "r", + "type": "Resource", + "deprecated": false, + "deprecationMessage": "" + }, + { + "name": "isEmpty", + "deprecated": false, + "deprecationMessage": "", + "defaultValue": "() => false" + } + ], + "returnType": "RemoteData", + "jsdoctags": [ + { + "name": "r", + "type": "Resource", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + }, + { + "name": "isEmpty", + "deprecated": false, + "deprecationMessage": "", + "defaultValue": "() => false", + "tagName": { + "text": "param" + } + } + ] + } + ], + "src/app/core/parse.ts": [ + { + "name": "parsePostcode", + "file": "src/app/core/parse.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result", + "jsdoctags": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + }, + { + "name": "parseUren", + "file": "src/app/core/parse.ts", + "ctype": "miscellaneous", + "subtype": "function", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "args": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "" + } + ], + "returnType": "Result", + "jsdoctags": [ + { + "name": "raw", + "type": "string", + "deprecated": false, + "deprecationMessage": "", + "tagName": { + "text": "param" + } + } + ] + } ] }, "groupedEnumerations": {}, @@ -2830,16 +4285,88 @@ "kind": 193 } ], + "src/app/core/fp.ts": [ + { + "name": "Brand", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "unknown", + "file": "src/app/core/fp.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Nominal typing: Brand<string, 'Postcode'> is assignable from a plain string\nonly through an explicit cast — so a smart constructor is the only minter.

\n", + "kind": 194 + }, + { + "name": "Result", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type", + "file": "src/app/core/fp.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

A computation that either succeeded with a value or failed with an error.\nPlain objects (no classes) to match the signal/httpResource ergonomics.

\n", + "kind": 193 + } + ], + "src/app/core/parse.ts": [ + { + "name": "Postcode", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "Brand", + "file": "src/app/core/parse.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

"Parse, don't validate." A validated value gets its own branded type, and the\nsmart constructor is the ONLY way to mint one. So once you hold a Postcode you\nknow it's well-formed — validity is carried in the type, not re-checked\neverywhere or tracked in a parallel error flag.

\n", + "kind": 184 + }, + { + "name": "Uren", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "Brand", + "file": "src/app/core/parse.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "", + "kind": 184 + } + ], "src/app/core/models.ts": [ { "name": "RegistrationStatus", "ctype": "miscellaneous", "subtype": "typealias", - "rawtype": "\"Geregistreerd\" | \"Doorgehaald\" | \"Geschorst\"", + "rawtype": "literal type | literal type | literal type", "file": "src/app/core/models.ts", "deprecated": false, "deprecationMessage": "", - "description": "", + "description": "

Registration status as a discriminated union: each variant owns exactly the\ndata that makes sense for it. Only an active (Geregistreerd) registration has\na herregistratie date; a struck-off (Doorgehaald) one cannot carry one. The\nold flat interface allowed that impossible combination — this makes it\nunrepresentable.

\n", + "kind": 193 + }, + { + "name": "StatusTag", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "RegistrationStatus", + "file": "src/app/core/models.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

Just the discriminant — for atoms that only need the label/color.

\n", + "kind": 200 + } + ], + "src/app/core/remote-data.ts": [ + { + "name": "RemoteData", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type | literal type | literal type", + "file": "src/app/core/remote-data.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

The four mutually-exclusive states of an async fetch, as a tagged union.\nCrucially the data lives ON the state: only Failure has an error, only\nSuccess has a value. "Loaded but no value" or "error with stale value"\nare unrepresentable — Richard Feldman's RemoteData.

\n", "kind": 193 } ], @@ -2856,19 +4383,6 @@ "kind": 193 } ], - "src/app/molecules/async/async.component.ts": [ - { - "name": "State", - "ctype": "miscellaneous", - "subtype": "typealias", - "rawtype": "\"loading\" | \"error\" | \"empty\" | \"loaded\"", - "file": "src/app/molecules/async/async.component.ts", - "deprecated": false, - "deprecationMessage": "", - "description": "", - "kind": 193 - } - ], "src/app/atoms/button/button.component.ts": [ { "name": "Variant", @@ -2881,6 +4395,19 @@ "description": "", "kind": 193 } + ], + "src/app/organisms/herregistratie-wizard/wizard.machine.ts": [ + { + "name": "WizardState", + "ctype": "miscellaneous", + "subtype": "typealias", + "rawtype": "literal type | literal type | literal type | literal type", + "file": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "deprecated": false, + "deprecationMessage": "", + "description": "

The whole wizard as one tagged union. step and errors exist ONLY while\nEditing; Submitting/Submitted/Failed carry a Valid payload and nothing else.\nSo "submitting while a field is invalid" or "showing the success screen with\nerrors set" are unrepresentable — the bug class is gone by construction.

\n", + "kind": 193 + } ] } }, @@ -2913,6 +4440,11 @@ "kind": "route-path", "filename": "src/app/app.routes.ts" }, + { + "name": "concepts", + "kind": "route-path", + "filename": "src/app/app.routes.ts" + }, { "name": "**", "kind": "route-path", @@ -2931,8 +4463,8 @@ ] }, "coverage": { - "count": 25, - "status": "low", + "count": 41, + "status": "medium", "files": [ { "filePath": "src/app/app.config.ts", @@ -3055,6 +4587,56 @@ "coverageCount": "1/14", "status": "low" }, + { + "filePath": "src/app/core/fp.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "assertNever", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/core/fp.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "err", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/core/fp.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "ok", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/core/fp.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "Brand", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/core/fp.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "Result", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, { "filePath": "src/app/core/models.ts", "type": "interface", @@ -3070,7 +4652,7 @@ "linktype": "interface", "name": "Registration", "coveragePercent": 0, - "coverageCount": "0/8", + "coverageCount": "0/7", "status": "low" }, { @@ -3079,6 +4661,56 @@ "linktype": "miscellaneous", "linksubtype": "typealias", "name": "RegistrationStatus", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/core/models.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "StatusTag", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/core/parse.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "parsePostcode", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/core/parse.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "parseUren", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/core/parse.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "Postcode", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/core/parse.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "Uren", "coveragePercent": 0, "coverageCount": "0/1", "status": "low" @@ -3092,6 +4724,36 @@ "coverageCount": "1/3", "status": "medium" }, + { + "filePath": "src/app/core/remote-data.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "foldRemote", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/core/remote-data.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "fromResource", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/core/remote-data.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "RemoteData", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, { "filePath": "src/app/core/scenario.interceptor.ts", "type": "variable", @@ -3137,8 +4799,8 @@ "type": "component", "linktype": "component", "name": "AsyncComponent", - "coveragePercent": 10, - "coverageCount": "1/10", + "coveragePercent": 9, + "coverageCount": "1/11", "status": "low" }, { @@ -3187,16 +4849,6 @@ "coverageCount": "1/1", "status": "very-good" }, - { - "filePath": "src/app/molecules/async/async.component.ts", - "type": "type alias", - "linktype": "miscellaneous", - "linksubtype": "typealias", - "name": "State", - "coveragePercent": 0, - "coverageCount": "0/1", - "status": "low" - }, { "filePath": "src/app/molecules/data-row/data-row.component.ts", "type": "component", @@ -3220,10 +4872,116 @@ "type": "component", "linktype": "component", "name": "ChangeRequestFormComponent", - "coveragePercent": 14, - "coverageCount": "1/7", + "coveragePercent": 12, + "coverageCount": "1/8", "status": "low" }, + { + "filePath": "src/app/organisms/change-request-form/change-request-form.component.ts", + "type": "interface", + "linktype": "interface", + "name": "ChangeRequest", + "coveragePercent": 25, + "coverageCount": "1/4", + "status": "low" + }, + { + "filePath": "src/app/organisms/herregistratie-wizard/herregistratie-wizard.component.ts", + "type": "component", + "linktype": "component", + "name": "HerregistratieWizardComponent", + "coveragePercent": 14, + "coverageCount": "2/14", + "status": "low" + }, + { + "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "type": "interface", + "linktype": "interface", + "name": "Draft", + "coveragePercent": 33, + "coverageCount": "1/3", + "status": "medium" + }, + { + "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "type": "interface", + "linktype": "interface", + "name": "Valid", + "coveragePercent": 33, + "coverageCount": "1/3", + "status": "medium" + }, + { + "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "back", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "next", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "resolve", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "submit", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "validate", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, + { + "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "type": "variable", + "linktype": "miscellaneous", + "linksubtype": "variable", + "name": "initial", + "coveragePercent": 0, + "coverageCount": "0/1", + "status": "low" + }, + { + "filePath": "src/app/organisms/herregistratie-wizard/wizard.machine.ts", + "type": "type alias", + "linktype": "miscellaneous", + "linksubtype": "typealias", + "name": "WizardState", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, { "filePath": "src/app/organisms/login-form/login-form.component.ts", "type": "component", @@ -3269,6 +5027,25 @@ "coverageCount": "1/2", "status": "medium" }, + { + "filePath": "src/app/pages/concepts/concepts.page.ts", + "type": "component", + "linktype": "component", + "name": "ConceptsPage", + "coveragePercent": 8, + "coverageCount": "1/12", + "status": "low" + }, + { + "filePath": "src/app/pages/concepts/concepts.page.ts", + "type": "function", + "linktype": "miscellaneous", + "linksubtype": "function", + "name": "fakeResource", + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" + }, { "filePath": "src/app/pages/dashboard/dashboard.page.ts", "type": "component", @@ -3283,9 +5060,9 @@ "type": "component", "linktype": "component", "name": "HerregistratiePage", - "coveragePercent": 16, - "coverageCount": "1/6", - "status": "low" + "coveragePercent": 100, + "coverageCount": "1/1", + "status": "very-good" }, { "filePath": "src/app/pages/login/login.page.ts",