fix(rapport): patch temporaire conclusion conseil_nclc quand NCLC dépassé (FTD-40, FTD-41)
This commit is contained in:
parent
822b02a2d1
commit
06fbfe3f9b
4 changed files with 148 additions and 33 deletions
|
|
@ -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, '<feature>')` 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
|
||||
|
|
@ -502,7 +554,7 @@ 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 |
|
||||
|
|
@ -527,3 +579,5 @@ Frontend :
|
|||
| 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.** |
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<section aria-label="Plan d'action NCLC">
|
||||
<h2 className="mb-3 text-base font-semibold text-ink-primary">Plan d'action NCLC</h2>
|
||||
|
|
@ -35,9 +42,16 @@ export function ConseilNclcCallout({ conseil }: Props) {
|
|||
Action prioritaire
|
||||
</p>
|
||||
<div className="text-sm leading-relaxed text-ink-primary">
|
||||
{depasse ? (
|
||||
<p>
|
||||
Excellent travail — vous avez dépassé votre objectif. Continuez sur cette lancée
|
||||
pour viser NCLC {nclc + 1} !
|
||||
</p>
|
||||
) : (
|
||||
<ReactMarkdown disallowedElements={['script', 'iframe']}>
|
||||
{conseil.action_prioritaire}
|
||||
</ReactMarkdown>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -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(<ConseilNclcCallout conseil={conseil} nclc={8} nclcCible={9} />)
|
||||
expect(screen.getByText(/connecteurs logiques/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('nclc = cible : rend action_prioritaire backend', () => {
|
||||
render(<ConseilNclcCallout conseil={conseil} nclc={9} nclcCible={9} />)
|
||||
expect(screen.getByText(/connecteurs logiques/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('nclc > cible : substitue par texte fixe avec NCLC {nclc+1}', () => {
|
||||
render(<ConseilNclcCallout conseil={conseil} nclc={10} nclcCible={9} />)
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
|
@ -268,7 +268,11 @@ export function RapportPage() {
|
|||
<CriteresSection rapport={rapport} />
|
||||
</BlurredSection>
|
||||
|
||||
<ConseilNclcCallout conseil={rapport.conseil_nclc} />
|
||||
<ConseilNclcCallout
|
||||
conseil={rapport.conseil_nclc}
|
||||
nclc={rapport.nclc}
|
||||
nclcCible={rapport.nclc_cible}
|
||||
/>
|
||||
|
||||
<BlurredSection
|
||||
visible={isSectionVisible(planData.plan, 'exercices')}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue