# Audit Deep Dive — Frontend Veza (vs standards SaaS type Discord/Spotify) **Date** : 2026-02-05 **Périmètre** : `apps/web` — structure des composants, styles (Tailwind / KŌDŌ), stories Storybook, décorateur global, accessibilité. **Référentiel** : cohérence design system, états d’interface, complexité des composants, illusion applicative, a11y. --- ## Synthèse exécutive Le frontend s’appuie sur un **système de design KŌDŌ** riche (tokens CSS, thème clair/sombre) et un **Storybook stabilisé** (0 erreur réseau/console, contrat documenté). Les écarts principaux par rapport à des standards type Discord/Spotify portent sur : **utilisation encore forte de valeurs arbitraires** malgré les tokens, **états d’interface incomplets** dans de nombreuses stories (Loading/Error/Disabled/Focus), **plusieurs composants “monolithes”** difficiles à tester et à faire évoluer, **décorateur Storybook** sans gestion des transitions ni du feedback tactile, et **a11y** partiellement couverte (addon présent, tests a11y en “todo”). Les problèmes sont classés en **Bloquant**, **Amélioration** et **Perfectionnement**. --- ## 1. Cohérence du système de design ### Ce qui est en place - **Tokens centralisés** dans `src/index.css` : palette (void, cyan, magenta, lime), sémantiques (primary, destructive, success, warning), radius (`--radius`), durées (`--duration-fast`, `--duration-normal`, etc.), easings, glows, glass. - **Thème Tailwind v4** via `@theme inline` : couleurs (`--color-primary`, `--color-sidebar-*`), radius (`--radius-sm` à `--radius-2xl`), typo (font-sans, font-mono, font-display). - **Composants UI de base** : `button.tsx` utilise des tokens (`bg-kodo-cyan`, `ring-kodo-cyan`, `focus-visible:ring-2`), CVA pour variants/sizes, pas de valeurs arbitraires dans les variants. ### Problèmes identifiés | Criticité | Constat | Détail | |-----------|--------|--------| | **Amélioration** | Valeurs arbitraires nombreuses | Plus de **170 fichiers** contiennent des classes type `h-[…]`, `w-[…]`, `p-[…]`, `gap-[…]`, `rounded-[…]`. Ex. : `h-[400px]`, `w-[300px]`, `min-h-[600px]`, `w-[500px]`, `h-[70px]` dans stories et composants. | | **Amélioration** | Stories comme source de dérive | Les stories utilisent souvent des conteneurs en `h-[600px]`, `w-[300px]` pour le rendu. Ces valeurs ne s’appuient pas sur les tokens (ex. `--radius-lg`, `h-96`, `max-w-3xl`). | | **Perfectionnement** | Échelle d’espacement | Les tokens d’espacement (4px, 8px, 16px, 24px…) sont partiellement reflétés en Tailwind ; des `p-4`, `p-8` coexistent avec des `p-[12px]` ou `gap-[11px]` ponctuels. | ### Recommandations - **Remplacer progressivement** les valeurs arbitraires par des tokens : par ex. `h-[400px]` → `h-[var(--height-panel)]` ou `min-h-96` si une scale est définie. - **Documenter une scale de hauteurs/largeurs** (cards, modals, sidebars) dans le design system et l’utiliser dans les stories. - **Linter / règle custom** (stylelint ou ESLint) pour limiter les `[…px]` / `[…]rem` hors tokens. ### Mise à jour (nettoyage systémique) **Layout primitives** ont été ajoutées dans `src/index.css` pour **App/Layouts** et **App/Pages** : - **Variables CSS** (`:root`) : `--layout-content-max-width` (100rem), `--layout-main-min-height`, `--layout-page-min-height` (37.5rem), `--layout-page-min-height-sm` (25rem), `--layout-story-decorator-min-height` (12rem). - **Classes utilitaires** (`@layer utilities`) : `.max-w-layout-content`, `.min-h-layout-main`, `.min-h-layout-page`, `.min-h-layout-page-sm`, `.min-h-layout-story`. - **Remplacements effectués** : `max-w-[1600px]` → `max-w-layout-content` (Layout, DashboardLayout, ChatPage) ; `min-h-[calc(100vh-64px)]` → `min-h-layout-main` (Layout) ; `min-h-[600px]` → `min-h-layout-page` (UserProfilePage, SettingsPage, PlaylistDetailPage) ; `min-h-[400px]` → `min-h-layout-page-sm` (LibraryPage, SessionsPage) ; `min-h-[200px]` (Navbar story) → `min-h-layout-story` ; `md:w-[400px]` (AudioPlayer) → `md:w-96` ; `min-h-[44px]` → `min-h-11` (PlaylistListPage) ; `min-w-[140px]` → `min-w-36` (SettingsPage) ; `h-[60%]` / `h-[40%]` → `h-3/5` / `h-2/5` (LibraryPage). Les ombres et valeurs viewport (ex. `h-[60vh]`) sont laissées en l’état. --- ## 2. Qualité des états d’interface (stories) ### Ce qui est en place - **Loading / Skeleton** : `LoadingState`, `Skeleton`, `TrackListSkeleton`, `PlayerLoading` ont des stories dédiées. - **Error** : `ErrorDisplay`, `PlayerError`, `AuthErrorMessage`, `ErrorBoundary`, `Alert` (variant error) sont couverts. - **Focus** : Le `Button` a `focus-visible:ring-2 focus-visible:ring-kodo-cyan` ; plusieurs composants UI (input, checkbox, select, tabs) ont du `focus:` ou `focus-visible:`. - **Disabled** : Présent dans ~23 fichiers de stories (Button, Checkbox, VolumeControl, etc.). ### Problèmes identifiés | Criticité | Constat | Détail | |-----------|--------|--------| | **Bloquant** | Addon a11y non exploité | Dans `.storybook/preview.tsx`, `a11y: { test: 'todo' }` — les tests a11y ne sont pas exécutés en CI ni utilisés comme garde-fou. | | **Amélioration** | Loading “factice” sur Button | La story `LoadingState` du Button est un `disabled` + texte "Loading..." sans spinner ni `aria-busy`. Les standards SaaS montrent un état de chargement explicite (spinner + désactivation). | | **Amélioration** | États manquants par type de composant | Beaucoup de composants “carte” ou “liste” n’ont qu’un état Default : pas de story **Empty**, **Error**, **Loading** ou **Disabled** (ex. plusieurs vues Playlist, Track, Chat, Commerce). | | **Amélioration** | Hover / Active peu documentés | Peu de stories nommées "Hover" ou "Active" ; les variants sont surtout visuels. Pas de démo systématique des états interactifs (hover/active) pour les boutons et liens. | | **Perfectionnement** | Focus visible inégal | Une soixantaine de fichiers de composants utilisent `focus:` ou `focus-visible:` ; d’autres (cards cliquables, list items, custom controls) peuvent ne pas avoir de ring/outline visible. | ### Recommandations - Passer **a11y.test** de `'todo'` à une config réelle (ex. `runOnly` avec règles WCAG 2.1 AA) et faire échouer le build/audit si des violations sont détectées. - Ajouter une story **Loading** réaliste au Button (spinner + `disabled` + `aria-busy`) et un état **Disabled** explicite partout où l’action peut être désactivée. - Pour les listes/cartes (Playlist, Track, Chat, Commerce), ajouter au moins **Empty** et **Error** (et **Loading** si async). - Documenter **Hover** / **Active** dans les stories des composants interactifs (boutons, nav, sidebar) pour valider la cohérence visuelle. --- ## 3. Complexité des composants (“composants dieux”) ### Composants les plus volumineux (≥ ~400 lignes) | Fichier | Lignes | Responsabilités identifiées | |---------|--------|------------------------------| | ~~`ProfileForm.tsx`~~ | ~~678~~ | **Refactorisé 2026-02-05** : module `profile-form/` avec `useProfileForm`, `ProfileCompletionCard`, `ProfileFormSkeleton`, schéma dédié. Stories : Default, Loading, Error, ProfileFormSkeleton. Re-export depuis `ProfileForm.tsx`. | | ~~`ProfileView.tsx`~~ | ~~602~~ | **Refactorisé 2026-02-05** : module `profile-view/` avec `useProfileViewData`, sous-composants (TrackCard, PlaylistCard, Sidebar, Overview, TracksTab, PlaylistsTab, AboutTab), `ProfileViewSkeleton`. Stories : Default, Loading, Error, ProfileViewSkeleton. Re-export depuis `ProfileView.tsx`. | | ~~`GearView.tsx`~~ | ~~559~~ | **Refactorisé 2026-02-05** : module `gear-view/` avec `useGearView`, `GearViewToolbar`, `GearViewSkeleton` ; props pour stories. Stories : Default, Loading, Empty, Error, GearViewSkeleton. Re-export depuis `GearView.tsx`. | | ~~`CommentThread.tsx`~~ | ~~547~~ | **Refactorisé 2026-02-05** : module `comment-thread/` avec hooks et sous-composants (voir ci-dessous). | | ~~`LazyComponent.tsx`~~ | ~~505~~ | **Refactorisé 2026-02-05** : module `lazy-component/` avec `LazyErrorFallback`, `LazyErrorBoundary`, `createLazyComponent`, `lazyExports`. Stories : LazyErrorFallback (Default, WithRetry, NoError), LazyComponent (Default, Loading). Re-export depuis `LazyComponent.tsx`. | | ~~`CloudFileBrowser.tsx`~~ | ~~503~~ | **Module existant** : `features/studio/components/cloud-file-browser/` — orchestrateur CloudFileBrowser.tsx (~188 lignes), FileToolbar, FileTable, FileGrid, FileGridCard, FileTableRow, CloudFileBrowserSkeleton, types. Ancien fichier monolithe `components/studio/CloudFileBrowser.tsx` supprimé. | | ~~`Search.tsx`~~ | ~~494~~ | **Refactorisé 2026-02-05** : module `features/search/components/search/` avec hooks et sous-composants (voir ci-dessous). | | ~~`UploadModal.tsx`~~ | ~~486~~ | **Refactorisé 2026-02-05** : module `upload-modal/` avec `useUploadModal`, `UploadModalDropzone`, `UploadModalFileDisplay`, `UploadModalProgress`, `UploadModalErrorAlert`, `UploadModalMetadataForm`. Stories : Default, Open. Re-export depuis `UploadModal.tsx`. | | ~~`file-upload.tsx`~~ | ~~478~~ | **Refactorisé 2026-02-05** : module `components/ui/file-upload/` avec `useFileUpload`, `FileUploadDropzone`, `FileUploadErrorList`, `FileUploadFileList`. Stories : Default, Empty, ImagesOnly, MaxSize, Disabled, Error, StateSimulation. Point d’entrée `file-upload/index.ts`. | | ~~`ChatSidebar.tsx`~~ | ~~469~~ | **Refactorisé 2026-02-05** : module `chat-sidebar/` avec hooks (`useChatConversations`, `useConversationActions`) et sous-composants (Header, Empty, Skeleton, ConversationItem). Stories : Default, Empty, Error, ChatSidebarSkeleton. | | ~~`select.tsx`~~ | ~~466~~ | **Refactorisé 2026-02-05** : module `components/ui/select/` avec `useSelect`, `SelectTrigger`, `SelectDropdownContent`, `SelectOptionItem`. Stories : Default, Empty, Disabled, Grouped, MultiSelect. Point d’entrée `select/index.ts`. | | ~~`NotificationsPage.tsx`~~ | ~~421~~ | **Refactorisé 2026-02-05** : module `features/notifications/components/notifications-page/` avec `useNotificationsPage`, Header, Filters, Item, Empty, Error, Skeleton. Stories : Default, Loading, Error, Empty. Re-export depuis `pages/NotificationsPage.tsx`. | | ~~`SearchPage.tsx`~~ | ~~235~~ | **Refactorisé 2026-02-05** : module `features/search/components/search-page/` avec `useSearchPage`, Header, Discovery, Empty, Error, Results, Skeleton. Stories : Default, Loading, Empty, Error. MSW : GET search → SearchResults. | | ~~`FileManagerView.tsx`~~ | ~~449~~ | **Refactorisé 2026-02-05** : module `components/views/file-manager-view/` avec `useFileManagerView`, Header, Toolbar, Table, Grid, Empty, Skeleton. Stories : Default, Loading, Empty. Re-export depuis `FileManagerView.tsx`. | | ~~`RegisterPage.tsx`~~ | ~~380~~ | **Refactorisé 2026-02-05** : module `features/auth/components/register-page/` avec `useRegisterPage`, RegisterPageForm, RegisterPageVerificationNotice, RegisterPageSkeleton. Stories : Default, Loading, WithError. Re-export depuis `pages/RegisterPage.tsx`. | | ~~`MonitoringDashboard.tsx`~~ | ~~423~~ | **Refactorisé 2026-02-05** : module `components/monitoring/monitoring-dashboard/` avec `useMonitoringDashboard`, `MonitoringDashboardContent`, `MonitoringDashboardSkeleton`, types. Stories : Default, Loading, Error. Layout primitive `min-h-layout-page-sm` pour skeleton et erreur. Re-export depuis `MonitoringDashboard.tsx`. | | ~~`PlaybackDashboard.tsx`~~ | ~~434~~ | **Refactorisé 2026-02-05** : module `features/streaming/components/playback-dashboard/` avec `usePlaybackDashboard`, StatsCard, TrendsCard, Charts, DetailedCard, Content, Skeleton. Stories : Default, Loading, Error, Empty. MSW : GET `/api/v1/tracks/:id/playback/dashboard`. Re-export depuis `PlaybackDashboard.tsx`. | | ~~`ShareLinkManager.tsx`~~ | ~~413~~ | **Refactorisé 2026-02-05** : module `components/share/share-link-manager/` avec `useShareLinkManager`, CreateForm, Item, Empty, Content, Skeleton. Props optionnelles `initialLinks`, `isLoading`. Stories : Default, Empty, Loading, Error. Re-export depuis `ShareLinkManager.tsx`. | | ~~`date-picker.tsx`~~ | ~~445~~ | **Refactorisé 2026-02-05** : module `components/ui/date-picker/` avec `useDatePicker`, `DatePickerTrigger`, `DatePickerCalendar`, types. Stories : SingleDate, DateRange, Disabled. Point d’entrée `date-picker/index.ts`. | | ~~`avatar-upload.tsx`~~ | ~~436~~ | **Refactorisé 2026-02-05** : module `components/ui/avatar-upload/` avec `useAvatarUpload`, `AvatarUploadDropzone`, `AvatarUploadActions`, `AvatarUploadSkeleton`. Stories : Default, WithExistingAvatar, Disabled, Large, Loading. MSW : POST avatar retourne `avatar_url`. Point d’entrée `avatar-upload/index.ts`. | | ~~`optimized-image.tsx`~~ | ~~407~~ | **Refactorisé 2026-02-05** : module `components/ui/optimized-image/` avec types, `generateImageSources`, `BlurPlaceholder`, `useImageFormatSupport`, `OptimizedImage`, `OptimizedImageSkeleton`, `useImagePreloader`, `ResponsiveImage`. Stories : Default, WithPlaceholder, ErrorState, Loading (skeleton). Re-export depuis `optimized-image.tsx`. | | ~~`DataList.tsx`~~ | ~~398~~ | **Refactorisé 2026-02-05** : module `components/ui/data-list/` avec types, `DataListSkeleton`, `DataListEmpty`, `DataListError`, `DataList`. Modal/Dropdown retirés (doublons de `modal.tsx`/`dropdown.tsx`). Stories : Default, Loading, Empty, Error, Skeleton. Re-export depuis `DataList.tsx`. | | ~~`components/player/AudioPlayer.tsx`~~ | ~~405~~ | **Refactorisé 2026-02-05** : module `components/player/audio-player/` avec types, `useAudioPlayerEffects`, `AudioPlayerTrackInfo`, `AudioPlayerControls`, `AudioPlayerProgress`, `AudioPlayerVolume`, `AudioPlayerSkeleton`, `AudioPlayer`. Stories : Default (avec mock store), Skeleton. Re-export depuis `AudioPlayer.tsx`. | | ~~`TrackFilters.tsx`~~ | ~~401~~ | **Refactorisé 2026-02-05** : module `features/tracks/components/track-filters/` avec `useTrackFilters`, `TrackFiltersHeader`, `TrackFiltersSearch`, `TrackFiltersGrid`, `TrackFiltersClear`, `TrackFiltersSkeleton`. Stories : Default, Collapsible, Loading. Re-export depuis `TrackFilters.tsx`. | | ~~`PlaylistList.tsx`~~ | ~~380~~ | **Refactorisé 2026-02-05** : module `features/playlists/components/playlist-list/` avec `usePlaylistList`, `PlaylistListToolbar`, `PlaylistListEmpty`, `PlaylistListError`. Skeleton existant `PlaylistListSkeleton`. Stories : Default, Grid, Empty (MSW), Loading (skeleton). Re-export depuis `PlaylistList.tsx`. | | ~~`dialog.tsx`~~ | ~~365~~ | **Refactorisé 2026-02-05** : module `components/ui/dialog/` avec types, `Dialog`, `DialogHeader`, `DialogBody`, `DialogFooter`, `DialogContent`, `DialogDescription`, `DialogTitle`, `DialogTrigger`, `DialogSkeleton`. Stories : Default, Alert, Composition, Loading. Re-export depuis `dialog.tsx`. | | ~~`AccountSettings.tsx`~~ | ~~362~~ | **Refactorisé 2026-02-05** : module `features/settings/components/account-settings/` avec `useAccountSettings`, `AccountSettingsErrorBanner`, `AccountSettingsPasswordCard`, `AccountSettingsExportCard`, `AccountSettingsDeleteCard`, `AccountSettingsSkeleton`. Stories : Default, Loading. Re-export depuis `AccountSettings.tsx`. | | ~~`TrackSearchFilters.tsx`~~ | ~~348~~ | **Refactorisé 2026-02-05** : module `features/tracks/components/track-search-filters/` avec `useTrackSearchFilters`, `TrackSearchFiltersBasic`, `TrackSearchFiltersAdvanced`, `TrackSearchFiltersSkeleton`. Stories : Default, Applied, Loading. Re-export depuis `TrackSearchFilters.tsx`. | | ~~`SessionsPage.tsx`~~ | ~~351~~ | **Refactorisé 2026-02-05** : module `features/auth/components/sessions-page/` avec `useSessionsPage`, `SessionsPageHeader`, `SessionsPageErrorBanner`, `SessionsPageRevokeAllButton`, `SessionsPageSessionItem`, `SessionsPageContent`, `SessionsPageEmpty`, `SessionsPageSkeleton`. Stories : Default, Loading, Empty, Error. MSW : GET/DELETE auth/sessions. Re-export depuis `pages/SessionsPage.tsx`. | | ~~`ProjectDetailView.tsx`~~ | ~~367~~ | **Refactorisé 2026-02-05** : module `components/studio/projects/project-detail-view/` avec `useProjectDetailView`, `ProjectDetailViewHeader`, `ProjectDetailViewTabs`, `ProjectDetailViewOverview`, `ProjectDetailViewFiles`, `ProjectDetailViewSettings`, `ProjectDetailViewSidebar`, `ProjectDetailViewSkeleton`. Stories : Default, Loading, Empty. Re-export depuis `projects/ProjectDetailView.tsx`. | | ~~`PlaybackHeatmap.tsx`~~ | ~~353~~ | **Refactorisé 2026-02-05** : module `features/streaming/components/playback-heatmap/` avec `usePlaybackHeatmap`, `PlaybackHeatmapHeader`, `PlaybackHeatmapStats`, `PlaybackHeatmapGrid`, `PlaybackHeatmapSkeleton`, `PlaybackHeatmapError`, `PlaybackHeatmapEmpty`. Stories : Default, CustomSegmentSize, Loading, Empty, Error. MSW : GET `/api/v1/tracks/:id/playback/heatmap`. Re-export depuis `PlaybackHeatmap.tsx`. | | ~~`ProductDetailView.tsx`~~ | ~~352~~ | **Refactorisé 2026-02-05** : module `components/marketplace/product-detail-view/` avec `useProductDetailView`, `ProductDetailViewHeader`, `ProductDetailViewGallery`, `ProductDetailViewInfo`, `ProductDetailViewLicenses`, `ProductDetailViewDescription`, `ProductDetailViewReviews`, `ProductDetailViewSimilar`, `ProductDetailViewSkeleton`. Stories : Default, Loading, Empty, WithReviews. Re-export depuis `ProductDetailView.tsx`. | | ~~`CourseLearningView.tsx`~~ | ~~353~~ | **Refactorisé 2026-02-05** : module `components/education/course-learning-view/` avec `useCourseLearningView`, `CourseLearningViewHeader`, `CourseLearningViewPlayer`, `CourseLearningViewTabs`, `CourseLearningViewSidebar`, `CourseLearningViewSkeleton`. Layout `min-h-layout-main`. Stories : Default, Loading, Empty, Complete. Re-export depuis `CourseLearningView.tsx`. | | ~~`CourseDetailView.tsx`~~ | ~~348~~ | **Refactorisé 2026-02-05** : module `components/education/course-detail-view/` avec `useCourseDetailView`, `CourseDetailViewHeader`, `CourseDetailViewTabs`, `CourseDetailViewSidebar`, `CourseDetailViewSkeleton`. Stories : Default, Loading, Empty, Enrolled. Re-export depuis `CourseDetailView.tsx`. | ### Problèmes identifiés | Criticité | Constat | Détail | |-----------|--------|--------| | **Amélioration** | Trop de responsabilités dans un seul fichier | Réduit : **CloudFileBrowser** est désormais un module (FileToolbar, FileTable, FileGrid, etc.) dans `features/studio/components/cloud-file-browser/`. | | **Amélioration** | Logique métier + UI mélangées | **CommentThread**, **ProfileForm**, **Search** : logique (appels API, état) et rendu (JSX) dans le même composant ; pas de découpage net en hooks + sous-composants. | | **Perfectionnement** | Réutilisabilité limitée | **GearView**, **ProfileView** : vues “pages” très spécifiques ; extraire des blocs (Header, Stats, List, Filters) permettrait des stories et des tests plus ciblés. | ### Recommandations - **CloudFileBrowser** : déjà découpé dans `features/studio/components/cloud-file-browser/` (FileToolbar, FileTable, FileGrid, CloudFileBrowserSkeleton, etc.) ; ancien monolithe `components/studio/` supprimé. - **CommentThread (fait 2026-02-05)** : logique extraite dans `useCommentReplies`, `useCommentActions` ; rendu délégué à `CommentThreadHeader`, `CommentThreadContent`, `CommentThreadActions`, `CommentReplyForm`, `CommentRepliesList` ; `CommentThreadSkeleton` pour l’état Loading. Stories : Default, WithReplies, DeeplyNested, Edited, EmptyReplies, LoadingReplies, ReplyError. MSW : PUT `/api/v1/comments/:id` ajouté. - **ProfileForm (fait 2026-02-05)** : module `features/user/components/profile-form/` avec `useProfileForm`, `ProfileCompletionCard`, `ProfileFormSkeleton`, schéma zod dédié. Stories : Default, Loading, Error, ProfileFormSkeleton. Re-export depuis `ProfileForm.tsx`. - **ProfileView (fait 2026-02-05)** : module `components/views/profile-view/` avec `useProfileViewData` ; sous-composants `ProfileViewTrackCard`, `ProfileViewPlaylistCard`, `ProfileViewSidebar`, `ProfileViewOverview`, `ProfileViewTracksTab`, `ProfileViewPlaylistsTab`, `ProfileViewAboutTab`, `ProfileViewSkeleton`. Stories : Default, Loading, Error, ProfileViewSkeleton. Re-export depuis `ProfileView.tsx`. - **Search (fait 2026-02-05)** : logique extraite dans `useSearchSuggestions` ; présentiel `SearchInput`, `SearchDropdown`, `SearchSkeleton` ; orchestrateur dans `Search.tsx`. Stories : Default, NoHistory, Loading, Empty, Error. Re-export depuis `components/search/Search`. - **ChatSidebar (fait 2026-02-05)** : module `features/chat/components/chat-sidebar/` avec `useChatConversations`, `useConversationActions` ; sous-composants `ChatSidebarHeader`, `ChatSidebarEmpty`, `ChatSidebarSkeleton`, `ConversationItem`. Stories : Default, Empty, Error, ChatSidebarSkeleton. Re-export depuis `ChatSidebar.tsx`. - **GearView (fait 2026-02-05)** : module `components/views/gear-view/` avec `useGearView` (filter, search, viewMode, itemsOverride, isLoading, error), `GearViewToolbar`, `GearViewSkeleton`. Stories : Default, Loading, Empty, Error, GearViewSkeleton. Re-export depuis `GearView.tsx`. - **LazyComponent (fait 2026-02-05)** : module `components/ui/lazy-component/` avec `LazyErrorFallback`, `LazyErrorBoundary`, `createLazyComponent`, `lazyExports`. Stories : LazyErrorFallback (Default, WithRetry, NoError), LazyComponent (Default, Loading). Layout primitive `min-h-layout-page-sm` pour l’erreur. Re-export depuis `LazyComponent.tsx`. - **UploadModal (fait 2026-02-05)** : module `features/upload/components/upload-modal/` avec `useUploadModal`, sous-composants (Dropzone, FileDisplay, Progress, ErrorAlert, MetadataForm). Stories : Default, Open. Re-export depuis `UploadModal.tsx`. - **file-upload (fait 2026-02-05)** : module `components/ui/file-upload/` avec `useFileUpload`, `FileUploadDropzone`, `FileUploadErrorList`, `FileUploadFileList`. Stories : Default, Empty, Error, Disabled, etc. Point d’entrée `file-upload/index.ts`. - **select (fait 2026-02-05)** : module `components/ui/select/` avec `useSelect`, `SelectTrigger`, `SelectDropdownContent`, `SelectOptionItem`. Stories : Default, Empty, Disabled, Grouped, MultiSelect. Point d’entrée `select/index.ts`. - **NotificationsPage (fait 2026-02-05)** : module `features/notifications/components/notifications-page/` avec `useNotificationsPage`, Header, Filters, Item, Empty, Error, Skeleton. Stories : Default, Loading, Error, Empty. MSW : GET notifications aligné avec notificationService. - **SearchPage (fait 2026-02-05)** : module `features/search/components/search-page/` avec `useSearchPage`, Header, Discovery, Empty, Error, Results, Skeleton. Stories : Default, Loading, Empty, Error. MSW : GET /api/v1/search → SearchResults. - **FileManagerView (fait 2026-02-05)** : module `components/views/file-manager-view/` avec `useFileManagerView`, Header, Toolbar, Table, Grid, Empty, Skeleton. Stories : Default, Loading, Empty. - **RegisterPage (fait 2026-02-05)** : module `features/auth/components/register-page/` avec `useRegisterPage`, Form, VerificationNotice, Skeleton. Stories : Default, Loading, WithError. - **MonitoringDashboard (fait 2026-02-05)** : module `components/monitoring/monitoring-dashboard/` avec `useMonitoringDashboard`, Content, Skeleton, état Error avec réessai. Stories : Default, Loading, Error. - **PlaybackDashboard (fait 2026-02-05)** : module `features/streaming/components/playback-dashboard/` avec `usePlaybackDashboard`, StatsCard, TrendsCard, Charts, DetailedCard, Skeleton. Stories : Default, Loading, Error, Empty. MSW playback dashboard. - **ShareLinkManager (fait 2026-02-05)** : module `components/share/share-link-manager/` avec `useShareLinkManager`, CreateForm, Item, Empty, Content, Skeleton. Stories : Default, Empty, Loading, Error. - **date-picker (fait 2026-02-05)** : module `components/ui/date-picker/` avec `useDatePicker`, Trigger, Calendar. Stories : SingleDate, DateRange, Disabled. - **avatar-upload (fait 2026-02-05)** : module `components/ui/avatar-upload/` avec `useAvatarUpload`, Dropzone, Actions, Skeleton. Stories : Default, WithExistingAvatar, Disabled, Large, Loading. - **optimized-image (fait 2026-02-05)** : module `components/ui/optimized-image/` avec types, `generateImageSources`, `BlurPlaceholder`, `useImageFormatSupport`, `OptimizedImage`, `OptimizedImageSkeleton`, `useImagePreloader`, `ResponsiveImage`. Stories : Default, WithPlaceholder, ErrorState, Loading. - **DataList (fait 2026-02-05)** : module `components/ui/data-list/` avec types, `DataListSkeleton`, `DataListEmpty`, `DataListError`, `DataList`. Stories : Default, Loading, Empty, Error, Skeleton. - **AudioPlayer components/player (fait 2026-02-05)** : module `components/player/audio-player/` avec `useAudioPlayerEffects`, `AudioPlayerTrackInfo`, `AudioPlayerControls`, `AudioPlayerProgress`, `AudioPlayerVolume`, `AudioPlayerSkeleton`. Stories : Default, Skeleton. - **TrackFilters (fait 2026-02-05)** : module `features/tracks/components/track-filters/` avec `useTrackFilters`, `TrackFiltersHeader`, `TrackFiltersSearch`, `TrackFiltersGrid`, `TrackFiltersClear`, `TrackFiltersSkeleton`. Stories : Default, Collapsible, Loading. - **TrackSearchFilters (fait 2026-02-05)** : module `features/tracks/components/track-search-filters/` avec `useTrackSearchFilters`, `TrackSearchFiltersBasic`, `TrackSearchFiltersAdvanced`, `TrackSearchFiltersSkeleton`. Stories : Default, Applied, Loading. - **SessionsPage (fait 2026-02-05)** : module `features/auth/components/sessions-page/` avec `useSessionsPage`, Header, ErrorBanner, RevokeAllButton, SessionItem, Content, Empty, Skeleton. Stories : Default, Loading, Empty, Error. MSW : GET/DELETE auth/sessions. - **ProjectDetailView (fait 2026-02-05)** : module `components/studio/projects/project-detail-view/` avec `useProjectDetailView`, Header, Tabs, Overview, Files, Settings, Sidebar, Skeleton. Stories : Default, Loading, Empty. - **PlaybackHeatmap (fait 2026-02-05)** : module `features/streaming/components/playback-heatmap/` avec `usePlaybackHeatmap`, Header, Stats, Grid, Skeleton, Error, Empty. Stories : Default, CustomSegmentSize, Loading, Empty, Error. MSW : GET heatmap. - **ProductDetailView (fait 2026-02-05)** : module `components/marketplace/product-detail-view/` avec `useProductDetailView`, Header, Gallery, Info, Licenses, Description, Reviews, Similar, Skeleton. Stories : Default, Loading, Empty, WithReviews. - **CourseLearningView (fait 2026-02-05)** : module `components/education/course-learning-view/` avec `useCourseLearningView`, Header, Player, Tabs, Sidebar, Skeleton. Layout `min-h-layout-main`. Stories : Default, Loading, Empty, Complete. - **CourseDetailView (fait 2026-02-05)** : module `components/education/course-detail-view/` avec `useCourseDetailView`, Header, Tabs, Sidebar, Skeleton. Stories : Default, Loading, Empty, Enrolled. - **PlaylistList (fait 2026-02-05)** : module `features/playlists/components/playlist-list/` avec `usePlaylistList`, `PlaylistListToolbar`, `PlaylistListEmpty`, `PlaylistListError`. Stories : Default, Grid, Empty (MSW), Loading (skeleton). - **dialog (fait 2026-02-05)** : module `components/ui/dialog/` avec Dialog, Header, Body, Footer, Content, Description, Title, Trigger, DialogSkeleton. Stories : Default, Alert, Composition, Loading. - **AccountSettings (fait 2026-02-05)** : module `features/settings/components/account-settings/` avec `useAccountSettings`, ErrorBanner, PasswordCard, ExportCard, DeleteCard, AccountSettingsSkeleton. Stories : Default, Loading. - Les autres composants volumineux restants : appliquer le même principe pour améliorer la testabilité et la maintenabilité. --- ## 4. Fidélité de l’illusion applicative (StorybookDecorator) ### Ce qui est en place - **Providers centralisés** : `I18nextProvider`, `ThemeProvider`, `QueryClientProvider`, `ToastProvider`, `AudioProvider`, `AuthProvider`, `MemoryRouter`. - **Router** : `parameters.router.initialEntries` pour les stories dépendant d’une route. - **Thème** : bascule light/dark via globals Storybook (backgrounds). - **Toasts** : en mode Storybook, toasts loggés en console (pas d’affichage intrusif). ### Problèmes identifiés | Criticité | Constat | Détail | |-----------|--------|--------| | **Amélioration** | Pas de gestion des transitions | Aucun wrapper `prefers-reduced-motion` ni fourniture de context pour désactiver les animations en Storybook. Les transitions CSS (`--duration-*`) sont appliquées telles quelles ; pas de mode “réduit” pour les tests ou l’accessibilité. | | **Amélioration** | Feedback tactile non simulé | Pas de décorateur ou paramètre pour simuler états tactiles (active, press) ou pointer (hover) de façon déterministe. Utile pour valider les états “pressed” / “hover” de boutons et cartes. | | **Perfectionnement** | Couleurs de fond en dur | Le décorateur utilise `#0a0a0a` et `#ffffff` en inline style au lieu des tokens (`var(--background)`). En cas d’évolution du thème, le conteneur Storybook peut diverger de l’app. | | **Perfectionnement** | Pas de “viewport” par défaut | Des viewports custom (mobile, tablet, desktop) existent ; le décorateur ne force pas de viewport par story. Pour un rendu type “app”, un conteneur largeur max (ex. 1440px) pourrait être optionnel. | ### Recommandations - Ajouter un **paramètre** (ex. `parameters.motion: 'reduce'`) et un wrapper qui applique `prefers-reduced-motion: reduce` (media query ou class) pour tester le comportement sans animations. - Utiliser **les tokens** pour le fond du conteneur : `background: 'var(--background)'` (et s’assurer que les variables sont bien appliquées dans l’iframe). - Optionnel : décorateur ou paramètre pour forcer un **état “interaction”** (hover/active) sur le premier élément focusable, pour valider le focus visible et les états actifs dans les stories. --- ## 5. Accessibilité (a11y) ### Ce qui est en place - **ARIA / rôles** : utilisés dans une soixantaine de composants (table, select, modal, tabs, pagination, breadcrumbs, tooltip, checkbox, progress, alert, etc.). - **Contrastes** : palette KŌDŌ en oklch avec séparation sémantique (primary, destructive, muted, etc.) ; fonds et textes prévus pour être lisibles. - **Addon a11y** : présent dans Storybook ; config actuelle `a11y: { test: 'todo' }`. - **Focus visible** : `button`, `input`, `checkbox`, `select`, `tabs` ont des styles `focus-visible:ring` ou équivalent. ### Problèmes identifiés | Criticité | Constat | Détail | |-----------|--------|--------| | **Bloquant** | Tests a11y non actifs | Tant que `test: 'todo'`, aucune violation a11y ne fait échouer l’audit ni le build. Risque de régressions (contraste, labels, rôles). | | **Amélioration** | Contraste à vérifier sur cas réels | Les combinaisons `muted-foreground` sur `muted`, ou textes sur “glass” / dégradés, n’ont pas été vérifiées automatiquement. Un audit manuel ou des tests a11y ciblés (contrast, label) sont recommandés. | | **Amélioration** | Zones cliquables non boutons | Cartes ou lignes entières cliquables sans `role="button"` ni `tabIndex={0}` ni gestion clavier (Enter/Space) — à vérifier dans les composants “card” et “list”. | | **Perfectionnement** | Annonces live (toasts, chargement) | Pas de revue systématique des `aria-live`, `aria-busy`, `aria-atomic` pour les toasts et états de chargement. | ### Recommandations - **Activer les tests a11y** dans Storybook : définir `a11y: { runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa'] } }` (ou équivalent) et intégrer le résultat à l’audit (ou au moins au build Storybook) pour faire échouer en cas de violations. - Vérifier les **cartes et listes cliquables** : soit `