• Rust 50.3%
  • JavaScript 23.6%
  • Vue 13.8%
  • Gherkin 7.2%
  • Python 2%
  • Other 3.1%
Find a file
Anne Schuth 8d48bfb424
feat(demo): opgenomen rondleiding met stem, dia's en veelgestelde vragen (#1654)
* feat(demo): opgenomen rondleiding met stem, dia's en veelgestelde vragen

Een rondleiding door de demo voor wie zonder presentator kijkt. De demo
staat er als video in, met de stem van de presentator; de dia's links
blijven echte tekst en zijn na de opname nog aan te passen. Optioneel de
presentator zelf in een rondje, ondertitels, hoofdstukken, uitgeschreven
tekst, en veelgestelde vragen die op het juiste moment verschijnen en een
eigen opgenomen antwoord hebben.

- Opnemen: `just walkthrough-record` opent de demo met een opnamepaneel.
  Het neemt het tabblad op (bijgesneden tot de werkruimte), de microfoon,
  de webcam en een log van dia's, klikken en typen. Alleen in de
  dev-server, alleen vanaf deze machine; niet in de productiebundel.
- Nabewerken: `just walkthrough prepare|build|export` (Python via uv):
  ruis eruit, transcriptie met woordtijden, voorstel voor knippen,
  knippen op hele frames, ondertitels, en een deelbare MP4.
- Afspelen: `/rondleiding` (`/en/tour`), met bediening uit het design
  system en "Probeer het zelf" naar de live demo.
- De video's staan niet in git maar in een GitHub-release; de
  Docker-build haalt ze op en controleert de checksum.

Er staat nog geen opname in; zonder timeline.json toont de demo geen knop.

* fix(demo): minder knoppen in de bediening van de rondleiding

Speelsnelheid, ondertitels, de uitgeschreven tekst, de presentator in beeld en terug naar de demo staan onder één menu. De knoppen voor tien seconden terug en vooruit zijn weg (j en l blijven werken), en 'Naar de demo' valt samen met 'Probeer het zelf'. Onder de bediening staat nog één knop, afhankelijk van waar de kijker is: terug uit een vraag, of de live demo in.

* fix(demo): rondleiding neemt het hele venster op, in zaalmodus

Een uitsnede van de werkruimte naast de rail liet zijpanelen en dialogen buiten beeld: die staan tegen de rand van het venster, en de wettenlijst opende over de rail. De opname is nu het hele tabblad, in zaalmodus zoals de demo in een zaal gegeven wordt; de speler zet de diatekst er weer naast. Het opnamepaneel verdwijnt tijdens de opname, de tabtitel toont ● REC. Op een dia over het hele scherm krijgt de bediening dezelfde marge als de voettekst van het dek.

* feat(demo): rondleiding speelt zich af in de echte demo, niet als video

De rondleiding is geen video van de demo meer. Je hoort de stem van de
presentator, en ondertussen doet de demo zelf wat de presentator deed:
het dek wisselt van dia en opent tabbladen, knoppen worden aangeklikt en
tekst verschijnt letter voor letter, in de live app. Een cursor beweegt
mee. Pauzeren geeft de demo aan de kijker; verder afspelen zet de
opgenomen toestand terug.

- Opnemen legt per actie vast welk element het was (tekst, label, link,
  door de shadow roots van het ontwerpsysteem heen), niet waar op het
  scherm. Een opname op de ene vensterbreedte speelt af op een andere;
  nagemeten van 1100 tot 1920 pixels, met de rail ernaast.
- Afspelen wacht op een element dat nog laadt, en de stem wacht mee. De
  klok staat op het moment van opnemen, zodat bedragen en termijnen
  kloppen met wat er gezegd wordt.
- Springen zet de toestand van het hoofdstuk terug en speelt de acties
  tot het doel snel af. De eigen sessie van de kijker wordt niet
  overschreven.
- De nabewerking levert de stem los, de acties (een actie in een knip
  verhuist naar de knip in plaats van te verdwijnen) en een toestand per
  hoofdstuk. Een telefoon krijgt de opname van het venster als video.

* fix(demo): wettenlijst naast de rail en de bevindingen uit de review

Een paneel dat links opent (de wettenlijst op het tabblad Wetten) stond
tegen de vensterrand, en met het dek als rail dus over de dia's, ook in
zelfstandig-modus. Het ontwerpsysteem heeft daar geen haak voor; tot die
er is zet de demo een regel in de shadow root van die componenten die het
paneel, en het inschuiven, naar de rand van de demo verplaatst. Een test
faalt zodra de interne klassenamen veranderen.

