chore(docs): delete obsolete bootstrap and prompt-template docs

Five files removed, all describing project states that no longer
apply post-WS-TOOLING-001:

- .cursor/instructions.md (8.4 KB): Phase 1-4 roadmap with all
  checkboxes empty; Phase 1 has been done for ~6 months. Broken
  'make portal' target. Content overlaps with CLAUDE.md.
- .cursor/ARCHITECTURE.md (18.9 KB): pre-WS-3 framing (dual SPA,
  dual cookies, dual SANCTUM_STATEFUL_DOMAINS) AND pre-Form-Builder
  schema (volunteer_profiles, public_forms with JSON fields). All
  six sections superseded by SCHEMA.md, AUTH_ARCHITECTURE.md,
  design-document.md, API.md, 102_multi_tenancy.mdc.
- dev-docs/MASTER_PROMPT_CC.md (13 KB): 'paste this above every task'
  workflow superseded by auto-loaded CLAUDE.md and structured
  Claude Chat-authored prompts. Stale dual-SPA + pre-Form-Builder
  assumptions throughout.
- dev-docs/MASTER_PROMPT_CURSOR.md (7.5 KB): same workflow obsoletion;
  Cursor is now IDE-only (Claude Code does all implementation).
  .cursor/rules/ system handles IDE-level guidance.
- dev-docs/dev-guide.md (32 KB): bootstrap-from-scratch document
  containing embedded snapshots of pre-Form-Builder CLAUDE.md,
  pre-Form-Builder SCHEMA.md, pre-Form-Builder API.md as
  copy-paste templates. Section 5 prompts pre-WS-TOOLING-001 era.
  Section 6 (agents) overlaps with CLAUDE_CODE_TOOLING.md.

Total: ~80 KB doc-rot removed.

Cross-reference check found four files outside the deleted set
referencing the deleted paths; all updated in the same commit:

