expria-frontend/docs/CHANGELOG.md
2026-04-22 20:14:38 +03:00

354 lines
No EOL
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 <nom>
### 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.6b — Qualité correction — Frontend
### Added
- `NclcCibleSelector` (segmented control NCLC 9 / NCLC 10) dans `SimulationForm` — valeur propagée au payload `POST /corrections/ee` via `SimulationFlowProvider.submitText(texte, nclcCible)`.
- Composants `rapport/` dans `features/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 pour `exercices_status` / `modele_status` en `'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èle `pending` (refresh manuel en MVP).
### Changed
- `entities/report/types.ts` — refonte complète alignée sur le backend Sprint 3.6a : `Report` remplace l'ancien (revelation, diagnostic, criteres enrichis, conseil_nclc, erreurs_codes top-level, exercices dynamiques, modele structuré, statuts pending/ready/error). Suppression de `feedback_court`, `erreurs[]`, `modele:string`, `idees[]` (obsolètes).
- `entities/report/lib.ts``BlurableSection` réduite à `'criteres' | 'exercices' | 'modele'` : `revelation`, `diagnostic`, `conseil_nclc` deviennent visibles pour tous les plans conformément à PLANS_TARIFAIRES.md §2.
- `entities/production/types.ts``SimulationState` étendu avec `nclc_cible`, `exercices`, `exercices_status`, `modele`, `modele_status` ; `SimulationRapport` aligné sur `CorrectionRapport` backend.
- `entities/report/api.ts``getReport` recombine `SimulationState.rapport` + `exercices` + `modele` + statuts en un `Report` unifié pour `useRapport`.
- `RapportPage.tsx` — réécriture complète : câble tous les nouveaux composants, branche le gating plan via `isSectionVisible`, affiche `JobStatusFallback` pour les jobs asynchrones. Résout l'écran blanc post-Sprint 3.6a.
- `floutage.test.ts` réécrit (17 tests — matrice de visibilité + helpers lib).
### Fixed
- **Race condition `modele_status`** (backend) : l'update principal de correction écrasait `modele_status='ready'` déjà posé par `runModeleJob` (lancé en parallèle option b). `correctionController.correctEE` ne 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/:id` est déclenchée une seule fois dans `correctMutation.onSuccess` du provider.
- **Boucle retour /sujets → SimulationPage** : même pattern sticky pour `step === 'choosing-subject' → navigate('/sujets')`. Supprimé ; navigation initiale vers `/sujets` déplacée dans `createMutation.onSuccess`.
- **RapportPage hors SimulationFlowProvider** : la route `/rapport/:id` n'était pas sous `SimulationFlowLayout` — l'appel à `useSimulation()` depuis RapportPage throw. Route déplacée sous le layout, l'instance du provider est partagée avec `/simulation/ee` et `/sujets`.
### Added
- Bouton « Nouvelle simulation » en bas de `RapportPage` qui `reset()` + `navigate('/simulation/ee')`.
- `reset()` explicite dans le bouton « ← Retour » de `SujetsPage` avant la navigation, pour empêcher tout re-déclenchement de la garde sticky.
### Changed
- Navigations post-mutation déplacées dans `onSuccess` du provider (pattern cohérent pour `createMutation``/sujets` et `correctMutation``/rapport/:id`). Plus de useEffect réactif aux changements de `step` côté SimulationPage.
- `SujetsPage` : garde étendue de `!production` à `!production \|\| step === 'idle' \|\| step === 'done'` pour couvrir le cas post-rapport (évite le 400 VALIDATION_ERROR sur `PATCH /simulations/:id/sujet` d'une simulation déjà corrigée).
- `RapportPage` breadcrumb : `<Link>` remplacé par `<button>` qui `reset()` avant navigate.
### Notes
- **Option β retenue** : frontend aligné sur la structure backend réelle du Sprint 3.6a. Aucun aller-retour backend.
- `feedback_court` supprimé de l'UI ; `diagnostic` remplace 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 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 `<button>` si `onClick` fourni)
- `src/shared/ui/Badge.tsx` primitive Badge (variants: plan/nclc/neutral ; couleur selon `planValue` pour variant plan)
### Modifié
- `src/app/router.tsx` layout routes via `PrivateLayout` (`ProtectedRoute` + `AppLayout` + `Outlet`) ; `ComingSoon` inline ; redirect `/simulation` `/simulation/ee`
- `src/features/simulations/components/TaskSelector.tsx` refonte avec `Card interactive` / `Card default opacity-60`, `Badge` "EE"/"EO", eyebrow `tracking-widest`, icône verrou
- `src/features/simulations/pages/SimulationPage.tsx` suppression header interne (Logo + ThemeToggle) ; root `<main>` ; `Button` migré vers `@/shared/ui/Button` `variant="secondary"`
- `src/features/dashboard/pages/DashboardPage.tsx` suppression header interne ; `Button` `variant="primary"` avec `navigate('/simulation/ee')` ; `Badge` `variant="plan" planValue={data.plan}` ; tout migré vers `@/shared/ui/`
### Documentation
- `docs/TECH_DEBT.md` v1.6 ajout FTD-18 (SimulationForm migration Button), FTD-19 (token `--shadow-focus` manquant)
### 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_MODE` dans `main.tsx` si `true`, aucun provider ne se monte, aucun appel réseau
- Variable `VITE_MAINTENANCE_MODE` dans `env.ts` (optionnelle, défaut `false`)
- 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 — ajout `VITE_MAINTENANCE_MODE` dans la liste des variables
- `TECH_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é)