# Audit stratégique frontend — Veza **Date** : 2025-02-08 **Équipe fictive** : Staff Frontend Engineer, Principal Product Designer, Architecte logiciel senior, Tech Lead perf/a11y/DX, Product Manager. **Objectif** : Diagnostic sans complaisance, écart vs Discord/Spotify, feuille de route et recommandations actionnables. --- ## A. Diagnostic brutal mais honnête ### Niveau global de l’application **Verdict : intermédiaire à senior (6/10), avec des poches de niveau amateur et quelques bases solides.** Justification en une phrase : *Vous avez mis en place une structure feature-based, un design system documenté, des patterns (skeletons, optimistic UI, MSW), et une batterie de tests/stories, mais l’exécution est inégale, les règles du projet sont régulièrement violées (valeurs arbitraires, double source d’auth), et le ressenti “produit fini” est entamé par des correctifs visibles (fix-*.css, patches dans main.tsx) et une cohérence a11y/UX partielle.* - **Architecture** : Correcte en intention (features/, services/, stores/, router lazy), mais **artisanale** dans les détails (double source d’auth, callbacks no-op en route config, stores globaux vs feature stores). - **Qualité du code** : Bonne sur les parties auditées (auth, playlists, player), **dette structurelle** (duplication auth Context vs Store, ~130 fichiers avec valeurs Tailwind arbitraires malgré une règle explicite). - **Performance** : Bonne base (lazy routes, React Query, skeletons), mais pas de stratégie Suspense/streaming ni de priorisation explicite perçue vs mesurée. Voir *Checklist priorisation performance* ci-dessous. - **Design system** : **Solide** sur le papier (KŌDŌ v3, tokens, layout primitives, dark mode), **fragile** en pratique (règles de layout violées massivement, pas de lint pour les primitives). - **UX / micro-interactions** : Suffisante pour un produit interne, **insuffisante** pour un produit “premium” (feedback incohérent, états vides/erreur pas toujours traités de façon homogène). - **Accessibilité** : Présente (aria, roles sur une partie des composants), mais **bonus** et non pilier (a11y Storybook en `todo`, pas de politique WCAG claire). - **DX** : **Correcte** (docs internes, MSW, Storybook centralisé), mais **terrain miné** sur certains sujets (deux façons d’accéder à l’auth, nombreux fix-* à comprendre, seuils de coverage peut‑être non tenus). --- ### 5 forces réelles 1. **Design system pensé et documenté** `index.css` : palette sémantique, layout primitives (`max-w-layout-content`, `min-h-layout-main`, etc.), easings, keyframes, dark mode. Les règles du projet interdisent les valeurs arbitraires et imposent ces primitives — même si non respectées partout, la cible est claire. 2. **Storybook-first et MSW bien intégrés** Preview avec MSW en mode strict (`onUnhandledRequest: 'error'`), décorateur global (Theme, QueryClient, Router, Toast, Audio, Auth, i18n), pas d’import de providers dans les stories. Handlers MSW très complets (~1600 lignes). Environ 324 stories : bonne couverture de surface. 3. **Patterns de chargement et d’erreur explicites** `LOADING_STATES_PATTERN.md`, composant `LoadingState` (spinner / inline / skeleton / minimal), skeletons dédiés par page (UserProfilePage, CloudFileBrowser, CourseDetailView, etc.). Données servies par React Query avec états loading/error gérés au niveau page. 4. **Optimistic UI et utilitaires métier** `OPTIMISTIC_UPDATES.md` et `optimisticUpdates.ts` (createOptimisticUpdate, createArrayOptimisticUpdate, createToggleOptimisticUpdate). Utilisation dans playlists, likes, commentaires. Rollback et invalidation documentés. 5. **Surface de tests unitaires et d’intégration** ~186 fichiers `*.test.tsx` et ~100 `*.test.ts`, intégration auth/playlists/player, Vitest avec coverage (seuils 80 % configurés). E2E Playwright et visuels présents. Base saine pour monter en rigueur. --- ### 5 faiblesses structurelles majeures 1. **Double source de vérité pour l’auth** `AuthContext` + `useAuth()` (Storybook, ProtectedRoute, quelques pages) vs `authStore` (Zustand) + `useAuthStore()` (App, Sidebar, Login, etc.). `App.tsx` et hydratation utilisent le store ; le décorateur Storybook utilise le Context. Risque de désync, double appel à l’API, et confusion pour tout nouveau dev. Dette d’architecture prioritaire. 2. **Règles de layout systématiquement violées** La règle “pas de valeurs arbitraires (w-[…], z-[…], etc.)” et “utiliser les layout primitives” est explicite dans `.cursorrules` et `index.css`, mais environ **130 fichiers** utilisent `w-[`, `h-[`, `z-[`, etc. La Sidebar elle‑même utilise `w-64`, `z-[90]`, `z-[95]`, `left-6`, `top-20`. Le design system est sous‑exploité et la cohérence visuelle fragilisée. 3. **Correctifs applicatifs visibles et fragiles** `main.tsx` et `App.tsx` chargent plusieurs correctifs : `fix-input-focus.css`, `fix-login-form.css`, `fixDisplayIssues`, `fixInputFocus`. Cela indique des problèmes de conception (focus, formulaires) résolus par patch plutôt qu’à la source. Coût de maintenance et risque de régression élevé. 4. **Route config et couplage produit** `routeConfig.tsx` injecte des callbacks vides dans les éléments de route : `onCreateProduct={() => {}}`, `onNavigateTrack={() => {}}`. La routing ne doit pas porter de callbacks métier ; ces props devraient remonter d’un layout ou d’un contexte. Signe que la frontière entre routing et feature n’est pas claire. 5. **Accessibilité et qualité perçue en option** Storybook : `a11y: { test: 'todo' }`. Beaucoup de composants ont des `aria-*` ou `role`, mais de façon non systématique (ex. `KodoEmptyState` sans role/aria pour l’état vide, Sidebar sans `role="navigation"` ni `aria-label`). Pas de politique WCAG ni de critères de sortie a11y. L’accessibilité reste un “plus” et non un pilier, ce qui limite la maturité produit et l’inclusivité. --- ## B. Écart avec Discord & Spotify Tableau comparatif **technique et concret** (pas d’idéalisation). | Dimension | Discord (référence) | Spotify (référence) | Veza (état actuel) | Écart principal | |----------|--------------------|--------------------|--------------------|------------------| | **Architecture** | Domains clairs, state par feature, peu de global ; routing découplé du métier. | App shell léger, données par view, cache agressif. | Features + stores globaux + double auth (Context + Store). | Une seule source d’auth ; pas de callbacks dans la route config ; frontière nette layout/feature. | | **UX** | Feedback immédiat, états loading/empty/error partout, modales et transitions prévisibles. | Transitions fluides, skeletons systématiques, pas de “trous” visuels. | Skeletons et LoadingState présents mais pas partout ; empty/error variables ; correctifs CSS. | Checklist loading/empty/error par écran ; suppression des fix-* par refonte des composants concernés. | | **Performance** | Time to interactive court, lazy lourd, prioritisation du above-the-fold. | Streaming / progressive, cache et préchargement. | Lazy routes OK ; pas de Suspense boundary au niveau route ; pas de stratégie “perçu vs mesuré”. | Suspense + skeletons aux limites de route ; métriques perçues (LCP, INP) et objectifs chiffrés. | | **Design** | Cohérence stricte, tokens partout, pas de valeurs en dur. | Design system unique, thème et spacing prévisibles. | Tokens et primitives définis mais ~130 fichiers en arbitraire ; primitives peu utilisées. | Lint (ou rule) qui interdit les arbitraires ; migration progressive vers primitives. | | **Sensation de qualité** | App “qui ne bugue pas”, erreurs gracieuses, pas de demi-mesures visibles. | Polish, micro-interactions, pas de formulaires “cassés” en prod. | Correctifs visibles (fix-*), callbacks no-op, incohérences a11y. | Réduire la dette visible : un correctif = une issue de refonte ; zero no-op dans la config. | En résumé : **Veza a les briques (design system, patterns, tests, Storybook)** mais manque de **rigueur d’exécution** (une seule auth, respect des règles de layout, accessibilité et UX traitées comme non négociables) et de **finition** (plus de polish, moins de patches) pour se rapprocher de Discord/Spotify. --- ### Checklist priorisation performance À traiter dans cet ordre pour maximiser l’impact ressenti sans tout refactorer : 1. **First meaningful paint** - Éviter tout JS bloquant avant le premier rendu (splitting par route déjà en place). - S’assurer que le shell (header + sidebar ou placeholder) + un skeleton de contenu s’affichent sans attendre les données. 2. **Skeletons systématiques** - Chaque page qui fetch des données doit afficher un skeleton dédié (pas un spinner générique) pendant le chargement. - Déjà partiellement fait ; compléter les écrans manquants (voir Phase 2). 3. **Optimistic UI pour les actions fréquentes** - Like, follow, add to playlist, etc. : mise à jour immédiate + rollback si erreur. - Déjà documenté et utilisé ; étendre aux actions qui n’ont pas encore le pattern. 4. **Réduction des re-renders inutiles** - Vérifier les composants lourds (listes, tableaux) : memo, sélecteurs Zustand fins, ou découpage pour limiter les sous-arbre qui se re-rendent. - React Query évite déjà des refetch inutiles si `staleTime` est configuré ; auditer les queryKeys et invalidation pour éviter des cascades. 5. **Métriques et objectifs** - Mesurer LCP, INP (ou FID), TTI sur les parcours critiques (login, dashboard, lecture). - Fixer des objectifs (ex. LCP < 2.5 s sur 3G) et un process de revue en cas de régression. 6. **Suspense aux limites de route** - Envelopper les arbres de route dans `}>` pour que le navigateur puisse afficher le fallback sans attendre tout le bundle enfant. - Optionnel mais utile dès que le bundle par route grossit. --- ## C. Feuille de route de transformation ### Phase 1 — Fondations (ce qui doit être corrigé avant tout) **Objectifs** - Une seule source de vérité pour l’auth. - Arrêt des correctifs “magiques” et renforcement du respect du design system. **Actions concrètes** 1. **Unifier l’auth** - Décider : soit tout sur Zustand (authStore), soit tout sur Context (AuthProvider). Recommandation : **Zustand** (déjà utilisé par App, hydratation, Sidebar). - Migrer tous les usages de `useAuth()` vers `useAuthStore()` (ou l’inverse si vous choisissez Context). - Faire du AuthProvider un simple consommateur du store (s’il est gardé pour Storybook/compat). - Supprimer la duplication et documenter la décision dans un ADR. 2. **Supprimer les callbacks de la route config** - Retirer `onCreateProduct`, `onNavigateTrack`, etc. des éléments de route. - Fournir ces comportements via layout (contexte ou props depuis un parent commun) ou navigation programmatique + state. 3. **Remplacer les fix-* par des corrections à la source** - Pour chaque `fix-input-focus`, `fix-login-form`, etc. : identifier le composant (formulaire, input, focus trap) et corriger le focus, les états et le style dans le composant ou le design system. - Supprimer les imports de fix-* une fois le comportement correct par défaut. 4. **Lint / règle pour les valeurs arbitraires** - Règle ESLint (ou plugin Tailwind) interdisant les classes du type `w-[...]`, `h-[...]`, `z-[...]` pour layout/spacing (avec liste d’exceptions si besoin). - Introduire les exceptions progressivement et traiter les ~130 fichiers par batch (par répertoire ou par feature). **Risques** - Régression auth si la migration n’est pas testée (E2E login/logout, refresh, Storybook). - Régressions visuelles si on supprime les fix-* sans vérifier tous les écrans. **Indicateurs de réussite** - Un seul mécanisme d’auth utilisé partout ; plus d’import de fix-* dans `main.tsx`/`App.tsx`. - Aucun callback métier dans `routeConfig.tsx`. - Lint en place et au moins un premier batch (ex. `components/layout`) sans violations. --- ### Phase 2 — Stabilisation et cohérence **Objectifs** - Loading / empty / error systématiques. - Accessibilité traitée comme exigence, pas comme option. - Design system respecté (primitives, tokens). **Actions concrètes** 1. **Checklist par écran** - Pour chaque page/feature : état Loading (skeleton dédié ou générique), Empty (KodoEmptyState ou équivalent), Error (ErrorDisplay + retry). - Documenter la checklist et l’appliquer aux nouvelles features ; remédier les écrans existants sans état manquant. 2. **Accessibilité** - Remplacer `a11y: { test: 'todo' }` par une config réelle dans Storybook (règles axe-core, seuils). - Définir un niveau cible (ex. WCAG 2.1 AA) et une liste de composants critiques (formulaires, navigation, player). - Corriger Sidebar (role="navigation", aria-label), KodoEmptyState (role/aria pour empty state), et autres composants identifiés par un audit axe. 3. **Migration vers layout primitives** - Remplacer progressivement les valeurs arbitraires par les classes du design system (`max-w-layout-content`, `min-h-layout-main`, etc.) et l’échelle Tailwind. - Prioriser : layout (Sidebar, Navbar, content), puis composants partagés (modales, cartes). 4. **Optimistic UI et erreurs** - Vérifier que tous les usages d’optimistic updates ont un rollback et un feedback utilisateur en cas d’échec (toast, réversion visuelle). - Centraliser la logique d’erreur (ex. `apiToastHelper`, ErrorDisplay) pour éviter des traitements ad hoc. **Risques** - Scope trop large si on veut tout migrer d’un coup ; prioriser par trafic ou par criticité. **Indicateurs de réussite** - Chaque écran couvert par la checklist loading/empty/error. - Storybook a11y activé et 0 violation sur les stories des composants critiques. - Réduction mesurée du nombre de violations “arbitraires” (objectif chiffré par sprint). --- ### Phase 3 — Polish et excellence perçue **Objectifs** - Transitions et micro-interactions cohérentes. - Performance perçue (skeleton, priorisation) et mesurée (LCP, INP). - Réduction des “petits bugs” visuels et comportementaux. **Actions concrètes** 1. **Suspense et streaming** - Définir des Suspense boundaries au niveau des routes (ou des sous-arbres) avec fallback skeleton. - Vérifier que le premier paint utile (header + skeleton contenu) est rapide. 2. **Métriques et objectifs** - Suivre LCP, INP (ou FID), TTI. - Définir des objectifs (ex. LCP < 2.5 s sur 3G) et un process de revue si dégradation. 3. **Micro-interactions** - Utiliser les variables déjà définies (`--duration-fast`, `--ease-out`, etc.) partout où c’est pertinent (hover, focus, modales). - Passer en revue les CTA et les listes interactives pour un feedback immédiat (disabled + spinner ou état “loading” sur le bouton). 4. **Nettoyage produit** - Supprimer ou documenter tout code mort et composants non utilisés. - S’assurer qu’aucun no-op ou placeholder visible ne reste dans les parcours principaux. **Risques** - Sur-optimisation sans mesure ; garder les métriques comme garde-fou. **Indicateurs de réussite** - LCP/INP dans la cible sur les parcours critiques. - Pas de régression a11y ; design system respecté sur les nouvelles livraisons. --- ### Phase 4 — Scalabilité et long terme **Objectifs** - Code prêt pour 10× fonctionnalités et 10× équipe. - Dette technique visible et pilotée. - Expérimentations (A/B, feature flags) sans tout casser. **Actions concrètes** 1. **Frontières de domaines** - Clarifier ce qui vit dans `features/` vs `components/` (ex. pas de duplication forms auth dans les deux). - Règle simple : composants réutilisables et design system dans `components/` ; logique métier et pages dans `features/`. 2. **State et données** - Documenter quand utiliser React Query vs Zustand (server state vs client state). - Éviter de dupliquer des données serveur dans des stores globaux (library, etc.) sans stratégie d’invalidation claire. 3. **Feature flags et expérimentations** - Introduire un mécanisme léger de feature flags (build-time ou runtime) pour découpler déploiement et activation. - Tester une feature derrière flag sans impacter les parcours principaux. 4. **Documentation et onboarding** - Un README frontend à jour (structure, auth unique, design system, comment lancer Storybook et tests). - Créer ou mettre à jour `docs/STORYBOOK_CONTRACT.md` (référencé dans le code) : états requis (Loading, Error, Empty), pas de providers dans les stories, MSW pour l’API. **Risques** - Refactors trop larges ; privilégier des incréments et des ADR pour les décisions d’architecture. **Indicateurs de réussite** - Nouveau dev opérationnel (première feature livrée) en un temps cible (ex. < 2 jours). - Dette listée et priorisée (ex. backlog “dette” avec critères de sortie). --- ## D. Recommandations non évidentes - **Ne pas “nettoyer” tout le code arbitraire d’un coup.** Une équipe junior aurait tendance à tout remplacer en un big bang. Mieux vaut : lint qui bloque en *nouveau* code, et migration par zones (layout d’abord, puis composants partagés). Les 130 fichiers se traitent par lots avec revue visuelle et régression. - **Traiter l’auth comme un projet de migration, pas comme une tâche.** Faire un inventaire exhaustif (grep `useAuth` / `useAuthStore`), un plan de migration (ordre des fichiers), des tests E2E et Storybook qui valident login/logout/refresh, puis migrer avec une PR par zone. Un “quick fix” (tout basculer d’un coup) sans tests est risqué. - **Ne pas activer a11y dans Storybook sans budget de correction.** Activer axe avec des règles strictes du jour au lendemain va faire exploser les “failed”. Mieux : activer sur un sous-ensemble de stories (ex. composants ui/ + layout), corriger ces stories, puis élargir. a11y doit devenir un critère de merge sur les composants critiques. - **Documenter pourquoi chaque fix-* existe.** Avant de les supprimer, ajouter un commentaire ou un petit doc : “fix-login-form existe parce que …”. Cela évite de recréer le même patch plus tard et guide la refonte (quel composant doit prendre en charge le focus, etc.). - **Mettre des indicateurs de “maturité” par feature.** Une grille simple (loading / empty / error / a11y / tests / Storybook) par feature ou page. Permet de prioriser les remédiations et de ne pas prétendre que “tout est à niveau” alors que certaines zones sont encore fragiles. - **Réserver du temps “dette” dans chaque sprint.** Un projet “correct” reporte la dette ; un projet qui vise l’excellence en consacre une part fixe (ex. 20 %). Chaque sprint : X violations arbitraires en moins, Y écrans avec checklist complète, Z stories avec a11y passante. - **Éviter d’ajouter de nouveaux stores globaux “au fil de l’eau”.** Vous avez déjà auth, ui, library, cart, chat. Avant d’en ajouter un nouveau : vérifier si React Query + état local (ou store de feature) ne suffit pas. Les stores globaux multiplient les sources de vérité et compliquent le debugging et la cohérence. --- ## Synthèse - **Niveau actuel** : Intermédiaire à senior (6/10), avec de vraies bases (design system, patterns, MSW, tests, Storybook) et des faiblesses structurelles (double auth, violations de layout, correctifs, a11y en option). - **Écart Discord/Spotify** : Moins une question de stack que de **rigueur d’exécution** (une auth, respect des règles, a11y et UX non négociables) et de **finition** (moins de patches, plus de polish). - **Ordre recommandé** : Fondations (auth, route config, fix-*, lint) → Stabilisation (checklist loading/empty/error, a11y, primitives) → Polish (Suspense, métriques, micro-interactions) → Scalabilité (frontières, state, flags, doc). - **Ton** : Exigeant mais réaliste : le projet peut viser un niveau “world-class” à condition de traiter la dette identifiée comme un plan structuré et de ne pas ajouter de nouvelle dette sur l’auth et le layout.