From 2e6ed9d3748df85335880f5db45f9b59e77c58c5 Mon Sep 17 00:00:00 2001 From: MaksTinyWorkshop Date: Mon, 23 Mar 2026 13:27:17 +0100 Subject: [PATCH] Restore entries lost during rebase conflict resolution --- 95_a_capitaliser.md | 1236 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1236 insertions(+) diff --git a/95_a_capitaliser.md b/95_a_capitaliser.md index e8eaf40..a80fcce 100644 --- a/95_a_capitaliser.md +++ b/95_a_capitaliser.md @@ -67,6 +67,1242 @@ Proposition : **VIGILANCE — Store Zustand : collections sans clé de contexte** Quand un store stocke des données qui dépendent d'un paramètre de navigation (forumSlug, threadId...), ne pas se contenter d'un guard `if (items.length > 0) return` — cela empêche le rechargement lors d'un changement de contexte. Soit stocker la clé de contexte avec les données (`categoriesForumSlug: string | null`), soit supprimer le guard et dépendre uniquement du changement de paramètre dans le useEffect. +--- +2026-03-16 — app-template-resto + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_risques_et_vigilance.md + +Pourquoi : +Code review story 1.9 a révélé un anti-pattern : les tâches de story peuvent déclarer [x] consumedAt sans que le champ existe réellement dans le schéma Prisma. + +Proposition : +**Anti-pattern : Divergence schéma / spec story** +Lors d'une implémentation, valider que chaque champ mentionné dans les tâches (consumedAt, tokenHash, etc.) existe réellement dans le schéma Prisma avant de marquer la tâche [x]. Une story peut décrire consumedAt comme stratégie de conception sans que le champ soit présent — toujours croiser avec schema.prisma. + +--- +2026-03-16 — app-template-resto + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_patterns_valides.md + +Pourquoi : +Le module sendPasswordResetEmail utilise `server-only` ce qui le rend non-importable dans le runner de tests Node. Résolution : tester la logique pure (safeHttpUrl) dans un fichier séparé sans dépendances Next.js. + +Proposition : +**Pattern : Isolation des guards purs des modules server-only** +Extraire la logique pure (validation URL, sanitisation) dans des fonctions utilitaires sans import `server-only` ou `nodemailer`. Le module email orchestre uniquement. Cela permet de tester les guards en isolation sans les contraintes du runtime Next.js. + +--- +2026-03-16 — app-template-resto + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_patterns_valides.md + +Pourquoi : +Pattern validé sur story 1.10 — la règle `server-only` vs testabilité est implicite dans le projet mais mérite d'être explicitée pour les agents futurs. + +Proposition : +**Pattern : `server-only` réservé aux modules avec APIs Next.js exclusivement serveur** +Ne pas mettre `import "server-only"` sur les modules de logique pure injectés via dépendances (ex: `deleteSession({ prisma, sessionToken })`). Réserver `server-only` aux modules qui appellent des APIs Next.js runtime-only (`cookies()`, `headers()`, `redirect()`). Les modules purs sans ces imports peuvent être importés par le test runner Node et testés unitairement sans friction. + +--- +2026-03-16 — app-template-resto + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_patterns_valides.md + +Pourquoi : +Pattern validé sur story 1.10 — suppression de session avec gestion idempotente, réutilisable pour toute opération de révocation. + +Proposition : +**Pattern : Suppression de session idempotente (P2025)** +Lors d'une déconnexion ou révocation de session, entourer le `prisma.session.delete()` d'un try/catch qui absorbe silencieusement le code Prisma `P2025` (record not found). Une session peut déjà avoir été supprimée (expiration, logout concurrent) — ce n'est pas une erreur, ne pas la propager. + +--- +2026-03-16 — app-template-resto + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_patterns_valides.md + +Pourquoi : +Pattern validé sur story 1.10 — Server Action Next.js qui orchestre des dépendances Next.js runtime non-testables : isoler la logique pure dans un module injectable. + +Proposition : +**Pattern : Server Action Next.js — isoler la logique pure dans un module injectable** + +--- +2026-03-16 — app-template-resto + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_frontend_risques_et_vigilance.md + +Pourquoi : +Problème rencontré en build réel sur Next 16/Turbopack. Le pattern `useSearchParams()` côté client casse le prerender si le composant n'est pas protégé par `Suspense`. + +Proposition : +**Risque : `useSearchParams()` sans `Suspense` casse le build Next.js App Router** +Avec Next.js App Router récent, tout composant client utilisant `useSearchParams()` peut provoquer un échec de prerender/build s'il est rendu sans boundary `Suspense` depuis la page/layout serveur. Quand un écran dépend de `useSearchParams()`, isoler ce composant client et le rendre sous `` au niveau de la page. + +--- +2026-03-16 — app-template-resto + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_risques_et_vigilance.md + +Pourquoi : +Le build Next a échoué sur une initialisation Prisma trop tôt dans le cycle, avant disponibilité effective de `DATABASE_URL`. Le correctif est réutilisable sur d'autres apps App Router. + +Proposition : +**Risque : initialiser Prisma au chargement de module peut casser le build** +Dans une app Next.js App Router, un import global qui initialise immédiatement Prisma peut faire échouer la collecte de pages/routes au build si `DATABASE_URL` n'est pas disponible dans l'environnement de build. Préférer une initialisation lazy-safe : soit retarder l'accès DB au moment de l'appel métier, soit retourner un proxy qui lève une erreur claire uniquement lors du premier accès réel à la DB. + +--- +2026-03-16 — app-template-resto + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_frontend_patterns_valides.md + +Pourquoi : +La migration de config ESLint a permis de remettre `lint` au vert avec Next 16 sans dépendre d'un pont de compatibilité cassant. + +Proposition : +**Pattern : utiliser directement les presets flat de `eslint-config-next`** +Sur un projet Next.js récent, préférer une config `eslint.config.mjs` qui importe directement `eslint-config-next/core-web-vitals` et `eslint-config-next/typescript`, puis ajoute des overrides ciblés. Cela évite les bugs de compatibilité de l'ancien `.eslintrc` et limite la dette de config quand le repo utilise déjà le flat config. +Une Server Action qui appelle `cookies()`, `headers()` ou `redirect()` ne peut pas être testée unitairement (imports runtime-only). Pattern : extraire la logique pure (suppression DB, validation) dans une fonction avec injection de dépendances (`performSignOut({ prismaClient, sessionToken, redirectFn })`). La Server Action reste fine et orchestre uniquement les dépendances Next.js. Le module extrait est testable sans friction avec le runner Node natif. + +--- +2026-03-16 — app-template-resto / code-review story 1.11 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_risques_et_vigilance.md + +Pourquoi : +`import "server-only"` dans les repositories casse les tests Node.js hors Next.js — rencontré lors de cette review. + +Proposition : +## Risque : `server-only` dans les repositories bloque les tests unitaires + +`import "server-only"` empêche l'exécution des fichiers hors runtime Next.js. +Solution : créer un stub `node_modules/server-only/index.js` (no-op) pour les tests. +Alternativement, ne mettre `server-only` que dans les fichiers qui utilisent des APIs +Next.js (`cookies()`, `headers()`), pas dans les repositories purs. + +--- + +2026-03-16 — app-template-resto / code-review story 2.1 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_frontend_risques_et_vigilance.md + +Pourquoi : +Deux anti-patterns Next.js App Router détectés en code review : duplication de type entre couche server et couche UI, et usage de React.createElement dans un fichier .ts pour contourner l'exigence JSX. + +Proposition : +## Anti-pattern : type ViewData dupliqué entre server et composant UI (Next.js App Router) + +Contexte : quand un service server-only (`src/server/...`) produit un type de données et qu'un composant UI (`src/app/...`) en a besoin, il est tentant de redéfinir le type localement dans le composant. + +Risque : divergence silencieuse — TypeScript accepte deux structures identiques par structural typing, mais si le type source évolue (champ ajouté, renommé), la couche UI reste désynchronisée sans erreur de compilation tant que les formes correspondent. + +Règle : le type appartient à la couche qui le produit. La couche UI importe et re-exporte uniquement. + +```ts +// ✅ Dans PublicHomeContent.tsx +export type { PublicHomeViewData } from "@/server/public/getPublicHomeData"; + +// ❌ À éviter : redéfinir le même type dans le composant +export type PublicHomeViewData = { tenantName: string; ... }; +``` + +--- + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_frontend_risques_et_vigilance.md + +Pourquoi : +Anti-pattern détecté : composant React écrit en .ts avec React.createElement pour éviter d'avoir à déclarer l'extension .tsx, rendant le code illisible et non extensible. + +Proposition : +## Anti-pattern : composant React dans un fichier .ts (React.createElement workaround) + +Tout fichier contenant du JSX ou un composant React doit avoir l'extension `.tsx`. Utiliser `React.createElement` dans un `.ts` fonctionne techniquement mais est un anti-pattern : +- Rend le code illisible comparé à JSX natif +- Donne une fausse impression que le fichier est "sans JSX" +- Empêche l'utilisation de la syntaxe JSX si besoin d'ajouter des enfants complexes +- Peut tromper les outils de linting et les reviewers + +Règle : si un fichier exporte une fonction retournant un ReactElement ou utilise React, l'extension est `.tsx`. + +--- + +2026-03-17 — app-template-resto / story 2-2 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_frontend_risques_et_vigilance.md + +Pourquoi : +Review story 2-2 — anti-pattern "double validation de garde" dans Next.js App Router : un layout enfant rejette déjà les segments invalides via notFound(), mais la page enfant répétait la même condition. Risque de désynchronisation silencieuse si l'une des deux est modifiée sans l'autre. + +Proposition : +**Anti-pattern : double validation de segment dynamique App Router** +Dans une structure layout → page, si le layout fait `notFound()` sur un segment invalide, la page ne doit PAS répéter la même condition. La page doit faire confiance à son layout parent. Répéter la validation : +- crée une fausse impression que les deux chemins sont indépendants +- rend le code difficile à maintenir (si on change la condition dans le layout, il faut penser à changer la page) +- peut induire en erreur sur quel composant est réellement responsable de la garde +Règle : une seule responsabilité par couche — le layout garde, la page consomme. + +--- + +2026-03-17 — app-template-resto / story 2-2 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_frontend_risques_et_vigilance.md + +Pourquoi : +Review story 2-2 — faux test d'exclusion : un test nommé "test négatif : une page X n'utilise pas helper Y" appelait Y et vérifiait un résultat null — ce n'est pas un test d'exclusion, c'est juste un test normal mal documenté. + +Proposition : +**Anti-pattern : faux test négatif — tester le helper au lieu de tester l'exclusion** +Un test intitulé "X n'utilise pas Y" doit vérifier que X n'importe pas Y, ou que le comportement par défaut de Y empêche l'effet indésirable. Si on appelle Y dans le test, on teste Y, pas l'exclusion de X. Pour les helpers à fallback optionnel, le vrai test négatif est : "avec fallbackToFr=false (défaut), une valeur EN vide n'est PAS remplacée silencieusement" — ce qui force l'appelant à choisir explicitement le fallback. + +--- + +2026-03-17 — app-template-resto / story 2-3 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_patterns_valides.md + +Pourquoi : +Pattern validé sur story 2.3 — séparation nette entre le repository (requête Prisma brute) et le service (mapping DTO + règles métier). La règle isVisible est appliquée dans le service, pas dans la requête DB, ce qui la rend testable sans Prisma. + +Proposition : +**Pattern : filtrage des règles métier dans le service, pas dans le repository** +Pour les données publiques avec des règles de visibilité (ex: `isVisible`, `isActive`), mettre le filtre dans le service et non dans la clause `where` du repository. Le repository charge tout ce qui est candidat (ex: catégories visibles, plats de toutes visibilités) ; le service applique les règles métier et mappe vers des DTOs. Avantages : +- la règle est testable unitairement sans Prisma (mock de données brutes) +- la requête DB reste simple et stable entre les contextes (dashboard edit ≠ rendu public) +- les futurs cas (ex: admin voit les invisibles) ne nécessitent pas de modifier la requête + +Exception acceptable : filtres de performance (pagination, tenant scoping) restent dans le `where`. + +--- + +2026-03-17 — app-template-resto / story 2-3 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_patterns_valides.md + +Pourquoi : +Pattern validé sur story 2.3 — les champs Decimal Prisma doivent être sérialisés explicitement avant de traverser des boundaries (service, réseau, tests). Sans toString(), le type Decimal Prisma n'est pas JSON-safe et peut provoquer des erreurs silencieuses. + +Proposition : +**Pattern : sérialiser les Decimal Prisma en string au niveau du repository** +Tout champ `Decimal` Prisma (ex: `price`) doit être converti en `string` (`decimal.toString()`) dans le repository avant d'être retourné au service. Ne pas laisser les objets Decimal traverser les couches — ils ne sont pas JSON-sérialisables nativement et leur comportement varie selon le contexte (Node vs browser vs test runner). Le DTO public utilise `string | null` pour le prix, pas `Decimal`. + +--- + +2026-03-17 — app-template-resto / code-review story 2-3 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_risques_et_vigilance.md + +Pourquoi : +Anti-pattern détecté en code review : les modèles menu (Allergen, Tag, MenuCategory, Dish) avaient chacun un champ `tenantId` sans relation Prisma `@relation` vers `Tenant`. La migration SQL ne créait pas non plus les FK correspondantes. Résultat : isolation multi-tenant non enforced au niveau DB. + +Proposition : +**Anti-pattern : champ tenantId sans FK ni relation Prisma vers Tenant** +Tout modèle tenant-scoped doit avoir : +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 (`ALTER TABLE ... ADD CONSTRAINT ... REFERENCES "tenants"`) + +Un `tenantId TEXT NOT NULL` sans ces trois éléments ne garantit aucune isolation au niveau DB — Prisma ne génère pas de FK automatiquement sans la relation déclarée. Vérifier systématiquement que les nouveaux modèles respectent ce guardrail lors du code review. + +--- + +2026-03-17 — app-template-resto / code-review story 2-3 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_patterns_valides.md + +Pourquoi : +Pattern validé en code review 2.3 — une fonction utilitaire de résolution de tenant (`resolvePublicTenantSelection`) était définie dans `getPublicHomeData.ts` et importée par le module menu. Ce couplage crée une dépendance sémantique incorrecte entre deux domaines distincts. + +Proposition : +**Pattern : extraire les helpers de résolution tenant dans un module partagé dédié** +Toute fonction utilitaire transverse aux domaines (ex: résolution du tenant public depuis les variables d'environnement) doit vivre dans `src/server/tenant/` plutôt que dans un module métier spécifique. Les modules métier importent depuis ce module partagé. L'ancien emplacement peut ré-exporter pour rétrocompatibilité le temps de la migration. +```ts +// ✅ src/server/tenant/resolvePublicTenant.ts +export function resolvePublicTenantSelection(env) { ... } + +// ✅ src/server/public/getPublicHomeData.ts (rétrocompatibilité) +export { resolvePublicTenantSelection } from "@/server/tenant/resolvePublicTenant"; +``` + +--- + +2026-03-17 — app-template-resto / story 2-4 + +FILE_UPDATE_PROPOSAL +Fichier cible : 90_debug_et_postmortem.md + +Pourquoi : +Bug discret détecté au build : un `export { fn }` ne rend pas `fn` disponible localement dans le même fichier. TypeScript et ESLint ne le signalent pas, mais le build strict (TypeScript `--noEmit`) le rejette avec "Cannot find name". Piège fréquent lors de refactors de module. + +Proposition : +**Bug : `export { fn }` ne constitue pas un import local — détecté uniquement au build** + +Dans `getPublicHomeData.ts`, la fonction `resolvePublicTenantSelection` était re-exportée via : +```ts +export { resolvePublicTenantSelection } from "@/server/tenant/resolvePublicTenant"; +``` +…puis utilisée localement dans le même fichier sans `import`. ESLint et `tsc` (hors build) ne l'ont pas signalé, mais `next build` avec TypeScript strict a levé `Cannot find name 'resolvePublicTenantSelection'`. + +Règle : un re-export ne crée pas de binding local. Si la fonction est utilisée dans le même fichier, ajouter un `import` séparé en plus du `export`. + +--- + +2026-03-17 — app-template-resto / code-review story 2-4 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_risques_et_vigilance.md + +Pourquoi : +Code review 2.4 — bug de boucle infinie potentielle : `resolvePublicPageAccess("home", ...)` redirige vers `buildLocalizedPath("home")` = `/`. Si `home` est désactivé, la page `/` redirige vers `/` indéfiniment. Next.js absorbe la boucle silencieusement mais le comportement utilisateur est cassé. + +Proposition : +**Anti-pattern : redirect vers la destination désactivée elle-même** + +Dans un mécanisme de redirection sur page désactivée (feature flags, pages publiques), toujours vérifier que la destination de fallback n'est pas la page en cours. Cas typique : `home` désactivé → redirect vers `/` (qui est `home`) → boucle. + +Règle : si `pageKey === fallbackKey`, ne pas rediriger. Retourner `null` (accès non bloqué ou comportement non défini en V1) plutôt que de boucler. + +```ts +if (pageKey === "home") return null; // évite redirect home → home +return buildLocalizedPath(locale, "home"); +``` + +--- + +2026-03-17 — app-template-resto / code-review story 2-4 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_patterns_valides.md + +Pourquoi : +Pattern validé sur story 2.4 — mécanisme centralisé d'activation de pages/features par tenant, reutilisable sans duplication dans chaque page ou composant. + +Proposition : +**Pattern : helper centralisé d'activation de features tenant-scoped** + +Pour les features activables par tenant (ex: pages publiques, modules optionnels), centraliser la logique dans un helper pur distinct : + +```ts +// 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]]; +} +``` + +Principes : +- Le helper est pur (pas d'I/O, testable sans Prisma). +- La config est chargée une seule fois par le module d'entrée (`getPublicPagesConfigFromEnv`). +- Les composants de navigation et les pages importent `isPublicPageEnabled` depuis ce module — jamais depuis Prisma directement. +- Comportement par défaut sain : `null`/`undefined` → tout activé (évite les régressions si la config n'a pas été provisionnée). + +Ce pattern s'applique à tous les feature flags tenant-scoped : pages, modules, intégrations tierces. + +--- + +2026-03-17 — app-template-resto / story 2-5 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_patterns_valides.md + +Pourquoi : +Story 2.5 — l'URL de réservation était déjà dans `PublicHomeProfile.reservationUrl`. Créer un modèle ou un champ dédié aurait introduit une duplication de donnée sans bénéfice en V1. + +Proposition : +**Pattern : réutiliser un champ existant plutôt que créer un modèle dédié pour un besoin V1** + +Avant d'ajouter un nouveau modèle ou une nouvelle table pour stocker une configuration simple, vérifier si le schéma existant ne contient pas déjà le champ. Une URL de réservation externe est une donnée de profil tenant — elle appartient naturellement à `PublicHomeProfile`, pas à un modèle `ReservationConfig` séparé. + +Règle : ne créer un modèle dédié que si la configuration a un cycle de vie, des relations, ou des cardinalités qui ne correspondent pas à un champ simple dans un modèle existant. En V1, un champ optionnel dans le modèle le plus proche est suffisant. + +--- + +2026-03-17 — app-template-resto / story 2-5 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_patterns_valides.md + +Pourquoi : +Story 2.5 — l'URL de réservation vient d'une config tenant et est rendue dans un lien public. Sans validation côté serveur, une URL mal formée ou avec un protocole non-https pourrait être injectée dans le HTML. + +Proposition : +**Pattern : valider le protocole d'une URL externe avant de la passer à un lien public** + +Toute URL provenant d'une config tenant et rendue dans un `` public doit être validée côté serveur avant d'être transmise au composant : + +```ts +function isSafeUrl(url: string): boolean { + try { + const { protocol } = new URL(url); + return protocol === "https:" || protocol === "http:"; + } catch { + return false; + } +} +// Retourner null si invalide — le composant gère l'absence d'URL +if (!url || !isSafeUrl(url)) return null; +``` + +Règle : ne jamais passer directement une URL de base de données dans un `` sans validation. Le composant UI reçoit soit une URL valide soit `null` — jamais une chaîne non vérifiée. Cela prévient les injections `javascript:` ou les URLs malformées. + +--- + +2026-03-20 — app-alexandrie + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_risques_et_vigilance.md + +Pourquoi : +Un agent dev a livré un NestJS controller syntaxiquement cassé (méthodes imbriquées, route dupliquée 3x) sans que TypeScript ne l'ait bloqué avant commit. Le code ne pouvait pas compiler mais le statut story était "completed". + +Proposition : +## Risque : Controller NestJS corrompu par insertions multiples + +Un agent qui insère des endpoints dans un controller existant peut briser la syntaxe TypeScript (méthodes imbriquées, décorateurs orphelins, routes dupliquées) si les modifications sont faites par concaténation plutôt que par réécriture structurée. + +Symptômes typiques : +- `@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 +- Le compilateur TypeScript ne catch pas toujours cela (dépend de la position dans l'AST) + +Règle : 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. + +--- + +2026-03-20 — app-alexandrie + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_risques_et_vigilance.md + +Pourquoi : +Quota TTL calculé avec heure locale serveur au lieu d'UTC — crée une dérive du reset de quota pouvant aller jusqu'à +/-12h selon le timezone serveur. Découvert en review adversariale story 4.3. + +Proposition : +## Risque : TTL Redis quota calculé en heure locale + +Toujours calculer le TTL des quotas journaliers en UTC : + +```typescript +// ✅ CORRECT — UTC midnight garanti +const midnight = new Date( + Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1), +); +const ttlMs = midnight.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()` pour le calcul. Vérifier systématiquement en review. + +--- + +2026-03-20 — app-alexandrie + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_risques_et_vigilance.md + +Pourquoi : +Un agent a marqué une story "completed" alors que le store mobile, le service mobile et les tests e2e étaient déclarés ❌ non implémentés dans son propre Dev Agent Record. La story Status ne doit jamais être "completed" si des ACs ou tâches sont marquées ❌. + +Proposition : +## Anti-pattern : Story "completed" avec tâches ❌ auto-déclarées + +Si le Dev Agent Record liste explicitement des items ❌ (non implémentés), le Status de la story doit être `in-progress` ou `review` — jamais `completed`. + +Règle : avant de setter `Status: completed`, vérifier que le Dev Agent Record ne contient aucun ❌. En cas de doute, setter `Status: review` pour déclencher la code review. + +--- + +2026-03-20 — app-alexandrie + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_frontend_risques_et_vigilance.md + +Pourquoi : +Code review Story 4.4 — l'état "isBookmarked" était hardcodé à `false` dans l'écran thread detail, ce qui rendait le bouton bookmark toujours en mode "ajouter" sans jamais réfléchir l'état réel. Anti-pattern récurrent quand l'état vient du store mais n'est pas dérivé correctement. + +Proposition : +## Anti-pattern : état booléen UI dérivé hardcodé au lieu d'être calculé depuis le store + +Dans un écran qui affiche un état toggle (bookmarké, liké, suivi), ne jamais initialiser l'état via `const isX = false` avec un commentaire "géré ci-dessous". L'état doit toujours être dérivé du store au moment du rendu : + +```typescript +// ❌ Anti-pattern — state hardcodé, jamais mis à jour +const isBookmarked = false; // état local géré ci-dessous via state + +// ✅ 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()` — pas de state local redondant. Cela garantit la cohérence entre les écrans sans synchronisation manuelle. + +--- + +2026-03-21 — app-template-resto / code-review story 2.8 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_backend_risques_et_vigilance.md + +Pourquoi : +Un agent (GPT-5 Codex) a marqué une story done avec une File List réduite à 2 fichiers meta, une completion note générique ("Ultimate context engine analysis completed"), et aucun code écrit. Le contexte d'exécution de cet agent était probablement dégradé (timeout, quota). + +Proposition : +## Anti-pattern : Story "done" avec File List vide de fichiers source + +Un agent peut halluciner la completion d'une story en produisant une note générique sans écrire de code. Signal d'alerte : la File List ne contient que des fichiers `_bmad-output/` (story file, sprint-status) mais aucun fichier `src/`, `prisma/`, `tests/`. + +Règle : lors d'une code review, si la File List ne contient aucun fichier source, traiter la story comme non implémentée. Vérifier avec `git log --follow src/` pour confirmer l'absence de commits. Ne pas faire confiance au status `done` sans preuve dans le code. + +--- + +2026-03-21 — app-template-resto / story 2.8 + +FILE_UPDATE_PROPOSAL +Fichier cible : 10_frontend_patterns_valides.md + +Pourquoi : +Pattern validé sur story 2.8 — le mode embed click-to-load est une décision de performance et de consentement implicite : aucun tiers ne se charge sans action explicite utilisateur. + +Proposition : +**Pattern : click-to-load strict pour les embeds tiers (iframe/widget)** + +Pour tout embed tiers chargé à la demande (module de réservation, map, chat) : +- La page se rend initialement sans l'iframe (état `loaded=false`) +- Un bouton explicite déclenche le chargement (`onClick={() => setLoaded(true)}`) +- L'iframe est conditionnellement rendu : `{loaded &&