Files
_Assistant_Lead_Tech/10_frontend_risques_et_vigilance.md
2026-03-20 13:59:42 +01:00

16 KiB
Raw Blame History

Front-end — Risques & vigilance

Ce fichier recense des risques front-end susceptibles de provoquer :

  • bugs subtils,
  • comportements inattendus,
  • dette technique rapide,
  • régressions UX/perf/a11y.

Dernière mise à jour : 20-03-2026


Règles dutilisation

  • 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 stack / version : on note le contexte.

Index


Auth côté client (mauvaise séparation des responsabilités)

Risques

  • Le front “décide” des permissions au lieu dappliquer un contrat backend
  • Affichage dactions interdites / fuite dinformations dans lUI
  • Tokens stockés de façon dangereuse (XSS)

Symptômes

  • Différences entre “ce que lUI permet” et “ce que lAPI 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 lUI ≠ 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)

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 derreur UI + fallback
  • Logging minimal côté client avec requestId/traceId quand possible

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

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 lepic 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” nest jamais rafraîchi
  • Code review qui force un refactor vers un store/cache au milieu dun 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)

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

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

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

} 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

Auto-reset dun état dégradé sur toute réponse 2xx

Risques

  • Le client sort trop tôt dun 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
  • LUI 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é quaprès une requête décriture réussie
  • Exclure GET et HEAD de la logique de reset
  • Conserver le mode dégradé tant quaucune mutation na prouvé le retour à la normale
  • Contexte technique : React Native / Expo — 10-03-2026

Refresh store en fire-and-forget après mutation

Risques

  • LUI 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 jusquau prochain reload

Bonnes pratiques / mitigations

  • await explicite du refresh si lUI dépend du résultat
  • Gestion derreur dédiée sur la phase de resynchronisation
  • Nutiliser le fire-and-forget que pour un effet secondaire réellement non bloquant
  • Contexte technique : React Native / Expo — 10-03-2026

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
  • Lutilisateur ne voit ni état derreur 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 dentré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 dentrée
  • Contexte technique : React Native / Expo / store dentitlements — 10-03-2026

Jest React Native — config node bloque les composants .tsx

Risques

  • SyntaxError: Cannot use import statement outside a module lors de limport dun barrel .ts qui réexporte des .tsx
  • Impossible dimporter 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

Bouton OAuth présent mais handler vide après refacto UI

Risques

  • LOAuth est silencieusement cassé sur le nouvel écran — zéro erreur au démarrage, zéro crash
  • LAC "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 napparaî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

Double système despacement dans un monorepo Expo

Risques

  • Deux échelles despacement coexistent avec des noms différents pour des valeurs identiques (Spacing.three = 16 vs spacing.base = 16)
  • Laudit "zéro hardcode" ne détecte pas linconsistance 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 lancien 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

Dimensions dimage via tokens spacing (React Native)

Risques

  • Si spacing.huge change pour une raison despacement, la taille de limage change silencieusement
  • Régression visuelle sans que personne ne réalise limpact — 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

// 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

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

// 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 davoir vérifié que le store a bien été peuplé
  • Contexte technique : Expo Router / Zustand — app-alexandrie story 4.1, 20-03-2026

useEffect fetch — guard incomplet sur les états terminaux

Risques

  • Si létat "zéro résultat intentionnel" (ex : paywallRequired) nest 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

// ❌ 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