- CHANGELOG: entrées Sprint 5b/5c/5d (198 → 219 tests)
- ROADMAP: Sprint 5 Billing marqué ✅
- TECH_DEBT: FTD-42 (modal prorata) + FTD-43 (race webhook) ouvertes. v1.26. 21 FTD actives.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
48 KiB
48 KiB
Changelog — Expria Frontend
Toutes les modifications notables du projet frontend sont documentées dans ce fichier.
Format basé sur Keep a Changelog.
Convention
Chaque entrée suit ce format :
## [Unreleased] — YYYY-MM-DD — Session <nom>
### Added (nouveautés)
- ...
### Changed (modifications)
- ...
### Fixed (corrections)
- ...
### Removed (suppressions)
- ...
### Security (sécurité)
- ...
[Unreleased] — 2026-04-26 — Sprint 5d — Customer Portal + page Paramètres
Added
src/features/billing/hooks/useCustomerPortal.ts— hook{ openPortal, isLoading, error }autour decreateCustomerPortalSession+ redirect full-page vers Stripe Customer Portal. Message d'erreur backend (NO_ACTIVE_SUBSCRIPTION) propagé tel quel.src/features/billing/components/AccountBillingSection.tsx— section UICard: badge plan + CTA contextuel (Free → lien « Voir les plans » vers/plan; Standard/Premium → bouton « Gérer mon abonnement » → Customer Portal).src/features/account/pages/ParametresPage.tsx— page conteneur/parametresavec section Abonnement + section Session (bouton « Se déconnecter » →signOut()+queryClient.clear()+navigate('/login')).- 6 tests (3 useCustomerPortal + 3 AccountBillingSection).
Changed
src/features/billing/pages/PricingPage.tsx— branche Standard→Premium routée versuseCustomerPortal.openPortal()au lieu de Stripe Checkout direct (le Customer Portal Stripe affiche le montant prorata + confirmation native).buildCtaConfigsrefactor : signature(plan, isStandardPending, isPremiumPending, onUpgrade); loading state combiné selon source ; erreur unifiéecheckoutError ?? portalError.src/features/billing/__tests__/PricingPage.test.tsx— 6e test : Standard click « Passer en Premium » →createCustomerPortalSessionappelé (etcreateCheckoutSessionnon appelé).src/app/router.tsx—/parametres→<ParametresPage />(sous PrivateLayout).
Notes
- Tests : 212 → 219 verts (+7).
- Customer Portal Stripe doit être configuré côté Dashboard Stripe (hors code) pour fonctionner en prod.
[Unreleased] — 2026-04-26 — Sprint 5c — Flow Checkout post-redirect
Added
src/features/billing/hooks/useStripeCheckout.ts— hook{ checkout, isLoading, pendingPriceType, error }autour de la mutation Stripe Checkout + redirect full-page sur succès.pendingPriceTypepermet l'affichage loading par carte sans state local.src/features/dashboard/hooks/useUpgradeSuccessHandler.ts— détecte?upgrade=successau mount du Dashboard, invalide le cachePLAN_QUERY_KEY(refetch automatique du plan), nettoie l'URL viahistory.replaceState(préserve les autres params utm_*, etc.).src/features/dashboard/components/UpgradeSuccessBanner.tsx— callout success-soft « Bienvenue ! Votre plan a été mis à jour. » + bouton dismiss.- 9 tests (4 useStripeCheckout + 5 useUpgradeSuccessHandler).
Changed
src/features/billing/pages/PricingPage.tsx— migration versuseStripeCheckout(suppressionuseMutationinline +pendingTypestate local).src/features/dashboard/pages/DashboardPage.tsx— brancheuseUpgradeSuccessHandler+ rend<UpgradeSuccessBanner>au-dessus de<DashboardContent>quandshowSuccess.
Cross-repo
expria-backend@28f8373—fix(stripe): cancel_url /tarifs → /plan. Bug détecté lors de cette session : la route/tarifsn'existe pas côté frontend, les checkouts annulés aboutissaient sur un 404. Corrigé en commit séparé sur le backend.
Notes
- Tests : 203 → 212 verts (+9).
- Race condition connue (FTD-43) : le webhook Stripe peut arriver après le redirect frontend ;
usePlan()peut retourner l'ancien plan brièvement. Le banner indique « rafraîchissez dans quelques secondes » pour gérer ce cas.
[Unreleased] — 2026-04-26 — Sprint 5b — Page tarifaire /plan
Added
src/features/billing/api.ts—createCheckoutSession(priceType)+createCustomerPortalSession()(utilisée Sprint 5d).src/features/billing/components/PlanCard.tsx— carte plan présentationnelle pure : propscta,currentBadge,highlighted,ctaHint,errorMessage.src/features/billing/pages/PricingPage.tsx— orchestration 3 colonnes (Découverte / Standard / Premium) avec gating dynamique selonusePlan(). CTA payant → Stripe Checkout (full-page redirect). Callout d'erreur sous la carte cliquée.- 5 tests PricingPage (rendu Free/Standard/Premium + click + erreur).
Changed
src/shared/config/env.ts+.env.example— ajoutVITE_STRIPE_PRICE_STANDARD+VITE_STRIPE_PRICE_PREMIUM(optionnels — public price_ids Stripe).src/app/router.tsx—/plan→<PricingPage />(sous PrivateLayout, donc ProtectedRoute).- Uniformisation CTA upgrade :
SimulationsList,RapportPage,TaskSelector,DashboardFreeView,PaywallBanner→ libellé « Voir les plans » (au lieu de « Passer en Standard » / « Passer en Premium → » / « Voir les offres »). Cibles navigation inchangées (/plan). SimulationsList.test.tsx— assertion adaptée au nouveau libellé.
Notes
- Tests : 198 → 203 verts (+5).
DashboardStandardViewetBlurredProgressionconservent leurs CTA orientés (« Passer en Premium ») — sémantiquement corrects (Standard a un seul upgrade possible ; pattern_analysis est Premium-only).
[Unreleased] — 2026-04-26 — Sprint 4.8 — Phonologie EO (frontend)
Added
src/entities/report/__tests__/getMaxScorePerCritere.test.ts— 7 tests (détection maxScore + mapping libellés EO).
Changed
src/entities/report/lib.ts— nouveau helpergetMaxScorePerCritere(rapport): 4 | 5(détection sur criteres.length === 5).CRITERE_NOM_TO_CODEétendu avec les 4 libellés EO Sprint 4.8.src/features/simulations/components/rapport/CritereCard.tsx— nouvelle propmaxScore: afficheX/4(EO Sprint 4.8) ouX/5(EE, EO legacy).src/features/simulations/pages/RapportPage.tsx— calcul maxScore propagé aux CritereCard.src/entities/report/types.ts— commentaire Critere.score clarifié.
Notes
- Rétrocompatibilité : rapports EO legacy (4 critères × /5) et EE (4 × /5) inchangés.
- Tests : 191 → 198 verts (+7).
[Unreleased] — 2026-04-26 — Sprint 4.6 — UI EO (waveform + timeline)
Added
RecordingWaveform.tsx— visualiseur audio animé (AnalyserNode, fftSize 256, smoothing 0.7, 32 barres). Visible uniquement pendantisRecording. Respecteprefers-reduced-motion(frame statique). AudioContext fermé au cleanup.RecordingTimeline.tsx— barre de progression colorée avec seuils fixes : vert (0 → maxSeconds-30s), orange (maxSeconds-30s → maxSeconds-15s), rouge (maxSeconds-15s → fin). Applicable T1 et T3.RecordingTimeline.test.tsx— 7 tests (logique seuils + rendu + clamp).
Changed
useAudioRecorder.ts— exposemediaStream: MediaStream | null(set au start, reset au cleanup).AudioRecorder.tsx— intègre Waveform + Timeline dans l'UI d'enregistrement.
Notes
- Aucun changement backend.
- Tests : 166 → 173 verts (+7).
[Unreleased] — 2026-04-25 — Sprint 4.5 Clean + fixes Golden Dataset
Added
features/simulations/components/rapport/__tests__/ScoreHero.test.tsx— 3 tests (un par état : depasse / atteint / !atteint)features/simulations/components/rapport/__tests__/ConseilNclcCallout.test.tsx— 3 tests (patch FTD-40)- Test d'hydratation EO_T1 dans
simulationFlowT1.test.tsx(resume au refresh : production + présentation)
Changed
ARCHITECTURE.md§3 — arborescence réelle reflétée (FTD-25) : noteapp/documente entry points + composants layout (AppLayout, Sidebar, Topbar, BottomNav, MaintenancePage).t2-live/etbilling/retirés (non implémentés). Ajoutentities/{patterns,presentation,transcription}etfeatures/{historique,progression,design-system}. Bump v1.1.ARCHITECTURE.md§3 — conventionshared/ui/(wrappers Expria PascalCase) vsshared/components/ui/(primitives shadcn kebab-case) documentée (FTD-26).ConseilNclcCallout.tsx— propsnclc+nclcCibleajoutées ; patch temporaire FTD-40 (texte fixe « Excellent travail — vous avez dépassé votre objectif. Continuez sur cette lancée pour viser NCLC {nclc+1} ! » quandnclc > nclcCible).RapportPage.tsx— passenclc+nclcCibleàConseilNclcCallout.ScoreHero.tsx— encart de conclusion à 3 états (depasse / atteint / !atteint).SimulationFlowProvider.tsx—useEffectpersisteproduction.iddanslocalStorage.expria_simulation_idpour TOUS les flows (EE + EO_T1 + EO_T3) → resume au refresh fonctionnel pour EO.
Fixed
- Sprint 4.5 Clean — 3 erreurs lint Sprint 4c corrigées :
useDeepgramLive.ts:152— directiveeslint-disable-next-lineorpheline retiréeuseAudioRecorder.test.ts:77,81— params_t/_timesliceneutralisés viavoid(signature mock préservée)useAudioRecorder.ts:73—eslint-disable-next-line react-hooks/refs+ commentaire renvoyant à FTD-38
QuestionnaireT1Page.test.tsx:10— importReactinutilisé supprimé (TS6133).
Notes
- TECH_DEBT.md bumps : v1.23 (FTD-25/26 fermées) → v1.24 (FTD-38/39 ouvertes) → v1.25 (FTD-40/41 ouvertes).
- FTD ouvertes Sprint 4.5 :
- FTD-38 🟢 —
useAudioRecorderref mise à jour pendant render (eslint-disable local en place) - FTD-39 🟡 — Règle D violée dans
StatCards.tsx:90(préexistant Sprint UI Polish) - FTD-40 🟡 — Conclusion
conseil_nclcbackend incohérente quand NCLC atteint > cible (patch frontend en place, fix backend prompt à venir) - FTD-41 🔴 — Persistance présentation EO T1 en BDD (résout FTD-35 ; localStorage instable)
- FTD-38 🟢 —
- FTD fermées Sprint 4.5 : FTD-25 🟢, FTD-26 🟡.
- Cap FTD : 19/15 — dépassé de 4. Résorption obligatoire au Sprint 5.5 avant toute nouvelle FTD.
- Tests : 159 → 166 verts (+7). Typecheck + lint : 0 erreur.
[Unreleased] — 2026-04-25 — Sprint 4c — Frontend EO (T1 + T3)
Added
features/simulations/pages/SimulationEOPage.tsx— TaskSelector EO (T1, T3, T2 cadenas Premium)features/simulations/pages/SujetsEOPage.tsx— grille sujets EO_T3 + bouton aléatoirefeatures/simulations/pages/PreEnregistrementEOPage.tsx— consigne + durée recommandéefeatures/simulations/pages/EnregistrementEOPage.tsx— enregistrement audio + auto-submit à expirationfeatures/simulations/pages/ModeChoixT1Page.tsx— choix Générer / Enregistrer directementfeatures/simulations/pages/QuestionnaireT1Page.tsx— 5 champs + validation Zod + génération IAfeatures/simulations/pages/PresentationGenereeT1Page.tsx— texte généré, modifier, copier, .txt, refaire, localStoragefeatures/simulations/hooks/useAudioRecorder.ts— MediaRecorder, timer, maxSeconds, auto-stop, downloadfeatures/simulations/hooks/useDeepgramLive.ts— conservé dormant (FTD-37)features/simulations/components/AudioRecorder.tsx— UI enregistrement, maxSeconds/onMaxReachedfeatures/simulations/components/TranscriptionDisplay.tsx— conservé dormantentities/transcription/— token Deepgram (dormant, FTD-37)entities/presentation/— generatePresentation (POST /presentations/generate)shared/lib/audio.ts— blobToBase64 helper
Changed
SimulationFlowProvider— étendu EO : submitEoAudio, presentationT1, résolution race condition step=doneentities/report/api.ts— CORRECTION_EE_TIMEOUT_MS=60s / CORRECTION_EO_TIMEOUT_MS=120sentities/report/types.ts— CorrectEoPayload étendu audioBase64/mimeType- MIME normalisé côté frontend (strip codec params)
- router.tsx — 7 nouvelles routes EO sous SimulationFlowLayout
- FTD-30 à 37 tracées dans TECH_DEBT.md
Notes
- Transcription live Deepgram abandonnée pour le MVP — Gemini batch côté backend
- Audio non stocké côté serveur — bouton télécharger local
- Tests : 122 → 159 verts (+37)
[Unreleased] — 2026-04-25 — Fix timeout API
Fixed
DEFAULT_TIMEOUT_MSaugmenté de 5s à 15s dansapi-client.ts. Le backend Render Starter a des latences occasionnelles >5s sur les premières requêtes authentifiées.
Notes
- UptimeRobot configuré pour pinger
https://api.expria.app/toutes les 5 minutes (keepalive serveur).
[Unreleased] — 2026-04-25 — Sprint UI Polish — Sidebar + Topbar + Dashboard
Added
src/app/Topbar.tsx— topbar sticky avec backdrop-blur, breadcrumb "Expria › {page}", barre de recherche (placeholder), icônes raccourcis clavier et notifications.src/app/route-titles.ts— mapping centralisé pathname → titre de page, consommé par Topbar.src/features/dashboard/components/NclcHero.tsx— carte hero NCLC avec jauge horizontale 5→10 + anneau SVG score circulaire. Supporte état placeholder (Free/vide).src/features/dashboard/components/StatCards.tsx— 3 cartes métriques (simulations restantes, NCLC estimé, dernier score avec delta coloré).src/features/dashboard/components/RecentSimulations.tsx— liste 3 dernières simulations avec badge NCLC coloré + navigation vers/rapport/:id.src/features/dashboard/components/NextStepCard.tsx— carte "Prochaine étape" recommandée, contenu statique par plan.src/features/dashboard/components/DashboardFreeView.tsx— vue dashboard Free (hero placeholder, stat cards, premiers pas, PaywallBanner).src/features/dashboard/components/DashboardStandardView.tsx— vue dashboard Standard (hero NCLC dernière simu, simulations récentes, NextStepCard).src/features/dashboard/components/DashboardPremiumView.tsx— vue dashboard Premium (tout Standard + MonProfilPreparation).
Changed
src/app/Sidebar.tsx— icônes lucide-react (LayoutGrid, Pencil, Mic, etc.), cadenas Lock sur items verrouillés, badge upgrade ArrowUpCircle sur "Mon plan", user footer (avatar initiales + nom + plan + ThemeToggle), logo header "EX|PRIA" avec séparateur et sous-titre.src/app/AppLayout.tsx— intégration Topbar sticky, padding reporté sur wrapper contenu.src/features/dashboard/pages/DashboardPage.tsx— refactoré en orchestrateur : routing vers Free/Standard/Premium viahasAccess. Aucunplan === 'xxx'(Règle D).src/features/dashboard/components/PaywallBanner.tsx— refonte full-width + correction tokens morts Boréal (border-brand-100,dark:border-brand/20).
Removed
src/app/MobileHeader.tsx— fonctionnalité reprise par Topbar + Sidebar (0 consommateur confirmé par grep).
Notes
- Tests : 122/122 verts. Typecheck : 0 erreur.
- Contenu NextStepCard statique par plan (pas d'endpoint backend dédié).
- Hero NCLC : Premium → usePatterns, Standard → NCLC dernière simulation, Free → état placeholder.
- Timeout API intermittent (cold start Render) préexistant — cause le fallback temporaire plan=free au chargement initial.
[Unreleased] — 2026-04-24 — Sprint DA Charcoal — Reskin complet
Changed
- Remplacement intégral
src/index.csspar palette Charcoal (DESIGN_SYSTEM.md v2.0). Dark = thème par défaut,.light= override via@custom-variant light. - Sidebar navy
#0C1528permanent (identique dark et light) avec tokens--color-sidebar-*. - Layout
AppLayout: radial-gradient sur<main>, sidebar 230px,max-w-[1100px]. - Script anti-FOUC inline dans
index.html(détectionprefers-color-scheme+localStorage). - Renommage tokens Boréal→Charcoal sur ~45 composants (ink-1→ink-primary, expria→brand, line→border, deep→sidebar-bg, *-bg→*-soft, etc.).
- Inversion
dark:→ baseline +light:sur 5 primitives shadcn (button, badge, input, dialog, avatar). DesignSystemPageréécrite avec palette Charcoal complète.docs/adr/006-stack-versions-2026.mdmis à jour : tokens Charcoal, suppression@variant darket.dark {}.
Fixed
- Logo Expria : wordmark forcé en
text-whitedans la sidebar (invisible en light mode sur fond navy).
Notes
- 59 fichiers modifiés, +1173/-727 lignes.
- Tests : 122/122 verts. Typecheck : 0 erreur.
- Timeout API intermittent observé (cold start Render) — préexistant, non lié au reskin.
[Unreleased] — 2026-04-23 — Clean FTD-23 + FTD-24
Fixed
- FTD-23 résolu :
useAutosavene fire plus après correction —enabledpropagé avecstep !== 'done' && step !== 'correcting'depuisSimulationForm. 2 tests de régression ajoutés. - FTD-24 résolu : polling automatique 3s dans
useRapportquandexercices_statusoumodele_status === 'pending'. Arrêt auto dès ready/error. Timeout 2 min avec message + bouton Réessayer dansJobStatusFallback. 5 tests ajoutés.
Notes
- Tests frontend : 122/122 verts (+7 vs baseline 115).
- TECH_DEBT.md → v1.19. 10 FTD actives (cap 15).
[Unreleased] — 2026-04-23 — FTD-28 — Semgrep CI + CI verte
Added
- Semgrep scan (
--severity=ERROR) dans les CI frontend et backend (FTD-28). - Variables d'env factices dans CI frontend pour les tests.
Fixed
- 4 erreurs ESLint corrigées : split SimulationFlowProvider (react-refresh), hook conditionnel MonProfilPreparation, ref render useTimer, setState effect AppLayout.
- Prettier format sur 7 fichiers.
- CI frontend verte pour la première fois depuis le 18 avril.
[Unreleased] — 2026-04-23 — FTD-27 — CI backend
Added
expria-backend/.github/workflows/ci.yml— CI GitHub Actions (test + audit, Node 22). CI verte au premier run.- FTD-27 fermée dans TECH_DEBT.md (v1.17).
[Unreleased] — 2026-04-23 — FTD-29 — Dependabot config
Added
.github/dependabot.ymlcréé dans les 2 dépôts (npm, weekly, limit 10 PRs).- FTD-29 fermée dans TECH_DEBT.md (v1.16).
[Unreleased] — 2026-04-23 — Réorg sécurité TECH_DEBT v1.15
Changed
TECH_DEBT.mdv1.14 → v1.15 — réorganisation sécurité.- Gelées (backlog post-MVP) : FTD-06 (AudioWorklet), FTD-08 (Tests E2E), FTD-15 (option 'system' thème).
- Ajoutées : FTD-27 🔴 (CI backend), FTD-28 🔴 (Semgrep CI), FTD-29 🟡 (Dependabot config).
- GitHub : Dependabot alerts + security updates activés sur les deux dépôts (UI GitHub).
[Unreleased] — 2026-04-23 — Triage FTD v1.14
Changed
TECH_DEBT.mdv1.13 → v1.14 — triage dette technique : 17 → 15 FTD actives (cap respecté).- Fermées : FTD-04 (miroir docs, accepté ADR 004), FTD-05 (scaffold caduc, audit clean), FTD-20 (GET /simulations/:id livré Sprint 3.6a), FTD-22 (code orphelin /sujets, résolution complète).
- Ajoutées : FTD-25 🟢 (ARCHITECTURE.md §3 désaligné), FTD-26 🟡 (cohabitation shared/ui vs shared/components/ui).
[Unreleased]
Added
- Documentation initiale du projet (ARCHITECTURE, ONBOARDING, SECURITY, etc.)
- 5 ADRs pour les décisions architecturales majeures
- Code source de
src/entities/user/access.tsetlib.tsavec tests
[Unreleased] — 2026-04-22 — Sprint 3.5 — Clean post-Sprint 3
Changed
- FTD-17 résolu :
PLAN_QUERY_KEYcentralisé danssrc/entities/user/query-keys.ts(constantes pures, aucun import runtime).usePlanle ré-exporte ;SimulationPageetRapportPageremplacent leuruseQueryinline par le hookusePlan()— déduplication totale de la clé et de la configstaleTime. - FTD-18 résolu :
SimulationFormmigré de@/shared/components/ui/button(shadcn) vers la primitive canonique@/shared/ui/Button. Aucun variant à adapter (usage sans propvariant). - FTD-19 résolu : token
--shadow-focusajouté dans@theme {}(0 0 0 3px rgba(27, 79, 216, 0.18)— conformeDESIGN_SYSTEM.md §2) et dans.dark {}(recalculé sur la teinte expria dark). Migration de 5 occurrencesring-2 ring-expria/20→ utilityshadow-focusdansButton,Card,SimulationForm(×3),SpecialCharsKeyboard. - Factorisation
SimulationForm: className dupliquée des deux boutons secondaires (« Suggestions d'idées » / « Changer de sujet ») extraite en const localesecondaryActionBtn. TECH_DEBT.md→ v1.11. 15 FTD actives (cap de 15 respecté).
Notes
- Timeouts DeepSeek intermittents observés pendant les tests manuels Groupe B + C — cause externe (API tierce), hors périmètre refactor Sprint 3.5.
- B8 : comportement actuel diffère du spec
PARCOURS_UTILISATEURS.md §2 "Quota atteint"— affichage d'une bannière inline au lieu du modal de blocage attendu. À corriger dans un sprint dédié (non inclus dans ce clean, qui n'introduit aucune nouvelle fonctionnalité).
[Unreleased] — 2026-04-22 — Sprint 3.6c — Analyse patterns (Backend + Frontend)
Added (backend)
GET /users/patterns— analyse des patterns récurrents pour utilisateur Premium.- Auth :
authMiddleware+planMiddleware('pattern_analysis')(403PLAN_INSUFFICIENTsi Free/Standard). - < 5 productions corrigées →
200 { ready: false, minimum: 5, current: N }. - ≥ 5 →
200 { ready: true, patterns, exercises, preparation_index, analyzed_productions, last_analysis }.
- Auth :
patternsController.aggregatePatterns(pure) : agrège leserreurs_codessur N productions, seuil 3/5, dédoublonnage intra-prod (un même code dans un rapport ne compte qu'une fois), codesautredistingués par description, tri par fréquence DESC.patternsController.computePreparationIndex(pure) : 60 % score moyen normalisé + 20 % régularité (médiane des intervalles entre prod) + 20 % tendance (pente linéaire sur les 5 scores). Clamp[0, 100], messages figés selon les seuils<40/40-70/>70.patternsController.list— orchestre fetch productions + cachepattern_analyses+ recompute + DeepSeek + INSERT. Stratégie d'invalidation :MAX(productions.created_at) > lastAnalysis.created_at→ recompute, sinon cache hit.generatePatternExercicesdanssrc/lib/deepseek.ts— prompt système validé par Hermann avec format{ consigne, exemple, correction, astuce }, température 0.4,AbortSignal.timeout(20_000), validation runtime des critères viaisValidCritere.- Table
pattern_analyses— migration005_sprint_3_6c_pattern_analyses.sql: UUID PK + FK cascade user_id +productions_ids UUID[]+ patterns/exercises JSONB + preparation_index (CHECK[0, 100]) + preparation_message + analyzed_count + RLS SELECT par user_id + index(user_id, created_at DESC). - 19 nouveaux tests (
patternsController.test.ts) : 7 suraggregatePatterns, 4 surcomputePreparationIndex, 8 sur route (401, 403 free/standard, <5 prod, cache hit, cache miss + insert, no patterns, DeepSeek fail gracieux). 205 tests backend verts (+19 vs baseline 186).
Added (frontend)
- Page
/progression— route sousAppLayout+ProtectedRoute, remplace le placeholderComingSoon. ProgressionPage— orchestreusePlan+usePatterns, gate plan viahasAccess('pattern_analysis').ProgressionPremium— orchestrateur : si not-ready →NotReadyState; sinon Hero indice + patterns + exercices long terme + footer « Analyse basée sur vos N dernières productions — il y a X ».PreparationIndexHero— score /100 + jauge horizontale colorée (rouge <40 / ambre 40-70 / vert >70) + message.PatternsList— liste des patterns avec libellé via nouveauCRITERE_LABELS+ badge fréquence (3/5, 4/5, 5/5).PatternExerciceCard— nouveau composant lesson-style, non interactif (contrairement àExerciceInteractivedu rapport individuel) : critère + diagnostic + consigne + bloc incorrect (barré rouge) côte à côte avec bloc correct (vert) + encart astuce proéminent (icône ampoule + fond warning).NotReadyState— barre de progression N/5 + CTADémarrer une simulation.BlurredProgression— aperçu flouté pour Free/Standard + bouton upgrade Premium.- Section Dashboard Premium
MonProfilPreparation— MetricCard indice (score + jauge compacte + message) + nombre d'erreurs récurrentes + CTA « Voir mon profil de préparation » vers/progression. Garde explicitehasAccess('pattern_analysis')→ composant retournenullpour Free/Standard (pas rendu dans le DOM). usePatterns(plan)— hook TanStack Query partagé entre/progressionet dashboard ; clé['users', 'patterns'],staleTime: 60s,enabledconditionné parhasAccesspour éviter un 403 parasite.entities/patterns/types.ts+entities/patterns/api.ts— types miroirs du backend (Pattern,PatternExercice,PreparationIndex,PatternsReady,PatternsNotReady) +getPatterns()avec timeout 25 s.CRITERE_LABELSexporté depuisentities/report/lib.ts— miroir du backend pour affichage du libellé humain à partir du code taxonomie.- 13 nouveaux tests : 6 sur
ProgressionPremium(not-ready, ready avec indice/patterns/exercices, footer, 0 pattern) + 7 surMonProfilPreparation(gating Free/Standard, Premium ready/not-ready, loading, error, 0 pattern). 115 tests frontend verts (+13 vs baseline 102).
Notes
- Formule indice arbitraire (60/20/20) — à affiner après observation prod si besoin.
- Dégradation gracieuse DeepSeek : si
generatePatternExercicesthrow, le backend persiste quand même l'analyse avecexercises: []et logue l'erreur. Le frontend affiche alors la liste des patterns sans section exercices (pas de message d'erreur explicite côté UI — l'utilisateur ne sait pas qu'il manque quelque chose). ExerciceInteractiveNON réutilisé pour les exercices long terme : les shapes et UX sont différents (lesson vs tentative). Deux composants distincts cohabitent.- Migration SQL à exécuter manuellement :
cd expria-backend && supabase db pushavant les tests end-to-end Premium.
[Unreleased] — 2026-04-22 — Sprint 3.7 — Historique (Backend + Frontend)
Added (backend)
GET /simulations— liste paginée des productions de l'utilisateur connecté.- Query params :
page(défaut 1, entier ≥ 1),limit(défaut 20, entier entre 1 et 50). - Tri :
created_at DESCcôté Supabase. - Filtre :
user_id = profile.id(double-protection avec RLS). - Projection :
id, tache, mode, score, nclc, nclc_cible, created_at— champs lourds (contenu,rapport,exercices,modele) exclus. - Réponse :
{ data: ListItem[], pagination: { page, limit, total } }. - Erreurs :
400 VALIDATION_ERRORsipage/limitinvalide,401 AUTH_REQUIREDsi JWT absent,500 INTERNAL_ERRORsi DB down.
- Query params :
simulationController.list(options, profile)+ interfacesListOptions,ListItem,ListResult.- 12 nouveaux tests sur la route
GET /simulations(186 tests backend verts, +12 vs baseline 174).
Added (frontend)
- Page
/historique(route sousAppLayout+ProtectedRoute, remplace le placeholderComingSoon). HistoriquePage— orchestreusePlan+useSimulationsList, state local de pagination, gating plan Free viahasAccess('dashboard').SimulationsList— composant liste avec :- Empty state + CTA « Démarrer une simulation » →
/simulation/ee - Loading skeleton (5 barres animées)
- Error state (callout discret
border-l-danger) - Aperçu flouté Free + bouton
variant="upgrade"« Passer en Standard » - Pagination Précédent / Suivant (masquée si une seule page)
- Affichage « Page X sur Y — Z simulations »
- Empty state + CTA « Démarrer une simulation » →
SimulationListItem— carte item : date relative, libellé de tâche (formatTache), score/20,NCLC atteint / cible, badges « Examen » et « En cours » (rapport non prêt). Clic →/rapport/:id.useSimulationsList(page, limit)— hook TanStack Query, clé['simulations', 'list', page, limit],staleTime: 30s,placeholderData: keepPreviousDatapour éviter le flash de squelette au changement de page.listSimulations(page, limit)dansentities/production/api.ts— wrapapiFetch+URLSearchParams.- Types
SimulationListItemetSimulationsListResponsedansentities/production/types.ts. src/shared/lib/date.ts— helperformatRelativeDate(iso, now?)basé surIntl.RelativeTimeFormat('fr', { numeric: 'auto' }). Seuils : secondes → minutes → heures → jours → semaines → mois → années. Zéro dépendance.- 18 nouveaux tests frontend (7
date.test.ts+ 11SimulationsList.test.tsx).
Notes
- Les simulations avec
score === null(en cours ou correction échouée) sont affichées avec un badge « En cours ». Clic →/rapport/:id—RapportPagegère le casREPORT_NOT_READY(FTD-21) en redirigeant vers/simulation/ee. BlurredPreviewdupliqué localement dansSimulationsList(pattern équivalent àBlurredSectiondeRapportPage). À extraire enshared/si le pattern se répète dans un 3ᵉ endroit — pas fait dans ce sprint.- Pagination : Précédent/Suivant (MVP) retenu contre scroll infini. Le choix sera revu si l'historique dépasse 100 items en prod.
- Tests frontend : 102/102 verts (+18 vs baseline 84).
[Unreleased] — 2026-04-22 — Sprint 3.6b — Qualité correction — Frontend
Added
NclcCibleSelector(segmented control NCLC 9 / NCLC 10) dansSimulationForm— valeur propagée au payloadPOST /corrections/eeviaSimulationFlowProvider.submitText(texte, nclcCible).- Composants
rapport/dansfeatures/simulations/components/:ScoreHero— score /20, jauge avec marqueur du seuil NCLC cible, écart vs objectif (« X points avant NCLC 9 »), badges NCLC atteint / cible.RevelationCards— 3 colonnes : ce que le candidat croit / ce que le correcteur observe / conséquence.DiagnosticCallout— callout « Ce qui freine votre progression ».CritereCard— carte enrichie par critère (exemple / suggestion / astuce + badges codes taxonomie).ConseilNclcCallout— plan d'action NCLC (objectif, écart, action prioritaire).ExerciceInteractive— carte exercice avec zone texte, bouton Indice (révélé une fois), bouton « Voir la correction » (activé après saisie), explication.ProductionModeleSection— texte final + notes pédagogiques + transformations original/amélioré + message encourageant.JobStatusFallback— fallback pourexercices_status/modele_statusen'pending'ou'error'.
- Helpers dans
entities/report/lib.ts:groupErreursByCritere,ecartVsCible,critereCodeFromNom. - Tests
ExerciceInteractive.test.tsx(6 tests) — couvre état interne : révélation unique indice, activation bouton correction, affichage correction + explication. - FTD-24 🟡 dans
TECH_DEBT.md— polling automatique pour exercices/modèlepending(refresh manuel en MVP).
Changed
entities/report/types.ts— refonte complète alignée sur le backend Sprint 3.6a :Reportremplace l'ancien (revelation, diagnostic, criteres enrichis, conseil_nclc, erreurs_codes top-level, exercices dynamiques, modele structuré, statuts pending/ready/error). Suppression defeedback_court,erreurs[],modele:string,idees[](obsolètes).entities/report/lib.ts—BlurableSectionréduite à'criteres' | 'exercices' | 'modele':revelation,diagnostic,conseil_nclcdeviennent visibles pour tous les plans conformément à PLANS_TARIFAIRES.md §2.entities/production/types.ts—SimulationStateétendu avecnclc_cible,exercices,exercices_status,modele,modele_status;SimulationRapportaligné surCorrectionRapportbackend.entities/report/api.ts—getReportrecombineSimulationState.rapport+exercices+modele+ statuts en unReportunifié pouruseRapport.RapportPage.tsx— réécriture complète : câble tous les nouveaux composants, branche le gating plan viaisSectionVisible, afficheJobStatusFallbackpour les jobs asynchrones. Résout l'écran blanc post-Sprint 3.6a.floutage.test.tsréécrit (17 tests — matrice de visibilité + helpers lib).
Fixed
- Race condition
modele_status(backend) : l'update principal de correction écrasaitmodele_status='ready'déjà posé parrunModeleJob(lancé en parallèle option b).correctionController.correctEEne touche plus aux colonnes*_status— pilotées exclusivement par les jobs asynchrones. - Boucle infinie retour rapport → SimulationPage : le useEffect sticky
step === 'done' → navigate('/rapport/:id')renvoyait l'utilisateur sur le rapport à chaque tentative de retour vers/simulation/ee. Supprimé ; la navigation initiale vers/rapport/:idest déclenchée une seule fois danscorrectMutation.onSuccessdu provider. - Boucle retour /sujets → SimulationPage : même pattern sticky pour
step === 'choosing-subject' → navigate('/sujets'). Supprimé ; navigation initiale vers/sujetsdéplacée danscreateMutation.onSuccess. - RapportPage hors SimulationFlowProvider : la route
/rapport/:idn'était pas sousSimulationFlowLayout— l'appel àuseSimulation()depuis RapportPage throw. Route déplacée sous le layout, l'instance du provider est partagée avec/simulation/eeet/sujets.
Added
- Bouton « Nouvelle simulation » en bas de
RapportPagequireset()+navigate('/simulation/ee'). reset()explicite dans le bouton « ← Retour » deSujetsPageavant la navigation, pour empêcher tout re-déclenchement de la garde sticky.
Changed
- Navigations post-mutation déplacées dans
onSuccessdu provider (pattern cohérent pourcreateMutation→/sujetsetcorrectMutation→/rapport/:id). Plus de useEffect réactif aux changements destepcôté SimulationPage. SujetsPage: garde étendue de!productionà!production \|\| step === 'idle' \|\| step === 'done'pour couvrir le cas post-rapport (évite le 400 VALIDATION_ERROR surPATCH /simulations/:id/sujetd'une simulation déjà corrigée).RapportPagebreadcrumb :<Link>remplacé par<button>quireset()avant navigate.
Notes
- Option β retenue : frontend aligné sur la structure backend réelle du Sprint 3.6a. Aucun aller-retour backend.
feedback_courtsupprimé de l'UI ;diagnosticremplace la section « Retour général ».- Polling automatique non implémenté (FTD-24) : refresh manuel de la page si
exercices_status/modele_status='pending'. - Tests : 84/84 verts (+8 vs baseline 76).
[Unreleased] — 2026-04-22 — Sprint 3.6a — Qualité correction — Backend
Added (backend)
src/lib/taxonomieErreurs.ts: constantes des 63 codes TCF Canada + 4 codesautrepar critère, validation runtimeisValidCode/isValidCritere, et injection au prompt viabuildTaxonomyPromptSection.- Prompts dynamiques dans
src/lib/deepseek.ts:buildCorrectionPrompt(prompt maître avecnclc_cible9 ou 10, sujet, documents T3),buildModelPrompt(production modèle cible NCLC 9 fixe),buildExercicesPrompt(3 exercices ciblés surerreurs_codes+ extraitsexemple, format{difficulte, theme, diagnostic, consigne, extrait, indice, correction, explication}). - Post-traitement production modèle :
wordCountTCF,stripModelAnnotations,truncateToMaxWords. - Route
POST /corrections/eeaccepte le paramètrenclc_cible(optionnel, défaut 9, valeurs acceptées : 9 ou 10 ; sinon 400 VALIDATION_ERROR). - Migration SQL
supabase/migrations/004_sprint_3_6a_qualite_correction.sql— colonnes :revelation,diagnostic,conseil_nclc,erreurs_codes,exercices,modele,nclc_cible,exercices_status,modele_status+ index GIN surerreurs_codes(pour Sprint 3.6c). controllers/__tests__/correctionController.test.ts(7 tests) : parallélisme, statuts ready/error,nclc_cible=10propagé, simulation introuvable/autre user.docs/TECH_DEBT.mdTD-15 🟡 : jobs fire-and-forget peuvent resterpendingsi redémarrage process.
Changed (backend)
correctEEdansdeepseek.ts— nouvelle signaturecorrectEE(CorrectionInput)+ nouvelle formeCorrectionRapport(revelation, diagnostic, criteres[{exemple,suggestion,astuce}], conseil_nclc, erreurs_codes).EERapportdevient alias deCorrectionRapport.correctionController.correctEE: lance 3 appels DeepSeek en parallèle ; await uniquement sur la correction pour répondre 200 ; modèle et exercices s'exécutent en fire-and-forget et mettent à jour{exercices,exercices_status}et{modele,modele_status}en base (pending → ready/error).simulationController.getByIdretourne les nouveaux champs :nclc_cible,exercices,exercices_status,modele,modele_statusen plus durapportenrichi.deepseek.test.tsréécrit — 25 tests (ancien pipeline supprimé, nouveaux tests sur correctEE/generateProductionModele/generateExercices/helpers + EO inchangé).
Notes
- Option A retenue : backend renvoie uniquement la nouvelle forme. Frontend (Sprint 3.6b) casse tant que non livré — livraison groupée sans déploiement intermédiaire.
- Prompt exercices rédigé côté backend (option b), basé sur les codes taxonomie + extraits
exempledes critères. Format aligné sur captures d'écran demandées. - Migration SQL à exécuter manuellement via
supabase db push— Hermann avant le premier test end-to-end. - Tests backend : 173/173 verts (+18 vs baseline de 155).
[Unreleased] — 2026-04-22 — Planification Sprint 3.6a/3.6b/3.6c
Added
- Sprints 3.6a (backend prompts + taxonomie), 3.6b (frontend rapport enrichi), 3.6c (analyse patterns Premium) ajoutés à la ROADMAP entre Sprint 3.5 et Sprint 4.
TAXONOMIE_ERREURS.md— 63 codes d'erreurs TCF Canada sur 4 critères + 4 codes « autre » + procédure d'enrichissement.
2026-04-21 — FTD-21 — Persistance session /simulation/ee
Added
useAutosave(simulationId, contenu, enabled): autosave debounce 30 s + flush surbeforeunload, dedup par dernier contenu sauvegardé (6 tests).SimulationFlowProviderhydrate la session au montage depuislocalStorage(expria_simulation_id) →GET /simulations/:id→ restaurestep='task-selected'+production+sujetsirapport=null; nettoie la clé sinon (3 tests resume).- Types
SimulationState,SimulationRapport+ APIgetSimulationState,autosaveContenu,updateSujetdansentities/production. - Indicateur "Sauvegardé à HH:MM" sous la textarea
SimulationForm(text-xs,aria-live="polite").
Changed
getReportdélègue désormais àgetSimulationStateet lèveREPORT_NOT_READYsirapport=null.RapportPagecatche cette erreur et redirige vers/simulation/eeavec message discret "Votre simulation est en cours.".SimulationFormacceptesimulationId,initialContenu,stepet persisteexpria_simulation_iddanslocalStoragetant que la simulation est active ; nettoie la clé quandstep='done'.changeSubjectpersiste le changement côté backend viaPATCH /simulations/:id/sujet(best-effort, silencieux si échec).
Security
- localStorage ne stocke que
simulation_id(UUID non-sensible) — conforme SECURITY.md §2.6.
Notes
- FTD-21 reste ouvert pour
/simulation/eo(Sprint 4) et/examen(Sprint 7).
2026-04-21 — Tâche G5 — Suggestions d'idées DeepSeek
Ajouté
- Backend —
POST /sujets/idees: génère 5 suggestions d'idées via DeepSeek pour aider l'étudiant à prolonger sa rédaction (prompt coach TCF Canada, temperature 0.5, timeout 15 s via AbortSignal, JSON strict{ idees: string[] }) generateIdees(consigne, contenu)danssrc/lib/deepseek.ts(validation tableau non vide)- 5 tests route
POST /sujets/idees: 401 sans auth, 400 sujet_consigne manquant, 400 contenu < 30 mots, 200 succès avec idees[], 500 DeepSeek throw - Frontend —
getIdees(consigne, contenu)dansentities/report/api.ts(POST/sujets/idees, timeoutMs 15 000) - Hook
useIdees—useMutationexposant{ idees, isLoading, error, fetchIdees, reset } - Composant
IdeesSuggestions— modal shadcn Dialog avec liste à puces, états loading/erreur/succès,reset()automatique à la fermeture - Bouton "Suggestions d'idées" (icône Lightbulb) dans
SimulationFormà côté de "Changer de sujet" - Prop
plan: Planajouté àSimulationForm(wiringplanData.plandepuisSimulationPage)
Règles d'accès
- Règle D respectée :
hasAccess(plan, 'tips')obligatoire - Plan Free : bouton visible mais désactivé avec tooltip "Disponible en Standard" (tips=false pour Free)
- Standard + Premium : bouton actif dès 30 mots écrits
- Désactivé également si
!sujet,isSubmitting, ouidees.isLoading
Tests
- Backend — Typecheck : 0 erreur, Vitest : 144/144 passés (+5 tests POST /sujets/idees)
- Frontend — Typecheck : 0 erreur, Vitest : 67/67 passés
- Test manuel : validé avec compte Standard (bouton actif à 30+ mots, modal affiche 5 idées) et Free (bouton verrouillé avec tooltip)
2026-04-21 — Tâche G4 + Refonte page /sujets + Fix quota simulations
Ajouté
- Tâche G4 — choix du sujet avec dropdown intégré et bouton
aléatoire dans SimulationForm (hook
useSujets, composantSujetSelector,getSujets()surGET /sujets?mode=&tache=) - Refonte UX
/sujets(Option A) — page dédiée avec grille de cartesSujetCard(responsive 1/2/3 colonnes), état partagé viaSimulationFlowProviderpour survivre aux navigations entre/simulation/eeet/sujets. MVP : refresh sur/sujetsredirige vers/simulation/ee. - Bouton "Changer de sujet" dans
SimulationForm— retour à/sujetsviagoToSubjectPicker - Prop
type: 'EE' | 'EO'surTaskSelector(EO_CARDS réservé usage futur — non routé,/simulation/eoresteComingSoonjusqu'au Sprint EO)
Modifié
useSimulationrefacto en consommateur deSimulationFlowProvider(source de vérité déplacée hors du hook)SujetDisplayredevient présentationnel (dropdown retiré)TaskSelector: retrait des cartes EO de la page Expression Écrite (affiche uniquement EE T1/T2/T3)
Corrigé
- Quota simulations (backend — commit
ecb478e, expria-backend) : incrémentsimulations_useddéplacé desimulationController.create()verscorrectionController.correctEE/EO(Option B). Une simulation créée mais jamais corrigée ne consomme plus le quota utilisateur.
Supprimé
SujetSelector.tsx— orphelin après refonte/sujets- Helper
selectSujetdeuseSimulation— orphelin - FTD-22 tracée résolue partiellement (step
'choosing-subject'goToSubjectPickerconservés intentionnellement)
Tests
- Typecheck : 0 erreur
- Vitest : 67/67 passés
- Test manuel : flux complet EE T1 avec choix de sujet (carte + aléatoire + changement de sujet) validé
2026-04-21 — Tâches G2+G3 — Clavier + Minuteur
Ajouté
- Composant SpecialCharsKeyboard — 30 caractères spéciaux français en flex-wrap, sticky au scroll
- Bloc "Temps restant" sticky avec TimerDisplay MM:SS (critique < 2min : rouge + pulse, expiré : rouge bold)
- Composant WordCountBar — barre de progression colorée (orange < cible, vert dans cible, rouge > cible)
- Hook useTimer avec 7 tests unitaires
- Config par tâche dans simulationConfig.ts (EE T1: 10min/60-120 mots, T2: 20min/120-150, T3: 30min/120-180)
- Auto-submit à l'expiration si ≥ 30 mots
- Bouton "Soumettre ma production" (était "Envoyer")
- Textarea auto-resize sans scroll interne
Changed
- Compteur de caractères remplacé par WordCountBar
- Bouton soumission bloqué si < 30 mots
Tests
- Typecheck : 0 erreur
- Vitest : 66/66 passés (+7 tests useTimer)
- Test manuel : minuteur + clavier validés sur mobile et desktop
2026-04-21 — Tâche G1 — Affichage de la consigne
Ajouté
- Interface SujetData dans entities/production/types.ts
- Production enrichie avec sujet: SujetData | null
- Composant SujetDisplay — affiche consigne, rôle, contexte, doc1, doc2 selon le sujet retourné
- useSimulation expose sujet dans son retour
- SimulationForm intègre SujetDisplay au-dessus de la textarea
- FTD-21 tracée (persistance session simulation)
Tests
- Typecheck : 0 erreur
- Vitest : 59/59 passés
- Test manuel : consigne affichée sur /simulation/ee
2026-04-20 — Audit frontend ↔ backend — alignement types Report
Modifié
src/entities/report/types.ts—Critere.note→Critere.score,Report.exercices: Exercice[]→Report.exercices: string[], JSDoc ajustésrc/features/simulations/pages/RapportPage.tsx— importExerciceretiré,critere.note→critere.score,ExerciceCardrefactoré pour consommer unestringrendue en Markdown, clé d'itération par index
Supprimé
- Interface
Exercice { titre, contenu }deentities/report/types.ts— remplacée parstring[]pour coller au contrat backend
Contexte (backend associé, expria-backend)
Quatre commits côté backend finalisent l'alignement du contrat Report :
feat(corrections): renommagesproduction_modele→modele,suggestions_idees→idees, ajoutfeedback_court+ prompts DeepSeek mis à jour + validations runtimefeat(corrections): réponse enrichie avecsimulation_idcôtécorrectionControllerfeat(simulations): nouvelle routeGET /simulations/:id(auth owner, gestionSIMULATION_NOT_FOUND/AUTH_REQUIRED/REPORT_NOT_READY) + 4 testsfeat(simulations): sujet aléatoire (tablesujets) retourné avec chaque production créée (EO_T2_LIVE exclu, non bloquant si aucun sujet actif)
Tests
- Typecheck : 0 erreur
- Vitest : 59/59 passés
À faire (hors scope — session frontend dédiée ultérieurement)
- Ajouter
sujet: SujetData | nulldansentities/production/types.ts - Consommer le sujet retourné dans
SimulationPage(affichage consigne + docs) - Consommer
feedback_courtdansRapportPage(rendu toujours visible — cf. PLANS_TARIFAIRES §2 — déjà supporté par le typeReport, reste à brancher dans l'UI si ce n'est pas déjà le cas)
2026-04-20 — Sprint 0.5 bis — AppLayout + primitives UI + refonte visuelle
Ajouté
src/app/AppLayout.tsx— layout applicatif desktop/mobile (sidebar fixe 240px, drawer mobile, BottomNav)src/app/Sidebar.tsx— navigation latérale avec verrouillagehasAccess()(Progression, Examen blanc, Historique)src/app/MobileHeader.tsx— header mobile sticky (Logo, ThemeToggle, bouton menu hamburger)src/app/BottomNav.tsx— navigation mobile fixe (4 items, bottom sheet "Simuler", tap target min 44px)src/shared/ui/Button.tsx— primitive Button (variants: primary/secondary/ghost/upgrade ; sizes: sm/md/lg ; loading Loader2)src/shared/ui/Card.tsx— primitive Card (variants: default/raised/interactive ; rendu<button>sionClickfourni)src/shared/ui/Badge.tsx— primitive Badge (variants: plan/nclc/neutral ; couleur selonplanValuepour variant plan)
Modifié
src/app/router.tsx— layout routes viaPrivateLayout(ProtectedRoute+AppLayout+Outlet) ;ComingSooninline ; redirect/simulation→/simulation/eesrc/features/simulations/components/TaskSelector.tsx— refonte avecCard interactive/Card default opacity-60,Badge"EE"/"EO", eyebrowtracking-widest, icône verrousrc/features/simulations/pages/SimulationPage.tsx— suppression header interne (Logo + ThemeToggle) ; root<main>;Buttonmigré vers@/shared/ui/Buttonvariant="secondary"src/features/dashboard/pages/DashboardPage.tsx— suppression header interne ;Buttonvariant="primary"avecnavigate('/simulation/ee');Badgevariant="plan" planValue={data.plan}; tout migré vers@/shared/ui/
Documentation
docs/TECH_DEBT.mdv1.6 — ajout FTD-18 (SimulationForm migration Button), FTD-19 (token--shadow-focusmanquant)
Tests
- Typecheck : 0 erreur
- Vitest : 59/59 passés
- Tests manuels : à valider par Hermann
2026-04-19 — Sprint 1 / Étape 6 — Maintenance mode + outillage sécurité
Ajouté
- Page de maintenance statique (
src/app/MaintenancePage.tsx) — logo + message, tokens Direction H, zéro dépendance - Guard
VITE_MAINTENANCE_MODEdansmain.tsx— sitrue, aucun provider ne se monte, aucun appel réseau - Variable
VITE_MAINTENANCE_MODEdansenv.ts(optionnelle, défautfalse) - Hook PreToolUse Claude Code (
security-check.sh) — 9 patterns SECURITY.md §2 - Hook Stop Claude Code (
check-file-size.sh) — alerte fichiers > 200 lignes - MCP server Semgrep enregistré dans Claude Code
Documentation
ARCHITECTURE.md§7 — ajoutVITE_MAINTENANCE_MODEdans la liste des variablesTECH_DEBT.md— FTD-16 résolu (maintenance mode implémenté)
Tests
- Typecheck : 0 erreur
- Vitest : 37/37 passés
- Test manuel : maintenance mode vérifié (page affichée, aucun appel réseau, routing bloqué)