Files
crewli/CLAUDE.md
bert.hausmans e5d86776b2 docs: sharpen TECH-TS-PORTAL-TSC + TECH-APP-VITEST priority and scope
Both items currently sit at default priority. Foundation-tooling
commit 3 (ts-reset install) surfaced them but didn't constrain
when they need to close.

Adds explicit S3b-trigger rationale: launching the form-builder
organizer UI in apps/app on top of:
  - 22 unverified pre-existing TypeScript errors in apps/portal,
    AND
  - zero Vitest setup in apps/app
is asymmetric quality, exactly the discipline gap that bites in
post-launch debugging. Both items now flagged "high before S3b
lands" with concrete close-criteria.

TECH-TS-PORTAL-TSC additionally clarifies tiptap node_modules
handling. Phase A check confirmed `skipLibCheck: true` is already
set in both SPAs' single tsconfig.json (no `tsconfig.app.json` /
`tsconfig.node.json` variants exist). Despite that, the 4 tiptap
errors persist because tiptap ships uncompiled `.ts` source files
in `node_modules/.../@tiptap/core/src/`, not `.d.ts` — and
`skipLibCheck` only suppresses checking of `.d.ts` files. Real
fix paths are upstream `@tiptap/*` upgrade (newer majors may ship
`.d.ts` only) or a focused `exclude` glob; flipping skipLibCheck
is a non-fix because it is already on.

TECH-APP-VITEST adds a 6-step setup outline scoped to "harness
exists + one test passes", explicitly excluding comprehensive
test-writing for existing apps/app code. `useImpersonationStore.ts`
called out as a natural early target — it has no runtime test
today and the pending TECH-TS-IMPERSONATION shape-validation work
benefits from coverage.

CLAUDE.md quality-gates list adds a Vitest entry that surfaces
the apps/portal / apps/app asymmetry, with a pointer to
TECH-APP-VITEST.

No code changes. Documentation only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 04:11:16 +02:00

286 lines
12 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)
## 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 `<style scoped>`
with a comment explaining why Vuexy/Vuetify couldn't handle it.
Concrete component rules:
- Tables: `v-data-table-server` with server-side pagination — never client-side for API data
- Cards: `v-card` directly, or `AppCardActions` when collapse/refresh/remove is needed
- Forms in dialogs: `v-dialog` + `v-card` + `v-form` — follow the established dialog pattern
- Detail panels: `v-navigation-drawer` with `temporary` and `location="end"` — follow ShiftDetailPanel pattern
- Date/time pickers: `AppDateTimePicker` from @core — never raw input[type=date]
- Status indicators: `v-chip` with color prop — never custom styled spans
- Loading states: `v-skeleton-loader` — never custom spinners
- Error states: `v-alert` with retry button — never custom error divs
- Empty states: `v-card` with icon + message + action button
- Notifications: `v-snackbar` — never custom toast components
- Page layout: `v-row` + `v-col` with Vuetify breakpoint props — never CSS grid or custom flexbox
**Before ANY frontend task:** read `/dev-docs/VUEXY_COMPONENTS.md` to verify
you are using available components rather than building custom ones.
### 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/api/use[Module].ts`
- `src/lib/axios.ts` is the canonical axios instance — the only place axios is imported directly
- Use Pinia stores for cross-component state — no prop drilling
### TypeScript
- No `any` types — ever. Every variable, prop, emit, return type, ref, computed must be fully typed
- Types first: create `src/types/[module].ts` before composables or components
- Mirror backend PHP Enums as `as const` objects in `src/types/`:
```typescript
export const ShiftStatus = { PENDING: 'pending', APPROVED: 'approved' } as const
export type ShiftStatus = typeof ShiftStatus[keyof typeof ShiftStatus]
```
### Forms
- VeeValidate for form state + Zod for schema validation — always together
- Zod schemas must mirror the backend Form Request rules (field names, required/optional, types)
- No inline validation logic in components
### 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
- **Three states per page:** every data-driven view must handle loading (skeleton/spinner), error (`v-alert` with retry button), and empty (helpful message with action button)
- Use Vuetify responsive props (`cols`, `sm`, `md`, `lg`) — no fixed pixel widths
- Custom CSS via `<style scoped>` only as last resort when no Vuetify utility exists
## 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`)
- Never: TypeScript `any` type (use proper types, generics, or `unknown` with type guards)
- Never: import axios directly in a component (use `src/lib/axios.ts` via a composable)
## 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. TypeScript types in `src/types/[module].ts`
11. API composable in `src/composables/api/use[Module].ts`
12. Pinia store in `src/stores/use[Module]Store.ts` (only if cross-component state is needed)
13. Vue page component in `src/pages/[module]/`
14. 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`