`) et bindent
+ `:error="errors?.email"` sur chaque champ Malio.
+
+## Fichiers touchés
+
+| Fichier | Action |
+|---|---|
+| `shared/utils/api.ts` | + `mapViolationsToRecord` |
+| `shared/composables/useFormErrors.ts` | **nouveau** composable |
+| `modules/commercial/pages/clients/new.vue` | scalaires (Main/Info/Compta) + erreurs par ligne |
+| `modules/commercial/pages/clients/[id]/edit.vue` | idem |
+| `modules/commercial/components/ClientContactBlock.vue` | + prop `:errors`, bind `:error` |
+| `modules/commercial/components/ClientAddressBlock.vue` | + prop `:errors`, bind `:error` |
+| RIB (inline dans new/edit) | bind `:error` par ligne |
+
+## Tests (Vitest — règle « pas d'E2E »)
+
+- `mapViolationsToRecord` : formats `violations` / `hydra:violations`, payload vide,
+ `propertyPath` manquant.
+- `useFormErrors` : `setServerErrors` mappe et retourne `true` / `false` sans violation,
+ `clearErrors`, fallback toast sur non-422.
+
+## Convention posée pour tous les forms
+
+À reporter dans `.claude/rules/frontend.md` une fois le pattern stabilisé :
+
+> Tout form qui veut un retour d'erreur par champ : appels API en `{ toast: false }` +
+> `useFormErrors` pour les champs scalaires (422 inline), `mapViolationsToRecord` par
+> ligne pour les collections. `useCategoryForm` migrera sur `useFormErrors`.
+
+## Fait dans la foulée (post-ERP-101 initial)
+
+- **`useCategoryForm` migré sur `useFormErrors`** : `errors` devient le `reactive` du
+ composable (drawer adapté : `form.errors.name` au lieu de `form.errors.value.name`,
+ bloc `_global` retiré → erreur transverse en toast). 28 tests verts.
+- **Convention reportée dans `.claude/rules/frontend.md`** (section « Validation des
+ formulaires — useFormErrors obligatoire »).
+
+## Hors scope ERP-101 (suivi : ticket ERP-107)
+
+- Langue / présence des messages de validation côté back : le `message` affiché est celui
+ renvoyé par le serveur. Audit des contraintes Symfony (présence d'un `message` FR,
+ contraintes manquantes, violations sans `propertyPath`) tracké dans **ERP-107**.
diff --git a/frontend/modules/catalog/components/CategoryDrawer.vue b/frontend/modules/catalog/components/CategoryDrawer.vue
index 89383d0..9ada8aa 100644
--- a/frontend/modules/catalog/components/CategoryDrawer.vue
+++ b/frontend/modules/catalog/components/CategoryDrawer.vue
@@ -20,7 +20,7 @@
:label="t('admin.categories.form.name')"
input-class="w-full"
:max-length="120"
- :error="form.errors.value.name"
+ :error="form.errors.name"
required
/>
@@ -32,15 +32,9 @@
:options="typeOptions"
:label="t('admin.categories.form.type')"
:empty-option-label="t('admin.categories.form.typePlaceholder')"
- :error="form.errors.value.categoryType"
+ :error="form.errors.categoryType"
:disabled="loadingTypes"
/>
-
-
-
- {{ form.errors.value._global }}
-