Files
crewli/CLAUDE.md
bert.hausmans c75a93aa5d docs: add git commit policy to CLAUDE.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:19:57 +02:00

194 lines
7.1 KiB
Markdown

# 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)
## Repository layout
- `api/` — Laravel backend
- `apps/admin/` — Super Admin SPA
- `apps/app/` — Organizer SPA (main product app)
- `apps/portal/` — External portal (volunteers, artists, suppliers, etc.)
## Apps and portal architecture
- `apps/admin/` — Super Admin: platform management, creating organisations
- `apps/app/` — Organizer: event management per organisation
- `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 three frontend origins in both Laravel (`config/cors.php` via env) and the Vite dev server proxy:
- admin: `localhost:5173`
- app: `localhost:5174`
- portal: `localhost:5175`
**Production (`crewli.app`):** API `https://api.crewli.app`, SPAs `https://admin.crewli.app`, `https://app.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)
### Vue components
- Always `<script setup lang="ts">` — never the Options API
- Type props with `defineProps<{...}>()`
- Declare emits with `defineEmits<{...}>()`
### API calls
- Use TanStack Query (`useQuery` / `useMutation`) for **all** API calls
- Never call axios directly from a component — always via a composable under `composables/`
- Use Pinia stores for cross-component state — no prop drilling
### Naming
- DB columns: `snake_case`
- TypeScript / JS variables: `camelCase`
- Vue components: PascalCase (e.g. `ShiftAssignPanel.vue`)
- Composables: `use` prefix (e.g. `useShifts.ts`)
- Pinia stores: `use` prefix + `Store` suffix (e.g. `useEventStore.ts`)
### UI
- Always use Vuexy/Vuetify for layout, forms, tables, dialogs
- Do not write custom CSS when a Vuetify utility class exists
- Responsive: mobile-first, usable from 375px width
## Forbidden patterns
- Never: `$user->role === 'admin'` (use policies)
- Never: `Model::all()` without a `where` clause (always scope)
- Never: leave `dd()` or `var_dump()` in code
- Never: hard-code `.env` values in code
- Never: use JSON columns for data you need to filter on
- Never: UUID v4 as primary key (use `HasUlids`)
## Order of work for each new module
1. Create and run migration(s)
2. Eloquent model with relationships, scopes, and `HasUlids`
3. Factory for test data
4. Policy for authorization
5. Form request(s) for validation
6. API resource for response shaping
7. Resource controller
8. Register routes in `api.php`
9. Write and run PHPUnit feature tests
10. Vue composable for API calls (e.g. `useShifts.ts`)
11. Pinia store if cross-component state is needed
12. Vue page component
13. Add route in Vue Router
## User Documentation (VitePress)
End-user documentation lives in /docs/ as a VitePress site.
Developer documentation (SCHEMA.md, API.md, etc.) lives in /dev-docs/.
### When to write docs
When completing a feature that introduces or changes **user-facing behaviour**,
create or update the corresponding documentation page under /docs/.
### How to write docs
1. Read /docs/.templates/style-guide.md for terminology, tone, and structure rules
2. Use /docs/.templates/feature-page.md or concept-page.md as your starting template
3. Every page MUST have frontmatter with: title, description, tags
4. Use Dutch language for all content (informal "je/jij")
5. Use Crewli terminology from the style guide — never English equivalents in user docs
### What to include
- What the feature does (2-3 sentences)
- Step-by-step instructions from the user's perspective
- Which roles have access (table format)
- Screenshot placeholders where visual guidance helps: `![Beschrijving](./images/placeholder.png)`
- Links to related pages
### File placement
Match the existing structure under /docs/:
- Organizer features → /docs/organizer/[category]/
- Volunteer features → /docs/volunteer/
- Portal features → /docs/portal/
- General concepts → /docs/guide/
## Git Commit Policy
**After every successful task completion, commit the changes immediately.**
Rules:
1. After all tests pass and build succeeds, stage and commit the changed files
2. Use conventional commit messages: `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `chore:`
3. One commit per logical unit of work (one feature, one bugfix, one refactor)
4. Never bundle unrelated changes in a single commit
5. Never commit with failing tests
6. Do NOT push automatically — only commit locally. The developer will push manually.
Commit message format:
```
feat: short description of the feature
fix: short description of the bug fix
refactor: short description of the refactoring
docs: short description of documentation changes
test: short description of test additions
```
Examples:
- `feat: person tags system with org-level skills and sync endpoint`
- `fix: auth race condition on page refresh`
- `docs: update SCHEMA.md with person_identity_matches table`