# Process de développement — OBLIGATOIRE pour tout agent (humain ou IA)

> Chargé automatiquement à chaque session. Chaque règle vient d'un incident réel de L4.
> Toute évolution passe par une PR sur ce fichier — jamais de règle orale.

## Avant d'écrire une ligne de code

1. **Lire l'issue ET son CA en entier** — le CA fait foi, pas le titre. Une paire
   back/front vit dans les DEUX dépôts, même jalon (`scripts/audit_sprints.py` vérifie).
2. **Nouvel écran = wireframe validé PUIS maquette HTML validée** (barre générale RÉELLE
   reproduite — logo image, avatar photo, rendu Chrome headless 1280 px, jamais qlmanage).
   Les règles UI : `docs/design/maquettes/README.md` (police ≥13 px, filtres en barre
   d'outils, ellipses, 7 colonnes par défaut).
3. **Un « tous les écrans » devient une LISTE cochable** dans l'issue (l'oubli de la
   vague D venait d'un « tous » jamais énuméré).

## Pendant le dev

4. **1 issue = 1 branche = 1 PR** ; merge UNIQUEMENT par `scripts/merge_train.sh` ;
   `git pull` sur development juste avant de brancher ; un seul train à la fois par dépôt.
5. **JAMAIS d'énumération de rôles en dur** (`role.includes("manager") || ...`) : quatre
   incidents identiques en L4 (#54, #89, #92, #96). Le droit vient de la garde de route,
   du référentiel de rôles et du serveur.
6. **Un mock se calque sur le contrat RÉEL de l'API**, jamais sur ce que le code espère
   (le sélecteur de managers vide : le mock reproduisait le bug, le test était aveugle).
7. **SQLite ≠ MySQL** : toute requête non triviale est REJOUÉE sur le MySQL de staging
   avant livraison (incidents 1093/1824 vécus).

## Design et maquettes — non négociable

14. **Une maquette AJOUTE, elle ne RETIRE jamais.** Toute capacité présente à l'écran
    aujourd'hui et absente d'une maquette est un **OUBLI du maquettiste**, jamais une
    décision de suppression. En cas de doute : demander, jamais trancher seul.
    Cas nommé (12/08) : la **recherche dans les listes déroulantes** (utilisateurs,
    formations), absente des maquettes, conservée partout.
    **Conséquence pratique** : avant de toucher un écran, écrire dans l'issue l'inventaire
    de ce qu'il sait faire ; après, le vérifier ligne à ligne. Une capacité perdue en
    silence ne se découvre qu'en recette, ou pire, chez le client.

15. **Lire la maquette EN ENTIER avant d'implémenter, et lire le FICHIER, pas son titre.**
    Trois affirmations fausses ont été produites le 13/08 faute de cette lecture : « aucune
    issue ne porte la refonte » (recherche par mot-clé au lieu du corps des issues), « la
    page cours n'est pas maquettée » (grep de titres au lieu du fichier — elle l'était
    intégralement), « six composants sont absents » (README cru sur parole — la plupart
    étaient dessinés). À chaque fois la réponse était dans un fichier non ouvert.

## Règle d'or — AUCUN code en dur

16. **Rien de ce qui pourra un jour être réglé ne s'écrit en dur.** Libellés
    affichés, seuils, délais, règles de choix, textes de messages : tout passe par
    un **paramètre**, jamais par une valeur écrite au milieu d'un composant.
    **Exception uniquement sur accord explicite d'Enguerran**, et notée à l'endroit
    du code concerné.

    **Le motif** : ces valeurs deviendront des réglages d'administration. Une valeur
    en dur, c'est un développement à refaire le jour où le client veut la changer —
    et un client qui demande « juste un mot » découvre qu'il faut un déploiement.

    **Le test qui tranche** : « si un client demandait à changer ça, faudrait-il
    recompiler ? » Si oui, c'est un paramètre.

    Cas fondateur (14/08) : les libellés d'action d'une formation — *Commencer* si
    non débutée, *Reprendre* si en cours, *Revoir* si terminée. La maquette les
    codait en dur (`st === 'done' ? 'Revoir' : …`) ; ils sont devenus des
    paramètres. Idem pour la règle qui désigne la formation mise en avant.

    ⚠️ **Ne vaut pas dispense d'écran d'administration** : les paramètres vivent
    d'abord dans un module de configuration, avec leurs valeurs par défaut. Leur
    passage sous la main de l'admin fait l'objet d'un lot dédié, après la migration
    de production (décision du 14/08) — il n'est donc PAS à traiter au fil de l'eau,
    ni à ouvrir en issue à chaque paramètre créé.

    Voisine de la règle 5 (jamais d'énumération de rôles en dur) et de la règle R6.1
    du dépôt studio (inclusion par CRITÈRE, jamais par énumération de noms métier) :
    même famille de défaut, trois portes d'entrée différentes.

## Règle d'or — lire le CA EN ENTIER, et les commentaires

17. **Une issue ne se lit jamais dans une liste.** Le titre ment par concision, et le
    commentaire porte souvent la décision qui compte. Avant toute action sur un sujet :
    ouvrir l'issue, lire **le CA en entier** et **tous les commentaires**.

    **Avant d'ouvrir une issue, chercher le doublon sur le CONTENU** — corps ET
    commentaires — pas sur le titre. Un sujet déjà traité s'y trouve rarement sous le
    nom qu'on lui donnerait.

    Cas fondateur (14/08) : avant d'ouvrir #398, une recherche dans les 197 corps et
    175 commentaires a montré que la **reprise à la seconde était déjà validée en
    recette le 09/08**, preuves de tracking à l'appui — l'issue aurait porté du travail
    fait. Et que #26 (EA-016) réalise bien l'agrégation demandée, mais **pour les API
    manager/admin** : information présente uniquement dans le détail de son CA.

    Trois affirmations fausses du 13/08 avaient la même cause — conclure sur une liste,
    un titre ou une prose au lieu d'ouvrir le fichier (cf. règle 15).

## Tests — non négociable

8. **Chaque PR embarque ses tests unitaires ET de non-régression.** Un bug corrigé = un
   test qui l'empêche de revenir. Vérifier que le test échoue sans le fix.
9. **Baseline mesurée par branche** : suite complète AVANT et APRÈS (`vitest run`,
   `artisan test`), comparaison des fichiers en échec. Zéro nouveau fichier rouge.
   Un échec « nouveau » se vérifie sur le code NON modifié avant d'accuser sa branche.

   ⚠️ **Côté FRONT, la mesure n'est valable qu'avec `npx vitest run --no-file-parallelism`.**
   En parallèle la suite est non déterministe : deux exécutions du même code ont donné 3 puis
   7 échecs, sans aucun recouvrement, alors que la version sérialisée est verte (constat du
   13/08, FRONT#148). Comparer deux exécutions parallèles ne prouve rien, ni la régression ni
   son absence.

   ⚠️ **Node 24 obligatoire côté FRONT** (`.nvmrc`, `engines`). Sous Node 25 et au-delà, Node
   expose son propre `localStorage` incomplet qui masque celui de jsdom : la quasi-totalité
   des tests échoue (`localStorage.clear is not a function`) alors que le code est sain.
   Vérifier `node --version` avant de conclure quoi que ce soit d'une suite rouge (FRONT#32).

   ⚠️ **Côté BACK, la suite exige un MySQL 8 joignable et une base `efektiv_testing`**
   (#578, 21/08). `phpunit.xml` désigne le pilote `mysql` — le moteur de la production — et
   FORCE le nom de la base : sans ce forçage, `RefreshDatabase` (`migrate:fresh`) viderait la
   base de développement du `.env`. L'hôte et les identifiants, eux, viennent du `.env`.
   Préparation, une fois :

   ```bash
   mysql -u root -e "CREATE DATABASE IF NOT EXISTS efektiv_testing \
     CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
   ```

   Un « Unknown database 'efektiv_testing' » ou un « Connection refused » ne dit **rien**
   du code : c'est cette préparation qui manque. Il n'existe plus de repli sqlite, et c'est
   volontaire — deux moteurs entretiendraient la divergence qu'on a supprimée.

## Livraison et recette

10. **Déploiement** : suivre `docs/roadmap/12_PROCEDURE_DEPLOIEMENT.md` (checklist 13
    points). Le point 13 est vital : **le hash du bundle servi doit avoir CHANGÉ** —
    deux builds ont échoué en silence en L4, « redéployé » s'affichait quand même.
11. **Recette UI en navigateur sur staging, par profil, avec données réelles** — les
    tests verts ne suffisent pas : les 4 plus gros bugs de L4 (mail jamais délivré,
    lien du mail vers une route inexistante, deux magasins de tokens étanches, admin
    éjecté) n'étaient visibles QUE de bout en bout. Comptes de recette : procédure
    tinker dans le doc de déploiement. Un e-mail se valide REÇU EN BOÎTE sur un vrai
    Gmail — yopmail accepte tout.
12. **Tout constat va au CA de l'issue** (pas en commentaire, pas en PR) ; statuts
    d'issue tenus à jour (`statut: en dev` → `en recette`) ; un écart trouvé en recette
    reste dans le LOT COURANT — on ne reporte jamais ses propres oublis au lot suivant.
13. **Une fonctionnalité inaccessible n'est pas livrée** : après chaque écran, vérifier
    le CHEMIN pour y arriver (bascule, barre latérale, redirections), pour chaque rôle.
14. **Avant `gh issue create`, LANCER `scripts/chercher_issue.sh "<motif>"`.** Pas
    « penser à vérifier » : lancer la commande, et coller son résultat dans la PR ou la
    réponse. La règle « chercher les doublons sur le CONTENU » existait déjà et a été
    enfreinte DEUX fois (#382/#350 le 13/08, #401/#398 le 14/08) — les deux fois parce
    que `gh issue list` n'affiche que les TITRES, et qu'un sujet vit dans le CA ou dans
    un commentaire. Une règle qu'on doit se rappeler d'appliquer finit par ne plus
    l'être : celle-ci est devenue une commande, qui fouille corps et commentaires des
    issues ouvertes ET fermées. **Deux issues sur un sujet, c'est une décision prise
    deux fois** — et donc, tôt ou tard, deux décisions différentes.
