capitalisation: intégration ~60 entrées RL799_V2 (triage 2026-05-02)

Triage du 95_a_capitaliser.md (~75 propositions) :
- 60 entrées intégrées dans knowledge/ (backend, frontend, workflow)
- 4 nouveaux fichiers : backend/patterns/tests.md, backend/risques/tests.md,
  frontend/patterns/general.md, workflow/patterns/general.md
- 6 doublons rejetés
- Mise à jour des READMEs index pour refléter les nouvelles entrées
- 95_a_capitaliser.md restauré à sa structure initiale
- 40_decisions_et_archi.md : décision mono-tenant déployable vs SaaS multi-tenant
- 90_debug_et_postmortem.md : sub-agents Write indisponible, effet iceberg CI,
  prisma migrate diffs cosmétiques

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MaksTinyWorkshop
2026-05-02 22:12:44 +02:00
parent 02ad0de258
commit b3417ad77b
31 changed files with 5370 additions and 12 deletions

View File

@@ -382,3 +382,176 @@ followingsError: string | null; // erreur de fetchFollowings
- Règle : dans un store qui gère à la fois des mutations et des listes paginées, chaque opération doit avoir sa propre clé d'erreur
- Contexte technique : React Native / Zustand — app-alexandrie review 5.3, 28-03-2026
---
<a id="risque-emit-vue-mutation-serveur-sans-listener"></a>
## `emit` Vue annonçant une mutation serveur sans listener parent → caches stale
### Risques
- Un composant enfant émet un événement (`emit('approved')`) après une mutation côté serveur, mais aucun parent n'écoute
- L'enfant met bien à jour son état local, mais les caches parents qui dérivent du même statut (badges accordion, verrous cascade, prop `previousAggregate` consommée par d'autres enfants) restent stale **indéfiniment**, jusqu'au prochain changement d'écran/route
- Bug invisible dans les tests structurels (`content.includes`) et passe inaperçu en revue parce que le code enfant est "correct"
### Symptômes
- Tout autre affichage du même statut dans le parent ou dans des sous-composants frères reste "Publiée" alors que la planche est "Approuvée" en DB
- L'UI ne se rafraîchit qu'au prochain reload manuel ou navigation
### Bonnes pratiques / mitigations
```bash
# Repérer les emits qui annoncent une mutation serveur
grep -rn "defineEmits" apps/frontend/src/components
# Pour chaque emit trouvé, chercher s'il a au moins un listener parent
grep -rn "@<eventName>=" apps/frontend/src/pages apps/frontend/src/components
```
Zéro listener = bug latent (sauf si l'emit est purement informatif — analytics, debug).
**Règle** : tout `emit` qui annonce une **mutation persistée serveur** (création, suppression, changement de statut, validation) doit avoir au moins un listener parent qui :
1. Invalide les caches locaux dérivés de la même donnée (Map de statuts, computed, props transitives)
2. Recharge la slice agrégée si le parent passe une prop construite à partir d'un autre fetch (`previousAggregate`, dashboard data)
3. **Ne se contente PAS de l'optimistic update** de l'enfant — la source de vérité reste serveur, le cache parent doit refléter l'état serveur post-mutation
```vue
<!-- Enfant émet, parent ignore. Le badge de l'accordion reste stale -->
<PlancheTraceeCard :tenue-id="planche.tenueId" mode="previous" />
<!-- Listener explicite qui invalide les caches dérivés -->
<PlancheTraceeCard
:tenue-id="planche.tenueId"
mode="previous"
@approved="onPlancheApproved(planche.tenueId)"
/>
```
**Couverture** : test de mount complet via `@vue/test-utils` qui simule l'événement et vérifie que le rendu parent change. Les tests `readFileSync + content.includes('emit')` valident que le code enfant émet, pas que le parent écoute.
- Contexte technique : Vue 3 — RL799_V2 29-04-2026
---
<a id="risque-templates-vue-references-orphelines"></a>
## Templates Vue — références orphelines invisibles à `tsc --noEmit`
### Risques
- Une variable supprimée du `<script setup>` mais encore référencée dans le `<template>` ne génère pas d'erreur compile avec `tsc --noEmit` seul (Volar moins strict que `tsc` pur sur les expressions template)
- Le composant crashe **uniquement au runtime** : `Cannot read properties of undefined` ou `[Vue warn] Property "X" was accessed during render but is not defined`
### Symptômes
- Refactor où on extrait un composable et oublie de destructurer une variable, ou on retire un import devenu (apparemment) inutilisé
- Typecheck passe, tests structurels passent, page charge mais une section ne s'affiche pas / un bouton ne fait rien
- `[Vue warn]` dans la console au mount
### Bonnes pratiques / mitigations
**Recommandation outillage** : migrer le `typecheck` du projet de `tsc --noEmit` vers `vue-tsc -p tsconfig.typecheck.json --noEmit`. Avec `vue-tsc`, les expressions template sont strictement typées contre le `<script setup>` exposé. Une ref orpheline → erreur de compile, pas warning runtime.
**Checklist étendue avant de marquer une extraction "done"** :
```bash
# Pour chaque symbole supprimé/non destructuré, grep dans le template
for symbol in normalizedQuery directoryOfficeLabel; do
echo "=== $symbol ==="
# Patterns template critiques
grep -nE "v-(if|else-if|show)=\"[^\"]*\\b$symbol\\b" apps/frontend/src/pages/<Page>.vue
grep -nE "\\{\\{[^}]*\\b$symbol\\b" apps/frontend/src/pages/<Page>.vue
grep -nE "(:|@)[a-z-]+=\"[^\"]*\\b$symbol\\b" apps/frontend/src/pages/<Page>.vue
done
```
**QA visuel obligatoire post-refactor** : pour tout refactor qui touche une page importante, ouvrir la page en browser dev avant de pousser :
- naviguer aux états critiques (search, modals, toggle accordion)
- vérifier la console : zéro `[Vue warn]` ou `ReferenceError`
- tester au moins un workflow complet par section refactorée
- Contexte technique : Vue 3 / Volar / `vue-tsc` — RL799_V2 29-04-2026
---
<a id="risque-symboles-orphelins-suppression-bloc"></a>
## Symboles orphelins après suppression d'un bloc — checklist grep
### Risques
- Refactor où on supprime un bloc cohérent (state + computeds + handlers) en utilisant un Edit ciblé. Tout compile, tous les tests passent, mais au runtime : `ReferenceError: <symbole> is not defined` au mount
- Le symbole supprimé était encore référencé dans un **lifecycle hook** (`onMounted`, `onUnmounted`), un **watcher**, ou un **handler asynchrone** non inclus dans le bloc supprimé
### Symptômes
- Page qui ne mount plus après refactor, alors que typecheck OK + tests verts
- Erreur visible uniquement à `cmd+R` sur la page
### Bonnes pratiques / mitigations
```bash
# Pour chaque symbole déclaré dans le bloc à supprimer
for symbol in updatePointerType pointerMql closeInsertMenuOnOutsideClick; do
echo "=== $symbol ==="
grep -n "\\b$symbol\\b" apps/frontend/src/pages/<Page>.vue
done
```
Doit retourner **uniquement les lignes du bloc à supprimer**. Si une référence apparaît hors du bloc → ne pas supprimer sans traiter explicitement (déplacer, adapter, ou laisser).
**Lieux à vérifier en priorité** :
| Lieu | Fréquence du piège |
|------|--------------------|
| `onMounted(() => { ... })` | ⚠️⚠️⚠️ très fréquent |
| `onUnmounted(() => { ... })` | ⚠️⚠️⚠️ très fréquent (cleanup) |
| `watch(() => x, () => { fn() })` | ⚠️⚠️ fréquent |
| Handler async (`.then(() => fn())`) | ⚠️ rare mais existe |
| Computed dans une autre section du fichier | ⚠️ rare |
| Template (`@click`, `:disabled`) | typecheck attrape via SFC plugin |
**Garde-fou complémentaire** : QA visuel obligatoire post-refactor (cf. `risque-templates-vue-references-orphelines`).
- Contexte technique : Vue 3 — RL799_V2 29-04-2026
---
<a id="risque-extraction-vue-ts-bug-typage-latent"></a>
## Extraction `.vue` → composable `.ts` révèle des bugs de typage latents
### Risques
- Vue 3 + Volar compile les blocs `<script setup>` avec une stratégie d'inférence moins agressive que `tsc` strict pur. Un handler peut compiler dans le `.vue` si le runtime n'utilise pas le champ manquant
- Le composable extrait est compilé par `tsc -p tsconfig.typecheck.json` **hors contexte Vue** → toutes les règles strictes s'appliquent → la divergence de type devient une erreur bloquante
- C'est un effet de bord **positif** mais qui peut bloquer l'extraction tant qu'on n'a pas diagnostiqué la divergence
### Symptômes
```
Type 'X' is not assignable to type 'Y'.
The types of '<champ>.<sous-champ>' are incompatible…
Type 'A' is missing the following properties from type 'B': …
```
Cas typique : un emit Vue annonçait `Detail` (type pour la liste) alors que la fonction service renvoyait `Data` (type pour la mutation). Silencieux dans le `.vue` d'origine, devenu visible dans le `.ts` extrait.
### Bonnes pratiques / mitigations
Trois cas typiques quand l'erreur apparaît après extraction :
1. **Le type annoncé ne matche pas le type réellement émis** : aligner sur le type réellement émis plutôt que sur le type annoncé dans `defineEmits`. Si possible, corriger aussi la signature de l'émetteur — mais c'est un autre scope
2. **Le `.vue` exploitait un cast implicite** : `v-if="x.foo"` réduit le union type. Dans un `.ts` extrait, narrow explicitement avec un `if` ou un type guard
3. **Volar n'analysait pas un chemin de type complexe** : type récursif, génériques imbriqués, `Pick<...>` dans une union → extraire un alias intermédiaire propre dans `@<module>/types.ts`
**Quoi faire face à l'erreur** :
1. **NE PAS** mettre `as Foo` pour faire taire le compilateur — c'est probablement masquer le même bug sous un autre nom
2. Identifier lequel des deux types est correct (généralement celui que la fonction service / l'API renvoie réellement)
3. Aligner la signature du handler/composable sur ce type-là
4. Documenter dans un commentaire au-dessus du handler que le type émis diverge du type annoncé dans `defineEmits` (si on ne corrige pas l'émetteur dans le même refactor)
5. Ouvrir un TODO si la correction de l'émetteur est hors scope
**Recommandation outillage** : `vue-tsc` plutôt que `tsc` pur en typecheck (cf. `risque-templates-vue-references-orphelines`). Ce genre de divergence aurait été détecté **avant** le refactor.
- Contexte technique : Vue 3 / Volar / `vue-tsc` — RL799_V2 29-04-2026