xpeditis2.0/docs/ui-architecture.md
2026-08-13 12:26:46 +02:00

40 KiB
Raw Blame History

Nouvelle architecture UI & plan de refonte — Xpeditis

Date : 2026-08-12 · Branche : ui_update Prérequis de lecture : design-system-audit.md (charte verrouillée) · ui-audit.md (cartographie)

Contrainte cardinale. Toute valeur visuelle produite ici provient du §1–§22 de design-system-audit.md. Aucune couleur, police, rayon ou token n'est introduit. Ce document ne fait que réorganiser et appliquer l'existant.


Sommaire


1. Principes directeurs

Quatre règles arbitrent chaque décision de la refonte. Elles sont volontairement peu nombreuses et opposables.

P1 — Le navy porte la structure, le turquoise porte l'action. brand-navy #10183A = chrome, titres, surfaces d'autorité, état actif. brand-turquoise #34CCCD = action primaire, focus, sélection, accent de données. Cette règle remplace, à elle seule, les 816 usages de bleu générique. Elle n'invente rien : c'est la lecture littérale de la charte.

P2 — La densité est une fonctionnalité. Xpeditis est un outil de transitaire : listes longues, colonnes nombreuses, comparaison de taux. La refonte va vers plus d'information par écran, pas moins. Concrètement : cartes KPI alignées à gauche et non centrées, tables en densité compacte par défaut, padding p-4/p-5 plutôt que p-6, suppression des conteneurs décoratifs intermédiaires.

P3 — Une seule manière de faire chaque chose. Un bouton, un champ, une table, une modale, un état vide. Les trois systèmes de boutons deviennent un. Les 7 paginations deviennent une. Toute variante doit être justifiée par un besoin fonctionnel distinct, pas par une préférence locale.

P4 — Le mouvement sert la compréhension. Une animation n'est acceptée que si elle explique une transition d'état ou une relation spatiale. 120–200 ms, ease-out, sur opacity / transform uniquement. Tout le reste est supprimé.


2. Vocabulaire visuel appliqué

2.1 Rôles de couleur — dérivés stricts de la charte

Rôle Token Valeur Application
Surface application bg-white #FFFFFF Cartes, panneaux, tables
Surface page neutral-50 #F8F9FC Fond de zone de contenu
Surface d'autorité brand-navy #10183A Sidebar admin, sections navy, en-têtes sombres
Bordure --border #E2E8F0 Bordures 1px, séparateurs
Texte principal brand-navy #10183A Titres, valeurs KPI
Texte courant neutral-800 #1E2859 Corps
Texte secondaire neutral-500 #5A6BB8 Descriptions, métadonnées
Action primaire brand-turquoise #34CCCD CTA, boutons primaires
Focus brand-turquoise #34CCCD Anneau de focus (via --ring)
État actif (nav) brand-navy + fond neutral-100 #10183A / #EDEEF5 Item de sidebar actif
Succès success / brand-green #067224 Accepté, validé, payé
Erreur --destructive #EF4444 Refusé, erreur, suppression
Avertissement amber-500 #F59E0B En attente, expiration proche

Trois substitutions systématiques, et rien d'autre : blue-* → brand-turquoise (action) ou brand-navy (structure) selon le rôle gray-* → neutral-* (échelle déjà définie dans la charte) purple-* / emerald-* / orange-* / indigo-* → rôle sémantique ci-dessus

2.2 Élévation — 3 niveaux, pas 6

L'audit relève 6 niveaux d'ombre utilisés sans règle. La refonte en retient trois :

Niveau Classe Usage
0 border seul Cartes, panneaux, tables — défaut
1 shadow-sm Éléments flottants ancrés : dropdowns, popovers, sidebar mobile
2 shadow-lg Éléments détachés : modales, drawers, toasts

shadow-md, shadow-xl, shadow-2xl et la classe fantôme shadow-brand sont retirés du produit. La carte par défaut passe de shadow-sm à border seul — c'est le principal levier pour un rendu net et non « cartonné ».

2.3 Rayons — 3 valeurs

Classe Valeur Usage
rounded-md 6px Contrôles : boutons, champs, selects, badges carrés
rounded-lg 8px Conteneurs : cartes, panneaux, modales, dropdowns
rounded-full — Pilules, avatars, points d'état

rounded-xl, rounded-2xl, rounded-3xl sont retirés du produit. (Conservés uniquement sur la landing, traitée en dernier lot — voir §13, L9.)

2.4 Typographie appliquée

L'échelle text-h1…h6 / text-body-* / text-label-* de la charte est enfin utilisée, en remplacement de l'échelle Tailwind par défaut :

