31 KiB
31 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-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é)