# 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 ## 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 `