Some checks are pending
CI / quality (push) Waiting to run
- CHANGELOG : bloc Sprint 6e (Voie A, Bugs 4/5/6, indicateur, cleanup, removed). - PARCOURS : section 4 T2 Live (notes + gating 30 mots, candidat initie, timers prepa/dialogue). - ROADMAP : Sprint 6 marque livre (6b/6c/6e). - GOLDEN_DATASET : D3 corrige (candidat en premier) + annotations Groupe D (D6 partiel, D7-D11 sprints futurs).
901 lines
57 KiB
Markdown
901 lines
57 KiB
Markdown
# 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] — 2026-06-29 — Sprint 6e — T2 Live « Voie A » (mix audio temps réel)
|
||
|
||
### Added
|
||
|
||
- `public/pcm-record-processor.js` — AudioWorklet « tap » branché sur le mix du contexte partagé : prélève le mix (micro candidat + voix IA) en temps réel et émet des chunks Int16 vers le hook d'enregistrement. Permet un WAV aligné temporellement sur une horloge unique.
|
||
- `useAudioCapture` expose désormais `contextRef` (AudioContext partagé) + `mixNodeRef` (GainNode point de convergence) pour partager une horloge unique entre capture, playback et enregistrement.
|
||
- Indicateur de prise de parole du candidat : VAD par RMS sur le flux micro Int16 (seuils `SPEAK_RMS=500` / `SILENCE_RMS=250`, debounce 700 ms) pilotant les transitions `speaking` ↔ `listening`.
|
||
- Détection `newTurn` : un chunk audio IA reçu après > 800 ms de silence IA marque la reprise de parole de l'examinateur → réalignement de l'edge-tracking micro + `USER_SILENT`.
|
||
|
||
### Changed
|
||
|
||
- **Architecture audio « Voie A »** : passage à **UN SEUL AudioContext au rate NATIF** (≈ 48 kHz), partagé par capture / playback / enregistrement. Suppression du forçage `{ sampleRate: 16000 }` et des deux contextes séparés 16 k / 24 k.
|
||
- `useAudioPlayback({ contextRef, mixNodeRef })` ne crée plus son propre contexte : la source IA est routée vers `ctx.destination` (audible) ET vers `mixGain` (captée par le tap). Buffer créé à 24 k, rééchantillonné automatiquement par le contexte natif.
|
||
- `useAudioRecording` : enregistrement via tap worklet sur le mix (`mixGain → recordNode → gain(0) → destination`, sink muet pour pull cross-navigateur). Buffer Int16 hors cycle de vie du contexte (`exportWAV()` survit à la fermeture). **WAV mono au rate natif, single-track, zéro resample** (remplace l'ancien WAV 24 k multi-piste).
|
||
- `useT2LiveSession` : cycle de vie audio aligné sur la « Voie A » — start sur `ws.onopen` après `capture.start()` résolu ; stop sur `endDialogue` (débranche le tap, buffer conservé) ; cancel ferme le contexte (buffer abandonné, aucun export).
|
||
- **Bug 6 — « Nouvelle simulation »** : le routage vers la bonne tâche s'appuie désormais sur le champ `tache` propagé dans le rapport (`report/api.ts`, `types.ts`, `RapportPage.tsx`), sans query param.
|
||
|
||
### Fixed
|
||
|
||
- **Anti-blanc EO** : suppression des silences/blancs en début de dialogue grâce à l'horloge unique et au scheduling continu de la voix IA.
|
||
- Correction de l'écho de la voix candidat (`mixGain` jamais connecté à `destination`).
|
||
- **Bug 4 — « Voir le rapport »** : la navigation vers `/rapport/:id` aboutit bien (garde `navigatingAwayRef` empêchant le cleanup/teardown d'avorter la redirection).
|
||
- **Bug 5 — « Annuler » (`cancelDialogue`)** : arrête l'enregistrement, ne déclenche aucune évaluation, ne produit aucun WAV et ne persiste aucune production (WS fermée sans message de fin).
|
||
- Stabilité de l'uplink micro : l'architecture « Voie A » supprime l'état React réactif sur la `MediaStream` (source du _starving_ d'uplink), au profit de refs stables sur le contexte/mix partagés.
|
||
|
||
### Removed
|
||
|
||
- Helpers `resample16kTo24k` et `mixTracksToInt16` de `audio-utils.ts` (rendus inutiles par l'horloge unique et le single-track). Helpers purs conservés : `arrayBufferToBase64`, `base64ToArrayBuffer`, `int16ToFloat32`, `float32ToInt16`, `concatInt16`, `buildWavHeader`.
|
||
- Instrumentation de diagnostic `[BISECT]` retirée des hooks T2 Live (logique runtime VAD / garde-fous / routage des messages conservée).
|
||
|
||
### Notes
|
||
|
||
- Tous les bugs ciblés (anti-blanc, Voie A, bugs 4/5/6, indicateur de parole) validés **à l'oreille en navigation privée** — console sans `[BISECT]`.
|
||
- Tests frontend : 259 → **269 verts (37 fichiers)**.
|
||
- AudioContext / AudioWorklet / WebSocket non matérialisables en jsdom → validation audio à l'oreille (objectif de la session). `useAudioRecording` couvert sur sa surface pure (export WAV, reset).
|
||
|
||
---
|
||
|
||
## [Unreleased] — 2026-04-26 — Sprint 6c — Frontend T2 Live UI
|
||
|
||
### Added
|
||
|
||
- `t2-machine.ts` — state machine pure T2 Live : 9 états (`idle` → `preparing` → `connecting` → `ready` → `speaking` ↔ `listening` → `processing` → `ended` / `error`), 8 events. 21 tests. Résout FTD-09.
|
||
- `useT2LiveSession.ts` — hook orchestrateur : WebSocket + state machine + hooks audio (capture/playback/recording). Parse format Gemini natif (`serverContent.modelTurn`) + messages applicatifs backend (`warning`/`report`/`error`). Close codes 1000/4001/4003/4004. Timer dialogue 210 s. Ping 30 s keep-alive.
|
||
- `T2LiveContext.tsx` — Provider léger pour partager le sujet sélectionné entre les pages T2.
|
||
- `T2SujetsPage.tsx` — grille de sélection des sujets T2 (`GET /sujets?mode=EO&tache=2`).
|
||
- `T2PreparationPage.tsx` — timer 2 min, consigne affichée, zone de notes locale, bouton « Suggestions d'idées » (DeepSeek, actif immédiatement), bouton « Je suis prêt », pré-warm micro via `getUserMedia`. Transition auto vers dialogue à 0:00.
|
||
- `T2DialoguePage.tsx` — timer 3:30, indicateur d'état IA, waveform, bouton « Terminer ». Écran terminal (state `ended`) : bouton « Télécharger l'audio » (WAV mono 24 kHz) + bouton « Voir le rapport » (→ `/rapport/:id`).
|
||
- 3 routes : `/simulation/eo/t2`, `/simulation/eo/t2/preparation`, `/simulation/eo/t2/dialogue` sous `T2LiveLayout`.
|
||
|
||
### Changed
|
||
|
||
- `TaskSelector.tsx` — carte EO T2 Live déverrouillée via `hasAccess(plan, 'oral_t2_live')` + prop `onT2LiveSelect`. Résout FTD-33.
|
||
- `SimulationEOPage.tsx` — branche `onT2LiveSelect` vers `/simulation/eo/t2`.
|
||
- `entities/production/` — `Tache` type, labels, `mapTacheToSujetParams`, config étendus avec `EO_T2_LIVE`.
|
||
- `features/historique/` — `TACHE_NUMBER` étendu.
|
||
|
||
### Notes
|
||
|
||
- Tests frontend : 238 → 259 verts (+21 — tous sur t2-machine).
|
||
- FTD-09 résolue (state machine testée).
|
||
- FTD-33 résolue (carte déverrouillée via hasAccess).
|
||
- `useT2LiveSession` non testé en unit (WebSocket non supporté jsdom) — validation manuelle prévue.
|
||
|
||
---
|
||
|
||
## [Unreleased] — 2026-04-26 — Sprint 6b — Frontend audio (T2 Live)
|
||
|
||
### Added
|
||
|
||
- `public/pcm-capture-processor.js` — AudioWorklet processor : capture micro, rééchantillonnage vers 16 kHz si `sampleRate` natif différent, conversion Float32 → Int16 LE, chunks de 4096 samples (~256 ms).
|
||
- `src/shared/lib/audio-utils.ts` — 6 helpers purs : `arrayBufferToBase64`, `base64ToArrayBuffer`, `int16ToFloat32`, `float32ToInt16`, `resample16kTo24k`, `buildWavHeader`.
|
||
- `src/features/t2-live/hooks/useAudioCapture.ts` — hook capture : `getUserMedia` (mono, echoCancellation, noiseSuppression) → AudioContext 16 kHz → AudioWorklet → callback `onChunk(base64)`. Cleanup au stop/unmount.
|
||
- `src/features/t2-live/hooks/useAudioPlayback.ts` — hook playback : AudioContext 24 kHz lazy-init, scheduling séquentiel via `start(max(currentTime, lastEndTime))` pour lecture sans gaps. Cleanup au stop/unmount.
|
||
- `src/features/t2-live/hooks/useAudioRecording.ts` — hook recording : buffer chronologique unique normalisé 24 kHz (chunks candidat rééchantillonnés 16→24k), `addAIChunk(base64)` décode en interne, `exportWAV()` → Blob `audio/wav` mono 24 kHz.
|
||
- 12 tests `audio-utils.test.ts` (round-trips base64/ArrayBuffer, clamping int16/float32, interpolation resample, header WAV).
|
||
- 7 tests `useAudioRecording.test.ts` (add candidat resample, add IA, alternance, header WAV, reset, export vide, chunks vides ignorés).
|
||
|
||
### Notes
|
||
|
||
- Tests frontend : 219 → 238 verts (+19).
|
||
- `useAudioCapture` et `useAudioPlayback` dépendent de AudioContext (API navigateur) — validation manuelle au Sprint 6c.
|
||
- AudioWorklet utilisé directement (pas ScriptProcessorNode) — FTD-06 ne s'applique plus pour T2 Live.
|
||
|
||
---
|
||
|
||
## [Unreleased] — 2026-04-26 — Sprint 5.5 Clean FTD
|
||
|
||
### Changed
|
||
|
||
- `StatCards.tsx:90` — `plan === 'free'` remplacé par `!hasAccess(plan, 'dashboard')` (FTD-39, Règle D).
|
||
- `useAudioRecorder.ts:80` — assignation `optionsRef` pendant render refactorée en `useEffect` sans deps, eslint-disable retiré (FTD-38).
|
||
|
||
### Docs
|
||
|
||
- `TECH_DEBT.md` v1.26 → v1.27 — triage dette technique :
|
||
- Gelées : FTD-09 (state machine T2 Live), FTD-33 (carte T2 Live en dur), FTD-42 (modal prorata — Customer Portal suffit).
|
||
- Fermée : FTD-35 (subsumée par FTD-41).
|
||
- Résolues : FTD-14 (anti-FOUC déjà en place, conforme DESIGN_SYSTEM v2.0), FTD-38, FTD-39.
|
||
- 21 → 14 FTD actives (cap 15 respecté).
|
||
|
||
### Notes
|
||
|
||
- FTD-14 : le script inline `.light` était déjà présent dans `index.html` (lignes 14-20), conforme à DESIGN_SYSTEM v2.0 (dark = défaut, `.light` = override). L'exemple `.dark` documenté dans la fiche FTD-14 datait de la DA Boréal v1.0.
|
||
- Tests frontend : 219/219 verts (inchangé).
|
||
|
||
---
|
||
|
||
## [Unreleased] — 2026-04-26 — Sprint 5d — Customer Portal + page Paramètres
|
||
|
||
### Added
|
||
|
||
- `src/features/billing/hooks/useCustomerPortal.ts` — hook `{ openPortal, isLoading, error }` autour de `createCustomerPortalSession` + redirect full-page vers Stripe Customer Portal. Message d'erreur backend (`NO_ACTIVE_SUBSCRIPTION`) propagé tel quel.
|
||
- `src/features/billing/components/AccountBillingSection.tsx` — section UI `Card` : 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 `/parametres` avec 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 vers `useCustomerPortal.openPortal()` au lieu de Stripe Checkout direct (le Customer Portal Stripe affiche le montant prorata + confirmation native). `buildCtaConfigs` refactor : signature `(plan, isStandardPending, isPremiumPending, onUpgrade)` ; loading state combiné selon source ; erreur unifiée `checkoutError ?? portalError`.
|
||
- `src/features/billing/__tests__/PricingPage.test.tsx` — 6e test : Standard click « Passer en Premium » → `createCustomerPortalSession` appelé (et `createCheckoutSession` non 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. `pendingPriceType` permet l'affichage loading par carte sans state local.
|
||
- `src/features/dashboard/hooks/useUpgradeSuccessHandler.ts` — détecte `?upgrade=success` au mount du Dashboard, invalide le cache `PLAN_QUERY_KEY` (refetch automatique du plan), nettoie l'URL via `history.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 vers `useStripeCheckout` (suppression `useMutation` inline + `pendingType` state local).
|
||
- `src/features/dashboard/pages/DashboardPage.tsx` — branche `useUpgradeSuccessHandler` + rend `<UpgradeSuccessBanner>` au-dessus de `<DashboardContent>` quand `showSuccess`.
|
||
|
||
### Cross-repo
|
||
|
||
- `expria-backend@28f8373` — `fix(stripe): cancel_url /tarifs → /plan`. Bug détecté lors de cette session : la route `/tarifs` n'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 : props `cta`, `currentBadge`, `highlighted`, `ctaHint`, `errorMessage`.
|
||
- `src/features/billing/pages/PricingPage.tsx` — orchestration 3 colonnes (Découverte / Standard / Premium) avec gating dynamique selon `usePlan()`. 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` — ajout `VITE_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).
|
||
- `DashboardStandardView` et `BlurredProgression` conservent 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 helper `getMaxScorePerCritere(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 prop `maxScore` : affiche `X/4` (EO Sprint 4.8) ou `X/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 pendant `isRecording`. Respecte
|
||
`prefers-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` — expose `mediaStream: 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) : note `app/` documente entry points + composants layout (AppLayout, Sidebar, Topbar, BottomNav, MaintenancePage). `t2-live/` et `billing/` retirés (non implémentés). Ajout `entities/{patterns,presentation,transcription}` et `features/{historique,progression,design-system}`. Bump v1.1.
|
||
- `ARCHITECTURE.md` §3 — convention `shared/ui/` (wrappers Expria PascalCase) vs `shared/components/ui/` (primitives shadcn kebab-case) documentée (FTD-26).
|
||
- `ConseilNclcCallout.tsx` — props `nclc` + `nclcCible` ajouté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} ! » quand `nclc > nclcCible`).
|
||
- `RapportPage.tsx` — passe `nclc` + `nclcCible` à `ConseilNclcCallout`.
|
||
- `ScoreHero.tsx` — encart de conclusion à 3 états (depasse / atteint / !atteint).
|
||
- `SimulationFlowProvider.tsx` — `useEffect` persiste `production.id` dans `localStorage.expria_simulation_id` pour 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` — directive `eslint-disable-next-line` orpheline retirée
|
||
- `useAudioRecorder.test.ts:77,81` — params `_t`/`_timeslice` neutralisés via `void` (signature mock préservée)
|
||
- `useAudioRecorder.ts:73` — `eslint-disable-next-line react-hooks/refs` + commentaire renvoyant à FTD-38
|
||
- `QuestionnaireT1Page.test.tsx:10` — import `React` inutilisé 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 🟢 — `useAudioRecorder` ref 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_nclc` backend 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 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éatoire
|
||
- `features/simulations/pages/PreEnregistrementEOPage.tsx` — consigne + durée recommandée
|
||
- `features/simulations/pages/EnregistrementEOPage.tsx` — enregistrement audio + auto-submit à expiration
|
||
- `features/simulations/pages/ModeChoixT1Page.tsx` — choix Générer / Enregistrer directement
|
||
- `features/simulations/pages/QuestionnaireT1Page.tsx` — 5 champs + validation Zod + génération IA
|
||
- `features/simulations/pages/PresentationGenereeT1Page.tsx` — texte généré, modifier, copier, .txt, refaire, localStorage
|
||
- `features/simulations/hooks/useAudioRecorder.ts` — MediaRecorder, timer, maxSeconds, auto-stop, download
|
||
- `features/simulations/hooks/useDeepgramLive.ts` — conservé dormant (FTD-37)
|
||
- `features/simulations/components/AudioRecorder.tsx` — UI enregistrement, maxSeconds/onMaxReached
|
||
- `features/simulations/components/TranscriptionDisplay.tsx` — conservé dormant
|
||
- `entities/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=done
|
||
- `entities/report/api.ts` — CORRECTION_EE_TIMEOUT_MS=60s / CORRECTION_EO_TIMEOUT_MS=120s
|
||
- `entities/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_MS` augmenté de 5s à 15s dans `api-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 via `hasAccess`. Aucun `plan === '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.css` par palette Charcoal (DESIGN_SYSTEM.md v2.0). Dark = thème par défaut, `.light` = override via `@custom-variant light`.
|
||
- Sidebar navy `#0C1528` permanent (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étection `prefers-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).
|
||
- `DesignSystemPage` réécrite avec palette Charcoal complète.
|
||
- `docs/adr/006-stack-versions-2026.md` mis à jour : tokens Charcoal, suppression `@variant dark` et `.dark {}`.
|
||
|
||
### Fixed
|
||
|
||
- Logo Expria : wordmark forcé en `text-white` dans 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** : `useAutosave` ne fire plus après correction — `enabled` propagé avec `step !== 'done' && step !== 'correcting'` depuis `SimulationForm`. 2 tests de régression ajoutés.
|
||
- **FTD-24 résolu** : polling automatique 3s dans `useRapport` quand `exercices_status` ou `modele_status === 'pending'`. Arrêt auto dès ready/error. Timeout 2 min avec message + bouton Réessayer dans `JobStatusFallback`. 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.yml` créé 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.md` v1.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.md` v1.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.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.6c — Analyse patterns (Backend + Frontend)
|
||
|
||
### Added (backend)
|
||
|
||
- `GET /users/patterns` — analyse des patterns récurrents pour utilisateur Premium.
|
||
- Auth : `authMiddleware` + `planMiddleware('pattern_analysis')` (403 `PLAN_INSUFFICIENT` si 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 }`.
|
||
- `patternsController.aggregatePatterns` (pure) : agrège les `erreurs_codes` sur N productions, seuil 3/5, dédoublonnage intra-prod (un même code dans un rapport ne compte qu'une fois), codes `autre` distingué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 + cache `pattern_analyses` + recompute + DeepSeek + INSERT. Stratégie d'invalidation : `MAX(productions.created_at) > lastAnalysis.created_at` → recompute, sinon cache hit.
|
||
- `generatePatternExercices` dans `src/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 via `isValidCritere`.
|
||
- Table `pattern_analyses` — migration `005_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 sur `aggregatePatterns`, 4 sur `computePreparationIndex`, 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 sous `AppLayout` + `ProtectedRoute`, remplace le placeholder `ComingSoon`.
|
||
- `ProgressionPage` — orchestre `usePlan` + `usePatterns`, gate plan via `hasAccess('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 nouveau `CRITERE_LABELS` + badge fréquence (3/5, 4/5, 5/5).
|
||
- **`PatternExerciceCard`** — _nouveau composant lesson-style_, non interactif (contrairement à `ExerciceInteractive` du 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 + CTA `Dé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 explicite `hasAccess('pattern_analysis')` → composant retourne `null` pour Free/Standard (pas rendu dans le DOM).
|
||
- `usePatterns(plan)` — hook TanStack Query partagé entre `/progression` et dashboard ; clé `['users', 'patterns']`, `staleTime: 60s`, `enabled` conditionné par `hasAccess` pour é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_LABELS` exporté depuis `entities/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 sur `MonProfilPreparation` (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 `generatePatternExercices` throw, le backend persiste quand même l'analyse avec `exercises: []` 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).
|
||
- **`ExerciceInteractive` NON 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 push` avant 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 DESC` cô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_ERROR` si `page`/`limit` invalide, `401 AUTH_REQUIRED` si JWT absent, `500 INTERNAL_ERROR` si DB down.
|
||
- `simulationController.list(options, profile)` + interfaces `ListOptions`, `ListItem`, `ListResult`.
|
||
- 12 nouveaux tests sur la route `GET /simulations` (186 tests backend verts, +12 vs baseline 174).
|
||
|
||
### Added (frontend)
|
||
|
||
- Page `/historique` (route sous `AppLayout` + `ProtectedRoute`, remplace le placeholder `ComingSoon`).
|
||
- `HistoriquePage` — orchestre `usePlan` + `useSimulationsList`, state local de pagination, gating plan Free via `hasAccess('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 »
|
||
- `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: keepPreviousData` pour éviter le flash de squelette au changement de page.
|
||
- `listSimulations(page, limit)` dans `entities/production/api.ts` — wrap `apiFetch` + `URLSearchParams`.
|
||
- Types `SimulationListItem` et `SimulationsListResponse` dans `entities/production/types.ts`.
|
||
- `src/shared/lib/date.ts` — helper `formatRelativeDate(iso, now?)` basé sur `Intl.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` + 11 `SimulationsList.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` — `RapportPage` gère le cas `REPORT_NOT_READY` (FTD-21) en redirigeant vers `/simulation/ee`.
|
||
- `BlurredPreview` dupliqué localement dans `SimulationsList` (pattern équivalent à `BlurredSection` de `RapportPage`). À extraire en `shared/` 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) 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é)
|