mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-06 13:31:43 +02:00
Intègre ~50 entrées depuis 95_a_capitaliser.md vers les fichiers validés :
- backend risques : +15 (GET sans authz, TOCTOU tenantId, TTL UTC, AdminRoleGuard, P3014...)
- backend patterns : P2002 amendé (create+update) + 10 nouveaux (Decimal, URL safe, EN enforcement...)
- frontend risques : +21 (defaultValue/key, useTransition global, consent state, Tailwind invalide...)
- frontend patterns : +6 (click-to-load, toggle optimiste, Server Action retourne entité...)
- debug/postmortem : export{fn} ne crée pas de binding local
95_a_capitaliser.md remis à l'état initial vide.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
29 KiB
29 KiB
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)
- Séparation claire server state / client state
- Formulaire robuste avec validation et erreurs explicites
- Navigation réactive post-action async (React / Expo Router)
- Refresh idempotent sur store de liste paginée
- UI admin légère sur domaine existant
- Intégration tierce en mode link-out — préférer une page locale canonique
- Design Tokens natifs TypeScript (Expo / React Native)
- Tests de styles React Native sans renderer JSX
- Export des styles de composant pour réutilisation partielle
- Token typography par usage sémantique (React Native)
- Click-to-load strict pour les embeds tiers (iframe/widget)
- Toggle optimiste avec rollback (React Server Action)
- Server Action retournant l'entité — élimination de
router.refresh()sur create/edit - ESLint flat config avec presets Next.js (
eslint.config.mjs) - Grilles 2 colonnes FR/EN — mobile-first
Règle d’or
Si ce n’est pas confirmé comme fonctionnel et utile,
ça n’a 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 d’exigence que le backend.
Format standard d’un pattern (obligatoire)
Pattern : <Nom clair et précis>
- Objectif : ce que le pattern résout
- Contexte : type d’application / contraintes
- Quand l’utiliser : 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)
(contenu volontairement minimal, lisible, non-magique)
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)
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
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)
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
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)
- 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
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
- Ne pas oublier les dépendances du
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)
// ❌ 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)
// 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 useEffectavec dépendances explicites (state pertinent + isLoading + error)- Cas d'erreur géré (ne pas naviguer si error est défini)
useRefsi le trigger vient d'un callback externe (OAuth, deep link)- Convention documentée dans la story foundations / project-context avant les premiers écrans
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
loadMoreetrefresh. - 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
refreshetloadMore - demande une clé d’identité stable pour dédupliquer les items
- impose une discipline claire entre
Validation
- Validé le : 10-03-2026
- Contexte technique : React Native / Expo / Zustand / listes paginées
Implémentation (exemple minimal)
- 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
refreshetloadMoreont 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
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)
- 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
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)
- 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
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
spacingpour les dimensions de composants (voir risques)
- les fichiers TTF doivent être présents dans
Validation
- Validé le : 19-03-2026
- Contexte technique : Expo SDK 52+ / React Native / TypeScript — app-alexandrie story 0.1
Implémentation (exemple minimal)
// 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 constpour inférence exacte - Pas de Context React — constantes TypeScript pures
- Types dérivés (
ColorToken = keyof typeof colors) pour l’autocomplétion useFontsdans_layout.tsxavec guard!fontsLoaded- Fichiers TTF présents dans
assets/fonts/et documentés dans la story
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: nodesans renderer JSX. - Contexte : config Jest avec
transform: { ‘^.+\\.ts$’: ‘ts-jest’ }— les.tsxne 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
// 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
.spec.ts(node) : tokens, valeurs, logique pure.spec.tsx(config séparée avec renderer) : rendu visuel, interactions
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
// 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]} />
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.tsdans 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
fontWeightexplicite — c’est souvent le signe que le mauvais token a été choisi
- en review : chercher les usages sans
Validation
- Validé le : 19-03-2026
- Contexte technique : React Native / TypeScript — app-alexandrie story 0.4
Implémentation
// 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".
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 d’architecture 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 n’entre pas encore ici
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
- Le fallback (lien externe +
Validation
- Validé le : 21-03-2026
- Contexte technique : React / Next.js — app-template-resto
Implémentation
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)} />}
</>
);
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
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
}
}
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 (~500ms–2s é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 :
revalidatePathreste 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
// 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.
Pattern : ESLint flat config avec presets Next.js (eslint.config.mjs)
Synthèse
- Objectif : éviter les bugs de compatibilité de l’ancien
.eslintrcavec 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
// 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
},
},
];
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
<!-- ✅ 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>