Uit de review van de afspeelmotor:
- een actie die nog wachtte, liep na een sprong, na verlaten of na
  overnemen door de kijker niet meer door in de demo;
- verlaten in het eerste hoofdstuk startte de rondleiding opnieuw;
- de ruimere zoektocht naar een element klikt geen willekeurige knop meer,
  en pas na wachten;
- springen speelt geen acties meer af die al in de toestand zaten;
- een lopende presentatie geeft haar toetsen af, de datum op de titeldia
  volgt de klok weer, en een taalwissel vervangt de opgenomen dia's niet;
- geen sneltoetsen van één letter meer (WCAG 2.1.4); spatie, pijltjes en
  Escape horen tijdens het afspelen bij de speler, ook als de focus nog in
  een veld staat waar de rondleiding in typte.

* feat(demo): hoofdstukken van de rondleiding uit een script, met gegenereerde stem

Een hoofdstuk kan nu ook uit tekst komen in plaats van uit een opname. Het
script is tekst met markeringen ("Ik open [1] de lijst met wetten"), naast
een stille opname met de klikken en het typen. De stem wordt per regel
gegenereerd (ElevenLabs, met een kloon van de eigen stem; om te proberen de
eigen stem van de Mac), met de tijd van elk teken, en elke handeling valt
op het woord na haar markering. Duurt een handeling langer dan de woorden,
dan wacht de volgende regel.

Opgenomen en gegenereerde stukken staan op één tijdlijn: een opgenomen
opening met de presentator in beeld, daarna de hoofdstukken uit het
script. Het rondje verdwijnt waar de gegenereerde stem begint, en naast de
bediening staat dat die stem met AI is gemaakt (AI-verordening, art. 50).
Een ondertitel breekt waar een opgenomen stuk overgaat in een gegenereerd.

Daarbij een fout uit de bestaande code: met meer dan één opname kwam de
stem uit het verkeerde bestand.

* feat(demo): twee soorten antwoord op een veelgestelde vraag

Een oordeel neemt de presentator in beeld op; zonder demo erbij staat het gezicht dan groot naast de vraag. Een laat-maar-zien-antwoord komt uit een script met de gegenereerde stem en de demo die meeklikt. Tijdens elk antwoord staat de vraag zelf als titel op de dia's, in plaats van de dia waarop het antwoord is opgenomen.

* feat(demo): van opname naar script, en publiceren van de rondleiding

walkthrough script maakt uit een opname een concept-script per dia: de zinnen die gezegd zijn, met een markering waar elke handeling begon. Wie een hoofdstuk opneemt zoals in een presentatie, praten en klikken tegelijk, krijgt zo zijn eigen woorden terug om door de gekloonde stem te laten voorlezen, met de klikken waar ze waren. Een script kan een stuk van een langere opname beslaan.

walkthrough voices toont de stemmen op het ElevenLabs-account, walkthrough publish zet de media in een GitHub-release (en vraagt dat eerst) en schrijft de tag in timeline.json. De docs hebben het draaiboek van opname tot release.

* feat(demo): de rondleiding neemt zoomen en slepen in de graaf op

Slepen en zoomen in de graaf laten geen aangeklikt element achter, dus het actielog zag ze niet. De graaf meldt nu waar hij naar kijkt (het midden van het vlak in graafcoördinaten plus de zoom), tien keer per seconde tijdens het bewegen en nog eens in rust; de rondleiding stuurt hem daar met een korte glijbeweging heen. Omdat het een punt in de graaf is en geen pixelpositie, landt het ook op een ander schermformaat goed.

Daarnaast: alles van de rondleiding gaat via één recept (just walkthrough record, prepare, check, script, build, publish, status, test), en just walkthrough check zegt per opname hoe hij klinkt: luidheid, oversturing, ruis, spreektempo en de gelogde handelingen, met een oordeel per regel.

* chore(dev): de rondleiding via één just-recept

just walkthrough record en just walkthrough test in plaats van losse recepten; de rest gaat door naar de pipeline. Hoort bij de vorige commit (het pad heet Justfile, met hoofdletter).

* feat(demo): microfoontest in het opnamepaneel

Voor een opname kies je de microfoon en zie je het spraakniveau live, met een oordeel: geen geluid, te zacht, goed of te hard. Gemeten als spraakniveau (RMS) en niet als piek: de eerste echte testopname piekte op -15 dB terwijl de stem rond -40 zat, veel te zacht. Een opname gebruikt de gekozen microfoon.

