- Rewrite index.css with complete SUMI token system (dark + light themes) - All --sumi-* variables: backgrounds, surfaces, borders, text, pigments, spacing, radius, shadows, glass, scrollbar, motion, z-index, layout - shadcn/Radix semantic mapping (--background, --foreground, etc.) - Tailwind @theme mapping with new fonts (Inter, Space Grotesk, JetBrains Mono) - SUMI keyframe animations (sumi-fade-in, sumi-slide-up, sumi-scale-in, etc.) - Delete 11 redundant CSS files (design-system.css, design-tokens.css, button.css, card.css, input.css, badge-avatar.css, header.css, fix-input-focus.css, fix-login-form.css, visual-enhancements.css, premium-utilities.css) - Update main.tsx: single CSS import (index.css only) - Update ThemeProvider: data-theme attribute instead of .dark class toggle - Update index.html FOUC script: data-theme attribute Co-authored-by: Cursor <cursoragent@cursor.com>
16 KiB
PHASE A — ARCHITECTURE FRONTEND
Scope :
apps/web/src/Analyse exhaustive : structure, responsabilités, state, requêtes réseau, routing, erreurs
A1. Structure des dossiers
Séparation features / shared / core
Le projet adopte un modèle hybride en transition :
| Dossier | Rôle | Fichiers estimés | Verdict |
|---|---|---|---|
src/features/ |
Feature modules (auth, player, tracks, playlists, chat, etc.) | ~900+ | ✅ Bien structuré |
src/components/ |
Composants "legacy" / partagés | ~500+ | ⚠️ Méga-dossier, 40+ sous-dossiers |
src/components/ui/ |
Primitives UI (Button, Modal, Tabs, etc.) | ~200+ | ✅ Bonne séparation |
src/services/ |
Services partagés (API client, websocket, cache) | ~30+ | ✅ Bien isolés |
src/stores/ |
Stores Zustand globaux | ~6 fichiers | ✅ Correct |
src/hooks/ |
Hooks partagés | ~15+ | ✅ Bien isolés |
src/utils/ |
Utilitaires transverses | ~20+ | ✅ Correct |
src/schemas/ |
Schemas Zod | ~5 fichiers | ✅ Bonne pratique |
src/types/ |
Types globaux + generated | ~10 fichiers | ✅ Correct |
Problème majeur : dualité components/ vs features/
Duplication de domaines détectée :
| Domaine | components/ |
features/ |
Problème |
|---|---|---|---|
| Player | components/player/ |
features/player/ |
Deux emplacements concurrents |
| Settings | components/settings/ (7 sous-dossiers) |
features/settings/ |
Même domaine, deux endroits |
| Auth | components/auth/ |
features/auth/ |
Confusion sur le canonique |
| Social | components/social/ |
— | Devrait être dans features/ |
| Education | components/education/ |
— | Devrait être dans features/ |
| Commerce | components/commerce/ |
— | Devrait être dans features/ |
| Gamification | components/gamification/ |
— | Pas de feature associée |
| Studio | components/studio/ |
features/studio/ |
Deux emplacements |
| Search | components/search/ |
features/search/ |
Deux emplacements |
Verdict : La migration vers une architecture feature-first est en cours mais incomplète. Environ 60% des domaines métier ont leur module dans features/, mais des vestiges significatifs restent dans components/. [src/components/:ensemble] [src/features/:ensemble]
Barrel exports
Les barrel exports (index.ts) sont présents et cohérents dans les modules features decomposés :
features/tracks/components/comment-thread/index.tsfeatures/playlists/components/playlist-list/index.tsfeatures/player/components/player-bar/index.ts- Tous les sous-composants UI décomposés (accordion, dialog, tabs, etc.)
Manquants : les composants de components/ n'ont pas toujours de barrel export cohérent.
Composants groupés par feature vs par type
Le pattern dominant est par feature dans features/, et par type/domaine dans components/. L'architecture cible est clairement feature-first.
Routes colocalisées
Les routes sont séparées dans src/router/ — pas colocalisées avec les features. C'est acceptable avec React Router v6, mais les features contiennent leurs propres pages/ (ex: features/tracks/pages/, features/auth/pages/) — bonne pratique.
Dossiers morts / orphelins potentiels
| Dossier suspect | Raison |
|---|---|
components/views/ |
20+ sous-dossiers de "views" qui ne sont pas des routes — probablement des prototypes Storybook |
components/demo/ |
Dossier de démo — mort probable |
components/base/ |
Composants "base" — potentiellement redondant avec components/ui/ |
stories/ (racine) |
Stories globales + assets Storybook default — à nettoyer |
A2. Séparation des responsabilités
Tableau des composants > 100 lignes
| Composant | Lignes | Type | Responsabilités mélangées ? | Verdict |
|---|---|---|---|---|
components/developer/DeveloperDashboardView.tsx |
323 | Container | ✅ Oui — API calls, state, rendering | 🔴 REFACTOR |
features/dashboard/pages/DashboardPage.tsx |
329 | Container | ✅ Oui — Data fetching, state, rendering | 🔴 REFACTOR |
components/layout/Sidebar.tsx |
295 | Presentation | ❌ Non — Pure UI | ✅ OK |
components/developer/SwaggerUI.tsx |
279 | Hybrid | ✅ Oui — Config, error handling, rendering | 🟡 REFACTOR |
components/upload/metadata/MetadataForm.tsx |
274 | Container | ✅ Oui — State, modals, form logic | 🟡 REFACTOR |
features/chat/components/ChatMessages.tsx |
218 | Container | ✅ Oui — Store access, state, rendering | 🟡 REFACTOR |
app/App.tsx |
192 | Hybrid | ✅ Oui — Auth init, theme, i18n, CSRF | 🟡 REFACTOR |
features/tracks/pages/track-detail-page/TrackDetailPageInfo.tsx |
178 | Presentation | ❌ Non — Pure UI | ✅ OK |
components/layout/Header.tsx |
172 | Hybrid | ✅ Oui — Auth, theme, navigation | 🟡 REFACTOR |
features/player/components/GlobalPlayer.tsx |
154 | Container | ⚠️ Partiel — Near limit | ✅ OK (à surveiller) |
features/tracks/components/TrackSearch.tsx |
147 | Container | ✅ Oui — Search logic, API calls | 🟡 REFACTOR |
components/forms/LoginForm.tsx |
135 | Hybrid | ⚠️ Partiel — Form pattern acceptable | ✅ OK |
features/tracks/pages/track-detail-page/TrackDetailPageCoverAndActions.tsx |
133 | Presentation | ❌ Non — Pure UI | ✅ OK |
features/playlists/components/PlaylistHeader.tsx |
130 | Presentation | ❌ Non — Pure UI | ✅ OK |
features/chat/pages/ChatPage.tsx |
114 | Container | ✅ Oui — API calls, state, rendering | 🟡 REFACTOR |
features/tracks/components/track-filters/TrackFilters.tsx |
103 | Hybrid | ⚠️ Partiel — Uses hook | ✅ OK |
features/player/components/PlayerControls.tsx |
106 | Presentation | ❌ Non — Pure UI | ✅ OK |
features/tracks/components/TrackList.tsx |
291 | Presentation | ❌ Non — Pure UI, 18 props | ✅ OK |
Synthèse :
- 27% des composants > 100 lignes nécessitent un refactoring
- 40% mélangent des responsabilités (au moins partiellement)
- 97% ont leurs props typées — excellent
- Le pattern
useXxxPage()hook est bien adopté dans les features récentes
Bonne pratique observée
Les pages récentes utilisent un hook dédié pour la logique :
features/tracks/pages/track-detail-page/useTrackDetailPage.ts→TrackDetailPage.tsxfeatures/profile/pages/user-profile-page/useUserProfilePage.ts→UserProfilePage.tsxfeatures/library/pages/library-page/useLibraryPage.ts→LibraryPage.tsx
Ce pattern de séparation est exemplaire et devrait être généralisé aux composants plus anciens.
A3. Gestion d'état
Couches de state identifiées
| Couche | Technologie | Nombre | Scope |
|---|---|---|---|
| State local | useState |
~200+ fichiers | Composant |
| Context API | createContext |
4 contextes | Sous-arbre React |
| Store global | Zustand | 7 stores | Application |
| Server state | TanStack React Query | ~50+ hooks | Cache serveur |
| URL state | React Router | Params + query | URL |
Stores Zustand détaillés
| Store | Fichier | State contenu | Middlewares | Persisté |
|---|---|---|---|---|
| UI Store | stores/ui.ts |
theme, language, sidebarOpen, notifications | persist, devtools, broadcastSync | Oui (theme, language, sidebarOpen) |
| Library Store | stores/library.ts |
filters | persist, devtools | Oui (filters) |
| Cart Store | stores/cartStore.ts |
items[], cart operations | persist | Oui |
| Rate Limit Store | stores/rateLimit.ts |
Rate limit headers | persist | Oui |
| Auth Store | features/auth/store/authStore.ts |
isAuthenticated, isLoading, error | persist, broadcastSync | Oui (isAuthenticated) |
| Chat Store | features/chat/store/chatStore.ts |
userId, conversations[], messages, typingUsers, WS state | devtools, immer | Non |
| Player Store | features/player/store/playerStore.ts |
currentTrack, isPlaying, queue[], volume, repeat, shuffle | persist | Oui (volume, queue, etc.) |
Contextes React
| Contexte | Fichier | Contenu | Hook |
|---|---|---|---|
| AuthContext | context/AuthContext.tsx |
user, isAuthenticated, isLoading, login, register, logout | useAuth() |
| ToastContext | components/feedback/ToastProvider.tsx |
toasts[], addToast, removeToast | useToast() |
| ThemeContext | components/theme/ThemeProvider.tsx |
theme, setTheme | useTheme() |
| AudioContext | context/audio-context/AudioContext.tsx |
Audio playback context | useAudio() |
Diagnostic
Conflits entre couches :
- ⚠️ Auth :
AuthContext(Context API) etauthStore(Zustand) gèrent l'authentification — duplication potentielle. Le contexte gèreuser+isAuthenticated, le store aussiisAuthenticated. Source de vérité ambiguë. [context/AuthContext.tsx] [features/auth/store/authStore.ts] - ✅ Player : Zustand store uniquement — pas de conflit
- ✅ Server data : React Query pour les données serveur — bien séparé du state UI
State normalisé ? :
- ⚠️ Le chat store contient
conversations[]etmessages{}— normalisé par conversation, mais les messages sont dans un Map — acceptable pour le chat - ✅ Les données serveur (tracks, playlists) sont gérées par React Query (cache automatique)
- ❌ Pas de normalisation explicite des entités côté client (pas de store normalisé type normalizr)
Prop drilling > 3 niveaux :
- Pas de chaîne évidente détectée. Les données descendent via hooks (React Query) ou stores (Zustand), rarement par props directes sur plus de 2 niveaux. [DONNÉES INSUFFISANTES — nécessite analyse AST complète]
A4. Gestion des requêtes réseau
Architecture API
Client centralisé : services/api/client.ts (2 237 lignes) — Axios-based avec intercepteurs complets.
Features du client :
- Token refresh automatique sur 401 avec queue de requêtes
- CSRF token injection avec retry sur 403
- Validation requêtes/réponses via Zod schemas
- Retry logic : 3 retries, exponential backoff (1s → 10s max)
- Request deduplication pour GET
- Response caching pour GET
- Rate limit tracking
- Slow request detection (seuil 1000ms)
- Offline queue support
Inventaire des services
56 fichiers service/API répartis entre services/ (partagés) et features/*/services/ (feature-specific).
Tableau des patterns d'appels majeurs
| Fichier | Pattern | Loading | Error | Cancel | Typé | Centralisé |
|---|---|---|---|---|---|---|
services/api/client.ts |
Axios + interceptors | ✅ | ✅ Centralisé | ✅ AbortController | ✅ | ✅ |
services/api/auth.ts |
apiClient.post/get | Via RQ | Via interceptor | ❌ | ✅ | ✅ |
features/tracks/api/trackApi.ts |
apiClient wrappers | Via RQ | Via interceptor | ❌ | ✅ | ✅ |
features/playlists/services/playlistService.ts |
apiClient.get/post | Via RQ | Via interceptor | ❌ | ✅ | ✅ |
features/tracks/services/uploadService.ts |
apiClient.post + FormData | useState | try/catch custom | ❌ | ✅ | ✅ |
services/websocket.ts |
WebSocket natif | Manual | Reconnect auto | ❌ | ⚠️ any×8 | ✅ |
features/streaming/services/hlsService.ts |
HLS.js | Via RQ | Via HLS.js | ❌ | ✅ | Spécifique |
React Query configuration
staleTime: 1 minutegcTime: 5 minutesretry: false(délégué au retry Axios)refetchOnWindowFocus: falserefetchOnReconnect: true
Patterns d'erreur
- 3 patterns coexistent :
- Try/catch avec custom error types (trackService)
- Propagation directe (erreurs gérées par l'intercepteur)
handleApiServiceError()utility wrapper
- Incohérence notable : certains services gèrent les erreurs localement, d'autres délèguent entièrement
AbortController
- ✅ Implémenté dans le client (
createCancellableRequest(),createRequestWithTimeout()) - ⚠️ Faiblement adopté dans les services individuels
- ⚠️ React Query hooks n'utilisent pas systématiquement le
signal
A5. Routing
Solution
- React Router v6 (
react-router-dom ^6.22.0) BrowserRouteravec future flags activés [src/main.tsx:227]
Routes
| Type | Nombre | Lazy-loaded |
|---|---|---|
| Routes publiques (auth) | 5 | ✅ Toutes |
| Routes publiques standalone | 2 | ✅ Toutes |
| Routes protégées | 25 | ✅ Toutes |
| Routes d'erreur | 2 | ✅ Toutes |
| Catch-all (*) | 1 | Redirect → /404 |
| Total | 35 | 100% lazy |
Guards d'authentification
ProtectedRoute: vérifieisAuthenticated+TokenStorage.getAccessToken()→ redirect/login[components/auth/ProtectedRoute.tsx]PublicRoute: redirige les utilisateurs connectés vers/dashboard[router/PublicRoute.tsx]ProtectedLayoutRoute: wraps routes protégées avecDashboardLayout[router/ProtectedLayoutRoute.tsx]
404 / Fallback
- ✅ Route
/404avec composantLazyNotFound - ✅ Route
/500avec composantLazyServerError - ✅ Catch-all
*→ redirect/404 - ✅ Error boundaries wrappent les routes d'erreur
Lazy loading
100% des routes sont lazy-loaded via createLazyComponent [components/ui/lazy-component/lazyExports.ts:3-235]. Implémentation basée sur React.lazy() + Suspense + LazyErrorBoundary — excellente configuration.
Deep linking
- ✅ Routes bookmarkables (paths sémantiques :
/tracks/:id,/playlists/*,/u/:username) - ✅ Paramètres URL utilisés (
useParams,useSearchParams)
A6. Gestion des erreurs
Error boundaries
| Boundary | Fichier | Scope | Sentry |
|---|---|---|---|
| Main ErrorBoundary | components/ui/ErrorBoundary.tsx |
Routes + app | ✅ |
| Legacy ErrorBoundary | components/ErrorBoundary.tsx |
Backup | ✅ |
| LazyErrorBoundary | components/ui/lazy-component/LazyErrorBoundary.tsx |
Lazy components | ❌ |
| PlaylistErrorBoundary | features/playlists/components/PlaylistErrorBoundary.tsx |
Feature playlist | ❌ |
- ✅ Error boundary au niveau app (App.tsx wrappé)
- ✅ Error boundaries au niveau routes
- ⚠️ 2 implémentations ErrorBoundary (main + legacy) — duplication
Erreurs réseau
- ✅ Feedback utilisateur via toast notifications (react-hot-toast)
- ✅ Parsing centralisé (
parseApiError()) avec messages user-friendly - ✅ Catégorisation : network, validation, auth, rate_limit, server_error, timeout
- ⚠️ Inconsistance : inline error display vs toast-only selon les composants
Erreurs de formulaire
- ✅ React Hook Form + Zod pour la validation client
- ✅ Validation timing configurable (onBlur, onChange, onSubmit)
- ⚠️ Validation serveur : certains formulaires ne remontent pas les erreurs serveur dans les champs
Logging frontend
- ✅ Sentry configuré (
lib/sentry.ts) avec :- Browser tracing
- Session replay
- Error filtering (prod uniquement si
VITE_SENTRY_DSNdéfini) - Context enrichment via logger
- ✅ Logger custom (
utils/logger.ts) avec Sentry integration dynamique
Fallback UI
- ✅ Skeleton loaders pour les chargements
- ✅
ErrorDisplaycomponent pour les erreurs - ✅
ComingSooncomponent pour les features non implémentées - ✅ Empty states gérés (composants
*Empty.tsxdans les views)
SCORE ARCHITECTURE : 7/10
Points gagnés
| Point | Score | Justification |
|---|---|---|
| Routing | +1.0 | 100% lazy-loaded, guards, 404, deep linking — exemplaire |
| TypeScript strict | +1.0 | Config stricte complète, types omniprésents |
| Server state | +1.0 | React Query bien configuré, staleTime/gcTime cohérents |
| Error handling | +0.8 | Sentry + boundaries + parsing centralisé |
| Feature modules | +0.7 | features/ bien structuré pour les domaines principaux |
| API client | +0.8 | Client robuste : retry, dedup, cache, CSRF, offline queue |
| Hooks pattern | +0.7 | useXxxPage() pattern bien adopté dans les features récentes |
Points perdus
| Point | Score | Justification |
|---|---|---|
| Dualité components/features | -1.0 | 40% des domaines ont des composants dans les deux dossiers |
| Auth state duplication | -0.5 | AuthContext + authStore — source de vérité ambiguë |
as any endémique |
-0.5 | 706 as any casts — ESLint rule explicitement off |
| Client.ts monstre | -0.5 | 2 237 lignes — devrait être décomposé en modules |
| Inconsistance error handling | -0.3 | 3 patterns d'erreur coexistent dans les services |
| AbortController sous-utilisé | -0.2 | Implémenté mais faiblement adopté |