_This file contains critical rules and patterns that AI agents must follow when implementing code in this project. Focus on unobvious details that agents might otherwise miss._
- Start all services: `composer dev` (runs server + queue + logs + Vite concurrently)
- SSR mode: `composer dev:ssr`
- Initial setup: `composer setup`
**CI/CD (GitHub Actions):**
-`lint` workflow: Pint + Prettier + ESLint on push/PR to `develop`, `main`, `master`
-`tests` workflow: Pest on PHP 8.4/8.5 matrix with Node 22
- All code must pass both workflows before merging
**Before Committing:**
- PHP: `composer lint` (Pint)
- JS/Vue: `npm run format` then `npm run lint`
- Tests: `composer test`
### Critical Don't-Miss Rules
**Never Do:**
- Never modify `resources/js/components/ui/*` — shadcn-vue auto-generated, use `npx shadcn-vue` to update
- Never hardcode routes in Vue — all URLs come from PHP controller props
- Never use `$guarded = []` — always explicit `$fillable`
- Never use inline `$request->validate()` — always Form Request classes
- Never use Gates/Policies — use custom `authorizeXxx()` protected methods with `abort(404)`
- Never add `RefreshDatabase` in tests — auto-applied via `Pest.php`
**Gotchas:**
- Workspace is session-based (`current_workspace_id`) — not in URL, not route-model-bound
- Nullable enums: use `$model->field?->value` (null-safe) when serializing
- Authorization returns `404` not `403` for workspace boundary violations (intentional)
- Client-facing routes (`/c/*`) use token-based `folder.invitation` middleware, not `auth`
- New business models must add Spatie `LogsActivity` trait + `getActivitylogOptions()` returning `logFillable()->logOnlyDirty()->dontSubmitEmptyLogs()`
- New models with files must add Spatie `InteractsWithMedia` trait and implement `HasMedia`
- Inertia render paths use lowercase subdirectory: `'clients/Index'`, not `'Clients/Index'`
- **WorkspaceUser pivot fields:** The `workspace_user` pivot table has `role` (cast to `WorkspaceUserRole` enum) and `permissions` (cast to array). Both `User::workspaces()` and `Workspace::users()` use `->withPivot('role', 'permissions')`. Access via `$user->workspaces()->first()->pivot->role`. The `WorkspaceUser` model extends `Pivot` (not `Model`) — use `Pivot::query()` for direct queries, not `WorkspaceUser::find()`. When eager-loading workspace members for controllers, always chain `->withPivot('role', 'permissions')` or the pivot data will be silently null.
- **Declaration column name:** The date column is `due_date`, NOT `deadline` — some older docs may reference `deadline` incorrectly
- **Declaration scoping:** There is no `Declaration::workspace()` query scope. Use `Declaration::where('workspace_id', $workspace->id)` instead. The model has `active()`, `archived()`, and `forUser()` scopes.
- **Declaration statuses:** 6 values — `created`, `en_cours`, `en_attente_client`, `mise_en_demeure`, `termine`, `ferme`. The `mise_en_demeure` status is a formal notice branch from `en_attente_client`.