Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
.sbdocs [type="radio"]::before {
content: none;
}
/* Vet in Docs-context: style.css geeft strong het halfvette gewicht van het prototype (500). In het
lettertype van Storybook is dat nauwelijks te onderscheiden van gewone tekst. */
.sbdocs strong,
.sbdocs b {
font-weight: 700;
}
/* Code: consistente styling in Docs-context */
.sbdocs code {
background: var(--toepassing-color-background-subtle, #f5f5f5);
Expand Down
49 changes: 29 additions & 20 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,39 @@ MijnOverheid Zakelijk (MOZa) is een HTML/CSS/JS prototype gebouwd met Eleventy e

## Ontwerp-principes (kernregels)

1. **Semantische HTML eerst**; gebruik de juiste elementen (`<button>`, `<nav>`, `<fieldset>`, `<h1>`–`<h6>`). ARIA alleen waar HTML niet volstaat. Gebruik `<dl>`/`<dt>`/`<dd>` voor sleutel-waardeparen (gegevensoverzichten), niet `<table>`.
2. **Toegankelijkheid altijd**; toetsenbordnavigatie, `:focus-visible`, `aria-current`, `aria-disabled` (niet `disabled`). Labels boven invoervelden, niet ernaast (WCAG 1.4.10 Reflow). Test met diverse invoer- (toetsenbord, spraak) en uitvoermethoden (screenreader, braille).
3. **CSS logical properties**; gebruik `inline-size`, `block-size`, `margin-block-start`, `padding-inline` etc. Nooit `width`, `height`, `margin-top`, `padding-left`.
4. **Design tokens**; gebruik altijd `--toepassing-*` variabelen, nooit `--rijkshuisstijl-*` of hardcoded waarden.
5. **Eenvoudigst mogelijke oplossing in de interface-laag**; HTML en CSS waar het kan, JavaScript waar het moet, platform boven framework. Dit geldt voor wat de gebruiker in de browser voor zich krijgt, niet voor de laag eromheen zoals build-scripts, datatransformaties, backend-koppelingen en testopstellingen.
6. **Spacing**; gebruik `> * + *` met margin voor content flow, `gap` met flex/grid voor component-layouts. Nooit beide tegelijk op dezelfde container.
7. **Feature flags**; gebruik `data-feature="Naam"` en `data-feature-type="pagina|functionaliteit"` om elementen togglebaar te maken. Features die standaard uit staan krijgen `data-feature-default="off"`.
1. **Semantische HTML eerst**: gebruik de juiste elementen (`<button>`, `<nav>`, `<fieldset>`, `<h1>`–`<h6>`). ARIA alleen waar HTML niet volstaat. Gebruik `<dl>`/`<dt>`/`<dd>` voor sleutel-waardeparen (gegevensoverzichten), niet `<table>`.
2. **Toegankelijkheid altijd**: toetsenbordnavigatie, `:focus-visible`, `aria-current`, `aria-disabled` (niet `disabled`). Labels boven invoervelden, niet ernaast (WCAG 1.4.10 Reflow). Test met diverse invoer- (toetsenbord, spraak) en uitvoermethoden (screenreader, braille).
3. **CSS logical properties**: gebruik `inline-size`, `block-size`, `margin-block-start`, `padding-inline` etc. Nooit `width`, `height`, `margin-top`, `padding-left`.
4. **Design tokens**: gebruik altijd `--toepassing-*` variabelen, nooit `--rijkshuisstijl-*` of hardcoded waarden.
5. **Eenvoudigst mogelijke oplossing in de interface-laag**: HTML en CSS waar het kan, JavaScript waar het moet, platform boven framework. Dit geldt voor wat de gebruiker in de browser voor zich krijgt, niet voor de laag eromheen zoals build-scripts, datatransformaties, backend-koppelingen en testopstellingen.
6. **Spacing**: gebruik `> * + *` met margin voor content flow, `gap` met flex/grid voor component-layouts. Nooit beide tegelijk op dezelfde container.
7. **Feature flags**: gebruik `data-feature="Naam"` en `data-feature-type="pagina|functionaliteit"` om elementen togglebaar te maken. Features die standaard uit staan krijgen `data-feature-default="off"`.

## Schrijfwijzer (kernregels)

### Aanspreking en toon

- Spreek de gebruiker aan met **"u"** en **"uw"**, nooit "je" of "jij"
- Genderneutraal: gebruik "die" of "diegene" als verwijswoord, niet "hij", "zij" of "hij/zij"
- Formeel maar toegankelijk, B1-taalniveau
- Actief boven passief: "Bekijk uw gegevens" niet "Uw gegevens kunnen bekeken worden"
- Spreek de gebruiker aan met **"u"** en **"uw"**, nooit "je" of "jij"; bij een persoonlijke begroeting mag de voornaam ("Welkom Robin Vogel")
- Genderneutraal: "de ondernemer" of "u" in plaats van "hij", "zij" of "hij/zij"; "die" of "diegene" als verwijswoord
- Formeel maar toegankelijk, B1-taalniveau, geen jargon zonder uitleg
- Actief boven passief, noem wie iets doet: "De RVO heeft uw aanvraag ontvangen" niet "Uw aanvraag is ontvangen"
- Direct en praktisch: zeg wat de gebruiker kan doen, niet wat het systeem doet
- Stimulerend, niet alarmerend: bij lege staten en fouten zeggen wat de gebruiker kan doen, niet alleen wat er misgaat
- Zinnen van 8 tot 16 woorden, hoogstens één bijzin; splits lange zinnen
- Veronderstel geen beperking: "Dit is niet zichtbaar" niet "U kunt dit niet zien"

### Terminologie

- Bewaar (niet Opslaan, Favoriet)
- Niet relevant (niet Verbergen, Verwijderen)
- Deel (niet Verstuur, Doorsturen)
- Berichten / Berichtenbox (niet Post, E-mail, Inbox)
- Bedrijfsgegevens (niet Gegevens, Profiel)
- Lopende zaken (niet Taken, Dossiers)
- Bewaar (niet Opslaan, Favoriet, Bladwijzer): een item in de eigen verzameling zetten om later terug te zien (subsidie, regeling, gesprek met de assistent)
- Niet relevant voor mij (knop; niet Verbergen, Verwijderen, Niet tonen)
- Deel (niet Doorsturen, Verzend)
- Berichten / Berichtenbox (niet Post, E-mail, Inbox; uitzondering: de tab “Inbox” in de Berichtenbox blijft zo heten)
- Bedrijfsgegevens (niet Gegevens, Registratie, Profiel)
- Lopende zaken (niet Taken, Aanvragen, Dossiers)
- Actualiteiten (niet Nieuws, Updates, Feed)
- Opslaan (formulieren), Annuleren (formulieren afbreken)
- Opslaan (gewijzigde gegevens vastleggen, zoals contactvoorkeuren; niet Bewaar, Verstuur, Bevestig)
- Verstuur (formulier naar een organisatie sturen, zoals een aanvraag of melding; niet Bewaar, Opslaan, Bevestig, Deel)
- Annuleer (formulier afbreken; niet Terug, Sluiten, Stoppen)

### Notatie

Expand All @@ -52,13 +58,16 @@ MijnOverheid Zakelijk (MOZa) is een HTML/CSS/JS prototype gebouwd met Eleventy e
- Scheiding in lopende tekst: standaard de komma. Bevat de zin al komma’s waardoor die onoverzichtelijk wordt, gebruik dan een puntkomma (voor volledige, nauw samenhangende zinnen of komma-rijke opsommingen). De gedachtestreep (—) alleen als uiterste middel, voor extra nadruk op een ingevoegd of slot-zinsdeel
- Typografische aanhalingstekens in lopende tekst: “dubbel” en ‘enkel’, ook in samentrekkingen (mkb’er, zzp’er, komma’s)
- Rechte quotes alleen in code en HTML-attributen
- Kopteksten als zelfstandige naamwoorden, geen punt aan het einde
- Nummers: telefoon met spaties (06 12 34 56 78), KVK-nummer zonder punten of spaties, btw-nummer aaneen met landcode (NL00012345678), bedragen met euroteken en punt als duizendtalscheider (€385.000)
- Afkortingen: bij de eerste vermelding de volledige term met de afkorting tussen haakjes; mkb’er, zzp’er met kleine letters en typografische apostrof; KVK en KVK-nummer in hoofdletters, btw en btw-nummer in kleine letters (zoals de KVK en de Belastingdienst het zelf schrijven)
- Kopteksten als zelfstandige naamwoorden, geen punt aan het einde. Uitzonderingen: een H1 mag een begroeting zijn, FAQ-koppen zijn een vraag

### Microcopy

- Knopteksten kort en werkwoord-gericht: "Opslaan", "Annuleren", "Inloggen"
- Knopteksten kort en werkwoord-gericht: "Opslaan", "Verstuur", "Annuleer", "Inloggen"
- Lege staten: benoem wat er nog niet is én geef een suggestie wat te doen
- Foutmeldingen: constructief en handelingsgericht, benoem wat nodig is
- Linkteksten beschrijvend: zeg waar de link heen gaat of wat die doet, nooit "klik hier", "meer info", "lees meer" of een kale URL (niet iedereen klikt; schermlezers lezen links los voor). Dezelfde tekst gaat naar dezelfde plek. De zichtbare tekst staat vooraan in de toegankelijke naam, zodat spraakbediening werkt (WCAG 2.4.4, 2.5.3)

## Technische conventies

Expand Down
9 changes: 4 additions & 5 deletions assets/javascript/berichtenbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@ import { ketenBron } from "./berichtenbox/keten-bron.js";

// Voorgoed verwijderen kan niet teruggedraaid worden, dus het gaat niet op één klik. Zelfde
// inline-paneel als bij verplaatsen: het hoort bij de knop waarop geklikt is, en niet bij de
// pagina als geheel. Sluit met Escape, met Annuleren, of door buiten het paneel te klikken.
// pagina als geheel. Sluit met Escape, met Annuleer, of door buiten het paneel te klikken.
let actiefVoorgoedPaneel = null;
let actieveVoorgoedKnop = null;

Expand All @@ -900,7 +900,7 @@ import { ketenBron } from "./berichtenbox/keten-bron.js";
if (knop) knop.setAttribute("aria-expanded", "false");
actiefVoorgoedPaneel = null;
actieveVoorgoedKnop = null;
// Na Escape of Annuleren hoort de focus terug bij de knop; anders valt hij terug naar het
// Na Escape of Annuleer hoort de focus terug bij de knop; anders valt hij terug naar het
// begin van de pagina en is de bezoeker zijn plek kwijt.
if (focusTerug && knop) knop.focus();
}
Expand Down Expand Up @@ -946,7 +946,7 @@ import { ketenBron } from "./berichtenbox/keten-bron.js";
const annuleer = document.createElement("button");
annuleer.type = "button";
annuleer.className = "secondary";
annuleer.textContent = "Annuleren";
annuleer.textContent = "Annuleer";

bevestig.addEventListener("click", () => {
const voorVoorgoed = state.voorgoedVerwijderd[berichtId];
Expand Down Expand Up @@ -1504,8 +1504,7 @@ import { ketenBron } from "./berichtenbox/keten-bron.js";
// Hier inline, want dit icoon komt pas in beeld nadat JavaScript weet waar het bericht staat,
// en de icoon-shortcode van Eleventy draait alleen bij de build. `aria-hidden` omdat de knop
// zijn naam uit zijn tekst haalt.
const ICOON_BERICHTENBOX =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M1 4v2.1l9.4 5.7c1 .6 2.2.6 3.1 0L23 6.1V4H1z" /><path fill="currentColor" d="M10.4 14 1 8.3V19h22V8.3L13.6 14c-1 .6-2.2.6-3.2 0z" /></svg>';
const ICOON_BERICHTENBOX = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M1 4v2.1l9.4 5.7c1 .6 2.2.6 3.1 0L23 6.1V4H1z" /><path fill="currentColor" d="M10.4 14 1 8.3V19h22V8.3L13.6 14c-1 .6-2.2.6-3.2 0z" /></svg>';

// Alleen het icoon vervangen, de tekst en de visually-hidden span laten staan.
function zetKnopIcoon(btn, svg) {
Expand Down
14 changes: 7 additions & 7 deletions assets/javascript/digitale-assistent-demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
(function () {
"use strict";

// Dezelfde KvK-nummers als de allowlist van de backend (TEST_KVK_NUMMERS).
// Dezelfde KVK-nummers als de allowlist van de backend (TEST_KVK_NUMMERS).
// Een persona daarbuiten hoort ook in de demo "log eerst in" te krijgen —
// als gewoon antwoord, niet als foutmelding.
var KVK_TOEGESTAAN = ["85234567", "62345681", "56789012"];
Expand Down Expand Up @@ -84,7 +84,7 @@
// verwijzen. Zonder `url` toont de chat de naam zonder link.
var BRONNEN_WALLET = [{ label: "Business Wallet", titel: "Energieverbruik-attestatie, afgegeven door uw netbeheerder" }];

var BRONNEN_KVK = [{ label: "KvK Handelsregister", titel: "Uittreksel onderneming: rechtsvorm, SBI-code en vestiging", url: "https://www.kvk.nl/handelsregister/" }];
var BRONNEN_KVK = [{ label: "KVK Handelsregister", titel: "Uittreksel onderneming: rechtsvorm, SBI-code en vestiging", url: "https://www.kvk.nl/handelsregister/" }];

function stap(wacht, event, data) {
return { wacht: wacht, event: event, data: data };
Expand Down Expand Up @@ -238,10 +238,10 @@
bedrijf = bedrijf || {};
var sbi = (bedrijf.sbi && bedrijf.sbi[0]) || null;
// Alleen regels tonen die de persona ook echt heeft: een uittreksel met
// lege velden zou de indruk wekken dat er gegevens ontbreken bij de KvK.
// lege velden zou de indruk wekken dat er gegevens ontbreken bij de KVK.
var regels = [
["Naam", bedrijf.handelsnaam],
["KvK-nummer", bedrijf.kvkNummer],
["KVK-nummer", bedrijf.kvkNummer],
["Rechtsvorm", bedrijf.rechtsvorm],
["SBI-code", sbi ? sbi.code + " – " + sbi.omschrijving : ""],
["Vestiging", bedrijf.vestigingsadresVolledig || bedrijf.vestigingsadres],
Expand All @@ -257,14 +257,14 @@

return [
[
antwoord(900, "Uw bedrijfsgegevens staan in het Handelsregister van de KvK. Ik kan ze voor u ophalen, maar doe dat pas nadat u toestemming geeft.", {
antwoord(900, "Uw bedrijfsgegevens staan in het Handelsregister van de KVK. Ik kan ze voor u ophalen, maar doe dat pas nadat u toestemming geeft.", {
toestemming_nodig: {
bron: "het KvK Handelsregister",
bron: "het KVK Handelsregister",
omschrijving: "De assistent wil uw naam, rechtsvorm, SBI-code en vestigingsadres ophalen uit het Handelsregister. Er wordt niets opgehaald voordat u akkoord geeft.",
},
}),
],
[stap(700, "status", { message: "kvk__zoek_onderneming" }), antwoord(1100, "Dit staat er over uw onderneming in het Handelsregister:\n\n" + regels + "\n\nKloppen deze gegevens niet, dan wijzigt u ze bij de KvK.", { bronnen: BRONNEN_KVK })],
[stap(700, "status", { message: "kvk__zoek_onderneming" }), antwoord(1100, "Dit staat er over uw onderneming in het Handelsregister:\n\n" + regels + "\n\nKloppen deze gegevens niet, dan wijzigt u ze bij de KVK.", { bronnen: BRONNEN_KVK })],
[antwoord(900, "Wilt u dat ik nog iets anders opzoek over uw onderneming?")],
];
}
Expand Down
16 changes: 8 additions & 8 deletions assets/javascript/digitale-assistent.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* De backend leeft in een eigen repo: github.com/MinBZK/moza-poc-digitale-assistent
* Bewaart per LLM/transport/persona-combinatie een sessie-id en gespreksgeschiedenis
* zodat wisselen niet leidt tot verlies.
* De bedrijfsidentiteit gaat als KvK-nummer van de actieve persona mee in de
* De bedrijfsidentiteit gaat als KVK-nummer van de actieve persona mee in de
* X-Test-User-header; de backend toetst dat aan zijn allowlist (TEST_KVK_NUMMERS)
* en injecteert het server-side bij elke bronaanroep. Staat het nummer daar niet
* in, dan antwoordt de assistent "log eerst in".
Expand Down Expand Up @@ -252,7 +252,7 @@
};

var DATA_SOURCE_LABELS = {
kvk: "KvK Handelsregister",
kvk: "KVK Handelsregister",
koop: "KOOP Regelingenbank",
netbeheerder: "Business Wallet",
};
Expand All @@ -273,12 +273,12 @@
// De Business Wallet is in dit prototype een mock: er is geen pagina om naar
// te verwijzen, dus die bron blijft zonder link.
{ key: "netbeheerder", label: "Business Wallet", url: "", uitleg: "Levert uw energieverbruik, afgegeven door uw netbeheerder." },
{ key: "kvk", label: "KvK Handelsregister", url: "https://www.kvk.nl/handelsregister/", uitleg: "Levert de gegevens van uw onderneming." },
{ key: "kvk", label: "KVK Handelsregister", url: "https://www.kvk.nl/handelsregister/", uitleg: "Levert de gegevens van uw onderneming." },
{ key: "koop", label: "KOOP Regelingenbank", url: "https://wetten.overheid.nl/", uitleg: "Levert de officiële wetteksten." },
];

// Naam van een bron uit een antwoord terugbrengen tot de URL uit STATUS_ITEMS.
// De assistent schrijft niet altijd de volledige naam ("KvK" of "KvK
// De assistent schrijft niet altijd de volledige naam ("KVK" of "KVK
// Handelsregister"), dus we vergelijken beide kanten op.
function bronURL(label) {
var naam = String(label == null ? "" : label)
Expand Down Expand Up @@ -334,7 +334,7 @@
});
}

// KvK-nummer van de actieve persona; de backend toetst dit aan zijn allowlist
// KVK-nummer van de actieve persona; de backend toetst dit aan zijn allowlist
// (env TEST_KVK_NUMMERS daar) en injecteert het bij elke bronaanroep. Het
// Flags-paneel kan een nummer forceren, handig om een nummer buiten de
// allowlist te testen. Geen persona of geen nummer = lege header; de backend
Expand Down Expand Up @@ -509,7 +509,7 @@
// Eén bronvermelding uit tekst omzetten naar { label, titel, url }. Accepteert
// wat de assistent in de praktijk schrijft:
// RegelRecht (art. 5.15 Besluit activiteiten leefomgeving)
// KvK Handelsregister: uittreksel onderneming
// KVK Handelsregister: uittreksel onderneming
// [RVO: informatieplicht](https://www.rvo.nl/...)
function maakBron(waarde) {
var tekst = String(waarde == null ? "" : waarde)
Expand Down Expand Up @@ -1322,7 +1322,7 @@
}

window.addEventListener("setting-changed", function (e) {
// Een ander KvK-nummer is een andere identiteit. Dat zit niet in de combo-sleutel
// Een ander KVK-nummer is een andere identiteit. Dat zit niet in de combo-sleutel
// (die zou dan bij elke toetsaanslag in het veld wisselen), dus vergeten we hier
// het session_id: het volgende bericht start een schoon gesprek.
if (e.detail && e.detail.key === "test-user-kvk") {
Expand Down Expand Up @@ -1853,7 +1853,7 @@
var TEST_ANTWOORD = "Ja, die geldt voor uw bedrijf. U gebruikt per jaar meer dan 50.000 kWh elektriciteit of 25.000 m³ aardgas, en dan bent u verplicht energie te besparen én te rapporteren welke maatregelen u heeft genomen.\n\n" + "Voor uw branche staan 5 erkende maatregelen op de lijst. U rapporteert uiterlijk 1 december 2026 bij de RVO.\n\n" + "Controleer dit bij twijfel bij uw omgevingsdienst: die houdt toezicht op deze plicht.";
var TEST_BRONNEN = [
{
label: "KvK Handelsregister",
label: "KVK Handelsregister",
titel: "Bedrijfsgegevens: SBI-code, rechtsvorm en vestiging",
url: "https://www.kvk.nl/handelsregister/",
},
Expand Down
4 changes: 2 additions & 2 deletions assets/javascript/feature-flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,13 +347,13 @@ function buildTogglePanel() {
});
}

// API Key velden, plus het KvK-nummer dat de assistent als bedrijfsidentiteit
// API Key velden, plus het KVK-nummer dat de assistent als bedrijfsidentiteit
// meestuurt: ingevuld wint dat van het nummer van de actieve persona. Geen
// geheim (het staat al in personas.json), dus een gewoon tekstveld.
[
{ key: "vlam-api-key", label: "VLAM API Key", type: "password", placeholder: "sk-..." },
{ key: "claude-api-key", label: "Claude API Key", type: "password", placeholder: "sk-..." },
{ key: "test-user-kvk", label: "KvK-nummer assistent", type: "text", placeholder: "85234567" },
{ key: "test-user-kvk", label: "KVK-nummer assistent", type: "text", placeholder: "85234567" },
].forEach(({ key, label: labelText, type, placeholder }) => {
const field = document.createElement("div");
field.className = "settings-field";
Expand Down
2 changes: 1 addition & 1 deletion moza/bedrijfsgegevens.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ <h2>Algemeen</h2>
<dd data-profiel="rsinNummer">62345681</dd>
</div>
<div>
<dt>BTW-identificatienummer</dt>
<dt>Btw-identificatienummer</dt>
<dd data-profiel="btwNummer">NL00062345681</dd>
</div>
<div>
Expand Down
Loading
Loading