mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-04-27 14:58:16 +02:00
322 lines
12 KiB
Markdown
322 lines
12 KiB
Markdown
# Frontend — Risques & vigilance : Général
|
|
|
|
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/risques/README.md` pour l'index complet.
|
|
|
|
---
|
|
|
|
<a id="risque-accessibilite-oubliee"></a>
|
|
## 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
|
|
|
|
---
|
|
|
|
<a id="risque-regex-globale-singleton-lastindex"></a>
|
|
## Regex globale `/g` en singleton — bug `lastIndex` stateful
|
|
|
|
### Risques
|
|
|
|
- Une regex avec flag `/g` ou `/y` définie comme constante au niveau module maintient un état `lastIndex` entre les appels
|
|
- `String.prototype.replace()` réinitialise `lastIndex`, mais `.test()` ou `.exec()` ne le font pas → bug stateful difficile à détecter, souvent introduit par un refactor ultérieur
|
|
|
|
### Symptômes
|
|
|
|
- `.test(str)` retourne alternativement `true` / `false` sur la même chaîne selon l'ordre d'appel
|
|
- Bug non reproductible en isolation, uniquement en séquence d'appels
|
|
|
|
### Bonnes pratiques / mitigations
|
|
|
|
```typescript
|
|
// ❌ RISQUÉ — regex globale partagée entre tous les appels
|
|
const LINK_PATTERN = /https?:\/\/\S+/gi;
|
|
function processLinks(content: string) {
|
|
return content.replace(LINK_PATTERN, ...); // OK today
|
|
// Mais si quelqu'un ajoute LINK_PATTERN.test(x) ailleurs → bug lastIndex
|
|
}
|
|
|
|
// ✅ SÛR — nouvelle instance à chaque appel, aucun état partagé
|
|
function makeLinkPattern(): RegExp {
|
|
return /https?:\/\/\S+/gi;
|
|
}
|
|
function processLinks(content: string) {
|
|
return content.replace(makeLinkPattern(), ...);
|
|
}
|
|
```
|
|
|
|
- **Règle** : les regex avec flag `/g` ou `/y` utilisées pour transformation de strings → toujours créer via une factory, jamais en singleton de module
|
|
|
|
- Contexte technique : TypeScript / React Native — app-alexandrie 24-03-2026
|
|
|
|
---
|
|
|
|
<a id="risque-alert-prompt-ios-only"></a>
|
|
## `Alert.prompt` iOS-only — fonctionnalité silencieusement cassée sur Android
|
|
|
|
### Risques
|
|
|
|
- `Alert.prompt` ne déclenche rien sur Android (retourne `undefined` silencieusement).
|
|
- Les tests unitaires passent (mock), mais le flux ne s'exécute jamais sur 50 % des devices en production.
|
|
|
|
### Symptômes
|
|
|
|
- Flux de saisie utilisateur qui fonctionne sur simulateur iOS mais est inactif sur Android
|
|
- Aucun message d'erreur côté dev ni côté utilisateur
|
|
|
|
### Bonnes pratiques / mitigations
|
|
|
|
1. Ne jamais utiliser `Alert.prompt` dans un projet Expo cross-platform.
|
|
2. Remplacer par une modale custom : `Modal` + `TextInput` React Native — portable, accessible, testable.
|
|
3. Wrapper le `TextInput` dans `KeyboardAvoidingView` avec `behavior={Platform.OS === 'ios' ? 'padding' : 'height'}`.
|
|
|
|
- Contexte technique : React Native / Expo cross-platform — app-alexandrie 31-03-2026
|
|
|
|
---
|
|
|
|
<a id="risque-primitive-ui-couplee-contexte-parent"></a>
|
|
## Primitive UI couplée au contexte parent (layout ou namespace métier)
|
|
|
|
### Risques
|
|
|
|
- Une primitive générique (`PageShell`, `ContentCard`, `SectionWrapper`) qui embarque des classes de surface, de largeur ou de namespace métier devient non réutilisable hors de son premier contexte
|
|
- Le couplage reste silencieux au lint et au typecheck, puis force l'ajout progressif de props `variant`, `layout`, `width` ou de classes externes contradictoires
|
|
|
|
### Symptômes
|
|
|
|
- La primitive applique directement des classes comme `.card`, `.card--dashboard`, `.dashboard__item`, `.profile__card`
|
|
- Le parent doit contourner le style natif de la primitive pour l'utiliser dans un autre écran
|
|
- Les classes `namespace__element` fuitent dans des composants supposés agnostiques du domaine
|
|
|
|
### Bonnes pratiques / mitigations
|
|
|
|
- Une primitive pose le squelette sémantique ; le parent pose la surface visuelle (card, width, background, espacement de contexte)
|
|
- Ne pas injecter de classes de namespace métier sur une primitive générique via `class`
|
|
- Si une variation réutilisable existe vraiment, l'exprimer via une API explicite et bornée (`tone`, `variant`) plutôt que par des classes métier ad hoc
|
|
- Contexte technique : Vue 3 / CSS modulaire — RL799_V2, 02-04-2026
|
|
|
|
---
|
|
|
|
<a id="risque-migration-partielle-composant-classes-legacy"></a>
|
|
## Migration partielle vers un composant standard — classes legacy conservées
|
|
|
|
### Risques
|
|
|
|
- La coexistence de classes legacy (`.primary`, `.ghost`, `.danger`) et de classes du nouveau composant (`.app-btn--primary`, `.app-btn--ghost`) crée une ambiguïté durable de convention
|
|
- Les nouveaux développements continuent d'utiliser l'ancien système faute de règle claire, ce qui ralentit la standardisation
|
|
|
|
### Symptômes
|
|
|
|
- Deux façons de produire la même affordance coexistent dans le même repo
|
|
- Un composant dédié existe, mais des liens ou boutons continuent d'utiliser les anciennes classes globales
|
|
|
|
### Bonnes pratiques / mitigations
|
|
|
|
- Lorsqu'un composant standardise une affordance, supprimer en même temps les classes CSS globales équivalentes
|
|
- Si un reliquat legacy doit rester temporairement, documenter explicitement son périmètre et sa date de sortie attendue
|
|
- En review, traiter toute nouvelle utilisation d'une classe legacy équivalente comme une régression de standardisation
|
|
- Contexte technique : Vue 3 / design system léger — RL799_V2, 02-04-2026
|
|
|
|
---
|
|
|
|
<a id="risque-aria-roles-sans-clavier"></a>
|
|
## ARIA roles sans comportement clavier associé
|
|
|
|
### Risques
|
|
|
|
- Poser `role="menu"` / `role="menuitem"` sur un composant sans implémenter le pattern clavier donne une fausse impression d'accessibilité
|
|
- Les rôles ARIA trompent les lecteurs d'écran et violent WCAG 2.1 (4.1.2 Name, Role, Value)
|
|
|
|
### Symptômes
|
|
|
|
- `role="menu"` sans fermeture via `Escape`
|
|
- Pas de navigation `ArrowUp` / `ArrowDown` ni de roving tabindex
|
|
|
|
### Bonnes pratiques / mitigations
|
|
|
|
Poser `role="menu"` / `role="menuitem"` implique obligatoirement :
|
|
- Fermeture via `Escape`
|
|
- Navigation via `ArrowUp` / `ArrowDown`
|
|
- Roving tabindex (`tabindex="0"` sur l'item actif, `-1` sur les autres)
|
|
- Focus automatique du premier item à l'ouverture
|
|
|
|
**Règle** : ne jamais poser un `role` ARIA de widget interactif sans implémenter le pattern clavier correspondant (cf. WAI-ARIA Authoring Practices)
|
|
|
|
- Contexte technique : Vue 3 / accessibilité — RL799_V2 03-04-2026
|
|
|
|
---
|
|
|
|
<a id="risque-duplication-logique-metier-monorepo"></a>
|
|
## Duplication de logique métier dans les composants UI (monorepo)
|
|
|
|
### Risques
|
|
|
|
- Dans un monorepo avec un package partagé (`shared`), les fonctions utilitaires métier (ex: conversion grade → rang) sont redéfinies localement dans les composants ou pages frontend
|
|
- Ce type de duplication silencieuse provoque des divergences à terme
|
|
|
|
### Symptômes
|
|
|
|
- Fonction `switch/case` ou mapping identique à une fonction déjà exportée par `shared`
|
|
- Même signature et même logique dans plusieurs fichiers de couches différentes (composant, page, service)
|
|
|
|
### Bonnes pratiques / mitigations
|
|
|
|
- Les fonctions utilitaires métier ne doivent jamais être redéfinies localement dans les composants ou pages frontend
|
|
- Importer systématiquement depuis le package partagé (`@monrepo/shared` ou équivalent) plutôt que de copier-coller la logique
|
|
- **Signal review** : grep des fonctions utilitaires existantes dans shared avant de valider un nouveau switch/case
|
|
|
|
- Contexte technique : Vue 3 / monorepo — RL799_V2 06-04-2026
|
|
|
|
---
|
|
|
|
<a id="risque-event-listeners-globaux-modales"></a>
|
|
## Event listeners globaux pour interactions modales
|
|
|
|
### Risques
|
|
|
|
- `window.addEventListener('keydown')` pour capturer Escape dans une modale crée un listener global qui peut confliter avec d'autres modales
|
|
- Le listener fuit si le composant est mal démonté
|
|
|
|
### Symptômes
|
|
|
|
- `window.addEventListener('keydown', handler)` dans un composant modale
|
|
- Cleanup dans `onBeforeUnmount` mais risque de fuite si le démontage échoue
|
|
|
|
### Bonnes pratiques / mitigations
|
|
|
|
- Utiliser `@keydown.escape` directement sur l'élément dialog avec `tabindex="-1"` + focus automatique à l'ouverture
|
|
- Élimine le besoin de cleanup et scope l'interaction au composant
|
|
- **Signal review** : dans tout composant modale, vérifier que les listeners clavier sont sur l'élément, pas sur `window`
|
|
|
|
- Contexte technique : Vue 3 / modales — RL799_V2 06-04-2026
|
|
|
|
---
|
|
|
|
<a id="risque-boutons-imbriques"></a>
|
|
## Boutons imbriqués dans les listes interactives
|
|
|
|
### Risques
|
|
|
|
- Un `<button>` ou `<a>` contenant un autre élément interactif (bouton, lien) est du HTML invalide
|
|
- Casse l'accessibilité et produit un comportement imprévisible selon les navigateurs
|
|
|
|
### Symptômes
|
|
|
|
- `<button>` conteneur avec un `<button>` enfant (ex: étoile favori dans une carte cliquable)
|
|
- Comportement de clic imprévisible, événements qui ne remontent pas correctement
|
|
|
|
### Bonnes pratiques / mitigations
|
|
|
|
- Utiliser un `<div>` conteneur avec des boutons séparés côte à côte
|
|
- Si toute la ligne doit être cliquable, séparer la zone de clic principale (bouton content) de l'action secondaire (bouton étoile/action)
|
|
- **Signal review** : dans tout composant liste avec actions inline, vérifier qu'aucun élément interactif n'est imbriqué dans un autre
|
|
|
|
- Contexte technique : HTML / accessibilité — RL799_V2 06-04-2026
|
|
|
|
---
|
|
|
|
<a id="risque-fire-and-forget-sans-feedback"></a>
|
|
## Fire-and-forget sans feedback sur actions non-critiques
|
|
|
|
### Risques
|
|
|
|
- Une action asynchrone non-critique (cache IndexedDB, analytics, sync) lancée en fire-and-forget sans feedback masque les échecs
|
|
- L'utilisateur croit que l'action est faite (ex: document disponible hors-ligne) alors qu'elle a échoué
|
|
|
|
### Symptômes
|
|
|
|
- `.then(...).catch(() => {})` sur une action secondaire
|
|
- `catch { /* ignore */ }` sans log ni feedback visuel
|
|
|
|
### Bonnes pratiques / mitigations
|
|
|
|
- Même si l'action est non-bloquante, afficher un feedback discret en cas d'échec (toast, badge absent)
|
|
- L'utilisateur doit pouvoir distinguer "fait" de "échoué silencieusement"
|
|
- **Signal review** : tout `.catch(() => {})` ou `catch { /* ignore */ }` mérite au minimum un log ou un feedback visuel
|
|
|
|
- Contexte technique : frontend / actions async — RL799_V2 07-04-2026
|
|
|
|
---
|
|
|
|
<a id="risque-monorepo-shim-js-desynchronise"></a>
|
|
## Monorepo ESM — shim runtime `.js` désynchronisé de l'index TypeScript
|
|
|
|
### Risques
|
|
- Le typecheck passe mais le runtime navigateur casse (`named export not found`).
|
|
|
|
### Symptômes
|
|
- Erreur Vite/browser sur export absent alors que `index.ts` est correct.
|
|
|
|
### Bonnes pratiques / mitigations
|
|
- Si un shim `.js` est maintenu, imposer une mise à jour miroir à chaque nouvel export.
|
|
- Ajouter un test/guard de cohérence exports TS vs JS shim.
|
|
|
|
- Contexte technique : monorepo / ESM shim runtime — RL799_V2 15-04-2026
|
|
|
|
---
|
|
|
|
<a id="risque-eslint-flat-tsconfigrootdir-manquant"></a>
|
|
## ESLint flat config TypeScript sans `tsconfigRootDir`
|
|
|
|
### Risques
|
|
- Erreurs de parsing massives en IDE/monorepo selon CWD d'exécution.
|
|
|
|
### Symptômes
|
|
- `No TsConfigRootDir` / `Cannot read tsconfig.json` alors que le build TS passe.
|
|
|
|
### Bonnes pratiques / mitigations
|
|
- Toujours définir `tsconfigRootDir: import.meta.dirname` quand `parserOptions.project` est utilisé.
|
|
- Redémarrer le serveur ESLint après correction.
|
|
|
|
- Contexte technique : tooling / ESLint flat config — RL799_V2 17-04-2026
|
|
|
|
---
|
|
|
|
<a id="risque-pwa-auth-cookie-cache"></a>
|
|
## PWA + auth cookie httpOnly — stratégie de cache non maîtrisée
|
|
|
|
### Risques
|
|
- Réponses sensibles servies depuis cache offline.
|
|
- Comportement d'auth incohérent entre réseau/cached.
|
|
|
|
### Symptômes
|
|
- Session/app state divergents après activation SW ou reprise réseau.
|
|
|
|
### Bonnes pratiques / mitigations
|
|
- Exclure explicitement les routes authentifiées sensibles du cache persistant.
|
|
- Définir une stratégie stricte par classe de route (auth, API privée, assets publics).
|
|
|
|
- Contexte technique : PWA / service worker / auth cookie — RL799_V2 18-04-2026
|
|
|
|
---
|
|
|
|
<a id="risque-pwa-beforeinstallprompt-tardif"></a>
|
|
## PWA install prompt — capture tardive de `beforeinstallprompt`
|
|
|
|
### Risques
|
|
- Événement perdu au cold boot, prompt jamais proposé.
|
|
|
|
### Symptômes
|
|
- Implémentation correcte en apparence mais aucun déclenchement sur Android.
|
|
|
|
### Bonnes pratiques / mitigations
|
|
- Installer l'écouteur le plus tôt possible dans le cycle d'initialisation.
|
|
- Ne pas baser la détection iOS uniquement sur l'UA (cas iPad en mode desktop).
|
|
|
|
- Contexte technique : PWA / install prompt — RL799_V2 18-04-2026
|
|
|
|
---
|