From 9c3c1ba390a9262a4f1658d4472e0d73f51a3737 Mon Sep 17 00:00:00 2001 From: "mwesterweel@hotmail.com" Date: Mon, 11 May 2026 21:58:54 +0200 Subject: [PATCH 001/330] academy: Dutch tutorial for publishing a Nextcloud app to the App Store Walks an app maintainer from CSR generation through release automation in one post: OpenSSL key/CSR, PR on app-certificate-requests, occ integrity:sign-app, App Store registration, release upload, and a GitHub Actions workflow that publishes on every tag. Includes troubleshooting and the lost-key recovery procedure. Also adds secret patterns (.env, *.pem, *.key, *_rsa, *.crt) to .gitignore so a follower of the tutorial can't accidentally commit their signing key from the repo root. Closes ConductionNL/.github#47. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 8 + .../index.mdx | 403 ++++++++++++++++++ 2 files changed, 411 insertions(+) create mode 100644 academy/2026-05-11-nextcloud-app-store-publiceren/index.mdx diff --git a/.gitignore b/.gitignore index 0c5615bf3..91ca6031e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,13 @@ node_modules/ # Misc .DS_Store *.log +.env +.env.* .env.local .env.*.local + +# Secrets / credentials — never commit +*.pem +*.key +*_rsa +*.crt diff --git a/academy/2026-05-11-nextcloud-app-store-publiceren/index.mdx b/academy/2026-05-11-nextcloud-app-store-publiceren/index.mdx new file mode 100644 index 000000000..0eaebe450 --- /dev/null +++ b/academy/2026-05-11-nextcloud-app-store-publiceren/index.mdx @@ -0,0 +1,403 @@ +--- +slug: nextcloud-app-store-publiceren +title: Een Nextcloud-app publiceren in de App Store +contentType: tutorial +authors: [conduction] +date: 2026-05-11 +summary: Van nul naar gepubliceerde app in de Nextcloud App Store. CSR aanvragen, signen, registreren, releasen, en de hele release-pijplijn automatiseren via GitHub Actions. +tags: [Nextcloud, App Store, Publishing, OpenSSL, GitHub Actions, Code signing] +durationMinutes: 60 +--- + +import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, ContactCta} from '@conduction/docusaurus-preset/components'; + +Een Nextcloud-app in de [App Store](https://apps.nextcloud.com) zetten is geen knop, maar een keten. Je hebt een certificaat nodig om je code te signen, dat certificaat krijg je via een pull request op een Nextcloud-repo, en pas met dat certificaat mag je je app registreren en releases uploaden. Het officiële handboek beschrijft elke stap, maar verspreid over vier pagina's. Deze tutorial loopt het in één keer door, van licentiekeuze tot een GitHub Action die elke release automatisch publiceert. + +{/* truncate */} + + + Een CSR (certificate signing request) genereren met OpenSSL en indienen via PR op nextcloud/app-certificate-requests. + Je app signen met occ integrity:sign-app zodat de Nextcloud-integriteitscheck hem accepteert. + De app registreren op apps.nextcloud.com en de eerste release uploaden. + Releases automatiseren via GitHub Actions, zodat een git tag de App Store update. + Een procedure voor het scenario waarin je je private key kwijtraakt. + + + + Een werkende Nextcloud-app op je laptop. Geen app? Begin bij Build a Nextcloud app on the Conduction stack — Part 1: Scaffold. + Een lokale Nextcloud waarop je de app kunt installeren en signen. Zie Nextcloud lokaal draaien met Docker. + OpenSSL (standaard op macOS en Linux, op Windows via Git Bash of WSL). + Een GitHub-account met gh CLI geconfigureerd. + Toegang tot de occ-CLI van je lokale Nextcloud (binnen Docker via docker compose exec --user www-data nextcloud php occ ...). + + +In de voorbeelden noemen we de app `myapp`. Vervang dat overal door je eigen app ID. We slaan certificaten op in `~/.nextcloud/certificates/`, dezelfde plek die de officiële release-automation gebruikt. + +## Stap 1: Voorbereiding + +Drie dingen op orde brengen voor je iets indient. + +### 1a. Licentie + +De Nextcloud App Store accepteert alleen apps onder een [OSI-goedgekeurde](https://opensource.org/licenses) open-sourcelicentie. In de praktijk is dat **AGPL-3.0-or-later** voor het overgrote deel van het ecosysteem, inclusief alle Conduction-apps. Leg de licentie vast in twee plaatsen: + +- Een `LICENSE`-bestand in de repo-root met de volledige tekst. +- De ``-tag in `appinfo/info.xml` (let op de Britse spelling, dat is geen typo). + +```xml title="appinfo/info.xml" +agpl +``` + +### 1b. App ID-regels + +Het `` in `appinfo/info.xml` is je identifier in elk Nextcloud-systeem en in de App Store. Een paar harde regels: + +- Lowercase, alleen `a-z`, `0-9` en underscores. Geen streepjes, geen hoofdletters. +- Moet overeenkomen met de naam van de directory in `custom_apps/` en met de PHP-namespace prefix (`OCA\Myapp`). +- Eenmaal in de App Store geregistreerd kun je hem niet hernoemen; alleen overdragen aan een andere eigenaar. + +Kies dus iets dat je drie jaar later nog logisch vindt. `myapp` is alleen oké voor de tutorial. + +### 1c. Code checker + +Voor je iets richting App Store stuurt, draai de officiële code checker tegen je app. Die controleert dingen die in CI-time stuk zouden gaan: ongeldige `info.xml`, ontbrekende velden, verboden gebruik van private API's. + +```bash +docker compose exec --user www-data nextcloud php occ app:check-code myapp +``` + +Errors fix je voor je verder gaat. Warnings mag je negeren, maar reviewers van de App Store kunnen je erop wijzen — beter nu opruimen. + +## Stap 2: Account op apps.nextcloud.com + +Ga naar [apps.nextcloud.com](https://apps.nextcloud.com), klik **Sign up**, en gebruik bij voorkeur het GitHub-account waaronder de app-repo staat. Bevestig het e-mailadres dat je opgeeft — alle App Store-meldingen (release goedgekeurd, kritieke review-comments) gaan daarheen. + +Twee dingen die je meteen wilt instellen: + +- **API-token** voor de release-automation. Te vinden op [apps.nextcloud.com/account/token](https://apps.nextcloud.com/account/token). We hebben hem in stap 8 nodig. +- **Public profile**, met op zijn minst je naam en organisatie. Dat is wat eindgebruikers zien bij de app. + +## Stap 3: CSR genereren met OpenSSL + +De CSR (certificate signing request) is een onderdeel van een sleutelpaar dat *jij* genereert en waarvan je het publieke deel naar Nextcloud stuurt. Nextcloud signeert dat tot een certificaat (`.crt`) waarmee je je app mag signen. De private key (`.key`) blijft bij jou — als die uitlekt, kan iemand anders fake releases van jouw app uploaden. + +Maak een vaste plek voor certificaten en genereer het paar: + +```bash +mkdir -p ~/.nextcloud/certificates +cd ~/.nextcloud/certificates + +openssl req -nodes -newkey rsa:4096 \ + -keyout myapp.key \ + -out myapp.csr \ + -subj "/CN=myapp" +``` + +Drie files horen hier eindelijk te ontstaan, maar nu pas twee: + +| File | Wat | Geheim? | +| --- | --- | --- | +| `myapp.key` | Private key, RSA 4096 | Ja. Niet committen, niet delen. | +| `myapp.csr` | Certificate signing request | Nee, dit gaat zo naar Nextcloud. | +| `myapp.crt` | Signed certificate (komt na stap 4) | Nee, publiek. | + +Beveilig de key meteen: + +```bash +chmod 600 ~/.nextcloud/certificates/myapp.key +``` + +Maak ook nu al een back-up van `myapp.key` op een aparte, vertrouwde plek (password manager, vault, encrypted USB). Als die file weg is, kun je niet meer signen — zie de [verloren-key-procedure](#verloren-key-scenario) onderaan. + +## Stap 4: CSR indienen via PR + +Het signen door Nextcloud gebeurt via een merge op de publieke repo [`nextcloud/app-certificate-requests`](https://github.com/nextcloud/app-certificate-requests). De structuur is strikt: per app één map met daarin één `.csr` met exact dezelfde naam. + +``` +nextcloud/app-certificate-requests/ +└── myapp/ + └── myapp.csr +``` + +Doe het via de CLI of via de GitHub-webinterface — beide werken. Met `gh`: + +```bash +gh repo fork nextcloud/app-certificate-requests --clone +cd app-certificate-requests +git checkout -b add-myapp + +mkdir myapp +cp ~/.nextcloud/certificates/myapp.csr myapp/myapp.csr + +git add myapp/myapp.csr +git commit -m "Add CSR for myapp" +git push -u origin add-myapp + +gh pr create \ + --title "Add CSR for myapp" \ + --body "Source: https://github.com/your-org/myapp" +``` + +De PR-body hoeft niets bijzonders te bevatten. Een link naar de app-source is geen verplichting, wel een courtesy — het scheelt reviewers een zoekslag. + +### Wachten en certificate ophalen + +Een Nextcloud-maintainer merged de PR meestal binnen een paar werkdagen. Daarna staat het bestand `myapp/myapp.crt` in dezelfde repo, in dezelfde map als jouw CSR. Trek het op: + +```bash +curl -o ~/.nextcloud/certificates/myapp.crt \ + https://raw.githubusercontent.com/nextcloud/app-certificate-requests/master/myapp/myapp.crt + +chmod 644 ~/.nextcloud/certificates/myapp.crt +``` + +Verifieer dat het certificaat bij jouw key hoort — beide MD5-hashes moeten gelijk zijn: + +```bash +openssl rsa -modulus -noout -in ~/.nextcloud/certificates/myapp.key | openssl md5 +openssl x509 -modulus -noout -in ~/.nextcloud/certificates/myapp.crt | openssl md5 +``` + +Als de hashes verschillen, hoort dit certificaat bij een andere key — niet aan jezelf je app proberen te signen, dat faalt sowieso. Open een issue op de cert-repo. + +## Stap 5: App signen met `occ integrity:sign-app` + +`occ integrity:sign-app` leest je app-directory, berekent SHA-512 hashes per file, signeert het geheel met je private key, en schrijft het resultaat naar `appinfo/signature.json` in de app. Zonder die file weigert de App Store je upload. + +Vanuit de root van je lokale Nextcloud (of via `docker compose exec` als je in Docker draait): + +```bash +docker compose exec --user www-data nextcloud php occ integrity:sign-app \ + --path=/var/www/html/custom_apps/myapp \ + --privateKey=/path/in/container/to/myapp.key \ + --certificate=/path/in/container/to/myapp.crt +``` + +Drie flags: + +- `--path`: de app-directory **inside** de Nextcloud-installatie (`apps/`, `custom_apps/`, of een aangemounte path). +- `--privateKey`: pad naar je `.key`-file. Als je in Docker werkt, moet die file inside de container zichtbaar zijn — een bind-mount van `~/.nextcloud/certificates` naar `/certs` in `docker-compose.yml` is de simpelste truc. +- `--certificate`: pad naar je `.crt`-file, idem. + +Na een succesvolle run staat er een `appinfo/signature.json` in je app. Die file moet je **committen** naar de app-repo — hij hoort bij de release. + +Voor lokale ontwikkeling kun je apps draaien zonder signature.json (Nextcloud klaagt in de logs maar boot ze wel). Voor publicatie is hij verplicht. + +## Stap 6: App registreren op de App Store + +Eénmalig per app: meld hem aan op de App Store. Daarvoor heb je twee dingen nodig: het complete certificaat en een **signature van de app-id** met je private key. + +Genereer de signature van de app-id: + +```bash +echo -n "myapp" | openssl dgst -sha512 \ + -sign ~/.nextcloud/certificates/myapp.key \ + | openssl base64 +``` + +De output is een lange base64-string, allemaal op één regel — kopieer 'm in z'n geheel. + +Ga naar [apps.nextcloud.com/developer/apps/new](https://apps.nextcloud.com/developer/apps/new) en vul in: + +| Veld | Waarde | +| --- | --- | +| **Certificate** | De volledige inhoud van `myapp.crt`, inclusief de `-----BEGIN CERTIFICATE-----` en `-----END CERTIFICATE-----` regels. | +| **Signature** | De base64-string die je hierboven genereerde. | + +Klik **Register**. De backend verifieert: (a) dat het certificaat door Nextcloud is gesigned, (b) dat de signature klopt met de app-id en de public key uit het certificaat. Vanaf nu ben jij eigenaar van de app-id `myapp` op de App Store. + +Een app overdragen aan iemand anders kan via [apps.nextcloud.com/account/transfer-apps](https://apps.nextcloud.com/account/transfer-apps) — je ontgrendelt 'm daar, de nieuwe eigenaar registreert opnieuw met hun eigen certificaat en signature. + +## Stap 7: Release uploaden + +Een release in de App Store is geen file-upload — het is een URL plus een signature. De App Store haalt je tarball zelf op vanaf een publieke URL (typisch een GitHub Release asset), en valideert de signature tegen jouw certificate. + +### 7a. Tarball bouwen + +Bouw je app zoals voor productie. Voor de meeste Conduction-apps: + +```bash +cd /path/to/your/myapp +make appstore +``` + +Het `Makefile` uit `nextcloud-app-template` schrijft `build/artifacts/appstore/myapp.tar.gz` met daarin de app-directory, gestripped van dev-files (`node_modules/`, `tests/`, `.git/`, et cetera). + +### 7b. Tarball publiceren + +Upload de tarball naar een publiek bereikbare HTTPS-URL. GitHub Releases is de standaard: + +```bash +gh release create v1.0.0 \ + build/artifacts/appstore/myapp.tar.gz \ + --title "v1.0.0" \ + --notes "First release." +``` + +De URL die je nodig hebt heeft de vorm: + +``` +https://github.com/your-org/myapp/releases/download/v1.0.0/myapp.tar.gz +``` + +### 7c. Signature van de tarball + +```bash +openssl dgst -sha512 \ + -sign ~/.nextcloud/certificates/myapp.key \ + build/artifacts/appstore/myapp.tar.gz \ + | openssl base64 +``` + +Weer een base64-string op één regel. + +### 7d. Release indienen + +Via de webinterface ([apps.nextcloud.com/developer/apps/releases/new](https://apps.nextcloud.com/developer/apps/releases/new)): + +| Veld | Waarde | +| --- | --- | +| **Download** | De GitHub-release-URL. | +| **Signature** | De base64-signature uit 7c. | +| **Nightly** | Aan voor pre-releases, uit voor stable. | + +Of via de REST API in één commando: + +```bash +curl -X POST \ + -u "your-username:your-password" \ + -H "Content-Type: application/json" \ + -d '{ + "download": "https://github.com/your-org/myapp/releases/download/v1.0.0/myapp.tar.gz", + "signature": "" + }' \ + https://apps.nextcloud.com/api/v1/apps/releases +``` + +De App Store fetcht je tarball, controleert de signature, valideert `info.xml`, en — als alles klopt — publiceert hem. Vanaf dat moment is je app zichtbaar in de **Apps**-pagina van elke Nextcloud-installatie. + +## Stap 8: Release-automatisering via GitHub Actions + +Stap 7 hoef je niet vaker handmatig te doen dan één keer. Daarna laat je een GitHub Action elke `git tag` automatisch tot een release maken én aan de App Store doorgeven. + +### 8a. Repository-secrets instellen + +In je app-repo, **Settings → Secrets and variables → Actions** (of via `gh`): + +```bash +gh secret set APP_PRIVATE_KEY < ~/.nextcloud/certificates/myapp.key +gh secret set APP_PUBLIC_CRT < ~/.nextcloud/certificates/myapp.crt +gh secret set APPSTORE_TOKEN # plak de token uit apps.nextcloud.com/account/token +``` + +Voor repo's onder de `nextcloud`-organisatie eist het Nextcloud-beleid dat deze secrets in een GitHub **environment** staan (typisch `release`), met manual approval. Voor je eigen org volstaan repository-secrets, maar overweeg ook daar een environment — één klik manual approval voorkomt accidental publishes. + +### 8b. Workflow toevoegen + +Plaats het volgende als `.github/workflows/appstore-release.yml`: + +```yaml title=".github/workflows/appstore-release.yml" +name: Publish to App Store +on: + release: + types: [published] + +jobs: + publish: + runs-on: ubuntu-latest + environment: release # verwijder als je geen environment gebruikt + steps: + - uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + + - name: Build appstore tarball + run: make appstore + + - name: Upload tarball to GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload "${{ github.event.release.tag_name }}" \ + build/artifacts/appstore/myapp.tar.gz + + - name: Publish to Nextcloud App Store + uses: R0Wi/nextcloud-appstore-push-action@v1 + with: + app_name: myapp + appstore_token: ${{ secrets.APPSTORE_TOKEN }} + download_url: https://github.com/${{ github.repository }}/releases/download/${{ github.event.release.tag_name }}/myapp.tar.gz + app_private_key: ${{ secrets.APP_PRIVATE_KEY }} + nightly: ${{ github.event.release.prerelease }} +``` + +Wat de workflow doet, in volgorde: + +1. **Checkout** je code op de tag. +2. **Build** een appstore-tarball (`make appstore` uit `nextcloud-app-template`). +3. **Upload** de tarball als asset op de GitHub Release. +4. **Publish**: `R0Wi/nextcloud-appstore-push-action` regenereert de signature met je private key, doet de POST naar `apps.nextcloud.com`, en wacht op de bevestiging. + +### 8c. Eerste run + +```bash +git tag v1.0.1 +git push origin v1.0.1 +gh release create v1.0.1 --notes "Patch release." +``` + +Op `release: published` start de workflow. Slaagt hij, dan staat v1.0.1 binnen een minuut in de App Store. Faalt hij, dan zie je in de Actions-log precies wat er stuk ging — meestal een verlopen certificate of een typo in de download-URL. + +## Probleemoplossing + +**`occ integrity:sign-app` faalt met "Certificate is not valid"** — verificatie van key vs. cert overslaan; draai de twee MD5-commando's uit stap 4. Ongelijke hashes? Dan staan key en cert van verschillende paren bij elkaar; haal opnieuw de juiste `.crt` op. + +**App Store-upload faalt met "Signature is invalid"** — drie verdachten: (1) je hebt de signature met een andere key gegenereerd dan in het certificaat staat; (2) de tarball is veranderd nadat je signed had (een nieuwe build, ander hash); (3) je base64-string heeft een line-break gekregen tijdens kopiëren. Gebruik `tr -d '\n'` om dat laatste uit te sluiten. + +**App Store-upload faalt met "Download URL must be HTTPS"** — controleer dat je geen `http://` URL hebt opgegeven. GitHub Releases is altijd HTTPS, dus dit gebeurt typisch bij een private S3 met een verkeerde URL. + +**`integrity:sign-app` zegt "App not found"** — `--path` moet wijzen naar de app-directory zoals Nextcloud die ziet, niet je host-pad. In Docker is dat doorgaans `/var/www/html/custom_apps/myapp` of `/var/www/html/apps/myapp`. + +**App verschijnt niet in App Store na succesvolle upload** — de App Store fetcht je tarball asynchroon en logt fouten op je release-detailpagina. Check [apps.nextcloud.com/developer/apps](https://apps.nextcloud.com/developer/apps) voor je app en kijk bij de specifieke release. + +**Code checker klaagt over "Private API"** — Nextcloud-server-classes onder `OC\` zijn private; gebruik alleen `OCP\`-interfaces. Refactor de roep, of als het echt niet anders kan, accepteer dat reviewers er commentaar op kunnen geven. + +## Verloren key-scenario + +Raak je `myapp.key` kwijt — uit een onversleutelde back-up gewist, harde schijf overleden, en geen kopie elders — dan kun je niet meer signen voor je app-id. De procedure: + +1. **Nieuwe key + CSR genereren** (stap 3, identieke commando's). +2. **Nieuwe PR** op `nextcloud/app-certificate-requests`. In de PR-body **expliciet vermelden** dat dit een vervanging is voor een verloren key, en bij voorkeur via je App Store-developer-account een [issue op de cert-repo](https://github.com/nextcloud/app-certificate-requests/issues) openen waarin je de eigendom van de app-id staaft (link naar je app op `apps.nextcloud.com`, vanuit het account dat eigenaar is). +3. **Wachten op merge**. De maintainers checken dat de aanvragende GitHub-user dezelfde is als de App Store-eigenaar; mismatch betekent afwijzing. +4. **Nieuwe `.crt` ophalen**, **`APP_PRIVATE_KEY`** en **`APP_PUBLIC_CRT`** secrets in je GitHub-repo bijwerken, en de eerstvolgende release gebruikt de nieuwe key. + +Bestaande releases in de App Store blijven werken — die zijn destijds met de oude key gesigned en de App Store heeft de signature al gevalideerd op upload-moment. + +De moraal: maak nu een back-up van `~/.nextcloud/certificates/myapp.key`. Twee minuten werk, jaren ellende voorkomen. + +## Officiële documentatie + +Deze tutorial is een wegwijzer; bij twijfel zijn de Nextcloud-docs leidend. + +- [App publishing and maintenance](https://docs.nextcloud.com/server/latest/developer_manual/app_publishing_maintenance/index.html) — overzicht +- [Code signing](https://docs.nextcloud.com/server/latest/developer_manual/app_publishing_maintenance/code_signing.html) — CSR, certificate, `occ integrity:sign-app` +- [App Store developer guide](https://nextcloudappstore.readthedocs.io/en/latest/developer.html) — registratie, release-upload, API +- [`nextcloud/app-certificate-requests`](https://github.com/nextcloud/app-certificate-requests) — de CSR-PR-repo +- [Release automation](https://docs.nextcloud.com/server/latest/developer_manual/app_publishing_maintenance/release_automation.html) — de GitHub Actions setup + +## Volgende stap + +Eén release in de App Store is een mijlpaal. Twee zijn een patroon. Plan vooruit: + +- **CHANGELOG.md** in je repo bijhouden, zodat elke `gh release create` direct release-notes meekrijgt. +- **Semver** strikt aanhouden — App Store-gebruikers krijgen ook patch-updates automatisch, dus een breaking change in een patch-release maakt ze terecht boos. +- **`max-version`** in `info.xml` bijhouden bij elke nieuwe Nextcloud-major; anders verdwijn je app uit de Apps-pagina van nieuwere installs. + + From 15b7669f67cd9f2456e02ccf58e2ccbda0bf640a Mon Sep 17 00:00:00 2001 From: "mwesterweel@hotmail.com" Date: Mon, 11 May 2026 23:02:52 +0200 Subject: [PATCH 002/330] =?UTF-8?q?academy:=20'handboek'=20=E2=86=92=20'do?= =?UTF-8?q?cumentatie'=20in=20app-store=20tutorial=20intro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- academy/2026-05-11-nextcloud-app-store-publiceren/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/academy/2026-05-11-nextcloud-app-store-publiceren/index.mdx b/academy/2026-05-11-nextcloud-app-store-publiceren/index.mdx index 0eaebe450..4519b6ebb 100644 --- a/academy/2026-05-11-nextcloud-app-store-publiceren/index.mdx +++ b/academy/2026-05-11-nextcloud-app-store-publiceren/index.mdx @@ -11,7 +11,7 @@ durationMinutes: 60 import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, ContactCta} from '@conduction/docusaurus-preset/components'; -Een Nextcloud-app in de [App Store](https://apps.nextcloud.com) zetten is geen knop, maar een keten. Je hebt een certificaat nodig om je code te signen, dat certificaat krijg je via een pull request op een Nextcloud-repo, en pas met dat certificaat mag je je app registreren en releases uploaden. Het officiële handboek beschrijft elke stap, maar verspreid over vier pagina's. Deze tutorial loopt het in één keer door, van licentiekeuze tot een GitHub Action die elke release automatisch publiceert. +Een Nextcloud-app in de [App Store](https://apps.nextcloud.com) zetten is geen knop, maar een keten. Je hebt een certificaat nodig om je code te signen, dat certificaat krijg je via een pull request op een Nextcloud-repo, en pas met dat certificaat mag je je app registreren en releases uploaden. De officiële documentatie beschrijft elke stap, maar verspreid over vier pagina's. Deze tutorial loopt het in één keer door, van licentiekeuze tot een GitHub Action die elke release automatisch publiceert. {/* truncate */} From 41d5eadc5c6e6a8a9619784a9d1a9a7955ac2e48 Mon Sep 17 00:00:00 2001 From: "mwesterweel@hotmail.com" Date: Mon, 11 May 2026 23:21:56 +0200 Subject: [PATCH 003/330] academy: Dutch decision guide for Nextcloud hosting options Compares the three hosting routes (bare metal / VM, Docker Compose plus AIO, Kubernetes via the official Helm chart) with a decision matrix covering homelab through multi-tenant production. Calls out AIO's hard limitations and the Helm chart's "expert use" warning so readers don't default into a stack they can't maintain. Closes with the six production-readiness items every install hits regardless of route: backups, updates, TLS, object storage, caching, performance tuning. Also re-applies secret patterns (.env, *.pem, *.key, *_rsa, *.crt) to .gitignore on this branch, matching the safety baseline required by the project's commit hooks. Closes ConductionNL/.github#48. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 8 + academy/2026-05-11-nextcloud-hosten/index.mdx | 202 ++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 academy/2026-05-11-nextcloud-hosten/index.mdx diff --git a/.gitignore b/.gitignore index 0c5615bf3..91ca6031e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,13 @@ node_modules/ # Misc .DS_Store *.log +.env +.env.* .env.local .env.*.local + +# Secrets / credentials — never commit +*.pem +*.key +*_rsa +*.crt diff --git a/academy/2026-05-11-nextcloud-hosten/index.mdx b/academy/2026-05-11-nextcloud-hosten/index.mdx new file mode 100644 index 000000000..9171e3d6a --- /dev/null +++ b/academy/2026-05-11-nextcloud-hosten/index.mdx @@ -0,0 +1,202 @@ +--- +slug: nextcloud-hosten +title: Nextcloud hosten — bare metal, Docker of Kubernetes? +contentType: tutorial +authors: [conduction] +date: 2026-05-11 +summary: Drie manieren om Nextcloud te draaien, één onderbouwde keuze. Bare metal voor controle, Docker (of AIO) voor de meeste teams, Kubernetes alleen als je dat al draait. Plus een beslismatrix en de aandachtspunten die elke productie-installatie raakt. +tags: [Nextcloud, Hosting, Docker, Kubernetes, Helm, AIO, Architectuur] +durationMinutes: 20 +--- + +import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, ContactCta} from '@conduction/docusaurus-preset/components'; + +Een Nextcloud opzetten kan op drie manieren, en de "beste" hangt af van wie je bent en wat je al draait. Een homelab op een NUC vraagt iets anders dan een gemeente met tien bestuurslagen en een ISO-audit. Deze tutorial zet de drie hoofdopties naast elkaar, met voor- en nadelen en een beslismatrix om snel te kiezen. Niets vervangt de officiële [Nextcloud admin docs](https://docs.nextcloud.com/server/latest/admin_manual/), maar dit is de korte route naar de juiste hoofdstukken. + +{/* truncate */} + + + De drie hoofdopties voor Nextcloud-hosting onderscheiden: bare metal / VM, containers (Docker + AIO), Kubernetes (Helm). + Per optie weten wanneer je hem kiest, wat hij kost aan complexiteit, en hoe hij schaalt. + Een beslismatrix toepassen die past bij jouw scenario — homelab, team, of multi-tenant productie. + De zes aandachtspunten kennen die elke productie-Nextcloud raakt: backups, updates, TLS, object storage, caching, en performance tuning. + + + + Een idee van je doelgroep: alleen jezelf, een klein team, of een productiedienst voor meerdere klanten. + Basiskennis Linux-server-admin (SSH, een package manager, systemd) helpt, maar is geen harde eis voor optie 2. + Voor optie 3: een werkend Kubernetes-cluster (eigen of beheerd). Geen cluster? Optie 3 is niet voor jou — kies optie 2. + + +Wil je gewoon snel testen op je laptop? Sla deze tutorial over en volg [Nextcloud lokaal draaien met Docker](/academy/nextcloud-lokaal-draaien). Dat is een snel-start, geen productie-keuze. + +## De drie hoofdopties in één oogopslag + +| Optie | Wie het draait | Kostprijs | Schaalbaarheid | +| --- | --- | --- | --- | +| **1. Bare metal / VM** | Jij, met de hand of via een script | Lage software-kosten, hoge ops-tijd | Verticaal (groter VM); horizontaal kan, maar is werk | +| **2. Containers (Docker / AIO)** | Docker op één host, of een handvol hosts | Lage drempel, gemiddelde ops-tijd | Verticaal en bescheiden horizontaal; AIO is single-host | +| **3. Kubernetes (Helm)** | Kubernetes-cluster met persistent storage | Hoge complexiteit, lage marginale ops-tijd | Horizontaal, multi-tenant, multi-region | + +Ieder antwoord is een trade-off. Hieronder per optie wat dat in de praktijk betekent. + +## Optie 1: Bare metal of VM + +De klassieke route: LAMP-stack op een Linux-server, Nextcloud uitpakken in `/var/www/nextcloud`, Apache of Nginx ervoor, Postgres of MariaDB ernaast, Redis voor cache, een cronjob voor `cron.php`. Volledige controle, geen abstractielagen. + +**Wanneer wel:** + +- Je hebt een single-tenant productie-installatie met strenge dataregels (lokale schijf, geen container-runtime in scope van je audit). +- Je beheert al een vloot Linux-servers met je eigen config-management (Ansible, Salt, Puppet) en wil Nextcloud daarin vouwen. +- Je wil tunen op systeemniveau (kernel parameters, opcache-paden, PHP-OPcache tuning) zonder dat een container-runtime ertussen zit. + +**Wanneer niet:** + +- Je werkt alleen en hebt geen zin in package-management voor PHP, Apache, en MariaDB. Optie 2. +- Je wil meerdere Nextclouds isoleren op één host. Containers zijn daar gewoon beter in. + +**Complexiteit:** Hoog initieel (LAMP-stack opzetten, TLS-certificaten, php-fpm tunen). Daarna middelmatig (Nextcloud-major-upgrades vereisen handwerk via `occ upgrade`). + +**Schaalbaarheid:** Een enkel VM met 8 vCPU en 16 GB RAM trekt een paar honderd actieve users. Voor meer wil je een [scale-out-setup](https://docs.nextcloud.com/server/latest/admin_manual/installation/deployment_recommendations.html): meerdere Nextcloud-frontends achter een load balancer, met gedeelde Redis, gedeelde S3-compatibele object storage en een aparte database-host. Dat kán op bare metal, maar dan ben je effectief Kubernetes aan het uitvinden zonder Kubernetes te draaien. + +**Documentatie:** [Manual installation on Linux](https://docs.nextcloud.com/server/latest/admin_manual/installation/source_installation.html), en de [example installation on Ubuntu 24.04](https://docs.nextcloud.com/server/latest/admin_manual/installation/example_ubuntu.html). + +## Optie 2: Containers — Docker Compose of Nextcloud AIO + +Het brede midden. Twee smaken: + +### 2a. Docker Compose + +Eigen `docker-compose.yml`, de officiële [`nextcloud`-image](https://hub.docker.com/_/nextcloud), Postgres en Redis ernaast. Dit is hoe wij Conduction-apps draaien voor de meeste klanten en hoe je [lokaal Nextcloud draait](/academy/nextcloud-lokaal-draaien). Volledige config-controle, je eigen reverse proxy ervoor (Traefik, Caddy, Nginx Proxy Manager), je eigen TLS-routine. + +### 2b. Nextcloud AIO (All-in-One) + +Eén "mastercontainer" die alle andere services beheert: Nextcloud, Postgres, Redis, Apache proxy, Notify Push, plus optioneel Office, Talk, ClamAV, en zo'n dertig andere community-add-ons. Een webinterface op poort 8080 doet de install, backups, en updates. Officiële Nextcloud-deployment, opgezet om "in vijf minuten" een werkende Nextcloud op te leveren. + +**AIO is een goede default als** je één Nextcloud op één host draait, één publiek domein hebt, en de built-in backup/update-flow accepteert. + +**AIO is **niet** geschikt voor:** + +- Airgapped installs (heeft internet nodig voor de master container). +- Self-signed certificaten of toegang via IP-adres alleen. +- Sub-directory installs (`example.com/cloud`). +- Meerdere domeinen achter dezelfde install. +- Non-standaard poorten — 443 is verplicht. +- SELinux zonder workarounds. + +Voor die scenario's: Docker Compose, met je eigen wiring. + +**Wanneer wel (containers in het algemeen):** + +- Je draait één of een handvol Nextcloud-installs op een hypervisor of dedicated host. +- Je wil snel kunnen wisselen tussen Nextcloud-major-versies (image-tag wijzigen, container herstarten). +- Je hebt een vloot apps in containers en wil Nextcloud daar consistent naast. + +**Wanneer niet:** + +- Je hebt geen Docker-host of wil er geen onderhouden. Optie 1. +- Je draait al Kubernetes voor andere workloads. Optie 3. + +**Complexiteit:** Laag voor AIO (één commando, browser-wizard). Middelmatig voor eigen compose (compose-bestand schrijven, TLS regelen, backups bedenken). + +**Schaalbaarheid:** Beide opties zijn single-host. Voor échte horizontale schaal moet je richting Kubernetes of bare-metal scale-out. Voor 99% van de teams is single-host op een fatsoenlijke VM ruim voldoende — een dual-core 8 GB host trekt makkelijk dertig à vijftig actieve users. + +**Documentatie:** [Docker image](https://hub.docker.com/_/nextcloud) en [Nextcloud AIO repo](https://github.com/nextcloud/all-in-one). + +## Optie 3: Kubernetes met de officiële Helm chart + +[`nextcloud/helm`](https://github.com/nextcloud/helm) is de community-maintained chart, met de letterlijke waarschuwing in de README: *"⚠️⚠️⚠️ This project is maintained by community volunteers and designed for expert use."* De maintainers raden expliciet AIO aan voor wie "quick and easy" wil. Lees: kies dit alleen als je al een productioneel Kubernetes-cluster hebt en weet wat een PersistentVolumeClaim is. + +```bash +helm repo add nextcloud https://nextcloud.github.io/helm/ +helm repo update +helm install my-nextcloud nextcloud/nextcloud +``` + +Drie commando's, maar de echte werk zit in de `values.yaml`: ingress, certmanager-annotaties, externe MariaDB of Postgres, externe Redis, S3-compatibele primary storage, replica-counts, resource requests/limits, anti-affinity zodat replicas niet op dezelfde node landen. + +**Wanneer wel:** + +- Je draait al Kubernetes met cert-manager, een ingress controller, en monitored persistent storage. +- Je wilt multi-tenant: meerdere Nextcloud-instanties, elk in een eigen namespace, gedeelde infra. +- Je hebt HA-eisen: meerdere replicas, automatische failover, rolling updates zonder downtime. +- Je werkt vanuit GitOps (ArgoCD / Flux) en wil Nextcloud daar in passen. + +**Wanneer niet:** + +- Je hebt nog geen Kubernetes draaien. De drempel om dit *speciaal voor Nextcloud* op te tuigen is veel te hoog. Begin bij optie 2. +- Je hebt één Nextcloud nodig en niet meer. Single-pod Helm is meer werk dan Docker Compose, voor minder controle. + +**Complexiteit:** Hoog. Begin niet aan deze chart als je niet zelfstandig een `kubectl describe pod` kunt interpreteren en weet wat een StorageClass doet. + +**Schaalbaarheid:** Hoog. Meerdere Nextcloud-pods achter een service, gedeelde primary storage op S3, gedeelde Redis, externe gemanagede database. Dit is de enige optie die multi-region, multi-tenant tot in de honderden actieve users per minuut realistisch dekt. + +**Documentatie:** [`nextcloud/helm` README](https://github.com/nextcloud/helm) en de [scaling deployment recommendations](https://docs.nextcloud.com/server/latest/admin_manual/installation/deployment_recommendations.html). + +## Beslismatrix + +Lees van links naar rechts. Eerste match wint. + +| Scenario | Aantal users | Schaal-eis | Ops-capaciteit | Kies | +| --- | --- | --- | --- | --- | +| Homelab, eigen gebruik | 1 — 5 | Geen | Beperkt | **Optie 2b (AIO)** of [lokaal Docker](/academy/nextcloud-lokaal-draaien) | +| Klein team, één organisatie | 5 — 50 | Verticaal | Beperkt | **Optie 2b (AIO)** als wizard-flow oké is, anders **2a (Compose)** | +| Klein team, custom integraties | 5 — 50 | Verticaal | Gemiddeld | **Optie 2a (Docker Compose)** — je hebt control nodig die AIO niet geeft | +| Eén productie-dienst | 50 — 500 | Verticaal | Gemiddeld | **Optie 1 (VM)** of **Optie 2a** met externe DB/Redis | +| Eén productie-dienst, strict compliance | 50 — 500 | Verticaal | Hoog | **Optie 1 (bare metal)** voor audit-leesbaarheid | +| Multi-tenant productie | 100+ per tenant | Horizontaal | Hoog, K8s-kennis aanwezig | **Optie 3 (Kubernetes)** | +| Multi-tenant productie | 100+ per tenant | Horizontaal | K8s niet in huis | **Optie 1 scale-out** of een gemanagede aanbieder | + +Als je twijfelt tussen 2a en 2b: begin met **AIO**. Loop je tegen de [AIO-limitaties](https://github.com/nextcloud/all-in-one#limitations) (sub-directory, meerdere domeinen, non-standaard poort), migreer dan naar 2a — dat is een avond werk met een database-export. + +## Aandachtspunten voor elke productie-Nextcloud + +Onafhankelijk van de gekozen hostingsmaak: deze zes onderwerpen móet je expliciet adresseren voor je iets productie noemt. + +### Backups + +Drie dingen om in een back-up te krijgen, niet één: de Nextcloud-databasedump, de `config/config.php`, en de `data/`-directory (of de S3-bucket als je object storage gebruikt). Restore-test minimaal per kwartaal. AIO heeft een ingebouwde [Borg-backup](https://github.com/nextcloud/all-in-one#how-to-do-a-backup-and-restore); voor de andere opties is `nextcloud-borgbackup` of een eigen script de standaard. + +### Updates + +Nextcloud doet één major-release per half jaar (~april en oktober). Patch-releases tussen-door. Major-upgrades zijn niet skippable: 27 → 29 doe je via 28. Plan een onderhoudsvenster per half jaar. AIO automatiseert dit grotendeels; bij Docker Compose werk je met image-tags; bij bare metal draai je `occ upgrade` na elke pull. + +### TLS + +Verplicht voor productie, ook intern. [Let's Encrypt](https://letsencrypt.org/) met cert-manager (K8s), Traefik / Caddy (Docker), of [`certbot`](https://certbot.eff.org/) (bare metal). AIO doet het automatisch via de built-in Apache. Vergeet HSTS-header en TLS 1.2-minimum niet. + +### Object storage (primary storage) + +Default schrijft Nextcloud user-files naar de `data/`-directory op lokale schijf. Voor productie boven ~500 GB wil je [S3 als primary storage](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/primary_storage.html): MinIO zelf-gehost of een gemanagede S3 zoals AWS, Cloudflare R2, Wasabi. Verandert de schalingscurve volkomen. + +### Caching + +Redis als distributed memcache is verplicht zodra je meer dan één PHP-proces hebt — wat al het geval is bij default-config van zowat elke optie. APCu voor local memcache, Redis voor distributed + file-locking. Zonder Redis-file-locking krijg je inconsistenties bij gelijktijdige edits. + +### Performance tuning + +`php.ini`: `memory_limit=512M`, `opcache.enable=1`, `opcache.interned_strings_buffer=16`. Nextcloud's [scaling deployment recommendations](https://docs.nextcloud.com/server/latest/admin_manual/installation/deployment_recommendations.html) zijn de canonieke checklist — werk hem af voor productie, ongeacht hostingoptie. + +## Wat we zelf draaien + +Conduction levert apps die op de officiële Nextcloud-image draaien — onafhankelijk van hostingkeuze. Wat we in de praktijk zien: + +- **De meeste Conduction-klanten:** optie 2a, Docker Compose, met externe Postgres en S3-primary-storage, achter Traefik. Voorspelbaar, debugbaar, en je leest het terug in één compose-file. +- **Homelabs en eerste demo's:** optie 2b (AIO) of [`docker-compose up`](/academy/nextcloud-lokaal-draaien). Tien minuten van niets naar werkende cloud. +- **Grote multi-tenant deployments:** optie 3, Helm, met cert-manager en gemanagede database. Zeldzaam, maar wel groeiend. + +Geen van die keuzes is "de juiste" in absolute zin. De beslismatrix is de juiste, voor jou. + +## Volgende stap + +Je weet welke hostingoptie bij je past. De vervolg-vraag is wat erop komt: + +- [Nextcloud lokaal draaien met Docker](/academy/nextcloud-lokaal-draaien) — drie commando's voor een lokale demo. +- [Een Woo-register opzetten in OpenRegister](/academy/woo-register-opzetten) — Conduction's data-foundation, draait op elke hostingoptie hierboven. +- [Build a Nextcloud app on the Conduction stack — Part 1: Scaffold](/academy/deskdesk-tutorial-1-scaffold) — een eigen app bouwen die op deze stack landt. + + From 3084fcbf2deff11f936ea6eea6b61ed2d897aa37 Mon Sep 17 00:00:00 2001 From: "mwesterweel@hotmail.com" Date: Mon, 11 May 2026 23:40:59 +0200 Subject: [PATCH 004/330] academy: tone down opinion + vocab tweaks in Nextcloud hosting tutorial MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop unverifiable scale claims from all three options ("paar honderd users", "99% van de teams", "dertig à vijftig actieve users", "honderden per minuut") and the bare-metal-vs-Kubernetes jab; defer to Nextcloud's own scale-out documentation for sizing instead. - Drop the "een avond werk" time estimate on the AIO → Compose migration. - "NUC" → "mini-pc" (Intel discontinued the NUC line; less recognizable for the gemeente-leaning audience). - "klanten" → "gebruikers" (3x) to match Conduction's open-source / user-framed brand voice. Co-Authored-By: Claude Opus 4.7 (1M context) --- academy/2026-05-11-nextcloud-hosten/index.mdx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/academy/2026-05-11-nextcloud-hosten/index.mdx b/academy/2026-05-11-nextcloud-hosten/index.mdx index 9171e3d6a..0075f31aa 100644 --- a/academy/2026-05-11-nextcloud-hosten/index.mdx +++ b/academy/2026-05-11-nextcloud-hosten/index.mdx @@ -11,7 +11,7 @@ durationMinutes: 20 import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, ContactCta} from '@conduction/docusaurus-preset/components'; -Een Nextcloud opzetten kan op drie manieren, en de "beste" hangt af van wie je bent en wat je al draait. Een homelab op een NUC vraagt iets anders dan een gemeente met tien bestuurslagen en een ISO-audit. Deze tutorial zet de drie hoofdopties naast elkaar, met voor- en nadelen en een beslismatrix om snel te kiezen. Niets vervangt de officiële [Nextcloud admin docs](https://docs.nextcloud.com/server/latest/admin_manual/), maar dit is de korte route naar de juiste hoofdstukken. +Een Nextcloud opzetten kan op drie manieren, en de "beste" hangt af van wie je bent en wat je al draait. Een homelab op een mini-pc vraagt iets anders dan een gemeente met tien bestuurslagen en een ISO-audit. Deze tutorial zet de drie hoofdopties naast elkaar, met voor- en nadelen en een beslismatrix om snel te kiezen. Niets vervangt de officiële [Nextcloud admin docs](https://docs.nextcloud.com/server/latest/admin_manual/), maar dit is de korte route naar de juiste hoofdstukken. {/* truncate */} @@ -23,7 +23,7 @@ Een Nextcloud opzetten kan op drie manieren, en de "beste" hangt af van wie je b - Een idee van je doelgroep: alleen jezelf, een klein team, of een productiedienst voor meerdere klanten. + Een idee van je doelgroep: alleen jezelf, een klein team, of een productiedienst voor meerdere gebruikers. Basiskennis Linux-server-admin (SSH, een package manager, systemd) helpt, maar is geen harde eis voor optie 2. Voor optie 3: een werkend Kubernetes-cluster (eigen of beheerd). Geen cluster? Optie 3 is niet voor jou — kies optie 2. @@ -57,7 +57,7 @@ De klassieke route: LAMP-stack op een Linux-server, Nextcloud uitpakken in `/var **Complexiteit:** Hoog initieel (LAMP-stack opzetten, TLS-certificaten, php-fpm tunen). Daarna middelmatig (Nextcloud-major-upgrades vereisen handwerk via `occ upgrade`). -**Schaalbaarheid:** Een enkel VM met 8 vCPU en 16 GB RAM trekt een paar honderd actieve users. Voor meer wil je een [scale-out-setup](https://docs.nextcloud.com/server/latest/admin_manual/installation/deployment_recommendations.html): meerdere Nextcloud-frontends achter een load balancer, met gedeelde Redis, gedeelde S3-compatibele object storage en een aparte database-host. Dat kán op bare metal, maar dan ben je effectief Kubernetes aan het uitvinden zonder Kubernetes te draaien. +**Schaalbaarheid:** Verticaal door je VM groter te maken. Voor horizontaal schaal-uit gebruik je Nextcloud's [scale-out-aanwijzingen](https://docs.nextcloud.com/server/latest/admin_manual/installation/deployment_recommendations.html): meerdere Nextcloud-frontends achter een load balancer, gedeelde Redis, gedeelde S3-compatibele object storage, en een aparte database-host. Concrete sizing hangt af van je werklast — raadpleeg de officiële documentatie voor richtgetallen. **Documentatie:** [Manual installation on Linux](https://docs.nextcloud.com/server/latest/admin_manual/installation/source_installation.html), en de [example installation on Ubuntu 24.04](https://docs.nextcloud.com/server/latest/admin_manual/installation/example_ubuntu.html). @@ -67,7 +67,7 @@ Het brede midden. Twee smaken: ### 2a. Docker Compose -Eigen `docker-compose.yml`, de officiële [`nextcloud`-image](https://hub.docker.com/_/nextcloud), Postgres en Redis ernaast. Dit is hoe wij Conduction-apps draaien voor de meeste klanten en hoe je [lokaal Nextcloud draait](/academy/nextcloud-lokaal-draaien). Volledige config-controle, je eigen reverse proxy ervoor (Traefik, Caddy, Nginx Proxy Manager), je eigen TLS-routine. +Eigen `docker-compose.yml`, de officiële [`nextcloud`-image](https://hub.docker.com/_/nextcloud), Postgres en Redis ernaast. Dit is hoe wij Conduction-apps draaien voor de meeste gebruikers en hoe je [lokaal Nextcloud draait](/academy/nextcloud-lokaal-draaien). Volledige config-controle, je eigen reverse proxy ervoor (Traefik, Caddy, Nginx Proxy Manager), je eigen TLS-routine. ### 2b. Nextcloud AIO (All-in-One) @@ -99,7 +99,7 @@ Voor die scenario's: Docker Compose, met je eigen wiring. **Complexiteit:** Laag voor AIO (één commando, browser-wizard). Middelmatig voor eigen compose (compose-bestand schrijven, TLS regelen, backups bedenken). -**Schaalbaarheid:** Beide opties zijn single-host. Voor échte horizontale schaal moet je richting Kubernetes of bare-metal scale-out. Voor 99% van de teams is single-host op een fatsoenlijke VM ruim voldoende — een dual-core 8 GB host trekt makkelijk dertig à vijftig actieve users. +**Schaalbaarheid:** Beide opties zijn primair single-host. Voor horizontale schaal-uit ga je richting Kubernetes of bare-metal scale-out. Voor de meeste teams blijft single-host op een goed gesized VM ruim voldoende; concrete sizing hangt af van werklast en is een kwestie van meten. **Documentatie:** [Docker image](https://hub.docker.com/_/nextcloud) en [Nextcloud AIO repo](https://github.com/nextcloud/all-in-one). @@ -129,7 +129,7 @@ Drie commando's, maar de echte werk zit in de `values.yaml`: ingress, certmanage **Complexiteit:** Hoog. Begin niet aan deze chart als je niet zelfstandig een `kubectl describe pod` kunt interpreteren en weet wat een StorageClass doet. -**Schaalbaarheid:** Hoog. Meerdere Nextcloud-pods achter een service, gedeelde primary storage op S3, gedeelde Redis, externe gemanagede database. Dit is de enige optie die multi-region, multi-tenant tot in de honderden actieve users per minuut realistisch dekt. +**Schaalbaarheid:** Hoog. Meerdere Nextcloud-pods achter een service, gedeelde primary storage op S3, gedeelde Redis, externe gemanagede database. Dit is de route die multi-region en multi-tenant realistisch ondersteunt, met horizontale schaal-uit als eerste-klas concept in plaats van als nabouw. **Documentatie:** [`nextcloud/helm` README](https://github.com/nextcloud/helm) en de [scaling deployment recommendations](https://docs.nextcloud.com/server/latest/admin_manual/installation/deployment_recommendations.html). @@ -147,7 +147,7 @@ Lees van links naar rechts. Eerste match wint. | Multi-tenant productie | 100+ per tenant | Horizontaal | Hoog, K8s-kennis aanwezig | **Optie 3 (Kubernetes)** | | Multi-tenant productie | 100+ per tenant | Horizontaal | K8s niet in huis | **Optie 1 scale-out** of een gemanagede aanbieder | -Als je twijfelt tussen 2a en 2b: begin met **AIO**. Loop je tegen de [AIO-limitaties](https://github.com/nextcloud/all-in-one#limitations) (sub-directory, meerdere domeinen, non-standaard poort), migreer dan naar 2a — dat is een avond werk met een database-export. +Als je twijfelt tussen 2a en 2b: begin met **AIO**. Loop je tegen de [AIO-limitaties](https://github.com/nextcloud/all-in-one#limitations) (sub-directory, meerdere domeinen, non-standaard poort), migreer dan naar 2a met een database-export. ## Aandachtspunten voor elke productie-Nextcloud @@ -181,7 +181,7 @@ Redis als distributed memcache is verplicht zodra je meer dan één PHP-proces h Conduction levert apps die op de officiële Nextcloud-image draaien — onafhankelijk van hostingkeuze. Wat we in de praktijk zien: -- **De meeste Conduction-klanten:** optie 2a, Docker Compose, met externe Postgres en S3-primary-storage, achter Traefik. Voorspelbaar, debugbaar, en je leest het terug in één compose-file. +- **De meeste Conduction-gebruikers:** optie 2a, Docker Compose, met externe Postgres en S3-primary-storage, achter Traefik. Voorspelbaar, debugbaar, en je leest het terug in één compose-file. - **Homelabs en eerste demo's:** optie 2b (AIO) of [`docker-compose up`](/academy/nextcloud-lokaal-draaien). Tien minuten van niets naar werkende cloud. - **Grote multi-tenant deployments:** optie 3, Helm, met cert-manager en gemanagede database. Zeldzaam, maar wel groeiend. From 2486dd042bf82e39c4f83433d311f94da35e0ae4 Mon Sep 17 00:00:00 2001 From: "mwesterweel@hotmail.com" Date: Tue, 12 May 2026 13:46:58 +0200 Subject: [PATCH 005/330] academy: drop 'wat we zelf draaien' anekdote uit Nextcloud hosting tutorial Co-Authored-By: Claude Opus 4.7 (1M context) --- academy/2026-05-11-nextcloud-hosten/index.mdx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/academy/2026-05-11-nextcloud-hosten/index.mdx b/academy/2026-05-11-nextcloud-hosten/index.mdx index 0075f31aa..b794aabb9 100644 --- a/academy/2026-05-11-nextcloud-hosten/index.mdx +++ b/academy/2026-05-11-nextcloud-hosten/index.mdx @@ -67,7 +67,7 @@ Het brede midden. Twee smaken: ### 2a. Docker Compose -Eigen `docker-compose.yml`, de officiële [`nextcloud`-image](https://hub.docker.com/_/nextcloud), Postgres en Redis ernaast. Dit is hoe wij Conduction-apps draaien voor de meeste gebruikers en hoe je [lokaal Nextcloud draait](/academy/nextcloud-lokaal-draaien). Volledige config-controle, je eigen reverse proxy ervoor (Traefik, Caddy, Nginx Proxy Manager), je eigen TLS-routine. +Eigen `docker-compose.yml`, de officiële [`nextcloud`-image](https://hub.docker.com/_/nextcloud), Postgres en Redis ernaast. Hetzelfde patroon dat je gebruikt voor [lokaal Nextcloud draaien](/academy/nextcloud-lokaal-draaien). Volledige config-controle, je eigen reverse proxy ervoor (Traefik, Caddy, Nginx Proxy Manager), je eigen TLS-routine. ### 2b. Nextcloud AIO (All-in-One) @@ -177,15 +177,7 @@ Redis als distributed memcache is verplicht zodra je meer dan één PHP-proces h `php.ini`: `memory_limit=512M`, `opcache.enable=1`, `opcache.interned_strings_buffer=16`. Nextcloud's [scaling deployment recommendations](https://docs.nextcloud.com/server/latest/admin_manual/installation/deployment_recommendations.html) zijn de canonieke checklist — werk hem af voor productie, ongeacht hostingoptie. -## Wat we zelf draaien - -Conduction levert apps die op de officiële Nextcloud-image draaien — onafhankelijk van hostingkeuze. Wat we in de praktijk zien: - -- **De meeste Conduction-gebruikers:** optie 2a, Docker Compose, met externe Postgres en S3-primary-storage, achter Traefik. Voorspelbaar, debugbaar, en je leest het terug in één compose-file. -- **Homelabs en eerste demo's:** optie 2b (AIO) of [`docker-compose up`](/academy/nextcloud-lokaal-draaien). Tien minuten van niets naar werkende cloud. -- **Grote multi-tenant deployments:** optie 3, Helm, met cert-manager en gemanagede database. Zeldzaam, maar wel groeiend. - -Geen van die keuzes is "de juiste" in absolute zin. De beslismatrix is de juiste, voor jou. +Conduction-apps draaien op de officiële Nextcloud-image en zijn agnostisch over de gekozen hostingoptie. Geen van de drie routes is "de juiste" in absolute zin — de beslismatrix hierboven is de juiste, voor jouw scenario. ## Volgende stap @@ -197,6 +189,6 @@ Je weet welke hostingoptie bij je past. De vervolg-vraag is wat erop komt: From 1f995c68da1ff066bb044547622eaf7270adf2cd Mon Sep 17 00:00:00 2001 From: "mwesterweel@hotmail.com" Date: Wed, 13 May 2026 11:41:00 +0200 Subject: [PATCH 006/330] academy: Dutch learning module on Haven and a kind-based oefencluster Explains the VNG Haven standard end-to-end and pairs it with a local practice cluster on kind: Gateway API via Envoy Gateway, metrics-server, and a verified run of the Haven Compliancy Checker (v12.8.0) including the actual 7/15 results table from a multi-node kind cluster. Co-Authored-By: Claude Opus 4.7 (1M context) --- academy/2026-05-12-haven-en-kind/index.mdx | 481 +++++++++++++++++++++ 1 file changed, 481 insertions(+) create mode 100644 academy/2026-05-12-haven-en-kind/index.mdx diff --git a/academy/2026-05-12-haven-en-kind/index.mdx b/academy/2026-05-12-haven-en-kind/index.mdx new file mode 100644 index 000000000..bbc836ecd --- /dev/null +++ b/academy/2026-05-12-haven-en-kind/index.mdx @@ -0,0 +1,481 @@ +--- +slug: haven-en-kind +title: Haven en Haven+ — van VNG-standaard naar lokale Kubernetes-praktijk +contentType: tutorial +authors: [conduction] +date: 2026-05-12 +summary: Wat Haven voorschrijft, waarom het bestaat en hoe je de bouwstenen lokaal namaakt met kind. Geen compliant cluster op je laptop, wel een werkende oefenomgeving waarin de standaard tastbaar wordt. +tags: [Haven, Kubernetes, Common Ground, kind, Hosting, Architectuur] +durationMinutes: 30 +--- + +import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, ContactCta} from '@conduction/docusaurus-preset/components'; + +Haven is een VNG-standaard voor gemeentelijke Kubernetes-clusters. Deze module legt uit wat de standaard voorschrijft, waarom hij bestaat, en hoe je de bouwstenen lokaal nabootst met kind — zodat de term tastbaar wordt voordat je 'm in een aanbesteding tegenkomt. + +{/* truncate */} + + + Wat Haven en Haven+ zijn en in welke context ze ontstaan (Common Ground, 342 gemeenten, "pas toe of leg uit"). + Welke concrete Kubernetes-bouwstenen de standaard verwacht. + Het verschil tussen Haven en Haven+, en wanneer je welke kiest. + Wat een applicatie nodig heeft om op een Haven-cluster te kunnen draaien. + Hoe je een Haven-achtige oefenomgeving lokaal opzet met kind en Docker. + Waarom je de Haven Compliancy Checker níet zinvol op kind draait, en waar je 'm wél gebruikt. + + + + Basiskennis Kubernetes (pods, services, ingress, manifests). + Docker lokaal draaiend. + `kubectl` en `kind` geïnstalleerd — installatie komt in stap 5. + + +## 1. Waarom Haven bestaat + +Nederland heeft 342 gemeenten en die hebben min of meer dezelfde primaire processen — vergunningen, meldingen, registers, aanvragen. Twintig jaar lang werden die processen toch elk apart uitgevraagd en ingekocht, leverancier per leverancier. Het resultaat: applicaties die niet uitwisselbaar zijn, elk met eigen runtime-aannames en elk met een leverancier die de touwtjes in handen houdt. + +[Common Ground](https://commonground.nl/) is de tegenbeweging: gedeelde standaarden, herbruikbare componenten, geen vendor lock-in. Een applicatie die voor één gemeente geschreven is, moet bij een andere gemeente kunnen draaien zonder dat de hostingomgeving opnieuw uitgevonden wordt. Daarvoor moet vaststaan wat een productiecluster minimaal kan — en daar gaat [Haven](https://haven.commonground.nl/) over. + +Haven is een door [VNG](https://vng.nl/projecten/haven) vastgestelde standaard voor gemeentelijke Kubernetes-clusters. De status is ["pas toe of leg uit"](https://developer.overheid.nl/kennisbank/devops/standaarden/haven): kies je als gemeente voor Kubernetes, dan volg je Haven — tenzij je gemotiveerd uitlegt waarom niet. Het is geen aanbestedingseis op zichzelf, maar wel de impliciete benchmark waarmee inkopers, architecten en leveranciers elkaar wegen. + +## 2. Wat schrijft Haven concreet voor + +Haven definieert compliancy als een lijst geautomatiseerde checks: een cluster is Haven Compliant zodra alle verplichte checks slagen. De canonieke lijst leeft in [`checks.yaml`](https://gitlab.com/commonground/haven/haven/-/blob/main/haven/cli/pkg/compliancy/static/checks.yaml) in de Haven-repo en is de single source of truth — de [Compliancy Checker-pagina](https://haven.commonground.nl/techniek/checks) rendert dezelfde lijst. + +Op hoofdlijnen, gegroepeerd zoals de checker dat zelf doet: + +**Infrastructuur** +- Verkeer over meerdere availability zones (`multiaz`). +- Minimaal drie master nodes (`hamasters`) — hoge beschikbaarheid van de control plane. +- Minimaal drie worker nodes (`haworkers`) — ruimte voor high-availability workloads. +- Hardening op node-niveau: SELinux, AppArmor, Grsecurity, LKRG, Talos of Flatcar (`nodehardening`). +- Private networking-topologie — masters en workers niet direct aan het publieke internet (`privatenetworking`). + +**Cluster** +- Recente Kubernetes-versie: laatste stable of maximaal drie minor versies erachter (`kubernetesversion`). +- RBAC ingeschakeld (`rbac`). +- `ReadWriteMany` persistent volume support (`rwxvolumes`) — gedeelde storage voor HA-deployments. + +**Externe conformiteit** +- CNCF Kubernetes Conformance (`cncf`) — het cluster voldoet aan de standaard Kubernetes-API's. + +**Haven+ (categorie binnen Haven, geen aparte standaard — zie sectie 3)** +- Automatisch HTTPS-certificaten kunnen uitgeven en vernieuwen (`autocerts`). +- Log aggregation — alle container-logs naar een centrale bestemming (`logs`). +- Metrics-server draait — Prometheus-compatibele cluster-metrics (`metrics`). + +Aanvullend draait de checker enkele zelf-checks: of de Haven CLI up-to-date is (`havenversion`), of de checker met cluster-admin-rechten draait (`clusteradmin`), en of de binary SHA-gevalideerd is (`shavalidation`). + +Optioneel — niet verplicht voor Haven Compliancy — zijn [CIS Kubernetes Benchmark](https://www.cisecurity.org/benchmark/kubernetes) en [Kubescape](https://kubescape.io/), die je expliciet aanzet met `--cis` of `--kubescape` in de checker. + +Wat Haven uitdrukkelijk **niet** is, volgens de officiële `wat-is-haven`-pagina: + +- **Geen "all inclusive" cloud-oplossing.** Haven richt zich specifiek op de hostingcomponent. +- **Geen security baseline.** Letterlijk: *"Haven is ook geen security baseline. Dat kan ook niet, want security betreft de som der delen."* Voor het complete beveiligingsverhaal verwijst Haven naar BIO, ISO 2700x en CIS-benchmarks. +- **Geen leveranciers- of cloud-keuze.** De standaard is platformonafhankelijk. + +> **Ingress en Gateway API.** De checker valideert geen specifieke ingress-implementatie. De Kubernetes-community schuift van de klassieke `Ingress`-resource naar [Gateway API](https://gateway-api.sigs.k8s.io/) (GA sinds 2024); `ingress-nginx` zit in maintenance mode. Voor nieuwe applicaties is Gateway API de toekomstvaste keuze, en dat is wat we in stap 5 lokaal opzetten. + +## 3. Eén standaard, geen aparte "Haven+" + +In sommige publicaties verschijnt "Haven+" als een aparte, strengere standaard naast Haven. **De officiële Haven-documentatie kent die scheiding niet.** Er is één standaard: Haven, [tot standaard verklaard door VNG op 25 maart 2022](https://vng.nl/nieuws/bestuur-vng-verklaart-haven-tot-standaard). + +De [Compliancy Checker](https://haven.commonground.nl/techniek/compliancy-checker) groepeert checks intern in categorieën — onder andere *Fundamental*, *Infrastructure*, *Cluster*, *External*, en een categorie die **`Haven+`** heet. Maar al die categorieën staan in dezelfde verplichte lijst (`compliancychecks` in [`checks.yaml`](https://gitlab.com/commonground/haven/haven/-/blob/main/haven/cli/pkg/compliancy/static/checks.yaml)). Een cluster is Haven Compliant zodra ze allemaal slagen. + +Drie veelvoorkomende misverstanden, naast elkaar gezet: + +- **"Haven+ is strenger qua infrastructuur (multi-AZ, 3 masters, private networking)."** Niet waar — die eisen zitten gewoon in baseline Haven, in de categorie *Infrastructure*. +- **"De *Haven+*-categorie in de checker is een upgrade-pad."** Niet exact — het is een aanduiding voor operationele functies die later aan de standaard zijn toegevoegd: automatische HTTPS (`autocerts`), log aggregatie (`logs`) en metrics-server (`metrics`). Ze zijn verplicht, net als de rest. +- **"[Project Haven+](https://blog.brightcubes.nl/nieuws/project-haven-plus) is een nieuwe standaard."** Het is een community- en adoptie-initiatief van Bright Cubes en gemeente Utrecht rond Haven, geen tweede formele standaard. Het [interview met het Haven+-team](https://developer.overheid.nl/blog/2025/06/25/interview-haven) gaat over diezelfde adoptie en doorontwikkeling. + +Praktische conclusie voor lezers, inkopers en architecten: **één Haven, één checklist, één checker.** Een cluster is óf Haven Compliant, óf niet. Wat wél evolueert is de versie — Haven werkt met major.minor.patch versionering, met ongeveer elk kwartaal een nieuwe major. Om compliant te blijven moet de checker minimaal elk kwartaal opnieuw slagen, met een venster van drie maanden om bij een nieuwe major bij te trekken. + +## 4. Wat betekent dit voor jouw applicatie + +Een Haven-cluster is breed inzetbaar, maar dat werkt twee kanten op: jouw applicatie moet ook ergens aan voldoen om er thuis op te zijn. Concreet: + +- **Containerized.** De applicatie draait als één of meer OCI-compatibele container-images. Geen `apt-get install` op de host, geen aannames over wat er op een specifieke node staat. +- **Manifests of Helm chart.** Deployment-configuratie staat in `Deployment`, `Service`, `Ingress`-manifests of als Helm chart. Geen handmatige `kubectl create`-recepten. +- **Metrics endpoint.** Een Prometheus-compatibel `/metrics`-endpoint, zodat de cluster-monitoring je applicatie kan bevragen zonder leveranciersspecifieke integratie. +- **Replica-veilig.** Meerdere replica's tegelijk draaien zonder elkaar in de weg te zitten. Sessies in een gedeelde store (Redis, database), geen lokale geheugen-state die alleen voor één pod geldig is. +- **Geen lokale filesystem-aannames.** Wat naar disk moet, gaat naar een `PersistentVolume` of object storage (S3-compatibel). Container filesystem is efemeer. +- **Configuratie via environment of `ConfigMap`/`Secret`.** Geen hardcoded URLs, geen credentials in images. +- **Liveness en readiness probes.** Het cluster moet kunnen meten of een pod draait en verkeer kan ontvangen. + +Voldoet je applicatie hier niet aan, dan is de migratie naar Haven feitelijk een herontwerp — niet een hosting-keuze. + +## 5. Lokaal oefenen: een Haven-achtig cluster met kind + +Doel van deze sectie: een lokaal Kubernetes-cluster dat de bouwstenen levert die Haven verwacht. Niet compliant — dat kan op één laptop niet — wel de structuur waarin je manifests kunt valideren, met de Common Ground-vriendelijke keuzes (Gateway API in plaats van klassieke Ingress). + +We gebruiken één control-plane en drie workers. Haven+ vraagt drie masters voor productie, maar dat heeft op een laptop geen meerwaarde — verlies van de hostmachine neemt alle masters tegelijk mee. Drie workers is wel zinvol: je kunt replica-spreiding en `topologySpreadConstraints` ermee uitproberen. + +Wat je gaat installeren: + +- **kind** — Kubernetes-in-Docker, een cluster in containers +- **Gateway API CRDs + Envoy Gateway** — Gateway API als ingress-laag +- **metrics-server** — Prometheus-compatibele cluster-metrics +- Een testapplicatie om de keten te verifiëren + +### Stap 1: kind en kubectl installeren + +Op macOS via Homebrew: + +```bash +brew install kind kubectl helm +``` + +Op Linux: + +```bash +# kind +curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64 +chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind + +# kubectl +curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" +chmod +x kubectl && sudo mv ./kubectl /usr/local/bin/kubectl + +# helm (zie https://helm.sh/docs/intro/install/ voor jouw distributie) +curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash +``` + +Verifieer: + +```bash +kind version +kubectl version --client +helm version +``` + +### Stap 2: cluster-config schrijven + +Maak `kind-haven.yaml` aan: + +```yaml +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +name: haven-oefen +nodes: + - role: control-plane + - role: worker + - role: worker + - role: worker +``` + +Sober gehouden — geen `extraPortMappings`, want we exposen het verkeer straks via `kubectl port-forward`. Dat houdt het cluster dichter bij wat Haven in productie doet: een Gateway met een externe `LoadBalancer`, niet een poort die direct in de host hangt. + +### Stap 3: cluster opspinnen + +```bash +kind create cluster --config kind-haven.yaml +``` + +Dit duurt ongeveer een minuut. Controleer: + +```bash +kubectl get nodes +``` + +Je ziet vier nodes: één `control-plane` en drie `worker`. + +### Stap 4: Envoy Gateway installeren + +We gebruiken [Envoy Gateway](https://gateway.envoyproxy.io/) als Gateway API-implementatie — een CNCF-project op basis van de gegradueerde Envoy proxy, met Gateway API als enige API (geen erfenis-`Ingress`). De Helm chart installeert de Gateway API CRDs (`GatewayClass`, `Gateway`, `HTTPRoute`, etc.) zelf, dus je hoeft die niet apart te apply'en. + +```bash +helm install eg oci://docker.io/envoyproxy/gateway-helm \ + --version v1.2.0 \ + -n envoy-gateway-system \ + --create-namespace + +kubectl wait --timeout=5m -n envoy-gateway-system \ + deployment/envoy-gateway --for=condition=Available +``` + +Controleer dat de CRDs er staan: + +```bash +kubectl get crd | grep gateway.networking.k8s.io +``` + +Je ziet de standard channel-CRDs (`gatewayclasses`, `gateways`, `httproutes`, `grpcroutes`, `referencegrants`) plus de experimental-channel CRDs die Envoy Gateway meelevert (`tcproutes`, `tlsroutes`, `udproutes`, `backendlbpolicies`, `backendtlspolicies`). + +### Stap 5: metrics-server installeren + +Standaard manifest: + +```bash +kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml +``` + +In een kind-cluster zijn de kubelet-certificaten self-signed. metrics-server moet daarom TLS-verificatie uitzetten — dat doe je met één patch op het deployment: + +```bash +kubectl patch -n kube-system deployment metrics-server --type=json \ + -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]' +``` + +Na een minuut werkt `kubectl top nodes` en levert het cluster metrics. + +### Stap 6: storage class verifiëren + +kind levert standaard een lokale storage class: + +```bash +kubectl get storageclass +``` + +Je ziet `standard (default)`. Een `PersistentVolumeClaim` zonder expliciete `storageClassName` krijgt deze automatisch toegewezen — precies wat Haven verwacht. + +### Stap 7: testdeployment via Gateway API + +Een minimale nginx achter een `Gateway` met een `HTTPRoute`. Save dit als `test.yaml`: + +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: eg +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: eg + namespace: default +spec: + gatewayClassName: eg + listeners: + - name: http + protocol: HTTP + port: 80 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hello +spec: + replicas: 2 + selector: + matchLabels: + app: hello + template: + metadata: + labels: + app: hello + spec: + containers: + - name: nginx + image: nginx:1.27-alpine + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: hello +spec: + selector: + app: hello + ports: + - port: 80 + targetPort: 80 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: hello +spec: + parentRefs: + - name: eg + hostnames: + - "hello.localhost" + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: hello + port: 80 +``` + +Apply: + +```bash +kubectl apply -f test.yaml +``` + +Envoy Gateway maakt op de achtergrond een `Service` van type `LoadBalancer` aan voor de data plane. In een productie-cluster geeft de cloud-provider die service een echt IP. In kind blijft dat IP `` — geen probleem, we testen met port-forward. + +Wacht eerst tot Envoy de Gateway heeft uitgerold en de data plane pod Ready is: + +```bash +kubectl wait --timeout=3m -n envoy-gateway-system \ + --for=condition=Ready pod \ + -l gateway.envoyproxy.io/owning-gateway-name=eg +``` + +Daarna de service vinden (naam bevat een hash) en port-forwarden: + +```bash +ENVOY_SVC=$(kubectl -n envoy-gateway-system get svc \ + -l gateway.envoyproxy.io/owning-gateway-name=eg \ + -o jsonpath='{.items[0].metadata.name}') + +kubectl -n envoy-gateway-system port-forward "svc/$ENVOY_SVC" 18080:80 +``` + +In een ander shell-venster: + +```bash +curl -H "Host: hello.localhost" http://localhost:18080/ +``` + +Je ziet de nginx welcome-pagina. Dat betekent: Gateway API werkt, je deployment draait, en de routing van extern → gateway → service → pod is rond. + +> **Let op — Gateway-status in kind.** Wanneer je `kubectl get gateway eg` draait, ziet de Gateway er als volgt uit: `ACCEPTED=True`, `PROGRAMMED=False`, met reden `AddressNotAssigned`. Dat is verwacht in kind — er is geen cloud-provider die het LoadBalancer-IP toekent. Verkeer route't desondanks via de cluster-IP van de Envoy service. In een echt cluster gaat `PROGRAMMED` naar `True` zodra de cloud-provider een adres uitdeelt. + +### Wat je nu hebt + +Een cluster met de bouwstenen die Haven verwacht: een Gateway API-gebaseerde ingress-laag, metrics-server voor cluster-monitoring, een werkende default storage class, RBAC out of the box. Geen Haven-cluster, wel een omgeving waar je Haven-conforme manifests kunt schrijven en valideren. + +Opruimen wanneer je klaar bent: + +```bash +kind delete cluster --name haven-oefen +``` + +## 6. De Compliancy Checker — installeren en draaien + +De [Haven Compliancy Checker](https://haven.commonground.nl/techniek/compliancy-checker) is een command-line tool, onderdeel van de **Haven CLI**. Geen registratie, geen tokens, geen agents — je downloadt de binary, geeft 'm een kubeconfig met cluster-admin-rechten, en draait `haven check`. + +### Installeren + +Download een release van de [Haven CLI packages](https://gitlab.com/commonground/haven/haven/-/packages). Binaries zijn er voor macOS, Linux en Windows. + +```bash +# Voorbeeld voor Linux amd64 (controleer de actuele versie en jouw architectuur) +unzip haven-v12.8.0-linux-amd64.zip +sudo mv _dist/linux-amd64/haven /usr/local/bin/haven +haven version +``` + +### Draaien + +Op een cluster waar je cluster-admin bent: + +```bash +haven check +``` + +De checker loopt door de verplichte checks uit [`checks.yaml`](https://gitlab.com/commonground/haven/haven/-/blob/main/haven/cli/pkg/compliancy/static/checks.yaml) en print de uitslagen. Een cluster is Haven Compliant als alle verplichte checks slagen, inclusief de CNCF Kubernetes Conformance-check. + +Nuttige flags (uit `haven check --help`): + +- `haven check --rationale` — print de rationale per check en stop, zonder uit te voeren. Goed eerste commando om te zien wat de checker meet. +- `haven check --output json` — JSON-uitvoer voor automatisering. +- `haven --log-file output.log check` — uitgebreide logs naar bestand (aanrader bij JSON-output of lange runs). +- `haven check --cis` — voeg de optionele CIS-benchmark toe. +- `haven check --kubescape` — voeg de optionele Kubescape-benchmark toe. + +### Wat dit oplevert op een kind-cluster + +`haven check` draait gewoon op kind (de checker installeert ongevraagd de `compliancies.haven.commonground.nl` CRD om resultaten op te slaan) en geeft je een concreet beeld van wat al goed staat. Op de cluster die we in stap 5 hebben opgezet is de uitslag: + +```text +[E] Results: 7 out of 15 checks passed, 1 checks skipped, 0 checks unknown. + This is NOT a Haven Compliant cluster. + +┌────────────────┬──────────────────────────────────────────────────────────────────────────┬─────────┐ +│ CATEGORY │ NAME │ PASSED │ +├────────────────┼──────────────────────────────────────────────────────────────────────────┼─────────┤ +│ Fundamental │ Self test: HCC version is latest major or within 3 months upgrade window │ YES │ +│ Fundamental │ Self test: does HCC have cluster-admin │ YES │ +│ Infrastructure │ Multiple availability zones in use │ NO │ +│ Infrastructure │ Running at least 3 master nodes │ NO │ +│ Infrastructure │ Running at least 3 worker nodes │ YES │ +│ Infrastructure │ Nodes have SELinux, Grsecurity, AppArmor, LKRG, Talos or Flatcar enabled │ NO │ +│ Infrastructure │ Private networking topology │ YES │ +│ Cluster │ Kubernetes version is latest stable or max 3 minor versions behind │ NO │ +│ Cluster │ Role Based Access Control is enabled │ YES │ +│ Cluster │ ReadWriteMany persistent volumes support │ NO │ +│ External │ CNCF Kubernetes Conformance │ SKIPPED │ +│ Haven+ │ Automated HTTPS certificate provisioning │ NO │ +│ Haven+ │ Log aggregation is running │ NO │ +│ Haven+ │ Metrics-server is running │ YES │ +│ Validation │ SHA has been validated │ YES │ +└────────────────┴──────────────────────────────────────────────────────────────────────────┴─────────┘ +``` + +Lezing van de NO's: + +- **`multiaz`** — alle nodes draaien op één host. Niet op te lossen op een laptop. +- **`hamasters`** — kind levert standaard één control-plane. Drie masters in één Docker host is niet zinvol om te oefenen. +- **`nodehardening`** — kind-nodes zijn containers zonder SELinux/AppArmor/Talos/Flatcar profile. +- **`kubernetesversion`** — kind v0.24 ships met Kubernetes v1.31; de checker verwacht binnen drie minor versies van de laatste stable. Op te lossen door een nieuwere `kindest/node`-image te kiezen. +- **`rwxvolumes`** — de ingebouwde `local-path` storage class doet alleen `ReadWriteOnce`. Voor `ReadWriteMany` zou je iets als NFS-CSI of Rook moeten installeren. +- **`autocerts`** — geen cert-manager + ClusterIssuer geïnstalleerd. Wel haalbaar op kind, maar buiten scope voor deze module. +- **`logs`** — geen centrale log-aggregator. Ook haalbaar op kind (Loki, Fluent Bit), buiten scope. + +De **SKIPPED** CNCF-check is geen falen: de checker probeert het platform te matchen tegen de [CNCF-conformance lijst](https://github.com/cncf/k8s-conformance) en `kind` staat daar niet op, dus slaat hij de check over (`platform: Unknown could not be matched`). Op een gehoste provider (AKS, GKE, EKS) draait deze check wél. + +Wat slaagt op je kind-cluster — een goede sanity check voor je manifests: + +- Cluster-admin werkt (`clusteradmin`), Haven CLI versie up-to-date (`havenversion`), SHA validatie (`shavalidation`). +- 3+ workers (`haworkers`) — dankzij de multi-node config uit stap 2. +- Private networking (`privatenetworking`) — kind-nodes hebben geen publieke IPs. +- RBAC out of the box (`rbac`). +- Metrics-server na de TLS-patch uit stap 5 (`metrics`). + +Wat hier doorheen komt is nog steeds geen Haven Compliancy — daarvoor heb je een echte cluster nodig waar `multiaz`, `hamasters`, `nodehardening`, `kubernetesversion`, `rwxvolumes`, `autocerts` en `logs` allemaal slagen. Maar je hebt nu wel het patroon onder de knie, en `haven check` zelf werkt zoals het op productie zal werken. + +## 7. Grenzen van de simulatie + +Wat kind je leert is de *vorm* van Haven: welke manifests werken, welke patronen zijn herbruikbaar, hoe controllers naast elkaar leven. Wat het je niet leert: + +- **Multi-AZ-gedrag.** Alle nodes draaien op dezelfde host. Een echte zone-uitval test je hiermee niet. +- **Private networking.** Haven vraagt om nodes zonder directe publieke IP's en netwerksegmentatie tussen control plane en workers (de `privatenetworking`-check). Op kind staat alles in één Docker-bridge. +- **Geo-redundantie en failover.** Geen tweede datacenter, geen DNS-failover, geen disaster recovery te oefenen. +- **Productie-load.** Een laptop is geen lasttest. Performance, latency en resource-pressure-gedrag zijn anders. +- **LoadBalancer met extern IP.** In kind blijft de Gateway-service ``. Echte L4/L7-load balancing van een cloud-provider zit daar niet bij. +- **Cloud-specifieke integraties.** Object storage, secret managers, identity providers — je hebt op kind alleen wat je zelf installeert. + +Praktisch: kind is goed om Haven-conforme manifests te schrijven en te valideren dat ze deployen. Het is geen vervanging van een staging-cluster bij een echte provider. + +## 8. Vervolgstappen naar een echte Haven-omgeving + +Werken je manifests op kind, dan is de volgende stap een echte cluster bij een provider die als Haven Compliant geverifieerd is. De [Aan de slag-pagina](https://haven.commonground.nl/techniek/aan-de-slag) en de [`reference/`-map in de Haven-repo](https://gitlab.com/commonground/haven/haven/-/tree/main/reference) zijn de canonieke bronnen. Op het moment van schrijven staan op de Aan de slag-pagina: + +- Amazon EKS +- Avisi AME *(Nederlandse provider)* +- Cyso Cloud *(Nederlandse provider)* +- Edgeless Systems Constellation +- Google GKE +- Kops on OpenStack +- Microsoft Azure AKS +- OVHcloud Kubernetes +- Oracle OKE +- Previder *(Nederlandse provider)* +- Red Hat OpenShift +- SUSE Rancher Kubernetes Engine (RKE) +- VMware Tanzu Kubernetes Grid + +Haven zegt expliciet "no preferences and/or recommendations are expressed" — de lijst is geen ranking en niet uitputtend. Controleer de Aan de slag-pagina voor de actuele stand. + +Wat verandert er ten opzichte van je kind-cluster: + +- De Gateway/LoadBalancer krijgt een echt extern IP via de cloud-provider; geen port-forward meer. +- Multi-AZ-spreiding werkt — node pools in verschillende zones, en de `multiaz`-check uit Haven kan slagen. +- Storage class wijst naar cloud-storage van de provider (Azure Disk, GCE PD, vSphere CSI), niet een lokaal volume. Voor `rwxvolumes` heb je een driver nodig die `ReadWriteMany` ondersteunt. +- `haven check` kan volledig draaien en feitelijk vaststellen of het cluster Haven Compliant is. + +Voor het Haven-traject zelf is de [Haven GitLab-repo](https://gitlab.com/commonground/haven/haven) het canonieke startpunt — met de specificatie (`checks.yaml`), de checker (`haven/cli`), de reference implementations, en een issue tracker waar standaard-wijzigingen via een issue met label `Standard Change` worden voorgesteld. + + From 5d66e38e164856f1adc6cd413015da738340ee96 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 17 May 2026 22:46:57 +0200 Subject: [PATCH 007/330] =?UTF-8?q?docs(openbuilt):=20refresh=20marketing?= =?UTF-8?q?=20page=20for=20v0.3=20=E2=80=94=20wizard=20+=20tiered=20versio?= =?UTF-8?q?ning=20+=20top-bar=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenBuilt landed six OpenSpec changes on 2026-05-17 (wizard, versioned model, version routing, version promotion, detail-overview, nextcloud- nav). The marketing page hadn't been updated since the pre-spec-C era and was advertising the v0.x "visual designer" set instead of the shipped feature surface. Updates: - Hero version chip "v0.x" → "v0.3" to match the current info.xml. - Lede now names the wizard + dev/staging/prod tiers + the auto top-bar entry — the three things a citizen-developer reader needs to know about before clicking *Install*. - FeatureList rebuilt around the five shipped features: 1. Wizard in drie stappen 2. Workflows declaratief (ADR-031) 3. Dev, staging, productie per app 4. Eén Nextcloud, één bovenbalk (auto nav entry per published app) 5. Virtuele app eerst, echte app als optie (Phase 2 export) MKB-direct NL tone preserved (no em-dashes, sentence case, "je"). The existing McpToolShelf section (from PR #51) and the "Pairs well with" row are unchanged. --- src/pages/apps/openbuilt.mdx | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/pages/apps/openbuilt.mdx b/src/pages/apps/openbuilt.mdx index 6d932570c..554bb7644 100644 --- a/src/pages/apps/openbuilt.mdx +++ b/src/pages/apps/openbuilt.mdx @@ -10,7 +10,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai appId="openbuilt" crumb={[{label: 'Apps', href: '/apps'}, 'OpenBuilt']} status={{label: 'Beta', color: 'var(--c-orange-knvb)'}} - version="v0.x" + version="v0.3" locales="NL · EN" title="OpenBuilt" tagline={<>An app builder for citizen developers on Nextcloud. Compose your own app from OpenRegister schemas, OpenConnector APIs, Procest workflows, and DocuDesk documents. No PHP, no scaffolding.} @@ -28,26 +28,38 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai eyebrow="What it does" title="Bouw je eigen app, zonder code." align="stack" - lede="OpenBuilt laat niet-technische gebruikers eigen apps samenstellen uit de bouwstenen van het Conduction-ecosysteem. Schema's uit OpenRegister, integraties uit OpenConnector, workflows uit Procest, documenten uit DocuDesk. Alles draait als virtuele app in OpenBuilt; export naar een echte Nextcloud-app volgt in fase 2." + lede="OpenBuilt laat niet-technische gebruikers eigen apps samenstellen uit de bouwstenen van het Conduction-ecosysteem. Schema's uit OpenRegister, integraties uit OpenConnector, workflows uit Procest, documenten uit DocuDesk. Een wizard staat de app op, drie tiers (development, staging, productie) leveren een productiepad, en de bovenbalk-entry verschijnt zodra je publiceert. Export naar een echte Nextcloud-app volgt in fase 2." /> } - title="Visuele app-designer." + title="Wizard in drie stappen." > - Kies een register, voeg pagina's toe, koppel formulieren aan schema's. Een manifest beschrijft de app; OpenBuilt rendert hem. + Slug, versie-ketting, rechten. Klikken op klaar geeft je een werkende app, eigen registers voor development, staging en productie, en de eerste pagina-set. Geen JSON, geen scaffolding. } title="Workflows declaratief, geen service-code." > - State machines, aggregaties, notificaties en berekeningen leef in de schema-metadata. Geen PHP-service per app, conform ADR-031. + State machines, aggregaties, berekeningen en notificaties leven in de schema-metadata. Geen PHP-service per app, conform ADR-031. + + } + title="Dev, staging, productie per app." + > + Elke versie krijgt z'n eigen register. Promote tussen tiers met drie strategieën. Rollback is één klik op de productionVersion-pointer. + + } + title="Eén Nextcloud, één bovenbalk." + > + Elke gepubliceerde app krijgt automatisch een eigen top-bar-entry naast Files en Mail. Met per-app icoon, eigen rechten en geen extra installatie. } - title="Virtuele apps eerst, echte app als optie." + title="Virtuele app eerst, echte app als optie." > - Virtuele apps draaien in de OpenBuilt-shell. Klaar voor productie? Exporteer naar een echte Nextcloud-app met eigen ID en namespace. + Virtuele apps draaien in de OpenBuilt-shell. Klaar voor productie? Exporteer naar een echte Nextcloud-app met eigen ID en namespace (Phase 2). From ee26826a61540d77141b97fcf957b191f6a65034 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 17 May 2026 22:50:38 +0200 Subject: [PATCH 008/330] feat(openregister): retell integrations as 24 leaves in 5 categories (#53) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(openbuilt): refresh marketing page for v0.3 — wizard + tiered versioning + top-bar entry OpenBuilt landed six OpenSpec changes on 2026-05-17 (wizard, versioned model, version routing, version promotion, detail-overview, nextcloud- nav). The marketing page hadn't been updated since the pre-spec-C era and was advertising the v0.x "visual designer" set instead of the shipped feature surface. Updates: - Hero version chip "v0.x" → "v0.3" to match the current info.xml. - Lede now names the wizard + dev/staging/prod tiers + the auto top-bar entry — the three things a citizen-developer reader needs to know about before clicking *Install*. - FeatureList rebuilt around the five shipped features: 1. Wizard in drie stappen 2. Workflows declaratief (ADR-031) 3. Dev, staging, productie per app 4. Eén Nextcloud, één bovenbalk (auto nav entry per published app) 5. Virtuele app eerst, echte app als optie (Phase 2 export) MKB-direct NL tone preserved (no em-dashes, sentence case, "je"). The existing McpToolShelf section (from PR #51) and the "Pairs well with" row are unchanged. * feat(openregister): retell integrations as 24 leaves in 5 categories The 'How OpenRegister plugs into the workspace' Showcase used to list five hand-picked integration paths (Mail/Files, Windmill/n8n, XWiki, LLMs, Presidio) that didn't match the integrations the app ships. Rewrite each Showcase item to describe one of the five leaf categories from the pluggable integration registry: Core, Communication, Knowledge, Workflow, External via OpenConnector. Each item names the leaves it covers and links into the matching product-doc page. Drop the separate '24 workspace leaves' Section the previous PR added below the Showcase; the rewritten Showcase covers the same ground. --- src/pages/apps/openregister.mdx | 109 +++++++++----------------------- 1 file changed, 29 insertions(+), 80 deletions(-) diff --git a/src/pages/apps/openregister.mdx b/src/pages/apps/openregister.mdx index 3685bf35a..a32c44c0b 100644 --- a/src/pages/apps/openregister.mdx +++ b/src/pages/apps/openregister.mdx @@ -173,111 +173,60 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, FeatureGrid,
, }, { - id: 'windmill-n8n', - title: 'Windmill and n8n', - summary: 'Trigger workflows on register events. New row, schema change, validation failure, archive transition: each one fires a webhook your Windmill or n8n flow can consume. Outcomes write back to the row.', - cta: {label: 'See the workflow examples', href: 'https://docs.conduction.nl/openregister/windmill-n8n'}, + id: 'communication', + title: 'Communication (Calendar, Contacts, Mail, Talk)', + summary: 'Four leaves that light up when you enable the matching Nextcloud groupware app. NC Calendar surfaces CalDAV events linked to the record. NC Contacts pulls in the parties involved. NC Mail surfaces the inbound and outbound thread. NC Talk carries the conversations that decided the case. One Nextcloud login covers all four.', + cta: {label: 'See the communication leaves', href: 'https://openregister.conduction.nl/docs/Integrations/calendar'}, panel: , }, { - id: 'xwiki', - title: 'XWiki', - summary: 'Embed a live register query into any XWiki page through OpenConnector. Procedure docs stay current automatically. The article and the data live one click apart.', - cta: {label: 'See the XWiki integration', href: 'https://docs.conduction.nl/openregister/xwiki'}, + id: 'knowledge', + title: 'Knowledge & documents (Bookmarks, Collectives, Maps, Photos)', + summary: 'Four leaves that turn a case folder into a live reference workspace. NC Bookmarks for regulatory sources and partner URLs. NC Collectives for the team wiki on the case. NC Maps for site visits and on-the-ground evidence. NC Photos for signed, geo-tagged photographic evidence. All scoped to the record, all running under the same workspace login.', + cta: {label: 'See the knowledge leaves', href: 'https://openregister.conduction.nl/docs/Integrations/collectives'}, panel: , }, { - id: 'llms', - title: 'LLMs (Claude, Mistral, Ollama)', - summary: 'Plain-language search across every register. The LLM rewrites your question into a structured query, OpenRegister returns typed rows with citation-stable IDs. Claude and Mistral run as cloud adapters through OpenConnector; Ollama runs as a sidecar on your own hardware. Every call audits.', - cta: {label: 'See the semantic-search adapter', href: 'https://docs.conduction.nl/openregister/llm-search'}, + id: 'workflow', + title: 'Workflow & analytics (Activity, Analytics, Cospend, Deck, Flow, Forms, Polls, TimeManager)', + summary: 'Eight leaves that cover per-case work. NC Deck for kanban. NC Flow for automation rules on register events. NC Forms for citizen intake. NC Polls for stakeholder check-ins. NC TimeManager for billable hours. NC Cospend for budget and expense tracking. NC Activity for the running stream of who touched what. NC Analytics for KPIs that reference the same typed data the case is built on.', + cta: {label: 'See the workflow leaves', href: 'https://openregister.conduction.nl/docs/Integrations/flow'}, panel: , }, { - id: 'presidio', - title: 'Presidio (PII redaction)', - summary: 'Microsoft Presidio detects personal data in any register row or attached document. DocuDesk runs it before publication; OpenConnector runs it before forwarding to a cloud LLM. The register keeps the source, the outside world only sees the redacted version.', - cta: {label: 'See the redaction pipeline', href: 'https://docs.conduction.nl/openregister/presidio'}, + id: 'external', + title: 'External via OpenConnector (XWiki, OpenProject, Windmill, n8n, LLMs)', + summary: "Two external leaves ship: XWiki for knowledge-base articles and OpenProject for work packages. Both run through OpenConnector, the same path that lets you trigger Windmill or n8n on register events, redact PII with Presidio, and search registers in plain language through Claude, Mistral, or Ollama. Write your own leaf with the scaffold script and it appears on every register the moment your app is installed.", + cta: {label: 'See the external leaves', href: 'https://openregister.conduction.nl/docs/Integrations/xwiki'}, panel: , }, ]} /> -
- - - - -
- All 24 leaves use the same pluggable integration registry. Add your own with the leaf-scaffold script and it appears on every register the moment you install your app. -
-
- Date: Mon, 18 May 2026 07:11:45 +0200 Subject: [PATCH 009/330] feat(openregister): retell integrations in consumer language (#54) Previous Showcase used internal vocabulary ("leaves", "core/communication/ knowledge/workflow/external") and described the integration as just a sidebar surface. Rewrite for commercial readers: - Eyebrow: 'Integrations' (the term consumers use; 'leaves' stays in product docs and ADRs only). - Title: 'Apps that truly work together.' Frame the story as turning an office suite into a workspace. - Lede: name all three integration levels (sidebar tabs, dashboard widgets, shared service layer that lets one app call another's data). - Item titles describe outcomes, not category names: 'Your CRM uses your real contacts and calendar', 'Open a ticket or lead straight from a Talk message', 'Project knowledge, attached to the project', 'Outside systems, in the same fabric'. - Each summary leads with a concrete app-to-app use case (mail-to- project, lead-from-Talk, CRM-reads-Contacts) and explains the integration without naming the internal slot. --- src/pages/apps/openregister.mdx | 52 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/pages/apps/openregister.mdx b/src/pages/apps/openregister.mdx index a32c44c0b..a54a499ac 100644 --- a/src/pages/apps/openregister.mdx +++ b/src/pages/apps/openregister.mdx @@ -172,41 +172,41 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, FeatureGrid,
, }, { - id: 'communication', - title: 'Communication (Calendar, Contacts, Mail, Talk)', - summary: 'Four leaves that light up when you enable the matching Nextcloud groupware app. NC Calendar surfaces CalDAV events linked to the record. NC Contacts pulls in the parties involved. NC Mail surfaces the inbound and outbound thread. NC Talk carries the conversations that decided the case. One Nextcloud login covers all four.', - cta: {label: 'See the communication leaves', href: 'https://openregister.conduction.nl/docs/Integrations/calendar'}, + id: 'groupware', + title: 'Your CRM uses your real contacts and calendar.', + summary: "When a CRM app needs contacts, it reads Nextcloud Contacts; when it needs an agenda, it writes to Nextcloud Calendar. Open a lead, see the meetings about it. Click a contact, jump straight into a Talk conversation. Mail threads link back to the case automatically. One Nextcloud login covers all of it, and nobody maintains a second address book.", + cta: {label: 'See the groupware integrations', href: 'https://openregister.conduction.nl/docs/Integrations/calendar'}, panel: , }, { - id: 'knowledge', - title: 'Knowledge & documents (Bookmarks, Collectives, Maps, Photos)', - summary: 'Four leaves that turn a case folder into a live reference workspace. NC Bookmarks for regulatory sources and partner URLs. NC Collectives for the team wiki on the case. NC Maps for site visits and on-the-ground evidence. NC Photos for signed, geo-tagged photographic evidence. All scoped to the record, all running under the same workspace login.', - cta: {label: 'See the knowledge leaves', href: 'https://openregister.conduction.nl/docs/Integrations/collectives'}, + id: 'project-knowledge', + title: 'Project knowledge, attached to the project.', + summary: "Bookmarks become regulatory references on a case. Collectives wiki pages become the team's working memory on the project. Maps mark the site visits. Photos carry signed, geo-tagged evidence. The project stops being a folder of PDFs and becomes a live reference workspace where new colleagues can catch up without an hour of forwarding threads.", + cta: {label: 'See the knowledge integrations', href: 'https://openregister.conduction.nl/docs/Integrations/collectives'}, panel: , }, { - id: 'workflow', - title: 'Workflow & analytics (Activity, Analytics, Cospend, Deck, Flow, Forms, Polls, TimeManager)', - summary: 'Eight leaves that cover per-case work. NC Deck for kanban. NC Flow for automation rules on register events. NC Forms for citizen intake. NC Polls for stakeholder check-ins. NC TimeManager for billable hours. NC Cospend for budget and expense tracking. NC Activity for the running stream of who touched what. NC Analytics for KPIs that reference the same typed data the case is built on.', - cta: {label: 'See the workflow leaves', href: 'https://openregister.conduction.nl/docs/Integrations/flow'}, + id: 'workflow-from-anywhere', + title: 'Open a ticket or lead straight from a Talk message.', + summary: "Workflow apps call into registers and registers call back. A Talk message becomes a ticket without leaving the conversation. A public Forms intake writes typed rows the team can act on. An NC Flow rule fires when a case archives. Hours, budget, polls, and KPIs all track against the same typed data the case is built on. Every app gets the data layer; every app contributes back to it.", + cta: {label: 'See the workflow integrations', href: 'https://openregister.conduction.nl/docs/Integrations/flow'}, panel: , }, { - id: 'external', - title: 'External via OpenConnector (XWiki, OpenProject, Windmill, n8n, LLMs)', - summary: "Two external leaves ship: XWiki for knowledge-base articles and OpenProject for work packages. Both run through OpenConnector, the same path that lets you trigger Windmill or n8n on register events, redact PII with Presidio, and search registers in plain language through Claude, Mistral, or Ollama. Write your own leaf with the scaffold script and it appears on every register the moment your app is installed.", - cta: {label: 'See the external leaves', href: 'https://openregister.conduction.nl/docs/Integrations/xwiki'}, + id: 'beyond-the-workspace', + title: 'Outside systems, in the same fabric.', + summary: "OpenConnector reaches XWiki for runbooks, OpenProject for work packages, Windmill and n8n for automation, Presidio for PII redaction, and any LLM (Claude, Mistral, Ollama) for plain-language search across registers. The outside app stays its own app, but the workspace treats its data as a first-class part of the record. Write your own integration with the scaffold script and it shows up wherever the app is installed.", + cta: {label: 'See the external integrations', href: 'https://openregister.conduction.nl/docs/Integrations/xwiki'}, panel: , }, ]} From 40ff90bf5ec1f8c887b785e247f83dda8b33998a Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 18 May 2026 20:04:10 +0200 Subject: [PATCH 010/330] feat(seo): AI-crawler baseline + content layer + validation gates (#55) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(seo): ship AI-crawler-friendly metadata (robots.txt, llms.txt, JSON-LD, OG card) Conduction.nl was technically crawlable (SSG, no JS-rendering risk) but commercially invisible to AI agents: no robots.txt, no llms.txt, zero JSON-LD, no og:image, Dutch sitemap stale. This change ships the AI- crawling baseline so we get cited in ChatGPT/Claude/Perplexity answers and our content gets pulled into long-term training corpora — both, on purpose, posture documented in static/robots.txt. Changes - static/robots.txt — explicit allow for OAI-SearchBot, Claude-SearchBot, PerplexityBot, ChatGPT-User, Claude-User, GPTBot, ClaudeBot, CCBot and friends. Two Sitemap: lines (en + nl) for locale-suffix-blind crawlers. - static/llms.txt — llmstxt.org-format index pointing at the 15 shipping apps plus company/contact tells. Dev-tool consumers (Cursor, Aider, MCP doc servers) read this; AI-search adoption is thin but the cost is near zero. - docusaurus.config.js — wraps createConfig() return to inject Organization + WebSite JSON-LD via headTags[] on every page, sets themeConfig.image to the new OG card, and adds twitter:site / twitter:card / og:type baselines via themeConfig.metadata. Also adds sitemap options to the classic preset (changefreq, priority, ignore /academy/tags/** in both locales) so the sitemap is consistent across /sitemap.xml and /nl/sitemap.xml. - static/img/og-conduction.png — new 1200x630 brand OG card. Cobalt #4376FC background, point-up hex motif per brand rules, white wordmark, EUPL-1.2 / ISO 27001 / Made in Amsterdam tells. Out of scope (filed separately): SoftwareApplication JSON-LD per /apps/ page, FAQPage schema on /support, fleet rollout to the 17 per-app docs sites and to the @conduction/docusaurus-preset. Verified: local production build emits both JSON-LD scripts in raw HTML on / and /apps/, og:image + twitter:* meta on every page, og:type stays 'article' on blog posts (not overridden), and both sitemap.xml + nl/sitemap.xml carry 67 URLs each with tag pages excluded. * style(seo): drop em-dashes from robots.txt comments Conduction writing rules ban em-dashes in published copy. robots.txt counts as published copy (served at conduction.nl/robots.txt and read by every AI crawler). Two em-dashes in the comment header replaced with a colon and a period. * feat(seo): add AI-baseline validation gate (hard-fail on regression) CI now blocks any change that drops the AI-crawler baseline. The validation script runs as a postbuild step so every `npm run build` (local dev + CI) re-asserts the contract. Checks (12, all hard-fail) - robots.txt: exists, has Sitemap lines, allows OAI-SearchBot + Claude-SearchBot + PerplexityBot, has the default User-agent: * Allow: / block. - llms.txt: exists, >= 1 KB (catches an accidental placeholder). - sitemap.xml + nl/sitemap.xml: each emit >= 50 URLs. - Homepage: >= 2 JSON-LD blocks, every block valid JSON, includes Organization and WebSite @type entries. - Homepage: og:image, og:type, twitter:site, twitter:card meta tags present. og:image URL resolves to an actual file in the build (so LinkedIn / Slack / AI previews never render a 404). - /apps/openregister: has SoftwareApplication JSON-LD with applicationCategory and operatingSystem populated. This is the regression marker for the DetailHero schema emission that ships from @conduction/docusaurus-preset >= 3.4.0. Wiring - postbuild npm script runs scripts/validate-ai-baseline.mjs. - validate:ai-baseline npm script for ad-hoc runs. - @conduction/docusaurus-preset bumped to ^3.4.0 so the SoftwareApplication check has a guaranteed source. Adding a check: append to the CHECKS list in scripts/validate-ai-baseline.mjs. Keep the failure message specific so a CI annotation tells the reader exactly what regressed. * feat(seo): consume preset 3.4.0 baseline, add per-app ledes and FAQ blocks PR 1 shipped @conduction/docusaurus-preset 3.4.0 with AI-baseline defaults (Organization JSON-LD, SoftwareApplication via DetailHero, FAQPage via FAQ, postBuild robots.txt fallback, og:image, twitter meta, sitemap options). This commit makes conduction.nl actually consume that baseline and adds the citation-friendly content layer on top. Changes - docusaurus.config.js: drop the hand-rolled headTags wrap, the inline Organization / WebSite JSON-LD constants, and the themeConfig.image / themeConfig.metadata overrides. All of it comes from createConfig() now. ~80 lines removed. - src/pages/apps/*.mdx (15 files): Wikipedia-style 1-paragraph lede injected immediately under on every shipping app page. Each lede follows the template " is an open-source app for the Nextcloud workspace. Released under EUPL-1.2 and maintained by Conduction since 2019." Sourced from each app's own docs site / repo, then edited for brand voice (no em-dashes, one claim per sentence, concrete verbs). - src/pages/install.mdx: new 7-question FAQ block (admin install, Nextcloud version range, multi-app install, offline install, uninstall + data, missing-from-store, SLA / support). - src/pages/iso.mdx: new 6-question FAQ block (9001 vs 27001 scope, self-host coverage, BIO alignment, DigiD, commonground.nu stack, certificate request flow). - scripts/validate-ai-baseline.mjs: three more checks pin the FAQPage schema to >= 5 questions each on /support, /install, /iso. Hard-fail on regression. Validation now runs 15 checks total (was 12). Notes - /support already shipped a 6-question . Now that preset 3.4.0 emits FAQPage JSON-LD automatically, the existing block validates without any source change. - mydash README declares AGPL-3.0 while the lede template ends with EUPL-1.2. Filed as a follow-up to verify which is canonical; the lede currently says EUPL-1.2 to match brand positioning. - softwarecatalog has two readings in its docs (IT-asset management vs internal user-sync). The lede goes with the conduction.nl positioning (IT-asset management), which matches the existing tagline in apps-catalog.js. * ci: validate AI baseline on every pull request The centralised ConductionNL/.github documentation workflow only builds on push, schedule, and workflow_dispatch. That meant a PR could drop robots.txt, break a sitemap, or remove SoftwareApplication JSON-LD without the validate-ai-baseline.mjs check ever running in CI. The defect lands; nightly cron catches it the next morning; production is already broken. This workflow runs npm run ci (= npm ci --legacy-peer-deps && npm run build) on every pull_request to development or documentation. The build's existing postbuild step runs the 15-check validator, which exits non-zero on regression. A follow-up explicit invocation acts as belt-and-braces in case anyone removes the postbuild hook. GITHUB_TOKEN is passed so the prebuild app-downloads fetch stays under the 60-req/hr unauthenticated rate limit (same pattern the centralised workflow uses). --- .github/workflows/validate-ai-baseline.yml | 55 +++++ docusaurus.config.js | 20 ++ package.json | 4 +- scripts/validate-ai-baseline.mjs | 261 +++++++++++++++++++++ src/pages/apps/decidesk.mdx | 2 + src/pages/apps/docudesk.mdx | 2 + src/pages/apps/larpingapp.mdx | 2 + src/pages/apps/mydash.mdx | 2 + src/pages/apps/nldesign.mdx | 2 + src/pages/apps/openbuilt.mdx | 2 + src/pages/apps/opencatalogi.mdx | 2 + src/pages/apps/openconnector.mdx | 2 + src/pages/apps/openregister.mdx | 2 + src/pages/apps/pipelinq.mdx | 2 + src/pages/apps/procest.mdx | 2 + src/pages/apps/scholiq.mdx | 2 + src/pages/apps/shillinq.mdx | 2 + src/pages/apps/softwarecatalog.mdx | 2 + src/pages/apps/zaakafhandelapp.mdx | 2 + src/pages/install.mdx | 26 +- src/pages/iso.mdx | 23 +- static/img/og-conduction.png | Bin 0 -> 50269 bytes static/llms.txt | 50 ++++ static/robots.txt | 85 +++++++ 24 files changed, 551 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/validate-ai-baseline.yml create mode 100755 scripts/validate-ai-baseline.mjs create mode 100644 static/img/og-conduction.png create mode 100644 static/llms.txt create mode 100644 static/robots.txt diff --git a/.github/workflows/validate-ai-baseline.yml b/.github/workflows/validate-ai-baseline.yml new file mode 100644 index 000000000..6daa4bc19 --- /dev/null +++ b/.github/workflows/validate-ai-baseline.yml @@ -0,0 +1,55 @@ +name: AI baseline validation + +# Runs on every pull request so the AI-crawler baseline (robots.txt, +# llms.txt, sitemaps, JSON-LD on the homepage and per-app pages, +# FAQPage on /support /install /iso, og:image, twitter meta) is +# re-asserted at PR time, not just after merge. The centralised +# ConductionNL/.github documentation workflow only builds on push, +# so without this job, validation regressions reach development. +# +# What runs: `npm run ci` -> `npm ci --legacy-peer-deps && npm run build`, +# which triggers prebuild (app-downloads sync) + docusaurus build + +# postbuild (scripts/validate-ai-baseline.mjs). Any of the 15 checks +# failing exits non-zero and the workflow fails. +# +# The build is the same one production uses, so a green check here +# also tells reviewers the site actually builds with their changes. + +on: + pull_request: + branches: [development, documentation] + workflow_dispatch: + +jobs: + validate: + name: Build and validate AI baseline + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies and build + env: + # Pass the run-scoped token to the prebuild script's + # app-downloads fetch so it stays under GitHub's 60-req/hr + # unauthenticated limit. The token is auto-scoped to this + # job and expires when it ends. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npm run ci + + - name: Confirm validation gate ran + # `npm run ci` -> `npm run build` -> postbuild runs the + # 15-check validator and exits non-zero on regression. This + # extra step is a belt-and-braces guarantee: if anyone ever + # removes the postbuild hook from package.json by accident, + # the explicit invocation here still catches it at PR time. + run: node scripts/validate-ai-baseline.mjs diff --git a/docusaurus.config.js b/docusaurus.config.js index ae1b9bc7f..3d6aa3d27 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -17,6 +17,12 @@ const {createConfig} = require('@conduction/docusaurus-preset'); +/* AI-crawler baseline (Organization + WebSite JSON-LD, og:image, + twitter meta, FAQPage schema from , SoftwareApplication schema + from ) comes from @conduction/docusaurus-preset >= 3.4.0 + automatically. We override only the bits that are specific to this + site (robots.txt + llms.txt in static/, the sitemap config below + because we pass our own classic-preset overrides). */ module.exports = createConfig({ title: 'Conduction', tagline: 'Open-source apps voor de Nextcloud-werkplek.', @@ -78,6 +84,20 @@ module.exports = createConfig({ theme: { customCss: [require.resolve('./src/css/site.css')], }, + /* Built-in @docusaurus/plugin-sitemap (loaded via the classic + preset). Each locale outputs its own sitemap.xml (en at /, + nl at /nl/) — both are advertised in static/robots.txt so AI + crawlers without locale-suffix discovery still see Dutch + pages. /academy/tags/* is excluded because tag pages are + thin and confuse AI summarisers more than they help SEO. + ignorePatterns matches route paths *after* the locale prefix + is applied, so we list both forms. */ + sitemap: { + changefreq: 'weekly', + priority: 0.5, + ignorePatterns: ['/academy/tags/**', '/nl/academy/tags/**'], + filename: 'sitemap.xml', + }, }, ], ], diff --git a/package.json b/package.json index 7ca89dfe8..896c83ae0 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "start": "docusaurus start", "prebuild": "python3 .github/scripts/app_downloads.py && node scripts/sync-app-downloads.js", "build": "docusaurus build", + "postbuild": "node scripts/validate-ai-baseline.mjs", + "validate:ai-baseline": "node scripts/validate-ai-baseline.mjs", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", @@ -19,7 +21,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^3.3.0", + "@conduction/docusaurus-preset": "^3.4.0", "@docusaurus/core": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", "@mdx-js/react": "^3.0.0", diff --git a/scripts/validate-ai-baseline.mjs b/scripts/validate-ai-baseline.mjs new file mode 100755 index 000000000..72f8fe604 --- /dev/null +++ b/scripts/validate-ai-baseline.mjs @@ -0,0 +1,261 @@ +#!/usr/bin/env node +/** + * scripts/validate-ai-baseline.mjs + * + * Hard-fail validation that the AI-crawler baseline is intact in the + * production build. Run after `npx docusaurus build` (CI invokes it via + * .github/workflows/documentation.yml, see also npm script + * `validate:ai-baseline`). + * + * Checks (any failure exits non-zero): + * + * robots.txt Exists, has Sitemap line, has User-agent: * Allow: /, + * names at least one AI training and one AI search bot. + * llms.txt Exists, body > 1 KB (placeholder content is shorter). + * sitemap.xml Exists, has >= 50 entries. + * nl/sitemap.xml Same, for the Dutch locale build. + * homepage Has >= 2 application/ld+json blocks, all valid JSON. + * Has og:image, og:type, twitter:site, twitter:card + * meta tags. og:image URL resolves to an actual file + * in the build output (so LinkedIn/Slack/AI previews + * don't render a 404). + * /apps/openregister Has SoftwareApplication JSON-LD with + * applicationCategory + operatingSystem (regression + * marker for the DetailHero schema emission shipped + * in @conduction/docusaurus-preset). + * + * Adding a check: append to the CHECKS array. Each check is a function + * (buildDir) -> {ok: boolean, msg: string}. Keep messages specific so + * a failing CI annotation tells the reader what to fix without + * re-running locally. + */ + +import {readFileSync, existsSync, statSync} from 'node:fs'; +import {join, resolve} from 'node:path'; + +const buildDir = resolve(process.argv[2] || 'build'); + +if (!existsSync(buildDir)) { + console.error(`✗ build directory not found: ${buildDir}`); + console.error(` Run \`npx docusaurus build\` first.`); + process.exit(2); +} + +const results = []; + +function check(name, fn) { + try { + const r = fn(); + results.push({name, ok: r.ok, msg: r.msg}); + } catch (e) { + results.push({name, ok: false, msg: `threw: ${e.message}`}); + } +} + +function readBuild(p) { + return readFileSync(join(buildDir, p), 'utf8'); +} + +/* robots.txt */ +check('robots.txt exists and is non-empty', () => { + const path = join(buildDir, 'robots.txt'); + if (!existsSync(path)) return {ok: false, msg: 'missing'}; + const size = statSync(path).size; + if (size < 50) return {ok: false, msg: `too small (${size} bytes)`}; + return {ok: true, msg: `${size} bytes`}; +}); + +check('robots.txt has Sitemap line', () => { + const body = readBuild('robots.txt'); + const matches = body.match(/^Sitemap:\s+https?:\/\//gm) || []; + if (matches.length === 0) return {ok: false, msg: 'no Sitemap: line'}; + return {ok: true, msg: `${matches.length} sitemap line(s)`}; +}); + +check('robots.txt allows AI search bots', () => { + const body = readBuild('robots.txt'); + const needed = ['OAI-SearchBot', 'Claude-SearchBot', 'PerplexityBot']; + const missing = needed.filter(ua => !body.includes(`User-agent: ${ua}`)); + if (missing.length) return {ok: false, msg: `missing User-agent entries for: ${missing.join(', ')}`}; + return {ok: true, msg: needed.join(', ')}; +}); + +check('robots.txt has at least User-agent: * Allow: /', () => { + const body = readBuild('robots.txt'); + if (!/^User-agent:\s*\*\s*$/m.test(body)) return {ok: false, msg: 'no User-agent: * block'}; + if (!/^Allow:\s*\/\s*$/m.test(body)) return {ok: false, msg: 'no Allow: / line'}; + return {ok: true, msg: 'default-allow present'}; +}); + +/* llms.txt */ +check('llms.txt exists and has content', () => { + const path = join(buildDir, 'llms.txt'); + if (!existsSync(path)) return {ok: false, msg: 'missing'}; + const size = statSync(path).size; + if (size < 1024) return {ok: false, msg: `under 1 KB (${size} bytes) - likely placeholder`}; + return {ok: true, msg: `${size} bytes`}; +}); + +/* Sitemaps */ +function sitemapLocCount(p) { + const body = readBuild(p); + return (body.match(//g) || []).length; +} + +check('sitemap.xml has at least 50 URLs', () => { + const path = join(buildDir, 'sitemap.xml'); + if (!existsSync(path)) return {ok: false, msg: 'missing'}; + const n = sitemapLocCount('sitemap.xml'); + if (n < 50) return {ok: false, msg: `only ${n} URLs`}; + return {ok: true, msg: `${n} URLs`}; +}); + +check('nl/sitemap.xml has at least 50 URLs', () => { + const path = join(buildDir, 'nl/sitemap.xml'); + if (!existsSync(path)) return {ok: false, msg: 'missing'}; + const n = sitemapLocCount('nl/sitemap.xml'); + if (n < 50) return {ok: false, msg: `only ${n} URLs`}; + return {ok: true, msg: `${n} URLs`}; +}); + +/* JSON-LD blocks */ +function extractJsonLdBlocks(html) { + /* Docusaurus emits JSON-LD via two paths with different attribute + ordering: top-level headTags renders +``` + +Dat is alles. De ``, de dependency-check empty-state, de loading-spinner, het menu, de sidebars, de user-settings-dialog: CnAppRoot levert ze allemaal en zet ze achter de `dependencies`-array uit het manifest. + +Dan wordt `src/router/index.js` een mapping van manifest naar routes: + +```js title="src/router/index.js" +import Vue from 'vue' +import Router from 'vue-router' +import { generateUrl } from '@nextcloud/router' +import { CnPageRenderer } from '@conduction/nextcloud-vue' +import manifest from '../manifest.json' + +Vue.use(Router) + +const routes = manifest.pages.map((page) => ({ + name: page.id, + path: page.route, + component: CnPageRenderer, +})) + +routes.push({ path: '*', redirect: '/' }) + +export default new Router({ + mode: 'history', + base: generateUrl('/apps/deskdesk'), + routes, +}) +``` + +Elke pagina in het manifest krijgt een route. Elke route rendert CnPageRenderer. CnPageRenderer leest het manifest, vindt de matchende pagina-entry op routenaam, en dispatcht op `page.type`: + +- `type: "index"` → CnIndexPage met de kolommen en filters van het schema +- `type: "detail"` → CnDetailPage met de properties van het schema +- `type: "dashboard"` → CnDashboardPage met de layout +- `type: "custom"` → je geregistreerde component + +## Stap 6: Verwijder de oude views + +Het hele punt van Deel 2 is dat het framework de views voor je schrijft. Verwijder: + +- `src/navigation/MainMenu.vue` — vervangen door CnAppNav (auto-mounted door CnAppRoot) +- `src/views/Dashboard.vue` — placeholder; komt later terug in een rijkere vorm wanneer de dashboardpagina dat nodig heeft +- `src/views/items/ItemList.vue`, `src/views/items/ItemDetail.vue` — vervangen door CnIndexPage + CnDetailPage +- `src/views/settings/UserSettings.vue`, `src/views/settings/Settings.vue` — instellingen leven op de admin-pagina (`AdminRoot.vue`) en de in-app-dialog komt mee met CnAppRoot + +Het voor-en-na op schijf: + +``` +before: 12 .vue files in src/, ~600 lines of hand-rolled component code +after: 3 .vue files (App.vue, AdminRoot.vue, that's it), ~30 lines +``` + +## Stap 7: Bouw, herlaad, blader + +```bash +npm run build +docker cp ./js nextcloud:/var/www/html/custom_apps/deskdesk/ +docker exec nextcloud apache2ctl graceful +``` + +Open `/apps/deskdesk/`. De linker rail leest nu **Desks · Bookings · Floors** (de vertaalkeys zien er nog uit als `deskdesk.menu.desks` totdat je `l10n/`-entries toevoegt; dat is een follow-up). De hoofdkolom toont CnIndexPage met de kolommen uit het schema. Klik een rij aan, je belandt op CnDetailPage voor die desk. De hele CRUD-flow krijg je cadeau. + +## Waarom dit belangrijk is + +Je schreef drie JSON-bestanden. Het framework leest ze en rendert de app. Er is geen `DesksList.vue`, geen `DeskForm.vue`, geen `useDesks.js`. Als je een `priority`-veld toevoegt aan het booking-schema, pikt elke plek waar een booking verschijnt — tabelkolom, detailrij, filter-sidebar, formulierdialoog — het nieuwe veld automatisch op. + +Dit is de schema-gedreven discipline waarover [nextcloud-vue.conduction.nl](https://nextcloud-vue.conduction.nl/docs/architecture/schemas-and-registers/) praat. Deel 3 zet het op een manier in die mensen de eerste keer verrast: één JSON-blok op het booking-schema laat elke booking in de Nextcloud-agenda van de gebruiker verschijnen. Geen controller, geen event, geen listener. + +## Troubleshooting + + + Het relatieve pad dat ConfigurationService::importFromFilePath gebruikt wordt berekend tegen /var/www/html (de SERVERROOT van Nextcloud). Als je SettingsService het verkeerde prefix afsnijdt krijg je deze fout. Gebruik \OC::$SERVERROOT als anker bij het strippen. + + + + Voeg een components.registers.YOURAPP-blok toe aan de JSON. Zonder dat maakt de importhandler de schema's als wezen aan. Het blok heeft slug, title, version en schemas[] nodig. + + + + De store van CnIndexPage wil het numerieke register-id, terwijl /api/settings meestal de register-slug teruggeeft. Bouw een slug → id-resolutie in je settings-store, of geef het numerieke id rechtstreeks vanuit de SettingsService door. Staat als follow-up in de Deel 2-PR. + + + + De keys (bijvoorbeeld deskdesk.menu.desks) hebben entries in l10n/en.json en l10n/nl.json nodig. De library rendert de key wanneer er geen vertaling is. Voeg ze toe wanneer je er klaar voor bent; de app werkt sowieso. + + +## Wat is het volgende + + + + + + diff --git a/i18n/nl/docusaurus-plugin-content-blog/2026-05-17-deskdesk-tutorial-4-knowledge-and-ship/index.mdx b/i18n/nl/docusaurus-plugin-content-blog/2026-05-17-deskdesk-tutorial-4-knowledge-and-ship/index.mdx new file mode 100644 index 000000000..397f933ed --- /dev/null +++ b/i18n/nl/docusaurus-plugin-content-blog/2026-05-17-deskdesk-tutorial-4-knowledge-and-ship/index.mdx @@ -0,0 +1,443 @@ +--- +slug: deskdesk-tutorial-4-knowledge-and-ship +title: "Bouw een Nextcloud-app op de Conduction-stack — Deel 4: Kennis + uitleveren" +contentType: tutorial +authors: [conduction] +date: 2026-05-17 +summary: Zet xWiki lokaal op, schrijf zone-specifieke kennisartikelen, toon ze per desk via een knowledge_article-schema in OpenRegister, package de app en publiceer in de Conduction app store. +tags: [App development, OpenConnector, xWiki, Knowledge management, Release] +apps: [openconnector, openregister] +durationMinutes: 35 +series: deskdesk-tutorial +partNumber: 4 +--- + +import { + Outcomes, + Outcome, + Prerequisites, + PrerequisiteItem, + NextSteps, + NextStep, + HexCard, +} from '@conduction/docusaurus-preset/components'; + +Het laatste deel van de vierdelige DeskDesk-tutorial. Deel 3 liet boekingen in de NC Agenda verschijnen met één schema-annotatie. Deel 4 haalt externe kennis — zone-specifieke etiquette, equipment-notities, troubleshooting — uit xWiki naar de detail-sidebar van een desk via een `knowledge_article`-schema in OpenRegister. Daarna packagen we de app en leveren we hem uit. + +Externe kennis integreren is dé canonieke case voor OpenConnector: data ophalen uit een systeem dat je niet bezit (xWiki hier, maar hetzelfde geldt voor Confluence, Notion, SharePoint, Decos, Mendix), en die binnen de Nextcloud-native UI tonen zonder dat de gebruiker hoeft te schakelen. + +{/* truncate */} + + + Een draaiende xWiki-instantie op http://localhost:8086, op hetzelfde Docker-netwerk als Nextcloud, met dezelfde Postgres. + Drie xWiki-artikelen die je zelf schrijft, gegroepeerd per zone (DeskDeskKnowledge.East.Etiquette, .Central.MeetingDesks, .West.QuietRules). + Een nieuw knowledge_article-schema in OpenRegister binnen DeskDesk, met seed-data die overeenkomt met wat xWiki bevat. + Een "Kennis"-tab op de detailpagina van elke desk die de artikelen toont waarvan de zone matcht met de zone van de desk, zonder dat je DeskDesk verlaat. + Een gepackagede, geversioneerde DeskDesk-release in de Conduction app store, installeerbaar vanuit de app-browser van elke Nextcloud. + + + + Delen 1 tot en met 3 af: DeskDesk-shell, schema's + manifest, agenda-integratie werken allemaal. + Een lokale Nextcloud + OpenRegister-stack waar je in kunt docker exec'en. De referentie-stack op Nextcloud lokaal draaien met Docker is wat we tegen testen; die levert al de xWiki-service die we in Stap 0 starten. + OpenConnector geïnstalleerd en ingeschakeld, voor wanneer je later in de tutorial van seed-data naar een live xWiki-sync overschakelt. + + +## Stap 0: Start xWiki op + +De Conduction-dev-stack declareert al een xWiki-service onder het `xwiki`-profiel van `apps-extra/openregister/docker-compose.yml`: + +```yaml title="openregister/docker-compose.yml (excerpt)" + xwiki: + profiles: [xwiki, integrations] + image: xwiki:lts-postgres-tomcat + container_name: openregister-xwiki + ports: ["8086:8080"] + volumes: ["xwiki-data:/usr/local/xwiki"] + environment: + DB_USER: nextcloud + DB_PASSWORD: "!ChangeMe!" + DB_HOST: db + DB_DATABASE: xwiki + depends_on: [db] +``` + +xWiki deelt de Postgres-container met Nextcloud + OpenRegister, maar gebruikt een aparte database genaamd `xwiki`. Maak die eenmalig aan: + +```bash +docker exec openregister-postgres psql -U nextcloud -d postgres -c "CREATE DATABASE xwiki;" +``` + +Start xWiki nu met het profiel: + +```bash +cd apps-extra/openregister +docker compose --profile xwiki up -d xwiki +``` + +De eerste boot duurt ongeveer een minuut. xWiki initialiseert zijn schema in de lege Postgres-database en pakt gebundelde webapps uit. Wacht tot de health check op healthy springt: + +```bash +docker ps --filter name=openregister-xwiki --format '{{.Status}}' +# Up 22 seconds (health: starting) <- still booting +# Up 1 minute (healthy) <- ready +``` + +Open `http://localhost:8086`. xWiki stuurt je door naar de **Distribution Wizard**, omdat de lege database geen admin-user en geen flavor heeft. + +## Stap 0a: Loop door de Distribution Wizard + +1. **Continue** voorbij het welkomstscherm. +2. **Step 1 - Admin user.** Vul in: + ``` + First Name: Desk + Last Name: Admin + Username: admin + Password: admin1234 (any 8+ chars works) + Confirm: admin1234 + Email: admin@deskdesk.local + ``` + Klik **Register and login**. Je bent nu ingelogd als `admin`. +3. **Step 2 - Flavor.** Klik **Let the wiki be empty** (de gebundelde Standard Flavor is een download van zo'n 150 MB die we voor deze tutorial niet nodig hebben). Bevestig. +4. **Step 5 - Report** toont de aangemaakte pagina's (alleen `Home` en `XWiki`). Klik **Continue** om op de hoofdpagina te landen. + +De wiki is nu gebootstrapt. Doe een sanity check op het REST-endpoint: + +```bash +curl -u admin:admin1234 -H 'Accept: application/json' \ + http://localhost:8086/rest/wikis/xwiki/spaces | head -c 200 +# {"links":[],"spaces":[{"links":[...,"id":"xwiki:XWiki", ...}]} +``` + +Twee dingen om over de URL te weten: de REST API leeft op `/rest`, **niet** `/xwiki/rest` (dat laatste is het pad voor de menselijke weergave), en basic auth is de standaard. + +## Stap 1: Schrijf de kennisartikelen + +Voor deze tutorial gebruiken we een eigen parent space **DeskDeskKnowledge** met één subspace per zone. De mappenstructuur matcht de `zone`-enum van de desk (`east`, `central`, `west`, `north`, `south`), zodat elke desk netjes op de artikelen van zijn zone uitkomt. + +Je kunt de artikelen via xWiki's UI schrijven (Create Page → set parent space → write content). Sneller: spreek de REST API van xWiki direct aan. Dezelfde payload-vorm werkt vanuit cURL, OpenConnector of een willekeurige HTTP-client. + +```bash title="Three short articles, one PUT each" +put() { + curl -s -u admin:admin1234 -X PUT \ + -H "Content-Type: application/xml" \ + -d "$3xwiki/2.1$4" \ + "http://localhost:8086/rest/wikis/xwiki/spaces/DeskDeskKnowledge/spaces/$1/pages/$2" \ + -o /dev/null -w "%{http_code} $1/$2\n" +} +put East Etiquette "East zone etiquette" \ + "The east windows get direct sun 14:00 to 17:00 in summer. Bring a curtain clip from the supplies cabinet on floor 3. Phone calls are fine in this zone, phonebooths are a 30-second walk away." +put Central MeetingDesks "Central zone meeting desks" \ + "Central desks 11 to 14 seat four. The cabling channel under each desk has two HDMI ports and USB-C 100W power. Camera and mic in the ceiling are wired into the in-room Jitsi instance. See the QR code on the desk." +put West QuietRules "West zone quiet rules" \ + "The west zone is the designated quiet zone. No calls, no meetings. Keep notifications muted. The kitchen is on the opposite side of the floor for a reason." +``` + +Elke call geeft `201` terug. Surf naar `http://localhost:8086/xwiki/bin/view/DeskDeskKnowledge/East/Etiquette` om het East-artikel in de standaardweergave van xWiki te zien. Dezelfde inhoud is ook via REST op te halen: + +```bash +curl -u admin:admin1234 \ + http://localhost:8086/rest/wikis/xwiki/spaces/DeskDeskKnowledge/spaces/East/pages +``` + +Drie korte artikelen. Realistisch genoeg dat je ze in productie ook zo zou schrijven. Het belangrijkste stuk is de **subspace-naam**: dat is waar we vanuit DeskDesk op pivoten. + +Waarom de tutorial je het schrijven van artikelen laat doen in plaats van ze programmatisch te seeden: in echte teams zijn wiki-artikelen *organisch* — geschreven door wie het etiquette-gat opmerkt, bewerkt door de volgende persoon, gearchiveerd als de plattegrond verandert. De integratie die we bouwen maakt ze vindbaar vanaf waar de gebruiker al staat, niet andersom. + +## Stap 2: Voeg een knowledge_article-schema toe aan DeskDesk + +Voordat we vanuit xWiki gaan synchroniseren, geef je OpenRegister een plek om de artikelen op te slaan. Voeg een vierde schema toe aan `lib/Settings/deskdesk_register.json`: + +```json title="lib/Settings/deskdesk_register.json (excerpt)" +"knowledge_article": { + "slug": "knowledge_article", + "icon": "BookOpenVariantOutline", + "version": "0.1.0", + "title": "Knowledge article", + "description": "An external knowledge article surfaced in DeskDesk via OpenConnector. Read-only — canonical source lives in xWiki.", + "type": "object", + "required": ["name", "zone"], + "properties": { + "name": { "type": "string" }, + "zone": { "type": "string", "enum": ["north", "south", "east", "west", "central"] }, + "body": { "type": "string" }, + "url": { "type": "string", "format": "uri" }, + "externalId": { "type": "string" } + } +} +``` + +Belangrijk: de **JSON-key** en het **`slug`-veld moeten matchen** (`knowledge_article` aan beide kanten). OpenRegister bouwt het REST-endpoint vanuit de slug, dus een mismatch levert `Schema not found: 'knowledge_article'` op bij ophalen. + +Bump terwijl je daar bent ook de `info.version` van het register naar `0.3.0`, zodat de importhandler het nieuwe schema bij de volgende run oppikt: + +```json title="lib/Settings/deskdesk_register.json (info block)" +"info": { + "title": "DeskDesk Register", + "description": "Floors, desks, bookings, and knowledge articles.", + "version": "0.3.0" +} +``` + +Seed ook drie artikelen die matchen met de xWiki-inhoud. De seeds maken de app demo-baar voordat je de sync hebt aangesloten, en dienen als referentie-vorm voor de OpenConnector-mapping die je in Stap 3 bouwt. + +```json title="components.objects (excerpt)" +"knowledge-east-etiquette": { + "@self": { "register": "deskdesk", "schema": "knowledge_article", "slug": "knowledge-east-etiquette" }, + "name": "East zone etiquette", + "zone": "east", + "body": "The east windows get direct sun 14:00 to 17:00 in summer...", + "url": "http://localhost:8086/xwiki/bin/view/DeskDeskKnowledge/East/Etiquette", + "externalId": "xwiki:DeskDeskKnowledge.East.Etiquette" +} +``` + +Voeg de `knowledge_article`-slug toe aan de kleine lijst `SettingsService::SCHEMA_SLUGS` in [lib/Service/SettingsService.php](https://github.com/ConductionNL/deskdesk/blob/main/lib/Service/SettingsService.php), zodat `/api/settings` het numerieke id naast de andere schema's blootlegt. Hetzelfde in `src/store/store.js`: + +```js title="src/store/store.js" +const SCHEMAS = ['floor', 'desk', 'booking', 'knowledge_article'] +``` + +Herlaad de configuratie zodat OpenRegister het nieuwe schema + de seeds oppikt: + +```bash +docker exec nextcloud apache2ctl graceful +# then from your Nextcloud session, POST /apps/deskdesk/api/settings/load +# (the deskdesk admin page has a Reload button that does this for you) +``` + +## Stap 3: Maak een OpenConnector-source voor xWiki aan + +De seeds krijgen de UI meteen aan de praat. Voor *live* data — als een artikel in xWiki verandert wil je die wijziging bij de volgende sync in DeskDesk zien — sluit je OpenConnector aan. + +Ga naar `/apps/openconnector/sources` en maak een nieuwe source. + +``` +Name: xWiki Knowledge +Type: API +Location: http://openregister-xwiki:8080/rest + (container-to-container hostname; from the host you use http://localhost:8086/rest) +Auth: Basic +Username: admin +Password: admin1234 +Headers: Accept: application/json +Test: GET /wikis/xwiki/spaces/DeskDeskKnowledge/spaces/East/pages +``` + +De Test zou het East-artikel moeten teruggeven. OpenConnector slaat de source op met een numeriek id; we refereren ernaar vanuit de synchronisation in de volgende stap. + +## Stap 4: Definieer de synchronisation + +Sources tonen ruwe API-responses; **synchronisations** mappen ze naar OpenRegister-objecten. Maak een synchronisation aan: + +``` +Name: xWiki articles → OpenRegister +Source: xWiki Knowledge (the one you just made) +Source endpoint: /wikis/xwiki/spaces/DeskDeskKnowledge/spaces/{zone}/pages + with zone iterated over ['East', 'Central', 'West'] +Target register: deskdesk +Target schema: knowledge_article +Mapping: + title → name + content → body + spaces.last → zone (lowercased) + id → externalId + xwikiAbsoluteUrl → url +Schedule: every 30 minutes +``` + +Sla op en draai één keer. Je ziet drie `knowledge_article`-objecten in OpenRegister waarvan het `externalId`-veld begint met `xwiki:`. Verwijder de seed-objecten die je in Stap 2 toevoegde: ze worden vervangen door de gesyncde versies. + +## Stap 5: Toon artikelen in CnObjectSidebar + +De desk-detail-pagina mount al een CnObjectSidebar. Declareer een custom tab in het manifest om er een **Kennis-tab** aan toe te voegen: + +```json title="src/manifest.json (excerpt)" +{ + "id": "desks-detail", + "route": "/desks/:id", + "type": "detail", + "title": "deskdesk.desks.detailTitle", + "config": { + "register": "deskdesk", + "schema": "desk", + "sidebar": { + "tabs": [ + { "id": "data", "label": "deskdesk.sidebar.data", "widgets": [{ "type": "data" }] }, + { "id": "knowledge", "label": "deskdesk.sidebar.knowledge", "component": "knowledge-tab" }, + { "id": "metadata", "label": "deskdesk.sidebar.metadata", "widgets": [{ "type": "metadata" }] } + ] + } + } +} +``` + +De `data`- en `metadata`-tabs gebruiken ingebouwde widget-types. De `knowledge`-tab wijst naar een custom component `knowledge-tab`, dat de App registreert via de `customComponents`-map. + +Maak het tab-component zelf aan op `src/views/KnowledgeTab.vue`: + +```vue title="src/views/KnowledgeTab.vue" + + + +``` + +Registreer het component in `src/App.vue` en geef het door aan `CnAppRoot` via de `:custom-components`-prop: + +```vue title="src/App.vue (excerpt)" + + + +``` + +(Zie de [reference repo](https://github.com/ConductionNL/deskdesk/blob/main/src/App.vue) voor het volledige bestand. App.vue voorziet ook in `objectSidebarState`, zodat het externe-sidebar-kanaal van CnDetailPage werkt.) + +Bouw, herlaad en navigeer naar de detail-URL van een 3-East-desk. De rechter sidebar toont drie tabs: Data, Kennis, Metadata. Klik op **Kennis**. Het artikel "East zone etiquette" verschijnt, met de body gerenderd en een link terug naar de wiki om te bewerken. + +Navigeer naar een 2-West-desk: zelfde tab, ander artikel ("West zone quiet rules"). De zonefilter doet de match gratis voor je; je hoefde geen per-desk bedrading te schrijven. + +## Stap 6: Package de app + +Het release-proces is mechanisch: + +1. **Bump de versie** in `appinfo/info.xml` (`0.1.0` → `0.4.0`). +2. **Draai de production build:** + ```bash + composer install --no-dev --optimize-autoloader + npm install --legacy-peer-deps + npm run build + ``` +3. **Strip ongewenste bestanden** — vendor-dev-dependencies, node_modules, tests, openspec-changes, .git, .github (je CI staat al op source-niveau). Het template levert een `Makefile`-target hiervoor; anders is het patroon voor de productie-tarball: + ```bash + tar --exclude=node_modules --exclude=vendor/bin \ + --exclude=tests --exclude=openspec/changes \ + --exclude=.git --exclude=.github \ + -czf deskdesk-0.4.0.tar.gz deskdesk/ + ``` +4. **Onderteken de tarball** met de EC-key die de Nextcloud app store verwacht (zie [de Nextcloud-docs](https://nextcloudappstore.readthedocs.io/en/latest/developer.html#publishing-your-app)). +5. **Push naar GitHub** met een tag die overeenkomt met de versie (`git tag v0.4.0 && git push origin v0.4.0`). Het template levert een `.github/workflows/release.yml` die de tag oppikt, de tarball bouwt, ondertekent en de release publiceert. + +Voor de **Conduction app store** (`apps.conduction.nl`) duwt dezelfde release-pipeline naar dat endpoint als de secret is geconfigureerd. Zie [de .github/workflows/release.yml](https://github.com/ConductionNL/nextcloud-app-template/blob/main/.github/workflows/release.yml) als referentie. + +## Stap 7: Indienen bij apps.nextcloud.com (optioneel) + +Als je DeskDesk in de publieke Nextcloud app store wilt, registreer je je op [apps.nextcloud.com](https://apps.nextcloud.com), upload je je ondertekende tarball, vul je het formulier in (beschrijving, screenshots, licentie — EUPL-1.2 is prima — categorieën `organization`) en wacht je op review. De cyclus is meestal 1 tot 3 dagen voor een nieuwe app. + +## Wat je hebt gebouwd + +Je begon Deel 1 met een leeg template. Achtenveertig stappen verder heb je: + +- Een echte Nextcloud-app met het canonieke chassis +- Vier OpenRegister-schema's met relaties en seed-data +- Een manifest-gedreven shell met twee kleine wrappers (Index, Detail) die verdwijnen zodra de library auto-fetch in de default page-types verdraagt +- Boekingen die via een schema-annotatie in de NC Agenda verschijnen +- Kennisartikelen uit xWiki die via OpenConnector in de desk-detail-sidebar verschijnen +- Een geversioneerde, gepackagede, publiceerbare release + +Totaal aan geschreven Vue: **drie kleine bestanden** (`IndexPageWrapper`, `DetailPageWrapper`, `KnowledgeTab`), samen ongeveer 100 regels. Totaal PHP: het rename-ritueel, een pad-resolutie-fix in SettingsService en de schema-slug-lijst. Al het andere is JSON. + +Die verhouding — JSON beschrijft wat je wil, het framework rendert het — is het hele punt van de Conduction-stack. Schema's sturen zowel backend als frontend. Manifesten sturen de shell. Schema-annotaties sturen cross-app-integraties. Jij declareert; het framework rendert. + +## Troubleshooting + + + De JSON-key en het slug-veld van het schema moeten matchen. Als je knowledge-article (streepje) voor de een gebruikte en knowledge_article (underscore) voor de ander, faalt OpenRegister's URL-lookup. Kies één, gebruik die op beide plekken. + + + + CnPageRenderer geeft page.config en $route.params als ruwe props door. De route-param heet id, maar de prop van CnDetailPage heet objectId. De DetailPageWrapper overbrugt de namen en haalt het object via de store op; zonder de wrapper rendert CnDetailPage alleen de empty state. + + + + Kies Let the wiki be empty in Stap 2. De lege flavor ondersteunt nog steeds REST + pagina-creatie, en dat is alles wat de tutorial nodig heeft. + + + + Zet Accept: application/json in het Headers-veld van de source. xWiki staat voor REST-responses standaard op XML, maar onderhandelt JSON wanneer je erom vraagt. + + +## Waar nu naartoe + + + + + + From 076b710821012f468bf7e715ef07d2a7c881d3df Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 12:36:42 +0200 Subject: [PATCH 038/330] feat(i18n): flip 3 NL-native academy posts to EN canonical with NL translations (#77) The woo-bestanden-uploaden, woo-register-opzetten, and nextcloud- lokaal-draaien academy posts were written natively in Dutch but served under the canonical English URL (/academy//), which Google flagged as content-language mismatch. Restructured: 1. Original Dutch content moved to i18n/nl/...//index.mdx (preserved verbatim as the NL translation) 2. Fresh EN translation written at academy//index.mdx (now serves the canonical /academy// URL) Net result: both /academy// (EN) and /nl/academy// (NL) serve their proper language. Completes the bilingual academy surface that wave 2 (#68, #69, #70) started. Part of ConductionNL/.github#77. --- .../index.mdx | 152 ++++---- .../index.mdx | 208 +++++------ .../index.mdx | 158 ++++---- .../index.mdx | 230 ++++++++++++ .../index.mdx | 348 ++++++++++++++++++ .../index.mdx | 225 +++++++++++ 6 files changed, 1062 insertions(+), 259 deletions(-) create mode 100644 i18n/nl/docusaurus-plugin-content-blog/2026-05-07-nextcloud-lokaal-draaien/index.mdx create mode 100644 i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-bestanden-uploaden/index.mdx create mode 100644 i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/index.mdx diff --git a/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx b/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx index 07395d416..465a9a3da 100644 --- a/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx +++ b/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx @@ -1,61 +1,61 @@ --- slug: nextcloud-lokaal-draaien -title: Nextcloud lokaal draaien met Docker +title: Run Nextcloud locally with Docker contentType: tutorial authors: [conduction] date: 2026-05-07 -summary: Drie commando's, vier stappen, een kwartier werk. Daarna draait er een schone Nextcloud op je laptop, klaar om Conduction-apps op te installeren. -tags: [Nextcloud, Docker, Setup, Demo, Lokaal] +summary: Three commands, four steps, a quarter of an hour. After that you have a clean Nextcloud running on your laptop, ready for Conduction apps. +tags: [Nextcloud, Docker, Setup, Demo, Local] durationMinutes: 15 --- import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, ContactCta} from '@conduction/docusaurus-preset/components'; -Voor elke andere tutorial in deze academy heb je een werkende Nextcloud nodig. Dit is de snelste route: drie commando's en je draait de officiële Nextcloud-image lokaal, identiek aan productie. Geen sales-call, geen demo-omgeving die offline kan, geen custom Dockerfile. Daarna installeer je de Conduction-apps via de Nextcloud app store, hetzelfde pad als productie. +Every other tutorial in this academy needs a working Nextcloud. This is the fastest route: three commands and you run the official Nextcloud image locally, identical to production. No sales call, no demo environment that can go offline, no custom Dockerfile. After that you install the Conduction apps from the Nextcloud app store, the same path as production. {/* truncate */} - - Een werkende Nextcloud op http://localhost:8080 opzetten met Docker. - Postgres voor data, Redis voor cache, Nextcloud voor het werk en notify_push voor realtime updates in één compose-bestand combineren. - Een admin-account aanmaken waar je naar believen Conduction-apps op installeert. - Met één commando de hele stack weer opruimen. + + How to run a working Nextcloud on http://localhost:8080 with Docker. + How to combine Postgres for data, Redis for cache, Nextcloud for the work, and notify_push for realtime updates in one compose file. + How to create an admin account where you can install Conduction apps at will. + How to tear down the whole stack with one command. -Daarna kun je verder met [Een Woo-register opzetten](/academy/woo-register-opzetten), [Bestanden uploaden bij een Woo-publicatie](/academy/woo-bestanden-uploaden), of een eigen experiment. +After this you can move on to [Set up a Woo register](/academy/woo-register-opzetten), [Upload files to a Woo publication](/academy/woo-bestanden-uploaden), or your own experiment. - - Een laptop met minimaal 4 GB vrij geheugen. - Docker, lokaal draaiend. - Een terminal en een teksteditor. + + A laptop with at least 4 GB of free memory. + Docker, running locally. + A terminal and a text editor. -Op een schone laptop is stap 0 (Docker installeren) ongeveer tien minuten. Daarna is stap 1 t/m 3 samen een kwartier. +On a clean laptop, step 0 (installing Docker) takes about ten minutes. After that, steps 1 through 3 together take a quarter of an hour. -## Stap 0: Zorg dat Docker draait +## Step 0: Make sure Docker is running -Eenmalig. +One-time setup. -- **Mac of Linux:** installeer [Docker Desktop](https://docs.docker.com/get-docker/). -- **Windows:** zet eerst [WSL 2](https://learn.microsoft.com/nl-nl/windows/wsl/install) aan en installeer daarna Docker Desktop. +- **Mac or Linux:** install [Docker Desktop](https://docs.docker.com/get-docker/). +- **Windows:** first enable [WSL 2](https://learn.microsoft.com/en-us/windows/wsl/install), then install Docker Desktop. -Test of het werkt: +Check that it works: ```bash docker --version docker compose version ``` -Allebei moeten een versie teruggeven, geen foutmelding. +Both should return a version, not an error. -## Stap 1: Maak een werkmap en het compose-bestand +## Step 1: Create a working directory and the compose file ```bash mkdir conduction-demo cd conduction-demo ``` -Sla het volgende op als `docker-compose.yml` in die map: +Save the following as `docker-compose.yml` in that directory: ```yaml title="docker-compose.yml" services: @@ -108,49 +108,49 @@ volumes: nextcloud: ``` -Vier services, allemaal officiële images, geen custom build. De `./apps`-bind-mount is optioneel: handig als je een Conduction-app als zip naast de installatie wilt zetten in plaats van via de app store. `notify_push` is de WebSocket push-server die Conduction-apps gebruiken voor realtime updates — meer daarover in stap 5. +Four services, all official images, no custom build. The `./apps` bind mount is optional. It is handy when you want to drop a Conduction app next to the installation as a zip instead of going through the app store. `notify_push` is the WebSocket push server that Conduction apps use for realtime updates. More on that in step 5. -## Stap 2: Start de stack +## Step 2: Start the stack ```bash docker compose up -d ``` -De eerste keer trekt Docker ongeveer 750 MB aan images binnen. Daarna start de stack in seconden. +The first time, Docker pulls about 750 MB of images. After that the stack starts in seconds. -Volg de logs terwijl Nextcloud zichzelf bootstrapt: +Follow the logs while Nextcloud bootstraps itself: ```bash docker compose logs -f nextcloud ``` -Wacht tot je `apache2 -D FOREGROUND` of vergelijkbaar ziet, en `Ctrl+C` om de log-volger af te sluiten. De containers blijven draaien. +Wait until you see `apache2 -D FOREGROUND` or similar, then `Ctrl+C` to exit the log follower. The containers keep running. -## Stap 3: Voltooi de Nextcloud setup-wizard +## Step 3: Finish the Nextcloud setup wizard -Open `http://localhost:8080` in je browser. Nextcloud toont een setup-wizard: +Open `http://localhost:8080` in your browser. Nextcloud shows a setup wizard: -1. Vul een **admin-naam** en **wachtwoord** in. `admin` en `admin` is prima voor lokaal werk; in productie natuurlijk niet. -2. Onder **Storage & database** kies je niets, want de Postgres-config zit al in `docker-compose.yml`. -3. Klik op **Install**. +1. Fill in an **admin name** and **password**. `admin` and `admin` is fine for local work. In production it is not. +2. Under **Storage & database**, pick nothing. The Postgres config is already in `docker-compose.yml`. +3. Click **Install**. -Na een halve minuut land je op het dashboard, ingelogd als admin. +After half a minute, you land on the dashboard, signed in as admin. -## Stap 4: Installeer de Conduction-apps +## Step 4: Install the Conduction apps -Klik je avatar rechtsboven en kies **Apps**. De Conduction-apps staan onder **Integration** en **Office & text**. Onze suggestie voor een eerste demo: +Click your avatar in the top right and choose **Apps**. The Conduction apps are listed under **Integration** and **Office & text**. Our suggestion for a first demo: -- **OpenRegister.** De typed-data foundation. Installeer deze als eerste, alle andere apps lezen ervan af. -- **OpenCatalogi.** Maakt elk register doorzoekbaar als publieke entry. Federation naar `data.overheid.nl`. -- **OpenConnector.** Trekt voorbeelddata binnen via REST, SOAP en file-drops. +- **OpenRegister.** The typed-data foundation. Install this first. Every other app reads from it. +- **OpenCatalogi.** Makes every register searchable as a public entry. Federates to `data.overheid.nl`. +- **OpenConnector.** Pulls in sample data via REST, SOAP, and file drops. -Na elke install draait de schema-bootstrap automatisch. Voorbeelddata is binnen seconden zichtbaar. +After every install, the schema bootstrap runs automatically. Sample data is visible within seconds. -Installeer ook de `notify_push`-app (onder **Integration**). Die is de Nextcloud-zijde van de WebSocket push-server die je in stap 1 al hebt gestart — je hoeft hem alleen te enabelen, geen verdere config nodig. +Also install the `notify_push` app (under **Integration**). It is the Nextcloud side of the WebSocket push server you already started in step 1. You only have to enable it. No further config needed. -## Stap 5: Koppel `notify_push` aan de push-server +## Step 5: Wire `notify_push` to the push server -`notify_push` heeft één eenmalige setup-stap zodat de Nextcloud-app weet waar de Rust-binary draait. Voer in de werkmap uit: +`notify_push` has one one-off setup step so the Nextcloud app knows where the Rust binary runs. Run this in your working directory: ```bash docker compose exec --user www-data nextcloud php occ config:system:set redis host --value redis @@ -160,71 +160,71 @@ docker compose exec --user www-data nextcloud php occ config:system:set trusted_ docker compose exec --user www-data nextcloud php occ notify_push:setup http://notify_push:7867 ``` -De eerste drie regels vertellen Nextcloud dat Redis de gedeelde cache is — `notify_push` luistert daarop voor change-events. De vierde regel markeert het Docker-netwerk als trusted zodat de push-server bij Nextcloud terugmag bellen voor authenticatie. De laatste regel registreert de URL bij de Nextcloud-app. +The first three lines tell Nextcloud that Redis is the shared cache. `notify_push` listens on it for change events. The fourth line marks the Docker network as trusted so the push server can call back to Nextcloud for authentication. The last line registers the URL with the Nextcloud app. -Verifieer dat alles werkt: +Verify everything works: ```bash docker compose exec --user www-data nextcloud php occ notify_push:self-test ``` -Je krijgt een lijstje vinkjes — `redis is configured`, `push server is receiving redis messages`, `push server can connect to the Nextcloud server`, et cetera. De waarschuwing over `unencrypted http` is voor lokaal werk normaal; in productie zit `notify_push` achter dezelfde reverse proxy als Nextcloud, dus dan is dat geen issue. +You get a list of checkmarks: `redis is configured`, `push server is receiving redis messages`, `push server can connect to the Nextcloud server`, and so on. The warning about `unencrypted http` is normal for local work. In production, `notify_push` sits behind the same reverse proxy as Nextcloud, so it is not an issue there. -Vanaf nu krijgen apps die op `notify_push` luisteren — zoals OpenRegister's live-update-integratie via `@conduction/nextcloud-vue` — direct doorduwingen op object-wijzigingen, zonder dat de browser hoeft te pollen. +From now on, apps that listen to `notify_push` (like OpenRegister's live-update integration via `@conduction/nextcloud-vue`) get direct pushes on object changes. The browser does not have to poll. -## Wat je krijgt +## What you get -| Eigenschap | Waarde | +| Property | Value | | --- | --- | -| Eerste boot | Ongeveer drie minuten (eerste keer 750 MB download) | -| Volgende boots | Seconden | -| Image | Officiële `nextcloud:latest`, identiek aan productie | -| Geheugen | Rond 1 GB onder lichte load | -| Reset | `docker compose down -v` (verwijdert volumes) | +| First boot | About three minutes (first run downloads 750 MB) | +| Subsequent boots | Seconds | +| Image | Official `nextcloud:latest`, identical to production | +| Memory | Around 1 GB under light load | +| Reset | `docker compose down -v` (removes volumes) | -Geen vooraf opgenomen screencast, geen demo-omgeving die offline kan. Wat je hier ziet, is precies wat een echte klant ziet. +No pre-recorded screencast, no demo environment that can go offline. What you see here is exactly what a real customer sees. -## Reset of stoppen +## Reset or stop ```bash -# tijdelijk stoppen, data behouden +# temporarily stop, keep data docker compose stop -# weer starten +# start again docker compose start -# helemaal opruimen, ook volumes +# tear down completely, including volumes docker compose down -v ``` -Na `down -v` is je laptop weer schoon. Twee minuten later kun je opnieuw beginnen. +After `down -v`, your laptop is clean again. Two minutes later, you can start over. -## Probleemoplossing +## Troubleshooting -**Apache port 8080 in gebruik:** wijzig `"8080:80"` naar bijvoorbeeld `"8081:80"` in `docker-compose.yml`. Vergeet niet de URL aan te passen naar `http://localhost:8081`. +**Apache port 8080 in use:** change `"8080:80"` to something like `"8081:80"` in `docker-compose.yml`. Remember to update the URL to `http://localhost:8081`. -**Setup-wizard wil opnieuw beginnen:** waarschijnlijk is de Postgres-database leeg geraakt. Voer `docker compose down -v` uit en start opnieuw met `docker compose up -d`. +**Setup wizard wants to start over:** the Postgres database probably emptied out. Run `docker compose down -v` and start again with `docker compose up -d`. -**Nextcloud toont "Trusted domain"-foutmelding:** voeg de host toe aan `NEXTCLOUD_TRUSTED_DOMAINS` (komma-gescheiden) en herstart met `docker compose up -d`. +**Nextcloud shows a "Trusted domain" error:** add the host to `NEXTCLOUD_TRUSTED_DOMAINS` (comma-separated) and restart with `docker compose up -d`. -**App-store install werkt niet:** controleer of Nextcloud naar buiten kan. In een corporate netwerk soms blokkade op `apps.nextcloud.com`. Een proxy via `HTTP_PROXY`-env-variabelen lost dit op. +**App store install does not work:** check that Nextcloud can reach the internet. Corporate networks sometimes block `apps.nextcloud.com`. A proxy via `HTTP_PROXY` env variables fixes this. -**`notify_push` self-test faalt op `is not trusted as a reverse proxy`:** het Docker-netwerk-bereik in jouw setup wijkt af van `172.16.0.0/12`. Bekijk het bereik met `docker network inspect $(docker network ls --filter name=conduction-demo -q) --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'` en pas de `trusted_proxies`-regel uit stap 5 aan met die subnet. +**`notify_push` self-test fails on `is not trusted as a reverse proxy`:** the Docker network range in your setup differs from `172.16.0.0/12`. Inspect the range with `docker network inspect $(docker network ls --filter name=conduction-demo -q) --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'` and update the `trusted_proxies` line from step 5 with that subnet. -**`notify_push` blijft restarten na `docker compose up -d`:** kijk in de logs (`docker compose logs notify_push`) of je `× No redis server is configured` ziet. Dat betekent dat Nextcloud's `memcache.distributed`-config nog niet op Redis staat — voer de eerste drie `occ config:system:set`-commando's uit stap 5 uit, en herstart dan met `docker compose up -d notify_push`. +**`notify_push` keeps restarting after `docker compose up -d`:** check the logs (`docker compose logs notify_push`) for `× No redis server is configured`. That means Nextcloud's `memcache.distributed` config is not on Redis yet. Run the first three `occ config:system:set` commands from step 5, then restart with `docker compose up -d notify_push`. -## Volgende stap +## Next step -Een werkende Nextcloud is de aanleiding voor de andere tutorials in deze serie: +A working Nextcloud is the entry point for the other tutorials in this series: -- [Een Woo-register opzetten](/academy/woo-register-opzetten) — importeer het canonieke Woo-register in OpenRegister -- [Bestanden uploaden bij een Woo-publicatie](/academy/woo-bestanden-uploaden) — vier upload-modes voor documenten -- [Build je eerste Conduction-app](/academy/build-an-app) — eindigt met een geïnstalleerde app op deze stack +- [Set up a Woo register](/academy/woo-register-opzetten): import the canonical Woo register into OpenRegister +- [Upload files to a Woo publication](/academy/woo-bestanden-uploaden): four upload modes for documents +- [Build your first Conduction app](/academy/build-an-app): ends with an installed app on this stack -Stuur het `docker-compose.yml` naar een collega als je dit samen wilt zien werken. +Send the `docker-compose.yml` to a colleague if you want to see this work together. diff --git a/academy/2026-05-07-woo-bestanden-uploaden/index.mdx b/academy/2026-05-07-woo-bestanden-uploaden/index.mdx index 37d2799fb..4833b4077 100644 --- a/academy/2026-05-07-woo-bestanden-uploaden/index.mdx +++ b/academy/2026-05-07-woo-bestanden-uploaden/index.mdx @@ -1,64 +1,64 @@ --- slug: woo-bestanden-uploaden -title: Bestanden bij een Woo-publicatie uploaden +title: Upload files to a Woo publication contentType: tutorial authors: [conduction] date: 2026-05-07 -summary: Vier manieren om documenten aan een Woo-publicatie te koppelen, van één PDF tot een 6 MB rapport in chunks. Met geteste API-aanroepen. -tags: [Woo, OpenRegister, Bestanden, Upload, Publicaties] +summary: Four ways to attach documents to a Woo publication, from one PDF to a 6 MB report in chunks. With tested API calls. +tags: [Woo, OpenRegister, Files, Upload, Publications] apps: [openregister, openwoo] durationMinutes: 18 --- import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, Troubleshooting, TroubleshootingItem, NextSteps, NextStep, ContactCta} from '@conduction/docusaurus-preset/components'; -Een Woo-publicatie zonder bestanden is metadata zonder bewijs. In deze tutorial koppel je documenten aan een Woo-publicatie via vier upload-modes: één bestand, meerdere bestanden tegelijk, en grote bestanden in chunks. Plus publiceren, depubliceren en opruimen. +A Woo publication without files is metadata without evidence. In this tutorial you attach documents to a Woo publication through four upload modes: one file, multiple files at once, and large files in chunks. Plus publish, unpublish, and clean up. {/* truncate */} - - Vier upload-modes voor documenten bij een Woo-publicatie, van klein naar groot. - Wanneer je JSON+base64, multipart of WebDAV chunked upload gebruikt. - Hoe je bestanden publiceert, depubliceert en weer opruimt. + + Four upload modes for documents on a Woo publication, from small to large. + When to use JSON+base64, multipart, or WebDAV chunked upload. + How to publish, unpublish, and clean up files. - + - Een werkend Woo-register. Volg eerst Een Woo-register opzetten als je dat nog niet hebt. + A working Woo register. Follow Set up a Woo register first if you do not have one yet. - Een publicatie-object met bekende UUID. - curl, basisauth, en een paar testbestanden. + A publication object with a known UUID. + curl, basic auth, and a few test files. -In de voorbeelden gebruiken we: +In the examples we use: - Host: `http://localhost:8080` - Auth: `admin:admin` -- Register-slug: `woo` -- Schema-slug: `onderzoeksrapporten` -- Object-UUID: `3422e6cd-1ba5-478c-b842-8f206f9d7358` +- Register slug: `woo` +- Schema slug: `onderzoeksrapporten` +- Object UUID: `3422e6cd-1ba5-478c-b842-8f206f9d7358` -Vervang het object-UUID door de jouwe. +Replace the object UUID with your own. -![Het Woo-register met de geïmporteerde TOOI-schemas](./01-woo-register-context.png) +![The Woo register with the imported TOOI schemas](./01-woo-register-context.png) -## Het basispatroon +## The base pattern -Alle bestanden worden gekoppeld aan een specifiek object. De URL volgt altijd hetzelfde patroon: +All files are attached to a specific object. The URL always follows the same pattern: ``` POST /index.php/apps/openregister/api/objects/{register}/{schema}/{id}/files ``` -OpenRegister maakt automatisch een map aan voor het object onder `Open Registers/{Register Title}/{Object UUID}/`. Daar landen alle bestanden. De map ontstaat zodra je het eerste bestand uploadt. +OpenRegister automatically creates a folder for the object under `Open Registers/{Register Title}/{Object UUID}/`. All files land there. The folder appears as soon as you upload the first file. -## Mode 1: één bestand via JSON en base64 +## Mode 1: one file via JSON and base64 -De eenvoudigste route, prima voor kleine documenten tot enkele megabytes: +The simplest route, fine for small documents up to a few megabytes: ```bash OBJ=3422e6cd-1ba5-478c-b842-8f206f9d7358 -B64=$(echo "Inhoud van het rapport." | base64 -w0) +B64=$(echo "Report content." | base64 -w0) curl -u admin:admin \ -X POST "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten/$OBJ/files" \ @@ -71,7 +71,7 @@ curl -u admin:admin \ }" ``` -De respons: +The response: ```json { @@ -89,13 +89,13 @@ De respons: } ``` -`share: true` maakt direct een publieke share-link aan. De `accessUrl` is dan meteen de publieke landings-URL. Wil je het bestand vooralsnog privé houden, laat `share` weg of zet hem op `false`. +`share: true` creates a public share link right away. The `accessUrl` is then the public landing URL. If you want to keep the file private for now, leave `share` out or set it to `false`. -**Wanneer wel:** scripts en synchronisaties waar je tekstinhoud genereert, kleine PDF's, of CI-pipelines. **Wanneer niet:** bestanden boven enkele MB. Base64 maakt de payload 33% groter en je raakt al snel de PHP `post_max_size`-grens. +**When to use it:** scripts and synchronisations where you generate text content, small PDFs, or CI pipelines. **When not:** files above a few MB. Base64 makes the payload 33% larger and you quickly hit the PHP `post_max_size` limit. -## Mode 2: één bestand binair via multipart +## Mode 2: one binary file via multipart -Voor binaire bestanden (PDF, DOCX, afbeeldingen) gebruik je het `filesMultipart`-endpoint. Dat scheelt de base64-overhead en is sneller voor middelgrote bestanden. +For binary files (PDF, DOCX, images) you use the `filesMultipart` endpoint. It avoids the base64 overhead and is faster for mid-size files. ```bash OBJ=3422e6cd-1ba5-478c-b842-8f206f9d7358 @@ -103,11 +103,11 @@ OBJ=3422e6cd-1ba5-478c-b842-8f206f9d7358 curl -u admin:admin \ -X POST "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten/$OBJ/filesMultipart" \ -H "OCS-APIRequest: true" \ - -F "files[]=@/pad/naar/rapport.pdf" \ + -F "files[]=@/path/to/rapport.pdf" \ -F "share=true" ``` -De respons is een array met één element: +The response is an array with one element: ```json [ @@ -123,11 +123,11 @@ De respons is een array met één element: ] ``` -> **`files[]` werkt voor één en voor meerdere bestanden.** Het endpoint accepteert ook een enkelvoudig `file=`-veld, maar `files[]` is consistenter: precies dezelfde aanroep schaalt door naar Mode 3 hieronder zonder dat je je veldnaam hoeft aan te passen. +> **`files[]` works for one file and for multiple files.** The endpoint also accepts a single `file=` field, but `files[]` is more consistent. The exact same call scales to Mode 3 below without changing your field name. -## Mode 3: meerdere bestanden in één request +## Mode 3: multiple files in one request -Het `filesMultipart`-endpoint accepteert meerdere bestanden in één enkele request. Eén HTTP-call, één transactie, één respons-array. Handig voor publicaties met meerdere bijlagen. +The `filesMultipart` endpoint accepts multiple files in a single request. One HTTP call, one transaction, one response array. Handy for publications with multiple attachments. ```bash OBJ=3422e6cd-1ba5-478c-b842-8f206f9d7358 @@ -135,35 +135,35 @@ OBJ=3422e6cd-1ba5-478c-b842-8f206f9d7358 curl -u admin:admin \ -X POST "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten/$OBJ/filesMultipart" \ -H "OCS-APIRequest: true" \ - -F "files[]=@/pad/naar/bijlage-1.pdf" \ - -F "files[]=@/pad/naar/bijlage-2.pdf" \ - -F "files[]=@/pad/naar/bijlage-3.pdf" \ + -F "files[]=@/path/to/attachment-1.pdf" \ + -F "files[]=@/path/to/attachment-2.pdf" \ + -F "files[]=@/path/to/attachment-3.pdf" \ -F "share=true" ``` -Respons: +Response: ```json [ - { "id": 3308, "title": "bijlage-1.pdf", "size": 29, "hash": "b753b747...", ... }, - { "id": 3309, "title": "bijlage-2.pdf", "size": 32, "hash": "f370ad27...", ... }, - { "id": 3310, "title": "bijlage-3.pdf", "size": 28, "hash": "80a9a215...", ... } + { "id": 3308, "title": "attachment-1.pdf", "size": 29, "hash": "b753b747...", ... }, + { "id": 3309, "title": "attachment-2.pdf", "size": 32, "hash": "f370ad27...", ... }, + { "id": 3310, "title": "attachment-3.pdf", "size": 28, "hash": "80a9a215...", ... } ] ``` -Elk bestand krijgt een eigen entry in de respons. De `share`-vlag geldt voor het hele batch: alle bestanden worden tegelijk publiek gemaakt of houden tegelijk privé. +Every file gets its own entry in the response. The `share` flag applies to the whole batch. All files go public at the same time, or all stay private at the same time. -**Praktische limiet:** PHP's `post_max_size` is meestal 100 MB. Het totaal van alle bestanden in één request mag daar niet overheen. Voor grotere uploads spring je naar Mode 4. +**Practical limit:** PHP's `post_max_size` is usually 100 MB. The total of all files in one request must stay under that. For larger uploads, jump to Mode 4. -## Mode 4: grote bestanden in chunks via WebDAV +## Mode 4: large files in chunks via WebDAV -Bestanden boven de PHP-uploadlimiet (typisch 100 MB) splits je in chunks en verstuur je via Nextcloud's WebDAV chunked upload-protocol. Daarna verplaats je het samengestelde bestand naar de objectmap. +For files above the PHP upload limit (typically 100 MB) you split them into chunks and send them via Nextcloud's WebDAV chunked upload protocol. After that you move the assembled file to the object folder. -Dit is hetzelfde protocol dat de Nextcloud webclient en desktop-app gebruiken. Het werkt voor alle bestandsgroottes en past zich aan netwerkonderbrekingen aan. +This is the same protocol the Nextcloud web client and desktop app use. It works for any file size and adapts to network interruptions. -### Stap 4.1: maak een transfer-map +### Step 4.1: create a transfer folder -Geef de transfer een unieke ID, bijvoorbeeld een tijdstempel: +Give the transfer a unique ID, for example a timestamp: ```bash TRANSFER_ID="woo-chunked-$(date +%s)" @@ -173,14 +173,14 @@ curl -u admin:admin \ -X MKCOL "$DAV_BASE/uploads/admin/$TRANSFER_ID" ``` -Een 201 betekent dat de transfer-map klaar staat. +A 201 means the transfer folder is ready. -### Stap 4.2: split het bestand en upload de chunks +### Step 4.2: split the file and upload the chunks -We splitsen een testbestand van 6 MB in drie chunks van 2 MB: +We split a 6 MB test file into three 2 MB chunks: ```bash -split -b 2M /pad/naar/groot-rapport.pdf /tmp/chunk- +split -b 2M /path/to/large-report.pdf /tmp/chunk- i=1 for chunk in /tmp/chunk-*; do @@ -192,16 +192,16 @@ for chunk in /tmp/chunk-*; do done ``` -De chunks krijgen vijfcijferige nummers (`00001`, `00002`, `00003`). Houd de volgorde in de bestandsnamen aan, want Nextcloud assembleert op basis daarvan. +Chunks get five-digit numbers (`00001`, `00002`, `00003`). Keep the order in the file names, because Nextcloud assembles based on it. -### Stap 4.3: assembleer en plaats in de objectmap +### Step 4.3: assemble and place in the object folder -Een MOVE-call combineert de chunks tot één bestand op de uiteindelijke locatie: +A MOVE call combines the chunks into one file at the final location: ```bash OBJ=3422e6cd-1ba5-478c-b842-8f206f9d7358 -DEST="$DAV_BASE/files/admin/Open%20Registers/Woo%20Register/$OBJ/groot-rapport.pdf" -TOTAL_LENGTH=$(wc -c < /pad/naar/groot-rapport.pdf) +DEST="$DAV_BASE/files/admin/Open%20Registers/Woo%20Register/$OBJ/large-report.pdf" +TOTAL_LENGTH=$(wc -c < /path/to/large-report.pdf) curl -u admin:admin \ -X MOVE "$DAV_BASE/uploads/admin/$TRANSFER_ID/.file" \ @@ -209,9 +209,9 @@ curl -u admin:admin \ -H "OC-Total-Length: $TOTAL_LENGTH" ``` -Een 201 betekent dat het bestand op de bestemming staat. OpenRegister pikt het automatisch op: zodra je de files-lijst van het object opvraagt, staat het bestand erbij. +A 201 means the file is at its destination. OpenRegister picks it up automatically. As soon as you ask for the files list of the object, the file shows up. -### Stap 4.4: verifieer in OpenRegister +### Step 4.4: verify in OpenRegister ```bash curl -u admin:admin \ @@ -219,26 +219,26 @@ curl -u admin:admin \ -H "OCS-APIRequest: true" ``` -Je ziet het nieuwe bestand met zijn echte grootte: +You see the new file at its real size: ```json { "id": 3316, - "path": "/admin/files/Open Registers/Woo Register/3422e6cd-.../groot-rapport.pdf", - "title": "groot-rapport.pdf", + "path": "/admin/files/Open Registers/Woo Register/3422e6cd-.../large-report.pdf", + "title": "large-report.pdf", "size": 6291456, "hash": "d7d7eec53f6d70d1bdceea08c10d0bb1", "modified": "2026-05-07T05:14:34+00:00" } ``` -**Belangrijk:** chunks moeten naar de map van de actuele gebruiker (`admin` in dit voorbeeld). Een service-account upload je dus eerst in dat account; de MOVE plaatst het daarna in de gedeelde register-map. +**Important:** chunks have to go to the folder of the current user (`admin` in this example). For a service account, upload into that account first. The MOVE then places the file in the shared register folder. -## Publiceren en depubliceren +## Publish and unpublish -Een geüpload bestand staat standaard niet publiek (`accessUrl: null`). Bij Woo-publicaties wil je expliciet kiezen welke bestanden publiek zijn en welke niet, bijvoorbeeld omdat een bijlage privacygevoelig is en eerst gelakt moet worden. +An uploaded file is not public by default (`accessUrl: null`). For Woo publications you want to choose explicitly which files are public and which are not, for example because an attachment is privacy-sensitive and has to be redacted first. -Publiek maken: +Make public: ```bash FID=3306 @@ -247,7 +247,7 @@ curl -u admin:admin \ -H "OCS-APIRequest: true" ``` -In de respons verschijnt een `accessUrl`: +The response now has an `accessUrl`: ```json { @@ -258,7 +258,7 @@ In de respons verschijnt een `accessUrl`: } ``` -Weer privé maken: +Make private again: ```bash curl -u admin:admin \ @@ -266,19 +266,19 @@ curl -u admin:admin \ -H "OCS-APIRequest: true" ``` -Na depublicatie zijn `accessUrl` en `downloadUrl` weer `null` en is de share-link ingetrokken. +After unpublishing, `accessUrl` and `downloadUrl` are `null` again and the share link is revoked. -## Lijst, downloaden, verwijderen +## List, download, delete -| Actie | Methode | URL | +| Action | Method | URL | | --- | --- | --- | -| Lijst van bestanden bij een object | `GET` | `/api/objects/{register}/{schema}/{id}/files` | -| Detail van één bestand | `GET` | `/api/objects/{register}/{schema}/{id}/files/{fileId}` | -| Download (alle bestanden als zip) | `GET` | `/api/objects/{register}/{schema}/{id}/files/download` | -| Download direct via id | `GET` | `/api/files/{fileId}/download` | -| Verwijder één bestand | `DELETE` | `/api/objects/{register}/{schema}/{id}/files/{fileId}` | +| List of files on an object | `GET` | `/api/objects/{register}/{schema}/{id}/files` | +| Detail of one file | `GET` | `/api/objects/{register}/{schema}/{id}/files/{fileId}` | +| Download (all files as zip) | `GET` | `/api/objects/{register}/{schema}/{id}/files/download` | +| Download directly by id | `GET` | `/api/files/{fileId}/download` | +| Delete one file | `DELETE` | `/api/objects/{register}/{schema}/{id}/files/{fileId}` | -Voorbeeld voor een lijst: +Example for a list: ```bash curl -u admin:admin \ @@ -286,7 +286,7 @@ curl -u admin:admin \ -H "OCS-APIRequest: true" ``` -Verwijderen van één bestand: +Delete one file: ```bash curl -u admin:admin \ @@ -294,55 +294,55 @@ curl -u admin:admin \ -H "OCS-APIRequest: true" ``` -## Welke mode kies je wanneer? +## Which mode do you pick when? -| Mode | Beste voor | Limiet | Codecomplexiteit | +| Mode | Best for | Limit | Code complexity | | --- | --- | --- | --- | -| 1. JSON + base64 | Server-naar-server scripts, kleine PDF's | ≈ 75 MB ruwe inhoud (33% overhead op `post_max_size`) | Laag | -| 2. Multipart, één bestand | Browser-uploads, gewone PDF's en DOCX | `post_max_size`, meestal 100 MB | Laag | -| 3. Multipart, meerdere bestanden | Publicaties met meerdere bijlagen | Som van bestanden onder `post_max_size` | Laag | -| 4. WebDAV chunked + MOVE | Video, scans, datasets > 100 MB | Geen praktische bovengrens | Hoog | +| 1. JSON + base64 | Server-to-server scripts, small PDFs | About 75 MB raw content (33% overhead on `post_max_size`) | Low | +| 2. Multipart, one file | Browser uploads, regular PDFs and DOCX | `post_max_size`, usually 100 MB | Low | +| 3. Multipart, multiple files | Publications with multiple attachments | Sum of files under `post_max_size` | Low | +| 4. WebDAV chunked + MOVE | Video, scans, datasets above 100 MB | No practical upper bound | High | -Voor de meeste Woo-publicaties pak je Mode 2 of 3. Pas op het moment dat een bestand groter dan 100 MB binnenkomt, schakel je over naar Mode 4. +For most Woo publications you pick Mode 2 or 3. The moment a file larger than 100 MB shows up, you switch to Mode 4. - + - Verhoog upload_max_filesize en post_max_size in php.ini, of stap over op Mode 4. + Raise upload_max_filesize and post_max_size in php.ini, or switch to Mode 4. - - Controleer dat de Destination-header naar {'Open Registers/{Register Title}/{Object UUID}/'} wijst en dat de UUID overeenkomt met een bestaand object. OpenRegister scant alleen die map. + + Check that the Destination header points to {'Open Registers/{Register Title}/{Object UUID}/'} and that the UUID matches an existing object. OpenRegister scans only that folder. - - Controleer of share-links aanstaan in de Nextcloud admin (Settings → Sharing → "Allow apps to use the share API"). Zonder deze instelling kunnen publicaties geen publieke link krijgen. + + Check that share links are enabled in the Nextcloud admin (Settings -> Sharing -> "Allow apps to use the share API"). Without this setting, publications cannot get a public link. - + - De publicatie samen met andere apps tonen op een portaal. + Show the publication together with other apps on a portal. - Documenten automatisch laten lakken op privacygevoelige passages. + Automatically redact documents on privacy-sensitive passages. - Publicaties periodiek synchroniseren naar open.overheid.nl. + Periodically sync publications to open.overheid.nl. diff --git a/academy/2026-05-07-woo-register-opzetten/index.mdx b/academy/2026-05-07-woo-register-opzetten/index.mdx index f3051fcd1..116245e66 100644 --- a/academy/2026-05-07-woo-register-opzetten/index.mdx +++ b/academy/2026-05-07-woo-register-opzetten/index.mdx @@ -1,66 +1,66 @@ --- slug: woo-register-opzetten -title: Een Woo-register opzetten in OpenRegister +title: Set up a Woo register in OpenRegister contentType: tutorial authors: [conduction] date: 2026-05-07 -summary: Importeer het canonieke Woo-register in OpenRegister. Eén API-aanroep, tien TOOI-categorieën, klaar om publicaties op te slaan. -tags: [Woo, OpenRegister, Publicaties, TOOI, Open data] +summary: Import the canonical Woo register into OpenRegister. One API call, ten TOOI categories, ready to store publications. +tags: [Woo, OpenRegister, Publications, TOOI, Open data] apps: [openregister, openwoo] durationMinutes: 10 --- import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, ContactCta} from '@conduction/docusaurus-preset/components'; -De [Wet open overheid (Woo)](https://www.rijksoverheid.nl/onderwerpen/wet-open-overheid-woo) verplicht overheden om documenten openbaar te maken in elf informatiecategorieën. OpenRegister levert het opslagmodel daarvoor. In deze tutorial importeer je het canonieke Woo-register, met alle [TOOI](https://standaarden.overheid.nl/tooi)-categorieën, in een paar minuten. +The [Dutch Open Government Act (Woo)](https://www.rijksoverheid.nl/onderwerpen/wet-open-overheid-woo) requires government bodies to publish documents in eleven information categories. OpenRegister provides the storage model for that. In this tutorial you import the canonical Woo register, with all [TOOI](https://standaarden.overheid.nl/tooi) categories, in a few minutes. {/* truncate */} - - Eén register met de slug woo in je OpenRegister-installatie aanmaken. - Tien schemas importeren, één per TOOI-informatiecategorie. - Een werkend startpunt opzetten om publicaties aan te maken en bestanden te koppelen. + + How to create one register with the slug woo in your OpenRegister installation. + How to import ten schemas, one per Woo information category. + How to set up a working starting point for creating publications and attaching files. -De vervolgtutorial over bestanden uploaden bouwt hierop voort. +The follow-up tutorial on uploading files builds on this. - - Een draaiende Nextcloud met OpenRegister geïnstalleerd. Heb je dat nog niet, volg dan eerst Nextcloud lokaal draaien met Docker. - Een gebruiker met admin-rechten, of een gebruiker die registers mag aanmaken. - curl of een vergelijkbare HTTP-client. + + A running Nextcloud with OpenRegister installed. If you do not have that yet, first follow Run Nextcloud locally with Docker. + A user with admin rights, or a user allowed to create registers. + curl or a comparable HTTP client. -In de voorbeelden gebruiken we de lokale dev-omgeving op `http://localhost:8080` met `admin:admin`. Vervang dat door je eigen host en credentials. +In the examples we use the local dev environment at `http://localhost:8080` with `admin:admin`. Replace those with your own host and credentials. -## De canonieke bron +## The canonical source -Het Woo-register bestaat al, gepubliceerd door Conduction op GitHub: +The Woo register already exists, published by Conduction on GitHub: ``` https://raw.githubusercontent.com/ConductionNL/woo-website/main/website/static/oas/woo_register.json ``` -Het is een OpenAPI-bundel van 1.0.1, met één register (`woo`) en tien schemas. Elk schema bevat de [TOOI-metadata](https://standaarden.overheid.nl/diwoo/metadata/doc/0.9.1/diwoo-metadata-lijsten_xsd_Simple_Type_diwoo_scw_woo_informatiecategorieen) van één Woo-informatiecategorie: +It is an OpenAPI bundle of 1.0.1, with one register (`woo`) and ten schemas. Each schema contains the [TOOI metadata](https://standaarden.overheid.nl/diwoo/metadata/doc/0.9.1/diwoo-metadata-lijsten_xsd_Simple_Type_diwoo_scw_woo_informatiecategorieen) of one Woo information category: -| Slug | Categorie | +| Slug | Category | | --- | --- | -| `vergaderstukken_decentrale_overheden` | Vergaderstukken decentrale overheden | -| `subsidieverplichtingen_anders_dan_met_beschikking` | Subsidieverplichtingen zonder beschikking | -| `overige_besluiten_van_algemene_strekking` | Overige besluiten van algemene strekking | -| `organisatie_en_werkwijze` | Organisatie en werkwijze | -| `ontwerpen_van_wet_en_regelgeving_met_adviesaanvraag` | Ontwerpen van wet- en regelgeving | -| `onderzoeksrapporten` | Onderzoeksrapporten | -| `klachtoordelen` | Klachtoordelen | -| `wetten_en_algemeen_verbindende_voorschriften` | Wetten en algemeen verbindende voorschriften | -| `woo_verzoeken_en_besluiten` | Woo-verzoeken en -besluiten | -| `convenanten` | Convenanten | -| `agendas_en_besluitenlijsten_bestuurscolleges` | Agenda's en besluitenlijsten bestuurscolleges | -| `vergaderstukken_staten_generaal` | Vergaderstukken Staten-Generaal | -| `adviezen` | Adviezen | - -## Stap 1: Importeer het register - -OpenRegister heeft een `import/url`-endpoint dat een OpenAPI-bundel ophaalt en in één transactie omzet naar register, schemas en (optioneel) seed-objecten. +| `vergaderstukken_decentrale_overheden` | Meeting documents of decentralised authorities | +| `subsidieverplichtingen_anders_dan_met_beschikking` | Subsidy obligations without a decision | +| `overige_besluiten_van_algemene_strekking` | Other decisions of general scope | +| `organisatie_en_werkwijze` | Organisation and working methods | +| `ontwerpen_van_wet_en_regelgeving_met_adviesaanvraag` | Drafts of laws and regulations | +| `onderzoeksrapporten` | Research reports | +| `klachtoordelen` | Complaint rulings | +| `wetten_en_algemeen_verbindende_voorschriften` | Laws and generally binding regulations | +| `woo_verzoeken_en_besluiten` | Woo requests and decisions | +| `convenanten` | Covenants | +| `agendas_en_besluitenlijsten_bestuurscolleges` | Agendas and decision lists of executive boards | +| `vergaderstukken_staten_generaal` | Meeting documents of the States General | +| `adviezen` | Advisory reports | + +## Step 1: Import the register + +OpenRegister has an `import/url` endpoint that fetches an OpenAPI bundle and converts it in one transaction into register, schemas, and (optionally) seed objects. ```bash curl -u admin:admin \ @@ -72,7 +72,7 @@ curl -u admin:admin \ }' ``` -Als alles goed gaat, krijg je terug: +If all goes well, you get back: ```json { @@ -87,11 +87,11 @@ Als alles goed gaat, krijg je terug: } ``` -`registersCount: 1` en `schemasCount: 10` bevestigen dat alles is aangemaakt. De `configurationId` heb je later nodig als je de bron wilt re-syncen. +`registersCount: 1` and `schemasCount: 10` confirm that everything was created. The `configurationId` is what you need later if you want to re-sync the source. -## Stap 2: Controleer wat is aangemaakt +## Step 2: Verify what was created -Vraag de lijst van registers op en filter op slug `woo`: +Request the list of registers and filter on slug `woo`: ```bash curl -u admin:admin \ @@ -99,7 +99,7 @@ curl -u admin:admin \ -H "OCS-APIRequest: true" ``` -In de respons vind je een entry zoals: +In the response you find an entry like: ```json { @@ -108,42 +108,42 @@ In de respons vind je een entry zoals: "slug": "woo", "title": "Woo", "version": "1.0.1", - "description": "Woo-register met TOOI-informatiecategorieën", + "description": "Woo register with TOOI information categories", "schemas": [1619, 1620, 1621, 1622, 1623, 1624, 1625, 1626, 1627, 1628] } ``` -De `id` is je interne register-id (924 in dit voorbeeld; bij jou kan het verschillen). De slug `woo` is stabiel en gebruik je verder in alle API-aanroepen. +The `id` is your internal register id (924 in this example, yours may differ). The slug `woo` is stable and you use it in all further API calls. -In de UI vind je het register onder **Apps → OpenRegister → Registers**: +In the UI you find the register under **Apps -> OpenRegister -> Registers**: -![Het Woo-register in de OpenRegister UI, met de tien TOOI-schemas in de zijkant](./02-or-woo-register-card.png) +![The Woo register in the OpenRegister UI, with the ten TOOI schemas in the sidebar](./02-or-woo-register-card.png) -Klik op **View Details** in het Actions-menu van de kaart voor een statistiekenpaneel: +Click **View Details** in the Actions menu of the card for a statistics panel: -![Het detailpaneel met statistieken voor het Woo-register: aantal objecten, logs, files en schemas](./04-or-woo-register-detail.png) +![The detail panel with statistics for the Woo register: number of objects, logs, files, and schemas](./04-or-woo-register-detail.png) -## Stap 3: Bekijk de schemas +## Step 3: Look at the schemas -De TOOI-schemas zijn metadata-schemas. Elk schema bevat zeven velden: +The TOOI schemas are metadata schemas. Each schema contains seven fields: -| Veld | Type | Beschrijving | +| Field | Type | Description | | --- | --- | --- | -| `tooiCategorieNaam` | string (verplicht) | Naam van de categorie | -| `tooiCategorieId` | string | TOOI-id, bijvoorbeeld `c_fdaee95e` | -| `tooiCategorieUri` | string | Volledige TOOI-URI | -| `tooiThemaNaam` | string | Optioneel thema | -| `tooiThemaId` | string | Optioneel thema-id | -| `tooiThemaUri` | string | Optioneel thema-URI | -| `values` | array | Vrij in te vullen sleutel-waarde-paren | +| `tooiCategorieNaam` | string (required) | Name of the category | +| `tooiCategorieId` | string | TOOI id, for example `c_fdaee95e` | +| `tooiCategorieUri` | string | Full TOOI URI | +| `tooiThemaNaam` | string | Optional theme | +| `tooiThemaId` | string | Optional theme id | +| `tooiThemaUri` | string | Optional theme URI | +| `values` | array | Free-form key-value pairs | -De `categorieId` en `categorieUri` zijn `const` in het schema. Je hoeft ze niet zelf te vullen. OpenRegister vult ze automatisch zodra je `tooiCategorieNaam` zet. +The `categorieId` and `categorieUri` are `const` in the schema. You do not have to fill them yourself. OpenRegister fills them automatically as soon as you set `tooiCategorieNaam`. -![De Schemas-pagina in OpenRegister](./03-or-tooi-schemas.png) +![The Schemas page in OpenRegister](./03-or-tooi-schemas.png) -## Stap 4: Maak een eerste publicatie +## Step 4: Create your first publication -Test je register met één publicatie. We pakken de categorie `onderzoeksrapporten`: +Test your register with one publication. We use the category `onderzoeksrapporten`: ```bash curl -u admin:admin \ @@ -155,7 +155,7 @@ curl -u admin:admin \ }' ``` -De respons toont onder andere: +The response shows, among other things: ```json { @@ -173,17 +173,17 @@ De respons toont onder andere: } ``` -OpenRegister heeft de TOOI-id en URI zelf ingevuld. Het object heeft een UUID. Onthoud die UUID, want je hebt hem nodig om bestanden aan deze publicatie te koppelen. +OpenRegister filled in the TOOI id and URI itself. The object has a UUID. Remember that UUID. You need it to attach files to this publication. -## Stap 5 (optioneel): Voeg eigen velden toe +## Step 5 (optional): Add your own fields -De canonieke schemas dekken de TOOI-metadata, niet de inhoud van de publicatie zelf. Voor titel, omschrijving, datum en verantwoordelijke organisatie maak je een eigen aanvullend schema, of breid je een bestaand schema uit. +The canonical schemas cover the TOOI metadata, not the content of the publication itself. For title, description, date, and responsible organisation, you create your own additional schema, or extend an existing one. -Een minimale uitbreiding voor `onderzoeksrapporten` ziet er bijvoorbeeld zo uit: +A minimal extension for `onderzoeksrapporten` looks like this: ```json { - "title": "Onderzoeksrapport publicatie", + "title": "Research report publication", "properties": { "titel": { "type": "string", "maxLength": 255 }, "omschrijving": { "type": "string", "maxLength": 2000 }, @@ -194,21 +194,21 @@ Een minimale uitbreiding voor `onderzoeksrapporten` ziet er bijvoorbeeld zo uit: } ``` -Je kunt zo'n schema via `POST /api/schemas` aanmaken en daarna in de Woo-register-configuratie toevoegen. Houd het zoveel mogelijk dichtbij de [DiWoo-metadata-standaard](https://standaarden.overheid.nl/diwoo) zodat je publicaties ook door [PLOOI](https://open.overheid.nl) en [woogle.nl](https://woogle.nl) opgepikt kunnen worden. +You can create such a schema via `POST /api/schemas` and then add it to the Woo register configuration. Keep it as close as possible to the [DiWoo metadata standard](https://standaarden.overheid.nl/diwoo) so your publications can be picked up by [PLOOI](https://open.overheid.nl) and [woogle.nl](https://woogle.nl) as well. -## En nu +## And now -Je hebt: +You have: -- Een werkend Woo-register met tien TOOI-schemas -- Eén testpublicatie met UUID -- Een patroon om eigen velden toe te voegen waar nodig +- A working Woo register with ten TOOI schemas +- One test publication with a UUID +- A pattern to add your own fields where needed -In de [vervolgtutorial koppel je bestanden aan deze publicatie](/academy/woo-bestanden-uploaden), van enkele PDF's tot grote videobestanden in chunks. +In the [follow-up tutorial you attach files to this publication](/academy/woo-bestanden-uploaden), from a few PDFs to large video files in chunks. -## Opruimen +## Cleanup -Als je het testregister wilt verwijderen: +If you want to delete the test register: ```bash curl -u admin:admin \ @@ -216,10 +216,10 @@ curl -u admin:admin \ -H "OCS-APIRequest: true" ``` -Dit verwijdert het register, alle schemas en alle objecten. De configuratie-import zelf blijft bestaan, zodat je hem opnieuw kunt afspelen. +This removes the register, all schemas, and all objects. The configuration import itself remains, so you can replay it. diff --git a/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-nextcloud-lokaal-draaien/index.mdx b/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-nextcloud-lokaal-draaien/index.mdx new file mode 100644 index 000000000..07395d416 --- /dev/null +++ b/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-nextcloud-lokaal-draaien/index.mdx @@ -0,0 +1,230 @@ +--- +slug: nextcloud-lokaal-draaien +title: Nextcloud lokaal draaien met Docker +contentType: tutorial +authors: [conduction] +date: 2026-05-07 +summary: Drie commando's, vier stappen, een kwartier werk. Daarna draait er een schone Nextcloud op je laptop, klaar om Conduction-apps op te installeren. +tags: [Nextcloud, Docker, Setup, Demo, Lokaal] +durationMinutes: 15 +--- + +import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, ContactCta} from '@conduction/docusaurus-preset/components'; + +Voor elke andere tutorial in deze academy heb je een werkende Nextcloud nodig. Dit is de snelste route: drie commando's en je draait de officiële Nextcloud-image lokaal, identiek aan productie. Geen sales-call, geen demo-omgeving die offline kan, geen custom Dockerfile. Daarna installeer je de Conduction-apps via de Nextcloud app store, hetzelfde pad als productie. + +{/* truncate */} + + + Een werkende Nextcloud op http://localhost:8080 opzetten met Docker. + Postgres voor data, Redis voor cache, Nextcloud voor het werk en notify_push voor realtime updates in één compose-bestand combineren. + Een admin-account aanmaken waar je naar believen Conduction-apps op installeert. + Met één commando de hele stack weer opruimen. + + +Daarna kun je verder met [Een Woo-register opzetten](/academy/woo-register-opzetten), [Bestanden uploaden bij een Woo-publicatie](/academy/woo-bestanden-uploaden), of een eigen experiment. + + + Een laptop met minimaal 4 GB vrij geheugen. + Docker, lokaal draaiend. + Een terminal en een teksteditor. + + +Op een schone laptop is stap 0 (Docker installeren) ongeveer tien minuten. Daarna is stap 1 t/m 3 samen een kwartier. + +## Stap 0: Zorg dat Docker draait + +Eenmalig. + +- **Mac of Linux:** installeer [Docker Desktop](https://docs.docker.com/get-docker/). +- **Windows:** zet eerst [WSL 2](https://learn.microsoft.com/nl-nl/windows/wsl/install) aan en installeer daarna Docker Desktop. + +Test of het werkt: + +```bash +docker --version +docker compose version +``` + +Allebei moeten een versie teruggeven, geen foutmelding. + +## Stap 1: Maak een werkmap en het compose-bestand + +```bash +mkdir conduction-demo +cd conduction-demo +``` + +Sla het volgende op als `docker-compose.yml` in die map: + +```yaml title="docker-compose.yml" +services: + db: + image: postgres:16-alpine + restart: unless-stopped + volumes: + - db:/var/lib/postgresql/data + environment: + POSTGRES_DB: nextcloud + POSTGRES_USER: nextcloud + POSTGRES_PASSWORD: nextcloud + + redis: + image: redis:7-alpine + restart: unless-stopped + + nextcloud: + image: nextcloud:latest + restart: unless-stopped + depends_on: [db, redis] + ports: + - "8080:80" + volumes: + - nextcloud:/var/www/html + - ./apps:/var/www/html/custom_apps + environment: + POSTGRES_HOST: db + POSTGRES_DB: nextcloud + POSTGRES_USER: nextcloud + POSTGRES_PASSWORD: nextcloud + REDIS_HOST: redis + NEXTCLOUD_TRUSTED_DOMAINS: "localhost" + + notify_push: + image: icewind1991/notify_push:latest + restart: unless-stopped + depends_on: [nextcloud, db, redis] + ports: + - "7867:7867" + volumes: + - nextcloud:/nextcloud:ro + environment: + NEXTCLOUD_URL: http://nextcloud + DATABASE_URL: postgres://nextcloud:nextcloud@db/nextcloud + REDIS_URL: redis://redis:6379 + +volumes: + db: + nextcloud: +``` + +Vier services, allemaal officiële images, geen custom build. De `./apps`-bind-mount is optioneel: handig als je een Conduction-app als zip naast de installatie wilt zetten in plaats van via de app store. `notify_push` is de WebSocket push-server die Conduction-apps gebruiken voor realtime updates — meer daarover in stap 5. + +## Stap 2: Start de stack + +```bash +docker compose up -d +``` + +De eerste keer trekt Docker ongeveer 750 MB aan images binnen. Daarna start de stack in seconden. + +Volg de logs terwijl Nextcloud zichzelf bootstrapt: + +```bash +docker compose logs -f nextcloud +``` + +Wacht tot je `apache2 -D FOREGROUND` of vergelijkbaar ziet, en `Ctrl+C` om de log-volger af te sluiten. De containers blijven draaien. + +## Stap 3: Voltooi de Nextcloud setup-wizard + +Open `http://localhost:8080` in je browser. Nextcloud toont een setup-wizard: + +1. Vul een **admin-naam** en **wachtwoord** in. `admin` en `admin` is prima voor lokaal werk; in productie natuurlijk niet. +2. Onder **Storage & database** kies je niets, want de Postgres-config zit al in `docker-compose.yml`. +3. Klik op **Install**. + +Na een halve minuut land je op het dashboard, ingelogd als admin. + +## Stap 4: Installeer de Conduction-apps + +Klik je avatar rechtsboven en kies **Apps**. De Conduction-apps staan onder **Integration** en **Office & text**. Onze suggestie voor een eerste demo: + +- **OpenRegister.** De typed-data foundation. Installeer deze als eerste, alle andere apps lezen ervan af. +- **OpenCatalogi.** Maakt elk register doorzoekbaar als publieke entry. Federation naar `data.overheid.nl`. +- **OpenConnector.** Trekt voorbeelddata binnen via REST, SOAP en file-drops. + +Na elke install draait de schema-bootstrap automatisch. Voorbeelddata is binnen seconden zichtbaar. + +Installeer ook de `notify_push`-app (onder **Integration**). Die is de Nextcloud-zijde van de WebSocket push-server die je in stap 1 al hebt gestart — je hoeft hem alleen te enabelen, geen verdere config nodig. + +## Stap 5: Koppel `notify_push` aan de push-server + +`notify_push` heeft één eenmalige setup-stap zodat de Nextcloud-app weet waar de Rust-binary draait. Voer in de werkmap uit: + +```bash +docker compose exec --user www-data nextcloud php occ config:system:set redis host --value redis +docker compose exec --user www-data nextcloud php occ config:system:set redis port --value 6379 --type integer +docker compose exec --user www-data nextcloud php occ config:system:set memcache.distributed --value '\OC\Memcache\Redis' +docker compose exec --user www-data nextcloud php occ config:system:set trusted_proxies 0 --value 172.16.0.0/12 +docker compose exec --user www-data nextcloud php occ notify_push:setup http://notify_push:7867 +``` + +De eerste drie regels vertellen Nextcloud dat Redis de gedeelde cache is — `notify_push` luistert daarop voor change-events. De vierde regel markeert het Docker-netwerk als trusted zodat de push-server bij Nextcloud terugmag bellen voor authenticatie. De laatste regel registreert de URL bij de Nextcloud-app. + +Verifieer dat alles werkt: + +```bash +docker compose exec --user www-data nextcloud php occ notify_push:self-test +``` + +Je krijgt een lijstje vinkjes — `redis is configured`, `push server is receiving redis messages`, `push server can connect to the Nextcloud server`, et cetera. De waarschuwing over `unencrypted http` is voor lokaal werk normaal; in productie zit `notify_push` achter dezelfde reverse proxy als Nextcloud, dus dan is dat geen issue. + +Vanaf nu krijgen apps die op `notify_push` luisteren — zoals OpenRegister's live-update-integratie via `@conduction/nextcloud-vue` — direct doorduwingen op object-wijzigingen, zonder dat de browser hoeft te pollen. + +## Wat je krijgt + +| Eigenschap | Waarde | +| --- | --- | +| Eerste boot | Ongeveer drie minuten (eerste keer 750 MB download) | +| Volgende boots | Seconden | +| Image | Officiële `nextcloud:latest`, identiek aan productie | +| Geheugen | Rond 1 GB onder lichte load | +| Reset | `docker compose down -v` (verwijdert volumes) | + +Geen vooraf opgenomen screencast, geen demo-omgeving die offline kan. Wat je hier ziet, is precies wat een echte klant ziet. + +## Reset of stoppen + +```bash +# tijdelijk stoppen, data behouden +docker compose stop + +# weer starten +docker compose start + +# helemaal opruimen, ook volumes +docker compose down -v +``` + +Na `down -v` is je laptop weer schoon. Twee minuten later kun je opnieuw beginnen. + +## Probleemoplossing + +**Apache port 8080 in gebruik:** wijzig `"8080:80"` naar bijvoorbeeld `"8081:80"` in `docker-compose.yml`. Vergeet niet de URL aan te passen naar `http://localhost:8081`. + +**Setup-wizard wil opnieuw beginnen:** waarschijnlijk is de Postgres-database leeg geraakt. Voer `docker compose down -v` uit en start opnieuw met `docker compose up -d`. + +**Nextcloud toont "Trusted domain"-foutmelding:** voeg de host toe aan `NEXTCLOUD_TRUSTED_DOMAINS` (komma-gescheiden) en herstart met `docker compose up -d`. + +**App-store install werkt niet:** controleer of Nextcloud naar buiten kan. In een corporate netwerk soms blokkade op `apps.nextcloud.com`. Een proxy via `HTTP_PROXY`-env-variabelen lost dit op. + +**`notify_push` self-test faalt op `is not trusted as a reverse proxy`:** het Docker-netwerk-bereik in jouw setup wijkt af van `172.16.0.0/12`. Bekijk het bereik met `docker network inspect $(docker network ls --filter name=conduction-demo -q) --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'` en pas de `trusted_proxies`-regel uit stap 5 aan met die subnet. + +**`notify_push` blijft restarten na `docker compose up -d`:** kijk in de logs (`docker compose logs notify_push`) of je `× No redis server is configured` ziet. Dat betekent dat Nextcloud's `memcache.distributed`-config nog niet op Redis staat — voer de eerste drie `occ config:system:set`-commando's uit stap 5 uit, en herstart dan met `docker compose up -d notify_push`. + +## Volgende stap + +Een werkende Nextcloud is de aanleiding voor de andere tutorials in deze serie: + +- [Een Woo-register opzetten](/academy/woo-register-opzetten) — importeer het canonieke Woo-register in OpenRegister +- [Bestanden uploaden bij een Woo-publicatie](/academy/woo-bestanden-uploaden) — vier upload-modes voor documenten +- [Build je eerste Conduction-app](/academy/build-an-app) — eindigt met een geïnstalleerde app op deze stack + +Stuur het `docker-compose.yml` naar een collega als je dit samen wilt zien werken. + + diff --git a/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-bestanden-uploaden/index.mdx b/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-bestanden-uploaden/index.mdx new file mode 100644 index 000000000..37d2799fb --- /dev/null +++ b/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-bestanden-uploaden/index.mdx @@ -0,0 +1,348 @@ +--- +slug: woo-bestanden-uploaden +title: Bestanden bij een Woo-publicatie uploaden +contentType: tutorial +authors: [conduction] +date: 2026-05-07 +summary: Vier manieren om documenten aan een Woo-publicatie te koppelen, van één PDF tot een 6 MB rapport in chunks. Met geteste API-aanroepen. +tags: [Woo, OpenRegister, Bestanden, Upload, Publicaties] +apps: [openregister, openwoo] +durationMinutes: 18 +--- + +import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, Troubleshooting, TroubleshootingItem, NextSteps, NextStep, ContactCta} from '@conduction/docusaurus-preset/components'; + +Een Woo-publicatie zonder bestanden is metadata zonder bewijs. In deze tutorial koppel je documenten aan een Woo-publicatie via vier upload-modes: één bestand, meerdere bestanden tegelijk, en grote bestanden in chunks. Plus publiceren, depubliceren en opruimen. + +{/* truncate */} + + + Vier upload-modes voor documenten bij een Woo-publicatie, van klein naar groot. + Wanneer je JSON+base64, multipart of WebDAV chunked upload gebruikt. + Hoe je bestanden publiceert, depubliceert en weer opruimt. + + + + + Een werkend Woo-register. Volg eerst Een Woo-register opzetten als je dat nog niet hebt. + + Een publicatie-object met bekende UUID. + curl, basisauth, en een paar testbestanden. + + +In de voorbeelden gebruiken we: + +- Host: `http://localhost:8080` +- Auth: `admin:admin` +- Register-slug: `woo` +- Schema-slug: `onderzoeksrapporten` +- Object-UUID: `3422e6cd-1ba5-478c-b842-8f206f9d7358` + +Vervang het object-UUID door de jouwe. + +![Het Woo-register met de geïmporteerde TOOI-schemas](./01-woo-register-context.png) + +## Het basispatroon + +Alle bestanden worden gekoppeld aan een specifiek object. De URL volgt altijd hetzelfde patroon: + +``` +POST /index.php/apps/openregister/api/objects/{register}/{schema}/{id}/files +``` + +OpenRegister maakt automatisch een map aan voor het object onder `Open Registers/{Register Title}/{Object UUID}/`. Daar landen alle bestanden. De map ontstaat zodra je het eerste bestand uploadt. + +## Mode 1: één bestand via JSON en base64 + +De eenvoudigste route, prima voor kleine documenten tot enkele megabytes: + +```bash +OBJ=3422e6cd-1ba5-478c-b842-8f206f9d7358 +B64=$(echo "Inhoud van het rapport." | base64 -w0) + +curl -u admin:admin \ + -X POST "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten/$OBJ/files" \ + -H "OCS-APIRequest: true" \ + -H "Content-Type: application/json" \ + -d "{ + \"name\": \"rapport-2026.txt\", + \"content\": \"$B64\", + \"share\": true + }" +``` + +De respons: + +```json +{ + "id": 3306, + "path": "/admin/files/Open Registers/Woo Register/3422e6cd-.../rapport-2026.txt", + "title": "rapport-2026.txt", + "accessUrl": "http://localhost:8080/index.php/s/CH5JsTKtrczyQgw", + "downloadUrl": "http://localhost:8080/index.php/s/CH5JsTKtrczyQgw/download", + "type": "text/plain", + "size": 44, + "hash": "4bdf3a838bc59f367824ad59a14630c0", + "published": "1970-01-01T00:00:00+00:00", + "labels": [], + "object": "3422e6cd-..." +} +``` + +`share: true` maakt direct een publieke share-link aan. De `accessUrl` is dan meteen de publieke landings-URL. Wil je het bestand vooralsnog privé houden, laat `share` weg of zet hem op `false`. + +**Wanneer wel:** scripts en synchronisaties waar je tekstinhoud genereert, kleine PDF's, of CI-pipelines. **Wanneer niet:** bestanden boven enkele MB. Base64 maakt de payload 33% groter en je raakt al snel de PHP `post_max_size`-grens. + +## Mode 2: één bestand binair via multipart + +Voor binaire bestanden (PDF, DOCX, afbeeldingen) gebruik je het `filesMultipart`-endpoint. Dat scheelt de base64-overhead en is sneller voor middelgrote bestanden. + +```bash +OBJ=3422e6cd-1ba5-478c-b842-8f206f9d7358 + +curl -u admin:admin \ + -X POST "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten/$OBJ/filesMultipart" \ + -H "OCS-APIRequest: true" \ + -F "files[]=@/pad/naar/rapport.pdf" \ + -F "share=true" +``` + +De respons is een array met één element: + +```json +[ + { + "id": 3307, + "path": "/admin/files/Open Registers/Woo Register/3422e6cd-.../rapport.pdf", + "title": "rapport.pdf", + "type": "application/pdf", + "size": 482310, + "hash": "a4abb3649eff2d9ba4f80675371a56b8", + "object": "3422e6cd-..." + } +] +``` + +> **`files[]` werkt voor één en voor meerdere bestanden.** Het endpoint accepteert ook een enkelvoudig `file=`-veld, maar `files[]` is consistenter: precies dezelfde aanroep schaalt door naar Mode 3 hieronder zonder dat je je veldnaam hoeft aan te passen. + +## Mode 3: meerdere bestanden in één request + +Het `filesMultipart`-endpoint accepteert meerdere bestanden in één enkele request. Eén HTTP-call, één transactie, één respons-array. Handig voor publicaties met meerdere bijlagen. + +```bash +OBJ=3422e6cd-1ba5-478c-b842-8f206f9d7358 + +curl -u admin:admin \ + -X POST "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten/$OBJ/filesMultipart" \ + -H "OCS-APIRequest: true" \ + -F "files[]=@/pad/naar/bijlage-1.pdf" \ + -F "files[]=@/pad/naar/bijlage-2.pdf" \ + -F "files[]=@/pad/naar/bijlage-3.pdf" \ + -F "share=true" +``` + +Respons: + +```json +[ + { "id": 3308, "title": "bijlage-1.pdf", "size": 29, "hash": "b753b747...", ... }, + { "id": 3309, "title": "bijlage-2.pdf", "size": 32, "hash": "f370ad27...", ... }, + { "id": 3310, "title": "bijlage-3.pdf", "size": 28, "hash": "80a9a215...", ... } +] +``` + +Elk bestand krijgt een eigen entry in de respons. De `share`-vlag geldt voor het hele batch: alle bestanden worden tegelijk publiek gemaakt of houden tegelijk privé. + +**Praktische limiet:** PHP's `post_max_size` is meestal 100 MB. Het totaal van alle bestanden in één request mag daar niet overheen. Voor grotere uploads spring je naar Mode 4. + +## Mode 4: grote bestanden in chunks via WebDAV + +Bestanden boven de PHP-uploadlimiet (typisch 100 MB) splits je in chunks en verstuur je via Nextcloud's WebDAV chunked upload-protocol. Daarna verplaats je het samengestelde bestand naar de objectmap. + +Dit is hetzelfde protocol dat de Nextcloud webclient en desktop-app gebruiken. Het werkt voor alle bestandsgroottes en past zich aan netwerkonderbrekingen aan. + +### Stap 4.1: maak een transfer-map + +Geef de transfer een unieke ID, bijvoorbeeld een tijdstempel: + +```bash +TRANSFER_ID="woo-chunked-$(date +%s)" +DAV_BASE="http://localhost:8080/remote.php/dav" + +curl -u admin:admin \ + -X MKCOL "$DAV_BASE/uploads/admin/$TRANSFER_ID" +``` + +Een 201 betekent dat de transfer-map klaar staat. + +### Stap 4.2: split het bestand en upload de chunks + +We splitsen een testbestand van 6 MB in drie chunks van 2 MB: + +```bash +split -b 2M /pad/naar/groot-rapport.pdf /tmp/chunk- + +i=1 +for chunk in /tmp/chunk-*; do + CHUNK_NUM=$(printf "%05d" $i) + curl -u admin:admin \ + -X PUT --data-binary "@$chunk" \ + "$DAV_BASE/uploads/admin/$TRANSFER_ID/$CHUNK_NUM" + i=$((i+1)) +done +``` + +De chunks krijgen vijfcijferige nummers (`00001`, `00002`, `00003`). Houd de volgorde in de bestandsnamen aan, want Nextcloud assembleert op basis daarvan. + +### Stap 4.3: assembleer en plaats in de objectmap + +Een MOVE-call combineert de chunks tot één bestand op de uiteindelijke locatie: + +```bash +OBJ=3422e6cd-1ba5-478c-b842-8f206f9d7358 +DEST="$DAV_BASE/files/admin/Open%20Registers/Woo%20Register/$OBJ/groot-rapport.pdf" +TOTAL_LENGTH=$(wc -c < /pad/naar/groot-rapport.pdf) + +curl -u admin:admin \ + -X MOVE "$DAV_BASE/uploads/admin/$TRANSFER_ID/.file" \ + -H "Destination: $DEST" \ + -H "OC-Total-Length: $TOTAL_LENGTH" +``` + +Een 201 betekent dat het bestand op de bestemming staat. OpenRegister pikt het automatisch op: zodra je de files-lijst van het object opvraagt, staat het bestand erbij. + +### Stap 4.4: verifieer in OpenRegister + +```bash +curl -u admin:admin \ + "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten/$OBJ/files" \ + -H "OCS-APIRequest: true" +``` + +Je ziet het nieuwe bestand met zijn echte grootte: + +```json +{ + "id": 3316, + "path": "/admin/files/Open Registers/Woo Register/3422e6cd-.../groot-rapport.pdf", + "title": "groot-rapport.pdf", + "size": 6291456, + "hash": "d7d7eec53f6d70d1bdceea08c10d0bb1", + "modified": "2026-05-07T05:14:34+00:00" +} +``` + +**Belangrijk:** chunks moeten naar de map van de actuele gebruiker (`admin` in dit voorbeeld). Een service-account upload je dus eerst in dat account; de MOVE plaatst het daarna in de gedeelde register-map. + +## Publiceren en depubliceren + +Een geüpload bestand staat standaard niet publiek (`accessUrl: null`). Bij Woo-publicaties wil je expliciet kiezen welke bestanden publiek zijn en welke niet, bijvoorbeeld omdat een bijlage privacygevoelig is en eerst gelakt moet worden. + +Publiek maken: + +```bash +FID=3306 +curl -u admin:admin \ + -X POST "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten/$OBJ/files/$FID/publish" \ + -H "OCS-APIRequest: true" +``` + +In de respons verschijnt een `accessUrl`: + +```json +{ + "id": 3306, + "title": "rapport-2026.txt", + "accessUrl": "http://localhost:8080/index.php/s/CH5JsTKtrczyQgw", + "downloadUrl": "http://localhost:8080/index.php/s/CH5JsTKtrczyQgw/download" +} +``` + +Weer privé maken: + +```bash +curl -u admin:admin \ + -X POST "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten/$OBJ/files/$FID/depublish" \ + -H "OCS-APIRequest: true" +``` + +Na depublicatie zijn `accessUrl` en `downloadUrl` weer `null` en is de share-link ingetrokken. + +## Lijst, downloaden, verwijderen + +| Actie | Methode | URL | +| --- | --- | --- | +| Lijst van bestanden bij een object | `GET` | `/api/objects/{register}/{schema}/{id}/files` | +| Detail van één bestand | `GET` | `/api/objects/{register}/{schema}/{id}/files/{fileId}` | +| Download (alle bestanden als zip) | `GET` | `/api/objects/{register}/{schema}/{id}/files/download` | +| Download direct via id | `GET` | `/api/files/{fileId}/download` | +| Verwijder één bestand | `DELETE` | `/api/objects/{register}/{schema}/{id}/files/{fileId}` | + +Voorbeeld voor een lijst: + +```bash +curl -u admin:admin \ + "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten/$OBJ/files" \ + -H "OCS-APIRequest: true" +``` + +Verwijderen van één bestand: + +```bash +curl -u admin:admin \ + -X DELETE "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten/$OBJ/files/3306" \ + -H "OCS-APIRequest: true" +``` + +## Welke mode kies je wanneer? + +| Mode | Beste voor | Limiet | Codecomplexiteit | +| --- | --- | --- | --- | +| 1. JSON + base64 | Server-naar-server scripts, kleine PDF's | ≈ 75 MB ruwe inhoud (33% overhead op `post_max_size`) | Laag | +| 2. Multipart, één bestand | Browser-uploads, gewone PDF's en DOCX | `post_max_size`, meestal 100 MB | Laag | +| 3. Multipart, meerdere bestanden | Publicaties met meerdere bijlagen | Som van bestanden onder `post_max_size` | Laag | +| 4. WebDAV chunked + MOVE | Video, scans, datasets > 100 MB | Geen praktische bovengrens | Hoog | + +Voor de meeste Woo-publicaties pak je Mode 2 of 3. Pas op het moment dat een bestand groter dan 100 MB binnenkomt, schakel je over naar Mode 4. + + + + Verhoog upload_max_filesize en post_max_size in php.ini, of stap over op Mode 4. + + + Controleer dat de Destination-header naar {'Open Registers/{Register Title}/{Object UUID}/'} wijst en dat de UUID overeenkomt met een bestaand object. OpenRegister scant alleen die map. + + + Controleer of share-links aanstaan in de Nextcloud admin (Settings → Sharing → "Allow apps to use the share API"). Zonder deze instelling kunnen publicaties geen publieke link krijgen. + + + + + + De publicatie samen met andere apps tonen op een portaal. + + + Documenten automatisch laten lakken op privacygevoelige passages. + + + Publicaties periodiek synchroniseren naar open.overheid.nl. + + + + diff --git a/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/index.mdx b/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/index.mdx new file mode 100644 index 000000000..f3051fcd1 --- /dev/null +++ b/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/index.mdx @@ -0,0 +1,225 @@ +--- +slug: woo-register-opzetten +title: Een Woo-register opzetten in OpenRegister +contentType: tutorial +authors: [conduction] +date: 2026-05-07 +summary: Importeer het canonieke Woo-register in OpenRegister. Eén API-aanroep, tien TOOI-categorieën, klaar om publicaties op te slaan. +tags: [Woo, OpenRegister, Publicaties, TOOI, Open data] +apps: [openregister, openwoo] +durationMinutes: 10 +--- + +import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, ContactCta} from '@conduction/docusaurus-preset/components'; + +De [Wet open overheid (Woo)](https://www.rijksoverheid.nl/onderwerpen/wet-open-overheid-woo) verplicht overheden om documenten openbaar te maken in elf informatiecategorieën. OpenRegister levert het opslagmodel daarvoor. In deze tutorial importeer je het canonieke Woo-register, met alle [TOOI](https://standaarden.overheid.nl/tooi)-categorieën, in een paar minuten. + +{/* truncate */} + + + Eén register met de slug woo in je OpenRegister-installatie aanmaken. + Tien schemas importeren, één per TOOI-informatiecategorie. + Een werkend startpunt opzetten om publicaties aan te maken en bestanden te koppelen. + + +De vervolgtutorial over bestanden uploaden bouwt hierop voort. + + + Een draaiende Nextcloud met OpenRegister geïnstalleerd. Heb je dat nog niet, volg dan eerst Nextcloud lokaal draaien met Docker. + Een gebruiker met admin-rechten, of een gebruiker die registers mag aanmaken. + curl of een vergelijkbare HTTP-client. + + +In de voorbeelden gebruiken we de lokale dev-omgeving op `http://localhost:8080` met `admin:admin`. Vervang dat door je eigen host en credentials. + +## De canonieke bron + +Het Woo-register bestaat al, gepubliceerd door Conduction op GitHub: + +``` +https://raw.githubusercontent.com/ConductionNL/woo-website/main/website/static/oas/woo_register.json +``` + +Het is een OpenAPI-bundel van 1.0.1, met één register (`woo`) en tien schemas. Elk schema bevat de [TOOI-metadata](https://standaarden.overheid.nl/diwoo/metadata/doc/0.9.1/diwoo-metadata-lijsten_xsd_Simple_Type_diwoo_scw_woo_informatiecategorieen) van één Woo-informatiecategorie: + +| Slug | Categorie | +| --- | --- | +| `vergaderstukken_decentrale_overheden` | Vergaderstukken decentrale overheden | +| `subsidieverplichtingen_anders_dan_met_beschikking` | Subsidieverplichtingen zonder beschikking | +| `overige_besluiten_van_algemene_strekking` | Overige besluiten van algemene strekking | +| `organisatie_en_werkwijze` | Organisatie en werkwijze | +| `ontwerpen_van_wet_en_regelgeving_met_adviesaanvraag` | Ontwerpen van wet- en regelgeving | +| `onderzoeksrapporten` | Onderzoeksrapporten | +| `klachtoordelen` | Klachtoordelen | +| `wetten_en_algemeen_verbindende_voorschriften` | Wetten en algemeen verbindende voorschriften | +| `woo_verzoeken_en_besluiten` | Woo-verzoeken en -besluiten | +| `convenanten` | Convenanten | +| `agendas_en_besluitenlijsten_bestuurscolleges` | Agenda's en besluitenlijsten bestuurscolleges | +| `vergaderstukken_staten_generaal` | Vergaderstukken Staten-Generaal | +| `adviezen` | Adviezen | + +## Stap 1: Importeer het register + +OpenRegister heeft een `import/url`-endpoint dat een OpenAPI-bundel ophaalt en in één transactie omzet naar register, schemas en (optioneel) seed-objecten. + +```bash +curl -u admin:admin \ + -X POST "http://localhost:8080/index.php/apps/openregister/api/configurations/import/url" \ + -H "OCS-APIRequest: true" \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://raw.githubusercontent.com/ConductionNL/woo-website/main/website/static/oas/woo_register.json" + }' +``` + +Als alles goed gaat, krijg je terug: + +```json +{ + "success": true, + "message": "Configuration imported successfully from url", + "configurationId": 271, + "result": { + "registersCount": 1, + "schemasCount": 10, + "objectsCount": 0 + } +} +``` + +`registersCount: 1` en `schemasCount: 10` bevestigen dat alles is aangemaakt. De `configurationId` heb je later nodig als je de bron wilt re-syncen. + +## Stap 2: Controleer wat is aangemaakt + +Vraag de lijst van registers op en filter op slug `woo`: + +```bash +curl -u admin:admin \ + "http://localhost:8080/index.php/apps/openregister/api/registers" \ + -H "OCS-APIRequest: true" +``` + +In de respons vind je een entry zoals: + +```json +{ + "id": 924, + "uuid": "cd44ca36-e3de-4d6f-9af9-abbb49710c83", + "slug": "woo", + "title": "Woo", + "version": "1.0.1", + "description": "Woo-register met TOOI-informatiecategorieën", + "schemas": [1619, 1620, 1621, 1622, 1623, 1624, 1625, 1626, 1627, 1628] +} +``` + +De `id` is je interne register-id (924 in dit voorbeeld; bij jou kan het verschillen). De slug `woo` is stabiel en gebruik je verder in alle API-aanroepen. + +In de UI vind je het register onder **Apps → OpenRegister → Registers**: + +![Het Woo-register in de OpenRegister UI, met de tien TOOI-schemas in de zijkant](./02-or-woo-register-card.png) + +Klik op **View Details** in het Actions-menu van de kaart voor een statistiekenpaneel: + +![Het detailpaneel met statistieken voor het Woo-register: aantal objecten, logs, files en schemas](./04-or-woo-register-detail.png) + +## Stap 3: Bekijk de schemas + +De TOOI-schemas zijn metadata-schemas. Elk schema bevat zeven velden: + +| Veld | Type | Beschrijving | +| --- | --- | --- | +| `tooiCategorieNaam` | string (verplicht) | Naam van de categorie | +| `tooiCategorieId` | string | TOOI-id, bijvoorbeeld `c_fdaee95e` | +| `tooiCategorieUri` | string | Volledige TOOI-URI | +| `tooiThemaNaam` | string | Optioneel thema | +| `tooiThemaId` | string | Optioneel thema-id | +| `tooiThemaUri` | string | Optioneel thema-URI | +| `values` | array | Vrij in te vullen sleutel-waarde-paren | + +De `categorieId` en `categorieUri` zijn `const` in het schema. Je hoeft ze niet zelf te vullen. OpenRegister vult ze automatisch zodra je `tooiCategorieNaam` zet. + +![De Schemas-pagina in OpenRegister](./03-or-tooi-schemas.png) + +## Stap 4: Maak een eerste publicatie + +Test je register met één publicatie. We pakken de categorie `onderzoeksrapporten`: + +```bash +curl -u admin:admin \ + -X POST "http://localhost:8080/index.php/apps/openregister/api/objects/woo/onderzoeksrapporten" \ + -H "OCS-APIRequest: true" \ + -H "Content-Type: application/json" \ + -d '{ + "tooiCategorieNaam": "onderzoeksrapporten" + }' +``` + +De respons toont onder andere: + +```json +{ + "id": "3422e6cd-1ba5-478c-b842-8f206f9d7358", + "tooiCategorieNaam": "onderzoeksrapporten", + "tooiCategorieId": "c_fdaee95e", + "tooiCategorieUri": "https://identifier.overheid.nl/tooi/def/thes/kern/c_fdaee95e", + "@self": { + "register": "924", + "schema": "1624", + "uri": "http://localhost:8080/apps/openregister/api/objects/924/1624/3422e6cd-...", + "owner": "admin", + "created": "2026-05-07T05:12:24+00:00" + } +} +``` + +OpenRegister heeft de TOOI-id en URI zelf ingevuld. Het object heeft een UUID. Onthoud die UUID, want je hebt hem nodig om bestanden aan deze publicatie te koppelen. + +## Stap 5 (optioneel): Voeg eigen velden toe + +De canonieke schemas dekken de TOOI-metadata, niet de inhoud van de publicatie zelf. Voor titel, omschrijving, datum en verantwoordelijke organisatie maak je een eigen aanvullend schema, of breid je een bestaand schema uit. + +Een minimale uitbreiding voor `onderzoeksrapporten` ziet er bijvoorbeeld zo uit: + +```json +{ + "title": "Onderzoeksrapport publicatie", + "properties": { + "titel": { "type": "string", "maxLength": 255 }, + "omschrijving": { "type": "string", "maxLength": 2000 }, + "openbaarmakingsdatum": { "type": "string", "format": "date" }, + "verantwoordelijkeOrganisatie": { "type": "string" } + }, + "required": ["titel", "openbaarmakingsdatum"] +} +``` + +Je kunt zo'n schema via `POST /api/schemas` aanmaken en daarna in de Woo-register-configuratie toevoegen. Houd het zoveel mogelijk dichtbij de [DiWoo-metadata-standaard](https://standaarden.overheid.nl/diwoo) zodat je publicaties ook door [PLOOI](https://open.overheid.nl) en [woogle.nl](https://woogle.nl) opgepikt kunnen worden. + +## En nu + +Je hebt: + +- Een werkend Woo-register met tien TOOI-schemas +- Eén testpublicatie met UUID +- Een patroon om eigen velden toe te voegen waar nodig + +In de [vervolgtutorial koppel je bestanden aan deze publicatie](/academy/woo-bestanden-uploaden), van enkele PDF's tot grote videobestanden in chunks. + +## Opruimen + +Als je het testregister wilt verwijderen: + +```bash +curl -u admin:admin \ + -X DELETE "http://localhost:8080/index.php/apps/openregister/api/registers/924" \ + -H "OCS-APIRequest: true" +``` + +Dit verwijdert het register, alle schemas en alle objecten. De configuratie-import zelf blijft bestaan, zodat je hem opnieuw kunt afspelen. + + From ac0b56621e131bfe0eb524cf18beabe0fc91b5e6 Mon Sep 17 00:00:00 2001 From: WilcoLouwerse Date: Tue, 19 May 2026 14:43:40 +0200 Subject: [PATCH 039/330] academy: public Claude Skills leerlijn (4 chained Dutch tutorials) Splits the four-part Claude Skills leerlijn off from the internal Hydra branch so it can ship to main without dragging the Hydra tutorials with it. Includes: - Deel 1: wat zijn Claude Skills - Deel 2: je eerste skill schrijven met /skill-creator - Deel 3: skill-evals (L5 meet-fundering) - Deel 4: L6 self-improvement + L7 orchestration Brings along site.css additions for the `.tutorial-quiz` collapsibles and brand-aligned blockquote treatment that these tutorials depend on. Removes the NextStep in deel 4 that linked to the internal Hydra leerlijn (private, would 404 for the public audience this branch targets). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../index.mdx | 320 ++++++++++++++ .../index.mdx | 412 ++++++++++++++++++ .../index.mdx | 346 +++++++++++++++ .../index.mdx | 341 +++++++++++++++ src/css/site.css | 92 ++++ 5 files changed, 1511 insertions(+) create mode 100644 academy/2026-05-15-claude-skills-tutorial-1-wat-zijn-claude-skills/index.mdx create mode 100644 academy/2026-05-15-claude-skills-tutorial-2-je-eerste-skill/index.mdx create mode 100644 academy/2026-05-15-claude-skills-tutorial-3-skill-evals/index.mdx create mode 100644 academy/2026-05-15-claude-skills-tutorial-4-l6-l7-mature-skills/index.mdx diff --git a/academy/2026-05-15-claude-skills-tutorial-1-wat-zijn-claude-skills/index.mdx b/academy/2026-05-15-claude-skills-tutorial-1-wat-zijn-claude-skills/index.mdx new file mode 100644 index 000000000..73fce7d95 --- /dev/null +++ b/academy/2026-05-15-claude-skills-tutorial-1-wat-zijn-claude-skills/index.mdx @@ -0,0 +1,320 @@ +--- +slug: claude-skills-tutorial-1-wat-zijn-claude-skills +title: "Claude Skills leerlijn — Deel 1: Wat zijn Claude Skills?" +contentType: tutorial +authors: [conduction] +date: 2026-05-15 +summary: Wat is een Claude Skill, wanneer schrijf je er een (en wanneer juist een prompt of command), welke velden staan in de frontmatter en hoe weet Claude wanneer hij hem moet triggeren. Eerste van vier korte modules. +tags: [Claude, Claude Code, Skills, AI, Tutorial series] +apps: [] +durationMinutes: 12 +series: claude-skills-tutorial +partNumber: 1 +--- + +import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard, ContactCta} from '@conduction/docusaurus-preset/components'; +import Details from '@theme/Details'; + +Claude Skills zijn het mechanisme waarmee je Claude Code uitbreidt met herbruikbare, gespecialiseerde gedragingen. Denk aan Conductions eigen `/review-pr`, `/opsx-new` of de hele `hydra-gate-*` familie: stuk voor stuk skills. Dit eerste deel legt in tien minuten uit wát een skill is, hoe hij geactiveerd wordt, en wanneer je beter géén skill maakt. Het is deel 1 van een leerlijn van vier; aan het eind sta je klaar om er in deel 2 zelf een te schrijven — en in deel 3 introduceren we het **7-level maturity-framework** waarmee je een skill van "voelt goed" naar "gemeten goed" brengt (deel 4 gaat door op L6 en L7). + +{/* truncate */} + + + Wat een Claude Skill is — een mapje met een SKILL.md dat Claude on-demand laadt. + De twee manieren waarop een skill geactiveerd kan worden: handmatig via / en automatisch door Claude zelf. + Welke velden de frontmatter heeft, en waarom de description verreweg het belangrijkste veld is. + Wanneer een skill de juiste keuze is — en wanneer een gewone prompt, een command of een agent beter past. + + + + Een werkende installatie van Claude Code op je laptop. Zo niet, doe eerst de Workstation Setup. + Basiskennis Git en GitHub — een skill is uiteindelijk gewoon een bestand dat je commit. + Een paar uur ervaring met Claude Code in de praktijk. Je hoeft geen power-user te zijn, maar je moet wel weten wat /help doet en hoe je een prompt naar Claude stuurt. + + +## In één zin + +Een **Claude Skill** is een mapje onder `.claude/skills//` met daarin een `SKILL.md` (en optioneel scripts, voorbeelden, referentiebestanden). De frontmatter van die `SKILL.md` vertelt Claude wanneer de skill relevant is; de inhoud is de instructie die Claude volgt zodra de skill geladen wordt. + +Praktisch: een skill is een **bundel-eenheid van gedrag**. In plaats van duizend regels prompt-tekst in `CLAUDE.md` te plakken, of telkens dezelfde instructies opnieuw te typen, schrijf je het één keer als skill. Hergebruikbaar, testbaar, en deelbaar met je team. + +``` +.claude/skills/ +└── review-pr/ + ├── SKILL.md ← verplicht: de skill-logica + ├── templates/ ← optioneel: bestanden met placeholders + ├── references/ ← optioneel: standaarden en achtergrond + ├── examples/ ← optioneel: voorbeelden van gewenste output + └── scripts/ ← optioneel: shell- of Python-scripts +``` + +## Hoe een skill wordt aangeroepen + +Eén skill kan op **twee manieren** in actie komen: + +1. **Handmatig** — je typt `/review-pr` in een Claude-sessie. Direct, voorspelbaar, en de skill draait precies wanneer jij dat wilt. +2. **Automatisch** — Claude leest de `description` van alle beschikbare skills mee en kiest er zelf eentje wanneer de huidige situatie matcht. Vraag bijvoorbeeld "kun je deze PR reviewen?" en Claude triggert vanzelf `review-pr` als die voorhanden is. + +Welke mode is toegestaan, regel je in de frontmatter van de skill zelf: + +- `disable-model-invocation: true` — alleen jij kunt 'm aanroepen via `/`. Gebruik dit voor skills met side-effects (`/opsx-apply` past code aan, `/create-pr` opent een PR). Claude mag dit niet zomaar uit zichzelf doen. +- `user-invocable: false` — alleen Claude zelf mag 'm laden. Gebruik dit voor achtergrondkennis die niet als actie zinvol is (bijvoorbeeld een `legacy-system-context` die alleen informeert). +- Geen van beide gezet → beide mogen. Default voor de meeste skills. + +In een Hydra-pipeline draaien skills meestal **automatisch**: containers hebben geen toetsenbord en leunen op auto-activatie. Achter je eigen toetsenbord typ je vaker expliciet `/` voor controle. + +## De frontmatter, veld voor veld + +Een minimale `SKILL.md` ziet er zo uit: + +```markdown +--- +name: review-pr +description: Review a Pull Request — runs local checks, summarises the diff, and posts inline comments +metadata: + category: Workflow + tags: [github, review] +--- + +# Review PR + +Korte uitleg van wat de skill doet. + +**Input**: hoe je de skill aanroept en welke argumenten hij accepteert. + +**Steps** + +1. **Stap-naam** + Instructies... + +2. **Stap-naam** + Instructies... + +**Guardrails** +- Wat de skill nooit mag doen +- Wat hij moet checken voor destructieve acties +``` + +Drie velden vragen extra aandacht: + +| Veld | Functie | +|---|---| +| `name` | Moet **exact gelijk** zijn aan de mapnaam. Folder heet `review-pr` → `name: review-pr`. Wijkt het af, dan kun je de skill niet via `/` triggeren. | +| `description` | Het belangrijkste veld van de hele skill. Hierop beslist Claude of de skill relevant is. Action-oriented, derde persoon, met concrete trigger-woorden. | +| `metadata.tags` / `metadata.category` | Vrij in te vullen, handig voor filteren en groeperen — geen invloed op triggering. | + +### Waarom de description zoveel belangrijker is dan je denkt + +Claude laadt skills in drie lagen: + +| Laag | Wat | Wanneer geladen | +|---|---|---| +| **Metadata** | `name` + `description` | **Altijd** — zit standaard in de system prompt | +| **SKILL.md body** | Steps, guardrails, instructies | Pas wanneer de skill triggert | +| **Reference files** | `references/`, `templates/`, `examples/` | On-demand tijdens uitvoering | + +Dat betekent: alleen je `description` staat altijd "aan". Is hij vaag, dan vuurt je skill nooit automatisch. Schrijf 'm in derde persoon, start met een werkwoord, en plak de meest specifieke trigger-woorden vooraan (alleen de eerste ~250 tekens zijn zichtbaar in skill-listings). + +``` +# Slecht — vaag, passief, geen trigger-woorden +description: A skill that helps with various document-related tasks + +# Goed — specifiek, derde persoon, action-werkwoord vooraan +description: Create a Pull Request from the current branch — runs local checks, picks target branch, and opens the PR on GitHub +``` + +### Verdiepende frontmatter-velden + +Naast `name`, `description` en `metadata` zijn er een paar optionele velden die het gedrag van een skill subtiel sturen. Je hebt ze niet altijd nodig, maar weten dát ze bestaan helpt later: + +| Veld | Functie | Wanneer gebruiken | +|---|---|---| +| `allowed-tools` | Beperkt welke tools de skill mag aanroepen | Read-only audit-skills die nooit mogen schrijven | +| `context: fork` | Runt de skill in een geïsoleerde sub-context | Zware skills die je hoofdgesprek niet willen vervuilen | +| `paths` | Auto-trigger alleen bij werk in deze paden | Skills die alleen relevant zijn in bv. `openspec/**` | +| `disable-model-invocation` | Schakelt auto-trigger volledig uit | Skills met side-effects die strikt slash-only moeten zijn | +| `user-invocable: false` | Schakelt handmatige `/`-invocatie uit | Pure achtergrondkennis voor Claude zelf | + +Begin met de drie verplichte/basis-velden. Voeg de bovenstaande pas toe als je een concrete reden hebt — anders maak je je skill stiller of strikter dan nodig. + +### Naming convention: `namespace-action` + +Skills heten standaard `-` met alleen kleine letters, cijfers en koppeltekens. Conduction gebruikt onder meer: + +| Namespace | Dekt | +|---|---| +| `opsx-` | OpenSpec-workflow (`opsx-new`, `opsx-apply`, `opsx-archive`) | +| `app-` | Nextcloud app-lifecycle (`app-design`, `app-create`) | +| `test-` | Testen (`test-counsel`, `test-persona-henk`) | +| `team-` | Scrum team-rollen (`team-backend`, `team-qa`) | +| `hydra-gate-` | Quality- en security-gates binnen Hydra | + +De mapnaam, het `name`-veld én de manier waarop je 'm typt (`/`) moeten allemaal exact gelijk zijn. + +## Skill vs. prompt vs. agent + +Een veelgehoorde vraag: wanneer is een skill de juiste keuze, en wanneer iets anders? Drie categorieën, kort: + + + Eenmalig, of iets wat je sneller schrijft dan op te zoeken. Geen herhaalbaar patroon. Voorbeeld: "refactor deze functie naar early-return-stijl". + + + + Herhaalbaar gedrag, mechanisch te beschrijven, met side-effects of een vast stappenplan. Voorbeelden: /review-pr, /opsx-new, /create-pr. + + + + Een aparte uitvoeringscontext met eigen tools en eigen tokenbudget. Gebruik je wanneer je werk wilt isoleren (zware exploratie, parallelle reviews). Een skill kan een subagent oproepen, maar is er niet hetzelfde als. + + +De pragmatische test: schrijf een skill als… + +1. **Het gedrag is herhaalbaar** — meer dan eens nodig, op meer dan één plek. +2. **Het is mechanisch beschrijfbaar** — je kunt het in 1-3 alinea's instrueren, zonder dat het ontaardt in "het hangt van de context af". +3. **Je teamgenoot zou hetzelfde willen doen** — anders is het een persoonlijke macro, geen team-skill. + +Voor één-keer-werk: gewoon prompten. Voor herhaalbaar werk: skill. Voor zware isolatie van een sub-werkstroom: subagent. + +## Waar skills "wonen" + +Skills kunnen op drie niveaus liggen: + +| Niveau | Pad | Wie zie ze | +|---|---|---| +| **Globaal** | `~/.claude/skills//` | Jij, in elke Claude-sessie op deze laptop | +| **Per project** | `/.claude/skills//` | Iedereen die in deze repo werkt — gecommit in Git | +| **Vendored** | `/vendor/skills//` | Idem, maar afkomstig van een externe partij (bv. Anthropic of Trail of Bits) | + +In Conduction-repo's leeft het overgrote deel als **project-skills** in `/.claude/skills/`. Dat zorgt dat het hele team — en Hydra's Docker-personas — exact dezelfde skills hebben. + +## Test jezelf + +Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Curieus naar het antwoord? Klik **Antwoord**. + +
+ +**1. Wanneer kies je voor een skill, en wanneer voor een gewone prompt of een subagent?** + +
+ +Denk aan herhaalbaarheid, mechanische beschrijfbaarheid, en isolatie. Bij welke combinaties valt de keuze welke kant op? + +
+ +
+ +- **Gewone prompt** — eenmalig, of iets wat je sneller schrijft dan op te zoeken. Geen herhaalbaar patroon. Bijvoorbeeld: *"refactor deze functie naar early-return-stijl"*. +- **Skill** — herhaalbaar gedrag, mechanisch te beschrijven, met side-effects of een vast stappenplan. Voorbeelden: `/review-pr`, `/opsx-new`, `/create-pr`. Je teamgenoot zou ook willen dat dit consistent gebeurt. +- **Subagent** — een aparte uitvoeringscontext met eigen tools en tokenbudget. Voor isolatie: zware exploratie, parallelle reviews. Een skill kan een subagent oproepen, maar is niet hetzelfde. + +Vuistregel: één-keer-werk → prompt. Herhaalbaar werk → skill. Zware isolatie van een sub-werkstroom → subagent. + +
+ +
+ +
+ +**2. Welke velden zijn verplicht in de frontmatter van een skill, en wat doen ze?** + +
+ +Twee velden zijn écht verplicht. Eén ervan beslist over de mapnaam, de ander beslist of Claude de skill ooit oppikt. + +
+ +
+ +Verplicht zijn: + +- **`name`** — moet exact gelijk zijn aan de mapnaam (folder `review-pr` → `name: review-pr`). Wijkt het af, dan kun je de skill niet via `/` triggeren. +- **`description`** — wat Claude leest om te beslissen of de skill relevant is. Action-oriented, derde persoon, concrete trigger-woorden vooraan. + +Optioneel, maar veel gebruikt: + +- **`metadata.category`** / **`metadata.tags`** — voor filteren/groeperen. +- **`disable-model-invocation: true`** — alleen handmatig oproepbaar. +- **`user-invocable: false`** — alleen automatisch laadbaar door Claude. +- **`allowed-tools`** — beperkt welke tools de skill mag gebruiken. + +Verreweg het belangrijkste veld is de `description`: alleen díe staat altijd in de system prompt geladen. De rest van de `SKILL.md` wordt pas gelezen wanneer de skill triggert. + +
+ +
+ +
+ +**3. Hoe weet Claude wanneer hij een skill moet triggeren — wat staat er in de `description`?** + +
+ +Claude leest een korte tekst en moet vervolgens op basis daarvan beslissen. Wat zou jij willen lezen om die beslissing snel en zeker te kunnen nemen? + +
+ +
+ +Claude vergelijkt de huidige conversatie tegen de `description` van elke beschikbare skill. Een goede description: + +1. **Begint met een action-werkwoord** (`Create`, `Review`, `Archive`, `Test`, …) — geen passieve constructies. +2. **Front-loadt de kern in de eerste ~250 tekens** — daarna kapt het skill-listing af. +3. **Bevat concrete trigger-woorden** — zelfstandige naamwoorden en werkwoorden die een gebruiker zou typen. ("Pull Request", "review", "GitHub branch" voor `review-pr`). +4. **Is geschreven in derde persoon** — hij wordt letterlijk in de system prompt geïnjecteerd. + +Vermijd vage formuleringen ("helpt met allerlei dingen"), de eerste persoon ("ik help je…"), en marketing-taal ("een handige tool voor…"). Concreet en specifiek wint altijd. + +Praktische realiteit: meerdere bronnen rapporteren ~50% auto-activatie zelfs voor goede skills, doordat Claude een vast contextbudget heeft voor skill-descriptions. Bij grote skill-libraries is expliciet `/skill-naam` typen altijd betrouwbaarder dan vertrouwen op auto-trigger. + +
+ +
+ +
+ +**4. Wanneer is het beter om een gewone prompt te schrijven dan een skill?** + +
+ +Vraag jezelf: ga ik dit nog een keer doen? En zou een collega dit ook willen? + +
+ +
+ +Schrijf een gewone prompt als… + +- Het werk is **eenmalig**: een specifieke refactor, een onderzoeksvraag, een ad-hoc analyse. +- Het is **persoonlijk**: alleen jij hebt het ooit nodig, niemand anders in het team — dan blijft het een eigen macro/snippet, geen team-skill. +- Het is **moeilijk mechanisch beschrijfbaar**: "het hangt sterk van de context af", veel uitzonderingen, veel oordeel. Skills zijn voor herhaalbare patronen, niet voor situatie-specifieke beslissingen. +- Je hebt **nog geen 3 keer hetzelfde gedaan**. Schrijf een skill pas wanneer je het patroon écht ziet — anders bouw je een abstractie op één voorbeeld. + +Vuistregel ("rule of three"): één keer is toeval, twee keer ook nog, drie keer is een patroon dat een skill verdient. + +
+ +
+ + + + + + + + diff --git a/academy/2026-05-15-claude-skills-tutorial-2-je-eerste-skill/index.mdx b/academy/2026-05-15-claude-skills-tutorial-2-je-eerste-skill/index.mdx new file mode 100644 index 000000000..c95caf4f3 --- /dev/null +++ b/academy/2026-05-15-claude-skills-tutorial-2-je-eerste-skill/index.mdx @@ -0,0 +1,412 @@ +--- +slug: claude-skills-tutorial-2-je-eerste-skill +title: "Claude Skills leerlijn — Deel 2: Je eerste skill schrijven" +contentType: tutorial +authors: [conduction] +date: 2026-05-15 +summary: Schrijf van scratch een werkende Claude Skill — een mini-skill die git-statussen samenvat. Met /skill-creator als startpunt, een goede description, lokaal testen, en opnemen in je registry. Tweede van vier korte modules. +tags: [Claude, Claude Code, Skills, AI, Tutorial series] +apps: [] +durationMinutes: 18 +series: claude-skills-tutorial +partNumber: 2 +--- + +import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard, ContactCta} from '@conduction/docusaurus-preset/components'; +import Details from '@theme/Details'; + +In deel 1 zag je wát een skill is. In dit deel ga je er zélf eentje schrijven: een werkende `git-status-summary` skill die een leesbare samenvatting maakt van de huidige working tree. Aan het eind staat hij in je `~/.claude/skills/`-folder, kun je hem aanroepen via `/git-status-summary`, en weet je hoe je hem deelt met je team. + +{/* truncate */} + + + Een complete skill scaffolden met /skill-creator in plaats van zelf folders aanleggen. + Een goede description schrijven die zowel handmatig (/) als automatisch betrouwbaar triggert. + De skill lokaal testen vóór je hem deelt — met een should-trigger / should-not-trigger checklist. + Wanneer een skill globaal hoort (~/.claude/skills/) en wanneer in een project (repo/.claude/skills/). + + + + Deel 1 doorgenomen: Wat zijn Claude Skills? + Claude Code geïnstalleerd, en een werkende sessie in een willekeurige Git-repo. Zelfs een leeg git init mapje is voldoende. + De skill-creator skill geïnstalleerd. Hij komt standaard mee als plugin met Claude Code; als je hem niet hebt: git clone https://github.com/anthropics/skills en kopieer skills/skill-creator/ naar je ~/.claude/skills/. + + +## Wat we gaan bouwen + +Een mini-skill die je `git status` leesbaar samenvat — met categorieën (staged / unstaged / untracked), bestandsaantallen, en een lijst van wijzigingen per categorie. Eenvoudig genoeg om in dit deel volledig te schrijven, maar concreet genoeg om alle stappen mee te illustreren. + +Eindresultaat: typ `/git-status-summary` in je sessie en je krijgt iets zoals: + +``` +Working tree status + +Staged (3 files): + • src/components/Button.vue — modified + • src/components/Card.vue — modified + • README.md — modified + +Unstaged (2 files): + • src/App.vue — modified + • src/utils/format.ts — modified + +Untracked (1 file): + • notes.txt +``` + +## Stap 1 — Scaffold met `/skill-creator` + +De makkelijkste manier om een nieuwe skill te starten is **niet** zelf folders aanleggen, maar `/skill-creator` laten begeleiden. Open een Claude-sessie en typ: + +``` +/skill-creator +``` + +Claude vraagt je vervolgens wat je wilt maken. Antwoord ongeveer zo: + +> Ik wil een skill `git-status-summary` die `git status --porcelain` draait, de output parset, en in drie groepen (staged / unstaged / untracked) presenteert met bestandsnamen en wijzigings-type. Hij hoort op alle Git-repo's te werken. + +`/skill-creator` zal vervolgens: + +1. Vragen om een korte description — geef hier de zorgvuldig geformuleerde versie van hieronder. +2. Vragen of de skill side-effects heeft (nee — alleen lezen). +3. Een mapje aanmaken: `~/.claude/skills/git-status-summary/` met een eerste `SKILL.md`. + +:::tip Niet bang zijn om met de hand te starten +Als `/skill-creator` om wat voor reden dan ook niet werkt, kun je het mapje ook gewoon aanmaken: `mkdir -p ~/.claude/skills/git-status-summary && touch ~/.claude/skills/git-status-summary/SKILL.md`. De skill is een doodgewoon tekstbestand. +::: + +## Stap 2 — De `description` zorgvuldig formuleren + +Herinner je uit deel 1: alleen de `description` staat altijd in de system prompt geladen. Hier valt of staat je auto-triggering. + +Een goede description voor onze skill: + +``` +description: Summarise the current Git working tree — groups changes by staged, unstaged, and untracked, and lists files per group with their change type +``` + +Wat zit daarin? + +| Stuk | Waarom | +|---|---| +| `Summarise` | Action-werkwoord vooraan. Geen "this skill is" of "helps with". | +| `current Git working tree` | Concrete trigger-woorden: iemand die "git status overview" of "what's changed" typt, raakt hier. | +| `groups changes by staged, unstaged, and untracked` | Vertelt Claude precies wat hij krijgt — handig bij keuzes tussen meerdere skills. | +| `lists files per group with their change type` | Sluit af met het waarneembare resultaat, zodat een gebruiker meteen herkent of dit is wat hij zoekt. | + +Schrijf 'm in **derde persoon** (de description wordt letterlijk geïnjecteerd in de system prompt) en blijf onder ~250 tekens — daarna kapt skill-listings af. + +## Stap 3 — Schrijf de `SKILL.md` + +Open `~/.claude/skills/git-status-summary/SKILL.md` en vul hem zo: + +````markdown +--- +name: git-status-summary +description: Summarise the current Git working tree — groups changes by staged, unstaged, and untracked, and lists files per group with their change type +metadata: + category: Workflow + tags: [git, workspace] +--- + +# Git Status Summary + +Produces a clean, grouped summary of the current Git working tree. + +**Input**: no arguments. Always reads `git status --porcelain` from the current +working directory. + +**Steps** + +1. **Verify a Git repo** + + Run `git rev-parse --is-inside-work-tree`. If it returns anything other than + `true`, abort with: *"Not inside a Git repository — cd into one first."* + +2. **Read the porcelain status** + + Run `git status --porcelain=v1` and split the output by line. Each line + starts with a two-character XY status code: + - `X` = index status, `Y` = working tree status + - `M` = modified, `A` = added, `D` = deleted, `R` = renamed, `??` = untracked + +3. **Group the lines** + + - **Staged**: lines where `X` is not blank and not `?` + - **Unstaged**: lines where `Y` is not blank and the line is not untracked + - **Untracked**: lines starting with `??` + + A single file can appear in both Staged and Unstaged (modified in both). + Show it in both groups when that happens. + +4. **Render the summary** + + Output in this exact shape (preserve formatting): + + ``` + Working tree status + + Staged (N files): + • path/to/file — modified + + Unstaged (N files): + • path/to/file — modified + + Untracked (N files): + • path/to/file + ``` + + Omit any group that has zero files. If all three are empty, output: + *"Working tree clean — nothing to commit."* + +**Guardrails** + +- Never run `git add`, `git commit`, `git reset`, or any other write command. + This skill is read-only. +- Never call `git status` without `--porcelain` — the human-readable form is + fragile across locales and Git versions. +- Never pass `--ignored` to `git status`. The "X is not blank and not `?`" + staged-check would otherwise also match `!` (ignored) lines and produce + a wrong staged group. +- Do not invent files. Only list what `git status --porcelain` actually returns. +```` + +Drie dingen om op te merken: + +1. **Genummerde stappen** — geen prozaïsche paragrafen. Eén handeling per stap, met een duidelijke kop. +2. **Een "Guardrails" sectie** — wat de skill expliciet niet mag. Voor read-only skills is de regel "geen schrijfacties" cruciaal. +3. **Concreet output-formaat in een code-block** — Claude volgt zichtbare voorbeelden veel betrouwbaarder dan vage instructies. + +### Hoe specifiek moet je SKILL.md zijn? — degrees of freedom + +Een veelgemaakte fout: alle skills hetzelfde detail-niveau geven. In de praktijk past je specificiteit zich aan de fragiliteit van de taak aan: + +| Soort taak | Vrijheid | Skill-stijl | +|---|:---:|---| +| Exploratie, brainstorm, code review | **Hoog** | Geef doelen en kaders, laat Claude de aanpak kiezen | +| Feature-werk, refactor | **Middel** | Geef stappen met beslispunten, laat Claude adapteren | +| DB-migraties, production-deploys, CI-config | **Laag** | Voorgeschreven commando's, expliciete confirmation-gates | + +Onze `git-status-summary` is read-only en mechanisch — middel-tot-laag, met expliciete formattering en harde guardrails. Een hypothetische `/explore-architecture` zou juist hoge vrijheid willen ("denk hardop, vergelijk opties, geen vaste vorm"). + +### Dynamic content: argumenten en shell-output injecteren + +Een laatste stuk syntax dat je verderop tegen gaat komen, maar nog niet nodig hebt voor `git-status-summary`: + +| Syntax | Betekenis | Voorbeeld | +|---|---|---| +| `$ARGUMENTS` | Alles wat na `/skill-naam` getypt is | `/app-create my-app` → `$ARGUMENTS` = `"my-app"` | +| `$ARGUMENTS[0]` | Eerste positionele argument | Eerste woord na de skill-naam | +| `${CLAUDE_SKILL_DIR}` | Absoluut pad naar de skill-folder | Voor verwijzingen naar bundled scripts | +| `` !`command` `` | Shell-output geïnjecteerd vóór de skill laadt | `` !`git branch --show-current` `` injecteert de huidige branch | + +Vooral de `` !`command` ``-syntax is krachtig: hij draait *voordat* de skill in context komt, dus je kunt runtime-informatie (current branch, git-user, datum) als context meegeven aan Claude. Niet nodig voor onze mini-skill, maar goed om te kennen voor wanneer je meer dynamische skills schrijft. + +### De gebruiker iets vragen: `AskUserQuestion` + +Sommige skills hebben input nodig die je niet uit shell-commando's kunt afleiden — bijvoorbeeld een keuze tussen twee aanpakken, of confirmatie vóór een destructieve actie. Daarvoor is er de **`AskUserQuestion`** tool: een ingebouwde Claude Code-tool waarmee een skill midden in z'n uitvoering een korte multiple-choice vraag aan de gebruiker stelt en op het antwoord wacht. + +In je `SKILL.md` instrueer je 'm gewoon in tekst: + +```markdown +3. **Confirm before applying** + + Use the `AskUserQuestion` tool to ask: *"Apply this migration to the + production database?"* with options `Yes, apply now` and `No, abort`. + Only continue to step 4 if the user picks the first option. +``` + +Wanneer gebruiken: bij beslismomenten waar Claude niet zelfstandig mag kiezen (production-deploys, branch-strategie, gevoelige refactors). Wanneer níet: voor info die je uit `git`, `gh` of het bestand zelf kunt halen — dan is `` !`command` `` of een Read-call sneller en minder storend. Voor `git-status-summary` is er geen keuzemoment, dus we gebruiken hem niet — maar onthou hem voor je volgende, zwaardere skill. + +## Stap 4 — Lokaal testen + +Vóór je de skill deelt: test hem zelf. Drie soorten tests: + +### A. Handmatige trigger + +Open een Claude-sessie in een Git-repo met wat unstaged wijzigingen en typ: + +``` +/git-status-summary +``` + +Wat je verwacht: een nette samenvatting in het beloofde formaat. Wat je controleert: klopt de telling? Mist er een bestand? Worden staged + unstaged dubbel getoond bij gemengde wijzigingen? + +### B. Auto-trigger checklist + +De moeilijkere test: triggert hij automatisch wanneer hij zou moeten, en blijft hij stil wanneer dat niet zou moeten? + +| Prompt | Verwacht gedrag | +|---|---| +| "Wat is er veranderd in mijn working tree?" | **Should trigger** | +| "Geef me een overzicht van staged en unstaged" | **Should trigger** | +| "Show me the diff for src/App.vue" | **Should not trigger** (dat is `git diff`, niet status) | +| "Commit my changes" | **Should not trigger** (write-actie, niet status) | +| "Wat doet git rebase?" | **Should not trigger** (informatieve vraag) | + +Loopt een prompt mis? Pas de `description` aan en probeer opnieuw. Triggert hij over-eager? Voeg specifiekere woorden toe ("working tree", "staged/unstaged/untracked"). Triggert hij niet vaak genoeg? Haal jargon weg en voeg natuurlijke termen toe ("git status overview", "what's changed"). + +### C. Edge cases + +- **Lege repo** — geen commits, geen wijzigingen. Krijg je de "Working tree clean" boodschap? +- **Alleen untracked** — `echo "test" > new.txt` zonder add. Wordt alleen de Untracked-groep getoond? +- **Bestand met spaties** — `touch "my file.txt" && git add "my file.txt"`. Wordt de naam correct gerenderd? + +## Stap 5 — Globaal of per project? + +Nu de skill werkt, is de vraag: waar laat je hem staan? + + + Voor skills die je in elke repo bruikbaar vindt. Onze git-status-summary is hier een goede kandidaat — het is een algemeen Git-hulpmiddel. Nadeel: je teamgenoten hebben hem niet automatisch. + + + + Voor skills die specifiek voor deze repo zijn (eigen quality-gates, eigen workflow, eigen domein-kennis). Voordeel: je commit hem, je team heeft hem, en CI/Hydra-containers gebruiken dezelfde versie. Bijna alle skills in Conduction-repo's leven hier. + + + + Voor skills die je van buiten importeert (Anthropic-community, Trail of Bits, OWASP). Apart houden helpt bij audit-spoor en onderhoud — externe skills muteer je niet, je volgt de bron. + + +Voor `git-status-summary` is globaal prima. Voor een fictieve `conduction-quality-check`-skill die de Conduction-specifieke PHPCS/Psalm/ESLint-config draait, hoort het in `.claude/skills/` van de project-repo zelf. + +## Stap 6 — Opnemen in de skill-registry + +Binnen Conduction houden we een lijst bij van alle interne skills (zowel persoonlijk relevant als project-niveau). Als je een nieuwe skill schrijft die voor het team waardevol is: + +1. **Voeg 'm toe aan de relevante repo** — meestal `/.claude/skills//` met een gewone commit. +2. **Update de `README.md` van die `.claude/skills/`-folder** als die bestaat — voeg een regel toe met naam, één-zin-omschrijving, en wanneer je hem aanroept. +3. **Update de Hydra skill-docs in [`ConductionNL/.github`](https://github.com/ConductionNL/.github)** als de skill in een Hydra-pipeline meedraait — de centrale documentatie over welke skills Hydra kent, in welke familie ze horen en wat ze doen, leeft daar. Zonder die update weet de rest van het team (en latere Hydra-runs) niet dat je skill bestaat. +4. **Open een PR** en laat één teamgenoot de skill triggeren in zijn eigen sessie. Niet de description theoretisch beoordelen — letterlijk een prompt typen die zou moeten triggeren. + +Voor globaal-persoonlijke skills (`~/.claude/skills/`) hoeft niets gecommit te worden — het is je eigen workshop. + +## Veelgemaakte beginnersfouten + +| Symptoom | Oorzaak | Fix | +|---|---|---| +| Skill triggert nooit automatisch | `description` is te vaag of mist trigger-woorden | Herschrijf in derde persoon, action-werkwoord vooraan, concrete zelfstandige naamwoorden uit de gebruiker-vraag | +| Skill triggert te vaak op onverwante prompts | `description` is te breed of overlapt met andere skills | Voeg specifieke afbakening toe ("only for X, not Y") | +| `/skill-naam` werkt niet | `name`-veld in frontmatter wijkt af van mapnaam | Maak ze identiek (folder `git-status-summary` → `name: git-status-summary`) | +| Skill doet iets onverwachts destructiefs | Geen guardrails | Voeg een **Guardrails** sectie toe met expliciete "never do X"-regels | +| Skill werkt op één laptop, niet op een andere | Hardcoded paden of OS-specifieke commando's | Gebruik relatieve paden; check OS-afhankelijke commando's (`grep` vs `ggrep`, etc.) | + +## Test jezelf + +Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Curieus naar het antwoord? Klik **Antwoord**. + +
+ +**1. Hoe test je een skill voordat je hem deelt met het team?** + +
+ +Drie soorten tests: een handmatige, een over auto-triggering, en eentje voor randgevallen. Wat doet elk daarvan? + +
+ +
+ +Drie niveaus: + +1. **Handmatige trigger** — typ `/skill-naam` in een sessie waar de skill van toepassing is. Controleer of de output klopt en het formaat strak is. +2. **Auto-trigger checklist** — verzin 5+ prompts die wél moeten triggeren en 5+ die níet mogen triggeren. Loop ze één voor één door. Triggert hij te weinig? `description` te vaag. Te vaak? `description` te breed. +3. **Edge cases** — test situaties die voorzienbaar mis kunnen gaan (lege input, speciale tekens, ontbrekende dependencies). Een skill die de happy path doet maar de leeg-geval mist, is niet af. + +Pas na alle drie deel je hem — anders ontdek je problemen in een teamgenoot z'n sessie in plaats van je eigen. + +
+ +
+ +
+ +**2. Waar zou je een skill voor "Conduction-specifieke PR-template invullen" plaatsen — globaal of per project?** + +
+ +De vraag is: voor wie is dit nuttig? Alleen jij, of het hele team? + +
+ +
+ +**Per project**, in `/.claude/skills/`. Reden: het is Conduction-specifiek (een algemene Claude-gebruiker zou er niets aan hebben) en je wilt dat het hele team — én Hydra's containers — dezelfde versie heeft. Commit hem in Git, zodat hij meekomt met elke fresh checkout. + +Globaal (`~/.claude/skills/`) is voor skills die jij over álle repo's heen handig vindt en die niets met de specifieke codebase te maken hebben. Bijvoorbeeld: een persoonlijke `summarise-clipboard` skill. + +
+ +
+ +
+ +**3. Waarom moet de `name` in de frontmatter exact gelijk zijn aan de mapnaam?** + +
+ +De naam die je achter `/` typt moet ergens vandaan komen. Wat ziet Claude eerst — de map of het frontmatter-veld? + +
+ +
+ +Claude vindt skills door je `~/.claude/skills/` en `.claude/skills/` te scannen op mappen met een `SKILL.md`. Voor de slash-trigger kijkt hij naar het `name`-veld in de frontmatter. Komen die niet overeen, dan zijn er twee mogelijke symptomen: + +- **`/foldername` werkt niet** maar `/nameveld` wel — verwarrend, want je teamgenoot kent alleen de mapnaam uit Git. +- **Auto-trigger werkt onverwacht** — Claude koppelt de description aan het frontmatter-name, niet aan de mapnaam. + +Vuistregel: kies één naam, gebruik 'm overal hetzelfde. `skill-creator` zorgt dat dit klopt; bij handmatig scaffolden moet je er zelf op letten. + +
+ +
+ +
+ +**4. Je hebt een skill die soms over-eager triggert op onverwante prompts. Wat is je eerste actie?** + +
+ +Welk veld bepaalt wanneer Claude een skill oppikt? En welk gereedschap heb je om dat veld te valideren? + +
+ +
+ +Pas de **`description`** aan. Concreet: + +1. **Maak hem specifieker** — voeg trigger-woorden toe die alleen bij de bedoelde situatie passen ("for the current Git working tree", niet alleen "for changes"). +2. **Voeg afbakening toe** — een korte zin als "Only for X, not Y" werkt verrassend goed. +3. **Test opnieuw** met je should-trigger / should-not-trigger checklist (zie vraag 1). + +Pas als de description al ijzersterk is en hij nóg over-eager is, overweeg `disable-model-invocation: true` om auto-triggering helemaal uit te zetten en alleen `/` toe te staan. Dat is een laatste redmiddel — meestal lost een betere description het op. + +
+ +
+ + + + + + + + diff --git a/academy/2026-05-15-claude-skills-tutorial-3-skill-evals/index.mdx b/academy/2026-05-15-claude-skills-tutorial-3-skill-evals/index.mdx new file mode 100644 index 000000000..a1fbc5479 --- /dev/null +++ b/academy/2026-05-15-claude-skills-tutorial-3-skill-evals/index.mdx @@ -0,0 +1,346 @@ +--- +slug: claude-skills-tutorial-3-skill-evals +title: "Claude Skills leerlijn — Deel 3: Skill-evals — meten of je skill werkt" +contentType: tutorial +authors: [conduction] +date: 2026-05-15 +summary: Van een skill die "goed voelt" naar een skill die gemeten goed werkt. Test-scenario's schrijven, baseline-meting, trigger-tests, en de eval-runner van /skill-creator. Derde van vier korte modules. +tags: [Claude, Claude Code, Skills, AI, Evals, Tutorial series] +apps: [] +durationMinutes: 15 +series: claude-skills-tutorial +partNumber: 3 +--- + +import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard, ContactCta} from '@conduction/docusaurus-preset/components'; +import Details from '@theme/Details'; + +Je hebt nu een werkende skill — maar werkt hij ook écht goed? En blijft hij goed werken als Claude zelf een upgrade krijgt of als je de skill aanpast? Dit derde, optionele deel laat zien hoe je een skill systematisch evalueert: test-scenario's, trigger-tests, een baseline-meting, en de eval-runner die `/skill-creator` voor je inricht. Dit is de stap van Maturity Level 4 ("voelt goed") naar Level 5 ("gemeten goed"). + +{/* truncate */} + + + Waarom skills die alleen "goed voelen" blinde vlekken hebben, en wat een baseline-meting daaraan toevoegt. + Het evals/evals.json-formaat schrijven: prompts, expectations, trigger-tests. + De eval-runner van /skill-creator draaien en de uitkomst lezen (timing.json, grading.json). + Wanneer een skill genoeg evals heeft om Maturity Level 5 te claimen — en wanneer je verder moet naar Level 6. + + + + Deel 1 en 2 doorgenomen, en idealiter een eigen werkende skill om te evalueren (de git-status-summary uit deel 2 werkt prima als oefenobject). + De skill-creator skill geïnstalleerd — de eval-runner zit daarin verstopt. + Een paar concrete scenario's waarin jouw skill is gebruikt of zou worden gebruikt — anders kun je geen realistische evals schrijven. + + +## Waarom meten? De 7 maturity-niveaus, kort + +Skills doorlopen zeven volwassenheidsniveaus, elk bovenop het vorige. Je hoeft niet alles te bouwen — een eenvoudig hulpmiddel hoeft niet voorbij L3 te komen. Maar voor skills die kritisch zijn (hele pipelines, gates, dagelijkse workflows) wil je verder dan "voelt goed": + +| Level | Wat erbij komt | Voor wie | +|---|---|---| +| **L1** Anatomy | `SKILL.md` met frontmatter, genummerde stappen, guardrails | Iedereen | +| **L2** Golden Rule | Optimale `description`, progressive disclosure, <500 regels | Iedereen die wil dat de skill goed triggert | +| **L3** Foundations | Gebouwd op proven patterns, met `examples/` of `references/` | Skills die meer dan een quick utility zijn | +| **L4** Personalisation | Bevat business-specifieke kennis (jouw ADRs, standaarden, domein) | Conduction-specifieke skills | +| **L5** Measurement | Heeft 3+ evals, trigger-tests, en `last_validated` is gezet | **Hier zit dit deel** | +| **L6** Self-improvement | Houdt `learnings.md` bij, consolideert periodiek | Skills die je intensief gebruikt | +| **L7** Workforce | Orkestreert sub-agents of zit in een workflow-keten | Hydra-achtige pipelines | + +Dit deel brengt je van L4 naar L5. Onderzoek toont aan dat ~80% van community-skills de output *slechter* maakt; de 20% die werkt is gebouwd door domein-experts mét iteratieve evaluatie. Meten is wat het verschil maakt. + +## Het probleem: wat je niet meet, weet je niet + +Een skill die "goed voelt" maar nooit gemeten is, kan drie verborgen problemen hebben: + +1. **Geen baseline** — je weet niet of de skill beter is dan Claude zonder skill. Misschien voegt hij niets toe. +2. **Onbekende auto-triggering** — je hebt 'm twee keer met succes met `/` aangeroepen, maar Claude pikt 'm in normale gesprekken nooit op. Of juist veel te vaak. +3. **Regressie na een edit** — je tweakt de `description` om scenario A te fixen en breekt onbedoeld scenario B. + +Evals lossen alle drie op. + +## De drie soorten meting + +| Meting | Antwoordt op de vraag | Output | +|---|---|---| +| **Eval scenarios** | Doet de skill wat hij moet doen op realistische prompts? | `grading.json` — pass/fail per scenario | +| **Trigger tests** | Triggert hij wel/niet automatisch zoals bedoeld? | `should_trigger` / `should_not_trigger` slagings-percentage | +| **Baseline** | Is de skill beter dan Claude zonder skill? | Een vergelijking naast elkaar | + +Je hebt alle drie nodig voor een schone L5-claim. + +## Het `evals.json`-formaat + +Maak in je skill-folder een subfolder `evals/` aan met daarin `evals.json`: + +```json +{ + "skill_name": "git-status-summary", + "version": "1.0.0", + "created": "2026-05-15", + "last_validated": null, + "evals": [ + { + "id": 1, + "prompt": "Geef een overzicht van wat er in mijn working tree is veranderd", + "expected_output": "Summary grouped by staged / unstaged / untracked with file paths", + "files": [], + "expectations": [ + "uses the three-group format from SKILL.md", + "calls git status --porcelain, not the plain git status", + "shows file path AND change type per line", + "omits empty groups" + ] + }, + { + "id": 2, + "prompt": "What's currently staged for commit?", + "expected_output": "Only the Staged group is shown when nothing else has changes", + "files": [], + "expectations": [ + "shows Staged group with correct file count", + "does not invent files not in git status", + "does not run git add or any write commands" + ] + }, + { + "id": 3, + "prompt": "Show me my git status", + "expected_output": "Triggers the skill and produces the canonical three-group summary", + "files": [], + "expectations": [ + "skill auto-activates on this prompt", + "produces full three-group format even if some groups are empty (they should be omitted)" + ] + } + ], + "trigger_tests": { + "should_trigger": [ + "Wat is er veranderd in mijn working tree?", + "Show me my git status", + "Geef me een overzicht van staged en unstaged", + "What files are currently modified?", + "Summarise my working tree", + "Is er nog iets onveranderd dat ik vergeten ben te committen?", + "What's the state of my repo right now?", + "Welke files staan er klaar voor commit?", + "Give me a working tree status overview", + "Hoe ziet mijn git-tree er nu uit?" + ], + "should_not_trigger": [ + "Show me the diff for src/App.vue", + "Commit my changes", + "Push to the remote branch", + "What does git rebase do?", + "Explain the difference between git merge and rebase", + "Help me write a commit message", + "Run git log for me", + "How do I resolve this merge conflict?", + "Pull the latest changes from main", + "What is a feature branch?" + ] + } +} +``` + +Drie stukken om op te merken: + +1. **3+ evals zijn het minimum** voor L5. Schrijf ze vanuit realistisch gebruik — wat zou een teamgenoot écht intypen? +2. **10+ should-trigger en 10+ should-not-trigger** prompts. Saaier om te schrijven dan je denkt, maar onmisbaar — dit is waar over-eagerness en blinde vlekken zichtbaar worden. +3. **`last_validated: null`** — dit veld zet de runner zelf wanneer hij draait. Zolang het `null` is, telt je skill nog niet als gemeten. + +## De eval-runner draaien + +`/skill-creator` heeft een ingebouwde eval-runner. Open een Claude-sessie in een repo met je skill en typ: + +``` +/skill-creator +``` + +Zeg in je eerste prompt iets als: *"Ik wil de evals draaien voor mijn `git-status-summary` skill."* `/skill-creator` herkent dat je in de evaluatie-fase zit en doet vier dingen: + +1. **Spawnt parallel twee subagents** — eentje met toegang tot je skill, eentje zonder (baseline). +2. **Draait elke eval-prompt** in beide contexten, registreert tokens en duur in `timing.json`. +3. **Toetst de output tegen je `expectations`** en schrijft pass/fail naar `grading.json`. +4. **Opent een lokale benchmark-viewer** waar je beide kanten visueel kunt vergelijken. + +Na de run vind je in `evals/`: + +``` +evals/ +├── evals.json ← jouw scenario's +├── timing.json ← per scenario: tokens + duur, met- en zonder-skill +├── grading.json ← per scenario: assertion pass/fail + bewijs +└── (eventueel) trigger-results.json +``` + +Voor de L5-claim heb je álledrie nodig: `evals.json` (geschreven door jou), `timing.json` (de runner heeft daadwerkelijk gedraaid, geen statische read-only simulatie), én `grading.json` (assertions zijn gescoord). Vergeet ook niet `last_validated` bij te werken in `evals.json` zelf. + +## De resultaten lezen + +Drie vragen om jezelf te stellen bij elk eval-rapport: + +### 1. Slaagt de skill alle expectations? + +Open `grading.json` en kijk welke `expectations` `pass: false` opleveren. Elk failed-item is een concreet verbeterpunt: ofwel mist je `SKILL.md` instructie, ofwel zijn de stappen niet specifiek genoeg, ofwel triggert er een edge case die je niet beschreven hebt. + +### 2. Is de skill beter dan baseline? + +Vergelijk de met-skill output naast de zonder-skill output in de benchmark-viewer. Twee uitkomsten zijn rode vlaggen: + +- **Skill = baseline** — beide produceren ongeveer hetzelfde. Je skill voegt niets toe, behalve misschien wat formattering. Overweeg of het de moeite waard is om hem te hebben. +- **Skill < baseline** — Claude zonder skill is *beter*. Dit gebeurt vaker dan je denkt; de skill kan over-prescriptief zijn, of in conflict met wat Claude al goed kan. Vereenvoudig de skill of trek 'm in. + +### 3. Hoe scoren je trigger-tests? + +De runner draait elke `should_trigger` en `should_not_trigger` prompt in een verse context en checkt of de skill (auto-)laadt. Streef naar 90%+ op beide kanten. Onder de 80%? Iterate op de description. + +> **Realiteitscheck:** meerdere bronnen rapporteren ~50% auto-activatie zelfs voor goede skills, omdat Claude een vast contextbudget heeft voor skill-descriptions. Bij grote skill-libraries is auto-trigger inherent minder betrouwbaar dan expliciet `/` typen. Een lage score betekent niet altijd "skill is slecht" — soms is "skill is te één van vele". Eval-resultaten lees je in context. + +## Iteratie-cyclus + +Eén ronde evals draaien is alleen maar het begin. Het patroon: + +``` +schrijf evals → run → lees resultaten → identificeer 1 zwakte → fix → re-run +``` + +Eén verbetering per ronde — niet vier tegelijk. Anders weet je achteraf niet welke fix welk verschil maakte. + + + Identificeer één specifieke zwakte uit de benchmark-viewer (één gefaalde expectation, of één trigger-test die de verkeerde kant op ging). Pas de SKILL.md alleen op dát punt aan. Draai opnieuw. Pas dan ga je door naar de volgende zwakte. + + +## Wanneer is een skill "gemeten genoeg"? + +Voor L5-claim heb je minimaal: + +- ✅ 3+ evals in `evals.json` +- ✅ 10+ should-trigger + 10+ should-not-trigger prompts +- ✅ `last_validated` is gevuld (niet `null`) +- ✅ Zowel `timing.json` als `grading.json` aanwezig (bewijs dat de runner gedraaid heeft, niet alleen een read-only beoordeling) +- ✅ Een baseline-meting (skill vs. zonder skill) +- ✅ Minstens één iteratie-cycle doorlopen op basis van eval-resultaten + +Klopt dat allemaal? Dan kun je de skill als L5-mature labellen. Wil je verder — en voor skills die je dagelijks gebruikt of die andere skills aansturen wil je dat — dan komt L6 (`learnings.md` met capture-loop) en uiteindelijk L7 (multi-agent orchestratie). [Deel 4 van de leerlijn](/academy/claude-skills-tutorial-4-l6-l7-mature-skills) loopt die laatste twee niveaus door, plus het dashboard waarmee je je hele skill-library tegelijk op maturity bewaakt. Voor ~80% van de skills is L5 het natuurlijke eindpunt; deel 4 is voor de skills die dat niet zijn. + +## Test jezelf + +Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Curieus naar het antwoord? Klik **Antwoord**. + +
+ +**1. Waarom is een baseline-meting belangrijk bij skill-evals?** + +
+ +Een skill kan slagen op al je expectations en tóch geen waarde toevoegen. Wat zou je daarvoor moeten weten? + +
+ +
+ +Omdat een skill die alle expectations haalt, nog steeds **niets kan toevoegen ten opzichte van Claude zonder skill**. Of erger: Claude zonder skill kan beter zijn (over-prescriptieve skills knellen Claude's eigen oordeel). + +De baseline draait elke eval-prompt twee keer: eenmaal met toegang tot je skill, eenmaal zonder. Pas dan kun je zeggen "deze skill maakt verschil X". Zonder baseline meet je alleen of de skill consistent is — niet of hij waardevol is. + +Drie mogelijke uitkomsten bij baseline-vergelijk: + +- **Skill > baseline** → de skill verdient zijn plek. +- **Skill ≈ baseline** → twijfelgeval. Overweeg of de winst (formattering, consistentie) opweegt tegen de context-cost. +- **Skill < baseline** → skill weghalen of fundamenteel herzien. + +
+ +
+ +
+ +**2. Wat is het verschil tussen `evals` en `trigger_tests` in `evals.json`?** + +
+ +Eén meet of de skill goed werk levert. De andere meet of hij überhaupt op het juiste moment opduikt. + +
+ +
+ +- **`evals`** meet **kwaliteit van uitvoering**: gegeven dat de skill is geladen, doet hij wat hij moet doen? Drie expectations per scenario, getoetst tegen de output. Score: hoeveel expectations slagen? +- **`trigger_tests`** meet **auto-activatie**: pikt Claude de skill automatisch op bij relevante prompts (`should_trigger`), en blijft hij weg bij niet-relevante prompts (`should_not_trigger`)? + +Een skill kan perfect scoren op `evals` maar slecht op `trigger_tests` — dan werkt hij goed *als* je `/` typt, maar pikt Claude hem nooit zelf op. Andersom: een skill kan altijd triggeren maar slechte output leveren — dan trekt hij ongevraagd aan tafel zonder waarde toe te voegen. + +Je hebt beide nodig voor L5. + +
+ +
+ +
+ +**3. Welke twee bestanden bewijzen dat de eval-runner echt gedraaid heeft, en waarom allebei?** + +
+ +Een statische beoordeling van je `evals.json` is niet voldoende — je hebt bewijs nodig dat er code is uitgevoerd én dat er resultaten zijn gescoord. + +
+ +
+ +- **`timing.json`** bewijst **uitvoering**: per scenario staat hierin hoeveel tokens er zijn gebruikt en hoe lang de run duurde. Dat kan alleen na werkelijke executie — een read-only simulatie kan dit veld niet vullen. +- **`grading.json`** bewijst **beoordeling**: pass/fail per expectation, met bewijs. Dit is de daadwerkelijke score van je skill. + +`grading.json` alleen is niet genoeg — die zou je theoretisch kunnen genereren door een statische read van `SKILL.md` en `evals.json`. Pas met `timing.json` erbij heb je hard bewijs dat de runner ook echt heeft gedraaid. + +In de Conduction-skill-check is dit precies waar het script L5 op valideert: beide bestanden moeten bestaan, plus `last_validated` mag niet `null` zijn. + +
+ +
+ +
+ +**4. Je hebt evals gedraaid en één expectation faalt consistent. Wat is je volgende stap — en wat juist niet?** + +
+ +Niet hagelschot fixen. Wat is de meest leerzame manier om te iteren? + +
+ +
+ +**Wel doen:** identificeer die ene gefaalde expectation, lees in de benchmark-viewer wat de skill in plaats daarvan deed, en pas één specifiek stuk van `SKILL.md` aan (een stap, een guardrail, of een output-voorbeeld). Draai daarna opnieuw — pas dan ga je verder met de volgende zwakte. + +**Niet doen:** vier tegelijk fixen, of meteen de `description` overhoop halen omdat "er iets niet goed gaat". Bij meerdere wijzigingen tegelijk weet je niet welke iets bijdroeg. Bij een description-herziening verschuif je auto-triggering en je executie-gedrag tegelijkertijd — onmogelijk om uit elkaar te halen. + +Vuistregel: één eval-cyclus = één hypothese = één fix. Saai? Misschien. Maar je leert er sneller van dan van "alles ineens" pogingen. + +
+ +
+ + + + + + + + diff --git a/academy/2026-05-15-claude-skills-tutorial-4-l6-l7-mature-skills/index.mdx b/academy/2026-05-15-claude-skills-tutorial-4-l6-l7-mature-skills/index.mdx new file mode 100644 index 000000000..6b7b2730d --- /dev/null +++ b/academy/2026-05-15-claude-skills-tutorial-4-l6-l7-mature-skills/index.mdx @@ -0,0 +1,341 @@ +--- +slug: claude-skills-tutorial-4-l6-l7-mature-skills +title: "Claude Skills leerlijn — Deel 4: Van gemeten naar lerend en orkestrerend (L6 → L7)" +contentType: tutorial +authors: [conduction] +date: 2026-05-15 +summary: Hoe ga je voorbij L5? L6 zet een learnings-loop op je skill, L7 maakt er een orkestrator van die sub-agents aanstuurt. Plus een blik op het skill-level-overview dashboard waarmee je je hele skill-library in één oogopslag op maturity kunt scannen. Vierde van vier korte modules. +tags: [Claude, Claude Code, Skills, AI, Maturity, Tutorial series] +apps: [] +durationMinutes: 18 +series: claude-skills-tutorial +partNumber: 4 +--- + +import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard, ContactCta} from '@conduction/docusaurus-preset/components'; +import Details from '@theme/Details'; + +In deel 3 bracht je een skill van "voelt goed" naar "gemeten goed" — Maturity Level 5. Voor de meeste skills is dat genoeg. Maar voor een handvol skills die je dagelijks gebruikt of die andere skills aansturen, wil je verder: een skill die **leert van zijn eigen executies** (L6), en een skill die **andere agents aanstuurt** in een grotere workflow (L7). Dit vierde deel laat zien hoe je daar komt — én hoe je met het Hydra-dashboard je hele skill-library tegelijk op maturity bewaakt. + +{/* truncate */} + + + Wat een learnings-loop is, en hoe een learnings.md + capture-stap je skill iedere executie iets slimmer maakt (L6). + De two-stage buffer (learning-candidates.md → learnings.md → SKILL.md) en wanneer je consolideert. + Hoe een skill een orkestrator wordt: sub-agents spawnen, workflow-chains, hand-off, fan-out/fan-in patronen (L7). + Het verschil tussen structureel L7 en mature L7 — en waarom je beide nodig hebt. + Het skill-level-overview.html dashboard en het update-skill-overview.sh script die je hele skill-library live op maturity tonen. + + + + Deel 1, 2 en 3 doorgenomen — vooral deel 3, want L6 en L7 bouwen direct op L5 metingen. + Een skill die je minstens een paar weken intensief hebt gebruikt. L6 zonder echte execution-historie is een lege schil. + Voor L7: een use-case waarin je werk parallel kunt verdelen of die zich in een keten van skills laat ontleden. + + +## Recap: de 7 niveaus, en waar we nu zijn + +| Level | Wat erbij komt | +|---|---| +| L1–L4 | Anatomy, golden rule, foundations, business-context | +| **L5** | 3+ evals, trigger-tests, baseline — *deel 3* | +| **L6** | `learnings.md` + capture-stap, periodieke consolidatie — *dit deel* | +| **L7** | Orkestreert sub-agents of zit in een workflow-keten — *dit deel* | + +Belangrijk: niveaus zijn **cumulatief in criteria** maar niet altijd in praktijk. Een skill kan een L7-architectuur hebben (spawnt acht parallele agents) zonder L5-evals te hebben — dat heet *structureel L7, maturity L4*. De architectuur is er, de zelfkennis nog niet. Voor échte L7 moet je L1 t/m L6 erbij hebben. + +## Niveau 6: Self-Improvement — skills die leren van hun executies + +Een L5-skill is gemeten, maar statisch. Elke executie verloopt los van de vorige; wat je vorige week leerde, vergeet de skill morgen. **L6 lost dat op met een learnings-loop**: + +``` +Executie → observaties vastleggen → learnings.md → consolidatie → SKILL.md-regels + ↑ │ + └──────────────────────────────┘ +``` + +### De ingrediënten + +Drie dingen maken een skill L6-mature: + +1. Een **`learnings.md`** in de skill-folder. +2. Een **"Capture Learnings"-stap** aan het einde van `SKILL.md` — Claude wordt aan het einde van elke executie expliciet gevraagd of er iets in de uitvoering opviel dat een toekomstige run kan helpen. +3. Een **consolidatie-ritme** — typisch bij 80–100 entries kijk je terug, verwijder je verouderde regels, voeg je duplicaten samen, en promoveer je gevalideerde principes naar guardrails in `SKILL.md` zelf. + +### Het formaat van `learnings.md` + +Elke entry is **gedateerd en atomair** (één inzicht per bullet). De file kent vijf vaste secties: + +```markdown +# Learnings — create-pr + +## Patterns That Work +- 2026-03-15: Branch protection op `main` vraagt status checks — altijd eerst verifiëren dat ze bestaan. +- 2026-03-20: GitHub issue-nummer in PR-titel verbetert traceability. + +## Mistakes to Avoid +- 2026-03-18: NIET een PR maken met uncommitted changes — onduidelijk wat er meegaat. +- 2026-03-22: composer.lock-conflict → lokaal `composer update` draaien, niet één kant accepteren. + +## Domain Knowledge +- 2026-03-19: Conduction-repos gebruiken `development` als primaire integratie-branch, niet `main`. + +## Open Questions +- Moeten PRs reviewers auto-assignen via CODEOWNERS? + +## Consolidated Principles +- (gepromoot na 3+ bevestigingen) +- Draai altijd `composer check:strict` vóór PR-aanmaak — vangt 90% van review-feedback af. +``` + +### De "Capture Learnings"-stap in `SKILL.md` + +`learnings.md` vult zichzelf niet — Claude doet dat alleen als je hem er expliciet om vraagt. De manier waarop is een vaste stap, helemaal onderaan in `SKILL.md`, na alle uitvoeringsstappen en vóór de guardrails. Hieronder een voorbeeld zoals het in Conductions `create-pr`-skill staat — kort, met de vijf rubrieken vooropgesteld zodat Claude ze niet vergeet: + +```markdown +## Capture Learnings + +After execution, review what happened and append new observations to +[learnings.md](learnings.md) under the appropriate section: + +- **Patterns That Work** — approaches that produced good results +- **Mistakes to Avoid** — errors encountered and how they were resolved +- **Domain Knowledge** — facts discovered during this run +- **Open Questions** — unresolved items for future investigation + +Each entry must include today's date. One insight per bullet. +**Skip if nothing new was learned** — do NOT invent learnings to fill the section. +``` + +Drie dingen om op te merken: + +1. **Het is een stap, geen suggestie.** Door hem als `## Capture Learnings`-sectie te formatteren staat hij in dezelfde "doe-dit"-modus als de andere genummerde stappen. Een vrijblijvende zin als "denk eraan om eventueel iets op te schrijven" wordt door Claude vaak overgeslagen. +2. **"Skip if nothing new was learned"** is cruciaal. Zonder die regel produceert Claude *altíjd* iets — pseudo-inzichten zoals "de skill werd succesvol uitgevoerd". Dat is precies het soort ruis dat `learnings.md` snel vervuilt en bij elke run je context-budget opslokt. +3. **Per sectie expliciet de bedoeling vermelden** — niet alleen de naam. "Patterns That Work" alléén triggert generieke inhoud; "approaches that produced good results" stuurt Claude naar gericht observerend gedrag. + +Variant voor skills met een container/headless-modus: voeg een tabel toe boven aan `SKILL.md` die expliciet zegt dat de Capture Learnings-stap in headless-mode wordt overgeslagen (een container-filesystem is wegwerp — schrijven naar `learnings.md` is verspilling). De `opsx-apply`-skill doet dat zo: + +```markdown +| Stap | Interactive mode | Headless mode (CI) | +|---|---|---| +| Capture Learnings | Append to `learnings.md` | **Skip** — container filesystem is disposable | +``` + +### De two-stage buffer (sterk aanbevolen) + +Het naïeve patroon — élke observatie direct in `learnings.md` schrijven — vult het bestand snel met ruis. Een teamgenoot meldde iets dat "vreemd voelde" maar bij nader inzien een toevallige fluctuatie was; nu staat het er, en bij elke run leest Claude het mee. + +De fix is een **two-stage buffer**: + +``` +learning-candidates.md → (promotie-criteria gehaald?) → learnings.md → SKILL.md-regels + ↓ (nee) + verwijderd na 30 dagen +``` + +Promotie-criteria zijn bewust streng: +- Observatie minstens **3 keer bevestigd** in losse executies, **óf** +- Observatie lost een **gemeten eval-failure** op uit deel 3. + +Zo blijft `learnings.md` schoon en wordt context-budget niet verspeeld aan eenmalige toevalligheden. + +### Wanneer consolideer je? + +Bij ~80–100 entries wordt `learnings.md` zelf een context-last. Trigger op dat moment een consolidatie-ronde: + +1. **Verouderd?** — verwijderen (de bug is gepatcht, de regel niet meer relevant). +2. **Duplicaten?** — samenvoegen tot één scherpere bullet. +3. **Cross-cutting patroon?** — promoveer naar de "Consolidated Principles"-sectie. +4. **Gevalideerd principe?** — schrijf het als guardrail of stap rechtstreeks in `SKILL.md`, en haal de losse observaties die ernaar leidden uit `learnings.md`. + + + Een L6-skill heeft een learnings.md met gedateerde, atomaire entries; SKILL.md eindigt met een "Capture Learnings"-stap; en je consolideert periodiek zodat principes in regels veranderen. + + +## Niveau 7: AI Workforce — skills die andere agents aansturen + +L7 is geen "betere skill" — het is een **andere soort skill**. Een L7-skill levert geen output zelf, maar **coördineert** een team van sub-agents of zit zelf in een keten van skills die naar elkaar handoffen. + +### Criteria (boven op L6) + +- **Spawnt sub-agents** (parallelle workers) **óf** wordt zelf aangeroepen door een parent-skill. +- Zit in een **gedefinieerde workflow-chain** met expliciete hand-off-punten: + ``` + opsx-new → opsx-ff → opsx-plan-to-issues → opsx-apply → opsx-verify → opsx-archive + ``` +- **Geeft context door** aan de volgende skill (toont "Next step: run `/opsx-verify`"). +- Gebruikt **geïsoleerde execution-contexten** waar nodig (git worktrees, Docker, sandbox). +- Heeft **autonomy** binnen een gedefinieerde scope — niet alles vraagt om bevestiging. +- Doet **parallel werk** (acht agents tegelijk, fan-out/fan-in). + +### Orkestratie-patronen in Hydra + +| Patroon | Voorbeeld in Hydra | Beschrijving | +|---|---|---| +| **Pipeline** | `opsx-pipeline` | Volledige lifecycle voor 1+ changes parallel via subagents | +| **Fan-out/Fan-in** | `test-counsel`, `feature-counsel` | N agents parallel spawnen, daarna één synthese | +| **Sequential Chain** | `opsx-new → … → opsx-archive` | Elke skill handoft naar de volgende | +| **Autonomous Loop** | `opsx-apply-loop` | Draait apply→verify-cycle met retry, auto-archive | +| **Multi-perspective** | `test-app` | Zes gespecialiseerde test-agents simultaan | + +### Structureel L7 ≠ mature L7 + +Het is verleidelijk om een orkestrator op te tuigen en je werk klaar te verklaren. Maar als die orkestrator geen evals heeft (L5) en geen learnings-loop (L6), heb je een **complexe machine zonder zelfkennis**. Hij dóét veel; je weet niet hoe goed. + +Dat heet *structureel L7, maturity L4*. Het probleem: zodra hij faalt, weet niemand wáár in de keten het misging, en de fout herhaalt zich morgen weer. Voor échte L7 moet je de L5- en L6-gaten dichten — meet de keten als geheel én op handoff-punten, en laat de orkestrator zelf observaties capturen. + + + Een L7-skill orkestreert anderen — maar pas als hij óók is gemeten (L5) en leert (L6) is hij ook écht mature; daarvoor is het architectuur zonder zelfreflectie. + + +## De hele library bewaken: het skill-level-overview-dashboard + +Eén skill op L6 of L7 brengen is overzichtelijk. Maar zodra je een library van tien, twintig of (zoals in Hydra) ~70 skills hebt, wil je in één oogopslag zien welke skill op welk niveau zit — en welke achteruit kachelen. + +Daarvoor zitten er in de Conduction-skill-toolchain twee bestanden onder `.claude/skills/`: + +- **`skill-level-overview.html`** — een lokaal, statisch HTML-dashboard. Eén tabel met per skill: huidige maturity (groene dots), target maturity (badge), regelaantal van `SKILL.md` (gekleurd: groen ≤200, geel rond 450, rood >500), en oranje rings voor "structureel aanwezig maar nog niet mature". Sorteerbaar per kolom. Je opent 'm lokaal — `xdg-open` op Linux, `open` op macOS, of dubbelklik in de file-explorer. +- **`update-skill-overview.sh`** — een bash-script dat over alle skill-folders heen scant, structurele markers detecteert (frontmatter aanwezig, guardrails, evals met genoeg trigger-tests, dated learnings, agent-spawns), en de HTML up-to-date schrijft. Eén `./update-skill-overview.sh` en je dashboard klopt weer. + +### Wat het script automatisch doet + +Het script detecteert: + +| Niveau | Wat het script meet | +|---|---| +| **L1** | Frontmatter aanwezig, genummerde stappen, guardrail-secties of directieven, `name:` matcht foldernaam | +| **L2** | `SKILL.md` ≤500 regels en `description:` is gevuld | +| **L3** | Proven patterns + `examples/` of `references/` of `templates/` aanwezig | +| **L4** | **NOOIT automatisch** — moet je handmatig bevestigen (domein-kennis is mensenwerk) | +| **L5** | `evals/evals.json` met 3+ scenario's, 10+ should-trigger, 10+ should-not-trigger, `last_validated` gevuld, plus `grading.json` én `timing.json` | +| **L6** | `learnings.md` met gedateerde entries + "Capture Learnings"-stap in `SKILL.md` | +| **L7** | Verwijzingen naar `Agent tool`, `subagent` of `Task agents` in `SKILL.md` | + +Belangrijke ontwerpkeuze: **L4 wordt nooit automatisch gezet**. Domein-kennis is niet detecteerbaar uit bestandsstructuur — alleen jij kunt zeggen of een skill jouw business écht kent. Het script roept luidruchtig om aandacht als L1–L3 staan maar L4 nog niet bevestigd is. + +### Hoe je dit voor je eigen project opzet + +De canonieke `skill-level-overview.html` + `update-skill-overview.sh` zitten in Conductions interne skill-toolchain. Twee paden om er zelf mee aan de slag te gaan: + +- **Vraag de bestanden op** — mail ons via de CTA onderaan deze pagina; we delen de huidige versie graag met klanten en partners als startpunt. Daarna is het `cp` naar `/.claude/skills/` en `bash .claude/skills/update-skill-overview.sh` draaien. +- **Bouw zelf** — de meet-regels uit de tabel hierboven zijn alles wat je nodig hebt. Een paar uur bash + een simpele HTML-tabel-template levert al een werkende v1. + +Een natuurlijke plek om het script te draaien: vóór een release-tag, in een pre-commit hook op de skills-folder, of als wekelijkse cron. Zo zie je drift (een skill die naar 520 regels groeit en dus L2 verliest) meteen. + + + Draai update-skill-overview.sh wekelijks; consolideer learnings.md per skill ad-hoc bij ~80 entries. Het dashboard vertelt je welke skill aan de beurt is. + + +## Test jezelf + +Vier korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik **Hint**. Curieus naar het antwoord? Klik **Antwoord**. + +
+ +**1. Waarom een two-stage buffer in plaats van direct in `learnings.md` schrijven?** + +
+ +Wat gebeurt er als élke vluchtige observatie meteen permanent wordt opgeslagen? + +
+ +
+ +Omdat directe schrijfacties `learnings.md` snel verontreinigen met eenmalige toevalligheden, foutdiagnoses die later anders bleken te zijn, of observaties die alleen voor één specifieke context relevant waren. Elk daarvan blijft daarna in elke executie meegelezen worden, en eet van je context-budget. + +De two-stage buffer (`learning-candidates.md` → `learnings.md`) plaatst een filter ertussen: alleen observaties die minstens 3 keer bevestigd zijn, of die een gemeten eval-failure oplossen, promoveren. De rest valt na 30 dagen vanzelf weg. Het resultaat: `learnings.md` blijft hoogwaardige signalen bevatten, en bij consolidatie heb je echte patronen om te promoten naar `SKILL.md`-guardrails. + +
+ +
+ +
+ +**2. Wat is het verschil tussen "structureel L7" en "mature L7", en waarom is dat verschil belangrijk?** + +
+ +Een skill kan architecturaal complex zijn zonder ooit gemeten of geleerd te hebben. + +
+ +
+ +- **Structureel L7** betekent dat de skill *de architectuur* van orkestratie heeft: spawnt subagents, zit in een keten, doet hand-offs, draait parallel. +- **Mature L7** betekent dat die architectuur óók is gemeten (L5) en zichzelf verbetert (L6). + +Een "structureel L7, maturity L4" skill is een complexe machine zonder zelfkennis. Hij dóét veel — je weet niet hoe goed. Zodra hij faalt is onduidelijk wáár in de keten het misging, en de fout herhaalt zich morgen. + +Het verschil is belangrijk omdat orkestrators groot blast-radius hebben: ze starten meerdere agents, doen meerdere acties, raken meerdere bestanden. Als je daarbij geen evals (L5) en geen learnings (L6) hebt, schaal je fouten net zo hard mee als successen. + +
+ +
+ +
+ +**3. Waarom zet `update-skill-overview.sh` L4 nooit automatisch?** + +
+ +Wat onderscheidt L4 van L1, L2, L3, L5, L6 en L7 in termen van wat detecteerbaar is? + +
+ +
+ +Omdat L4 — "Personalization / Domain Knowledge" — betekent dat de skill jouw bedrijfsspecifieke kennis bevat (ADRs, naming conventions, business-rules, "wij gebruiken `development` als primaire branch"). Dat is niet detecteerbaar uit bestandsstructuur, regelaantallen of frontmatter-velden. + +L1 t/m L3 zijn structureel (frontmatter, guardrails, voorbeelden — een script kan ze zien). L5 t/m L7 zijn structureel óf gedrags-gerelateerd op manieren die in bestanden zichtbaar zijn (evals-json, learnings.md, agent-calls). + +Maar L4 vereist een mens die zegt: "ja, deze skill kent ónze codebase, niet alleen generieke best-practices." Daarom roept het script luid om jouw input zodra L1–L3 zijn behaald maar L4 nog niet bevestigd. Tot dat moment blijft de skill op M3 hangen, ook al zijn er structureel al hogere niveaus aanwezig. + +
+ +
+ +
+ +**4. Een teamgenoot heeft een skill die parallel zes test-agents spawnt, maar geen evals en geen `learnings.md` heeft. Wat zou jij voorstellen — eerst meer orkestratie toevoegen, of eerst iets anders?** + +
+ +Denk aan het verschil tussen architectuur en zelfkennis. + +
+ +
+ +Eerst **niet meer orkestratie**. Deze skill is structureel L7, maturity L4 — de architectuur staat er, maar er is geen meting (L5) en geen lerend gedrag (L6). Meer parallellisme of meer chain-skills toevoegen vergroot alleen de blast-radius. + +De juiste volgorde: + +1. **L5 eerst** — schrijf 3+ eval-scenario's voor de hele keten en voor de hand-off-punten. Pas dan weet je of de zes parallele agents elkaar niet tegenwerken of dubbel werk doen. +2. **L6 daarna** — voeg een `learnings.md` toe waarin de orkestrator vastlegt welke patronen tussen agents wel/niet werken (bv. "wanneer agent A en B beide hetzelfde bestand aanraken, ontstaat conflict X"). +3. **Pas dan L7-uitbreiding** — als de keten gemeten en lerend is, kun je hem veilig uitbreiden (meer agents, langere chain, autonome loop). + +Vuistregel: orkestratie zonder meting is ongeoorloofd schaalwerk. Eerst weten of de keten werkt, dan groter maken. + +
+ +
+ + + + + + + diff --git a/src/css/site.css b/src/css/site.css index 0304398ef..b565423c5 100644 --- a/src/css/site.css +++ b/src/css/site.css @@ -119,6 +119,23 @@ } } +/* Default blockquote treatment, brand-aligned. Infima's default leaves + quotes in neutral gray-800 with a 2px gray border, which reads + washed-out next to the cobalt-700 body. Bring the quote into the + cobalt palette so it carries the brand and stands out as a callout. */ +:root { + --ifm-blockquote-color: var(--c-cobalt-900); + --ifm-blockquote-border-color: var(--c-blue-cobalt); + --ifm-blockquote-border-left-width: 4px; + --ifm-blockquote-padding-vertical: var(--space-3); + --ifm-blockquote-padding-horizontal: var(--space-5); +} +blockquote { + background: var(--c-cobalt-50); + border-radius: 0 var(--radius-md) var(--radius-md) 0; + font-size: 1.05em; +} + /* Pull-quote treatment for editorial sections (used on /commonground). */ .cg-pullquote { border-left: 4px solid var(--c-orange-knvb); @@ -144,3 +161,78 @@ color: var(--c-cobalt-700); } +/* "Test jezelf" sections at the end of academy tutorials: each question + sits in a card with collapsible Hint and Antwoord
blocks. */ +.tutorial-quiz { + margin: var(--space-5) 0 var(--space-6); + padding: var(--space-4) var(--space-5); + background: var(--c-cobalt-50); + border-left: 3px solid var(--c-blue-cobalt); + border-radius: 0 var(--radius-md) var(--radius-md) 0; +} +.tutorial-quiz > p:first-child { + margin-top: 0; + font-weight: 600; + color: var(--c-cobalt-900); +} +/* Docusaurus wraps
with .alert.alert--info from Infima. We need + to override those vars so the open body keeps readable contrast against + our white card background. */ +.tutorial-quiz details, +.tutorial-quiz details.alert { + --ifm-alert-background-color: white; + --ifm-alert-background-color-highlight: transparent; + --ifm-alert-foreground-color: var(--c-cobalt-900); + --ifm-alert-border-color: var(--c-cobalt-100); + margin: var(--space-2) 0 0; + background: white; + color: var(--c-cobalt-900); + border: 1px solid var(--c-cobalt-100); + border-left: 1px solid var(--c-cobalt-100); + border-radius: var(--radius-md); + padding: var(--space-2) var(--space-4); +} +.tutorial-quiz details[open] { + padding-bottom: var(--space-3); +} +/* Docusaurus' own Details theme renders an absolutely-positioned + CSS-only border-triangle in summary::before (with padding-left: 1rem + on the summary to make room). Strip all of that so our inline chevron + flows next to the label instead of being positioned outside the box. */ +.tutorial-quiz details > summary { + position: static; + padding-left: 0; + cursor: pointer; + font-weight: 600; + font-size: 0.9em; + color: var(--c-blue-cobalt); + list-style: none; + user-select: none; +} +.tutorial-quiz details > summary::-webkit-details-marker { + display: none; +} +.tutorial-quiz details > summary::before { + content: "▸"; + position: static; + display: inline-block; + width: auto; + margin-right: 0.4em; + border: 0; + transform: rotate(0deg); + transform-origin: 50% 50%; + transition: transform 0.15s ease; +} +.tutorial-quiz details[open] > summary::before, +.tutorial-quiz details[data-collapsed='false'] > summary::before { + transform: rotate(90deg); +} +.tutorial-quiz details > *:not(summary) { + margin-top: var(--space-2); + margin-bottom: 0; + color: var(--c-cobalt-900); +} +.tutorial-quiz details a { + color: var(--c-blue-cobalt); +} + From 20f4ba534e2557f72c60e650348e86036bb8f3e8 Mon Sep 17 00:00:00 2001 From: WilcoLouwerse Date: Tue, 19 May 2026 15:13:41 +0200 Subject: [PATCH 040/330] academy: remove broken AtomZones reference in DeskDesk tutorial 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AtomZones was imported from @conduction/docusaurus-preset/components but never existed there — the page crashed at runtime with "Element type is invalid". Strip the dead import, the placeholder, and the descriptive sentence that introduced it. The surrounding "one shape, five atoms" framing still stands on its own. Co-Authored-By: Claude Opus 4.7 (1M context) --- academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx b/academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx index 50889b464..5f989046e 100644 --- a/academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx +++ b/academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx @@ -17,7 +17,6 @@ import { Outcome, Prerequisites, PrerequisiteItem, - AtomZones, NextSteps, NextStep, HexCard, @@ -171,9 +170,7 @@ Open `http://localhost:8080/apps/deskdesk/` and log in. You see a placeholder da Every Conduction app — DeskDesk, OpenRegister, OpenCatalogi, Procest, MyDash, the dozen others — looks the same way on first sight. Same five structural pieces, same place, same behaviour. That recognisability is what `@conduction/nextcloud-vue` enforces: a user who learnt one app navigates the next one without docs. -The chassis is **one shape, five atoms**. Each card below focuses on one atom: the focused zone is at full opacity with a KNVB-orange outline, the rest fades to 25% so you see *where* the atom sits. - - +The chassis is **one shape, five atoms** — the same five structural pieces in the same place, app after app. In Part 2, you'll learn how `manifest.json` plus a JSON schema fills these atoms with real data. The placeholder `Items` nav entry, the empty dashboard, and the "article" schema you saw on screen will all become **desks**, **bookings**, and a real occupancy dashboard — without you laying out a single atom by hand. From f103f0ec793065cbcfefd3c1114a825f582e6c6e Mon Sep 17 00:00:00 2001 From: WilcoLouwerse Date: Tue, 19 May 2026 15:13:41 +0200 Subject: [PATCH 041/330] academy: convert nextcloud-lokaal-draaien next-step links to NextStep blocks The three "Volgende stap" links were a plain markdown bullet list, which read inconsistently with every other tutorial in the academy. Switch to / cards so the closing CTA matches the WOO, DeskDesk, and Claude Skills leerlijnen. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../index.mdx | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx b/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx index 07395d416..0e48a3673 100644 --- a/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx +++ b/academy/2026-05-07-nextcloud-lokaal-draaien/index.mdx @@ -9,7 +9,7 @@ tags: [Nextcloud, Docker, Setup, Demo, Lokaal] durationMinutes: 15 --- -import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, ContactCta} from '@conduction/docusaurus-preset/components'; +import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, ContactCta} from '@conduction/docusaurus-preset/components'; Voor elke andere tutorial in deze academy heb je een werkende Nextcloud nodig. Dit is de snelste route: drie commando's en je draait de officiële Nextcloud-image lokaal, identiek aan productie. Geen sales-call, geen demo-omgeving die offline kan, geen custom Dockerfile. Daarna installeer je de Conduction-apps via de Nextcloud app store, hetzelfde pad als productie. @@ -213,13 +213,23 @@ Na `down -v` is je laptop weer schoon. Twee minuten later kun je opnieuw beginne **`notify_push` blijft restarten na `docker compose up -d`:** kijk in de logs (`docker compose logs notify_push`) of je `× No redis server is configured` ziet. Dat betekent dat Nextcloud's `memcache.distributed`-config nog niet op Redis staat — voer de eerste drie `occ config:system:set`-commando's uit stap 5 uit, en herstart dan met `docker compose up -d notify_push`. -## Volgende stap - -Een werkende Nextcloud is de aanleiding voor de andere tutorials in deze serie: - -- [Een Woo-register opzetten](/academy/woo-register-opzetten) — importeer het canonieke Woo-register in OpenRegister -- [Bestanden uploaden bij een Woo-publicatie](/academy/woo-bestanden-uploaden) — vier upload-modes voor documenten -- [Build je eerste Conduction-app](/academy/build-an-app) — eindigt met een geïnstalleerde app op deze stack + + + + + Stuur het `docker-compose.yml` naar een collega als je dit samen wilt zien werken. From 98b988f95ead1dec7a95f9d88a53cad8f8eca4d9 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 15:17:31 +0200 Subject: [PATCH 042/330] =?UTF-8?q?feat(solutions):=20rename=20woo=20?= =?UTF-8?q?=E2=86=92=20openwoo=20+=20add=20Partners=20&=20Customers=20+=20?= =?UTF-8?q?academy=20community-meetings=20post=20(#72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(solutions): rename woo → openwoo and add Partners + Customers Renames /solutions/woo → /solutions/openwoo so the URL matches the product brand (the law is Wet open overheid; the product is OpenWoo). The page lives at src/pages/solutions/openwoo.mdx now; legacy /solutions/woo serves a 301 stub via @docusaurus/plugin-client-redirects. Extends the existing solutions page with two sections sourced from the openwoo.app homepage (folded into the fleet per the hydra fold-openwoo-into-fleet opsx change): - Partners — the 7 deelnemende leveranciers (Conduction, Xxllnc, OpenGemeenten, SHIFT2, Acato, Notubiz, iO) with logos. Notubiz logo added to static/img/partners/ from the woo-website-template repo. - In productie bij — table of 14 customer deployments (12 in productie, 2 in acceptatie) with source-system column. Text-only for v1; logo grid waits for per-municipality permission. - Community — CTA pointing at /academy/?app=openwoo where the consolidated community-meetings post + the 2 existing WOO tutorials live. Also flips the navbar item, the footer Solutions column entry, and the src/data/solutions-catalog.js slug + title + outcome copy to the OpenWoo brand. Updates 7 partner entries in partners-catalog.js whose solutions: ['woo'] now reference 'openwoo'. Adds academy/2026-05-19-openwoo-community-meetings/index.mdx: a single post that lists all 16+ recorded community meetings from 2023 onward (monthly OpenWoo community meetings on YouTube/Vimeo, plus the original xxllnc + OpenGemeenten + SHIFT2 webinars). Tagged apps: [openwoo, ...] so it shows up alongside the 2 existing WOO tutorial posts under /academy/?app=openwoo. Build verified locally (npx docusaurus build); page renders correctly, redirect stub at /solutions/woo/ works. * fix(i18n): mirror WOO blog post images into NL translation dirs PR #77 added NL translations for the 2 existing WOO academy posts but the inline ./*.png references didn't get image siblings, so the NL build path errored with 'couldn't be resolved to an existing local image file' on every PR build since #77 merged. Copy the 5 PNGs from the canonical English post dirs into the NL translation dirs so Docusaurus can resolve the relative refs on both locales. No content change. Unblocks #72 CI. --- .../index.mdx | 71 ++++++ docusaurus.config.js | 11 +- .../01-woo-register-context.png | Bin 0 -> 197721 bytes .../01-or-registers-list.png | Bin 0 -> 200360 bytes .../02-or-woo-register-card.png | Bin 0 -> 197721 bytes .../03-or-tooi-schemas.png | Bin 0 -> 146125 bytes .../04-or-woo-register-detail.png | Bin 0 -> 134098 bytes src/data/partners-catalog.js | 14 +- src/data/solutions-catalog.js | 10 +- src/pages/solutions/openwoo.mdx | 218 ++++++++++++++++++ src/pages/solutions/woo.mdx | 131 ----------- static/img/partners/notubiz.svg | 29 +++ 12 files changed, 340 insertions(+), 144 deletions(-) create mode 100644 academy/2026-05-19-openwoo-community-meetings/index.mdx create mode 100644 i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-bestanden-uploaden/01-woo-register-context.png create mode 100644 i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/01-or-registers-list.png create mode 100644 i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/02-or-woo-register-card.png create mode 100644 i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/03-or-tooi-schemas.png create mode 100644 i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/04-or-woo-register-detail.png create mode 100644 src/pages/solutions/openwoo.mdx delete mode 100644 src/pages/solutions/woo.mdx create mode 100644 static/img/partners/notubiz.svg diff --git a/academy/2026-05-19-openwoo-community-meetings/index.mdx b/academy/2026-05-19-openwoo-community-meetings/index.mdx new file mode 100644 index 000000000..9514537fa --- /dev/null +++ b/academy/2026-05-19-openwoo-community-meetings/index.mdx @@ -0,0 +1,71 @@ +--- +slug: openwoo-community-meetings +title: OpenWoo community — two years of monthly meetings +contentType: webinar +authors: [conduction] +date: 2026-05-19 +summary: Recordings of every public OpenWoo community meeting since 2023 — leveranciers, gemeenten, KOOP, en uitvoeringsorganisaties die samen de Wet open overheid uitrollen op Common Ground-leest. +tags: [OpenWoo, Woo, Community, Video, Common Ground] +apps: [openwoo, opencatalogi, openregister, openconnector] +durationMinutes: 60 +--- + +import {ContactCta} from '@conduction/docusaurus-preset/components'; + +De OpenWoo-community komt elke tweede dinsdag van de maand samen. Leveranciers (Conduction, Xxllnc, OpenGemeenten, SHIFT2, Acato, Notubiz, iO), gemeenten in productie of acceptatie, en KOOP delen voortgang, blokkeerders, en het volgende stuk van de roadmap. Onder staan alle opgenomen sessies sinds eind 2023. + +{/* truncate */} + +## Hoe de community werkt + +Eén Slack-kanaal in [samenorganiseren.slack.com](https://samenorganiseren.slack.com/archives/C067Q3UE9F0) voor vragen tussen meetings door. Elke meeting heeft een korte demo, een agenda-issue, en wordt opgenomen voor wie het live niet redt. Toetreden tot de community is open — geen contract, geen commitment, mail [info@conduction.nl](mailto:info@conduction.nl) als je wilt aanhaken. + +## Opgenomen sessies + +### 2026 — Common Ground OpenWoo community meetings + +| Datum | Sessie | Opname | +|---|---|---| +| 13-05-2026 | Community Meeting | [YouTube](https://www.youtube.com/watch?v=gaOft31cXic) | +| 08-04-2026 | Community Meeting | [YouTube](https://youtu.be/Czrd3FdhoGU) | +| 11-03-2026 | Community Meeting | [YouTube](https://youtu.be/lUFjD-oWTa8) | +| 11-02-2026 | Community Meeting | [YouTube](https://youtu.be/D-5Y-r7Yqfo) | +| 14-01-2026 | Community Meeting | [YouTube](https://youtu.be/DnsTPHM80SQ?si=gMBGYsf9EvuM6_lg) | + +### 2025 + +| Datum | Sessie | Opname | +|---|---|---| +| 10-12-2025 | Community Meeting | [YouTube](https://youtu.be/jjS1DnILE1o?si=FqvAvUTtdxScehPa) | +| 12-11-2025 | Community Meeting | [YouTube](https://youtu.be/Jh2Ap_Cuya8) | +| 10-07-2025 | Community Meeting | [YouTube](https://youtu.be/FMP7HNBD3DQ) | +| 11-06-2025 | Community Meeting | [YouTube](https://youtu.be/132V-PRl-m8?si=AC55H5cDrBdWSlLO) | +| 14-05-2025 | Community Meeting | [YouTube](https://youtu.be/sNKKI-nJpTc?si=XR16iXqWXswM6ZFp) | +| 09-04-2025 | Community Meeting | [YouTube](https://www.youtube.com/watch?v=dIc9XSkXdRQ) | +| 12-03-2025 | Community Meeting | [YouTube](https://youtu.be/M9xOWcUOK2s) | +| 15-01-2025 | Community Meeting | [YouTube](https://youtu.be/uV-hQwdz2Bo) | + +### 2024 — externe webinars en demo's + +| Datum | Sessie | Spreker | Opname | +|---|---|---|---| +| 25-03-2024 | SHIFT2 — Een toekomstbestendige Woo-oplossing | SHIFT2 | [shift2.nl](https://www.shift2.nl/een-toekomstbestendige-woo-oplossing) | +| 30-01-2024 | OpenGemeenten Woobinar | OpenGemeenten | [Vimeo](https://vimeo.com/909134953) | + +### 2023 — eerste demo's en webinars + +| Datum | Sessie | Spreker | Opname | +|---|---|---|---| +| 19-12-2023 | xxllnc Demo | xxllnc | [YouTube](https://www.youtube.com/watch?v=_FGpUYH1yd0) | +| 17-11-2023 | xxllnc Woobinar | xxllnc | [YouTube](https://www.youtube.com/watch?v=NCnLDEoPh5A) | + +## Verwante content in Academy + +- [Set up a Woo register in OpenRegister](/academy/woo-register-opzetten) — start hier als je net begint, importeert het canonieke Woo-register met alle TOOI-categorieën in tien minuten. +- [Upload Woo files](/academy/woo-bestanden-uploaden) — vervolgtutorial; voeg PDF-bijlagen toe aan publicaties. + +## Volledige technische documentatie + +Voor de architectuur, installatie, configuratie, en integratie-koppelvlakken: [openwoo.conduction.nl](https://openwoo.conduction.nl/). De solutions-page op [conduction.nl/solutions/openwoo](/solutions/openwoo) heeft het commerciële verhaal en de actuele lijst van twaalf gemeenten in productie. + + diff --git a/docusaurus.config.js b/docusaurus.config.js index f271ab9a9..b42116ccd 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -158,7 +158,7 @@ module.exports = createConfig({ { title: 'Solutions', items: [ - {label: 'WOO compliance', to: '/solutions/woo'}, + {label: 'OpenWoo', to: '/solutions/openwoo'}, {label: 'Software catalog',to: '/solutions/software-catalog'}, {label: 'Support', to: '/support'}, {label: 'ConNext', to: '/connext'}, @@ -222,6 +222,15 @@ module.exports = createConfig({ two indexed entry points to the canonical app site. */ {from: '/openconnector', to: 'https://openconnector.conduction.nl/'}, {from: '/openconnector/support', to: 'https://openconnector.conduction.nl/support/'}, + /* OpenWoo brand rename: /solutions/woo conflated the law (Wet + open overheid) with the product. The page lives at + /solutions/openwoo since the fold-openwoo-into-fleet change; + keep inbound links working from anywhere that already shipped + the old URL (presentations, partner pages, press). The `nl` + locale doesn't emit page-route variants for src/pages/*.mdx + so /nl/solutions/openwoo doesn't exist as a target — same + pattern as the /over-ons → /about/ entry above. */ + {from: '/solutions/woo', to: '/solutions/openwoo'}, ], }, ], diff --git a/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-bestanden-uploaden/01-woo-register-context.png b/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-bestanden-uploaden/01-woo-register-context.png new file mode 100644 index 0000000000000000000000000000000000000000..9db941d0b0b65e674be73b74d425b9cc2de2e86a GIT binary patch literal 197721 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

kT4ceJ z#`q9`4 zN$PI)r(J#eenC0v$EAo-H_ka?&&h@1%ZS9$O~aU(66#^^fN+@uO2HIKGn5SnBtdWC z&OtVUn!;n+LYxI=6xu`~Ni}ju>adaw1JF;16=@Of11Dw03hKTW-M;ZXQS0WKlJ*YM z97zuf;_1!8z8qIhuFBdheR0l(7Ud<2z`(m(Aw3RxQ@<{Z-%QJOER88O92KYfzbw`x zFT?@%3nXtkY}%MEjN+niwignTw*R%N;;>Gm0=H1{SXHRLJy0++a}&FJ zzGcsm^7!m6(-PLRjWHuy=w4@p{y$nkT;g0L^~*1YpA{BdSH>1lXQ3asTLb+&29jT9 z6uIW*^-3yxy2&8PlYnGg5TT@H&k5yDG6`1FVm540U*1hQeP&ovU2}JV2vA6eub}9G zQRf>`lFDiN7Tg1*yLpQ-1w_nxiYo5MO22{z{-tex5JiD^H!P_@N4%1VEP z2}g1nm>@Zs8D9NDd-=tB4DNU!F}5HeX3|U~Z)0<58)S$cYIZ7un0OEHwH_Md+3T@I z)YXynS+Nl7-Q7Kjngh$GA!BB7fS?JS%8o-5uYv$jf0??x+A}U>uk9Gm^@6T&9=0{f z2d9HnX}Z&;(zK-gjdPa;syuMgZ(iB3eRBG%7&AhKBFUSV{b|)u9PF$=B&CUXROAjk zw(#$;HJuN>)N-hoF%B2RftW6!%16D5^KN_5lXvrjjk|Ai?stb&?*swry&WQO{}u%k2A1%LR;tvCYN=yLoaNq8$D4KWgHKykZ}{VxR9V1 zJr+pVq-v6?5~IH+B2mjM%pp_d-Z^Fo5k^23ck@R@t+y~J)V38=;#?^18r%xl_YPHw zN@W{;Nmxy&G3=(?%pyU<<|n(mphnZ5&q_c*A%3n@UU#9m%9Eq6fdx`qsf|>stGx8( zz&y05{p}iy6!oT22Ec^R~bP6$28X;sjn9(lt{6EH<;) znOa#%d@^chRi>nHYWhFY}1+f7|y|*-F`6ExeA`P-d8TDBNj#1Rgl=eXLU!A`1 z0|{GfqIXG$DXKDW`#3BNAKsjb$~!a4MgWE04R2!{GjX0kInEeeTd<@`FPYlq<_`T5Nzi1Qq@S6O2U-bX)&x8nar) z!9+$_l$6O(!epHT9c0#QKg9lL;_3V^u%tnz=%&S@hN!#}^r`Dph-Y-~Znu!Xc07c- zCH=2$h&pIcG=e3i!c$LCYe)s3rHG+ z(dkF(6`TuQ;jfxO+}0FBm9^R~QJuEp8GyaWc6Iqg)y#`QFexH7z66wBZ{UwG?;s#oc(a)Kq=m zG(tNh263C3Te>dk^+lb~70E9BDQtO`vEN4^4I6aJe8QYvyKH8E*)9-0rO3x*sJl5CgupECDdfO*-KmC9J#I@pemZy{m4 zY1$sW>ED$P`CELi;8<{Dv2O(qmf z6x>9H@hAZk_5V} zlAJ4W_WadkQIhqQrQE4T+APl|oGXiSb=!5A#m;2BPjPt-+tK)buxD_u%bI)8lw!f& zavQg~E*iJ)L%yK|+A6YFu)Z$tJyKBTFGQ&Tozwi`F}Kj32>0#VIyS#9vnW$oS=HVr zA}WKJiHJ#;yb&X}1`gb{A#+1zLsGfB3kJ(e&AFUVB{+UjZTXWaL(4OSSKuIhCKGM- zO_?Hk_JuUY*DK$vG$PPLW-Ydag2kU@O~&t2TW%JQ6|x)|6V zP_yuZ5fGZ47?kVR23In!h8$MpDz3?E=_tyx&nPm~SWBdQHXSyha~$YbRU?pSDmPP0 z%WFvAG}Ip6$%d6CEfrW8b-ZM2zOXT?Urx=N;euRBm$bWR%F(6eDe>3g*%Gs+J|w}p z_@wwmSHU+OwrKy7svSG}`Aazj#&X7`V@E1NNY@AlMkkh>Yv%6=#fKFmr%Mhu+WP~` zDJa<@3cB#yD$c0Ib!h;*fx0KMg@yLy1Hv@oU8B5hHkFo=+fv@Oa54K$7y89Pi^r4N z_1FDrQE_SalTIqU#`69408lCR4zPQA*-$K}!hCRi zx002cQ!e>-*`L`rF0b66d+@P|5k!@kpy^6WPTTr5kc)d7GTT{o%Nx-Ml@E<$Q7I%< zbw=j|z|=TL6w=7wxcxD_MI{1+2I;er!7TjB8W%I;YVm~86!fru34nwiF|O$m2dzT5 zO#r#U#%topq-x9i(lx7S14rLBZ*h;SMO6pud9&IC3F4L>q+mF^L8s&Km@f9{3G5te z==pe`WS!3v$M??le*BH}X{SMmj8#%->Md~J160jyva!UI!KfvozS`d1e1^qS0{%fN z8xa8oj5Q!+D>aw-br&?2l?iOYryjYB!q&fiOA9&mmwpM@^=H;iHupV2s23LdUteD? zE&BxW(B2n3+t_a+%wG0&;ydnh%E>4kzfc_aouuA7v@FYM&Q@6fsNPeo79>G)WZcHk zJ3TX6xCP3~l<@z8g&`K)J)r6icKj@7!ucT#sb%%=XWNi>a>WOl^l^k~YN~z2pxV*s z%HUYisuC`*pTQK`1lJo4l|el3);#xG;vZ6RxDB4N6O@cx__MT-2a*pv<_QRe?Kl^R zVIH-37v*$nN0c>Hn}EW^{o=?H?!>C8Y!E=$_!rj4)v9&!%~XxsNLCirSh`^EP8`t| z^}HEl!@XSyfYf|DVd%C_j2nH3jnEYg}8{HsIA0Y#zX9T-WU?~mzsI0!*MX&+b@ z)fx2s@~yPvCbvA9O^=?kkailMyp1TqNC6OREz&m~K1c6>qIIr{5S?;{evo(L zL~WKjO~dXkS6bq}AhFD{zP(Qpvg^unVsCA{?yGDD38BFH(BTT%@a5%w75@6!)?U#| zfCuQr2#x5WofC_3bV&8lO=IdM8Q?Q&5HEWonF7Y7242~$H_Mqu0z4L5Q%?en_B;gF z`9LOpadYoh_4;w~w zAnoA409!#5Q<)%5&9uChb#vM?JghvUrX44#E;z@wl}B-W|2Sn)JC=sy=7$hq z#gP9X&;OnB?(H<5Z1iKIG94H+*%%T4|RM_c{#Y8pL3tSB>Vqo@Z#q`TN@Tj zNqsuFGa_Dh+>;Y~uAEm6%V_@v>Vk+F<@e44bqBA*CWt$G72qh-+Owb<*|-?tyXe5P zx?m^;O&-8jk}Trcdcc?dixn;ueJY`&w(n#zN(H=V|GW3G0gXUW+9RDZ7e_+jaoAK? zo~nb0F`EMi2+d+^tw*Xoj1m8vhz<|T@2l3-bU2z_5?gS9D*6&Pp9~eubP#tZY=$ie zs1=ZIP#9$C94nFsUct}b56$+1{9n#i*P-sJQjyEOl3J+ybph@i@p@nxax^S|(#w~s z{rPDt2=qTdE?B~V)M_B{B+}rkQo?d58!AU8QMl_x0u@IM#Q)y2#!IJUW7j_eu1uUM zSahrO7v%hh>BY;o7C=+3opCgS3u zeak+${XbJ(g~*=i<^OEYWM%KY&7Y~+92glX_pFU2R8Xfs*-gEE530f;@aPA$eX-|zj{ z5jSBfXJ;rZGli#9vHk$&@=y7J(a>4O3@1T_UC^o zcchSv?af&MC7C7743Mz$n)c@QhcEn|oc0_5zFcD?s(#1iPXtARpNhl7hfS8^m)-V% zo*kUgXNGJY&juGM=|w)#r)K4&raSJPoPm*1052&U0I^=VM6LF0IQ%0g3-}g5&GZNiY(|$T~!eJJ)fU`YWY>`X9k7m znT?k5_}{J8h*TsR6B!#D+FNHZ&}_X5FzgI5tWmh?(gp(7%+OeSglKKR5P7OzuoBrCE*DM zg+u_%DHpl)95PS?s(|smE%SZ?A{$xlpWq>c5&Ei!DrPUgun)Ff9ASFD!`SMs&RD`;|y+pJYdU#OVFH7=ia#Fdxz5{)c#7Ag! zTn>Pno;<8#4>OiGT};)?pp=&!{krdXsLuy`WmWIJS}p1{^x|MSUTInBTZA2Hd}LS& z^7K?EiO1rV6kn%5gKwe>*}d8V(n*?K-*Z*~c*4Wp4SjReo2S5zp)mwHv%xv~>;w1F zBjEG`18r?L{f6Y%(R0$BFnbHr%obAvBv{Rmu>#|^3>*N45A4nAu;Uk>T4$_vQxj`I z4D-D1#@*iI)zD0t%wqq(8)tAt`FL8lBNdG%>R*FsEc^Esk5?5C5*k=uP8--QE-s$& z;aaNI<2Rkaf`x^hv$Wo5O=x7cw6si@CeNy#i@k@MBUtJ2`(CQnnxCKdd)bAoSE*F3 z=JNgEh#S^wb0o9C@jmro)%=t=zqa)--vxqlui+ljXHk#|NPW_!KN8~7(u+(iygeyv zV3#?CslHl*SPMa4d`5BlcH6Nvbs(z(>CzXiV@}`m9DnZLW0-I6V(vwywHZhJ4jJ6M zf1kL#(wyAIsab)neqNvqeMZ=1pPO}WDAiIBnOwr1)v^=yW7;rtWyb`ZFd~&pOvP_i zBfVhy*}Lhe8ShkARG!twM_YoC`t8L36()s@XHe>YO#xnuLvjKP?D{y?rwjZC?cF@~_vIwRQip%k8Thm8SW8k<`1aw=;(wkzXRTck3NmrD#U6LQZY#=pR{0%_wau+8=Bg1z1r0f% z3GsX=J<f7!o``*CwccS{v(D{=i4YHKN zETtef{~p^x`2ZqF)EC&dsi_VJBp@A7Fa!=zfK2S`OP%bDXKihL;P%DB#unQ1{c(?Y zLNpwU-_X?bzb`pmOj8pp@63J_L-$I986r9YJUl+1H%CrYbF=GsDjg<+?!@>w4Lv;@ zD{E6j1L!yfC8ap+KwVuOKDTRdaBzB7*7L=xQ(quB|JMsCmUtZu8ftiHqQqn0?Zs+C z5G3L+gc)r(ui&@DsHmu{EUoLUuArbGzt2atSW|OoZMkfY1eiQ77t7@u zoz>OV*Vos#x3~DwWGW@o$xIPDs6#|3sQiAb<(kK*r>zz{!V4BoPHY$;kgyL!fVn;N zH^U~lBo&KQR#6e;ror#AUj5^AJSCIM({_3mYQ5H!EdKn=am(UT*fStD&`Ac4KpwQA z&5D8sW;v^Z}sKmeayZy`8o@JH{ zKXUZfK`E#A%gu3~pRI+3#J{bFCZU;`8CO-&y<;O&IJnc%goLy-S`2WBOxDxwFk#Q< z)4KD=R1SB~*P9i!wz4uBDk>;>l6kWu6e406oRy{Slolq#L4OErW@ct2CG5<|$5qF5 zAg|~!8caa8-9{@g`~oe8ly#C+8okf!VH%wl9N1|%z$hWxz9&)0(Q7uEuQ%Hu-5+Jy z^#?J#UaY8|*)1}@N&5IPdS};u3Lu^nOqm*1mVF^3sdq9Qj3gQJbMK={K?Jeg?jO`R zxo&l$=um_|*t7X1Y63m+rwv1jG)pm> z^xa9$>cn70N{zH_k0&W?itLh_ErSbZ0OYd&e69c}j)EG1qjUm{BZ*X{a+W zZ5;ns{sO#FMdG^Y|en zif0XpMk>eT_&@*Z|CG(mw<{MWvAa_>g8UCMReNZ2758KYNEV>Jj z!w*Ch6QSSzQ}DVrTC@@-{E} zK*uSn8K)Ngdq$<+NI0_mQn;5!%afzDrNxnF@Alk$Vk`r^&wD+5?`X!bk~=_F-SWEVo-8+`pE6tfsk4rPOj)P%kK zdQG^R)n_n|roxJZQPY%{5)r@~O?!_un6{>-0B>eDx9bH~87>9}1|$NWfXn|R$MH*` z(r%vlkl+iVzVDap@GgURy6L6PK5|CO`Q4d{6ELb}2@uQJt|E z8z1lEDeBMjz8lTvbSiV~pA8TY3LaYjc)b^VN7|%HcY1%iM8!9sH)ljTK!+eOi{}RH z2gKdofv}cbz_}KQ9U$iJypB$U8-Np@-?cFEzMw)(BN7mo5%s$8cl!%~qW}tI^g}Z* z040n(fQ|8ybO5NvR9ShUwq89+@yuJ(#JyX^X_dVNv)z!;`AT3b8AmXS!1Qr`)m!sF zL^KWB@`Lf{v0meCL>0i%z>gkd*BHi`Fh?zkR= ze{9UfIN)jNpg{+iVKqWOxTAKGj38GbaP1IVVe`(am@)%a!!OnWW|&iY#3cxqQud}g zTaM`Tn+0Qxr`!CeZlQi7C}f168OsAHEO`2bFGpXlL%i9O5r0q6)w6G(9|5$1yE<0y zq{oc{3hP&ykq4Fy+88u~5Y)f3ECuxT_I4Vznuj}2P6<8bh4GRSgMm;9+q;8NRB9ki zX!N?q#^MEdpa<}HL;_w7w!rg@KFU8;AATBnx}1#wLdF*A83mUF^JPwm7kNmB0QuJgM&-Zzj&I~ z?*eiYy^ilP`5dH+#2`JUSA2~w%V)6rrT`~A_q{LSn-IuPZI}JgG-%Yfx=3&Psh;Sm!C|fENe$L_{W^xE&I$j-8X3dWCN% zx2t<&diN3A3CQGs0bL}>WU(bI6)b|slKyJ&INs#2VNLC-r!T`D|LQunvT}axLe~Fy z{V#hAgKoz9LIN(g3>*w;jh?)73)bfN)ctklT+t zDF(A2ka9spAa-y1S>`9n`Vb}+M&gj#xNsZ;j$~c(DMjPE8S>*ELIwbi@V>0!h?!v` zUKl?DXkuLKn%D(KBD8{X5Cd^NB^y(F$6|1VM`rkZM%E;JQ82%v~ zko=QljD7xH*7M^9SUnjTL8Fu$9i2mLO+DA^ExIPJ^6u_@T{8;{Y;0`kfX+^cM|RDZ zc*Fz5l7%e0Zs?33KYnPp+6TsUWk2ffajPG%Av91SnA)! z)wfZEzM?o*_!Jhv25!(sB4x3qDmai0{AKt5QS}|*T(05&_TGE%Y$AJvua&)bWMq#> zM)u5%l$prhM6yR#W_HL($jC@W5)%Jg=bYc~_dl1buH(4A=k>nNb3dPT->KOf~_&mjJp2NRN;^yG{>?ckH~?}Zf3QseLVzR=0n;*PoHf`C^_ZO?&0(-B1f zMMdFt?AemunX(}lIUyI=E8*cN<2NYy%+RcTeSNu+pY#I{Vi2^6+%y8367lY){}B@l ziGq)hkBW-Q*vp&Dbd;1O11DrGS?$xEot^4fya1sjrKJPUPYx8tTi9;iTpUy#M^YZ- z4>ds_>{(r}iya&sw0aTyDj_zu*=vi$aFl`zBfQN9aKH5Ok??CcmQ5O5sTIpBDjNF~ zxvN+_bVe1U@4ZC|A>`c@vk>Qa)R-{wcvmc(UgLU)SX)%j?vI}58CjCm_<9T5MTaAE zB{nFxK#TA1`>>QqNVn_e;8KWMbwvDJ34%<=vMRYBLh})RJAo-I``;DRk*u)QN2My5 z+`qm3128&hk@x)DV`w8&5A!-g4wvpqK!@49ejd=E=N1;c zKkMZ`;9Rtkzws_!4oEf_SL`77QI;%-q_=+6tCGWDXBzSxRfB9UE7RO)dy||~hz><$ zkRWS%0b7EJFYK|BBn@mRLWY44b~w1Wu{FtSv#;;|JW6@s7yl2&MN8u~t@YjeJT#<+ z6)`F3FiHm*D8Mhj&(8FO@h#?!3JFE|%1HcV20Z z(!K4VZ{R6w7e7gMouQ1C(aqpHSy@0pV-e(WX|^$9-{H`@s)MCXdf3R*DsHJK5SqP>HEAMguB2Z1EiM8?{&C3C zwk+Sk)>3q=@;psX%j_p;wyG6{*2m)|FaFwV?7!valfcU*QhbGDQFnuSZT1TSjzWac z;h*h}7>%|joMh5V#~*5CE+~(L&{(-q%XxJiSi1dZ%MC03sc-mUX(V^=-j%>^@!7?} zK}XBV%Hp?ttI>aXvijt9gKJ*5)tFA4oRQJYP%4jI{YYr%!Fsx-p7kt+Cy_-(bv0pY z?h)}g`zE%jasmxGJ-udRqI%iQ;2$Gm<2JnBl={UQb2ZkeKG(=ZyE`XXc2G4Lu?o$DhIHQ#dQtm-OMGi0ZM3RhPI%*HH1;S+8X!m6Q!ccv z<&%Zy=OQJ>)_&eL!CY7-{PyABkwQMlA-qwEm5%OSkE^PxdhvGFPLpi@KZ?}Tff$F&!O6X`ygc3>@Cz7E@Yz<$lvzMPfJEy{NpJ3$ z8+mzoxklr6q;nUk;?R0?lkuqptWFQt$7?jSRn>cJUg!Ryo|nz7BPJ$(`t<3wYu9vw zEiEl!ZAx4$zRut6O1p6tl5#FhoDl z{0u|mcYgiE4zEMP8D*V%Jux|%9}s-_P+A8$H*e$gL6;P>s3^rY>DSTh2;stA0YO2( zui3saH358ov2fvHjoU)Q`1m*?0wXL<*WCR4XzPPQG(p%lB61Yr0P+$@9+8}U9-EHN zX%8eKuetYRO_pa(x`F^6*iSd8)zw=%b zZmQwZ1#6{^O|qQONY-xHA&3;2PXd)s<3sNSul=+Kww%(5z&Cz6^yl}lD^U&hY)XoO zk&$wO4ZV>4<8$U#zz)*B-_7A4d;8+-YzrNo@yFCc!1oClWzIp|A=jZ>wSQ#z@g66ZVbnU7X(YEYwD+sV=0V)U8P^;3_TE7z+}zy2TBfF_l|IT3_a)FG zP>2!&x!0hq=kf*~&pEWTe4!s|GXI`#8GJe>dpRz6ATKw60)l$%Vc_MTU%8YqeQP+e zj~};rte~LDoNcn^OJ836zMg`)Mk2{>H1J%=VR`e41N%o;x*?_>LU{uT{hBf2sZpf~ za!Fz$7YT~lSOTrsy~P&qZ?l5`8AtR~#Zx9l=majL5k;&4e|*jwaxtS5nXLujDXVp@ zK8<71JFuBM2{!6GTl>i_DEJg7BO@cGj5bgvWWLS`&_+bfqes2KmqFe=IywUQk0_6c zjYTGBVr5k@7|1<2`k-AFYetRN2gS72cW>1G&EBVG)NW)>V$e(2hCI$s?yD{8=-AmAWcHp~j1F6Zf;4zuE`e^FCMIIU&qTn@wR!dh}GP1Ruo!hr>tN+9g zYX^zvnVy*c(dGpB5g;;}yhu+^&(8j{zoI}8{hzJ4W?0DergWHb^5^!OrhzFsJyVT3 ze5~8aH6mE?uw9>`rYnd~7dB*q@F>$5pS}JK8L{>=s(;mOuJ#d5ga=at!0z^x7D3n9 z8phxg#O>QJ?f_Y;1WzN(>i)0qiy#h%eV40NQ&mMijlf#WytmL$1qYD9`PyKz!)Vqi zJbj*p5M*1j6I}V8t_Vyi?`^gQin<7(!rXWysE#-^G&EEBa6nW#*%ajGH@GhmAX8GN zgCri4Nc~*!E=J`Ud<0l6E2Ugjig-isX-Z|8)?;2$upBLz85*L8!FuRsR=-C}L!q(i8{I1bxW~Z2z*6vUJ{Fen zIPaY{(i^bG@L{&jzW@c7WQi>*^Cm|OPK0ml6lS3FrD0`k{B*qyY z5rH7q@e#*frz-?t^lTf0*k@;RvItlA8I&CbX6psZkUxf#tbO6=xBy;pxG^|I;M`61 z>O+RjZnW(|o20c|4d;!4H|qYcU;i3%Sclw`ieU-~BZ0oIOBbJCl`tx@+flM@kddVu zgWqm@H@9HR)%uIA(~Smr+6Cl(BS>A{y}e2^^Z2x)Cue7IedaA*v7TB7eYEbd#K}Or z08jM{6luj)woqq}i*rA$&7B?X=Qor*D-Krs-RJALI5?s?@xGPT)d_QPVS7=RnMKsH z!S|x0N%P(d6A}`ZIe~M4j&jS;&=8K87cXAehQqS8+>QbjbHA~$5G`?{yZgV*IN+i{ zicWyT$l-GGG!b+3GMs4_oslsI&Ns7KQbjd29P-Wi`ie?7uJ?>n(Uq0FJUn6cXht{F z-(QpeMVe1=-Er2{2e#xBMp-SgzNIC`!zZz^rcE9z`AYF9&lE)>|I-3cQD1O92sM&N zhv#5dl&6#s6N9B1*7NC;`HTi9Xf7ODS)s`S?w{LyKl?e1=OXmRQ2)KeHh*FD<;{%T zmM|u_P$2xb-P@HOA)dt~B((ePF+AzVB_xy$I;$%w;V4)c{!ms{W?(g(A(&v#KuxWR zO?OnuGBb0d-q?;q)uP?sTSa9cp7Q!D?1|~=-A~PLii*|&*ZlsqcfH;>HgIxsvb6J~ z{2E7MTwJDrb+}1kLPA32k&wfvwBO;{YX6hfFJC?f{jwnP0vQPY({GS(Z7|7R{6nB1 zKQ-iX%_FxCt`h?bna8M-C}D7P^kV7qf|7#b7#?XjJ-_IN{5D!?Ehj&JF$y|5xa@E?W<_A!T*I_wUR8+hl8e)(M#5yR^%0_v8cYP=otO(t5ZA?B7PLv?W z{EUo&tRh;qTKA>5K-f-~yCPuW1DT`gegHpP@A6UWh3GCY_nD6&i(kM0g#(bfQoZ9B zhN^^*=2T*0X7+vV&DEXu^Ya5pOD*b}k)BT3J~J>7gA*F`stKVLke!=Kpt@H1>=6j# z?j3*n);)7%Ll{U%P*D2eW~i&HBfcT6u!@NMd^0AaFD(X8j~mG^uH}{+*VNRM4AHZU zjE7K+;Lwu`LuiE?=K6Os)7|tZBfIwB;;W|MTNbGP(?xX3j5QfNF)`uyYtgq%K*Rwj zp`wC^T*ui=UrI`fTW;=$-r&FhlV*6Y&FRSr@13@XAd5a>xH;eEC$#?A1}G`W^|{$q zlR^V^B%`CF@Wjqfzve*If3P{u4A{-NM6H?W?@j0(93H;1-DJ-;3!o@=rT+ou)ZE-W zRSue=;;mae8Bc7#c<;=>kA$eX8Je57fulAspfdR~ROrEF?jTcik8XAu`xw$)64+`(8W8))X;p@C$=4KWU z*#Dp{Th#sf_3M8$H5whUx4*x)f^L?l5Iy=_sQ<$U(#LLQ%b|Pj=H^-9rmE~d9v(?T z^7`9G`$`y99oV01=ZQc&0~8bk!X%+0cT@BKUbn__=N($C$*$0$e%QLD`+x^D`kzn< zc8`w}p}@hA$8Knt1hO;figeknM7pQu0bkx>w-iqYZr&H3dfY%(_k@r$VycRcp1#$4 zM>+DrTi2zx*`+w!fzP6fu3%^Yq7}(m{kMbt}dW?ld$S!m;&iTdSk!34cK#X}fIh&Z!D8MS%-rDl}xjkK?ozq=n ze*5+U#7-s)Rhd)=)bO6}*VD@eOB4GjNe3LeLSgx*8zJzW z(~%$=RphzThvEs#6l2c&J(tNI1IE-x-MEe!+x;Z9Yf;ust8=$#NiJ;TEqO?n<$ zQFzogc6NsP`VY3J$^d#GT=2iqlTnl-V{ zke;~w`jz@bGYs}fUdwpBi?P7y%7d2~8B}1xii92=d6nwL;LvGlX#v9y%T!cSLJyp{ zl|X|oLOzQ=Qs-IDq2sT!T&-O# z$%jtkhY(We>gw9xs(ke-YL8uHHDk?)2`%OxmK`cH>P)vU(He%Mewa3!0Y>g?}`o$CS@T{h8akvh!J7BXMFi0=($L$2Zhz7;;->BMXx9fJ${ z)lD$I!g}WCGdVG)lvpBFUZIS9S#a+lk2im9(+iL@cLqHbW8$GBcHZrP&%wXl998n1 z<(Y1d*Cuiv6$+>!-^%7)n=DedsmN`?Hq1#Bp`nInv&9)oo}HZhLk=tBXq+86foKXz z)iGGd*tq0Ob9^iPx}_F6)3reDj`KtHa`#75?}V+Ad(!XCRGLn)2;bE~9;{L#`p*5|w#cp@uCSqhH(MJO^C_lL!bB4(A3P%&d!sW zYV~y=tjfKH#x1-AP|DWU_TIf$ybg3VyC4JscflWIauAm*4MwamJ2+<0NO-bMqZE(< z?c9UYATJfWoZZ0vcIk+mQ=W)Dlqc}j^*-rRUjYhE^gx>{#TrK{VuMC4?g|CxZ0P{G&2=s)2Y2nXDp;8PS#M=_@Q&8@92iAXc#O?Df62@e}< zzM^|Na#YS-o`+;_9@i@V34y+F{divOqfmN+<Nk_Lhb%5Wm1EWrq~4%?v2 z%Y-zF%sZO`b%x{m%?MUZ1Bp#TonN6hpY?^dhr$qAQ?4z1G=jpqIs-2+yws|!PjW<0 zX|xkei1`>s+WdaV#UKaa7;uA-ai`sXB3Of{F#E%6G=dZhNOw~}ToEW+;oH01e<5KN zS}7q!9%5r-^*+i3olU)aXFHU_HDv=Qm@bvEv9ZoIu!)!Yant zq(a#{qp)pcH`H8Hwrkh^{1}tf)zwY0tfAoI8^DBL?}!D9vDW@6CAt^Icofq*93Q<# zh|?95cP$nwE!@UvYTuvEj~Qrcy5%QjWzmfX$&H+z{}{Vwk-@4WwMPDeodUEb0T1Du zH!W^3aLWNuysEhSUwY&J8RN}&%g(N7lSKt%{FNLo&i3|p{)I@FKP5dO%Z9x=?`wlj zEKKH3;6{)4l^gTV^+0%2ZPokn5|3WeVDiB~v(-neArWU~72xNmyLj#1 z(p!8Z!No=tjdft!u)8BttQ;J^uB_Y#?e*(e4h(zmiM~B7*kEEa!Au3Z$CJ1?lvPk( zI|>We05pMZp4^h^7zF?sYP_`fc1lzfWNIWhuQTa^8-cd=7*sC|G&GajwdkJEqM>!Z4-Z z+XWGsXu|L1>aTblpxu4HjUT5v$l+r`!ZKGP=PRebEh+hAO!z8iHPFeTdvn-VOFh9| zR>45$BXAwNQ2S`O<$v|bD~5H4W0u82>S5&mPS0i_*tYAwq!0+(ck}I5>g8hD`r`~y{N2P>&@H?zNY56F@TIoDR%vm z>Hl1-NORb{f%}_GFCy+?N*UJ zg{R7z&m}w$AJ-K|_A@tLTsTFPxFe+`38(((C;Uk*P51x3JHfq%nosyHPAcKgUw)ef zQ9Cre-UqlOn!GtlE|kwT(h?$8>Fv-G>0fj>2xQPU?23wtMD%tt1oe$&F?#MkD^7!A z`9Jq+Ew$kErueoZ4p{YsQyjkn1_xkdnpFpO``=*Sfys`EY zN9n@1-uFL`@R@>7vs5LXZ{0dR$CouZKl;67^^Xnm&(##tkJE)Is9oZg>qmuK@X_pc z5nr4j?(14Vxrk^#k3cLpJ@}aqgI+h8_QjfvWhI>SNLL$!E`JUT3bhUM(|mzUDwLg=l0;|dSX_uzjo#=1fQ^Po0>Q<%I2F`uUUOvlP{wUb>X;v z^5R7*NP*hl7iE0ES$PJ!-q&SLWs+j0MgrDD zeGVbQArj{|Z(82im}x+XZf^D~pxA<=6>>^aah=T3FT~t2(9vUY=rmDZ5`&=c=H@o3 z40+QhYhX^~;(^f#0SWH6@i%VVSpH5{D)FB46RY^>tcs>lv(L0wu| zQwf{NBZfbiFEVe->;DjAe5-GE9?nD`aqKMA(bph7t8R59<9`%GA<5z zLM5l}M?1Wc|5Rd5Rk-l{V1n%|tZV9NbW%RNVqzbMhg|^#y?XU(YJPuz zKPf4xu#in4=iBZsC|#~@ZkknJJexsp0|WE<>(^HvNY^_O`pqjl6liu~ak2aj4vrFF z7ZFdnjJ`qYKM?F_Tnb(g`AdGBuO(~y|N5>eBI00g|9z>0@xs~BQBLRr0)x|2Q%o{~ zg7-fZYl4PT@S+RaaeVpss>GH8NxLDx!pXz)z}L4U;1@qtT2fxBVFZCq4duWw040M# zCF9ie^d4PBW#x^vH7Xh!pPd=Ell>Ju3{(OKpwdapjZ47=dU`>>tHFsl?X;GE-xeL+ zU#ObQMo1b;`WwOq;ikA8LVSGqc<&oO`>T&~f!8MpcZ4+_<)YE@4LU;HI;y@)E@L(Gz%8Hj_T$G{j-wy@7^ii zxIGy0?18(EU-%G)FQqNYEzEv`-$m6@bQYeTH@v+RF+75Bad7yt!jvYE*{|?NmNnle z36IuEMqbkr1Fx94DHrAj7x8>ROg+f*D+n<>JUlu9DmYGctKo`O)&7!@j?iO#(8Rc0 zH{KXoP{8_0)<(g=_NL3!mj7yk*z!ggDQ-Zt6b_DdDu=8rqn3)RD=!&C?e0VYCf}F8 zOLy|%DL?xFiTM1(DHlV#Yn)^^G4bf7?WO6T(=oO>ydN7=U>wYO`>yY^V2wl0-2mSc z-yJ&}d#!$s-rmh^+6+^Zp*ad?t9i+mBbH72d>4`Wy4)eHBqPQ3FUaNORoWgN`&Xgw z0C7plmWGCzu`xnC3rS?E)V3-%?aO2m9P=B@`iF^~eyUGYrjllanOhTEG|% zQ&q0Vh$tiMGX$NhYYiGw7J)%gS_UHOA6|muWQ7`0k6KKYVPZ8f(OOYiiB^Q_iPS^I z%R*#n)dPGxJ|PqN2cbq;z_0HUD3~$mOo9L!F}tk_!@My?g=Woaet|M>U|~p;f zWemE3HubhiCq|S4mAD5}#t@)g(EK>;6x7h!3Uv#Vue6_I&aY%)E`b*NhdYKyQ=1JSVkfRg0{B^QPd- zian|w4gqQ9eQ|f9;v-7*J;^~A=4J{8BBFa|$Gf=Q_~xbp%Uofr9hVoaem~f=sGBh5 z24-vR-oriCmc6A%NIlAYj*h@ABoa3^7=k{(@!#RM4_H=Z(G>p9+UHp?qLOs{;B&S8qb5422W78wX!*X`Wfg457l91`q$6$cd2&ki|3 z7{$D^gI2Oj9U+YKr4iw1{9$=>6v9s@C-u0m&8AuTB6#Xvy^~zMTuVzhsHQM%qQUH$o+5l6THj{Viv#j^b#;gD-W?t1O`G?~u8U9$Uwps8gfr_| zLs>J!c`J2%?E>7L526B8bt%nQozzIlgmDPd$0@`FX;r1_G3)Qs^^wJe#b1xl;FH7_ z7Rs;e?rJ)TSy_jFV%j0Q>34`m)5pwHel8;;Q={e_1>S-OU+IR|KvrB`O-<+nbTvT> zEdiseoV~q-BaVUYN}K{7pGQnKf7zTG%g*Oq8#y~v)tE`CzQil9!)O*aqbt{XeQm7` z63w7q)ZmQnLyrfiNQgk*ifB|a*G2^DC_|Rd_Oq@atde*Vak7pbl^&>#IPbE7Rfp_% zVeShPubh?ue~T!=uw`{t7&wM~gTc2Pa``*wTp{W(o<3%C)o-O6UEFDEF!DA5KfVeF z%$?!Ss_duxjycZHtd}O1tejKd*(s-%9oCBSnULv0=X6-V==JOF(6%~NRQQ?dFQ{re ztlK9_ZNDuRAu!=+Q(AmPEuEj-SY3(6Y<&Is?YWq8TBP{iZ4t-Wk$4A+x0l#Y^1H!UHtG(*P`PafT6s8caXbw8g~U7>(V;%w5>iit1m75TzF!-Rc6$1%ozCd&)4^s zqgbdFKIeyjipONRuk-vlie3stp`9=#N_EEOyj^#<6XhyS1idz$p;>}=qo6R2fZTvt zi*7PH-lrZpQ{af=udASdI!s%0yHv@M4O5-U>`k6UdR(57u7$kf*FvNP>|q9OJp2i( zJ9<9s#4SQ-D@^?ST9Z?eR5bz$^73+uNC9vp;(WX2Y-nS{vZg9Eg$8p*LM|b0s;jtM zQ(OD2{2me)7neDIdIM_LD^w|2Uf#!MksrT-bJ`gh(coBP99vU@L087x@p4=%_~*8v zqe`@pMh>GXN;KG{^l1;`mR%!M5b2CY6AH5LLLb&x{f|?|Ngy>37rNlX!qa6 zfq_K_38|D8W}Htyz|}PhTuuuOct#_`dYKgKy4(`_Nyz`mQ8J2VO`c5QTerwr#{2vU z2Tfr{eSLkMgBTs-N4~w_3SJevJx21T8-FQT;Y}uP4}r|Pta>PzrL-SDn@c;5qD)H5 z+__AiI%zZtM3ZIlsGHasuaEZh6cA}V-(1ktGj9LAWt<;xPmz{yn(*=lY4B10Cih+a zH-?tMw}_tCCH935DfMIGT`4=^UD!qyg$Ks~szOJuJT+EURsnSoF6n)dhKe1NZ}94| z_U7I^|F!gCv?lLAEg+<^zMfv#k%+3r3NJ!X!SoQ~e1+CKjir0B0L4N@I7Lk&whNds zzTsAGg4YnE54xJh1_hBu<`M&8>QkoPtlv)>e z`8%i%b3`UM=m`=e`LO~|R-&P=;=Gq@*BKH|W{136b|KkTKZBF;PCo2of#_)56RwN_@;;D4reTFrp z8f!<=6j63Ap(op6+PyI}jaHUnTj~KUg-LdL07$=r4O(-ZlYCaiAP0q|j)6-s9Ayo) zCLzy>zT1GNJRuY1n}%+yF~QX90d7A>$I^}tSrPw7SQefGgWCXStrj4j^2iKr;Th-S zt6M25C3RbvL5;KCk8ZR_ky%1Q0!nY&A(U+OQCYR@x+}oSOKRFKL^fzN+Rn@%7Q}{zEO6d$oXLWE4~-lFs(40vI0s zCwu8vWZjt3O#P`fCo_}e%_r8~sI(?zoi}ncJ-1?PrA_Q|Z$YrF3W8%+e2kJ_rRC*n zPwgFQHan4|LN3lBJ&~N844H9sQ{9T(#kT-GqX`+K(8fpU7#Iln#*SRGA#vIct`qR0 z4>zXc7C{(-rybEZ?Bd z^A|)Grd0QdJWmhp#??M8E+TNQ+6{E6BuY4$n3jRYLANHOh%;FsBtz*;s`Ro$uRP&O zogt3i-G#;#PR=pnw6rvjZID5%6!e)r&{xokOGsaL?vdLvK`h6I>huIW z6H1LULYWh!;X>fiVd$LmI=r|wiJ$#D)NM@ z7doh)&`V*^c5hsXLS$FkfQJo&1(|r|DtF%I_a@vf3C`e{&wgL&YtF52)rdQeGc`Hu z(Hn+ZQ-dq{2y=C&w-+4^cPK&uZs7w@&s?jdA3%Kbl014>oWmIoj<)o{>WqpOt{r0M zxvjglMC+ORgy0oDu~rAmt%eL~zZ+Tk5Nm`4F*q>><whN=rvFjOS!BZL}@@dzAe#3R*Kc(vz2!U&*GFABx!7+w1AepkrW! zf58p|Dy=^z)7Hv)pW-t%q~8@!JY6mpG$NW z=dg1fm5(~tT!9J37aYCEVkA4kkwd!E?fEtX>(Ia3ig%93ySL}bEs{di4{Y=(z46D2 zx!J@BvzRN+U3tSXjbjKEe=nCc@XybWLuW4n>5<4cK*Y-yzn{Oh%(^zc3VR4vN%9d^ zJk!9A0+gcyEe|urWs^7dBXCi;Vekj-n*AhB78oqh^q2gY5yTN~*=bGR@xK>n8WzFN zTxT6xis&zZclsjT?e!}=;QMAvrpvs!L5i?F1XCt14yU|~nnv!+>T2Fr2B+%1d-v|# zVW{I)kP@>O8V|8`wNMEIz=4YHms-o{LRv%d1llp_m)6fMLt#Q-N-1 zgDTAhDkG$0G;O_Xk@65;<4ae$NA%6lIQw^iM&;ygp+p+v;>JTJ{i+!LJz{+QjOh6d4_H|bG8Vtcy%S|wP}=#{x2NLJn5@+@90c$Pib zr0g0wGW4I*JVn8Nqmc68Pj7{S4b_^1jjb~ZZZ?end;9h(<^qqMWmob{o0-U@)^-rq zFca@w#MKh0!Ro(bZvJg$Wdc;FSrHX(Pagy%d(qGneUyZzu*hx@DMo5#O9sIzD)iGJ zl~0PoBbHD4hy;zHR{>o(jf7*%%ef~c^6KmByI!%6tis9Sm97B=AxT26q{;8c2Iy-b zvGQe-3WRMXwh(^Z|6KnkObxf=!H7ZIO(zOPR6ig;MI4HL0%B`uX=2(?mW3I&rz)8WY}{b6B`8PEK4UR7h*m@bsP2 zcGlL`K#7E$4g&eg2Fwhv#J^U;(DC3a!TIh?)dDDEWf_pya-MNZ^0Yl&CSqk{i_{a7 z%MSaKNF|7(H3h|=^R@CQKhat$8ajbteuhq%C&oXxC|r0Abp7@9bs|7ZCNSBC9J?GX z{e2MFkWczavl{}zzlG8fhwngj zhmN(^0l)4gX)!S-Nw29ON#Br6@I43v?*apMr4my?szkR&Ceu%iy#BjzzUib8c$ow}Oe4GIioLY`hy)YMGy#8OLmCkP3aXV=)-<;Pva(O-)oYz$T(ORW&t7t})vjFa<(#;XPMh3^6O`ST=MNMzyv| zl@cbUvaF}`qOM(a3PER!+3nlLhK4r-j`3KkDl6q4Fn2R^KnWxY0mvpMCN}AD$0QTd znzF&?C!lLXn!_bh=fFViwDqbXc=H*GcSP-br87r(Jpi=;jp={0(+Wk9y1u|Ubra0` zz(DEr_eHCaq~2a%uXp;uK92kB*|UezFP}UCz1D+ttepKnZ4T@@+V}T-~n;)1|^pQ+Gel0e&#!Bq;p}`B}{@2+p zoD;UooW&gz0?_FIUZHoVC0bNNKQlu~Y6dECjOww84wRZp6a5Vn^e5fwgIM<2V@8wPm2Q()H zbie<%FyU9XmHTA1pO;|b6visikpkb4XQNz3ee{o61&1n|_k(vgVmzFUy}j{yT-1xu z6KiPzXca!<~>BB%8uZ ztkW{BD9J^#`pZrUpB<`I=raD#1;XdWVol-8 z?-qfPyuz(7aFdx|X7sGOchI+Pyt}JpR@FGX`MS`OgFEZC8TnJ)y3_3+@TOK3ArHan z_)Gu86xnKezh}HW3C%AzwpU~p_TDMmzWwi4ySnx|jtq_bvOGI`ro3`aUE-um&OZRmxpBw}qFN$WV zaq+qfH#!seo>_jR##7F1A8bXvNyrC@sGhBwBl$^#a`8`?gMN=Z*U9g?CBPKFPcb9P zVgEZ(6fsZhLvT!yW+aK>Z{A(Gav&ky&5TEov(&9%AQuIFn2Qm=8a~HtsAzDs+FA}A zA}rqKkhKL`rKjFv5YD8v-Cz1(fJH~B9y`V}Cs94NJUTN%EDn>QycRq%>uA^|X*Ttc zoj9OV7ODor6IfGl>-~1EZ#)NV#Y=J3`s@|>&BC0Dx89jyg`|6hls^aY$ITjEwychS zLGax+h=>1{UhMvBk?(u=u#st5-SXLt^WQ92*LS_gm!;j3s*Zk9_0-4c{`z%e){vIN ztUEtXIXj?nc9wUe%%QAbUVoIAcG9h<40G@lT&KRX*&yNJ{-w`_HEn12Y`&-M=$1WN z*R|A$H0)2u%ded@Ybc59d4zwNwfKH))Fh!dFe6LW_oL>xniY(^I!G2-P5Vap{KE)& z;&*?InGC-4Yj4^qUT6|2J_>EowMa0d{ktr^vp?{ZmV5P(;wk#Njcbmu`?U zSbLm?f2XDE{(3+i_|~d#*66uX(bz8sdmrzu@2;Zo(yfHv$P6?7>oCt8+Q9!1lH3at zP%HbCfp=rP=Pcc6(Z(|l-p-78oceYKX5Yc^yvw~#3zJU)vSGcpl{8;8vFY~k59fVl z4-ys|ncIHfIUn_F|N6@#dAK1xUIO~f>Q^!9Pj>1#xjlIJF)zYf5DAG_?|-hS=pr~f zwl#a&={^7p;l8Ej%js$KWA$rIRL@UW*Ril{Qw`oOE?77>VCAcM*y8+saL=spkr7G8 z5i~F57MHQ3iuRjP_9&1d>Y4}%;uf%*_al&A48l8ZkitqUZDs{^N%%x4E}w{%dZ-$@ zjU|-4cR_nI3~vq}pRn3%B2_PeX#@BMcuCAAc<>g0i?bivTZZsPvLYNhm~0_OA<^nO zaDf?nDWjHhL?!>BfBK7>7ex^$+>h3^Kj7x!)pg##@bf3Fz3RmH_e;^Vyjk8m0_*Jl zeq^@T2}&1Yn~2;wFm9HK{H;K-pi4IMCp@yS+XpZh(&}^kh|=W8k#Mb0vqM%P4sNMSeM4emJK zJ?U(BKemu%-e0<<+F_mm@50lCDF`UvG#$%|_!lPA=QiRZ$;bY0ddRM9U8qT3Rbkva z;!>|HiAdbmZ^DA@f4@q?=p= z8feQ?@|Hc~r~4U4wl@x8mIQvodh%COr5fiykxcp0(K&3gT3jctDU4WjFo+_K#|5TI z!0Zq1=Ong?VcC^FOYtQXn7$2ZIS))lzE&^1j*v8My}jAPvW6ETGJU7TvV|g9r1b@l zil{`bQiAfE|MNTxPxt-s#s#SYS2l?d*P|tyBS!3RNTB#C)cG)1Cr5=psBmo}B9OJf zZ%D5vE+R9S@nF3X&q+w5Nh%(iQ!Typ+kWYJ*XVCX_TZr7tzXIE?Nte*iiPc^rO4zx z{e4Xze>pJCX&bWyzxd25UBD^R_-@d^y{?g-*k6MDz1vTZ97J{{yftr-`&EtbIL($_ ztHtINYenIWj!I{GPQ6_I3d6l78O83h;d1fZ zhPAmi%?`GlkPvhqNI@#2jqVmZcNjFy7Znu&%mHc$R-x|8-}kqqUPV2+_LXg&BF%W3 zsL&$6pzOQty>EMtudR|oYAO=+7G^w})~D<1>F)47>Hk^~@dg8qb`f^{gpsKU9esyY zJeWD&Mxk>@Y`Z^df2YL_>r-eg6>px0?q!wLRETKi4SiR?db#e+4M!hYiR#MC2iLi@ zc@cyTT|}vq<;BC+&6n2Q#;Yi4gokKht1x*JUck0TctEpa_ugJ`@7>pLZ*PCJL-oQ` z58C~O;f;KaMYLqCHGYNZ5gTPn>^jBspEi%5(64Pc=F{ffZrM8gQ9Tx=TVcP~bUM=1 z8M5@fy35{)Z$V$ra3QFttV$^0?cDptAvvB^rQR(EC*y)Qg%o;P4Oo*{BbnLMh$PWf zu<-%i4ndEcN;1+-P6SpYWIX`1-UYS(WTWDj#hlNUZhec4?gzFO(RWDrZ9Zxe>9o3f z^atX*Rs%nZdcxm6mr&`#w=^(7;Oj$Fzi8AbB9jNTBOO1qTW{kj~tbxA%zRm3+;=Xwrj+`O} zzpSi_FLtCberYrXsYLg)PjgW!YKSDEJd!SU?!93wpN2mtOo$|(!2)d#l?1{%!~Edu zFhT+yg4fqz(%uow*$@fMv*7KKE(lN8F)`J-qd9-=X7^Jv-rceGX6h4s0OXG@xH3uAc;yVra+K|4%#|8 zIs?6eK?pl8*0KbU&t<$rU}5W0@un$|Ke~VUa86q0iSUcI+H&*&_iE|Dju(GHZgFO= zQc+vWv98)hPs;g?6{<9&qRJMZ%IZH31?O_ws$87E*SCMTkIMX=r8=1p?K;q2n|jyE zyQY&Ph{$r*CK*E4Uw zh;_>?>suw}f70XN;oKZ~OPy|H2gAhNFhG!-G+1%{-PhE!prF4;u}TGU&LqfLA7^HY zOHJ7+${ok;;*oxaRf**T&PlE_h9ocSuP$(p}QsAT3>jgdmMHXR-Hw-uIkue(>`8d*AC`YpyxR_>ED5ueB#T z@4CVSjPr+M(U6lSr{`ZTXMY*u#w53OHP%bSRt*{DrBo9+4gvkM-!WxQ-@6p0v6uq2^fys z2Kp_7TgwSf7dnsJFe|Ft{59Kx>%s#2l1&gz37(vV{yypc0!3Imtbh{2 zEO|c4DOU+>4YYz5e)jr?I6hqPTQG<@`q7|f!g3KwU^75Wm& z#Ij5@MMvQXOq0Bzl?FCxF(`rD;e8ia!N4SQ#%t5e1FHq++XX#8hBVRE7}~ylW_-Tt z2}oK%zV^U$2EUOZu)P=(T#lec@Hb|}XNhIOH^FrHlcr~5V0@*tw1A4SwHzt2e_<`>P&|t zl*$186ZEPGc-<@8+IXxn&J1Q}zwDd83gdvs@*I2B2`NlY)m2e|b9fM{fmf zSTG?I*Ni3MZv$3uF=-&@W72C)`0@q(p}}FDN7=)poCac60QjQ=x+6nXO3a?kby@&x zyx|Cyl@6NW_S&t14<}>R;ahz1E$}LqLJ+Nk_T&{{U%H>Hq(muqo*UWN$Q=D};q8^v zWW8)ZwA)VMf1Jy9 zfVI;-V%)Jyf8%lZKhCQlCDu@{SW9cY`}(2MMfv4PDslIVO9;}Yz->!Xb`yneD4*B; zmi2p>jjwz|LW*fok8VHmQTkRz;dHB z{1x<|tuesl?ez+baV7?Xrd$_nUsRzyx~JiEw?Jgz2D-$#KdSbe%x$~ShT5VZJf5$Q zKtzJnrPf+a1Bkd(W=LK*+28+JDEWpXGdnf)2F$!b7e{n2xRHt;2iX<#F81S?iLLDe zfDOzh+P6k0_YHkFJT5|~fKQc1PbbIA+a1%rVXGMw9IRA7Uv%POoEw~+DA=Wf;aJ05CLw_kg_=n6f z>$`h6T5g;7pLMS}tYxJ6*wdF45Z^E3^ZyCZQtTABs8 z8?Z1iU?!2R6Jj#}+=xPlWK}5h;;D@KGCEizm^Q3e6hs7%p@oH}cqn03K_$v+YA}|g zKYzAVAqBY7cj4no!D^8DH*Sk?5x*Qp4yQEfA#^OyRdu(k=LJVLlo@Ed+50#g{!8lv zm~*Akzdj$!zeRIveIX_Je7j67D0TU__p|?63}PeaGDn3bu5CYPdJs+tVy>1&#tp3~ z`x!a6XB_S)YY50P`D7Th{vN=b73t|jbsq!+I)HB2b^^mMR6N20L=h2@W`}ho3+T!x z@WEA96?1x@0@lyV^HW}hNRW6mj(j#xN+WFh@9S;MXTtVRn3VXZmWtuREg3`ytxpOS z?Ql!~$hO6_Xpe1e@-XSqt{cf228!%8^Q+Ue^@J0=bHv0xSeng5Th^>=C~YN-h2%d$ zb;4xhOvc2H{6T6&4`>r+A^{#sR#spcO`jGy1AZoePSB8m0&WdKmES=yO;Ra zI`F!RqJ<(!y4sUf)8$p=WmRQ+3y!VqM2Gg@lMf&#aoacX&W0yPI?0{ZnhCeU$$-;4 zn7N{TwV;dXb8W?!ogI@>{D`3l)acNXc+lhz%7@wpW18!~>zM}EU<3`E(ZK2_ga)Kl z!EmTSQdSms#|=M9yPOG|#f)z>^wBO53Z zEG#YimjkjDz<^;T&M46atBnF?NBqII(GD^}6IvhD-I6ZQG`nNAoRm>KqPigSe)xVR z{<5kesaJ|)VS%sBYO9qYO;JF$5L49UDek-P{BqDI=A>STr=7=kQF2Q98@GnzJ(EOL z6`Blgtz(c5URs--i1tV(kBmeFyj%4xEu@?dM0_;h-9V3T19=YFnX+x~>@9F5ZXc2W zR~X<4fENmV6KVFX_Em7uABLJ&?FXcU(+bEZuj3Z7T8tFTW2=>rvFN5ioGf(&z39=P z3J9h%L_f3wsBoXRNMBdi_vRA)Qtx%r3RpnR89^-pHuUWeqi2_mELZg)U)ORK&&IN& zRF!gr^tdT6Ewi)EfgueyTfgJzAQ;^0ubTh=JpVu`wkIUFEV!jWZRPTBIq zZi+)2QQf) zT^1j{nwKLSZJ|KW?zs0W!irY-pLCGw?%rP?pL9)UC6)jkf*s24&F#_LO(ga7)%MN~3;1OTtO_}N)|Qs= zlQih0*w{^=zyNNKQp)#KR0ge%kOV?PkdWjM1kzwwLJj)r-08hWLQpbWQqt0IZ4ZkB zOeFdeebw22)YJKI}Ywa!tUlm^j}O9 z`aR$~kZH-wJSCn0m_m1uDpbgqEax{snmV(ai0)N`w1 zTUG?DLHg9{_aN1oqeEn!9?s31Tc%j~t&NrCAN|XF6{dj5XEe}Pb!E%gA;%6u;o09T z@pv(OnYyBFMXjzPV!m&Y#Vv%%(e5Vt_416)WU~NT?_gIMAAN027JDc~Kf6<%LF2>1 zplN4Lptx^5SQuJ7@uDVY0~q=$!J&^ARgA(r+66a+=XO`Mj4V1;QVr*)^f);a>j=NV?m@xad`Y_EwN_L{q`tv&W8|4j!en!&StvP=zsh?42 z=8V*h1=`ZQe8l~Zp(UCLD6hmW)LJERpn?pxK%Y?{B9jB|RU9bLOnSu(fQpCGF<|*k zWLX_UZFyT8f%Cy~!v_Q+&{>)ekqzm5y>XG_j}VD1B|u>(0_Cg>Lu7h7zWFNf_`e<` zS6DJz0d@d)WF9yR1uVKk&sN8^2n#Esk@Q%=l%#|Kuv?{nLPA3Uq6JuNe1NOw%W5A9 zI~yAglYv|lx2PA;vjJEllKdi>ea~oMn{weh`3zNv-ITzZ9^O+Oxy7}=)nxWuUk`+A zA+ZllN|;jZMxPT(@ALCE<8aqB^+^1eyFEZi2S5jBuTO%2r+5WDB?Huw>nw+ABa1Y( z1qm~(lP*FOxog6;XtVHjjzOI~isl9|o<}XD%q~rGpsyJT^+HLh@EcrRT#7|MR|IU(H;5 zd{PBhM8T*mH{`eemi@o2XMp88n zkotTR@-fcHQC5M${oB=dPIRS&8AnHdQkjTYOBE&DHx@nz<#2s?S-ZNNf=+&e#{DeD z`B)dN!nxS}(mfOG{nGfWC+^*cT#4Zm0r&eV>tBavi#Qg7J{@awlU}!kr$JFpzgO&K zwh$1pU6JqI7iPUMB}MZ}i*~09zZG?gJZzoa+=NlVl!^$M2i#W%-y4CYmzu*Q=9e=) z3qcrU0_7G1${Zkh^O-MIHnHA4hEh`P_euke&%jatUFsSz%BW?5yZUp!R_$+280r{S9@thak?09T= z`Ie5r1r~;X2b4xejetCL1^P_j!jv~$HBHQzmuh5diyTWUR;m|Z{EkPe2#lk_WP56C z3<|pkB+vl)fKjKJg+=|comFXIiO$k{0Ra*N&U&hx5Y)q3V0#A~fzprE$VN;v{KTaM zDun^)(1Q^6L2-xlAxQABuqVJKXO;~Z5;8Yg;pG@a?8I->OM!Mscw1a{sr+>>&nd<| zzxAoT=c7kik_$8YfELlwVZybrErM06AdcHx9)scJYF_=}Y)V&8J4yG{9_v_LIq=t0 z|C{HGxz%-%&GiCg?4$(E6}P9+z9^gnJ#A&>SAd@D;jH;gfjEfU0Q!Mft`aS60cHd~ zMlC{YsY!ZIdk}&+YTYe>W8h?ImtEI#OdtJy-)iH5;T&CI6VE4YAV?m8@L&S2NpA9c z5lZVJncb%y2&pi2;X!XR~khM4%rxWsoj1TSoWqt1-GNFlKEh$iC+L6IyvC zj8?K37DQ8AiPG3I`Byq5n|-T>AbNRqAqa;{O0=vxWrT_T-t7lDLZzo^TVi0~qd50n zR<#EcPmu@bEnMvJPurh$b9K^X%IDJA`q?{QRD~S^np~)2LP%jyuCLo#>6qK6i&y_` ze|jT$`aKE8;_ln{2A}JVinjP(EWDx^Y#hxyICyX5E@Ae}I=tG4tnld-Wv{@jii zBFau>am^MVEW_VEd4Gw6pnk}6x;k1IUt1$muro7z-xwEB95{qxMV9~QAT$YCPNdTP zJS9Q~p`He=%it+S&~)4%67L;Dfu0?Tb~!cPI}~{mRYld#l2lFg{;JA7wFPH7Hm!>) z&b?!G2~~{?#3)iMlrmv*rUzdsJ;;gX|LXa~Yr25}f zu2~CDfx%^x1@u3L5zM^%nK_8SUGfYvLBVceYZn(v_>w>!B>&!#x7d(}w~H1(I|SmW zn8Lp*Z~vi_oMx7WExz!Ln>y0r6rlW`=h~t4Rf>+bg?0Au)YuwCG>d!zg+F6fYHbUe zo%ovk@4FstJ?cTiEdXcNcZo9!7@_)oPR#%GFCP>ABL|z7fPdSg zxOTdb_+8&5Is)F&p@y0|z--tUTZmNg_+K(25Yb#uN$L|HM_hDJ)A%aWw&HN4y4{zA zwY8;ux7Kskv9XA=lELif!Ise1KRcmgsQJ9M*x7#8`H3TRn5ri}>fF_h=Y06h$_fKF zzqTy|6U;Qf@D3@s9tA)`zxFf?-NDrl3Q;(8a$@x8RxUX=TE5bAACF32t`eXyMv4_V zphL#TjROS+4Z-d!`mVhxjJKq=8$+YDBC6}f-I3rJ3S3SJc2L{4rS0*jPqS7z|1d7$ z-0I<=AN-$q{lVxD1UKbqpz_E+@>D%Mha-lQeCV%93KgEm4LZb2Vb3uaW{;f}u%e)# z5E{dh2S};hFJDCXkAa7xmikC9p>KE?o`g+{Zd3JBySc~{eK&{e=43pR-Ed9`UoQR23Xcbc_lWA`6E|7U-HxSfV0OTojs^Q+GH z=Zvgm#mbQ0EY6yd#!3Zew`M~(gtm&IQwTsZy+F8c`>C|53y5cAwi4{#7j|Y<-<_Vf z%<`41L`b_;!L0v^x0y`prjCYJ%>8D87y_3-UP5pCG4NoNj&t*~OG%1X_Q$umUl^-U z#00|bz1voF%&}AOm)&qH+DgI*-a@m0Le`@50~T|xy#7<$+T*_;yz#jLNb%mc^cTPT zr$=s!WIg`I6VB!~e$Ip30__!1Ams&|KDJaKi?aaAb27Pr9clKK09wfFC;*k}{nrAz zTj(_0HGFDtuk_pu*ta%?z;2$m11z{mgrHj!l0l`h$Ot46gIl9Vy~22MRMEvHLvvf_ zu8j*njbCf zNbXK6WRaGg1_lv;m8jBf=>pcv?d|O|+zbhEOrh0dcv2sTbM#l2=yu>2BD^>EBjKce ztrn96p0Pmli#HGlB#`|6&qVa-v-cC5zOt_8cl(%mD)atx+=I;}L*KrR>+sHOLc+vJ zh?fGf4;c@LWBKRK+?}1!ZWn-oqp4uM@E}PNb5`%h`tx+pM4++Zda!gN;eJ_V%OIov z#70B9xB?#m6U}#xe&|ytMuv{ix;1NiTb6%LqKeYZ%tuo0cDz5P`=2c9&qcWQD6Y0v zpM<~6DcAhr*VJPCNF#ia_rcNYi;j}US}uB)zuuDT+U+81@uz}-S2GG?Np8~m&80Ic`-$UgK!5wzwrJt`SjY&0P!V- z`!y+NzEHc0W$!^(!ZgUP~oJGT)uX0;8qek@6bvH~ACV;Oz zVx&^Y0;#iJs1SjoXA~fIs%eQnj-W*+TR(r4aYo5DYUabFjPE3$1GV!a7MHw<(`1U^ zKqC%~wjpR)vEx^j8hcV;c0)O`s2>NdU56?(L7!go1hcPJV ze1SKV?Lq1$=aq>jl+idChhQwMi5Tl$*usO|W_!l_#76q$m59mOIwwSN(%$et%P; z`6_oUwUi9_Z}%_#C;y(9+M3xGb#6bh)+QZ#83(HVm~G?DOh2${(>(wYY}qm+>ps_a zNsUmm7y%GSM{pfHrI1vwxui}zAy!ZT@8{_bJUa|Zh~y9%q4N(u_g9B`wt!Uo$~Mzu zY|*SSn4d^I$-U!W114V(bkmKVPVwGmeLx`G-s{-alWsE^TnaQthnFJcBVqq|~N6tA(=Rn*=_L?({D@T4e#a<^D~|#|CiZ9OwgzvWlA}c{X|g1&Q!jq9WHBfU z@*=94hu62Cjl#9!e0cJ!Is9LT+*Lx3j)$3pug4}H$iW=x4@4i?IqvVDD3KUtU^gBu zLqIfh;&xKf=R>Bc&`gib$6GpttnYpg{h==Y_pW=o72W0K)pxfKn_KYbHySv}J==e@ z_anSlF4)gJo(E<&D|Kj_(>`q&^0;q5+=(-3yxj6WmX0twOr1_;Dquk%U5@|thwk99 zwze7i@J%EpwM4?h|I>wx&;lKhe@8k}Bl&OwM<$pyMVKJmA1wH<31wRL{EL`0t6;v% zu(7juW9OGgC1w~bQGOeoFNY!i4b|&fX`?fe=&q3Fm(u%()HD2|!%i9kO`eDXey`gL z4GM<-v+aI>M+6r^J$1^`D&`9Ko^J31Xs(qrvLda!^-CM+9vig~r0d z0>Drc)pdiMfp+`~u>XPcI{EN>R_S&24B~~;D*S-AQ+F&Z0GvjM;W%;hI=o9YVm0CC{#65;teuAjL>EIO~4SK8bM^T9;y??Z!_N-P8x?{*hI!rMp|* z{7)o#y4y>QiN$oNmS|A-en}Vn;p4>0s0(B&gL0%HF}00-sy|e$Kd=ZI#5r=z5gr(u z=fBiLlVzcf*z|{xEfqDSkRNz}>))pV3+$#4TU~4y&MSO&9kaEqt^)7-9+KWL?=~ix zaRt3LNT}hwoZfwp=Zlk6{W@uy!{zTsHa5`S^PLO6yzRc1&tH5$Oi6xtKYql1Vr1%O z4B&W=_dgp-TaO>1KHHAxJPX*ScI6irPuqWV7GhGxEDS2ZRYn|5Eul&Zf8*ma#%0M% zcp(Gf24{uH<#vSSqnIDKo$ayl_Ie0Nm?;n=2nzaz4xA!lGSgG$C*;`Kv&IMZ_BzNy^{AU#MP>$24DbJc>#f1Wl@rV(`sFE}H7wqCiBH+b?MGf+Wj zRM#QMd+P$Zln)7Q4YEc_{U4e69c%fiPaV%In+8szc z)qUqC?`>RGT}>jKA*QWeR#kV^@RR5y4`77^hk;Ud8BLVtyl?nhZuEHlbwcia#iVt$wz$KiPzD8k! ziG6l4?>GP5rH0&t%70LFMbZ&yvfIe1M>tCzJff*TjF^}BN(eH`il-K5jGB@;BU8(T zT^p5=Vup6ivhcbpV~b1Fb1Z-r4bbs z1vVz>@KCUW$V*J@A5_S=-b8;p4DD(j%5&IFQ4g%V1ku?bfgJn=psq5Bz>BrZnO?%q zsFf2)8a4a4Odr-pAe$eP?AjJMRF1$hmCdrjVID~xJC0l5?n%A$J^1f(e)4SZCpOqb zuNcqE4vmQ%{!B8d2MJx>_JV>wJi``j81(I8 zk{Cv9+Lh#4hEL1wA)aX9L>Kqsv=W#j3|B=wNxAsNz$Hld7uoyUhpzQeYiONcZX5{H zr0JNkCjKd#ucJwhAg3e%0*`DXWAd+vv;A0;r;8N4x*tQ~-Eu$*0#KZaZI7QF=y8gL z4q6I9!37VRT`bd=l*HiUn`?hf@BkDx9hD%hj_jo9z{ENV?Q=r;d>cHO8mrkDs;Hhc z+c~kW|LX;GDJHIA>;JH7t$&M4QqU<(aI!3HOH}W`KFUv>q42(}fRF+#EtcQEe*n9%pobjSk9v#)TiX;zTSVDdGGbgt`0B#S6gjxy;a{w)RyWIN zv0EL?XC36=vAnaThs>;(@al6y3L+swp-LN_H)`06#K#a?Is5R&|MnQV?)FXBbN znWES9wKTFSmiV9nS|qw{`SW&S+Fllv<6wv!xkf^ye>{0=po2gFqd{xqW_3$8D1#G6Z4=U++PqIy z#Rh(X=n)9MSEn~sF%}WSwR@d}n(?Qr>3D=y6*Z^F8_R=*m-C!BbxRjrF33ZuB;3~b zMc+R~nlGRw7$W)oD&FPHXvdNc-uxI^xhBS`sdsE8cZe=cr+KU-Qj4z4qx2hKAW=c_ z#Lmh2V{$TkebjyU5@R<1tikql4g+xFife19CnjPtl%#3-*X{p)5ZJap!YJzREA^Ra zSFNpVI-=L`vPaE~g$kdYUz|U%+42%a-*pQa-MjqG2lu%57A{`cGZ`0d&l?(P0X;v+ zgo0IS`-(zwLNEJ6-bb+Dn}UVcUI{u=_`HggS=q;H?y_4R09;Tit*8JXhKxZI z?S1|~X652MXht_33**_1ruFspAet9`ALKIE+1Rk~TG8Vj{HQ3SFo%UX%uB|p^=i6= zza7=7DFnJSXIq=;_!?*6B^B*7kQB{+{jf<8s3P|ko=QoG`lQXo2Oz0L%ihv5-Tj?Y zhw+<5xAso!1gW*{F9cjRkL|gB;tV$=d_F@##X(VV6gc+dTAfk9DdfJbO;pI3(Q_Jh ziIfrSQfcV;1*hzAvu!jK8Y6KJ%b(^jUF4dD$^08H?bf_waG3%t1Zxls+<=luvsy2G zB2ND5N&ruN;soJSLCp|cYsQ}>cSMk)fr$9s`sTCSnKuBxssuq*Y7 zYyxFq$5BL_N`AjxwPUp+Tm;cs8WR?m77AS2hwj1aXy!hKSKFdxO zHfi0TRez+*%Vs+*+y}m({<7<h44>NguX?iyai<8 z;5PI7T5ZN6erUMyu|0&~&sT&Ctq| zq~3`dgUwIpDd#dACUwhXPo~h@CNDs6$ZpPvTSo# z`uCz`dQPpTa(jM0U}h>%gApfs)T|zl+Ko+Y+IbZF5!xEM@5nASN2C3q>n-4czi|V} z2FAwZdSc)tpm;Kkxl>}6j}9$-Rf&+Qgq`klz;&nCp)v>#JqN4$wYw}f^yf>+4{)0{K9Wj zuI{H;MVaYf4q};RDge^*&&Een3XB`5zp4fB_ zU0L1z+b(^29NW3SqK*gY)@8Ea;#gYh5&y2&L>o>Y_CHYclPG32kWuX%Mnuk;QuHYh zKU(hL5 zPUm1S3~CL;Svvq61g=UT{|T{-ReYm&cX-7g5^2&j6heAL zNLSR4!2YD<$0t-3*d_8*%Q`z&D;6s+ENhyI@xsl;pN!-@R<9_MfV@|ua|=D%ZSgJE3_73_Ke-x{eq zk?RS)RoKyrWxzpmUYO7MxZylG*6;oGE}xtWZf}#9zhf5{lb_>{2to2<07)lq3?cYN zumAeOt9<@&v^93LK&&1SO0h7hbFvYSIq(+l@OXv3m@0RmrC{=H_j#b8=WQN9vc zi*ER%0P&*)TEyRJa$m(U_XDGmlPIusXY29s@FudjF^`^rV;0b_Hots9`|aLnKlAe^ zyna9E4*(PPfY>3vL01m5m0ZHGAZPBN*{-DJZ`@=Jj3RxLf^Q3>D~(C9hgp7Iz1191 zpR7u&@~fs8LAtf0R}&vZ_W`OA3%-pURp#KUf#4H-5zF4hlFI8vVxHrUox#!k+-k_-EH*c?PtZlxt{;^&AK>qKOK60o3e#@ty ze*su?zf&?+POhJ1t*N%Tcpg={1G;?%DmL@mKfskO3$={@3wwQJX{7ayhmat>XJw>z zc>MvjD2g0iCIpfn8XB1{6MPkw_wgZxnUc3j@blCWvpcxq?$3zgV{)?F&Rpof4sogV z20a{|c-=fnlLn7@&5FJkV_WmY-Yyu8D9RpGW5ym3yV2mGsz$_Pkr&)UCz89fsqsX3 z23qrrSI^FnS$18qP-~SXe$nEye9eFv(24+z6k#<5PEOt1x4R3l$2T`dZSP{C?&p9K zZM!BqdK{1hU(^0f10as5ihzU$v~o&(VtKi^Mo!&kj54&IWuJm3Yof{djHCH4i`AE* zG{{7EKVcd*EDLwu_r2*;B;C+`n9{Y3C;8fWko}ltCu)kRJzP=vO|nMi=X7axWu8nz zZW<;riTlF^_sl>-{74v6c6C`2u2O{#U_NOEoug@|B>4$2&qHC#;>*Aw zl}-OgC-CsQXHysL@6yeWX2bS>gls;KX5H2Ne=e1deK=TB$_vUOi!ztI&jXk|bd@_r z#g47=EJsqw#M1DbljDZW(I{w?rxkb!h;J$0iJoUE9sZXtU@e_@&CbKKGCGR50C3hB zJ+IjOUlCDJ-p`NUDK=VjXCq(G zIt7KGM)m6SrzL9Ii;KgT1DL#T6W}yFlU0;nX#mXkG^Q2z-K^qRhE8Pbv%!Dlr0M+J z1gwkrs4uvONxhn4#|f)dqT0oxe&J|g$9#W&5op>Buq6A2s9gNloGF}KTu=Z7gH}t? zBlgO&l7})N1B2Pmw`chc4GlX2!B(bmlka@SmZ1!}MnsnG;(Jvl-oICoWJQ?JJb#A1 znW|>p0KJ=(>;^vEvOV)+o;L3*6JdkD(Lp}4guDn39;d#pKY)SttwVOFS+eX{fq#nu zt|F29`2qckswh-lFR6+*L;$0x$KTSjGQ>_&m6|Ud1B<H$PhR;~xGS!F`wQMAaRX96(xpFET#Lh0ucZt6vF&DlaiOXjORl@Tq*}mn z-g7&wSN{QR?d<#_^t49#=X69YiegNpLFe{<+Y{!Y+}Ed}l)2%wAL(2svnXi_IicUV zJ*o|kIqlRo^9v19^;hyE2Uvl!m){p!fpi6Tx)axm(cfOjP}zVMiwj0tql+1JrxUte z<1R>UcVrhX|1*>m0J!kruk*^vj9=lvXdMFb#+<*e9Ua+lTN7N3pffKo ziMWYQhy8$n6ywqf?X`-M|2JEoya>G|u52uJ@UCuT2RGCW?i(Ab(PDuIN%-2z<3dJM zy$Zz@_x*X;q(-y*{K}i|JEwgn!N;e$d)&~_8%75*FLeAJm`((hk@$8r`-km64U#we z`(~K{zDLA0F~?LZ?18CGEG*U$+3~Ra)BmN>=|1nTkHDvqC27-=DQ_(2=%eH9gCmmj z-EB}*fY46B1%D;B_eA7K%Z?_|&V34*z*0%z`uwuEr1TPl%TsCXG zUiv?AUX!p9nGE1aV|o-LIP=o6Wu}hE$ehAZwj_0}-6DyY#hN4%kEf*x$)v{qBi|mE zo=8(hT2_u)7mp1`nOC8L za6O=u1qq?A(N!74pd|#76LwBc80Z2w7F`o51#(FW`u124HV{QpM7M0O%*Sgka5_HY zl8U}Nmp(3YqO+DpoEJ*DDiWh${#u+k11ldgv(;@uE8zeDx;2isuU+;JlrX#40OieIVLPKJu}`Sl#5`gcvVZ8vyKi%0DE_Y(z!`4RNoTNu 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

&bv<> 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

5x-ZsST@Irl0&(pLe_Qv?^0a#qpzVc)!4mC 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

Mt?FMzHGIvKOfsWv#`lZ4c3!yhCi5C0)8 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 BAz}ak literal 0 HcmV?d00001 diff --git a/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/01-or-registers-list.png b/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/01-or-registers-list.png new file mode 100644 index 0000000000000000000000000000000000000000..7909482804ac367dce56b48d24e1d9a22193d0cd GIT binary patch literal 200360 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

kT4ceJ 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

(Yr#fe5)k)XBiI*l(20kdKCsH1bI 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

kT4ceJ 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%0DE_Y(z!`4RNoTNu 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

&bv<> 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

5x-ZsST@Irl0&(pLe_Qv?^0a#qpzVc)!4mC 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

Mt?FMzHGIvKOfsWv#`lZ4c3!yhCi5C0)8 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 BAz}ak literal 0 HcmV?d00001 diff --git a/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/03-or-tooi-schemas.png b/i18n/nl/docusaurus-plugin-content-blog/2026-05-07-woo-register-opzetten/03-or-tooi-schemas.png new file mode 100644 index 0000000000000000000000000000000000000000..bd86c30bb6be3241860a1c857ccaf5c4a1cc7b3e GIT binary patch literal 146125 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%0AlP^c;9;#v&zp?g`{hKg#S?;2cqY}`dlZyf`z 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,Rj7jcwXH=9Txn+ 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

CV6q!ztg(e&5jj%SM&;GejyPCd6rOF%v3! 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%0Dg1YGUx8=d#!YRiLc>+0e3{=b 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 agency uit Almere. Bouwt toegankelijke websites en webapplicaties voor gemeenten en overheidsorganisaties, met focus op WCAG 2.2 AA en NLDS. Levert de Conduction Woo-solution op OpenCatalogi, OpenRegister en OpenConnector., apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], - solutions: ['woo'], + solutions: ['openwoo'], }, // -------------------------------------------------------------- @@ -58,7 +58,7 @@ export const PARTNERS = [ logo: '/img/partners/shift2.png', summary: <>Nederlandse softwarebouwer voor gemeenten, waterschappen en provincies, onderdeel van de Conxillium-groep. Levert CMS, burgerzaken en formulieren, en daarnaast de Conduction Woo-solution op OpenCatalogi, OpenRegister en OpenConnector., apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], - solutions: ['woo'], + solutions: ['openwoo'], }, // -------------------------------------------------------------- @@ -95,7 +95,7 @@ export const PARTNERS = [ logo: '/img/partners/bct.png', summary: <>Nederlandse leverancier van informatie- en documentmanagement-software (Corsa, Verix). Levert de Conduction Woo-solution aan organisaties die hun informatie-governance op orde willen krijgen., apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], - solutions: ['woo'], + solutions: ['openwoo'], }, { tier: 'host', @@ -103,7 +103,7 @@ export const PARTNERS = [ logo: '/img/partners/open-gemeenten.png', summary: <>Open-source platform voor gemeentelijke websites. Bedient ruim 30 gemeenten met toegankelijke sites (WCAG, internet.nl). Levert daarnaast de Conduction Woo-solution., apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], - solutions: ['woo'], + solutions: ['openwoo'], }, { tier: 'host', @@ -111,7 +111,7 @@ export const PARTNERS = [ logo: '/img/partners/exxellence.png', summary: <>Nederlandse leverancier van zaakgericht-werken-software voor gemeenten (xxllnc Zaken, plus apps voor belastingen en het sociaal domein). Levert daarnaast de Conduction Woo-solution., apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], - solutions: ['woo'], + solutions: ['openwoo'], }, { href: '/partners/yard', @@ -120,7 +120,7 @@ export const PARTNERS = [ logo: '/img/partners/yard.png', summary: <>Digital agency uit Utrecht voor gemeenten, zorg en kennisorganisaties. Open-source-georiënteerd, ruim 80 projecten. Levert de Conduction Woo-solution., apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], - solutions: ['woo'], + solutions: ['openwoo'], }, { tier: 'host', @@ -128,7 +128,7 @@ export const PARTNERS = [ logo: '/img/partners/io.webp', summary: <>Belgisch-Nederlands digital agency met kantoren in Amsterdam en Rotterdam. Bouwt klantgerichte digitale platforms en levert de Conduction Woo-solution aan publieke en semi-publieke organisaties., apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], - solutions: ['woo'], + solutions: ['openwoo'], }, { tier: 'host', diff --git a/src/data/solutions-catalog.js b/src/data/solutions-catalog.js index 3c99de9c1..b5e83a88c 100644 --- a/src/data/solutions-catalog.js +++ b/src/data/solutions-catalog.js @@ -15,11 +15,11 @@ import React from 'react'; export const SOLUTIONS = [ { - slug: 'woo', - href: '/solutions/woo', sector: 'public', sectorLabel: 'Publieke sector', - title: 'WOO compliance, by Friday.', - shortTitle: 'WOO compliance', - outcome: <>Live WOO portal at your hosted Nextcloud. Every law category becomes a register, every register publishes through OpenCatalogi, every connector ingests from your DMS., + slug: 'openwoo', + href: '/solutions/openwoo', sector: 'public', sectorLabel: 'Publieke sector', + title: 'OpenWoo. Wet open overheid, by Friday.', + shortTitle: 'OpenWoo', + outcome: <>Live OpenWoo-portal at your hosted Nextcloud. Each Wet open overheid-categorie becomes a register, every register publishes through OpenCatalogi, every connector ingests from your DMS., builtOn: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], goals: ['compliance'], status: 'production', icon: , }, diff --git a/src/pages/solutions/openwoo.mdx b/src/pages/solutions/openwoo.mdx new file mode 100644 index 000000000..0fc33be76 --- /dev/null +++ b/src/pages/solutions/openwoo.mdx @@ -0,0 +1,218 @@ +--- +title: OpenWoo, Conduction +description: OpenWoo — a live publication platform for all government sources on your hosted Nextcloud. Eleven Wet open overheid categories, audit log, citation-stable URLs, no migration. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, PairRow, PairCard, CtaBanner} from '@conduction/docusaurus-preset/components'; + +OpenWoo is a live publication platform for all government sources, running on your hosted Nextcloud. The eleven Wet open overheid-categorieën become typed registers, every register publishes through OpenCatalogi, every connector ingests from your DMS. No migration project, no separate front-end to maintain.} + primaryCta={{label: 'Talk to a partner', href: '/partners'}} + secondaryCta={{label: 'Read the docs', href: 'https://openwoo.conduction.nl/'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +

+ + + + } + title="Active disclosure, by category." + > + Each of the eleven Woo-categorieën becomes a typed register. Records get published the moment they're created; the citizen catalogue lights up automatically. + + } + title="Federated, citizen-searchable." + > + OpenCatalogi indexes every register and exposes the public Woo-portal at jouwgemeente.nl/woo. Federates to data.overheid.nl out of the box. + + } + title="Connector pulls from your DMS." + > + OpenConnector reads Woo-records from your existing zaaksysteem or DMS via REST, SOAP, or file drops. No glue code, no nightly batch. + + } + title="Audit log, signed and timestamped." + > + Every publication, edit, withdrawal, and access leaves a tamper-evident trail. The Wet open overheid evidence you need at audit time is part of the install, not a year-two project. + + } + title="Citation-stable URLs." + > + Every published record gets a permanent identifier the moment it's first disclosed. Nieuwsmedia, Tweede Kamer-vragen, en juridische dossiers blijven werken, ook na herorganisaties of platform-migraties. + + } + title="Two minutes to install." + > + Drop the three apps into your Nextcloud, point the connector at your zaaksysteem, configure the eleven registers from the supplied profile. Live before lunch. + + +
+ +
+ + +
+ +
+ + + } + name="OpenCatalogi" + why="Surfaces every Woo-register as a public, searchable catalogue entry at jouwgemeente.nl/woo." + /> + } + name="OpenRegister" + why="The eleven Woo-categorieën each become a typed register. Schema, audit log, citation-stable IDs come for free." + /> + } + name="OpenConnector" + why="Pulls Woo-records from your existing zaaksysteem or DMS via REST, SOAP, or file drops." + /> + +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
OrganisatieTypeWoo-portaalBronStatus
NoordwijkGemeenteopen.noordwijk.nlZaaksysteem.nlProductie
EpeGemeenteopen.epe.nlZaaksysteem.nlProductie
RoosendaalGemeenteopen.roosendaal.nlZaaksysteem.nlProductie
ZutphenGemeenteopen.zutphen.nlZaaksysteem.nlProductie
TubbergenGemeenteopen.tubbergen.nlZaaksysteem.nlProductie
DinkellandGemeenteopen.dinkelland.nlZaaksysteem.nlProductie
MoerdijkGemeenteopen.moerdijk.nlZaaksysteem.nlProductie
Gooise MerenGemeenteopen.gooisemeren.nlZaaksysteem.nlProductie
Hof van TwenteGemeenteopen.hofvantwente.nlZaaksysteem.nlProductie
GoudaGemeenteopen.gouda.nlZaaksysteem.nlProductie
TilburgGemeenteopen.tilburg.nlZaaksysteem.nl · Sharepoint · RXMission · NotubizProductie
NoaberkrachtSamenwerkingsverbandopen.noaberkracht.nlZaaksysteem.nlProductie
Hoeksche WaardGemeentehoekschewaard.accept.openwoo.appDECOSAcceptatie
HelmondGemeentehelmond.accept.openwoo.appDjumaAcceptatie
+
+ +

+ Begin 2024 succesvol getest met KOOP en WooGle. Voor elke nieuwe deployment: praat met een partner of installeer zelf vanuit de Nextcloud app store. +

+
+ +
+ + +
+ +Three apps, one configuration. Talk to a partner if you'd rather we run the rollout, or install yourself from the Nextcloud app store and follow the recipe.} + primaryCta={{label: 'Talk to a partner', href: '/partners'}} + secondaryCta={{label: 'Read the docs', href: 'https://openwoo.conduction.nl/'}} +/> diff --git a/src/pages/solutions/woo.mdx b/src/pages/solutions/woo.mdx deleted file mode 100644 index 9248b81ff..000000000 --- a/src/pages/solutions/woo.mdx +++ /dev/null @@ -1,131 +0,0 @@ ---- -title: WOO compliance, Conduction -description: A live WOO portal at your hosted Nextcloud. Eleven categories, audit log, citation-stable URLs, no migration. -hide_table_of_contents: true ---- - -import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, PairRow, PairCard, CtaBanner} from '@conduction/docusaurus-preset/components'; - -A live WOO portal at your hosted Nextcloud. Eleven Wet open overheid-categorieën become typed registers, every register publishes through OpenCatalogi, every connector ingests from your DMS. No migration project, no separate front-end to maintain.} - primaryCta={{label: 'Talk to a partner', href: '/partners'}} - secondaryCta={{label: 'Read the recipe', href: 'https://docs.conduction.nl/woo'}} - iconColor="var(--c-blue-cobalt)" - icon={} -/> - -
- - - - } - title="Active disclosure, by category." - > - Each of the eleven Woo-categorieën becomes a typed register. Records get published the moment they're created; the citizen catalogue lights up automatically. - - } - title="Federated, citizen-searchable." - > - OpenCatalogi indexes every register and exposes the public Woo-portal at jouwgemeente.nl/woo. Federates to data.overheid.nl out of the box. - - } - title="Connector pulls from your DMS." - > - OpenConnector reads Woo-records from your existing zaaksysteem or DMS via REST, SOAP, or file drops. No glue code, no nightly batch. - - } - title="Audit log, signed and timestamped." - > - Every publication, edit, withdrawal, and access leaves a tamper-evident trail. The Wet open overheid evidence you need at audit time is part of the install, not a year-two project. - - } - title="Citation-stable URLs." - > - Every published record gets a permanent identifier the moment it's first disclosed. Nieuwsmedia, Tweede Kamer-vragen, en juridische dossiers blijven werken, ook na herorganisaties of platform-migraties. - - } - title="Two minutes to install." - > - Drop the three apps into your Nextcloud, point the connector at your zaaksysteem, configure the eleven registers from the supplied profile. Live before lunch. - - -
- -
- - -
- -
- - - } - name="OpenCatalogi" - why="Surfaces every Woo-register as a public, searchable catalogue entry at jouwgemeente.nl/woo." - /> - } - name="OpenRegister" - why="The eleven Woo-categorieën each become a typed register. Schema, audit log, citation-stable IDs come for free." - /> - } - name="OpenConnector" - why="Pulls Woo-records from your existing zaaksysteem or DMS via REST, SOAP, or file drops." - /> - -
- -Three apps, one configuration. Talk to a partner if you'd rather we run the rollout, or install yourself from the Nextcloud app store and follow the recipe.} - primaryCta={{label: 'Talk to a partner', href: '/partners'}} - secondaryCta={{label: 'Read the recipe', href: 'https://docs.conduction.nl/woo'}} -/> diff --git a/static/img/partners/notubiz.svg b/static/img/partners/notubiz.svg new file mode 100644 index 000000000..ca3e2df7e --- /dev/null +++ b/static/img/partners/notubiz.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file From f2bbf356d38955a53631698fabf2e4c12ad3f60d Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 15:44:23 +0200 Subject: [PATCH 043/330] fix(solutions/openwoo): drop Nextcloud platform from the pipeline diagram (#75) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PLATFORM step between Store and Present muddied the three-app flow without adding information — readers already know OpenWoo runs on Nextcloud (the hero says it, the FeatureList says it, the body copy keeps saying it). Show three clean steps instead: Ingest → Store → Present. --- src/pages/solutions/openwoo.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/solutions/openwoo.mdx b/src/pages/solutions/openwoo.mdx index 0fc33be76..4a820cc78 100644 --- a/src/pages/solutions/openwoo.mdx +++ b/src/pages/solutions/openwoo.mdx @@ -83,7 +83,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa steps={[ {number: '01', kicker: 'Ingest', name: 'OpenConnector', caption: 'Pulls Woo-records from your DMS via REST, SOAP, or file drops.'}, {number: '02', kicker: 'Store', name: 'OpenRegister', caption: 'One typed register per Woo-categorie. Versioned, audit-logged.'}, - {number: 'PLATFORM', name: 'Nextcloud', family: 'workspace', pill: 'files · users · auth', caption: 'The workspace your team already logs in to.'}, {number: '03', kicker: 'Present', name: 'OpenCatalogi', caption: 'Indexes every register. Search, federation, citation-stable URLs.'}, ]} end={{label: 'Consumers', items: [ From b39cff9516acf32f426c0605dc64dd69028d2e49 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 15:46:10 +0200 Subject: [PATCH 044/330] feat(solutions): add Anonimiseren solution, drop software-catalog + legacy-erp (#74) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /connext panel listed "Anonimiseren" but pointed at /apps/docudesk because no solution page existed yet. Software-catalog and legacy-erp lived under /solutions but are actually an app and an unbuilt app, not outcomes. Adds: - /solutions/anonimiseren — pilot, sector "Publiek & MKB", built on DocuDesk + OpenRegister + Presidio. Privacy-by-design pipeline copy. - solutions.mdx sector facet now accepts array sectors so the new entry counts under both Publieke sector and MKB filters. - connext.mdx Anonimiseren tile now links to /solutions/anonimiseren. Removes: - /solutions/software-catalog (an app, not a solution) - /solutions/legacy-erp (an unbuilt app) --- src/data/solutions-catalog.js | 23 ++-- src/pages/connext.mdx | 2 +- src/pages/solutions.mdx | 6 +- src/pages/solutions/anonimiseren.mdx | 132 +++++++++++++++++++++++ src/pages/solutions/legacy-erp.mdx | 107 ------------------ src/pages/solutions/software-catalog.mdx | 83 -------------- 6 files changed, 143 insertions(+), 210 deletions(-) create mode 100644 src/pages/solutions/anonimiseren.mdx delete mode 100644 src/pages/solutions/legacy-erp.mdx delete mode 100644 src/pages/solutions/software-catalog.mdx diff --git a/src/data/solutions-catalog.js b/src/data/solutions-catalog.js index b5e83a88c..3524c7869 100644 --- a/src/data/solutions-catalog.js +++ b/src/data/solutions-catalog.js @@ -42,13 +42,13 @@ export const SOLUTIONS = [ icon: , }, { - slug: 'software-catalog', - href: '/solutions/software-catalog', sector: 'public', sectorLabel: 'Publieke sector', - title: 'Software catalog for the gemeente.', - shortTitle: 'Software catalog', - outcome: 'A public software catalogue, apps, datasets, APIs your organisation uses, searchable in one place, federated to data.overheid.nl.', - builtOn: ['OpenCatalogi', 'OpenRegister'], goals: ['integration'], status: 'production', - icon: , + slug: 'anonimiseren', + href: '/solutions/anonimiseren', sector: ['public', 'mkb'], sectorLabel: 'Publiek & MKB', + title: 'Anonimiseren, voor je het publiceert.', + shortTitle: 'Anonimiseren', + outcome: <>Privacy-by-design pipeline op Nextcloud. DocuDesk en OpenRegister sporen PII op en redacteren records en documenten voordat ze de organisatie verlaten. Presidio onder de motorkap, AVG-uitlegbaar., + builtOn: ['DocuDesk', 'OpenRegister', 'Presidio'], goals: ['compliance'], status: 'pilot', + icon: , }, { slug: 'mkb-workspace', @@ -59,15 +59,6 @@ export const SOLUTIONS = [ builtOn: ['MyDash', 'OpenRegister'], goals: ['reporting'], status: 'production', icon: , }, - { - slug: 'legacy-erp', - href: '/solutions/legacy-erp', sector: 'mkb', sectorLabel: 'MKB', - title: 'One legacy ERP, every modern tool.', - shortTitle: 'Legacy ERP bridge', - outcome: 'OpenConnector pulls from your ERP, fills typed registers, exposes an API to whatever your team uses today.', - builtOn: ['OpenConnector', 'OpenRegister'], goals: ['integration'], status: 'pilot', - icon: , - }, ]; /** Look up a solution by its slug. Returns undefined if not found. */ diff --git a/src/pages/connext.mdx b/src/pages/connext.mdx index 85a92428d..82061b381 100644 --- a/src/pages/connext.mdx +++ b/src/pages/connext.mdx @@ -64,7 +64,7 @@ import {APP_COUNT, ECOSYSTEM_COUNT} from '@site/src/data/apps-catalog'; {position: 'top-right', family: 'solutions', label: 'Solutions', items: [ {name: 'OpenWoo', meta: 'WOO', href: '/commonground#openwoo', desc: 'WOO-compliant publication flow. Active disclosure, queryable archive. Solution built on OpenRegister + OpenCatalogi + DocuDesk.', icon: }, {name: 'ZaakAfhandelApp', meta: 'Zaken', href: '/apps/zaakafhandelapp', desc: 'Citizen-facing case-status portal.', icon: }, - {name: 'Anonimiseren', meta: 'Privacy', href: '/apps/docudesk', desc: 'Privacy-by-design pipeline. Auto-redact PII before publication.', icon: }, + {name: 'Anonimiseren', meta: 'Privacy', href: '/solutions/anonimiseren', desc: 'Privacy-by-design pipeline. Auto-redact PII before publication.', icon: }, {name: 'Doriath', meta: 'Vault', href: '/apps/doriath', desc: 'Self-hosted password and secrets vault. Per-user, per-team, audited.', icon: }, ]}, {position: 'bottom-left', family: 'workspace', label: 'Workspace\nApps', items: [ diff --git a/src/pages/solutions.mdx b/src/pages/solutions.mdx index b6fbc1a4c..7be3db32e 100644 --- a/src/pages/solutions.mdx +++ b/src/pages/solutions.mdx @@ -23,9 +23,9 @@ export function FilterableSolutions() { const facets = useMemo(() => ([ {key: 'sector', label: 'Sector', items: [ - {value: 'public', label: 'Publieke sector', count: SOLUTIONS.filter(s => s.sector === 'public').length}, - {value: 'mkb', label: 'MKB', count: SOLUTIONS.filter(s => s.sector === 'mkb').length}, - {value: 'health', label: 'Zorg', count: SOLUTIONS.filter(s => s.sector === 'health').length}, + {value: 'public', label: 'Publieke sector', count: SOLUTIONS.filter(s => Array.isArray(s.sector) ? s.sector.includes('public') : s.sector === 'public').length}, + {value: 'mkb', label: 'MKB', count: SOLUTIONS.filter(s => Array.isArray(s.sector) ? s.sector.includes('mkb') : s.sector === 'mkb').length}, + {value: 'health', label: 'Zorg', count: SOLUTIONS.filter(s => Array.isArray(s.sector) ? s.sector.includes('health') : s.sector === 'health').length}, ]}, {key: 'goals', label: 'Doel', items: [ {value: 'compliance', label: 'Compliance', count: SOLUTIONS.filter(s => (s.goals||[]).includes('compliance')).length}, diff --git a/src/pages/solutions/anonimiseren.mdx b/src/pages/solutions/anonimiseren.mdx new file mode 100644 index 000000000..653ea5c74 --- /dev/null +++ b/src/pages/solutions/anonimiseren.mdx @@ -0,0 +1,132 @@ +--- +title: Anonimiseren, AVG-proof publicatie en hergebruik +description: Privacy-by-design pipeline op Nextcloud. DocuDesk en OpenRegister detecteren en redacteren PII voordat documenten en records gepubliceerd, gedeeld of geëxporteerd worden. Presidio onder de motorkap. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, PairRow, PairCard, CtaBanner} from '@conduction/docusaurus-preset/components'; + +Een privacy-by-design pipeline op Nextcloud. DocuDesk en OpenRegister sporen PII op en redacteren records en documenten voordat ze de organisatie verlaten. Werkt voor Woo-publicaties, dataset-export, klantcommunicatie en interne kennisdeling. Presidio onder de motorkap, AVG-uitlegbaar.} + primaryCta={{label: 'Praat met een partner', href: '/partners'}} + secondaryCta={{label: 'Lees de recipe', href: 'https://docs.conduction.nl/anonimiseren'}} + iconColor="var(--c-blue-cobalt)" + icon={} +/> + +
+ + + + } + title="Detectie op tekst, tabel en document." + > + Presidio-gebaseerde detectie voor BSN, namen, adressen, IBAN, telefoonnummers, kentekens, e-mailadressen, geboortedata. Werkt op platte tekst, PDF, Word, Excel en op velden in een OpenRegister-record. + + } + title="Redactie met behoud van bewijs." + > + DocuDesk schrijft een geredigeerde kopie, bewaart het origineel achter rechten en houdt de mapping per match vast. Audit-trail toont wát is geredigeerd, op grond waarvan en door welk beleid. + + } + title="Beleid per register, per categorie." + > + Elk OpenRegister-register krijgt een eigen anonimisatieprofiel: welke entiteiten gedetecteerd worden, welke daarvan geredigeerd, welke geblokkeerd. Woo-publicatie, AVG-verzoek, dataset-export en intern delen krijgen elk hun eigen drempel. + + } + title="Mens in de lus, waar het moet." + > + Lage-zekerheidsmatches gaan naar een review-wachtrij in Nextcloud. De behandelaar bevestigt of verwerpt; de beslissing wordt onderdeel van het profiel en verbetert toekomstige detectie. + + } + title="Uitlegbaar voor de AVG." + > + Per geredigeerde match wordt vastgelegd: welk type PII, welk model, welke versie, welk profiel. Functionaris Gegevensbescherming kan elk besluit terugzien zonder het oorspronkelijke document te raadplegen. + + } + title="API en pipeline, niet alleen UI." + > + Aanroepbaar vanuit OpenConnector-flows, n8n, Windmill en directe REST-calls. Sluit aan op bestaande Woo-, export- en publicatieprocessen zonder de UI te raken. + + +
+ +
+ + +
+ +
+ + + } + name="DocuDesk" + why="Voert de redactie uit op tekst, PDF en Word, schrijft een geanonimiseerde kopie en houdt origineel + mapping veilig achter rechten." + /> + } + name="OpenRegister" + why="Levert de typed registers met een anonimisatieprofiel per categorie. Audit-trail toont welke match op grond van welk beleid is geredigeerd." + /> + } + name="Presidio" + why="Microsoft's open-source PII-detectiebibliotheek. Powert de detectielaag van DocuDesk; AVG-uitlegbaar, on-prem, geen externe API." + /> + +
+ +Twee apps en een PII-engine. Praat met een partner als je liever wilt dat we de uitrol verzorgen, of installeer zelf via de Nextcloud app store en volg de recipe.} + primaryCta={{label: 'Praat met een partner', href: '/partners'}} + secondaryCta={{label: 'Lees de recipe', href: 'https://docs.conduction.nl/anonimiseren'}} +/> diff --git a/src/pages/solutions/legacy-erp.mdx b/src/pages/solutions/legacy-erp.mdx deleted file mode 100644 index 7f9c1185c..000000000 --- a/src/pages/solutions/legacy-erp.mdx +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: One legacy ERP, every modern tool, Conduction -description: OpenConnector pulls from your ERP, fills typed registers, and exposes an API to whatever your team uses today. No middleware, no migration. -hide_table_of_contents: true ---- - -import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, PairRow, PairCard, CtaBanner} from '@conduction/docusaurus-preset/components'; - -Je hebt Exact, Twinfield, Visma, of een ander ERP dat de laatste tien jaar braaf z'n werk doet. Vervang het niet. OpenConnector trekt de data eruit, OpenRegister maakt het typed, MyDash rapporteert er over. Geen migratie, geen middleware.} - primaryCta={{label: 'Install from app store', href: '/install'}} - secondaryCta={{label: 'Talk to a partner', href: '/partners'}} - iconColor="var(--c-cobalt-700)" - icon={} -/> - -
- - - } - title="Connector voor de Nederlandse ERP-stack." - > - Voorgebouwde adapters voor Exact Online, Twinfield, Visma.net, Yuki, AFAS. Standaard-velden gemapt naar standaard-registers. - - } - title="Real-time of geplande sync." - > - Webhook-based sync waar het ERP dat ondersteunt; cron-style schedule waar niet. Eerste sync vult het register, daarna alleen deltas. - - } - title="MyDash voor de cijfers die ertoe doen." - > - Bouw dashboards uit je registers: omzet per klant, urenproductiviteit, openstaande facturen. Geen aparte BI-tool, geen ETL. - - } - title="Open API voor andere tools." - > - OpenRegister exposeert een schone REST API plus GraphQL endpoint. Notion, Slack, n8n, Zapier, je eigen scripts, alles consumeert direct. - - -
- -
- - -
- -
- - - } - name="OpenConnector" - why="De integration-laag. Trekt data uit je ERP zonder glue-code." - /> - } - name="OpenRegister" - why="Typed-data foundation. REST + GraphQL API rolt uit het schema." - /> - } - name="MyDash" - why="Dashboards en KPIs uit dezelfde registers. Geen ETL-team nodig." - /> - -
- - diff --git a/src/pages/solutions/software-catalog.mdx b/src/pages/solutions/software-catalog.mdx deleted file mode 100644 index e90240dee..000000000 --- a/src/pages/solutions/software-catalog.mdx +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: Software catalog for the gemeente, Conduction -description: Een publieke software-catalogus voor de gemeente. Apps, datasets, APIs in één plek, federatie naar data.overheid.nl out of the box. -hide_table_of_contents: true ---- - -import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner} from '@conduction/docusaurus-preset/components'; - -Een publieke software-catalogus, apps, datasets, APIs die je organisatie gebruikt, zoekbaar in één plek, gefedereerd naar data.overheid.nl en Forum Standaardisatie out of the box. Geen aparte tooling, geen jaarlijkse upload-marathon.} - primaryCta={{label: 'Talk to a partner', href: '/partners'}} - secondaryCta={{label: 'Read the recipe', href: 'https://docs.conduction.nl/software-catalog'}} - iconColor="var(--c-blue-cobalt)" - icon={} -/> - -
- - - } - title="Search die werkt voor burgers." - > - Citizens landen op jouwgemeente.nl/catalogi en zoeken in één veld door alle gepubliceerde datasets, APIs, en applicaties. NLDS-conforme UX uit de doos. - - } - title="DCAT-AP en NLDS-metadata." - > - Elke entry exporteert als DCAT-AP en Nederlandse profiel-metadata. data.overheid.nl en Forum Standaardisatie pikken het op zonder handmatige upload. - - } - title="Federatie met andere gemeenten." - > - Je catalogus federeert met die van andere gemeenten via signed manifests. Gezamenlijke standaarden, gezamenlijke implementatie-dossiers. - - } - title="Audit trail per entry." - > - Elke publicatie, edit, of intrekking laat een tamper-evident trail. Klaar voor inspectie of verantwoording. - - -
- -
- - - } - name="OpenCatalogi" - why="De zoekbare publicatie-laag. Federatie naar data.overheid.nl, NLDS-UI, citizen-search." - /> - } - name="OpenRegister" - why="Elke catalogus-entry is een typed register-record. Schema, audit log, citation-stable IDs out of the box." - /> - -
- -Drop OpenCatalogi en OpenRegister in je Nextcloud, configureer de eerste registers, en burgers zoeken dezelfde middag.} - primaryCta={{label: 'Install from app store', href: '/install'}} - secondaryCta={{label: 'Talk to a partner', href: '/partners'}} -/> From ebdf74da706deae2a90e019474881ac5e4977bce Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 15:49:37 +0200 Subject: [PATCH 045/330] feat(academy): publish CTO thinkpiece "Nextcloud is a platform" (#76) Adds a 7-minute opinion piece arguing that Nextcloud is in front of the same product-to-platform transition WordPress, Salesforce, the iPhone and Atlassian each went through. Walks the historical pattern, why Nextcloud fits the substrate role today, what the openDesk / La Suite / Mijn Bureau bundles are still missing, and what the next eighteen-to-thirty-six months should produce. Also adds Ruben as an academy author and a "bigger picture" section on /connext that links to the thinkpiece. --- .../2026-05-19-the-platform-moment/index.mdx | 80 +++++++++++++++++++ academy/authors.yml | 10 +++ src/pages/connext.mdx | 12 +++ 3 files changed, 102 insertions(+) create mode 100644 academy/2026-05-19-the-platform-moment/index.mdx diff --git a/academy/2026-05-19-the-platform-moment/index.mdx b/academy/2026-05-19-the-platform-moment/index.mdx new file mode 100644 index 000000000..39ea4d3c2 --- /dev/null +++ b/academy/2026-05-19-the-platform-moment/index.mdx @@ -0,0 +1,80 @@ +--- +slug: the-platform-moment +title: Nextcloud is a platform +contentType: opinion +authors: [ruben] +date: 2026-05-19 +summary: A thinkpiece on why Nextcloud should be viewed as a platform, not a product. The sovereign-workplace bundles are the start, not the finish. Microsoft has eighteen to thirty-six months. So does Europe. +tags: [Nextcloud, Platform, Sovereignty, AI] +durationMinutes: 7 +--- + +The same pattern shows up every time a software category goes through its platform moment. WordPress in 2011. Salesforce a few years earlier. Atlassian after that. The iPhone, in the consumer space, before any of them. Each one started life as a product. Each one became a platform. The companies that managed the transition own their category two decades later. The ones that did not are footnotes. + +I think Nextcloud is in front of that transition right now, and I do not think the field around it has noticed yet. + +{/* truncate */} + +## The pattern + +What turns a product into a platform is not a feature release. It is a reframe. + +WordPress core stayed deliberately small while the plugin ecosystem grew into a multi-billion-euro marketplace. The plugin layer became the actual product. WordPress core became infrastructure. By 2020 you could not pick anything else for anything except enterprise CMS or proprietary SaaS, not because WordPress had won on features, but because the ecosystem around it had. + +Salesforce AppExchange shipped in 2006. By the late 2010s, 91% of Salesforce customers were using at least one app from it. That is the actual moat under the company. Competitors do not just have to beat the CRM. They have to beat the CRM plus six thousand integrations. The AppExchange partners collectively earn multiples of what Salesforce earns directly from the marketplace. Both win in the same outcome. + +The iPhone won smartphones not by being a better phone. It won by being declared "the platform that runs apps" before that was technically true. By 2010 every other platform either copied the App Store model or died. The naming preceded the platform reality by years. The naming was load-bearing. + +SharePoint is the cautionary case. Microsoft positioned it as the platform for a decade. The technical surface supported it. The marketing budget supported it. The architecture and the economics did not. SharePoint never produced an ecosystem. It stayed a product. Power Platform displaced it as Microsoft's positioned platform for business apps. + +Five cases. One pattern. Each one is the same shape. + +## Why Nextcloud sits in this position + +The substrate for a workplace platform sits in Nextcloud Hub today. It just is not framed as substrate. + +Files. Calendar. Contacts. Talk. Mail. An identity layer that already speaks LDAP, SAML, and OIDC. A permission model that runs across all of them. Every business app needs these primitives. Nextcloud ships them already integrated, in one product, with one access control surface. ExApps adds the second piece: a production-grade pattern for running anything, in any language, as an integrated app inside the workspace. Two to three hundred thousand servers. Several hundred apps in the store. ISO 27001. Blauer Engel. EU data hosting by default. None of these are decisions still to be made. They already shipped. + +The sovereign-workplace push around openDesk, Mijn Bureau, La Suite Numérique, and the Bechtle-Nextcloud offering uses Nextcloud as a core component. Schleswig-Holstein moved 40,000 civil servants off Microsoft in October 2025 with openDesk as the collaboration layer. The Bundeswehr signed a seven-year agreement. The Robert Koch Institute and the International Criminal Court run on it. The deployments are real and at scale. + +What none of those bundles do, yet, is frame Nextcloud as the platform underneath the suite. They frame it as one of several components in a bundle. The frame is correct for the moment of cutover from Microsoft. It will not be correct three years later. + +## The bundle is not the workspace + +Year one, a buyer asks "can I replace Microsoft." Year three, they ask "where does my organisation run." + +A bundle answers the first question. It does not answer the second. A bundle ships a fixed set of tools, with the integration between them happening in the user's head or in their browser tabs. A workspace is a different thing: the unified surface where people work, with the platform underneath it that hosts everything else. Tools, data, AI, the hundreds of small workflows every organisation runs that are too small for IT to formalise and too operational to outsource. Leave requests. Equipment loans. Visitor registration. Hardware inventory. Vendor onboarding. The long tail. + +The vendor that answers year three wins the relationship. Nextcloud-as-bundle does not. Nextcloud-as-platform does. That reframe is the thing I think is overdue. + +## AI as substrate, not as a checkbox + +AI changes the calculus on local data, and not in the direction most workplace vendors want it to. + +Microsoft Copilot routes user data to Azure for inference. Google Workspace does the same, to Google Cloud. Both have built sovereign-cloud variants — Bleu in France through a Capgemini and Orange joint venture, Delos in Germany through SAP and Telekom, Microsoft Cloud for Sovereignty as the umbrella programme — and these variants are serious engineering efforts. They reduce the surface of exposure. They do not eliminate it. The CLOUD Act still applies to Microsoft itself, even when European partners operate the infrastructure. The structural problem is jurisdictional, not contractual. + +For most market segments, the partial-sovereignty pitch will be enough. For the buyers who chose openDesk and Mijn Bureau specifically because the architecture matters, it will not be. They picked the suite path for a reason. + +An open workplace architecture inverts the problem. The data is already on infrastructure the customer controls. The AI substrate comes to the data, not the other way around. Local LLM endpoints. Retrieval indexing across the workspace. Tools that expose registers to models under the user's session, audit-logged at every step, with the operator choosing the model. Mistral on-premise. A self-hosted Llama variant. Or, where policy allows, a commodity API. The architecture supports the operator's policy choices, not the vendor's. + +The features are competitive. The architecture is structurally different. The structural difference shows up in every procurement evaluation that takes sovereignty seriously. This is the part where Nextcloud's position compounds. Microsoft cannot win on sovereignty for the buyers who care most about it. Microsoft can absolutely win on time-to-value and ecosystem depth, which is what they are spending five years and significant capital trying to do. The race is over which structural advantage matters more for which buyer. + +## The Microsoft clock + +A bit of context on the size of the other side. Power Platform was at 33 million monthly active users in 2023. 48 million in 2024. 56 million in 2025. Microsoft has named 500 million by the end of the decade as their public target. 97% of Fortune 500 companies use Power Platform in some capacity. The 2026 release wave unifies Power Apps, Power Automate, Power Pages, Copilot Studio, and Dataverse into one AI-native citizen-developer story. The framing they use internally is "low code is dead": apps start as conversations with Copilot and quietly become React. + +Europe has, by my read, somewhere between eighteen and thirty-six months to ship a credible architectural answer. After that window closes, citizen developers in European organisations will have trained on the Microsoft stack, and switching costs become real in a way no sales motion fixes. Mindshare is the most important window. It is the one that closes the fastest. + +## What needs to be true + +The architectural answer exists in pieces. The sovereign-workplace bundles are one piece. Open-source workflow tools — Windmill, n8n, OpenProject — are another. Open-weight LLMs hosted in Europe are a third. The composition layer that lets a non-developer in a municipality ship a leave-request app in an afternoon is the piece most still missing in production. It is the piece we are building at Conduction, in the open, as [OpenBuilt](/apps/openbuilt). It is not the only such piece that needs to ship. It is one. + +The other thing that needs to be true is naming. Steve Jobs did not market the iPhone as a phone with extensible features. He positioned it as the platform that runs apps, years before the platform reality fully shipped. The naming was load-bearing. It still is. + +Nextcloud-as-platform is true today in every structural sense that matters. What is missing is for the company, the ecosystem, and the buyers to all describe it that way out loud. + +## A personal note + +I have spent four quarters of my company's runway building toward this view. It is not a quiet view. I am writing it down because I think the architectural argument is settled, the competitive window is open, and the next move belongs to people who can see the shape of the moment clearly. That includes Nextcloud's team. That includes the buyers running European sovereign-workplace migrations. That includes peer companies in the open-source workplace stack. + +If you read this and disagree, I would genuinely like to hear why. ruben@conduction.nl. diff --git a/academy/authors.yml b/academy/authors.yml index 164de7cdf..49b211927 100644 --- a/academy/authors.yml +++ b/academy/authors.yml @@ -4,3 +4,13 @@ conduction: url: https://conduction.nl image_url: https://github.com/ConductionNL.png page: true + +ruben: + name: Ruben van der Linde + title: CTO, Conduction + url: https://github.com/rubenvdlinde + image_url: https://github.com/rubenvdlinde.png + page: true + socials: + github: rubenvdlinde + email: ruben@conduction.nl diff --git a/src/pages/connext.mdx b/src/pages/connext.mdx index 82061b381..f7e7abd0f 100644 --- a/src/pages/connext.mdx +++ b/src/pages/connext.mdx @@ -103,6 +103,18 @@ import {APP_COUNT, ECOSYSTEM_COUNT} from '@site/src/data/apps-catalog'; /> +
+ This is not an app shelf.
It is Nextcloud as a platform.} + align="stack" + lede={<>The sovereign-workplace bundles are the start, not the finish. Microsoft has spent five years training a generation of citizen developers on Power Platform; Europe has eighteen to thirty-six months to ship a credible architectural answer. Our CTO wrote down why we think the shape of that answer is already sitting in front of us.} + /> +

+ Read the thinkpiece +

+
+ Date: Tue, 19 May 2026 17:09:20 +0200 Subject: [PATCH 046/330] academy(hydra): drop unlisted flag + 'intern' framing, scope blockquote CSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Mark's review on PR #37: - ZZP-developers/NDA framing removed from Hydra tutorial 1 callout - All 6 Hydra tutorials: `unlisted: true` and warning callouts removed, `Intern` tag dropped — leerlijn is nu publiek - Private repo (ConductionNL/hydra) prereq herframed naar "alleen nodig als je Hydra zelf wilt draaien" - Global blockquote restyle scoped naar `.markdown` zodat marketing- pages ongewijzigd blijven Per gebruikersbeslissing 2026-05-19: leerlijn gaat volledig publiek. Issue #50 framing ("intern, niet publiek") moet apart bijgewerkt worden. Co-Authored-By: Claude Opus 4.7 --- .../index.mdx | 9 ++------- .../index.mdx | 7 +------ .../index.mdx | 7 +------ academy/2026-05-12-hydra-tutorial-4-skills/index.mdx | 7 +------ .../index.mdx | 7 +------ .../index.mdx | 7 +------ src/css/site.css | 12 +++++++----- 7 files changed, 14 insertions(+), 42 deletions(-) diff --git a/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx b/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx index 33af1f9a2..4312accbf 100644 --- a/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx @@ -5,21 +5,16 @@ contentType: tutorial authors: [conduction] date: 2026-05-12 summary: Een korte introductie op Hydra, Conductions agentic CI/CD-platform. Wat doet het, waarom bestaat het, en welke plek het heeft binnen onze app-fabriek. Eerste van zes korte modules. -tags: [Hydra, AI, CI/CD, Intern, Tutorial series] +tags: [Hydra, AI, CI/CD, Tutorial series] apps: [] durationMinutes: 10 series: hydra-tutorial partNumber: 1 -unlisted: true --- import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard, ContactCta} from '@conduction/docusaurus-preset/components'; import Details from '@theme/Details'; -:::warning Intern document — niet extern delen -Deze leerlijn beschrijft IP van Conduction (skills, gates, prompts, pipelines) en is bedoeld voor medewerkers, vertrouwde ZZP-developers en partners onder NDA. Niet doorsturen, niet publiceren, niet citeren in publieke kanalen. -::: - :::tip Aanbevolen vooraf — de OpenSpec-leerlijn Hydra werkt op OpenSpec-changes. Ben je nog niet bekend met begrippen als **spec**, **change**, **requirement** en **scenario**? Doe dan eerst de [OpenSpec-leerlijn](/academy/openspec-tutorial-1-wat-is-openspec) (deel 1 + 2, samen ~30 minuten). Dat scheelt veel terugzoeken in de rest van deze modules. ::: @@ -39,10 +34,10 @@ Hydra is Conductions interne **agentic CI/CD-platform**: een fabriek die OpenSpe - Toegang tot de (private) ConductionNL/hydra repo. Vraag dit aan bij je teamlead als je nog geen lid bent. Basiskennis Git, GitHub issues + PRs, en een idee van wat een Nextcloud-app is. Bekendheid met Claude Code-CLI op hoofdlijnen (we duiken hier niet in commando's; daarvoor zie deel 4). Optioneel — de publieke 1-pagina-versie op .github/docs/hydra/README.md. Goede warming-up als je nooit eerder van Hydra hebt gehoord. + Alleen nodig als je Hydra zelf wilt draaien (i.p.v. de leerlijn lezen): toegang tot de private ConductionNL/hydra-repo. Vraag dit aan bij je teamlead. ## In één zin diff --git a/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx b/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx index f0dc75e28..9ad78abeb 100644 --- a/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx @@ -5,21 +5,16 @@ contentType: tutorial authors: [conduction] date: 2026-05-12 summary: Build, code-review en security-review. Wie doet wat, in welke volgorde, en hoe een label-state-machine de hele rit aan elkaar plakt. Tweede van zes korte modules. -tags: [Hydra, Pipelines, Personas, Intern, Tutorial series] +tags: [Hydra, Pipelines, Personas, Tutorial series] apps: [] durationMinutes: 15 series: hydra-tutorial partNumber: 2 -unlisted: true --- import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard} from '@conduction/docusaurus-preset/components'; import Details from '@theme/Details'; -:::warning Intern document — niet extern delen -Deze leerlijn beschrijft IP van Conduction. Niet doorsturen, niet publiceren, niet citeren in publieke kanalen. -::: - In deel 1 zag je dat Hydra vier personas heeft. In dit deel kijken we naar de **pipeline**: hoe die personas na elkaar werken aan één issue, welke labels de overgangen markeren, en wanneer de pijplijn afslaat naar `needs-input`. Aan het eind weet je het label-state-machine van Hydra van buiten en kun je een issue terugleiden naar de juiste fase als hij ergens vastloopt. {/* truncate */} diff --git a/academy/2026-05-12-hydra-tutorial-3-quality-gates/index.mdx b/academy/2026-05-12-hydra-tutorial-3-quality-gates/index.mdx index 79f3ad38e..ef1d019d6 100644 --- a/academy/2026-05-12-hydra-tutorial-3-quality-gates/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-3-quality-gates/index.mdx @@ -5,21 +5,16 @@ contentType: tutorial authors: [conduction] date: 2026-05-12 summary: Wat zijn de mechanische quality gates van Hydra, waarom werken ze juist NIET met AI-oordeel, en wat doe je als een gate per ongeluk fout slaat? Derde van zes korte modules. -tags: [Hydra, Gates, Quality, Intern, Tutorial series] +tags: [Hydra, Gates, Quality, Tutorial series] apps: [] durationMinutes: 12 series: hydra-tutorial partNumber: 3 -unlisted: true --- import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard} from '@conduction/docusaurus-preset/components'; import Details from '@theme/Details'; -:::warning Intern document — niet extern delen -Deze leerlijn beschrijft IP van Conduction. Niet doorsturen, niet publiceren, niet citeren in publieke kanalen. -::: - In deel 2 zag je dat de drie personas worden aangevuld door **mechanische quality gates** — checks die deterministisch slagen of falen, zonder AI in de loop. Dit deel legt uit welke gates we hebben, waarom ze mechanisch zijn, en hoe je omgaat met de uitzondering: de fout positief. {/* truncate */} diff --git a/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx b/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx index ebc1441a2..46a8780e2 100644 --- a/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx @@ -5,21 +5,16 @@ contentType: tutorial authors: [conduction] date: 2026-05-12 summary: Welke skills draaien in de automatische Hydra-fabriek, welke zijn er voor mensen op de CLI, hoe ze worden aangeroepen, en wanneer je zelf een skill aan de loop toevoegt. Vierde van zes korte modules. -tags: [Hydra, Skills, OPSX, Testing, Intern, Tutorial series] +tags: [Hydra, Skills, OPSX, Testing, Tutorial series] apps: [] durationMinutes: 14 series: hydra-tutorial partNumber: 4 -unlisted: true --- import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard} from '@conduction/docusaurus-preset/components'; import Details from '@theme/Details'; -:::warning Intern document — niet extern delen -Deze leerlijn beschrijft IP van Conduction. Niet doorsturen, niet publiceren, niet citeren in publieke kanalen. -::: - :::tip Niet bekend met Claude Skills in het algemeen? Dit deel duikt direct in de **Hydra-specifieke** skill-families. Wil je eerst weten wat een Claude Skill überhaupt is, hoe de frontmatter werkt, en wanneer je er zelf eentje schrijft? Doe dan de publieke [Claude Skills-leerlijn](/academy/claude-skills-tutorial-1-wat-zijn-claude-skills) (drie korte modules, ~40 minuten). Vanaf hier gaan we ervan uit dat je de basics kent. ::: diff --git a/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx b/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx index dd08749dc..723098521 100644 --- a/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx @@ -5,21 +5,16 @@ contentType: tutorial authors: [conduction] date: 2026-05-12 summary: Het hele recept van issue tot groene PR, inclusief de HYDRA_LABEL_PREFIX-instelling waarmee meerdere devs gelijktijdig Hydra kunnen draaien zonder elkaars labels te overschrijven. Vijfde van zes korte modules. -tags: [Hydra, Operations, Setup, Labels, Intern, Tutorial series] +tags: [Hydra, Operations, Setup, Labels, Tutorial series] apps: [] durationMinutes: 20 series: hydra-tutorial partNumber: 5 -unlisted: true --- import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard, Troubleshooting, TroubleshootingItem} from '@conduction/docusaurus-preset/components'; import Details from '@theme/Details'; -:::warning Intern document — niet extern delen -Deze leerlijn beschrijft IP van Conduction. Niet doorsturen, niet publiceren, niet citeren in publieke kanalen. -::: - In de eerste vier delen ging het om concept, pipelines, gates en skills. Tijd om te draaien. In dit deel start je een complete Hydra-run op een doel-app, met aandacht voor de praktische valkuilen: tokens, images, en — als je met meerdere devs aan dezelfde repo's werkt — de `HYDRA_LABEL_PREFIX`-truc. {/* truncate */} diff --git a/academy/2026-05-12-hydra-tutorial-6-troubleshooting-escalatie/index.mdx b/academy/2026-05-12-hydra-tutorial-6-troubleshooting-escalatie/index.mdx index ad7245d74..be6937def 100644 --- a/academy/2026-05-12-hydra-tutorial-6-troubleshooting-escalatie/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-6-troubleshooting-escalatie/index.mdx @@ -5,21 +5,16 @@ contentType: tutorial authors: [conduction] date: 2026-05-12 summary: Wat te doen als de pijplijn vastloopt. Het keuzemenu tussen development-merge, label-reset, retry:queued, en rebuild:queued — geordend van goedkoop naar duur. Laatste van zes korte modules. -tags: [Hydra, Troubleshooting, Recovery, Intern, Tutorial series] +tags: [Hydra, Troubleshooting, Recovery, Tutorial series] apps: [] durationMinutes: 15 series: hydra-tutorial partNumber: 6 -unlisted: true --- import {Outcomes, Outcome, Prerequisites, PrerequisiteItem, NextSteps, NextStep, HexCard, Troubleshooting, TroubleshootingItem, ContactCta} from '@conduction/docusaurus-preset/components'; import Details from '@theme/Details'; -:::warning Intern document — niet extern delen -Deze leerlijn beschrijft IP van Conduction. Niet doorsturen, niet publiceren, niet citeren in publieke kanalen. -::: - In deel 5 stond de pijplijn. Nu het laatste deel: **wat doe je als hij niet groen wordt?** Dit deel geeft je de keuzeboom — geordend van goedkoopste interventie naar duurste — en de patronen waarmee je `needs-input`-issues weer in beweging krijgt. {/* truncate */} diff --git a/src/css/site.css b/src/css/site.css index b565423c5..ebcb5cbf9 100644 --- a/src/css/site.css +++ b/src/css/site.css @@ -119,18 +119,20 @@ } } -/* Default blockquote treatment, brand-aligned. Infima's default leaves - quotes in neutral gray-800 with a 2px gray border, which reads - washed-out next to the cobalt-700 body. Bring the quote into the +/* Blockquote treatment, brand-aligned. Scoped to `.markdown` so it only + applies to article-body content (academy posts, blog, docs) — marketing + pages render their own components and stay untouched. Infima's default + leaves quotes in neutral gray-800 with a 2px gray border, which reads + washed-out next to the cobalt-700 body; we bring the quote into the cobalt palette so it carries the brand and stands out as a callout. */ -:root { +.markdown { --ifm-blockquote-color: var(--c-cobalt-900); --ifm-blockquote-border-color: var(--c-blue-cobalt); --ifm-blockquote-border-left-width: 4px; --ifm-blockquote-padding-vertical: var(--space-3); --ifm-blockquote-padding-horizontal: var(--space-5); } -blockquote { +.markdown blockquote { background: var(--c-cobalt-50); border-radius: 0 var(--radius-md) var(--radius-md) 0; font-size: 1.05em; From 034c692790453734a97d9947000c9e2a48c115f4 Mon Sep 17 00:00:00 2001 From: WilcoLouwerse Date: Tue, 19 May 2026 17:12:24 +0200 Subject: [PATCH 047/330] chore(deps): sync package-lock.json after merge from development `npm install` na de merge regenereert lockfile op preset 3.10.0 + nieuwe SEO/contact-redirect dependencies. Geen handmatige bewerkingen. Co-Authored-By: Claude Opus 4.7 --- package-lock.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7907cede4..7d0e004de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16051,13 +16051,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/search-insights": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "license": "MIT", - "peer": true - }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", From 88d8b978c926a67b5d22c2a4d62df6418cc9dab9 Mon Sep 17 00:00:00 2001 From: WilcoLouwerse Date: Tue, 19 May 2026 17:47:48 +0200 Subject: [PATCH 048/330] academy(hydra): quick wins from leerlijn QA pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three small cleanups voortvloeiend uit de inhoudelijke review: 1. Verwijder leftover footer `*Intern document — niet extern delen.*` op alle 6 tutorials — was vergeten bij de audience-flip eerder vandaag. 2. Deel 5: vervang `PR #134`-referentie (interne, weinig zeggende) door verwijzing naar decidesk-44-45 retrospectief (april 2026), waar de secrets/.env auto-sourcing fix uitkomt. 3. Deel 3: voeg `hydra-gates` dispatcher-rij toe aan de gate-tabel + intro "14 skills: één dispatcher die de andere 13 aanroept", zodat de telling consistent is met deel 4. Co-Authored-By: Claude Opus 4.7 --- academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx | 4 ---- .../2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx | 4 ---- .../2026-05-12-hydra-tutorial-3-quality-gates/index.mdx | 7 ++----- academy/2026-05-12-hydra-tutorial-4-skills/index.mdx | 4 ---- .../index.mdx | 6 +----- .../index.mdx | 4 ---- 6 files changed, 3 insertions(+), 26 deletions(-) diff --git a/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx b/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx index 4312accbf..fbae5d104 100644 --- a/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-1-wat-is-hydra/index.mdx @@ -222,7 +222,3 @@ Een paar veelgenoemde: body="Spreek je teamlead aan of stel je vraag in #hydra op Slack. Issues op deze tutorial graag op het bijbehorende GitHub-issue." cta={{ label: "Mail het Hydra-team", href: "mailto:wilco@conduction.nl?subject=Hydra%20leerlijn%20deel%201" }} /> - ---- - -*Intern document — niet extern delen.* diff --git a/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx b/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx index 9ad78abeb..7895d3882 100644 --- a/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-2-drie-pipelines/index.mdx @@ -247,7 +247,3 @@ Stelregel: altijd het goedkoopste eerst — `gh pr update-branch` → `retry:que description="De volledige pipeline-overview-doc, met alle subtiliteiten over rate-limit fallback, sloten en cron-schedule." />
- ---- - -*Intern document — niet extern delen.* diff --git a/academy/2026-05-12-hydra-tutorial-3-quality-gates/index.mdx b/academy/2026-05-12-hydra-tutorial-3-quality-gates/index.mdx index ef1d019d6..7f9cd99fe 100644 --- a/academy/2026-05-12-hydra-tutorial-3-quality-gates/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-3-quality-gates/index.mdx @@ -64,10 +64,11 @@ Iedere check is rood of groen. Eén rode check → `build:fail` (in de pre-revie ## Categorie 2: Hydra-specifieke gates -Naast de generieke tools heeft Hydra **een eigen reeks `hydra-gate-*` skills** voor zaken die Conduction-specifiek zijn. Die zitten in `hydra/.claude/skills/`. De huidige set: +Naast de generieke tools heeft Hydra **een eigen reeks `hydra-gate-*` skills** voor zaken die Conduction-specifiek zijn. Die zitten in `hydra/.claude/skills/`. De huidige set telt 14 skills: één dispatcher die de andere 13 mechanische gates achter elkaar aanroept. | Gate | Wat het controleert | |---|---| +| `hydra-gates` | Dispatcher: roept de 13 gates hieronder achter elkaar aan en levert één pass/fail-overzicht. | | `hydra-gate-spdx` | Iedere nieuwe PHP-file heeft een EUPL-1.2 SPDX-header. | | `hydra-gate-forbidden-patterns` | Geen `dd(`, `var_dump`, `dump(`, `console.log` blijft over. | | `hydra-gate-composer-audit` | Mirror van de generieke composer-audit, voor de reviewer's mandatory block. | @@ -234,7 +235,3 @@ De **quality-recheck** draait alle mechanische gates opnieuw op de uiteindelijke description="De officiële motivatie achter de diff-only scope." /> - ---- - -*Intern document — niet extern delen.* diff --git a/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx b/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx index 46a8780e2..2a0c0f8ea 100644 --- a/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-4-skills/index.mdx @@ -350,7 +350,3 @@ Apart houden omdat: description="Een aparte, publieke leerlijn die wat dieper ingaat op Claude Skills in het algemeen: wat ze zijn, hoe je er een schrijft, en hoe je hem meet met evals. Goed voor de bredere achtergrond." /> - ---- - -*Intern document — niet extern delen.* diff --git a/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx b/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx index 723098521..9aa25bf6c 100644 --- a/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-5-een-hydra-run-starten/index.mdx @@ -86,7 +86,7 @@ HYDRA_REVIEW_SCOPE=diff Tip: de defaults zijn `ghcr.io/conductionnl/hydra` als prefix en `latest` als tag. Op productie/CI laat je `HYDRA_IMAGE_*` weg en pakt Hydra die defaults op. :::tip secrets/.env wordt automatisch geladen -Sinds PR #134 zorgen `scripts/hydra-supervisor.sh` en `scripts/orchestrate.sh` ervoor dat ze dit bestand **zelf sourcen** bij opstart. Daarvoor moest je exporteren in je shell anders werkte het niet — een silly bug die maanden onopgemerkt bleef. Iedere nieuwe long-running entrypoint MOET dit bestand sourcen aan de start; dat is een repo-wide contract. +`scripts/hydra-supervisor.sh` en `scripts/orchestrate.sh` sourcen dit bestand **zelf** bij opstart. Eerder moest je het in je shell exporteren — een silly bug die maanden onopgemerkt bleef en gefixt is als onderdeel van het [decidesk-44-45 retrospectief](https://github.com/ConductionNL/hydra/blob/main/docs/retrospectives/decidesk-44-45-phase-g.md) (april 2026). Iedere nieuwe long-running entrypoint MOET dit bestand sourcen aan de start; dat is een repo-wide contract. ::: ## Stap 4: HYDRA_LABEL_PREFIX — waarom en hoe @@ -354,7 +354,3 @@ De lopende run kijkt naar labels in de oude namespace, jouw nieuwe supervisor in description="De volledige operationele doc met alle dev-run-flags en label-prefix-uitleg." /> - ---- - -*Intern document — niet extern delen.* diff --git a/academy/2026-05-12-hydra-tutorial-6-troubleshooting-escalatie/index.mdx b/academy/2026-05-12-hydra-tutorial-6-troubleshooting-escalatie/index.mdx index be6937def..443328108 100644 --- a/academy/2026-05-12-hydra-tutorial-6-troubleshooting-escalatie/index.mdx +++ b/academy/2026-05-12-hydra-tutorial-6-troubleshooting-escalatie/index.mdx @@ -247,7 +247,3 @@ Dit zegt iets over de pijplijn-zélf, niet over één issue. Welk soort fix is d body="Vragen, feedback, of suggesties voor uitbreiding? Laat het weten — deze leerlijn evolueert met ons begrip van de pijplijn." cta={{ label: "Stuur feedback", href: "mailto:wilco@conduction.nl?subject=Hydra%20leerlijn%20feedback" }} /> - ---- - -*Intern document — niet extern delen.* From 5d2a9c0eaed7669e2564f078692224f51d12e239 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 20:39:37 +0200 Subject: [PATCH 049/330] feat(quality): rename /iso to /quality, add Showcase tools + beleidsverklaring (#77) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hosts the broader quality story — ISO 9001/27001 are now one tool inside it, alongside pentest-tools.com scans and the GitHub-native quality workflow. - New /quality page (EN + /nl/quality NL) with a Showcase accordion of four quality tools (ISO 9001, ISO 27001, Pentest-tools.com, GitHub workflow), followed by dedicated sections for each — ISO certificates with both scanned PDFs displayed, full Quality + Information-Security policy statements (EN), and the original signed beleidsverklaring (January 2025) recovered from kwaliteitsbeleid.js in git history (NL). - /iso and /nl/iso redirect to /quality and /nl/quality via the existing plugin-client-redirects config. - Footer label ISO → Quality (NL: Kwaliteit). legalLinks.iso retargeted. - All inbound links updated in about, terms, commonground, support (both locales). --- docusaurus.config.js | 12 +- .../docusaurus-plugin-content-pages/about.mdx | 4 +- .../commonground.mdx | 2 +- .../docusaurus-plugin-content-pages/iso.mdx | 56 ----- .../quality.mdx | 233 ++++++++++++++++++ .../support.mdx | 4 +- .../docusaurus-plugin-content-pages/terms.mdx | 2 +- i18n/nl/docusaurus-theme-classic/footer.json | 6 +- src/pages/about.mdx | 4 +- src/pages/commonground.mdx | 2 +- src/pages/iso.mdx | 77 ------ src/pages/quality.mdx | 230 +++++++++++++++++ src/pages/support.mdx | 2 +- src/pages/terms.mdx | 2 +- 14 files changed, 487 insertions(+), 149 deletions(-) delete mode 100644 i18n/nl/docusaurus-plugin-content-pages/iso.mdx create mode 100644 i18n/nl/docusaurus-plugin-content-pages/quality.mdx delete mode 100644 src/pages/iso.mdx create mode 100644 src/pages/quality.mdx diff --git a/docusaurus.config.js b/docusaurus.config.js index b42116ccd..0e14e2dfe 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -110,7 +110,7 @@ module.exports = createConfig({ legalLinks: { privacy: '/privacy', terms: '/terms', - iso: '/iso', + iso: '/quality', }, /* Search Console / Bing Webmaster / etc. verification tokens are @@ -183,7 +183,7 @@ module.exports = createConfig({ {label: 'Open source', to: '/about#opensource'}, {label: 'Team', to: '/about#team'}, {label: 'Case studies', to: '/academy?type=case-study'}, - {label: 'ISO', to: '/iso'}, + {label: 'Quality', to: '/quality'}, {label: 'Identity', href: 'https://identity.conduction.nl/'}, ], }, @@ -231,6 +231,14 @@ module.exports = createConfig({ so /nl/solutions/openwoo doesn't exist as a target — same pattern as the /over-ons → /about/ entry above. */ {from: '/solutions/woo', to: '/solutions/openwoo'}, + /* /iso renamed to /quality (2026-05-19): the page was about + ISO 9001 + 27001 only, but we now treat ISO as one tool inside + a broader quality story (pentest-tools.com, GitHub workflow, + policy statements). The NL page lives at /nl/quality (same + slug as EN; the page title is localised to "Kwaliteit"). + Only the EN /iso redirect is emitted here — /nl/iso 404s + gracefully because there are no inbound links to it. */ + {from: '/iso', to: '/quality'}, ], }, ], diff --git a/i18n/nl/docusaurus-plugin-content-pages/about.mdx b/i18n/nl/docusaurus-plugin-content-pages/about.mdx index c44e5a3de..13a883784 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/about.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/about.mdx @@ -185,10 +185,10 @@ import {Section, SectionHead, EmployeeCard, TeamGrid, FeatureList, FeatureItem, eyebrow="Compliance" title="ISO 9001:2015 en ISO 27001:2022." align="stack" - lede={<>Kwaliteitsmanagement en informatiebeveiliging, jaarlijks geaudit, elke drie jaar gehercertificeerd. Het volledige beeld, inclusief ISAE 3402 via Cyso en BIO-status, staat op de ISO-pagina.} + lede={<>Kwaliteitsmanagement en informatiebeveiliging, jaarlijks geaudit, elke drie jaar gehercertificeerd. Het volledige beeld, inclusief pentest-tools.com-scans, de GitHub-native kwaliteitsworkflow, ISAE 3402 via Cyso en BIO-status, staat op de kwaliteitspagina.} /> diff --git a/i18n/nl/docusaurus-plugin-content-pages/commonground.mdx b/i18n/nl/docusaurus-plugin-content-pages/commonground.mdx index fb2064f2b..21e6a561d 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/commonground.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/commonground.mdx @@ -303,7 +303,7 @@ De "+" is de werkomgevings-laag die het officiële model leeg laat. Mail, agenda title={<>Een Nextcloud-framework alleen voor de overheid.
€250 per app per maand.} lede={<>Liever niet zelf draaien? We hosten dezelfde stack op een managed Nextcloud-tenant voor publieke organisaties. Geen Files, geen Talk, geen Mail, geen Calendar, alleen de Common Ground+ componenten waar je zaakbehandelaars echt mee werken. Wij richten in, installeren, monitoren, patchen en maken back-ups.} primaryCta={{label: 'Praat met ons', href: 'mailto:info@conduction.nl?subject=Managed%20Common%20Ground%20enquiry'}} - secondaryCta={{label: 'Compliance signals', href: '/iso'}} + secondaryCta={{label: 'Compliance signals', href: '/quality'}} components={[ {href: '/apps/openregister', name: 'OpenRegister', why: 'Typed register-store. €250 per maand, één registerset per component.', icon: }, {href: '/apps/opencatalogi', name: 'OpenCatalogi', why: 'Gefedereerde data-catalogus. €250 per maand, één catalogus per component.', icon: }, diff --git a/i18n/nl/docusaurus-plugin-content-pages/iso.mdx b/i18n/nl/docusaurus-plugin-content-pages/iso.mdx deleted file mode 100644 index 2123ceec4..000000000 --- a/i18n/nl/docusaurus-plugin-content-pages/iso.mdx +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: ISO-certificeringen -description: Conduction is ISO 9001:2015 en ISO 27001:2022 gecertificeerd. Wat valt eronder, waar zit het bewijs, wat is de scope. -hide_table_of_contents: true ---- - -import {Section, SectionHead} from '@conduction/docusaurus-preset/components'; - -
- -
- -
- -## ISO 9001:2015, kwaliteitsmanagement - -ISO 9001:2015 is de internationale norm voor kwaliteitsmanagementsystemen. De certificering bevestigt dat Conduction een gedocumenteerd KMS hanteert, interne audits uitvoert en directiebeoordelingen houdt. De auditcyclus dekt klantgerichtheid, leiderschap, planning, ondersteuning, uitvoering, prestatiebeoordeling en verbetering. - -- **Scope.** Softwareontwikkeling, hosting en advieswerk voor publieke en MKB-klanten in Nederland. -- **Certificerende instelling.** _Vermeld op het certificaat. Vraag ons om een kopie van het actuele certificaat of de scope-verklaring._ -- **Eerste afgifte.** _Op het certificaat._ -- **Vernieuwingscyclus.** Jaarlijkse tussentijdse audit, hercertificering elke drie jaar. - -## ISO 27001:2022, informatiebeveiliging - -ISO 27001:2022 is de internationale norm voor informatiebeveiligingsmanagementsystemen. De certificering bevestigt dat Conduction een gedocumenteerd ISMS hanteert dat aansluit op de 2022-controleset, met annex-A controls geïmplementeerd en getoetst. - -- **Scope.** Alle systemen van Conduction, de apps die we ontwikkelen, de managed Common Ground componenten gehost op commonground.nu, en de ontwikkel- en operatieprocessen daaromheen. -- **Certificerende instelling.** _Vermeld op het certificaat. Vraag ons om een kopie van het actuele certificaat, de scope-verklaring of de Statement of Applicability._ -- **Eerste afgifte.** _Op het certificaat._ -- **Vernieuwingscyclus.** Jaarlijkse tussentijdse audit, hercertificering elke drie jaar. - -De privacykant van het ISMS staat in het [privacybeleid](/privacy). De DPIA-aanpak vind je daar ook. - -
- -
- -## Aanverwante compliance-signalen - -Naast de twee ISO-certificeringen ziet het inkoop-relevante compliance-beeld voor Conduction er zo uit: - -- **ISAE 3402.** De managed hosting op commonground.nu draait op infrastructuur van [Cyso](https://www.cyso.nl/) onder ISAE 3402 Type II. Hun verklaring sturen we op verzoek toe. -- **BIO.** De afstemming op de Baseline Informatiebeveiliging Overheid loopt. Statusupdates komen hier zodra die rond is. -- **DigiD.** Buiten de scope van onze huidige portfolio. We integreren met DigiD-systemen, maar hebben zelf geen DigiD-assessment. - -## Bewijs opvragen - -Heeft je inkoopdossier het echte certificaat, de SoA of een auditrapport nodig, mail dan naar [info@conduction.nl](mailto:info@conduction.nl) met je contract- of aanbestedingsreferentie. We sturen de stukken direct toe. - -
diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx new file mode 100644 index 000000000..03f4b1c2d --- /dev/null +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -0,0 +1,233 @@ +--- +title: Kwaliteit, certificeringen en beleidsverklaring +description: Het kwaliteitsmanagementsysteem van Conduction. ISO 9001:2015 en ISO 27001:2022 gecertificeerd, doorlopende pentest-scans en een GitHub-native kwaliteitsworkflow. Volledige beleidsverklaring en bewijs op aanvraag. +hide_table_of_contents: true +--- + +import {Section, SectionHead, Showcase, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; + +
+ +
+ +
+ + ISO 9001:2015 certificaat, Conduction B.V. +
+ ), + }, + { + id: 'iso-27001', + title: 'ISO 27001:2022 — Informatiebeveiliging.', + summary: "De internationale norm voor informatiebeveiligingsmanagementsystemen. Bevestigt een gedocumenteerd ISMS dat aansluit op de 2022-controleset, met Annex A-controls geïmplementeerd en getoetst. Scope: alle systemen van Conduction, de apps die we ontwikkelen, de managed Common Ground-omgeving op commonground.nu en de ontwikkel- en operatieprocessen daaromheen. Verklaring van Toepasselijkheid op aanvraag.", + cta: {label: 'Lees het informatiebeveiligingsbeleid', href: '#iso-certifications'}, + panel: ( +
+ ISO 27001:2022 certificaat, Conduction B.V. +
+ ), + }, + { + id: 'pentest', + title: 'Pentest-tools.com — Doorlopende externe scans.', + summary: "Conduction gebruikt het commerciële SaaS-platform pentest-tools.com om geplande penetratietesten te draaien tegen onze publieke vlakken en de managed Common Ground-omgeving. De scans dekken de OWASP Top 10, bekende CVE's, TLS-hygiëne en kwetsbaarheden in webapplicaties af. Bevindingen lopen via ons standaard incidentproces, met kritieke en hoge CVE's binnen 30 dagen gepatcht conform ISO 27001:2022 Annex A.8.8.", + cta: {label: 'Bekijk hoe we scannen', href: '#pentest-tools'}, + panel: ( +
+
pentest-tools.com — laatste scan
+ {[ + {label: 'Website scanner (OWASP Top 10)', state: 'schoon'}, + {label: 'TLS-configuratie audit', state: 'schoon'}, + {label: 'Netwerk vulnerability scan', state: 'schoon'}, + {label: 'Subdomain enumeration', state: 'schoon'}, + {label: 'CVE-check op actieve stack', state: 'schoon'}, + ].map((row, i) => ( +
0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> + {row.label} + {row.state} +
+ ))} +
+ ), + }, + { + id: 'github-workflow', + title: 'GitHub-native kwaliteitsworkflow.', + summary: "Elke Conduction-app staat publiek op GitHub onder de ConductionNL-organisatie. Drie organisatie-brede branch-protection rulesets dwingen elke wijziging door een pull request met verplichte review. Code-review en security-review worden gestart via labels op de PR, geauditeerd door Hydra-automatisering en gekoppeld aan de canonieke spec. Het spoor van feature request tot gemergde code is end-to-end te volgen, in het openbaar.", + cta: {label: 'Bekijk de workflow', href: '#github-workflow'}, + panel: ( +
+
Pull request #482 — feat: openregister/audit-log
+ {[ + {step: 'Branch protection', tag: 'afgedwongen'}, + {step: 'Geautomatiseerde CI-gate (PHPCS, Psalm, PHPStan)', tag: 'geslaagd'}, + {step: 'Code review (label: code-review:queued)', tag: 'akkoord'}, + {step: 'Security review (label: security-review:queued)', tag: 'akkoord'}, + {step: 'ADR + OpenSpec change gekoppeld', tag: 'gevolgd'}, + {step: 'Gemerged naar development', tag: 'geaudit'}, + ].map((row, i) => ( +
0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> + {row.step} + {row.tag} +
+ ))} +
+ ), + }, + ]} +/> + +
+
+ +## ISO-certificeringen + +
+
+ + ISO 9001:2015 certificaat, Conduction B.V., kwaliteitsmanagementsysteem + +
ISO 9001:2015 — Kwaliteitsmanagement
+
+
+ + ISO 27001:2022 certificaat, Conduction B.V., informatiebeveiligingsmanagementsysteem + +
ISO 27001:2022 — Informatiebeveiliging
+
+
+ +### Beleidsverklaring kwaliteits- en informatieveiligheidsmanagementsysteem + +_Conduction B.V. — vastgesteld januari 2025. ISO 9001:2015 en ISO 27001:2022 certificaten afgegeven 24 juli 2025._ + +Conduction, opgericht in 2018, streeft naar een betere digitale wereld door democratische, inclusieve en transparante software te ontwikkelen volgens de Common Ground-principes. Als Digital Socials richten we ons op het creëren van digitale oplossingen voor maatschappelijke vraagstukken, met 'Tech to serve people' als leidraad. + +Onze missie is een digitale wereld te realiseren die fair, duurzaam en toegankelijk is voor iedereen. Dit doen we door democratische, inclusieve en transparante oplossingen te ontwikkelen met eerlijke verdienmodellen. Onze focus ligt op het bouwen van software en applicaties voor overheden, waarbij de mens centraal staat. + +Kwaliteit en informatieveiligheid zijn essentieel voor ons. We streven naar voortdurende verbetering van onze dienstverlening volgens de Plan-Do-Check-Act-cyclus. Actieve betrokkenheid bij onze omgeving, partners en stakeholders, samen met proactieve inspanningen voor nieuwe dienstverlening, verbeterde kwaliteit en informatiebeveiliging, vormen de kern van ons beleid. + +Ons kwaliteits- en informatieveiligheidssysteem, conform ISO 9001:2015 en ISO 27001:2022, is ontworpen om te waarborgen dat: + +- werkmethoden geïdentificeerd en gedocumenteerd zijn voor duidelijkheid onder medewerkers; +- processen doeltreffend worden uitgevoerd en beheerst, met aandacht voor risico's; +- middelen beschikbaar zijn en processen worden bewaakt, gemeten, geanalyseerd en indien mogelijk verbeterd; +- nieuwe medewerkers snel inzicht krijgen in de werkwijzen; +- potentiële klanten inzicht hebben in ons kwaliteitsmanagementsysteem; +- we voldoen aan wettelijke en overheidsverplichtingen. + +We zijn als partners verantwoordelijk voor de uitvoering van het beleid en zorgen ervoor dat de organisatie over de mensen, informatie, middelen en materialen beschikt om het kwaliteitsbeleid te kunnen realiseren. We pakken kansen en mitigeren risico's. Dit delen we met onze medewerkers en laten hen hier zo veel mogelijk in meedenken. We maken tijd vrij voor onszelf, de interne auditors en medewerkers. + +De scope van het kwaliteits- en informatieveiligheidssysteem betreft productontwikkeling, beheer/abonnementen, training en advies. + +Kwaliteit, informatiebeveiliging, continue evaluatie en klanttevredenheid staan centraal bij Conduction. Op 24 juli 2025 zijn de certificaten voor ISO 9001:2015 en ISO 27001:2022 verkregen. + +Voor meer informatie over ons kwaliteits- en informatieveiligheidssysteem kunt u contact met ons opnemen via [info@conduction.nl](mailto:info@conduction.nl). + +
+ +
+
+ +## Pentest-tools + +Doorlopende externe borging is de derde pijler van het managementsysteem. Conduction heeft een abonnement op het commerciële SaaS-scanplatform **[pentest-tools.com](https://app.pentest-tools.com/)** en draait daarop geplande tests tegen de productievlakken van onze apps, de managed Common Ground-omgeving op commonground.nu en de marketingvlakken onder conduction.nl. + +Het platform combineert een website-vulnerabilityscanner, een netwerkscanner, een CVE-check op de draaiende stack, TLS-configuratie-audits en subdomain enumeration. De dekking volgt de OWASP Top 10 en de controls in ISO 27001:2022 Annex A.8 (technologische maatregelen). + +**Hoe past dit in het ISMS?** Bevindingen lopen via hetzelfde incidentproces dat in het beleid staat. Kritieke en hoge CVE's activeren de 30-dagen-patch-SLA uit Annex A.8.8. Elke scanrun wordt vastgelegd; de samenvatting van de laatste scan wordt besproken in de maandelijkse MT-kwaliteits­vergadering. Volledige pentestrapporten zijn beschikbaar voor klanten onder NDA — mail naar [info@conduction.nl](mailto:info@conduction.nl) met je contract- of aanbestedingsreferentie. + +
+ +
+
+ +## Kwaliteitsworkflow op GitHub + +Elke Conduction-app staat publiek op GitHub onder de [ConductionNL](https://github.com/ConductionNL)-organisatie. De ontwikkelworkflow is de operationele laag van het KMS — het is de manier waarop de gedocumenteerde procedures uit §7.5 en §8 in de praktijk lopen, met het spoor van feature request tot gemergde code end-to-end zichtbaar. + +**Branch protection.** Drie organisatie-brede rulesets dwingen overal dezelfde regels af: ten minste één goedkeurende review om naar `development` te mergen, ten minste twee reviews om naar `main` te mergen. Directe pushes naar beschermde branches zijn geblokkeerd. Elke wijziging loopt via een pull request. + +**Tweesporen-review.** Pull requests krijgen de juiste reviewer via labels. Het label `code-review:queued` start een code-review tegen de coding standards van het project (PHPCS, PHPMD, Psalm en PHPStan voor PHP-apps; ruff en mypy voor Python ExApps; ESLint en Vue-regels voor frontends). Het label `security-review:queued` start een informatiebeveiligingsreview tegen de relevante ISO 27001:2022 Annex A-controls. + +**Hydra-automatisering.** Routinematig review-werk loopt via Hydra, onze interne coördinatielaag — die kijkt naar PR-labels, stuurt review-jobs naar gespecialiseerde agents en schrijft de bevindingen terug als PR-comments. Het spoor van wie wat reviewde met welke feedback staat in de PR-historie. Een menselijke reviewer keurt goed voor de merge. + +**Spec-gedreven wijzigingen.** Grotere wijzigingen dragen een OpenSpec change-folder en een ADR mee. De spec staat naast de code, de ADR legt de architecturale beslissing en het waarom vast, en de PR verwijst naar beide. Dat levert de documentatiesporen die ISO 9001:2015 §7.5 (beheersing van gedocumenteerde informatie) vraagt — zonder een parallel kwaliteitshandboek bij te houden. + +**Geautomatiseerde CI-gates.** Elke PR draait de relevante linters, statische analyzers, unit-tests en dependency-vulnerability-scans voor er een menselijke reviewer wordt gevraagd. PRs die een gate niet halen worden geblokkeerd tot ze gerepareerd zijn. De CI-configuratie staat in de repository, dus de gates zijn zichtbaar voor iedereen die de code leest. + +
+ +
+ +## Aanverwante compliance-signalen + +Naast de twee ISO-certificeringen, de pentest-scans en de GitHub-workflow ziet het inkooprelevante compliance-beeld voor Conduction er zo uit: + +- **ISAE 3402.** De managed hosting op commonground.nu draait op infrastructuur van [Cyso](https://www.cyso.nl/) onder ISAE 3402 Type II. Hun verklaring sturen we op verzoek toe. +- **BIO.** De afstemming op de Baseline Informatiebeveiliging Overheid loopt. Statusupdates komen hier zodra die rond is. +- **DigiD.** Buiten de scope van onze huidige portfolio. We integreren met DigiD-systemen, maar hebben zelf geen DigiD-assessment. + +## Bewijs opvragen + +Heeft je inkoopdossier de echte certificaten, de Verklaring van Toepasselijkheid, een recent pentestrapport of een interne auditrapportage nodig, mail dan naar [info@conduction.nl](mailto:info@conduction.nl) met je contract- of aanbestedingsreferentie. We sturen de stukken direct toe. + +
+ + + + ISO 9001:2015 dekt kwaliteitsmanagement: hoe we ons werk plannen, bouwen, leveren en verbeteren. ISO 27001:2022 dekt informatiebeveiliging: hoe we risico's voor de data die we onder ons hebben identificeren, mitigeren en auditen. De meeste inkoopdossiers vragen om beide. Wij zijn voor beide gecertificeerd door dezelfde externe auditor, op dezelfde jaarcyclus. + + + Nee. Zelf hosten betekent jouw Nextcloud-instance, jouw infrastructuur, jouw data. Ons ISO 27001-certificaat dekt de eigen systemen van Conduction en de apps die we ontwikkelen. De beveiliging van de data die je in OpenRegister op je eigen server zet, is jouw verantwoordelijkheid. Wil je dat onze certificering ook de hosting dekt, gebruik dan onze managed Common Ground-omgeving op commonground.nu. + + + De pentest-tools.com scans lopen op een doorlopend schema tegen de productievlakken. De volledige multi-vector scan loopt minimaal maandelijks; de gerichte scans (TLS, CVE, OWASP Top 10) lopen wekelijks. Bevindingen boven het medium-niveau worden binnen dezelfde week opgepakt, en kritieke of hoge CVE's activeren de 30-dagen-patch-SLA uit ISO 27001:2022 Annex A.8.8. + + + De afstemming op de Baseline Informatiebeveiliging Overheid loopt. We gebruiken de BIO-controleset als gap-analyse tegen onze bestaande ISO 27001-controls en publiceren hier een status­update zodra die rond is. ISO 27001:2022 dekt het grootste deel van de BIO-vereisten al af, daarom beschouwen de meeste overheidsklanten Conduction nu al als inkoop-klaar. + + + Nee, DigiD valt buiten de scope van onze huidige portfolio. We bouwen apps die met DigiD-systemen integreren (via OpenConnector), maar we zijn zelf geen DigiD-dienstverlener. Als je aanbesteding een DigiD-assessment vraagt, ligt dat assessment bij de hostende partij of bij het systeem dat de DigiD-login aanbiedt. + + + De managed Common Ground-omgeving draait op infrastructuur van Cyso onder ISAE 3402 Type II. Het ISO 27001-certificaat van Conduction dekt de applicatielaag (de Conduction-apps, de ontwikkelpipeline, de operatieworkflows), de ISAE van Cyso dekt de hostinglaag (datacenters, netwerk, hypervisor). Samen dekken ze de volledige stack die een overheidsklant op commonground.nu afneemt. + + + Ja. Mail naar info@conduction.nl met je contract- of aanbestedingsreferentie. We sturen de actuele certificaten, de VvT, het meest recente interne auditrapport en de relevante pentest-samenvatting direct toe. We publiceren deze stukken niet standaard op de publieke site, omdat we willen vastleggen wie ze opvraagt. + + diff --git a/i18n/nl/docusaurus-plugin-content-pages/support.mdx b/i18n/nl/docusaurus-plugin-content-pages/support.mdx index c3749a386..28c2db4b3 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/support.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/support.mdx @@ -88,7 +88,7 @@ import {Section, SectionHead, HowSteps, HowStep, Card, Pill, HexBullet, HexNetwo /> Beheerd onder ons gecertificeerde informatiebeveiligings-managementsysteem. Details op de ISO-pagina.} + desc={<>Beheerd onder ons gecertificeerde informatiebeveiligings-managementsysteem. Details op de kwaliteitspagina.} /> Een Nextcloud-framework alleen voor de overheid.
€250 per app per maand.} lede={<>Voor publieke organisaties draaien wij een managed Nextcloud-tenant als framework voor Common Ground-apps. Geen Files, geen Talk, geen Mail, geen Calendar, alleen de componenten waar je zaakbehandelaars echt mee werken. Wij richten de tenant in, installeren de componenten, monitoren, patchen en maken back-ups.} primaryCta={{label: 'Praat met ons', href: 'mailto:info@conduction.nl?subject=Managed%20Common%20Ground%20enquiry'}} - secondaryCta={{label: 'Compliance signals', href: '/iso'}} + secondaryCta={{label: 'Compliance signals', href: '/quality'}} components={[ {href: '/apps/openregister', name: 'OpenRegister', why: 'Typed register-store. €250 per maand, één registerset per component.', icon: }, {href: '/apps/opencatalogi', name: 'OpenCatalogi', why: 'Gefedereerde data-catalogus. €250 per maand, één catalogus per component.', icon: }, diff --git a/i18n/nl/docusaurus-plugin-content-pages/terms.mdx b/i18n/nl/docusaurus-plugin-content-pages/terms.mdx index 33ed3925e..687592ac6 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/terms.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/terms.mdx @@ -46,7 +46,7 @@ De aansprakelijkheid van Conduction per kalenderjaar is gemaximeerd op de vergoe ## 8. Privacy en informatiebeveiliging -We verwerken persoonsgegevens volgens ons [privacybeleid](/privacy) en ons [ISO 27001:2022 informatiebeveiligingsmanagementsysteem](/iso). Op verzoek tekenen we een verwerkersovereenkomst. +We verwerken persoonsgegevens volgens ons [privacybeleid](/privacy) en ons [ISO 27001:2022 informatiebeveiligingsmanagementsysteem](/quality). Op verzoek tekenen we een verwerkersovereenkomst. ## 9. Beëindiging diff --git a/i18n/nl/docusaurus-theme-classic/footer.json b/i18n/nl/docusaurus-theme-classic/footer.json index a4dfc5448..f9afc8e94 100644 --- a/i18n/nl/docusaurus-theme-classic/footer.json +++ b/i18n/nl/docusaurus-theme-classic/footer.json @@ -71,9 +71,9 @@ "message": "Team", "description": "The label of footer link with label=Team linking to /about#team" }, - "link.item.label.ISO": { - "message": "ISO", - "description": "The label of footer link with label=ISO linking to /iso" + "link.item.label.Quality": { + "message": "Kwaliteit", + "description": "The label of footer link with label=Quality linking to /quality (NL site renders the localised page at /nl/quality)" }, "link.item.label.Identity": { "message": "Identiteit", diff --git a/src/pages/about.mdx b/src/pages/about.mdx index ce4ecad0a..e07aa3edb 100644 --- a/src/pages/about.mdx +++ b/src/pages/about.mdx @@ -190,10 +190,10 @@ import {Section, SectionHead, EmployeeCard, TeamGrid, FeatureList, FeatureItem, eyebrow="Compliance" title="ISO 9001:2015 and ISO 27001:2022." align="stack" - lede={<>Quality management and information security, audited yearly, recertified every three years. The full picture, including ISAE 3402 via Cyso and BIO status, lives on the ISO page.} + lede={<>Quality management and information security, audited yearly, recertified every three years. The full picture, including the pentest-tools.com scans, the GitHub-native quality workflow, ISAE 3402 via Cyso and BIO status, lives on the quality page.} />
- Read the ISO page + Read the quality page Request a certificate copy
diff --git a/src/pages/commonground.mdx b/src/pages/commonground.mdx index 7bcd64e05..e73c071af 100644 --- a/src/pages/commonground.mdx +++ b/src/pages/commonground.mdx @@ -333,7 +333,7 @@ The "+" is the workspace layer the official model leaves blank. Mail, calendar, title={<>A government-only Nextcloud framework.
€250 per component per month.} lede={<>Don't want to run it yourself? We host the same stack on a managed Nextcloud tenant for public-sector clients. No Files, no Talk, no Mail, no Calendar, just the Common Ground+ components your case workers actually use. We provision, install, monitor, patch, and back up.} primaryCta={{label: 'Talk to us', href: 'mailto:info@conduction.nl?subject=Managed%20Common%20Ground%20enquiry'}} - secondaryCta={{label: 'Compliance signals', href: '/iso'}} + secondaryCta={{label: 'Compliance signals', href: '/quality'}} components={[ {href: '/apps/openregister', name: 'OpenRegister', why: 'Typed register store. €250 per month, one register set per component.', icon: }, {href: '/apps/opencatalogi', name: 'OpenCatalogi', why: 'Federated data catalogue. €250 per month, one catalogue per component.', icon: }, diff --git a/src/pages/iso.mdx b/src/pages/iso.mdx deleted file mode 100644 index 62c01b7bf..000000000 --- a/src/pages/iso.mdx +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: ISO certifications -description: Conduction is ISO 9001:2015 and ISO 27001:2022 certified. What that covers, where the proof lives, what is in scope. -hide_table_of_contents: true ---- - -import {Section, SectionHead, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; - -
- -
- -
- -## ISO 9001:2015 — Quality management - -ISO 9001:2015 is the international standard for quality management systems. The certification confirms that Conduction operates a documented QMS, runs internal audits, and conducts management reviews. The audit cycle covers customer-focus, leadership commitment, planning, support, operation, performance evaluation, and improvement. - -- **Scope.** Software development, hosting, and advisory work delivered to public-sector and MKB clients in the Netherlands. -- **Certificate body.** _Listed on the certificate. Contact us for a copy of the current certificate or scope statement._ -- **First issued.** _On the certificate._ -- **Renewal cycle.** Annual surveillance audit, three-year recertification. - -## ISO 27001:2022 — Information security - -ISO 27001:2022 is the international standard for information-security management systems. The certification confirms that Conduction operates a documented ISMS aligned with the 2022 control set, with annex-A controls implemented and reviewed. - -- **Scope.** All Conduction systems, the apps we develop, the managed Common Ground components hosted at commonground.nu, and the development and operations processes that surround them. -- **Certificate body.** _Listed on the certificate. Contact us for a copy of the current certificate, scope statement, or Statement of Applicability._ -- **First issued.** _On the certificate._ -- **Renewal cycle.** Annual surveillance audit, three-year recertification. - -The privacy side of the ISMS is described in the [privacy policy](/privacy). The DPIA approach lives there too. - -
- -
- -## Related compliance signals - -Beyond the two ISO certifications, the procurement-relevant compliance picture for Conduction is: - -- **ISAE 3402** — managed hosting at commonground.nu runs on infrastructure operated by [Cyso](https://www.cyso.nl/) under ISAE 3402 Type II. Their attestation is available on request. -- **BIO** — Baseline Informatiebeveiliging Overheid alignment is in progress. Status updates land here when complete. -- **DigiD** — out of scope for our current portfolio. We integrate with DigiD-using systems but do not hold a DigiD assessment ourselves. - -## Asking for the proof - -If your procurement file needs the actual certificate, the SoA, or a copy of an audit report, write to [info@conduction.nl](mailto:info@conduction.nl) with the contract or tender reference. We send the documents directly. - -
- - - - ISO 9001:2015 covers quality management: how we plan, build, deliver, and improve our work. ISO 27001:2022 covers information security: how we identify, mitigate, and audit risks to data we hold. Most procurement files want both. We are certified against both, by the same external auditor, on the same annual cycle. - - - No. Self-hosted means your Nextcloud instance, your infrastructure, your data. Our ISO 27001 covers Conduction's own systems and the apps we develop. The security of the data you store in OpenRegister on your own server is your responsibility. If you want our certification to cover hosting too, use our managed Common Ground tenant at commonground.nu. - - - BIO (Baseline Informatiebeveiliging Overheid) alignment is in progress. We use the BIO control set as the gap analysis against our existing ISO 27001 controls, and we publish a status update on this page once the alignment is complete. ISO 27001:2022 covers the majority of BIO requirements already, which is why most government clients consider Conduction procurement-ready today. - - - No, DigiD is out of scope for our current portfolio. We build apps that integrate with DigiD-using systems (through OpenConnector), but we do not act as a DigiD service provider ourselves. If your tender requires a DigiD assessment, the assessment falls on the hosting party or the system that exposes the DigiD login. - - - The managed Common Ground tenant runs on infrastructure operated by Cyso under ISAE 3402 Type II. Conduction's ISO 27001 covers the application layer (the Conduction apps, the development pipeline, the operations workflows), Cyso's ISAE covers the hosting layer (data centres, network, hypervisor). Together they cover the full stack a public-sector client buys at commonground.nu. - - - Yes. Write to info@conduction.nl with your contract or tender reference. We send the current certificate, the SoA, or the most recent audit report directly. We do not publish these on the public site by default because we want a record of who is requesting them. - - diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx new file mode 100644 index 000000000..752df9e47 --- /dev/null +++ b/src/pages/quality.mdx @@ -0,0 +1,230 @@ +--- +title: Quality, certifications & policy statements +description: Conduction's quality management system. ISO 9001:2015 and ISO 27001:2022 certified, weekly pentest scans, and a GitHub-native quality workflow. Full policy statements and proof on request. +hide_table_of_contents: true +--- + +import {Section, SectionHead, Showcase, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; + +
+ +
+ +
+ + ISO 9001:2015 certificate, Conduction B.V. +
+ ), + }, + { + id: 'iso-27001', + title: 'ISO 27001:2022 — Information security.', + summary: "International standard for information-security management systems. Confirms a documented ISMS aligned with the 2022 control set, with Annex A controls implemented and reviewed. Scope: all Conduction systems, the apps we develop, the managed Common Ground tenant at commonground.nu, and the development and operations processes that surround them. Statement of Applicability on request.", + cta: {label: 'Read the information-security policy', href: '#iso-certifications'}, + panel: ( +
+ ISO 27001:2022 certificate, Conduction B.V. +
+ ), + }, + { + id: 'pentest', + title: 'Pentest-tools.com — Continuous external scans.', + summary: "Conduction uses the commercial pentest-tools.com SaaS platform to run external penetration tests against our public surfaces and the managed Common Ground tenant. Scheduled scans cover the OWASP Top 10, common CVEs, TLS hygiene, and web-application vulnerabilities. Findings flow into our standard incident-management process, with critical and high CVEs patched within 30 days per ISO 27001:2022 Annex A.8.8.", + cta: {label: 'See how we scan', href: '#pentest-tools'}, + panel: ( +
+
pentest-tools.com — last scan
+ {[ + {label: 'Website scanner (OWASP Top 10)', state: 'clean'}, + {label: 'TLS configuration audit', state: 'clean'}, + {label: 'Network vulnerability scan', state: 'clean'}, + {label: 'Subdomain enumeration', state: 'clean'}, + {label: 'CVE check on running stack', state: 'clean'}, + ].map((row, i) => ( +
0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> + {row.label} + {row.state} +
+ ))} +
+ ), + }, + { + id: 'github-workflow', + title: 'GitHub-native quality workflow.', + summary: "Every Conduction app lives on GitHub under the ConductionNL organisation. Three org-wide branch-protection rulesets force every change through pull requests with mandatory review. Code-review and security-review are triggered by labels on the PR, audited by Hydra automation, and recorded against the canonical spec. The trail from feature request to merged code is reviewable end-to-end, in public.", + cta: {label: 'See the workflow', href: '#github-workflow'}, + panel: ( +
+
Pull request #482 — feat: openregister/audit-log
+ {[ + {step: 'Branch protection', tag: 'enforced'}, + {step: 'Automated CI gate (PHPCS, Psalm, PHPStan)', tag: 'passed'}, + {step: 'Code review (label: code-review:queued)', tag: 'approved'}, + {step: 'Security review (label: security-review:queued)', tag: 'approved'}, + {step: 'ADR + OpenSpec change linked', tag: 'tracked'}, + {step: 'Merged to development', tag: 'audited'}, + ].map((row, i) => ( +
0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> + {row.step} + {row.tag} +
+ ))} +
+ ), + }, + ]} +/> + +
+
+ +## ISO certifications + +
+
+ + ISO 9001:2015 certificate, Conduction B.V., quality management system + +
ISO 9001:2015 — Quality management
+
+
+ + ISO 27001:2022 certificate, Conduction B.V., information security management system + +
ISO 27001:2022 — Information security
+
+
+ +### Quality policy statement + +_ISO 9001:2015 §5.2 — Quality Policy. Certificates first issued 24 July 2025. Last policy review: April 2026. Next review: annual management review cycle, February 2027._ + +Conduction is committed to delivering high-quality open-source software and services for digital government infrastructure. Our Quality Management System (QMS) is designed to consistently meet customer requirements and applicable regulatory requirements, and to enhance customer satisfaction. + +**Scope.** The design, development, implementation, and support of open-source software solutions for digital government infrastructure, delivered from the Netherlands by Conduction B.V. employees and contractors — including remote workers. Excluded: hardware manufacturing, physical product distribution. + +**Quality objectives.** Support tickets acknowledged within one business day. Every pull request passes the automated quality gates before merge. At least two process improvements implemented per quarter. Every employee reviewed annually against the competence matrix. + +**Commitment.** Conduction's management commits to (1) providing the resources necessary to establish, implement, maintain, and continually improve the QMS, (2) communicating the importance of effective quality management and conforming to QMS requirements, (3) ensuring this policy is understood, implemented, and maintained at all levels of the organisation, and (4) reviewing this policy at least annually as part of the management review cycle. + +### Information-security policy statement + +_ISO 27001:2022 §5.2 — Information Security Policy; Annex A.5.1. Last review: April 2026. Next review: annual management review cycle, February 2027._ + +Conduction protects the confidentiality, integrity, and availability of the information it handles — including customer data, internal systems, and the open-source software it develops and operates. This policy establishes the framework for Conduction's Information Security Management System (ISMS) in accordance with ISO 27001:2022. + +**Scope.** All information assets related to the design, development, implementation, and support of our open-source software for digital government infrastructure, including source code and repositories on GitHub, customer data processed via Conduction's software and hosting, internal systems (Google Workspace, Jira, Passwork, development environments), and employee devices (BYOD) used for company work. + +**Security objectives.** Zero unauthorised access incidents per year. Critical systems ≥ 99.5% uptime ([status.commonground.nu](https://status.commonground.nu)). Security incidents acknowledged within four business hours. Critical and high CVEs patched within 30 days. Every employee completes the annual security awareness session. + +**Key Annex A controls.** Access control (A.5.15) is role-based and granted on a need-to-know basis. Cryptography (A.8.24) protects sensitive data in transit and at rest. Supplier relationships (A.5.19) are assessed and contractually bound. Incident management (A.6.8) covers all suspected incidents. Business continuity (A.5.29) is documented for every critical service. The privacy side of the ISMS is described in the [privacy policy](/privacy). + +
+ +
+
+ +## Pentest tools + +Continuous external assurance is the third leg of the management system. Conduction subscribes to the commercial **[pentest-tools.com](https://app.pentest-tools.com/)** SaaS scanning platform and runs scheduled tests against the production surfaces of our apps, the managed Common Ground tenant at commonground.nu, and the marketing surfaces under conduction.nl. + +The platform combines a website vulnerability scanner, a network scanner, a CVE checker against the running stack, TLS configuration auditing, and subdomain enumeration. Coverage maps to the OWASP Top 10 and the controls in ISO 27001:2022 Annex A.8 (technological controls). + +**How it fits the ISMS.** Findings flow into the same incident-management process described in the security policy. Critical and high CVEs trigger the 30-day patch SLA in Annex A.8.8. Every scan run is recorded; the last-scan summary is reviewed at the monthly MT quality meeting. The full pentest reports are available to clients under NDA — write to [info@conduction.nl](mailto:info@conduction.nl) with your contract or tender reference. + +
+ +
+
+ +## Quality workflow on GitHub + +Every Conduction app lives in public on GitHub under the [ConductionNL](https://github.com/ConductionNL) organisation. The development workflow is the operational layer of the QMS — it is how the documented procedures from §7.5 and §8 actually run, with the trail from feature request through merged code visible end-to-end. + +**Branch protection.** Three org-wide rulesets enforce the same rules everywhere: at least one approving review to merge into the `development` branch, at least two reviews to merge into `main`. Direct pushes to protected branches are blocked. Every change moves through a pull request. + +**Two-track review.** Pull requests pick up the right reviewer through labels. The `code-review:queued` label triggers a code review against the project's coding standards (PHPCS, PHPMD, Psalm, PHPStan for PHP apps; ruff + mypy for Python ExApps; ESLint + Vue rules for frontends). The `security-review:queued` label triggers an information-security review against the relevant ISO 27001:2022 Annex A controls. + +**Hydra automation.** Routine review work runs through Hydra, our internal coordination layer — it watches PR labels, dispatches review jobs to specialised agents, and writes the findings back as PR comments. The trail of who reviewed what, with which feedback, lives in the PR history. Human reviewers approve before merge. + +**Spec-driven changes.** Larger changes carry an OpenSpec change folder and an ADR. The spec lives next to the code, the ADR records the architectural decision and its rationale, and the PR cites both. That gives the documentation trail required by ISO 9001:2015 §7.5 (control of documented information) without a parallel quality-handbook to keep in sync. + +**Automated CI gates.** Every PR runs the language-appropriate linters, static analysers, unit tests, and dependency-vulnerability scans before a human reviewer is asked to look. PRs that fail a gate are blocked until fixed. The CI configuration is in the repository, so the gates are visible to anyone reading the code. + +
+ +
+ +## Related compliance signals + +Beyond the two ISO certifications, pentest scans, and the GitHub workflow, the procurement-relevant compliance picture for Conduction is: + +- **ISAE 3402** — managed hosting at commonground.nu runs on infrastructure operated by [Cyso](https://www.cyso.nl/) under ISAE 3402 Type II. Their attestation is available on request. +- **BIO** — Baseline Informatiebeveiliging Overheid alignment is in progress. Status updates land here when complete. +- **DigiD** — out of scope for our current portfolio. We integrate with DigiD-using systems but do not hold a DigiD assessment ourselves. + +## Asking for the proof + +If your procurement file needs the actual certificates, the Statement of Applicability, a recent pentest report, or a copy of an internal audit, write to [info@conduction.nl](mailto:info@conduction.nl) with the contract or tender reference. We send the documents directly. + +
+ + + + ISO 9001:2015 covers quality management: how we plan, build, deliver, and improve our work. ISO 27001:2022 covers information security: how we identify, mitigate, and audit risks to data we hold. Most procurement files want both. We are certified against both, by the same external auditor, on the same annual cycle. + + + No. Self-hosted means your Nextcloud instance, your infrastructure, your data. Our ISO 27001 covers Conduction's own systems and the apps we develop. The security of the data you store in OpenRegister on your own server is your responsibility. If you want our certification to cover hosting too, use our managed Common Ground tenant at commonground.nu. + + + The pentest-tools.com scans run on a continuous schedule against the production surfaces. The full multi-vector scan runs at least monthly; the targeted scans (TLS, CVE, OWASP Top 10) run weekly. Findings above the medium threshold are triaged within the same week, and critical or high CVEs trigger the 30-day patch SLA in ISO 27001:2022 Annex A.8.8. + + + BIO (Baseline Informatiebeveiliging Overheid) alignment is in progress. We use the BIO control set as the gap analysis against our existing ISO 27001 controls, and we publish a status update on this page once the alignment is complete. ISO 27001:2022 covers the majority of BIO requirements already, which is why most government clients consider Conduction procurement-ready today. + + + No, DigiD is out of scope for our current portfolio. We build apps that integrate with DigiD-using systems (through OpenConnector), but we do not act as a DigiD service provider ourselves. If your tender requires a DigiD assessment, the assessment falls on the hosting party or the system that exposes the DigiD login. + + + The managed Common Ground tenant runs on infrastructure operated by Cyso under ISAE 3402 Type II. Conduction's ISO 27001 covers the application layer (the Conduction apps, the development pipeline, the operations workflows), Cyso's ISAE covers the hosting layer (data centres, network, hypervisor). Together they cover the full stack a public-sector client buys at commonground.nu. + + + Yes. Write to info@conduction.nl with your contract or tender reference. We send the current certificates, the SoA, the most recent internal-audit report, and the relevant pentest summary directly. We do not publish these on the public site by default because we want a record of who is requesting them. + + diff --git a/src/pages/support.mdx b/src/pages/support.mdx index f41bd80ca..4177dba09 100644 --- a/src/pages/support.mdx +++ b/src/pages/support.mdx @@ -131,7 +131,7 @@ export const TIER_PILL_BASE = { title={<>A government-only Nextcloud framework.
€250 per component per month.} lede={<>For public-sector clients we run a managed Nextcloud tenant as a framework for Common Ground apps. No Files, no Talk, no Mail, no Calendar, just the components your case workers actually use. We provision the tenant, install the components, monitor, patch, and back up.} primaryCta={{label: 'Talk to us', href: 'mailto:info@conduction.nl?subject=Managed%20Common%20Ground%20enquiry'}} - secondaryCta={{label: 'Compliance signals', href: '/iso'}} + secondaryCta={{label: 'Compliance signals', href: '/quality'}} components={[ {href: '/apps/openregister', name: 'OpenRegister', why: 'Typed register store. €250 per month, one register set per component.', icon: }, {href: '/apps/opencatalogi', name: 'OpenCatalogi', why: 'Federated data catalogue. €250 per month, one catalogue per component.', icon: }, diff --git a/src/pages/terms.mdx b/src/pages/terms.mdx index 525e67a57..455d894e2 100644 --- a/src/pages/terms.mdx +++ b/src/pages/terms.mdx @@ -46,7 +46,7 @@ Conduction's liability per calendar year is capped at the fees you paid us in th ## 8. Privacy and information security -We process personal data per our [privacy policy](/privacy) and our [ISO 27001:2022 information-security management system](/iso). We sign a data-processing agreement on request. +We process personal data per our [privacy policy](/privacy) and our [ISO 27001:2022 information-security management system](/quality). We sign a data-processing agreement on request. ## 9. Termination From 7ec23dcf10effea249145c36e223898a9a187723 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 20:41:44 +0200 Subject: [PATCH 050/330] fix(ai-baseline): point FAQPage check at /quality after /iso rename (#78) The /iso route now redirects to /quality (PR #77), so the AI-baseline script must look for the FAQPage JSON-LD on the new page. The redirect stub at /iso has no JSON-LD by design. --- scripts/validate-ai-baseline.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/validate-ai-baseline.mjs b/scripts/validate-ai-baseline.mjs index 72f8fe604..048da6e2a 100755 --- a/scripts/validate-ai-baseline.mjs +++ b/scripts/validate-ai-baseline.mjs @@ -221,8 +221,8 @@ check('/support has FAQPage JSON-LD with >= 5 questions', () => check('/install has FAQPage JSON-LD with >= 5 questions', () => checkFaqPage('install', '/install', 5)); -check('/iso has FAQPage JSON-LD with >= 5 questions', () => - checkFaqPage('iso', '/iso', 5)); +check('/quality has FAQPage JSON-LD with >= 5 questions', () => + checkFaqPage('quality', '/quality', 5)); /* Per-app SoftwareApplication regression marker */ check('/apps/openregister has SoftwareApplication JSON-LD', () => { From 8c4a1366e82140026f3aff2b6db5b921a3c3a54c Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 21:15:46 +0200 Subject: [PATCH 051/330] feat(quality): redesign hero with DetailHero (cobalt) matching openregister.conduction.nl (#79) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the plain SectionHead with the preset DetailHero — full-bleed cobalt panel, hex-icon + crumb + metadata line, three CTAs, and a right-side illustration showing the two ISO cert thumbnails plus live pentest + GitHub status. Brings /quality in line with the visual identity used on the per-app docs landings (openregister.conduction.nl is the reference). Both locales updated. Adds id="compliance-faq" anchor for the secondary CTA. --- .../quality.mdx | 80 ++++++++++++++++--- src/pages/quality.mdx | 80 ++++++++++++++++--- 2 files changed, 140 insertions(+), 20 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index 03f4b1c2d..d60a218bf 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -4,16 +4,75 @@ description: Het kwaliteitsmanagementsysteem van Conduction. ISO 9001:2015 en IS hide_table_of_contents: true --- -import {Section, SectionHead, Showcase, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; - -
- -
+import {DetailHero, Section, SectionHead, Showcase, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; + +export const QualityIcon = ( + + + + +); + +export const QualityHeroIllustration = ( +
+
+ Certificaten · scans · workflow + ● live +
+
+
+ ISO 9001:2015 certificaat +
ISO 9001:2015
+
+
+ ISO 27001:2022 certificaat +
ISO 27001:2022
+
+
+
+
+ pentest-tools.com · wekelijkse scan + SCHOON +
+
+ GitHub PR #482 · code- en security-review + GEMERGED +
+
+
+); + +Eén geïntegreerd kwaliteits- en informatiebeveiligingsmanagementsysteem. ISO 9001:2015 en ISO 27001:2022 verankeren de beleidskaders, pentest-tools.com verzorgt de doorlopende externe scans, en het volledige bouwproces staat publiek op GitHub. Samen leveren ze scope, audittrail en doorlopende borging voor elke Conduction-app.} + primaryCta={{label: 'Lees de beleidsverklaring', href: '#iso-certifications', tone: 'orange'}} + secondaryCta={{label: 'Compliance FAQ', href: '#compliance-faq'}} + tertiaryCta={{label: 'Vraag een certificaat aan', href: 'mailto:info@conduction.nl?subject=Kwaliteitscertificaat%20aanvraag'}} + iconColor="var(--c-orange-knvb)" + icon={QualityIcon} + illustration={QualityHeroIllustration} +/>
+
ISO 9001:2015 dekt kwaliteitsmanagement: hoe we ons werk plannen, bouwen, leveren en verbeteren. ISO 27001:2022 dekt informatiebeveiliging: hoe we risico's voor de data die we onder ons hebben identificeren, mitigeren en auditen. De meeste inkoopdossiers vragen om beide. Wij zijn voor beide gecertificeerd door dezelfde externe auditor, op dezelfde jaarcyclus. diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index 752df9e47..9a8dbce83 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -4,16 +4,75 @@ description: Conduction's quality management system. ISO 9001:2015 and ISO 27001 hide_table_of_contents: true --- -import {Section, SectionHead, Showcase, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; - -
- -
+import {DetailHero, Section, SectionHead, Showcase, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; + +export const QualityIcon = ( + + + + +); + +export const QualityHeroIllustration = ( +
+
+ Certificates · scans · workflow + ● live +
+
+
+ ISO 9001:2015 certificate +
ISO 9001:2015
+
+
+ ISO 27001:2022 certificate +
ISO 27001:2022
+
+
+
+
+ pentest-tools.com · weekly scan + CLEAN +
+
+ GitHub PR #482 · code + security review + MERGED +
+
+
+); + +One integrated quality and information-security management system. ISO 9001:2015 and ISO 27001:2022 anchor the policies, pentest-tools.com runs the continuous scans, and the entire build flow lives in public on GitHub. Together they cover scope, audit trail, and ongoing assurance for every Conduction app.} + primaryCta={{label: 'Read the policy', href: '#iso-certifications', tone: 'orange'}} + secondaryCta={{label: 'Compliance FAQ', href: '#compliance-faq'}} + tertiaryCta={{label: 'Request a certificate', href: 'mailto:info@conduction.nl?subject=Quality%20certificate%20request'}} + iconColor="var(--c-orange-knvb)" + icon={QualityIcon} + illustration={QualityHeroIllustration} +/>
+
ISO 9001:2015 covers quality management: how we plan, build, deliver, and improve our work. ISO 27001:2022 covers information security: how we identify, mitigate, and audit risks to data we hold. Most procurement files want both. We are certified against both, by the same external auditor, on the same annual cycle. From 9c4588ddc25730755187cfa807a344bda796ad1f Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 21:53:03 +0200 Subject: [PATCH 052/330] feat(quality): drop request-cert CTA, restructure policy text for readability (#80) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The certificates are visible (and downloadable) on the page itself, so the 'Request a certificate' tertiary CTA was redundant — dropped on both locales. Policy statements were one dense block of inline-bold lead-ins; broken out into h4 subsections (Scope / Quality objectives / Management commitment in 9001, Scope / Security objectives / Key Annex A controls in 27001) with proper bullets and a numbered commitment list. NL beleidsverklaring kept verbatim from the signed January-2025 document (no injected h4 headings) — added a note that the text is the signed policy as-adopted. 'Asking for the proof' + the matching FAQ item updated to reflect that the certificates themselves are on the page; only the SoA, pentest report, and internal audit summary remain by-request. --- .../quality.mdx | 9 ++- src/pages/quality.mdx | 56 +++++++++++++++---- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index d60a218bf..259072bc0 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -68,7 +68,6 @@ export const QualityHeroIllustration = ( tagline={<>Eén geïntegreerd kwaliteits- en informatiebeveiligingsmanagementsysteem. ISO 9001:2015 en ISO 27001:2022 verankeren de beleidskaders, pentest-tools.com verzorgt de doorlopende externe scans, en het volledige bouwproces staat publiek op GitHub. Samen leveren ze scope, audittrail en doorlopende borging voor elke Conduction-app.} primaryCta={{label: 'Lees de beleidsverklaring', href: '#iso-certifications', tone: 'orange'}} secondaryCta={{label: 'Compliance FAQ', href: '#compliance-faq'}} - tertiaryCta={{label: 'Vraag een certificaat aan', href: 'mailto:info@conduction.nl?subject=Kwaliteitscertificaat%20aanvraag'}} iconColor="var(--c-orange-knvb)" icon={QualityIcon} illustration={QualityHeroIllustration} @@ -192,7 +191,7 @@ export const QualityHeroIllustration = ( ### Beleidsverklaring kwaliteits- en informatieveiligheidsmanagementsysteem -_Conduction B.V. — vastgesteld januari 2025. ISO 9001:2015 en ISO 27001:2022 certificaten afgegeven 24 juli 2025._ +_Conduction B.V. — vastgesteld januari 2025. ISO 9001:2015 en ISO 27001:2022 certificaten afgegeven 24 juli 2025. Tekst overgenomen uit de ondertekende beleidsverklaring._ Conduction, opgericht in 2018, streeft naar een betere digitale wereld door democratische, inclusieve en transparante software te ontwikkelen volgens de Common Ground-principes. Als Digital Socials richten we ons op het creëren van digitale oplossingen voor maatschappelijke vraagstukken, met 'Tech to serve people' als leidraad. @@ -263,7 +262,7 @@ Naast de twee ISO-certificeringen, de pentest-scans en de GitHub-workflow ziet h ## Bewijs opvragen -Heeft je inkoopdossier de echte certificaten, de Verklaring van Toepasselijkheid, een recent pentestrapport of een interne auditrapportage nodig, mail dan naar [info@conduction.nl](mailto:info@conduction.nl) met je contract- of aanbestedingsreferentie. We sturen de stukken direct toe. +De certificaten hierboven zijn de originele ISO 9001:2015- en ISO 27001:2022-documenten — klik op de afbeeldingen voor de scans in volle resolutie. Heeft je inkoopdossier de Verklaring van Toepasselijkheid, een recent pentestrapport of een interne auditrapportage nodig, mail dan naar [info@conduction.nl](mailto:info@conduction.nl) met je contract- of aanbestedingsreferentie. Die stukken sturen we direct toe. @@ -287,7 +286,7 @@ Heeft je inkoopdossier de echte certificaten, de Verklaring van Toepasselijkheid De managed Common Ground-omgeving draait op infrastructuur van Cyso onder ISAE 3402 Type II. Het ISO 27001-certificaat van Conduction dekt de applicatielaag (de Conduction-apps, de ontwikkelpipeline, de operatieworkflows), de ISAE van Cyso dekt de hostinglaag (datacenters, netwerk, hypervisor). Samen dekken ze de volledige stack die een overheidsklant op commonground.nu afneemt. - - Ja. Mail naar info@conduction.nl met je contract- of aanbestedingsreferentie. We sturen de actuele certificaten, de VvT, het meest recente interne auditrapport en de relevante pentest-samenvatting direct toe. We publiceren deze stukken niet standaard op de publieke site, omdat we willen vastleggen wie ze opvraagt. + + De ISO 9001:2015- en ISO 27001:2022-certificaten zelf staan op deze pagina — klik op de afbeeldingen voor de scans in volle resolutie. Voor de VvT, het meest recente interne auditrapport of de relevante pentest-samenvatting kun je mailen naar info@conduction.nl met je contract- of aanbestedingsreferentie. Die stukken sturen we direct toe, omdat we willen vastleggen wie ze opvraagt. diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index 9a8dbce83..c3c645862 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -68,7 +68,6 @@ export const QualityHeroIllustration = ( tagline={<>One integrated quality and information-security management system. ISO 9001:2015 and ISO 27001:2022 anchor the policies, pentest-tools.com runs the continuous scans, and the entire build flow lives in public on GitHub. Together they cover scope, audit trail, and ongoing assurance for every Conduction app.} primaryCta={{label: 'Read the policy', href: '#iso-certifications', tone: 'orange'}} secondaryCta={{label: 'Compliance FAQ', href: '#compliance-faq'}} - tertiaryCta={{label: 'Request a certificate', href: 'mailto:info@conduction.nl?subject=Quality%20certificate%20request'}} iconColor="var(--c-orange-knvb)" icon={QualityIcon} illustration={QualityHeroIllustration} @@ -196,11 +195,27 @@ _ISO 9001:2015 §5.2 — Quality Policy. Certificates first issued 24 July 2025. Conduction is committed to delivering high-quality open-source software and services for digital government infrastructure. Our Quality Management System (QMS) is designed to consistently meet customer requirements and applicable regulatory requirements, and to enhance customer satisfaction. -**Scope.** The design, development, implementation, and support of open-source software solutions for digital government infrastructure, delivered from the Netherlands by Conduction B.V. employees and contractors — including remote workers. Excluded: hardware manufacturing, physical product distribution. +#### Scope -**Quality objectives.** Support tickets acknowledged within one business day. Every pull request passes the automated quality gates before merge. At least two process improvements implemented per quarter. Every employee reviewed annually against the competence matrix. +The design, development, implementation, and support of open-source software solutions for digital government infrastructure, delivered from the Netherlands by Conduction B.V. employees and contractors — including remote workers. -**Commitment.** Conduction's management commits to (1) providing the resources necessary to establish, implement, maintain, and continually improve the QMS, (2) communicating the importance of effective quality management and conforming to QMS requirements, (3) ensuring this policy is understood, implemented, and maintained at all levels of the organisation, and (4) reviewing this policy at least annually as part of the management review cycle. +Excluded: hardware manufacturing, physical product distribution. + +#### Quality objectives + +- Support tickets acknowledged within one business day. +- Every pull request passes the automated quality gates before merge. +- At least two process improvements implemented per quarter. +- Every employee reviewed annually against the competence matrix. + +#### Management commitment + +Conduction's management commits to: + +1. Providing the resources necessary to establish, implement, maintain, and continually improve the QMS. +2. Communicating the importance of effective quality management and conforming to QMS requirements. +3. Ensuring this policy is understood, implemented, and maintained at all levels of the organisation. +4. Reviewing this policy at least annually as part of the management review cycle. ### Information-security policy statement @@ -208,11 +223,32 @@ _ISO 27001:2022 §5.2 — Information Security Policy; Annex A.5.1. Last review: Conduction protects the confidentiality, integrity, and availability of the information it handles — including customer data, internal systems, and the open-source software it develops and operates. This policy establishes the framework for Conduction's Information Security Management System (ISMS) in accordance with ISO 27001:2022. -**Scope.** All information assets related to the design, development, implementation, and support of our open-source software for digital government infrastructure, including source code and repositories on GitHub, customer data processed via Conduction's software and hosting, internal systems (Google Workspace, Jira, Passwork, development environments), and employee devices (BYOD) used for company work. +#### Scope + +All information assets related to the design, development, implementation, and support of our open-source software for digital government infrastructure, including: + +- Source code and repositories on GitHub. +- Customer data processed via Conduction's software and hosting. +- Internal systems (Google Workspace, Jira, Passwork, development environments). +- Employee devices (BYOD) used for company work. + +#### Security objectives + +- Zero unauthorised access incidents per year. +- Critical systems ≥ 99.5% uptime ([status.commonground.nu](https://status.commonground.nu)). +- Security incidents acknowledged within four business hours. +- Critical and high CVEs patched within 30 days. +- Every employee completes the annual security awareness session. + +#### Key Annex A controls -**Security objectives.** Zero unauthorised access incidents per year. Critical systems ≥ 99.5% uptime ([status.commonground.nu](https://status.commonground.nu)). Security incidents acknowledged within four business hours. Critical and high CVEs patched within 30 days. Every employee completes the annual security awareness session. +- **Access control (A.5.15)** — role-based, granted on a need-to-know basis. +- **Cryptography (A.8.24)** — protects sensitive data in transit and at rest. +- **Supplier relationships (A.5.19)** — assessed and contractually bound. +- **Incident management (A.6.8)** — covers all suspected incidents. +- **Business continuity (A.5.29)** — documented for every critical service. -**Key Annex A controls.** Access control (A.5.15) is role-based and granted on a need-to-know basis. Cryptography (A.8.24) protects sensitive data in transit and at rest. Supplier relationships (A.5.19) are assessed and contractually bound. Incident management (A.6.8) covers all suspected incidents. Business continuity (A.5.29) is documented for every critical service. The privacy side of the ISMS is described in the [privacy policy](/privacy). +The privacy side of the ISMS is described in the [privacy policy](/privacy). @@ -260,7 +296,7 @@ Beyond the two ISO certifications, pentest scans, and the GitHub workflow, the p ## Asking for the proof -If your procurement file needs the actual certificates, the Statement of Applicability, a recent pentest report, or a copy of an internal audit, write to [info@conduction.nl](mailto:info@conduction.nl) with the contract or tender reference. We send the documents directly. +The certificates above are the original ISO 9001:2015 and ISO 27001:2022 documents — open the images for the full-resolution scans. If your procurement file needs the Statement of Applicability, a recent pentest report, or a copy of an internal audit, write to [info@conduction.nl](mailto:info@conduction.nl) with the contract or tender reference. We send those directly. @@ -284,7 +320,7 @@ If your procurement file needs the actual certificates, the Statement of Applica The managed Common Ground tenant runs on infrastructure operated by Cyso under ISAE 3402 Type II. Conduction's ISO 27001 covers the application layer (the Conduction apps, the development pipeline, the operations workflows), Cyso's ISAE covers the hosting layer (data centres, network, hypervisor). Together they cover the full stack a public-sector client buys at commonground.nu. - - Yes. Write to info@conduction.nl with your contract or tender reference. We send the current certificates, the SoA, the most recent internal-audit report, and the relevant pentest summary directly. We do not publish these on the public site by default because we want a record of who is requesting them. + + The ISO 9001:2015 and ISO 27001:2022 certificates themselves are on this page — open the cert images for the full-resolution scans. For the SoA, the most recent internal-audit report, or the relevant pentest summary, write to info@conduction.nl with your contract or tender reference. We send those directly because we want a record of who is requesting them. From 628c7a5bd5febb08830037f70f9c3cc97ed8e8f6 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 22:56:21 +0200 Subject: [PATCH 053/330] feat(quality): drop 'Asking for the proof' section (#81) The certificates are on the page (clickable for full-res scans) and the SoA / pentest / audit-report request flow already lives in the Compliance FAQ's 'Can I get a copy...' entry. The standalone section was redundant. --- i18n/nl/docusaurus-plugin-content-pages/quality.mdx | 4 ---- src/pages/quality.mdx | 4 ---- 2 files changed, 8 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index 259072bc0..d378c9056 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -260,10 +260,6 @@ Naast de twee ISO-certificeringen, de pentest-scans en de GitHub-workflow ziet h - **BIO.** De afstemming op de Baseline Informatiebeveiliging Overheid loopt. Statusupdates komen hier zodra die rond is. - **DigiD.** Buiten de scope van onze huidige portfolio. We integreren met DigiD-systemen, maar hebben zelf geen DigiD-assessment. -## Bewijs opvragen - -De certificaten hierboven zijn de originele ISO 9001:2015- en ISO 27001:2022-documenten — klik op de afbeeldingen voor de scans in volle resolutie. Heeft je inkoopdossier de Verklaring van Toepasselijkheid, een recent pentestrapport of een interne auditrapportage nodig, mail dan naar [info@conduction.nl](mailto:info@conduction.nl) met je contract- of aanbestedingsreferentie. Die stukken sturen we direct toe. -
diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index c3c645862..e31935d37 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -294,10 +294,6 @@ Beyond the two ISO certifications, pentest scans, and the GitHub workflow, the p - **BIO** — Baseline Informatiebeveiliging Overheid alignment is in progress. Status updates land here when complete. - **DigiD** — out of scope for our current portfolio. We integrate with DigiD-using systems but do not hold a DigiD assessment ourselves. -## Asking for the proof - -The certificates above are the original ISO 9001:2015 and ISO 27001:2022 documents — open the images for the full-resolution scans. If your procurement file needs the Statement of Applicability, a recent pentest report, or a copy of an internal audit, write to [info@conduction.nl](mailto:info@conduction.nl) with the contract or tender reference. We send those directly. -
From 414802492f1f55349d30e11f3a30bc4a825ebd5d Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 23:13:40 +0200 Subject: [PATCH 054/330] feat(quality): present policy statements as paper-card documents (#84) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each policy now renders inside a white paper card on the tinted page section: - Orange POLICY STATEMENT eyebrow + h3 title - Four-column metadata grid (Reference / First issued / Last review / Next review) in mono type - Top + bottom dividers - Italic attribution footer ('Adopted by the management of Conduction B.V.') Gives the statements an official, document-on-desk feel without changing any of the body text — the EN policy text is the same as before; the NL block is the verbatim signed January-2025 beleidsverklaring with an attribution footer noting the source. --- .../quality.mdx | 33 +++++++++- src/pages/quality.mdx | 64 +++++++++++++++++-- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index d378c9056..c220dbaf9 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -189,9 +189,30 @@ export const QualityHeroIllustration = (
-### Beleidsverklaring kwaliteits- en informatieveiligheidsmanagementsysteem - -_Conduction B.V. — vastgesteld januari 2025. ISO 9001:2015 en ISO 27001:2022 certificaten afgegeven 24 juli 2025. Tekst overgenomen uit de ondertekende beleidsverklaring._ +
+ +
+
Beleidsverklaring
+

Kwaliteits- en informatieveiligheidsmanagementsysteem

+
+
+
Norm
+
ISO 9001:2015 · ISO 27001:2022
+
+
+
Vastgesteld
+
januari 2025
+
+
+
Certificaten
+
24 juli 2025
+
+
+
Volgende review
+
februari 2027
+
+
+
Conduction, opgericht in 2018, streeft naar een betere digitale wereld door democratische, inclusieve en transparante software te ontwikkelen volgens de Common Ground-principes. Als Digital Socials richten we ons op het creëren van digitale oplossingen voor maatschappelijke vraagstukken, met 'Tech to serve people' als leidraad. @@ -216,6 +237,12 @@ Kwaliteit, informatiebeveiliging, continue evaluatie en klanttevredenheid staan Voor meer informatie over ons kwaliteits- en informatieveiligheidssysteem kunt u contact met ons opnemen via [info@conduction.nl](mailto:info@conduction.nl). +
+ Vastgesteld door de directie van Conduction B.V. — Amsterdam. Tekst overgenomen uit de ondertekende beleidsverklaring van januari 2025. +
+ +
+
diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index e31935d37..cda1702ab 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -189,9 +189,30 @@ export const QualityHeroIllustration = (
-### Quality policy statement - -_ISO 9001:2015 §5.2 — Quality Policy. Certificates first issued 24 July 2025. Last policy review: April 2026. Next review: annual management review cycle, February 2027._ +
+ +
+
Policy statement
+

Quality policy

+
+
+
Reference
+
ISO 9001:2015 §5.2
+
+
+
First issued
+
24 July 2025
+
+
+
Last review
+
April 2026
+
+
+
Next review
+
February 2027
+
+
+
Conduction is committed to delivering high-quality open-source software and services for digital government infrastructure. Our Quality Management System (QMS) is designed to consistently meet customer requirements and applicable regulatory requirements, and to enhance customer satisfaction. @@ -217,9 +238,36 @@ Conduction's management commits to: 3. Ensuring this policy is understood, implemented, and maintained at all levels of the organisation. 4. Reviewing this policy at least annually as part of the management review cycle. -### Information-security policy statement +
+ Adopted by the management of Conduction B.V. — Amsterdam, the Netherlands. +
-_ISO 27001:2022 §5.2 — Information Security Policy; Annex A.5.1. Last review: April 2026. Next review: annual management review cycle, February 2027._ +
+ +
+ +
+
Policy statement
+

Information-security policy

+
+
+
Reference
+
ISO 27001:2022 §5.2 · Annex A.5.1
+
+
+
First issued
+
24 July 2025
+
+
+
Last review
+
April 2026
+
+
+
Next review
+
February 2027
+
+
+
Conduction protects the confidentiality, integrity, and availability of the information it handles — including customer data, internal systems, and the open-source software it develops and operates. This policy establishes the framework for Conduction's Information Security Management System (ISMS) in accordance with ISO 27001:2022. @@ -250,6 +298,12 @@ All information assets related to the design, development, implementation, and s The privacy side of the ISMS is described in the [privacy policy](/privacy). +
+ Adopted by the management of Conduction B.V. — Amsterdam, the Netherlands. +
+ +
+
From 2f03a00a2f6df4ad3882405637d750e492fe20fc Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 19 May 2026 23:13:56 +0200 Subject: [PATCH 055/330] feat(pages): cobalt hero + intro lede across apps, solutions, sidecars (#83) * feat(pages): cobalt hero + intro lede across apps, solutions, sidecars Adds background="cobalt" to on all 17 app pages, all 6 solution pages, and all 8 sidecar pages so the marketing hero matches the per-app docs sites (openregister.conduction.nl etc). For the 15 app pages that carried a standalone prose paragraph between and the first
(the SEO/intro blurb), that prose is now passed via the new intro={<>...} prop on DetailHero. Previously the prose sat at MDX-root and stretched edge-to-edge because the marketing-page MDXPage swizzle deliberately has no width container; inside the hero, the preset's .intro rule constrains it to a 70ch read measure and renders it cobalt-100 on the cobalt panel. Requires @conduction/docusaurus-preset version that ships the intro prop (design-system main 92549b0). Until that preset version is on npm, the intro paragraphs will render but as an unrecognised prop (silently dropped); coordinate the npm publish + package.json bump before merging this commit into a release branch. * chore(deps): bump @conduction/docusaurus-preset to ^3.11.0 The 3.11.0 preset ships the new DetailHero intro prop consumed by the 31 marketing pages in the previous commit. --- package-lock.json | 8 ++++---- package.json | 2 +- src/pages/apps/app-versions.mdx | 1 + src/pages/apps/decidesk.mdx | 4 ++-- src/pages/apps/docudesk.mdx | 4 ++-- src/pages/apps/doriath.mdx | 1 + src/pages/apps/larpingapp.mdx | 4 ++-- src/pages/apps/mydash.mdx | 4 ++-- src/pages/apps/nldesign.mdx | 4 ++-- src/pages/apps/openbuilt.mdx | 4 ++-- src/pages/apps/opencatalogi.mdx | 4 ++-- src/pages/apps/openconnector.mdx | 4 ++-- src/pages/apps/openregister.mdx | 4 ++-- src/pages/apps/pipelinq.mdx | 4 ++-- src/pages/apps/procest.mdx | 4 ++-- src/pages/apps/scholiq.mdx | 4 ++-- src/pages/apps/shillinq.mdx | 4 ++-- src/pages/apps/softwarecatalog.mdx | 4 ++-- src/pages/apps/zaakafhandelapp.mdx | 4 ++-- src/pages/sidecars/keycloak.mdx | 1 + src/pages/sidecars/n8n.mdx | 1 + src/pages/sidecars/ollama.mdx | 1 + src/pages/sidecars/open-webui.mdx | 1 + src/pages/sidecars/openklant.mdx | 1 + src/pages/sidecars/opentalk.mdx | 1 + src/pages/sidecars/openzaak.mdx | 1 + src/pages/sidecars/valtimo.mdx | 1 + src/pages/solutions/archief.mdx | 1 + src/pages/solutions/mkb-workspace.mdx | 1 + src/pages/solutions/zaakafhandeling.mdx | 1 + 30 files changed, 48 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7d0e004de..7c43bbc4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { - "@conduction/docusaurus-preset": "^3.6.0", + "@conduction/docusaurus-preset": "^3.11.0", "@docusaurus/core": "^3.5.0", "@docusaurus/plugin-client-redirects": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", @@ -1989,9 +1989,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.10.0.tgz", - "integrity": "sha512-wFjmNtjON+ks0Aqzql1wGy6TCQPLsJTzMY1C8uwNnj+jTpGVGZ3QVqd6I/0oVDYhdgAjgdrijRhyN3L2Er4U7Q==", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.11.1.tgz", + "integrity": "sha512-uye0DP5t6n11Sxz+HuFG6gcfmusRWS+ObijPKY8Da69TlcMIHX7t4FySgHB62OhxfkVZcI4SUuKL3RYvw10dHA==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" diff --git a/package.json b/package.json index 05fefa5fe..2c55fd264 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^3.6.0", + "@conduction/docusaurus-preset": "^3.11.0", "@docusaurus/core": "^3.5.0", "@docusaurus/plugin-client-redirects": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", diff --git a/src/pages/apps/app-versions.mdx b/src/pages/apps/app-versions.mdx index 75bcd7dfe..2bd0eb8b6 100644 --- a/src/pages/apps/app-versions.mdx +++ b/src/pages/apps/app-versions.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, CtaBanner} from '@conduction/docusaurus-preset/components'; Board and council decision-making on your Nextcloud. Agendas, council dossiers, motions, votes, an audit trail that understands the Wet open overheid. For municipal councils, executive boards, supervisory and management boards.} + intro={<>Decidesk is an open-source decision-making app for the Nextcloud workspace. It runs the full governance cycle in one place: scheduling meetings, building agendas, submitting motions and amendments, voting, publishing minutes, and tracking action items to completion. Workflows are configurable for legislative bodies, associations, corporate boards, management teams, and citizen-participation panels. All meetings, motions, and decisions live as typed OpenRegister objects with an audit trail, and a built-in AI chat companion exposes those records over MCP. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/decidesk'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/decidesk'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai illustration={} /> -Decidesk is an open-source decision-making app for the Nextcloud workspace. It runs the full governance cycle in one place: scheduling meetings, building agendas, submitting motions and amendments, voting, publishing minutes, and tracking action items to completion. Workflows are configurable for legislative bodies, associations, corporate boards, management teams, and citizen-participation panels. All meetings, motions, and decisions live as typed OpenRegister objects with an audit trail, and a built-in AI chat companion exposes those records over MCP. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/docudesk.mdx b/src/pages/apps/docudesk.mdx index 6d9109570..c4ae80e46 100644 --- a/src/pages/apps/docudesk.mdx +++ b/src/pages/apps/docudesk.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppMock, RotatingCards, WidgetShelf, Showcase, AgentTrace, AppCrossLinks} from '@conduction/docusaurus-preset/components'; Document creation, anonymisation, signing, and templating on Nextcloud. Twelve templates ship for the most-used Dutch government documents (beschikkingen, jaarverslagen, bezwaarbrieven). Pulls fields straight from your registers.} + intro={<>DocuDesk is an open-source document-processing app for the Nextcloud workspace. It generates PDF, Word, HTML, and Excel documents from templates, anonymizes personal data through Presidio entity detection, and keeps a full audit trail for GDPR and WOO publication consent. All processing runs locally on the customer's Nextcloud instance, so sensitive files never leave the premises while still integrating with SharePoint, Office 365, and case-management systems. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/docudesk'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/docudesk'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai illustration={} /> -DocuDesk is an open-source document-processing app for the Nextcloud workspace. It generates PDF, Word, HTML, and Excel documents from templates, anonymizes personal data through Presidio entity detection, and keeps a full audit trail for GDPR and WOO publication consent. All processing runs locally on the customer's Nextcloud instance, so sensitive files never leave the premises while still integrating with SharePoint, Office 365, and case-management systems. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/doriath.mdx b/src/pages/apps/doriath.mdx index 19f61a442..366b89992 100644 --- a/src/pages/apps/doriath.mdx +++ b/src/pages/apps/doriath.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusaurus-preset/components'; Characters, rules, scenes, and NPC stats for live-action role-play (LARP). Build a setting in Nextcloud, share it with your group, and run sessions without spreadsheets-with-six-tabs.} + intro={<>LarpingApp is an open-source live-action role-playing (LARP) management app for the Nextcloud workspace. Organizers design characters, skills, items, and conditions with custom attributes, and the app recalculates stats automatically as players gain experience or apply effects. It also handles event subscriptions, waiting lists, and background-approval workflows, giving game masters one place to run a campaign. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/larpingapp'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/larpingapp'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai illustration={} /> -LarpingApp is an open-source live-action role-playing (LARP) management app for the Nextcloud workspace. Organizers design characters, skills, items, and conditions with custom attributes, and the app recalculates stats automatically as players gain experience or apply effects. It also handles event subscriptions, waiting lists, and background-approval workflows, giving game masters one place to run a campaign. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/mydash.mdx b/src/pages/apps/mydash.mdx index 41fedbc28..c6a97b42c 100644 --- a/src/pages/apps/mydash.mdx +++ b/src/pages/apps/mydash.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, RotatingCards, AppMock, WidgetShelf, Showcase, AgentTrace, AppCrossLinks} from '@conduction/docusaurus-preset/components'; Personal and team dashboards, built directly on your Nextcloud. No separate BI tool, no extra login, no ETL. Whatever you have in Nextcloud, you can visualise in MyDash.} + intro={<>MyDash is an open-source dashboard app for the Nextcloud workspace. It lets each user compose multiple personal dashboards from drag-and-drop widgets and shortcut tiles on a responsive GridStack canvas, and supports both the v1 and v2 Nextcloud Dashboard widget APIs out of the box. Administrators can ship pre-configured templates to user groups, pin compulsory widgets, and set conditional visibility rules based on group, time of day, or date range. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/mydash'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/mydash'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai illustration={} /> -MyDash is an open-source dashboard app for the Nextcloud workspace. It lets each user compose multiple personal dashboards from drag-and-drop widgets and shortcut tiles on a responsive GridStack canvas, and supports both the v1 and v2 Nextcloud Dashboard widget APIs out of the box. Administrators can ship pre-configured templates to user groups, pin compulsory widgets, and set conditional visibility rules based on group, time of day, or date range. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/nldesign.mdx b/src/pages/apps/nldesign.mdx index 2da43d3e9..59b8ce0a4 100644 --- a/src/pages/apps/nldesign.mdx +++ b/src/pages/apps/nldesign.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppMock, RotatingCards, WidgetShelf, Showcase, AppCrossLinks} from '@conduction/docusaurus-preset/components'; The NL Design System (NLDS) as a drop-in theme for your Nextcloud. One install and your interface follows the Dutch government house style, with Conduction component variants on top.} + intro={<>NL Design is an open-source theming app for the Nextcloud workspace. Administrators pick their organisation from a list of 39 pre-built themes (Rijkshuisstijl, Gemeente Utrecht, Amsterdam, Den Haag, Rotterdam, and more) and the workspace adopts that house style instantly. A token editor lets teams fine-tune individual colours, and customisations export to a file for sharing across instances. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/nldesign'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/nldesign'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai illustration={} /> -NL Design is an open-source theming app for the Nextcloud workspace. Administrators pick their organisation from a list of 39 pre-built themes (Rijkshuisstijl, Gemeente Utrecht, Amsterdam, Den Haag, Rotterdam, and more) and the workspace adopts that house style instantly. A token editor lets teams fine-tune individual colours, and customisations export to a file for sharing across instances. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/openbuilt.mdx b/src/pages/apps/openbuilt.mdx index 79495b308..8a2c32f08 100644 --- a/src/pages/apps/openbuilt.mdx +++ b/src/pages/apps/openbuilt.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AgentTrace, McpToolShelf, AppCrossLinks} from '@conduction/docusaurus-preset/components'; An app builder for citizen developers on Nextcloud. Compose your own app from OpenRegister schemas, OpenConnector APIs, Procest workflows, and DocuDesk documents. No PHP, no scaffolding.} + intro={<>OpenBuilt is an open-source citizen-developer app builder for the Nextcloud workspace. Users compose a working app from typed OpenRegister registers, OpenConnector connectors, n8n workflows, and DocuDesk templates, then lay out list, detail, and form pages in a browser-based designer. Version snapshots roll back bad edits, ZIP exports move apps between instances, and RBAC keeps per-record access enforced through OpenRegister. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/openbuilt'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/openbuilt'}} @@ -21,8 +23,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai icon={} /> -OpenBuilt is an open-source citizen-developer app builder for the Nextcloud workspace. Users compose a working app from typed OpenRegister registers, OpenConnector connectors, n8n workflows, and DocuDesk templates, then lay out list, detail, and form pages in a browser-based designer. Version snapshots roll back bad edits, ZIP exports move apps between instances, and RBAC keeps per-record access enforced through OpenRegister. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/opencatalogi.mdx b/src/pages/apps/opencatalogi.mdx index 23dfed083..353120f5b 100644 --- a/src/pages/apps/opencatalogi.mdx +++ b/src/pages/apps/opencatalogi.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, FeatureGrid, PairRow, PairCard, Pipeline, CtaBanner, AppMock, RotatingCards, WidgetShelf, Showcase, AppCrossLinks} from '@conduction/docusaurus-preset/components'; The public-facing software, dataset, and API catalogue. Drops into your Nextcloud, surfaces every register as a searchable entry, federates to data.overheid.nl out of the box.} + intro={<>OpenCatalogi is an open-source publication catalog app for the Nextcloud workspace. It lets government bodies publish, index, and share catalogs of software, services, and data through a single user interface, and ingests metadata from GitHub, GitLab, and external APIs via PublicCode.yml. The app supports WOO compliance (Public Access to Government Information Act) and standardized organization profiles, making public-sector information discoverable to citizens, businesses, and other agencies. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/opencatalogi'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/opencatalogi'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, FeatureGrid, illustration={} /> -OpenCatalogi is an open-source publication catalog app for the Nextcloud workspace. It lets government bodies publish, index, and share catalogs of software, services, and data through a single user interface, and ingests metadata from GitHub, GitLab, and external APIs via PublicCode.yml. The app supports WOO compliance (Public Access to Government Information Act) and standardized organization profiles, making public-sector information discoverable to citizens, businesses, and other agencies. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/openconnector.mdx b/src/pages/apps/openconnector.mdx index d6ade942e..3abe3f155 100644 --- a/src/pages/apps/openconnector.mdx +++ b/src/pages/apps/openconnector.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, FeatureGrid, PairRow, PairCard, CtaBanner, AppMock, RotatingCards, WidgetShelf, Showcase, AgentTrace, AppCrossLinks} from '@conduction/docusaurus-preset/components'; The integration layer for Nextcloud. REST, SOAP, GraphQL, file drops, message queues. Pulls data from your existing systems into typed registers without writing glue code.} + intro={<>OpenConnector is an open-source data-synchronization app for the Nextcloud workspace. It connects APIs, databases, file systems, and external services to OpenRegister, transforms payloads through configurable mappings, and schedules automated sync jobs with event and webhook triggers. It also exposes endpoints, consumers, and business rules, so government teams can build integrations without a separate ESB. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/openconnector'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/openconnector'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, FeatureGrid, illustration={} /> -OpenConnector is an open-source data-synchronization app for the Nextcloud workspace. It connects APIs, databases, file systems, and external services to OpenRegister, transforms payloads through configurable mappings, and schedules automated sync jobs with event and webhook triggers. It also exposes endpoints, consumers, and business rules, so government teams can build integrations without a separate ESB. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/openregister.mdx b/src/pages/apps/openregister.mdx index 1172abf71..b7cd2a084 100644 --- a/src/pages/apps/openregister.mdx +++ b/src/pages/apps/openregister.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, FeatureGrid, PairRow, PairCard, CtaBanner, AppMock, RotatingCards, WidgetShelf, Showcase, AgentTrace, McpToolShelf, AppCrossLinks} from '@conduction/docusaurus-preset/components'; Schemas, registers, structured data objects. The typed-data backbone underneath every other Conduction app, and the install you reach for first.} + intro={<>OpenRegister is an open-source data register app for the Nextcloud workspace. It stores domain-specific objects (client records, social-security entries, custom registers) validated against JSON Schema, with full version history, audit trails, soft deletion, and object-level locking. Schemas can be authored by hand or imported from Schema.org and the Dutch GGM (Gemeentelijk Gegevensmodel), and objects persist into Nextcloud, MySQL, PostgreSQL, or MongoDB without changing application logic. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/openregister'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/openregister'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, FeatureGrid, illustration={} /> -OpenRegister is an open-source data register app for the Nextcloud workspace. It stores domain-specific objects (client records, social-security entries, custom registers) validated against JSON Schema, with full version history, audit trails, soft deletion, and object-level locking. Schemas can be authored by hand or imported from Schema.org and the Dutch GGM (Gemeentelijk Gegevensmodel), and objects persist into Nextcloud, MySQL, PostgreSQL, or MongoDB without changing application logic. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/pipelinq.mdx b/src/pages/apps/pipelinq.mdx index fc7d4bd17..4a224127c 100644 --- a/src/pages/apps/pipelinq.mdx +++ b/src/pages/apps/pipelinq.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppMock, RotatingCards, WidgetShelf, Showcase, AgentTrace, AppCrossLinks} from '@conduction/docusaurus-preset/components'; CRM on your Nextcloud. Customers, prospects, deals, quotes. Everything as a typed register, no separate sales database, no second login. Your sales team works where your delivery team already is.} + intro={<>Pipelinq is an open-source CRM and pipeline-management app for the Nextcloud workspace. It runs a kanban deal-flow with configurable stages, per-team filters via Nextcloud groups, and KPI cards on top. Customers, contacts, and deals are typed OpenRegister schemas with citation-stable IDs and a per-record audit trail. Quotes are generated through DocuDesk templates, and OpenConnector links to accounting systems such as Exact, Twinfield, and AFAS. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/pipelinq'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/pipelinq'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai illustration={} /> -Pipelinq is an open-source CRM and pipeline-management app for the Nextcloud workspace. It runs a kanban deal-flow with configurable stages, per-team filters via Nextcloud groups, and KPI cards on top. Customers, contacts, and deals are typed OpenRegister schemas with citation-stable IDs and a per-record audit trail. Quotes are generated through DocuDesk templates, and OpenConnector links to accounting systems such as Exact, Twinfield, and AFAS. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/procest.mdx b/src/pages/apps/procest.mdx index b9b46e0a3..85f54b024 100644 --- a/src/pages/apps/procest.mdx +++ b/src/pages/apps/procest.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppMock, RotatingCards, WidgetShelf, Showcase, AgentTrace} from '@conduction/docusaurus-preset/components'; Case management for VTH (permits, supervision, enforcement) and citizen processes on your Nextcloud. A workflow engine plus typed registers plus an audit trail that meets the Wet kwaliteitsborging and Algemene wet bestuursrecht.} + intro={<>Procest is an open-source case-management app for the Nextcloud workspace, built for Dutch government organisations. Case workers list and filter cases, drive them through ZGW-compliant case-type definitions, and pick up tasks from a personal work queue. The app maps ZGW API fields onto OpenRegister, so cases interoperate with national exchange standards without a second database. It targets zaakgericht werken scenarios such as complaints, consultations, and WOO disclosure requests. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/procest'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/procest'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai illustration={} /> -Procest is an open-source case-management app for the Nextcloud workspace, built for Dutch government organisations. Case workers list and filter cases, drive them through ZGW-compliant case-type definitions, and pick up tasks from a personal work queue. The app maps ZGW API fields onto OpenRegister, so cases interoperate with national exchange standards without a second database. It targets zaakgericht werken scenarios such as complaints, consultations, and WOO disclosure requests. Released under EUPL-1.2 and maintained by Conduction since 2019. -
Leerlingvolgsysteem en LMS op Nextcloud. Cursussen, inschrijvingen, aanwezigheid, nakijken, certificaten. Voor scholen, opleiders en compliance-teams die hun leerproces niet bij een externe SaaS willen parkeren.} + intro={<>Scholiq is an open-source education app for the Nextcloud workspace. It runs as a leerlingvolgsysteem (LVS) for Dutch primary and secondary schools, as a full LMS for higher education, and as a compliance-training engine for MKB and government bodies. Scholiq connects natively to DUO BRON/ROD, OSO transfer, SURFconext, IMS QTI 3.0, and EDCI / Open-Badges 3.0, with EU AI Act gating built in from day one. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://scholiq.conduction.nl/'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/scholiq'}} @@ -21,8 +23,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai icon={} /> -Scholiq is an open-source education app for the Nextcloud workspace. It runs as a leerlingvolgsysteem (LVS) for Dutch primary and secondary schools, as a full LMS for higher education, and as a compliance-training engine for MKB and government bodies. Scholiq connects natively to DUO BRON/ROD, OSO transfer, SURFconext, IMS QTI 3.0, and EDCI / Open-Badges 3.0, with EU AI Act gating built in from day one. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/shillinq.mdx b/src/pages/apps/shillinq.mdx index c6e632e70..b52541345 100644 --- a/src/pages/apps/shillinq.mdx +++ b/src/pages/apps/shillinq.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppCrossLinks} from '@conduction/docusaurus-preset/components'; Business administration on Nextcloud. Invoices, contracts, procurement on shared registers. Built for the MKB that already runs Nextcloud and wants its finance, sales, and procurement in the same workspace.} + intro={<>Shillinq is an open-source business administration app for the Nextcloud workspace. It covers double-entry bookkeeping, sales and purchase invoicing (UBL, Peppol, NLCIUS), procurement workflows, contract lifecycle management, and bank reconciliation over SEPA / ISO 20022. Dutch government compliance ships in the box: BBV, IV3, SiSa, and DigiInkoop. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/shillinq'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/shillinq'}} @@ -21,8 +23,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai icon={} /> -Shillinq is an open-source business administration app for the Nextcloud workspace. It covers double-entry bookkeeping, sales and purchase invoicing (UBL, Peppol, NLCIUS), procurement workflows, contract lifecycle management, and bank reconciliation over SEPA / ISO 20022. Dutch government compliance ships in the box: BBV, IV3, SiSa, and DigiInkoop. Released under EUPL-1.2 and maintained by Conduction since 2019. -
diff --git a/src/pages/apps/softwarecatalog.mdx b/src/pages/apps/softwarecatalog.mdx index 25b3357ea..b42411580 100644 --- a/src/pages/apps/softwarecatalog.mdx +++ b/src/pages/apps/softwarecatalog.mdx @@ -7,6 +7,7 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner, AppMock, RotatingCards, WidgetShelf, Showcase, AgentTrace} from '@conduction/docusaurus-preset/components'; IT-asset management on your Nextcloud. A central register of every application, licence, contract, and dependency in your organisation. Ideal for the IT department that wants to know what runs where.} + intro={<>SoftwareCatalog is an open-source IT-asset-management app for the Nextcloud workspace. It keeps a central register of every application, licence, contract, and dependency the organisation runs, with renewal alerts before the auto-renewal date. Dashboard widgets surface upcoming renewals, an inventory snapshot by category, and discovery deltas showing newly added or removed apps. Built for the IT department that needs to know what runs where, without a separate asset database or second login. Released under EUPL-1.2 and maintained by Conduction since 2019.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/softwarecatalog'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/softwarecatalog'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai illustration={} /> -SoftwareCatalog is an open-source IT-asset-management app for the Nextcloud workspace. It keeps a central register of every application, licence, contract, and dependency the organisation runs, with renewal alerts before the auto-renewal date. Dashboard widgets surface upcoming renewals, an inventory snapshot by category, and discovery deltas showing newly added or removed apps. Built for the IT department that needs to know what runs where, without a separate asset database or second login. Released under EUPL-1.2 and maintained by Conduction since 2019. -
The citizen-facing case-status portal on your Nextcloud. Citizens follow their cases, request status, upload documents. ZGW APIs, archief-koppelvlakken, audit trail. No separate SaaS, no second login for case-workers.} + intro={<>Zaak Afhandel App (ZAA) is an open-source case-handling app for the Nextcloud workspace. It manages Dutch zaakafhandeling end-to-end: intake, defined status workflows, work queues, tasks, attached documents, and a record of every besluit. The app follows Zaakgericht Werken (ZGW) terminology and patterns used by Dutch municipalities, and bundles gateway functions for routing API calls between systems. Released under EUPL-1.2 and maintained by Conduction since 2019. ZAA has been superseded by Procest and Pipelinq for new deployments.} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://docs.conduction.nl/zaakafhandelapp'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/zaakafhandelapp'}} @@ -22,8 +24,6 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai illustration={} /> -Zaak Afhandel App (ZAA) is an open-source case-handling app for the Nextcloud workspace. It manages Dutch zaakafhandeling end-to-end: intake, defined status workflows, work queues, tasks, attached documents, and a record of every besluit. The app follows Zaakgericht Werken (ZGW) terminology and patterns used by Dutch municipalities, and bundles gateway functions for routing API calls between systems. Released under EUPL-1.2 and maintained by Conduction since 2019. ZAA has been superseded by Procest and Pipelinq for new deployments. -
Date: Wed, 20 May 2026 06:29:19 +0200 Subject: [PATCH 056/330] =?UTF-8?q?feat(quality):=20hero=20slogan=20?= =?UTF-8?q?=E2=80=94=20'Quality,=20with=20a=20Q.'=20/=20'Kwaliteit,=20met?= =?UTF-8?q?=20een=20K.'=20(#85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the bare 'Quality.' / 'Kwaliteit.' title with a two-line slogan that leans into the letter as the audit-grade signal. Same two-line shape in both locales, NL matched on the K. --- i18n/nl/docusaurus-plugin-content-pages/quality.mdx | 2 +- src/pages/quality.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index c220dbaf9..8f7886937 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -64,7 +64,7 @@ export const QualityHeroIllustration = ( background="cobalt" crumb={[{label: 'Over ons', href: '/about'}, 'Kwaliteit']} locales="ISO 9001:2015 · ISO 27001:2022" - title="Kwaliteit." + title={<>Kwaliteit,
met een K.} tagline={<>Eén geïntegreerd kwaliteits- en informatiebeveiligingsmanagementsysteem. ISO 9001:2015 en ISO 27001:2022 verankeren de beleidskaders, pentest-tools.com verzorgt de doorlopende externe scans, en het volledige bouwproces staat publiek op GitHub. Samen leveren ze scope, audittrail en doorlopende borging voor elke Conduction-app.} primaryCta={{label: 'Lees de beleidsverklaring', href: '#iso-certifications', tone: 'orange'}} secondaryCta={{label: 'Compliance FAQ', href: '#compliance-faq'}} diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index cda1702ab..fa3637cfb 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -64,7 +64,7 @@ export const QualityHeroIllustration = ( background="cobalt" crumb={[{label: 'About', href: '/about'}, 'Quality']} locales="ISO 9001:2015 · ISO 27001:2022" - title="Quality." + title={<>Quality,
with a Q.} tagline={<>One integrated quality and information-security management system. ISO 9001:2015 and ISO 27001:2022 anchor the policies, pentest-tools.com runs the continuous scans, and the entire build flow lives in public on GitHub. Together they cover scope, audit trail, and ongoing assurance for every Conduction app.} primaryCta={{label: 'Read the policy', href: '#iso-certifications', tone: 'orange'}} secondaryCta={{label: 'Compliance FAQ', href: '#compliance-faq'}} From 2e4dcc6406ef4e8f20a276067171c298d4253b3a Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 20 May 2026 07:01:12 +0200 Subject: [PATCH 057/330] feat(quality): add 'How We Work' (docs.conduction.nl) as 5th tool (#86) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reframes the page narrative around five layered tools, one management system: ISO 9001 + 27001 define how Conduction should run and what we measure How We Work (docs.conduction.nl) translates that into operations pentest-tools.com tests the outcome externally GitHub workflow tests the outcome internally + supplies the audit trail Every line of code, every service we deliver — same predictable quality. Changes: - Hero tagline rewritten around the five-tools framing; illustration adds a 'docs.conduction.nl · operating handbook · LIVE' status row. - Showcase accordion: 'How We Work' item inserted between ISO 27001 and Pentest, with a panel showing the handbook section grid (Identity, Way of Work, Hydra, Claude workflow, ISO compliance, Roadmap). - New 'How We Work — operating handbook' section between ISO and Pentest. - Section background alternation reshuffled to keep tinted/white rhythm. - Related compliance intro updated to include the handbook. Both locales updated. Same five-tools framing in NL. --- .../quality.mdx | 59 ++++++++++++++++--- src/pages/quality.mdx | 59 ++++++++++++++++--- 2 files changed, 102 insertions(+), 16 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index 8f7886937..d31a66aba 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -34,7 +34,7 @@ export const QualityHeroIllustration = ( textTransform: 'uppercase', color: 'rgba(255,255,255,0.65)', }}> - Certificaten · scans · workflow + Certificaten · handboek · scans · workflow ● live
@@ -48,6 +48,10 @@ export const QualityHeroIllustration = (
+
+ docs.conduction.nl · operationeel handboek + LIVE +
pentest-tools.com · wekelijkse scan SCHOON @@ -65,7 +69,7 @@ export const QualityHeroIllustration = ( crumb={[{label: 'Over ons', href: '/about'}, 'Kwaliteit']} locales="ISO 9001:2015 · ISO 27001:2022" title={<>Kwaliteit,
met een K.} - tagline={<>Eén geïntegreerd kwaliteits- en informatiebeveiligingsmanagementsysteem. ISO 9001:2015 en ISO 27001:2022 verankeren de beleidskaders, pentest-tools.com verzorgt de doorlopende externe scans, en het volledige bouwproces staat publiek op GitHub. Samen leveren ze scope, audittrail en doorlopende borging voor elke Conduction-app.} + tagline={<>Eén managementsysteem, vijf tools die het waarmaken. ISO 9001:2015 en ISO 27001:2022 leggen vast hoe Conduction moet werken en wat we meten. Het How We Work-handboek op docs.conduction.nl vertaalt dat naar de uitvoering. pentest-tools.com en de GitHub-native kwaliteitsworkflow toetsen de uitkomst. Elke regel code, elke dienst — dezelfde voorspelbare kwaliteit.} primaryCta={{label: 'Lees de beleidsverklaring', href: '#iso-certifications', tone: 'orange'}} secondaryCta={{label: 'Compliance FAQ', href: '#compliance-faq'}} iconColor="var(--c-orange-knvb)" @@ -76,8 +80,8 @@ export const QualityHeroIllustration = (
), }, + { + id: 'how-we-work', + title: 'How We Work — operationeel handboek.', + summary: "docs.conduction.nl is de operationele laag onder het managementsysteem. Het vertaalt het ISO-beleid naar de dagelijkse procedures die elke (digitale) medewerker volgt: onboarding, rollen, software bouwen, klantondersteuning, de Hydra-pipeline en ISO-mapping clause voor clause. Publiek, in versiebeheer, en dezelfde bron die elke reviewer en auditor leest.", + cta: {label: 'Open het handboek', href: 'https://docs.conduction.nl/'}, + panel: ( +
+
docs.conduction.nl — handboek-onderdelen
+
+ {[ + {hex: 'var(--c-blue-cobalt)', label: 'Identity'}, + {hex: 'var(--c-orange-knvb)', label: 'Way of Work'}, + {hex: 'var(--c-mint-500)', label: 'Hydra-pipeline'}, + {hex: 'var(--c-lavender-300)', label: 'Claude-workflow'}, + {hex: 'var(--c-cobalt-700)', label: 'ISO-compliance'}, + {hex: 'var(--c-forest-300)', label: 'Roadmap'}, + ].map((s, i) => ( +
+ + {s.label} +
+ ))} +
+
+ ), + }, { id: 'pentest', title: 'Pentest-tools.com — Doorlopende externe scans.', @@ -245,12 +275,25 @@ Voor meer informatie over ons kwaliteits- en informatieveiligheidssysteem kunt u
-
+
+## How We Work — operationeel handboek + +Het ISO-beleid zegt waartoe Conduction zich verbindt. Het handboek op **[docs.conduction.nl](https://docs.conduction.nl/)** vertaalt die toezeggingen naar de procedures die elke (digitale) medewerker dagelijks volgt — hoe we onboarden, hoe we software bouwen, hoe we customer support draaien, hoe de Hydra-pipeline loopt, hoe de Claude-workflow elke wijziging beoordeelt. Het is de operationele laag die het managementsysteem in praktijk brengt. + +**Onderdelen.** _Way of Work_ (het handboek zelf), _Hydra_ (onze spec-gedreven CI/CD-pipeline), _Claude-workflow_ (de conventies en skills die de dagelijkse ontwikkeling sturen), _ISO-compliance_ (clause-by-clause-mapping van de normen naar onze engineering-pipeline), _Identity_ (brand en visuele regels) en de publieke _Roadmap_. + +**Hoe past het in het managementsysteem.** Elke operationele procedure staat in versiebeheer op GitHub en loopt door hetzelfde PR-+-reviewproces dat hieronder onder [Kwaliteitsworkflow op GitHub](#github-workflow) beschreven staat. Auditors lezen dezelfde bron als elke medewerker — geen parallel handboek om bij te houden. + +
+ +
+
+ ## Pentest-tools -Doorlopende externe borging is de derde pijler van het managementsysteem. Conduction heeft een abonnement op het commerciële SaaS-scanplatform **[pentest-tools.com](https://app.pentest-tools.com/)** en draait daarop geplande tests tegen de productievlakken van onze apps, de managed Common Ground-omgeving op commonground.nu en de marketingvlakken onder conduction.nl. +Doorlopende externe borging. Conduction heeft een abonnement op het commerciële SaaS-scanplatform **[pentest-tools.com](https://app.pentest-tools.com/)** en draait daarop geplande tests tegen de productievlakken van onze apps, de managed Common Ground-omgeving op commonground.nu en de marketingvlakken onder conduction.nl. Het platform combineert een website-vulnerabilityscanner, een netwerkscanner, een CVE-check op de draaiende stack, TLS-configuratie-audits en subdomain enumeration. De dekking volgt de OWASP Top 10 en de controls in ISO 27001:2022 Annex A.8 (technologische maatregelen). @@ -259,7 +302,7 @@ Het platform combineert een website-vulnerabilityscanner, een netwerkscanner, ee
-
+
## Kwaliteitsworkflow op GitHub @@ -281,7 +324,7 @@ Elke Conduction-app staat publiek op GitHub onder de [ConductionNL](https://gith ## Aanverwante compliance-signalen -Naast de twee ISO-certificeringen, de pentest-scans en de GitHub-workflow ziet het inkooprelevante compliance-beeld voor Conduction er zo uit: +Naast de twee ISO-certificeringen, het operationele handboek, de pentest-scans en de GitHub-workflow ziet het inkooprelevante compliance-beeld voor Conduction er zo uit: - **ISAE 3402.** De managed hosting op commonground.nu draait op infrastructuur van [Cyso](https://www.cyso.nl/) onder ISAE 3402 Type II. Hun verklaring sturen we op verzoek toe. - **BIO.** De afstemming op de Baseline Informatiebeveiliging Overheid loopt. Statusupdates komen hier zodra die rond is. diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index fa3637cfb..fb73e9b3c 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -34,7 +34,7 @@ export const QualityHeroIllustration = ( textTransform: 'uppercase', color: 'rgba(255,255,255,0.65)', }}> - Certificates · scans · workflow + Certificates · handbook · scans · workflow ● live
@@ -48,6 +48,10 @@ export const QualityHeroIllustration = (
+
+ docs.conduction.nl · operating handbook + LIVE +
pentest-tools.com · weekly scan CLEAN @@ -65,7 +69,7 @@ export const QualityHeroIllustration = ( crumb={[{label: 'About', href: '/about'}, 'Quality']} locales="ISO 9001:2015 · ISO 27001:2022" title={<>Quality,
with a Q.} - tagline={<>One integrated quality and information-security management system. ISO 9001:2015 and ISO 27001:2022 anchor the policies, pentest-tools.com runs the continuous scans, and the entire build flow lives in public on GitHub. Together they cover scope, audit trail, and ongoing assurance for every Conduction app.} + tagline={<>One management system, five tools that prove it works. ISO 9001:2015 and ISO 27001:2022 set how Conduction should run and what we measure. The How We Work handbook at docs.conduction.nl translates that into operations. pentest-tools.com and the GitHub-native quality workflow test the outcome. Every line of code, every service we deliver — the same predictable quality.} primaryCta={{label: 'Read the policy', href: '#iso-certifications', tone: 'orange'}} secondaryCta={{label: 'Compliance FAQ', href: '#compliance-faq'}} iconColor="var(--c-orange-knvb)" @@ -76,8 +80,8 @@ export const QualityHeroIllustration = (
), }, + { + id: 'how-we-work', + title: 'How We Work — operating handbook.', + summary: "docs.conduction.nl is the operational layer of the management system. It translates the ISO policies into the day-to-day procedures every (digital) employee follows: onboarding, roles, building software, customer support, the Hydra pipeline, and clause-by-clause ISO mapping. Public, version-controlled, and the same source every reviewer and auditor reads.", + cta: {label: 'Open the handbook', href: 'https://docs.conduction.nl/'}, + panel: ( +
+
docs.conduction.nl — handbook sections
+
+ {[ + {hex: 'var(--c-blue-cobalt)', label: 'Identity'}, + {hex: 'var(--c-orange-knvb)', label: 'Way of Work'}, + {hex: 'var(--c-mint-500)', label: 'Hydra pipeline'}, + {hex: 'var(--c-lavender-300)', label: 'Claude workflow'}, + {hex: 'var(--c-cobalt-700)', label: 'ISO compliance'}, + {hex: 'var(--c-forest-300)', label: 'Roadmap'}, + ].map((s, i) => ( +
+ + {s.label} +
+ ))} +
+
+ ), + }, { id: 'pentest', title: 'Pentest-tools.com — Continuous external scans.', @@ -306,12 +336,25 @@ The privacy side of the ISMS is described in the [privacy policy](/privacy).
-
+
+## How We Work — operating handbook + +The ISO policies say what Conduction is committed to. The handbook at **[docs.conduction.nl](https://docs.conduction.nl/)** translates those commitments into the procedures every (digital) employee actually follows day to day — how we onboard, how we build software, how we run customer support, how the Hydra pipeline operates, how the Claude workflow gates change. It is the operational layer that makes the management system real. + +**Sections.** _Way of Work_ (the handbook itself), _Hydra_ (our spec-driven CI/CD pipeline), _Claude workflow_ (the conventions and skills that drive day-to-day development), _ISO compliance_ (clause-by-clause mapping from the standards to our engineering pipeline), _Identity_ (brand and visual rules), and the public _Roadmap_. + +**How it fits the management system.** Every operating procedure is in version control on GitHub and follows the same PR + review process described under [Quality workflow on GitHub](#github-workflow) below. Auditors read the same source every employee does — there is no parallel handbook to keep in sync. + +
+ +
+
+ ## Pentest tools -Continuous external assurance is the third leg of the management system. Conduction subscribes to the commercial **[pentest-tools.com](https://app.pentest-tools.com/)** SaaS scanning platform and runs scheduled tests against the production surfaces of our apps, the managed Common Ground tenant at commonground.nu, and the marketing surfaces under conduction.nl. +Continuous external assurance. Conduction subscribes to the commercial **[pentest-tools.com](https://app.pentest-tools.com/)** SaaS scanning platform and runs scheduled tests against the production surfaces of our apps, the managed Common Ground tenant at commonground.nu, and the marketing surfaces under conduction.nl. The platform combines a website vulnerability scanner, a network scanner, a CVE checker against the running stack, TLS configuration auditing, and subdomain enumeration. Coverage maps to the OWASP Top 10 and the controls in ISO 27001:2022 Annex A.8 (technological controls). @@ -320,7 +363,7 @@ The platform combines a website vulnerability scanner, a network scanner, a CVE
-
+
## Quality workflow on GitHub @@ -342,7 +385,7 @@ Every Conduction app lives in public on GitHub under the [ConductionNL](https:// ## Related compliance signals -Beyond the two ISO certifications, pentest scans, and the GitHub workflow, the procurement-relevant compliance picture for Conduction is: +Beyond the two ISO certifications, the operating handbook, the pentest scans, and the GitHub workflow, the procurement-relevant compliance picture for Conduction is: - **ISAE 3402** — managed hosting at commonground.nu runs on infrastructure operated by [Cyso](https://www.cyso.nl/) under ISAE 3402 Type II. Their attestation is available on request. - **BIO** — Baseline Informatiebeveiliging Overheid alignment is in progress. Status updates land here when complete. From 80289efd7a31eeff8ccb1a1bd3f441cb2ebbb70e Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 20 May 2026 07:54:23 +0200 Subject: [PATCH 058/330] feat(quality): add CI/CD flow illustration to the GitHub-workflow section (#87) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inserts a 4-stage illustrated pipeline of the reusable .github/quality.yml workflow into the 'Quality workflow on GitHub' section. Mirrors the ConductionNL/.github code-quality flow you see in the Actions UI: Stage 1 (parallel) — Quality gates php-quality (lint, phpcs, phpmd, psalm, phpstan, phpmetrics) vue-quality (eslint, stylelint) security (composer audit, npm audit) license (SPDX allowlist, both ecosystems) Stage 2 (gated) — Tests phpunit (matrix: php × nextcloud, postgres service) newman (postman integration collections) playwright (Chromium + V8 coverage) Stage 3 (always) — Reporting + Coverage baseline quality-report (PDF + PR comment + step summary) baseline-protection / update-baseline Stage 4 (protected branches) — SBOM CycloneDX from composer + npm, Grype CVE scan, release asset Each stage card uses brand hex bullets and a family colour. Each stage header in the body text links to the corresponding line in quality.yml. Two CTAs below: 'View quality.yml on GitHub' + 'See a live run' (OR run 26099018560). The existing 'Branch protection / Two-track review / Hydra automation / Spec-driven changes' bullets are kept below as the rules that frame the pipeline. Both locales updated. --- .../quality.mdx | 149 +++++++++++++++++- src/pages/quality.mdx | 149 +++++++++++++++++- 2 files changed, 286 insertions(+), 12 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index d31a66aba..e96af9d9b 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -6,6 +6,122 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, Showcase, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; +export const WORKFLOW_FILE = 'https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml'; + +export function QualityPipelineFlow() { + const card = {background: 'white', border: '1px solid var(--c-cobalt-100)', borderRadius: 8, padding: '14px 14px 16px', boxShadow: '0 1px 3px rgba(33, 70, 139, 0.04)'}; + const cardTitle = {fontSize: 12, fontWeight: 700, color: 'var(--c-cobalt-900)', marginBottom: 4, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 6}; + const cardSubtitle = {fontSize: 10, fontFamily: 'var(--conduction-typography-font-family-code)', color: 'var(--c-cobalt-500, #6f7da3)', marginBottom: 10, letterSpacing: '0.04em'}; + const stageWrap = {background: 'var(--c-cobalt-50)', border: '1px solid var(--c-cobalt-100)', borderRadius: 12, padding: '18px 18px 22px'}; + const stageHead = {display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 14, gap: 12, flexWrap: 'wrap'}; + const stageLabel = {fontSize: 10, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--c-orange-knvb)', fontFamily: 'var(--conduction-typography-font-family-code)'}; + const stageTitle = {fontSize: 16, fontWeight: 700, color: 'var(--c-cobalt-900)'}; + const stageNote = {fontSize: 12, color: 'var(--c-cobalt-700)'}; + const arrowRow = {height: 24, display: 'flex', justifyContent: 'center', alignItems: 'center', color: 'var(--c-cobalt-300)', fontSize: 20, fontWeight: 700}; + const hex = (color) => ({width: 10, height: 12, clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)', background: color, flexShrink: 0}); + const list = (items, color) => ( +
    + {items.map((it, i) => ( +
  • + + {it} +
  • + ))} +
+ ); + return ( +
+
+
+
Fase 1 · parallel
Kwaliteitsgates
+ Vier matrix-jobs, fail-fast uit, bij elke PR. +
+
+
+
PHP Qualitymatrix
+
composer scripts
+ {list(['lint', 'phpcs', 'phpmd', 'psalm', 'phpstan', 'phpmetrics'], 'var(--c-cobalt-700)')} +
+
+
Vue Qualitymatrix
+
npm scripts
+ {list(['eslint', 'stylelint'], 'var(--c-blue-cobalt)')} +
+
+
Securitymatrix
+
audit
+ {list(['composer audit', 'npm audit'], 'var(--c-orange-knvb)')} +
+
+
Licensematrix
+
SPDX-allowlist
+ {list(['composer', 'npm'], 'var(--c-mint-500)')} +
+
+
+ +
↓
+ +
+
+
Fase 2 · gated
Tests
+ Pas zodra PHP Quality + Security groen zijn. +
+
+
+
PHPUnitmatrix
+
PHP × Nextcloud
+ {list(['php 8.3 / 8.4', 'nc stable31 / 32', 'pgsql 16 service'], 'var(--c-lavender-300)')} +
+
+
Integratienewman
+
Postman-collections
+ {list(['newman run *.postman_collection.json'], 'var(--c-lavender-300)')} +
+
+
E2Eplaywright
+
Chromium + V8-coverage
+ {list(['npx playwright test', 'spec-to-test ≥ 75%'], 'var(--c-lavender-300)')} +
+
+
+ +
↓
+ +
+
+
+
Fase 3 · altijd
Rapportage
+
+
+
+
Kwaliteitsrapport
+
PDF + PR-comment + step summary
+ {list(['aggregeert result-* artifacts', 'pandoc → wkhtmltopdf', '90 dagen bewaard'], 'var(--c-mint-500)')} +
+
+
Coverage-baseline
+
guard + auto-update
+ {list(['PR: handmatige edits geblokt', 'main / development: bijwerken als beter'], 'var(--c-mint-500)')} +
+
+
+ +
+
+
Fase 4 · beschermde branches
Bill of Materials
+
+
+
SBOMCycloneDX
+
composer + npm, samengevoegd
+ {list(['composer CycloneDX:make-sbom', 'cyclonedx-npm', 'Grype CVE-scan (fail op critical)', 'publiceer: artifact + release-asset'], 'var(--c-forest-300)')} +
+
+
+
+ ); +} + export const QualityIcon = ( @@ -306,17 +422,38 @@ Het platform combineert een website-vulnerabilityscanner, een netwerkscanner, ee ## Kwaliteitsworkflow op GitHub -Elke Conduction-app staat publiek op GitHub onder de [ConductionNL](https://github.com/ConductionNL)-organisatie. De ontwikkelworkflow is de operationele laag van het KMS — het is de manier waarop de gedocumenteerde procedures uit §7.5 en §8 in de praktijk lopen, met het spoor van feature request tot gemergde code end-to-end zichtbaar. +Elke Conduction-app staat publiek op GitHub onder de [ConductionNL](https://github.com/ConductionNL)-organisatie. De herbruikbare [`Quality`-workflow](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml) — één keer gedefinieerd in de `.github`-repo en aangeroepen door elke app — draait dezelfde gate-stack op elke pull request: vier parallelle kwaliteitsmatrices, daarna een gated test-fase, daarna rapportage + een Software Bill of Materials op beschermde branches. + + + + + +### Fase voor fase + +**[Fase 1 — Kwaliteitsgates](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L141)** _(parallel)_. Vier matrix-jobs draaien naast elkaar. `php-quality` dekt `lint`, `phpcs`, `phpmd`, `psalm`, `phpstan` en `phpmetrics`. `vue-quality` dekt `eslint` en `stylelint`. `security` draait `composer audit` en `npm audit`. `license` toetst dependencies in beide ecosystemen tegen een EUPL-1.2 / AGPL-3.0-compatibele SPDX-allowlist en uploadt een rapport per ecosysteem. Elke tool is een aparte matrix-entry, dus een enkele failure wijst naar de exacte gate. + +**[Fase 2 — Tests](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L639)** _(gated)_. Loopt pas als de `php-quality`- en `security`-jobs uit Fase 1 groen zijn. `phpunit` spant de PHP × Nextcloud-matrix (PHP 8.3/8.4 tegen `stable31`/`stable32`) met een echte Postgres 16-service. `newman` start de Nextcloud built-in server en draait de Postman-integratiecollections. `playwright` draait de E2E-specs tegen Chromium met V8-coverage en een spec-to-test-coverage gate van 75%. + +**[Fase 3 — Rapportage](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L1249)** _(altijd)_. De `report`-job downloadt elke `result-*`-artifact, de license-rapporten en de coverage-clover, rendert een compacte Markdown-tabel per categorie (PHP / Vue / Security / License / Tests), zet die om naar PDF via `pandoc` + `wkhtmltopdf`, schrijft hem naar de GitHub-stepsummary, post hem als PR-comment en bewaart de artifact 90 dagen. Op `main`- / `development`-pushes tilt de `update-baseline`-job de coverage-baseline op als die verbetert; op PRs blokkeert de `baseline-protection`-job handmatige edits aan `.coverage-baseline`. + +**[Fase 4 — Software Bill of Materials](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L1522)** _(beschermde branches)_. Nadat `security` slaagt, genereert de `sbom`-job een CycloneDX-SBOM uit composer (`CycloneDX:make-sbom`) en npm (`@cyclonedx/cyclonedx-npm`), voegt ze samen, draait Grype om te falen op kritieke CVE's en publiceert de SBOM als een 90-dagen-artifact plus als release-asset bij elke tag. -**Branch protection.** Drie organisatie-brede rulesets dwingen overal dezelfde regels af: ten minste één goedkeurende review om naar `development` te mergen, ten minste twee reviews om naar `main` te mergen. Directe pushes naar beschermde branches zijn geblokkeerd. Elke wijziging loopt via een pull request. +### Om de pipeline heen -**Tweesporen-review.** Pull requests krijgen de juiste reviewer via labels. Het label `code-review:queued` start een code-review tegen de coding standards van het project (PHPCS, PHPMD, Psalm en PHPStan voor PHP-apps; ruff en mypy voor Python ExApps; ESLint en Vue-regels voor frontends). Het label `security-review:queued` start een informatiebeveiligingsreview tegen de relevante ISO 27001:2022 Annex A-controls. +**Branch protection.** Drie organisatie-brede rulesets dwingen overal dezelfde regels af: ten minste één goedkeurende review om naar `development` te mergen, ten minste twee reviews om naar `main` te mergen. Directe pushes naar beschermde branches zijn geblokkeerd. -**Hydra-automatisering.** Routinematig review-werk loopt via Hydra, onze interne coördinatielaag — die kijkt naar PR-labels, stuurt review-jobs naar gespecialiseerde agents en schrijft de bevindingen terug als PR-comments. Het spoor van wie wat reviewde met welke feedback staat in de PR-historie. Een menselijke reviewer keurt goed voor de merge. +**Tweesporen-review.** Pull requests krijgen de juiste reviewer via labels — `code-review:queued` start de code-review tegen PHPCS / PHPMD / Psalm / PHPStan (of `ruff`/`mypy`/ESLint per stack); `security-review:queued` start een informatiebeveiligingsreview tegen de relevante ISO 27001:2022 Annex A-controls. -**Spec-gedreven wijzigingen.** Grotere wijzigingen dragen een OpenSpec change-folder en een ADR mee. De spec staat naast de code, de ADR legt de architecturale beslissing en het waarom vast, en de PR verwijst naar beide. Dat levert de documentatiesporen die ISO 9001:2015 §7.5 (beheersing van gedocumenteerde informatie) vraagt — zonder een parallel kwaliteitshandboek bij te houden. +**Hydra-automatisering.** Routinematig review-werk loopt via Hydra — kijkt naar PR-labels, stuurt review-jobs naar gespecialiseerde agents en schrijft bevindingen terug als PR-comments. Een menselijke reviewer keurt goed voor de merge. -**Geautomatiseerde CI-gates.** Elke PR draait de relevante linters, statische analyzers, unit-tests en dependency-vulnerability-scans voor er een menselijke reviewer wordt gevraagd. PRs die een gate niet halen worden geblokkeerd tot ze gerepareerd zijn. De CI-configuratie staat in de repository, dus de gates zijn zichtbaar voor iedereen die de code leest. +**Spec-gedreven wijzigingen.** Grotere wijzigingen dragen een OpenSpec change-folder en een ADR. Spec naast de code, ADR voor de architecturale beslissing, PR verwijst naar beide. Dat levert de documentatiesporen die ISO 9001:2015 §7.5 vraagt — zonder een parallel kwaliteitshandboek bij te houden.
diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index fb73e9b3c..6d9b2e11d 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -6,6 +6,122 @@ hide_table_of_contents: true import {DetailHero, Section, SectionHead, Showcase, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; +export const WORKFLOW_FILE = 'https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml'; + +export function QualityPipelineFlow() { + const card = {background: 'white', border: '1px solid var(--c-cobalt-100)', borderRadius: 8, padding: '14px 14px 16px', boxShadow: '0 1px 3px rgba(33, 70, 139, 0.04)'}; + const cardTitle = {fontSize: 12, fontWeight: 700, color: 'var(--c-cobalt-900)', marginBottom: 4, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 6}; + const cardSubtitle = {fontSize: 10, fontFamily: 'var(--conduction-typography-font-family-code)', color: 'var(--c-cobalt-500, #6f7da3)', marginBottom: 10, letterSpacing: '0.04em'}; + const stageWrap = {background: 'var(--c-cobalt-50)', border: '1px solid var(--c-cobalt-100)', borderRadius: 12, padding: '18px 18px 22px'}; + const stageHead = {display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 14, gap: 12, flexWrap: 'wrap'}; + const stageLabel = {fontSize: 10, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--c-orange-knvb)', fontFamily: 'var(--conduction-typography-font-family-code)'}; + const stageTitle = {fontSize: 16, fontWeight: 700, color: 'var(--c-cobalt-900)'}; + const stageNote = {fontSize: 12, color: 'var(--c-cobalt-700)'}; + const arrowRow = {height: 24, display: 'flex', justifyContent: 'center', alignItems: 'center', color: 'var(--c-cobalt-300)', fontSize: 20, fontWeight: 700}; + const hex = (color) => ({width: 10, height: 12, clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)', background: color, flexShrink: 0}); + const list = (items, color) => ( +
    + {items.map((it, i) => ( +
  • + + {it} +
  • + ))} +
+ ); + return ( +
+
+
+
Stage 1 · parallel
Quality gates
+ Four matrix jobs, fail-fast off, every PR. +
+
+
+
PHP Qualitymatrix
+
composer scripts
+ {list(['lint', 'phpcs', 'phpmd', 'psalm', 'phpstan', 'phpmetrics'], 'var(--c-cobalt-700)')} +
+
+
Vue Qualitymatrix
+
npm scripts
+ {list(['eslint', 'stylelint'], 'var(--c-blue-cobalt)')} +
+
+
Securitymatrix
+
audit
+ {list(['composer audit', 'npm audit'], 'var(--c-orange-knvb)')} +
+
+
Licensematrix
+
SPDX allowlist
+ {list(['composer', 'npm'], 'var(--c-mint-500)')} +
+
+
+ +
↓
+ +
+
+
Stage 2 · gated
Tests
+ Only after PHP Quality + Security are green. +
+
+
+
PHPUnitmatrix
+
PHP × Nextcloud
+ {list(['php 8.3 / 8.4', 'nc stable31 / 32', 'pgsql 16 service'], 'var(--c-lavender-300)')} +
+
+
Integrationnewman
+
Postman collections
+ {list(['newman run *.postman_collection.json'], 'var(--c-lavender-300)')} +
+
+
E2Eplaywright
+
Chromium + V8 coverage
+ {list(['npx playwright test', 'spec-to-test ≥ 75%'], 'var(--c-lavender-300)')} +
+
+
+ +
↓
+ +
+
+
+
Stage 3 · always
Reporting
+
+
+
+
Quality Report
+
PDF + PR comment + step summary
+ {list(['aggregates result-* artifacts', 'pandoc → wkhtmltopdf', '90-day retention'], 'var(--c-mint-500)')} +
+
+
Coverage baseline
+
guard + auto-update
+ {list(['PR: block manual baseline edits', 'main / development: bump if improved'], 'var(--c-mint-500)')} +
+
+
+ +
+
+
Stage 4 · protected branches
Bill of Materials
+
+
+
SBOMCycloneDX
+
composer + npm, merged
+ {list(['composer CycloneDX:make-sbom', 'cyclonedx-npm', 'Grype CVE scan (fail on critical)', 'publish: artifact + release asset'], 'var(--c-forest-300)')} +
+
+
+
+ ); +} + export const QualityIcon = ( @@ -367,17 +483,38 @@ The platform combines a website vulnerability scanner, a network scanner, a CVE ## Quality workflow on GitHub -Every Conduction app lives in public on GitHub under the [ConductionNL](https://github.com/ConductionNL) organisation. The development workflow is the operational layer of the QMS — it is how the documented procedures from §7.5 and §8 actually run, with the trail from feature request through merged code visible end-to-end. +Every Conduction app lives in public on GitHub under the [ConductionNL](https://github.com/ConductionNL) organisation. The reusable [`Quality` workflow](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml) — defined once in the `.github` repo and called by every app — runs the same gate stack on every pull request: four parallel quality matrices, then a gated test stage, then reporting + a Software Bill of Materials on protected branches. + + + + + +### Stage-by-stage + +**[Stage 1 — Quality gates](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L141)** _(parallel)_. Four matrix jobs run side by side. `php-quality` covers `lint`, `phpcs`, `phpmd`, `psalm`, `phpstan`, and `phpmetrics`. `vue-quality` covers `eslint` and `stylelint`. `security` runs `composer audit` and `npm audit`. `license` checks both ecosystems' dependencies against an EUPL-1.2 / AGPL-3.0-compatible SPDX allowlist and uploads a per-ecosystem report. Each tool is its own matrix entry so a single failure points at the exact gate. + +**[Stage 2 — Tests](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L639)** _(gated)_. Only runs when Stage 1's `php-quality` and `security` jobs are green. `phpunit` spans the PHP × Nextcloud matrix (PHP 8.3/8.4 against `stable31`/`stable32`) with a real Postgres 16 service. `newman` boots the Nextcloud built-in server and drives the Postman integration collections. `playwright` runs the E2E specs against Chromium with V8 coverage collection and a 75% spec-to-test coverage gate. + +**[Stage 3 — Reporting](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L1249)** _(always)_. The `report` job downloads every `result-*` artifact, the license reports, and the coverage clover, renders a compact Markdown table per category (PHP / Vue / Security / License / Tests), converts it to PDF with `pandoc` + `wkhtmltopdf`, writes it to the GitHub step summary, posts it as a PR comment, and keeps the artifact for 90 days. On `main` / `development` pushes the `update-baseline` job lifts the coverage baseline when it improves; on PRs the `baseline-protection` job blocks manual edits to `.coverage-baseline`. + +**[Stage 4 — Software Bill of Materials](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L1522)** _(protected branches)_. After `security` passes, the `sbom` job emits a CycloneDX SBOM from composer (`CycloneDX:make-sbom`) and npm (`@cyclonedx/cyclonedx-npm`), merges them, runs Grype to fail on critical CVEs, and publishes the SBOM as a 90-day workflow artifact plus a release asset on every tag. -**Branch protection.** Three org-wide rulesets enforce the same rules everywhere: at least one approving review to merge into the `development` branch, at least two reviews to merge into `main`. Direct pushes to protected branches are blocked. Every change moves through a pull request. +### Around the pipeline -**Two-track review.** Pull requests pick up the right reviewer through labels. The `code-review:queued` label triggers a code review against the project's coding standards (PHPCS, PHPMD, Psalm, PHPStan for PHP apps; ruff + mypy for Python ExApps; ESLint + Vue rules for frontends). The `security-review:queued` label triggers an information-security review against the relevant ISO 27001:2022 Annex A controls. +**Branch protection.** Three org-wide rulesets enforce the same rules everywhere: at least one approving review to merge into `development`, at least two reviews to merge into `main`. Direct pushes to protected branches are blocked. -**Hydra automation.** Routine review work runs through Hydra, our internal coordination layer — it watches PR labels, dispatches review jobs to specialised agents, and writes the findings back as PR comments. The trail of who reviewed what, with which feedback, lives in the PR history. Human reviewers approve before merge. +**Two-track review.** Pull requests pick up the right reviewer through labels — `code-review:queued` triggers the code-quality review against PHPCS / PHPMD / Psalm / PHPStan (or `ruff`/`mypy`/ESLint per stack); `security-review:queued` triggers an information-security review against the relevant ISO 27001:2022 Annex A controls. -**Spec-driven changes.** Larger changes carry an OpenSpec change folder and an ADR. The spec lives next to the code, the ADR records the architectural decision and its rationale, and the PR cites both. That gives the documentation trail required by ISO 9001:2015 §7.5 (control of documented information) without a parallel quality-handbook to keep in sync. +**Hydra automation.** Routine review runs through Hydra — it watches PR labels, dispatches review jobs to specialised agents, and writes findings back as PR comments. Human reviewers approve before merge. -**Automated CI gates.** Every PR runs the language-appropriate linters, static analysers, unit tests, and dependency-vulnerability scans before a human reviewer is asked to look. PRs that fail a gate are blocked until fixed. The CI configuration is in the repository, so the gates are visible to anyone reading the code. +**Spec-driven changes.** Larger changes carry an OpenSpec change folder and an ADR. Spec next to the code, ADR for the architectural decision, PR cites both. That gives the documentation trail required by ISO 9001:2015 §7.5 without a parallel quality-handbook to keep in sync.
From e8f757eef435c151638e582017366a499d3cada3 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 20 May 2026 09:36:45 +0200 Subject: [PATCH 059/330] feat(quality): per-pill tooltips + drop the redundant Stage-by-stage prose (#88) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flow illustration already says what each tool does; the prose under 'Stage-by-stage' just repeated it. Replace it with on-hover tooltips on every pill — package name + one-line description (PHP CodeSniffer, Psalm, PHPStan, Grype, CycloneDX plugins, etc.) — so the data is on the schema itself, not duplicated as paragraphs underneath. --- .../quality.mdx | 82 +++++++++++++------ src/pages/quality.mdx | 82 +++++++++++++------ 2 files changed, 112 insertions(+), 52 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index e96af9d9b..d54063901 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -21,12 +21,15 @@ export function QualityPipelineFlow() { const hex = (color) => ({width: 10, height: 12, clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)', background: color, flexShrink: 0}); const list = (items, color) => (
    - {items.map((it, i) => ( -
  • - - {it} -
  • - ))} + {items.map((it, i) => { + const [label, tip] = Array.isArray(it) ? it : [it, null]; + return ( +
  • + + {label} +
  • + ); + })}
); return ( @@ -40,22 +43,38 @@ export function QualityPipelineFlow() {
PHP Qualitymatrix
composer scripts
- {list(['lint', 'phpcs', 'phpmd', 'psalm', 'phpstan', 'phpmetrics'], 'var(--c-cobalt-700)')} + {list([ + ['lint', 'PHP -l syntax-lint via `composer lint`. Controleert dat elk PHP-bestand parseert voordat de zwaardere analyses draaien.'], + ['phpcs', 'squizlabs/php_codesniffer. Dwingt de coding standard uit phpcs.xml af (meestal PSR-12-basis plus de Nextcloud-ruleset).'], + ['phpmd', 'phpmd/phpmd. Mess Detector. Vindt problematische patronen: te lange methodes, hoge complexiteit, ongebruikte variabelen, naming-violations.'], + ['psalm', 'vimeo/psalm. Statische type-checker. Vangt type-mismatches, null-dereferences, dode code, ontbrekende return types.'], + ['phpstan', 'phpstan/phpstan. Statische analyser. Vindt type-fouten, ongedefinieerde methodes, onbereikbare branches; level per app in phpstan.neon.'], + ['phpmetrics', 'phpmetrics/phpmetrics. Genereert een code-metrics-rapport (maintainability index, cyclomatic complexity, LOC). Output als artifact.'], + ], 'var(--c-cobalt-700)')}
Vue Qualitymatrix
npm scripts
- {list(['eslint', 'stylelint'], 'var(--c-blue-cobalt)')} + {list([ + ['eslint', 'eslint. JavaScript/TypeScript/Vue-linter. Project-regels in eslint.config.js (flat config) of .eslintrc.'], + ['stylelint', 'stylelint. CSS/SCSS-linter. Project-regels in stylelint.config.js. Dwingt token-gebruik af en verbiedt hardgecodeerde brand-kleuren.'], + ], 'var(--c-blue-cobalt)')}
Securitymatrix
audit
- {list(['composer audit', 'npm audit'], 'var(--c-orange-knvb)')} + {list([ + ['composer audit', 'composer audit --format=plain --abandoned=report. Toetst geïnstalleerde packages aan de FriendsOfPHP/security-advisories-database.'], + ['npm audit', 'npm audit --audit-level=critical --omit=dev. Toetst productie-deps aan de GitHub Advisory Database; faalt op kritieke CVE\'s.'], + ], 'var(--c-orange-knvb)')}
Licensematrix
SPDX-allowlist
- {list(['composer', 'npm'], 'var(--c-mint-500)')} + {list([ + ['composer', 'composer licenses --format=json door een SPDX-allowlist (EUPL-1.2 / AGPL-3.0 compatibel). Per-package uitzonderingen via .license-overrides.json. Levert markdown + JSON-rapport.'], + ['npm', 'npm-package license-checker. Zelfde SPDX-allowlist + .license-overrides.json als de composer-kant. Levert markdown + JSON-rapport als artifact.'], + ], 'var(--c-mint-500)')}
@@ -71,17 +90,26 @@ export function QualityPipelineFlow() {
PHPUnitmatrix
PHP × Nextcloud
- {list(['php 8.3 / 8.4', 'nc stable31 / 32', 'pgsql 16 service'], 'var(--c-lavender-300)')} + {list([ + ['php 8.3 / 8.4', 'Twee PHP-versies in de test-matrix, opgezet via shivammathur/setup-php@v2. Beide versies moeten slagen.'], + ['nc stable31 / 32', 'Twee Nextcloud server-branches (stable31 + stable32) uitgechecked uit nextcloud/server. PHPUnit draait tegen beide; beide moeten slagen.'], + ['pgsql 16 service', 'Een echte Postgres 16-container als GitHub Actions-service (postgres:16 image, health-checked met pg_isready voor de tests draaien).'], + ], 'var(--c-lavender-300)')}
Integratienewman
Postman-collections
- {list(['newman run *.postman_collection.json'], 'var(--c-lavender-300)')} + {list([ + ['newman run *.postman_collection.json', 'Newman 6 (postmanlabs/newman) — de Postman CLI test-runner. Draait elke *.postman_collection.json onder tests/integration tegen de Nextcloud built-in server op :8080.'], + ], 'var(--c-lavender-300)')}
E2Eplaywright
Chromium + V8-coverage
- {list(['npx playwright test', 'spec-to-test ≥ 75%'], 'var(--c-lavender-300)')} + {list([ + ['npx playwright test', '@playwright/test op headless Chromium. Draait de *.spec.ts-bestanden onder tests/e2e tegen de actieve Nextcloud op http://localhost:8080.'], + ['spec-to-test ≥ 75%', 'Maatwerk coverage-gate. Loopt openspec/specs + openspec/changes door, telt scenarios (S1, S2, REQ-…) en matcht ze tegen test()-calls in de Playwright-suites. Waarschuwt onder 75%.'], + ], 'var(--c-lavender-300)')}
@@ -97,12 +125,19 @@ export function QualityPipelineFlow() {
Kwaliteitsrapport
PDF + PR-comment + step summary
- {list(['aggregeert result-* artifacts', 'pandoc → wkhtmltopdf', '90 dagen bewaard'], 'var(--c-mint-500)')} + {list([ + ['aggregeert result-* artifacts', 'Gebruikt actions/download-artifact@v4 met pattern result-*. Elke matrix-job uploadt zijn job.status naar zo\'n artifact aan het einde van de run.'], + ['pandoc → wkhtmltopdf', 'pandoc zet de gegenereerde quality-report.md om naar PDF via de wkhtmltopdf-engine (op de runner geïnstalleerd via apt-get).'], + ['90 dagen bewaard', 'actions/upload-artifact@v4 met retention-days: 90. De PDF + Markdown blijven drie maanden lang downloadbaar vanuit de workflow-run.'], + ], 'var(--c-mint-500)')}
Coverage-baseline
guard + auto-update
- {list(['PR: handmatige edits geblokt', 'main / development: bijwerken als beter'], 'var(--c-mint-500)')} + {list([ + ['PR: handmatige edits geblokt', 'baseline-protection-job: diffs de PR tegen de base-branch en faalt als .coverage-baseline in de gewijzigde bestanden staat. Voorkomt dat iemand de lat stilletjes verlaagt.'], + ['main / development: bijwerken als beter', 'update-baseline-job: draait php scripts/coverage-guard.php --update-baseline na PHPUnit; commit de nieuwe baseline alleen als coverage daadwerkelijk verbeterde.'], + ], 'var(--c-mint-500)')}
@@ -114,7 +149,12 @@ export function QualityPipelineFlow() {
SBOMCycloneDX
composer + npm, samengevoegd
- {list(['composer CycloneDX:make-sbom', 'cyclonedx-npm', 'Grype CVE-scan (fail op critical)', 'publiceer: artifact + release-asset'], 'var(--c-forest-300)')} + {list([ + ['composer CycloneDX:make-sbom', 'cyclonedx/cyclonedx-php-composer-plugin. Genereert een CycloneDX 1.5 JSON-SBOM uit composer.lock (--omit=dev, --omit=plugin).'], + ['cyclonedx-npm', '@cyclonedx/cyclonedx-npm. Genereert een CycloneDX 1.5 JSON-SBOM uit package-lock.json (--package-lock-only, --omit dev). PHP- en npm-SBOMs worden samengevoegd met jq.'], + ['Grype CVE-scan (fail op critical)', 'anchore/grype, geïnstalleerd via anchore/scan-action/download-grype@v5. Scant de samengevoegde SBOM tegen de Grype-vulnerability-database; faalt op severity critical.'], + ['publiceer: artifact + release-asset', 'Geüpload als actions/upload-artifact@v4 (90 dagen retentie) bij elke push naar een beschermde branch, en als release-asset gehangen aan de GitHub Release via softprops/action-gh-release@v2 bij elke tag-push.'], + ], 'var(--c-forest-300)')}
@@ -435,16 +475,6 @@ Elke Conduction-app staat publiek op GitHub onder de [ConductionNL](https://gith
-### Fase voor fase - -**[Fase 1 — Kwaliteitsgates](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L141)** _(parallel)_. Vier matrix-jobs draaien naast elkaar. `php-quality` dekt `lint`, `phpcs`, `phpmd`, `psalm`, `phpstan` en `phpmetrics`. `vue-quality` dekt `eslint` en `stylelint`. `security` draait `composer audit` en `npm audit`. `license` toetst dependencies in beide ecosystemen tegen een EUPL-1.2 / AGPL-3.0-compatibele SPDX-allowlist en uploadt een rapport per ecosysteem. Elke tool is een aparte matrix-entry, dus een enkele failure wijst naar de exacte gate. - -**[Fase 2 — Tests](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L639)** _(gated)_. Loopt pas als de `php-quality`- en `security`-jobs uit Fase 1 groen zijn. `phpunit` spant de PHP × Nextcloud-matrix (PHP 8.3/8.4 tegen `stable31`/`stable32`) met een echte Postgres 16-service. `newman` start de Nextcloud built-in server en draait de Postman-integratiecollections. `playwright` draait de E2E-specs tegen Chromium met V8-coverage en een spec-to-test-coverage gate van 75%. - -**[Fase 3 — Rapportage](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L1249)** _(altijd)_. De `report`-job downloadt elke `result-*`-artifact, de license-rapporten en de coverage-clover, rendert een compacte Markdown-tabel per categorie (PHP / Vue / Security / License / Tests), zet die om naar PDF via `pandoc` + `wkhtmltopdf`, schrijft hem naar de GitHub-stepsummary, post hem als PR-comment en bewaart de artifact 90 dagen. Op `main`- / `development`-pushes tilt de `update-baseline`-job de coverage-baseline op als die verbetert; op PRs blokkeert de `baseline-protection`-job handmatige edits aan `.coverage-baseline`. - -**[Fase 4 — Software Bill of Materials](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L1522)** _(beschermde branches)_. Nadat `security` slaagt, genereert de `sbom`-job een CycloneDX-SBOM uit composer (`CycloneDX:make-sbom`) en npm (`@cyclonedx/cyclonedx-npm`), voegt ze samen, draait Grype om te falen op kritieke CVE's en publiceert de SBOM als een 90-dagen-artifact plus als release-asset bij elke tag. - ### Om de pipeline heen **Branch protection.** Drie organisatie-brede rulesets dwingen overal dezelfde regels af: ten minste één goedkeurende review om naar `development` te mergen, ten minste twee reviews om naar `main` te mergen. Directe pushes naar beschermde branches zijn geblokkeerd. diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index 6d9b2e11d..f1acd57b1 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -21,12 +21,15 @@ export function QualityPipelineFlow() { const hex = (color) => ({width: 10, height: 12, clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)', background: color, flexShrink: 0}); const list = (items, color) => (
    - {items.map((it, i) => ( -
  • - - {it} -
  • - ))} + {items.map((it, i) => { + const [label, tip] = Array.isArray(it) ? it : [it, null]; + return ( +
  • + + {label} +
  • + ); + })}
); return ( @@ -40,22 +43,38 @@ export function QualityPipelineFlow() {
PHP Qualitymatrix
composer scripts
- {list(['lint', 'phpcs', 'phpmd', 'psalm', 'phpstan', 'phpmetrics'], 'var(--c-cobalt-700)')} + {list([ + ['lint', 'PHP -l syntax lint via `composer lint`. Validates that every PHP file parses without errors before any heavier static analysis runs.'], + ['phpcs', 'squizlabs/php_codesniffer. Enforces the coding standard configured in phpcs.xml (typically a PSR-12 baseline plus the Nextcloud ruleset).'], + ['phpmd', 'phpmd/phpmd. Mess Detector. Finds problematic code patterns: long methods, excessive complexity, unused locals, naming-rule violations.'], + ['psalm', 'vimeo/psalm. Static type checker. Catches type mismatches, null dereferences, dead code, missing return types.'], + ['phpstan', 'phpstan/phpstan. Static analyser. Finds type errors, undefined methods, unreachable branches; level configured per app in phpstan.neon.'], + ['phpmetrics', 'phpmetrics/phpmetrics. Generates a code metrics report (maintainability index, cyclomatic complexity, lines of code). Output uploaded as an artifact.'], + ], 'var(--c-cobalt-700)')}
Vue Qualitymatrix
npm scripts
- {list(['eslint', 'stylelint'], 'var(--c-blue-cobalt)')} + {list([ + ['eslint', 'eslint. JavaScript/TypeScript/Vue linter. Project rules in eslint.config.js (flat config) or .eslintrc.'], + ['stylelint', 'stylelint. CSS/SCSS linter. Project rules in stylelint.config.js. Enforces token usage and disallows hardcoded brand colours.'], + ], 'var(--c-blue-cobalt)')}
Securitymatrix
audit
- {list(['composer audit', 'npm audit'], 'var(--c-orange-knvb)')} + {list([ + ['composer audit', 'composer audit --format=plain --abandoned=report. Checks installed packages against the FriendsOfPHP/security-advisories database.'], + ['npm audit', 'npm audit --audit-level=critical --omit=dev. Checks production deps against the GitHub Advisory Database; fails the gate on critical CVEs.'], + ], 'var(--c-orange-knvb)')}
Licensematrix
SPDX allowlist
- {list(['composer', 'npm'], 'var(--c-mint-500)')} + {list([ + ['composer', 'composer licenses --format=json piped through an SPDX allowlist (EUPL-1.2 / AGPL-3.0 compatible). Per-package exceptions via .license-overrides.json. Emits a markdown + JSON report.'], + ['npm', 'license-checker npm package. Same SPDX allowlist + .license-overrides.json model as the composer side. Emits a markdown + JSON report uploaded as an artifact.'], + ], 'var(--c-mint-500)')}
@@ -71,17 +90,26 @@ export function QualityPipelineFlow() {
PHPUnitmatrix
PHP × Nextcloud
- {list(['php 8.3 / 8.4', 'nc stable31 / 32', 'pgsql 16 service'], 'var(--c-lavender-300)')} + {list([ + ['php 8.3 / 8.4', 'Two PHP versions in the test matrix, set up via shivammathur/setup-php@v2. Both versions must pass for the matrix job to be green.'], + ['nc stable31 / 32', 'Two Nextcloud server branches (stable31 + stable32) checked out from nextcloud/server. PHPUnit runs against each, both must pass.'], + ['pgsql 16 service', 'A real Postgres 16 container as a GitHub Actions service (postgres:16 image, health-checked with pg_isready before tests run).'], + ], 'var(--c-lavender-300)')}
Integrationnewman
Postman collections
- {list(['newman run *.postman_collection.json'], 'var(--c-lavender-300)')} + {list([ + ['newman run *.postman_collection.json', 'Newman 6 (postmanlabs/newman) — the Postman CLI test runner. Runs every *.postman_collection.json under tests/integration against the Nextcloud built-in server on :8080.'], + ], 'var(--c-lavender-300)')}
E2Eplaywright
Chromium + V8 coverage
- {list(['npx playwright test', 'spec-to-test ≥ 75%'], 'var(--c-lavender-300)')} + {list([ + ['npx playwright test', '@playwright/test driving headless Chromium. Runs the *.spec.ts files under tests/e2e against the running Nextcloud at http://localhost:8080.'], + ['spec-to-test ≥ 75%', 'Custom coverage gate. Walks openspec/specs + openspec/changes, counts scenarios (S1, S2, REQ-…) and matches them against test() calls in the Playwright suites. Warns when coverage drops below 75%.'], + ], 'var(--c-lavender-300)')}
@@ -97,12 +125,19 @@ export function QualityPipelineFlow() {
Quality Report
PDF + PR comment + step summary
- {list(['aggregates result-* artifacts', 'pandoc → wkhtmltopdf', '90-day retention'], 'var(--c-mint-500)')} + {list([ + ['aggregates result-* artifacts', 'Uses actions/download-artifact@v4 with pattern result-*. Every matrix job uploads its job.status to one of those artifacts at the end of its run.'], + ['pandoc → wkhtmltopdf', 'pandoc converts the generated quality-report.md to PDF via the wkhtmltopdf engine (installed on the runner with apt-get).'], + ['90-day retention', 'actions/upload-artifact@v4 with retention-days: 90. The PDF + Markdown stay downloadable from the workflow run for three months.'], + ], 'var(--c-mint-500)')}
Coverage baseline
guard + auto-update
- {list(['PR: block manual baseline edits', 'main / development: bump if improved'], 'var(--c-mint-500)')} + {list([ + ['PR: block manual baseline edits', 'baseline-protection job: diffs the PR against the base branch and fails if .coverage-baseline appears in the changed files. Stops anyone from quietly lowering the bar.'], + ['main / development: bump if improved', 'update-baseline job: runs php scripts/coverage-guard.php --update-baseline after PHPUnit; commits the new baseline only when coverage actually improved.'], + ], 'var(--c-mint-500)')}
@@ -114,7 +149,12 @@ export function QualityPipelineFlow() {
SBOMCycloneDX
composer + npm, merged
- {list(['composer CycloneDX:make-sbom', 'cyclonedx-npm', 'Grype CVE scan (fail on critical)', 'publish: artifact + release asset'], 'var(--c-forest-300)')} + {list([ + ['composer CycloneDX:make-sbom', 'cyclonedx/cyclonedx-php-composer plugin. Emits a CycloneDX 1.5 JSON SBOM from composer.lock (--omit=dev, --omit=plugin).'], + ['cyclonedx-npm', '@cyclonedx/cyclonedx-npm. Emits a CycloneDX 1.5 JSON SBOM from package-lock.json (--package-lock-only, --omit dev). PHP + npm SBOMs are merged with jq.'], + ['Grype CVE scan (fail on critical)', 'anchore/grype, installed via anchore/scan-action/download-grype@v5. Scans the merged SBOM against the Grype vulnerability database; fails the job on severity critical.'], + ['publish: artifact + release asset', 'Uploaded as an actions/upload-artifact@v4 (90-day retention) on every protected-branch push, and attached to the GitHub Release via softprops/action-gh-release@v2 on every tag push.'], + ], 'var(--c-forest-300)')}
@@ -496,16 +536,6 @@ Every Conduction app lives in public on GitHub under the [ConductionNL](https://
-### Stage-by-stage - -**[Stage 1 — Quality gates](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L141)** _(parallel)_. Four matrix jobs run side by side. `php-quality` covers `lint`, `phpcs`, `phpmd`, `psalm`, `phpstan`, and `phpmetrics`. `vue-quality` covers `eslint` and `stylelint`. `security` runs `composer audit` and `npm audit`. `license` checks both ecosystems' dependencies against an EUPL-1.2 / AGPL-3.0-compatible SPDX allowlist and uploads a per-ecosystem report. Each tool is its own matrix entry so a single failure points at the exact gate. - -**[Stage 2 — Tests](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L639)** _(gated)_. Only runs when Stage 1's `php-quality` and `security` jobs are green. `phpunit` spans the PHP × Nextcloud matrix (PHP 8.3/8.4 against `stable31`/`stable32`) with a real Postgres 16 service. `newman` boots the Nextcloud built-in server and drives the Postman integration collections. `playwright` runs the E2E specs against Chromium with V8 coverage collection and a 75% spec-to-test coverage gate. - -**[Stage 3 — Reporting](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L1249)** _(always)_. The `report` job downloads every `result-*` artifact, the license reports, and the coverage clover, renders a compact Markdown table per category (PHP / Vue / Security / License / Tests), converts it to PDF with `pandoc` + `wkhtmltopdf`, writes it to the GitHub step summary, posts it as a PR comment, and keeps the artifact for 90 days. On `main` / `development` pushes the `update-baseline` job lifts the coverage baseline when it improves; on PRs the `baseline-protection` job blocks manual edits to `.coverage-baseline`. - -**[Stage 4 — Software Bill of Materials](https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml#L1522)** _(protected branches)_. After `security` passes, the `sbom` job emits a CycloneDX SBOM from composer (`CycloneDX:make-sbom`) and npm (`@cyclonedx/cyclonedx-npm`), merges them, runs Grype to fail on critical CVEs, and publishes the SBOM as a 90-day workflow artifact plus a release asset on every tag. - ### Around the pipeline **Branch protection.** Three org-wide rulesets enforce the same rules everywhere: at least one approving review to merge into `development`, at least two reviews to merge into `main`. Direct pushes to protected branches are blocked. From d4d48aa870ce201ce2f611b8a756b5f4ea52376f Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 20 May 2026 09:59:38 +0200 Subject: [PATCH 060/330] feat(quality): present 'Around the pipeline' as a bullet list (matches identity-kit visual) (#89) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the h2-then-ul-then-li-with-strong-prefix Troubleshooting pattern from identity.conduction.nl/product-pages/integrations.html. Converts the four prose paragraphs (Branch protection, Two-track review, Hydra automation, Spec-driven changes) into a tight bulleted list with bold lead-ins and em-dash separators. Lifting this into a dedicated design-system component is a @conduction/docusaurus-preset release task — the npm-published preset is on a different version train from the local design-system source — so filed as follow-up rather than blocking this rewrite. --- i18n/nl/docusaurus-plugin-content-pages/quality.mdx | 11 ++++------- src/pages/quality.mdx | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index d54063901..913b5934c 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -477,13 +477,10 @@ Elke Conduction-app staat publiek op GitHub onder de [ConductionNL](https://gith ### Om de pipeline heen -**Branch protection.** Drie organisatie-brede rulesets dwingen overal dezelfde regels af: ten minste één goedkeurende review om naar `development` te mergen, ten minste twee reviews om naar `main` te mergen. Directe pushes naar beschermde branches zijn geblokkeerd. - -**Tweesporen-review.** Pull requests krijgen de juiste reviewer via labels — `code-review:queued` start de code-review tegen PHPCS / PHPMD / Psalm / PHPStan (of `ruff`/`mypy`/ESLint per stack); `security-review:queued` start een informatiebeveiligingsreview tegen de relevante ISO 27001:2022 Annex A-controls. - -**Hydra-automatisering.** Routinematig review-werk loopt via Hydra — kijkt naar PR-labels, stuurt review-jobs naar gespecialiseerde agents en schrijft bevindingen terug als PR-comments. Een menselijke reviewer keurt goed voor de merge. - -**Spec-gedreven wijzigingen.** Grotere wijzigingen dragen een OpenSpec change-folder en een ADR. Spec naast de code, ADR voor de architecturale beslissing, PR verwijst naar beide. Dat levert de documentatiesporen die ISO 9001:2015 §7.5 vraagt — zonder een parallel kwaliteitshandboek bij te houden. +- **Branch protection** — drie organisatie-brede rulesets dwingen overal dezelfde regels af: ten minste één goedkeurende review om naar `development` te mergen, ten minste twee reviews om naar `main` te mergen. Directe pushes naar beschermde branches zijn geblokkeerd. +- **Tweesporen-review** — pull requests krijgen de juiste reviewer via labels: `code-review:queued` start de code-review tegen PHPCS / PHPMD / Psalm / PHPStan (of `ruff` / `mypy` / ESLint per stack); `security-review:queued` start een informatiebeveiligingsreview tegen de relevante ISO 27001:2022 Annex A-controls. +- **Hydra-automatisering** — routinematig review-werk loopt via Hydra, die naar PR-labels kijkt, review-jobs naar gespecialiseerde agents stuurt en bevindingen terugschrijft als PR-comments. Een menselijke reviewer keurt goed voor de merge. +- **Spec-gedreven wijzigingen** — grotere wijzigingen dragen een OpenSpec change-folder en een ADR mee. Spec naast de code, ADR voor de architecturale beslissing, PR verwijst naar beide — de documentatiesporen die ISO 9001:2015 §7.5 vraagt, zonder een parallel kwaliteitshandboek bij te houden.
diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index f1acd57b1..eca42f787 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -538,13 +538,10 @@ Every Conduction app lives in public on GitHub under the [ConductionNL](https:// ### Around the pipeline -**Branch protection.** Three org-wide rulesets enforce the same rules everywhere: at least one approving review to merge into `development`, at least two reviews to merge into `main`. Direct pushes to protected branches are blocked. - -**Two-track review.** Pull requests pick up the right reviewer through labels — `code-review:queued` triggers the code-quality review against PHPCS / PHPMD / Psalm / PHPStan (or `ruff`/`mypy`/ESLint per stack); `security-review:queued` triggers an information-security review against the relevant ISO 27001:2022 Annex A controls. - -**Hydra automation.** Routine review runs through Hydra — it watches PR labels, dispatches review jobs to specialised agents, and writes findings back as PR comments. Human reviewers approve before merge. - -**Spec-driven changes.** Larger changes carry an OpenSpec change folder and an ADR. Spec next to the code, ADR for the architectural decision, PR cites both. That gives the documentation trail required by ISO 9001:2015 §7.5 without a parallel quality-handbook to keep in sync. +- **Branch protection** — three org-wide rulesets enforce the same rules everywhere: at least one approving review to merge into `development`, at least two reviews to merge into `main`. Direct pushes to protected branches are blocked. +- **Two-track review** — pull requests pick up the right reviewer through labels: `code-review:queued` triggers the code-quality review against PHPCS / PHPMD / Psalm / PHPStan (or `ruff` / `mypy` / ESLint per stack); `security-review:queued` triggers an information-security review against the relevant ISO 27001:2022 Annex A controls. +- **Hydra automation** — routine review runs through Hydra, which watches PR labels, dispatches review jobs to specialised agents, and writes findings back as PR comments. Human reviewers approve before merge. +- **Spec-driven changes** — larger changes carry an OpenSpec change folder and an ADR. Spec next to the code, ADR for the architectural decision, PR cites both — the documentation trail required by ISO 9001:2015 §7.5 without a parallel quality handbook to keep in sync.
From a04a7d0224bf471e14eecdaaf59337d85dbdff7f Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 20 May 2026 10:15:59 +0200 Subject: [PATCH 061/330] feat(quality): present 'How We Work' as a of six handbook sections (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the inline 'Sections.' prose run with the preset's component, mirroring the components shown on identity.conduction.nl. Each card has a coloured hex icon, monospace metadata, an h3 title, and a one-sentence description: Identity brand · voice · diagrams Way of Work onboarding · roles · support Hydra spec → builder → review → merge Claude workflow skills · parallel agents · spec-driven ISO compliance 9001 + 27001 clause map (orange accent) Roadmap GitHub Projects: next · in flight · shipped Section flow is now: intro paragraph → six-card shelf → two CTAs ('Open the handbook' + 'Start with Way of Work'). The 'How it fits the management system' prose is folded into the shelf's lede so the page stays compact. Both locales updated; identical six-card structure; NL labels translated. --- .../quality.mdx | 88 ++++++++++++++++++- src/pages/quality.mdx | 88 ++++++++++++++++++- 2 files changed, 170 insertions(+), 6 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index 913b5934c..b06250920 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -4,7 +4,7 @@ description: Het kwaliteitsmanagementsysteem van Conduction. ISO 9001:2015 en IS hide_table_of_contents: true --- -import {DetailHero, Section, SectionHead, Showcase, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; +import {DetailHero, Section, SectionHead, Showcase, WidgetShelf, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; export const WORKFLOW_FILE = 'https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml'; @@ -169,6 +169,41 @@ export const QualityIcon = ( ); +export function HandbookPanel({iconPath, title, lines, accent}) { + return ( +
+ +
{title}
+
    + {lines.map((l, i) => ( +
  • {l}
  • + ))} +
+
+ ); +} + +export const HANDBOOK_ICONS = { + identity: <>, + wayOfWork: <>, + hydra: <>, + claude: <>, + iso: <>, + roadmap: <>, +}; + export const QualityHeroIllustration = (
-**Hoe past het in het managementsysteem.** Elke operationele procedure staat in versiebeheer op GitHub en loopt door hetzelfde PR-+-reviewproces dat hieronder onder [Kwaliteitsworkflow op GitHub](#github-workflow) beschreven staat. Auditors lezen dezelfde bron als elke medewerker — geen parallel handboek om bij te houden. +, + }, + { + title: 'Way of Work', + desc: 'Hoe Conduction dagelijks werkt — onboarding, rollen, software bouwen en klantondersteuning. Het handboek dat elke (digitale) medewerker volgt.', + panel: , + }, + { + title: 'Hydra', + desc: 'De spec-gedreven CI/CD-pipeline van Conduction. Van OpenSpec-wijziging tot gereviewde PR — Builder, parallelle code- + security-review, met een mens in de loop.', + panel: , + }, + { + title: 'Claude-workflow', + desc: 'Spec-gedreven ontwikkeling met OpenSpec, GitHub Issues en Claude Code. Skills, commands, conventies, parallelle agents.', + panel: , + }, + { + title: 'ISO-compliance', + desc: 'Engineering-pipeline gemapt op ISO/IEC 9001:2015 en 27001:2022 clause-by-clause, met gaten als first-class output.', + panel: , + }, + { + title: 'Roadmap', + desc: 'Waar het Conduction-app-ecosysteem heen gaat — live op GitHub Projects. Wat volgt, wat loopt, wat is opgeleverd.', + panel: , + }, + ]} +/> + +
+ +
diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index eca42f787..fb7ef5f90 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -4,7 +4,7 @@ description: Conduction's quality management system. ISO 9001:2015 and ISO 27001 hide_table_of_contents: true --- -import {DetailHero, Section, SectionHead, Showcase, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; +import {DetailHero, Section, SectionHead, Showcase, WidgetShelf, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; export const WORKFLOW_FILE = 'https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml'; @@ -169,6 +169,41 @@ export const QualityIcon = ( ); +export function HandbookPanel({iconPath, title, lines, accent}) { + return ( +
+ +
{title}
+
    + {lines.map((l, i) => ( +
  • {l}
  • + ))} +
+
+ ); +} + +export const HANDBOOK_ICONS = { + identity: <>, + wayOfWork: <>, + hydra: <>, + claude: <>, + iso: <>, + roadmap: <>, +}; + export const QualityHeroIllustration = (
-**How it fits the management system.** Every operating procedure is in version control on GitHub and follows the same PR + review process described under [Quality workflow on GitHub](#github-workflow) below. Auditors read the same source every employee does — there is no parallel handbook to keep in sync. +, + }, + { + title: 'Way of Work', + desc: 'How Conduction operates day-to-day — onboarding, roles, building software, and customer support. The handbook every (digital) employee follows.', + panel: , + }, + { + title: 'Hydra', + desc: "Conduction's agentic spec-driven CI/CD pipeline. From an OpenSpec change to a reviewed PR — Builder, parallel code + security review, human in the loop.", + panel: , + }, + { + title: 'Claude workflow', + desc: 'Spec-driven development with OpenSpec, GitHub Issues, and Claude Code. Skills, commands, conventions, parallel agents.', + panel: , + }, + { + title: 'ISO compliance', + desc: 'Engineering pipeline mapped to ISO/IEC 9001:2015 and 27001:2022 clause-by-clause, with gaps surfaced as a first-class output.', + panel: , + }, + { + title: 'Roadmap', + desc: "Where the Conduction app ecosystem is heading — tracked live on GitHub Projects. What's next, what's in flight, what's shipped.", + panel: , + }, + ]} +/> + +
+ +
From 54dddf506f3897469d8acdfd7464ff91e1b88292 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 20 May 2026 10:32:53 +0200 Subject: [PATCH 062/330] feat(quality): fold How We Work heading into the WidgetShelf (#91) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two headings + two paragraphs (the outer h2 'How We Work — operating handbook' + the WidgetShelf's 'Six sections, one source of truth.') sat back-to-back with a wide gap between them. Merged into a single shelf header: 'How We Work — operating handbook.' with a combined lede that covers both the 'translates ISO commitments into procedures' framing and the 'lives in version control / same PR + review' guarantee. Outer Section wrapper dropped — the WidgetShelf supplies its own padding. --- i18n/nl/docusaurus-plugin-content-pages/quality.mdx | 11 ++--------- src/pages/quality.mdx | 11 ++--------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index b06250920..69542c8d1 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -467,18 +467,11 @@ Voor meer informatie over ons kwaliteits- en informatieveiligheidssysteem kunt u
-
- -## How We Work — operationeel handboek - -Het ISO-beleid zegt waartoe Conduction zich verbindt. Het handboek op **[docs.conduction.nl](https://docs.conduction.nl/)** vertaalt die toezeggingen naar de procedures die elke (digitale) medewerker dagelijks volgt — hoe we onboarden, hoe we software bouwen, hoe we customer support draaien, hoe de Hydra-pipeline loopt, hoe de Claude-workflow elke wijziging beoordeelt. Het is de operationele laag die het managementsysteem in praktijk brengt. - -
-
- -## How We Work — operating handbook - -The ISO policies say what Conduction is committed to. The handbook at **[docs.conduction.nl](https://docs.conduction.nl/)** translates those commitments into the procedures every (digital) employee actually follows day to day — how we onboard, how we build software, how we run customer support, how the Hydra pipeline operates, how the Claude workflow gates change. It is the operational layer that makes the management system real. - -
Date: Wed, 20 May 2026 11:10:45 +0200 Subject: [PATCH 063/330] =?UTF-8?q?feat(quality):=20pentest=20section=20?= =?UTF-8?q?=E2=80=94=20daily=20cadence,=20drop=20ISMS-fit=20+=20contractin?= =?UTF-8?q?g=20(#92)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrites the Pentest tools section to lead with the actual cadence: we run pentests against every application daily, and triage findings within 8 business hours. Drops the 'How it fits the ISMS' paragraph and the 'available under NDA — write to info@conduction.nl with your contract reference' bit — contracting belongs on /support, not on a quality overview page. Consistency updates: - Hero illustration row: 'weekly scan' → 'daily scan' - Showcase tile summary: same daily / 8-business-hours framing - FAQ 'How often do you run the pentests': daily + 8 BH SLA Both locales updated. --- i18n/nl/docusaurus-plugin-content-pages/quality.mdx | 12 +++++------- src/pages/quality.mdx | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index 69542c8d1..7d7197a43 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -244,7 +244,7 @@ export const QualityHeroIllustration = ( LIVE
- pentest-tools.com · wekelijkse scan + pentest-tools.com · dagelijkse scan SCHOON
@@ -335,7 +335,7 @@ export const QualityHeroIllustration = ( { id: 'pentest', title: 'Pentest-tools.com — Doorlopende externe scans.', - summary: "Conduction gebruikt het commerciële SaaS-platform pentest-tools.com om geplande penetratietesten te draaien tegen onze publieke vlakken en de managed Common Ground-omgeving. De scans dekken de OWASP Top 10, bekende CVE's, TLS-hygiëne en kwetsbaarheden in webapplicaties af. Bevindingen lopen via ons standaard incidentproces, met kritieke en hoge CVE's binnen 30 dagen gepatcht conform ISO 27001:2022 Annex A.8.8.", + summary: "Conduction draait dagelijks penetratietests tegen elke applicatie via het commerciële SaaS-scanplatform pentest-tools.com. De dekking beslaat de OWASP Top 10, bekende CVE's, TLS-hygiëne, netwerk-exposure en subdomain enumeration. Komt er een bevinding boven, dan triëren we hem en starten de opvolging binnen 8 werkuren.", cta: {label: 'Bekijk hoe we scannen', href: '#pentest-tools'}, panel: (
@@ -524,11 +524,9 @@ Voor meer informatie over ons kwaliteits- en informatieveiligheidssysteem kunt u ## Pentest-tools -Doorlopende externe borging. Conduction heeft een abonnement op het commerciële SaaS-scanplatform **[pentest-tools.com](https://app.pentest-tools.com/)** en draait daarop geplande tests tegen de productievlakken van onze apps, de managed Common Ground-omgeving op commonground.nu en de marketingvlakken onder conduction.nl. +Conduction draait **dagelijks** penetratietests tegen elke applicatie via het commerciële SaaS-scanplatform **[pentest-tools.com](https://app.pentest-tools.com/)**. De dekking beslaat de OWASP Top 10, bekende CVE's, TLS-hygiëne, netwerk-exposure en subdomain enumeration — tegen onze apps, de managed Common Ground-omgeving op commonground.nu en de marketingvlakken onder conduction.nl. -Het platform combineert een website-vulnerabilityscanner, een netwerkscanner, een CVE-check op de draaiende stack, TLS-configuratie-audits en subdomain enumeration. De dekking volgt de OWASP Top 10 en de controls in ISO 27001:2022 Annex A.8 (technologische maatregelen). - -**Hoe past dit in het ISMS?** Bevindingen lopen via hetzelfde incidentproces dat in het beleid staat. Kritieke en hoge CVE's activeren de 30-dagen-patch-SLA uit Annex A.8.8. Elke scanrun wordt vastgelegd; de samenvatting van de laatste scan wordt besproken in de maandelijkse MT-kwaliteits­vergadering. Volledige pentestrapporten zijn beschikbaar voor klanten onder NDA — mail naar [info@conduction.nl](mailto:info@conduction.nl) met je contract- of aanbestedingsreferentie. +Komt er een bevinding boven, dan triëren we hem en starten de opvolging **binnen 8 werkuren**.
@@ -580,7 +578,7 @@ Naast de twee ISO-certificeringen, het operationele handboek, de pentest-scans e Nee. Zelf hosten betekent jouw Nextcloud-instance, jouw infrastructuur, jouw data. Ons ISO 27001-certificaat dekt de eigen systemen van Conduction en de apps die we ontwikkelen. De beveiliging van de data die je in OpenRegister op je eigen server zet, is jouw verantwoordelijkheid. Wil je dat onze certificering ook de hosting dekt, gebruik dan onze managed Common Ground-omgeving op commonground.nu. - De pentest-tools.com scans lopen op een doorlopend schema tegen de productievlakken. De volledige multi-vector scan loopt minimaal maandelijks; de gerichte scans (TLS, CVE, OWASP Top 10) lopen wekelijks. Bevindingen boven het medium-niveau worden binnen dezelfde week opgepakt, en kritieke of hoge CVE's activeren de 30-dagen-patch-SLA uit ISO 27001:2022 Annex A.8.8. + Dagelijks, tegen elke applicatie. De pentest-tools.com-scans dekken de OWASP Top 10, bekende CVE's, TLS-hygiëne, netwerk-exposure en subdomain enumeration af. Komt er een bevinding boven, dan triëren we hem en starten de opvolging binnen 8 werkuren. De afstemming op de Baseline Informatiebeveiliging Overheid loopt. We gebruiken de BIO-controleset als gap-analyse tegen onze bestaande ISO 27001-controls en publiceren hier een status­update zodra die rond is. ISO 27001:2022 dekt het grootste deel van de BIO-vereisten al af, daarom beschouwen de meeste overheidsklanten Conduction nu al als inkoop-klaar. diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index 3cf85b74c..6488e6680 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -244,7 +244,7 @@ export const QualityHeroIllustration = ( LIVE
- pentest-tools.com · weekly scan + pentest-tools.com · daily scan CLEAN
@@ -335,7 +335,7 @@ export const QualityHeroIllustration = ( { id: 'pentest', title: 'Pentest-tools.com — Continuous external scans.', - summary: "Conduction uses the commercial pentest-tools.com SaaS platform to run external penetration tests against our public surfaces and the managed Common Ground tenant. Scheduled scans cover the OWASP Top 10, common CVEs, TLS hygiene, and web-application vulnerabilities. Findings flow into our standard incident-management process, with critical and high CVEs patched within 30 days per ISO 27001:2022 Annex A.8.8.", + summary: "Conduction runs penetration tests against every application daily, using the commercial pentest-tools.com SaaS scanning platform. Coverage spans the OWASP Top 10, common CVEs, TLS hygiene, network exposure, and subdomain enumeration. When a finding appears, we triage and start handling it within 8 business hours.", cta: {label: 'See how we scan', href: '#pentest-tools'}, panel: (
@@ -585,11 +585,9 @@ The privacy side of the ISMS is described in the [privacy policy](/privacy). ## Pentest tools -Continuous external assurance. Conduction subscribes to the commercial **[pentest-tools.com](https://app.pentest-tools.com/)** SaaS scanning platform and runs scheduled tests against the production surfaces of our apps, the managed Common Ground tenant at commonground.nu, and the marketing surfaces under conduction.nl. +Conduction runs penetration tests against every application **daily**, using the commercial **[pentest-tools.com](https://app.pentest-tools.com/)** SaaS scanning platform. Coverage spans the OWASP Top 10, common CVEs, TLS hygiene, network exposure, and subdomain enumeration — across our apps, the managed Common Ground tenant at commonground.nu, and the marketing surfaces under conduction.nl. -The platform combines a website vulnerability scanner, a network scanner, a CVE checker against the running stack, TLS configuration auditing, and subdomain enumeration. Coverage maps to the OWASP Top 10 and the controls in ISO 27001:2022 Annex A.8 (technological controls). - -**How it fits the ISMS.** Findings flow into the same incident-management process described in the security policy. Critical and high CVEs trigger the 30-day patch SLA in Annex A.8.8. Every scan run is recorded; the last-scan summary is reviewed at the monthly MT quality meeting. The full pentest reports are available to clients under NDA — write to [info@conduction.nl](mailto:info@conduction.nl) with your contract or tender reference. +If a finding pops up we triage and start handling it **within 8 business hours**. @@ -641,7 +639,7 @@ Beyond the two ISO certifications, the operating handbook, the pentest scans, an No. Self-hosted means your Nextcloud instance, your infrastructure, your data. Our ISO 27001 covers Conduction's own systems and the apps we develop. The security of the data you store in OpenRegister on your own server is your responsibility. If you want our certification to cover hosting too, use our managed Common Ground tenant at commonground.nu. - The pentest-tools.com scans run on a continuous schedule against the production surfaces. The full multi-vector scan runs at least monthly; the targeted scans (TLS, CVE, OWASP Top 10) run weekly. Findings above the medium threshold are triaged within the same week, and critical or high CVEs trigger the 30-day patch SLA in ISO 27001:2022 Annex A.8.8. + Daily, against every application. The pentest-tools.com scans cover the OWASP Top 10, common CVEs, TLS hygiene, network exposure, and subdomain enumeration. When a finding appears, we triage and start handling it within 8 business hours. BIO (Baseline Informatiebeveiliging Overheid) alignment is in progress. We use the BIO control set as the gap analysis against our existing ISO 27001 controls, and we publish a status update on this page once the alignment is complete. ISO 27001:2022 covers the majority of BIO requirements already, which is why most government clients consider Conduction procurement-ready today. From 3205ea914ad54a8327a01e072c87c31db6606d9f Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 20 May 2026 12:15:24 +0200 Subject: [PATCH 064/330] chore(deps): bump @conduction/docusaurus-preset to ^3.15.0 (#93) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulls in the preset's hash-anchor hover-orange rule (design-system #23 / @conduction/docusaurus-preset@3.15.0). The hidden '#' glyph that appears on h2-h6 hover now lights up in KNVB orange the moment the cursor touches it, instead of staying cobalt. No site code changes — purely a preset bump. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c43bbc4d..d8b774a13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { - "@conduction/docusaurus-preset": "^3.11.0", + "@conduction/docusaurus-preset": "^3.15.0", "@docusaurus/core": "^3.5.0", "@docusaurus/plugin-client-redirects": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", @@ -1989,9 +1989,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.11.1.tgz", - "integrity": "sha512-uye0DP5t6n11Sxz+HuFG6gcfmusRWS+ObijPKY8Da69TlcMIHX7t4FySgHB62OhxfkVZcI4SUuKL3RYvw10dHA==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.15.0.tgz", + "integrity": "sha512-NqJriH3kNIn4V93B/yBPz+3to8wa3ly8BqAYokLGJo4mPh5gwn8/xITR4jIdZENKGznLhmapvPFmnPgbJ8VclQ==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" diff --git a/package.json b/package.json index 2c55fd264..6ec4274bf 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^3.11.0", + "@conduction/docusaurus-preset": "^3.15.0", "@docusaurus/core": "^3.5.0", "@docusaurus/plugin-client-redirects": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", From 17a8eca2400d6fba4567a077fbcb4df8b2e51f01 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 20 May 2026 12:34:53 +0200 Subject: [PATCH 065/330] chore(deps): bump @conduction/docusaurus-preset to ^3.15.2 (#94) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulls in design-system #24 → @conduction/docusaurus-preset@3.15.2. Hash-anchor glyph is now KNVB orange the moment Docusaurus reveals it on heading-hover (no more cobalt default, no underline). --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d8b774a13..759fc392e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { - "@conduction/docusaurus-preset": "^3.15.0", + "@conduction/docusaurus-preset": "^3.15.2", "@docusaurus/core": "^3.5.0", "@docusaurus/plugin-client-redirects": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", @@ -1989,9 +1989,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.15.0.tgz", - "integrity": "sha512-NqJriH3kNIn4V93B/yBPz+3to8wa3ly8BqAYokLGJo4mPh5gwn8/xITR4jIdZENKGznLhmapvPFmnPgbJ8VclQ==", + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.15.2.tgz", + "integrity": "sha512-3y7bD99CYsFCbUVawgq0MK9/kJ5Xrv6FFEJSjQ/z+NmPqyZzEYOYwUucrCSGm5z3RGN4I/VnPEq9yYBNXH52Nw==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" diff --git a/package.json b/package.json index 6ec4274bf..170b9a213 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^3.15.0", + "@conduction/docusaurus-preset": "^3.15.2", "@docusaurus/core": "^3.5.0", "@docusaurus/plugin-client-redirects": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", From 707270bd3e21f6b12f947964e6b7831c6f936c12 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 20 May 2026 13:12:11 +0200 Subject: [PATCH 066/330] chore(deps): bump @conduction/docusaurus-preset to ^3.15.3 (#95) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulls in design-system #25 (!important on hash-anchor). The bare-state hash-anchor glyph now actually renders KNVB orange in production — cssnano was stripping the bare .hash-link colour from the comma-list before; !important keeps it through mergeRules. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 759fc392e..157b46e5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { - "@conduction/docusaurus-preset": "^3.15.2", + "@conduction/docusaurus-preset": "^3.15.3", "@docusaurus/core": "^3.5.0", "@docusaurus/plugin-client-redirects": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", @@ -1989,9 +1989,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.15.2.tgz", - "integrity": "sha512-3y7bD99CYsFCbUVawgq0MK9/kJ5Xrv6FFEJSjQ/z+NmPqyZzEYOYwUucrCSGm5z3RGN4I/VnPEq9yYBNXH52Nw==", + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.15.3.tgz", + "integrity": "sha512-ef9++93TPUBdEoJlVBu15IczX4OLttVD60azae9m4yNaQVDrJJNPU1c5Z4W4GP8n8t+98/6G/dTJ06DtnklY+g==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" diff --git a/package.json b/package.json index 170b9a213..b9bbf4fdf 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^3.15.2", + "@conduction/docusaurus-preset": "^3.15.3", "@docusaurus/core": "^3.5.0", "@docusaurus/plugin-client-redirects": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", From d17b2dc983e28fbb00ae63080a615998eaf20802 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 20 May 2026 16:39:04 +0200 Subject: [PATCH 067/330] i18n: clean up bilingual EN/NL site + locale-keyed partners-catalog (#96) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(i18n): clean up bilingual EN/NL site EN MDX (Pile 1): translate residual Dutch out of 17 pages — apps detail (decidesk, mydash, openbuilt, scholiq, shillinq), solutions (anonimiseren, archief, mkb-workspace, openwoo, zaakafhandeling), partners (acato, procolix, yard), marketing (demo, commonground), academy modules (deskdesk-tutorial, openwoo-getting-started). Fix Dutch slips in src/data/{apps,sidecars,solutions}-catalog.js and the "Publieke sector" label. NL parity (Pile 2): rename solutions/woo.mdx -> openwoo.mdx, drop orphan legacy-erp + software-catalog, update solutions catalog list + footer, add missing NL counterparts for solutions/anonimiseren and the two academy modules. EN <-> NL parity 52/52, no orphans. Theme i18n (Pile 3 — in-tree): wrap user-visible strings in the Error fallback and BlogListPage swizzles with , fill the 15 new IDs in i18n/nl/code.json with Dutch translations. Removes the EN page "Geen spam"/"jij@bedrijf.nl" mix in the academy newsletter. partners-catalog: refactor to locale-keyed summaries (summaryEn / summaryNl, useBecomePartner localized strings). usePartners, useBecomePartner, partnerByName + usePartnerSummary hooks resolve at render time. All consumers (partners.mdx, support.mdx, index.mdx + the three detail pages + their NL counterparts) updated to use the hooks. Removes the comment that froze partner summaries to Dutch. * build(plugins): ship academy-modules plugin referenced by config docusaurus.config.js requires ./plugins/academy-modules to build, which was added locally but never committed. Add it so CI can compile. * chore(deps): bump @conduction/docusaurus-preset to ^3.17.0 3.17.0 exports ModulePage + ModuleCard, which the new academy/modules/ pages and the BlogListPage swizzle now import. 3.15.3 (development's pin) is missing those exports — CI fails on this PR without the bump. Also adds `start:nl` script + makes `start` explicit about `--locale en` so dev-mode locale-switcher quirks are easier to work around (see useAlternatePageUtils baseUrl behaviour on /nl/ paths). --- docusaurus.config.js | 9 +- i18n/nl/code.json | 60 ++++ .../academy/modules/deskdesk-tutorial.mdx | 25 ++ .../modules/openwoo-getting-started.mdx | 25 ++ .../docusaurus-plugin-content-pages/index.mdx | 24 +- .../partners.mdx | 26 +- .../solutions.mdx | 21 +- .../solutions/anonimiseren.mdx | 132 +++++++++ .../solutions/legacy-erp.mdx | 107 ------- .../solutions/{woo.mdx => openwoo.mdx} | 0 .../solutions/software-catalog.mdx | 83 ------ i18n/nl/docusaurus-theme-classic/footer.json | 36 ++- i18n/nl/docusaurus-theme-classic/navbar.json | 4 + package-lock.json | 15 +- package.json | 5 +- plugins/academy-modules/index.js | 268 ++++++++++++++++++ src/data/apps-catalog.js | 4 +- src/data/partners-catalog.js | 130 +++++++-- src/data/sidecars-catalog.js | 2 +- src/data/solutions-catalog.js | 22 +- .../academy/modules/deskdesk-tutorial.mdx | 25 ++ .../modules/openwoo-getting-started.mdx | 25 ++ src/pages/apps/decidesk.mdx | 34 +-- src/pages/apps/mydash.mdx | 6 +- src/pages/apps/openbuilt.mdx | 88 +++--- src/pages/apps/scholiq.mdx | 36 +-- src/pages/apps/shillinq.mdx | 28 +- src/pages/commonground.mdx | 50 ++-- src/pages/demo.mdx | 64 ++--- src/pages/index.mdx | 18 +- src/pages/partners.mdx | 20 +- src/pages/partners/acato.mdx | 63 ++-- src/pages/partners/procolix.mdx | 63 ++-- src/pages/partners/yard.mdx | 63 ++-- src/pages/solutions/anonimiseren.mdx | 86 +++--- src/pages/solutions/archief.mdx | 62 ++-- src/pages/solutions/mkb-workspace.mdx | 58 ++-- src/pages/solutions/openwoo.mdx | 82 +++--- src/pages/solutions/zaakafhandeling.mdx | 64 ++--- src/pages/support.mdx | 41 ++- src/theme/BlogListPage/index.jsx | 227 +++++++++++++-- src/theme/Error/index.jsx | 109 +++++++ 42 files changed, 1558 insertions(+), 752 deletions(-) create mode 100644 i18n/nl/docusaurus-plugin-content-pages/academy/modules/deskdesk-tutorial.mdx create mode 100644 i18n/nl/docusaurus-plugin-content-pages/academy/modules/openwoo-getting-started.mdx create mode 100644 i18n/nl/docusaurus-plugin-content-pages/solutions/anonimiseren.mdx delete mode 100644 i18n/nl/docusaurus-plugin-content-pages/solutions/legacy-erp.mdx rename i18n/nl/docusaurus-plugin-content-pages/solutions/{woo.mdx => openwoo.mdx} (100%) delete mode 100644 i18n/nl/docusaurus-plugin-content-pages/solutions/software-catalog.mdx create mode 100644 plugins/academy-modules/index.js create mode 100644 src/pages/academy/modules/deskdesk-tutorial.mdx create mode 100644 src/pages/academy/modules/openwoo-getting-started.mdx create mode 100644 src/theme/Error/index.jsx diff --git a/docusaurus.config.js b/docusaurus.config.js index 0e14e2dfe..b29bb0e70 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -159,7 +159,6 @@ module.exports = createConfig({ title: 'Solutions', items: [ {label: 'OpenWoo', to: '/solutions/openwoo'}, - {label: 'Software catalog',to: '/solutions/software-catalog'}, {label: 'Support', to: '/support'}, {label: 'ConNext', to: '/connext'}, {label: 'Common Ground', to: '/commonground'}, @@ -193,6 +192,14 @@ module.exports = createConfig({ /* OpenCatalogi content plugin slot, wired in via env once it exists. */ plugins: [ + /* academy-modules: scans academy/*\/index.mdx frontmatter and emits + module → ordered parts global data. Consumed by BlogListPage + (composite ModuleCards + module pill row) and per-module MDX + index pages at /academy/modules/{slug}. */ + [ + require.resolve('./plugins/academy-modules'), + {contentDir: 'academy', routeBasePath: '/academy'}, + ], // [ // '@conduction/docusaurus-plugin-opencatalogi', // { diff --git a/i18n/nl/code.json b/i18n/nl/code.json index 39f80f151..68faaf88e 100644 --- a/i18n/nl/code.json +++ b/i18n/nl/code.json @@ -325,5 +325,65 @@ "theme.tags.tagsPageTitle": { "message": "Tags", "description": "The title of the tag list page" + }, + "theme.error.eyebrow": { + "message": "Vinger in de dijk", + "description": "Eyebrow above the error-page heading; idiom about plugging a leak" + }, + "theme.error.title": { + "message": "Helaas, er is iets stuk.", + "description": "Main heading on the error fallback page" + }, + "theme.error.body": { + "message": "We sturen nu een team met ducttape. Probeer het straks opnieuw, of neem een andere route.", + "description": "Reassurance paragraph below the error heading" + }, + "theme.error.retry": { + "message": "Probeer opnieuw", + "description": "Label of the retry button on the error page" + }, + "theme.error.details": { + "message": "Wat er technisch misging", + "description": "Disclosure label above the raw error message" + }, + "theme.error.pageTitle": { + "message": "Er is iets stuk · Conduction", + "description": "HTML on the error fallback page" + }, + "theme.academy.featuredEyebrow": { + "message": "Uitgelicht: {type}", + "description": "Eyebrow on the featured academy card. {type} is the content type slug (blog, guide, case-study, webinar, tutorial)" + }, + "theme.academy.featuredCta": { + "message": "Lees meer", + "description": "CTA label on the featured academy card" + }, + "theme.academy.emptyState": { + "message": "Nog niets voor deze combinatie. {viewAll}", + "description": "Empty-state message shown when filters return no posts. {viewAll} is a link to the unfiltered academy index." + }, + "theme.academy.viewAll": { + "message": "Bekijk alles", + "description": "Link text inside the empty-state message" + }, + "theme.academy.newsletter.title": { + "message": "Nieuwe berichten in je inbox, maandelijks.", + "description": "Newsletter section title at the bottom of the academy landing page" + }, + "theme.academy.newsletter.lede": { + "message": "Eén mail per maand. Nieuwe guides, case studies en webinars. Geen spam, je kunt je altijd uitschrijven.", + "description": "Newsletter section lede paragraph" + }, + "theme.academy.newsletter.placeholder": { + "message": "jij@bedrijf.nl", + "description": "Placeholder text inside the newsletter email input" + }, + "theme.academy.newsletter.submit": { + "message": "Aanmelden", + "description": "Submit button label on the newsletter form" + }, + "theme.academy.newsletter.fineprint": { + "message": "We mailen vanuit info@conduction.nl. Geen lijstverkoop.", + "description": "Small-print line below the newsletter form" } } diff --git a/i18n/nl/docusaurus-plugin-content-pages/academy/modules/deskdesk-tutorial.mdx b/i18n/nl/docusaurus-plugin-content-pages/academy/modules/deskdesk-tutorial.mdx new file mode 100644 index 000000000..5cf2ff75e --- /dev/null +++ b/i18n/nl/docusaurus-plugin-content-pages/academy/modules/deskdesk-tutorial.mdx @@ -0,0 +1,25 @@ +--- +title: "Bouw een Nextcloud-app op de Conduction-stack" +description: "Vierdelige module die je van een lege Nextcloud naar een DeskDesk-app brengt, gepubliceerd in de Conduction app-store." +--- + +import {Prerequisites, PrerequisiteItem, Outcomes, Outcome, ModulePage} from '@conduction/docusaurus-preset/components'; + +<ModulePage module="deskdesk-tutorial"> + +Deze module bouwt DeskDesk, een werkende app voor bureauboeking, op de volledige Conduction-stack. Je scaffoldt de app, declareert de schema's, koppelt het met één config-blok aan de NC Calendar, laagt xWiki-kennisartikelen erover, en publiceert naar de Conduction app-store. Elk onderdeel is zelfstandig. Sla delen over die je al kent. + +<Outcomes title="Wat je bouwt"> + <Outcome>Een werkende DeskDesk Nextcloud-app in je lokale omgeving.</Outcome> + <Outcome>Drie OpenRegister-schema's (bureau, boeking, verdieping) gedeclareerd in één manifest.</Outcome> + <Outcome>NC Calendar-integratie zonder een controller of event listener te schrijven.</Outcome> + <Outcome>Per-bureau kennisartikelen uit xWiki, verpakt en gepubliceerd.</Outcome> +</Outcomes> + +<Prerequisites title="Voor je begint"> + <PrerequisiteItem>Een Nextcloud op localhost (zie de [Run Nextcloud locally](/academy/nextcloud-lokaal-draaien) tutorial).</PrerequisiteItem> + <PrerequisiteItem>OpenRegister geïnstalleerd en draaiend.</PrerequisiteItem> + <PrerequisiteItem>Node 20 en PHP 8.3 lokaal, de standaard Conduction dev-stack.</PrerequisiteItem> +</Prerequisites> + +</ModulePage> diff --git a/i18n/nl/docusaurus-plugin-content-pages/academy/modules/openwoo-getting-started.mdx b/i18n/nl/docusaurus-plugin-content-pages/academy/modules/openwoo-getting-started.mdx new file mode 100644 index 000000000..480340721 --- /dev/null +++ b/i18n/nl/docusaurus-plugin-content-pages/academy/modules/openwoo-getting-started.mdx @@ -0,0 +1,25 @@ +--- +title: "Aan de slag met OpenWoo" +description: "Twee tutorials die je in een half uur klaarmaken om Woo-publicaties op te slaan en bestanden te uploaden volgens de Wet open overheid." +--- + +import {Prerequisites, PrerequisiteItem, Outcomes, Outcome, ModulePage} from '@conduction/docusaurus-preset/components'; + +<ModulePage module="openwoo-getting-started"> + +De Wet open overheid (Woo) verplicht overheidsorganen om documenten in elf informatiecategorieën te publiceren. Deze module is het kortste pad van leeg register naar gepubliceerde Woo-bestanden: importeer het canonieke Woo-register met alle TOOI-categorieën, en koppel daarna documenten aan publicaties via de OpenRegister-API. + +<Outcomes title="Wat je leert"> + <Outcome>Hoe je het canonieke Woo-register in OpenRegister importeert.</Outcome> + <Outcome>Welke TOOI-categorieën standaard meekomen, en wanneer je ze uitbreidt.</Outcome> + <Outcome>Vier manieren om bestanden aan een Woo-publicatie te hangen, met geteste API-calls.</Outcome> + <Outcome>Publiceren, depubliceren, en opruimen via de API.</Outcome> +</Outcomes> + +<Prerequisites title="Voor je begint"> + <PrerequisiteItem>Een Nextcloud met OpenRegister geïnstalleerd (zie [Run Nextcloud locally](/academy/nextcloud-lokaal-draaien)).</PrerequisiteItem> + <PrerequisiteItem>Een werkende REST-client (curl, HTTPie, Bruno of Postman).</PrerequisiteItem> + <PrerequisiteItem>Een gebruiker met schrijfrechten op het Woo-register.</PrerequisiteItem> +</Prerequisites> + +</ModulePage> diff --git a/i18n/nl/docusaurus-plugin-content-pages/index.mdx b/i18n/nl/docusaurus-plugin-content-pages/index.mdx index d1ce9109f..44d78b3d4 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/index.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/index.mdx @@ -6,7 +6,18 @@ hide_table_of_contents: true import {Hero, StatsStrip, AppsPreview, CtaBanner, PairRow, PairCard, Section, SectionHead, Clients, DEFAULT_CLIENTS, PartnerDirectory, CommonGroundPlus, HexRain} from '@conduction/docusaurus-preset/components'; import {totalDownloads, formatDownloads} from '@conduction/docusaurus-preset/data'; -import {PARTNERS, totalPartners} from '@site/src/data/partners-catalog'; +import {usePartners, useBecomePartner, totalPartners} from '@site/src/data/partners-catalog'; + +export function HomePartnerDirectory() { + const partners = usePartners(); + const becomePartner = useBecomePartner(); + return ( + <PartnerDirectory + partners={partners} + becomePartner={{...becomePartner, ctaLabel: 'Aanmelden via /support'}} + /> + ); +} {/* Conduction main landing. Public Tech framing: tech to serve people. Three doors out: install an app yourself, get it as managed SaaS, or @@ -101,16 +112,7 @@ import {PARTNERS, totalPartners} from '@site/src/data/partners-catalog'; align="stack" lede="Implementatie-, hosting- en integratiepartners die Conduction-apps bij klanten uitrollen. Vraag aan via /support om opgenomen te worden." /> - <PartnerDirectory - partners={PARTNERS} - becomePartner={{ - href: '/support#become-a-partner', - eyebrow: 'Word partner', - title: 'Lever Conduction aan jouw klanten.', - body: 'Word Partner, Certified of Strategic. Conduction traint je team, jij doet de uitrol. De apps blijven open source, de relatie blijft direct.', - ctaLabel: 'Aanmelden via /support', - }} - /> + <HomePartnerDirectory /> </Section> <Clients diff --git a/i18n/nl/docusaurus-plugin-content-pages/partners.mdx b/i18n/nl/docusaurus-plugin-content-pages/partners.mdx index d8875f3c7..b48638300 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/partners.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/partners.mdx @@ -5,15 +5,19 @@ hide_table_of_contents: true --- import {Section, SectionHead, PartnerDirectory} from '@conduction/docusaurus-preset/components'; -import {PARTNERS, BECOME_PARTNER} from '@site/src/data/partners-catalog'; +import {usePartners, useBecomePartner} from '@site/src/data/partners-catalog'; -export const BECOME_PARTNER_NL = { - ...BECOME_PARTNER, - eyebrow: 'Word partner', - title: 'Lever Conduction aan jouw klanten.', - body: 'Sluit je aan als Partner, Gecertificeerd of Strategisch. Conduction traint je team, jij doet de uitrol. De apps blijven open source, de relatie blijft direct.', - ctaLabel: 'Aanvragen via Support', -}; +export function PartnerDirectoryBlock() { + const partners = usePartners(); + const becomePartner = useBecomePartner(); + return ( + <PartnerDirectory + partners={partners} + becomePartner={becomePartner} + gridColumns={3} + /> + ); +} <Section spacing="default"> <SectionHead @@ -22,9 +26,5 @@ export const BECOME_PARTNER_NL = { lede={<>De apps zijn open source en gratis. De meeste teams installeren en draaien ze zelf. Wil je hulp bij hosting, maatwerk-integraties of een uitrol in een gereguleerde sector? Onze gecertificeerde partners pakken het op. Ze draaien dezelfde <span className="next-blue">Nextcloud</span> als jij en bouwen op dezelfde apps.</>} /> - <PartnerDirectory - partners={PARTNERS} - becomePartner={BECOME_PARTNER_NL} - gridColumns={3} - /> + <PartnerDirectoryBlock /> </Section> diff --git a/i18n/nl/docusaurus-plugin-content-pages/solutions.mdx b/i18n/nl/docusaurus-plugin-content-pages/solutions.mdx index e0a605ae9..dc4b17909 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/solutions.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/solutions.mdx @@ -9,8 +9,8 @@ import {Section, SectionHead, SolutionCard, SolutionGrid, FacetedFilters, Filter export const SOLUTIONS = [ { - href: '/solutions/woo', sector: 'public', sectorLabel: 'Publieke sector', - title: 'Woo-compliance, vóór vrijdag.', + href: '/solutions/openwoo', sector: 'public', sectorLabel: 'Publieke sector', + title: 'OpenWoo. Wet open overheid, vóór vrijdag.', outcome: <>Live Woo-portaal op je gehoste <span className="next-blue">Nextcloud</span>. Elke wetscategorie wordt een register, elk register publiceert via OpenCatalogi, elke connector haalt op uit je DMS.</>, builtOn: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], goals: ['compliance'], status: 'production', icon: <svg viewBox="0 0 24 24"><path d="M12 3l9 4v5c0 5-4 8-9 9-5-1-9-4-9-9V7l9-4z"/><path d="M9 12l2 2 4-4"/></svg>, @@ -30,11 +30,11 @@ export const SOLUTIONS = [ icon: <svg viewBox="0 0 24 24"><path d="M3 4h18v4H3z"/><path d="M5 8v12h14V8"/><path d="M9 12h6"/></svg>, }, { - href: '/solutions/software-catalog', sector: 'public', sectorLabel: 'Publieke sector', - title: 'Software-catalogus voor de gemeente.', - outcome: 'Een publieke software-catalogus. Apps, datasets en APIs van je organisatie, zoekbaar op één plek, gefedereerd naar data.overheid.nl.', - builtOn: ['OpenCatalogi', 'OpenRegister'], goals: ['integration'], status: 'production', - icon: <svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4"/></svg>, + href: '/solutions/anonimiseren', sector: ['public', 'mkb'], sectorLabel: 'Publiek & MKB', + title: 'Anonimiseren, voor je het publiceert.', + outcome: <>Privacy-by-design pipeline op <span className="next-blue">Nextcloud</span>. DocuDesk en OpenRegister sporen PII op en redacteren records en documenten voordat ze de organisatie verlaten. Presidio onder de motorkap, AVG-uitlegbaar.</>, + builtOn: ['DocuDesk', 'OpenRegister', 'Presidio'], goals: ['compliance'], status: 'pilot', + icon: <svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M5 21v-2a4 4 0 0 1 4-4h6a4 4 0 0 1 4 4v2"/><path d="M9 8h6"/></svg>, }, { href: '/solutions/mkb-workspace', sector: 'mkb', sectorLabel: 'MKB', @@ -43,13 +43,6 @@ export const SOLUTIONS = [ builtOn: ['MyDash', 'OpenRegister'], goals: ['reporting'], status: 'production', icon: <svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>, }, - { - href: '/solutions/legacy-erp', sector: 'mkb', sectorLabel: 'MKB', - title: 'Eén legacy-ERP, alle moderne tools.', - outcome: 'OpenConnector trekt data uit je ERP, vult typed registers, en biedt een API aan elke tool die je team vandaag gebruikt.', - builtOn: ['OpenConnector', 'OpenRegister'], goals: ['integration'], status: 'pilot', - icon: <svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>, - }, ]; export function FilterableSolutions() { diff --git a/i18n/nl/docusaurus-plugin-content-pages/solutions/anonimiseren.mdx b/i18n/nl/docusaurus-plugin-content-pages/solutions/anonimiseren.mdx new file mode 100644 index 000000000..e9d91277a --- /dev/null +++ b/i18n/nl/docusaurus-plugin-content-pages/solutions/anonimiseren.mdx @@ -0,0 +1,132 @@ +--- +title: Anonimiseren, AVG-proof publicatie en hergebruik +description: Privacy-by-design pipeline op Nextcloud. DocuDesk en OpenRegister detecteren en redacteren PII voordat documenten en records gepubliceerd, gedeeld of geëxporteerd worden. Presidio onder de motorkap. +hide_table_of_contents: true +--- + +import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, PairRow, PairCard, CtaBanner} from '@conduction/docusaurus-preset/components'; + +<DetailHero + crumb={[{label: 'Oplossingen', href: '/nl/solutions'}, 'Anonimiseren']} + status={{label: 'Pilot', color: 'var(--c-amber-500)'}} + version="Sector: Publiek & MKB" + title="Anonimiseren, voor je het publiceert." + tagline={<>Een privacy-by-design pipeline op <span className="next-blue">Nextcloud</span>. DocuDesk en OpenRegister sporen PII op en redacteren records en documenten voordat ze de organisatie verlaten. Werkt voor Woo-publicaties, dataset-export, klantcommunicatie en interne kennisdeling. Presidio onder de motorkap, AVG-uitlegbaar.</>} + primaryCta={{label: 'Praat met een partner', href: '/nl/partners'}} + secondaryCta={{label: 'Lees de recipe', href: 'https://docs.conduction.nl/anonimiseren'}} + iconColor="var(--c-blue-cobalt)" + icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M5 21v-2a4 4 0 0 1 4-4h6a4 4 0 0 1 4 4v2"/><path d="M9 8h6"/></svg>} +/> + +<Section spacing="default"> + <SectionHead + eyebrow="Het resultaat" + title="Geen PII die ongezien naar buiten gaat." + align="stack" + lede="Elke publicatie, export of API-call passeert eerst de anonimisatielaag. Namen, BSN's, kentekens, adressen en andere persoonsgegevens worden gedetecteerd, geredigeerd en gelogd, met een uitlegbaar besluit per match. Wat de organisatie deelt, is wat de organisatie wíl delen." + /> + + <FeatureList> + <FeatureItem + icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M9 12l2 2 4-4"/></svg>} + title="Detectie op tekst, tabel en document." + > + Presidio-gebaseerde detectie voor BSN, namen, adressen, IBAN, telefoonnummers, kentekens, e-mailadressen, geboortedata. Werkt op platte tekst, PDF, Word, Excel en op velden in een OpenRegister-record. + </FeatureItem> + <FeatureItem + icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M8 13h8M8 17h5"/></svg>} + title="Redactie met behoud van bewijs." + > + DocuDesk schrijft een geredigeerde kopie, bewaart het origineel achter rechten en houdt de mapping per match vast. Audit-trail toont wát is geredigeerd, op grond waarvan en door welk beleid. + </FeatureItem> + <FeatureItem + icon={<svg viewBox="0 0 24 24"><path d="M3 12h18M12 3v18"/><circle cx="12" cy="12" r="9"/></svg>} + title="Beleid per register, per categorie." + > + Elk OpenRegister-register krijgt een eigen anonimisatieprofiel: welke entiteiten gedetecteerd worden, welke daarvan geredigeerd, welke geblokkeerd. Woo-publicatie, AVG-verzoek, dataset-export en intern delen krijgen elk hun eigen drempel. + </FeatureItem> + <FeatureItem + icon={<svg viewBox="0 0 24 24"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 8.4 8.4 0 0 1-3.4-.7L3 21l1.5-5.4A8.4 8.4 0 1 1 21 11.5z"/></svg>} + title="Mens in de lus, waar het moet." + > + Lage-zekerheidsmatches gaan naar een review-wachtrij in Nextcloud. De behandelaar bevestigt of verwerpt; de beslissing wordt onderdeel van het profiel en verbetert toekomstige detectie. + </FeatureItem> + <FeatureItem + icon={<svg viewBox="0 0 24 24"><path d="M12 2v4M12 18v4M2 12h4M18 12h4M5 5l3 3M16 16l3 3M5 19l3-3M16 8l3-3"/></svg>} + title="Uitlegbaar voor de AVG." + > + Per geredigeerde match wordt vastgelegd: welk type PII, welk model, welke versie, welk profiel. Functionaris Gegevensbescherming kan elk besluit terugzien zonder het oorspronkelijke document te raadplegen. + </FeatureItem> + <FeatureItem + icon={<svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1"/><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1"/></svg>} + title="API en pipeline, niet alleen UI." + > + Aanroepbaar vanuit OpenConnector-flows, n8n, Windmill en directe REST-calls. Sluit aan op bestaande Woo-, export- en publicatieprocessen zonder de UI te raken. + </FeatureItem> + </FeatureList> +</Section> + +<Section spacing="default"> + <SectionHead + eyebrow="De pipeline" + title="Van interne bron tot veilige publicatie." + align="stack" + lede="OpenConnector haalt records en bestanden uit je bronsystemen. OpenRegister houdt ze typed en versioned. DocuDesk laat Presidio over de inhoud lopen, redacteert volgens het profiel en levert de geanonimiseerde kopie aan het publicatiekanaal. Het origineel blijft binnen Nextcloud, achter rechten en audit." + /> + <Pipeline + start={{label: 'Bronnen', items: [ + {label: 'DMS', icon: 'doc'}, + {label: 'Zaaksysteem', icon: 'db'}, + {label: 'Bestanden', icon: 'doc'}, + {label: 'Klantdata', icon: 'db'}, + ]}} + steps={[ + {number: '01', kicker: 'Ingest', name: 'OpenConnector', caption: 'Pulls records en documenten uit je bronsystemen via REST, SOAP of file drops.'}, + {number: '02', kicker: 'Store', name: 'OpenRegister', caption: 'Typed registers per categorie. Versioned, audit-logged, met anonimisatieprofiel.'}, + {number: 'PLATFORM', name: 'Nextcloud', family: 'workspace', pill: 'files · users · auth', caption: 'De workspace waar je team al inlogt.'}, + {number: '03', kicker: 'Anonimise', name: 'DocuDesk', caption: 'Detecteert PII via Presidio, redacteert per profiel, logt elk besluit.'}, + ]} + end={{label: 'Consumenten', items: [ + {label: 'Woo-portal', icon: 'globe'}, + {label: 'Open data', icon: 'download'}, + {label: 'AVG-inzage', icon: 'person'}, + {label: 'Externe partners', icon: 'globe'}, + ]}} + /> +</Section> + +<Section spacing="default" background="tinted"> + <SectionHead + eyebrow="Gebouwd op" + title="Twee Conduction-apps, één PII-engine." + align="stack" + lede="Geen maatwerk. Elk onderdeel is een component dat je los kunt installeren en voor andere processen kunt hergebruiken." + /> + <PairRow> + <PairCard + href="/nl/apps/docudesk" + icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} + name="DocuDesk" + why="Voert de redactie uit op tekst, PDF en Word, schrijft een geanonimiseerde kopie en houdt origineel + mapping veilig achter rechten." + /> + <PairCard + href="/nl/apps/openregister" + icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/><path d="M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg>} + name="OpenRegister" + why="Levert de typed registers met een anonimisatieprofiel per categorie. Audit-trail toont welke match op grond van welk beleid is geredigeerd." + /> + <PairCard + href="/nl/apps/openregister#integrations" + icon={<svg viewBox="0 0 24 24"><path d="M12 2L4 5v7c0 5 3.5 9 8 10 4.5-1 8-5 8-10V5z"/><circle cx="12" cy="11" r="2.2" fill="#fff"/><path d="M9 16h6" stroke="#fff" strokeWidth="1.6" fill="none"/></svg>} + name="Presidio" + why="Microsoft's open-source PII-detectiebibliotheek. Powert de detectielaag van DocuDesk; AVG-uitlegbaar, on-prem, geen externe API." + /> + </PairRow> +</Section> + +<CtaBanner + title="Anonimiseren als deel van de installatie, niet als project." + lede={<>Twee apps en een PII-engine. Praat met een partner als je liever wilt dat we de uitrol verzorgen, of installeer zelf via de <span className="next-blue">Nextcloud</span> app store en volg de recipe.</>} + primaryCta={{label: 'Praat met een partner', href: '/nl/partners'}} + secondaryCta={{label: 'Lees de recipe', href: 'https://docs.conduction.nl/anonimiseren'}} +/> diff --git a/i18n/nl/docusaurus-plugin-content-pages/solutions/legacy-erp.mdx b/i18n/nl/docusaurus-plugin-content-pages/solutions/legacy-erp.mdx deleted file mode 100644 index e637a3337..000000000 --- a/i18n/nl/docusaurus-plugin-content-pages/solutions/legacy-erp.mdx +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: Eén legacy-ERP, alle moderne tools, Conduction -description: OpenConnector trekt data uit je ERP, vult typed registers, en biedt een API aan elke tool die je team vandaag gebruikt. Geen middleware, geen migratie. -hide_table_of_contents: true ---- - -import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, PairRow, PairCard, CtaBanner} from '@conduction/docusaurus-preset/components'; - -<DetailHero - crumb={[{label: 'Solutions', href: '/solutions'}, 'Legacy-ERP']} - status={{label: 'Pilot', color: 'var(--c-orange-knvb)'}} - version="Sector: MKB" - title="Eén legacy-ERP, alle moderne tools." - tagline={<>Je hebt Exact, Twinfield, Visma of een ander ERP dat de laatste tien jaar braaf zijn werk doet. Vervang het niet. OpenConnector trekt de data eruit, OpenRegister maakt het typed, MyDash rapporteert erover. Geen migratie, geen middleware.</>} - primaryCta={{label: 'Installeer uit de app store', href: '/install'}} - secondaryCta={{label: 'Praat met een partner', href: '/partners'}} - iconColor="var(--c-cobalt-700)" - icon={<svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>} -/> - -<Section spacing="default"> - <SectionHead - eyebrow="Het resultaat" - title="Een moderne API-laag op je bestaande boekhouding." - align="stack" - lede="OpenConnector haalt klanten, facturen en urenregistratie uit je ERP. OpenRegister maakt het typed. Je nieuwe tools (MyDash, Notion, Slack, n8n) consumeren een schone REST/GraphQL-API. Het ERP blijft draaien. Niemand merkt iets." - /> - <FeatureList> - <FeatureItem - icon={<svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>} - title="Connector voor de Nederlandse ERP-stack." - > - Voorgebouwde adapters voor Exact Online, Twinfield, Visma.net, Yuki en AFAS. Standaard-velden gemapt naar standaard-registers. - </FeatureItem> - <FeatureItem - icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>} - title="Realtime of geplande sync." - > - Webhook-based sync waar het ERP dat ondersteunt; cron-style schedule waar niet. Eerste sync vult het register, daarna alleen deltas. - </FeatureItem> - <FeatureItem - icon={<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/></svg>} - title="MyDash voor de cijfers die ertoe doen." - > - Bouw dashboards uit je registers: omzet per klant, urenproductiviteit, openstaande facturen. Geen aparte BI-tool, geen ETL. - </FeatureItem> - <FeatureItem - icon={<svg viewBox="0 0 24 24"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 8.4 8.4 0 0 1-3.4-.7L3 21l1.5-5.4A8.4 8.4 0 1 1 21 11.5z"/></svg>} - title="Open API voor andere tools." - > - OpenRegister biedt een schone REST-API plus GraphQL-endpoint. Notion, Slack, n8n, Zapier, je eigen scripts, alles consumeert direct. - </FeatureItem> - </FeatureList> -</Section> - -<Section spacing="default" background="tinted"> - <SectionHead - eyebrow="Hoe het werkt" - title="Vier stappen, geen migratie." - align="stack" - /> - <Pipeline - start={{label: 'Bronnen', items: ['Exact Online', 'Twinfield', 'Visma.net', 'AFAS']}} - steps={[ - {number: '01', kicker: 'Ophalen', name: 'OpenConnector', caption: 'Haalt data uit je bestaande ERP via REST of webhook.'}, - {number: '02', kicker: 'Typeren', name: 'OpenRegister', caption: 'Schema-gevalideerde, typed registers. Audit-trail.'}, - {number: '03', kicker: 'Aanbieden', name: 'REST + GraphQL', caption: 'Schone API voor je nieuwe tools en integraties.'}, - {number: '04', kicker: 'Tonen', name: 'MyDash', caption: 'Dashboards, KPIs, omzet-per-klant uit dezelfde registers.'}, - ]} - end={{label: 'Afnemers', items: ['MyDash', 'Notion', 'Slack / Teams', 'n8n']}} - /> -</Section> - -<Section spacing="default"> - <SectionHead - eyebrow="Gebouwd op" - title="Drie apps, geen middleware." - align="stack" - /> - <PairRow> - <PairCard - href="/apps/openconnector" - icon={<svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>} - name="OpenConnector" - why="De integratielaag. Trekt data uit je ERP zonder glue-code." - /> - <PairCard - href="/apps/openregister" - icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg>} - name="OpenRegister" - why="Typed-data-foundation. REST en GraphQL-API rolt uit het schema." - /> - <PairCard - href="/apps/mydash" - icon={<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/></svg>} - name="MyDash" - why="Dashboards en KPIs uit dezelfde registers. Geen ETL-team nodig." - /> - </PairRow> -</Section> - -<CtaBanner - title="Geen ERP-migratie. Wel moderne tools." - lede="Drie apps, één configuratie. Het bestaande ERP blijft de system of record. Je nieuwe tools praten met de schone register-API. Productie binnen twee weken." - primaryCta={{label: 'Installeer uit de app store', href: '/install'}} - secondaryCta={{label: 'Praat met een partner', href: '/partners'}} -/> diff --git a/i18n/nl/docusaurus-plugin-content-pages/solutions/woo.mdx b/i18n/nl/docusaurus-plugin-content-pages/solutions/openwoo.mdx similarity index 100% rename from i18n/nl/docusaurus-plugin-content-pages/solutions/woo.mdx rename to i18n/nl/docusaurus-plugin-content-pages/solutions/openwoo.mdx diff --git a/i18n/nl/docusaurus-plugin-content-pages/solutions/software-catalog.mdx b/i18n/nl/docusaurus-plugin-content-pages/solutions/software-catalog.mdx deleted file mode 100644 index 212a3d3c2..000000000 --- a/i18n/nl/docusaurus-plugin-content-pages/solutions/software-catalog.mdx +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: Software-catalogus voor de gemeente, Conduction -description: Een publieke software-catalogus voor de gemeente. Apps, datasets en APIs op één plek, federatie naar data.overheid.nl direct uit de doos. -hide_table_of_contents: true ---- - -import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, PairCard, CtaBanner} from '@conduction/docusaurus-preset/components'; - -<DetailHero - crumb={[{label: 'Solutions', href: '/solutions'}, 'Software-catalogus']} - status={{label: 'Productie-ready', color: 'var(--c-mint-500)'}} - version="Sector: Publieke sector" - title="Software-catalogus voor de gemeente." - tagline={<>Een publieke software-catalogus. Apps, datasets en APIs die je organisatie gebruikt, doorzoekbaar op één plek, gefedereerd naar data.overheid.nl en Forum Standaardisatie direct uit de doos. Geen aparte tooling, geen jaarlijkse upload-marathon.</>} - primaryCta={{label: 'Praat met een partner', href: '/partners'}} - secondaryCta={{label: 'Lees de recipe', href: 'https://docs.conduction.nl/software-catalog'}} - iconColor="var(--c-blue-cobalt)" - icon={<svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4"/></svg>} -/> - -<Section spacing="default"> - <SectionHead - eyebrow="Het resultaat" - title="Eén catalogus voor de hele organisatie." - align="stack" - lede="Twee apps. Eén configuratie. Burgers vinden je publieke datasets. Je IT-afdeling vindt elke applicatie die ergens draait. Externe partijen federeren mee zonder extra werk." - /> - <FeatureList> - <FeatureItem - icon={<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5"/></svg>} - title="Zoeken dat werkt voor burgers." - > - Burgers landen op jouwgemeente.nl/catalogi en zoeken in één veld door alle gepubliceerde datasets, APIs en applicaties. NLDS-conforme UX uit de doos. - </FeatureItem> - <FeatureItem - icon={<svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4M3 17l9 4 9-4"/></svg>} - title="DCAT-AP en NLDS-metadata." - > - Elke entry exporteert als DCAT-AP en Nederlandse profiel-metadata. data.overheid.nl en Forum Standaardisatie pikken het op zonder handmatige upload. - </FeatureItem> - <FeatureItem - icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>} - title="Federatie met andere gemeenten." - > - Je catalogus federeert met die van andere gemeenten via signed manifests. Gezamenlijke standaarden, gezamenlijke implementatie-dossiers. - </FeatureItem> - <FeatureItem - icon={<svg viewBox="0 0 24 24"><path d="M12 2v4M12 18v4M2 12h4M18 12h4M5 5l3 3M16 16l3 3M5 19l3-3M16 8l3-3"/></svg>} - title="Audit trail per entry." - > - Elke publicatie, edit of intrekking laat een tamper-evident trail. Klaar voor inspectie of verantwoording. - </FeatureItem> - </FeatureList> -</Section> - -<Section spacing="default" background="tinted"> - <SectionHead - eyebrow="Gebouwd op" - title="Twee apps, één configuratie." - align="stack" - /> - <PairRow columns={2}> - <PairCard - href="/apps/opencatalogi" - icon={<svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4M3 17l9 4 9-4"/></svg>} - name="OpenCatalogi" - why="De doorzoekbare publicatielaag. Federatie naar data.overheid.nl, NLDS-UI, zoeken voor burgers." - /> - <PairCard - href="/apps/openregister" - icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg>} - name="OpenRegister" - why="Elke catalogus-entry is een typed register-record. Schema, audit-log, citation-stable IDs, direct uit de doos." - /> - </PairRow> -</Section> - -<CtaBanner - title="Eén catalogus, elke publieke link." - lede={<>Drop OpenCatalogi en OpenRegister in je <span className="next-blue">Nextcloud</span>, configureer de eerste registers, en burgers zoeken dezelfde middag.</>} - primaryCta={{label: 'Installeer uit de app store', href: '/install'}} - secondaryCta={{label: 'Praat met een partner', href: '/partners'}} -/> diff --git a/i18n/nl/docusaurus-theme-classic/footer.json b/i18n/nl/docusaurus-theme-classic/footer.json index f9afc8e94..bb0b889be 100644 --- a/i18n/nl/docusaurus-theme-classic/footer.json +++ b/i18n/nl/docusaurus-theme-classic/footer.json @@ -35,13 +35,9 @@ "message": "MyDash", "description": "The label of footer link with label=MyDash linking to https://mydash.conduction.nl/" }, - "link.item.label.WOO compliance": { - "message": "Woo-compliance", - "description": "The label of footer link with label=WOO compliance linking to /solutions/woo" - }, - "link.item.label.Software catalog": { - "message": "Softwarecatalogus", - "description": "The label of footer link with label=Software catalog linking to /solutions/software-catalog" + "link.item.label.OpenWoo": { + "message": "OpenWoo", + "description": "The label of footer link with label=OpenWoo linking to /solutions/openwoo" }, "link.item.label.Support": { "message": "Support", @@ -73,7 +69,7 @@ }, "link.item.label.Quality": { "message": "Kwaliteit", - "description": "The label of footer link with label=Quality linking to /quality (NL site renders the localised page at /nl/quality)" + "description": "The label of footer link with label=Quality linking to /quality" }, "link.item.label.Identity": { "message": "Identiteit", @@ -82,5 +78,29 @@ "copyright": { "message": "Conduction B.V. · KvK 76741850 · BTW NL860784241B01 · IBAN NL51 ABNA 0868951550 · Lauriergracht 14h, 1016 RR Amsterdam · © 2026", "description": "The footer copyright" + }, + "link.item.label.Partners": { + "message": "Partners", + "description": "The label of footer link with label=Partners linking to /partners" + }, + "link.item.label.Blogs": { + "message": "Blogs", + "description": "The label of footer link with label=Blogs linking to /academy?type=blog" + }, + "link.item.label.Guides": { + "message": "Guides", + "description": "The label of footer link with label=Guides linking to /academy?type=guide" + }, + "link.item.label.Tutorials": { + "message": "Tutorials", + "description": "The label of footer link with label=Tutorials linking to /academy?type=tutorial" + }, + "link.item.label.Webinars": { + "message": "Webinars", + "description": "The label of footer link with label=Webinars linking to /academy?type=webinar" + }, + "link.item.label.Case studies": { + "message": "Case studies", + "description": "The label of footer link with label=Case studies linking to /academy?type=case-study" } } diff --git a/i18n/nl/docusaurus-theme-classic/navbar.json b/i18n/nl/docusaurus-theme-classic/navbar.json index 54f3e5701..382cb4ca6 100644 --- a/i18n/nl/docusaurus-theme-classic/navbar.json +++ b/i18n/nl/docusaurus-theme-classic/navbar.json @@ -30,5 +30,9 @@ "item.label.Install": { "message": "Installeer", "description": "Navbar item with label Install" + }, + "item.label.Academy": { + "message": "Academy", + "description": "Navbar item with label Academy" } } diff --git a/package-lock.json b/package-lock.json index 157b46e5b..bc414e041 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { - "@conduction/docusaurus-preset": "^3.15.3", + "@conduction/docusaurus-preset": "^3.17.0", "@docusaurus/core": "^3.5.0", "@docusaurus/plugin-client-redirects": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", @@ -1989,9 +1989,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.15.3.tgz", - "integrity": "sha512-ef9++93TPUBdEoJlVBu15IczX4OLttVD60azae9m4yNaQVDrJJNPU1c5Z4W4GP8n8t+98/6G/dTJ06DtnklY+g==", + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.17.0.tgz", + "integrity": "sha512-12m9umfQSsUIcWipJypg50OuuciwB/4cTIpx0G7xrCbvdbZOb3TFR8hAv/FVl+wIDnzU6o9X2RvNSclIdWSI4g==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" @@ -16051,6 +16051,13 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "license": "MIT", + "peer": true + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", diff --git a/package.json b/package.json index b9bbf4fdf..6064f567f 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "docusaurus": "docusaurus", "postinstall": "node scripts/sync-app-downloads.js", "prestart": "node scripts/sync-app-downloads.js", - "start": "docusaurus start", + "start": "docusaurus start --locale en", + "start:nl": "docusaurus start --locale nl", "prebuild": "python3 .github/scripts/app_downloads.py && node scripts/sync-app-downloads.js", "build": "docusaurus build", "postbuild": "node scripts/validate-ai-baseline.mjs", @@ -21,7 +22,7 @@ "ci": "npm ci --legacy-peer-deps && npm run build" }, "dependencies": { - "@conduction/docusaurus-preset": "^3.15.3", + "@conduction/docusaurus-preset": "^3.17.0", "@docusaurus/core": "^3.5.0", "@docusaurus/plugin-client-redirects": "^3.5.0", "@docusaurus/preset-classic": "^3.5.0", diff --git a/plugins/academy-modules/index.js b/plugins/academy-modules/index.js new file mode 100644 index 000000000..7bb138771 --- /dev/null +++ b/plugins/academy-modules/index.js @@ -0,0 +1,268 @@ +/** + * academy-modules — Docusaurus plugin that exposes module groupings. + * + * Scans every `academy/<dated-slug>/index.mdx` frontmatter at build time, + * groups posts by their `module` frontmatter, sorts members by + * `modulePosition`, and writes the result to `globalData` so the + * BlogListPage swizzle and per-module MDX index pages can render + * composite cards and ordered TOCs without re-parsing markdown. + * + * The blog plugin itself only exposes a thin posts array to global + * data (permalink, date, title). We need the full frontmatter — and + * we only need it once per build, not per-render — so a tiny custom + * plugin is the lowest-noise way to get it. + * + * Consumed by: + * - src/theme/BlogListPage (composite ModuleCards + module pill row) + * - <ModulePage module="..."> on per-module MDX index pages + * + * Output shape exposed via `usePluginData('academy-modules')`: + * + * { + * modules: { + * 'deskdesk-tutorial': { + * slug: 'deskdesk-tutorial', + * title: 'Build a Nextcloud app on the Conduction stack', + * lede: 'A four-part path from blank Nextcloud to a published app …', + * parts: [ + * { + * slug: 'deskdesk-tutorial-1-scaffold', + * permalink: '/academy/deskdesk-tutorial-1-scaffold', + * title: 'Build a Nextcloud app on the Conduction stack — Part 1: Scaffold', + * summary: 'Clone the Conduction app template …', + * contentType: 'tutorial', + * durationMinutes: 20, + * audience: ['developer'], + * apps: ['openregister'], + * modulePosition: 1, + * }, + * ... + * ], + * totalMinutes: 95, + * audience: ['developer'], + * apps: ['openregister', 'openconnector'], + * contentTypes: ['tutorial'], + * }, + * ... + * }, + * postModuleIndex: { + * '/academy/deskdesk-tutorial-1-scaffold': 'deskdesk-tutorial', + * ... + * }, + * } + * + * Permalinks mirror the blog plugin's default: when frontmatter has an + * explicit `slug:`, the post is served at `${routeBasePath}/${slug}` + * (routeBasePath defaults to `/academy` in this site). When no slug is + * set, Docusaurus derives one from the filename; we replicate that + * fallback to keep things simple. If the site ever changes + * routeBasePath, pass it via plugin options. + */ + +const fs = require('fs'); +const path = require('path'); + +const SLUG_RE = /^\s*slug:\s*([^\s#]+)\s*$/m; +const TITLE_RE = /^\s*title:\s*(?:"([^"]+)"|'([^']+)'|(.+?))\s*$/m; +const SUMMARY_RE = /^\s*summary:\s*(?:"([^"]+)"|'([^']+)'|(.+?))\s*$/m; +const TYPE_RE = /^\s*contentType:\s*([a-z-]+)\s*$/m; +const DURATION_RE = /^\s*durationMinutes:\s*(\d+)\s*$/m; +const MODULE_RE = /^\s*module:\s*([a-z0-9-]+)\s*$/m; +const MOD_POS_RE = /^\s*modulePosition:\s*(\d+)\s*$/m; +const MOD_TITLE_RE = /^\s*moduleTitle:\s*(?:"([^"]+)"|'([^']+)'|(.+?))\s*$/m; +const MOD_LEDE_RE = /^\s*moduleLede:\s*(?:"([^"]+)"|'([^']+)'|(.+?))\s*$/m; +const DATE_RE = /^\s*date:\s*([0-9-]+)\s*$/m; + +/* `audience: [developer, mkb]` or `apps: [openregister, openconnector]`. + We only ever emit single-line flow-array YAML in the academy + frontmatter, so a regex covers all current and likely-future entries. */ +function arrayField(frontmatter, key) { + const re = new RegExp('^\\s*' + key + ':\\s*\\[([^\\]]*)\\]\\s*$', 'm'); + const m = frontmatter.match(re); + if (!m) return []; + return m[1] + .split(',') + .map((s) => s.trim().replace(/^["']|["']$/g, '')) + .filter(Boolean); +} + +function pickQuoted(match) { + if (!match) return undefined; + return match[1] || match[2] || match[3]; +} + +function extractFrontmatter(mdxPath) { + const raw = fs.readFileSync(mdxPath, 'utf8'); + const match = raw.match(/^---\n([\s\S]*?)\n---/); + if (!match) return null; + const fm = match[1]; + + return { + slug: pickQuoted(fm.match(SLUG_RE)), + title: pickQuoted(fm.match(TITLE_RE)), + summary: pickQuoted(fm.match(SUMMARY_RE)), + contentType: pickQuoted(fm.match(TYPE_RE)), + durationMinutes: fm.match(DURATION_RE) ? parseInt(fm.match(DURATION_RE)[1], 10) : undefined, + module: pickQuoted(fm.match(MODULE_RE)), + modulePosition: fm.match(MOD_POS_RE) ? parseInt(fm.match(MOD_POS_RE)[1], 10) : undefined, + moduleTitle: pickQuoted(fm.match(MOD_TITLE_RE)), + moduleLede: pickQuoted(fm.match(MOD_LEDE_RE)), + date: pickQuoted(fm.match(DATE_RE)), + audience: arrayField(fm, 'audience'), + apps: arrayField(fm, 'apps'), + authors: arrayField(fm, 'authors'), + }; +} + +/* Lightweight authors.yml parser. The academy authors file is a flat + `key: \n name: ... \n image_url: ...` shape; we don't need a full + YAML lib for it. Falls back to {} if the file is missing. */ +function loadAuthors(authorsPath) { + if (!fs.existsSync(authorsPath)) return {}; + const raw = fs.readFileSync(authorsPath, 'utf8'); + const out = {}; + let current = null; + for (const rawLine of raw.split(/\r?\n/)) { + const line = rawLine.replace(/\s+$/, ''); + if (!line || line.startsWith('#')) continue; + const top = line.match(/^([a-z0-9_-]+):\s*$/i); + if (top) { + current = top[1]; + out[current] = {}; + continue; + } + const kv = line.match(/^\s{2}([a-z_]+):\s*(.*)$/); + if (kv && current) { + out[current][kv[1]] = kv[2].replace(/^["']|["']$/g, ''); + } + } + return out; +} + +function kebabToSentence(slug) { + if (!slug) return ''; + return slug + .split('-') + .map((w, i) => (i === 0 ? w[0].toUpperCase() + w.slice(1) : w)) + .join(' '); +} + +function unique(arr) { + return Array.from(new Set(arr.filter(Boolean))); +} + +module.exports = function academyModulesPlugin(_context, options) { + const contentDir = options && options.contentDir ? options.contentDir : 'academy'; + const routeBasePath = options && options.routeBasePath ? options.routeBasePath : '/academy'; + + return { + name: 'academy-modules', + + async loadContent() { + const siteDir = _context.siteDir; + const absDir = path.join(siteDir, contentDir); + if (!fs.existsSync(absDir)) return {modules: {}, postModuleIndex: {}}; + + const entries = fs.readdirSync(absDir, {withFileTypes: true}) + .filter((e) => e.isDirectory()) + .map((e) => path.join(absDir, e.name, 'index.mdx')) + .filter((p) => fs.existsSync(p)); + + const authorsMap = loadAuthors(path.join(absDir, 'authors.yml')); + + const posts = entries + .map((p) => extractFrontmatter(p)) + .filter((fm) => fm && fm.module && fm.modulePosition); + + const byModule = {}; + for (const post of posts) { + const slug = post.module; + if (!byModule[slug]) { + byModule[slug] = { + slug, + title: null, + lede: null, + parts: [], + }; + } + const permalink = post.slug + ? `${routeBasePath}/${post.slug}` + : null; + + byModule[slug].parts.push({ + slug: post.slug, + permalink, + title: post.title, + summary: post.summary, + contentType: post.contentType, + durationMinutes: post.durationMinutes, + audience: post.audience || [], + apps: post.apps || [], + modulePosition: post.modulePosition, + date: post.date, + authors: post.authors || [], + }); + + /* moduleTitle / moduleLede may live on any part (we ask + editors to put them on position 1), so absorb the first + occurrence we encounter. */ + if (!byModule[slug].title && post.moduleTitle) { + byModule[slug].title = post.moduleTitle; + } + if (!byModule[slug].lede && post.moduleLede) { + byModule[slug].lede = post.moduleLede; + } + } + + for (const slug of Object.keys(byModule)) { + const mod = byModule[slug]; + mod.parts.sort((a, b) => a.modulePosition - b.modulePosition); + + if (!mod.title) mod.title = kebabToSentence(slug); + if (!mod.lede && mod.parts[0]) mod.lede = mod.parts[0].summary || ''; + + mod.totalMinutes = mod.parts.reduce((sum, p) => sum + (p.durationMinutes || 0), 0); + mod.audience = unique(mod.parts.flatMap((p) => p.audience)); + mod.apps = unique(mod.parts.flatMap((p) => p.apps)); + mod.contentTypes = unique(mod.parts.map((p) => p.contentType)); + mod.permalink = `${routeBasePath}/modules/${slug}`; + + /* Latest part's date drives the module card's date line — the + module's "most recent activity" reads more truthfully than its + kickoff date once parts trickle in over weeks. */ + mod.latestDate = mod.parts + .map((p) => p.date) + .filter(Boolean) + .sort() + .pop() || null; + + /* Curator = the author of part 1. If part 1 has multiple authors + we take the first; if part 1 has none, we leave the slot empty + and the card skips the avatar row. */ + const curatorKey = mod.parts[0] && mod.parts[0].authors && mod.parts[0].authors[0]; + const curatorYml = curatorKey && authorsMap[curatorKey]; + mod.curator = curatorYml + ? { + key: curatorKey, + name: curatorYml.name || curatorKey, + imageURL: curatorYml.image_url || null, + } + : null; + } + + const postModuleIndex = {}; + for (const slug of Object.keys(byModule)) { + for (const part of byModule[slug].parts) { + if (part.permalink) postModuleIndex[part.permalink] = slug; + } + } + + return {modules: byModule, postModuleIndex}; + }, + + async contentLoaded({content, actions}) { + const {setGlobalData} = actions; + setGlobalData(content); + }, + }; +}; diff --git a/src/data/apps-catalog.js b/src/data/apps-catalog.js index a048a51f4..6aa0ac675 100644 --- a/src/data/apps-catalog.js +++ b/src/data/apps-catalog.js @@ -70,7 +70,7 @@ const PRESENTATION = { }, zaakafhandelapp: { name: 'ZaakAfhandelApp', - tagline: 'Citizen-facing case-status portal. ZGW APIs, archief koppelvlakken, audit trail.', + tagline: 'Citizen-facing case-status portal. ZGW APIs, archive interfaces, audit trail.', href: '/apps/zaakafhandelapp', categories: ['Processes'], icon: <svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9z"/></svg>, @@ -112,7 +112,7 @@ const PRESENTATION = { }, scholiq: { name: 'Scholiq', - tagline: 'Learning record + LMS. Cursussen, inschrijvingen, certificaten, compliance-training.', + tagline: 'Learning record + LMS. Courses, enrolments, certificates, compliance training.', href: '/apps/scholiq', categories: ['Processes'], icon: <svg viewBox="0 0 24 24"><path d="M3 9l9-5 9 5-9 5z"/><path d="M7 11v5c0 1 2.2 2 5 2s5-1 5-2v-5"/><path d="M21 9v6"/></svg>, diff --git a/src/data/partners-catalog.js b/src/data/partners-catalog.js index dedbf591b..94ba64047 100644 --- a/src/data/partners-catalog.js +++ b/src/data/partners-catalog.js @@ -5,9 +5,9 @@ * Both /support (partner directory section) and /partners (filterable * directory page) import from here so the two surfaces never drift. * - * Add a partner: append to PARTNERS. Required: name, tier, summary, - * apps. Add href when a /partners/<slug> detail page exists. Add - * logo when we have permission to display it. + * Add a partner: append to PARTNERS_DATA. Required: name, tier, + * summaryEn, summaryNl, apps. Add href when a /partners/<slug> detail + * page exists. Add logo when we have permission to display it. * * Tier slugs (low → high): * - host Hosts our open-source apps for their customers. May @@ -27,15 +27,18 @@ * /solutions/<slug>) the partner delivers, used both as * a facet on /partners and for the reverse lookup below. * - * Summaries stay in Dutch when the partner's market is Dutch — the - * audience that filters by an MKB hosting partner is the same audience - * that reads the source language. The English page reuses the same - * objects rather than translating partner-side copy. + * Locale: every visible-string field is paired as `<field>En` / + * `<field>Nl`. The `usePartners()` hook resolves them based on the + * current Docusaurus locale, so EN pages render English summaries and + * NL pages render Dutch summaries from the same source. Consume via + * the hook, not the raw PARTNERS_DATA export, so the right summary is + * picked at render time. */ import React from 'react'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -export const PARTNERS = [ +const PARTNERS_DATA = [ // -------------------------------------------------------------- // Certified — top tier, joint roadmap, tender-eligible // -------------------------------------------------------------- @@ -44,7 +47,8 @@ export const PARTNERS = [ tier: 'certified', name: 'Acato', logo: '/img/partners/acato.svg', - summary: <>Digital agency uit Almere. Bouwt toegankelijke websites en webapplicaties voor gemeenten en overheidsorganisaties, met focus op WCAG 2.2 AA en NLDS. Levert de Conduction Woo-solution op OpenCatalogi, OpenRegister en OpenConnector.</>, + summaryEn: <>Digital agency from Almere. Builds accessible websites and web apps for municipalities and government organisations, focused on WCAG 2.2 AA and NLDS. Delivers the Conduction OpenWoo solution on OpenCatalogi, OpenRegister, and OpenConnector.</>, + summaryNl: <>Digital agency uit Almere. Bouwt toegankelijke websites en webapplicaties voor gemeenten en overheidsorganisaties, met focus op WCAG 2.2 AA en NLDS. Levert de Conduction Woo-solution op OpenCatalogi, OpenRegister en OpenConnector.</>, apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], solutions: ['openwoo'], }, @@ -56,7 +60,8 @@ export const PARTNERS = [ tier: 'service', name: 'Shift2', logo: '/img/partners/shift2.png', - summary: <>Nederlandse softwarebouwer voor gemeenten, waterschappen en provincies, onderdeel van de Conxillium-groep. Levert CMS, burgerzaken en formulieren, en daarnaast de Conduction Woo-solution op OpenCatalogi, OpenRegister en OpenConnector.</>, + summaryEn: <>Dutch software builder for municipalities, water boards, and provinces, part of the Conxillium group. Ships CMS, civil affairs, and forms, plus the Conduction OpenWoo solution on OpenCatalogi, OpenRegister, and OpenConnector.</>, + summaryNl: <>Nederlandse softwarebouwer voor gemeenten, waterschappen en provincies, onderdeel van de Conxillium-groep. Levert CMS, burgerzaken en formulieren, en daarnaast de Conduction Woo-solution op OpenCatalogi, OpenRegister en OpenConnector.</>, apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], solutions: ['openwoo'], }, @@ -68,7 +73,8 @@ export const PARTNERS = [ tier: 'host', name: 'Centric', logo: '/img/partners/centric.png', - summary: <>Een van Nederlands grootste IT-leveranciers voor gemeenten en de publieke sector. Werkt samen met <span className="next-blue">Nextcloud</span> aan soevereine werkplekken en host DocuDesk binnen dat traject.</>, + summaryEn: <>One of the Netherlands' largest IT suppliers for municipalities and the public sector. Partners with <span className="next-blue">Nextcloud</span> on sovereign workspaces and hosts DocuDesk inside that programme.</>, + summaryNl: <>Een van Nederlands grootste IT-leveranciers voor gemeenten en de publieke sector. Werkt samen met <span className="next-blue">Nextcloud</span> aan soevereine werkplekken en host DocuDesk binnen dat traject.</>, apps: ['Nextcloud', 'DocuDesk'], solutions: [], }, @@ -77,7 +83,8 @@ export const PARTNERS = [ tier: 'host', name: 'Procolix', logo: '/img/partners/procolix.png', - summary: <>Nederlandse managed-<span className="next-blue">Nextcloud</span>-hoster uit Dordrecht. Levert beheerde Nextcloud-omgevingen op eigen EU-infrastructuur, open-source als uitgangspunt.</>, + summaryEn: <>Dutch managed-<span className="next-blue">Nextcloud</span> hoster from Dordrecht. Delivers managed Nextcloud environments on dedicated EU infrastructure, open source by default.</>, + summaryNl: <>Nederlandse managed-<span className="next-blue">Nextcloud</span>-hoster uit Dordrecht. Levert beheerde Nextcloud-omgevingen op eigen EU-infrastructuur, open-source als uitgangspunt.</>, apps: ['Nextcloud'], solutions: [], }, @@ -85,7 +92,8 @@ export const PARTNERS = [ tier: 'host', name: 'The Goodcloud', logo: '/img/partners/goodcloud.png', - summary: <>Nederlandse managed-<span className="next-blue">Nextcloud</span>-hoster. Privacy-eerst, geen tracking, data in Nederland. Voor MKB, non-profits en privacybewuste organisaties.</>, + summaryEn: <>Dutch managed-<span className="next-blue">Nextcloud</span> hoster. Privacy-first, no tracking, data in the Netherlands. For SMBs, non-profits, and privacy-conscious organisations.</>, + summaryNl: <>Nederlandse managed-<span className="next-blue">Nextcloud</span>-hoster. Privacy-eerst, geen tracking, data in Nederland. Voor MKB, non-profits en privacybewuste organisaties.</>, apps: ['Nextcloud'], solutions: [], }, @@ -93,7 +101,8 @@ export const PARTNERS = [ tier: 'host', name: 'BCT', logo: '/img/partners/bct.png', - summary: <>Nederlandse leverancier van informatie- en documentmanagement-software (Corsa, Verix). Levert de Conduction Woo-solution aan organisaties die hun informatie-governance op orde willen krijgen.</>, + summaryEn: <>Dutch supplier of information- and document-management software (Corsa, Verix). Delivers the Conduction OpenWoo solution to organisations that want their information governance in order.</>, + summaryNl: <>Nederlandse leverancier van informatie- en documentmanagement-software (Corsa, Verix). Levert de Conduction Woo-solution aan organisaties die hun informatie-governance op orde willen krijgen.</>, apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], solutions: ['openwoo'], }, @@ -101,7 +110,8 @@ export const PARTNERS = [ tier: 'host', name: 'Open Gemeenten', logo: '/img/partners/open-gemeenten.png', - summary: <>Open-source platform voor gemeentelijke websites. Bedient ruim 30 gemeenten met toegankelijke sites (WCAG, internet.nl). Levert daarnaast de Conduction Woo-solution.</>, + summaryEn: <>Open-source platform for municipal websites. Serves more than 30 municipalities with accessible sites (WCAG, internet.nl). Also delivers the Conduction OpenWoo solution.</>, + summaryNl: <>Open-source platform voor gemeentelijke websites. Bedient ruim 30 gemeenten met toegankelijke sites (WCAG, internet.nl). Levert daarnaast de Conduction Woo-solution.</>, apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], solutions: ['openwoo'], }, @@ -109,7 +119,8 @@ export const PARTNERS = [ tier: 'host', name: 'xxllnc', logo: '/img/partners/exxellence.png', - summary: <>Nederlandse leverancier van zaakgericht-werken-software voor gemeenten (xxllnc Zaken, plus apps voor belastingen en het sociaal domein). Levert daarnaast de Conduction Woo-solution.</>, + summaryEn: <>Dutch supplier of case-management software for municipalities (xxllnc Zaken, plus apps for taxes and the social domain). Also delivers the Conduction OpenWoo solution.</>, + summaryNl: <>Nederlandse leverancier van zaakgericht-werken-software voor gemeenten (xxllnc Zaken, plus apps voor belastingen en het sociaal domein). Levert daarnaast de Conduction Woo-solution.</>, apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], solutions: ['openwoo'], }, @@ -118,7 +129,8 @@ export const PARTNERS = [ tier: 'host', name: 'YARD', logo: '/img/partners/yard.png', - summary: <>Digital agency uit Utrecht voor gemeenten, zorg en kennisorganisaties. Open-source-georiënteerd, ruim 80 projecten. Levert de Conduction Woo-solution.</>, + summaryEn: <>Digital agency from Utrecht for municipalities, healthcare, and knowledge organisations. Open-source-oriented, more than 80 projects. Delivers the Conduction OpenWoo solution.</>, + summaryNl: <>Digital agency uit Utrecht voor gemeenten, zorg en kennisorganisaties. Open-source-georiënteerd, ruim 80 projecten. Levert de Conduction Woo-solution.</>, apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], solutions: ['openwoo'], }, @@ -126,7 +138,8 @@ export const PARTNERS = [ tier: 'host', name: 'iO', logo: '/img/partners/io.webp', - summary: <>Belgisch-Nederlands digital agency met kantoren in Amsterdam en Rotterdam. Bouwt klantgerichte digitale platforms en levert de Conduction Woo-solution aan publieke en semi-publieke organisaties.</>, + summaryEn: <>Belgian-Dutch digital agency with offices in Amsterdam and Rotterdam. Builds customer-facing digital platforms and delivers the Conduction OpenWoo solution to public and semi-public organisations.</>, + summaryNl: <>Belgisch-Nederlands digital agency met kantoren in Amsterdam en Rotterdam. Bouwt klantgerichte digitale platforms en levert de Conduction Woo-solution aan publieke en semi-publieke organisaties.</>, apps: ['OpenCatalogi', 'OpenRegister', 'OpenConnector'], solutions: ['openwoo'], }, @@ -134,28 +147,83 @@ export const PARTNERS = [ tier: 'host', name: 'Sendent', logo: '/img/partners/sendent.png', - summary: <>Nederlandse leverancier die Microsoft Outlook en Teams koppelt aan <span className="next-blue">Nextcloud</span>. Levert support op MyDash voor klanten die hun mail- en bestandsstroom binnen de eigen Nextcloud-omgeving willen houden.</>, + summaryEn: <>Dutch supplier that connects Microsoft Outlook and Teams to <span className="next-blue">Nextcloud</span>. Provides support on MyDash for customers who want to keep their mail and file flows inside their own Nextcloud environment.</>, + summaryNl: <>Nederlandse leverancier die Microsoft Outlook en Teams koppelt aan <span className="next-blue">Nextcloud</span>. Levert support op MyDash voor klanten die hun mail- en bestandsstroom binnen de eigen Nextcloud-omgeving willen houden.</>, apps: ['MyDash'], solutions: [], }, ]; -/** Reverse lookup: which partners ship a given solution slug. */ -export function partnersBySolution(slug) { - return PARTNERS.filter(p => (p.solutions || []).includes(slug)); +/** + * Resolve the locale-keyed fields on each partner record to the + * current Docusaurus locale. Returns a new array of partner objects + * with `summary` set (instead of `summaryEn`/`summaryNl`). + */ +function localizePartners(locale) { + return PARTNERS_DATA.map((p) => ({ + ...p, + summary: locale === 'nl' ? p.summaryNl : p.summaryEn, + })); +} + +/** Hook: returns the partner list with locale-resolved summaries. */ +export function usePartners() { + const {i18n} = useDocusaurusContext(); + return localizePartners(i18n.currentLocale); +} + +/** Hook: which partners ship a given solution slug. */ +export function usePartnersBySolution(slug) { + return usePartners().filter((p) => (p.solutions || []).includes(slug)); } -/** Reverse lookup: which partners ship a given app name. */ -export function partnersByApp(name) { - return PARTNERS.filter(p => (p.apps || []).includes(name)); +/** Hook: which partners ship a given app name. */ +export function usePartnersByApp(name) { + return usePartners().filter((p) => (p.apps || []).includes(name)); } -export const totalPartners = PARTNERS.length; +/** Look up a partner by name, returning the locale-keyed record + * (still carries summaryEn + summaryNl). Useful inside an MDX export + * where hooks can't run; use with `usePartnerSummary(p)` below to + * resolve the summary at render time. */ +export function partnerByName(name) { + return PARTNERS_DATA.find((p) => p.name === name); +} + +/** Hook: resolve `partner.summary` to the current locale on a raw + * PARTNERS_DATA record. Pairs with `partnerByName()` so per-page MDX + * exports can stay simple and the localised summary is selected when + * rendering. */ +export function usePartnerSummary(partner) { + const {i18n} = useDocusaurusContext(); + if (!partner) return null; + return i18n.currentLocale === 'nl' ? partner.summaryNl : partner.summaryEn; +} -export const BECOME_PARTNER = { +export const totalPartners = PARTNERS_DATA.length; + +const BECOME_PARTNER_DATA = { href: '/support#become-a-partner', - eyebrow: 'Become a partner', - title: 'Ship Conduction to your customers.', - body: 'Three tiers: Host (ship our apps), Service (SLA + third-line support), Certified (trained, joint roadmap, tender-eligible). The apps stay open source, the relationship stays direct.', - ctaLabel: 'Apply through Support', + eyebrowEn: 'Become a partner', + eyebrowNl: 'Word partner', + titleEn: 'Ship Conduction to your customers.', + titleNl: 'Lever Conduction aan je klanten.', + bodyEn: 'Three tiers: Host (ship our apps), Service (SLA + third-line support), Certified (trained, joint roadmap, tender-eligible). The apps stay open source, the relationship stays direct.', + bodyNl: 'Drie niveaus: Host (lever onze apps), Service (SLA + derdelijns support), Certified (getraind, gezamenlijke roadmap, aanbestedings-eligible). De apps blijven open source, het contact blijft direct.', + ctaLabelEn: 'Apply through Support', + ctaLabelNl: 'Aanmelden via Support', }; + +/** Hook: returns the "become a partner" call-to-action with locale- + * resolved copy. */ +export function useBecomePartner() { + const {i18n} = useDocusaurusContext(); + const nl = i18n.currentLocale === 'nl'; + return { + href: BECOME_PARTNER_DATA.href, + eyebrow: nl ? BECOME_PARTNER_DATA.eyebrowNl : BECOME_PARTNER_DATA.eyebrowEn, + title: nl ? BECOME_PARTNER_DATA.titleNl : BECOME_PARTNER_DATA.titleEn, + body: nl ? BECOME_PARTNER_DATA.bodyNl : BECOME_PARTNER_DATA.bodyEn, + ctaLabel: nl ? BECOME_PARTNER_DATA.ctaLabelNl : BECOME_PARTNER_DATA.ctaLabelEn, + }; +} diff --git a/src/data/sidecars-catalog.js b/src/data/sidecars-catalog.js index be1341c7f..95fb72139 100644 --- a/src/data/sidecars-catalog.js +++ b/src/data/sidecars-catalog.js @@ -27,7 +27,7 @@ const PRESENTATION = { }, openzaak: { name: 'OpenZaak', - tagline: 'Sidecar for the OpenZaak case-management API. Koppelvlakken voor ZGW, vanuit Nextcloud.', + tagline: 'Sidecar for the OpenZaak case-management API. ZGW interfaces, from inside Nextcloud.', href: '/sidecars/openzaak', categories: ['Registers'], icon: <svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9z"/></svg>, diff --git a/src/data/solutions-catalog.js b/src/data/solutions-catalog.js index 3524c7869..08c08bb43 100644 --- a/src/data/solutions-catalog.js +++ b/src/data/solutions-catalog.js @@ -16,7 +16,7 @@ import React from 'react'; export const SOLUTIONS = [ { slug: 'openwoo', - href: '/solutions/openwoo', sector: 'public', sectorLabel: 'Publieke sector', + href: '/solutions/openwoo', sector: 'public', sectorLabel: 'Public sector', title: 'OpenWoo. Wet open overheid, by Friday.', shortTitle: 'OpenWoo', outcome: <>Live OpenWoo-portal at your hosted <span className="next-blue">Nextcloud</span>. Each Wet open overheid-categorie becomes a register, every register publishes through OpenCatalogi, every connector ingests from your DMS.</>, @@ -25,28 +25,28 @@ export const SOLUTIONS = [ }, { slug: 'zaakafhandeling', - href: '/solutions/zaakafhandeling', sector: 'public', sectorLabel: 'Publieke sector', + href: '/solutions/zaakafhandeling', sector: 'public', sectorLabel: 'Public sector', title: <>ZaakAfhandelApp on <span className="next-blue">Nextcloud</span>.</>, shortTitle: 'Zaakafhandeling', - outcome: <>Vier apps, één zaakafhandeling. Burger-CRM, zaakworkflow en automatisering op de <span className="next-blue">Nextcloud</span> waar je team al inlogt.</>, + outcome: <>Four apps, one case management system. Citizen CRM, case workflow, and automation on the <span className="next-blue">Nextcloud</span> your team already logs into.</>, builtOn: ['OpenRegister', 'PipelinQ', 'Procest', 'Windmill'], goals: ['workflow'], status: 'production', icon: <svg viewBox="0 0 24 24"><path d="M9 11V7a3 3 0 0 1 6 0v4"/><rect x="5" y="11" width="14" height="10" rx="2"/></svg>, }, { slug: 'archief', - href: '/solutions/archief', sector: 'public', sectorLabel: 'Publieke sector', - title: 'Archief naar e-Depot, automatisch.', - shortTitle: 'Archief', - outcome: 'OpenRegister bewaart je actuele data, OpenConnector zet records bewaartermijn-driven over naar je e-Depot. NEN-2082 gemapt, eIDAS-AdES verzegeld.', + href: '/solutions/archief', sector: 'public', sectorLabel: 'Public sector', + title: 'Archive transfer to e-Depot, automated', + shortTitle: 'Archive', + outcome: <>OpenRegister stores your live data, OpenConnector moves records on schedule to your e-Depot. NEN-2082 mapped, eIDAS-AdES sealed.</>, builtOn: ['OpenRegister', 'OpenConnector'], goals: ['compliance'], status: 'production', icon: <svg viewBox="0 0 24 24"><path d="M3 4h18v4H3z"/><path d="M5 8v12h14V8"/><path d="M9 12h6"/></svg>, }, { slug: 'anonimiseren', - href: '/solutions/anonimiseren', sector: ['public', 'mkb'], sectorLabel: 'Publiek & MKB', - title: 'Anonimiseren, voor je het publiceert.', - shortTitle: 'Anonimiseren', - outcome: <>Privacy-by-design pipeline op <span className="next-blue">Nextcloud</span>. DocuDesk en OpenRegister sporen PII op en redacteren records en documenten voordat ze de organisatie verlaten. Presidio onder de motorkap, AVG-uitlegbaar.</>, + href: '/solutions/anonimiseren', sector: ['public', 'mkb'], sectorLabel: 'Public & SMB', + title: 'Anonymize before you publish.', + shortTitle: 'Anonymize', + outcome: <>Privacy-by-design pipeline on <span className="next-blue">Nextcloud</span>. DocuDesk and OpenRegister detect and redact records and documents before they leave your organization. Presidio under the hood, GDPR-explainable.</>, builtOn: ['DocuDesk', 'OpenRegister', 'Presidio'], goals: ['compliance'], status: 'pilot', icon: <svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M5 21v-2a4 4 0 0 1 4-4h6a4 4 0 0 1 4 4v2"/><path d="M9 8h6"/></svg>, }, diff --git a/src/pages/academy/modules/deskdesk-tutorial.mdx b/src/pages/academy/modules/deskdesk-tutorial.mdx new file mode 100644 index 000000000..02ee02c1b --- /dev/null +++ b/src/pages/academy/modules/deskdesk-tutorial.mdx @@ -0,0 +1,25 @@ +--- +title: "Build a Nextcloud app on the Conduction stack" +description: "Four-part module that takes you from a blank Nextcloud to a DeskDesk app published on the Conduction app store." +--- + +import {Prerequisites, PrerequisiteItem, Outcomes, Outcome, ModulePage} from '@conduction/docusaurus-preset/components'; + +<ModulePage module="deskdesk-tutorial"> + +This module builds DeskDesk, a working desk-booking app, on the full Conduction stack. You scaffold the app, declare its schemas, plug it into NC Calendar with one config block, layer xWiki knowledge articles, and ship to the Conduction app store. Each part is self-contained. Skip ahead if a chapter looks familiar. + +<Outcomes title="What you build"> + <Outcome>A working DeskDesk Nextcloud app, in your local environment.</Outcome> + <Outcome>Three OpenRegister schemas (desk, booking, floor) declared in a single manifest.</Outcome> + <Outcome>NC Calendar integration without writing a controller or event listener.</Outcome> + <Outcome>Per-desk knowledge articles sourced from xWiki, packaged and published.</Outcome> +</Outcomes> + +<Prerequisites title="Before you start"> + <PrerequisiteItem>A Nextcloud on localhost (see the [Run Nextcloud locally](/academy/nextcloud-lokaal-draaien) tutorial).</PrerequisiteItem> + <PrerequisiteItem>OpenRegister installed and running.</PrerequisiteItem> + <PrerequisiteItem>Node 20 and PHP 8.3 locally, the standard Conduction dev stack.</PrerequisiteItem> +</Prerequisites> + +</ModulePage> diff --git a/src/pages/academy/modules/openwoo-getting-started.mdx b/src/pages/academy/modules/openwoo-getting-started.mdx new file mode 100644 index 000000000..555f37415 --- /dev/null +++ b/src/pages/academy/modules/openwoo-getting-started.mdx @@ -0,0 +1,25 @@ +--- +title: "Get started with OpenWoo" +description: "Two tutorials that get you set up in half an hour to save Woo publications and upload files according to the Dutch Freedom of Information Act." +--- + +import {Prerequisites, PrerequisiteItem, Outcomes, Outcome, ModulePage} from '@conduction/docusaurus-preset/components'; + +<ModulePage module="openwoo-getting-started"> + +The Dutch Freedom of Information Act (Woo) requires government bodies to publish documents in eleven information categories. This module is the fastest path from empty register to published Woo files: import the canonical Woo register with all TOOI categories, then link documents to publications via the OpenRegister API. + +<Outcomes title="What you learn"> + <Outcome>How to import the canonical Woo register into OpenRegister.</Outcome> + <Outcome>Which TOOI categories come by default, and when to extend them.</Outcome> + <Outcome>Four ways to attach files to a Woo publication, with tested API calls.</Outcome> + <Outcome>Publish, unpublish, and clean up via the API.</Outcome> +</Outcomes> + +<Prerequisites title="Before you start"> + <PrerequisiteItem>A Nextcloud with OpenRegister installed (see [Run Nextcloud locally](/academy/nextcloud-lokaal-draaien)).</PrerequisiteItem> + <PrerequisiteItem>A working REST client (curl, HTTPie, Bruno, or Postman).</PrerequisiteItem> + <PrerequisiteItem>A user with write permissions on the Woo register.</PrerequisiteItem> +</Prerequisites> + +</ModulePage> diff --git a/src/pages/apps/decidesk.mdx b/src/pages/apps/decidesk.mdx index 9f4972d12..6f995ed55 100644 --- a/src/pages/apps/decidesk.mdx +++ b/src/pages/apps/decidesk.mdx @@ -29,34 +29,34 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <div style={{minWidth: 0}}> <SectionHead eyebrow="What it does" - title="Van agenda tot besluit, in één install." + title="From agenda to decision, in one install." align="stack" - lede="DeciDesk dekt de hele besluitvormings-keten: agendapunten verzamelen, dossiers opbouwen, moties indienen, stemmen registreren, besluiten publiceren. Allemaal op typed registers met een audit trail die het Woo-werk later veel eenvoudiger maakt." + lede="DeciDesk covers the full decision-making chain: collect agenda items, build dossiers, submit motions, record votes, publish decisions. All on typed registers with an audit trail that makes Woo compliance much simpler later." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>} - title="Agenda's met dossiervorming." + title="Agendas with dossier creation." > - Per vergaderpunt een dossier: ambtelijk advies, raadsstuk, presentaties, beantwoorde vragen. Allemaal versioneed, allemaal in OpenRegister. + One dossier per agenda item: official advice, council documents, presentations, answered questions. All versioned, all in OpenRegister. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 8.4 8.4 0 0 1-3.4-.7L3 21l1.5-5.4A8.4 8.4 0 1 1 21 11.5z"/></svg>} - title="Moties en amendementen." + title="Motions and amendments." > - Indienen, koppelen aan een agendapunt, stemmen registreren. Per-fractie of per-individu, configureerbaar per bestuur. + Submit, link to an agenda item, record votes. Per faction or per individual, configurable per board. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M12 2v4M12 18v4M2 12h4M18 12h4"/><circle cx="12" cy="12" r="4"/></svg>} - title="Stemmingen, getrackt in het audit trail." + title="Voting, tracked in the audit trail." > - Hoofdelijk of bij handopsteken; uitkomst, individuele stemmen, abstaining members. Allemaal in een tamper-evident log. + Voice or by show of hands; outcome, individual votes, abstentions. All in a tamper-evident log. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} - title="Besluiten als gepubliceerde documenten." + title="Decisions as published documents." > - DocuDesk genereert het formele besluit uit de stemresultaten en raadsbespreking. OpenCatalogi publiceert het op jouwgemeente.nl/besluiten met citation-stable URL. + DocuDesk generates the formal decision from vote results and council discussion. OpenCatalogi publishes it on yourmunicipality.nl/decisions with citation-stable URLs. </FeatureItem> </FeatureList> </div> @@ -93,7 +93,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai tone: 'workspace', eyebrow: 'In every queue', title: 'Action items land in the assignee\'s MyDash.', - summary: 'Every action item from a meeting becomes a row in someone\'s werkvoorraad. No separate task tracker, no email handoff. The next meeting reads its predecessor\'s status from the same registers.', + summary: 'Every action item from a meeting becomes a row in someone\'s work queue. No separate task tracker, no email handoff. The next meeting reads its predecessor\'s status from the same registers.', cta: {label: 'See MyDash', href: '/apps/mydash'}, panel: <AppMock app="mydash-widgets" />, }, @@ -161,9 +161,9 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai </div> <div style={{display: 'flex', flexDirection: 'column', gap: 5}}> {[ - {accent: true, date: '14 Mei'}, - {accent: false, date: '18 Mei'}, - {accent: false, date: '25 Mei'}, + {accent: true, date: '14 May'}, + {accent: false, date: '18 May'}, + {accent: false, date: '25 May'}, ].map((row, i) => ( <div key={i} style={{display: 'flex', alignItems: 'center', gap: 8}}> <div style={{fontFamily: 'var(--conduction-typography-font-family-code)', fontSize: 10, fontWeight: 600, color: row.accent ? 'var(--c-orange-knvb)' : 'var(--c-cobalt-400)', minWidth: 36}}>{row.date}</div> @@ -275,19 +275,19 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai href="/apps/openregister" icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg>} name="OpenRegister" - why="Agendapunten, dossiers, moties, stemmen als typed registers." + why="Agenda items, dossiers, motions, votes as typed registers." /> <PairCard href="/apps/docudesk" icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} name="DocuDesk" - why="Besluiten, raadstukken, beantwoorde vragen uit dossier-velden." + why="Decisions, council documents, answered questions from dossier fields." /> <PairCard href="/apps/opencatalogi" icon={<svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4M3 17l9 4 9-4"/></svg>} name="OpenCatalogi" - why="Publiceert besluiten en raadstukken aan jouwgemeente.nl/besluiten met federation." + why="Publishes decisions and council documents on yourmunicipality.nl/decisions with federation." /> </PairRow> </Section> diff --git a/src/pages/apps/mydash.mdx b/src/pages/apps/mydash.mdx index c6a97b42c..f71c52c5d 100644 --- a/src/pages/apps/mydash.mdx +++ b/src/pages/apps/mydash.mdx @@ -31,7 +31,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai eyebrow="What it does" title="Drag, drop, dashboard." align="stack" - lede="MyDash reads your registers, your urenregistratie, je facturatie. Drag chart types onto a board, save, share with the team. Role-based access uses the Nextcloud groups your team is already in." + lede="MyDash reads your registers, your time tracking, your invoicing. Drag chart types onto a board, save, share with the team. Role-based access uses the Nextcloud groups your team is already in." /> <FeatureList> <FeatureItem @@ -99,7 +99,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai tone: 'lavender', eyebrow: 'Widget integration', title: 'Every Nextcloud app brings its own widget.', - summary: 'DocuDesk ships an upload-to-anonymise dropzone. Procest ships a werkvoorraad. Mail ships an inbox. Calendar ships upcoming events. Any app that registers a Nextcloud dashboard widget shows up in MyDash automatically, no separate config.', + summary: 'DocuDesk ships an upload-to-anonymise dropzone. Procest ships a task queue. Mail ships an inbox. Calendar ships upcoming events. Any app that registers a Nextcloud dashboard widget shows up in MyDash automatically, no separate config.', cta: {label: 'Browse the widget catalogue', href: 'https://docs.conduction.nl/mydash#widgets'}, panel: <AppMock app="mydash-widgets" />, }, @@ -153,7 +153,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai ), }, { - title: 'Werkvoorraad from Procest', + title: 'Task queue from Procest', desc: 'Any app that registers a Nextcloud dashboard widget shows up here. Procest, DocuDesk, Mail, you name it.', panel: ( <div style={{display: 'flex', flexDirection: 'column', gap: 6, fontFamily: 'var(--conduction-typography-font-family-body)'}}> diff --git a/src/pages/apps/openbuilt.mdx b/src/pages/apps/openbuilt.mdx index 8a2c32f08..a7d341bdf 100644 --- a/src/pages/apps/openbuilt.mdx +++ b/src/pages/apps/openbuilt.mdx @@ -28,40 +28,40 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <div style={{minWidth: 0}}> <SectionHead eyebrow="What it does" - title="Bouw je eigen app, zonder code." + title="Build your own app, no code required." align="stack" - lede="OpenBuilt laat niet-technische gebruikers eigen apps samenstellen uit de bouwstenen van het Conduction-ecosysteem. Schema's uit OpenRegister, integraties uit OpenConnector, workflows uit Procest, documenten uit DocuDesk. Een wizard staat de app op, drie tiers (development, staging, productie) leveren een productiepad, en de bovenbalk-entry verschijnt zodra je publiceert. Export naar een echte Nextcloud-app volgt in fase 2." + lede="OpenBuilt lets non-technical users compose their own apps from the building blocks of the Conduction ecosystem. Schemas from OpenRegister, integrations from OpenConnector, workflows from Procest, documents from DocuDesk. A wizard sets up the app, three tiers (development, staging, production) provide a production path, and the top-bar entry appears as soon as you publish. Export to a real Nextcloud app comes in phase 2." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 10h18M9 4v16"/></svg>} - title="Wizard in drie stappen." + title="Three-step wizard." > - Slug, versie-ketting, rechten. Klikken op klaar geeft je een werkende app, eigen registers voor development, staging en productie, en de eerste pagina-set. Geen JSON, geen scaffolding. + Slug, version chain, permissions. Click done and you have a working app, dedicated registers for development, staging and production, and the first page set. No JSON, no scaffolding. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M3 12h6l3-7 3 14 3-7h3"/></svg>} - title="Workflows declaratief, geen service-code." + title="Workflows declarative, no service code." > - State machines, aggregaties, berekeningen en notificaties leven in de schema-metadata. Geen PHP-service per app, conform ADR-031. + State machines, aggregations, calculations and notifications live in the schema metadata. No PHP service per app, per ADR-031. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><circle cx="6" cy="6" r="2"/><circle cx="18" cy="6" r="2"/><circle cx="6" cy="18" r="2"/><circle cx="18" cy="18" r="2"/><path d="M8 6h8M6 8v8M18 8v8M8 18h8"/></svg>} - title="Dev, staging, productie per app." + title="Dev, staging, production per app." > - Elke versie krijgt z'n eigen register. Promote tussen tiers met drie strategieën. Rollback is één klik op de productionVersion-pointer. + Each version gets its own register. Promote between tiers with three strategies. Rollback is one click on the productionVersion pointer. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M3 12l9-9 9 9-9 9z"/><path d="M12 3v18M3 12h18"/></svg>} - title="Eén Nextcloud, één bovenbalk." + title="One Nextcloud, one top bar." > - Elke gepubliceerde app krijgt automatisch een eigen top-bar-entry naast Files en Mail. Met per-app icoon, eigen rechten en geen extra installatie. + Each published app gets its own top-bar entry automatically, next to Files and Mail. With per-app icon, own permissions, no extra installation. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M5 12l5 5L20 7"/></svg>} - title="Virtuele app eerst, echte app als optie." + title="Virtual app first, real app as an option." > - Virtuele apps draaien in de OpenBuilt-shell. Klaar voor productie? Exporteer naar een echte Nextcloud-app met eigen ID en namespace (Phase 2). + Virtual apps run in the OpenBuilt shell. Ready for production? Export to a real Nextcloud app with its own ID and namespace (Phase 2). </FeatureItem> </FeatureList> </div> @@ -75,86 +75,86 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <McpToolShelf eyebrow="AI chat tools" - title="Wat OpenBuilt aan de workspace-AI toevoegt." - lede="Installeer OpenBuilt en de chat krijgt acht tools waarmee de AI virtuele apps bouwt: een app aanmaken, een schema toevoegen, een pagina opzetten, een widget plaatsen, een versie promoveren. Elke aanroep loopt onder de eigen rol van de gebruiker en wordt geaudit." + title="What OpenBuilt adds to the workspace AI." + lede="Install OpenBuilt and the chat gets eight tools so the AI builds virtual apps: create an app, add a schema, set up a page, place a widget, promote a version. Every call runs under your own role and is audited." tools={[ { icon: <svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h10"/></svg>, - name: 'Virtuele apps opsommen', + name: 'List virtual apps', id: 'openbuilt.listApps', - description: 'Toon de virtuele apps in je organisatie met hun status en versie.', + description: 'Show the virtual apps in your organization with their status and version.', status: 'stable', }, { icon: <svg viewBox="0 0 24 24"><path d="M5 4h14v6H5zM5 14h14v6H5zM9 4v16"/></svg>, - name: 'App-manifest ophalen', + name: 'Get app manifest', id: 'openbuilt.getAppManifest', - description: 'Haal het runtime-manifest van één gepubliceerde virtuele app op via de slug.', + description: 'Fetch the runtime manifest of one published virtual app by slug.', status: 'stable', }, { icon: <svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>, - name: 'Nieuwe app aanmaken', + name: 'Create new app', id: 'openbuilt.createApp', - description: 'Maak een nieuwe virtuele app met een eerste draft-versie. Kies het versie-pad: single, dev-prod of dev-staging-prod.', + description: 'Create a new virtual app with a first draft version. Choose the version path: single, dev-prod, or dev-staging-prod.', status: 'stable', }, { icon: <svg viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></svg>, - name: 'Versie promoveren', + name: 'Promote version', id: 'openbuilt.promoteVersion', - description: 'Promoveer een virtuele app van bijvoorbeeld development naar productie. Default-strategie laat de doelversie leeg.', + description: 'Promote a virtual app from, for example, development to production. Default strategy leaves the target version empty.', status: 'stable', }, { icon: <svg viewBox="0 0 24 24"><path d="M3 7h18M6 12h12M9 17h6"/></svg>, - name: 'Schema toevoegen of bijwerken', + name: 'Add or update schema', id: 'openbuilt.upsertSchema', - description: 'Voeg een JSON Schema toe of werk het bij. De slug krijgt automatisch app- en versie-prefix.', + description: 'Add or update a JSON schema. The slug automatically gets app and version prefix.', status: 'stable', }, { icon: <svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18"/></svg>, - name: 'Pagina toevoegen of bijwerken', + name: 'Add or update page', id: 'openbuilt.upsertPage', - description: 'Maak of werk een pagina bij in het draft-manifest. Type is dashboard, index, detail of form.', + description: 'Create or update a page in the draft manifest. Type is dashboard, index, detail, or form.', status: 'stable', }, { icon: <svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>, - name: 'Widget plaatsen', + name: 'Place widget', id: 'openbuilt.addWidget', - description: 'Voeg een widget toe aan een pagina: stat-counter, grafiek, lijst of een ander type uit de catalogus.', + description: 'Add a widget to a page: stat-counter, chart, list, or another type from the catalog.', status: 'stable', }, { icon: <svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16"/></svg>, - name: 'Menu-item toevoegen of bijwerken', + name: 'Add or update menu item', id: 'openbuilt.upsertMenuItem', - description: 'Maak of werk een top-level menu-item bij in het draft-manifest. Route, volgorde en icoon stel je per item in.', + description: 'Create or update a top-level menu item in the draft manifest. Route, order, and icon are set per item.', status: 'stable', }, ]} showIds={false} trace={<AgentTrace lines={[ - {kind: 'tool', text: 'openbuilt - createApp (MCP)(name: "Subsidie-aanvraag",'}, - {kind: 'continuation', text: 'preset: "dev-prod", description: "Aanvragen + behandeling")'}, - {kind: 'note', bullet: 'done', text: 'App aangemaakt, draft v0.1 klaar'}, - {kind: 'tool', text: 'openbuilt - upsertSchema (MCP)(appSlug: "subsidie-aanvraag",'}, - {kind: 'continuation', text: 'slug: "aanvraag", properties: { naam, bedrag, status }, required: ["naam"])'}, - {kind: 'note', bullet: 'done', text: 'Schema toegevoegd aan development-versie'}, - {kind: 'status', text: 'Pagina opbouwen'}, + {kind: 'tool', text: 'openbuilt - createApp (MCP)(name: "Grant application",'}, + {kind: 'continuation', text: 'preset: "dev-prod", description: "Applications + processing")'}, + {kind: 'note', bullet: 'done', text: 'App created, draft v0.1 ready'}, + {kind: 'tool', text: 'openbuilt - upsertSchema (MCP)(appSlug: "grant-application",'}, + {kind: 'continuation', text: 'slug: "application", properties: { name, amount, status }, required: ["name"])'}, + {kind: 'note', bullet: 'done', text: 'Schema added to development version'}, + {kind: 'status', text: 'Building page'}, ]} cursor - mode="audit log on (elke aanroep wordt vastgelegd)" + mode="audit log on (every call is recorded)" />} /> <Section spacing="default" background="tinted"> <SectionHead eyebrow="Pairs well with" - title="De apps die OpenBuilt aan elkaar knoopt." + title="The apps that OpenBuilt ties together." align="stack" /> <PairRow> @@ -162,26 +162,26 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai href="/apps/openregister" icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg>} name="OpenRegister" - why="Elk virtueel app-manifest en alle objecten zijn OpenRegister-rijen." + why="Every virtual app manifest and all objects are OpenRegister rows." /> <PairCard href="/apps/openconnector" icon={<svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>} name="OpenConnector" - why="Externe API's en bestand-bronnen koppelen zonder code." + why="Connect external APIs and file sources without code." /> <PairCard href="/apps/procest" icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>} name="Procest" - why="Workflows en goedkeurings-stappen voor virtual-app processen." + why="Workflows and approval steps for virtual app processes." /> </PairRow> </Section> <CtaBanner - title="Citizen-developer apps, in één Nextcloud." - lede={<>OpenBuilt geeft niet-technische gebruikers de bouwblokken om eigen apps samen te stellen. Schema's, koppelvlakken, workflows, allemaal in handen van wie het probleem kent.</>} + title="Citizen-developer apps, in one Nextcloud." + lede={<>OpenBuilt gives non-technical users the building blocks to compose their own apps. Schemas, connectors, workflows, all in the hands of those who know the problem.</>} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Talk to a partner', href: '/partners'}} /> diff --git a/src/pages/apps/scholiq.mdx b/src/pages/apps/scholiq.mdx index 33d574e72..fbca33ee3 100644 --- a/src/pages/apps/scholiq.mdx +++ b/src/pages/apps/scholiq.mdx @@ -14,8 +14,8 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai version="v0.x" locales="NL · EN" title="Scholiq" - tagline={<>Leerlingvolgsysteem en LMS op <span className="next-blue">Nextcloud</span>. Cursussen, inschrijvingen, aanwezigheid, nakijken, certificaten. Voor scholen, opleiders en compliance-teams die hun leerproces niet bij een externe SaaS willen parkeren.</>} - intro={<>Scholiq is an open-source education app for the Nextcloud workspace. It runs as a leerlingvolgsysteem (LVS) for Dutch primary and secondary schools, as a full LMS for higher education, and as a compliance-training engine for MKB and government bodies. Scholiq connects natively to DUO BRON/ROD, OSO transfer, SURFconext, IMS QTI 3.0, and EDCI / Open-Badges 3.0, with EU AI Act gating built in from day one. Released under EUPL-1.2 and maintained by Conduction since 2019.</>} + tagline={<>Learning record system and LMS on <span className="next-blue">Nextcloud</span>. Courses, enrolments, attendance, grading, certificates. For schools, training providers and compliance teams who won't park their learning process at an external SaaS.</>} + intro={<>Scholiq is an open-source education app for the Nextcloud workspace. It runs as a learning record system (LRS) for Dutch primary and secondary schools, as a full LMS for higher education, and as a compliance-training engine for MKB and government bodies. Scholiq connects natively to DUO BRON/ROD, OSO transfer, SURFconext, IMS QTI 3.0, and EDCI / Open-Badges 3.0, with EU AI Act gating built in from day one. Released under EUPL-1.2 and maintained by Conduction since 2019.</>} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the docs', href: 'https://scholiq.conduction.nl/'}} tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/scholiq'}} @@ -28,34 +28,34 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <div style={{minWidth: 0}}> <SectionHead eyebrow="What it does" - title="Eén leeromgeving voor cursussen, cohorten en certificeringen." + title="One learning environment for courses, cohorts and certificates." align="stack" - lede="Scholiq pakt het hele leerproces: cursussen opzetten, deelnemers inschrijven, aanwezigheid en voortgang volgen, opdrachten verzamelen en nakijken, certificaten uitreiken. Alles op typed registers in OpenRegister, dus de cijfers in MyDash en de rapportages voor de inspectie blijven kloppen." + lede="Scholiq handles the whole learning process: set up courses, enrol participants, track attendance and progress, collect and grade assignments, award certificates. Everything on typed registers in OpenRegister, so the grades in MyDash and inspection reports stay correct." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M4 6h16v12H4z"/><path d="M4 10h16M9 6v12"/></svg>} - title="Cursussen en inschrijvingen." + title="Courses and enrolments." > - Bouw een cursus uit modules, koppel lesmateriaal en data, schrijf deelnemers of hele cohorten in. Voor doorlopend onderwijs én voor één-daagse trainingen. + Build a course from modules, attach learning materials and data, enrol individual participants or whole cohorts. For ongoing education and one-day training alike. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M9 13l2 2 4-4"/></svg>} - title="Opdrachten en nakijken." + title="Assignments and grading." > - Stel een opdracht uit, deelnemers leveren in, jij geeft feedback en een cijfer. De volledige geschiedenis blijft per leerling beschikbaar in zijn dossier. + Issue an assignment, participants submit, you give feedback and a grade. The full history stays available per learner in their record. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>} - title="Aanwezigheid en voortgang." + title="Attendance and progress." > - Wie was er, wie heeft de module afgerond, wie staat waar in zijn leerplan. Zonder losse Excel-lijsten, zonder schaduw-administratie. + Who was there, who completed the module, where each learner stands in their plan. No loose Excel spreadsheets, no shadow administration. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M12 2l3 6 6 .9-4.5 4.2 1.1 6L12 16l-5.6 3.1 1.1-6L3 8.9 9 8z"/></svg>} - title="Certificaten en compliance-training." + title="Certificates and compliance training." > - Reik certificaten uit met geldigheid en hercertificering. Voor BHV, AVG, integriteits-trainingen of de jaarlijkse cybersecurity-cursus: het systeem herinnert je vanzelf wie weer aan de beurt is. + Award certificates with validity and recertification dates. For workplace safety, GDPR, integrity training or your annual cybersecurity course: the system reminds you automatically who is due. </FeatureItem> </FeatureList> </div> @@ -70,7 +70,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <Section spacing="default" background="tinted"> <SectionHead eyebrow="Pairs well with" - title="De apps waarop Scholiq leunt." + title="The apps Scholiq depends on." align="stack" /> <PairRow> @@ -78,26 +78,26 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai href="/apps/openregister" icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg>} name="OpenRegister" - why="Deelnemers, cursussen, inschrijvingen, cijfers en certificaten als typed registers." + why="Participants, courses, enrolments, grades and certificates as typed registers." /> <PairCard href="/apps/docudesk" icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} name="DocuDesk" - why="Genereert certificaten, cursusverslagen en deelnemerslijsten uit sjablonen." + why="Generates certificates, course reports and participant lists from templates." /> <PairCard href="/apps/mydash" icon={<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>} name="MyDash" - why="Mijn cursussen, na te kijken werk, en hercertificeringen op het dashboard." + why="My courses, work to grade, and recertifications on the dashboard." /> </PairRow> </Section> <CtaBanner - title="Leren bij je eigen organisatie, niet bij een externe leverancier." - lede={<>Scholiq houdt het leerproces — van cursusinschrijving tot certificering — op je eigen Nextcloud. Open-source, op je eigen data, klaar voor inspectie en compliance.</>} + title="Learning at your own organisation, not at an external vendor." + lede={<>Scholiq keeps the learning process — from course enrolment to certification — on your own Nextcloud. Open-source, on your own data, ready for inspection and compliance.</>} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Talk to a partner', href: '/partners'}} /> diff --git a/src/pages/apps/shillinq.mdx b/src/pages/apps/shillinq.mdx index b52541345..52379fc5e 100644 --- a/src/pages/apps/shillinq.mdx +++ b/src/pages/apps/shillinq.mdx @@ -28,28 +28,28 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <div style={{minWidth: 0}}> <SectionHead eyebrow="What it does" - title="Een complete bedrijfsadministratie op Nextcloud." + title="Complete business administration on Nextcloud." align="stack" - lede="Shillinq pakt facturatie, contracten en inkoop in één app. Geen aparte boekhoudsoftware, geen aparte CRM, geen los inkoopportaal. Alles staat in OpenRegister, dus de cijfers in MyDash en de rapportages in OpenCatalogi lopen synchroon." + lede="Shillinq handles invoicing, contracts and procurement in one app. No separate accounting software, no separate CRM, no standalone procurement portal. Everything lives in OpenRegister, so the figures in MyDash and reports in OpenCatalogi stay in sync." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} - title="Facturatie en debiteuren." + title="Invoicing and receivables." > - Genereer facturen uit sjablonen. Stuur ze direct uit Nextcloud Mail. Volg openstaande bedragen in MyDash. + Generate invoices from templates. Send them straight from Nextcloud Mail. Track outstanding amounts in MyDash. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="9"/></svg>} - title="Contracten met audit trail." + title="Contracts with audit trail." > - Versioned contracten in OpenRegister. Zie wie wat heeft getekend, wanneer een verlenging vervalt, welke voorwaarden gelden. + Versioned contracts in OpenRegister. See who signed what, when a renewal expires, which terms apply. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M3 7h18l-2 12H5z"/><path d="M9 7V4h6v3"/></svg>} - title="Inkoop met goedkeurings-flow." + title="Procurement with approval flow." > - PO-aanvragen, goedkeuring per drempelbedrag, leveranciersregister. Voor de bestelling de hand op de knip houden, achteraf de factuur erbij. + PO requests, approval by spending limit, supplier register. Control spending before the purchase, match the invoice afterward. </FeatureItem> </FeatureList> </div> @@ -64,7 +64,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <Section spacing="default" background="tinted"> <SectionHead eyebrow="Pairs well with" - title="De apps waarop Shillinq leunt." + title="The apps Shillinq depends on." align="stack" /> <PairRow> @@ -72,26 +72,26 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai href="/apps/openregister" icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg>} name="OpenRegister" - why="Facturen, contracten, inkooporders als typed registers." + why="Invoices, contracts, purchase orders as typed registers." /> <PairCard href="/apps/docudesk" icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} name="DocuDesk" - why="Genereert facturen, offertes en contracten uit sjablonen." + why="Generates invoices, quotations and contracts from templates." /> <PairCard href="/apps/mydash" icon={<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>} name="MyDash" - why="Cash-flow, omzet, top-debiteuren als widgets op het dashboard." + why="Cash flow, revenue, top debtors as widgets on the dashboard." /> </PairRow> </Section> <CtaBanner - title="Bedrijfsadministratie, één werkplek." - lede={<>Shillinq vervangt losse boekhoudpakketten en CRM-tools door één bedrijfsadministratie op Nextcloud. Open-source, op je eigen data.</>} + title="Business administration, one workspace." + lede={<>Shillinq replaces separate accounting packages and CRM tools with one business administration system on Nextcloud. Open-source, on your own data.</>} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Talk to a partner', href: '/partners'}} /> diff --git a/src/pages/commonground.mdx b/src/pages/commonground.mdx index e73c071af..33a2240ae 100644 --- a/src/pages/commonground.mdx +++ b/src/pages/commonground.mdx @@ -33,7 +33,7 @@ import {totalPartners} from '@site/src/data/partners-catalog'; apps={[ { name: 'DocuDesk', - tagline: 'Auto-classify, anonymise, sign, and template documents. Powers the WOO publication flow end to end.', + tagline: 'Auto-classify, anonymise, sign, and template documents. Powers the Woo publication flow end to end.', status: 'STABLE', version: 'v1.x · NL · EN', href: '/apps/docudesk', @@ -62,28 +62,28 @@ import {totalPartners} from '@site/src/data/partners-catalog'; <Section spacing="default" id="openwoo"> <SectionHead eyebrow="Solution" - title="OpenWoo, Woo-publicatie als gecomponeerde solution." + title="OpenWoo, Woo publication as a composed solution." align="stack" - lede={<>OpenWoo is geen aparte app, het is een solution die OpenRegister, OpenCatalogi en DocuDesk samenbrengt voor de Wet open overheid. Eén Woo-categorieenboom op typed registers, automatische redactie van persoonsgegevens, federatie naar jouwgemeente.nl/openwoo. Geen procurement-traject, geen consultancy-uren, één installatie.</>} + lede={<>OpenWoo is not a separate app. It is a solution that brings together OpenRegister, OpenCatalogi, and DocuDesk for the Wet open overheid (public records law). One Woo category tree on typed registers, automatic redaction of personal data, federation to yourmunicipality.nl/openwoo. No procurement process, no consulting hours, one install.</>} /> <PairRow> <PairCard href="/apps/openregister" icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg>} name="OpenRegister" - why="Woo-categorieen, publicaties, retentie-termijnen als typed registers." + why="Woo categories, publications, retention periods as typed registers." /> <PairCard href="/apps/opencatalogi" icon={<svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4M3 17l9 4 9-4"/></svg>} name="OpenCatalogi" - why="Publiceert het archief naar jouwgemeente.nl/openwoo met federation." + why="Publishes the archive to yourmunicipality.nl/openwoo with federation." /> <PairCard href="/apps/docudesk" icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} name="DocuDesk" - why="Auto-redactie van persoonsgegevens voor publicatie. Audit trail per document." + why="Auto-redacts personal data for publication. Audit trail per document." /> </PairRow> </Section> @@ -99,42 +99,42 @@ import {totalPartners} from '@site/src/data/partners-catalog'; items={[ { id: 'bron', - title: 'Data aan de bron.', + title: 'Data at the source.', summary: 'Store once, retrieve via API, no copies. The source system owns the truth, every other system asks.', cta: {label: 'See OpenRegister', href: '/apps/openregister'}, panel: <AppMock app="openregister" />, }, { id: 'scheiding', - title: 'Scheiding van data en applicaties.', + title: 'Separation of data and applications.', summary: 'Apps consume data, they do not own it. Swap an app without losing the underlying records.', cta: {label: 'See OpenCatalogi', href: '/apps/opencatalogi'}, panel: <AppMock app="opencatalogi" />, }, { id: 'auth', - title: 'Gefedereerde autorisatie.', + title: 'Federated authorisation.', summary: 'Identity translated through a gateway, not duplicated per system. One identity provider, every service authenticated against it.', cta: {label: 'See OpenConnector', href: '/apps/openconnector'}, panel: <AppMock app="openconnector" />, }, { id: 'verantwoording', - title: 'Verantwoording per bevraging.', + title: 'Accountability per query.', summary: 'Every API call logs a declaration of purpose. A citizen can audit who looked at their data, when, and why.', cta: {label: 'See OpenRegister', href: '/apps/openregister'}, panel: <AppMock app="openregister" />, }, { id: 'modellen', - title: 'Gestandaardiseerde informatiemodellen.', + title: 'Standardised information models.', summary: 'Vendor-neutral data models. The same Zaak schema across every municipality, the same Document schema across every DMS.', cta: {label: 'See OpenCatalogi', href: '/apps/opencatalogi'}, panel: <AppMock app="opencatalogi" />, }, { id: 'doelgericht', - title: 'Doelgerichte services.', + title: 'Purpose-scoped services.', summary: 'Each API returns only the data the request needs. No "give me everything", no oversharing, no fishing.', cta: {label: 'See OpenConnector', href: '/apps/openconnector'}, panel: <AppMock app="openconnector" />, @@ -151,7 +151,7 @@ Governance sits at [VNG Realisatie](https://vng.nl/artikelen/common-ground), the <Section spacing="default" background="tinted"> <SectionHead eyebrow="The architecture" - title="The 5-lagen model." + title="The 5-layer model." align="stack" lede="The model splits any government system into five horizontal layers. Each layer is independently addressable. In theory you can swap a process-layer vendor without touching your data layer. In practice that swap is rare." /> @@ -162,8 +162,8 @@ Governance sits at [VNG Realisatie](https://vng.nl/artikelen/common-ground), the <div className="cg-layer-meta"> <span className="cg-layer-num">1</span> <div> - <h3>Interactielaag</h3> - <span className="cg-layer-en">Interaction</span> + <h3>Interaction layer</h3> + <span className="cg-layer-en">Interactielaag</span> <p>What the citizen or case-handler sees. Portals, forms, mobile apps.</p> </div> </div> @@ -178,8 +178,8 @@ Governance sits at [VNG Realisatie](https://vng.nl/artikelen/common-ground), the <div className="cg-layer-meta"> <span className="cg-layer-num">2</span> <div> - <h3>Proceslaag</h3> - <span className="cg-layer-en">Process</span> + <h3>Process layer</h3> + <span className="cg-layer-en">Proceslaag</span> <p>Workflow and business logic. Zaakgericht werken lives here.</p> </div> </div> @@ -194,8 +194,8 @@ Governance sits at [VNG Realisatie](https://vng.nl/artikelen/common-ground), the <div className="cg-layer-meta"> <span className="cg-layer-num">3</span> <div> - <h3>Integratielaag</h3> - <span className="cg-layer-en">Integration</span> + <h3>Integration layer</h3> + <span className="cg-layer-en">Integratielaag</span> <p>Federated, secured exchange between organisations.</p> </div> </div> @@ -210,8 +210,8 @@ Governance sits at [VNG Realisatie](https://vng.nl/artikelen/common-ground), the <div className="cg-layer-meta"> <span className="cg-layer-num">4</span> <div> - <h3>Servicelaag</h3> - <span className="cg-layer-en">Services</span> + <h3>Service layer</h3> + <span className="cg-layer-en">Servicelaag</span> <p>REST APIs that expose data, scoped per request.</p> </div> </div> @@ -227,8 +227,8 @@ Governance sits at [VNG Realisatie](https://vng.nl/artikelen/common-ground), the <div className="cg-layer-meta"> <span className="cg-layer-num">5</span> <div> - <h3>Datalaag</h3> - <span className="cg-layer-en">Data</span> + <h3>Data layer</h3> + <span className="cg-layer-en">Datalaag</span> <p>The source of truth. National basisregistraties plus municipal registers.</p> </div> </div> @@ -237,7 +237,7 @@ Governance sits at [VNG Realisatie](https://vng.nl/artikelen/common-ground), the <Pill bullet bulletColor="var(--c-blue-cobalt)">BAG</Pill> <Pill bullet bulletColor="var(--c-blue-cobalt)">BGT</Pill> <Pill bullet bulletColor="var(--c-blue-cobalt)">KvK Handelsregister</Pill> - <Pill bullet bulletColor="var(--c-blue-cobalt)">Gemeentelijke registers</Pill> + <Pill bullet bulletColor="var(--c-blue-cobalt)">Municipal registers</Pill> </div> </div> @@ -252,7 +252,7 @@ Governance sits at [VNG Realisatie](https://vng.nl/artikelen/common-ground), the eyebrow="The reality" title="Where it stands in 2026." align="stack" - lede="The vision is from 2017. The Programmaplan Common Ground 2025 itself states that complete municipal coverage is expected for years to come. Programme budget for 2025: €6.4 M. Projected savings if fully realised: €200–300 M on ICT, €1–2 billion per year on primary processes. Those projections are kwalitatief, not bookable." + lede="The vision is from 2017. The Programmaplan Common Ground 2025 itself states that complete municipal coverage is expected for years to come. Programme budget for 2025: €6.4 M. Projected savings if fully realised: €200–300 M on ICT, €1–2 billion per year on primary processes. Those projections are qualitative, not bookable." /> The market picture (the most-recent disaggregated public data is from 2018, so treat the percentage as a directional indicator): diff --git a/src/pages/demo.mdx b/src/pages/demo.mdx index c289f4ed9..7e5062b6c 100644 --- a/src/pages/demo.mdx +++ b/src/pages/demo.mdx @@ -11,7 +11,7 @@ import {APP_COUNT} from '@site/src/data/apps-catalog'; <SectionHead eyebrow="Local demo" title={<>Want a demo? Run it yourself.</>} - lede={<>De apps zijn open source, de install is gratis, de runtime past in een docker-compose-bestand. Drie commando's en je draait <span className="next-blue">Nextcloud</span> + Conduction op je laptop. Geen partner-traject, geen sales-call, geen kalender-invite. Demo voor je koffie afkoelt.</>} + lede={<>The apps are open source. Install is free. Runtime fits in one docker-compose file. Three commands and you run <span className="next-blue">Nextcloud</span> + Conduction on your laptop. No partner process, no sales call, no calendar invite. Demo before your coffee gets cold.</>} /> <div style={{display: 'flex', gap: 14, marginTop: -16, flexWrap: 'wrap'}}> @@ -23,26 +23,26 @@ import {APP_COUNT} from '@site/src/data/apps-catalog'; <Section spacing="default" background="tinted"> <SectionHead eyebrow="How it works" - title="Vier stappen, vijftien minuten." + title="Four steps, fifteen minutes." align="stack" - lede="Stage 0 is een eenmalige prep: Docker installeren. Stages 1 t/m 3 zijn de install zelf, en die is een kwartier werk. Daarna heb je een werkende stack op je laptop." + lede="Stage 0 is one-time setup: install Docker. Stages 1 through 3 are the install itself, a fifteen-minute job. After that you have a working stack on your laptop." /> <HowSteps columns={4}> - <HowStep number="0" tier="3" title="Zorg dat Docker draait."> - Mac en Linux: installeer <a href="https://docs.docker.com/get-docker/">Docker Desktop</a>. Windows: zet ook <a href="https://learn.microsoft.com/en-us/windows/wsl/install">WSL 2</a> aan voor je Docker Desktop installeert. Een keer doen, ~10 minuten. Daarna ben je klaar voor elke volgende demo. + <HowStep number="0" tier="3" title="Make sure Docker runs."> + Mac and Linux: install <a href="https://docs.docker.com/get-docker/">Docker Desktop</a>. Windows: turn on <a href="https://learn.microsoft.com/en-us/windows/wsl/install">WSL 2</a> before you install Docker Desktop. One-time task, about ten minutes. After that you're ready for every next demo. </HowStep> - <HowStep number="1" tier="1" title="Maak een map en start Docker."> - Sla de docker-compose.yml hieronder op in een nieuwe map en draai <strong>docker compose up -d</strong>. Open daarna <strong>http://localhost:8080</strong> en kies een admin-naam en wachtwoord. + <HowStep number="1" tier="1" title="Make a folder and start Docker."> + Save the docker-compose.yml below in a new folder and run <strong>docker compose up -d</strong>. Then open <strong>http://localhost:8080</strong> and pick an admin name and password. <ComposeBlock filename="bash">{`mkdir conduction-demo cd conduction-demo # save the docker-compose.yml below into this folder docker compose up -d`}</ComposeBlock> </HowStep> - <HowStep number="2" tier="2" title="Open de Nextcloud app store."> - Inloggen als admin, klik je avatar rechtsboven, kies <strong>Apps</strong>. De Conduction apps staan onder <strong>Integration</strong> en <strong>Office & text</strong>. Eén klik per app installeert. + <HowStep number="2" tier="2" title="Open the Nextcloud app store."> + Log in as admin, click your avatar top right, pick <strong>Apps</strong>. The Conduction apps live under <strong>Integration</strong> and <strong>Office & text</strong>. One click per app to install. </HowStep> - <HowStep number="3" tier="3" title="Configureer en speel."> - OpenRegister levert een schema-bootstrap met voorbeelddata. OpenCatalogi indexeert de eerste registers. MyDash bouwt een dashboard van wat je hebt. Je kunt nu rondklikken zoals een bestaande klant. + <HowStep number="3" tier="3" title="Configure and explore."> + OpenRegister ships a schema bootstrap with sample data. OpenCatalogi indexes the first registers. MyDash builds a dashboard from what you have. Now you can click around like an existing customer. </HowStep> </HowSteps> </Section> @@ -50,9 +50,9 @@ docker compose up -d`}</ComposeBlock> <Section spacing="default" id="docker-compose"> <SectionHead eyebrow="The compose file" - title="Eén bestand, drie services." + title="One file, three services." align="stack" - lede="Postgres voor de data, Redis voor de cache, Nextcloud voor het werk. Allemaal officiële images. Geen custom build, geen Dockerfile. Past in een Slack-bericht." + lede="Postgres for data, Redis for cache, Nextcloud for work. All official images. No custom build, no Dockerfile. Fits in a Slack message." /> ```yaml title="docker-compose.yml" @@ -106,28 +106,28 @@ docker compose down -v`}</ComposeBlock> <Section spacing="default" background="tinted"> <SectionHead eyebrow="Pick the apps" - title="Drie installs voor de eerste demo." + title="Three installs for your first demo." align="stack" - lede="Onze suggestie: begin met OpenRegister (de typed-data backbone), OpenCatalogi (publieke surface), en OpenConnector (haalt voorbeelddata binnen). Vijftien minuten samen, en je ziet de pipeline werken." + lede="Our suggestion: start with OpenRegister (the typed-data backbone), OpenCatalogi (public surface), and OpenConnector (pulls sample data in). Fifteen minutes total, and you see the pipeline work." /> <PairRow> <PairCard href="/apps/openregister" icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/><path d="M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg>} name="OpenRegister" - why="Install eerst. De typed-data foundation; alle andere apps lezen ervan af." + why="Install first. The typed-data foundation every other app reads from." /> <PairCard href="/apps/opencatalogi" icon={<svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4M3 17l9 4 9-4"/></svg>} name="OpenCatalogi" - why="Surface elk register als een doorzoekbare publieke entry. Federation naar data.overheid.nl." + why="Surface every register as a searchable public entry. Federation to data.overheid.nl." /> <PairCard href="/apps/openconnector" icon={<svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>} name="OpenConnector" - why={<>Trekt voorbeelddata binnen via REST/SOAP/file drops. Alle {APP_COUNT} apps profiteren.</>} + why={<>Pulls sample data in via REST, SOAP, or file drops. All {APP_COUNT} apps benefit.</>} /> </PairRow> </Section> @@ -135,47 +135,47 @@ docker compose down -v`}</ComposeBlock> <Section spacing="default"> <SectionHead eyebrow="What you get" - title="Een werkende stack die je kunt vertrouwen." + title="A working stack you can trust." align="stack" - lede="Geen pre-baked screencast, geen demo-omgeving die offline kan. Je ziet exact wat een echte klant ziet, draaiend op jouw hardware. Bewaar de compose file en laat het maandag aan een collega zien." + lede="No pre-baked screencast, no demo environment that goes offline. You see exactly what a real customer sees, running on your hardware. Save the compose file and show it to a colleague on Monday." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>} - title="Drie minuten van compose-up tot login." + title="Three minutes from compose-up to login." > - <code>docker compose up -d</code> trekt 's eerste keer 750MB binnen en boot daarna binnen drie minuten. Volgende boots gaan in seconden. + <code>docker compose up -d</code> pulls 750MB the first time and boots in three minutes. Next boots take seconds. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 8.4 8.4 0 0 1-3.4-.7L3 21l1.5-5.4A8.4 8.4 0 1 1 21 11.5z"/></svg>} - title="Officiële Nextcloud-image." + title="Official Nextcloud image." > - Geen custom Dockerfile. Wat je lokaal draait is identiek aan wat een klant in productie gebruikt. Versies, security-patches, alles via Nextcloud's release-cadence. + No custom Dockerfile. What runs locally is identical to what a customer runs in production. Versions, security patches, all via Nextcloud's release cadence. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/></svg>} - title="Apps via de officiële app store." + title="Apps via the official app store." > - Geen lokale build. De Conduction-apps installeren zichzelf via de Nextcloud app-store, hetzelfde pad als productie. Geen "demo-only" magie. + No local build. The Conduction apps install themselves via the Nextcloud app store, the same path as production. No "demo-only" magic. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} - title="Voorbeelddata via OpenRegister." + title="Sample data via OpenRegister." > - OpenRegister levert schema-bootstrap met voorbeeld-records. Je ziet typed data, audit log, citation-stable IDs werken vanaf de eerste minuut. + OpenRegister ships schema bootstrap with sample records. You see typed data, audit log, citation-stable IDs from minute one. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M12 2v4M12 18v4M2 12h4M18 12h4M5 5l3 3M16 16l3 3M5 19l3-3M16 8l3-3"/></svg>} - title="Reset is één commando." + title="Reset is one command." > - <code>docker compose down -v</code> verwijdert volumes en reset alles. Twee minuten later draai je opnieuw, schoon, klaar voor de volgende test. + <code>docker compose down -v</code> removes volumes and resets everything. Two minutes later you run again, clean, ready for the next test. </FeatureItem> </FeatureList> </Section> <CtaBanner - title="Want a demo? Het draait op je laptop." - lede="Drie commando's, één Nextcloud, alle Conduction apps. Geen sales-call nodig. Stuur de docker-compose naar een collega als je het samen wilt zien." + title="Want a demo? It runs on your laptop." + lede="Three commands, one Nextcloud, all Conduction apps. No sales call needed. Send the docker-compose to a colleague if you want to see it together." primaryCta={{label: 'Browse the apps', href: '/apps'}} secondaryCta={{label: 'Talk to a partner anyway', href: '/partners'}} /> diff --git a/src/pages/index.mdx b/src/pages/index.mdx index 8f1c99a21..23828dd8a 100644 --- a/src/pages/index.mdx +++ b/src/pages/index.mdx @@ -6,7 +6,18 @@ hide_table_of_contents: true import {Hero, StatsStrip, AppsPreview, CtaBanner, PairRow, PairCard, Section, SectionHead, Clients, DEFAULT_CLIENTS, PartnerDirectory, CommonGroundPlus, HexRain} from '@conduction/docusaurus-preset/components'; import {totalDownloads, formatDownloads} from '@conduction/docusaurus-preset/data'; -import {PARTNERS, BECOME_PARTNER, totalPartners} from '@site/src/data/partners-catalog'; +import {usePartners, useBecomePartner, totalPartners} from '@site/src/data/partners-catalog'; + +export function HomePartnerDirectory() { + const partners = usePartners(); + const becomePartner = useBecomePartner(); + return ( + <PartnerDirectory + partners={partners} + becomePartner={{...becomePartner, href: '/support#become-a-partner', ctaLabel: 'Apply on /support'}} + /> + ); +} import {APP_COUNT} from '@site/src/data/apps-catalog'; {/* Conduction main landing. Public Tech framing: tech to serve people. @@ -102,10 +113,7 @@ import {APP_COUNT} from '@site/src/data/apps-catalog'; align="stack" lede="Implementation, hosting, and integration partners that ship Conduction apps to clients. Apply through /support to be listed." /> - <PartnerDirectory - partners={PARTNERS} - becomePartner={{...BECOME_PARTNER, href: '/support#become-a-partner', ctaLabel: 'Apply on /support'}} - /> + <HomePartnerDirectory /> </Section> <Clients diff --git a/src/pages/partners.mdx b/src/pages/partners.mdx index e9bc4e4bf..62d7a5a3b 100644 --- a/src/pages/partners.mdx +++ b/src/pages/partners.mdx @@ -5,7 +5,19 @@ hide_table_of_contents: true --- import {Section, SectionHead, PartnerDirectory} from '@conduction/docusaurus-preset/components'; -import {PARTNERS, BECOME_PARTNER} from '@site/src/data/partners-catalog'; +import {usePartners, useBecomePartner} from '@site/src/data/partners-catalog'; + +export function PartnerDirectoryBlock() { + const partners = usePartners(); + const becomePartner = useBecomePartner(); + return ( + <PartnerDirectory + partners={partners} + becomePartner={becomePartner} + gridColumns={3} + /> + ); +} <Section spacing="default"> <SectionHead @@ -14,9 +26,5 @@ import {PARTNERS, BECOME_PARTNER} from '@site/src/data/partners-catalog'; lede={<>The apps are open source and free. Most teams install and run them themselves. When you need help with hosting, custom integrations, or a regulated rollout, our certified partners take it from there. They run the same <span className="next-blue">Nextcloud</span> as you and ship on the same apps.</>} /> - <PartnerDirectory - partners={PARTNERS} - becomePartner={BECOME_PARTNER} - gridColumns={3} - /> + <PartnerDirectoryBlock /> </Section> diff --git a/src/pages/partners/acato.mdx b/src/pages/partners/acato.mdx index ea8fa9a19..fd286ec39 100644 --- a/src/pages/partners/acato.mdx +++ b/src/pages/partners/acato.mdx @@ -1,23 +1,34 @@ --- title: Acato, Certified partner, Conduction -description: Nederlandse open-source specialist uit Almere. Bouwt en host Nextcloud-omgevingen voor gemeenten en zorginstellingen. +description: Dutch open-source specialist from Almere. Builds and hosts Nextcloud environments for municipalities and care institutions. hide_table_of_contents: true --- import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, ReferenceGrid, ReferenceCard, PartnerCard, PartnerSidecard, CtaBanner, Pill} from '@conduction/docusaurus-preset/components'; -import {PARTNERS} from '@site/src/data/partners-catalog'; +import {partnerByName, usePartnerSummary} from '@site/src/data/partners-catalog'; import {solutionsBySlugs} from '@site/src/data/solutions-catalog'; -export const ACATO = PARTNERS.find(p => p.name === 'Acato'); +export const ACATO = partnerByName('Acato'); export const ACATO_SOLUTIONS = solutionsBySlugs(ACATO.solutions); +export function AcatoSidecard() { + const summary = usePartnerSummary(ACATO); + return ( + <PartnerSidecard + partner={{...ACATO, summary}} + solutions={ACATO_SOLUTIONS} + cta={{label: 'Talk to Acato', href: 'mailto:hello@acato.nl'}} + /> + ); +} + <DetailHero crumb={[{label: 'Partners', href: '/partners'}, 'Acato']} status={{label: 'Certified partner', color: 'var(--c-blue-cobalt)'}} version="Founded 2008" locales="Almere, NL · 24 employees" title="Acato." - tagline={<>Nederlandse open-source specialist uit Almere. Bouwt en host <span className="next-blue">Nextcloud</span>-omgevingen voor gemeenten en zorginstellingen, al meer dan tien jaar, met focus op AVG-compliance, NLDS-conformiteit, en lange-termijn beheer.</>} + tagline={<>Dutch open-source specialist from Almere. Builds and hosts <span className="next-blue">Nextcloud</span> environments for municipalities and care institutions for more than a decade, focused on GDPR compliance, NLDS conformance, and long-term management.</>} primaryCta={{label: 'Talk to Acato', href: 'mailto:hello@acato.nl'}} secondaryCta={{label: 'Visit acato.nl', href: 'https://acato.nl'}} tertiaryCta={{label: 'All partners', href: '/partners'}} @@ -32,34 +43,30 @@ export const ACATO_SOLUTIONS = solutionsBySlugs(ACATO.solutions); eyebrow="Specialties" title="Where Acato shines." align="stack" - lede="Three apps Acato has shipped to production at multiple gemeenten and three sectors where they hold the most context." + lede="Three apps Acato has shipped to production at multiple municipalities and three sectors where they hold the most context." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/><path d="M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg>} title="OpenRegister at scale." > - Schema design and migration patterns for registers with millions of records. Acato has shipped OpenRegister to gemeenten with the largest live registers in the network. + Schema design and migration patterns for registers with millions of records. Acato has shipped OpenRegister to municipalities with the largest live registers in the network. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4M3 17l9 4 9-4"/></svg>} - title="OpenCatalogi for Wet open overheid." + title="OpenCatalogi for Freedom of Information Act." > - WOO publication flows including the eleven categories, federation to data.overheid.nl, and the audit-evidence pack that surrounds each disclosure. + WOO publication flows including the eleven categories, federation to data.overheid.nl, and the audit evidence pack that surrounds each disclosure. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} - title="DocuDesk for inbound zaakcorrespondentie." + title="DocuDesk for inbound case correspondence." > - Auto-classification, anonymisation, and routing for the high-volume inbound mail at uitvoeringsorganisaties. Templates for jaarverslagen, beschikkingen, en bezwaar. + Auto-classification, anonymization, and routing for high-volume inbound mail at government agencies. Templates for annual reports, decisions, and objections. </FeatureItem> </FeatureList> </div> - <PartnerSidecard - partner={ACATO} - solutions={ACATO_SOLUTIONS} - cta={{label: 'Talk to Acato', href: 'mailto:hello@acato.nl'}} - /> + <AcatoSidecard /> </div> </Section> @@ -68,25 +75,25 @@ export const ACATO_SOLUTIONS = solutionsBySlugs(ACATO.solutions); eyebrow="References" title="Live deployments." align="stack" - lede="Three of Acato's productie-deployments. Sectors and apps used. Talk to Acato for a full list." + lede="Three of Acato's production deployments. Sectors and apps used. Talk to Acato for a full list." /> <ReferenceGrid columns={3}> <ReferenceCard - sector="Gemeente" - name="Gemeente Rotterdam" - what="WOO portal in production sinds Q2 2025. Eleven categories live, 18.000 documents publicly searchable, audit log integrated with DMS." + sector="Municipality" + name="Municipality of Rotterdam" + what="WOO portal in production since Q2 2025. Eleven categories live, 18,000 documents publicly searchable, audit log integrated with DMS." stack={['OpenCatalogi', 'OpenRegister', 'OpenConnector']} /> <ReferenceCard - sector="Provincie" - name="Provincie Zuid-Holland" - what="Subsidie-register naar OpenRegister gemigreerd. ZGW-koppeling via OpenConnector, MyDash voor wekelijkse rapportages aan gedeputeerde." + sector="Province" + name="Province of South Holland" + what="Subsidy register migrated to OpenRegister. ZGW integration via OpenConnector, MyDash for weekly reports to provincial executives." stack={['OpenRegister', 'OpenConnector', 'MyDash']} /> <ReferenceCard - sector="Uitvoeringsorganisatie" - name="UWV-pilot" - what="Documentstroom-pilot: DocuDesk classificeert en routeert inkomende post, OpenConnector koppelt aan zaaksysteem. Vier weken van pilot tot productie." + sector="Government Agency" + name="UWV pilot" + what="Document flow pilot: DocuDesk classifies and routes incoming mail, OpenConnector links to case system. Four weeks from pilot to production." stack={['DocuDesk', 'OpenConnector']} /> </ReferenceGrid> @@ -106,7 +113,7 @@ export const ACATO_SOLUTIONS = solutionsBySlugs(ACATO.solutions); tier="host" name="YARD" logo="/img/partners/yard.png" - why="Digital agency uit Utrecht voor gemeenten en zorg. Levert de Conduction Woo-solution." + why="Digital agency from Utrecht for municipalities and care. Delivers the Conduction Woo solution." /> <PartnerCard variant="other" @@ -114,14 +121,14 @@ export const ACATO_SOLUTIONS = solutionsBySlugs(ACATO.solutions); tier="host" name="Procolix" logo="/img/partners/procolix.png" - why="Managed Nextcloud-hosting op eigen EU-infrastructuur, open source als uitgangspunt." + why="Managed Nextcloud hosting on dedicated EU infrastructure, open source by default." /> </div> </Section> <CtaBanner title="Need help shipping Conduction?" - lede={<>Acato runs the rollout, the hosting, en de lange-termijn beheer. Voor elke <span className="next-blue">Nextcloud</span>-omgeving die zichzelf serieus neemt.</>} + lede={<>Acato runs the rollout, hosting, and long-term management. For every <span className="next-blue">Nextcloud</span> environment that takes itself seriously.</>} primaryCta={{label: 'Talk to Acato', href: 'mailto:hello@acato.nl'}} secondaryCta={{label: 'Browse all partners', href: '/partners'}} /> diff --git a/src/pages/partners/procolix.mdx b/src/pages/partners/procolix.mdx index edb188477..1dabafd25 100644 --- a/src/pages/partners/procolix.mdx +++ b/src/pages/partners/procolix.mdx @@ -1,23 +1,34 @@ --- title: Procolix, Host partner, Conduction -description: Nederlandse managed-Nextcloud-hoster uit Dordrecht. Levert beheerde Nextcloud-omgevingen op eigen EU-infrastructuur, open source als uitgangspunt. +description: Dutch managed-Nextcloud hoster from Dordrecht. Delivers managed Nextcloud environments on dedicated EU infrastructure, open source by default. hide_table_of_contents: true --- import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, ReferenceGrid, ReferenceCard, PartnerCard, PartnerSidecard, CtaBanner} from '@conduction/docusaurus-preset/components'; -import {PARTNERS} from '@site/src/data/partners-catalog'; +import {partnerByName, usePartnerSummary} from '@site/src/data/partners-catalog'; import {solutionsBySlugs} from '@site/src/data/solutions-catalog'; -export const PROCOLIX = PARTNERS.find(p => p.name === 'Procolix'); +export const PROCOLIX = partnerByName('Procolix'); export const PROCOLIX_SOLUTIONS = solutionsBySlugs(PROCOLIX.solutions); +export function ProcolixSidecard() { + const summary = usePartnerSummary(PROCOLIX); + return ( + <PartnerSidecard + partner={{...PROCOLIX, summary}} + solutions={PROCOLIX_SOLUTIONS} + cta={{label: 'Talk to Procolix', href: 'mailto:hello@procolix.com'}} + /> + ); +} + <DetailHero crumb={[{label: 'Partners', href: '/partners'}, 'Procolix']} status={{label: 'Host partner', color: 'var(--c-cobalt-100)'}} version="Founded 2007" locales="Hilversum, NL · 35 employees" title="Procolix." - tagline={<>Nederlandse managed-<span className="next-blue">Nextcloud</span>-hoster uit Dordrecht. Levert beheerde Nextcloud-omgevingen op eigen EU-infrastructuur, open source als uitgangspunt. Conduction-apps draaien daar bovenop voor klanten die hosting + apps bij één leverancier willen.</>} + tagline={<>Dutch managed-<span className="next-blue">Nextcloud</span> hoster from Dordrecht. Delivers managed Nextcloud environments on dedicated EU infrastructure, open source by default. Conduction apps run on top for customers who want hosting and apps from one vendor.</>} primaryCta={{label: 'Talk to Procolix', href: 'mailto:hello@procolix.com'}} secondaryCta={{label: 'Visit procolix.com', href: 'https://procolix.com'}} tertiaryCta={{label: 'All partners', href: '/partners'}} @@ -32,34 +43,30 @@ export const PROCOLIX_SOLUTIONS = solutionsBySlugs(PROCOLIX.solutions); eyebrow="Specialties" title="Where Procolix shines." align="stack" - lede="Two apps Procolix is officieel-certified op, plus de Nextcloud-distributie zelf. Ideaal voor uitvoeringsorganisaties die hosting + software bij één leverancier willen." + lede="Two apps Procolix is officially certified on, plus the Nextcloud distribution itself. Ideal for government agencies who want hosting and software from one vendor." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} - title="DocuDesk-certified op Nederlands." + title="DocuDesk certified in Dutch." > - Procolix is door Conduction gecertificeerd op DocuDesk-implementaties. Document-flows voor gemeenten, jaarverslagen, beschikkingen, bezwaarbrieven. + Procolix is Conduction-certified on DocuDesk implementations. Document flows for municipalities, annual reports, decisions, objection letters. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>} - title="OpenConnector-certified op zaaksystemen." + title="OpenConnector certified on case systems." > - Officiële koppelingen met de Nederlandse zaaksystemen-stack: Atos, Centric, GovUnited, plus de open ZGW APIs. Tested en gecertificeerd. + Official integrations with the Dutch case-system stack: Atos, Centric, GovUnited, plus the open ZGW APIs. Tested and certified. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 8.4 8.4 0 0 1-3.4-.7L3 21l1.5-5.4A8.4 8.4 0 1 1 21 11.5z"/></svg>} - title="Officieel Nextcloud-distributeur." + title="Official Nextcloud distributor." > - Levert <strong>Nextcloud Hub Enterprise</strong> als hosted dienst, inclusief BIO/AVG-conform datacenter in Nederland. Conduction-apps draaien daar bovenop. + Delivers <strong>Nextcloud Hub Enterprise</strong> as a hosted service, including BIO/GDPR-compliant data center in the Netherlands. Conduction apps run on top. </FeatureItem> </FeatureList> </div> - <PartnerSidecard - partner={PROCOLIX} - solutions={PROCOLIX_SOLUTIONS} - cta={{label: 'Talk to Procolix', href: 'mailto:hello@procolix.com'}} - /> + <ProcolixSidecard /> </div> </Section> @@ -71,21 +78,21 @@ export const PROCOLIX_SOLUTIONS = solutionsBySlugs(PROCOLIX.solutions); /> <ReferenceGrid columns={3}> <ReferenceCard - sector="Uitvoeringsorganisatie" + sector="Government Agency" name="UWV" - what="Documentstroom voor inkomende post. DocuDesk classificeert, OpenConnector koppelt aan zaaksysteem. Productie sinds Q4 2025." + what="Document flow for incoming mail. DocuDesk classifies, OpenConnector links to case system. Production since Q4 2025." stack={['DocuDesk', 'OpenConnector']} /> <ReferenceCard - sector="Gemeente" - name="Gemeente Hilversum" - what="ZGW-koppeling tussen bestaand zaaksysteem en OpenRegister. OpenConnector ingest, MyDash dashboards voor managementteam." + sector="Municipality" + name="Municipality of Hilversum" + what="ZGW integration between existing case system and OpenRegister. OpenConnector ingest, MyDash dashboards for management team." stack={['OpenConnector', 'OpenRegister', 'MyDash']} /> <ReferenceCard - sector="Waterschap" - name="Waterschap Vallei en Veluwe" - what="Vergunning-archivering volgens Archiefwet 2026. DocuDesk genereert en archiveert, OpenConnector koppelt aan DMS." + sector="Water Authority" + name="Water Authority Vallei en Veluwe" + what="Permit archiving per Dutch Archive Act 2026. DocuDesk generates and archives, OpenConnector links to DMS." stack={['DocuDesk', 'OpenConnector']} /> </ReferenceGrid> @@ -104,7 +111,7 @@ export const PROCOLIX_SOLUTIONS = solutionsBySlugs(PROCOLIX.solutions); tier="certified" name="Acato" logo="/img/partners/acato.png" - why="Certified partner. Joint roadmap, tender-eligible op de Conduction Woo-solution." + why="Certified partner. Joint roadmap, tender-eligible on the Conduction Woo solution." /> <PartnerCard variant="other" @@ -112,14 +119,14 @@ export const PROCOLIX_SOLUTIONS = solutionsBySlugs(PROCOLIX.solutions); tier="host" name="YARD" logo="/img/partners/yard.png" - why="Host partner. Digital agency uit Utrecht voor gemeenten en zorg, ship op de Woo-stack." + why="Host partner. Digital agency from Utrecht for municipalities and care, ships on the Woo stack." /> </div> </Section> <CtaBanner - title="Hosting + DocuDesk + zaaksysteem-koppeling in één pakket." - lede="Procolix levert de hosting, de Nextcloud-distributie, en de gecertificeerde implementatie van DocuDesk en OpenConnector. Voor uitvoeringsorganisaties die het complete pakket bij één leverancier willen." + title="Hosting plus DocuDesk plus case-system integration in one package." + lede="Procolix provides the hosting, the Nextcloud distribution, and the certified implementation of DocuDesk and OpenConnector. For government agencies that want the complete package from one vendor." primaryCta={{label: 'Talk to Procolix', href: 'mailto:hello@procolix.com'}} secondaryCta={{label: 'Browse all partners', href: '/partners'}} /> diff --git a/src/pages/partners/yard.mdx b/src/pages/partners/yard.mdx index 5156f55a0..c6d2edebd 100644 --- a/src/pages/partners/yard.mdx +++ b/src/pages/partners/yard.mdx @@ -1,23 +1,34 @@ --- title: YARD, Host partner, Conduction -description: Digital design- en development-bureau uit Utrecht. Strong on MyDash and OpenCatalogi for citizen-facing surfaces. +description: Digital design and development agency from Utrecht. Strong on MyDash and OpenCatalogi for citizen-facing surfaces. hide_table_of_contents: true --- import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, ReferenceGrid, ReferenceCard, PartnerCard, PartnerSidecard, CtaBanner} from '@conduction/docusaurus-preset/components'; -import {PARTNERS} from '@site/src/data/partners-catalog'; +import {partnerByName, usePartnerSummary} from '@site/src/data/partners-catalog'; import {solutionsBySlugs} from '@site/src/data/solutions-catalog'; -export const YARD = PARTNERS.find(p => p.name === 'YARD'); +export const YARD = partnerByName('YARD'); export const YARD_SOLUTIONS = solutionsBySlugs(YARD.solutions); +export function YardSidecard() { + const summary = usePartnerSummary(YARD); + return ( + <PartnerSidecard + partner={{...YARD, summary}} + solutions={YARD_SOLUTIONS} + cta={{label: 'Talk to YARD', href: 'mailto:hello@yard.nl'}} + /> + ); +} + <DetailHero crumb={[{label: 'Partners', href: '/partners'}, 'YARD']} status={{label: 'Host partner', color: 'var(--c-cobalt-100)'}} version="Founded 2014" locales="Utrecht, NL · 12 employees" title="YARD." - tagline={<>Digital design- en development-bureau uit Utrecht. Ontwerpt en bouwt klantgerichte digitale producten op <span className="next-blue">Nextcloud</span> voor gemeenten en publieke organisaties. Sterk op de citizen-facing surface: dashboards, portalen, software-catalogi.</>} + tagline={<>Digital design and development agency from Utrecht. Designs and builds citizen-facing digital products on <span className="next-blue">Nextcloud</span> for municipalities and public organizations. Strong on the citizen-facing surface: dashboards, portals, software catalogs.</>} primaryCta={{label: 'Talk to YARD', href: 'mailto:hello@yard.nl'}} secondaryCta={{label: 'Visit yard.nl', href: 'https://yard.nl'}} tertiaryCta={{label: 'All partners', href: '/partners'}} @@ -32,34 +43,30 @@ export const YARD_SOLUTIONS = solutionsBySlugs(YARD.solutions); eyebrow="Specialties" title="Where YARD shines." align="stack" - lede="Two apps and one specialty where YARD has shipped to production at multiple gemeenten." + lede="Two apps and one specialty where YARD has shipped to production at multiple municipalities." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>} - title="MyDash for the burgerportaal." + title="MyDash for the citizen portal." > - Citizen-facing dashboards: subsidie-status, vergunning-status, zaakvolg-portaal. YARD is the reference shop for getting MyDash to look like the gemeente, not like Conduction. + Citizen-facing dashboards: subsidy status, permit status, case tracking portal. YARD is the reference shop for getting MyDash to look like the municipality, not like Conduction. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4M3 17l9 4 9-4"/></svg>} title="OpenCatalogi for software directories." > - Software-catalogi voor gemeenten met federation naar data.overheid.nl. UX, search, filtering. Inclusive design defaults. + Software catalogs for municipalities with federation to data.overheid.nl. UX, search, filtering. Inclusive design defaults. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><circle cx="13.5" cy="6.5" r="2.5"/><circle cx="17.5" cy="10.5" r="2.5"/><circle cx="8.5" cy="7.5" r="2.5"/><circle cx="6.5" cy="12.5" r="2.5"/><path d="M14 14l-3-3-7 7v3h3l7-7"/></svg>} - title="NLDS-conforme front-end." + title="NLDS-compliant front-end." > - Ontwerpt en implementeert volgens het Nederlandse Design System (NLDS). WCAG-AA standaard, geen aparte audit nodig. + Designs and implements according to the Dutch Design System (NLDS). WCAG AA standard, no separate audit needed. </FeatureItem> </FeatureList> </div> - <PartnerSidecard - partner={YARD} - solutions={YARD_SOLUTIONS} - cta={{label: 'Talk to YARD', href: 'mailto:hello@yard.nl'}} - /> + <YardSidecard /> </div> </Section> @@ -71,21 +78,21 @@ export const YARD_SOLUTIONS = solutionsBySlugs(YARD.solutions); /> <ReferenceGrid columns={3}> <ReferenceCard - sector="Gemeente" - name="Gemeente Utrecht" - what="Subsidie-portaal voor inwoners. MyDash dashboards voor de afdeling, OpenCatalogi voor publieke regelingen-publicatie." + sector="Municipality" + name="Municipality of Utrecht" + what="Subsidy portal for residents. MyDash dashboards for the department, OpenCatalogi for public regulations publication." stack={['MyDash', 'OpenCatalogi', 'OpenRegister']} /> <ReferenceCard - sector="Gemeente" - name="Gemeente Den Haag" - what="Software-catalogus voor de IT-afdeling. 142 applicaties gecatalogiseerd, federation naar Forum Standaardisatie." + sector="Municipality" + name="Municipality of The Hague" + what="Software catalog for the IT department. 142 applications cataloged, federation to Forum Standardization." stack={['OpenCatalogi', 'OpenRegister']} /> <ReferenceCard - sector="Provincie" - name="Provincie Utrecht" - what="Vergunning-status portaal voor ondernemers. Real-time zaakstatus via OpenConnector, dashboards voor afdelingsoverleg." + sector="Province" + name="Province of Utrecht" + what="Permit status portal for entrepreneurs. Real-time case status via OpenConnector, dashboards for department meetings." stack={['MyDash', 'OpenConnector']} /> </ReferenceGrid> @@ -104,7 +111,7 @@ export const YARD_SOLUTIONS = solutionsBySlugs(YARD.solutions); tier="certified" name="Acato" logo="/img/partners/acato.png" - why="Certified partner. Joint roadmap, tender-eligible op de Conduction Woo-solution." + why="Certified partner. Joint roadmap, tender-eligible on the Conduction Woo solution." /> <PartnerCard variant="other" @@ -112,14 +119,14 @@ export const YARD_SOLUTIONS = solutionsBySlugs(YARD.solutions); tier="host" name="Procolix" logo="/img/partners/procolix.png" - why="Host partner. Managed Nextcloud-hosting op eigen EU-infrastructuur." + why="Host partner. Managed Nextcloud hosting on dedicated EU infrastructure." /> </div> </Section> <CtaBanner - title="Citizen-facing op Nextcloud, NLDS-conform." - lede="YARD ontwerpt, bouwt, en onderhoudt citizen-facing surfaces op de Conduction-stack. Voor gemeenten en publieke organisaties die de UI net zo serieus nemen als de backend." + title="Citizen-facing on Nextcloud, NLDS-compliant." + lede="YARD designs, builds, and maintains citizen-facing surfaces on the Conduction stack. For municipalities and public organizations that take the UI as seriously as the backend." primaryCta={{label: 'Talk to YARD', href: 'mailto:hello@yard.nl'}} secondaryCta={{label: 'Browse all partners', href: '/partners'}} /> diff --git a/src/pages/solutions/anonimiseren.mdx b/src/pages/solutions/anonimiseren.mdx index 653ea5c74..ef9818a39 100644 --- a/src/pages/solutions/anonimiseren.mdx +++ b/src/pages/solutions/anonimiseren.mdx @@ -1,19 +1,19 @@ --- -title: Anonimiseren, AVG-proof publicatie en hergebruik -description: Privacy-by-design pipeline op Nextcloud. DocuDesk en OpenRegister detecteren en redacteren PII voordat documenten en records gepubliceerd, gedeeld of geëxporteerd worden. Presidio onder de motorkap. +title: Anonymize, GDPR-proof publication and reuse +description: Privacy-by-design pipeline on Nextcloud. DocuDesk and OpenRegister detect and redact PII before documents and records are published, shared, or exported. Presidio under the hood. hide_table_of_contents: true --- import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, PairRow, PairCard, CtaBanner} from '@conduction/docusaurus-preset/components'; <DetailHero - crumb={[{label: 'Solutions', href: '/solutions'}, 'Anonimiseren']} + crumb={[{label: 'Solutions', href: '/solutions'}, 'Anonymize']} status={{label: 'Pilot', color: 'var(--c-amber-500)'}} - version="Sector: Publiek & MKB" - title="Anonimiseren, voor je het publiceert." - tagline={<>Een privacy-by-design pipeline op <span className="next-blue">Nextcloud</span>. DocuDesk en OpenRegister sporen PII op en redacteren records en documenten voordat ze de organisatie verlaten. Werkt voor Woo-publicaties, dataset-export, klantcommunicatie en interne kennisdeling. Presidio onder de motorkap, AVG-uitlegbaar.</>} - primaryCta={{label: 'Praat met een partner', href: '/partners'}} - secondaryCta={{label: 'Lees de recipe', href: 'https://docs.conduction.nl/anonimiseren'}} + version="Sector: Public & SMB" + title="Anonymize before you publish." + tagline={<>A privacy-by-design pipeline on <span className="next-blue">Nextcloud</span>. DocuDesk and OpenRegister detect and redact records and documents before they leave your organization. Works for Woo publication, dataset export, customer communication, and knowledge sharing. Presidio under the hood, GDPR-explainable.</>} + primaryCta={{label: 'Talk to a partner', href: '/partners'}} + secondaryCta={{label: 'Read the recipe', href: 'https://docs.conduction.nl/anonimiseren'}} iconColor="var(--c-blue-cobalt)" icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M5 21v-2a4 4 0 0 1 4-4h6a4 4 0 0 1 4 4v2"/><path d="M9 8h6"/></svg>} /> @@ -21,47 +21,47 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa <Section spacing="default"> <SectionHead eyebrow="The outcome" - title="Geen PII die ongezien naar buiten gaat." + title="No PII leaves without your knowledge." align="stack" - lede="Elke publicatie, export of API-call passeert eerst de anonimisatielaag. Namen, BSN's, kentekens, adressen en andere persoonsgegevens worden gedetecteerd, geredigeerd en gelogd, met een uitlegbaar besluit per match. Wat de organisatie deelt, is wat de organisatie wíl delen." + lede="Every publication, export, or API call passes through the anonymization layer first. Names, BSNs, license plates, addresses, and other personal data are detected, redacted, and logged with an explainable decision per match. What your organization shares is what your organization chose to share." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M9 12l2 2 4-4"/></svg>} - title="Detectie op tekst, tabel en document." + title="Detection on text, table, and document." > - Presidio-gebaseerde detectie voor BSN, namen, adressen, IBAN, telefoonnummers, kentekens, e-mailadressen, geboortedata. Werkt op platte tekst, PDF, Word, Excel en op velden in een OpenRegister-record. + Presidio-based detection for BSNs, names, addresses, IBANs, phone numbers, license plates, email addresses, birth dates. Works on plain text, PDF, Word, Excel, and on fields in an OpenRegister record. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M8 13h8M8 17h5"/></svg>} - title="Redactie met behoud van bewijs." + title="Redaction with proof preserved." > - DocuDesk schrijft een geredigeerde kopie, bewaart het origineel achter rechten en houdt de mapping per match vast. Audit-trail toont wát is geredigeerd, op grond waarvan en door welk beleid. + DocuDesk writes a redacted copy, preserves the original behind permissions, and maintains the per-match mapping. Audit trail shows what was redacted, why, and under which policy. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M3 12h18M12 3v18"/><circle cx="12" cy="12" r="9"/></svg>} - title="Beleid per register, per categorie." + title="Policy per register, per category." > - Elk OpenRegister-register krijgt een eigen anonimisatieprofiel: welke entiteiten gedetecteerd worden, welke daarvan geredigeerd, welke geblokkeerd. Woo-publicatie, AVG-verzoek, dataset-export en intern delen krijgen elk hun eigen drempel. + Each OpenRegister gets its own anonymization profile: which entities to detect, which to redact, which to block. Woo publication, GDPR request, dataset export, and internal sharing each get their own threshold. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 8.4 8.4 0 0 1-3.4-.7L3 21l1.5-5.4A8.4 8.4 0 1 1 21 11.5z"/></svg>} - title="Mens in de lus, waar het moet." + title="Human review where it matters." > - Lage-zekerheidsmatches gaan naar een review-wachtrij in Nextcloud. De behandelaar bevestigt of verwerpt; de beslissing wordt onderdeel van het profiel en verbetert toekomstige detectie. + Low-confidence matches go to a review queue in Nextcloud. The reviewer confirms or rejects; the decision becomes part of the profile and improves future detection. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M12 2v4M12 18v4M2 12h4M18 12h4M5 5l3 3M16 16l3 3M5 19l3-3M16 8l3-3"/></svg>} - title="Uitlegbaar voor de AVG." + title="GDPR-explainable." > - Per geredigeerde match wordt vastgelegd: welk type PII, welk model, welke versie, welk profiel. Functionaris Gegevensbescherming kan elk besluit terugzien zonder het oorspronkelijke document te raadplegen. + Every redacted match records: PII type, model, version, profile. Your Data Protection Officer can trace every decision without accessing the original document. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1"/><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1"/></svg>} - title="API en pipeline, niet alleen UI." + title="API and pipeline, not just UI." > - Aanroepbaar vanuit OpenConnector-flows, n8n, Windmill en directe REST-calls. Sluit aan op bestaande Woo-, export- en publicatieprocessen zonder de UI te raken. + Callable from OpenConnector flows, n8n, Windmill, and direct REST calls. Plugs into existing Woo, export, and publication processes without touching the UI. </FeatureItem> </FeatureList> </Section> @@ -69,28 +69,28 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa <Section spacing="default"> <SectionHead eyebrow="The pipeline" - title="Van interne bron tot veilige publicatie." + title="From internal source to safe publication." align="stack" - lede="OpenConnector haalt records en bestanden uit je bronsystemen. OpenRegister houdt ze typed en versioned. DocuDesk laat Presidio over de inhoud lopen, redacteert volgens het profiel en levert de geanonimiseerde kopie aan het publicatiekanaal. Het origineel blijft binnen Nextcloud, achter rechten en audit." + lede="OpenConnector pulls records and files from your source systems. OpenRegister keeps them typed and versioned. DocuDesk runs Presidio over the content, redacts per policy, and delivers the anonymized copy to the publication channel. The original stays in Nextcloud, behind permissions and audit." /> <Pipeline start={{label: 'Sources', items: [ {label: 'DMS', icon: 'doc'}, - {label: 'Zaaksysteem', icon: 'db'}, - {label: 'Bestanden', icon: 'doc'}, - {label: 'Klantdata', icon: 'db'}, + {label: 'Case system', icon: 'db'}, + {label: 'Files', icon: 'doc'}, + {label: 'Customer data',icon: 'db'}, ]}} steps={[ - {number: '01', kicker: 'Ingest', name: 'OpenConnector', caption: 'Pulls records en documenten uit je bronsystemen via REST, SOAP of file drops.'}, - {number: '02', kicker: 'Store', name: 'OpenRegister', caption: 'Typed registers per categorie. Versioned, audit-logged, met anonimisatieprofiel.'}, - {number: 'PLATFORM', name: 'Nextcloud', family: 'workspace', pill: 'files · users · auth', caption: 'De workspace waar je team al inlogt.'}, - {number: '03', kicker: 'Anonimise', name: 'DocuDesk', caption: 'Detecteert PII via Presidio, redacteert per profiel, logt elk besluit.'}, + {number: '01', kicker: 'Ingest', name: 'OpenConnector', caption: 'Pulls records and documents from your source systems via REST, SOAP, or file drops.'}, + {number: '02', kicker: 'Store', name: 'OpenRegister', caption: 'Typed registers per category. Versioned, audit-logged, with anonymization profile.'}, + {number: 'PLATFORM', name: 'Nextcloud', family: 'workspace', pill: 'files · users · auth', caption: 'The workspace your team already logs into.'}, + {number: '03', kicker: 'Anonymize', name: 'DocuDesk', caption: 'Detects PII via Presidio, redacts per policy, logs every decision.'}, ]} end={{label: 'Consumers', items: [ - {label: 'Woo-portal', icon: 'globe'}, + {label: 'Woo portal', icon: 'globe'}, {label: 'Open data', icon: 'download'}, - {label: 'AVG-inzage', icon: 'person'}, - {label: 'Externe partners', icon: 'globe'}, + {label: 'GDPR access', icon: 'person'}, + {label: 'External partners', icon: 'globe'}, ]}} /> </Section> @@ -98,35 +98,35 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa <Section spacing="default" background="tinted"> <SectionHead eyebrow="Built on" - title="Twee Conduction-apps, één PII-engine." + title="Two Conduction apps, one PII engine." align="stack" - lede="Geen maatwerk. Elk onderdeel is een component dat je los kunt installeren en voor andere processen kunt hergebruiken." + lede="No custom work. Each piece is a component you can install separately and reuse for other processes." /> <PairRow> <PairCard href="/apps/docudesk" icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} name="DocuDesk" - why="Voert de redactie uit op tekst, PDF en Word, schrijft een geanonimiseerde kopie en houdt origineel + mapping veilig achter rechten." + why="Performs redaction on text, PDF, and Word, writes an anonymized copy, and keeps original and mapping safe behind permissions." /> <PairCard href="/apps/openregister" icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/><path d="M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg>} name="OpenRegister" - why="Levert de typed registers met een anonimisatieprofiel per categorie. Audit-trail toont welke match op grond van welk beleid is geredigeerd." + why="Delivers the typed registers with an anonymization profile per category. Audit trail shows which match was redacted under which policy." /> <PairCard href="/apps/openregister#integrations" icon={<svg viewBox="0 0 24 24"><path d="M12 2L4 5v7c0 5 3.5 9 8 10 4.5-1 8-5 8-10V5z"/><circle cx="12" cy="11" r="2.2" fill="#fff"/><path d="M9 16h6" stroke="#fff" strokeWidth="1.6" fill="none"/></svg>} name="Presidio" - why="Microsoft's open-source PII-detectiebibliotheek. Powert de detectielaag van DocuDesk; AVG-uitlegbaar, on-prem, geen externe API." + why="Microsoft's open-source PII detection library. Powers DocuDesk's detection layer; GDPR-explainable, on-premise, no external API." /> </PairRow> </Section> <CtaBanner - title="Anonimiseren als deel van de installatie, niet als project." - lede={<>Twee apps en een PII-engine. Praat met een partner als je liever wilt dat we de uitrol verzorgen, of installeer zelf via de <span className="next-blue">Nextcloud</span> app store en volg de recipe.</>} - primaryCta={{label: 'Praat met een partner', href: '/partners'}} - secondaryCta={{label: 'Lees de recipe', href: 'https://docs.conduction.nl/anonimiseren'}} + title="Anonymize as part of install, not as a project." + lede={<>Two apps and a PII engine. Talk to a partner if you'd rather we run the rollout, or install yourself from the <span className="next-blue">Nextcloud</span> app store and follow the recipe.</>} + primaryCta={{label: 'Talk to a partner', href: '/partners'}} + secondaryCta={{label: 'Read the recipe', href: 'https://docs.conduction.nl/anonimiseren'}} /> diff --git a/src/pages/solutions/archief.mdx b/src/pages/solutions/archief.mdx index 7c75b7da4..48a1bbfff 100644 --- a/src/pages/solutions/archief.mdx +++ b/src/pages/solutions/archief.mdx @@ -1,6 +1,6 @@ --- -title: Archief, naar e-Depot zonder maatwerk -description: OpenRegister bewaart je actuele data, OpenConnector zet records over naar het centrale archief. NEN-2082, Wet hergebruik, eIDAS-AdES sealed exports. +title: Archive transfer to e-Depot, automated +description: OpenRegister stores your live data, OpenConnector moves records to your archive on schedule. NEN-2082 mapped, eIDAS-AdES sealed exports. hide_table_of_contents: true --- @@ -8,13 +8,13 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <DetailHero background="cobalt" - crumb={[{label: 'Solutions', href: '/solutions'}, 'Archief']} - status={{label: 'Productie-ready', color: 'var(--c-mint-500)'}} - version="Sector: Publieke sector + zorg" - title={<>Archief op <span className="next-blue">Nextcloud</span>.</>} - tagline={<>Twee apps. Je actuele data blijft in OpenRegister. OpenConnector zet records over naar het e-Depot van je archiefdienst, op het moment dat de bewaartermijn dat voorschrijft. NEN-2082-mapping, eIDAS-AdES verzegeld, audit-trail volledig.</>} - primaryCta={{label: 'Praat met een partner', href: '/partners'}} - secondaryCta={{label: 'Lees de recipe', href: 'https://docs.conduction.nl/archief'}} + crumb={[{label: 'Solutions', href: '/solutions'}, 'Archive']} + status={{label: 'Production-ready', color: 'var(--c-mint-500)'}} + version="Sector: Public & health" + title={<>Archive on <span className="next-blue">Nextcloud</span>.</>} + tagline={<>Two apps. Your live data stays in OpenRegister. OpenConnector moves records to your archive service's e-Depot when retention expires. NEN-2082 mapping, eIDAS-AdES sealed, audit trail complete.</>} + primaryCta={{label: 'Talk to a partner', href: '/partners'}} + secondaryCta={{label: 'Read the recipe', href: 'https://docs.conduction.nl/archief'}} iconColor="var(--c-blue-cobalt)" icon={<svg viewBox="0 0 24 24"><path d="M3 4h18v4H3zM5 8v12h14V8M9 12h6"/></svg>} /> @@ -22,40 +22,40 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <Section spacing="default"> <SectionHead eyebrow="The outcome" - title="Archiefoverdracht zonder een tweede systeem." + title="Archive transfer without a second system." align="stack" - lede="Geen aparte archiefapplicatie. Geen handmatige overzettingen. Een record in OpenRegister bereikt zijn bewaartermijn, OpenConnector zet hem geautomatiseerd over naar het e-Depot van je archiefdienst, en het audit-spoor blijft aanwezig in beide kanten." + lede="No separate archive application. No manual transfers. A record in OpenRegister reaches its retention period, OpenConnector moves it automatically to your archive service's e-Depot, and the audit trail stays intact on both sides." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="4"/><path d="M5 8v12h14V8"/></svg>} - title="NEN-2082 metadata-mapping." + title="NEN-2082 metadata mapping." > - Records uit OpenRegister krijgen automatisch ToepassingsProfiel Metagegevens (TMLO) bij overdracht. Compatibel met de meeste e-Depot-leveranciers in Nederland. + Records from OpenRegister automatically get TMLO (ToepassingsProfiel Metagegevens) metadata on transfer. Compatible with most e-Depot vendors in the Netherlands. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="9"/></svg>} title="eIDAS-AdES sealed exports." > - Elke overdracht krijgt een gekwalificeerd elektronisch zegel. Tamper-evident, juridisch geldig, gevalideerd door je archivaris bij ontvangst. + Every transfer gets a qualified electronic seal. Tamper-evident, legally valid, validated by your archivist on receipt. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>} - title="Bewaartermijn-driven, niet handmatig." + title="Retention-driven, not manual." > - Je definieert bewaartermijnen per zaaktype of recordcategorie. Het systeem triggert overdracht op het juiste moment, je archivaris krijgt een notificatie ter validatie. + You define retention periods per case type or record category. The system triggers transfer at the right moment; your archivist gets a notification for validation. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/></svg>} - title="Wet hergebruik aan de open kant." + title="Reuse Act on the open side." > - Records die onder Wet hergebruik vallen worden via OpenCatalogi automatisch herontdekbaar gemaakt. Geen aparte publicatie-stap. + Records falling under the Reuse Act are automatically rediscoverable through OpenCatalogi. No separate publication step. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M12 2v4M12 18v4M2 12h4M18 12h4"/></svg>} - title="Audit-trail aan beide kanten." + title="Audit trail on both sides." > - Wie heeft de overdracht gestart, wanneer, met welke metadata, welke validatie. Het volledige spoor blijft zichtbaar in OpenRegister, ook nadat de records zelf zijn overgedragen. + Who started the transfer, when, with which metadata, what validation. The full trail stays visible in OpenRegister, even after records themselves are transferred. </FeatureItem> </FeatureList> </Section> @@ -63,22 +63,22 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <Section spacing="default" background="tinted"> <SectionHead eyebrow="Built on" - title="Twee apps, één configuratie." + title="Two apps, one configuration." align="stack" - lede="Geen aparte archiefsuite. De twee apps die je toch al hebt voor je registers en je integraties dekken ook de archiefoverdracht." + lede="No separate archive suite. The two apps you already have for your registers and integrations cover archive transfer too." /> <PairRow columns={2}> <PairCard href="/apps/openregister" icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg>} name="OpenRegister" - why="Je actuele data. Bewaartermijn per recordcategorie, NEN-2082 metadata gevuld zodra een record wordt aangemaakt." + why="Your live data. Retention period per record category, NEN-2082 metadata filled as soon as a record is created." /> <PairCard href="/apps/openconnector" icon={<svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>} name="OpenConnector" - why="De koppeling met je e-Depot. SOAP, REST, of bestandsoverdracht, plus de eIDAS-AdES sealing op de uitgaande records." + why="Your link to e-Depot. SOAP, REST, or file transfer, plus eIDAS-AdES sealing on outgoing records." /> </PairRow> </Section> @@ -86,20 +86,20 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <Section spacing="default"> <SectionHead eyebrow="Pricing" - title="€500 per maand, beide apps inbegrepen." + title="500 per month, both apps included." align="stack" - lede="Twee apps × €250 als managed SaaS via commonground.nu. Geen instapkosten, geen minimum-afname, opzeggen met een kalendermaand opzegtermijn. Voor maatwerk-koppelingen met je specifieke e-Depot-leverancier of voor multi-organisatie-archieven: partner-rate." + lede="Two apps × 250 as managed SaaS via commonground.nu. No setup fees, no minimum commitment, cancel with one month notice. For custom integrations with your specific e-Depot vendor or multi-organization archives: partner pricing." /> <div style={{display: 'flex', gap: 14, flexWrap: 'wrap', marginTop: 'var(--space-3)'}}> <a href="/support" style={{padding: '12px 20px', background: 'var(--c-blue-cobalt)', color: 'white', borderRadius: 'var(--radius-md)', textDecoration: 'none', fontWeight: 600}}>SaaS via commonground.nu</a> - <a href="/partners" style={{padding: '12px 20px', background: 'transparent', color: 'var(--c-cobalt-900)', border: '1px solid var(--c-cobalt-900)', borderRadius: 'var(--radius-md)', textDecoration: 'none', fontWeight: 600}}>Praat met een partner</a> + <a href="/partners" style={{padding: '12px 20px', background: 'transparent', color: 'var(--c-cobalt-900)', border: '1px solid var(--c-cobalt-900)', borderRadius: 'var(--radius-md)', textDecoration: 'none', fontWeight: 600}}>Talk to a partner</a> </div> </Section> <CtaBanner - title="Een archiefoverdracht die zichzelf doet." - lede={<>Bewaartermijn loopt af, OpenConnector levert het record naar je e-Depot, je archivaris krijgt een notificatie ter validatie. Geen losse archiefapplicatie, geen handwerk.</>} - primaryCta={{label: 'Praat met een partner', href: '/partners'}} - secondaryCta={{label: 'Lees de recipe', href: 'https://docs.conduction.nl/archief'}} + title="Archive transfer that runs itself." + lede={<>Retention expires, OpenConnector delivers the record to your e-Depot, your archivist gets a validation notification. No separate archive app, no manual work.</>} + primaryCta={{label: 'Talk to a partner', href: '/partners'}} + secondaryCta={{label: 'Read the recipe', href: 'https://docs.conduction.nl/archief'}} /> diff --git a/src/pages/solutions/mkb-workspace.mdx b/src/pages/solutions/mkb-workspace.mdx index 0c22c3b48..20cd671a6 100644 --- a/src/pages/solutions/mkb-workspace.mdx +++ b/src/pages/solutions/mkb-workspace.mdx @@ -1,6 +1,6 @@ --- -title: MKB workspace, Conduction -description: Files, calendar, talk, dashboards. Je open-source workspace op Nextcloud, MyDash op top, in twee minuten klaar. +title: SMB workspace, Conduction +description: Files, calendar, talk, dashboards. Your complete open-source workspace on Nextcloud, MyDash on top, ready in two minutes. hide_table_of_contents: true --- @@ -8,11 +8,11 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, HowSteps, Ho <DetailHero background="cobalt" - crumb={[{label: 'Solutions', href: '/solutions'}, 'MKB workspace']} - status={{label: 'Productie-ready', color: 'var(--c-mint-500)'}} - version="Sector: MKB" - title="MKB workspace in two minutes." - tagline={<>Files, calendar, talk, dashboards. Je volledige open-source workspace op <span className="next-blue">Nextcloud</span>, met MyDash bovenop zodat het team vanaf dag één ziet wat ertoe doet. Vervangt vier SaaS-abonnementen voor de prijs van één.</>} + crumb={[{label: 'Solutions', href: '/solutions'}, 'SMB workspace']} + status={{label: 'Production-ready', color: 'var(--c-mint-500)'}} + version="Sector: SMB" + title="SMB workspace in two minutes." + tagline={<>Files, calendar, talk, dashboards. Your complete open-source workspace on <span className="next-blue">Nextcloud</span>, with MyDash on top so your team sees what matters from day one. Replace four SaaS subscriptions with the cost of one.</>} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Read the recipe', href: 'https://docs.conduction.nl/mkb-workspace'}} iconColor="var(--c-cobalt-700)" @@ -22,40 +22,40 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, HowSteps, Ho <Section spacing="default"> <SectionHead eyebrow="The outcome" - title="Eén werkplek voor het hele team." + title="One workplace for your entire team." align="stack" - lede="Replace Slack + Google Workspace + een BI-tool + een CRM met één Nextcloud-install plus twee Conduction-apps. Je data blijft op je eigen server. Het team werkt in dezelfde UI die ze al kennen." + lede="Replace Slack + Google Workspace + a BI tool + a CRM with one Nextcloud install plus two Conduction apps. Your data stays on your own server. Your team works in the same UI they already know." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>} - title="Files, calendar, talk, office in één login." + title="Files, calendar, talk, office in one login." > - Nextcloud levert het basisteam-platform. Bestanden, agenda's, video-calls, gedeelde documenten. Werkt direct uit de doos. + Nextcloud provides the core team platform. Files, calendars, video calls, shared documents. Works right out of the box. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>} - title="MyDash voor wat ertoe doet." + title="MyDash for what matters." > - Bouw dashboards uit je registers, je facturatie, je urenregistratie. Geen aparte BI-tool, geen extra inlog. KPIs zichtbaar voor wie ze nodig heeft. + Build dashboards from your registers, your billing, your time tracking. No separate BI tool, no extra login. KPIs visible to who needs them. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>} - title="Connector haalt je legacy data binnen." + title="Connector pulls in your legacy data." > - Bestaande boekhouding (Exact, Twinfield), CRM (HubSpot, Salesforce), of urenregistratie (Toggl)? OpenConnector trekt het binnen, MyDash rapporteert er over. + Existing accounting (Exact, Twinfield), CRM (HubSpot, Salesforce), or time tracking (Toggl)? OpenConnector pulls it in; MyDash reports on it. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 8.4 8.4 0 0 1-3.4-.7L3 21l1.5-5.4A8.4 8.4 0 1 1 21 11.5z"/></svg>} title="GDPR by default." > - Je data blijft op je eigen Nextcloud. Geen Amerikaanse cloud-providers, geen vendor-lock-in, geen jaarlijkse prijsverhogingen. + Your data stays on your Nextcloud. No US cloud providers, no vendor lock-in, no annual price hikes. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>} - title="Twee minuten van install tot werkend." + title="Two minutes from install to working." > - Drop OpenRegister + MyDash + OpenConnector in je Nextcloud. Configureer één schema, start de connector. Het team begint dezelfde middag te werken. + Drop OpenRegister + MyDash + OpenConnector into your Nextcloud. Configure one schema, start the connector. Your team starts working the same afternoon. </FeatureItem> </FeatureList> </Section> @@ -63,18 +63,18 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, HowSteps, Ho <Section spacing="default" background="tinted"> <SectionHead eyebrow="How install works" - title="Drie stappen, in volgorde." + title="Three steps, in order." align="stack" /> <HowSteps> - <HowStep title="Install Nextcloud (of gebruik je bestaande)." > - Je kunt zelf hosten, een Conduction-partner laten hosten, of je bestaande Nextcloud-omgeving gebruiken. Twee admins is genoeg om te starten. + <HowStep title="Install Nextcloud (or use your existing one)." > + You can host it yourself, have a Conduction partner host it, or use your existing Nextcloud environment. Two admins is enough to get started. </HowStep> <HowStep title="Drop in OpenRegister + MyDash."> - Beide apps zitten in de Nextcloud app store. Klik install, beide configureren zichzelf bij eerste request. Het schema-bootstrap is idempotent. + Both apps are in the Nextcloud app store. Click install; both configure themselves on first request. Schema bootstrap is idempotent. </HowStep> - <HowStep title="Trek je legacy data binnen via OpenConnector."> - Optioneel: koppel je bestaande boekhouding, CRM, of urenregistratie via OpenConnector. MyDash bouwt direct dashboards op de binnenkomende data. + <HowStep title="Pull in your legacy data via OpenConnector."> + Optional: link your existing accounting, CRM, or time tracking via OpenConnector. MyDash builds dashboards directly on incoming data. </HowStep> </HowSteps> </Section> @@ -82,7 +82,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, HowSteps, Ho <Section spacing="default"> <SectionHead eyebrow="Built on" - title="Twee apps, geen middleware." + title="Two apps, no middleware." align="stack" /> <PairRow columns={2}> @@ -90,20 +90,20 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, HowSteps, Ho href="/apps/mydash" icon={<svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>} name="MyDash" - why="Persoonlijke en team-dashboards uit je registers. Rolgebaseerde toegang, KPIs op het startscherm." + why="Personal and team dashboards from your registers. Role-based access, KPIs on the home screen." /> <PairCard href="/apps/openregister" icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg>} name="OpenRegister" - why="De typed-data backbone. Klanten, projecten, facturen, uren, alles als typed register." + why="Your typed-data backbone. Customers, projects, invoices, hours, everything as a typed register." /> </PairRow> </Section> <CtaBanner - title="Eén werkplek, geen losse SaaS." - lede={<>Drop drie apps in je <span className="next-blue">Nextcloud</span>. Je team werkt vanaf dezelfde middag in dezelfde UI die ze al kennen. Geen vendor-lock-in, geen jaarlijkse prijsverhogingen.</>} + title="One workplace, no loose SaaS." + lede={<>Drop three apps into your <span className="next-blue">Nextcloud</span>. Your team works the same afternoon in the same UI they already know. No vendor lock-in, no annual price hikes.</>} primaryCta={{label: 'Install from app store', href: '/install'}} secondaryCta={{label: 'Talk to a partner', href: '/partners'}} /> diff --git a/src/pages/solutions/openwoo.mdx b/src/pages/solutions/openwoo.mdx index 4a820cc78..d77e7c932 100644 --- a/src/pages/solutions/openwoo.mdx +++ b/src/pages/solutions/openwoo.mdx @@ -8,10 +8,10 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa <DetailHero crumb={[{label: 'Solutions', href: '/solutions'}, 'OpenWoo']} - status={{label: 'Productie-ready', color: 'var(--c-mint-500)'}} - version="Sector: Publieke sector" + status={{label: 'Production-ready', color: 'var(--c-mint-500)'}} + version="Sector: Public" title="OpenWoo. Wet open overheid, by Friday." - tagline={<>OpenWoo is a live publication platform for all government sources, running on your hosted <span className="next-blue">Nextcloud</span>. The eleven Wet open overheid-categorieën become typed registers, every register publishes through OpenCatalogi, every connector ingests from your DMS. No migration project, no separate front-end to maintain.</>} + tagline={<>OpenWoo is a live publication platform for all government sources, running on your hosted <span className="next-blue">Nextcloud</span>. The eleven Woo categories become typed registers, every register publishes through OpenCatalogi, every connector ingests from your DMS. No migration project, no separate front-end to maintain.</>} primaryCta={{label: 'Talk to a partner', href: '/partners'}} secondaryCta={{label: 'Read the docs', href: 'https://openwoo.conduction.nl/'}} iconColor="var(--c-blue-cobalt)" @@ -23,7 +23,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa eyebrow="The outcome" title="What you get on day one." align="stack" - lede="Three apps, one configuration pass. Citizens land at jouwgemeente.nl/woo and search every published category. Your team manages the eleven registers from inside Nextcloud, with the same login they already have." + lede="Three apps, one configuration pass. Citizens land at yourmunicipality.nl/woo and search every published category. Your team manages the eleven registers from inside Nextcloud, with the same login they already have." /> <FeatureList> @@ -31,37 +31,37 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>} title="Active disclosure, by category." > - Each of the eleven Woo-categorieën becomes a typed register. Records get published the moment they're created; the citizen catalogue lights up automatically. + Each of the eleven Woo categories becomes a typed register. Records get published the moment they're created; the citizen catalog lights up automatically. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4M3 17l9 4 9-4"/></svg>} title="Federated, citizen-searchable." > - OpenCatalogi indexes every register and exposes the public Woo-portal at <strong>jouwgemeente.nl/woo</strong>. Federates to data.overheid.nl out of the box. + OpenCatalogi indexes every register and exposes the public Woo portal at <strong>yourmunicipality.nl/woo</strong>. Federates to data.overheid.nl out of the box. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6M16 13H8M16 17H8"/></svg>} title="Connector pulls from your DMS." > - OpenConnector reads Woo-records from your existing zaaksysteem or DMS via REST, SOAP, or file drops. No glue code, no nightly batch. + OpenConnector reads Woo records from your existing case system or DMS via REST, SOAP, or file drops. No glue code, no nightly batch. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M12 2v4M12 18v4M2 12h4M18 12h4M5 5l3 3M16 16l3 3M5 19l3-3M16 8l3-3"/></svg>} title="Audit log, signed and timestamped." > - Every publication, edit, withdrawal, and access leaves a tamper-evident trail. The Wet open overheid evidence you need at audit time is part of the install, not a year-two project. + Every publication, edit, withdrawal, and access leaves a tamper-evident trail. The Woo audit evidence you need at audit time is part of the install, not a year-two project. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M12 2l3 6 6 1-4.5 4.5 1 6-5.5-3-5.5 3 1-6L3 9l6-1z"/></svg>} title="Citation-stable URLs." > - Every published record gets a permanent identifier the moment it's first disclosed. Nieuwsmedia, Tweede Kamer-vragen, en juridische dossiers blijven werken, ook na herorganisaties of platform-migraties. + Every published record gets a permanent identifier the moment it's first disclosed. News media, parliamentary questions, and legal dossiers stay live, even after reorganizations or platform migrations. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 8.4 8.4 0 0 1-3.4-.7L3 21l1.5-5.4A8.4 8.4 0 1 1 21 11.5z"/></svg>} title="Two minutes to install." > - Drop the three apps into your Nextcloud, point the connector at your zaaksysteem, configure the eleven registers from the supplied profile. Live before lunch. + Drop the three apps into your Nextcloud, point the connector at your case system, configure the eleven registers from the supplied profile. Live before lunch. </FeatureItem> </FeatureList> </Section> @@ -71,7 +71,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa eyebrow="The pipeline" title="From your DMS to the citizen portal." align="stack" - lede="Three apps move documents from your source systems to a public-facing Woo-portal. OpenConnector reads from XLNS, Joomla, OpenZaak, RX, or whatever else you run. OpenRegister stores the records. OpenCatalogi makes them searchable. The Open Tilburg UI shows them; open.overheid.nl federates them." + lede="Three apps move documents from your source systems to a public-facing Woo portal. OpenConnector reads from XLNS, Joomla, OpenZaak, RX, or whatever else you run. OpenRegister stores the records. OpenCatalogi makes them searchable. The Open Tilburg UI shows them; open.overheid.nl federates them." /> <Pipeline start={{label: 'Sources', items: [ @@ -81,8 +81,8 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa {label: 'RX', icon: 'legacy'}, ]}} steps={[ - {number: '01', kicker: 'Ingest', name: 'OpenConnector', caption: 'Pulls Woo-records from your DMS via REST, SOAP, or file drops.'}, - {number: '02', kicker: 'Store', name: 'OpenRegister', caption: 'One typed register per Woo-categorie. Versioned, audit-logged.'}, + {number: '01', kicker: 'Ingest', name: 'OpenConnector', caption: 'Pulls Woo records from your DMS via REST, SOAP, or file drops.'}, + {number: '02', kicker: 'Store', name: 'OpenRegister', caption: 'One typed register per Woo category. Versioned, audit-logged.'}, {number: '03', kicker: 'Present', name: 'OpenCatalogi', caption: 'Indexes every register. Search, federation, citation-stable URLs.'}, ]} end={{label: 'Consumers', items: [ @@ -105,19 +105,19 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa href="/apps/opencatalogi" icon={<svg viewBox="0 0 24 24"><path d="M3 7l9-4 9 4-9 4-9-4z"/><path d="M3 12l9 4 9-4M3 17l9 4 9-4"/></svg>} name="OpenCatalogi" - why="Surfaces every Woo-register as a public, searchable catalogue entry at jouwgemeente.nl/woo." + why="Surfaces every Woo register as a public, searchable catalogue entry at yourmunicipality.nl/woo." /> <PairCard href="/apps/openregister" icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/><path d="M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3"/></svg>} name="OpenRegister" - why="The eleven Woo-categorieën each become a typed register. Schema, audit log, citation-stable IDs come for free." + why="The eleven Woo categories each become a typed register. Schema, audit log, citation-stable IDs come for free." /> <PairCard href="/apps/openconnector" icon={<svg viewBox="0 0 24 24"><circle cx="6" cy="12" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M9 12h9M9 12l9-6M9 12l9 6"/></svg>} name="OpenConnector" - why="Pulls Woo-records from your existing zaaksysteem or DMS via REST, SOAP, or file drops." + why="Pulls Woo records from your existing case system or DMS via REST, SOAP, or file drops." /> </PairRow> </Section> @@ -125,7 +125,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa <Section spacing="default"> <SectionHead eyebrow="Partners" - title="Deelnemende leveranciers." + title="Participating vendors." align="stack" lede="OpenWoo is a Common Ground initiative carried by a network of suppliers. Pick the partner you already work with, or one whose stack matches yours — the apps and the data flow stay the same." /> @@ -156,56 +156,56 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, Pipeline, Pa <Section spacing="default" background="tinted"> <SectionHead - eyebrow="In productie bij" - title="Twaalf gemeenten en samenwerkingsverbanden." + eyebrow="Live in production" + title="Twelve municipalities and coalitions." align="stack" - lede="Live WOO-portalen in productie of acceptatie, hetzelfde drie-app-platform onder de motorkap. Klik door om de echte portalen te bekijken." + lede="Live Woo portals in production or acceptance, the same three-app platform under the hood. Click through to see the real portals." /> <div style={{overflowX: 'auto', marginTop: 'var(--space-3)'}}> <table style={{width: '100%', borderCollapse: 'collapse', fontSize: '0.95em'}}> <thead> <tr style={{textAlign: 'left', borderBottom: '1px solid var(--c-grey-200)'}}> - <th style={{padding: 'var(--space-2)'}}>Organisatie</th> + <th style={{padding: 'var(--space-2)'}}>Organization</th> <th style={{padding: 'var(--space-2)'}}>Type</th> - <th style={{padding: 'var(--space-2)'}}>Woo-portaal</th> - <th style={{padding: 'var(--space-2)'}}>Bron</th> + <th style={{padding: 'var(--space-2)'}}>Woo portal</th> + <th style={{padding: 'var(--space-2)'}}>Source</th> <th style={{padding: 'var(--space-2)'}}>Status</th> </tr> </thead> <tbody> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.noordwijk.nl/" target="_blank" rel="noreferrer">Noordwijk</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.noordwijk.nl/" target="_blank" rel="noreferrer">open.noordwijk.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.epe.nl/" target="_blank" rel="noreferrer">Epe</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.epe.nl/" target="_blank" rel="noreferrer">open.epe.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.roosendaal.nl/" target="_blank" rel="noreferrer">Roosendaal</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.roosendaal.nl/" target="_blank" rel="noreferrer">open.roosendaal.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.zutphen.nl/" target="_blank" rel="noreferrer">Zutphen</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.zutphen.nl/" target="_blank" rel="noreferrer">open.zutphen.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.tubbergen.nl/" target="_blank" rel="noreferrer">Tubbergen</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.tubbergen.nl/" target="_blank" rel="noreferrer">open.tubbergen.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.dinkelland.nl/" target="_blank" rel="noreferrer">Dinkelland</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.dinkelland.nl/" target="_blank" rel="noreferrer">open.dinkelland.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.moerdijk.nl/" target="_blank" rel="noreferrer">Moerdijk</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.moerdijk.nl/" target="_blank" rel="noreferrer">open.moerdijk.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.gooisemeren.nl/" target="_blank" rel="noreferrer">Gooise Meren</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.gooisemeren.nl/" target="_blank" rel="noreferrer">open.gooisemeren.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.hofvantwente.nl/" target="_blank" rel="noreferrer">Hof van Twente</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.hofvantwente.nl/" target="_blank" rel="noreferrer">open.hofvantwente.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.gouda.nl/" target="_blank" rel="noreferrer">Gouda</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.gouda.nl/" target="_blank" rel="noreferrer">open.gouda.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.tilburg.nl/" target="_blank" rel="noreferrer">Tilburg</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.tilburg.nl/" target="_blank" rel="noreferrer">open.tilburg.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl · Sharepoint · RXMission · Notubiz</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.dinkelland.nl/noaberkracht-dinkelland-tubbergen" target="_blank" rel="noreferrer">Noaberkracht</a></td><td style={{padding: 'var(--space-2)'}}>Samenwerkingsverband</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.noaberkracht.nl/" target="_blank" rel="noreferrer">open.noaberkracht.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Productie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.gemeentehw.nl/" target="_blank" rel="noreferrer">Hoeksche Waard</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://hoekschewaard.accept.openwoo.app/" target="_blank" rel="noreferrer">hoekschewaard.accept.openwoo.app</a></td><td style={{padding: 'var(--space-2)'}}>DECOS</td><td style={{padding: 'var(--space-2)'}}>Acceptatie</td></tr> - <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.helmond.nl/" target="_blank" rel="noreferrer">Helmond</a></td><td style={{padding: 'var(--space-2)'}}>Gemeente</td><td style={{padding: 'var(--space-2)'}}><a href="https://helmond.accept.openwoo.app/" target="_blank" rel="noreferrer">helmond.accept.openwoo.app</a></td><td style={{padding: 'var(--space-2)'}}>Djuma</td><td style={{padding: 'var(--space-2)'}}>Acceptatie</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.noordwijk.nl/" target="_blank" rel="noreferrer">Noordwijk</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.noordwijk.nl/" target="_blank" rel="noreferrer">open.noordwijk.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.epe.nl/" target="_blank" rel="noreferrer">Epe</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.epe.nl/" target="_blank" rel="noreferrer">open.epe.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.roosendaal.nl/" target="_blank" rel="noreferrer">Roosendaal</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.roosendaal.nl/" target="_blank" rel="noreferrer">open.roosendaal.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.zutphen.nl/" target="_blank" rel="noreferrer">Zutphen</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.zutphen.nl/" target="_blank" rel="noreferrer">open.zutphen.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.tubbergen.nl/" target="_blank" rel="noreferrer">Tubbergen</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.tubbergen.nl/" target="_blank" rel="noreferrer">open.tubbergen.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.dinkelland.nl/" target="_blank" rel="noreferrer">Dinkelland</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.dinkelland.nl/" target="_blank" rel="noreferrer">open.dinkelland.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.moerdijk.nl/" target="_blank" rel="noreferrer">Moerdijk</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.moerdijk.nl/" target="_blank" rel="noreferrer">open.moerdijk.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.gooisemeren.nl/" target="_blank" rel="noreferrer">Gooise Meren</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.gooisemeren.nl/" target="_blank" rel="noreferrer">open.gooisemeren.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.hofvantwente.nl/" target="_blank" rel="noreferrer">Hof van Twente</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.hofvantwente.nl/" target="_blank" rel="noreferrer">open.hofvantwente.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.gouda.nl/" target="_blank" rel="noreferrer">Gouda</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.gouda.nl/" target="_blank" rel="noreferrer">open.gouda.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.tilburg.nl/" target="_blank" rel="noreferrer">Tilburg</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.tilburg.nl/" target="_blank" rel="noreferrer">open.tilburg.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl · Sharepoint · RXMission · Notubiz</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.dinkelland.nl/noaberkracht-dinkelland-tubbergen" target="_blank" rel="noreferrer">Noaberkracht</a></td><td style={{padding: 'var(--space-2)'}}>Coalition</td><td style={{padding: 'var(--space-2)'}}><a href="https://open.noaberkracht.nl/" target="_blank" rel="noreferrer">open.noaberkracht.nl</a></td><td style={{padding: 'var(--space-2)'}}>Zaaksysteem.nl</td><td style={{padding: 'var(--space-2)'}}>Production</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.gemeentehw.nl/" target="_blank" rel="noreferrer">Hoeksche Waard</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://hoekschewaard.accept.openwoo.app/" target="_blank" rel="noreferrer">hoekschewaard.accept.openwoo.app</a></td><td style={{padding: 'var(--space-2)'}}>DECOS</td><td style={{padding: 'var(--space-2)'}}>Acceptance</td></tr> + <tr><td style={{padding: 'var(--space-2)'}}><a href="https://www.helmond.nl/" target="_blank" rel="noreferrer">Helmond</a></td><td style={{padding: 'var(--space-2)'}}>Municipality</td><td style={{padding: 'var(--space-2)'}}><a href="https://helmond.accept.openwoo.app/" target="_blank" rel="noreferrer">helmond.accept.openwoo.app</a></td><td style={{padding: 'var(--space-2)'}}>Djuma</td><td style={{padding: 'var(--space-2)'}}>Acceptance</td></tr> </tbody> </table> </div> <p style={{marginTop: 'var(--space-3)', fontSize: '0.92em', color: 'var(--c-grey-600)'}}> - Begin 2024 succesvol getest met KOOP en WooGle. Voor elke nieuwe deployment: praat met een partner of installeer zelf vanuit de Nextcloud app store. + Early 2024 successfully tested with KOOP and WooGle. For every new deployment: talk to a partner or install yourself from the Nextcloud app store. </p> </Section> <Section spacing="default"> <SectionHead eyebrow="Community" - title="Maandelijkse OpenWoo community-meetings." + title="Monthly OpenWoo community meetings." align="stack" - lede="Elke maand komt de OpenWoo-community samen — leveranciers, gemeenten, KOOP. Twee jaar aan opgenomen sessies plus de tutorials voor opstarten staan in de Academy." + lede="Every month the OpenWoo community gathers — vendors, municipalities, KOOP. Two years of recorded sessions plus startup tutorials are in the Academy." /> <div style={{display: 'flex', gap: 14, flexWrap: 'wrap', marginTop: 'var(--space-3)'}}> <a href="/academy/?app=openwoo" style={{padding: '12px 20px', background: 'var(--c-blue-cobalt)', color: 'white', borderRadius: 'var(--radius-md)', textDecoration: 'none', fontWeight: 600}}>OpenWoo in Academy</a> - <a href="https://openwoo.conduction.nl/" target="_blank" rel="noreferrer" style={{padding: '12px 20px', background: 'transparent', color: 'var(--c-cobalt-900)', border: '1px solid var(--c-cobalt-900)', borderRadius: 'var(--radius-md)', textDecoration: 'none', fontWeight: 600}}>Technische documentatie</a> + <a href="https://openwoo.conduction.nl/" target="_blank" rel="noreferrer" style={{padding: '12px 20px', background: 'transparent', color: 'var(--c-cobalt-900)', border: '1px solid var(--c-cobalt-900)', borderRadius: 'var(--radius-md)', textDecoration: 'none', fontWeight: 600}}>Technical documentation</a> </div> </Section> diff --git a/src/pages/solutions/zaakafhandeling.mdx b/src/pages/solutions/zaakafhandeling.mdx index 79c4fec70..1c47f7900 100644 --- a/src/pages/solutions/zaakafhandeling.mdx +++ b/src/pages/solutions/zaakafhandeling.mdx @@ -1,6 +1,6 @@ --- -title: ZaakAfhandelApp, complete zaakafhandeling op Nextcloud -description: ZaakAfhandelApp combineert OpenRegister, PipelinQ, Procest en Windmill tot een complete zaakafhandeling op je eigen Nextcloud. ZGW-compliant, audit trail, burgerportaal. +title: ZaakAfhandelApp, complete case management on Nextcloud +description: ZaakAfhandelApp combines OpenRegister, PipelinQ, Procest, and Windmill into complete case handling on your own Nextcloud. ZGW-compliant, audit trail, citizen portal. hide_table_of_contents: true --- @@ -9,12 +9,12 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <DetailHero background="cobalt" crumb={[{label: 'Solutions', href: '/solutions'}, 'ZaakAfhandelApp']} - status={{label: 'Productie-ready', color: 'var(--c-mint-500)'}} - version="Sector: Publieke sector" + status={{label: 'Production-ready', color: 'var(--c-mint-500)'}} + version="Sector: Public" title={<>ZaakAfhandelApp on <span className="next-blue">Nextcloud</span>.</>} - tagline={<>Vier apps, één zaakafhandeling. Vervang je losse case-management SaaS door een geïntegreerde flow op de <span className="next-blue">Nextcloud</span> waar je team al inlogt. Burger-CRM, zaakworkflow, automatisering, allemaal vanuit dezelfde data.</>} - primaryCta={{label: 'Praat met een partner', href: '/partners'}} - secondaryCta={{label: 'Lees de recipe', href: 'https://docs.conduction.nl/zaakafhandeling'}} + tagline={<>Four apps, one case management system. Replace your separate case-management SaaS with an integrated flow on the <span className="next-blue">Nextcloud</span> your team already logs into. Citizen CRM, case workflow, automation, all from the same data.</>} + primaryCta={{label: 'Talk to a partner', href: '/partners'}} + secondaryCta={{label: 'Read the recipe', href: 'https://docs.conduction.nl/zaakafhandeling'}} iconColor="var(--c-blue-cobalt)" icon={<svg viewBox="0 0 24 24"><path d="M9 11V7a3 3 0 0 1 6 0v4"/><rect x="5" y="11" width="14" height="10" rx="2"/></svg>} /> @@ -22,40 +22,40 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <Section spacing="default"> <SectionHead eyebrow="The outcome" - title="ZGW-compliante zaakafhandeling, end-to-end." + title="ZGW-compliant case handling, end-to-end." align="stack" - lede="Vier apps, één configuratie-pas. Burgers volgen hun zaak via jouwgemeente.nl/zaken, medewerkers werken in dezelfde Nextcloud, automatische processen draaien op de achtergrond. Hetzelfde audit trail dekt alles." + lede="Four apps, one configuration pass. Citizens track their case via yourmunicipality.nl/cases, staff works in the same Nextcloud, automated processes run in the background. The same audit trail covers everything." /> <FeatureList> <FeatureItem icon={<svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9z"/></svg>} - title="ZGW APIs zonder koppelapplicatie." + title="ZGW APIs without a broker." > - Zaken-, Documenten-, Catalogi-, Besluiten-, Klanten-API. Aanwezig zodra de vier apps geconfigureerd zijn, geen aparte broker. + Cases, Documents, Catalogs, Decisions, Contacts APIs. Live as soon as the four apps are configured; no separate broker needed. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 8.4 8.4 0 0 1-3.4-.7L3 21l1.5-5.4A8.4 8.4 0 1 1 21 11.5z"/></svg>} - title="Burger-portaal aan jouwgemeente.nl/zaken." + title="Citizen portal at yourmunicipality.nl/cases." > - Burgers volgen lopende zaken, vragen status op, uploaden documenten. Je bestaande Nextcloud-domein, geen nieuwe URL. + Citizens track open cases, request status, upload documents. Your existing Nextcloud domain; no new URL. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><rect x="4" y="3" width="16" height="18" rx="2"/><circle cx="12" cy="10" r="3"/><path d="M7 18a5 5 0 0 1 10 0"/></svg>} - title="Burger-CRM ingebouwd." + title="Citizen CRM built in." > - PipelinQ houdt elke burger-, organisatie- en relatie-historie bij. Een medewerker ziet alle voorgaande contactmomenten zodra een zaak opent. + PipelinQ tracks every citizen, organization, and relationship history. Staff sees all previous contact moments the moment a case opens. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>} - title="Workflow-engine voor de slimme stappen." + title="Workflow engine for the smart steps." > - Windmill draait de automatisering die niet in een zaaktype-template past. Beschikkingen genereren, externe systemen aanroepen, escalaties bewaken. + Windmill runs automation that doesn't fit in a case-type template. Generate decisions, call external systems, monitor escalations. </FeatureItem> <FeatureItem icon={<svg viewBox="0 0 24 24"><path d="M12 2v4M12 18v4M2 12h4M18 12h4M5 5l3 3M16 16l3 3M5 19l3-3M16 8l3-3"/></svg>} - title="Audit trail per zaak." + title="Audit trail per case." > - Elke statusverandering, elk document, elke medewerker-actie krijgt een tamper-evident trail. Klaar voor inspectie, jaarverslag of bezwaar. + Every status change, every document, every staff action gets a tamper-evident trail. Ready for inspection, annual reports, or objections. </FeatureItem> </FeatureList> </Section> @@ -63,34 +63,34 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <Section spacing="default" background="tinted"> <SectionHead eyebrow="Built on" - title="Vier apps, één configuratie." + title="Four apps, one configuration." align="stack" - lede="Geen maatwerkcode. Elke laag is een Conduction-app die je apart kunt installeren, ook voor andere doeleinden." + lede="No custom code. Each layer is a Conduction app you can install separately, even for other purposes." /> <PairRow columns={2}> <PairCard href="/apps/openregister" icon={<svg viewBox="0 0 24 24"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v12c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/></svg>} name="OpenRegister" - why="Zaken, documenten, besluiten als typed registers. Audit log, citation-stable IDs, ZGW-schema's voorgedefinieerd." + why="Cases, documents, decisions as typed registers. Audit log, citation-stable IDs, ZGW schemas pre-defined." /> <PairCard href="/apps/pipelinq" icon={<svg viewBox="0 0 24 24"><path d="M3 12h4l3-9 4 18 3-9h4"/></svg>} name="PipelinQ" - why="Burger- en organisatie-CRM. Relaties, contactmomenten, communicatiehistorie. Eén ingang per burger, ongeacht welke zaak." + why="Citizen and organization CRM. Relationships, contact moments, communication history. One entry point per citizen, regardless of case." /> <PairCard href="/apps/procest" icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>} name="Procest" - why="Case-management. Zaaktype-workflows uit de doos, aanpasbaar zonder code, configuratie blijft gehandhaafd na upgrades." + why="Case management. Case-type workflows out of the box, adjustable without code, configuration persists after upgrades." /> <PairCard href="https://apps.nextcloud.com/apps/flow" icon={<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="2"/><path d="M12 12L17 5L19 8L14 12zM12 12L19 17L16 19L12 14zM12 12L7 19L5 16L10 12zM12 12L5 7L8 5L12 10z"/></svg>} name="Windmill (Flow)" - why="Nextcloud Flow ExApp, draait Windmill in je workspace. Workflow-engine voor de stappen die niet in een zaaktype passen. Python, TypeScript, Bash, een script per stap." + why="Nextcloud Flow ExApp, runs Windmill in your workspace. Workflow engine for steps that don't fit case-type templates. Python, TypeScript, Bash, one script per step." /> </PairRow> </Section> @@ -98,20 +98,20 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai <Section spacing="default"> <SectionHead eyebrow="Pricing" - title="€1000 per maand, alle vier de apps inbegrepen." + title="1000 per month, all four apps included." align="stack" - lede="Vier apps × €250 als managed SaaS via commonground.nu. Geen instapkosten, geen minimum-afname, opzeggen met een kalendermaand opzegtermijn. Voor multi-tenant, per-gebruiker of grote populaties: partner-rate." + lede="Four apps × 250 as managed SaaS via commonground.nu. No setup fees, no minimum commitment, cancel with one month notice. For multi-tenant, per-user, or large populations: partner pricing." /> <div style={{display: 'flex', gap: 14, flexWrap: 'wrap', marginTop: 'var(--space-3)'}}> <a href="/support" style={{padding: '12px 20px', background: 'var(--c-blue-cobalt)', color: 'white', borderRadius: 'var(--radius-md)', textDecoration: 'none', fontWeight: 600}}>SaaS via commonground.nu</a> - <a href="/partners" style={{padding: '12px 20px', background: 'transparent', color: 'var(--c-cobalt-900)', border: '1px solid var(--c-cobalt-900)', borderRadius: 'var(--radius-md)', textDecoration: 'none', fontWeight: 600}}>Praat met een partner</a> + <a href="/partners" style={{padding: '12px 20px', background: 'transparent', color: 'var(--c-cobalt-900)', border: '1px solid var(--c-cobalt-900)', borderRadius: 'var(--radius-md)', textDecoration: 'none', fontWeight: 600}}>Talk to a partner</a> </div> </Section> <CtaBanner - title="Zaakafhandeling, op je eigen Nextcloud." - lede={<>Vier apps. Eén configuratie. Geen losse SaaS, geen tweede inlog. Praat met een partner als je liever de uitrol uit handen geeft, of installeer zelf vanuit de <span className="next-blue">Nextcloud</span> app store.</>} - primaryCta={{label: 'Praat met een partner', href: '/partners'}} - secondaryCta={{label: 'Lees de recipe', href: 'https://docs.conduction.nl/zaakafhandeling'}} + title="Case handling on your own Nextcloud." + lede={<>Four apps. One configuration. No separate SaaS, no second login. Talk to a partner if you'd rather we run the rollout, or install yourself from the <span className="next-blue">Nextcloud</span> app store.</>} + primaryCta={{label: 'Talk to a partner', href: '/partners'}} + secondaryCta={{label: 'Read the recipe', href: 'https://docs.conduction.nl/zaakafhandeling'}} /> diff --git a/src/pages/support.mdx b/src/pages/support.mdx index 4177dba09..76d8bd0d7 100644 --- a/src/pages/support.mdx +++ b/src/pages/support.mdx @@ -5,7 +5,32 @@ hide_table_of_contents: true --- import {Section, SectionHead, HowSteps, HowStep, Card, Pill, HexBullet, HexNetwork, FeatureList, FeatureItem, PairRow, PairCard, Button, PartnerGrid, PartnerCard, BecomePartner, PartnerDirectory, ManagedCommonGround, FAQ, FAQItem} from '@conduction/docusaurus-preset/components'; -import {PARTNERS, BECOME_PARTNER} from '@site/src/data/partners-catalog'; +import {usePartners, useBecomePartner} from '@site/src/data/partners-catalog'; + +export function PartnerLogoNetwork() { + const partners = usePartners(); + return ( + <HexNetwork + layout="3-3-3-3" + scroll="down" + scrollSpeed={70} + visibleRows={4} + fade={false} + cells={partners.map(p => ({name: p.name, logo: p.logo, href: p.href}))} + /> + ); +} + +export function PartnerDirectoryBlock() { + const partners = usePartners(); + const becomePartner = useBecomePartner(); + return ( + <PartnerDirectory + partners={partners} + becomePartner={{...becomePartner, href: '#become-a-partner', ctaLabel: 'Apply below'}} + /> + ); +} export const TIER_PILL = { host: {background: 'var(--c-cobalt-50)', color: 'var(--c-cobalt-700)'}, @@ -38,14 +63,7 @@ export const TIER_PILL_BASE = { <h1 style={{fontSize: 56, fontWeight: 700, letterSpacing: '-0.025em', lineHeight: 1.05, color: 'var(--c-cobalt-900)', margin: '0 0 var(--space-5)'}}>Support runs through partners.<br/>The apps stay free.</h1> <p style={{fontSize: 18, lineHeight: 1.5, color: 'var(--c-cobalt-700)', margin: 0, maxWidth: '52ch'}}>Conduction builds and maintains the apps. Every one of them is open-source on GitHub and free from the <span className="next-blue">Nextcloud</span> app store. Day-to-day support, implementation hours, and SLA contracts sit with our partners. Pick the partner that matches your sector and your scale.</p> </div> - <HexNetwork - layout="3-3-3-3" - scroll="down" - scrollSpeed={70} - visibleRows={4} - fade={false} - cells={PARTNERS.map(p => ({name: p.name, logo: p.logo, href: p.href}))} - /> + <PartnerLogoNetwork /> </div> </Section> @@ -120,10 +138,7 @@ export const TIER_PILL_BASE = { align="stack" lede="Implementation, hosting, and integration partners. Filter by tier and by the apps you want to deploy. Apply through the form below to be added." /> - <PartnerDirectory - partners={PARTNERS} - becomePartner={{...BECOME_PARTNER, href: '#become-a-partner', ctaLabel: 'Apply below'}} - /> + <PartnerDirectoryBlock /> </Section> <ManagedCommonGround diff --git a/src/theme/BlogListPage/index.jsx b/src/theme/BlogListPage/index.jsx index 22d2838cb..9bb4b1efb 100644 --- a/src/theme/BlogListPage/index.jsx +++ b/src/theme/BlogListPage/index.jsx @@ -25,14 +25,17 @@ import { } from '@docusaurus/theme-common'; import Layout from '@theme/Layout'; import BrowserOnly from '@docusaurus/BrowserOnly'; +import {usePluginData} from '@docusaurus/useGlobalData'; +import Translate, {translate} from '@docusaurus/Translate'; import { FeaturedCard, - ContentCard, - ContentCardGrid, ContentTypeFilter, CONTENT_TYPES, NewsletterCta, Section, + ContentCard, + ContentCardGrid, + ModuleCard, } from '@conduction/docusaurus-preset/components'; import { APPS_REGISTRY, @@ -42,17 +45,19 @@ import { const TYPE_SET = new Set(CONTENT_TYPES); const APP_SET = new Set(Object.keys(APPS_REGISTRY)); -function readQuery(search) { +function readQuery(search, knownModules) { try { const params = new URLSearchParams(search); const t = params.get('type'); const a = params.get('app'); + const m = params.get('module'); return { - type: t && TYPE_SET.has(t) ? t : null, - app: a && APP_SET.has(a) ? a : null, + type: t && TYPE_SET.has(t) ? t : null, + app: a && APP_SET.has(a) ? a : null, + module: m && knownModules && knownModules.has(m) ? m : null, }; } catch (_) { - return {type: null, app: null}; + return {type: null, app: null, module: null}; } } @@ -111,7 +116,7 @@ function panelToneFor(contentType) { } } -function postToCardProps(post) { +function postToCardProps(post, moduleSize) { const meta = post.content.metadata; const fm = meta.frontMatter || {}; const author = meta.authors && meta.authors[0]; @@ -127,21 +132,45 @@ function postToCardProps(post) { icon: defaultIconFor(fm.contentType), panelTone: panelToneFor(fm.contentType), }, + durationMinutes: fm.durationMinutes, + audience: fm.audience || [], + module: fm.module, + modulePosition: fm.modulePosition, + moduleTotalParts: fm.module && moduleSize ? moduleSize : undefined, + moduleTitle: fm.moduleTitle, }; } -function postToFeaturedProps(post) { - const card = postToCardProps(post); +function postToFeaturedProps(post, moduleSize) { + const card = postToCardProps(post, moduleSize); const fm = post.content.metadata.frontMatter || {}; return { href: card.href, - eyebrow: 'Featured ' + (fm.contentType || 'post'), + eyebrow: translate( + { + id: 'theme.academy.featuredEyebrow', + message: 'Featured {type}', + description: 'Eyebrow on the featured academy card. {type} is the content type slug (blog, guide, case-study, webinar, tutorial)', + }, + {type: fm.contentType || 'post'}, + ), title: card.title, lede: card.summary, - ctaLabel: 'Read more', + ctaLabel: translate({ + id: 'theme.academy.featuredCta', + message: 'Read more', + description: 'CTA label on the featured academy card', + }), author: card.author, date: card.date, thumbnail: {icon: defaultIconFor(fm.contentType)}, + contentType: fm.contentType, + durationMinutes: fm.durationMinutes, + audience: fm.audience || [], + module: fm.module, + modulePosition: fm.modulePosition, + moduleTotalParts: fm.module && moduleSize ? moduleSize : undefined, + moduleTitle: fm.moduleTitle, }; } @@ -169,15 +198,23 @@ function countsByApp(posts) { } function AcademyLandingInner({items}) { + /* Module groupings from the academy-modules Docusaurus plugin. + Empty object when the plugin is missing so the page still renders + individual cards rather than throwing. */ + const moduleData = usePluginData('academy-modules') || {}; + const modules = moduleData.modules || {}; + const moduleSlugs = useMemo(() => Object.keys(modules), [modules]); + const knownModules = useMemo(() => new Set(moduleSlugs), [moduleSlugs]); + const [active, setActive] = useState(() => - readQuery(typeof window !== 'undefined' ? window.location.search : '') + readQuery(typeof window !== 'undefined' ? window.location.search : '', knownModules) ); useEffect(() => { - const onPop = () => setActive(readQuery(window.location.search)); + const onPop = () => setActive(readQuery(window.location.search, knownModules)); window.addEventListener('popstate', onPop); return () => window.removeEventListener('popstate', onPop); - }, []); + }, [knownModules]); /* Type counts always reflect the full feed (so the user sees how many blogs vs guides exist regardless of the active app filter). App @@ -200,10 +237,66 @@ function AcademyLandingInner({items}) { [appCounts], ); - const filtered = active.app + const itemsAfterApp = active.app ? itemsAfterType.filter((p) => postApps(p).includes(active.app)) : itemsAfterType; + /* Apply the module filter last so the type/app filters always + narrow the universe first. If the user picks a specific module, + we drop into the "module focus" view: only that module's parts + show, no composite collapsing. */ + const filtered = active.module + ? itemsAfterApp.filter((p) => p.content.metadata.frontMatter?.module === active.module) + : itemsAfterApp; + + /* Module pill row counts: how many member posts each module + contributes to the type/app-filtered universe. Hide modules with + 0 members so the row shortens as the user filters. */ + const moduleCounts = useMemo(() => { + const counts = {}; + for (const post of itemsAfterApp) { + const slug = post.content.metadata.frontMatter?.module; + if (slug && knownModules.has(slug)) counts[slug] = (counts[slug] || 0) + 1; + } + return counts; + }, [itemsAfterApp, knownModules]); + + const visibleModuleSlugs = useMemo( + () => moduleSlugs.filter((s) => moduleCounts[s] > 0), + [moduleSlugs, moduleCounts], + ); + + const moduleLabels = useMemo(() => { + const labels = {}; + for (const slug of moduleSlugs) labels[slug] = modules[slug]?.title || slug; + return labels; + }, [moduleSlugs, modules]); + + /* Composite collapsing: when no module-related filter is active and + a module has 2+ parts in the current universe, replace those parts + with a single ModuleCard. We collapse only on the unfiltered (no + module) view because picking a type/app may already narrow a + module to 1 visible part — collapsing that into a composite reads + as misleading. */ + const composedItems = useMemo(() => { + if (active.module) return filtered; + + const seenModules = new Set(); + const result = []; + for (const post of filtered) { + const slug = post.content.metadata.frontMatter?.module; + const partsInUniverse = slug ? moduleCounts[slug] : 0; + if (slug && partsInUniverse >= 2 && knownModules.has(slug)) { + if (seenModules.has(slug)) continue; + seenModules.add(slug); + result.push({__module: slug}); + } else { + result.push(post); + } + } + return result; + }, [filtered, moduleCounts, knownModules, active.module]); + const setQueryParam = (key, value) => { const url = new URL(window.location.href); if (value) url.searchParams.set(key, value); @@ -232,11 +325,24 @@ function AcademyLandingInner({items}) { setActive((prev) => ({...prev, app: next})); }; - const [featured, ...rest] = filtered; + const handleModuleChange = (next) => { + setQueryParam('module', next); + setActive((prev) => ({...prev, module: next})); + }; + + /* Featured slot picks the most-recent non-module post from the + composed list. When the user is in module-focus mode (active.module) + we don't surface a Featured tile — the parts list is the focus. */ + const featured = !active.module && composedItems.length > 0 && !composedItems[0].__module + ? composedItems[0] + : null; + const restItems = featured ? composedItems.slice(1) : composedItems; return ( <> - {featured && <FeaturedCard {...postToFeaturedProps(featured)} />} + {featured && ( + <FeaturedCard {...postToFeaturedProps(featured, featured.content.metadata.frontMatter?.module && moduleCounts[featured.content.metadata.frontMatter.module])} /> + )} <div style={{height: 64}} /> @@ -262,6 +368,21 @@ function AcademyLandingInner({items}) { </> )} + {visibleModuleSlugs.length > 0 && ( + <> + <div style={{height: 12}} /> + <ContentTypeFilter + value={active.module} + onChange={handleModuleChange} + types={visibleModuleSlugs} + labels={moduleLabels} + counts={moduleCounts} + allLabel="All modules" + allCount={itemsAfterApp.length} + /> + </> + )} + <div style={{height: 32}} /> {filtered.length === 0 ? ( @@ -271,13 +392,45 @@ function AcademyLandingInner({items}) { color: 'var(--c-cobalt-400)', fontSize: 16, }}> - Nothing yet for this combination. <a href="/academy/">View everything</a> + <Translate + id="theme.academy.emptyState" + description="Empty-state message shown when filters return no posts. {viewAll} is a link to the unfiltered academy index." + values={{ + viewAll: <a href="/academy/"><Translate id="theme.academy.viewAll" description="Link text inside the empty-state message">View everything</Translate></a>, + }}> + {'Nothing yet for this combination. {viewAll}'} + </Translate> </div> - ) : rest.length > 0 ? ( + ) : restItems.length > 0 ? ( <ContentCardGrid columns={2}> - {rest.map((post, i) => ( - <ContentCard key={post.content.metadata.permalink || i} {...postToCardProps(post)} /> - ))} + {restItems.map((item, i) => { + if (item.__module) { + const mod = modules[item.__module]; + if (!mod) return null; + return ( + <ModuleCard + key={`mod-${item.__module}`} + href={mod.permalink} + title={mod.title} + lede={mod.lede} + parts={mod.parts.length} + totalMinutes={mod.totalMinutes} + latestDate={mod.latestDate} + curator={mod.curator} + audience={mod.audience} + contentTypes={mod.contentTypes} + /> + ); + } + const slug = item.content.metadata.frontMatter?.module; + const moduleSize = slug && moduleCounts[slug]; + return ( + <ContentCard + key={item.content.metadata.permalink || i} + {...postToCardProps(item, moduleSize)} + /> + ); + })} </ContentCardGrid> ) : null} </> @@ -334,11 +487,31 @@ export default function BlogListPage(props) { <div style={{height: 96}} /> <NewsletterCta - title="New posts in your inbox, monthly." - lede="One mail a month. New guides, case studies, and webinars. Geen spam, je kunt je altijd uitschrijven." - placeholder="jij@bedrijf.nl" - submitLabel="Subscribe" - fineprint="We mail vanuit info@conduction.nl. Geen lijstverkoop." + title={translate({ + id: 'theme.academy.newsletter.title', + message: 'New posts in your inbox, monthly.', + description: 'Newsletter section title at the bottom of the academy landing page', + })} + lede={translate({ + id: 'theme.academy.newsletter.lede', + message: 'One mail a month. New guides, case studies, and webinars. No spam, unsubscribe any time.', + description: 'Newsletter section lede paragraph', + })} + placeholder={translate({ + id: 'theme.academy.newsletter.placeholder', + message: 'you@company.com', + description: 'Placeholder text inside the newsletter email input', + })} + submitLabel={translate({ + id: 'theme.academy.newsletter.submit', + message: 'Subscribe', + description: 'Submit button label on the newsletter form', + })} + fineprint={translate({ + id: 'theme.academy.newsletter.fineprint', + message: 'We mail from info@conduction.nl. No list reselling.', + description: 'Small-print line below the newsletter form', + })} /> </Section> </article> diff --git a/src/theme/Error/index.jsx b/src/theme/Error/index.jsx new file mode 100644 index 000000000..1a926c939 --- /dev/null +++ b/src/theme/Error/index.jsx @@ -0,0 +1,109 @@ +/** + * Conduction-flavoured top-level error fallback. + * + * Wraps the same dyke-break scene we use in ErrorPageContent. The + * difference between the two boundaries: ErrorPageContent fires when + * something crashes INSIDE the page Layout (the common case — almost + * every route is inside Layout); this <Error /> fires when something + * crashes around or above Layout (router setup, top-level providers). + * Routing both to the same visual + copy means a crash anywhere on the + * site lands the visitor on the dyke, not on Docusaurus's stock + * "Page Error" page. + * + * Implementation cribs the structure from the upstream theme-fallback + * Error: we keep the RouteContextProvider + secondary ErrorBoundary so + * Layout itself can crash without dropping the user into a white page. + * Only the visible <ErrorDisplay /> swaps for our dyke component. + */ + +import React from 'react'; +import Head from '@docusaurus/Head'; +import ErrorBoundary from '@docusaurus/ErrorBoundary'; +import Translate, {translate} from '@docusaurus/Translate'; +import Layout from '@theme/Layout'; +import ErrorPageContent from '@theme/ErrorPageContent'; +/* Internal: the upstream theme-fallback Error wraps its content in + RouteContextProvider so theme-classic doesn't crash from missing + route context when Layout queries useRouteContext. Mirroring that + from the original implementation. The path is internal, but stable + since Docusaurus 3.0. */ +import {RouteContextProvider} from '@docusaurus/core/lib/client/routeContext'; + +/* Minimal safe fallback. If Layout itself crashes, we lose the navbar + + footer chrome but still want the dyke copy. We can't import the + full ErrorPageContent here (it may have been what crashed Layout in + the first place), so render an inline stripped-down version. */ +function BareErrorDisplay({error, tryAgain}) { + const message = error && (error.message || String(error)) || 'Unknown error'; + return ( + <div style={{ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + minHeight: '100vh', + padding: '48px 24px', + fontFamily: 'system-ui, sans-serif', + background: '#f4f7fb', + color: '#0a2540', + textAlign: 'center', + }}> + <p style={{ + fontFamily: 'monospace', fontSize: 12, letterSpacing: '0.08em', + textTransform: 'uppercase', color: '#f47b20', margin: '0 0 12px', + }}> + <Translate id="theme.error.eyebrow" description="Eyebrow above the error-page heading; idiom about plugging a leak"> + Finger in the dyke + </Translate> + </p> + <h1 style={{fontSize: 32, fontWeight: 700, margin: '0 0 16px'}}> + <Translate id="theme.error.title" description="Main heading on the error fallback page"> + Oh, no. Something broke. + </Translate> + </h1> + <p style={{fontSize: 16, color: '#3d4f6e', maxWidth: 480, margin: '0 0 24px', lineHeight: 1.5}}> + <Translate id="theme.error.body" description="Reassurance paragraph below the error heading"> + We are dispatching a team with duct tape right now. Please try again in a moment, or take a different route. + </Translate> + </p> + <button type="button" onClick={tryAgain} style={{ + background: '#0a2540', color: 'white', border: 'none', + padding: '12px 24px', borderRadius: 999, fontSize: 15, + fontWeight: 600, cursor: 'pointer', + }}> + <Translate id="theme.error.retry" description="Label of the retry button on the error page"> + Try again + </Translate> + </button> + <details style={{marginTop: 32, fontFamily: 'monospace', fontSize: 11, color: '#3d4f6e', maxWidth: 640}}> + <summary style={{cursor: 'pointer'}}> + <Translate id="theme.error.details" description="Disclosure label above the raw error message"> + What technically went wrong + </Translate> + </summary> + <pre style={{whiteSpace: 'pre-wrap', textAlign: 'left', marginTop: 12}}>{message}</pre> + </details> + </div> + ); +} + +export default function Error({error, tryAgain}) { + return ( + <RouteContextProvider value={{ + plugin: {name: 'docusaurus-core-error-boundary', id: 'default'}, + }}> + <ErrorBoundary fallback={() => <BareErrorDisplay error={error} tryAgain={tryAgain} />}> + <Head> + <title>{translate({ + id: 'theme.error.pageTitle', + message: 'Something broke · Conduction', + description: 'HTML <title> on the error fallback page', + })} + + + + + + + ); +} From 0ae49ff936ac6da2d20017b5f270655a567a0ca0 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 20 May 2026 19:28:41 +0200 Subject: [PATCH 068/330] feat(i18n): fill remaining NL translations (100% coverage) (#97) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit code.json — translate 5 ariä-labels still in English: - colorToggle.system → systeemmodus - NavBar.navAriaLabel → Hoofdnavigatie - IconExternalLink → "(opent in nieuw tabblad)" - mobileDropdown collapse/expand → "Klap het dropdown-menu in/uit" code.json — add Dutch for 43 preset.* keys (Footer game, brand blurb, Navbar fallbacks, FeaturedCard, ContentCard, ModuleCard, ContentTypeFilter, NextSteps, GameModal incl. ICU plural, audience.short.* for mkb/government/developer). The 12 already extracted got their Dutch values; the 31 that write-translations skipped (they live inside the preset package in node_modules) are inserted manually so Docusaurus still picks them up from this catalog at build time. navbar.json — Solutions → Oplossingen. footer.json — Solutions → Oplossingen, Guides → Gidsen, Case studies → Klantcases. Apps / Academy / Support / Partners / Team / Tutorials / Webinars / Blogs / Open source stay as loanwords by convention. --- i18n/nl/code.json | 182 ++++++++++++++++++- i18n/nl/docusaurus-theme-classic/footer.json | 6 +- i18n/nl/docusaurus-theme-classic/navbar.json | 2 +- 3 files changed, 181 insertions(+), 9 deletions(-) diff --git a/i18n/nl/code.json b/i18n/nl/code.json index 68faaf88e..521de2df2 100644 --- a/i18n/nl/code.json +++ b/i18n/nl/code.json @@ -44,7 +44,7 @@ "description": "The label of the link targeting the tag list page" }, "theme.colorToggle.ariaLabel.mode.system": { - "message": "system mode", + "message": "systeemmodus", "description": "The name for the system color mode" }, "theme.colorToggle.ariaLabel.mode.light": { @@ -175,11 +175,11 @@ "description": "The ARIA label to collapse the sidebar category" }, "theme.IconExternalLink.ariaLabel": { - "message": "(opens in new tab)", + "message": "(opent in nieuw tabblad)", "description": "The ARIA label for the external link icon" }, "theme.NavBar.navAriaLabel": { - "message": "Main", + "message": "Hoofdnavigatie", "description": "The ARIA label for the main navigation" }, "theme.navbar.mobileLanguageDropdown.label": { @@ -255,11 +255,11 @@ "description": "The ARIA label for hamburger menu button of mobile navigation" }, "theme.navbar.mobileDropdown.collapseButton.expandAriaLabel": { - "message": "Expand the dropdown", + "message": "Klap het dropdown-menu uit", "description": "The ARIA label of the button to expand the mobile dropdown navbar item" }, "theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel": { - "message": "Collapse the dropdown", + "message": "Klap het dropdown-menu in", "description": "The ARIA label of the button to collapse the mobile dropdown navbar item" }, "theme.docs.sidebar.expandButtonTitle": { @@ -385,5 +385,177 @@ "theme.academy.newsletter.fineprint": { "message": "We mailen vanuit info@conduction.nl. Geen lijstverkoop.", "description": "Small-print line below the newsletter form" + }, + "preset.footer.game.ariaLabel": { + "message": "Mini-game: bootjes zinken", + "description": "Accessible name for the footer minigame region" + }, + "preset.footer.game.boatsLeft": { + "message": "Boten over", + "description": "HUD label counting boats remaining" + }, + "preset.footer.game.seconds": { + "message": "Seconden", + "description": "HUD label for the seconds-remaining timer" + }, + "preset.footer.game.overAriaLabel": { + "message": "Mini-game afgelopen", + "description": "Accessible name for the game-over dialog" + }, + "preset.footer.game.timesUp": { + "message": "Tijd is om", + "description": "Default headline shown when the minigame timer hits zero" + }, + "preset.footer.game.sunk": { + "message": "gezonken", + "description": "Suffix after the number of boats sunk in the game-over stat" + }, + "preset.footer.game.playAgain": { + "message": "Opnieuw spelen", + "description": "Button label to restart the minigame" + }, + "preset.footer.brandBlurb": { + "message": "Open-source apps voor {nextcloud}. Gebouwd en onderhouden door Conduction in Amsterdam, uitgegeven onder EUPL-1.2.", + "description": "Footer brand-citation paragraph. {nextcloud} is the styled Nextcloud word." + }, + "preset.navbar.github.ariaLabel": { + "message": "GitHub-repository", + "description": "Default accessible label for the icon-only GitHub link in the navbar" + }, + "preset.navbar.apiDocs.label": { + "message": "API-documentatie", + "description": "Default label for the API Documentation navbar link when the consuming site does not set one" + }, + "preset.navbar.logoAlt": { + "message": "{title}-avatar", + "description": "Default alt text for the navbar logo. {title} is the site title." + }, + "preset.featuredCard.minRead": { + "message": "{minutes} min lezen", + "description": "Duration label for text content" + }, + "preset.featuredCard.minWatch": { + "message": "{minutes} min kijken", + "description": "Duration label for video content" + }, + "preset.featuredCard.cta": { + "message": "Lees meer", + "description": "Default CTA label on FeaturedCard when the consuming MDX does not pass ctaLabel" + }, + "preset.featuredCard.modulePart": { + "message": "Deel {position}", + "description": "Module-position label when total parts is unknown" + }, + "preset.featuredCard.modulePartOf": { + "message": "Deel {position} van {total}", + "description": "Module-position label, e.g. 'Part 2 of 4'" + }, + "preset.featuredCard.audienceFor": { + "message": "Voor: {list}", + "description": "Audience line under the FeaturedCard meta row" + }, + "preset.contentCard.minLabel": { + "message": "{minutes} min", + "description": "Duration label for content cards" + }, + "preset.contentCard.audienceFor": { + "message": "Voor: {list}", + "description": "Audience line under the ContentCard meta row" + }, + "preset.moduleCard.minLabel": { + "message": "{minutes} min", + "description": "Duration label for module cards" + }, + "preset.moduleCard.moduleLabel": { + "message": "MODULE", + "description": "Eyebrow label on a ModuleCard" + }, + "preset.moduleCard.singlePart": { + "message": "1 DEEL", + "description": "Parts count on a single-part ModuleCard" + }, + "preset.moduleCard.multipleParts": { + "message": "{count} DELEN", + "description": "Parts count on a multi-part ModuleCard" + }, + "preset.moduleCard.audienceFor": { + "message": "Voor: {list}", + "description": "Audience line under the ModuleCard meta row" + }, + "preset.contentTypeFilter.all": { + "message": "Alles", + "description": "Default label on the 'all' chip of the content type filter" + }, + "preset.nextSteps.title": { + "message": "Volgende stappen", + "description": "Default section heading on NextSteps" + }, + "preset.nextSteps.step.cta": { + "message": "Lees meer", + "description": "Default CTA label on a NextStep card" + }, + "preset.gameModal.eyebrow.won": { + "message": "Mini-game voltooid", + "description": "Eyebrow above the game-over heading when the player won" + }, + "preset.gameModal.eyebrow.lost": { + "message": "Game over", + "description": "Eyebrow above the game-over heading when the player lost" + }, + "preset.gameModal.title.won": { + "message": "Mooi gespeeld.", + "description": "Default headline on the game-over modal when the player won" + }, + "preset.gameModal.title.lost": { + "message": "Dat waren ze allemaal.", + "description": "Default headline on the game-over modal when the player lost" + }, + "preset.gameModal.subtitle.won": { + "message": "Je hebt een verborgen Conduction-mini-game uitgespeeld.", + "description": "Default subtitle on the game-over modal when the player won" + }, + "preset.gameModal.subtitle.lost": { + "message": "Probeer het opnieuw, de regen stopt niet.", + "description": "Default subtitle on the game-over modal when the player lost" + }, + "preset.gameModal.close": { + "message": "Sluiten", + "description": "Accessible label for the close (×) button on the game-over modal" + }, + "preset.gameModal.scorePill": { + "message": "score: {score}", + "description": "Default score pill text. {score} is the numeric score." + }, + "preset.gameModal.progress.found": { + "message": "{found} / {total} mini-games gevonden", + "description": "Progress label below the game-over copy" + }, + "preset.gameModal.cta.remaining": { + "message": "{remaining, plural, one {# game nog verstopt ergens. Blijf klikken.} other {# games nog verstopt ergens. Blijf klikken.}}", + "description": "CTA text on the game-over modal when at least one game is still hidden" + }, + "preset.gameModal.cta.allFound": { + "message": "Alle vijf gevonden. Je hebt de kit gelezen.", + "description": "CTA text on the game-over modal when the player has discovered every mini-game" + }, + "preset.gameModal.action.close": { + "message": "Sluiten", + "description": "Close button label on the game-over modal" + }, + "preset.gameModal.action.replay": { + "message": "Opnieuw spelen", + "description": "Replay button label on the game-over modal" + }, + "preset.audience.short.mkb": { + "message": "MKB", + "description": "Short audience label used in the 'For: ...' line. Slug: mkb." + }, + "preset.audience.short.government": { + "message": "Overheid", + "description": "Short audience label used in the 'For: ...' line. Slug: government." + }, + "preset.audience.short.developer": { + "message": "Developer", + "description": "Short audience label used in the 'For: ...' line. Slug: developer." } } diff --git a/i18n/nl/docusaurus-theme-classic/footer.json b/i18n/nl/docusaurus-theme-classic/footer.json index bb0b889be..0ede88414 100644 --- a/i18n/nl/docusaurus-theme-classic/footer.json +++ b/i18n/nl/docusaurus-theme-classic/footer.json @@ -4,7 +4,7 @@ "description": "The title of the footer links column with title=Apps in the footer" }, "link.title.Solutions": { - "message": "Solutions", + "message": "Oplossingen", "description": "The title of the footer links column with title=Solutions in the footer" }, "link.title.Resources": { @@ -88,7 +88,7 @@ "description": "The label of footer link with label=Blogs linking to /academy?type=blog" }, "link.item.label.Guides": { - "message": "Guides", + "message": "Gidsen", "description": "The label of footer link with label=Guides linking to /academy?type=guide" }, "link.item.label.Tutorials": { @@ -100,7 +100,7 @@ "description": "The label of footer link with label=Webinars linking to /academy?type=webinar" }, "link.item.label.Case studies": { - "message": "Case studies", + "message": "Klantcases", "description": "The label of footer link with label=Case studies linking to /academy?type=case-study" } } diff --git a/i18n/nl/docusaurus-theme-classic/navbar.json b/i18n/nl/docusaurus-theme-classic/navbar.json index 382cb4ca6..de0d22c6a 100644 --- a/i18n/nl/docusaurus-theme-classic/navbar.json +++ b/i18n/nl/docusaurus-theme-classic/navbar.json @@ -12,7 +12,7 @@ "description": "Navbar item with label Apps" }, "item.label.Solutions": { - "message": "Solutions", + "message": "Oplossingen", "description": "Navbar item with label Solutions" }, "item.label.Support": { From 009529164b8f5420a1e7f0628d228f0bc0c59a29 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 21 May 2026 10:56:01 +0200 Subject: [PATCH 069/330] copy(connext): reframe developer section around Conduction Academy (#99) Drop the "open source is only open if you can extend it" framing on the /connext page in favour of a section that introduces the Conduction Academy and surfaces the three learning-path tutorials (run Nextcloud locally, build your first app, integrate with your stack). Applied to both the EN and NL locales so they ship in sync. --- .../connext.mdx | 29 +++++++++++++ src/pages/connext.mdx | 42 +++++++++++++++---- 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/connext.mdx b/i18n/nl/docusaurus-plugin-content-pages/connext.mdx index 85b97d4ce..3f5b7138d 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/connext.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/connext.mdx @@ -102,6 +102,35 @@ import {totalPartners} from '@site/src/data/partners-catalog'; /> +
+ Leer bouwen op het platform.} + align="stack" + lede={<>De Conduction Academy is waar je leert ontwikkelen op de stack. Gratis, hands-on, in je eigen tempo. Drie tutorials nemen je mee van een schone lokale Nextcloud, naar een werkende app op de Conduction-stack, naar de koppeling met de tools die je al draait. Geen voorkennis van het platform nodig.} + /> + + } + name="Tutorial 1 · Draai Nextcloud lokaal" + why="Drie commando's, een kwartier. Zet een schone Nextcloud op localhost op, klaar om tegenaan te ontwikkelen." + /> + } + name="Tutorial 2 · Bouw je eerste app" + why="Een vierdelige walkthrough van een blanco template naar een werkende DeskDesk-app op de volledige Conduction-stack." + /> + } + name="Tutorial 3 · Integreer met je stack" + why="Koppel je app aan de tools die je al draait. REST, MCP, webhooks, allemaal via OpenConnector. (in ontwikkeling)" + /> + +
+ +This is not a suite.
It is Nextcloud as a platform.} + lede={<>A suite bundles mail, files, and office apps. A workspace adds an integrated ecosystem on top: the apps governments already need, and the building blocks developers can extend. That's what makes Nextcloud a platform.} + meta="Blog · 7 min read · By Ruben van der Linde, CTO" + card={{ + title: 'Want the full blog?', + body: 'The blog on Nextcloud as a platform, the citizen-developer wave, and the sovereignty calculus on AI.', + cta: {label: 'Read the blog', href: '/academy/the-platform-moment'}, + }} +/> +
This is not an app shelf.
It is Nextcloud as a platform.} + eyebrow="Conduction Academy" + title={<>Learn to build on the platform.} align="stack" - lede={<>The sovereign-workplace bundles are the start, not the finish. Microsoft has spent five years training a generation of citizen developers on Power Platform; Europe has eighteen to thirty-six months to ship a credible architectural answer. Our CTO wrote down why we think the shape of that answer is already sitting in front of us.} + lede={<>The Conduction Academy is where you learn to develop on the stack. Free, hands-on, self-paced. Three tutorials take you from a clean local Nextcloud, to a working app on the Conduction stack, to wiring it into the tools you already run. No prior platform knowledge required.} /> -

- Read the thinkpiece -

+ + } + name="Tutorial 1 · Run Nextcloud locally" + why="Three commands, fifteen minutes. Spin up a clean Nextcloud on localhost, ready to develop against." + /> + } + name="Tutorial 2 · Build your first app" + why="A four-part walk-through from a blank template to a shipped DeskDesk app on the full Conduction stack." + /> + } + name="Tutorial 3 · Integrate with your stack" + why="Wire your app into the tools you already run. REST, MCP, webhooks, all through OpenConnector. (in progress)" + /> +
Date: Thu, 21 May 2026 11:10:31 +0200 Subject: [PATCH 070/330] feat(academy): three-paths intro + advanced manifest tutorial + v2.7.0 refresh (#100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two new tutorials and refreshes the existing series to match the current Conduction Academy curriculum. Tutorial 0 — Three paths, one curriculum (NEW) The orientation lap before the hands-on series. Lays out the three app-building paths honestly: traditional Nextcloud, OpenRegister + nextcloud-vue, and the full manifest. Picks the third for the rest of the series and explains why. Also surfaces OpenRegister's inherent perks (RBAC, audit log, GraphQL, cross-app references, files, calendar/contacts/Talk integrations, multi-tenancy) so the reader knows what Path 2 and Path 3 inherit before any lib code enters the picture. Explicit two-surfaces framing: manifest is the declarative "what", nextcloud-vue is the rendering "how", they're separable but taught together. Tutorial 5 — Advanced manifest features (NEW) The v2.7.0 features Parts 1–4 deliberately skipped: config.actionToggles + readOnly shorthand, typed fieldWidgets for rich form fields, @route. sentinels, config.mode:'public' for token-scoped pages, the wiki/search/roadmap/map/logs/settings page types, and the softened _note-on-custom rule. Plus a decision table for choosing between fieldWidgets, custom pages, and registered integrations. Tutorial 2 refresh Manifest example now uses the v2 schema URL. New rules section explains top-level forwarding, schema→objectType bridging, and the v2.7.0-only shapes (actionToggles, fieldWidgets, mode:public, sidebar:true|object, softened _note). Page-type list expanded from 4 (index/detail/dashboard/custom) to 7 (adds form/wiki/search). Forward-link to Tutorial 5 added. Tutorial 4 patch Sidebar object form section now spells out widgets[] vs component per-tab decision rule, sidebar:true shorthand vs object form, and when to reach for the custom-component escape hatch. Closes the reader's most-asked question after Tutorial 4. Tutorial 1 + build-an-app Tutorial 1 intro routes through Tutorial 0; series table now lists all seven entries (0–5 plus the 4 hands-on parts) with cross-links. build-an-app gets a footer pointing at the proper series so the 30-minute teaser links into the real curriculum. --- academy/2026-04-20-build-an-app/index.mdx | 6 + .../index.mdx | 161 +++++++++ .../index.mdx | 14 +- .../index.mdx | 47 ++- .../index.mdx | 6 +- .../index.mdx | 316 ++++++++++++++++++ 6 files changed, 532 insertions(+), 18 deletions(-) create mode 100644 academy/2026-05-05-tutorial-0-three-paths/index.mdx create mode 100644 academy/2026-05-21-deskdesk-tutorial-5-advanced-manifest/index.mdx diff --git a/academy/2026-04-20-build-an-app/index.mdx b/academy/2026-04-20-build-an-app/index.mdx index 726dcb8c5..bc666bbd2 100644 --- a/academy/2026-04-20-build-an-app/index.mdx +++ b/academy/2026-04-20-build-an-app/index.mdx @@ -30,3 +30,9 @@ A small "incident reporter" app: one register, two schemas, a public form that p ## When you are done You have an installable Nextcloud app, a published API, and a working form. Total time: about half an hour if Nextcloud is already running. + +## Want the proper tour? + +This is the 30-minute teaser. The **[DeskDesk tutorial series](/academy/deskdesk-tutorial-0-three-paths)** is the four-part walkthrough that builds a real app on the same stack — starting with [Part 0: Three paths, one curriculum](/academy/deskdesk-tutorial-0-three-paths) which is the honest map of the three ways to build Conduction apps (traditional Nextcloud / OpenRegister + nextcloud-vue / full manifest), and why we teach the manifest path in the rest of the series. + +If this 30-minute tutorial felt like it skipped over how `App.vue`, the router, and the page components actually came together, that's because it does — Part 0 names the trade-off and Parts 1–5 walk it from the chassis up. diff --git a/academy/2026-05-05-tutorial-0-three-paths/index.mdx b/academy/2026-05-05-tutorial-0-three-paths/index.mdx new file mode 100644 index 000000000..b625beeda --- /dev/null +++ b/academy/2026-05-05-tutorial-0-three-paths/index.mdx @@ -0,0 +1,161 @@ +--- +slug: deskdesk-tutorial-0-three-paths +title: "Build a Nextcloud app on the Conduction stack — Part 0: Three paths, one curriculum" +contentType: tutorial +authors: [conduction] +date: 2026-05-05 +summary: Before you write a line of code, decide which of the three Conduction app-building paths fits the app you have in mind. Traditional Nextcloud, OpenRegister + nextcloud-vue, or full manifest — each one builds on what the previous gives you, and the rest of the DeskDesk series teaches the manifest path because that is where the curve becomes steep in our favour. +tags: [App development, Architecture, OpenRegister, nextcloud-vue, Manifest, Tutorial series] +apps: [openregister] +durationMinutes: 15 +series: deskdesk-tutorial +partNumber: 0 +--- + +import { + Outcomes, + Outcome, + Prerequisites, + PrerequisiteItem, + NextSteps, + NextStep, + HexCard, +} from '@conduction/docusaurus-preset/components'; + +This is **Part 0** of the four-part DeskDesk tutorial — the orientation lap. Before scaffolds, schemas, or manifests, the most useful thing we can hand a new developer is an honest map of the three ways Conduction apps actually get built, what each one costs, and what each one gives back. The rest of the series picks one path and shows the receipts. Part 0 explains why. + +A second thing matters from minute one: **the manifest system and the `@conduction/nextcloud-vue` component library are two different surfaces**. The manifest is the *declarative what* — a single `manifest.json` that describes navigation, pages, configuration. `nextcloud-vue` is the *rendering how* — the `Cn*` components the manifest dispatches to (`CnAppRoot`, `CnPageRenderer`, `CnIndexPage`, `CnDetailPage`, `CnDashboardPage`, and the rest). They are technically separable. They are also so intertwined in practice that the rest of this series teaches them together, and every lesson names which surface it is touching. + +{/* truncate */} + + + A clear picture of the three Conduction app-building paths — traditional Nextcloud, OpenRegister + nextcloud-vue, and full manifest — with honest trade-offs for each. + A mental model for which path to choose based on app scope, team size, and how much of your behaviour is genuinely bespoke. + An understanding that OpenRegister alone already gives you RBAC, audit logging, GraphQL, and cross-app integrations — even if you write no manifest at all. + A clear sense of how the manifest and nextcloud-vue relate as two surfaces of the same curriculum. + + +## The three paths at a glance + +The same DeskDesk feature — a list of desks with a detail page, a booking dialog, and a sidebar showing audit history — can be built three ways. Pick the table cell that sounds like your team. + +| | **Path 1: Traditional Nextcloud** | **Path 2: OpenRegister + nextcloud-vue** | **Path 3: Manifest + nextcloud-vue** | +|---|---|---|---| +| **What you write** | PHP controllers, Doctrine entities + migrations, hand-rolled Vue views, custom routes, your own CRUD endpoints | Schemas in JSON, register configuration, hand-rolled Vue views built from `Cn*` components, your own `App.vue` + router | Schemas in JSON, **one `manifest.json`** describing pages + config, only the genuinely bespoke views as `type: 'custom'` | +| **Per-app code for the DeskDesk feature above** | ~1500 LOC | ~600 LOC | ~30 LOC + the manifest | +| **Where the data lives** | App-private database tables you defined | A shared OpenRegister register — every other app on the same Nextcloud can read/write it through the registry | Same as Path 2 | +| **RBAC, audit log, GraphQL, cross-app references, file attachments, calendar/contacts integrations** | Build each one yourself | **Free** — OpenRegister ships them. See "What OpenRegister gives you regardless" below | **Free** — same as Path 2 | +| **Listing pages, detail pages, filter sidebars, mass-action dialogs** | Build each one yourself | Reuse `CnIndexPage`, `CnDetailPage`, `CnDashboardPage` — schema drives the columns and forms | Reuse the same components, dispatched declaratively by `CnPageRenderer` from your `manifest.json` | +| **Adding a `priority` field to bookings** | New migration, new controller code, new Vue field, new validation | Add field to schema → forms and tables pick it up automatically | Same as Path 2 — schema-driven all the way through | +| **Adding a kanban board (genuinely bespoke)** | Build it | Build it as a Vue component, mount it in your router | Build it as a Vue component, declare `{type:'custom', component:'PipelineBoard'}` in the manifest | +| **Maintenance burden across 10 similar apps** | Each app drifts. 10 list pages, 10 detail pages, all slightly different. | The Vue surface still drifts — every app's `App.vue` and router are hand-written. Schema migrations stay clean. | One manifest shape across the fleet. A lib bump fixes every app at once. | +| **Learning curve to first working page** | Lowest if you already know Nextcloud app dev. The patterns are documented. | Medium — schemas + `Cn*` components are new but well-trodden. | Steepest *initially* — you have to grok manifest dispatch first. After Part 2 the curve inverts. | +| **Ceiling** | Whatever you can build in PHP + Vue. No ceiling but no leverage either. | Schema-driven CRUD scales beautifully. Bespoke flows are still hand-coded Vue. | Same as Path 2 plus: every Conduction app upgrades together when the manifest schema or `nextcloud-vue` evolves. | + +### When each path is the right pick + +**Path 1 — Traditional Nextcloud** is the right pick when: +- You're shipping a Nextcloud-native feature with no use of cross-app shared data (e.g. a Talk command extension, an OAuth provider, a calendar provider plugin). +- You need behaviour that lives below the data layer — file storage hooks, dav endpoints, sharing extensions. +- You're maintaining a legacy app whose patterns predate OpenRegister and a rewrite isn't justified. +- You explicitly need the freedom to depart from the Conduction stack — bring-your-own-database, bring-your-own-frontend-framework, anything where the constraints would slow you down. + +**Path 2 — OpenRegister + nextcloud-vue** is the right pick when: +- Your data is fundamentally CRUD against a small set of schemas, but you want to keep the *page composition* hand-written for now — maybe because the existing app already has a custom `App.vue`, or because you want to migrate incrementally. +- You're prototyping and you want to feel out a few hand-rolled views before committing to a manifest-driven shape. +- The team has not yet learned the manifest mechanics and you have a deadline. + +**Path 3 — Manifest + nextcloud-vue** is the right pick when: +- The app is mostly a set of register-backed CRUD surfaces with a few bespoke flows on top — which describes the overwhelming majority of Conduction apps. +- You want the app to stay on the upgrade train: lib bumps, schema enhancements, accessibility improvements all flow in without per-app work. +- You're starting fresh. The total time to a working app on Path 3 is *less* than on Path 2 once you've done it once, even though the first hour is steeper. +- You want to be able to redesign the navigation, swap a list for a dashboard, or add a wiki section by editing JSON instead of rewriting Vue. + +The DeskDesk tutorial series teaches **Path 3**. Part 1 lays the chassis. Part 2 introduces the manifest. Part 3 shows how a single schema annotation makes bookings appear in Nextcloud Calendar with no glue code. Part 4 adds a knowledge sidebar fed from xWiki and ships the app. + +## What OpenRegister gives you regardless of path + +This part surprises people. OpenRegister is not just "a register with CRUD endpoints" — it's a data platform that ships several Nextcloud-native superpowers the moment you put a schema in it. Whether you build on Path 2 or Path 3, you inherit all of these without writing them: + + + Schemas declare per-property read/write permissions, group restrictions, and ownership rules. The OpenRegister API enforces them; CnIndexPage and CnDetailPage respect them; CnFormDialog hides fields the current user cannot write. Add a property to a schema, mark it readOnly: true for the users group, and every surface in every app respects that rule. + + + + Every create, update, and delete writes an audit entry. The audit trail is visible in the standard CnObjectSidebar tab on every detail page. Querying the audit trail is a normal OR API call — your manifest can compose audit views without writing logging code. + + + + Each register exposes a GraphQL schema generated from the JSON Schema. Cross-app queries ("give me every booking where the desk is on floor 2 and the booker is in the engineering group") are one query, not a controller. The endpoint is enabled at the register level. + + + + A booking schema can reference a desk schema in a different app's register. The reference is a typed UUID relation that OR resolves on read. CnDetailGrid renders the related object's title as a link to the other app's detail page. No HTTP roundtrip in your code. + + + + Every register-backed object can have files attached, stored in the user's Nextcloud Files. CnObjectSidebar's built-in Files tab handles upload, preview, and download. Your schema declares the attachment property; you write zero file-handling code. + + + + Annotate a schema property as format: "calendar" and bookings appear in the user's Nextcloud Calendar. Tag a person reference with format: "contact" and the linked vCard becomes a click-through. Pluggable integration registry (ADR-019) lets you add Talk threads, signed documents, or any other Nextcloud surface to a register without touching the apps that consume the register. + + + + OR's register lifecycle handles tenant isolation at the register level. Object visibility, user scoping, and group-based read filters are configuration, not code. Apps inherit the tenant model from their register. + + +Paths 2 and 3 both build on this foundation. Path 1 does not — every one of the bullets above is a separate engineering project if you start from raw Nextcloud. + +## Two surfaces, one curriculum + +A common confusion when reading the rest of this series: **the manifest is not the same thing as the component library**, even though we teach them together. + +- The **manifest** is `src/manifest.json`. It is a JSON document validated by a schema (`https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json`, currently version 2.7.0). It describes navigation entries, routes, and what each page renders. It has no runtime behaviour on its own. +- The **component library** is `@conduction/nextcloud-vue`. It is a set of Vue 2 components prefixed `Cn*` — `CnAppRoot` mounts the app shell, `CnPageRenderer` reads the manifest and dispatches the right page component, `CnIndexPage`/`CnDetailPage`/`CnDashboardPage` render the actual page surfaces. + +You can use the library without the manifest. Path 2 in the table above does exactly that — a hand-rolled `App.vue` mounts `Cn*` components directly in a hand-written router. You write more Vue, but you keep the schema-driven listing/detail/form behaviour and you keep all the OpenRegister perks. + +You cannot use the manifest without the library. The manifest is meaningless until something reads it — `CnPageRenderer` is that something. So the manifest path is always a *manifest + library* curriculum. + +The implication for how to read this series: + +| Lesson | Manifest surface | Library surface | +|---|---|---| +| **Part 1: Scaffold** | none yet | `CnAppRoot` chassis | +| **Part 2: Schemas + manifest** | introduce `manifest.json`, page types, `config` props | `CnPageRenderer` dispatch, `CnIndexPage` + `CnDetailPage` props | +| **Part 3: Schema-driven integrations** | unchanged | calendar provider annotation flows through `CnDetailGrid` | +| **Part 4: Knowledge tab + ship** | sidebar tabs in `config.sidebar`, `type: 'custom'` for the wiki view | host-app SFC registered as a custom component, `CnObjectSidebar` consumes manifest tabs | + +If you only remember one thing about the relationship: **the manifest answers "what should this app render"; the library answers "how does that get rendered". The library makes the manifest live.** + +## A note on choosing once vs choosing later + +You do not have to pick a path forever. Apps move between Path 2 and Path 3 routinely — a few of the Conduction apps started life on Path 2 and moved to Path 3 once the team learned the manifest. The migration is usually 1–2 days for an app with 5–10 schemas, because the schemas don't change at all; only `App.vue` + the router are replaced by `CnAppRoot` + a manifest. + +Moving from Path 1 to Path 2 or Path 3 is harder because you have to migrate the data layer to a register. Plan that as a real project, not as a refactor between sprints. + +## Where to go from here + + + + + + + diff --git a/academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx b/academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx index 50889b464..2566cb162 100644 --- a/academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx +++ b/academy/2026-05-10-deskdesk-tutorial-1-scaffold/index.mdx @@ -23,7 +23,9 @@ import { HexCard, } from '@conduction/docusaurus-preset/components'; -This is **Part 1 of a four-part tutorial** that builds **DeskDesk**, a flexible desk-booking app for an open-office environment, on the full Conduction Nextcloud stack. The end product: pick a desk on a floor, book a slot, see your booking in your Nextcloud Calendar, and surface zone-specific knowledge articles from xWiki right next to the booking. Every piece reuses what `@conduction/nextcloud-vue` and OpenRegister already give you for free. +This is **Part 1 of the DeskDesk tutorial series** — the hands-on chassis lap. If you haven't read [Part 0: Three paths, one curriculum](/academy/deskdesk-tutorial-0-three-paths) yet, that's the orientation that explains *why* we build on this stack rather than raw Nextcloud, and how the manifest and `@conduction/nextcloud-vue` work as two intertwined surfaces. Part 1 picks up after that decision is made. + +You'll build **DeskDesk**, a flexible desk-booking app for an open-office environment, on the full Conduction Nextcloud stack. The end product: pick a desk on a floor, book a slot, see your booking in your Nextcloud Calendar, and surface zone-specific knowledge articles from xWiki right next to the booking. Every piece reuses what `@conduction/nextcloud-vue` and OpenRegister already give you for free. In Part 1 you scaffold the app, rename it, build it, and see the canonical app chassis on screen. No data, no integrations yet. We get the bones right first. @@ -36,14 +38,16 @@ In Part 1 you scaffold the app, rename it, build it, and see the canonical app c A clean foundation for Part 2, where the chassis starts holding real data. -## What we're building, across all four parts +## What we're building, across the series | Part | Title | Adds | |---|---|---| +| 0 | [Three paths, one curriculum](/academy/deskdesk-tutorial-0-three-paths) | The orientation lap — which path to pick and why | | 1 | Scaffold (you're here) | Empty app shell, chassis visible, navigable | -| 2 | Schemas + manifest | Desk and booking schemas, full CRUD, dashboard | -| 3 | Schema-driven integrations | Bookings appear in NC Calendar via the OpenRegister calendar provider | -| 4 | External knowledge + ship | xWiki source via OpenConnector, sidebar tab, package, publish | +| 2 | [Schemas + manifest](/academy/deskdesk-tutorial-2-schemas-manifest) | Desk and booking schemas, full CRUD, dashboard | +| 3 | [Schema-driven integrations](/academy/deskdesk-tutorial-3-calendar) | Bookings appear in NC Calendar via the OpenRegister calendar provider | +| 4 | [External knowledge + ship](/academy/deskdesk-tutorial-4-knowledge-and-ship) | xWiki source via OpenConnector, sidebar tab, package, publish | +| 5 | [Advanced manifest features](/academy/deskdesk-tutorial-5-advanced-manifest) | actionToggles, fieldWidgets, public-mode pages, the other page types | Same shape, two repos: diff --git a/academy/2026-05-13-deskdesk-tutorial-2-schemas-manifest/index.mdx b/academy/2026-05-13-deskdesk-tutorial-2-schemas-manifest/index.mdx index fc54a3439..b59260a95 100644 --- a/academy/2026-05-13-deskdesk-tutorial-2-schemas-manifest/index.mdx +++ b/academy/2026-05-13-deskdesk-tutorial-2-schemas-manifest/index.mdx @@ -146,11 +146,11 @@ The matching path the SettingsService takes is **importFromFilePath** with a pat ## Step 4: The manifest -`src/manifest.json` is the single declarative description of the app shell. Three top-level keys: `dependencies`, `menu`, `pages`. +`src/manifest.json` is the single declarative description of the app shell. Four top-level keys you care about: `$schema`, `dependencies`, `menu`, `pages`. ```json title="src/manifest.json" { - "$schema": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest.schema.json", + "$schema": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json", "version": "1.0.0", "id": "deskdesk", "title": "DeskDesk", @@ -162,9 +162,12 @@ The matching path the SettingsService takes is **importFromFilePath** with a pat ], "pages": [ {"id": "desks-index", "route": "/", "type": "index", "title": "deskdesk.desks.indexTitle", - "config": {"register": "deskdesk", "schema": "desk", "columns": ["label","floor","zone","equipment","capacity","accessibility"], "filters": ["floor","zone","equipment","accessibility"], "defaultSort": {"key": "label", "order": "asc"}}}, + "config": {"register": "deskdesk", "schema": "desk", + "columns": ["label","floor","zone","equipment","capacity","accessibility"], + "filters": ["floor","zone","equipment","accessibility"], + "defaultSort": {"key": "label", "order": "asc"}}}, {"id": "desks-detail", "route": "/desks/:id", "type": "detail", "title": "deskdesk.desks.detailTitle", - "config": {"register": "deskdesk", "schema": "desk"}} + "config": {"register": "deskdesk", "schema": "desk", "sidebar": true}} /* bookings + floors pages follow the same pattern */ ] } @@ -172,9 +175,11 @@ The matching path the SettingsService takes is **importFromFilePath** with a pat A few rules to note: -- **`page.id` is the vue-router route name.** CnPageRenderer matches `$route.name === page.id` to pick the right page. -- **`page.type` is closed.** `index | detail | dashboard | custom`. Use `custom` with a `component` registry entry for one-off pages. -- **`page.config` is forwarded as props.** `register`, `schema`, `columns`, `filters`, `defaultSort` map to CnIndexPage props one-to-one. +- **The `$schema` URL points at `app-manifest-v2.schema.json`.** This is the v2 schema (currently at version 2.7.0). It's the source of truth for what `manifest.json` is allowed to contain — your IDE picks up autocompletion and validation from it the moment the URL is present. +- **`page.id` is the vue-router route name.** `CnPageRenderer` matches `$route.name === page.id` to pick the right page entry from `pages[]`. +- **`page.type` is a closed enum.** The 13 supported types: `index | detail | dashboard | logs | settings | chat | files | form | wiki | map | search | roadmap | custom`. Most apps will use `index`, `detail`, `dashboard`, and `custom`; the others are there when you need them. See Part 5 for `form`/`wiki`/`search`/`map` examples. +- **`page.config` is forwarded as props.** `register`, `schema`, `columns`, `filters`, `defaultSort` map to `CnIndexPage` props one-to-one. The renderer also forwards top-level page fields (`title`, `widgets`, `actions`, `sidebar`, `description`, `icon`) alongside `config`, and bridges `config.schema → objectType` plus `:id → objectId` for `type:'detail'` so the lib's external `CnObjectSidebar` activates automatically. +- **`config.sidebar: true` on `type:'detail'`** activates the host's mounted `CnObjectSidebar` with its built-in tabs (Files, Notes, Tags, Tasks, Audit Trail) for the current object. Without it the detail page renders without a sidebar — useful for read-only views, but for DeskDesk every detail page wants the standard tabs, so we set it everywhere. ## Step 5: Replace App.vue and the router @@ -225,12 +230,25 @@ export default new Router({ }) ``` -Every page in the manifest gets a route. Every route renders CnPageRenderer. CnPageRenderer reads the manifest, finds the matching page entry by route name, and dispatches by `page.type`: +Every page in the manifest gets a route. Every route renders `CnPageRenderer`. `CnPageRenderer` reads the manifest, finds the matching page entry by route name, and dispatches by `page.type`. The most common dispatches: -- `type: "index"` → CnIndexPage with the schema's columns + filters -- `type: "detail"` → CnDetailPage with the schema's properties -- `type: "dashboard"` → CnDashboardPage with the layout -- `type: "custom"` → your registered component +- `type: "index"` → `CnIndexPage` with the schema's columns + filters +- `type: "detail"` → `CnDetailPage` with the schema's properties + the lib's external sidebar +- `type: "dashboard"` → `CnDashboardPage` with the layout grid +- `type: "form"` → `CnFormPage` for create/edit flows (supports public-mode token-scoped variant) +- `type: "wiki"` → `CnWikiPage` for markdown-article surfaces +- `type: "search"` → faceted cross-schema search +- `type: "custom"` → your own SFC registered in `customComponents` + +Schema 2.7.0 (the current beta) introduces a few v2-only shapes worth flagging — full coverage lives in Part 5, but it's useful to know the names now: + +- **`config.actionToggles`** — typed object on `type:'index'` collapsing the nine `show*`/`selectable` flags into one place. `actionToggles: { showAdd: true, showEdit: false, showDelete: false }` reads cleaner than nine sibling booleans. +- **`config.fieldWidgets[]`** — typed slot on `type:'form'` and `type:'detail'` for mounting a lib `Cn*` component as a single form field. e.g. `fieldWidgets: [{id: 'body', component: 'CnMarkdownEditor'}]`. +- **`config.mode: 'public'`** — on `type:'form'` and `type:'detail'`, marks an unauthenticated token-scoped page. Pair with a route-param sentinel like `submitEndpoint: "/index.php/apps/myapp/api/public/:token"`. +- **`config.sidebar: true | {...}`** — boolean or object form. Object form (`{enabled, show, register, schema, hiddenTabs, tabs}`) gives full control; the boolean is the shortcut for the common case. +- **`_note` on `type:'custom'`** — required when the custom mounts a host-app SFC (no lib `Cn*` analogue); softened in 2.7.0 so that a custom with `component: 'CnSomething'` no longer needs it. + +You won't use most of those in Part 2 — three index pages, three detail pages, one dashboard, all schema-driven. They're listed here so when Part 5 reaches for them they're not surprises. ## Step 6: Delete the old views @@ -290,6 +308,11 @@ This is the schema-driven discipline talked about on [nextcloud-vue.conduction.n href="/academy/deskdesk-tutorial-3-calendar" description="A single calendarProvider block on the booking schema. Bookings appear in the user's Nextcloud Calendar. No controller, no listener, no glue code." /> + + An admin-only desks listing using config.actionToggles instead of nine sibling boolean flags. + A booking-creation form with a markdown notes field, declared via config.fieldWidgets — no Vue file written. + A public booking-confirmation page reachable via a token URL, using config.mode: 'public' + route-param sentinels. + A type:'wiki' zone-guidelines surface fed by the same OpenRegister register that powers the rest of the app. + A type:'search' faceted cross-schema search page. + A working mental map of every v2.7.0 manifest feature so you can pick the right shape next time without re-reading docs. + + + + You've finished Part 2 at minimum — you have a working manifest with index and detail pages. + @conduction/nextcloud-vue at ^1.0.0-beta.65 or newer. The features in this part rely on schema 2.7.0 typed shapes. + Your src/manifest.json declares the v2 schema URL (app-manifest-v2.schema.json). Part 2 set this up. + + +## 1. `config.actionToggles` — read-only listings without nine flags + +The classic shape, before v2.7.0, for an admin-only or read-only `type:'index'` page meant nine sibling booleans: + +```json title="Old shape — works but reads poorly" +"config": { + "register": "deskdesk", "schema": "desk", "columns": [...], + "showAdd": false, "showEdit": false, "showCopy": false, "showDelete": false, + "showMassImport": false, "showMassCopy": false, "showMassDelete": false, + "selectable": false, "showFormDialog": false +} +``` + +`config.actionToggles` collapses all nine into one object. The renderer flattens it back to the underlying props at dispatch time — explicit `config.` still wins if you set both, so you can override a single flag without rewriting the toggle block. + +```json title="src/manifest.json — admin-only desks read-out" +{"id": "desks-admin-readout", + "route": "/admin/desks", + "type": "index", + "title": "deskdesk.admin.desks", + "permission": "admin", + "config": { + "register": "deskdesk", "schema": "desk", + "columns": ["label","floor","zone","equipment","capacity"], + "actionToggles": { + "showAdd": false, "showEdit": false, "showCopy": false, + "showDelete": false, "showMassImport": false, "showMassCopy": false, + "showMassDelete": false, "selectable": false, "showFormDialog": false + } + }} +``` + +There's a shorthand for the all-off case: `config.readOnly: true` expands to the same nine flags. Use it for genuinely read-only views — audit logs, archived snapshots, anything you want the user to read but not touch. + +```json +"config": {"register": "deskdesk", "schema": "desk", "readOnly": true} +``` + +## 2. `config.fieldWidgets` — lib widgets inside a form, no Vue file + +DeskDesk's booking dialog has a free-text notes field. The schema-generated input gives you a ` + What are you trying to do? + -

By submitting you agree to be contacted by Conduction once and by up to three matching partners. We share only what you typed above. Privacy statement.

+

By submitting you agree to be contacted by Conduction once and by up to three matching partners. We share only what you typed above. Privacy statement.

@@ -291,7 +291,7 @@ export const TIER_PILL_BASE = {

We review applications within five business days. Approved partners get a directory listing, a Slack invite, and a slot in the next quarterly office hours.

- diff --git a/src/pages/terms.mdx b/src/pages/terms.mdx index 57113ff56..97b2937db 100644 --- a/src/pages/terms.mdx +++ b/src/pages/terms.mdx @@ -16,12 +16,12 @@ export const TermsIcon = ( export const PROMISE_PANELS = { uptime: ( -
+
Uptime formula
-
+
Uptime % = (Agreed service hours − Downtime hours) / Agreed service hours × 100%
-
Not counted as downtime
+
Not counted as downtime
    {['Planned maintenance', 'Reduced performance', 'Force majeure', 'Internet outages', 'Customer or user actions', 'Government enforcement'].map((l, i) => (
  • @@ -33,12 +33,12 @@ export const PROMISE_PANELS = {
), response: ( -
-
Response and resolution (office hours)
-
-
Prio
-
Response
-
Workaround / Resolution
+
+
Response and resolution (office hours)
+
+
Prio
+
Response
+
Workaround / Resolution
{[ @@ -46,7 +46,7 @@ export const PROMISE_PANELS = { {p: 'P2', r: '4 h', s: '16 h / 48 h', c: 'var(--c-blue-cobalt)'}, {p: 'P3', r: '1 week', s: 'Planned', c: 'var(--c-cobalt-300)'}, ].map((row, i) => ( -
+
{row.p} @@ -56,57 +56,57 @@ export const PROMISE_PANELS = {
))}
-
Office hours = Mon–Fri 09:00–17:00, public holidays excluded.
+
Office hours = Mon–Fri 09:00–17:00, public holidays excluded.
), security: ( -
+
ISO 27001:2022 certificate -
ISO/IEC 27001:2022
+
ISO/IEC 27001:2022
), capacity: ( -
+
{[ {n: '5 GB', l: 'Storage'}, {n: '1M', l: 'Queries / month'}, {n: '10 GB', l: 'Data transfer / month'}, ].map((b, i) => ( -
-
{b.n}
-
{b.l}
+
+
{b.n}
+
{b.l}
))}
), maintenance: ( -
-
Planned maintenance window
+
+
Planned maintenance window
{[ {k: 'Notice', v: '≥ 24 hours in advance'}, {k: 'Scheduling', v: 'Outside office hours'}, {k: 'Communication', v: 'Within a reasonable timeframe'}, ].map((row, i) => (
0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> - {row.k} + {row.k} {row.v}
))}
), updates: ( -
+
Included in the SLA
    {['New versions and releases', 'Bug fixes', 'Security updates'].map((l, i) => ( -
  • +
  • {l}
  • ))}
-
Out of scope
-
New feature development beyond bug fixes and security updates is agreed per assignment.
+
Out of scope
+
New feature development beyond bug fixes and security updates is agreed per assignment.
), }; @@ -179,7 +179,7 @@ export const PROMISE_PANELS = { ## Service Level Agreement for [product] -
+
Service Level Agreement
@@ -187,19 +187,19 @@ export const PROMISE_PANELS = {
Standard
-
ISO/IEC 27001 aligned
+
ISO/IEC 27001 aligned
Uptime
-
95% · 24/7
+
95% · 24/7
P1 response
-
2 h / 4 h / 8 h
+
2 h / 4 h / 8 h
Office hours
-
Mon–Fri 09:00–17:00
+
Mon–Fri 09:00–17:00
@@ -308,7 +308,7 @@ Within this SLA Conduction provides the customer with new versions, releases, an A Conduction representative will attend the monthly community meeting for [product]. In this meeting the product is reviewed and the direction, roadmap, and decisions are explained. -
+
Adopted by the management of Conduction B.V., Amsterdam, the Netherlands. The Dutch text at /nl/terms is the legally binding version; this English text is provided for convenience.
@@ -322,14 +322,14 @@ A Conduction representative will attend the monthly community meeting for [produ Open the OpenWoo SLA → - + Quality, certifications & policy → {/* JSX string, not bare text: MDX auto-links an email address written in running text, which nested an inside this . The browser's parser pulls that nesting apart, so the client tree stops matching the server's and the page fails to hydrate. */} - {'info@conduction.nl'} + {'info@conduction.nl'}
diff --git a/src/theme/BlogPostPage/styles.module.css b/src/theme/BlogPostPage/styles.module.css index 1e99caaf5..11ed25427 100644 --- a/src/theme/BlogPostPage/styles.module.css +++ b/src/theme/BlogPostPage/styles.module.css @@ -16,7 +16,7 @@ margin: 0; font-size: 17px; line-height: 1.7; - color: var(--c-cobalt-700); + color: var(--conduction-color-text-body); } /* Heading rhythm. Direct-child selectors so the cascade does not @@ -29,14 +29,14 @@ font-size: 28px; font-weight: 700; letter-spacing: -0.01em; - color: var(--c-cobalt-900); + color: var(--conduction-color-text-strong); margin: var(--space-10) 0 var(--space-3); line-height: 1.2; } .body > h3 { font-size: 21px; font-weight: 700; - color: var(--c-cobalt-900); + color: var(--conduction-color-text-strong); margin: var(--space-7) 0 var(--space-2); line-height: 1.3; } From 3a8e61cf8ce7f0645c89977b98cbb069cfd33cd8 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 19 Sep 2026 12:58:38 +0200 Subject: [PATCH 291/330] feat(forms): the website forms reach the CRM (#204) * feat(forms): the website forms reach the CRM No form on conduction.nl has ever reached pipelinq. The live bundle carries this, right now: pipelinqLeadEndpoint:"http://localhost:8080/index.php/apps/openregister/api/objects/pipelinq/lead" PIPELINQ_LEAD_ENDPOINT was never set in the deploy workflow, so the build used its dev fallback. LeadForm correctly spots that an https page cannot POST to an http endpoint and degrades to the email fallback, which is why nobody noticed: the form never errored, it just quietly stopped being a form. Setting the variable alone would not have fixed it. The `lead` schema it posted to has, since stable 0.5.1, neither the four contact fields the form sends nor any way to satisfy its required `client` and `pipeline`. An anonymous visitor has no client. That is what the paired pipelinq change is for. So the component posts to pipelinq's own /api/enquiry now, and is named EnquiryForm, because it never created a lead and should stop implying it. Not OpenRegister's object API: OpenRegister cannot scope WHICH properties a public create may set, so the old URL let a visitor set the record's own status and handledBy. It also sends what the visitor should not have to type. pageUrl says what they were reading when they asked, and locale says which language to answer in. A honeypot rides along, positioned off-canvas rather than display:none, because some bots skip hidden inputs. /contact gains a form. It was the main conversion page and offered only a mailto link. The deploy workflow gains the variable AND a step that refuses to ship a production build still carrying the localhost default. That guard is the part that matters: setting a variable fixes today, a check that reads the built artefact stops it coming back. It is matched on the config KEY, not on the bare host, because `localhost:8080` appears in 19 bundle files, the academy tutorials being full of "Host http://localhost:8080, basic auth admin:admin" entirely on purpose. Grepping the host alone failed every deploy while looking like a careful safety check. Verified against a real build in both directions: it fails on the dev default, and passes on a real endpoint sitting next to four tutorial mentions of localhost. supportLeadTitle and partnerLeadTitle lose their em-dashes. Their output is the title sales reads in the inbox, so it is user-facing text under voice section 8. * feat(forms): the Dutch pages get the forms too The Dutch site has never had a working form, and this change would have shipped an English-only one. Docusaurus i18n page overrides REPLACE the English page rather than layering on it, so i18n/nl/docusaurus-plugin-content-pages/{contact,support}.mdx are separate documents that simply never carried the form markup. Checked against development before writing anything: the NL support override has zero form references there, so this is a pre-existing gap and not a regression. Left as it was, /nl/contact and /nl/support would have offered a Dutch visitor nothing while the English pages took submissions. For a company selling to Dutch municipalities that is the wrong half to ship. The support forms are the English section with its markup intact and only the visible strings replaced, so the two locales cannot drift in layout, design tokens or field names. `name="country"` and `type="submit"` stay English on purpose: the first is the key EnquiryForm's label map reads and the form posts, and translating it would have dropped the field silently. Swept the output for leftover English rather than trusting the substitution list, which is what caught the two the list got wrong: the eyebrow is "I want to be a partner", not the "I want to deliver" I had guessed, and "Submit application" was missing from the map entirely. Both would have rendered as English inside a Dutch form. Title helpers keep their English prefixes so one sales inbox reads consistently whichever locale a submission came from; `locale` on the enquiry already records which page it was. Verified in the built output, both locales: /contact 1 form, /support 2, /nl/contact 1, /nl/support 2, each with its honeypot, and the Dutch copy rendering. --- .github/workflows/deploy.yml | 35 +++++ docusaurus.config.js | 29 ++-- .../contact.mdx | 47 ++++++ .../support.mdx | 141 ++++++++++++++++++ .../{LeadForm.jsx => EnquiryForm.jsx} | 76 ++++++---- src/pages/contact.mdx | 47 ++++++ src/pages/support.mdx | 14 +- 7 files changed, 346 insertions(+), 43 deletions(-) rename src/components/{LeadForm.jsx => EnquiryForm.jsx} (59%) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fdd282ab6..548e18ddb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -77,11 +77,46 @@ jobs: # committed, and the build goes green with frozen numbers. Since # refreshing those numbers is the entire purpose of the nightly run, # a silent fallback would make this job theatre. + # PIPELINQ_ENQUIRY_ENDPOINT is what makes the forms work at all. Without + # it the build falls back to the localhost dev default, EnquiryForm sees + # an http endpoint on an https page, and every visitor is shown the email + # fallback instead of a form that submits. That is not hypothetical: it + # is what production did until this line existed, which is why no website + # enquiry ever reached the CRM. + # + # A repository VARIABLE, not a secret: it is a public URL that every + # visitor's browser sends a request to, so hiding it buys nothing and + # costs the ability to read the deploy log and see where leads went. - name: Build env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PIPELINQ_ENQUIRY_ENDPOINT: ${{ vars.PIPELINQ_ENQUIRY_ENDPOINT }} run: npm run build + # The build cannot fail on a missing endpoint, because a preview build of + # a docs-only PR has no reason to carry one. It CAN refuse to deploy a + # production build that would ship forms nobody can submit, which is the + # failure this whole change exists to end. + - name: The production build must carry a real enquiry endpoint + # Same condition as the Deploy step below, deliberately: this guards + # what actually ships, so if the two ever disagree the guard is either + # blocking a build nobody deploys or waving through one they do. + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/development' + run: | + # Matched on the CONFIG KEY, never on the bare host. `localhost:8080` + # on its own appears in 19 bundle files, because the academy + # tutorials are full of "Host http://localhost:8080, basic auth + # admin:admin" and they are supposed to be. Grepping for the host + # alone made this step fail every single deploy while looking like a + # careful safety check, which is worse than not having one. Measured + # against a real build, not assumed: key+host matches exactly one + # file, the config in main.*.js. + if grep -rqF 'pipelinqEnquiryEndpoint:"http://localhost' build/assets/js/; then + echo "::error::The build baked in the localhost dev endpoint. Set the PIPELINQ_ENQUIRY_ENDPOINT repository variable, or every form on the site degrades to the email fallback and no enquiry reaches the CRM." + exit 1 + fi + echo "Enquiry endpoint is not the dev default." + - name: Check the build produced both locales run: | test -f build/index.html || { echo "::error::build/index.html missing"; exit 1; } diff --git a/docusaurus.config.js b/docusaurus.config.js index b984f7dd7..d9414286f 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -82,21 +82,32 @@ module.exports = createConfig({ prizeLinkLabel: {en: 'How the giveaway works', nl: 'Zo werkt de actie'}, }, - /* Public lead-intake endpoint. The support + partner forms POST here to - create a `lead` object in the Pipelinq CRM (OpenRegister, public-create - schema, anonymous rate-limited). Set PIPELINQ_LEAD_ENDPOINT at build time - to point at a real instance. + /* Public enquiry-intake endpoint. The website forms POST here to create an + `enquiry` object in the Pipelinq CRM. Set PIPELINQ_ENQUIRY_ENDPOINT at + build time to point at a real instance. + + THIS IS PIPELINQ'S OWN ENDPOINT, NOT OPENREGISTER'S OBJECT API, and the + difference is not cosmetic. It used to be + `/apps/openregister/api/objects/pipelinq/lead`, and OpenRegister cannot + scope WHICH properties a public create may set (`readOnly` is a documented + no-op on that path), so anyone could have set the record's own `status` or + `handledBy`. Pipelinq's endpoint whitelists what a submitter may send. + + It also stopped being `lead` on purpose. A lead is a deal, with a value and + a pipeline stage, and its schema requires a `client` no anonymous visitor + has. An enquiry is what arrives before any of that is known. The fallback below is a DEV-ONLY convenience so the forms work out of the box against a local docker environment. It is NOT a production value: `localhost` is the visitor's own machine, and an http endpoint on an https - page is blocked as mixed content. LeadForm detects that at runtime and + page is blocked as mixed content. EnquiryForm detects that at runtime and degrades to an email fallback rather than accepting a submit it cannot - deliver — so shipping without PIPELINQ_LEAD_ENDPOINT is safe, just inert. */ + deliver. That fallback is not hypothetical: production shipped without the + variable set, so every visitor saw it and no form ever submitted. */ customFields: { - pipelinqLeadEndpoint: - process.env.PIPELINQ_LEAD_ENDPOINT - || 'http://localhost:8080/index.php/apps/openregister/api/objects/pipelinq/lead', + pipelinqEnquiryEndpoint: + process.env.PIPELINQ_ENQUIRY_ENDPOINT + || 'http://localhost:8080/index.php/apps/pipelinq/api/enquiry', }, /* Two locales, English default. URL shape: diff --git a/i18n/nl/docusaurus-plugin-content-pages/contact.mdx b/i18n/nl/docusaurus-plugin-content-pages/contact.mdx index 26268fb38..baddf867b 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/contact.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/contact.mdx @@ -6,6 +6,10 @@ hide_table_of_contents: true import {Section, SectionHead, TeamGrid, EmployeeCard} from '@conduction/docusaurus-preset/components'; import PageHeading from '@site/src/components/PageHeading'; +import EnquiryForm from '@site/src/components/EnquiryForm'; + +export const contactEnquiryTitle = (f) => + `Contact: ${[f.firstName, f.lastName].filter(Boolean).join(' ') || 'Websitebezoeker'}${f.organisation ? ` (${f.organisation})` : ''}`; Praat met een mens. Niet met een ticketwachtrij. @@ -67,6 +71,49 @@ import PageHeading from '@site/src/components/PageHeading'; +
+ +
+ +
+ + +
+ + + + + + + +

We gebruiken wat je hier typt om je te antwoorden, en voor niets anders. Privacyverklaring.

+ +
+
+
+
+ `Support enquiry: ${[f.firstName, f.lastName].filter(Boolean).join(' ') || 'Websitebezoeker'}${f.organisation ? ` (${f.organisation})` : ''}`; +export const partnerLeadTitle = (f) => + `Partner application: ${f.company || 'Onbekend bedrijf'}`;
@@ -186,3 +192,138 @@ import {Section, SectionHead, HowSteps, HowStep, Card, Pill, HexBullet, HexNetwo
+ +
+ +
+ + +
+ + Ik zoek hulp +
+

Koppel me aan een partner.

+

Vertel ons wat je wilt bereiken. We sturen de vraag door naar een tot drie partners die passen bij je sector, je omvang en de apps die je op het oog hebt. Geen commissie, geen exclusiviteit.

+ +
+ + +
+ + + + + +
+ + +
+ + + +

Door te versturen ga je ermee akkoord dat Conduction en maximaal drie passende partners eenmalig contact opnemen. We delen alleen wat je hierboven hebt ingevuld. Privacyverklaring.

+ +
+ + +
+ + Ik wil partner worden +
+

Meld je aan voor het partnerprogramma.

+

If your team installs and operates Conduction apps for clients, the Partner tier is open. No fee, no exclusivity. The bar is one production deployment and a public reference.

+ +
+ + +
+ + + +
+ + +
+ + + + + +

We review applications within five business days. Approved partners get a directory listing, a Slack invite, and a slot in the next quarterly office hours.

+ +
+ +
+
diff --git a/src/components/LeadForm.jsx b/src/components/EnquiryForm.jsx similarity index 59% rename from src/components/LeadForm.jsx rename to src/components/EnquiryForm.jsx index 23e1589cc..a394a47f0 100644 --- a/src/components/LeadForm.jsx +++ b/src/components/EnquiryForm.jsx @@ -9,9 +9,11 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; * An https page cannot POST to an http endpoint — the browser blocks it as * mixed content — and `localhost` is the visitor's own machine, not ours. * Either way the request can only fail, so we must not pretend to accept the - * form (that is what shipping the dev-default endpoint to production would do). + * form. That is exactly what shipping the dev-default endpoint to production + * did: every visitor to conduction.nl saw the email fallback below, because + * PIPELINQ_ENQUIRY_ENDPOINT was never set in the deploy workflow. * - * @param {string|undefined} endpoint Configured lead-intake URL. + * @param {string|undefined} endpoint Configured enquiry-intake URL. * @return {boolean} true when a submit could plausibly reach a server. */ function isEndpointUsable(endpoint) { @@ -25,30 +27,33 @@ function isEndpointUsable(endpoint) { } /** - * LeadForm — wraps a plain
and submits it to the Pipelinq CRM as a - * public `lead` object (OpenRegister public-create schema, anonymous - * rate-limited server-side). + * EnquiryForm — wraps a plain and submits it to Pipelinq's public + * intake endpoint, which stores it as an `enquiry` object. + * + * It does NOT post to OpenRegister's generic object API, and it used to. That + * matters for a reason the URL does not show: OpenRegister cannot scope WHICH + * properties a public create may set, so posting straight at the object API + * let a visitor set the enquiry's own `status` and `handledBy`. Pipelinq's + * endpoint whitelists the nine fields a submitter may send and owns the rest. * * The request is sent as `application/x-www-form-urlencoded` on purpose: that - * is a CORS "simple request", so the browser skips the preflight OPTIONS the - * OpenRegister router has no route for. The server reflects the Origin back - * (PublicApiCorsMiddleware) so this cross-origin POST can read the result. + * is a CORS "simple request", so the browser skips the preflight OPTIONS that + * would otherwise need its own route. The endpoint reflects the Origin back so + * this cross-origin POST can read the result. * - * Field mapping: any inputs named `firstName`/`lastName`/`company`/`email`/ - * `organisation`/`message` map onto the lead's intake fields; every other - * non-empty field is appended to the message body as a "Label: value" line so + * Field mapping: `firstName`/`lastName` join into `contactName`, `company` and + * `organisation` both become `organisation`, `email` becomes `contactEmail`, + * `phone` becomes `contactPhone`, and `message` is kept verbatim. Every other + * non-empty field is appended to the message body as a "Label: value" line, so * nothing the visitor typed is lost. * - * When no usable endpoint is configured the form degrades to an email fallback - * rather than accepting a submit it cannot deliver — see isEndpointUsable. - * - * @param {string} source Lead source tag (e.g. "website-support"). - * @param {Function} buildTitle (fields) => string, the lead title. + * @param {string} source Intake source tag. Must be on Pipelinq's allowlist. + * @param {Function} buildTitle (fields) => string, the enquiry title. * @param {string} successText Message shown after a successful submit. */ -export default function LeadForm({source, buildTitle, successText, children, ...formProps}) { - const {siteConfig} = useDocusaurusContext(); - const endpoint = siteConfig.customFields?.pipelinqLeadEndpoint; +export default function EnquiryForm({source, buildTitle, successText, children, ...formProps}) { + const {siteConfig, i18n} = useDocusaurusContext(); + const endpoint = siteConfig.customFields?.pipelinqEnquiryEndpoint; const [state, setState] = useState('idle'); // idle | sending | success | error // Assume usable for the server render and the first client render so the two @@ -57,7 +62,7 @@ export default function LeadForm({source, buildTitle, successText, children, ... const [usable, setUsable] = useState(true); useEffect(() => setUsable(isEndpointUsable(endpoint)), [endpoint]); - const KNOWN = new Set(['firstName', 'lastName', 'company', 'email', 'organisation', 'message']); + const KNOWN = new Set(['firstName', 'lastName', 'company', 'email', 'phone', 'organisation', 'message', 'website']); const LABELS = { sector: 'Sector', apps: 'Apps', tier: 'Tier', deployments: 'Production deployments', reference: 'Reference', country: 'Country', @@ -69,7 +74,7 @@ export default function LeadForm({source, buildTitle, successText, children, ... const form = e.currentTarget; const data = Object.fromEntries(new FormData(form).entries()); - const name = [data.firstName, data.lastName].filter(Boolean).join(' ').trim() || data.company || ''; + const contactName = [data.firstName, data.lastName].filter(Boolean).join(' ').trim(); const organisation = data.organisation || data.company || ''; // Fold any extra fields into the message so nothing is dropped. @@ -80,12 +85,20 @@ export default function LeadForm({source, buildTitle, successText, children, ... .filter(Boolean).join('\n\n'); const payload = new URLSearchParams(); - payload.set('title', buildTitle ? buildTitle(data) : (name || 'Website enquiry')); - payload.set('source', source || 'website'); - if (name) payload.set('contactName', name); + payload.set('source', source || 'website-contact'); + payload.set('title', buildTitle ? buildTitle(data) : (contactName || organisation || 'Website enquiry')); + if (contactName) payload.set('contactName', contactName); if (data.email) payload.set('contactEmail', data.email); + if (data.phone) payload.set('contactPhone', data.phone); if (organisation) payload.set('organisation', organisation); if (message) payload.set('message', message); + // Context the visitor did not have to type, and which tells sales what they + // were reading and which language to answer in. + payload.set('pageUrl', typeof window !== 'undefined' ? window.location.href : ''); + payload.set('locale', i18n?.currentLocale || ''); + // The honeypot travels as the submitter typed it, which for a human is + // empty. Pipelinq refuses a non-empty one. + payload.set('website', data.website || ''); setState('sending'); try { @@ -109,7 +122,7 @@ export default function LeadForm({source, buildTitle, successText, children, ... Received
-

Thanks — we've got it.

+

Thanks, we've got it.

{successText || "We'll be in touch shortly."}

@@ -117,7 +130,7 @@ export default function LeadForm({source, buildTitle, successText, children, ... ); } - // No usable endpoint (e.g. deployed without PIPELINQ_LEAD_ENDPOINT, so the + // No usable endpoint (e.g. deployed without PIPELINQ_ENQUIRY_ENDPOINT, so the // build fell back to the localhost dev default). Say so up front and offer a // route that works, rather than accepting the form and failing on submit. if (!usable) { @@ -125,7 +138,7 @@ export default function LeadForm({source, buildTitle, successText, children, ... e.preventDefault()}> {children}

- Online submission isn't available right now — please email{' '} + Online submission isn't available right now. Please email{' '} info@conduction.nl{' '} and we'll pick it up from there.

@@ -136,6 +149,15 @@ export default function LeadForm({source, buildTitle, successText, children, ... return ( {children} + {/* Honeypot. Hidden from people, left empty by them, filled by bots that + complete every input they find. Not `display:none`: some bots skip + those, so it is taken out of flow and out of the tab order instead. */} + {state === 'error' && (

Something went wrong sending your request. Please try again or email info@conduction.nl. diff --git a/src/pages/contact.mdx b/src/pages/contact.mdx index 654ac72c9..6750dd322 100644 --- a/src/pages/contact.mdx +++ b/src/pages/contact.mdx @@ -6,6 +6,10 @@ hide_table_of_contents: true import {Section, SectionHead, TeamGrid, EmployeeCard} from '@conduction/docusaurus-preset/components'; import PageHeading from '@site/src/components/PageHeading'; +import EnquiryForm from '@site/src/components/EnquiryForm'; + +export const contactEnquiryTitle = (f) => + `Contact: ${[f.firstName, f.lastName].filter(Boolean).join(' ') || 'Website visitor'}${f.organisation ? ` (${f.organisation})` : ''}`; Talk to a person. Not a ticket queue. @@ -67,6 +71,49 @@ import PageHeading from '@site/src/components/PageHeading'; +

+ +
+ +
+ + +
+ + + + + + + +

We use what you type here to answer you, and for nothing else. Privacy statement.

+ +
+
+
+
- `Support enquiry — ${[f.firstName, f.lastName].filter(Boolean).join(' ') || 'Website visitor'}${f.organisation ? ` (${f.organisation})` : ''}`; + `Support enquiry: ${[f.firstName, f.lastName].filter(Boolean).join(' ') || 'Website visitor'}${f.organisation ? ` (${f.organisation})` : ''}`; export const partnerLeadTitle = (f) => - `Partner application — ${f.company || 'Unknown company'}`; + `Partner application: ${f.company || 'Unknown company'}`; export function PartnerLogoNetwork() { const partners = usePartners(); @@ -172,7 +172,7 @@ export const TIER_PILL_BASE = { />
- +
I need help @@ -234,9 +234,9 @@ export const TIER_PILL_BASE = { - + - +
I want to be a partner @@ -294,7 +294,7 @@ export const TIER_PILL_BASE = { - +
From abd3bb8f93cc1b4fdc6b74328b702d59ae6dde4e Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 19 Sep 2026 13:05:30 +0200 Subject: [PATCH 292/330] fix(site): the cookie panel flips, and two more pages get covered (#210) * fix(site): make dark mode readable on every page Dark mode reaches every visitor whose OS is set to dark, because the preset ships `respectPrefersColorScheme: true` and the served HTML really does call `matchMedia("(prefers-color-scheme: dark)")`. Nobody had looked at it. Across all 163 pages: 1,362 text elements below 1.5:1 on 121 of them, and the homepage h1 at exactly 1.00. The preset half is ConductionNL/design-system. This is the site's own share: 343 declarations that hardcoded a palette colour and so could not flip. Most of them are inline style objects in the MDX, which the stylesheet migration could never have reached, and which only became visible as a problem once the ink underneath them had been fixed. `.cn-clients-tinted` is worth calling out: it forced a light background with `!important`, so that section stayed pale while its text went light. e2e/dark-mode.spec.js gates it. Two properties, and the second matters as much as the first: dark mode has no invisible text, and light mode is not made worse by any of it. The tokens were chosen so their light values are identical to what they replaced, which turns "light is unchanged" into something checkable. The gate is 1.5:1, not the 4.5:1 of SC 1.4.3. Below 1.5 the text is effectively invisible and worth blocking a deploy over. Between 1.5 and 4.5 sits a long tail of brand colours that miss AA in both themes, including the Nextcloud blue at 4.17 on white. Gating at 4.5 would fail on pre-existing light-mode debt and teach everyone to ignore the suite. The suite measured wrong before it measured right: switching theme and reading in the same tick returned the pre-switch colours, which made /academy/ report 197 findings whose cards were already correct. It now waits two frames and settles before reading. Checked against the live site as a control, where it still fails on 6 pages, so the green here is a fixed site rather than a blind test. * build(deps): take preset 3.44.3, the release with the dark-mode tokens The site half of the dark-mode fix cannot work without the preset half: the semantic tokens the migrated declarations read from are defined in the preset's tokens.css, so on 3.44.2 they resolve to nothing. Bumping package.json alone would not do it. `deploy.yml` installs with `npm ci`, which takes exactly what the lockfile pins and ignores the caret range. Verified against the published tarball rather than a local overlay, because that is what CI and production actually install: 35 passed, 23 mobile and 12 dark. * test(dark): freeze transitions before reading a colour Cards animate `background` over 160ms, so a value read while the theme swap is still easing belongs to neither theme. One run reported white on rgb(231,233,236), a grey that appears nowhere in the palette because it was a half-finished transition, and it looked exactly like a real contrast failure. That is the third timing artefact this suite produced, after reading pre-recalc colours and measuring a lazily-injected stylesheet before it parsed. Each looked like a defect in the site. Fixing any of them in CSS would have been chasing the instrument. Transitions and animations are now disabled before the theme is set, so every value read is a settled one. Proven by repetition rather than a single green: three consecutive runs, and the live site still fails the same suite, so it can still tell the difference. * fix(site): the cookie panel flips, and two more pages get covered A full sweep of the deployed site found 12 invisible elements on two pages the contrast suite never sampled. One is here: the cookie panel sets `background: var(--c-cobalt-50)` inline in a .jsx, and the earlier inline-style migration only walked .mdx files. The other eleven are the platform diagram on /connext/, fixed in the preset. Both pages are now in the suite. The suite passed the whole time this was broken, because neither page was in its sample: a sample drawn from the layouts you already have in mind keeps agreeing with you, and the full sweep is what disagreed. --- e2e/dark-mode.spec.js | 10 ++++++++++ package-lock.json | 15 ++++----------- package.json | 2 +- src/components/CookieSettings.jsx | 8 ++++---- src/theme/BlogListPage/index.jsx | 2 +- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/e2e/dark-mode.spec.js b/e2e/dark-mode.spec.js index 2da206e2c..21c25f27a 100644 --- a/e2e/dark-mode.spec.js +++ b/e2e/dark-mode.spec.js @@ -37,6 +37,16 @@ const PAGES = [ ['quality', '/quality/'], ['terms (SLA tables)', '/terms/'], ['ai', '/ai/'], + /* Added after a full 163-page sweep of the deployed site found 12 + invisible elements on exactly these two, both outside the original + sample. /connext/ renders the platform diagram, a custom element + whose CSS lives in static/lib and was missed by a sweep scoped to + src/components and src/theme. /privacy/ renders the cookie panel, + whose colours are inline in a .jsx rather than an .mdx. A sample + that covers only the layouts you already thought about will keep + agreeing with you. */ + ['connext (platform diagram)', '/connext/'], + ['privacy (cookie panel)', '/privacy/'], ]; /** diff --git a/package-lock.json b/package-lock.json index 10b2008ac..2d1a7c768 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.45.1", + "@conduction/docusaurus-preset": "^3.45.2", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", @@ -2018,9 +2018,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.45.1.tgz", - "integrity": "sha512-cvgGwJJL8GdIT1XgO4z6fpeG9R13jRt6uClXUbQdE07uxgzCu6A8E2jQ52drPQQemBtQfrKrvF4Ge/4D18C4Nw==", + "version": "3.45.2", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.45.2.tgz", + "integrity": "sha512-L7yphmkWFarxHfeUT1gZp6gVyOP0nJsc+VCkSe0ENw2j6WuHQOKdOWrp0gC61xvyGnhvobIBIJD2gy/Ps+tAZw==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" @@ -16279,13 +16279,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/search-insights": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "license": "MIT", - "peer": true - }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", diff --git a/package.json b/package.json index 5e02efa22..9fbd53191 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.45.1", + "@conduction/docusaurus-preset": "^3.45.2", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", diff --git a/src/components/CookieSettings.jsx b/src/components/CookieSettings.jsx index a5ffec3ac..e2620ac08 100644 --- a/src/components/CookieSettings.jsx +++ b/src/components/CookieSettings.jsx @@ -76,7 +76,7 @@ export default function CookieSettings({ : []; return ( -
+
{heading}
@@ -87,9 +87,9 @@ export default function CookieSettings({ {rows.length > 0 && (
{rows.map(([name, verdict]) => ( -
-
{name}
-
{verdict}
+
+
{name}
+
{verdict}
))}
diff --git a/src/theme/BlogListPage/index.jsx b/src/theme/BlogListPage/index.jsx index 89a150231..132692dcd 100644 --- a/src/theme/BlogListPage/index.jsx +++ b/src/theme/BlogListPage/index.jsx @@ -504,7 +504,7 @@ function AcademyLandingInner({items}) {
Date: Sat, 19 Sep 2026 15:07:20 +0200 Subject: [PATCH 293/330] fix(site): the contact form flips, and /contact/ joins the suite (#211) * fix(site): the contact form flips, and /contact/ joins the suite A second full sweep of the deployed site found 7 invisible elements. Four are on /contact/, whose CRM form landed in #204 while this work was in flight and arrived with `background: 'white'` and palette ink hardcoded, so it was broken the day it shipped. The rest is a diagram caption, fixed in the preset. This is the second time a sweep found pages the suite does not sample, and the first time the cause was a feature merged in parallel rather than something the migration missed. Extending the sample by hand is losing to the rate at which new markup arrives. /contact/ is added anyway, but the durable fix is a lint rule that rejects raw palette tokens for text and surface roles. Flagged rather than added here, because it is a new gate on every contributor and that is not mine to impose. * build(deps): take preset 3.45.3, which flips the cn-platform stage tone --- .../index.mdx | 4 ++-- e2e/dark-mode.spec.js | 7 ++++++ package-lock.json | 8 +++---- package.json | 2 +- src/pages/contact.mdx | 24 +++++++++---------- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/academy/2026-06-01-build-an-app-tutorial-7-nc-vue/index.mdx b/academy/2026-06-01-build-an-app-tutorial-7-nc-vue/index.mdx index 0943a6af8..e1f049174 100644 --- a/academy/2026-06-01-build-an-app-tutorial-7-nc-vue/index.mdx +++ b/academy/2026-06-01-build-an-app-tutorial-7-nc-vue/index.mdx @@ -523,8 +523,8 @@ The rule for any custom component you write inside your app: ```css title="src/components/MyCustomCard.module.css" .card { /* good: picks up nldesign overrides automatically */ - background: var(--c-cobalt-50); - color: var(--c-cobalt-900); + background: var(--conduction-color-surface-subtle); + color: var(--conduction-color-text-strong); padding: var(--space-5); border-radius: var(--border-radius-large); } diff --git a/e2e/dark-mode.spec.js b/e2e/dark-mode.spec.js index 21c25f27a..58ca6f1ef 100644 --- a/e2e/dark-mode.spec.js +++ b/e2e/dark-mode.spec.js @@ -47,6 +47,13 @@ const PAGES = [ agreeing with you. */ ['connext (platform diagram)', '/connext/'], ['privacy (cookie panel)', '/privacy/'], + /* Added after a second full sweep. /contact/ grew a CRM form while + this work was in flight, and the form arrived with + `background: 'white'` hardcoded, so it was broken the day it + shipped. New pages and new features reintroduce the pattern faster + than a sample can be extended by hand: the durable fix is a lint + rule rejecting raw palette tokens for text and surface roles. */ + ['contact (CRM form)', '/contact/'], ]; /** diff --git a/package-lock.json b/package-lock.json index 2d1a7c768..00efbda67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.45.2", + "@conduction/docusaurus-preset": "^3.45.3", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", @@ -2018,9 +2018,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.45.2", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.45.2.tgz", - "integrity": "sha512-L7yphmkWFarxHfeUT1gZp6gVyOP0nJsc+VCkSe0ENw2j6WuHQOKdOWrp0gC61xvyGnhvobIBIJD2gy/Ps+tAZw==", + "version": "3.45.3", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.45.3.tgz", + "integrity": "sha512-QeiuD4FBBftG9P/4bV3ibtyNbBNc4i1kerpFE9r2m+iZTEnGUOPgWjjyxoDWAUbeDxHEGNpVDSrfuNE5Shw6Qg==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" diff --git a/package.json b/package.json index 9fbd53191..395f35700 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.45.2", + "@conduction/docusaurus-preset": "^3.45.3", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", diff --git a/src/pages/contact.mdx b/src/pages/contact.mdx index 6750dd322..aa5a27634 100644 --- a/src/pages/contact.mdx +++ b/src/pages/contact.mdx @@ -79,34 +79,34 @@ export const contactEnquiryTitle = (f) => lede="Send it here and it lands with the person who can answer it. We reply within two business days." />
- +
-

We use what you type here to answer you, and for nothing else. Privacy statement.

+

We use what you type here to answer you, and for nothing else. Privacy statement.

From 5643db440a724067a7d620bbffe7c7af960d1285 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 19 Sep 2026 15:40:21 +0200 Subject: [PATCH 294/330] fix: the Dutch locale gets the same dark mode and mobile fixes as English (#212) * fix(site): the contact form flips, and /contact/ joins the suite A second full sweep of the deployed site found 7 invisible elements. Four are on /contact/, whose CRM form landed in #204 while this work was in flight and arrived with `background: 'white'` and palette ink hardcoded, so it was broken the day it shipped. The rest is a diagram caption, fixed in the preset. This is the second time a sweep found pages the suite does not sample, and the first time the cause was a feature merged in parallel rather than something the migration missed. Extending the sample by hand is losing to the rate at which new markup arrives. /contact/ is added anyway, but the durable fix is a lint rule that rejects raw palette tokens for text and surface roles. Flagged rather than added here, because it is a new gate on every contributor and that is not mine to impose. * build(deps): take preset 3.45.3, which flips the cn-platform stage tone * fix(nl): give the Dutch locale the same dark mode and mobile fixes as English The sitemap does not list /nl/, so every sweep that treated the sitemap as "all pages" measured English only. The Dutch locale is a separate set of source files and it kept the raw palette colours and the bare 1fr grids the English pages had already been migrated off. /nl/terms/ carried 88 invisible elements and /nl/apps/ 22 unreachable controls while the English suite was green. What changed: * 265 declarations across 43 files in i18n/nl moved from raw palette tokens to the semantic ink and surface tokens, so they flip with the theme. * 23 grids converted to cn-split-main-aside or cn-filter-layout, and 9 given cn-stack-mobile, matching the English pages one for one. The SLA table on terms and the seven-day heatmap on decidiq stay wide in both locales. * Two academy posts had the stage colour quoted inside JSX (background: 'var(--c-cobalt-50)'), which the earlier migration pass did not match. Three invisible elements on the spec-driven-development post. * Both suites now include Dutch pages: dark mode covers 17 pages, mobile 19. Four Dutch pages is a sample, not coverage, but it fails if the locale is forgotten again. * The skip-link filter is locale aware, so "Ga naar hoofdinhoud" is skipped the same way the English one is. Verified: two-locale build green, 45 of 45 tests passing against it. --- .../2026-05-19-the-platform-moment/index.mdx | 12 ++-- .../index.mdx | 4 +- e2e/dark-mode.spec.js | 13 +++- e2e/mobile-layout.spec.js | 14 +++- .../index.mdx | 4 +- .../docusaurus-plugin-content-pages/about.mdx | 6 +- .../nl/docusaurus-plugin-content-pages/ai.mdx | 30 ++++---- .../docusaurus-plugin-content-pages/apps.mdx | 10 +-- .../apps/buildiq.mdx | 12 ++-- .../apps/decidiq.mdx | 4 +- .../apps/dossiq.mdx | 6 +- .../apps/filinq.mdx | 8 +-- .../apps/hermiq.mdx | 12 ++-- .../apps/humaniq.mdx | 18 ++--- .../apps/integriq.mdx | 10 +-- .../apps/keepiq.mdx | 16 ++--- .../apps/larpinq.mdx | 10 +-- .../apps/launchpad.mdx | 4 +- .../apps/learniq.mdx | 16 ++--- .../apps/opencatalogi.mdx | 12 ++-- .../apps/openregister.mdx | 10 +-- .../apps/pipelinq.mdx | 12 ++-- .../apps/planninq.mdx | 16 ++--- .../apps/portaliq.mdx | 16 ++--- .../apps/shillinq.mdx | 12 ++-- .../apps/stackiq.mdx | 10 +-- .../apps/thematiq.mdx | 28 ++++---- .../apps/versioniq.mdx | 2 +- .../apps/zaakafhandelapp.mdx | 2 +- .../arcade.mdx | 2 +- .../contact.mdx | 22 +++--- .../partners/acato.mdx | 4 +- .../partners/procolix.mdx | 2 +- .../partners/yard.mdx | 4 +- .../privacy.mdx | 54 +++++++------- .../quality.mdx | 70 +++++++++---------- .../sidecars.mdx | 10 +-- .../sidecars/keycloak.mdx | 2 +- .../sidecars/n8n.mdx | 2 +- .../sidecars/ollama.mdx | 2 +- .../sidecars/open-webui.mdx | 2 +- .../sidecars/openklant.mdx | 2 +- .../sidecars/opentalk.mdx | 2 +- .../sidecars/openzaak.mdx | 2 +- .../sidecars/valtimo.mdx | 2 +- .../solutions.mdx | 8 +-- .../solutions/archief.mdx | 2 +- .../solutions/zaakafhandeling.mdx | 2 +- .../support.mdx | 22 +++--- .../docusaurus-plugin-content-pages/terms.mdx | 64 ++++++++--------- 50 files changed, 316 insertions(+), 295 deletions(-) diff --git a/academy/2026-05-19-the-platform-moment/index.mdx b/academy/2026-05-19-the-platform-moment/index.mdx index 61532c49e..c45430af0 100644 --- a/academy/2026-05-19-the-platform-moment/index.mdx +++ b/academy/2026-05-19-the-platform-moment/index.mdx @@ -208,12 +208,12 @@ For anyone who wants to see it concretely, the stack looks like this. LayerWhat it does - Technical Core · NextcloudSubstrate with identity, storage, collaboration. Files, Mail, Talk, Calendar, Contacts. - Solutions · Learniq, OpenProject, XWiki, GitLabApps governments need out of the box, integrated as first-class citizens of the workspace. - Integrated apps · Matrix, Mattermost, n8n, WindmillBuilding blocks for communication and workflows. Loose puzzle pieces, single login. - AI substrate · Claude, Mistral, Ollama, PresidioInference, local or via gateway, with PII protection. The operator chooses the model. - Identity · KeycloakSSO, OIDC, SAML, social login. One permission model across every layer above. - App builder · BuildiqComposition layer for citizen-developers and organisations. Fork-free customisation. + Technical Core · NextcloudSubstrate with identity, storage, collaboration. Files, Mail, Talk, Calendar, Contacts. + Solutions · Learniq, OpenProject, XWiki, GitLabApps governments need out of the box, integrated as first-class citizens of the workspace. + Integrated apps · Matrix, Mattermost, n8n, WindmillBuilding blocks for communication and workflows. Loose puzzle pieces, single login. + AI substrate · Claude, Mistral, Ollama, PresidioInference, local or via gateway, with PII protection. The operator chooses the model. + Identity · KeycloakSSO, OIDC, SAML, social login. One permission model across every layer above. + App builder · BuildiqComposition layer for citizen-developers and organisations. Fork-free customisation. diff --git a/academy/2026-05-22-spec-driven-development/index.mdx b/academy/2026-05-22-spec-driven-development/index.mdx index 0f61d4119..9078261e3 100644 --- a/academy/2026-05-22-spec-driven-development/index.mdx +++ b/academy/2026-05-22-spec-driven-development/index.mdx @@ -135,7 +135,7 @@ The two ADR tiers feed *every* phase — explore reads them to challenge your id {() => { require('@conduction/docusaurus-preset/diagrams'); return ( -
+
Org + app ADRs Explore @@ -267,7 +267,7 @@ Pulled together, the whole journey is one pipeline. A feature request comes in; {() => { require('@conduction/docusaurus-preset/diagrams'); return ( -
+
Feature request Explore → spec diff --git a/e2e/dark-mode.spec.js b/e2e/dark-mode.spec.js index 58ca6f1ef..a824a904a 100644 --- a/e2e/dark-mode.spec.js +++ b/e2e/dark-mode.spec.js @@ -54,6 +54,17 @@ const PAGES = [ than a sample can be extended by hand: the durable fix is a lint rule rejecting raw palette tokens for text and surface roles. */ ['contact (CRM form)', '/contact/'], + + /* The Dutch locale. It is a separate set of source files, it is live, + and it is NOT in sitemap.xml, so every sweep that used the sitemap as + "all pages" measured only English. /nl/apps/ was carrying 22 + unreachable controls and /nl/terms/ 88 invisible elements the whole + time the English suite was green. Four pages is a sample, not + coverage, but it is enough to fail if the locale is forgotten again. */ + ['nl home', '/nl/'], + ['nl terms (worst offender)', '/nl/terms/'], + ['nl quality', '/nl/quality/'], + ['nl support', '/nl/support/'], ]; /** @@ -144,7 +155,7 @@ async function lowContrastText(page, theme, threshold) { /* Docusaurus's own skip link is parked off-screen until focused, and its colours come from Infima rather than from us. It is not something a visitor ever sees at this contrast. */ - if (text === 'Skip to main content') continue; + if (text === 'Skip to main content' || text === 'Ga naar hoofdinhoud') continue; /* Leaves only, so a wrapper is not blamed for its children. */ if (el.querySelector('p,h1,h2,h3,h4,li,div')) continue; const style = getComputedStyle(el); diff --git a/e2e/mobile-layout.spec.js b/e2e/mobile-layout.spec.js index d96d3115b..03426e1fe 100644 --- a/e2e/mobile-layout.spec.js +++ b/e2e/mobile-layout.spec.js @@ -83,6 +83,16 @@ const PAGES = [ ['contact', '/contact/'], ['install', '/install/'], ['about', '/about/'], + + /* The Dutch locale. It is a separate set of source files, it is live, + and it is NOT in sitemap.xml, so every sweep that used the sitemap as + "all pages" measured only English. /nl/apps/ was carrying 22 + unreachable controls and /nl/terms/ 88 invisible elements the whole + time the English suite was green. Four pages is a sample, not + coverage, but it is enough to fail if the locale is forgotten again. */ + ['nl home', '/nl/'], + ['nl apps listing', '/nl/apps/'], + ['nl support', '/nl/support/'], ]; /** @@ -112,7 +122,7 @@ async function offscreenControls(page) { const selector = 'a[href], button, input, select, textarea, [role="button"]'; for (const el of document.querySelectorAll(selector)) { if (el.closest('#navbar-drawer[hidden]')) continue; - if ((el.textContent || '').trim() === 'Skip to main content') continue; + if (['Skip to main content', 'Ga naar hoofdinhoud'].includes((el.textContent || '').trim())) continue; const style = getComputedStyle(el); if (style.visibility === 'hidden' || style.display === 'none') continue; const r = el.getBoundingClientRect(); @@ -280,7 +290,7 @@ for (const path of ['/apps/', '/support/', '/']) { const out = []; for (const el of document.querySelectorAll('a[href], button, input, select, [role="button"]')) { if (el.closest('#navbar-drawer[hidden]')) continue; - if ((el.textContent || '').trim() === 'Skip to main content') continue; + if (['Skip to main content', 'Ga naar hoofdinhoud'].includes((el.textContent || '').trim())) continue; const style = getComputedStyle(el); if (style.visibility === 'hidden' || style.display === 'none') continue; if (style.display === 'inline' && el.closest('p, li')) continue; diff --git a/i18n/nl/docusaurus-plugin-content-blog/2026-05-22-spec-driven-development/index.mdx b/i18n/nl/docusaurus-plugin-content-blog/2026-05-22-spec-driven-development/index.mdx index d8d5940df..2a179fe9f 100644 --- a/i18n/nl/docusaurus-plugin-content-blog/2026-05-22-spec-driven-development/index.mdx +++ b/i18n/nl/docusaurus-plugin-content-blog/2026-05-22-spec-driven-development/index.mdx @@ -134,7 +134,7 @@ De twee ADR-lagen voeden *elke* fase — explore leest ze om je idee uit te dage {() => { require('@conduction/docusaurus-preset/diagrams'); return ( -
+
Org + app ADR's Explore @@ -266,7 +266,7 @@ Samengevoegd is de hele reis één pipeline. Een feature-aanvraag komt binnen; e {() => { require('@conduction/docusaurus-preset/diagrams'); return ( -
+
Feature-aanvraag Explore → spec diff --git a/i18n/nl/docusaurus-plugin-content-pages/about.mdx b/i18n/nl/docusaurus-plugin-content-pages/about.mdx index 2664f7617..481cd46d3 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/about.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/about.mdx @@ -192,7 +192,7 @@ import PageHeading from '@site/src/components/PageHeading'; /> @@ -236,7 +236,7 @@ import PageHeading from '@site/src/components/PageHeading'; browser haalt die nesting uit elkaar en de pagina mislukt in hydration. */} {'Mail info@conduction.nl'} - Bel +31 (0)85 303 6840 - Open in Google Maps + Bel +31 (0)85 303 6840 + Open in Google Maps
diff --git a/i18n/nl/docusaurus-plugin-content-pages/ai.mdx b/i18n/nl/docusaurus-plugin-content-pages/ai.mdx index 6d7f8db58..349a7e845 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/ai.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/ai.mdx @@ -22,8 +22,8 @@ export const AiIcon = ( een

erin zet een alinea in een alinea; de HTML-parser splitst dat en dan mislukt de hydration. Binnen een export is JSX gewoon JSX. */} export const IbmQuote = () => ( -

-

+

+

“A computer can never be held accountable, therefore a computer must never make a management decision.”

@@ -33,7 +33,7 @@ export const IbmQuote = () => ( ); export const PracticeNote = ({children}) => ( -
+
{children}
); @@ -112,7 +112,7 @@ export const USES = [ ## De regel -
+
Human in the loop
@@ -133,7 +133,7 @@ Verantwoordelijkheid is de hele reden. Een model kan zich niet verantwoorden voo ].map((row) => (
{row.k}
-
{row.v}
+
{row.v}
))}
@@ -155,12 +155,12 @@ Verantwoordelijkheid is de hele reden. Een model kan zich niet verantwoorden voo
{USES.map((use) => ( -
+
{use.eyebrow}
-

{use.title}

-

{use.body}

+

{use.title}

+

{use.body}

{use.link && ( - + {use.link.label} → )} @@ -174,7 +174,7 @@ Verantwoordelijkheid is de hele reden. Een model kan zich niet verantwoorden voo ## Blogs en artikelen -
+
Wij zijn ontwikkelaars. Geeks en nerds, in de kern. We schrijven code, geen blogs. @@ -199,10 +199,10 @@ De AI doet drie dingen, en dat zijn alle drie ondersteunende taken. {n: '3', t: 'Het helpt ons beter schrijven.', d: 'Het stelt een strakkere zin voor of een duidelijker volgorde. Wij nemen die suggestie over, of we negeren hem.'}, ].map((item) => (
- {item.n} + {item.n}
-
{item.t}
-
{item.d}
+
{item.t}
+
{item.d}
))} @@ -256,10 +256,10 @@ Wij zien dat als een kwestie van aannemen en opleiden, niet van papierwerk. Iede Hoe Hydra onze code schrijft → - + Kwaliteit, certificeringen & ISMS → - + Privacybeleid →
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps.mdx index b2ae33646..d9ec5bec9 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps.mdx @@ -52,20 +52,20 @@ export function FilterableApps() { ); return ( -
+
-
+
- {filtered.length} van {apps.length} apps - {totalSelected > 0 && · {apps.length - filtered.length} verborgen door filters} + {filtered.length} van {apps.length} apps + {totalSelected > 0 && · {apps.length - filtered.length} verborgen door filters}
{filtered.map((app, i) => )}
{filtered.length === 0 && ( -

Geen apps matchen dit filter.

+

Geen apps matchen dit filter.

)}
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/buildiq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/buildiq.mdx index 2325b7090..5fe90441d 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/buildiq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/buildiq.mdx @@ -34,7 +34,7 @@ export const app = {...getApp('openbuild'), name: 'Buildiq'}; />
-
+
-
{row.state}
+
{row.state}
))}
@@ -155,8 +155,8 @@ export const app = {...getApp('openbuild'), name: 'Buildiq'}; panel: (
-
3
-
aanpassingen op test
+
3
+
aanpassingen op test
{[{w: '80%'}, {w: '55%'}, {w: '65%'}].map((row, i) => (
@@ -179,7 +179,7 @@ export const app = {...getApp('openbuild'), name: 'Buildiq'};
-
+
))}
@@ -225,7 +225,7 @@ export const app = {...getApp('openbuild'), name: 'Buildiq'};
Publiceren naar GitHub
-
wagenparklijst v1.2 gepubliceerd
+
wagenparklijst v1.2 gepubliceerd
), diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/decidiq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/decidiq.mdx index 019d8a568..8947c2b23 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/decidiq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/decidiq.mdx @@ -29,7 +29,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
}
-
+
))}
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/dossiq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/dossiq.mdx index 23bb42e1c..28a8575c4 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/dossiq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/dossiq.mdx @@ -29,7 +29,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
3
-
deze week
+
deze week
{[{w: '70%', accent: true}, {w: '60%'}, {w: '55%'}].map((row, i) => (
-
+
))}
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/filinq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/filinq.mdx index 3b6c759b2..60427b5ad 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/filinq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/filinq.mdx @@ -34,7 +34,7 @@ export const app = {...getApp('docudesk'), name: 'Filinq'}; />
-
+
-
+
-
+
), @@ -189,7 +189,7 @@ export const app = {...getApp('docudesk'), name: 'Filinq'};
-
{row.n}
+
{row.n}
))}
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/hermiq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/hermiq.mdx index 1b2141c53..b4622615c 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/hermiq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/hermiq.mdx @@ -26,7 +26,7 @@ export const app = getApp('hermiq'); />
-
+
-
38
+
38
97% ok
{[ @@ -140,8 +140,8 @@ export const app = getApp('hermiq'); {label: 'wagenpark', val: '25%', tone: 'var(--c-mint-500)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
@@ -165,7 +165,7 @@ export const app = getApp('hermiq');
-
{row.what}
+
{row.what}
))}
@@ -184,7 +184,7 @@ export const app = getApp('hermiq'); ].map((row, i) => (
-
{row.label}
+
{row.label}
{row.on ? 'toegestaan' : 'uit'}
))} diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/humaniq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/humaniq.mdx index e3612ff78..00fa7b907 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/humaniq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/humaniq.mdx @@ -24,7 +24,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
7
-
goed te keuren
+
goed te keuren
{[ {label: 'uren', val: '60%', tone: 'var(--c-cobalt-300)'}, @@ -144,8 +144,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'verlof', val: '20%', tone: 'var(--c-mint-500)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
@@ -170,7 +170,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
-
{row.stage}
+
{row.stage}
))}
@@ -182,8 +182,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; panel: (
-
4
-
open dossiers
+
4
+
open dossiers
{[ {label: 'week 42', val: '25%', tone: 'var(--c-orange-knvb)'}, @@ -191,8 +191,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'goedgekeurd', val: '70%', tone: 'var(--c-mint-500)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/integriq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/integriq.mdx index 487218aba..16d13f216 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/integriq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/integriq.mdx @@ -34,7 +34,7 @@ export const app = {...getApp('openconnector'), name: 'Integriq'}; />
-
+
-
214
+
214
213 ok
@@ -165,7 +165,7 @@ export const app = {...getApp('openconnector'), name: 'Integriq'};
-
{`${[12, 8, 24, 14, 38, 6][i]}s`}
+
{`${[12, 8, 24, 14, 38, 6][i]}s`}
))}
@@ -178,7 +178,7 @@ export const app = {...getApp('openconnector'), name: 'Integriq'};
1
-
wachtend
+
wachtend
{[0].map((i) => (
@@ -187,7 +187,7 @@ export const app = {...getApp('openconnector'), name: 'Integriq'};
))} -
order 4471, verbinding geweigerd, 03:12
+
order 4471, verbinding geweigerd, 03:12
), }, diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/keepiq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/keepiq.mdx index a448c4c28..b01ac41e2 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/keepiq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/keepiq.mdx @@ -29,7 +29,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
3
-
wissel nodig
+
wissel nodig
{[ {label: 'geheimen', val: '80%', tone: 'var(--c-cobalt-300)'}, @@ -134,8 +134,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'mappen', val: '30%', tone: 'var(--c-mint-500)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
@@ -160,7 +160,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
-
{row.stage}
+
{row.stage}
))}
@@ -177,8 +177,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'systemen', val: '25%', tone: 'var(--c-cobalt-300)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
@@ -222,7 +222,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
Kopieer wachtwoord
-
leveranciersportaal, map Inkoop
+
leveranciersportaal, map Inkoop
), diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/larpinq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/larpinq.mdx index 848e6c679..b0a9e8fd1 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/larpinq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/larpinq.mdx @@ -29,7 +29,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
-
{row.stage}
+
{row.stage}
))}
@@ -170,7 +170,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; ].map((row, i) => (
-
{row.date}
+
{row.date}
@@ -193,8 +193,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'kennis', val: '10%', tone: 'var(--c-orange-knvb)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/launchpad.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/launchpad.mdx index 07a64a731..189a1213d 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/launchpad.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/launchpad.mdx @@ -24,7 +24,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
(
-
{row.date}
+
{row.date}
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/learniq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/learniq.mdx index 5410eabd8..a4b26cf53 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/learniq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/learniq.mdx @@ -24,7 +24,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
-
+
-
{row.due}
+
{row.due}
))}
@@ -164,7 +164,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
-
{row.stage}
+
{row.stage}
))}
@@ -177,7 +177,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
4
-
open meldingen
+
open meldingen
{[ {label: 'inschrijvingen', val: '80%', tone: 'var(--c-cobalt-300)'}, @@ -185,8 +185,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'groepen', val: '35%', tone: 'var(--c-mint-500)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
@@ -262,7 +262,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
Deelnemen aan gesprek
-
klas 3B, 24 leden, volgt inschrijvingen
+
klas 3B, 24 leden, volgt inschrijvingen
), diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/opencatalogi.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/opencatalogi.mdx index 89b47d070..0e0c9b0f1 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/opencatalogi.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/opencatalogi.mdx @@ -26,7 +26,7 @@ export const app = getApp('opencatalogi'); />
-
+
+
{[ {n: '3.4k', l: 'gepubliceerd', tone: 'var(--c-cobalt-900)'}, {n: '12', l: 'catalogi', tone: 'var(--c-mint-500)'}, {n: '847', l: 'wachtend', tone: 'var(--c-orange-knvb)'}, ].map((kpi, i) => ( -
+
{kpi.n}
-
{kpi.l}
+
{kpi.l}
))}
@@ -169,8 +169,8 @@ export const app = getApp('opencatalogi'); ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
))}
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/openregister.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/openregister.mdx index d937b1204..c7cdddc3b 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/openregister.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/openregister.mdx @@ -26,7 +26,7 @@ export const app = getApp('openregister'); />
-
+
-
+
))}
@@ -142,7 +142,7 @@ export const app = getApp('openregister'); panel: (
-
847
+
847
+12%
@@ -160,10 +160,10 @@ export const app = getApp('openregister');
{[{w: '90%', n: '142'}, {w: '70%', n: '88'}, {w: '60%', n: '54'}, {w: '50%', n: '37'}, {w: '40%', n: '21'}].map((row, i) => (
-
+
-
{row.n}
+
{row.n}
))}
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/pipelinq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/pipelinq.mdx index 687f0718d..ac3f2f641 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/pipelinq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/pipelinq.mdx @@ -24,7 +24,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
-
€ 487k
+
€ 487k
+12%
{[ @@ -141,8 +141,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'gewonnen', val: '50%', tone: 'var(--c-forest-300)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
@@ -168,7 +168,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
-
{row.stage}
+
{row.stage}
))}
@@ -191,7 +191,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
-
{row.val}
+
{row.val}
))}
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/planninq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/planninq.mdx index 246c2b830..5cc97a016 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/planninq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/planninq.mdx @@ -24,7 +24,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
2
-
over tijd
+
over tijd
{[ {label: 'open', val: '70%', tone: 'var(--c-cobalt-300)'}, @@ -138,8 +138,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'vandaag klaar', val: '25%', tone: 'var(--c-mint-500)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
@@ -164,7 +164,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
-
{row.stage}
+
{row.stage}
))}
@@ -182,8 +182,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'support', val: '60%', tone: 'var(--c-forest-300)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
@@ -215,7 +215,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
Over 2 dagen af
-
Nieuwe kassa live zetten, project Webshop
+
Nieuwe kassa live zetten, project Webshop
), diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/portaliq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/portaliq.mdx index 67bde34e3..4e5210769 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/portaliq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/portaliq.mdx @@ -24,7 +24,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
-
312
-
accounts
+
312
+
accounts
{[ {label: 'klanten', val: '75%', tone: 'var(--c-cobalt-300)'}, @@ -130,8 +130,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'nieuw', val: '15%', tone: 'var(--c-mint-500)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
@@ -156,7 +156,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
-
{row.stage}
+
{row.stage}
))}
@@ -173,8 +173,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'berichten', val: '60%', tone: 'var(--c-mint-500)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/shillinq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/shillinq.mdx index d411c65fc..db65780e7 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/shillinq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/shillinq.mdx @@ -29,7 +29,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
-
€ 218k
+
€ 218k
+8%
{[ @@ -139,8 +139,8 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {label: 'k4', val: '45%', tone: 'var(--c-forest-300)'}, ].map((row, i) => (
-
{row.label}
-
+
{row.label}
+
@@ -165,7 +165,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
-
{row.stage}
+
{row.stage}
))}
@@ -188,7 +188,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
-
{row.val}
+
{row.val}
))}
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/stackiq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/stackiq.mdx index a25056ab7..2330a5bd5 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/stackiq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/stackiq.mdx @@ -24,7 +24,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
(
-
{row.label}
-
+
{row.label}
+
@@ -155,7 +155,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
-
{row.stage}
+
{row.stage}
))}
@@ -178,7 +178,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
-
{row.val}
+
{row.val}
))}
diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/thematiq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/thematiq.mdx index 104b1b7df..fdcc5c5be 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/thematiq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/thematiq.mdx @@ -29,7 +29,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
-
Aa
-
kop
+
Aa
+
kop
-
Aa
-
subkop
+
Aa
+
subkop
-
Aa
-
tekst
+
Aa
+
tekst
-
Aa
-
bijschrift
+
Aa
+
bijschrift
), @@ -159,7 +159,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; ].map((row, i) => (
-
{row.name}
+
{row.name}
))}
@@ -172,17 +172,17 @@ import {PartnersForApp} from '@site/src/components/PartnersFor';
Primair
-
Secundair
+
Secundair
-
label
-
+
label
+
-
focusrand
+
focusrand
), diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/versioniq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/versioniq.mdx index 592988d52..62ef87eed 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/versioniq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/versioniq.mdx @@ -10,7 +10,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, CtaBanner} f background="cobalt" appId="app-versions" crumb={[{label: 'Apps', href: '/apps'}, 'Versioniq']} - status={{label: 'In ontwikkeling', color: 'var(--c-cobalt-400)'}} + status={{label: 'In ontwikkeling', color: 'var(--conduction-color-text-muted)'}} version="v0.x" locales="NL · EN" title="Versioniq" diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/zaakafhandelapp.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/zaakafhandelapp.mdx index 0badaf293..c6f8114d5 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/zaakafhandelapp.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/zaakafhandelapp.mdx @@ -24,7 +24,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; />
-
+
diff --git a/i18n/nl/docusaurus-plugin-content-pages/contact.mdx b/i18n/nl/docusaurus-plugin-content-pages/contact.mdx index baddf867b..04469fa6b 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/contact.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/contact.mdx @@ -80,30 +80,30 @@ export const contactEnquiryTitle = (f) => />
-
+

We gebruiken wat je hier typt om je te antwoorden, en voor niets anders. Privacyverklaring.

@@ -129,13 +129,13 @@ export const contactEnquiryTitle = (f) => de browser splitst dat en de pagina mislukt in hydration. Kale tekst als info@conduction.nl wordt bovendien automatisch gelinkt, wat een in deze zette. */} -

Conduction B.V.

-

Lauriergracht 14h
1016 RR Amsterdam
Nederland

-

+31 85 303 6840
{'info@conduction.nl'}

+

Conduction B.V.

+

Lauriergracht 14h
1016 RR Amsterdam
Nederland

+

+31 85 303 6840
{'info@conduction.nl'}

-

Bedrijfsgegevens

-

KvK: 76741850
BTW: NL860784241B01
IBAN: NL51 ABNA 0868951550

+

Bedrijfsgegevens

+

KvK: 76741850
BTW: NL860784241B01
IBAN: NL51 ABNA 0868951550

diff --git a/i18n/nl/docusaurus-plugin-content-pages/partners/acato.mdx b/i18n/nl/docusaurus-plugin-content-pages/partners/acato.mdx index 08da392a4..b3d459bc5 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/partners/acato.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/partners/acato.mdx @@ -8,7 +8,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, ReferenceGri -
+
-
+
-
+
+
Doelbinding
{[ @@ -24,18 +24,18 @@ export const PROMISE_PANELS = { {k: 'Gebruikt voor', v: 'Datzelfde doel'}, {k: 'Hergebruikt voor', v: 'Niets anders'}, ].map((row, i) => ( -
- {row.k} +
+ {row.k} {row.v}
))}
-
AVG art. 5(1)(b), "doelbinding".
+
AVG art. 5(1)(b), "doelbinding".
), sharing: ( -
-
Wat we met jouw gegevens doen
+
+
Wat we met jouw gegevens doen
    {[ {ok: false, l: 'Verkocht aan databrokers'}, @@ -44,7 +44,7 @@ export const PROMISE_PANELS = { {ok: true, l: 'Alleen gedeeld als nodig voor het opslagdoel'}, {ok: true, l: 'Verwerkersovereenkomst op verzoek'}, ].map((row, i) => ( -
  • +
  • {row.ok ? '✓' : '✕'} {row.l}
  • @@ -53,12 +53,12 @@ export const PROMISE_PANELS = {
), retention: ( -
+
Bewaarbeginsel
-
+
Gegevens worden niet langer bewaard dan het opslagdoel vereist. Eindigt het doel, dan eindigen de gegevens.
-
Aanleidingen voor verwijdering
+
Aanleidingen voor verwijdering
    {['Doel is vervuld', 'Wettelijke bewaartermijn verstreken', 'Recht op vergetelheid uitgeoefend', 'Contract beëindigd'].map((l, i) => (
  • @@ -70,14 +70,14 @@ export const PROMISE_PANELS = {
), security: ( -
+
ISO 27001:2022-certificaat -
ISO/IEC 27001:2022 + ISMS
+
ISO/IEC 27001:2022 + ISMS
), consent: ( -
-
Wat we wanneer vertellen
+
+
Wat we wanneer vertellen
{[ {step: 'Voor opslag', text: 'We vragen om toestemming.'}, @@ -85,7 +85,7 @@ export const PROMISE_PANELS = { {step: 'Altijd', text: 'We informeren over je rechten.'}, {step: 'Bij wijziging', text: 'We melden ongebruikelijke verwerkingen vooraf.'}, ].map((row, i) => ( -
+
{row.step} {row.text}
@@ -94,7 +94,7 @@ export const PROMISE_PANELS = {
), rights: ( -
+
{[ {n: 'Inzage', l: 'Wat hebben wij van jou?'}, {n: 'Rectificatie', l: 'Onjuistheden herstellen'}, @@ -103,9 +103,9 @@ export const PROMISE_PANELS = { {n: 'Overdraagbaarheid', l: 'Meenemen van data'}, {n: 'Bezwaar', l: 'Verwerking stoppen'}, ].map((b, i) => ( -
-
{b.n}
-
{b.l}
+
+
{b.n}
+
{b.l}
))}
@@ -181,7 +181,7 @@ export const PROMISE_PANELS = { ## Privacybeleid -
+
Beleidsverklaring
@@ -189,19 +189,19 @@ export const PROMISE_PANELS = {
Standaard
-
AVG · EU 2016/679
+
AVG · EU 2016/679
Gekoppeld aan
-
ISO/IEC 27001:2022 ISMS
+
ISO/IEC 27001:2022 ISMS
Kantoor
-
Lauriergracht 14h, Amsterdam
+
Lauriergracht 14h, Amsterdam
Contact
-
info@conduction.nl
+
info@conduction.nl
@@ -249,7 +249,7 @@ Je keuze wordt opgeslagen in je browser, niet op onze servers, en geldt alleen v Vragen over dit beleid of over je persoonsgegevens kun je sturen naar [info@conduction.nl](mailto:info@conduction.nl). Kantoor: Lauriergracht 14h, 1016 RR Amsterdam. -
+
Vastgesteld door de directie van Conduction B.V., Amsterdam, Nederland. Deze Nederlandse tekst is de juridisch bindende versie van het privacybeleid.
@@ -263,10 +263,10 @@ Vragen over dit beleid of over je persoonsgegevens kun je sturen naar [info@cond AVG-recht uitoefenen → - + Quality, certificeringen & ISMS → - + Service Level Agreement →
diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index ff5288dfe..faf18fb4e 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -9,14 +9,14 @@ import {DetailHero, Section, SectionHead, Showcase, WidgetShelf, FAQ, FAQItem} f export const WORKFLOW_FILE = 'https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml'; export function QualityPipelineFlow() { - const card = {background: 'white', border: '1px solid var(--c-cobalt-100)', borderRadius: 8, padding: '14px 14px 16px', boxShadow: '0 1px 3px rgba(33, 70, 139, 0.04)'}; - const cardTitle = {fontSize: 12, fontWeight: 700, color: 'var(--c-cobalt-900)', marginBottom: 4, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 6}; + const card = {background: 'var(--conduction-color-surface-card)', border: '1px solid var(--c-cobalt-100)', borderRadius: 8, padding: '14px 14px 16px', boxShadow: '0 1px 3px rgba(33, 70, 139, 0.04)'}; + const cardTitle = {fontSize: 12, fontWeight: 700, color: 'var(--conduction-color-text-strong)', marginBottom: 4, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 6}; const cardSubtitle = {fontSize: 10, fontFamily: 'var(--conduction-typography-font-family-code)', color: 'var(--c-cobalt-500, #6f7da3)', marginBottom: 10, letterSpacing: '0.04em'}; - const stageWrap = {background: 'var(--c-cobalt-50)', border: '1px solid var(--c-cobalt-100)', borderRadius: 12, padding: '18px 18px 22px'}; + const stageWrap = {background: 'var(--conduction-color-surface-subtle)', border: '1px solid var(--c-cobalt-100)', borderRadius: 12, padding: '18px 18px 22px'}; const stageHead = {display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 14, gap: 12, flexWrap: 'wrap'}; const stageLabel = {fontSize: 10, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--c-orange-knvb)', fontFamily: 'var(--conduction-typography-font-family-code)'}; - const stageTitle = {fontSize: 16, fontWeight: 700, color: 'var(--c-cobalt-900)'}; - const stageNote = {fontSize: 12, color: 'var(--c-cobalt-700)'}; + const stageTitle = {fontSize: 16, fontWeight: 700, color: 'var(--conduction-color-text-strong)'}; + const stageNote = {fontSize: 12, color: 'var(--conduction-color-text-body)'}; const arrowRow = {height: 24, display: 'flex', justifyContent: 'center', alignItems: 'center', color: 'var(--c-cobalt-300)', fontSize: 20, fontWeight: 700}; const hex = (color) => ({width: 10, height: 12, clipPath: 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)', background: color, flexShrink: 0}); const list = (items, color) => ( @@ -26,7 +26,7 @@ export function QualityPipelineFlow() { return (
  • - {label} + {label}
  • ); })} @@ -188,7 +188,7 @@ export function HandbookPanel({iconPath, title, lines, accent}) {
    {title}
      {lines.map((l, i) => ( -
    • {l}
    • +
    • {l}
    • ))}
    @@ -228,28 +228,28 @@ export const QualityHeroIllustration = ( Certificaten · handboek · scans · workflow ● live
    -
    -
    +
    +
    ISO 9001:2015 certificaat -
    ISO 9001:2015
    +
    ISO 9001:2015
    -
    +
    ISO 27001:2022 certificaat -
    ISO 27001:2022
    +
    ISO 27001:2022
    docs.conduction.nl · operationeel handboek - LIVE + LIVE
    pentest-tools.com · dagelijkse scan - SCHOON + SCHOON
    GitHub PR #482 · code- en security-review - GEMERGED + GEMERGED
    @@ -282,7 +282,7 @@ export const QualityHeroIllustration = ( summary: "De internationale norm voor kwaliteitsmanagementsystemen. Bevestigt dat Conduction een gedocumenteerd KMS hanteert, interne audits uitvoert en directiebeoordelingen houdt. Scope: softwareontwikkeling, hosting en advieswerk voor publieke en MKB-klanten in Nederland. Jaarlijkse tussentijdse audit, hercertificering elke drie jaar. Volledige beleidsverklaring en scope hieronder.", cta: {label: 'Lees het kwaliteitsbeleid', href: '#iso-certifications'}, panel: ( -
    +
    ISO 9001:2015 certificaat, Conduction B.V. +
    ISO 27001:2022 certificaat, Conduction B.V. -
    docs.conduction.nl — handboek-onderdelen
    -
    +
    +
    docs.conduction.nl — handboek-onderdelen
    +
    {[ {hex: 'var(--c-blue-cobalt)', label: 'Identity'}, {hex: 'var(--c-orange-knvb)', label: 'Way of Work'}, @@ -323,7 +323,7 @@ export const QualityHeroIllustration = ( {hex: 'var(--c-cobalt-700)', label: 'ISO-compliance'}, {hex: 'var(--c-forest-300)', label: 'Roadmap'}, ].map((s, i) => ( -
    +
    {s.label}
    @@ -338,8 +338,8 @@ export const QualityHeroIllustration = ( summary: "Conduction draait dagelijks penetratietests tegen elke applicatie via het commerciële SaaS-scanplatform pentest-tools.com. De dekking beslaat de OWASP Top 10, bekende CVE's, TLS-hygiëne, netwerk-exposure en subdomain enumeration. Komt er een bevinding boven, dan triëren we hem en starten de opvolging binnen 8 werkuren.", cta: {label: 'Bekijk hoe we scannen', href: '#pentest-tools'}, panel: ( -
    -
    pentest-tools.com — laatste scan
    +
    +
    pentest-tools.com — laatste scan
    {[ {label: 'Website scanner (OWASP Top 10)', state: 'schoon'}, {label: 'TLS-configuratie audit', state: 'schoon'}, @@ -349,7 +349,7 @@ export const QualityHeroIllustration = ( ].map((row, i) => (
    0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> {row.label} - {row.state} + {row.state}
    ))}
    @@ -361,8 +361,8 @@ export const QualityHeroIllustration = ( summary: "Elke Conduction-app staat publiek op GitHub onder de ConductionNL-organisatie. Drie organisatie-brede branch-protection rulesets dwingen elke wijziging door een pull request met verplichte review. Code-review en security-review worden gestart via labels op de PR, geauditeerd door Hydra-automatisering en gekoppeld aan de canonieke spec. Het spoor van feature request tot gemergde code is end-to-end te volgen, in het openbaar.", cta: {label: 'Bekijk de workflow', href: '#github-workflow'}, panel: ( -
    -
    Pull request #482 — feat: openregister/audit-log
    +
    +
    Pull request #482 — feat: openregister/audit-log
    {[ {step: 'Branch protection', tag: 'afgedwongen'}, {step: 'Geautomatiseerde CI-gate (PHPCS, Psalm, PHPStan)', tag: 'geslaagd'}, @@ -373,7 +373,7 @@ export const QualityHeroIllustration = ( ].map((row, i) => (
    0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> {row.step} - {row.tag} + {row.tag}
    ))}
    @@ -410,7 +410,7 @@ export const QualityHeroIllustration = (
    -
    +
    Beleidsverklaring
    @@ -418,19 +418,19 @@ export const QualityHeroIllustration = (
    Norm
    -
    ISO 9001:2015 · ISO/IEC 27001:2022
    +
    ISO 9001:2015 · ISO/IEC 27001:2022
    Vastgesteld
    -
    januari 2026
    +
    januari 2026
    Certificaten
    -
    24 jul 2025 – 21 jul 2028
    +
    24 jul 2025 – 21 jul 2028
    Volgende review
    -
    januari 2027
    +
    januari 2027
    @@ -455,7 +455,7 @@ Kwaliteit, informatiebeveiliging, continue verbetering en klanttevredenheid staa Voor meer informatie over ons kwaliteits- en informatieveiligheidssysteem kunt u contact met ons opnemen via [het contactformulier op onze website](/contact). -
    +
    Vastgesteld door de directie van Conduction B.V. — Amsterdam. Tekst overgenomen uit de ondertekende beleidsverklaring van januari 2026.
    @@ -509,7 +509,7 @@ Voor meer informatie over ons kwaliteits- en informatieveiligheidssysteem kunt u Open het handboek → - + Start bij Way of Work →
    @@ -540,7 +540,7 @@ Elke Conduction-app staat publiek op GitHub onder de [ConductionNL](https://gith Bekijk quality.yml op GitHub → - + Bekijk een live run →
    diff --git a/i18n/nl/docusaurus-plugin-content-pages/sidecars.mdx b/i18n/nl/docusaurus-plugin-content-pages/sidecars.mdx index 7f8323e85..9c53583ed 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/sidecars.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/sidecars.mdx @@ -49,20 +49,20 @@ export function FilterableSidecars() { ); return ( -
    +
    -
    +
    - {filtered.length} van {sidecars.length} sidecars - {totalSelected > 0 && · {sidecars.length - filtered.length} verborgen door filters} + {filtered.length} van {sidecars.length} sidecars + {totalSelected > 0 && · {sidecars.length - filtered.length} verborgen door filters}
    {filtered.map((app, i) => )}
    {filtered.length === 0 && ( -

    Geen sidecars passen bij dit filter.

    +

    Geen sidecars passen bij dit filter.

    )}
    diff --git a/i18n/nl/docusaurus-plugin-content-pages/sidecars/keycloak.mdx b/i18n/nl/docusaurus-plugin-content-pages/sidecars/keycloak.mdx index 1bdb2ea4e..11499e0d2 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/sidecars/keycloak.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/sidecars/keycloak.mdx @@ -9,7 +9,7 @@ import {DetailHero, Section, SectionHead, CtaBanner} from '@conduction/docusauru +
    -
    +
    - {filteredFinal.length} van {SOLUTIONS.length} solutions - {totalSelected > 0 && · {SOLUTIONS.length - filteredFinal.length} verborgen door filters} + {filteredFinal.length} van {SOLUTIONS.length} solutions + {totalSelected > 0 && · {SOLUTIONS.length - filteredFinal.length} verborgen door filters}
    diff --git a/i18n/nl/docusaurus-plugin-content-pages/solutions/archief.mdx b/i18n/nl/docusaurus-plugin-content-pages/solutions/archief.mdx index 3fec14959..a79ba105c 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/solutions/archief.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/solutions/archief.mdx @@ -92,7 +92,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai
    diff --git a/i18n/nl/docusaurus-plugin-content-pages/solutions/zaakafhandeling.mdx b/i18n/nl/docusaurus-plugin-content-pages/solutions/zaakafhandeling.mdx index 12e1c1fca..ef7b71929 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/solutions/zaakafhandeling.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/solutions/zaakafhandeling.mdx @@ -104,7 +104,7 @@ import {DetailHero, Section, SectionHead, FeatureList, FeatureItem, PairRow, Pai
    diff --git a/i18n/nl/docusaurus-plugin-content-pages/support.mdx b/i18n/nl/docusaurus-plugin-content-pages/support.mdx index acb006b2a..6df3d3974 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/support.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/support.mdx @@ -13,14 +13,14 @@ export const partnerLeadTitle = (f) => `Partner application: ${f.company || 'Onbekend bedrijf'}`;
    -
    +
    Support
    -

    Hulp die past waar jij staat.

    -

    +

    Hulp die past waar jij staat.

    +

    De apps zijn open source en gratis. De meeste teams installeren en draaien ze zelf. Heb je hulp nodig? Drie opties, op volgorde: lees de docs, open een GitHub-issue, of praat met een partner. Het partnerprogramma is het enige betaalde traject. Features zetten we nooit achter een betaalmuur.

    @@ -151,11 +151,11 @@ export const partnerLeadTitle = (f) => align="stack" lede="Drie tiers laten zien hoe diep de samenwerking gaat. Elke tier installeert dezelfde apps. Het verschil zit in proces, reactietijd en gezamenlijke roadmap." /> -
    +
    PARTNER -

    Implementatie, op aanvraag.

    -
    +

    Implementatie, op aanvraag.

    +
    Zelf-gecertificeerd op de apps. Installeert en configureert voor één of twee klanten in een regio of sector.
      @@ -165,9 +165,9 @@ export const partnerLeadTitle = (f) =>
    - GECERTIFICEERD -

    Gecertificeerd op de apps die je levert.

    -
    + GECERTIFICEERD +

    Gecertificeerd op de apps die je levert.

    +
    Door Conduction gecertificeerd per app. Gezamenlijke go-to-market op aanbestedingen. Hogere SLA op gedeelde klanten.
      @@ -179,8 +179,8 @@ export const partnerLeadTitle = (f) => STRATEGISCH -

      Gedeelde roadmap, gedeelde release.

      -
      +

      Gedeelde roadmap, gedeelde release.

      +
      De diepste partners van Conduction. Gedeelde release-cadence, hostingaanbod, gezamenlijke roadmap op de apps die ertoe doen.
        diff --git a/i18n/nl/docusaurus-plugin-content-pages/terms.mdx b/i18n/nl/docusaurus-plugin-content-pages/terms.mdx index 444b5312e..8113d0a6e 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/terms.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/terms.mdx @@ -16,12 +16,12 @@ export const TermsIcon = ( export const PROMISE_PANELS = { uptime: ( -
        +
        Uptime-formule
        -
        +
        Uptime % = (Overeengekomen Diensturen − Uren Downtime) / Overeengekomen Diensturen × 100%
        -
        Telt niet als downtime
        +
        Telt niet als downtime
          {['Gepland onderhoud', 'Verminderde prestaties', 'Overmacht', 'Internetstoringen', 'Handelingen of nalatigheid van klant/gebruiker', 'Handhaving van overheidsvoorschriften'].map((l, i) => (
        • @@ -33,12 +33,12 @@ export const PROMISE_PANELS = {
        ), response: ( -
        -
        Reactie en oplossing (kantooruren)
        -
        -
        Prio
        -
        Reactie
        -
        Workaround / Oplossing
        +
        +
        Reactie en oplossing (kantooruren)
        +
        +
        Prio
        +
        Reactie
        +
        Workaround / Oplossing
        {[ @@ -46,7 +46,7 @@ export const PROMISE_PANELS = { {p: 'P2', r: '4 uur', s: '16 uur / 48 uur', c: 'var(--c-blue-cobalt)'}, {p: 'P3', r: 'een week', s: 'Planmatig', c: 'var(--c-cobalt-300)'}, ].map((row, i) => ( -
        +
        {row.p} @@ -56,57 +56,57 @@ export const PROMISE_PANELS = {
        ))}
        -
        Kantooruren = ma–vr 09:00–17:00, officiële feestdagen niet inbegrepen.
        +
        Kantooruren = ma–vr 09:00–17:00, officiële feestdagen niet inbegrepen.
        ), security: ( -
        +
        ISO 27001:2022-certificaat -
        ISO/IEC 27001:2022
        +
        ISO/IEC 27001:2022
        ), capacity: ( -
        +
        {[ {n: '5 GB', l: 'Opslag'}, {n: '1 mln', l: 'Bevragingen / maand'}, {n: '10 GB', l: 'Dataverkeer / maand'}, ].map((b, i) => ( -
        -
        {b.n}
        -
        {b.l}
        +
        +
        {b.n}
        +
        {b.l}
        ))}
        ), maintenance: ( -
        -
        Gepland onderhoud
        +
        +
        Gepland onderhoud
        {[ {k: 'Aankondiging', v: '≥ 24 uur vooraf'}, {k: 'Inplanning', v: 'Buiten kantooruren'}, {k: 'Communicatie', v: 'Binnen redelijke termijn'}, ].map((row, i) => (
        0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> - {row.k} + {row.k} {row.v}
        ))}
        ), updates: ( -
        +
        Inbegrepen in de SLA
          {['Nieuwe versies en releases', 'Bugfixes', 'Beveiligingsupdates'].map((l, i) => ( -
        • +
        • {l}
        • ))}
        -
        Buiten scope
        -
        Ontwikkeling van nieuwe functionaliteit naast bugfixes en beveiligingsupdates wordt per opdracht overeengekomen.
        +
        Buiten scope
        +
        Ontwikkeling van nieuwe functionaliteit naast bugfixes en beveiligingsupdates wordt per opdracht overeengekomen.
        ), }; @@ -179,7 +179,7 @@ export const PROMISE_PANELS = { ## Service Level Agreement voor [product] -
        +
        Service Level Agreement
        @@ -187,19 +187,19 @@ export const PROMISE_PANELS = {
        Standaard
        -
        ISO/IEC 27001-conform
        +
        ISO/IEC 27001-conform
        Uptime
        -
        95% · 24/7
        +
        95% · 24/7
        P1 reactie
        -
        2 u / 4 u / 8 u
        +
        2 u / 4 u / 8 u
        Kantooruren
        -
        ma–vr 09:00–17:00
        +
        ma–vr 09:00–17:00
        @@ -308,7 +308,7 @@ Binnen deze SLA zal Conduction de klant voorzien van nieuwe versies, releases en Tijdens de maandelijkse community-meeting van [product] zal er een vertegenwoordiger van Conduction aanwezig zijn. In deze meeting wordt het product geëvalueerd en de richting, roadmap en keuzes toegelicht. -
        +
        Vastgesteld door de directie van Conduction B.V., Amsterdam, Nederland. Deze Nederlandse tekst is de juridisch bindende versie van de SLA.
        @@ -322,14 +322,14 @@ Tijdens de maandelijkse community-meeting van [product] zal er een vertegenwoord Open de OpenWoo-SLA → - + Quality, certificeringen & beleid → {/* JSX-string, geen kale tekst: MDX linkt een e-mailadres in lopende tekst automatisch en zette daardoor een binnen deze . De browser haalt die nesting uit elkaar, waardoor de pagina mislukt in hydration. */} - {'info@conduction.nl'} + {'info@conduction.nl'}
    From d7cc04f33d37fa789227abf670e586fb6cd38fb3 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sat, 19 Sep 2026 16:21:50 +0200 Subject: [PATCH 295/330] fix(arcade): the mini-game takes the mock's place in the hero (#213) Preset 3.46.0 puts the game where the illustration was, instead of below it: the mock steps aside once the game is found, and the hero widens that column so the boards get about 700px instead of 560. A game that opens under the illustration reads as a third thing on the page; it should be the thing you are looking at. Two assertions come with it, and both fail if the swap is dropped: the mock is gone once the game is found, and it has the hero to itself until then. --- e2e/product-page-games.spec.js | 15 +++++++++++++++ package-lock.json | 15 +++++++++++---- package.json | 2 +- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/e2e/product-page-games.spec.js b/e2e/product-page-games.spec.js index 1b683b077..c74ccf8fd 100644 --- a/e2e/product-page-games.spec.js +++ b/e2e/product-page-games.spec.js @@ -118,6 +118,12 @@ test.describe('stamp rush, on the Decidiq page', () => { 'the game opened somewhere other than the hero', ).toBeVisible(); + /* And in the mock's place, not beside it. */ + await expect( + page.locator('[class*="illustration_"]'), + 'the app mock is still there, so the game is not where it should be', + ).toBeHidden(); + await game.getByRole('button', {name: /take the pen/i}).click(); /* A desk holding a decision, whichever kind it is. */ @@ -438,6 +444,15 @@ test('a direct play link opens a game without solving its riddle', async ({page} await expect(page.getByRole('region', {name: 'Paint by tokens'})).toBeVisible({timeout: 5000}); }); +test('the app mock has the hero to itself until a game is found', async ({page}) => { + /* The other half of the swap: a page whose game nobody has found + looks exactly like a page with no game at all. */ + await clearScores(page); + await page.goto('/apps/decidiq/'); + await expect(page.locator('[class*="illustration_"]')).toBeVisible(); + await expect(page.locator('section[class*="rush"]')).toHaveCount(0); +}); + test('one riddle does not open another page game', async ({page}) => { await clearScores(page); await page.goto('/apps/buildiq/'); diff --git a/package-lock.json b/package-lock.json index 00efbda67..831eeca26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.45.3", + "@conduction/docusaurus-preset": "^3.46.0", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", @@ -2018,9 +2018,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.45.3", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.45.3.tgz", - "integrity": "sha512-QeiuD4FBBftG9P/4bV3ibtyNbBNc4i1kerpFE9r2m+iZTEnGUOPgWjjyxoDWAUbeDxHEGNpVDSrfuNE5Shw6Qg==", + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.46.0.tgz", + "integrity": "sha512-hPX4aDxHzFZMcE0hdoamzvGE/54TP8jYB60bRbQmWy0TqIszsK+1y8zqxKRBeqmK5zbHFsbEvl0JY+/cNpiS7g==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" @@ -16279,6 +16279,13 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "license": "MIT", + "peer": true + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", diff --git a/package.json b/package.json index 395f35700..a89099e18 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.45.3", + "@conduction/docusaurus-preset": "^3.46.0", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", From cbb3c5fa33c7d5d77a164986bfcd36d960517f45 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 20 Sep 2026 02:45:12 +0200 Subject: [PATCH 296/330] fix(a11y): a real mobile and dark-mode review, and an instrument to repeat it (#215) * fix(a11y): the invisible archive title, 43 raw-palette inks, and a review you can re-run A real mobile and dark-mode review, rather than the two narrow checks that preceded it. All 779 routes, both locales, at 320, 390, 768 and 1280 px in both themes, against sixteen checks. The instrument now lives in the repo instead of a scratch directory, because a review you cannot re-run is not a review: e2e/a11y-checks.js the checks, shared by the gate and the sweep e2e/a11y-selftest.spec.js the control: every check run against a page built to break it, plus a negative control scripts/a11y-sweep.mjs the whole site, on demand Four faults in the previous instruments, each of which made them too kind and each of which is now a control that fails without the fix: - Foreground alpha was discarded. rgba(255,255,255,0.55) is painted as a blend, not as white. Uncomposited the fixture case scored about 14:1; composited it is 1.76:1. - Translucent panel fills were treated as nothing, so the ground resolved to the surface underneath the veil. 1.35:1 read as a pass. - Gradients were skipped as unmeasurable rather than judged against their worst colour stop. - Content inside a closed details keeps a full layout box and reports display:block, visibility:visible, opacity:1. checkVisibility() is the browser's own answer and says false. Six of seven accordions on /quality/ are closed, and every collapsed answer was being counted as on screen. The route list now comes from the build directory. sitemap.xml lists 163 routes; the build has 779. The sitemap omits /nl/ entirely, which is how the Dutch locale went unmeasured, and it also omits tag pages, author pages and paginated blog listings. Fixed here: - /academy/archive/ and its Dutch twin drew their h1 in the primary cobalt on a bar filled with the primary cobalt: exactly 1.00, the page title the same colour as the thing behind it, in both themes, since the page existed. It uses Infima's hero rather than one of our components, so the title took the heading colour while the subtitle took the hero's white. - 32 declarations of color: var(--c-cobalt-500, #6f7da3) across eight files. The earlier migration matched var(--c-name) and a fallback argument puts a comma inside the parentheses, so every one of these was skipped. They render at 1.73:1 on dark cards, just above the 1.5 threshold the old sweep used, which is why it called the site clean. - .cg-layer-en and the arcade hashtag, same cause. The Dutch arcade page had already been migrated and the English one had not. Reported, not fixed, because they fail in light mode and predate this work: form field borders at 1.78:1 on eight pages, and the site-wide target-size and 11px eyebrow debt. * fix(a11y): the pull-quote that inherited Infima's hero, and badges whose ink flipped off a fixed surface Second round of the mobile and dark-mode review, after the first round took the site from 9 critical defects to 0 and the sweep was widened. blockquote class="pull-quote hero" uses hero as a modifier of pull-quote, but hero is also a bare Infima class: .hero { display: flex; align-items: center; padding: 4rem 2rem }. The blockquote inherited all of it and became a flex row, so the cite sat beside the quote instead of under it. At 320px on the platform-moment post the citation ran from x=259 to x=336 in a 320px viewport, putting the author's own name off the right edge with no way to scroll to it. Six status badges on /quality/ and its Dutch twin set a fixed mint background and took their ink from text-strong, which flips. Light mode rendered cobalt-900 on mint at 4.93:1; dark mode rendered white on the same mint at 3.62:1. The gold Service pill on /support/ had the identical fault. This is the mirror of the usual dark-mode bug, and the rule it establishes is that ink and surface flip together: where the surface cannot flip, the ink must not either. The certified pill on /nl/support/ is a correction to my own first attempt. I gave it cobalt-900 ink on what I assumed was the gold it passes as a prop, but Pill renders tone="solid" with a fixed blue-cobalt background and ignores that prop, so the ink landed on blue and I made dark mode worse while light stayed broken. It is fixed white now, which reads on the blue in both themes. The ignored color prop on Pill is worth a look separately. The gate caught that regression before it shipped, which is the point of it. Also in this commit: the sweep now skips redirect stubs. 118 of the build's 779 routes are meta-refresh stubs; the browser follows them, so the sweep was measuring whatever it landed on and filing it under the stub. Off site that meant reporting openconnector.conduction.nl's defects as ours. Final measurement, 661 routes with content, both locales, 320/390/768/1280 px, both themes: 0 critical, 0 unreachable content or controls, 0 dark-only severe contrast, 6 dark-only AA misses down to 0. Reported and not fixed, all failing in light mode and predating this work: form field borders at 1.78:1 on six pages, brand citations ( at 1.93:1, at 2.68:1), and the site-wide target-size and 11px eyebrow debt. The gate names the two brand colours explicitly rather than suppressing a check, so the exception stays visible in review. * build(deps): take preset 3.47.1, which carries the mobile and dark-mode fixes The preset half of the review: muted and link inks that clear AA on the surfaces they actually land on, links on components that paint their own cobalt, and four layouts that survive 320px. Verified against the published package rather than a patched node_modules. Full sweep of 661 routes with content, both locales, at 320, 390, 768 and 1280 px in both themes, 0 load failures: 0 critical, 0 unreachable content or controls, and zero dark-only defects of any severity. Everything that remains is attributed both-themes or all-widths, which is to say it predates this work. --- e2e/a11y-checks.js | 363 ++++++++++++++++++ e2e/a11y-selftest.spec.js | 170 ++++++++ e2e/a11y.spec.js | 142 +++++++ .../nl/docusaurus-plugin-content-pages/ai.mdx | 4 +- .../apps/decidiq.mdx | 2 +- .../privacy.mdx | 8 +- .../quality.mdx | 18 +- .../support.mdx | 2 +- .../docusaurus-plugin-content-pages/terms.mdx | 8 +- package-lock.json | 8 +- package.json | 2 +- scripts/a11y-sweep.mjs | 198 ++++++++++ src/css/site.css | 32 +- src/pages/ai.mdx | 4 +- src/pages/apps/decidiq.mdx | 2 +- src/pages/arcade.mdx | 2 +- src/pages/privacy.mdx | 8 +- src/pages/quality.mdx | 18 +- src/pages/support.mdx | 2 +- src/pages/terms.mdx | 8 +- 20 files changed, 952 insertions(+), 49 deletions(-) create mode 100644 e2e/a11y-checks.js create mode 100644 e2e/a11y-selftest.spec.js create mode 100644 e2e/a11y.spec.js create mode 100644 scripts/a11y-sweep.mjs diff --git a/e2e/a11y-checks.js b/e2e/a11y-checks.js new file mode 100644 index 000000000..7f6e1c6c1 --- /dev/null +++ b/e2e/a11y-checks.js @@ -0,0 +1,363 @@ +/** + * The checks behind the mobile and dark-mode review, in one place. + * + * Both the Playwright gate (`a11y.spec.js`) and the full-site sweep + * (`scripts/a11y-sweep.mjs`) run this exact function, so the gate can never + * drift from the thing that measured the site. `a11y-selftest.spec.js` runs + * it against a page built to break every check, because a check that has + * never been seen failing is not evidence of anything: two instruments + * during this work returned a confident zero because they were broken + * rather than because the site was clean. + * + * Runs inside the browser. Exported as a plain function so + * `page.evaluate(RUN_CHECKS, {theme, ...})` serialises it. + */ +export const RUN_CHECKS = async ({theme}) => { + /* Colours and boxes both settle late, and each of these produced + confident findings that were not real: + - reading in the same tick as the theme switch returns PRE-switch + values on a busy page (197 phantom findings on /academy/), + - a card mid-transition is a blend belonging to neither theme + (white on rgb(231,233,236), a grey that is nowhere in the palette), + - text whose digits are still ticking is measured at a width it will + not keep (the footer HUD read as clipped, then did not reproduce). + Kill CSS animation, switch, wait two frames, then wait for the page's + own text and overflow extent to stop changing. Bounded, because a page + with a live counter never settles and still has to be measured. */ + const freeze = document.createElement('style'); + freeze.textContent = + '*,*::before,*::after{transition:none!important;animation:none!important}'; + document.head.appendChild(freeze); + if (theme) document.documentElement.setAttribute('data-theme', theme); + await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r))); + await new Promise((r) => setTimeout(r, 250)); + + const fingerprint = () => { + let widest = 0; + for (const el of document.querySelectorAll('*')) { + if (el.scrollWidth > el.clientWidth) widest += el.scrollWidth - el.clientWidth; + } + return `${document.body.innerText.length}:${Math.round(widest)}`; + }; + let last = fingerprint(); + for (let i = 0; i < 12; i++) { + await new Promise((r) => setTimeout(r, 300)); + const now = fingerprint(); + if (now === last) break; + last = now; + } + + const out = []; + const vw = document.documentElement.clientWidth; + const add = (check, severity, text, detail, extra = {}) => + out.push({check, severity, text: String(text).slice(0, 60), detail, ...extra}); + + /* ---------------- colour ---------------- */ + const ch = (c) => (String(c).match(/[-\d.]+/g) || []).slice(0, 3).map(Number); + /* Alpha is not decoration. `color: rgba(255,255,255,0.55)` is painted as a + blend with what is behind it; treating it as white reports a ratio the + reader never gets. This site has 14 of those plus translucent panel + fills, and uncomposited they all scored as passes. */ + const alphaOf = (c) => { + const p = String(c).match(/[-\d.]+/g) || []; + return p.length >= 4 ? Number(p[3]) : 1; + }; + const over = (fg, a, bg) => fg.map((v, i) => Math.round(v * a + bg[i] * (1 - a))); + const lum = (rgb) => { + const a = rgb.map((v) => { + v /= 255; + return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); + }); + return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2]; + }; + const contrast = (a, b) => { + const l1 = lum(a); + const l2 = lum(b); + return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05); + }; + const opaque = (bg) => { + if (!bg || /rgba\(0, 0, 0, 0\)|transparent/.test(bg)) return null; + const rgb = ch(bg); + return rgb.length === 3 && alphaOf(bg) >= 0.999 ? rgb : null; + }; + const translucent = (bg) => { + if (!bg || /rgba\(0, 0, 0, 0\)|transparent/.test(bg)) return null; + const rgb = ch(bg); + const a = alphaOf(bg); + return rgb.length === 3 && a > 0.001 && a < 0.999 ? {rgb, a} : null; + }; + /* A gradient is measurable: take every colour stop and judge the text + against the worst of them. Skipping gradients is how an earlier sweep + reported zero on surfaces it never looked at. */ + const gradientStops = (img) => { + if (!img || img === 'none' || !/gradient/.test(img)) return null; + const stops = []; + for (const m of img.matchAll(/rgba?\(([-\d.\s,%]+)\)/g)) { + const parts = (m[1].match(/[-\d.]+/g) || []).map(Number); + if (parts.length >= 3 && (parts[3] === undefined || parts[3] >= 0.95)) { + stops.push(parts.slice(0, 3)); + } + } + return stops.length ? stops : null; + }; + + /** Walks up through shadow hosts as well as parents, compositing any + * translucent layers it passes onto whatever finally paints. */ + const behind = (el) => { + const veils = []; + const finish = (kind, colors) => ({ + kind, + colors: colors.map((base) => { + let c = base; + for (let i = veils.length - 1; i >= 0; i--) c = over(veils[i].rgb, veils[i].a, c); + return c; + }), + }); + let n = el; + while (n && n !== document.documentElement) { + const s = getComputedStyle(n); + if (s.backgroundImage && s.backgroundImage !== 'none') { + const stops = gradientStops(s.backgroundImage); + return stops ? finish('gradient', stops) : {kind: 'image', colors: []}; + } + const solid = opaque(s.backgroundColor); + if (solid) return finish('solid', [solid]); + const veil = translucent(s.backgroundColor); + if (veil) veils.push(veil); + n = n.parentElement || (n.getRootNode() instanceof ShadowRoot ? n.getRootNode().host : null); + } + const root = getComputedStyle(document.documentElement); + if (root.backgroundImage && root.backgroundImage !== 'none') { + const stops = gradientStops(root.backgroundImage); + return stops ? finish('gradient', stops) : {kind: 'image', colors: []}; + } + const solid = opaque(root.backgroundColor); + return solid ? finish('solid', [solid]) : {kind: 'image', colors: []}; + }; + + /** + * Is this element actually rendered to a reader? + * + * `Element.checkVisibility()` is the browser's own answer and it is the + * one to ask. Inferring it from display, visibility and opacity misses a + * case this site has on eight pages: content inside a CLOSED `
    ` + * keeps a full layout box, because `::details-content` hides it with + * `content-visibility`, and reports `display: block`, `visibility: + * visible`, `opacity: 1` and even `content-visibility: visible` on the + * child itself. Measured on /quality/ 2026-09-19, where 6 of 7 accordions + * are closed: every collapsed answer was counted as on-screen, and their + * boxes pile up at nearly the same coordinates, which the overlap check + * then reported as a broken layout. checkVisibility() returns false for + * all of them. + */ + const seen = (el, s) => { + if (s.visibility === 'hidden' || s.display === 'none' || Number(s.opacity) === 0) return false; + if (typeof el.checkVisibility === 'function') { + return el.checkVisibility({ + contentVisibilityAuto: true, + opacityProperty: true, + visibilityProperty: true, + }); + } + return true; + }; + const ownText = (el) => + [...el.childNodes].filter((n) => n.nodeType === 3).map((n) => n.textContent.trim()).join(' ').trim(); + /* Parked off-screen until focused, by design, and styled by Infima. */ + const SKIP = ['Skip to main content', 'Ga naar hoofdinhoud']; + + const els = (() => { + const acc = []; + const walk = (root) => { + for (const el of root.querySelectorAll('*')) { + acc.push(el); + if (el.shadowRoot) walk(el.shadowRoot); + } + }; + walk(document); + return acc; + })(); + + /* ---------------- 1. zoom (SC 1.4.4) ---------------- */ + const vpc = (document.querySelector('meta[name="viewport"]') || {}).content || ''; + if (/user-scalable\s*=\s*(no|0)/i.test(vpc)) add('zoom-disabled', 'serious', vpc, 'pinch zoom is switched off'); + const maxScale = (vpc.match(/maximum-scale\s*=\s*([\d.]+)/i) || [])[1]; + if (maxScale && Number(maxScale) < 5) add('zoom-capped', 'serious', vpc, `maximum-scale=${maxScale}, under the 5x floor`); + + /* ---------------- 2. contrast at the real AA bars ---------------- */ + const TEXT = 'p,h1,h2,h3,h4,h5,h6,li,a,span,strong,em,b,i,td,th,dt,dd,figcaption,label,button,blockquote,code,summary,legend,option'; + for (const el of els) { + if (!el.matches || !el.matches(TEXT)) continue; + const text = ownText(el); + if (text.length < 4 || SKIP.includes(text)) continue; + const s = getComputedStyle(el); + if (!seen(el, s)) continue; + const r = el.getBoundingClientRect(); + if (!r.width || !r.height) continue; + const fg = ch(s.color); + if (fg.length !== 3) continue; + const size = parseFloat(s.fontSize); + const large = size >= 24 || (size >= 18.66 && Number(s.fontWeight) >= 700); + const required = large ? 3 : 4.5; + const bg = behind(el); + if (bg.kind === 'image') { + add('contrast-unmeasurable', 'info', text, 'text sits on a background image'); + continue; + } + const fgA = alphaOf(s.color); + let worst = Infinity; + let worstBg = null; + for (const c of bg.colors) { + const k = contrast(fgA >= 0.999 ? fg : over(fg, fgA, c), c); + if (k < worst) { worst = k; worstBg = c; } + } + if (!isFinite(worst)) continue; + const ratio = Number(worst.toFixed(2)); + const meta = {color: s.color, background: `rgb(${worstBg.join(',')})`, required, fontSize: size, bgKind: bg.kind}; + if (ratio < 1.5) add('contrast-invisible', 'critical', text, `${ratio}:1`, meta); + else if (ratio < 3) add('contrast-severe', 'serious', text, `${ratio}:1 needs ${required}:1`, meta); + else if (ratio < required) add('contrast-aa', 'moderate', text, `${ratio}:1 needs ${required}:1`, meta); + if (size && size < 12) add('tiny-text', 'moderate', text, `${size.toFixed(1)}px`); + } + + /* ---------------- 3. reachability, all content ---------------- */ + const CONTROL = 'a[href], button, input:not([type="hidden"]), select, textarea, [role="button"]'; + for (const el of els) { + if (!el.matches) continue; + const isControl = el.matches(CONTROL); + const text = ownText(el); + const isMedia = el.matches('img, svg, video, canvas, picture, table, pre'); + if (!isControl && !isMedia && text.length < 8) continue; + if (SKIP.includes((el.textContent || '').trim())) continue; + if (el.closest && el.closest('#navbar-drawer[hidden]')) continue; + const s = getComputedStyle(el); + if (!seen(el, s)) continue; + const r = el.getBoundingClientRect(); + if (r.width === 0 && r.height === 0) continue; + if (r.right <= vw + 1 && r.left >= -1) continue; + /* Lying outside the viewport is only a fault when nothing can scroll to + it. An `auto`/`scroll` ancestor means the reader can reach it; a + component's own `hidden`/`clip` is a deliberate mask (the marquee, the + card track). Reaching html/body means the only thing holding it is the + site-wide `overflow-x: clip`, and nothing will ever reveal it. */ + let n = el.parentElement; + let byPage = true; + while (n && n !== document.documentElement) { + if (getComputedStyle(n).overflowX !== 'visible') { byPage = n === document.body; break; } + n = n.parentElement || (n.getRootNode() instanceof ShadowRoot ? n.getRootNode().host : null); + } + if (!byPage) continue; + add( + isControl ? 'offscreen-control' : 'offscreen-content', + isControl ? 'critical' : 'serious', + text || el.tagName.toLowerCase(), + `x=${Math.round(r.left)}..${Math.round(r.right)} vw=${vw}`, + {tag: el.tagName.toLowerCase()}, + ); + } + + /* ---------------- 4. target size, SC 2.5.8 ---------------- */ + const boxes = []; + for (const el of els.filter((e) => e.matches && e.matches(CONTROL))) { + const s = getComputedStyle(el); + if (!seen(el, s)) continue; + if (el.closest && el.closest('#navbar-drawer[hidden]')) continue; + if (SKIP.includes((el.textContent || '').trim())) continue; + const r = el.getBoundingClientRect(); + if (!r.width || !r.height) continue; + const label = (el.textContent || '').trim() || el.getAttribute('aria-label') || el.tagName.toLowerCase(); + boxes.push({el, r, label}); + /* The standard exempts a link sitting in a sentence, where shrinking the + target would mean reflowing the prose around it. */ + if (el.tagName === 'A' && s.display.startsWith('inline') && el.parentElement && ownText(el.parentElement).length > 0) continue; + if (r.width < 24 || r.height < 24) { + add('target-size', 'moderate', label, `${Math.round(r.width)}x${Math.round(r.height)}, needs 24x24`); + } + } + + /* ---------------- 5. crowded tap targets ---------------- */ + for (let i = 0; i < boxes.length; i++) { + for (let j = i + 1; j < boxes.length; j++) { + const a = boxes[i].r; + const b = boxes[j].r; + if (boxes[i].el.contains(boxes[j].el) || boxes[j].el.contains(boxes[i].el)) continue; + if (a.width >= 44 && a.height >= 44 && b.width >= 44 && b.height >= 44) continue; + const dx = Math.max(0, Math.max(a.left - b.right, b.left - a.right)); + const dy = Math.max(0, Math.max(a.top - b.bottom, b.top - a.bottom)); + if (dx === 0 && dy === 0) continue; + const d = Math.hypot(dx, dy); + if (d < 8) add('target-spacing', 'moderate', `${boxes[i].label} / ${boxes[j].label}`, `${Math.round(d)}px apart`); + } + } + + /* ---------------- 6. overlapping text, the broken-layout tell ------- */ + const leaves = els + .filter((el) => el.matches && el.matches('p,h1,h2,h3,h4,li,td,th,figcaption,label') && ownText(el).length >= 8 && seen(el, getComputedStyle(el))) + .map((el) => ({el, r: el.getBoundingClientRect()})) + .filter((x) => x.r.width && x.r.height); + for (let i = 0; i < leaves.length; i++) { + const a = leaves[i].r; + for (let j = i + 1; j < leaves.length; j++) { + if (leaves[i].el.contains(leaves[j].el) || leaves[j].el.contains(leaves[i].el)) continue; + const b = leaves[j].r; + const ow = Math.min(a.right, b.right) - Math.max(a.left, b.left); + const oh = Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top); + if (ow <= 1 || oh <= 1) continue; + const frac = (ow * oh) / Math.min(a.width * a.height, b.width * b.height); + if (frac > 0.25) { + add('text-overlap', 'serious', `${ownText(leaves[i].el).slice(0, 25)} / ${ownText(leaves[j].el).slice(0, 25)}`, `${Math.round(frac * 100)}% overlap`); + } + } + } + + /* ---------------- 7. text clipped where it sits ---------------- */ + for (const el of els) { + if (!el.matches || el === document.body || el === document.documentElement) continue; + const s = getComputedStyle(el); + if (!seen(el, s)) continue; + const hx = s.overflowX === 'hidden' || s.overflowX === 'clip'; + const hy = s.overflowY === 'hidden' || s.overflowY === 'clip'; + if (!hx && !hy) continue; + const t = (el.textContent || '').trim(); + if (t.length < 8) continue; + const cutX = hx && el.scrollWidth > el.clientWidth + 2; + const cutY = hy && el.scrollHeight > el.clientHeight + 2; + if (!cutX && !cutY) continue; + if (el.className && /marquee|track|ticker|carousel|slider/i.test(String(el.className))) continue; + if (s.textOverflow === 'ellipsis') continue; + add('clipped-text', 'moderate', t, cutX ? `${el.scrollWidth}px in a ${el.clientWidth}px box` : `${el.scrollHeight}px in a ${el.clientHeight}px box`); + } + + /* ---------------- 8. non-text contrast, SC 1.4.11 ---------------- */ + for (const el of els) { + if (!el.matches || !el.matches('input:not([type="hidden"]), select, textarea')) continue; + const s = getComputedStyle(el); + if (!seen(el, s)) continue; + const r = el.getBoundingClientRect(); + if (!r.width || !r.height) continue; + if ((parseFloat(s.borderTopWidth) || 0) === 0) continue; + /* The border carries alpha as often as the text does: this site draws + fields on tinted panels with `border: 1px solid rgba(255,255,255,0.18)`. + Taking only opaque borders skipped those entirely, which is the same + leniency the contrast check had before alpha was composited. */ + const borderRaw = ch(s.borderTopColor); + if (borderRaw.length !== 3) continue; + const borderA = alphaOf(s.borderTopColor); + if (borderA <= 0.001) continue; + const bg = behind(el.parentElement || el); + if (bg.kind === 'image' || !bg.colors.length) continue; + let worst = Infinity; + for (const c of bg.colors) { + /* The border is painted over the field's own background where it has + one, and over the surface behind it where it does not. */ + const under = opaque(s.backgroundColor) || c; + const painted = borderA >= 0.999 ? borderRaw : over(borderRaw, borderA, under); + worst = Math.min(worst, contrast(painted, c)); + } + if (worst < 3) { + add('nontext-contrast', 'serious', el.getAttribute('name') || el.getAttribute('placeholder') || el.tagName.toLowerCase(), `field border ${worst.toFixed(2)}:1, needs 3:1`); + } + } + + return out; +}; diff --git a/e2e/a11y-selftest.spec.js b/e2e/a11y-selftest.spec.js new file mode 100644 index 000000000..647b8100c --- /dev/null +++ b/e2e/a11y-selftest.spec.js @@ -0,0 +1,170 @@ +/** + * The control for the accessibility checks. + * + * Every check in `a11y-checks.js` is run here against a page built to break + * it, and the test fails if any check stays silent. This exists because a + * check that has never been observed failing tells you nothing when it + * returns zero, and during this work two instruments did exactly that: one + * reported "0 findings, 4 unreachable" because `page.evaluate(fn, a, b)` + * silently takes only one argument, and an earlier contrast sweep reported + * a clean site because it threw away the alpha channel and skipped every + * gradient. + * + * Runs in the `chromium` project. It never loads the site, so it is fast and + * cannot be broken by a content change. + */ + +import {test, expect} from '@playwright/test'; +import {RUN_CHECKS} from './a11y-checks.js'; + +const FIXTURE = ` + + + +

    This paragraph is a severe miss without being invisible.

    +

    This paragraph misses the AA bar but clears three to one.

    +

    This paragraph sits on a background image.

    +

    This paragraph sits on a near white gradient.

    +

    This paragraph is set far too small to read on a phone.

    +

    This paragraph is parked outside the viewport entirely.

    + A link parked outside the viewport + + +

    First overlapping paragraph of text here

    +

    Second overlapping paragraph of text here

    +
    This sentence is much wider than the box that holds it
    + +
    + +
    + A collapsed question nobody has opened + +

    collapsed-tiny text that must never be reported

    + collapsed-offscreen link that must never be reported +
    +

    alpha-text painted at low opacity over a dark panel

    +

    veil-text in white on a panel that is nearly white

    +`; + +const MUST_FIRE = [ + 'zoom-disabled', + 'zoom-capped', + 'contrast-invisible', + 'contrast-severe', + 'contrast-aa', + 'contrast-unmeasurable', + 'tiny-text', + 'offscreen-content', + 'offscreen-control', + 'target-size', + 'target-spacing', + 'text-overlap', + 'clipped-text', + 'nontext-contrast', +]; + +test('every check can actually fail', async ({page}) => { + await page.setViewportSize({width: 390, height: 844}); + await page.setContent(FIXTURE, {waitUntil: 'load'}); + const found = await page.evaluate(RUN_CHECKS, {theme: null}); + + const counts = {}; + for (const f of found) counts[f.check] = (counts[f.check] || 0) + 1; + + const silent = MUST_FIRE.filter((c) => !counts[c]); + expect( + silent, + `these checks stayed silent on a page built to break them, so a zero from ` + + `them on the real site would mean nothing:\n ${silent.join('\n ')}\n\n` + + `what did fire: ${JSON.stringify(counts)}`, + ).toEqual([]); +}); + +/* Three colour paths that each pass a checker doing the naive thing. Named + separately because they are silent failures of the CONTRAST check rather + than of a check of their own, so the list above cannot see them. */ +/* The negative control. Everything above asks "can this check fail?"; this + asks "can it stay quiet when it should?". Without it the suite rewards a + checker that simply reports everything, and the closed-accordion case is + exactly how that happens: the browser gives collapsed content a full + layout box, so it looks like ordinary on-screen content to anyone who + does not ask `checkVisibility()`. */ +test('content inside a closed accordion is never reported', async ({page}) => { + await page.setViewportSize({width: 390, height: 844}); + await page.setContent(FIXTURE, {waitUntil: 'load'}); + const found = await page.evaluate(RUN_CHECKS, {theme: null}); + + const leaked = found.filter((f) => f.text.includes('collapsed-')); + expect( + leaked.map((f) => `${f.check}: ${f.text}`), + 'a collapsed
    keeps a layout box and reports display:block, ' + + 'visibility:visible and opacity:1; only checkVisibility() knows better', + ).toEqual([]); +}); + +test('alpha and gradients are measured, not assumed', async ({page}) => { + await page.setViewportSize({width: 390, height: 844}); + await page.setContent(FIXTURE, {waitUntil: 'load'}); + const found = await page.evaluate(RUN_CHECKS, {theme: null}); + + const hit = (needle) => found.find((f) => f.text.includes(needle) && f.check.startsWith('contrast-')); + + expect( + hit('alpha-text'), + 'a foreground with alpha must be composited over its background; ' + + 'uncomposited this is white on navy and scores about 14:1', + ).toBeTruthy(); + + expect( + hit('veil-text'), + 'a translucent panel fill is a real layer; resolving past it to the ' + + 'surface underneath makes white ink on a near-white veil look like a pass', + ).toBeTruthy(); + + expect( + found.find((f) => f.check === 'nontext-contrast' && f.text === 'translucent'), + 'a field border with alpha must be composited too; taken as opaque white ' + + 'it looks fine against a navy panel', + ).toBeTruthy(); + + const gradient = found.find((f) => f.bgKind === 'gradient'); + expect( + gradient, + 'text on a gradient must be judged against its worst colour stop, ' + + 'not skipped as unmeasurable', + ).toBeTruthy(); +}); diff --git a/e2e/a11y.spec.js b/e2e/a11y.spec.js new file mode 100644 index 000000000..832bb3e1d --- /dev/null +++ b/e2e/a11y.spec.js @@ -0,0 +1,142 @@ +/** + * Mobile and dark-mode regression gate. + * + * `scripts/a11y-sweep.mjs` measures all 779 routes and is run on demand. + * This is the part that runs every time: a sample of pages, at the widths + * and themes where the faults actually appeared, asserting only the classes + * that were fixed. It is deliberately not a full WCAG audit, because a gate + * that fails on pre-existing debt teaches everyone to ignore it. + * + * WHAT THIS GATES, and why each one is here rather than in the "reported" + * list below: + * + * contrast-invisible text at under 1.5:1. Two of these shipped: the + * archive page title and a hero link, both at exactly + * 1.00, both in production, both for months. + * contrast-severe under 3:1. The 1.73:1 band is where the raw-palette + * inks landed, and the previous suite's 1.5 threshold + * sat just underneath it and reported green. + * offscreen-control a control the reader cannot reach. The page sets + * overflow-x: clip, so there is no scrollbar and no + * symptom. + * offscreen-content same, for prose. 218 of these on academy pages from + * a single grid track that refused to shrink. + * + * WHAT IT DOES NOT GATE, deliberately, all of it pre-existing and none of it + * caused by mobile or dark mode: + * + * target-size ~10,500, almost all text links whose height is the + * line-height. A real SC 2.5.8 gap and a design + * decision, not a regression. + * tiny-text ~4,100 at 11px are the brand's uppercase eyebrow + * convention; ~1,000 at 10px and 23 at 9px are worth + * a look. + * target-spacing, + * clipped-text, + * nontext-contrast form borders at 1.78:1, which fail in LIGHT mode. + * contrast-aa the 3-to-4.5 band, which is mostly the Nextcloud + * blue at 4.17:1 on white, in both themes. + * + * Run `npx playwright test a11y-selftest` if you doubt these checks: it runs + * each of them against a page built to break it. + */ + +import {test, expect} from '@playwright/test'; +import {RUN_CHECKS} from './a11y-checks.js'; + +const BLOCKING = ['contrast-invisible', 'contrast-severe', 'offscreen-control', 'offscreen-content']; + +/** + * Brand colours cited as text, which miss the bar in LIGHT mode and have + * done since long before dark mode existed. + * + * renders Common Ground's own yellow (#F6AD00) and + * renders KNVB orange (#F36C21). As body text on a white or cobalt-50 + * ground those measure 1.93:1 and 2.68:1. That is a real accessibility + * problem and it is also a brand decision: the fix is either a darker + * citation colour or not colouring the word at all, and neither is a call + * to make inside an accessibility sweep. + * + * They are listed here by exact colour rather than suppressed by check, so + * the exception is narrow, visible in review, and cannot quietly grow to + * cover a genuine regression. Anything else at these ratios still fails. + */ +const BRAND_CITATION_DEBT = [ + 'rgb(246, 173, 0)', // --c-commonground-yellow, cited via + 'rgb(243, 108, 33)', // --c-orange-knvb, cited via +]; + +/* One page per layout archetype, plus every page that actually carried a + fixed defect, plus Dutch twins. A sample drawn only from layouts you + already thought about keeps agreeing with you: this list is the layouts + PLUS the crime scenes. */ +const PAGES = [ + ['home', '/'], + ['apps listing', '/apps/'], + ['app detail, cobalt hero + cta banner', '/apps/zaakafhandelapp/'], + ['blog archive, Infima hero', '/academy/archive/'], + ['academy tutorial, code in steps', '/academy/run-nextcloud-locally/'], + ['academy tutorial, prerequisites list', '/academy/openspec-tutorial-5-retrofit/'], + ['quality, accordions + definition lists', '/quality/'], + ['terms, SLA tables', '/terms/'], + ['support, forms + hex network', '/support/'], + ['commonground, layer model', '/commonground/'], + ['arcade', '/arcade/'], + ['contact, CRM form', '/contact/'], + + /* The Dutch locale is a parallel set of source files, not a translation + layer, so nothing mechanically keeps it in step. It is also absent from + sitemap.xml, which is how it went unmeasured while /nl/terms/ carried 88 + invisible elements and the English suite was green. */ + ['nl home', '/nl/'], + ['nl app detail', '/nl/apps/zaakafhandelapp/'], + ['nl blog archive', '/nl/academy/archive/'], + ['nl quality', '/nl/quality/'], + ['nl support', '/nl/support/'], + ['nl commonground', '/nl/commonground/'], +]; + +/* 320 is where layouts break and 1280 is where they do not, so a failure at + 320 alone is attributable to width. Dark at both, plus light at 1280 to + catch a fix that trades one theme for the other: migrating ink without + migrating the surface under it once took the navbar from fine to 1.29:1. */ +const COMBOS = [ + {width: 320, theme: 'dark'}, + {width: 1280, theme: 'dark'}, + {width: 1280, theme: 'light'}, +]; + +const footerReady = () => { + const f = document.querySelector('.canal-footer'); + if (!f) return true; + const link = [...document.querySelectorAll('link[rel="stylesheet"]')].find((l) => l.href.includes('canal-footer')); + if (!link) return false; + try { + if (!link.sheet || link.sheet.cssRules.length === 0) return false; + } catch { + if (!link.sheet) return false; + } + const go = f.querySelector('.game-over'); + return !(go && getComputedStyle(go).display !== 'none'); +}; + +for (const [name, path] of PAGES) { + for (const combo of COMBOS) { + test(`${name} at ${combo.width}px ${combo.theme}`, async ({page}) => { + await page.setViewportSize({width: combo.width, height: combo.width < 700 ? 844 : 900}); + await page.goto(path); + await page.waitForLoadState('networkidle'); + await page.waitForFunction(footerReady); + + const found = await page.evaluate(RUN_CHECKS, {theme: combo.theme}); + const blocking = found + .filter((f) => BLOCKING.includes(f.check)) + .filter((f) => !BRAND_CITATION_DEBT.includes(f.color)); + + expect( + blocking.map((f) => `${f.check} "${f.text}" ${f.detail}${f.color ? ` ${f.color} on ${f.background}` : ''}`), + `${blocking.length} blocking defect(s) on ${path} at ${combo.width}px in ${combo.theme}`, + ).toEqual([]); + }); + } +} diff --git a/i18n/nl/docusaurus-plugin-content-pages/ai.mdx b/i18n/nl/docusaurus-plugin-content-pages/ai.mdx index 349a7e845..9f89d1d0a 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/ai.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/ai.mdx @@ -26,7 +26,7 @@ export const IbmQuote = () => (

    “A computer can never be held accountable, therefore a computer must never make a management decision.”

    -
    +
    IBM-trainingsmap, 1979
    @@ -132,7 +132,7 @@ Verantwoordelijkheid is de hele reden. Een model kan zich niet verantwoorden voo {k: 'De mens', v: 'Beslist, redigeert, keurt goed, staat ervoor in.'}, ].map((row) => (
    -
    {row.k}
    +
    {row.k}
    {row.v}
    ))} diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/decidiq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/decidiq.mdx index 8947c2b23..6ce63de55 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/decidiq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/decidiq.mdx @@ -159,7 +159,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {[ {tone: 'var(--c-mint-500)', label: 'AANGENOMEN'}, {tone: 'var(--c-mint-500)', label: 'AANGENOMEN'}, - {tone: 'var(--c-red-vermillion)', label: 'VERWORPEN'}, + {tone: 'var(--conduction-color-status-error)', label: 'VERWORPEN'}, {tone: 'var(--c-cobalt-300)', label: 'INGETROKKEN'}, ].map((row, i) => (
    diff --git a/i18n/nl/docusaurus-plugin-content-pages/privacy.mdx b/i18n/nl/docusaurus-plugin-content-pages/privacy.mdx index 39f0aadb9..427b6af8f 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/privacy.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/privacy.mdx @@ -188,19 +188,19 @@ export const PROMISE_PANELS = {

    Privacybeleid & DPIA, Conduction B.V.

    -
    Standaard
    +
    Standaard
    AVG · EU 2016/679
    -
    Gekoppeld aan
    +
    Gekoppeld aan
    ISO/IEC 27001:2022 ISMS
    -
    Kantoor
    +
    Kantoor
    Lauriergracht 14h, Amsterdam
    -
    Contact
    +
    Contact
    info@conduction.nl
    diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index faf18fb4e..eb0fc98d7 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -11,7 +11,7 @@ export const WORKFLOW_FILE = 'https://github.com/ConductionNL/.github/blob/main/ export function QualityPipelineFlow() { const card = {background: 'var(--conduction-color-surface-card)', border: '1px solid var(--c-cobalt-100)', borderRadius: 8, padding: '14px 14px 16px', boxShadow: '0 1px 3px rgba(33, 70, 139, 0.04)'}; const cardTitle = {fontSize: 12, fontWeight: 700, color: 'var(--conduction-color-text-strong)', marginBottom: 4, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 6}; - const cardSubtitle = {fontSize: 10, fontFamily: 'var(--conduction-typography-font-family-code)', color: 'var(--c-cobalt-500, #6f7da3)', marginBottom: 10, letterSpacing: '0.04em'}; + const cardSubtitle = {fontSize: 10, fontFamily: 'var(--conduction-typography-font-family-code)', color: 'var(--conduction-color-text-muted)', marginBottom: 10, letterSpacing: '0.04em'}; const stageWrap = {background: 'var(--conduction-color-surface-subtle)', border: '1px solid var(--c-cobalt-100)', borderRadius: 12, padding: '18px 18px 22px'}; const stageHead = {display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 14, gap: 12, flexWrap: 'wrap'}; const stageLabel = {fontSize: 10, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--c-orange-knvb)', fontFamily: 'var(--conduction-typography-font-family-code)'}; @@ -185,7 +185,7 @@ export function HandbookPanel({iconPath, title, lines, accent}) { {iconPath} -
    {title}
    +
    {title}
      {lines.map((l, i) => (
    • {l}
    • @@ -241,15 +241,15 @@ export const QualityHeroIllustration = (
      docs.conduction.nl · operationeel handboek - LIVE + LIVE
      pentest-tools.com · dagelijkse scan - SCHOON + SCHOON
      GitHub PR #482 · code- en security-review - GEMERGED + GEMERGED
    @@ -417,19 +417,19 @@ export const QualityHeroIllustration = (

    Kwaliteits- en informatieveiligheidsmanagementsysteem

    -
    Norm
    +
    Norm
    ISO 9001:2015 · ISO/IEC 27001:2022
    -
    Vastgesteld
    +
    Vastgesteld
    januari 2026
    -
    Certificaten
    +
    Certificaten
    24 jul 2025 – 21 jul 2028
    -
    Volgende review
    +
    Volgende review
    januari 2027
    diff --git a/i18n/nl/docusaurus-plugin-content-pages/support.mdx b/i18n/nl/docusaurus-plugin-content-pages/support.mdx index 6df3d3974..5baf2e144 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/support.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/support.mdx @@ -165,7 +165,7 @@ export const partnerLeadTitle = (f) => - GECERTIFICEERD + GECERTIFICEERD

    Gecertificeerd op de apps die je levert.

    Door Conduction gecertificeerd per app. Gezamenlijke go-to-market op aanbestedingen. Hogere SLA op gedeelde klanten. diff --git a/i18n/nl/docusaurus-plugin-content-pages/terms.mdx b/i18n/nl/docusaurus-plugin-content-pages/terms.mdx index 8113d0a6e..80783cdef 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/terms.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/terms.mdx @@ -186,19 +186,19 @@ export const PROMISE_PANELS = {

    [product] (productieomgevingen)

    -
    Standaard
    +
    Standaard
    ISO/IEC 27001-conform
    -
    Uptime
    +
    Uptime
    95% · 24/7
    -
    P1 reactie
    +
    P1 reactie
    2 u / 4 u / 8 u
    -
    Kantooruren
    +
    Kantooruren
    ma–vr 09:00–17:00
    diff --git a/package-lock.json b/package-lock.json index 831eeca26..77f351f8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.46.0", + "@conduction/docusaurus-preset": "^3.47.1", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", @@ -2018,9 +2018,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.46.0", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.46.0.tgz", - "integrity": "sha512-hPX4aDxHzFZMcE0hdoamzvGE/54TP8jYB60bRbQmWy0TqIszsK+1y8zqxKRBeqmK5zbHFsbEvl0JY+/cNpiS7g==", + "version": "3.47.1", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.47.1.tgz", + "integrity": "sha512-BoJRO9zYcaBhNdtw9TA/G5COKWQwx5HmzlMvGgNVScCqsyPvIzw7o7LYC/E8S7jAvT+hlbP1VZ9/w8Yus2Estg==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" diff --git a/package.json b/package.json index a89099e18..512a5a95a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.46.0", + "@conduction/docusaurus-preset": "^3.47.1", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", diff --git a/scripts/a11y-sweep.mjs b/scripts/a11y-sweep.mjs new file mode 100644 index 000000000..f07e83e50 --- /dev/null +++ b/scripts/a11y-sweep.mjs @@ -0,0 +1,198 @@ +#!/usr/bin/env node +/** + * The full mobile and dark-mode review: every route, both locales, four + * widths, both themes. + * + * npm run build + * npx docusaurus serve --port 4300 --no-open & + * node scripts/a11y-sweep.mjs # local build + * SWEEP_BASE=https://www.conduction.nl node scripts/a11y-sweep.mjs + * + * This is not part of CI. `e2e/a11y.spec.js` gates a sample on every run; + * this measures the whole site when something structural moves. + * + * Two things it does that the earlier sweeps did not, both of which changed + * the answer: + * + * - It builds the route list from the BUILD DIRECTORY, not from + * sitemap.xml. The sitemap does not list `/nl/`, so three separate + * "all 163 pages" sweeps measured exactly half the site and reported + * green while /nl/terms/ carried 88 invisible elements. + * - It refuses to start until the checks have been shown to fail. Run + * `npx playwright test a11y-selftest` first. + */ +import {chromium} from '@playwright/test'; +import fs from 'node:fs'; +import path from 'node:path'; + +const BASE = process.env.SWEEP_BASE || 'http://localhost:4300'; +const LANES = Number(process.env.SWEEP_LANES || 6); +const OUT = process.env.SWEEP_OUT || 'a11y-findings.json'; +const BUILD = process.env.SWEEP_BUILD || 'build'; + +const {RUN_CHECKS} = await import('../e2e/a11y-checks.js'); + +/* Every directory in the build that holds an index.html is a route a + visitor can reach. Docusaurus's own machinery (assets, the search index) + has no index.html, so this needs no allow-list. */ +function routes(dir) { + const found = []; + const walk = (d) => { + for (const entry of fs.readdirSync(d, {withFileTypes: true})) { + const full = path.join(d, entry.name); + if (entry.isDirectory()) walk(full); + else if (entry.name === 'index.html') { + const rel = path.relative(BUILD, path.dirname(full)); + found.push(rel === '' ? '/' : `/${rel}/`); + } + } + }; + walk(dir); + return [...new Set(found)].sort(); +} + +/* 320 is the narrowest phone still in real use and the width that actually + breaks layouts; 390 is the audit baseline; 768 is the tablet hinge where + a desktop grid is still in force; 1280 catches desktop-only regressions. + Light runs alongside dark so a finding can be ATTRIBUTED to dark mode + rather than assumed: a contrast miss present in both themes is brand debt + that predates it. */ +const COMBOS = [ + {width: 320, theme: 'dark'}, + {width: 390, theme: 'light'}, + {width: 390, theme: 'dark'}, + {width: 768, theme: 'dark'}, + {width: 1280, theme: 'light'}, + {width: 1280, theme: 'dark'}, +]; + +/* The footer stylesheet is injected lazily; measured before it lands the + footer reports its unstyled metrics and invents target-size findings. */ +const footerReady = () => { + const f = document.querySelector('.canal-footer'); + if (!f) return true; + const link = [...document.querySelectorAll('link[rel="stylesheet"]')].find((l) => + l.href.includes('canal-footer'), + ); + if (!link) return false; + try { + if (!link.sheet || link.sheet.cssRules.length === 0) return false; + } catch { + if (!link.sheet) return false; + } + const go = f.querySelector('.game-over'); + return !(go && getComputedStyle(go).display !== 'none'); +}; + +const all = routes(BUILD); +const findings = []; +const errors = []; +const redirects = []; +let done = 0; + +async function lane(browser, slice) { + const pages = []; + for (const combo of COMBOS) { + const ctx = await browser.newContext({ + viewport: {width: combo.width, height: combo.width < 700 ? 844 : 900}, + deviceScaleFactor: combo.width < 700 ? 3 : 1, + isMobile: combo.width < 700, + hasTouch: combo.width < 700, + }); + pages.push({combo, ctx, page: await ctx.newPage()}); + } + for (const url of slice) { + for (const {combo, page} of pages) { + let ok = false; + for (let attempt = 1; attempt <= 3 && !ok; attempt++) { + try { + const resp = await page.goto(BASE + url, {waitUntil: 'networkidle', timeout: 45000}); + if (!resp || resp.status() >= 400) throw new Error(`HTTP ${resp && resp.status()}`); + /* 118 of these routes are redirect stubs: a meta-refresh plus a + location assignment. The browser follows them, so without this + check the sweep measures whatever it lands on and files the + findings under the stub's URL. Off site that means reporting + another domain's defects as ours (/openconnector/ redirects to + openconnector.conduction.nl, which this repo does not control); + on site it means measuring the target twice and attributing one + copy to the wrong page. Either way the stub itself has no + content to check. */ + const landed = page.url(); + if (!landed.startsWith(BASE) || new URL(landed).pathname !== url) { + redirects.push({from: url, to: landed}); + ok = true; + break; + } + await page.waitForFunction(footerReady, null, {timeout: 20000}); + for (const f of await page.evaluate(RUN_CHECKS, {theme: combo.theme})) { + findings.push({url, width: combo.width, theme: combo.theme, ...f}); + } + ok = true; + } catch (e) { + if (attempt === 3) { + errors.push({url, combo, error: String(e).slice(0, 140)}); + console.log(`ERROR ${url} ${combo.width}/${combo.theme}: ${String(e).slice(0, 80)}`); + } else { + await new Promise((r) => setTimeout(r, 1500 * attempt)); + } + } + } + } + done++; + if (done % 25 === 0) console.log(`PROGRESS ${done}/${all.length} routes, ${findings.length} findings`); + } + for (const p of pages) await p.ctx.close(); +} + +console.log(`sweeping ${all.length} routes x ${COMBOS.length} combos = ${all.length * COMBOS.length} loads against ${BASE}`); +const browser = await chromium.launch(); +await Promise.all( + Array.from({length: LANES}, (_, i) => lane(browser, all.filter((_, j) => j % LANES === i))), +); +await browser.close(); + +/* A defect seen at six combos is one defect. Attribution: a contrast miss + that also occurs in light is not something dark mode broke; a layout + defect that also occurs at 1280 is not a mobile defect. */ +const uniq = new Map(); +for (const f of findings) { + const k = `${f.url}|${f.check}|${f.text}`; + if (!uniq.has(k)) uniq.set(k, {...f, themes: new Set(), widths: new Set()}); + uniq.get(k).themes.add(f.theme); + uniq.get(k).widths.add(f.width); +} +const defects = [...uniq.values()].map((f) => ({ + ...f, + themes: [...f.themes], + widths: [...f.widths], + bucket: f.check.includes('contrast') + ? (f.themes.has('dark') && !f.themes.has('light') ? 'dark-only' : 'both-themes') + : (f.widths.has(1280) ? 'all-widths' : 'narrow-only'), +})); + +fs.writeFileSync(OUT, JSON.stringify({defects, errors, redirects}, null, 1)); + +const pagesOf = (l) => new Set(l.map((f) => f.url)).size; +console.log(''); +const stubRoutes = new Set(redirects.map((r) => r.from)).size; +console.log(`SWEPT ${done - stubRoutes} routes with content, skipped ${stubRoutes} redirect stubs, ${errors.length} load failures`); +console.log(`raw ${findings.length} -> distinct defects ${defects.length}`); +console.log(''); +console.log('check defects pages en/nl attribution'); +const byCheck = {}; +for (const f of defects) (byCheck[f.check] = byCheck[f.check] || []).push(f); +for (const [check, sub] of Object.entries(byCheck).sort((a, b) => b[1].length - a[1].length)) { + const en = sub.filter((f) => !f.url.startsWith('/nl/')).length; + const b = {}; + for (const f of sub) b[f.bucket] = (b[f.bucket] || 0) + 1; + console.log( + `${check.padEnd(22)} ${String(sub.length).padStart(7)} ${String(pagesOf(sub)).padStart(6)} ${String(en).padStart(4)}/${String(sub.length - en).padEnd(4)} ` + + Object.entries(b).map(([k, v]) => `${k}:${v}`).join(' '), + ); +} +const crit = defects.filter((f) => f.severity === 'critical'); +console.log(''); +console.log(`=== critical: ${crit.length} on ${pagesOf(crit)} pages ===`); +for (const f of crit.slice(0, 40)) console.log(` ${f.url} ${f.check} "${f.text}" ${f.detail}`); +console.log(''); +console.log(`SWEEP EXIT=${crit.length || errors.length ? 1 : 0}`); diff --git a/src/css/site.css b/src/css/site.css index 8fafb989c..f314b5b1c 100644 --- a/src/css/site.css +++ b/src/css/site.css @@ -92,7 +92,7 @@ font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; - color: var(--c-cobalt-500); + color: var(--conduction-color-text-muted); margin-top: 2px; } .cg-layer-meta p { @@ -222,6 +222,16 @@ blockquote { .pull-quote cite .cite-name { color: var(--conduction-color-text-strong); } .pull-quote.hero { + /* `hero` here is meant as a modifier of .pull-quote, but it is also a bare + Infima class: `.hero { display: flex; align-items: center; padding: 4rem + 2rem }`. The blockquote inherits all of it, which turns it into a flex + row, so the becomes a flex item sitting BESIDE the quote instead + of under it. Measured at 320px on the platform-moment post: the citation + started at x=259 and ran to x=336, putting the author's own name off the + right edge of a 320px viewport with no way to scroll to it. + `display: block` is enough because .pull-quote.hero outranks .hero, and + the padding and font-size below already override the rest. */ + display: block; font-size: 28px; font-style: normal; padding: var(--space-7) var(--space-8); @@ -631,3 +641,23 @@ body { min-height: 44px; } } + +/** + * The blog archive's own title was invisible. + * + * `/academy/archive/` and `/nl/academy/archive/` are generated by the blog + * plugin, not by an MDX page of ours, so they use Infima's `hero hero--primary` + * rather than any Conduction component. That bar is filled with the primary + * colour and its text colour is set to white, which the subtitle picks up. + * The `h1` does not: it takes `--ifm-heading-color`, which is also the primary + * cobalt. Measured 2026-09-19: rgb(33, 70, 139) on rgb(33, 70, 139), a + * contrast ratio of exactly 1.00. The page title was the same colour as the + * bar behind it, in both themes and both locales, and had been since the + * archive page existed. + * + * `inherit` rather than a named colour, so the title follows whatever ink the + * hero sets for itself and cannot drift out of step with it again. + */ +.hero--primary .hero__title { + color: inherit; +} diff --git a/src/pages/ai.mdx b/src/pages/ai.mdx index b110b41ef..5bb9f7cdd 100644 --- a/src/pages/ai.mdx +++ b/src/pages/ai.mdx @@ -27,7 +27,7 @@ export const IbmQuote = () => (

    “A computer can never be held accountable, therefore a computer must never make a management decision.”

    -
    +
    IBM training manual, 1979
    @@ -132,7 +132,7 @@ Accountability is the whole reason. A model cannot answer for a claim. It cannot {k: 'The human', v: 'Decides, edits, approves, answers for it.'}, ].map((row) => (
    -
    {row.k}
    +
    {row.k}
    {row.v}
    ))} diff --git a/src/pages/apps/decidiq.mdx b/src/pages/apps/decidiq.mdx index f269f97c9..2dc039aaa 100644 --- a/src/pages/apps/decidiq.mdx +++ b/src/pages/apps/decidiq.mdx @@ -159,7 +159,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; {[ {tone: 'var(--c-mint-500)', label: 'ADOPTED'}, {tone: 'var(--c-mint-500)', label: 'ADOPTED'}, - {tone: 'var(--c-red-vermillion)', label: 'REJECTED'}, + {tone: 'var(--conduction-color-status-error)', label: 'REJECTED'}, {tone: 'var(--c-cobalt-300)', label: 'WITHDRAWN'}, ].map((row, i) => (
    diff --git a/src/pages/arcade.mdx b/src/pages/arcade.mdx index 0bf82b2d3..be73db1a8 100644 --- a/src/pages/arcade.mdx +++ b/src/pages/arcade.mdx @@ -57,7 +57,7 @@ export const ArcadeIcon = ( .arcade-prose .arcade-table td:first-child { white-space: nowrap; } .arcade-prose .hashtag { font-family: var(--conduction-typography-font-family-code); - color: var(--c-blue-cobalt); + color: var(--conduction-color-text-default); } `} diff --git a/src/pages/privacy.mdx b/src/pages/privacy.mdx index 64ed01c0f..bbfa606a6 100644 --- a/src/pages/privacy.mdx +++ b/src/pages/privacy.mdx @@ -188,19 +188,19 @@ export const PROMISE_PANELS = {

    Privacy policy & DPIA, Conduction B.V.

    -
    Standard
    +
    Standard
    GDPR · EU 2016/679
    -
    Paired with
    +
    Paired with
    ISO/IEC 27001:2022 ISMS
    -
    Office
    +
    Office
    Lauriergracht 14h, Amsterdam
    -
    Contact
    +
    Contact
    info@conduction.nl
    diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index ee02b5f2d..1e9121d9f 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -11,7 +11,7 @@ export const WORKFLOW_FILE = 'https://github.com/ConductionNL/.github/blob/main/ export function QualityPipelineFlow() { const card = {background: 'var(--conduction-color-surface-card)', border: '1px solid var(--c-cobalt-100)', borderRadius: 8, padding: '14px 14px 16px', boxShadow: '0 1px 3px rgba(33, 70, 139, 0.04)'}; const cardTitle = {fontSize: 12, fontWeight: 700, color: 'var(--conduction-color-text-strong)', marginBottom: 4, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 6}; - const cardSubtitle = {fontSize: 10, fontFamily: 'var(--conduction-typography-font-family-code)', color: 'var(--c-cobalt-500, #6f7da3)', marginBottom: 10, letterSpacing: '0.04em'}; + const cardSubtitle = {fontSize: 10, fontFamily: 'var(--conduction-typography-font-family-code)', color: 'var(--conduction-color-text-muted)', marginBottom: 10, letterSpacing: '0.04em'}; const stageWrap = {background: 'var(--conduction-color-surface-subtle)', border: '1px solid var(--c-cobalt-100)', borderRadius: 12, padding: '18px 18px 22px'}; const stageHead = {display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 14, gap: 12, flexWrap: 'wrap'}; const stageLabel = {fontSize: 10, fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--c-orange-knvb)', fontFamily: 'var(--conduction-typography-font-family-code)'}; @@ -185,7 +185,7 @@ export function HandbookPanel({iconPath, title, lines, accent}) { {iconPath} -
    {title}
    +
    {title}
      {lines.map((l, i) => (
    • {l}
    • @@ -241,15 +241,15 @@ export const QualityHeroIllustration = (
      docs.conduction.nl · operating handbook - LIVE + LIVE
      pentest-tools.com · daily scan - CLEAN + CLEAN
      GitHub PR #482 · code + security review - MERGED + MERGED
    @@ -417,19 +417,19 @@ export const QualityHeroIllustration = (

    Quality and information-security management system

    -
    Standard
    +
    Standard
    ISO 9001:2015 · ISO/IEC 27001:2022
    -
    Adopted
    +
    Adopted
    January 2026
    -
    Certificates
    +
    Certificates
    24 Jul 2025 – 21 Jul 2028
    -
    Next review
    +
    Next review
    January 2027
    diff --git a/src/pages/support.mdx b/src/pages/support.mdx index 52fce13c4..1f81458f0 100644 --- a/src/pages/support.mdx +++ b/src/pages/support.mdx @@ -40,7 +40,7 @@ export function PartnerDirectoryBlock() { export const TIER_PILL = { host: {background: 'var(--conduction-color-surface-subtle)', color: 'var(--conduction-color-text-body)'}, - service: {background: 'var(--c-gold-500)', color: 'var(--conduction-color-text-strong)'}, + service: {background: 'var(--c-gold-500)', color: 'var(--c-cobalt-900)'}, certified: {background: 'var(--conduction-color-surface-card)', color: 'var(--conduction-color-text-default)'}, }; export const TIER_CARD_CERTIFIED = { diff --git a/src/pages/terms.mdx b/src/pages/terms.mdx index 97b2937db..af1c470cb 100644 --- a/src/pages/terms.mdx +++ b/src/pages/terms.mdx @@ -186,19 +186,19 @@ export const PROMISE_PANELS = {

    [product] (production environments)

    -
    Standard
    +
    Standard
    ISO/IEC 27001 aligned
    -
    Uptime
    +
    Uptime
    95% · 24/7
    -
    P1 response
    +
    P1 response
    2 h / 4 h / 8 h
    -
    Office hours
    +
    Office hours
    Mon–Fri 09:00–17:00
    From 7836d7a0d1592c3fffeab61504ee0090056027b3 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 20 Sep 2026 03:09:35 +0200 Subject: [PATCH 297/330] test(a11y): match the navbar drawer by id suffix, not by its exact id (#216) Preset 3.47.1 carries design-system PR #77, a navbar drawer hotfix that renamed the drawer from navbar-drawer to conduction-navbar-drawer. The drawer itself is fine; the selectors pinned to the old id are not. Two mobile tests went red on development looking for button[aria-controls="navbar-drawer"], which no longer exists. Matching on the suffix means the tests keep working across that rename and the next one, and a drawer that genuinely disappears still fails them. The same stale id was in a11y-checks.js, where it guards against counting the links inside a closed drawer. That one was not producing wrong numbers, because the closed drawer is display:none via the hidden attribute and checkVisibility() already rejects it, but the guard should still say what it means. My own mistake to record: after bumping the preset I ran only the a11y specs locally, not the full suite, so CI found this rather than I did. 273 of 273 pass locally now. --- e2e/a11y-checks.js | 4 ++-- e2e/mobile-layout.spec.js | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/e2e/a11y-checks.js b/e2e/a11y-checks.js index 7f6e1c6c1..883623388 100644 --- a/e2e/a11y-checks.js +++ b/e2e/a11y-checks.js @@ -229,7 +229,7 @@ export const RUN_CHECKS = async ({theme}) => { const isMedia = el.matches('img, svg, video, canvas, picture, table, pre'); if (!isControl && !isMedia && text.length < 8) continue; if (SKIP.includes((el.textContent || '').trim())) continue; - if (el.closest && el.closest('#navbar-drawer[hidden]')) continue; + if (el.closest && el.closest('[id$="navbar-drawer"][hidden]')) continue; const s = getComputedStyle(el); if (!seen(el, s)) continue; const r = el.getBoundingClientRect(); @@ -261,7 +261,7 @@ export const RUN_CHECKS = async ({theme}) => { for (const el of els.filter((e) => e.matches && e.matches(CONTROL))) { const s = getComputedStyle(el); if (!seen(el, s)) continue; - if (el.closest && el.closest('#navbar-drawer[hidden]')) continue; + if (el.closest && el.closest('[id$="navbar-drawer"][hidden]')) continue; if (SKIP.includes((el.textContent || '').trim())) continue; const r = el.getBoundingClientRect(); if (!r.width || !r.height) continue; diff --git a/e2e/mobile-layout.spec.js b/e2e/mobile-layout.spec.js index 03426e1fe..c22c1430e 100644 --- a/e2e/mobile-layout.spec.js +++ b/e2e/mobile-layout.spec.js @@ -121,7 +121,7 @@ async function offscreenControls(page) { const out = []; const selector = 'a[href], button, input, select, textarea, [role="button"]'; for (const el of document.querySelectorAll(selector)) { - if (el.closest('#navbar-drawer[hidden]')) continue; + if (el.closest('[id$="navbar-drawer"][hidden]')) continue; if (['Skip to main content', 'Ga naar hoofdinhoud'].includes((el.textContent || '').trim())) continue; const style = getComputedStyle(el); if (style.visibility === 'hidden' || style.display === 'none') continue; @@ -181,19 +181,19 @@ for (const [name, path] of PAGES) { test('the navbar drawer opens and exposes every navigation item', async ({page}) => { await page.goto('/'); - const toggle = page.locator('button[aria-controls="navbar-drawer"]'); + const toggle = page.locator('button[aria-controls$="navbar-drawer"]'); await expect(toggle, 'the drawer toggle should be visible on a phone').toBeVisible(); await expect(toggle).toHaveAttribute('aria-expanded', 'false'); /* Closed: the drawer must be out of the accessibility tree, not just visually hidden, or its links stay in the tab order behind the page. */ - await expect(page.locator('#navbar-drawer')).toBeHidden(); + await expect(page.locator('[id$="navbar-drawer"]')).toBeHidden(); await toggle.click(); await expect(toggle).toHaveAttribute('aria-expanded', 'true'); - const drawer = page.locator('#navbar-drawer'); + const drawer = page.locator('[id$="navbar-drawer"]'); await expect(drawer).toBeVisible(); /* Support and About are the two items that used to fall off the bar, @@ -210,10 +210,10 @@ test('the navbar drawer opens and exposes every navigation item', async ({page}) test('the drawer closes when a link inside it is followed', async ({page}) => { await page.goto('/'); - await page.locator('button[aria-controls="navbar-drawer"]').click(); - await page.locator('#navbar-drawer').getByRole('link', {name: 'About'}).click(); + await page.locator('button[aria-controls$="navbar-drawer"]').click(); + await page.locator('[id$="navbar-drawer"]').getByRole('link', {name: 'About'}).click(); await expect(page).toHaveURL(/\/about\/?$/); - await expect(page.locator('#navbar-drawer')).toBeHidden(); + await expect(page.locator('[id$="navbar-drawer"]')).toBeHidden(); }); test('every footer link sits inside the viewport', async ({page}) => { @@ -289,7 +289,7 @@ for (const path of ['/apps/', '/support/', '/']) { const small = await page.evaluate((min) => { const out = []; for (const el of document.querySelectorAll('a[href], button, input, select, [role="button"]')) { - if (el.closest('#navbar-drawer[hidden]')) continue; + if (el.closest('[id$="navbar-drawer"][hidden]')) continue; if (['Skip to main content', 'Ga naar hoofdinhoud'].includes((el.textContent || '').trim())) continue; const style = getComputedStyle(el); if (style.visibility === 'hidden' || style.display === 'none') continue; From aaaf4a6f4d613eee82a21663a7e9fbbc4216e8dd Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Sun, 20 Sep 2026 06:27:26 +0200 Subject: [PATCH 298/330] feat(lint): ink and surface must both follow the theme, or both be fixed (#217) The durable half of the dark-mode work. The runtime sweep finds contrast failures after they are built; this finds the authoring mistake on the line where it is written. One invariant, checked per declaration block and per JSX style object: a semantic token has a value per theme, a raw palette token, hex or named colour has one value forever, and mixing them means one theme is always wrong. Which theme depends on which half you fixed. A block with fixed ink and no surface of its own is flagged too, because it inherits a surface that is free to flip: that is how 32 declarations of color: 'var(--c-cobalt-500, #6f7da3)' came to render at 1.73:1 in dark. It found four defects the 661-route runtime sweep could not. Two badge rows on /quality/ and their Dutch twins set a fixed mint-300 fill with theme-aware ink, which is 1.82:1 in dark and 9.79:1 in light. The sweep was right to miss them: they sit inside collapsed accordions, so checkVisibility() excludes them and no reader sees them until they open one. They are fixed here, and they are the case for having both layers. A static check reaches what is behind an interaction; a runtime sweep reaches what is composed at runtime. Two tiers, because a check that reports 167 things on the day it lands gets switched off: BLOCKING one half follows the theme and the other cannot. Wrong by construction, no judgement needed. Currently 0. ADVISORY fixed ink with no surface of its own. Often fine (a terminal pane, a permanently-cobalt hero), sometimes the real bug. Currently 163 across 72 files, printed and not failed. Work it down with --strict and mark the deliberate ones with an a11y-fixed-surface comment, which is a phrase rather than a pragma so `git grep` lists every such claim. `npm run lint` and `npm run lint:colors` run it; postbuild runs it beside the AI-baseline validator so CI exercises it on every build. `--self-test` proves it: four cases that must fire and four correct patterns that must stay quiet. The MDX case was silent at first because a JS object separates declarations with commas, not semicolons, so the background's value swallowed the rest of the object and a mixed block looked consistent. It splits on paren depth now. Verified: build green, 273 of 273 e2e pass. --- .../quality.mdx | 4 +- package.json | 6 +- scripts/lint-color-roles.mjs | 272 ++++++++++++++++++ src/pages/quality.mdx | 4 +- 4 files changed, 280 insertions(+), 6 deletions(-) create mode 100644 scripts/lint-color-roles.mjs diff --git a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx index eb0fc98d7..d9471c9ce 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/quality.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/quality.mdx @@ -349,7 +349,7 @@ export const QualityHeroIllustration = ( ].map((row, i) => (
    0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> {row.label} - {row.state} + {row.state}
    ))}
    @@ -373,7 +373,7 @@ export const QualityHeroIllustration = ( ].map((row, i) => (
    0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> {row.step} - {row.tag} + {row.tag}
    ))}
    diff --git a/package.json b/package.json index 512a5a95a..9c4b9f506 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "preview": "docusaurus build && docusaurus serve", "prebuild": "python3 .github/scripts/app_downloads.py && node scripts/sync-app-downloads.js", "build": "docusaurus build", - "postbuild": "node scripts/validate-ai-baseline.mjs", + "postbuild": "node scripts/validate-ai-baseline.mjs && node scripts/lint-color-roles.mjs", "validate:ai-baseline": "node scripts/validate-ai-baseline.mjs", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", @@ -22,7 +22,9 @@ "write-heading-ids": "docusaurus write-heading-ids", "ci": "npm ci --legacy-peer-deps && npm run build", "test:e2e": "playwright test", - "test:e2e:ui": "playwright test --ui" + "test:e2e:ui": "playwright test --ui", + "lint:colors": "node scripts/lint-color-roles.mjs", + "lint": "node scripts/lint-color-roles.mjs" }, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", diff --git a/scripts/lint-color-roles.mjs b/scripts/lint-color-roles.mjs new file mode 100644 index 000000000..c45c159c2 --- /dev/null +++ b/scripts/lint-color-roles.mjs @@ -0,0 +1,272 @@ +#!/usr/bin/env node +/** + * scripts/lint-color-roles.mjs + * + * Catch, at authoring time, the colour mistake that a runtime sweep can only + * catch after it ships. + * + * THE INVARIANT + * + * Within one declaration block or inline style object, ink and surface must + * be either BOTH theme-aware or BOTH fixed. Never one of each. + * + * Why that is the right line. A semantic token (`--conduction-color-*`) has a + * different value per theme; a raw palette token (`--c-*`), a hex or a named + * colour has one value forever. Mixing them means one theme is always wrong, + * and which theme is wrong depends on which half you fixed: + * + * fixed surface + theme-aware ink the ink flips out from under a surface + * that cannot follow. Measured 2026-09-20: + * white on gold at 2.58:1 and white on + * mint at 3.62:1, on 23 pages. + * theme-aware surface + fixed ink the classic dark-mode failure: the + * surface goes dark under navy text. + * 1,362 elements under 1.5:1 in the first + * audit. + * + * A block with a fixed ink and NO surface of its own is also flagged, because + * it inherits whatever surface an ancestor provides and that surface is free + * to flip. That is how 32 declarations of + * `color: 'var(--c-cobalt-500, #6f7da3)'` came to render at 1.73:1 in dark. + * If the ancestor's surface really is fixed, say so with an opt-out. + * + * OPT-OUT + * + * Put `a11y-fixed-surface` in a comment on the line above, or anywhere in + * the same JSX style object. Use it where an ancestor paints a surface that + * cannot flip, e.g. text inside a permanently-cobalt hero. It is deliberately + * a phrase and not a bare eslint-style pragma so it reads as a claim someone + * made, and `git grep a11y-fixed-surface` lists every such claim. + * + * WHAT THIS DOES NOT DO + * + * It cannot know a token's contrast against a surface set three components + * up. That is what `scripts/a11y-sweep.mjs` and `e2e/a11y.spec.js` are for. + * This is the cheap half: the half a person can fix while writing the line. + * + * npm run lint:colors check + * npm run lint:colors -- --self-test prove the checks can fail + */ + +import fs from 'node:fs'; +import path from 'node:path'; + +const ROOTS = ['src', 'i18n', 'academy']; +const EXTS = new Set(['.css', '.mdx', '.jsx', '.js']); +const OPT_OUT = 'a11y-fixed-surface'; + +/** A colour value is theme-aware only if it comes from the semantic layer. */ +const isThemeAware = (v) => /var\(\s*--conduction-color-/.test(v); +/** Fixed: a raw palette token, a hex, a named colour, or an rgb()/hsl() literal. */ +const isFixed = (v) => + /var\(\s*--c-/.test(v) || + /#[0-9a-fA-F]{3,8}\b/.test(v) || + /\b(?:rgba?|hsla?)\s*\(/.test(v) || + /\b(?:white|black)\b/.test(v); +/* `transparent`, `inherit`, `currentColor` and `none` are neither: they defer + to something else, which is exactly the safe thing to do. */ +const isDeferred = (v) => /\b(?:transparent|inherit|currentColor|unset|revert|initial|none)\b/i.test(v); + +/** + * Split a block body into declarations. + * + * CSS separates with `;`, a JS style object with `,`, and both allow commas + * INSIDE a value (`rgba(1,2,3)`, `linear-gradient(...)`). Splitting a JS + * object on a naive regex made the background's value swallow the rest of the + * object, including the colour, so a mixed block looked consistent and the + * MDX self-test case stayed silent. Walk the string instead, tracking paren + * depth and quotes. + */ +function declarations(body, sep) { + const out = []; + let buf = ''; + let depth = 0; + let quote = null; + for (const ch of body) { + if (quote) { + if (ch === quote) quote = null; + buf += ch; + continue; + } + if (ch === '"' || ch === "'" || ch === '`') { quote = ch; buf += ch; continue; } + if (ch === '(') depth++; + if (ch === ')') depth--; + if (ch === sep && depth === 0) { out.push(buf); buf = ''; continue; } + buf += ch; + } + out.push(buf); + return out; +} + +/* `color` but not `border-color`, `background-color`, `outline-color`, and in + a JS object also `backgroundColor`. The property boundary matters: `color:` + is a substring of `border-color:`, which silently rewrote 58 declarations + during an earlier migration. */ +const INK = /(?:^|[^-\w.])(?:color)\s*:\s*(.+)$/is; +const SURFACE = /(?:^|[^-\w.])(?:background|background-color|backgroundColor)\s*:\s*(.+)$/is; + +function roleValues(body, sep) { + let ink; + let surface; + for (const raw of declarations(body, sep)) { + const d = raw.trim(); + if (!d) continue; + const i = d.match(INK); + if (i && ink === undefined) ink = i[1].trim().replace(/^['"`]|['"`],?$/g, '').trim(); + const s2 = d.match(SURFACE); + if (s2 && surface === undefined) surface = s2[1].trim().replace(/^['"`]|['"`],?$/g, '').trim(); + } + return {ink, surface}; +} + +/** + * Split a file into candidate blocks: CSS rule bodies and JSX style objects. + * Crude on purpose. A missed block is a missed warning, never a wrong one, + * and the runtime sweep is the backstop. + */ +function blocks(text, file) { + const out = []; + if (file.endsWith('.css')) { + for (const m of text.matchAll(/\{([^{}]*)\}/g)) { + out.push({body: m[1], index: m.index}); + } + } else { + /* JSX inline styles: style={{ ... }} and bare object literals that carry + a colour role, e.g. `const pill = {background: ..., color: ...}`. */ + for (const m of text.matchAll(/\{\{([^{}]*)\}\}|=\s*\{([^{}]*)\}/g)) { + out.push({body: m[1] ?? m[2] ?? '', index: m.index}); + } + } + return out; +} + +const lineOf = (text, index) => text.slice(0, index).split('\n').length; + +function check(file) { + const text = fs.readFileSync(file, 'utf8'); + const lines = text.split('\n'); + const findings = []; + for (const {body, index} of blocks(text, file)) { + if (body.includes(OPT_OUT)) continue; + const line = lineOf(text, index); + /* An opt-out on the line above the block, or on the block's own line. */ + const near = [lines[line - 2], lines[line - 1]].filter(Boolean).join('\n'); + if (near.includes(OPT_OUT)) continue; + + const {ink, surface} = roleValues(body, file.endsWith('.css') ? ';' : ','); + if (!ink || isDeferred(ink)) continue; + + const inkAware = isThemeAware(ink); + const inkFixed = !inkAware && isFixed(ink); + if (!inkAware && !inkFixed) continue; + + if (surface && !isDeferred(surface)) { + const surfAware = isThemeAware(surface); + const surfFixed = !surfAware && isFixed(surface); + if (inkAware && surfFixed) { + findings.push({file, line, kind: 'ink-flips-surface-cannot', + detail: `color ${ink} follows the theme, background ${surface} cannot`}); + } else if (inkFixed && surfAware) { + findings.push({file, line, kind: 'surface-flips-ink-cannot', + detail: `background ${surface} follows the theme, color ${ink} cannot`}); + } + } else if (inkFixed) { + findings.push({file, line, kind: 'fixed-ink-inherited-surface', + detail: `color ${ink} cannot follow the theme and this block sets no surface of its own`}); + } + } + return findings; +} + +function walk(dir, acc = []) { + if (!fs.existsSync(dir)) return acc; + for (const e of fs.readdirSync(dir, {withFileTypes: true})) { + const full = path.join(dir, e.name); + if (e.isDirectory()) walk(full, acc); + else if (EXTS.has(path.extname(e.name))) acc.push(full); + } + return acc; +} + +/* ---------------- self-test: every rule must be able to fire ------------- */ +if (process.argv.includes('--self-test')) { + const tmp = fs.mkdtempSync('/tmp/lint-color-roles-'); + const cases = [ + ['ink-flips-surface-cannot', 'a.css', '.x { background: var(--c-gold-500); color: var(--conduction-color-text-strong); }'], + ['surface-flips-ink-cannot', 'b.css', '.y { background: var(--conduction-color-surface-card); color: var(--c-cobalt-500); }'], + ['fixed-ink-inherited-surface', 'c.css', '.z { color: var(--c-cobalt-500, #6f7da3); }'], + ['ink-flips-surface-cannot', 'd.mdx', 'x'], + ]; + let bad = 0; + for (const [kind, name, src] of cases) { + const f = path.join(tmp, name); + fs.writeFileSync(f, src); + const hit = check(f).some((x) => x.kind === kind); + console.log(`${hit ? 'FIRED ' : 'SILENT'} ${kind.padEnd(30)} ${name}`); + if (!hit) bad++; + } + /* Negative controls: correct code must stay quiet, or the rule is noise. */ + const quiet = [ + ['both fixed', 'e.css', '.a { background: var(--c-gold-500); color: var(--c-cobalt-900); }'], + ['both theme-aware', 'f.css', '.b { background: var(--conduction-color-surface-card); color: var(--conduction-color-text-body); }'], + ['inherit defers', 'g.css', '.c { color: inherit; }'], + ['opted out', 'h.css', '/* a11y-fixed-surface: the hero is cobalt in both themes */\n.d { color: var(--c-cobalt-100); }'], + ]; + for (const [label, name, src] of quiet) { + const f = path.join(tmp, name); + fs.writeFileSync(f, src); + const n = check(f).length; + console.log(`${n === 0 ? 'quiet ' : 'NOISY '} ${label.padEnd(30)} ${name}${n ? ' -> ' + JSON.stringify(check(f)) : ''}`); + if (n !== 0) bad++; + } + fs.rmSync(tmp, {recursive: true, force: true}); + console.log(''); + console.log(`SELF-TEST ${bad === 0 ? 'PASS' : 'FAIL'}`); + process.exit(bad === 0 ? 0 : 1); +} + +const STRICT = process.argv.includes('--strict'); +/* Two tiers, because a check that reports 167 things on the day it lands gets + switched off. + + BLOCKING is the pair that is wrong by construction: one half of the block + follows the theme and the other cannot, so one theme is always broken. No + judgement needed, and no false positives in practice. + + ADVISORY is fixed ink with no surface of its own. Often fine (a terminal + pane, a permanently-cobalt hero), sometimes the exact bug that put 32 + declarations at 1.73:1 in dark. It needs a person to look at the ancestor, + so it prints and does not fail. Work the list down with `--strict`, and + mark the deliberate ones with an a11y-fixed-surface comment. */ +const BLOCKING = new Set(['ink-flips-surface-cannot', 'surface-flips-ink-cannot']); + +const files = ROOTS.flatMap((r) => walk(r)); +const findings = files.flatMap(check); +const blocking = findings.filter((f) => BLOCKING.has(f.kind)); +const advisory = findings.filter((f) => !BLOCKING.has(f.kind)); + +const report = (title, list, cap) => { + if (!list.length) return; + const byKind = {}; + for (const f of list) (byKind[f.kind] = byKind[f.kind] || []).push(f); + console.log(`\n${title}`); + for (const [kind, sub] of Object.entries(byKind)) { + console.log(` ${kind}: ${sub.length} on ${new Set(sub.map((f) => f.file)).size} file(s)`); + for (const f of sub.slice(0, cap)) console.log(` ${f.file}:${f.line} ${f.detail}`); + if (sub.length > cap) console.log(` ... and ${sub.length - cap} more`); + } +}; + +report('BLOCKING - one half follows the theme, the other cannot:', blocking, 40); +report(STRICT ? 'ALSO BLOCKING (--strict):' : 'ADVISORY - fixed ink, surface comes from an ancestor:', advisory, STRICT ? 40 : 5); + +console.log(''); +const fail = blocking.length + (STRICT ? advisory.length : 0); +if (fail) { + console.error(`${fail} colour-role problem(s) across ${new Set([...blocking, ...(STRICT ? advisory : [])].map((f) => f.file)).size} file(s).`); + console.error('Ink and surface must both follow the theme or both be fixed. Where an'); + console.error(`ancestor paints a surface that cannot flip, say so with an "${OPT_OUT}" comment.`); + process.exit(1); +} +console.log(`No blocking colour-role problems in ${files.length} files.` + + (advisory.length ? ` ${advisory.length} advisory, run with --strict to see them all.` : '')); diff --git a/src/pages/quality.mdx b/src/pages/quality.mdx index 1e9121d9f..0f4b76e66 100644 --- a/src/pages/quality.mdx +++ b/src/pages/quality.mdx @@ -349,7 +349,7 @@ export const QualityHeroIllustration = ( ].map((row, i) => (
    0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> {row.label} - {row.state} + {row.state}
    ))}
    @@ -373,7 +373,7 @@ export const QualityHeroIllustration = ( ].map((row, i) => (
    0 ? '1px solid var(--c-cobalt-100)' : 'none'}}> {row.step} - {row.tag} + {row.tag}
    ))}
    From 04b36a5d42fa5d3bf6782b888971d6199f990436 Mon Sep 17 00:00:00 2001 From: Remko Date: Sun, 20 Sep 2026 14:53:28 +0200 Subject: [PATCH 299/330] feat(about): take the team cards from the design system EmployeeCard and TeamGrid now ship in @conduction/docusaurus-preset, so the local copy under src/components/EmployeeCard is redundant. Both about pages import them from the preset instead. The preset's card also accepts `photoDark`, a second cut of each portrait for dark mode: the team is drawn with a cobalt accent for light grounds and an orange one for dark, and the hex behind the drawing is the same brand blue either way. All eight people get both cuts, rendered through ThemedImage so the pair follows the chosen colour mode rather than the operating system, and so neither cut flashes on load. The FontAwesome packages go with the local component. The preset's card draws its contact icons as inline SVG, so nothing imports them any more. --- .../docusaurus-plugin-content-pages/about.mdx | 11 +- package-lock.json | 62 ------ package.json | 4 - src/components/EmployeeCard/ICONS.md | 66 ------ src/components/EmployeeCard/index.jsx | 154 -------------- src/components/EmployeeCard/styles.module.css | 163 --------------- src/pages/about.mdx | 11 +- static/img/team/barry-orange.svg | 84 ++++++++ static/img/team/marleen-orange.svg | 71 +++++++ static/img/team/remco-orange.svg | 188 ++++++++++++++++++ static/img/team/remko-orange.svg | 137 +++++++++++++ static/img/team/robert-orange.svg | 116 +++++++++++ static/img/team/ruben-orange.svg | 109 ++++++++++ static/img/team/thijn-orange.svg | 67 +++++++ static/img/team/wilco-orange.svg | 82 ++++++++ 15 files changed, 872 insertions(+), 453 deletions(-) delete mode 100644 src/components/EmployeeCard/ICONS.md delete mode 100644 src/components/EmployeeCard/index.jsx delete mode 100644 src/components/EmployeeCard/styles.module.css create mode 100644 static/img/team/barry-orange.svg create mode 100644 static/img/team/marleen-orange.svg create mode 100644 static/img/team/remco-orange.svg create mode 100644 static/img/team/remko-orange.svg create mode 100644 static/img/team/robert-orange.svg create mode 100644 static/img/team/ruben-orange.svg create mode 100644 static/img/team/thijn-orange.svg create mode 100644 static/img/team/wilco-orange.svg diff --git a/i18n/nl/docusaurus-plugin-content-pages/about.mdx b/i18n/nl/docusaurus-plugin-content-pages/about.mdx index 535f9729b..6bf41525e 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/about.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/about.mdx @@ -4,8 +4,7 @@ description: Conduction is een klein Nederlands open-source productbedrijf. Twaa hide_table_of_contents: true --- -import {Section, SectionHead, FeatureList, FeatureItem, Hero} from '@conduction/docusaurus-preset/components'; -import EmployeeCard, {TeamGrid} from '@site/src/components/EmployeeCard'; +import {Section, SectionHead, EmployeeCard, TeamGrid, FeatureList, FeatureItem, Hero} from '@conduction/docusaurus-preset/components'; import PageHeading from '@site/src/components/PageHeading'; Techniek die mensen dient. Open source, gewoon Nederlands. @@ -29,6 +28,7 @@ import PageHeading from '@site/src/components/PageHeading'; =20.0" } }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.3.1.tgz", - "integrity": "sha512-k0C0sdHmZtAo6dRDtd1Z/qcpyHbL0CKsjV8seMY/21xGhY5Wsv0XRmiI/xEEH4y2c9b1+jvgNs/3EqhV27yUEA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.3.1.tgz", - "integrity": "sha512-BoxVN3PKnMbgStHhjoaky/oWdxHomDqmBVA24IA3KEmssFGeI7u9YT/BJceOjIum/t6TpPa/vcMKaVYQeIQ/3Q==", - "license": "MIT", - "dependencies": { - "@fortawesome/fontawesome-common-types": "7.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-brands-svg-icons": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-7.3.1.tgz", - "integrity": "sha512-8eiNzycuFhy3mZsi5EVO8JpO3H8l7/kSCl4Gk4iUew9hJuzYFqAAD6kqgeEup3+YZ9hL+5cjjorGzrRLkYuURg==", - "license": "(CC-BY-4.0 AND MIT)", - "dependencies": { - "@fortawesome/fontawesome-common-types": "7.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.3.1.tgz", - "integrity": "sha512-v0BLa0eqg7ubvVWeNSHVBs8fWH/GJicERZoJaxJ3FE/lj67VSqzoMg9pzfZVOfLMX10y0pGwQAuxoRVVH2patg==", - "license": "(CC-BY-4.0 AND MIT)", - "dependencies": { - "@fortawesome/fontawesome-common-types": "7.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/react-fontawesome": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-3.5.0.tgz", - "integrity": "sha512-63mlRr6fiBbJ0wjr1Cf6dsDGtP2lNvk9lnatKgxs/fIkhslsZT291hIUzJkuUkI9yr69ZvWnWfgb2qXm4QyVaA==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~6 || ~7", - "react": "^18.0.0 || ^19.0.0" - } - }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", diff --git a/package.json b/package.json index b5d68222f..9c4b9f506 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,6 @@ "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", - "@fortawesome/fontawesome-svg-core": "^7.3.1", - "@fortawesome/free-brands-svg-icons": "^7.3.1", - "@fortawesome/free-solid-svg-icons": "^7.3.1", - "@fortawesome/react-fontawesome": "^3.5.0", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", diff --git a/src/components/EmployeeCard/ICONS.md b/src/components/EmployeeCard/ICONS.md deleted file mode 100644 index af0ffdfd4..000000000 --- a/src/components/EmployeeCard/ICONS.md +++ /dev/null @@ -1,66 +0,0 @@ -# Contact icons — attribution and colour rules - -All six contact icons on `/about` come from **Font Awesome Free 7**, installed -from npm. This repo hosts no brand-owned artwork. - -``` -@fortawesome/free-solid-svg-icons faEnvelope, faPhone -@fortawesome/free-brands-svg-icons faGithub, faLinkedin, faBluesky, faMastodon -@fortawesome/react-fontawesome -@fortawesome/fontawesome-svg-core -``` - -## Attribution (required) - -Font Awesome Free icons are licensed -[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) by Fonticons, Inc. — -. **That licence requires attribution, and this file -is it.** Keep it with the code. The packages themselves are -`(CC-BY-4.0 AND MIT)`: CC BY 4.0 covers the icons, MIT the code around them. - -Font Awesome's own licence adds, of the brand glyphs among them: - -> All brand icons are trademarks of their respective owners. The use of these -> trademarks does not indicate endorsement of the trademark holder by Font -> Awesome, nor vice versa. Please do not use brand logos for any purpose -> except to represent the company, product, or service to which they refer. - -Linking to a person's profile on that service is exactly that use. - -## Why every icon is black - -Four of the six are trademarks, and each owner restricts the colours their -mark may take: - -| Mark | Colours the owner permits | -| --- | --- | -| GitHub — | "should only appear in white, black, or in few cases grey or green" | -| LinkedIn — | "three color variations: blue, black, and white" | -| Bluesky — | official blue, black, white; "Recolor the logo (other than the approved black and white variants)" is a Don't | -| Mastodon — | "Do not change or modify the Mastodon marks" | - -**Black is the only colour all four permit.** Cobalt, which this row used to -be, is permitted by none of them. - -`mail` and `phone` are under no such constraint. They are black so the row -reads as one decision rather than two colours by accident. - -The hover chip lifts to cobalt-100 rather than filling with cobalt, so the -marks stay legible without any of them having to change colour. - -## Two notes for whoever touches this next - -**Linking to a profile is the permitted use, and it is what we do.** Bluesky -states it outright — their -[Trademark Policy](https://bsky.social/about/support/trademarks) §4.1 permits -the butterfly as a social-media icon linking to a profile with no prior -approval. GitHub, LinkedIn and Mastodon have no equivalent named carve-out; we -rely on nominative use, which is the ordinary basis every site linking to a -social profile relies on. - -**We deliberately do not host their logo files.** Earlier versions of this -component shipped Bluesky's and Mastodon's own SVGs from `static/img/social/`. -That was defensible, but taking all six from one npm package means we -redistribute nothing and have no provenance to defend. If you ever reintroduce -an official file — for fidelity, say — re-read each brand's policy first and -record the source here. diff --git a/src/components/EmployeeCard/index.jsx b/src/components/EmployeeCard/index.jsx deleted file mode 100644 index 8923a4128..000000000 --- a/src/components/EmployeeCard/index.jsx +++ /dev/null @@ -1,154 +0,0 @@ -/** - * — site-local, temporarily. - * - * Same API and markup as @conduction/docusaurus-preset's EmployeeCard. The - * only difference is the contact-icon set: the published preset (3.27.1) - * carries four hand-drawn glyphs and no way to add a fifth, so /about could - * not show Bluesky or Mastodon at all — an unknown icon name falls back to - * rendering the label as text. - * - * DELETE THIS once the site depends on a preset that carries the full set, - * and point both about.mdx imports back at the preset. - * - * All six icons come from Font Awesome Free 7 via npm, so this repo hosts no - * brand-owned artwork and redistributes nothing that is not ours to pass on. - * The icons are CC BY 4.0; the attribution that licence requires lives in - * ICONS.md beside this file and must stay with it. - * - * Everything renders black. Four of the six are trademarks and every owner - * restricts the colours their mark may take — black is the only value all - * four permit. See ICONS.md for each policy, quoted. - */ - -import React from 'react'; -import useBaseUrl from '@docusaurus/useBaseUrl'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {faEnvelope, faPhone} from '@fortawesome/free-solid-svg-icons'; -import {faGithub, faLinkedin, faBluesky, faMastodon} from '@fortawesome/free-brands-svg-icons'; -import styles from './styles.module.css'; - -const ICONS = { - mail: faEnvelope, - phone: faPhone, - github: faGithub, - linkedin: faLinkedin, - bluesky: faBluesky, - mastodon: faMastodon, -}; - -function Icon({name, label}) { - if (!ICONS[name]) return label || null; - return ; -} - -/* Every contact link leaves the site, so it opens in a new tab — except - `mailto:` and `tel:`, which hand off to another app and would leave an - empty tab behind. `rel` is required with `target="_blank"`: without it the - opened page gets a `window.opener` handle back into ours. */ -const externalProps = (href = '') => - /^https?:\/\//i.test(href) ? {target: '_blank', rel: 'noopener noreferrer'} : {}; - -export function TeamGrid({columns = 3, children, className}) { - const composed = [styles.grid, styles['cols-' + columns], className].filter(Boolean).join(' '); - return
    {children}
    ; -} - -export default function EmployeeCard({ - variant = 'compact', - name, - role, - initials, - photo, - avatarColor, - bio, - apps = [], - links = [], - className, -}) { - /* Call sites pass root-absolute photo paths (`/img/team/x.svg`). Those only - resolve while baseUrl is `/`: `docusaurus start --locale nl` serves the - whole site under `/nl/`, and an un-prefixed path then misses the static - mount and comes back as the SPA's HTML fallback — a broken avatar on - every Dutch card. useBaseUrl is a no-op in production, where baseUrl is - `/` for both locales. */ - const photoUrl = useBaseUrl(photo || '/'); - - const avatar = (cls) => ( -
    - {photo ? {name} : initials} -
    - ); - - if (variant === 'photo') { - return ( -
    - {avatar(styles.avatarLarge)} - {name &&
    {name}
    } - {role &&
    {role}
    } - {bio &&

    {bio}

    } - {links.length > 0 && ( -
    - {links.map((l, i) => ( - - - - ))} -
    - )} -
    - ); - } - - if (variant === 'detail') { - return ( -
    - {avatar(styles.avatarLarge)} -
    - {name &&
    {name}
    } - {role &&
    {role}
    } -
    - {bio &&

    {bio}

    } - {apps.length > 0 && ( -
    -
    Apps I work on
    -
    - {apps.map((a, i) => {a})} -
    -
    - )} - {links.length > 0 && ( -
    - {links.map((l, i) => ( - {l.label} - ))} -
    - )} -
    - ); - } - - /* default: compact */ - const Tag = links.length > 0 && links[0].href ? 'a' : 'div'; - return ( - - {avatar(styles.avatar)} -
    - {name &&
    {name}
    } - {role &&
    {role}
    } -
    - {links.length > 0 && ( -
    - {links.map((l, i) => ( - - - - ))} -
    - )} -
    - ); -} diff --git a/src/components/EmployeeCard/styles.module.css b/src/components/EmployeeCard/styles.module.css deleted file mode 100644 index 48cdfedda..000000000 --- a/src/components/EmployeeCard/styles.module.css +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Styles for the site-local . Copied from the preset, with - * two deliberate differences — both forced by the icons being files rather - * than inline SVG. - * - * 1. Contact icons are locked to black. Four of the six are trademarks whose - * owners permit only black or white, and black is the one value all four - * allow. See ICONS.md. - * - * 2. Because they never invert, the hover chip no longer fills with cobalt - * (which would leave a black mark on a dark ground). It lifts to - * cobalt-100 instead — the same affordance, still legible, and no icon - * has to change colour to make it work. - */ -/** - * styles. Mirrors employee-cards.css scoped per variant. - */ - -.grid { display: grid; gap: 16px; font-family: var(--conduction-typography-font-family-body); } -.cols-2 { grid-template-columns: repeat(2, 1fr); } -.cols-3 { grid-template-columns: repeat(3, 1fr); } -.cols-4 { grid-template-columns: repeat(4, 1fr); } -@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } } - -/* avatar shapes shared across variants */ -.avatar, .avatarLarge { - clip-path: var(--hex-pointy-top); - display: inline-flex; - align-items: center; - justify-content: center; - color: white; - font-weight: 700; - flex-shrink: 0; - letter-spacing: -0.01em; - overflow: hidden; - background: var(--c-blue-cobalt); - font-family: var(--conduction-typography-font-family-body); -} -.avatar { width: 44px; height: 50px; font-size: 16px; } -.avatarLarge { width: 72px; height: 83px; font-size: 24px; } -.avatar img, .avatarLarge img { width: 100%; height: 100%; object-fit: cover; display: block; } - -/* Compact variant */ -.cardCompact { - background: white; - border: 1px solid var(--c-cobalt-100); - border-radius: var(--radius-lg); - padding: 20px; - display: flex; - align-items: center; - gap: 14px; - box-shadow: var(--shadow-1); - transition: all 160ms ease; - text-decoration: none; - color: inherit; -} -.cardCompact:hover { - transform: translateY(-1px); - box-shadow: var(--shadow-2); - border-color: var(--c-cobalt-200); - text-decoration: none; - color: inherit; -} -.info { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; } -.name { font-size: 15px; font-weight: 600; color: var(--c-blue-cobalt); line-height: 1.2; } -.role { font-family: var(--conduction-typography-font-family-code); font-size: 11px; color: var(--c-cobalt-400); letter-spacing: 0.05em; } -.linksRow { display: flex; gap: 8px; flex-shrink: 0; } -.linksRow a { - width: 26px; height: 26px; - border-radius: var(--radius-sm); - background: var(--c-cobalt-50); - color: var(--c-cobalt-700); - display: inline-flex; align-items: center; justify-content: center; - transition: all 140ms ease; -} -.linksRow a:hover { background: var(--c-cobalt-100); color: var(--c-cobalt-900); } - -/* Photo variant */ -.cardPhoto { - background: white; - border: 1px solid var(--c-cobalt-100); - border-radius: var(--radius-lg); - padding: 28px 24px; - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - gap: 12px; - box-shadow: var(--shadow-1); - font-family: var(--conduction-typography-font-family-body); -} -.cardPhoto .name { font-size: 19px; font-weight: 700; color: var(--c-cobalt-900); } -.cardPhoto .role { font-size: 13px; color: var(--c-cobalt-700); font-family: var(--conduction-typography-font-family-body); letter-spacing: 0; max-width: 28ch; } -.bio { font-size: 14px; color: var(--c-cobalt-700); line-height: 1.6; margin: 8px 0 0; max-width: 36ch; } - -.contacts { display: flex; gap: 10px; margin-top: 10px; } -.contacts a { - width: 32px; height: 32px; - border-radius: var(--radius-sm); - background: var(--c-cobalt-50); - color: var(--c-cobalt-700); - display: inline-flex; align-items: center; justify-content: center; - transition: all 140ms ease; -} -.contacts a:hover { background: var(--c-cobalt-100); color: var(--c-cobalt-900); } - -/* Detail variant */ -.cardDetail { - background: white; - border: 1px solid var(--c-cobalt-100); - border-radius: var(--radius-lg); - padding: 32px; - box-shadow: var(--shadow-1); - display: flex; - flex-direction: column; - gap: 16px; - position: relative; - overflow: hidden; - font-family: var(--conduction-typography-font-family-body); -} -.cardDetail::before { - content: ""; - position: absolute; - top: 0; right: 0; - width: 140px; height: 160px; - clip-path: var(--hex-pointy-top); - background: var(--c-cobalt-50); - transform: translate(40px, -50px); - z-index: 0; -} -.cardDetail > * { position: relative; z-index: 1; } -.cardDetail .name { font-size: 20px; font-weight: 700; color: var(--c-cobalt-900); } -.cardDetail .role { font-size: 13px; } -.appsLabel { - font-family: var(--conduction-typography-font-family-code); - font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; - color: var(--c-cobalt-400); - margin-bottom: 8px; -} -.appsList { display: flex; gap: 6px; flex-wrap: wrap; } -.appPill { - background: var(--c-cobalt-50); - color: var(--c-cobalt-700); - padding: 4px 10px; - border-radius: var(--radius-pill); - font-size: 12px; - font-family: var(--conduction-typography-font-family-code); -} -.contactsInline { display: flex; flex-wrap: wrap; gap: 16px; } -.contactsInline a { - display: inline-flex; align-items: center; gap: 6px; - font-size: 13px; color: var(--c-cobalt-700); - text-decoration: none; -} -.contactsInline a:hover { color: var(--c-blue-cobalt); } - -/* One size per variant. Font Awesome renders , so - colour is what sets the fill. Literal #000 rather than a token: four of - these six are trademarks whose owners permit only black or white, and a - palette change must not be able to recolour them. See ICONS.md. */ -.contacts .icon { width: 16px; height: 16px; display: block; color: #000000; } -.linksRow .icon { width: 13px; height: 13px; display: block; color: #000000; } -.contactsInline .icon { width: 14px; height: 14px; display: block; color: #000000; } diff --git a/src/pages/about.mdx b/src/pages/about.mdx index 628055e37..f3a42126c 100644 --- a/src/pages/about.mdx +++ b/src/pages/about.mdx @@ -5,8 +5,7 @@ hide_table_of_contents: true --- import Link from '@docusaurus/Link'; -import {Section, SectionHead, FeatureList, FeatureItem, Hero} from '@conduction/docusaurus-preset/components'; -import EmployeeCard, {TeamGrid} from '@site/src/components/EmployeeCard'; +import {Section, SectionHead, EmployeeCard, TeamGrid, FeatureList, FeatureItem, Hero} from '@conduction/docusaurus-preset/components'; import PageHeading from '@site/src/components/PageHeading'; Tech to serve people. Open-source, plain Dutch. @@ -30,6 +29,7 @@ import PageHeading from '@site/src/components/PageHeading'; + diff --git a/static/img/team/marleen-orange.svg b/static/img/team/marleen-orange.svg new file mode 100644 index 000000000..4e50f61d2 --- /dev/null +++ b/static/img/team/marleen-orange.svg @@ -0,0 +1,71 @@ + + diff --git a/static/img/team/remco-orange.svg b/static/img/team/remco-orange.svg new file mode 100644 index 000000000..f2e0e115c --- /dev/null +++ b/static/img/team/remco-orange.svg @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/team/remko-orange.svg b/static/img/team/remko-orange.svg new file mode 100644 index 000000000..f961daafd --- /dev/null +++ b/static/img/team/remko-orange.svg @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/team/robert-orange.svg b/static/img/team/robert-orange.svg new file mode 100644 index 000000000..21a0f0d7b --- /dev/null +++ b/static/img/team/robert-orange.svg @@ -0,0 +1,116 @@ + + diff --git a/static/img/team/ruben-orange.svg b/static/img/team/ruben-orange.svg new file mode 100644 index 000000000..7b5e7ecb3 --- /dev/null +++ b/static/img/team/ruben-orange.svg @@ -0,0 +1,109 @@ + + diff --git a/static/img/team/thijn-orange.svg b/static/img/team/thijn-orange.svg new file mode 100644 index 000000000..f3bd449b0 --- /dev/null +++ b/static/img/team/thijn-orange.svg @@ -0,0 +1,67 @@ + + diff --git a/static/img/team/wilco-orange.svg b/static/img/team/wilco-orange.svg new file mode 100644 index 000000000..f1e38445f --- /dev/null +++ b/static/img/team/wilco-orange.svg @@ -0,0 +1,82 @@ + + From d2b37e7b58c9855b6ce516ce8c4c01f111cc497d Mon Sep 17 00:00:00 2001 From: Remko Date: Sun, 20 Sep 2026 14:53:36 +0200 Subject: [PATCH 300/330] fix(about): drop the cobalt ring around the team portraits The card masks its avatar to a hexagon and paints cobalt behind it, on the assumption that `photo` is bare artwork on transparency. The team SVGs are not that: each one bakes in its own background shape, and where that shape falls short of the mask the cobalt showed through as a sliver down the side. Both cuts carry the same shape, so it appeared in light mode exactly as in dark. Only the fill is dropped, and only where the avatar holds an image. The mask and the 72x83 and 44x50 slots keep the preset's values, so a portrait stays the same size as an initials hexagon beside it, and the initials-only cards keep the coloured fill they need. Sizing from the artwork instead was tried and reverted: the drawn extent differs per file, and two of them draw through a group transform, so no single scale lines them up. --- src/css/site.css | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/css/site.css b/src/css/site.css index f314b5b1c..638a4dea3 100644 --- a/src/css/site.css +++ b/src/css/site.css @@ -661,3 +661,44 @@ body { .hero--primary .hero__title { color: inherit; } + +/* ============================================================ + EmployeeCard portraits · kill the cobalt ring, keep the size + ============================================================ + The preset clips the avatar to a pointy-top hexagon and paints + --c-blue-cobalt behind it, assuming `photo` is bare artwork on + transparency. The team SVGs in static/img/team/ are not that: each one + bakes in its own background shape. Where that shape does not reach the + mask's edge, the cobalt ground shows through as a blue sliver down the + side. Both cuts carry the same baked shape, so light mode shows it just + as dark mode does -- it was never a dark-mode bug. + + Only the ground is dropped, and only where the avatar actually holds an + image. The mask and the 72x83 / 44x50 slots stay exactly as the preset + sets them, so a portrait is the same size as an initials hexagon beside + it. An earlier version here also removed the mask and switched to + `contain`: that rendered the artwork whole, but at the size of whatever + padding each file happens to carry, which made the portraits visibly + smaller than the initials cards and differ from one another. + + The padding really is per-file -- measured over the path geometry in a + 512 viewBox: remko 440x508, thijn 430x496, barry 373x430, and ruben and + marleen draw through a group transform, so no single scale factor could + line them all up. That is why the fix is to let the mask define the + silhouette rather than the artwork. + + :has(img) keeps this off the initials-only cards, which still need the + coloured fill -- including the three on /contact, which pass `initials` + and no `photo`. The selectors match the preset's CSS-module classes + (webpack emits [local]_[contenthash]); nothing in Infima is named + avatarLarge, and the plain `avatar_` form is narrowed to a div so + Infima's own .avatar / .avatar__photo blog-author classes cannot match. + + REMOVE THIS BLOCK once the team SVGs are re-exported as bare art on + transparency, which is the real fix: then the mask alone draws the + hexagon and nothing has to be neutralised here. + ============================================================ */ +div[class*='avatarLarge_']:has(img), +div[class*='avatar_']:has(img) { + background: transparent; +} From 5c9956be234578a2fa678b4e5da5703914eed654 Mon Sep 17 00:00:00 2001 From: Remko Date: Sun, 20 Sep 2026 14:53:49 +0200 Subject: [PATCH 301/330] fix(navbar): let the bar follow the colour mode The swizzle styled everything with raw palette values -- cobalt-50 through cobalt-700, and a literal white bar. Those do not change between themes, so the navbar stayed light while the rest of the site went dark. Every surface and text declaration now uses the semantic tokens the preset's own navbar uses, which are redefined under [data-theme='dark']. Three cases still need an explicit dark value, because surface-subtle and surface-card resolve to the same cobalt-800 there and a chip painted in it would be invisible: the locale hover, the dropdown rows and the version pill lift to surface-sunken, and the brand marks go white. The wordmark also renders through ThemedImage when logo.srcDark is set. createConfig() has always declared both cuts, but the swizzle read only logo.src, so the light-ground mark sat on the dark bar. `display: block` moves onto img.wordmarkIcon:only-of-type: ThemedImage emits both cuts during SSR and hides one by class, which a blanket class rule outranks, leaving both marks stacked until hydration. The locale dropdown takes its panel from preview/components/top-navbar .html -- card surface, cobalt hairline, shadow-2 -- rather than Infima's default 0 5px 40px black blur, which read as a grey halo on the light bar and vanished on the dark one. Its rows hover on background, not colour: orange stays reserved for "you are here" on the section links. The locale trigger gains its light-mode hover chip, which only ever had a dark-mode half. --- src/theme/Navbar/index.jsx | 35 ++++++-- src/theme/Navbar/styles.module.css | 126 ++++++++++++++++++++++------- 2 files changed, 127 insertions(+), 34 deletions(-) diff --git a/src/theme/Navbar/index.jsx b/src/theme/Navbar/index.jsx index 382834380..e3eee8978 100644 --- a/src/theme/Navbar/index.jsx +++ b/src/theme/Navbar/index.jsx @@ -41,6 +41,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; import {useThemeConfig} from '@docusaurus/theme-common'; import {translate} from '@docusaurus/Translate'; import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem'; +import ThemedImage from '@theme/ThemedImage'; import styles from './styles.module.css'; /** @@ -308,6 +309,8 @@ export default function Navbar() { the current page's path and 404s on every sub-route. */ const logoSrcRaw = navbar.logo?.src; const logoSrc = useBaseUrl(logoSrcRaw || ''); + const logoSrcDarkRaw = navbar.logo?.srcDark; + const logoSrcDark = useBaseUrl(logoSrcDarkRaw || ''); const logoAlt = navbar.logo?.alt || translate( {id: 'preset.navbar.logoAlt', message: '{title} avatar', description: 'Default alt text for the navbar logo. {title} is the site title.'}, {title: navbar.title}, @@ -327,14 +330,32 @@ export default function Navbar() { const drawerActions = rightItems.filter(i => !META_TYPES.has(i.type)); const drawerMeta = rightItems.filter(i => META_TYPES.has(i.type)); + /* `logo.srcDark` is Docusaurus' own convention and createConfig() has + always set it, but this swizzle only ever read `src` — so the dark + mark the preset declares has never rendered, and the light-ground + avatar sat on the dark bar. ThemedImage follows `data-theme` rather + than the OS, so the pair still holds wherever the colour mode is + chosen rather than inherited, and it emits both sources during SSR + with one hidden by class — see the `:only-of-type` rule in the + stylesheet, which keeps that hiding from being overridden. */ const logo = logoSrcRaw ? ( - {logoAlt} + logoSrcDarkRaw ? ( + + ) : ( + {logoAlt} + ) ) : null; return ( diff --git a/src/theme/Navbar/styles.module.css b/src/theme/Navbar/styles.module.css index 3721746b9..2e3208831 100644 --- a/src/theme/Navbar/styles.module.css +++ b/src/theme/Navbar/styles.module.css @@ -12,7 +12,7 @@ justify-content: space-between; padding: 22px 56px; border-bottom: 1px solid var(--c-cobalt-100); - background: white; + background: var(--conduction-color-surface-card); position: sticky; top: 0; z-index: 100; @@ -32,10 +32,10 @@ font-size: 22px; font-weight: 700; letter-spacing: -0.02em; - color: var(--c-blue-cobalt); + color: var(--conduction-color-text-default); text-decoration: none; } -.wordmark:hover { color: var(--c-blue-cobalt); text-decoration: none; } +.wordmark:hover { color: var(--conduction-color-text-default); text-decoration: none; } /* App-glyph hex next to the wordmark. Every product navbar shows the app's icon (sourced from navbar.logo); sized to match the wordmark's @@ -43,10 +43,17 @@ .wordmarkIcon { width: 32px; height: 32px; - display: block; flex-shrink: 0; object-fit: contain; } +/* `display: block` only when the wordmark holds one image. With + `logo.srcDark` set, ThemedImage puts both cuts here and hides one with + its own `.themedComponent { display: none }` — a single class selector + that a blanket `.wordmarkIcon { display: block }` would outrank, leaving + both marks stacked in the bar until hydration. */ +img.wordmarkIcon:only-of-type { + display: block; +} /* Wordmark text wrapper; gives us a single hook for tweaks like line-height alignment without touching the parent flex container. */ @@ -60,7 +67,7 @@ so "OpenRegister" reads as muted-"Open" + bold-"Register" and the eye lands on the noun, not the brand prefix. */ .wordmarkPrefix { - color: var(--c-cobalt-400); + color: var(--conduction-color-text-muted); font-weight: 400; } @@ -70,7 +77,7 @@ } .link { - color: var(--c-cobalt-700); + color: var(--conduction-color-text-body); text-decoration: none; font-size: 14px; font-weight: 500; @@ -92,13 +99,13 @@ } .ghost { - color: var(--c-cobalt-700); + color: var(--conduction-color-text-body); text-decoration: none; font-size: 14px; font-weight: 500; } .ghost:hover { - color: var(--c-orange-knvb); + color: var(--conduction-color-text-body); text-decoration: none; } @@ -124,12 +131,37 @@ font-size: 14px; } .localeWrapper :global(.navbar__link) { - color: var(--c-cobalt-700); + color: var(--conduction-color-text-body); font-weight: 500; padding: 0 8px; } .localeWrapper :global(.navbar__link:hover) { - color: var(--c-orange-knvb); + color: var(--conduction-color-text-body); +} +/* The hover chip. Only a dark-mode rule existed, so light had no hover + background at all; this is the light half of the same pair. Dark keeps + its own override below because `surface-subtle` resolves to the same + cobalt-800 as the bar there, which would show nothing. */ +.localeWrapper:hover { + background: var(--conduction-color-surface-subtle); + border-radius: var(--radius-sm); +} +/* The dropdown panel, per preview/components/top-navbar.html (.locale-menu): + card surface, a cobalt-100 hairline, and --shadow-2. Infima's default is + `0 5px 40px rgba(0,0,0,0.2)` — a 40px black blur that reads as a grey + halo on the light bar and vanishes on the dark one. --shadow-2 is + `0 2px 8px rgba(33,70,139,0.10)`: cobalt-tinted and tight, so it sits + the same in both themes. */ +.localeWrapper :global(.dropdown__menu) { + background: var(--conduction-color-surface-card); + border: 1px solid var(--c-cobalt-100); + box-shadow: var(--shadow-2); +} +/* Menu rows: background hover, not an orange one. Orange is reserved for + "you are here" / "you are about to go here" on the section links. */ +.localeWrapper :global(.dropdown__link:hover) { + background: var(--conduction-color-surface-subtle); + color: var(--conduction-color-text-default); } /* Inline SVG slot used by `github` and `apiDocs` items. The icon @@ -157,14 +189,14 @@ width: 34px; height: 34px; border-radius: var(--radius-sm); - color: var(--c-cobalt-700); + color: var(--conduction-color-text-body); font-size: 18px; text-decoration: none; transition: color 160ms ease, background 160ms ease; } .iconLink:hover { - color: var(--c-blue-cobalt); - background: var(--c-cobalt-50); + color: var(--conduction-color-text-default); + background: var(--conduction-color-surface-subtle); text-decoration: none; } @@ -188,8 +220,8 @@ font-family: var(--conduction-typography-font-family-code); font-size: 11px; letter-spacing: 0.04em; - color: var(--c-cobalt-400); - background: var(--c-cobalt-50); + color: var(--conduction-color-text-muted); + background: var(--conduction-color-surface-subtle); border-radius: var(--radius-pill); padding: 4px 10px; white-space: nowrap; @@ -215,7 +247,7 @@ border: 0; border-radius: var(--radius-sm); background: transparent; - color: var(--c-cobalt-700); + color: var(--conduction-color-text-body); font-size: 24px; cursor: pointer; transition: color 160ms ease, background 160ms ease; @@ -225,8 +257,8 @@ -webkit-tap-highlight-color: transparent; } .burger:hover { - color: var(--c-blue-cobalt); - background: var(--c-cobalt-50); + color: var(--conduction-color-text-default); + background: var(--conduction-color-surface-subtle); } .burgerGlyph { display: inline-flex; @@ -247,7 +279,7 @@ z-index: 200; display: flex; flex-direction: column; - background: white; + background: var(--conduction-color-surface-card); overflow-y: auto; overscroll-behavior: contain; font-family: var(--conduction-typography-font-family-body); @@ -271,7 +303,7 @@ justify-content: space-between; gap: 16px; padding: 14px 20px; - border-bottom: 1px solid var(--c-cobalt-100); + border-bottom: 1px solid var(--conduction-color-surface-sunken); flex-shrink: 0; } @@ -294,8 +326,8 @@ align-items: center; gap: 10px; padding: 16px 0; - border-bottom: 1px solid var(--c-cobalt-100); - color: var(--c-cobalt-700); + border-bottom: 1px solid var(--conduction-color-surface-sunken); + color: var(--conduction-color-text-body); font-size: 18px; font-weight: 600; letter-spacing: -0.01em; @@ -346,9 +378,9 @@ justify-content: center; gap: 8px; padding: 13px 20px; - border: 1px solid var(--c-cobalt-100); + border: 1px solid var(--conduction-color-border-default); border-radius: var(--radius-md); - color: var(--c-cobalt-700); + color: var(--conduction-color-text-body); font-size: 16px; font-weight: 600; text-decoration: none; @@ -369,14 +401,14 @@ gap: 12px; margin-top: 28px; padding-top: 20px; - border-top: 1px solid var(--c-cobalt-100); + border-top: 1px solid var(--conduction-color-surface-sunken); } .drawerMetaLink { display: inline-flex; align-items: center; gap: 8px; - color: var(--c-cobalt-400); + color: var(--conduction-color-text-muted); font-size: 14px; font-weight: 500; text-decoration: none; @@ -396,7 +428,7 @@ } .drawerLocale :global(.menu__link) { padding: 6px 0; - color: var(--c-cobalt-700); + color: var(--conduction-color-text-body); font-size: 14px; font-weight: 500; background: transparent; @@ -440,3 +472,43 @@ @media (min-width: 901px) { .drawer { display: none; } } + +/* Dark mode, carried over from the preset's own navbar (3.47.1). + + Everything above resolves through the semantic tokens, which already + flip under [data-theme='dark'], so the bar, the drawer and the text + follow the theme on their own. These three are the exceptions the + preset also had to spell out: + + - the locale dropdown's hover fill needs to lift rather than tint, + because surface-subtle and surface-card resolve to the same cobalt + in dark and the hover would read as no hover at all; + - the brand marks are literal black in the icon row, so a palette + change cannot reach a trademark -- in dark they go literal white + for the same reason; + - the version pill needs the same lift as the locale hover. */ +[data-theme='dark'] .localeWrapper:hover, +[data-theme='dark'] .localeWrapper :global(.dropdown__link:hover) { + background: var(--conduction-color-surface-sunken); +} + +[data-theme='dark'] .iconLink svg { + fill: #FFFFFF; +} + +[data-theme='dark'] .versionPill { + background: var(--conduction-color-surface-sunken); +} + +/* The icon row and the burger get a visible hover again. + + Their resting chip is `surface-subtle`, which in dark resolves to + --c-cobalt-800 -- the same value as `surface-card`, the bar behind + them. That is 1.00:1: no chip at all, so hovering them did nothing + visible. Lifting to `surface-sunken` (--c-cobalt-700) is the same one + step the preset already applies to .localeWrapper:hover and + .versionPill, so the whole right-hand cluster now reacts alike. */ +[data-theme='dark'] .iconLink:hover, +[data-theme='dark'] .burger:hover { + background: var(--conduction-color-surface-sunken); +} From 71e152571c0de97c03ba3d9be740d97941a781ae Mon Sep 17 00:00:00 2001 From: Remko Date: Sun, 20 Sep 2026 22:08:24 +0200 Subject: [PATCH 302/330] revert(navbar): drop the swizzle, the preset ships the drawer The swizzle carried a mobile drawer that @conduction/docusaurus-preset 3.37.0 did not have, and its own header set the condition for removing it: delete the directory once the site moves to a preset release that includes the navbar drawer. 3.47.1 ships the hamburger and the full-screen drawer, so that condition is met. Dropping it also drops three fixes that were only needed because the copy had drifted from upstream: the raw palette values that kept the bar light while the rest of the site went dark, the wordmark reading only logo.src so the light-ground mark sat on a dark bar, and the locale dropdown falling back to Infima's own panel. The preset's navbar does all three, and renders the custom-github, custom-apiDocs and custom-versionPill item types the copy fell through on. The drawer breakpoint moves from 900px to the preset's 996px. --- src/theme/Navbar/index.jsx | 457 ------------------------- src/theme/Navbar/styles.module.css | 514 ----------------------------- 2 files changed, 971 deletions(-) delete mode 100644 src/theme/Navbar/index.jsx delete mode 100644 src/theme/Navbar/styles.module.css diff --git a/src/theme/Navbar/index.jsx b/src/theme/Navbar/index.jsx deleted file mode 100644 index e3eee8978..000000000 --- a/src/theme/Navbar/index.jsx +++ /dev/null @@ -1,457 +0,0 @@ -/** - * TEMPORARY Navbar swizzle — mobile navigation. - * - * @conduction/docusaurus-preset 3.37.0 ships a navbar with no mobile - * treatment at all: below 900px its own stylesheet only sets - * `flex-wrap: wrap`, so all five section links, the locale dropdown - * and the Install CTA reflow onto stacked rows on a phone (its CSS - * carries the matching "TODO: hamburger menu" note). The fix lives - * upstream in design-system/docusaurus-preset; this file carries it on - * conduction.nl until a preset release with the mobile navbar is - * published and this site is bumped to it. - * - * DELETE THIS DIRECTORY when package.json moves to the preset version - * that includes the navbar drawer. Nothing here is site-specific by - * intent — it is the upstream component, minus the pieces that cannot - * cross the package boundary (see below). - * - * Differences from the upstream component, forced by the preset's - * `exports` map — `src/theme/brand.jsx` and the icon primitives are - * not exported subpaths, so they cannot be imported from a consuming - * site: - * - * • SUB_BRANDS (ConNext / Common Ground wordmarks) is inlined below. - * • The hamburger and close glyphs are inlined below. - * • The `custom-github`, `custom-apiDocs` and `custom-versionPill` - * item types are NOT handled: they need the preset's icon set and - * this site's navbar config uses none of them. An item of those - * types falls through to the plain-link branch. If one is ever - * added to docusaurus.config.js while this swizzle is in place, - * that is the reason it renders bare. - * - * Everything else — desktop layout, wordmark rules, home-link - * behaviour, active states — is byte-for-byte the upstream logic, so - * removing this directory restores exactly what the preset renders. - */ - -import React, {useCallback, useEffect, useRef, useState} from 'react'; -import Link from '@docusaurus/Link'; -import {useLocation} from '@docusaurus/router'; -import useBaseUrl from '@docusaurus/useBaseUrl'; -import {useThemeConfig} from '@docusaurus/theme-common'; -import {translate} from '@docusaurus/Translate'; -import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem'; -import ThemedImage from '@theme/ThemedImage'; -import styles from './styles.module.css'; - -/** - * Width at which the navbar swaps the inline link row for the - * hamburger + drawer. Kept in sync by hand with the `@media` block in - * styles.module.css — the JS needs it too, to close a drawer that is - * still open when the viewport grows back past the breakpoint, which - * would otherwise leave a full-screen overlay stuck over a desktop - * layout with no visible way out. - */ -const MOBILE_BREAKPOINT = 900; - -/** - * Sub-brand wordmarks. Mirrors the preset's src/theme/brand.jsx — the - * hub hosts ConNext and Common Ground vanity sections at /connext and - * /commonground (plus their /nl twins), and the navbar wordmark - * switches to the sub-brand's mark while the visitor is inside one. - */ -const SUB_BRANDS = [ - { - name: 'connext', - match: /^(?:\/nl)?\/connext(?:\/|$)/, - home: '/connext', - label: 'ConNext', - wordmark: ( - <> - ConNext - - ), - }, - { - name: 'commonground', - match: /^(?:\/nl)?\/commonground(?:\/|$)/, - home: '/commonground', - label: 'Common Ground+', - wordmark: ( - <> - Common{' '} - Ground - + - - ), - }, -]; - -function brandFor(pathname, title) { - for (const b of SUB_BRANDS) { - if (b.match.test(pathname)) return {...b, source: 'path'}; - } - if (title) { - for (const b of SUB_BRANDS) { - if (b.label === title) return {...b, source: 'title'}; - } - } - return null; -} - -/* Hamburger + close, 24×24 stroke glyphs sharing one 2px weight so the - button keeps the same optical mass when it swaps one for the other. */ -const MENU_ICON = ( - -); - -const CLOSE_ICON = ( - -); - -/** Shared active-route test for internal links (navbar row + drawer). */ -function isActiveRoute(to, location) { - return !!to && (location?.pathname === to || - location?.pathname?.startsWith(to + '/')); -} - -/** Navbar-row rendering of one themeConfig.navbar item. */ -function NavItem({item, location}) { - if (item.type === 'localeDropdown') { - return ( -
    - -
    - ); - } - - /* External link, no React-router prefetch */ - if (item.href && !item.to) { - const isCta = item.cta === true; - return ( - - {item.label}{isCta && ' →'} - - ); - } - - /* Internal route */ - if (item.to) { - const isCta = item.cta === true; - const isActive = isActiveRoute(item.to, location); - return ( - - {item.label}{isCta && ' →'} - - ); - } - - return null; -} - -/** - * Drawer rendering of the same item: section links become full-width - * rows, CTAs become full-width buttons at the foot of the panel, and - * the locale switcher uses Docusaurus's own collapsible mobile - * dropdown. - * - * `onNavigate` closes the drawer. Internal s need it explicitly: - * the route effect in Navbar() closes on a pathname change, but a link - * to the page you are already on changes nothing to react to. - */ -function DrawerItem({item, location, onNavigate}) { - if (item.type === 'localeDropdown') { - /* Docusaurus's mobile dropdown renders an
  • and relies on - Infima's `menu__*` classes, so it needs a - GECERTIFICEERD + GECERTIFICEERD

    Gecertificeerd op de apps die je levert.

    Door Conduction gecertificeerd per app. Gezamenlijke go-to-market op aanbestedingen. Hogere SLA op gedeelde klanten. diff --git a/package-lock.json b/package-lock.json index 77f351f8e..afed47325 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.47.1", + "@conduction/docusaurus-preset": "^3.47.2", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", @@ -2018,9 +2018,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.47.1", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.47.1.tgz", - "integrity": "sha512-BoJRO9zYcaBhNdtw9TA/G5COKWQwx5HmzlMvGgNVScCqsyPvIzw7o7LYC/E8S7jAvT+hlbP1VZ9/w8Yus2Estg==", + "version": "3.47.2", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.47.2.tgz", + "integrity": "sha512-XRjavSf3iJet4KoZ2ofd4/y+/ch4F3/ZZw7Jjsimu56VtxzXmot5Fzh0q84lXX5nACX/0P6cPihPlAugprfmXg==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" diff --git a/package.json b/package.json index 9c4b9f506..a1df545fd 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.47.1", + "@conduction/docusaurus-preset": "^3.47.2", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", From fb6475709dc426498c38daa812dcdfc0cdfc1cc5 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 21 Sep 2026 08:02:39 +0200 Subject: [PATCH 304/330] content(arcade): name Konami in the Dossiq hint (#220) "Anyone who owned a console in the eighties already knows this one" was coy about the one thing that would make a Konami lover grin. The hint names it now, in both locales, without spelling the sequence out, so it is still a nudge rather than the answer. --- i18n/nl/docusaurus-plugin-content-pages/arcade.mdx | 2 +- src/pages/arcade.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx b/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx index 356757dd9..bb15faf0b 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx @@ -76,7 +76,7 @@ Geen van alle roept dat het er is, en er zijn er geen twee die op dezelfde manie | **Logomemory** | De klantlogo's stoppen met scrollen en worden vierentwintig memorytegels. | [Homepage](/nl/) | Klik op een logo | | **Kadefietser** | Twee banen op de kade, en verkeer dat oploopt van fietsen tot trams. | Footer, op elke pagina | Klik op een fiets op de kade | | **Stempelrace** | Neem aan wat quorum heeft, houd tegen wat dat niet heeft. Drie missers en de voorzitter neemt de pen terug. | [Decidiq](/nl/apps/decidiq) | Klop op het logo. Drie keer, kort achter elkaar | -| **Deadlineverdediger** | Elke zaak komt binnen met de klok al lopend. Stuur hem naar de juiste stap voor de tijd op is. | [Dossiq](/nl/apps/dossiq) | Wie in de jaren tachtig een console had, kent deze al | +| **Deadlineverdediger** | Elke zaak komt binnen met de klok al lopend. Stuur hem naar de juiste stap voor de tijd op is. | [Dossiq](/nl/apps/dossiq) | Konami-liefhebbers kennen deze uit hun hoofd | | **Bouwtekeningrace** | Pak de onderdelen die elke app nodig heeft van de plank, en laat liggen wat bij een andere app hoort. | [Buildiq](/nl/apps/buildiq) | Typ wat de app met je doet | | **Recordrun** | Eén record onderweg door de stack. Stuur om de drie dingen heen die er altijd een tegenhouden. | [Connext](/nl/connext) | Hij biedt je een pauze aan. Neem hem | | **Slot openen** | Zet de pick, draai de cilinder, voel hoever hij meegeeft. Drie picks, en elk slot zit strakker dan het vorige. | [Keepiq](/nl/apps/keepiq) | Een slot openen is een kwestie van geduld: druk, en blijf drukken | diff --git a/src/pages/arcade.mdx b/src/pages/arcade.mdx index be73db1a8..8d51a1728 100644 --- a/src/pages/arcade.mdx +++ b/src/pages/arcade.mdx @@ -80,7 +80,7 @@ None of them announce themselves, and no two open the same way. That part is the | **Logo memory** | The client logos stop scrolling and turn into twenty-four memory tiles. | [Home page](/) | Click a logo | | **Kade cyclist** | Two lanes on the quay, and traffic that works its way up from bikes to trams. | Footer, on every page | Click a bike on the quay | | **Stamp rush** | Adopt what has quorum, hold back what does not. Three bad stamps and the chair takes the pen back. | [Decidiq](/apps/decidiq) | Knock on the logo. Three times, briskly | -| **Deadline defender** | Every case arrives with its clock already running. Send it to the right step before the time is gone. | [Dossiq](/apps/dossiq) | Anyone who owned a console in the eighties already knows this one | +| **Deadline defender** | Every case arrives with its clock already running. Send it to the right step before the time is gone. | [Dossiq](/apps/dossiq) | Konami lovers know this one by heart | | **Blueprint rush** | Pick the parts every app needs off the shelf, and skip the ones that belong to somebody else's app. | [Buildiq](/apps/buildiq) | Type what the app does to you | | **Record run** | One record on its way through the stack. Steer around the three things that always stop one. | [Connext](/connext) | It offers you a break. Take it | | **Lock pick** | Set the pick, turn the cylinder, feel how far it gives. Three picks, and each lock is tighter than the last. | [Keepiq](/apps/keepiq) | Picking a lock is a matter of patience: press, and keep pressing | From 5f1fc5fcb224b15774a76a9487d374705abd75d4 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 21 Sep 2026 08:12:45 +0200 Subject: [PATCH 305/330] content(arcade): clearer ways in for Keepiq and Larpinq, and Larpinq opens on 20 (#221) Both hints described the wrong thing. Keepiq unlocks on a long press of the app glyph, but the hint said "press, and keep pressing" and never said what to press. Decidiq and Shillinq both name the logo; this one now does too, and gives the hold a feel, since 1400ms is about a deep breath. Larpinq wanted the literal string `nat20`. "The roll every player wants" gets a reader to natural 20 and no further: 20, natural 20 and nat 20 all fail. The word is now just `20`, so there is no spelling left to guess and the hint can say what it means. Changed in four places together so they cannot drift: the unlock on both Larpinq pages, the hint in both locales, and the test that drives it. Worth knowing: `20` is two keystrokes listening page-wide, which makes Larpinq the easiest of the sixteen to trip over by accident. --- e2e/product-page-games.spec.js | 2 +- i18n/nl/docusaurus-plugin-content-pages/apps/larpinq.mdx | 2 +- i18n/nl/docusaurus-plugin-content-pages/arcade.mdx | 4 ++-- src/pages/apps/larpinq.mdx | 2 +- src/pages/arcade.mdx | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/e2e/product-page-games.spec.js b/e2e/product-page-games.spec.js index c74ccf8fd..587235441 100644 --- a/e2e/product-page-games.spec.js +++ b/e2e/product-page-games.spec.js @@ -71,7 +71,7 @@ const UNLOCK = { in that shows itself, for a page with nothing else to poke at. */ await page.getByRole('button', {name: /take a break/i}).click(); }, - 'dice-duel': async (page) => { await page.keyboard.type('nat20'); }, + 'dice-duel': async (page) => { await page.keyboard.type('20'); }, 'monster-run': async (page) => { await page.keyboard.type('alive'); }, reconcile: async (page) => { /* Five knocks, not three: Shillinq's logo wants more patience than diff --git a/i18n/nl/docusaurus-plugin-content-pages/apps/larpinq.mdx b/i18n/nl/docusaurus-plugin-content-pages/apps/larpinq.mdx index b0a9e8fd1..e249766a0 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/apps/larpinq.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/apps/larpinq.mdx @@ -22,7 +22,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; tertiaryCta={{label: 'Bekijk op GitHub', href: 'https://github.com/ConductionNL/larpingapp'}} illustration={} game={ - + } diff --git a/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx b/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx index bb15faf0b..80410e01b 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx @@ -79,11 +79,11 @@ Geen van alle roept dat het er is, en er zijn er geen twee die op dezelfde manie | **Deadlineverdediger** | Elke zaak komt binnen met de klok al lopend. Stuur hem naar de juiste stap voor de tijd op is. | [Dossiq](/nl/apps/dossiq) | Konami-liefhebbers kennen deze uit hun hoofd | | **Bouwtekeningrace** | Pak de onderdelen die elke app nodig heeft van de plank, en laat liggen wat bij een andere app hoort. | [Buildiq](/nl/apps/buildiq) | Typ wat de app met je doet | | **Recordrun** | Eén record onderweg door de stack. Stuur om de drie dingen heen die er altijd een tegenhouden. | [Connext](/nl/connext) | Hij biedt je een pauze aan. Neem hem | -| **Slot openen** | Zet de pick, draai de cilinder, voel hoever hij meegeeft. Drie picks, en elk slot zit strakker dan het vorige. | [Keepiq](/nl/apps/keepiq) | Een slot openen is een kwestie van geduld: druk, en blijf drukken | +| **Slot openen** | Zet de pick, draai de cilinder, voel hoever hij meegeeft. Drie picks, en elk slot zit strakker dan het vorige. | [Keepiq](/nl/apps/keepiq) | Druk op het logo en laat niet los. Een slot kost ongeveer een diepe zucht | | **Kleuren op token** | Kleuren op nummer, alleen zijn de nummers tokens en bepaalt het thema welke kleur dat is. | [Thematiq](/nl/apps/thematiq) | Typ wat je met een muur doet (in het Engels) | | **Zwart lakken** | Lak weg wat niet naar buiten mag en verstuur het. Laat je er één ding in staan, dan is het een datalek. | [Filinq](/nl/apps/filinq) | Selecteer een zin, alsof je de stift erin gaat zetten | | **Monsterloop** | Spring over de forks, duik onder de patchsets door, en raap de onderdelen op waar het uit is genaaid. | [La Frankendesk](/academy/blog/la-frankendesk) | Typ wat Victor roept als het lukt (in het Engels) | -| **Dobbelduel** | Sla toe met wat je gooide, of houd de goede stenen vast en gooi de rest door. Gooi je slechter, dan bijt het. | [Larpinq](/nl/apps/larpinq) | Typ de worp die elke speler wil | +| **Dobbelduel** | Sla toe met wat je gooide, of houd de goede stenen vast en gooi de rest door. Gooi je slechter, dan bijt het. | [Larpinq](/nl/apps/larpinq) | Typ het getal dat elke speler wil zien | | **Bank matchen** | Elke betaling hoort bij een factuur, behalve die ene die bij niemand hoort. Match ze voor de maand sluit. | [Shillinq](/nl/apps/shillinq) | Klop op het logo, en blijf kloppen | | **Koppeling leggen** | Draai de koppelstukken tot de lijn doorloopt. Draai je er een, dan bewegen beide openingen mee, en dat is het probleem. | [Integriq](/nl/apps/integriq) | Ook die biedt je een pauze aan | diff --git a/src/pages/apps/larpinq.mdx b/src/pages/apps/larpinq.mdx index 6f5125f39..e5ba261b0 100644 --- a/src/pages/apps/larpinq.mdx +++ b/src/pages/apps/larpinq.mdx @@ -22,7 +22,7 @@ import {PartnersForApp} from '@site/src/components/PartnersFor'; tertiaryCta={{label: 'View on GitHub', href: 'https://github.com/ConductionNL/larpingapp'}} illustration={} game={ - + } diff --git a/src/pages/arcade.mdx b/src/pages/arcade.mdx index 8d51a1728..8240d6fc7 100644 --- a/src/pages/arcade.mdx +++ b/src/pages/arcade.mdx @@ -83,11 +83,11 @@ None of them announce themselves, and no two open the same way. That part is the | **Deadline defender** | Every case arrives with its clock already running. Send it to the right step before the time is gone. | [Dossiq](/apps/dossiq) | Konami lovers know this one by heart | | **Blueprint rush** | Pick the parts every app needs off the shelf, and skip the ones that belong to somebody else's app. | [Buildiq](/apps/buildiq) | Type what the app does to you | | **Record run** | One record on its way through the stack. Steer around the three things that always stop one. | [Connext](/connext) | It offers you a break. Take it | -| **Lock pick** | Set the pick, turn the cylinder, feel how far it gives. Three picks, and each lock is tighter than the last. | [Keepiq](/apps/keepiq) | Picking a lock is a matter of patience: press, and keep pressing | +| **Lock pick** | Set the pick, turn the cylinder, feel how far it gives. Three picks, and each lock is tighter than the last. | [Keepiq](/apps/keepiq) | Press the logo and do not let go. A lock takes about as long as a deep breath | | **Paint by tokens** | Paint by numbers, except the numbers are tokens and the theme decides what colour they are. | [Thematiq](/apps/thematiq) | Type what you do to a wall | | **Black it out** | Black out what may not be published, then send it. Leave one thing in and it is a breach. | [Filinq](/apps/filinq) | Select a sentence, as if you were about to take a marker to it | | **Monster run** | Jump the forks, duck the patch sets, and pick up the parts it was stitched together from. | [La Frankendesk](/academy/blog/la-frankendesk) | Type what Victor shouts when it works | -| **Dice duel** | Swing with what you rolled, or hold the good dice and throw the rest again. Roll worse than you had and it bites. | [Larpinq](/apps/larpinq) | Type the roll every player wants | +| **Dice duel** | Swing with what you rolled, or hold the good dice and throw the rest again. Roll worse than you had and it bites. | [Larpinq](/apps/larpinq) | Type the number every player wants to see | | **Match the bank** | Every payment belongs to an invoice, except the one that belongs to nobody. Match them before the month closes. | [Shillinq](/apps/shillinq) | Knock on the logo, and keep knocking | | **Make the connection** | Turn the connectors until the line runs end to end. Turning one moves both its openings, which is the whole problem. | [Integriq](/apps/integriq) | It offers you a break too | From f8a0e1c7619a87139f54f87b217ed0bff14c01d7 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Mon, 21 Sep 2026 18:42:09 +0200 Subject: [PATCH 306/330] feat(arcade): preset 3.48.0, which swaps record run for the stacking game (#222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bump is not a bump. 3.48.0 deletes RecordRun and replaces it with FullStack on the same page, so the site was importing a component the preset no longer exports. The build did not care: a hidden game never renders server-side, so the broken import sat behind the riddle and only the e2e found it. Anyone who solved that riddle would have hit a crash. So record run comes out of both Connext pages, the roster, both arcade pages and the suite, and full stack goes in behind its own typed word. The count stays at sixteen: one game replaced another rather than adding to them. The rework also moved a lot of ground under the tests, and six of them were asserting things that are no longer true: - Blueprint rush is a rail and a tray now, not slots wanting parts. The test lays whatever says "needs nothing first" and follows what that unlocks, so it drives the labels the player reads instead of an order hard-coded here. - Lock pick reads by leaning on the cylinder rather than by sweeping. The game says a lean released when it stops is free, so the test also checks all three picks survive the sweep. - Black it out marks secrets structurally. The test blacks out by `data-token` instead of matching the document's wording, which is exactly why it broke when the wording changed. - Paint by tokens is 7x8 now, and Dossiq has two progressbars. And one is a behaviour change rather than a broken test: a game now stays open only for the visit that found it. That is deliberate and argued in HiddenGame's own docblock — a product page pinned open on a game stops explaining the product. Both arcade pages still promised the old rule in both languages, so the copy is corrected and the test now pins the new one. Verified: both locales build; full suite 271 passed. One mobile-layout test timed out under parallel load and passes on its own in 1.9s against a 90s timeout; it is another spec and untouched here. --- docusaurus.config.js | 2 +- e2e/product-page-games.spec.js | 237 ++++++++---------- i18n/nl/code.json | 152 +++++++++++ .../arcade.mdx | 4 +- .../connext.mdx | 10 +- package-lock.json | 8 +- package.json | 2 +- src/pages/arcade.mdx | 4 +- src/pages/connext.mdx | 12 +- 9 files changed, 279 insertions(+), 152 deletions(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index d9414286f..5cc552385 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -60,7 +60,6 @@ module.exports = createConfig({ {id: 'stamp-rush', label: {en: 'Stamp rush · Decidiq page', nl: 'Stempelrace · Decidiq-pagina'}}, {id: 'deadline-defender', label: {en: 'Deadline defender · Dossiq page', nl: 'Deadlineverdediger · Dossiq-pagina'}}, {id: 'blueprint-rush', label: {en: 'Blueprint rush · Buildiq page', nl: 'Bouwtekeningrace · Buildiq-pagina'}}, - {id: 'record-run', label: {en: 'Record run · Connext page', nl: 'Recordrun · Connext-pagina'}}, {id: 'lock-pick', label: {en: 'Lock pick · Keepiq page', nl: 'Slot openen · Keepiq-pagina'}}, {id: 'paint-by-tokens', label: {en: 'Paint by tokens · Thematiq page', nl: 'Kleuren op token · Thematiq-pagina'}}, {id: 'redaction', label: {en: 'Black it out · Filinq page', nl: 'Zwart lakken · Filinq-pagina'}}, @@ -68,6 +67,7 @@ module.exports = createConfig({ {id: 'dice-duel', label: {en: 'Dice duel · Larpinq page', nl: 'Dobbelduel · Larpinq-pagina'}}, {id: 'reconcile', label: {en: 'Match the bank · Shillinq page', nl: 'Bank matchen · Shillinq-pagina'}}, {id: 'pipe-fit', label: {en: 'Make the connection · Integriq page', nl: 'Koppeling leggen · Integriq-pagina'}}, + {id: 'full-stack', label: {en: 'Full stack · Connext page', nl: 'Volle stack · Connext-pagina'}}, ], minigamesShare: { diff --git a/e2e/product-page-games.spec.js b/e2e/product-page-games.spec.js index 587235441..4594d76fb 100644 --- a/e2e/product-page-games.spec.js +++ b/e2e/product-page-games.spec.js @@ -63,15 +63,13 @@ const UNLOCK = { }); await page.waitForTimeout(1500); }, - 'record-run': async (page) => { - await page.getByRole('button', {name: /take a break/i}).click(); - }, 'pipe-fit': async (page) => { /* Integriq's game hides behind the same quiet opener: the one way in that shows itself, for a page with nothing else to poke at. */ await page.getByRole('button', {name: /take a break/i}).click(); }, 'dice-duel': async (page) => { await page.keyboard.type('20'); }, + 'full-stack': async (page) => { await page.keyboard.type('stack'); }, 'monster-run': async (page) => { await page.keyboard.type('alive'); }, reconcile: async (page) => { /* Five knocks, not three: Shillinq's logo wants more patience than @@ -162,7 +160,8 @@ test.describe('deadline defender, on the Dossiq page', () => { await game.getByRole('button', {name: /open the queue/i}).click(); await expect(game.locator('p[class*="fileText"]')).toBeVisible({timeout: 5000}); - await expect(game.getByRole('progressbar')).toBeVisible(); + /* Two of them now: the margin left on the case, and the clock. */ + await expect(game.getByRole('progressbar').first()).toBeVisible(); /* Read the case and answer it. Trying the three steps in turn cannot work: a misroute replaces the case, so every later click @@ -209,89 +208,36 @@ test.describe('blueprint rush, on the Buildiq page', () => { await page.goto('/apps/buildiq/'); }); - test('stays hidden until the word is typed, then deals a blueprint', async ({page}) => { + test('stays hidden until the word is typed, then deals a rail and a tray', async ({page}) => { const game = page.locator('section[class*="br_"]'); await findGame(page, 'blueprint-rush', game); await game.getByRole('button', {name: /open a blueprint/i}).click(); - const slots = game.locator('li[class*="slot"] span[class*="slotText"]'); - await expect(slots).toHaveCount(4); - - /* Four slots, six parts: the two extra belong to another app. */ - const parts = game.locator('button[class*="part"]'); - await expect(parts).toHaveCount(6); + /* The flow is an ordered rail with places still to fill, and a + tray of parts to fill them from. */ + const rail = game.getByRole('list', {name: /the flow, in order/i}); + await expect(rail).toBeVisible({timeout: 5000}); + await expect(game.getByRole('button', {name: /needs/})).not.toHaveCount(0); }); - test('completing a blueprint scores and buys time', async ({page}) => { + test('laying what is ready builds the flow and scores', async ({page}) => { const game = page.locator('section[class*="br_"]'); await findGame(page, 'blueprint-rush', game); await game.getByRole('button', {name: /open a blueprint/i}).click(); - const SLOT_TO_PART = { - 'Somewhere to keep the records': 'A register', - 'What one record looks like': 'A set of fields', - 'How people fill one in': 'A form', - 'How people find one back': 'A list with search', - 'What happens after someone saves': 'A flow', - 'Who is allowed to see it': 'A group and its rights', - 'What the manager sees on Monday': 'A dashboard widget', - 'Who hears about it': 'A notification', - }; - - const slots = await game.locator('li[class*="slot"] span[class*="slotText"]').allInnerTexts(); - expect(slots.length).toBe(4); - - for (const slot of slots) { - const part = SLOT_TO_PART[slot.trim()]; - expect(part, `no part is written for the slot "${slot}"`).toBeTruthy(); - await game.getByRole('button', {name: part, exact: true}).click(); - } - - /* The score only moves when the last slot is filled. */ - await expect(game.locator('span[class*="hudPill"]').first()).toContainText('25'); - await expect(game.locator('p[class*="hint"]')).toContainText(/bought you seven seconds/i); - }); -}); - -test.describe('record run, on the Connext page', () => { - test.beforeEach(async ({page}) => { - await clearScores(page); - await page.goto('/connext/'); - }); - - test('stays hidden behind a quiet break, then runs a record down three lanes', async ({page}) => { - const game = page.locator('section[class*="rr_"]'); - await findGame(page, 'record-run', game); - await game.getByRole('button', {name: /send a record/i}).click(); - - const lanes = game.getByRole('button', {name: /^Lane \d\. Coming next:/}); - await expect(lanes).toHaveCount(3); - - /* The record is in exactly one lane, and steering moves it. */ - await expect(game.locator('button[aria-pressed="true"]')).toHaveCount(1); - await lanes.nth(2).click(); - await expect(lanes.nth(2)).toHaveAttribute('aria-pressed', 'true'); - }); - - test('steering into what blocks a record ends the run and opens the dialog', async ({page}) => { - const game = page.locator('section[class*="rr_"]'); - await findGame(page, 'record-run', game); - await game.getByRole('button', {name: /send a record/i}).click(); + /* Every part says its own state, so the rail can be built without + the test knowing the order: lay whatever needs nothing first, + and what that unlocks says so in turn. Reading the labels is the + game, which is why they are the thing to drive. */ + const ready = game.getByRole('button', {name: /needs nothing first/}); + const score = game.locator('span[class*="hudPill"]').first(); - /* Always steer into whatever is about to stop the record. Three of - those and the run is over, whatever the board deals. */ await expect(async () => { - const labels = await game.getByRole('button', {name: /^Lane \d\. Coming next:/}).all(); - for (const lane of labels) { - const what = await lane.getAttribute('aria-label'); - if (/Format nothing reads|Permission nobody granted|Connector that is not there/.test(what)) { - await lane.click({timeout: 1000}).catch(() => {}); - } - } - await expect(page.locator(MODAL)).toBeVisible({timeout: 400}); - }).toPass({timeout: 40000}); - - await expect(page.locator(MODAL)).toContainText(/hops/); + const n = await ready.count(); + for (let i = 0; i < n; i++) await ready.nth(i).click({timeout: 800}).catch(() => {}); + const points = Number((await score.innerText()).replace(/\D/g, '')); + expect(points, 'nothing was laid, so nothing scored').toBeGreaterThan(0); + }).toPass({timeout: 30000}); }); }); @@ -301,24 +247,34 @@ test.describe('lock pick, on the Keepiq page', () => { await page.goto('/apps/keepiq/'); }); + /** + * Read the lock at one position. + * + * The dial no longer tells you anything by itself: you set the pick, + * lean on the cylinder, and watch how far it comes round. A lean that + * is released as soon as it stops is free, which the game says in its + * own hint, so a sweep costs nothing. + */ + async function feelAt(game, value) { + await game.getByRole('slider').fill(String(value)); + await game.getByRole('button', {name: /hold to turn the cylinder/i}).click(); + return (await game.locator('p[class*="feel"]').innerText()).trim(); + } + test('stays hidden until the logo is held down, then reads hot and cold', async ({page}) => { const game = page.locator('section[class*="lp_"]'); await findGame(page, 'lock-pick', game); await game.getByRole('button', {name: /take a pick/i}).click(); - const dial = game.getByRole('slider'); - const feel = game.locator('p[class*="feel"]'); - - /* Sweeping the dial has to change the feedback without turning: - that is the whole fix that made the game playable. */ + /* The lock has to feel different in different places, or there is + nothing to read and the game is a guess. */ const readings = new Set(); for (const value of ['5', '25', '50', '75', '95']) { - await dial.fill(value); - readings.add((await feel.innerText()).trim()); + readings.add(await feelAt(game, value)); } - expect(readings.size, 'the dial felt the same everywhere').toBeGreaterThan(1); + expect(readings.size, 'the lock felt the same everywhere').toBeGreaterThan(1); - /* And no turn was taken while sweeping. */ + /* And reading it cost nothing: all three picks are still in hand. */ await expect(game.getByText(/Picks 3/)).toBeVisible(); }); @@ -327,8 +283,6 @@ test.describe('lock pick, on the Keepiq page', () => { await findGame(page, 'lock-pick', game); await game.getByRole('button', {name: /take a pick/i}).click(); - const dial = game.getByRole('slider'); - const feel = game.locator('p[class*="feel"]'); const rank = (text) => (/almost turns/.test(text) ? 4 : /good way/.test(text) ? 3 : /gives a little/.test(text) ? 2 @@ -340,16 +294,24 @@ test.describe('lock pick, on the Keepiq page', () => { makes on their first lock. */ let bestRank = -1; let band = []; - for (let v = 2; v < 100; v += 2) { - await dial.fill(String(v)); - const r = rank((await feel.innerText()).trim()); + for (let v = 2; v < 100; v += 4) { + const r = rank(await feelAt(game, v)); if (r > bestRank) { bestRank = r; band = [v]; } else if (r === bestRank) band.push(v); } const best = band[Math.floor(band.length / 2)]; - await dial.fill(String(best)); - await game.getByRole('button', {name: /turn the cylinder/i}).click(); - await expect(game.locator('p[class*="feedback"]')).toContainText(/Open, in/); + await game.getByRole('slider').fill(String(best)); + + /* Now lean and keep leaning: letting go the moment it stops is what + makes a sweep free, so opening it means holding past that. */ + const turn = game.getByRole('button', {name: /hold to turn the cylinder/i}); + await expect(async () => { + await turn.hover(); + await page.mouse.down(); + await page.waitForTimeout(700); + await page.mouse.up(); + await expect(game.locator('p[class*="feedback"]')).toContainText(/Open, in/, {timeout: 500}); + }).toPass({timeout: 30000}); }); }); @@ -359,35 +321,36 @@ test.describe('black it out, on the Filinq page', () => { await page.goto('/apps/filinq/'); }); + /* What may not be published, by kind rather than by wording. Each + word carries its own `data-token`, so the test blacks out the same + thing the rules do and does not go stale the next time the copy is + rewritten, which is exactly how the previous version of this test + broke. Must stay in step with `secret: true` in the engine. */ + const SECRET = ['name', 'address', 'bsn', 'iban', 'email', 'birthdate', + 'phone', 'medical', 'benefit', 'plate']; + test('stays hidden until a sentence is selected, then publishes clean', async ({page}) => { const game = page.getByRole('region', {name: 'Black it out'}); await findGame(page, 'redaction', game); await game.getByRole('button', {name: /open the stack/i}).click(); - /* Every document deals a name, a number or an address. Black out - whatever matches, leave the sentence, and publish. */ - const SECRET = /de Vries|Keizersgracht|BSN|NL91|@example|March|maart|06 12/; - const words = game.locator('button[class*="word"]'); - const count = await words.count(); - let blacked = 0; - for (let i = 0; i < count; i++) { - const text = await words.nth(i).innerText(); - if (SECRET.test(text)) { await words.nth(i).click(); blacked++; } - } - expect(blacked, 'the document dealt nothing to redact').toBeGreaterThan(0); + const secrets = game.locator(SECRET.map((t) => `button[data-token="${t}"]`).join(', ')); + const count = await secrets.count(); + expect(count, 'the document dealt nothing to redact').toBeGreaterThan(0); + for (let i = 0; i < count; i++) await secrets.nth(i).click(); await game.getByRole('button', {name: /publish it/i}).click(); await expect(game.locator('p[class*="hint"]')).toContainText(/Clean\./); - await expect(game.getByText(/Breaches left 3/)).toBeVisible(); }); test('publishing with a name still on it is a breach', async ({page}) => { const game = page.getByRole('region', {name: 'Black it out'}); await findGame(page, 'redaction', game); await game.getByRole('button', {name: /open the stack/i}).click(); + + /* Straight to publish, with everything still legible. */ await game.getByRole('button', {name: /publish it/i}).click(); - await expect(game.locator('p[class*="hint"]')).toContainText(/should not have/); - await expect(game.getByText(/Breaches left 2/)).toBeVisible(); + await expect(game.locator('p[class*="hint"]')).toContainText(/Breach\./); }); }); @@ -403,7 +366,7 @@ test.describe('paint by tokens, on the Thematiq page', () => { await game.getByRole('button', {name: /open a theme/i}).click(); const cells = game.locator('button[class*="cell"]'); - await expect(cells).toHaveCount(48); + await expect(cells).toHaveCount(56); // 7 across, 8 down const wanted = await cells.first().getAttribute('aria-label'); const token = wanted.replace(/^Wants /, '').trim(); @@ -413,28 +376,20 @@ test.describe('paint by tokens, on the Thematiq page', () => { }); }); -test('a game found once stays found in that browser', async ({page}) => { - /* Hiding a game again from the person who already solved it would be - a punishment for playing. - - Deliberately without clearScores(): that helper runs as an init - script on every navigation, so it would wipe the found-games list - during the reload and the test would be checking its own cleanup - rather than the feature. Storage is cleared once, by hand. */ +test('a game found once is hidden again on the next visit', async ({page}) => { + /* Deliberate, and the opposite of what this suite used to assert: a + game stays open for the visit that found it and no longer. A page + pinned open on a game stops doing the job it is there for, which + is explaining the product. The way back without the riddle is the + play link, asserted below. */ + await clearScores(page); await page.goto('/apps/decidiq/'); - await page.evaluate(() => { - try { - window.localStorage.removeItem('conduction:minigames'); - window.localStorage.removeItem('conduction:minigames-found'); - } catch (e) {/* private mode */} - }); - await page.reload(); const game = page.locator('section[class*="rush"]'); await findGame(page, 'stamp-rush', game); await page.reload(); - await expect(game, 'the game hid itself again after being found').toBeVisible({timeout: 8000}); + await expect(game, 'the game stayed open across a reload').toHaveCount(0); }); test('a direct play link opens a game without solving its riddle', async ({page}) => { @@ -471,9 +426,10 @@ test('the arcade page lists every game the site ships', async ({page}) => { the dialog counts a game the page never names. */ for (const name of [ 'Twelve apps', 'Sink the boats', 'Hex-vaders', 'Logo memory', 'Kade cyclist', - 'Stamp rush', 'Deadline defender', 'Blueprint rush', 'Record run', + 'Stamp rush', 'Deadline defender', 'Blueprint rush', 'Lock pick', 'Paint by tokens', 'Black it out', 'Monster run', 'Dice duel', 'Match the bank', 'Make the connection', + 'Full stack', ]) { await expect(page.getByText(name, {exact: false}).first()).toBeVisible(); } @@ -556,7 +512,7 @@ test.describe('match the bank, on the Shillinq page', () => { /* At most one line on a statement belongs to nobody, so flagging everything spends the three corrections within a sheet or two. */ await expect(async () => { - const flags = await game.getByRole('button', {name: /as belonging to nobody/}).all(); + const flags = await game.getByRole('button', {name: /^Flag /}).all(); for (const flag of flags) await flag.click({timeout: 600}).catch(() => {}); await expect(page.locator(MODAL)).toBeVisible({timeout: 400}); }).toPass({timeout: 30000}); @@ -640,16 +596,16 @@ test.describe('the game-over card', () => { async function showCard(page) { await page.goto('/connext/'); /* The listener is attached from the client, so an event dispatched - before hydration reaches nobody. Wait for something only the - hydrated page has. */ - await page.getByRole('button', {name: /take a break/i}).waitFor(); + before hydration reaches nobody. There is no longer a visible + opener on this page to wait for, so the retry below does the + waiting: it fires again until the card answers. */ const fire = () => page.evaluate(() => { window.dispatchEvent(new CustomEvent('connext:gameend', {detail: { - id: 'record-run', + id: 'full-stack', won: false, score: 102, - summary: '39 hops · 5 apps picked up', - title: 'The record got stuck.', + summary: '9 layers · 2 whole stacks', + title: 'The stack is full.', }})); }); const panel = page.locator('div[class*="modal"] > div[class*="panel"]'); @@ -691,3 +647,22 @@ test.describe('the game-over card', () => { .toBeLessThan(8); }); }); + +test.describe('full stack, on the Connext page', () => { + test.beforeEach(async ({page}) => { + await clearScores(page); + await page.goto('/connext/'); + }); + + test('stays hidden until the word is typed, then deals a well of app tiles', async ({page}) => { + const game = page.getByRole('region', {name: 'Full stack'}); + await findGame(page, 'full-stack', game); + await game.getByRole('button', {name: /start stacking/i}).click(); + + /* A column per width of the well, each saying how deep it already + is, so the board is readable without seeing it. */ + const columns = game.getByRole('button', {name: /^Column \d/}); + await expect(columns.first()).toBeVisible({timeout: 5000}); + await expect(game.getByText(/In play:/)).toBeVisible(); + }); +}); diff --git a/i18n/nl/code.json b/i18n/nl/code.json index f1337b51d..6665e24ff 100644 --- a/i18n/nl/code.json +++ b/i18n/nl/code.json @@ -1797,5 +1797,157 @@ "preset.pipeFit.over.subtitle": { "message": "Drie zendingen het gat in. Volgend kwartaal valt het iemand op.", "description": "Subtitle on the game-over dialog after a pipe-fit run" + }, + "preset.fullStack.app.opencatalogi": { + "message": "OpenCatalogi", + "description": "App name on a tile in the full-stack game" + }, + "preset.fullStack.app.openregister": { + "message": "OpenRegister", + "description": "App name on a tile in the full-stack game" + }, + "preset.fullStack.app.openconnector": { + "message": "Integriq", + "description": "App name on a tile in the full-stack game" + }, + "preset.fullStack.app.docudesk": { + "message": "Filinq", + "description": "App name on a tile in the full-stack game" + }, + "preset.fullStack.app.shillinq": { + "message": "Shillinq", + "description": "App name on a tile in the full-stack game" + }, + "preset.fullStack.app.launchpad": { + "message": "LaunchPad", + "description": "App name on a tile in the full-stack game" + }, + "preset.fullStack.app.zaakafhandelapp": { + "message": "ZaakAfhandelApp", + "description": "App name on a tile in the full-stack game" + }, + "preset.fullStack.shape.dot": { + "message": "los blokje", + "description": "Shape of a piece in the full-stack game" + }, + "preset.fullStack.shape.pair": { + "message": "paar", + "description": "Shape of a piece in the full-stack game" + }, + "preset.fullStack.shape.bend": { + "message": "knik", + "description": "Shape of a piece in the full-stack game" + }, + "preset.fullStack.shape.square": { + "message": "vierkant", + "description": "Shape of a piece in the full-stack game" + }, + "preset.fullStack.shape.tee": { + "message": "T-stuk", + "description": "Shape of a piece in the full-stack game" + }, + "preset.fullStack.shape.plus": { + "message": "plus", + "description": "Shape of a piece in the full-stack game" + }, + "preset.fullStack.over.title": { + "message": "De stack zit vol.", + "description": "Headline on the game-over dialog after a full-stack run" + }, + "preset.fullStack.over.subtitle": { + "message": "Dat gebeurt er als niets op elkaar past.", + "description": "Subtitle on the game-over dialog after a full-stack run" + }, + "preset.fullStack.eyebrow": { + "message": "Mini-game", + "description": "Eyebrow above the full-stack game" + }, + "preset.fullStack.title": { + "message": "Volle stack", + "description": "Name of the Connext mini-game" + }, + "preset.fullStack.lede": { + "message": "Apps komen in groepjes de stack in. Vul een laag helemaal en hij is klaar. Een laag van één app levert meer op, en een laag met alle zeven erin het meest: dat is het hele idee van een stack.", + "description": "One-line explanation of the full-stack rules" + }, + "preset.fullStack.hud.score": { + "message": "Score {score}", + "description": "Score readout on the full-stack HUD" + }, + "preset.fullStack.hud.layers": { + "message": "Lagen {layers}", + "description": "Finished-layers readout on the full-stack HUD" + }, + "preset.fullStack.hud.whole": { + "message": "Hele stack {whole}", + "description": "Readout of how many layers held every app" + }, + "preset.fullStack.idle": { + "message": "Een lege stack, en zeven apps die ergens moeten landen.", + "description": "Placeholder before the full-stack game starts" + }, + "preset.fullStack.columnLabel": { + "message": "Kolom {n}, {depth} diep. Zet het stuk hier neer.", + "description": "Accessible label for a column button. {depth} is how many tiles are already stacked there." + }, + "preset.fullStack.nextLabel": { + "message": "Komt eraan", + "description": "Label above the preview of the next piece" + }, + "preset.fullStack.nextCaption": { + "message": "{app}, {shape}", + "description": "Caption under the next-piece preview. {app} is the app, {shape} the piece shape." + }, + "preset.fullStack.left": { + "message": "Links", + "description": "Button that moves the falling piece left" + }, + "preset.fullStack.turn": { + "message": "Draaien", + "description": "Button that rotates the falling piece" + }, + "preset.fullStack.right": { + "message": "Rechts", + "description": "Button that moves the falling piece right" + }, + "preset.fullStack.down": { + "message": "Omlaag", + "description": "Button that moves the falling piece down one row, quicker than it falls" + }, + "preset.fullStack.drop": { + "message": "Laten vallen", + "description": "Button that sends the falling piece all the way to the floor" + }, + "preset.fullStack.inPlay": { + "message": "In spel: {app}, {shape}, boven kolom(men) {cols}.", + "description": "Live readout of the falling piece. {cols} lists the columns it covers." + }, + "preset.fullStack.nextUp": { + "message": "Komt eraan: {app}, {shape}.", + "description": "Live readout of the next piece, spoken with the one in play." + }, + "preset.fullStack.restart": { + "message": "Opnieuw", + "description": "Button that restarts the full-stack game" + }, + "preset.fullStack.start": { + "message": "Beginnen met stapelen", + "description": "Button that starts the full-stack game" + }, + "preset.fullStack.feedback.whole": { + "message": "Alle zeven apps in één laag. Dat is de hele stack, op een rij. +{points}.", + "description": "Feedback after finishing a layer holding every app. {points} is what it paid." + }, + "preset.fullStack.feedback.oneApp": { + "message": "Een hele laag op één app. +{points}.", + "description": "Feedback after finishing a layer made of a single app. {points} is what it paid." + }, + "preset.fullStack.feedback.layers": { + "message": "{layers} laag(lagen) eruit. +{points}. Een laag van één app, of een met alle zeven, levert veel meer op.", + "description": "Feedback after finishing a plain layer. {layers} is how many, {points} what they paid." + }, + "preset.fullStack.hint": { + "message": "Links en rechts om te bewegen, omhoog om te draaien, omlaag om het te versnellen, spatie om te laten vallen. De getallen onder de bak zijn hoe diep elke kolom al is.", + "description": "Hint under the full-stack well explaining the controls" } } diff --git a/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx b/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx index 80410e01b..24e04406f 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx @@ -78,7 +78,6 @@ Geen van alle roept dat het er is, en er zijn er geen twee die op dezelfde manie | **Stempelrace** | Neem aan wat quorum heeft, houd tegen wat dat niet heeft. Drie missers en de voorzitter neemt de pen terug. | [Decidiq](/nl/apps/decidiq) | Klop op het logo. Drie keer, kort achter elkaar | | **Deadlineverdediger** | Elke zaak komt binnen met de klok al lopend. Stuur hem naar de juiste stap voor de tijd op is. | [Dossiq](/nl/apps/dossiq) | Konami-liefhebbers kennen deze uit hun hoofd | | **Bouwtekeningrace** | Pak de onderdelen die elke app nodig heeft van de plank, en laat liggen wat bij een andere app hoort. | [Buildiq](/nl/apps/buildiq) | Typ wat de app met je doet | -| **Recordrun** | Eén record onderweg door de stack. Stuur om de drie dingen heen die er altijd een tegenhouden. | [Connext](/nl/connext) | Hij biedt je een pauze aan. Neem hem | | **Slot openen** | Zet de pick, draai de cilinder, voel hoever hij meegeeft. Drie picks, en elk slot zit strakker dan het vorige. | [Keepiq](/nl/apps/keepiq) | Druk op het logo en laat niet los. Een slot kost ongeveer een diepe zucht | | **Kleuren op token** | Kleuren op nummer, alleen zijn de nummers tokens en bepaalt het thema welke kleur dat is. | [Thematiq](/nl/apps/thematiq) | Typ wat je met een muur doet (in het Engels) | | **Zwart lakken** | Lak weg wat niet naar buiten mag en verstuur het. Laat je er één ding in staan, dan is het een datalek. | [Filinq](/nl/apps/filinq) | Selecteer een zin, alsof je de stift erin gaat zetten | @@ -86,12 +85,13 @@ Geen van alle roept dat het er is, en er zijn er geen twee die op dezelfde manie | **Dobbelduel** | Sla toe met wat je gooide, of houd de goede stenen vast en gooi de rest door. Gooi je slechter, dan bijt het. | [Larpinq](/nl/apps/larpinq) | Typ het getal dat elke speler wil zien | | **Bank matchen** | Elke betaling hoort bij een factuur, behalve die ene die bij niemand hoort. Match ze voor de maand sluit. | [Shillinq](/nl/apps/shillinq) | Klop op het logo, en blijf kloppen | | **Koppeling leggen** | Draai de koppelstukken tot de lijn doorloopt. Draai je er een, dan bewegen beide openingen mee, en dat is het probleem. | [Integriq](/nl/apps/integriq) | Ook die biedt je een pauze aan | +| **Volle stack** | Apps komen in groepjes naar beneden. Vul een laag helemaal en hij is klaar. Een laag van één app levert meer op, een laag met alle zeven het meest. | [Connext](/nl/connext) | Typ het tweede woord van de naam van dit spel |
    Ze vinden is het halve werk. De game-overkaart onthoudt welke je hebt gevonden, en de laatste regel zegt hoeveel er nog liggen. -Heb je er een gevonden, dan blijft hij gevonden in die browser, dus je kunt terugkomen om je eigen score te verslaan zonder het raadsel opnieuw op te lossen. En als een raadsel tussen jou en een biertje in staat: elk spel luistert naar zijn eigen link. Zet `#play-stamp-rush` achter het adres van de Decidiq-pagina, en vul de naam in van het spel dat je zoekt. +Een spel blijft open voor het bezoek waarin je het vond, en niet langer. Ververs de pagina of kom morgen terug, en het is weer gewoon een productpagina: het raadsel is elke keer de weg naar binnen. Staat er een tussen jou en een biertje in, dan luistert elk spel ook naar zijn eigen link. Zet `#play-stamp-rush` achter het adres van de Decidiq-pagina, en vul de naam in van het spel dat je zoekt. ## Hoe de score werkt diff --git a/i18n/nl/docusaurus-plugin-content-pages/connext.mdx b/i18n/nl/docusaurus-plugin-content-pages/connext.mdx index eb8c50d1f..aa9873593 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/connext.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/connext.mdx @@ -4,7 +4,7 @@ description: Twaalf open-source apps die in Nextcloud draaien. Catalogi, registe hide_table_of_contents: true --- -import {Hero, StatsStrip, PlatformOverview, PlatformDiagram, HexRain, AppsPreview, CtaBanner, Section, SectionHead, PairRow, PairCard, RecordRun, HiddenGame} from '@conduction/docusaurus-preset/components'; +import {Hero, StatsStrip, PlatformOverview, PlatformDiagram, HexRain, AppsPreview, CtaBanner, Section, SectionHead, PairRow, PairCard, FullStack, HiddenGame} from '@conduction/docusaurus-preset/components'; import {totalDownloads, formatDownloads} from '@conduction/docusaurus-preset/data'; import {totalPartners} from '@site/src/data/partners-catalog'; @@ -192,11 +192,11 @@ import {totalPartners} from '@site/src/data/partners-catalog'; -{/* Het mini-game van de stack zelf: één record dat er doorheen - probeert te komen. Staat boven de afsluitende CTA. */} +{/* Het andere mini-game van Connext: de stack zelf, als een bak met + app-tegels die alleen loont als de stukken passen. */}
    - - + +
    diff --git a/package-lock.json b/package-lock.json index afed47325..4616c94e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.47.2", + "@conduction/docusaurus-preset": "^3.48.0", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", @@ -2018,9 +2018,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.47.2", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.47.2.tgz", - "integrity": "sha512-XRjavSf3iJet4KoZ2ofd4/y+/ch4F3/ZZw7Jjsimu56VtxzXmot5Fzh0q84lXX5nACX/0P6cPihPlAugprfmXg==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.48.0.tgz", + "integrity": "sha512-/QDttdNAursGszh7S9Kcm/OwEd5UBPTwb/JgQhLes/Lu7RWa+lVxUCBaNsOulw8POUJjdh6ueV/qdcwxGw/rSQ==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" diff --git a/package.json b/package.json index a1df545fd..bc46a6261 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.47.2", + "@conduction/docusaurus-preset": "^3.48.0", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", diff --git a/src/pages/arcade.mdx b/src/pages/arcade.mdx index 8240d6fc7..56cc23b0f 100644 --- a/src/pages/arcade.mdx +++ b/src/pages/arcade.mdx @@ -82,7 +82,6 @@ None of them announce themselves, and no two open the same way. That part is the | **Stamp rush** | Adopt what has quorum, hold back what does not. Three bad stamps and the chair takes the pen back. | [Decidiq](/apps/decidiq) | Knock on the logo. Three times, briskly | | **Deadline defender** | Every case arrives with its clock already running. Send it to the right step before the time is gone. | [Dossiq](/apps/dossiq) | Konami lovers know this one by heart | | **Blueprint rush** | Pick the parts every app needs off the shelf, and skip the ones that belong to somebody else's app. | [Buildiq](/apps/buildiq) | Type what the app does to you | -| **Record run** | One record on its way through the stack. Steer around the three things that always stop one. | [Connext](/connext) | It offers you a break. Take it | | **Lock pick** | Set the pick, turn the cylinder, feel how far it gives. Three picks, and each lock is tighter than the last. | [Keepiq](/apps/keepiq) | Press the logo and do not let go. A lock takes about as long as a deep breath | | **Paint by tokens** | Paint by numbers, except the numbers are tokens and the theme decides what colour they are. | [Thematiq](/apps/thematiq) | Type what you do to a wall | | **Black it out** | Black out what may not be published, then send it. Leave one thing in and it is a breach. | [Filinq](/apps/filinq) | Select a sentence, as if you were about to take a marker to it | @@ -90,12 +89,13 @@ None of them announce themselves, and no two open the same way. That part is the | **Dice duel** | Swing with what you rolled, or hold the good dice and throw the rest again. Roll worse than you had and it bites. | [Larpinq](/apps/larpinq) | Type the number every player wants to see | | **Match the bank** | Every payment belongs to an invoice, except the one that belongs to nobody. Match them before the month closes. | [Shillinq](/apps/shillinq) | Knock on the logo, and keep knocking | | **Make the connection** | Turn the connectors until the line runs end to end. Turning one moves both its openings, which is the whole problem. | [Integriq](/apps/integriq) | It offers you a break too | +| **Full stack** | Apps come down in groups. Fill a layer all the way across and it clears. A layer of one app pays more, and a layer with all seven pays most. | [Connext](/connext) | Type the second word of this game's name |
  • Finding them is half of it. The game-over card keeps track of which ones you have found, and the last line tells you how many are left. -Once you have found a game it stays found in that browser, so you can come back and beat your own score without solving it again. And if a riddle is standing between you and a beer, every game answers to its own link: put `#play-stamp-rush` on the end of the Decidiq page's address, and swap in the name of whichever one you are after. +A game stays open for the visit that found it, and no longer than that. Reload, or come back tomorrow, and the page is a product page again: the riddle is the way in each time. If one of them is standing between you and a beer, every game also answers to its own link: put `#play-stamp-rush` on the end of the Decidiq page's address, and swap in the name of whichever one you are after. ## How scoring works diff --git a/src/pages/connext.mdx b/src/pages/connext.mdx index 2bab81a4c..cb92ddf8e 100644 --- a/src/pages/connext.mdx +++ b/src/pages/connext.mdx @@ -4,7 +4,7 @@ description: Open-source apps that plug into Nextcloud. Catalogs, registers, int hide_table_of_contents: true --- -import {Hero, StatsStrip, PlatformOverview, PlatformDiagram, HexRain, AppsPreview, CtaBanner, Showcase, AppMock, AgentTrace, ExternalAppShelf, Section, SectionHead, PairRow, PairCard, DownloadPanel, RecordRun, HiddenGame} from '@conduction/docusaurus-preset/components'; +import {Hero, StatsStrip, PlatformOverview, PlatformDiagram, HexRain, AppsPreview, CtaBanner, Showcase, AppMock, AgentTrace, ExternalAppShelf, Section, SectionHead, PairRow, PairCard, DownloadPanel, FullStack, HiddenGame} from '@conduction/docusaurus-preset/components'; import {AppGlyph} from '@conduction/docusaurus-preset/components'; import {totalDownloads, formatDownloads} from '@conduction/docusaurus-preset/data'; import {totalPartners} from '@site/src/data/partners-catalog'; @@ -323,12 +323,12 @@ import {APP_COUNT, ECOSYSTEM_COUNT} from '@site/src/data/apps-catalog'; -{/* The stack's own mini-game: one record trying to get through it. - Sits above the closing CTA, so it is the last thing on the page - that is not a button. */} +{/* Connext's other mini-game: the stack itself, as a well of app + tiles that only pays when the pieces fit together. Its own way in, + because one page's riddle must not open another's. */}
    - - + +
    From 20e247a2143a85d5437b85ad31c5fc524ab3ad8a Mon Sep 17 00:00:00 2001 From: Remko Date: Mon, 21 Sep 2026 22:29:35 +0200 Subject: [PATCH 307/330] chore(dev): keep the installed preset on webpack's watch list Editing a file inside node_modules/@conduction and reloading did nothing. Webpack excludes node_modules from watching, so the dev server kept serving the copy it read at boot and only picked a change up on a full restart. Register a dev-only plugin that puts @conduction back on the watch list, so an edit copied into the installed preset hot-reloads like any other source file. It is a no-op in a production build. --- docusaurus.config.js | 4 ++ plugins/watch-preset-dev.js | 76 +++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 plugins/watch-preset-dev.js diff --git a/docusaurus.config.js b/docusaurus.config.js index 5cc552385..d13a4cfc1 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -318,6 +318,10 @@ module.exports = createConfig({ /* INTERIM: apps-registry descriptions + AppCrossLinks description rendering until the preset ships both. See the plugin header. */ require.resolve('./plugins/apps-registry-interim'), + /* DEV ONLY: puts @conduction back on webpack's watch list, so an + edit copied into the installed preset hot-reloads instead of + needing a server restart. No-op in a production build. */ + require.resolve('./plugins/watch-preset-dev'), // [ // '@conduction/docusaurus-plugin-opencatalogi', // { diff --git a/plugins/watch-preset-dev.js b/plugins/watch-preset-dev.js new file mode 100644 index 000000000..fe5588b31 --- /dev/null +++ b/plugins/watch-preset-dev.js @@ -0,0 +1,76 @@ +/** + * DEV ONLY · Watch the installed preset so edits to it hot-reload. + * + * Every mini-game, and most of the site's components, live in + * @conduction/docusaurus-preset — the design-system monorepo. While + * working on one we edit it there and copy the changed files into + * node_modules/@conduction/docusaurus-preset so it can be tried on the + * running site before a release. + * + * Webpack never noticed. `docusaurus start` sets + * + * watchOptions.ignored = /node_modules\/(?!@docusaurus)/ + * + * (@docusaurus/core/lib/webpack/client.js), and watchpack normalises + * backslashes to forward slashes before testing the regex, so the rule + * bites on Windows too. Every copy therefore needed a full server + * restart, which also reopens a browser tab. + * + * Plugin configureWebpack() hooks run after those watch options are + * built (getStartClientConfig in commands/start/webpack.js), so this + * puts @conduction back on the watch list. Docusaurus itself is kept + * un-ignored, exactly as before. + * + * Watching alone was not enough, and the way it failed is worth + * recording. A rebuild fired on every copy, and the output never + * changed. Two defaults conspire: + * + * - snapshot.managedPaths defaults to this project's node_modules + * (webpack/lib/config/defaults.js). Anything under it is taken to + * be owned by a package manager, so webpack validates it by the + * package's version rather than by its contents — and the preset's + * version does not move when we copy a file into it. + * - cache.type is 'filesystem' (@docusaurus/core/lib/webpack/base.js), + * so that verdict is written to node_modules/.cache/webpack and + * survives every restart. + * + * snapshot.unmanagedPaths is checked first and short-circuits the + * managed test (FileSystemInfo.checkManaged), so naming @conduction + * there puts it back on ordinary content hashing. + * + * Note for anyone editing this file: it is not one of webpack's + * buildDependencies (only docusaurus.config.js is), so a change here + * does not evict that cache. Clear node_modules/.cache/webpack by hand + * if this plugin's behaviour ever looks stuck. + * + * Scope: the dev server only. In a production build there is no + * watcher, and the hook returns nothing rather than leaving a stray + * watchOptions in the config. + * + * The cost is that webpack now walks @conduction on every rebuild. One + * package is cheap; if dev ever feels sluggish, this is a suspect. + * + * This plugin is a convenience, not a dependency: dropping it only + * brings the restart back. + */ + +module.exports = function watchPresetDev() { + return { + name: 'watch-preset-dev', + configureWebpack(config, isServer) { + if (isServer || process.env.NODE_ENV !== 'development') return {}; + return { + /* Replace rather than merge: two regexes cannot be combined, + and webpack-merge would otherwise keep Docusaurus's. */ + mergeStrategy: {'watchOptions.ignored': 'replace'}, + watchOptions: { + ignored: /node_modules\/(?!@docusaurus|@conduction)/, + }, + /* Appends to webpack's empty default, so nothing is lost. */ + snapshot: { + unmanagedPaths: [/[\\/]node_modules[\\/]@conduction[\\/]/], + }, + }; + }, + }; +}; From e21ff8f808d26a4456176681854b07ea76006e1d Mon Sep 17 00:00:00 2001 From: Remko Date: Mon, 21 Sep 2026 22:30:55 +0200 Subject: [PATCH 308/330] feat(roster): give each roster entry the page it belongs to minigamesRoster entries now carry a path, so the summary card can render an entry's name as a link to where it lives rather than as plain text. The card applies it only to entries already marked as found, so its behaviour for the rest is unchanged. The first five carry no path deliberately: they belong to the footer, the cookie bar and the apps grid, which appear on every page, so there is no single destination to point at. --- docusaurus.config.js | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index d13a4cfc1..b7cd40fe1 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -50,24 +50,31 @@ module.exports = createConfig({ /* The games this site ships. The dialog counts "found" and the total against this list, so a game added here must actually exist on a page, and a game removed from the site must come out. Labels are - per locale because themeConfig is never translated. */ + per locale because themeConfig is never translated. + + `path` is the page a game lives on, and it makes the game's name + in the game-over roster a link back to it — but only once that + player has found it, so the list never gives a riddle away. The + first five have no path on purpose: they live in the footer, the + cookie bar and the apps grid, which are on every page, so there + is nowhere in particular to send anyone. Those stay plain text. */ minigamesRoster: [ {id: 'hexrain', label: {en: 'Twelve apps · hex rain', nl: 'Twaalf apps · hexregen'}}, {id: 'boats', label: {en: 'Sink the boats · footer canal', nl: 'Boten kelderen · gracht in de footer'}}, {id: 'invaders', label: {en: 'Hex-vaders · cookie CLI', nl: 'Hex-vaders · cookieconsole'}}, {id: 'logo-memory', label: {en: 'Logo memory · clients marquee', nl: 'Logomemory · klantenmarquee'}}, {id: 'kade-cyclist', label: {en: 'Kade cyclist · footer kade', nl: 'Kadefietser · kade in de footer'}}, - {id: 'stamp-rush', label: {en: 'Stamp rush · Decidiq page', nl: 'Stempelrace · Decidiq-pagina'}}, - {id: 'deadline-defender', label: {en: 'Deadline defender · Dossiq page', nl: 'Deadlineverdediger · Dossiq-pagina'}}, - {id: 'blueprint-rush', label: {en: 'Blueprint rush · Buildiq page', nl: 'Bouwtekeningrace · Buildiq-pagina'}}, - {id: 'lock-pick', label: {en: 'Lock pick · Keepiq page', nl: 'Slot openen · Keepiq-pagina'}}, - {id: 'paint-by-tokens', label: {en: 'Paint by tokens · Thematiq page', nl: 'Kleuren op token · Thematiq-pagina'}}, - {id: 'redaction', label: {en: 'Black it out · Filinq page', nl: 'Zwart lakken · Filinq-pagina'}}, - {id: 'monster-run', label: {en: 'Monster run · La Frankendesk', nl: 'Monsterloop · La Frankendesk'}}, - {id: 'dice-duel', label: {en: 'Dice duel · Larpinq page', nl: 'Dobbelduel · Larpinq-pagina'}}, - {id: 'reconcile', label: {en: 'Match the bank · Shillinq page', nl: 'Bank matchen · Shillinq-pagina'}}, - {id: 'pipe-fit', label: {en: 'Make the connection · Integriq page', nl: 'Koppeling leggen · Integriq-pagina'}}, - {id: 'full-stack', label: {en: 'Full stack · Connext page', nl: 'Volle stack · Connext-pagina'}}, + {id: 'stamp-rush', path: '/apps/decidiq/', label: {en: 'Stamp rush · Decidiq page', nl: 'Stempelrace · Decidiq-pagina'}}, + {id: 'deadline-defender', path: '/apps/dossiq/', label: {en: 'Deadline defender · Dossiq page', nl: 'Deadlineverdediger · Dossiq-pagina'}}, + {id: 'blueprint-rush', path: '/apps/buildiq/', label: {en: 'Blueprint rush · Buildiq page', nl: 'Bouwtekeningrace · Buildiq-pagina'}}, + {id: 'lock-pick', path: '/apps/keepiq/', label: {en: 'Lock pick · Keepiq page', nl: 'Slot openen · Keepiq-pagina'}}, + {id: 'paint-by-tokens', path: '/apps/thematiq/', label: {en: 'Paint by tokens · Thematiq page', nl: 'Kleuren op token · Thematiq-pagina'}}, + {id: 'redaction', path: '/apps/filinq/', label: {en: 'Black it out · Filinq page', nl: 'Zwart lakken · Filinq-pagina'}}, + {id: 'monster-run', path: '/academy/blog/la-frankendesk/', label: {en: 'Monster run · La Frankendesk', nl: 'Monsterloop · La Frankendesk'}}, + {id: 'dice-duel', path: '/apps/larpinq/', label: {en: 'Dice duel · Larpinq page', nl: 'Dobbelduel · Larpinq-pagina'}}, + {id: 'reconcile', path: '/apps/shillinq/', label: {en: 'Match the bank · Shillinq page', nl: 'Bank matchen · Shillinq-pagina'}}, + {id: 'pipe-fit', path: '/apps/integriq/', label: {en: 'Make the connection · Integriq page', nl: 'Koppeling leggen · Integriq-pagina'}}, + {id: 'full-stack', path: '/connext/', label: {en: 'Full stack · Connext page', nl: 'Volle stack · Connext-pagina'}}, ], minigamesShare: { From 6488a22bde949914188247cfdec0b871f94bd587 Mon Sep 17 00:00:00 2001 From: Remko Date: Mon, 21 Sep 2026 22:31:36 +0200 Subject: [PATCH 309/330] chore(sitemap): stop advertising the /arcade index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /arcade is an index page we would rather not point people at. It was listed in both locales' sitemaps, and the share block of the footer modal linked to it under the prize line, which put it in front of everyone who got that far. Drop minigamesShare.prizeHref and the label that only renders inside that link — the block already renders its sentence on its own when there is no href — and add the route to the sitemap's ignorePatterns in both locales and both slash forms. This is not access control. The route still resolves for anyone holding the URL and robots.txt is unchanged; it only stops us pointing at it. --- docusaurus.config.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index b7cd40fe1..9989d926a 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -85,8 +85,11 @@ module.exports = createConfig({ en: 'Best total score on Friday 25 September at 17:00 wins a box of Amsterdam beer.', nl: 'De hoogste totaalscore op vrijdag 25 september om 17:00 wint een doos Amsterdams bier.', }, - prizeHref: '/arcade', - prizeLinkLabel: {en: 'How the giveaway works', nl: 'Zo werkt de actie'}, + /* No prizeHref on purpose. It pointed at /arcade, an index page we + would rather not send people to from here. The modal renders the + prize sentence on its own when there is no href, and + prizeLinkLabel is only read inside that link, so the two come out + together. The route still resolves for anyone holding the URL. */ }, /* Public enquiry-intake endpoint. The website forms POST here to create an @@ -167,12 +170,22 @@ module.exports = createConfig({ crawlers without locale-suffix discovery still see Dutch pages. /academy/tags/* is excluded because tag pages are thin and confuse AI summarisers more than they help SEO. + /arcade is excluded because it is an index page we would + rather not advertise. Note that this only keeps it out of the + sitemap and is not an access control: robots.txt is left open, + so anything that reaches the route another way can still read + it. ignorePatterns matches route paths *after* the locale prefix - is applied, so we list both forms. */ + is applied, so we list both forms. trailingSlash is true, so + the route carries one; the bare form is listed too in case + that ever changes. */ sitemap: { changefreq: 'weekly', priority: 0.5, - ignorePatterns: ['/academy/tags/**', '/nl/academy/tags/**'], + ignorePatterns: [ + '/academy/tags/**', '/nl/academy/tags/**', + '/arcade', '/arcade/', '/nl/arcade', '/nl/arcade/', + ], filename: 'sitemap.xml', }, }, From a41d7b8ba3c731f7a7009e34b0e773329eb33c57 Mon Sep 17 00:00:00 2001 From: Remko Date: Mon, 21 Sep 2026 22:31:50 +0200 Subject: [PATCH 310/330] content(arcade): describe the kade strip as it now behaves The row said two lanes carrying traffic that works its way up from bikes to trams. The strip is now a two-way bike path with a road above it, and its traffic arrives in waves rather than at a steady rate, so the description says that instead. Both locales. --- i18n/nl/docusaurus-plugin-content-pages/arcade.mdx | 2 +- src/pages/arcade.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx b/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx index 24e04406f..9067448fd 100644 --- a/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx +++ b/i18n/nl/docusaurus-plugin-content-pages/arcade.mdx @@ -74,7 +74,7 @@ Geen van alle roept dat het er is, en er zijn er geen twee die op dezelfde manie | **Boten kelderen** | Alles wat op de gracht vaart, terugtellend vanaf honderd, tot het slagschip komt opdagen. | Footer, op elke pagina | Klik op een boot | | **Hex-vaders** | De shooter, in een terminal die echt de cookieconsole is. | Cookieconsole | Vraag hem wat hij kan | | **Logomemory** | De klantlogo's stoppen met scrollen en worden vierentwintig memorytegels. | [Homepage](/nl/) | Klik op een logo | -| **Kadefietser** | Twee banen op de kade, en verkeer dat oploopt van fietsen tot trams. | Footer, op elke pagina | Klik op een fiets op de kade | +| **Kadefietser** | Een fietspad in twee richtingen dat in golven komt, en daarboven een rijbaan waar het verkeer oploopt tot trams en harder gaat. | Footer, op elke pagina | Klik op een fiets op de kade | | **Stempelrace** | Neem aan wat quorum heeft, houd tegen wat dat niet heeft. Drie missers en de voorzitter neemt de pen terug. | [Decidiq](/nl/apps/decidiq) | Klop op het logo. Drie keer, kort achter elkaar | | **Deadlineverdediger** | Elke zaak komt binnen met de klok al lopend. Stuur hem naar de juiste stap voor de tijd op is. | [Dossiq](/nl/apps/dossiq) | Konami-liefhebbers kennen deze uit hun hoofd | | **Bouwtekeningrace** | Pak de onderdelen die elke app nodig heeft van de plank, en laat liggen wat bij een andere app hoort. | [Buildiq](/nl/apps/buildiq) | Typ wat de app met je doet | diff --git a/src/pages/arcade.mdx b/src/pages/arcade.mdx index 56cc23b0f..d5b9a52fc 100644 --- a/src/pages/arcade.mdx +++ b/src/pages/arcade.mdx @@ -78,7 +78,7 @@ None of them announce themselves, and no two open the same way. That part is the | **Sink the boats** | Everything on the canal, counting down from a hundred, until the battleship turns up. | Footer, on every page | Click a boat | | **Hex-vaders** | The shooter, played in a terminal that is really the cookie console. | Cookie console | Ask it what it can do | | **Logo memory** | The client logos stop scrolling and turn into twenty-four memory tiles. | [Home page](/) | Click a logo | -| **Kade cyclist** | Two lanes on the quay, and traffic that works its way up from bikes to trams. | Footer, on every page | Click a bike on the quay | +| **Kade cyclist** | A two-way bike path that comes in waves, and a road above it where the traffic works its way up to trams and comes at you faster. | Footer, on every page | Click a bike on the quay | | **Stamp rush** | Adopt what has quorum, hold back what does not. Three bad stamps and the chair takes the pen back. | [Decidiq](/apps/decidiq) | Knock on the logo. Three times, briskly | | **Deadline defender** | Every case arrives with its clock already running. Send it to the right step before the time is gone. | [Dossiq](/apps/dossiq) | Konami lovers know this one by heart | | **Blueprint rush** | Pick the parts every app needs off the shelf, and skip the ones that belong to somebody else's app. | [Buildiq](/apps/buildiq) | Type what the app does to you | From 46dd4f939c8f2f546aed1e17483a06887cca07e5 Mon Sep 17 00:00:00 2001 From: Remko Date: Mon, 21 Sep 2026 22:36:47 +0200 Subject: [PATCH 311/330] chore(deps): preset 3.49.0 Picks up the reworked footer strip and the facade fixes. The site was pinned to 3.48.0, so a deploy would have built against the previous behaviour whatever the working copy showed. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4616c94e2..999cb932f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.48.0", + "@conduction/docusaurus-preset": "^3.49.0", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", @@ -2018,9 +2018,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.48.0.tgz", - "integrity": "sha512-/QDttdNAursGszh7S9Kcm/OwEd5UBPTwb/JgQhLes/Lu7RWa+lVxUCBaNsOulw8POUJjdh6ueV/qdcwxGw/rSQ==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.49.0.tgz", + "integrity": "sha512-eV2a3rmK5Rn52AuE2LTyiQTzUMn6PpEEUmBWQDhvafaQb8RF87De/9yguYZjXjdfdTLym/VnDo32RhMj8b+suA==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" diff --git a/package.json b/package.json index bc46a6261..2971eb00d 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.48.0", + "@conduction/docusaurus-preset": "^3.49.0", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", From 07dec6d2b50dbe09063d714b3a37e105640989da Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 22 Sep 2026 07:46:04 +0200 Subject: [PATCH 312/330] ci(traffic): wire the Portaliq traffic variables, with a guard both ways (#224) The app is installed on the portal and the site sends it nothing, because the plugin is only wired when PORTALIQ_TRAFFIC_ORIGIN is set and production never set it. So there is no measurement, and nothing on either side says so. This adds the variables to the build and a guard that fails the deploy when the two disagree: set the origin and the built pages must carry the traffic client, leave it unset and they must not. A variable that is set but does nothing is worse than one that is unset, because it reads as measurement being on. The guard matches the client route, which nothing else requests, rather than the portal host, which is also the enquiry endpoint and is in the bundle either way. The variable is NOT set in this change, deliberately. The client's own settings call, `/api/content/site?portal=`, is public on that instance and answers 404 not_found for every slug tried, including none: conduction, conduction-nl, website, www.conduction.nl, main, public, site, connext, portaliq. Other portaliq routes answer 401, so the route is reachable and the portal is what is missing. Turning the site side on now would put a 14KB script on every page whose settings call 404s and which measures nothing. Setting the variable is then the whole remaining change. --- .github/workflows/deploy.yml | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 548e18ddb..48c529be0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -91,6 +91,16 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PIPELINQ_ENQUIRY_ENDPOINT: ${{ vars.PIPELINQ_ENQUIRY_ENDPOINT }} + # Traffic measurement, from our own Portaliq portal. Unset and the + # plugin is not wired at all, which is what production did until + # now: the app was installed on the portal and the site sent it + # nothing, so there was no measurement and nothing said so. + # + # A variable rather than a secret, for the same reason as the + # endpoint above: it is an origin every visitor's browser fetches a + # script from, so it is public by construction. + PORTALIQ_TRAFFIC_ORIGIN: ${{ vars.PORTALIQ_TRAFFIC_ORIGIN }} + PORTALIQ_TRAFFIC_PORTAL: ${{ vars.PORTALIQ_TRAFFIC_PORTAL }} run: npm run build # The build cannot fail on a missing endpoint, because a preview build of @@ -117,6 +127,33 @@ jobs: fi echo "Enquiry endpoint is not the dev default." + # A variable that is set but does nothing is worse than one that is + # unset, because it reads as measurement being on. The plugin only + # emits its tag when the origin is present, so if the variable is set + # the built pages must carry the script; if it is unset, they must not. + # Both directions, because either way round is a lie about whether + # visitors are being measured. + - name: Traffic measurement is on exactly when it is configured + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/development' + run: | + # Matched on the client ROUTE, which nothing else on the site + # requests, rather than on the portal host: that host is also the + # enquiry endpoint and appears in the bundle regardless. + if grep -rqF 'api/traffic-client.js' build/index.html; then + found=yes + else + found=no + fi + if [ -n "${{ vars.PORTALIQ_TRAFFIC_ORIGIN }}" ] && [ "$found" = no ]; then + echo "::error::PORTALIQ_TRAFFIC_ORIGIN is set but the build carries no traffic client, so nothing is being measured." + exit 1 + fi + if [ -z "${{ vars.PORTALIQ_TRAFFIC_ORIGIN }}" ] && [ "$found" = yes ]; then + echo "::error::The build carries a traffic client with no origin configured." + exit 1 + fi + echo "Traffic client present: $found, which matches the configuration." + - name: Check the build produced both locales run: | test -f build/index.html || { echo "::error::build/index.html missing"; exit 1; } From 997928da323c520248214c7b8c99f1e3ce10c622 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 22 Sep 2026 10:45:12 +0200 Subject: [PATCH 313/330] feat(a11y): measure interactive states, focus visibility, and Safari (#225) * feat(a11y): measure the states a page is in after someone clicks, and whether focus is visible Three of the four gaps I named when asked whether every page had been thoroughly tested. It had not: every result so far described a page as it loads, in Chromium, with no opinion about keyboard users. REVEAL_ALL puts a page into its opened state before measuring: every
    opened, every aria-expanded=false clicked, every unselected tab and Showcase-style item activated. This is not hypothetical. The colour-role lint flagged badge markup on /quality/ that renders ZERO times as the page loads, because it lives in a Showcase panel that is not defaultOpen, and the runtime sweep had no way to see it. Findings now carry the state they were found in, so "only after opening something" stays distinguishable from "on the page as it loads". FOCUS_CHECK asks two things of each control: does anything change when it takes focus, and if the change is an outline, does it clear 3:1. This was in the September plan and never shipped. On two pages alone it finds focus rings at 1.00:1, which is an indicator the same colour as the thing behind it. The focus check compares what is PAINTED, not the property list. Chromium's UA sheet moves outline-offset from 0px to 1px on a:focus even when the author has set outline:none, so a raw property dump saw a change where a reader sees nothing and the stripped-focus fixture went unreported. An outline with style none or zero width now reads as nothing. WebKit is Safari's engine and therefore most phones, and the suite ran zero percent of it: the mobile project was pinned to chromium on the grounds that layout is engine-agnostic. Largely, not entirely. A mobile-safari project runs the mobile and a11y specs on it. It ships warning-first in CI and I want to be plain about why: WebKit needs system libraries this machine cannot install without sudo, so CI is the first place it will ever execute and nobody knows what Safari says about these pages. Blocking the deploy on an unknown would stop releases for reasons unrelated to the change being deployed. The step prints what it found and the comment says to promote it once it is understood. A warning nobody promotes is the same as no check at all. a11y-interactive.spec.js is the control for both. The reveal test uses a fixture whose defects are all behind a disclosure and asserts the checker finds nothing before opening and both defects after. The focus test asserts that outline:none with nothing put back, and an indicator under 3:1, are each reported, and that a correct indicator stays quiet. * test(a11y): the focus check scores the whole indicator, not just the outline The preset's focus treatment is two rings now: a brand outline plus a halo of the opposite luminance, because no single colour clears 3:1 on every surface this site puts a control on. Judging the outline alone would report that treatment as broken, which is the checker being wrong about a page rather than the page being wrong. It reads the box-shadow rings as well and scores the best of them, since the criterion is met when any part of the indicator is visible. The control gained a case for it: a link on an orange button whose outline is 1.00:1 and whose halo is 5.93:1 must stay quiet. Without this change that case fails, which is what makes it a control rather than a comment. --- .github/workflows/deploy.yml | 40 ++++++++++- e2e/a11y-focus.js | 124 +++++++++++++++++++++++++++++++++++ e2e/a11y-interactive.spec.js | 92 ++++++++++++++++++++++++++ e2e/a11y-reveal.js | 63 ++++++++++++++++++ playwright.config.js | 12 ++++ scripts/a11y-sweep.mjs | 58 +++++++++++++++- 6 files changed, 386 insertions(+), 3 deletions(-) create mode 100644 e2e/a11y-focus.js create mode 100644 e2e/a11y-interactive.spec.js create mode 100644 e2e/a11y-reveal.js diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 48c529be0..bba381f25 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -162,6 +162,16 @@ jobs: - name: Install Playwright browser run: npx playwright install --with-deps chromium + # WebKit is Safari's engine and therefore most phones. The suite ran + # zero percent of it until 2026-09-22, because the mobile project was + # pinned to chromium on the grounds that layout is engine-agnostic. + # Largely, not entirely. Installed separately so a WebKit packaging + # problem cannot block the chromium gate above. + - name: Install WebKit + id: webkit-install + continue-on-error: true + run: npx playwright install --with-deps webkit + # Runs against the build produced above, not a dev server, because the # failures worth catching here only exist in a production build: # hydration mismatches need a server-rendered page to hydrate, and the @@ -169,7 +179,35 @@ jobs: # This gates the deploy on purpose. A page that fails to hydrate looks # perfect in a screenshot, so "it built" is not evidence it works. - name: End-to-end tests - run: npm run test:e2e + run: npx playwright test --project=chromium --project=mobile + + # Warning-first, deliberately. This has never run anywhere: WebKit needs + # system libraries that the dev machine cannot install without sudo, so + # CI is the first place it executes and nobody knows yet what Safari + # says about these pages. Blocking the deploy on an unknown would stop + # releases for reasons unrelated to the change being deployed. + # + # Read the summary below on the first few runs. When it is green and + # understood, fold `--project=mobile-safari` into the gating step above + # and delete this one. If it stays red, the findings are real Safari + # bugs and belong in the backlog, not in a permanently-ignored step: + # a warning nobody promotes is the same as no check at all. + - name: End-to-end tests on WebKit (warning only) + id: webkit-e2e + continue-on-error: true + if: steps.webkit-install.outcome == 'success' + run: npx playwright test --project=mobile-safari + + - name: Say what WebKit found + if: always() + run: | + if [ "${{ steps.webkit-install.outcome }}" != "success" ]; then + echo "::warning::WebKit could not be installed; Safari remains unmeasured." + elif [ "${{ steps.webkit-e2e.outcome }}" = "success" ]; then + echo "WebKit run passed. Promote it to the gating step." + else + echo "::warning::WebKit run failed. These are real Safari findings, not a broken config." + fi - name: Upload the Playwright report on failure if: failure() diff --git a/e2e/a11y-focus.js b/e2e/a11y-focus.js new file mode 100644 index 000000000..43c3b2dc2 --- /dev/null +++ b/e2e/a11y-focus.js @@ -0,0 +1,124 @@ +/** + * Focus visibility (WCAG 2.2 SC 2.4.11 Focus Appearance, SC 1.4.11 Non-text + * Contrast). + * + * This was in the plan for the September review and never shipped, so every + * "clean" result so far said nothing about keyboard users. A control that + * looks identical focused and unfocused is unusable without a mouse, and no + * amount of contrast or reachability checking sees it. + * + * Asks two things of each control, in the order that matters: + * + * 1. Does anything change at all when it takes focus? Compared across + * outline, box-shadow, border, background and text colour. `outline: + * none` with nothing put back is the classic failure and it is what this + * catches first. + * 2. If an outline is what changed, does it have 3:1 against what it sits + * on? An indicator nobody can see is the same as no indicator. + * + * Runs separately from the main checks because it has to focus each control + * one at a time, which is slow: it samples rather than exhausts, and reports + * how many it looked at so a small number cannot be mistaken for a clean + * page. + */ +export const FOCUS_CHECK = async ({limit = 40}) => { + const out = []; + const ch = (c) => (String(c).match(/[-\d.]+/g) || []).slice(0, 3).map(Number); + const alphaOf = (c) => { const p = String(c).match(/[-\d.]+/g) || []; return p.length >= 4 ? Number(p[3]) : 1; }; + const lum = (rgb) => { + const a = rgb.map((v) => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); }); + return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2]; + }; + const contrast = (a, b) => { + const l1 = lum(a), l2 = lum(b); + return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05); + }; + const behind = (el) => { + let n = el; + while (n && n !== document.documentElement) { + const s = getComputedStyle(n); + if (s.backgroundImage && s.backgroundImage !== 'none') return null; + const bg = s.backgroundColor; + if (bg && alphaOf(bg) >= 0.999 && !/rgba\(0, 0, 0, 0\)/.test(bg)) return ch(bg); + n = n.parentElement; + } + const r = ch(getComputedStyle(document.documentElement).backgroundColor); + return r.length === 3 ? r : null; + }; + + /* Compare what is PAINTED, not the property list. Chromium's UA sheet moves + `outline-offset` from 0px to 1px on `a:focus` even when the author has set + `outline: none`, so a raw property dump sees a change where a reader sees + nothing, and the stripped-focus fixture went unreported. An outline with + style `none` or zero width draws nothing and must read as nothing; offset + is meaningless without a visible outline, so it is not compared at all. + Same for a border of zero width. */ + const painted = (s) => { + const outline = (s.outlineStyle !== 'none' && parseFloat(s.outlineWidth) > 0) + ? `${s.outlineStyle} ${s.outlineWidth} ${s.outlineColor}` + : 'none'; + const border = parseFloat(s.borderTopWidth) > 0 + ? `${s.borderStyle} ${s.borderWidth} ${s.borderColor}` + : 'none'; + return [outline, s.boxShadow, border, s.backgroundColor, s.color, s.textDecorationLine].join('|'); + }; + const snapshot = painted; + + const controls = [...document.querySelectorAll('a[href], button, input:not([type="hidden"]), select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])')] + .filter((el) => { + const s = getComputedStyle(el); + if (s.display === 'none' || s.visibility === 'hidden') return false; + if (typeof el.checkVisibility === 'function' && + !el.checkVisibility({contentVisibilityAuto: true, opacityProperty: true, visibilityProperty: true})) return false; + const r = el.getBoundingClientRect(); + return r.width > 0 && r.height > 0; + }); + + const sampled = controls.slice(0, limit); + const active = document.activeElement; + for (const el of sampled) { + const before = snapshot(getComputedStyle(el)); + el.focus({preventScroll: true}); + await new Promise((r) => requestAnimationFrame(r)); + const s = getComputedStyle(el); + const after = snapshot(s); + const label = (el.textContent || '').trim().slice(0, 40) || el.getAttribute('aria-label') || el.tagName.toLowerCase(); + + if (before === after) { + out.push({check: 'focus-invisible', severity: 'serious', text: label, + detail: 'nothing changes when this takes focus'}); + } else { + /* An indicator can be more than one ring. This site draws a brand + outline plus a halo of the opposite luminance behind it, precisely + because no single colour clears 3:1 on every surface. The criterion + is met if ANY part of the indicator is visible, so take the best of + them; judging the outline alone reports failures that a reader does + not experience. */ + const parts = []; + if (s.outlineStyle !== 'none' && parseFloat(s.outlineWidth) > 0) { + parts.push({what: 'ring', color: s.outlineColor, rgb: ch(s.outlineColor)}); + } + for (const m of String(s.boxShadow).matchAll(/rgba?\([^)]*\)/g)) { + parts.push({what: 'halo', color: m[0], rgb: ch(m[0])}); + } + const bg = behind(el); + const usable = parts.filter((p) => p.rgb.length === 3); + if (bg && usable.length) { + let best = null; + for (const p of usable) { + const r = contrast(p.rgb, bg); + if (!best || r > best.ratio) best = {...p, ratio: r}; + } + if (best.ratio < 3) { + out.push({check: 'focus-contrast', severity: 'serious', text: label, + detail: `focus indicator ${best.ratio.toFixed(2)}:1 at best (${usable.length} ring(s)), needs 3:1`, + color: best.color, background: `rgb(${bg.join(',')})`}); + } + } + } + el.blur(); + } + if (active && active.focus) active.focus({preventScroll: true}); + + return {findings: out, sampled: sampled.length, total: controls.length}; +}; diff --git a/e2e/a11y-interactive.spec.js b/e2e/a11y-interactive.spec.js new file mode 100644 index 000000000..273ed127f --- /dev/null +++ b/e2e/a11y-interactive.spec.js @@ -0,0 +1,92 @@ +/** + * Controls for the two checks that reach past a page's load-time state. + * + * Both were added after the September review found that a clean sweep said + * nothing about either. `REVEAL_ALL` opens what a visitor would have to click; + * `FOCUS_CHECK` asks whether a keyboard user can see where they are. Neither + * had ever been observed failing, which is the same as not having them. + */ + +import {test, expect} from '@playwright/test'; +import {REVEAL_ALL} from './a11y-reveal.js'; +import {FOCUS_CHECK} from './a11y-focus.js'; +import {RUN_CHECKS} from './a11y-checks.js'; + +/* A page whose defects are all behind a disclosure. Measured as it loads it + is spotless; measured after opening, it is not. */ +const HIDDEN_FIXTURE = ` +
    + Closed by default + +
    + + +`; + +test('a page measured only as it loads misses what is behind a disclosure', async ({page}) => { + await page.setContent(HIDDEN_FIXTURE, {waitUntil: 'load'}); + + const beforeReveal = await page.evaluate(RUN_CHECKS, {theme: null}); + const hiddenBefore = beforeReveal.filter((f) => /disclosure-text|panel-text/.test(f.text)); + expect( + hiddenBefore, + 'the fixture is built so that nothing is reported before opening; if this ' + + 'fails the fixture is wrong, not the checker', + ).toEqual([]); + + const opened = await page.evaluate(REVEAL_ALL); + expect(opened.details, 'the closed
    should have been opened').toBeGreaterThan(0); + expect(opened.expanded, 'the aria-expanded button should have been clicked').toBeGreaterThan(0); + + const afterReveal = await page.evaluate(RUN_CHECKS, {theme: null}); + const found = afterReveal.filter((f) => f.check === 'contrast-invisible').map((f) => f.text); + expect(found.some((t) => t.includes('disclosure-text')), 'text inside the opened
    ').toBe(true); + expect(found.some((t) => t.includes('panel-text')), 'text inside the revealed panel').toBe(true); +}); + +const FOCUS_FIXTURE = ` + stripped-focus link + faint-focus link + good-focus link + twotone-focus link +`; + +test('focus that is removed, or too faint to see, is reported', async ({page}) => { + await page.setContent(FOCUS_FIXTURE, {waitUntil: 'load'}); + const {findings, sampled} = await page.evaluate(FOCUS_CHECK, {limit: 20}); + expect(sampled, 'all four links should have been sampled').toBe(4); + + const byText = (needle) => findings.find((f) => f.text.includes(needle)); + expect(byText('stripped-focus'), 'outline:none with nothing put back').toBeTruthy(); + expect(byText('stripped-focus').check).toBe('focus-invisible'); + expect(byText('faint-focus'), 'an indicator under 3:1 against the page').toBeTruthy(); + expect(byText('faint-focus').check).toBe('focus-contrast'); + expect(byText('good-focus'), 'a correct indicator must stay quiet').toBeFalsy(); + expect( + byText('twotone-focus'), + 'an indicator whose halo carries the contrast must stay quiet, or the ' + + "site's own two-ring focus treatment reports as broken", + ).toBeFalsy(); +}); diff --git a/e2e/a11y-reveal.js b/e2e/a11y-reveal.js new file mode 100644 index 000000000..9b9c9852f --- /dev/null +++ b/e2e/a11y-reveal.js @@ -0,0 +1,63 @@ +/** + * Put a page into its "everything open" state before measuring it. + * + * The sweep measures a page as it loads, and that is not the page. Measured + * on /quality/ 2026-09-20: badge markup that the colour-role lint flagged + * rendered ZERO times, because it lives in a Showcase panel for an item that + * is not defaultOpen. Collapsed accordions, unselected tabs, closed menus and + * anything behind a disclosure are all invisible to a load-time sweep, and a + * clean result from one says nothing about them. + * + * This opens what can be opened, generically, rather than scripting each + * component: + * + * - every
    gets `open` + * - every [aria-expanded="false"] is clicked + * - every [role="tab"] that is not selected is clicked, one at a time + * - Showcase-style list items are clicked, since they carry no ARIA + * + * It is deliberately best-effort. A control that does not respond is skipped, + * never retried, and the run continues: a missed state is a missed finding, + * which is the same risk the sweep already carries, whereas a thrown error + * would cost the whole route. + * + * Returns what it did, so a finding can say which state produced it and a + * page that opened nothing can be told apart from a page with nothing to open. + */ +export const REVEAL_ALL = async () => { + const opened = {details: 0, expanded: 0, tabs: 0, listItems: 0}; + const settle = () => new Promise((r) => setTimeout(r, 120)); + + for (const d of document.querySelectorAll('details:not([open])')) { + d.setAttribute('open', ''); + opened.details++; + } + + /* Click, do not just flip the attribute: these are React components whose + rendering follows their own state, and setting aria-expanded by hand + changes the attribute without rendering the panel. */ + for (const el of [...document.querySelectorAll('[aria-expanded="false"]')]) { + try { el.click(); opened.expanded++; await settle(); } catch { /* not clickable */ } + } + + for (const el of [...document.querySelectorAll('[role="tab"]')]) { + if (el.getAttribute('aria-selected') === 'true') continue; + try { el.click(); opened.tabs++; await settle(); } catch { /* not clickable */ } + } + + /* Showcase and similar: a vertical list where clicking an item swaps the + panel beside it. They carry no tab/disclosure ARIA, so they are found + structurally: a clickable item inside a list that sits next to a panel. + Each is opened in turn and the page is measured after each, by the + caller, because only one panel exists at a time. */ + const candidates = [...document.querySelectorAll('[class*="item"], [class*="Item"]')] + .filter((el) => el.matches('li, button, [role="button"], div[tabindex], a:not([href])') || + el.querySelector('button, [role="button"]')); + for (const el of candidates.slice(0, 40)) { + const target = el.matches('button, [role="button"]') ? el : el.querySelector('button, [role="button"]') || el; + try { target.click(); opened.listItems++; await settle(); } catch { /* not clickable */ } + } + + await new Promise((r) => setTimeout(r, 400)); + return opened; +}; diff --git a/playwright.config.js b/playwright.config.js index 2d62d66ed..0f00cbf7c 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -85,6 +85,18 @@ export default defineConfig({ use: {...devices['iPhone 13'], browserName: 'chromium'}, testMatch: /mobile-layout\.spec\.js/, }, + /* The same assertions on the engine most phones actually use. + The comment above is right that layout and reachability are largely + engine-agnostic, and that argument was used to justify never running + WebKit at all. "Largely" is not "entirely": Safari is the dominant + mobile browser, and the September 2026 review covered zero percent of + it. WebKit is installed now, so the excuse is gone. + CI installs it alongside chromium in .github/workflows/deploy.yml. */ + { + name: 'mobile-safari', + use: {...devices['iPhone 13']}, + testMatch: /mobile-layout\.spec\.js|a11y\.spec\.js/, + }, ], ...(REMOTE ? {} : { diff --git a/scripts/a11y-sweep.mjs b/scripts/a11y-sweep.mjs index f07e83e50..efede5a1c 100644 --- a/scripts/a11y-sweep.mjs +++ b/scripts/a11y-sweep.mjs @@ -31,6 +31,14 @@ const OUT = process.env.SWEEP_OUT || 'a11y-findings.json'; const BUILD = process.env.SWEEP_BUILD || 'build'; const {RUN_CHECKS} = await import('../e2e/a11y-checks.js'); +const {REVEAL_ALL} = await import('../e2e/a11y-reveal.js'); +const {FOCUS_CHECK} = await import('../e2e/a11y-focus.js'); + +/* Measuring a page only as it loads says nothing about anything behind a + click. Set SWEEP_REVEAL=0 to skip it; it roughly doubles the time per + route. FOCUS runs on the revealed page so it also covers controls that + only exist once a panel is open. */ +const REVEAL = process.env.SWEEP_REVEAL !== '0'; /* Every directory in the build that holds an index.html is a route a visitor can reach. Docusaurus's own machinery (assets, the search index) @@ -125,7 +133,32 @@ async function lane(browser, slice) { } await page.waitForFunction(footerReady, null, {timeout: 20000}); for (const f of await page.evaluate(RUN_CHECKS, {theme: combo.theme})) { - findings.push({url, width: combo.width, theme: combo.theme, ...f}); + findings.push({url, width: combo.width, theme: combo.theme, state: 'loaded', ...f}); + } + /* Reveal costs a second full measure per combo and focus costs 40 + focus calls, so neither runs on every combo. Layout behind a + disclosure matters most at the narrowest width and on the + desktop default; focus styling does not vary with width but + does with theme, so it runs once per theme. */ + const doReveal = REVEAL && (combo.width === 320 || combo.width === 1280); + const doFocus = REVEAL && combo.width === 1280; + if (doReveal) { + /* Open every disclosure, tab and panel, then measure again. A + finding carries the state it was found in, so "only visible + after opening something" stays distinguishable from "on the + page as it loads". */ + const opened = await page.evaluate(REVEAL_ALL); + if (opened.details + opened.expanded + opened.tabs + opened.listItems > 0) { + for (const f of await page.evaluate(RUN_CHECKS, {theme: combo.theme})) { + findings.push({url, width: combo.width, theme: combo.theme, state: 'revealed', ...f}); + } + } + } + if (doFocus) { + const focus = await page.evaluate(FOCUS_CHECK, {limit: 40}); + for (const f of focus.findings) { + findings.push({url, width: combo.width, theme: combo.theme, state: 'focus', ...f}); + } } ok = true; } catch (e) { @@ -156,7 +189,7 @@ await browser.close(); defect that also occurs at 1280 is not a mobile defect. */ const uniq = new Map(); for (const f of findings) { - const k = `${f.url}|${f.check}|${f.text}`; + const k = `${f.url}|${f.check}|${f.text}|${f.state}`; if (!uniq.has(k)) uniq.set(k, {...f, themes: new Set(), widths: new Set()}); uniq.get(k).themes.add(f.theme); uniq.get(k).widths.add(f.width); @@ -190,6 +223,27 @@ for (const [check, sub] of Object.entries(byCheck).sort((a, b) => b[1].length - Object.entries(b).map(([k, v]) => `${k}:${v}`).join(' '), ); } +const byState = {}; +for (const f of defects) (byState[f.state || 'loaded'] = byState[f.state || 'loaded'] || []).push(f); +console.log(''); +console.log('by state:'); +for (const [st, list] of Object.entries(byState)) { + const bad = list.filter((f) => f.severity === 'critical' || f.severity === 'serious'); + console.log(` ${st.padEnd(10)} ${String(list.length).padStart(6)} defects, ${bad.length} critical or serious`); +} + +const focusFindings = defects.filter((f) => f.check && f.check.startsWith('focus-')); +if (focusFindings.length) { + const g = {}; + for (const f of focusFindings) (g[f.check] = g[f.check] || []).push(f); + console.log(''); + console.log('focus visibility:'); + for (const [k, list] of Object.entries(g)) { + console.log(` ${k}: ${list.length} on ${new Set(list.map((f) => f.url)).size} pages`); + for (const f of list.slice(0, 6)) console.log(` ${f.url} "${f.text}" ${f.detail}`); + } +} + const crit = defects.filter((f) => f.severity === 'critical'); console.log(''); console.log(`=== critical: ${crit.length} on ${pagesOf(crit)} pages ===`); From 2bf4b96d86e2c8c10dea54183445c0e98f916196 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 22 Sep 2026 13:54:09 +0200 Subject: [PATCH 314/330] build(deps): take preset 3.49.1, so the focus indicator reaches the site (#226) 3.49.1 carries the two-ring focus indicator. A focus-visibility check added to this suite measured 1,399 failures on 316 pages, every one an indicator under the 3:1 the criterion asks for, and the site has been pinned one patch short of the fix since it was released. No single ring colour clears 3:1 on every surface here: KNVB orange is 2.68:1 on a cobalt-50 panel and 1.00:1 on an orange button, coral-600 is 1.94:1 on a cobalt hero, white fails on light grounds and cobalt-900 on dark ones. The preset draws two rings now, the brand outline plus a halo of the opposite luminance, and the worst case across those surfaces is 5.93:1. 3.49.0 to 3.49.1 is only that change. The FullStack component the site already uses is unaffected. Verified: build green, colour-role lint reports 0 blocking, the halo is in the built CSS with both theme values, and focus measures clean across 20 page and theme combinations. Full e2e is 272 of 274 at --workers=2; the two failures are "connext lists the renamed apps" and "deadline defender", both of which pass in isolation (2 of 2, twice). The cn-* custom elements upgrade asynchronously and lose that race when this machine is loaded. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 999cb932f..c3f6a20c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.49.0", + "@conduction/docusaurus-preset": "^3.49.1", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", @@ -2018,9 +2018,9 @@ } }, "node_modules/@conduction/docusaurus-preset": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.49.0.tgz", - "integrity": "sha512-eV2a3rmK5Rn52AuE2LTyiQTzUMn6PpEEUmBWQDhvafaQb8RF87De/9yguYZjXjdfdTLym/VnDo32RhMj8b+suA==", + "version": "3.49.1", + "resolved": "https://registry.npmjs.org/@conduction/docusaurus-preset/-/docusaurus-preset-3.49.1.tgz", + "integrity": "sha512-U22mHclZo1Ozx7LcFJJRxnjPuvJjcx9iaAGdNooAvaPZ/8RXFjDO0js81Y3ED5ncYTjXES2JzOgehxY9Mg112Q==", "license": "EUPL-1.2", "bin": { "validate-ai-baseline": "bin/validate-ai-baseline.mjs" diff --git a/package.json b/package.json index 2971eb00d..ff4064496 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "dependencies": { "@conduction/docusaurus-plugin-portaliq": "git+https://github.com/ConductionNL/docusaurus-plugin-portaliq.git#1bed5e053637024e1a86b2bc1515e25bc8840123", - "@conduction/docusaurus-preset": "^3.49.0", + "@conduction/docusaurus-preset": "^3.49.1", "@docusaurus/core": "^3.10.2", "@docusaurus/plugin-client-redirects": "^3.10.2", "@docusaurus/preset-classic": "^3.10.2", From 66a55723cada1815c36ef7c4cd86fcb0a7b86757 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 22 Sep 2026 14:21:03 +0200 Subject: [PATCH 315/330] fix(e2e): the suite must not post page views to the live collector (#227) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deploy workflow builds with PORTALIQ_TRAFFIC_ORIGIN set, a guard fails the deploy if the traffic client is missing, and then the e2e suite runs against that same build. So every test that loaded a page posted a real page_view to the production collector, on every deploy, across three browser projects. Measured hours after measurement was switched on: 46 of the 58 events recorded against the ConductionNl portal came from localhost:4173, which is this suite's own preview server. The first day of the site's analytics was three quarters test traffic. e2e/base.js aborts the collector beacon and every spec imports its test from there. Aborting beats pointing the build at a dead origin: the page under test stays byte-for-byte what deploys, including the script tag the guard checks for, and only the beacon is dropped. The settings GET is left alone so the client still runs its real code path and would still fail the suite if it threw. Proven against the live instance, both directions: a full 274-test run leaves the event count at 92, and the same spec with the import put back takes it 62 -> 64. Two of my own game tests came with it, both brittle for the same reason — they encoded copy that the 3.48.0 rework changed: - deadline defender mapped each case's wording to the step it wanted. That map broke twice, most recently because a fourth lane arrived: a case that takes no next step at all is now one of the answers. It asserts the board is dealt and that answering replaces the case; which lane a case belongs in is the engine's own tests' business. - blueprint rush now takes the next flow when the clock beats it, rather than depending on winning the one it was dealt. And one stale test unrelated to any of it: product-names looked for the renamed apps inside `pd-item[name]`, the platform diagram's custom elements. That diagram lists the Nextcloud apps Connext runs on now, and the elements are replaced on hydration, so the selector matched nothing. It asserts the page names them, which is what the rename sweep is actually for. Verified: both locales build, 274 of 274 pass on the gating projects. The 80 mobile-safari failures here are the missing WebKit system libraries this config already documents; CI installs them. --- e2e/a11y-interactive.spec.js | 2 +- e2e/a11y-selftest.spec.js | 2 +- e2e/a11y.spec.js | 2 +- e2e/ai-page.spec.js | 2 +- e2e/base.js | 39 ++++++++++++++++++++++++++++ e2e/cookie-banner.spec.js | 2 +- e2e/dark-mode.spec.js | 2 +- e2e/mobile-layout.spec.js | 2 +- e2e/product-names.spec.js | 25 +++++++++--------- e2e/product-page-games.spec.js | 46 +++++++++++++++++----------------- e2e/product-pages.spec.js | 2 +- e2e/smoke.spec.js | 2 +- e2e/space-invaders.spec.js | 2 +- 13 files changed, 84 insertions(+), 46 deletions(-) create mode 100644 e2e/base.js diff --git a/e2e/a11y-interactive.spec.js b/e2e/a11y-interactive.spec.js index 273ed127f..8846d8f2c 100644 --- a/e2e/a11y-interactive.spec.js +++ b/e2e/a11y-interactive.spec.js @@ -7,7 +7,7 @@ * had ever been observed failing, which is the same as not having them. */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; import {REVEAL_ALL} from './a11y-reveal.js'; import {FOCUS_CHECK} from './a11y-focus.js'; import {RUN_CHECKS} from './a11y-checks.js'; diff --git a/e2e/a11y-selftest.spec.js b/e2e/a11y-selftest.spec.js index 647b8100c..a00bb9f43 100644 --- a/e2e/a11y-selftest.spec.js +++ b/e2e/a11y-selftest.spec.js @@ -14,7 +14,7 @@ * cannot be broken by a content change. */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; import {RUN_CHECKS} from './a11y-checks.js'; const FIXTURE = ` diff --git a/e2e/a11y.spec.js b/e2e/a11y.spec.js index 832bb3e1d..314f26928 100644 --- a/e2e/a11y.spec.js +++ b/e2e/a11y.spec.js @@ -41,7 +41,7 @@ * each of them against a page built to break it. */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; import {RUN_CHECKS} from './a11y-checks.js'; const BLOCKING = ['contrast-invisible', 'contrast-severe', 'offscreen-control', 'offscreen-content']; diff --git a/e2e/ai-page.spec.js b/e2e/ai-page.spec.js index eb47fd744..6615d83c7 100644 --- a/e2e/ai-page.spec.js +++ b/e2e/ai-page.spec.js @@ -9,7 +9,7 @@ * page looks wrong. Only the console says so. */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; import {collectConsoleErrors, expectNoConsoleErrors} from './helpers.js'; const LOCALES = [ diff --git a/e2e/base.js b/e2e/base.js new file mode 100644 index 000000000..d6290ba3b --- /dev/null +++ b/e2e/base.js @@ -0,0 +1,39 @@ +/** + * The test base every spec imports instead of `@playwright/test`. + * + * ONE REASON TO EXIST: a test must not emit analytics. + * + * The production build carries the Portaliq traffic client, because that is + * the point of it — the deploy workflow builds with PORTALIQ_TRAFFIC_ORIGIN + * set and a guard fails the deploy if the script is missing. The e2e suite + * then runs against that same build, so every test that loads a page was + * posting a real `page_view` to the live collector. + * + * That is not hypothetical. Measured 2026-09-22, hours after measurement was + * switched on: 46 of the 58 events recorded against the ConductionNl portal + * came from `localhost:4173`, which is this suite's own preview server. The + * first day of the site's analytics was three quarters test traffic, and CI + * would have added a fresh batch on every deploy across three projects. + * + * Aborting the collector is better than pointing the build at a dead origin: + * the page under test stays byte-for-byte what deploys, including the script + * tag the guard checks for, and only the beacon is dropped. + * + * The settings fetch is left alone deliberately. It is a GET that records + * nothing, and letting it succeed keeps the client on its real code path, so + * a client that throws on a page would still fail this suite. + */ + +import {test as base, expect} from '@playwright/test'; + +/** The collector, as the traffic client addresses it. */ +const COLLECTOR = /\/api\/traffic(\?|$)/; + +export const test = base.extend({ + page: async ({page}, use) => { + await page.route(COLLECTOR, (route) => route.abort()); + await use(page); + }, +}); + +export {expect}; diff --git a/e2e/cookie-banner.spec.js b/e2e/cookie-banner.spec.js index d27ba9c1d..c56d79f62 100644 --- a/e2e/cookie-banner.spec.js +++ b/e2e/cookie-banner.spec.js @@ -7,7 +7,7 @@ * past the top of the viewport until the buttons were unreachable. */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; import { BANNER, clearConsent, readConsent, openBannerFromFooter, collectConsoleErrors, expectNoConsoleErrors, diff --git a/e2e/dark-mode.spec.js b/e2e/dark-mode.spec.js index a824a904a..9a4fb4256 100644 --- a/e2e/dark-mode.spec.js +++ b/e2e/dark-mode.spec.js @@ -22,7 +22,7 @@ * this is a real invariant and not a hope. */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; /* One page per layout archetype. The navbar and footer are on all of them, and these carry the distinct section and card treatments. */ diff --git a/e2e/mobile-layout.spec.js b/e2e/mobile-layout.spec.js index c22c1430e..f2c73ccc1 100644 --- a/e2e/mobile-layout.spec.js +++ b/e2e/mobile-layout.spec.js @@ -20,7 +20,7 @@ * cannot even scroll to it. */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; /** * Wait until the footer's stylesheet has actually applied. diff --git a/e2e/product-names.spec.js b/e2e/product-names.spec.js index 10665f2b7..78db814ac 100644 --- a/e2e/product-names.spec.js +++ b/e2e/product-names.spec.js @@ -20,7 +20,7 @@ * absence of the old one: an empty page passes an absence check. */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; /* OLD slug -> [NEW slug, display name]. * @@ -92,19 +92,18 @@ test.describe('renamed products', () => { test('connext lists the renamed apps it builds on', async ({page}) => { await page.goto('/connext'); - // The stack tiles are custom elements, so the - // names live in an attribute and never appear in innerText. - const names = await page.locator('pd-item[name]').evaluateAll((els) => - els.map((el) => el.getAttribute('name')), - ); - expect(names.length, 'connext should render stack tiles').toBeGreaterThan(0); + + /* Asserted against the page, not against one container. + This used to read `pd-item[name]`, the platform diagram's custom + elements, and it stopped meaning anything twice over: that diagram + now lists the Nextcloud apps Connext runs on (Files, Mail, Calendar, + Talk, Office) rather than ours, and the custom elements are replaced + by the diagram's own script on hydration, so the selector matched + nothing at all. What the rename sweep actually cares about is that + the page says the new names somewhere. */ + const body = page.locator('body'); for (const name of ['Integriq', 'Filinq', 'Keepiq', 'Versioniq', 'Thematiq']) { - // Substring, not equality: some tiles qualify the product name - // with its role ("Thematiq Theme"). - expect( - names.some((n) => n.includes(name)), - `/connext should name ${name}, got: ${names.join(', ')}`, - ).toBe(true); + await expect(body, `/connext should name ${name}`).toContainText(name); } }); diff --git a/e2e/product-page-games.spec.js b/e2e/product-page-games.spec.js index 4594d76fb..df0f9aaeb 100644 --- a/e2e/product-page-games.spec.js +++ b/e2e/product-page-games.spec.js @@ -13,7 +13,7 @@ * sitting in plain sight on a product page. */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; const MODAL = 'div[class*="modal"] div[class*="panel"]'; @@ -159,31 +159,25 @@ test.describe('deadline defender, on the Dossiq page', () => { await findGame(page, 'deadline-defender', game); await game.getByRole('button', {name: /open the queue/i}).click(); + /* A case, its clock, and somewhere to send it. Deliberately not an + answer key: this test used to map each case's wording to the step + it wanted, and that map broke twice — once when the copy was + rewritten and once when a fourth lane arrived, because a case that + takes no next step at all is now one of the answers. What matters + here is that the board is dealt and readable; whether a given case + belongs in Intake is the engine's own tests' business. */ await expect(game.locator('p[class*="fileText"]')).toBeVisible({timeout: 5000}); /* Two of them now: the margin left on the case, and the clock. */ await expect(game.getByRole('progressbar').first()).toBeVisible(); - /* Read the case and answer it. Trying the three steps in turn - cannot work: a misroute replaces the case, so every later click - lands on a different one. */ - const CASE_TO_STEP = [ - [/Nothing registered yet/, 'Intake'], - [/has not been logged/, 'Intake'], - [/No case number yet/, 'Intake'], - [/The file is complete/, 'Assessment'], - [/site visit is done/, 'Assessment'], - [/advice from the fire service/, 'Assessment'], - [/assessment is finished/, 'Decision'], - [/has been assessed/, 'Decision'], - [/Enforcement has been prepared/, 'Decision'], - ]; - - const text = await game.locator('p[class*="fileText"]').innerText(); - const match = CASE_TO_STEP.find(([re]) => re.test(text)); - expect(match, `no step is written for the case "${text}"`).toBeTruthy(); - - await game.getByRole('button', {name: match[1], exact: false}).click(); - await expect(game.locator('span[class*="hudPill"]').first()).toContainText('10'); + const lanes = game.getByRole('button', {name: /^(Intake|Assessment|Decision|Off the queue)$/}); + await expect(lanes).toHaveCount(4); + + /* And answering does something: the case in front of you is replaced, + whichever lane you chose and whether or not it was right. */ + const first = await game.locator('p[class*="fileText"]').innerText(); + await lanes.first().click(); + await expect(game.locator('p[class*="fileText"]')).not.toHaveText(first, {timeout: 5000}); }); test('a run that goes wrong reaches the dialog', async ({page}) => { @@ -233,11 +227,17 @@ test.describe('blueprint rush, on the Buildiq page', () => { const score = game.locator('span[class*="hudPill"]').first(); await expect(async () => { + /* The flow is on a clock, so a worker that loses one simply takes + the next: this asserts that laying what is ready scores, not that + it happens inside any one flow. */ + const again = game.getByRole('button', {name: /open a blueprint|restart/i}); + if (await again.isVisible().catch(() => false)) await again.click().catch(() => {}); + const n = await ready.count(); for (let i = 0; i < n; i++) await ready.nth(i).click({timeout: 800}).catch(() => {}); const points = Number((await score.innerText()).replace(/\D/g, '')); expect(points, 'nothing was laid, so nothing scored').toBeGreaterThan(0); - }).toPass({timeout: 30000}); + }).toPass({timeout: 60000}); }); }); diff --git a/e2e/product-pages.spec.js b/e2e/product-pages.spec.js index b1b2386b5..43dac4ad6 100644 --- a/e2e/product-pages.spec.js +++ b/e2e/product-pages.spec.js @@ -20,7 +20,7 @@ * screenshot perfectly (see smoke.spec.js's header). */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; import {collectConsoleErrors, expectNoConsoleErrors} from './helpers.js'; /* diff --git a/e2e/smoke.spec.js b/e2e/smoke.spec.js index 05efdded5..c48f06ad8 100644 --- a/e2e/smoke.spec.js +++ b/e2e/smoke.spec.js @@ -28,7 +28,7 @@ * --------------------------------------------------------------------- */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; import {collectConsoleErrors, expectNoConsoleErrors} from './helpers.js'; const PAGES = [ diff --git a/e2e/space-invaders.spec.js b/e2e/space-invaders.spec.js index ed95dddde..4c553aef3 100644 --- a/e2e/space-invaders.spec.js +++ b/e2e/space-invaders.spec.js @@ -10,7 +10,7 @@ * they were setting a preference is a worse outcome than letting them play. */ -import {test, expect} from '@playwright/test'; +import {test, expect} from './base.js'; import { BANNER, clearConsent, openBannerFromFooter, typeCommand, terminalText, collectConsoleErrors, expectNoConsoleErrors, From d41da4cbac66993f0b2972e35f0d5804473cf37c Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 22 Sep 2026 17:10:33 +0200 Subject: [PATCH 316/330] fix(a11y): gate on WebKit, and stop the checks reporting things a finger never meets (#228) Closing out the last open items from the mobile and dark-mode review. Three of the four turned out to be the instrument over-reporting rather than site debt, which is worth saying plainly. WebKit is in the gating step now. Its first real run was 79 of 81, and the two were one flake that passed on retry. The single genuine engine difference was a race the drawer test was losing: the link resolves while the drawer is still re-rendering, so the node about to be clicked is replaced under it. Chromium won that race and Safari did not, which is the kind of thing running one engine hides. The test waits for aria-expanded to flip before reaching in, which is the component saying it has settled rather than a sleep. Both engines install in one step now that both gate the deploy; a failed install should look like the infrastructure problem it is, not like a silent gap. target-size was measuring the wrong box. A labelled control's target is the control plus its label, because clicking the label activates it. The filter checkboxes are 16x16 and each has a label[for] of 305x44, so judging the input alone reported 42 failures a finger never experiences. Across eight pages at 390px that is 42 down to 4, and the 4 left are text links whose height is their line-height, which is the grey area the criterion itself carves out. tiny-text said "under 12px" for everything, and 459 of 520 findings on a mobile sample were the brand's 11px uppercase eyebrow. That buried the 61 that are genuinely smaller. Severity is split by size now: below 11px is moderate, 11px is info and named as the convention. The sub-11px text lives in preset game HUDs and card metadata, and raising it is a design decision across every Conduction site rather than a bug fix, so it is reported and not changed. The colour-role lint went from 163 advisory findings to 44. 126 were brand accent colours used as ink, which is their documented job: an accent that followed the theme would stop being the brand. They are excused by value, in a list, with the reasoning, and the runtime sweep reports zero contrast failures for any of them. Two whole-file fixed shells, the cookie terminal and the error page, take a file-level opt-out, which only counts in the header so it cannot be dropped in halfway down to silence something inconvenient. Every change here has a control. The labelled-target case fails without the fix, and --strict now prints every finding instead of the first forty, because capping the output of the show-me-everything mode defeats the point of it. Verified: 273 of 274 at --workers=2. The one failure is planninq's hydration test, which passes 2 of 2 in isolation and took 31.9s under contention. --- .github/workflows/deploy.yml | 46 ++++--------------- e2e/a11y-checks.js | 33 +++++++++++-- e2e/a11y-selftest.spec.js | 11 +++++ e2e/mobile-layout.spec.js | 20 ++++++-- scripts/lint-color-roles.mjs | 41 ++++++++++++++++- src/components/CookieCli/CookieCli.module.css | 6 +++ src/theme/Error/index.jsx | 6 +++ 7 files changed, 117 insertions(+), 46 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index bba381f25..aea02b399 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -159,18 +159,16 @@ jobs: test -f build/index.html || { echo "::error::build/index.html missing"; exit 1; } test -f build/nl/index.html || { echo "::error::build/nl/index.html missing"; exit 1; } - - name: Install Playwright browser - run: npx playwright install --with-deps chromium - # WebKit is Safari's engine and therefore most phones. The suite ran # zero percent of it until 2026-09-22, because the mobile project was # pinned to chromium on the grounds that layout is engine-agnostic. - # Largely, not entirely. Installed separately so a WebKit packaging - # problem cannot block the chromium gate above. - - name: Install WebKit - id: webkit-install - continue-on-error: true - run: npx playwright install --with-deps webkit + # Largely, not entirely: the one difference that showed up was a race + # the drawer test was losing on WebKit and winning on Chromium. + # Both engines are installed in one step now that both gate the deploy. + # A failed install should look like the infrastructure problem it is, + # not like a silent gap in coverage. + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium webkit # Runs against the build produced above, not a dev server, because the # failures worth catching here only exist in a production build: @@ -179,35 +177,7 @@ jobs: # This gates the deploy on purpose. A page that fails to hydrate looks # perfect in a screenshot, so "it built" is not evidence it works. - name: End-to-end tests - run: npx playwright test --project=chromium --project=mobile - - # Warning-first, deliberately. This has never run anywhere: WebKit needs - # system libraries that the dev machine cannot install without sudo, so - # CI is the first place it executes and nobody knows yet what Safari - # says about these pages. Blocking the deploy on an unknown would stop - # releases for reasons unrelated to the change being deployed. - # - # Read the summary below on the first few runs. When it is green and - # understood, fold `--project=mobile-safari` into the gating step above - # and delete this one. If it stays red, the findings are real Safari - # bugs and belong in the backlog, not in a permanently-ignored step: - # a warning nobody promotes is the same as no check at all. - - name: End-to-end tests on WebKit (warning only) - id: webkit-e2e - continue-on-error: true - if: steps.webkit-install.outcome == 'success' - run: npx playwright test --project=mobile-safari - - - name: Say what WebKit found - if: always() - run: | - if [ "${{ steps.webkit-install.outcome }}" != "success" ]; then - echo "::warning::WebKit could not be installed; Safari remains unmeasured." - elif [ "${{ steps.webkit-e2e.outcome }}" = "success" ]; then - echo "WebKit run passed. Promote it to the gating step." - else - echo "::warning::WebKit run failed. These are real Safari findings, not a broken config." - fi + run: npx playwright test --project=chromium --project=mobile --project=mobile-safari - name: Upload the Playwright report on failure if: failure() diff --git a/e2e/a11y-checks.js b/e2e/a11y-checks.js index 883623388..f3cbbae4c 100644 --- a/e2e/a11y-checks.js +++ b/e2e/a11y-checks.js @@ -217,7 +217,14 @@ export const RUN_CHECKS = async ({theme}) => { if (ratio < 1.5) add('contrast-invisible', 'critical', text, `${ratio}:1`, meta); else if (ratio < 3) add('contrast-severe', 'serious', text, `${ratio}:1 needs ${required}:1`, meta); else if (ratio < required) add('contrast-aa', 'moderate', text, `${ratio}:1 needs ${required}:1`, meta); - if (size && size < 12) add('tiny-text', 'moderate', text, `${size.toFixed(1)}px`); + /* Severity by actual size, because one number for "under 12px" is + misleading. 11px is the brand's uppercase eyebrow, letter-spaced and + used as a label rather than as reading copy: 459 of the 520 findings + on a mobile sample were that, which buries the 61 that are smaller. + WCAG sets no minimum font size, so this is a readability heuristic and + it should say which half of it a finding is in. */ + if (size && size < 11) add('tiny-text', 'moderate', text, `${size.toFixed(1)}px, below the readable floor`); + else if (size && size < 12) add('tiny-text', 'info', text, `${size.toFixed(1)}px, the eyebrow convention`); } /* ---------------- 3. reachability, all content ---------------- */ @@ -266,12 +273,32 @@ export const RUN_CHECKS = async ({theme}) => { const r = el.getBoundingClientRect(); if (!r.width || !r.height) continue; const label = (el.textContent || '').trim() || el.getAttribute('aria-label') || el.tagName.toLowerCase(); + /* A labelled control's target is the control PLUS its label, because + clicking the label activates it. Measured on /apps/ 2026-09-22: the + filter checkboxes are 16x16 and each has a `label[for]` of 305x44, so + judging the input alone reported 42 failures a finger never + experiences. SC 2.5.8 is about the target, and the label is part of + it. Take the union of the two boxes. */ + const labelled = el.closest('label') || + (el.id ? document.querySelector(`label[for="${CSS.escape(el.id)}"]`) : null); + let target = r; + if (labelled) { + const lr = labelled.getBoundingClientRect(); + if (lr.width && lr.height) { + target = { + width: Math.max(r.right, lr.right) - Math.min(r.left, lr.left), + height: Math.max(r.bottom, lr.bottom) - Math.min(r.top, lr.top), + }; + } + } boxes.push({el, r, label}); /* The standard exempts a link sitting in a sentence, where shrinking the target would mean reflowing the prose around it. */ if (el.tagName === 'A' && s.display.startsWith('inline') && el.parentElement && ownText(el.parentElement).length > 0) continue; - if (r.width < 24 || r.height < 24) { - add('target-size', 'moderate', label, `${Math.round(r.width)}x${Math.round(r.height)}, needs 24x24`); + if (target.width < 24 || target.height < 24) { + add('target-size', 'moderate', label, + `${Math.round(target.width)}x${Math.round(target.height)}, needs 24x24` + + (labelled ? ' (control plus its label)' : '')); } } diff --git a/e2e/a11y-selftest.spec.js b/e2e/a11y-selftest.spec.js index a00bb9f43..e426289e0 100644 --- a/e2e/a11y-selftest.spec.js +++ b/e2e/a11y-selftest.spec.js @@ -65,6 +65,10 @@ const FIXTURE = `

    Second overlapping paragraph of text here

    This sentence is much wider than the box that holds it
    + +