* feat(demo): rondleiding houdt bij wat hij niet terugvindt

Een handeling waarvan de replay het element niet vindt, komt in een lijst (en in de console tijdens ontwikkelen): dat is wat er na een wijziging in de demo opnieuw opgenomen moet worden. Een opname bewaart ook de naam van de microfoon, zodat een te zachte opname te herleiden is.

* fix(demo): menu's van de rondleiding openen naar boven

De bediening staat onderaan het scherm. Floating UI krimpt een menu dat onder te weinig ruimte heeft in plaats van het altijd om te klappen, waardoor het menu onder '...' de tweede keer afgesneden werd. Beide menu's openen nu naar boven.

* feat(demo): taalmodel kijkt het transcript van een opname na

Whisper verstaat beleidstaal verkeerd. Na het transcriberen krijgt een
taalmodel de tekst met de woordenlijst en de diateksten erbij, en geeft
dezelfde spraak terug met de verhaspelingen verbeterd. De verbeterde tekst
wordt letter voor letter teruggelegd op de tijden van Whisper, zodat
ondertitels en acties op hun plek blijven.

* feat(demo): nakijken van het transcript ziet ook wat er op het scherm stond

Het taalmodel krijgt schermbeelden uit de opname (bij elke routewissel,
elke klik en om de zes seconden) en de labels van waarop geklikt is. Een
woord dat in beeld staat, zoals bsn, wint dan van wat Whisper meende te
horen.

* feat(demo): cameratest in het opnamepaneel

Een ronde voorvertoning zoals de kijker het bolletje ziet, een keuze van
de camera en een oordeel over het licht, zodat achtergrond en licht voor
de opname goed staan. De opname gebruikt de gekozen camera.

* fix(demo): rondleiding houdt de taal van de kijker en loopt niet vast

- De knoppen van de speler blijven in de taal van de kijker; alleen de
  demo eronder draait in het Nederlands, zoals de stem.
- De pagina startte de rondleiding twee keer (mount en activate), waardoor
  de taal van de kijker verloren ging en er twee replays liepen.
- Een sprong is meteen eigenaar van de events: afspelen tijdens een sprong
  past geen events van de oude positie meer toe.
- De klok loopt door op wandtijd zolang de stem stilstaat, zodat ids op
  Date.now() niet botsen.
- Ontbrekende media geven de pagina 'nog geen rondleiding' in plaats van
  een eeuwige spinner, zonder herstart-lus.

* fix(demo): dia's en formaat uit de openingsopname; .walkthrough/.env in de docs-check

De rondleiding nam de dia's en de beeldgrootte van de alfabetisch eerste
opname, niet van het eerste segment. De docs noemen het lokale
sleutelbestand, dat niet in git staat; de padcontrole kent het nu.

* feat(demo): voorpagina en presentatietab zeggen wat elke ingang is

De voorpagina had drie gelijke knoppen zonder uitleg; nu zijn het drie
kaarten (rondleiding, zelf rondkijken, presenteren) met in één regel wat
je krijgt. De presentatietab scheidt zelf presenteren (modus en toetsen)
van kijken zonder presentator, met de dia's eronder. De uitleg van de
zaalmodus is ingekort tot twee zinnen.

* fix(demo): rondleiding vindt velden met een zelfgemaakte id en toont het gezicht groot in de opening

- Een id die een component bij elk renderen verzint (nldd-field-input-<uuid>)
  zegt niets over het veld; de locator laat hem weg, ook in opnames die hem
  nog dragen. De standaardpremie in de simulatie speelt nu terug.
- Op een volle dia met camera (de opening) staat het gezicht groot naast de
  dia, zoals al bij de vragen; zodra de demo opent krimpt het.
- Afspelen of springen terwijl de rondleiding nog laadt, wordt niet meer
  ongedaan gemaakt door de start.

* feat(demo): eerste opname van de rondleiding, opening tot en met de simulatie

Opname van 3 oktober, 12 minuten, met camera. Geknipt: stiltes, een
verspreking, het zoeken naar de vergelijking in de simulatie en het einde
na het doorrekenen van de standaardpremie. De media staan in de release
walkthrough-2026-10-03. Mijn overheid, het zaaksysteem en de afsluiting
volgen in een volgende opname.

* fix(demo): ondertitels leesbaar in dark mode

De tekstkleur kwam uit een primitief token van het ontwerpsysteem, en die
zijn light-dark()-paren: wit werd zwart in dark mode, op een vast zwart
vlak. Ondertitel, rand van het bolletje en cursor hebben nu vaste kleuren.