Élément Token de charte
Titre de page text-h3 (24px/600, Manrope)
Description de page text-body-sm + neutral-500
Titre de section / carte text-h5 (18px/500)
Valeur KPI text-h2 (32px/600) + brand-navy
Corps text-body-sm (14px) — densité produit
En-tête de table text-label (12px/600, +0.05em, capitales)
Cellule de table text-body-sm
Libellé de champ text-label-lg (14px/600)
Aide / erreur de champ text-body-xs (12px)

Le titre de page passe de text-3xl (30px) à text-h3 (24px). C'est volontaire : combiné à la suppression du double titre (§3), l'écran gagne en hiérarchie et en espace utile.

2.5 Rythme d'espacement — échelle 4/8

Multiples retenus : 4, 8, 12, 16, 24, 32, 48. (gap-1 gap-2 gap-3 gap-4 gap-6 gap-8 gap-12)

Contexte Valeur
Padding de carte p-5 (20px) desktop, p-4 mobile
Padding de cellule de table px-4 py-3 (compact) / px-4 py-4 (confortable)
Gap de grille gap-4
Espacement entre sections space-y-6
Padding de zone de contenu p-4 lg:p-6 2xl:p-8

3. App shell

3.1 Structure cible

┌──────────────────────────────────────────────────────────────┐
│ Sidebar (w-60 / w-16 replié)  │  Topbar (h-14)               │
│ ─────────────────────────────  ├──────────────────────────────│
│  Logo            [«]           │ Breadcrumb  ⌘K  🔔  🌐  👤   │
│                                ├──────────────────────────────│
│  ── Pilotage                   │                              │
│  ▸ Tableau de bord             │  PageHeader                  │
│  ▸ Réservations                │   Titre (h3) + description   │
│  ▸ Documents                   │   [onglets]      [actions]   │
│  ▸ Suivi                       │  ──────────────────────────  │
│                                │                              │
│  ── Ressources                 │  Contenu                     │
│  ▸ Wiki                        │                              │
│                                │                              │
│  ── Organisation               │                              │
│  ▸ Paramètres                  │                              │
│    · Organisation              │                              │
│    · Membres                   │                              │
│    · Clés API                  │                              │
│  ─────────────────────────────  │                              │
│  👤 Utilisateur          [⋮]   │                              │
└──────────────────────────────────────────────────────────────┘

3.2 Décisions

Décision Avant Après Motif
Suppression du double titre Topbar affiche le nom de nav + page affiche son <h1> Topbar affiche le breadcrumb ; la page seule porte le <h1> Corrige A5
Sidebar repliable w-64 fixe w-60 / w-16 replié, persisté en localStorage Rend 240px aux tables larges
Groupes de navigation 8 entrées à plat 3 groupes (Pilotage / Ressources / Organisation) avec libellés text-label Lisibilité, prépare la croissance
Paramètres regroupés 3 entrées de nav dispersées 1 entrée + sous-navigation Corrige la dispersion relevée en B2
Un seul avatar Sidebar + topbar Pied de sidebar uniquement, avec menu (Profil / Déconnexion) Corrige la redondance
Logout dans le menu Bouton rouge pleine largeur permanent Entrée du menu utilisateur Poids visuel proportionné
Command menu ⌘K Absent Recherche globale : navigation, réservations, ports, actions Standard SaaS attendu
Bottom-nav mobile 5 entrées sur 8 4 entrées + « Plus » ouvrant le drawer complet Aucune section inaccessible
Shell mutualisé 2 layouts dupliqués à 80 % AppShell paramétré (`variant: 'app' 'admin'`)
Icônes SVG inline manuscrits Menu, X, PanelLeftClose de lucide Cohérence

Variante admin : même AppShell, variant="admin" → sidebar bg-brand-navy text-white, logo blanc, séparateurs border-white/10. Le contenu des pages admin adopte le même vocabulaire que le dashboard (correction de la rupture chrome/contenu relevée en B3).

3.3 États de l'item de navigation

État Style
Défaut text-neutral-700 hover:bg-neutral-100 hover:text-brand-navy
Actif bg-neutral-100 text-brand-navy font-semibold + barre w-0.5 bg-brand-turquoise à gauche
Verrouillé (plan) text-neutral-400 + icône Lock + Tooltip expliquant le plan requis, lien /pricing
Replié icône seule centrée + Tooltip au survol

L'état actif abandonne le fond bleu au profit d'un fond neutre + accent turquoise en filet. Plus discret, plus précis, strictement dans la charte.


4. Composants de navigation