- README.md: Documentation table rebuilt around CLAUDE.md +
  dev-docs/* (also dropped stale resources/design/ row pointing
  at a directory that no longer exists, and corrected docs/*
  paths to dev-docs/*)
- dev-docs/CLAUDE_DESKTOP_SETUP.md: dropped MASTER_PROMPT_CC,
  MASTER_PROMPT_CURSOR, dev-guide entries from the
  bewust-verwijderd exclusion list; updated Gerelateerd pointer
  from dev-guide.md -> SETUP.md
- dev-docs/ARCH-CONSOLIDATION-2026-04.md: updated future-distribution
  pointer from dev-guide.md -> SETUP.md (sprint briefing is
  historical so the change is purely doc-hygiene)
- dev-docs/VIBE_CODING_CHECKLIST.md: removed Dev guide row from
  the bestandspaden table

Remaining references in dev-docs/BACKLOG.md (lines 862-869) live
inside the TECH-DOCS-APPS-PORTAL-PURGE entry that closes in the
next commit.

Canonical replacements: dev-docs/SETUP.md (rewritten this PR),
CLAUDE.md, CLAUDE_CODE_TOOLING.md, and the ARCH-*.md series.
This commit is contained in:
2026-05-06 02:14:10 +02:00
parent 2c4d2257ae
commit d33c119d75
9 changed files with 8 additions and 1939 deletions

View File

@@ -1,436 +0,0 @@
# Crewli - Architecture
> Multi-tenant SaaS platform for event- and festival management.
> Source of truth: `/resources/design/design-document.md`
## System Overview
```
┌─────────────────────────────────────────────────────────────────────────┐
│ INTERNET │
└─────────────────────────────────────────────────────────────────────────┘
┌───────────────┼───────────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Organizer + │ │ Portal SPA │
│ Admin SPA │ │ (External) │
│ :5174 │ │ :5175 │
└───────┬───────┘ └───────┬───────┘
│ │
└───────────────┼───────────────┘
│ CORS + Sanctum tokens
┌───────────────────────┐
│ Laravel 12 REST API │
│ (JSON only, no │
│ Blade views) │
│ :8000 │
└───────────┬───────────┘
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ MySQL 8 │ │ Redis │ │ Mailpit │
│ :3306 │ │ :6379 │ │ :8025 │
└───────────┘ └───────────┘ └───────────┘
```
**Golden Rule:** Laravel is exclusively a JSON REST API. No Blade views, no Mix, no Inertia. Every response is `application/json`. Vue handles ALL UI via two SPAs.
---
## Applications
### Organizer App (`apps/app/`)
**Purpose**: Main application for event management per organisation. Also serves as the platform admin interface for `super_admin` users via `/platform/*` routes.
**Users**: Organisation Admins, Event Managers, Staff Coordinators, Artist Managers, Volunteer Coordinators, Super Admins (platform management via `/platform/*`).
**Features**:
- Event lifecycle management (Draft through Closed)
- Festival Sections, Time Slots, Shift planning
- Person & Crowd management (Crew, Volunteers, Artists, Guests, Press, Partners, Suppliers)
- Accreditation engine (categories, items, access zones)
- Artist booking & advancing
- Timetable & stage management
- Briefing builder & communication hub
- Mission Control (show day operations)
- Form builder with conditional logic
- Supplier & production management
- Reporting & insights
- Platform admin: organisation management, billing, platform users (`/platform/*` routes, `super_admin` only)
**Vuexy Version**: `typescript-version/full-version` (customized navigation)
---
### Portal (`apps/portal/`)
**Purpose**: External-facing portal with two access modes.
**Users**: Volunteers, Crew (login-based), Artists, Suppliers, Press (token-based).
**Access Modes**:
| User | Access Mode | Rationale |
|------|-------------|-----------|
| Volunteer / Crew | Login (`auth:sanctum`) | Long-term relationship, festival passport, shift history |
| Artist / Tour Manager | Token (`portal.token` middleware) | Per-event, advance portal via signed URL |
| Supplier / Partner | Token (`portal.token` middleware) | Per-event, production request via token |
| Press / Media | Token (`portal.token` middleware) | Per-event accreditation, no recurring relationship |
**Router guard logic**: If `route.query.token` -> token mode. If `authStore.isAuthenticated` -> login mode. Otherwise -> redirect to `/login`.
**Vuexy Version**: `typescript-version/starter-kit` (stripped: no sidebar, no customizer, no dark mode toggle; uses Vuetify components + Vuexy SCSS)
---
## Multi-Tenant Data Model
Shared database schema with organisation scoping on all tables. No row-level security at DB level; scoping enforced via Laravel Policies and Eloquent Global Scopes.
**Scoping Rule**: EVERY query on event data MUST have an `organisation_id` scope via `OrganisationScope` Global Scope.
**Tenancy Hierarchy**:
```
Platform (Super Admin)
└─ Organisation (client A)
└─ Event (event 1)
└─ Festival Section (Bar, Hospitality, Technical, ...)
├─ Time Slots (DAY1-EARLY-CREW, DAY1-EARLY-VOLUNTEER, ...)
└─ Shifts (Bar x DAY1-EARLY-VOLUNTEER, 5 slots)
```
---
## Three-Level Role & Permission Model
Managed via Spatie `laravel-permission` with team-based permissions.
| Level | Scope | Roles | Implementation |
|-------|-------|-------|----------------|
| App Level | Whole platform | `super_admin`, `support_agent` | Spatie role |
| Organisation Level | Within one org | `org_admin`, `org_member`, `org_readonly` | Spatie team = organisation |
| Event Level | Within one event | `event_manager`, `artist_manager`, `staff_coordinator`, `volunteer_coordinator`, `accreditation_officer` | `event_user_roles` pivot table |
**Middleware**: `OrganisationRoleMiddleware` and `EventRoleMiddleware` check per route.
---
## Event Lifecycle
| Phase | Description |
|-------|-------------|
| `draft` | Created but not published. Only admin sees it. |
| `published` | Active in planning. Internal modules available. External portals closed. |
| `registration_open` | Volunteer registration and artist advance portals open. |
| `buildup` | Setup days. Crew shifts begin. Accreditation distribution starts. |
| `showday` | Active event days. Mission Control active. Real-time check-in. |
| `teardown` | Breakdown days. Inventory return. Shift closure. |
| `closed` | Event completed. Read-only. Reports available. |
---
## API Structure
### Base URL
- Development: `http://localhost:8000/api/v1`
### Route Groups
```
# Public (no auth)
POST /auth/login
POST /portal/token-auth Token-based portal access
POST /portal/form-submit Public form submission
# Protected (auth:sanctum)
POST /auth/logout
GET /auth/me Returns user + organisations + event roles
# Organisations
GET/POST /organisations
GET/PUT /organisations/{id}
POST /organisations/{id}/invite
GET /organisations/{id}/members
# Events (nested under organisations)
GET/POST /organisations/{org}/events
GET/PUT/DELETE /events/{id}
PUT /events/{id}/status
# Festival Sections
GET/POST /events/{event}/sections
GET/PUT/DELETE /sections/{id}
GET /sections/{id}/dashboard
# Time Slots
GET/POST /events/{event}/time-slots
PUT/DELETE /time-slots/{id}
# Shifts
GET/POST /sections/{section}/shifts
PUT/DELETE /shifts/{id}
POST /shifts/{id}/assign
POST /shifts/{id}/claim Volunteer self-service
# Persons
GET/POST /events/{event}/persons
GET/PUT /persons/{id}
POST /persons/{id}/approve
POST /persons/{id}/checkin
# Crowd Types & Lists
GET/POST /organisations/{org}/crowd-types
GET/POST /events/{event}/crowd-lists
# Artists & Advancing
GET/POST /events/{event}/artists
GET/PUT /artists/{id}
GET/POST /artists/{id}/sections Advance sections
POST /sections/{id}/submit Advance submission
# Accreditation
GET/POST /events/{event}/accreditation-items
POST /persons/{id}/accreditations
GET/POST /events/{event}/access-zones
# Briefings & Communication
GET/POST /events/{event}/briefings
POST /briefings/{id}/send
GET/POST /events/{event}/campaigns
POST /campaigns/{id}/send
# Mission Control
GET /events/{event}/mission-control
POST /persons/{id}/checkin-item
# Scanners & Inventory
GET/POST /events/{event}/scanners
POST /scan
GET/POST /events/{event}/inventory
# Reports
GET /events/{event}/reports/{type}
# Portal (token-based, portal.token middleware)
GET /portal/artist
POST /portal/advancing
GET /portal/supplier
POST /portal/production-request
```
### API Response Format
```json
{
"data": { ... },
"meta": {
"pagination": {
"current_page": 1,
"per_page": 15,
"total": 100,
"last_page": 7
}
}
}
```
---
## Core Database Schema
**Primary Keys**: ULID on all business tables via `HasUlids` trait. Pure pivot tables use auto-increment integer PK.
**Soft Deletes ON**: organisations, events, festival_sections, shifts, shift_assignments, persons, artists, companies, production_requests.
**Soft Deletes OFF** (audit records): check_ins, briefing_sends, message_replies, shift_waitlist, volunteer_festival_history.
**JSON Columns**: ONLY for opaque config (blocks, fields, settings, items). NEVER for dates, status values, foreign keys, booleans, or anything filtered/sorted/aggregated.
### 1. Foundation
| Table | Key Columns | Notes |
|-------|-------------|-------|
| `users` | id (ULID), name, email, password, timezone, locale, avatar, deleted_at | Platform-wide, unique per email. |
| `organisations` | id (ULID), name, slug, billing_status, settings (JSON: display prefs only), deleted_at | hasMany events, crowd_types. |
| `organisation_user` | id (int AI), user_id, organisation_id, role | Pivot. Integer PK. |
| `user_invitations` | id (ULID), email, invited_by_user_id, organisation_id, event_id (nullable), role, token (ULID unique), status, expires_at | INDEX: (token), (email, status). |
| `events` | id (ULID), organisation_id, name, slug, start_date, end_date, timezone, status (enum), deleted_at | INDEX: (organisation_id, status). |
| `event_user_roles` | id (int AI), user_id, event_id, role | Pivot. Integer PK. |
### 2. Locations
| Table | Key Columns | Notes |
|-------|-------------|-------|
| `locations` | id (ULID), event_id, name, address, lat, lng, description, access_instructions | INDEX: (event_id). |
### 3. Festival Sections, Time Slots & Shifts
| Table | Key Columns | Notes |
|-------|-------------|-------|
| `festival_sections` | id (ULID), event_id, name, sort_order, deleted_at | INDEX: (event_id, sort_order). |
| `time_slots` | id (ULID), event_id, name, person_type (CREW/VOLUNTEER/PRESS/...), date, start_time, end_time | INDEX: (event_id, person_type, date). |
| `shifts` | id (ULID), festival_section_id, time_slot_id, location_id, slots_total, slots_open_for_claiming, status, deleted_at | INDEX: (festival_section_id, time_slot_id). |
| `shift_assignments` | id (ULID), shift_id, person_id, time_slot_id (denormalized), status (pending_approval/approved/rejected/cancelled/completed), auto_approved, deleted_at | UNIQUE(person_id, time_slot_id). |
| `volunteer_availabilities` | id (ULID), person_id, time_slot_id, submitted_at | UNIQUE(person_id, time_slot_id). |
| `shift_waitlist` | id (ULID), shift_id, person_id, position, added_at | UNIQUE(shift_id, person_id). |
| `shift_swap_requests` | id (ULID), from_assignment_id, to_person_id, status, auto_approved | |
| `shift_absences` | id (ULID), shift_assignment_id, person_id, reason, status | |
### 4. Volunteer Profile & History
| Table | Key Columns | Notes |
|-------|-------------|-------|
| `volunteer_profiles` | id (ULID), user_id (unique), bio, tshirt_size, first_aid, driving_licence, reliability_score (0.00-5.00) | Platform-wide, 1:1 with users. |
| `volunteer_festival_history` | id (ULID), user_id, event_id, hours_planned, hours_completed, no_show_count, coordinator_rating, would_reinvite | UNIQUE(user_id, event_id). Never visible to volunteer. |
| `post_festival_evaluations` | id (ULID), event_id, person_id, overall_rating, would_return, feedback_text | |
| `festival_retrospectives` | id (ULID), event_id (unique), KPI columns, top_feedback (JSON) | |
### 5. Crowd Types, Persons & Crowd Lists
| Table | Key Columns | Notes |
|-------|-------------|-------|
| `crowd_types` | id (ULID), organisation_id, name, system_type (CREW/GUEST/ARTIST/VOLUNTEER/PRESS/PARTNER/SUPPLIER), color, icon | Org-level config. |
| `persons` | id (ULID), user_id (nullable), event_id, crowd_type_id, company_id (nullable), name, email, phone, status, is_blacklisted, custom_fields (JSON), deleted_at | user_id nullable for externals. UNIQUE(event_id, user_id) WHERE user_id IS NOT NULL. |
| `companies` | id (ULID), organisation_id, name, type, contact_*, deleted_at | Shared across events within org. |
| `crowd_lists` | id (ULID), event_id, crowd_type_id, name, type (internal/external), auto_approve, max_persons | |
| `crowd_list_persons` | id (int AI), crowd_list_id, person_id | Pivot. |
### 6. Accreditation Engine
| Table | Key Columns | Notes |
|-------|-------------|-------|
| `accreditation_categories` | id (ULID), organisation_id, name, sort_order, icon | Org-level. |
| `accreditation_items` | id (ULID), accreditation_category_id, name, is_date_dependent, barcode_type, cost_price | Org-level items. |
| `event_accreditation_items` | id (ULID), event_id, accreditation_item_id, max_quantity_per_person, is_active | Activates item per event. UNIQUE(event_id, accreditation_item_id). |
| `accreditation_assignments` | id (ULID), person_id, accreditation_item_id, event_id, date, quantity, is_handed_out | |
| `access_zones` | id (ULID), event_id, name, zone_code (unique per event) | |
| `access_zone_days` | id (int AI), access_zone_id, day_date | UNIQUE(access_zone_id, day_date). |
| `person_access_zones` | id (int AI), person_id, access_zone_id, valid_from, valid_to | |
### 7. Artists & Advancing
| Table | Key Columns | Notes |
|-------|-------------|-------|
| `artists` | id (ULID), event_id, name, booking_status (concept/requested/option/confirmed/contracted/cancelled), portal_token (ULID unique), deleted_at | |
| `performances` | id (ULID), artist_id, stage_id, date, start_time, end_time, check_in_status | INDEX: (stage_id, date, start_time). |
| `stages` | id (ULID), event_id, name, color, capacity | |
| `stage_days` | id (int AI), stage_id, day_date | UNIQUE(stage_id, day_date). |
| `advance_sections` | id (ULID), artist_id, name, type, is_open, sort_order | |
| `advance_submissions` | id (ULID), advance_section_id, data (JSON), status | |
| `artist_contacts` | id (ULID), artist_id, name, email, role | |
| `artist_riders` | id (ULID), artist_id, category (technical/hospitality), items (JSON) | |
### 8. Communication & Briefings
| Table | Key Columns | Notes |
|-------|-------------|-------|
| `briefing_templates` | id (ULID), event_id, name, type, blocks (JSON) | |
| `briefings` | id (ULID), event_id, briefing_template_id, name, target_crowd_types (JSON), status | |
| `briefing_sends` | id (ULID), briefing_id, person_id, status (queued/sent/opened/downloaded) | NO soft delete. |
| `communication_campaigns` | id (ULID), event_id, type (email/sms/whatsapp), status | |
| `messages` | id (ULID), event_id, sender_user_id, recipient_person_id, urgency (normal/urgent/emergency) | |
| `broadcast_messages` | id (ULID), event_id, sender_user_id, body, urgency | |
### 9. Forms, Check-In & Operational
| Table | Key Columns | Notes |
|-------|-------------|-------|
| `public_forms` | id (ULID), event_id, crowd_type_id, fields (JSON), conditional_logic (JSON), iframe_token | |
| `form_submissions` | id (ULID), public_form_id, person_id, data (JSON) | |
| `check_ins` | id (ULID), event_id, person_id, scanned_by_user_id, scanned_at | NO soft delete. Immutable audit record. |
| `scanners` | id (ULID), event_id, name, type, pairing_code | |
| `inventory_items` | id (ULID), event_id, name, item_code, assigned_to_person_id | |
| `production_requests` | id (ULID), event_id, company_id, title, status, token (ULID unique) | |
| `material_requests` | id (ULID), production_request_id, category, name, quantity, status | |
---
## Model Relationships
**User**
- belongsToMany Organisations (via `organisation_user`)
- belongsToMany Events (via `event_user_roles`)
**Organisation**
- hasMany Events
- hasMany CrowdTypes
- hasMany AccreditationCategories
- hasMany Companies
- belongsToMany Users (via `organisation_user`)
**Event**
- belongsTo Organisation
- hasMany FestivalSections
- hasMany TimeSlots
- hasMany Persons
- hasMany Artists
- hasMany Briefings
- hasMany Locations
- hasMany AccessZones
- hasMany PublicForms
**FestivalSection**
- belongsTo Event
- hasMany Shifts
**TimeSlot**
- belongsTo Event
- hasMany Shifts
- hasMany ShiftAssignments (denormalized)
**Shift**
- belongsTo FestivalSection
- belongsTo TimeSlot
- belongsTo Location (nullable)
- hasMany ShiftAssignments
**Person**
- belongsTo Event
- belongsTo CrowdType
- belongsTo User (nullable)
- belongsTo Company (nullable)
- hasMany ShiftAssignments
- hasMany AccreditationAssignments
- hasMany CheckIns
**Artist**
- belongsTo Event
- hasMany Performances
- hasMany AdvanceSections
- hasMany ArtistContacts
---
## Security & CORS
Two frontend origins in `config/cors.php` (via env):
| App | Dev URL | Env Variable |
|-----|---------|--------------|
| App | `http://localhost:5174` | `FRONTEND_APP_URL` |
| Portal | `http://localhost:5175` | `FRONTEND_PORTAL_URL` |
Production (registered domain **crewli.app**): API `https://api.crewli.app` (`APP_URL`); SPAs `https://crewli.app`, `https://portal.crewli.app` via the same env keys. Frontends use `VITE_API_URL=https://api.crewli.app/api/v1`. `SANCTUM_STATEFUL_DOMAINS` = comma-separated SPA hostnames only (e.g. `crewli.app,portal.crewli.app`). **`crewli.nl`** is reserved for a future marketing site only — not used for this application stack.
---
## Real-time Events (WebSocket)
Via Laravel Echo + Pusher/Soketi:
- `PersonCheckedIn`
- `ShiftFillRateChanged`
- `ArtistCheckInStatusChanged`
- `AdvanceSectionSubmitted`
- `AccreditationItemHandedOut`
- `BriefingSendQueued`
---
*Source: Crewli Design Document v1.3, March 2026*

View File

@@ -1,222 +0,0 @@
# Crewli - Cursor AI Instructions
> Multi-tenant SaaS platform for event- and festival management.
> Design Document: `/resources/design/design-document.md`
> Dev Guide: `/resources/design/dev-guide.md`
> Start Guide: `/resources/design/start-guide.md`
## Project Overview
**Name**: Crewli
**Type**: Multi-tenant SaaS platform (API-first architecture)
**Status**: Development
### Description
Crewli is a multi-tenant SaaS platform for professional event and festival management. It supports the full operational cycle: artist booking and advancing, staff planning and volunteer management, accreditation, briefings, and real-time show-day operations (Mission Control). Built for a professional volunteer organisation, with SaaS expansion potential.
## Quick Reference
| Component | Technology | Location | Port |
|-----------|------------|----------|------|
| API | Laravel 12 + Sanctum + Spatie Permission | `api/` | 8000 |
| Organizer + Admin App (Main) | Vue 3 + Vuexy (full) | `apps/app/` | 5174 |
| Portal (External) | Vue 3 + Vuexy (stripped) | `apps/portal/` | 5175 |
| Database | MySQL 8 | Docker | 3306 |
| Cache / Queues | Redis | Docker | 6379 |
| Mail | Mailpit | Docker | 8025 |
## Documentation Structure
```
.cursor/
├── instructions.md # This file - overview and quick start
├── ARCHITECTURE.md # System architecture, schema, API routes
└── rules/
├── 001_workspace.mdc # Project structure, conventions, multi-tenancy
├── 100_laravel.mdc # Laravel API patterns and templates
├── 101_vue.mdc # Vue + Vuexy patterns and templates
└── 200_testing.mdc # Testing strategies and templates
```
---
## Core Modules
### Phase 1 - Foundation
- [ ] Multi-tenant architecture + Auth (Sanctum + Spatie)
- [ ] Users, Roles & Permissions (three-level model)
- [ ] Organisations CRUD + User Invitations
- [ ] Events CRUD with lifecycle status
- [ ] Crowd Types (org-level configuration)
- [ ] Festival Sections + Time Slots + Shifts
- [ ] Persons & Crowd Lists
- [ ] Accreditation Engine (categories, items, access zones)
### Phase 2 - Core Operations
- [ ] Briefings & Communication (template builder, queue-based sending)
- [ ] Staff & Crew Management (crowd pool, accreditation matrix)
- [ ] Volunteer Management + Portal (registration, shift claiming, approval flow)
- [ ] Form Builder (drag-drop, conditional logic, iframe embed)
- [ ] Artist Advancing + Portal (token-based access)
- [ ] Timetable & Stage management
- [ ] Show Day Mode
- [ ] Shift Swap & Waitlist
- [ ] Volunteer Profile + Festival Passport
- [ ] Communication Hub (email/SMS/WhatsApp via Zender, urgency levels)
### Phase 3 - Advancing & Show Day
- [ ] Guests & Hospitality
- [ ] Suppliers & Production (production requests, supplier portal)
- [ ] Mission Control (real-time check-in, artist handling, scanner management)
- [ ] Communication Campaigns (email + SMS batch)
- [ ] Allocation Sheet PDF (Browsershot)
- [ ] Scan infrastructure (hardware pairing)
- [ ] Reporting & Insights
- [ ] No-show automation
- [ ] Post-festival evaluation + retrospective
### Phase 4 - Differentiators
- [ ] Real-time WebSocket notifications (Echo + Pusher/Soketi)
- [ ] Cross-event crew pool with reliability score
- [ ] Global search (cmd+K)
- [ ] Crew PWA
- [ ] Public REST API + webhook system
- [ ] CO2/sustainability reporting
---
## Module Development Order (per module)
Always follow this sequence:
1. Migration(s) - ULID PKs, composite indexes, constrained FKs
2. Eloquent Model - HasUlids, relations, scopes, OrganisationScope
3. Factory - realistic Dutch test data
4. Policy - authorization via Spatie roles
5. Form Request(s) - Store + Update validation
6. API Resource - computed fields, `whenLoaded()`, permission-dependent fields
7. Resource Controller - index/show/store/update/destroy
8. Routes in `api.php`
9. PHPUnit Feature Test - happy path (200/201) + unauthenticated (401) + wrong organisation (403) + validation (422)
10. Vue Composable (`useModuleName.ts`) - TanStack Query
11. Pinia Store (if cross-component state needed)
12. Vue Page Component
13. Vue Router entry
---
## Getting Started Prompts
### 1. Phase 1 Foundation (Backend)
```
Read CLAUDE.md. Then generate Phase 1 Foundation:
1. Migrations: Update users (add timezone, locale, deleted_at). Create organisations (ULID, name, slug, billing_status, settings JSON, deleted_at), organisation_user pivot, user_invitations, events (ULID, organisation_id, name, slug, start_date, end_date, timezone, status enum, deleted_at), event_user_roles pivot.
2. Models: User (update), Organisation, UserInvitation, Event. All with HasUlids, SoftDeletes where applicable, OrganisationScope on Event.
3. Spatie Permission: RoleSeeder with roles: super_admin, org_admin, org_member, event_manager, staff_coordinator, volunteer_coordinator.
4. Auth: LoginController, LogoutController, MeController (returns user + organisations + active event roles).
5. Organisations: Controller, Policy, Request, Resource.
6. Events: Controller nested under organisations, Policy, Request, Resource.
7. Feature tests per step. Run php artisan test after each step.
```
### 2. Phase 1 Foundation (Frontend)
```
Build auth flow in apps/app/:
1. stores/useAuthStore.ts - token storage, isAuthenticated, me() loading
2. pages/login.vue - use Vuexy login layout
3. Router guard - redirect to login if not authenticated
4. Replace Vuexy demo navigation with Crewli structure
5. CASL permissions: connect to Spatie roles from auth/me response
```
### 3. Module Generation (example: Shifts)
```
Build the Shifts module following CLAUDE.md module order:
- Migration with ULID PK, festival_section_id, time_slot_id, location_id, slots_total, slots_open_for_claiming, status. Composite indexes.
- Model with HasUlids, SoftDeletes, relations, computed accessors (slots_filled, fill_rate).
- shift_assignments with denormalized time_slot_id, status machine (pending_approval > approved/rejected/cancelled/completed), UNIQUE(person_id, time_slot_id).
- Configurable auto-approve per shift.
- Queued notification jobs using ZenderService for WhatsApp.
- Feature tests covering 200/401/403/422.
```
---
## Common Tasks
### Add a New API Endpoint
1. Create/update Controller in `app/Http/Controllers/Api/V1/`
2. Create Form Request in `app/Http/Requests/Api/V1/`
3. Create/update API Resource in `app/Http/Resources/Api/V1/`
4. Add route in `routes/api.php`
5. Create Service class if complex business logic needed
6. Write PHPUnit Feature Test (200/401/403/422)
### Add a New Vue Page
1. Create page component in `src/pages/`
2. Route added automatically by file-based routing (or add to router)
3. Add navigation item in `src/navigation/`
4. Create composable for API calls in `src/composables/`
5. Use Vuexy/Vuetify components for UI
### Add a New Database Table
1. Create migration with ULID PK, composite indexes
2. Create model with HasUlids, relations, OrganisationScope (if applicable)
3. Create factory with realistic Dutch test data
4. Create Policy, Form Request, Resource, Controller
5. Register routes in `api.php`
6. Write PHPUnit Feature Test
---
## Code Generation Preferences
When generating code, always:
- Use PHP 8.2+ features (typed properties, enums, match, readonly)
- Use `declare(strict_types=1);`
- Use ULID primary keys via HasUlids trait
- Use Spatie laravel-permission for roles (never hardcode role strings)
- Scope all queries on `organisation_id` via Global Scope
- Use `<script setup lang="ts">` for Vue components
- Use TanStack Query for all API calls
- Use VeeValidate + Zod for form validation
- Use Vuetify/Vuexy components for UI (never custom CSS if Vuetify class exists)
---
## Environment Setup
### Docker Services
```bash
make services # Start MySQL, Redis, Mailpit
make services-stop # Stop services
```
### Development Servers
```bash
make api # Laravel on :8000
make app # Organizer + Admin SPA on :5174
make portal # Portal SPA on :5175
```
### Database
```bash
make migrate # Run migrations
make fresh # Fresh migrate + seed
```
### Testing
```bash
cd api && php artisan test # All tests
cd api && php artisan test --filter=ShiftTest # Specific test
cd api && php artisan test --coverage # With coverage
```

View File

@@ -124,13 +124,12 @@ make db-shell
| Resource | Contents |
|----------|----------|
| [resources/design/](resources/design/) | **Canonical product specs** in Markdown. Referenced by `.cursor` and `CLAUDE.md` as source of truth for features and data model: `design-document.md`, `dev-guide.md`, `start-guide.md`. |
| [.cursor/ARCHITECTURE.md](.cursor/ARCHITECTURE.md) | System diagram, apps, multi-tenancy, roles, event lifecycle, API route map, core schema overview (summarises `resources/design` when present) |
| [.cursor/instructions.md](.cursor/instructions.md) | Quick reference, phased roadmap, module build order |
| [.cursor/rules/](.cursor/rules/) | Workspace, Laravel, Vue, testing conventions |
| [docs/SETUP.md](docs/SETUP.md) | Environment and local setup |
| [docs/API.md](docs/API.md) | API notes (if maintained) |
| [docs/SCHEMA.md](docs/SCHEMA.md) | Schema notes (if maintained) |
| [CLAUDE.md](CLAUDE.md) | Project conventions, vibe-coding principles, Vuexy-first decision tree (auto-loaded by Claude Code). |
| [.cursor/rules/](.cursor/rules/) | Workspace, Laravel, Vue, testing conventions. |
| [dev-docs/SETUP.md](dev-docs/SETUP.md) | Environment and local setup. |
| [dev-docs/SCHEMA.md](dev-docs/SCHEMA.md) | Database schema (kept in sync with migrations). |
| [dev-docs/API.md](dev-docs/API.md) | API contract. |
| [dev-docs/design-document.md](dev-docs/design-document.md) | Product specification. |
---

View File

@@ -4,7 +4,7 @@
> **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
> `/dev-docs/SETUP.md`. Tijdens de sprint is het de enkele bron van waarheid voor wát
> we aan het doen zijn en waaróm.
---

View File

@@ -111,11 +111,8 @@ start-guide.md
SETUP.md
SECURITY_AUDIT.md
SCHEMA.md
MASTER_PROMPT_CURSOR.md
MASTER_PROMPT_CC.md
form-builder-migration-playbook.md
form-builder-getting-started.md
dev-guide.md
design-document.md
COPY_CATALOGUE.md
BACKLOG.md
@@ -169,4 +166,4 @@ subprocess-spawn (bij volgende tool call).
## Gerelateerd
- `CLAUDE.md` (repo root) — instructies voor Claude Code, niet Claude Desktop
- `dev-docs/dev-guide.md` — ontwikkelworkflow
- `dev-docs/SETUP.md` — ontwikkelworkflow

View File

@@ -1,294 +0,0 @@
# Crewli — Claude Code Master Prompt
# Plak dit BOVEN elke task. Vervang [TASK] onderaan.
## MANDATORY PREAMBLE — READ BEFORE DOING ANYTHING
Read `/CLAUDE.md` and `/dev-docs/SCHEMA.md` in full before starting. These are
your single source of truth. Do not deviate from them. Do not make assumptions
about the database schema — always verify against SCHEMA.md. Also read
`/dev-docs/API.md` for the existing API contract.
If the task involves a module that already has existing code, read that code
first. Understand the current state before making changes.
## PROJECT CONTEXT
Crewli is a multi-tenant SaaS platform for event and festival management.
- **Backend:** Laravel 12 REST API (no Blade views, no Inertia), Sanctum auth,
Spatie Permission, MySQL 8, Redis
- **Frontend:** Two standalone Vue 3 + TypeScript SPAs on Vuexy 9.5 /
Vuetify 3.10 — `apps/app/` (port 5174), `apps/portal/` (port 5175)
- **State:** Pinia + TanStack Vue Query
- **Forms:** VeeValidate + Zod
- **API base path:** `/api/v1/`
- **Testing:** PHPUnit (backend), Vitest (frontend)
### Repository structure
```
crewli/
├── api/ # Laravel 12 backend
│ ├── app/
│ │ ├── Http/
│ │ │ ├── Controllers/Api/V1/
│ │ │ ├── Middleware/
│ │ │ └── Requests/ # Form Requests per endpoint
│ │ ├── Models/
│ │ ├── Policies/
│ │ ├── Services/ # Business logic (NOT in controllers)
│ │ ├── Enums/ # PHP Enums for all status/type fields
│ │ ├── Events/ + Listeners/
│ │ └── Jobs/ # Queued jobs (briefings, notifications)
│ ├── database/
│ │ ├── migrations/
│ │ ├── factories/
│ │ └── seeders/
│ └── tests/Feature/Api/V1/
├── apps/
│ ├── app/ # Organizer + Platform Admin SPA (main app)
│ │ └── src/
│ │ ├── lib/axios.ts # THE ONLY axios instance
│ │ ├── composables/api/ # TanStack Query composables
│ │ ├── stores/ # Pinia stores
│ │ ├── types/ # TypeScript interfaces
│ │ └── pages/
│ └── portal/ # Dual-mode portal
├── dev-docs/ # Developer documentation (source of truth)
│ ├── SCHEMA.md
│ ├── API.md
│ ├── BACKLOG.md
│ ├── design-document.md
│ ├── dev-guide.md
│ └── start-guide.md
├── docs/ # VitePress end-user documentation (Dutch)
├── CLAUDE.md # This file's companion — workspace rules
└── .cursorrules
```
### Key architectural decisions (final, non-negotiable)
- **ULID** primary keys on all business tables via `HasUlids` — NEVER UUID v4
- **Integer auto-increment** on pure pivot tables only
- **JSON columns** exclusively for opaque config (settings, blocks) — never
for queryable data
- **Multi-tenancy** via `OrganisationScope` Eloquent Global Scope on all
event-related models
- **Soft delete** per table type as documented in SCHEMA.md — immutable audit
records (check_ins, form_submissions, briefing_sends) do NOT get soft delete
- **Portal dual-mode:** Sanctum session for volunteers/crew (persistent
identity), `portal.token` middleware for artists/suppliers/press
(event-specific, no account)
---
## ZERO-COMPROMISE RULES — VIOLATIONS ARE BLOCKERS
These rules are absolute. No workarounds. No "we'll fix it later". No partial
implementations. If something cannot be done properly, STOP and report it.
### Architecture & design
1. **ARCHITECTURE FIRST.** If the task requires a new pattern, data structure,
or integration that isn't documented in CLAUDE.md or SCHEMA.md — stop and
ask. Do not invent architecture. Write an Architecture Decision Record (ADR)
in `/dev-docs/decisions/` if a significant design choice is needed.
2. **DELETE OVER ADAPT.** If you find duplicate logic, conflicting patterns, or
legacy code that does the same thing differently: delete the worse version.
Do not build alongside it. Do not "wrap" it. If two implementations exist,
one must die. Consolidate to one source of truth.
3. **STRICT LAYERING.** Business logic belongs in `app/Services/`, NEVER in
controllers. Controllers handle HTTP concerns only: receive request, call
service, return resource. Data access patterns go through Eloquent models
with proper scopes. No "quick fixes" in the wrong layer.
- Controller → receives FormRequest, calls Service, returns API Resource
- Service → contains business logic, validation beyond FormRequest,
orchestration
- Model → relationships, scopes, accessors, mutators
- Job → async work dispatched by Service
4. **CONTRACT-FIRST.** Before implementing any module:
- Define the PHP Enum(s) in `app/Enums/` for all status/type fields
- Define the API Resource (response shape) first
- Define the Form Request (input validation) first
- Then implement the Service and Controller
Types and contracts define behaviour. Implementation follows.
5. **CONSISTENCY OVER CLEVERNESS.** Use the same pattern for every similar
problem. If existing modules use a specific approach for pagination, error
handling, or resource loading — use that exact same approach. Never
introduce a "better" alternative pattern without refactoring ALL existing
code to match. One pattern per problem type, across the entire codebase.
6. **SINGLE SOURCE OF TRUTH.** Every piece of information exists in exactly
one place:
- Enum values → PHP Enum class (not string literals)
- Validation rules → Form Request (not duplicated in frontend)
- Response shape → API Resource (not ad-hoc arrays)
- Config values → .env / config files (not hardcoded)
- Schema definition → SCHEMA.md (not guessed)
### Code quality
7. **NO TODO / FIXME / HACK.** Zero tolerance. If you cannot complete
something, stop and report it. Do not leave stubs, placeholders, or
"implement later" comments. Every file you touch must be production-ready.
8. **NO UNTYPED CODE.** PHP: `declare(strict_types=1)` on every file. Return
types on all methods. Typed properties. Use PHP Enums (not string literals)
for all status, type, and role fields. No `mixed` where a concrete type
or union type would work.
9. **NO GENERIC NAMES.** Names must be specific and self-documenting:
-`DataService`, `Helper`, `Manager`, `handleData()`, `processItem()`
-`ShiftAssignmentService`, `VolunteerAvailabilityChecker`,
`resolveShiftConflict()`, `calculateFillRate()`
10. **NO SILENT ERROR HANDLING.** No empty catch blocks. No `catch { return
null; }`. Every error must be: logged (via `Log::error()` with context),
or rethrown, or handled with a proper API error response. Use
`report($e)` for unexpected errors.
### Testing
11. **TESTS ARE DESIGN, NOT AFTERTHOUGHT.** Tests define expected behaviour.
Every controller needs feature tests covering:
- 200/201 (happy path for each action)
- 401 (unauthenticated access)
- 403 (wrong organisation — cross-tenant access attempt)
- 403 (insufficient role/permission)
- 422 (validation errors with specific field assertions)
- Edge cases specific to the module (e.g., shift conflict, capacity full)
Run `php artisan test` after EVERY module. Fix failures before proceeding.
Never skip, comment out, or mark tests as incomplete.
12. **FACTORIES ARE REALISTIC.** Use realistic Dutch test data (names,
addresses, company names) that reflects actual usage. Factories must create
valid, complete records — no missing required fields or placeholder values.
### Data & persistence
13. **DATA MODEL IS SACRED.** Never deviate from SCHEMA.md. Every column,
every constraint, every index documented there must be in the migration.
If SCHEMA.md is unclear, ask — do not guess.
14. **EVERY MIGRATION HAS down().** The `down()` method must cleanly reverse
the `up()`. Drop tables, remove columns, restore previous state. No
`down()` methods that throw or do nothing.
15. **INDEXES ARE MANDATORY.** Every foreign key column, every column used in
WHERE/ORDER BY clauses, every unique constraint from SCHEMA.md must have
an explicit index. Verify composite indexes match the documented patterns.
### Security & multi-tenancy
16. **NO UNSCOPED QUERIES.** Every query on event-related models must be
scoped to `organisation_id` via `OrganisationScope`. Mental test: can
User A from Org 1 ever see, modify, or infer the existence of data from
Org 2? If yes → security bug → fix immediately.
17. **POLICIES ARE COMPLETE.** Every policy method checks:
- Does the user belong to the correct organisation?
- Does the user have the required Spatie role/permission for this action?
- No `return true` placeholders. No missing methods.
18. **FORM REQUESTS ARE COMPLETE.** Every store/update has a Form Request
with full validation matching SCHEMA.md constraints: required, nullable,
max length, enum values (referencing the PHP Enum), exists rules with
proper scoping (e.g., `exists:events,id` scoped to organisation).
### API responses
19. **NO BARE MODEL RETURNS.** Every API response goes through an API
Resource. Never `return $model`, `$model->toArray()`, or raw arrays.
Resources define the public contract. Computed fields (fill_rate,
status_label, slot counts) belong in the Resource.
20. **CONSISTENT RESPONSE STRUCTURE.** Follow the existing pattern:
`{ data: {...}, meta: {...} }` for paginated lists. Consistent error
format with `{ message: "...", errors: {...} }` for validation failures.
### Resilience & operations
21. **IDEMPOTENT OPERATIONS.** Every queued job must be safe to retry.
Check-before-act: verify state hasn't changed. Use database transactions
for multi-step mutations. Queued notifications and external API calls
(Zender SMS/WhatsApp) must handle duplicates gracefully.
22. **OBSERVABILITY.** Log significant business events using
`spatie/laravel-activitylog` (already installed). Every create, update,
delete, and status change on business entities must be logged with:
- `causedBy($user)` — who did it
- `performedOn($model)` — what was affected
- `withProperties([...])` — relevant context (old values, new values)
23. **API VERSIONING.** All routes under `/api/v1/`. Controllers in
`App\Http\Controllers\Api\V1\`. Never introduce breaking changes to
existing endpoints — add new fields, don't rename or remove.
### Process
24. **MODULE GENERATION ORDER.** Always follow this sequence. No skipping.
1. PHP Enum(s) for status/type fields (`app/Enums/`)
2. Migration(s) — verify against SCHEMA.md
3. Eloquent Model with: HasUlids, HasFactory, SoftDeletes (if documented),
OrganisationScope (if event-related), relationships, scopes, accessors
4. Factory with realistic Dutch test data
5. Service class for business logic (`app/Services/`)
6. Policy for authorisation
7. Form Request(s) for validation
8. API Resource for response transformation
9. Resource Controller (thin — delegates to Service)
10. Routes in `api/routes/api.php`
11. Feature tests — run them, fix failures
12. Activity log integration in Service methods
13. Update `/dev-docs/API.md` with new routes
25. **GIT.** Auto-commit after each completed module:
`feat(module-name): add backend scaffold with tests`
---
## VERIFICATION CHECKLIST (run before reporting "done")
```bash
# All tests pass
php artisan test
# Database rebuilds cleanly
php artisan migrate:fresh --seed
# New routes are visible
php artisan route:list --path=api/v1
# No forbidden patterns
grep -rn "TODO\|FIXME\|HACK\|dd(\|dump(\|var_dump\|Model::all()" \
api/app/ api/tests/ --include="*.php"
# No UUID v4 in migrations
grep -rn "uuid(" api/database/migrations/ --include="*.php"
# Static analysis (if configured)
./vendor/bin/phpstan analyse
```
Manual verification:
- [ ] Every new model has: HasUlids, HasFactory, correct SoftDeletes, complete
$fillable, all relationships from SCHEMA.md
- [ ] Every new model with event data has OrganisationScope
- [ ] Every controller action is covered by a Policy method
- [ ] Every Service method logs activity via spatie/laravel-activitylog
- [ ] Every Form Request references PHP Enums (not string literals) for
enum validation
- [ ] Business logic is in Service classes, not in Controllers
- [ ] API Resource includes computed fields where applicable
- [ ] No N+1: index actions use `with()` for all accessed relationships
- [ ] `/dev-docs/API.md` updated with new routes
---
## TASK
[INSERT SPECIFIC TASK HERE]

View File

@@ -1,176 +0,0 @@
# Crewli — Cursor Master Prompt
# Plak dit BOVEN elke task. Vervang [TASK] onderaan.
## MANDATORY PREAMBLE
Read `@CLAUDE.md` and `@dev-docs/SCHEMA.md` before starting. Use `@workspace`
for full codebase context. These documents are your source of truth.
If modifying existing code, read the current implementation first. Understand
the patterns already in use before writing anything new.
## PROJECT CONTEXT
Crewli — multi-tenant SaaS for event/festival management.
- **Frontend:** Vue 3 + TypeScript + Vuexy 9.5 (Vuetify 3.10) + Pinia +
TanStack Vue Query + VeeValidate + Zod
- **Two SPAs:**
- `apps/app/` — Organizer + Platform Admin main app (port 5174)
- `apps/portal/` — Dual-mode portal (port 5175)
- **API base:** `VITE_API_URL` from `.env.local` (default `http://localhost:8000`)
- **Axios instance:** `src/lib/axios.ts` — this is the ONLY axios instance.
Never create another. Never import axios directly in components.
- **Backend Enums:** PHP Enums in `api/app/Enums/` define all valid status/type
values. TypeScript equivalents must mirror these exactly in `src/types/`.
### Frontend file structure (per app)
```
src/
├── lib/axios.ts # Singleton axios instance (DO NOT DUPLICATE)
├── types/ # TypeScript interfaces per module
│ └── [module].ts
├── composables/api/ # TanStack Query composables per module
│ └── use[Module].ts
├── stores/ # Pinia stores (cross-component state only)
│ └── use[Module]Store.ts
├── pages/ # Page components
│ └── [module]/
│ ├── index.vue # List view
│ └── [id].vue # Detail view (or side panel)
└── router/ # Vue Router config
```
---
## ZERO-COMPROMISE RULES — VIOLATIONS ARE BLOCKERS
### TypeScript & typing
1. **NO `any` TYPES. EVER.** Every variable, prop, emit, return type, ref,
reactive, computed, and API response is fully typed. If you don't know the
type, check the backend API Resource in `api/app/Http/Resources/` — that
defines the contract. Create matching interfaces in `src/types/[module].ts`.
2. **TYPES FIRST.** Before writing any composable or component for a new
module, create the TypeScript interfaces in `src/types/[module].ts`. These
mirror the backend API Resource shape. Include:
- Entity interface (e.g., `Shift`, `Person`, `Event`)
- Create/Update DTOs (matching backend Form Request fields)
- Enum-like union types or const objects matching backend PHP Enums
- Paginated response wrapper if applicable
3. **ENUMS AS CONST OBJECTS.** Mirror backend PHP Enums as TypeScript const
objects with `as const`, not as loose string unions scattered across files:
```typescript
// src/types/shift.ts
export const ShiftAssignmentStatus = {
PENDING_APPROVAL: 'pending_approval',
APPROVED: 'approved',
REJECTED: 'rejected',
CANCELLED: 'cancelled',
COMPLETED: 'completed',
} as const
export type ShiftAssignmentStatus = typeof ShiftAssignmentStatus[keyof typeof ShiftAssignmentStatus]
```
### Architecture & patterns
4. **NO DIRECT AXIOS IN COMPONENTS.** All API calls go through composables in
`composables/api/use[Module].ts` using TanStack Query (`useQuery` /
`useMutation`). Components never import axios or `src/lib/axios.ts`.
The composable is the only layer that knows about HTTP.
5. **NO OPTIONS API.** Always `<script setup lang="ts">`. Props via
`defineProps<{...}>()`, emits via `defineEmits<{...}>()`, expose via
`defineExpose()`. No `export default { ... }`.
6. **NO PROP DRILLING.** If state needs to cross more than one component
boundary, use a Pinia store (`src/stores/use[Module]Store.ts`). Access
reactive state via `storeToRefs()`. Mutations only through store actions.
7. **DELETE OVER ADAPT.** If you find duplicate composables, overlapping
stores, or conflicting patterns: delete the worse version. Do not build
alongside existing code that does the same thing. One implementation per
concern.
8. **CONSISTENCY OVER CLEVERNESS.** Look at how existing modules handle the
same pattern (table views, form dialogs, detail panels, error handling).
Use the exact same approach. Never introduce a "better" alternative without
refactoring ALL existing modules to match.
9. **SINGLE SOURCE OF TRUTH.** Don't duplicate:
- Validation logic → Zod schema mirrors backend Form Request. One schema
per form, referenced by VeeValidate.
- Enum values → const objects in `src/types/`, matching backend Enums.
Never use raw string literals like `'approved'` in templates or logic.
- API URLs → constructed in composable from module prefix, never
hardcoded in components.
### UI & UX
10. **NO CUSTOM CSS WHERE VUETIFY HAS A SOLUTION.** Before writing any CSS:
check if a Vuetify utility class, component prop, or slot achieves the
result. Custom CSS is a last resort, must be `<style scoped>`, and must
have a comment explaining why Vuetify couldn't handle it.
11. **EVERY PAGE HAS THREE STATES:**
- **Loading:** Vuetify skeleton loader or progress indicator
- **Error:** User-facing message with retry action (`v-alert` with retry
button). Show what went wrong in user terms, not technical jargon.
- **Empty:** Helpful message explaining why the list is empty and what
action to take (not a blank white screen).
Never show only the happy path.
12. **MOBILE-FIRST.** Every component must be usable at 375px width. Use
Vuetify's responsive props (`cols`, `sm`, `md`, `lg` on `v-col`). Never
use fixed pixel widths for layout. Tables on mobile → card views or
horizontal scroll with visual indicator.
13. **FORMS USE VEEVALIDATE + ZOD.** Define the Zod schema matching the
backend Form Request rules. No inline validation logic in templates. Use
`useForm()` and `useField()` from VeeValidate with the Zod resolver.
### Code quality
14. **NO TODO / FIXME / HACK.** Complete the implementation or report the
blocker. No stubs, no "implement later", no placeholder components.
15. **NO ORPHANED IMPORTS OR UNUSED VARIABLES.** Run `npx tsc --noEmit`
after every change. Zero errors, zero warnings. Dead code is deleted
immediately, not commented out.
16. **NO GENERIC NAMES.** Component and composable names must be specific:
- ❌ `DataTable.vue`, `useApi.ts`, `helpers.ts`, `utils.ts`
- ✅ `ShiftAssignmentTable.vue`, `useShifts.ts`,
`formatShiftTimeRange.ts`
### Routing & auth
17. **ROUTER GUARDS.** Protected routes check auth state via navigation
guard. Portal routes validate token. No unguarded routes to
authenticated content. Redirect to login on 401.
### Process
18. **COMPONENT CREATION ORDER.** For every new page/feature:
1. TypeScript types in `src/types/[module].ts`
2. API composable in `src/composables/api/use[Module].ts`
3. Pinia store in `src/stores/use[Module]Store.ts` (if cross-component)
4. Vue page component in `src/pages/[module]/`
5. Router entry in `src/router/`
19. **VERIFY BEFORE DONE:**
- `npx tsc --noEmit` — zero errors
- No `any` types anywhere (search: `grep -rn ": any\|as any" src/`)
- No TODO/FIXME/HACK
- All three states (loading, error, empty) implemented
- Mobile responsive at 375px
- Consistent with existing module patterns
---
## TASK
[INSERT SPECIFIC TASK HERE]

View File

@@ -40,7 +40,6 @@ overzicht en review-checklist.
| Schema definitie | `/dev-docs/SCHEMA.md` | `/docs/SCHEMA.md` |
| API contract | `/dev-docs/API.md` | `/docs/API.md` |
| Design document | `/dev-docs/design-document.md` | `/docs/design-document.md` |
| Dev guide | `/dev-docs/dev-guide.md` | `/docs/dev-guide.md` |
| User docs (VitePress) | `/docs/` | — |
| Workspace rules | `/CLAUDE.md` (root) | — |
| Axios instance (app) | `apps/app/src/lib/axios.ts` | `src/utils/api.ts` etc. |

View File

@@ -1,798 +0,0 @@
# Crewli Development Guide
Cursor & Claude Code — Van Leeg Project naar Productie
**Versie:** 1.0 | **Datum:** Maart 2026 | **Stack:** Laravel 12 + Vue 3 | **AI Tools:** Cursor + Claude Code
## 1. Strategie & Mindset
Cursor vs Claude Code — wanneer gebruik je wat?
Voordat je begint met ontwikkelen is het belangrijk te begrijpen hoe Cursor en Claude Code zich tot elkaar verhouden. Ze zijn complementair — niet concurrerend.
| **Tool** | **Wanneer inzetten** |
|----|----|
| Cursor (IDE) | Dagelijks coderen. Inline autocomplete, context-aware suggesties, kleine refactors, code reviews, directe file-edits. Beste voor: een specifiek component bouwen, een bug fixen, een test schrijven. |
| Claude Code (Terminal) | Grote, multi-file taken. Scaffolding van een volledig module (migrations + model + controller + tests + Vue-pagina). Autonome agent die zelfstandig werkt, tests uitvoert en fouten corrigeert. Beste voor: 'Bouw het volledige shift-module end-to-end.' |
| Samen | Aanbevolen workflow: Claude Code genereert het skelet en alle bestanden. Cursor verfijnt, debugt en voegt details toe. Claude Code draait de test-suite. Cursor doet code review en stijlcorrecties. |
> **KERNPRINCIPE**
>
> Claude Code is je senior developer die grote blokken werk autonoom uitvoert.
>
> Cursor is je pair programmer die naast je zit terwijl jij zelf ook werkt.
>
> Jij bent de architect en product owner: jij beslist, zij bouwen.
## 2. De Eerste Stappen
Wat je vandaag doet voordat je één regel code schrijft
### 2.1 Repository structuur definitief maken
Controleer en bevestig de folderstructuur
Jouw huidige setup heeft al een goede basis. Bevestig of maak de volgende structuur:
```
crewli/ # Monorepo root
├── api/ # Laravel 12 backend
│ ├── app/
│ │ ├── Http/
│ │ │ ├── Controllers/Api/V1/
│ │ │ ├── Middleware/
│ │ │ └── Requests/ # Form Requests per endpoint
│ │ ├── Models/
│ │ ├── Policies/ # Laravel Policies per model
│ │ ├── Services/ # Business logic buiten controllers
│ │ ├── Events/ + Listeners/
│ │ └── Jobs/ # Queue jobs (briefings, PDF, notifs)
│ ├── database/
│ │ ├── migrations/
│ │ ├── factories/
│ │ └── seeders/
│ └── tests/Feature/Api/V1/ # PHPUnit feature tests per controller
├── apps/
│ ├── app/ # Organizer + Platform Admin SPA (Vuexy) -- HOOFDAPP
│ └── portal/ # Externe portals (vrijwilliger, artiest, leverancier)
├── docs/ # Design document, API docs, ERD
│ ├── design-document.md
│ └── dev-guide.md
└── .cursorrules # Cursor workspace rules
```
### 2.2 Dependencies installeren
Backend en frontend klaarstomen
**Backend (api/)**
```bash
cd api
# Spatie permissions (rollen/permissies)
composer require spatie/laravel-permission
# Audit log
composer require spatie/laravel-activitylog
# Media library (bestandsbeheer)
composer require spatie/laravel-medialibrary
# PDF generatie
composer require barryvdh/laravel-dompdf
# QR codes
composer require endroid/qr-code
# Publiceer Spatie configs
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan vendor:publish --provider="Spatie\LaravelActivitylog\ActivitylogServiceProvider"
```
**Frontend — alle apps (apps/app/, apps/portal/)**
```bash
# TanStack Query voor API state management
npm install @tanstack/vue-query
# Formuliervalidatie
npm install vee-validate zod @vee-validate/zod
# Drag-and-drop (form builder, timetable, prioriteitsranking)
npm install vuedraggable@next
# In apps/app/ ook:
npm install @fullcalendar/vue3 @fullcalendar/timeline @fullcalendar/resource-timeline
```
### 2.3 CLAUDE.md aanmaken
Het belangrijkste bestand in je hele repo
CLAUDE.md is de instructieset voor Claude Code. Het wordt automatisch geladen bij elke sessie. Dit bestand is de meest impactvolle investering die je doet — een uur hieraan besteden bespaart honderden uren aan correcties.
Maak aan: /crewli/CLAUDE.md (root niveau, zodat het voor alle sub-projecten geldt)
## 3. Helper Files — Volledige Inhoud
De exacte bestanden die je aanmaakt voor de eerste prompt
### 3.1 CLAUDE.md — Root niveau
Dit is de volledige, aanbevolen inhoud voor je CLAUDE.md. Kopieer dit letterlijk en pas aan waar nodig.
```
# Crewli — Claude Code Instructies
## Project Context
Crewli is een multi-tenant SaaS platform voor event- en festivalbeheer.
Gebouwd voor een professionele vrijwilligersorganisatie, met SaaS-uitbreidingspotentieel.
Design Document: /resources/design/design-document.md
## Tech Stack
- Backend: PHP 8.2+, Laravel 12, Sanctum, Spatie Permission, MySQL 8, Redis
- Frontend: TypeScript, Vue 3 (Composition API), Vuexy/Vuetify, Pinia, TanStack Query
- Testing: PHPUnit (backend), Vitest (frontend)
## Repository Structuur
- api/ Laravel backend
- apps/app/ Organizer + Platform Admin SPA (hoofdapp, super admin onder /platform/*)
- apps/portal/ Externe portals (vrijwilliger, artiest, leverancier)
## Backend Regels (STRIKT VOLGEN)
### Multi-tenancy
- ELKE query op event-data MOET scoperen op organisation_id
- Gebruik OrganisationScope als Eloquent Global Scope op alle event-gerelateerde modellen
- Nooit directe id-checks in controllers — gebruik altijd Policies
### Controllers
- Gebruik Resource Controllers (index/show/store/update/destroy)
- Namespace: App\Http\Controllers\Api\V1\
- Alle responses via API Resources (nooit model-attributen direct teruggeven)
- Validatie via Form Requests (nooit inline validate())
### Modellen
- Gebruik HasUlids trait op alle business-modellen (GEEN UUID v4)
- Soft deletes op: Organisation, Event, FestivalSection, Shift, ShiftAssignment, Person, Artist
- GEEN soft deletes op: CheckIn, BriefingSend, MessageReply, ShiftWaitlist (audit-records)
- JSON kolommen ALLEEN voor opaque configuratie — nooit voor queryable data
### Database
- Primaire sleutels: ULID via HasUlids (niet UUID v4, niet auto-increment voor business tables)
- Elke migratie in volgorde aanmaken: eerst foundation, dan afhankelijke tabellen
- ALTIJD composite indexes toevoegen zoals gedocumenteerd in het design document sectie 3.5
### Rollen & Permissies
- Gebruik Spatie laravel-permission
- Check rollen via $user->hasRole() en Policies — nooit hardcoded role strings in controllers
- Drie niveaus: app (super_admin), organisatie (org_admin/org_member), event (event_manager etc.)
### Testing
- Schrijf PHPUnit Feature Tests per controller
- Minimaal per endpoint: happy path + unauthenticated (401) + wrong organisation (403)
- Gebruik factories voor alle test-data
- Draai tests NA elke module: php artisan test --filter=ModuleNaam
## Frontend Regels (STRIKT VOLGEN)
### Vue Componenten
- Altijd <script setup lang='ts'> — nooit Options API
- Props altijd getypeerd met defineProps<{...}>()
- Emits altijd gedeclareerd met defineEmits<{...}>()
### API Calls
- Gebruik TanStack Query (useQuery / useMutation) voor ALLE API calls
- Nooit direct axios in een component — altijd via een composable in composables/api/
- Pinia stores voor cross-component state — nooit prop drilling
### Naamgeving
- DB kolommen: snake_case
- TypeScript/JS variabelen: camelCase
- Vue componenten: PascalCase (bijv. ShiftAssignPanel.vue)
- Composables: use-prefix (bijv. useShifts.ts)
- Pinia stores: use-suffix store (bijv. useEventStore.ts)
### UI
- Gebruik ALTIJD Vuexy/Vuetify componenten voor layout, forms, tabellen, dialogen
- Nooit custom CSS schrijven als een Vuetify klasse bestaat
- Responsief: mobile-first, minimaal werkend op 375px breedte
## Verboden Patronen
- NOOIT: $user->role === 'admin' (gebruik policies)
- NOOIT: Model::all() zonder where-clausule (altijd scopen)
- NOOIT: dd() of var_dump() achterlaten in code
- NOOIT: .env waarden hardcoden in code
- NOOIT: JSON kolommen gebruiken voor data waarop gefilterd wordt
- NOOIT: UUID v4 als primaire sleutel (gebruik HasUlids)
## Volgorde bij elke nieuwe module
1. Migratie(s) aanmaken en draaien
2. Eloquent Model met relaties, scopes en HasUlids
3. Factory voor test-data
4. Policy voor autorisatie
5. Form Request(s) voor validatie
6. API Resource voor response transformatie
7. Resource Controller
8. Routes registreren in api.php
9. PHPUnit Feature Test schrijven en draaien
10. Vue composable voor API calls (useModuleNaam.ts)
11. Pinia store indien cross-component state nodig
12. Vue pagina component
13. Route toevoegen in Vue Router
```
### 3.2 .cursorrules — Root niveau
Dit is het equivalent van CLAUDE.md maar voor Cursor's autocomplete en inline AI. Korter en meer gefocust op directe code-stijl.
```
# Crewli Cursor Rules
## Stack
PHP 8.2 + Laravel 12 | TypeScript + Vue 3 + Vuexy/Vuetify | Pinia + TanStack Query
## Laravel
- Resource Controllers, Form Requests, API Resources — altijd
- HasUlids op business modellen, HasFactory, SoftDeletes waar gedocumenteerd
- Global Scope OrganisationScope op event-gerelateerde modellen
- Policies voor autorisatie, nooit inline role checks
## Vue 3
- <script setup lang='ts'> altijd
- TanStack Query voor API state, Pinia voor UI state
- Vuetify componenten eerst, custom CSS als laatste redmiddel
## Naamgeving
- snake_case DB | camelCase JS | PascalCase Vue | use* composables | use*Store Pinia
## Tests
- PHPUnit Feature Test per controller, minimaal: 200 + 401 + 403
```
### 3.3 dev-docs/SCHEMA.md — Levend schema-document
Maak een Markdown bestand aan in /dev-docs/ dat de tabel-definitie bevat als platte tekst. Claude Code gebruikt dit als primaire referentie bij het genereren van migraties.
```
# Crewli Database Schema
# Versie: 1.3 | Gegenereerd vanuit Design Document
## Regels
- Primaire sleutels: ULID via HasUlids (nooit UUID v4)
- Soft delete: zie lijst per tabel hieronder
- JSON kolommen: alleen voor opaque config
## Tabellen
### users
- id (ulid, PK)
- name (string)
- email (string, unique)
- password (string)
- timezone (string, default: Europe/Amsterdam)
- locale (string, default: nl)
- avatar (string, nullable)
- email_verified_at (timestamp, nullable)
- deleted_at (timestamp, nullable) -- soft delete
### organisations
- id (ulid, PK)
- name (string)
- slug (string, unique)
- billing_status (enum: trial|active|suspended|cancelled, default: trial)
- settings (json, nullable) -- UI display prefs only
- deleted_at (timestamp, nullable)
# ... (volledig schema uit Design Document sectie 3.5)
```
### 3.4 docs/API.md — API contract
Een simpele route-lijst die Claude Code gebruikt als referentie bij het genereren van controllers en Vue composables.
```
# Crewli API Contract
# Base: /api/v1/
# Auth: Bearer token (Sanctum)
## Auth
POST /auth/login
POST /auth/logout
GET /auth/me
## Organisations
GET /organisations -- lijst (super admin)
POST /organisations -- aanmaken
GET /organisations/{org} -- detail
PUT /organisations/{org} -- bijwerken
GET /organisations/{org}/members -- leden
POST /organisations/{org}/invite -- uitnodigen
## Events
GET /organisations/{org}/events
POST /organisations/{org}/events
GET /organisations/{org}/events/{event}
PUT /organisations/{org}/events/{event}
## Festival Sections
GET /events/{event}/sections
POST /events/{event}/sections
GET /events/{event}/sections/{section}
## Time Slots
GET /events/{event}/time-slots
POST /events/{event}/time-slots
## Shifts
GET /events/{event}/sections/{section}/shifts
POST /events/{event}/sections/{section}/shifts
PUT /events/{event}/sections/{section}/shifts/{shift}
POST /events/{event}/sections/{section}/shifts/{shift}/assign
POST /events/{event}/sections/{section}/shifts/{shift}/claim
## Persons
GET /events/{event}/persons
POST /events/{event}/persons
GET /events/{event}/persons/{person}
PUT /events/{event}/persons/{person}
POST /events/{event}/persons/{person}/approve
# ... (volledig API contract uitbreiden per module)
```
## 4. Development Workflow
Hoe je van leeg project naar werkende feature gaat
Elke feature volgt dezelfde drielaagse workflow. Commit altijd per voltooide laag — nooit halfafgebouwde code in main.
| **Laag** | **Wat je doet en met welk tool** |
|----|----|
| Laag 1 — Backend (API) | Claude Code genereert: migratie + model + factory + policy + form request + resource + controller + test. Jij reviewt en draait tests. |
| Laag 2 — Frontend (Vue) | Claude Code genereert: composable + Pinia store + Vue pagina + router entry. Cursor verfijnt de UI met Vuexy componenten. |
| Laag 3 — Integration | Cursor: verbind frontend met backend. Test end-to-end. Fix type-errors. Review mobile weergave. |
### 4.1 De Module-generatie volgorde
Altijd in deze volgorde. Nooit stappen overslaan — later toevoegen kost meer tijd dan nu correct doen.
| **Stap** | **Commando / Actie** |
|----|----|
| 1. Migratie | php artisan make:migration create_shifts_table |
| 2. Model | php artisan make:model Shift -mfp (migration + factory + policy) |
| 3. Form Request | php artisan make:request StoreShiftRequest + UpdateShiftRequest |
| 4. API Resource | php artisan make:resource ShiftResource + ShiftCollection |
| 5. Controller | php artisan make:controller Api/V1/ShiftController --api |
| 6. Registreer routes | In api/routes/api.php toevoegen |
| 7. Test | php artisan make:test ShiftControllerTest + draaien |
| 8. Composable | apps/app/src/composables/api/useShifts.ts aanmaken |
| 9. Store (indien nodig) | apps/app/src/stores/useShiftStore.ts |
| 10. Vue pagina | apps/app/src/pages/sections/[id]/shifts.vue |
| 11. Route | apps/app/src/router/index.ts |
### 4.2 Fase-planning: wat bouw je wanneer
| **Fase** | **Inhoud** |
|----|----|
| Fase 1 — Foundation (nu) | Auth (login/logout/me), Organisations CRUD, Events CRUD, User invitations, Multi-tenant scope, Roles & permissions setup, Basis dashboard shell |
| Fase 2 — Core Operations | Persons & Crowd Types, Festival Sections + Time Slots + Shifts, Shift claiming + goedkeuring, Vrijwilligers registratie + portaal, Accreditatie engine, Basis briefings |
| Fase 3 — Advancing & Show Day | Artist advancing + portaal, Timetable, Mission Control, Formulierbouwer, Post-festival evaluatie, PDF allocatiesheet, Campagnes (email + WhatsApp via Zender) |
| Fase 4 — Differentiators | Real-time WebSockets, Show Day Mode, Vrijwilligersprofiel + festival-paspoort, Shift swap & wachtlijst, Retrospectief rapport, Leveranciersportaal uitgebreid |
## Gedeelde frontend packages
`apps/portal/` en `apps/app/` delen een klein aantal schema-gedreven modules. Deze sectie legt vast wat wel en niet in die gedeelde laag hoort, en hoe je de aliases opzet.
### Locatie
`packages/form-schema/` — pure TypeScript, **geen** `package.json`, alias-only. Geen npm-package en geen pnpm workspace member; puur geconsumeerd via per-app TypeScript- en Vite-path-aliases.
### Wat hoort erin
- Schema-types die backend PHP-enums en API-resources mirroren (bv. `PublicFormField`, `FormFieldType`, `PublicFormSchema`)
- Schema-gedreven gedrag: validation rule builders, conditional-logic evaluator, step-navigation, stored-value formatters
- Alles wat pure TypeScript is en in portal én app exact dezelfde output moet produceren
### Wat hoort er NIET in
- Vue SFCs (`.vue` bestanden) — elke app rendert zijn eigen UI
- Vuetify/Vuexy-specifieke code, theming of styling
- Vue Router-code, route-definities of navigation guards
- Pinia stores of app-level state
- Axios-instanties of API-client code
- Composables die afhangen van app-runtime (auth, i18n, router)
### Rationale
Portal is submit-facing, app is builder- en review-facing. De twee apps zullen visueel uit elkaar groeien naarmate product-requirements verschillen. Gedeelde Vue-componenten zouden hun stijlen aan elkaar koppelen — daarom delen we het *contract en de logica* (schema-types, gedrag) maar laat elke app zijn eigen UI bezitten.
### Alias-setup (verplicht op drie plekken per app)
Beide apps moeten `@form-schema/*` registreren in álle drie:
- `apps/<app>/tsconfig.json``compilerOptions.paths`
- `apps/<app>/vite.config.ts``resolve.alias`
- `apps/<app>/vitest.config.ts``resolve.alias`
Alle drie zijn nodig omdat `vitest.config.ts` niet overerft van `vite.config.ts`. Eén van de drie vergeten breekt óf de productie-build, óf de dev-server, óf de test-suite. Wanneer een derde app consumer wordt, wire dan alle drie tegelijk.
### Vue-resolutie (dependency-note)
Het gedeelde package heeft geen `package.json` en kan dus geen eigen dependencies declareren. Om `vue` resolvable te maken tijdens TypeScript-compilatie vanuit `packages/form-schema/src/`, voegen beide apps een `"vue": ["./node_modules/vue"]`-entry toe aan `compilerOptions.paths`. Dit is het minimum; als het package ooit niet-triviale externe imports krijgt (bv. `@vueuse/core`, `lodash`), voeg dan per app een vergelijkbare paths-entry toe — níet een `package.json` in het package — of promoveer het package tot workspace-member.
## 5. Prompt Bibliotheek
Kant-en-klare prompts voor elke ontwikkelstap
Gebruik deze prompts letterlijk of als basis. De meest effectieve prompts zijn: specifiek, contextueel en taak-gebaseerd. Verwijs altijd naar de docs/ bestanden die je hebt aangemaakt.
### 5.1 Kickstart prompts
> **Fase 1 kickstart — Alles genereren in een sweep**
>
> Lees /resources/design/design-document.md sectie 3.5 (schema) en /CLAUDE.md.
>
> Genereer alle Fase 1 componenten in de juiste volgorde:
>
> 1. Migrations voor: users (update), organisations, organisation_user, user_invitations, events, event_user_roles
> 2. Eloquent modellen met HasUlids, relaties, OrganisationScope global scope waar van toepassing
> 3. Factories met realistic test data
> 4. Spatie Permission seeder: maak rollen aan (super_admin, org_admin, org_member, event_manager, staff_coordinator, volunteer_coordinator)
> 5. Auth controller (login/logout/me) met Sanctum
> 6. Organisations controller (CRUD) met Policy en Feature Test
> 7. Events controller (CRUD) met Policy en Feature Test
>
> Draai na elke stap: php artisan test. Los fouten op voor je verder gaat.
> **Module genereren — Shifts als voorbeeld**
>
> Lees /CLAUDE.md en /dev-docs/SCHEMA.md voor de shifts tabel definitie.
>
> Bouw het volledige Shifts module in de volgorde uit CLAUDE.md sectie 'Volgorde bij elke nieuwe module'.
>
> Specifieke eisen voor Shifts:
>
> - time_slot_id MOET gedenormaliseerd worden in shift_assignments voor de UNIQUE(person_id, time_slot_id) constraint
> - ShiftAssignment heeft een status machine: pending_approval > approved/rejected/cancelled/completed
> - Auto-approve is configureerbaar per shift (auto_approved bool op shift niveau)
> - Bij approve: stuur notificatie naar vrijwilliger (queued job, gebruik ZenderService voor WhatsApp)
> - ShiftResource moet slots_filled (count van approved assignments) en fill_rate (percentage) berekend teruggeven
>
> Eindig met: php artisan test --filter=Shift
### 5.2 Backend prompts
> **Migration genereren**
>
> Genereer een Laravel migratie voor de tabel [TABELNAAM] op basis van /dev-docs/SCHEMA.md.
>
> Gebruik $table->ulid('id')->primary() als PK.
>
> Voeg alle indexes toe zoals gedocumenteerd (composite indexes, unique constraints).
>
> Voeg timestamps() en softDeletes() toe indien van toepassing per CLAUDE.md.
>
> Gebruik constrained() op alle foreign keys voor cascade-gedrag.
> **Model met alle features**
>
> Genereer het Eloquent model voor [MODELNAAM].
>
> Gebruik: HasUlids, HasFactory, SoftDeletes (indien van toepassing).
>
> Voeg toe: OrganisationScope global scope, alle relaties (hasMany, belongsTo, belongsToMany),
> computed accessors (fill_rate, available_slots), status-gerelateerde scopes (scopePending, scopeApproved),
> en $fillable of $guarded array.
>
> Schrijf ook de factory met realistic Nederlandse testdata.
> **API Resource met computed velden**
>
> Genereer een Laravel API Resource voor [MODELNAAM].
>
> Voeg toe: alle relevante velden, computed velden (fill_rate, status_label),
> conditioneel geladen relaties (whenLoaded), en wanneer van toepassing: when() voor permissie-afhankelijke velden.
>
> De Resource mag NOOIT model-attributen direct weggeven zonder transformatie.
> **Feature test schrijven**
>
> Schrijf een PHPUnit Feature Test voor [CONTROLLERNAAM].
>
> Dek minimaal af: index (200), show (200), store (201), update (200), destroy (204),
> unauthenticated (401 op alle routes), wrong organisation (403), validatiefouten (422).
>
> Gebruik RefreshDatabase, ActingAs met correcte rol via Spatie Permission.
>
> Maak test data via factories — nooit hardcoded IDs.
> **ZenderService aanmaken (WhatsApp/SMS)**
>
> Maak app/Services/ZenderService.php aan.
>
> Zender is een self-hosted SMS/WhatsApp gateway (CodeCanyon product).
>
> Config: ZENDER_API_URL en ZENDER_API_KEY uit .env.
>
> Methoden: sendSms(string $to, string $message): bool
> sendWhatsApp(string $to, string $message): bool
> sendByUrgency(string $to, string $message, string $urgency): bool
>
> urgency: normal=email only, urgent=whatsapp, emergency=sms+whatsapp parallel
>
> Gebruik Laravel HTTP Client (Http::). Log alle sends via activitylog.
>
> Schrijf ook een ZenderServiceTest met HTTP fake.
### 5.3 Frontend prompts
> **Vue pagina voor een lijst-overzicht**
>
> Maak apps/app/src/pages/[module]/index.vue.
>
> Gebruik \<script setup lang='ts'\>.
>
> API calls via useQuery() uit TanStack Query — niet direct axios.
>
> Tabel via VDataTable van Vuetify — niet custom HTML.
>
> Bovenaan: status KPI-tiles (totaal, goedgekeurd, pending) als klikbare VCard componenten.
>
> Rij klik: opent een side panel (niet navigeert naar nieuwe pagina) met detail-informatie.
>
> Loading state: VSkeleton loader. Error state: VAlert met retry knop.
>
> Mobiel: tabel collapst naar een VList op viewport < 768px.
> **Composable voor API calls**
>
> Maak apps/app/src/composables/api/use[Module].ts.
>
> Exporteer: use[Module]List (useQuery), use[Module]Detail (useQuery met id param),
> useCreate[Module] (useMutation), useUpdate[Module] (useMutation), useDelete[Module] (useMutation).
>
> Gebruik axios via de centrale api.ts instance (met Sanctum CSRF en auth header).
>
> Mutations invalideren automatisch de relevante query keys na succes.
>
> Alle response types volledig getypeerd via TypeScript interfaces in types/[module].ts.
> **Pinia store aanmaken**
>
> Maak apps/app/src/stores/use[Module]Store.ts.
>
> Gebruik defineStore met Setup syntax (niet Options syntax).
>
> Sla op: geselecteerde IDs, UI state (open sidepanel, actief tab), filters.
>
> NIET in Pinia: server data (dat zit in TanStack Query). Pinia is alleen voor UI state.
>
> Exporteer: alle state als readonly via storeToRefs.
> **Shift claim workflow — volledig end-to-end**
>
> Bouw de volledige shift claim workflow:
>
> Backend: POST /shifts/{shift}/claim endpoint in ShiftController.
>
> - Valideer: shift heeft slots_open_for_claiming beschikbaar
> - Valideer: geen bestaande approved assignment voor zelfde time_slot_id voor deze person
> - Maak ShiftAssignment aan met status=pending_approval
> - Dispatch NotifyCoordinatorOfClaimJob (queued)
> - Return ShiftAssignmentResource
>
> Frontend: 'Claim' knop in portal/shifts/index.vue.
>
> - Disable knop als conflict of geen slots beschikbaar
> - Na claim: toon 'Wachten op goedkeuring' badge
> - Optioneel: 'Op wachtlijst' knop als shift vol is
### 5.4 Agent-aanstuurprompts
> **Grote module — een prompt voor alles**
>
> Je bent een senior fullstack developer die werkt aan Crewli. Lees /CLAUDE.md volledig.
>
> Bouw het volledige [MODULE] module:
>
> Backend (in volgorde):
>
> 1. Migrations voor alle tabellen uit /dev-docs/SCHEMA.md sectie [X.X]
> 2. Models met alle relaties, scopes en accessors
> 3. Factories
> 4. Policies
> 5. Form Requests
> 6. API Resources
> 7. Controllers
> 8. Routes
> 9. Feature tests — draai ze, los fouten op
>
> Frontend:
>
> 10. TypeScript types in apps/app/src/types/[module].ts
> 11. Composables in apps/app/src/composables/api/
> 12. Vue pagina's (lijst + detail side panel)
> 13. Router entries
>
> Stop na elke laag en vraag bevestiging voor je doorgaat.
>
> Als een test faalt: los het op voor je verdergaat — nooit overslaan.
> **Bug fix prompt**
>
> Er is een probleem met [BESCHRIJVING VAN HET PROBLEEM].
>
> Relevante bestanden: [BESTANDSPADEN].
>
> Foutmelding: [PLAK EXACTE ERROR].
>
> Verwacht gedrag: [WAT ZOU ER MOETEN GEBEUREN].
>
> Analyseer de oorzaak, schrijf een failing test die het probleem reproduceert,
> fix het probleem, en bevestig dat de test slaagt.
> **Code review prompt**
>
> Review de code in [BESTANDSPAD] als senior Laravel/Vue developer.
>
> Check specifiek op:
>
> - Multi-tenancy: wordt organisation_id correct gescopeerd?
> - Security: worden Policies gebruikt? Geen directe role-checks?
> - Performance: ontbrekende eager loading (N+1), ontbrekende indexes?
> - Conventies: volgt het CLAUDE.md regels?
> - Types: zijn alle TypeScript types volledig (geen any)?
>
> Geef concrete verbeterpunten met codevoorbeelden.
## 6. Agents — Autonome Ontwikkeling
Hoe je Claude Code en Cursor agents effectief inzet
### 6.1 Claude Code als Agent
Claude Code kan volledig autonoom werken: bestanden lezen, aanmaken, aanpassen, tests draaien en fouten corrigeren — zonder dat jij elke stap bevestigt. Dit is het krachtigste en snelste werkmode.
| **Mode** | **Wanneer gebruiken** |
|----|----|
| Interactief (standaard) | Als je wil meekijken en goedkeuren. Claude Code stelt elke actie voor en wacht. Gebruik voor: eerste keer een module bouwen, complexe refactors. |
| Autonoom (--dangerously-skip-permissions) | Als je een grote taak wil delegeren en wegloopt. Claude Code werkt door tot klaar. Gebruik voor: routine-modules die je eerder hebt gebouwd, test-driven fixes. |
| Aanbevolen aanpak | Start autonoom voor scaffolding. Schakel naar interactief bij UI-componenten of business-logica die project-specifieke kennis vereist. |
**Claude Code opstarten**
```bash
# Installeer Claude Code (eenmalig)
npm install -g @anthropic-ai/claude-code
# Start in je project root
cd /pad/naar/crewli
claude
# Of: direct met een taak
claude --print 'Genereer de Shift migration op basis van CLAUDE.md'
# Autonoom mode (voorzichtig gebruiken)
claude --dangerously-skip-permissions
```
### 6.2 Cursor Agent Mode
Cursor heeft een ingebouwde Agent mode die vergelijkbaar is met Claude Code maar geintegreerd in de IDE. Activeer via Cmd+Shift+P > 'Cursor: Open Agent'.
| **Feature** | **Gebruik** |
|----|----|
| @workspace | Geeft de agent toegang tot je hele codebase als context. Altijd meegeven bij module-niveau taken. |
| @file | Verwijs naar een specifiek bestand. Bijv: @CLAUDE.md @api/app/Models/Shift.php |
| @docs | Verwijs naar externe documentatie (Laravel docs, Vuetify docs). Cursor indexeert deze. |
| Composer mode | Meerdere bestanden tegelijk bewerken. Ideaal voor: tegelijk model + controller + test aanpassen. |
### 6.3 De optimale agent-workflow per dag
> **DAGELIJKSE ROUTINE**
>
> Ochtend: Open Claude Code. Geef de taak voor die dag: 'Bouw het volledig Persons module op basis van CLAUDE.md en SCHEMA.md.' Laat autonoom draaien.
>
> Middag: Review de gegenereerde code in Cursor. Check: volgt het de conventies? Zijn de tests groen? Zijn de TypeScript types compleet?
>
> Namiddag: Cursor voor UI fijnafstelling, Vuexy componenten integratie, visuele correcties.
>
> Einde dag: php artisan test (alle tests groen). Commit alles met duidelijke commit messages.
### 6.4 Context window management
Claude Code heeft een beperkt context window. Bij grote taken verliest het de context van eerdere bestanden. Beheer dit proactief:
- Begin elke nieuwe sessie met: 'Lees /CLAUDE.md voor je begint.'
- Verwijs expliciet naar relevante bestanden: 'Zie /dev-docs/SCHEMA.md voor de tabel definitie.'
- Splits grote modules: 'Bouw eerst alleen de backend (stappen 1-9). Stop dan.'
- Na een context-verlies: geef een samenvatting: 'We bouwen Crewli. Tot nu toe klaar: auth, organisations, events. Nu: Shifts module backend.'
- Gebruik /dev-docs/API.md als levend document — houd bij wat er al gebouwd is.
## 7. Tips, Valkuilen & Best Practices
Geleerd van ervaring — lees dit voordat je begint
### 7.1 De grootste tijdverspillers
| **Valkuil** | **Oplossing** |
|----|----|
| Te brede prompts: 'Bouw de hele app' | Altijd per module. Per module maximaal 1 laag tegelijk. Breed = vaag = slechte output. |
| CLAUDE.md niet up-to-date houden | Na elke architectuurbeslissing: update CLAUDE.md. Dit is je bron van waarheid. Verouderde regels leiden tot inconsistente code. |
| Tests overslaan 'want het werkt toch' | Schrijf tests altijd. AI-gegenereerde code heeft subtiele bugs die pas later opduiken. Tests vangen dit vroeg. |
| Alle gegenereerde code blindelings accepteren | Review altijd: check multi-tenancy scoping, check indexes, check error handling. AI mist soms subtiele business logica. |
| Frontend en backend tegelijk bouwen | Backend eerst, compleet en getest. Dan frontend. Nooit parallel — je verliest overzicht. |
| Geen versiecontrole per module | Commit na elke voltooide module (backend + frontend + tests). Kleine commits = makkelijk terugdraaien. |
### 7.2 Prompts die altijd goed werken
- **Geef altijd context: 'Crewli is een multi-tenant SaaS voor festival-organisatie...'**
- **Verwijs naar bestanden: 'Op basis van CLAUDE.md en SCHEMA.md...'**
- **Specificeer de output: 'Genereer X, Y en Z. Niets anders.'**
- **Vraag om uitleg: 'Leg uit waarom je deze aanpak kiest voor de shift conflict-check.'**
- **Gebruik iteratief: 'Dit klopt niet omdat... Pas aan en draai tests opnieuw.'**
### 7.3 Kwaliteitscontrole checklist
Gebruik dit als checklist voor elke voltooide module voordat je verder gaat:
| **Check** | **Wat je controleert** |
|----|----|
| Tests | php artisan test draait groen. Minimaal: 200, 401, 403 per endpoint. |
| Multi-tenancy | Elke query heeft organisation_id scope. Controleer via tinker. |
| N+1 queries | Gebruik Laravel Debugbar of query logging. Geen N+1 in lijst-endpoints. |
| TypeScript | Geen 'any' types in Vue composables en components. npx tsc --noEmit groen. |
| Mobile | Pagina is bruikbaar op 375px. Open Chrome DevTools en check. |
| Error states | Wat ziet een gebruiker bij: lege lijst, API fout, netwerk timeout? |
| CLAUDE.md | Geen verboden patronen (Model::all, hardcoded roles, UUID v4). |
### 7.4 Handige Laravel commando's
```bash
# Alles in een keer voor een nieuwe model
php artisan make:model Shift -a # model + migration + factory + seeder + policy + controller + resource
# Tests draaien
php artisan test # alle tests
php artisan test --filter=ShiftTest # specifieke test class
php artisan test --coverage # met coverage rapport
# Database
php artisan migrate:fresh --seed # reset + migreer + seed
php artisan tinker # REPL voor quick checks
# Routes inspecteren
php artisan route:list --path=api/v1 # alle API routes
# Queue (voor briefings, notificaties)
php artisan queue:work --queue=notifications,briefings,default
```
### 7.5 Eerste dag: exacte volgorde
| **#** | **Actie** |
|----|----|
| 1 | Repository structuur controleren (sectie 2, stap 01) |
| 2 | Dependencies installeren (sectie 2, stap 02) |
| 3 | CLAUDE.md aanmaken en invullen (sectie 3.1) |
| 4 | .cursorrules aanmaken (sectie 3.2) |
| 5 | docs/SCHEMA.md aanmaken met volledig schema uit design document |
| 6 | docs/API.md aanmaken met initiiele routes |
| 7 | Claude Code starten: 'Lees CLAUDE.md. Daarna: genereer Fase 1 — auth, organisations, events.' |
| 8 | Tests draaien: php artisan test — los fouten op |
| 9 | Commit: 'feat: fase 1 foundation — auth, organisations, events' |
| 10 | Morgen: Fase 2 starten met Persons & Crowd Types |
---
Crewli Development Guide v1.0 — Maart 2026