* fix(demo): rondleiding scrolt naar dezelfde regel, op elk scherm

Een scrollpositie in pixels klopt alleen in de layout van de opname; waar
de wettekst of de trace anders afbreekt, landt hij ergens anders. De
recorder legt nu vast welke regel er midden in beeld stond, en de replay
zet die regel terug op dezelfde plek. Scrollvlakken binnen componenten van
het ontwerpsysteem (shadow DOM) worden nu ook opgenomen, en de rustpositie
van een scrollbeweging altijd. Voor oudere opnames meet `walkthrough
anchors` de ankers achteraf; de opname van 3 oktober heeft ze nu.

* fix(demo): terug naar een opname na een hertake begint in de staat van dat moment

Een stuk dat na een opnieuw opgenomen hoofdstuk terugkeert naar de eerste
opname, zette de demo terug naar het begin van die opname. Nu is het de
momentopname van de dia waar het stuk begint.

* fix(demo): terugkeer naar een opname speelt eerdere handelingen niet opnieuw af

Na een hertake begon het stuk uit de eerste opname met alles wat daar
vóór het teruggezette moment gebeurde. Nu alleen wat na de dia van de
momentopname kwam.

* feat(demo): rondleiding met de opnieuw opgenomen wet en kortere diateksten

De wet (dia 6) komt uit een nieuwe opname waarin het scrollen door de
YAML is vastgelegd; daaromheen de opname van 3 oktober. De diateksten
van de rondleiding volgen kort wat er gezegd wordt; de zaal-deck blijft
zoals hij is.

* feat(demo): skill voor de rondleiding en walkthrough verify

De walkthrough-skill zegt hoe je na een opname te werk gaat: verwerken,
beoordelen, knippen op zinsgrenzen, een hoofdstuk opnieuw opnemen en
aansluiten, wat te doen als de replay iets niet nadoet, en publiceren.
`walkthrough verify` speelt elk hoofdstuk headless terug in twee
vensterformaten en noemt elke handeling die niet gevonden werd.

* ci: rondleiding speelt terug als controle bij elke wijziging aan de demo

Een wijziging in de demo kan een opgenomen klik of scroll zonder doel
laten, en dan laat de rondleiding iets anders zien dan de stem zegt. De
job 'Rondleiding speelt terug' bouwt de engine, start de dev-server en
speelt elk hoofdstuk headless terug in twee vensterformaten; een handeling
die haar element niet vindt laat hem falen, met het hoofdstuk erbij. Een
handeling die alleen losser nog iets vindt is een waarschuwing. Hij draait
bij wijzigingen aan frontend-demo/, corpus/demo/, de engine of het schema
en hangt aan de Test-poort. Lokaal is het hetzelfde commando: just
walkthrough verify (nu in Node, de Python-variant is weg). De installatie
van wasm-bindgen staat in een gedeeld script voor deze job en e2e.

* ci: job 'Rondleiding speelt terug' en het gedeelde wasm-bindgen-script

Hoort bij de vorige commit; de wijziging aan ci.yml was daar niet in
meegekomen. De job draait bij wijzigingen aan de demo, het demo-corpus,
de engine of het schema en hangt aan de Test-poort; e2e installeert
wasm-bindgen nu via script/install-wasm-bindgen.sh.

* feat(demo): korte ondertitels in plaats van letterlijke spreektaal

Spreektaal maakt lange ondertitels. `walkthrough subtitles` laat een
taalmodel per ondertitel een korte versie schrijven (zonder vulwoorden en
herhalingen, met begrippen en getallen zoals gezegd) in subtitles.yaml,
een lijst van wat gezegd is en wat er in beeld komt, met de hand te
corrigeren. De build toont ze, ook in de uitgeschreven tekst. 125 van de
207 ondertitels van de rondleiding zijn nu korter.

* fix(demo): padcontroles kennen de map voor gegenereerde hoofdstukken

corpus/demo/walkthrough/script/ ontstaat pas als een hoofdstuk met een
gegenereerde stem wordt gemaakt; de docs en de walkthrough-skill noemen
hem al. Beide controles kennen hem nu, met de reden erbij.

* feat(demo): rondleiding pauzeert om de kijker zelf te laten rondkijken

