From 06fbfe3f9b34c77394e292059ff927bb79d248cc Mon Sep 17 00:00:00 2001
From: Hermann_Kitio
Date: Sat, 25 Apr 2026 21:16:00 +0300
Subject: [PATCH] =?UTF-8?q?fix(rapport):=20patch=20temporaire=20conclusion?=
=?UTF-8?q?=20conseil=5Fnclc=20quand=20NCLC=20d=C3=A9pass=C3=A9=20(FTD-40,?=
=?UTF-8?q?=20FTD-41)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/TECH_DEBT.md | 108 +++++++++++++-----
.../components/rapport/ConseilNclcCallout.tsx | 24 +++-
.../__tests__/ConseilNclcCallout.test.tsx | 43 +++++++
.../simulations/pages/RapportPage.tsx | 6 +-
4 files changed, 148 insertions(+), 33 deletions(-)
create mode 100644 src/features/simulations/components/rapport/__tests__/ConseilNclcCallout.test.tsx
diff --git a/docs/TECH_DEBT.md b/docs/TECH_DEBT.md
index 36d3143..2523a32 100644
--- a/docs/TECH_DEBT.md
+++ b/docs/TECH_DEBT.md
@@ -1,6 +1,6 @@
# TECH_DEBT.md — Expria Frontend
-> **Document de référence — Version 1.23**
+> **Document de référence — Version 1.25**
> Ce document recense les décisions techniques prises par pragmatisme qui devront être revisitées, les stubs temporaires, et les fonctionnalités reportées.
> À mettre à jour après chaque session de développement.
>
@@ -209,6 +209,58 @@ Vient du pattern `c.json(result, result.status)` où `result` contient déjà `s
---
+### FTD-38 — `useAudioRecorder` : mise à jour de ref pendant le render
+
+**Priorité :** 🟢 Mineur
+**Statut :** Ouvert — introduit Sprint 4c-1, signalé Sprint 4.5
+**Estimation de session :** 1h
+**Description :** `optionsRef.current = options` est exécuté pendant le render (ligne 73 de `useAudioRecorder.ts`) pour capturer les callbacks inline frais (`onMaxReached`) sans réabonner les effets à chaque render. ESLint signale ce pattern via la règle `react-hooks/refs` (« Cannot update ref during render »). Désactivation locale en place avec commentaire explicatif renvoyant à cette FTD.
+
+**À faire :** refactorer en `useEffect(() => { optionsRef.current = options })` ou équivalent — l'effet s'exécute après commit, donc avant le prochain render qui utilisera la ref. Risque : 195 lignes de tests sur ce hook (`useAudioRecorder.test.ts`) — vérifier que la sémantique reste identique (auto-stop à `maxSeconds`, callback `onMaxReached` invoqué une seule fois, etc.).
+
+**Condition de résolution :** session dédiée — non bloquant pour la prod (le pattern fonctionne, c'est un avertissement de bonnes pratiques React 19).
+
+---
+
+### FTD-39 — Règle D violée dans `StatCards.tsx` (`plan === 'free'` en dur)
+
+**Priorité :** 🟡 Important
+**Statut :** Ouvert — introduit Sprint UI Polish (commit `4005673`), signalé Sprint 4.5
+**Estimation de session :** 30 min
+**Description :** [`src/features/dashboard/components/StatCards.tsx:90`](../src/features/dashboard/components/StatCards.tsx) contient `{plan === 'free' && (...)}` — viole la Règle D de `DEVELOPMENT_PRINCIPLES.md` (interdiction de comparer `plan === 'xxx'` en dur dans les composants). Doit passer par `hasAccess(plan, '')` ou `canSimulate(plan, used)` selon le sens du gating.
+
+**À faire :**
+
+- Identifier la sémantique du gating (probablement « afficher le compteur de simulations restantes uniquement aux Free » → utiliser le résultat de `canSimulate(plan, simulationsUsed)`).
+- Remplacer la condition par le helper approprié.
+- Vérifier que les tests Dashboard (Free/Standard/Premium) restent verts.
+
+**Condition de résolution :** session dédiée Dashboard — pas de refacto sans plan validé (Règle A).
+
+---
+
+### FTD-40 — Conclusion rapport incohérente quand NCLC atteint > cible (backend)
+
+**Priorité :** 🟡 Important
+**Statut :** Ouvert — patch frontend temporaire en place (Sprint 4.5)
+**Estimation de session :** 1h (session backend)
+**Description :** Le prompt maître DeepSeek génère toujours un message d'encouragement vers l'objectif cible, même quand `nclcObtenu > nclcCible`. Le champ `conseil_nclc.action_prioritaire` contient alors un texte incohérent (« tu atteindras facilement le niveau 9 » pour un candidat NCLC 10). Patch frontend en place dans [ConseilNclcCallout.tsx](../src/features/simulations/components/rapport/ConseilNclcCallout.tsx) (condition `depasse` → texte générique). Fix robuste : modifier le prompt maître backend pour détecter `nclcObtenu > nclcCible` et générer un message de maintien/progression vers NCLC suivant.
+
+**Condition de résolution :** prompt backend mis à jour + patch frontend retiré.
+
+---
+
+### FTD-41 — Persistance présentation EO T1 en base de données
+
+**Priorité :** 🔴 Critique
+**Statut :** Ouvert — localStorage instable (FTD-35 partiellement liée)
+**Estimation de session :** 1 jour (session fullstack)
+**Description :** La présentation générée pour EO T1 est stockée uniquement en localStorage (`expria_eo_t1_presentation`). Au refresh, une redirect prématurée dans `PresentationGenereeT1Page` (`shouldRedirect` déclenché avant hydratation async complète) efface la session. Solution retenue : nouvelle colonne `presentation_t1` (TEXT, nullable) sur la table `productions` + `PATCH /simulations/:id/presentation` + bouton « Sauvegarder » explicite dans `PresentationGenereeT1Page`. Le localStorage devient brouillon temporaire uniquement. Résout FTD-35.
+
+**Condition de résolution :** migration DB + endpoint backend + bouton frontend implémentés et testés.
+
+---
+
### FTD-33 — Carte EO_T2_LIVE verrouillée en dur (pas via `hasAccess`)
**Priorité :** 🟢 Mineur
@@ -501,29 +553,31 @@ Frontend :
## 6. Historique de ce document
-| Version | Date | Changements |
-| ------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| 1.0 | 2026-04-17 | Création initiale avec 9 FTD identifiées depuis l'audit backend et les décisions d'architecture |
-| 1.1 | 2026-04-17 | Ajout FTD-10 (Semgrep CI), FTD-11 (`@theme` Tailwind 4), FTD-12 (tests `api-client`) suite à l'étape 11 du Sprint 0 |
-| 1.2 | 2026-04-17 | Ajout FTD-13 résolu (incompatibilité Vitest 3 / Vite 8) suite à l'étape 12-bis du Sprint 0 |
-| 1.3 | 2026-04-18 | FTD-11 résolu (design system Sprint 0.5) ; ajout FTD-14 (anti-FOUC), FTD-15 (option 'system' thème) |
-| 1.4 | 2026-04-18 | FTD-16 résolu (VITE_MAINTENANCE_MODE implémenté — Sprint 1 étape 6) |
-| 1.5 | 2026-04-19 | Ajout FTD-17 (clé ['plan'] dupliquée entre features — Sprint 3 étape 14) |
-| 1.6 | 2026-04-20 | Ajout FTD-18 (SimulationForm shadcn Button — Sprint 0.5 bis D2) ; ajout FTD-19 (token --shadow-focus manquant — Sprint 0.5 bis D2) |
-| 1.7 | 2026-04-20 | Ajout FTD-20 🔴 (GET /simulations/:id manquant backend — bloque RapportPage Sprint 3 étape 15) |
-| 1.8 | 2026-04-20 | Ajout FTD-21 🔴 (persistance session simulation — prod + sujet perdus au refresh, session dédiée après G1-G5) |
-| 1.9 | 2026-04-21 | FTD-22 résolu partiellement (nettoyage code orphelin refonte `/sujets` — `SujetSelector` + `selectSujet` supprimés ; `choosing-subject` + `goToSubjectPicker` conservés) |
-| 1.10 | 2026-04-21 | FTD-21 résolu partiellement pour `/simulation/ee` (autosave 30 s + `beforeunload` + reprise via `localStorage` + `PATCH /:id/contenu` + `PATCH /:id/sujet` + `getById` tolère `rapport=null`) ; EO + examen restent ouverts |
-| 1.11 | 2026-04-22 | Sprint 3.5 Clean — FTD-17, FTD-18, FTD-19 résolus. 15 FTD actives restantes (cap de 15 respecté) |
-| 1.12 | 2026-04-22 | Sprint 3.6a — Ajout FTD-23 🟡 (useAutosave fire après correction). 16 FTD actives → cap de 15 dépassé temporairement, à revoir au prochain clean. |
-| 1.13 | 2026-04-22 | Sprint 3.6b — Ajout FTD-24 🟡 (polling auto exercices/modèle pending). 17 FTD actives → cap dépassé, un clean 3.6.5 devra résoudre FTD-23/24 ensemble. |
-| 1.14 | 2026-04-23 | Triage : FTD-04, FTD-05, FTD-20, FTD-22 fermées. FTD-25, FTD-26 ajoutées. 15 FTD actives (cap respecté). |
-| 1.15 | 2026-04-23 | Réorg sécurité : FTD-06, FTD-08, FTD-15 gelées (backlog post-MVP). FTD-27 🔴, FTD-28 🔴, FTD-29 🟡 ajoutées (sécurité). 15 FTD actives (cap respecté). |
-| 1.16 | 2026-04-23 | FTD-29 fermée (Dependabot config). 14 FTD actives. |
-| 1.17 | 2026-04-23 | FTD-27 fermée (CI backend). 13 FTD actives. |
-| 1.18 | 2026-04-23 | FTD-28 fermée (Semgrep CI). CI frontend verte pour la première fois. 12 FTD actives. |
-| 1.19 | 2026-04-23 | FTD-23 et FTD-24 fermées (clean useAutosave après correction + polling automatique jobs pending dans useRapport). 10 FTD actives (cap 15). |
-| 1.20 | 2026-04-25 | Sprint 4c-1 — Ajout FTD-30 🟡 (rotation token Deepgram sans grace period), FTD-31 🟢 (page enregistrement EO non resumable), FTD-32 🟢 (Safari iOS non testé), FTD-33 🟢 (EO_T2_LIVE verrouillé en dur). 14 FTD actives (cap 15 respecté). |
-| 1.21 | 2026-04-25 | Sprint 4c-2 — Ajout FTD-34 🟢 (présentation T1 en localStorage clair), FTD-35 🟡 (refresh sans simulation active sur PresentationGenereeT1Page). **16 FTD actives — cap dépassé temporairement, accepté par Hermann pour cette session ; clean à planifier au prochain Sprint.** |
-| 1.22 | 2026-04-25 | Sprint 4c-3 — Ajout FTD-36 🟡 (upload audio base64 sans progression), FTD-37 🟢 (code Deepgram live dormant à trancher). FTD-30 dégradée 🟡→🟢 et passée en « gelé » (Deepgram live mis en pause). **17 FTD actives — cap toujours dépassé, clean prioritaire au Sprint suivant.** |
-| 1.23 | 2026-04-25 | FTD-25 et FTD-26 fermées (ARCHITECTURE.md §3 reflète l'arborescence réelle + convention `shared/ui/` vs `shared/components/ui/` documentée). 15 FTD actives (cap respecté). |
+| Version | Date | Changements |
+| ------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| 1.0 | 2026-04-17 | Création initiale avec 9 FTD identifiées depuis l'audit backend et les décisions d'architecture |
+| 1.1 | 2026-04-17 | Ajout FTD-10 (Semgrep CI), FTD-11 (`@theme` Tailwind 4), FTD-12 (tests `api-client`) suite à l'étape 11 du Sprint 0 |
+| 1.2 | 2026-04-17 | Ajout FTD-13 résolu (incompatibilité Vitest 3 / Vite 8) suite à l'étape 12-bis du Sprint 0 |
+| 1.3 | 2026-04-18 | FTD-11 résolu (design system Sprint 0.5) ; ajout FTD-14 (anti-FOUC), FTD-15 (option 'system' thème) |
+| 1.4 | 2026-04-18 | FTD-16 résolu (VITE_MAINTENANCE_MODE implémenté — Sprint 1 étape 6) |
+| 1.5 | 2026-04-19 | Ajout FTD-17 (clé ['plan'] dupliquée entre features — Sprint 3 étape 14) |
+| 1.6 | 2026-04-20 | Ajout FTD-18 (SimulationForm shadcn Button — Sprint 0.5 bis D2) ; ajout FTD-19 (token --shadow-focus manquant — Sprint 0.5 bis D2) |
+| 1.7 | 2026-04-20 | Ajout FTD-20 🔴 (GET /simulations/:id manquant backend — bloque RapportPage Sprint 3 étape 15) |
+| 1.8 | 2026-04-20 | Ajout FTD-21 🔴 (persistance session simulation — prod + sujet perdus au refresh, session dédiée après G1-G5) |
+| 1.9 | 2026-04-21 | FTD-22 résolu partiellement (nettoyage code orphelin refonte `/sujets` — `SujetSelector` + `selectSujet` supprimés ; `choosing-subject` + `goToSubjectPicker` conservés) |
+| 1.10 | 2026-04-21 | FTD-21 résolu partiellement pour `/simulation/ee` (autosave 30 s + `beforeunload` + reprise via `localStorage` + `PATCH /:id/contenu` + `PATCH /:id/sujet` + `getById` tolère `rapport=null`) ; EO + examen restent ouverts |
+| 1.11 | 2026-04-22 | Sprint 3.5 Clean — FTD-17, FTD-18, FTD-19 résolus. 15 FTD actives restantes (cap de 15 respecté) |
+| 1.12 | 2026-04-22 | Sprint 3.6a — Ajout FTD-23 🟡 (useAutosave fire après correction). 16 FTD actives → cap de 15 dépassé temporairement, à revoir au prochain clean. |
+| 1.13 | 2026-04-22 | Sprint 3.6b — Ajout FTD-24 🟡 (polling auto exercices/modèle pending). 17 FTD actives → cap dépassé, un clean 3.6.5 devra résoudre FTD-23/24 ensemble. |
+| 1.14 | 2026-04-23 | Triage : FTD-04, FTD-05, FTD-20, FTD-22 fermées. FTD-25, FTD-26 ajoutées. 15 FTD actives (cap respecté). |
+| 1.15 | 2026-04-23 | Réorg sécurité : FTD-06, FTD-08, FTD-15 gelées (backlog post-MVP). FTD-27 🔴, FTD-28 🔴, FTD-29 🟡 ajoutées (sécurité). 15 FTD actives (cap respecté). |
+| 1.16 | 2026-04-23 | FTD-29 fermée (Dependabot config). 14 FTD actives. |
+| 1.17 | 2026-04-23 | FTD-27 fermée (CI backend). 13 FTD actives. |
+| 1.18 | 2026-04-23 | FTD-28 fermée (Semgrep CI). CI frontend verte pour la première fois. 12 FTD actives. |
+| 1.19 | 2026-04-23 | FTD-23 et FTD-24 fermées (clean useAutosave après correction + polling automatique jobs pending dans useRapport). 10 FTD actives (cap 15). |
+| 1.20 | 2026-04-25 | Sprint 4c-1 — Ajout FTD-30 🟡 (rotation token Deepgram sans grace period), FTD-31 🟢 (page enregistrement EO non resumable), FTD-32 🟢 (Safari iOS non testé), FTD-33 🟢 (EO_T2_LIVE verrouillé en dur). 14 FTD actives (cap 15 respecté). |
+| 1.21 | 2026-04-25 | Sprint 4c-2 — Ajout FTD-34 🟢 (présentation T1 en localStorage clair), FTD-35 🟡 (refresh sans simulation active sur PresentationGenereeT1Page). **16 FTD actives — cap dépassé temporairement, accepté par Hermann pour cette session ; clean à planifier au prochain Sprint.** |
+| 1.22 | 2026-04-25 | Sprint 4c-3 — Ajout FTD-36 🟡 (upload audio base64 sans progression), FTD-37 🟢 (code Deepgram live dormant à trancher). FTD-30 dégradée 🟡→🟢 et passée en « gelé » (Deepgram live mis en pause). **17 FTD actives — cap toujours dépassé, clean prioritaire au Sprint suivant.** |
+| 1.23 | 2026-04-25 | FTD-25 et FTD-26 fermées (ARCHITECTURE.md §3 reflète l'arborescence réelle + convention `shared/ui/` vs `shared/components/ui/` documentée). 15 FTD actives (cap respecté). |
+| 1.24 | 2026-04-25 | Sprint 4.5 Clean — Ajout FTD-38 🟢 (`useAudioRecorder` ref mise à jour pendant render — eslint-disable local en place) et FTD-39 🟡 (Règle D violée dans `StatCards.tsx` — préexistant Sprint UI Polish). 17 FTD actives — cap dépassé temporairement, à résorber au Sprint 5.5. |
+| 1.25 | 2026-04-25 | Sprint 4.5 — Ajout FTD-40 🟡 (conclusion `conseil_nclc` backend incohérente quand NCLC atteint > cible — patch frontend en place dans `ConseilNclcCallout`) et FTD-41 🔴 (persistance présentation EO T1 en BDD — résout FTD-35). **19 FTD actives — cap 15 dépassé de 4. Résorption obligatoire au Sprint 5.5 avant toute nouvelle FTD.** |
diff --git a/src/features/simulations/components/rapport/ConseilNclcCallout.tsx b/src/features/simulations/components/rapport/ConseilNclcCallout.tsx
index 63e87b2..17a2c5e 100644
--- a/src/features/simulations/components/rapport/ConseilNclcCallout.tsx
+++ b/src/features/simulations/components/rapport/ConseilNclcCallout.tsx
@@ -9,13 +9,20 @@
import ReactMarkdown from 'react-markdown'
import { Card } from '@/shared/ui/Card'
-import type { ConseilNclc } from '@/entities/report/types'
+import type { ConseilNclc, NclcCible } from '@/entities/report/types'
interface Props {
conseil: ConseilNclc
+ nclc: number
+ nclcCible: NclcCible
}
-export function ConseilNclcCallout({ conseil }: Props) {
+export function ConseilNclcCallout({ conseil, nclc, nclcCible }: Props) {
+ // PATCH TEMPORAIRE — à retirer quand FTD-40 (fix prompt backend) est résolu.
+ // Le prompt maître DeepSeek génère un message d'encouragement vers `nclcCible`
+ // même quand `nclc > nclcCible` ; on substitue alors un texte cohérent.
+ const depasse = nclc > nclcCible
+
return (
Plan d'action NCLC
@@ -35,9 +42,16 @@ export function ConseilNclcCallout({ conseil }: Props) {
Action prioritaire
-
- {conseil.action_prioritaire}
-
+ {depasse ? (
+
+ Excellent travail — vous avez dépassé votre objectif. Continuez sur cette lancée
+ pour viser NCLC {nclc + 1} !
+
+ ) : (
+
+ {conseil.action_prioritaire}
+
+ )}
diff --git a/src/features/simulations/components/rapport/__tests__/ConseilNclcCallout.test.tsx b/src/features/simulations/components/rapport/__tests__/ConseilNclcCallout.test.tsx
new file mode 100644
index 0000000..50edb21
--- /dev/null
+++ b/src/features/simulations/components/rapport/__tests__/ConseilNclcCallout.test.tsx
@@ -0,0 +1,43 @@
+/**
+ * Tests — ConseilNclcCallout (Sprint 4.5).
+ *
+ * Couvre le patch temporaire FTD-40 :
+ * - nclc < cible : affiche action_prioritaire backend
+ * - nclc = cible : affiche action_prioritaire backend
+ * - nclc > cible : substitue le texte par « Excellent travail — … NCLC {nclc+1} »
+ */
+
+import { describe, it, expect, afterEach } from 'vitest'
+import { render, screen, cleanup } from '@testing-library/react'
+import { ConseilNclcCallout } from '../ConseilNclcCallout'
+import type { ConseilNclc } from '@/entities/report/types'
+
+afterEach(cleanup)
+
+const conseil: ConseilNclc = {
+ nclc_cible: 'NCLC 9',
+ ecart: '2 points',
+ action_prioritaire: 'Concentre-toi sur les connecteurs logiques pour atteindre NCLC 9.',
+}
+
+describe('ConseilNclcCallout — patch FTD-40', () => {
+ it('nclc < cible : rend action_prioritaire backend', () => {
+ render()
+ expect(screen.getByText(/connecteurs logiques/i)).toBeInTheDocument()
+ })
+
+ it('nclc = cible : rend action_prioritaire backend', () => {
+ render()
+ expect(screen.getByText(/connecteurs logiques/i)).toBeInTheDocument()
+ })
+
+ it('nclc > cible : substitue par texte fixe avec NCLC {nclc+1}', () => {
+ render()
+ expect(
+ screen.getByText(
+ /Excellent travail — vous avez dépassé votre objectif\. Continuez sur cette lancée pour viser NCLC 11 !/,
+ ),
+ ).toBeInTheDocument()
+ expect(screen.queryByText(/connecteurs logiques/i)).not.toBeInTheDocument()
+ })
+})
diff --git a/src/features/simulations/pages/RapportPage.tsx b/src/features/simulations/pages/RapportPage.tsx
index 1385579..f807336 100644
--- a/src/features/simulations/pages/RapportPage.tsx
+++ b/src/features/simulations/pages/RapportPage.tsx
@@ -268,7 +268,11 @@ export function RapportPage() {
-
+