334 lines
16 KiB
Markdown
334 lines
16 KiB
Markdown
|
|
# 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.ts`
|
|||
|
|
- `features/playlists/components/playlist-list/index.ts`
|
|||
|
|
- `features/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.tsx`
|
|||
|
|
- `features/profile/pages/user-profile-page/useUserProfilePage.ts` → `UserProfilePage.tsx`
|
|||
|
|
- `features/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) **et** `authStore` (Zustand) gèrent l'authentification — **duplication potentielle**. Le contexte gère `user` + `isAuthenticated`, le store aussi `isAuthenticated`. 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[]` et `messages{}` — 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 minute
|
|||
|
|
- `gcTime`: 5 minutes
|
|||
|
|
- `retry: false` (délégué au retry Axios)
|
|||
|
|
- `refetchOnWindowFocus: false`
|
|||
|
|
- `refetchOnReconnect: true`
|
|||
|
|
|
|||
|
|
### Patterns d'erreur
|
|||
|
|
|
|||
|
|
- **3 patterns coexistent** :
|
|||
|
|
1. Try/catch avec custom error types (trackService)
|
|||
|
|
2. Propagation directe (erreurs gérées par l'intercepteur)
|
|||
|
|
3. `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`)
|
|||
|
|
- `BrowserRouter` avec 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érifie `isAuthenticated` + `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 avec `DashboardLayout` [router/ProtectedLayoutRoute.tsx]
|
|||
|
|
|
|||
|
|
### 404 / Fallback
|
|||
|
|
|
|||
|
|
- ✅ Route `/404` avec composant `LazyNotFound`
|
|||
|
|
- ✅ Route `/500` avec composant `LazyServerError`
|
|||
|
|
- ✅ 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_DSN` défini)
|
|||
|
|
- Context enrichment via logger
|
|||
|
|
- ✅ Logger custom (`utils/logger.ts`) avec Sentry integration dynamique
|
|||
|
|
|
|||
|
|
### Fallback UI
|
|||
|
|
|
|||
|
|
- ✅ Skeleton loaders pour les chargements
|
|||
|
|
- ✅ `ErrorDisplay` component pour les erreurs
|
|||
|
|
- ✅ `ComingSoon` component pour les features non implémentées
|
|||
|
|
- ✅ Empty states gérés (composants `*Empty.tsx` dans 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é |
|