---
project_name: "l'ami fiduciaire"
user_name: 'Saad'
date: '2026-03-08'
sections_completed: ['technology_stack', 'language_rules', 'framework_rules', 'testing_rules', 'code_quality', 'workflow_rules', 'critical_rules']
status: 'complete'
rule_count: 52
optimized_for_llm: true
---

# Project Context for AI Agents

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

---

## Technology Stack & Versions

- **Backend:** PHP ^8.2, Laravel 12
- **Frontend:** Vue 3.5 (`<script setup lang="ts">`), TypeScript 5.2 (strict mode)
- **Bridge:** Inertia.js 2.0 (`@inertiajs/vue3` ^2.3.7)
- **Build:** Vite 7.0, `laravel-vite-plugin` 2.0
- **Styling:** Tailwind CSS 4.1 (`@tailwindcss/vite`), shadcn-vue (reka-ui)
- **Auth:** Laravel Fortify ^1.30
- **Media:** spatie/laravel-medialibrary ^11.21
- **Logging:** spatie/laravel-activitylog ^4.12
- **Enums:** bensampo/laravel-enum ^6.12
- **Routes:** laravel/wayfinder ^0.1.9 (type-safe frontend routes)
- **Icons:** lucide-vue-next
- **CSS Utils:** clsx + tailwind-merge + CVA via `cn()` helper
- **Testing:** Pest 4.4, Laravel Pint (PHP formatting)
- **JS Tooling:** ESLint 9, Prettier 3.4 (with tailwindcss plugin)

## Critical Implementation Rules

### Language-Specific Rules

**TypeScript:**
- Strict mode enabled — never use `any` implicitly (explicit `any` allowed per ESLint config)
- MUST use `import type { ... }` for type-only imports (separate statements, not inline `type` specifier)
- Import order enforced: builtin → external → internal → parent → sibling → index (alphabetical within groups)
- Path alias `@/` maps to `resources/js/` — always use it, never relative paths like `../../`
- `isolatedModules: true` — no `const enum`, no namespace merging

**PHP:**
- Model casts: use `protected function casts(): array` method, NOT the `$casts` property
- Model mass assignment: always use explicit `$fillable`, never `$guarded = []`
- Relationship return types: always add PHPDoc generics `/** @return BelongsTo<Model, $this> */`
- Enums: use `bensampo/laravel-enum` — serialize with `->value`, cast in models with Enum class
- All controller methods must have explicit return type annotations (`Response`, `RedirectResponse`)
- PHP formatting via Laravel Pint — run `composer lint` before committing

### Framework-Specific Rules

**Vue 3 + Composition API:**
- Always use `<script setup lang="ts">` — never Options API
- Define props: `type Props = { ... }` + `defineProps<Props>()`; use `withDefaults()` for defaults
- Export form data types from form components for reuse in page components
- Composables follow `use` prefix convention (`useCurrentUrl`, `useInitials`)

**Inertia.js:**
- ALL URLs must be passed as props from PHP controllers — never hardcode routes in Vue
- Standard forms: `useForm<T>()` with `form.post(props.storeUrl)`
- File uploads: use `<Form>` component with `enctype="multipart/form-data"`, NOT `useForm`
- Navigation: `router.delete()`, `router.get()`, `router.post()` for programmatic actions
- Page titles: always use `<Head title="..." />` component
- Shared props accessed via `usePage()` (e.g., `page.props.auth`)

**Layout System:**
- Pages wrap content in `<AppLayout :breadcrumbs="[...]">` — breadcrumbs are required
- Auth pages use `<AuthLayout>` wrapper
- Settings pages use `settings/Layout.vue` with its own sidebar navigation

**Laravel Controllers:**
- Workspace: resolve from session (`current_workspace_id`), never from URL params
- Authorization: custom `authorizeXxx()` protected methods with `abort(404)`, no Gates/Policies
- Validation: dedicated Form Request classes, never inline `$request->validate()`
- Data shaping: manually build arrays in controllers, no API Resources
- Always pass URLs as props via `route()` helper (e.g., `'showUrl' => route('clients.show', $client)`)
- Single-action controllers use `__invoke()` method
- Enum labels: protected methods on controllers returning label arrays

### Testing Rules

- Use Pest syntax (`test()` closures), never PHPUnit class-based tests
- Feature tests: `RefreshDatabase` is auto-applied via `Pest.php` — don't add it manually
- Test descriptions: lowercase, descriptive strings (`'authenticated users can visit the dashboard'`)
- Assertions: prefer Pest's `expect()` chaining over PHPUnit `assert*()` methods
- Use `route()` helper for URLs in tests, never hardcoded paths
- Feature tests grouped by domain subdirectory mirroring controller structure
- Factory states for user roles: `User::factory()->admin()->create()`
- Run tests: `composer test` (clears config, runs Pint lint check, then `php artisan test`)
- No frontend JS tests configured — testing is PHP-only

### Code Quality & Style Rules

**Prettier (enforced):**
- Semicolons, single quotes, 4-space indentation, 80 char print width
- Tailwind plugin sorts classes in `clsx`, `cn`, `cva` calls
- YAML files use 2-space indentation (override)

**ESLint:**
- `vue/multi-word-component-names` is OFF — single-word names allowed
- `resources/js/components/ui/*` is ignored — never lint or modify shadcn-vue components
- Run `npm run lint` to fix, `npm run format` to format

**File Naming:**
- Vue pages/components: PascalCase (`ClientForm.vue`, `Index.vue`)
- Vue page subdirectories: lowercase by domain (`clients/`, `folders/`, `auth/`)
- UI component subdirectories: kebab-case (`dropdown-menu/`, `input-otp/`)
- TypeScript type files: lowercase (`auth.ts`, `navigation.ts`)
- Composables: `use` prefix camelCase (`useCurrentUrl.ts`)
- Each UI component group has an `index.ts` barrel file

**Code Organization:**
- Pages: `resources/js/pages/{domain}/{Action}.vue`
- Components: `resources/js/components/` (app-level) and `components/ui/` (shadcn-vue)
- Types: `resources/js/types/` with barrel `index.ts`
- Composables: `resources/js/composables/`
- Lib utilities: `resources/js/lib/utils.ts` (`cn()` helper)

### Development Workflow Rules

**Local Development:**
- 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`.

---

## Usage Guidelines

**For AI Agents:**
- Read this file before implementing any code
- Follow ALL rules exactly as documented
- When in doubt, prefer the more restrictive option
- Update this file if new patterns emerge

**For Humans:**
- Keep this file lean and focused on agent needs
- Update when technology stack changes
- Review quarterly for outdated rules
- Remove rules that become obvious over time

Last Updated: 2026-03-24
