From 5188714235fc7e4eb83745268b9f8b0db8310945 Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sat, 25 Apr 2026 17:59:10 +0300 Subject: [PATCH] =?UTF-8?q?docs(architecture):=20refl=C3=A9ter=20l'arbores?= =?UTF-8?q?cence=20r=C3=A9elle=20+=20documenter=20convention=20shared/ui?= =?UTF-8?q?=20(FTD-25,=20FTD-26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ARCHITECTURE.md | 318 ++++++++++++++++++++++++++++--------------- docs/TECH_DEBT.md | 9 +- 2 files changed, 212 insertions(+), 115 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 62a68ec..2714d4b 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -47,24 +47,25 @@ Le frontend communique avec Supabase **uniquement pour l'authentification** (log Versions officielles au 2026-04-17 (cf. ADR 006 pour la justification) : -| Domaine | Choix | Version | Justification | -|---|---|---|---| -| Framework UI | React | 19.2.x | Compilateur React, Actions, useOptimistic | -| Build tool | Vite | 8.0.x | HMR rapide, moteur Rolldown, config minimale | -| Langage | TypeScript (strict mode) | 6.0.x | Typage fort obligatoire pour détecter les bugs de permissions à la compilation | -| Styling | Tailwind CSS | 4.2.x | Configuration CSS-first via `@theme`, moteur Oxide (builds microseconde) | -| UI components | shadcn/ui | CLI latest | Copy-paste, total contrôle, supporte Tailwind 4 + React 19 depuis 2025 | -| Routing | React Router | v7.14.x | Compatible API v6, data loaders disponibles | -| État serveur | TanStack Query | 5.x | Cache, refetch, invalidation, remplace Redux/SWR | -| État local | `useState` / `useReducer` | React 19 built-in | Pas de store global pour la V2 (voir ADR 003) | -| Auth | Supabase JS | 2.103.x | Côté frontend : auth uniquement. Cf. `ARCHITECTURE.md` backend §2 | -| Validation | Zod | latest | Validation des inputs formulaires (cf. SECURITY.md SEC-04) | -| Rendu Markdown | react-markdown | latest | Rendu sécurisé des rapports IA (cf. SECURITY.md SEC-05) | -| Tests | Vitest + React Testing Library | latest | Parité avec backend (qui utilise Vitest) | -| Lint + Format | ESLint + Prettier | 9.x + latest | Standard | -| CI | GitHub Actions | — | Typecheck + tests + `npm audit` | +| Domaine | Choix | Version | Justification | +| -------------- | ------------------------------ | ----------------- | ------------------------------------------------------------------------------ | +| Framework UI | React | 19.2.x | Compilateur React, Actions, useOptimistic | +| Build tool | Vite | 8.0.x | HMR rapide, moteur Rolldown, config minimale | +| Langage | TypeScript (strict mode) | 6.0.x | Typage fort obligatoire pour détecter les bugs de permissions à la compilation | +| Styling | Tailwind CSS | 4.2.x | Configuration CSS-first via `@theme`, moteur Oxide (builds microseconde) | +| UI components | shadcn/ui | CLI latest | Copy-paste, total contrôle, supporte Tailwind 4 + React 19 depuis 2025 | +| Routing | React Router | v7.14.x | Compatible API v6, data loaders disponibles | +| État serveur | TanStack Query | 5.x | Cache, refetch, invalidation, remplace Redux/SWR | +| État local | `useState` / `useReducer` | React 19 built-in | Pas de store global pour la V2 (voir ADR 003) | +| Auth | Supabase JS | 2.103.x | Côté frontend : auth uniquement. Cf. `ARCHITECTURE.md` backend §2 | +| Validation | Zod | latest | Validation des inputs formulaires (cf. SECURITY.md SEC-04) | +| Rendu Markdown | react-markdown | latest | Rendu sécurisé des rapports IA (cf. SECURITY.md SEC-05) | +| Tests | Vitest + React Testing Library | latest | Parité avec backend (qui utilise Vitest) | +| Lint + Format | ESLint + Prettier | 9.x + latest | Standard | +| CI | GitHub Actions | — | Typecheck + tests + `npm audit` | **Choix motivés par ADR :** + - ADR 001 : Cloudflare Pages (hébergement) - ADR 002 : Découplage `auth-client` / `api-client` - ADR 003 : Pas de Zustand pour la V2 @@ -95,10 +96,16 @@ expria-frontend/ │ └── 005-has-access-typed-strict.md │ ├── src/ -│ ├── app/ # CONFIGURATION ET ENTRY POINTS -│ │ ├── providers.tsx # QueryClientProvider + AuthProvider + Router +│ ├── app/ # ENTRY POINTS + LAYOUT DE LA COQUILLE +│ │ ├── main.tsx # Entry point React (montage DOM) +│ │ ├── providers.tsx # QueryClientProvider + ThemeProvider + Router │ │ ├── router.tsx # Routes déclaratives -│ │ └── main.tsx # Entry point React +│ │ ├── route-titles.ts # Mapping route → titre (breadcrumb Topbar) +│ │ ├── AppLayout.tsx # Coquille app (sidebar + topbar + outlet) +│ │ ├── Sidebar.tsx # Navigation desktop (navy permanent) +│ │ ├── Topbar.tsx # Topbar sticky (breadcrumb, recherche, theme toggle) +│ │ ├── BottomNav.tsx # Navigation mobile (< 1024px) +│ │ └── MaintenancePage.tsx # Page affichée si VITE_MAINTENANCE_MODE=true │ │ │ ├── entities/ # OBJETS MÉTIER (indépendants de l'UI) │ │ ├── user/ @@ -106,67 +113,109 @@ expria-frontend/ │ │ │ ├── lib.ts # hasAccess(), canSimulate() │ │ │ ├── access.ts # IDENTIQUE à expria-backend/src/lib/access.ts │ │ │ ├── api.ts # GET /plans/status, POST /auth/verify-token +│ │ │ ├── query-keys.ts # Constantes de clés TanStack (PLAN_QUERY_KEY) │ │ │ └── __tests__/ -│ │ │ ├── hasAccess.test.ts -│ │ │ └── canSimulate.test.ts │ │ │ │ │ ├── production/ │ │ │ ├── types.ts # Production, Tache, Mode │ │ │ ├── lib.ts # helpers (format tache, etc.) -│ │ │ └── api.ts # POST /simulations, GET /simulations/:id +│ │ │ └── api.ts # POST /simulations, GET /simulations/:id, PATCH /:id/contenu │ │ │ -│ │ └── report/ -│ │ ├── types.ts # Report, Critere -│ │ ├── lib.ts # Logique de floutage selon plan -│ │ ├── api.ts # POST /corrections/ee, POST /corrections/eo -│ │ └── __tests__/ -│ │ └── floutage.test.ts +│ │ ├── report/ +│ │ │ ├── types.ts # Report, Critere, Revelation, Diagnostic +│ │ │ ├── lib.ts # Logique de floutage selon plan +│ │ │ ├── api.ts # POST /corrections/ee, POST /corrections/eo +│ │ │ └── __tests__/ +│ │ │ +│ │ ├── patterns/ # Sprint 3.6c — analyse patterns Premium +│ │ │ ├── types.ts # Pattern, PatternAnalysis, PreparationIndex +│ │ │ └── api.ts # GET /users/patterns +│ │ │ +│ │ ├── presentation/ # Sprint 4c-2 — présentation T1 EO +│ │ │ ├── types.ts +│ │ │ └── api.ts # POST /presentations/generate +│ │ │ +│ │ └── transcription/ # Sprint 4c — code Deepgram dormant (cf. FTD-37) +│ │ ├── types.ts +│ │ └── api.ts │ │ │ ├── features/ # UI (composants + pages + hooks) │ │ ├── auth/ -│ │ │ ├── components/ # LoginForm, RegisterForm, ProtectedRoute +│ │ │ ├── components/ # ProtectedRoute │ │ │ ├── pages/ # LoginPage, RegisterPage │ │ │ └── hooks/ # useAuth │ │ │ │ │ ├── dashboard/ -│ │ │ ├── components/ # DashboardFreeView, DashboardStandardView, DashboardPremiumView +│ │ │ ├── components/ # DashboardFreeView/StandardView/PremiumView, +│ │ │ │ # NclcHero, StatCards, RecentSimulations, +│ │ │ │ # NextStepCard, PaywallBanner, MonProfilPreparation │ │ │ ├── pages/ # DashboardPage (orchestre les vues selon le plan) │ │ │ └── hooks/ # usePlan │ │ │ │ │ ├── simulations/ -│ │ │ ├── components/ # SimulationForm, AudioRecorder, TimerExam -│ │ │ ├── pages/ # SimulationPage, RapportPage -│ │ │ └── hooks/ # useSimulation, useExamMode +│ │ │ ├── components/ # SimulationForm, AudioRecorder, TimerDisplay, +│ │ │ │ │ # TaskSelector, SujetCard/Display, IdeesSuggestions, +│ │ │ │ │ # NclcCibleSelector, SpecialCharsKeyboard, +│ │ │ │ │ # WordCountBar, TranscriptionDisplay +│ │ │ │ └── rapport/ # ScoreHero, RevelationCards, CritereCard, +│ │ │ │ # DiagnosticCallout, ConseilNclcCallout, +│ │ │ │ # ExerciceInteractive, ProductionModeleSection, +│ │ │ │ # JobStatusFallback +│ │ │ ├── pages/ # EE : SimulationPage, SujetsPage, RapportPage +│ │ │ │ # EO : SujetsEOPage, PreEnregistrementEOPage, +│ │ │ │ # EnregistrementEOPage, SimulationEOPage, +│ │ │ │ # ModeChoixT1Page, QuestionnaireT1Page, +│ │ │ │ # PresentationGenereeT1Page +│ │ │ ├── hooks/ # useSimulation, useSujets, useRapport, useTimer, +│ │ │ │ # useAutosave, useIdees, useAudioRecorder, +│ │ │ │ # useDeepgramLive (dormant — FTD-37) +│ │ │ ├── lib/ # simulationConfig.ts (durées, mots cibles, etc.) +│ │ │ └── state/ # SimulationFlowProvider + simulationFlow (machine d'état) │ │ │ -│ │ ├── t2-live/ -│ │ │ ├── components/ # DialogueView, AudioVisualizer -│ │ │ ├── pages/ # T2LivePage -│ │ │ ├── hooks/ # useT2LiveSession -│ │ │ ├── lib/ -│ │ │ │ ├── ws-client.ts # WebSocket + reconnexion -│ │ │ │ └── audio.ts # Capture PCM + lecture réponse -│ │ │ └── state/ -│ │ │ └── t2-machine.ts # State machine (idle → connecting → listening → ...) +│ │ ├── historique/ # Sprint 3.7 — liste des productions +│ │ │ ├── components/ # SimulationsList, SimulationListItem +│ │ │ ├── pages/ # HistoriquePage +│ │ │ └── hooks/ # useSimulationsList │ │ │ -│ │ └── billing/ -│ │ ├── components/ # PaymentSummary -│ │ ├── pages/ # PricingPage, CheckoutPage, UpgradePage -│ │ └── hooks/ # useStripeCheckout +│ │ ├── progression/ # Sprint 3.6c — analyse patterns Premium +│ │ │ ├── components/ # PreparationIndexHero, PatternsList, +│ │ │ │ # PatternExerciceCard, ProgressionPremium, +│ │ │ │ # BlurredProgression, NotReadyState +│ │ │ ├── pages/ # ProgressionPage +│ │ │ └── hooks/ # usePatterns +│ │ │ +│ │ └── design-system/ # Page interne de référence visuelle (DA Charcoal) +│ │ └── DesignSystemPage.tsx │ │ │ ├── shared/ # CODE RÉUTILISABLE NON MÉTIER +│ │ ├── ui/ # PRIMITIVES EXPRIA (PascalCase) — voir note ci-dessous +│ │ │ ├── Button.tsx +│ │ │ ├── Card.tsx +│ │ │ └── Badge.tsx │ │ ├── components/ -│ │ │ ├── ui/ # Button, Modal, Badge (shadcn/ui) -│ │ │ ├── PaywallModal.tsx # Blocage + boutons upgrade -│ │ │ └── Spinner.tsx -│ │ ├── hooks/ # useDebounce, useLocalStorage +│ │ │ ├── ui/ # PRIMITIVES SHADCN BRUTES (kebab-case) — voir note +│ │ │ │ ├── avatar.tsx +│ │ │ │ ├── badge.tsx +│ │ │ │ ├── button.tsx +│ │ │ │ ├── dialog.tsx +│ │ │ │ ├── input.tsx +│ │ │ │ ├── label.tsx +│ │ │ │ ├── progress.tsx +│ │ │ │ └── separator.tsx +│ │ │ ├── Logo.tsx +│ │ │ └── ThemeToggle.tsx +│ │ ├── hooks/ # useTheme │ │ ├── lib/ │ │ │ ├── auth-client.ts # Supabase Auth uniquement (ADR 002) │ │ │ ├── api-client.ts # Fetch + retry + timeout + logging (ADR 002) │ │ │ ├── query-client.ts # Configuration TanStack Query -│ │ │ └── logger.ts # Logging structuré frontend +│ │ │ ├── logger.ts # Logging structuré frontend +│ │ │ ├── theme.ts # getInitialTheme / applyTheme / persistTheme +│ │ │ ├── audio.ts # Helpers MediaRecorder + mime detection +│ │ │ ├── date.ts # formatRelativeDate (Intl.RelativeTimeFormat) +│ │ │ └── utils.ts # cn() — clsx + tailwind-merge │ │ ├── types/ -│ │ │ ├── api.ts # ApiResponse, ApiError -│ │ │ └── common.ts # Types utilitaires +│ │ │ └── api.ts # ApiError, ApiErrorCode, FrontendErrorCode │ │ └── config/ │ │ └── env.ts # Validation des variables d'environnement au démarrage │ │ @@ -186,6 +235,29 @@ expria-frontend/ └── README.md ``` +### Note sur `app/` — entry points + layout + +Le dossier `app/` contient les entry points React (`main.tsx`, `providers.tsx`, `router.tsx`) **ET** les composants layout de la coquille applicative (`AppLayout`, `Sidebar`, `Topbar`, `BottomNav`, `MaintenancePage`). Ces composants ne sont rattachés à aucune feature : ils définissent la structure visuelle globale de l'app et orchestrent l'affichage des routes. Leur rôle structurel justifie leur place dans `app/` plutôt que dans `shared/components/` ou dans une feature dédiée. + +> Note : `t2-live/` (Sprint 6) et `billing/` (Sprint 5) ne sont pas encore implémentés et n'apparaissent volontairement pas dans cette arborescence. Voir `ROADMAP.md` pour le calendrier. + +### Convention `shared/ui/` vs `shared/components/ui/` + +Deux dossiers UI cohabitent dans `shared/`. **La distinction est volontaire :** + +| Dossier | Convention | Contenu | Usage | +| ----------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `shared/ui/` | PascalCase (`Button.tsx`) | **Wrappers Expria maison** : tokens DA Charcoal appliqués, API simplifiée, variants métier (`primary`, `secondary`, `ghost`, `upgrade`). | **À utiliser par défaut dans toutes les features.** | +| `shared/components/ui/` | kebab-case (`button.tsx`) | **Primitives shadcn/ui brutes** générées par la CLI shadcn. | À utiliser **uniquement** comme base interne pour construire un wrapper Expria, ou quand une primitive Radix (Dialog, Popover) est nécessaire directement. | + +**Règle d'évolution :** + +- Toute nouvelle primitive Expria va dans `shared/ui/`. +- Aucune nouvelle primitive ne doit être ajoutée manuellement dans `shared/components/ui/` — ce dossier est réservé aux fichiers générés par la CLI shadcn. +- Si un wrapper Expria s'appuie sur une primitive shadcn, il l'importe depuis `shared/components/ui/` et l'expose sous une API simplifiée dans `shared/ui/.tsx`. + +Cette dualité est tracée dans `TECH_DEBT.md` (FTD-26) — documentée, pas à fusionner. + ### Règles de dépendance entre dossiers ``` @@ -264,12 +336,12 @@ Composant React **Gestion des close codes côté frontend :** -| Close code | Cause | Action côté frontend | -|---|---|---| -| 1000 | Fermeture normale | State → 'ended', afficher le rapport | -| 4001 | AUTH_REQUIRED | State → 'error', redirect `/login` | -| 4003 | PLAN_INSUFFICIENT | State → 'error', afficher PaywallModal Premium | -| Autre | Erreur réseau ou serveur | State → 'error', message générique + bouton "Réessayer" | +| Close code | Cause | Action côté frontend | +| ---------- | ------------------------ | ------------------------------------------------------- | +| 1000 | Fermeture normale | State → 'ended', afficher le rapport | +| 4001 | AUTH_REQUIRED | State → 'error', redirect `/login` | +| 4003 | PLAN_INSUFFICIENT | State → 'error', afficher PaywallModal Premium | +| Autre | Erreur réseau ou serveur | State → 'error', message générique + bouton "Réessayer" | --- @@ -349,44 +421,44 @@ export interface ApiError { error: true code: ApiErrorCode message: string - status?: number // quirk backend (simulations, corrections) + status?: number // quirk backend (simulations, corrections) } export type ApiErrorCode = - | 'AUTH_REQUIRED' // 401 — JWT absent, invalide ou expiré - | 'PLAN_INSUFFICIENT' // 403 — feature non disponible pour ce plan - | 'QUOTA_REACHED' // 403 — quota de simulations Free épuisé - | 'VALIDATION_ERROR' // 400 — corps de requête invalide (simulations, corrections) - | 'INVALID_BODY' // 400 — corps de requête invalide (plans, stripe) — voir note - | 'INVALID_PLAN' // 400 — valeur de plan inconnue - | 'NO_ACTIVE_SUBSCRIPTION' // 400 — tentative d'upgrade sans abonnement actif - | 'SIMULATION_NOT_FOUND' // 404 — simulation inexistante ou non accessible - | 'STRIPE_WEBHOOK_INVALID' // 400 — signature webhook invalide - | 'INTERNAL_ERROR' // 500 — erreur serveur inattendue + | 'AUTH_REQUIRED' // 401 — JWT absent, invalide ou expiré + | 'PLAN_INSUFFICIENT' // 403 — feature non disponible pour ce plan + | 'QUOTA_REACHED' // 403 — quota de simulations Free épuisé + | 'VALIDATION_ERROR' // 400 — corps de requête invalide (simulations, corrections) + | 'INVALID_BODY' // 400 — corps de requête invalide (plans, stripe) — voir note + | 'INVALID_PLAN' // 400 — valeur de plan inconnue + | 'NO_ACTIVE_SUBSCRIPTION' // 400 — tentative d'upgrade sans abonnement actif + | 'SIMULATION_NOT_FOUND' // 404 — simulation inexistante ou non accessible + | 'STRIPE_WEBHOOK_INVALID' // 400 — signature webhook invalide + | 'INTERNAL_ERROR' // 500 — erreur serveur inattendue // Erreurs générées côté frontend uniquement (pas envoyées par le backend) export type FrontendErrorCode = - | 'TIMEOUT' // timeout côté client (AbortController) - | 'NETWORK_ERROR' // pas de réponse réseau - | 'PARSE_ERROR' // réponse non-JSON + | 'TIMEOUT' // timeout côté client (AbortController) + | 'NETWORK_ERROR' // pas de réponse réseau + | 'PARSE_ERROR' // réponse non-JSON ``` > **Note sur `VALIDATION_ERROR` vs `INVALID_BODY`** : le backend utilise deux codes pour la même classe d'erreur (corps invalide). `VALIDATION_ERROR` dans les routes simulations/corrections, `INVALID_BODY` dans les routes plans/stripe. Cette inconsistance est documentée dans `TECH_DEBT.md` backend (TD-15 à créer). Côté frontend, les deux codes sont gérés de la même manière dans l'UI. ### Codes d'erreur — mapping HTTP -| Code backend | HTTP | Signification | Routes émettrices | -|---|---|---|---| -| `AUTH_REQUIRED` | 401 | JWT absent, invalide, expiré, ou profil introuvable | middleware, corrections | -| `PLAN_INSUFFICIENT` | 403 | Feature réservée à un plan supérieur | middleware, simulations | -| `QUOTA_REACHED` | 403 | 5/5 simulations utilisées (plan Free) | simulations | -| `VALIDATION_ERROR` | 400 | Corps de requête invalide (simulations, corrections) | simulations, corrections | -| `INVALID_BODY` | 400 | Corps de requête invalide (plans, stripe) | plans, stripe | -| `INVALID_PLAN` | 400 | Valeur de plan inconnue dans le payload | plans, stripe | -| `NO_ACTIVE_SUBSCRIPTION` | 400 | Upgrade prorata sans abonnement actif | plans | -| `SIMULATION_NOT_FOUND` | 404 | Simulation inexistante ou non accessible | corrections | -| `STRIPE_WEBHOOK_INVALID` | 400 | Signature webhook invalide | stripe | -| `INTERNAL_ERROR` | 500 | Erreur serveur inattendue | plans, stripe, corrections, simulations | +| Code backend | HTTP | Signification | Routes émettrices | +| ------------------------ | ---- | ---------------------------------------------------- | --------------------------------------- | +| `AUTH_REQUIRED` | 401 | JWT absent, invalide, expiré, ou profil introuvable | middleware, corrections | +| `PLAN_INSUFFICIENT` | 403 | Feature réservée à un plan supérieur | middleware, simulations | +| `QUOTA_REACHED` | 403 | 5/5 simulations utilisées (plan Free) | simulations | +| `VALIDATION_ERROR` | 400 | Corps de requête invalide (simulations, corrections) | simulations, corrections | +| `INVALID_BODY` | 400 | Corps de requête invalide (plans, stripe) | plans, stripe | +| `INVALID_PLAN` | 400 | Valeur de plan inconnue dans le payload | plans, stripe | +| `NO_ACTIVE_SUBSCRIPTION` | 400 | Upgrade prorata sans abonnement actif | plans | +| `SIMULATION_NOT_FOUND` | 404 | Simulation inexistante ou non accessible | corrections | +| `STRIPE_WEBHOOK_INVALID` | 400 | Signature webhook invalide | stripe | +| `INTERNAL_ERROR` | 500 | Erreur serveur inattendue | plans, stripe, corrections, simulations | ### Pattern `apiFetch` @@ -402,9 +474,14 @@ const { data, error, isLoading } = useQuery({ // error est de type ApiError | null if (error) { switch (error.code) { - case 'AUTH_REQUIRED': redirectToLogin(); break - case 'QUOTA_REACHED': openUpgradeModal(); break - default: showGenericErrorToast() + case 'AUTH_REQUIRED': + redirectToLogin() + break + case 'QUOTA_REACHED': + openUpgradeModal() + break + default: + showGenericErrorToast() } } ``` @@ -488,9 +565,18 @@ export const PLANS = { }, } -export function getPlanPermissions(plan: Plan) { /* ... */ } -export function canUserSimulate(user: { plan: string; simulations_used: number }): { allowed, reason? } { /* ... */ } -export function checkFeatureAccess(plan: Plan, feature: Feature): boolean { /* ... */ } +export function getPlanPermissions(plan: Plan) { + /* ... */ +} +export function canUserSimulate(user: { plan: string; simulations_used: number }): { + allowed + reason? +} { + /* ... */ +} +export function checkFeatureAccess(plan: Plan, feature: Feature): boolean { + /* ... */ +} ``` ### Alias frontend-idiomatiques @@ -499,11 +585,7 @@ Le fichier `src/entities/user/lib.ts` ré-exporte ces fonctions sous des noms st ```typescript // src/entities/user/lib.ts -import { - canUserSimulate, - checkFeatureAccess, - getPlanPermissions, -} from './access' +import { canUserSimulate, checkFeatureAccess, getPlanPermissions } from './access' /** * Alias frontend-idiomatique de checkFeatureAccess. @@ -568,6 +650,7 @@ VITE_MAINTENANCE_MODE=false # true = affiche MaintenancePage avant tout pr - `STRIPE_WEBHOOK_SECRET` Cette règle est vérifiée par : + - Le plugin Security Guidance de Claude Code (voir `SECURITY.md`) - Une règle Semgrep dans la CI - Le scan de secrets GitHub (Dependabot) @@ -582,12 +665,12 @@ Cette règle est vérifiée par : ### Infrastructure cible (cf. ADR 001) -| Composant | Plateforme | URL | -|---|---|---| -| Frontend | Cloudflare Pages | `https://expria.app` | -| Backend API | Render (Frankfurt) | `https://api.expria.app` | -| DNS | Vercel (actuellement) | — | -| Base de données | Supabase (Frankfurt) | — | +| Composant | Plateforme | URL | +| --------------- | --------------------- | ------------------------ | +| Frontend | Cloudflare Pages | `https://expria.app` | +| Backend API | Render (Frankfurt) | `https://api.expria.app` | +| DNS | Vercel (actuellement) | — | +| Base de données | Supabase (Frankfurt) | — | ### Workflow de déploiement @@ -622,13 +705,13 @@ Tests ciblés sur la logique critique, pas exhaustifs. On copie la stratégie ba ### Fichiers obligatoirement couverts -| Fichier | Nombre de tests minimum | -|---|---| -| `src/entities/user/__tests__/hasAccess.test.ts` | 14+ | -| `src/entities/user/__tests__/canSimulate.test.ts` | 7 | -| `src/entities/report/__tests__/floutage.test.ts` | 8+ (un par critère à flouter × 3 plans) | -| `src/features/t2-live/state/__tests__/t2-machine.test.ts` | 6+ (transitions d'états) | -| `src/features/dashboard/hooks/__tests__/usePlan.test.ts` | 3+ (cache, refetch, invalidation) | +| Fichier | Nombre de tests minimum | +| --------------------------------------------------------- | --------------------------------------- | +| `src/entities/user/__tests__/hasAccess.test.ts` | 14+ | +| `src/entities/user/__tests__/canSimulate.test.ts` | 7 | +| `src/entities/report/__tests__/floutage.test.ts` | 8+ (un par critère à flouter × 3 plans) | +| `src/features/t2-live/state/__tests__/t2-machine.test.ts` | 6+ (transitions d'états) | +| `src/features/dashboard/hooks/__tests__/usePlan.test.ts` | 3+ (cache, refetch, invalidation) | ### Fichiers non testés (par design) @@ -655,39 +738,50 @@ Un échec sur l'un de ces jobs bloque le merge. Ces règles sont héritées de `DEVELOPMENT_PRINCIPLES.md` backend et adaptées au frontend. ### Règle 1 — Séparation stricte + Le frontend affiche des données et relaie des actions. Aucune logique métier. ### Règle 2 — Source de vérité unique pour les plans + `src/entities/user/access.ts` est identique à `expria-backend/src/lib/access.ts`. Toute modification se fait dans les deux dépôts, dans le même commit logique. ### Règle 3 — Maximum 3 fichiers par étape + Hérité du backend. Si une tâche nécessite plus de 3 fichiers, elle est découpée. ### Règle 4 — Plan avant code + Aucune session Claude Code ne commence à coder sans plan validé. ### Règle 5 — Tests verts avant de continuer + `npm run test` et `npm run typecheck` doivent passer après chaque étape. ### Règle 6 — Jamais de clé privée dans le frontend + Variables `VITE_*` uniquement. Cf. section 7. ### Règle 7 — Jamais de `if (plan === 'xxx')` + Toute vérification de permission passe par `hasAccess()` ou `canSimulate()`. Cf. ADR 005. ### Règle 8 — Jamais de logique métier dans `features/` + Les règles de floutage, de quotas, de permissions vivent dans `entities/*/lib.ts`. Les composants de `features/` appellent ces fonctions. ### Règle 9 — Jamais d'appel direct à Supabase pour les données métier + Supabase côté frontend est **uniquement** pour l'authentification. Toute lecture/écriture passe par le backend Hono. ### Règle 10 — Signaler tout écart par rapport au plan + Identique à la Règle H backend. --- ## 11. Historique des versions de ce document -| Version | Date | Auteur | Changements | -|---|---|---|---| -| 1.0 | 2026-04-17 | Hermann (avec assistance Claude) | Création initiale | +| Version | Date | Auteur | Changements | +| ------- | ---------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1.0 | 2026-04-17 | Hermann (avec assistance Claude) | Création initiale | +| 1.1 | 2026-04-25 | Hermann (avec assistance Claude) | FTD-25 + FTD-26 — §3 reflète l'arborescence réelle ; ajout note `app/` (entry points + layout) ; ajout convention `shared/ui/` vs `shared/components/ui/` | diff --git a/docs/TECH_DEBT.md b/docs/TECH_DEBT.md index 0ba0de6..36d3143 100644 --- a/docs/TECH_DEBT.md +++ b/docs/TECH_DEBT.md @@ -1,6 +1,6 @@ # TECH_DEBT.md — Expria Frontend -> **Document de référence — Version 1.19** +> **Document de référence — Version 1.23** > Ce document recense les décisions techniques prises par pragmatisme qui devront être revisitées, les stubs temporaires, et les fonctionnalités reportées. > À mettre à jour après chaque session de développement. > @@ -271,7 +271,7 @@ Vient du pattern `c.json(result, result.status)` où `result` contient déjà `s ### FTD-25 — Mise à jour ARCHITECTURE.md §3 (arborescence réelle) **Priorité :** 🟢 Mineur -**Statut :** Ouvert +**Statut :** Résolu — 2026-04-25 **Estimation de session :** 1h **Description :** ARCHITECTURE.md §3 ne liste pas `entities/patterns`, `features/historique`, `features/progression`, `features/design-system` (ajoutés aux Sprints 3.6c et 3.7). Les composants layout (`AppLayout`, `Sidebar`, `MobileHeader`, `BottomNav`, `MaintenancePage`) sont dans `app/` alors que §3 ne prévoit que `providers`, `router`, `main` dans ce dossier. @@ -287,7 +287,7 @@ Vient du pattern `c.json(result, result.status)` où `result` contient déjà `s ### FTD-26 — Clarifier cohabitation `shared/ui/` vs `shared/components/ui/` **Priorité :** 🟡 Important -**Statut :** Ouvert +**Statut :** Résolu — 2026-04-25 **Estimation de session :** 2h **Description :** Deux conventions UI cohabitent sans documentation : @@ -493,6 +493,8 @@ Frontend : | FTD-18 | SimulationForm utilise encore le shadcn Button au lieu de la primitive `@/shared/ui/Button` | 2026-04-22 | Résolu au Sprint 3.5. Remplacement de l'import `@/shared/components/ui/button` par `@/shared/ui/Button` dans `SimulationForm.tsx`. Aucun variant à adapter (usage du Button sans prop `variant` → `primary` par défaut dans les deux implémentations). Les 7 autres consommateurs shadcn (`Login/RegisterPage`, `PaywallBanner`, `DesignSystemPage`, `ThemeToggle`, `dialog.tsx`) restent hors scope de cette FTD. | | FTD-23 | `useAutosave` continue après correction → 400 VALIDATION_ERROR | 2026-04-23 | `enabled` corrigé dans `SimulationForm` (`!isSubmitting && step !== 'done' && step !== 'correcting'`). Le `beforeunload` handler et le debounce lisent `enabled` via `latestRef` — tous deux neutralisés dès que `step` transite. 2 tests de régression ajoutés dans `useAutosave.test.ts` : (a) `enabled` true→false annule le debounce en cours, (b) `enabled=false` + `beforeunload` = aucun appel. | | FTD-24 | Pas de polling automatique pour exercices / modèle `pending` | 2026-04-23 | Polling conditionnel dans `useRapport` via `refetchInterval: 3000` tant que `exercices_status === 'pending' \|\| modele_status === 'pending'`. Arrêt automatique dès que les deux sortent de pending (ready ou error). Timeout global 2 min → `hasTimedOut = true` + bouton « Réessayer » dans `JobStatusFallback` (primitive `@/shared/ui/Button`). `refetch()` réinitialise le flag et relance le polling. `staleTime: Infinity` conservé. 5 tests nouveaux dans `useRapport.test.tsx`. | +| FTD-25 | Mise à jour ARCHITECTURE.md §3 (arborescence réelle) | 2026-04-25 | §3 réécrite : `app/` documenté avec entry points + layout (AppLayout, Sidebar, Topbar, BottomNav, MaintenancePage) ; ajout `entities/{patterns,presentation,transcription}` ; ajout `features/{historique,progression,design-system}` ; extension `simulations/` (pages EO, components/rapport/, lib/, state/) ; mise à jour `shared/`. `t2-live/` et `billing/` retirés (non implémentés — voir ROADMAP). Note explicative ajoutée sous `app/`. Bump doc v1.1. | +| FTD-26 | Clarifier cohabitation `shared/ui/` vs `shared/components/ui/` | 2026-04-25 | Section dédiée ajoutée dans ARCHITECTURE.md §3 : tableau de distinction (PascalCase wrappers Expria vs kebab-case primitives shadcn) + règle d'évolution (toute nouvelle primitive Expria va dans `shared/ui/`, `shared/components/ui/` réservé à la CLI shadcn). Aucun fichier déplacé — documentation uniquement. | | FTD-19 | Token `--shadow-focus` absent de `src/index.css` | 2026-04-22 | Résolu au Sprint 3.5. Ajout de `--shadow-focus: 0 0 0 3px rgba(27, 79, 216, 0.18)` dans `@theme {}` (valeur conforme à `DESIGN_SYSTEM.md §2`) et `--shadow-focus: 0 0 0 3px rgba(91, 127, 255, 0.32)` dans `.dark {}` (recalculé sur la teinte expria dark `#5B7FFF`). Tailwind 4 génère automatiquement l'utility `shadow-focus`. Migration de 5 occurrences `ring-2 ring-expria/20` → `shadow-focus` dans `Button.tsx`, `Card.tsx`, `SimulationForm.tsx` (×3), `SpecialCharsKeyboard.tsx`. Factorisation bonus : className dupliquée des boutons secondaires de `SimulationForm` extraite en const `secondaryActionBtn`. | --- @@ -524,3 +526,4 @@ Frontend : | 1.20 | 2026-04-25 | Sprint 4c-1 — Ajout FTD-30 🟡 (rotation token Deepgram sans grace period), FTD-31 🟢 (page enregistrement EO non resumable), FTD-32 🟢 (Safari iOS non testé), FTD-33 🟢 (EO_T2_LIVE verrouillé en dur). 14 FTD actives (cap 15 respecté). | | 1.21 | 2026-04-25 | Sprint 4c-2 — Ajout FTD-34 🟢 (présentation T1 en localStorage clair), FTD-35 🟡 (refresh sans simulation active sur PresentationGenereeT1Page). **16 FTD actives — cap dépassé temporairement, accepté par Hermann pour cette session ; clean à planifier au prochain Sprint.** | | 1.22 | 2026-04-25 | Sprint 4c-3 — Ajout FTD-36 🟡 (upload audio base64 sans progression), FTD-37 🟢 (code Deepgram live dormant à trancher). FTD-30 dégradée 🟡→🟢 et passée en « gelé » (Deepgram live mis en pause). **17 FTD actives — cap toujours dépassé, clean prioritaire au Sprint suivant.** | +| 1.23 | 2026-04-25 | FTD-25 et FTD-26 fermées (ARCHITECTURE.md §3 reflète l'arborescence réelle + convention `shared/ui/` vs `shared/components/ui/` documentée). 15 FTD actives (cap respecté). |