mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-06 21:41:42 +02:00
16 KiB
16 KiB
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 d’utilisation
- Chaque entrée doit dire :
- ce qui peut mal se passer,
- comment on le voit (symptômes),
- comment on le maîtrise (mitigation).
- Si c’est lié à une stack / version : on note le contexte.
Index
- Auth côté client
- Erreurs silencieuses / écrans blancs
- Mélange server state / client state
- Appels API en state local d’écran
- Performances : sur-renders + bundle
- Accessibilité oubliée (a11y)
- Catch silencieux — erreur inconnue sans feedback utilisateur
- Auto-reset d’un état dégradé sur toute réponse 2xx
- Refresh store en fire-and-forget après mutation
- Loading infini sur écran gated par droits distants
- Jest React Native — config node bloque les composants
.tsx - Bouton OAuth présent mais handler vide après refacto UI
- Double système d'espacement dans un monorepo Expo
- Dimensions d'image via tokens
spacing(React Native) - Écran détail Expo Router — store vide en deep link / reload
useEffectfetch — guard incomplet sur les états terminaux
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)
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
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 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/dataet 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
catchqui 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
catchdoit avoir une brancheelse(oudefault) qui affiche un feedback utilisateur explicite. - Contexte technique : React Native / Expo — 09-03-2026
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
isReadOnlyouisDegraded - 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
GETetHEADde 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
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
awaitexplicite 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
Loading infini sur écran gated par droits distants
Risques
- Un écran protégé reste bloqué dans un faux
loadingaprè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
entitlementsou autorisations laissés ànullaprès erreuruseEffectou logique d’entrée qui retrigger le fetch à chaque rendu
Bonnes pratiques / mitigations
- Distinguer explicitement
loading,error,ready - Ne pas réutiliser
nullcomme é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
Jest React Native — config node bloque les composants .tsx
Risques
SyntaxError: Cannot use import statement outside a modulelors de l’import d’un barrel.tsqui 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
.tsqui 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.tsxavec une config séparée (@testing-library/react-native+babel-jest) - Exporter le
StyleSheetde chaque composant pour le tester sans JSX (voir pattern dédié dans10_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
- 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
disabledavec 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 d’espacement dans un monorepo Expo
Risques
- Deux échelles d’espacement coexistent avec des noms différents pour des valeurs identiques (
Spacing.three = 16vsspacing.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 avecimport { 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 viderconstants/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.tsavecSpacing = { 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 d’image via tokens spacing (React Native)
Risques
- Si
spacing.hugechange 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.hugepour 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
É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
// 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
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 === 0etisLoading === 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