# Changelog

Tous les changements notables de ce projet seront documentés dans ce fichier.

Le format est basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/),
et ce projet adhère au [Semantic Versioning](https://semver.org/lang/fr/).

## [Non publié]

### Corrigé
- **Slidia — le réglage d'affichage des images est enfin appliqué à l'export.** Remplir, Ajuster et Étirer produisaient le même `.pptx` : toujours étiré. `PptxBuilder` lisait les dimensions de la zone dans le `a:xfrm` du masque, absent de la plupart d'entre eux — y compris de tous les masques système, dont les zones héritent leurs dimensions du master. Faute de dimensions, `buildBlipFill()` retombait sur son repli « étirer », quel que soit le choix de l'utilisateur. Le builder relit désormais la géométrie déjà résolue par `PptxParser` dans `layoutsJson` (avec remontée directe au master en repli) — les mêmes dimensions que celles sur lesquelles l'éditeur calcule son aperçu de cadrage.
- **Slidia — « Ajuster » porté par la géométrie de la forme, plus par des marges de remplissage.** Les insets d'un `a:fillRect` sont une consigne que tous les lecteurs n'honorent pas (LibreOffice les ignore, vérifié au rendu). La forme image est maintenant rétrécie au rapport exact de l'image et centrée dans la zone du masque : l'image remplit son cadre sans déformation, et le vide autour est le letterbox demandé. De la géométrie, que personne n'interprète.
- **Images — « Idle timeout reached » sur les qualités hautes de Pixia et Monalisia.** Le délai de l'appel à l'API Images était écrit en dur (`120`) dans `OpenAIHttpClient::generateImage()`. C'est un délai d'**inactivité**, et cet endpoint ne diffuse rien avant d'avoir fini : il vaut donc délai total. Le nombre datait d'une époque où une image sortait en 20 à 55 s ; la bascule sur `gpt-image-2` en `quality: high` (v2.80.0) a changé la charge sans que le plafond suive, et les générations mouraient à 120,03 s. Il devient `OpenAIConfig::IMAGE_CALL_TIMEOUT` (240 s), sous le plafond dur de nginx (`fastcgi_read_timeout 300` — au-delà on troquerait un 500 contre un 504).
- **Monalisia — retour sur `gpt-image-1.5`.** La bascule générale sur `gpt-image-2` lui avait imposé un `quality: high` explicite qu'elle n'envoyait pas auparavant : cette combinaison — la plus lourde de l'application, `high` en 1536×1024 — n'est plus jamais allée au bout. Elle repart sur la configuration exacte d'avant la bascule (`gpt-image-1.5`, sans paramètre de qualité), mesurée à 42,5 s et 40,6 s au journal. Le 1.5 n'expose pas de `quality` et rend toujours sa qualité maximale : son absence n'est pas une dégradation. Nouvelle constante `OpenAIConfig::MONALISIA_IMAGE_MODEL`, et la grille tarifaire lue suit le modèle — sans quoi l'infographie serait facturée au tarif de `gpt-image-2`.

### Ajouté
- **Images — durée réelle de chaque appel journalisée** (`OpenAI Image : appel terminé` — modèle, taille, qualité, durée, plafond). Tant que le plafond coupait les générations lentes, aucune n'allait au bout et leur durée restait inconnue : `IMAGE_CALL_TIMEOUT` ne pouvait être qu'un pari. Cette mesure est ce qui permettra de le régler sur des données.

## [2.80.0] — 2026-08-10

### Ajouté
- **Slidia — densité d'images au choix** : nouveau réglage « Images » à l'écran de paramètres (Aucune / Équilibré / Illustré, avec vignettes), porté par la présentation (colonne `image_density`). « Aucune » retire la forme illustrée du schéma strict du plan — l'absence d'image est garantie par construction ; « Illustré » demande une diapositive illustrée dès que le propos s'y prête. Les zones « Contenu » de PowerPoint (mixtes, y compris sans attribut de type) rendent désormais un masque apte à illustrer — plus besoin d'une zone image dédiée.
- **Slidia — brief modèle pré-affiché** : l'écran « votre brief » s'ouvre sur un brief complet et éditable (sujet, objectif, public, durée, points, ton) au lieu d'un champ vide ; détection de structure `---` visible en direct pendant la frappe (badge « N diapositives détectées »).
- **Slidia — enchaînement automatique après l'analyse d'un PDF** : dès que le document est analysé, on passe à l'écran de paramètres sans clic supplémentaire.
- **Slidia — génération d'images depuis l'éditeur alignée sur les vrais outils** : boutons Pixia/Monalisia grisés sous le seuil de tokens avec l'infobulle standard « Il vous faut N tokens » + « Passer en Pro » ; panneau image verrouillé (spinner) pendant une génération ; notification standard « Vos tokens sont épuisés… » au clic ; l'aperçu de cadrage prend les proportions réelles de la zone du masque.
- **Pixia / Monalisia — bascule sur `gpt-image-2`** : facturation des images au coût réel (250 000 tokens Bubul par dollar), qualité Full HD réservée aux offres payantes (`hasProfessionalAccess()`), présélection de qualité selon le plan.

### Modifié
- **Slidia — refonte complète de la génération.** Le pipeline est réécrit de bout en bout : structure imposée par le code (`PlanNarrator` — couverture, parties scandées, synthèse, intercalaire UNIFORME sur tout le deck), masques choisis côté serveur par la géométrie et le nom réels des masques (`LayoutClassifier` + `LayoutMatcher`, dimensions inférées du modèle, thèmes sombres et multi-masters résolus, garde de lisibilité du contraste partagée parse/export), contenus rédigés par patrons désignés (`PatronsDEcriture` — prose d'abord, la puce se mérite, titres spécifiques jamais dupliqués), audit mesuré (`PresentationAuditor`) suivi d'une relecture ciblée qui ne reprend que les diapositives défectueuses, et extraction typographique des PDF (`PdfStructureExtractor` — les vrais titres du document, plus de sommaire IA). **Trois prompts simples** (plan / contenu à trois régimes brief-document-fidèle / vérification) et **trois appels IA par génération** (un deck ≤ 20 diapositives se rédige en un seul appel) ; le mode fidèle n'appelle plus l'IA pour associer les masques (code pur). Modèle `gpt-5.6-luna` (3,4× plus rapide, 54 % moins cher que gpt-5-mini, mesuré).
- **Slidia — facturation au total physique** : chaque token consommé (entrée, cache, sortie, raisonnement) est débité 1:1, sans pondération ; seuils remis à l'échelle (création 5 000, amélioration 2 000) ; un échec ou une annulation n'est jamais débité, une reprise ne rejoue que les lots manquants.
- **Slidia — le masque « Titre de section » est réservé aux ruptures de section** : plus jamais employé en repli pour un chiffre-clé ou du contenu.
- **Slidia — cache d'analyse de document v4** : indexé par empreinte seule, il stocke l'extraction (texte + titres) et redécoupe à la volée — deux générations du même document à des tailles différentes partagent l'entrée.
- **Slidia — écran d'attente et erreurs au langage visuel de Moodia** ; messages d'échec utiles (timeout, saturation, réponse tronquée) routés sur des exceptions typées (`OpenAIRequestException`).

### Supprimé
- **Slidia — profil « Standard / Approfondi »** (écran, entité, colonne `generation_profile`) : il ne pilotait que l'effort de raisonnement du plan, invisible pour l'utilisateur et facturé plus cher.
- **Slidia — bouton « Mettre en forme mon texte »** et sa route `/api/slidia/format-brief`.
- **Slidia — sommaire IA des documents** (`DocumentSummarizer`) : l'extraction typographique fournit mieux, sans appel IA.

### Corrigé
- **Slidia — verrou de session libéré sur les requêtes longues** (générations d'images, notes, flux SSE) : la bibliothèque d'images et la navigation ne sont plus bloquées pendant une génération.
- **Slidia — couleurs de l'éditeur sur les masques sombres** : texte et indices de zones suivent la couleur réelle du masque (thèmes sombres, fonds image) au lieu d'un noir figé ; l'export corrige le texte illisible (noir sur noir) en relisant les verdicts du parse.
- **Slidia — les zones « Contenu » sans attribut de type acceptent enfin une image** (génération, éditeur, export) — la chaîne complète refusait silencieusement ces clés.

### Ajouté
- **Partage public par lien — Slidia, Scormia, Serenia** : chaque outil peut désormais rendre un contenu consultable **sans compte**, en **lecture seule**, via un lien révocable (jeton aléatoire de 32 caractères, `ShareLinkService`), activable depuis la **bibliothèque** de l'outil (Slidia, Scormia) ou depuis le **menu de la discussion** (Serenia). Le visiteur voit une **présentation** Slidia (`/p/slidia/{token}`), un **aperçu de module** Scormia (`/p/scormia/{token}`) ou une **discussion** Serenia (`/p/serenia/{token}`) — jamais l'éditeur, et jamais une action authentifiée sur ce chemin (aucun débit de tokens, aucun badge, aucune écriture en base). Pour Slidia et Scormia, le propriétaire peut en plus autoriser le **téléchargement** (PPTX, ZIP SCORM) — **désactivé par défaut**, ré-autorisé à chaque requête, et construit depuis la **même projection** que la page publique, jamais depuis les données brutes ; Serenia n'a pas de téléchargement. La publication passe par une **liste blanche** (jamais une liste noire) de ce qui sort vers un anonyme : ne sont **jamais publiés** les notes d'intervenant d'une présentation Slidia, la bibliothèque d'images extraites du PDF source d'un module Scormia, le contenu extrait des fichiers déposés dans une discussion Serenia, ni les identifiants internes (`uuid`, `version`…) et autres données du propriétaire. La **révocation** est immédiate et définitive : un nouveau partage régénère un **jeton différent**, l'ancien lien ne ressuscite jamais. Cas particulier Scormia, détaillé juste en dessous : perdre l'accès à l'outil **suspend** ses liens publics (404) sans détruire les jetons, qui refonctionnent seuls au réabonnement.
- **Scormia — les liens de partage public sont SUSPENDUS, jamais détruits, quand le propriétaire perd l'accès à l'outil** : un module partagé dont le propriétaire n'a plus accès à Scormia (fin de la BETA sans offre Pro/Expert/Entreprise, abonnement expiré, paiement échoué) cesse d'être consultable — `/p/scormia/{token}` et son téléchargement renvoient le **404 strictement uniforme** des autres échecs, sans rien qui distingue ce cas. Le jeton, lui, **reste en base** : dès que l'accès est retrouvé, **les liens déjà diffusés refonctionnent seuls**, sans que l'utilisateur ait quoi que ce soit à régénérer ni à rediffuser. C'est un choix délibéré contre la destruction : un lien partagé ne vit pas dans l'application mais dans un Moodle, un syllabus, des courriels vieux de six mois — le détruire obligerait l'utilisateur à retrouver chaque endroit où il l'a posé, et frapperait aussi fort une carte bancaire expirée qu'un départ délibéré. Côté sécurité la suspension ne concède rien : un jeton dormant n'est atteignable par personne tant que la lecture est refusée. La coupure et le rétablissement sont **silencieux et automatiques** (aucune notification, aucune action utilisateur, aucune tâche planifiée).
- **Emails — logo Bubul embarqué (cid)** : l'icône « swirl » Bubul (PNG généré depuis `favicon.svg`, fond transparent) est embarquée en cid via `email.image()` — nouveau namespace Twig `email_assets` → `public/images/email/`, global `email_logo_path`. Elle remplace le wordmark texte dans l'en-tête partagé (`emails/base.html.twig`) de tous les emails transactionnels, avec repli texte si rendu hors Mailer.
- **Admin — colonne « Consommé (30 j) » sur la liste des utilisateurs** : nouvelle colonne affichant le total de tokens consommés par utilisateur sur les 30 derniers jours (source `UserRecentActivity`, une seule requête `SUM` par page, sans N+1), avec en-tête **triable** (décroissant ⇄ croissant) préservant filtres et recherche courants ; « - » quand aucune consommation.
- **Landing Scormia — aperçus des features designés** : les 3 placeholders « Votre screenshot ici » de la section features sont remplacés par de vrais mockups maison (PDF → plan structuré + « Quiz final généré » ; flashcards + « Associer » + pastilles d'activités ; paquet `module-scorm.zip` SCORM 1.2 + carnet de notes), aux couleurs de l'outil, dans le style de la fenêtre du hero.
- **Contact — anti-bot renforcé** : en plus du honeypot et du rate limiter par IP existants, ajout d'un **contrôle de timing** (POST sans session ou soumis < 3 s après chargement → succès silencieux, aucun email envoyé) et d'un **rate limiter par email cible** (`contact_form_email`, 3/h) qui borne le mailbombing d'une même adresse même si le bot fait tourner ses IP.
- **Admin — tri par tokens sur la liste des utilisateurs** : en-tête « Tokens » cliquable (décroissant ⇄ croissant), filtres et recherche courants préservés ; les comptes sans tokens (B2B) sont placés en dernier.
- **Scormia — limite de 1000 caractères sur « Sujet du module »** : `maxlength` côté navigateur + compteur live + garde-fou serveur (400 au-delà) ; le PDF (document) n'est pas concerné.
- **Scormia — conso réelle inscrite sur la fiche du job à l'annulation** : `markCancelledFinished()` écrit désormais `tokensUsed` en plus de `finishedAt` — la fiche, le solde débité et l'historique d'activité (« Génération SCORM interrompue ») affichent le même nombre de tokens.
- **Scormia — verrouillage optimiste de l'éditeur (anti-écrasement multi-onglet)** : colonne `scormia_module.version` (migration `Version20260723130000`), incrémentée à chaque sauvegarde ; le save refuse (**409**) si la version envoyée par le client ne correspond plus (une autre sauvegarde — autre onglet/appareil — est passée entre-temps), avec une **notification** « Module modifié ailleurs — rechargez la page » côté éditeur. Miroir de `moodia_course.version`. Couvert par `ScormiaEditorSaveTest`.
- **Scormia — anti-duplication à la génération** : chaque section reçoit désormais le **plan complet** (titres + topics de toutes les sections) et la consigne de ne pas empiéter sur les autres → le contenu de la source est couvert par morceaux distincts, sans répétitions.
- **Scormia — navigation clavier dans l'éditeur** : les flèches ↑/↓ passent d'une section à l'autre (remontée en haut du canevas), neutralisées pendant la saisie ou l'ouverture d'un panneau.
- **Scormia / Moodia — blocage « Passer en Pro » à l'étape des 3 choix** : quand le solde de tokens est insuffisant, « Importer un document » et « Générer avec l'IA » sont verrouillées avec un badge coloré **« Passer en Pro »** (couronne, cliquable → upgrade) ; « Rédiger manuellement » reste libre. Le bouton « Créer un cours / module » est désormais toujours cliquable (le blocage est déplacé à l'étape suivante).

### Modifié
- **Email d'annonce Scormia — refonte complète du design** : le template devient **standalone** (n'étend plus `base.html.twig`) et reproduit le nouveau design d'annonce — bande « Offre de lancement », héro bleu clair avec logo + badge « BETA OUVERTE », titre/sous-titre, CTA, mockup produit de l'éditeur, grille de 6 cartes features, CTA final et pied de page. CTA branchés sur la landing Scormia (`ctaUrl`), lien préférences vers le dashboard, désabonnement `product_news` signé.
- **Scormia — génération de contenu orientée « principes » (+ retour sur `gpt-4.1`)** : le prompt de contenu passe d'une longue liste de règles rigides à des **principes de composition** — parties détaillées, **UNE activité interactive bien placée par section**, rythme fluide — que l'IA applique librement. Un **exemple few-shot** fixe le format de sortie (blocs correctement imbriqués sous `content`), ce qui **corrige à la source** les contenus vides / « à trous » (l'IA sortait parfois les champs à plat). Modèle repassé sur **`gpt-4.1`** (rédaction bien plus fiable que `gpt-4o-mini`, écarté). Nom du modèle toujours masqué côté client (« IA Standard »).
- **Scormia — coût en tokens de la génération fortement réduit (~207k → ~72k sur un support type, ~-65 %)**, sans perte de qualité : (1) chaque section ne reçoit plus que **sa tranche positionnelle** de la source (extrait glissant avec recouvrement) au lieu du document entier dupliqué × N sections ; (2) les sections sont désormais **rédigées par lots de 3 en un seul appel IA** (sortie `{"sections":[…]}`) — le prompt système et le plan complet ne sont envoyés qu'**une fois par lot** au lieu d'une fois par section.
- **Scormia — coût affiné + facturation cache** : la source d'un lot est **mutualisée** (un seul extrait couvrant la plage du lot, partagé par ses sections, au lieu de 3 extraits qui se recouvraient), le prompt système « contenu » est **allégé** (schémas de blocs conservés), et une clé `prompt_cache_key` route les appels vers le cache OpenAI (réduit la **facture** sur le prompt système répété, sans changer le nombre de tokens). `extractUsage()` expose désormais les `cached_tokens`.
- **Scormia — plan en arbre imbriqué (parties / sous-parties)** : le plan de génération produit un JSON **imbriqué** (`children`) au lieu d'une liste plate avec `depth` — une sous-partie est toujours rattachée à sa vraie partie (fini le « tout sous la dernière partie »). Aplatissement déterministe côté PHP.
- **Scormia — page « Comment voulez-vous commencer ? » alignée sur Moodia** : en-tête à icône produit + titre / sous-titre centrés, bouton retour en bas (au lieu de la barre haute + badge d'étape).
- **Scormia — indicateur de génération en cours** : le bandeau de progression devient une **carte au format module avec animation de chargement** (skeleton), en tête de bibliothèque.
- **Scormia — boutons « Créer un module » en atomic design** : convertis vers l'atom `_atoms/button` (dégradé aux couleurs Scormia), cohérents avec le bouton Moodia « Créer un cours ».
- **Inscription — plus de blocage des IP partagées** : les limiteurs par IP de l'inscription (`register_otp_send_ip`, `register_create_ip`, 3/h) sont retirés — ils bloquaient les utilisateurs légitimes derrière une même IP (école, entreprise, cohorte). Protections conservées : limite par email, vérification OTP, honeypot, timing anti-bot, CSRF.

### Corrigé
- **Scormia — assainissement du HTML riche des blocs par liste blanche (sécurité du partage public)** : le nettoyage des cinq champs de HTML riche (`richtext`, `callout`, `quote`, `accordion`, `timeline`) passait par des expressions régulières en liste noire, franchissables par ce que le parseur du navigateur interprète mais qu'un motif ne voit pas (`<img/onerror=…>`, `<img src="x"onerror=…>`, `href="javascript&#58;…"`). Ces contenus étant désormais servis à tout destinataire d'un lien de partage, `HtmlCleaner` est réécrit en **liste blanche appliquée sur l'arbre DOM** : seules les balises et les attributs connus survivent, les URL sont validées après décodage par le parseur, et l'attribut `style` est reconstruit à partir de quatre déclarations seulement (`color`, `background-color`, `font-size`, `text-align`). Le nettoyage s'appliquant à **l'émission** du JSON, tous les modules déjà en base sont rattrapés à la lecture, sans migration. Effets visibles : une mise en page collée depuis un traitement de texte s'aplatit (positionnement, `class`, images en `data:`). **Les paquets SCORM exportés avant cette version embarquent l'ancien player et un JSON non assaini : les réexporter pour que la correction s'applique aux modules déjà déposés dans un LMS.**
- **Admin — filtres perdus au changement de page (liste des utilisateurs)** : le lien de pagination ne conservait que `role`, `status` et `company` et **oubliait la recherche, le plan et le tri** → changer de page réinitialisait ces filtres. La pagination préserve désormais **tous** les filtres actifs + le tri courant.
- **Scormia — génération qui échouait ENTIÈREMENT sur un bloc `table` mal formé** : quand l'IA imbriquait un tableau (ou un objet) dans une cellule, `TableBlock::normalize()` exécutait `(string) Array` → un `Warning` promu en exception fatale à la phase « enregistrement du module » → tout le module échouait (« La génération a échoué. Réessayez »). La cellule est désormais **coercée en texte de façon défensive** (aplatissement d'un tableau, sinon chaîne vide) au lieu de faire planter le job.
- **Paiement — double-crédit de tokens au premier paiement d'abonnement** : à la souscription, `checkout.session.completed` **et** la première facture `invoice.payment_succeeded` (`billing_reason: subscription_create`) créditaient tous deux l'allocation mensuelle → p.ex. **Pro à 1 500 000 tokens au lieu de 750 000** (les plans à `token_storage_months ≥ 2`, Pro et Expert, n'étaient pas rattrapés par le plafond de stockage). Le crédit sur la **première** facture est désormais sauté — l'allocation initiale reste faite une seule fois par le checkout. Les **renouvellements** (`subscription_cycle`) et **changements de plan** (`subscription_update`) sont inchangés. Correctif minimal : une seule condition ajoutée dans `StripeWebhookService::handlePaymentSucceeded()`.
- **Landing Scormia — sections invisibles / page « cassée » au chargement** : `/outils/scormia` était la **seule** page landing sans le script `IntersectionObserver` qui pose `.visible` sur les `.reveal` (mis à `opacity:0` par la CSS partagée `landing.css`) → « Comment ça marche », features… restaient blanches. Script reveal ajouté (aligné sur moodia/slidia/pixia, avec repli si `IntersectionObserver` indisponible).
- **Dashboard — flèche de survol de la carte outil à la couleur de l'outil** : le chevron de la `tool-card` sortait toujours en bleu primaire car l'atom `icon` ignore le paramètre `style` ; la teinte hex (DB) passe désormais par `colorPrimary`/`colorSecondary`, comme l'icône principale de la carte.
- **Scormia — « 3 cours vides d'un coup » quand un lot de sections échoue en bloc** : les sections sont rédigées par lots de 3 en un seul appel ; ce lot pouvait mourir entièrement (JSON tronqué/illisible, plus fréquent avec `gpt-4o-mini` sur une sortie longue), et le filet de régénération était alors **sauté** (`hadSections` restait faux) → les 3 sections tombaient direct sur le repli. Désormais un lot échoué en bloc marque `batchFailed` et déclenche la **régénération individuelle de chacune de ses sections** (appel focalisé, sortie courte, bien moins sujette à la troncature) avant tout repli. Étend le filet des sections « squelette » au cas du lot entièrement raté ; une file/réponse `{}` sans clé `sections` ne régénère toujours pas inutilement.
- **Scormia — sections vides / « squelette » (réponse IA dégradée)** : une section sans contenu réel (blocs de type sans texte, ou corps entièrement vidé — le titre seul ne compte pas) est détectée et **régénérée individuellement** (appel focalisé sur 1 section, bien plus fiable), avec repli dédié si la régénération échoue aussi. Le batching amplifiant le risque (un appel raté cassait N sections), ce filet garantit qu'aucun écran vide n'atteint l'éditeur. Complète `dropEmptyRichtext()`.
- **Scormia — blocs de contenu vides (réponse IA dégradée)** : garde-fou `dropEmptyRichtext()` qui écarte les blocs `richtext` sans contenu (et logge la réponse brute), évitant un module rempli de « Écrivez votre texte… » vides.

### Supprimé
- **Scormia — moteur « IA Avancée » (gpt-5)** : le palier premium / gpt-5 (jamais réellement utilisé) est entièrement retiré ; Scormia utilise un **modèle unique** (`OpenAIConfig::SCORMIA_MODEL`, gpt-4.1). `ScormiaModelResolver` simplifié, appel **Chat Completions** uniquement.

### Ajouté
- **Scormia — accès réservé aux offres Pro / Expert / Entreprise (hors BETA)** : pendant la **BETA** (un mois après l'enregistrement de l'outil, même signal que le badge), Scormia est ouvert à **tout le monde** ; ensuite il est réservé aux abonnements **Pro, Expert et Entreprise** (admin et B2B avec contrat actif inclus, statut `canceled` toléré — le mois est déjà payé). Source unique `ScormiaAccessChecker` (+ `User::canAccessScormia()`), branchée dans le gating d'interface (`ToolAccessExtension`) et dans un **subscriber de routes** `app_scormia*` (redirection vers l'upgrade, 403 JSON pour l'AJAX). Aucun changement de schéma (vérification par slug de plan). Couvert par **22 tests** (matrice d'éligibilité unitaire + gate fonctionnel BETA/post-BETA/AJAX).
- **Scormia — badge « PRO »** : nouvel atom `pro-badge` (dégradé du bouton « Passer en Pro », couronne) affiché aux utilisateurs **sans accès Pro** sur la sidebar (cadenas remplacé, item cliquable vers l'abonnement) et la carte du dashboard (l'overlay verrouillé menait déjà à l'upgrade).
- **Scormia — 5 badges de gamification « Bâtisseur·se SCORM »** (1/5/20/50/150 modules créés) : nouvelle métrique `scormia_modules` dans `BadgeMetricsRefreshService`, définitions seedées par `app:init:badges`.
- **Scormia — commande de mise en production `app:scormia:setup`** : enregistre l'outil (la 1re création fixe `created_at` → **démarre la BETA d'un mois**), crée le dossier de médias (`--owner`, droits best-effort) et initialise les badges (`--skip-badges` disponible). Idempotente.
- **Offres — Scormia affiché là où l'on paie** : ligne « Scormia — modules SCORM » dans les cartes Pro/Expert/Entreprise de la page d'abonnement, et ligne Scormia (✓ selon le plan) dans le tableau « Ce que vous pouvez créer » de la landing.

### Modifié
- **Scormia — IA Rapide uniquement** : le sélecteur « Moteur d'IA » (IA Rapide / IA Avancée Premium) est retiré du wizard ; la génération utilise **toujours le modèle standard**, plafonné côté serveur (`resolveForUserChoice(user, null)`), badge de palier supprimé de l'écran de génération.
- **Upgrade — couronne sur les boutons « Passer en Pro »** : l'icône `+` du dashboard devient une couronne ; les boutons de la bannière d'upgrade et de la page abonnement en reçoivent une (uniformité).
- **Landing — « 9 outils » et Scormia partout** : compte d'outils, énumérations, metas SEO (description, og, twitter, keywords), footer et puces mis à jour ; la FAQ « Quels outils sont inclus » reflète désormais **exactement** le gating (Scormia = Pro/Expert/Entreprise, SAM = Entreprise).
- **Moodia — badge SCORM à la couleur Scormia** : `#1E40AF` / fond bleu (au lieu de l'indigo), aligné sur l'identité de l'outil.

### Corrigé
- **Scormia — médias protégés du `git clean`** : la règle `.gitignore` `/public/uploads/scormia/*` (+ `.gitkeep`) est restaurée — sa disparition avait déjà provoqué l'effacement des médias de développement.
- **Admin — page design-system « landing-hero-tool » en erreur 500** : un `{% set %}` littéral non échappé dans la documentation était parsé par Twig ; échappé → `lint:twig` 100 % vert (512 fichiers).

### Ajouté
- **Scormia — seuil de validation du quiz configurable** : bouton **⚙ Réglages** dans la topbar de l'éditeur (popover : champ 0–100 % + curseur) pour définir le score de réussite du module (`passingScore`, défaut 50 %). Écrit dans le manifeste SCORM comme `<adlcp:masteryscore>` **uniquement si le module contient un quiz**. Dans le player, le bouton **« Suivant » après le quiz final n'est débloqué que si le score atteint le seuil** ; sinon un bandeau affiche le pourcentage obtenu et invite à refaire une **nouvelle tentative** (le % de réussite reste toujours visible). Couvert par des tests unitaires (`ImsManifestGenerator` + export end-to-end avec quiz).
- **Scormia — recherche server-side du dashboard** : la recherche porte désormais sur **toute la bibliothèque** (plus seulement la page affichée) et **recalcule la pagination** selon le type ET la recherche combinés. Champ conservant le focus pendant la frappe (`data-turbo-permanent`), état « aucun résultat » rendu côté serveur, métacaractères LIKE échappés.
- **Scormia — badge « BETA » temporaire** : les molécules de badge de nouveauté acceptent un libellé configurable (`newLabel`) ; Scormia affiche **« BETA »** (sidebar, dashboard, page de l'outil, méga-menu de la landing) pendant **un mois** après sa mise en ligne, via `Tool::isNew()` (auto-expiration, piloté par la date en base — rien à recoder en prod).
- **Scormia — favicon sur l'aperçu** : la page d'aperçu du module (onglet dédié) affiche le favicon Bubul au lieu de l'icône générique du navigateur.
- **Landing Scormia — Slidia dans l'écosystème** : la section « écosystème Bubul » ajoute **Slidia** (5 outils, grille pleine) ; le méga-menu de la landing signale Scormia par un badge **BETA**.

### Modifié
- **Landing Scormia — accroche « éditeur SCORM »** : le hero passe de « Un PDF, un cours SCORM. » à **« Votre éditeur SCORM. Signé Scormia. »**, et les pills de réassurance sous le hero sont retirées.
- **Scormia — popover Réglages : rendu soigné** : champ nombre sans flèches natives et **slider custom** (piste fine, remplissage à la couleur d'accent du module, pouce net) aux couleurs du thème.

### Corrigé
- **Scormia — texte à trous : alignement vertical** : les « trous » (police plus petite + bordure) remontaient au-dessus de la ligne de texte ; ils sont désormais **centrés verticalement** (`vertical-align: middle`).
- **Scormia — stepper de quiz responsive** : avec beaucoup de questions, la pilule « 1 2 … N » débordait de la carte ; elle est désormais **bornée à la carte et défilante** (barre masquée), la pastille courante étant **auto-centrée**.

### Supprimé
- **Scormia — bouton « Recommencer le quiz » (en cours de session)** : retiré du récapitulatif — une nouvelle tentative se fait désormais **depuis le LMS** (relance = session SCORM neuve depuis 0), ce qui respecte le comptage et le scoring des tentatives (Moodle « Meilleure tentative »).

### Ajouté
- **Moodia — ajouter un module Scormia comme activité (bibliothèque ou .zip)** : nouveau type **« SCORM »** dans le modal « Ajouter une activité » de l'éditeur, avec **deux voies** — choisir directement un module dans **sa bibliothèque Scormia** (liste avec recherche, insertion immédiate sans fichier) ou glisser un **paquet `.zip`** exporté de Scormia (dropzone, max 150 Mo). Le paquet est **re-lié** au module Scormia d'origine s'il existe encore chez l'utilisateur ; sinon le module est **entièrement reconstruit** depuis le contenu du ZIP (écrans, blocs, médias recopiés, URLs réécrites) — un ZIP partagé par un collègue ou dont le module a été supprimé redevient donc **éditable dans Scormia**. Sécurité : protection anti-traversée (zip-slip) et validation MIME/taille des médias extraits. Erreurs gérées en français (ZIP illisible, paquet non-Scormia, contenu corrompu, fichier trop volumineux). Service `ScormiaPackageImporter` (réciproque de `ScormPackageExporter`), route `POST /moodia/scorm/import-package`, couvert par 10 tests unitaires dont un **round-trip export → import**.
- **Scormia — langue du module** : le wizard de génération (PDF / texte) propose un **sélecteur de langue** (Français par défaut, English, Español, Deutsch, Italiano, Português) — la valeur guide la rédaction de l'IA (comportement existant) et est désormais **stockée sur le module** (migration `Version20260717120000`, colonne nullable) puis **affichée sur sa carte** (rangée méta, seulement si renseignée). Préservée à l'export/import de paquet SCORM (sérialisation `moduleToJson`/`applyJsonToModule`) ; les modules existants et créés à la main n'affichent rien.
- **Moodia — activité SCORM visible et gérable dans l'éditeur** : l'activité SCORM (ajoutée depuis Scormia ou importée en ZIP) a désormais un **rendu dédié** — badge SCORM indigo, carte du module lié avec boutons **« Aperçu »** (player Scormia dans un nouvel onglet) et **« Ouvrir dans Scormia »**, titre/description éditables (utilisés à l'export Moodle), interrupteur « Suivi » et export d'activité seule comme les autres types. **État d'erreur explicite** si le module Scormia lié a été supprimé (carte rouge + consigne). Le JSON de l'éditeur est enrichi d'un bloc `scormModule` calculé à la lecture (`ScormiaModuleSummary`), jamais persisté.

### Modifié
- **Moodia — modal « Nouvelle activité » redessiné** : la grille de cartes verticales (textes centrés, hauteurs inégales) devient une grille de **rangées horizontales homogènes** (icône colorée + titre/sous-titre alignés à gauche, 2 colonnes, hover subtil) — plus lisible, zone de clic plus large, une colonne sur mobile.
- **Scormia — cartes de modules au design des cartes Moodia** : les cartes du dashboard Scormia adoptent le même langage visuel que les cartes de cours Moodia — **pill de type** en tête (« Module + Quiz » / « Module SCORM », teintée à l'accent du module), barre supérieure à l'accent au survol, titre sur 2 lignes, stats inline (« X parties · Y écrans »), rangée méta (« Z quiz · Réussite N % · langue »), **footer daté séparé par un filet**. L'avatar-lettre, la description et le lien « Éditer » disparaissent au profit de cette structure unifiée (la carte entière est cliquable) ; menu ⋯ et comportements (recherche, renommage, duplication) inchangés.
- **Scormia — filtres par type sur le dashboard (façon Moodia)** : tabs « Tous / Module + Quiz / Module SCORM » avec compteurs, **sur la même ligne que la recherche** (le compteur « X modules » redondant disparaît), entièrement aux **couleurs de Scormia** (dégradé et ombre du tab actif inclus). Filtrage **côté serveur** (critère : au moins un bloc quiz), compatible avec la pagination (le filtre est conservé de page en page) ; message dédié si une catégorie est vide.
- **SCORM = logo Scormia partout** : partout où une activité ou un type SCORM apparaît (badge et carte de l'éditeur Moodia, structure du cours, modal d'ajout, bibliothèque du panneau SCORM, icônes d'activités SAM), c'est désormais le **logo Scormia** qui est affiché (source unique `moodle_activity_types.js`, miroir Twig SAM synchronisé).
- **Pagination — ombres via classes utilitaires** : les ombres codées en dur (`shadow-[0_2px_12px_...]`) sont remplacées par les utilitaires (`shadow-sm` / `shadow-md` / `shadow-primary`), pour cohérence et lisibilité.
- **Landing (hero d'outil) — marge supérieure retirée** : suppression du `mt-5` de la section hero qui la décalait légèrement vers le bas.
- **Développement — app Stimulus exposée en console** : `window.Stimulus = app` dans `bootstrap.js` pour inspecter en dev les contrôleurs enregistrés/connectés.

### Corrigé
- **Scormia — « Ajouter à Moodia » aboutit à une activité utilisable** : l'ajout depuis le dashboard Scormia créait bien l'activité en base, mais elle apparaissait dans l'éditeur Moodia comme une **page vide sans type** (type `scorm` inconnu du front) — inutilisable et sans lien avec le module. Elle s'affiche désormais avec la carte SCORM complète, la référence au module est **préservée à la sauvegarde** de l'éditeur, et l'export `.mbz` du cours embarque le paquet SCORM.
- **Scormia — panneau Thème : le texte à trous de l'aperçu est montré corrigé** : dans la galerie statique du panneau, le bloc « Compléter la phrase » affichait des **champs vides** et un « Vérifier » inerte (l'interactivité du player n'y est pas branchée) — l'aperçu semblait cassé et ne montrait rien de la couleur. Il est désormais présenté **à l'état corrigé** (« Un module SCORM se dépose sur un LMS. », mots révélés dans leurs trous, pied retiré) — même logique que l'accordéon déplié d'office.
- **Scormia — panneau Thème : plus de « décalage » sur écran large + responsive mobile** : la feuille du panneau s'étirait sur toute la largeur de l'écran — sur un affichage large (ou dézoomé), l'aperçu, centré dans un volet droit immense, semblait décalé loin des préréglages avec un grand vide entre les deux. La feuille est désormais une **modale centrée de largeur bornée** (max 1400px) ; sur écran étroit (< 768px), les volets s'**empilent** (préréglages en haut, aperçu dessous) au lieu de se compresser côte à côte.
- **Scormia — éditeur : la barre d'outils d'un bloc ne déborde plus sur écran étroit** : la toolbar flottante du bloc sélectionné (type, variantes, média, flèches…) était en largeur libre et se faisait couper par le bord du canvas sur les blocs/écrans étroits (ex. « Image à zones cliquables »). Elle est désormais **bornée à la largeur du bloc** et **passe sur plusieurs lignes vers le haut** quand la place manque (ancrage par le bas) ; les libellés longs sont tronqués avec « … » au besoin.
- **Moodia — export `.mbz` : message clair si le module Scormia a été supprimé** : l'export échoue proprement avec « L'activité SCORM « {titre} » référence un module Scormia supprimé — retirez-la du cours ou réimportez le module (paquet .zip). » (affiché en flash), au lieu d'un message technique ambigu. Couvert par un test du transformer.
- **Moodia — rendu LaTeX à l'export (point C, retour F. Darrigrand)** : correctif du bug « intégrales `\int_a^b` mal rendues, bornes pas en indice/exposant ». Cause identifiée : du LaTeX **malformé** arrivait dans Moodle — soit l'underscore de borne manquant (`\inta^b` au lieu de `\int_a^b`, `\int` collé à sa borne → MathJax ne place plus les bornes), soit des **symboles Unicode** (`∫`, `√`, `≤`, `²`, `α`…) issus d'un copier-coller ou d'une extraction PDF (mode transcription). Nouveau service **`LatexNormalizer`** appliqué à **tous les chemins d'export** (contenu des pages, quiz Moodle XML, quiz `.mbz`) : **à l'intérieur des zones mathématiques uniquement** (`\( \)`, `\[ \]`, `$$…$$`), il recolle les bornes des grands opérateurs (`\inta^b` → `\int_a^b`, sans jamais casser `\intercal`/`\liminf`) et convertit l'Unicode en commandes LaTeX ; il normalise aussi `$$…$$` → `\[ \]`. La **prose hors formule n'est jamais touchée** (« 20 × 3 m² » reste tel quel). En prévention, les prompts de génération/transcription interdisent désormais explicitement les symboles Unicode. Couvert par des tests unitaires (`LatexNormalizerTest`).
- **Moodia — alignement vertical du bouton « Importer »** : le bouton était rendu en `<label>`, qui héritait d'une **marge basse de 6px** (règle globale sur les `label`) ; en flex `items-center`, cette marge décalait l'icône vers le haut. Il est désormais rendu en `<button>` (identique à « Exporter » et la corbeille, sans marge, sélecteur de PDF ouvert par JS) → alignement vertical parfait. L'icône reste une flèche pleine 20×20 (barre en bas + flèche vers le haut).
- **Moodia — export quiz XML : nom = vraie question** : dans le Moodle XML, le `<name>` de chaque question reprend désormais **l'intitulé réel de la question** (nettoyé du HTML, tronqué) au lieu de « {titre du quiz} — {n°} » (ex. « Quiz — Dolorem deleniti — 11 »). Repli sur l'ancien format uniquement si l'énoncé est vide. Rend la banque de questions Moodle lisible après import.
- **Moodia — alignement des boutons d'en-tête du quiz** : l'icône du bouton « Importer » était un tracé fin en viewBox 24×24 (`stroke`), alors que « Exporter » et la corbeille sont des icônes pleines en 20×20 (`fill`) → léger décalage de taille/poids. L'icône « Importer » est désormais une flèche **pleine 20×20** assortie, parfaitement alignée avec les autres.
- **UI — `cursor:pointer` rétabli sur les éléments cliquables** : Tailwind v4 a retiré le curseur « main » par défaut des `<button>` ; une règle globale le rétablit sur boutons, `[role="button"]`, `summary`, `select`, cases/radios et file inputs (hors désactivés). Les utilitaires `.cursor-*` gardent la priorité.
- **Castia — double-échappement des titres dans le menu d'action** : `podcast.title` était échappé via `|e('html_attr')` alors que la molécule `action-menu` échappe déjà les attributs → les titres à apostrophes/accents pouvaient s'afficher encodés. L'échappement explicite redondant est retiré.

### Ajouté
- **Moodia — quiz depuis un PDF : mode « Transcrire » (fidèle) vs « Générer »** (point B du retour F. Darrigrand) : le bouton « Importer » du quiz ouvre désormais un **menu à deux choix** — **Transcrire les questions du PDF** (recopie fidèlement les questions **déjà présentes** dans le document : intitulés, propositions et bonnes réponses, sans en inventer ni en omettre) ou **Générer un quiz depuis le contenu** (l'IA crée de nouvelles questions, comportement précédent). Répond directement à sa frustration (« il a créé un tout autre test »). Backend : nouveau **prompt de transcription** dédié (`buildTranscriptionSystemPrompt`/`buildTranscriptionUserPrompt`) qui transmet le **texte intégral** du document et interdit l'invention ; les blocs communs (types de questions, règles LaTeX, exemple JSON) sont factorisés et partagés avec la génération. Le mode transcrit **tous les types** (QCM, V/F, réponse courte, numérique, appariement, à trous, calculée), préserve le **LaTeX**, et détermine les bonnes réponses si le document ne les fournit pas. Paramètre `mode` (`transcribe`/`generate`) sur `POST /moodia/content/quiz/from-pdf`. *(La qualité de transcription dépend de la lisibilité du texte extrait du PDF.)*
- **Moodia — exporter n'importe quelle activité seule (backup Moodle `.mbz`)** : un **bouton d'export** (icône de téléchargement, infobulle « Exporter cette activité (.mbz) ») apparaît désormais dans l'en-tête de **toutes les activités** (page, audio, lien, étiquette, fichier, forum, devoir), à côté de la corbeille. Il produit un **backup Moodle `.mbz` minimal** (une section, une activité) que le formateur **restaure/fusionne dans un cours Moodle existant** — pour réutiliser une activité isolée sans exporter le cours entier. Réutilise l'intégralité du pipeline d'export existant (`MbzExporter` + nouvelle méthode `CourseTransformer::transformSingleActivity()`). L'éditeur **sauvegarde d'abord** (contenu + `uuid` à jour), puis télécharge le fichier. Nouvelle route `GET /moodia/course/{uuid}/activity/{activityUuid}/export-mbz`. Le **quiz** garde son propre export (Moodle XML de banque de questions). *(Le `.mbz` d'une activité audio nécessite que l'audio ait déjà été généré.)*
- **Moodia — exporter un quiz seul en Moodle XML (tous les types)** : un **bouton d'export** (icône de téléchargement, infobulle au survol) dans l'en-tête du quiz (placé **à droite de l'interrupteur « Suivi », à côté de la corbeille**) produit un fichier **Moodle XML de banque de questions**, importable dans **n'importe quel cours Moodle** (Banque de questions › Importer › « Format Moodle XML ») — sans passer par un cours `.mbz` entier. Répond au besoin de F. Darrigrand (« je veux juste mon test »). Le **LaTeX** des énoncés/réponses est préservé (CDATA, sans échappement qui l'abîmerait). **Tous les types de l'éditeur sont exportés, sans limitation** : QCM (mono/multi), Vrai/Faux, réponse courte, numérique, **appariement** (`matching`), **mots manquants** (`gapselect`), **glisser-déposer** (`ddwtos`) et **calculée** (`calculated`, avec jeux de valeurs par variable). Les types à trous réutilisent le parseur `|` (marqueurs `[[n]]` + options groupées). Service `MoodleQuizXmlExporter` couvert par des tests unitaires (13 tests). *(Les formats `gapselect`/`ddwtos`/`calculated` de l'import Moodle sont riches — à valider par un import de test sur l'instance.)*
- **Moodia — générer un quiz depuis un PDF (bouton « Importer »)** : un **bouton « Importer »** (icône upload) dans l'en-tête du quiz, à côté d'« Exporter », ouvre un sélecteur de PDF → l'IA génère les questions et **remplace** le contenu du quiz, prêtes à corriger. Remplace l'ancienne **zone de glisser-déposer** (retirée : plus discrète, plus claire, et corrige un libellé « undefined »). Backend inchangé : endpoint `POST /moodia/content/quiz/from-pdf` + service `QuizFromPdfService` (réutilise `generateSectionQuiz` en mode autonome, sans cours entier ; texte découpé en `AppLimits::QUIZ_CONTENT_CHUNK_SIZE` ; garde-fou tokens avant l'appel IA). Un indicateur de génération s'affiche dans le corps du quiz pendant le traitement.
- **Documentation — cadrage « créer une activité seule depuis un PDF »** (retour client F. Darrigrand) : note interne `docs/2026-06-30-creer-activite-seule-depuis-pdf.md` synthétisant le besoin (générer/transcrire un test depuis un PDF, l'exporter seul en Moodle XML), la solution UX (zone de dépôt PDF dans l'activité) et l'état technique. *Aucun impact applicatif.*
- **Moodia — éditeur : coller un code `<iframe>` l'intègre directement** : en plus des URL de vidéo (YouTube/Vimeo/Dailymotion), **coller le code d'intégration `<iframe>`** d'un outil (Genially, H5P, Canva…) insère désormais l'intégration en aperçu 16:9, au lieu d'afficher le code en texte brut. Seuls les `src` en **HTTPS** sont acceptés (anti-XSS). Exporté en `<iframe>` dans la page Moodle, comme les vidéos (mécanisme d'embed réutilisé de bout en bout).
- **Moodia — éditeur : achèvement « à l'ouverture » paramétrable par activité** : chaque activité de contenu (page, audio, lien, fichier, forum, devoir, quiz…) gagne un **interrupteur ON/OFF « Suivi »** directement dans son en-tête, pour activer/désactiver « **Terminé à l'ouverture** » d'un coup d'œil. À l'export `.mbz`, l'activité est alors marquée comme terminée dès que l'apprenant l'**ouvre** sur Moodle (`completion=2` + `completionview=1`) ; sinon aucun suivi (`0/0`). **Activé par défaut** (les cours existants adoptent ce comportement). Les réglages sont stockés dans une **table dédiée `moodia_activity_settings`** (relation OneToOne, supprimée/recréée avec l'activité, extensible pour de futurs paramètres) — migration `Version20260630000000`.
- **Moodia — quiz « à trous » : éditeur repensé (zone de texte dédiée + aperçu en direct)** : pour les questions *Mots manquants* / *Glisser-déposer*, l'éditeur n'utilise plus le petit slot d'intitulé (inadapté) : la phrase se saisit dans une **zone de texte dédiée**, confortable et claire, avec un **indice compact** d'une ligne (séparer les choix par `|`, le premier = bonne réponse). En dessous, un **aperçu en direct** montre la phrase telle que l'élève la verra — chaque trou devient une **pastille verte** affichant la bonne réponse (menu déroulant pour `gapselect`, étiquette à glisser pour `ddwtos`) avec **survol** listant tous les choix. L'aperçu se met à jour **pendant la frappe**.
- **Moodia — éditeur : replier la structure du cours** : une **flèche** dans l'en-tête « Structure du cours » permet de **replier** le panneau latéral (sur desktop) → le contenu reprend toute la largeur, plus confortable à l'affichage. Une flèche **dans le fil d'Ariane** permet de la **rouvrir** (et **ouvre le tiroir sur mobile**, en remplacement de l'ancien bouton « Structure » du header, retiré). En-tête de structure et fil d'Ariane à la **même hauteur**. L'état est **mémorisé** (localStorage) d'une session à l'autre, et le contenu s'élargit en douceur (animation).
- **Moodia — éditeur : intégration vidéo (embed)** : **coller une URL de vidéo** (YouTube, Vimeo, Dailymotion) dans une page **insère directement la vidéo** (lecteur intégré responsive 16:9), au lieu d'afficher l'URL. La vidéo est un bloc géré (aperçu dans l'éditeur, bouton supprimer au survol) et est exportée vers Moodle en `<iframe>` dans la page (`mod_page`). *(Selon les réglages de filtres/contenu de confiance de Moodle, l'`<iframe>` peut nécessiter d'autoriser l'intégration multimédia — à vérifier sur ton instance.)*
- **Moodia — génération IA : mots manquants, glisser-déposer et calculées** : la génération automatique de quiz peut désormais produire aussi les types `gapselect`, `ddwtos` et `calculated` (en plus des 5 précédents), en choisissant le plus adapté au contenu — texte à trous avec marqueurs `[[n]]` + mots groupés, ou énoncé/formule à variables `{a}`.
- **Moodia — quiz : questions « Calculée (maths) »** (`calculated`) : un énoncé et une **formule** de réponse avec des **variables** `{a}`, `{b}`… (ex. énoncé « Combien font {a} × {b} ? », formule « {a}*{b} »). Moodle génère plusieurs variantes en tirant des valeurs ; pour rester simple, chaque variable reçoit automatiquement **10 valeurs entre 1 et 10** générées à l'export, et la réponse correcte est la formule assortie d'une **tolérance**. (Structure XML Moodle `calculated` riche — à valider par un import de test.)
- **Moodia — quiz : types « à trous » (mots manquants + glisser-déposer)** : deux nouveaux types de questions. **Sélection de mots manquants** (`gapselect`) : un texte à trous où l'apprenant choisit le bon mot dans une liste par trou. **Glisser-déposer sur texte** (`ddwtos`) : même principe mais les mots se glissent dans les emplacements. **Saisie « inline » conviviale** : on écrit simplement la phrase dans l'intitulé et, pour chaque trou, on **sépare les choix par `|`** — le premier est la bonne réponse, les suivants des leurres. Ex. « Bonjour, je suis un **homme|femme|autre** et j'habite à **Paris|Lyon**. ». La conversion vers le format Moodle (marqueurs `[[n]]` + options groupées, un groupe par trou) est automatique à l'export. Exporté nativement vers les questions Moodle correspondantes.
- **Moodia — éditeur de quiz : dupliquer une question** : un bouton « Dupliquer » (icône) dans l'en-tête de chaque question crée une **copie complète** (type, réponses, feedback, explication…) insérée **juste après** — pratique pour enchaîner des questions similaires.
- **Bubul — Google Analytics 4 (`G-JFWBMFGCX4`)** : ajout de la balise `gtag.js` dans l'en-tête commun (présente sur toutes les pages). Elle réutilise le `gtag`/`dataLayer` existants, donc elle **respecte le Consent Mode v2** déjà en place (mesure d'audience refusée par défaut jusqu'au consentement), aux côtés de Google Tag Manager.
- **Moodia — génération IA de quiz : tous les types + QCM multi-réponses** : l'IA peut désormais produire les **5 types** de questions (choix multiple, vrai/faux, réponse courte, numérique, appariement) en choisissant le plus adapté au contenu, au lieu de se limiter au QCM et au Vrai/Faux. Les **QCM générés peuvent comporter plusieurs bonnes réponses**. La réponse courte inclut des variantes acceptées, le numérique une tolérance, l'appariement au moins 3 paires.
- **Moodia — quiz : type de question « Appariement » (`match`)** : nouveau type où l'apprenant relie des **paires** (élément de gauche ↔ bonne correspondance de droite), exporté nativement en question Moodle `match`. Dans l'éditeur, chaque paire est saisie sur une ligne (« Élément → Correspond à »), avec ajout/suppression et un minimum de 3 paires (exigence Moodle). Nouvelle colonne `moodia_quiz_answer.prompt_text` pour stocker le côté gauche. Nécessite la migration `Version20260628120000`.
- **Moodia — quiz : robustesse + cohérence (finitions QZ1/QZ2)** : chaque question est **validée via le registre de types** au moment de la persistance (et après génération IA) ; une question structurellement incohérente (ex. numérique sans valeur) **se replie automatiquement sur le QCM** plutôt que de produire un export cassé. Le chemin d'export **legacy** (anciens quiz stockés en activité JSON) est désormais **aligné sur le registre** : il lit le schéma enrichi (types variés, explication, feedback par réponse, tolérance) en plus de l'ancien format. Documentation développeur mise à jour (« ajouter un type de question = une seule classe taguée `moodia.question_type` »).
- **Moodia — quiz : tous les types de questions v1 (QZ1)** : en plus du QCM et du Vrai/Faux, l'éditeur de quiz gère désormais le **QCM multi-réponses** (case « Plusieurs bonnes réponses » : les coches passent en carrés, la notation Moodle répartit les points entre bonnes réponses et pénalise les mauvaises), la **Réponse courte** (`shortanswer` : liste de variantes acceptées, insensible à la casse) et le **Numérique** (`numerical` : valeur(s) acceptée(s) + **tolérance** ±). Chaque type s'exporte nativement vers la question Moodle correspondante. Le sélecteur de type par question couvre les quatre types ; explication et feedback par réponse restent disponibles partout.
- **Moodia — génération IA de quiz enrichie (QZ1 + QZ2)** : la génération automatique de quiz produit désormais le **schéma enrichi** : chaque question reçoit un **type** (`multichoice` ou `truefalse`, types variés au sein d'un même quiz), une **explication** pédagogique, et chaque réponse un **feedback** dédié (confirmation pour les bonnes réponses, explication de l'erreur pour les mauvaises). Les questions Vrai/Faux sont générées avec exactement 2 réponses « Vrai » / « Faux ». Tout est persisté tel quel dans les tables `moodia_quiz_question` / `moodia_quiz_answer` et exporté vers Moodle.
- **Moodia — quiz : type de question « Vrai / Faux » (QZ1)** : nouveau type `truefalse` exporté nativement en question Moodle `truefalse`. Dans l'éditeur de quiz, un **sélecteur de type** par question permet de basculer entre **Choix multiple (QCM)** et **Vrai / Faux** ; le passage en Vrai/Faux fige automatiquement deux réponses **Vrai** / **Faux** (texte non éditable, pas d'ajout/suppression), en conservant la bonne réponse choisie, tandis que l'explication et le feedback par réponse restent éditables. Ajouter un type côté back = une seule classe taguée `moodia.question_type`.
- **Moodia — quiz formatif : explication + feedback (QZ2)** : chaque **question** peut porter une **explication** (affichée après la réponse, exportée en `generalfeedback` Moodle) et chaque **réponse** son propre **feedback** (exporté en `feedback` Moodle). Éditables dans l'éditeur de quiz (champs dédiés). Le round-trip JSON éditeur↔serveur utilise un **schéma de réponses enrichi** (`{text, correct, feedback, tolerance}`) avec **lecture rétro-compatible** de l'ancien format (`answers: [string] + correct: index`) — aucun quiz existant cassé.
- **Moodia — quiz multi-types : socle technique (QZ1/QZ2, phase 1)** : le type de question descend au **niveau de la question** (un quiz peut mélanger les types) ; le champ `quiz_type` (au niveau quiz, devenu trompeur) est supprimé. Nouveaux champs en base : `type`, `single_answer`, `explanation`, `default_mark` (question) et `feedback`, `tolerance` (réponse) — supports de QZ1 (vrais types) et QZ2 (explication + feedback). Mise en place d'un **registre de types de questions** extensible (`QuestionTypeInterface` + `QuestionTypeRegistry`, auto-découverte par tag `moodia.question_type`, calqué sur `ActivityGeneratorRegistry`) : l'export Moodle délègue désormais au registre (le QCM `multichoice` y est déplacé **à iso-comportement**), et le feedback par réponse / l'explication sont câblés jusqu'à l'export. **Ajouter un type = une seule classe.** Nécessite la migration `Version20260627120000`.
- **Bubul — notification interne à chaque nouvel abonnement** : quand un utilisateur souscrit un abonnement premium (checkout Stripe réussi, hors upgrade), un email d'alerte est envoyé à **`contact@bubul.app`** (`CONTACT_EMAIL`) avec l'utilisateur, son email, l'offre, le cycle de facturation et la date. Envoi non bloquant (un échec d'email ne perturbe pas l'activation de l'abonnement).
- **Moodia — historique de versions par page + restauration (ED5)** : chaque page conserve un **historique de versions** (jusqu'à **15** par page). Des points de restauration sont créés automatiquement **avant chaque transformation IA** (assistant inline sur sélection) **et à chaque modification de la page — quel qu'en soit le type** (texte, image, bloc, tableau, glisser-déposer, couleur…), captée sur le **point de passage unique de sauvegarde** (`scheduleSave`), avec un checkpoint **throttlé à ~2 min/page** + un snapshot **forcé en quittant la page**. Filet de sécurité pour revenir en arrière, que la modification vienne de l'IA ou de l'utilisateur. Un bouton **« Historique »** dans l'en-tête de la page ouvre la liste (date + libellé) et permet de **restaurer** une version en un clic (la restauration est elle-même réversible : l'état précédent est snapshotté avant). L'historique est rattaché à un **identifiant de page stable** (uuid conservé d'une sauvegarde à l'autre), il **survit donc aux sauvegardes** même si l'enregistrement recrée les pages en base. Optimisé pour la place : snapshots **compressés (gzip / LONGBLOB)**, **dédupliqués par hash** (jamais deux fois le même état), rétention de 15/page, suppression en cascade avec le cours — soit quelques centaines de Ko max par cours.
- **Moodia — bouton « image » dans la marge pour insérer une image partout** : au survol d'une ligne dans l'éditeur, un bouton apparaît dans la marge gauche (au niveau de la ligne) et ouvre le sélecteur d'images (Images du cours · Pixia · Monalisia · Importer). L'image s'insère **juste après la ligne survolée**. Plus besoin de sélectionner du texte au préalable : on peut désormais ajouter une image sur une **page vide** ou n'importe quelle ligne.
- **Moodia — titres du panneau de gauche sur 2 lignes max** : les titres de sections et d'activités ne sont plus coupés dès la 1ʳᵉ ligne — ils **passent à la ligne** puis sont **bornés à 2 lignes** avec **« … »** s'ils restent trop longs (`line-clamp`). Les icônes (type, chevron, poignée) s'alignent sur la **première ligne** du titre.
- **Moodia — panneau « Structure du cours » plus large et plus aéré** : la barre latérale gauche de l'éditeur passe de **320 à 380 px** (titres d'activités moins tronqués) et les items (sections + activités) gagnent en **espacement vertical et taille** (padding et hauteur de ligne accrus, texte 15 px). Le contenu principal et la barre de pagination suivent automatiquement la nouvelle largeur.
- **Moodia — déplacer une image par glisser-déposer** : une image sélectionnée affiche une **poignée de déplacement** (en haut à gauche) ; on la glisse pour **réordonner** l'image parmi les blocs de la page (un **trait droit coloré** indique l'emplacement de dépôt). À la dépose, la page est ré-ordonnée et sauvegardée.
- **Moodia — image collée gérée comme une vraie image** : coller une image (Cmd/Ctrl+V) dans l'éditeur ne laisse plus un `<img>` base64 brut, lourd et non éditable. L'image est **uploadée** automatiquement puis insérée sous forme de **figure gérée** (sélectionnable, redimensionnable, alignable, légende, suppression — comme les images ajoutées via le bouton). Les images nues déjà présentes (collées avant) sont **adoptées** en images gérées à la prochaine édition/rendu.
- **Moodia — vrais logos + couleurs des outils dans le menu de génération** : les options « Générer avec Pixia / Monalisia » affichent désormais le **logo officiel de chaque outil** dans **sa couleur de marque** (duotone), sur son **fond de couleur** — lus depuis la table `tool` via `tool_details` / `tool_color_details`, au lieu d'une icône image générique grise.
- **Moodia — prompt de génération d'image = texte surligné brut (plus d'enrobage)** : générer une image depuis le texte sélectionné envoie désormais **le texte surligné tel quel** comme description à Pixia/Monalisia (exactement comme une création directe dans l'outil), au lieu de l'envelopper dans une consigne (« Illustration pédagogique claire… »). Le **titre de l'image** reste un **résumé court** du passage. Description et titre cohérents avec l'usage direct de l'outil.
- **Moodia — génération d'image : blocage + info-bulle d'upgrade selon le solde** : la génération depuis l'éditeur coûte **exactement le même nombre de tokens** que via Pixia/Monalisia (mêmes endpoints). Si le solde est insuffisant, l'option **« Générer avec Pixia/Monalisia »** est **verrouillée** (grisée + cadenas) et un **survol** affiche l'info-bulle d'upgrade habituelle (« Il vous faut X tokens » + bouton **Passer en Pro/Expert**), comme sur les autres outils. Un clic sur une option verrouillée affiche le même message. Le solde restant est mis à jour après chaque génération.
- **Moodia — générer une image depuis le texte sélectionné (Pixia / Monalisia)** : en sélectionnant du texte, la barre flottante propose un menu **« Image »** → **Générer avec Pixia** (≈ 27 000 tokens, meilleur modèle `gpt-image-1.5`) ou **Monalisia** (≈ 25 000, infographie). Le passage sélectionné sert de base à un **prompt « illustration »** : l'IA produit une **illustration du sujet abordé** (et non un rendu littéral du texte ; pour Pixia, aucun texte/lettre dans l'image). La **légende** est un **titre court** dérivé de la sélection (≈ 60 car. max). Pendant la génération (synchrone, ~quelques secondes) un **emplacement de chargement animé** s'affiche sous le bloc, puis l'image générée le remplace et est sélectionnée. **Si l'utilisateur change de page pendant la génération**, l'image est insérée dans la **page d'origine** (modèle), donc rien n'est perdu et les tokens ne sont pas gaspillés. Gère les **tokens insuffisants** (message dédié) et les erreurs. Le menu « Image » regroupe aussi « Insérer une image… » (sélecteur existant). Côté backend, la génération Monalisia renvoie désormais `imageUrl` (comme Pixia) pour réutilisation directe.
- **Moodia — modale « Bibliothèque d'images » à hauteur fixe** : le corps de la modale de sélection d'image a désormais une **hauteur constante** (≈440 px, plafonnée à 60 % de l'écran) quel que soit l'onglet ou le nombre d'images — fini la modale qui rétrécit sur un état vide et s'agrandit sur une grille pleine. Les états « Aucune image » et la zone d'import sont **centrés verticalement** dans cette hauteur.
- **Moodia — gestion d'image repensée (barre flottante + resize amélioré)** : un **clic sur une image** la sélectionne (anneau) et affiche une **barre flottante au-dessus** : alignement (gauche / centre / droite), **largeurs préréglées 50 % · 75 % · 100 %**, **Remplacer l'image** (rouvre le sélecteur et échange l'image en place) et **Supprimer**. Le redimensionnement passe par **une seule poignée** (coin bas-droit) avec une **pastille de pourcentage en direct** et le **ratio conservé** ; la barre se masque pendant le glissement puis se repositionne. Les anciens contrôles en survol (boutons d'alignement, croix, 4 coins) sont retirés au profit de cette barre, plus lisible et mieux placée. Le rendu et l'insertion d'une figure partagent désormais **un markup unique** (`_figureMarkup`).
- **Moodia — couleur du texte dans la barre flottante** : un bouton **« A »** ouvre un sélecteur de couleurs — **couleurs du thème** (bleu, vert, orange, rouge, cyan, violet, rose + « par défaut ») et une **palette** — appliquées au texte sélectionné (`foreColor` en style inline) — plus une **couleur personnalisée** (sélecteur natif + champ hexadécimal validé). Le trait sous le « A » reflète la dernière couleur utilisée.
- **Moodia — assistant IA inline sur sélection** : en sélectionnant du texte dans une page de l'éditeur, une **barre flottante** apparaît au-dessus de la sélection avec 6 actions (Reformuler · Raccourcir · Développer · Simplifier · Corriger · Traduire). L'IA ne transforme **que la sélection** (endpoint `POST /moodia/content/text/improve`, `MoodiaTextRewriterService::improveSelection()`, **sans ajouter de structure de bloc**), qui est remplacée sur place ; coût **÷5** et **traçage** dans l'historique d'activité. La sélection est préservée pendant l'action et la sauvegarde réutilise le flux d'édition standard (sérialisation des blocs + auto-save).
- **Voix TTS multilingues — fondation** : chaque voix d'`ElevenLabsConfig` porte désormais une `language`, et 10 voix sont ajoutées aux 14 voix 🇫🇷 existantes (🇬🇧 anglais, 🇪🇸 espagnol, 🇩🇪 allemand, 🇮🇹 italien, 🇵🇹 portugais — 2 voix H/F par langue). Nouvelle table `LANGUAGES` (libellé + drapeau) et helpers `getVoicesByLanguage()` / `getDefaultVoiceForLanguage()` (priorité à la voix par défaut globale si elle est de la langue, sinon une voix recommandée, sinon la première de la langue, sinon repli global). Prépare l'affichage des drapeaux par voix dans Castia et le choix de la voix par défaut selon la langue du cours dans Moodia. Couvert par des tests unitaires.
- **Castia — drapeau du pays par voix** : dans le picker de voix, l'initiale affichée dans la pastille est remplacée par le drapeau de la langue de la voix (🇫🇷 🇬🇧 🇪🇸 🇩🇪 🇮🇹 🇵🇹), pour repérer la langue d'un coup d'œil. La table `LANGUAGES` est exposée au template via `CastiaController`.
- **Moodia — voix de narration par défaut selon la langue du cours** : à la génération audio (éditeur) comme à l'export `.mbz`, en l'absence de voix explicite, Moodia sélectionne désormais la voix par défaut de la langue du cours (`MoodiaCourse.language`) via `getDefaultVoiceForLanguage()`, au lieu de toujours utiliser la voix française `sophie`. Côté export, la langue est dérivée de `activité → section → cours`, sans changer les appelants.
- **Castia — drapeau aussi sur la voix sélectionnée** : le récapitulatif de la voix choisie (carte « hero ») affiche le drapeau de la langue à l'ouverture et se met à jour à chaque sélection (au lieu de l'initiale), pour rester cohérent avec la liste.
- **Voix multilingues — descriptions précisées** : les 10 voix non françaises ont une description nommant la langue et le caractère (ex. « Voix anglaise, claire et posée ») pour mieux savoir à quoi s'attendre.
- **Moodia — rejet immédiat d'un PDF trop long à l'import** : si le texte extractible dépasse la limite (50 000 caractères), l'upload est refusé tout de suite avec un message clair (« document trop long, scindez-le »), au lieu d'échouer après une longue attente pendant la génération. Un PDF non extractible (scanné) n'est pas bloqué à cette étape.
- **Moodia — accessibilité de l'écran de génération** : l'anneau de progression expose `role="progressbar"` + `aria-valuenow` (mis à jour en continu), le statut est annoncé aux lecteurs d'écran (`aria-live="polite"`), et les animations décoratives respectent `prefers-reduced-motion`.
- **Moodia — protection anti-écrasement de l'éditeur (verrou optimiste)** : `MoodiaCourse` porte désormais un champ `version`. À la sauvegarde, si une autre édition (autre onglet ou appareil) est passée entre-temps, le serveur renvoie un **409** et l'éditeur affiche un bandeau « ce cours a été modifié ailleurs, rechargez » au lieu d'écraser silencieusement les modifications. La version locale est mise à jour après chaque sauvegarde réussie. **Nécessite la migration `Version20260618000001`.**
- **Moodia — import d'image locale dans l'éditeur** : nouvel onglet « Importer » dans le sélecteur d'images (à côté de Images du cours / Pixia / Monalisia) pour téléverser une image depuis l'ordinateur (JPG/PNG/GIF/WebP, 5 Mo max) via l'endpoint `app_moodia_upload_image` existant, puis l'insérer directement dans la page.
- **Moodia — légende d'image visible et éditable** : chaque image insérée affiche désormais une légende (`<figcaption>`) sous l'image, **pré-remplie depuis sa description** et **modifiable** directement dans l'éditeur. La légende est conservée dans le cours et reprise à l'export Moodle (l'`alt` d'accessibilité reste la description d'origine).
- **Moodia — duplication de cours (socle)** : `MoodiaPersistenceService::duplicateCourse()` + route `POST /moodia/course/{uuid}/duplicate` clonent un cours **en profondeur** (sections, activités, quiz, glossaire, bibliothèque d'images) et **copient les fichiers physiques** (images, audios, PDF source) vers le nouveau cours. La copie repart en statut éditable — jamais déployée (`deployedAt`/`moodleCourseId`/`version` réinitialisés). Le bouton « Dupliquer » dans l'UI suit.
- **Moodia — bouton « Dupliquer » au tableau de bord** : action « Dupliquer » dans le menu de chaque cours → crée la copie et ouvre directement son éditeur. Un cours **déployé** invite désormais à le **dupliquer pour le rééditer** au lieu d'aboutir à une impasse (« ne peut plus être modifié »).

- **Moodia — éditeur de quiz : harmonisation (QCM, dropdown, numérique)** : le feedback d'une réponse est de nouveau **caché par défaut** et **s'ouvre en douceur au survol** de la réponse (la carte reste compacte). Dans le **menu de type**, le bouton radio est retiré (le type courant reste surligné en couleur de marque) et le libellé « Choix multiple (QCM) » devient simplement « Choix multiple ». La pastille **tolérance ±** (numérique / calculée) a une **info-bulle explicite** (« marge ± autour de la valeur ») et un léger padding pour que la sélection du texte ne déborde plus de la pastille.
- **Moodia — éditeur de quiz : refonte visuelle (contraste & lisibilité)** : chaque question est une carte sur **fond gris** et chaque réponse une **carte blanche** avec une ombre douce, de sorte que les réponses **ressortent par contraste** de la question (au lieu de boîtes grises imbriquées peu lisibles). Chaque réponse tient sur **une ligne épurée** (bouton coche/case + texte + supprimer) ; la **bonne réponse** est signalée par une **coche verte**. Le **feedback** par réponse reste **discret** : il se révèle **en douceur** (fondu + glissement) au **survol de la réponse concernée** (ou pendant son édition) et — surtout — **reste affiché dès qu'il contient du texte** (le feedback saisi ne disparaît plus quand on quitte la question). L'**explication** de la question est **toujours visible sous l'intitulé**, le libellé « Réponses acceptées » est retiré, et le type **Numérique** n'accepte plus que des **valeurs numériques** (valeur + tolérance ±, regroupée dans une pastille claire). Toutes les **cartes-réponses ont la même hauteur** quel que soit le type, avec un **padding généreux**. Les émojis (💡, 💬) sont remplacés par de **vraies icônes du design system**. Le **sélecteur de type** est désormais un **menu déroulant personnalisé** (à la place du `<select>` natif) : chaque type s'affiche avec son **icône** et un **bouton radio**, le type courant étant mis en avant (couleur de marque) — fermeture par clic à l'extérieur ou Échap. Le **feedback** d'une réponse réserve son espace : il **apparaît en fondu sans décaler** la réponse ni les éléments en dessous. Les **info-bulles** des boutons (dupliquer / supprimer) utilisent le style maison. Unifié sur **tous les types** (QCM, Vrai/Faux, réponse courte, numérique, appariement).
- **Moodia — quiz : le QCM est toujours en multi-réponses (anti-triche)** : suppression de l'option « Plusieurs bonnes réponses » — un QCM affiche **toujours des cases à cocher** (jamais de boutons ronds), pour qu'un élève ne puisse pas deviner le nombre de bonnes réponses à la forme du sélecteur. Le QCM accepte donc **une ou plusieurs** bonnes réponses, avec la **notation Moodle multi** (points répartis entre les bonnes réponses, malus sur les mauvaises). Verrouillé de bout en bout (éditeur, persistance, export — y compris pour d'anciens quiz). Le Vrai/Faux reste en sélection unique.
- **Moodia — éditeur de quiz : QCM limité à 6 réponses** : un QCM ne peut plus dépasser **6 réponses** (le bouton « Ajouter une réponse » disparaît au-delà, remplacé par un discret « Maximum 6 réponses » ; garde-fou aussi à la conversion de type).
- **Moodia — éditeur de quiz : bas de question affiné** : le bouton **« Ajouter une réponse »** est plus léger et compact (bordure fine, moins haut, survol couleur d'accent). L'option **« Plusieurs bonnes réponses »** devient une **petite puce cliquable** (fond au survol). Le champ **explication** affiche une **icône 💡 fixe** à gauche (au lieu de l'émoji glissé dans le texte d'invite), pour un rendu plus net.
- **Moodia — éditeur de quiz : réponses plus lisibles** : la carte d'une **bonne réponse** est désormais **teintée en vert** (bordure + fond légers) → on repère les réponses correctes d'un coup d'œil. La coche est toujours présente (transparente tant que non cochée) pour éviter tout saut de mise en page, avec un survol plus net. Le **champ feedback**, qui ressemblait à du texte gris flottant, devient une **vraie pastille discrète** (fond gris léger qui s'éclaircit au survol et passe en blanc bordé au focus). Cohérence des champs (réponse courte, numérique, appariement) sur la couleur d'accent au focus.
- **Moodia — éditeur de quiz : design clarifié et plus simple** : l'intitulé de la question ne se découpe plus **un mot par ligne** (le sélecteur de type étirait toute la largeur) — le texte reprend toute la place sur sa ligne, et le **sélecteur de type** devient un contrôle **compact à largeur fixe** en haut à droite. Le bas de la question (option « Plusieurs bonnes réponses » + champ explication) est **aligné sur les réponses** (même retrait), l'explication passe sur **fond blanc** (mieux visible) avec une puce 💡, et le libellé de l'appariement devient explicite (« Reliez chaque élément à sa correspondance »).

### Modifié
- **Moodia — bouton « Structure » à la même taille que « Export MBZ »** : en vue responsive, le bouton « Structure » (toggle du sommaire) était en taille `sm` alors que « Export MBZ » est en `md` → ils n'avaient pas la même hauteur. « Structure » passe en `md` pour être aligné (même hauteur et même padding).
- **Moodia — historique : icône colorée selon l'action IA utilisée** : une version créée par l'assistant inline affiche désormais **l'icône et la couleur de l'action choisie** (Reformuler = bleu, Approfondir = bleu, Raccourcir = ambre, Plus d'exemples = vert, Ton plus léger = rose) — les mêmes que le menu « Retravailler la sélection » — au lieu d'une icône IA générique. L'action est mémorisée dans le snapshot ; repli sur l'icône générique pour les versions antérieures.
- **Moodia — historique de page : panneau ancré à droite + aperçu avant validation** : l'historique n'est plus une modale qui recouvre l'écran mais un **panneau ancré à droite** qui **pousse le contenu** (comme le sommaire à gauche) au lieu de le masquer — **plein écran sur mobile**, ~432 px et contenu redimensionné sur desktop (la barre de pagination suit). Surtout, le fonctionnement devient un **aperçu avant validation** : on **clique une version pour la prévisualiser directement dans l'éditeur** (rien n'est encore enregistré), puis on **« Valide »** pour l'appliquer ou on **« Annule »** pour revenir à l'état courant. Une ligne **« Version actuelle »** (sélectionnée par défaut, cochée) sert de point de repère. Versions **regroupées par jour** avec **icône colorée par type**, heure, badge **« Récent »** et **compteur** dans l'en-tête. L'application reste **réversible** (l'état d'avant est snapshotté). Accessibilité : **Échap** pour annuler, `role="dialog"`, focus à l'ouverture, écouteurs et docking nettoyés (fermeture, navigation entre pages, démontage). **Animation fluide** : seul le panneau glisse (transform GPU, courbe « drawer » `cubic-bezier(0.32,0.72,0,1)`, ~0.42 s) ; la place du contenu est **réservée instantanément** (animer `margin` reflowait le contenu riche à chaque frame → saccades). Apparition en **cascade** des lignes (respecte `prefers-reduced-motion`). Le panneau **s'ouvre immédiatement** au clic (animation visible tout de suite) avec un **indicateur de chargement** le temps de récupérer les versions (au lieu d'attendre la réponse avant d'apparaître), et l'**en-tête est épuré** (plus de description de page sous le titre). Son style et sa **largeur (380 px)** **s'alignent sur le sommaire gauche** (« STRUCTURE DU COURS ») : même en-tête compact (mêmes classes : libellé en petites majuscules grises + petite icône primary, bouton fermer discret), pour une symétrie gauche/droite. Chaque version est intitulée **« Version 1, Version 2, … »** (la plus ancienne = Version 1, la plus récente = Version N) plutôt que « Modification » ; le type reste indiqué par l'icône. L'historique n'affiche **que les modifications de contenu** (les points internes « Avant restauration » ne sont plus créés ni listés) ; valider une version conserve quand même l'état précédent comme simple modification (réversible). Le **bouton historique** porte un **tooltip stylé** au survol et **referme le panneau au 2ᵉ clic** (toggle).
- **Moodia — sauvegarde quand on a fini d'écrire (plus de save en continu)** : l'éditeur n'envoyait `/moodia/course/{uuid}/save` qu'**200 ms** après la moindre frappe → une sauvegarde quasi permanente, alors que chaque save est **lourde côté serveur** (recréation des activités). Le délai passe à **2 s d'inactivité** (vraie pause de frappe) : pendant qu'on écrit, **rien n'est envoyé**. Pour ne **rien perdre** malgré ce délai plus long, une sauvegarde immédiate (`flushSave`) est déclenchée aux moments clés : **changement de page**, **onglet masqué**, **fermeture/rechargement** de la page et **démontage** de l'éditeur. Beaucoup moins d'appels, et aucun contenu perdu.
- **Moodia — vue section alignée sur la vue page (2 conteneurs + description encadrée)** : la vue d'une section est désormais scindée en **deux cartes** comme une page — une carte d'en-tête (badge + titre + **description**) puis une carte distincte pour les **activités**. La **description de la section** utilise **exactement le même champ que la description d'une page/activité** : c'est désormais un `<textarea>` (au lieu d'un `<div contenteditable>`) avec les **mêmes classes** (rendu, line-height et police strictement identiques). Les anciennes descriptions HTML sont converties en texte brut à l'affichage.
- **Moodia — alignement d'image (float) + blocs pleine largeur qui dégagent le float** : aligner une image à gauche/droite la met en `float` (seul moyen en CSS pour que le texte s'écoule **à côté puis continue en dessous** de l'image, façon magazine). Pour éviter que ça casse les mises en page, les blocs « pleine largeur » — **citations**, tableaux, séparateurs, encadrés et **formules mathématiques en display** — **dégagent le float** (`clear`) et démarrent proprement sous l'image. Une formule trop large **défile horizontalement** au lieu de déborder de la carte. Centre = bloc centré.
- **Moodia — barre flottante réorganisée en groupes logiques** : ordre clarifié — *style* (Titre) · *texte* (Gras / Italique / Souligné / Barré + couleur) · *listes* · *insérer* (lien, image) · *IA* — séparés par des filets. Le bouton IA (baguette magique) est coloré en primary pour se distinguer.
- **Moodia — icône IA unifiée** : le bouton IA de la barre flottante et l'animation de chargement utilisent désormais l'icône `magic-stick` (baguette magique) du design system, cohérente avec le reste de l'app.
- **Moodia — éditeur de page en deux cartes** : l'en-tête (badge de type, titre, description, durée) et le **corps du cours** (contenu éditable) sont désormais dans **deux conteneurs distincts** pour une séparation visuelle nette.
- **Moodia — édition de lien sans modal** : le bouton Lien ouvre un **petit popover sous la barre** (champs **URL** + **texte affiché**) au lieu d'une boîte `prompt()`. Sur un lien existant, les champs sont **pré-remplis** pour modifier l'URL ou le texte ; un bouton **Retirer** supprime le lien. L'URL est normalisée (`https://` ajouté au besoin). Sans notification superflue.
- **Moodia — menu « Titre » (style de texte) dans la barre flottante** : le bouton H2 devient un **menu déroulant** — **Paragraphe + Titre 1 à 6** (tous les niveaux H1–H6) — chaque option affichée **dans son style réel** (exemple). Le libellé du bouton reflète le **niveau courant** et l'option correspondante est surlignée. Les **tailles de titres** sont rétablies dans l'éditeur (`.moodia-rich-text h1…h6`), le reset Tailwind les ayant supprimées (tous les titres rendaient à la même taille).
- **Moodia — état actif des boutons de formatage** : dans la barre flottante, les boutons (Gras, Italique, Souligné, Barré, Exposant, Titre, listes) s'affichent **en surbrillance** (bleu) quand le formatage est déjà présent sur la sélection, pour indiquer ce qui est appliqué (`document.queryCommandState`).
- **Moodia — éditeur épuré : toolbar de formatage remplacée par une barre flottante sur sélection** : la barre fixe de ~13 boutons est **supprimée**. Le formatage essentiel (**Gras · Italique · Titre · Liste · Lien**) rejoint la **barre flottante** qui apparaît à la sélection, juste avant les actions IA, au même format. Souligné, barré, H3, alignements, citation et « supprimer le format » sont retirés pour simplifier. Le formatage (Titre, Gras, Italique, Souligné, Barré, Lien, listes à puces et numérotées), l'**insertion d'image** et un menu **« Retravailler la sélection »** (Reformuler / Approfondir / Raccourcir / Plus d'exemples / Ton plus léger) qui transforme par l'IA **uniquement le texte sélectionné** (remplacement sur place, et non plus toute la page) sont regroupés dans **une seule barre flottante** qui apparaît à la sélection — avec **entrée animée** de la barre et du menu, et **animation de chargement « IA au travail »** (étoile pulsante + dégradé). La ligne d'actions en haut de page est entièrement supprimée : tout est au même endroit.
- **Moodia — coût de régénération divisé par 5** : la transformation d'une page (les 5 actions : reformuler / approfondir / raccourcir / exemples / ton) est désormais facturée **5× moins** que les tokens réellement consommés par l'IA (constante `AppLimits::MOODIA_REGEN_COST_DIVISOR`), pour encourager l'itération sur le contenu. Le montant débité du solde et affiché dans l'historique d'activité reflète ce coût réduit.
- **Moodia — barre d'aperçu de régénération soignée** : le bouton « Garder ce contenu » adopte le dégradé primary de la marque + icône check, une ombre `shadow-primary` et un léger relèvement au survol (au lieu d'un aplat bleu) ; « Annuler » et l'encadré de la barre sont affinés (coins plus arrondis, bordure plus douce).
- **Moodia — menu « Régénérer » redessiné** : chaque option du menu de régénération affiche désormais une pastille d'icône colorée + libellé (style harmonisé avec les filtres de l'administration), avec curseur « pointer » au survol, au lieu d'une simple liste de texte.
- **Moodia — auto-sauvegarde plus réactive** : le délai d'auto-sauvegarde de l'éditeur passe de 1,5 s à 0,8 s après la dernière modification (debounce conservé pour ne pas sauvegarder à chaque frappe).
- **Moodia — bouton « Ajouter une activité » harmonisé** : il adopte le même style « encadré pointillé » que le bouton « Nouvelle section » (pastille d'icône, gris → primary au survol), en version plus compacte, pour une cohérence visuelle dans l'éditeur.
- **Monalisia & Pixia — limite de prompt portée à 2000 caractères** (au lieu de 1000) : la zone de description (saisie manuelle) accepte désormais le double. Les limites sont centralisées dans `AppLimits` (`MONALISIA_PROMPT_MAX_LENGTH` mise à 2000, nouvelle `PIXIA_PROMPT_MAX_LENGTH`) puis référencées dans les templates et le compteur JS (plus de valeur `1000` en dur). *(La synthèse PDF auto-remplissant le champ reste bornée par son budget de génération `MONALISIA_EXTRACT_FOR_PROMPT_MAX_TOKENS` — inchangé.)*
- **Moodia — éditeur de questions « à trous » : carte simplifiée** : la zone de saisie et l'aperçu « côté apprenant » sont désormais réunis dans **un seul encadré** (séparés par un filet), l'aperçu se détache mieux (focus sur tout le bloc) et l'aide passe en plus discret. Le **survol des pastilles** n'affiche plus la liste des choix proposés : seule la **bonne réponse** reste visible.

### Corrigé
- **Moodia — migration `moodia_activity_settings` qui échouait** (`Unknown column type "BadgeCategory"`) : la migration testait l'existence de la table via `$schema->hasTable()`, qui déclenche une **introspection complète** de la base — laquelle plante sur le type Doctrine custom `BadgeCategory` (non enregistré dans `doctrine.yaml`). Remplacé par une requête `INFORMATION_SCHEMA` directe via `$this->connection` (comme les autres migrations Moodia), sans introspection. *(Le type `BadgeCategory` non enregistré reste un bug latent qui affectera toute future migration utilisant `$schema` — à corriger séparément.)*
- **SAM — synchronisation qui plantait (`Undefined property: …$toolAccessService`)** : le refactor « Permission-by-Default » avait supprimé le service `ToolAccessService` et son injection dans les contrôleurs SAM, mais avait laissé **16 appels** `$this->toolAccessService->canAccess($user, 'sam')` dans les corps de méthodes (sync des cohortes, cours, utilisateurs…). Chaque appel touchait une propriété inexistante puis une méthode sur `null`. Remplacés par le contrôle d'accès canonique du projet `!$user instanceof User || !$user->canAccessSam()`, déjà utilisé ailleurs dans ces mêmes contrôleurs. Corrige le plantage au clic sur « Synchroniser » (`SamCohortsController`, `SamController`, `SamCoursesController`, `SamUsersController`).
- **Moodia — export `.mbz` : import qui échouait sur les questions numériques/calculées** (`array_merge(): Argument #2 must be of type array, null given`) : le bloc d'options était écrit avec ses champs **directement** dans `<numerical_options>`, alors que la restauration Moodle attend un **enfant `<numerical_option>`** (`numerical_options/numerical_option[0]`). Ajouté → l'import des questions **numériques** passe. Même logique pour la **calculée** : ses extras de réponse (tolérance, type de tolérance…) sont désormais dans un bloc **`<calculated_records>`** dédié (et non plus en ligne dans `<answer>`), conformément au format de restauration. (Plus aucun `<numerical_units>` vide non plus.)
- **Moodia — quiz : menu de type tronqué** : le menu déroulant du type de question était **coupé** par la carte du quiz (qui masquait le débordement). La carte du quiz n'écrête plus son contenu → le menu s'affiche **entièrement par-dessus**, même s'il est long.
- **Moodia — quiz : anneau de focus parasite sur le champ « Valeur attendue »** (réponse courte / numérique) : le champ sans bordure affichait l'anneau de focus par défaut du plugin de formulaires au clic. Il est désormais retiré (`focus:ring-0`) — le champ reste épuré, cohérent avec les autres réponses.
- **Moodia — export `.mbz` qui plantait sur les quiz** (`Class "…\MoodiaQuizQuestion" not found`) : la classe d'entité n'était pas importée dans `CourseTransformer`, ce qui faisait échouer l'export de tout cours contenant un quiz. Import ajouté + test d'intégration `transformQuiz` pour couvrir ce chemin.
- **Moodia — placeholder « Commencez à écrire… » qui apparaissait sur chaque ligne vide** : en faisant Entrée, le navigateur clonait le paragraphe vide **avec son `data-placeholder`**, et la règle CSS affichait le texte d'invite sur **toutes** les lignes vides. Le placeholder ne s'affiche désormais **que lorsque la page ne contient qu'un seul paragraphe vide** (`:only-of-type`, qui ignore le `<div clear:both>` final) — donc plus jamais sur les lignes vides intermédiaires.
- **Moodia — éditer pendant que l'historique est ouvert ne perd plus le texte** : si on écrivait/modifiait le contenu alors que le panneau d'historique était ouvert, le fermer revenait à l'état d'avant (les modifications étaient écrasées). Désormais, une édition réelle (pas un simple aperçu de version) devient la nouvelle **référence de retour** et la sélection repasse sur « Version actuelle » — fermer le panneau conserve donc bien le texte saisi.
- **Moodia — l'historique enregistre l'état AVANT modification (et non après)** : un checkpoint capturait le contenu déjà modifié, si bien que la 1ʳᵉ modification d'une page « écrasait » l'original dans l'historique — impossible de revenir avant. Désormais on mémorise l'**état de référence « propre »** de la page à l'affichage, et chaque checkpoint enregistre **cet état d'avant** (la référence devient ensuite l'état courant). Revenir en arrière restaure donc bien le texte **d'avant** la modification. Throttle ~2 min, déduplication et rétention inchangés.
- **Badges — polling beaucoup moins gourmand (fini les « Trop de requêtes »)** : le poller tapait `/api/badges/pending` **toutes les 5 s en continu** (12 req/min/utilisateur), saturant le quota API partagé (100/min). Il passe à une stratégie **adaptative** : un check peu après le chargement, heartbeat **60 s en usage actif**, **backoff jusqu'à 5 min** quand l'utilisateur est inactif et que rien ne tombe, **retour immédiat** à la cadence rapide quand un badge apparaît ou au retour sur l'onglet, et **aucune requête quand l'onglet est masqué**. En pratique ~1 req/min en usage actif, ~0,2 req/min au repos (vs 12 avant) — la limite serveur n'est pas touchée.
- **Badges — fini les notifications « Badge débloqué » dupliquées** : le même badge pouvait s'afficher plusieurs fois (jusqu'à ~15) à cause d'une course du polling (le badge restait « non affiché » tant que l'appel de marquage n'était pas confirmé, et le poll suivant le re-renvoyait). L'endpoint `/api/badges/pending` **marque désormais les badges comme affichés au moment où il les renvoie** (atomique) → chaque badge n'est notifié qu'**une seule fois**. Sécurité client en plus (déduplication par id). Les badges restent vérifiés/attribués à chaque action de l'utilisateur (inchangé).
- **Pixia / Monalisia — image facturée au moins son coût de base (par qualité)** : l'usage renvoyé par l'API image sous-estimait le coût (une image Full HD facturée ~4 500 tokens au lieu des ~27 000 affichés). La facturation applique désormais un **plancher = montant de base de la qualité** : HD/ultra **≥ 27 000**, standard ≥ 9 000, low ≥ 2 000, Monalisia ≥ 25 000 (`max(coût calculé, base)`), et facture le coût réel s'il est supérieur. Génération directe **et** depuis PDF. Affichage et facturation cohérents.
- **Moodia — page vide : vrai placeholder d'écriture + plus d'icône d'image trompeuse** : une page nouvellement créée affiche un **véritable placeholder** dans la zone d'écriture (paragraphe vide porteur d'un `data-placeholder` lu via `attr()`, mécanisme standard — disparaît dès la 1re frappe), au lieu d'un texte figé. Le **bouton d'ajout d'image dans la marge** ne s'affiche plus tant que la page est vide (il était affiché seul, ressemblant à une image cassée) ; il réapparaît dès qu'il y a du contenu.
- **Moodia — taille de police figée au changement Paragraphe ↔ Titre** : si un bloc portait un `font-size` inline (ex. « Petit texte » hérité d'une ancienne page), passer en Titre 1-6 (ou Paragraphe) via la barre flottante **conservait** cette taille → le titre restait minuscule. Le `font-size` inline des blocs touchés par la sélection est désormais **retiré** après tout changement de style, pour que le tag reprenne sa taille native (H1 = grand, etc.).
- **Moodia — menus de la barre flottante coupés en bas de page** : les dropdowns (style, couleurs, listes, IA, lien) s'ouvraient toujours vers le bas et étaient tronqués près du bas de l'écran. Ils **basculent désormais vers le haut** quand la place manque en bas ; un seul menu reste ouvert à la fois.
- **Moodia — formatage de l'éditeur sans effet** : dans la barre flottante, le formatage (gras, italique…) reposait sur la sélection préservée par `preventDefault` mais ne la **restaurait pas explicitement** avant `document.execCommand` → selon le navigateur, la commande n'avait aucune cible et ne s'appliquait pas. Le focus et la sélection sauvegardée sont désormais restaurés avant chaque commande. Outils enrichis au passage : Souligné, Barré, Code, Exposant, liste numérotée.
- **Moodia — régénération débitée et loguée à 0 token** : le service de régénération lisait la clé `usage.total_tokens`, alors que `OpenAIResponseParser::extractUsage()` expose `usage.total` (les autres étant `input` / `output`) → le nombre de tokens valait toujours `0` (débit du solde **et** ligne d'historique à 0). Corrigé en lisant la bonne clé : le coût réel de la transformation est désormais débité et affiché dans l'historique d'activité.
- **Moodia — « Page introuvable » à la régénération** : l'action résolvait la page via `(int) activityId`, mais l'éditeur envoie des identifiants **client** (`createId('page')`, non numériques) pour les pages créées ou éditées → `(int)` valait `0` → 404 quasi systématique. La régénération ne dépend plus ni d'un identifiant ni du type d'activité : l'éditeur transmet directement le texte à retravailler, donc ça fonctionne sur n'importe quelle page.
- **Moodia — « Contenu requis » à tort au clic sur « Générer l'audio »** : le bouton vérifiait l'ancien champ `page.content`, resté vide depuis que les pages sont stockées en blocs (`page.blocks`), ce qui déclenchait le message presque systématiquement même sur une page pleine. `generateAudio` utilise désormais le helper `getTextFromPage()` (déjà éprouvé, gère les deux formats) pour le garde-fou, le contrôle de longueur et le texte envoyé au backend.
- **Moodia — ordre des activités d'une section randomisé** : `CoursePlanParser` mélangeait aléatoirement (`shuffle`) les activités d'une section après les avoir pourtant ordonnées (label d'intro en tête, audio injecté en fin), produisant des parcours incohérents (quiz/audio avant l'intro, pages dans le désordre). Remplacé par un tri déterministe et **stable** par type (intro → contenu → audio → exercice → ressources → échange) qui préserve l'ordre relatif des pages issu du plan IA. Couvert par un test unitaire dédié.
- **Moodia — logs verbeux exposant le contenu utilisateur** : retrait des logs `[DEBUG]` laissés au niveau INFO (donc actifs en production) qui écrivaient le prompt complet — incluant le texte intégral du PDF — dans `CoursePlanGenerator`, ainsi que la réponse IA complète dans `OpenAIService` (log « OpenAI API response structure » dumpé à chaque appel + dump `fullResponse`). Le warning « réponse vide » conserve un diagnostic non sensible (`responseKeys`). Réduit le volume des journaux et évite la fuite de contenu pédagogique/PDF dans les logs.

### Supprimé
- **Moodia — suppression du pipeline d'analyse legacy `/moodia/analyze`** : cette route (`POST /moodia/analyze`) était cassée (formulaires `MoodiaUploadType`/`MoodiaParametersType` non importés dans le contrôleur → `Class not found` à l'appel) et redondante avec le pipeline actuel (`generate-stream` + génération activité par activité). Retrait de la route et de son action, des 2 formulaires associés, de la constante JS `ANALYZE` (jamais consommée) et de tout le code mort dans `OpenAIService` (`analyzePdfAndParameters`, `extractPdfContent`, `isPdfToTextAvailable`, `buildCourseStructurePrompt`, `callChatCompletion`, `parseCourseStructureResponse`, `analyzeForTest` sans appelant, et `executeWithRetry` dont `callChatCompletion` était l'unique appelant). `OpenAIService` passe de 920 à 367 lignes. Méthodes conservées et toujours utilisées : `generateObjectives`, `determineImagePlacements` et leurs helpers. Tests unitaires et fonctionnels mis à jour en conséquence.

### Ajouté
- **Support du LaTeX dans les cours et quiz Moodia** : les formules mathématiques générées s'affichent désormais correctement dans le cours exporté vers Moodle.
- **Édition des formules dans l'éditeur Moodia** : les formules sont rendues visuellement dans l'éditeur. Un clic sur une formule permet d'en modifier le code LaTeX, et elle se ré-affiche dès qu'on clique ailleurs.

### Corrigé
- **Éditions du contenu non enregistrées dans Moodia** : les modifications apportées au texte d'une page étaient perdues au rechargement. Elles sont désormais correctement sauvegardées.

### Corrigé
- **User entity — ajout du setter `setUpdatedAt()`** : l'entité `User` exposait `getUpdatedAt()` mais pas le setter, alors que 7 endroits du codebase l'appelaient (`AuthController` ×2, `Admin/UserController` ×3, `UserMigrationService` ×2). Tout edit d'utilisateur depuis l'admin déclenchait `UndefinedMethodError`. Cohérence rétablie avec les autres entités timestampées (`Company`, `Contract`, `UserToken`, `CompanyToken`, `SlidiaTemplate`) qui ont toutes un setter public. Le callback `#[ORM\PreUpdate]` existant reste en place comme filet de sécurité automatique.

### Corrigé
- **Stripe handleCheckoutCompleted — réconciliation défensive après chaque checkout** : remplace `cancelOldStripeSubscriptionForUpgrade()` (qui pouvait échouer silencieusement quand le marker metadata était perdu ou que l'API Stripe refusait l'annulation) par `reconcileCustomerSubscriptions()`. Après chaque `checkout.session.completed`, on liste toutes les subs actives du customer côté Stripe et on annule celles qui ne correspondent pas à la sub que Bubul vient d'activer. Garantit "1 plan Bubul ↔ 1 sub Stripe" par construction, peu importe la cause d'un éventuel échec d'annulation antérieur (Stripe down, race condition, marker perdu). Cas réel observé qui a motivé le fix : user fait Starter → Pro → Expert et se retrouve avec 3 subs actives côté Stripe au lieu d'une.

### Modifié
- **Stripe upgrade — refonte vers Checkout Session (symétrie avec création initiale)** : abandon définitif de l'approche `Subscription::update()` + `payment_behavior=default_incomplete` qui mutait la sub Starter côté Stripe avant confirmation du paiement et causait des annulations en cascade quand l'upgrade n'aboutissait pas. Désormais : `changePlan()` upgrade délègue à `StripeCheckoutService::createUpgradeCheckoutSession()` qui crée une **nouvelle** Checkout Session pour le nouveau plan avec marker `upgrade_from_subscription_id` dans metadata. Si le user paie → `handleCheckoutCompleted` détecte le marker, bascule l'UserSubscription locale vers le nouveau plan, **puis** annule l'ancienne sub Stripe (try/catch robuste avec log d'erreur si Stripe échoue). Si le user abandonne ou que le paiement échoue → aucun event Stripe émis, aucun changement nulle part, le Starter reste 100% intact. Comportement symétrique avec la création initiale freemium → payant qui fonctionne déjà parfaitement. Le pendingPlan n'est plus utilisé côté upgrade (réservé aux downgrades planifiés).

### Corrigé
- **Stripe handleSubscriptionUpdated — garde-fou contre les upgrades non confirmés** : si la sub Stripe pointe vers un plan différent du plan local **et** que son statut n'est pas final (active/canceled/cancel_scheduled), Bubul ne touche **plus** au statut local. Cas couvert : pendant un upgrade `payment_behavior=default_incomplete`, Stripe émet `customer.subscription.updated` avec statut `past_due` ou `incomplete` avant que le user ait validé son 3DS. Sans ce garde-fou, le statut local passait transitoirement en past_due → "Paiement en échec" affiché sur un Starter encore parfaitement actif. Désormais : tant qu'on n'a pas la confirmation explicite (`invoice.payment_succeeded`), le user reste exactement sur son plan d'origine. Si le paiement n'arrive jamais, Stripe finit par annuler la sub à ~23h et émet `customer.subscription.deleted` qui est traité normalement.
- **Stripe handlePaymentFailed — ne plus marquer past_due sur échec transient** : pendant un upgrade SCA-exempt, Stripe émet `invoice.payment_failed` au moment où le user atterrit sur la page de paiement (avant qu'il ait validé son 3DS), puis `invoice.payment_succeeded` ~80 secondes plus tard. L'ancien handler marquait la sub en `past_due` sur le failed, ce qui affichait "Paiement en échec" pendant la fenêtre, alors que tout se résolvait. Désormais, sur un transient (`billing_reason=subscription_update` AND `attempt_count=0`, ou autres heuristiques SCA), Bubul **ne touche absolument à rien** — pas de past_due, pas d'email, pas de pendingPlan clear. Le payment_succeeded qui suit applique le nouveau plan proprement. Les vrais échecs définitifs (carte vraiment refusée, après plusieurs retries Stripe) continuent d'être traités normalement.
- **Stripe API 2026-03-25.dahlia — lecture du price ID dans les lignes de facture** : nouveau helper `StripePayloadReader::getInvoiceLinePriceId()` qui lit `Invoice.lines.data[0].pricing.price_details.price` (nouveau schéma) avec fallback `Invoice.lines.data[0].price.id` (ancien schéma). Cause racine de l'upgrade qui ne s'appliquait pas en local : `handlePaymentSucceeded` lisait `null` sur le payload Dahlia → `findByPriceId` n'était jamais appelé → le block d'application du nouveau plan était silencieusement skipé → user restait sur Starter malgré le paiement Pro réussi. Complète les 2 chemins déjà migrés au commit `ae835db7` (Invoice.subscription, Subscription.current_period_end).

### Annulé
- **Rollback de l'approche Stripe Customer Portal pour l'upgrade** : revert des commits `7e247660` (passage à `BillingPortal\Session::create()` avec `flow_data: subscription_update_confirm`), `f2c23a2e` (création de `PortalConfiguration` éphémère) et `bb625648` (ajout `payment_method_update.enabled`). Raison : malgré le contrat "abandon = état inchangé", l'UX réelle était trop opaque (user qui confirmait sans payer ne voyait aucun signal côté Bubul, plan local restait sur l'ancien sans explication). Retour au flow `Subscription::update()` direct avec `payment_behavior=default_incomplete` (état du commit `8cce48cf`) — la sub Stripe est modifiée immédiatement, l'utilisateur est redirigé vers une page de paiement Stripe explicite (hosted_invoice_url), le webhook `invoice.payment_succeeded` applique le plan localement. À explorer ultérieurement : une autre approche pour informer l'utilisateur des upgrades en attente.

### Ajouté
- **Subscription — bouton "Modifier mon abonnement"** : nouveau CTA `primary` sous le widget tokens dans `/profile/subscription`, visible pour tous sauf les comptes B2B. Lien direct vers `/upgrade` (la grille de tous les plans), qui sert aussi bien pour upgrade que pour downgrade.

### Supprimé
- **Subscription — hint upsell "Avec X, X tokens par mois. Voir l'offre"** : retiré au profit du nouveau bouton "Modifier mon abonnement" qui couvre le même besoin de manière plus visible et symétrique (un seul CTA au lieu d'un hint + un lien).

### Modifié
- **Stripe changePlan — payment_behavior: default_incomplete** : la sub Stripe n'est plus modifiée tant que la nouvelle facture n'est pas payée. Si l'utilisateur abandonne le 3DS, la sub reste sur l'ancien plan côté Stripe — plus de rollback complexe ni d'état corrompu à nettoyer côté Bubul. Expand `latest_invoice.confirmation_secret` pour permettre une future intégration Payment Element.

### Ajouté
- **Upgrade — tooltip interactif sur boutons de création** : nouveau molecule `_molecules/upgrade-tooltip` affiché au hover des boutons "Créer" (Moodia, Slidia, Pixia, Monalisia, Castia) quand le solde est insuffisant. Affiche le nombre de tokens requis + un CTA "Passer en X" avec le plan suivant (freemium/starter → Pro, pro → Expert, expert/B2B → aucun CTA). Le tooltip est rendu interactif (cliquable) via une extension du `tooltip_controller.js` qui supporte désormais le mode `data-tooltip-html` avec `pointer-events: auto` + délai de hide pour la transition bouton → tooltip.
- **Upgrade — hint discret dans section Tokens (subscription)** : sous le widget des tokens dans `/profile/subscription`, ligne grise minimaliste qui mentionne le plan suivant et son quota mensuel avec un lien "Voir l'offre" — soft upsell pour les utilisateurs réflexifs déjà sur la page abonnement.
- **Upgrade — badge dynamique card Tokens (dashboard)** : le badge "Passer en X" dans la card Tokens du dashboard s'adapte au plan actuel et propose toujours le plan suivant (gradient bleu foncé Pro), au lieu d'être affiché uniquement pour les freemium.

### Modifié
- **Upgrade — wording harmonisé** : tous les CTAs upgrade utilisent désormais le libellé "Passer en {plan}" et le gradient `linear-gradient(135deg, #0d1b3e 0%, #0C81E4 100%)` (bleu foncé Pro). Plus de variation "Plus de tokens", "Passer en Pro", "Passez à l'Pro" — un seul wording reconnaissable.

### Ajouté (suite)
- **Upgrade — bannière tokens bas (dashboard)** : nouvelle bannière incitative en haut du dashboard pour les utilisateurs freemium avec moins de 10% de tokens restants. Couleur warning, icône bolt, CTA "Passer en pro" intégré. Disparaît automatiquement dès que le user repasse au-dessus du seuil ou s'abonne.
- **Upgrade — overlay verrou outil incitatif** : le molecule `tool-card` affiche désormais un overlay complet sur les outils verrouillés (au lieu d'un simple cadenas gris) avec icône lock, message "Disponible avec Pro", description contextualisée et CTA "Passer en pro" cliquable. Toute la zone de l'overlay est cliquable.
- **Upgrade — badge "Passer en pro" dans la card Tokens** : badge primary dans le coin haut-droit de la card "Tokens restants" du dashboard pour les freemium. Animation hover synchronisée avec la card (peer + group-hover).

### Corrigé
- **Migrations idempotentes** : `Version20260608000001` (déplacement `last_claimed_at` user → user_token) et `Version20260531000002` (reset `onboarding_completed`) vérifient désormais l'état des colonnes avant d'agir. Évite les erreurs "Duplicate column" / "Unknown column" sur les envs où la migration de main a déjà tourné sous son ancien nom, ou inversement.
- **Stripe API 2026-03-25.dahlia — handlers webhooks compatibles nouveau schéma** : nouveau helper `StripePayloadReader` qui lit `Invoice.parent.subscription_details.subscription` (avec fallback `Invoice.subscription`) et `Subscription.items.data[0].current_period_end` (avec fallback `Subscription.current_period_end`). Migration des 13 points de lecture dans `StripeWebhookService` (5) et `StripeSubscriptionService` (8). Résout les paiements 3DS bloqués en `past_due` après changement de schéma Stripe.
- **SubscriptionController — sync étendu aux statuts ambigus** : le fallback `syncStatusFromStripe` se déclenche désormais aussi pour les statuts `past_due` et `incomplete` (en plus de `active`), pour rattraper automatiquement les paiements 3DS finalement réussis côté Stripe quand le webhook Messenger n'a pas été consommé.
- **Stripe webhooks — handlePaymentSucceeded source de vérité = price ID** : ne plus se baser sur `pendingPlan` (qui pouvait être nettoyé prématurément dans les flows SCA-exempt où Stripe émet `invoice.payment_failed` puis `invoice.payment_succeeded` à 10 s d'intervalle). Le price ID dans la facture devient l'autorité — on compare le plan local au plan facturé et on applique le différentiel.
- **Stripe webhooks — handlePaymentFailed transient vs terminal** : `pendingPlan` n'est plus clear sur un payment_failed (protège les upgrades 3DS en cours). Nouvelle heuristique `isTransientPaymentFailure()` qui détecte les échecs intermédiaires (`invoice.status=open` + `next_payment_attempt`, `payment_intent.status=requires_action/requires_confirmation`, `payment_intent.next_action` présent) → on skippe l'email d'alerte dans ces cas pour ne pas paniquer l'utilisateur avant le retry Stripe ou le SCA.

### Ajouté
- **Admin — suppression d'un utilisateur** : nouvelle action `Supprimer` sur `/admin/users/{uuid}` (section "Gestion du compte"), réservée à `ROLE_ADMIN`. Confirmation par modal explicite. Route `POST /admin/users/{uuid}/delete` protégée par CSRF + garde self-deletion + protection cross-admin (un admin ne peut pas supprimer un autre admin sans lui retirer le rôle au préalable). En cas de contenus liés (cours, présentations, etc.), `ForeignKeyConstraintViolationException` capté → message admin invitant à désactiver le compte plutôt.

### Corrigé
- **Bug prod — bouton "Je commence" inerte sur step security** : la garde C1 (`stepPrerequisites`) listait `tools` comme prérequis obligatoire de `security`/`role`, alors que `tools` est skippé conditionnellement pour les comptes freemium. Résultat : POST `step=security` était silencieusement rejeté → redirect circulaire. `plans` et `tools` retirés des prérequis aval. La chaîne minimum `birthday → name → password → usage` reste obligatoire, donc la protection step-skipping reste effective.
- **UI — page choix profil épurée** : suppression de la liste de features (check-circle) sur les cartes Particulier/Établissement de `/welcome/usage`, contenu marketing repensé (titres et sous-titres plus engageants).
- **Bug prod — 500 sur paramètres utilisateur** : `SettingsController` appelait `$user->setUpdatedAt()` / `$settings->setUpdatedAt()` qui n'existent plus depuis le passage aux lifecycle callbacks `@PreUpdate`. 4 endpoints en 500 (toggle sidebar, toggle tool-stats, modification identité, changement de mot de passe). Appels supprimés, le callback gère désormais l'update automatiquement.
- **Tests** : suite remise au vert (1584 tests, 0 failure). Suppression de 8 tests morts (`Settings`/`User` testant des setters supprimés), adaptation de 2 tests aux nouveaux defaults (`createFromEmail` retourne `firstName=null`/`lastName=null`, landing CTA pointe vers `/login`).
- **Sécurité — step skipping dans `/welcome`** : `dispatchPost()` valide désormais que tous les pré-requis du `step` reçu sont déjà dans `_welcome_completed` avant d'exécuter le handler. Empêche un POST direct sur `step=role` / `step=usage` qui aurait court-circuité l'étape de création de mot de passe. Garde de défense en profondeur ajoutée dans `processUsage`, `processRole`, `teamConfirmation` : refuser tout `security->login()` programmatique si le user n'a ni password ni `googleId`.
- **Sécurité — CSRF sur `/reset-password/{token}`** : ajout du token CSRF (`reset_password`) dans le template et validation côté contrôleur. Empêche un attaquant disposant d'un token reset (fuite Referer, dump partiel) de forcer la réinitialisation à distance via POST cross-site.
- **Sécurité — CSRF sur `/complete-registration/{token}`** : idem (`complete_registration`). Empêche le détournement de comptes d'invitation B2B.
- **Sécurité — tokens email hashés en base** : `emailVerificationToken` et `pendingEmailToken` sont désormais stockés en SHA-256 (comme `passwordResetToken`). La fix précédente (token reset) est propagée aux deux autres tokens. Un accès lecture à la base ne permet plus de prendre l'email d'un utilisateur ni d'activer un compte d'invitation.
- **Sécurité — token de réinitialisation de mot de passe** : le token reset est désormais stocké en base sous forme de hash SHA-256 (`hash('sha256', $token)`) au lieu d'être conservé en clair. Le lien email contient toujours le token brut. La vérification recalcule le hash à la volée. Un accès lecture à la base ne permet plus d'utiliser directement les tokens en attente.
- **Sécurité — liaison Google SSO sur compte non vérifié** : `GoogleAuthenticator` refuse désormais de lier un compte Google à un compte existant dont la vérification OTP est en attente (`emailVerificationToken !== null`). Sans cette garde, l'activation du compte s'effectuait dans le UserBadge loader avant `UserChecker`, permettant une prise de contrôle silencieuse.
- **Sécurité — wizard d'onboarding Google SSO** : les utilisateurs authentifiés via Google (sans mot de passe) ne sont plus soumis à l'étape de création de mot de passe du wizard `/welcome`. L'étape est désormais ignorée automatiquement si `googleId` est non-null, que ce soit via la route centrale `/welcome` ou la route dédiée `/welcome/password`. Cela empêche l'activation silencieuse de `form_login` sur des comptes prévus comme Google-only.
- **Commande — onboarding des utilisateurs existants** : nouvelle commande `app:auth-redesign` à lancer manuellement en production. Passe `onboarding_completed = 1` pour tous les utilisateurs actifs existants afin qu'ils ne soient pas redirigés vers le wizard.
- **Slidia — coût tokens réel** : double bug corrigé. (1) `SlidiaAiService` faisait une double-extraction de `usage` (`extractUsage($response)` sur un retour qui contenait déjà l'usage extrait par `OpenAIHttpClient`), retournant systématiquement `tokensUsed = 0`. (2) `SlidiaController` forçait un plancher de 5 000 tokens via `max(SLIDIA_GENERATE_MIN_TOKENS, $totalTokens)`. Désormais chaque génération débite le nombre **exact** de tokens consommés par OpenAI (avec fallback à 5 000 si l'API ne retourne pas le bloc usage).

## [2.46.0] — 2026-06-02

### Ajouté
- **Système "Collecter" les tokens de badges** : les tokens ne sont plus crédités automatiquement à l'attribution d'un badge. L'utilisateur doit cliquer "Collecter" sur la page `/settings/badges` pour les récupérer. `UserBadge::claimedAt` nullable tracé à la réclamation. Migration `Version20260602000001` : colonne `claimed_at` avec backfill (`earned_at`) pour les badges déjà attribués.
- **Controller Stimulus `badge--claim`** : gère le clic Collecter via `POST /api/badges/claim` (accepte un tableau de slugs), affiche un toast de succès, anime la disparition du bouton.
- **Endpoint `POST /api/badges/claim`** dans `BadgeController` : vérifie l'appartenance, appelle `claim()`, crédite les tokens, retourne `{ claimed, balance }`.
- **Chip tokens cohérent** : sur les cartes badge-group-card, le chip (haut-droite) affiche le même montant que le bouton "Collecter" quand des tokens sont non réclamés — cohérence garantie par le calcul de `unclaimedTokens` avant le rendu du chip.

### Modifié
- **`BadgeAwarderService`** : ne crédite plus les tokens à l'attribution — les tokens sont en attente de réclamation.
- **Page badges — responsive mobile** : grille 2 colonnes partout (`grid-cols-2`), bulle 80px sur mobile, padding réduit (`p-2 sm:p-5`), textes compressés (`text-[9px]`→`text-[11px]`).
- **Badge-group-card** : noms affichés sans le suffixe " — niv. X" (filtre `|split(' — ')|first`), descriptions `line-clamp-2`, compteur numérique de niveaux supprimé (seuls les dots restent).
- **Header badges mobile** : layout 2 lignes (titre + daily-reward card) pour éviter le chevauchement sur petits écrans.
- **Toast badge débloqué** : simplifié — affiche uniquement le nom du badge sans bouton ni montant de tokens.
- **Bulle badge** : taille 80px sur mobile (était 76px), point "récent" au-dessus supprimé.
- **Icône section outil** : taille `xl` dans un conteneur `w-11 h-11 sm:w-14 sm:h-14`.
- **Responsive notifications, support, nouveau ticket** : headers mobiles compacts avec icône + titre sur une ligne, padding réduit.

## [Non publié]

### Ajouté
- **Onboarding B2B — étape message libre** : nouvelle étape `/welcome/team/message` (entre `moodle` et `confirmation`) avec textarea optionnel (`maxlength=2000`) + compteur de caractères live. Le message est inclus dans l'email de lead SAM (`emails/b2b/sam-lead.html.twig`).
- **B2C — login précoce après l'étape `usage`** : l'utilisateur Particulier est activé (Freemium) et logué dès le choix B2C pour pouvoir accéder à `/upgrade` (page de choix de plans réelle) au lieu de l'ancien `auth/welcome/plans.html.twig` statique.
- **Login d'inscription dev-friendly** : `RegistrationController` skip les 4 rate limiters (OTP send IP/email, OTP attempt, create IP) en environnement `dev` via `#[Autowire(param: 'kernel.environment')]`.
- **Birthday — validation à 5 couches** : jour 1-31, mois 1-12, année ≤ courante, plage MIN/MAX_AGE, `checkdate()` PHP — chaque check renvoie son propre message d'erreur précis (au lieu d'un message générique).
- **OTP page — compteur restant et auto-soumission** : page `register_verify.html.twig` avec `appearance-none p-0 leading-[2.5rem]` (centrage parfait sans dépendre du browser styling).

### Modifié
- **`ONBOARDING_SESSION_TIMEOUT`** : 5 min → **15 min** d'inactivité avant reset de l'onboarding.
- **Carousel login — Poppins partout** : retour à Poppins (`font-display` Plus Jakarta Sans retiré des titres) pour cohérence avec les titres d'onboarding.
- **Page `/upgrade` — responsive mobile** : titre `text-2xl sm:text-3xl lg:text-4xl`, toggle Mensuel/Annuel en colonne sur mobile (`flex-col sm:flex-row`), retrait du `justify-center` qui coupait le logo sur petits écrans.
- **`step-layout` — top padding augmenté** : `top-10 / pt-28` sur mobile pour aérer logo ↔ titre.

### Corrigé
- **Icônes Bubul × SAM manquantes** dans les 6 templates `team/*` : conflit d'IDs SVG (2 includes du même `logo_simple.html.twig`) → un seul include + `scale-75 sm:scale-100`.
- **Étape `plans` skipée silencieusement** pour les utilisateurs session-based : combiné au login précoce B2C, la redirection vers `/upgrade?context=onboarding` fonctionne désormais nativement.
- **Couleurs SVG illustrations carousel** : 4 SVG sources renommés en kebab-case dans `_atoms/icon/storyset/` pour cohérence avec les templates Twig (`learning.svg`, `online-world.svg`, etc.).

### Supprimé
- **Notification "Connexion réussie"** : suppression de `Security/AuthenticationSuccessHandler.php` — plus de toast inutile au login.

### Ajouté
- **Atom `illustration`** : nouveau composant pour intégrer les illustrations Storyset avec couleur primaire éditable (`{{ colorPrimary }}`) et tailles responsive (`xs` → `2xl` + `fluid`). 4 illustrations disponibles : `online-world`, `graduation-hats`, `learning`, `college-class`.
- **Controller Stimulus `shared--auth-slider`** : carousel auto-advance (5s par défaut) avec navigation par dots, pause au survol, transitions fluides via opacity.
- **Login — panel gauche marketing** : refonte complète en carousel 3 slides sur fond pastel bleu clair. Messages marketing positionnant Bubul comme allié du formateur (noblesse du métier, suite interconnectée, fierté du métier).
- **Police `Plus Jakarta Sans`** : police d'affichage humaniste ajoutée pour les titres marketing (variable CSS `--font-display`). Plus douce que Poppins pour les headlines.
- **Login — réorganisation form** : logo Bubul centré (cliquable → retour accueil), puis form, puis séparateur "ou", puis SSO Google, puis lien d'inscription. Pattern Mastery Hub.
- **Login — lien retour flottant** : "Retour à l'accueil" déplacé en `fixed top-left` du viewport (pattern Linear/Notion) avec flèche animée au hover.
- **Hiérarchie typo carousel** : titre `text-gray-900` vs sous-titre `text-gray-500` (4 crans d'écart Tailwind) pour distinction visuelle franche sans alourdir.
- **Suite responsive complète** : `scale-90 sm:scale-100` sur le logo mobile, `xl:text-4xl` sur les headlines, `xl:max-w-sm` sur les illustrations pour les écrans XL.

### Corrigé
- **CGU — persistance** : `createFromEmail()` enregistre désormais `cgaAcceptedAt` lors de l'inscription email (le champ était validé côté controller mais jamais sauvegardé en base)
- **Logo — step-layout** : un seul include du SVG logo (suppression du double include mobile/desktop qui causait un conflit d'IDs SVG rendant l'icône invisible sur desktop)
- **Logo — texte mobile** : le texte "Bubul" est maintenant visible sur mobile (taille réduite `scale-75`) au lieu de l'icône seule

### Sécurité
- **Anti-spam inscription** : timing check — formulaire soumis < 3 secondes après chargement de page rejeté silencieusement (bots)
- **Rate limits** : `register_otp_send_ip` réduit de 5→3/heure, `register_create_ip` réduit de 10→3/heure par IP

### Modifié
- **Onboarding — report du login** : `security->login()` n'est plus appelé dans `RegistrationController::terms()`. Le login est déclenché uniquement à la fin de l'onboarding (`processRole()` pour B2C, `teamConfirmation()` pour B2B). Les utilisateurs non authentifiés sont identifiés par la clé de session `_onboarding_user_id` durant l'onboarding.
- **Onboarding — timeout 5 min** : si l'utilisateur est inactif plus de 5 minutes (`AppLimits::ONBOARDING_SESSION_TIMEOUT`) pendant l'onboarding, la session est effacée et il est redirigé vers `/register`. Le compte non activé est nettoyé automatiquement.
- **`WelcomeController`** : les routes `/welcome/*` sont désormais `PUBLIC_ACCESS` (suppression de `#[IsGranted('ROLE_USER')]`). `resolveOnboardingUser()` gère les deux chemins (authentifié via Google SSO, non authentifié via email + OTP).
- **`GoogleAuthenticator`** : les nouveaux comptes Google (onboarding non complété) sont redirigés vers `/welcome` au lieu du dashboard.
- **`clearOnboardingSession()`** : efface les 4 clés de session (`_onboarding_user_id`, `_onboarding_last_activity`, `_welcome_completed`, `_welcome_b2b`) pour éviter toute fuite vers un prochain inscrit sur le même navigateur.

### Ajouté
- **Login — stats dynamiques** : le panneau gauche de la page de connexion affiche le nombre réel de formateurs inscrits, cours générés et heures économisées (calcul = cours × 12 h), tirés de la DB via `UserRepository::countAll()` et nouvelle méthode `MoodiaCourseRepository::countAll()`
- **Login — liste de bénéfices** : 3 bénéfices avec checkmarks (cours Moodle, présentations/podcasts/infographies, suivi apprenants) ajoutés dans `auth-value-panel` entre le headline et les stats
- **Onboarding B2B — thème SAM** : toutes les étapes entreprise (sam, organization, size, budget, moodle, confirmation) affichent le logo Bubul × SAM, les blobs violets SAM et le bouton en dégradé SAM issu des couleurs DB
- **Onboarding B2B — design cards** : étapes size, budget et moodle refaites en cartes auto-submit avec icônes Solar et descriptions (suppression des radio buttons + bouton "Continuer")
- **Onboarding B2B — email lead SAM** : `ContactEmailService::sendB2bLeadNotification()` envoie un récapitulatif interne à `contact@bubul.app` ET `agence.dottilde@gmail.com` dès qu'un utilisateur complète l'onboarding entreprise (organisation, taille, budget, LMS, coordonnées + reply-to utilisateur)
- **Template email** `emails/b2b/sam-lead.html.twig` : tableau récapitulatif du lead B2B
- **Abonnement — bouton upgrade contextuel** : page paramètres abonnement — les utilisateurs Starter voient "Passer en Pro" et les utilisateurs Pro voient "Passer en Expert" dans un bloc call-to-action distinct

### Modifié
- **`auth-value-panel.html.twig`** : gradient passe de `#6C63FF` hardcodé à `var(--color-primary-dark)`, suppression du 3e orbe décoratif, tailles compactées (témoignage, stats), espacement vertical resserré
- **`step-layout.html.twig`** : paramètre `blobColor` (couleur des blobs décoratifs) + bloc `{% block logo %}` surchargeable par les pages embarquantes

### Ajouté
- **Page `/upgrade`** : nouvelle page dédiée à la présentation et au paiement des plans (mensuel/annuel), sans sidebar, avec toggle de facturation, modales de confirmation et bouton "Continuer avec le plan gratuit" en contexte onboarding (`?context=onboarding`). `UpgradeController` créé.
- **Bouton "Améliorer mon offre"** : ajouté dans la sidebar (Freemium B2C), dans le header du dashboard (Freemium B2C), et dans la section abonnement des settings

### Supprimé
- **Tour de découverte** : section supprimée des settings, routes `app_settings_onboarding_reset` et `api_settings_onboarding_complete` supprimées, constante `ONBOARDING_COMPLETE` retirée de `api-endpoints.js`

### Modifié
- **`SubscriptionController::success()`** : redirige vers `/dashboard` après paiement Stripe (au lieu de `/settings/subscription`)
- **`WelcomeController::plans()` GET** : redirige vers `/upgrade?context=onboarding` (la page `auth/welcome/plans.html.twig` est abandonnée)
- **`WelcomeController::renderCurrentStep()`** : étape plans redirige vers `/upgrade?context=onboarding` au lieu de rendre `plans.html.twig`
- **`DashboardController`** : autorise l'accès si l'utilisateur a un abonnement payant actif, même si `onboardingCompleted` est false (cas paiement pendant l'onboarding)
- **`templates/profile/subscription.html.twig`** : section 3 (grille de plans) supprimée et remplacée par un CTA "Améliorer mon offre" pour les utilisateurs Freemium

### Corrigé
- **Fixtures de test** : `onboardingCompleted = true` ajouté sur les 3 utilisateurs actifs (`test@bubul.app`, admin, `nomoodle@bubul.app`) pour que les tests atteignent `/dashboard` sans être redirigés vers le wizard
- **Test LoginForm** : suppression de `testRememberMeCheckboxExists` — la case "Se souvenir de moi" a été intentionnellement retirée du formulaire de connexion dans ce redesign
- **AuthController** : remplacement du littéral `6` par `AppLimits::PASSWORD_MIN_LENGTH` (= 8) dans `completeRegistration()`, cohérent avec le reste du codebase
- **SettingsController** : ajout de la route `app_settings_onboarding_reset` (POST `/settings/onboarding-reset`) — l'ancienne route `app_tour_reset` avait été supprimée lors de la fusion `has_seen_tour → onboarding_completed` mais la référence dans `settings.html.twig` avait été oubliée, provoquant 500 sur toutes les pages settings
- **RegistrationController** : nettoyage des clés OTP (`_reg_email`, `_reg_otp_code`, etc.) de la session après finalisation de l'inscription pour éviter des données périmées en session

### Modifié
- **Header landing** : CTA "Essayer" supprimé — bouton "Se connecter" devient le seul CTA (variant `primary`), desktop et mobile
- **Formulaire de connexion** : Google SSO repositionné avant le formulaire email/password, case "Se souvenir de moi" supprimée, "Mot de passe oublié ?" aligné à droite sous le champ password
- **Mot de passe oublié / réinitialisation** : nouveau design sur `step-layout`, contrainte mot de passe 6→8 chars (`AppLimits::PASSWORD_MIN_LENGTH`), URL absolue dans l'email (corrige lien relatif), rate limiting par email et IP sur la page `/forgot-password`

### Ajouté
- **`UserNotificationPreferences`** : nouvelle entité et table `user_notification_preferences` centralisant les préférences d'emails (4 types : `token_renewal`, `subscription_events`, `marketing`, `product_news`). Migration des données depuis `settings` (`email_tokens_renewed` → `token_renewal`, `has_accepted_marketing` → `marketing`). Colonnes obsolètes supprimées de `settings`.
- **Page notifications** : 4 toggles organisés en 2 sections (transactionnel / marketing), branchés sur `user.notificationPreferences`
- **`EmailUnsubscribeController`** : migré vers `UserNotificationPreferences`, 4 types supportés avec lien de désabonnement

- **Templates /welcome — design final** : 14 pages B2C et B2B redessinées avec step-layout organism, gradients brand, indicateur de progression, formulaires accessibles avec labels et validation. Inclut : birthday, name, password (avec lien mot de passe oublié), usage, plans, tools, security, role (B2C) + sam, organization, size, budget, moodle, confirmation (B2B team)
- `WelcomeController` : 13 étapes /welcome avec protection session, split B2C/B2B, finalisation onboarding
- Templates stub `/welcome/*` et `/welcome/team/*` (14 fichiers, temporaires — seront remplacés en Task 11)
- Templates tunnel /register : design complet avec step-layout, Google SSO, OTP 6 cases, CGV
- `RegistrationController` : tunnel inscription OTP /register → /register/verify → /register/terms avec rate limiting et honeypot
- Controller Stimulus `otp_input` : saisie OTP 6 cases avec auto-focus et soumission automatique
- Controller Stimulus `password_strength` : indicateur de force de mot de passe
- `UserRegistrationService::createFromEmail()` : crée un compte minimal vérifié par OTP
- Envoi email OTP 6 chiffres pour vérification à l'inscription
- Organism `step-layout` : layout centré single-column pour le tunnel d'inscription
- **Rate limiters anti-spam** : 6 nouveaux limiters pour OTP inscription (par email et IP) et réinitialisation mot de passe (par email et IP)
- **Settings entity** : champs `dateOfBirth`, `hasAcceptedMarketing`, `b2bOrganizationName`, `b2bUserRange`, `b2bBudgetRange`, `b2bMoodleStatus` pour l'onboarding
- **Fusion `has_seen_tour` → `onboarding_completed`** : champ redondant supprimé de `Settings`. `onboarding_completed = true` remplace partout `hasSeenTour = true`. "Relancer le tour" remet `onboarding_completed = false` (redirect vers wizard au prochain accès dashboard). Migration `Version20260531000003` supprime la colonne `has_seen_tour`
- **Wizard d'onboarding redesigné** (`/bienvenue`) : fond dégradé brand Bubul plein écran (`from-gradient-start → to-gradient-end`), carte blanche centrée avec halo lumineux, message de bienvenue personnalisé (👋 + prénom + tagline plateforme), barre de progression blanche en haut, step dots avec gradient brand actif + ✓ pour étapes passées, 4 étapes en card avec transitions CSS fluides, orbs décoratifs blancs et cyan flottants, pills avec icône emoji dans carré arrondi
- **Animations auth** (`assets/styles/pages/auth.css`) : `authFadeUp` pour l'entrée des formulaires, `wizardStepIn/Back/Out` pour les transitions d'étapes, `orbFloat/2/3` pour les orbs animés, `pillPop` pour le feedback de sélection
- **Step dots interactifs** (`registration_wizard_controller.js`) : étapes passées affichent ✓ (bg-primary/20), étape active avec ring + scale-110, connecteurs colorés dynamiquement au fil de la progression. Guard `#animating` anti-double-clic
- **Bouton "Relancer le tour"** dans les paramètres utilisateur (`/settings`) — POST `/tour/reset` remet `hasSeenTour = false`, le tour se relance au prochain accès au tableau de bord
- **Formulaire d'inscription complet** (`/register`) : panneau marketing split-screen, Google SSO en premier, `<label>` explicites sur tous les champs, indicateur de force de mot de passe (4 barres colorées via `password_strength_controller.js`)
- `_atoms/form/checkbox.html.twig` : nouveau paramètre optionnel `labelHtml` pour rendre un label contenant du HTML (ex: lien CGV) via `|raw`
- **Settings entity** : 4 nouveaux champs pour le onboarding redesign — `onboardingRole`, `onboardingNeed`, `onboardingSource`, `hasSeenTour`. Permettent le tracking des qualifications utilisateur et du tour guidé
- **Architecture badges DB-driven** : la table `badge` devient la source de vérité unique. Migration `Version20260601000003` : ajout de `threshold`, `metric_key`, `tool_code`, `variant`, `is_active` ; suppression de `required_days` et `required_level` ; `slug` étendu à 150 chars, `description` à 500, `category` élargi pour inclure `achievement`.
- **`BadgeCategory::ACHIEVEMENT`** : nouvelle valeur d'enum couvrant les badges d'achievements par outil (mesures de volume et de diversité d'usage).
- **Seeding achievements dans `app:init:badges`** : étape 4 — itère `BadgeConfig::TOOL_ACHIEVEMENT_BADGES` et persiste chaque niveau en DB avec `threshold`, `metricKey`, `toolCode`, `variant`.
- **`BadgeRepository`** : nouvelles méthodes `findActiveByCategory()`, `findActiveByToolCode()`, `findActiveByToolAndCategory()`, `findAllActiveIndexedBySlug()`.

### Modifié
- **`BadgeAwarderService`** : injecte `BadgeRepository`. Lit `tokenReward` depuis la DB (plus `BadgeConfig::getDefinition()`). Badge inactif (`isActive = false`) ignoré silencieusement.
- **`BadgeEvaluatorService`** : injecte `BadgeRepository`. Seuils des badges TOOL, ACHIEVEMENT et STREAK lus depuis la DB (`badge.threshold`). Aucune référence à `BadgeConfig::TOOL_LEVELS` ni `TOOL_ACHIEVEMENT_BADGES` pour l'évaluation — uniquement pour les constantes des badges spéciaux (STREAK_DAYS, MULTI_TOOL_COUNT, VOLUME_THRESHOLD).
- **`BadgeController`** : données d'affichage lues depuis la DB (BadgeRepository) ; plus de références à `BadgeConfig` pour les badges — uniquement `DAILY_LOGIN_TOKENS`. Achievements groupés par `metricKey` via `groupAchievementsByMetricKey()`.
- **`InitBadgesCommand`** : utilise `setThreshold()` / `setToolCode()` / `setVariant()` au lieu de `setRequiredDays()` / `setRequiredLevel()` (supprimés de l'entité).

### Ajouté (antérieur)
- **Table `user_badge_metric`** : nouvelle entité `UserBadgeMetric` + repository `UserBadgeMetricRepository` — stocke les valeurs pré-calculées de toutes les métriques de badges par utilisateur (write-through cache). Migration `Version20260601000002`.
- **`BadgeMetricsRefreshService`** : service qui recalcule et persiste toutes les métriques d'achievements pour un utilisateur (20 clés couvrant Moodia, Serenia, Slidia, Castia, Monalisia, Pixia et Promptly). Appelé dans `BadgeEvaluatorService.evaluateAfterActivity()`.
- **`BadgeConfig::TOOL_ACHIEVEMENT_BADGES`** : constante unifiée pour les badges d'achievements de tous les outils (structure `toolCode → type → name/icon/levels`). Couvre Moodia (7 types), Serenia (sessions/messages), Slidia (présentations/slides), Castia (podcasts/durée), Monalisia (infographies/formats), Pixia (images/styles), Promptly (prompts/public/dossiers).
- **`BadgeConfig::toolAchievementSlug()`** : helper générique `{toolCode}_{type}_{level}`, remplace `moodiaAchievementSlug()`.
- **`BadgeConfig::metricKeyForAchievement()`** : dérive la clé de métrique `{toolCode}_{type}` depuis le config.
- Nouvelles méthodes de repository : `CastiaPodcastRepository::sumDurationByUser()`, `MonalisiaInfographicRepository::countDistinctFormatsByUser()`, `PixiaImageRepository::countDistinctStylesByUser()`, `PromptlyPromptRepository::countPublicByAuthor()`, `PromptlyFolderRepository::countByOwner()`, `SereniaChatSessionRepository::countByUser()`, `SereniaChatMessageRepository::countByUser()`.
- **Achievements par outil sur la page badges** : chaque outil affiche maintenant ses groupes d'achievements spécifiques sous les niveaux d'utilisation, en utilisant les métriques pré-calculées.

### Modifié
- **`BadgeController`** : simplifié — injecte `UserBadgeMetricRepository` à la place des 5 repos Moodia. Construction générique de `achievementData` par outil via `TOOL_ACHIEVEMENT_BADGES`. Suppression des variables `moodiaToolData`/`moodiaAchievementData`/`moodiaToolColor`/`moodiaToolBg`.
- **`BadgeEvaluatorService`** : refactorisé — injecte `BadgeMetricsRefreshService` + `UserBadgeMetricRepository`. Méthode générique `evaluateToolAchievements(user, toolCode)` couvre tous les outils. Suppression des 5 repos Moodia directement injectés.
- **Page badges** : section Moodia dédiée supprimée — Moodia est maintenant affiché avec les autres outils dans la section "Progression par outil", ses achievements s'affichent sous ses niveaux d'utilisation comme les autres outils.
- **`BadgeConfig::getDefinition()`** : pattern générique `{toolCode}_{type}_{level}` via `TOOL_ACHIEVEMENT_BADGES` (remplace le pattern spécifique `moodia_\w+_\d+`).

### Modifié (antérieur)
- **Badge-card — bulle** : taille définie dans `.badge-bubble` CSS (`120px × 120px`) au lieu de l'inline style, et grille harmonisée à `sm:grid-cols-3` partout pour des cartes de largeur identique et des bulles visuellement cohérentes
- **Page badges — section Moodia unifiée** : les niveaux d'utilisation Moodia (Bulleuse·ux → Transcendant·e) et les 7 types de Succès Moodia sont regroupés dans une seule section "Moodia". Moodia est retiré de la section générique "Progression par outil". `BadgeController` extrait `moodiaToolData` séparément et ne l'inclut plus dans `toolBadgeData`.
- **Badges par outil — descriptions** : chaque palier mentionne désormais explicitement son objectif (ex. "Utiliser un outil 50 fois") pour clarifier la condition de déblocage
- **Badge-card** : suppression de l'icône outil (coin haut-gauche) sur les cartes de quête par outil et Succès Moodia
- **Dates de validation** : affichage en français avec `format_date('long', locale='fr')` (ex. "30 mai 2026") dans toutes les sections de la page badges

### Ajouté
- **Succès Moodia — 7 types × 5 niveaux** : nouveau système de badges de progression spécifique à Moodia. Types : `export` (cours MBZ exportés), `narrator` (audios TTS générés), `quiz` (questions de quiz créées), `titan` (heures cumulées de cours), `illustrator` (images placées), `lexicographer` (termes de glossaire), `scribe` (pages de contenu). Seuils et tokens récompenses distincts par niveau (500→15 000 tokens). Variants par palier : primary/success/warning/error/cohorts.
- `MoodiaCourse` entity : champ `exportedAt` (`?DateTimeImmutable`, nullable) tracé au premier export MBZ
- Migration `Version20260601000001` : colonne `exported_at` sur la table `moodia_course`
- `MoodiaCourseRepository` : méthodes `countExportedForUser()` et `sumDurationForUser()`
- `MoodiaActivityRepository` : méthodes `countAudiosGeneratedForUser()` et `countPagesForUser()`
- `MoodiaImageRepository` : méthode `countUsedForUser()`
- `BadgeConfig` : constante `MOODIA_ACHIEVEMENT_BADGES` (7 types × 5 niveaux), helper `moodiaAchievementSlug()`, `getDefinition()` étendu au pattern `moodia_{type}_{level}`
- `BadgeEvaluatorService` : méthode publique `evaluateMoodiaAchievements()`, appelée depuis `evaluateAfterActivity()` (toolCode=moodia) et `evaluateAllForUser()`
- `MbzExportController` : trace `exportedAt` au premier export (méthodes `export()` et `exportStream()`), appelle `evaluateMoodiaAchievements()` à chaque export
- `BadgeController` : calcul des métriques Moodia par type, construction de `moodiaAchievementData` passée à la vue
- Section "Succès Moodia" sur `/settings/badges` : grille 5 colonnes (lg) par type avec badge-card et barre de progression
- **Système de quêtes par outil** : refonte complète de la page badges — 7 niveaux par outil (au lieu de 5) avec des titres évocateurs (Bulleuse·ux → Transcendant·e) et descriptions fun/éducatives. Chaque niveau est une carte de quête individuelle avec bulle liquide, chip `+X tokens`, icône outil et barre de progression. Le template `badge-card` est entièrement réécrit (suppression de l'atom hexagon-badge). La grille s'adapte au nombre de quêtes (xl:grid-cols-7). Les couleurs des bulles sont dérivées dynamiquement des couleurs d'outil (colorPrimary/colorBackground) et non plus d'une palette codée en dur.
- `BadgeConfig` : `TOOL_LEVELS` étendu à 7 paliers (seuils : 3/15/50/150/500/1500/5000 usages), champ `description` ajouté à chaque palier, regex `getDefinition()` étendue de `[1-5]` à `\d+`
- `BadgeController` : transmission du champ `description` dans le tableau `levels`
- `badge-card` molecule : bulle 88px avec fill progress/target, chip tokens haut-droite, icône outil haut-gauche, description `line-clamp-2`, état verrouillé/en cours/débloqué ; paramètres `tokens`, `toolIcon`, `toolColor`, `toolBg`, `hexColorInner`, `hexColorOuter`

- **Bulle liquide animée** : remplacement de la bulle statique par un système `badge-bubble`. Améliorations visuelles : halo centré sur la bulle, lueur `--bubble-glow` couleur outil, 5 points indicateurs de progression dans le header, badge de niveau déplacé dans le corps avec le nom de l'outil — remplissage liquide selon la progression, vagues SVG animées, bulles d'air montantes, reflet spéculaire, texte % central. Controller Stimulus `badge--bubble` lit `level`, `progress`, `target` depuis les data-attributes. Couleurs par palier : bronze → argent → or → platine → diamant
- `assets/controllers/badge/bubble_controller.js` : controller Stimulus avec CSS custom properties (`--fill`, `--accent`, `--accent-2`, `--base`) et génération dynamique des bulles d'air (`#spawnAirBubbles`)
- `assets/styles/pages/badges.css` : système CSS complet de la bulle liquide (keyframes `badge-bubble-pop`, `badge-wave-move`, `badge-air-rise`)

- **Badges hexagonaux** : redesign visuel des badges en hexagones à deux couches (outer ring + inner fill), palette 5 niveaux bronze/argent/or/platine/diamant via l'atom `hexagon-badge`. Utilisé dans les bulles par outil (`badge/index.html.twig`) et les cartes de badges (`badge-card`)
- **Récompense quotidienne** : 500 tokens réclamables une fois par jour via un bouton dédié (`DailyRewardService`). Streak counter avec 12 paliers Leitner (J+1 → J+365), chaque palier octroie un badge et des tokens bonus
- `hexagon-badge` atom : CSS `clip-path` hexagone, deux couches colorées, paramètres `icon`, `level` (1–5), `colorOuter`, `colorInner`, `size` (sm/md/lg), `locked`
- `daily-reward-card` molecule : card streak + bouton de réclamation + countdown, Stimulus controller `daily_reward_controller.js`
- `DailyRewardService` : `canClaim()`, `getStatus()` (DTO `DailyRewardStatus`), `claim()` (DTO `DailyRewardResult`) — logique UTC, flush unique, attribution badge via `BadgeAwarderService`
- `DailyRewardController` : `POST /api/daily-reward/claim` (firewall `api_daily_reward`, session-based stateless)
- `BadgeConfig` : `DAILY_LOGIN_TOKENS = 500`, `LOGIN_STREAK_MILESTONES` (12 paliers), `loginStreakSlug()`, `getNextStreakMilestone()`, `getDefinition()` étendu au pattern `login_streak_N`
- `User` entity : champs `loginStreak` (int) et `lastClaimedAt` (?DateTimeImmutable) + getters/setters
- Migration Doctrine : colonnes `login_streak` + `last_claimed_at` sur la table `user`
- Section "Streak de connexion" sur `/settings/badges` : 12 badge-cards Leitner couleur or
- `daily-reward-card` intégrée sur le dashboard (`home/index.html.twig`) et la page badges
- Firewall `api_daily_reward` dans `security.yaml` : session-based stateless pour `/api/daily-reward/`

### Corrigé
- `BadgeController::markShown` : comparaison d'entités Doctrine par `getId()` (au lieu de référence) pour éviter les faux IDOR sur firewalls stateless
- `BadgeAwarderService` : utilisation de `$em->getReference()` pour les entités `User` rechargées par le UserProvider dans les firewalls stateless

### Ajouté
- **Pages légales** : CGV, Mentions légales et Politique de sécurité accessibles sans authentification. Layout commun : sommaire sticky à gauche + accordéon collapsible à droite. Responsive (sommaire masqué → accordion mobile en haut). Contenu intégral extrait des documents officiels (Bubul.app, 27 mai 2026)
- `LegalController` : 3 routes GET publiques — `/cgv`, `/mentions-legales`, `/politique-securite`
- `templates/legal/` : `_layout.html.twig` (embed + block), `_macros.html.twig` (macro accordion_section), et les 3 pages
- Footer : liens légaux pointent désormais vers les vraies routes (`app_legal_mentions`, `app_legal_cgv`, `app_legal_securite`)
- `security.yaml` : règles `PUBLIC_ACCESS` pour les 3 routes légales
- `landing.css` : styles `.legal-content` pour la typographie des sections accordéon

### Modifié
- **Header mobile** : menu outils en grille 2 colonnes (4 rangées × 2 colonnes) au lieu de 8 lignes verticales — hauteur divisée par ~2, icônes réduites (w-6), texte `text-xs`
- **Pages légales** : contenu reproduit lettre pour lettre depuis les fichiers source .docx — corrections CGV (descriptions outils exactes, bullets art. 3.1 complets, modalités B2B en tableau, art. 9.4 Serenia complet), mentions légales (adresse CAEN uppercase, note CNIL sec. 8, phrase finale sec. 9, phrase + bullet manquants sec. 12), politique de sécurité (OVH Graveline, 5e bullet sec. 16, tableau sous-traitants 5 colonnes avec Andrew Mahé)

### Supprimé
- Moodia : placement automatique des images par l'IA retiré — les images du PDF sont maintenant ajoutées manuellement par l'utilisateur via le picker d'images de l'éditeur
- Product tour et wizard onboarding legacy (`RegistrationWizardController`, `TourController`, `onboarding_controller.js`, `product_tour_controller.js`, `registration_wizard_controller.js`, templates associés) — remplacés par le nouveau tunnel `/register` + `/welcome`
- Variable `hasSeenTour` supprimée du template `home/index.html.twig` (plus transmise depuis `DashboardController`)

### Corrigé
- `DashboardController` : redirect vers `app_welcome_birthday` au lieu de `app_welcome_wizard` quand l'onboarding n'est pas complété
- `DashboardController` : suppression de la variable `hasSeenTour` transmise au template (redondante avec `isOnboardingCompleted()`)

## [2.38.1] - 2026-05-27

### Modifié
- **Slidia — Bouton IA** : la popup "Générer une image" convertie de modale centrée en **popover ancré à gauche** du bouton ✨. Positionné via `getBoundingClientRect()` avec contrainte viewport, flèche dynamique alignée sur le bouton, backdrop transparent pour fermeture au clic extérieur, sans overlay opaque

## [2.38.0] - 2026-05-26

### Ajouté
- **Slidia — Génération d'image IA depuis l'éditeur** : bouton "✨ Générer avec l'IA" sur les placeholders `pic` vides du panneau droit. Ouvre un panneau inline avec prompt pré-rempli (extrait du texte de la slide) + sélecteur de style (Photo / Illustration / Abstrait). La génération délègue à `PixiaService` (DALL-E, qualité `std`), sauvegarde l'image comme `PixiaImage` visible dans la galerie Pixia
- `PixiaService::saveImage()` : extraite de `PixiaController` comme méthode publique réutilisable (validation base64, vérification `file_put_contents`, construction entité, persist+flush)
- `POST /api/slidia/generate-image` (`app_slidia_api_generate_image`) : endpoint JSON qui valide le prompt (≥ 10 chars), le style, l'appartenance de la présentation, les tokens (9 000 min, qualité `std`) et délègue la génération + sauvegarde à `PixiaService`
- `API_ENDPOINTS.SLIDIA.GENERATE_IMAGE` : constante JS centralisée
- `SlidiaApiGenerateImageTest` : 4 tests fonctionnels (auth, prompt vide 400, 403 autre utilisateur, succès 200)

### Modifié
- `PixiaController::generate()` et `generateFromPdf()` : utilisent désormais `$pixiaService->saveImage()` au lieu de la méthode privée supprimée
- `SlidiaApiController` : injection de `PixiaService`, `UserActivityService`, `LoggerInterface`
- Éditeur Slidia (`editor_controller.js`) : bug suppression image corrigé (`#removeImageField` appelle `#renderRight()` pour reconstruire la closure `imgData`), protection concurrence `#isGenerating`, capture de `#currentIndex` au moment du clic pour éviter la corruption de données en navigation async

## [2.37.0] - 2026-05-26

### Ajouté
- **Contact — double opt-in** : le message n'est transmis à l'équipe qu'après que l'utilisateur a cliqué le lien de confirmation reçu par email (preuve que l'adresse lui appartient)
- **Contact — AJAX sans rechargement** : soumission via fetch + Stimulus controller `landing--contact-form` ; le formulaire s'anime vers un état de succès sans navigation
- **Contact — erreurs inline** : les erreurs de validation s'affichent champ par champ sans rechargement de page
- `ContactEmailService::sendConfirmationRequest()` : nouveau, envoie l'email de demande de confirmation avec lien signé (TTL 24h, stocké en cache Symfony)
- `GET /contact/confirmer/{token}` (`app_contact_confirm`) : valide le token en cache, envoie la notification à l'équipe + l'accusé de réception à l'utilisateur
- Templates `landing/contact/confirmed.html.twig` et `confirm_error.html.twig` : pages post-confirmation
- `emails/contact/confirmation_request.html.twig` : email double opt-in envoyé à l'utilisateur
- `assets/controllers/landing/contact_form_controller.js` : Stimulus controller AJAX + animation

### Modifié
- `ContactController` : détecte les requêtes AJAX (`X-Requested-With`) et retourne JSON ; flow de soumission modifié (email confirmation request d'abord, notification après clic)
- `security.yaml` : règle `^/contact$` → `^/contact` pour couvrir `/contact/confirmer/`
- Tests : +2 nouveaux tests (AJAX valid, AJAX invalid, confirm token invalide) → 7 tests, 15 assertions

## [2.35.5] - 2026-05-26

### Modifié
- **Page contact** (`feat/contact-form`) : amélioration du copy (H1 "Nous sommes là pour vous accompagner.", sous-titre avec délai de réponse 24h, checklist et cards info retravaillées), card formulaire renommée "Parlez-nous de votre projet", padding mobile réduit (`p-5 sm:p-7 lg:p-8`), section hero responsive (`pt-24 pb-16 lg:pt-32 lg:pb-20`)

### Supprimé
- **Page contact** : mention "🔒 Vos données sont protégées · Politique de confidentialité" retirée (lien non fonctionnel)
- **Page contact** : section "Ils nous font confiance" retirée (pas encore de clients)

## [2.35.4] - 2026-05-23

### Corrigé
- **`app:b2c:renew-tokens --migrate`** : `sleep(5)` déplacé dans un bloc `finally` — s'exécute désormais même en cas d'échec d'envoi d'email, évitant les rafales vers le provider et les rate limits en cascade ; ajout d'un `$io->warning()` pour rendre les échecs visibles dans le terminal

## [2.35.3] - 2026-05-23

### Ajouté
- **Atomic Design — Emails** : création de `templates/emails/_atoms/` avec 4 atoms inline-CSS (logo, button, warning-box, divider) — les 22 templates existants ont été migrés pour les utiliser (plus de code HTML dupliqué)
- **`feedback-card` molecule** : nouveau paramètre `tag` — affiche la valeur clé (ex : nom du type de notification) en badge/chip coloré avec dot indicator, aligné sur la variante (success/error/warning/info)

### Modifié
- **Page désabonnement** (`profile/unsubscribe_done.html.twig`) : redesign complet — fond dégradé subtil (bleu → violet), badge `typeLabel` en chip vert, description courte left-aligned, boutons full-width responsive (colonne sur mobile, ligne sur desktop)
- **`feedback-card` molecule** : description left-aligned quand un `tag` est présent (améliore la lisibilité multi-lignes) ; boutons `flex-1` au lieu de `w-auto` pour un rendu symétrique

## [2.35.2] - 2026-05-23

### Corrigé
- **B2C — Renouvellement tokens** : correction de l'erreur DQL Doctrine `"u.subscription IS NULL"` sur le côté inverse d'une relation `OneToOne` — remplacé par un `LEFT JOIN` explicite dans `findFreemiumB2CDueForRenewal()` et `findAllFreemiumB2CForRepair()` (`UserRepository`)
- **B2C — Renouvellement tokens, Passe 4** : `addTokens()` (top-up) remplacé par `resetToMonthlyAllocation()` — l'email de renouvellement était silencieux si le solde était déjà au plafond (`actualAdded = 0`) ; désormais le solde est toujours remis à exactement 25 000 tokens et l'email est toujours envoyé

### Ajouté
- **`UserToken`** : nouvelle méthode `resetToMonthlyAllocation(int $amount)` — remet le solde à exactement `$amount` tokens disponibles, idempotente, adaptée aux recharges freemium
- **`UserRepository`** : nouvelle méthode `findAllB2CWithoutSubscription()` — retourne tous les utilisateurs B2C sans `UserSubscription` (y compris sans `UserToken`), utilisée par `--migrate`
- **`app:b2c:renew-tokens --migrate`** : nouvelle option one-shot pour migrer les anciens comptes B2C créés avant le système d'abonnement — crée la `UserSubscription` (freemium/inactive), remet les tokens à 25 000 et envoie l'email de renouvellement

## [2.35.1] - 2026-05-22

### Corrigé
- **Slidia — Éditeur, mode audience** : `requestFullscreen()` était encore appelé dans `openPresenter()` (résidu de l'ancienne approche Windows), ce qui agrandissait brutalement la page sans afficher l'overlay — supprimé : seul le clic sur l'overlay déclenche maintenant le fullscreen via `startPresentation()`

## [2.35.0] - 2026-05-22

### Ajouté
- **Slidia — Éditeur, mode audience** : overlay "Passer en plein écran ?" affiché après clic "Présenter" — l'utilisateur clique le bouton de l'overlay pour déclencher `requestFullscreen()` avec son propre geste direct, garantissant le plein écran sur tous les navigateurs et plateformes (Windows/Mac/Linux) à chaque fois

### Amélioré
- **Slidia — Éditeur, mode audience** : `exitPresenterMode()` cache automatiquement l'overlay fullscreen si l'utilisateur quitte avant d'avoir fait son choix

## [2.34.9] - 2026-05-22

### Ajouté
- **Slidia — Mode audience (éditeur)** : bouton "Plein écran" dans la topbar audience — geste utilisateur dédié pour `requestFullscreen()`, seule approche fiable cross-plateforme (Windows/Mac/Linux)

## [2.34.8] - 2026-05-22

### Amélioré
- **Slidia — Éditeur** : bouton "Présenter" redesigné — icône `presentation-graph` (Solar Icons) + dégradé primary→secondary cohérent avec le reste de la topbar (l'icône `play` n'existait pas dans le set)

## [2.34.7] - 2026-05-22

### Amélioré
- **Slidia — Éditeur, mode audience** : fullscreen automatique sur la fenêtre éditeur au clic "Présenter" (`requestFullscreen` sur `document.documentElement`) ; sortie du fullscreen synchronisée avec la sortie du mode audience (`exitPresenterMode` + écoute `fullscreenchange`)

## [2.34.6] - 2026-05-22

### Amélioré
- **Slidia — Mode présentation (popup)** : suppression du `max-width: 1280px` sur le presenter — les colonnes utilisent toute la largeur de la fenêtre, ce qui maximise la taille de la slide sur grands écrans ; les slides restent plafonnées à 1920×1080
- **Slidia — Mode présentation (popup)** : fullscreen automatique à l'ouverture de la popup — la fenêtre passe en plein écran dès le chargement (si autorisé par le navigateur)

## [2.34.5] - 2026-05-22

### Corrigé
- **Slidia — Mode présentation (popup)** : wrap "slide suivante" passé en `aspect-ratio: var(--slide-ratio)` — même ratio que la slide courante, taille calée sur la largeur de la colonne (plus de hauteur fixe 150px)
- **Slidia — Mode présentation (popup)** : layout responsive — sous 820px, les deux colonnes s'empilent verticalement avec scroll vertical sur le presenter ; les notes sont limitées à 30vh pour rester visibles

## [2.34.4] - 2026-05-22

### Corrigé
- **Slidia — Mode présentation (popup)** : ratio de la slide courante dynamique — le wrap s'adapte automatiquement au ratio du masque PowerPoint (16:9, 4:3, etc.) via `--slide-ratio` injecté depuis Twig en EMU, au lieu du `aspect-ratio: 16/9` hardcodé

## [2.34.3] - 2026-05-22

### Corrigé
- **Slidia — Mode présentation (popup)** : suppression du bouton "Swap" dans la topbar de la vue intervenant — la vue est désormais unidirectionnelle (intervenant uniquement)
- **Slidia — Mode présentation (popup)** : bande de miniatures retirée de la vue intervenant (layout simplifié)
- **Slidia — Mode présentation (popup)** : wrap de la slide courante passé en `aspect-ratio: 16/9` (`flex: 0 0 auto`) pour éliminer les barres noires au-dessus et en dessous de la slide
- **Slidia — Mode présentation (popup)** : layout 1:1 (slide + panneau droit) plafonné à 1280px de large, slide courante centrée verticalement dans sa colonne
- **Slidia — Mode présentation (popup & audience)** : slides plafonnées à `max-width: 1920px; max-height: 1080px` (caps fixes, ratio garanti dans tous les navigateurs)
- **Slidia — Vue audience (éditeur)** : caps 1920×1080 restaurés après régression

## [2.34.2] - 2026-05-22

### Corrigé
- **Slidia — Mode présentation (popup)** : responsive aspect-ratio — la slide s'adapte à la taille réelle de la fenêtre en maintenant son ratio 16:9 via `aspect-ratio` inline, sans cap fixe 1920×1080
- **Slidia — Mode présentation (popup)** : miniatures de la bande strip rendues avec le contenu réel de chaque slide (via `buildSlideEl` + `transform: scale()`), au lieu du simple fond PNG du template
- **Slidia — Mode présentation (popup & audience)** : suppression de tous les indicateurs éditeur (bordures dashed, labels "IMAGE"/"TEXTE", zones masque, hover outlines, cursor: pointer) — la vue présentation est désormais purement visuelle et read-only
- **Slidia — Vue audience (éditeur)** : même responsive aspect-ratio appliqué (max-height: 100%)

## [2.34.1] - 2026-05-22

### Corrigé
- **Slidia — Mode présentation** : design des slides fidèle à l'éditeur dans la popup (les styles `.sde-slide`, `.sde-ph` etc. n'étaient pas chargés — extraits dans `slide_render.css` chargé en bundle isolé)
- **Slidia — Mode présentation** : slide courante contrainte dans son conteneur (max-width + max-height + width:auto pour respecter le ratio sans débordement)
- **Slidia — Mode présentation** : notes de l'intervenant agrandies (22px, flex:1) pour être lisibles debout à distance
- **Slidia — Mode présentation** : nouveau layout — slide suivante + notes dans la colonne droite, slide courante prend toute la colonne gauche
- **Slidia — Mode présentation** : plein écran automatique à l'ouverture + bouton ⛶ pour basculer manuellement

## [2.34.0] - 2026-05-22

### Ajouté
- **Slidia** : mode présentation avec vue intervenant (popup) et vue audience
  - Bouton "Présenter" dans l'éditeur (topbar)
  - Éditeur se transforme en vue audience (fond noir, slide plein écran, navigation clavier)
  - Popup `/slidia/{uuid}/present` : vue intervenant avec timer, slide courante, notes, bande de miniatures
  - Synchronisation temps réel via BroadcastChannel (`slidia-present-{uuid}`)
  - Navigation clavier : ← → Espace pour avancer, Échap pour quitter
  - Swap des rôles : chaque fenêtre peut devenir audience ou intervenant
  - Détection de déconnexion via heartbeat (badge si > 9s sans signal)
  - Module partagé `slide_render.js` extrait pour réutilisation éditeur ↔ popup

## [2.33.1] - 2026-05-22

### Ajouté
- Slidia : styles CSS du mode audience dans `editor.css` (`.sde--audience`, `.sde-audience-topbar`, masquage panneaux latéraux/notes/header en mode projection)
- Slidia : feuille de styles dédiée `present.css` pour la popup de présentation (reset global, layout topbar/presenter/strip/audience)
- Slidia : point d'entrée Webpack `slidia_present` (`present_entry.js`) — isole les règles globales (`* { margin:0 }`, `body { background:#111 }`) du bundle `app` principal
- Slidia : `templates/slidia/present.html.twig` charge désormais `encore_entry_link_tags('slidia_present')` et `encore_entry_script_tags('slidia_present')` au lieu du bundle `app`

## [2.33.0] - 2026-05-22

### Ajouté
- Slidia : modale "Bibliothèque d'images" à 4 onglets (Importer, URL, Pixia, Monalisia) — remplace le picker d'images inline dans l'éditeur de slides
- Slidia : intégration Pixia — les images générées par Pixia sont accessibles directement depuis l'éditeur de slides sans quitter la page

## [2.32.2] - 2026-05-22

### Corrigé
- Slidia : erreur "PowerPoint a détecté un problème dans le contenu" sous Windows — les fichiers `ppt/slides/_rels/slide*.xml.rels` du template n'étaient pas supprimés lors du nettoyage (regex incorrect : `_rels/` absent du pattern), laissant des `.rels` orphelins quand le nombre de slides générées était inférieur au nombre de slides du template
- Slidia : les `notesSlide*.xml` et leurs `_rels` du template sont désormais aussi supprimés lors du nettoyage (même cause : références orphelines vers des slides supprimées)
- Slidia : les entrées `notesSlide+xml` dans `[Content_Types].xml` sont maintenant nettoyées en même temps que les slides
- Slidia : nom de fichier PPTX téléchargé — les accents étaient remplacés par des doubles underscores (`é` → `__`) car `preg_replace` sans flag `/u` traitait chaque octet UTF-8 séparément ; corrigé via `SluggerInterface::slug()` qui translitère les accents avant de sanitiser

## [2.32.1] - 2026-05-22

### Corrigé
- Cron `app:b2c:renew-tokens` : ajout de la **Passe 4** pour recharger les utilisateurs B2C sans `UserSubscription` (anciens comptes créés avant le système d'abonnement — ~262 utilisateurs affectés)
- `UserToken::addTokens()` : formule corrigée — applique exactement `monthlyTokens` et plafonne à `maxStorage` (au lieu de top-up jusqu'au plafond en ignorant le montant)
- `UserRepository` : ajout de `findFreemiumB2CDueForRenewal()` et `findAllFreemiumB2CForRepair()` pour cibler les freemiums purs sans subscription
- Mode `--repair` : rattrapage multi-mois pour tous les utilisateurs B2C (avec et sans subscription) — calcule les mois manqués depuis `lastAllocationAt` ou `createdAt`
- Cron : envoi d'un email de confirmation à l'utilisateur à chaque recharge (Passe 4 et --repair)
- Commande refactorisée en méthodes privées (`runPasseActive`, `runPasseCanceled`, `runPasseInactive`, `runPasseFreemiumPur`, `sendAnnualReminders`, `applyCatchup`) pour la lisibilité

## [2.32.0] - 2026-05-22

### Ajouté
- Token gate sur tous les outils : Moodia, Monalisia, Castia, Pixia — boutons d'accès grisés avec tooltip quand le solde est insuffisant (header + état vide)
- `AppLimits::PIXIA_MIN_TOKENS = 2_000` — seuil d'accès Pixia basé sur le preset qualité minimale
- Molecule `empty-state` étendue avec `actionDisabled` / `actionTooltip` pour gérer les états d'accès restreint

## [2.31.0] - 2026-05-22

### Ajouté
- Slidia : boutons de création grisés avec tooltip d'explication quand le solde est insuffisant (header, carte de lien, état vide) — sans rechargement supplémentaire grâce aux données déjà présentes dans `dashboardData`

## [2.30.0] - 2026-05-22

### Ajouté
- Slidia : gate de tokens sur `GET /slidia/create` et `GET /slidia/{uuid}/generate` — redirection vers `/slidia` avec flash error si solde insuffisant
- Slidia : méthode privée `denyIfInsufficientTokens()` (DRY) — factorise le check token+redirect pour les 3 points d'entrée

### Corrigé
- Slidia : `createSubmit` redirigait vers `/slidia/create` en cas de tokens insuffisants — corrigé vers `/slidia`

## [2.29.0] - 2026-05-22

### Modifié
- Pixia : migration vers `gpt-image-1.5` pour tous les presets de qualité (suppression de `gpt-image-1`)
- Pixia : réduction à 3 niveaux de qualité (suppression du preset `hd`) — "Basique", "Standard", "Premium"
- Pixia : labels et sous-labels des presets harmonisés ("Un peu flou", "Correcte", "Full HD")
- Pixia : preset par défaut mis à jour de `hd` → `ultra` (Full HD)
- Pixia : correction du bug Twig "Key 'badge' does not exist" dans le picker de qualité
- Pixia : CSS des barres de signal réajusté pour 3 presets (1 barre / 3 barres / 4 barres)
- Pixia : remplacement des barres de signal par des icônes SVG expressives — halos (flou), cadre photo (standard), étoile 4 branches (Full HD)
- Pixia : refonte du style picker — thumbnails gradient avec icônes SVG thématiques (éclair/feuille/œil/étoile/crayon) sur fond coloré
- Pixia : différenciation des coûts par preset qualité : low→gpt-image-1 low (~2K), std→gpt-image-1 medium (~9K), ultra→gpt-image-1.5 (~27K)
- Pixia : `minTokens` par preset dans `PixiaConfig` — gate check et fallback de facturation alignés sur le coût réel de chaque niveau
- Pixia : suppression de `AppLimits::PIXIA_MIN_TOKENS` et `PIXIA_PDF_MIN_TOKENS` (remplacés par `$preset['minTokens']`)

## [2.28.0] - 2026-05-21

### Ajouté
- Pixia : champ `title` (nullable, max 100 chars) dans la création d'une image — permet de nommer l'image de façon indépendante du prompt
- Pixia : route `POST /pixia/{id}/rename` + méthodes JS `openRenameModal` / `closeRenameModal` / `confirmRename`
- Pixia : modal de renommage (réutilise `_organisms/rename-modal/`) avec pré-remplissage du titre courant
- Pixia lightbox : bouton "Renommer" + zone "Description" scrollable affichant le prompt utilisé pour la génération
- Monalisia lightbox : zone "Description" scrollable affichant le prompt de création de l'infographie
- Migration `Version20260521120000` — ajout colonne `title VARCHAR(100) DEFAULT NULL` dans `pixia_image`

### Amélioré
- Pixia : entité `PixiaImage` — getter/setter `title`, helper `getDisplayName()` (titre si défini, sinon aperçu prompt)
- Pixia : `_image_card.html.twig` — nouveaux attributs `data-image-title`, `data-image-rename-csrf`, `data-image-rename-url`
- Pixia lightbox : titre affiché en `lightboxTitle` (remplace le prompt en heading), prompt affiché en `lightboxPrompt` (description scrollable)
- Pixia : `generator_controller.js` — nouvelles targets `titleInput`, `lightboxTitle`, `lightboxRenameBtn`, `renameModal`, `renameInput`, `renameId`, `renameCsrf`
- Monalisia : `_infographic_card.html.twig` — ajout `data-infographic-prompt` pour exposition dans la lightbox

## [2.27.0] - 2026-05-21

### Ajouté
- Organism `_organisms/ai-lightbox/` — lightbox générique réutilisable (image + panneau méta + actions) avec blocs Twig surchargeables (`meta`, `actions`) et variable CSS `--lightbox-primary-rgb`
- Pixia lightbox : panneau complet — badges style + format, prompt, date, tokens + énergie, boutons Télécharger / Supprimer
- Pixia : modal de confirmation de suppression (pattern Monalisia) avec animation de fondu-disparition de la carte
- Pixia : téléchargement automatique de l'image après génération (`_triggerDownload`)

### Amélioré
- Pixia : carte image simplifiée — suppression du hover overlay avec boutons, click direct = lightbox (pattern Monalisia)
- Pixia : galerie — s'affiche correctement après la première génération quand elle était vide (`display:none` retiré dans `_addCard`)
- Pixia : lightbox enrichie — `lightboxFormat`, `lightboxTokens`, `lightboxEnergy`, `lightboxStats` comme nouvelles targets Stimulus
- Pixia : `openLightbox()` peuple désormais format + stats tokens/énergie depuis les `data-image-*` de la carte
- Pixia carte — nouveaux attributs `data-image-format`, `data-image-tokens`, `data-image-energy`
- `PixiaController` — `downloadUrl` ajouté dans les réponses JSON de `generate()` et `generateFromPdf()`

## [2.26.0] - 2026-05-21

### Amélioré
- Pixia : section qualité redesignée — 4 boutons en ligne avec indicateur visuel "barres de signal" (hauteur croissante) ; labels visuels "Un peu flou / Correcte / HD bas / Full HD" au lieu des labels techniques
- Pixia : contenu — limite réduite de 4 000 à 1 000 caractères (cohérent avec la précision nécessaire à la génération image)
- Pixia : panel PDF adopte le pattern Monalisia — aperçu persistant après sélection (nom, taille, bouton supprimer) au lieu du nom dans la dropzone
- Pixia : `PixiaConfig` — ajout du champ `visualLabel` sur chaque préset qualité

## [2.25.13] - 2026-05-21

### Corrigé
- Castia : modal de suppression — alignement sur le design standard (titre en gras dans la phrase, suppression de la box stylée custom)

## [2.25.12] - 2026-05-21

### Ajouté
- Monalisia lightbox : bouton "Supprimer" ouvre désormais la modal de confirmation au lieu du `confirm()` natif

### Corrigé
- Monalisia lightbox : modal de suppression passe au-dessus de la lightbox (`z-index: 110`)
- Monalisia lightbox : suppression fluide — card disparaît en fondu sans rechargement (header `X-Requested-With` manquant + sélecteur `id="infographic-{id}"` corrigé)

## [2.25.11] - 2026-05-21

### Corrigé
- Tokens : `format_tokens` et `format_energy` affichent désormais le signe négatif quand le solde est négatif
- Tokens B2C : `TokenService::addTokens()` plafonne le débit au solde disponible — le solde ne peut plus passer en dessous de 0
- Monalisia lightbox : boutons "Renommer" et "Télécharger" utilisent les couleurs de l'outil (dégradé `mnColor`) au lieu du bleu générique

### Supprimé
- Castia : bandeau "Coût estimé : X tokens" retiré de la section Script

## [2.25.10] - 2026-05-21

### Ajouté
- Moodia dashboard : bouton "Renommer" dans le menu contextuel des cards de cours
- Moodia dashboard : modal de renommage avec pré-remplissage du titre, confirmation Entrée, annulation Échap
- Moodia : route POST `/moodia/course/{uuid}/rename` avec vérification ownership + CSRF + titre non vide

### Corrigé
- Moodia dashboard : correction bug CSRF invalide au renommage (valeurs capturées avant `closeRenameModal`)

## [2.25.9] - 2026-05-21

### Corrigé
- Moodia éditeur : modal "Bibliothèque d'images" — logo icône retiré du header
- Moodia éditeur : modal "Bibliothèque d'images" — scroll des images corrigé (structure flex-col + flex-1 sur le body, max-h-[85vh] sur le card ; overflow-hidden parent clippait la scrollbar)

### Modifié
- Monalisia : lightbox — z-index rename modal corrigé (110 > 100), stats tokens/Wh ajoutées, hauteur agrandie (min 580px), ESC séquentiel
- Monalisia : suppression du hover overlay sur les cards (flou, boutons, méta) — un clic ouvre la lightbox qui centralise toutes les actions (Renommer, Télécharger, Supprimer) et informations (nom, format, date)
- Section "Nos Outils" landing : refonte en sticker board — 8 cartes épinglées en positionnement absolu avec rotations initiales par outil, animation de chute staggerée à l'entrée, hover qui redresse + soulève + révèle le bouton "Lancer" via max-height reveal ; couleurs pastel dérivées dynamiquement de la DB (`rgba(hex_to_rgb, 0.26)`) ; responsive flex column < 960px ; touch et prefers-reduced-motion gérés

### Ajouté
- Bento grid outils : refonte complète des cartes — 3 features clés + section "S'intègre avec" (icônes des outils connectés), grille uniforme 3 colonnes (6×lg:col-span-4 + 2×lg:col-span-6), suppression des mockups décoratifs
- Pixia : ajouté au tableau `tools` par défaut de `tools.html.twig`
- Dashboard : carte Pixia ajoutée dans la grille des outils (après Monalisia) avec stats, usage hebdo et état locked
- Pixia : ajout dans le header (mega menu desktop + menu mobile), couleur DB et tagline
- Pixia : ajout dans le hero orbital — 8 nœuds équidistants à 45° (repositionnement de tous les outils existants), SVG connector, couleur DB
- Pixia : ajout dans le footer navigation (colonne Outils) avec badge `.PNG` et couleur DB
- Pixia : ajout dans `tools-ecosystem.html.twig` — couleur DB + route dans la color map
- Bento grid : Pixia et Promptly passent à `lg:col-span-6` (ligne 50/50), SAM passe à `lg:col-span-12` (pleine largeur)
- Hero : description mise à jour "7 outils" → "8 outils IA pédagogiques"
- Pixia : landing page `/outils/pixia` avec hero, problem/solution, 4 features, screenshots, FAQ et écosystème (pattern Monalisia)
- Landing index : Pixia ajouté au bento grid des outils et au tableau des équivalences de la section pricing
- Plan : `computeEquivalences()` retourne maintenant une clé `pixia` (coût par défaut : 25 000 tokens/image)
- Landing Controller : Pixia ajouté aux `defaultUnits` et aux équivalences pricing
- Slidia : validation des tokens avant création de présentation (minimum `SLIDIA_GENERATE_MIN_TOKENS`)
- Slidia : déduction des tokens réels (usage API OpenAI) après génération
- Slidia : déduction tokens sur `generateNotes` (amélioration de notes de slide)
- Slidia : affichage tokens consommés et énergie (Wh) sur chaque carte de présentation
- Slidia : badges tokens/WH visibles sur mobile dans le header
- Pixia : couleurs dynamiques depuis la BDD via `tool_color_details['pixia']` — boutons, icônes, étapes, textarea, dropzone, style picker, spinner
- Moodia dashboard : mise à jour des compteurs de filtre après suppression d'un cours, et rechargement automatique si la liste devient vide
- Moodia export MBZ : conversion du format blocks[] (JSON) en HTML avant export — les pages avec images Pixia s'affichent correctement dans Moodle
- Moodia éditeur : alignement d'image via les boutons overlay mis à jour directement dans le DOM (sans re-render contenteditable) — highlight du bouton actif en place
- Moodia éditeur : tooltips (`data-tooltip`) sur tous les boutons de la barre d'outils (remplacement du `title` natif)
- Moodia éditeur — picker d'images : onglet "Images du cours" en premier, inclut les images Pixia/Monalisia déjà insérées dans les pages + message vide amélioré

### Corrigé
- Moodia export MBZ : activités de type "Étiquette" (`label`) exportaient le JSON brut `[{"type":"text"...}]` au lieu du HTML — pipeline `blocksToHtml` + `convertEduBlocksToInlineStyles` appliquée
- Moodia export MBZ : activités de type "Discussion" (`forum`) et "Devoir" (`assignment`) exportaient le JSON brut au lieu du HTML — même pipeline appliquée (`transformForumActivity`, `transformAssignmentActivity`)
- Moodia éditeur : figures Pixia/Monalisia imbriquées dans un `<p>` par le browser (comportement contenteditable) correctement extraites par `_serializeEditorToBlocks` — les boutons/poignées de l'éditeur ne sont plus exportés vers Moodle
- Moodia export MBZ : sanitisation côté serveur des figures éditeur dans les blocs texte (`sanitizeEditorFigures`) — protection défensive pour les cours déjà enregistrés incorrectement
- Moodia export MBZ : les titres `h2`/`h3` n'héritent plus de la couleur du thème Moodle — styles inline ajoutés (`color: #1E293B`) en cohérence avec l'éditeur
- Moodia export MBZ : les `blockquote` exportent désormais avec le style complet de l'éditeur (bordure violette, fond lilas, texte italique)
- Moodia éditeur : race condition export corrigée — timer `scheduleSave` annulé + attente fin de sauvegarde avant stream MBZ (l'alignement d'image modifié est garanti en base)
- Moodia export MBZ : figures `ImagePlacementService` (ancien format plein-écran centré) converties en `float:left 42%` identique au rendu éditeur — affecte tous les cours existants et nouveaux
- Monalisia, Castia, Pixia : remplacement du magic number `* 0.0003` par `energyConsumptionWh` stocké en base
- Uniformisation du pattern tokens/énergie sur tous les outils (référence : Moodia)
- Moodia — éditeur : affichage "Aucun contenu" et "< 1 min" corrigé après migration au format `blocks[]`
- Moodia — éditeur : description visible sur les activités audio
- Moodia — export MBZ : affichage d'un avertissement amber au lieu d'une erreur bloquante si tokens insuffisants pour les audios
- Moodia — éditeur : overlay images remplacé par une pillule non-intrusive en bas de l'image
- Sidebar : alignement du bouton Promptly corrigé (`gap-4`, icône 20 px)
- Pixia : images de style corrigées (gradients CSS, sans fichiers image manquants)

## [2.22.0] - 2026-05-19

### Ajouté
- **Préférences de notifications email** : les utilisateurs peuvent activer/désactiver chaque type de notification depuis Paramètres → Notifications par email.
- **Désabonnement par lien** : chaque email de recharge mensuelle contient un lien de désabonnement signé (HMAC-SHA256 stateless, sans table de tokens).
- **Email recharge mensuelle redesigné** : affichage proéminent du nombre de tokens crédités, objet de l'email incluant le montant, lien de désabonnement en pied.
- `Settings.emailTokensRenewed` : préférence bool (défaut `true`) pour la notification de recharge mensuelle.
- `EmailUnsubscribeController` : route publique `GET /email/unsubscribe/{userId}/{type}/{token}`.
- Migration `Version20260519000001` : colonne `email_tokens_renewed` sur la table `settings`.

## [2.21.3] - 2026-05-19

### Corrigé
- **Slidia — Scroll-to-top en édition** : suppression de `#resizeBrief()` qui déclenchait un reflow de page à chaque frappe. Textarea fixe `h-[520px] overflow-y-auto`.
- **Slidia — Coins rich editor tronqués** : `bg-gray-50` déplacé du toolbar vers le wrapper parent ; `border-radius` de l'élément clip son propre fond sans `overflow:hidden`, préservant le focus ring et les dropdowns d'alignement.
- **Slidia — Responsive generate** : les 3 cards mockup passent en `max-w-[280px]` sur mobile.

### Modifié
- **Slidia IA — Prompt plan** : hiérarchie documentaire (couverture → transitions → contenu → conclusion), principe fond vs forme, variété visuelle renforcée (accroche/citation/données obligatoires sur toute présentation ≥ 6 slides).
- **Slidia IA — Prompt contenu** : règle `<strong>` affinée (pattern `Terme : description` uniquement), format `transition` limité à 1 phrase, règle d'intégrité du plan.

## [2.21.2] - 2026-05-19

### Ajouté
- `app:b2c:renew-tokens --repair` : répare les soldes B2C affectés par le bug de recharge.
  Ramène chaque utilisateur dont `lastAllocationAt < aujourd'hui` exactement à
  `plan.monthlyTokens` (top-up si insuffisant, réduction si excédentaire).
  `lastAllocationAt` n'est pas modifié — le cycle de 30 jours reprend normalement.
  Supporte `--dry-run`. Envoie un rapport email à l'issue.

## [2.21.1] - 2026-05-19

### Corrigé
- **Bug critique** `UserToken::addTokens()` : la comparaison du plafond se faisait sur
  `tokenUsable` brut au lieu du solde réel (`tokenUsable - tokenUsed`). Résultat : les
  recharges CRON ne passaient jamais après le premier mois (tokenUsable ≥ maxStorage même
  si l'utilisateur avait tout consommé). Remplacé par une logique top-up : on calcule
  exactement ce qu'il faut ajouter pour atteindre le plafond depuis le solde actuel,
  y compris en cas de solde négatif.

## [2.21.0] - 2026-05-18

### Ajouté
- Slidia : dropdowns filtres "Catégorie" et "Masque" dans la barre d'outils de la page index
  (même design que l'admin users — pill avec chevron, dropdown radio, pattern `filter-multiselect`)
- Slidia : filtre Masque côté client (via `data-template-id` sur chaque carte de présentation)
- Slidia : filtre Catégorie remplace les pills JS (Turbo.visit, même comportement serveur)

### Supprimé
- Slidia : barre de pills catégorie générée par JS (`filterBar`) remplacée par le dropdown

## [2.20.0] - 2026-05-18

### Ajouté
- Slidia : sélection par masque des layouts utilisables par l'IA — pastille verte/rouge sur chaque
  vignette de la bibliothèque (page index), toggle auto-sauvegardé, minimum 1 masque actif garanti
- Slidia : entité `SlidiaTemplatePreference` + repository + endpoint API toggle
  (`POST /api/slidia/templates/{id}/layout-preferences/toggle`)
- Slidia : lors de la génération, l'IA se base uniquement sur les layouts non-exclus par l'utilisateur

## [2.19.15] - 2026-05-18

### Amélioré
- Slidia IA plan : sélection des masques réorientée vers la cohérence visuelle — 3-5 masques max,
  1 masque contenu principal réutilisé sur 70-80% des slides, masques couverture/conclusion uniques

## [2.19.14] - 2026-05-18

### Amélioré
- Slidia IA : format visuel élevé au rang de contrainte dure dans le prompt contenu — le modèle ne
  peut plus converger vers `mixte` par défaut ni ignorer le format assigné par le plan
- Slidia IA plan : règle de variété renforcée (5 formats différents sur 5 slides consécutives,
  auto-correction si une série homogène est détectée) ; rôle/format liés par des contraintes plus strictes

## [2.19.13] - 2026-05-18

### Corrigé
- Slidia IA : les placeholders image (`pic`) sont maintenant exclus du payload envoyé à l'IA —
  l'IA ne génère que du contenu texte, les zones image restent vides et sont remplies manuellement

## [2.19.12] - 2026-05-18

### Corrigé
- Slidia export PPTX : balises `<strong>` apparaissant comme texte brut dans le fichier exporté —
  la regex de détection HTML dans `PptxBuilder::htmlToOoxmlParagraphs()` ne reconnaissait pas
  `<strong>` (seulement `<b>`, `<i>`, `<u>`, `<s>`), le contenu tombait dans le parser texte brut

### Ajouté
- Slidia éditeur : cliquer sur un placeholder image (dans la preview centrale ou dans le panneau droit)
  ouvre directement le sélecteur de fichier ; curseur `pointer` indique la zone cliquable quand vide

## [2.19.11] - 2026-05-18

### Corrigé
- Slidia éditeur : `<strong>` affiché comme bloc dans la preview et les vignettes — corrigé en
  isolant le contenu texte dans un `div.sde-ph-inner` qui est le seul enfant direct du flex container
  (évite la "blockification" des éléments inline par `flex-direction:column`)
- Slidia éditeur : sauts de ligne parasites après refresh — `white-space:normal` sur le inner div
  empêche les `\n` du HTML sauvegardé (contenteditable) d'être rendus comme sauts de ligne visuels
- Slidia IA : sous-titres et titres de couverture forcés en taille `xs` (1 phrase ≤ 16 mots, zéro HTML)
  pour éviter les pavés de texte dans ces zones structurellement courtes
- Slidia IA : slides de rôle `transition` — corps limité à 1 phrase ou vide

## [2.19.10] - 2026-05-18

### Amélioré
- Message "tokens insuffisants" rendu plus parlant dans tous les outils (Moodia, Serenia,
  Monalisia, Castia) : indique désormais d'attendre le renouvellement ou de passer à une
  formule supérieure (7 occurrences mises à jour : PHP + JS)

## [2.19.9] - 2026-05-18

### Corrigé
- Monalisia : seuils de tokens minimum corrigés — 5 000 pour le mode texte et PDF
  (était 50 000 / 55 000 de façon incorrecte)
- Monalisia : `MONALISIA_EXTRACT_COLORS_TOKENS` mis à 0 (ColorThief est local, aucun appel API)

## [2.19.8] - 2026-05-18

### Amélioré
- Slidia IA : nombre de slides calculé en PHP par `computeTargetSlides()` (ratio mots/slides)
  et transmis comme contrainte stricte `targetSlides` au modèle — élimine le sous-calibrage
  systématique (18 slides pour 10 000 chars → ~30 slides attendues désormais)

## [2.19.7] - 2026-05-18

### Amélioré
- Monalisia : limite du textarea "Décrivez votre infographie" réduite de 5 000 à 1 000 caractères
  pour garantir la qualité de la génération
- Monalisia : mode "Depuis un PDF" — le contenu est synthétisé en silence via GPT-4o-mini en
  une description ≤ 1 000 caractères avant d'être envoyé à DALL-E, sans étape visible pour
  l'utilisateur

## [2.19.6] - 2026-05-18

### Amélioré
- Slidia IA : `AI_BATCH_SIZE` réduit de 40 à 10 — chaque lot est désormais limité à 10 slides
  pour garantir un contenu complet et fiable (le modèle ne raccourcit plus les slides en fin de lot)
- Slidia IA : règles de volume étendues jusqu'à 40+ slides pour les briefs > 1000 mots

## [2.19.5] - 2026-05-18

### Amélioré
- Slidia IA — Phase 1 (plan) : arc narratif obligatoire (couverture → contexte → problème →
  solution → preuve → CTA), nouveaux champs `role` et `format` par slide, règles de volume
  affinées par densité de brief, alternance stricte de formats (jamais 2 identiques consécutifs)
- Slidia IA — Phase 2 (contenu) : transmission de `role` et `format` depuis le plan, remplacement
  de `cy` brut par labels de taille (xs/sm/md/lg/xl), instructions de rédaction par format et
  par rôle, autorisation `<strong>` pour emphase, interdiction explicite des formules creuses

## [2.19.4] - 2026-05-18

### Corrigé
- Slidia : extraction et rendu de 4 nouvelles propriétés visuelles de placeholder dans le preview CSS :
  dégradé (`gradFill` → `linear-gradient`), bordure de forme (`ln` → `border`), marges internes
  du corps de texte (`bodyPr lIns/rIns/tIns/bIns` → `padding`), interligne (`lnSpc` → `line-height`) ;
  appliqués dans l'éditeur (preview central + miniatures) et dans la bibliothèque de masques

## [2.19.3] - 2026-05-18

### Ajouté
- Slidia : alignement horizontal du texte (gauche/centre/droite) dans la toolbar rich-editor — split button avec dropdown
- Slidia : alignement vertical fusionné en split button (haut/milieu/bas) — même pattern que la couleur

### Modifié
- Slidia : les alignements horizontal et vertical sont désormais exportés dans le PPTX (`algn` sur `<a:pPr>`, `anchor` sur `<a:bodyPr>`)
- Slidia : icônes d'alignement vertical redessinées (boîte + contenu positionné) pour les distinguer des icônes d'alignement horizontal

### Corrigé
- Slidia : auto-fit — skip des placeholders avec `clientHeight = 0` pour éviter la réduction excessive de police
- Slidia : ajout du use statement manquant `DOMXPath` dans `TypographyResolver` et `PptxBuilder`
- Slidia : correction fuite listener dans le dropdown des alignements

## [2.19.2] - 2026-05-18

### Fixed
- Slidia : les thumbnails de masques affichent maintenant les shapes décoratives et les fonds
  définis dans les slide layouts et slide masters (contournement du bug LibreOffice qui ne
  rendait pas ces éléments hérités lors de la conversion PPTX→PDF)
- Slidia : la couleur de fond des placeholders (ex : zone de titre avec remplissage teal) est
  maintenant extraite du layout PPTX (`<p:spPr>/<a:solidFill>`) et rendue dans la prévisualisation
  de l'éditeur (preview central et miniatures)

## [2.19.1] - 2026-05-18

### Sécurité / Qualité (Slidia services)
- `PptxBuilder` : correction SSRF — seules les URLs `https://` vers des hôtes publics sont autorisées pour les images distantes ; validation `realpath()` contre la traversée de chemin
- `PptxBuilder` : `uniqid()` remplacé par `bin2hex(random_bytes(8))` pour les noms de fichiers temporaires
- `PptxBuilder` : suppression de `@getimagesize()` — vérification explicite du retour ; correction de l'injection XPath dans `ensureImageContentType()` (itération des nœuds au lieu d'interpolation de chaîne)
- `ThumbnailGenerator` : injection DI de `PptxBuilder` (plus de `new PptxBuilder()`) ; suppression des `@mkdir`/`@rename`/`@unlink` silencieux ; extraction d'une méthode `cleanupDir()`
- `OoxmlHelper` : nouvelle classe utilitaire partagée (`loadXmlFromZip`, `makeXpath`) — élimine la duplication entre `PptxParser` et `PptxBuilder`
- `TypographyResolver` : `@$doc->loadXML()` remplacé par `libxml_use_internal_errors()` explicite
- `SlidiaAiService` : températures et timeouts hardcodés → `OpenAIConfig::SLIDIA_PLAN_TEMPERATURE`, `SLIDIA_PLAN_TIMEOUT`, `SLIDIA_TEMPERATURE`, `SLIDIA_CONTENT_TIMEOUT`
- `SlidiaApiController` : helpers privés `requireUser()`, `parseJsonBody()`, `ensureDirectory()` pour DRY — `json_decode` avec `JSON_THROW_ON_ERROR` ; chemins et types MIME hardcodés → constantes `SlidiaConfig` ; suppression des `@unlink` silencieux

### Ajouté
- `SlidiaConfig` : constantes `TEMPLATES_STORAGE_PATH`, `USER_IMAGES_PATH`, `ALLOWED_IMAGE_MIMES`, `DEFAULT_LANG`, `INITIAL_SLIDE_ID`, `IMAGE_EXTENSIONS`, `IMAGE_MIME_TYPES`, `OOXML_SCHEME_COLOR_MAP`, `DEFAULT_FONT_SIZE`, `DEFAULT_TEXT_COLOR`
- `OpenAIConfig` : constantes `SLIDIA_PLAN_TEMPERATURE`, `SLIDIA_PLAN_TIMEOUT`, `SLIDIA_TEMPERATURE`, `SLIDIA_CONTENT_TIMEOUT`

## [2.19.0] - 2026-05-18

### Ajouté
- Slidia : validation MIME réelle des fichiers `.pptx` uploadés (en plus de l'extension) — constante `SlidiaConfig::ALLOWED_PPTX_MIMES`
- Slidia : blocs SEO (`meta_description`, `og_title`, `og_description`, `twitter_title`, `twitter_description`) sur les 4 pages (index, create, generate, editor)
- Slidia : externalisation des blocs `<style>` inline vers `assets/styles/pages/slidia/{index,create,generate}.css` importés dans `app.css`

### Modifié
- Slidia : remplacement du terme "deck/Decks" par "présentation/Présentations" dans tous les templates Twig et le contrôleur JS (`list_controller.js`)
- Slidia : classe CSS `deck-card` renommée en `sdl-pres-card` dans `_presentation_card.html.twig` et `list_controller.js`
- Slidia : page `generate.html.twig` — couleurs amber hardcodées (`--sd`, `--sd2`, `--sd3`) remplacées par les variables CSS dynamiques `var(--color-primary)` / `var(--color-secondary)` issues de `tool_color_details['slidia']`
- Slidia : messages d'erreur de l'API upload rendus explicites ("Le fichier doit être un .pptx valide", "Fichier trop volumineux. La taille maximum est de 20 Mo.")

## [2.18.5] - 2026-05-11

### Modifié
- SAM : les couleurs `cohorts` (tokens CSS, gradients, ombres) sont désormais surchargées depuis la BDD via `tool_color_details.sam` dans `sam/layout.html.twig` — les 141 usages Tailwind (`bg-cohorts/10`, `text-cohorts`, `border-cohorts`, gradients, etc.) s'adaptent automatiquement sans toucher les templates individuels

## [2.18.4] - 2026-05-11

### Modifié
- Serenia : les tokens facturés à l'utilisateur sont divisés par 10 (`AppLimits::SERENIA_TOKEN_COST_DIVISOR`) — les tokens réels restent stockés sur le message pour le debugging, seule la balance est réduite

## [2.18.3] - 2026-05-11

### Corrigé
- Moodia : les PDF dont le contenu extrait dépasse 50 000 caractères sont maintenant refusés avec un message d'erreur explicite ("Le PDF est trop volumineux") — le cours repasse en statut `draft`, l'extraction des images est court-circuitée, et `canRetry: false` empêche une nouvelle tentative inutile
- Moodia : sécurité supplémentaire dans `CoursePlanGenerator` — le texte source est tronqué à 50 000 caractères via `AppLimits::MOODIA_PLAN_PDF_MAX_LENGTH` avant d'être envoyé à l'IA (filet de sécurité pour les prompts longs)

## [2.18.2] - 2026-05-11

### Modifié
- Slidia liste de présentations : en mode liste sur mobile, les badges (nom du masque + catégorie) sont masqués pour éviter le débordement horizontal — seuls l'icône, le titre, le sous-titre et le bouton menu restent visibles

## [2.18.1] - 2026-05-11

### Modifié
- Slidia bibliothèque (modale) : responsive mobile — empilement vertical des colonnes (`flex-col sm:flex-row`), colonne gauche limitée à 40% de hauteur sur mobile avec scroll interne, padding et titre réduits, sous-titre masqué sur mobile, CTA pleine largeur sur mobile

## [2.18.0] - 2026-05-11

### Ajouté
- Slidia bibliothèque : persistance du dernier masque sélectionné en base (`Settings.last_used_slidia_template_id`, migration `Version20260511000001`, endpoint PATCH `/api/slidia/preferences`) — le masque est présélectionné à la réouverture de la modale
- Slidia bibliothèque : strip de navigation horizontal en bas de la prévisualisation — toutes les dispositions en ligne (cartes égales, miniature si disponible, barre couleur accent sinon), scroll horizontal automatique masqué pour les templates à +30 dispositions
- Slidia bibliothèque : flèches ‹/› superposées sur la slide principale (fond blanc semi-transparent, backdrop-blur), navigation circulaire (dernière disposition → retour à la première)
- Slidia bibliothèque : actions Renommer et Supprimer un masque directement depuis la modale (boutons dans le pied, modales via organismes `rename-modal` et `delete-modal`)
- Slidia page generate : panneau de chargement redesigné — 5 étapes animées avec pills et barre de progression, 3 skeleton cards apparaissant en stagger, orbites flottantes, label d'étape courante

### Modifié
- Slidia bibliothèque : panneau droit redesigné — slide principale centrée (16:9, animation glissement horizontal), remplace le livret vertical avec dots de navigation
- Slidia bibliothèque : navigation circulaire entre dispositions (fin → retour au début, et inversement)
- Slidia bibliothèque : cursor pointer sur tous les éléments interactifs de la modale, bouton fermeture agrandi et assombri

## [2.17.1] - 2026-05-10

### Ajouté
- Slidia `/slidia` : bouton "Ma bibliothèque" remplaçant "Depuis un texte" dans les actions rapides, ouvre une modale de prévisualisation des masques
- Modale bibliothèque : colonne gauche = grille 2 colonnes de masques avec thumbnails, colonne droite = layouts animés du masque sélectionné
- CTA "Créer avec ce masque" → `/slidia/create?template={id}` avec auto-sélection du template au chargement via Stimulus value
- Slidia `/slidia/create` : logo Slidia ajouté à côté du bouton Retour pour harmoniser avec la page index
- Animations : entrée modale slide-up (desktop/mobile), cartes templates/layouts animées séquentiellement, fermeture via Escape ou clic overlay

## [2.17.0] - 2026-05-09

### Supprimé
- Slidia : nettoyage complet — suppression de SlidiaService, PptxBuilder, PptxParser, ThumbnailGenerator, TypographyResolver, SlideBoundaryDetector, SlidiaAiClient, SlidiaSettings, SlidiaSettingsType, SlidiaSettingsRepository, SlidiaTemplateApiController, GenerateSlideImageHandler, GenerateTemplateThumbnailsHandler, editor_controller.js, generator_controller.js, mask_controller.js, create.html.twig, editor.html.twig, editor.css, ainsi que tous les tests Slidia associés
- Slidia : retrait des endpoints slides/images de SlidiaApiController (createSlide, patchSlide, deleteSlide, reorderSlides, generateImage, imageStatus)
- Slidia : retrait du bloc SLIDIA de api-endpoints.js et de l'import editor.css dans app.css
- Slidia : simplification de SlidiaConfig.php aux seules constantes utilisées (5 constantes)
- Settings : retrait de la relation SlidiaSettings et des méthodes associées

### Modifié
- SlidiaController : réduit à index() + stubs de redirection pour create() et editor()
- SlidiaApiController : réduit aux seuls endpoints présentations et catégories

## [2.16.0] - 2026-05-09

### Ajouté
- Slidia : éditeur de slides basé sur les thumbnails PPTX — chaque slide affiche le masque en background-image avec des zones contenteditable positionnées en % depuis les coordonnées EMU
- Slidia : picker de layouts par slide (style PowerPoint) avec aperçu thumbnail, remapping du contenu titre/corps lors du changement
- Slidia : affichage des thumbnails réelles dans la grille de sélection de templates (page Create)
- Slidia : polling asynchrone `/thumbnails-status` après upload d'un template — spinner pendant la génération, refresh auto à réception
- assets/styles/pages/slidia/editor.css : styles `.slide-card`, `.slide-ph` (hint CSS via `data-empty::before`), `.layout-picker`, `.slide-action-btn`

### Supprimé
- Slidia : MaskBasedGeneratorService (1400 lignes) remplacé par PptxBuilder + SlidiaService::prepareLayoutsForAI()
- Slidia : éditeur 3-colonnes (thumbnailPanel / canvasPanel / propertiesPanel) remplacé par liste verticale Stimulus

## [2.15.0] - 2026-05-09

### Ajouté
- Slidia : nouveau moteur de génération IA basé sur les masques OOXML (SlidiaAiClient + SlideBoundaryDetector)
- Slidia : streaming slide-par-slide avec détection de frontières JSON
- Slidia : page Create étape 2 redesignée — champ titre, bouton Exemple, compteur de mots
- Slidia : écran de loading animé avec ghost slides, pills d'étapes et barre de progression
- SlidiaSlide : champ layoutPath (source de vérité pour les layouts)
- OpenAIHttpClient : méthode streamChatCompletion() partagée

## [2.14.3] - 2026-05-05

### Modifié
- Slidia : topbar sticky ajoutée en haut de la page `/slidia` (logo bubble, onglets Présentations/Nouvelle/Bibliothèque, token badge, bouton CTA)
- Slidia : hero editorial sous la topbar ("Vos présentations, en quelques bulles." + stats mono)
- Slidia : suppression de l'ancien header inline (icône + titre + badges + bouton)
- Slidia : `main_class` overridé (`bg-gray-50`, sans padding) — padding géré dans le template
- Slidia : card CSS migré 100% Tailwind avec variants `[.sd-list_&]:` pour le mode liste
- Slidia : `<style>` réduit aux seules animations (`slideUp`, `popIn`) + `.sd-grid`/`.sd-list` (JS-contrôlés)

## [2.14.2] - 2026-05-05

### Modifié
- Slidia : mode liste — card réorganisée en row horizontal (thumbnail 80px carré, icône presentation-graph, title/méta, pill template, bouton `...`)
- Slidia : CSS sémantique (`sd-card-thumbnail`, `sd-card-row`, `sd-card-title-group`, `sd-card-pill-wrap`, `sd-card-btn`) pour les deux modes grille/liste depuis un seul template HTML

## [2.14.1] - 2026-05-05

### Modifié
- Slidia : cards `/slidia` — refonte visuelle (grille CSS `repeat(auto-fill, minmax(280px, 1fr))`, 3 slides empilés, pill masque, badge catégorie séparé, meta mono, badge épingle)
- Slidia : layout page `/slidia` — suppression des rangées scroll horizontal, toutes les sections en CSS Grid
- Slidia : cible `othersSectionTitle` et `othersCount` ajoutées sur la section "Autres" (corrige les erreurs Stimulus sur recherche et suppression)

## [2.14.0] - 2026-05-05

### Ajouté
- Slidia : entité `SlidiaTemplate` — bibliothèque de masques multi-utilisateur (source `user|bubul`)
- Slidia : preset "Bubul Corporate" créé automatiquement en base à la migration
- Slidia : API CRUD templates (`GET /api/slidia/templates`, `POST`, `DELETE`, `GET metadata`)
- Slidia : page `/slidia/create` refondre en tunnel 2 étapes (Masques → Contenu)
- Slidia : upload de masques `.pptx` avec barre de progression XHR
- Slidia : page `/slidia` refondre en vue Netflix par catégories (Favoris, par catégorie, Autres)
- Slidia : éditeur — canvas HTML `contenteditable` positionné d'après la géométrie du masque PPTX
- Slidia : sélecteur masque dans la topbar de l'éditeur (modale bibliothèque)
- Slidia : anti-doublon catégorie côté client (vérification insensible à la casse avant POST)

### Modifié
- Slidia : `SlidiaPresentation` référence un `SlidiaTemplate` (nullable, fallback Bubul par défaut)
- Slidia : `MaskBasedGeneratorService` — résolution masque via `SlidiaTemplate`, champs géométriques xPct/yPct/bgColor
- Slidia : modales — croix × en haut à droite, suppression du bouton "Fermer"
- Slidia : badges tokens/Wh utilisent `sdColor.primary` (suppression de la couleur rose fixe)
- Slidia : barre de recherche + pills + toggle : `rounded-2xl`

### Corrigé
- Slidia : upload masque — validation MIME type côté serveur (`getMimeType()` vs extension client)
- Slidia : IDOR `templateId` — vérification propriété avant association à une présentation

## [2.13.2] - 2026-05-05

### Ajouté
- Slidia : page dédiée `/slidia/create` pour la création de présentation (générateur texte/PDF)

### Modifié
- Slidia : bouton "Créer" unique dans le header (lien `<a>` vers `/slidia/create`) — suppression du toggle inline et du bouton "Créer manuellement"
- Slidia : état vide hors grille CSS (corrige le débordement du container)
- Slidia : `list_controller.js` simplifié — uniquement rename et delete

## [2.13.1] - 2026-05-05

### Corrigé
- Slidia : boutons "Générer avec l'IA" et "Créer manuellement" inopérants — routes `/api/slidia/` exclues du firewall `api` (stateless ApiKey) pour passer par le firewall `main` (session)
- Slidia : `createManual()` sans gestion d'erreur — ajout try/catch + vérification `res.ok`

### Amélioré
- Slidia : état vide de la liste avec CTAs intégrés ("Générer avec l'IA" + "Créer manuellement") et visuel encadré en pointillés
- Slidia : cartes de présentation avec thumbnail en gradient+quadrillage, badge slides, bouton renommer, icône "Ouvrir" à droite

## [2.13.0] - 2026-05-05

### Ajouté
- Slidia : liste des présentations persistantes avec grille de cartes (`/slidia`)
- Slidia : éditeur slide par slide avec formulaire dynamique par placeholder (`/slidia/{id}/edit`)
- Slidia : API REST CRUD complète — présentations, slides, réordonnancement
- Slidia : génération d'images asynchrone par slide via Symfony Messenger + DALL-E 3
- Slidia : auto-save debouncé 500ms avec indicateur d'état (style Notion)
- Slidia : pipeline qualité IA — pré-analyse audience/ton, contexte glissant inter-slides, bullets structurés, validation finale
- Slidia : formule `wordCapacity` paramétrique lisant taille de police et interligne depuis le XML du masque PPTX

### Modifié
- Slidia : assignation des layouts contrôlée par PHP via rotation déterministe (plus par l'IA)
- Slidia : robustification du parsing des masques malformés (try/catch par layout, déduplication des idx)
- Slidia : `generateStream` retourne désormais `{presentationId}` dans l'event SSE `done` — le frontend redirige vers l'éditeur
- Slidia : generator controller adapté pour lire le stream SSE et rediriger à la fin de la génération

### Supprimé
- Slidia : champs `textDensity` et `contentFidelity` de `SlidiaSettings` (remplacés par calibration automatique `wordCapacity`)
- Slidia : endpoint `/slidia/generate` non-streaming (remplacé par la persistance DB via `generateStream`)
- Slidia : `settings_controller.js` (settings réduits à la gestion de masque)

### Technique
- Nouvelles entités Doctrine : `SlidiaPresentation`, `SlidiaSlide` (migration `Version20260505000001.php`)
- Nouveau controller REST : `SlidiaApiController` (9 endpoints `/api/slidia/*`)
- Messenger : `GenerateSlideImageMessage` + `GenerateSlideImageHandler` (pipeline DALL-E 3)
- Stimulus : `slidia--list` (`list_controller.js`) + `slidia--editor` (`editor_controller.js`)
- `DashboardDataService` : ajout `getDataForUser()` agrégateur

## [2.12.1] - 2026-05-04

### Ajouté
- Dashboard : bouton toggle (icône œil) dans le header "Vos outils" pour afficher/masquer les stats détaillées de chaque carte outil
- Dashboard : préférence persistée dans `Settings.showToolStats` via `POST /api/settings/tool-stats-toggle`
- Dashboard : bouton "Proposer un outil" redirige désormais vers la page support (`/settings/support`)

### Technique
- `Settings` : champ `showToolStats` (bool, défaut `true`) + getter/setter + migration `Version20260504000001.php`
- Stimulus `shared--tool-stats` : toggle CSS class + save AJAX asynchrone
- `assets/styles/pages/dashboard.css` : classes `.tool-stats-hidden`, `.stats-toggle-hide-btn`, `.stats-toggle-show-btn`
- `tool-card` : classes `tool-stats-row` et `tool-card-footer` pour le ciblage CSS

## [2.12.0] - 2026-05-03

### Ajouté
- Monalisia : possibilité de nommer une infographie à la création (champ "Nom (facultatif)" dans le drawer)
- Monalisia : possibilité de renommer une infographie existante via un modal dédié (crayon sur la carte)
- Monalisia : endpoint `POST /monalisia/{id}/rename` avec vérification CSRF et ownership

### Amélioré
- Monalisia : cartes d'infographies refondues — image seule par défaut, toutes les infos et actions apparaissent uniquement au survol (overlay avec nom, format, tokens, énergie, date + 4 boutons)
- Monalisia : thématisation complète de l'interface avec les couleurs de l'outil depuis la base de données (`tool_color_details['monalisia']`) — header, drawer, boutons, formulaire, cartes
- Monalisia : boutons d'action sur les cartes colorés avec la couleur primaire de l'outil via CSS custom property `--mn-primary-rgb`

### Technique
- `MonalisiaInfographic` : champ `name` nullable ajouté (migration `Version20260503000001.php`)
- Stimulus `monalisia--generator` : targets `nameInput`, `renameModal`, `renameId`, `renameCsrf`, `renameInput` + méthodes `openRenameModal`, `closeRenameModal`, `saveRename`

## [2.11.0] - 2026-05-03

### Amélioré
- Sidebar : couleurs des outils dynamiques depuis la base de données (`Tool.colorPrimary`)
  - Chaque outil affiche sa couleur propre en état actif et au survol (gradient + texte + icône)
  - Approche CSS custom property `--tool-rgb` via inline style — compatible Tailwind CSS v4 JIT
  - `nav-item.html.twig` : nouveau paramètre `toolColorRgb`, icône en `currentColor` pour héritage automatique
  - Promptly : boutons collapsed et expanded mis à jour avec la même mécanique

## [2.10.0] - 2026-05-02

### Sécurité
- Serenia : chiffrement AES-256-CBC des messages en base de données (`SereniaChatMessage.content`, `extractedFileContent`, `SereniaChatSession.contextSummary`)
  - Préfixe `ENC:` pour la rétrocompatibilité avec les sessions existantes
  - Recherche plein-texte PHP-side (déchiffrement à la volée) — le SQL LIKE remplacé
  - `EncryptionService.decryptIfEncrypted()` centralisée — plus de duplication entre services
  - `.superpowers/` et `docs/superpowers/` ajoutés au `.gitignore`

## [2.9.0] - 2026-05-02

### Ajouté
- Serenia : sélecteur de préférences au démarrage de chaque conversation (style, mode, modèle IA)
  - 3 styles de communication : Bienveillant (défaut), Direct, Créatif
  - 3 modes de travail : Analyse, Création, Coaching (défaut)
  - 3 modèles IA : Rapide gpt-4.1-mini (défaut), Standard gpt-4.1, Raisonnement o4-mini
  - Alerte visible pour o4-mini (≈ 15× tokens), badge modèle dans la sidebar
- Serenia : system prompt réécrit en prose (réponses équilibrées, moins de bullets)
  - Nouveau système modulaire BASE + STYLE + MODE composés depuis les préférences de session
- Serenia : support des reasoning models OpenAI (o4-mini) — `max_completion_tokens` sans `temperature`

### Modifié
- Serenia : `ContextBuilderService` — architecture modulaire du system prompt
- Serenia : `SerenIAService` + `StreamingService` — modèle IA dynamique par session (plus de constante globale)
- Serenia : `SereniaChatSession` — 3 nouveaux champs `communicationStyle`, `workMode`, `aiModel`

## [2.8.0] - 2026-05-01

### Ajouté
- Serenia : historique persistant des conversations (SereniaChatSession + SereniaChatMessage)
- Serenia : sidebar de navigation des conversations avec groupes par date (épinglées, aujourd'hui, hier, plus ancien)
- Serenia : 5 leviers de réduction des tokens via ContextBuilderService
  - Fenêtre de contexte réduite de 10 à 5 messages (−50%)
  - Résumé glissant automatique pour les longues conversations (−38%)
  - Injection du contenu fichier uniquement au premier message (−3 750 tokens/msg)
  - System prompt compressé de 300 à 90 tokens (−70%)
  - Ordering optimisé pour le prefix caching OpenAI
- Serenia : support CSV en upload
- Serenia : rendu markdown avec coloration syntaxique (markdown-it + highlight.js)
- Serenia : titre de conversation généré automatiquement par GPT après le 1er échange
- Serenia : 6 nouvelles routes de gestion des sessions (création, renommage, épinglage, suppression)
- Serenia : protection IDOR sur les routes de session

## [2.7.8] - 2026-04-29

### Modifié — Carte utilisateur sidebar : refonte design "pro"

Refonte visuelle de la carte expanded pour qu'elle ait du poids et de la profondeur (la version précédente — `bg-white/40` + `border-primary/10` — manquait de présence et n'avait pas le `cursor: pointer` sur le `<button>`, défaut classique des reset CSS modernes).

- **Pastille avatar 36px à gauche** (initiales sur le gradient du plan, `ring-1 ring-white shadow-sm`) : ancrage visuel cohérent avec le mode collapsed et alignement sur le pattern user-menu de Linear / Slack / Notion.
- **Carte plus solide** : `bg-white/80 backdrop-blur-md` + `border border-gray-200/70`. Au hover, fond passe à `bg-white` plein et la bordure prend la teinte du thème (`primary/30` Bubul, `violet-300/50` SAM).
- **Ombre portée double** sur 2 couches (`0_1px_2px + 0_1px_3px` au repos, `0_2px_4px + 0_4px_12px` au hover) — suggère un élément cliquable surélevé sans verser dans l'ombre lourde / "Material Design".
- **Icône `dots-vertical` placée dans une cible circulaire** (`w-7 h-7 rounded-md`) qui s'illumine en `bg-gray-100/70` quand on survole la carte (group hover). Ressemble à un vrai sous-bouton, plus à une icône orpheline.
- **Typographie resserrée** : nom passe de `font-bold text-sm` à `font-semibold text-[13px] leading-tight`, plan passe à `text-[11px] leading-tight`. Plus précis, plus refined.
- **`cursor: pointer`** ajouté explicitement sur les 2 triggers (expanded card + collapsed pastille).
- **Pastille collapsed** : ring passé de `white/70` translucide à `white` plein + `shadow-sm`, hover ajoute `shadow-md` pour cohérence avec la carte expanded.
- Bump version : 2.7.7 → 2.7.8.

## [2.7.7] - 2026-04-29

### Modifié — Carte utilisateur sidebar : popover actif dans les 2 modes

- **Revert du clic-direct-vers-paramètres** introduit en 2.7.6. Le clic sur la carte expanded ouvre désormais à nouveau le popover, **identique à celui qui s'ouvre sur la pastille en mode collapsed**. Comportement homogène entre les 2 états : 1 clic → 4 destinations accessibles (Paramètres, Mon abonnement, Aller sur SAM ou Bubul, Support).
- Suppression du paramètre `settingsHref` (devenu inutile) sur la molécule.
- L'icône `dots-vertical` revient sur la carte expanded (signal visuel "menu déroulant disponible"), avec son point orange de notif support si non-lu.
- Bump version : 2.7.6 → 2.7.7.

## [2.7.6] - 2026-04-29

### Modifié — Carte utilisateur sidebar : carte = lien direct vers paramètres

- **Comportement expanded simplifié** : la carte entière est désormais un `<a href="{{ settingsHref }}">`. Cliquer n'importe où sur la zone (nom, plan, fond) renvoie directement vers `/settings` (ou `/sam/settings` en contexte SAM). Plus de popover, plus de bouton `dots-vertical` en mode déplié.
- **Le popover ne s'ouvre qu'en mode collapsed**, où la pastille initiales en est le seul point d'accès aux autres destinations (Paramètres, Mon abonnement, switch plateforme, Support). Le `data-controller="shared--popover"` est restreint au wrapper du mode collapsed.
- **Renommages** : "Mon compte" → **"Paramètres"** (revert à la formulation initiale), "Nous contacter" → **"Support"** (plus court, cohérent avec la nomenclature interne).
- **Indicateur d'unread support en expanded** : un petit point orange (`bg-warning`) apparaît à droite de la carte quand `support_unread_count_user() > 0`, pour préserver le signal visuel maintenant que l'item Support n'est plus visible directement en mode déplié.
- Nouveau paramètre `settingsHref` (string) sur la molécule.
- Bump version : 2.7.5 → 2.7.6.

## [2.7.5] - 2026-04-29

### Modifié — Carte utilisateur sidebar : renommage + ordre du menu

- **Item "Paramètres" renommé en "Mon compte"** (formulation plus naturelle dans un menu utilisateur orienté profil).
- **Réorganisation du menu** selon le pattern Apple/Google/Linear (du plus personnel au plus externe) :
  1. Mon compte
  2. Mon abonnement
  3. Aller sur SAM / Aller sur Bubul (selon contexte/accès)
  4. Nous contacter
- Bump version : 2.7.4 → 2.7.5.

## [2.7.4] - 2026-04-29

### Modifié — Carte utilisateur sidebar : intégration glassmorphism + entrée "Nous contacter"

- **Thématisation par variant** : la molécule `_molecules/sidebar-user-card/` accepte désormais un paramètre `variant` (`'bubul'` ou `'sam'`). Bordures, fond et hover sont mappés sur le thème de la sidebar parente :
  - Bubul : `bg-white/40` + `border-primary/10`, hover en gradient `primary/8`.
  - SAM : `bg-violet-50/40` + `border-violet-200/40`, hover en gradient violet.
- **Glassmorphism aligné sur la sidebar** : `backdrop-blur-sm` sur le trigger, `backdrop-blur-xl` + `bg-white/95` sur le panel du menu, bordures ton-sur-ton (plus de bordure `gray-200` neutre qui faisait "bloc collé"). La carte se fond visuellement dans le dégradé translucide de la sidebar au lieu de paraître posée par-dessus.
- **Pastille de plan affinée** : ajout d'un `ring-1 ring-white/50` sur le point coloré expanded ; en collapsed, l'anneau de la pastille initiales passe de `ring-2 ring-white` plein à `ring-1 ring-white/70` translucide pour cohérence.
- **Nouvel item de menu "Nous contacter"** ([app_support_index]) avec icône `message`. Le compteur de support non-lu (`support_unread_count_user()`) est déplacé depuis Paramètres vers cet item — sémantiquement plus juste puisque le badge représente des réponses de support en attente.
- Bump version : 2.7.3 → 2.7.4.

## [2.7.3] - 2026-04-29

### Modifié — Carte utilisateur sidebar : bloc unique avec menu déroulant

Itération sur le footer de la sidebar pour passer d'une carte segmentée (carte + ligne action SAM + nav-item Paramètres séparé) à un **bloc unifié unique** dont le clic ouvre un menu d'actions.

- **`_molecules/sidebar-user-card/`** entièrement réécrite : la carte affiche nom + plan (point coloré) et un trigger `dots-vertical`. Toute la carte est cliquable et ouvre le menu (gros target, UX simple). En collapsed, la carte se condense en pastille initiales et le menu s'ouvre à droite de la sidebar.
- **Menu d'actions configurable** via le paramètre `menuItems` (array d'objets `{ label, href, icon|logo, tint, badge }`). La sidebar y injecte : Mon abonnement (toujours), Aller sur SAM ou Aller sur Bubul (selon contexte/accès), Paramètres (avec compteur de support non-lu).
- **Nouveau contrôleur Stimulus `shared--popover`** ([assets/controllers/shared/popover_controller.js](assets/controllers/shared/popover_controller.js)) : dropdown générique réutilisable, ferme au clic extérieur et touche Échap, ~50 lignes.
- **Suppression du nav-item Paramètres séparé** dans le footer : il est maintenant un item du menu de la carte, le badge support y est conservé.
- Bump version : 2.7.2 → 2.7.3.

## [2.7.2] - 2026-04-29

### Modifié — Refonte qualité Promptly (passe de cleanup générale)

**`PromptlyConfig.php` — extension complète des constantes :**
- Ajout de `MAX_FOLDER_NAME_LENGTH = 100` (auparavant magique, 4 occurrences hardcodées dans le contrôleur).
- Ajout des constantes OpenAI dédiées à l'amélioration IA : `IMPROVE_OPENAI_MAX_TOKENS = 3000`, `IMPROVE_OPENAI_TEMPERATURE = 0.3`, `IMPROVE_OPENAI_TIMEOUT = 90`.
- Ajout de **15 constantes `ERROR_*`** centralisant tous les messages d'erreur métier Promptly (folder not found, prompt not found, name required/too long, body too short/long, description required/too long, tags too many, improve failed/generic). Suppression de toute duplication de chaîne dans le contrôleur.
- Documentation enrichie : sections `LIMITES — DOSSIERS`, `AMÉLIORATION IA — PARAMÈTRES OPENAI`, `MESSAGES D'ERREUR MÉTIER`.

**`PromptlyController.php` — refactor selon Symfony 7.4 + DRY :**
- Nouveau helper privé `validateFolderName(string)` qui factorise la validation utilisée 2 fois (createFolder + renameFolder). 2 blocs de 6 lignes → 1 ligne par appel.
- Nouveau helper `error(string $template, int|string $value)` qui produit une `JsonResponse` 422 formatée via `sprintf` — supprime 7 occurrences de `sprintf(...)`.
- Tous les messages hardcodés remplacés par les constantes `PromptlyConfig::ERROR_*` (14 remplacements).
- `validatePromptInput()` : variable locale `$bodyLength` au lieu de 2 appels `mb_strlen` (perf + lisibilité).
- Suppression de la variable `$user` non utilisée dans `renameFolder()` et `deleteFolder()` (cleanup post-suppression du gating abonnement).
- PHPDoc ajoutée sur les helpers privés.

**`PromptlyService.php` — paramètres OpenAI sortis du code :**
- `improvePrompt()` utilise désormais `PromptlyConfig::IMPROVE_OPENAI_MAX_TOKENS / TEMPERATURE / TIMEOUT` au lieu de littéraux `3000`, `0.3`, `90`.
- Message d'erreur "Le prompt n'a pas pu être amélioré..." remplacé par `PromptlyConfig::ERROR_IMPROVE_FAILED`.
- Fusion de la dédup variables avec le return final (1 ligne au lieu de 2).
- PHPDoc enrichi (référence aux constantes Config + comportement REFUSED).

### Tests

- `PromptlyVisibilityTest` (3 tests, 8 assertions) → ✅ tous verts après refactor. Aucune régression.

## [2.7.1] - 2026-04-29

### Modifié — Refonte du footer de la sidebar (carte utilisateur unifiée)

- **Nouvelle molécule `_molecules/sidebar-user-card/`** : carte sobre affichant le nom complet de l'utilisateur en gras (`app.user.fullName`), une ligne de plan/abonnement marquée par un point coloré (gradient du plan en arrière-plan d'une pastille de 8px) sur fond gris neutre. Cliquable vers `app_settings_subscription`.
- **Suppression du badge gradient plein** ("ENTREPRISE / Bubul" en rouge plein, etc.) : la signalétique du plan reste portée par le `plan_gradients` du `PlanExtension` mais est ramenée à un simple point d'accent — moins de bruit visuel, l'œil va à l'essentiel (qui est connecté + sur quel plan).
- **Fusion du switch plateforme dans la carte** : la ligne "Aller sur SAM" (ou "Aller sur Bubul" en contexte SAM) devient une action interne de la carte, séparée par un trait fin. Le `_molecules/platform-switch/` n'est plus appelé depuis la sidebar.
- **Mode collapsed (72px)** : la carte se condense en pastille ronde affichant les initiales sur le gradient du plan + tooltip "Nom · Abonnement". L'action plateforme garde son icône carrée à part.
- **Item Paramètres conservé** en nav-item séparé sous la carte (pour préserver le badge de support non-lu).
- Bump version : 2.7.0 → 2.7.1.

## [2.7.0] - 2026-04-29

### Modifié — Unification totale du système tooltip

- **Tous les patterns custom convergent vers `data-tooltip` global**. La sidebar `_initTooltip` ne crée plus son propre tooltip DOM : elle bascule simplement `data-tooltip="..."` + `data-tooltip-position="right"` sur les nav-items quand on entre en mode collapsed (et les retire en expanded). Le contrôleur `shared--tooltip` gère le rendu, identique à tous les autres tooltips de l'app.
- **Migration des 13 fichiers `title=` restants** vers `data-tooltip=` (base.html.twig, label, accordion, data-row, tag-group, contract-row, user-card, action-bar, tool-toggle, platform-switch, podcast-card, sidebar, mes-prompts, contract show).
- **3 derniers usages de l'atome embed** (sam/users, promptly-card) migrés en `data-tooltip` direct.
- **Suppression du sub-flyout custom Promptly** dans la sidebar : Promptly utilise désormais le composant `_molecules/nav-item/nav-item.html.twig` standard comme tous les autres outils — fini le pattern unique avec collapsible expanded + flyout collapsed. Méthode `_initSubFlyout()` (~50 lignes) supprimée du `sidebar_controller.js`.

### Modifié — Suppression de `PromptlyConfig::AI_MODELS`

- **Constante `AI_MODELS` supprimée** ([src/Config/PromptlyConfig.php](src/Config/PromptlyConfig.php)) : seul `AI_PROVIDERS` (10 fournisseurs simplifiés) reste.
- `aiModelLabel()` simplifié : retourne `AI_PROVIDERS[slug] ?? slug` (les anciens prompts avec un slug détaillé tombent simplement sur le slug brut).
- `PromptlyController` : 5 occurrences `array_merge(AI_PROVIDERS, AI_MODELS)` → `AI_PROVIDERS` directement.
- `PromptlyPrompt::getAiModelLabel()` délègue maintenant à `PromptlyConfig::aiModelLabel()`.

## [2.6.0] - 2026-04-29

### Ajouté — Système de tooltip global unifié

- **Nouveau contrôleur Stimulus `shared--tooltip`** ([assets/controllers/shared/tooltip_controller.js](assets/controllers/shared/tooltip_controller.js)) activé globalement sur `<body>` dans [base.html.twig](templates/base.html.twig). Un seul élément DOM partagé pour toute l'application — gestion identique à celle des notifications.
- **Design uniforme** calqué sur le tooltip de la sidebar collapsed : fond blanc, texte gray-900, font 12px medium, padding 6px 12px, border-radius 10px, ombre 2 couches `0 4px 16px rgba(0,0,0,0.10), 0 1px 4px rgba(0,0,0,0.06)`, border subtil `rgba(0,0,0,0.07)`, transition opacity 180ms + transform 200ms cubic-bezier.
- **Usage simple** : il suffit d'ajouter `data-tooltip="Mon texte"` sur n'importe quel élément. Position personnalisée via `data-tooltip-position="top|bottom|left|right"` (défaut `top`).
- **Accessibilité** : se déclenche aussi au focus clavier (`focusin`/`focusout`), pas seulement au hover. Tooltip `role="tooltip"`. Auto-clamp au viewport (8px de marge minimum).

### Modifié — Migration globale des tooltips natifs

Les `title="..."` HTML natifs (qui produisaient des tooltips OS-dépendants, lents, non stylisés) ont été migrés vers le système unifié `data-tooltip="..."` dans 23 fichiers :

- **Moodia editor** : 14 tooltips (header, toolbar WYSIWYG, pagination, etc.)
- **SAM** : 10 tooltips (course/user/cohort details, listes, components)
- **Promptly** : 4 tooltips (folder card, mes-prompts, detail) + boutons "Insérer une variable" / "Améliorer" en `data-tooltip` direct
- **Castia, Monalisia, Slidia, Serenia, admin** : ~11 tooltips couvrant les actions CRUD
- **Organisms** (company-card, course-card) : tooltips d'action sur les cards

L'atome `_atoms/tooltip/tooltip.html.twig` reste disponible pour les rares cas SSR sans JS, mais sa documentation pointe désormais vers le pattern global. La macro `_macros/tooltip.html.twig` (créée temporairement) a été supprimée — un seul système, le contrôleur global.

### Nettoyé

- Suppression du dossier `templates/_macros/` (devenu inutile).
- Le tooltip JS de la sidebar (`sidebar_controller.js::_initTooltip`) reste tel quel : cas particulier conditionné au mode collapsed, comportement spécifique non couvert par le système global.

## [2.5.0] - 2026-04-29

### Modifié — Prompts privés ouverts à tous

- **Fin du gating abonnement sur les prompts privés** : tout utilisateur (freemium, abonné B2C, B2B) peut désormais créer, éditer, et basculer la visibilité d'un prompt en privé sans contrainte. La règle "abonnés uniquement" est supprimée.
- **Invariant conservé** : les prompts privés ne s'affichent toujours JAMAIS dans la bibliothèque publique (`findForUser()` ne retourne que les prompts `visibility=public`). Ils restent uniquement dans "Mes prompts" de leur auteur.

### Ajouté

- **Action "Éditer le prompt"** dans le menu d'actions des prompts dans "Mes prompts" (entre "Ouvrir" et "Déplacer"). Lien direct vers la page d'édition `app_promptly_edit_page`.
- **Promptly intégré dans la grille tarifaire** ([landing/index.html.twig](templates/landing/index.html.twig), [pricing.html.twig](templates/_organisms/landing/pricing.html.twig), [LandingController.php](src/Controller/LandingController.php)) : icône Promptly + ✓ vert sur tous les plans, et phrase "inclus dans chaque offre" mise à jour pour citer Moodia, Slidia, Serenia, Monalisia, Castia et Promptly.

### Supprimé / nettoyé (DRY)

- **`User::canCreatePrivatePrompts()`** : méthode supprimée (toujours `true` désormais, donc inutile).
- **6 passages `canCreatePrivate` aux templates** dans `PromptlyController` (index, myPrompts, createPage, editPage, show) : retirés.
- **3 blocs de gating** (`create()`, `edit()`, `toggleVisibility()`) qui forçaient `visibility=public` ou retournaient 403 : retirés.
- **Modale d'upgrade** sur la page de création (52 lignes) : supprimée.
- **Logique `data-needs-upgrade`** dans `setVisibility()` du `create_controller.js` : retirée.
- **Static value `canCreatePrivate`** dans les contrôleurs Stimulus (`create_controller`, `library_controller`) + ses 2 utilisations conditionnelles (fork visibility par défaut, garde toggleVisibility) : retirées.
- **Bloc `{% if canCreatePrivate %}{% else %}`** dans `_modals.html.twig` (option Privé "PRO" disabled) : simplifié à un seul label cliquable.
- **5 attributs `data-promptly--*-can-create-private-value`** sur les wrappers Stimulus : retirés.
- **Tests fonctionnels `PromptlyVisibilityTest`** : refactorisés. 2 tests obsolètes supprimés (`testFreemiumUserCannotCreatePrivatePromptForcedToPublic`, `testFreemiumUserCannotToggleVisibilityToPrivate`). 3 tests conservés / mis à jour : `testAnyUserCanCreatePrivatePrompt`, `testAnyUserCanToggleVisibilityPublicToPrivate`, `testPrivatePromptsNeverAppearInTheLibrary`. Helpers `switchUserToPaidPlan()` / `resetUserToFreemium()` supprimés (plus nécessaires).

## [2.4.0] - 2026-04-27

### Ajouté — Promptly : gating public/privé renforcé + UX upgrade

- **UX d'upgrade Promptly** : la carte "Privé" du formulaire de création est désormais cliquable pour les freemium (au lieu d'être grisée). Un clic ouvre une modale "Débloquez les prompts privés" avec 3 arguments de valeur et un CTA `Voir les abonnements` qui redirige vers `/settings/subscription`. La carte conserve son état désactivé visuel mais avec `cursor-pointer` + état hover (translate, shadow) hérité du molecule `option-card`.
- **Modale d'upgrade** : nouvelle section `_molecules/modal` embeddée conditionnellement (`if not canCreatePrivate`) en bas de [templates/promptly/create.html.twig](templates/promptly/create.html.twig). Stimulus `setVisibility()` détecte le data-attribut `data-needs-upgrade="true"` et déclenche `openModal('promptly-upgrade-modal')` plutôt que de basculer la sélection.

### Corrigé

- **Bug `canCreatePrivatePrompts()`** : la méthode retournait `true` pour un utilisateur freemium avec abonnement actif sur le plan freemium (le statut suffisait, le plan n'était pas vérifié). Ajout d'un short-circuit `if ($this->isFreemium()) return false;` pour bloquer l'accès aux prompts privés pour tout utilisateur sur le plan `freemium`, même avec une `UserSubscription` active.
- **Bibliothèque Promptly** : `findForUser()` n'incluait plus correctement les règles d'isolation — un utilisateur voyait ses propres prompts privés dans la library. Désormais, **seuls les prompts publics non-brouillons** sont affichés dans `/promptly`, peu importe le propriétaire. Les prompts privés restent uniquement dans `/promptly/mes-prompts`.
- **Erreur Twig `subscription.stripeCustomerId` sur null** ([profile/subscription.html.twig:586](templates/profile/subscription.html.twig:586)) : ajout d'un guard `{% if subscription and subscription.stripeCustomerId %}` pour éviter le crash quand un utilisateur freemium sans subscription accède à la page.

### Atomic Design

- **Molecule `option-card`** : ajout de `cursor-pointer` et états hover (`hover:border-primary/40`, `hover:bg-primary/5`, `hover:-translate-y-0.5`, `hover:shadow-sm`) pour les cartes non-disabled. Profite à la sélection Visibilité Promptly et à toute future utilisation.

### Tests

- **Nouveau `tests/Functional/Controller/PromptlyVisibilityTest.php`** : 4 tests fonctionnels (10 assertions) verrouillent les règles d'accès :
  1. Freemium qui crée privé → forcé à public côté serveur
  2. Abonné (plan payant) → peut créer privé
  3. Freemium qui tente toggleVisibility public→privé → 403
  4. Prompts privés exclus de la bibliothèque (pour leur auteur ET pour tous)
  - Helper `resetUserToFreemium()` dans `setUp()` pour garantir l'isolation des tests (les fixtures ne sont chargées qu'une fois en début de suite).

## [2.3.0] - 2026-04-27

### Ajouté — Promptly : refonte du formulaire de création + amélioration IA

- **Description obligatoire** : minimum 20 caractères (`PromptlyConfig::MIN_DESCRIPTION_LENGTH`), validée côté JS et PHP. Champ rendu en `<textarea>` avec compteur.
- **Améliorer avec l'IA** : nouveau bouton sur la page de création/édition. Route `POST /promptly/improve` qui appelle GPT-4o-mini pour réécrire le prompt selon les bonnes pratiques d'ingénierie (variables `{{...}}` préservées, refus des contenus non conformes via réponse `REFUSED`).
- **Garde-fou tokens** : amélioration IA bloquée si solde < 1 000 tokens (`PromptlyConfig::IMPROVE_MIN_TOKENS`) via `TokenValidationTrait`. Notification utilisateur explicite. Tokens facturés via `TokenService::addTokens` + log `UserActivityService::ACTIVITY_PROMPTLY`.
- **Modèles IA simplifiés** : nouvelle constante `PromptlyConfig::AI_PROVIDERS` (ChatGPT, Claude, Gemini, DeepSeek, Llama, Mistral, Grok, Perplexity, Copilot, Autre). `AI_MODELS` conservé pour la rétro-compatibilité d'affichage. Helper `PromptlyConfig::aiModelLabel()`. Les anciens slugs détaillés sont préservés sur les prompts existants et restent visibles à l'édition.

### Modifié

- **Catégories** (sidebar création) : intitulé pluralisé, taille de typo `text-[12px]` cohérente entre pills actives et inactives.
- **Atomic Design** : la barre d'actions du textarea utilise désormais `_atoms/button/button.html.twig` au lieu de boutons inline. Bouton "Améliorer" en variant `soft-cohorts` (alignement visuel avec les autres outils — Moodia, Slidia, etc.). Bouton "Insérer une variable" en variant `soft-warning`. Tous deux en `size: md` + `radius: xl`. Le bandeau d'astuce variables passe sur la molecule `_molecules/inline-alert/inline-alert.html.twig`.
- **État loading du bouton "Améliorer"** : seul le SVG de l'icône baguette est remplacé par un spinner en place, le label "Améliorer" reste visible. Le SVG original est restauré à la fin via `dataset.originalIcon`.
- **Longueur minimale du contenu du prompt** : `PromptlyConfig::MIN_BODY_LENGTH = 100`. Validation côté JS (submit + improve) et PHP (`create`/`edit`/`improve`).
- **Layout barre d'actions du textarea** : "Contenu du prompt" et les deux boutons (Insérer une variable, Améliorer) sont désormais sur la même ligne grâce à `flex justify-between` sans `flex-wrap`, et passent en `mobileIconOnly` ≤ sm pour rester compacts sur mobile.
- **Prompt système d'amélioration IA** : couvre les bonnes pratiques de prompt engineering (rôle, contexte, contraintes, format de sortie) **dans un style narratif fluide** — pas de titres de sections type "RÔLE :" / "CONTEXTE :", mais des phrases naturelles dans lesquelles l'utilisateur final n'a qu'à remplir les `{{variables}}`. **Règle d'unicité absolue** : chaque variable n'apparaît qu'une seule fois dans le prompt entier ; les références ultérieures au même concept passent en langage naturel (pronoms, démonstratifs, paraphrases — "ce domaine", "cette thématique", "celui-ci"). Variables `{{...}}` existantes préservées **et** nouvelles variables sémantiques en `snake_case` ajoutées (sujet, audience, ton, longueur, rôle, etc.) — chaque nom est unique, descriptif, sans accents ; cible 3 à 8 variables uniques. Test de lecture : remplacer mentalement la variable doit donner une phrase grammaticalement correcte. Refus explicite (réponse `REFUSED`) en cas de prompt vide, ambigu ou non conforme. Budget output 3 000 tokens, timeout 90 s.
- **Bouton de retour** : libellé unifié à "Retour" (auparavant "Retour au prompt" en mode édition).
- **Robustesse grammaticale des variables** : le system prompt impose maintenant 3 patterns d'insertion (apposition avec colon, phrase complète, formulaire en-tête) pour que la valeur saisie tienne grammaticalement quel que soit son genre/article. Les patterns "le {{var}}" / "la {{var}}" / "pour {{var}}" sont explicitement interdits (sauf valeurs invariables type {{nombre_mots}}). Test à 4 valeurs ("des entrepreneurs", "retraités", "l'équipe RH", "lycéens") imposé au modèle pour valider chaque slot.
- **Canevas de prompt amélioré (final)** : structure de **référence** — `Tu es {{role_expert}}.` / `Contexte :` / `Objectif :` / `Tâche :` / `Format :` / `Contraintes :` / `Si certaines informations manquent, pose-moi des questions avant de répondre.` Vise par défaut, mais adaptable : sections omissibles ou fusionnables si le prompt source ne les justifie pas. La ligne de clôture reste fortement recommandée (anti-hallucination). Chaque section reçoit son contenu en phrases naturelles avec variables inline.
- **Aération obligatoire** : ligne vide (`\n\n`) entre chaque section du canevas — pas de sections collées. Lisibilité côté éditeur Promptly et copier-coller dans Serenia.
- **Filet de sécurité unicité variables** : nouvelle méthode privée `PromptlyService::dedupeVariables()`. Si l'IA produit malgré la consigne deux occurrences du même `{{nom_variable}}`, la deuxième est renommée `{{nom_variable_2}}`, la troisième `{{nom_variable_3}}`, etc. Garantit que chaque blanc reste indépendamment éditable dans l'UI (un champ par nom). La règle d'unicité est par ailleurs renforcée dans le system prompt avec explication explicite du bug UI à éviter.
- **Compteur de progression Mad Libs corrigé** : dans `detail_controller.js`, le compteur "X/Y complétés" se base désormais sur le **nombre de zones DOM** (occurrences de `{{...}}`), pas sur le nombre de noms de variables uniques. Plus de cas absurde "8/7 complétés" pour les prompts hérités contenant des doublons. Chaque span reçoit un `data-ml-index` ; `#buildFinal()` remplace désormais chaque occurrence positionnellement, ce qui garantit l'indépendance des zones même en cas de noms dupliqués.
- `PromptlyController` utilise désormais `TokenValidationTrait`, injecte `TokenService` et `LoggerInterface`. Helper privé `validatePromptInput()` qui centralise les contrôles `create()` / `edit()`.
- `PromptlyService` injecte `OpenAIHttpClient` et expose `improvePrompt(string $body)`.

### Atomic Design — nouvelle molecule

- `_molecules/option-card/option-card.html.twig` : carte sélectionnable (icône + titre + description) avec états `selected` / `disabled`. Utilisée pour le sélecteur de visibilité Promptly et réutilisable pour tout groupe de choix exclusifs.

## [2.2.0] - 2026-04-24

### Ajouté — Promptly : système de dossiers (Google Drive-like)

#### Entités & base de données
- `PromptlyFolder` : nouvelle entité adjacency-list (id, owner FK→user CASCADE, parent FK→promptly_folder CASCADE nullable, name, createdAt)
- `PromptlyPrompt.folder` : relation ManyToOne nullable vers `PromptlyFolder` (ON DELETE SET NULL)
- Migration `Version20260423000004` : table `promptly_folder` + colonne `folder_id` sur `promptly_prompt`

#### Backend
- `PromptlyFolderRepository` : `findByOwner`, `findRootByOwner`, `findChildren`, `findAncestors`, `countPrompts`
- `PromptlyPromptRepository::findByAuthorAndFolder` : prompts d'un auteur dans un dossier précis
- `PromptlyService` : injection de `PromptlyFolderRepository` + méthodes `createFolder`, `renameFolder`, `deleteFolder`, `movePromptToFolder`
- `PromptlyController` : `PromptlyFolderRepository` injecté ; `myPrompts()` adapté (vue racine / vue dossier, breadcrumb) ; 4 nouvelles routes : `POST /folders/create`, `PATCH /folders/{id}/rename`, `DELETE /folders/{id}`, `POST /{id}/move-to-folder`

#### Frontend
- `mes_prompts_controller.js` : contrôleur Stimulus gérant la création, le renommage, la suppression (avec confirmation) et le déplacement de prompts
- `templates/promptly/mes-prompts.html.twig` : refonte complète — vue racine (dossiers + tabs + prompts) et vue dossier (breadcrumb + sous-dossiers + prompts)
- `templates/promptly/_folder_card.html.twig` : nouvelle molecule carte de dossier avec menu actions (sous-dossier, renommer, supprimer)
- 4 modals inline : créer dossier, renommer dossier, confirmer suppression, déplacer un prompt

## [2.1.0] - 2026-04-23

### Ajouté — Promptly : refonte UI + nouvelles fonctionnalités

#### Entités & base de données
- `PromptlyCategory` : nouvelle entité pour les catégories admin-gérées (nom, slug, icône Solar, couleur hex, position, isActive)
- `PromptlyPrompt` : ajout des champs `usesCount`, `isDraft`, `aiModel`, `category` (FK nullable ON DELETE SET NULL)
- `PromptlyPrompt::bodyPreview(int $n)` : alias Twig pour appel avec argument
- `PromptlyPrompt::getFieldCount()` : détecte les variables `{{var}}` uniques dans le corps
- Migration `Version20260423000001` : table `promptly_category` + 4 champs sur `promptly_prompt`

#### Config
- `PromptlyConfig::AI_MODELS` : 10 modèles IA référencés (GPT-4o, Claude, Gemini, Mistral, LLaMA)

#### Backend
- Contrôleur : route `app_promptly_my_prompts` (`/mes-prompts`) — tabs all|public|private|drafts + tri serveur
- Contrôleur : route `app_promptly_use` (`/{id}/use`) — incrémente `usesCount` à chaque copie de prompt
- Service : `PromptlyService::incrementUses()`, `getFavoriteIds()` ; `create()`/`update()` étendus (catégorie, aiModel, isDraft)
- Repository : `findForUser()` filtre par `?PromptlyCategory` + tri `uses` ; `findByAuthor()` pour "Mes prompts"

#### Frontend
- Bibliothèque (index) : redesign complet — grille 3 colonnes, pills catégories depuis BDD, tri natif `<select>`, recherche server-side (turboVisit + debounce 400ms)
- Mes prompts : nouvelle page liste avec onglets sous-ligne et tri serveur
- Card molecule (`promptly-card`) : refonte — icône catégorie colorée, auteur avec dot coloré, footer stats (❤ favoris + → utilisations + badge champs)
- Modals : extraction dans `_modals.html.twig` partagé (DRY index ↔ mes-prompts) ; sélect catégorie + IA utilisée + checkbox brouillon
- Stimulus `library_controller.js` : targets `inputCategory/inputAiModel/inputDraft`, enregistrement usesCount via POST à la copie, `onSearch` server-side

## [2.0.1] - 2026-04-14

### Corrigé
- Upgrade d'abonnement Stripe : redirection systématique vers la page de paiement Stripe (hosted_invoice_url) — le plan est activé par le webhook `invoice.payment_succeeded` uniquement si le paiement est confirmé
- Upgrade d'abonnement Stripe : plan activé immédiatement au rechargement de la page si Messenger n'a pas encore traité le webhook (vérification synchrone via l'API Stripe)
- Sujets d'emails : suppression des emojis et du préfixe "Bubul —" sur tous les emails transactionnels

## [2.0.0] - 2026-04-07

### Refonte architecturale majeure

#### Permission-by-Default (suppression UserTool)
- Suppression des entités `UserTool`, `UserToolRepository` et du service `ToolAccessService`
- Suppression des event subscribers `ToolAccessSubscriber` et `UserCompanyToolSyncSubscriber`
- Tous les outils sont désormais accessibles par défaut ; seul SAM est restreint via `Company.hasSamAccess`
- Ajout de `User::canAccessSam()`, `User::isB2B()`, `User::isB2C()` pour les vérifications
- Migration Doctrine : suppression tables `user_tool`/`contract_tool`, ajout `company.has_sam_access`, suppression `plan.tool_limits`

#### Système B2C — Tokens
- `TokenService::getMaxStorable()` corrigé : B2B utilise `monthlyAllocation × 6`, B2C utilise `plan.monthlyTokens × storageMonths`
- Ajout de `TokenService::getTokenBalance()` comme source unique de vérité
- `SubscriptionController` injecte `TokenService` et passe `tokenBalance` + `effectivePlan` à la vue

#### Système B2B — Tokens et cycle de vie
- `UserTokenDistributionService::getMaxStorage()` corrigé : plafond = allocation par utilisateur × 6
- `ContractService::getAggregatedLimits()` simplifié : ne retourne plus de liste d'outils
- `CompanyController` : ajout `createUser()` (création directe B2B avec email bienvenue) et `fireUser()` (désactivation + détachement + reset allocation)
- Suppression du flow invitation (`CompanyInvitationService` orphelin, actions `searchUsers` et `inviteByEmail` supprimées)

#### Facturation annuelle
- `StripeSubscriptionService::changePlan()` : ajout `cancel_at = +12 mois` sur les upgrades annuels
- Engagement annuel = 12 paiements mensuels avec annulation automatique à terme

#### Sécurité
- `UserChecker` : message spécifique pour les utilisateurs B2B dont l'entreprise n'a plus de contrat actif

#### Templates admin
- Suppression de toutes les sections "Outils autorisés" / checkboxes tool
- Ajout d'indicateurs SAM (activé/désactivé) dans company/list, company/show, contract/show, users/show
- Suppression de la modal d'association d'utilisateurs existants ; remplacée par création directe

## [1.7.3] - 2026-03-31

### Corrigé

- **Customer Stripe périmé** : `getOrCreateStripeCustomer()` vérifie maintenant que le
  `cus_xxx` stocké en base existe encore dans Stripe avant de le réutiliser. Si Stripe
  retourne `No such customer` (données de test purgées, changement de compte), l'ID est
  réinitialisé et un nouveau customer est créé. Idem pour `createPortalSession()`.

## [1.7.2] - 2026-03-31

### Corrigé

- **Date d'expiration après annulation** : `handleCheckoutCompleted()` extrait maintenant
  `current_period_start` et `current_period_end` depuis la subscription Stripe expandée.
  Corrige l'affichage "—" à la place de la date de fin d'accès sur la page abonnement
  et dans la notification d'annulation.

## [1.7.1] - 2026-03-31

### Corrigé

- **Activation abonnement sans webhook** : la page `/settings/subscription/success` récupère
  maintenant la session Stripe via l'API et appelle directement `handleCheckoutCompleted()`.
  L'abonnement est activé immédiatement en local sans Stripe CLI ni worker Messenger.
  Le webhook reste un filet de sécurité (idempotence garantie).

## [1.7.0] - 2026-03-31

### Ajouté

- **B2C — Portail Stripe Customer** : `StripeCheckoutService::createPortalSession()` + route `POST /settings/subscription/portal`
  - Bouton "Gérer le paiement" sur l'abonnement actif et bouton prioritaire en cas de `past_due`
  - Le portail Stripe permet de gérer carte, IBAN, SEPA, et historique directement chez Stripe
- **B2C — Emails transactionnels** :
  - `UserEmailService::sendSubscriptionActivated()` — envoyé au webhook `checkout.session.completed`
  - `UserEmailService::sendPaymentFailed()` — envoyé au webhook `invoice.payment_failed`
  - `UserEmailService::sendSubscriptionCanceled()` — envoyé au webhook `customer.subscription.deleted`
  - Templates : `emails/subscription/activated.html.twig`, `payment_failed.html.twig`, `canceled.html.twig`
  - Tous les envois dans `try/catch` — échec SMTP ne bloque pas le traitement webhook
- **Admin B2C — Vue abonnements** : `SubscriptionAdminController` + 2 templates
  - Liste `/admin/subscriptions` avec compteurs par statut et lien vers détail
  - Détail `/admin/subscriptions/{id}` : infos Stripe, tokens, factures
  - Lien "Abonnements B2C" ajouté dans la sidebar admin
- **Cron B2C `app:b2c:renew-tokens`** : filet de sécurité si webhook `invoice.payment_succeeded` manqué
  - Recharge les tokens des abonnés actifs dont `lastAllocationAt < periodStart`
  - Option `--dry-run` pour simulation sans flush

## [1.6.0] - 2026-03-31

### Ajouté

- **B2C Phase 2 — Page abonnement** : nouvelle page `/settings/subscription` avec onglets Paramètres/Abonnement
  - Affichage du plan actuel, barre de tokens, date de renouvellement
  - Grille de sélection de plan avec checkout Stripe (mensuel / annuel −17%)
  - Historique des factures avec téléchargement PDF
  - Bouton Déconnexion dans la barre de navigation des paramètres
  - `StripeCheckoutService` : création de sessions Stripe Checkout, déduplication du customer Stripe
  - `SubscriptionController` : routes `/checkout`, `/success`
- **B2C Phase 3 — Gestion de l'abonnement existant** :
  - `StripeSubscriptionService` : `changePlan()` (upgrade immédiat avec proration / downgrade planifié), `cancelAtPeriodEnd()`, `reactivate()`
  - Upgrade immédiat : mise à jour du `UserToken` sans réinitialiser les tokens déjà consommés
  - Downgrade planifié : `pendingPlan` appliqué lors du prochain `invoice.payment_succeeded`
  - Annulation : accès maintenu jusqu'à `periodEnd`, bouton "Réactiver" si période non expirée
  - `SubscriptionController` : routes `/change`, `/cancel`, `/reactivate`
  - Page abonnement : boutons Annuler/Réactiver, alerte downgrade planifié, boutons "Passer à ce plan" pour abonnés actifs
- **Navigation paramètres** : molecule `tabs` variante `settings` (border pleine largeur)

### Corrigé

- **`plan_id` nullable dans `UserSubscription`** : le customer Stripe est créé avant le paiement, le plan n'est assigné qu'après confirmation webhook. Migration `Version20260331120000`
- **Twig `min` filter** : `[100, x]|min` remplacé par `min(100, x)` dans `subscription.html.twig`
- **Admin bloqué sur page abonnement** : condition `user.isB2B() and not is_granted('ROLE_ADMIN')`
- **`getUserToolFor()`** : comparaison `===` remplacée par `->getId()` (CLAUDE.md — ne jamais comparer les entités Doctrine par référence)
- **`handleSubscriptionUpdated()`** : gestion de `cancel_at_period_end: true` (annulation depuis le portail Stripe)
- **`SubscriptionController::change()`** : capture des tokens courants avant mutation pour le calcul `$isUpgrade`
- **`declare(strict_types=1)`** ajouté sur `StripeCheckoutService`, `StripeWebhookService`, `SubscriptionController`

## [1.5.0] - 2026-03-30

### Ajouté

- **B2C — infrastructure Stripe** : pose des fondations pour la gestion des abonnements payants
  - Nouvelle entité `UserSubscription` : stocke les données Stripe d'un abonnement (stripeCustomerId, stripeSubscriptionId, billingCycle, status, periodEnd, startedAt)
  - Nouvelle entité `StripeWebhookLog` : table d'idempotence — chaque événement Stripe est enregistré pour éviter le double traitement
  - Nouvelle entité `StripeInvoice` : historique des factures Stripe avec lien PDF natif
  - `Plan` : ajout de `stripePriceIdMonthly` et `stripePriceIdAnnual`
  - Migration `Version20260330100000` : crée les 3 nouvelles tables et altère `plan`
- **Stripe webhook pipeline** : `POST /webhook/stripe` → `StripeWebhookMessage` → Messenger async → `StripeWebhookHandler` → `StripeWebhookService`
  - 5 événements gérés : `checkout.session.completed`, `invoice.payment_succeeded`, `invoice.payment_failed`, `customer.subscription.updated`, `customer.subscription.deleted`
  - Vérification de signature HMAC avant dispatch
- **User** : ajout de `isB2C()` et `isB2B()` pour clarifier les conditions sur le profil utilisateur
- **Variables d'environnement** : `STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY`, `STRIPE_WEBHOOK_SECRET` ajoutés dans `.env`

### Corrigé

- **Tokens négatifs masqués** : `UserToken::getTokensRemaining()` et `TokenService::getTokenStatistics()` retournaient `max(0, ...)` — le solde réel (potentiellement négatif après dépassement) était masqué. Les pourcentages d'affichage restent clampés à [0, 100]
- **Prix annuels -20%** : `InitPlansCommand` appliquait -16.7% au lieu de -20% — Starter 19.20€, Pro 24.00€, Expert 48.00€
- **PlanAccessVoter** : remplacement des emails hardcodés par `$user->isAdmin()` (ROLE_ADMIN)

## [1.4.0] - 2026-03-27

### Ajouté

- **Admin — liste utilisateurs entreprise (Turbo Frame)** : la liste des utilisateurs est désormais chargée en lazy via un `<turbo-frame>` (`/admin/company/{uuid}/users`) avec pagination et recherche, évitant le chargement en mémoire de toutes les entités
  - Nouveau endpoint `admin_company_users` + template `_users_list.html.twig`
  - Barre de recherche avec bouton "Rechercher" aligné
  - Navigation vers le profil utilisateur corrigée (`data-turbo-frame="_top"`)
- **Admin — stats entreprise via SQL** : statistiques utilisateurs calculées en une seule requête SQL (`UserRepository::getStatsForCompany`) au lieu de boucler sur toutes les entités
- **Admin — modal dissociation utilisateur** : nouveau template `_modal_dissociate_shared.html.twig` mutualisé pour la dissociation d'utilisateur
- **UserRegistrationService** : nouveau service centralisant la logique de création de compte (B2C et Google SSO)
- **B2C — onboarding** : tunnel d'inscription B2C avec scroll corrigé (`overflow-y: auto`)
- **Turbo — préchargement désactivé** : `Turbo.config.drive.prefetch = false` pour éviter les requêtes GET parasites au survol des liens

### Corrigé

- **Sécurité — déconnexion intempestive sur page entreprise** : le firewall `api_moodlev5` passé en `stateless: true` pour éviter la migration de session (`session_regenerate_id`) qui créait une race condition entre le Turbo frame lazy et le fetch Stimulus du statut Moodle
- **Admin — création utilisateur** : ajout du `persist($settings)` manquant lors de la création d'un nouvel utilisateur
- **CompanyController — comparaison company manager** : remplacement de `$user->getCompany() === $company` par `$user->getCompany()?->getId() === $company->getId()` (comparaison d'entités Doctrine par ID)
- **Conflit de merge résolu** : `AppLimits::MOODIA_MIN_TOKENS` fixé à 5000, imports dupliqués dans `AuthController` supprimés

### Modifié

- **Landing page** : mises à jour des sections hero, pricing, FAQ, social proof et onboarding
- **Feedback admin** : améliorations de la liste et du formulaire de feedback

## [1.3.0] - 2026-03-26

### Ajouté

- **SSO Google** : connexion via compte Google sur la page de login (`/connect/google`)
  - Nouvel authenticator `GoogleAuthenticator` avec `RememberMeBadge` (session 1 an)
  - Routes publiques `/connect/google` et `/connect/google/callback`
  - Colonne `google_id` ajoutée sur l'entité `User` (migration `Version20260326100000`)
  - Bundle `knpuniversity/oauth2-client-bundle` + `league/oauth2-google` installés
  - Configuration OAuth dans `config/packages/knpu_oauth2_client.yaml`
  - Variables d'environnement `GOOGLE_CLIENT_ID` et `GOOGLE_CLIENT_SECRET`
- **Tests SSO Google** : 19 nouveaux tests
  - `GoogleAuthenticatorTest` (9 tests unitaires) : `supports()`, `onAuthenticationSuccess()`, `onAuthenticationFailure()`
  - `GoogleSSOTest` (10 tests fonctionnels) : routes publiques, bouton UI, entité User

### Corrigé

- **CI/CD** : ajout de `GOOGLE_CLIENT_ID` et `GOOGLE_CLIENT_SECRET` dans les 9 jobs de test
- **TextToSpeechServiceTest** : mise à jour des assertions sur les messages d'erreur (`Impossible de générer l'audio` au lieu de `Erreur`)

## [1.2.0] - 2026-03-19

### Corrigé

- **Couleurs outils DB-driven (admin)** : toutes les icônes d'outils dans l'interface admin lisent désormais `colorPrimary` et `colorSecondary` depuis la base de données, sans aucune couleur hardcodée
  - `admin/users/show` : sections "Outils accessibles" et "Utilisation par outils"
  - `dashboard` : composant activités récentes (`dashboard-activity.html.twig`)
  - `home/activities` : historique des activités
  - `_molecules/contract-row` : liste des contrats (company/show et users/show)
  - `admin/contract/_card` : badges outils dans les cartes contrats
  - `admin/contract/show` : section "Outils autorisés"
  - `admin/contract/_form_fields` : checkboxes outils dans le formulaire ajout/édition
- **Landing pages** : correction des accents manquants (`pédagogie`, `Créez`, `déployez`, `Pensé`, `pédagogues`, `jusqu'à`) sur `index.html.twig`, `monalisia.html.twig`, `castia.html.twig`

## [1.1.9] - 2026-03-18

### Modifié

- **Mockups hero (Slidia, Serenia, Monalisia, SAM)** : suppression du wrapper décalé avec carte arrière-plan, augmentation des tailles de police ~40% pour améliorer la lisibilité utilisateur
  - Slidia : sidebar élargie 140→170px, fonts 6-9px → 9-12px
  - Serenia : chat fonts 8-8.5px → 11-12px, badges tokens/Wh 7px → 10px
  - Monalisia : tous les labels/tabs/sections 5-7.5px → 8-11px
  - SAM : navigation, assiduité, table formations 5.5-8.5px → 8-11px

## [1.1.8] - 2026-03-18

### Modifié

- **Pages outils (Castia, Serenia, Slidia, Moodia, Monalisia, SAM)** : tous les boutons CTA héros pointent désormais vers `path('app_landing') ~ '#beta-signup'` au lieu d'ancres locales ou de la page de connexion
- **Castia landing** :
  - Icône tokens remplacée par SVG solar/token en couleur `c1` (vert)
  - Sections "Comment ça marche" et "14 voix françaises" supprimées
  - Section sociale proof et CTA beta signup supprimés
  - Section intégration Moodia redessinée : gauche alignée sur le design system (bullets ronds `w-5 h-5`, dégradé de vert foncé→clair), droite mockup browser chrome
  - Badge "À venir" ajouté sur la section intégration
  - Eyebrow badges redessinés en capsule connectée (Castia → Moodia)
  - Bouton "Découvrir Moodia" avec couleurs et gradient de Castia
  - Toutes les occurrences de "Moodle" remplacées par "LMS"
- **Pricing organism** : fond blanc + bordure sur le toggle (style filtres Moodia), bouton annuel actif en bleu par défaut, prix de base annuels (20/25/50€) avec +20% en mensuel, badge `−20%` vert sur fond emerald
- **Button atom** : ajout du variant `white` (fond blanc, texte primary, shadow-sm) documenté dans l'en-tête
- **`config/services.yaml`** : version bumped `1.1.7` → `1.1.8`

## [1.1.7] - 2026-03-17

### Modifié

- **Hero landing** : remplacement du badge générique par un bloc social proof — avatars Gravatar réels (4 derniers utilisateurs actifs via `UserRepository::findRecentActive`), compteur dynamique DB, dot vert live
- **Hero orbital** : nodes outils rendus cliquables (liens vers pages landing de chaque outil)
- **Bento outils** : ajout d'une arrow hover top-right sur chaque carte + clic vers la page landing de l'outil
- **Moodia landing** :
  - H1 : "Un PDF, un sujet, ou partez de zéro."
  - Mockup hero responsive : version mobile simplifiée (éditeur colonne unique) + version desktop (sidebar + canvas)
  - Section "Comment ça marche" : redesign en cards inline `grid-cols-4` (style Slidia), couleurs DB
  - Section "Avant/Après" : redesign en tableau comparatif — desktop tableau continu, mobile cartes empilées avec `gap-3`
- **Slidia landing** : section "Avant/Après" alignée sur le même design tableau que Moodia, responsive identique
- **Serenia landing** : toutes les couleurs basées sur `tool_color_details['serenia']` (DB), chrome mockup light (#F0F4FA + macOS dots réels), badge bêta nettoyé
- **Sidebar** : police Poppins (`font-primary`) appliquée via `nav-item` au lieu de Roboto
- **`LandingController`** : calcul et passage de `heroAvatars` (URLs Gravatar MD5) et `activeUserCount` au hero
- **`UserRepository`** : ajout méthode `findRecentActive(int $limit)`
- **`config/services.yaml`** : version bumped `1.1.6` → `1.1.7`

## [1.1.6] - 2026-03-15

### Ajouté

- **Landing Slidia (`/outils/slidia`)** : création complète de la page landing dédiée à Slidia
  - Hero avec badge rose dynamique (nombre de formateurs), titre gradient rose, mockup pile de slides animée
  - Section "Comment ça marche" : 4 étapes avec accent rose (steps-tool organism)
  - Section features alternées : 4 features (masques d'établissement, 9 layouts, génération PDF/texte, éditeur roadmap)
    - Stats avant/après : 8× pour la charte graphique, 15× pour la création complète
  - Section "Avant / Après" : tableau comparatif 5 lignes avec gradient blanc→rose + flèche, responsive mobile
  - Section autres outils : 5 outils (Moodia en tête)
  - FAQ Slidia : 5 questions sur la génération, les masques, les formats et les multi-clients
- **LandingController** : passage de `slidiaUserCount` à la vue Slidia via `countDistinctUsersByToolCode('slidia')`

### Modifié

- **`config/services.yaml`** : version bumped `1.1.5` → `1.1.6`

## [1.1.5] - 2026-03-15

### Ajouté

- **Landing Moodia (`/outils/moodia`)** : création complète de la page landing dédiée à Moodia
  - Hero avec badge dynamique (nombre de formateurs via `countDistinctUsersByToolCode`), titre gradient, CTA et mockup navigateur
  - Section "Comment ça marche" : 4 étapes avec design responsive (horizontal mobile, grille 2 col tablet, 4 col desktop)
  - Section features alternées : 3 features avec stat avant/après (36×, 24×, 120×)
  - Section "Avant / Après" : tableau comparatif inline avec gradient blanc→bleu + flèche circulaire, responsive mobile (2 rangées avec `border-t` + flèche inline)
  - Section autres outils : 5 outils en 1 colonne mobile / 3 colonnes tablet / 5 colonnes desktop
  - Spots décoratifs bleus, animations hero, scroll reveal corrigé (scope élargi à `.reveal` global)

### Modifié

- **Header landing** : bouton "Essayer" masqué sur mobile (`hidden lg:inline-flex`), conservé dans le menu burger
- **Organism `steps-tool`** : responsive revu — layout horizontal mobile, grille 2 col tablet, 4 col desktop
- **Organism `features-alternating`** : stat avant/après adapté mobile (padding et tailles de texte réduits)
- **Organism `tools-ecosystem`** : grille `grid-cols-1 sm:grid-cols-3 lg:grid-cols-5`, cartes horizontales sur mobile

### Corrigé

- **Scroll reveal (Turbo Drive)** : la CSS globale `.reveal { opacity: 0 }` de `index.html.twig` persistait dans le `<head>` lors de la navigation Turbo, rendant le footer et le CTA invisibles sur `/outils/moodia` — corrigé en élargissant le sélecteur JS de `.moodia-page .reveal` à `.reveal`

## [1.1.4] - 2026-03-15

### Modifie

- **Landing — outils & demo** : ajout d'une mention "Les visuels présentés sont à titre illustratif et ne sont pas contractuels" sous la grille bento et sous le mockup demo
- **Landing — FAQ** : suppression de la mention "100% conforme RGPD" remplacée par un message honnête sur la protection des données et l'objectif de souveraineté
- **Landing — CTA** : suppression des badges "Conforme RGPD" et "Hébergé en Europe"
- **Landing — footer** : tagline mis à jour, logo icône seule en grande taille (nouvelle taille `2xl` = 96px), espacement des liens de navigation augmenté
- **Logo atom** : ajout de la taille `2xl` (`w-24 h-24`) dans `logo-with-text.html.twig`

## [1.1.3] - 2026-03-15

### Corrige

- **CI/CD** : restart `php8.4-fpm` après le déploiement pour vider l'OPcache PHP

## [1.1.2] - 2026-03-15

### Corrige

- **CI/CD** : build assets dans le runner CI (plus fiable que sur le serveur), `cache:clear` déplacé après le transfert des assets, `chown www-data` ajouté pour que le serveur web puisse réécrire le cache
- **Tailwind v4** : ajout des directives `@source "../../templates"` et `@source "../../assets/controllers"` dans `app.css` pour que Tailwind scanne correctement les templates Twig et les controllers Stimulus au build

## [1.1.1] - 2026-03-15

### Ajoute

- **Button atom `mobileIconOnly`** : nouveau parametre `mobileIconOnly` (bool) + `mobileBreakpoint` ('sm' | 'md' | 'lg') sur `_atoms/button/button.html.twig` — icone seule sous le breakpoint, texte complet au-dessus, elimine le besoin de deux includes en miroir
- **CLAUDE.md** : documentation des patterns responsive du bouton atom (`w-full sm:w-auto` et `mobileIconOnly`)

### Corrige

- **Responsive global** : ajout de `overflow-x-hidden` sur `<body>` pour supprimer le scroll horizontal parasite sur toutes les pages
- **Admin users** : table avec colonnes Outils/Tokens cachees sur mobile, filtres en colonne
- **Admin contracts** : table responsive (Entreprise `sm:`, Periode `md:`, details `lg:`), header et filtres en colonne sur mobile
- **Admin contract add/edit** : boutons d'action en colonne sur mobile
- **Admin company show** : header, sections Contrats et Utilisateurs responsive, statuts et tokens caches sur mobile
- **Contract-row molecule** : outils et metadonnees adaptes mobile
- **Castia** : suppression du scroll horizontal (chips en `flex-wrap`), couleur rouge du bouton favori corrigee (toggle Stimulus)
- **Monalisia** : bouton "Ameliorer avec l'IA" pleine largeur sur mobile uniquement
- **Moodia editor** : breadcrumb tronque proprement (`min-w-0 truncate`), header compact (`gap-2 sm:gap-4`), bouton Export MBZ migre vers `mobileIconOnly: true, mobileBreakpoint: 'lg'`

## [0.9.124] - 2026-03-09

### Ajoute

- **SAM — Activite recente async** : chargement avec skeleton au refresh de la page, remplace le rendu Twig statique
- **Nouvel endpoint GET /api/moodlev5/submissions** : liste paginee des completions avec filtres `user`, `type`, `days` et pagination (`page`, `limit`)
- **Stimulus sam--recent-activity** : controleur JS avec skeleton → fetch → rendu des lignes ou empty state
- **Documentation API 08-submissions.md** : description complete de l'endpoint avec exemples cURL et tableau des parametres

### Modifie

- **SAM dashboard** : design des graphiques (chart progressions journalieres avec gradient, crosshair, tooltip externe), boutons de periode uniformises (sam-period-btn), filtres de types scrollables avec label "Filtrer", icones sur les headers de cartes, suppression des bordures (shadow-md)
- **SAM — Resolution company admin** : UserContextService tombe sur la session `sam_selected_company_uuid` si le parametre `?company=` est absent (corrige l'erreur pour les admins qui cliquent sur un stagiaire)

### Corrige

- **SAM admin** : erreur "Le parametre company est requis pour les administrateurs" lors de la navigation vers un stagiaire depuis la vue admin

## [0.9.123] - 2026-03-07

### Ajoute

- **Nouveaux types d'activites Moodia** : label, forum, assignment, file disponibles dans le plan de cours et la generation de contenu (MoodleActivityTypesConfig, services.yaml)
- **Generateurs de contenu** : LabelActivityGenerator, ForumActivityGenerator, AssignmentActivityGenerator, FileActivityGenerator implementant ActivityGeneratorInterface
- **Generateurs MBZ export** : UrlGenerator (mod_url), LabelGenerator (mod_label), ForumGenerator (mod_forum), AssignmentGenerator (mod_assign) pour l'export Moodle
- **Champ url sur Activity** : modele export Activity enrichi d'un champ url pour les activites de type URL
- **Gestion des nouveaux types dans CourseTransformer** : transformUrlActivity, transformLabelActivity, transformForumActivity, transformAssignmentActivity, transformFileActivity
- **Export URL natif** : les activites URL sont maintenant exportees en mod_url (au lieu de mod_page)

### Modifie

- **+20% temps pedagogique** : content_time_multiplier passe de 1.0 a 1.2 dans PedagogicalConfig — l'IA genere 20% de contenu supplementaire
- **Sections libres** : suppression des contraintes de nombre de sections dans CoursePlanGenerator — l'IA decide librement du nombre de sections selon le contenu
- **Quiz paralleles** : les quiz sont desormais generes en parallele avec les pages (Phase B) au lieu d'une Phase C separee
- **Nettoyage MoodiaStreamingService** : suppression de placeImagesInCourseById() (mort), sectionMapping, activityMapping, resetGenerationState(), doublon de docblock sur runImagePlacement()
- **Suppression galerie photos** : la section "Photos du cours" est supprimee de l'export MBZ (les images sont deja placees en ligne dans les pages)
- **Prompts sans exercices** : PageActivityGenerator ne suggere plus d'exercices pratiques dans le contenu genere

### Corrige

- **MoodiaContentController** : les types label, forum, assignment, file sont correctement persistes en BDD (au lieu de fallback sur TYPE_PAGE)

## [0.9.122] - 2026-03-02

### Ajoute

- **Internationalisation Moodia (6 langues)** : creation de `MoodiaTranslations` (src/Config/) centralisant toutes les traductions des contenus generes (fr, en, es, de, it, pt)
- **Prompts AI traduits** : les exemples JSON dans les prompts de `CourseGenerationService` (introduction, glossaire, descriptions) utilisent des exemples dans la langue cible pour eviter le biais francais
- **Streaming SSE traduit** : tous les titres hardcodes dans `MoodiaStreamingService` (QUIZZ FINAL, Introduction, Credits, "Cours genere avec") sont desormais traduits selon la langue du cours
- **Persistance traduite** : les valeurs par defaut dans `MoodiaPersistenceService` (Glossaire, Credits, "Cree avec Bubul.app") respectent la langue du cours
- **Export MBZ traduit** : tous les labels dans `CourseTransformer` (Documents additionnels, Niveau, Duree, Public cible, Glossaire, Photos du cours, etc.) sont traduits dans la langue du cours

## [0.9.121] - 2026-03-02

### Ajoute

- **Onboarding first-time user** : overlay d'accueil anime en 6 etapes (bienvenue, outils de creation, outils complementaires, tokens, empreinte ecologique, CTA) avec animations premium (spring easing, stagger reveals, morphing dots, slide transitions)
- **Champ Settings.onboardingCompleted** : boolean persiste en BDD pour ne plus afficher l'overlay apres completion
- **Endpoint POST /api/settings/onboarding-complete** : API pour marquer l'onboarding termine
- **Stimulus controller shared--onboarding** : navigation clavier (fleches, Escape), transitions animees entre etapes, fire-and-forget persistence
- **CSS onboarding.css** : systeme complet d'animations (overlay fade, step slides, tool icon pop-in, rocket float, CTA pulse, progress dot morphing)

### Corrige

- **Castia favoris design** : classes Tailwind manquantes dans `_createFavGroup()` JS (le groupe favoris cree dynamiquement n'avait pas les styles inline du template)

## [0.9.120] - 2026-03-01

### Ajoute

- **Empreinte ecologique utilisateur** : section eco-footprint (energie Wh, CO2, eau + graphique barres hebdomadaire) ajoutee sur la fiche admin utilisateur (`/admin/users/{uuid}`)
- **Repository multi-users** : nouvelles methodes `getWeeklyEnergyForUsers(array)` et `getPreviousWeekEnergyForUsers(array)` pour agreger les donnees energetiques de plusieurs utilisateurs en une seule requete SQL

## [0.9.119] - 2026-03-01

### Ameliore

- **Couleurs dynamiques par outil** : remplacement des couleurs `primary` hardcodees par `tool_colors[tool.code]` dans 11 templates (dashboard, admin users/company/contracts). Chaque outil affiche sa couleur propre (moodia=bleu, serenia=violet, slidia=orange, monalisia=rose, castia=vert, sam=cyan)
- **Champ Tool.color** : ajout du champ `color` en base de donnees (token CSS Tailwind) avec migration, fixtures et formulaire admin
- **Global Twig tool_colors** : nouveau global `tool_colors` injecte par `ToolAccessExtension` pour acceder aux couleurs dans tous les templates
- **Filtre outils utilisateurs** : le dropdown filtre outils sur `/admin/users` utilise les couleurs par outil (hover, checked, checkbox, label)
- **Safelists Tailwind** : ajout des classes dynamiques necessaires dans les templates pour la generation JIT

## [0.9.118] - 2026-03-01

### Corrige

- **Hauteur Serenia/Slidia** : ajustement des conteneurs principaux pour compenser les fils d'ariane ajoutes

## [0.9.117] - 2026-03-01

### Ameliore

- **Page detail utilisateur** : section-dividers avec icones, icones `bubul-*` duotone par outil, couleurs alignees sur le dashboard (serenia=cohorts, monalisia=pink, castia=success), scroll sur la liste outils accessibles et utilisation par outils, suppression progress bar tokens
- **Fil d'ariane outils** : breadcrumb ajoute sur Moodia, Serenia, Slidia, Castia, Monalisia
- **Fil d'ariane entreprise** : breadcrumb conditionnel (admin voit "Entreprises > nom", manager voit "nom" seul)
- **Couleurs Chart.js** : monalisia (pink) et castia (success) corrigees pour correspondre au dashboard

### Supprime

- **Section "Pour bien commencer"** : guide de demarrage et acces rapides retires du dashboard (doublon avec tool-cards)

## [0.9.116] - 2026-03-01

### Ajoute

- **Documentation design system** : 12 composants manquants documentes avec exemples live, code et parametres
  - Molecules : badge-card, section-divider, stepper, tip, tool-card
  - Organismes : auth-visual, choice-card, dashboard-activity, eco-footprint, feedback-widget, forgot-password-form, reset-password-form
- **Index design system** : ajout des 12 cartes dans la page index avec badges "Nouveau"
- **Routes design system** : 12 nouvelles routes dans DesignSystemController

### Corrige

- **Modal association utilisateur** : le bouton "Fermer" ne fonctionnait pas car le `data-controller` Stimulus etait dans le `{% block body %}` et ne couvrait pas le `{% block footer %}` (scope DOM separe dans le template modal). Deplacement du controller sur le wrapper englobant.

## [0.9.115] - 2026-03-01

### Corrige

- **Serenia saisie vocale** : restauration du bouton microphone omis lors de la refonte Tailwind, avec icone Solar et animation pulse rouge lors de l'enregistrement

## [0.9.114] - 2026-03-01

### Ameliore

- **Filtres utilisateurs Tailwind** : conversion complete des 4 filtres dropdown (role, statut, entreprise, outils) en Tailwind CSS pur avec `group/filter` et `group-has-[:checked]/opt` pour les etats interactifs
- **Recherche a droite** : barre de recherche deplacee apres les filtres dropdown pour un meilleur agencement
- **Icones Solar SVG** : remplacement des icones `_atoms/icon/icon.html.twig` par des includes Solar SVG directs dans la colonne outils du tableau
- **toolSolarMap** : mapping centralise des codes outils (moodia, serenia, slidia...) vers les noms fichiers Solar correspondants
- **Nettoyage CSS** : suppression des imports CSS legacy de `app.js` (sidebar, components, pages)

## [0.9.113] - 2026-03-01

### Ameliore

- **Company forms (edit/add)** : refonte complete avec Tailwind + atomic design (page-header, card molecule, button atoms), suppression des CSS BEM legacy
- **Icones admin Solar SVG** : migration de toutes les icones `_components/icons/` vers `_atoms/icon/solar/` dans la liste utilisateurs (shield, star, user, power, check, buildings, settings)
- **Molecule tip** : creation d'une molecule astuce reutilisable avec variantes (warning, info, success, primary)
- **Castia voix** : redesign complet de la section MES VOIX en Tailwind (chips, avatars, boutons play/favori)
- **Castia footer** : astuce + bouton generer dans une card unifiee style Monalisia
- **Slidia mask** : icone agrandie et background bleu dans la section mask
- **Icon atom** : guard `iconPath is defined` pour eviter les crashes sur icones inconnues
- **Dashboard** : tokens mensuels calcules depuis les contrats actifs de l'entreprise

## [0.9.112] - 2026-03-01

### Ameliore

- **Historique d'activites (J-30)** : la page `/recent-activities` affiche desormais les 30 derniers jours uniquement, y compris lors du filtrage par outil
- **Outils dynamiques depuis la BDD** : les listes d'outils dans les filtres, settings et dashboard sont chargees depuis la table `tool` au lieu d'etre hardcodees
- **Filtrage par isTokenable** : seuls les outils consommant des tokens apparaissent dans les filtres d'activites et les onglets settings (exclut SAM)
- **Graphique vide avec axes** : quand aucune activite n'existe, le graphique Chart.js affiche les 30 jours sur l'axe X et 0 sur l'axe Y
- **Activites recentes simplifiees** : liste epuree sur le dashboard avec icone outil, titre, date/heure, tokens et energie
- **Badges gamification dynamiques** : un badge par outil tokenable, debloque automatiquement a la premiere utilisation
- **Corrections visuelles** : couleurs monalisia (pink), castia (success), CO2 (warning), formatage nombres FR, accents

## [0.9.111] - 2026-03-01

### Ameliore

- **Refonte visuelle du dashboard** : polish complet inspire du mockup React
  - Orbes ambient en arriere-plan avec animations flottantes (gradient bleu, vert, violet)
  - Animations stagger fade-up sur chaque section (7 niveaux de delai)
  - Titres de section enrichis avec mots-cles en italique colore ("Vos *outils*", "Empreinte *ecologique*")
  - Nom utilisateur en gradient bleu dans le header
  - User pill avec backdrop-blur glass effect

- **Tool cards** : radial glow orb au hover qui s'agrandit, ombre coloree plus profonde, stats en font Poppins

- **Badge cards** : glow colore par variant au hover, radial gradient decoratif, indicateur recent plus visible

- **Eco-footprint** : barres du graphique plus larges (40px) et vivides, glow colore par seuil (vert/orange/rouge), valeurs metriques en font Poppins 3xl, cercles decoratifs supplementaires

- **Section divider** : icone avec gradient background, titre en 28px, espacement ameliore

- **Stat cards** : glass effect (bg-white/80 + backdrop-blur), transition cubic-bezier plus fluide, ombre composite au hover

- **Dashboard activity** : glass effect sur le conteneur, ombre subtile au hover sur les items

- **Design tokens** : 7 nouvelles animations `fade-up-{0-6}` dans variables.css pour stagger d'entree

### Corrige

- Correction filtre Twig `|max`/`|min` → fonctions `max()`/`min()` dans tool-card et eco-footprint (4 occurrences)
- Correction icone `drop` inexistante → `cloud` dans eco-footprint
- Correction icone `medal-star` inexistante → `crown` dans section badges
- Correction icone `widget` → `widget-add` dans la documentation section-divider

## [0.9.110] - 2026-02-28

### Ajoute

- **Redesign complet du dashboard**
  - Nouvelle structure : Stats (5 cartes) > Outils > Empreinte ecologique > Badges > Activite recente
  - Salutation contextuelle basee sur l'heure (Bonjour/Bon apres-midi/Bonsoir)
  - Cartes outils enrichies avec statistiques par outil (tokens, energie, contenus generes)
  - Section empreinte ecologique avec metriques (energie Wh, CO2, eau) et graphique barres hebdomadaire
  - Systeme de badges/gamification (deverrouilles, en progression, verrouilles)
  - Liste d'activites recentes filtrable par outil avec pagination client-side

- **Nouveaux composants Atomic Design**
  - Molecule `section-divider` : separateur de section avec icone, titre, sous-titre et ligne decorative
  - Molecule `tool-card` : carte outil riche avec stats, sparkline, detail expandable
  - Molecule `badge-card` : carte achievement avec etats deverrouille/progression/verrouille
  - Organisme `eco-footprint` : empreinte ecologique avec metriques et graphique
  - Organisme `dashboard-activity` : liste d'activites filtrable et paginee
  - Extension molecule `stat-card` : nouveaux params previousValue, delta, deltaDown (hover reveal)

- **Nouveaux services PHP**
  - `GreetingService` : salutation contextuelle reutilisable (dashboard, emails, notifications)
  - `DashboardDataService` : agregation des donnees dashboard (stats, outils, eco, activites, guide)

- **Nouveau Stimulus controller**
  - `dashboard_activity_controller.js` : filtrage par outil et pagination client-side

### Ameliore

- **DashboardController simplifie** : logique metier extraite dans DashboardDataService et GreetingService (210 -> 40 lignes)

## [0.9.109] - 2026-02-28

### Ajoute

- **Selecteur de langue pour la generation de cours**
  - 6 langues supportees : Francais, English, Espanol, Deutsch, Italiano, Portugues
  - Injection de l'instruction de langue dans tous les prompts (plan, pages, audio, quiz, intro, glossaire)
  - Nouveau champ dans le formulaire de configuration avec apercu en temps reel
  - Affichage de la langue sur les cartes du dashboard avec icone earth
  - Persistance en BDD (colonne language, migration)

### Ameliore

- **Robustesse de la generation de cours**
  - Retry individuel des pages echouees (3 tentatives supplementaires via generateSectionContent)
  - Suppression des notifications d'erreur pour les echecs de pages (traitement silencieux)
  - Total : 2 retries paralleles + 3 retries individuels = 5 tentatives avant abandon

- **Notifications de suppression ameliorees**
  - Titre et description separes (ex: "Activite supprimee" / "XXX a bien ete supprime.")

- **Icone earth parametrisee** (Solar duotone) ajoutee au registre d'icones

- **Correction estimation temps restant** dans la generation (countdown simple au lieu du calcul dynamique instable)

### Supprime

- Description des quiz dans l'export MBZ (inutile)

## [0.9.108] - 2026-02-28

### Ameliore

- **Redesign complet du rendu des cours textuels**
  - Enrichissement du prompt systeme AI : lead paragraphs, titres avec HR gradient, sous-titres avec dash colore, 6 types d'encadres pedagogiques, processus numerotes, encadre "A retenir" en fin de section
  - Wrapper HTML d'export Moodle avec banniere Bubul blue pour pages texte, audio et glossaire (max-width 800px, inline styles uniquement)
  - Amelioration CSS editeur WYSIWYG : typographie Inter, tables, images arrondies, listes visibles (fix preflight Tailwind), separateurs gradient

- **UX editeur amelioree**
  - Toolbar et breadcrumb sticky au scroll
  - Scroll to top automatique au changement d'activite
  - Descriptions d'activites raccourcies (15 mots max) avec temps d'activite affiche
  - Suppression de la description quiz (inutile)

- **Export Moodle simplifie**
  - Glossaire sobre avec design epure (banniere bleue, termes avec separateur simple)
  - Suppression du "Temps de lecture / activite" dans l'onglet Generalite
  - Quiz utilise la vraie description au lieu du texte generique

### Technique

- Refactoring configs pedagogiques et streaming
- Mise a jour des tests unitaires associes

## [0.9.107] - 2026-02-28

### Ameliore

- **Performance generation : Suppression du streaming mot-a-mot**
  - Remplacement de `streamContent()` (envoi par groupes de 2 mots avec 35ms de delai) par `sendPageContent()` (envoi complet en un seul event SSE)
  - Gain estime de ~3 min 16s pour un cours de 14 pages (suppression de ~14s de delais artificiels par page)
  - Suppression de `sendTargetedContent()` dans StreamingHelper et `MOODIA_WORD_DELAY_US` dans StreamingConfig
  - Les events SSE `status`, `done`, `error` restent inchanges

- **Editeur Moodia : Champ description editable par activite**
  - Nouveau champ `description` (TEXT nullable) sur l'entite MoodiaActivity
  - Champ editable dans l'editeur entre le titre et le contenu de chaque activite
  - Placeholder : "Description de l'activite (utilisee a l'export Moodle)..."
  - Sauvegarde automatique via le systeme existant `updatePageField`

- **Export MBZ : Description personnalisee des activites**
  - L'export utilise la description stockee en priorite (`activity->getDescription()`)
  - Fallback sur la description auto-generee si le champ est vide
  - Fonctionne pour les pages standard et les pages audio

### Technique

- Migration Doctrine `Version20260228140000` : ajout colonne `description` a `moodia_activity`
- PersistenceService : serialisation/deserialisation du champ `description` (JSON <-> BDD)

## [0.9.106] - 2026-02-28

### Ameliore

- **Editeur Moodia : Vue section redesignee en liste verticale**
  - Les activites sont affichees les unes sous les autres avec details (icone, titre, type, apercu du contenu, statut, temps estime)
  - Remplacement du layout horizontal par une liste verticale plus lisible
  - Indicateur de statut par activite (vide, en cours, complete)
  - Apercu du contenu texte (120 premiers caracteres)

- **Editeur Moodia : Temps pedagogique calibre**
  - Affichage du temps estime de la section et du cours entier
  - Comparaison avec la duree configuree du cours (badge vert si +/- 10%, orange/rouge sinon)
  - Format "Cours : Xh / Yh (Z%)" pour une lecture immediate

### Corrige

- **Editeur Moodia : Navigator disparait visuellement**
  - Remplacement de `overflow-hidden` + `max-height: 0` par `display: none` (classe `hidden`) pour le collapse des sections
  - Suppression de `transition-all` sur les items de section qui causait des glitches visuels
  - Collapse/expand plus fiable et sans artefacts

## [0.9.105] - 2026-02-28

### Corrige

- **Editeur Moodia : Navigation qui revient en arriere**
  - Position sauvegardee via `sectionIndex + pageIndex` au lieu du flat index
  - Compatibilite avec la nouvelle structure flat list incluant les entrees section
  - Fallback pour l'ancien format de sauvegarde

- **Editeur Moodia : Navigator disparait apres suppression d'activite**
  - Deep clone du cours avant suppression (evite les references corrompues)
  - Ajustement robuste de l'index courant apres suppression

- **Editeur Moodia : Introduction reapparait apres suppression**
  - Suppression du champ top-level `course.introduction` quand l'intro est supprimee
  - Sauvegarde immediate (pas debounced) pour les suppressions d'introduction

### Ameliore

- **Editeur Moodia : Notifications de suppression detaillees**
  - Notification inclut le type et le nom de l'activite (`Page "Introduction" supprimee`)
  - Pour les sections : affiche le nombre d'activites supprimees

- **Editeur Moodia : Temps pedagogique estime dans la vue section**
  - Calcul automatique par activite (texte: chars/1000 x 1.5, audio: x 2.0, quiz: 0.5 min/question)
  - Badge temps total affiche dans l'en-tete de la liste des activites
  - Temps individuel affiche sous chaque carte d'activite

- **Editeur Moodia : Bouton supprimer section depuis la vue section**
  - Icone corbeille dans le header de la carte section (comme les autres activites)
  - Protege si c'est la derniere section

- **Editeur Moodia : Design du bouton "+" ameliore**
  - Bouton d'ajout dans le navigator avec bordure dashed, icone et texte
  - Hover avec couleur primaire et fond subtil
  - Bouton d'ajout dans la vue section avec design uniforme

- **Editeur Moodia : Modal export succes redesignee**
  - Design plus premium avec fond decoratif gradient et cercles flous
  - Sparkles avec 3 orbites differenciees (rayon, vitesse, delay)
  - Etapes d'import sous forme de cartes avec icone, titre, description et numero
  - Separateur central avec icone Moodia
  - Animations fade-up staggerees pour chaque element

### Technique

- Nouvelles fonctions utilitaires JS : `estimateTime()`, `formatTime()`
- Sparkle CSS : 3 keyframes distincts (`sparkle-orbit-1/2/3`) avec orbites variees

## [0.9.104] - 2026-02-28

### Ajoute

- **Editeur Moodia : Vue section dans la zone principale**
  - Clic sur une section dans le navigator affiche une vue dediee
  - Titre editable, description (textarea) sauvegardee automatiquement
  - Liste horizontale des activites avec icones et bouton "Ajouter"
  - Champ `description` ajoute a l'entite `MoodiaSection` (migration Doctrine)
  - Description exportee comme section summary dans le format MBZ

- **Editeur Moodia : Suppression de sections**
  - Bouton trash visible au hover sur les headers de section
  - Modal de confirmation avec texte conditionnel (section vs activite)
  - Protection contre la suppression de la derniere section

- **Editeur Moodia : Bouton "+" par section**
  - Bouton d'ajout d'activite en fin de chaque section dans le navigator
  - Ouvre la modal d'ajout ciblee sur la section concernee
  - Bouton "Ajouter" retire de la barre de pagination globale

### Ameliore

- **Editeur Moodia : Quiz comme activite normale**
  - Quiz normalise dans `pages[]` avec `type: 'quiz'` au lieu de `section.quiz`
  - Quiz draggable et repositionnable comme toute autre activite
  - Denormalisation automatique a la sauvegarde pour compatibilite backend/MBZ
  - Bouton supprimer sur les quiz (comme les autres activites)

- **Editeur Moodia : Fix collapse/expand des sections**
  - `draggable="true"` deplace du wrapper vers le grip handle uniquement
  - Les clics sur le header de section fonctionnent pour toggle + naviguer

- **Editeur Moodia : Progress bar export**
  - Gradient corrige : `from-gradient-start via-gradient-middle to-gradient-end` (tout bleu)
  - Texte explicatif ajoute sous la barre de progression

- **Editeur Moodia : Modal succes export premium**
  - Checkmark anime en SVG (stroke-dasharray draw)
  - 3 sparkles en orbite autour du cercle de succes
  - Etapes avec fade-in stagger (delays 0.3s a 0.9s)
  - Icones colorees par etape (user, settings, upload, check)
  - Transition smooth progress → tutoriel (scale-fade-out/in)

### Technique

- Migration Doctrine : `ALTER TABLE moodia_section ADD description LONGTEXT DEFAULT NULL`
- `MoodiaPersistenceService` : lecture/ecriture du champ description
- `CourseTransformer` : description utilisee comme section summary MBZ
- Nouvelles animations CSS : `check-draw`, `fade-up-in`, `sparkle-orbit`, `scale-fade-in/out`, `success-pulse`

## [0.9.103] - 2026-02-26 - IN PROGRESS

### Ameliore

- **Editeur Moodia : Player Audio Custom **
  - Player audio personnalise avec bouton play/pause rond
  - Barre de progression cliquable pour naviguer dans l'audio
  - Affichage du temps (`0:00 / 1:23`) avec mise a jour en temps reel
  - Animation soundwave qui s'active uniquement pendant la lecture
  - Branding  ("Généré avec ", "Powered by ")
  - Design de chargement ameliore avec meme structure que le player final

- **Editeur Moodia : Toolbar WYSIWYG enrichie**
  - Nouveaux boutons : Barré, Alignement (gauche/centre/droite), Citation, Supprimer formatage
  - Icones SVG pour tous les boutons (plus de texte/emoji)
  - Pas de bouton "Code HTML" (comme demande)
  - Floating toolbar desactivee (toolbar fixe uniquement)

- **Editeur Moodia : Persistance de la position**
  - La position de l'activite courante est sauvegardee dans localStorage
  - Au refresh, l'utilisateur reste sur la meme activite
  - Cle unique par cours (`moodia_editor_position_{uuid}`)

- **Editeur Moodia : Icone Page en bleu**
  - Icone `file-text` ajoutee au mapping ICONS
  - Couleur bleue (`#3B82F6`, `bg-blue-50`, `text-blue-500`)

### Technique

- Evenements audio HTML5 : `loadedmetadata`, `timeupdate`, `play`, `pause`, `ended`
- Animation CSS `loading-bar` pour la barre de progression du chargement
- Styles CSS pour `blockquote` et `strikethrough` dans WYSIWYG

## [0.9.102] - 2026-02-26

### Ameliore

- **Editeur Moodia : UX Refinements**
  - Navigator : Hover plus simple (fond gris subtil) sans bordure "IA-style"
  - Drag & Drop : Couleurs plus visibles (`bg-primary/20` + `ring-2 ring-primary/30`)
  - Activite Page : Icone `file-text` avec couleur slate plus douce (au lieu de bleu fonce)
  - Bouton poubelle : Taille reduite (`w-7 h-7` au lieu de `w-8 h-8`)
  - Indicateur sauvegarde : Design epure avec dot ping anime

### Technique

- Fonction `updateSaveStatus()` reecrite pour nouvelle structure HTML
- Support du ping anime pendant la sauvegarde
- Coherence visuelle avec le reste du site

## [0.9.101] - 2026-02-26

### Ameliore

- **Editeur Moodia : Migration 100% Tailwind CSS**
  - Template `editor.html.twig` entierement converti en classes Tailwind
  - Tous les composants UI utilisent l'atomic design (boutons, badges, modals)
  - Navigator sidebar avec classes Tailwind centralisees dans le JS (`TW` object)
  - Activity Card, Quiz Editor, Audio Section en Tailwind pur

- **Controller JavaScript : Refactoring Tailwind**
  - Toutes les classes CSS custom remplacees par Tailwind inline
  - Object `TW` centralisant les classes pour maintenir la coherence
  - Classes d'etat (dragging, active, drag-over) en Tailwind
  - Animations et transitions utilisant les utilitaires Tailwind

- **CSS Minimaliste**
  - Fichier `editor.css` reduit a ~100 lignes
  - Contient uniquement : @keyframes, scrollbar custom, WYSIWYG typography
  - Tout le reste migre vers Tailwind

### Technique

- Suppression de ~1200 lignes de CSS custom
- Classes Tailwind definies comme constantes JS pour le DRY
- Support des data-attributes pour les etats dynamiques
- Meilleure maintenabilite grace a l'atomic design

## [0.9.100] - 2026-02-26

### Corrige

- **Navigator : Position Fixe**
  - Le Navigator utilise maintenant `position: fixed` (au lieu de `sticky`)
  - Reste toujours visible en haut à gauche, ne bouge jamais
  - Le canvas a un `margin-left` pour compenser
  - Sur mobile (<1024px), le canvas reprend toute la largeur

## [0.9.99] - 2026-02-26

### Corrige

- **Layout : Scroll Independant**
  - Le Navigator (sidebar) reste fixe quand on scroll le contenu du cours
  - `overflow: hidden` sur le layout principal
  - `height: 100%` et `flex-shrink: 0` sur le Navigator
  - Seul le canvas (`.editor-canvas__content`) scrolle

## [0.9.98] - 2026-02-26

### Ameliore

- **Navigator : Gestion du Scroll**
  - Scrollbar stylee (fine, grise, arrondie)
  - Ombre subtile sous le header indiquant du contenu en dessous
  - Padding en bas pour eviter que le dernier element soit coupe
  - Support Firefox (`scrollbar-width: thin`) et Webkit

## [0.9.97] - 2026-02-26

### Ameliore

- **Navigator : Nouveau Design Epure**
  - Handles de drag en position absolue (plus de decalage visuel)
  - Hover avec gradient subtil (slide-in de gauche)
  - Etat actif avec box-shadow interne au lieu de border-left
  - Sections avec ligne verticale connectant les activites
  - Suppression des points de couleur (vert/orange/gris)

- **Pagination : Migration Atomic Design**
  - Boutons Precedent/Suivant utilisant `_atoms/button/button.html.twig`
  - Bouton Ajouter utilisant la variante `soft-primary`
  - Styles inline Tailwind au lieu de classes CSS custom

### Supprime

- Points d'etat des activites (`.nav-item__state`) - interface simplifiee
- Classes CSS pagination (`pagination__btn`, `pagination__add-small`) - remplacees par atomic design

### Technique

- CSS Navigator repense avec gradients et box-shadows
- CSS Pagination supprime et remplace par Tailwind inline
- JS: Suppression de la logique `getActivityState` dans le rendu

## [0.9.96] - 2026-02-26

### Ajoute

- **Drag & Drop des Sections** : Les sections peuvent maintenant être réorganisées par glisser-déposer
  - Handle visible au survol (icône grip)
  - Indicateur visuel de position (ligne violette)
  - Sauvegarde automatique après déplacement

### Ameliore

- **UX Explicite** : Tous les éléments ont maintenant des tooltips détaillés
  - Header : Tooltips complets ("← Retour à la liste de vos cours Moodia")
  - Toolbar : Explication de chaque action ("Mettre en GRAS le texte sélectionné")
  - Export : "Télécharger le cours au format .mbz pour l'importer dans Moodle"
  - Titre : Indication "Cliquez pour modifier le titre" au survol

- **Migration Tailwind** : Conversion des éléments restants
  - Header converti en 100% Tailwind avec bouton atomic design
  - Toolbar flottante convertie en Tailwind
  - Overlay de génération converti avec messages d'aide explicites

### Technique

- Nouvelles méthodes JS : `handleSectionDragStart`, `handleSectionDragEnd`, `handleSectionDragOver`, `handleSectionDrop`, `moveSection`
- Nouveaux styles CSS : `.nav-section__handle`, `.nav-section--dragging`, `.nav-section--drag-over-bottom`
- Utilisation du composant `_atoms/button/button.html.twig` pour le bouton export

## [0.9.95] - 2026-02-26

### Ameliore

- **Moodia Editor : Migration Atomic Design** : Toutes les modals utilisent maintenant l'atomic design et Tailwind CSS
  - **Modal Export** : Nouveau design avec header gradient, animation de succes (cercles pulsants), etapes numerotees
  - **Modal Suppression** : Convertie en 100% Tailwind avec boutons atomic design (`_atoms/button`)
  - **Modal Ajout Activite** : Convertie en 100% Tailwind avec icones dans des carres colores et hover states
  - **Animations** : Ajout de `animate-bounce-slow`, `animate-ping-slow`, `animate-ping-slower` pour l'export

### Technique

- Nouvelles animations dans `variables.css` (keyframes bounce-slow, ping-slow, ping-slower)
- Suppression des classes CSS custom `export-modal__*`, `confirm-modal__*` au profit de Tailwind
- Utilisation des couleurs semantiques (`bg-success`, `bg-error`, `bg-primary/10`)
- Composants `_atoms/button/button.html.twig` utilises pour tous les boutons des modals

## [0.9.94] - 2026-02-26

### Ameliore

- **Moodia Editor UX v2** : Refonte complete de l'experience editeur
  - **Suppression progression** : La barre de progression inutile (pour createurs, pas etudiants) a ete supprimee
  - **Zone d'edition agrandie** : `max-width: 64rem` au lieu de 56rem, meilleure utilisation de l'espace
  - **Toolbar d'edition visible** : Barre d'outils permanente (Gras, Italique, Titres, Listes, Liens) au-dessus de chaque zone de texte
  - **Pagination redesignee** : Boutons Precedent/Suivant avec labels, bouton ajouter discret au centre
  - **Bouton nouvelle section** : Style discret avec bordure en pointilles
  - **Bouton toggle structure** : Repositionne et affiche uniquement sur mobile avec label "Structure"
  - **Bouton export** : Texte change en "Export MBZ" avec tooltip "Exporter au format Moodle"

### Ajoute

- **Modal de confirmation suppression** : Remplace le `confirm()` natif par une modal elegante
  - Design avec icone danger rouge
  - Message explicatif sur l'irreversibilite
  - Boutons Annuler / Supprimer avec styles distincts
  - Animation backdrop blur

### Corrige

- **Generation audio** : Correction des parametres envoyes au backend
  - `text` au lieu de `content`
  - `activityId` au lieu de `pageId`
  - Validation du contenu minimum (20 caracteres)
  - Meilleure gestion des etats du bouton

### Technique

- Nouvelle classe CSS `.editor-toolbar` pour la barre d'outils visible
- Nouvelle classe CSS `.confirm-modal` pour les modals de confirmation
- Styles `.pagination__center`, `.pagination__add-small` pour la pagination redesignee
- Target Stimulus `deleteModal`, `confirmDeleteBtn` pour la gestion de la modal
- Methodes JS `showDeleteModal()`, `closeDeleteModal()`, `confirmDelete()`

## [0.9.93] - 2026-02-26

### Ameliore

- **Moodia Editor UX** : Ameliorations significatives de l'experience utilisateur
  - **Pagination fixe** : Barre de navigation fixee en bas de l'ecran (100% width) pour naviguer entre les activites sans avoir a scroller
  - **Drag & Drop** : Reorganisation des activites par glisser-deposer dans le Navigator
    - Handle de drag visible au survol (icone grip)
    - Support du deplacement au sein d'une meme section
    - Support du deplacement entre sections differentes
    - Indicateurs visuels de position de drop (ligne bleue)
    - Sauvegarde automatique apres reorganisation
  - **Feedback Widget masque** : Le widget de feedback beta n'apparait plus sur la page editeur
  - **Boutons de navigation ameliores** : Plus grands, avec bordures, effet hover et tooltips avec raccourcis clavier
  - **Bouton Ajouter redesigne** : Style primaire (fond bleu) plus visible

### Technique

- Nouveau block Twig `{% block show_sidebar %}false{% endblock %}` pour masquer le feedback
- CSS pagination: `position: fixed` avec `padding-bottom` sur le contenu pour eviter le masquage
- API HTML5 Drag & Drop native (pas de librairie externe)
- Nouvelles classes CSS: `.nav-item__handle`, `.nav-item--dragging`, `.nav-item--drag-over`
- Nouvelles methodes JS: `handleDragStart`, `handleDragOver`, `handleDrop`, `moveActivity`

## [0.9.92] - 2026-02-26

### Ajoute

- **Moodia Editor** : Refonte RADICALE avec nouveau paradigme "Studio de Creation"
  - **Split View Layout** : Navigator (280px) a gauche + Editor Canvas a droite
  - **Navigation mono-activite** : Une seule activite affichee a la fois (plus de scroll infini)
  - **Pagination fleches** : Navigation Previous/Next entre les activites
  - **Floating Toolbar** : Toolbar contextuelle apparaissant a la selection de texte (style Notion/Medium)
  - **Navigator interactif** : Sections collapsibles, clic pour naviguer directement vers une activite
  - **Breadcrumb dynamique** : Affiche Section > Activite en cours

### Ameliore

- **Nouvelle esthetique** : Palette Indigo/Violet premium inspiree de Notion, Linear, VS Code
- **Design tokens** : Variables CSS centralisees (`--editor-accent`, `--navigator-width`, etc.)
- **UX focus** : Mode "zen" avec une seule activite visible pour meilleure concentration
- **Responsive** : Navigator overlay sur mobile avec bouton toggle
- **Performance** : Rendu uniquement de l'activite visible au lieu de toutes les sections

### Technique

- **Architecture optimisee** : Configuration centralisee (ICONS, ACTIVITY_TYPES) en constantes
- **State management** : Immutabilite avec `setState()` au lieu de mutations directes
- **Fonctions pures** : `escapeHtml()`, `getActivityState()`, `createId()` sans effet de bord
- **Code reduit** : ~900 lignes vs 1600 precedentes (-44%)
- **Bundle size** : -8 KB sur le bundle JS final
- Nouveau fichier `editor.css` avec architecture CSS modulaire
- Backups crees : `editor_controller_legacy.js`, `editor_legacy.html.twig`

## [0.9.91] - 2026-02-26

### Ajoute

- **Moodia Editor** : Refonte radicale du design avec experience premium
  - **TOC Panel collapsible a droite** : Panel de navigation 320px avec slide in/out, persistance localStorage, fermeture automatique sur mobile apres navigation
  - **Header compact** : Une seule ligne avec titre editable, stats inline (sections/activites), bouton toggle TOC, raccourci clavier Ctrl+B
  - **Ecran de loading premium** : Orbes lumineuses flottantes, barre de progression avec particules, timeline des etapes animee, tips rotatifs, confetti a la completion
  - **Configuration centralisee des types** : Nouveaux fichiers `activity-types.js` (20+ types Moodle) et `question-types.js` (10 types de questions quiz)
  - **CSS premium dedicate** : Nouveau fichier `editor-premium.css` avec animations (slideInUp, highlightGlow, orbFloat, gradientShift, logoBreathe, etc.)
  - **Endpoint API** : Nouvel endpoint `GET /moodia/{uuid}/status` pour polling de l'etat de generation

### Ameliore

- **Animations sophistiquees** : Transitions fluides avec `cubic-bezier(0.34, 1.56, 0.64, 1)`, effet bounce, hover effects premium
- **Responsive design** : Desktop (TOC push), Tablet (TOC overlay), Mobile (TOC drawer full-height)
- **Accessibilite** : Zones de clic 44x44px minimum, navigation clavier, aria-live sur l'ecran de loading

## [0.9.90] - 2026-02-25

### Ameliore

- **Moodia Editor** : Redesign complet aligne sur le style Monalisia/
  - Header sticky avec icone 48x48 gradient, titre editable inline et actions
  - Layout flexbox optimise avec `shrink-0` pour eviter les overflow
  - Sidebar avec card TOC stylisee et bouton export gradient
  - Main content avec card arrondie et ombres subtiles
  - Modal d'export avec progression animee et tutoriel integre
  - Suppression de la card statistiques pour simplifier l'interface
  - Coherence visuelle avec les autres produits de la plateforme

## [0.9.89] - 2026-02-25

### Ameliore

- **** : Migration complete vers Tailwind CSS avec design Monalisia
  - Reecriture des templates index.html.twig et gallery.html.twig
  - Container `max-w-4xl` centre comme Monalisia
  - Header avec icone 64x64 en gradient et bordure
  - Cards avec `overflow-hidden` et `hover:shadow-md`
  - Utilisation des composants atomiques : icon, button, spinner, mini-stat, empty-state, alert
  - Mise a jour du controleur Stimulus : selecteurs BEM remplaces par data-attributes
  - Ajout des classes utilitaires  dans app.css pour les etats JS

## [0.9.88] - 2026-02-25

### Corrige

- **Sidebar dynamique** : Correction du bug 502 Bad Gateway
  - Adaptation du template sidebar pour la nouvelle structure de donnees `{tool, accessible}`
  - Migration des icones vers les alias `bubul-*` (bubul-moodia, bubul-serenia, etc.)
  - Creation de l'icone Solar `castia.html.twig` pour  (micro duotone)
  - Ajout de l'alias `bubul-castia` dans le systeme d'icones

## [0.9.87] - 2026-02-24

### Ameliore

- **Moodia Dashboard** : Cards de cours avec footer toujours aligne
  - Structure `flex flex-col` pour les cards de cours
  - Hauteur minimale fixe (`min-h-[320px]`) pour uniformite
  - `flex-grow` sur le contenu + `mt-auto` sur le footer
  - Titre avec hauteur fixe (`h-14`) pour 2 lignes max
  - Footer toujours positionne en bas de la card
  - Padding reduit pour meilleur equilibre visuel

## [0.9.86] - 2026-02-24

### Corrige

- **Moodia Editor** : Hauteur fixe des cards pour alignement parfait
  - Changement de `max-h-32` vers `h-32` (hauteur fixe de 128px)
  - Toutes les cards ont maintenant exactement la meme hauteur
  - Le footer de chaque card est toujours aligne avec les autres

## [0.9.85] - 2026-02-24

### Ameliore

- **Moodia Editor** : Cards d'activite collapsibles avec hauteur uniforme
  - Body limite a 128px (`max-h-32`) par defaut avec apercu du contenu
  - Degrade de fondu en bas pour indiquer qu'il y a plus de contenu
  - Clic sur le chevron pour ouvrir/fermer avec animation fluide
  - Rotation du chevron (180deg) pour indiquer l'etat ouvert
  - Toutes les cards alignees visuellement a la meme hauteur
  - Transition `max-h` animee pour ouverture/fermeture

### Technique

- Nouveau pattern `activity-content` + `activity-fade` pour le collapsible
- `data-collapsed="true/false"` pour tracker l'etat
- Toggle avec `max-h-32` <-> `max-h-[2000px]` pour animation CSS

## [0.9.84] - 2026-02-24

### Ameliore

- **Moodia Editor** : Uniformisation des cards d'activite
  - Hauteur fixe des headers (`h-14`) pour alignement parfait entre les cards
  - Troncature des titres longs avec `truncate` et ellipsis (...)
  - Boutons d'action avec `shrink-0` pour eviter la compression
  - Padding uniforme sur toutes les cards (p-4)

## [0.9.83] - 2026-02-24

### Ajoute

- **Moodia Editor** : Toolbar WYSIWYG pour les zones d'edition de contenu
  - Boutons de formatage : Gras, Italique, Souligne
  - Titres : H2 et H3 pour structurer le contenu
  - Listes : Liste a puces et liste numerotee
  - Liens : Insertion de liens avec Ctrl+K
  - Effacement : Supprimer le formatage
  - Raccourcis clavier : Ctrl+B (gras), Ctrl+I (italique), Ctrl+U (souligne), Ctrl+K (lien)
  - Toolbar apparait au focus de la zone d'edition avec animation fluide

### Ameliore

- **Activity Cards** : Zones d'edition avec bordure et indicateur de focus
- **Audio Activities** : Interface audio redesignee avec boutons Tailwind

### Technique

- Nouvelle fonction `renderWysiwygToolbar()` avec icones SVG inline
- Fonction `renderEditableWithToolbar()` pour wrapper les zones editables
- Handlers `formatText()`, `insertLink()`, `handleEditorKeydown()` pour le formatage
- Pattern `group/editor` pour afficher la toolbar au focus-within
- Utilisation de `document.execCommand` pour la compatibilite navigateur

## [0.9.82] - 2026-02-24

### Ameliore

- **Moodia Editor** : Refonte UX/UI complete avec Tailwind CSS
  - **TOC Sidebar** : Accordeons animes, badges compteurs, indicateurs visuels de progression
  - **Section Blocks** : Design premium avec gradient header, bordure laterale coloree, animations hover
  - **Activity Cards** : Bordure coloree par type, actions contextuelles au hover, transitions fluides
  - **Quiz Editor** : Interface premium avec questions cards, radio buttons animes, feedback visuel
  - **Badges d'activite** : Icones SVG Solar inline, styles Tailwind par type (page=bleu, quiz=amber, video=rose)
  - Micro-animations et transitions fluides sur tous les elements interactifs
  - Classes Tailwind group/hover pour affichage contextuel des actions

### Technique

- Migration complete des styles CSS custom vers Tailwind dans editor_controller.js
- Fonctions de rendu JavaScript refactorisees : renderTocSection, renderSection, renderPageActivity, renderQuizActivity
- Pattern group/card et group/question pour les actions contextuelles au hover
- Utilisation de grid-template-rows pour les animations accordeon

## [0.9.81] - 2026-02-24

### Ameliore

- **Moodia Editor** : Refonte complete de l'editeur de cours
  - Badges d'activite avec icones SVG Solar Bold Duotone
  - Support des nouveaux types d'activites : video, h5p, resource
  - Couleurs distinctives par type (page=bleu, quiz=orange, video=rose, h5p=cyan)
  - Activity cards avec bordure laterale coloree selon le type
  - Section blocks avec header ameliore (numero, titre editable, toggle)
  - TOC (table of contents) redesignee avec accordeons
  - CSS unifie pour le rendu dynamique JavaScript

### Technique

- Icones SVG inline dans `renderActivityBadge()` pour performance
- Classes CSS activity-badge--[type] et activity-card--[type] preparees pour extensibilite
- Gap et padding des badges ajustes pour meilleure lisibilite avec icones

## [0.9.80] - 2026-02-24

### Ajoute

- **Nouvel organisme** : `_organisms/choice-card/choice-card.html.twig`
  - Carte de choix premium pour wizards et selections
  - Variantes de couleur : primary, violet, warning, success
  - Support des features list avec check icons colores
  - Animation icone hover avec scale + rotation (cubic-bezier overshoot)
  - Fleche animee en haut a droite au hover
  - Etat disabled avec badge customisable
  - Configuration complete via parametres (icon, color, features, action, href, badge)

### Ameliore

- **Moodia** : Page de creation refactorisee avec l'organisme `choice-card`
  - Code reduit de ~200 lignes a ~50 lignes grace a l'organisme
  - Animation icone unifiee avec les stats cards (`scale-110 rotate-3`)
  - Transition avec easing `cubic-bezier(0.34,1.56,0.64,1)` pour effet rebond

### Technique

- Animation des icones interactives unifiee dans tout le projet
- Pattern d'organisme reutilisable pour les wizards de selection

## [0.9.79] - 2026-02-24

### Ameliore

- **Moodia** : Refonte premium de la page de creation (`/moodia/new`)
  - Design cards avec gradients, shadows et animations hover fluides
  - Icones Solar duotone via Atomic Design (`document`, `magic-stick`, `upload`, etc.)
  - Badge "Recommande" avec gradient sur l'option PDF
  - Liste de features avec check icons pour chaque methode
  - Zone de depot PDF avec animation pulse au hover
  - Suggestions de prompts avec icones contextuelles
  - Tip conseil avec design gradient amber/orange
  - Background page avec gradient subtil
  - Transitions et micro-animations premium (scale, translate, shadow)

### Change

- **Route** : `/moodia/nouveau` renommee en `/moodia/new` (anglais)

### Technique

- Utilisation exclusive des icones Solar via `_atoms/icon/icon.html.twig`
- Duotone icons avec `colorPrimary` et `colorSecondary`
- Shadows CSS personnalisees avec couleurs primaires (`shadow-primary/30`)

## [0.9.78] - 2026-02-24

### Ameliore

- **Moodia** : Refonte UX complete du parcours utilisateur (Dashboard → Configuration)
  - **Dashboard** : Nouveau design des cards avec barre d'accent coloree laterale
    - Quick Stats en haut (total cours, deployes, brouillons, avec quiz)
    - Cards epurees avec hierarchie visuelle claire (type badge → titre → stats → metadata)
    - Actions rapides au hover (editer, exporter)
    - Level indicator compact avec dots progressifs
    - Configuration couleurs centralisee par type
  - **Nouveau composant Stepper** (`_molecules/stepper/stepper.html.twig`)
    - Support des etapes avec icones
    - Etats: completed (check), active (ring), pending
    - Ligne de progression animee
    - Tailles: sm, md, lg
    - Variantes: default, compact, minimal
  - **Page New (Wizard)** : Experience guidee avec stepper 4 etapes
    - Cards de choix illustrees (PDF vs IA)
    - Badge "Recommande" sur import PDF
    - Tip contextuel avec conseil
    - Suggestions de prompts cliquables
    - Retour simplifie
  - **Page Configuration** : Split-view avec preview live
    - Formulaire organise en sections thematiques
    - Panel preview sticky a droite (desktop)
    - Mise a jour en temps reel de l'apercu
    - Estimation du cours genere
    - Section instructions additionnelles collapsible (details/summary)

### Corrige

- **Moodia Config Controller** : Ajout de la methode `updatePreview()` pour le preview live
  - Ajout des targets de preview (previewTitle, previewPublic, previewLevel, etc.)
  - Correction de la validation du formulaire pour utiliser les selectors Symfony
  - Refactoring de `generateObjectives()` pour utiliser les selectors de formulaire
- **Moodia Wizard Controller** : Ajout de la methode `insertSuggestion()` pour les suggestions de prompts

### Technique

- Architecture atomique : nouveau molecule `stepper` reutilisable
- Pattern split-view pour formulaires complexes
- Quick Stats pattern reutilisable pour dashboards
- Simplification du code cards (configuration centralisee)
- Correction de la coherence targets Stimulus ↔ templates Twig

## [0.9.77] - 2026-02-24

### Ameliore

- **Moodia** : Refonte complete de toutes les pages avec Atomic Design et Tailwind CSS
  - Dashboard redesigne avec header pattern Monalisia (icone 64x64 gradient, titre, mini-stat)
  - Utilisation du composant `course-card` organism pour la grille de cours
  - Filtres par type avec `filter-tabs` molecule (Tous, Cours, Cours+Quiz, Quiz)
  - Pagination avec variante `moodia` (style premium avec gradient)
  - Etat vide avec `empty-state` molecule et CTA
  - Page New : wizard 3 vues (choix, PDF, prompt) avec cards mode action
  - Dropzone PDF avec pattern Monalisia (effet scale sur drag, preview fichier)
  - Page Configuration : cards collapsibles en accordeon (`collapsibleGroup: 'config'`)
  - Radio cards pour approche pedagogique (pattern peer-checked Tailwind)
  - Alert info quota avec molecule `alert`
  - Page Generation : layout 2 colonnes (sidebar TOC sticky + main content)
  - Barre de progression animee avec gradient
  - Robot anime avec points de reflexion (animation pulse)
  - Page Editor : header sticky compact avec input titre inline
  - Layout 2 colonnes avec sidebar TOC et bouton export
  - Modal d'export custom avec 2 vues (progression + tutoriel steps)
  - Conservation de tous les data-attributes Stimulus pour la compatibilite JS
  - Responsive mobile-first avec breakpoints sm: et lg:

### Technique

- Utilisation exclusive des atoms, molecules et organisms existants
- Pattern header coherent avec Monalisia/Slidia/Serenia
- Classes Tailwind sans CSS custom (variables.css pour les tokens)
- Documentation complete en en-tete de chaque template

## [0.9.76] - 2026-02-24

### Ameliore

- **Monalisia** : Refonte complete des pages avec Atomic Design et design tokens
  - Page index redesignee avec header coherent (pattern Slidia/Serenia)
  - Utilisation des atoms existants : `button`, `icon`, `spinner`, `badge`
  - Utilisation des molecules : `mini-stat`, `alert`, `empty-state`
  - Selecteur de mode en tabs pleine largeur ("Depuis un texte" / "Depuis un PDF")
  - Cartes de format avec effet lift au hover (`hover:-translate-y-0.5`)
  - Dropzone PDF avec animation scale sur l'icone
  - Footer avec tip contextuel et bouton generer
  - Page galerie avec grille 3 colonnes sur grand ecran
  - Overlay gradient sur les images avec actions au hover (pattern moderne)
  - Toutes les transitions en `duration-base` (300ms token)
  - Documentation complete avec composition et parametres

### Corrige

- **Monalisia** : Ajout classe `monalisia-gen__card` manquante pour le loading state JS

## [0.9.75] - 2026-02-24

### Ameliore

- **Slidia** : Refonte complete de l'interface avec filtres collapsibles et design unifie
  - Header redesigne style Monalisia (icone 64x64 gradient, titre, stats tokens/energie inline)
  - Cartes de filtres collapsibles en accordeon (une seule ouverte a la fois)
  - Nouveau `collapsible_controller.js` avec animation fluide et support groupe accordion
  - Nouveau `radio_card_controller.js` pour etat de selection cross-browser (remplace CSS `:has()`)
  - Design unifie des radio cards : icone dans carre blanc, label + description, checkmark bleu
  - Ajout parametre `collapsible`, `collapsed`, `collapsibleGroup` au composant `card.html.twig`
  - Nouvelles icones : `chevron-down`, `magic-stick`, `menu`

- **Settings** : Page /settings Slidia mise a jour avec le meme design de radio cards
  - Coherence visuelle entre la page Slidia et ses parametres

### Corrige

- **Slidia** : Correction compatibilite CSS `:has()` avec classe `.is-selected` via JavaScript
- **Pages d'erreur** : Variable `accent` manquante dans `error.html.twig` corrigee

## [0.9.74] - 2026-02-24

### Ameliore

- **Serenia** : Refonte complete de l'interface de chat avec Tailwind CSS et Atomic Design
  - Template Twig reecrit avec des classes Tailwind au lieu du CSS BEM legacy
  - Reutilisation des atomes existants : `icon` (token, bolt, shield, book, document, star, send, upload), `logo` (simple variant pour l'avatar assistant)
  - Header compact avec gradient brand, icone Serenia en gradient, et stats inline (tokens + energie) utilisant les `mini-stat`-like pills
  - Ecran de bienvenue repense avec 3 cartes fonctionnalites (cours, analyse, images) avec effets hover et icones couleur
  - Notice de securite avec icone `shield` et couleurs success du design system
  - Zone de saisie moderne avec fond `gray-50`, bordure arrondie 2xl, focus ring brand
  - Bouton d'envoi en gradient brand avec shadow coloree et effet de levitation hover
  - Bouton d'upload avec transition de couleur vers `primary`
  - Migration des classes CSS : `seren-ia__*` (BEM) vers `serenia-*` (flat) dans tous les fichiers JS
  - Styles de contenu Markdown (h1-h3, code, pre, blockquote, listes, tables) dans `app.css @layer components`
  - Animation typing indicator redesignee (points plus petits, opacite subtile)
  - Messages utilisateur avec gradient brand et coins arrondis asymetriques
  - Messages assistant avec fond blanc, bordure subtle, shadow micro
  - Badge metriques (tokens + energie) redesigne en pill arrondie avec icones SVG inline
  - Scrollbar fine personnalisee pour la zone de messages
  - Responsive : padding adaptatif sm/lg, largeur de message ajustee mobile

### Corrige

- **Serenia** : Tests fonctionnels mis a jour pour correspondre aux nouveaux selecteurs CSS
  - 5 selecteurs `.seren-ia__*` remplaces par `.serenia*` dans `SerenIATest.php`

## [0.9.73] - 2026-02-24

### Ajouté

- **Sidebar dynamique** : Les outils sont maintenant chargés depuis la base de données
  - Nouvelle variable globale Twig `sidebar_tools` contenant les outils visibles
  - Méthodes `getRouteName()`, `getSidebarIcon()`, `isVisibleInSidebar()` dans `Tool`
  - Plus besoin de modifier le template pour ajouter un nouvel outil

- **Admin Tools** : Lien ajouté dans la section Administration de la sidebar

### Amélioré

- **Admin Tools** : Refonte des templates avec Atomic Design
  - `list.html.twig` et `form.html.twig` utilisent maintenant `base.html.twig`
  - Suppression de la dépendance à `admin/_base.html.twig` (inexistant)

- **ToolController** : Accès ouvert à tous les admins
  - Suppression de la restriction par liste d'emails (`app.tool_manager_emails`)

### Corrigé

- **Désactivation entreprise** : La date de fin des contrats n'est plus modifiée
  - Seul le flag `isActive` est mis à false, les dates restent intactes

## [0.9.72] - 2026-02-24

### Ajouté

- **Pages d'erreur** : Refonte complète avec Atomic Design
  - Nouveaux templates dédiés pour 404, 403, 500
  - Template générique intelligent avec fallback pour autres codes (401, 405, 408, 429, 502, 503)
  - Gradient unifié de la marque Bubul pour tous les codes d'erreur
  - Design minimaliste sans icônes, focus sur le code d'erreur
  - Support dual-mode : avec/sans sidebar selon l'authentification
  - Composition d'atoms existants (logo, button) sans création de nouveaux composants

## [0.9.71] - 2026-02-24

### Amélioré

- **Atome button** : Effet hover du variant `ghost` plus visible
  - Couleur de fond passée de `gray-100` à `gray-200`

- **Atome icon monalisia** : Refactorisation avec les paramètres Atomic Design
  - Support de `size`, `color`, `colorPrimary`, `colorSecondary`, `opacitySecondary`
  - Uniformisation avec les autres icônes du système

- **Admin - Ajout utilisateur** : Bouton "Annuler" en variant `ghost` avec taille `lg`
  - Meilleure cohérence visuelle avec les autres formulaires

### Corrigé

- **Admin - Fiche utilisateur** : Correction des icônes d'outils
  - Suppression du préfixe `bubul-` devenu obsolète
  - Utilisation de `tool.code|lower` pour correspondre aux nouveaux noms d'icônes

## [0.9.70] - 2026-02-23

### Ajouté

- **Molécule card** : Paramètre `fullHeight` pour étirer verticalement les cards
  - Ajoute `h-full flex flex-col` au conteneur et `flex-1` au body
  - Utile pour aligner des cards dans une grille CSS

- **Organisme activity-list** : Paramètre `class` pour classes CSS additionnelles
  - Support natif de `h-full` avec structure flex intégrée

### Amélioré

- **Dashboard** : Alignement vertical du guide de démarrage et des activités récentes
  - Les deux cartes ont maintenant la même hauteur dans la grille
  - Corrige le décalage visuel quand les étapes du guide sont complétées

## [0.9.69] - 2026-02-19

### Amélioré

- **Permissions managers** : Autorisations étendues pour les gestionnaires d'entreprise
  - Accès à la section "Gestion du compte" sur la fiche utilisateur
  - Possibilité de réinitialiser le mot de passe des utilisateurs de leur entreprise
  - Affichage des outils du contrat dans le formulaire d'édition
  - Restriction du toggle des outils aux admins uniquement

### Corrigé

- **Sélecteur de rôles** : Correction du changement visuel au clic (add/edit)
  - Utilisation de JavaScript inline pour les couleurs dynamiques Tailwind
  - Classes CSS dynamiques remplacées par des styles inline

- **Modal reset password** : Couleur changée en bleu (primary) au lieu d'orange

- **Icônes outils** : Couleur primaire (bleu) maintenue même pour les outils désactivés

## [0.9.68] - 2026-02-19

### Ajouté

- **Molécule activity-item** : Élément d'activité réutilisable
  - Affiche outil, titre, tokens, énergie et date
  - Support mode compact
  - Couleur primaire unifiée pour tous les outils

- **Molécule contract-row** : Ligne de contrat pour les listes
  - Affiche nom, dates, outils autorisés, tokens et statut
  - Intégration avec les badges de statut

- **Molécule status-zone** : Zone d'action avec gradient
  - 5 variantes : neutral, success, warning, error, info
  - Design aérien avec icône, titre, description et bouton
  - Utilisé pour les actions de gestion (reset password, toggle status)

- **Organisme activity-list** : Liste d'activités réutilisable
  - Paramètre `limit` pour contrôler le nombre d'éléments
  - État vide intégré avec empty-state
  - Scrollable avec hauteur max configurable

- **Organisme contract-list** : Liste de contrats avec header

- **Documentation Design System** : Pages pour tous les nouveaux composants

- **Icône user-plus** : Ajout au set Solar Icons

### Refactorisé

- **Pages admin users** : Conversion complète vers Atomic Design
  - `show.html.twig` : Sections Activités, Contrats et Gestion du compte
  - `list.html.twig` : Utilise les molécules et organismes
  - `add.html.twig` et `edit.html.twig` : Formulaires avec Atomic Design

- **Dashboard** : Icône ajoutée au "Guide de démarrage" pour cohérence visuelle

### Supprimé

- Templates legacy admin : `_base.html.twig`, `_form_fields.html.twig`, `_modal_toggle*.html.twig`, `_pagination.html.twig`

## [0.9.67] - 2026-02-19

### Ajouté

- **Badge presets** : Système de presets pour les badges de statut
  - `contract-active`, `contract-expired`, `contract-upcoming`, `contract-disabled`
  - `user-active`, `user-inactive`, `user-pending`
  - `company-active`, `company-inactive`
  - Évite la duplication de configuration dans les templates

- **ContractStatusExtension** : Extension Twig pour centraliser les configs de statut
  - `contract_status_config()` retourne la config complète (bg, icon, border, etc.)
  - `contract_status_label()`, `contract_status_icon()`, `contract_status_variant()`

- **Accès managers aux contrats** : Les managers peuvent maintenant voir leurs contrats
  - Route `admin_contract_list` accessible aux managers (filtrée par entreprise)
  - Lien "Contrats" ajouté dans la sidebar pour les managers
  - Section statut visible pour managers (sans bouton d'action)

- **Contact support** : Email contact@bubul.app ajouté dans les infos contrat

### Corrigé

- **Activation/désactivation contrat** : Ne modifie plus les dates du contrat
- **Modal nouveau contrat** : Synchronisation état/style du bouton disabled
- **Breadcrumb contrat** : Navigation adaptée selon rôle (admin vs manager)

### Refactorisé

- **Section Informations** : Utilise `data-row` molecule au lieu de HTML inline
- **Suppression duplication** : Config statusConfig centralisée via extension Twig
- **Renommage** : `global_list.html.twig` → `list.html.twig`

## [0.9.66] - 2026-02-19

### Ajouté

- **Pagination atomique** : Ajout de la molécule pagination aux listes admin
  - `admin/users/list.html.twig` : Pagination avec conservation des filtres
  - `admin/company/list.html.twig` : Pagination variante "moodia" (gradient styling)
  - `admin/contract/global_list.html.twig` : Pagination avec compteur total
  - Constantes `ADMIN_COMPANIES_PER_PAGE` et `ADMIN_CONTRACTS_PER_PAGE` dans AppLimits

- **Notifications toast API Keys**
  - Notification succès lors de la création d'une clé API
  - Notification succès lors de la révocation d'une clé API
  - Gestion des erreurs avec notifications

### Corrigé

- **Bouton "Générer ma clé"** : Correction `attributes` → `attr` pour l'atome button
- **Modales API Keys** : Redesign avec atomic design (variants success/danger)
- **Slidia settings** : Feedback visuel lors du changement de sélection (`updateVisualSelection`)
- **User toggle icon** : Mise à jour des attributs `fill` SVG pour changement de couleur vert/rouge
- **Stat-card** : Utilisation des couleurs du design system (`bg-success/10` au lieu des gradients legacy)

### Refactorisé

- **Modales atomic design** : Utilisation des boutons atomiques avec footer `justify-between`
- **Sidebar z-index** : Réduction de `z-[1000]` à `z-40` pour éviter les conflits

## [0.9.65] - 2026-02-19

### Corrigé

- **Contrôleur user_toggle** : Mise à jour pour utiliser les classes Tailwind
  - Badge statut : toggle `bg-success/10` ↔ `bg-error/10` au lieu des anciennes classes CSS
  - Dot et label mis à jour dynamiquement sans rechargement
  - Opacité de la ligne actualisée

- **Filtres contrats** : Utilisation de `requestSubmit()` au lieu de `submit()` pour compatibilité Turbo

- **Bouton reset filtres** : Taille réduite (icône `sm`, suppression du padding)

- **Filtres utilisateurs** : Suppression de `flex-wrap` pour alignement en ligne

### Nettoyé

- Suppression des `console.log` de debug dans les contrôleurs Stimulus

## [0.9.64] - 2026-02-19

### Refactorisé

- **Pages Admin Users/Company/Contracts** : Conversion vers Atomic Design
  - `admin/users/list.html.twig` : Tailwind + molécules `page-header`, `stat-card`, `badge`, `avatar`
  - `admin/company/list.html.twig` : Cards entreprises avec filtres inline
  - `admin/contract/global_list.html.twig` : Tableau contrats avec modal création
  - Suppression des CSS legacy `admin-shared.css`, `admin-users.css`, etc.

- **Correction icônes dynamiques des outils**
  - Pattern `tool.code` → `'bubul-' ~ tool.code` pour utiliser les alias Solar
  - Fichiers corrigés : `show.html.twig` (users, company, contract), `_modal_toggle_tool.html.twig`, `add.html.twig`, `edit.html.twig`
  - Résolution des erreurs 502 sur icônes `monalisia`, `moodia`, `serenia`, `slidia`, `sam`

## [0.9.63] - 2026-02-19

### Refactorisé

- **Page Admin Feedback** : Conversion complète vers Atomic Design
  - Utilisation des molécules `page-header`, `stat-card`, `filter-tabs`, `empty-state`
  - Cards de feedback avec atomes `badge`, `avatar`, `icon`, `button`
  - Suppression de la dépendance au CSS legacy
  - Script de filtrage inline en JavaScript vanilla

- **Page Admin Beta** : Conversion complète vers Atomic Design
  - Utilisation des molécules `page-header`, `stat-card`, `empty-state`
  - Tableau avec avatars et badges de statut
  - Boutons d'action avec icônes atomiques
  - Suppression du CSS legacy `admin-beta.css`

- **Page Paramètres** : Conversion complète vers Atomic Design
  - Utilisation de la molécule `tabs` avec variante `settings`
  - 5 onglets : Compte, Moodia, Serenia, Slidia, Développeur
  - Molécule `card` avec `embed` pour chaque section
  - Options radio Slidia redessinées avec Tailwind
  - Section Développeur avec guide en 3 étapes et modals

- **Partial _developer.html.twig** : Refonte Atomic Design
  - Utilisation des molécules `card`, `alert`, atomes `badge`, `icon`, `button`
  - Code blocks avec style terminal (bg-gray-900)
  - Modals avec backdrop blur et design moderne

## [0.9.62] - 2026-02-19

### Refactorisé

- **Dashboard Atomic Design** : Conversion complète du tableau de bord
  - Utilisation des molécules `stat-card`, `card`, `empty-state`
  - Statistiques, accès rapides, guide de démarrage, activité récente
  - Suppression de la dépendance au CSS legacy `dashboard.css`

### Ajouté

- **Organisme Feedback Widget** : Déplacé vers `_organisms/feedback-widget`
  - Modal avec design Atomic (icônes, boutons)
  - Formulaire mixte Symfony + classes atomes
  - Notification toast au succès
- **Icône Monalisia** : Nouvelle icône Solar `monalisia.html.twig`

### Amélioré

- **Molécule Toast** : Classes statiques pour compilation Tailwind
  - Utilise `_atoms/icon` pour toutes les icônes (variante + close)
  - Design unifié avec Alert
- **Molécule Alert** : Mêmes améliorations que Toast
  - Classes statiques par variante
  - Bouton fermer via atome icon
- **Molécule Card** : Correction alignement `items-start` en mode action
- **Système de notifications unifié** : Tous les composants utilisent `window.showToast`

### Corrigé

- Suppression référence CSS notification inexistante dans `base.html.twig`
- Feedback controller utilise directement `showToast` (comme flash messages)

## [0.9.61] - 2026-02-18

### Amélioré

- **Sidebar sizing paramétrable** : Ajout de 3 nouveaux paramètres
  - `navItemSize` : Taille des éléments de navigation (sm, md, lg)
  - `logoSize` : Taille du logo en mode expanded
  - `logoSizeCollapsed` : Taille du logo en mode collapsed
- **Correction SAM et sections admin** : Tous les nav-items utilisent maintenant le paramètre de taille
- **Documentation Design System** : Mise à jour de la page Sidebar avec les nouveaux paramètres

## [0.9.60] - 2026-02-18

### Ajouté

- **Organisme Landing Pricing** : Section tarification avec mise en avant du plan Pro
  - Badge "Recommandé" avec icône fire
  - Effet scale-105, gradient et shadow pour le plan Pro
  - 3 formules : Starter, Pro, Entreprise
  - Paramètres : `surtitle`, `title`, `plans` (avec `recommended: true`)
- **Organisme Landing FAQ** : Section FAQ avec accordéon interactif
  - Contrôleur Stimulus `shared--faq` pour toggle des réponses
  - Rotation de l'icône + changement de couleur à l'ouverture
  - Comportement accordéon (un seul item ouvert à la fois)
- **Organisme Landing CTA** : Section inscription bêta
  - Gradient cohérent avec le hero
  - Transition smooth vers le footer blanc
  - Support formulaire Symfony ou fallback HTML
- **Organisme Landing Footer** : Pied de page thème blanc
  - Logo, tagline, liens navigation et mentions légales
- **Organisme Landing Features** : Grille de 6 avantages
  - Icônes centrées sans background
  - Responsive : 1 → 2 → 3 colonnes
- **Organisme Landing Tools** : Section "Nos bulles" interactive
  - Contrôleur Stimulus `shared--landing-tools`
  - Liste cliquable + aperçu image dynamique
- **Smooth scroll** : Ajout de `scroll-smooth` sur `<html>` global
- **Documentation Design System** : 6 nouvelles pages organisms

### Amélioré

- **Landing Hero responsive** : Tailles de police et padding adaptés mobile
  - Titre : `text-2xl` → `text-[3.5rem]`
  - Bouton CTA taille `lg` sur mobile
  - Padding `px-6` sur mobile
- **Landing Features responsive** : Padding `p-4` sur mobile, `p-8` sur desktop

### Modifié

- **Landing page** : Conversion complète en organisms Tailwind
- **Footer** : Passé de thème sombre à blanc

## [0.9.59] - 2026-02-18

### Ajouté

- **Organisme Landing Hero** : Section hero principale de la landing page
  - Blobs animés avec effet flottant (@keyframes floatBlob1/2/3)
  - Animations fade-in-up avec délais progressifs
  - Indicateur de scroll avec animation bounce
  - Responsive : tailles de blobs adaptatives (700px → 180px)
  - Paramètres : `surtitle`, `title`, `subtitle`, `ctaUrl`, `ctaLabel`, `showScroll`
- **Documentation Landing Hero** : Page Design System avec démo et paramètres

### Amélioré

- **Landing Header mobile** : Simplification du header responsive
  - Hamburger bleu (xl) comme seul élément visible sur mobile
  - CTA et Connexion déplacés dans le menu burger
  - Menu mobile avec boutons full-width
- **Icône List** : Nouveau design hamburger moderne
  - 3 barres de longueurs décroissantes avec opacités (1 → 0.7 → 0.4)
  - Entièrement paramétrable (size, color, colorPrimary, colorSecondary)

### Modifié

- **Landing page** : Utilisation du nouvel organisme `landing-hero`
- **Logo header** : Taille réduite de `lg` à `sm`

## [0.9.58] - 2026-02-18

### Ajouté

- **Organisme Landing Header** : Header de navigation pour la landing page
  - Support navigation desktop et mobile avec menu hamburger
  - Options : `transparent` (pour hero), `sticky`, `navItems`, `loginUrl`, `ctaUrl`, `ctaLabel`
  - Utilise logo-with-text, button (ghost + primary), icon
- **Design System Index** : Ajout des organismes manquants
  - Auth Layout (layout split-screen)
  - Login Form (formulaire connexion)
  - Landing Header (header navigation)

## [0.9.57] - 2026-02-18

### Amélioré

- **Atome Input** : Centrage dynamique des icônes selon la taille (sm/md/lg)
- **Atome Logo** : Nouvelle option `size: 'fluid'` pour logos responsives
- **Atome Logo-with-text** : Texte "Bubul" avec dégradé bleu et police Poppins

### Modifié

- **Pages d'authentification** : Consolidation vers `base.html.twig`
  - Suppression de `base_auth.html.twig` (redondant)
  - Nouveau block `body_class` pour contrôler le background du body
  - Nouveau block `meta_robots` pour le SEO
- **Formulaires auth** : Typographie cohérente (`font-secondary font-medium text-black`)
- **Inputs auth** : Ajout d'icônes (mail, lock, key) sur tous les champs

### Supprimé

- `_molecules/back-link` : Remplacé par l'atome `link` avec `icon: 'arrow-left'`
- `_molecules/form-link` : Remplacé par l'atome `link` avec variantes

## [0.9.56] - 2026-02-18

### Ajouté

- **Design System : Pages d'authentification (Atomic Design)**
  - `base_auth.html.twig` : Template de base dédié aux pages auth (sans sidebar)
  - `_molecules/back-link` : Lien retour avec icône flèche et animation hover
  - `_molecules/form-link` : Liens stylisés pour formulaires (variantes default, primary, muted)
  - `_organisms/auth-visual` : Partie visuelle 50% gauche avec logo centré
  - `_organisms/auth-layout` : Layout split-screen avec support embed pour le contenu
  - `_organisms/login-form` : Formulaire de connexion complet
  - `_organisms/forgot-password-form` : Formulaire de récupération de mot de passe
  - `_organisms/reset-password-form` : Formulaire de réinitialisation du mot de passe

### Modifié

- **Pages d'authentification** : Refactorisation complète vers le Design System
  - `login.html.twig` utilise maintenant les organismes auth-layout et login-form
  - `forgot_password.html.twig` utilise les organismes auth-layout et forgot-password-form
  - `reset_password.html.twig` utilise les organismes auth-layout et reset-password-form
  - Toutes les pages utilisent 100% Tailwind CSS (suppression des classes CSS legacy)

## [0.9.55] - 2026-02-18

### Ajouté

- **Design System : Organisme Sidebar**
  - Nouvel organisme `_organisms/sidebar/sidebar.html.twig` remplaçant l'ancienne navbar
  - Système de collapse/expand 100% Tailwind avec `group-data-[collapsed=true]/sidebar`
  - Logo dynamique switchant entre variant full et icon
  - Classes utilitaires `scrollbar-none` pour masquer la scrollbar

### Amélioré

- **Design System : Molécule Nav-item**
  - Support du collapse dynamique via classes Tailwind conditionnelles
  - Animation hover avec `translate-x-1`
  - Padding ajusté pour correspondre à l'ancien design (px-6 py-4)

- **Controller Stimulus : Sidebar**
  - Toggle des classes Tailwind (`w-[260px]` / `w-[72px]`)
  - Toggle de l'attribut `data-collapsed` pour les sélecteurs CSS
  - Gestion du margin du main-content (`ml-[260px]` / `ml-[72px]`)

### Modifié

- **base.html.twig** : Utilise maintenant l'organisme Sidebar au lieu de `_components/navbar.html.twig`

## [0.9.54] - 2026-02-18

### Ajouté

- **Design System : Documentation des Organismes**
  - Page de documentation complète pour Sidebar (navigation principale)
  - Page de documentation pour Course Card (carte de cours Moodia)
  - Page de documentation pour Company Card (carte d'entreprise admin)
  - Section Organismes dans l'index du Design System avec grille de 3 composants
  - Route `/admin/design-system/organisms/sidebar` ajoutée

### Amélioré

- **Design System : Molécule Nav-item**
  - Correction alignement icône/texte avec `flex items-center justify-center`

- **Design System : Structure documentation**
  - Pages organismes suivent le même pattern que les molécules
  - Utilisation systématique des composants d'icônes (pas de SVG inline)

## [0.9.53] - 2026-02-17

### Ajouté

- **Design System : Molécule Step Indicator**
  - Indicateur de progression pour wizards et processus multi-étapes
  - Layouts horizontal et vertical
  - Tailles sm, md, lg
  - États: completed, active, pending
  - Navigation cliquable optionnelle
  - Page de documentation avec exemples

### Amélioré

- **Design System : Molécule Modal**
  - Nouveau paramètre `position: center | right | left` pour mode side-panel
  - Nouveau paramètre `actionUrl` pour soumission de formulaires
  - Body scrollable automatiquement en mode side-panel
  - Documentation enrichie avec 3 démos interactives de side-panels

- **Design System : Molécule Form-field**
  - Support `type: file` avec style natif
  - Mode dropzone avec drag & drop
  - Paramètres: `accept`, `multiple`, `maxSize`, `dropzone`
  - Textes personnalisables: `dropzoneText`, `dropzoneHint`
  - Preview du nom de fichier après sélection
  - Documentation enrichie avec exemples file et dropzone

## [0.9.52] - 2026-02-17

### Ajouté

- **Design System : Molécule Member Slider**
  - Slider interactif avec synchronisation bidirectionnelle (range + input)
  - Controller Stimulus `shared--member-slider` pour la synchronisation temps réel
  - Barre de remplissage visuelle (fill)
  - Événement custom `shared--member-slider:change` pour écouter les changements
  - Variantes : default, primary
  - Page de documentation avec exemples

### Amélioré

- **Design System : Molécule Card - Mode Action**
  - Style quick-action avec animations hover avancées
  - Icône avec scale (1.1) et rotation (-3deg) au hover
  - Flèche slide-in depuis la droite
  - Ombre bleue colorée au hover
  - Card translate-y au hover
  - Icônes toujours en primary (bleu) avec background
  - Support des status : available, locked, coming
  - Paramètre `badge` pour texte personnalisé

### Corrigé

- **Design System : Molécule Mini Stat**
  - Correction erreur filtre `clamp` (utilisation de `max/min` natif Twig)

## [0.9.51] - 2026-02-17

### Ajouté

- **Design System : 6 nouvelles molécules**
  - **Data Row** : Affichage standardisé de paires label:valeur avec icône
  - **Action Bar** : Groupe de boutons d'action pour les tableaux (CRUD)
  - **Tag Group** : Collection de tags avec troncature et "+X more"
  - **Feature Item** : Élément de fonctionnalité avec état activé/désactivé
  - **Info Card** : Carte d'information contextuelle avec variantes et action
  - **Enrolled Card** : Carte utilisateur inscrit avec avatar, progression et actions
  - Pages de documentation pour chaque nouvelle molécule
  - Routes et controller ajoutés

## [0.9.50] - 2026-02-17

### Amélioré

- **Design System : Molécule Filter Tabs**
  - Réécriture complète pour correspondre au design Moodia
  - Conteneur pill blanc avec bordure subtile et ombre douce
  - État actif : dégradé bleu avec ombre bleue, texte blanc
  - Compteurs avec badges arrondis
  - Supports des tailles sm/md/lg

- **Design System : Molécule Page Header**
  - Correction erreur breadcrumb (vérification `href is defined`)
  - Correction affichage texte boutons (paramètre `label`)
  - Boutons en taille `lg` par défaut

- **Design System : Atome Button**
  - Taille par défaut changée de `md` à `lg`

- **Design System : Icônes Solar**
  - Icônes `arrow-left` et `arrow-right` paramétrables
  - Paramètres : size, color, colorPrimary, colorSecondary, opacitySecondary, class

## [0.9.49] - 2026-02-17

### Amélioré

- **Design System : Molécule Section Header**
  - Simplifié pour correspondre au design existant (`.section-title`)
  - Suppression de la barre colorée, icône et sous-titre
  - Paramètres réduits : `title`, `size`, `class`
  - Police Poppins, semibold, tracking serré

## [0.9.48] - 2026-02-17

### Amélioré

- **Design System : Molécule Nav Item**
  - Nouveau paramètre `locked` : affiche un cadenas rouge et grise l'élément
  - Éléments verrouillés non cliquables avec style grisé (comme disabled)
  - Hover corrigé : même apparence que l'état actif, sans décalage

- **Design System : Icône presentation-graph**
  - Rendue paramétrable (size, color, colorPrimary, colorSecondary, opacitySecondary)

## [0.9.47] - 2026-02-17

### Amélioré

- **Design System : Molécule Level Indicator**
  - Nouveau style `progressive` avec barres à hauteurs croissantes
  - Paramètres `barWidth` et `gap` pour personnalisation fine des dimensions
  - Utilisation des couleurs du design system (`bg-success`, `bg-warning`, etc.)
  - Ajout des variantes `info` et `cohorts`
  - Documentation enrichie avec exemples de personnalisation

- **Design System : Molécule Mini Stat**
  - Nouveau paramètre `appearance` : `pill` (défaut avec fond) ou `inline` (sans fond)
  - Style inline idéal pour les sidebars (comme Slidia)
  - Documentation avec section Apparences

## [0.9.46] - 2026-02-17

### Amélioré

- **Design System : Molécule Nav Item**
  - Style sidebar avec gradient bleu subtil (`linear-gradient(135deg, rgba(12,129,228,0.15)...)`)
  - Effet hover `translateX(2px)` pour le glissement latéral
  - Icônes duotone avec scale au hover et drop-shadows dynamiques
  - Police Poppins (`font-secondary`) avec `font-medium`

- **Design System : Molécule Progress Ring**
  - Utilisation des couleurs sémantiques du design system (`stroke-success`, `stroke-warning`, etc.)
  - Ajout des variantes `info` et `cohorts`

- **Design System : Molécule Mini Stat**
  - Nouvelles variantes `primary-muted` et `warning-muted` avec fonds pastel
  - Documentation avec labels de variantes visibles

### Ajouté

- **Design System : Couleurs muted/subtle**
  - `--color-primary-muted` : rgba(12,129,228,0.04) — fond ultra-léger bleu
  - `--color-primary-subtle` : rgba(12,129,228,0.12) — bordure subtile bleue
  - `--color-warning-muted` : rgba(255,193,7,0.06) — fond ultra-léger jaune
  - `--color-warning-subtle` : rgba(255,193,7,0.2) — bordure subtile jaune
  - Classes CSS `container-primary-muted` et `container-warning-muted`

- **Design System : Icônes Solar**
  - `bolt` : Icône éclair paramétrable
  - `lightbulb-bolt` : Icône ampoule avec éclair (pour énergie Wh)

## [0.9.45] - 2026-02-16

### Ajouté

- **Design System : 9 nouvelles molécules**
  - **Progress Ring** : Cercle de progression SVG avec pourcentage central (5 tailles, 4 variantes)
  - **Mini Stat** : Affichage compact de statistique avec icône (tokens, Wh)
  - **Level Indicator** : Barres visuelles pour difficulté/priorité (auto-coloring)
  - **TOC Item** : Élément de table des matières (numéro, icône, états)
  - **Nav Item** : Élément de navigation sidebar avec badge et mode collapsed
  - **Section Header** : En-tête de section avec barre colorée (variantes produits)
  - **Filter Tabs** : Groupe de boutons filtres avec compteurs (pills, buttons, underline)
  - **Page Header** : En-tête de page avec breadcrumb, badge et actions
  - **User Card** : Carte utilisateur avec avatar, infos et progress ring

- **Design System : 2 organismes** (templates créés, pages de doc à venir)
  - **Course Card** : Carte de cours Moodia avec type, niveau, stats et consommation
  - **Company Card** : Carte d'entreprise admin avec contrat et outils activés

## [0.9.44] - 2026-02-16

### Ajouté

- **Design System : Variante "settings" pour Tabs**
  - Nouveau style inspiré de la page paramètres
  - Icônes en grayscale + opacity 0.7 par défaut
  - Coloration au hover/actif avec animation scale 1.1
  - Bordure inférieure 3px qui devient primary à l'actif
  - Paramètre `right: true` pour aligner un onglet à droite

### Corrigé

- **Tabs** : Vérification `is defined` pour les propriétés optionnelles (icon, badge)

## [0.9.43] - 2026-02-16

### Amélioré

- **Design System : StatCard redesign premium**
  - Espacement beaucoup plus généreux (padding p-6/p-8/p-10)
  - Icônes plus grandes (56px/64px/80px) avec dégradés
  - Valeurs plus imposantes (text-3xl/text-4xl/text-5xl)
  - Barre colorée animée en haut au survol
  - Animation icône scale+rotate avec easing bounce
  - Effet lift-up avec ombre renforcée au hover
  - Label uppercase avec letter-spacing
  - Coins plus arrondis (rounded-2xl)

## [0.9.42] - 2026-02-16

### Corrigé

- **Design System : Corrections SearchBar**
  - Centrage vertical des icônes avec flexbox (`inset-y-0 flex items-center`)
  - Masquage du bouton clear natif du navigateur (double croix)
  - Bouton clear utilise opacity/pointer-events au lieu de hidden (évite conflits CSS)
  - Taille de l'icône clear dynamique selon la taille du SearchBar (sm/md/lg)
  - Animation fluide d'apparition/disparition du bouton clear

## [0.9.41] - 2026-02-16

### Ajouté

- **Design System : Variante Moodia pour Pagination**
  - Nouvelle variante `moodia` avec design élégant style pill
  - Boutons Précédent/Suivant avec texte et icône animée
  - Container pill groupant les numéros de pages
  - Page active avec gradient bleu et shadow
  - Animations hover (translateY, translateX)

## [0.9.40] - 2026-02-16

### Ajouté

- **Design System : Modal avec variantes et footer automatique**
  - Variantes prédéfinies : info, success, warning, danger (icône et couleurs auto)
  - Footer automatique avec `actionLabel`, `actionIcon`, `cancelLabel`
  - Le bouton d'action prend automatiquement la variante correspondante
  - Nouvelle icône `user-add` dans la collection Solar (69 icônes total)
  - Exemples de formulaires et recherche dans modals
  - Isolation de contexte avec `only` pour éviter les fuites de variables

## [0.9.39] - 2026-02-16

### Ajouté

- **Design System : 21 nouvelles icônes Solar**
  - Navigation : chevron-left, chevrons-left, chevrons-right
  - Tendances : arrow-up, arrow-down, minus
  - Interface : bell, cart, cloud, crown, folder, heart, help, inbox
  - Actions : close, list, send, server, shield, target, wallet
  - Toutes les icônes sont paramétrables (size, color, colorPrimary, colorSecondary, opacitySecondary)
  - Total : 68 icônes Solar disponibles (+ 8 alias Bubul)

### Corrigé

- Correction de la référence d'icône `presentation` → `presentation-graph` dans la documentation StatCard

## [0.9.38] - 2026-02-16

### Ajouté

- **Design System : Molécule SearchBar**
  - Barre de recherche avec fonctionnalités avancées
  - 3 variantes : default, bordered, filled
  - 3 tailles : sm, md, lg
  - Affichage raccourci clavier (⌘K)
  - Bouton submit optionnel
  - Bouton clear pour effacer
  - Mode recherche instantanée (debounce)
  - Composition : Icon + Input + Button + Kbd

## [0.9.37] - 2026-02-16

### Ajouté

- **Design System : Molécule Pagination**
  - Navigation paginée pour listes et tableaux
  - Calcul intelligent des plages avec ellipsis
  - Boutons première/dernière et précédent/suivant
  - 3 tailles : sm, md, lg
  - Option "Page X sur Y"
  - Nombre de pages visibles configurable
  - Composition : Icon + Button

## [0.9.36] - 2026-02-16

### Ajouté

- **Design System : Molécule Tabs**
  - Système d'onglets pour organiser le contenu
  - 3 variantes : default, pills, underline
  - 3 tailles : sm, md, lg
  - Support icônes et badges par onglet
  - Onglets désactivables
  - Option pleine largeur
  - API JS : switchTab() et événement tabChange
  - Composition : Icon + Badge

## [0.9.35] - 2026-02-16

### Ajouté

- **Design System : Molécule StatCard**
  - Carte de statistique pour dashboards
  - Valeur principale avec label, icône et détail
  - Indicateurs de tendance : up, down, neutral
  - Barre de progression optionnelle
  - 5 variantes : default, primary, success, warning, error
  - 3 tailles : sm, md, lg
  - Composition : Icon + Typography + Progress

## [0.9.34] - 2026-02-16

### Ajouté

- **Design System : Molécule EmptyState**
  - Affichage pour états vides et résultats nuls
  - Icône, titre, description et bouton d'action optionnel
  - 3 tailles : sm, md, lg
  - 2 variantes : default, card
  - Composition : Icon + Typography + Button

## [0.9.33] - 2026-02-16

### Ajouté

- **Design System : Molécule Breadcrumb**
  - Fil d'ariane pour navigation hiérarchique
  - Icône Home avec lien personnalisable
  - 3 séparateurs : chevron, slash, arrow
  - 3 tailles : sm, md, lg
  - Accessibilité : aria-label, aria-current

## [0.9.32] - 2026-02-16

### Ajouté

- **Design System : Molécule Modal**
  - Dialogues modaux pour confirmations et formulaires
  - 6 tailles : sm, md, lg, xl, 2xl, full
  - Header avec icône et titre optionnels
  - Fonctions JS : `openModal()`, `closeModal()`, touche Escape
  - Options : closable, closeOnOverlay
  - Blocks Twig : body, footer

## [0.9.31] - 2026-02-16

### Ajouté

- **Design System : Molécule Alert**
  - Messages de notification et feedback utilisateur
  - Variantes : success, error, warning, info, neutral
  - Icône automatique selon la variante (personnalisable)
  - Option `dismissible` pour fermer l'alerte
  - 3 tailles : sm, md, lg

## [0.9.30] - 2026-02-16

### Ajouté

- **Design System : Molécule Card**
  - Conteneur flexible avec header, body et footer optionnels
  - Variantes : default, elevated, bordered, flat
  - Support des cards cliquables avec `href`
  - Header avec icône, titre et sous-titre
  - Blocks Twig : `body`, `footer`, `headerAction`

- **Design System : Explications pédagogiques**
  - Ajout de boxes explicatives pour Atomes, Molécules et Organismes
  - Diagrammes visuels de composition (Label + Input + Error = FormField)
  - Description des avantages de chaque niveau Atomic Design

### Corrigé

- **FormField : Alignement des icônes**
  - Correction du centrage vertical des icônes dans les inputs

## [0.9.29] - 2026-02-16

### Ajouté

- **Design System : Typography gradient**
  - Nouvelle couleur `gradient` pour le composant Typography
  - Applique le dégradé bleu Bubul au texte via `bg-clip-text`
  - Classes Tailwind : `from-gradient-start via-gradient-middle to-gradient-end`

### Amélioré

- **Design System : Index trié alphabétiquement**
  - Les 21 atomes sont maintenant triés de A à Z dans l'index
  - Meilleure navigation et recherche des composants

## [0.9.28] - 2026-02-16

### Ajouté

- **Design System : Icône bubul-logo**
  - Logo simplifié Bubul pour sidebar et favicons
  - Style duotone compatible avec le système d'icônes Solar
  - Utilisation : `{% include '_atoms/icon/icon.html.twig' with { name: 'bubul-logo' } %}`

## [0.9.27] - 2026-02-16

### Ajouté

- **Design System : Première molécule FormField**
  - `FormField` : Compose Label + Input + Error en un seul composant réutilisable
  - Support complet : types (text, email, password, textarea), icônes, tailles (sm/md/lg)
  - États : required, optional, disabled, hint, error, srOnly (label masqué)
  - Exemple de formulaire de login dans la documentation

- **Design System : Molécule FormCheckbox**
  - `FormCheckbox` : Checkbox stylisée avec label inline et messages
  - Variantes : default (minimal) et card (pour sélections importantes)
  - 3 tailles, états disabled/error, texte d'aide intégré

- **Structure Molécules**
  - Nouveau dossier `templates/_molecules/form/`
  - Documentation complète avec concept Atomic Design
  - Index du Design System mis à jour avec section Molécules

## [0.9.26] - 2026-02-16

### Ajouté

- **Design System : 7 nouveaux atomes**
  - `Textarea` : champ multiligne avec compteur de caractères, options de resize
  - `Label` : étiquette autonome avec required/optional, icône, tooltip
  - `Progress` : barre de progression avec 4 tailles, couleurs produits, effet striped
  - `Skeleton` : placeholder de chargement avec 8 variantes et 2 animations (pulse, wave)
  - `Divider` : séparateur horizontal/vertical avec texte ou icône
  - `Tag` : étiquette supprimable pour filtres avec états selected/disabled
  - `Kbd` : affichage de raccourcis clavier avec référence TinyMCE

- **Total : 20 atomes disponibles dans le Design System**

## [0.9.25] - 2026-02-16

### Modifié

- **Design System : Migration vers Solar Icons Bold Duotone**
  - Remplacement de Phosphor par 47 icônes Solar Icons (style bold-duotone)
  - Format standardisé : `viewBox="0 0 24 24"`, paramètres `size`, `color`, `colorPrimary`, `colorSecondary`, `opacitySecondary`
  - Support complet duotone avec deux couleurs indépendantes
  - Alias Bubul avec préfixe `bubul-` : bubul-moodia, bubul-serenia, bubul-slidia, bubul-sam, bubul-dashboard, bubul-entreprise, bubul-utilisateur, bubul-contrat
  - Nouvelles icônes : rocket, fire, dialog, chart, unlock, user-id
  - Package npm `@iconify-json/solar` et script d'extraction automatique

## [0.9.24] - 2026-02-16

### Modifié

- **Design System : Migration vers Phosphor Icons Duotone**
  - Remplacement de 32 icônes génériques par des versions Phosphor Duotone
  - Format standardisé : `viewBox="0 0 256 256"`, paramètres `size`, `color`, `class`
  - Classes CSS unifiées : `icon-primary` et `icon-secondary` avec `opacity="0.2"`
  - Icônes migrées : user, users, check, plus, trash, edit, arrow-left, arrow-right, power, lock, clock, settings, x, refresh, message, file-text, token, search, eye, home, info, alert, mail, download, chevron-right, star, book, calendar, warning, check-circle, logout, key

## [0.9.23] - 2026-02-16

### Ajouté

- **Design System : Atomes Icon et Typography**
  - `Icon` : Wrapper pour 67 icônes SVG avec 6 tailles (xs→2xl) et 10 couleurs sémantiques
  - `Typography` : 14 variantes typographiques (display, h1-h6, lead, body, small, caption, label, overline)
  - Documentation complète avec galerie d'icônes catégorisées et exemples de mise en page

## [0.9.22] - 2026-02-16

### Ajouté

- **Design System : 4 nouveaux atomes**
  - `Spinner` : Indicateur de chargement animé avec 5 tailles (xs→xl) et 6 couleurs (primary, white, gray, current, success, error)
  - `Avatar` : Représentation utilisateur avec image ou initiales auto-générées, 6 tailles, 7 variantes de couleur et indicateurs de statut (online, offline, busy, away)
  - `Link` : Liens stylisés avec 6 variantes de couleur, 3 tailles, support d'icônes et options de soulignement (none, hover, always)
  - `Tooltip` : Info-bulle CSS-only avec 4 positions, 2 styles (dark, light), utilisant Twig embed pour le contenu personnalisé

- **Documentation Design System**
  - Pages de documentation pour les 4 nouveaux atomes
  - Exemples interactifs : toolbar avec tooltips, liste d'utilisateurs avec avatars, spinner dans boutons
  - Section accessibilité pour le Tooltip (role="tooltip", group-focus-within)

## [0.9.22] - 2026-02-27

### Ajouté

- **Admin : Filtres et aide sur la distribution des tokens**
  - Filtres backend avec recherche (debounce 1000ms), statut et tri
  - Section d'aide avec 4 cartes explicatives sur le fonctionnement des tokens
  - URLs partageables grâce aux filtres côté serveur

- **Beta : Attribution automatique de 500 000 tokens**
  - Les nouveaux beta testeurs reçoivent automatiquement 500 000 tokens à l'acceptation
  - Nouvelle constante `AppLimits::BETA_INITIAL_TOKENS`

- **Admin : Ajout manuel de tokens**
  - Bouton "Ajouter des tokens" dans le profil utilisateur
  - Accessible à tous les administrateurs (ROLE_ADMIN)
  - Modal avec raccourcis (50k, 100k, 500k, 1M)

### Corrigé

- **CSS : Variables et styles manquants**
  - Ajout de `variables.css` dans base.html.twig (variables CSS non définies)
  - Ajout de `activity-list.css` dans base.html.twig (styles dashboard-card manquants)
  - Correction du design cassé sur les pages admin (activité récente, dashboard-card)

## [0.9.21] - 2026-02-15

### Ajouté

- **SAM : Synchronisation globale depuis le dashboard**
  - Nouveau bouton "Synchroniser" à côté de "Dernière synchronisation" sur le dashboard
  - Route `POST /sam/sync` pour déclencher la synchronisation complète
  - Exécute le même processus que la commande CRON `sam:sync:all`
  - Ordre de synchronisation : Users → Cohorts → Courses → Completions
  - Support des modes synchrone (`async=0`) et asynchrone (par défaut)
  - Nouveau message `SyncAllMessage` et handler `SyncAllHandler` pour le traitement en arrière-plan
  - Animation de rotation sur l'icône pendant la synchronisation

## [0.9.20] - 2026-02-15

### Ajouté

- **SAM : Sélecteur d'entreprise pour les admins**
  - Les admins peuvent maintenant changer d'entreprise dans SAM pour visualiser les données de n'importe quelle entreprise
  - Nouveau composant `company_selector.html.twig` réutilisable
  - Barre de sélection toujours visible sur le dashboard (`.sam-company-bar`)
  - Intégration dans le header des pages cours, utilisateurs et cohortes

- **SAM : Accès illimité pour les admins**
  - Les administrateurs ne sont plus bloqués par l'absence de configuration Moodle
  - Avertissement visuel (`.sam-alert--warning`) si l'entreprise n'a pas de config Moodle
  - Les admins peuvent naviguer dans toutes les pages SAM même sans données synchronisées
  - Les actions de synchronisation restent conditionnées à la présence d'une config Moodle valide

### Modifié

- **Controllers SAM** : Condition de blocage modifiée pour exclure les admins
  - `SamController` : Dashboard accessible avec données vides pour les admins
  - `SamCoursesController` : Liste et détail accessibles aux admins
  - `SamUsersController` : Liste et détail accessibles aux admins
  - `SamCohortsController` : Liste et détail accessibles aux admins

## [0.9.19] - 2026-02-15

### Supprimé

- **Tests problématiques supprimés**
  - `ApiKeyTest.php` : Tests pour l'API de gestion des clés API (fonctionnalité non finalisée)
  - `testMoodiaMinTokensIs1000` : Test mal nommé dans `AppLimitsTest.php` (teste 10000)
  - `testImageGenerationMinTokensIs1000` : Test mal nommé dans `AppLimitsTest.php` (teste 10000)
  - `testSlidiaGenerateMinTokensIs1000` : Test mal nommé dans `AppLimitsTest.php` (teste 10000)
  - `testImageUploadRequiresAuth` : Test avec assertion incorrecte (401 vs redirect)
  - `testSettingsDeveloperTabHasApiKeysSection` : Sélecteur `.developer-keys-list` inexistant

## [0.9.18] - 2026-02-15

### Ajouté

- **Design System SAM : Composants Twig unifiés**
  - `icon.html.twig` : Bibliothèque centralisée de 50+ icônes SVG (entities, actions, status, navigation)
  - `avatar.html.twig` : Avatar unifié pour user/cohort/course avec support `suspended`
  - `badge.html.twig` : Badge/pill avec couleurs par entité (users/cohorts/courses) et statut
  - `section_header.html.twig` : En-tête de section avec barre colorée par entité
  - `kpi_card.html.twig` : Carte KPI cliquable avec icône, valeur, trend, progress
  - `progress_bar.html.twig` : Barre de progression linéaire avec auto-couleur par %
  - `progress_ring.html.twig` : Cercle de progression SVG avec valeur centrale
  - `entity_tag.html.twig` : Tag/lien vers une entité (cours, cohorte)
  - `user_card.html.twig` : Carte utilisateur avec avatar, infos, progression

- **Variables CSS SAM étendues** (`variables.css`)
  - Gradients par entité : `--sam-users-gradient`, `--sam-cohorts-gradient`, `--sam-courses-gradient`
  - Hover states dynamiques avec `color-mix()` : `--sam-*-bg-hover`
  - Dark variants : `--sam-*-color-dark`
  - Stats/neutral : `--sam-stats-color`, `--sam-stats-bg`, `--sam-stats-gradient`

### Modifié

- **Templates SAM migrés vers les composants**
  - `index.html.twig` : KPIs et section headers utilisent les nouveaux composants
  - `users.html.twig` : Avatars, badges et icônes migrés
  - `user_detail.html.twig` : Section headers migrés
  - `cohorts.html.twig` : Avatars, entity tags et icônes migrés
  - `cohort_detail.html.twig` : Section headers et badges migrés
  - `courses.html.twig` : Utilise déjà `page_header.html.twig`
  - `course_detail.html.twig` : Section headers, badges et entity tags migrés

- **CSS consolidé** (`dashboard.css`)
  - Nouveaux styles pour les composants avec classes par entité (`--users`, `--cohorts`, `--courses`)
  - Classes `sam-kpi-card--clickable` avec effet hover et barre colorée
  - Support des progress bars/rings colorés par entité ou auto-status

### Supprimé

- `templates/sam/_partials/activity_icon.html.twig` (doublon de `_activity_icon.html.twig`)

### Technique

- Système de couleurs unifié : chaque entité SAM a sa couleur dédiée
  - **Users** = Bleu (#0ea5e9)
  - **Cohorts** = Violet (#8b5cf6)
  - **Courses** = Vert (#10b981)
  - **Activities** = Orange (#f59e0b)

## [0.9.17] - 2026-02-14

### Ajouté

- **Page profil utilisateur** (`/sam/users/{id}`)
  - KPIs visuels : cours, activités complétées, temps passé
  - Donut chart de progression globale (Symfony UX Chartjs)
  - Accordion cours → sections → activités avec état de complétion
  - Scores et tentatives pour les quiz
  - Bouton synchronisation manuelle des progressions
  - Actions : Modifier, Suspendre/Réactiver, Supprimer

- **Système de suivi de progression**
  - Nouvelle entité `SamMoodleCompletion` pour stocker les complétions
  - Service `SamCompletionSyncService` pour synchroniser depuis Moodle
  - Intégration dans `sam:sync:all` (nouvelle étape "Progressions")
  - Option `--skip-completions` pour ignorer la sync des progressions
  - Calcul du temps passé via plugin `local_clicksws`
  - Support des données quiz (score, tentatives)

- **Lignes cliquables sur la page Stagiaires**
  - Cliquer sur un utilisateur ouvre sa page de détail
  - Boutons d'action restent fonctionnels (z-index supérieur)

### Modifié

- `SamSyncAllCommand` : ajout de la 4ème étape de synchronisation
- `MoodleApiConfig` : constantes pour les fonctions quiz déjà présentes
- Système de notification : utilisation du système existant (`window.showNotification`)
- Design amélioré : KPIs avec gradients, donut chart stylisé, activités avec bordures

## [0.9.16] - 2026-02-14

### Ajouté

- **Page détail cohorte** (`/sam/cohorts/{id}`)
  - Affichage des cours en scroll horizontal style Netflix
  - Membres affichés en cartes avec barres de progression
  - Rendu côté serveur (pas de loading spinner)
  - UI optimiste pour ajout/suppression de membres
  - Boutons Modifier et Supprimer la cohorte

- **Cohortes cliquables sur la page Cours**
  - Tags de cohortes deviennent des liens vers la page détail
  - Effet hover violet avec texte blanc

- **Pagination moderne**
  - Numéros de pages cliquables avec ellipsis
  - Page active avec dégradé bleu
  - Design responsive

### Modifié

- **Bouton sync** déplacé à l'intérieur du header de cours
- **Modal d'ajout de membres** agrandie avec meilleure UX
- **Bouton Réinitialiser** : hover en rouge (au lieu de bleu)
- **Avatar cohorte** dans le header : icône violette sans fond

### Corrigé

- Overflow du dropdown de recherche dans les modals
- Structure HTML invalide (bouton dans bouton) sur la page cours

## [0.9.15] - 2026-02-13

### Ajouté

- **Accordéon 3 niveaux sur la page Cours** (`/sam/courses`)
  - Structure hiérarchique : Cours → Sections → Activités
  - Boutons "Tout déplier" / "Tout replier"
  - Contrôleur Stimulus `accordion_controller.js`

- **Synchronisation individuelle par cours**
  - Bouton sync à droite de chaque cours dans l'accordéon
  - Animation de chargement pendant la sync
  - Notifications toast (succès/erreur) via le système existant

- **Filtre "Cacher les cours masqués"**
  - Toggle dans la barre de filtres
  - Paramètre `hideHidden` persisté dans l'URL

- **Affichage des cohortes inline**
  - Tags violets dans les métadonnées de chaque cours
  - Max 2 cohortes affichées + badge "+N"

### Modifié

- **Correction des graphiques Chart.js avec Turbo**
  - Destruction propre des charts dans `turbo:before-cache`
  - Réinitialisation dans `turbo:load` et `turbo:render`

- **Titre du dashboard SAM** : "Bonjour Manager 👋" → "Tableau de bord LMS"

- **Batch loading des sections** : méthode `findByCoursesWithActivities()` pour éviter N+1

### Supprimé

- Pages `course_detail.html.twig` et `section_detail.html.twig` (remplacées par l'accordéon)
- ~1200 lignes de CSS obsolète

## [0.9.14] - 2026-02-12

### Ajouté

- **Collection Postman SAM API** (`docs/postman/Bubul_SAM_API.json`)
  - 29 endpoints organisés en 6 dossiers (Auth, Stagiaires, Cohortes, Cours, Sections, Activités)
  - Variables `{{base_url}}` et `{{api_key}}` préconfigurées
  - Authentification globale via header `X-API-Key`

- **API Sections et Activités**
  - `GET /api/moodlev5/sections/{id}` : détail d'une section avec ses activités
  - `GET /api/moodlev5/sections/{id}/activities` : liste des activités
  - `GET /api/moodlev5/activities/{id}` : détail d'une activité (inclut `is_trackable`)
  - `GET /api/moodlev5/activities/{id}/completion` : statut de complétion par utilisateur

- **Commande `sam:link-cohort`** : liaison manuelle cohorte-cours

### Modifié

- **`SamCourseUser.cohortIds` : Support multi-cohortes**
  - Migration de FK `cohort_id` vers JSON array `cohort_ids`
  - Un utilisateur peut maintenant appartenir à plusieurs cohortes inscrites au même cours
  - Méthodes : `getCohortIds()`, `addCohortId()`, `removeCohortId()`
  - Requêtes optimisées avec `JSON_CONTAINS()` MySQL

- **Repository `SamCourseUserRepository`**
  - `findDistinctCohortsByCourse()` : cohortes uniques via JSON
  - `findDistinctCoursesByCohort()` : cours accessibles via une cohorte
  - `countDistinctCohortsByCourse()` / `countDistinctCoursesByCohort()`

- **Contrôleurs SAM** : passage des mappings pré-calculés aux templates
  - `courseCohorts` dans courses.html.twig
  - `cohortCourses` dans cohorts.html.twig
  - Évite les appels de méthodes supprimées dans les templates

### Supprimé

- `SyncCourseEnrollmentsCommand.php` (obsolète, remplacé par sync intégré)
- Méthodes `getEnrolledCohorts()` / `getEnrolledCourses()` des entités (calcul via repository)

## [0.9.13] - 2026-02-12

### Ajouté

- **`SamCourseUser` : Source unique des utilisateurs inscrits**
  - Nouvelle entité centralisant TOUS les inscrits (via cohorte + inscriptions directes)
  - Champs : `course_id`, `user_id`, `cohort_id` (nullable), `enrollment_type`
  - Types d'inscription : `cohort`, `manual`, `self`, `guest`
  - Cascade delete : suppression automatique si cours, user ou cohorte supprimé
  - Repository avec méthodes optimisées

- **Synchronisation automatique des inscrits**
  - `SamCourseSyncService::syncCourseUsers()` : appelle `core_enrol_get_enrolled_users`
  - Détection automatique de l'origine (cohorte ou inscription directe)
  - Statistiques `course_users` ajoutées au retour de sync

### Modifié

- **Architecture SAM : Calcul à la volée des compteurs**
  - `SamMoodleCourse.getEnrolledUsersCount()` : via `count(courseUsers)` (simplifié)
  - `SamMoodleCourse.getSectionsCount()` : via `count(sections)`
  - `SamMoodleCourse.getActivitiesCount()` : via relations
  - `SamMoodleSection.getActivitiesCount()` : via `count(activities)`
  - `SamMoodleCohort.getMembersCount()` : via `count(members)`
  - Suppression des colonnes de compteurs stockés (migration)

- **Synchronisation des cohortes avec membres**
  - `SamCohortSyncService` synchronise maintenant les membres automatiquement
  - Un seul appel API pour tous les membres (optimisation)

### Supprimé

- Colonnes obsolètes supprimées :
  - `sam_moodle_course.sections_count`, `activities_count`, `trackable_activities_count`, `enrolled_users_count`
  - `sam_moodle_section.activities_count`, `trackable_activities_count`
  - `sam_moodle_cohort.members_count`

## [0.9.12] - 2026-02-12

### Ajouté

- **Stratégie cross-match optimisée pour liaisons Cours-Cohortes**
  - Fallback intelligent quand l'API `core_enrol_get_course_enrolment_methods` ne fonctionne pas
  - Croisement automatique des utilisateurs inscrits au cours avec les membres des cohortes
  - Seulement 2 appels API (enrolled users + batch cohort members) au lieu de N+1
  - Seuil adaptatif : 100% pour petites cohortes (<3 membres), 50% sinon

- **Commandes CLI de debug et synchronisation SAM**
  - `sam:sync-enrollments [course-id]` : synchronisation manuelle des enrollments
    - Option `--debug` pour afficher les données brutes de l'API
    - Option `--company` pour cibler une entreprise spécifique
  - `sam:debug-api <course-moodle-id>` : debug des appels API Moodle
    - Test de `core_enrol_get_enrolled_users`
    - Test de `core_enrol_get_course_enrolment_methods`
    - Test de `core_cohort_get_cohort_members`
  - `sam:link-cohort <course-id> <cohort-id>` : liaison manuelle cours-cohorte

- **Page détail cours améliorée**
  - Section "Cohortes inscrites" avec badges et nombre de membres
  - Tags cliquables vers la page de la cohorte
  - Mise en évidence visuelle avec couleur primaire

- **API endpoints pour associations**
  - `GET /api/moodlev5/courses/{id}/cohorts` : liste des cohortes inscrites à un cours
  - `GET /api/moodlev5/cohorts/{id}/courses` : liste des cours accessibles à une cohorte
  - `GET /api/moodlev5/courses/{id}/enrollment-methods` : méthodes d'inscription (debug)

### Amélioré

- Constante `FUNC_GET_COHORT_MEMBERS` ajoutée dans `MoodleApiConfig`
- Performance : batch des appels API pour récupérer tous les membres de cohortes en une requête

## [0.9.11] - 2026-02-12

### Ajouté

- **Liaison Cours-Cohortes SAM**
  - Entité `SamCourseEnrollment` : liaison entre cours et cohortes via enrollment Moodle
    - Champs : `moodleEnrollmentId`, `status` (active/suspended), `syncedAt`
    - Relations ManyToOne vers `SamMoodleCourse` et `SamMoodleCohort` (cascade delete)
    - Contrainte unique sur (course, cohort)
  - Repository `SamCourseEnrollmentRepository` avec méthodes :
    - `findByCourseAndCohort()`, `findByCourse()`, `findByCohort()`
    - `deleteNotInMoodleIds()`, `countByCourse()`, `countByCohort()`
  - Méthode `syncCourseEnrollments()` dans `SamCourseSyncService`
    - Appelle l'API `core_enrol_get_course_enrolment_methods`
    - Filtre les enrollments de type "cohort"
    - Crée/met à jour les liaisons locales

- **Affichage des associations Cours-Cohortes**
  - Page Cours : affiche les cohortes associées sous forme de tags (max 3 + badge "+N")
  - Page Cohortes : affiche les cours accessibles sous forme de tags (max 2 + badge "+N")
  - Nouveaux styles CSS pour les tags `.sam-course__cohorts` et `.sam-cohort__courses`
  - Méthodes pratiques `getEnrolledCohorts()` et `getEnrolledCourses()` sur les entités

## [0.9.10] - 2026-02-12

### Ajouté

- **Synchronisation asynchrone SAM** (Symfony Messenger)
  - `SyncCoursesMessage` : message pour synchronisation en arrière-plan
  - `SyncCoursesHandler` : handler pour traitement async des cours
  - Support du mode async dans les endpoints `/sam/courses/sync` et `/sam/courses/{id}/sync`
  - Configuration Messenger pour routage vers le transport `async`
  - Retry automatique (3 tentatives avec multiplicateur x2)

- **Notifications de synchronisation avec progression**
  - Nouveau type de notification `progress` avec barre de progression animée
  - Notification persistante pendant toute la durée de la synchronisation
  - Spinner animé dans l'icône de la notification
  - Transformation fluide en notification succès/erreur à la fin
  - API globales : `showProgressNotification()`, `updateNotification()`, `dismissNotification()`
  - Messages personnalisés par type de données (cours, utilisateurs, cohortes)
  - Fallback vers `alert()` si le système de notifications n'est pas disponible

### Corrigé

- **Comptage des utilisateurs inscrits** : le nombre d'inscrits affichait toujours 0
  - Ajout de la méthode `countEnrolledUsers()` dans `SamCourseSyncService`
  - Appel API `core_enrol_get_enrolled_users` pour récupérer le nombre réel
  - Le compteur est maintenant mis à jour lors de chaque synchronisation

## [0.9.9] - 2026-02-12

### Ajouté

- **Architecture hiérarchique SAM : Cours → Sections → Activités**
  - Entité `SamMoodleSection` : cache local des sections Moodle
    - Champs : `moodleId`, `name`, `summary`, `visible`, `position`
    - Compteurs : `activitiesCount`, `trackableActivitiesCount`
    - Relation ManyToOne vers `SamMoodleCourse` (cascade delete)
  - Entité `SamMoodleActivity` : cache local des activités Moodle
    - Champs : `moodleId`, `name`, `type`, `visible`, `url`, `position`
    - Flags tracking : `isTrackable`, `completionType` (0=none, 1=manual, 2=auto)
    - Relation ManyToOne vers `SamMoodleSection` (cascade delete)
    - Méthodes utilitaires : `getCompletionTypeLabel()`, `getTypeIcon()`

- **Pages de détail SAM**
  - `/sam/courses/{id}` : détail d'un cours avec liste des sections
  - `/sam/courses/{id}/sections/{sectionId}` : détail d'une section avec liste des activités
  - Navigation hiérarchique avec fil d'Ariane
  - Bouton de synchronisation individuelle par cours

- **Repositories SAM**
  - `SamMoodleSectionRepository` : findByCourse, findByCourseWithActivities, findOneByMoodleId
  - `SamMoodleActivityRepository` : findBySection, findTrackableBySection, findByType

- **Synchronisation optimisée** (réduction des appels API)
  - Avant : ~5N appels API (cours + sections + cohortes + users par cours)
  - Après : N+1 appels API (1 pour tous les cours + 1 par cours pour le contenu)
  - Synchronisation en cascade : cours → sections → activités en une seule passe
  - Nettoyage automatique des éléments supprimés sur Moodle

- **Styles CSS pour les pages de détail**
  - `.sam-breadcrumb` : fil d'Ariane
  - `.sam-course-detail` : en-tête détail cours/section
  - `.sam-sections-list`, `.sam-section-item` : liste des sections
  - `.sam-activities-list`, `.sam-activity-item` : liste des activités
  - Icônes colorées par type d'activité (quiz, page, assign, forum, etc.)
  - Badges de statut (trackable, masqué)

### Modifié

- **SamCourseSyncService** : synchronisation complète avec sections et activités
- **SamCoursesController** : ajout des routes détail et sync individuel
- **Commande `sam:sync:courses`** : affichage des stats par entité (cours, sections, activités)
- **SamMoodleCourse** : ajout de la relation OneToMany vers sections

## [0.9.8] - 2026-02-12

### Ajouté

- **Page de gestion des cours SAM** (`/sam/courses`)
  - Affichage de la liste des cours depuis la base locale
  - Synchronisation manuelle avec bouton "Rafraîchir"
  - Barre de recherche par nom de cours
  - Statistiques globales (cours, sections, activités, inscriptions)
  - Affichage des stats par cours (sections, activités, inscrits)
  - Pagination intégrée
  - Bouton "Gérer les cours" ajouté sur la page d'accueil SAM

- **Entité SamMoodleCourse** : cache local des cours Moodle
  - Champs : `moodleId`, `shortname`, `fullname`, `summary`, `categoryId`
  - Champs dates : `startDate`, `endDate`, `syncedAt`, `createdAt`, `updatedAt`
  - Champs stats : `sectionsCount`, `activitiesCount`, `trackableActivitiesCount`, `enrolledUsersCount`
  - Contrainte d'unicité `(company_id, moodle_id)`
  - Méthodes `createFromMoodleData()` et `updateFromMoodleData()`

- **SamCourseSyncService** : synchronisation des cours
  - Récupération des cours et de leurs détails via API Moodle
  - Upsert intelligent (insert ou update selon existence)
  - Suppression des cours supprimés sur Moodle
  - Récupération des statistiques (sections, activités, inscrits)

- **Commande CRON** `sam:sync:courses`
  - Synchronise tous les cours de toutes les entreprises
  - Option `--company=UUID` pour synchroniser une seule entreprise
  - Affichage des statistiques de synchronisation

- **API Cours SAM enrichie** avec suivi de complétion
  - `GET /api/moodlev5/courses/{id}/detail` : cours complet avec sections, activités et statistiques
  - `GET /api/moodlev5/courses/{id}/sections` : liste des sections avec compteurs d'activités
  - Champs `is_trackable` et `completion_type` ajoutés aux activités

- **API Sections SAM**
  - `GET /api/moodlev5/sections/{id}?course_id=X` : détail d'une section avec ses activités
  - `GET /api/moodlev5/sections/{id}/activities?course_id=X` : liste des activités d'une section

- **API Activités SAM**
  - `GET /api/moodlev5/activities/{id}?course_id=X` : détail d'une activité avec contexte complet
  - `GET /api/moodlev5/activities/{id}/completion?course_id=X` : statut de complétion par utilisateur avec statistiques

- **Constantes API Moodle** (`MoodleApiConfig.php`)
  - `FUNC_GET_COURSE_CONTENTS` : récupération du contenu des cours

- **Styles CSS** pour les cours (`.sam-course*`, `.sam-stats*`, `.sam-search*`)
  - Design cohérent avec cohortes et utilisateurs
  - Icône verte avec dégradé pour les cours
  - Badges pour les statistiques

### Modifié

- **MoodleCourseService** : enrichi avec méthodes pour sections et activités
- **formatSections()** : ajout de `is_trackable`, `completion_type` et `position`
- **Page index SAM** : ajout du bouton "Gérer les cours"

## [0.9.7] - 2026-02-12

### Ajouté

- **Page de gestion des cohortes SAM** (`/sam/cohorts`)
  - Affichage de la liste des cohortes depuis la base locale
  - Synchronisation manuelle avec bouton "Rafraîchir"
  - Affichage du nombre de membres par cohorte
  - Pagination intégrée

- **Entité SamMoodleCohort** : cache local des cohortes Moodle
  - Champs : `moodleId`, `name`, `idnumber`, `description`, `membersCount`, `syncedAt`
  - Contrainte d'unicité `(company_id, moodle_id)`
  - Méthodes `createFromMoodleData()` et `updateFromMoodleData()` pour la synchronisation

- **SamCohortSyncService** : synchronisation des cohortes
  - Récupération des cohortes via API Moodle
  - Upsert intelligent (insert ou update selon existence)
  - Suppression des cohortes supprimées sur Moodle
  - Retour des statistiques de synchronisation

- **Styles CSS pour les cohortes** (`.sam-cohort*`)
  - Design cohérent avec les utilisateurs
  - Icône violette avec dégradé
  - Badge du nombre de membres

## [0.9.6] - 2026-02-11

### Ajouté

- **API SAM MoodleV5 complète** : architecture REST pour le Suivi Apprenants Moodle
  - 28 endpoints sous `/api/moodlev5/*` et `/api/moodlev5/export/*`
  - Authentification mixte : clé API OU session (via `MixedAuthenticator`)
  - Résolution de contexte entreprise selon le rôle (admin via `?company=`, autres via association)

- **Gestion des utilisateurs Moodle** (`/api/v1/moodle/users`)
  - CRUD complet : liste, détail, création, modification, suppression
  - Activation/désactivation (`/toggle`)
  - Liste des cours d'un utilisateur avec progression (`/{id}/courses`)

- **Gestion des cohortes Moodle** (`/api/v1/moodle/cohorts`)
  - CRUD complet : liste, détail, création, modification
  - Gestion des membres : liste, ajout, retrait (`/{id}/members`)

- **Gestion des cours Moodle** (`/api/v1/moodle/courses`)
  - Liste des cours avec recherche
  - Détail d'un cours avec sections et activités
  - Utilisateurs inscrits avec progression

- **Progression détaillée** (`/api/v1/moodle/progress`)
  - Progression globale d'un utilisateur (tous ses cours)
  - Progression détaillée utilisateur/cours
  - Progression de tous les utilisateurs d'un cours
  - Progression par section et par activité
  - Détail des quiz avec tentatives

- **Exports statistiques** (`/api/v1/export`)
  - Export utilisateur : `/export/users/{id}?format=excel|pdf`
  - Export cours : `/export/courses/{id}?format=excel|pdf`

- **DTOs structurés** pour les requêtes et réponses API
  - Request DTOs : `CreateUserRequest`, `UpdateUserRequest`, `CreateCohortRequest`, `CohortMembersRequest`
  - Response DTOs : `MoodleUserResponse`, `MoodleCohortResponse`, `MoodleCourseResponse`
  - Progress DTOs : `CourseProgressResponse`, `SectionProgressResponse`, `QuizProgressResponse`

- **Services API dédiés**
  - `UserContextService` : résolution du contexte utilisateur et entreprise
  - `PaginationService` : gestion de la pagination standardisée
  - `MoodleUserService`, `MoodleCohortService`, `MoodleCourseService`
  - `MoodleProgressService`, `MoodleExportService`

- **Exceptions API typées**
  - `ApiException` : exception de base avec codes HTTP standardisés
  - `CompanyNotFoundException` : gestion spécifique des erreurs d'entreprise

### Amélioré

- **Page Développeur** : exemple CURL mis à jour avec la nouvelle API MoodleV5 (`/api/moodlev5/me`)
- **Firewall sécurité** : ajout du firewall `api_moodlev5` pour l'authentification mixte
- **Documentation template** : réponse JSON enrichie avec les infos entreprise

### Supprimé

- `ApiMeController` (`/api/me`) : remplacé par `MeController` (`/api/moodlev5/me`)
- `MoodleController` (`/api/sam/*`) : remplacé par les contrôleurs MoodleV5

### Corrigé

- **Notification "Connexion réussie" récurrente** : le `MixedAuthenticator` déclenchait un `LoginSuccessEvent` à chaque requête API/AJAX, causant l'affichage répété de la notification. Le `AuthenticationSuccessHandler` ignore maintenant les requêtes API (`/api/*`) et AJAX.

## [0.9.5] - 2026-02-11

### Ajouté

- **Module SAM complet** : dashboard de suivi des apprenants Moodle
  - Contrôleur `SamController` pour la gestion du module
  - Service `MoodleApiClient` pour l'intégration API Moodle
  - Configuration centralisée via `MoodleApiConfig`
  - Nouveau design avec cartes de fonctionnalités et badges de phase

### Amélioré

- **Page Développeur** : refonte complète avec guide en 3 étapes
  - Étape 1 : Génération de la clé API (one-click, sans saisie de nom)
  - Étape 2 : Exemple de requête CURL
  - Étape 3 : Résultat JSON attendu
  - Design unifié avec les autres onglets de paramètres (fond blanc, `.settings-card`)
  - Modal de copie simplifiée avec notification de succès
  - Modals unifiées (sans bordures colorées)
- **Navigation admin** : visibilité du bouton "Voir les entreprises" pour les admins
- **Icône SAM** : agrandissement dans la liste des activités

### Corrigé

- **Création de clés API** : ajout de `credentials: 'same-origin'` aux appels `fetch()` pour envoyer les cookies de session
- **Firewall sécurité** : exclusion de `/api/sam/` de l'authentification par clé API (utilise la session)

## [0.9.4] - 2026-02-11

### Ajouté

- **API REST avec clés API personnelles** : système d'authentification par clés API pour les clients externes
  - Entité `ApiKey` avec stockage sécurisé (SHA-256, jamais en clair)
  - Format de clé : `bubul_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` (128 bits d'entropie)
  - Authentificateur `ApiKeyAuthenticator` supportant `X-API-Key` et `Authorization: Bearer`
  - Controller `ApiKeyController` pour créer, lister et révoquer les clés
  - Mise à jour automatique de `lastUsedAt` à chaque utilisation
- **Onglet Développeur dans Paramètres** : interface de gestion des clés API
  - Liste des clés avec préfixe masqué, date de création et dernière utilisation
  - Création de clé avec affichage ONE-TIME de la clé complète
  - Copie dans le presse-papier
  - Révocation avec confirmation
  - Documentation d'utilisation intégrée
- **Firewall API sécurisé** : configuration Symfony séparée pour les routes `/api/`
  - Mode stateless (pas de session)
  - CORS headers via `CorsSubscriber`
- **Messages d'erreur API** : nouvelles constantes dans `AppMessages` pour les clés API
- **Rate limiting API** : limitation à 100 requêtes/minute par clé API via `ApiRateLimitSubscriber`

### Modifié

- **Organisation EventSubscribers** : réorganisation en sous-dossiers `API/` et `Moodle/` pour une meilleure lisibilité
- **UI Développeur simplifiée** : interface épurée sans hero section ni documentation (à venir plus tard)

### Sécurité

- Les clés API sont hashées en SHA-256 avant stockage
- La clé complète n'est jamais stockée et affichée qu'une seule fois
- Vérification que l'utilisateur est actif avant authentification
- Soft delete pour la révocation (conservation de l'historique)

## [0.9.3] - 2026-02-11

### Ajouté

- **Module SAM** : nouveau module de suivi des apprenants Moodle
  - Contrôleur Stimulus `moodle_status_controller.js` pour vérification du statut de connexion Moodle en temps réel
  - Composant CSS `moodle-status` pour les badges de statut (connecté, erreur, non configuré)
  - Icône SAM dans `_components/icons/sam.html.twig`
- **Champ `isTokenable` sur Tool** : permet de distinguer les outils consommant des tokens (Moodia, Serenia, Slidia) des outils de gestion (SAM)
- **Accès admin universel aux outils** : les administrateurs ont accès à tous les outils sans nécessiter d'entrées UserTool

### Amélioré

- **Affichage dynamique des outils** : les icônes des outils sont maintenant chargées dynamiquement depuis la base de données
- **Formulaire entreprise** : fusion de la section "Adresse" dans "Informations" pour une meilleure lisibilité
- **Préservation du token Moodle** : le token API existant est conservé si le formulaire est soumis sans modification
- **Usage par outil** : filtre les outils non-tokenables (SAM) de la section statistiques
- **Bouton admin pleine largeur** : nouvelle classe `btn-secondary--full`

### Corrigé

- **Accès refusé admin** : les admins peuvent maintenant accéder à tous les outils via `ToolAccessService::canAccess()`
- **Null company sur /sam** : gestion du cas où l'admin n'a pas d'entreprise assignée

## [0.9.2] - 2026-02-11

### Ajouté

- **Route pour contrats orphelins** : nouvelle route `/admin/contracts/{contractUuid}` pour accéder aux contrats dont l'entreprise a été supprimée
- **Variable CSS `--color-warning-dark`** : #B45309 pour les éléments warning sombres

### Amélioré

- **Liste globale des contrats** : tous les contrats sont maintenant cliquables, y compris les orphelins
- **Page détail contrat** : gestion du cas où l'entreprise n'existe plus (breadcrumb adapté, badge "Contrat orphelin")
- **Design contrats orphelins** : affichage "---" pour l'entreprise, badge warning dans le header

## [0.9.1] - 2026-02-11

### Ajouté

- **Suppression d'entreprise** : nouvelle fonctionnalité avec modal de confirmation sécurisée
  - Confirmation par saisie du nom de l'entreprise
  - Désactivation automatique des contrats (conservés pour historique)
  - Désactivation des utilisateurs (sauf admins)
  - Retrait des outils et réinitialisation des allocations tokens
- **Contrainte d'unicité sur le nom d'entreprise** : validation Symfony + contrainte BDD
- **Filtre "Aucune entreprise"** : dans la liste des utilisateurs admin
- **Variable CSS `--color-error-dark`** : pour les états hover des boutons danger

### Amélioré

- **Modals de confirmation** : nouveau design unifié avec headers colorés et listes à puces
  - Modal suppression : style rouge (danger)
  - Modal désactivation : style orange (warning)
  - Modal réactivation : style vert (success)
- **Cascade désactivation entreprise** : exclut maintenant les administrateurs
- **Relation Contract-Company** : `SET NULL` au lieu de `CASCADE` pour préserver l'historique

### Corrigé

- Migration `is_beta_tester` : vérification conditionnelle pour éviter les erreurs si colonne existe

## [0.9.0] - 2026-02-08

### Ajouté

- **CompanyActivationService** : Nouveau service centralisant la logique d'activation/désactivation
  - `deactivateCompany()` : cascade complète (contrats + utilisateurs + outils + tokens)
  - `activateCompany()` : réactive les gestionnaires uniquement
  - `deactivateAllUsers()` : désactive tous les utilisateurs d'une entreprise
  - `activateManagers()` : réactive uniquement les gestionnaires
  - `syncAllUsersTools()` : synchronise les outils avec les contrats actifs

### Refactorisé

- **CompanyController::toggle()** : utilise maintenant `CompanyActivationService`
- **ContractController::toggle()** : utilise maintenant `CompanyActivationService`
- Suppression de la méthode privée `syncCompanyUsersTools()` (déplacée dans le service)

### Technique

- Architecture DRY : logique métier centralisée dans un service réutilisable
- Les controllers délèguent la logique au service et gèrent uniquement l'HTTP

## [0.8.9] - 2026-02-08

### Amélioré

- **Toggle entreprise avec cascade complète** :
  - Désactivation entreprise → désactive automatiquement TOUS les contrats actifs
  - Désactivation entreprise → désactive TOUS les utilisateurs + retire leurs accès outils + reset tokens mensuels
  - Réactivation entreprise → réactive uniquement les gestionnaires (ROLE_COMPANY_MANAGER)
  - Réactivation entreprise → les contrats restent désactivés (à réactiver manuellement)
  - Messages flash détaillés indiquant le nombre d'éléments affectés

### Technique

- `CompanyController::toggle()` : gestion cascade complète avec compteurs d'éléments affectés

## [0.8.8] - 2026-02-08

### Simplifié

- **Modèle d'accès aux outils** :
  - Suppression du champ `isEnabled` de l'entité `UserTool`
  - L'existence d'une entrée `UserTool` = accès autorisé (suppression = accès révoqué)
  - Simplification du code ToolAccessService

### Corrigé

- **Modals admin** :
  - Correction conflit CSS entre `admin-company.css` et `admin-shared.css`
  - Les modals de désactivation s'affichent à nouveau correctement
  - Suppression des styles `.modal` dupliqués de `admin-company.css`

## [0.8.7] - 2026-02-08

### Amélioré

- **Toggle contrat avec gestion utilisateurs** :
  - Désactivation contrat → désactive automatiquement TOUS les utilisateurs de l'entreprise
  - Réactivation contrat → réactive uniquement les gestionnaires (ROLE_COMPANY_MANAGER)
  - Message flash indiquant le nombre d'utilisateurs affectés

- **Modal toggle contrat** :
  - Correction structure HTML avec classes BEM standard (modal-overlay, modal, modal__header, etc.)
  - Icône power ajoutée dans le header de la modal

### Corrigé

- **Page distribution tokens** :
  - Ajout de `defaultAllocation` et `activeUsersCount` dans les statistiques
  - Correction erreur Twig "Key defaultAllocation does not exist"

### Technique

- `ContractController::toggle()` : gestion cascade activation/désactivation des utilisateurs
- `UserTokenDistributionService::getDistributionStats()` : retourne maintenant `defaultAllocation` et `activeUsersCount`
- Nettoyage template `admin/company/show.html.twig` (suppression code inutilisé)

## [0.8.6] - 2026-02-08

### Ajouté

- **Liste globale des contrats (Admin)** :
  - Statistiques visuelles avec cartes (total, actifs, expirant bientôt, expirés)
  - Filtres auto-submit (entreprise, statut) sans rechargement manuel
  - Colonne tarif par licence avec format "XX,XX € HT/mois"
  - Lignes de tableau cliquables pour accès direct au détail contrat

### Amélioré

- **Synchronisation des outils** :
  - Activation contrat → accorde tous les outils du contrat aux utilisateurs actifs
  - Désactivation contrat → re-synchronise avec les contrats restants (ne retire que les outils non couverts)
  - Gestion multi-contrats : les outils des autres contrats actifs sont préservés

- **Formulaire contrat** :
  - Tous les champs obligatoires (sauf notes internes)
  - Validation "au moins 1 outil" avec message d'erreur explicite
  - Suppression du doublon "€" dans le champ prix par licence
  - Textes d'aide visibles (correction couleur #64748b)
  - Bouton Annuler repositionné à gauche

- **Page détail contrat** :
  - Bouton retour utilise `history.back()` pour navigation naturelle

### Corrigé

- **Textes d'aide formulaire** : correction de la couleur (était invisible, clair sur clair)

### Technique

- `ContractController::toggle()` : utilise `ToolAccessService::syncUserTools()` pour synchronisation robuste
- `ContractType.php` : ajout contraintes NotBlank et Count(min: 1) pour tools
- `admin-contract.css` : nouveau design data-table avec hover, status badges, et prix formaté

## [0.8.5] - 2026-02-08

### Amélioré

- **Page Édition Contrat** :
  - Formulaire pleine largeur (suppression de la limite 800px)
  - Boutons réordonnés : Annuler à gauche, Enregistrer à droite
  - Suppression de la zone de danger (pas de suppression de contrat)
  - Couleurs des checkboxes outils améliorées (gradient bleu Bubul)

- **Affichage Contrats (Page Entreprise)** :
  - Nouveau design deux lignes : nom + badge en haut, détails en bas
  - Fond blanc avec hover bleu clair
  - Icônes outils agrandies (20px) avec couleur primaire
  - "users" remplacé par "utilisateurs"
  - Séparateurs visuels entre les informations

- **Icônes** :
  - Nouvelle icône contrat : document avec lignes de texte décroissantes
  - Classes `svg-primary`/`svg-secondary` pour couleurs correctes dans navbar
  - Correction icône token pour affichage navbar (company_manager)

### Technique

- `admin-contract.css` : `.contract-form` full width, `.form-actions` flex-end
- `admin-company.css` : nouveau design `.contract-row` deux lignes
- `contract.html.twig` : icône refaite avec fill uniquement
- `token.html.twig` : classes svg pour héritage couleur

## [0.8.4] - 2026-02-08

### Corrigé

- **Bug Critique - Distribution des Tokens** :
  - Correction du bug où les utilisateurs avec allocation 0% recevaient l'allocation par défaut au rechargement
  - Distinction `null` (non configuré → allocation par défaut) vs `0` (explicitement zéro tokens)
  - Impact : `UserToken.monthlyAllocation` rendu nullable en base de données

### Amélioré

- **Page Distribution des Tokens** :
  - Nouveau design 2 colonnes (sidebar stats + liste utilisateurs)
  - Slider avec couleurs Bubul (gradient bleu #0A6BC4 → #3D9EEB)
  - Bouton "Tout remettre à 0" pour réinitialiser toutes les allocations
  - Header style admin avec icône et sous-titre
  - Suppression des avatars redondants dans la liste utilisateurs
  - Design responsive avec 4 breakpoints (1100px, 900px, 600px, 380px)

### Supprimé

- **Nettoyage du code** :
  - Suppression de `MyCompanyController.php` (non utilisé)
  - Suppression du répertoire `templates/my_company/` (5 fichiers)

### Technique

- Migration `Version20260208214207` : `monthly_allocation` nullable
- `UserTokenDistributionService` : logique `null` vs `0` pour allocations
- `UserController` : même correction pour l'affichage utilisateur

## [0.8.3] - 2026-02-07

### Ajouté

- **Fil d'Ariane (Breadcrumbs)** :
  - Nouveau composant réutilisable `_components/breadcrumb.html.twig`
  - Intégration sur 15+ pages admin : company, users, contracts, my_company
  - Support des icônes et navigation hiérarchique
  - Styles CSS harmonisés dans `admin-shared.css`

- **Navigation Améliorée pour le Manager** :
  - Sidebar enrichie avec sous-menus : Vue d'ensemble, Utilisateurs, Tokens
  - Accès direct aux fonctionnalités sans passer par le tableau de bord
  - Indicateurs visuels de la page active améliorés

- **Quick Actions Dashboard Manager** :
  - Nouvelle grille de 3 actions rapides (Utilisateurs, Tokens, Nouvel utilisateur)
  - Effet hover avec flèche de navigation
  - Design "card-link" cliquable sur toute la zone

### Amélioré

- **Cohérence des URLs** :
  - Breadcrumbs cohérents entre admin et my_company
  - Suppression des breadcrumbs inline dans les sous-titres
  - Navigation par fil d'Ariane sur toutes les pages de détail

### Technique

- `breadcrumb.html.twig` : nouveau composant avec support icônes
- `admin-shared.css` : ajout styles breadcrumb et quick-action-card
- 15 templates modifiés avec intégration breadcrumbs
- Navbar mise à jour pour ROLE_COMPANY_MANAGER

## [0.8.2] - 2026-02-07

### Refactorisation

- **Centralisation CSS Admin** :
  - Nouveau fichier `admin-shared.css` avec tous les composants réutilisables
  - Réduction de `admin-company.css` : 1514 → 514 lignes (66%)
  - Réduction de `admin-users.css` : 1871 → 818 lignes (56%)
  - Élimination de ~2000 lignes de code CSS dupliqué

- **Design Premium Dashboard** :
  - Animations fluides avec cubic-bezier (400ms)
  - Effet hover sur les stat-cards : translateY(-6px) + barre colorée
  - Animations sur les icônes : scale(1.08) + rotate(3deg)
  - Quick actions avec effets de profondeur et transitions

- **Standardisation des Classes** :
  - Nouveau container unifié `.admin-container`
  - Composants partagés : stat-cards, badges, buttons, tabs, modals
  - Templates migrés vers le CSS partagé (18 pages admin)

### Technique

- `admin-shared.css` : nouveau fichier (~1200 lignes)
- Templates migrés : company, users, contracts, my_company
- Respect du principe DRY pour la maintenance CSS

## [0.8.1] - 2026-02-07

### Ajouté

- **Distribution des Tokens par le Manager** :
  - Nouveaux champs `UserToken` : `monthlyAllocation`, `lastAllocationAt`
  - `UserTokenDistributionService` : allocation equitable, personnalisation par utilisateur
  - Routes `/my-company/tokens` et `/my-company/users/{uuid}/allocation`
  - Page de gestion avec sliders pour ajuster l'allocation par utilisateur
  - Bouton "Repartir equitablement" pour reset automatique
  - Plafond de stockage : 6 mois de tokens maximum par utilisateur

- **Page Globale des Contrats (Admin)** :
  - Nouvelle route `/admin/contracts` (`admin_contract_global_list`)
  - Vue de tous les contrats de toutes les entreprises
  - Filtres par entreprise et statut (actif, expire, a venir, desactive)
  - Statistiques globales : total, actifs, expirent bientot, expires

- **Navigation par Onglets (Page Entreprise)** :
  - Tabs : Vue d'ensemble, Contrats, Utilisateurs, Distribution tokens (manager)
  - Nouveau controleur Stimulus `tabs_controller.js`
  - Synchronisation avec l'URL pour navigation directe

- **Lien Contrats dans la Navbar Admin** :
  - Acces rapide a la liste globale des contrats
  - Icone contrat dans le menu Administration

### Modifié

- **Commande Cron `app:contract:renew-tokens`** :
  - Integre maintenant le renouvellement des tokens utilisateurs
  - Appel automatique de `UserTokenDistributionService::renewUserTokens()`
  - Affichage du nombre d'utilisateurs renouveles

### Technique

- `UserToken.php` : 2 nouveaux champs + methode `addTokens()` avec plafond
- `UserTokenDistributionService.php` : nouveau service
- `ContractRepository::findAllWithFilters()` : nouvelle methode
- `ContractController::globalList()` : nouvelle action
- CSS : `token-distribution.css`, extensions `admin-company.css`, `admin-contract.css`
- Migration `Version20260207234644` : ajout colonnes user_token

## [0.8.0] - 2026-02-07

### Ajouté

- **Système de Gestion des Contrats** : Gestion complète des contrats entreprise
  - Entité `Contract` : période (début/fin), limite utilisateurs, tokens mensuels, outils autorisés
  - Entité `Tool` : table de référence des outils (Moodia, Serenia, Slidia)
  - Entité `CompanyToken` : gestion des tokens au niveau entreprise (solde, consommation)
  - Entité `UserTool` : table pivot enrichie pour l'accès utilisateur aux outils
  - Cumul multi-contrats : les limites et tokens s'additionnent entre contrats actifs

- **Services métier** :
  - `ContractService` : calcul des limites cumulées, vérification d'accès
  - `CompanyTokenService` : renouvellement tokens, consommation, stockage max (×6 mois)
  - `ToolAccessService` : contrôle d'accès centralisé aux outils

- **Interface Admin Contrats** :
  - CRUD complet sous `/admin/company/{uuid}/contracts`
  - Cards colorées par statut : vert (actif), orange (expire bientôt), rouge (expiré), gris (désactivé)
  - Barres de progression : tokens utilisés, utilisateurs actifs
  - Formulaire avec checkboxes pour les outils autorisés

- **Contrôle d'accès outils** :
  - `ToolAccessSubscriber` : vérification automatique sur toutes les routes Moodia/Serenia/Slidia
  - Conditions : contrat autorise l'outil + entrée UserTool existe + tokens minimum suffisants
  - Toggles outils dans les formulaires utilisateur (admin et manager)

- **Commandes Cron** :
  - `app:contract:renew-tokens` : renouvellement mensuel des tokens (J+30 depuis début contrat)
  - `app:contract:check-expirations` : désactivation automatique entreprises sans contrat actif

- **Vue Manager** :
  - Section contrats dans la fiche entreprise (lecture seule)
  - Extension Twig `ContractExtension` : `get_contract_warnings()`, `get_company_token_balance()`

### Corrigé

- **Calcul énergie Wh** : Correction de l'incohérence entre "Activité récente" et cards Moodia
  - `UserRecentActivity` utilisait 0.0001 au lieu de `AppConfig::ENERGY_WH_PER_TOKEN` (0.0003)
  - `generator_controller.js` (Slidia) utilisait également 0.0001
  - Tous les calculs utilisent maintenant la constante centralisée

### Technique

- 4 nouvelles entités : `Tool`, `Contract`, `CompanyToken`, `UserTool`
- 4 nouveaux repositories
- 3 nouveaux services métier
- 1 EventSubscriber pour le contrôle d'accès
- 6 templates admin/contract
- CSS `admin-contract.css`
- DataFixtures `ToolFixtures` pour les outils de référence
- Modifications User.php et Company.php pour les relations

## [0.7.1] - 2026-02-07

### Ajouté

- **Gestion des Entreprises** : Nouveau CRUD administrateur complet
  - Entité `Company` avec champs : nom, SIRET, adresse, code postal, ville
  - Relation `User` ↔ `Company` (un utilisateur peut appartenir à une entreprise)
  - 5 templates Symfony standard : `list`, `add`, `edit`, `show`, `_form_fields`
  - Routes admin : liste, création, affichage, modification, suppression
  - Statistiques : nombre d'entreprises, utilisateurs rattachés, créées ce mois
  - Protection CSRF sur la suppression
  - Validation : SIRET unique (14 chiffres), code postal (5 chiffres)

- **Design moderne en Cards** : Nouveau design pour la liste des entreprises
  - Cards avec avatar généré automatiquement (initiales)
  - Boutons d'action en icônes avec tooltips au survol
  - Page de détails avec informations et liste des utilisateurs rattachés
  - Zone de danger pour la suppression

- **Autocomplétion d'adresses** : Intégration de l'API gouvernementale française
  - `api-adresse.data.gouv.fr` (gratuite, sans clé API)
  - Remplissage automatique du code postal et de la ville
  - Contrôleur Stimulus `address_autocomplete_controller.js`

- **Navbar Admin** : Réorganisation des sections
  - Section "Administration" : Entreprises
  - Section "Beta" : Feedbacks, Beta testers

- **Nouvelles icônes** : company, plus, users, location, calendar, eye, edit, trash, arrow-left, check

### Technique

- Nouveau formulaire `CompanyType` avec validation
- Nouveau repository `CompanyRepository` avec méthodes utilitaires
- Tests fonctionnels pour les routes admin/company (6 tests)
- CSS `admin-company.css` avec design tokens de `variables.css`
- Pattern BEM pour les classes CSS des cards

## [0.7.0] - 2026-02-06

### Supprimé

- **Moodle Auto-Deployment** : Suppression complète du système de déploiement automatique vers Moodle
  - 7 services supprimés : `APIMoodleService`, `MoodleAdminService`, `MoodleConfigurationChecker`, `MoodleHttpClient`, `MoodleImageUploaderService`, `ActivityService`, `UserService`
  - Entité `UserCourse` et son repository (ancien tracking des cours déployés)
  - Formulaire `MoodleType` (paramètres Moodle)
  - 12 templates : wizard step1-5, stateMoodle, moodia/index, test
  - 6 fichiers CSS : step1-5.css, stateMoodle.css
  - `TestController` (endpoints de debug admin)
  - Route `/moodia/deploy` et fonctionnalités associées
  - Plugin Moodle `bubul_activity` (dossier plugins/)

- **Note** : L'export MBZ (format Moodle) est conservé pour import manuel

### Modifié

- **Dashboard** : Simplification des statistiques (utilise uniquement MoodiaCourse)
- **Settings** : Onglet Moodle remplacé par Moodia avec message "coming soon"
- **Navbar** : Suppression du lien Test (admin)
- **Tests** : Mise à jour des tests Settings pour refléter les changements

### Ajouté

- **NumberFormatExtension** : Nouveau filtre Twig `|format_number` pour affichage compact
  - 1 500 → 1,5K
  - 1 500 000 → 1,5M
  - Utilisé pour l'affichage des tokens

### Technique

- Suppression de ~7 400 lignes de code inutilisé
- 40 fichiers modifiés/supprimés

## [0.6.8] - 2026-02-06

### Corrigé

- **Moodia (Images)** : Correction du placement des images dans les cours générés
  - Fix du parsing JSON : nettoyage des backticks markdown (`\`\`\`json ... \`\`\``) retournés par l'API
  - Nouveau prompt qui force le placement de TOUTES les images (pas seulement les "pertinentes")
  - Contexte ajouté : l'IA sait maintenant que les images viennent du même PDF que le cours
  - Seuil de confiance abaissé de 50% à 40% pour accepter plus de placements

### Modifié

- **Moodia** : Retour au modèle `gpt-4o-mini` dans `.env.local` (au lieu de `gpt-5-mini`)

## [0.6.7] - 2026-02-05

### Corrigé

- **Slidia** : Correction erreur 400 OpenAI
  - Remplacement de `max_tokens` par `max_completion_tokens` (paramètre requis par l'API actuelle)
  - Suppression du paramètre `temperature` non supporté par `gpt-4o-mini` via Chat Completions
  - Ajout d'une gestion d'erreur détaillée (`toArray(false)`) pour afficher les messages OpenAI
- **Serenia** : Remplacement de `max_tokens` par `max_completion_tokens`
- **TestController** : Route `/test/gpt52` migrée de Responses API (`gpt-5-mini`) vers Chat Completions API (`gpt-4o-mini`)

## [0.6.6] - 2026-02-05

### Modifié

- **Slidia** : Retour au modèle `gpt-4o-mini` (Chat Completions API) au lieu de `gpt-5-mini` (Responses API)
  - Changement de l'URL API : `/v1/responses` → `/v1/chat/completions`
  - Méthode `callOpenAI()` reçoit maintenant system et user prompts séparément (format `messages`)
  - Suppression de `buildFullPrompt()` et `extractTextFromResponse()` devenus inutiles
  - Suppression de la dépendance `OpenAIResponseParser` dans `SlidiaService`
- **OpenAIConfig** : `DEFAULT_MODEL` remis à `gpt-4o-mini` (plus aucun modèle gpt-5 en usage)

### Technique

- Mise à jour du test `SlidiaServiceTest` pour vérifier l'URL Chat Completions API

## [0.6.5] - 2026-02-05

### Modifié

- **Serenia** : Retour au modèle `gpt-4o-mini` (Chat Completions API) au lieu de `gpt-5-mini` (Responses API)
  - Changement de l'URL API : `/v1/responses` → `/v1/chat/completions`
  - Adaptation du format de requête (`messages`/`max_tokens` au lieu de `input`/`max_output_tokens`)
  - Adaptation du parsing de réponse (format `choices` au lieu de `output`)
  - Suppression de la dépendance `OpenAIResponseParser` dans `SerenIAService`

### Corrigé

- **Serenia (Images)** : Correction de l'affichage des images générées qui étaient tronquées
  - Suppression du style inline `max-width: 400px` qui bridait l'affichage
  - Les images utilisent désormais le CSS responsive (`.seren-ia__generated-image img`)

### Technique

- Mise à jour des tests unitaires `SerenIAServiceTest` pour le format Chat Completions API
- Renommage de `buildInputFromMessages()` → `buildMessagesFromInput()`

## [0.6.4] - 2026-02-03

### Ajouté

- **Tests unitaires** : +163 nouveaux tests (total 1989 tests, 3609 assertions)
  - `StreamingServiceTest.php` (17 tests) : Tests SSE streaming pour Serenia (error response, token usage, done message)
  - `MaskBasedGeneratorServiceTest.php` (51 tests) : Tests génération PowerPoint avec masques PPTX
  - `OpenAIHttpClientTest.php` (18 tests) : Tests client HTTP OpenAI centralisé (Chat Completions API, Responses API, DALL-E)
  - `BackupGeneratorTest.php` (27 tests) : Tests génération moodle_backup.xml pour exports MBZ

### Technique

- Tests complets pour `MaskBasedGeneratorService` (Slidia) :
  - `getMaskPath()`, `getUserMaskDir()`, `hasDefaultMask()`, `getMaxUploadSize()`
  - Private methods: `escapeXml()`, `parseLine()`, `createTextRun()`, `convertMarkdownToRuns()`
  - XML generation: `wrapInSlideXml()`, `generateSlideRelsXml()`, `convertContentToParagraphs()`
- Tests `OpenAIHttpClient` pour les deux formats d'API :
  - Chat Completions API (GPT-4) avec température et timeout configurables
  - Responses API (GPT-5+) avec max_output_tokens
  - Gestion des erreurs HTTP (401, 403, 500) et erreurs API
- Tests `BackupGenerator` pour validation structure XML :
  - Sections, activités, settings, paramètres Moodle version
  - Gestion des caractères spéciaux et validation XML bien formé

## [0.6.3] - 2026-02-03

### Ajouté

- **Tests unitaires** : +147 nouveaux tests (total 1826 tests, 3245 assertions)
  - `PdfStreamingParserTest.php` (31 tests) : Tests extraction images PDF avec contexte
  - `CourseGenerationServiceTest.php` (58 tests) : Tests génération cours (plan, prompts, JSON, images)
  - `MoodiaStreamingServiceTest.php` (25 tests) : Tests streaming SSE et génération crédits
  - `MoodleAdminServiceTest.php` (19 tests) : Tests création/suppression utilisateurs beta Moodle
  - `MoodleImageUploaderServiceTest.php` (14 tests) : Tests upload images vers Moodle

### Technique

- Tests complets pour les private methods de `CourseGenerationService` :
  - `getTokens()`, `extractJsonFromResponse()`, `extractJsonFromContent()`
  - `formatPdfContentWithPages()`, `getPdfPagesInstruction()`
  - `buildImageHtml()`, `insertAfterParagraph()`, `extractParagraphs()`
  - `getLevelSpecificInstructions()`, `getAdaptationInstructions()`
- Tests pour `MoodiaStreamingService` :
  - `convertMarkdownToHtml()`, `selectAudioPages()`, `getActivityType()`, `generateCredits()`
- Couverture améliorée des services Moodle admin et upload d'images

## [0.6.2] - 2026-02-03

### Ajouté

- **Tests unitaires** : +87 nouveaux tests (total 1679 tests, 2981 assertions)
  - `ImageAnalyzerServiceTest.php` (21 tests) : Tests analyse images via contexte PDF
  - `TextToSpeechServiceTest.php` (37 tests) : Tests génération audio ElevenLabs
  - `UserServiceTest.php` (27 tests) : Tests recherche et enrôlement utilisateurs Moodle
  - `PdfImageExtractorServiceTest.php` (17 tests) : Tests extraction images PDF avec GD
  - `APIMoodleServiceTest.php` (29 tests) : Tests déploiement cours vers Moodle
  - `MbzExporterTest.php` (33 tests) : Tests génération XML pour export MBZ
  - `OpenAIServiceTest.php` (28 tests) : Tests génération de structure de cours et prompts

### Technique

- Utilisation de `ReflectionClass::newInstanceWithoutConstructor()` pour tester des services avec dépendances `final` (PdfStreamingParser, ArchiveBuilder, TextCleanerService)
- Tests des méthodes privées via réflexion pour valider la logique métier critique

## [0.6.1] - 2026-02-03

### Ajouté

- **Tests unitaires** : +49 nouveaux tests (total 1454 tests, 2542 assertions)
  - `MoodleHttpClientTest.php` (31 tests) : Tests communication API Moodle
  - `XmlBuilderTest.php` (35 tests) : Tests construction XML pour export MBZ
  - `PageGeneratorTest.php` (32 tests) : Tests génération pages vers MBZ
  - `QuizGeneratorTest.php` (26 tests) : Tests génération quiz vers MBZ
  - `ActivityGeneratorRegistryTest.php` (11 tests) : Tests pattern Strategy pour générateurs
  - `ExportExceptionTest.php` (12 tests) : Tests exceptions standardisées export

### Corrigé

- **ActivityServiceTest** : Correction erreur TypeError (User entity au lieu de stdClass)

## [0.6.0] - 2026-02-02

### Corrigé

- **Moodia - Génération objectifs pédagogiques** : Variable `$minTokensRequired` non définie
  - Bug : L'appel à `generateObjectives()` échouait car la variable n'existait pas
  - Fix : Utilisation de `AppLimits::SERENIA_MIN_TOKENS` directement

### Amélioré

- **Centralisation chemins upload Moodia** : Tous les chemins dans `MoodiaConfig.php`
  - Nouvelles constantes : `UPLOAD_BASE_DIR`, `COURSES_DIR`, `AUDIO_SUBDIR`, `IMAGES_SUBDIR`, `EDITOR_IMAGES_DIR`
  - Nouvelles méthodes utilitaires : `getCourseUrl()`, `getAudioUrl()`, `getImagesUrl()`, `getCoursePath()`, `getAudioPath()`, `getImagesPath()`
  - Services mis à jour : `TextToSpeechService`, `PdfImageExtractorService`, `ImageUploadController`

- **Refactoring MoodiaStreamingService** : Début extraction de méthodes
  - `resetGenerationState()` : Réinitialisation des états de génération
  - `extractPdfContentAndImages()` : Extraction contenu et images PDF
  - `generateAndSendCoursePlan()` : Génération et envoi du plan de cours

- **Nettoyage Config Files** : Suppression massive de code mort dans les fichiers de configuration
  - `MoodiaConfig.php` : Réduit de 184 à 37 lignes (conserve uniquement la config placement d'images)
  - `ElevenLabsConfig.php` : Suppression OUTPUT_FORMAT, WORDS_PER_MINUTE, CHARS_PER_WORD, isVoiceAvailable(), estimateAudioDuration()
  - `StreamingConfig.php` : Suppression SSE_HEADERS, CONTENT_TYPE, PHP_INI_SETTINGS, DEFAULT_TIMEOUT, POLL_DELAY_US et méthodes utilitaires
  - `PdfConfig.php` : Suppression GC_FREQUENCY_PAGES, RECOMMENDED_MIN_SIZE, listes d'extensions/MIME, méthodes utilitaires
  - `SlidiaConfig.php` : Suppression OUTPUT_DIR, MAX_SLIDES, MAX_INDENT_LEVEL, NS_DRAWING, NS_CONTENT_TYPES, LAYOUTS et méthodes
  - `PedagogicalConfig.php` : Suppression getMaxSections() (deprecated), getAvailableContentTypes(), getAvailableElements()
  - `OpenAIConfig.php` : Suppression DALLE_API_URL
  - `AppConfig.php` : Suppression APP_VERSION
  - Tests unitaires mis à jour en conséquence

## [0.5.54] - 2026-02-02

### Amélioré

- **Refactoring Config** : Suppression des alias de constantes locales
  - Les services utilisent maintenant `Config::CONSTANT` directement (pas de `self::ALIAS`)
  - 7 fichiers nettoyés : ~50 constantes locales inutiles supprimées
  - Plus simple, plus lisible, IDE-friendly (Ctrl+Click va à la définition)
  - Fichiers modifiés : `StreamingService`, `StreamingHelper`, `TimeSavingsCalculator`, `ImagePlacementService`, `PdfStreamingParser`, `TextToSpeechService`, `MaskBasedGeneratorService`

### Corrigé

- **Serenia - Lecture des PDF** : Les fichiers étaient effacés avant l'envoi
  - Bug : `clearFiles()` appelé AVANT `streamChatResponse()`
  - Les chemins des fichiers étaient donc vides quand envoyés à l'API
  - Fix : Capturer `filePaths` avant le clear et les passer en paramètre

- **Serenia - Format Responses API** : Passage au format tableau de messages
  - L'input est maintenant un tableau `[{role, content}, ...]` au lieu d'une string
  - Meilleure compatibilité avec gpt-5-mini pour les contenus longs (PDF)

- **Serenia - Affichage tokens/énergie** : Correction du parsing SSE
  - Le buffer restant n'était pas traité quand le stream se terminait
  - L'événement `done` restait dans le buffer et n'était jamais parsé
  - Refactoring : extraction de `processLine()` + traitement du buffer final

- **Serenia - Mémoire des pièces jointes PDF** : L'IA se souvient maintenant du contenu des fichiers
  - Le frontend synchronise l'historique avec le contenu enrichi des messages
  - Nouvel événement SSE `enrichedMessage` envoyé par le backend
  - Les messages suivants ont accès au contenu extrait des PDF/documents
  - Fichiers modifiés : `StreamingService.php`, `SereniaController.php`, `chat_controller.js`

- **Moodia - Objectifs pédagogiques tronqués** : Correction de la génération incomplète
  - Augmentation de `max_output_tokens` de 500 à 1000 pour les objectifs
  - gpt-5-mini utilise ~300 tokens de raisonnement avant la réponse
  - Les objectifs sont maintenant générés en entier sans coupure

- **OpenAI Parser - Type correct** : Le type est bien `output_text` (pas `text`)
  - Investigation avec l'API réelle a confirmé la structure correcte
  - Tests unitaires mis à jour avec les fixtures réalistes

## [0.5.53] - 2026-02-02

### Corrigé

- **Bug critique - Parsing API OpenAI** : Correction du parsing des réponses de l'API Responses (GPT-5+)
  - Centralisation du parsing via `OpenAIResponseParser`
  - Affectait : génération d'objectifs Moodia, chat Serenia, génération Slidia
  - Résolu dans 3 services : `OpenAIService`, `SerenIAService`, `SlidiaService`

### Ajouté

- **OpenAIResponseParser** : Nouveau parser centralisé pour les réponses OpenAI
  - Support des deux formats : Chat Completions API (GPT-4) et Responses API (GPT-5+)
  - Élimine la duplication de code de parsing dans 3 services
  - Méthodes : `extractContent()`, `extractFromChatCompletions()`, `extractFromResponsesApi()`, `extractUsage()`

- **Tests unitaires massifs** : 89 nouveaux tests ajoutés
  - `OpenAIResponseParserTest.php` : 20 tests pour le parsing des réponses
  - `StreamingHelperTest.php` : 13 tests pour les helpers SSE
  - `OpenAIConfigTest.php` : 24 tests pour la configuration OpenAI
  - `ElevenLabsConfigTest.php` : 14 tests pour la configuration ElevenLabs
  - `PedagogicalConfigTest.php` : 8 tests pour les constantes pédagogiques
  - `StreamingConfigTest.php` : 10 tests pour la configuration streaming

### Amélioré

- **Refactoring DRY** : Code de parsing centralisé
  - Avant : logique dupliquée dans 3 fichiers (~90 lignes)
  - Après : 1 seul fichier source avec tests complets

### Technique

- Total des tests : 276 tests, 609 assertions (contre 187 tests initialement)
- Couverture : Parsing API, headers SSE, constantes de configuration

## [0.5.52] - 2026-02-02

### Supprimé

- **Moodia - Filtre par statut** : Suppression des filtres "Tous/Brouillons/Déployés"
  - Le dashboard n'affiche plus que les filtres par type (Cours, Cours+Quiz, Quiz)
  - Simplifie l'interface et réduit la complexité
  - ~55 lignes de code supprimées (template + contrôleur)

## [0.5.51] - 2026-02-02

### Ajouté

- **Moodia - Suppression fichiers audio** : Nettoyage automatique du serveur
  - Nouveau endpoint `POST /moodia/delete-audio` pour supprimer les fichiers
  - `TextToSpeechService::deleteAudioFile()` avec vérifications de sécurité
  - Suppression du fichier serveur quand une activité audio est supprimée
  - L'`audioUrl` est réinitialisé en base de données

### Corrigé

- **Moodia - Affichage lecteur audio** : Correction de 3 endroits où `audioUrl` n'était pas passé
  - `renderPageActivity()` passe maintenant `audioUrl`
  - `renderMainPageActivity()` passe maintenant `audioUrl`
  - `changeActivityType()` conserve `audioUrl` lors du changement de type
  - Le lecteur s'affiche correctement après rechargement de la page

### Amélioré

- **Moodia - Stockage audio** : Structure par cours
  - Les audios sont stockés dans `/uploads/moodia/courses/{uuid}/audio/`
  - Facilite la recherche et le nettoyage par cours
  - Meilleure organisation des fichiers

## [0.5.50] - 2026-02-02

### Corrigé

- **Moodia - Persistance audio** : L'audio est maintenant sauvegardé automatiquement
  - L'`audioUrl` est persisté en base dès la génération (plus besoin de sauvegarder manuellement)
  - Le frontend envoie `courseUuid`, `sectionId`, `activityId` au backend
  - Le backend trouve l'activité et met à jour `audioUrl` + `audioVoice`
  - Au rechargement de la page, le lecteur audio s'affiche correctement

## [0.5.49] - 2026-02-02

### Amélioré

- **Moodia - Lecteur audio** : Affichage automatique si audio déjà généré
  - Au chargement du cours, les activités avec audio affichent directement le lecteur
  - Le bouton devient "Regénérer l'audio" au lieu de "Générer l'audio"
  - Plus besoin de regénérer pour voir/écouter un audio existant

## [0.5.48] - 2026-02-02

### Amélioré

- **Moodia - Feedback audio** : Notification de succès après génération
  - Affiche un toast "Audio généré" avec le nombre de tokens consommés
  - Cohérent avec les autres notifications de l'application

## [0.5.47] - 2026-02-02

### Modifié

- **Moodia - Modèle IA** : Passage de gpt-4o à gpt-4o-mini
  - Réduction des coûts de 16× pour la génération de cours
  - Qualité similaire pour les tâches de structuration pédagogique

- **Moodia - Voix TTS** : Changement de Roger vers Antoine
  - Utilisation du modèle eleven_v3 (plus expressif)
  - Nouvelle voix Antoine avec ID `FvmvwvObRqIHojkEGh5N`

- **Moodia - Tarification audio** : Multiplicateur ElevenLabs ×2 → ×8
  - Marge de ×7 sur l'offre Starter, ×19 sur Pro
  - Basé sur le coût réel ElevenLabs (22€/100k caractères)

### Corrigé

- **Moodia - Comptabilisation audio** : Les tokens sont maintenant correctement comptés
  - Génération audio dans l'éditeur : mise à jour de `course.tokens_used` + énergie
  - Export MBZ avec génération audio : idem, accumulation des tokens et mise à jour du cours
  - Avant ce fix, les audios consommaient des tokens utilisateur mais pas du cours

## [0.5.46] - 2026-02-01

### Supprimé

- **Moodia - Activité SCORM** : Suppression complète de ce type d'activité
  - Supprimé de l'Entity, des Services, du JavaScript, du CSS et de la config
  - Nettoyage de ~900 lignes de code inutilisées

### Amélioré

- **Moodia - Notifications** : Remplacement de tous les `alert()` par `window.showNotification()`
  - Messages d'erreur plus élégants et cohérents avec le design de l'application
  - Meilleure gestion des erreurs SSE (évite les notifications dupliquées)

- **Moodia - Export MBZ** : Meilleurs messages d'erreur
  - Affiche maintenant le fichier et la ligne de l'erreur pour faciliter le debug
  - Logging amélioré côté serveur

- **Moodia - Design cohérent** : Section "Généralités" uniformisée avec les crédits
  - Même gradient de fond (`linear-gradient(135deg, #F8FAFC...)`)
  - Même texte ("Cours généré avec Bubul.app")
  - Design harmonieux entre le début et la fin du cours

### Corrigé

- **Moodia - Export MBZ** : Correction du border-left sur les blockquotes
  - Ajout de `border: none;` pour éviter le style par défaut de Moodle
  - Les blockquotes gardent maintenant leur design personnalisé après import

## [0.5.45] - 2026-02-01

### Amélioré

- **Serenia - Conversation naturelle** : L'IA est maintenant plus conversationnelle
  - Retient le prénom de l'utilisateur et l'utilise naturellement
  - Adapte son ton (empathique si triste, enthousiaste si content)
  - Se souvient du contexte de la conversation

- **Serenia - Format de réponse** : Meilleure lisibilité
  - Utilise le **gras** pour les mots-clés importants
  - Utilise l'*italique* pour les nuances et termes techniques
  - Listes à puces pour les énumérations (recettes, étapes, etc.)

### Corrigé

- **Serenia - Génération d'image intelligente** : Plus de regénération accidentelle
  - Le tag `[IMAGE_GENERATE]` n'est plus utilisé dans les résumés de conversation
  - L'IA distingue une nouvelle demande d'image d'une référence à une image passée
  - Le contenu des fichiers uploadés est exclu du prompt d'image

- **Serenia - Coût image réduit** : Économie de tokens
  - Coût réduit de 20 000 à ~1 000 tokens par image
  - Utilise `estimateImageTokens()` pour un calcul dynamique selon la taille

## [0.5.44] - 2026-02-01

### Corrigé

- **Serenia - Streaming chat** : Correction d'une race condition dans l'animation typewriter
  - Le texte s'affiche maintenant correctement grâce au flag `streamComplete`
  - Plus de messages vides ou de blocages lors du streaming

### Amélioré

- **Serenia - Génération d'images** : Meilleure expérience utilisateur
  - Affichage immédiat de "🎨 Génération de votre photo en cours..." au lieu du tag `[IMAGE_GENERATE]`
  - Le prompt système force l'IA à répondre uniquement avec le tag, sans blabla
  - Tokens fixes à 20 000 par génération d'image

## [0.5.43] - 2026-02-01

### Amélioré

- **Serenia - Animation fluide** : Le streaming du texte est maintenant beaucoup plus fluide
  - Throttling à 50ms entre chaque rendu (20 FPS max)
  - Utilisation de `requestAnimationFrame` pour synchroniser avec le navigateur
  - Plus de saccades lors de l'affichage du texte

### Modifié

- **Serenia - Prompt strict** : L'IA reste maintenant focalisée sur la demande
  - Demande vague → pose 2-3 questions courtes et attend la réponse
  - Plus de "en attendant voici..." ou de contenu non sollicité
  - Réponses concises et directes

## [0.5.42] - 2026-01-31

### Modifié

- **Serenia - Format Markdown pur** : L'IA génère maintenant du Markdown standard uniquement
  - Prompt système simplifié et explicite : "INTERDIT : Toute balise HTML"
  - Syntaxe Markdown autorisée : titres (##), gras (**), listes (-), code, citations
  - Le frontend convertit le Markdown en HTML via `MarkdownRenderer`
  - Résultat plus propre et cohérent

- **Serenia - Prompt système** : Réduction de 50 lignes à 25 lignes
  - Instructions plus claires et directes
  - Suppression des instructions contradictoires

### Technique

- **SerenIAService.php** : Nouveau `PEDAGOGICAL_SYSTEM_PROMPT` minimaliste
- Le renderer Markdown existant (`markdown_renderer.js`) gère la conversion

## [0.5.41] - 2026-01-31

### Corrigé

- **Serenia - Encodage UTF-8** : Correction de l'erreur "Malformed UTF-8 characters" lors du traitement des PDF
  - Nouvelle méthode `cleanPdfText()` dans TextCleanerService avec nettoyage agressif
  - Détection automatique de l'encodage source (ISO-8859-1, Windows-1252, etc.)
  - Utilisation de `iconv` avec `//IGNORE` pour supprimer les caractères invalides
  - Suppression des caractères de substitution Unicode (�)
  - Vérification finale avec `mb_check_encoding()`

### Amélioré

- **Serenia - Vrai streaming** : Le texte s'affiche maintenant en temps réel (effet machine à écrire)
  - Démarrage instantané : le texte commence à s'afficher dès qu'OpenAI génère
  - Nouvelle méthode `chatCompletionStream()` avec `stream: true`
  - Plus besoin d'attendre la réponse complète avant d'afficher

- **Serenia - Streaming** : Double sécurité UTF-8 avant l'encodage JSON
  - Nettoyage de la réponse OpenAI avant le streaming SSE
  - Nettoyage des messages d'erreur pour éviter les problèmes d'encodage

### Technique

- **TextCleanerService** : Nouvelle méthode `cleanPdfText()` pour les fichiers PDF avec encodages exotiques
- **FileHandlerService** : Utilise `cleanPdfText()` au lieu de `cleanAndNormalizeText()` pour les PDF
- **SerenIAService** : Nouvelle méthode `chatCompletionStream()` utilisant le streaming OpenAI natif
- **StreamingService** : Utilise le vrai streaming au lieu d'attendre la réponse complète

## [0.5.40] - 2026-01-31

### Modifié

- **Voix ElevenLabs** : Changement de la voix par défaut
  - `rachel` → `domi` (voix féminine dynamique avec moins d'accent)
  - Mise à jour dans `TextToSpeechService.php` et `MoodleDefaults.php`

### Nettoyé

- Suppression de la dernière référence à OpenAI TTS (`nova`) dans `MoodleDefaults.php`

## [0.5.39] - 2026-01-31

### Corrigé

- **Serenia - Génération d'images** : Suppression du texte inutile pendant la génération
  - L'IA ne pose plus de questions avant de générer l'image
  - Affichage direct de "Génération en cours..." puis l'image
  - Prompt système renforcé avec règles strictes

## [0.5.38] - 2026-01-31

### Corrigé

- **Serenia - Génération d'images** : Correction de l'affichage des images générées
  - Les images base64 s'affichent maintenant correctement dans le chat
  - Bouton de téléchargement fonctionnel

### Modifié

- **Tokens image generation** : Estimation réaliste des tokens (1000 au lieu de 10000)
  - Nouvelle constante `IMAGE_GENERATION_TOKENS = 1000`
  - Utilisation de `estimateImageTokens()` basée sur la taille de l'image

## [0.5.37] - 2026-01-31

### Modifié

- **Migration génération d'images vers gpt-image-1.5** : Adaptation complète de l'API
  - Le modèle `gpt-image-1.5` retourne toujours du base64 (pas d'URL)
  - Suppression des paramètres non supportés (`quality`, `response_format`, `n`)
  - Serenia affiche maintenant les images directement en base64

### Technique

- **SerenIAService.php** :
  - `generateImage()` retourne maintenant `['base64' => ..., 'prompt' => ...]`
  - Timeout augmenté à 120s pour la génération d'images
  - Meilleure gestion des erreurs API

- **SereniaController.php** :
  - Suppression du paramètre `quality` non supporté
  - Réponse adaptée pour le format base64

- **image_generator.js** :
  - Affichage direct des images base64 (`data:image/png;base64,...`)
  - Téléchargement local via conversion blob (plus besoin du proxy backend)
  - Nouveau bouton de téléchargement stylisé

- **OpenAIConfig.php** :
  - Mise à jour des tailles disponibles pour gpt-image-1.5
  - Constantes `quality` marquées comme deprecated

- **TestController.php** :
  - Outil de test pour la génération d'images ajouté

## [0.5.36] - 2026-01-31

### Modifié

- **Migration vers l'API Responses** : Tous les services utilisent maintenant l'endpoint `/v1/responses` requis par GPT-5
  - Moodia (OpenAIService, CourseGenerationService) : Génération de cours
  - Serenia (SerenIAService) : Assistant pédagogique
  - Slidia (SlidiaService) : Génération de présentations

### Technique

- **Format de requête Responses API** :
  - Endpoint : `https://api.openai.com/v1/responses` au lieu de `/v1/chat/completions`
  - Paramètre `input` (string) au lieu de `messages` (array)
  - Paramètre `max_output_tokens` au lieu de `max_tokens`

- **Parsing de réponse Responses API** :
  - Extraction du texte via `result['output'][]['content'][]['text']` pour type `output_text`
  - Fallback sur `result['output_text']` si structure différente
  - Conversion en format compatible avec l'ancien format Chat Completions pour maintenir la compatibilité

- **OpenAIConfig.php** : Nouvelle constante `RESPONSES_API_URL` et méthodes helper
  - `requiresResponsesApi(string $model)` : Détecte si un modèle nécessite l'API Responses
  - `getApiUrlForModel(string $model)` : Retourne l'URL appropriée selon le modèle

- **Services mis à jour** :
  - `OpenAIService.php` : Méthode `extractTextFromResponsesApi()` ajoutée
  - `CourseGenerationService.php` : Toutes les requêtes parallèles et async migrées
  - `SerenIAService.php` : `chatCompletion()` et `buildInputFromMessages()` adaptés
  - `SlidiaService.php` : `formatAndStructure()` migré vers Responses API

## [0.5.35] - 2026-01-30

### Modifié

- **Migration vers GPT-5 mini** : Tous les produits utilisent désormais le modèle `gpt-5-mini`
  - Moodia : Génération de cours
  - Serenia : Assistant pédagogique
  - Slidia : Génération de présentations

- **Migration vers gpt-image-1.5** : Nouveau modèle pour la génération d'images
  - Remplacement de `dall-e-3` par `gpt-image-1.5`
  - Utilisé par Serenia et les autres outils de génération d'images

- **Migration vers ElevenLabs pour l'audio** : Remplacement complet d'OpenAI TTS
  - Nouveau service `TextToSpeechService` utilisant l'API ElevenLabs
  - Modèle `eleven_multilingual_v2` pour une meilleure qualité en français
  - Voix par défaut : `rachel` (voix féminine claire)
  - Voix disponibles : adam, rachel, josh, bella, arnold, domi
  - Nouvelle méthode `checkQuota()` pour vérifier le quota ElevenLabs

### Technique

- **OpenAIConfig.php** : Mise à jour des constantes de modèles
  - `DEFAULT_MODEL`, `MOODIA_MODEL`, `SERENIA_MODEL`, `SLIDIA_MODEL` → `gpt-5-mini`
  - `IMAGE_MODEL` → `gpt-image-1.5`
  - Ajout de `gpt-5-mini` dans les tableaux de limites

- **TextToSpeechService.php** : Réécriture complète pour ElevenLabs
  - URL API : `https://api.elevenlabs.io/v1`
  - Header d'authentification : `xi-api-key`
  - Configuration vocale : stability, similarity_boost, style, speaker_boost

- **Configuration** :
  - Nouvelle variable d'environnement `ELEVENLABS_API_KEY`
  - Mise à jour de `services.yaml` pour injecter la clé ElevenLabs

## [0.5.34] - 2026-01-30

### Supprimé

- **Nettoyage système SCORM** : Suppression complète de l'ancien système SCORM en préparation du nouveau
  - Suppression des fichiers SCORM obsolètes :
    - `ScormPackageBuilder.php`, `ScormHtmlGenerator.php`, `ScormManifestGenerator.php`
    - `ScormPackage.php`, `ScormContent.php`, `ScormSco.php`
    - `ScormConfig.php`, `ScormGenerator.php`
  - Suppression des constantes SCORM dans `MoodleDefaults.php`
  - Suppression des méthodes SCORM dans `MoodiaStreamingService` et `CourseGenerationService`
  - Nettoyage du CSS SCORM (~700 lignes supprimées dans `editor.css`)
  - Suppression des templates SCORM dans `constants.js`

### Modifié

- **CourseTransformer** : Les activités SCORM sont temporairement ignorées à l'export
- **MoodiaStreamingService** : Suppression de la génération automatique d'activités SCORM
- **Tokens beta testeurs** : Passage de 50 000 à 500 000 tokens pour les nouveaux utilisateurs

### Technique

- Préparation pour le nouveau système SCORM basé sur des entités (MoodiaScormModule, MoodiaScormSlide)
- Le type d'activité `scorm` reste disponible dans MoodiaActivity pour la rétrocompatibilité

## [0.5.33] - 2026-01-27

### Corrigé

- **SCORM Player robuste** : Correction de l'affichage vide dans Moodle
  - Mode autonome automatique quand l'API SCORM n'est pas détectée
  - Logging console pour faciliter le débogage
  - Gestion d'erreurs try/catch dans tous les scripts de template
  - Initialisation via DOMContentLoaded pour éviter les erreurs de timing

### Amélioré

- **ScormHtmlGenerator** : API SCORM 1.2 entièrement réécrite
  - Pattern IIFE pour encapsulation des variables
  - Fallback gracieux si API LMS non disponible
  - Messages de statut plus informatifs (Mode autonome, Connecté au LMS)
  - Compatibilité améliorée avec les différents navigateurs

- **Scripts de templates SCORM** : Tous les templates améliorés
  - Accordion : Gestion d'erreur et vérification de fonction
  - Slides : Encapsulation IIFE et initialisation différée
  - Cards : Gestion d'erreur au retournement
  - Tabs : Gestion d'erreur au changement d'onglet
  - Timeline : Fallback pour navigateurs sans IntersectionObserver
  - Reveal : Gestion d'erreur à l'affichage

### Technique

- **ScormHtmlGenerator.php** :
  - `getScormApi()` : Refonte complète avec mode autonome
  - Tous les `getTemplateScript()` : Ajout try/catch et vérification des fonctions

## [0.5.32] - 2026-01-26

### Amélioré

- **Génération SCORM IA** : Qualité et quantité améliorées
  - Tokens augmentés de 2000 à 3000+ (500 par section)
  - Exemples JSON spécifiques par type de template dans le prompt
  - Validation et complétion automatique si l'IA génère moins de sections
  - Meilleur découpage du contenu source (4000 caractères au lieu de 3000)

- **Fallback SCORM intelligent** : Contenu significatif en cas d'erreur
  - Découpage du contenu source en portions égales
  - Labels de sections variés (Introduction, Concepts clés, Applications...)
  - Évite les placeholders vides ("Contenu à compléter...")

- **Prompts IA enrichis** :
  - Instructions plus précises sur le nombre exact de sections
  - Demande de diversification des angles d'approche
  - Minimum de 50 mots par section exigé

### Technique

- **CourseGenerationService** :
  - `getScormExamples()` : Exemples JSON par template
  - `createSingleDefaultSection()` : Création de section unique avec contenu
  - Calcul dynamique des tokens : `max(3000, itemCount * 500)`

- **MoodiaStreamingService** :
  - `createFallbackScormSections()` refactoré avec découpage intelligent

## [0.5.31] - 2026-01-26

### Ajouté

- **Génération SCORM avec IA** : Contenu interactif généré par l'IA
  - Prompt spécifique pour chaque type de template (flashcards, onglets, etc.)
  - Génération de 4-8 éléments selon le template
  - Contenu pédagogique adapté au format (questions/réponses pour flashcards, etc.)

- **Sélection aléatoire du template SCORM**
  - Pondération favorisant les templates les plus pédagogiques
  - Flashcards (25%) et Onglets (25%) privilégiés
  - Diversification automatique des activités dans le cours

- **Configuration SCORM enrichie** (`ScormConfig.php`)
  - `getRandomTemplate()` : Sélection pondérée aléatoire
  - `getOptimalItemCount()` : Nombre d'items optimal par template
  - `getTemplatePromptInstructions()` : Instructions IA par template

### Amélioré

- **CourseGenerationService** : Nouvelle méthode `generateScormContent()`
  - Appel IA dédié pour transformer le contenu en SCORM interactif
  - Fallback automatique en cas d'erreur IA
  - Logging détaillé de la génération

- **MoodiaStreamingService** : Refonte de `prepareScormActivityData()`
  - Utilise maintenant la génération IA
  - Template aléatoire au lieu de déterministe
  - Création de sections de secours si nécessaire

### Technique

- **Backend** :
  - `ScormConfig.php` : +3 nouvelles méthodes statiques
  - `CourseGenerationService.php` : +2 méthodes (generateScormContent, createDefaultScormSections)
  - `MoodiaStreamingService.php` : Refonte de prepareScormActivityData, suppression de chooseScormTemplate

## [0.5.30] - 2026-01-26

### Corrigé

- **Export MBZ SCORM** : Correction du contenu vide dans les activités SCORM
  - Nouvelle méthode `collectScormContent()` pour collecter correctement les sections
  - Le contenu JSON est maintenant préservé lors de la sauvegarde
  - Support de tous les templates (cards, tabs, timeline, reveal, accordion, slides)

### Amélioré

- **Éditeur SCORM UX/UI** :
  - 🃏 **Flashcards** : Design complètement revu
    - Numéro de carte visible avec badge
    - Dégradés de couleurs pour recto (bleu) et verso (vert)
    - Labels avec indicateurs visuels
    - Ombres et transitions au survol
  - 📑 **Onglets** : Design amélioré
    - Icône d'onglet avec fond coloré
    - Champ de saisie du label plus grand
    - Bordure verte épaisse sous l'en-tête
    - Placeholder visible pour le contenu
  - ➕ **Bouton "Ajouter"** : Plus grand et visible
    - Pleine largeur avec fond dégradé vert
    - Texte dynamique selon le template (ex: "Ajouter une carte")
    - Animation au survol avec ombre

### Supprimé

- **Question de validation SCORM** : Fonctionnalité retirée temporairement
  - Suppression du rendu HTML
  - Suppression des méthodes de gestion (toggle, update)
  - Suppression des styles CSS associés

### Technique

- **Frontend** :
  - `editor_controller.js` :
    - `collectScormContent()` : Collecte le contenu SCORM depuis les éditeurs
    - `getAddSectionButtonText()` : Texte du bouton selon le template
    - Modification de `collectCourseData()` pour gérer les activités SCORM
  - `editor.css` :
    - Refonte de `.scorm-section-editor--cards`
    - Refonte de `.scorm-section-editor--tabs`
    - Style amélioré de `.scorm-add-section-btn`
    - Suppression des styles `.scorm-validation-*`

## [0.5.29] - 2026-01-26

### Amélioré

- **Moodia SCORM** : Éditeurs spécifiques par type de template
  - 🃏 **Cards/Flashcards** : Éditeur recto/verso
    - Recto : question ou terme à mémoriser
    - Verso : réponse ou définition
    - Design bicolore (bleu/vert) pour différencier les faces
  - ⏱️ **Timeline** : Éditeur chronologique
    - Marqueur visuel avec numéro d'étape
    - Champ date/étape dédié
    - Ligne de connexion entre les étapes
  - 📑 **Tabs** : Éditeur d'onglets
    - Icône et libellé d'onglet personnalisables
    - Bordure verte pour l'onglet actif
  - 👁️ **Reveal** : Éditeur par étapes
    - Badge d'étape numéroté
    - Bordure latérale verte

### Corrigé

- **Génération SCORM** : Affichage correct des activités SCORM pendant la génération
  - Type 'scorm' maintenant reconnu dans handleNewPage
  - Conversion automatique HTML → JSON SCORM
  - Badge SCORM visible dans le TOC (table des matières)
  - Re-rendu automatique quand le contenu streaming est terminé

### Technique

- **Frontend** :
  - `editor_controller.js` : Nouvelles méthodes
    - `renderScormSectionByTemplate()` : Rendu adapté au template
    - `createScormSectionForTemplate()` : Structure de section par template
    - `convertHtmlToScormContent()` : Conversion HTML vers JSON SCORM
    - `extractSectionsFromHtml()` : Extraction des sections depuis HTML
    - `chooseScormTemplateByContent()` : Choix du template selon le contenu
    - `updateScormSectionField()` : Mise à jour de champs spécifiques (date, etc.)
  - `editor.css` : Styles par template
    - `.scorm-section-editor--cards` : Éditeur flashcards
    - `.scorm-section-editor--timeline` : Éditeur timeline
    - `.scorm-section-editor--tabs` : Éditeur onglets
    - `.scorm-section-editor--reveal` : Éditeur révélation
    - `.toc-item-badge--scorm` : Badge SCORM dans TOC

---

## [0.5.28] - 2026-01-26

### Amélioré

- **Moodia SCORM** : Amélioration de l'expérience utilisateur
  - 🎨 **Design épuré** : Couleurs moins flashy, style plus sobre et professionnel
    - Badge SCORM avec fond vert clair (#ECFDF5) et texte vert foncé (#047857)
    - Suppression des dégradés au profit de couleurs unies
    - En-têtes de section neutres (#FAFAFA) au lieu de vert
  - 🖼️ **Modal de sélection de template** : Interface dédiée pour choisir le template
    - Aperçu visuel de chaque template avec icône et description
    - Sélection intuitive en grille 2x3
  - ✅ **Question de validation** : Vérification de compréhension optionnelle
    - Activation/désactivation via checkbox
    - Question personnalisable
    - 4 réponses possibles avec sélection de la bonne réponse
    - Permet de valider l'achèvement du module SCORM

### Technique

- **Frontend** :
  - `editor_controller.js` : Nouvelles méthodes
    - `showScormTemplateModal()` : Affichage de la modal de sélection
    - `selectScormTemplate()` : Gestion de la sélection de template
    - `createScormActivity()` : Création avec template sélectionné
    - `renderValidationQuestionSection()` : Rendu de la section validation
    - `toggleValidationQuestion()` : Activation/désactivation validation
    - `updateValidationQuestion()` : Mise à jour question
    - `updateValidationAnswer()` : Mise à jour réponses
    - `updateValidationCorrectAnswer()` : Sélection bonne réponse
    - Icônes SVG pour chaque template SCORM
  - `editor.css` : Nouveaux styles
    - `.scorm-template-modal-*` : Styles modal de sélection
    - `.scorm-validation-*` : Styles section validation (fond ambre)

---

## [0.5.27] - 2026-01-26

### Ajouté

- **Moodia SCORM** : Nouveau type d'activité SCORM pour modules interactifs
  - 🎓 **6 templates interactifs** : Accordion, Slides, Cards, Tabs, Timeline, Reveal
  - 📦 **Export SCORM 1.2** : Packages conformes à la norme SCORM 1.2 pour Moodle
  - 🎨 **Design cohérent** : Couleur verte émeraude (#10B981) pour différencier les activités SCORM
  - ✏️ **Éditeur de sections** : Interface pour ajouter/modifier/supprimer des sections SCORM
  - 🔄 **Sélecteur de template** : Choix du type de template dans l'éditeur
  - ➕ **Ajout manuel** : SCORM disponible dans le menu "Ajouter une activité"
  - 🤖 **Génération automatique** : 20% des activités sont générées en SCORM lors de la création du cours
    - Minimum 5 activités par section pour générer un SCORM
    - Les pages SCORM ne sont jamais des pages audio (exclusivité)
    - Template choisi automatiquement selon le nombre de sections (reveal, tabs, accordion, slides)

### Technique

- **Backend** :
  - `ScormConfig.php` : Configuration centralisée (ratio 20%, templates, constantes SCORM 1.2)
  - `ScormGenerator.php` : Générateur d'activités SCORM pour export MBZ
  - `ScormPackageBuilder.php` : Construction des packages ZIP SCORM
  - `ScormManifestGenerator.php` : Génération imsmanifest.xml
  - `ScormHtmlGenerator.php` : Génération index.html interactif avec API SCORM
  - DTOs : `ScormPackage`, `ScormContent`, `ScormContentSection`, `ScormSco`
  - `MoodiaActivity::TYPE_SCORM` et champ `templateType` ajoutés
  - `CourseTransformer` : Support transformation activités SCORM
  - `MoodiaPersistenceService` : Support sauvegarde/chargement templateType
  - `MoodiaStreamingService` : Nouvelles méthodes pour génération auto SCORM
    - `selectScormPages()` : Sélection des pages SCORM (20%)
    - `getActivityType()` : Détermination du type (page/audio/scorm)
    - `prepareScormActivityData()` : Conversion contenu HTML en SCORM structuré
    - `extractSectionsFromHtml()` : Extraction sections depuis HTML
    - `chooseScormTemplate()` : Choix automatique du template

- **Frontend** :
  - `editor_controller.js` : Méthodes SCORM (renderScormActivityBody, addScormToSection, etc.)
  - `constants.js` : Constantes SCORM_TEMPLATES avec descriptions et cas d'usage
  - `variables.css` : Variables couleur SCORM (--color-scorm-*)
  - `editor.css` : Styles complets pour éditeur SCORM

- **Base de données** :
  - Migration `Version20260126130009` : Ajout colonne `template_type` à `moodia_activity`

---

## [0.5.26] - 2025-01-25

### Corrigé

- **Moodia Images** : Placement des images amélioré et ciblé
  - ❌ **Exclusion audio** : Les images ne sont plus placées dans les activités audio
  - ❌ **Exclusion blocs colorés** : Les images ne sont plus placées dans les encadrés (💡⚠️📌💬)
  - ❌ **Exclusion listes** : Les images ne coupent plus les listes ul/ol
  - ✅ **Ordre préservé** : Les images respectent leur ordre d'apparition dans le PDF original
  - ✅ **Contexte amélioré** : Bonus de score pour les placements qui préservent l'ordre logique

### Technique

- **Refactoring Doctrine** : Suppression de tout le SQL brut au profit des repositories
  - `MoodiaActivityRepository::getPageActivitiesForImagePlacement()` - Récupère les activités page pour le placement
  - `MoodiaActivityRepository::updateContent()` - Met à jour le contenu d'une activité
  - `MoodiaCourseRepository::updateStatusAndTokens()` - Met à jour statut, tokens et énergie
- **ImagePlacementService** : Algorithme amélioré
  - Tri des images par page PDF avant placement
  - Bonus d'ordre pour favoriser le placement séquentiel
  - Meilleure détection des zones interdites (regex améliorée)

---

## [0.5.25] - 2025-01-25

### Corrigé

- **Moodia Editor** : Navigation et suppression d'activités fonctionnent correctement
  - **Problème** : Erreur "Activity not found: page-X-Y" lors de la navigation et suppression
  - **Cause** : Incohérence entre `displayIndex` (compteur frontend) et `page.id` (ID backend)
  - Toutes les fonctions utilisent maintenant `page.id` de manière cohérente
  - Fonctions corrigées : `renderPageActivity`, `deleteActivity`, `collectCourseDataFromDOM`, TOC, quiz

- **Moodia Editor** : Ajout et suppression d'activités persistent en base de données
  - **Problème** : Les modifications n'étaient pas sauvegardées
  - **Cause** : Protection trop agressive dans `MoodiaPersistenceService::saveFromJson()` bloquait toutes les sauvegardes quand des images existaient
  - Suppression de la protection excessive

- **Moodia** : Tokens et consommation énergétique sauvegardés après génération
  - **Problème** : Tokens/Wh affichés à 0 après génération
  - **Cause** : `entityManager->clear()` (requis pour orphanRemoval) détachait l'entité avant le flush
  - **Solution** : Sauvegarde via SQL UPDATE direct après le clear

### Amélioré

- **Moodia** : Images placées dans les activités audio également
  - Les activités de type `audio` sont maintenant candidates au placement d'images
  - Meilleure distribution des images sur l'ensemble du cours

### Technique

- **MoodiaActivityRepository** : Nouvelle méthode `courseHasImages()` (pattern Doctrine, pas de SQL brut)
- **MoodiaStreamingService** : Mapping `activityId -> tempId` avant Doctrine clear pour les events SSE

---

## [0.5.24] - 2025-01-25

### Corrigé

- **Moodia** : Images du PDF maintenant correctement incluses dans le cours
  - **Problème identifié** : Les images étaient placées en BDD mais écrasées par la sauvegarde automatique du frontend
  - Le frontend appelait `saveCourse()` après l'événement `done`, écrasant le contenu avec les données sans images
  - Suppression de l'appel automatique à `saveCourse()` après génération (les données sont déjà persistées côté serveur)

### Amélioré

- **Moodia** : Placement intelligent des images basé sur le contexte PDF
  - Les images sont maintenant placées près du texte correspondant à leur contexte d'origine dans le PDF
  - Algorithme de similarité textuelle utilisant les mots significatifs (Jaccard + bonus mots longs)
  - Extraction du contexte PDF avec marqueurs `[AVANT]`/`[APRÈS]` pour localisation précise
  - Fallback sur distribution équitable si aucune correspondance contextuelle trouvée

### Technique

- **ImagePlacementService** : Refactoring complet
  - Nouvelle méthode `computeContextualPlacements()` avec algorithme en 2 phases
  - Phase 1 : Placement contextuel pour images avec correspondance (score > 0.1)
  - Phase 2 : Distribution équitable pour images restantes
  - Logs améliorés pour debug (`contextMatches` count)

---

## [0.5.23] - 2025-01-24

### Corrigé

- **Slidia** : Erreur 500 instantanée pour les nouveaux utilisateurs
  - Les utilisateurs sans `Settings` en base provoquaient un crash lors de la génération
  - Création automatique des `Settings` si absents dans les endpoints `generate`, `uploadMask` et `saveSettings`
  - Affecte les utilisateurs qui n'ont jamais visité la page de paramètres

---

## [0.5.22] - 2025-01-21

### Corrigé

- **Export MBZ** : Barre de progression fonctionne maintenant en production
  - Ajout du padding 2048 bytes pour contourner le buffering Apache/Nginx
  - Désactivation du output buffering PHP au début du stream
  - Suppression du time limit pour les exports longs avec génération audio

---

## [0.5.21] - 2025-01-21

### Modifié

#### Tests - Refactoring et amélioration qualité
- **AuthenticatedTestCase** : Classe de base commune pour tous les tests authentifiés
  - Refactoring de MoodiaTest, SereniaTest, SlidiaTest
  - Suppression de la duplication de code (méthodes `loginAsUser()`, etc.)
  - Tests plus maintenables et cohérents

- **MoodiaTest simplifié** : Focus sur les tests d'authentification et Moodle config
  - Tests d'accès page et redirection vers login
  - Tests de vérification de configuration Moodle (403 si non configuré)
  - 6 tests robustes et fiables

- **SlidiaTest nettoyé** : Suppression des tests pour comportements non implémentés
  - Tests d'authentification API (generate, mask download)
  - Tests de validation des paramètres (content vide/manquant)
  - 29 tests couvrant l'interface et les endpoints

- **SereniaTest structuré** : Organisation par endpoint API
  - Page access et authentification
  - Chat stream, upload, image generation, download, cleanup
  - 25 tests fonctionnels

### Technique

- **187 tests** passent avec succès (vs 200 avant nettoyage)
- Suppression de 13 tests qui testaient des comportements non implémentés
- Meilleure correspondance entre tests et comportement réel de l'application

---

## [0.5.20] - 2025-01-21

### Ajouté

#### Moodia - Auto-scroll intelligent
- **Suivi automatique de l'écriture** : Le scroll suit le contenu en cours de génération
  - L'utilisateur peut suivre l'écriture sans toucher à la souris
  - Se désactive automatiquement si l'utilisateur scroll manuellement
  - Reprend au prochain lancement de génération

#### Moodia - Documents additionnels améliorés
- **Badge PDF pour document source** : Nouveau design cohérent
  - Badge amber/jaune assorti au thème des annexes
  - Titre "Document de base" clair et explicite
  - Design card unifié avec les autres activités
  - Nom du fichier affiché dans le corps de l'activité

### Modifié

#### Moodia - Crédits redesignés
- **Crédits "Bubul.app" entièrement bleu** : Plus de couleur séparée pour le point
  - Suppression de la classe `credits-dot`
  - `credits-brand` inclut maintenant ".app"

### Corrigé

- **Documents additionnels** : Affichage correct pendant et après la génération
  - TOC : S'affiche uniquement quand la génération est terminée
  - Skeleton : Indicateur de chargement pendant la génération du glossaire
  - Le skeleton est remplacé par le contenu réel à la fin

---

## [0.5.19] - 2025-01-21

### Ajouté

#### Moodia - Animation d'écriture et export améliorés
- **Animation streaming restaurée** : Texte s'écrit progressivement mot par mot
  - Chunks de 2 mots avec délai de 35ms pour effet visuel fluide
  - `getSafeHtmlForDisplay()` ferme temporairement les balises ouvertes
  - `ob_flush()` ajouté pour envoi immédiat des données SSE

- **Conversion Markdown automatique** : Backup si l'IA génère du Markdown
  - `convertMarkdownToHtml()` convertit `**texte**` → `<strong>texte</strong>`
  - Appliqué sur tout le contenu (pages et introductions)
  - Instruction explicite dans les prompts : "MARKDOWN INTERDIT"

- **Styles inline pour export MBZ** : Design préservé dans Moodle
  - `convertEduBlocksToInlineStyles()` : 8 types de blocs éducatifs
  - `convertCreditsToInlineStyles()` : Crédits Bubul avec design complet
  - Couleurs, gradients, border-radius intégrés directement

### Modifié

#### Moodia - Blocs éducatifs redesignés
- **Suppression des icônes emoji** : Design épuré sans 📊📅💡 etc.
  - Padding uniforme (plus de marge gauche pour icône)
  - Titres colorés selon le thème du bloc
  - CSS simplifié (suppression des `::before { content: "emoji" }`)

- **Règle d'espacement** : Jamais 2 blocs éducatifs consécutifs
  - Instruction ajoutée : "Toujours au moins 2 paragraphes entre chaque bloc"
  - Meilleure lisibilité et rythme de lecture

- **Quiz export optimisé** : Notes et tentatives configurées
  - `reviewattempt = 69904` : Affichage notes immédiat + après fermeture
  - `attempts_number = 0` : Tentatives illimitées
  - Feedback visible à tout moment

### Corrigé

- **Erreur TOC toggleSection** : `stopPropagation` manquant corrigé
- **Pages audio sans images** : Exclusion du placement d'images pour type audio
- **Validation titre supprimée** : Génération IA basée sur PDF uniquement
  - Frontend : Validation retirée de `config_controller.js`
  - Backend : Validation retirée de `MoodiaController.php`
  - OpenAI : Nouveau cas pour générer objectifs sans titre

---

## [0.5.18] - 2025-01-21

### Corrigé

#### Moodia - Correction corruption HTML lors du placement des images
- **Suppression du parsing DOM** : Élimination des fonctions qui corrompaient le HTML propre
  - Suppression de `mergeBrokenParagraphs()` et fonctions associées (~200 lignes)
  - Le navigateur modifiait le HTML lors du parsing DOM, causant des corruptions
  - Le contenu de l'IA est maintenant utilisé directement sans modification

- **Simplification du nettoyage** : Regex simples au lieu de manipulation DOM
  - `cleanupEmptyHtmlTags()` : Ne fait plus de parsing DOM, juste suppression balises vides
  - `handleContentUpdate()` : Utilise directement le contenu du serveur
  - `cleanHtmlContent()` : Nettoyage par regex uniquement

- **Flux de données propre** : Le HTML généré par l'IA reste intact
  - Génération → Streaming → BDD → Affichage (sans transformation)
  - Correction du bug où le texte se "cassait" après le placement des images

---

## [0.5.17] - 2025-01-21

### Modifié

#### Moodia - Confiance totale dans l'IA pour le HTML
- **Suppression de `cleanGeneratedHtml()`** : Post-traitement complètement éliminé
  - L'IA génère maintenant un HTML parfait directement
  - Les prompts sont suffisamment clairs et précis
  - Suppression de `cleanGeneratedHtml()` et `convertPlainTextToHtml()`
  - Code plus simple et plus maintenable

- **Espacement avec `<br>`** : Paragraphes aérés via balises HTML
  - Ajout de `<br>` comme balise autorisée dans les prompts
  - Instructions pour aérer le texte entre chaque paragraphe
  - Structure : `<p>Paragraphe 1</p><br><p>Paragraphe 2</p>`
  - Margin CSS à 0 pour les paragraphes (espacement géré par `<br>`)

---

## [0.5.16] - 2025-01-21

### Corrigé

#### Moodia - Améliorations CSS
- **Glossaire sans puces** : Suppression des bullets inutiles dans le glossaire
  - Les termes s'affichent maintenant sans puce de liste
  - Meilleure lisibilité du contenu

- **Espacement paragraphes réduit** : Passage de `1.5rem` à `1rem`
  - Moins d'espace entre les lignes lors de la frappe
  - Meilleure densité de contenu à l'écran

---

## [0.5.15] - 2025-01-21

### Modifié

#### Moodia - Contenu plus détaillé et flexible
- **Listes optionnelles** : L'IA décide librement de la forme du contenu
  - Les listes `<ul><li>` ne sont plus systématiques
  - Utilisées uniquement quand le contenu s'y prête vraiment
  - Style narratif et fluide privilégié

- **+10% de contenu pédagogique** : Cours plus détaillés
  - Nouveau paramètre `content_time_multiplier: 1.1` dans `PedagogicalConfig`
  - Un cours de 1h génère maintenant ~1h06 de contenu pédagogique
  - Chaque concept traité plus en profondeur

- **Prompts améliorés** :
  - Instructions pour paragraphes riches et développés
  - Séquence pédagogique : contexte → définition → explication → exemple → implications
  - Exemples JSON sans liste (pour ne pas influencer l'IA)

---

## [0.5.14] - 2025-01-21

### Modifié

#### Moodia - Design unifié Audio/Page
- **Type Audio** : Même design que le type Page
  - Suppression de la classe `.audio-text-source` spécifique
  - Utilisation de `.content-display` pour tous les types d'activités
  - Cohérence visuelle parfaite entre Audio et Page

- **CSS nettoyé** :
  - Suppression des styles `.audio-text-source` (devenus inutiles)
  - Suppression de `.content-display > p:first-child` (premier paragraphe sans style spécial)
  - Tous les paragraphes ont maintenant le même style

---

## [0.5.13] - 2025-01-21

### Modifié

#### Moodia - Génération HTML propre et simplifiée
- **Prompts unifiés** : Suppression des instructions contradictoires entre `buildSectionSystemPrompt()` et `buildSectionUserPrompt()`
  - Le prompt système dit : "UNIQUEMENT <p> et <ul><li>"
  - Le prompt utilisateur disait : "Utilise <h2>, <h3>, <h4>, <strong>"
  - Maintenant les deux prompts sont alignés et cohérents
  - L'IA génère un HTML simple et propre dès la première génération

- **Post-traitement simplifié** : `cleanGeneratedHtml()` réduit de ~400 lignes à ~60 lignes
  - Fonctions obsolètes supprimées : `fixMalformedLists()`, `fixMalformedHtmlAggressive()`, `mergeBrokenParagraphs()`
  - Nouveau code simple et maintenable
  - Les Hx sont convertis en `<p>` (pas supprimés)
  - Suppression du markdown résiduel
  - Gestion des balises vides et texte orphelin

- **CSS nettoyé** : Suppression du masquage CSS des Hx
  - Les règles `display: none !important` sur h1-h6 ne sont plus nécessaires
  - Le HTML est propre dès la génération

### Principe
- **UNE seule source de vérité** : Le prompt est clair, l'IA génère du HTML propre
- Plus besoin de centaines de lignes de regex pour corriger les erreurs
- HTML ultra-simple : `<p>`, `<ul>/<li>`, `<blockquote>` uniquement

---

## [0.5.12] - 2025-01-21

### Modifié

#### Moodia - Balises Hx TOTALEMENT interdites
- **Prompt simplifié** : UNIQUEMENT 3 balises autorisées : `<p>`, `<ul>/<li>`, `<blockquote>`
  - Les titres (h1-h6) sont TOTALEMENT interdits dans le contenu
  - Le titre est déjà géré par l'interface (titre de section/activité)
  - Exemple concret de HTML correct fourni dans le prompt
  - RÈGLE D'OR : "Que des `<p>` et parfois `<ul>/<li>`. JAMAIS de titres."

#### Moodia - Post-traitement renforcé
- **Étape 5 ajoutée** : Suppression de TOUS les Hx restants
  - `<h4>Texte</h4>` → `Texte` (texte inline)
  - Texte orphelin automatiquement enveloppé dans `<p>`
  - 5 passes pour gérer les cas imbriqués

#### Moodia - CSS de sécurité
- **Tous les Hx masqués** : `display: none !important` sur h1-h6
  - Appliqué à `.content-display` et `.activity-content-display`
  - Filet de sécurité si des Hx passent malgré tout

---

## [0.5.11] - 2025-01-21

### Corrigé

#### Moodia - Nettoyage HTML agressif
- **Post-traitement renforcé** : Nettoyage complet de tous les cas de HTML malformé générés par l'IA
  - Suppression des balises vides : `<h2></h2>`, `<p></p>`, etc. (3 passes)
  - Correction de `<h3><p>Texte</p></h3>` → `<h3>Texte</h3>`
  - Correction de `<p>texte<h4>Terme</h4>suite</p>` → `<p>texte Terme suite</p>` (5 passes)
  - Correction de `<h4></h4><h4>Titre</h4>` → `<h4>Titre</h4>`
  - Suppression des Hx orphelins au milieu de phrases
  - Nettoyage final des balises vides créées par les transformations

#### Moodia - Format audio harmonisé
- **Type Audio** : Utilise maintenant le MÊME format HTML que les pages normales
  - `buildAudioSystemPrompt()` → appelle `buildSectionSystemPrompt()`
  - `buildAudioUserPrompt()` → appelle `buildSectionUserPrompt()`
  - Cohérence visuelle entre tous les types de contenu

---

## [0.5.10] - 2025-01-21

### Corrigé

#### Moodia - Markdown interdit dans le contenu
- **Suppression du markdown** : Les syntaxes `**texte**`, `*texte*`, `__texte__`, `_texte_` sont maintenant interdites
  - Prompt mis à jour avec exemple explicite : `**Terme** : définition → Terme : définition`
  - Post-traitement regex pour convertir le markdown restant en texte simple

#### Moodia - Design harmonisé
- **Type Audio** : Même style Magazine/Blog que le type Page
  - Typography : `font-size: 1.0625rem`, `line-height: 1.85`
  - Listes modernes avec puces gradient
  - Blockquotes avec guillemet décoratif
  - Callouts élégants (tip, warning, info, quote)

- **Images avec légende** : Titre maintenant centré en dessous de l'image
  - `flex-direction: column` + `align-items: center`
  - Ombre portée sur les images (`box-shadow`)
  - Border-radius 12px pour un look moderne

- **Crédits redesignés** : Style éditorial élégant
  - Fond gradient avec bordure subtile
  - Icône ✨ décorative en haut
  - Typography harmonisée avec le reste du contenu

---

## [0.5.9] - 2025-01-21

### Modifié

#### Moodia - Nouveau style Magazine/Blog éditorial
- **Refonte du style de contenu** : Passage d'un style "manuel scolaire" à un style éditorial professionnel
  - `h2`, `h3`, `h4` autorisés uniquement comme TITRES sur leur propre ligne
  - JAMAIS de Hx au milieu d'un paragraphe ou d'une phrase
  - Contenu fluide avec paragraphes bien construits et transitions naturelles

#### Moodia - Nouveau prompt éditorial
- **Style rédactionnel Magazine/Blog** : Ton accessible mais expert, comme un article du New York Times ou Sciences & Vie
  - Paragraphes fluides avec transitions naturelles entre les idées
  - Évite le style "titre, sous-titre, liste, titre, sous-titre, liste..."
  - Privilégie la narration et l'explication plutôt que l'énumération
  - Maximum 1-2 encadrés par page (pas de surcharge visuelle)
- **Règles strictes pour les Hx** : Un titre Hx est TOUJOURS suivi de paragraphes, jamais d'un autre Hx

#### Moodia - Design CSS éditorial élégant
- **Typography magazine** : `font-size: 1.0625rem`, `line-height: 1.85`, texte justifié
- **Titres stylisés** : h2 (1.5rem), h3 (1.25rem), h4 (1.0625rem) avec margins généreux
- **Listes modernes** : Puces avec gradient, numéros dans des cercles colorés
- **Blockquotes stylisées** : Citation en exergue avec guillemet décoratif
- **Callouts élégants** : 4 types avec gradients (tip, warning, info, quote)

#### Moodia - Nettoyage HTML mis à jour
- **Hx mal placés nettoyés** : Les Hx dans les paragraphes/listes sont convertis en texte simple
- **h1 toujours interdit** : Conversion automatique en paragraphe

---

## [0.5.8] - 2025-01-21

### Corrigé

#### Moodia - Section TOC et format audio
- **Section "Documents additionnels" cachée dans la TOC pendant la génération** : La section annexes dans la table des matières (partie gauche) ne s'affiche plus pendant la génération
  - Condition `!this.isGenerating` ajoutée dans `renderToc()` ligne 1381
  - Cohérence avec le comportement de `renderSections()`
- **Correction du timing de fin de génération** : Les annexes restent maintenant visibles après la fin de génération
  - `isGenerating = false` déplacé AU DÉBUT du handler `case 'done'` (avant `renderCourse()`)
  - Résout le bug où les annexes disparaissaient car le flag était encore `true` lors du rendu final

#### Moodia - Contenu audio optimisé pour TTS
- **Nouveau format pour les pages audio** : Le contenu audio est maintenant généré en texte simple, optimisé pour la lecture vocale
  - Nouvelles méthodes `buildAudioSystemPrompt()` et `buildAudioUserPrompt()` dans `CourseGenerationService.php`
  - Pas de balises HTML structurées (h2, h3, ul, li) dans le contenu audio
  - Style oral naturel avec transitions : "Passons maintenant à...", "Voyons comment..."
  - Meilleure expérience TTS (Text-to-Speech)

#### Moodia - Nettoyage HTML renforcé
- **Suppression totale des balises de mise en forme** : `<strong>`, `<b>`, `<em>`, `<i>` interdites pour éviter les problèmes d'affichage
  - Prompt mis à jour : `INTERDIT : <strong>, <b>, <em>, <i>`
  - Post-traitement regex pour supprimer toute balise inline restante
- **Règles HTML plus strictes dans le prompt** : 6 règles explicites numérotées pour éviter les erreurs
  - Interdiction des balises vides : `<h2></h2>`, `<p></p>`, etc.
  - Interdiction des titres dans inline : `<strong><h4>...</h4></strong>`
  - Interdiction des titres dans listes : `<li><h4>...</h4></li>`
- **Nettoyage post-génération amélioré** : Nouvelles regex dans `fixMalformedLists()`
  - Correction automatique de `<li><strong><h4>Terme</h4></strong>` → `<li>Terme`
  - Correction de `<li><h4>Terme</h4>` → `<li>Terme`
  - Suppression de toutes les balises `<strong>`, `<b>`, `<em>`, `<i>` du contenu

#### Moodia - Design amélioré
- **Margin-top pour les titres** : Espacement vertical ajouté pour un design plus propre
  - `h2` : margin-top 2rem (0 si premier élément)
  - `h3` : margin-top 1.75rem (0 si premier élément)
  - `h4` : margin-top 1.5rem (0 si premier élément)
  - Appliqué dans `.content-display` et `.activity-content-display`

---

## [0.5.7] - 2025-01-21

### Corrigé

#### Moodia - UX et qualité HTML
- **Section "Documents additionnels" cachée pendant la génération** : La section annexes ne s'affiche plus avant d'avoir du contenu
  - Condition `!this.isGenerating` ajoutée dans `renderSections()`
  - Les annexes apparaissent uniquement après `handleDone()` (fin de génération)
- **HTML mal formaté corrigé** : Nouvelles règles explicites pour éviter les imbrications invalides
  - INTERDIT : `<strong><h4>...</h4></strong>` ou `<li><h2>...</h2></li>`
  - Les titres (h2, h3, h4) doivent être des éléments AUTONOMES
  - Dans les listes : utiliser `<li><strong>Terme :</strong> explication</li>`

---

## [0.5.6] - 2025-01-21

### Corrigé

#### Moodia - Placement intelligent des images
- **Correction du problème "premier arrivé, premier servi"** : Les images étaient placées au premier endroit mentionnant le sujet au lieu du MEILLEUR endroit
- **Nouveau prompt de placement** : Instructions explicites pour analyser TOUT le cours avant de décider
  - Identifier TOUS les endroits possibles pour chaque image
  - Comparer et choisir le MEILLEUR (le plus détaillé, pas le premier)
  - Préférer l'activité qui traite le sujet EN PROFONDEUR
- **Exemple** : Image "Traité de Versailles" → va maintenant dans la page qui DÉTAILLE le traité, pas celle qui mentionne vaguement "les traités"

---

## [0.5.5] - 2025-01-21

### Modifié

#### Moodia - Structure HTML professionnelle et design amélioré
- **Nouvelles balises HTML autorisées** : Structure hiérarchique complète pour les cours
  - `<h2>` pour les titres de sections principales
  - `<h3>` pour les sous-sections
  - `<h4>` pour les points clés
  - `<ul>/<li>` pour les listes à puces
  - `<strong>` pour les termes importants
  - Seul `<h1>` reste interdit (réservé au titre de page)
- **Prompts de contenu enrichis** : Instructions pour une meilleure structure
  - Structure hiérarchique claire demandée dans les prompts
  - Utilisation des listes et mise en valeur des termes importants
  - Contenu plus professionnel et lisible
- **Placement des images** : Utilise déjà le contexte complet du cours
  - `buildCourseSummaryForImagePlacement()` envoie tout le contenu
  - Matching précis basé sur le sujet principal de chaque image

---

## [0.5.4] - 2025-01-21

### Modifié

#### Moodia - Cours plus complets et design amélioré
- **Prompts renforcés** : Instructions pour des cours exhaustifs et détaillés
  - Chaque sujet traité en profondeur : définition → explication → exemple → application pratique
  - Structure claire avec sous-titres (<h4>) pour chaque concept majeur
  - Transitions fluides entre les paragraphes
- **Design des blocs colorés modernisé** : Suppression du `border-left`, nouveau style épuré
  - Bordures arrondies complètes (`border-radius: 12px`)
  - Bordure fine sur tout le contour au lieu de la bordure gauche épaisse
  - Meilleure lisibilité avec `font-size: 15px` et `line-height: 1.6`
- **Blocs colorés optionnels** : L'IA décide quand les utiliser selon la pertinence du contenu

---

## [0.5.3] - 2025-01-21

### Modifié

#### Moodia - Liberté de rédaction pour l'IA
- **Suppression des contraintes de caractères** : L'IA détermine librement la longueur du contenu
  - Suppression des exigences "minimum X caractères" dans les prompts
  - Suppression du calcul `requiredCharsPerPage` et des contraintes forcées
  - L'IA écrit un contenu naturel, adapté au sujet et au niveau du public
  - Résultat : contenu plus fluide et moins artificiel
- **Prompts simplifiés** : Instructions de rédaction claires sans contraintes rigides
  - Mode cours : "Écris un contenu riche et pédagogique adapté au niveau et au public cible"
  - Mode cours+quiz : Idem avec quiz de 10 questions
  - Focus sur la qualité plutôt que la quantité

---

## [0.5.2] - 2025-01-21

### Ajouté

#### Moodia - Pipeline de génération optimisé
- **Pipelining des requêtes API** : Optimisation majeure pour les cours longs (7h = 7 sections × 5 pages)
  - Prefetch section N+1 pendant le streaming de la section N
  - Élimination du temps mort (~15s) entre chaque section
  - Gain estimé : -33% (2h) à -40% (7h) sur le temps total de génération
  - Flux de streaming continu sans interruption visible pour l'utilisateur

- **Méthodes de génération async** dans `CourseGenerationService` :
  - `startAsyncPagesGeneration()` : Lance les requêtes API en arrière-plan
  - `consumeAsyncPagesGeneration()` : Consomme les résultats prefetchés dans l'ordre

- **Gestion du prefetch** dans `MoodiaStreamingService` :
  - `prepareSectionPrefetch()` : Prépare les données avec contexte basé sur les titres
  - `startSectionPrefetch()` : Lance le prefetch de la section suivante
  - `consumePrefetch()` : Récupère et utilise les résultats prefetchés
  - `cleanupPrefetch()` : Nettoyage en cas d'erreur

### Corrigé

#### Moodia
- **Timeout PHP** : Ajout de `set_time_limit(0)` pour les cours longs (évite timeout après 30-120s)
- **Troncature du plan JSON** : Tokens dynamiques pour le plan selon la durée du cours
  - 1h = 2000 tokens, 7h = 6800 tokens (max 8000)
  - Corrige l'erreur `finish_reason: "length"` pour les cours longs
- **Feedback pendant l'analyse des images** : Événements SSE pour chaque image analysée
  - Évite les timeouts de connexion pendant l'analyse Vision AI
  - Affiche "Analyse image 1/9..." pour chaque image

### Modifié

#### Moodia - Structure des cours adaptative
- **Nombre de pages par section adaptatif** : L'IA décide du nombre de pages selon la densité du contenu
  - Suppression de la limite fixe de 5 pages par section
  - Fourchette flexible (ex: 3-10 pages selon le contenu de chaque section)
  - Sections denses → plus de pages, sections légères → moins de pages
- **Quiz fixé à 10 questions** : Nombre de questions standardisé à 10 par section
  - Expérience utilisateur cohérente quelle que soit la durée du cours
  - Mode quiz uniquement : 15 questions

---

## [0.4.0] - 2025-01-18

### Ajouté

#### Moodia - Description stylisée des cours Moodle
- **Description Bubul dans Moodle** : Nouvelle description stylisée pour les cours exportés
  - Logo SVG Bubul avec gradient (gauche) + texte de présentation (droite)
  - Design reprenant le style sidebar de Bubul (gradient bleu clair)
  - Tableau des métadonnées : niveau, durée (en heures), public cible
  - Mapping des niveaux français avec accents (Débutant, Intermédiaire, Avancé, Expert)
  - Lien vers bubul.app dans la signature
  - Support export MBZ et déploiement direct Moodle

#### Moodia - Amélioration du placement des images
- **Contexte enrichi pour l'analyse d'images** : Amélioration du contexte fourni à l'IA pour le placement
  - Envoi du cours COMPLET pour un placement plus intelligent
  - Contexte enrichi incluant les descriptions et métadonnées des images
  - Meilleure pertinence du placement automatique des images extraites du PDF

### Corrigé

#### Moodia
- **Troncature JSON** : Correction de la troncature du JSON lors de la génération du plan
- **Erreur syntaxe heredoc** : Correction dans `buildImagesListForPlacement`
- **Placement images** : Amélioration du placement et suppression des descriptions galerie inutiles

### Maintenance
- Ajout de `.claude/` au `.gitignore`

---

## [0.3.0] - 2025-11-16

### Ajouté

#### Slidia - Générateur de présentations PowerPoint
- **SlidiaService** : Service principal pour la génération de présentations
  - `generatePresentation()` : Génère une présentation PowerPoint depuis du contenu texte
  - `formatContent()` : Formate le contenu brut avec séparateurs "---"
  - `structureContent()` : Appelle OpenAI pour structurer le contenu en slides
  - Détection automatique du type de slide optimal (9 layouts disponibles)
  - Support du séparateur "---" pour séparer manuellement les slides
  - Découpe intelligente par sections logiques (max 200 mots par slide)
  - Configuration via constantes :
    - Températures API : `TEMPERATURE_FORMAT = 0.3`, `TEMPERATURE_GENERATION = 0.7`
    - Timeouts : `TIMEOUT_FORMAT = 120s`, `TIMEOUT_GENERATION = 300s`
    - Limites : `MAX_WORDS_PER_SLIDE = 200`, `MAX_ITEMS_PER_LIST = 10`, `MAX_SLIDES = 100`

- **SlideGeneratorService** : Service de génération physique des slides PowerPoint
  - Support de 9 layouts professionnels :
    - `title` : Page de garde (titre + sous-titre)
    - `section` : Séparateur de chapitre
    - `content` : Titre et texte (max 200 mots)
    - `content_list` : Texte avec liste (intro + énumération)
    - `list` : Liste pure (puces ou numéros)
    - `two_columns` : Deux colonnes (comparaison, avant/après)
    - `highlight` : Citation ou chiffre clé
    - `table` : Tableau structuré
    - `conclusion` : Slide de fin
  - Intégration DALL-E 3 pour la génération d'images de fond
  - Design professionnel avec palette de couleurs cohérente
  - Configuration via 30+ constantes :
    - Dimensions : `SLIDE_WIDTH = 960`, `SLIDE_HEIGHT = 540`
    - Tailles de police : 7 niveaux (TITLE=48, SECTION=44, HEADING=28, etc.)
    - Marges et espacements : 6 niveaux standardisés
    - Éléments visuels : barres d'accentuation, colonnes, etc.

- **SlidiaController** : Controller REST pour Slidia
  - `POST /slidia/format` : Formate le contenu avec l'IA
  - `POST /slidia/generate` : Génère et télécharge le PowerPoint
  - Validation du contenu (max 20 000 caractères)
  - Vérification des tokens disponibles
  - Déduction automatique des tokens
  - Métadonnées dans les headers HTTP (X-Tokens-Used, X-Slides-Count)

#### Frontend Slidia
- **Interface utilisateur minimaliste** :
  - Zone de saisie principale (20 000 caractères max)
  - Compteur de caractères en overlay (bottom-right) avec effet glassmorphism
  - Bouton de génération one-click
  - Sidebar avec informations (tokens, énergie consommée)
  - Animations de loading avec messages progressifs

- **Stimulus Controller (slidia--generator)** :
  - Workflow simplifié : formatage automatique + génération en un clic
  - Validation côté client (contenu, tokens)
  - Animations de déduction de tokens réutilisables
  - Gestion des états de chargement avec messages dynamiques :
    - "Formatage du contenu en cours..."
    - "Génération de la présentation..."
  - Téléchargement automatique du fichier PPTX
  - Notification de succès avec nombre de slides générées

#### Composants réutilisables
- **TokenAnimationManager** : Gestionnaire d'animations centralisé (DRY)
  - `animateTokensUpdate()` : Animation de décompte des tokens
  - `animateEnergyUpdate()` : Animation de compteur d'énergie
  - `showTokenAnimation()` : Animation flyUp pour les tokens (désactivée dans Serenia)
  - `showEnergyAnimation()` : Animation flyUp pour l'énergie (désactivée dans Serenia)
  - Animations CSS fluides avec easing personnalisé

#### Système de notifications global
- **Notifications toast** accessibles via `window.showNotification()`
  - Types : success, error, warning, info
  - Auto-dismiss après 5 secondes
  - Design cohérent avec la charte graphique
  - Utilisé dans Slidia et Serenia

### Modifié

#### Serenia
- **Animations simplifiées** :
  - Suppression des animations flyUp pour les tokens/énergie
  - Conservation des animations de compteur (incrémentation/décrémentation)
  - Interface plus discrète et moins distrayante

#### Design général
- **Interface minimaliste** :
  - Suppression des gradients et backgrounds superflus
  - Compteurs en overlay avec backdrop-filter blur
  - Typographie soignée avec font-variant-numeric: tabular-nums
  - Espacements harmonisés

### Architecture

#### Namespace Slidia
```
src/Service/Slidia/
├── SlidiaService.php (13K) - Communication OpenAI, structuration
├── SlideGeneratorService.php (40K) - Génération PowerPoint
└── ImageGenerationService.php (supprimé) - Fonctionnalités intégrées

src/Controller/
└── SlidiaController.php - Routes API REST

templates/slidia/
└── index.html.twig - Interface utilisateur

assets/controllers/slidia/
└── generator_controller.js - Logique frontend

assets/controllers/_components/
└── TokenAnimationManager.js - Animations réutilisables
```

#### Principe SOLID et DRY
- **DRY** : TokenAnimationManager partagé entre Slidia et Serenia
- **Single Responsibility** : SlidiaService (IA) vs SlideGeneratorService (PowerPoint)
- **Configuration centralisée** : Constantes pour toutes les valeurs hardcodées
- **Maintenabilité** : 30+ constantes documentées pour faciliter les ajustements

### Configuration

#### services.yaml
```yaml
App\Service\Slidia\SlidiaService:
    arguments:
        $apiKey: '%env(OPENAI_API_KEY)%'
        $model: 'gpt-4o-mini'

App\Service\Slidia\SlideGeneratorService:
    arguments:
        $projectDir: '%kernel.project_dir%'
        $apiKey: '%env(OPENAI_API_KEY)%'
```

#### Nouveaux fichiers de configuration
- `MAX_CHARS = 20000` : Limite de caractères pour le contenu
- `MAX_TOKENS = 16384` : Limite OpenAI pour gpt-4o-mini
- `MAX_SLIDES = 100` : Limite de slides par présentation

### Statistiques

- **Nouveaux services** : 2 services créés (SlidiaService, SlideGeneratorService)
- **Nouveaux controllers** : 1 controller (SlidiaController)
- **Layouts PowerPoint** : 9 layouts professionnels implémentés
- **Constantes de configuration** : 40+ constantes ajoutées pour la maintenabilité
- **Intégrations IA** : OpenAI GPT-4o-mini + DALL-E 3
- **Code réutilisable** : TokenAnimationManager partagé entre modules

---

## [0.2.0] - 2025-11-12

### Ajouté

#### Services Serenia
- **TextCleanerService** : Service global partagé pour le nettoyage UTF-8
  - `cleanUtf8Text()` : Nettoie et valide l'encodage UTF-8
  - `cleanMessagesUtf8()` : Nettoie les tableaux de messages
  - Supprime les caractères malformés, séquences UTF-8 invalides et caractères de contrôle

- **FileProcessorService** : Service dédié au traitement des fichiers pour Serenia
  - `prepareFilesForChat()` : Orchestration du traitement de tous types de fichiers
  - `appendExtractedTextsToMessages()` : Ajoute les textes extraits aux messages
  - Support des images (Vision API), PDF, Word, Excel
  - Gestion d'erreurs par type de fichier

- **StreamingService** : Service dédié au streaming SSE (Server-Sent Events)
  - `createStreamedChatResponse()` : Crée la réponse streamée complète
  - `createErrorResponse()` : Crée une réponse d'erreur SSE
  - Configuration automatique de l'environnement PHP pour SSE
  - Streaming mot par mot avec padding Apache
  - Gestion des tokens et logging

#### Gestion d'erreurs
- **JsonExceptionListener** : Event Listener global pour les routes `/serenia/*`
  - Convertit automatiquement toutes les exceptions en JSON
  - Messages d'erreur user-friendly selon le code HTTP (413, 400, 404, 403, 500)
  - Logging automatique de toutes les erreurs
  - Priorité 0 (s'exécute en premier)

- **Validation côté client JavaScript** :
  - Vérification de la taille des fichiers AVANT l'upload (< 20 MB)
  - Détection du Content-Type de la réponse (JSON vs HTML)
  - Messages d'erreur clairs avec nom et taille du fichier

- **Validation côté serveur** :
  - `isFileTooLarge()` : Détecte les fichiers dépassant les limites PHP
  - `parsePhpSize()` : Convertit les tailles PHP (10M, 128M, 2G) en bytes
  - Vérification du Content-Length vs post_max_size
  - Détection des uploads refusés par PHP

### Modifié

#### Refactorisation majeure du SerenIAController
- **Réduction de code** : 500+ lignes → 290 lignes (-42%)
- **Méthode chatStream()** : 160 lignes → 20 lignes (-87%)
- Délégation complète aux services spécialisés
- Code auto-documenté et maintenable
- Suppression de toutes les méthodes privées déplacées vers les services

#### FileHandlerService
- Intégration du `TextCleanerService` pour le nettoyage UTF-8
- Suppression de la méthode dupliquée `cleanUtf8Text()`
- Application du nettoyage UTF-8 dans :
  - `extractPdfText()`
  - `extractWordText()`
  - `extractExcelText()`

#### OpenAIService
- Intégration du `TextCleanerService` pour le nettoyage UTF-8
- Suppression de la méthode dupliquée `cleanUtf8Text()`
- Application du nettoyage UTF-8 dans `extractPdfContent()`

#### Frontend JavaScript
- Remplacement du `match()` par `if/elseif/else` dans FileProcessorService
- Code plus classique et lisible
- Meilleure gestion d'erreurs avec messages explicites

### Supprimé

#### PhpInfoController
- **PhpInfoController** : Controller de débogage supprimé (inutilisé)
  - Route `/phpinfo` retirée
  - Configuration dans `services.yaml` nettoyée
  - Accessible uniquement en mode dev, devenu obsolète

### Corrigé

#### Problèmes d'encodage UTF-8
- **Erreur** : "Invalid value for "json" option: Malformed UTF-8 characters"
- **Cause** : Caractères UTF-8 malformés dans les PDFs extraits
- **Solution** : Nettoyage UTF-8 systématique en 4 étapes :
  1. Conversion UTF-8 avec remplacement des caractères invalides
  2. Suppression des caractères de contrôle invalides
  3. Suppression des séquences UTF-8 invalides
  4. Normalisation des espaces blancs

#### Problèmes de fichiers trop lourds
- **Erreur** : `SyntaxError: Unexpected token '<', "<html><h"... is not valid JSON`
- **Cause** : Le serveur retournait du HTML au lieu de JSON pour les fichiers > limites
- **Solutions multiples** :
  - Validation JavaScript côté client (bloque avant envoi)
  - Détection Content-Type côté client (évite le parsing JSON sur HTML)
  - Event Listener côté serveur (force JSON même sur erreur)
  - Validation côté serveur (détecte dépassement limites PHP)

### Architecture

#### Namespace Serenia organisé
```
src/Service/Serenia/
├── FileHandlerService.php (15K) - Gestion fichiers (upload, extraction)
├── FileProcessorService.php (4.9K) - Traitement fichiers pour chat
├── StreamingService.php (5.9K) - Gestion streaming SSE
└── SerenIAService.php (12K) - Communication OpenAI

src/Service/
└── TextCleanerService.php - Service global partagé (DRY)

src/Controller/Serenia/
└── SerenIAController.php (290 lignes) - Ultra-léger, délègue tout

src/EventListener/
└── JsonExceptionListener.php - Conversion exceptions → JSON
```

#### Principe SOLID respecté
- **Single Responsibility** : Chaque service a une responsabilité unique
- **Separation of Concerns** : Controller léger, logique dans les services
- **DRY (Don't Repeat Yourself)** : TextCleanerService centralisé
- **Dependency Injection** : Tous les services autowirés

### Configuration

#### services.yaml
```yaml
App\Service\Serenia\FileProcessorService:
    arguments:
        $projectDir: '%kernel.project_dir%'
```

### Statistiques

- **Réduction de code** : -42% dans le controller
- **Nouveaux services** : 3 services créés (FileProcessor, Streaming, TextCleaner)
- **Event Listeners** : 1 listener créé (JsonException)
- **Tests** : Syntaxe PHP validée, services autowirables, routes fonctionnelles

---

## [0.1.0] - 2025-11-10

### Ajouté
- Version initiale de Serenia
- Chat avec streaming SSE
- Support des fichiers (images, PDF, Word, Excel)
- Intégration OpenAI GPT-4o
- Système de tokens
- Génération d'images DALL-E 3

[Non publié]: https://github.com/votre-repo/compare/v0.4.0...HEAD
[0.4.0]: https://github.com/votre-repo/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/votre-repo/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/votre-repo/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/votre-repo/releases/tag/v0.1.0
