Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y): - Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source for layout/shell (index.css), shadows (design-system.css), durations/easing. - Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height (max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500 replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes. - Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls, AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item, TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable. - ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary. - Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts. - Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories. - .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification. - apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual). Co-authored-by: Cursor <cursoragent@cursor.com>
280 lines
20 KiB
Markdown
280 lines
20 KiB
Markdown
# 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 `<Suspense fallback={<PageSkeleton />}>` 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.
|