Changelog
What changed, straight from the code
This isn't a marketing page: it's the actual CHANGELOG.md shipped inside the downloadable zip, read automatically on every visit — never retyped by hand, so it never drifts out of sync.
Current version : v1.4.0 Download this version
[1.1.1] - 2026-08-07
Corrigé (2026-08-07, retours après premier essai de /suggest_design + sélecteur de taille)
- Retour utilisateur — « je viens de faire une 2e test [...] Échec : Modification impossible : Deck non conforme au schéma : [...] 'paragraphs' was unexpected » : sur une slide contenant un tableau, l'IA tentait parfois de reformater le contenu de ses cellules en y ajoutant
paragraphs(structure d'une zone de texte) au lieu de conserverruns(seule structure valide d'une cellule de tableau, cf.deck.schema.json::table.rows) — rejeté par la validation du schéma, échec brut affiché tel quel dans le fil de discussion.services/design_suggest_service.py::instruction_for_optionajoute désormais une consigne explicite dès qu'une zone "tableau"/"graphique" est concernée : la déplacer/redimensionner (changer sonrect) SANS toucher à sa structure interne, qui n'a de toute façon aucune raison de changer pour un simple repositionnement. - Retour utilisateur — « dans la console de l'assistant [...] je pense que ça pourrait être plus clair lorsque l'on sélectionne une des propositions et qu'il est en train de la traiter » : les 2 étapes IA de
/suggest_design(proposer, puis appliquer la mise en page choisie) affichaient chacune un "Terminé en X s" générique, indiscernable l'une de l'autre. Message dédié à chaque transition (« N suggestion(s) prête(s) — sélectionnez... », « Application de la mise en page « X »… », ou « Suggestion de mise en page ignorée. » en cas d'annulation). - Retour utilisateur — « pour le sélecteur de taille il faudrait préciser entre parenthèses la taille pour mieux comprendre les différences entre chaque si on ne connaît pas la charte par cœur » : le combo Taille (panneau de propriétés, éditeur de tableau — réglage global et par cellule) affichait un simple nom de rôle technique ("title"/"body"...) sans aucun repère chiffré. Affiche désormais un libellé FR + la taille en points RÉSOLUE depuis la charte courante (ex. « Corps (16pt) »), recalculé à chaque changement de charte/projet — jamais figé sur la première charte rencontrée dans la session.
- +8 tests (extension de
tests/test_suggest_design_feature.py,tests/test_properties_panel_manual_size.py,tests/test_table_formatting.py).
Ajouté (2026-08-07, suggestions de mise en page IA — commande /suggest_design)
- Retour utilisateur — « je suis sur une slide avec une frise chronologique et je souhaite saisir une fonction genre "/suggest_design" [...] au lieu de réfléchir à générer une slide précise avec tout le contenu, tu me proposes plusieurs mises en forme possibles [...] tu me montres une vision des possibilités (avec des formes, pas besoin d'être ultra précis) et je sélectionne celle qui me plaît [...] tu l'utilises pour créer la vraie slide [...] accessible depuis un clic droit sur une slide [...] je ne pense pas que cette fonctionnalité doit être possible lorsque l'on est sur l'ensemble du deck » : nouvelle commande de chat
/suggest_design(autocomplétion "/", TOUJOURS bornée à la slide courante, jamais proposée/exécutable en portée "tout le deck") + entrée « Suggérer des mises en forme… » dans le menu contextuel de la bande de vignettes (masquée dès qu'une sélection multiple est active — une slide précise à la fois). Déroulement en 2 appels IA successifs : (1)services/design_suggest_service.py::propose_designsdemande au modèle 4 mises en page STRUCTURELLEMENT différentes pour représenter le contenu actuel de la slide (analysé via une capture écran) — jamais de texte réel, seulement des zones abstraites (rôle + position/taille en %) ; (2) chaque proposition est rendue en WIREFRAME (render/wireframe.py— rectangles gris avec libellé "Titre"/"Image"/"Texte"..., aucune couleur de charte, aucun contenu réel) et présentée dans une galerie de vignettes cliquables (ui/design_suggest_dialog.py) ; la mise en page choisie est traduite en instruction en langage naturel et réutilise le pipeline d'édition existant (services/edit_service.py::edit_by_prompt, même chemin qu'un prompt de chat classique — undo, aperçu diff, historique de versions inclus) pour produire la VRAIE diapositive avec le contenu réel et la charte actuelle. - +21 tests (nouveau fichier
tests/test_suggest_design_feature.py; nouveau fichiertests/test_slash_command_suggest_design.py— 5 ; extension detests/test_slash_command_agent.py/tests/test_slash_command_layout.pypour la 4e commande de 1er niveau).
Corrigé (2026-08-07, formes hexagonales + personnalisation de couleur — 2e fichier réel externe)
- Retour utilisateur — « lors de l'import de la slide 15 tu n'as pas su traduire toutes les formes hexagonales [...] tu as mis un type rectangle » : les tuiles hexagonales décoratives (
MSO_AUTO_SHAPE_TYPE.HEXAGON, motif "Designer" fréquent) n'avaient jusqu'ici aucune correspondance dans_AUTO_SHAPE_KINDet retombaient sur "rectangle" (repli par défaut) — nouveaushape_kind: "hexagon"reconnu à l'import, rendu à l'écran et à l'export PPTX (MSO_SHAPE.HEXAGON), et disponible manuellement (menu Insérer > Forme, panneau de propriétés). - Retour utilisateur — « on ne peut pas sélectionner le contour [...] j'imagine que c'est parce qu'il n'est pas possible encore de personnaliser une couleur, il faudrait ajouter cette option dans tous les objets » + « il n'est pas possible de voir la couleur à laquelle [un rôle de charte] se réfère [...] il faut mettre la couleur à côté pour prévisualiser » : les sélecteurs de couleur du panneau de propriétés et de l'éditeur de tableau (remplissage, contour, couleur du texte, fond de zone, couleur des puces, fond d'en-tête/cellules) étaient limités aux rôles fixes de la charte, sans aucun aperçu visuel du rôle (juste son nom en texte). Nouveau combo partagé (
ui/color_picker_combo.py, extrait du sélecteur déjà existant pour les séries de graphique et généralisé à TOUS les champs de couleur de l'application) : chaque rôle de charte porte désormais une pastille de couleur (remplissage plein ou contour selon l'usage, cf.ui/icons.py::fill_color_icon/line_color_icon), et une entrée « Personnalisée… » ouvre le sélecteur système (QColorDialog) pour n'importe quelle couleur hors charte — y compris pour une couleur déjà présente sur une cellule de tableau (posée par l'IA ou importée d'un pptx réel), jusqu'ici silencieusement perdue par l'ancien combo si elle ne correspondait à aucun rôle connu. - +29 tests (nouveau fichier
tests/test_color_picker_combo.py— 8 ; extension detests/test_pptx_importer.py/tests/test_roundtrip_pptx.py/tests/test_table_formatting.pypour l'hexagone et les combos).
Corrigé (2026-08-07, import PowerPoint — 2e fichier réel externe, écarts restants)
- Retour utilisateur — « on ne peut pas gérer de couleur de fond, mais seulement une image » :
<p:bg>(couleur unie, référencée au thème via<p:bgRef>, ou image plein cadre) n'était jusqu'ici JAMAIS lu à l'import — alors quedeck.schema.json::slide.backgroundet les DEUX moteurs de rendu le prennent déjà en charge de bout en bout (rendu écran, export, dialogue dédiéui/background_image_dialog.py) : seule l'EXTRACTION manquait, même situation que les graphiques embarqués la veille. Hérité diapo → mise en page → master (même priorité que PowerPoint) ; dégradé approximé par sa première couleur d'arrêt. Vérifié sur le fichier réel fourni : fond bleu marine uni (#0F253E) correctement repris sur toutes les diapositives concernées, jusqu'ici blanches. - Retour utilisateur — « pour le slide 4 [...] la mise en page est avec une forme que tu n'as jamais gérée [...] il faudrait l'ajouter, donner la possibilité à l'utilisateur de l'utiliser » : nouveau réglage Recadrage en forme (Aucune / Hexagone / Ellipse) sur les éléments image (
deck.schema.json::image.clip_shape), disponible pour TOUTE image (pas seulement celles importées) depuis le panneau de propriétés, et respecté par les deux moteurs de rendu (aperçu écran via unQPainterPathde découpe, export PPTX via un vrai<a:prstGeom>natif PowerPoint — pas une image pré-découpée, reste modifiable dans PowerPoint comme dans SlydeForge). L'import détecte désormais ce recadrage (<a:prstGeom>direct, ou hérité de la MISE EN PAGE pour un placeholder image sans géométrie propre — cas réel du fichier fourni, un cadre photo hexagonal "Designer" ; un<a:custGeom>personnalisé est traité comme un hexagone, best-effort, motif de très loin le plus fréquent pour ce genre de cadre). - Retour utilisateur — « dans la gestion actuelle de SlydeForge il y a systématiquement des séparateurs de colonne ou de ligne [...] il faut offrir cette option manuellement à l'utilisateur » : nouveaux réglages indépendants Lignes/Colonnes dans l'éditeur de tableau (
deck.schema.json::table.row_lines/col_lines, par défaut activés = comportement historique inchangé), pour reproduire un tableau "designer" qui n'a souvent AUCUNE grille de colonnes (juste des bandes de couleur alternées). L'import détecte désormais l'absence réelle de bordures (majorité de<a:lnL>/<a:lnR>/<a:lnT>/<a:lnB>explicitementnoFillsur les cellules du fichier source) plutôt que d'imposer systématiquement les 4 côtés — aucun signal explicite dans le fichier source (cas le plus courant, le style de tableau référencé dessine déjà sa propre grille) garde le comportement historique, jamais une supposition risquée. - Retour utilisateur — « sur la slide 7 [...] la mise en page perd en lisibilité par rapport à l'original » : root cause — un texte de placeholder SANS gras/couleur PROPRE à son run (cas normal, ces attributs vivent dans le
defRPrdu niveau de style hérité de la mise en page, ex. noms/fonctions en blanc gras sur un fond sombre) ne récupérait jusqu'ici que l'alignement/la taille (corrigés la veille) — jamais le gras ni la couleur, qui retombaient sur le noir non-gras par défaut de SlydeForge, PRESQUE INVISIBLE sur un fond sombre. Chaîne d'héritage (mise en page → master) complétée pour ces deux propriétés, avec la même discipline « l'explicite gagne toujours » que pour les cellules de tableau (un gras explicitement désactivé sur un run ne doit jamais être réactivé par l'héritage). - +19 tests (extension de
tests/test_pptx_importer.py— 13,tests/test_roundtrip_pptx.py— 4 ; nouveau fichiertests/test_properties_panel_clip_shape.py— 5 ; extension detests/test_table_formatting.py— 3).
Corrigé (2026-08-06, import PowerPoint — fidélité de reprise sur un fichier réel, retour utilisateur avec captures)
- Retour utilisateur — « c'est bien au milieu mais en hauteur pas bien positionné [...] à l'alignement vertical d'une zone de texte » + « des problèmes sur la taille de la police » + « je ne retrouve pas les zones de textes vides » : l'alignement vertical, la taille/police de caractères et l'alignement horizontal d'un texte importé retombaient sur des valeurs par défaut au lieu de l'héritage réel diapositive → layout → master (chaîne
<p:txStyles>/<a:lstStyle>par niveau, correspondance placeholder paridx/type) —render/pptx_importer.pyrésout désormais cette chaîne complète (_resolve_inherited_para_defaults,_resolve_inherited_anchor). Les placeholders titre/sous-titre/corps VIDES dans le fichier source (jamais remplis, ex. « Cliquez pour ajouter ») étaient jusqu'ici purement ignorés — ils deviennent des zones de texte vides (comme sur la diapositive 3 du fichier fourni), modifiables normalement au lieu de disparaître. - Retour utilisateur — « tu n'as pas su reprendre les graphiques [...] il existe un composant de type graphique mais est-ce qu'il est assez évolué ? » : les graphiques réels du fichier (
<c:chart>) étaient jusqu'ici toujours ignorés avec un avertissement — extraits désormais en éléments graphiques natifs SlydeForge (type, séries, catégories, couleurs), modifiables comme un graphique créé depuis SlydeForge. - Fond/logo/en-tête de LAYOUT manquants (formes non-placeholder du modèle de mise en page source, ex. bandeau/logo répété sur chaque diapositive d'une section) : jusqu'ici seules les formes de la diapositive elle-même étaient reprises — les formes du LAYOUT source (hors placeholders) sont désormais fusionnées en couche d'arrière-plan (mise en cache par layout, pour ne pas la recalculer à chaque diapositive).
- Tableaux (diapositive 12) : la couleur/le gras des cellules d'en-tête et de coin, absents du fichier importé, viennent maintenant du style de tableau réel (
tableStyles.xml, bandefirstRow) avec repli sur le format explicite de la cellule quand il existe — bug de priorité corrigé au passage (un gras EXPLICITEMENT désactivé sur une cellule ne doit jamais être réactivé par le style de bande). - Retour utilisateur — « icone barré, texte avec un lien + retour à la ligne + couleur du lien » (diapositive 15) + « cadre autour de rectangles, couleur des liens, couleur des bullets points » (diapositive 19) : les sauts de ligne manuels (
<a:br/>) à l'intérieur d'un paragraphe étaient perdus (le texte se retrouvait sur une seule ligne) ; les liens hypertexte (<a:hlinkClick>) n'étaient pas extraits (texte sans lien ni couleur d'accent) ; les bordures de forme (<a:ln>) et la couleur des puces (<a:buClr>) n'étaient jamais reprises. Les quatre sont désormais extraits et rendus à l'identique (écran et export PowerPoint). - Diapositives masquées : une diapositive marquée masquée dans PowerPoint (
<p:sld show="0">) apparaissait comme une diapositive normale après import. Prise en charge de bout en bout : import (hidden: truedans le deck), export (réécritshow="0"), bande de vignettes (icône 🚫 + bascule Afficher/Masquer dans le menu contextuel). - +~60 tests (extension de
tests/test_pptx_importer.py,tests/test_roundtrip_pptx.py; nouveau fichiertests/test_hidden_slides.py— 8).
Corrigé (2026-08-06, plantage à l'ajout/la modification d'un agent)
- Retour utilisateur — « lorsque j'ajoute ou modifie un agent l'application plante » (log :
QThread: Destroyed while thread '' is still running) : root cause générale, pas spécifique aux agents —ui/spellcheck_text_edit.py::SpellcheckTextEditlance unQThreadde vérification orthographique à chaque frappe (debounce), mais la fermeture d'un dialogue (done()/accept/reject) ne l'attendait jamais : si un thread était encore en vol au moment de fermer, Python libérait la dernière référence Python à l'objetQThreadpendant qu'il tournait encore côté C++ — plantage natif, non spécifique au dialogue Agents, qui se contentait d'être l'endroit où l'utilisateur retapait le plus vite (prompt long). Nouvelle méthodeshutdown(): arrête le minuteur de debounce, attend chaque thread en vol avec un délai borné, et conserve une référence Python aux threads qui n'ont pas fini à temps (_retired_workers) plutôt que de la laisser filer. Câblé dansdone()des 4 dialogues concernés (Agents, Génération, Analyse de charte, Éditeur de modèle de diapositive) +ChatPanel.shutdown_spellcheck(). - +~10 tests (extension de
tests/test_spellcheck_text_edit.py,tests/test_agent_dialog.py,tests/test_generate_dialog.py,tests/test_slide_template_editor_dialog.py,tests/test_theme_analyze_dialog.py).
Ajouté (2026-08-06, graphiques : rendu réel + configuration complète)
- Retour utilisateur — « on est loin de pouvoir faire des choses avec un rendu minimum [...] pas la possibilité de voir apparaitre les noms des séries ou les valeurs [...] j'ai beau changé le type, le graphique est toujours représenté pareil [...] un camembert [...] le design n'est pas le bon » : le rendu écran (
render/items.py::_paint_chart) ignorait jusqu'ici le type réel du graphique (tout rendu comme des barres, même un « pie ») et n'affichait ni légende ni valeurs. Réécriture complète par type (barres, barres horizontales, lignes, camembert), avec légende, étiquettes de valeurs et couleur de texte à contraste automatique (clair/sombre selon le fond) — miroir exact côté export PowerPoint (render/pptx_renderer.py::_render_chart), pour que l'aperçu écran corresponde au .pptx généré. - Retour utilisateur — « je veux pouvoir paramétrer si je veux voir les valeurs ou non [...] idem pour les noms de catégories [...] je ne peux toujours pas définir une couleur manuellement [...] bouton supprimer une série [...] sans icône [...] choisir la couleur du texte car le texte n'est pas lisible » : nouveau dialogue d'édition de graphique — cases à cocher indépendantes Valeurs/Catégories, sélecteur de couleur par série (rôles de charte + « Couleur personnalisée… » via la palette système), sélecteur de couleur de texte, icône de suppression explicite (corbeille rouge + infobulle) à la place du bouton « ✕ » muet. Nouveaux champs
show_values/show_category_labels/text_colordans le schéma, lus par les deux moteurs de rendu. - +~35 tests (extension de
tests/test_theme.py,tests/test_roundtrip_pptx.py,tests/test_inline_edit_and_dialogs.py).
Ajouté (2026-08-06, gestion en masse des projets + multi-sélection des diapositives)
- Retour utilisateur — « pouvoir sélectionner plusieurs projets et faire les actions Supprimer en masse [...] assigner un tag en masse [...] barre de progression [lors de la suppression] [...] sélectionner plusieurs slides dans la preview pour les déplacer/supprimer » : le gestionnaire de projets et la bande de vignettes passent en sélection multiple (Ctrl/Maj-clic). Suppression : confirmation unique listant tous les projets/diapositives visés, barre de progression pendant l'opération (seule/en masse). Tags : sur une sélection multiple, les tags cochés sont AJOUTÉS (union) à ceux déjà présents sur chacun des projets, jamais un remplacement qui effacerait les tags déjà posés individuellement. Diapositives : déplacement et suppression en masse depuis le menu contextuel de la bande.
- +~25 tests (nouveau fichier
tests/test_slide_multi_select_delete.py— 9 ; extension detests/test_project_manager.py).
Ajouté (2026-08-06, refonte du panneau de propriétés)
- Retour utilisateur — « revoir la mise en page pour que ça soit plus logique [...] plus moderne, lisible rapidement et pro » (capture jointe) : le groupe « Texte » (et par cohérence les autres types d'éléments) passe d'un empilement ad-hoc de lignes à une mise en page en formulaire avec sous-sections étiquetées (POLICE / PARAGRAPHE / ZONE DE TEXTE), lignes non pertinentes pour le type d'élément courant masquées plutôt que grisées. Nouveau contrôle d'alignement vertical (Haut/Milieu/Bas) — la propriété était déjà gérée par le moteur de rendu depuis la vague précédente mais restait sans commande dans l'UI.
- +5 tests (nouveau fichier
tests/test_properties_panel_vertical_align.py).
Corrigé (2026-08-06, lenteur du dialogue Projets à l'ouverture — performance)
- Retour utilisateur — « la popup pour ouvrir un projet est maintenant un peu longue à s'ouvrir [...] avec la multiplication du nombre de projets [...] je crains que ça ne soit plus gérable [...] une solution pérenne sans dégrader les fonctionnalités » : root cause —
list_projects_with_previews()désérialisait le deck JSON COMPLET (y compris toutes les images encodées en base64 de toutes les diapositives) et refaisait un rendu Qt de la 1re diapositive pour CHAQUE projet, à CHAQUE ouverture du dialogue, même pour un projet totalement inchangé depuis la dernière fois. Nouvelle tableproject_previews(miniature + nombre de diapositives déjà calculés,storage/db.py), invalidée par une clé bon marché — aucun JSON parsé pour vérifier la fraîcheur — dérivée de l'horodatage d'autosave ou de la version courante (storage/projects.py::_current_source_key) : un projet non modifié retourne directement sa miniature en cache, un projet neuf/modifié retombe sur le calcul complet une seule fois puis se met en cache pour la prochaine ouverture (ui/project_manager_dialog.py::_refresh+store_preview()). Solution durable au sens propre : le cache s'auto-invalide sur toute vraie modification (aucune fenêtre où une miniature périmée serait montrée), sans supprimer ni dégrader aucune fonctionnalité existante. Mesuré sur un jeu de 60 projets/10 diapositives avec images embarquées : 0,098s → 0,005s pour la seule désérialisation (≈20x), et jusqu'à ≈168x en incluant le rendu Qt de la miniature (12,4ms/projet) qui n'a plus lieu d'être une fois en cache. - +8 tests (
tests/test_project_previews.py, réécrit pour le nouveau cache).
Ajouté (2026-08-05, écran de démarrage + dupliquer un projet)
- Retour utilisateur — « afficher une icône de chargement moderne et pro [...] avec une barre de chargement [...] pouvoir fermer rapidement l'application s'il s'est trompé » : nouvel écran de démarrage (
ui/splash_screen.py), affiché dès la création deQApplication(avant les imports lourds de l'interface). Réutilise l'éclair animé déjà existant (ui/spinner_widget.py, même tracé/dégradé que l'icône applicative) plutôt qu'un visuel générique, avec une barre de progression déterminée par étape (données, interface, thème, fenêtre). Bouton ✕ + touche Échap ferment IMMÉDIATEMENT (app.py::mainrevérifiesplash.cancelledaprès chaque étape et s'arrête au plus tôt, sans terminer le travail restant). - Retour utilisateur — « pouvoir dupliquer un projet complet pour conserver une ancienne version et appliquer de nouveaux éléments » : nouveau bouton « Dupliquer » dans le gestionnaire de projets (
storage/projects.py::duplicate_project+ui/project_manager_dialog.py). Copie l'état ACTUEL dans un tout nouveau projet indépendant (l'original garde son historique intact ; le duplicata démarre sa propre timeline avec une seule version « Dupliqué depuis « X » ») — pas une copie de l'historique complet, cohérent avec le besoin exprimé (l'original reste la référence figée). Nommage automatique « (copie) » (déduplication déjà gérée par_unique_title), charte associée (theme_id) copiée. La nouvelle ligne est sélectionnée automatiquement après duplication. - +18 tests (nouveau fichier
tests/test_splash_screen.py; extension detests/test_project_manager.py). - Retour utilisateur — « ranger des projets par rubrique [...] système de tags multiple [...] avec système de couleurs [...] et surtout filtrer, car on a rapidement de plus en plus de projets » : nouveau système de tags multi-valués (
storage/tags.py, tabletags/project_tags,ON DELETE CASCADEdans les deux sens). Gestionnaire de projets : colonne Tags (pastilles colorées,ui/tag_dialog.py::tag_chip), bouton « Tags… » ouvrant un dialogue unique d'assignation ET de gestion (cocher/décocher, renommer ✎, recolorer, supprimer, créer à la volée — jamais un écran « gérer les tags » séparé, sans intérêt hors du contexte d'assignation). Nouvelle rangée de filtres (un bouton-bascule par tag, coloré à l'identique) au-dessus du tableau, combinable avec la recherche texte déjà existante ; sélectionner plusieurs tags resserre (intersection — les projets doivent porter TOUS les tags cochés), pas une union, cohérent avec le besoin de retrouver un contexte précis (ex. « Présentation COSYNC » + « Projet TIMELEC »). - +45 tests (nouveaux fichiers
tests/test_tags.py— 20,tests/test_tag_dialog.py— 14 ; extension detests/test_project_manager.py— 11). - Retour utilisateur — « mettre un système d'agent pour pouvoir reproduire des demandes récurrentes et modifier le contenu des slides [...] configurer des prompts d'agents récurrents et pouvoir les appeler depuis un projet avec les fonctions "/agent" + le nom de l'agent (liste de sélection avec pré-filtrage lors de la saisie dynamique) ou avec un appel par le menu où on aura configuré l'agent pour ceux qui ne savent pas utiliser les fonctions » : nouveaux agents configurables (
storage/agents.py— nom unique, description, prompt), gérés depuis un dialogue dédié (ui/agent_dialog.py, accessible via le nouveau menu Agents → Gérer les agents…). Deux chemins d'appel STRICTEMENT équivalents (le même agent est utilisable des deux façons) : (1) taper/agentdans le chat déclenche une liste de sélection avec pré-filtrage dynamique (même mécanisme que/layoutdéjà existant, cf.ui/chat_panel.py) ; (2) le menu Agents liste directement chaque agent configuré (reconstruit à chaque ouverture), pour les personnes qui n'utilisent pas les commandes tapées. Dans les deux cas, le prompt complet de l'agent est INSÉRÉ dans la saisie (jamais envoyé directement) — laisse la main pour relire/compléter avant d'envoyer, cohérent avec le comportement déjà établi de/layout. - +68 tests (nouveaux fichiers
tests/test_agents.py— 17,tests/test_agent_dialog.py— 16,tests/test_slash_command_agent.py— 9,tests/test_agents_menu.py— 6 ; extension detests/test_slash_command_layout.py— 1). - Retour utilisateur — « démarrer un projet depuis un fichier powerpoint existant [...] pour ensuite demander à l'IA par échange de l'aider à le modifier [...] avec fichier Excel à l'appui, à partir de données plus récentes » : nouveau Fichier → Nouveau depuis un fichier PowerPoint existant…, conversion structurelle best-effort d'un .pptx quelconque (pas forcément généré par SlydeForge) vers
deck.schema.json(render/pptx_importer.py— texte/mise en forme des runs, puces/numérotation réelles, images, tableaux, formes avec remplissage, position/taille exactes ; format 16:9/4:3 déduit des dimensions réelles de la diapo source). Un élément non pris en charge (graphique) est IGNORÉ avec un avertissement affiché dans le chat — jamais un import qui échoue entièrement pour un seul élément secondaire. Le fichier importé devient un NOUVEAU projet standard, immédiatement modifiable comme n'importe quel autre — la mise à jour assistée par IA depuis un fichier Excel demandée n'a nécessité AUCUN développement supplémentaire : le pipeline existant (joindre un .xlsx dans le chat + décrire la demande,generate/ingest.py) fonctionne déjà tel quel sur un deck importé. - +24 tests (nouveaux fichiers
tests/test_pptx_importer.py— 18,tests/test_main_window_pptx_import.py— 6).
Corrigé (2026-08-05, import PowerPoint — retour utilisateur avec fichier réel joint, mise en page mal reprise)
- Non proposé depuis l'écran de démarrage : le tout premier écran (« Comment souhaitez-vous commencer ? ») ne listait que brief IA/vierge/ouvrir un projet — souvent le tout premier geste pour reprendre un support existant. Nouveau bouton « Nouveau projet depuis un fichier PowerPoint existant… » dans
ui/startup_dialog.py, routé comme les 3 autres choix parMainWindow.run_startup_chooser(). - « Il n'arrive pas à reprendre correctement la mise en page » (fichier réel joint, miniatures blanches) : root cause — un GROUPE de formes pptx (icônes, légendes, pastilles de couleur…) était jusqu'ici ignoré ENTIÈREMENT, perdant tout son contenu même quand celui-ci était par ailleurs parfaitement représentable (rectangles, texte…).
render/pptx_importer.pyaplati désormais récursivement les groupes (transformation de coordonnées OOXMLoff/ext/chOff/chExtcorrectement recalculée, groupes imbriqués inclus) — seules les formes réellement irreproductibles à l'intérieur (contours vectoriels arbitraires de type FREEFORM, ex. le tracé d'une carte du monde dessinée à la main) restent ignorées, mais désormais signalées de façon groupée par diapositive (« Diapositive 8 : 261 formes ignorées (forme non représentable dans SlydeForge). ») plutôt que noyées dans un avertissement générique par forme — sur le fichier réel testé, une diapositive « Map » totalement vide passe ainsi de 0 à 11 éléments récupérés (4 pastilles-image + 4 étiquettes + titre). Les diapositives réellement vides dans le fichier source (placeholders « Cliquez pour ajouter » jamais remplis, ex. diapositives modèles d'une bibliothèque de mises en page) restent légitimement blanches — ce n'est pas un bug, il n'y a rien à en tirer. - Retour utilisateur — « le type de slide devrait être chargé depuis le fichier powerpoint d'origine » : chaque diapositive importée porte désormais
slide_type= nom du LAYOUT source (ex. « Section », « Text 1 », « Thank you »), visible dans la bande de vignettes (« Type : Section ») — corrige au passage un affichage trompeur (« Généré par l'IA » s'affichait par défaut pour toute diapositive sansslide_type, texte pensé à l'origine pour la génération IA, pas pour un import). - +7 tests (extension de
tests/test_pptx_importer.py— 3,tests/test_startup_chooser.py— 4).
[1.0.11] - 2026-08-05
Corrigé (2026-08-05, SlydeForge.exe qui ne lance rien — même bug qu'un lanceur .exe frère, ChargeCapa.exe)
- Retour utilisateur : « je n'arrive pas à utiliser le .exe, j'avais le même problème sur l'exe de ChargeCapa et tu viens de réussir à le fixer » — root cause identifiée sur ChargeCapa.exe et appliquée ici par analogie (même design de lanceur, cf.
packaging/launcher_src.py) : l'exe ATTENDAIT (subprocess.call, bloquant) queslydeforge.batse termine — orslydeforge.batse termine lui-même PAR DESIGN dès que le vrai travail (vérification des dépendances) est fait, en lançant l'appli réelle en tâche de fond (start "" /B) puis en quittant aussitôt après. Un exe qui attend ce process se termine donc lui aussi prématurément, avec le symptôme classique « une fenêtre DOS s'ouvre puis se ferme aussitôt, rien ne se lance ». Fix :subprocess.PopenDÉTACHÉ (fire-and-forget, jamais attendu,CREATE_NEW_CONSOLE) — reproduit exactement ce qui se passe lors d'un double-clic manuel surslydeforge.bat. Ajout d'un journallauncher.log(%LOCALAPPDATA%\SlydeForge\logs\), indépendant de tout le reste (aucune dépendance à l'appli/aux paquets Python) — jusqu'ici un échec à ce stade précis (avant même d'atteindreslydeforge.bat) ne laissait ABSOLUMENT AUCUNE trace nulle part. Vérifié empiriquement de bout en bout sur ce poste : l'exe rend la main immédiatement (code 0) ET l'application réelle (pythonw app.py) démarre bien en tâche de fond. - +9 tests (
tests/test_launcher_src.py, réécrit pour le nouveau design détaché + journal).
Corrigé (2026-08-05, 3 bugs réels sur les puces/numéros fraîchement livrés — retour utilisateur immédiat avec capture d'écran)
- « les numéros/puces sont tout petits » (capture) :
render/textfmt.py::_paragraph_html— le<span>du marqueur ne portait AUCUNE taille explicite (contrairement au texte, cf._run_html), retombant sur la taille par défaut du document plutôt que celle du texte qu'il précède. Résolue désormais depuis le 1er run du paragraphe (même police/taille que le début du texte). - « en mode édition, activer les bullets/numérotation ne les affiche pas » :
PROP_BULLET/PROP_LIST_STYLE(propriétés custom posées la veille) ne sont que des données INERTES pourQTextDocument— jamais un mécanisme d'affichage.render/richtext.py::build_documentattache désormais un VRAIQTextListnatif Qt (marqueur + indentation dessinés par Qt lui-même) en plus de ces propriétés custom, qui restent la seule source de vérité relue parextract_paragraphs. Numérotation ininterrompue entre paragraphes consécutifs de même niveau/style (même liste Qt partagée, comme le compteur du rendu statique) ; interrompue par un paragraphe hors-liste ou un changement de style (nouvelle liste, repart de 1) — vérifié par 6 tests dédiés qui inspectent directementblock.textList(). - « on ne voit pas ce qui est actif au niveau de l'alignement, bullets... » (plus général que juste les 2 nouveaux boutons) : bug réel plus profond trouvé en creusant, confirmé par un diff de pixels avant/après bascule (0 pixel changé,
:checkedou non) — le fond/la bordure de la mini-barre flottante (ui/rich_text_toolbar.py) étaient posés via un sélecteur QSSQWidget { ... }SANS PORTÉE, qui s'appliquait aussi à CHAQUE bouton descendant (unQPushButtonEST unQWidget) et écrasait silencieusement le style:checked(fond/bordure teintés) de la feuille globale — pour TOUS les boutons de cette barre (Gras/Italique/Souligné/Alignement/Puces/Numéros), pas seulement les 2 nouveaux.QWidget#RichTextToolbarRootrestreint désormais la règle au seul fond de la barre. 4 nouveaux tests par diff de pixels (fond/bordure uniquement, jamais de texte — indépendant des polices système absentes du bac à sable de test). - +12 tests (
tests/test_bullet_and_numbered_lists.py,tests/test_rich_text_toolbar.py).
Ajouté (2026-08-05, insérer une slide par clic droit + puces/numéros dans le menu flottant + mise en forme des tableaux)
- Retour utilisateur — « pouvoir insérer un nouveau slide directement depuis un clic droit depuis les vignettes, ce qui permet de savoir où l'insérer (position) » : le menu contextuel de la bande de vignettes propose désormais « Insérer une slide avant »/« Insérer une slide après » (position exacte, réutilise le choix Vierge/type de charte déjà existant) ; un clic droit sur la zone vide sous la dernière vignette propose « Ajouter une slide » (fin du tableau).
MainWindow._prompt_add_slideaccepte désormais un index d'insertion explicite (None= fin, comportement du bouton « + Slide » inchangé). - Retour utilisateur — « je ne vois pas où tu as ajouté la gestion des bullets points / numérotation [...] je pensais que tu allais le faire depuis le menu flottant qui permet de gérer la police, la taille, l'alignement » : ajouté EN PLUS du panneau de propriétés (livré la veille) — 2 nouveaux boutons Puces/Numéros dans la mini-barre flottante de l'éditeur de texte inline (
ui/rich_text_toolbar.py), à côté de l'alignement, avec icônes dessinées à la main (ui/icons.py, chiffres 1/2/3 façon afficheur 7 segments pour rester distincts des puces sans dépendre d'une police système). Bascule à 3 états au clic (aucune liste par défaut, jamais une exclusion figée façon groupe de boutons classique — recliquer le bouton déjà actif redésactive la liste). Nouvelles propriétés de BLOCPROP_BULLET/PROP_LIST_STYLEdansrender/richtext.py, qui SUIVENT le paragraphe à travers une scission/fusion pendant l'édition (Entrée/Retour arrière) — contrairement à l'ancien report par position d'index (fragile), vérifié par un test dédié qui scinde un paragraphe numéroté en 2 et confirme que les deux moitiés restent numérotées. - Retour utilisateur — « dans les tableaux, il ne gère pas bien la mise en forme ex taille de la police [...] revoir cette fonctionnalité pour que ce soit plus pro et que la gestion des tableaux soit complète » : 3 bugs réels trouvés en creusant, tous corrigés. (1)
ui/table_edit_dialog.py: le combo Taille n'avait AUCUN « (inchangé) » — la taille de TOUTES les cellules était réécrite à CHAQUE clic sur OK, même sans y toucher. (2)render/items.py::_paint_table(aperçu écran) : la mise en forme de cellule (taille/police/couleur/gras/italique/alignement) était intégralement IGNORÉE — texte peint avec la police par défaut du widget, alignement centré forcé quel que soitcell["align"]. (3)render/pptx_renderer.py::_render_table(export) : même trou,cell.text = "..."ne pose aucun style. Les 3 sont corrigés (résolution des tokens de charte +geometry.px_per_ptpour l'échelle écran, comme un textbox). Gras/Italique/Alignement n'avaient d'ailleurs aucun moyen d'être réglés depuis l'UI — nouveaux contrôles ajoutés. Et, retour utilisateur suite à une question de clarification — « alignement/mise en forme par cellule individuelle » explicitement demandé : nouveau panneau « Cellule(s) sélectionnée(s) » dans le dialogue, distinct des réglages globaux « Tout le tableau » — sélectionner une ou plusieurs cellules dans la grille édite directement LEUR style (fond/couleur/taille/alignement/gras/italique), porté parQTableWidgetItem.setData(Qt le déplace automatiquement avec la cellule lors d'un +/− Ligne/Colonne, sans bookkeeping manuel d'index). - +46 tests (nouveau fichier
tests/test_table_formatting.py— 29 ; extensions detests/test_add_slide_chooser.py/tests/test_rich_text_toolbar.py/tests/test_canvas_rich_inline_edit.py/tests/test_icons.py).
[1.0.10] - 2026-08-05
Ajouté (2026-08-05, ajouter une slide selon un layout + puces/numéros)
- Retour utilisateur — « comment ajouter manuellement une slide ? [...] pouvoir sélectionner une slide vierge ou selon les layout dispo dans la charte choisie » : la fonction existait déjà en 2 morceaux disjoints — le bouton « + Slide » de la bande de vignettes (toujours une slide vierge, sans choix) et un item de menu Insérer séparé, « Slide depuis un modèle de type… » (jamais relié au bouton). Le bouton « + Slide » propose désormais un choix explicite « (Slide vierge) » + un par modèle de type déjà enregistré pour la charte courante (
MainWindow._prompt_add_slide), sans dialogue superflu si la charte n'a aucun modèle (cas le plus courant). Réutilisecore/deck.py::duplicate_slidepour l'instanciation (ids toujours régénérés) — factorisé avec l'ancien code dupliqué de « Slide depuis un modèle de type… », qui reste disponible tel quel dans le menu Insérer. - Retour utilisateur — « dans l'édition du texte il faut pouvoir ajouter des bullets points ou des numéros facilement » :
paragraphs[].bullet(niveau de puce) existait déjà dans le schéma mais n'était modifiable QUE par l'IA (JSON) — aucun contrôle UI. Nouveau combo « Liste » (Aucune / Puces / Numéros) dans le panneau de propriétés (zone de texte), uniforme sur toute la zone comme Taille/Alignement ; préserve un niveau d'indentation déjà imbriqué (2-5, ex. posé par l'IA) tant que seul le style change. Nouveau champlist_style("bullet"|"number") dans le schéma. Bug latent découvert et corrigé en creusant l'export :bulletne posait jusqu'ici qu'un niveau d'indentation OOXML (p.level) côté PPTX, JAMAIS de vrai marqueur — un textboxadd_textbox()n'hérite d'aucun style de liste de master, donc AUCUNE puce ne s'affichait réellement dans PowerPoint à l'export, malgré l'illusion donnée par le rendu écran (caractère peint à la main).render/pptx_renderer.pyinjecte désormais un vrai<a:buChar>/<a:buAutoNum type="arabicPeriod"/>/<a:buNone>selon le cas. Numérotation à l'écran calculée par niveau (compteurs indépendants, style liste imbriquée Word/PowerPoint : « 1. / 1a. / 1b. / 2. » plutôt que redémarrer dès qu'un niveau différent s'intercale) dansrender/textfmt.py. Préservée pendant une édition inline sur le canevas (render/richtext.py, même discipline que le niveau de puce déjà existant). - +31 tests (nouveaux fichiers
tests/test_add_slide_chooser.py,tests/test_bullet_and_numbered_lists.py).
[1.0.9] - 2026-08-04
Ajouté (2026-08-04, dupliquer une slide + image non déformée + raccourci épinglable)
- Retour utilisateur — dupliquer une slide : nouvelle entrée « Dupliquer » dans le menu contextuel de la bande de vignettes, à côté de « Renommer »/« Supprimer ».
core/deck.py::duplicate_sliderégénère l'id de la slide ET l'id/group de chaque élément (même discipline queduplicate_elements), mais — contrairement au copier/coller d'éléments — garde les positions D'ORIGINE : c'est la slide entière qui est dupliquée, pas des éléments réinsérés qui se superposeraient sinon à l'identique. La copie est insérée juste après l'originale, sélectionnée automatiquement, annulable (Ctrl+Z). - Retour utilisateur — insertion d'images : ajouter l'option pour ne pas déformer l'image : 2 correctifs complémentaires. (1) Le rect par défaut d'une image tout juste insérée était un CARRÉ fixe (25,25,50,50), déformant visuellement toute photo non carrée tant que personne n'ajustait la taille à la main —
ui/asset_thumbnail.py::default_image_rectcalcule désormais une boîte aux VRAIES proportions de l'image (tient dans le même encombrement max 50×50%, centrée). (2) Nouvelle case à cocher « Conserver les proportions au redimensionnement » dans le panneau de propriétés (image), cochée par défaut à l'insertion — verrouille le ratio largeur/hauteur pendant un glisser de poignée sur le canevas (render/items.py::ElementItem._do_resize), coin OU bord, en gardant le point ancré opposé fixe (même convention que « Verrouiller les proportions » dans PowerPoint/Word). Nouveau champlock_aspect(booléen, image uniquement) dans le schéma. - Retour utilisateur — impossible d'épingler le raccourci à la barre des tâches : root cause — Windows refuse silencieusement d'épingler tout raccourci
.lnkdont la cible est un SCRIPT (.bat/.cmd/.ps1), quelle que soit son icône ; seule une cible.exeest épinglable. Le raccourci créé parmake_shortcut.ps1pointait jusqu'ici versslydeforge.bat, alors queSlydeForge.exe(déjà construit et fourni dans chaque release depuis 1.0.7, cf.packaging/build_launcher_exe.py) existait justement pour ça. Fix : le raccourci pointe désormais versSlydeForge.exequand il est présent (repli sur le.batsinon, ex lancement depuis les sources).slydeforge.batinvoque désormaismake_shortcut.ps1à CHAQUE lancement (plus seulement si le raccourci est absent) : le script détecte et corrige lui-même un raccourci déjà existant mais encore pointé vers l'ancienne cible.bat(installs mises à jour depuis une version antérieure) — aucune suppression manuelle du.lnknécessaire. - +20 tests (nouveau fichier
tests/test_slide_duplicate_and_image_lock_aspect.py). 1304 tests passent en suite complète (2 runs consécutifs, ~186-191s chacun, hors les 2 tests spinner), aucune régression.