# Crewli — Claude Code Instructions ## Project context Crewli is a multi-tenant SaaS platform for event and festival management. Built for a professional volunteer organisation, with potential to expand as SaaS. Design document: `/dev-docs/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) ## Quality gates - `php artisan test` — PHPUnit feature suite. See Testing rules below. - `./vendor/bin/pint` — code style. Runs pre-commit in scope; format before merge. - `composer analyse` — Larastan static analysis at level 6 with accept-all baseline. New errors beyond the baseline must be fixed before merge. See `/dev-docs/LARASTAN.md`. - `composer rector` — Rector dry-run for modernisation suggestions. See `/dev-docs/RECTOR.md`. Apply only in scoped sprints, never automatically. - ts-reset patches TypeScript's loosest default types in both SPAs. See `/dev-docs/FRONTEND-TOOLING.md`. New TypeScript code adheres to ts-reset's stricter types automatically. - Vitest — `apps/portal` has 113+ tests; `apps/app` currently has no Vitest setup (tracked as TECH-APP-VITEST, must close before S3b lands). ## Development tooling - Laravel Telescope at `/telescope` — queries, jobs, mails, redis, events. Local + testing only, never production. super_admin role required to view. See `/dev-docs/TELESCOPE.md`. ## Repository layout - `api/` — Laravel backend - `apps/app/` — Organizer SPA (main product app + Platform Admin for super admins) - `apps/portal/` — External portal (volunteers, artists, suppliers, etc.) ## Apps and portal architecture - `apps/app/` — Organizer: event management per organisation. Includes **Platform Admin** section (`/platform/*`) for super_admin users (organisation management, user management, impersonation, activity log). - `apps/portal/` — External users: one app, two access modes: - Login-based (`auth:sanctum`): volunteers, crew — persons with `user_id` - Token-based (`portal.token` middleware): artists, suppliers, press — persons without `user_id` ### CORS Configure two frontend origins in both Laravel (`config/cors.php` via env) and the Vite dev server proxy: - app: `localhost:5174` - portal: `localhost:5175` **Production (`crewli.app`):** API `https://api.crewli.app`, SPAs `https://crewli.app`, `https://portal.crewli.app` — see `api/.env.example` for `FRONTEND_*` and `SANCTUM_STATEFUL_DOMAINS`. **`crewli.nl`** is only for a future marketing site; this application stack uses **`crewli.app`** (not `.nl` for API, SPAs, or transactional mail). ## Backend rules (strict) ### Multi-tenancy - Every query on event data **must** scope on `organisation_id` - Use `OrganisationScope` as an Eloquent global scope on all event-related models - Never use raw ID checks in controllers — always use policies ### Controllers - Use resource controllers (`index` / `show` / `store` / `update` / `destroy`) - Namespace: `App\Http\Controllers\Api\V1\` - Return all responses through API resources (never return raw model attributes) - Validate with form requests (never inline `validate()`) ### Models - Use the `HasUlids` trait on all business models (not UUID v4) - Soft deletes on: Organisation, Event, FestivalSection, Shift, ShiftAssignment, Person, Artist - No soft deletes on: CheckIn, BriefingSend, MessageReply, ShiftWaitlist (audit records) - JSON columns **only** for opaque configuration — never for queryable/filterable data ### Database - Primary keys: ULID via `HasUlids` (not UUID v4, not auto-increment on business tables) - Create migrations in dependency order: foundation first, then dependent tables - Always add composite indexes as documented in the design document (section 3.5) ### Roles and permissions - Use Spatie `laravel-permission` - Check roles via `$user->hasRole()` and policies — never hard-code role strings in controllers - Three levels: app (`super_admin`), organisation (`org_admin` / `org_member`), event (`event_manager`, etc.) ### Testing - Write PHPUnit feature tests per controller - Minimum per endpoint: happy path + unauthenticated (401) + wrong organisation (403) - Use factories for all test data - After each module: `php artisan test --filter=ModuleName` ## Frontend rules (strict) ### Vuexy reference source (mandatory) When referencing Vuexy demo pages, components, or patterns, ALWAYS use the TypeScript Vue version at: ``` resources/vuexy-admin-v10.11.1/vue-version/typescript-version/full-version/ ``` This is the **ONLY** valid reference path. Never use: - `javascript-version/` — wrong language - `starter-kit/` — incomplete, missing components - Any other variant or version Before implementing any Vuexy-based page or component, read the reference implementation from this path first: ```bash # Example: find auth page references find resources/vuexy-admin-v10.11.1/vue-version/typescript-version/full-version/src/pages -name "*.vue" | grep -i "login\|auth" ``` ### Vuexy-first strategy Before writing ANY frontend component, consult `/dev-docs/VUEXY_COMPONENTS.md` and follow this decision tree: 1. **Can a standard Vuetify component do this?** → Use it with default props. Do not wrap it in a custom component. 2. **Does Vuexy provide an @core component for this?** → Use it. Check `/dev-docs/VUEXY_COMPONENTS.md` section 1 for the full registry. 3. **Does an existing Crewli page already solve a similar UI pattern?** → Copy that pattern exactly. Check `/dev-docs/VUEXY_COMPONENTS.md` section 3 for established patterns and their reference implementations. 4. **None of the above?** → Only then write custom code. Add `