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>
30 KiB
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-*.mden/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:
- Is dit een scope-uitbreiding? → backlog
- Is dit elegantie-verbetering zonder concrete trigger? → niet doen
- 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.
-
Binding als eerste-klas burger. Aparte
form_field_bindingstabel 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. -
FormBindingApplicator als hoofdpad. Nieuwe service die na
FormSubmissionSubmittedbindings doorloopt en entities bijwerkt. Inclusief automatische Person-creation bij public EVENT_REGISTRATION submissions zonder subject. Details in §6.2. -
Denormalized columns op
form_submissions.event_id,organisation_idensubmitted_by_user_idzijn al aanwezig of worden toegevoegd als directe foreign keys, niet alleen via schema-joins. Details in §6.3. -
Purpose registry in config, geen
customescape. 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 inconfig/form_builder/purposes.php+ typed value-objects. Nieuwe purposes vereisen een migratie + listener — dat is correct, niet lastig. Details in §6.4. -
ULID consistent overal, ook pivots. Bestaande integer-PK-pivots worden gemigreerd. Opsporings-pas in week 1 van de sprint. Details in §6.5.
-
Eigen tabellen voor
validation_rules,conditional_logic,form_fields.options. Typed, queryable.form_schemas.settingskrimpt naar echt opaque config (rendering hints). Details in §6.6. -
Eén SPA op één domein.
apps/portal/wordt een route-tree binnenapps/app/. Alles opcrewli.app. Route-guards bepalen welke layout, welke context, welke permissies. Multi-role users krijgen een context- switcher in de nav-balk. Details in §4. -
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.
-
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 SPAapi.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:
// 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_bindingstabel - WS-5b —
form_field_validation_rulestabel - WS-5c —
form_field_conditional_logictabel - WS-5d —
form_field_optionstabel
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
bindingJSON-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.bindingJSON-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
FormSubmissionSubmittedworden 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:
event_registration— vrijwilligers/crew aanmelden, subject=Personartist_advance— artiesten advance invullen, subject=Artistsupplier_intake— leveranciers onboarding, subject=Companypost_event_evaluation— feedback na afloop, subject=Personincident_report— incidenten melden, subject=Personsignature_contract— contracten ondertekenen, subject=Useruser_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(targetingvalidation_rulesJSON)form_field_conditional_logic(targetingconditional_logicJSON, met AND/OR tree structuur)form_field_options(targetingoptionsJSON, 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) enapps/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.mdmet: 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-commitenpre-push1:1 gemigreerd naarlefthook.yml(scripts blijven source of truth, lefthook dispatcht). Drie layout-skeletons toegevoegd inapps/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 --fixpass: 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 indev-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 lintis ontkoppeld van--fix(pnpm lint:fixis nu de expliciete autofix-variant). Tier 3 config files met succes hand-gereviewed en gefixt (vite/themeConfig/vitest; build smoke groen).src/@core/**ensrc/@layouts/**toegevoegd aan ignorePatterns (vendored Vuexy). Twee bekende restpunten voor follow-up: 24 indent items inuseTimeSlotDropdown.ts(rule-config conflict tussenindentrule's defaultSwitchCase: 0en de codebase'sSwitchCase: 1style), en 2 promise/no-promise-in-callback warnings inlib/axios.ts:61,73(Q4'svoidprefix 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.cjstweaks en één axios refactor: (1)indentrule 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-*.vueoverride oplines-around-commentmetbeforeBlockComment: false+beforeLineComment: falsezodat SFC<script>-tag-aangrenzende comments het rule niet meer triggeren (resolveert 3 items in PortalLayout/PublicLayout/AppKpiCard, lost het 1b-ii-empirisch geconstateerde conflict metvue/block-tag-newlineop); (3) axios response interceptor herschreven vanerror => { void import(...).then(...) }naarasync error => { ... await import(...) }— semantisch identiek (de 2 sites navigeren beide viawindow.location.hrefweg) maar voldoet wel aanpromise/no-promise-in-callback. Build smoke groen (12.13s). Baseline ging van 32 → 1. Het laatste item is een pre-existingsonarjs/no-collapsible-ifopuseImpersonationStore.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 aanapps/app/package.jsonper de TECH-PORTAL-ESLINT-DEPS lesson, en geactiveerd inapps/app/.eslintrc.cjsmet layered-architecture matrix: 10 zones (types,utils,lib,plugins,composables,stores,navigation,components,layouts,pages) met richtgevende edges (rationale + bewijs indev-docs/WS-3-SESSION-1C-AUDIT.md). Vendored@core/,@layouts/, het dodeviews/bestand en orchestratie-rootsApp.vue+main.tsstaan inboundaries/ignore. Vierlib → storesviolations inlib/axios.ts(regels 3, 4, 61, 72) gemarkeerd met per-lineeslint-disable-next-linecomments referencerend naarTECH-AXIOS-STORE-COUPLING— de structurele decoupling van axios is bewust uitgesteld naar een dedicated sessie omdat het architectuurwerk is, geen tooling-cleanup. Boundary-rule blijfterror, matrix blijft strict; toekomstigelib/X.tsschrijvers 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(verwijdersrc/views/),TECH-WS3-BOUNDARIES-SUBZONES(sub-zone enforcement na PR-B),TECH-WS3-BOUNDARIES-ROUTER-ZONE(matrix-update wanneerplugins/1.router/naarrouter/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.tsdecoupled from stores viaregisterInterceptors(client, deps)callback seam,plugins/3.axios-bindings.tsprovides the runtime wiring; the four per-line disables are removed.
- 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
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.mdv2.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.