capitalisation: triage 95_a_capitaliser + création domaine infra

Triage des 27 propositions du buffer de capitalisation (skill
capitalisation-triage), avec vérification des doublons contre la base.

Intégré dans knowledge/ (23 entrées):
- backend: redis (compensation incrBy non-atomique), nestjs (injection
  cassée sous tsx watch; guard write mode dégradé), async (test rollback
  pipeline multi-fichiers), contracts (idempotence POST), auth (disclosure
  comptes soft-deleted), prisma (index partial soft-delete), llm-providers
  (nouveau: OAuth vs API key, prompt caching).
- frontend: tests (garde-fous parking Later), navigation (fichiers
  non-route sous src/app Expo Router), general (type client vs payload
  backend), state (fallback catch-all mapping DB→UI).
- workflow: story-tracking (statut BMAD vs narratif obsolète).
- product: general (nouveau: doc feature store sans UI).
- infra: NOUVEAU DOMAINE (traefik, tailscale, docker, docker-networking,
  reverse-proxy-paths, sidecar tailscale) + 00_INDEX.md.

Autres:
- 90_debug_et_postmortem.md: post-mortem réseau Docker partagé hors compose.
- Rejeté 3 doublons (types enum contracts, getter PrismaService, $transaction).
- Buffer 95_a_capitaliser.md purgé et restauré à son état initial.
- _projects.conf: MAJ statuts epics + ajout app-rl799.
This commit is contained in:
MaksTinyWorkshop
2026-06-25 10:31:22 +02:00
parent 1c876309f1
commit ef24d85d57
31 changed files with 1042 additions and 27 deletions
+1 -1
View File
@@ -13,5 +13,5 @@ Avant toute proposition frontend, identifie le fichier dont le nom et la descrip
| `navigation.md` | Navigation, routing, Expo Router, intégrations tierces | Navigation réactive post-action async, link-out page locale canonique, factorisation page mode dynamique via `meta.mode` typé |
| `design-tokens.md` | Design tokens, typographie, spacing, Tailwind, RN StyleSheet | Tokens TypeScript Expo/RN, typography sémantique, export styles composant, grilles 2 colonnes |
| `nextjs.md` | Next.js App Router, embeds, ESLint | Click-to-load embeds tiers, ESLint flat config Next.js |
| `tests.md` | Tests styles React Native, smoke checks, mount + mock composable | Tests de styles sans renderer JSX, smoke checks `readFileSync`, classe CSS modifier vs texte, cleanup E2E best-effort, helpers SW purs, mount + mock composable, assertions React Email |
| `tests.md` | Tests styles React Native, smoke checks, mount + mock composable | Tests de styles sans renderer JSX, smoke checks `readFileSync`, classe CSS modifier vs texte, cleanup E2E best-effort, helpers SW purs, mount + mock composable, assertions React Email, garde-fous de non-activation feature parking Later |
| `general.md` | Focus visible, inputs date HTML5, journaux/audit logs, pages admin | Focus visible interne pour overflow clip, restyle global `<input type="date">`, UI patterns journaux d'audit, structuration pages admin (eyebrows + grille filtres + variante danger) |
+42 -2
View File
@@ -2,10 +2,10 @@
title: Frontend — Patterns : Tests
domain: frontend
bucket: patterns
tags: [tests, react-native, jest, styles, ui]
tags: [tests, react-native, jest, styles, ui, regression-guard, parking-later]
applies_to: [implementation, review]
severity: medium
validated_on: 2026-04-07
validated_on: 2026-06-25
source_projects: [app-alexandrie, RL799_V2]
---
@@ -518,3 +518,43 @@ assert.doesNotMatch(html, /<link\s+rel/i); // pas de preload (≠ mode mail)
assert.doesNotMatch(html, /<script\s+src/i);
assert.doesNotMatch(html, /<img[^>]+src=["'](?!data:)/i); // pas de http(s)
```
---
<a id="pattern-garde-fous-non-activation-parking-later"></a>
## Pattern : Garde-fous de non-activation pour features "parking Later"
### Synthèse
- **Objectif** : protéger une feature explicitement décidée "parking Later" (non implémentée en V1) contre une activation accidentelle, via des tests de non-régression couvrant **tous** ses points d'entrée futurs.
- **Contexte** : une décision de design liste N fonctions/écrans comme points d'injection futurs de la feature (ex : N fonctions de fetch où un futur filtre par auteur sera ajouté).
- **Quand l'utiliser** : dès qu'une feature est parkée mais que son chemin d'activation futur est déjà identifié.
- **Quand l'éviter** : feature sans aucun point d'entrée prévu (rien à protéger).
### Analyse
- **Avantages** :
- documente l'intention de non-activation à l'endroit où le dev futur travaillera
- détecte une activation partielle ou accidentelle dans n'importe lequel des points d'entrée
- **Limites / vigilance** :
- **un garde-fou partiel donne une fausse confiance** : couvrir une seule fonction laisse les autres points d'injection sans protection
- **règle** : si le design futur liste N fonctions/écrans, écrire N tests de non-présence correspondants (N fonctions → N tests)
### Validation
- Validé le : 25-06-2026
- Contexte technique : React Native / Jest — app-alexandrie
### Implémentation
```typescript
// ✅ CORRECT : tous les points d'entrée du design futur couverts
it('fetchEntries ne filtre pas par auteur', ...)
it('loadMoreEntries ne filtre pas par auteur', ...)
it('fetchEntriesByCategory ne filtre pas par auteur', ...)
// ❌ INSUFFISANT : un seul point d'entrée couvert
it('fetchEntries ne filtre pas par auteur', ...)
```
Le dev qui activera la feature plus tard ajoutera le comportement dans les N fonctions simultanément ; chaque point d'entrée doit avoir son test de non-activation.
+3 -3
View File
@@ -9,11 +9,11 @@ Avant toute proposition frontend, identifie le fichier dont le nom et la descrip
| Fichier | Domaine | Entrées clés |
|---------|---------|--------------|
| `auth.md` | Auth, guards de rôle, entitlements, OAuth | Auth côté client, loading infini écran gated, bouton OAuth vide, guard rôle flash UX |
| `state.md` | Zustand, state management, erreurs async, optimistic UI | Erreurs silencieuses, catch sans feedback, auto-reset état dégradé, fire-and-forget refresh, boolean UI hardcodé, flag isLoading unique, erreur sans rethrow, optimistic update sous-listes |
| `navigation.md` | Expo Router, Vue Router, deep link, useEffect fetch, contexte store | Store vide deep link/reload, guard incomplet états terminaux, collection sans clé contexte, double route racine, router-link disabled, état local query param |
| `state.md` | Zustand, state management, erreurs async, optimistic UI | Erreurs silencieuses, catch sans feedback, auto-reset état dégradé, fire-and-forget refresh, boolean UI hardcodé, flag isLoading unique, erreur sans rethrow, optimistic update sous-listes, fallback catch-all mapping statut DB → UI |
| `navigation.md` | Expo Router, Vue Router, deep link, useEffect fetch, contexte store | Store vide deep link/reload, guard incomplet états terminaux, collection sans clé contexte, double route racine, router-link disabled, état local query param, fichiers non-route sous `src/app` Expo Router |
| `design-tokens.md` | Design tokens, spacing, Tailwind, StyleSheet RN | Double système espacement, dimensions via spacing, inline styles dashboard, classes Tailwind invalides |
| `nextjs.md` | Next.js App Router, SSR, Server Actions, sécurité | useSearchParams sans Suspense, type ViewData dupliqué, composant React .ts, double validation segment, consent state ambigu, script inline XSS, window.location.reload, useTransition snapshot, window.confirm, import type server, img natif, useTransition global liste, formulaire defaultValue sans key |
| `tests.md` | Jest, ts-jest, tests React Native, Vue | Config node bloque .tsx, faux test négatif, helpers copiés, test écran indirect, test façade flux réel, tests présence textuelle |
| `performance.md` | Re-renders, memoization, useCallback, fetch | Sur-renders bundle non maîtrisé, useCallback inutile inline, fetch sans timeout |
| `react-native.md` | React Native, fetch, ScrollView, TextInput | Focus ring TextInput, contentInset iOS-only, fetch sans response.ok |
| `general.md` | Accessibilité, regex, patterns transversaux, monorepo | Accessibilité oubliée a11y, regex globale singleton lastIndex, Alert.prompt iOS-only, primitive UI couplée, migration partielle classes legacy, ARIA roles sans clavier, duplication logique métier monorepo, event listeners globaux modales, boutons imbriqués, fire-and-forget sans feedback |
| `general.md` | Accessibilité, regex, patterns transversaux, monorepo | Accessibilité oubliée a11y, regex globale singleton lastIndex, Alert.prompt iOS-only, primitive UI couplée, migration partielle classes legacy, ARIA roles sans clavier, duplication logique métier monorepo, event listeners globaux modales, boutons imbriqués, fire-and-forget sans feedback, type client non MAJ extension payload backend |
+29
View File
@@ -790,3 +790,32 @@ Garder `<fieldset>/<legend>` uniquement quand on accepte le rendu natif (groupe
- Contexte technique : HTML / a11y / CSS — RL799_V2 02-05-2026
---
<a id="risque-type-client-non-maj-extension-payload-backend"></a>
## Type client non mis à jour lors d'une extension de payload backend
### Risques
- Quand un nouveau champ est ajouté dans un objet retourné par une Server Action, le type TS côté client n'est pas toujours mis à jour en parallèle
- Si le retour de la Server Action est casté vers un type plus étroit, TypeScript accepte l'incompatibilité **silencieusement** : le champ existe au runtime mais reste invisible pour les consommateurs TS
### Symptômes
- Le payload contient un champ supplémentaire en runtime, mais le type client ne l'expose pas
- Une tâche "mettre à jour le test si le payload expose le nouveau champ" est marquée done en vérifiant le test, sans vérifier le type source qui l'alimente
- Aucune erreur de compile car le cast masque la divergence
### Bonnes pratiques / mitigations
Pour toute extension d'un payload backend (ajout de champ), appliquer une **checklist de propagation** :
1. Le type TS côté client (ex : type d'item média, type de l'entité)
2. Le contrat Zod si présent
3. Les fixtures de test qui typent le payload
4. Les composants qui déstructurent le payload (vérifier les accès au nouveau champ manquants)
- Règle : ne pas laisser cette propagation à la décision implicite du dev — l'expliciter comme checklist dans la story / la PR.
- Contexte technique : Next.js / Server Actions / TypeScript / Zod — app-template-resto 25-06-2026
---
+24
View File
@@ -296,3 +296,27 @@ const routes = [
- Contexte technique : Vue Router / navigation dynamique — RL799_V2 15-04-2026
---
<a id="risque-fichiers-non-route-sous-src-app-expo-router"></a>
## Fichiers non-route sous `src/app` avec Expo Router
### Risques
- Expo Router scanne récursivement `src/app` pour construire les routes : tout fichier TypeScript laissé dans cet arbre (`*.spec.ts`, `*.utils.ts`, `*.screen-logic.ts`) est traité comme une route potentielle, même s'il s'agit d'un test, d'un helper ou d'une logique d'écran
- Symptômes initiaux trompeurs : une succession de warnings au build puis un crash runtime qui ressemble à un problème d'app, alors que la cause est un simple fichier mal placé
### Symptômes
- Warning `Route "...spec.ts" is missing the required default export`
- Warning `Route "...screen-logic.ts" is missing the required default export`
- Erreur runtime `Property 'describe' doesn't exist` ou `Property 'jest' doesn't exist` dans Expo Go (le runtime de l'app charge un fichier de test)
### Bonnes pratiques / mitigations
- `src/app` ne doit contenir **que** des fichiers de routing Expo Router
- Déplacer helpers, utils, tests et logique d'écran ailleurs (`src/domains`, `src/features`, `src/shared`)
- **Diagnostic** : en debug mobile, si Expo Go remonte `describe` / `jest` au runtime, vérifier immédiatement qu'aucun fichier de test n'est resté sous `src/app`
- Contexte technique : Expo Router — app-alexandrie 25-06-2026
---
+38
View File
@@ -555,3 +555,41 @@ Trois cas typiques quand l'erreur apparaît après extraction :
**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
---
<a id="risque-fallback-catch-all-mapping-statut-db-ui"></a>
## Fallback catch-all dans le mapping statut DB → UI
### Risques
- Un mapping statut DB → statut UI avec un `return` catch-all final accepte implicitement **toute** nouvelle valeur d'enum DB sans alerte
- Une nouvelle valeur (ex : `pending`) atterrit alors dans la branche par défaut (ex : rendue comme `processing`) avec un libellé incorrect, violant silencieusement le contrat d'affichage
### Symptômes
- Une valeur d'enum DB non explicitement gérée affiche le mauvais état UI (ex : "En cours…" au lieu de "En attente")
- L'ajout d'une valeur d'enum côté DB ne déclenche aucune erreur de compile ni de test ; le mauvais libellé passe inaperçu
### Bonnes pratiques / mitigations
```ts
// ❌ Le catch-all masque silencieusement "pending" sous "processing"
function dbStatusToUiStatus(s: DBStatus): UIStatus {
if (s === "ready") return "ready";
if (s === "failed") return "failed";
return "processing"; // "pending" atterrit ici sans libellé correct
}
// ✅ Mapping exhaustif : un if par valeur d'enum
function dbStatusToUiStatus(s: DBStatus): UIStatus {
if (s === "ready") return "ready";
if (s === "failed") return "failed";
if (s === "pending") return "pending";
return "processing";
}
```
- Règle : chaque valeur de l'enum DB doit avoir son propre `if` dans le mapping. Éviter le `return` catch-all final qui absorbe les valeurs non prévues.
- Contexte technique : frontend / mapping statut DB → UI — app-template-resto 25-06-2026