# Crewli — Product Backlog > Gedocumenteerde wensen en features die bewust zijn uitgesteld. > Bijgewerkt: April 2026 > > **Gebruik:** Voeg nieuwe items toe als ze tijdens development ontstaan. > Geef elk item een prioriteit en fase zodra je het gaat oppakken. ## Architectuur consolidatie sprint (actief) Zie `dev-docs/ARCH-CONSOLIDATION-2026-04.md` voor volledige scope, principes en werkstroomvolgorde. Sprint gestart april 2026, 8 werkstromen, 22-32 dagen werk totaal. Tijdens de sprint worden bestaande backlog-items die door de sprint worden opgelost daar expliciet gemarkeerd, en krijgen items die na de sprint worden opgepakt een `[post-consolidatie]` tag. --- ## Fase 3 — Geplande features ### ARCH-01 — Recurrence / Terugkerende events **Aanleiding:** Schaatsbaan use case — 8 weken, elke za+zo openingsdagen. **Wat:** Organisator definieert één template sub-event met RRULE. Platform genereert automatisch alle instanties. **Details:** - RRULE formaat (RFC 5545): `FREQ=WEEKLY;BYDAY=SA,SU;UNTIL=20270126` - `events.recurrence_rule` (string nullable) — al gereserveerd in schema - `events.recurrence_exceptions` (JSON) — cancelled + modified dates - UI: "Genereer openingsdagen" wizard - Aanpassen van één instantie raakt template niet - "Alleen deze dag" / "Alle volgende dagen" / "Alle dagen" (Google Calendar patroon) **Schema:** Kolommen al aanwezig in v1.7. Alleen generator-logica ontbreekt. --- ### ARCH-02 — Min/max shifts per vrijwilliger **Aanleiding:** Zonder limiet claimen enthousiaste vrijwilligers 8+ shifts (48 uur in één weekend), resulterend in burn-out en no-shows op latere shifts. **Wat:** Per event/festival instelbaar minimum en maximum aantal shifts dat een vrijwilliger kan claimen. **Details:** - `events.min_shifts_per_volunteer` (int nullable) - `events.max_shifts_per_volunteer` (int nullable) - ShiftAssignmentService checkt limiet bij claim/assign - Portal toont voortgang: "Je hebt 2 van minimaal 4 shifts geclaimd" - Bij bereiken maximum: verdere claims geblokkeerd met melding **Prioriteit:** Laag — Nice-to-have. Geen prioriteit op dit moment. **Afhankelijk van:** Shift claiming flow --- ### ARCH-03 — Sectie templates / kopiëren van vorig event **Aanleiding:** Organisatoren die elk jaar dezelfde secties en shifts opzetten. **Wat:** "Kopieer secties van vorig festival" functie in de UI. Kopieert festival_sections + shifts structuur (zonder toewijzingen). **Details:** - UI: dropdown "Kopieer structuur van..." bij aanmaken festival - Optie: kopieer alleen secties / secties + shifts / alles - Tijden worden proportioneel aangepast aan nieuwe datums **Prioriteit:** Hoog — bespaart veel handmatig werk bij terugkerende festivals --- ### ARCH-04 — Cross-festival conflictdetectie **Aanleiding:** Vrijwilliger die bij twee festivals van dezelfde organisatie op dezelfde dag ingepland staat. **Wat:** Waarschuwing (geen blokkade) als iemand al actief is op een ander festival van dezelfde organisatie op dezelfde datum. **Details:** - Soft check — waarschuwing tonen, niet blokkeren - Relevant bij organisaties met meerdere festivals tegelijk - Query: `shift_assignments` cross-festival op person_id + datum --- ### ARCH-05 — Shift fairness / prioriteitswachtrij **Aanleiding:** Populaire shifts worden direct volgeboekt door snelle vrijwilligers. **Wat:** Optionele wachtrij-modus waarbij het systeem eerlijk verdeelt op basis van: reliability score, aantal uren al ingepland, aanmeldvolgorde. **Details:** - `shifts.assignment_mode` (enum: first_come | fair_queue | manual) - Fair queue: systeem wijst toe op basis van algoritme - Organisator keurt resultaat goed voor publicatie **Prioriteit:** Middel — nice-to-have voor grote festivals --- ### ARCH-06 — Locatie-gebaseerd shift-overzicht Cross sub-event filter op location_id. Toont alle shifts op een fysieke locatie ongeacht programmaonderdeel. **Schema:** `locations` tabel en `shifts.location_id` bestaan al. **Prioriteit:** Laag --- ### ARCH-07 — Accreditatie-templates per sectie/dag combinatie MUST-HAVE bij accreditatie build. Templates worden primaire toewijzingsmethode. Per crowd_type + sectie + dag → automatisch voorgestelde accreditatie-items. Handmatige per-persoon toewijzing is de uitzondering, niet de norm. **Schema:** Nieuwe tabel `accreditation_templates` nodig. **Prioriteit:** Hoog — direct meebouwen bij accreditatie-module --- ## Fase 3 — Communicatie & Notificaties ### COMM-01 — Real-time WebSocket notificaties **Aanleiding:** Differentiator — geen van de concurrenten heeft dit. **Wat:** Push notificaties via Laravel Echo + Soketi voor: - Nieuwe vrijwilliger aanmelding - Shift geclaimd - Uitnodiging geaccepteerd - Shift niet gevuld (waarschuwing) - No-show alert op show-dag **Tech:** Laravel Echo + Soketi (zelf-gehoste WebSocket server) **Frontend:** Notificatie bell in topbar activeren --- ### COMM-02 — Topbar volledig activeren **Aanleiding:** Vuexy topbar staat er maar is niet aangesloten op Crewli. **Wat:** - Zoekbalk (CTRL+K) aansluiten op Crewli-entiteiten (personen, events, secties zoeken) - Notificatie bell koppelen aan COMM-01 - App switcher: Organizer / Portal wisselen (admin SPA retired; platform admin in `/platform/*`) - User avatar: gekoppeld aan ingelogde gebruiker (deels al gedaan) **Prioriteit:** Middel — werkt zonder maar verbetert UX significant --- ### COMM-03 — Globale zoekfunctie (cmd+K) **Aanleiding:** Differentiator — cross-entiteit zoeken. **Wat:** Modal zoekbalk die zoekt over: personen, events, artiesten, secties, shifts **Tech:** Meilisearch of database full-text search **Prioriteit:** Laag — Fase 4 --- ### COMM-04 — SMS + WhatsApp campagnes via Zender **Aanleiding:** WeezCrew heeft dit als sterk punt. **Wat:** Bulk communicatie via Zender (zelf-gehoste SMS/WhatsApp gateway) - Normal urgency → email - Urgent → WhatsApp - Emergency → SMS + WhatsApp parallel **Tech:** ZenderService (al gedocumenteerd in dev guide) **Afhankelijk van:** Communicatie module backend --- ## Fase 3 — Show Day & Operationeel ### OPS-01 — Mission Control **Aanleiding:** In2Event's sterkste feature. **Wat:** Real-time operationele hub op show-dag: - Live check-in overzicht per sectie - Artiest handling (aankomst, soundcheck, performance status) - No-show alerts met automatische opvolging - Inventaris uitgifte (portofoons, hesjes) **Prioriteit:** Hoog voor show-dag gebruik --- ### OPS-02 — No-show automatisering **Aanleiding:** 30-minuten alert voor niet-ingecheckte vrijwilligers. **Wat:** Automatische WhatsApp/SMS via Zender als vrijwilliger niet is ingecheckt 30 min na shift-starttijd. **Schema:** `show_day_absence_alerts` al aanwezig ✅ **Afhankelijk van:** COMM-04 (Zender), OPS-01 (Mission Control) --- ### OPS-03 — Allocatiesheet PDF generator **Aanleiding:** WeezCrew heeft branded PDF per crew. **Wat:** Gepersonaliseerde PDF per vrijwilliger/crew: taakbeschrijving, tijden, locatie, QR-code voor check-in. **Tech:** DomPDF (al geïnstalleerd) **Prioriteit:** Middel --- ### OPS-04 — Scanner infrastructuur **Aanleiding:** QR check-in op locatie. **Wat:** Scanstations configureren, koppelen aan hardware. `scanners` tabel al aanwezig in schema ✅ **Prioriteit:** Laag — Fase 4 --- ## Fase 3 — Vrijwilligers & Portal ### VOL-01 — apps/portal/ vrijwilliger self-service **Aanleiding:** Vrijwilligers moeten zichzelf kunnen aanmelden en shifts claimen zonder toegang tot de Organizer app. **Wat:** - Publiek registratieformulier (multi-step) - Login portal voor vrijwilligers - Beschikbaarheid opgeven (time slots kiezen) - My Shifts overzicht - Shift claimen met conflictdetectie - "Ik kan toch niet komen" workflow **Afhankelijk van:** Sections + Shifts backend (al klaar ✅) --- ### VOL-02 — Vrijwilliger paspoort + reliability score **Aanleiding:** Platform-breed profiel dat accumuleert over jaren. **Wat:** - Festival-paspoort: visuele tijdlijn van deelgenomen festivals - Reliability score (0.0-5.0): berekend via scheduled job - Coordinator-beoordeling per festival (intern, nooit zichtbaar) - "Would reinvite" indicator bij heruitnodiging **Schema:** `volunteer_profiles`, `volunteer_festival_history` al aanwezig ✅ --- ### VOL-03 — Post-festival evaluatie + retrospectief **Aanleiding:** Automatische feedback na het festival. **Wat:** - 24u na laatste shift: evaluatiemail naar vrijwilligers - Max 5 vragen (beleving, shift kwaliteit, terugkomen?) - Gegenereerd retrospectief rapport per festival - Coordinator-beoordeling parallel (intern) **Schema:** `post_festival_evaluations`, `festival_retrospectives` al aanwezig ✅ --- ### VOL-04 — Shift swap workflow (portal) **Aanleiding:** Vrijwilliger wil shift ruilen met collega. **Wat:** - Open swap: iedereen mag reageren - Persoonlijke swap: specifieke collega vragen - Na akkoord beide: coordinator bevestigt (of auto-approve) - Wachtlijst: bij uitval automatisch aanschrijven **Schema:** `shift_swap_requests`, `shift_absences`, `shift_waitlist` al aanwezig ✅ --- ## Fase 3 — Artiesten & Advancing ### ART-01 — Artist advancing portal (apps/portal/) **Aanleiding:** Crescat's sterkste feature. **Wat:** - Sectie-gebaseerd advance portal via gesignde URL - Per sectie onafhankelijk submitbaar (Guest List, Contacts, Production) - Milestone pipeline: Offer In → Advance Received - Per-artiest zichtbaarheidscontrole van advance secties - Submission diff tracking (created/updated/untouched/deleted) **Schema:** `advance_sections`, `advance_submissions` al aanwezig ✅ --- ### ART-02 — Timetable (stage + drag-drop) **Aanleiding:** FullCalendar timeline view voor podia-planning. **Wat:** - Timeline view per podium - Drag-and-drop performances - B2B detectie (twee artiesten op zelfde podium zelfde tijd) **Tech:** FullCalendar (al in stack ✅) --- ## Fase 3 — Formulieren & Leveranciers ### FORM-01 — Formulierbouwer **Aanleiding:** WeezCrew heeft een krachtige drag-sorteerbare builder. **Wat:** - Drag-sorteerbaar, conditionele logica - Live preview - Iframe embed voor externe websites - Configureerbare velden per crowd type **Schema:** `public_forms` al aanwezig ✅ --- ### FORM-02 — TAG_PICKER → user_organisation_tags sync rebuild ✅ Done in S2b (2026-04-17) **Aanleiding:** TagSyncService verwijderd in S2a Form Builder legacy purge. Semantiek (TAG_PICKER-antwoorden syncen naar user_organisation_tags bij registratie-goedkeuring) blijft valide. **Wat:** Herbouwen als listener op FormSubmissionSubmitted tegen de nieuwe FormValue + TAG_PICKER field_type. Integreren via PersonIdentityService::confirmMatch zonder directe service-injection in PersonController. **Eerdere call-sites (nu verwijderd):** PersonController::approve(), PersonIdentityService::syncRegistrationTags(). **Landed artefacts:** - `App\Services\FormBuilder\FormTagSyncService::rebuildForPerson` — idempotent union-of-TAG_PICKER-values rebuild, only mutates `source=self_reported` rows, no-op when `person.user_id IS NULL`. - `App\Listeners\FormBuilder\SyncTagPickerSelectionsOnSubmit` — ShouldQueue listener on `FormSubmissionSubmitted`, filters to `event_registration` purpose with `subject_type=person` + at least one `TAG_PICKER` value. Logs + swallows errors so sibling listeners (§31.1/§31.3/§31.8) keep running. - `App\Services\PersonIdentityService::confirmMatch` — calls `FormTagSyncService::rebuildForPerson` after setting `person.user_id` (deferred-sync path for person who submitted before the user account existed). - Contract frozen in ARCH-FORM-BUILDER.md §31.10 (authoritative block) and covered by `tests/Feature/FormBuilder/Integration/TagPickerSyncListenerTest`. **Deferred integration tests (move under FORM-03 if needed):** GdprDeleteCascadeTest, EmailNotificationFlowTest, CodeOfConductGatingTest, SupplierIntakeFlowTest, CrowdListAutoAddTest (§31.9). Only §31.10 ships with S2b; other contracts wait until their feature arrives. --- ### FORM-BINDING-SNAPSHOT-MULTI — snapshot shape voor multi-binding per field **Aanleiding:** WS-5a legt de relationele `form_field_bindings` tabel neer met een UNIQUE op `(owner_type, owner_id, target_entity, target_attribute)`. Dat laat meerdere bindings per field toe zolang ze op verschillende kolom-paren landen. De snapshot-writer (`FormSubmissionService::buildSnapshot` via `FormFieldBindingService::toJsonShape`) embed op dit moment maar één binding per field — de eerste. `schema_snapshot.fields[*].binding` is een object, geen array. **Wat:** Snapshot-shape besluiten voor multi-binding: ofwel `binding` → array-of-objects, ofwel een nieuwe sleutel `bindings`. Migratiepad voor bestaande snapshots (ARCH §4.6.1). Reader-compat behouden. **Trigger:** wanneer ARCH §6.1 patroon-scenario's multi-binding op één field rechtvaardigen (bv. Pattern C naar twee target entities tegelijk). **Prioriteit:** Laag — out-of-scope van WS-5a, geen huidige user impact. --- ### FORM-BUILDER-LIBRARY-AUDIT-LOG — Audit FormFieldLibrary-level changes to bindings, validation rules, configs, and options **Aanleiding:** Post-WS-5d, four form-builder child-table services (`FormFieldBindingService`, `FormFieldValidationRuleService`, `FormFieldConfigService`, `FormFieldOptionService`) emit activity-log events on FormField subjects only. Changes to FormFieldLibrary entries — which affect organisation-wide reusable field definitions — land silently in the audit log. This is the consistent behaviour inherited from WS-5a and extended through WS-5b/c/d, but it represents an audit-trail gap for library administration. **Wat:** introduce parallel `library.*` activity-log events (`library.bindings_replaced`, `library.validation_rules_replaced`, `library.configs_replaced`, `library.options_replaced`) emitted by the same four services when the owner is a `FormFieldLibrary`. Document the convention addition in `ARCH-FORM-BUILDER.md` §6.7 and §17.4.2 + §17.5.2 + §17.6.3. Single cross-cutting work package. **Prioriteit:** Middel — geen blocker; candidate sprint post-WS-5, before any external audit tooling is wired up (consumers shouldn't have to deal with the asymmetry). **Related:** WS-5a §6.7 activity log events paragraph; WS-5b §17.4.2 / §17.5.2 paragraphs; WS-5d §17.6.3 paragraph. --- ### ~~FORM-BUILDER-MORPH-SCOPE-BASE-CLASS — Extract base class across the four WS-5 morph-scope siblings~~ **Status: closed 2026-04-25** — `FormFieldChildTableMorphScope` abstract base extracted; the four concrete scopes are now marker subclasses preserving identity. Phase A diff verification confirmed the four concrete `apply()` + `resolveOrganisationId()` bodies were byte-equal (zero divergence across three pairwise comparisons). Net diff: +165 / −377 lines. Tests 1208 → 1208 (3260 assertions, identical). Larastan baseline clean; Rector dry-run 357 → 355. See `app/Models/Scopes/FormFieldChildTableMorphScope.php` and ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md §"Uitvoering — base scope- class extractie (2026-04-25)". --- ### FORM-04 — `grace_days` configurable on public_token rotation **Aanleiding:** S2c §10.4 opgeleverd met een hardgecodeerd 7-daagse grace window in `PublicFormTokenResolver`. `rotatePublicToken` endpoint accepteert wel een `grace_days` request param maar schrijft die nergens naartoe; `form_schemas` heeft geen `grace_days` kolom. **Wat:** - Kolom `form_schemas.public_token_grace_days` (unsignedSmallInteger nullable, default null). - `rotatePublicToken` service persisteert de ontvangen `grace_days` value (fallback: config default). - `PublicFormTokenResolver::GRACE_DAYS` leest uit `form_schemas.public_token_grace_days ?? config('form_builder.public_token.default_grace_days', 7)`. - Test: rotatie met grace_days=3 levert 410 na 4 dagen. **Prioriteit:** Laag — operationele tuning, niet frontend-blocking. --- ### DOC-01 — Scramble / OpenAPI generator voor API.md **Aanleiding:** `dev-docs/API.md` wordt met de hand bijgehouden per sprint — bij snelle iteratie landt hij altijd een slag achter de code. Scramble (of equivalent) genereert OpenAPI uit FormRequest + Resource introspectie zonder annotaties. **Wat:** Scramble installeren, publieke form endpoints een dedicated `public` tag geven, CI-hook die de generated spec vergelijkt met een checked-in `dev-docs/api.openapi.yaml`, README link naar de live viewer. **Prioriteit:** Middel — verlaagt docs-drift substantieel; past in een "developer-experience" sprint. --- ### DOC-02 — VitePress docs:build faalt op missing image **Aanleiding:** `/docs/volunteer/je-aanmelden-via-een-link.md` verwijst naar `./images/placeholder.png` dat niet bestaat. Dev mode werkt, build faalt. Blokkeert CI als die `docs:build` gaat draaien. **Wat:** Placeholder afbeelding toevoegen OF de referentie weghalen / vervangen door een echte screenshot van het registratieflow. **Prioriteit:** Laag — cosmetisch, niet blokkerend voor dev. --- ### DOC-03 — Formulieren sidebar story is incompleet **Aanleiding:** Tijdens S3a PR 2 is `docs/organizer/forms/concepts/wat-is-een-formulier.md` gewired in de sidebar, samen met de nieuwe veldtype-pagina's. Maar de bredere Formulieren-sidebar mist nog: publicatieflow, inzendingen-overzicht, templates, webhook-configuratie, conditionele logica. **Wat:** Dedicated docs-sprint voor de Formulieren-module in VitePress. Schat: 6-8 pagina's in Nederlands, aimed at organisatoren die formulieren configureren. **Prioriteit:** Middel — landt best vlak voor/na S3b (organizer form-builder UI), omdat screenshots pas zin hebben als de UI staat. --- ### DOC-04 — `scripts/install-claude-sync-hooks.sh` opnemen in SETUP/onboarding **Aanleiding:** WS-4 pre-flight audit vond dat `scripts/sync-claude-docs.sh` bestaat en door de post-commit hook draait, maar de hook-installer (`scripts/install-claude-sync-hooks.sh`) is niet terug te vinden in de developer onboarding-instructies. Nieuwe clones missen de hook. **Wat:** Voeg een regel aan `dev-docs/SETUP.md` (of een post-install checklist) toe die nieuwe developers opdraagt `install-claude-sync-hooks.sh` te runnen. 1 regel, geen scope-impact. **Prioriteit:** Laag — nice-to-have, niet blokkerend voor dev of CI. --- ### FORM-05 — Smart identity-match on public submission values **Stub-status (S3a PR 2, 2026-04-23):** Public event_registration submissions landen al met `identity_match_status='pending'` via de bestaande `TriggerPersonIdentityMatchOnFormSubmit` listener. De portal `IdentityMatchBanner` leest dit veld en toont de juiste copy. Contract ligt vast in `tests/Feature/FormBuilder/Listeners/TriggerPersonIdentityMatchOnFormSubmitTest`. **Resterend werk (de eigenlijke FORM-05):** public form submissions (subject_type=null) krijgen momenteel *altijd* 'pending' omdat er nog geen Person bestaat om tegen te matchen. Breid uit met: - Nieuwe methode op PersonIdentityService: `detectMatchesByValues(array $values, string $organisationId): MatchResult` - Een extra tak in `TriggerPersonIdentityMatchOnFormSubmit::resolveStatus` die voor public submissions de values uit `FormSubmission->values` extraheert (email / first_name / last_name via de schema binding), deze methode aanroept, en 'matched' / 'pending' / 'none' schrijft. Zo krijgt de portal-UX een betekenisvol signaal in plaats van een constante 'pending'. Prioriteit: Medium. Kan gebundeld worden met de organizer `person_identity_matches` UI (ook nog een frontend gap). --- ### SUP-01 — Leveranciersportal + productieverzoeken **Aanleiding:** Leveranciers moeten productie-informatie kunnen indienen. **Wat:** - Token-gebaseerde portal toegang (geen account nodig) - Productieverzoek indienen (mensen, tech, stroom, voertuigen) - Crowd list indienen voor hun crew **Schema:** `production_requests`, `material_requests` al aanwezig ✅ --- ## Fase 4 — Differentiators ### DIFF-01 — Cross-event crew pool + reliability score **Aanleiding:** Vrijwilligers hergebruiken over events van dezelfde organisatie. **Wat:** Eén klik heruitnodiging op basis van vorig jaar. Reliability score zichtbaar naast naam in de lijst. --- ### DIFF-02 — Crew PWA (mobiel) **Aanleiding:** On-site zelfservice voor crew op hun telefoon. **Wat:** Progressive Web App voor: shifts bekijken, briefing lezen, clock-in, push notificaties. --- ### DIFF-03 — Publieke REST API + webhooks **Aanleiding:** Enterprise integraties. **Wat:** Gedocumenteerde publieke API + webhook systeem voor third-party integraties (ticketing, HR, etc.) --- ### DIFF-04 — CO2 / Duurzaamheidsrapportage **Aanleiding:** Toenemende focus op duurzame events. **Wat:** Emissieberekeningen op basis van transport en energieverbruik. **Status:** Expliciet out of scope voor v1.x --- ## Apps & Platforms ### ~~APPS-01 — apps/admin/ volledig bouwen~~ RETIRED **Status:** Retired — admin SPA (`apps/admin/`) is afgeschaft. Super admin functionaliteit is verplaatst naar `apps/app/` onder `/platform/*` routes voor `super_admin` gebruikers. --- ### APPS-02 — OrganisationSwitcher ingeklapte staat fix **Aanleiding:** Flikkering/hover-bug bij ingeklapte sidebar. **Wat:** Correcte weergave en animatie in ingeklapte staat. **Prioriteit:** Low — cosmetisch, werkt functioneel wel --- ## Technische schuld ### TECH-01 — Bestaande tests bijwerken na festival/event refactor **Aanleiding:** Na toevoegen parent_event_id worden bestaande tests mogelijk fragiel door gewijzigde factory-setup. **Wat:** Alle Feature tests reviewen en bijwerken waar nodig. --- ### TECH-05 — ESLint configuratie herstellen in apps/app/ **Aanleiding:** `npm run lint` faalt omdat `.eslintrc.cjs` niet bestaat en er ook geen flat-config equivalent aanwezig is. Effectief draait de app zonder lint, wat botst met CLAUDE.md's zero-compromise regels. **Wat:** Juiste flat-config installeren en afstemmen op het huidige Vuexy 9.5 template. Moet in één keer groen draaien. **Prioriteit:** Middel — tooling-gap. --- ### TECH-06 — ESLint config ontbreekt in apps/portal **Aanleiding:** `npm run lint` faalt in `apps/portal/` omdat `.eslintrc.cjs` niet bestaat. Geen flat-config equivalent aanwezig. Portal draait dus effectief zonder lint, wat botst met CLAUDE.md's zero-compromise regels. Apart van TECH-05 (dat over apps/app gaat). **Wat:** Flat-config ESLint installeren in `apps/portal/`, afgestemd op Vue 3 + TypeScript + Vuexy 10.11.1. In één keer groen laten draaien. Bij voorkeur gedeelde shared-config tussen apps/app en apps/portal om drift te voorkomen. **Prioriteit:** Middel — tooling-gap, niet user-facing. --- ### TECH-PORTAL-ESLINT-DEPS — Audit apps/portal/package.json op missing direct ESLint deps **Aanleiding:** Tijdens de Cursor ESLint-integratie fix in `apps/app/` (commit `4369806`, 2026-04-30) bleek dat 15 ESLint plugins, parsers en configs alleen via pnpm-hoisting werden gevonden, niet als directe dependencies in `package.json`. Cursor's ESLint extension gebruikt strict module resolution en crashte op elke missing plugin in de `@antfu/eslint-config-vue` extends-chain. Aannemelijk dat `apps/portal/package.json` hetzelfde patroon heeft, want zelfde antfu- config-keten en zelfde pnpm-monorepo-structuur. Zonder fix breekt het ESLint-formatter pad voor iedereen die de portal opent in Cursor — zelfde 3-uur-diagnose die we vandaag hebben doorgemaakt. **Wat:** - Run de diagnose-keten van vandaag (`ls node_modules/.pnpm | grep "^eslint-plugin-"` vs `ls node_modules | grep "^eslint-plugin-"`, plus de scoped variant en `vue-eslint-parser`/`@antfu/eslint-config-*` audits) op `apps/portal/`. - Voeg alle ontbrekende plugins als directe deps toe via `pnpm add -D` met versies die matchen wat in pnpm store zit (zero version shifts). - Verifieer in Cursor dat de portal ESLint extension activeert zonder errors in Output Channel, en dat save-on-format ESLint correct firet. **Prioriteit:** Middel — moet vóór sessie 2 (Pages migration) waar portal-files actief in Cursor bewerkt worden. Niet kritisch nu, maar de eerste developer die portal in Cursor opent stuit op hetzelfde issue als vandaag. --- ### TECH-ESLINT-V9-MIGRATION — Migreer apps/app + apps/portal naar ESLint v9 + flat config **Aanleiding:** ESLint v8.57.1 is end-of-life sinds eind 2024 (zie pnpm install warnings: `eslint@8.57.1: This version is no longer supported`). Daarnaast zijn meerdere config-pakketten in onze chain deprecated en migreren naar flat config: `@antfu/eslint-config-vue`, `@antfu/eslint-config-basic`, `@antfu/eslint-config-ts`, en `eslint-plugin-markdown`. De huidige `.eslintrc.cjs` legacy-config werkt, maar er komen geen security fixes meer voor v8 en de transitieve deprecated-warnings groeien per `pnpm install`. Migratie naar v9 + flat config (`eslint.config.js`) + modern `@antfu/eslint-config` lost in één klap alle deprecated warnings op én moderniseert de toolchain. **Wat:** - Eigen workstream — niet meeliften op andere sprints want config- rewrite raakt 200+ regels. - ESLint 8.57.1 → 9.x upgrade. - `.eslintrc.cjs` (legacy) → `eslint.config.js` (flat config). - `@antfu/eslint-config@0.43.x` (legacy) → `@antfu/eslint-config@latest` (flat-config variant). - Alle plugins meeschalen naar versies die met v9 + flat config werken. - Test op apps/app + apps/portal — de hele lint-baseline moet groen blijven (0 problems voor app, baseline voor portal). - Hoort vóór WS-3 sessie 2 (pages migration) als de portal-eslint baseline ook op 0 staat, anders na alle WS-3 sessies. Schat 1-2 dagen werk inclusief regression-fixing. **Prioriteit:** Middel-Hoog — security-EOL is een doorslaggevend argument; uitstel tot na WS-3 acceptabel maar niet onbeperkt. Eigen sprint waard, geen meelift-pad. --- ### TECH-AXIOS-STORE-COUPLING — Decouple lib/axios.ts from stores layer **Aanleiding:** WS-3 sessie 1c (eslint-plugin-boundaries enforcement) constateerde dat `apps/app/src/lib/axios.ts` 4 imports heeft uit `stores/` (2 statisch op regel 3-4 voor `useNotificationStore` / `useOrganisationStore`, 2 dynamisch op regel 61, 72 voor `useImpersonationStore` / `useAuthStore` uit 1b-iii). De `lib → stores` edge schendt de layered-architecture matrix. Om sessie 1c on-time te landen zijn de 4 sites gemarkeerd met `eslint-disable-next-line` + verwijzing naar dit backlog-item; de structurele fix is bewust uitgesteld naar een dedicated sessie omdat het architectuurwerk is, geen tooling- cleanup. **Wat:** - Decouple `lib/axios.ts` van stores zodat het puur HTTP-infrastructuur wordt. Twee paden, kies bij refactor: - **Approach 1 (preferred starting point):** `lib/axios.ts` exporteert de axios-instance plus een `registerInterceptors(client, deps)` functie die callbacks accepteert (`onAuthFail`, `onImpersonationDrop`, `getActiveOrgId`, `notify(message, level)`). Een nieuwe `plugins/axios-bindings.ts` (mag `stores` importeren per matrix) roept `registerInterceptors` aan bij app-init met closures over de stores. - **Approach 2 (fallback):** event-bus / callback registry; axios.ts emit-eert semantische events (`auth-failed`, `needs-org-header`, `notify-error`) en `plugins/axios-bindings.ts` subscribet. - Verwijder alle 4 `eslint-disable-next-line` comments uit `lib/axios.ts`. - Tests moeten dekken: X-Organisation-Id header injection, 401/403 logout flow, impersonation revocation flow, error toast op 4xx/5xx. - Optioneel: meteen ook de static/dynamic import-split in axios.ts uniformeren (nu inconsistent om legacy 1b-iii-redenen). **Prioriteit:** Middel — geen blokker voor andere workstreams, maar elke maand dat dit blijft staan is een vlek op de boundaries-enforcement geloofwaardigheid. Aanbevolen: meelift met de eerste WS-3 PR die `lib/` of `plugins/` raakt, of een dedicated 2-3 uur sessie na WS-6 sluiting. --- ### TECH-TYPED-ROUTER-DRIFT — apps/app/typed-router.d.ts drifts when pages/ changes are merged without rebuild **Aanleiding:** Op 2026-05-04 bleek `apps/app/typed-router.d.ts` achter te lopen op de pages-tree: vier `form-failures` routes (organisation + platform, list + detail) waren al maanden geleden gelandt in main, maar de gegenereerde route-types waren nooit mee-gecommit. Pas een lokale `pnpm build` triggerde `unplugin-vue-router` om het bestand te regenereren, waarna een losse commit (`3198698`) de drift dichtmaakte. Zonder die toevallige build-run had de drift onbeperkt door kunnen lopen — TypeScript flagde de stale routes niet, en niemand routeert via de typed names hard genoeg dat het brak. Het bestand is tracked, niet gitignored. Dat betekent: elke PR die een file in `apps/app/src/pages/` toevoegt of hernoemt, moet óók de regeneratie van `typed-router.d.ts` meecommitten. In de praktijk gebeurt dat nu inconsistent. **Wat:** Drie reële paden, kies bij implementatie: - **Approach 1 (preferred): pre-commit hook in lefthook.** Voeg een lefthook pre-commit hook toe die — wanneer een `apps/app/src/pages/**` file in de staging-set zit — `unplugin-vue-router` triggert en `apps/app/typed-router.d.ts` re-stage't als hij is veranderd. Hook is silent op de happy path; faalt loud bij regeneratie-error. Voordeel: types altijd in sync in git, fresh clones werken zonder eerst te builden. Nadeel: extra commit-tijd voor pages-changes (~2-5s per commit in de plugin), en `unplugin-vue-router` heeft geen standalone CLI-mode dus de hook wordt een Node-script dat de plugin laadt en handmatig aanstuurt — fragiel bij plugin-versie-upgrades. - **Approach 2 (alternative): gitignore + regenereer in postinstall.** Voeg `apps/app/typed-router.d.ts` toe aan `.gitignore`. Voeg een `postinstall` script in `apps/app/package.json` dat `vue-tsc` of een vergelijkbare prebuild-stap triggert die `unplugin-vue-router` zijn type-emit laat doen. Voordeel: drift is structureel onmogelijk — er is niets meer in git om uit sync te raken. Nadeel: fresh clones zijn een paar seconden trager (`pnpm install` doet meer werk), en als `postinstall` faalt heb je IDE-rode-squiggles totdat je het oplost. - **Approach 3 (status quo + alarm): CI-check.** Houd het bestand tracked, maar voeg een CI-stap toe die `unplugin-vue-router` regenereert en faalt als de output verschilt van wat in git staat. Voordeel: minimale lokale workflow-impact. Nadeel: CI-only — drift wordt pas gevonden bij PR-build. Werkt alleen als er CI is (op het moment van schrijven: er is nog geen GitHub-Actions / Drone / Gitea-Actions pipeline geconfigureerd in deze repo). **Prioriteit:** Laag — geen functionele impact, alleen DX en type-safety-betrouwbaarheid. Geen blocker voor andere workstreams. Aanbevolen moment: meelift met de eerste substantiële pages-tree refactor (bijvoorbeeld WS-3 PR-B die de portal pages naar `apps/app/src/pages/portal/` verhuist — dán is de drift-kans het grootst en de pijn van onbeschermd laten ook). --- ### TECH-WS3-BOUNDARIES-SUBZONES — Sub-zone import-boundaries inside components/ and pages/ **Aanleiding:** WS-3 sessie 1c heeft top-level zone-boundaries in `apps/app/` neergezet via `eslint-plugin-boundaries`. De `/dev-docs/ARCH-CONSOLIDATION-2026-04.md` §4.2 target layout introduceert sub-zones binnen die top-level zones — specifiek `components/{organizer,portal,shared}/` en `pages/{(auth),portal,register,events,persons,organisations,platform}/`. De architecturale intent is dat `components/portal` niet uit `components/organizer` mag importeren (en vice versa), met `shared` als de gemeenschappelijke uitgang. Sessie 1c heeft die sub-zone enforcement bewust uitgesteld omdat de sub-folders nog niet bestaan; pre-emptieve rules op niet-bestaande directories worden stille dode config die drift. **Wat:** - **Precondition:** WS-3 PR-B is gemerged en de §4.2 sub-folder structuur is gelandt (`components/{organizer,portal,shared}/` en `pages/{(auth),portal,...}/` bestaan fysiek met content). - Breid `boundaries/elements` in `apps/app/.eslintrc.cjs` uit met: - `{ type: 'components-organizer', pattern: 'src/components/organizer/**' }` - `{ type: 'components-portal', pattern: 'src/components/portal/**' }` - `{ type: 'components-shared', pattern: 'src/components/shared/**' }` - (ontworpen sub-zones voor `pages/` analoog) - Voeg per-sub-zone rules toe: `components-portal` en `components-organizer` mogen beide uit `components-shared` importeren, maar niet uit elkaar. `pages/portal/` mag niet uit `pages/events/` (en de andere organizer-pages) importeren, en omgekeerd. - Resolve violations die bij eerste activatie naar boven komen. - ETA: 1-2 uur zodra precondities ervoor liggen. **Prioriteit:** Middel — preventieve architectuur-discipline voor de multi-tenant context-isolatie tussen organizer en portal UI-paden. Zonder deze rules is de kans groot dat een ontwikkelaar tijdens een PR-B follow-up onbewust portal- en organizer-componenten verstrengelt. --- ### TECH-WS3-BOUNDARIES-ROUTER-ZONE — Add `router/` zone to boundaries matrix **Aanleiding:** WS-3 sessie 1c audit (§3 forward-compatibility) flagde dat de §4.2 target layout `src/plugins/1.router/` vervangt door een flat `src/router/`. De huidige boundaries-matrix in `apps/app/.eslintrc.cjs` mapt router-files naar de `plugins` zone (omdat ze fysiek in `src/plugins/1.router/` zitten). Zodra de verhuizing plaatsvindt — geplant in een latere WS-3 PR — moet de matrix-config dat reflecteren, anders vallen router-files buiten de `boundaries/elements` mapping en flag-stormt de plugin met "no rule found". **Wat:** In dezelfde commit/PR die `src/plugins/1.router/` naar `src/router/` verhuist: - Voeg toe aan `boundaries/elements` in `apps/app/.eslintrc.cjs`: ```js { type: 'router', pattern: 'src/router/**' }, ``` Plaats vóór `plugins` in de array (first-match-wins ordering). - Voeg toe aan `boundaries/element-types` rules: ```js { from: 'router', allow: ['types', 'utils', 'lib', 'plugins', 'stores'] }, ``` - Verifieer `pnpm lint` blijft op 0 problemen. **Trigger:** "src/plugins/1.router/" → "src/router/" verhuizing (latere WS-3 PR, vermoedelijk PR-B als die de router-tree consolideert). **Prioriteit:** Laag — geen actie tot de verhuizing plaatsvindt; dan verplicht 5-minute follow-up. --- ### TECH-08 — Paginated response meta wordt weggegooid in organizer composables **Aanleiding:** `apps/app/src/composables/api/useSections.ts` en `apps/app/src/composables/api/useFormSchemas.ts` definiëren beiden een lokale `PaginatedResponse = { data: T[] }` shape die alleen de `data` array eruit trekt. De Laravel paginator geeft ook `links` en `meta` (huidige pagina, totaal, per-page) terug — die informatie gaat nu verloren. Voor de huidige consumers geen probleem (geen paginatie- controls), maar zodra een lijstweergave in de organizer UI een "Volgende"-knop, pagina-selector of totaaltelling wil tonen, loopt de composable tegen die beperking aan. PR-b2 (/forms lijst-view) is de eerste concrete trigger. **Wat:** Upgrade de shared response-shape in beide composables naar `{ data: T[], links: { first, last, prev, next }, meta: { current_page, from, last_page, path, per_page, to, total } }` (exacte veldnamen conform Laravel's `ResourceCollection` default). Retourneer het hele meta-blok mee uit de `useXList` composables zodat de UI kan paginate. Bij voorkeur één gedeelde TypeScript interface exporteren uit een nieuwe `apps/app/src/types/api.ts` zodat de derde, vierde, ... composable die volgt hetzelfde patroon erft. Nieuwe composables voor lijst-endpoints moeten vanaf dat moment deze interface gebruiken. **Prioriteit:** Middel — blokkeert geen huidige features, maar elke composable die zonder paginering-support wordt gebouwd voegt werk toe aan de latere migratie. Oplossen vlak vóór PR-b2 paginering-UI introduceert is het natuurlijke moment. --- ### ~~TECH-02 — scopeForFestival helper op Event model~~ ✅ OPGELOST --- ### ~~TECH-03 — DevSeeder uitbreiden met festival-structuur~~ ✅ OPGELOST --- ### ~~TECH-04 — EventController.store() redundante ternary~~ ✅ OPGELOST --- ### ~~TECH-07 — @form-schema transitive dep op @core/utils/validators~~ ✅ OPGELOST — resolved in PR-a1 --- ## Opgeloste items (april 2026) De volgende items zijn geïmplementeerd en afgerond (673+ tests): - ~~TECH-02: scopeForFestival + scopeWithChildren helper scopes op Event model~~ ✅ - ~~TECH-03: DevSeeder uitgebreid met festival-structuur (secties, tijdsloten, personen)~~ ✅ - ~~TECH-04: EventController.store() redundante ternary~~ ✅ - ~~Auth race condition (CTRL+R fix)~~ ✅ - ~~Section edit dialog bug~~ ✅ - ~~Time slot duplicate button~~ ✅ - ~~Browser autocomplete disabled op dialog form fields~~ ✅ - ~~Category + icon fields op festival_sections~~ ✅ - ~~IconPicker component~~ ✅ - ~~Crowd Types beheer-UI~~ ✅ - ~~Companies CRUD~~ ✅ - ~~Person tags backend (person_tags + user_organisation_tags)~~ ✅ - ~~Event status state machine (dedicated transition endpoint, prerequisites, festival cascade)~~ ✅ - ~~Event status transition buttons (frontend + backend, state machine, cascade)~~ ✅ - ~~Festival tab-navigatie (uniform tabs, Programmaonderdelen tab)~~ ✅ - ~~SectionsShiftsPanel extractie als herbruikbaar component~~ ✅ - ~~Cross-event section auto-redirect~~ ✅ - ~~Shift claiming in portal (5 endpoints, 26 tests, ClaimenTab + RoosterTab)~~ ✅ - ~~Cross-app auth isolation (CookieBearerToken per app, 3 isolatietests)~~ ✅ - ~~Password reset (beide SPAs, custom notification, app-aware links)~~ ✅ - ~~Email change with verification (self-service + admin, 24h token expiry)~~ ✅ - ~~Password change while logged in~~ ✅ - ~~"Lid toevoegen als deelnemer" shortcut (2 endpoints, 11 tests)~~ ✅ - ~~Person Identity Matching (detect→suggest→confirm, fuzzy name, DOB tiebreaker)~~ ✅ - ~~Naam-splitsing first_name + last_name (66 files)~~ ✅ - ~~Date of birth op persons en users~~ ✅ - ~~Smart assign dialog (tags, preferences, availability, cascading filters)~~ ✅ - ~~Soft capacity + approve overbook fix~~ ✅ - ~~Cancellation source tracking + re-assignment~~ ✅ - ~~VitePress user documentation (3 core pages)~~ ✅ - ~~Registration settings (show_in_registration)~~ ✅ - ~~Premium portal wizard (banner, branding, success page)~~ ✅ - ~~Global error handling (useNotificationStore + axios 422 interceptor)~~ ✅ - ~~S3a PR 2: TAG_PICKER / AVAILABILITY_PICKER / SECTION_PRIORITY renderen in het publieke registratieformulier. Seeder uitgebreid met twee showcase-velden + parent-level VOLUNTEER time slot + duplicate section name voor dedup-dekking. SECTION_PRIORITY waarde-shape gevalideerd in FormValueService. `FormSubmissionResource` krijgt admin-facing `identity_match` block. 64 nieuwe assertions over backend + Vitest.~~ ✅ - ~~FORM-09: TriggerPersonIdentityMatchOnFormSubmit sync refactor (eager state transition, async resolution deferred to FORM-05)~~ ✅ --- ## Bekende gaps — nog te bouwen Overzicht van bekende ontbrekende onderdelen die nog niet gebouwd zijn: | Item | Status | Prioriteit | |------|--------|-----------| | Person Tags frontend UI | Backend compleet, geen organiser UI | Hoog | | Accreditatie Engine (SCHEMA 3.5.6, ARCH-07 templates) | Volgende grote module | Hoog | | ARCH-03 — Sectie templates / kopiëren van vorig event | Niet gestart | Hoog | | Briefings & Communicatie basis | Niet gestart | Middel | | Artist Advancing portal | Niet gestart | Middel | | UX-01 — Festival setup checklist | Niet gestart | Middel | | UX-03 — Personen per sub-event | Niet gestart | Middel | | ARCH-06 — Locatie-gebaseerd shift-overzicht | Niet gestart | Laag | | ARCH-09 — Artist Eloquent model + migration | Prerequisite for artist_advance purpose | Hoog (blocker voor artist_advance) | --- ## Nieuwe backlog items ### ARCH-06 — Locatie-gebaseerd shift-overzicht Cross sub-event filter op location_id. Toont alle shifts op een fysieke locatie ongeacht programmaonderdeel. **Prioriteit:** Laag --- ### ARCH-07 — Accreditatie-templates per sectie/dag combinatie MUST-HAVE bij accreditatie build. Templates worden primaire toewijzingsmethode. Per crowd_type + sectie + dag → automatisch voorgestelde accreditatie-items. **Prioriteit:** Hoog — direct meebouwen bij accreditatie-module --- ### ARCH-08 — Recurrence voor time slots Herhalingsfunctie: "genereer 5 time slots in één keer" voor opbouwdagen etc. **Prioriteit:** Middel --- ### ARCH-09 — Artist Eloquent model + migration **Aanleiding:** `artist_advance` purpose is geregistreerd in `PurposeRegistry` (v1.0) met `subject_type = 'artist'`, maar het `App\Models\Artist` model en de `artists` tabel bestaan nog niet. `AppServiceProvider::PURPOSE_SUBJECT_FQCN` bevat `'artist' => 'App\\Models\\Artist'` als string-literal (gedocumenteerd in de constant-docblock) om morph-map-registratie te laten slagen — resolution is lazy en knalt pas bij de eerste echte artist-submission. **Wat:** Artist Eloquent model + migratie + factory, conform het patroon van de overige business-tabellen (ULID PK, `HasUlids`, `OrganisationScope`, soft deletes per SCHEMA §3.5.7). Na het landen van het model: `PURPOSE_SUBJECT_FQCN` omzetten van string-literal naar `Artist::class` import. **Prioriteit:** Hoog — blokkeert elke feature-sprint rond artist_advance. **Afhankelijk van:** SCHEMA §3.5.7 finalisatie (artists, performances, stages etc. — momenteel in `/dev-docs/ARCH-PLANNED-MODULES.md` na WS-8). --- ### ART-03 — Artist profile met cross-event rider defaults Organisatie-niveau artiest-profiel dat rider-defaults, contacten en interne notities opslaat over events heen. "Importeer van vorig jaar" functie. **Prioriteit:** Laag --- ### UX-01 — Festival setup checklist / onboarding wizard Checklist widget op festival dashboard die door de configuratiestappen leidt. Items worden groen als ze zijn afgerond. **Prioriteit:** Middel --- ### UX-02 — Aandachtsmatrix op event dashboard **Aanleiding:** Organisator verliest overzicht bij 200+ vrijwilligers en 30 secties. Kritieke problemen (onderbezette shifts, wachtende goedkeuringen, onopgeloste identity matches) worden pas ontdekt als het te laat is. **Wat:** Drie metric cards op het event Overzicht-tab: - Goedgekeurde personen zonder shift-toewijzing (telling) - Wachtende shift-claims (telling) - Onopgeloste identiteitsmatches (telling) Elke card is klikbaar en navigeert naar de relevante module. **Prioriteit:** Hoog — eerste frontend-taak op Overzicht-tab. Data is beschikbaar via bestaande endpoints (aggregate queries). --- ### UX-03 — Personen-tab op sub-event niveau Gefilterde view: alleen personen met shifts in dit programmaonderdeel. Met link "Bekijk alle personen op festival-niveau". **Prioriteit:** Middel --- ### COMM-05 — Resend invitation endpoint **Aanleiding:** Uitnodigingen kunnen nu alleen ingetrokken worden of verlopen vanzelf. Organisatoren willen een "opnieuw versturen" actie voor gevallen waarin de oorspronkelijke mail in de spamfilter belandde, gemist werd, of het e-mailadres net gecorrigeerd is. **Wat:** - Backend: `POST /api/v1/organisations/{org}/invitations/{id}/resend` (idempotent: regenereert de mail zonder token of verloopdatum te wijzigen). Zelfde endpoint voor `/admin` scope. - Frontend: "Opnieuw versturen" actie activeren in de sectie openstaande uitnodigingen op `/members` (useMembers heeft al een `useResendInvitation` stub-ready). **Prioriteit:** Middel — user-requested UX-verbetering. --- ### UX-04 — Leveranciers-deadline waarschuwing **Aanleiding:** Leveranciers die hun personeelslijst niet tijdig indienen veroorzaken last-minute chaos. De organisator heeft geen zicht op welke externe lijsten nog niet compleet zijn. **Wat:** Op het event dashboard en in de publiekslijsten-tab: - Badge "Nog niet compleet" op externe lijsten waar persons_count < max_persons - Optioneel: deadline-datum veld op crowd_lists (nieuw kolom) - Waarschuwingsbanner X dagen voor de deadline: "3 leveranciers hebben hun lijst nog niet compleet ingediend" **Prioriteit:** Middel — meebouwen bij leveranciersportaal (SUP-01) --- ## Larastan reduction sprints Larastan (PHPStan for Laravel) is geïnstalleerd op level 6 met een accept-all baseline van 1556 errors over 678 files (41 distinct identifiers). Zie `/dev-docs/LARASTAN.md` voor werkmodel. Per-categorie reduction-sprints hieronder — elke sprint mikt op één identifier, laat de baseline krimpen en regenereert hem aan het einde. ### TECH-LARASTAN-01 — property.notFound **Priority:** Middel (post-foundation, incremental) **Scope:** baseline-entries met identifier `property.notFound`. **Estimate:** 613 errors over 87 files. **Completion gate:** category count daalt naar 0 in geregenereerde baseline; volledige test suite groen. **Approach:** - Merendeel zit op Eloquent-modellen waar `$user->id` of vergelijkbaar niet door PHPDoc wordt herkend — los op door `@property` annotaties op modellen toe te voegen (of via `php artisan ide-helper:models` als dat acceptabel wordt gevonden). - Commit per sub-directory als >50 errors. ### TECH-LARASTAN-02 — missingType.generics **Priority:** Middel **Scope:** baseline-entries met identifier `missingType.generics`. **Estimate:** 289 errors over 52 files. **Completion gate:** category count naar 0; tests groen. **Approach:** - Zit vooral op factories (`extends Factory`) en `HasFactory`-gebruik zonder template. Voeg type-params toe aan class-declaraties en docblocks. - Overlapt deels met TYPE_DECLARATION-sprint van Rector. ### TECH-LARASTAN-03 — argument.templateType **Priority:** Middel **Scope:** baseline-entries met identifier `argument.templateType`. **Estimate:** 154 errors over 31 files. **Completion gate:** category count naar 0; tests groen. **Approach:** - Voornamelijk `collect(...)` calls waar PHPStan de generieke template TKey/TValue niet kan resolven. Typeer de input expliciet of gebruik `Collection::make([...])` met generieke annotatie. ### TECH-LARASTAN-04 — missingType.iterableValue **Priority:** Middel **Scope:** baseline-entries met identifier `missingType.iterableValue`. **Estimate:** 98 errors over 61 files. **Completion gate:** category count naar 0; tests groen. **Approach:** - Methode-return-types als `array` zonder value-type. Voeg `array` of specifieker toe aan form-requests, resource `toArray()` methods, factory `definition()` methods. ### TECH-LARASTAN-05 — argument.type **Priority:** Middel **Scope:** baseline-entries met identifier `argument.type`. **Estimate:** 77 errors over 32 files. **Completion gate:** category count naar 0; tests groen. **Approach:** - Reële type-mismatches (bijv. string doorgegeven waar `'strict'|'lax'` vereist is). Case-by-case reviewen — niet mechanisch. ### TECH-LARASTAN-06 — method.notFound **Priority:** Middel **Scope:** baseline-entries met identifier `method.notFound`. **Estimate:** 50 errors over 26 files. **Completion gate:** category count naar 0; tests groen. **Approach:** - Meestal "Call to an undefined method Illuminate\\…::users()" — relationship methods die PHPStan niet kent. Los op via `@method` annotaties of generieke relationship-return types. ### TECH-LARASTAN-07 — method.childReturnType **Priority:** Laag **Scope:** baseline-entries met identifier `method.childReturnType`. **Estimate:** 35 errors over 35 files. **Completion gate:** category count naar 0; tests groen. **Approach:** - Eén-op-één met factory `definition()` methodes. Smeedt samen met TECH-LARASTAN-02 in één sprint indien praktisch. ### TECH-LARASTAN-08 — method.unresolvableReturnType **Priority:** Laag **Scope:** baseline-entries met identifier `method.unresolvableReturnType`. **Estimate:** 32 errors over 9 files. **Completion gate:** category count naar 0; tests groen. ### TECH-LARASTAN-09 — assign.propertyType **Priority:** Middel (reële type-bug kans hoger dan bij generics) **Scope:** baseline-entries met identifier `assign.propertyType`. **Estimate:** 31 errors over 10 files. **Completion gate:** category count naar 0; tests groen. **Approach:** - Meestal Carbon vs string mismatch op Eloquent properties — modelcasts goed zetten zodat Eloquent de datetime teruggeeft waar hij beloofd is. ### TECH-LARASTAN-10 — instanceof.alwaysTrue **Priority:** Laag **Scope:** baseline-entries met identifier `instanceof.alwaysTrue`. **Estimate:** 28 errors over 17 files. **Completion gate:** category count naar 0; tests groen. **Approach:** - Dead `instanceof`-checks. Prefab voor Rector's `DEAD_CODE` sprint — wachten of combineren. ### TECH-LARASTAN-CI — CI integration **Priority:** Middel **Scope:** wire `composer analyse` als blokkerende PR-gate in CI. **Depends on:** CI-infrastructuurkeuze. ### TECH-LARASTAN-L8 — level 8 migration **Priority:** Laag **Scope:** niveau 6→8 verhogen nadat level-6 baseline op 0 staat. **Estimate:** onbekend totdat level 6 leeg is. --- ## Rector application sprints Rector is geïnstalleerd en geconfigureerd op PHP 8.2 + safe quality + Laravel code-quality rule sets. Dry-run rapporteert **487 rule-applications over 357 files** verdeeld over ~35 distinct rules. Zie `/dev-docs/RECTOR.md` voor werkmodel. Per-ruleset sprints hieronder — elke sprint beperkt zich tot één scope, past toe, verifieert tests + Larastan, regenereert waar nodig. ### TECH-RECTOR-01 — DEAD_CODE sprint **Priority:** Middel **Scope:** `SetList::DEAD_CODE` over app/, database/, tests/. **Estimate:** Top 13 unused-variable removals via `RemoveUnusedVariableAssignRector`, plus verwante dead-code rules. Exact totaal: ~30-50 changes. **Completion gate:** `composer rector:apply` clean voor deze set; test suite groen; Larastan baseline geregenereerd en kleiner (of gelijk). Commit per sub-directory indien >50 wijzigingen. **Approach:** - Tijdelijk in rector.php alleen DEAD_CODE aanzetten, andere sets uitcommentariseren. - `composer rector` om diff te reviewen voor zekerheid. - `composer rector:apply`. - `composer test` + `composer analyse`. - Herstel rector.php naar volledige config. - Commit. ### TECH-RECTOR-02 — TYPE_DECLARATION sprint **Priority:** Middel (hoogste volume — 174+ changes) **Scope:** `SetList::TYPE_DECLARATION`. **Estimate:** ~174+ changes — top drivers: `AddClosureVoidReturnTypeWhereNoReturnRector` (103), `AddArrowFunctionReturnTypeRector` (71), plus `AddArrayFunctionClosureParamTypeRector` en kleinere. **Completion gate:** zie TECH-RECTOR-01. **Extra**: deze set lost waarschijnlijk veel `missingType.*` errors in Larastan baseline op — regenereer en commit gereduceerde phpstan-baseline.neon mee. ### TECH-RECTOR-03 — LARAVEL_CODE_QUALITY + LARAVEL_COLLECTION sprint **Priority:** Middel **Scope:** `LaravelSetList::LARAVEL_CODE_QUALITY` + `LaravelSetList::LARAVEL_COLLECTION`. **Estimate:** ~80 changes — `AppToResolveRector` (51), `DispatchToHelperFunctionsRector` (8), `EloquentOrderByToLatestOrOldestRector` (7), `CarbonToDateFacadeRector` (4), plus collection-idioms. **Completion gate:** zie TECH-RECTOR-01. **Approach:** - Splits in twee commits (één per set) als het totaal te groot is om in één review te laten passen. ### TECH-RECTOR-04 — CODE_QUALITY + EARLY_RETURN + PRIVATIZATION **Priority:** Laag **Scope:** resterende quality-sets (~80 changes). **Estimate:** `ConvertStaticToSelfRector` (34), `ReadOnlyClassRector` (27), `ReturnBinaryOrToEarlyReturnRector` (16), `ClosureToArrowFunctionRector` (9), etc. **Completion gate:** zie TECH-RECTOR-01. ### TECH-RECTOR-05 — Laravel modernisation sprint **Priority:** Laag **Scope:** review en selectief enable van `LaravelLevelSetList::UP_TO_LARAVEL_*` in rector.php. **Estimate:** onbekend tot per-set dry-runs zijn bekeken. **Completion gate:** per-set applicatie als gescoopte commits. ### TECH-RECTOR-CI — CI integration **Priority:** Laag **Scope:** `composer rector` (dry-run) als PR-comment-surface. Apply blijft handmatig. --- ## Frontend type-safety ### TECH-TS-IMPERSONATION — runtime validation of impersonation state **Aanleiding:** ts-reset install (april 2026) surfaced dat `apps/app/src/stores/useImpersonationStore.ts` blindly vertrouwt op `JSON.parse(sessionStorage.getItem(...))`. De return is nu `unknown` in plaats van `any`; we fixten dit door twee `as ImpersonationState` casts toe te voegen. Die maken de bestaande trust expliciet maar valideren de shape niet. **Wat:** vervang beide casts (lines 19 + 123) door een narrowing-helper `parseStoredState(raw: string | null): ImpersonationState | null` die het JSON parseert, de verwachte keys controleert (incl. geneste `impersonatedUser: AdminUser` fields), en `null` teruggeeft als de shape niet klopt. Zelfde helper gebruiken bij beide call sites. sessionStorage is in theorie tamperbaar door lokale users, dus dit is ook een kleine security verbetering. **Prioriteit:** Laag — defensive hardening, geen user-impact. ### ~~TECH-TS-PORTAL-TSC — `tsc --noEmit` baseline reduction (apps/portal)~~ **Status: closed 2026-04-25** — `pnpm exec vue-tsc --noEmit` exits clean in `apps/portal/`. Sprint commits: `f7bb864` (tiptap 2.27.2 upgrade) + `a7ccd2b` (4 long-tail fixes). **Correction to the original framing:** the entry initially read "22 pre-existing tsc errors in own code + 4 in tiptap node_modules". The "4" was the **delta** that ts-reset added in foundation-tooling commit 3 (ts-reset's `JSON.parse → unknown` tightening surfaced 4 new tiptap errors), **not** the absolute pre-existing tiptap baseline. The actual absolute count was **~707 tiptap node_modules errors**, silently invisible because the project's CI runs build + vitest only, not `vue-tsc`. **Root cause of the 707:** tiptap 2.27.1's `dist/index.d.ts` re-exported from `'../src/CommandManager.js'` and ~22 similar lines that referenced `.js` files which did not exist (only `.ts` source). With `moduleResolution: "Bundler"`, vue-tsc fell through and pulled tiptap's entire uncompiled source tree into the program. Tiptap 2.27.2 (a patch release) fixed the dist exports to use sibling- relative paths (`./CommandManager.js`) that resolve correctly to the existing `dist/*.d.ts` siblings. **Why `skipLibCheck` did not help:** `skipLibCheck: true` was already set in `apps/portal/tsconfig.json` (and `apps/app/`) **but only suppresses checking of `.d.ts` declaration files**. Tiptap's uncompiled `.ts` source files in `node_modules/.../src/` were raw `.ts`, not `.d.ts`, so they bypassed `skipLibCheck` once the import graph reached them. The 2.27.2 packaging fix made the import graph stop there, no exclusion needed. **Final error tally:** - Pre: 729 vue-tsc errors (~22 own-code, of which 18 were TS2339 downstream of tiptap's broken types; ~707 in node_modules/@tiptap) - Post-tiptap-upgrade: 4 own-code (the tiptap-independent stragglers: `vite.config.ts` TS7006, `themeConfig.ts` TS2322 via `LayoutConfig.title` `Lowercase` over-constraint, `build-icons.ts` TS2307 missing `@iconify/types`, `casl.ts` TS2345 vue-router meta cast) - Post-long-tail-fixes: 0 own-code, 0 node_modules **Aftermath:** - The S3b form-builder organizer UI now lands on a verified zero-error baseline. - The "new code introduces no new errors" discipline still depends on a CI gate — see TECH-PORTAL-TSC-CI-GATE follow-up. ### TECH-PORTAL-TSC-CI-GATE — vue-tsc as a blocking gate for apps/portal **Aanleiding:** TECH-TS-PORTAL-TSC reached zero in commits `f7bb864` + `a7ccd2b`, but the project has no pre-commit infrastructure (no husky, lefthook, or simple-git-hooks) and CI does not run `vue-tsc` either. Without a blocking gate, the baseline can drift back to non-zero between commits — exactly the discipline gap that produced the 22 pre-existing errors in the first place. **Wat:** - Add `pnpm exec vue-tsc --noEmit` as a CI step in the workflow that runs portal build/vitest (most natural location). - OR introduce a pre-commit infrastructure (lefthook is lighter than husky for monorepos) and run vue-tsc on portal-touching commits. - Either path: the gate must fail the run on any new vue-tsc error in `apps/portal/`. **Out of scope:** extending the same gate to `apps/app/` — that SPA still has a different mix of errors and would be a separate sprint (out of scope today, would land alongside TECH-APP-VITEST or later). **Prioriteit:** Middel — without the gate, TECH-TS-PORTAL-TSC's zero state has no enforcement. Should land before S3b organizer UI work to keep that sprint's "new code introduces no new errors" discipline mechanically enforceable. ### TECH-APP-VITEST — apps/app Vitest setup **Priority:** medium → **high before S3b organizer UI lands.** **Scope:** install Vitest + config + sample test in `apps/app/`. Mirror `apps/portal/` setup. Add `test` script to package.json. **Trigger to upgrade priority to "must-fix-now":** any S3b form-builder organizer UI commit. apps/portal has 113 Vitest tests as of foundation-tooling commit 0; apps/app has zero. Launching new organizer UI uncovered while the portal SPA is well-tested is asymmetric quality, exactly the discipline gap that bites during post-launch debugging. **Setup outline (1-2 hours, isolated commit):** 1. `cd apps/app && pnpm add -D vitest @vue/test-utils @testing-library/vue happy-dom` 2. Mirror `apps/portal/vitest.config.ts` adapted for apps/app paths 3. Mirror `apps/portal/tests/setup.ts` if relevant 4. Add `"test": "vitest"` and `"test:run": "vitest run"` to `apps/app/package.json` scripts 5. Write one sample test against an existing apps/app component (any simple Vue component with a clear input → output mapping — confirm the harness works end-to-end). `useImpersonationStore.ts` is a natural early target (no runtime test today; the ts-reset-surfaced TECH-TS-IMPERSONATION runtime-validation work would benefit from that coverage) 6. Foundation-tooling-style return deliverable: confirm `pnpm test --run` exits 0 with at least one test passing **Out of scope:** writing comprehensive tests for existing apps/app code. This sprint sets up the harness; per-feature test coverage follows organically as features land or are touched. --- ### WS-6 Deferred #### ARTIST-ADV-SECTION-APPLY Section-level binding apply runtime activation. **Removal trigger:** when artist_advance feature work begins (post-S5). **Action:** set `FORM_BUILDER_SECTION_APPLY=true`, write section-scoped tests, activate `ApplyBindingsOnFormSectionSubmitted` listener registration, remove the feature-flag early-return guard from the listener. **Refs:** RFC-WS-6.md §3 Q10. #### FORM-BINDING-COMPOSITE-IDENTITY Multi-attribute identity-key resolution (e.g. `email OR (first_name+last_name+DOB)`). **Trigger:** when a purpose requires composite identity matching that single binding cannot satisfy. Currently `MaxOneIdentityKeyPerTargetEntity` guard enforces single-key only. **Refs:** RFC-WS-6.md §3 Q8, §6. #### FORM-LIBRARY-RESYNC Admin action to propagate `FormFieldLibrary` binding updates to existing field instances. Currently library-bindings are copy-on-instantiation only (see `ARCH-BINDINGS.md §1`). **Trigger:** when organisations report friction updating shared field templates. **Refs:** RFC-WS-6.md §3 Q11. #### FORM-FAILURE-DAILY-DIGEST Daily digest mailable for orgs with open `FormSubmissionActionFailure` rows above a threshold. **Deferred until** notification framework lands (post-accreditation engine). Requires NotificationBell + NotificationCenter infrastructure. **Refs:** RFC-WS-6.md §3 Q5. #### LOAD-TEST-FOUNDATION Wall-clock concurrent load testing infrastructure (k6 or equivalent) against staging-API. Separate workstream from WS-6. **Trigger:** pre-release hardening phase. **Refs:** RFC-WS-6.md §4 V4. #### ARTIST-ADV-BINDING-MODEL WS-6 v1 omits artist binding-target registry entries entirely. The artist_advance purpose accepts schemas without bindings (Artist subject resolved via portal_token, not via field-to-attribute mapping). For v2, decide: - Should there be an `Artist` Eloquent model class? Currently the `artists` table exists but no class — only the morph map alias points at `App\Models\Artist` (a string). - Which artist attributes are bindable from form data? The advance form is OUTPUT-shaped: it gathers info FROM the artist (rider, hospitality, technical needs); it does not provision Artist attributes the way event_registration provisions Person attributes. - Is the binding model even the right abstraction for advance forms, or do they use a different sync mechanism (e.g. typed AdvanceSection fields)? **Trigger:** when v2 design discussions for artist_advance feature work begin. May result in registry entries, an Artist model, OR a domain-specific alternative to bindings. **Refs:** RFC-WS-6.md §3 Q9 v1.2 addendum, ARCH-BINDINGS.md appendix. #### FORM-BINDING-JSON-PATH WS-6 v1's binding-target registry handles only top-level model columns. JSON-path attributes (e.g. `persons.custom_fields.dietary_preferences`) are not bindable in v1. Adding support requires: - Registry shape extension (path-string syntax: `'custom_fields.dietary_preferences'`) - Applicator's `setAttribute` change → typed json_set / array_set helper - Conflict resolution: do JSON-path siblings resolve independently? - Type-validation: dietary_preferences is a list, but custom_fields itself is JSON — what does identity_key_eligible mean here? For v1 the recommendation: model dietary_preferences (and similar `custom_fields` properties) as a `TAG_PICKER` form_field with a tag_categories config. The TAG_PICKER → user_organisation_tags sync (per ARCH-FORM-BUILDER §31.10) handles this without requiring binding-target column mapping. **Trigger:** when an organisation requests dietary_preferences (or other `custom_fields` properties) as a form binding target. May coincide with Crewli's v2 dietary management feature work. **Refs:** RFC-WS-6.md §3 Q9 v1.2 addendum, ARCH-BINDINGS.md appendix. --- _Laatste update: April 2026_ _Voeg nieuwe items toe met prefix: ARCH-, COMM-, OPS-, VOL-, ART-, FORM-, SUP-, DIFF-, APPS-, TECH-, UX-_