Zegt de presentator "kijk gerust even zelf rond" of "ga je gang", dan
stopt de speler aan het eind van die zin, met een coach mark
(nldd-just-in-time-education) bij de afspeelknop, die dan Verder heet. De
build vindt die zinnen in het nagekeken transcript met een vaste lijst
uitdrukkingen en legt de pauzes door de knippen heen; walkthrough.yaml kan
er een toevoegen of weghalen, of de herkenning per opname uitzetten.

Gaat de kijker tijdens het afspelen zelf klikken, dan wacht de speler met
dezelfde coach mark ("Ga je gang"), en zegt hij desgewenst een korte zin
in de eigen stem (bumpers.wait); bij Verder een tweede (bumpers.resume)
terwijl de demo wordt teruggezet. Die zinnen zijn stukjes uit een opname,
eigen audiobestanden die meegaan in de release.

* fix(demo): rondleiding vindt elementen terug na de update van het ontwerpsysteem

Na 0.8.94 en de nieuwe demowetten misten twee opgenomen klikken in de
graaf. Een verzonnen name (nldd-segmented-2) telt nu net als een verzonnen
id niet mee; een klik op een onderdeel dat het component niet meer rendert
gaat bij een lossere match naar het component zelf; en de hele tekst van
een pagina laat een lossere match vallen. Beide tellen als waarschuwing
in walkthrough verify.

* fix(demo): zorgtoeslag en awb openen in de stand waarin ze verteld worden

De leeftijdstoets in de zorgtoeslag begint dichtgeklapt als 'subject: AGE',
zodat de twee voorwaarden als twee korte blokken te lezen zijn. Bij awb/bezwaar
staan de twee lijsten onder definitions uitgeschreven en beginnen input en
output dicht, als aantal.

De opgenomen rondleiding klapte reference_date met de hand dicht; die klik
vervalt, want de knoop staat nu al dicht.

* fix(demo): escape in een open paneel stopt de presentatie niet meer

Escape sloot het tracepaneel en beeindigde in dezelfde toetsaanslag de
presentatie. In een opname verdwenen daardoor alle dia's daarna. Een open
dialog of popover houdt nu zijn eigen Escape, en tijdens een opname stopt
Escape het dek helemaal niet; stoppen gaat daar met Shift+R.

* feat(demo): rondleiding verdraagt een opname met het dek uit

Een presentator die halverwege het dek uitzet en via de tabbladen verder
loopt, leverde een opname op die niet terugspeelde. Wat er nu kan:

- hoofdstukken achteraf: `chapters:` onder een opname in walkthrough.yaml
  geeft een stuk zonder dia's zijn eigen dia's en hoofdstukken;
  `just walkthrough states` legt de stand van de demo bij elk daarvan vast
  door de rondleiding een keer in echte tijd af te spelen
- `actions:` onder een opname zet handelingen erbij die al gedaan waren
  toen de opname begon
- de speler zoekt een element niet meer op een id dat bij elke run anders
  is (data-run, een zaak-id) of op een toestand van het moment (data-focused)
- een klik op een tabblad dat naast het dek verborgen is, volgt de link
- een opgenomen 'Demo resetten' haalt het dek niet meer weg
- `off: true` bij een pauze werkte niet: YAML leest `off` als false
- versneld naspelen wacht op een knop die nog uitgeschakeld is, en de
  terugspeelcheck speelt een hoofdstuk met een gemiste handeling nog een
  keer op opnametempo voor het telt, zodat een trage machine niet rood geeft
- ondertitels worden in genummerde blokken ingekort, zodat een samengevoegde
  regel niet alles daarna verschuift

Op een klein scherm speelde de rondleiding als video van het opgenomen
venster. Die toont de dia's van de opnamedag en niets waar het dek uit
stond. De video wordt niet meer gemaakt; een klein scherm krijgt uitleg en
een verwijzing naar een groot scherm.

* feat(demo): rondleiding opnieuw opgenomen, met vier vragen en pauzes

De doorloop van 11 oktober in een keer: 20:41, vijftien hoofdstukken. De
laatste zes zijn achteraf afgeleid, omdat het dek vanaf de simulatie uit
stond. Drie pauzes waar de kijker wordt uitgenodigd zelf rond te kijken,
vier vragen met een opgenomen antwoord (open normen, juridisch bindend,
de volgorde van artikelen, of de vertaling klopt) en de tussenzinnen voor
als de kijker zelf gaat klikken. Ondertitels ingekort.

* fix(demo): rondleiding scrolt naar het eind waar de presentator dat deed

Een scroll helemaal naar beneden werd teruggespeeld op het anker in het
midden van het beeld. Bij een lange wettekst is dat de alinea zelf, en waar
die anders afbreekt belandde de kijker halverwege in plaats van bij de
parameters eronder. Een scroll tot het eind (of het begin) landt nu daar.

