Refonte Structure

This commit is contained in:
MaksTinyWorkshop
2026-03-25 08:32:13 +01:00
parent d8a947eb79
commit 9b7af9f1b0
55 changed files with 4743 additions and 4906 deletions

View File

@@ -37,30 +37,18 @@ Référence portable à utiliser dans les scripts et templates :
---
## Patterns validés
## Patterns validés & Risques
Patterns éprouvés en production ou en projets réels.
Organisés dans `knowledge/` par domaine. Chaque domaine a un sous-dossier `patterns/` et `risques/`, chacun avec un `README.md` servant d'index.
- `10_backend_patterns_valides.md`
- `10_frontend_patterns_valides.md`
- `10_ux_patterns_valides.md`
- `10_product_patterns_valides.md`
- `10_n8n_patterns_valides.md`
---
## Risques et antipatterns
Situations connues pouvant entraîner :
- bugs difficiles
- dette technique
- complexité inutile
- `10_backend_risques_et_vigilance.md`
- `10_frontend_risques_et_vigilance.md`
- `10_ux_risques_et_vigilance.md`
- `10_n8n_risques_et_vigilance.md`
| Domaine | Patterns | Risques |
| ------- | -------- | ------- |
| Backend | `knowledge/backend/patterns/` | `knowledge/backend/risques/` |
| Frontend | `knowledge/frontend/patterns/` | `knowledge/frontend/risques/` |
| UX | `knowledge/ux/patterns/` | `knowledge/ux/risques/` |
| n8n | `knowledge/n8n/patterns/` | `knowledge/n8n/risques/` |
| Product | `knowledge/product/patterns/` | `knowledge/product/risques/` |
| Workflow | — | `knowledge/workflow/risques/` |
---

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,830 +0,0 @@
# Patterns front-end validés
Ce fichier contient **uniquement** des patterns front-end :
- testés,
- validés,
- utilisés dans des projets réels (ou des apps complètes, pas des snippets isolés).
Il sert de **mémoire durable** pour éviter :
- de refaire les mêmes erreurs,
- de redélibérer éternellement sur des sujets déjà tranchés,
- de propager des “bonnes pratiques” théoriques non éprouvées.
Dernière mise à jour : 23-03-2026
---
## Index
- [Gestion explicite des états UI (loading / empty / error)](#pattern-etats-ui-loading-empty-error)
- [Séparation claire server state / client state](#pattern-separation-server-state-client-state)
- [Formulaire robuste avec validation et erreurs explicites](#pattern-formulaire-robuste)
- [Navigation réactive post-action async (React / Expo Router)](#pattern-navigation-reactive-post-action-async)
- [Refresh idempotent sur store de liste paginée](#pattern-refresh-idempotent-liste-paginee)
- [UI admin légère sur domaine existant](#pattern-ui-admin-legere-domaine-existant)
- [Intégration tierce en mode link-out — préférer une page locale canonique](#pattern-link-out-page-locale-canonique)
- [Design Tokens natifs TypeScript (Expo / React Native)](#pattern-design-tokens-expo-rn)
- [Tests de styles React Native sans renderer JSX](#pattern-tests-styles-sans-renderer)
- [Export des styles de composant pour réutilisation partielle](#pattern-export-styles-composant)
- [Token typography par usage sémantique (React Native)](#pattern-token-typography-semantique)
- [Click-to-load strict pour les embeds tiers (iframe/widget)](#pattern-click-to-load-embeds-tiers)
- [Toggle optimiste avec rollback (React Server Action)](#pattern-toggle-optimiste-rollback)
- [Server Action retournant l'entité — élimination de `router.refresh()` sur create/edit](#pattern-server-action-retourne-entite)
- [ESLint flat config avec presets Next.js (`eslint.config.mjs`)](#pattern-eslint-flat-config-nextjs)
- [Grilles 2 colonnes FR/EN — mobile-first](#pattern-grilles-2-colonnes-mobile-first)
---
## Règle dor
Si ce nest pas **confirmé comme fonctionnel et utile**,
**ça na rien à faire ici**.
- Pas de conseils vagues
- Pas de patterns “à la mode”
- Pas de dépendance implicite à un framework ou une version non précisée
---
## Périmètre couvert
- SPA et webapps
- UX technique (forms, erreurs, loading, feedback)
- State management (client / server)
- Architecture front-end
- Performance et accessibilité
- Sécurité front (au niveau applicatif)
- DX et maintenabilité
Ce fichier traite le **front-end comme un logiciel en production**,
au même niveau dexigence que le backend.
---
## Format standard dun pattern (obligatoire)
## Pattern : <Nom clair et précis>
- Objectif : ce que le pattern résout
- Contexte : type dapplication / contraintes
- Quand lutiliser : cas pertinents
- Quand léviter : contre-exemples
- Avantages : bénéfices concrets
- Limites / vigilance : pièges, dette potentielle
- Validé le : DD-MM-YYYY
- Contexte technique : framework + version + tooling principal
### Implémentation (exemple minimal)
```txt
(contenu volontairement minimal, lisible, non-magique)
```
---
<a id="pattern-etats-ui-loading-empty-error"></a>
## Pattern : Gestion explicite des états UI (loading / empty / error)
### Synthèse
- **Objectif** : éviter les interfaces ambiguës ou incohérentes en rendant explicites tous les états possibles dune vue.
- **Contexte** : SPA ou webapp consommant des données asynchrones (API, backend, cache).
- **Quand lutiliser** : dès quune vue dépend de données externes ou dun traitement async.
- **Quand léviter** : vues purement statiques ou synchrones sans dépendance externe.
### Analyse
- **Avantages** :
- UX plus prévisible et compréhensible
- Debug facilité (état visible = problème identifiable)
- Base saine pour tests et accessibilité
- **Limites / vigilance** :
- Peut sembler verbeux sur des écrans simples
- Nécessite une discipline pour ne pas “court-circuiter” les états
### Validation
- Validé le : 25-01-2026
- Contexte technique : SPA (React / Vue / Svelte agnostique), API HTTP
### Implémentation (exemple minimal)
```txt
if (loading) {
afficher un skeleton ou spinner
} else if (error) {
afficher un message clair + action possible
} else if (data est vide) {
afficher un état empty explicite
} else {
afficher la vue nominale
}
```
### Checklist
- [ ] Aucun écran blanc ou silencieux
- [ ] Message derreur compréhensible pour lutilisateur
- [ ] États testables individuellement
- [ ] Accessibilité respectée (focus, lecture écran)
- [ ] Pas de logique métier cachée dans le rendu
---
<a id="pattern-separation-server-state-client-state"></a>
## Pattern : Séparation claire server state / client state
### Synthèse
- **Objectif** : éviter le mélange des responsabilités entre données serveur et état local UI.
- **Contexte** : SPA ou webapp consommant une API avec interactions utilisateur.
- **Quand lutiliser** : dès que lapplication affiche des données distantes modifiables ou synchronisées.
- **Quand léviter** : applications très simples ou purement statiques.
### Analyse
- **Avantages** :
- Logique plus lisible et testable
- Réduction des bugs liés aux états incohérents
- Évolutivité facilitée quand lapp grossit
- **Limites / vigilance** :
- Demande de la rigueur dans le découpage
- Peut sembler abstrait au début pour des petits projets
### Validation
- Validé le : 25-01-2026
- Contexte technique : SPA agnostique (React / Vue / Svelte), API HTTP
### Implémentation (exemple minimal)
```txt
serverState = données venant du backend (fetch, cache, sync)
clientState = état local UI (filtres, onglets, modales, formulaires)
Ne jamais :
- stocker du state UI dans le cache serveur
- dériver la logique UI directement des réponses API sans adaptation
```
### Checklist
- [ ] Les données serveur peuvent être invalidées / rechargées
- [ ] Létat UI est local et réinitialisable
- [ ] Les responsabilités sont lisibles dans le code
- [ ] Les tests peuvent cibler chaque type détat
- [ ] Pas de dépendance implicite entre UI et API
---
<a id="pattern-formulaire-robuste"></a>
## Pattern : Formulaire robuste avec validation et erreurs explicites
### Synthèse
- **Objectif** : garantir des formulaires fiables, compréhensibles et maintenables.
- **Contexte** : toute interface avec saisie utilisateur et règles métier.
- **Quand lutiliser** : dès quun formulaire dépasse un simple champ isolé.
- **Quand léviter** : formulaires ultra-simples sans validation réelle.
### Analyse
- **Avantages** :
- UX claire (lutilisateur sait quoi corriger)
- Moins derreurs silencieuses
- Base saine pour tests et accessibilité
- **Limites / vigilance** :
- Peut sembler verbeux sans discipline
- Risque de duplication si mal factorisé
### Validation
- Validé le : 25-01-2026
- Contexte technique : Front-end agnostique, API HTTP
### Implémentation (exemple minimal)
```txt
- Validation côté client (format, champs requis)
- Validation côté serveur (règles métier)
- Mapping explicite des erreurs serveur → champs UI
- Aucun submit silencieux
```
### Checklist
- [ ] Messages derreur compréhensibles et localisés
- [ ] Validation client + serveur cohérente
- [ ] Focus automatique sur le champ en erreur
- [ ] États loading / disabled gérés
- [ ] Tests sur cas valides et invalides
---
<a id="pattern-navigation-reactive-post-action-async"></a>
## Pattern : Navigation réactive post-action async (React / Expo Router)
### Synthèse
- **Objectif** : déclencher la navigation après une action asynchrone (login, register, submit) de façon idiomatique et sans bypasser la réactivité React.
- **Contexte** : SPA ou app mobile React avec state management (Zustand, Redux, Context) et router déclaratif (React Router, Expo Router, Next.js App Router).
- **Quand l'utiliser** : dès qu'une navigation dépend du résultat d'une action async.
- **Quand l'éviter** : navigations synchrones sans état async impliqué.
### Analyse
- **Avantages** :
- Respecte le cycle de vie React (pas de lecture de state hors cycle)
- Re-render automatique si l'état change entre-temps
- Testable : on peut assert sur l'état, pas sur des effets de bord
- **Limites / vigilance** :
- Ne pas oublier les dépendances du `useEffect` (ESLint react-hooks/exhaustive-deps)
- Gérer le cas "composant démonté" si la navigation peut être annulée
### Validation
- Validé le : 07-03-2026
- Contexte technique : React 18+ / Zustand / Expo Router — pattern applicable sur React Router, Next.js App Router
### Implémentation (exemple minimal)
```typescript
// ❌ Anti-pattern : lecture de state hors cycle React
const handleSubmit = async () => {
await login(email, password);
const { accessToken } = useAuthStore.getState(); // bypasse la réactivité
if (accessToken) router.replace('/(tabs)');
};
// ✅ Pattern correct : useEffect réactif sur le state
const { accessToken, isLoading, error } = useAuthStore();
useEffect(() => {
if (accessToken && !isLoading && !error) {
router.replace('/(tabs)');
}
}, [accessToken, isLoading, error]);
const handleSubmit = async () => {
await login(email, password);
// la navigation se déclenche via useEffect quand le store se met à jour
};
```
### Pour les callbacks OAuth (ref nécessaire)
```typescript
// Quand un callback externe déclenche la navigation
const pendingOAuth = useRef(false);
useEffect(() => {
if (pendingOAuth.current && accessToken) {
pendingOAuth.current = false;
router.replace('/(tabs)');
}
}, [accessToken]);
const handleOAuth = async () => {
pendingOAuth.current = true;
await exchangeWithIdp(token);
};
```
### Checklist
- [ ] Aucun `store.getState()` utilisé pour lire l'état post-action dans un handler
- [ ] `useEffect` avec dépendances explicites (state pertinent + isLoading + error)
- [ ] Cas d'erreur géré (ne pas naviguer si error est défini)
- [ ] `useRef` si le trigger vient d'un callback externe (OAuth, deep link)
- [ ] Convention documentée dans la story foundations / project-context avant les premiers écrans
---
<a id="pattern-refresh-idempotent-liste-paginee"></a>
## Pattern : Refresh idempotent sur store de liste paginée
### Synthèse
- **Objectif** : garantir quun pull-to-refresh recharge une liste paginée sans doublons, sans courses réseau et sans état intermédiaire incohérent.
- **Contexte** : app mobile ou SPA avec store de domaine (ex. Zustand) et pagination incrémentale.
- **Quand lutiliser** : dès quune même liste supporte à la fois `loadMore` et `refresh`.
- **Quand léviter** : listes purement statiques ou données entièrement remplacées sans pagination.
### Analyse
- **Avantages** :
- évite les doublons lors des refresh concurrents
- garde une transition atomique entre ancien et nouvel état
- rend le comportement async testable côté store
- **Limites / vigilance** :
- impose une discipline claire entre `refresh` et `loadMore`
- demande une clé didentité stable pour dédupliquer les items
### Validation
- Validé le : 10-03-2026
- Contexte technique : React Native / Expo / Zustand / listes paginées
### Implémentation (exemple minimal)
```txt
- conserver une promesse de refresh partagée tant quun refresh est en vol
- refuser ou réutiliser tout refresh concurrent au lieu den lancer un second
- remplacer atomiquement la liste à la fin du refresh
- dédupliquer les items par identifiant au merge des pages suivantes
- empêcher `loadMore` de fusionner sur un snapshot devenu obsolète
```
### Checklist
- [ ] Une seule promesse de refresh en vol à la fois
- [ ] `refresh` et `loadMore` ont des garde-fous explicites
- [ ] La liste est remplacée atomiquement après refresh
- [ ] Les pages suivantes sont dédupliquées par identifiant stable
- [ ] Tests sur refresh concurrent + refresh suivi de pagination
---
<a id="pattern-ui-admin-legere-domaine-existant"></a>
## Pattern : UI admin légère sur domaine existant
### Synthèse
- **Objectif** : ajouter une capacité interne simple sans ouvrir trop tôt un back-office séparé ni dupliquer la logique métier.
- **Contexte** : app mobile ou SPA avec un domaine métier déjà structuré et quelques actions internes ponctuelles.
- **Quand lutiliser** : publication, activation, modération légère, bascule de statut, action opérateur simple.
- **Quand léviter** : permissions complexes, workflows multiples, audit riche ou volume dactions qui justifie un vrai espace dadministration.
### Analyse
- **Avantages** :
- réutilise le service et le store métier existants
- limite le coût de structure pour une capacité admin mince
- garde les mutations testables et lisibles
- **Limites / vigilance** :
- ne pas laisser cette approche dériver vers un pseudo back-office implicite
- le refresh après mutation doit être explicite sur les vues impactées
### Validation
- Validé le : 10-03-2026
- Contexte technique : React Native / Expo Router / store de domaine
### Implémentation (exemple minimal)
```txt
- ajouter une route dédiée minimale pour laction interne
- réutiliser le service/store métier existant au lieu de créer une couche parallèle
- afficher le statut courant avant action
- bloquer les actions concurrentes avec un flag explicite (`isUpdating*`)
- déclencher un refresh explicite des vues impactées après succès
- éviter les mutations en fire-and-forget
```
### Checklist
- [ ] Route dédiée minimale, pas de mini back-office générique
- [ ] Réutilisation du domaine métier existant
- [ ] Garde-fou explicite contre les doubles actions
- [ ] Refresh explicite après mutation réussie
- [ ] Tests sur succès, erreur et action concurrente
---
---
<a id="pattern-link-out-page-locale-canonique"></a>
## Pattern : Intégration tierce en mode link-out — préférer une page locale canonique
### Synthèse
- **Objectif** : éviter les parcours concurrents et centraliser les garde-fous UX quand une fonctionnalité publique dépend dun service tiers externe.
- **Contexte** : site ou webapp avec CTA publics menant vers un tiers de réservation, paiement, prise de rendez-vous ou formulaire externe.
- **Quand lutiliser** : dès quune fonctionnalité externe dispose dune page locale dédiée côté produit (`/reservation`, `/booking`, etc.).
- **Quand léviter** : si le produit assume volontairement une sortie directe unique vers le tiers, sans page locale intermédiaire ni besoin de contextualisation.
### Analyse
- **Avantages** :
- UX plus cohérente entre home, navigation et pages dédiées
- garde-fous, wording et fallbacks centralisés au même endroit
- facilite lévolution future vers embed, click-to-load ou variantes de parcours
- **Limites / vigilance** :
- ajoute une étape intermédiaire si la page locale napporte aucune valeur
- demande de maintenir lalignement entre les CTA internes et le parcours canonique
### Validation
- Validé le : 19-03-2026
- Contexte technique : site web public / intégration tierce en mode lien externe
### Implémentation (exemple minimal)
```txt
- faire pointer les CTA internes (home, nav, landing) vers une page locale dédiée
- faire de cette page locale le point canonique vers le service tiers externe
- centraliser sur cette page le contexte utile, les garde-fous et les fallbacks
- éviter les sorties directes concurrentes vers le tiers depuis plusieurs endroits du site
```
### Checklist
- [ ] Un parcours canonique unique est défini
- [ ] Les CTA internes convergent vers la page locale dédiée
- [ ] Les garde-fous et fallbacks sont centralisés
- [ ] Les sorties directes concurrentes vers le tiers sont évitées ou justifiées
---
<a id="pattern-design-tokens-expo-rn"></a>
## Pattern : Design Tokens natifs TypeScript (Expo / React Native)
### Synthèse
- **Objectif** : centraliser les tokens de design sans librairie externe (NativeBase, Tamagui), typés et barrel-exportés.
- **Contexte** : app Expo / React Native avec un système de design à maintenir.
- **Quand lutiliser** : dès le début dun projet mobile, avant les premiers composants.
- **Quand léviter** : si une librairie UI opinionée est déjà choisie et gère ses propres tokens.
### Analyse
- **Avantages** :
- aucune dépendance externe, zéro configuration magique
- autocomplétion TypeScript exacte via `as const` + types dérivés
- facile à migrer vers un design system plus élaboré ultérieurement
- **Limites / vigilance** :
- les fichiers TTF doivent être présents dans `assets/fonts/` — Google Fonts ne peut pas être téléchargé automatiquement, documenter comme pré-requis dans la story
- ne pas réutiliser les tokens `spacing` pour les dimensions de composants (voir risques)
### Validation
- Validé le : 19-03-2026
- Contexte technique : Expo SDK 52+ / React Native / TypeScript — app-alexandrie story 0.1
### Implémentation (exemple minimal)
```typescript
// apps/mobile/src/theme/colors.ts
export const colors = {
primary: #2563EB,
error: #DC2626,
// ...
} as const;
export type ColorToken = keyof typeof colors;
// apps/mobile/src/theme/spacing.ts
export const spacing = { xs: 4, sm: 8, md: 12, base: 16, lg: 24 } as const;
export type SpacingToken = keyof typeof spacing;
// apps/mobile/src/theme/index.ts (barrel export)
export * from ./colors;
export * from ./spacing;
export * from ./typography;
export * from ./shadows;
```
### Checklist
- [ ] Tous les tokens `as const` pour inférence exacte
- [ ] Pas de Context React — constantes TypeScript pures
- [ ] Types dérivés (`ColorToken = keyof typeof colors`) pour lautocomplétion
- [ ] `useFonts` dans `_layout.tsx` avec guard `!fontsLoaded`
- [ ] Fichiers TTF présents dans `assets/fonts/` et documentés dans la story
---
<a id="pattern-tests-styles-sans-renderer"></a>
## Pattern : Tests de styles React Native sans renderer JSX
### Synthèse
- **Objectif** : tester les tokens et styles de composants React Native dans un environnement Jest `testEnvironment: node` sans renderer JSX.
- **Contexte** : config Jest avec `transform: { ^.+\\.ts$: ts-jest }` — les `.tsx` ne sont pas transformés.
- **Quand lutiliser** : tokens de thème, logique pure, valeurs de style exportées.
- **Quand léviter** : rendu conditionnel (styles dynamiques inline) — nécessite `@testing-library/react-native`.
### Analyse
- **Avantages** :
- teste que le composant utilise les bons tokens, pas seulement que les tokens ont des valeurs
- détecte les régressions de style sans renderer
- rapide, aucune config Jest supplémentaire
- **Limites / vigilance** :
- ne teste pas le style calculé au runtime (style conditionnel dynamique)
### Validation
- Validé le : 19-03-2026
- Contexte technique : React Native / Jest / ts-jest — app-alexandrie story 0.2
### Implémentation
```typescript
// Button.tsx — exporter le StyleSheet avec un nom préfixé
export const buttonStyles = StyleSheet.create({
base: { borderRadius: 20, height: 57 },
primary: { backgroundColor: colors.primary },
});
export function Button(...) { ... }
// ui-components.spec.ts — importer et vérifier les tokens
import { buttonStyles } from ./Button;
import { colors } from @/theme;
it(variante primary utilise colors.primary, () => {
expect(buttonStyles.primary.backgroundColor).toBe(colors.primary);
});
```
### Deux niveaux de tests UI recommandés
1. `.spec.ts` (node) : tokens, valeurs, logique pure
2. `.spec.tsx` (config séparée avec renderer) : rendu visuel, interactions
---
<a id="pattern-export-styles-composant"></a>
## Pattern : Export des styles de composant pour réutilisation partielle (React Native)
### Synthèse
- **Objectif** : partager les dimensions et formes dun composant UI vers des éléments custom qui en dérivent, sans dupliquer les valeurs.
- **Contexte** : app React Native où des screens construisent des éléments qui doivent être "au gabarit" dun composant existant.
- **Quand lutiliser** : bouton custom OAuth, container calqué sur un composant de base, etc.
- **Quand léviter** : si lécart visuel est intentionnel — dans ce cas, une constante locale est plus claire.
### Analyse
- **Avantages** :
- zéro drift silencieux : si les dimensions du composant changent, tous les éléments dérivés suivent
- tests de styles possibles en dehors du composant
- **Limites / vigilance** :
- à nutiliser que pour des éléments vraiment dérivés, pas comme contournement de design system
### Validation
- Validé le : 19-03-2026
- Contexte technique : React Native / StyleSheet — app-alexandrie story 0.3
### Implémentation
```typescript
// Button.tsx
export const buttonStyles = StyleSheet.create({
base: { borderRadius: 20, height: 57 },
primary: { backgroundColor: colors.primary },
});
export function Button(...) { ... }
// login.tsx — bouton OAuth au gabarit du Button
import { buttonStyles } from @/components/ui/Button;
<TouchableOpacity style={[buttonStyles.base, styles.facebookButton]} />
```
---
<a id="pattern-token-typography-semantique"></a>
## Pattern : Token typography par usage sémantique (React Native)
### Synthèse
- **Objectif** : éviter les mauvais usages de tokens typography visuellement proches mais sémantiquement distincts.
- **Contexte** : fichier `typography.ts` dans un design system React Native.
- **Quand lutiliser** : dès que deux tokens partagent la même taille mais un poids différent.
- **Quand léviter** : jamais — les tokens typography doivent toujours refléter lusage, pas lapparence.
### Analyse
- **Avantages** :
- prévient les "approximations" de tokens en code review
- changement de style dusage spécifique sans régression globale
- **Limites / vigilance** :
- en review : chercher les usages sans `fontWeight` explicite — cest souvent le signe que le mauvais token a été choisi
### Validation
- Validé le : 19-03-2026
- Contexte technique : React Native / TypeScript — app-alexandrie story 0.4
### Implémentation
```typescript
// Bon : nommé par usage sémantique
listItemTitle: { fontSize: 12, fontWeight: 600 }, // titre dun item de liste
caption: { fontSize: 12, fontWeight: 500 }, // info secondaire, hints
// Mauvais : nommé par apparence
mediumText12: { fontSize: 12, fontWeight: 500 }, // ambigu, réutilisé à tort
```
**Règle** : `caption` (Medium) ≠ `listItemTitle` (SemiBold) même si la taille est identique. Ne jamais piocher un token "par approximation".
---
### Principes transverses
- Un pattern = une responsabilité claire
- On privilégie la simplicité locale avant la généricité globale
- Le code doit rester compréhensible 6 mois plus tard
- Si un pattern devient central → il mérite une décision darchitecture dédiée
## Notes importantes
- 3 bons patterns > 30 moyens
- Si un pattern évolue :
- on met à jour la date
- on précise le nouveau contexte
- En cas de doute → le pattern nentre pas encore ici
---
<a id="pattern-click-to-load-embeds-tiers"></a>
## Pattern : Click-to-load strict pour les embeds tiers (iframe/widget)
### Synthèse
- **Objectif** : ne charger aucun service tiers sans action explicite de lutilisateur (performance + consentement implicite).
- **Contexte** : site/webapp avec modules de réservation, map, chat ou tout embed iframe à la demande.
- **Quand lutiliser** : dès quun embed tiers est chargé à la demande (pas au premier rendu).
- **Quand léviter** : si lembed est central à la page et doit être visible immédiatement.
### Analyse
- **Avantages** :
- LCP non pollué par des tiers (performance-first)
- Aucun tiers ne reçoit de données utilisateur sans action volontaire (consentement implicite)
- Fallback toujours disponible en cas derreur iframe
- **Limites / vigilance** :
- Le fallback (lien externe + `tel:`) doit être actionnable même si lembed échoue
### Validation
- Validé le : 21-03-2026
- Contexte technique : React / Next.js — app-template-resto
### Implémentation
```tsx
const [loaded, setLoaded] = useState(false);
const [errored, setErrored] = useState(false);
if (errored) return <a href={url}>Ouvrir {label}</a>;
return (
<>
{!loaded && <button onClick={() => setLoaded(true)}>Charger {label}</button>}
{loaded && <iframe src={url} onError={() => setErrored(true)} />}
</>
);
```
---
<a id="pattern-toggle-optimiste-rollback"></a>
## Pattern : Toggle optimiste avec rollback (React Server Action)
### Synthèse
- **Objectif** : masquer la latence serveur sur un toggle boolean en mettant à jour lUI immédiatement, avec rollback en cas derreur.
- **Contexte** : toggles boolean (visibilité, disponibilité, settings) où la latence doit être masquée.
- **Quand lutiliser** : toggles sans besoin de re-fetcher lentité entière après mutation.
- **Quand léviter** : mutations qui retournent des données complexes → préférer le pattern "Server Action retournant lentité".
### Validation
- Validé le : 21-03-2026
- Contexte technique : React / Next.js App Router — app-template-resto
### Implémentation
```tsx
const [optimistic, setOptimistic] = useState(initialValue);
async function handleToggle() {
const prev = optimistic;
setOptimistic(!prev); // update immédiat
try {
await toggleAction(!prev);
router.refresh(); // synchronise le Server Component parent
} catch {
setOptimistic(prev); // rollback si erreur
}
}
```
---
<a id="pattern-server-action-retourne-entite"></a>
## Pattern : Server Action retournant lentité — élimination de `router.refresh()` sur create/edit
### Synthèse
- **Objectif** : mettre à jour létat local directement avec les données réelles retournées par le serveur, sans round-trip SSR supplémentaire.
- **Contexte** : liste ditems managée côté client (`useState`) avec création et modification via Server Actions.
- **Quand lutiliser** : create et edit dentités dans une liste. Plus performant que toggle optimiste + `router.refresh()`.
- **Quand léviter** : simples toggles boolean → le pattern optimiste avec rollback suffit.
### Analyse
- **Avantages vs toggle optimiste + `router.refresh()` :**
- Zéro aller-retour SSR supplémentaire (~500ms2s économisés sur mobile)
- État local garanti cohérent avec la DB (données réelles, pas calculées localement)
- Pas de flash de rechargement
- **Limites / vigilance** :
- `revalidatePath` reste nécessaire pour invalider le cache des pages publiques
### Validation
- Validé le : 22-03-2026
- Contexte technique : React / Next.js App Router — app-template-resto story 3.8
### Implémentation
```typescript
// Repository — retourne lentité complète
export async function createItem(tenantId: string, data: Input): Promise<ItemRow> {
return prisma.item.create({ data: { tenantId, ...data }, select: { ...fullSelect } });
}
// Action — retourne la donnée au client
export async function createItemAction(formData: FormData): Promise<ItemRow> {
const actor = await requireOwner();
const item = await createItem(actor.tenantId, input);
revalidatePath("/dashboard/..."); // invalider cache pages publiques
return item; // ← clé : retourner lentité
}
// Client — mise à jour locale sans round-trip SSR
const created = await createItemAction(formData);
setItems((prev) => [...prev, created]); // pas de router.refresh()
```
**Pour les entités avec relations :** utiliser un helper `findItemById(tenantId, id)` appelé après la mutation pour retourner la forme complète avec les relations résolues.
---
<a id="pattern-eslint-flat-config-nextjs"></a>
## Pattern : ESLint flat config avec presets Next.js (`eslint.config.mjs`)
### Synthèse
- **Objectif** : éviter les bugs de compatibilité de lancien `.eslintrc` avec Next.js récent.
- **Contexte** : projet Next.js récent utilisant déjà le flat config ESLint.
- **Quand lutiliser** : nouveau projet Next.js ou migration ESLint.
- **Quand léviter** : si le projet doit rester compatible avec des outils legacy ESLint.
### Validation
- Validé le : 16-03-2026
- Contexte technique : Next.js 16+ / ESLint flat config — app-template-resto
### Implémentation
```javascript
// eslint.config.mjs
import nextPlugin from "@next/eslint-plugin-next";
export default [
...nextPlugin.configs["core-web-vitals"],
...nextPlugin.configs["typescript"],
{
rules: {
// overrides ciblés ici
},
},
];
```
---
<a id="pattern-grilles-2-colonnes-mobile-first"></a>
## Pattern : Grilles 2 colonnes FR/EN — mobile-first
### Synthèse
- **Objectif** : afficher les champs FR + EN côte à côte sur desktop, en colonne unique sur mobile.
- **Contexte** : formulaires dashboard avec champs bilingues FR/EN côte à côte.
- **Quand lutiliser** : tout formulaire avec colonnes parallèles sur un projet mobile-first.
- **Quand léviter** : si les champs sont indépendants et nont pas de relation visuelle FR/EN.
### Validation
- Validé le : 22-03-2026
- Contexte technique : Tailwind CSS / React — app-template-resto
### Implémentation
```html
<!-- ✅ Mobile-first — colonne unique sur < 640px, 2 colonnes sur ≥ 640px -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<input placeholder="Nom (FR)" />
<input placeholder="Name (EN)" />
</div>
<!-- ❌ À éviter — 2 colonnes trop étroites sur mobile -->
<div class="grid grid-cols-2 gap-4">...</div>
```

File diff suppressed because it is too large Load Diff

View File

@@ -1,70 +0,0 @@
# Patterns UX/UI validés
Ce fichier contient **uniquement** des patterns UX/UI :
- observés en conditions réelles,
- validés sur des projets livrés,
- utiles à réutiliser sur d'autres produits.
Objectif : éviter de redélibérer sur des sujets déjà tranchés et capitaliser
ce qui fonctionne du point de vue utilisateur.
Dernière mise à jour : 2026-03-09
---
## Index
_(à remplir au fil des validations)_
---
## Règle d'or
Si ce n'est pas **validé par l'usage ou l'expérience projet**, ça n'a pas sa place ici.
- Pas de "bonnes pratiques" génériques
- Pas de conseils théoriques sans contexte
- Toujours préciser le contexte (type d'app, audience, plateforme)
---
## Périmètre couvert
- Patterns d'interaction (navigation, feedback, états)
- Formulaires et flows critiques (auth, onboarding, checkout)
- Gestion des états vides, d'erreur et de chargement
- Accessibilité au niveau design
- Conventions de design system
- Décisions UX structurantes validées
---
## Format standard d'un pattern
## Pattern : <Nom clair>
- Objectif : …
- Contexte : …
- Quand l'utiliser : …
- Quand l'éviter : …
- Avantage : …
- Limites / vigilance : …
- Validé le : DD-MM-YYYY
- Contexte produit : (obligatoire) ex. `App mobile / auth` ou `Webapp B2B / dashboard`
### Description
(comportement attendu, logique d'interaction, règles visuelles clés)
### Checklist (si pertinente)
---
## Notes importantes
- On préfère 5 patterns solides à 50 "guidelines de design".
- Un pattern UX = une décision d'interaction assumée + son cadre d'application.
- La frontière avec `10_frontend_patterns_valides.md` :
ce fichier traite **l'intention UX et le comportement attendu**,
le fichier frontend traite **l'implémentation technique**.

View File

@@ -1,49 +0,0 @@
# Risques et anti-patterns UX/UI
Ce fichier recense les **erreurs UX récurrentes**, les pièges connus
et les anti-patterns observés en conditions réelles.
Objectif : ne pas répéter les mêmes erreurs de conception d'un projet à l'autre.
Dernière mise à jour : 2026-03-09
---
## Index
_(à remplir au fil des observations)_
---
## Règle d'or
Chaque entrée doit être **issue d'un problème réel**, pas d'une opinion.
- Toujours préciser l'impact observé (abandon, confusion, support, bug signalé)
- Toujours proposer l'alternative correcte
---
## Périmètre couvert
- Anti-patterns d'interaction
- Pièges sur les flows critiques (auth, onboarding, formulaires)
- Erreurs fréquentes sur les états (vide, erreur, chargement)
- Accessibilité — erreurs courantes
- Décisions UX qui semblent bonnes mais posent problème
---
## Format standard
## Anti-pattern : <Nom clair>
- Problème : …
- Impact observé : …
- Contexte : …
- À éviter parce que : …
- Alternative correcte : …
- Observé le : DD-MM-YYYY
- Contexte produit : ex. `App mobile / onboarding` ou `Webapp / formulaire`
---

View File

@@ -10,15 +10,17 @@ Le contenu de ce fichier **n'est pas encore validé**.
Une fois relues et confirmées, les propositions doivent être **déplacées**
vers les fichiers appropriés :
- `10_backend_patterns_valides.md`
- `10_frontend_patterns_valides.md`
- `10_ux_patterns_valides.md`
- `10_product_patterns_valides.md`
- `10_n8n_patterns_valides.md`
- `10_backend_risques_et_vigilance.md`
- `10_frontend_risques_et_vigilance.md`
- `10_ux_risques_et_vigilance.md`
- `10_n8n_risques_et_vigilance.md`
- `knowledge/backend/patterns/<thème>.md`
- `knowledge/backend/risques/<thème>.md`
- `knowledge/frontend/patterns/<thème>.md`
- `knowledge/frontend/risques/<thème>.md`
- `knowledge/ux/patterns/<thème>.md`
- `knowledge/ux/risques/<thème>.md`
- `knowledge/n8n/patterns/general.md`
- `knowledge/n8n/risques/general.md`
- `knowledge/product/patterns/general.md`
- `knowledge/product/risques/<thème>.md`
- `knowledge/workflow/risques/story-tracking.md`
- `10_conventions_redaction.md`
- `40_decisions_et_archi.md`
- `90_debug_et_postmortem.md`
@@ -27,7 +29,7 @@ Ce fichier ne doit donc **jamais devenir une documentation permanente**.
---
_Aucune entrée pour le moment_
_Aucune entrée pour le moment._
---
@@ -39,7 +41,7 @@ Chaque proposition doit suivre ce format :
DATE — PROJET
FILE_UPDATE_PROPOSAL
Fichier cible : <10_backend_patterns_valides.md | 10_frontend_patterns_valides.md | 10_ux_patterns_valides.md | 10_product_patterns_valides.md | 10_n8n_patterns_valides.md | 10_backend_risques_et_vigilance.md | 10_frontend_risques_et_vigilance.md | 10_ux_risques_et_vigilance.md | 10_n8n_risques_et_vigilance.md | 10_conventions_redaction.md | 40_decisions_et_archi.md | 90_debug_et_postmortem.md>
Fichier cible : <knowledge/backend/patterns/<thème>.md | knowledge/backend/risques/<thème>.md | knowledge/frontend/patterns/<thème>.md | knowledge/frontend/risques/<thème>.md | knowledge/ux/patterns/<thème>.md | knowledge/ux/risques/<thème>.md | knowledge/n8n/patterns/general.md | knowledge/n8n/risques/general.md | knowledge/product/patterns/general.md | knowledge/product/risques/<thème>.md | knowledge/workflow/risques/story-tracking.md | 10_conventions_redaction.md | 40_decisions_et_archi.md | 90_debug_et_postmortem.md>
Pourquoi :
<raison pour laquelle ce savoir mérite d'être capitalisé>
@@ -56,7 +58,7 @@ Proposition :
2026-03-08 — portfolio
FILE_UPDATE_PROPOSAL
Fichier cible : 10_backend_patterns_valides.md
Fichier cible : knowledge/backend/patterns/prisma.md
Pourquoi :
Pattern réutilisable validé sur un projet réel.

View File

@@ -13,23 +13,39 @@ Langue de travail : **français**.
## Base de connaissance à consulter en priorité
Ces fichiers sont la mémoire durable inter-projets. Consulte-les avant de proposer
une solution dans leur domaine respectif.
La base de connaissance est organisée dans `knowledge/` par domaine.
Consulte-la avant de proposer une solution dans le domaine concerné.
| Fichier | Contenu |
| ------------------------------------- | ---------------------------------------------- |
| `10_backend_patterns_valides.md` | Patterns backend validés en conditions réelles |
| `10_frontend_patterns_valides.md` | Patterns frontend/mobile validés |
| `10_ux_patterns_valides.md` | Patterns UX/UI validés |
| `10_product_patterns_valides.md` | Patterns produit / métier validés |
| `10_n8n_patterns_valides.md` | Patterns n8n validés |
| `10_backend_risques_et_vigilance.md` | Risques et anti-patterns backend |
| `10_frontend_risques_et_vigilance.md` | Risques et anti-patterns frontend |
| `10_ux_risques_et_vigilance.md` | Risques et anti-patterns UX/UI |
| `10_n8n_risques_et_vigilance.md` | Nodes et patterns n8n à risque |
| `10_conventions_redaction.md` | Conventions de documentation technique |
| `40_decisions_et_archi.md` | Décisions techniques (mini-ADR) |
| `90_debug_et_postmortem.md` | Post-mortems et bugs capitalisés |
### Procédure d'accès
1. Identifie le domaine : `backend`, `frontend`, `ux`, `workflow`
2. Lis le `README.md` du sous-dossier `patterns/` ou `risques/` concerné
3. Dans ce README, repère les fichiers dont le nom et la description matchent le contexte
4. Lis ces fichiers avant de proposer quoi que ce soit
### Structure
| Dossier | Contenu |
| ------- | ------- |
| `knowledge/backend/patterns/` | Patterns backend validés (auth, contracts, prisma, stripe, nestjs, multi-tenant, nextjs, async) |
| `knowledge/backend/risques/` | Risques backend (auth, contracts, prisma, stripe, nestjs, redis, nextjs, general) |
| `knowledge/frontend/patterns/` | Patterns frontend/mobile validés (state, forms, navigation, design-tokens, nextjs, tests) |
| `knowledge/frontend/risques/` | Risques frontend (auth, state, navigation, design-tokens, nextjs, tests, performance, general) |
| `knowledge/ux/patterns/` | Patterns UX/UI validés |
| `knowledge/ux/risques/` | Risques et anti-patterns UX/UI |
| `knowledge/n8n/patterns/` | Patterns n8n validés |
| `knowledge/n8n/risques/` | Risques et anti-patterns n8n |
| `knowledge/product/patterns/` | Patterns produit / métier validés |
| `knowledge/product/risques/` | Risques et anti-patterns produit |
| `knowledge/workflow/risques/` | Risques workflow agent (story-tracking) |
### Fichiers globaux (hors knowledge/)
| Fichier | Contenu |
| ------- | ------- |
| `10_conventions_redaction.md` | Conventions de documentation technique |
| `40_decisions_et_archi.md` | Décisions techniques (mini-ADR) |
| `90_debug_et_postmortem.md` | Post-mortems et bugs capitalisés |
## Règles de mise à jour
@@ -70,17 +86,19 @@ Les agents peuvent proposer librement des entrées dans :
Ce fichier sert de **zone tampon** pour les apprentissages à analyser.
Après validation, le contenu est déplacé vers le fichier approprié :
Après validation, le contenu est déplacé vers le fichier approprié dans `knowledge/` :
- `10_backend_patterns_valides.md`
- `10_frontend_patterns_valides.md`
- `10_ux_patterns_valides.md`
- `10_product_patterns_valides.md`
- `10_n8n_patterns_valides.md`
- `10_backend_risques_et_vigilance.md`
- `10_frontend_risques_et_vigilance.md`
- `10_ux_risques_et_vigilance.md`
- `10_n8n_risques_et_vigilance.md`
- `knowledge/backend/patterns/<thème>.md`
- `knowledge/backend/risques/<thème>.md`
- `knowledge/frontend/patterns/<thème>.md`
- `knowledge/frontend/risques/<thème>.md`
- `knowledge/ux/patterns/<thème>.md`
- `knowledge/ux/risques/<thème>.md`
- `knowledge/n8n/patterns/general.md`
- `knowledge/n8n/risques/general.md`
- `knowledge/product/patterns/general.md`
- `knowledge/product/risques/<thème>.md`
- `knowledge/workflow/risques/story-tracking.md`
- `10_conventions_redaction.md`
- `40_decisions_et_archi.md`
- `90_debug_et_postmortem.md`
@@ -101,9 +119,9 @@ automatiquement les chemins selon la machine (Mac / NUC).
## Patterns clés à appliquer systématiquement
- **Contracts-First / Zod-Infer / No-DTO** : voir `10_backend_patterns_valides.md`
- **Navigation réactive useEffect** : voir `10_frontend_patterns_valides.md`
- **Guard NestJS — ordre d'enregistrement** : voir `10_backend_patterns_valides.md`
- **Contracts-First / Zod-Infer / No-DTO** : voir `knowledge/backend/patterns/contracts.md`
- **Navigation réactive useEffect** : voir `knowledge/frontend/patterns/navigation.md`
- **Guard NestJS — ordre d'enregistrement** : voir `knowledge/backend/patterns/nestjs.md`
- **Format d'erreur API standardisé** : `{ error: { code, message, requestId } }`
- **Sessions avec TTL** : toujours un champ `expiresAt`, filtrer dans les queries

View File

@@ -8,33 +8,39 @@ Langue de travail : **français**.
## Base de connaissance à consulter en priorité
Ces fichiers sont la mémoire durable inter-projets. Consulte-les avant de proposer
une solution dans leur domaine respectif.
La base de connaissance est organisée dans `knowledge/` par domaine.
Consulte-la avant de proposer une solution dans le domaine concerné.
| Fichier | Contenu |
| ------------------------------------- | ---------------------------------------------- |
| `10_backend_patterns_valides.md` | Patterns backend validés en conditions réelles |
| `10_frontend_patterns_valides.md` | Patterns frontend/mobile validés |
| `10_ux_patterns_valides.md` | Patterns UX/UI validés |
| `10_product_patterns_valides.md` | Patterns produit / métier validés |
| `10_n8n_patterns_valides.md` | Patterns n8n validés |
| `10_backend_risques_et_vigilance.md` | Risques et anti-patterns backend |
| `10_frontend_risques_et_vigilance.md` | Risques et anti-patterns frontend |
| `10_ux_risques_et_vigilance.md` | Risques et anti-patterns UX/UI |
| `10_n8n_risques_et_vigilance.md` | Nodes et patterns n8n à risque |
| `10_conventions_redaction.md` | Conventions de documentation technique |
| `40_decisions_et_archi.md` | Décisions techniques (mini-ADR) |
| `90_debug_et_postmortem.md` | Post-mortems et bugs capitalisés |
### Procédure daccès
## Règles de mise à jour
1. Identifie le domaine : `backend`, `frontend`, `ux`, `n8n`, `product`, `workflow`
2. Lis le `README.md` du sous-dossier `patterns/` ou `risques/` concerné
3. Dans ce README, repère les fichiers dont le nom et la description matchent le contexte
4. Lis ces fichiers avant de proposer quoi que ce soit
Quand tu repères qu'un pattern mérite d'être capitalisé :
### Structure
```
FILE_UPDATE_PROPOSAL
Fichier : `<nom_du_fichier>`
Pourquoi : <1-2 phrases>
```
| Dossier | Contenu |
| ------- | ------- |
| `knowledge/backend/patterns/` | Patterns backend validés (auth, contracts, prisma, stripe, nestjs, multi-tenant, nextjs, async) |
| `knowledge/backend/risques/` | Risques backend (auth, contracts, prisma, stripe, nestjs, redis, nextjs, general) |
| `knowledge/frontend/patterns/` | Patterns frontend/mobile validés (state, forms, navigation, design-tokens, nextjs, tests) |
| `knowledge/frontend/risques/` | Risques frontend (auth, state, navigation, design-tokens, nextjs, tests, performance, general) |
| `knowledge/ux/patterns/` | Patterns UX/UI validés |
| `knowledge/ux/risques/` | Risques et anti-patterns UX/UI |
| `knowledge/n8n/patterns/` | Patterns n8n validés |
| `knowledge/n8n/risques/` | Risques et anti-patterns n8n |
| `knowledge/product/patterns/` | Patterns produit / métier validés |
| `knowledge/product/risques/` | Risques et anti-patterns produit |
| `knowledge/workflow/risques/` | Risques workflow agent (story-tracking) |
### Fichiers globaux (hors knowledge/)
| Fichier | Contenu |
| ------- | ------- |
| `10_conventions_redaction.md` | Conventions de documentation technique |
| `40_decisions_et_archi.md` | Décisions techniques (mini-ADR) |
| `90_debug_et_postmortem.md` | Post-mortems et bugs capitalisés |
## Capitalisation du savoir
@@ -59,47 +65,20 @@ Validation
Lead_tech
```
Les agents peuvent proposer librement des entrées dans :
Les agents peuvent proposer librement des entrées dans `95_a_capitaliser.md`.
`95_a_capitaliser.md`
Ce fichier sert de **zone tampon** pour les apprentissages à analyser.
Après validation, le contenu est déplacé vers le fichier approprié :
- `10_backend_patterns_valides.md`
- `10_frontend_patterns_valides.md`
- `10_ux_patterns_valides.md`
- `10_product_patterns_valides.md`
- `10_n8n_patterns_valides.md`
- `10_backend_risques_et_vigilance.md`
- `10_frontend_risques_et_vigilance.md`
- `10_ux_risques_et_vigilance.md`
- `10_n8n_risques_et_vigilance.md`
- `10_conventions_redaction.md`
- `40_decisions_et_archi.md`
- `90_debug_et_postmortem.md`
Objectif :
- éviter de polluer la base de connaissance
- capitaliser progressivement les retours d'expérience
- maintenir `Lead_tech` comme mémoire fiable et validée
Après validation, le contenu est déplacé vers le fichier approprié dans `knowledge/`.
## Projets actifs
La liste des projets actifs est maintenue dans `_projects.conf`.
Ce fichier constitue le registre central des projets (stack, scope, état).
Les scripts de lenvironnement Lead_tech lutilisent pour résoudre
automatiquement les chemins selon la machine (Mac / NUC).
## Patterns clés à appliquer systématiquement
- **Contracts-First / Zod-Infer / No-DTO** : voir `10_backend_patterns_valides.md`
- **Navigation réactive useEffect** : voir `10_frontend_patterns_valides.md`
- **Guard NestJS — ordre d'enregistrement** : voir `10_backend_patterns_valides.md`
- **Format d'erreur API standardisé** : `{ error: { code, message, requestId } }`
- **Contracts-First / Zod-Infer / No-DTO** : voir `knowledge/backend/patterns/contracts.md`
- **Navigation réactive useEffect** : voir `knowledge/frontend/patterns/navigation.md`
- **Guard NestJS — ordre denregistrement** : voir `knowledge/backend/patterns/nestjs.md`
- **Format derreur API standardisé** : `{ error: { code, message, requestId } }`
- **Sessions avec TTL** : toujours un champ `expiresAt`, filtrer dans les queries
## Infrastructure NUC

View File

@@ -0,0 +1,18 @@
# Backend — Patterns validés — Index
Patterns backend testés et validés en conditions réelles.
Avant toute proposition backend, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
---
| Fichier | Domaine | Entrées clés |
|---------|---------|--------------|
| `auth.md` | Auth, sessions, tokens, erreurs API, corrélation | Format erreur standardisé, middleware requestId, anti-énumération, token usage unique, autorisation interne, opérations atomiques |
| `contracts.md` | Contrats API, Zod, error codes, HTTP sémantique | Contracts-First/Zod-Infer/No-DTO, error codes comme contrat, HTTP 200 payload métier |
| `prisma.md` | Prisma, DB, migrations, pagination | Soft delete, pagination cursor, idempotency key, P2002 unique, Decimal sérialisation, migration manuelle P3014, filtrage métier dans service |
| `stripe.md` | Stripe, paiements, webhooks entrants, subscriptions | Provider-Strategy, metadata subscription_data, parsing webhook unique, restauration achats, Trial vs Paid |
| `nestjs.md` | NestJS, guards, Redis, quotas | Guard global APP_GUARD, RedisHealthService cache court, quota INCR+EXPIREAT atomique |
| `multi-tenant.md` | Multi-tenant, isolation, feature flags | 403 vs 404, repository tenant-aware, tenantId dans updates, helper tenant partagé, feature flag tenant, EN enforcement |
| `nextjs.md` | Next.js App Router, Server Actions, isolation | Runtime-only logique pure, server-only isolation, utilitaires purs sans server-only, réutiliser champ V1, validation URL externe |
| `async.md` | Jobs async, webhooks sortants, queues | Exécution asynchrone outbox light, webhooks sortants HMAC + retries idempotents |

View File

@@ -0,0 +1,79 @@
# Backend — Patterns : Async
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
---
<a id="pattern-execution-asynchrone-taches-longues"></a>
## Pattern : Exécution asynchrone des tâches longues (queue + outbox light)
- Objectif : sortir les opérations longues ou fragiles du chemin request/response.
- Contexte : envoi d'emails, appels SaaS, génération de PDF, traitements batch, webhooks sortants.
- Quand l'utiliser : dès qu'une opération peut dépasser la latence acceptable ou dépendre d'un service externe.
- Quand l'éviter : opérations réellement instantanées et sans dépendances externes.
- Avantage :
- API plus rapide et plus fiable
- Retries maîtrisés
- Meilleure résilience aux pannes externes
- Limites / vigilance :
- Demande une discipline stricte sur l'idempotence
- Nécessite une stratégie minimale de dead-letter ou d'alerting
- Validé le : 25-01-2026
- Contexte technique : Backend agnostique + DB transactionnelle + worker
### Implémentation (exemple minimal)
```txt
- API écrit un job ou event en DB dans la transaction métier
- Worker lit les jobs en attente et exécute
- Retries avec backoff + compteur
- Statut FAILED ou dead-letter + alerte
- Idempotence par clé métier ou idempotency key
```
### Checklist
- Job créé dans une transaction (évite les pertes)
- Retries et backoff définis
- Dead-letter ou statut FAILED visible
- Idempotence garantie
- Logs corrélés (requestId/traceId)
---
<a id="pattern-webhooks-sortants-robustes-idempotents"></a>
## Pattern : Webhooks sortants robustes et idempotents
- Objectif : garantir des intégrations fiables avec des systèmes externes.
- Contexte : notifications, synchronisations, événements métier sortants.
- Quand l'utiliser : dès qu'un événement doit être transmis à un tiers.
- Quand l'éviter : intégrations strictement synchrones et internes.
- Avantage :
- Tolérance aux pannes réseau
- Retries maîtrisés
- Observabilité des échecs
- Limites / vigilance :
- Gestion des retries et du volume
- Nécessite une idempotence côté consommateur
- Validé le : 25-01-2026
- Contexte technique : Backend + HTTP + worker/queue
### Implémentation (exemple minimal)
```txt
- Événement persisté (outbox) en DB
- Envoi asynchrone via worker
- Retries avec backoff
- Signature du payload (HMAC)
- Idempotency key dans le header
```
### Checklist
- Payload signé et vérifiable
- Retries + backoff définis
- Dead-letter ou statut FAILED visible
- Idempotence documentée
- Logs corrélés (requestId/traceId)

View File

@@ -0,0 +1,230 @@
# Backend — Patterns : Auth
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
---
<a id="pattern-format-derreur-api-standardise"></a>
## Pattern : Format d'erreur API standardisé
- Objectif : fournir des erreurs prévisibles, exploitables et cohérentes pour tous les clients.
- Contexte : API consommée par front-end, automatisations ou intégrations externes.
- Quand l'utiliser : dès qu'une API est exposée à autre chose qu'un usage interne trivial.
- Quand l'éviter : jamais.
- Avantage :
- Debug plus rapide
- UX maîtrisée côté client
- Observabilité améliorée
- Limites / vigilance :
- Discipline requise pour éviter les formats ad hoc
- Validé le : 25-01-2026
- Contexte technique : API HTTP agnostique
### Implémentation (exemple minimal)
```json
{
"error": {
"code": "USER_NOT_FOUND",
"message": "Utilisateur introuvable",
"requestId": "abc-123"
}
}
```
### Checklist
- Codes HTTP cohérents (4xx / 5xx)
- Codes d'erreur applicatifs stables
- Message utilisateur non technique
- requestId présent
---
<a id="pattern-middleware-correlation-requestid-traceid"></a>
## Pattern : Middleware de corrélation (requestId / traceId)
- Objectif : relier chaque requête aux logs et erreurs associées.
- Contexte : toute API ou service exposé.
- Quand l'utiliser : systématiquement en production.
- Quand l'éviter : jamais.
- Avantage :
- MTTR réduit drastiquement
- Debug cross-services possible
- Limites / vigilance :
- Doit être propagé partout (logs, erreurs, appels sortants)
- Validé le : 25-01-2026
- Contexte technique : Backend agnostique (HTTP)
### Implémentation (exemple minimal)
```txt
- Générer un requestId à l'entrée si absent
- Le propager dans le contexte de requête
- L'inclure dans chaque log et réponse d'erreur
```
### Checklist
- requestId généré ou repris d'un header existant
- Présent dans tous les logs
- Présent dans les erreurs retournées
---
<a id="pattern-anti-enumeration-auth-email"></a>
## Pattern : Anti-énumération sur endpoints auth liés à un email
- Objectif : empêcher qu'un endpoint auth révèle si un compte existe, n'existe pas ou n'est pas éligible.
- Contexte : reset de mot de passe, invitation, vérification de compte, login ou tout flux qui part d'un email utilisateur.
- Quand l'utiliser : dès qu'une requête auth touche un identifiant de type email.
- Quand l'éviter : jamais sur une surface exposée.
- Avantage :
- réduit la fuite d'information sur les comptes existants
- homogénéise les réponses côté client
- se combine bien avec les garde-fous anti-abus
- Limites / vigilance :
- ne protège pas seul contre le brute-force, à combiner avec du rate-limiting
- les logs internes doivent conserver la vraie cause sans l'exposer au client
- Validé le : 16-03-2026
- Contexte technique : Node.js / auth applicative / API HTTP
### Implémentation (exemple minimal)
```txt
- retourner la même réponse HTTP 200 qu'un compte existe ou non
- ne jamais distinguer "email inconnu", "email connu" ou "compte OAuth-only" dans la réponse
- journaliser la cause réelle côté serveur
- ajouter un rate-limiting basé sur email + IP
```
### Checklist
- Réponse client uniforme pour les cas compte connu/inconnu/non éligible
- Aucune fuite d'existence dans le message ou le code d'erreur
- Rate-limiting présent sur les endpoints exposés
- Logs internes exploitables
---
<a id="pattern-token-usage-unique"></a>
## Pattern : Token à usage unique — génération, hash et invalidation atomique
- Objectif : standardiser la création et la consommation de tokens sensibles sans stocker de secret brut en base.
- Contexte : invitation, reset de mot de passe, vérification d'email, lien magique ou tout token one-shot.
- Quand l'utiliser : pour tout token à usage unique transmis à l'utilisateur.
- Quand l'éviter : sessions longues ou secrets devant être relus en clair côté serveur.
- Avantage :
- réduit l'impact d'une fuite de base
- garde des tokens URL-safe
- favorise une consommation atomique et réutilisable
- Limites / vigilance :
- la consommation doit rester atomique
- la politique d'expiration doit être explicite
- Validé le : 16-03-2026
- Contexte technique : Node.js `crypto` / Prisma / email ou URL signée
### Implémentation (exemple minimal)
```txt
- générer le token avec `crypto.randomBytes(32).toString("base64url")`
- stocker uniquement le hash SHA-256 du token en base
- transmettre le token brut uniquement via URL ou email
- recalculer le hash côté serveur lors de la consommation
- invalider le token dans une transaction atomique après usage
```
### Checklist
- Token brut jamais persisté en base
- Hash recalculé côté serveur pour la vérification
- Expiration explicite
- Invalidation atomique après consommation
---
<a id="pattern-autorisation-interne-minimale"></a>
## Pattern : Autorisation interne minimale sans RBAC complet
- Objectif : sécuriser une capacité interne sensible sans ouvrir trop tôt un chantier RBAC complet.
- Contexte : application avec peu de rôles, besoin ponctuel d'une capacité admin ou opérateur clairement identifiée.
- Quand l'utiliser : quand une story métier demande un pouvoir interne limité mais réel.
- Quand l'éviter : si les permissions deviennent nombreuses, hiérarchiques ou contextuelles.
- Avantage :
- sécurisation rapide et lisible d'une capacité sensible
- source de vérité backend explicite
- chemin d'évolution propre vers un RBAC plus complet
- Limites / vigilance :
- ne pas laisser proliférer des rôles ad hoc non gouvernés
- ne remplace pas un vrai modèle de permissions si le domaine grossit
- Validé le : 10-03-2026
- Contexte technique : NestJS / auth par session ou JWT / API métier interne
### Implémentation (exemple minimal)
```txt
- introduire un enum de rôle minimal côté backend (ex. USER | ADMIN)
- propager ce rôle dans la session ou le token d'auth
- créer un décorateur + guard dédiés pour la capacité sensible
- interdire les booléens front, emails hardcodés ou `if` dispersés dans les contrôleurs
```
### Checklist
- Le rôle vit dans la source de vérité backend
- Le rôle est propagé dans le mécanisme d'auth existant
- Les endpoints sensibles passent par un guard dédié
- Aucun contrôle d'accès critique n'est piloté par le front
- Le passage à RBAC reste possible sans casser le contrat existant
---
<a id="pattern-auth-operations-atomiques"></a>
## Pattern : Opérations auth sensibles — atomiques, idempotentes et cohérentes
- Objectif : garantir que les opérations multi-étapes auth (reset, logout, révocation) ne laissent jamais un état incohérent.
- Contexte : tout flux auth qui combine plusieurs writes : hash de mot de passe, invalidation de token, suppression de session.
- Quand l'utiliser : systématiquement sur toute opération qui touche plusieurs tables auth en séquence.
- Quand l'éviter : opérations de lecture pure.
- Avantage :
- pas de token valide après reset de mot de passe si l'opération est interrompue
- suppression de session idempotente (P2025 absorbé silencieusement)
- comportement prévisible même en cas de retry ou de concurrence
- Limites / vigilance :
- `$transaction` Prisma ne couvre pas les effets de bord réseau (email, cookies) — ces étapes restent hors transaction
- Validé le : 16-03-2026
- Contexte technique : Node.js / Prisma / auth par session ou token
### Implémentation (exemple minimal)
```typescript
// consumePasswordReset — atomique dans une transaction
await prisma.$transaction([
prisma.passwordResetToken.update({
where: { tokenHash },
data: { consumedAt: new Date() },
}),
prisma.user.update({
where: { id: userId },
data: { passwordHash: newHash },
}),
prisma.session.deleteMany({ where: { userId } }),
]);
// Suppression de session — idempotente (P2025 absorbé)
try {
await prisma.session.delete({ where: { sessionToken } });
} catch (err) {
if (err?.code !== 'P2025') throw err; // session déjà supprimée → OK
}
```
### Checklist
- [ ] Toute opération hash + update + delete dans une `$transaction`
- [ ] `P2025` absorbé silencieusement sur les suppressions de session
- [ ] Effets de bord hors transaction documentés (cookie, email)
- [ ] Tests couvrant le cas d'une session déjà expirée

View File

@@ -0,0 +1,138 @@
# Backend — Patterns : Contracts
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
---
<a id="pattern-contracts-first-zod-infer-no-dto"></a>
## Pattern : Contracts-First / Zod-Infer / No-DTO (monorepo TypeScript fullstack)
- Objectif : avoir une seule source de vérité pour les contrats d'interface entre API et client, sans redéfinition manuelle de types.
- Contexte : monorepo TypeScript avec un package partagé (`packages/contracts` ou équivalent), consommé par le backend et le front/mobile.
- Quand l'utiliser : dès qu'une API est consommée par un client TypeScript dans le même repo.
- Quand l'éviter : si le client est externe (autre organisation, autre langage) — dans ce cas, OpenAPI reste la référence.
- Avantage :
- Zéro drift entre contrat et implémentation
- Types TypeScript gratuits via `z.infer<>` — aucune réécriture
- Changement de contrat = erreur de compilation immédiate côté client
- Mocks de tests alignés automatiquement
- Limites / vigilance :
- Ne pas mettre de logique métier dans `packages/contracts` (IO only)
- Attention aux dépendances circulaires si le package grossit
- Validé le : 07-03-2026
- Contexte technique : TypeScript / Zod / NestJS + Expo (React Native) — pattern agnostique framework
### Implémentation (exemple minimal)
```typescript
// packages/contracts/src/auth/auth.schemas.ts
export const RegisterRequestSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
export type RegisterRequest = z.infer<typeof RegisterRequestSchema>; // type GRATUIT
// packages/contracts/src/index.ts
export * from './auth/auth.schemas';
export * from './errors/error-code';
// apps/api/src/modules/auth/auth.controller.ts
import type { RegisterRequest } from '@monrepo/contracts';
// + ZodValidationPipe → validation automatique, zéro DTO manuel
// apps/mobile/src/domains/auth/auth.store.ts
import type { RegisterRequest } from '@monrepo/contracts';
// même type, même schéma, zéro duplication
```
### Structure cible du package contracts
```
packages/contracts/src/
auth/auth.schemas.ts ← request/response auth
users/users.schemas.ts ← request/response users
billing/billing.schemas.ts ← request/response billing (Epic suivant)
errors/error-code.ts ← enum codes d'erreur stables
http/envelopes.ts ← { data, meta } / { error, meta }
index.ts ← re-export tout
```
### Ce qui appartient à contracts
- Schémas Zod request/response
- Types inférés (`z.infer<>`)
- Codes d'erreur applicatifs stables
- Enums et constantes partagées (ex : liste officielle de sujets/topics)
### Ce qui n'appartient PAS à contracts
- Logique métier
- Modules/services/guards framework (NestJS, etc.)
- State management client (Zustand, Redux, etc.)
### Checklist
- [ ] Zéro DTO manuel dans l'API — uniquement `z.infer<typeof Schema>`
- [ ] `ZodValidationPipe` global ou par endpoint pour la validation d'entrée
- [ ] Constantes partagées (enums, listes) dans contracts, jamais dupliquées
- [ ] Mocks de tests importent les types depuis contracts
---
<a id="pattern-contracts-error-codes"></a>
## Pattern : Contracts-First — error codes comme contrat obligatoire
- Objectif : maintenir les codes d'erreur API dans `packages/contracts` pour éviter les clients stringly-typed.
- Contexte : monorepo TypeScript avec `packages/contracts/src/errors/error-code.ts`.
- Règle : toute nouvelle erreur API ⇒ ajout obligatoire dans `error-code.ts` **avant merge**, pas après.
- Risque si ignoré : clients qui testent des strings hardcodées au lieu d'importer l'enum → drift silencieux.
- Validé le : 09-03-2026
- Contexte technique : TypeScript / NestJS + Expo (React Native)
### Checklist
- [ ] Nouvel `error.code` → ajout dans `packages/contracts/src/errors/error-code.ts` en même commit
- [ ] Clients importent l'enum, pas une string littérale
- [ ] PR review : vérifier `error-code.ts` à chaque ajout d'endpoint d'erreur
---
<a id="pattern-http-200-payload-metier"></a>
## Pattern : Réponse HTTP 200 avec payload métier pour les états d'accès
- Objectif : éviter les codes 4xx pour des états métier normaux qui nécessitent un rendu côté client.
- Contexte : endpoints dont la réponse varie selon les droits ou l'état d'abonnement, sans que l'absence de contenu soit une erreur.
- Quand l'utiliser : paywall, trial read-only, quota soft, état d'accès partiel — quand le client doit décider du rendu.
- Quand l'éviter : accès réellement interdit côté serveur (403), non authentifié (401), endpoint inexistant (404).
- Avantage :
- pas de gestion d'exception côté client mobile pour des états courants
- rendu conditionnel (paywall, teaser, empty) piloté par le payload
- log serveur propre — 4xx réservés aux erreurs techniques/sécurité
- Limites / vigilance :
- ne pas généraliser aux vraies erreurs de sécurité — 401/403/404 gardent leur sémantique HTTP
- Validé le : 20-03-2026
- Contexte technique : NestJS / Expo React Native — app-alexandrie story 4.1
### Implémentation (exemple minimal)
```typescript
// GET /community/forums
// Sans abonnement → 200 + { data: { forums: [], paywallRequired: true }, meta }
// Avec abonnement → 200 + { data: { forums: [...], paywallRequired: false }, meta }
// ❌ Anti-pattern
return res.status(402).json({ error: { code: 'SUBSCRIPTION_REQUIRED' } });
// ✅ Pattern correct
return res.status(200).json({
data: { forums: [], paywallRequired: true },
meta: { total: 0 },
});
```
### Règle
- **4xx** = erreur technique ou de sécurité (401 non authentifié, 403 accès interdit, 404 introuvable)
- **200 + flag métier** = état métier normal que le client doit interpréter pour le rendu

View File

@@ -0,0 +1,188 @@
# Backend — Patterns : Multi-tenant
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
---
<a id="pattern-guardrails-multi-tenant-403-404"></a>
## Pattern : Guardrails multi-tenant — 403 vs 404 selon la sémantique
- Objectif : éviter les fuites d'information inter-tenant tout en gardant une sémantique d'erreur claire.
- Contexte : API multi-tenant avec ressources métier isolées et surfaces internes ou opérateur.
- Quand l'utiliser : dès qu'une vérification d'appartenance tenant peut soit refuser explicitement l'accès, soit masquer l'existence d'une ressource.
- Quand l'éviter : contexte mono-tenant ou endpoints purement internes sans enjeu de fuite.
- Avantage :
- clarifie la convention de sécurité
- évite les réponses incohérentes selon les modules
- facilite les tests d'isolation tenant
- Limites / vigilance :
- la convention doit être documentée et appliquée partout
- un mauvais choix entre 403 et 404 peut révéler une information sensible
- Validé le : 16-03-2026
- Contexte technique : API multi-tenant / HTTP / services métier
### Implémentation (exemple minimal)
```txt
- `assertTenantMatch(actor, expectedTenantId)` -> 403 quand la ressource est connue mais l'accès refusé
- `assertResourceBelongsToTenant(actor, resourceTenantId)` -> 404 quand il faut masquer l'existence d'une ressource d'un autre tenant
- documenter la convention dans le module
- couvrir les deux sémantiques par des tests dédiés
```
### Checklist
- Convention 403 vs 404 documentée
- Helpers distincts selon la sémantique métier
- Aucune fuite d'existence cross-tenant sur les ressources métier
- Tests dédiés sur les deux comportements
---
<a id="pattern-repository-tenant-aware"></a>
## Pattern : Repository tenant-aware — `tenantId` obligatoire dans la signature
- Objectif : rendre impossible par construction une query non scopée sur un domaine multi-tenant.
- Contexte : repositories ou services d'accès aux données sur ressources tenant-scoped.
- Quand l'utiliser : dès qu'un domaine métier est massivement filtré par tenant.
- Quand l'éviter : domaines réellement globaux ou méthodes volontairement cross-tenant.
- Avantage :
- force le scoping dès la signature TypeScript
- réduit les oublis de filtre tenant dans les call sites
- rend les exceptions cross-tenant visibles
- Limites / vigilance :
- les exceptions cross-tenant doivent être rares et documentées explicitement
- ne dispense pas d'un second garde-fou dans les mutations sensibles
- Validé le : 16-03-2026
- Contexte technique : TypeScript / Prisma / architecture repository
### Implémentation (exemple minimal)
```txt
- chaque méthode métier tenant-scoped prend `tenantId` en paramètre obligatoire
- les méthodes réellement cross-tenant sont nommées et documentées comme exception
- les call sites Prisma directs sur ces domaines sont interdits ou supprimés
```
### Checklist
- `tenantId` obligatoire sur les méthodes tenant-scoped
- Exceptions cross-tenant documentées
- Appels directs concurrents à Prisma supprimés
- Tests sur scoping tenant au niveau repository
---
<a id="pattern-tenantid-dans-updates"></a>
## Pattern : Défense en profondeur — inclure `tenantId` dans les updates
- Objectif : éviter une mutation cross-tenant même si un identifiant a été mal résolu en amont.
- Contexte : `update` ou `updateMany` sur une ressource tenant-scoped.
- Quand l'utiliser : dès qu'une mutation dépend d'un `id` reçu ou résolu dans un flux multi-tenant.
- Quand l'éviter : ressources globales non liées à un tenant.
- Avantage :
- ajoute une seconde barrière côté base
- réduit l'impact d'un call site mal scopé
- rend la mutation plus sûre sans complexité forte
- Limites / vigilance :
- ne remplace pas le scoping en lecture ni la vérification d'autorisation
- suppose que `tenantId` soit disponible au moment de la mutation
- Validé le : 16-03-2026
- Contexte technique : Prisma / multi-tenant / mutations métier
### Implémentation (exemple minimal)
```txt
- préférer `where: { id, tenantId }` à `where: { id }` sur les updates tenant-scoped
- appliquer la même règle sur `updateMany` et opérations de révocation
- conserver les vérifications métier amont, mais ne pas leur déléguer toute la sécurité
```
### Checklist
- `tenantId` présent dans les clauses `where` des updates sensibles
- Pas de mutation tenant-scoped basée sur `id` seul
- Revue explicite des exceptions documentées
---
<a id="pattern-helper-tenant-module-partage"></a>
## Pattern : Extraire les helpers de résolution tenant dans un module partagé dédié
- Objectif : éviter les couplages sémantiques incorrects entre domaines en centralisant les utilitaires transverses tenant.
- Contexte : toute fonction de résolution de tenant utilisée par plusieurs domaines métier.
- Quand l'utiliser : dès qu'un helper est importé par plus d'un module métier.
- Risque si ignoré : un module métier devient dépendance implicite d'un autre domaine distinct.
- Validé le : 17-03-2026
- Contexte technique : Next.js / TypeScript — app-template-resto
### Implémentation
```typescript
// ✅ src/server/tenant/resolvePublicTenant.ts
export function resolvePublicTenantSelection(env: NodeJS.ProcessEnv) { ... }
// ✅ Rétrocompatibilité depuis l'ancien emplacement si nécessaire
export { resolvePublicTenantSelection } from "@/server/tenant/resolvePublicTenant";
```
---
<a id="pattern-helper-feature-flag-tenant"></a>
## Pattern : Helper centralisé d'activation de features tenant-scoped
- Objectif : centraliser la logique d'activation/désactivation de pages ou modules par tenant dans un helper pur.
- Contexte : app multi-tenant avec features activables (pages publiques, modules optionnels, intégrations).
- Quand l'utiliser : dès qu'une feature peut être activée/désactivée par tenant.
- Avantage :
- helper pur et testable sans I/O
- comportement par défaut sain (`null`/`undefined` → tout activé)
- composants de navigation et pages importent ce helper, jamais Prisma directement
- Validé le : 17-03-2026
- Contexte technique : Next.js App Router / TypeScript — app-template-resto
### Implémentation
```typescript
// src/server/public/publicPagesConfig.ts
export function isPublicPageEnabled(
config: PublicPagesConfigRecord | null | undefined,
pageKey: PublicPageKey
): boolean {
if (!config) return true; // config absente = tout activé par défaut
return config[PAGE_KEY_TO_CONFIG_FIELD[pageKey]];
}
```
**Règle :** `null`/`undefined` → tout activé. Évite les régressions si la config n'a pas été provisionnée.
---
<a id="pattern-en-enforcement-tenant"></a>
## Pattern : EN enforcement optionnel par tenant (toggle + publish gate)
- Objectif : permettre à un tenant d'activer l'obligation de remplir les champs traduits EN, avec une gate à la publication.
- Contexte : app multi-tenant avec internationalisation optionnelle.
- Quand l'utiliser : dès qu'un tenant peut choisir d'activer/désactiver une exigence de contenu i18n.
- Validé le : 21-03-2026
- Contexte technique : Prisma / Next.js App Router — app-template-resto
### Implémentation
```typescript
// 1. Modèle Tenant
enableEn Boolean @default(false)
// 2. Vérification dans chaque action mutante (create/update)
const { enableEn } = await getEnConfig(tenantId);
if (enableEn && !labelEn) throw new HttpError("Traduction EN requise.", { status: 400 });
// 3. Gate publish — vérification de complétude
const result = await checkEnCompleteness(tenantId); // 4 requêtes en Promise.all
// Exclut : isSystem:true, tenantId:null, isVisible:false
if (!result.complete) throw new HttpError("Contenu EN incomplet.", { status: 422 });
```
**Règles :**
- `isVisible: false` n'est pas inclus dans le check (une entité masquée ne bloque pas la publication)
- `revalidatePath` sur **toutes** les pages menu après toggle du flag (pas seulement `/settings`)

View File

@@ -0,0 +1,138 @@
# Backend — Patterns : NestJS
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
---
<a id="pattern-guard-global-nestjs"></a>
## Pattern : Guard global NestJS — ordre d'enregistrement et décorateurs de bypass
- Objectif : protéger tous les endpoints par défaut, avec un mécanisme explicite pour les exceptions.
- Contexte : API NestJS avec plusieurs guards globaux (authn, authz, feature flags...).
- Quand l'utiliser : dès qu'on a 2+ guards globaux dont l'un dépend du résultat de l'autre.
- Quand l'éviter : si un seul guard suffit.
- Avantage :
- Sécurité par défaut (opt-out, pas opt-in)
- Ordre d'exécution garanti et explicite
- Bypass documenté et traçable via décorateurs
- Limites / vigilance :
- L'ordre des `APP_GUARD` dans `providers[]` est l'ordre d'exécution — ne pas inverser
- Exporter le service depuis son module si injecté dans un guard global d'un autre module
- Validé le : 07-03-2026
- Contexte technique : NestJS v10+
### Implémentation (exemple minimal)
```typescript
// app.module.ts
providers: [
{ provide: APP_GUARD, useClass: AuthGuard }, // 1er : peuple request.user
{ provide: APP_GUARD, useClass: EmailVerifiedGuard }, // 2ème : lit request.user
{ provide: APP_GUARD, useClass: EntitlementsGuard }, // 3ème : lit request.user + entitlements
]
// skip-auth.decorator.ts
export const SKIP_AUTH = 'skipAuth';
export const SkipAuth = () => SetMetadata(SKIP_AUTH, true);
// auth.guard.ts
const skip = this.reflector.getAllAndOverride<boolean>(SKIP_AUTH, [
context.getHandler(),
context.getClass(), // permet @SkipAuth() au niveau classe
]);
if (skip) return true;
```
### Checklist
- [ ] AuthGuard enregistré en premier dans `providers[]`
- [ ] AuthModule exporte AuthService si AuthGuard est dans AppModule
- [ ] Décorateur `@SkipAuth()` sur tous les endpoints publics (auth, health, docs)
- [ ] Tests unitaires sur le guard avec reflector mocké
---
<a id="pattern-redis-health-cache-court"></a>
## Pattern : RedisHealthService avec cache interne court
- Objectif : exposer un état Redis exploitable par les guards globaux sans ping Redis à chaque requête.
- Contexte : backend Node/NestJS avec Redis consulté dans le chemin de décision d'écriture.
- Quand l'utiliser : quand plusieurs requêtes concurrentes doivent consulter l'état Redis.
- Quand l'éviter : si Redis n'est pas consulté dans le chemin request/response.
- Avantage :
- réduit fortement le flood de `PING`
- garde un signal d'état suffisamment frais
- Limites / vigilance :
- la fenêtre de cache doit rester courte
- l'état initial doit être explicite et assumé
- Validé le : 10-03-2026
- Contexte technique : NestJS / Redis
### Implémentation (exemple minimal)
```txt
- Mémoriser lastStatus et lastCheck
- Si le dernier check a moins de 5s, retourner l'état en cache
- Sinon exécuter un vrai PING et mettre le cache à jour
- Utiliser un état initial optimiste (`up`) si le produit ne doit pas bloquer les écritures au boot
```
### Checklist
- Cache court documenté
- Pas de ping Redis à chaque requête
- Comportement initial explicite
---
<a id="pattern-quota-redis-atomique"></a>
## Pattern : Quota journalier Redis atomique (INCR + EXPIREAT pipeline)
- Objectif : implémenter un quota d'action journalier sans race condition ni clé TTL orpheline.
- Contexte : quota par utilisateur sur une fenêtre calendaire UTC (posts, requêtes, actions sensibles).
- Quand l'utiliser : toute limite d'action journalière avec Redis disponible.
- Quand l'éviter : si Redis est down — prévoir un mode dégradé permissif (voir implémentation).
- Avantage :
- atomicité garantie : `INCR + EXPIREAT` dans un pipeline `MULTI/EXEC`
- pas de clé sans TTL même en cas de deux requêtes simultanées (`count === 1` concurrent)
- mode dégradé explicite si Redis down (`count === null` → permissif)
- Limites / vigilance :
- compensation `incrBy(-1)` en cas de dépassement — ne couvre pas les crashes entre INCR et la vérification
- la fenêtre expire à minuit UTC, pas à minuit local
- Validé le : 20-03-2026
- Contexte technique : Redis / NestJS / app-alexandrie story 4.2
### Implémentation (exemple minimal)
```typescript
// RedisService — méthode dédiée
async incrWithExpireAt(key: string, expireAtMs: number): Promise<number | null> {
const pipeline = this.client.multi();
pipeline.incr(key);
pipeline.expireAt(key, Math.floor(expireAtMs / 1000));
const results = await pipeline.exec();
return results[0] as number; // valeur post-INCR
}
// Service métier
const today = new Date().toISOString().split('T')[0]; // yyyy-mm-dd UTC
const midnight = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1));
const quotaKey = `app:quota:post:${userId}:${today}`;
const count = await redis.incrWithExpireAt(quotaKey, midnight.getTime());
if (count !== null && count > QUOTA_MAX) {
await redis.incrBy(quotaKey, -1); // compensation
throw new HttpException({ error: { code: 'QUOTA_EXCEEDED' } }, HttpStatus.TOO_MANY_REQUESTS);
}
// count === null → Redis down → mode dégradé permissif
```
### Checklist
- [ ] Vérifier le quota AVANT la création en DB
- [ ] `INCR + EXPIREAT` dans un pipeline atomique
- [ ] Mode dégradé permissif si `count === null` (Redis down)
- [ ] Clé nommée `{app}:quota:{action}:{userId}:{yyyy-mm-dd}` (date UTC)
- [ ] Anti-pattern évité : `incrBy` + `setEx` séparés (race condition si count === 1 concurrent)

View File

@@ -0,0 +1,166 @@
# Backend — Patterns : Next.js
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
---
<a id="pattern-nextjs-runtime-only-logique-pure-testable"></a>
## Pattern : Next.js runtime-only — orchestration en bord et logique pure testable
- Objectif : préserver la testabilité unitaire et la lisibilité du code serveur Next.js en limitant les dépendances runtime-only aux couches d'orchestration.
- Contexte : applications Next.js avec Server Actions, route handlers, modules email/auth et logique métier testée côté Node.
- Quand l'utiliser : dès qu'un flux serveur mélange APIs Next.js runtime-only (`cookies()`, `headers()`, `redirect()`, `server-only`) et logique métier réutilisable.
- Quand l'éviter : petits modules purement runtime sans logique métier notable, ou fonctions triviales sans intérêt à être testées séparément.
- Avantage :
- garde la logique métier importable dans un runner Node standard
- évite que `server-only` contamine des modules purs
- facilite les tests unitaires sans mocks lourds du runtime Next.js
- clarifie la responsabilité des Server Actions et handlers serveur
- Limites / vigilance :
- demande une discipline de découpage
- peut introduire une indirection inutile si la logique extraite est réellement triviale
- les frontières d'injection doivent rester simples pour éviter un excès d'abstraction
- Validé le : 19-03-2026
- Contexte technique : Next.js / Server Actions / Node test runner / modules backend injectables
### Implémentation (exemple minimal)
```txt
- réserver `import "server-only"` aux fichiers qui utilisent réellement des APIs runtime Next.js
- garder la Server Action, route handler ou module email comme couche d'orchestration fine
- extraire la logique métier pure dans une fonction ou un service sans dépendance à `cookies()`, `headers()`, `redirect()` ou `server-only`
- injecter explicitement les dépendances utiles (client DB, token, callback de redirect, logger, etc.)
- tester unitairement le module pur dans le runner Node ; tester l'orchestrateur plus légèrement
```
### Checklist
- `server-only` absent des modules de logique pure
- APIs Next.js runtime-only limitées aux couches d'entrée
- Logique métier principale testable sans runtime Next.js
- Dépendances injectées explicitement quand utile
- Server Action ou handler fin et lisible
---
<a id="pattern-nextjs-server-only-isolation"></a>
## Pattern : Next.js server-only & Server Actions — règles d'isolation
- Objectif : permettre les tests unitaires Node tout en gardant les contraintes runtime Next.js là où elles sont nécessaires.
- Contexte : monorepo Next.js App Router avec logique métier testée en Node runner natif.
- Quand l'utiliser : dès qu'un module mixe logique pure et dépendances runtime Next.js.
- Quand l'éviter : modules purement UI côté client.
- Avantage :
- logique pure testable sans friction (runner Node natif)
- Server Action fine et lisible — orchestration uniquement
- `server-only` explicite et intentionnel, pas par habitude
- Limites / vigilance :
- ne pas mettre `server-only` dans les repositories purs — casse les tests Node hors Next.js
- Validé le : 16-03-2026
- Contexte technique : Next.js App Router / Node.js test runner
### Règles
```txt
- `server-only` uniquement sur les modules qui appellent des APIs Next.js runtime
(cookies(), headers(), redirect()) — pas sur les repositories ni la logique pure
- Logique pure extraite dans un module injectable sans `server-only` :
deleteSession({ prismaClient, sessionToken })
→ testable avec le runner Node sans friction
- Server Action = orchestration mince, elle appelle les modules purs injectés
et gère les dépendances Next.js runtime uniquement
- Logique de validation / sanitisation (safeHttpUrl, etc.) → module utilitaire séparé,
sans import nodemailer / server-only
```
### Checklist
- [ ] `server-only` absent des repositories et modules de logique pure
- [ ] Server Action ≤ 10 lignes, délègue au module pur injectable
- [ ] Modules purs couverts par des tests `.spec.ts` Node sans config spéciale
- [ ] La logique pure ne dépend pas du runtime pour être exécutée
---
<a id="pattern-utilitaires-purs-module-partage"></a>
## Pattern : Utilitaires purs — extraire dans un module sans `server-only`
- Objectif : permettre aux repositories et aux tests d'importer la même implémentation des utilitaires purs sans friction.
- Contexte : fonctions pures (slugify, formatters, validators) utilisées par des repositories qui ont `server-only`.
- Quand l'utiliser : dès qu'une fonction pure est utilisée dans un repository ET dans des tests.
- Risque si ignoré : logique dupliquée dans les tests qui diverge silencieusement de l'implémentation réelle.
- Validé le : 21-03-2026
- Contexte technique : Node.js / Next.js — app-template-resto
### Implémentation
```
src/server/menuAdmin/
allergensRepository.ts ← import { slugify } from "./slugify"
slugify.ts ← export function slugify() {} // pas de "server-only"
tests/
allergens-admin.test.ts ← import { slugify } from "../src/server/menuAdmin/slugify.ts"
```
---
<a id="pattern-reutiliser-champ-existant-v1"></a>
## Pattern : Réutiliser un champ existant plutôt que créer un modèle dédié en V1
- Objectif : éviter la sur-ingénierie en V1 en réutilisant un champ existant quand le besoin est simple.
- Contexte : early-stage, besoin de stocker une configuration simple (URL, flag, valeur unique).
- Quand l'utiliser : quand la donnée a le même cycle de vie qu'un modèle existant et ne nécessite pas de relations.
- Quand l'éviter : si la configuration a son propre cycle de vie, des cardinalités multiples, ou des relations distinctes.
- Avantage : zéro migration supplémentaire, zéro scope creep
- Validé le : 17-03-2026
- Contexte technique : Prisma / Node.js — app-template-resto
### Règle
```txt
- Avant de créer un modèle ReservationConfig, vérifier si PublicHomeProfile.reservationUrl suffit
- Un champ optionnel dans le modèle le plus proche est suffisant en V1
- Ne créer un modèle dédié que si : cycle de vie distinct, relations, ou cardinalités multiples
```
---
<a id="pattern-validation-url-externe"></a>
## Pattern : Valider le protocole d'une URL externe avant de la passer à un lien public
- Objectif : prévenir les injections `javascript:` et URLs malformées dans les `<a href>` ou `<img src>` publics.
- Contexte : toute URL venant d'une config tenant, DB ou saisie utilisateur, rendue dans le HTML.
- Quand l'utiliser : systématiquement sur tout champ URL libre stocké en DB et rendu côté HTML.
- Risque si ignoré : injection `javascript:`, URL malformée, potentiel XSS.
- Validé le : 17-03-2026
- Contexte technique : Node.js / Next.js — app-template-resto
### Implémentation
```typescript
function isSafeUrl(url: string): boolean {
try {
const { protocol } = new URL(url);
return protocol === "https:" || protocol === "http:";
} catch {
return false;
}
}
// Validation complète en service/repository
if (mediaUrl) {
try { new URL(mediaUrl); } catch { throw new HttpError("URL invalide.", { status: 400 }); }
if (!mediaUrl.startsWith("https://") && !mediaUrl.startsWith("http://"))
throw new HttpError("URL doit commencer par https://.", { status: 400 });
if (mediaUrl.length > 500)
throw new HttpError("URL trop longue.", { status: 400 });
}
// Retourner null si invalide — le composant gère l'absence d'URL
```
### Checklist
- [ ] Validation format (`new URL()`) + protocole + longueur max
- [ ] Retourner `null` si invalide, jamais passer la string brute
- [ ] Composant UI reçoit `string | null`, jamais une string non vérifiée

View File

@@ -0,0 +1,247 @@
# Backend — Patterns : Prisma
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
---
<a id="pattern-soft-delete-archivage-explicite"></a>
## Pattern : Soft delete et archivage explicite
- Objectif : permettre la suppression logique sans perte immédiate de données.
- Contexte : données métier critiques, besoins d'audit, restauration ou conformité.
- Quand l'utiliser : dès qu'une suppression peut avoir des impacts métier ou légaux.
- Quand l'éviter : données purement techniques ou réellement éphémères.
- Avantage :
- Restauration possible
- Audit et traçabilité
- Réduction des suppressions irréversibles
- Limites / vigilance :
- Complexité accrue sur les requêtes
- Nécessite une discipline stricte (filtres par défaut)
- Validé le : 25-01-2026
- Contexte technique : API + DB relationnelle
### Implémentation (exemple minimal)
```txt
- Champ deletedAt (nullable) ou status
- Les requêtes standards filtrent deletedAt IS NULL
- Endpoints dédiés pour restauration / purge
- Index DB tenant compte du soft delete
```
### Checklist
- Filtrage soft delete par défaut
- Restauration explicite possible
- Purge maîtrisée (cron / job)
- Index DB adaptés
- Tests sur cas supprimé / restauré
---
<a id="pattern-pagination-robuste-cursor-based"></a>
## Pattern : Pagination robuste (cursor-based) pour les listings
- Objectif : fournir des listings stables et performants sans incohérences entre pages.
- Contexte : endpoints de liste (ex. /users, /orders) avec volume potentiellement important.
- Quand l'utiliser : dès qu'un listing peut dépasser quelques dizaines/centaines d'items ou subir des écritures concurrentes.
- Quand l'éviter : listes strictement petites et statiques.
- Avantage :
- Résultats stables malgré insertions/suppressions
- Meilleure performance que l'offset sur gros volumes
- Expérience client plus fiable
- Limites / vigilance :
- Nécessite un tri déterministe (champ + tie-breaker)
- Complexité légèrement supérieure à offset/limit
- Validé le : 25-01-2026
- Contexte technique : API HTTP + DB (Postgres/MySQL), agnostique framework
### Implémentation (exemple minimal)
```txt
- Trier par (createdAt DESC, id DESC) (exemple)
- Le client envoie cursor = dernier (createdAt,id) reçu
- Le backend renvoie nextCursor si plus de résultats
- Ne jamais exposer de cursor implicite ou non documenté
```
### Checklist
- Tri déterministe (avec tie-breaker)
- nextCursor renvoyé et documenté
- Limite max de page (protection)
- Index DB aligné avec le tri
---
<a id="pattern-idempotency-key-operations-sensibles"></a>
## Pattern : Idempotency key pour opérations sensibles
- Objectif : empêcher les doublons lors de retries ou timeouts.
- Contexte : création de ressources, paiements, webhooks.
- Quand l'utiliser : toute opération non strictement en lecture.
- Quand l'éviter : endpoints purement GET.
- Avantage :
- Protection contre doublons
- Robustesse face aux retries
- Limites / vigilance :
- Stockage et expiration des clés à gérer
- Validé le : 25-01-2026
- Contexte technique : API HTTP + DB transactionnelle
### Implémentation (exemple minimal)
```txt
- Client fournit Idempotency-Key
- Backend stocke la clé + résultat
- Retry retourne le résultat initial
```
### Checklist
- Clé obligatoire sur endpoints sensibles
- Contrainte d'unicité côté DB
- Comportement documenté
---
<a id="pattern-prisma-p2002-update-unique"></a>
## Pattern : mapping explicite de `P2002` Prisma sur create/update de champ unique
- Objectif : transformer un conflit d'unicité prévisible en erreur métier exploitable plutôt qu'en 500 opaque.
- Contexte : `create`, `update` ou `upsert` Prisma sur un champ `@unique` alimenté par une source externe, concurrente, ou après un pre-check.
- Quand l'utiliser : dès qu'un champ unique peut entrer en collision — à la création ET à la modification.
- Quand l'éviter : jamais si le champ peut réellement entrer en collision.
- Avantage :
- réponse client stable
- diagnostic métier plus rapide
- Limites / vigilance :
- le mapping doit rester cohérent avec le format d'erreur API standardisé
- Validé le : 10-03-2026
- Contexte technique : Prisma / PostgreSQL / NestJS
### Implémentation (exemple minimal)
```txt
- Catch explicite de PrismaClientKnownRequestError code P2002
- Mapping vers une erreur métier stable
- Conserver requestId et format d'erreur standardisé
```
### Implémentation (exemple complet)
```typescript
import { Prisma } from "@prisma/client";
try {
await prisma.item.create({ data: { ... } });
// ou: await prisma.item.update({ where: { id }, data: { ... } });
} catch (err) {
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
throw new HttpError("Un élément avec ce nom existe déjà.", { status: 409 });
}
throw err;
}
```
**Important :** un pre-check applicatif (`findUnique` avant `create`) ne suffit pas contre les race conditions. Le `try/catch P2002` est le seul garde-fou fiable. S'applique à `create`, `update`, `updateMany`, `upsert`.
### Checklist
- `P2002` intercepté sur les creates ET les updates sensibles
- Code d'erreur métier stable (409 Conflict)
- Pas de 500 générique sur conflit prévisible
---
<a id="pattern-decimal-prisma-serialisation"></a>
## Pattern : Sérialiser les champs `Decimal` Prisma en string au niveau du repository
- Objectif : éviter que les objets `Decimal` Prisma traversent les couches et causent des erreurs de sérialisation JSON silencieuses.
- Contexte : tout champ `Decimal` en Prisma (ex: `price`) retourné via API ou Server Action.
- Quand l'utiliser : systématiquement sur tout champ `Decimal` dans les repositories.
- Risque si ignoré : `Decimal` n'est pas JSON-sérialisable nativement — comportement varie selon Node vs browser vs test runner.
- Validé le : 17-03-2026
- Contexte technique : Prisma / Node.js — app-template-resto
### Implémentation
```typescript
// Repository — convertir avant de retourner
return {
...dish,
price: dish.price?.toString() ?? null, // Decimal → string
};
// DTO public
type DishDto = {
price: string | null; // pas Decimal
};
```
---
<a id="pattern-prisma-migration-manuelle-p3014"></a>
## Pattern : Prisma — Migration manuelle sans shadow DB (P3014)
- Objectif : créer et appliquer une migration Prisma quand la shadow database est interdite (DB managée, permissions restreintes).
- Contexte : DB managées — Supabase, PlanetScale, Railway avec rôle limité, RDS sans superuser.
- Quand l'utiliser : quand `prisma migrate dev` échoue avec `P3014 Prisma Migrate could not create the shadow database`.
- Risque si ignoré : blocage complet de la migration sur env managé.
- Validé le : 23-03-2026
- Contexte technique : Prisma v7+ — app-alexandrie / Supabase
### Implémentation
```bash
# 1. Écrire le SQL manuellement
mkdir -p prisma/migrations/<timestamp>_<nom>
# Créer migration.sql à la main
# 2. Appliquer le SQL directement en DB
npx prisma db execute --file prisma/migrations/<timestamp>_<nom>/migration.sql
# 3. Marquer la migration comme appliquée dans _prisma_migrations
npx prisma migrate resolve --applied <timestamp>_<nom>
# Note Prisma v7 : ne pas utiliser --schema= (option supprimée), utiliser prisma.config.ts
```
**Ne pas utiliser `prisma db push` en production** — il ne versionne pas les migrations.
---
<a id="pattern-filtrage-metier-service"></a>
## Pattern : Filtrage des règles métier dans le service, pas dans le repository
- Objectif : séparer la couche d'accès aux données (repository) des règles de visibilité métier (service).
- Contexte : entités publiques avec règles de filtrage (`isVisible`, `isActive`), qui varient selon le contexte appelant (public vs admin).
- Quand l'utiliser : dès qu'une règle de visibilité dépend du contexte d'appel.
- Quand l'éviter : filtres de performance (pagination, tenant scoping) — ceux-là restent dans le `where`.
- Avantage :
- la règle est testable unitairement sans Prisma (mock de données brutes)
- la requête DB reste simple et stable entre contextes
- les cas futurs (ex: admin voit les invisibles) ne nécessitent pas de modifier la requête
- Validé le : 17-03-2026
- Contexte technique : Prisma / Node.js / Next.js — app-template-resto
### Implémentation (exemple minimal)
```typescript
// Repository — charge tout ce qui est candidat
async findCategories(tenantId: string) {
return prisma.category.findMany({ where: { tenantId } }); // pas de filtre isVisible
}
// Service — applique la règle métier et mappe vers DTO
const raw = await repo.findCategories(tenantId);
return raw.filter(c => c.isVisible).map(toPublicDto);
// Admin : même repo, filtre différent dans le service admin
return raw.map(toAdminDto); // retourne tout, visible ou non
```

View File

@@ -0,0 +1,160 @@
# Backend — Patterns : Stripe
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
---
<a id="pattern-provider-strategy-integrations-tierces"></a>
## Pattern : Provider-Strategy pour intégrations tierces — périmètre complet
- Objectif : isoler intégralement la logique propre à un prestataire (Stripe, Brevo, Firebase…) derrière une interface stable, pour éviter la contamination du domaine par le SDK tiers.
- Contexte : backend NestJS/TypeScript avec 1+ prestataires externes (paiement, email, storage…).
- Quand l'utiliser : dès qu'un service applicatif dépend d'un SDK tiers (et plus encore s'il y a des webhooks).
- Quand l'éviter : intégration ponctuelle non critique sans effet de bord (rare) — sinon on perd vite le contrôle.
- Avantage :
- Testabilité : mock du provider, pas du SDK
- Remplacement du prestataire sans refactor "en cascade"
- Responsabilités claires : provider = "parle Stripe", service = "parle domaine"
- Limites / vigilance :
- L'interface doit exposer des **types normalisés** (pas de types Stripe)
- Le provider gère aussi les webhooks : validation signature, parsing event, mapping
- Validé le : 09-03-2026
- Contexte technique : NestJS v10+ / intégration Stripe (webhooks) — pattern généralisable
### Implémentation (exemple minimal)
```typescript
// billing-provider.interface.ts (pas d'import Stripe)
export type BillingPlan = 'MONTHLY' | 'ANNUAL';
export type BillingWebhookResult = {
userId: string;
externalId: string;
plan: BillingPlan;
status: 'ACTIVE' | 'INACTIVE' | 'CANCELLED';
currentPeriodEnd: Date | null;
};
export interface BillingProvider {
createCheckoutSession(userId: string, plan: BillingPlan): Promise<{ checkoutUrl: string }>;
cancelSubscription(externalId: string): Promise<void>;
handleWebhook(rawBody: Buffer, signature: string): Promise<BillingWebhookResult | null>;
}
// billing.service.ts (domaine uniquement)
async handleWebhook(rawBody: Buffer, signature: string): Promise<void> {
const result = await this.billingProvider.handleWebhook(rawBody, signature);
if (!result) return;
await this.prisma.subscription.upsert({ /* données normalisées */ });
}
```
---
<a id="pattern-stripe-subscription-metadata"></a>
## Pattern : Stripe — metadata sur `subscription_data`, pas sur la Session
- Objectif : garantir que `userId` (ou tout identifiant métier) soit accessible dans les events `customer.subscription.*`, pas seulement dans `checkout.session.completed`.
- Contexte : intégration Stripe Checkout avec webhooks abonnement.
- Quand l'utiliser : systématiquement dès qu'on crée une Checkout Session liée à une Subscription.
- Risque si ignoré : `metadata.userId` absent des events `customer.subscription.updated/deleted` → silent failure en prod.
- Validé le : 09-03-2026
- Contexte technique : Stripe API v17+ / NestJS
### Implémentation
```typescript
stripe.checkout.sessions.create({
metadata: { userId }, // pour checkout.session.completed
subscription_data: { metadata: { userId } }, // pour customer.subscription.*
});
```
---
<a id="pattern-webhook-parsing-unique"></a>
## Pattern : Webhooks entrants — parsing unique (single `constructWebhookEvent`)
- Objectif : appeler `constructWebhookEvent` une seule fois par requête, puis router vers des extracteurs purs.
- Contexte : endpoint webhook recevant des events de plusieurs types (subscription, pack, facture…).
- Quand l'utiliser : dès qu'on a 2+ handlers webhook sur le même endpoint.
- Risque si ignoré : double vérification de signature + états partiels possibles (sub OK / pack KO).
- Validé le : 09-03-2026
- Contexte technique : Stripe / NestJS
### Implémentation
```typescript
// 1. Parser unique — 1 seul constructWebhookEvent(rawBody, sig) → event opaque
// 2. Extracteurs purs, sans effet de bord :
handleSubscriptionWebhookEvent(event): WebhookResult | null
handlePackWebhookEvent(event): PackWebhookResult | null
// 3. Orchestrateur unique appelle les extracteurs, persiste les résultats
```
---
<a id="pattern-restauration-achats-stripe"></a>
## Pattern : restauration d'achats Stripe en 3 étapes
- Objectif : reconstruire un état local cohérent à partir de Stripe sans dépendre d'une hypothèse fragile.
- Contexte : flux de restore purchases mobile/web avec état local potentiellement désynchronisé.
- Quand l'utiliser : dès qu'un utilisateur peut restaurer des achats depuis un nouveau device ou après désynchronisation.
- Quand l'éviter : si l'état Stripe n'est pas la source de vérité.
- Avantage :
- rend la réconciliation explicite
- supporte retries et restaurations tardives
- Limites / vigilance :
- la pagination Stripe et l'idempotence d'écriture restent obligatoires
- Validé le : 10-03-2026
- Contexte technique : Stripe API / backend Node/NestJS
### Implémentation (exemple minimal)
```txt
1. Résolution du customer Stripe (ID persisté en DB, fallback robuste si absent)
2. Reconstruction de l'état Stripe utile au domaine
3. Réconciliation et écritures locales idempotentes
```
### Checklist
- `stripeCustomerId` persistant côté app
- Réconciliation explicite documentée
- Upsert ou écriture idempotente
---
<a id="pattern-subscription-trial-vs-paid"></a>
## Pattern : Sémantique explicite `Trial` vs `Paid` dans Subscription
- Objectif : aligner le modèle métier, les guards et les jeux de tests sur une définition unique de l'abonnement payant actif.
- Contexte : modèle `Subscription``trialEndsAt` matérialise un essai.
- Quand l'utiliser : dès qu'un même enregistrement supporte trial et abonnement payant.
- Quand l'éviter : si trial et abonnement payant sont modélisés par des entités distinctes.
- Avantage :
- évite les incohérences silencieuses dans les guards
- rend les fixtures et mocks e2e cohérents avec la règle métier
- Limites / vigilance :
- toute logique `isActive` doit préciser si elle signifie "trial ou paid" ou "paid only"
- Validé le : 10-03-2026
- Contexte technique : Backend agnostique / modèle d'abonnement
### Implémentation (exemple minimal)
```txt
- Un abonnement payant actif n'est pas seulement status = ACTIVE
- Il doit aussi avoir trialEndsAt = null
- Les fixtures et mocks e2e d'un abonnement payant fixent toujours trialEndsAt: null
```
### Checklist
- Règle métier explicitée
- Guards alignés sur la sémantique choisie
- Fixtures et seeds cohérents

View File

@@ -0,0 +1,18 @@
# Backend — Risques & vigilance — Index
Risques backend susceptibles de provoquer des incidents prod, failles de sécurité, bugs non diagnostiquables, ou régressions coûteuses.
Avant toute proposition backend, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
---
| Fichier | Domaine | Entrées clés |
|---------|---------|--------------|
| `auth.md` | Auth, sessions, guards, accès | AuthN/AuthZ dispersée, guard global manquant, null-check request.user, AdminRoleGuard sans @RequireAdminRole, GET sans contrôle accès, cookie après révocation, mock session sans expiresAt, buildApp partagé e2e |
| `contracts.md` | Contrats, validation, codes erreur | Contrats implicites, erreurs non standardisées, duplication constantes, schema orphelin, code erreur générique 409, ForbiddenException pour validation |
| `prisma.md` | Prisma, DB, transactions, migrations | @unique nullable, TOCTOU transaction, OR tenantId null, nextOrder race condition, tenantId sans FK, schema divergence spec, getter manquant, init module build, clearAllMocks imbriqué, cursor non validé |
| `stripe.md` | Stripe, paiements, webhooks, subscriptions | billing_cycle_anchor vs current_period_end, list() sans has_more, concurrence trial→payant, non-idempotence, 200 pendant processing |
| `nestjs.md` | NestJS, controllers, providers | TooManyRequestsException NestJS 11, controller corrompu insertions, repository dead layer, interface provider incomplète |
| `redis.md` | Redis, cache, quotas, TTL | Thrash connexion sous charge, entitlements TTL > SLA, compteurs in-memory, TTL heure locale ±12h |
| `nextjs.md` | Next.js, build, routing | Prisma init au chargement module, server-only dans repositories, redirect boucle infinie feature flags |
| `general.md` | Observabilité, migrations, performance | Observabilité insuffisante, migrations non reproductibles, upsert N+1 provider |

View File

@@ -0,0 +1,225 @@
# Backend — Risques & vigilance : Auth
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet.
---
<a id="risque-authn-authz-dispersee"></a>
## AuthN/AuthZ dispersée (contrôles d'accès au fil de l'eau)
### Risques
- Règles de permissions incohérentes selon endpoints
- Failles "oubliées" sur un endpoint secondaire
- Audit impossible
### Symptômes
- Utilisateurs qui accèdent à des ressources non prévues
- Correctifs en urgence "on ajoute un if ici"
- Bugs qui réapparaissent après refactor
### Bonnes pratiques / mitigations
- Centraliser authn/authz (middleware/policies)
- Tests sur règles critiques
- Logs/audit des décisions d'accès
---
<a id="risque-guard-global-manquant"></a>
## Guard global manquant (request.user jamais peuplé)
### Risques
- Chaîne auth bâtie sur une fondation inopérante (tout "a l'air OK" en dev/tests, mais casse en prod)
- Guards aval qui dépendent de `request.user` en erreur (ou contournements involontaires)
- Découvert tard (souvent uniquement en code review ou en prod)
### Symptômes
- `request.user` vaut `undefined` dans un guard supposé "après auth"
- Endpoints qui passent alors qu'ils devraient être refusés (si les guards aval se désactivent/retournent true par défaut)
- Tests "verts" car trop mockés (pas de test e2e qui valide le pipeline complet)
### Bonnes pratiques / mitigations
- Poser explicitement le guard global dès les foundations (au moins `AuthGuard`)
- Vérifier l'ordre des `APP_GUARD` (AuthGuard avant tout guard qui lit `request.user`)
- Ajouter au minimum 1 test d'intégration/e2e qui prouve que `request.user` est bien peuplé sur un endpoint protégé
---
<a id="risque-guard-request-user-null"></a>
## Guard NestJS route-level — null-check manquant sur `request.user`
### Risques
- Un guard route-level qui lit `request.user.userId` sans null-check lève une `TypeError` (500) si `request.user` est absent
- Mauvaise registration de module, test d'intégration mal configuré, ou middleware custom peuvent produire cet état
### Symptômes
- `TypeError: Cannot read properties of undefined (reading 'userId')` en prod
- Tests "verts" car `request.user` mocké globalement, mais pas le guard isolé
### Bonnes pratiques / mitigations
```typescript
const user = (request as any).user as { userId: string } | undefined;
if (!user?.userId) {
throw new UnauthorizedException({ error: { code: 'UNAUTHENTICATED', message: '...' } });
}
```
- **Règle** : les guards route-level ne font pas confiance aux guards globaux pour leurs invariants — ils se défendent eux-mêmes.
- Contexte technique : NestJS v10+ — 09-03-2026
---
<a id="risque-cookie-apres-revocation-db"></a>
## Suppression du cookie après révocation DB sur logout
### Risques
- Si la révocation DB échoue avant la suppression du cookie, l'utilisateur garde un cookie local devenu incohérent
- L'utilisateur peut rester bloqué dans un état où il ne peut plus se déconnecter proprement
- Le comportement diffère selon la disponibilité de la base
### Symptômes
- Logout qui échoue par intermittence quand la DB est instable
- Cookie de session toujours présent côté navigateur après erreur serveur
- Réessais de logout qui produisent des états difficiles à diagnostiquer
### Bonnes pratiques / mitigations
- Toujours supprimer le cookie en premier, même si la révocation DB échoue ensuite
- Traiter la suppression côté DB en best-effort ou avec gestion d'idempotence adaptée
- Vérifier en test qu'un échec DB ne laisse pas l'accès browser actif
- Contexte technique : Next.js / auth par cookie / session persistée — 16-03-2026
---
<a id="risque-get-sans-controle-acces"></a>
## Endpoints GET sans contrôle d'accès sur ressource protégée
### Risques
- Un endpoint de lecture expose des données premium/protégées à tout utilisateur authentifié
- La règle "seuls les writes vérifient les droits" est un anti-pattern qui cause des fuites silencieuses
### Symptômes
- `getCategories`, `getThreads` ou équivalent accessible sans vérification d'entitlements
- Endpoint write protégé par `assertForumAccess` mais GET correspondant non protégé
### Bonnes pratiques / mitigations
- Tout endpoint retournant des données liées à une ressource protégée (forum pack, contenu premium) doit appeler `assertForumAccess` ou équivalent, même pour les GET
- **Checklist review** : pour chaque nouveau GET, vérifier qu'il passe par le guard/helper d'accès si la ressource appartient à un scope protégé
- Contexte technique : NestJS / app-alexandrie — 23-03-2026
---
<a id="risque-adminroleguard-sans-decorateur"></a>
## NestJS `@UseGuards(AdminRoleGuard)` sans `@RequireAdminRole()` — silencieusement ouvert
### Risques
- `AdminRoleGuard.canActivate()` lit la metadata `REQUIRE_ADMIN_ROLE_KEY` posée par `@RequireAdminRole()`
- Si le décorateur est absent, `requiresAdmin = false/undefined` → le guard retourne `true` et laisse passer sans vérification
### Symptômes
- Endpoint admin accessible à tout utilisateur authentifié
- Zéro erreur de compilation ou de démarrage — le bug est silencieux
### Bonnes pratiques / mitigations
```typescript
// ✅ Correct — les deux décorateurs ensemble
@Post('admin/ressource')
@UseGuards(AdminRoleGuard)
@RequireAdminRole()
async createRessource(...) {}
// ❌ Silencieusement non protégé — @RequireAdminRole() manquant
@Post('admin/ressource')
@UseGuards(AdminRoleGuard)
async createRessource(...) {}
```
- Règle : s'applique à tout guard NestJS qui délègue la décision à une metadata de décorateur
- **Checklist review** : vérifier systématiquement les endpoints admin que `@RequireAdminRole()` est présent
- Contexte technique : NestJS / guards metadata — app-alexandrie 23-03-2026
---
<a id="risque-mock-session-sans-expiresat"></a>
## Mock Prisma session sans filtre `expiresAt` — divergence test/prod
### Risques
- Le mock `session.findFirst` omet de filtrer `expiresAt` → des sessions expirées passent en test alors qu'elles seraient rejetées en prod
- Masque des régressions sur la logique d'expiration de session
### Symptômes
- Tests e2e verts avec un token de session expiré
- Bug découvert uniquement en prod quand la TTL est dépassée
### Bonnes pratiques / mitigations
Le mock doit répliquer **tous** les critères de `getUserByToken()` en prod : `revokedAt === null` ET `expiresAt > now` :
```typescript
// ✅ Mock complet fidèle à la prod
findFirst: jest.fn().mockImplementation(({ where }) => {
const session = store[where.accessToken];
if (!session) return null;
if (where.revokedAt === null && session.revokedAt !== null) return null;
if (where.expiresAt?.gt && session.expiresAt <= where.expiresAt.gt) return null;
return session;
})
```
- **Règle** : `seedSession()` doit initialiser `expiresAt` à +30j par défaut. Ajouter un helper `seedExpiredSession()` si des tests de session expirée sont nécessaires.
- Contexte technique : NestJS / Prisma mock / e2e — app-alexandrie 24-03-2026
---
<a id="risque-tests-e2e-buildapp-partage"></a>
## Tests e2e autorisation : scénarios non-abonné avec `buildApp` partagé
### Risques
- Un `describe` e2e avec `buildApp` partagé en `beforeAll` (entitlements actifs) rend impossible le test de scénarios non-abonné sans pollution entre tests
- Tenter de surcharger le mock partagé (`jest.fn().mockResolvedValueOnce(...)`) dans un `it` intermédiaire est fragile et crée des effets de bord
### Symptômes
- Scénario "non-abonné → 403" n'est jamais testé, ou pollue les autres tests si le mock est modifié en cours de describe
### Bonnes pratiques / mitigations
Créer une instance `buildApp` isolée pour les scénarios d'autorisation alternatifs :
```typescript
it('retourne 403 si subscription inactive', async () => {
const isolatedApp = await buildApp({
getEntitlementsForUser: jest.fn().mockResolvedValue({
subscription: { isActive: false, plan: 'free' }
})
});
// ... tests
await isolatedApp.close();
});
```
- **Règle** : ne jamais tenter de surcharger un mock partagé dans un `it` — créer un `buildApp` isolé avec `app.close()` en fin de test
- Contexte technique : NestJS / Jest e2e — app-alexandrie 24-03-2026

View File

@@ -0,0 +1,165 @@
# Backend — Risques & vigilance : Contracts
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet.
---
<a id="risque-contrats-api-implicites"></a>
## Contrats API implicites (validation faible ou absente)
### Risques
- Entrées non validées → erreurs bizarres / vulnérabilités
- Changements qui cassent le front et les intégrations
### Symptômes
- 500 sur erreurs utilisateur
- Incohérences de format de réponse
- "Ça marche en staging, pas en prod" (données réelles)
### Bonnes pratiques / mitigations
- Schémas (OpenAPI/JSON Schema) + validation serveur
- Formats de réponse cohérents
- Versionner/éviter breaking changes
---
<a id="risque-erreurs-non-standardisees"></a>
## Erreurs non standardisées (4xx/5xx incohérents)
### Risques
- Front et automatisations impossibles à rendre robustes
- Debug long (pas de codes internes, pas de corrélation)
### Symptômes
- Clients qui "retry" sur des 4xx
- Messages techniques exposés aux utilisateurs
- Logs inexploitables
### Bonnes pratiques / mitigations
- Mapping HTTP standard + format d'erreur stable
- Codes internes d'erreurs applicatives
- requestId/traceId partout
---
<a id="risque-duplication-constantes-contracts"></a>
## Duplication silencieuse de constantes partagées (contracts) via fichier orphelin
### Risques
- Deux sources de vérité qui divergent silencieusement (ex : topics officiels, enums métier, slugs)
- Bug non détecté par TypeScript si la duplication est dans un fichier non importé (code mort)
### Symptômes
- Incohérences entre API et client sur des listes/enums "censées être partagées"
- "Ça marche chez moi" selon l'endroit où la constante est importée
- Un fichier de config existe dans `apps/*` mais n'est jamais importé/greffé au runtime
### Bonnes pratiques / mitigations
- Toute constante partagée vit dans `packages/contracts/src/` et est importée depuis là (jamais recopiée dans `apps/*`)
- En review : repérer les fichiers "config/constants" ajoutés dans `apps/*` sur des domaines déjà couverts par `contracts`
- (Optionnel) Outillage : intégrer une étape de détection de code mort / exports inutilisés au CI si ça devient récurrent
---
<a id="risque-contracts-schema-orphelin"></a>
## Contracts : schema orphelin / type de retour désynchronisé
### Risques
- Un `RequestSchema` défini dans `packages/contracts` mais jamais importé dans le controller ni le service mobile → dead code silencieux qui crée une fausse confiance
- Un type de retour inline (`string` brut) à la place du type contracts → désynchronisation silencieuse entre contrat et implémentation
### Symptômes
- `grep` du nom du schema ne trouve aucun `import` en dehors de sa définition
- Service retourne `Promise<{ status: string }>` au lieu de `Promise<CurationResponse>` — le `status` n'est pas validé comme `CurationStatus`
- Endpoints `POST /action` sans body ayant un schema `{ pathParam: string }` — le param vient du path, pas du body
### Bonnes pratiques / mitigations
À chaque story qui ajoute des schemas dans `packages/contracts`, vérifier en review :
1. Chaque `RequestSchema` est utilisé dans un `ZodValidationPipe` (API) ou importé dans le service mobile.
2. Les `ResponseSchema` correspondent au type de retour typé du service (`Promise<TheType>`, pas un type inline).
3. Les endpoints sans body (`POST /action`) définissent `z.object({})` ou omettent le body schema — ne jamais placer les path params dans le body schema.
```typescript
// ❌ Anti-pattern — type inline, status non typé
async showcaseThread(...): Promise<{ threadId: string; status: string }> { ... }
// ✅ Pattern correct — type contracts importé
import type { CurationResponse } from '@app-alexandrie/contracts';
async showcaseThread(...): Promise<CurationResponse> { ... }
```
- Contexte technique : NestJS / Zod / contracts-first — app-alexandrie 23-03-2026
---
<a id="risque-code-erreur-generique-409"></a>
## Code d'erreur générique sur statut HTTP sémantique (409 CONFLICT)
### Risques
- Utiliser `VALIDATION_ERROR` ou `INTERNAL_ERROR` sur un 409 rend les erreurs indistinguables côté client et monitoring
- Les clients (mobile, monitoring, tests) ne peuvent pas brancher une logique conditionnelle sans un code sémantique
### Symptômes
- Tous les conflits métier remontent le même code → impossible de distinguer "alias déjà résolu" de "handle déjà pris"
- Tests forcés à matcher le message texte au lieu du code → fragiles
### Bonnes pratiques / mitigations
Chaque scénario métier distinct doit avoir son propre code dans `error-code.ts` :
```typescript
// ❌ Anti-pattern — code générique sur 409
throw new ConflictException({ error: { code: 'VALIDATION_ERROR', message: '...' } });
// ✅ Correct — code sémantique spécifique
throw new ConflictException({ error: { code: 'ALIAS_ALREADY_RESOLVED', message: '...' } });
throw new ConflictException({ error: { code: 'HANDLE_ALREADY_TAKEN', message: '...' } });
```
- **Règle** : 1 scénario métier distinct = 1 code d'erreur distinct
- **Checklist review** : tout 409/422 doit avoir un code dans `error-code.ts`, jamais `VALIDATION_ERROR` ou `INTERNAL_ERROR`
- Contexte technique : NestJS / error-code.ts — app-alexandrie 24-03-2026
---
<a id="risque-forbidden-pour-validation"></a>
## `ForbiddenException` (403) utilisé pour des erreurs de validation
### Risques
- Les clients qui filtrent par HTTP 400 manquent les erreurs de validation lancées en 403
- Sémantique API incorrecte → comportements clients imprévisibles
### Symptômes
- `ForbiddenException` lancée pour des tags invalides, des formats incorrects, des liens HTTP
- Clients API qui ignorent ces erreurs ou les traitent comme des refus d'accès
### Bonnes pratiques / mitigations
Tableau de correspondance :
| Cas | Exception correcte | Code HTTP |
|---|---|---|
| Tags invalides, contenu trop long, format incorrect | `BadRequestException` | 400 |
| Accès refusé explicitement (accès forum, trial read-only) | `ForbiddenException` | 403 |
| Quota dépassé | `HttpException(429)` via `HttpStatus.TOO_MANY_REQUESTS` | 429 |
- **Règle** : HTTP 403 = "tu n'as pas le droit d'effectuer cette action". HTTP 400 = "ta requête est mal formée".
- Contexte technique : NestJS / HTTP — 20-03-2026

View File

@@ -0,0 +1,72 @@
# Backend — Risques & vigilance : Général
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet.
---
<a id="risque-observabilite-insuffisante"></a>
## Observabilité insuffisante (logs non structurés, pas de corrélation)
### Risques
- MTTR très élevé : on devine
- Incapacité à mesurer l'impact utilisateur
### Symptômes
- Logs "ça a crash" sans contexte
- Impossible de relier une requête à une erreur
- Latence qui dérive sans alerte
### Bonnes pratiques / mitigations
- Logs structurés + requestId/traceId
- Métriques de base (latence, erreurs, throughput)
- Alertes simples sur 5xx/latence
---
<a id="risque-migrations-risquees"></a>
## Migrations risquées / non reproductibles
### Risques
- Downtime
- Perte de données
- Incohérence entre environnements
### Symptômes
- "Ça marche en local" mais pas en prod
- Migration qui échoue à mi-chemin
- Rollback impossible
### Bonnes pratiques / mitigations
- Migrations versionnées + tests staging
- Stratégie expand/contract si besoin
- Plan de rollback/mitigation
---
<a id="risque-upsert-n-plus-un-provider"></a>
## Boucle `upsert` N+1 sur synchronisation provider
### Risques
- Latence multipliée par le nombre d'items
- Charge DB inutile
- Timeouts ou contention sur gros volumes
### Symptômes
- Une boucle applicative exécute un `upsert` par item
- Temps de traitement qui explose avec le volume
- Logs SQL répétitifs et séquentiels
### Bonnes pratiques / mitigations
- Batcher quand c'est possible
- Précharger les données nécessaires avant boucle
- Mesurer explicitement le coût d'un `upsert` unitaire dans les flux de sync
- Contexte technique : Prisma / synchronisation provider — 10-03-2026

View File

@@ -0,0 +1,102 @@
# Backend — Risques & vigilance : NestJS
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet.
---
<a id="risque-nestjs-toomanyrequest"></a>
## NestJS 11 — `TooManyRequestsException` inexistante
### Risques
- `TooManyRequestsException` n'est pas exportée par `@nestjs/common` en NestJS ≥ 11
- Erreur de compilation ou 500 si utilisée directement
### Symptômes
- `Cannot find name 'TooManyRequestsException'` à la compilation
- Test qui passe sur NestJS 10 mais échoue sur 11+
### Bonnes pratiques / mitigations
```typescript
// Pattern sûr pour HTTP 429
throw new HttpException(
{ error: { code: 'QUOTA_EXCEEDED', message: '...' } },
HttpStatus.TOO_MANY_REQUESTS,
);
```
- Contexte technique : NestJS v11+ — 20-03-2026
---
<a id="risque-controller-corrompu-insertions"></a>
## Controller NestJS corrompu par insertions multiples
### Risques
- Des méthodes imbriquées, décorateurs orphelins ou routes dupliquées cassent la syntaxe TypeScript sans que le compilateur ne l'attrape toujours
- La story est marquée "completed" alors que le code ne compile pas
### Symptômes
- `@Get('/route')` apparaît dans le corps d'une autre méthode
- La même route est déclarée 2-3 fois dans le même controller
- Erreur NestJS au runtime mais pas à la compilation
### Bonnes pratiques / mitigations
- Quand on ajoute >3 endpoints à un controller existant, réécrire le fichier entier en partant du fichier original
- Ne jamais insérer par blocs séparés — la concaténation casse la structure AST
- **Checklist review** : grep `@Get\|@Post\|@Patch\|@Delete` dans le controller et vérifier qu'aucune route n'est dupliquée
- Contexte technique : NestJS / TypeScript — app-alexandrie 20-03-2026
---
<a id="risque-repository-dead-layer"></a>
## Repository layer non branché (dead layer)
### Risques
- Donner une impression de sécurité alors que le code métier continue d'appeler l'ORM directement
- Multiplier les chemins d'accès aux données avec des règles différentes
- Payer le coût d'une abstraction qui n'a aucun effet réel
### Symptômes
- Un repository est créé mais les anciens call sites Prisma restent en place
- Les nouvelles règles de scoping ou de sécurité ne s'appliquent pas partout
- La review montre des fichiers de repository peu ou jamais importés
### Bonnes pratiques / mitigations
- Vérifier qu'une nouvelle couche d'abstraction est réellement branchée dans les call sites existants
- Rechercher explicitement les appels directs restants lors de la review
- Refuser l'introduction d'une couche repository tant que la migration effective n'est pas faite
- Contexte technique : TypeScript / Prisma / refactor d'accès aux données — 16-03-2026
---
<a id="risque-interface-provider-incomplete"></a>
## Interface provider incomplète ou divergente de ses implémentations
### Risques
- Une implémentation expose des méthodes non déclarées dans le contrat commun
- Les appelants contournent l'interface et se couplent à un provider concret
- Une stratégie provider devient non interchangeable en pratique
### Symptômes
- Appels avec cast ou accès direct à une implémentation spécifique
- Méthodes présentes dans une classe mais absentes de l'interface
- Régression lors d'un changement de provider
### Bonnes pratiques / mitigations
- Toute capacité commune attendue par les appelants doit être déclarée dans l'interface
- Interdire les méthodes "cachées" consommées hors contrat
- Tester au moins une implémentation par le contrat abstrait
- Contexte technique : TypeScript / provider strategy — 10-03-2026

View File

@@ -0,0 +1,75 @@
# Backend — Risques & vigilance : Next.js
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet.
---
<a id="risque-prisma-init-module-build"></a>
## Prisma initialisé au chargement de module — casse le build Next.js
### Risques
- Un import global qui initialise Prisma immédiatement peut faire échouer la collecte de pages/routes au build si `DATABASE_URL` n'est pas disponible dans l'environnement de build
### Symptômes
- `PrismaClientInitializationError` ou `Error: Environment variable not found: DATABASE_URL` au `next build`
- L'app tourne en dev mais le build CI échoue
### Bonnes pratiques / mitigations
- Préférer une initialisation lazy-safe : retarder l'accès DB au moment de l'appel métier
- Retourner un proxy qui lève une erreur claire uniquement lors du premier accès réel à la DB
- Ne jamais instancier `new PrismaClient()` au top-level d'un module importé par Next.js
- Contexte technique : Next.js App Router / Prisma — app-template-resto 16-03-2026
---
<a id="risque-server-only-repositories-tests"></a>
## `server-only` dans les repositories — bloque les tests unitaires
### Risques
- `import "server-only"` empêche l'exécution des fichiers hors runtime Next.js
- Les tests Node.js échouent avec `Error: This module cannot be imported from a Client Component module`
### Symptômes
- Tests qui passent via le dev server mais échouent via `jest` en mode node
- Erreur au `require()` d'un repository depuis un test unitaire
### Bonnes pratiques / mitigations
- Ne mettre `server-only` que dans les fichiers qui utilisent des APIs Next.js runtime (`cookies()`, `headers()`, `redirect()`)
- **Ne pas** mettre `server-only` dans les repositories purs (qui n'appellent que Prisma)
- Alternative de secours : créer un stub `node_modules/server-only/index.js` no-op pour les tests
- Contexte technique : Next.js App Router / Jest — app-template-resto 16-03-2026
---
<a id="risque-redirect-boucle-infinie"></a>
## Redirect vers la page désactivée elle-même (boucle infinie feature flags)
### Risques
- Une page désactivée redirige vers elle-même via le fallback — boucle infinie silencieuse absorbée par Next.js mais UX cassée
### Symptômes
- Page `/` désactivée → redirect vers `buildLocalizedPath("home")` = `/` → boucle
- Next.js absorbe la boucle mais l'utilisateur voit un écran bloqué ou vide
### Bonnes pratiques / mitigations
```typescript
// Si la page est sa propre destination de fallback, ne pas rediriger
if (pageKey === "home") return null; // évite redirect home → home
return buildLocalizedPath(locale, "home");
```
- Règle : dans tout mécanisme de redirection sur page désactivée, toujours vérifier que `pageKey !== fallbackKey`
- Retourner `null` (accès non bloqué) plutôt que de boucler
- Contexte technique : Next.js App Router / feature flags tenant — app-template-resto 17-03-2026

View File

@@ -0,0 +1,306 @@
# Backend — Risques & vigilance : Prisma
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet.
---
<a id="risque-prisma-unique-nullable"></a>
## PostgreSQL / Prisma : `@unique` sur champ nullable (idempotence cassée)
### Risques
- Doublons en base malgré un "unique" attendu (PostgreSQL autorise plusieurs `NULL` dans un index UNIQUE)
- Upserts non idempotents si la clé peut être `null` (`where: { externalId: null }` crée plusieurs lignes)
### Symptômes
- Plusieurs enregistrements "équivalents" avec `externalId = NULL`
- Rejouer un webhook / retry réseau crée une nouvelle ligne au lieu d'upsert
### Bonnes pratiques / mitigations
- Toute clé utilisée dans un `where` d'`upsert` doit être **non-nullable**
- Si un identifiant externe peut légitimement être `null`, ne pas l'utiliser comme clé d'idempotence : choisir une autre clé unique non-nullable
---
<a id="risque-prisma-transaction-toctou-tenantid"></a>
## Prisma `$transaction` : fenêtres TOCTOU (check hors transaction)
### Risques
- Un pre-check + une `$transaction` avec un `update` non sécurisé crée une fenêtre TOCTOU
- Deux appels concurrents peuvent tous deux passer le check et agir simultanément
- En multi-tenant : un bug upstream peut permettre une écriture cross-tenant malgré le guard applicatif
### Symptômes
- Double action sur un état booléen (ex : double mise en vitrine) si le check n'est pas dans la transaction
- Écriture sur une ressource d'un autre tenant possible en race condition
### Bonnes pratiques / mitigations
**Cas 1 — Multi-tenant : inclure `tenantId` dans chaque écriture**
```typescript
// ❌ Anti-pattern — check OK mais écriture sans tenantId
const existing = await prisma.item.findMany({ where: { id: { in: ids }, tenantId } });
await prisma.$transaction(
ids.map((id, idx) => prisma.item.update({ where: { id }, data: { sortOrder: idx + 1 } }))
);
// ✅ Défense en profondeur — tenantId dans chaque écriture
await prisma.$transaction(
ids.map((id, idx) => prisma.item.updateMany({ where: { id, tenantId }, data: { sortOrder: idx + 1 } }))
);
```
- Règle : toute écriture Prisma sur une ressource tenant-aware doit inclure `tenantId` dans le WHERE, même dans une transaction précédée d'un check
- Utiliser `updateMany`/`deleteMany` pour inclure `tenantId` sans exception si 0 lignes
**Cas 2 — Idempotence / plafond : re-check d'état à l'intérieur de la transaction**
```typescript
// ❌ Anti-pattern : check d'état hors transaction
if (resource.isActive) throw ...;
await prisma.$transaction(async (tx) => {
// resource.isActive a pu changer entre-temps
return tx.resource.update(...);
});
// ✅ Pattern correct : check ET update dans la transaction
await prisma.$transaction(async (tx) => {
const current = await tx.resource.findUnique({ where: { id } });
if (current?.isActive) throw ...; // re-check atomique
const count = await tx.resource.count(...);
if (count >= LIMIT) throw ...;
return tx.resource.update(...);
});
```
- Règle : tout guard métier de type "déjà fait / plafond atteint" doit être vérifié à l'intérieur de la transaction, pas avant
- Contexte technique : Prisma / multi-tenant — app-template-resto 21-03-2026 ; NestJS / Prisma — app-alexandrie 23-03-2026
---
<a id="risque-prisma-or-tenantid-null"></a>
## Prisma OR multi-tenant : `tenantId: null` manquant sur la branche système
### Risques
- Sur un modèle à `tenantId` nullable distinguant ressources "système" et "tenant", un filtre `{ isSystem: true }` sans `tenantId: null` expose des ressources corrompues à tous les tenants
### Symptômes
- Un tag `isSystem: true` avec `tenantId` non-null est exposé à tous les tenants
- Bug de sécurité difficile à détecter car le comportement nominal semble correct
### Bonnes pratiques / mitigations
```typescript
// ❌ Trop permissif
OR: [{ isSystem: true }, { tenantId, isSystem: false }]
// ✅ Défense en profondeur — double condition sur la branche système
OR: [{ isSystem: true, tenantId: null }, { tenantId, isSystem: false }]
```
- Règle : sur tout modèle `tenantId?` (nullable) + flag `isSystem`/`isGlobal`/`isPublic`, la branche "ressource publique" du filtre OR doit toujours inclure `tenantId: null`
- Contexte technique : Prisma / multi-tenant — app-template-resto 21-03-2026
---
<a id="risque-nextorder-hors-transaction"></a>
## Calcul de `nextOrder` hors transaction (race condition `sortOrder`)
### Risques
- Deux requêtes concurrentes obtiennent le même `MAX(sortOrder)` et créent deux entités avec le même `sortOrder`
### Symptômes
- Deux items avec le même `sortOrder` dans la même catégorie/scope
- Bug aléatoire selon la charge — invisible en dev, présent en prod
### Bonnes pratiques / mitigations
```typescript
// ✅ Calcul dans la transaction interactive
return prisma.$transaction(async (tx) => {
const maxOrder = await tx.entity.aggregate({
where: { tenantId, scopeId },
_max: { sortOrder: true },
});
const nextOrder = (maxOrder._max.sortOrder ?? 0) + 1;
return tx.entity.create({ data: { ..., sortOrder: nextOrder } });
});
```
- Règle : ne jamais calculer `maxOrder` hors de la transaction qui crée l'entité
- Contexte technique : Prisma / transactions — app-template-resto 21-03-2026
---
<a id="risque-tenantid-sans-fk-relation"></a>
## Champ `tenantId` sans FK ni relation Prisma vers `Tenant`
### Risques
- Un `tenantId TEXT NOT NULL` sans relation Prisma ne génère aucune FK en DB
- L'isolation multi-tenant n'est pas enforced au niveau base de données
### Symptômes
- Migration SQL sans `ALTER TABLE ... ADD CONSTRAINT ... REFERENCES "tenants"`
- Prisma ne génère pas de FK automatiquement sans `@relation` déclarée
### Bonnes pratiques / mitigations
Tout modèle tenant-scoped doit avoir les trois :
1. `tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)` dans le modèle Prisma
2. La relation inverse dans `Tenant` (ex: `menuCategories MenuCategory[]`)
3. La FK correspondante dans la migration SQL
- **Checklist review** : vérifier systématiquement que les nouveaux modèles respectent ce guardrail
- Contexte technique : Prisma / multi-tenant — app-template-resto 17-03-2026
---
<a id="risque-schema-divergence-spec-story"></a>
## Divergence schéma Prisma / spec story (champ déclaré ✅ mais absent)
### Risques
- Une tâche de story cochée ✅ implique un champ (ex: `consumedAt`, `tokenHash`) qui n'existe pas dans `schema.prisma`
- Le code compile ou passe en review sans que le champ soit réellement présent en DB
### Symptômes
- Erreur à l'exécution sur un champ inexistant malgré une story marquée "done"
- `schema.prisma` ne contient pas le champ mentionné dans les tâches
### Bonnes pratiques / mitigations
- Avant de marquer une tâche ✅, croiser avec `schema.prisma` pour confirmer que le champ existe réellement
- Une story peut décrire un champ comme stratégie de conception sans l'avoir intégré — toujours vérifier
- Contexte technique : Prisma / app-template-resto — 16-03-2026
---
<a id="risque-prismaservice-getter-manquant"></a>
## PrismaService — getter explicite manquant sur nouveau modèle
### Risques
- L'ajout d'un modèle dans `schema.prisma` sans son getter dans `PrismaService` casse le typecheck
- Erreur silencieuse si les modules sont peu typés
### Symptômes
- `Property 'forum' does not exist on type 'PrismaService'` à la compilation
- Module fonctionnel sur le `PrismaClient` direct mais cassé via `PrismaService`
### Bonnes pratiques / mitigations
Tout ajout de modèle Prisma = **deux actions** :
1. Ajouter le modèle dans `schema.prisma`
2. Ajouter le getter dans `prisma.service.ts`
```typescript
// apps/api/src/infra/prisma/prisma.service.ts
get forum() {
return this.client.forum;
}
```
- **Checklist review** : à chaque nouvelle migration Prisma, vérifier que `prisma.service.ts` est mis à jour.
- Contexte technique : NestJS / PrismaService encapsulé — app-alexandrie 20-03-2026
---
<a id="risque-prisma-init-module-build"></a>
## Prisma initialisé au chargement de module — casse le build Next.js
### Risques
- Un import global qui initialise Prisma immédiatement peut faire échouer la collecte de pages/routes au build si `DATABASE_URL` n'est pas disponible dans l'environnement de build
### Symptômes
- `PrismaClientInitializationError` ou `Error: Environment variable not found: DATABASE_URL` au `next build`
- L'app tourne en dev mais le build CI échoue
### Bonnes pratiques / mitigations
- Préférer une initialisation lazy-safe : retarder l'accès DB au moment de l'appel métier
- Retourner un proxy qui lève une erreur claire uniquement lors du premier accès réel à la DB
- Ne jamais instancier `new PrismaClient()` au top-level d'un module importé par Next.js
- Contexte technique : Next.js App Router / Prisma — app-template-resto 16-03-2026
---
<a id="risque-jest-clearallmocks-imbrique"></a>
## `jest.clearAllMocks()` dans des `beforeEach` imbriqués avec mocks Prisma
### Risques
- Remise à zéro d'un setup attendu par un scope de test plus profond
- Tests verts ou rouges pour de mauvaises raisons
- Forte difficulté à comprendre l'état réel des mocks
### Symptômes
- Comportement différent selon l'ordre ou le niveau d'imbrication des `describe`
- Mocks Prisma "perdus" entre deux tests
- Corrections locales qui cassent d'autres blocs de tests
### Bonnes pratiques / mitigations
- Centraliser la stratégie de reset des mocks
- Éviter les `clearAllMocks()` concurrents à plusieurs niveaux de nesting
- Préférer un setup explicite et local par scénario quand les mocks Prisma sont structurants
- Contexte technique : Jest / Prisma / tests NestJS — 10-03-2026
---
<a id="risque-cursor-pagination-opaque"></a>
## Cursor de pagination opaque — validation manquante (500 au lieu de 400)
### Risques
- Un cursor base64url+JSON non validé crash en HTTP 500 si malformé ou corrompu
- Exposé à des attaques par input malveillant sur les endpoints paginés publics ou semi-publics
### Symptômes
- `JSON.parse` ou décodage base64 lève une exception non catchée → 500 en prod
- Les logs montrent une stack trace sur un endpoint paginé avec un cursor externe
### Bonnes pratiques / mitigations
```typescript
// ❌ DANGEREUX — crash 500 si cursor corrompu
const decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString());
// ✅ CORRECT — validation avec code d'erreur sémantique
let decoded = null;
if (cursor) {
try {
decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString());
if (!decoded.createdAt || !decoded.id) throw new Error('Champs manquants');
} catch {
throw new BadRequestException({ error: { code: 'INVALID_CURSOR', message: 'Cursor de pagination invalide.' } });
}
}
```
- **Règle** : ajouter un test unitaire "cursor invalide → 400" sur tout endpoint paginé par cursor
- Contexte technique : NestJS / pagination — app-alexandrie 24-03-2026

View File

@@ -0,0 +1,105 @@
# Backend — Risques & vigilance : Redis
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet.
---
<a id="risque-redis-thrash-connexion"></a>
## Redis — thrash de connexion sous charge
### Risques
- Connexions concurrentes multiples si `connect()` est appelé "à la demande" sans lock
- Spam logs + saturation connexions quand Redis est down ou lent
### Symptômes
- N appels simultanés → N tentatives de connexion en parallèle
- Logs "Redis connection failed" en rafale au démarrage ou lors d'un restart Redis
### Bonnes pratiques / mitigations
```typescript
// Pattern single-flight + cooldown + fallback DB best-effort
if (!this.connectPromise) {
this.connectPromise = this.client.connect().finally(() => { this.connectPromise = null; });
}
await this.connectPromise;
// Si échec → nextConnectRetryAtMs = now + 1000 → return false → fallback DB
```
- Contexte technique : Redis / NestJS — 09-03-2026
---
<a id="risque-entitlements-ttl-sla"></a>
## Entitlements — TTL cache supérieur au SLA de propagation
### Risques
- TTL cache > SLA propagation → un webhook raté viole mécaniquement le SLA (accès stale plus long que garanti)
- Utilisateur avec accès périmé ou sans accès dû, pendant toute la durée du TTL résiduel
### Symptômes
- Accès premium encore actif après annulation (ou inversement)
- NFR "propagation ≤ 60s" non respecté en cas de webhook manqué
### Bonnes pratiques / mitigations
- TTL cache ≤ SLA cible (ex : NFR "≤ 60s" → TTL = 60s max)
- Toujours coupler TTL + invalidation explicite via webhook (les deux, pas l'un ou l'autre)
- Contexte technique : Redis / entitlements / NestJS — 09-03-2026
---
<a id="risque-compteurs-inmemory"></a>
## Compteurs in-memory ≠ métriques persistées
### Risques
- Compteurs in-memory remis à zéro au restart (perte de données)
- Non agrégables sur plusieurs instances (données partielles par pod)
### Symptômes
- Métriques qui "repartent de 0" à chaque déploiement
- Dashboards incorrects en environnement multi-instance
### Bonnes pratiques / mitigations
- V1 low-cost : `Redis INCRBY` best-effort par `eventType` → persisté et agrégé multi-instances
- Évolutif vers Prometheus/OTel sans changer l'interface (abstraction dès le départ)
- Contexte technique : Redis / NestJS — 09-03-2026
---
<a id="risque-ttl-redis-heure-locale"></a>
## TTL Redis quota calculé en heure locale (dérive jusqu'à ±12h)
### Risques
- Le reset du quota journalier dérive selon le timezone du serveur, pouvant aller jusqu'à ±12h d'écart par rapport à minuit UTC
### Symptômes
- Quota qui se remet à zéro à des heures inattendues selon l'environnement de déploiement
- Comportement différent en dev local (TZ machine) et en prod (TZ container)
### Bonnes pratiques / mitigations
```typescript
// ✅ CORRECT — UTC midnight garanti
const midnight = new Date(
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1),
);
const ttlMs = midnight.getTime() - now.getTime();
// ❌ RISQUÉ — heure locale du serveur
const endOfDay = new Date();
endOfDay.setHours(23, 59, 59, 999); // dérive selon TZ serveur
```
- Règle : tout `expireAt` ou `TTL` de quota journalier doit utiliser `Date.UTC()` — vérifier systématiquement en review
- Contexte technique : Redis / NestJS — app-alexandrie 20-03-2026

View File

@@ -0,0 +1,116 @@
# Backend — Risques & vigilance : Stripe
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/risques/README.md` pour l'index complet.
---
<a id="risque-stripe-current-period-end"></a>
## Stripe (v17+) : confusion `billing_cycle_anchor` vs `current_period_end`
### Risques
- Stocker une date de fin de période incorrecte en DB (bug silencieux)
- État d'abonnement incohérent (UI, relances, accès premium)
### Symptômes
- `currentPeriodEnd` correspond à une date "bizarre" (souvent proche de la création), ou à un jour du mois
- Des accès premium expirent trop tôt / trop tard
### Bonnes pratiques / mitigations
- Ne jamais interpréter `billing_cycle_anchor` comme une date de fin de période
- Utiliser `subscription.current_period_end` (timestamp) pour la fin de période courante
- Ajouter un test sur un événement webhook/Subscription qui vérifie la date persistée
---
<a id="risque-stripe-list-has-more"></a>
## Stripe `list()` sans gestion de `has_more`
### Risques
- Pagination tronquée silencieusement
- Réconciliation incomplète d'abonnements, achats ou moyens de paiement
- Décisions métier prises sur un jeu de données partiel
### Symptômes
- Comportement correct sur petits comptes mais faux sur comptes plus chargés
- Premiers éléments traités, les suivants ignorés
- Absence de boucle de pagination ou d'auto-pagination
### Bonnes pratiques / mitigations
- Traiter explicitement `has_more`
- Utiliser l'auto-pagination Stripe si adaptée
- Tester au moins un cas avec plusieurs pages de résultats
- Contexte technique : Stripe API — 10-03-2026
---
<a id="risque-trial-payant-concurrence"></a>
## Concurrence entre activation locale et webhook sur transition trial → payant
### Risques
- Double création ou double attachement d'une ressource unique
- Conflit `P2002`
- État local différent de l'état Stripe pendant la transition
### Symptômes
- La transition fonctionne parfois, puis échoue aléatoirement
- Un webhook Stripe et une action applicative écrivent la même mutation métier
- Erreurs d'unicité lors de l'activation payante
### Bonnes pratiques / mitigations
- Définir une seule source autorisée pour chaque transition d'état
- Rendre les écritures idempotentes
- Sérialiser ou réconcilier explicitement les transitions pilotées à la fois par action utilisateur et webhook
- Contexte technique : Stripe / Prisma / trial subscription — 10-03-2026
---
<a id="risque-non-idempotence"></a>
## Non-idempotence sur opérations sensibles
### Risques
- Doubles paiements / doubles créations
- Webhooks rejoués qui cassent l'état
### Symptômes
- Doublons de lignes en DB
- Actions exécutées 2 fois après timeout/retry
- Incidents difficiles à reproduire
### Bonnes pratiques / mitigations
- Idempotency key sur endpoints critiques
- Protection anti-doublon côté DB (contraintes uniques)
- Comportement défini en cas de retry
---
<a id="risque-webhook-200-processing"></a>
## Webhooks entrants — répondre 200 pendant `processing` (event perdu)
### Risques
- Le provider (Stripe, etc.) arrête ses retries après un 2xx, même si le premier worker a échoué
- Event non appliqué mais marqué "traité" → état incohérent silencieux
### Symptômes
- Webhook reçu, 200 retourné, mais l'état en base n'est pas mis à jour
- Aucun retry du provider → impossible à détecter sans monitoring actif
### Bonnes pratiques / mitigations
- Lock DB (`WebhookEvent`) avec machine d'état : `pending``processing``processed` / `failed`
- Si `processing` détecté (concurrent) : attendre brièvement la transition `processed`, sinon répondre **non-2xx** (force retry provider)
- Ne jamais passer à `processed` sans preuve d'un traitement effectif
- Contexte technique : Stripe / NestJS — 09-03-2026

View File

@@ -0,0 +1,16 @@
# Frontend — Patterns validés — Index
Patterns frontend/mobile testés et validés en conditions réelles.
Avant toute proposition frontend, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
---
| Fichier | Domaine | Entrées clés |
|---------|---------|--------------|
| `state.md` | State management, UI states, Zustand, listes paginées | États UI loading/empty/error, séparation server/client state, refresh idempotent, UI admin légère |
| `forms.md` | Formulaires, validation, Server Actions, optimistic UI | Formulaire robuste, toggle optimiste rollback, Server Action retourne entité |
| `navigation.md` | Navigation, routing, Expo Router, intégrations tierces | Navigation réactive post-action async, link-out page locale canonique |
| `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, Jest node env | Tests de styles sans renderer JSX |

View File

@@ -0,0 +1,186 @@
# Frontend — Patterns : Design Tokens
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
---
<a id="pattern-design-tokens-expo-rn"></a>
## Pattern : Design Tokens natifs TypeScript (Expo / React Native)
### Synthèse
- **Objectif** : centraliser les tokens de design sans librairie externe (NativeBase, Tamagui), typés et barrel-exportés.
- **Contexte** : app Expo / React Native avec un système de design à maintenir.
- **Quand l'utiliser** : dès le début d'un projet mobile, avant les premiers composants.
- **Quand l'éviter** : si une librairie UI opinionée est déjà choisie et gère ses propres tokens.
### Analyse
- **Avantages** :
- aucune dépendance externe, zéro configuration magique
- autocomplétion TypeScript exacte via `as const` + types dérivés
- facile à migrer vers un design system plus élaboré ultérieurement
- **Limites / vigilance** :
- les fichiers TTF doivent être présents dans `assets/fonts/` — Google Fonts ne peut pas être téléchargé automatiquement, documenter comme pré-requis dans la story
- ne pas réutiliser les tokens `spacing` pour les dimensions de composants (voir risques)
### Validation
- Validé le : 19-03-2026
- Contexte technique : Expo SDK 52+ / React Native / TypeScript — app-alexandrie story 0.1
### Implémentation (exemple minimal)
```typescript
// apps/mobile/src/theme/colors.ts
export const colors = {
primary: '#2563EB',
error: '#DC2626',
// ...
} as const;
export type ColorToken = keyof typeof colors;
// apps/mobile/src/theme/spacing.ts
export const spacing = { xs: 4, sm: 8, md: 12, base: 16, lg: 24 } as const;
export type SpacingToken = keyof typeof spacing;
// apps/mobile/src/theme/index.ts (barrel export)
export * from './colors';
export * from './spacing';
export * from './typography';
export * from './shadows';
```
### Checklist
- [ ] Tous les tokens `as const` pour inférence exacte
- [ ] Pas de Context React — constantes TypeScript pures
- [ ] Types dérivés (`ColorToken = keyof typeof colors`) pour l'autocomplétion
- [ ] `useFonts` dans `_layout.tsx` avec guard `!fontsLoaded`
- [ ] Fichiers TTF présents dans `assets/fonts/` et documentés dans la story
---
<a id="pattern-token-typography-semantique"></a>
## Pattern : Token typography par usage sémantique (React Native)
### Synthèse
- **Objectif** : éviter les mauvais usages de tokens typography visuellement proches mais sémantiquement distincts.
- **Contexte** : fichier `typography.ts` dans un design system React Native.
- **Quand l'utiliser** : dès que deux tokens partagent la même taille mais un poids différent.
- **Quand l'éviter** : jamais — les tokens typography doivent toujours refléter l'usage, pas l'apparence.
### Analyse
- **Avantages** :
- prévient les "approximations" de tokens en code review
- changement de style d'usage spécifique sans régression globale
- **Limites / vigilance** :
- en review : chercher les usages sans `fontWeight` explicite — c'est souvent le signe que le mauvais token a été choisi
### Validation
- Validé le : 19-03-2026
- Contexte technique : React Native / TypeScript — app-alexandrie story 0.4
### Implémentation
```typescript
// Bon : nommé par usage sémantique
listItemTitle: { fontSize: 12, fontWeight: '600' }, // titre d'un item de liste
caption: { fontSize: 12, fontWeight: '500' }, // info secondaire, hints
// Mauvais : nommé par apparence
mediumText12: { fontSize: 12, fontWeight: '500' }, // ambigu, réutilisé à tort
```
**Règle** : `caption` (Medium) ≠ `listItemTitle` (SemiBold) même si la taille est identique. Ne jamais piocher un token "par approximation".
---
<a id="pattern-export-styles-composant"></a>
## Pattern : Export des styles de composant pour réutilisation partielle (React Native)
### Synthèse
- **Objectif** : partager les dimensions et formes d'un composant UI vers des éléments custom qui en dérivent, sans dupliquer les valeurs.
- **Contexte** : app React Native où des screens construisent des éléments qui doivent être "au gabarit" d'un composant existant.
- **Quand l'utiliser** : bouton custom OAuth, container calqué sur un composant de base, etc.
- **Quand l'éviter** : si l'écart visuel est intentionnel — dans ce cas, une constante locale est plus claire.
### Analyse
- **Avantages** :
- zéro drift silencieux : si les dimensions du composant changent, tous les éléments dérivés suivent
- tests de styles possibles en dehors du composant
- **Limites / vigilance** :
- à n'utiliser que pour des éléments vraiment dérivés, pas comme contournement de design system
### Validation
- Validé le : 19-03-2026
- Contexte technique : React Native / StyleSheet — app-alexandrie story 0.3
### Implémentation
```typescript
// Button.tsx
export const buttonStyles = StyleSheet.create({
base: { borderRadius: 20, height: 57 },
primary: { backgroundColor: colors.primary },
});
export function Button(...) { ... }
// login.tsx — bouton OAuth au gabarit du Button
import { buttonStyles } from '@/components/ui/Button';
<TouchableOpacity style={[buttonStyles.base, styles.facebookButton]} />
```
<a id="pattern-tests-styles-sans-renderer"></a>
## Pattern : Tests de styles React Native sans renderer JSX
### Synthèse
- **Objectif** : tester les tokens et styles de composants React Native dans un environnement Jest `testEnvironment: node` sans renderer JSX.
- **Contexte** : config Jest avec `transform: { '^.+\\.ts$': 'ts-jest' }` — les `.tsx` ne sont pas transformés.
- **Quand l'utiliser** : tokens de thème, logique pure, valeurs de style exportées.
- **Quand l'éviter** : rendu conditionnel (styles dynamiques inline) — nécessite `@testing-library/react-native`.
### Analyse
- **Avantages** :
- teste que le composant utilise les bons tokens, pas seulement que les tokens ont des valeurs
- détecte les régressions de style sans renderer
- rapide, aucune config Jest supplémentaire
- **Limites / vigilance** :
- ne teste pas le style calculé au runtime (style conditionnel dynamique)
### Validation
- Validé le : 19-03-2026
- Contexte technique : React Native / Jest / ts-jest — app-alexandrie story 0.2
### Implémentation
```typescript
// Button.tsx — exporter le StyleSheet avec un nom préfixé
export const buttonStyles = StyleSheet.create({
base: { borderRadius: 20, height: 57 },
primary: { backgroundColor: colors.primary },
});
export function Button(...) { ... }
// ui-components.spec.ts — importer et vérifier les tokens
import { buttonStyles } from './Button';
import { colors } from '@/theme';
it('variante primary utilise colors.primary', () => {
expect(buttonStyles.primary.backgroundColor).toBe(colors.primary);
});
```
### Deux niveaux de tests UI recommandés
1. `.spec.ts` (node) : tokens, valeurs, logique pure
2. `.spec.tsx` (config séparée avec renderer) : rendu visuel, interactions

View File

@@ -0,0 +1,130 @@
# Frontend — Patterns : Forms
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
---
<a id="pattern-formulaire-robuste"></a>
## Pattern : Formulaire robuste avec validation et erreurs explicites
### Synthèse
- **Objectif** : garantir des formulaires fiables, compréhensibles et maintenables.
- **Contexte** : toute interface avec saisie utilisateur et règles métier.
- **Quand l'utiliser** : dès qu'un formulaire dépasse un simple champ isolé.
- **Quand l'éviter** : formulaires ultra-simples sans validation réelle.
### Analyse
- **Avantages** :
- UX claire (l'utilisateur sait quoi corriger)
- Moins d'erreurs silencieuses
- Base saine pour tests et accessibilité
- **Limites / vigilance** :
- Peut sembler verbeux sans discipline
- Risque de duplication si mal factorisé
### Validation
- Validé le : 25-01-2026
- Contexte technique : Front-end agnostique, API HTTP
### Implémentation (exemple minimal)
```txt
- Validation côté client (format, champs requis)
- Validation côté serveur (règles métier)
- Mapping explicite des erreurs serveur → champs UI
- Aucun submit silencieux
```
### Checklist
- [ ] Messages d'erreur compréhensibles et localisés
- [ ] Validation client + serveur cohérente
- [ ] Focus automatique sur le champ en erreur
- [ ] États loading / disabled gérés
- [ ] Tests sur cas valides et invalides
---
<a id="pattern-toggle-optimiste-rollback"></a>
## Pattern : Toggle optimiste avec rollback (React Server Action)
### Synthèse
- **Objectif** : masquer la latence serveur sur un toggle boolean en mettant à jour l'UI immédiatement, avec rollback en cas d'erreur.
- **Contexte** : toggles boolean (visibilité, disponibilité, settings) où la latence doit être masquée.
- **Quand l'utiliser** : toggles sans besoin de re-fetcher l'entité entière après mutation.
- **Quand l'éviter** : mutations qui retournent des données complexes → préférer le pattern "Server Action retournant l'entité".
### Validation
- Validé le : 21-03-2026
- Contexte technique : React / Next.js App Router — app-template-resto
### Implémentation
```tsx
const [optimistic, setOptimistic] = useState(initialValue);
async function handleToggle() {
const prev = optimistic;
setOptimistic(!prev); // update immédiat
try {
await toggleAction(!prev);
router.refresh(); // synchronise le Server Component parent
} catch {
setOptimistic(prev); // rollback si erreur
}
}
```
---
<a id="pattern-server-action-retourne-entite"></a>
## Pattern : Server Action retournant l'entité — élimination de `router.refresh()` sur create/edit
### Synthèse
- **Objectif** : mettre à jour l'état local directement avec les données réelles retournées par le serveur, sans round-trip SSR supplémentaire.
- **Contexte** : liste d'items managée côté client (`useState`) avec création et modification via Server Actions.
- **Quand l'utiliser** : create et edit d'entités dans une liste. Plus performant que toggle optimiste + `router.refresh()`.
- **Quand l'éviter** : simples toggles boolean → le pattern optimiste avec rollback suffit.
### Analyse
- **Avantages vs toggle optimiste + `router.refresh()` :**
- Zéro aller-retour SSR supplémentaire (~500ms2s économisés sur mobile)
- État local garanti cohérent avec la DB (données réelles, pas calculées localement)
- Pas de flash de rechargement
- **Limites / vigilance** :
- `revalidatePath` reste nécessaire pour invalider le cache des pages publiques
### Validation
- Validé le : 22-03-2026
- Contexte technique : React / Next.js App Router — app-template-resto story 3.8
### Implémentation
```typescript
// Repository — retourne l'entité complète
export async function createItem(tenantId: string, data: Input): Promise<ItemRow> {
return prisma.item.create({ data: { tenantId, ...data }, select: { ...fullSelect } });
}
// Action — retourne la donnée au client
export async function createItemAction(formData: FormData): Promise<ItemRow> {
const actor = await requireOwner();
const item = await createItem(actor.tenantId, input);
revalidatePath("/dashboard/..."); // invalider cache pages publiques
return item; // ← clé : retourner l'entité
}
// Client — mise à jour locale sans round-trip SSR
const created = await createItemAction(formData);
setItems((prev) => [...prev, created]); // pas de router.refresh()
```
**Pour les entités avec relations :** utiliser un helper `findItemById(tenantId, id)` appelé après la mutation pour retourner la forme complète avec les relations résolues.

View File

@@ -0,0 +1,168 @@
# Frontend — Patterns : Navigation
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
---
<a id="pattern-navigation-reactive-post-action-async"></a>
## Pattern : Navigation réactive post-action async (React / Expo Router)
### Synthèse
- **Objectif** : déclencher la navigation après une action asynchrone (login, register, submit) de façon idiomatique et sans bypasser la réactivité React.
- **Contexte** : SPA ou app mobile React avec state management (Zustand, Redux, Context) et router déclaratif (React Router, Expo Router, Next.js App Router).
- **Quand l'utiliser** : dès qu'une navigation dépend du résultat d'une action async.
- **Quand l'éviter** : navigations synchrones sans état async impliqué.
### Analyse
- **Avantages** :
- Respecte le cycle de vie React (pas de lecture de state hors cycle)
- Re-render automatique si l'état change entre-temps
- Testable : on peut assert sur l'état, pas sur des effets de bord
- **Limites / vigilance** :
- Ne pas oublier les dépendances du `useEffect` (ESLint react-hooks/exhaustive-deps)
- Gérer le cas "composant démonté" si la navigation peut être annulée
### Validation
- Validé le : 07-03-2026
- Contexte technique : React 18+ / Zustand / Expo Router — pattern applicable sur React Router, Next.js App Router
### Implémentation (exemple minimal)
```typescript
// ❌ Anti-pattern : lecture de state hors cycle React
const handleSubmit = async () => {
await login(email, password);
const { accessToken } = useAuthStore.getState(); // bypasse la réactivité
if (accessToken) router.replace('/(tabs)');
};
// ✅ Pattern correct : useEffect réactif sur le state
const { accessToken, isLoading, error } = useAuthStore();
useEffect(() => {
if (accessToken && !isLoading && !error) {
router.replace('/(tabs)');
}
}, [accessToken, isLoading, error]);
const handleSubmit = async () => {
await login(email, password);
// la navigation se déclenche via useEffect quand le store se met à jour
};
```
### Pour les callbacks OAuth (ref nécessaire)
```typescript
// Quand un callback externe déclenche la navigation
const pendingOAuth = useRef(false);
useEffect(() => {
if (pendingOAuth.current && accessToken) {
pendingOAuth.current = false;
router.replace('/(tabs)');
}
}, [accessToken]);
const handleOAuth = async () => {
pendingOAuth.current = true;
await exchangeWithIdp(token);
};
```
### Checklist
- [ ] Aucun `store.getState()` utilisé pour lire l'état post-action dans un handler
- [ ] `useEffect` avec dépendances explicites (state pertinent + isLoading + error)
- [ ] Cas d'erreur géré (ne pas naviguer si error est défini)
- [ ] `useRef` si le trigger vient d'un callback externe (OAuth, deep link)
- [ ] Convention documentée dans la story foundations / project-context avant les premiers écrans
---
<a id="pattern-link-out-page-locale-canonique"></a>
## Pattern : Intégration tierce en mode link-out — préférer une page locale canonique
### Synthèse
- **Objectif** : éviter les parcours concurrents et centraliser les garde-fous UX quand une fonctionnalité publique dépend d'un service tiers externe.
- **Contexte** : site ou webapp avec CTA publics menant vers un tiers de réservation, paiement, prise de rendez-vous ou formulaire externe.
- **Quand l'utiliser** : dès qu'une fonctionnalité externe dispose d'une page locale dédiée côté produit (`/reservation`, `/booking`, etc.).
- **Quand l'éviter** : si le produit assume volontairement une sortie directe unique vers le tiers, sans page locale intermédiaire ni besoin de contextualisation.
### Analyse
- **Avantages** :
- UX plus cohérente entre home, navigation et pages dédiées
- garde-fous, wording et fallbacks centralisés au même endroit
- facilite l'évolution future vers embed, click-to-load ou variantes de parcours
- **Limites / vigilance** :
- ajoute une étape intermédiaire si la page locale n'apporte aucune valeur
- demande de maintenir l'alignement entre les CTA internes et le parcours canonique
### Validation
- Validé le : 19-03-2026
- Contexte technique : site web public / intégration tierce en mode lien externe
### Implémentation (exemple minimal)
```txt
- faire pointer les CTA internes (home, nav, landing) vers une page locale dédiée
- faire de cette page locale le point canonique vers le service tiers externe
- centraliser sur cette page le contexte utile, les garde-fous et les fallbacks
- éviter les sorties directes concurrentes vers le tiers depuis plusieurs endroits du site
```
### Checklist
- [ ] Un parcours canonique unique est défini
- [ ] Les CTA internes convergent vers la page locale dédiée
- [ ] Les garde-fous et fallbacks sont centralisés
- [ ] Les sorties directes concurrentes vers le tiers sont évitées ou justifiées
---
<a id="pattern-click-to-load-embeds-tiers"></a>
## Pattern : Click-to-load strict pour les embeds tiers (iframe/widget)
### Synthèse
- **Objectif** : ne charger aucun service tiers sans action explicite de l'utilisateur (performance + consentement implicite).
- **Contexte** : site/webapp avec modules de réservation, map, chat ou tout embed iframe à la demande.
- **Quand l'utiliser** : dès qu'un embed tiers est chargé à la demande (pas au premier rendu).
- **Quand l'éviter** : si l'embed est central à la page et doit être visible immédiatement.
### Analyse
- **Avantages** :
- LCP non pollué par des tiers (performance-first)
- Aucun tiers ne reçoit de données utilisateur sans action volontaire (consentement implicite)
- Fallback toujours disponible en cas d'erreur iframe
- **Limites / vigilance** :
- Le fallback (lien externe + `tel:`) doit être actionnable même si l'embed échoue
### Validation
- Validé le : 21-03-2026
- Contexte technique : React / Next.js — app-template-resto
### Implémentation
```tsx
const [loaded, setLoaded] = useState(false);
const [errored, setErrored] = useState(false);
if (errored) return <a href={url}>Ouvrir {label}</a>;
return (
<>
{!loaded && <button onClick={() => setLoaded(true)}>Charger {label}</button>}
{loaded && <iframe src={url} onError={() => setErrored(true)} />}
</>
);
```

View File

@@ -0,0 +1,67 @@
# Frontend — Patterns : Next.js
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
---
<a id="pattern-eslint-flat-config-nextjs"></a>
## Pattern : ESLint flat config avec presets Next.js (`eslint.config.mjs`)
### Synthèse
- **Objectif** : éviter les bugs de compatibilité de l'ancien `.eslintrc` avec Next.js récent.
- **Contexte** : projet Next.js récent utilisant déjà le flat config ESLint.
- **Quand l'utiliser** : nouveau projet Next.js ou migration ESLint.
- **Quand l'éviter** : si le projet doit rester compatible avec des outils legacy ESLint.
### Validation
- Validé le : 16-03-2026
- Contexte technique : Next.js 16+ / ESLint flat config — app-template-resto
### Implémentation
```javascript
// eslint.config.mjs
import nextPlugin from "@next/eslint-plugin-next";
export default [
...nextPlugin.configs["core-web-vitals"],
...nextPlugin.configs["typescript"],
{
rules: {
// overrides ciblés ici
},
},
];
```
---
<a id="pattern-grilles-2-colonnes-mobile-first"></a>
## Pattern : Grilles 2 colonnes FR/EN — mobile-first
### Synthèse
- **Objectif** : afficher les champs FR + EN côte à côte sur desktop, en colonne unique sur mobile.
- **Contexte** : formulaires dashboard avec champs bilingues FR/EN côte à côte.
- **Quand l'utiliser** : tout formulaire avec colonnes parallèles sur un projet mobile-first.
- **Quand l'éviter** : si les champs sont indépendants et n'ont pas de relation visuelle FR/EN.
### Validation
- Validé le : 22-03-2026
- Contexte technique : Tailwind CSS / React — app-template-resto
### Implémentation
```html
<!-- ✅ Mobile-first — colonne unique sur < 640px, 2 colonnes sur ≥ 640px -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<input placeholder="Nom (FR)" />
<input placeholder="Name (EN)" />
</div>
<!-- ❌ À éviter — 2 colonnes trop étroites sur mobile -->
<div class="grid grid-cols-2 gap-4">...</div>
```

View File

@@ -0,0 +1,189 @@
# Frontend — Patterns : State
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
---
<a id="pattern-etats-ui-loading-empty-error"></a>
## Pattern : Gestion explicite des états UI (loading / empty / error)
### Synthèse
- **Objectif** : éviter les interfaces ambiguës ou incohérentes en rendant explicites tous les états possibles d'une vue.
- **Contexte** : SPA ou webapp consommant des données asynchrones (API, backend, cache).
- **Quand l'utiliser** : dès qu'une vue dépend de données externes ou d'un traitement async.
- **Quand l'éviter** : vues purement statiques ou synchrones sans dépendance externe.
### Analyse
- **Avantages** :
- UX plus prévisible et compréhensible
- Debug facilité (état visible = problème identifiable)
- Base saine pour tests et accessibilité
- **Limites / vigilance** :
- Peut sembler verbeux sur des écrans simples
- Nécessite une discipline pour ne pas "court-circuiter" les états
### Validation
- Validé le : 25-01-2026
- Contexte technique : SPA (React / Vue / Svelte agnostique), API HTTP
### Implémentation (exemple minimal)
```txt
if (loading) {
afficher un skeleton ou spinner
} else if (error) {
afficher un message clair + action possible
} else if (data est vide) {
afficher un état empty explicite
} else {
afficher la vue nominale
}
```
### Checklist
- [ ] Aucun écran blanc ou silencieux
- [ ] Message d'erreur compréhensible pour l'utilisateur
- [ ] États testables individuellement
- [ ] Accessibilité respectée (focus, lecture écran)
- [ ] Pas de logique métier cachée dans le rendu
---
<a id="pattern-separation-server-state-client-state"></a>
## Pattern : Séparation claire server state / client state
### Synthèse
- **Objectif** : éviter le mélange des responsabilités entre données serveur et état local UI.
- **Contexte** : SPA ou webapp consommant une API avec interactions utilisateur.
- **Quand l'utiliser** : dès que l'application affiche des données distantes modifiables ou synchronisées.
- **Quand l'éviter** : applications très simples ou purement statiques.
### Analyse
- **Avantages** :
- Logique plus lisible et testable
- Réduction des bugs liés aux états incohérents
- Évolutivité facilitée quand l'app grossit
- **Limites / vigilance** :
- Demande de la rigueur dans le découpage
- Peut sembler abstrait au début pour des petits projets
### Validation
- Validé le : 25-01-2026
- Contexte technique : SPA agnostique (React / Vue / Svelte), API HTTP
### Implémentation (exemple minimal)
```txt
serverState = données venant du backend (fetch, cache, sync)
clientState = état local UI (filtres, onglets, modales, formulaires)
Ne jamais :
- stocker du state UI dans le cache serveur
- dériver la logique UI directement des réponses API sans adaptation
```
### Checklist
- [ ] Les données serveur peuvent être invalidées / rechargées
- [ ] L'état UI est local et réinitialisable
- [ ] Les responsabilités sont lisibles dans le code
- [ ] Les tests peuvent cibler chaque type d'état
- [ ] Pas de dépendance implicite entre UI et API
---
<a id="pattern-refresh-idempotent-liste-paginee"></a>
## Pattern : Refresh idempotent sur store de liste paginée
### Synthèse
- **Objectif** : garantir qu'un pull-to-refresh recharge une liste paginée sans doublons, sans courses réseau et sans état intermédiaire incohérent.
- **Contexte** : app mobile ou SPA avec store de domaine (ex. Zustand) et pagination incrémentale.
- **Quand l'utiliser** : dès qu'une même liste supporte à la fois `loadMore` et `refresh`.
- **Quand l'éviter** : listes purement statiques ou données entièrement remplacées sans pagination.
### Analyse
- **Avantages** :
- évite les doublons lors des refresh concurrents
- garde une transition atomique entre ancien et nouvel état
- rend le comportement async testable côté store
- **Limites / vigilance** :
- impose une discipline claire entre `refresh` et `loadMore`
- demande une clé d'identité stable pour dédupliquer les items
### Validation
- Validé le : 10-03-2026
- Contexte technique : React Native / Expo / Zustand / listes paginées
### Implémentation (exemple minimal)
```txt
- conserver une promesse de refresh partagée tant qu'un refresh est en vol
- refuser ou réutiliser tout refresh concurrent au lieu d'en lancer un second
- remplacer atomiquement la liste à la fin du refresh
- dédupliquer les items par identifiant au merge des pages suivantes
- empêcher `loadMore` de fusionner sur un snapshot devenu obsolète
```
### Checklist
- [ ] Une seule promesse de refresh en vol à la fois
- [ ] `refresh` et `loadMore` ont des garde-fous explicites
- [ ] La liste est remplacée atomiquement après refresh
- [ ] Les pages suivantes sont dédupliquées par identifiant stable
- [ ] Tests sur refresh concurrent + refresh suivi de pagination
---
<a id="pattern-ui-admin-legere-domaine-existant"></a>
## Pattern : UI admin légère sur domaine existant
### Synthèse
- **Objectif** : ajouter une capacité interne simple sans ouvrir trop tôt un back-office séparé ni dupliquer la logique métier.
- **Contexte** : app mobile ou SPA avec un domaine métier déjà structuré et quelques actions internes ponctuelles.
- **Quand l'utiliser** : publication, activation, modération légère, bascule de statut, action opérateur simple.
- **Quand l'éviter** : permissions complexes, workflows multiples, audit riche ou volume d'actions qui justifie un vrai espace d'administration.
### Analyse
- **Avantages** :
- réutilise le service et le store métier existants
- limite le coût de structure pour une capacité admin mince
- garde les mutations testables et lisibles
- **Limites / vigilance** :
- ne pas laisser cette approche dériver vers un pseudo back-office implicite
- le refresh après mutation doit être explicite sur les vues impactées
### Validation
- Validé le : 10-03-2026
- Contexte technique : React Native / Expo Router / store de domaine
### Implémentation (exemple minimal)
```txt
- ajouter une route dédiée minimale pour l'action interne
- réutiliser le service/store métier existant au lieu de créer une couche parallèle
- afficher le statut courant avant action
- bloquer les actions concurrentes avec un flag explicite (`isUpdating*`)
- déclencher un refresh explicite des vues impactées après succès
- éviter les mutations en fire-and-forget
```
### Checklist
- [ ] Route dédiée minimale, pas de mini back-office générique
- [ ] Réutilisation du domaine métier existant
- [ ] Garde-fou explicite contre les doubles actions
- [ ] Refresh explicite après mutation réussie
- [ ] Tests sur succès, erreur et action concurrente

View File

@@ -0,0 +1,53 @@
# Frontend — Patterns : Tests
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet.
---
<a id="pattern-tests-styles-sans-renderer"></a>
## Pattern : Tests de styles React Native sans renderer JSX
### Synthèse
- **Objectif** : tester les tokens et styles de composants React Native dans un environnement Jest `testEnvironment: node` sans renderer JSX.
- **Contexte** : config Jest avec `transform: { '^.+\\.ts$': 'ts-jest' }` — les `.tsx` ne sont pas transformés.
- **Quand l'utiliser** : tokens de thème, logique pure, valeurs de style exportées.
- **Quand l'éviter** : rendu conditionnel (styles dynamiques inline) — nécessite `@testing-library/react-native`.
### Analyse
- **Avantages** :
- teste que le composant utilise les bons tokens, pas seulement que les tokens ont des valeurs
- détecte les régressions de style sans renderer
- rapide, aucune config Jest supplémentaire
- **Limites / vigilance** :
- ne teste pas le style calculé au runtime (style conditionnel dynamique)
### Validation
- Validé le : 19-03-2026
- Contexte technique : React Native / Jest / ts-jest — app-alexandrie story 0.2
### Implémentation
```typescript
// Button.tsx — exporter le StyleSheet avec un nom préfixé
export const buttonStyles = StyleSheet.create({
base: { borderRadius: 20, height: 57 },
primary: { backgroundColor: colors.primary },
});
export function Button(...) { ... }
// ui-components.spec.ts — importer et vérifier les tokens
import { buttonStyles } from './Button';
import { colors } from '@/theme';
it('variante primary utilise colors.primary', () => {
expect(buttonStyles.primary.backgroundColor).toBe(colors.primary);
});
```
### Deux niveaux de tests UI recommandés
1. `.spec.ts` (node) : tokens, valeurs, logique pure
2. `.spec.tsx` (config séparée avec renderer) : rendu visuel, interactions

View File

@@ -0,0 +1,18 @@
# Frontend — Risques & vigilance — Index
Risques frontend/mobile susceptibles de provoquer des bugs subtils, comportements inattendus, dette technique ou régressions UX.
Avant toute proposition frontend, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
---
| 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, deep link, useEffect fetch, contexte store | Store vide deep link/reload, guard incomplet états terminaux, collection sans clé contexte |
| `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 | Config node bloque .tsx, faux test négatif |
| `performance.md` | Re-renders, memoization, useCallback | Sur-renders bundle non maîtrisé, useCallback inutile inline |
| `general.md` | Accessibilité, regex, patterns transversaux | Accessibilité oubliée a11y, regex globale singleton lastIndex |

View File

@@ -0,0 +1,114 @@
# Frontend — Risques & vigilance : Auth
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet.
---
<a id="risque-auth-cote-client"></a>
## Auth côté client (mauvaise séparation des responsabilités)
### Risques
- Le front "décide" des permissions au lieu d'appliquer un contrat backend
- Affichage d'actions interdites / fuite d'informations dans l'UI
- Tokens stockés de façon dangereuse (XSS)
### Symptômes
- Différences entre "ce que l'UI permet" et "ce que l'API accepte"
- Bugs "ça marche chez moi" selon sessions/rôles
- Incohérences sur refresh / multi-tab
### Bonnes pratiques / mitigations
- Le backend reste source de vérité (authz)
- Cacher l'UI ≠ sécuriser : toujours sécuriser côté API
- Stockage tokens : privilégier cookies httpOnly si modèle adapté
- Gérer proprement expiration/refresh + révocation
### Contexte technique
- Observé : (à compléter)
- Stack : (à préciser)
---
<a id="risque-loading-infini-ecran-gated"></a>
## Loading infini sur écran gated par droits distants
### Risques
- Un écran protégé reste bloqué dans un faux `loading` après une erreur de chargement des entitlements
- Un effet relance automatiquement la récupération en boucle sans action utilisateur
- L'utilisateur ne voit ni état d'erreur ni issue de sortie claire
### Symptômes
- Spinner infini sur un écran soumis à permissions distantes
- `entitlements` ou autorisations laissés à `null` après erreur
- `useEffect` ou logique d'entrée qui retrigger le fetch à chaque rendu
### Bonnes pratiques / mitigations
- Distinguer explicitement `loading`, `error`, `ready`
- Ne pas réutiliser `null` comme état ambigu "pas encore chargé" et "chargement en erreur"
- Bloquer les retries automatiques en boucle après erreur
- Réautoriser un retry seulement via action utilisateur explicite ou nouvelle condition d'entrée
- Contexte technique : React Native / Expo / store d'entitlements — 10-03-2026
---
<a id="risque-oauth-handler-vide"></a>
## Bouton OAuth présent mais handler vide après refacto UI
### Risques
- L'OAuth est silencieusement cassé sur le nouvel écran — zéro erreur au démarrage, zéro crash
- L'AC "toutes les fonctionnalités préservées" peut être coché alors que le bouton est mort
### Symptômes
- `<Button title="Google" onPress={() => {}} />` — handler vide après copie depuis un ancien écran
- OAuth fonctionnel sur l'écran précédent (`welcome.tsx`) mais absent sur le nouvel écran refactorisé
### Bonnes pratiques / mitigations
- Toute refacto UI qui introduit un bouton OAuth doit brancher le hook existant (`useGoogleAuth(onSuccess)`)
- Si la story exclut explicitement la fonctionnalité : soit le bouton n'apparaît pas, soit `disabled` avec un label explicite ("bientôt disponible")
- **Checklist review** : chercher `onPress={() => {}}` sur tous les boutons OAuth dans les écrans refactorisés
- Contexte technique : Expo Router / React Native — app-alexandrie story 0.3, 19-03-2026
---
<a id="risque-guard-role-return-conditionnel"></a>
## Guard de rôle via return conditionnel dans le render (flash UX)
### Risques
- `if (user?.role !== 'ADMIN') return <AccessDenied />` directement dans le corps du composant : pendant le chargement du store auth, `user` est `null`, ce qui déclenche un affichage momentané de l'écran "Accès refusé" avant le re-render
- UX instable : flash visible, potentiellement suivi d'une boucle de re-render
### Symptômes
- L'écran "Accès refusé" clignote brièvement au montage avant d'afficher le bon contenu
- Bug reproductible uniquement au chargement initial ou après un reload
### Bonnes pratiques / mitigations
```typescript
// ❌ Anti-pattern — flash si user === null au montage
if (user?.role !== 'ADMIN') return <AccessDenied />;
// ✅ Pattern correct — useEffect + rendu vide pendant chargement
useEffect(() => {
if (user !== null && user.role !== 'ADMIN') {
router.replace('/(tabs)');
}
}, [user, router]);
if (user === null || user.role !== 'ADMIN') return <View />;
```
- **Règle** : tout guard de rôle dans un composant React Native doit utiliser `useEffect` + redirect + rendu vide, jamais un return conditionnel direct
- Contexte technique : React Native / Expo Router / Zustand auth — app-alexandrie 24-03-2026

View File

@@ -0,0 +1,100 @@
# Frontend — Risques & vigilance : Design Tokens
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet.
---
<a id="risque-double-systeme-espacement"></a>
## Double système d'espacement dans un monorepo Expo
### Risques
- Deux échelles d'espacement coexistent avec des noms différents pour des valeurs identiques (`Spacing.three = 16` vs `spacing.base = 16`)
- L'audit "zéro hardcode" ne détecte pas l'inconsistance car les deux sont des constantes nommées
- Les deux échelles peuvent diverger silencieusement
### Symptômes
- `import { Spacing } from '@/constants/theme'` coexiste avec `import { spacing } from '@/theme'`
- Certains screens refactorisés utilisent l'ancien système sans que personne ne le détecte
### Bonnes pratiques / mitigations
- Dès la création de `src/theme/spacing.ts`, supprimer ou vider `constants/theme.ts` (sauf constantes vraiment spécifiques : `MaxContentWidth`, `BottomTabInset`)
- Faire un `grep from '@/constants/theme'` à chaque story pour détecter les usages résiduels
- **Cause racine** : le template Expo génère `constants/theme.ts` avec `Spacing = { one, two, three... }` — à purger explicitement lors de la story design tokens
- Contexte technique : Expo / React Native — app-alexandrie story 0.5, 19-03-2026
---
<a id="risque-dimensions-image-via-spacing"></a>
## Dimensions d'image via tokens `spacing` (React Native)
### Risques
- Si `spacing.huge` change pour une raison d'espacement, la taille de l'image change silencieusement
- Régression visuelle sans que personne ne réalise l'impact — les deux changements semblent indépendants
### Symptômes
- `width: spacing.huge, height: spacing.huge` pour une image dont la taille est fixée par la spec Figma
### Bonnes pratiques / mitigations
```typescript
// Correct : constante locale ou token dédié
const THUMBNAIL_SIZE = 48; // Figma spec node 1-16147
// OU token dans un fichier sizes.ts dédié si la valeur est partagée
export const sizes = { thumbnail: 48, avatar: 40 } as const;
```
**Règle** : `spacing` = espacement entre éléments. `sizes` ou constantes locales = dimensions de composants.
- Contexte technique : React Native / design tokens — app-alexandrie story 0.4, 19-03-2026
---
<a id="risque-inline-styles-dashboard"></a>
## Inline styles dans les composants dashboard
### Risques
- Contourne le système Tailwind + tokens CSS
- Crée des incohérences visuelles non détectées par le linter
### Symptômes
- `style={{ color: '#123456', marginTop: 8 }}` dans un composant dashboard
### Bonnes pratiques / mitigations
- Bloquer en code review systématiquement tout `style={{...}}` dans les composants dashboard
- Exception acceptable uniquement : animations CSS dynamiques (valeurs calculées au runtime)
- Contexte technique : React / Tailwind — app-template-resto 22-03-2026
---
<a id="risque-tailwind-classes-invalides"></a>
## Classes Tailwind invalides courantes (bugs silencieux)
### Risques
- Classes Tailwind invalides sont silencieusement ignorées — aucun warning, comportement visuellement cassé
### Symptômes
- Item masqué affiché à pleine opacité (`opacity-55` → invalide)
- Largeur incorrecte (`w-35` → invalide)
### Bonnes pratiques / mitigations
Erreurs courantes :
- `opacity-55` → invalide. Scale : 0/5/10/20/25/30/40/50/60/70/75/80/90/95/100 → utiliser `opacity-50` ou `opacity-60`
- `w-35` → invalide. Scale saute de `w-32` à `w-36` → utiliser `w-36`
- `box-border` → redondant. Tailwind Preflight applique déjà `box-sizing: border-box` globalement
- Toujours vérifier les classes custom/non-standard avec l'extension Tailwind IntelliSense
- Contexte technique : Tailwind CSS — app-template-resto 22-03-2026

View File

@@ -0,0 +1,63 @@
# Frontend — Risques & vigilance : Général
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet.
---
<a id="risque-accessibilite-oubliee"></a>
## Accessibilité oubliée (a11y)
### Risques
- App inutilisable au clavier/lecteur d'écran
- Régressions silencieuses sur focus/labels
### Symptômes
- Modales impossibles à fermer au clavier
- Inputs sans labels/erreurs non annoncées
- Focus "perdu"
### Bonnes pratiques / mitigations
- Checklist a11y minimale sur chaque écran clé
- Gestion de focus (modales, erreurs formulaire)
- Labels/aria cohérents + tests simples
---
<a id="risque-regex-globale-singleton-lastindex"></a>
## Regex globale `/g` en singleton — bug `lastIndex` stateful
### Risques
- Une regex avec flag `/g` ou `/y` définie comme constante au niveau module maintient un état `lastIndex` entre les appels
- `String.prototype.replace()` réinitialise `lastIndex`, mais `.test()` ou `.exec()` ne le font pas → bug stateful difficile à détecter, souvent introduit par un refactor ultérieur
### Symptômes
- `.test(str)` retourne alternativement `true` / `false` sur la même chaîne selon l'ordre d'appel
- Bug non reproductible en isolation, uniquement en séquence d'appels
### Bonnes pratiques / mitigations
```typescript
// ❌ RISQUÉ — regex globale partagée entre tous les appels
const LINK_PATTERN = /https?:\/\/\S+/gi;
function processLinks(content: string) {
return content.replace(LINK_PATTERN, ...); // OK today
// Mais si quelqu'un ajoute LINK_PATTERN.test(x) ailleurs → bug lastIndex
}
// ✅ SÛR — nouvelle instance à chaque appel, aucun état partagé
function makeLinkPattern(): RegExp {
return /https?:\/\/\S+/gi;
}
function processLinks(content: string) {
return content.replace(makeLinkPattern(), ...);
}
```
- **Règle** : les regex avec flag `/g` ou `/y` utilisées pour transformation de strings → toujours créer via une factory, jamais en singleton de module
- Contexte technique : TypeScript / React Native — app-alexandrie 24-03-2026

View File

@@ -0,0 +1,83 @@
# Frontend — Risques & vigilance : Navigation
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet.
---
<a id="risque-store-vide-deep-link"></a>
## Écran détail Expo Router — store vide en deep link / reload
### Risques
- L'écran détail (`[slug].tsx`) lit ses données depuis un store Zustand peuplé par l'écran liste
- En deep link, kill + reopen ou navigation OS back, le store est vide → "introuvable" affiché à tort
### Symptômes
- Écran détail vide ou erreur "non trouvé" sur accès direct (pas via la liste)
- Fonctionne normalement en navigation standard mais échoue sur reload
### Bonnes pratiques / mitigations
```typescript
// useEffect de secours dans l'écran détail
useEffect(() => {
if (!accessToken) return;
if (items.length > 0 || isLoading || errorState) return;
void fetchItems(accessToken);
}, [accessToken, items.length, isLoading, errorState, fetchItems]);
```
- Ne pas afficher "introuvable" avant d'avoir vérifié que le store a bien été peuplé
- Contexte technique : Expo Router / Zustand — app-alexandrie story 4.1, 20-03-2026
---
<a id="risque-useeffect-guard-incomplet"></a>
## `useEffect` fetch — guard incomplet sur les états terminaux
### Risques
- Si l'état "zéro résultat intentionnel" (ex : `paywallRequired`) n'est pas dans les conditions de court-circuit, le fetch est re-déclenché à chaque re-render ou focus
- Boucle de fetch infini sur un état métier normal
### Symptômes
- `forums.length === 0` et `isLoading === false` → le guard ne court-circuite pas → fetch re-déclenché en boucle
- Visible en focus sur l'écran depuis un autre onglet
### Bonnes pratiques / mitigations
```typescript
// ❌ Pattern à risque — re-fetch si paywallRequired (forums vide + isLoading false)
if (forums.length > 0 || isLoading) return;
// ✅ Pattern correct — court-circuit sur l'état terminal
if (forums.length > 0 || isLoading || paywallRequired) return;
```
**Règle** : les états "zéro résultat intentionnel" (liste vide + flag métier) doivent être traités comme "données présentes" dans le guard de fetch.
- Contexte technique : React Native / Zustand / Expo Router — app-alexandrie story 4.1, 20-03-2026
---
<a id="risque-zustand-collection-sans-cle-contexte"></a>
## Store Zustand : collections sans clé de contexte (navigation inter-contexte)
### Risques
- Un store qui stocke des collections dépendant d'un paramètre de navigation (forumSlug, threadId...) sans stocker ce paramètre affiche des données périmées lors d'une navigation inter-contexte
### Symptômes
- Naviguer du forum A vers le forum B affiche encore les catégories/threads du forum A
- Guard `if (items.length > 0) return` empêche le rechargement lors d'un changement de contexte
### Bonnes pratiques / mitigations
- Stocker la clé de contexte avec les données : `categoriesForumSlug: string | null`
- Invalider si `categoriesForumSlug !== currentForumSlug` avant de retourner depuis le cache
- Ou supprimer le guard et dépendre uniquement du changement de paramètre dans le `useEffect`
- Contexte technique : React Native / Zustand / Expo Router — app-alexandrie 23-03-2026

View File

@@ -0,0 +1,378 @@
# Frontend — Risques & vigilance : Next.js
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet.
---
<a id="risque-usesearchparams-sans-suspense"></a>
## `useSearchParams()` sans `Suspense` casse le build Next.js App Router
### Risques
- Un composant client utilisant `useSearchParams()` peut provoquer un échec de prerender/build s'il est rendu sans boundary `Suspense` depuis la page/layout serveur
### Symptômes
- `Error: useSearchParams() should be wrapped in a suspense boundary` au `next build`
- Fonctionne en dev mais échoue à la CI/CD
### Bonnes pratiques / mitigations
- Isoler le composant client qui utilise `useSearchParams()` et le rendre sous `<Suspense fallback={...}>` au niveau de la page
- Ne jamais appeler `useSearchParams()` directement dans un composant rendu sans `Suspense` depuis un Server Component
- Contexte technique : Next.js App Router récent / Turbopack — app-template-resto 16-03-2026
---
<a id="risque-type-viewdata-duplique"></a>
## Type `ViewData` dupliqué entre couche serveur et composant UI (Next.js)
### Risques
- TypeScript accepte deux structures identiques par structural typing — si le type source évolue, la couche UI reste désynchronisée sans erreur de compilation tant que les formes correspondent
### Symptômes
- Deux définitions du même type dans `src/server/` et `src/app/`
- Champ ajouté côté serveur mais absent dans le composant UI sans warning
### Bonnes pratiques / mitigations
```typescript
// ✅ La couche UI importe et re-exporte
export type { PublicHomeViewData } from "@/server/public/getPublicHomeData";
// ❌ À éviter — redéfinition locale
export type PublicHomeViewData = { tenantName: string; ... };
```
- Règle : le type appartient à la couche qui le produit. La couche UI importe uniquement.
- Contexte technique : Next.js App Router / TypeScript — app-template-resto 16-03-2026
---
<a id="risque-composant-react-fichier-ts"></a>
## Composant React dans un fichier `.ts` — `React.createElement` workaround
### Risques
- Code illisible vs JSX natif
- Fausse impression que le fichier est "sans JSX" — peut tromper les outils de linting et les reviewers
- Empêche l'utilisation de la syntaxe JSX si on doit ajouter des enfants complexes
### Symptômes
- `React.createElement(...)` dans un fichier `.ts`
### Bonnes pratiques / mitigations
- Tout fichier exportant une fonction retournant un `ReactElement` ou utilisant React doit avoir l'extension `.tsx`
- Sans exception
- Contexte technique : TypeScript / React — app-template-resto 16-03-2026
---
<a id="risque-double-validation-segment-app-router"></a>
## Double validation de segment dynamique App Router (layout + page)
### Risques
- Si le layout fait `notFound()` sur un segment invalide ET que la page répète la même condition, les deux deviennent désynchronisés silencieusement lors d'une modification
### Symptômes
- Même condition de validation dans `layout.tsx` et `page.tsx` d'un même segment
- Modification du layout n'est pas reportée dans la page (comportement divergent)
### Bonnes pratiques / mitigations
- Si le layout garde, la page consomme — une seule responsabilité par couche
- La page doit faire confiance à son layout parent
- **Règle** : un seul composant est responsable de la garde sur un segment dynamique
- Contexte technique : Next.js App Router — app-template-resto 17-03-2026
---
<a id="risque-window-location-reload-nextjs"></a>
## Next.js App Router : `window.location.reload()` au lieu de `router.refresh()`
### Risques
- Full reload = perd l'état React, navigation complète, plus lent
- `router.refresh()` est l'outil idoine : retrigger le fetch des Server Components sans détruire l'état client
### Symptômes
- `window.location.reload()` après un Server Action dans un Client Component
- Flash de rechargement visible, perte de l'état local (scroll, focus, état de formulaire)
### Bonnes pratiques / mitigations
```tsx
// ❌ Anti-pattern — full reload
await createCategoryAction(formData);
window.location.reload();
// ✅ Pattern correct — RSC diff, préserve l'état client
const router = useRouter();
await createCategoryAction(formData);
router.refresh();
```
- `router.refresh()` refetch uniquement les Server Components affectés (via `revalidatePath`) et applique un diff. L'état des Client Components est préservé.
- Contexte technique : Next.js App Router — app-template-resto 21-03-2026
---
<a id="risque-consent-state-false-ambigu"></a>
## Consent state : `false` ambigu entre "pas de décision" et "refus explicite"
### Risques
- Sans champ `decided`, `analytics: false` peut signifier "première visite" ou "refus explicite" — indistinguables
- Le banner de consentement réapparaît à chaque visite après un refus, violant l'AC de persistance du choix
### Symptômes
- Banner qui réapparaît après rechargement malgré un refus explicite
### Bonnes pratiques / mitigations
```typescript
type ConsentState = {
analytics: boolean;
decided: boolean; // true = l'utilisateur a fait un choix (cookie présent)
};
const DEFAULT: ConsentState = { analytics: false, decided: false };
// À la lecture du cookie :
if (!cookieValue) return DEFAULT; // decided=false (première visite)
return { analytics: parsed.analytics, decided: true };
```
- L'état initial du banner doit être `!decided`, pas `!analytics`
- Contexte technique : Next.js / cookies — app-template-resto 21-03-2026
---
<a id="risque-script-inline-interpolation-directe"></a>
## Script inline : interpolation directe au lieu de `JSON.stringify`
### Risques
- Injection XSS potentielle via une valeur de configuration interpolée directement dans un `<Script>` inline
- La regex de validation en amont peut évoluer et laisser passer des valeurs dangereuses
### Symptômes
- `` {`gtag('config', '${measurementId}');`} `` — interpolation directe sans échappement
### Bonnes pratiques / mitigations
```tsx
// ❌ Anti-pattern — interpolation directe
{`gtag('config', '${measurementId}');`}
// ✅ Pattern correct — JSON.stringify garantit l'échappement
{`gtag('config', ${JSON.stringify(measurementId)});`}
```
- S'applique aussi aux `dangerouslySetInnerHTML` et aux attributs `data-*` injectés en JS
- Contexte technique : Next.js / `<Script>` — app-template-resto 21-03-2026
---
<a id="risque-usetransition-snapshot-apres-setstate"></a>
## `useTransition` + optimistic update : snapshot capturé après `setState`
### Risques
- Stale closure classique : le snapshot est capturé après `setState`, donc `categories` peut déjà référencer la nouvelle liste au moment du rollback
### Symptômes
- Rollback optimiste qui ne restaure pas l'ancienne valeur
- Après une erreur serveur, l'état reste sur la nouvelle liste au lieu de revenir à l'état précédent
### Bonnes pratiques / mitigations
```tsx
// ❌ Anti-pattern — snapshot capturé après setState
const newList = [...categories];
setCategories(newList);
startTransition(async () => {
try { await action(); }
catch { setCategories(categories); } // peut être newList
});
// ✅ Pattern correct — snapshot AVANT toute mutation d'état
const snapshot = categories; // capturer AVANT setCategories
setCategories(newList);
startTransition(async () => {
try { await action(); }
catch { setCategories(snapshot); } // rollback garanti
});
```
- **Règle** : toujours assigner le snapshot dans un `const` **avant** le premier `setState`
- Contexte technique : React / Next.js App Router — app-template-resto 21-03-2026
---
<a id="risque-window-confirm-react"></a>
## `window.confirm()` dans une app React/Next.js
### Risques
- Bloque le thread principal
- Ne fonctionne pas en SSR
- Non stylable, UX mobile mauvaise
### Symptômes
- `if (!confirm("Supprimer ?")) return;` dans un Client Component
### Bonnes pratiques / mitigations
```tsx
// ❌ Anti-pattern
if (!confirm("Supprimer ?")) return;
// ✅ Pattern correct — confirmation inline via état React
const [deletingId, setDeletingId] = useState<string | null>(null);
{deletingId === item.id && (
<div>
<span>Supprimer « {item.label} » ?</span>
<button onClick={() => { setDeletingId(null); doDelete(item.id); }}>Confirmer</button>
<button onClick={() => setDeletingId(null)}>Annuler</button>
</div>
)}
```
- S'applique aussi à `window.alert()` et `window.prompt()`
- Contexte technique : React / Next.js — app-template-resto 21-03-2026
---
<a id="risque-import-type-server-composant-client"></a>
## `import type` depuis `src/server/**` dans un composant client
### Risques
- Violation de boundary même si l'import est type-only (effacé à la compilation)
- Ouvre la porte à des imports runtime si le code est refactoré rapidement
- La règle ESLint `no-restricted-imports` doit couvrir les `import type` aussi
### Symptômes
- `import type { Foo } from "@/server/..."` dans un fichier `"use client"`
- Passe en review car le compilateur ne bloque pas les type-only imports
### Bonnes pratiques / mitigations
- Types partagés entre server et client doivent vivre dans `src/types/` ou `src/lib/`
- Configurer `no-restricted-imports` avec `allowTypeImports: false` pour les paths serveur
- Contexte technique : Next.js App Router / TypeScript — app-template-resto 22-03-2026
---
<a id="risque-img-natif-nextjs"></a>
## Next.js : `<img>` natif interdit dans les composants
### Risques
- Warning ESLint `@next/next/no-img-element` → avec `--max-warnings=0` : erreur CI
- Pas de lazy loading, pas d'optimisation WebP, risque de layout shift (CLS)
### Symptômes
- `<img src="..." />` dans un composant Next.js
### Bonnes pratiques / mitigations
- Toujours utiliser `<Image>` de `next/image` à la place
- Exception acceptable : composants de test ou storybook uniquement
- Contexte technique : Next.js / ESLint — app-template-resto 22-03-2026
---
<a id="risque-usetransition-global-liste-items"></a>
## `useTransition` global pour des listes d'items interactifs
### Risques
- `isPending` global désactive **tous** les boutons de tous les items pendant qu'une opération est en cours sur un seul item
- Sur mobile : UX bloquée, impossible d'agir pendant qu'une autre opération tourne
### Symptômes
- Clic sur "Masquer" pour l'item A → boutons des items B et C grisés
### Bonnes pratiques / mitigations
```tsx
// ❌ Avant — bloque tout
const [isPending, startTransition] = useTransition();
// render : disabled={isPending}
// ✅ Après — per-item
const [pendingId, setPendingId] = useState<string | null>(null);
function handleToggle(id: string) {
setPendingId(id);
(async () => {
try { await toggleAction(id); }
catch (err) { handleError(err); }
finally { setPendingId(null); }
})();
}
// render : disabled={pendingId === item.id}
```
**Règles :**
- `pendingId === item.id` pour les boutons d'item (désactive uniquement l'item en cours)
- `pendingId !== null` pour les boutons globaux (ex: "Ajouter")
- `finally` garantit la réinitialisation même en cas d'erreur
- Contexte technique : React / Next.js — app-template-resto 22-03-2026
---
<a id="risque-formulaire-defaultvalue-sans-key"></a>
## Formulaire React avec `defaultValue` sans `key` prop
### Risques
- `defaultValue`, `defaultChecked`, `defaultSelected` ne s'appliquent qu'au montage
- Si le composant est réutilisé (même nœud DOM, nouvelle prop) sans être démonté, les valeurs ne se mettent pas à jour
### Symptômes
- L'utilisateur édite l'entité A, clique sur "Modifier" pour l'entité B → le formulaire affiche encore les données de A
### Bonnes pratiques / mitigations
```tsx
// Fix obligatoire : key unique basée sur l'ID de l'entité éditée
<EntityForm
key={formState.mode === "edit" ? formState.entity.id : `create-${formState.contextId}`}
...
/>
```
- **Règle** : tout formulaire d'édition réutilisé pour plusieurs entités doit avoir une `key` distincte par entité
- Contexte technique : React / Next.js — app-template-resto 21-03-2026

View File

@@ -0,0 +1,57 @@
# Frontend — Risques & vigilance : Performance
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet.
---
<a id="risque-performances-sur-renders"></a>
## Performances : sur-renders + bundle non maîtrisé
### Risques
- App lente sur mobile
- Bundle qui grossit sans contrôle
- Chargements inutiles (images, libs)
### Symptômes
- Input lag
- Temps de chargement qui dérive à chaque feature
- Requêtes réseaux inutiles
### Bonnes pratiques / mitigations
- Lazy loading routes/features
- Mesurer (au minimum) : temps de chargement + re-renders critiques
- Politique images (formats, tailles, lazy)
- Audit régulier des dépendances
---
<a id="risque-usecallback-inutile-inline"></a>
## `useCallback` inutile quand le callback est wrappé en inline au render
### Risques
- Le handler stable est re-wrappé dans une arrow inline lors du passage en prop → nouvelle référence à chaque render → `React.memo` ne peut pas éviter le re-render
### Symptômes
```tsx
const handleToggle = useCallback((id: string) => { ... }, []); // stable ✓
// Mais au render :
<ItemCard onToggle={() => handleToggle(item.id)} />
// ↑ nouvelle closure à chaque render → memo inutile
```
### Bonnes pratiques / mitigations
- `useCallback` n'a de valeur que si le callback est passé **directement** en prop, sans re-wrapping
- Si la signature doit capturer des variables de boucle, deux options :
1. Passer les données en props et laisser l'enfant appeler le handler avec ses propres props
2. Accepter que `memo` ne soit pas protégé et supprimer le `useCallback` inutile
- Ne pas laisser un `useCallback` "pour faire bien" si son effet réel est nul
- Contexte technique : React — app-template-resto 22-03-2026

View File

@@ -0,0 +1,280 @@
# Frontend — Risques & vigilance : State
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet.
---
<a id="risque-erreurs-silencieuses"></a>
## Erreurs silencieuses / écrans blancs
### Risques
- Exceptions non gérées → app inutilisable
- États async mal gérés → UI incohérente (loading infini, vide incompris)
### Symptômes
- Écran blanc après une action
- Toast générique "Une erreur est survenue" sans corrélation
- Pas de moyen de reproduire / diagnostiquer
### Bonnes pratiques / mitigations
- Pattern "états UI explicites" (loading/empty/error)
- Boundary d'erreur UI + fallback
- Logging minimal côté client avec requestId/traceId quand possible
---
<a id="risque-melange-server-client-state"></a>
## Mélange server state / client state
### Risques
- Cache pollué par des états UI (onglets, filtres)
- UI qui reflète une donnée périmée sans le savoir
- Re-renders et bugs de synchronisation
### Symptômes
- "Ça revient tout seul" après refresh
- Données affichées ≠ données du backend
- Debug très long car état implicite
### Bonnes pratiques / mitigations
- Séparer explicitement server state vs client state
- Invalidation/reload explicite du server state
- État UI local réinitialisable
---
<a id="risque-api-state-local-ecran"></a>
## Appels API gérés en state local d'écran (refactor coûteux)
### Risques
- Server state non partageable entre écrans (liste/detail, wizard, tabs) → duplication et incohérences
- Pas de cache / invalidation standard → bugs subtils et re-fetchs inutiles
- Refactor tardif quand l'epic s'étend (mutations, cache, offline, pagination)
### Symptômes
- Même appel API recopié dans plusieurs écrans
- Un écran "A" modifie une ressource mais l'écran "B" n'est jamais rafraîchi
- Code review qui force un refactor vers un store/cache au milieu d'un epic
### Bonnes pratiques / mitigations
- Par défaut : créer un store de domaine (ex : Zustand) ou un cache de server state pour tout domaine susceptible d'être réutilisé
- Centraliser `isLoading`/`error`/`data` et la stratégie de refresh/invalidation
- Exception acceptable : état purement UI, local et jetable (ex : input de recherche, filtres temporaires non persistés)
---
<a id="risque-catch-silencieux"></a>
## Catch silencieux — erreur inconnue sans feedback utilisateur
### Risques
- Un `catch` qui ne traite que les cas connus laisse l'utilisateur face à un spinner qui disparaît sans message
- L'état d'erreur reste implicite → impossible de diagnostiquer ou de reproduire
### Symptômes
- Bouton spinner qui s'arrête, rien ne se passe
- Pas de toast / message d'erreur affiché
- Erreur "avalée" silencieusement dans les logs
### Bonnes pratiques / mitigations
```typescript
} catch (err: unknown) {
const code = (err as { code?: string }).code;
if (code === 'SUBSCRIPTION_REQUIRED') {
setSubscriptionRequired(true);
} else {
setError('Une erreur est survenue. Veuillez réessayer.'); // toujours un fallback
}
}
```
- **Règle** : tout `catch` doit avoir une branche `else` (ou `default`) qui affiche un feedback utilisateur explicite.
- Contexte technique : React Native / Expo — 09-03-2026
---
<a id="risque-auto-reset-etat-degrade"></a>
## Auto-reset d'un état dégradé sur toute réponse 2xx
### Risques
- Le client sort trop tôt d'un mode dégradé alors que la cause serveur est toujours présente
- Le bandeau ou l'état read-only clignote puis disparaît à tort
- Les utilisateurs retentent une action d'écriture qui va encore échouer
### Symptômes
- Un GET réussi réinitialise `isReadOnly` ou `isDegraded`
- L'UI redevient "normale" alors que Redis ou un service critique est toujours indisponible
- Les erreurs reviennent immédiatement à la prochaine mutation
### Bonnes pratiques / mitigations
- Ne réinitialiser l'état dégradé qu'après une requête d'écriture réussie
- Exclure `GET` et `HEAD` de la logique de reset
- Conserver le mode dégradé tant qu'aucune mutation n'a prouvé le retour à la normale
- Contexte technique : React Native / Expo — 10-03-2026
---
<a id="risque-refresh-store-fire-and-forget"></a>
## Refresh store en fire-and-forget après mutation
### Risques
- L'UI affiche un succès alors que la resynchronisation a échoué
- État local incohérent avec l'état serveur
- Erreurs silencieuses impossibles à diagnostiquer
### Symptômes
- Mutation réussie puis store jamais rafraîchi
- Spinner coupé avant que l'écran soit réellement à jour
- Données anciennes qui persistent jusqu'au prochain reload
### Bonnes pratiques / mitigations
- `await` explicite du refresh si l'UI dépend du résultat
- Gestion d'erreur dédiée sur la phase de resynchronisation
- N'utiliser le fire-and-forget que pour un effet secondaire réellement non bloquant
- Contexte technique : React Native / Expo — 10-03-2026
---
<a id="risque-boolean-ui-hardcode-store"></a>
## État booléen UI dérivé hardcodé au lieu d'être calculé depuis le store
### Risques
- Un état toggle (`isBookmarked`, `isLiked`, `isFollowed`) initialisé à `false` en dur ne reflète jamais l'état réel
- Le bouton est toujours en mode "ajouter" sans jamais passer en mode "supprimer"
### Symptômes
- `const isBookmarked = false; // état local géré ci-dessous via state`
- Bouton bookmark/like toujours dans le même état visuel peu importe l'état réel
### Bonnes pratiques / mitigations
```typescript
// ❌ Anti-pattern — état hardcodé
const isBookmarked = false;
// ✅ Pattern correct — dérivé du store au rendu
const { bookmarks } = useCommunityStore();
const isBookmarked = bookmarks.some((b) => b.thread.id === threadId);
```
- Règle : si le store contient la liste (bookmarks, likes, follows), l'état booléen se dérive avec `.some()` ou `.has()`
- Contexte technique : React Native / Zustand — app-alexandrie story 4.4, 20-03-2026
---
<a id="risque-flag-isloading-unique-nature-differente"></a>
## Flag `isLoading` unique pour des opérations de natures différentes
### Risques
- Un même flag (ex: `isBookmarking`) utilisé à la fois pour les mutations (add/remove) et le chargement de la liste provoque des bugs visuels — spinner manquant au premier chargement si une mutation est en cours en parallèle
### Symptômes
- Spinner absent au premier chargement de la liste bookmarks
- Bouton "ajouter" désactivé alors qu'aucune mutation n'est en cours
### Bonnes pratiques / mitigations
```typescript
// ❌ Anti-pattern — un seul flag pour tout
isBookmarking: boolean;
// ✅ Pattern correct — séparation claire
isBookmarking: boolean; // mutations add/remove
isLoadingBookmarks: boolean; // chargement de la liste (GET)
```
- Contexte technique : React Native / Zustand — app-alexandrie story 4.4, 20-03-2026
---
<a id="risque-zustand-optimistic-update-sous-listes"></a>
## Zustand : optimistic update sur item absent de la liste principale
### Risques
- Une action admin qui cherche l'item uniquement dans `state.threads` (liste paginée principale) manque les items présents exclusivement dans `state.pinnedThreads` ou `state.showcasedThreads`
- L'optimistic update ne se reflète pas visuellement même si l'appel API a réussi
### Symptômes
- L'item mis à jour par une action admin n'apparaît pas dans la nouvelle sous-liste après l'action
- Bug reproductible uniquement quand l'item est épinglé / en vitrine mais pas dans la page courante du flux principal
### Bonnes pratiques / mitigations
```typescript
// ❌ Anti-pattern : cherche uniquement dans la liste principale paginée
const target = state.threads.find((t) => t.id === threadId);
// → manque les items présents uniquement dans pinnedThreads / showcasedThreads
// ✅ Pattern correct : fallback sur toutes les sous-listes du store
const target =
state.threads.find((t) => t.id === threadId) ??
state.pinnedThreads.find((t) => t.id === threadId) ??
state.showcasedThreads.find((t) => t.id === threadId);
```
- **Règle** : toute action qui opère sur un item pouvant être présent dans plusieurs sous-listes doit chercher dans l'ensemble de ces listes
- Règle complémentaire : ne pas mettre à jour une sous-liste (ex: `pinnedThreads`) lors d'une action qui n'y a pas de rapport (ex: mise en vitrine ne touche pas `pinnedThreads`)
- Contexte technique : React Native / Zustand — app-alexandrie 23-03-2026
---
<a id="risque-zustand-erreur-sans-rethrow"></a>
## Store Zustand : méthodes `update*` qui avalent les erreurs sans rethrow
### Risques
- Une méthode store qui catch une erreur sans la relancer (`throw`) avale silencieusement les erreurs métier (ex: `UNSAFE_LINK`)
- L'écran appelant ne reçoit jamais l'erreur → impossible d'afficher un feedback à l'utilisateur
### Symptômes
- L'action semble réussir côté UI mais la donnée n'a pas changé en base
- Erreurs métier (ex: lien interdit) invisibles pour l'utilisateur final
### Bonnes pratiques / mitigations
```typescript
// ❌ MAUVAIS — l'erreur est avalée, l'écran ne sait pas que ça a échoué
async updateThread(forumSlug, threadId, body) {
await communityService.updateThread(accessToken, forumSlug, threadId, body);
},
// ✅ BON — l'erreur est propagée pour que l'écran puisse réagir
async updateThread(forumSlug, threadId, body) {
try {
await communityService.updateThread(accessToken, forumSlug, threadId, body);
} catch (e) {
const err = e as Error & { code?: string };
throw err; // Le code d'erreur (ex: UNSAFE_LINK) est préservé sur l'objet
}
},
```
- **Règle** : toute méthode store qui appelle le service réseau doit soit (1) relancer l'erreur enrichie avec `throw err`, soit (2) la stocker dans le state (`set({ error: err.message })`). Jamais les deux à la fois sans rethrow si l'écran doit réagir au catch.
- Contexte technique : React Native / Zustand — app-alexandrie 24-03-2026

View File

@@ -0,0 +1,46 @@
# Frontend — Risques & vigilance : Tests
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet.
---
<a id="risque-jest-rn-config-node"></a>
## Jest React Native — config node bloque les composants `.tsx`
### Risques
- `SyntaxError: Cannot use import statement outside a module` lors de l'import d'un barrel `.ts` qui réexporte des `.tsx`
- Impossible d'importer des composants React Native dans les tests — JSX non transformé
### Symptômes
- Erreur de syntaxe inattendue au run des tests sur un fichier `.ts` qui importe un `.tsx`
- Les tests de tokens passent mais tout test touchant un composant échoue
### Bonnes pratiques / mitigations
- `transform: { '^.+\\.ts$': 'ts-jest' }` ne transforme que `.ts` — pas `.tsx`
- **Pattern recommandé** : tester la logique pure (tokens, valeurs de style) dans `.spec.ts`, le rendu visuel dans `.spec.tsx` avec une config séparée (`@testing-library/react-native` + `babel-jest`)
- Exporter le `StyleSheet` de chaque composant pour le tester sans JSX (voir pattern dédié dans `10_frontend_patterns_valides.md`)
- Contexte technique : React Native / Jest / ts-jest — app-alexandrie 19-03-2026
---
<a id="risque-faux-test-negatif"></a>
## Faux test négatif — tester le helper au lieu de tester l'exclusion
### Risques
- Un test nommé "X n'utilise pas Y" qui appelle Y en interne est un test normal mal documenté, pas un test d'exclusion
- Donne une fausse confiance sur le comportement par défaut du helper
### Symptômes
- Test intitulé "sans fallback, la valeur EN vide n'est pas remplacée" mais qui appelle le helper avec fallback activé
### Bonnes pratiques / mitigations
- Un vrai test négatif vérifie que X n'importe pas Y, ou que le comportement par défaut empêche l'effet indésirable
- Pour un helper à fallback optionnel : tester explicitement le cas `fallbackToFr=false` (défaut) avec une valeur vide
- Contexte technique : TypeScript / Jest — app-template-resto 17-03-2026

View File

@@ -0,0 +1,11 @@
# n8n — Patterns validés — Index
Patterns n8n testés et validés en conditions réelles.
Avant toute proposition n8n, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
---
| Fichier | Domaine | Entrées clés |
|---------|---------|--------------|
| `general.md` | Principes de conception, feature flags, routage | Feature flags pour routage plateformes |

View File

@@ -1,51 +1,18 @@
# Patterns n8n validés
# n8n — Patterns validés : Général
Ce fichier contient **uniquement** des patterns :
- testés,
- validés,
- utilisés en conditions réelles.
Dernière mise à jour : 2026-03-09
---
## Index
- [Feature flags pour routage plateformes](#pattern-n8n-feature-flags-routage-plateformes)
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/n8n/patterns/README.md` pour l'index complet.
---
## Principes de conception
- Un workflow = un objectif clair
- Noms de nodes explicites (décrivent ce que le node fait, pas ce quil est)
- Noms de nodes explicites (décrivent ce que le node fait, pas ce qu'il est)
- Gestion des erreurs prévue dès le départ (Error Trigger ou Try/Catch)
- Facilité de reprise après plusieurs semaines : documenter les workflows non triviaux
## Règle dor
Si ce nest pas confirmé comme fonctionnel, **ça na rien à faire ici**.
---
## Format standard dun pattern
## Pattern : <Nom clair>
- Node(s) : …
- Contexte : …
- Avantage : …
- Limites / vigilance : …
- Validé le : DD-MM-YYYY
- Contexte technique : (optionnel) ex. `n8n 1.121.2 / self-hosted / docker`
### Exemple / snippet
```txt
(contenu)
```
<a id="pattern-n8n-feature-flags-routage-plateformes"></a>
## Pattern : Feature flags pour routage plateformes
@@ -66,5 +33,3 @@ const config = {
linkedin: true,
};
```
---

View File

@@ -0,0 +1,11 @@
# n8n — Risques & vigilance — Index
Nodes, fonctionnalités et patterns n8n sensibles, susceptibles de provoquer des bugs subtils ou comportements inattendus.
Avant toute proposition n8n, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
---
| Fichier | Domaine | Entrées clés |
|---------|---------|--------------|
| `general.md` | Vigilance générale, IF Node, staticData | Expressions undefined silencieux, comparaisons IF ambiguës, staticData persistance entre exécutions |

View File

@@ -1,21 +1,6 @@
# Nodes n8n à risque / vigilance
# n8n — Risques & vigilance : Général
Ce fichier recense les **nodes, fonctionnalités ou patterns n8n sensibles**,
cest-à-dire susceptibles de provoquer :
- des bugs subtils,
- des comportements inattendus,
- des problèmes lors des upgrades.
Dernière mise à jour : 2026-03-09
---
## Index
- [Vigilance générale](#risque-n8n-vigilance-generale)
- [IF Node](#risque-n8n-if-node)
- [staticData (`$getWorkflowStaticData`)](#risque-n8n-staticdata)
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/n8n/risques/README.md` pour l'index complet.
---
@@ -26,15 +11,6 @@ Dernière mise à jour : 2026-03-09
- **Expressions mal évaluées** : `{{ $json.field }}` peut retourner `undefined` silencieusement
- **Comportements implicites** des nodes : toujours tester les cas limites (null, tableau vide, erreur HTTP)
## Règles dutilisation
- On documente uniquement des cas vus en vrai (ou très probables + signalés).
- Chaque entrée doit dire :
- ce qui peut mal se passer,
- comment on le voit (symptômes),
- comment on le maîtrise (mitigation).
- Si cest lié à une version : on note la version.
---
<a id="risque-n8n-if-node"></a>
@@ -47,7 +23,7 @@ Dernière mise à jour : 2026-03-09
### Symptômes
- Branche IF inversée par rapport à lintuition
- Branche IF "inversée" par rapport à l'intuition
- Cas limites qui passent en prod mais pas en test
### Bonnes pratiques / mitigations
@@ -69,21 +45,21 @@ Dernière mise à jour : 2026-03-09
### Risques
- Persistance entre exécutions (effet mémoire non voulu)
- Dépendance forte à l`executionId` si on fait de lagrégation
- Dépendance forte à l'`executionId` si on fait de l'agrégation
- Sensible aux upgrades n8n (comportements qui peuvent changer)
### Symptômes
- Données fantômes réutilisées
- Données "fantômes" réutilisées
- Résultats incohérents entre exécutions
- Branches parallèles qui sécrasent
- Branches parallèles qui s'écrasent
### Bonnes pratiques / mitigations
- Toujours lier les données à `executionId`
- Nettoyer/reset explicitement à chaque run
- Documenter le pattern dès quil est utilisé
- Préférer un stockage externe si létat devient critique (DB/Redis/etc.)
- Documenter le pattern dès qu'il est utilisé
- Préférer un stockage externe si l'état devient critique (DB/Redis/etc.)
### Contexte technique

View File

@@ -0,0 +1,11 @@
# Product — Patterns validés — Index
Patterns de cadrage produit, priorisation et analyse fonctionnelle validés sur des projets réels.
Avant toute proposition produit, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
---
| Fichier | Domaine | Entrées clés |
|---------|---------|--------------|
| `general.md` | Cadrage, roadmap, architecture fonctionnelle V1 | Epic UI Fondation prérequis bloquant, Progression V1 sans module dédié |

View File

@@ -1,63 +1,6 @@
# Patterns produit / métier validés
# Product — Patterns validés : Général
Ce fichier contient des patterns de **cadrage produit, priorisation et analyse fonctionnelle** :
- observés en conditions réelles,
- validés sur des projets livrés,
- utiles à réutiliser sur d'autres produits.
Objectif : éviter de redélibérer sur des sujets déjà tranchés, capitaliser ce qui fonctionne
du point de vue product management et analyse métier.
Dernière mise à jour : 2026-03-20
---
## Index
- [Epic UI Fondation — prérequis bloquant avant Epic 1](#pattern-epic-ui-fondation)
- [Progression V1 sans module dédié — calcul depuis la source de vérité](#pattern-progression-v1-sans-module)
---
## Règle d'or
Si ce n'est pas **validé par l'expérience projet**, ça n'a pas sa place ici.
- Pas de frameworks théoriques génériques (pas de "appliquer le RICE scoring" sans contexte)
- Toujours préciser le contexte (type de produit, taille d'équipe, stade du projet)
---
## Périmètre couvert
- Patterns de cadrage et découverte (discovery)
- Priorisation et arbitrages (backlog, roadmap)
- Gestion des exigences et user stories
- Patterns d'analyse fonctionnelle récurrents
- Décisions produit structurantes validées
- Anti-patterns de pilotage produit (dans ce fichier ou dans un fichier dédié)
---
## Format standard d'un pattern
## Pattern : <Nom clair>
- Objectif : …
- Contexte : …
- Quand l'utiliser : …
- Quand l'éviter : …
- Avantage : …
- Limites / vigilance : …
- Validé le : DD-MM-YYYY
- Contexte produit : (obligatoire) ex. `SaaS B2B / early-stage` ou `App mobile / feature mature`
### Description
(comportement attendu, logique de décision, règles d'application)
### Checklist (si pertinente)
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/product/patterns/README.md` pour l'index complet.
---
@@ -127,5 +70,3 @@ Plutôt que d'ouvrir un domaine `achievements` ou `analytics` dès la V1 :
- [ ] Compteurs calculés depuis la source de vérité existante
- [ ] Catalogue d'objectifs en code (pas en DB)
- [ ] Interface de sortie stable même si les sources de données évoluent
---

View File

@@ -0,0 +1,12 @@
# Product — Risques & vigilance — Index
Anti-patterns de pilotage produit et erreurs de cadrage observées en conditions réelles.
Avant toute proposition produit, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
---
| Fichier | Domaine | Entrées clés |
|---------|---------|--------------|
_(aucune entrée pour le moment — à alimenter via `95_a_capitaliser.md`)_

View File

@@ -0,0 +1,12 @@
# UX — Patterns validés — Index
Patterns UX/UI observés en conditions réelles, validés sur des projets livrés.
Avant toute proposition UX, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
---
| Fichier | Domaine | Entrées clés |
|---------|---------|--------------|
_(aucune entrée pour le moment — à alimenter via `95_a_capitaliser.md`)_

View File

@@ -0,0 +1,12 @@
# UX — Risques & vigilance — Index
Anti-patterns et erreurs UX récurrentes observées en conditions réelles.
Avant toute proposition UX, identifie le fichier dont le nom et la description matchent le domaine traité, puis lis-le.
---
| Fichier | Domaine | Entrées clés |
|---------|---------|--------------|
_(aucune entrée pour le moment — à alimenter via `95_a_capitaliser.md`)_

View File

@@ -0,0 +1,9 @@
# Workflow — Risques & vigilance — Index
Risques liés au process de développement, aux agents BMAD, et au tracking des stories.
---
| 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 |

View File

@@ -0,0 +1,50 @@
# Workflow — Risques & vigilance : Story tracking
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/workflow/risques/README.md` pour l'index complet.
---
<a id="risque-story-completed-taches-echec"></a>
## Story "completed" avec tâches ❌ auto-déclarées
### Risques
- Un agent sette `Status: completed` alors que son propre Dev Agent Record liste des items ❌ non implémentés
- Le store mobile, service ou tests peuvent être déclarés manquants par l'agent lui-même mais la story semble terminée
### Symptômes
- Dev Agent Record contient `❌ store mobile non implémenté` mais `Status: completed`
- Code review découvre des ACs non satisfaits
### Bonnes pratiques / mitigations
- Avant de setter `Status: completed`, vérifier que le Dev Agent Record ne contient aucun ❌
- En cas de doute ou d'item manquant, setter `Status: review` pour déclencher la code review
- **Règle** : `Status: completed` = zéro ❌ auto-déclaré dans le Dev Agent Record
- Contexte technique : BMAD / workflow agent — app-alexandrie 20-03-2026
---
<a id="risque-story-done-file-list-vide"></a>
## Story "done" sans aucun fichier source dans la File List
### Risques
- Un agent peut halluciner la completion d'une story en produisant une note générique sans écrire de code
- La File List ne contient que des fichiers `_bmad-output/` mais aucun `src/`, `prisma/`, `tests/`
### Symptômes
- Completion note générique du type "Ultimate context engine analysis completed"
- File List réduite à 2 fichiers meta (story file, sprint-status)
- `git log --follow src/` ne montre aucun commit lié à la story
### Bonnes pratiques / mitigations
- Lors d'une code review, si la File List ne contient aucun fichier source : traiter comme non implémentée
- Vérifier avec `git log --follow src/` avant d'accepter le `Status: done`
- Ne pas faire confiance au status `done` sans preuve dans le code
- Contexte technique : BMAD / agent Codex — app-template-resto 21-03-2026