Assessment Boekhouding: multiuser platform om samen te werken aan assessments#283
Draft
robbertbos wants to merge 33 commits intomainfrom
Draft
Assessment Boekhouding: multiuser platform om samen te werken aan assessments#283robbertbos wants to merge 33 commits intomainfrom
robbertbos wants to merge 33 commits intomainfrom
Conversation
Migrate from single form-app to a monorepo with three workspaces: - packages/assessment-core: shared assessment engine extracted from form-app - apps/boekhouding-frontend: Vue 3 SPA with Keycloak auth and project management - apps/boekhouding-backend: Fastify REST API with PostgreSQL (Drizzle ORM) - apps/standalone-form: standalone form (successor to form-app) Add container setup (Containerfiles, compose.yaml, Keycloak dev config), database migrations, and updated CI/CD workflows for the new structure.
CLAUDE.md with project conventions, assessments plugin with domain knowledge skills (assessment-schema, begrippenkader, DPIA) and a validation agent, plus a standalone RVO styling skill.
- Instance IDs use taskId (non-repeatable) or taskId[index] (repeatable) instead of taskId_nanoid, making them deterministic and comparable - Assessment output uses $schema reference to versioned JSON Schema - Export contains only answers (no navigation state), flat without namespace nesting - URN in YAML sources split into urn + version; getUrn() assembles them - Backend diffSnapshots builds URN-based field_id for assessment_edits - Snapshot migration (v1→v2) converts legacy nanoid keys on load - Schema files renamed with version: assessment-definition.v1, assessment-output.v2, begrippenkader.v1 - Answer fields renamed: timestamp→lastEditedAt, added lastEditedBy - Metadata: savedAt→createdAt, optional createdBy for audit exports - Fix: login redirect to /projecten, anchor button color inheritance
Update Claude plugin marketplace source from "local" to "directory" and rename standalone fallback URL from /standalone/ to /invulhulpen/.
Rename @par-assessment/* to @overheid-assessment/* across all packages, imports, configs and documentation. Clean up Swagger UI by hiding the Fastify topbar and servers dropdown, adding rate limit documentation to the OpenAPI spec, and increasing the rate limit to 300/min.
Edits worden de enige bron van waarheid voor assessment-state. De state-kolom is verwijderd uit assessmentVersions — state wordt opgebouwd via rebuildState() door edits te replayen. cachedState op de instance blijft de snelle read-path. Backend: - initial_state edit bij POST voor bootstrapping van rebuild - rebuildState() utility met fallback naar cachedState voor legacy data - GET /versions/:v/edits endpoint voor versie-diffs - GET /versions/:v?includeState=true rebuildt state on demand - expectedVersion check verplaatst vóór alle save-paden (inclusief no-change path) — voorkomt stale saves - expectedVersion is nu verplicht bij state-saves (400 bij ontbreken) - Consolidation verwijderd — elke save maakt een nieuwe versie voor betrouwbare optimistic locking Frontend: - VersionHistory gebruikt edits-API i.p.v. twee volledige state-diffs - Edits worden gecollapsed tot netto wijzigingen per veld - task_instance_add/remove gefilterd uit diff-weergave - HTML definition spans gestript in diff en conflict dialog - ConflictResolutionDialog.vue voor field-level conflict resolution - ApiPersistence: pendingChanges tracking, auto-merge bij geen veld-overlap, conflict dialog bij overlap - Auto-merge en resolve slaan direct op (niet via debounce) - flushSave annuleert alleen timer, geen API call bij unmount - lastSavedState altijd gezet (ook voor lege assessments) - structuredClone vervangen door JSON.parse/stringify (Vue proxies) - true/false weergegeven als Ja/Nee in conflict dialog - Lege currentRootTaskId overgeslagen bij applyAppState (restore fix) - Ophalen van taken... tekst uitgelijnd en spatie voor ellipsis verwijderd
Same-user saves within 15 minutes are consolidated into the existing version instead of creating a new one, reducing version noise during active editing. Schema renamed savedBy/savedAt to createdBy/createdAt with new updatedAt column. Debounce lowered from 2s to 500ms since consolidation now prevents version proliferation. Seed script cleaned up unused state field on assessment_versions inserts.
- Switch build and release workflows from npm to pnpm via corepack - Bump actions/checkout, setup-node, setup-python from v4 to v5 - Rename YAML sources to lowercase (case-sensitive Linux CI) - Remove PR preview workflow (not needed on this branch) - Fix standalone-form build: add env.d.ts to assessment-core, fix allowImportingTsExtensions conflict, fix clearSavedState type - Apply ruff-format to generate_licenses.py - Fix trailing newlines in Drizzle files and SKILL.md
f67eb1a to
6697937
Compare
4dcd720 to
f429263
Compare
- Move all container files to containers/ directory (Containerfiles, compose.dev.yaml, nginx config, keycloak realm) - Add build-containers.yaml workflow for GHCR (dev/frontend, dev/backend) - Rename build.yaml to build-standalone.yaml - Add nginx security headers (NCSC/BIO2) with nginx-unprivileged on port 8080 - Add runtime config.json via envsubst (no more VITE_* build-args needed) - Align env var names with ZAD platform (OIDC_URL, OIDC_REALM, OIDC_CLIENT_ID, DATABASE_SERVER_FULL, PUBLIC_HOST for CORS) - Add OIDC_INTERNAL_URL for split-horizon Keycloak in Docker networks - Add deployment.md with architecture, env vars, CI/CD, ZAD config - Remove hardcoded python-version from all workflows - Include standalone form dist (with favicon) in frontend container
f429263 to
d7e284a
Compare
- Inline favicon als data-URI in standalone HTML via custom Vite plugin - Kopieer favicon naar boekhouding-frontend public/ voor /favicon.ico - Voeg security headers toe aan standalone invulhulpen nginx location - Hernoem workflow naar "Build Containers"
053c8cd to
c9e066c
Compare
277aae6 to
d82c693
Compare
d3d5de7 to
e2e1aaa
Compare
e2e1aaa to
e676139
Compare
- Import detecteert automatisch DPIA of pre-scan via URN, namespace of answer keys — gebruiker hoeft niet meer zelf het type te kiezen - Ondersteunt zowel AssessmentOutput (flat) als AssessmentState (namespaced) formaat, inclusief v1→v2 migratie - Navigatieknoppen (vorige/volgende/voltooid) wrappen correct op kleine schermen via flexbox met responsive breakpoint - applyStateToStores en parseAndValidateImport als gedeelde utils (vervangt gedupliceerde logica in Api/LocalPersistence) - completedTasks in export-schema zodat sectiestatus bewaard blijft - snapshotBaseline in Form.vue voorkomt phantom diffs na initialisatie - Versie 1 wordt niet meer geconsolideerd (importstatus behouden) - WCAG: aria-labels op file inputs, role="group" op navigatie - ExportProvider/EXPORT_KEY verwijderd, directe exportToPdf aanroep
Contributor
🚀 Preview DeploymentYour changes have been deployed to a preview environment: api: https://assessments.rijksapp.nl frontend: https://assessments.rijksapp.nl This deployment will be automatically cleaned up when the PR is closed. |
- Voeg must-revalidate toe aan hoofd-SPA en standalone invulhulp - Browser checkt altijd bij server, krijgt 304 als ongewijzigd - Hashed assets (js/css) blijven 1 jaar gecached via immutable
- Update Autoriteit Persoonsgegevens URL (verplaatste pagina) - Exclude Docker-interne hostnames (keycloak, backend, postgres) - Exclude npmjs.com (blokkeert bots) en httpproblems.com (template literal)
Herhaalbare groepen (zoals persoonsgegevens) konden niet meerdere antwoorden opslaan. Oorzaak: het state-formaat had geen structuur voor meerdere instanties. Herschreven naar arrays met _index per element, namespace-wrapping verwijderd, completedTasks naar metadata, UI-state naar localStorage. Pre-scan import in DPIA: antwoorden gaan naar apart _prescanAnswers veld zodat usePreScanReferences ze vindt zonder DPIA-secties onterecht als voltooid te markeren. completedTasks worden niet meer afgeleid uit answer keys voor moderne exports (met $schema/urn). Backend diffStates en rebuildState herschreven voor nieuw formaat met backward-compatibiliteit voor opgeslagen legacy data.
9f1e1ef to
14c9122
Compare
Nieuw `image` veldtype voor het uploaden van afbeeldingen in assessments. Afbeeldingen worden als base64 data-URI opgeslagen in de JSON-state, waardoor exports (JSON, PDF, Markdown) volledig zelfstandig zijn. - ImageValue datamodel met optionele titel, omschrijving en bron - Drag & drop upload met visuele overlay bij vervangen - Client-side resize met formaatbehoud (PNG→PNG, JPEG→JPEG) - SVG-bestanden worden veilig gerasterd naar PNG op 2x resolutie - EXIF-metadata (GPS, auteur, camera) wordt automatisch verwijderd via Canvas API - Slimme foutmeldingen voor onondersteunde bestandstypen - PDF export: afbeeldingen als losse blokken met metadata, paginabreed - Markdown export: afbeeldingen buiten tabellen met metadata - Versiegeschiedenis: afbeelding-thumbnails in plaats van raw base64 - Legacy migratie: oude URL-referenties worden graceful afgehandeld - Assessment definition schema v2 met image type en item_name - Output schema uitgebreid met imageValue definitie - API body-limiet verhoogd naar 25 MB - YAML-bronnen verwijzen naar definitie-schema - item_name voor enkelvoudsvorm in knoppen van herhaalbare velden - Documentatie over image handling en privacy
Taak 1.2 "Afbeeldingen" is omgezet van een tekstveld (URL-referentie) naar een volledig image-veldtype met drag & drop upload, metadata en embedded opslag als WebP data-URI. - ImageValue datamodel: data (WebP base64), titel, omschrijving, bron - Drag & drop upload met visuele overlay bij vervangen - EXIF-metadata automatisch verwijderd via Canvas API - SVG veilig gerasterd naar WebP (XSS-preventie) - Lossless WebP voor diagrammen, lossy voor foto's - PDF export: on-the-fly WebP→PNG conversie (pdfmake ondersteunt geen WebP) - Markdown export: referentie-stijl links (data onderaan document) - Versiegeschiedenis: afbeelding-thumbnails i.p.v. raw base64 - Legacy migratie: oude URL-referenties graceful afgehandeld - Assessment definition schema v2 met image type en item_name - Toegankelijkheid: ARIA labels, keyboard support, live regions - Documentatie: docs/image-handling.md
Gedeelde autoGrowTextarea util toegevoegd en toegepast op alle textarea-velden. Bestaande duplicatie in ProjectDetail en VersionHistory vervangen door dezelfde util.
Open tekstvelden hebben nu een Lezen/Bewerken toggle die markdown rendert als opgemaakte tekst. Beveiligingsmaatregelen: allowlist-renderer (geen DOMPurify nodig), protocol-filtering op links (javascript:/data: gestript), raw HTML en afbeeldingen gestript, task list checkboxes als unicode. PDF-export verwerkt markdown-opmaak in antwoorden.
5a0483c to
454898d
Compare
Voegt een opmerkingensysteem toe waarmee projectleden opmerkingen kunnen plaatsen bij individuele formuliervelden, reacties kunnen geven op elkaars opmerkingen, en threads kunnen oplossen. Backend: - Comments tabel met threading (max 1 niveau diep) - REST API met CRUD, resolve/reopen, en polling via ?since= parameter - Rolgebaseerde toegang: commenter+ mag plaatsen, editor+ mag oplossen - currentUserId in response voor frontend eigenaarschap-check Frontend: - CommentPanel met positionering naast formuliervelden - CommentBadge met teller in de header - Inline "Opmerking" buttons per veld via useFieldCommentIndicators - Pinia store met 10s polling en incrementele merge - Bewerken door op eigen tekst te klikken, auto-resize textareas - Outline action-buttons met icons en kleur-coded hover states - Responsief: op mobiel stackt het panel met klikbare veldnaam-links - Panel schuift naar content via CSS transform op brede schermen - Versie-mismatch banner wanneer een collega wijzigingen aanbrengt Toegankelijkheid (WCAG 2.1 AA): - Keyboard-toegankelijke bewerkbare opmerkingen (role, tabindex, keydown) - aria-hidden op decoratieve icons, aria-label op textareas - focus-visible outlines op alle interactieve elementen - Kleurcontrast timestamps verbeterd (#999 → #666) - Opgeloste threads via border/achtergrond i.p.v. opacity
…ming Verlopen tokens werden niet gedetecteerd: saves faalden stilletjes zonder melding aan de gebruiker, waardoor werk verloren ging bij pagina-refresh. - Background token refresh elke 4 minuten via updateToken() - SessionExpiredError bij 401-responses in api.ts - Modale dialoog "Je bent uitgelogd" met re-login knop - Onopgeslagen wijzigingen bewaard in sessionStorage over re-login heen - Comment polling stopt bij sessie-verloop - User-switch detectie na re-login voorkomt data-vermenging - 23 unit tests voor auth, API-interceptie en sessionStorage-roundtrip
454898d to
40cea1d
Compare
lastModifiedAt retourneerde 1970-01-01 (Unix epoch) bij assessments zonder comments, waardoor de frontend steeds met ?since=1970 pollde. Nu retourneert de backend null en pollt de frontend zonder since-parameter.
Standaard markdown voegt regels samen tenzij er twee spaties of een lege regel tussen staan. Met breaks: true (GFM-gedrag) worden enkele newlines nu <br> in zowel de HTML-preview als de PDF-export.
Co-authored-by: mybee-bot <mybee@local> Co-committed-by: mybee-bot <mybee@local>
`syncInstances` koppelde target-instances aan bronnen via de in-memory property `mappedFromInstanceId`. Die wordt nergens gepersisteerd, dus na elke herload was ``ie leeg — iedere bron werd als nieuw gezien en kreeg een duplicaat-target via `max_index + 1`. Op productie was taak 7.1 zo opgelopen tot **569 instances** bij 3 bron-instances. Reconciliatie gebeurt nu op gedeelde `_index`, die wél persisteert. Single-pass over de index-union: match → pointer bijwerken, alleen bron → target aanmaken, alleen target → target verwijderen. ## Meegenomen fixes - **Parent-link bij nieuwe targets**: `addRepeatableTaskInstance` krijgt `task.parentId` mee, zodat TaskGroup ze via `getInstanceIdsForTask(taskId, parentInstanceId)` daadwerkelijk rendert. - **Geneste repeatables**: `rebuildRepeatableInstances` slaat repeatables over waarvan de parent-taak zelf al repeatable is. Die child-instances zijn al correct gelinkt via `createTaskInstance`'s child-propagatie; opnieuw aanmaken overschreef de parent-link met een task-id i.p.v. een instance-id, waardoor TaskGroup de outer instance als lege groep renderde. - **Sectie-brede warning bij lege bron**: `missingSourceDependencies` in TaskSection liep alleen over leaf-tasks. Dependencies op intermediate repeatables (zoals 7.1) werden zo overgeslagen — de sectie verborg zichzelf zonder melding. Recursie telt elk niveau nu mee. - **Inline waarschuwing per instance**: als een gemapte instance gekoppeld is aan een bron met lege waarde (bijv. half-ingevulde Partij namen bij sectie 6), toont 7.1 voor die instance *Vul eerst "Partij naam" in bij sectie "6. Betrokken partijen"* in plaats van lege invulvelden. Zodra de bron ingevuld wordt, verschijnt de normale inhoud. Co-authored-by: mybee-bot <mybee@local> Co-committed-by: mybee-bot <mybee@local>
Voorkomt stille dataverdwijning op twee plekken en voegt een gedeelde utility toe die uitrekent welke antwoorden onbereikbaar worden door een gebruikersactie. Drie consumers: - Delete-dialog in TaskGroup: klik op Verwijder van een repeatable instance met ingevulde afhankelijke antwoorden toont eerst een native <dialog> met opsplitsing per sectie. Instances zonder afhankelijke data worden zonder tussenklik direct weggehaald. Was: cascade-verwijdering ging stil en zonder waarschuwing. - Conditional undo-window in Form.vue: wijzigt de gebruiker een parent zodat dependents onzichtbaar worden, dan worden die antwoorden uit de store gehaald (save schrijft ze echt weg) en parallel 60 seconden in een in-memory cache gezet. Flipt de gebruiker binnen die window terug, dan komen ze weer in de store — natuurlijke undo zonder toast of knop. Was: verborgen antwoorden bleven stil in cachedState staan tot expliciet wissen. - JSON-export-filter: buildOutputData past shouldShowTask toe voor groepering, gelijk aan de pdf- en markdown-export. Was: JSON lekte antwoorden die in PDF/Markdown al gefilterd werden. Meegenomen fixes: - parseAndValidateImport behoudt groepering in de output zodat de backend-opslag ná import consistent blijft met wat latere saves opleveren. Zonder deze wijziging vertaalt diffStates de eerste save als honderden structuur-edits omdat v1 flat was opgeslagen en v2 groept. - Vue-watcher in useConditionalHideReconcile staat via een armed- flag inert tot seedFromStore draait na init. Voorkomt dat applyStateToStores + rebuildRepeatableInstances + syncInstances tijdens load worden aangezien voor gebruikerswijzigingen. Co-authored-by: mybee-bot <mybee@local> Co-committed-by: mybee-bot <mybee@local>
- Vier files (drizzle migration + meta + test fixture) missten een trailing newline, wat de end-of-file-fixer pre-commit hook liet falen. - Het voorbeeld 'http://myserver:5174' in vite.config.ts werd door de link-checker als echte URL behandeld en faalde op DNS. Scheme verwijderd zodat het een illustratief host:port is.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
assessment-corepackage,boekhouding-frontend,boekhouding-backendenstandalone-formappsNog te doen