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 |
Formulaire profil, validation, champs multiples, sous-vues, édition avatar, etc. |
ProfileView.tsx |
602 |
Layout profil, onglets, stats, contenu dynamique, modales. |
GearView.tsx |
559 |
Vue “équipement”, liste, filtres, actions, détail. |
CommentThread.tsx |
547 |
Arbre de commentaires, réponses, édition, suppression, chargement paginé. |
LazyComponent.tsx |
505 |
Lazy load, fallback, erreur, timeout, plusieurs modes. |
CloudFileBrowser.tsx |
503 |
Vue liste/grid, recherche, tri, filtres par tags, sélection, modales (metadata, watermark), détail fichier, navigation. |
Search.tsx |
494 |
Recherche globale, suggestions, historique, catégories, résultats. |
UploadModal.tsx |
486 |
Upload, drag & drop, progression, métadonnées, validation. |
file-upload.tsx |
478 |
Gestion fichiers, preview, validation, multi-type. |
ChatSidebar.tsx |
469 |
Liste salons, création, recherche, états (non lu, favoris). |
select.tsx |
466 |
Select avec recherche, multi, combobox, keyboard nav. |
Problèmes identifiés
| Criticité |
Constat |
Détail |
| Amélioration |
Trop de responsabilités dans un seul fichier |
CloudFileBrowser : vue (list/grid), recherche, tri, filtres, tags, sélection, 2 modales, détail fichier, appels service. Difficile à tester unitairement et à faire évoluer (ex. ajouter un état “empty” ou “error” propre). |
| 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
- Découper CloudFileBrowser : extraire
FileBrowserToolbar, FileBrowserGrid, FileBrowserList, FileTagFilter, et un hook useCloudFiles (loading, error, data). Chaque bloc avec sa story (Default, Loading, Empty, Error).
- Extraire la logique de CommentThread (hooks
useComments, useCommentActions) et garder un composant de rendu (thread + réponses) plus simple.
- ProfileForm / ProfileView : séparer par section (Avatar, Infos, Sécurité, etc.) en composants ou sous-vues avec leurs propres stories.
- Appliquer le même principe (un rôle par composant, données via props ou hooks) pour Search, ChatSidebar, UploadModal, select afin d’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
<button> ou div avec role="button", tabIndex={0}, et gestion onKeyDown (Enter/Space).
- Planifier un audit contraste (manuel ou outil) sur les écrans clés (login, player, sidebar, modales) et documenter les ratios pour les combinaisons utilisées.
6. Tableau de synthèse par criticité
| Criticité |
Thème |
Action prioritaire |
| Bloquant |
a11y |
Activer les tests a11y dans Storybook (retirer test: 'todo', définir des règles WCAG et les faire échouer le job si besoin). |
| Amélioration |
Design system |
Réduire les valeurs arbitraires (h-[…], w-[…]) en les remplaçant par des tokens ou des utilitaires Tailwind cohérents. |
| Amélioration |
États d’interface |
Compléter les stories : Loading réaliste (Button), Empty/Error/Loading pour listes et cartes, états Disabled et si possible Hover/Active. |
| Amélioration |
Composants dieux |
Découper CloudFileBrowser, CommentThread, ProfileForm/ProfileView en sous-composants et hooks testables. |
| Amélioration |
Illusion applicative |
Utiliser les tokens pour le fond du décorateur ; ajouter support reduced-motion et optionnellement états hover/active. |
| Amélioration |
a11y |
Vérifier cartes/listes cliquables (rôle, clavier) ; audit contraste sur écrans clés. |
| Perfectionnement |
Design system |
Scale d’espacement/hauteurs documentée ; linter pour limiter les valeurs arbitraires. |
| Perfectionnement |
États d’interface |
Stories Hover/Active explicites ; revue focus visible sur tous les contrôles. |
| Perfectionnement |
Composants |
Extraire blocs réutilisables des vues “page” (GearView, ProfileView, Search). |
| Perfectionnement |
a11y |
Revue aria-live / aria-busy pour toasts et chargement. |
7. Prochaines étapes suggérées
- Court terme : Activer a11y dans Storybook et corriger les violations bloquantes ; ajouter les stories Empty/Error (et Loading si pertinent) aux composants listes/cartes les plus utilisés.
- Moyen terme : Découper 2–3 composants “dieux” (ex. CloudFileBrowser, CommentThread) et aligner les nouvelles stories sur les tokens (réduction des valeurs arbitraires).
- Long terme : Documenter une grille de hauteurs/largeurs et une checklist “états d’interface” pour toute nouvelle story ; intégrer reduced-motion et feedback tactile dans le décorateur.
Rapport généré dans le cadre de l’audit frontend Veza — Storybook stabilisé, contrat dans docs/STORYBOOK_CONTRACT.md.