feat(showcase): WP-39 — linked code snippets + teaching animations
CI / frontend (push) Successful in 2m5s
CI / storybook-a11y (push) Successful in 5m56s
CI / backend (push) Successful in 1m30s
CI / e2e (push) Successful in 2m51s
CI / semgrep (push) Successful in 1m1s
CI / api-client-drift (push) Successful in 2m6s

Anti-drift snippets: `?raw` isn't supported by Angular's esbuild build, so real code
is exposed via // #region showcase:<name> markers in source (registration/remote-data/
postcode/change-request.machine/intake.machine) → scripts/gen-snippets.mjs → committed
snippets.generated.ts, with a CI drift gate so the shown code is the shipped code. The
/concepts page renders the 5 real snippets (union/fold/parse/machine/steps) with a source
caption + a tiny dependency-free highlightTs (+spec); deliberately-wrong illustrations stay
authored. Teaching motion: active state node pops on transition + parse result animates in
(existing reduced-motion-safe toolkit). Generated file prettier-ignored (like api-client).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-23 14:49:52 +02:00
co-authored by Claude Opus 4.8
parent f8cd77a48a
commit 19f2e9b734
16 changed files with 258 additions and 34 deletions
@@ -24,11 +24,13 @@ export type Errors = Partial<Record<keyof Draft, string>>;
* Submitting/Submitted/Failed carry the parsed `Valid`. Illegal states (submitting
* an invalid draft, a success screen with errors) are unrepresentable.
*/
// #region showcase:machine
export type ChangeRequestState =
| { tag: 'Editing'; draft: Draft; errors: Errors }
| { tag: 'Submitting'; data: Valid }
| { tag: 'Editing'; draft: Draft; errors: Errors } // draft/errors exist ONLY while editing
| { tag: 'Submitting'; data: Valid } // carries the parsed value, no errors
| { tag: 'Submitted'; data: Valid; referentie: string }
| { tag: 'Failed'; data: Valid; error: string };
// #endregion showcase:machine
export const initial: ChangeRequestState = {
tag: 'Editing',
+3 -1
View File
@@ -5,10 +5,12 @@
* old flat interface allowed that impossible combination — this makes it
* unrepresentable.
*/
// #region showcase:union
export type RegistrationStatus =
| { tag: 'Geregistreerd'; herregistratieDatum: string } // ISO date
| { tag: 'Geregistreerd'; herregistratieDatum: string } // only this variant carries the date
| { tag: 'Geschorst'; geschorstTot: string; reden: string }
| { tag: 'Doorgehaald'; doorgehaaldOp: string; reden: string };
// #endregion showcase:union
/** Just the discriminant — for atoms that only need the label/color. */
export type StatusTag = RegistrationStatus['tag'];
@@ -7,6 +7,7 @@ import { Brand, Result, ok, err } from '@shared/kernel/fp';
*/
export type Postcode = Brand<string, 'Postcode'>;
// #region showcase:parse
export function parsePostcode(raw: string): Result<string, Postcode> {
const t = raw.trim().toUpperCase();
if (!/^[1-9]\d{3}\s?[A-Z]{2}$/.test(t)) {
@@ -15,3 +16,4 @@ export function parsePostcode(raw: string): Result<string, Postcode> {
// Normalise to "1234 AB" — the parser also cleans up.
return ok(t.replace(/^(\d{4})\s?([A-Z]{2})$/, '$1 $2') as Postcode);
}
// #endregion showcase:parse