WS-6 (FormBindingApplicator pipeline) is fully landed in main — sessions 1, 2, and 3 all merged. Verification on 2026-05-04 confirmed every RFC-WS-6.md §7 deliverable plus the v1.1/v1.2 addenda. Backend test suite green at 1486 tests, above the RFC §8 target of 1445-1465. Adds a closure-marker note documenting what's verified in main and adds a single status line under §6.2 of the consolidation plan pointing at it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
675 lines
30 KiB
Markdown
675 lines
30 KiB
Markdown
# Crewli — Architectuur Consolidatie Sprint (april 2026)
|
|
|
|
> **Status:** briefing document, vertrekpunt van de consolidatie-sprint.
|
|
> **Doelgroep:** Bert (product owner + solo dev), Claude (architect/PM rol in chat), Claude Code (executie).
|
|
> **Positie t.o.v. andere docs:** dit document is tijdelijk. Na afronding van de sprint wordt
|
|
> de inhoud gedistribueerd over `/CLAUDE.md`, `/dev-docs/SCHEMA.md`, `/dev-docs/ARCH-*.md` en
|
|
> `/dev-docs/dev-guide.md`. Tijdens de sprint is het de enkele bron van waarheid voor wát
|
|
> we aan het doen zijn en waaróm.
|
|
|
|
---
|
|
|
|
## 0. Context in één alinea
|
|
|
|
Crewli is een multi-tenant SaaS-platform voor festival- en event-personeelsbeheer
|
|
(vrijwilligers, crew, artiesten, leveranciers, pers, gasten). Pre-launch. Geen
|
|
gebruikers, geen live data, geen tijdsdruk. Ambitie: een enterprise-grade product
|
|
dat binnen Stichting Echt Feesten gebruikt wordt en daarna bredere markt op
|
|
kan. Het fundament is vandaag op ~80% — functioneel grotendeels aanwezig,
|
|
architecturaal op sommige plekken een compromis dat onder productie-druk had
|
|
moeten standhouden. Dit document legt vast hoe we die laatste 20% invullen
|
|
voordat we aan de 4 kern-workflows (EVENT_REGISTRATION, ARTIST_ADVANCE,
|
|
SUPPLIER_INTAKE en één generiek type) beginnen.
|
|
|
|
---
|
|
|
|
## 1. Leidende principes voor de sprint
|
|
|
|
Deze principes zijn toetssteen voor elke beslissing tijdens de consolidatie.
|
|
Bij twijfel tijdens executie: teruglezen, toetsen, beslissen.
|
|
|
|
**P1 — Enterprise betekent discipline in scope, niet afwezigheid van scope.**
|
|
We bouwen een fundament dat 23 denkbare purposes aan kan, maar we implementeren
|
|
er nu 4. De rest is vocabulaire, geen functionaliteit. Elke feature die niet
|
|
onmisbaar is voor het fundament of voor de 4 kern-workflows wordt op de backlog
|
|
gezet met duidelijke trigger-conditie ("als X, dan bouwen we Y").
|
|
|
|
**P2 — Queryable data krijgt eigen kolommen of tabellen. JSON is uitsluitend
|
|
voor echt opaque config.** Herinterpretatie van het bestaande principe: strenger
|
|
toepassen. Validatieregels, conditional logic, field options, bindings — allemaal
|
|
eigen tabellen. `settings` JSON krimpt fors.
|
|
|
|
**P3 — ULID overal. Zonder uitzonderingen.** Ook pivot-tabellen. Ook logging-tabellen.
|
|
Consistent = onderhoudbaar = veilig.
|
|
|
|
**P4 — Polymorfe relaties zijn een bewust architectuur-patroon, geen kludge.**
|
|
`form_schemas.owner_type/owner_id` en `form_submissions.subject_type/subject_id`
|
|
blijven polymorf. Nieuwe polymorfe relaties voegen we toe waar het domein dit
|
|
rechtvaardigt (niet speculatief).
|
|
|
|
**P5 — Eén SPA, twee hostnames.** Geen twee SPAs. Geen drie. Herconsolidatie
|
|
van `apps/portal/` in `apps/app/` met domein-gebaseerde routing. Zie §4.
|
|
|
|
**P6 — End-to-end-werkend hoofdpad voor fancy features.** FormBindingApplicator
|
|
+ Person-creation + entity-updates zijn eerste-klas fundament, niet een addendum.
|
|
Geen enkele fancy feature (conditional logic, webhooks, delegation) mag afgerond
|
|
heten zolang de basis-pipeline niet volledig gebouwd en getest is.
|
|
|
|
**P7 — Observability is fundament, geen nice-to-have.** Een enterprise-product
|
|
zonder gestructureerde logging, error tracking en performance monitoring is geen
|
|
enterprise-product. Deze laag wordt in de sprint neergezet.
|
|
|
|
**P8 — Documentatie loopt synchroon met code.** Elke sprint-PR wijzigt of
|
|
creëert dev-docs. Geen "docs volgen later". Dit is al praktijk bij Crewli en
|
|
blijft zo.
|
|
|
|
---
|
|
|
|
## 2. De drie valstrikken, en hoe we ze tegenhouden
|
|
|
|
De grote risico's van een open-ended consolidatie-sprint zijn bekend. Expliciet
|
|
benoemen zodat we ze kunnen herkennen als ze zich voordoen.
|
|
|
|
**Valstrik 1 — Scope-creep tijdens refactor.** "We zijn toch bezig in deze code,
|
|
laten we ook X meenemen." Weerstand: X komt op `/dev-docs/BACKLOG.md` met
|
|
trigger-conditie, wordt niet mee-genomen. Enige uitzondering: X is een directe
|
|
blocker voor het huidige consolidatie-werkpakket.
|
|
|
|
**Valstrik 2 — Gold-plating.** Iets verder perfectioneren dan nodig. Elke
|
|
refactor-actie moet een concrete trigger hebben: "dit veroorzaakt drift in Y"
|
|
of "dit blokkeert feature Z" of "dit is een security-risico". "Dit kan
|
|
eleganter" is geen geldige trigger.
|
|
|
|
**Valstrik 3 — Feature-sprawl verkapt als fundering.** "Als we toch bezig zijn
|
|
met bindings, laten we ook een binding-designer-UI bouwen." Nee. Fundering =
|
|
datamodel + services + tests. Features komen daarna, in de 4-workflow-build.
|
|
|
|
**Bij elke nieuwe ingeving tijdens de sprint stelt Claude de drie vragen:**
|
|
1. Is dit een scope-uitbreiding? → backlog
|
|
2. Is dit elegantie-verbetering zonder concrete trigger? → niet doen
|
|
3. Is dit een feature verkleed als fundering? → naar na de consolidatie
|
|
|
|
---
|
|
|
|
## 3. Vastgelegde besluiten (niet ter discussie in deze sprint)
|
|
|
|
Deze beslissingen zijn genomen en worden *niet* opnieuw geopend. Voor volgende
|
|
chats: behandel deze als gegeven.
|
|
|
|
1. **Binding als eerste-klas burger.** Aparte `form_field_bindings` tabel met
|
|
typed kolommen: `field_id`, `target_entity`, `target_attribute`, `merge_strategy`,
|
|
`trust_level`. Eén veld kan meerdere bindings hebben (multi-binding scenarios).
|
|
Details in §6.1.
|
|
|
|
2. **FormBindingApplicator als hoofdpad.** Nieuwe service die na
|
|
`FormSubmissionSubmitted` bindings doorloopt en entities bijwerkt. Inclusief
|
|
automatische Person-creation bij public EVENT_REGISTRATION submissions
|
|
zonder subject. Details in §6.2.
|
|
|
|
3. **Denormalized columns op `form_submissions`.** `event_id`, `organisation_id`
|
|
en `submitted_by_user_id` zijn al aanwezig of worden toegevoegd als directe
|
|
foreign keys, niet alleen via schema-joins. Details in §6.3.
|
|
|
|
4. **Purpose registry in config, geen `custom` escape.** Afgeslankte set van
|
|
7 purposes voor v1.0: EVENT_REGISTRATION, ARTIST_ADVANCE, SUPPLIER_INTAKE,
|
|
POST_EVENT_EVALUATION, INCIDENT_REPORT, SIGNATURE_CONTRACT, USER_PROFILE.
|
|
Registry leeft in `config/form_builder/purposes.php` + typed value-objects.
|
|
Nieuwe purposes vereisen een migratie + listener — dat is correct, niet
|
|
lastig. Details in §6.4.
|
|
|
|
5. **ULID consistent overal, ook pivots.** Bestaande integer-PK-pivots worden
|
|
gemigreerd. Opsporings-pas in week 1 van de sprint. Details in §6.5.
|
|
|
|
6. **Eigen tabellen voor `validation_rules`, `conditional_logic`,
|
|
`form_fields.options`.** Typed, queryable. `form_schemas.settings` krimpt
|
|
naar echt opaque config (rendering hints). Details in §6.6.
|
|
|
|
7. **Eén SPA op één domein.** `apps/portal/` wordt een route-tree binnen
|
|
`apps/app/`. Alles op `crewli.app`. Route-guards bepalen welke layout,
|
|
welke context, welke permissies. Multi-role users krijgen een context-
|
|
switcher in de nav-balk. Details in §4.
|
|
|
|
8. **Observability foundation.** Sentry voor error tracking, structured
|
|
logging via Laravel's context, performance monitoring via Laravel Telescope
|
|
(dev) + Sentry Performance (prod). Details in §6.7.
|
|
|
|
9. **Enterprise scope betekent 4 kern-workflows voor v1.0.** Niet 23. De
|
|
overige purposes in de registry zijn vocabulaire zonder implementatie;
|
|
zij krijgen implementatie wanneer een concrete klant-case dit rechtvaardigt.
|
|
|
|
---
|
|
|
|
## 4. Het één-SPA besluit: uitvoerbaar overzicht
|
|
|
|
Gekozen architectuur: **één codebase, één build, één domein.**
|
|
|
|
### 4.1 Hostnames
|
|
- `crewli.app` — alle gebruikers, alle rollen, één SPA
|
|
- `api.crewli.app` — Laravel backend (ongewijzigd)
|
|
- `docs.crewli.app` — VitePress docs (ongewijzigd)
|
|
|
|
Rationale: vrijwilligers en organizers zijn verschillende rollen binnen
|
|
hetzelfde product, niet verschillende producten op hetzelfde platform. Dit volgt
|
|
het patroon van Slack/Linear/Notion/GitHub, waar alle gebruikers onder één
|
|
domein werken en de UI zich aanpast aan rol + context. Subdomeinen per rol (zoals
|
|
Shopify/Stripe) worden pas zinvol wanneer er fundamenteel verschillende producten
|
|
zijn — niet ons geval.
|
|
|
|
### 4.2 Directory-structuur na consolidatie
|
|
|
|
```
|
|
apps/
|
|
app/ ← enige Vue SPA
|
|
src/
|
|
pages/
|
|
(auth)/ ← login, forgot-password, MFA
|
|
portal/ ← wat nu in apps/portal/src/pages/ zit
|
|
my-shifts/
|
|
availability/
|
|
profile/
|
|
...
|
|
register/ ← publieke formulier-flow (niet ingelogd)
|
|
[public_token].vue
|
|
events/ ← organizer: events + sub-pages
|
|
persons/ ← organizer: persons overview
|
|
organisations/ ← organizer: settings
|
|
platform/ ← super-admin (role:super_admin)
|
|
index.vue ← post-login landing of context-switcher
|
|
components/
|
|
organizer/ ← organizer-only componenten
|
|
portal/ ← portal-only componenten
|
|
shared/ ← gedeeld tussen beide
|
|
composables/
|
|
forms/ ← voormalige packages/form-schema/ komt hier
|
|
...
|
|
layouts/
|
|
OrganizerLayout.vue ← voor organizer routes, met organizer-nav
|
|
PortalLayout.vue ← voor portal routes, met portal-nav
|
|
PublicLayout.vue ← voor /register/* routes, minimaal
|
|
router/
|
|
index.ts ← route-guards bepalen layout + toegang
|
|
routes.ts ← één route-tree, layout per section
|
|
plugins/
|
|
theme.ts ← context-based Vuetify theme injectie
|
|
portal/ ← verdwijnt na sprint
|
|
api/ ← ongewijzigd
|
|
packages/
|
|
form-schema/ ← verdwijnt, inhoud verhuist naar apps/app/src/composables/forms/
|
|
```
|
|
|
|
### 4.3 Routing en context-switching
|
|
|
|
Er is geen hostname-detectie. Routes bepalen alles:
|
|
|
|
```ts
|
|
// Conceptueel — definitieve implementatie in sprint
|
|
const routes = [
|
|
// Publiek (geen auth)
|
|
{ path: '/login', component: LoginPage, meta: { layout: 'PublicLayout' } },
|
|
{ path: '/register/:publicToken', component: PublicFormPage, meta: { layout: 'PublicLayout' } },
|
|
{ path: '/forgot-password', component: ForgotPasswordPage, meta: { layout: 'PublicLayout' } },
|
|
|
|
// Portal routes (auth required, any role)
|
|
{
|
|
path: '/portal',
|
|
meta: { layout: 'PortalLayout', requiresAuth: true, context: 'portal' },
|
|
children: [
|
|
{ path: 'my-shifts', component: MyShiftsPage },
|
|
{ path: 'availability', component: AvailabilityPage },
|
|
{ path: 'profile', component: ProfilePage },
|
|
],
|
|
},
|
|
|
|
// Organizer routes (auth + organizer role)
|
|
{
|
|
path: '/',
|
|
meta: { layout: 'OrganizerLayout', requiresAuth: true, context: 'organizer',
|
|
requiresRole: 'organizer' },
|
|
children: [
|
|
{ path: '', component: OrganizerDashboard },
|
|
{ path: 'events', component: EventsList },
|
|
// ... etc
|
|
],
|
|
},
|
|
|
|
// Platform admin (auth + super_admin role)
|
|
{
|
|
path: '/platform',
|
|
meta: { layout: 'OrganizerLayout', requiresAuth: true, requiresRole: 'super_admin' },
|
|
children: [...]
|
|
},
|
|
]
|
|
```
|
|
|
|
**Post-login landing voor multi-role gebruikers:**
|
|
- Enkel portal-rol → redirect naar `/portal/my-shifts`
|
|
- Enkel organizer-rol → redirect naar `/` (dashboard)
|
|
- Beide rollen → landing-page op `/` met twee kaarten (Portal / Organizer) óf
|
|
direct naar laatst-gebruikte context (lokaal opgeslagen als preference)
|
|
|
|
**Context-switcher in nav-balk** voor multi-role users:
|
|
- Permanent zichtbaar in OrganizerLayout en PortalLayout
|
|
- Eén klik switch naar andere context
|
|
- Vergelijkbaar met Slack's workspace-switcher
|
|
|
|
### 4.4 Wat blijft werken ongewijzigd
|
|
|
|
- Sanctum session auth (één cookie, één domein) — simpeler dan eerst gedacht
|
|
- Portal-token-auth voor publieke routes (`/register/{public_token}`,
|
|
`/public/forms/...` API-endpoints) — blijft als aparte middleware
|
|
- Impersonation — blijft binnen organizer-tree
|
|
- API-calls naar `api.crewli.app` — ongewijzigd
|
|
|
|
**Wat wegvalt door één-domein-keuze:** cross-domain cookie isolation is niet meer
|
|
nodig. De `CookieBearerToken`-middleware per app (uit AUTH_ARCHITECTURE.md) mag
|
|
vereenvoudigd worden in de sprint — dit wordt meegenomen onder WS-3.
|
|
|
|
### 4.5 Wat dit kost
|
|
|
|
Reële inschatting: 2-4 dagen werk in de consolidatie-sprint.
|
|
- 1 dag: directory-structuur omzetten, imports repareren
|
|
- 1 dag: router + layouts + route-guards + context-switcher
|
|
- 1 dag: theming per context, Vuetify-config, auth-cleanup
|
|
- 1 dag: testen, deploy-config verifiëren
|
|
|
|
Iets simpeler dan bij het hostname-alternatief omdat we geen hostname-detector
|
|
hoeven te bouwen en omdat cross-domain cookie complexiteit vervalt.
|
|
|
|
### 4.6 Toekomst-deur openhouden
|
|
|
|
Als later één van deze drie scenario's optreedt, kunnen we alsnog splitsen
|
|
zonder dat we vandaag te vroeg gesplitst hadden:
|
|
|
|
- Portal wil SSR voor SEO → portal splitsen naar Nuxt op aparte subdomain
|
|
- Een third product (marketplace, kassa-app) verschijnt → apart project op
|
|
eigen subdomain
|
|
- Whitelabel per organisatie met eigen subdomain → routing-laag erboven
|
|
|
|
Geen van deze scenario's is vandaag aan de orde. Splitsen vanuit één SPA is
|
|
goed te doen wanneer nodig (één tot twee weken werk).
|
|
|
|
---
|
|
|
|
## 5. Prioriteitsvolgorde werkstromen
|
|
|
|
De consolidatie-sprint kent **acht werkstromen** (afgekort WS). Ze worden in
|
|
deze volgorde uitgevoerd, met waar mogelijk parallelle PRs binnen een werkstroom.
|
|
|
|
**WS-1 — Opsporings-pas (1 dag)**
|
|
Volledige scan van de codebase op: niet-ULID primary keys, JSON-kolommen met
|
|
queryable data die ik nog niet geïdentificeerd heb, polymorfe relaties die
|
|
inconsistent zijn. Output: een definitieve lijst van te migreren items.
|
|
Gaat voor alle andere werkstromen, omdat latere WS-en hun scope ervan
|
|
afhangen.
|
|
|
|
**WS-2 — Purpose registry + legacy purpose-cleanup (2-3 dagen)**
|
|
Van 23 naar 7 purposes. Registry in config. Typed value-objects. Migratie
|
|
die bestaande `form_schemas` opschoont (alle schemas met "weg te halen"
|
|
purposes → archief of delete, afhankelijk van of er seed-data hangt).
|
|
|
|
**WS-3 — Eén SPA consolidatie (3-5 dagen)**
|
|
Zoals in §4. Kan deels parallel aan WS-4 en WS-5 want raakt vooral
|
|
frontend. Apart maken van frontend-consolidatie en backend-consolidatie
|
|
vermijdt rebase-hel.
|
|
|
|
**WS-4 — ULID-consistentie + denormalized submission columns (2-3 dagen)**
|
|
Pivot-tabellen naar ULID. `event_id`, `organisation_id` op submissions
|
|
(en elders waar de opsporings-pas ze vindt).
|
|
|
|
**WS-5 — JSON-kolom-opsplitsing (4-6 dagen)**
|
|
Vier sub-werkstromen die in serie moeten (elk heeft data-migratie):
|
|
- WS-5a — `form_field_bindings` tabel
|
|
- WS-5b — `form_field_validation_rules` tabel
|
|
- WS-5c — `form_field_conditional_logic` tabel
|
|
- WS-5d — `form_field_options` tabel
|
|
|
|
**WS-6 — Binding-pipeline / FormBindingApplicator (4-5 dagen)**
|
|
Nieuwe service. Listener. Person-creation-flow. Guardrails in
|
|
FormSchemaService. Volledige testdekking voor alle 7 purposes (ook als
|
|
ze nog geen functionaliteit hebben — tests verifiëren dat de binding-
|
|
infrastructuur ze ondersteunt).
|
|
|
|
**WS-7 — Observability foundation (2-3 dagen)**
|
|
Sentry SDK (backend + frontend). Structured logging conventies. Telescope
|
|
voor dev. Performance monitoring. Runbook voor incident response.
|
|
|
|
**WS-8 — Documentatie-consolidatie (2-3 dagen)**
|
|
`/CLAUDE.md` bijwerken met nieuwe conventies. `/dev-docs/SCHEMA.md` volledige
|
|
rewrite naar v2.0 met nieuwe tabellen. `/dev-docs/ARCH-FORM-BUILDER.md`
|
|
bijwerken. Nieuwe `/dev-docs/ARCH-BINDINGS.md`. Nieuwe
|
|
`/dev-docs/ARCH-OBSERVABILITY.md`. `/dev-docs/BACKLOG.md` opschonen:
|
|
verwijderen wat door de sprint is opgelost, expliciet markeren wat na
|
|
de sprint nog wordt opgepakt.
|
|
|
|
**Totale inschatting sprint:** 22-32 dagen werk.
|
|
**Doorlooptijd bij actieve inzet:** 4-6 weken.
|
|
**Aantal PRs:** 25-35, veel zijn klein en focused.
|
|
|
|
---
|
|
|
|
## 6. Per werkstroom: scope, uitkomst, klaar-criteria
|
|
|
|
### 6.1 WS-5a — form_field_bindings
|
|
|
|
**Scope:** Nieuwe tabel `form_field_bindings`. Vrije `binding` JSON-kolom op
|
|
`form_fields` wordt vervangen door een queryable relatie. Eén veld kan meerdere
|
|
bindings hebben.
|
|
|
|
**Tabel-ontwerp (concept, finetunen in sprint):**
|
|
```
|
|
form_field_bindings:
|
|
id (ulid, primary)
|
|
form_field_id (ulid, foreign, cascade)
|
|
target_entity (string, 50) -- 'person', 'artist', 'company', 'user'
|
|
target_attribute (string, 100) -- 'email', 'first_name', 'stage_name', etc.
|
|
merge_strategy (enum) -- 'overwrite', 'append', 'replace', 'first_write_wins'
|
|
trust_level (int, default 50) -- 0-100, hoger = dominanter bij conflict
|
|
is_identity_key (bool, default false)-- wordt gebruikt voor person-matching?
|
|
created_at, updated_at
|
|
unique (form_field_id, target_entity, target_attribute)
|
|
```
|
|
|
|
**Uitkomst:**
|
|
- Bestaande `binding` JSON-data gemigreerd naar rijen in deze tabel
|
|
- Builder-UI (later) kan een dropdown tonen gevuld uit binding-registry
|
|
- Backend kan queryen "welke velden binden aan `person.email`"
|
|
- Conflict-strategie per binding expliciet
|
|
|
|
**Klaar-criteria:**
|
|
- Migratie loopt vooruit én terug zonder data-verlies
|
|
- Feature tests: schema create → binding toevoegen → submission submit → entity updated
|
|
- `form_fields.binding` JSON-kolom verdwenen
|
|
- ARCH-BINDINGS.md geschreven met complete uitleg
|
|
|
|
### 6.2 WS-6 — FormBindingApplicator
|
|
|
|
**Scope:** Nieuwe service `App\Services\FormBuilder\FormBindingApplicator`. Nieuwe
|
|
listener `App\Listeners\FormBuilder\ApplyBindingsOnFormSubmit`. Person-creation-
|
|
helper. Update van `FormSchemaService` met pre-publish-validation.
|
|
|
|
**Uitkomst:**
|
|
- Bij elke `FormSubmissionSubmitted` worden alle bindings doorlopen
|
|
- Voor elke binding: lookup value in submission, haal merge-strategie op,
|
|
update target entity
|
|
- Bij public EVENT_REGISTRATION zonder subject: creëer Person, zet als
|
|
subject op submission, voer dan bindings uit
|
|
- Identity-match draait na binding-applicatie op nieuwe Person
|
|
- Alle actions gelogged in activity log met bron-submission
|
|
|
|
**Klaar-criteria:**
|
|
- Test: public registratie → Person aangemaakt met alle binding-attributen
|
|
- Test: authenticated update-submission → bestaande Person bijgewerkt
|
|
- Test: conflict (twee submissions, zelfde email-binding, verschillende waarde)
|
|
→ last-write-wins met activity log entry
|
|
- Test: tag-picker additief (niet overschrijvend)
|
|
- Test: schema met AVAILABILITY_PICKER zonder event-koppeling kan niet publiceren
|
|
- Test: EVENT_REGISTRATION schema zonder email-binding kan niet publiceren
|
|
|
|
**Status:** Complete (verified 2026-05-04). All three sessions landed in main; backend test count 1486, exceeds RFC §8 target of 1445-1465. See dev-docs/WS-6-COMPLETE-VERIFICATION-2026-05-04.md for verification details.
|
|
|
|
### 6.3 WS-4 — Denormalized submission columns
|
|
|
|
**Scope:** Kolommen `event_id` (nullable), `organisation_id` (niet-nullable),
|
|
`submitted_by_user_id` (bestaat al) op `form_submissions`. Observer vult ze bij
|
|
create. Backfill-script voor bestaande submissies (indien aanwezig in seed-data).
|
|
|
|
**Uitkomst:**
|
|
- Queries zoals "alle submissions voor event X" worden directe queries, geen joins
|
|
- Analytics / reporting kan efficiënt draaien
|
|
- Multi-event reuse scenario's (ARTIST_ADVANCE) werken: één schema, submissies
|
|
naar meerdere events
|
|
|
|
**Klaar-criteria:**
|
|
- Feature tests: submission krijgt correcte event_id en organisation_id
|
|
- Scope-middleware gebruikt event_id direct (waar relevant)
|
|
- SCHEMA.md bijgewerkt
|
|
|
|
### 6.4 WS-2 — Purpose registry
|
|
|
|
**Scope:** `app/FormBuilder/Purposes/PurposeDefinition.php` (value object).
|
|
`config/form_builder/purposes.php` (registry). `PurposeRegistry` service.
|
|
`form_schemas.purpose` blijft een string-kolom, maar `custom_purpose_slug`
|
|
kolom verdwijnt.
|
|
|
|
**Purposes v1.0:**
|
|
1. `event_registration` — vrijwilligers/crew aanmelden, subject=Person
|
|
2. `artist_advance` — artiesten advance invullen, subject=Artist
|
|
3. `supplier_intake` — leveranciers onboarding, subject=Company
|
|
4. `post_event_evaluation` — feedback na afloop, subject=Person
|
|
5. `incident_report` — incidenten melden, subject=Person
|
|
6. `signature_contract` — contracten ondertekenen, subject=User
|
|
7. `user_profile` — profiel-updates, subject=User
|
|
|
|
**Uitkomst:**
|
|
- Purposes zijn data-driven: je kunt in config een nieuwe toevoegen zonder
|
|
enum-migratie, maar je moet wel bewust zijn dat een nieuwe purpose meestal
|
|
een listener vereist
|
|
- Elke PurposeDefinition heeft: label, subject_type, default_submission_mode,
|
|
allows_public_access, required_bindings (lijst van bindings die het schema
|
|
moet hebben om publiceerbaar te zijn)
|
|
|
|
**Klaar-criteria:**
|
|
- Tests voor elke purpose: kan schema gecreëerd worden, kan gepubliceerd worden
|
|
(met juiste bindings), submit-flow werkt end-to-end
|
|
- ARCH-FORM-BUILDER.md bijgewerkt
|
|
|
|
### 6.5 WS-4 — ULID-consistentie
|
|
|
|
**Scope:** Alle pivot-tabellen met integer PK → ULID. Opsporings-pas (WS-1)
|
|
definieert de exacte lijst. Data-migratie per tabel.
|
|
|
|
**Uitkomst:** Consistent ID-gebruik. Geen ID-enumeration-risico meer.
|
|
Eenvormig test-patroon.
|
|
|
|
**Klaar-criteria:**
|
|
- Alle primary keys in business-schema zijn ULID
|
|
- Alle foreign keys consistent
|
|
- Tests lopen door
|
|
|
|
### 6.6 WS-5b/c/d — Remaining JSON-splits
|
|
|
|
Dezelfde aanpak als WS-5a maar voor:
|
|
- `form_field_validation_rules` (targeting `validation_rules` JSON)
|
|
- `form_field_conditional_logic` (targeting `conditional_logic` JSON, met
|
|
AND/OR tree structuur)
|
|
- `form_field_options` (targeting `options` JSON, met position + label +
|
|
metadata per optie)
|
|
|
|
**Conditional logic verdient speciale aandacht.** AND/OR trees in een relationele
|
|
tabel zijn niet triviaal. Overweging in de sprint: platte lijst met
|
|
`logical_group_id` vs recursieve tabel met `parent_id`. Beslissing tijdens
|
|
WS-5c, niet vooruit.
|
|
|
|
### 6.7 WS-7 — Observability
|
|
|
|
**Scope:**
|
|
- Sentry SDK installeren in `apps/api/` (Laravel) en `apps/app/` (Vue)
|
|
- Structured logging convention: alle logs via `Log::channel('structured')`
|
|
met context (organisation_id, user_id, request_id)
|
|
- Laravel Telescope in dev only
|
|
- Sentry Performance voor transaction tracking in prod
|
|
- Runbook in `dev-docs/RUNBOOK.md` met: hoe te reageren op alerts, waar
|
|
logs te vinden, hoe een error te tracen
|
|
|
|
**Klaar-criteria:**
|
|
- Test: geforceerde exception in een controller verschijnt in Sentry (staging)
|
|
- Test: elke request heeft een unique request_id in logs
|
|
- Telescope werkt in dev, niet in prod
|
|
|
|
### 6.8 WS-3 — Eén SPA consolidatie
|
|
|
|
Zie §4 voor scope en stappen.
|
|
|
|
**Voortgang:**
|
|
- **Sessie 1a (2026-04-29)** — _foundation gap closure, klaar._
|
|
Lefthook geïnstalleerd op repo-root; `.githooks/post-commit` en
|
|
`pre-push` 1:1 gemigreerd naar `lefthook.yml` (scripts blijven
|
|
source of truth, lefthook dispatcht). Drie layout-skeletons
|
|
toegevoegd in `apps/app/src/layouts/`: `OrganizerLayout.vue`,
|
|
`PortalLayout.vue`, `PublicLayout.vue` — nog niet aan router
|
|
gekoppeld. Vitest-smoketests dekken alle drie (8 nieuwe tests,
|
|
41 → 49 in apps/app). ESLint-baseline en router-wiring volgen
|
|
in 1b/1c.
|
|
- **Sessie 1b-i (2026-04-29)** — _lint baseline reductie + audit,
|
|
klaar._ Risk-tiered `eslint --fix` pass: Tier 1 (Vue templates) +
|
|
Tier 2 (TypeScript plumbing) + trailing-whitespace cleanup. De
|
|
baseline ging van **1451 → 231** problemen. Tier 3 (vite.config.ts,
|
|
themeConfig.ts, vitest.config.ts) is bewust uitgesteld naar 1b-ii
|
|
vanwege runtime-risico van quote/semi/sort-imports autofixes op
|
|
config bestanden. Audit rapport met 6-bucket categorisatie van de
|
|
resterende 231 items in `dev-docs/WS-3-LINT-BASELINE-AUDIT-2026-04-29.md`.
|
|
Vijf open vragen voor Bert + Claude Chat staan klaar voor 1b-ii.
|
|
- **Sessie 1b-ii (2026-04-29)** — _lint baseline cleanup execution,
|
|
klaar._ Alle 16 actiepunten uit het 1b-i audit-rapport uitgevoerd
|
|
volgens de Q1-Q5 dispositions. Baseline ging van **231 → 32** problemen
|
|
(30 errors, 2 warnings). `pnpm lint` is ontkoppeld van `--fix`
|
|
(`pnpm lint:fix` is nu de expliciete autofix-variant). Tier 3 config
|
|
files met succes hand-gereviewed en gefixt (vite/themeConfig/vitest;
|
|
build smoke groen). `src/@core/**` en `src/@layouts/**` toegevoegd
|
|
aan ignorePatterns (vendored Vuexy). Twee bekende restpunten voor
|
|
follow-up: 24 indent items in `useTimeSlotDropdown.ts` (rule-config
|
|
conflict tussen `indent` rule's default `SwitchCase: 0` en de
|
|
codebase's `SwitchCase: 1` style), en 2 promise/no-promise-in-callback
|
|
warnings in `lib/axios.ts:61,73` (Q4's `void` prefix recipe blijkt
|
|
empirisch de rule niet te kalmeren — vraag voor 1b-iii).
|
|
- **Sessie 1b-iii (2026-04-29)** — _lint baseline mop-up, klaar._
|
|
Drie restpunten uit 1b-ii afgesloten via twee `.eslintrc.cjs`
|
|
tweaks en één axios refactor: (1) `indent` rule krijgt
|
|
`{ SwitchCase: 1 }` om de codebase-style te matchen (resolveert 24
|
|
items in useTimeSlotDropdown.ts in één config-regel, geen code-rewrite
|
|
nodig); (2) per-`*.vue` override op `lines-around-comment` met
|
|
`beforeBlockComment: false` + `beforeLineComment: false` zodat
|
|
SFC `<script>`-tag-aangrenzende comments het rule niet meer triggeren
|
|
(resolveert 3 items in PortalLayout/PublicLayout/AppKpiCard, lost
|
|
het 1b-ii-empirisch geconstateerde conflict met `vue/block-tag-newline`
|
|
op); (3) axios response interceptor herschreven van
|
|
`error => { void import(...).then(...) }` naar
|
|
`async error => { ... await import(...) }` — semantisch identiek
|
|
(de 2 sites navigeren beide via `window.location.href` weg) maar
|
|
voldoet wel aan `promise/no-promise-in-callback`. Build smoke
|
|
groen (12.13s). Baseline ging van **32 → 1**. Het laatste item is
|
|
een pre-existing `sonarjs/no-collapsible-if` op
|
|
`useImpersonationStore.ts:103` — niet in scope van 1b-iii's
|
|
drie geplande wijzigingen, doorschuiven naar follow-up. WS-3 lint
|
|
cleanup workstream effectief afgerond; sessie 1c
|
|
(eslint-plugin-boundaries) kan starten op een schone baseline.
|
|
- **Sessie 1c (2026-04-30)** — _import-boundaries enforcement, klaar._
|
|
`eslint-plugin-boundaries@6.0.2` (MIT, ESLint ≥6 peer-dep, Node ≥18.18)
|
|
toegevoegd als directe devDep aan `apps/app/package.json` per de
|
|
TECH-PORTAL-ESLINT-DEPS lesson, en geactiveerd in `apps/app/.eslintrc.cjs`
|
|
met layered-architecture matrix: 10 zones (`types`, `utils`, `lib`,
|
|
`plugins`, `composables`, `stores`, `navigation`, `components`, `layouts`,
|
|
`pages`) met richtgevende edges (rationale + bewijs in
|
|
`dev-docs/WS-3-SESSION-1C-AUDIT.md`). Vendored `@core/`, `@layouts/`,
|
|
het dode `views/` bestand en orchestratie-roots `App.vue` + `main.ts`
|
|
staan in `boundaries/ignore`. Vier `lib → stores` violations in
|
|
`lib/axios.ts` (regels 3, 4, 61, 72) gemarkeerd met per-line
|
|
`eslint-disable-next-line` comments referencerend naar
|
|
`TECH-AXIOS-STORE-COUPLING` — de structurele decoupling van axios is
|
|
bewust uitgesteld naar een dedicated sessie omdat het architectuurwerk
|
|
is, geen tooling-cleanup. Boundary-rule blijft `error`, matrix blijft
|
|
strict; toekomstige `lib/X.ts` schrijvers stoten alsnog tegen de regel.
|
|
Lint baseline 0 errors / 0 warnings; build smoke groen; Vitest groen
|
|
(49 tests). Drie backlog-items aangemaakt voor toekomstige actie:
|
|
`TECH-AXIOS-STORE-COUPLING` (decouple axios), `TECH-DELETE-DEAD-VIEWS`
|
|
(verwijder `src/views/`), `TECH-WS3-BOUNDARIES-SUBZONES` (sub-zone
|
|
enforcement na PR-B), `TECH-WS3-BOUNDARIES-ROUTER-ZONE` (matrix-update
|
|
wanneer `plugins/1.router/` naar `router/` verhuist). WS-3 lint cleanup
|
|
+ boundaries enforcement effectief afgerond; volgende WS-3 stap is PR-B
|
|
(portal merge) zodra WS-6 sessie 2 in main is geland. Debt closed in
|
|
commit `53f6a7b`: `lib/axios.ts` decoupled from stores via
|
|
`registerInterceptors(client, deps)` callback seam,
|
|
`plugins/3.axios-bindings.ts` provides the runtime wiring; the four
|
|
per-line disables are removed.
|
|
|
|
**Klaar-criteria:**
|
|
- `apps/portal/` is verwijderd
|
|
- Beide hostnames serveren correcte route-tree
|
|
- Auth werkt op beide hostnames met isolatie
|
|
- Alle bestaande tests in apps/app/ draaien groen
|
|
- Nieuwe tests voor hostname-detectie
|
|
- Deploy-config (DirectAdmin) verifieert beide hostnames
|
|
|
|
---
|
|
|
|
## 7. Starten van de volgende chat
|
|
|
|
Deze chat wordt afgesloten. De volgende chat start met een dichte briefing die
|
|
**exact dit document** en de volgende meta-instructie als context geeft.
|
|
|
|
**Opening-prompt voor de volgende chat (kopieer letterlijk):**
|
|
|
|
```
|
|
Nieuwe chat in project Crewli. We beginnen de architectuur-consolidatie-sprint
|
|
zoals vastgelegd in /dev-docs/ARCH-CONSOLIDATION-2026-04.md (lees dat document
|
|
eerst uit Gitea, main branch).
|
|
|
|
Context:
|
|
- Pre-launch. Geen gebruikers, geen tijdsdruk. Doel: enterprise-grade fundament.
|
|
- 8 werkstromen (WS-1 t/m WS-8). We starten met WS-1: opsporings-pas.
|
|
- Ik wil dat je ook /CLAUDE.md en /dev-docs/SCHEMA.md leest voor basiscontext,
|
|
en kort aangeeft wat je hebt begrepen.
|
|
|
|
Verzoek voor deze eerste chat:
|
|
1. Bevestig dat je het consolidatie-document en CLAUDE.md hebt gelezen.
|
|
2. Lever de prompt voor WS-1 (opsporings-pas). Deze moet een Claude Code opdracht
|
|
zijn die het hele codebase doorloopt op:
|
|
- Primary keys die geen ULID zijn
|
|
- JSON-kolommen met queryable data die niet in §6 zijn benoemd
|
|
- Polymorfe relaties die inconsistent gemodelleerd zijn
|
|
- Overige architectuur-smells die we in de consolidatie moeten meenemen
|
|
3. Output van WS-1 is een rapport (Markdown), geen code. Op basis daarvan
|
|
beslissen we of de scope van andere werkstromen aanpassing behoeft.
|
|
|
|
Regels die tijdens de sprint gelden:
|
|
- Elke nieuwe ingeving: toets aan de drie valstrikken in §2 van het
|
|
consolidatie-document. Bij twijfel: backlog.
|
|
- Elke architectuur-keuze tijdens executie: toets aan de principes in §1.
|
|
- Geen scope-uitbreiding zonder expliciete go van mij.
|
|
|
|
Bevestig dat je klaar bent voor WS-1.
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Verwijzingen naar bestaand materiaal
|
|
|
|
Voor de volgende chat is het nuttig dat deze documenten als achtergrond
|
|
beschikbaar zijn (niet allemaal lezen, maar weten dat ze bestaan):
|
|
|
|
- `/CLAUDE.md` — projectafspraken, zero-compromise regels
|
|
- `/dev-docs/SCHEMA.md` v2.1 — huidig database-schema
|
|
- `/dev-docs/ARCH-FORM-BUILDER.md` — bestaande form-builder architectuur
|
|
- `/dev-docs/AUTH_ARCHITECTURE.md` — dual-mode auth, cross-app isolation
|
|
- `/dev-docs/VUEXY_COMPONENTS.md` — frontend component-bibliotheek
|
|
- `/dev-docs/BACKLOG.md` — bestaande backlog, wordt opgeschoond in WS-8
|
|
|
|
---
|
|
|
|
## 9. Openstaand (niet in scope van consolidatie-sprint)
|
|
|
|
Om expliciet te zijn over wat *niet* in deze sprint gebeurt:
|
|
|
|
- Implementatie van de 4 kern-workflows (komt erna als aparte sprint S3c...S3f)
|
|
- Nieuwe UI-modules (komt erna)
|
|
- Marketplace / subscription / billing (toekomst)
|
|
- Mobile app (toekomst)
|
|
- Locale-uitbreiding voorbij NL/EN (komt wanneer marktbehoefte concreet is)
|
|
- Performance-optimalisatie voorbij observability foundation (wanneer metrics
|
|
dit rechtvaardigen)
|
|
|
|
---
|
|
|
|
## 10. Sign-off ruimte
|
|
|
|
Voor Bert om te bevestigen voordat we de volgende chat starten:
|
|
|
|
- [ ] Leidende principes §1 akkoord
|
|
- [ ] Vastgelegde besluiten §3 akkoord
|
|
- [ ] SPA-consolidatie §4 akkoord
|
|
- [ ] Werkstroom-volgorde §5 akkoord
|
|
- [ ] Purposes v1.0 lijst §6.4 akkoord
|
|
- [ ] Openings-prompt §7 klaar om te gebruiken
|
|
|
|
Na jouw sign-off commit ik dit document naar de repo en sluiten we deze chat.
|