docs(architecture): refléter l'arborescence réelle + documenter convention shared/ui (FTD-25, FTD-26)
This commit is contained in:
parent
8175438eea
commit
5188714235
2 changed files with 212 additions and 115 deletions
|
|
@ -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) :
|
Versions officielles au 2026-04-17 (cf. ADR 006 pour la justification) :
|
||||||
|
|
||||||
| Domaine | Choix | Version | Justification |
|
| Domaine | Choix | Version | Justification |
|
||||||
|---|---|---|---|
|
| -------------- | ------------------------------ | ----------------- | ------------------------------------------------------------------------------ |
|
||||||
| Framework UI | React | 19.2.x | Compilateur React, Actions, useOptimistic |
|
| Framework UI | React | 19.2.x | Compilateur React, Actions, useOptimistic |
|
||||||
| Build tool | Vite | 8.0.x | HMR rapide, moteur Rolldown, config minimale |
|
| 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 |
|
| 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) |
|
| 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 |
|
| 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 |
|
| 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 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) |
|
| É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 |
|
| 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) |
|
| 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) |
|
| 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) |
|
| Tests | Vitest + React Testing Library | latest | Parité avec backend (qui utilise Vitest) |
|
||||||
| Lint + Format | ESLint + Prettier | 9.x + latest | Standard |
|
| Lint + Format | ESLint + Prettier | 9.x + latest | Standard |
|
||||||
| CI | GitHub Actions | — | Typecheck + tests + `npm audit` |
|
| CI | GitHub Actions | — | Typecheck + tests + `npm audit` |
|
||||||
|
|
||||||
**Choix motivés par ADR :**
|
**Choix motivés par ADR :**
|
||||||
|
|
||||||
- ADR 001 : Cloudflare Pages (hébergement)
|
- ADR 001 : Cloudflare Pages (hébergement)
|
||||||
- ADR 002 : Découplage `auth-client` / `api-client`
|
- ADR 002 : Découplage `auth-client` / `api-client`
|
||||||
- ADR 003 : Pas de Zustand pour la V2
|
- ADR 003 : Pas de Zustand pour la V2
|
||||||
|
|
@ -95,10 +96,16 @@ expria-frontend/
|
||||||
│ └── 005-has-access-typed-strict.md
|
│ └── 005-has-access-typed-strict.md
|
||||||
│
|
│
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── app/ # CONFIGURATION ET ENTRY POINTS
|
│ ├── app/ # ENTRY POINTS + LAYOUT DE LA COQUILLE
|
||||||
│ │ ├── providers.tsx # QueryClientProvider + AuthProvider + Router
|
│ │ ├── main.tsx # Entry point React (montage DOM)
|
||||||
|
│ │ ├── providers.tsx # QueryClientProvider + ThemeProvider + Router
|
||||||
│ │ ├── router.tsx # Routes déclaratives
|
│ │ ├── 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)
|
│ ├── entities/ # OBJETS MÉTIER (indépendants de l'UI)
|
||||||
│ │ ├── user/
|
│ │ ├── user/
|
||||||
|
|
@ -106,67 +113,109 @@ expria-frontend/
|
||||||
│ │ │ ├── lib.ts # hasAccess(), canSimulate()
|
│ │ │ ├── lib.ts # hasAccess(), canSimulate()
|
||||||
│ │ │ ├── access.ts # IDENTIQUE à expria-backend/src/lib/access.ts
|
│ │ │ ├── access.ts # IDENTIQUE à expria-backend/src/lib/access.ts
|
||||||
│ │ │ ├── api.ts # GET /plans/status, POST /auth/verify-token
|
│ │ │ ├── api.ts # GET /plans/status, POST /auth/verify-token
|
||||||
|
│ │ │ ├── query-keys.ts # Constantes de clés TanStack (PLAN_QUERY_KEY)
|
||||||
│ │ │ └── __tests__/
|
│ │ │ └── __tests__/
|
||||||
│ │ │ ├── hasAccess.test.ts
|
|
||||||
│ │ │ └── canSimulate.test.ts
|
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ ├── production/
|
│ │ ├── production/
|
||||||
│ │ │ ├── types.ts # Production, Tache, Mode
|
│ │ │ ├── types.ts # Production, Tache, Mode
|
||||||
│ │ │ ├── lib.ts # helpers (format tache, etc.)
|
│ │ │ ├── lib.ts # helpers (format tache, etc.)
|
||||||
│ │ │ └── api.ts # POST /simulations, GET /simulations/:id
|
│ │ │ └── api.ts # POST /simulations, GET /simulations/:id, PATCH /:id/contenu
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ └── report/
|
│ │ ├── report/
|
||||||
│ │ ├── types.ts # Report, Critere
|
│ │ │ ├── types.ts # Report, Critere, Revelation, Diagnostic
|
||||||
│ │ ├── lib.ts # Logique de floutage selon plan
|
│ │ │ ├── lib.ts # Logique de floutage selon plan
|
||||||
│ │ ├── api.ts # POST /corrections/ee, POST /corrections/eo
|
│ │ │ ├── api.ts # POST /corrections/ee, POST /corrections/eo
|
||||||
│ │ └── __tests__/
|
│ │ │ └── __tests__/
|
||||||
│ │ └── floutage.test.ts
|
│ │ │
|
||||||
|
│ │ ├── 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)
|
│ ├── features/ # UI (composants + pages + hooks)
|
||||||
│ │ ├── auth/
|
│ │ ├── auth/
|
||||||
│ │ │ ├── components/ # LoginForm, RegisterForm, ProtectedRoute
|
│ │ │ ├── components/ # ProtectedRoute
|
||||||
│ │ │ ├── pages/ # LoginPage, RegisterPage
|
│ │ │ ├── pages/ # LoginPage, RegisterPage
|
||||||
│ │ │ └── hooks/ # useAuth
|
│ │ │ └── hooks/ # useAuth
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ ├── dashboard/
|
│ │ ├── dashboard/
|
||||||
│ │ │ ├── components/ # DashboardFreeView, DashboardStandardView, DashboardPremiumView
|
│ │ │ ├── components/ # DashboardFreeView/StandardView/PremiumView,
|
||||||
|
│ │ │ │ # NclcHero, StatCards, RecentSimulations,
|
||||||
|
│ │ │ │ # NextStepCard, PaywallBanner, MonProfilPreparation
|
||||||
│ │ │ ├── pages/ # DashboardPage (orchestre les vues selon le plan)
|
│ │ │ ├── pages/ # DashboardPage (orchestre les vues selon le plan)
|
||||||
│ │ │ └── hooks/ # usePlan
|
│ │ │ └── hooks/ # usePlan
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ ├── simulations/
|
│ │ ├── simulations/
|
||||||
│ │ │ ├── components/ # SimulationForm, AudioRecorder, TimerExam
|
│ │ │ ├── components/ # SimulationForm, AudioRecorder, TimerDisplay,
|
||||||
│ │ │ ├── pages/ # SimulationPage, RapportPage
|
│ │ │ │ │ # TaskSelector, SujetCard/Display, IdeesSuggestions,
|
||||||
│ │ │ └── hooks/ # useSimulation, useExamMode
|
│ │ │ │ │ # 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/
|
│ │ ├── historique/ # Sprint 3.7 — liste des productions
|
||||||
│ │ │ ├── components/ # DialogueView, AudioVisualizer
|
│ │ │ ├── components/ # SimulationsList, SimulationListItem
|
||||||
│ │ │ ├── pages/ # T2LivePage
|
│ │ │ ├── pages/ # HistoriquePage
|
||||||
│ │ │ ├── hooks/ # useT2LiveSession
|
│ │ │ └── hooks/ # useSimulationsList
|
||||||
│ │ │ ├── lib/
|
|
||||||
│ │ │ │ ├── ws-client.ts # WebSocket + reconnexion
|
|
||||||
│ │ │ │ └── audio.ts # Capture PCM + lecture réponse
|
|
||||||
│ │ │ └── state/
|
|
||||||
│ │ │ └── t2-machine.ts # State machine (idle → connecting → listening → ...)
|
|
||||||
│ │ │
|
│ │ │
|
||||||
│ │ └── billing/
|
│ │ ├── progression/ # Sprint 3.6c — analyse patterns Premium
|
||||||
│ │ ├── components/ # PaymentSummary
|
│ │ │ ├── components/ # PreparationIndexHero, PatternsList,
|
||||||
│ │ ├── pages/ # PricingPage, CheckoutPage, UpgradePage
|
│ │ │ │ # PatternExerciceCard, ProgressionPremium,
|
||||||
│ │ └── hooks/ # useStripeCheckout
|
│ │ │ │ # 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
|
│ ├── shared/ # CODE RÉUTILISABLE NON MÉTIER
|
||||||
|
│ │ ├── ui/ # PRIMITIVES EXPRIA (PascalCase) — voir note ci-dessous
|
||||||
|
│ │ │ ├── Button.tsx
|
||||||
|
│ │ │ ├── Card.tsx
|
||||||
|
│ │ │ └── Badge.tsx
|
||||||
│ │ ├── components/
|
│ │ ├── components/
|
||||||
│ │ │ ├── ui/ # Button, Modal, Badge (shadcn/ui)
|
│ │ │ ├── ui/ # PRIMITIVES SHADCN BRUTES (kebab-case) — voir note
|
||||||
│ │ │ ├── PaywallModal.tsx # Blocage + boutons upgrade
|
│ │ │ │ ├── avatar.tsx
|
||||||
│ │ │ └── Spinner.tsx
|
│ │ │ │ ├── badge.tsx
|
||||||
│ │ ├── hooks/ # useDebounce, useLocalStorage
|
│ │ │ │ ├── button.tsx
|
||||||
|
│ │ │ │ ├── dialog.tsx
|
||||||
|
│ │ │ │ ├── input.tsx
|
||||||
|
│ │ │ │ ├── label.tsx
|
||||||
|
│ │ │ │ ├── progress.tsx
|
||||||
|
│ │ │ │ └── separator.tsx
|
||||||
|
│ │ │ ├── Logo.tsx
|
||||||
|
│ │ │ └── ThemeToggle.tsx
|
||||||
|
│ │ ├── hooks/ # useTheme
|
||||||
│ │ ├── lib/
|
│ │ ├── lib/
|
||||||
│ │ │ ├── auth-client.ts # Supabase Auth uniquement (ADR 002)
|
│ │ │ ├── auth-client.ts # Supabase Auth uniquement (ADR 002)
|
||||||
│ │ │ ├── api-client.ts # Fetch + retry + timeout + logging (ADR 002)
|
│ │ │ ├── api-client.ts # Fetch + retry + timeout + logging (ADR 002)
|
||||||
│ │ │ ├── query-client.ts # Configuration TanStack Query
|
│ │ │ ├── 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/
|
│ │ ├── types/
|
||||||
│ │ │ ├── api.ts # ApiResponse<T>, ApiError
|
│ │ │ └── api.ts # ApiError, ApiErrorCode, FrontendErrorCode
|
||||||
│ │ │ └── common.ts # Types utilitaires
|
|
||||||
│ │ └── config/
|
│ │ └── config/
|
||||||
│ │ └── env.ts # Validation des variables d'environnement au démarrage
|
│ │ └── env.ts # Validation des variables d'environnement au démarrage
|
||||||
│ │
|
│ │
|
||||||
|
|
@ -186,6 +235,29 @@ expria-frontend/
|
||||||
└── README.md
|
└── 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/<name>` et l'expose sous une API simplifiée dans `shared/ui/<Name>.tsx`.
|
||||||
|
|
||||||
|
Cette dualité est tracée dans `TECH_DEBT.md` (FTD-26) — documentée, pas à fusionner.
|
||||||
|
|
||||||
### Règles de dépendance entre dossiers
|
### Règles de dépendance entre dossiers
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -264,12 +336,12 @@ Composant React
|
||||||
|
|
||||||
**Gestion des close codes côté frontend :**
|
**Gestion des close codes côté frontend :**
|
||||||
|
|
||||||
| Close code | Cause | Action côté frontend |
|
| Close code | Cause | Action côté frontend |
|
||||||
|---|---|---|
|
| ---------- | ------------------------ | ------------------------------------------------------- |
|
||||||
| 1000 | Fermeture normale | State → 'ended', afficher le rapport |
|
| 1000 | Fermeture normale | State → 'ended', afficher le rapport |
|
||||||
| 4001 | AUTH_REQUIRED | State → 'error', redirect `/login` |
|
| 4001 | AUTH_REQUIRED | State → 'error', redirect `/login` |
|
||||||
| 4003 | PLAN_INSUFFICIENT | State → 'error', afficher PaywallModal Premium |
|
| 4003 | PLAN_INSUFFICIENT | State → 'error', afficher PaywallModal Premium |
|
||||||
| Autre | Erreur réseau ou serveur | State → 'error', message générique + bouton "Réessayer" |
|
| Autre | Erreur réseau ou serveur | State → 'error', message générique + bouton "Réessayer" |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -349,44 +421,44 @@ export interface ApiError {
|
||||||
error: true
|
error: true
|
||||||
code: ApiErrorCode
|
code: ApiErrorCode
|
||||||
message: string
|
message: string
|
||||||
status?: number // quirk backend (simulations, corrections)
|
status?: number // quirk backend (simulations, corrections)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiErrorCode =
|
export type ApiErrorCode =
|
||||||
| 'AUTH_REQUIRED' // 401 — JWT absent, invalide ou expiré
|
| 'AUTH_REQUIRED' // 401 — JWT absent, invalide ou expiré
|
||||||
| 'PLAN_INSUFFICIENT' // 403 — feature non disponible pour ce plan
|
| 'PLAN_INSUFFICIENT' // 403 — feature non disponible pour ce plan
|
||||||
| 'QUOTA_REACHED' // 403 — quota de simulations Free épuisé
|
| 'QUOTA_REACHED' // 403 — quota de simulations Free épuisé
|
||||||
| 'VALIDATION_ERROR' // 400 — corps de requête invalide (simulations, corrections)
|
| 'VALIDATION_ERROR' // 400 — corps de requête invalide (simulations, corrections)
|
||||||
| 'INVALID_BODY' // 400 — corps de requête invalide (plans, stripe) — voir note
|
| 'INVALID_BODY' // 400 — corps de requête invalide (plans, stripe) — voir note
|
||||||
| 'INVALID_PLAN' // 400 — valeur de plan inconnue
|
| 'INVALID_PLAN' // 400 — valeur de plan inconnue
|
||||||
| 'NO_ACTIVE_SUBSCRIPTION' // 400 — tentative d'upgrade sans abonnement actif
|
| 'NO_ACTIVE_SUBSCRIPTION' // 400 — tentative d'upgrade sans abonnement actif
|
||||||
| 'SIMULATION_NOT_FOUND' // 404 — simulation inexistante ou non accessible
|
| 'SIMULATION_NOT_FOUND' // 404 — simulation inexistante ou non accessible
|
||||||
| 'STRIPE_WEBHOOK_INVALID' // 400 — signature webhook invalide
|
| 'STRIPE_WEBHOOK_INVALID' // 400 — signature webhook invalide
|
||||||
| 'INTERNAL_ERROR' // 500 — erreur serveur inattendue
|
| 'INTERNAL_ERROR' // 500 — erreur serveur inattendue
|
||||||
|
|
||||||
// Erreurs générées côté frontend uniquement (pas envoyées par le backend)
|
// Erreurs générées côté frontend uniquement (pas envoyées par le backend)
|
||||||
export type FrontendErrorCode =
|
export type FrontendErrorCode =
|
||||||
| 'TIMEOUT' // timeout côté client (AbortController)
|
| 'TIMEOUT' // timeout côté client (AbortController)
|
||||||
| 'NETWORK_ERROR' // pas de réponse réseau
|
| 'NETWORK_ERROR' // pas de réponse réseau
|
||||||
| 'PARSE_ERROR' // réponse non-JSON
|
| '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.
|
> **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
|
### Codes d'erreur — mapping HTTP
|
||||||
|
|
||||||
| Code backend | HTTP | Signification | Routes émettrices |
|
| Code backend | HTTP | Signification | Routes émettrices |
|
||||||
|---|---|---|---|
|
| ------------------------ | ---- | ---------------------------------------------------- | --------------------------------------- |
|
||||||
| `AUTH_REQUIRED` | 401 | JWT absent, invalide, expiré, ou profil introuvable | middleware, corrections |
|
| `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 |
|
| `PLAN_INSUFFICIENT` | 403 | Feature réservée à un plan supérieur | middleware, simulations |
|
||||||
| `QUOTA_REACHED` | 403 | 5/5 simulations utilisées (plan Free) | simulations |
|
| `QUOTA_REACHED` | 403 | 5/5 simulations utilisées (plan Free) | simulations |
|
||||||
| `VALIDATION_ERROR` | 400 | Corps de requête invalide (simulations, corrections) | simulations, corrections |
|
| `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_BODY` | 400 | Corps de requête invalide (plans, stripe) | plans, stripe |
|
||||||
| `INVALID_PLAN` | 400 | Valeur de plan inconnue dans le payload | plans, stripe |
|
| `INVALID_PLAN` | 400 | Valeur de plan inconnue dans le payload | plans, stripe |
|
||||||
| `NO_ACTIVE_SUBSCRIPTION` | 400 | Upgrade prorata sans abonnement actif | plans |
|
| `NO_ACTIVE_SUBSCRIPTION` | 400 | Upgrade prorata sans abonnement actif | plans |
|
||||||
| `SIMULATION_NOT_FOUND` | 404 | Simulation inexistante ou non accessible | corrections |
|
| `SIMULATION_NOT_FOUND` | 404 | Simulation inexistante ou non accessible | corrections |
|
||||||
| `STRIPE_WEBHOOK_INVALID` | 400 | Signature webhook invalide | stripe |
|
| `STRIPE_WEBHOOK_INVALID` | 400 | Signature webhook invalide | stripe |
|
||||||
| `INTERNAL_ERROR` | 500 | Erreur serveur inattendue | plans, stripe, corrections, simulations |
|
| `INTERNAL_ERROR` | 500 | Erreur serveur inattendue | plans, stripe, corrections, simulations |
|
||||||
|
|
||||||
### Pattern `apiFetch<T>`
|
### Pattern `apiFetch<T>`
|
||||||
|
|
||||||
|
|
@ -402,9 +474,14 @@ const { data, error, isLoading } = useQuery({
|
||||||
// error est de type ApiError | null
|
// error est de type ApiError | null
|
||||||
if (error) {
|
if (error) {
|
||||||
switch (error.code) {
|
switch (error.code) {
|
||||||
case 'AUTH_REQUIRED': redirectToLogin(); break
|
case 'AUTH_REQUIRED':
|
||||||
case 'QUOTA_REACHED': openUpgradeModal(); break
|
redirectToLogin()
|
||||||
default: showGenericErrorToast()
|
break
|
||||||
|
case 'QUOTA_REACHED':
|
||||||
|
openUpgradeModal()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
showGenericErrorToast()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -488,9 +565,18 @@ export const PLANS = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPlanPermissions(plan: Plan) { /* ... */ }
|
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 canUserSimulate(user: { plan: string; simulations_used: number }): {
|
||||||
|
allowed
|
||||||
|
reason?
|
||||||
|
} {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
export function checkFeatureAccess(plan: Plan, feature: Feature): boolean {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Alias frontend-idiomatiques
|
### Alias frontend-idiomatiques
|
||||||
|
|
@ -499,11 +585,7 @@ Le fichier `src/entities/user/lib.ts` ré-exporte ces fonctions sous des noms st
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// src/entities/user/lib.ts
|
// src/entities/user/lib.ts
|
||||||
import {
|
import { canUserSimulate, checkFeatureAccess, getPlanPermissions } from './access'
|
||||||
canUserSimulate,
|
|
||||||
checkFeatureAccess,
|
|
||||||
getPlanPermissions,
|
|
||||||
} from './access'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias frontend-idiomatique de checkFeatureAccess.
|
* Alias frontend-idiomatique de checkFeatureAccess.
|
||||||
|
|
@ -568,6 +650,7 @@ VITE_MAINTENANCE_MODE=false # true = affiche MaintenancePage avant tout pr
|
||||||
- `STRIPE_WEBHOOK_SECRET`
|
- `STRIPE_WEBHOOK_SECRET`
|
||||||
|
|
||||||
Cette règle est vérifiée par :
|
Cette règle est vérifiée par :
|
||||||
|
|
||||||
- Le plugin Security Guidance de Claude Code (voir `SECURITY.md`)
|
- Le plugin Security Guidance de Claude Code (voir `SECURITY.md`)
|
||||||
- Une règle Semgrep dans la CI
|
- Une règle Semgrep dans la CI
|
||||||
- Le scan de secrets GitHub (Dependabot)
|
- Le scan de secrets GitHub (Dependabot)
|
||||||
|
|
@ -582,12 +665,12 @@ Cette règle est vérifiée par :
|
||||||
|
|
||||||
### Infrastructure cible (cf. ADR 001)
|
### Infrastructure cible (cf. ADR 001)
|
||||||
|
|
||||||
| Composant | Plateforme | URL |
|
| Composant | Plateforme | URL |
|
||||||
|---|---|---|
|
| --------------- | --------------------- | ------------------------ |
|
||||||
| Frontend | Cloudflare Pages | `https://expria.app` |
|
| Frontend | Cloudflare Pages | `https://expria.app` |
|
||||||
| Backend API | Render (Frankfurt) | `https://api.expria.app` |
|
| Backend API | Render (Frankfurt) | `https://api.expria.app` |
|
||||||
| DNS | Vercel (actuellement) | — |
|
| DNS | Vercel (actuellement) | — |
|
||||||
| Base de données | Supabase (Frankfurt) | — |
|
| Base de données | Supabase (Frankfurt) | — |
|
||||||
|
|
||||||
### Workflow de déploiement
|
### 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
|
### Fichiers obligatoirement couverts
|
||||||
|
|
||||||
| Fichier | Nombre de tests minimum |
|
| Fichier | Nombre de tests minimum |
|
||||||
|---|---|
|
| --------------------------------------------------------- | --------------------------------------- |
|
||||||
| `src/entities/user/__tests__/hasAccess.test.ts` | 14+ |
|
| `src/entities/user/__tests__/hasAccess.test.ts` | 14+ |
|
||||||
| `src/entities/user/__tests__/canSimulate.test.ts` | 7 |
|
| `src/entities/user/__tests__/canSimulate.test.ts` | 7 |
|
||||||
| `src/entities/report/__tests__/floutage.test.ts` | 8+ (un par critère à flouter × 3 plans) |
|
| `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/t2-live/state/__tests__/t2-machine.test.ts` | 6+ (transitions d'états) |
|
||||||
| `src/features/dashboard/hooks/__tests__/usePlan.test.ts` | 3+ (cache, refetch, invalidation) |
|
| `src/features/dashboard/hooks/__tests__/usePlan.test.ts` | 3+ (cache, refetch, invalidation) |
|
||||||
|
|
||||||
### Fichiers non testés (par design)
|
### 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.
|
Ces règles sont héritées de `DEVELOPMENT_PRINCIPLES.md` backend et adaptées au frontend.
|
||||||
|
|
||||||
### Règle 1 — Séparation stricte
|
### Règle 1 — Séparation stricte
|
||||||
|
|
||||||
Le frontend affiche des données et relaie des actions. Aucune logique métier.
|
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
|
### 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.
|
`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
|
### 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.
|
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
|
### Règle 4 — Plan avant code
|
||||||
|
|
||||||
Aucune session Claude Code ne commence à coder sans plan validé.
|
Aucune session Claude Code ne commence à coder sans plan validé.
|
||||||
|
|
||||||
### Règle 5 — Tests verts avant de continuer
|
### Règle 5 — Tests verts avant de continuer
|
||||||
|
|
||||||
`npm run test` et `npm run typecheck` doivent passer après chaque étape.
|
`npm run test` et `npm run typecheck` doivent passer après chaque étape.
|
||||||
|
|
||||||
### Règle 6 — Jamais de clé privée dans le frontend
|
### Règle 6 — Jamais de clé privée dans le frontend
|
||||||
|
|
||||||
Variables `VITE_*` uniquement. Cf. section 7.
|
Variables `VITE_*` uniquement. Cf. section 7.
|
||||||
|
|
||||||
### Règle 7 — Jamais de `if (plan === 'xxx')`
|
### Règle 7 — Jamais de `if (plan === 'xxx')`
|
||||||
|
|
||||||
Toute vérification de permission passe par `hasAccess()` ou `canSimulate()`. Cf. ADR 005.
|
Toute vérification de permission passe par `hasAccess()` ou `canSimulate()`. Cf. ADR 005.
|
||||||
|
|
||||||
### Règle 8 — Jamais de logique métier dans `features/`
|
### 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.
|
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
|
### 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.
|
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
|
### Règle 10 — Signaler tout écart par rapport au plan
|
||||||
|
|
||||||
Identique à la Règle H backend.
|
Identique à la Règle H backend.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. Historique des versions de ce document
|
## 11. Historique des versions de ce document
|
||||||
|
|
||||||
| Version | Date | Auteur | Changements |
|
| Version | Date | Auteur | Changements |
|
||||||
|---|---|---|---|
|
| ------- | ---------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| 1.0 | 2026-04-17 | Hermann (avec assistance Claude) | Création initiale |
|
| 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/` |
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# TECH_DEBT.md — Expria Frontend
|
# 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.
|
> 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.
|
> À 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)
|
### FTD-25 — Mise à jour ARCHITECTURE.md §3 (arborescence réelle)
|
||||||
|
|
||||||
**Priorité :** 🟢 Mineur
|
**Priorité :** 🟢 Mineur
|
||||||
**Statut :** Ouvert
|
**Statut :** Résolu — 2026-04-25
|
||||||
**Estimation de session :** 1h
|
**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.
|
**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/`
|
### FTD-26 — Clarifier cohabitation `shared/ui/` vs `shared/components/ui/`
|
||||||
|
|
||||||
**Priorité :** 🟡 Important
|
**Priorité :** 🟡 Important
|
||||||
**Statut :** Ouvert
|
**Statut :** Résolu — 2026-04-25
|
||||||
**Estimation de session :** 2h
|
**Estimation de session :** 2h
|
||||||
**Description :** Deux conventions UI cohabitent sans documentation :
|
**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-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-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-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`. |
|
| 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.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.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.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é). |
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue