mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-05-18 08:18:15 +02:00
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:
@@ -208,3 +208,270 @@ Sans état transitoire formel, les guards lisent des valeurs incomplètes et dé
|
||||
- Store : `hydrateStatus` + promesse partagée en cours pour les appels concurrents.
|
||||
- Guards : `await hydrate()` avant toute décision d'accès.
|
||||
- UI : fallback de rendu tant que l'hydratation n'est pas `ready`.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-refactor-monolithe-vue-sous-lots"></a>
|
||||
## Pattern : Refactor monolithe Vue — sous-lots Go/No-Go + ordre topologique
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : découper un composant Vue monolithique (> 1500 lignes script ou > 2000 lignes total) en composables + sous-composants livrés en commits successifs validés un à un.
|
||||
- **Contexte** : page Vue avec plusieurs responsabilités métier mêlées, peu ou pas de `describe`, sans sous-découpage interne.
|
||||
- **Quand l'utiliser** : fichier > 1500 lignes script, fenêtre de calme sans PR concurrente prévue, tests E2E robustes en place.
|
||||
- **Quand l'éviter** : page < 1000 lignes, pas de tests E2E pour servir de filet, ou planning serré sans Go/No-Go possible entre commits.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- aucune régression à chaque sous-lot validé indépendamment
|
||||
- helpers réutilisables émergent naturellement
|
||||
- reporter vitest / Playwright groupe les échecs par responsabilité
|
||||
- **Limites / vigilance** :
|
||||
- les `data-testid` E2E doivent être **copiés-collés exactement** dans les composants enfants (sinon les E2E rotent silencieusement)
|
||||
- les bindings template doivent rester alignés avec les noms destructurés du composable
|
||||
- le typecheck `tsc --noEmit` ne suffit pas — utiliser `vue-tsc` (cf. `frontend/risques/state.md` risque-templates-vue-references-orphelines)
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 29-04-2026
|
||||
- Contexte technique : Vue 3 / Composition API — RL799_V2 (4 pages refactorées, 17 commits, 644/644 tests verts)
|
||||
|
||||
### Stratégie en 3 étapes
|
||||
|
||||
1. **Audit complet préalable** : sections du template avec plages de lignes + rôle métier + `v-if` clé, blocs script regroupés par responsabilité avec évaluation `autonome` vs `couplé`, imports + composables + DTOs consommés, tests existants, couplages externes (deep-links, sélecteurs CSS).
|
||||
2. **Plan en sous-lots ordonnés par risque croissant** :
|
||||
- L1 : composables purement autonomes (zéro dépendance interne)
|
||||
- L2 : composants enfants auto-contenus (modales)
|
||||
- L3 : composables avec couplage modéré (cache + watchers)
|
||||
- L4 : composables avec couplage métier subtil (cascade, propagation)
|
||||
- L5 : composant enfant complexe (D&D, drag-handle conditionnel)
|
||||
3. **Go/No-Go explicite entre chaque lot** : 1 commit thématique par lot avec validation typecheck + tests + diff montré au pair avant push.
|
||||
|
||||
### Ordre topologique des dépendances dans le script post-refactor
|
||||
|
||||
Quand plusieurs composables se consomment mutuellement, respecter strictement l'ordre topologique (la déclaration de la donnée doit précéder son usage, sous peine de TDZ) :
|
||||
|
||||
```typescript
|
||||
// 1. Data centrale de la page
|
||||
const currentSoiree = ref<SoireeData | null>(null);
|
||||
const error = ref('');
|
||||
|
||||
// 2. Composables qui ne consomment que la data centrale
|
||||
const { lifecycle, isLiveView } = useSoireeLifecycle(currentSoiree, allTenuesCancelled);
|
||||
|
||||
// 3. Composables qui produisent des refs consommées par d'autres
|
||||
const { responsesData } = useResponseTracking(currentSoiree, error);
|
||||
|
||||
// 4. Composables qui consomment les refs produites
|
||||
const { pastActiveGrade } = useGradeSelection(soireeTenues, responsesData);
|
||||
```
|
||||
|
||||
### Anti-patterns
|
||||
|
||||
- ❌ Grouper plusieurs préoccupations dans un même composable juste parce qu'elles sont voisines dans le script
|
||||
- ❌ Sortir un composable qui consomme directement `process.cwd()` ou un store global sans le passer en argument (couplage caché)
|
||||
- ❌ Extraire le CSS scoped vers le composant enfant **avant** de vérifier que toutes les classes y sont effectivement utilisées (certaines classes vivent en CSS global)
|
||||
- ❌ Sauter le grep des références orphelines avant de supprimer un bloc
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] `data-testid` copiés-collés exactement dans les composants enfants
|
||||
- [ ] Bindings template alignés avec les noms destructurés
|
||||
- [ ] Props/events des composants enfants alignés avec les usages
|
||||
- [ ] `vue-tsc` (pas `tsc`) en vérification typecheck
|
||||
- [ ] QA visuel obligatoire post-refactor (mount réel en browser)
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-convention-pages-module-scope"></a>
|
||||
## Pattern : Convention `pages/<module>/{composables,components,utils,__tests__}/`
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : structurer une app Vue qui dépasse 20 pages avec plusieurs domaines métier, en regroupant la logique extraite par module.
|
||||
- **Contexte** : app Vue avec routing par page et logique extraite (composables, sous-composants, tests).
|
||||
- **Quand l'utiliser** : page > 1000 lignes envisage le scope, > 1500 lignes le crée systématiquement.
|
||||
- **Quand l'éviter** : page < 500 lignes sans logique extraite — laisser au niveau racine.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- un module métier = un sous-dossier, navigation simplifiée
|
||||
- l'alias `@/pages/<module>/<X>` rend les fichiers résilients aux déplacements
|
||||
- les tests d'un module vivent avec lui (pas dans un dossier global qui mélange tout)
|
||||
- **Limites / vigilance** :
|
||||
- les tests scopés calculent leur `root` avec **4 niveaux** de remontée (`'../../../..'`), pas 3 — source d'erreur fréquente lors d'un déplacement
|
||||
- le dossier `pages/__tests__/` global reste réservé aux tests transverses + tests des pages legacy
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 29-04-2026
|
||||
- Contexte technique : Vue 3 / Vite / Vitest — RL799_V2 (5 modules scopés, 35 fichiers extraits)
|
||||
|
||||
### Structure type
|
||||
|
||||
```
|
||||
pages/<module>/
|
||||
├── <Module>Page.vue # page principale (carcasse + template)
|
||||
├── <Autre>Page.vue # autres pages du même module si existent
|
||||
├── composables/ # logique métier extraite
|
||||
│ ├── use<X>.ts
|
||||
│ └── use<Y>.ts
|
||||
├── components/ # sous-composants .vue scopés au module
|
||||
├── utils/ # helpers purs (formatters, defensive wrappers)
|
||||
├── styles.css # CSS partagé non-scoped (cf. pattern dédié)
|
||||
└── __tests__/ # tests scopés au module
|
||||
```
|
||||
|
||||
### Ce qui reste dans `pages/__tests__/` global
|
||||
|
||||
Trois cas légitimes uniquement :
|
||||
|
||||
1. **Tests transverses** qui couvrent plusieurs modules (`lifecycleUnification.test.mjs`)
|
||||
2. **Tests d'infrastructure** non rattachés à un module métier (`OfflineIntegration.test.mjs` pour le SW PWA)
|
||||
3. **Tests des pages encore à plat** (legacy non-encore scopées) — `LoginPage.test.mjs` reste à `pages/__tests__/` tant que `LoginPage.vue` est à `pages/`
|
||||
|
||||
### Calcul de `root` dans les tests scopés
|
||||
|
||||
```typescript
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
// 4 niveaux : __tests__ → <module> → pages → src → frontend
|
||||
const root = resolve(here, '../../../..');
|
||||
```
|
||||
|
||||
Si le test crashe avec `ENOENT: no such file … '<frontend>/src/src/pages/...'`, c'est que le `root` n'a pas été ajusté.
|
||||
|
||||
### Imports : alias `@/` plutôt que relatif
|
||||
|
||||
Toujours utiliser l'alias `@/pages/<module>/<X>` plutôt que `./X` ou `../X`. Bénéfice : déplacer un fichier ne casse pas ses imports internes (juste les imports depuis l'extérieur, qu'on met à jour via sed bulk).
|
||||
|
||||
### Critère extraction composable vs composant
|
||||
|
||||
| Cas | Préférer composable | Préférer composant |
|
||||
|-----|---------------------|---------------------|
|
||||
| Logique pure (state + actions, pas de markup) | ✓ | |
|
||||
| Modale auto-contenue, > 30 lignes template | | ✓ |
|
||||
| Form > 50 lignes avec validation | | ✓ |
|
||||
| Plusieurs refs/computeds entrelacés | ✓ | |
|
||||
| CSS spécifique > 50 lignes | | ✓ (avec styles.css si partagé) |
|
||||
|
||||
Préférence générale : composables script-only quand possible (risque CSS nul, plus simple à tester).
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-styles-css-module-non-scoped"></a>
|
||||
## Pattern : `styles.css` partagé non-scoped pour modules avec composants extraits
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : partager des classes CSS entre la page parente et ses sous-composants extraits sans dupliquer le CSS dans chaque `<style scoped>` enfant.
|
||||
- **Contexte** : refactor d'une page Vue monolithique en N composants enfants (modales, forms) qui partagent des classes communes.
|
||||
- **Quand l'utiliser** : ≥ 2 composants enfants partagent des classes (modales, forms, badges du module).
|
||||
- **Quand l'éviter** : composants enfants strictement indépendants, ou règle CSS utilisée nulle part ailleurs.
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- une seule définition par classe partagée
|
||||
- dérive impossible (un changement profite à tous les composants du module)
|
||||
- **Limites / vigilance** :
|
||||
- tentation de tout sortir en non-scoped "au cas où" → refusé, pollue le namespace global
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 29-04-2026
|
||||
- Contexte technique : Vue 3 / scoped CSS — RL799_V2 (152 lignes CSS migrées dans `pages/venerable/styles.css`)
|
||||
|
||||
### Pattern
|
||||
|
||||
```vue
|
||||
<!-- pages/<module>/<Module>Page.vue -->
|
||||
<template>
|
||||
<!-- … -->
|
||||
</template>
|
||||
|
||||
<!-- Styles partagés du module : importés en non-scoped pour atteindre
|
||||
les composants enfants extraits (modales/forms) qui ne peuvent
|
||||
pas hériter d'un `<style scoped>` parent -->
|
||||
<style src="@/pages/<module>/styles.css"></style>
|
||||
|
||||
<style scoped>
|
||||
/* Classes spécifiques au layout de la page parente uniquement */
|
||||
</style>
|
||||
```
|
||||
|
||||
Les composants enfants utilisent les classes sans rien importer :
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="vm-modal"> <!-- vient de styles.css -->
|
||||
<h2 class="vm-modal__title">…</h2>
|
||||
<button class="primary">…</button> <!-- vient du CSS global -->
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Quoi mettre dans `styles.css`
|
||||
|
||||
- Classes utilisées par > 1 composant enfant du module
|
||||
- Classes utilisées par la page ET par un composant enfant
|
||||
- Conventions/tokens visuels propres au module
|
||||
|
||||
### Quoi NE PAS y mettre
|
||||
|
||||
- Classes utilisées uniquement dans la page parente → `<style scoped>` de la page
|
||||
- Classes utilisées uniquement dans un seul composant enfant → `<style scoped>` du composant
|
||||
- Classes globales (`primary`, `ghost`, etc.) qui vivent déjà dans `style.css` global
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-annuaire-client-side-ttl-refresh"></a>
|
||||
## Pattern : Annuaire client-side avec TTL + refresh + `lastFetchedAt`
|
||||
|
||||
### Synthèse
|
||||
|
||||
- **Objectif** : permettre un load complet en mémoire au mount avec filtre client (annuaire de N membres, catalog de produits) tout en évitant le refetch inutile entre ouvertures.
|
||||
- **Contexte** : composable Vue qui charge un dataset moyen (< quelques milliers d'items) consommé par filtres client.
|
||||
- **Quand l'utiliser** : modale ou page consultée fréquemment qui n'a pas besoin de pagination serveur.
|
||||
- **Quand l'éviter** : dataset > 10k items (utiliser pagination/keyset), ou besoin de temps réel cross-clients (basculer SSE/WS).
|
||||
|
||||
### Analyse
|
||||
|
||||
- **Avantages** :
|
||||
- cache TTL configurable (par défaut 5 min) → pas de refetch entre ouvertures
|
||||
- `refresh()` méthode publique pour forcer après création/update
|
||||
- `lastFetchedAt` exposé pour debug / UI ("annuaire mis à jour il y a X")
|
||||
- **Limites / vigilance** :
|
||||
- si user B crée une entrée pendant que user A a la modale ouverte, A ne voit pas l'entrée tant qu'il ne ferme/rouvre pas ou clique refresh
|
||||
- TTL atténue mais ne résout pas — pour temps réel, basculer SSE/WS
|
||||
|
||||
### Validation
|
||||
|
||||
- Validé le : 01-05-2026
|
||||
- Contexte technique : Vue 3 / Composition API — RL799_V2
|
||||
|
||||
### Implémentation
|
||||
|
||||
```typescript
|
||||
const useEntityDirectory = (options: { ttlMs?: number } = {}) => {
|
||||
const ttlMs = options.ttlMs ?? 5 * 60 * 1000;
|
||||
const directory = ref<Entry[]>([]);
|
||||
const lastFetchedAt = ref<Date | null>(null);
|
||||
|
||||
const isCacheStale = (): boolean => {
|
||||
if (!lastFetchedAt.value) return true;
|
||||
return Date.now() - lastFetchedAt.value.getTime() > ttlMs;
|
||||
};
|
||||
|
||||
const loadDirectory = async (params: { force?: boolean } = {}) => {
|
||||
if (!params.force && !isCacheStale() && directory.value.length > 0) return;
|
||||
const data = await api.fetchDirectory();
|
||||
directory.value = data;
|
||||
lastFetchedAt.value = new Date();
|
||||
};
|
||||
|
||||
const refresh = () => loadDirectory({ force: true });
|
||||
return { directory, lastFetchedAt, loadDirectory, refresh };
|
||||
};
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user