Files
crewli/dev-docs/MASTER_PROMPT_CC.md
bert.hausmans 945e22f322 docs: remove admin SPA references and update production URLs
The admin SPA (apps/admin/) has been retired. Its functionality now
lives in apps/app/ under /platform/* routes for super_admin users.
Updated all documentation to reflect: 2 SPAs instead of 3, removed
FRONTEND_ADMIN_URL/port 5173 references, changed production URL from
app.crewli.app to crewli.app. Retired admin-specific security audit
findings (A13-2, A13-4, A13-5, A13-7) and APPS-01 backlog item.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:21:44 +02:00

13 KiB

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

  1. 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.

  2. 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.

  3. NO GENERIC NAMES. Names must be specific and self-documenting:

    • DataService, Helper, Manager, handleData(), processItem()
    • ShiftAssignmentService, VolunteerAvailabilityChecker, resolveShiftConflict(), calculateFillRate()
  4. 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

  1. 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.
  2. 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

  1. 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.

  2. 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.

  3. 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

  1. 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.

  2. 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.
  3. 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

  1. 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.

  2. CONSISTENT RESPONSE STRUCTURE. Follow the existing pattern: { data: {...}, meta: {...} } for paginated lists. Consistent error format with { message: "...", errors: {...} } for validation failures.

Resilience & operations

  1. 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.

  2. 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)
  3. 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

  1. 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
  2. GIT. Auto-commit after each completed module: feat(module-name): add backend scaffold with tests


VERIFICATION CHECKLIST (run before reporting "done")

# 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]