mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-06 21:41:42 +02:00
capitalisation
This commit is contained in:
@@ -15,4 +15,5 @@ Avant toute proposition frontend, identifie le fichier dont le nom et la descrip
|
||||
| `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 |
|
||||
| `react-native.md` | React Native, fetch, ScrollView, TextInput | Focus ring TextInput, contentInset iOS-only, fetch sans response.ok |
|
||||
| `general.md` | Accessibilité, regex, patterns transversaux | Accessibilité oubliée a11y, regex globale singleton lastIndex |
|
||||
|
||||
@@ -98,3 +98,24 @@ Erreurs courantes :
|
||||
- Toujours vérifier les classes custom/non-standard avec l'extension Tailwind IntelliSense
|
||||
|
||||
- Contexte technique : Tailwind CSS — app-template-resto 22-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-imports-morts-tokens-rn"></a>
|
||||
## Imports morts de tokens dans les composants React Native
|
||||
|
||||
### Risques
|
||||
|
||||
- Les imports de tokens abandonnés (ex : `fontWeight` après passage aux fontes nommées par variante) ne génèrent pas d'erreur TypeScript car le type est compatible avec les usages implicites
|
||||
- La migration de design system peut laisser des dépendances obsolètes indétectables au build
|
||||
|
||||
### Symptômes
|
||||
|
||||
- `import { fontWeight } from '@/tokens'` présent mais inutilisé — aucun lint warning sans règle dédiée
|
||||
- Tokens refactorisés encore référencés dans des composants après migration
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Activer `@typescript-eslint/no-unused-vars` et `no-unused-imports` dans la config ESLint mobile
|
||||
- Lors de toute migration de tokens, auditer les imports de chaque composant UI concerné
|
||||
- Contexte technique : React Native / ESLint — app-alexandrie, 25-03-2026
|
||||
|
||||
@@ -81,3 +81,55 @@ if (forums.length > 0 || isLoading || paywallRequired) return;
|
||||
- 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
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-tabs-name-label-inverses"></a>
|
||||
## Expo Router — mapping `name`/`label` des tabs inversés sans erreur
|
||||
|
||||
### Risques
|
||||
|
||||
- `<Tabs.Screen name="x">` route vers `app/(tabs)/x.tsx` — le `title` (label affiché) est totalement indépendant du routage
|
||||
- Un label "Communauté" sur `name="explore"` affiche `explore.tsx` sans aucune erreur de build ni de lint
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Le bon label est affiché, mais l'écran affiché est celui d'un boilerplate ou d'un autre module
|
||||
- Bug invisible jusqu'au test manuel de chaque onglet
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
- Lors de tout ajout ou renommage de tab, valider visuellement que chaque label correspond à l'écran attendu
|
||||
- Convention : aligner le `name` et le nom de fichier avec le wording du label (ex : `name="community"` → `community.tsx` → `title="Communauté"`)
|
||||
- Ajouter un test de smoke de navigation si la structure de tabs est critique
|
||||
|
||||
- Contexte technique : Expo Router — app-alexandrie, 25-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-cross-groupe-router-push"></a>
|
||||
## Expo Router — ne jamais préfixer le groupe dans `router.push`
|
||||
|
||||
### Risques
|
||||
|
||||
- `router.push('/(auth)/forgot-password')` depuis un écran `(tabs)/` peut échouer silencieusement ou lever une erreur selon la version d'Expo Router
|
||||
- La résolution des groupes de routes se fait par contexte de navigation — un préfixe de groupe explicite n'est pas un chemin de route valide
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Navigation vers un écran `(auth)/` qui n'aboutit pas ou lève une erreur au runtime
|
||||
- Fonctionne dans certaines versions d'Expo Router mais pas d'autres
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
```typescript
|
||||
// ❌ Anti-pattern — préfixe de groupe explicite
|
||||
router.push('/(auth)/forgot-password');
|
||||
|
||||
// ✅ Pattern correct — chemin sans groupe
|
||||
router.push('/forgot-password' as never);
|
||||
```
|
||||
|
||||
- Règle : les groupes `(auth)`, `(tabs)`, etc. sont des conventions d'organisation de fichiers, pas des segments de route — ne jamais les inclure dans les appels de navigation programmatique
|
||||
|
||||
- Contexte technique : Expo Router — app-alexandrie, 25-03-2026
|
||||
|
||||
90
knowledge/frontend/risques/react-native.md
Normal file
90
knowledge/frontend/risques/react-native.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Frontend — Risques & vigilance : React Native
|
||||
|
||||
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet.
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-focus-ring-textinput"></a>
|
||||
## Focus ring sur `TextInput` React Native
|
||||
|
||||
### Risques
|
||||
|
||||
- React Native n'a pas de pseudo-classe `:focus` — le focus visuel ne s'implémente pas en CSS
|
||||
- Tâche souvent marquée [x] sans vérification effective du state `focused`
|
||||
|
||||
### Symptômes
|
||||
|
||||
- `TextInput` sans indication visuelle de focus → accessibilité et UX dégradées
|
||||
- Story marquée done mais aucun handler `onFocus`/`onBlur` présent dans le composant
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
```typescript
|
||||
const [focused, setFocused] = useState(false);
|
||||
|
||||
<TextInput
|
||||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => setFocused(false)}
|
||||
style={[styles.input, focused && styles.inputFocused]}
|
||||
/>
|
||||
```
|
||||
|
||||
- Règle de review : toute story "focus ring" doit présenter un state `focused` + handlers + style conditionnel
|
||||
- Contexte technique : React Native — app-alexandrie, 25-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-contentinset-ios-only"></a>
|
||||
## `ScrollView.contentInset` — propriété iOS-only
|
||||
|
||||
### Risques
|
||||
|
||||
- `contentInset` n'est pas supporté sur Android — le contenu passe sous la bottom tab bar sans aucune erreur de build
|
||||
- Pattern fréquent quand on copie un pattern iOS existant dans un nouvel écran
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Sur iOS : rendu correct avec padding bottom
|
||||
- Sur Android : contenu masqué sous la bottom tab bar
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
```typescript
|
||||
// ❌ Anti-pattern — iOS-only, cassé sur Android
|
||||
<ScrollView contentInset={{ bottom: insets.bottom }}>
|
||||
|
||||
// ✅ Pattern correct — cross-platform
|
||||
<ScrollView contentContainerStyle={{ paddingBottom: insets.bottom }}>
|
||||
```
|
||||
|
||||
- Règle de review : auditer systématiquement tout `ScrollView` avec `contentInset` dans les PR concernant des écrans avec bottom navigation
|
||||
- Contexte technique : React Native / react-native-safe-area-context — app-alexandrie, 25-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-fetch-sans-response-ok"></a>
|
||||
## `fetch` sans vérification `response.ok` — erreurs non-2xx silencieuses
|
||||
|
||||
### Risques
|
||||
|
||||
- Sans vérification de `response.ok`, les réponses 404/500 retournent le JSON d'erreur sans exception → le code appelant ne sait pas que la requête a échoué
|
||||
- En cas de proxy qui retourne du HTML sur erreur (ex : 502 Bad Gateway), `response.json()` throw une `SyntaxError` cryptique plutôt qu'une erreur métier
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Store qui reçoit `{ error: { code, message } }` comme si c'était une réponse valide
|
||||
- `SyntaxError: JSON Parse error` inexpliquée lors des erreurs réseau
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
```typescript
|
||||
const response = await fetch(url, options);
|
||||
const json = (await response.json()) as T;
|
||||
if (!response.ok) {
|
||||
throw json; // throw le body d'erreur structuré pour un catch cohérent
|
||||
}
|
||||
return json;
|
||||
```
|
||||
|
||||
- Règle : tout `fetch` dans le http-client doit vérifier `response.ok` avant de retourner le JSON parsé
|
||||
- Contexte technique : React Native / fetch — app-alexandrie review 5.2, 27-03-2026
|
||||
@@ -278,3 +278,107 @@ async updateThread(forumSlug, threadId, body) {
|
||||
- **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
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-catch-objet-throw-vs-error"></a>
|
||||
## Catch Zustand : objet structuré throwé vs instance `Error`
|
||||
|
||||
### Risques
|
||||
|
||||
- Quand `apiRequest` propage une erreur HTTP en throwant le JSON brut `{ error: { code, message } }`, un catch limité à `err instanceof Error` laisse le message inaccessible → fallback générique ou message vide affiché
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Toast "Erreur de connexion au serveur" affiché même quand le serveur retourne un message explicite
|
||||
- `err.message` undefined alors que `(err as ApiError).error.message` contient la cause réelle
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
```typescript
|
||||
catch (err: unknown) {
|
||||
let message = 'Erreur de connexion au serveur.';
|
||||
if (err instanceof Error) {
|
||||
message = err.message;
|
||||
} else if (
|
||||
typeof err === 'object' && err !== null &&
|
||||
'error' in err &&
|
||||
typeof (err as { error: { message?: string } }).error?.message === 'string'
|
||||
) {
|
||||
message = (err as { error: { message: string } }).error.message;
|
||||
}
|
||||
set({ error: message, isLoading: false });
|
||||
}
|
||||
```
|
||||
|
||||
- Règle : tout store qui appelle `apiRequest` directement doit inspecter les deux cas — `Error` natif et objet structuré `{ error: { message } }`
|
||||
- Contexte technique : React Native / Zustand — app-alexandrie review 5.2, 27-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-flag-global-actions-paralleles"></a>
|
||||
## Flag booléen global pour des actions par entité — préférer `Set<string>`
|
||||
|
||||
### Risques
|
||||
|
||||
- Un flag `isLoading: boolean` global pour une action par item (follow, like, bookmark sur une liste) désactive tous les boutons dès qu'une action est en cours sur un seul item
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Cliquer "Suivre" sur une carte désactive tous les autres boutons "Suivre" de la liste
|
||||
- Impossible de déclencher deux actions en parallèle sur des entités différentes
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
```typescript
|
||||
// ❌ Anti-pattern — un seul flag pour toutes les instances
|
||||
followIsLoading: boolean;
|
||||
|
||||
// ✅ Pattern correct — un Set des IDs en cours
|
||||
followingInProgress: Set<string>;
|
||||
|
||||
// Selector
|
||||
isFollowInProgress: (userId: string) => get().followingInProgress.has(userId),
|
||||
|
||||
// Mutation
|
||||
set((state) => {
|
||||
const next = new Set(state.followingInProgress);
|
||||
next.add(targetUserId);
|
||||
return { followingInProgress: next };
|
||||
});
|
||||
// Après succès/erreur
|
||||
next.delete(targetUserId);
|
||||
```
|
||||
|
||||
- Règle : toute action async sur des items d'une liste (like, bookmark, follow, reaction) doit utiliser `Set<EntityId>` au lieu d'un `boolean` global
|
||||
- Contexte technique : React Native / Zustand — app-alexandrie review 5.3, 28-03-2026
|
||||
|
||||
---
|
||||
|
||||
<a id="risque-erreur-partagee-action-liste"></a>
|
||||
## Clé d'erreur partagée entre action et liste dans un store Zustand
|
||||
|
||||
### Risques
|
||||
|
||||
- Une clé d'erreur partagée entre une mutation (follow/unfollow) et un fetch de liste (followers, followings) affiche une erreur d'action périmée comme erreur de chargement
|
||||
- Ex : `followError: 'ALREADY_FOLLOWING'` stocké depuis une action précédente s'affiche dans l'écran "Abonnés" comme "Erreur de chargement"
|
||||
|
||||
### Symptômes
|
||||
|
||||
- Écran de liste affiche une erreur après une action précédente sans rapport direct
|
||||
- Bug intermittent difficile à reproduire, corrélé à l'ordre des actions utilisateur
|
||||
|
||||
### Bonnes pratiques / mitigations
|
||||
|
||||
```typescript
|
||||
// ❌ Anti-pattern — clé partagée
|
||||
followError: string | null;
|
||||
|
||||
// ✅ Pattern correct — clés séparées par nature
|
||||
followError: string | null; // erreur de followUser/unfollowUser
|
||||
followersError: string | null; // erreur de fetchFollowers
|
||||
followingsError: string | null; // erreur de fetchFollowings
|
||||
```
|
||||
|
||||
- Règle : dans un store qui gère à la fois des mutations et des listes paginées, chaque opération doit avoir sa propre clé d'erreur
|
||||
- Contexte technique : React Native / Zustand — app-alexandrie review 5.3, 28-03-2026
|
||||
|
||||
Reference in New Issue
Block a user