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
```