capitalisation

This commit is contained in:
MaksTinyWorkshop
2026-03-28 12:50:07 +01:00
parent e9c1cb8ff9
commit ef99f2a2ca
6 changed files with 269 additions and 222 deletions

View File

@@ -29,228 +29,7 @@ Ce fichier ne doit donc **jamais devenir une documentation permanente**.
---
2026-03-25 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/frontend/risques/react-native.md
Pourquoi :
Bug récurrent détecté lors du theming : les imports non utilisés (`fontWeight`) ne génèrent pas d'erreur TypeScript dans React Native, car le type `fontWeight` est une string — ils passent silencieusement au lint et dans les tests. À signaler comme zone à surveiller lors de toute migration de tokens.
Proposition :
## Imports morts de tokens dans les composants UI (React Native)
Lors d'une migration de design system, 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. Vérifier systématiquement les imports non utilisés avec `eslint @typescript-eslint/no-unused-vars` ou `no-unused-imports` activé dans la config ESLint mobile.
---
2026-03-25 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/frontend/risques/expo-router.md
Pourquoi :
Anti-pattern découvert lors de la review theming-1 : labels et routes des tabs inversés sans erreur visible à la compilation. Le tab "Communauté" routait vers `explore.tsx` (boilerplate) au lieu de `community.tsx`. Aucun test ne couvrait ce mapping.
Proposition :
## Vérifier le mapping name/label des Tabs Expo Router
Dans Expo Router, `<Tabs.Screen name="x">` correspond au fichier `app/(tabs)/x.tsx`. Les labels (`title`) sont indépendants du routage — un label "Communauté" sur `name="explore"` affichera `explore.tsx` sans aucune erreur. Lors d'un refacto de navigation (ajout/renommage de tabs), valider visuellement que chaque label correspond bien à l'écran attendu. Ajouter un test de smoke ou de snapshot de la nav si possible.
---
2026-03-25 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/frontend/risques/react-native.md
Pourquoi :
Input focus ring (border au focus) non implémenté malgré la tâche marquée [x]. React Native ne propose pas de `:focus` CSS — il faut gérer `onFocus`/`onBlur` manuellement avec un state local. Ce pattern est souvent oublié ou marqué done à tort.
Proposition :
## Focus ring sur TextInput React Native
React Native n'a pas de pseudo-classe `:focus`. Pour implémenter un focus ring sur un `TextInput`, il faut :
1. `const [focused, setFocused] = useState(false)`
2. `onFocus={() => setFocused(true)}` / `onBlur={() => setFocused(false)}` sur le `TextInput`
3. Appliquer un style conditionnel sur le container : `[styles.container, focused && styles.containerFocused]`
Ne jamais marquer cette tâche [x] sans avoir vérifié la présence du state `focused` et des handlers.
---
2026-03-25 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/frontend/risques/react-native.md
Pourquoi :
Dette repérée sur login.tsx : écran en anglais alors que tout le reste du flux auth est en français. Aucune erreur de build ou de lint — passe complètement inaperçu jusqu'au test manuel.
Proposition :
## Cohérence de langue dans les écrans auth (React Native / Expo Router)
Les textes UI en dur ne sont pas vérifiés par TypeScript ni par les tests unitaires. Une dette de localisation (anglais vs français) peut s'accumuler silencieusement écran par écran. Points de vigilance :
- Vérifier chaque nouvel écran auth lors de la code review : titres, labels de boutons, messages d'erreur, placeholders
- Les tests de snapshot Storybook ou les reviews visuelles sont le seul filet pour ce type de dette
- Prioriser la correction avant la mise en prod (expérience utilisateur incohérente)
---
2026-03-25 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/frontend/risques/expo-router.md
Pourquoi :
Anti-pattern découvert en review 5.1b : `router.push('/(auth)/forgot-password')` depuis un tab `(tabs)/` — navigation cross-groupe avec préfixe de groupe explicite. Peut échouer selon la version d'Expo Router. Le reste du projet utilise `/forgot-password` sans préfixe de groupe.
Proposition :
## Navigation cross-groupe Expo Router — ne jamais préfixer avec le groupe dans router.push
Depuis un écran dans `(tabs)/`, utiliser `router.push('/(auth)/forgot-password')` peut échouer silencieusement ou lever une erreur selon la version d'Expo Router, car la résolution des groupes de routes se fait par contexte de navigation. Toujours utiliser le chemin sans groupe explicite : `router.push('/forgot-password' as never)`. Les groupes `(auth)`, `(tabs)` etc. sont des conventions d'organisation de fichiers, pas des segments de route réels.
---
2026-03-25 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/frontend/risques/react-native.md
Pourquoi :
Pattern `contentInset` iOS-only trouvé dans deux écrans (settings.tsx, profile.tsx déjà existant) — aucune erreur au build, invisible sur iOS, cassé sur Android (contenu sous la tab bar).
Proposition :
## `contentInset` est iOS-only sur ScrollView React Native
`ScrollView.contentInset` n'est pas supporté sur Android. Pour gérer le padding bottom autour d'une bottom tab bar, utiliser `contentContainerStyle={{ paddingBottom: insets.bottom }}` à la place. Pattern à auditer systématiquement lors de la review de tout nouvel écran avec ScrollView + bottom navigation.
---
2026-03-27 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/frontend/risques/react-native.md
Pourquoi :
Anti-pattern récurrent dans les stores Zustand mobile : le catch d'une erreur throwée depuis `apiRequest` (qui throw un objet JSON, pas une Error) ne donne pas de message lisible si on fait `err instanceof Error` uniquement. Détecté lors de la review 5.2.
Proposition :
## Catch Zustand store — objet throwé vs Error
Quand `apiRequest` propage une erreur HTTP en throwant le JSON brut `{ error: { code, message } }`, le catch Zustand ne peut pas se limiter à `err instanceof Error`. Le pattern robuste :
```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 });
}
```
Appliquer ce pattern dans tous les stores qui appellent `apiRequest` directement.
---
2026-03-27 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/frontend/risques/react-native.md
Pourquoi :
`apiRequest` (http-client.ts) ne vérifiait pas `response.ok` — retournait le JSON brut même sur 404/500. Erreur non throwée → `res.error` dépendait du format de l'API NestJS. En cas de réponse non-JSON (proxy 502 HTML), `response.json()` throwait une SyntaxError cryptique. Détecté review 5.2.
Proposition :
## `fetch` sans vérification `response.ok` — toujours throw sur non-2xx
Après tout `fetch()`, toujours vérifier `response.ok` avant de retourner le JSON :
```typescript
const json = (await response.json()) as T;
if (!response.ok) {
throw json; // throw le body d'erreur structuré
}
return json;
```
Sans ce check, les erreurs 404/500 passent silencieusement si le service appelant ne vérifie que le body. Risque accru avec les proxies qui retournent du HTML sur erreur.
---
2026-03-28 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/frontend/risques/state.md
Pourquoi :
Anti-pattern découvert en review 5.3 : `followIsLoading: boolean` global dans un store Zustand bloquait tous les boutons "Suivre" de l'annuaire simultanément. Un seul flag booléen ne peut pas représenter des états par entité.
Proposition :
## État de chargement par entité dans les stores Zustand — préférer un Set<string>
Quand plusieurs instances d'un même composant peuvent déclencher une action async en parallèle (ex: bouton "Suivre" sur chaque carte d'une liste), un flag booléen global `isLoading: boolean` est insuffisant — il désactive toutes les instances dès qu'une action est en cours.
Pattern correct :
```typescript
// État
followingInProgress: Set<string>; // userId actuellement en cours de follow/unfollow
// Selector
isFollowInProgress: (userId: string) => boolean;
// dans le store : (userId) => 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)
```
Appliquer ce pattern pour toute action async dans une liste (like, bookmark, follow, reaction, etc.).
---
2026-03-28 — app-alexandrie
FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/frontend/risques/state.md
Pourquoi :
Review 5.3 : `followError` partagé entre les actions follow/unfollow et les erreurs de chargement des listes followers/followings. Un ancien `followError` de type "ALREADY_FOLLOWING" pouvait s'afficher comme "Erreur de chargement" dans l'écran followers.
Proposition :
## Séparer les erreurs d'action et les erreurs de liste dans les stores Zustand
Quand un store gère à la fois des actions (mutations) et des listes (fetches paginés), ne pas partager la même clé d'erreur. Nommer explicitement :
```typescript
followError: string | null; // erreur de followUser/unfollowUser
followersError: string | null; // erreur de fetchFollowers/loadMoreFollowers
followingsError: string | null; // erreur de fetchFollowings/loadMoreFollowings
```
Les écrans de liste doivent afficher leur propre erreur (`followersError`) et non l'erreur d'action globale (`followError`).
_Aucune entrée pour le moment_
---

View File

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

View File

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

View File

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

View 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

View File

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