docs(claude) : refactorise CLAUDE.md en index + extrait les regles dans .claude/rules/

- CLAUDE.md devient un index concis : contexte, stack, regles absolues
  numerotees, pointeurs vers les fichiers de regles detaillees via
  references @.claude/rules/*.md
- Les conventions detaillees (architecture, backend, frontend, testing,
  naming, git, workflow) sont extraites dans .claude/rules/ pour rester
  chargees a la demande sans gonfler le context du CLAUDE.md principal
- Ajoute la regle absolue "Ne jamais mentionner Claude/IA dans commits
  ou PR" (point 10) pour garder l'historique git signe par l'utilisateur
This commit is contained in:
Matthieu
2026-04-23 11:02:04 +02:00
parent b1255bb57a
commit 711774425b
8 changed files with 398 additions and 298 deletions

View File

@@ -0,0 +1,66 @@
# Architecture — Modular Monolith DDD
## Principe fondamental
Le **backend est la source de verite unique**. Il dicte :
- Quels modules sont actifs (`config/modules.php`)
- L'organisation de la sidebar (`config/sidebar.php`, decouplee des modules)
Le frontend scanne `modules/*/` comme layers Nuxt et consomme l'API pour la navigation. Il ne decide rien.
## Endpoints API cles
- `GET /api/version` (public) — version de l'app
- `GET /api/modules` (public) — IDs des modules actifs
- `GET /api/sidebar` (public) — sections filtrees par modules actifs + `disabledRoutes` (items dont le module owner est inactif)
- `GET /api/me` (auth) — user courant
## Arborescence minimale (detail complet : @README.md)
```
src/
Shared/ # Noyau technique partage (Domain/, Application/Bus/, Infrastructure/ApiPlatform/)
Module/
Core/ # Module obligatoire (auth, users)
CoreModule.php # ID, LABEL, REQUIRED, permissions()
Domain/ Application/ Infrastructure/
Commercial/ # Exemple d'autre module
frontend/
app/ # Shell (layouts, middlewares)
shared/ # Code inter-modules (composables, stores, utils)
modules/ # Layers Nuxt auto-detectes
core/ commercial/
```
## Declaration d'un module
Chaque module expose un `*Module.php` avec :
- `ID` (snake_case, ex: `commercial`, `gestion_rh`)
- `LABEL`
- `REQUIRED` (bool)
- Methode statique `permissions()` retournant les RBAC du module
## Activer / desactiver un module
Editer uniquement `config/modules.php` (commenter la ligne). Cascade automatique :
1. `/api/modules` ne retourne plus l'ID
2. `/api/sidebar` filtre les items `module: '<id>'` et supprime les sections vides
3. Middleware front `modules.global.ts` redirige toute navigation vers une route desactivee
4. Le code reste dans le bundle (layer auto-detecte) → reactivation instantanee sans rebuild
## Reorganiser la sidebar
Editer uniquement `config/sidebar.php`. Le code des modules n'est pas touche — seule la place des items change. Chaque item reference son module owner via la cle `module`.
## Communication inter-modules
**Interdit** : import direct d'une classe d'un autre module.
**Autorise** :
- Via `Shared/Domain/Contract/` (interfaces : `UserResolverInterface`, `TenantAwareInterface`...)
- Via domain events (`Shared/Domain/Event/DomainEventInterface`)
## Migrations
- **Par defaut** : `src/Module/<Module>/Infrastructure/Doctrine/Migrations/` (namespace modulaire)
- **Exception** : les migrations d'initialisation critiques (setup user, RBAC, seed de base) vivent au namespace racine `DoctrineMigrations` dans `migrations/`.
- Raison : avec plusieurs `migrations_paths`, Doctrine Migrations 3.x trie par FQCN alphabetique et non par version timestamp → ordre incorrect entre namespaces sur base vide.
- A supprimer quand un `MigrationsComparator` custom ou un upgrade Doctrine resoudra le tri.

55
.claude/rules/backend.md Normal file
View File

@@ -0,0 +1,55 @@
# Backend — Regles PHP / Symfony / API Platform
## Structure de fichier
- Toujours `declare(strict_types=1);` en tete de tout fichier PHP
- PHP CS Fixer : regles Symfony + PSR-12 + strict types (commande : `make php-cs-fixer-allow-risky`)
- Commentaires (docblock, inline, bloc) **en francais** ; code (classes, methodes, variables) en anglais
## API Platform (pas de controllers)
- Toujours utiliser `#[ApiResource]` + Providers + Processors — pas de controllers Symfony classiques
- Routes prefixees `/api` (via `config/routes/api_platform.yaml`)
- Le login `/login_check` est **hors** prefix `/api` (nginx reecrit `REQUEST_URI` vers `/login_check`)
- **Exception** : si tu dois creer un controller custom sous `/api/`, mettre `priority: 1` sur `#[Route]` pour eviter le conflit avec API Platform `{id}`
## Repositories
- Interface : `*RepositoryInterface` dans `Domain/Repository/`
- Implementation Doctrine : `Doctrine*Repository` dans `Infrastructure/Doctrine/`
- Le domaine garde les attributs ORM (approche pragmatique)
## RBAC (permissions)
Format obligatoire : `module.resource[.subresource].action` en snake_case.
- Exemples : `core.users.view`, `commercial.clients.contacts.edit`, `core.audit_log.view`
- Declarees via la methode statique `permissions()` des `*Module.php`
- Synchronisation : `app:sync-permissions`
- Verification API Platform : `is_granted('module.resource.action')`
- Verification front : `usePermissions()`
## Roles
- Hierarchie dans `config/packages/security.yaml` : `ROLE_ADMIN`, `ROLE_USER`
- Le role ne remplace pas la permission RBAC — deux niveaux complementaires
## Audit (obligatoire)
- Toute entite metier (nouvelle ou existante) : `#[Auditable]` (de `Shared/Domain/Attribute/`)
- Champs sensibles (password, token, secret) : `#[AuditIgnore]`
- Audit ManyToMany : trace automatiquement `{fieldName: {added: [ids], removed: [ids]}}` — aucune action supplementaire
- Spec complete : @doc/audit-log.md
## Serialization
Pour embarquer une relation dans le JSON (au lieu d'un IRI Hydra), ajouter le groupe du parent sur les proprietes de l'entite cible.
Exemple : pour qu'`User.profile` soit embarque au lieu d'un lien IRI sous le groupe `user:read`, annoter `Profile.$firstName` avec `#[Groups(['user:read'])]`.
## Upload de fichiers
- Valider cote serveur avec `$file->getMimeType()`**jamais** `getClientMimeType()` (spoofable par le client)
## PostgreSQL
- Noms de colonnes toujours en **minuscules** dans le SQL brut (commun a tous les projets MALIO)

69
.claude/rules/frontend.md Normal file
View File

@@ -0,0 +1,69 @@
# Frontend — Regles Nuxt 4 / Vue 3 / @malio/layer-ui
## Base
- TypeScript strict
- 4 espaces d'indentation
- Commentaires (JSDoc, inline, bloc) **en francais** ; code (variables, types) en anglais
- Chaque module front = un layer Nuxt auto-detecte (`frontend/modules/*/nuxt.config.ts` minimal)
## Appels API
- Toujours `useApi()` — jamais `$fetch`, `ofetch`, `axios` en direct
- `useApi()` gere : cookies JWT, erreurs, toasts i18n, parsing Hydra
## Stores (Pinia)
- `useAuthStore` pour l'authentification
- `useUiStore` pour l'etat UI global (sidebar, modales, etc.)
- Composables avec state singleton (refs module-level) : exposer une fonction `reset*()` et la rappeler au logout (ex: `useSidebar().resetSidebar()`)
## Middlewares globaux
- `auth.global.ts` protege les routes + charge la sidebar apres login
- `modules.global.ts` redirige si la route demandee est dans `disabledRoutes`
## i18n et sidebar
- Labels de sidebar = cles i18n `sidebar.<module>.*`, jamais du texte brut
- Le layout `default.vue` applique `t()` sur les labels retournes par `/api/sidebar`
- Traductions dans `frontend/i18n/locales/`
## Composants formulaires — @malio/layer-ui obligatoire
Tout champ de formulaire / filtre doit utiliser les composants `Malio*` plutot que `<input>` / `<select>` bruts :
- `MalioInputText`, `MalioInputNumber`, `MalioInputAmount`, `MalioInputPassword`, `MalioInputTextArea`
- `MalioSelect`, `MalioSelectCheckbox`, `MalioCheckbox`, `MalioRadioButton`
- `MalioInputUpload`, `MalioTime`
- `MalioButton`, `MalioButtonIcon`
**Exceptions autorisees** (commenter un `// TODO` pour migrer quand la lib couvrira le cas) :
1. Type non couvert : `datetime-local`, `date`, color picker, file drag & drop
2. Bug connu bloquant (ex: `MalioSelect` avec options string) — documenter le bug en commentaire
Toute autre exception requiert validation avant merge.
## Tableaux de donnees — MalioDataTable obligatoire
Tout affichage LISTE tabulaire (donnees metier paginees, CRUD admin) doit passer par `MalioDataTable` :
- Pagination integree
- Slots `#header-*` pour filtres, `#cell-*` pour rendu custom
- Pas de `<table>` brut avec pagination custom
**Exception** : tableaux purement presentationnels non paginables (diff field/old/new, grille de comparaison, matrice RBAC d'admin, etc.) peuvent rester en `<table>` HTML brut.
## Etat des tableaux — pas de persistance URL
**Interdit** de persister l'etat d'un tableau (filtres, pagination, tri par colonne, selection, ligne active, scroll) dans la query string ou de le reinjecter depuis `route.query` au montage.
- L'etat vit uniquement dans le composant (`reactive`, `ref` locales)
- Seuls les deep links "de navigation metier" (ex: ouvrir un detail precis `/users/42`) sont dans l'URL
- Exceptions autorisees **sur demande explicite** de l'utilisateur
## Interdits
- `modules-loader.ts`, `.module.ts` — le scan des layers est automatique
- Hardcode de la sidebar cote front — elle vient de `/api/sidebar`
- Edition manuelle de `extends` dans `frontend/nuxt.config.ts` — les layers sont scannes
- Import d'un module front depuis un autre module — passer par `frontend/shared/`

39
.claude/rules/git.md Normal file
View File

@@ -0,0 +1,39 @@
# Git
## Commits
Format : `<type>(<scope optionnel>) : <message>`
**Espaces obligatoires** autour du `:`.
Types autorises (minuscules uniquement) : `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`
Exemples :
- `feat : add login page`
- `fix(auth) : prevent null token crash`
- `chore : bump version to v1.2.3`
## Regles de commit
- **Jamais commit sans demande explicite** de l'utilisateur
- **Jamais force push** sans confirmation
- **Jamais modifier la config git**
- **Jamais mentionner Claude, Anthropic ou une IA** dans un commit (message, titre, body, footer, trailer) ou une PR (titre, description). Concretement, aucun des elements suivants ne doit apparaitre :
- Trailer `Co-Authored-By: Claude <...>` / `Co-Authored-By: Anthropic`
- Footer type `Generated with Claude Code`, `Generated by AI`, etc.
- Emoji robot `🤖` ou similaire en tete/fin de message
- Toute phrase attribuant la paternite du changement a une IA
Les commits sont signes **uniquement** par l'utilisateur. Si un hook ou un template ajoute ces elements automatiquement, les retirer manuellement avant push.
## Versioning & tags
La version de l'app est dans `config/version.yaml` (parametre `app.version`).
Workflow de release :
1. Bump `config/version.yaml` a la nouvelle version
2. Commit dedie : `chore : bump version to v<X.Y.Z>`
3. Tag : `git tag v<X.Y.Z>`
4. Push : `git push origin develop --tags`
**A chaque creation de tag**, toujours mettre a jour `config/version.yaml` avec la meme version — sinon l'app ne connait pas sa propre version.
CI Gitea automatise le bump sur push `develop` (cf. `.gitea/workflows/`), mais un tag manuel reste possible.

18
.claude/rules/naming.md Normal file
View File

@@ -0,0 +1,18 @@
# Nommage
| Element | Convention | Exemple |
|---------|-----------|---------|
| Module back | PascalCase | `Module/Commercial/` |
| Module front | kebab-case | `modules/commercial/` |
| Module ID (dans code/config) | snake_case | `commercial`, `gestion_rh` |
| Entity Doctrine | PascalCase singulier | `User.php` |
| Repository interface | `*RepositoryInterface` | `UserRepositoryInterface.php` |
| Repository impl Doctrine | `Doctrine*Repository` | `DoctrineUserRepository.php` |
| DTO | `*Output` / `*Input` | `UserOutput.php` |
| API Platform Resource | classe dans `Infrastructure/ApiPlatform/Resource/` | `UserResource.php` |
| API Platform Provider | `*Provider` | `MeProvider.php` |
| API Platform Processor | `*Processor` | `UserPasswordHasherProcessor.php` |
| Module declaration back | `*Module.php` | `CommercialModule.php` |
| Composable front | `use*` | `useSidebar.ts` |
| Cles i18n sidebar | `sidebar.<module>.*` | `sidebar.commercial.overview` |
| Permission RBAC | `module.resource[.subresource].action` | `core.users.view`, `commercial.clients.contacts.edit` |

36
.claude/rules/testing.md Normal file
View File

@@ -0,0 +1,36 @@
# Tests
## Trois suites
| Suite | Commande | Outil | Where |
|---|---|---|---|
| Back | `make test` | PHPUnit | Container PHP ; fixtures dediees sous `tests/Fixtures/` |
| Front unitaire | `make nuxt-test` | Vitest (happy-dom) | Container Node ; <30s ; composables/utils/stores |
| Front E2E | `make test-e2e` | Playwright | **Host** (pas container, navigateur reel requis) |
Bootstrap E2E (une fois par poste de dev) : `make install-e2e-deps` (Chromium + libs systeme, sudo).
Re-run uniquement si `@playwright/test` upgrade majeur.
Workflow E2E : `make start && make seed-e2e && make dev-nuxt` (terminal 1), `make test-e2e` (terminal 2).
UI interactive pour debug : `make test-e2e-ui`.
## Regle d'or E2E
**Un nouveau test E2E ne s'ajoute QUE si un bug critique est passe en prod.**
Sinon, la bonne place est un test unitaire Vitest :
- Plus rapide
- Plus stable
- Moins de faux positifs
Pour etendre la couverture RBAC, etendre un **persona existant** dans `frontend/tests/e2e/_fixtures/personas.ts` plutot que de creer un nouveau test.
## Matrice RBAC — 3 endroits obligatoires
Ajouter/modifier une permission testable = toucher les 3 miroirs (sinon drift garanti) :
1. `config/sidebar.php` — attacher `permission` au bon item
2. `frontend/tests/e2e/_fixtures/personas.ts` — ajuster `permissions` + `expectedAdminLinks` d'un persona existant
3. `src/Module/Core/Infrastructure/Console/SeedE2ECommand.php` — miroir back du meme persona
Tout changement sur l'un des trois sans les deux autres = test casse ou faux positif.

66
.claude/rules/workflow.md Normal file
View File

@@ -0,0 +1,66 @@
# Workflow
## Langue
- **UI** : francais (tout ce qui est visible utilisateur)
- **Communication avec l'utilisateur** : francais
- **Code** (noms de classes, methodes, variables, types) : anglais
- **Commentaires** (PHP, TS, Vue — docblock, inline, bloc) : **francais**. Objectif : faciliter la relecture par l'equipe FR sans polluer l'API publique du code
## Delegation Codex
Pour les taches mecaniques (generation de tests boilerplate, renommages massifs, refacto repetitif, scaffolding), **deleguer a Codex** via le plugin `codex` (skill `codex:rescue`).
- **Codex** = junior rapide et pas cher → executions mecaniques
- **Claude** = senior qui verifie et reflechit → design, review, decisions
Ratio qualite/credits optimal : Claude conserve la reflexion et la validation, Codex avale le boilerplate.
## Avant d'implementer un ticket
Les specs fonctionnelles vivent sous `docs/{rbac,sites,modules}/ticket-*-spec.md`.
Avant de coder :
1. Lire la spec correspondante en entier (elles sont longues mais exhaustives)
2. Ne pas inventer de detail non specifie — demander a l'utilisateur ou citer explicitement la section manquante
3. Si la spec contredit le code existant, poser la question avant de choisir
## Verification avant "c'est fini"
Ne jamais declarer une tache terminee sans avoir lance les verifications applicables :
| Ce qui a bouge | Commande a lancer |
|---|---|
| Fichiers PHP | `make test` + `make php-cs-fixer-allow-risky` |
| Fichiers front (Vue/TS) | `make nuxt-test` |
| Migrations | `make migration-migrate` (sur BDD fraiche ideal : `make db-reset`) |
| Sidebar / permissions | Verifier que les 3 miroirs RBAC sont alignes (cf. @.claude/rules/testing.md) |
| UI visible | Demarrer `make dev-nuxt` et verifier le golden path dans le navigateur |
Si une verification echoue ou ne peut pas etre lancee (ex : container pas demarre), le dire explicitement plutot que d'annoncer "fini".
## Time tracking Lesstime
Au demarrage de toute tache de dev sur Coltura, creer une time entry via l'API Lesstime (cf. `~/.claude/CLAUDE.md` pour la procedure complete).
- Projet : `/api/projects/6` (COLTURA)
- Tags : choisir selon le type (Backend `3`, Frontend `2`, Infra `5`, UI/UX `4`, Maintenance `6`, Gestion projet `9`, etc.)
## Fix `make cache-clear` (permissions `var/`)
Si `make cache-clear` echoue sur les permissions de `var/` :
```bash
docker exec -t -u root php-coltura-fpm chown -R www-data:www-data /var/www/html/var
docker exec -t -u www-data php-coltura-fpm php bin/console cache:clear
```
A terme : integrer ce fix dans le `makefile` lui-meme.
## Docker — references utiles
- Container PHP : `php-coltura-fpm`
- Container Nginx : `nginx-coltura` (port 8083)
- Container DB : PostgreSQL port **5437** (interne et externe)
- Config dev : `infra/dev/.env.docker` (override local : `infra/dev/.env.docker.local`)
- Config prod : `infra/prod/` (Dockerfile multi-stage, `docker-compose.prod.yml`)
- Apres modif nginx : `docker restart nginx-coltura`

347
CLAUDE.md
View File

@@ -1,317 +1,68 @@
# Coltura
CRM/ERP. Monorepo Symfony 8 (API Platform 4) + Nuxt 4. **Architecture Modular Monolith DDD.**
## Contexte
CRM/ERP en architecture **modular monolith DDD**. Le backend est la source de verite unique (modules actifs, sidebar). Le frontend scanne `frontend/modules/*/` comme layers Nuxt et consomme l'API pour la navigation. Multi-tenant : chaque module est activable/desactivable.
## Architecture Modulaire
Le projet suit une architecture **modular monolith** pilotee par le backend : chaque module metier est un bounded context autonome, activable/desactivable par tenant. Le module `Core` est obligatoire.
**Principe fondamental : le backend est la source de verite unique.**
- Le backend dicte quels modules sont actifs (`config/modules.php`).
- Le backend dicte l'organisation de la sidebar (`config/sidebar.php`), decouplee des modules eux-memes.
- Le frontend ne connait rien : il scanne automatiquement les modules comme layers Nuxt et demande la sidebar au backend.
### Backend — Organisation par module
```
src/
Kernel.php
Shared/ # Noyau technique partage
Domain/
ValueObject/ # VO de base (Email...)
Event/ # DomainEventInterface
Contract/ # Interfaces inter-modules (UserResolverInterface, TenantAwareInterface)
Application/
Bus/ # CommandBusInterface, QueryBusInterface (interfaces seules)
Infrastructure/
ApiPlatform/
Resource/ # AppVersion, ModulesResource, SidebarResource
State/ # AppVersionProvider, ModulesProvider, SidebarProvider
Module/
Core/ # Module obligatoire (auth, users)
CoreModule.php # Declaration (ID, LABEL, REQUIRED)
Domain/
Entity/ # Entites Doctrine + API Platform (User)
Repository/ # Interfaces repositories (UserRepositoryInterface)
Event/ # Domain events (UserCreated)
Application/
DTO/ # UserOutput
Infrastructure/
Doctrine/ # DoctrineUserRepository, Migrations/
ApiPlatform/
State/
Provider/ # MeProvider
Processor/ # UserPasswordHasherProcessor
Console/ # CreateUserCommand
DataFixtures/ # AppFixtures
Commercial/ # Autre module (exemple)
CommercialModule.php
config/
modules.php # Liste des modules actifs (source de verite activation)
sidebar.php # Structure de la sidebar (source de verite navigation)
version.yaml
jwt/ # Cles JWT
packages/ # Config Symfony
migrations/ # Anciennes migrations Doctrine
infra/dev/ # Docker dev
infra/prod/ # Docker prod (multi-stage)
```
### Frontend — Organisation modulaire (auto-detectee)
```
frontend/
app/ # Shell applicatif
layouts/ # default.vue, auth.vue
middleware/ # auth.global.ts, modules.global.ts
shared/ # Code partage (hors modules)
composables/ # useApi, useAppVersion, useSidebar
components/ui/ # AppTopNav, ...
stores/ # auth, ui
services/ # auth
types/ # SidebarSection, SidebarItem, UserData
utils/ # api (Hydra)
modules/ # Modules auto-detectes comme layers Nuxt
core/
nuxt.config.ts # Marqueur layer (vide)
pages/ # index.vue, login.vue
commercial/
nuxt.config.ts
pages/ # commercial.vue
app.vue # Composant racine
nuxt.config.ts # Scanne modules/*/ automatiquement
i18n/locales/ # Traductions (cles sidebar.*, etc.)
assets/ # CSS, images
public/ # Fichiers statiques
```
### Endpoints API cles
- `GET /api/version` (public) — version de l'app
- `GET /api/modules` (public) — liste des IDs de modules actifs
- `GET /api/sidebar` (public) — sections de la sidebar + `disabledRoutes`
- Filtre automatiquement les items dont le `module` owner n'est pas actif
- Les sections vides apres filtrage sont supprimees
- `disabledRoutes` = `to` des items filtres (utilise par le middleware front)
- `GET /api/me` (auth) — user courant
### Flux d'activation/desactivation d'un module
Pour activer/desactiver un module, tu touches **uniquement** `config/modules.php` :
```php
return [
\App\Module\Core\CoreModule::class,
// \App\Module\Commercial\CommercialModule::class, // commente = desactive
];
```
Cascade automatique :
1. `GET /api/modules` ne retourne plus `commercial`
2. `GET /api/sidebar` filtre les items `module: 'commercial'` → section "Commercial" disparait, ses routes passent dans `disabledRoutes`
3. Frontend : sidebar se met a jour, middleware `modules.global.ts` redirige toute navigation vers `/commercial` ou `/commercial/*`
4. Le code du module reste dans le bundle Nuxt (layer auto-detecte) → reactivation instantanee sans rebuild
### Reorganiser la sidebar sans toucher aux modules
Pour deplacer un item (ex: "Commandes fournisseurs") d'une section a une autre, tu edites juste `config/sidebar.php` :
```php
// Avant : sous Commercial
['label' => 'sidebar.commercial.suppliers', 'to' => '/commercial/suppliers', 'module' => 'commercial'],
// Apres : sous Production (l'item reste "owned" par Commercial, seule sa place change)
[
'label' => 'sidebar.production.section',
'items' => [
['label' => 'sidebar.commercial.suppliers', 'to' => '/commercial/suppliers', 'module' => 'commercial'],
],
],
```
Le code du module Commercial n'est pas touche.
### Regles d'architecture
**Backend :**
- Le domaine (`Domain/`) peut garder les attributs ORM (approche pragmatique) mais les repositories sont des interfaces
- Communication inter-modules par `Shared/Domain/Contract/` ou domain events — jamais d'import direct entre modules
- Chaque module declare un `*Module.php` avec `ID`, `LABEL`, `REQUIRED`
- `config/modules.php` = seule source de verite pour l'activation
- `config/sidebar.php` = seule source de verite pour l'organisation de la sidebar (chaque item reference son module owner via la cle `module`)
- Migrations par module dans `src/Module/{Module}/Infrastructure/Doctrine/Migrations/`
- **Exception connue** : avec plusieurs `migrations_paths` configures, Doctrine Migrations 3.x trie les migrations par FQCN alphabetique et non par version timestamp → ordre d'execution incorrect entre namespaces sur une base vide. Tant que ce n'est pas resolu (via un `MigrationsComparator` custom ou un upgrade), les migrations d'initialisation critiques (setup user, RBAC, etc.) vivent au namespace racine `DoctrineMigrations` dans `migrations/`. Le namespace modulaire reste configure pour les futures migrations applicatives (qui dependent d'un schema deja cree).
**Frontend :**
- Chaque module est un layer Nuxt auto-detecte (`modules/*/nuxt.config.ts` minimal)
- Un module front ne doit pas importer depuis un autre module — utiliser `shared/`
- `useSidebar()` fetch `/api/sidebar` et expose `sections`, `disabledRoutes`, `isRouteDisabled()`
- Le layout `default.vue` itere sur les sections retournees par l'API, applique `t()` sur les labels
- Middleware `auth.global.ts` charge la sidebar apres authentification
- Middleware `modules.global.ts` redirige si la route demandee est dans `disabledRoutes`
- Les composables avec state singleton (refs module-level) doivent exposer une fonction `reset*()` et etre reinitialises au logout (ex: `useSidebar().resetSidebar()`)
- **Interdit** : `.module.ts`, `modules-loader.ts`, hardcode de la sidebar, edition manuelle de `extends` dans `nuxt.config.ts`
Doc humaine : @README.md — Spec audit : @doc/audit-log.md
## Stack
- Backend : PHP 8.4, Symfony 8, API Platform 4, Doctrine ORM, PostgreSQL 16 (port 5437)
- Frontend : Nuxt 4 (SPA), Vue 3, Pinia, Tailwind, @malio/layer-ui, @nuxtjs/i18n
- Auth : JWT HTTP-only cookie (Lexik), login a `/login_check`
- Containers : `php-coltura-fpm`, `nginx-coltura` (port 8083), dev Nuxt port **3004**
- **Backend** : PHP 8.4, Symfony 8.0, API Platform 4, Doctrine ORM, PostgreSQL 16
- **Frontend** : Nuxt 4 (SSR off / SPA), Vue 3, Pinia, Tailwind CSS, @malio/layer-ui, nuxt-toast, @nuxtjs/i18n, @nuxt/icon
- **Auth** : JWT HTTP-only cookie (lexik/jwt-authentication-bundle), login a `/login_check`, cookie `BEARER`
- **Docker** : PHP-FPM + Node 24, Nginx (port 8083), PostgreSQL (port 5437)
## Regles ABSOLUES
## Commandes
```bash
make start # Demarrer les containers
make stop # Arreter les containers
make restart # Redemarrer les containers
make install # Install complet (composer, migrations, fixtures, build Nuxt)
make reset # Tout supprimer et reinstaller (supprime la BDD)
make dev-nuxt # Dev server Nuxt (hot reload, port 3004)
make shell # Shell dans le container PHP
make shell-root # Shell root dans le container PHP
make cache-clear # Vider le cache Symfony
make migration-migrate # Lancer les migrations
make fixtures # Charger les fixtures
make db-reset # Reset BDD + migrations + fixtures
make test # PHPUnit
make nuxt-test # Vitest (tests unitaires frontend)
make php-cs-fixer-allow-risky # Fix code style PHP
make logs-dev # Tail logs Symfony
```
Si `make cache-clear` echoue pour cause de permissions sur `var/` :
```bash
docker exec -t -u root php-coltura-fpm chown -R www-data:www-data /var/www/html/var
docker exec -t -u www-data php-coltura-fpm php bin/console cache:clear
```
### Tests E2E (Playwright)
La suite E2E vit dans `frontend/tests/e2e/` (personas, Page Objects, specs). Elle tourne sur l'host (Playwright a besoin d'un navigateur reel, pas dans un container PHP).
**Bootstrap one-time par poste de dev :**
```bash
make install-e2e-deps # Telecharge Chromium + installe les libs systeme (sudo)
```
A relancer uniquement si `@playwright/test` upgrade de version majeure.
**Workflow nominal :**
```bash
# Terminal 1
make start # Containers up
make seed-e2e # Cree les 6 personas e2e.* (idempotent)
make dev-nuxt # Dev server sur :3004 (garder ouvert)
# Terminal 2
make test-e2e # Run la suite
# ou
make test-e2e-ui # UI interactive pour debug
```
**Regle d'or E2E** (a respecter pour garder la suite maintenable sur la duree) : un nouveau test E2E ne s'ajoute QUE quand un bug critique est passe en prod. Sinon, la bonne place est un test unitaire Vitest (plus rapide, plus stable). Cf. `frontend/tests/e2e/_fixtures/personas.ts` pour etendre la matrice RBAC via un persona existant plutot que d'ajouter un test.
**Etendre la matrice RBAC** : pour ajouter une permission testable, toucher les 3 endroits (sinon drift garanti) :
1. `config/sidebar.php` — attacher `permission` au bon item
2. `frontend/tests/e2e/_fixtures/personas.ts` — ajuster `permissions` + `expectedAdminLinks` d'un persona existant (ne pas creer de nouveau persona par reflexe)
3. `src/Module/Core/Infrastructure/Console/SeedE2ECommand.php` — miroir back du meme persona
1. **Ne jamais importer d'un module a un autre** — passer par `Shared/Domain/Contract/` (interfaces) ou domain events.
2. **Toujours `declare(strict_types=1);`** en tete de tout fichier PHP.
3. **Toujours annoter `#[Auditable]`** les entites metier ; `#[AuditIgnore]` sur champs sensibles (password, token, secret).
4. **Toujours utiliser `useApi()`** pour les appels API cote front — jamais `$fetch` / `ofetch` direct.
5. **Toujours utiliser les composants `Malio*`** (@malio/layer-ui) pour formulaires/filtres ; `MalioDataTable` pour listes paginees.
6. **Jamais persister l'etat de tableau dans l'URL** (filtres, pagination, tri, selection) — state local uniquement.
7. **Jamais ajouter un test E2E** sauf si un bug critique est passe en prod ; preferer un test Vitest.
8. **Toujours mettre a jour les 3 sources RBAC ensemble** : `config/sidebar.php`, `frontend/tests/e2e/_fixtures/personas.ts`, `src/Module/Core/Infrastructure/Console/SeedE2ECommand.php`.
9. **Jamais commit sans demande explicite** de l'utilisateur ; jamais force push sans confirmation.
10. **Jamais mentionner Claude, Anthropic ou une IA** dans un commit (message, titre, body, footer, trailer) ou une PR (titre, description). Pas de `Co-Authored-By: Claude`, pas de `Generated with Claude Code`, pas de `🤖`, pas d'emoji robot, rien. Les commits sont signes par l'utilisateur uniquement.
11. **Migrations d'initialisation au namespace racine** `DoctrineMigrations` dans `migrations/` (setup user, RBAC, seed de base). Les migrations modulaires (`src/Module/*/Infrastructure/Doctrine/Migrations/`) sont reservees aux evolutions post-schema (ajout de colonnes, index) — cf. @.claude/rules/architecture.md pour la raison.
## Conventions
@.claude/rules/architecture.md
@.claude/rules/backend.md
@.claude/rules/frontend.md
@.claude/rules/testing.md
@.claude/rules/naming.md
@.claude/rules/git.md
@.claude/rules/workflow.md
### Commits
## Commandes (liste complete dans @README.md)
Format : `<type>(<scope optionnel>) : <message>` (espace avant et apres `:`)
- Demarrer : `make start`
- Dev front (hot reload) : `make dev-nuxt` (port 3004)
- Shell PHP : `make shell`
- Tests back : `make test`
- Tests front unitaires : `make nuxt-test`
- Tests E2E : `make test-e2e` (prerequis : `make seed-e2e && make dev-nuxt`)
- Reset BDD : `make db-reset`
- Lint PHP : `make php-cs-fixer-allow-risky`
Types autorises (minuscules) : `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`
## Activer / desactiver un module
Exemples : `feat : add login page`, `fix(auth) : prevent null token crash`
Editer uniquement `config/modules.php` (commenter la ligne). Cascade automatique via `/api/modules`, `/api/sidebar`, middleware front `modules.global.ts`. Details : @.claude/rules/architecture.md
### Tags & Versioning
## A NE PAS faire
- La version de l'app est dans `config/version.yaml` (parametre `app.version`)
- A chaque creation de tag, **toujours** mettre a jour `config/version.yaml` avec la meme version
- Faire un commit separe de bump : `chore : bump version to v<X.Y.Z>`
- Puis creer le tag et pusher : `git tag v<X.Y.Z> && git push origin develop --tags`
- Pas de controller Symfony custom sous `/api/` sans `priority: 1` sur `#[Route]` (conflit API Platform `{id}`).
- Pas de `getClientMimeType()` pour valider un upload — utiliser `$file->getMimeType()` (serveur).
- Pas de hardcode de la sidebar cote front, pas de `modules-loader.ts` ni `.module.ts`.
- Pas d'edition manuelle de `extends` dans `frontend/nuxt.config.ts` — les layers sont scannes automatiquement.
- Pas de commentaires en anglais dans le code — **commentaires en francais**, code (noms) en anglais.
- Pas d'`<input>` / `<select>` / `<table>` bruts quand un composant `@malio/layer-ui` existe (exceptions documentees dans @.claude/rules/frontend.md).
- Pas de modification de la config git.
### Nommage
## Skills projet
| Element | Convention | Exemple |
|---------|-----------|---------|
| Module back | PascalCase | `Module/Commercial/` |
| Module front | kebab-case | `modules/commercial/` |
| Module ID | snake_case | `commercial`, `gestion_rh` |
| Entity | PascalCase singulier | `User.php` |
| Repository interface | `*RepositoryInterface` | `UserRepositoryInterface.php` |
| Repository impl | `Doctrine*Repository` | `DoctrineUserRepository.php` |
| DTO | `*Output` / `*Input` | `UserOutput.php` |
| API Resource | classe dans `Infrastructure/ApiPlatform/Resource/` | `UserResource.php` |
| Provider | `*Provider` | `MeProvider.php` |
| Processor | `*Processor` | `UserPasswordHasherProcessor.php` |
| Module declaration back | `*Module.php` | `CommercialModule.php` |
| Composable front | `use*` | `useSidebar.ts` |
| Cles i18n sidebar | `sidebar.<module>.*` | `sidebar.commercial.overview` |
- `create-module` — scaffolder un nouveau module back+front et wirer la sidebar.
### Backend
## Credentials (dev)
- Toujours `declare(strict_types=1)` en haut des fichiers PHP
- **Commentaires en francais** : tout commentaire PHP (docblock, inline, bloc) doit etre redige en francais. Le code (noms de classes, methodes, variables) reste en anglais. Objectif : faciliter la relecture par l'equipe FR sans polluer l'API publique du code.
- API Platform : utiliser ApiResource, Providers, Processors — pas de controllers
- Routes API prefixees `/api` (via `config/routes/api_platform.yaml`)
- Le login (`/login_check`) est hors prefix `/api`, nginx reecrit `REQUEST_URI` vers `/login_check`
- PHP CS Fixer : regles Symfony + PSR-12 + strict types
- Roles : `ROLE_ADMIN`, `ROLE_USER` — hierarchie dans `security.yaml`
- **Permissions RBAC** : format obligatoire `module.resource[.subresource].action` en snake_case, ex : `core.users.view`, `commercial.clients.contacts.edit`. Declarees via la methode statique `permissions()` des `*Module.php`, synchronisees par la commande `app:sync-permissions`. Verification via `is_granted('module.resource.action')` cote API Platform et `usePermissions()` cote front.
- PostgreSQL : noms de colonnes toujours en **minuscules** dans le SQL brut
- Controllers custom sous `/api/` : ajouter `priority: 1` sur `#[Route]` pour eviter le conflit avec API Platform `{id}`
- Serialization : pour embarquer une relation (pas IRI), ajouter le groupe du parent aux proprietes de l'entite cible
- Upload fichiers : utiliser `$file->getMimeType()` (pas `getClientMimeType()`) pour valider cote serveur
- **Audit obligatoire** : toute entite (nouvelle ou existante) doit porter `#[Auditable]` (dans `Shared/Domain/Attribute/`). Les champs sensibles (password, token, secret) doivent etre annotes `#[AuditIgnore]`. Spec complete : `doc/audit-log.md`
### Frontend
- TypeScript strict
- **Commentaires en francais** : tout commentaire TS/Vue (JSDoc, inline, bloc) doit etre redige en francais. Le code reste en anglais. Meme regle que cote backend.
- Composable `useApi()` pour tous les appels API (gere cookies, erreurs, toasts, i18n)
- Stores Pinia : `useAuthStore` (auth), `useUiStore` (ui)
- Middleware global `auth.global.ts` protege les routes + charge la sidebar apres login
- Middleware global `modules.global.ts` redirige les routes des modules desactives
- Traductions dans `frontend/i18n/locales/` avec le namespace `sidebar.*` pour la nav
- 4 espaces d'indentation
- Les labels de sidebar sont des cles i18n, jamais du texte brut (le layout applique `t()` dessus)
- **Tableaux : pas de persistance URL.** Aucun etat de tableau (filtres, pagination, tri, tri par colonne, selection, ligne active...) ne doit etre persiste dans la query string ou reinjecte depuis `route.query` au montage. L'etat vit uniquement dans le composant (reactive locale). Seuls les deep links "de navigation metier" (ex: ouvrir un detail precis `/users/42`) sont dans l'URL, jamais l'etat UI du tableau. Exceptions autorisees sur demande explicite de l'utilisateur.
- **Composants formulaires : utiliser `@malio/layer-ui`.** Tout champ de formulaire / filtre doit utiliser les composants `Malio*` (MalioInputText, MalioSelect, MalioSelectCheckbox, MalioCheckbox, MalioRadioButton, MalioInputNumber, MalioInputAmount, MalioInputPassword, MalioInputTextArea, MalioInputUpload, MalioTime, MalioButton, MalioButtonIcon) plutot que des `<input>` / `<select>` bruts.
- **Exceptions autorisees** (a commenter en TODO lors du premier usage, pour pouvoir migrer quand la lib evoluera) :
1. Type de champ non couvert par la lib : `datetime-local`, `date`, `color picker`, `file drag & drop`, etc.
2. Bug connu bloquant du composant : ex. `MalioSelect` avec options a valeur string (cf. note dans le Lesstime CLAUDE.md). Documenter le bug avec un commentaire pointant la limitation.
- Toute autre exception doit etre validee par l'equipe avant merge.
- **Tableaux de donnees : utiliser `MalioDataTable`.** Tout affichage LISTE tabulaire (donnees metier paginees, CRUD admin) doit passer par `MalioDataTable` (pagination + slots `#header-*` pour filtres + `#cell-*` pour rendu custom). Pas de `<table>` brut avec pagination custom. **Exception** : les tableaux purement presentationnels non-paginables (diff field/old/new, grille de comparaison, matrice RBAC, etc.) peuvent rester en `<table>` HTML brut — MalioDataTable n'est pas adapte a ces cas.
- **Audit ManyToMany** : le listener trace les modifications de collections to-many (`permissions`, etc.) sous forme `{fieldName: {added: [ids], removed: [ids]}}`. Toute nouvelle entite `#[Auditable]` portant des ManyToMany a auditer beneficie automatiquement de cette couverture — aucune action supplementaire requise.
### Nginx
- `/api/*` -> Symfony (via try_files + index.php)
- `/api/login_check` -> location exact match, fastcgi direct avec REQUEST_URI reecrit en `/login_check`
- `/` -> SPA frontend (`frontend/dist/`)
## Docker
- Container PHP : `php-coltura-fpm`
- Container Nginx : `nginx-coltura`
- Container DB : PostgreSQL sur port **5437** (interne et externe)
- Config Docker dev : `infra/dev/.env.docker` (override local : `infra/dev/.env.docker.local`)
- Config Docker prod : `infra/prod/` (Dockerfile multi-stage, docker-compose.prod.yml)
- Apres modif nginx : `docker restart nginx-coltura`
## Fixtures
- User admin : `admin` / `admin` (ROLE_ADMIN)
- Users internes : `alice` / `alice`, `bob` / `bob` (ROLE_USER)
## Delegation Codex
Pour les taches mecaniques (tests, boilerplate, renommages, refacto repetitif), delegue a Codex via le plugin `codex`. Garde Claude pour la reflexion, l'architecture et la verification.
- **Codex** = junior dev rapide et pas cher (executions mecaniques)
- **Claude** = senior dev qui verifie et reflechit (design, review, decisions)
C'est le meilleur ratio qualite/credits.
`admin` / `admin` (ROLE_ADMIN) ; `alice` / `alice`, `bob` / `bob` (ROLE_USER).