# Changelog — Expria Frontend Toutes les modifications notables du projet frontend sont documentées dans ce fichier. Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/). ## Convention Chaque entrée suit ce format : ``` ## [Unreleased] — YYYY-MM-DD — Session ### Added (nouveautés) - ... ### Changed (modifications) - ... ### Fixed (corrections) - ... ### Removed (suppressions) - ... ### Security (sécurité) - ... ``` --- ## [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.ts` et `lib.ts` avec tests ## [Unreleased] — 2026-04-22 — Sprint 3.5 — Clean post-Sprint 3 ### Changed - **FTD-17 résolu** : `PLAN_QUERY_KEY` centralisé dans `src/entities/user/query-keys.ts` (constantes pures, aucun import runtime). `usePlan` le ré-exporte ; `SimulationPage` et `RapportPage` remplacent leur `useQuery` inline par le hook `usePlan()` — déduplication totale de la clé et de la config `staleTime`. - **FTD-18 résolu** : `SimulationForm` migré de `@/shared/components/ui/button` (shadcn) vers la primitive canonique `@/shared/ui/Button`. Aucun variant à adapter (usage sans prop `variant`). - **FTD-19 résolu** : token `--shadow-focus` ajouté dans `@theme {}` (`0 0 0 3px rgba(27, 79, 216, 0.18)` — conforme `DESIGN_SYSTEM.md §2`) et dans `.dark {}` (recalculé sur la teinte expria dark). Migration de 5 occurrences `ring-2 ring-expria/20` → utility `shadow-focus` dans `Button`, `Card`, `SimulationForm` (×3), `SpecialCharsKeyboard`. - Factorisation `SimulationForm` : className dupliquée des deux boutons secondaires (« Suggestions d'idées » / « Changer de sujet ») extraite en const locale `secondaryActionBtn`. - `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.6a — Qualité correction — Backend ### Added (backend) - `src/lib/taxonomieErreurs.ts` : constantes des 63 codes TCF Canada + 4 codes `autre` par critère, validation runtime `isValidCode` / `isValidCritere`, et injection au prompt via `buildTaxonomyPromptSection`. - Prompts dynamiques dans `src/lib/deepseek.ts` : `buildCorrectionPrompt` (prompt maître avec `nclc_cible` 9 ou 10, sujet, documents T3), `buildModelPrompt` (production modèle cible NCLC 9 fixe), `buildExercicesPrompt` (3 exercices ciblés sur `erreurs_codes` + extraits `exemple`, format `{difficulte, theme, diagnostic, consigne, extrait, indice, correction, explication}`). - Post-traitement production modèle : `wordCountTCF`, `stripModelAnnotations`, `truncateToMaxWords`. - Route `POST /corrections/ee` accepte le paramètre `nclc_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 sur `erreurs_codes` (pour Sprint 3.6c). - `controllers/__tests__/correctionController.test.ts` (7 tests) : parallélisme, statuts ready/error, `nclc_cible=10` propagé, simulation introuvable/autre user. - `docs/TECH_DEBT.md` TD-15 🟡 : jobs fire-and-forget peuvent rester `pending` si redémarrage process. ### Changed (backend) - `correctEE` dans `deepseek.ts` — nouvelle signature `correctEE(CorrectionInput)` + nouvelle forme `CorrectionRapport` (revelation, diagnostic, criteres[{exemple,suggestion,astuce}], conseil_nclc, erreurs_codes). `EERapport` devient alias de `CorrectionRapport`. - `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.getById` retourne les nouveaux champs : `nclc_cible`, `exercices`, `exercices_status`, `modele`, `modele_status` en plus du `rapport` enrichi. - `deepseek.test.ts` réé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 `exemple` des 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 sur `beforeunload`, dedup par dernier contenu sauvegardé (6 tests). - `SimulationFlowProvider` hydrate la session au montage depuis `localStorage` (`expria_simulation_id`) → `GET /simulations/:id` → restaure `step='task-selected'` + `production` + `sujet` si `rapport=null` ; nettoie la clé sinon (3 tests resume). - Types `SimulationState`, `SimulationRapport` + API `getSimulationState`, `autosaveContenu`, `updateSujet` dans `entities/production`. - Indicateur "Sauvegardé à HH:MM" sous la textarea `SimulationForm` (text-xs, `aria-live="polite"`). ### Changed - `getReport` délègue désormais à `getSimulationState` et lève `REPORT_NOT_READY` si `rapport=null`. `RapportPage` catche cette erreur et redirige vers `/simulation/ee` avec message discret "Votre simulation est en cours.". - `SimulationForm` accepte `simulationId`, `initialContenu`, `step` et persiste `expria_simulation_id` dans `localStorage` tant que la simulation est active ; nettoie la clé quand `step='done'`. - `changeSubject` persiste le changement côté backend via `PATCH /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)` dans `src/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)` dans `entities/report/api.ts` (POST `/sujets/idees`, timeoutMs 15 000) - Hook `useIdees` — `useMutation` exposant `{ 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: Plan` ajouté à `SimulationForm` (wiring `planData.plan` depuis `SimulationPage`) ### 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`, ou `idees.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`, composant `SujetSelector`, `getSujets()` sur `GET /sujets?mode=&tache=`) - **Refonte UX `/sujets`** (Option A) — page dédiée avec grille de cartes `SujetCard` (responsive 1/2/3 colonnes), état partagé via `SimulationFlowProvider` pour survivre aux navigations entre `/simulation/ee` et `/sujets`. MVP : refresh sur `/sujets` redirige vers `/simulation/ee`. - Bouton "Changer de sujet" dans `SimulationForm` — retour à `/sujets` via `goToSubjectPicker` - Prop `type: 'EE' | 'EO'` sur `TaskSelector` (EO_CARDS réservé usage futur — non routé, `/simulation/eo` reste `ComingSoon` jusqu'au Sprint EO) ### Modifié - `useSimulation` refacto en consommateur de `SimulationFlowProvider` (source de vérité déplacée hors du hook) - `SujetDisplay` redevient 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ément `simulations_used` déplacé de `simulationController.create()` vers `correctionController.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 `selectSujet` de `useSimulation` — orphelin - FTD-22 tracée résolue partiellement (step `'choosing-subject'` + `goToSubjectPicker` conservé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` — import `Exercice` retiré, `critere.note` → `critere.score`, `ExerciceCard` refactoré pour consommer une `string` rendue en Markdown, clé d'itération par index ### Supprimé - Interface `Exercice { titre, contenu }` de `entities/report/types.ts` — remplacée par `string[]` pour coller au contrat backend ### Contexte (backend associé, expria-backend) Quatre commits côté backend finalisent l'alignement du contrat `Report` : - `feat(corrections)`: renommages `production_modele`→`modele`, `suggestions_idees`→`idees`, ajout `feedback_court` + prompts DeepSeek mis à jour + validations runtime - `feat(corrections)`: réponse enrichie avec `simulation_id` côté `correctionController` - `feat(simulations)`: nouvelle route `GET /simulations/:id` (auth owner, gestion `SIMULATION_NOT_FOUND`/`AUTH_REQUIRED`/`REPORT_NOT_READY`) + 4 tests - `feat(simulations)`: sujet aléatoire (table `sujets`) 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 | null` dans `entities/production/types.ts` - Consommer le sujet retourné dans `SimulationPage` (affichage consigne + docs) - Consommer `feedback_court` dans `RapportPage` (rendu toujours visible — cf. PLANS_TARIFAIRES §2 — déjà supporté par le type `Report`, 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 verrouillage `hasAccess()` (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 `