From 81fde912597fa15f4b6da352df146dc34b0d1d95 Mon Sep 17 00:00:00 2001 From: MaksTinyWorkshop Date: Thu, 25 Jun 2026 15:48:53 +0200 Subject: [PATCH] =?UTF-8?q?docs(knowledge):=20capitalisation=20workflow=20?= =?UTF-8?q?+=20ux=20=E2=80=94=20int=C3=A9gration=20du=20triage=20local=20(?= =?UTF-8?q?mai-juin=202026)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- knowledge/ux/patterns/README.md | 3 +- knowledge/ux/patterns/general.md | 205 ++++++++ knowledge/ux/risques/general.md | 119 +++++ knowledge/workflow/patterns/README.md | 2 +- knowledge/workflow/patterns/general.md | 262 +++++++++- knowledge/workflow/risques/README.md | 2 +- knowledge/workflow/risques/story-tracking.md | 502 +++++++++++++++++++ 7 files changed, 1089 insertions(+), 6 deletions(-) create mode 100644 knowledge/ux/patterns/general.md create mode 100644 knowledge/ux/risques/general.md diff --git a/knowledge/ux/patterns/README.md b/knowledge/ux/patterns/README.md index a3d2f85..f4c1c88 100644 --- a/knowledge/ux/patterns/README.md +++ b/knowledge/ux/patterns/README.md @@ -8,5 +8,4 @@ Avant toute proposition UX, identifie le fichier dont le nom et la description m | Fichier | Domaine | Entrées clés | |---------|---------|--------------| - -_(aucune entrée pour le moment — à alimenter via `95_a_capitaliser.md`)_ +| `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 | diff --git a/knowledge/ux/patterns/general.md b/knowledge/ux/patterns/general.md new file mode 100644 index 0000000..aa579ca --- /dev/null +++ b/knowledge/ux/patterns/general.md @@ -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. + +--- + + +## 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) + +--- + + +## 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) + +--- + + +## 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 : `` 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) + +--- + + +## 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) + +--- + + +## 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) + +--- + + +## 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/.ts` par domaine exposant : un `Record` interne `WORDING_FALLBACK` (générique), une fonction `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 + +--- + + +## 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 ? : }`. 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) + +--- + + +## 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) + +--- + + +## 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) diff --git a/knowledge/ux/risques/general.md b/knowledge/ux/risques/general.md new file mode 100644 index 0000000..cbde18b --- /dev/null +++ b/knowledge/ux/risques/general.md @@ -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. + +--- + + +## 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 `` 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 `` 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 `` 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 `` dans un ``. + +- Contexte technique : RN/Expo + TopBar globale — app-alexandrie 28-05-2026 (ux-cleanup-2) + +--- + + +## 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 + +- `` / `` 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 ` +## 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) + +--- + + +## 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) + +--- + + +## 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) diff --git a/knowledge/workflow/patterns/README.md b/knowledge/workflow/patterns/README.md index 172f501..c00042c 100644 --- a/knowledge/workflow/patterns/README.md +++ b/knowledge/workflow/patterns/README.md @@ -8,4 +8,4 @@ Avant toute proposition workflow, identifie le fichier dont le nom et la descrip | 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 | diff --git a/knowledge/workflow/patterns/general.md b/knowledge/workflow/patterns/general.md index 2eac33e..462f648 100644 --- a/knowledge/workflow/patterns/general.md +++ b/knowledge/workflow/patterns/general.md @@ -5,8 +5,8 @@ bucket: patterns tags: [review, adversarial, git, chantier, agent] applies_to: [analysis, implementation, review] severity: high -validated_on: 2026-04-27 -source_projects: [RL799_V2] +validated_on: 2026-06-25 +source_projects: [RL799_V2, app-alexandrie] --- # Workflow — Patterns : Général @@ -155,3 +155,261 @@ Les `index` et `@@` viennent du `git diff` original — pas besoin de recalculer ### Communication au user > *"Sub-agent X signale 4 fichiers, j'ai validé 1/4 et invalidé 3/4. Voici le plan d'action corrigé."* + +--- + + +## 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 (``, ``, `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 `