Composant Rôle Notes d'implémentation
AppShell Enveloppe sidebar + topbar + contenu + bottom-nav Remplace les 2 layouts
Sidebar Navigation groupée, repliable, drawer sous lg Focus trap en mode drawer
Topbar Breadcrumb + ⌘K + notifications + langue + avatar h-14, sticky, border-b
Breadcrumb Fil d'Ariane dérivé de la route + libellés i18n Tronque au milieu sur mobile
PageHeader Titre h3, description, onglets, actions Extension de l'existant (déjà sur 9 pages)
Tabs Sous-navigation de page (paramètres, détail réservation) Sur @radix-ui/react-tabs (installé)
CommandMenu Palette ⌘K Sur ui/command.tsx complété
BottomNav Navigation mobile 4 + « Plus » —

5. Composants de contenu

5.1 DataTable — pièce maîtresse

Fondé sur @tanstack/react-table v8 (installé, utilisé une seule fois) et @tanstack/react-virtual (installé, jamais utilisé).

Capacité Détail
Colonnes Définition déclarative, tri, redimensionnement, visibilité
Densité compact (py-3) par défaut / comfortable (py-4)
Sélection Cases à cocher + barre d'actions groupées flottante
En-tête sticky top-0, text-label, fond neutral-50
Ligne hover:bg-neutral-50, sélectionnée bg-neutral-100
Colonne d'actions sticky right-0, menu sur @radix-ui/react-dropdown-menu — remplace le positionnement manuel menuPos relevé en B2
Responsive ≥ lg : table · md : colonnes secondaires masquées · < md : liste de DataCard (titre, 3 champs clés, menu d'actions)
Virtualisation Activable, pour /admin/logs et les résultats de recherche
États loading → TableSkeleton · empty → EmptyState · error → ErrorState
Pagination Pagination intégré, taille de page persistée

Ce composant absorbe 26 tables brutes, 7 paginations et 21 overflow-x-auto.

5.2 Autres

Composant Rôle
Card Rectifié : border seul, p-5, CardTitle en text-h5
StatCard KPI : libellé text-label, valeur text-h2, delta, icône discrète. Aligné à gauche.
DataCard Représentation mobile d'une ligne de table
Badge Variants sémantiques : neutral info success warning danger — tokens de charte uniquement
StatusBadge Conservé (badges d'abonnement Silver/Gold/Platinium)
Callout Encadré éditorial : info warning success danger — absorbe les ~30 encadrés du wiki
Pagination Précédent/Suivant + pages + sélecteur de taille + compteur
DescriptionList Paires libellé/valeur des pages de détail
Timeline Suivi d'expédition (/track-trace), étapes de réservation

6. Composants de saisie

Composant Base Correction apportée
Button cva + cn() + @radix-ui/react-slot asChild (corrige le <a><button> invalide) · état loading · active: · hover:bg-accent turquoise plein supprimé sur ghost/outline
Input Existant corrigé Focus turquoise · états error · préfixe/suffixe · tailles sm/md
Textarea Nouveau Aligné sur Input
Select @radix-ui/react-select Réécriture — l'actuel n'affiche jamais sa valeur
Combobox Command + Popover Radix Remplace les autocomplétions manuelles (ports, compagnies)
Checkbox / Radio Nouveaux Aujourd'hui inputs bruts
Switch Existant Conservé, aligné sur les tokens
DatePicker react-day-picker + date-fns (installé) Dates de départ/arrivée
FormField Nouveau Label + contrôle + aide + erreur + aria-describedby/aria-invalid. Absorbe 175 inputs bruts.
Form react-hook-form + zod (installés) Validation unifiée ; remplace les 25 useState de /register
FileDropzone Nouveau Glisser-déposer + progression + validation. Absorbe 4 implémentations.
SearchInput Nouveau Champ de recherche debounced avec effacement
FilterBar Nouveau Filtres + jetons actifs + réinitialisation, état synchronisé à l'URL

Variants de Button (définitifs)

Variant Style Usage
primary bg-brand-turquoise text-white hover:bg-brand-turquoise/90 Action principale — une seule par écran
secondary bg-brand-navy text-white hover:bg-brand-navy/90 Action structurante
outline border border-[--border] text-brand-navy hover:bg-neutral-50 Action secondaire
ghost text-neutral-700 hover:bg-neutral-100 Actions de table, icônes
destructive bg-[--destructive] text-white hover:bg-[--destructive]/90 Suppression
link text-brand-turquoise hover:underline underline-offset-4 Liens en ligne

Tailles : sm (32px) · md (36px, défaut) · lg (40px) · icon (36×36).

Les hauteurs passent de 40/36/44 à 36/32/40 : cohérent avec P2 (densité) et avec la topbar h-14.


7. Composants de surface

Composant Base Notes
Dialog @radix-ui/react-dialog Portal, focus trap, Escape, scroll-lock, aria-modal. Tailles sm/md/lg/full. Absorbe les modales inline de /documents et /admin/blog
ConfirmDialog Sur Dialog Remplace les 14 confirm() natifs. Variante destructive avec libellé d'action explicite
Drawer Sur Dialog Radix Panneau latéral : détail de réservation, filtres mobiles, aperçu de document
Popover @radix-ui/react-popover Portalisé — corrige le clipping
DropdownMenu @radix-ui/react-dropdown-menu Menus d'actions de table, menu utilisateur
Tooltip @radix-ui/react-tooltip Items de sidebar repliée, icônes, valeurs tronquées
Toast / Toaster Nouveau, monté dans Providers Remplace les 44 alert(). Variants success error warning info. Bas-droite desktop, haut mobile. Auto-dismiss 4 s, action optionnelle, empilement max 3
Sheet Sur Dialog Drawer mobile de la sidebar

8. Composants d'état

Quatre composants qui absorbent ~126 réimplémentations.

Composant Remplace Contenu
Skeleton + TableSkeleton / CardSkeleton / FormSkeleton ~30 animate-pulse inline bg-neutral-100 animate-pulse rounded-md, forme calquée sur le contenu réel
EmptyState 27 blocs vides Icône (cercle neutral-100), titre text-h5, description text-body-sm, action. Variantes : aucune donnée / aucun résultat de filtrage / accès verrouillé
ErrorState 29 bandeaux inline Icône, message, bouton « Réessayer », détail repliable
Spinner 40 fichiers animate-spin Tailles sm/md/lg, couleur brand-turquoise

Règle de complétude : toute vue asynchrone doit traiter les 4 états — chargement, vide, erreur, contenu. C'est un critère de revue par page.


9. Architecture de fichiers

apps/frontend/src/
├── components/
│   ├── ui/                    # Primitives — aucune logique métier
│   │   ├── button.tsx  input.tsx  textarea.tsx  select.tsx  combobox.tsx
│   │   ├── checkbox.tsx  radio.tsx  switch.tsx  date-picker.tsx
│   │   ├── card.tsx  badge.tsx  callout.tsx  separator.tsx  avatar.tsx
│   │   ├── dialog.tsx  drawer.tsx  popover.tsx  dropdown-menu.tsx  tooltip.tsx
│   │   ├── toast.tsx  toaster.tsx
│   │   ├── table.tsx  pagination.tsx  tabs.tsx  command.tsx
│   │   ├── skeleton.tsx  spinner.tsx  empty-state.tsx  error-state.tsx
│   │   └── index.ts           # export unique
│   ├── shell/                 # Chrome applicatif
│   │   ├── app-shell.tsx  sidebar.tsx  topbar.tsx
│   │   ├── breadcrumb.tsx  bottom-nav.tsx  command-menu.tsx
│   │   └── user-menu.tsx
│   ├── data/                  # Affichage de données
│   │   ├── data-table.tsx  data-card.tsx  stat-card.tsx
│   │   ├── description-list.tsx  timeline.tsx
│   │   └── filter-bar.tsx  search-input.tsx
│   ├── forms/
│   │   ├── form.tsx  form-field.tsx  file-dropzone.tsx
│   │   └── port-autocomplete.tsx
│   └── [feature]/             # bookings/ documents/ rate-search/ admin/ …
├── hooks/
│   ├── use-toast.ts  use-media-query.ts  use-sidebar.ts
│   └── use-url-state.ts       # synchronisation filtres ↔ URL
└── lib/
    └── utils.ts               # cn() — déjà présent

Conventions :

  • ui/ ne connaît ni l'API, ni l'i18n, ni le routage. Composants purs et testables.
  • Tout composant utilise cn() pour fusionner les classes (aujourd'hui : concaténation de strings).
  • Aucun texte en dur : tout passe par next-intl. messages/fr.json et messages/en.json (4 600 lignes chacun) restent synchronisés clé pour clé.
  • Les pages orchestrent, les composants affichent. Objectif : aucune page > 300 lignes.

10. Responsive

10.1 Paliers et intentions

Palier Largeur Layout
Mobile < 640px 1 colonne · bottom-nav · sidebar en drawer · tables → DataCard · filtres en Drawer · actions en DropdownMenu
Tablette 640–1023px 2 colonnes · sidebar en drawer · tables réduites aux colonnes essentielles · filtres en ligne repliables
Desktop 1024–1535px Sidebar fixe · tables complètes · 3–4 colonnes de KPI · panneaux latéraux
Grand écran ≥ 1536px Contenu jusqu'à 1600px · 4–6 colonnes de KPI · densité augmentée · p-8

10.2 Corrections ciblées

  • A8 — grand écran. max-w-7xl (1280px) devient max-w-[1600px] sur les vues de données (listes, tables, dashboard). Les vues de lecture (wiki, blog, légal) restent contraintes à max-w-3xl pour la longueur de ligne. Introduction effective du palier 2xl: (aujourd'hui 0 occurrence).
  • A9 — tablette. Le palier md reçoit un traitement propre : grilles KPI en 2 colonnes, tables à colonnes réduites, PageHeader en pile. Cible : md: passe de 120 à un niveau comparable à sm:/lg:.
  • A10 — tables. Bascule DataTable → DataCard sous md, native au composant. Aucun scroll horizontal subi.
  • Cibles tactiles. Minimum 44×44px sur mobile pour toute action (bottom-nav, menus, boutons de table).

11. Micro-interactions

Technologies déjà présentes uniquement : tailwindcss-animate (installé, quasi inutilisé) pour l'UI, framer-motion (installé) réservé à la landing.

Interaction Effet Durée
Survol de bouton background-color 120 ms ease-out
Pression de bouton scale(0.98) 80 ms
Survol de ligne de table background-color 100 ms
Ouverture de modale fade-in + zoom-in-95 180 ms ease-out
Ouverture de dropdown/popover fade-in + slide-in-from-top-1 140 ms
Toast slide-in-from-bottom / fade-out 200 ms
Drawer / sidebar mobile translate-x 220 ms ease-out
Onglets Indicateur glissant 160 ms
Sidebar repliée width 200 ms ease-out
Skeleton animate-pulse —

Règles : opacity et transform uniquement (pas de height/width animées hors sidebar) · jamais de délai avant une réponse à un clic · pas d'animation d'entrée sur le contenu de page (perçu comme lent) · prefers-reduced-motion respecté globalement :

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

12. Accessibilité

Cible : WCAG 2.1 AA.

Point Action
Focus focus-visible partout (aujourd'hui 454 focus: contre 13 focus-visible:), anneau ring-2 ring-brand-turquoise ring-offset-2
--ring Corrigé vers le turquoise de marque — restauration de charte, pas modification
Clavier Navigation complète : tables, menus, modales, combobox, tunnel de recherche
Focus trap Modales, drawers, sidebar mobile (via Radix)
Libellés Tout contrôle a un <label> associé ou un aria-label ; tout bouton-icône a un aria-label (29 aujourd'hui pour 299 boutons)
Régions <nav>, <main>, <aside>, <header> + lien d'évitement
Annonces aria-live sur les toasts et les résultats de recherche
Contraste Vérifier neutral-400/neutral-500 sur blanc ; brand-turquoise #34CCCD ne passe pas AA en texte sur blanc → réservé aux fonds, bordures et grandes tailles, jamais au texte courant sur blanc
Mouvement prefers-reduced-motion

Le point contraste est important : il ne change pas la charte, il encadre l'usage du turquoise. Texte turquoise sur blanc → remplacé par navy, le turquoise restant sur les surfaces d'action (texte blanc sur turquoise = conforme).


13. Plan de refonte par lots

11 lots, séquencés par dépendance. Chaque lot est autonome, compilable et validable.

Lot Contenu Fichiers Effort
L0 — Fondations Corriger --ring · cn() partout · réécrire Button/Input/Select/Dialog/Popover sur Radix · créer Toast, Skeleton, EmptyState, ErrorState, Spinner, Pagination, Breadcrumb, Callout, FormField, Tooltip, DropdownMenu, Checkbox, Radio, Textarea · monter Toaster dans Providers · prefers-reduced-motion ~25 nouveaux, 8 réécrits 2–3 j
L1 — App shell AppShell mutualisé · sidebar repliable groupée · topbar + breadcrumb · suppression du double titre · CommandMenu ⌘K · bottom-nav complète · UserMenu · restauration charte sur le chrome 2 layouts → ~8 composants 2 j
L2 — Données DataTable + DataCard + FilterBar + SearchInput + StatCard + useUrlState · virtualisation ~8 composants 2–3 j
L3 — Dashboard & réservations /dashboard (hiérarchie KPI, charts aux couleurs de charte, suppression du #8884d8) · /bookings · /bookings/[id] · arbitrage D1 4 pages 3 j
L4 — Documents & recherche /documents (démonolithisation 1169 l., FileDropzone, modales Radix, 9 alert() → toasts) · /search · /search-advanced (état en URL, Combobox) · /results · /track-trace 5 pages 3–4 j
L5 — Paramètres & profil SettingsLayout à onglets · /settings/organization · /settings/users · /settings/api-keys · /profile · /notifications 6 pages 2 j
L6 — Admin Alignement chrome/contenu · DataTable sur les 5 listes · démonolithisation /admin/blog (1341 l.) · 11+9 alert() → toasts · virtualisation /admin/logs 8 pages 3–4 j
L7 — Wiki & docs Callout (~30) · .prose + @tailwindcss/typography · sommaire + ancres · largeur de lecture · breadcrumb · nav précédent/suivant · 93 blue-* corrigés 13 + 2 pages 2 j
L8 — Auth & portails AuthLayout mutualisé · /register sur react-hook-form+zod (25 useState) · fusion accept/reject · états d'expiration de lien · robustesse mot de passe 11 pages 2 j
L9 — Marketing Extraction de / (1082 l.) en sections · alignement rayons/ombres · suppression des nuances navy inventées (#1A2550, #1A2A5E) · LegalPageLayout 11 pages 2 j
L10 — Nettoyage Supprimer /test-image, /demo-carte, src/legacy-pages/ · trancher src/app/rates/csv-search · gray-* → neutral-* · retirer .btn-*/.input/.card de globals.css · trancher le dark mode · mettre à jour DESIGN_SYSTEM.md — 1 j

Total estimé : 24–29 jours de travail.

Chemin critique : L0 → L1 → L2 conditionnent tout le reste. L3 à L9 sont ensuite parallélisables.


14. Protocole de validation

Après chaque lot

cd "apps/frontend"

# 1. La charte n'a pas bougé
git diff tailwind.config.ts                 # doit être VIDE
git diff app/globals.css                    # seul --ring modifié (L0), validé

# 2. Les dérives reculent
grep -rE "\b(bg|text|border|ring|from|to)-blue-" app src --include="*.tsx" | wc -l   # jamais croissant
grep -rE "[^.](alert|confirm)\(" app src --include="*.tsx" | wc -l                    # jamais croissant

# 3. Qualité
npm run type-check
npm run lint
npm run build
npm test

# 4. i18n synchronisé
python3 -c "import json;a=json.load(open('messages/fr.json'));b=json.load(open('messages/en.json'));\
def k(d,p=''):\
 s=set()\
 for x,v in d.items():\
  s|=k(v,p+x+'.') if isinstance(v,dict) else {p+x}\
 return s
print('écart:', k(a)^k(b))"

Indicateurs de progression

Mesures prises avec scripts de comptage homogènes (occurrences via grep -o, jamais des lignes).

⚠️ Correction de métrique (lot L5). Deux lignes de ce tableau étaient fausses dans les versions précédentes du document :

  • Dialogues natifs : le motif de comptage excluait window.confirm( (précédé d'un point). Le vrai point de départ est 62, pas 58.
  • Fichiers > 600 lignes : la valeur de départ « 12 » était une estimation non mesurée. Le comptage réel sur HEAD donne 17.

Les colonnes ci-dessous sont recalculées avec la métrique corrigée. Les autres lignes étaient exactes.

Indicateur Départ L0 L1 L2 L3 L4 L5 L6 L7 Cible
blue-* 816 729 722 722 691 552 438 312 177 0
gray-* 2 485 2 355 2 331 2 331 2 108 1 833 1 523 1 088 759 0 (→ neutral-*)
Dialogues natifs 62 — — — — 48 44 7 7 0
<button> bruts 299 291 287 290 278 262 262 262 262 < 30
<input> bruts 175 146 147 147 146 143 143 143 143 < 20
<table> bruts 26 26 26 27 26 25 25 25 25 0
Fichiers > 600 lignes 17 — — — — — 12 12 12 0
2xl: 0 0 1 1 1 1 1 1 1 > 40
focus-visible: 13 41 62 71 71 82 93 93 97 > 100
Paquets Radix importés 0 7 7 7 7 7 8 8 8 ≥ 8
Tests frontend 123 123 135 144 144 144 144 144 144 croissant

Après L8 : blue-* 148 · gray-* 610 · dialogues natifs 6.

Après L9 : blue-* = 0 · gray-* = 0.

🎯 Objectif atteint — la charte est restaurée dans toute l'application

Indicateur Départ Aujourd'hui
blue-* (accent hors charte) 816 0
gray-* (échelle neutre concurrente) 2 485 0
Nuances de marque inventées (#1a2550, #0e9999…) 9 0
Dialogues natifs 62 6 (dont 4 en code mort ou faux positifs)

Vérification reproductible :

cd apps/frontend
grep -rhoE '\b[a-z:-]*-(blue|gray)-[0-9]+' app src --include='*.tsx' | wc -l   # 0
git diff --quiet tailwind.config.ts && echo "charte intacte"                    # charte intacte

19. État à l'issue du lot L10

Indicateur Départ Fin Cible
blue-* 816 0 ✅ 0
gray-* 2 485 0 ✅ 0
Nuances de marque inventées 9 0 ✅ 0
Dialogues natifs 62 2 0
Paquets Radix importés 0 8 ✅ ≥ 8
Tests frontend 123 144 ✅ croissant
focus-visible: 13 97 > 100
<button> bruts 299 255 < 30
<input> bruts 175 143 < 20
<table> bruts 26 24 0
2xl: 0 1 > 40
Fichiers > 600 lignes 17 12 0

Les 2 dialogues natifs restants sont des faux positifs : une fonction locale nommée confirm() dans dashboard/booking/[id]/payment-success/page.tsx, sans rapport avec le dialogue du navigateur.

Code mort supprimé

Cible Motif
src/legacy-pages/ (3 fichiers) Aucune référence dans le projet
src/app/ (rates/csv-search) Jamais construit : app/ existe à la racine, donc Next.js ignore src/app/. Absent du manifeste de build.
dashboard/search + dashboard/bookings/new Îlot orphelin fermé (L10a)
test-image, demo-carte Pages de test

Corrections d'accessibilité appliquées à globals.css

  • .btn-primary : texte blanc → navy sur turquoise (2,05:1 → 8,49:1)
  • .badge-info : libellé turquoise → navy sur fond turquoise clair
  • .link et Button variant="link" : soulignement permanent (un lien identifié par la seule couleur échoue au critère WCAG 1.4.1) et passage au navy

Les classes @layer components ne sont pas supprimées : .btn-primary sert dans 8 fichiers, .label et .link dans /register et /admin/login. Leur retrait est conditionné au passage de ces pages aux primitives.


20. Travaux restants

Ce qui n'a pas été fait, explicitement.

Démonolithisation

Fichier Lignes
src/components/docs/DocsPageContent.tsx 1 506
app/[locale]/admin/blog/page.tsx 1 341
app/[locale]/page.tsx 1 082
app/[locale]/dashboard/search-advanced/page.tsx 1 067
app/[locale]/dashboard/settings/users/page.tsx 884
app/[locale]/register/page.tsx · admin/organizations 832
+ 5 autres 621–736

Adoption des primitives

255 <button>, 143 <input> et 24 <table> restent écrits à la main. Leur mise en forme est conforme à la charte, mais ils ne bénéficient ni des états, ni de l'accessibilité, ni de la cohérence des primitives.

Responsive grand écran

2xl: n'apparaît qu'une fois (dans AppShell). Les pages n'exploitent pas la largeur au-delà de 1 280 px.

Autres

  • Callout non substitué aux ~30 encadrés du wiki
  • Sommaire, ancres et navigation précédent/suivant du wiki
  • Chaînes françaises codées en dur dans admin/blog et RichTextEditor (hors i18n)
  • Usage du turquoise en texte à auditer : 176 occurrences de text-brand-turquoise, dont une partie sur fond clair
  • Défaut /booking du middleware (§18) — en attente d'arbitrage

18. Défaut fonctionnel relevé — liens magiques de réservation

Découvert en vérifiant le rendu des portails token au lot L8. Sans rapport avec la refonte : middleware.ts n'a été modifié à aucun moment.

apps/frontend/middleware.ts liste /carrier dans prefixPublicPaths, mais pas /booking. Or /booking/confirm/[token] et /booking/reject/[token] sont des pages atteintes par lien magique, envoyées par e-mail à des clients qui ne sont pas connectés.

Conséquence : ces deux liens redirigent vers /login?redirect=…. Le destinataire ne peut ni confirmer ni refuser sa réservation.

Vérification :

curl -s -o /dev/null -w "%{http_code}" http://localhost:3001/fr/carrier/accept/xxx   # 200
curl -s -o /dev/null -w "%{http_code}" http://localhost:3001/fr/booking/confirm/xxx  # 307

Correction : ajouter '/booking' à prefixPublicPaths. Une ligne.

Non appliquée : modifier une frontière d'authentification dépasse le périmètre d'une refonte UI et relève d'une décision produit. À arbitrer.

Les 7 dialogues natifs restants (contre 62 au départ) :

Emplacement Nombre Traitement
src/legacy-pages/CarrierManagement.tsx 3 Code mort — supprimé en L10
src/app/rates/csv-search/page.tsx 1 Route orpheline — arbitrage L10
carrier/documents/[token]/page.tsx 1 L8
dashboard/booking/[id]/payment-success/page.tsx 2 Faux positifs — une fonction locale nommée confirm(), sans rapport avec le dialogue natif

Soit 2 appels natifs réels hors code mort et route orpheline.

Fichiers restant au-dessus de 600 lignes après L5 (12) — cible des lots suivants :

Fichier Lignes Lot
src/components/docs/DocsPageContent.tsx 1 506 L7
app/[locale]/admin/blog/page.tsx 1 341 L6
app/[locale]/page.tsx 1 082 L9
app/[locale]/dashboard/search-advanced/page.tsx 1 067 reporté
app/[locale]/dashboard/settings/users/page.tsx 884 reporté
app/[locale]/register/page.tsx 832 L8
app/[locale]/admin/organizations/page.tsx 832 L6
app/[locale]/dashboard/booking/new/page.tsx 736 reporté
app/[locale]/admin/bookings/page.tsx 735 L6
app/[locale]/admin/documents/page.tsx 684 L6
app/[locale]/contact/page.tsx 681 L9
app/[locale]/dashboard/booking/[id]/pay/page.tsx 621 reporté

Réduction de volume :

Fichier Avant Après Lot
dashboard/documents/page.tsx 1 169 l. 549 l. L4
dashboard/bookings/page.tsx 908 l. 501 l. L3
dashboard/page.tsx 454 l. 372 l. L3
dashboard/bookings/[id]/page.tsx 284 l. 239 l. L3

17. Correction de contraste — texte sur turquoise

Constat fait en cours de lot L4, vérifié par calcul de luminance relative WCAG :

Couple Contraste Verdict
Blanc sur brand-turquoise #34CCCD 2,05:1 ❌ Échoue AA (4,5:1 requis)
brand-navy #10183A sur brand-turquoise 8,49:1 ✅ Passe AAA

Le token accent.foreground de tailwind.config.ts vaut #FFFFFF. Il n'est pas modifié — la charte reste verrouillée. Ce qui change est le couple appliqué dans les composants : les aplats turquoise portent désormais du texte navy.

Ce n'est pas une invention : la sidebar d'administration d'origine employait déjà bg-brand-turquoise text-brand-navy. La refonte généralise une pratique déjà présente dans le projet, et la rend conforme.

Les légères hausses de <button> (+3) et <table> (+1) après L2 sont attendues : ce sont les composants mutualisés eux-mêmes (DataTable, FilterBar, SearchInput) qui contiennent désormais ces éléments — une fois pour toutes, à la place des 26 tables et des 290 boutons dispersés qu'ils vont absorber à partir de L3.

Le recul entre « Départ » et « Après L0 » provient de la suppression du code mort validé (L10a). L0 construit les fondations sans toucher aux pages. Après L1, le chrome applicatif ne contient plus aucun bleu générique : les 722 occurrences restantes sont toutes dans le corps des pages, traitées par les lots L3 à L9.

Revue visuelle par lot

Captures avant/après aux 4 paliers (375 / 768 / 1440 / 1920 px), sur chromium, firefox, webkit via la configuration Playwright existante.


15. Ce que je ne ferai pas

Engagements explicites, opposables en revue :

  1. ❌ Modifier brand-navy, brand-turquoise, brand-green, brand-gray.
  2. ❌ Modifier primary.*, accent.*, success.*, l'échelle neutral-*.
  3. ❌ Changer Manrope / Montserrat ou leurs affectations.
  4. ❌ Modifier l'échelle fontSize de tailwind.config.ts.
  5. ❌ Modifier --radius: 0.5rem.
  6. ❌ Remplacer lucide-react ou toucher aux logos et assets de marque.
  7. ❌ Introduire une couleur d'accent nouvelle, un dégradé décoratif, une teinte inventée.
  8. ❌ Glassmorphism, ombres portées lourdes, rayons > 8px dans le produit, cartes empilées gratuitement.
  9. ❌ Animer pour animer.
  10. ❌ Ajouter une dépendance non justifiée. Nouvelles dépendances envisagées, toutes déjà partiellement présentes ou triviales : @radix-ui/react-popover, @radix-ui/react-tooltip, @radix-ui/react-checkbox, react-day-picker. Chacune sera soumise à validation avant installation.
  11. ❌ Modifier le backend, les contrats d'API ou la logique métier. La refonte est strictement frontend.
  12. ❌ Supprimer une route ou du code mort sans arbitrage explicite (voir §F de ui-audit.md).

16. Arbitrages requis avant démarrage

# Question Bloque
1 Validez-vous le remplacement blue-* → navy/turquoise sur les 64 fichiers ? C'est le changement le plus visible de la refonte. Sans lui, l'application conserve deux identités. L1 et tous les suivants
2 Lequel des deux tunnels de réservation est actif : /dashboard/bookings/new ou /dashboard/booking/new ? L3
3 src/app/rates/csv-search (hors i18n, hors middleware) : migrer ou supprimer ? L10
4 src/legacy-pages/ : code mort confirmé ? L10
5 Dark mode : l'implémenter réellement, ou retirer le scaffolding mort ? L0
6 Wiki : le contenu reste-t-il en JSX, ou passe-t-il en MDX ? L7
7 Les 4 dépendances du §15.10 sont-elles autorisées ? L0