docs(knowledge): capitalisation workflow + ux — intégration du triage local (mai-juin 2026)

Triage et intégration des propositions workflow et UX du buffer 95_a_capitaliser.md.

WORKFLOW :
- risques/story-tracking.md : 24 risques de suivi de story (enabler AC non-bloquant,
  tests plumbing vs scénario, reformat hors scope, xit sans story de suivi, re-scope mid-PR,
  statut migré non vérifié, périmètre auto-déclaré vs git diff, composant/page livré sans
  câblage — reciblages venus de backend #21 et frontend #257)
- patterns/general.md : audit cartographique pré-chantier, Go/No-Go par lot, sub-agent review
  fresh-context, sweep read-only délégué (#156), revue adverse de spec, audit-first migration

UX (domaine amorcé — était vide) :
- patterns/general.md : 9 patterns (mount-based read, fiche détail single-scroll, FAB étendu,
  ligne de contexte filtres, état read-only caché, avatar par hash, audit a11y touch targets)
- risques/general.md : 5 risques (bouton retour dans ScrollView #108, lien sans handler,
  wording cross-écran divergent, token sans détection, padding multi-écrans)
- READMEs ux/workflow mis à jour

Vérifié : aucun doublon d'ancre/titre, fichiers racine 40_/90_ non modifiés (propositions
réservées pour validation séparée). Source 95_ non purgée (purge en fin de capitalisation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MaksTinyWorkshop
2026-06-25 15:48:53 +02:00
parent 5f5c87296e
commit 81fde91259
7 changed files with 1089 additions and 6 deletions
+1 -2
View File
@@ -8,5 +8,4 @@ Avant toute proposition UX, identifie le fichier dont le nom et la description m
| Fichier | Domaine | Entrées clés | | Fichier | Domaine | Entrées clés |
|---------|---------|--------------| |---------|---------|--------------|
| `general.md` | Notifications, fiche détail, CTA, listes, états read-only, i18n, a11y mobile | Marquage "lu" au mount, fiche détail single-scroll/méta/chip, FAB vs sticky bottom, ligne de contexte sous filtres, CTA toggle différencié, table i18n inclusive, état read-only cacher vs griser, avatar pastel buckets distincts, audit a11y touch targets |
_(aucune entrée pour le moment — à alimenter via `95_a_capitaliser.md`)_
+205
View File
@@ -0,0 +1,205 @@
---
title: UX — Patterns validés : Général
domain: ux
bucket: patterns
tags: [mobile, navigation, cta, fab, liste, etat, i18n, a11y, notifications]
applies_to: [design, implementation, review]
severity: medium
validated_on: 2026-06-25
source_projects: [app-alexandrie]
---
# UX — Patterns validés : Général
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/ux/patterns/README.md` pour l'index complet.
---
<a id="pattern-ux-mark-read-au-mount"></a>
## Pattern : Marquage "lu" au mount de l'écran cible (mount-based, pas click-based)
- Objectif : éviter les "clics accidentels qui consomment" une notification — marquer comme lu uniquement quand l'utilisateur a réellement vu le contenu.
- Contexte : liste de notifications qui navigue vers un écran cible (thread, conversation, achievements).
- Quand l'utiliser : tout flux notification → écran cible où le clic ne prouve pas la lecture.
- Quand l'éviter : action où le clic EST l'acte de lecture (ex. dépliage inline).
### Description
Mauvais pattern : `onPress(item) → markRead(item.id); navigate(target)` — un clic accidentel puis back immédiat consomme la notif sans qu'elle ait été vue ; la notif disparaît avant que l'écran cible monte.
Bon pattern : déléguer le mark-read à l'écran cible, déclenché au mount via query param (`?notificationId=`). Un hook `useMarkNotificationReadOnMount()` lit le param et appelle `markRead` dans un `useEffect`, avec un guard d'idempotence (`alreadyMarkedRef`) contre les re-renders (focus refresh, hot reload).
Comportements obtenus : cas nominal (tap → écran monte → lu), cas annulation (tap → back avant mount → reste non lu), deep-link analytics-friendly et testable.
- Validé le : 28-05-2026 — app-alexandrie (IA-v2.8 AC4)
---
<a id="pattern-ux-fiche-detail-immersive"></a>
## Pattern : Fiche détail — single-scroll immersif, méta-infos en tête, chip drill-down
- Objectif : structurer une fiche de contenu/produit/praticien/événement sans "rooms vides" ni dispersion des infos.
- Contexte : fiche détail mobile/web avec contenu de profondeur variable.
- Quand l'utiliser : fiche détail à contenu court ou partiellement livré.
- Quand l'éviter : fiche à forte profondeur réelle (chapitres + avis nombreux) où les tabs sont justifiés.
### Trois patterns indépendants
1. **Single-scroll vs tabs Udemy/Masterclass** : pour une fiche courte (pas de chapitres, pas d'avis livrés), préférer un single-scroll vertical. Les tabs créent des "rooms vides" qui sapent la crédibilité (onglet "Chapitres" vide, "Avis" avec un faux 5 étoiles). Réintégrer les sections en single-scroll est plus simple que remplir artificiellement des tabs.
2. **Méta-infos groupées en tête** : regrouper toutes les méta-infos dans un bloc dense entre le hero et le contenu (overline catégorie > titre H1 > InfoChips > ligne progression compacte). Évite la dispersion ; donne un signal fort sur "ce qu'est ce contenu" avant de plonger. La progression tient sur une ligne (icon + label + date), c'est une info légère.
3. **Chip cliquable drill-down** : plutôt qu'une section vide (avis sans backend, stats peu lues), exposer l'info en chip dans la zone méta (état neutre si vide : "☆ Aucun avis") qui devient l'entrée vers une page dédiée. Évite les empty states verbeux.
- Validé le : 29-05-2026 — app-alexandrie (ux-cleanup-7)
---
<a id="pattern-ux-fab-vs-sticky-bottom"></a>
## Pattern : Préférer le FAB étendu au sticky bottom plein-largeur sur écran à nav persistante
- Objectif : éviter le piège de positionnement à 3 couches synchronisées (sticky `bottom` + paddingBottom ScrollView + safeArea) du CTA sticky.
- Contexte : écran avec nav persistante (TabBar/BottomBar en `position: absolute, bottom: 0`) ET un CTA principal contextuel.
- Quand l'utiliser : écran à nav persistante avec CTA (commencer, reprendre, créer).
- Quand l'éviter : écran SANS nav persistante (modal, full-screen) où le sticky devient naturel ; ou CTA à label très long ; ou page de paiement validée maquette par maquette.
### Description
Un sticky bottom plein-largeur exige de re-synchroniser 3 hauteurs à chaque évolution de la nav, et un drift → le CTA chevauche la TabBar (bug observé sur device, invisible en tests Jest). Préférer un **FAB étendu Material 3** (icon + label, bottom-right) qui se positionne via UNE seule valeur (`bottom = NAV_HEIGHT + spacing + safeArea`) gérée par le composant lui-même, n'ajoute pas de surface visuelle redondante, et est immune aux évolutions de la nav.
Anti-pattern : `<View position="absolute" bottom={const}>` avec un Button plein-largeur — semble simple mais oblige à recalculer 3 hauteurs à chaque changement de nav.
- Validé le : 29-05-2026 — app-alexandrie (ux-cleanup-7, le sticky a généré ~3 cycles de bugs sur device)
---
<a id="pattern-ux-ligne-contexte-sous-filtres"></a>
## Pattern : Ligne de contexte sous les filtres de liste
- Objectif : permettre à l'utilisateur de comprendre instantanément ce qu'il regarde (combien, quel filtre) et pourquoi la liste est vide.
- Contexte : toute liste filtrée (annuaire, feed, bibliothèque).
- Quand l'utiliser : liste avec filtres et/ou recherche.
- Quand l'éviter : liste sans filtre ni recherche.
### Description
Afficher une ligne de contexte juste sous les filtres et au-dessus du premier résultat. Elle : compte les résultats (singulier/pluriel correct), mentionne le filtre actif ("Ma cohorte • 5 membres"), contextualise la query ("Aucun résultat pour « pnl »"), explicite le vide ("Aucun membre dans votre cohorte" plutôt qu'une chaîne générique), et ne s'affiche PAS pendant le 1er chargement (count=0 + isLoading) pour éviter le flash "Aucun X".
Implémentation type : helper pur `formatXxxContext({filter, count, query, isLoading}) → string` (testable à 100% en node) + composant léger en `textSecondary` 13px.
Anti-pattern : empty state global (centré, gros) sur une liste filtrée — il masque les filtres alors que l'utilisateur veut comprendre POURQUOI c'est vide.
- Validé le : 29-05-2026 — app-alexandrie (ux-cleanup-9)
---
<a id="pattern-ux-cta-toggle-differencie"></a>
## Pattern : CTA toggle différencié au-delà du libellé
- Objectif : rendre l'état d'un CTA toggle (Suivre/Suivi, Souscrire/Souscrit) perceptible au premier regard.
- Contexte : CTA qui bascule entre deux états (action / état "fait").
- Quand l'utiliser : tout bouton à état binaire persistant.
- Quand l'éviter : bouton d'action one-shot sans état.
### Description
Deux éléments visuels distincts au-delà du libellé :
- **État inactif (action disponible)** : ghost button (fond transparent, bordure + texte `colors.primary`, libellé verbe infinitif "Suivre").
- **État actif ("fait")** : filled tinted (fond `${colors.primary}1F` ≈ 12% d'alpha, bordure transparente, icône remplie alignée gauche, libellé participe passé "Suivi" — l'icône porte l'info, pas une coche dans le texte).
**Pourquoi `${color}1F` plutôt qu'`opacity: 0.12`** : `opacity` fade TOUT l'enfant (icône + label), `${color}1F` n'applique l'alpha qu'au background — icône et label gardent leur saturation pleine.
A11y obligatoire : `accessibilityState={{ selected: isActive }}` + `accessibilityLabel` explicitant état + action.
Anti-pattern : différencier UNIQUEMENT par le libellé (Suivre → Suivi ✓) — le ✓ Unicode est faible visuellement et inaccessible.
- Validé le : 29-05-2026 — app-alexandrie (ux-cleanup-9 AC3)
---
<a id="pattern-ux-wordings-inclusifs-i18n"></a>
## Pattern : Table i18n de wordings inclusifs
- Objectif : centraliser les wordings pour qu'ils soient auditables et inclusifs, et empêcher le composant d'inventer ses propres libellés.
- Contexte : app francophone avec wordings genrés potentiels (notifications, messages).
- Quand l'utiliser : tout domaine produisant des wordings adressés à l'utilisateur.
- Quand l'éviter : —
### Structure
Un fichier `i18n/<domain>.ts` par domaine exposant : un `Record<TypeEnum, string>` interne `WORDING_FALLBACK` (générique), une fonction `<domain>Wording(type, ctx?)` qui combine type + contexte, et un `.spec.ts` qui vérifie fallbacks, cas avec contexte, dégradation gracieuse, et la règle d'inclusivité via un garde-fou regex anti-régression (`expect(wording).not.toMatch(/mentionnée(?![\w·])/)`).
### Règles d'inclusivité française
1. **Point milieu `·e`** pour les participes : "mentionné·e", "invité·e".
2. **Nom commun neutre** quand possible : "abonné" plutôt que "follower".
3. **Passé composé impersonnel** sans acteur nommé : "Nouvelle réponse à votre fil" plutôt que "Vous avez été répondu".
4. **Nommer l'acteur quand on l'a** : "Bob a répondu à votre fil" (plus actionnable).
Anti-patterns : wording inline dans le composant (inaudit­able) ; wording dans le push payload backend sans miroir dans la table mobile (divergence lockscreen vs in-app) ; test "smoke" qui vérifie juste que le wording n'est pas vide (rate les régressions de genre).
- Validé le : 29-05-2026 — app-alexandrie
---
<a id="pattern-ux-etat-readonly-cacher-vs-griser"></a>
## Pattern : État read-only — cacher l'interactif plutôt que griser
- Objectif : éviter le grisage avec placeholder ("Action désactivée"), un anti-pattern d'invitation qui fait essayer puis échouer l'utilisateur.
- Contexte : élément interactif (composer DM, bouton "Acheter", formulaire) indisponible pour cause d'état métier (lecture seule, compte suspendu, plan inactif).
- Quand l'utiliser : indisponibilité durable liée à un état métier.
- Quand l'éviter : désactivation transitoire pendant un async court (préférer un loading).
### Description
Ne pas griser — **cacher** l'élément et afficher en remplacement un **bandeau neutre** qui explique l'état.
Pourquoi cacher est meilleur : pas d'invitation à essayer ; explicite > implicite (le bandeau explique POURQUOI) ; a11y (`accessibilityRole="alert"` annonce l'état au mount, un input `editable={false}` ne signale rien) ; visuel (1 zone au lieu de composer grisé + bandeau redondant).
Pattern de code : `{isReadOnly ? <ReadOnlyBanner message="..." /> : <Composer ... />}`. Ton neutre (`textSecondary` / `surfaceContainerLow`), pas `error` (réservé aux vraies erreurs réversibles).
Anti-patterns : placeholder "Envoi désactivé" dans un TextInput non éditable ; `opacity: 0.5` (reste cliquable visuellement) ; bandeau ET composer affichés ensemble.
- Validé le : 29-05-2026 — app-alexandrie (ux-cleanup-11 AC4)
---
<a id="pattern-ux-avatar-pastel-buckets-distincts"></a>
## Pattern : Avatar coloré par hash — buckets distincts du fallback
- Objectif : éviter qu'un utilisateur tombant dans un bucket trop neutre soit confondu avec un compte supprimé.
- Contexte : avatar coloré "hash du nom" avec un bucket fallback (compte supprimé/anonyme).
- Quand l'utiliser : avatars générés par hash.
- Quand l'éviter : avatars uploadés / sans fallback identitaire.
### Description
Ne mettre dans le pool hashable QUE des accents colorés (`primary`/`secondary`/`tertiary` + leurs containers). Le fallback (`outlineVariant`) reste réservé aux fallbacks identitaires (compte supprimé, handle vide).
Anti-pattern : inclure un token "surface" très clair (background-like) dans le pool — il ressemble au fallback grisé en light theme. Validation : tester visuellement les 8-12 tokens du pool sous light + dark ; retirer tout token proche du fallback.
- Validé le : 30-05-2026 — app-alexandrie (ux-cleanup-11 M1)
---
<a id="pattern-ux-audit-a11y-touch-targets"></a>
## Pattern : Audit a11y des touch targets mobile RN
- Objectif : garantir des cibles tactiles conformes (EAA 2025) sans linter a11y RN mainstream.
- Contexte : app mobile React Native / Expo avec composants interactifs denses.
- Quand l'utiliser : audit a11y ou story de polish touch targets.
- Quand l'éviter : —
### Cible et méthode
Cible : 44×44pt iOS (Apple HIG) / 48×48dp Android (Material). Méthode (grep + calcul, pas de linter) : grep tous les `Pressable`/`TouchableOpacity`/`onPress` ; pour chacun, taille effective = `max(width, iconSize + paddingH*2) × max(height, fontSize + paddingV*2)` + `hitSlop` ; marquer non conforme si une dimension < 44pt ; rapport ordonné par gravité.
### Décision hitSlop vs padding
| Choisir... | Quand... |
|---|---|
| `hitSlop` | Composant qui DOIT rester visuellement compact (chip dense, icône + label adjacent, pill en liste serrée), réutilisé multi-écrans (changer le padding casse les callers), pas de feedback visuel attendu au-delà de la cible (chevron, lien "Voir tout") |
| `padding` / `minHeight: 44` | Composant qui peut grossir sans gêne (CTA EmptyState, bouton primaire), feedback visuel attendu sur la zone élargie, composant isolé sans dépendance layout serrée |
Syntaxe : `hitSlop={{ top: N, bottom: N, left: M, right: M }}` — N pour atteindre exactement 44pt en hauteur ; `left/right` plus petits (4-8) si chips côte-à-côte pour éviter l'overlap. Limite acceptée : audit visuel + grep seul (cf. risque associé dans `ux/risques/general.md`).
- Validé le : 31-05-2026 — app-alexandrie (ux-cleanup-14)
+119
View File
@@ -0,0 +1,119 @@
---
title: UX — Risques & vigilance : Général
domain: ux
bucket: risques
tags: [mobile, navigation, handler, wording, tokens, a11y]
applies_to: [design, implementation, review]
severity: medium
validated_on: 2026-06-25
source_projects: [app-alexandrie]
---
# UX — Risques & vigilance : Général
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/ux/risques/README.md` pour l'index complet.
---
<a id="risque-ux-back-button-dans-scrollview"></a>
## Bouton retour placé à l'intérieur du ScrollView
### Risques
- Sur une app mobile RN/Expo avec une TopBar globale sans back natif, un bouton retour ajouté localement dans le `<ScrollView>` disparaît dès que l'utilisateur défile
- Sur une page critique (gestion d'abonnement, résiliation, support), perdre l'accès au retour pendant le scroll est un piège UX direct
### Symptômes
- Le bouton retour est le premier enfant du contenu scrollable et défile avec lui ; il devient inaccessible plus bas dans une page longue
### Bonnes pratiques / mitigations
- Tout bouton retour local doit vivre **hors du ScrollView** : header local sticky en `<View>` parent (flex:1) + ScrollView frère.
- Stop conditions : ne PAS doubler `insets.top` (le header le consomme → `contentInset.top = 0`) ; ne PAS placer le bouton dans `contentContainerStyle`. Si l'app utilise `<Stack.Screen>` Expo Router avec header natif, préférer `headerBackVisible: true`.
- Filet de test : test statique vérifiant qu'un fichier avec `handleBack` ne place pas le `<Pressable accessibilityLabel="Retour">` dans un `<ScrollView>`.
- Contexte technique : RN/Expo + TopBar globale — app-alexandrie 28-05-2026 (ux-cleanup-2)
---
<a id="risque-ux-bouton-sans-handler-hors-scope"></a>
## Bouton/lien sans handler laissé dans l'UI ("hors scope")
### Risques
- Un bouton survit dans l'UI sans `onPress` (résidu de template ou de dépendance retirée) : il tapote sans rien faire → boucle de frustration, l'utilisateur conclut que l'app est cassée et abandonne (grave sur le flow auth, critique pour l'acquisition)
- "Hors scope" utilisé comme argument pour laisser passer → dérive de dette story après story
### Symptômes
- `<TouchableOpacity>` / `<Pressable>` visible sans prop `onPress` (ex. bouton "Facebook" sans OAuth côté API)
### Bonnes pratiques / mitigations
- **Règle** : tout bouton/lien visible doit avoir un `onPress` qui fait quelque chose. Si la feature n'est pas livrée : supprimer le bouton (défaut) ; OU le mettre derrière un feature flag interne ; OU afficher un toast/modal "Bientôt disponible".
- Garde-fou review : grep `<TouchableOpacity`/`<Pressable` sans `onPress=` → finding HIGH. "Hors scope" n'est jamais un permis : un bouton mort sur un écran qu'on édite est dans le scope.
- Contexte technique : RN/Expo — app-alexandrie 29-05-2026 (ux-cleanup-5, login.tsx)
---
<a id="risque-ux-wording-divergent-cross-ecran"></a>
## Wording divergent cross-écran pour le même état métier
### Risques
- Deux écrans/composants affichent le même état métier (`NOT_STARTED`, `OUT_OF_STOCK`) avec des libellés différents, souvent parce que livrés par des stories distinctes
- L'utilisateur traverse les écrans et voit deux mots pour le même état → confusion ; ne casse aucun test, dette qui s'accumule en silence
### Symptômes
- `CourseListItem` affiche "Nouveau" pour `NOT_STARTED`, `ProgressionInline` affiche "Pas encore commencé" pour le même état
### Bonnes pratiques / mitigations
- Centraliser le mapping état → libellé dans une seule fonction pure exportée (`statusLabel(state) => string`) consommée par tous les écrans.
- Review : grep les libellés de status sur les écrans du domaine ; > 1 wording par état = finding MEDIUM.
- Préférer les libellés courts et positifs ("Nouveau" > "Pas encore commencé").
- Contexte technique : RN/Expo — app-alexandrie 29-05-2026 (ux-cleanup-6/7)
---
<a id="risque-ux-regle-token-sans-detection"></a>
## Règle d'usage de token sans stratégie de détection
### Risques
- Un token décoratif (`outlineVariant` MD3 marqué "ne pas utiliser pour des séparations fonctionnelles") continue d'être utilisé pour des bordures fonctionnelles
- La règle reste écrite dans le README mais inappliquée → divergence README vs code
### Symptômes
- Callsites résiduels utilisant le token décoratif en `borderColor`/`borderTopColor` malgré la règle documentée
### Bonnes pratiques / mitigations
- **Une règle d'usage de token doit être assortie d'une stratégie de détection** : (1) lint custom qui flag le token dans une prop bordure/couleur ; (2) audit récurrent (CI cron qui liste les usages) ; (3) renommer le token (`outlineDecorative` rend l'intention explicite).
- Le coût "écrire la règle" est ~10 min, le coût "détecter les violations" est récurrent — investir dans la détection automatique.
- Contexte technique : design tokens MD3 — app-alexandrie 30-05-2026 (ux-cleanup-8/11)
---
<a id="risque-ux-touch-target-padding-composant-partage"></a>
## Augmenter le padding d'un composant réutilisé multi-écrans (touch targets)
### Risques
- Élargir un touch target en augmentant le `padding` d'un composant réutilisé multi-écrans casse le layout des callers (liste serrée, ligne d'icônes header avec gap fixe)
### Symptômes
- Une cible tactile passe sous 44pt ; la corriger par `padding` change la taille visuelle et décale les callers
### Bonnes pratiques / mitigations
- Pour un composant réutilisé, préférer `hitSlop` au `padding` : seul effet = zone tap étendue, taille visuelle inchangée (cf. pattern d'audit dans `ux/patterns/general.md`).
- Contexte technique : RN/Expo a11y — app-alexandrie 31-05-2026 (ux-cleanup-14)
+1 -1
View File
@@ -8,4 +8,4 @@ Avant toute proposition workflow, identifie le fichier dont le nom et la descrip
| Fichier | Domaine | Entrées clés | | Fichier | Domaine | Entrées clés |
|---------|---------|--------------| |---------|---------|--------------|
| `general.md` | Review adversarial, isolation de hunks, méthode de chantier | Review adversarial obligatoire sur chemin critique, isolation chirurgicale d'un hunk via `git apply --cached` | | `general.md` | Review adversarial, méthode de chantier, sub-agents, migration, traçabilité | Review adversarial sur chemin critique, isolation de hunk via `git apply --cached`, triangulation sub-agent, audit cartographique avant chantier, Go/No-Go par lot, phasage feature avec placeholders, préparation Epic N+1 via rétro, sub-agent review fresh-context, revue adverse "information asymmetry", sweep read-only double-agent, audit-first migration large-scale, story polish multi-écrans, test obsolète après durcissement enum, génération palette Stitch MCP |
+260 -2
View File
@@ -5,8 +5,8 @@ bucket: patterns
tags: [review, adversarial, git, chantier, agent] tags: [review, adversarial, git, chantier, agent]
applies_to: [analysis, implementation, review] applies_to: [analysis, implementation, review]
severity: high severity: high
validated_on: 2026-04-27 validated_on: 2026-06-25
source_projects: [RL799_V2] source_projects: [RL799_V2, app-alexandrie]
--- ---
# Workflow — Patterns : Général # Workflow — Patterns : Général
@@ -155,3 +155,261 @@ Les `index` et `@@` viennent du `git diff` original — pas besoin de recalculer
### Communication au user ### Communication au user
> *"Sub-agent X signale 4 fichiers, j'ai validé 1/4 et invalidé 3/4. Voici le plan d'action corrigé."* > *"Sub-agent X signale 4 fichiers, j'ai validé 1/4 et invalidé 3/4. Voici le plan d'action corrigé."*
---
<a id="pattern-audit-cartographique-avant-chantier"></a>
## Pattern : Audit cartographique 15-30 min avant tout chantier de fusion ou refacto transverse
- Objectif : cadrer le chantier sur la **vraie** surface d'impact, pas sur une estimation à priori souvent fausse à ×3-5.
- Contexte : chantier qui touche un composant/pattern utilisé en plusieurs endroits (fusion de 2 composants, migration de N call-sites, consolidation de pattern).
- Quand l'utiliser : avant de planifier les sous-lots, dès que le scope annoncé dépasse 1-2 fichiers.
- Quand l'éviter : modification locale à un seul fichier sans dépendance inverse.
- Validé le : 05-05-2026
- Contexte technique : monorepo Vue / pnpm — RL799_V2
### Checklist d'audit
1. **Call-sites directs** : `grep` du nom du composant/symbole (`<X />`, `<x />`, `import X`).
2. **Call-sites indirects** : grep des **classes CSS**, des **data-testid**, des **types/enums** exportés. Piège fréquent : un composant scoped qui réutilise une classe CSS globale sans importer le composant.
3. **Tests** : grep dans `__tests__/`, `tests/e2e/`, `tests/`. Inclure string-match (lecture du source) ET tests de mount.
4. **Dépendances inverses** : qui produit/consomme les types métier exportés ? (un call-site peut dépendre du type sans dépendre du composant.)
5. **Doublons CSS scoped vs global** : une classe nommée identiquement en `<style scoped>` Vue et en `style.css` global est isolée par le hash scoped — pas de collision réelle même si le grep matche les deux.
6. **Convention déjà appliquée ailleurs** : si la cible ressemble à un pattern existant dans le repo, c'est le **modèle cible** — copier sa structure.
### Anti-pattern
- Faire confiance au scope annoncé dans une todo (écrite il y a 4 minutes ou 4 mois) sans le valider.
- Attaquer en mode "je verrai les surprises en route" — découvrir 3 call-sites de plus après avoir committé 2 lots.
- Grep uniquement `import X from` : on rate les usages par classe CSS, data-testid, type exporté.
Cas validé : fusion `AppBadge``StatusBadge` (RL799_V2) — todo annonçait "~17 call-sites", audit a révélé 3 directs. Scope final divisé par ~5.
---
<a id="pattern-go-no-go-par-lot"></a>
## Pattern : Go/No-Go par lot sur chantier > 8 h estimées
- Objectif : préserver la possibilité de bisect/rollback ciblé et garder la revue lisible diff par diff sur un chantier long.
- Contexte : chantier estimé > 6-8 h (refonte schéma DB + endpoints + UI + hooks), trop gros pour un seul commit reviewable.
- Quand l'utiliser : dès qu'un chantier dépasse ~6-8 h ou touche un changement structurel (schéma, cascade de types).
- Quand l'éviter : fix ponctuel, story atomique.
- Validé le : 13-05-2026
- Contexte technique : monorepo — RL799_V2, app-alexandrie
### Mécanique
1. **Décomposer en lots atomiques** avant la première ligne. Chaque lot = un commit indépendant qui passe les tests existants seul. Idéalement lot 1 = schéma DB + cascade de types (pas de feature visible), lots suivants = endpoints/services/UI.
2. **Point d'étape (1-2 phrases + état tests) à la fin de chaque lot** ; le dev valide explicitement "Go pour le suivant" — pas d'autopilot multi-lots.
3. **Commit par lot** avec message structuré : Pourquoi (lien spec), Quoi (Schéma/Services/UI/Tests), décisions clés, stats tests, récap chantier dans le commit final.
4. **Tests complets entre chaque lot** (`test:api`, `test:shared`, `test:frontend`) — l'effet iceberg émerge dès qu'un changement structurel touche un fichier référencé ailleurs.
5. **Lot plus gros que prévu** (ex. 3-4 h estimées, 5 h sans fin) → arrêter, mini point d'étape : sous-découper (1a/1b/1c) ou confirmer. Pas de "on verra".
### Anti-pattern
- "Je commit tout en fin pour un beau commit propre" : on perd la granularité de bisect et le diff de relecture (~4500 LOC) devient illisible. Un méga-commit rend impossible le `git revert <lot>` chirurgical.
---
<a id="pattern-phasage-feature-placeholders-ux"></a>
## Pattern : Phasage feature avec placeholders UX
- Objectif : livrer rapidement la structure UX d'une feature multi-volets et brancher le backend coûteux en phase 2, pour valider le design avant l'investissement backend.
- Contexte : feature multi-volets dont certains panels nécessitent un backend significatif (endpoints, DTOs, tests api, streaming) ; la structure UX seule prend < 1 h.
- Quand l'utiliser : feature à N volets où l'utilisateur bénéficie déjà de la navigation/structure avant le backend complet.
- Quand l'éviter : feature mono-volet ; ou si la structure UX ne peut pas être validée sans données.
- Validé le : 06-05-2026
- Contexte technique : Vue / monorepo — RL799_V2
### Forme
- **Phase 1 (~1-2 h)** : structure UX complète (page wrapper, tabs, routing, redirections legacy, FAB conditionnel) + panel "core" 100% opérationnel + panels périphériques en **placeholder "Bientôt disponible"** + tests mount complets (les placeholders sont assertés).
- **Phase 2 (commit séparé)** : backend complet (endpoints + DTOs + repository + tests api) + branchement des placeholders + tests mount adaptés.
### Anti-pattern
- **Cacher les onglets futurs** : l'utilisateur ne sait pas que la feature s'étendra. Le placeholder rend l'intention visible.
- **Backend partiel** : ne JAMAIS livrer un endpoint qui retourne `[]` sans test. Le panel placeholder doit être **frontend-only** tant qu'il n'est pas branché.
- Placeholder ambigu : préférer "Bientôt disponible — la liste s'affichera ici" à "Aucun résultat" (confusion avec un empty state normal).
### Détection en review
`grep "Bientôt disponible"` / `data-testid=".*-placeholder"` → vérifier `data-testid` stable + commentaire JSDoc (Phase X, cible backend) + test mount.
---
<a id="pattern-preparation-epic-n-plus-1-via-retro"></a>
## Pattern : Préparation Epic N+1 via rétro Epic N
- Objectif : trancher les dépendances et arbitrages d'Epic N+1 pendant la rétro Epic N, où le contexte est frais — zéro refonte mid-epic.
- Contexte : enchaînement d'epics où Epic N+1 a des dépendances techniques/produit sur Epic N, surtout s'il introduit un domaine cross-cutting.
- Quand l'utiliser : à la clôture de tout epic suivi d'un epic dépendant.
- Quand l'éviter : epic terminal ou totalement indépendant du suivant.
- Validé le : 13-05-2026
- Contexte technique : NestJS + Expo / BMAD — app-alexandrie
### En clôture d'Epic N, identifier explicitement
1. **Dépendances N → N+1 non documentées** (comportement d'Epic N utilisé par N+1 sans spec — ex. soft delete → comportement DM destinataire).
2. **Décisions d'architecture à trancher AVANT le dev** (transport polling/WebSocket, persistance, cache/concurrence) → documentées dans les dev notes de la première story N+1.
3. **Schémas de contrat partagés à créer dans `packages/contracts` AVANT le dev** (schéma Zod canonique du nouveau domaine) → évite la divergence API ↔ mobile.
4. **Matrice de test combinatoire pré-définie** quand N+1 croise plusieurs domaines.
Format rétro : section "Préparation Epic N+1 — chemin critique" avec tableau `# | Tâche | Owner | Bloquant ? | Critère de succès`. Au démarrage N+1, vérifier que tous les items bloquants sont traités.
Indicateurs de succès : zéro refonte mid-epic ; review adversariale concentrée sur les angles techniques (concurrence, sécurité) plutôt que sur des arbitrages métier oubliés.
---
<a id="pattern-sub-agent-review-fresh-context"></a>
## Pattern : Sub-agent review fresh-context pour stories non triviales
- Objectif : casser le biais de rationalisation du dev qui review son propre code, via un relecteur sans contexte de conception.
- Contexte : story non triviale (multi-fichiers, async, state management complexe) avant de marquer "done".
- Quand l'utiliser : stories non triviales touchant un chemin sensible.
- Quand l'éviter : stories triviales (1 fichier, peu de logique), fixes mineurs — l'overhead n'est pas justifié.
- Validé le : 14-05-2026
- Contexte technique : multi-agent / BMAD — app-alexandrie
### Mécanique
1. Push le commit à reviewer.
2. Spawn un subagent (general-purpose) fresh-context avec un prompt self-contained : "aucun contexte" + working dir + branche + commit SHA + persona ([CR]) + story (path) + ACs + bug class à vérifier + format de sortie + "N'APPLIQUE AUCUN FIX, rapport pur".
3. Le parent décide quoi faire des findings (croisé par grep — cf. pattern de triangulation ci-dessus).
Coût ~3 min wall-clock, contexte parent préservé. Cas validé : story 11.2 (app-alexandrie) — 3 HIGH légitimes (race-token sur loadMore/refresh) ratés par le dev.
**Caveat** : même modèle = biais d'entraînement partagés. Pour les concerns architecture/sécurité critiques, la review humaine reste indispensable.
---
<a id="pattern-revue-adverse-information-asymmetry"></a>
## Pattern : Revue adverse de spec en "information asymmetry"
- Objectif : trouver les angles morts qu'un auteur ne peut PAS voir de l'intérieur (fichiers fantômes, ACs impossibles, analogies de pattern fausses), avant de marquer une spec `ready-for-dev`.
- Contexte : spec/story complexe avant le dev, surtout multi-lots dépendants.
- Quand l'utiliser : avant de figer une spec non triviale.
- Quand l'éviter : spec triviale ou déjà très bien cadrée.
- Validé le : 18-06-2026
- Contexte technique : quick-spec + review-adversarial / sous-agent — RL799_V2
### Mécanique
Passer la spec à un relecteur (sous-agent isolé) qui : (a) a accès **lecture** au repo, (b) ne connaît QUE le fichier de spec (zéro historique de conception), (c) doit **vérifier chaque affirmation** (numéros de ligne, noms de symboles, existence de fichiers/helpers) dans le code réel, posture cynique "assume que des problèmes existent, ≥10 findings". Pour une spec multi-lots, lui faire lire AUSSI les lots prérequis (contradictions inter-specs).
Catégories de findings que l'asymétrie révèle et que la relecture par l'auteur rate :
1. **fichier déclaré NEW qui existe déjà** ailleurs (doublon, régression des tests existants) ;
2. **AC testant un cas impossible** (ex. `onDelete` sur User alors que les Users sont soft-deleted) ;
3. **analogie de pattern fausse** ("imite X" alors que X fait l'inverse) ;
4. **coercition runtime silencieuse** non signalée par TS ;
5. **garde RBAC mal nommée** (`requireVenerable` inexistant = en fait `ROLES_GOVERNANCE`).
Traitement : numéroter les findings (sévérité + validité réel/bruit/indécis), ne pas exclure par sévérité, corriger AVANT dev. Cas validé : 3 specs ODJ RL799 → 44 findings dont plusieurs Critical réels.
---
<a id="pattern-sweep-read-only-double-agent"></a>
## Pattern : Sweep read-only délégué à 2 agents Explore + cross-check grep
- Objectif : produire un audit/sweep haute précision sans toucher au code source.
- Contexte : story d'audit/migration en mode read-only (relevé d'occurrences, cartographie de dette).
- Quand l'utiliser : sweep dont la précision ligne-à-ligne est critique pour la story consommatrice.
- Quand l'éviter : recherche ponctuelle d'un seul symbole.
- Validé le : 28-05-2026
- Contexte technique : multi-agent / BMAD — app-alexandrie
### Mécanique
Déléguer le sweep à 2 agents Explore parallèles + un cross-check `grep` direct. Filet anti-régression en review : revérifier les numéros de ligne ET la claim de complétude ("X % propre") par un sweep indépendant ciblé sur les zones déclarées propres. Confirmer l'invariant read-only via `git status` / `git show --stat` (seuls les artefacts `_bmad-output/` doivent bouger).
**Convention de comptage obligatoire** : poser explicitement en tête du rapport la règle de comptage ("1 ligne de code = 1 hit, hors NOISE"). Résumé exécutif, tableaux de détail ET Completion Notes doivent citer les MÊMES chiffres. Un "Total" ne doit jamais additionner un ensemble et son sous-ensemble. En review, recompter à partir des tableaux de détail (source de vérité), pas des chiffres de tête.
---
<a id="pattern-audit-first-migration-large-scale"></a>
## Pattern : Audit-first sur migration large-scale
- Objectif : confronter le prémis de la story à la réalité du code AVANT d'engager une migration transverse, et permettre au scope de muter.
- Contexte : story enabler à effort large (2-3 j+) sur une migration transverse (theming, refacto archi, sécurité).
- Quand l'utiliser : avant toute migration > ~30 fichiers ou estimée > 2 j.
- Quand l'éviter : migration mécanique triviale à scope connu et fermé.
- Validé le : 29-05-2026
- Contexte technique : NestJS + Expo / BMAD — app-alexandrie
### Phases
1. **Audit (1-2 h)** : extraire la réalité (grep, métriques, dette). Rapport : nombre exact de fichiers/lignes, hotspots (top 10 concentrant >50% de la dette), cas spéciaux, 2-4 options stratégiques avec effort **révisé**.
2. **Arbitrage stakeholder** : présenter rapport + options. **Permettre au scope de muter** — l'audit révèle souvent que la story était mal cadrée.
3. **Migration découpée par phases** : chaque phase = livrable autonome (commit + push), pour pouvoir s'arrêter sans laisser l'app dans un état pire qu'avant.
### Délégation à un agent (migration > 30 fichiers, mode mécanique)
Brief structurant : liste **exhaustive** des fichiers (pas "trouve-les"), pattern canon AVANT/APRÈS, mapping sémantique pour les choix non-mécaniques (ex. `'red' → themed.error`), règles strictes ("ne déborde pas", "ne touche pas aux tests"), récap structuré attendu (occurrences/fichier, faux positifs, incidents).
Anti-pattern : engager une migration large sans audit — à mi-parcours on découvre un scope 2-3× plus gros, working tree déjà sali. Cas validé : ux-cleanup-8 (app-alexandrie) — "tokens light cassés" → en réalité pas de palette light du tout, scope 2-3 j → 4-7 j.
---
<a id="pattern-story-polish-multi-ecrans-sous-livraisons"></a>
## Pattern : Story "polish multi-écrans" en sous-livraisons logiques
- Objectif : garder pilotable une story qui agrège de nombreux items courts mais cohérents (4-8 écrans + transverses).
- Contexte : story de polish/cleanup couvrant plusieurs écrans avec des dépendances internes.
- Quand l'utiliser : story à N items cohérents trop gros pour 1 commit, mais qui perdraient leur cohérence en N stories.
- Quand l'éviter : items indépendants sans dépendance partagée (préférer des stories distinctes).
- Validé le : 29-05-2026
- Contexte technique : Expo / BMAD — app-alexandrie
### Découpage
A (helpers + composants génériques) → B (refonte des écrans utilisant A) → C (transverses post-refonte). **DoD lint + typecheck + tests à chaque palier** — signal anti-régression fort, prévient l'accumulation silencieuse.
Anti-patterns : (1) tout faire en 1 commit (context-window + review massive) ; (2) éclater en N stories (perd la cohérence, multiplie les cycles) ; (3) skipper la DoD intermédiaire.
HALT et escalader si : audit révèle > 3-4 points complexes inattendus / nouvelle dépendance / champ de contrat absent (décider : extension dans la story ou story dédiée). Cas validé : ux-cleanup-15 — 18 ACs / 3 sous-livraisons / 971 tests verts à chaque palier.
---
<a id="pattern-test-obsolete-durcissement-enum"></a>
## Pattern : Test obsolète après durcissement de type — supprimer plutôt que muter
- Objectif : ne pas conserver un test qui protège contre un cas devenu structurellement impossible.
- Contexte : durcissement d'un type (String → enum DB, `z.enum()`, union TS strict) qui rend une valeur invalide impossible en runtime.
- Quand l'utiliser : un test force une valeur invalide pour vérifier un fallback applicatif, et cette valeur ne peut plus arriver.
- Quand l'éviter : voir exceptions ci-dessous.
- Validé le : 05-05-2026
- Contexte technique : Prisma enum / Zod — RL799_V2
### Règle
Quand un test force une valeur invalide (`status: 'unknown_value'`) devenue **structurellement impossible** (rejetée par la DB/Zod/le compilateur) : **supprimer le test**, ne pas le muter (`as any`, `@ts-expect-error`). Le filet structurel **remplace** le filet test ; un test qui valide un cas impossible ne protège plus contre rien et porte une charge de maintenance à chaque évolution du type. Documenter la suppression dans le commit.
### Quand garder/muter quand même
- Test E2E qui simule un INSERT admin (raw SQL bypass) : garder.
- Test de boundary sur entrées externes (HTTP body avant Zod) : garder.
- Test unitaire qui force une valeur via mock pour tester un branch : garder, en re-typant proprement le mock.
---
<a id="pattern-generation-palette-stitch-mcp"></a>
## Pattern : Génération de variante de palette via Stitch MCP
- Objectif : générer une variante d'un design system existant (ex. light depuis dark) sans repartir de zéro.
- Contexte : projet avec un design system Stitch (Google) déjà validé via `mcp__stitch__list_design_systems`.
- Quand l'utiliser : besoin d'une variante de palette cohérente avec un seed/source existant.
- Quand l'éviter : pas de design system Stitch ; ou besoin programmatique de récupérer les hex (voir limite ci-dessous).
- Validé le : 29-05-2026
- Contexte technique : Stitch MCP — app-alexandrie
### Limites connues (vérifiées 2026-05-29)
1. **`namedColors` n'est PAS retourné** par `create_design_system` ni `update_design_system` — la palette n'est visible que via l'UI Stitch web. Workaround : ouvrir `https://stitch.withgoogle.com/projects/<projectId>`, copier l'objet `namedColors`.
2. **`update_design_system` retourne une session** (`projects/{id}/sessions/{id}`) — l'update est asynchrone / stocké en draft.
3. Récupérer la palette finale par programme nécessite probablement `apply_design_system` sur un screen test (non confirmé).
### Workflow recommandé
Créer le design system avec le `designMd` complet (philosophie + contraintes WCAG) → copier les `namedColors` depuis l'UI → coller dans `colors.ts` → auditer les ratios WCAG (texte ≥ 4.5:1, UI ≥ 3:1) → dériver manuellement les tokens custom hors MD3 standard.
Anti-pattern : attendre une réponse synchrone de l'API avec les hex — elle ne fonctionne pas ainsi.
+1 -1
View File
@@ -6,4 +6,4 @@ Risques liés au process de développement, aux agents BMAD, et au tracking des
| Fichier | Domaine | Entrées clés | | Fichier | Domaine | Entrées clés |
|---------|---------|--------------| |---------|---------|--------------|
| `story-tracking.md` | BMAD, agents, story completion | Story "completed" avec tâches ❌, story "done" sans fichiers source dans File List, statut BMAD correct mais sections narratives obsolètes, stratégie de fix d'une suite E2E qui rote en masse | | `story-tracking.md` | BMAD, agents, story completion, scope, traçabilité, tests | Story "completed" avec tâches ❌, story "done" sans fichiers source, statut BMAD vs sections narratives, fix d'une suite E2E en masse, enabler avec AC coûteux non-bloquant, tests "plumbing" vs "scénario", "test forwarding" vs intersection, reformat hors scope, code de "préparation" non testé, "fix CI" devient refacto, `xit`/skip sans story de suivi, AC déféré vers story fantôme, re-scope mid-PR, writer+reader décorrélés, écart AC "à confirmer", enabler sans traçabilité, statut "X% migré" non grepé, bump dépendance non déclaré, statut done sans commit, suppression feature + artefacts orphelins, lot soudé = commit groupé, review = suite complète, infra VERSIONNÉ vs EXÉCUTÉ, auto-déclaration de périmètre, Completion Notes vs code, doublon d'emplacement story, composant sans point d'entrée UI, page sans câblage navigation |
@@ -339,3 +339,505 @@ source_projects: [app-alexandrie, app-template-resto, RL799_V2]
- Traiter une divergence narrative comme un signal de doute sur la complétion réelle, pas comme un simple oubli cosmétique - Traiter une divergence narrative comme un signal de doute sur la complétion réelle, pas comme un simple oubli cosmétique
- Contexte technique : BMAD / workflow agent — app-alexandrie 01-04-2026 - Contexte technique : BMAD / workflow agent — app-alexandrie 01-04-2026
---
<a id="risque-story-enabler-ac-couteux-non-bloquant"></a>
## Story enabler avec AC coûteux non-bloquant
### Risques
- Une story "foundations" / "infrastructure" reste `in-progress` des semaines/mois alors que les stories métier qu'elle débloquait sont toutes livrées
- Le sprint-status est pollué durablement, la vraie progression est masquée
### Symptômes
- Story enabler `in-progress` depuis > 14 jours ; ≥ 2 epics métier postérieurs en `done`/`in-progress` ; la majorité des AC de l'enabler sont cochés (preuve par les faits que l'AC coûteux n'était pas bloquant)
### Bonnes pratiques / mitigations
- **Correction rétroactive** : identifier les AC réellement bloquants (les epics suivants ont-ils pu être livrés sans ?), clore l'enabler en `done` avec Completion Note de dérogation, extraire les AC déférés vers une story enabler hors-epic métier (`epic-infra/...`).
- **Prévention** : au sizing, appliquer le test "si cet AC n'était pas livré, pourrais-je démarrer la story 1.2 ?" → si oui, c'est une story séparée. CI mobile E2E (Maestro/Detox) Android+iOS = **toujours** une story enabler séparée (coût runner macOS + flakiness).
- Contexte technique : BMAD — app-alexandrie 13-05-2026 (story 1.1 bloquée 68 jours par AC4 CI Maestro)
---
<a id="risque-test-ac-plumbing-vs-scenario"></a>
## Tests d'AC en mode "plumbing" plutôt que "scénario"
### Risques
- Un AC de seuil ("au 4e", "au 21e") est testé en mockant directement la valeur de retour (`incrWithExpireAt.mockResolvedValue(4)`) au lieu de simuler le parcours réel
- Régression silencieuse si la limite change (le mock continue de mentir) ou si l'incrément se casse (l'INCR n'est jamais appelé)
### Symptômes
- Le test valide le code d'erreur (`POST_QUOTA_EXCEEDED`) mais PAS que 1+2+3 passent et que 4 est rejeté
### Bonnes pratiques / mitigations
- Pour un AC de seuil, le test e2e doit : (1) simuler un compteur qui incrémente réellement (`mockImplementation` avec map d'état, pas `mockResolvedValue` constant) ; (2) effectuer les N appels + le N+1 ; (3) vérifier que les N premiers passent ET le N+1 est rejeté ET (si applicable) que la compensation a décrémenté.
- Contexte technique : BMAD / Redis quota — app-alexandrie 13-05-2026 (story 8.4)
---
<a id="risque-test-forwarding-vs-intersection"></a>
## "Test forwarding" confondu avec "test intersection"
### Risques
- Une task qui revendique tester une combinaison de filtres / un AND backend n'a comme seul test qu'un mock service client vérifiant que les params transitent
- Refacto futur = régression silencieuse de l'intersection
### Symptômes
- `expect(mockService).toHaveBeenCalledWith(token, { A: true, B: ['x'] })` → prouve uniquement que le store forward, PAS que le backend fait le AND
### Bonnes pratiques / mitigations
- Pour toute intersection/combinaison logique serveur, exiger un test e2e backend avec seed couvrant les 4 quadrants : match (A && B), A seul, B seul, ni l'un ni l'autre — vérifier que seul "match" remonte.
- Bonus discriminant AND vs OR : un filtre B incompatible doit renvoyer `items=[]`.
- Review : pour chaque task "combinaison X+Y testée", ouvrir le test et vérifier qu'il SEED des données différenciées et ASSERT le filtre, pas le forwarding.
- Contexte technique : BMAD — app-alexandrie 14-05-2026 (story 11.3)
---
<a id="risque-reformat-hors-scope"></a>
## Reformat / changement de fichiers hors scope dans un commit de story
### Risques
- Une story embarque silencieusement un reformat prettier ou un changement sur des fichiers non listés (ex. ~32 fichiers, ~2400 insertions de pur reformatage)
- Une régression réelle se noie dans le bruit ; surface de merge conflict énorme ; diff de revue faussé
### Symptômes
- `git status --porcelain` / `git diff --stat` montrent des fichiers absents de la File List ; un `prettier --write .` ou un auto-format on save a fait dériver le scope
### Bonnes pratiques / mitigations
- **Règle** : "un commit de story = la File List ± 0 fichier". Avant tout commit : vérifier `git status --porcelain` + `git diff --stat` ; tout fichier non listé → (a) l'ajouter à la File List avec justification, soit (b) `git checkout HEAD -- <files>` avant commit.
- Pour un reformat global voulu : story chore dédiée (`chore/prettier-pass`) sur sa propre branche.
- Contexte technique : BMAD / prettier — app-alexandrie 13-05-2026 (story 10.1)
---
<a id="risque-preparation-non-testee"></a>
## Code de "préparation" non testé (anticipation des stories suivantes)
### Risques
- Une story implémente du code spéculatif pour les stories suivantes ("préparation Story X.Y", "déjà câblé pour la suite") sans tests pour ces chemins
- Si la story suivante change de design, le code mort traîne
### Symptômes
- Dev Agent Record contient "Préparation Story X.Y" ; des branches logiques `if (opts.x)` n'ont aucun test
### Bonnes pratiques / mitigations
- **Règle** : toute branche logique introduite doit avoir AU MOINS un test l'adressant, même si la story qui l'utilisera arrive plus tard. Sinon on n'introduit pas le code maintenant (YAGNI strict).
- Review : compter les tests couvrant chaque `if (opts.x)` du nouveau code. Tests = AC, pas plus, pas moins.
- Contexte technique : BMAD — app-alexandrie 14-05-2026 (story 11.1)
---
<a id="risque-fix-ci-devient-refacto"></a>
## "Fix CI" qui devient un refacto architectural
### Risques
- Une story enabler (CI, deploy, infra) déclare un scope limité (5 fichiers) ; au premier run CI des steps préexistantes échouent ; le dev débloque le pipeline dans la même branche en touchant 40-70 fichiers sous un commit massif "fix(ci): débloquer pipeline"
- La File List ment, le commit message ment (un "fix lint" cache une refacto DI), les enablers parallèles deviennent ambigus, les rétros perdent leur valeur
### Symptômes
- Un commit "fix CI" touche > 10 fichiers / plus d'un module / des fichiers de production (pas seulement tests/config)
### Bonnes pratiques / mitigations
- **Option A (préférée)** : stop, créer une story sœur `infra-N+1`, traiter sur branche séparée puis cherry-pick — ou conditionner la step CI cassée et déférer.
- **Option B (acceptable)** : tout faire dans la branche MAIS commits séparés (1 = scope original, 1 = "fix lint préexistant", 1 = "refacto DI" message clair) + documenter l'extension de scope dans le Change Log.
- **Option C (interdite)** : commit géant unique "fix CI". Si déjà commis : créer rétroactivement les stories sœurs (status `done` immédiat + File List pointant vers le commit géant).
- Détection review : un "fix CI" qui touche > 10 fichiers ou > 1 module → exiger un re-split ou une story rétroactive avant merge.
- Contexte technique : BMAD / CI — app-alexandrie 20-05-2026 (infra-1, 5 → 73 fichiers)
---
<a id="risque-xit-supprime-couverture-critique"></a>
## `xit` / skip qui supprime de la couverture critique sans story de suivi
### Risques
- Un test e2e est marqué `xit` avec un commentaire `xit-reason:` honnête, mais la couverture du comportement critique disparaît
- Le commentaire donne l'illusion d'avoir tracé la dette ; aucun outil ne signale qu'un comportement n'est plus couvert
### Symptômes
- `xit('bloque une écriture community avec session PENDING (423)')` avec un `xit-reason:` qui ne cite aucune story de restauration
### Bonnes pratiques / mitigations
- Tout `xit`/`skip` doit déclencher soit (a) un fix immédiat avec setup ad-hoc (souvent 10-30 lignes de mock dynamique), soit (b) la création d'une story dédiée explicite qui le restaure.
- Le commentaire doit citer la story qui le ressuscitera (`xit-reason: HealthModule retiré, restauré par story infra-4`).
- Un `xit` sans story de suivi nommée = blocker en code review.
- Contexte technique : BMAD / e2e — app-alexandrie 20-05-2026 (infra-5)
---
<a id="risque-ac-defere-story-fantome"></a>
## AC déféré vers une story fantôme
### Risques
- Un AC est marqué `[DEFERRED]` avec référence à une story qui n'existe pas dans le tracking → l'AC est "tracé" mais en réalité perdu
### Symptômes
- `[DEFERRED AC5] ... (story infra-1-phase-2)``infra-1-phase-2` n'existe pas dans `_bmad-output/` ni dans `sprint-status.yaml`
### Bonnes pratiques / mitigations
- Ne JAMAIS déférer un AC vers une story inexistante. Si on défère, créer **immédiatement** la story cible (titre + AC repris + status `ready-for-dev`) et l'ajouter à `sprint-status.yaml`.
- Détection review : grep `DEFERRED|TODO|à faire dans` dans la story et vérifier que les références pointent vers des fichiers existants.
- Contexte technique : BMAD — app-alexandrie 21-05-2026 (infra-5/infra-6)
---
<a id="risque-rescope-mid-pr-renommer"></a>
## Re-scope mid-PR — renommer AVANT de merger
### Risques
- Une story/PR change de scope en cours de route mais le titre PR, le nom de fichier story et la clé sprint-status gardent l'ancien intitulé
- Le titre de PR se propage dans le merge commit et le displayTitle de TOUS les runs CI — historique GitHub durablement trompeur, non corrigeable après merge
### Symptômes
- Code et corps de la story re-scopés, mais slug `infra-7-reactivation-…` qui contient en réalité un "retrait"
### Bonnes pratiques / mitigations
- Checklist re-scope (les quatre ensemble, AVANT le merge) : titre PR + nom de fichier story (`git mv`) + clé `story_key` dans `sprint-status.yaml` + corps de la story.
- Contexte technique : BMAD / CI — app-alexandrie 22-05-2026 (infra-7)
---
<a id="risque-writer-reader-decorreles"></a>
## "Writer + reader" décorrélés dans une feature à état partagé
### Risques
- Une feature reposant sur un store / event bus / queue partagée nécessite DEUX intégrations (writer qui produit l'état, reader qui le consomme) ; si seul l'un est livré, l'AC est cassé alors que toutes les subtasks sont cochées
- Les tests unitaires en isolation passent (store + bouton OK séparément) ; l'intégration ne marche pas car personne n'alimente le store
### Symptômes
- Store créé + spec verte, reader appelle `lastRoutes[tab]`, mais le writer manque dans `_layout.tsx``lastRoutes` reste `{}` à vie
### Bonnes pratiques / mitigations
- À la rédaction de story : nommer explicitement les DEUX callsites ("créer le store" + "brancher le writer dans X" + "brancher le reader dans Y").
- Au dev : un test d'intégration minimal exerçant writer→reader.
- À la review : grep le nom de l'action mutator (`setLastRoute`, `enqueue`, `publish`). Si UN SEUL match (= le store lui-même), la feature est cassée même si toutes les tâches sont cochées.
- Contexte technique : BMAD — app-alexandrie 28-05-2026 (IA-v2.8)
---
<a id="risque-ecart-ac-a-confirmer-jamais-confirme"></a>
## Écart AC ↔ implémentation "à confirmer" qui n'est jamais confirmé
### Risques
- Un dev s'écarte d'un AC pour une raison légitime, documente l'écart dans les Completion Notes mais ne met PAS à jour l'AC → l'AC reste formellement non respecté et l'écart sera oublié à la prochaine relecture
### Symptômes
- AC dit "CTA vers `/(tabs)/feed`" alors que le code pousse `/(tabs)/explore`, avec une note "cosmétique, à confirmer"
### Bonnes pratiques / mitigations
- Documenter dans Completion Notes ne suffit PAS. **Amender l'AC lui-même** avec justification inline (l'AC est le contrat) OU créer un follow-up daté ("à confirmer d'ici 2026-XX-XX, sinon AC réécrit").
- Garde-fou : "écart à confirmer" = finding MEDIUM ; la review doit trancher, pas laisser vivre l'ambigu.
- Contexte technique : BMAD — app-alexandrie 29-05-2026 (ux-cleanup-4)
---
<a id="risque-story-enabler-sans-tracabilite"></a>
## Story (enabler) sans sections de traçabilité
### Risques
- Une story dont les AC contiennent une checklist ("vérifier écran par écran", "smoke 5 écrans") est livrée sans Dev Agent Record, File List ni Change Log (fichier resté `Status: draft`)
- La review ne peut pas distinguer "pas fait" de "fait mais non documenté" → pression à passer en `done` sans preuve, ou revue manuelle linéaire (coût ×10)
### Symptômes
- 14 commits livrés, 0 trace structurée dans la story ; AC checklist non prouvables sans reconstitution a posteriori
### Bonnes pratiques / mitigations
- Toute story à AC-checklist doit comporter **dès le draft** : Dev Agent Record (tableau par phase commit), File List (ou pointer `git diff --name-only`), Change Log chronologique, et une checklist par AC avec statut (✓/⚠️/❌) par item.
- Garde-fou Bob/SM avant `ready-for-dev` : la story expose-t-elle ces 4 sections (au moins en placeholder) ?
- Contexte technique : BMAD — app-alexandrie 29-05-2026 (ux-cleanup-8)
---
<a id="risque-statut-migre-non-verifie-grep"></a>
## Statut "✅ X% migré" non vérifié par grep en fin de story
### Risques
- Le rapport d'audit de début de story ("110 occurrences") est repris tel quel dans le README final sans re-grep → le statut ment par optimisme (17 résiduelles)
- Un futur dev fait confiance au statut et n'audite plus jamais
### Symptômes
- README "✅ 110 occurrences remplacées" alors qu'un grep serré en révèle 17 résiduelles
### Bonnes pratiques / mitigations
- Ajouter en fin de workflow de migration un step "audit de vérification" : grep après migration DOIT être ≤ 0 (ou documenter chaque exception en code). Le statut README doit refléter ce grep, pas le rapport initial. Si 17 résiduelles → écrire "100/117 (85 %) migrées, 17 documentées".
- Contexte technique : BMAD / migration — app-alexandrie 30-05-2026 (ux-cleanup-12)
---
<a id="risque-bump-dependance-non-declare"></a>
## Bump de dépendance non déclaré dans une story
### Risques
- Une story bumpe `package.json` / `pnpm-lock.yaml` "en passant" sans le déclarer dans la File List ni les Dev Notes → pollution du commit, surprise du reviewer, risque silencieux sur d'autres stories utilisant la lib
### Symptômes
- Story "a11y touch targets" qui bumpe `react-native-reanimated` ; le reviewer le découvre en grep
### Bonnes pratiques / mitigations
- Bump **nécessaire** au scope → le déclarer (Dev Notes/Risques + File List). Bump **opportuniste** → story chore dédiée ou justification explicite. Changement de **pinning** (exact → `~`/`^`) → décision d'archi méritant une mention explicite ou une story dédiée.
- Garde-fou : le reviewer doit pouvoir grep `package.json` dans le diff et tracer chaque ligne modifiée.
- Contexte technique : BMAD — app-alexandrie 31-05-2026 (ux-cleanup-14/17)
---
<a id="risque-statut-done-sans-commit"></a>
## Statut `done`/`review` non atomique avec un commit Git pushé
### Risques
- Une story est marquée `done`/`review` dans `sprint-status.yaml` AVANT que son code soit commité/pushé : statut trompeur (WIP local cru en prod), perte si rollback d'une story partageant un fichier, tracking story → commit cassé
- Cas subtil : fichiers cœur jamais `git add` (untracked discrets `??`) alors que tests/lint/typecheck passent via le filesystem → repo orphelin, prod down
### Symptômes
- `sprint-status.yaml` dit `done` mais `git log --grep "story-name"` ne retourne aucun commit pushé
- `git status` montre des `??` non vus (concentration sur les `M`)
### Bonnes pratiques / mitigations
- Ordre atomique : implémenter (`in-progress`) → tests/lint/typecheck verts → **commit isolé****push origin** → PUIS changer le statut.
- Stories partageant un fichier : la story #1 doit être commit-push AVANT que #2 ne touche au fichier.
- Garde-fou : à la transition `review → done`, `git status --porcelain` ne doit laisser AUCUN `??` dans les répertoires de la story ; chaque "Nouveau fichier" du Dev Agent Record doit passer `git ls-files --error-unmatch <path>`.
- Contexte technique : BMAD / git — app-alexandrie 31-05-2026 (ux-cleanup-17/18/22)
---
<a id="risque-suppression-feature-artefacts-orphelins"></a>
## Suppression de feature et artefacts de planning orphelins
### Risques
- Une story SUPPRIME une feature ; le code est propre mais les artefacts de planning (wireframes, specs UX, PRD) qui la décrivaient "à créer" deviennent des pièges qu'un agent futur rejouera
### Symptômes
- Wireframe daté décrit toujours la feature supprimée comme cible (composants, routes, props) ; la code review du code seul ne le détecte pas
### Bonnes pratiques / mitigations
- Toute story de suppression doit inclure un AC "cohérence documentaire" : grep le nom de la feature dans `_bmad-output/planning-artifacts/` et annoter/invalider (bandeau "ABANDONNÉ <story>") tout doc qui la décrit encore. Ne pas se contenter du grep dans `src/`.
- Contexte technique : BMAD — app-alexandrie 02-06-2026 (classroom-0)
---
<a id="risque-lot-soude-revue-commit-groupes"></a>
## Lot soudé = revue et commit groupés (contrats partagés)
### Risques
- Plusieurs stories d'un lot touchent un MÊME contrat partagé et y ajoutent des champs **requis** consommés des deux côtés → elles forment une unité de compilation indivisible ; un commit story-par-story ne compile pas
### Symptômes
- Au moment de committer seulement les stories revues, le découpage est techniquement impossible (champs de contrat ajoutés par une story non incluse mais référencés par le code inclus)
### Bonnes pratiques / mitigations
- Quand plusieurs stories d'un lot modifient le même schéma Zod partagé avec des champs requis : soit (a) reviewer tout le lot et committer en bloc, soit (b) imposer dès le cadrage que les champs partagés soient `.optional()` tant que toutes les stories ne sont pas mergées (permet le commit incrémental).
- Détecter tôt : deux stories "can-run-with" qui modifient le même fichier de contrat → le découpage commit sera bloqué, l'anticiper au sprint-planning.
- Contexte technique : BMAD / contracts-first — app-alexandrie 05-06-2026
---
<a id="risque-review-suite-complete-pas-sous-ensemble"></a>
## Review = suite COMPLÈTE, pas le sous-ensemble de la story
### Risques
- Le dev lance des sous-ensembles ciblés (la suite du module touché), pas la suite entière → les fichiers de test transverses/partagés (mocks e2e d'autres modules, gardes d'exhaustivité jumelles) restent périmés et cassent en silence
- Une story qui annonce "e2e 78/78" sans avoir lancé la suite globale donne un faux signal de complétude
### Symptômes
- En review, lancer la suite complète révèle des fails dans des fichiers que la story N'A PAS touchés (mock e2e d'un autre module consommant une signature changée, garde "couvre exactement les N types" jumelle non mise à jour)
### Bonnes pratiques / mitigations
- **Règle review** : toujours lancer la suite entière (unit + e2e + mobile), jamais seulement la File List.
- Corollaire dev : après tout changement de signature publique (provider/service) ou ajout d'une valeur d'enum, `grep` tous les consommateurs de tests + lancer la suite complète avant de déclarer "tests verts".
- Contexte technique : BMAD — app-alexandrie 08-06-2026 (ux-parcours-3, bo-6, ux-parcours-7)
---
<a id="risque-story-infra-versionne-vs-execute"></a>
## Story infra/ops : distinguer VERSIONNÉ de EXÉCUTÉ-SUR-CIBLE
### Risques
- Sur une story infra (déploiement, backup, config service externe), l'agent produit des artefacts reproductibles mais ne peut PAS exécuter les gestes sur l'environnement réel (créer le realm, exécuter/TESTER le backup, smoke-test prod)
- Piège : marquer ces sous-tâches `[x]` parce que la PROCÉDURE est documentée alors que l'ACTION n'a pas eu lieu → la story surestime son achèvement, le geste à plus forte valeur ("restauration TESTÉE") reste un trou non signalé
### Symptômes
- Story dit `[x]` (theme appliqué, smoke-test, restauration testée) alors que la table de preuve du runbook est `(à remplir)` / `[ ]`
### Bonnes pratiques / mitigations
- **3 états, pas 2** : `[x]` = versionné/documenté ET vérifiable par l'agent ; `[~]` = artefact versionné mais exécution cible pending ; `[ ]` = geste ops non fait.
- Un AC "TESTÉE" (restauration, smoke-test, login réel) n'est JAMAIS satisfait par une procédure documentée — il exige une preuve d'exécution consignée (date/étapes/résultat).
- En review, croiser les `[x]` de la story avec le runbook ; lister les gestes hors-repo dans un Dev Agent Record dédié. La story reste `review` (artefacts prêts), `done` au déploiement réel prouvé.
- Contexte technique : BMAD / Keycloak prod — RL799_V2 15-06-2026 (K1.9)
---
<a id="risque-auto-declaration-perimetre-non-verifiee"></a>
## Auto-déclaration de périmètre d'une story (à vérifier contre `git diff --stat`)
### Risques
- Une étiquette "contenu uniquement" / "fix minimal" / "pas de code applicatif" est une CLAIM, pas un fait. Une story ainsi étiquetée peut livrer une refonte UI substantielle (+461 lignes nettes) documentée comme "2 fixes de typage minimaux", avec File List incomplète et "test:frontend vert" alors qu'il est rouge
### Symptômes
- Un "fix de typage minimal" qui touche un fichier passant de 274 à 735 lignes — l'écart entre la description et le `wc -l` est le tell
### Bonnes pratiques / mitigations
- Procédure review : (1) identifier le commit de référence (`Depends-on`/`Previous`), (2) `git diff --stat <ref>..HEAD` + working tree, (3) tout fichier > ~50 lignes de delta non listé dans la File List = finding MEDIUM minimum, (4) toute affirmation "suite X verte" = à REJOUER (un fichier de logique partagée qui change de signature casse un test préexistant non mis à jour en silence).
- Quand une refonte se glisse dans une story de contenu : exiger commit séparé + File List fidèle + tests propres.
- Contexte technique : BMAD — RL799_V2 22-06-2026
---
<a id="risque-completion-notes-vs-code-deux-options"></a>
## Completion Notes vs code, surtout quand deux options d'implémentation existaient
### Risques
- Quand une story propose une approche A et une alternative B, la Completion Notes peut affirmer avoir fait A alors que le dev a silencieusement choisi B (souvent le meilleur choix)
- Un reviewer qui croit la note sur parole rate l'écart ; la prochaine personne partira sur une fausse hypothèse
### Symptômes
- Completion Notes "ROLES_X étendu avec 'hospitalier'" alors que le bypass est réalisé autrement (au niveau repo) ; comportement réel correct mais note factuellement fausse
### Bonnes pratiques / mitigations
- Pour chaque tâche `[x]` mentionnant un fichier/symbole précis ("étendu le set X", "ajouté le champ Y"), ouvrir CE fichier et vérifier la présence réelle du changement — ne jamais cocher sur la foi de la note.
- L'écart "note dit A / code fait B" est un finding MEDIUM (doc trompeuse) même quand B est correct : c'est une note à corriger, pas du code à changer.
- Contexte technique : BMAD — RL799_V2 23-06-2026 (v2-5-3)
---
<a id="risque-doublon-emplacement-story-create-vs-dev"></a>
## Divergence d'emplacement create-story vs dev-story (fichiers homonymes)
### Risques
- `create-story` génère la story dans l'arborescence sprint sous-dossiée (`implementation-artifacts/version-X/<flow>/`, committée), mais `dev-story` écrit un fichier homonyme à PLAT dans `implementation-artifacts/` → deux fichiers : l'un committé/vierge, l'autre untracked/rempli (la vraie story)
- Une review peut se baser sur le mauvais fichier ou laisser un doublon désynchronisé
### Symptômes
- `find <implementation-artifacts> -name '<story-key>*.md'` retourne > 1 fichier ; l'un en `ready-for-dev`/tasks `[ ]`, l'autre en `review`/tasks `[x]`/Dev Record rempli
### Bonnes pratiques / mitigations
- Avant toute code-review : vérifier l'unicité du fichier de story (`find`). En cas de doublon, identifier la vraie story (status `review`/`done` + tasks cochées + File List), copier son contenu réel vers l'emplacement sprint COMMITTÉ, supprimer l'orphelin plat — préserver l'arborescence sprint comme source de vérité git.
- Contexte technique : BMAD — RL799_V2 23-06-2026 (v2-6-1)
---
<a id="risque-composant-livre-sans-point-entree-ui"></a>
## Composant "livré" sans point d'entrée dans l'UI (fonctionnalité mort-née)
### Risques
- Un composant créé (service + composable + vue + tests) mais non intégré dans aucune page/route est invisible : l'utilisateur ne peut pas y accéder
- Les tests passent (composant présentationnel monté isolément), ce qui masque l'absence d'intégration ; une tâche peut être `[x]` alors que le composant est orphelin
### Symptômes
- `SeasonReportView.vue` présent, testé, mais non importé dans aucune page ni dans le router
### Bonnes pratiques / mitigations
- Checklist revue frontend (si l'une des 3 est non → finding HIGH) : (1) le composant est-il importé dans au moins une page parente ? (2) la page parente est-elle accessible via une route ? (3) existe-t-il un chemin de navigation visible pour l'utilisateur cible ?
- Pattern de correction : intégrer dans la page parente existante (onglet) plutôt que créer une nouvelle page ; ajouter un sélecteur de contexte (saison, période) si besoin.
- Contexte technique : BMAD / Vue — RL799_V2 19-06-2026 (v2-3-1)
---
<a id="risque-page-module-sans-cablage-navigation"></a>
## Page/module livré sans câblage de navigation (dette fonctionnelle invisible)
### Risques
- Une page/module peut être 100% fonctionnel (backend gardé, page, service, tests verts, CI verte) et pourtant TOTALEMENT inaccessible faute de câblage navigation
- Classe de bug invisible : aucun test ne vérifie "ce module est atteignable"
### Symptômes
- "Je ne vois plus mon module X" alors que tout le code existe et que la CI est verte ; seul le câblage navigation manque
### Bonnes pratiques / mitigations
- Maillons à câbler (sur une app multi-surface mobile + desktop, ils sont SÉPARÉS — câbler l'un ne suffit pas) : (1) constante de rôles/permissions partagée + son ré-export (attention `export *` vs exports explicites) ; (2) route (path + name + meta de garde) ; (3) nav desktop (sidebar) + icône ; (4) nav mobile (liste/computed distincts) ; (5) tout hub/plan/menu de découverte secondaire qui mappe entité→route.
- Détection : à chaque nouvelle page, vérifier qu'une route Y MÈNE et qu'une entrée de nav LA RÉFÉRENCE, sur TOUTES les surfaces. Idéalement un test de garde "toute page de module a une route nommée + une entrée de nav".
- Prévention : la DoD d'un module inclut "accessible depuis la navigation sur mobile ET desktop", pas seulement "page + back + tests".
- Contexte technique : Vue / app multi-surface — RL799_V2 21-06-2026 (Epic v2-4)