* feat(demo): rondleiding toont de demo op de breedte van de opname

In een venster smaller dan dat van de opname braken regels anders af en
schoof wat de presentator aanwees uit beeld. De demo naast het dek wordt nu
verkleind tot de opgenomen breedte, met een ondergrens van 70 procent; het
dek houdt zijn grootte.

Een opgenomen Escape sluit het paneel waarin hij gedrukt werd. Een nagemaakte
toetsaanslag zet het sluiten door de browser niet in gang, waardoor de trace
open bleef staan.
2026-10-11 16:06:44 +00:00
.claude feat(demo): opgenomen rondleiding met stem, dia's en veelgestelde vragen (#1654) 2026-10-11 16:06:44 +00:00
.github feat(demo): opgenomen rondleiding met stem, dia's en veelgestelde vragen (#1654) 2026-10-11 16:06:44 +00:00
bdd fix(engine): de bezwaartermijn uit Vw 69 houdt zijn override-herkomst in de receipt 2026-10-01 10:39:05 +02:00
conformance docs: kernpagina's in lijn met de code, plus controle op gherkin-stappen (#1579) 2026-09-25 08:53:23 +00:00
corpus feat(demo): opgenomen rondleiding met stem, dia's en veelgestelde vragen (#1654) 2026-10-11 16:06:44 +00:00
corpus-poc fix(corpus): Awb 4:13 in de Napp-poc lezen als buitengrens, niet als termijn 2026-10-01 10:39:05 +02:00
deploy/nginx fix: security-headers en een csp die er echt is, op elk domein (#1177) 2026-08-07 19:09:11 +02:00
dev refactor(dev): fold dev-frontend into just dev, drop the backend-only stack (#1667) 2026-10-02 11:19:09 +00:00
docs feat(demo): opgenomen rondleiding met stem, dia's en veelgestelde vragen (#1654) 2026-10-11 16:06:44 +00:00
frontend feat(editor): root-pad en repo van de eigen traject-bron aanpasbaar (#1462) 2026-10-10 10:37:26 +00:00
frontend-demo feat(demo): opgenomen rondleiding met stem, dia's en veelgestelde vragen (#1654) 2026-10-11 16:06:44 +00:00
frontend-lawmaking chore(deps): bump the npm-minor-patch group with 3 updates (#1693) 2026-10-10 05:20:59 +00:00
frontend-poc-napp chore(deps): bump the npm-minor-patch group with 3 updates (#1693) 2026-10-10 05:20:59 +00:00
frontend-poc-nieuwkomersbekostiging chore(deps): bump the npm-minor-patch group with 3 updates (#1693) 2026-10-10 05:20:59 +00:00
frontend-poc-portal chore(deps): bump the npm-minor-patch group with 3 updates (#1693) 2026-10-10 05:20:59 +00:00
frontend-poc-terugbetaalregimes chore(deps): bump the npm-minor-patch group with 3 updates (#1693) 2026-10-10 05:20:59 +00:00
LICENSES chore: licentie-informatie per bestand vastleggen met REUSE (#1646) 2026-10-02 15:21:55 +00:00
packages feat(editor): root-pad en repo van de eigen traject-bron aanpasbaar (#1462) 2026-10-10 10:37:26 +00:00
pocs feat(frontend): de OCW-pocs bijgeschaafd, en een uitvoeringslastmodel voor het terugbetalen (#1512) 2026-09-18 11:27:11 +00:00
schema docs: kernpagina's in lijn met de code, plus controle op gherkin-stappen (#1579) 2026-09-25 08:53:23 +00:00
script feat(demo): opgenomen rondleiding met stem, dia's en veelgestelde vragen (#1654) 2026-10-11 16:06:44 +00:00
tests/fixtures/federation feat(schema): v0.5.0 operation set with engine, corpus migration, and WOO (#312) 2026-03-30 21:59:09 +02:00
.dockerignore feat(demo): de regelrecht-demo in de monorepo, met rfc-036 (afwezig en onbekend) en rfc-037 (typecontrole) in de engine (#1358) 2026-09-10 12:53:33 +00:00
.env.example refactor(dev): fold dev-frontend into just dev, drop the backend-only stack (#1667) 2026-10-02 11:19:09 +00:00
.env.poc.example feat(frontend): portaal met proof-of-concepts achter een wachtwoord (#1432) 2026-09-15 15:43:56 +02:00
.env.sso-local.example fix(editor): allow Safari for local SSO dev (#782) 2026-06-10 14:26:11 +02:00
.gitignore feat(demo): opgenomen rondleiding met stem, dia's en veelgestelde vragen (#1654) 2026-10-11 16:06:44 +00:00
.npmrc refactor(frontend): extract shared auth/color-scheme/fetch package (#860) 2026-06-26 14:04:30 +02:00
.pre-commit-config.yaml chore: licentie-informatie per bestand vastleggen met REUSE (#1646) 2026-10-02 15:21:55 +00:00
.yamllint feat(frontend): portaal met proof-of-concepts achter een wachtwoord (#1432) 2026-09-15 15:43:56 +02:00
AGENTS.md fix(docs): designsysteem bij zijn nieuwe adres en naam, landingsdemo met het toetsenbord bereikbaar (#1697) 2026-10-09 15:07:31 +00:00
CLAUDE.md chore(dev): instructies voor agents staan in AGENTS.md (#1541) 2026-09-23 12:03:48 +00:00
CONTRIBUTING.md feat(docs): werkpakketten krijgen een slug, en elke pull request noemt er een (#1485) 2026-09-17 15:07:43 +02:00
corpus-registry.yaml feat(editor): opt-in local test corpus trajecten (#832) 2026-06-18 18:12:12 +02:00
deny.toml feat(editor): notities inchecken via PR (federated schrijfpad) (#652) 2026-05-19 21:27:24 +02:00
docker-compose.dev.yml feat(pipeline): hourly enrich limit with night multiplier (#904) 2026-07-03 16:48:03 +02:00
Justfile feat(demo): opgenomen rondleiding met stem, dia's en veelgestelde vragen (#1654) 2026-10-11 16:06:44 +00:00
LICENSE Add license 2025-11-10 14:14:42 +01:00
package-lock.json chore(deps): vijf npm-advisories uit de lockfiles halen (#1700) 2026-10-10 05:21:01 +00:00
package.json feat(frontend): portaal met proof-of-concepts achter een wachtwoord (#1432) 2026-09-15 15:43:56 +02:00
publiccode.yml chore: waarschuwingen van de parser uit publiccode.yml halen (#1698) 2026-10-09 16:18:54 +00:00
README.md feat: regelwerk als naam voor een regeling in YAML (#1658) 2026-10-05 15:48:58 +00:00
REUSE.toml feat(demo): opgenomen rondleiding met stem, dia's en veelgestelde vragen (#1654) 2026-10-11 16:06:44 +00:00
REVIEW.md chore: opruimwerk uit zes losse branches gebundeld (#1205) 2026-09-24 19:13:15 +00:00
rust-toolchain.toml chore(ci): consolidate pipeline Dockerfiles, align Rust base images, matrix twin CI jobs (#793) 2026-06-15 10:59:32 +02:00

regelrecht

CI License: EUPL-1.2 Rust 2021 Mutation Testing Docs

Machine-readable Dutch law execution. regelrecht takes legal texts, encodes them as structured YAML, and runs them as deterministic decision logic.

What does it do

  • The engine takes a regulation and a set of inputs, evaluates the decision logic, and returns a result with a full explanation trail
  • Laws are tested against real-world scenarios using BDD (Gherkin) tests, many derived from legislative explanatory memoranda. The vocabulary those scenarios speak lives in bdd/grammar.yaml and every engine's step bindings are generated from it
  • A harvester downloads and tracks Dutch legislation from the official BWB repository
  • Regulations can be edited through a web UI with live execution preview

Components

Rust packages

Package Description
packages/engine/ Law execution engine (also compiles to WASM)
packages/law-model/ The Rust representation of the law format, conforming to schema/
packages/harvester/ Downloads Dutch legislation from BWB
packages/pipeline/ PostgreSQL job queue for law processing
packages/admin/ Harvester-admin API; its dashboard UI lives in the editor
packages/editor-api/ Backend API for the law editor
packages/corpus/ Git integration for the regulation corpus
packages/auth/ Shared OIDC/SSO authentication
packages/github/ Shared GitHub REST client
packages/shared/ Shared domain types across crates
packages/arch-extract/ Derives the architecture model from the code (just arch-explore)
packages/tui/ Terminal dashboard (Ratatui)

Frontends and sites

Directory Description
frontend/ Law editor UI (Vue 3 + Vite)
frontend-lawmaking/ Law-making process visualization (Vue 3 + Vite)
packages/frontend-shared/ Shared frontend primitives (auth, colour scheme, API fetch)
docs/ Astro site: landing page + documentation

Data and testing

Directory Description
corpus/regulation/ A working set of ruleworks, some of them synthetic (test_*)
schema/ Versioned JSON schema for the law format (schema/latest points at the current version)
bdd/ The Gherkin vocabulary (grammar.yaml) and the engine-conformance suite
corpus/regulation/**/scenarios/ Law-validation scenarios, next to the law they test
packages/grafana/ Grafana monitoring dashboards (provisioning, not a crate)

The full corpus lives elsewhere

corpus/regulation/ in this repo holds a couple of dozen laws: the ones the tests and BDD scenarios run against, plus synthetic test_* laws for the conformance suite. The harvested body of Dutch legislation is far too large to carry here and lives in its own repository:

MinBZK/regelrecht-corpus — currently some 22,000 YAML files under regulation/nl.

Which branch you want depends on what you are after:

Branch What is on it
development The working branch, and what corpus-registry.yaml points this repo at. Harvested laws land here first
main Only moves when development is merged into it, so it lags
enrich/* Output of the enrichment runs, one branch per enricher (enrich/claude, enrich/opencode)

Short-lived prN and editor/* branches belong to preview deployments and editor sessions; they are not meant to be read directly.

Deployed services

Service URL
Editor https://editor.regelrecht.rijks.app
Landing page https://regelrecht.rijks.app
Documentation https://docs.regelrecht.rijks.app
Law-making https://lawmaking.regelrecht.rijks.app
Harvester admin https://harvester-admin.regelrecht.rijks.app
Grafana https://grafana.regelrecht.rijks.app

The pipeline API and the harvester and enrich workers deploy alongside these but have no web UI of their own.

A PR gets a preview environment once it carries the deploy:preview label; taking the label off, or closing the PR, cleans it up again.

Getting started

Prerequisites: Rust (rustup installs the version pinned in rust-toolchain.toml) and just. The dev stacks below also need Docker and Node.js, and on x86_64 Linux mold.

just check           # everything CI runs (format, lint, build, validate, tests)
just test            # every Rust test in the workspace
just test-no-docker  # the same, minus the container-backed suites
just bdd             # the BDD suites: law scenarios and engine conformance

just test and just check start PostgreSQL containers for the crates that need one, so they want a Docker daemon. Without one, just test-no-docker covers everything else. just bdd runs separately: it executes against the real corpus and is deliberately not part of just test.

Run just dev-setup once. It does three things:

  1. Points every worktree at one shared target dir — a new worktree reuses the already-built dependency graph instead of cold-building ~600 crates.
  2. Puts that target on fast local storage when the repo lives on a slow mount. A Rust build writes tens of thousands of small files; on a 9p/NFS/SMB mount (e.g. a WSL2 or Docker-Desktop dev container where the repo is a Windows drive) that I/O dominates build time — often more than everything else combined. dev-setup detects this and relocates the target to ~/.cache/regelrecht/ (local disk). This is usually the single biggest win.
  3. Installs mold + sccache. mold (wired into packages/.cargo/config.toml) speeds up linking and is a hard requirement for the dev recipes; sccache is installed but left off locally.

sccache is disabled locally because it requires CARGO_INCREMENTAL=0 and so disables incremental compilation, which slows every rebuild after an edit. Enable it only for cold or flag-varying builds:

export RUSTC_WRAPPER=sccache CARGO_INCREMENTAL=0

CI uses both mold and sccache (see .github/workflows/ci.yml).

Running a dev stack

just dev             # editor (7300) + lawmaking (7500), editor-api on 8000, admin API on 8001
just dev editor      # just the editor (editor-api + editor UI + DB)
just dev admin       # just the admin API + DB
just dev lawmaking   # just the lawmaking UI (no backend)
just dev-down        # stop it

just dev starts only what the chosen app needs: no Grafana, Prometheus or workers. The editor runs with real SSO against the central Keycloak, so it needs .env.sso-local (copy .env.sso-local.example).

Vite ports default to 7300/7500 (overridable via EDITOR_PORT / LAWMAKING_PORT). When a native backend can't reach Postgres on localhost (e.g. a WSL2/Docker-Desktop dev container, where Postgres is published on the Docker host), point it at host.docker.internal: for the admin API set DB_HOST=host.docker.internal in .env; for the editor that host comes from DATABASE_URL in .env.sso-local (the .env.sso-local.example already uses host.docker.internal).

See the docs site for full development instructions.

License

EUPL-1.2