Se connecter Accueil
Fonctionnalités
L'IA FAQ
Bibliothèques
Télécharger
Journal des modifications

Ce qui a changé, en direct du code

Le contenu ci-dessous n'est pas une page marketing : c'est le fichier CHANGELOG.md réellement livré dans le zip téléchargeable, lu automatiquement à chaque affichage — jamais ressaisi à la main, donc jamais désynchronisé.

Version actuelle : v1.4.0 Télécharger cette version

[1.0.3] - 2026-07-24
Corrigé (2026-07-24, vignette live périmée au clic + image de fond manquante des types de slide)

- Bug réel corrigé — cliquer sur une vignette pendant l'aperçu live de
génération montrait encore l'ANCIENNE slide
, pas celle tout juste
générée : MainWindow._on_slide_selected rechargeait toujours depuis
self.deck (le deck RÉEL, pas encore mis à jour tant que la génération
n'a pas abouti/été acceptée). Nouveau snapshot _live_preview_slides
(tenu à jour par _show_live_slides), consulté en priorité par
_on_slide_selected pendant l'aperçu live — cliquer une vignette déjà
apparue affiche désormais bien la slide correspondante, en lecture seule
comme le reste de l'aperçu.
- Bug réel corrigé (captures d'écran) — l'image de fond d'un type de slide
de charte (ex bandeau d'en-tête illustré) disparaissait à la génération/
édition
, alors que couleurs/icônes/mise en forme survivaient bien. Root
cause : le fond d'un modèle (souvent une image volumineuse en base64) était
confié au LLM pour reproduction dans SA PROPRE réponse — peu fiable pour
une longue chaîne opaque (même famille de limite déjà contournée pour les
images d'ÉLÉMENTS via 'asset_ref', résolu par le code). Nouveau
core/deck.py::apply_slide_type_background réapplique désormais le fond
du modèle PROGRAMMATIQUEMENT après coup, jamais depuis ce que le LLM a
produit — câblé dans generate/slide_gen.py::_normalize (génération) ET
core/patch.py::apply_patch (les 2 pipelines d'édition, un seul point de
câblage). Prompts allégés en conséquence (generate/prompts.py) : le LLM
n'a plus à recopier /background du tout, réduisant aussi la taille de
réponse attendue (aide indirectement la limite de tokens).
- 936 tests pytest passent (2 runs complets consécutifs, ~108-119s chacun,
hors les 2 tests spinner). Enrichis : tests/test_live_preview_canvas.py
(+2), nouveau fichier tests/test_slide_type_background_fidelity.py (11).

Corrigé (2026-07-24, popup "/layout" bloquait la saisie — bug réel remonté juste après livraison)

- Bug réel corrigé — le popup "/layout" empêchait de continuer à taper
("je suis bloqué sur le menu contextuel et je ne peux pas continuer à
écrire") : root cause — Qt.Popup (utilisé pour le popup) GRABBE TOUT le
clavier au niveau de la distribution d'évènements de QApplication dès
qu'il est affiché, comportement Qt DOCUMENTÉ ("grabs all keyboard and
mouse input until closed") qui s'applique MÊME avec setFocusPolicy(Qt.
NoFocus)
sur le widget (ce réglage n'a aucune influence sur ce grab —
fausse piste initiale). Plus aucune touche, y compris les lettres,
n'atteignait le champ de texte tant que le popup était affiché.
- Fix : ui/slash_command_popup.py::SlashCommandPopup utilise désormais
Qt.ToolTip (jamais de grab clavier, une info-bulle ne devant jamais
intercepter quoi que ce soit) — le clavier continue d'aller normalement au
champ de texte, la liste se filtre bien EN CONTINU pendant la frappe et
disparaît dès qu'il n'y a plus de correspondance, exactement le
comportement demandé. Contrepartie assumée : Qt.ToolTip ne se referme
plus tout seul au clic extérieur (propriété propre à Qt.Popup) —
ChatPanel le referme désormais explicitement sur perte de focus du champ
de texte.
- Piège de test découvert en creusant : les tests existants (appelant
eventFilter directement) ne pouvaient PAS détecter ce bug — le grab de
Qt.Popup intervient AVANT que le code applicatif ne voie l'évènement,
invisible à un appel de méthode direct. Seul un test utilisant de VRAIS
évènements clavier distribués par Qt (QTest.keyClicks) l'exerce
réellement — ajouté en conséquence.
- 923 tests pytest passent (2 runs complets consécutifs, ~106-107s chacun,
hors les 2 tests spinner). tests/test_slash_command_layout.py (+3).

Ajouté (2026-07-24, référence rapide à un layout de charte via "/layout")

- Nouveau popup de suggestions dans le champ de prompt du chat, déclenché
par « / »
(retour utilisateur) : taper / propose la commande layout
; la sélectionner (ou taper /layout directement) affiche la liste des
types de slides personnalisés de la charte active (ex « Frise
chronologique »), filtrable en continuant de taper. Choisir un layout
insère son nom EXACT dans le prompt — élimine tout risque de faute de
frappe que l'IA ne reconnaîtrait pas (root cause de plusieurs correctifs
précédents sur ce même sujet). Navigation clavier complète (↑/↓/Entrée/
Échap) sans jamais voler le focus du champ de texte, fermeture automatique
au clic extérieur.
- Nouveau widget générique réutilisable ui/slash_command_popup.py::
SlashCommandPopup
.
- ChatPanel.set_layout_choices(items) — même pattern déjà en place pour
set_theme_choices — alimenté par MainWindow._refresh_chat_theme_
choices
(mêmes points de rafraîchissement : projet ouvert, charte
changée…), en utilisant la version LA PLUS À JOUR de la bibliothèque de
chartes (même correctif de fraîcheur que pour le contexte IA — un type
tout juste ajouté à la charte apparaît immédiatement, sans réapplication
au deck).
- 920 tests pytest passent (2 runs complets consécutifs, ~106-107s chacun,
hors les 2 tests spinner). Nouveau fichier tests/test_slash_command_
layout.py
(22 tests : widget popup, détection "/"/"/layout", filtrage,
insertion, navigation clavier réelle, alimentation depuis la charte avec
fraîcheur bibliothèque).

Ajouté (2026-07-24, numéro de page configurable dans la charte)

- Nouvelle section « Numéro de page » dans l'éditeur de charte (retour
utilisateur) : activation, position (6 choix — haut/bas × gauche/centre/
droite), gabarit d'affichage ({n}/{total}), taille et couleur. Posé
automatiquement par le renderer sur CHAQUE slide (écran ET export PPTX),
au même titre que le logo — jamais un élément généré par le LLM
(render/branding.py::page_number_anchor/format_page_number, même
principe que logo_anchor déjà en place). Désactivé par défaut : aucune
charte existante n'affiche soudainement un numéro sans action explicite.
Nouveau champ schéma theme.page_number (deck.schema.json).
- Câblé sur les surfaces où l'utilisateur voit réellement une slide : canevas
d'édition principal, bande de vignettes, capture utilisée pour l'édition
IA en portée "Cette slide", aperçu live de génération, export PPTX
(render/scene_builder.py, render/pptx_renderer.py, render/capture.py,
ui/canvas_view.py, ui/main_window.py, ui/slide_strip.py). L'aperçu de
l'éditeur de charte utilise des valeurs de démonstration (page 3/8) pour
montrer concrètement le style choisi. Volontairement PAS câblé sur les
aperçus hors deck réel (modèle de type de slide, dialogue de fond
d'image) — un numéro de page n'a pas de sens hors d'un deck complet.
- 898 tests pytest passent (2 runs complets consécutifs, ~110s chacun, hors
les 2 tests spinner). Nouveaux fichiers : tests/test_theme_editor_page_
number.py
(7), tests/test_page_number_wiring.py (4) ; enrichi :
tests/test_branding.py (+12).

Ajouté (2026-07-24, auto-fermeture temporisée de la barre de retour)

- La demande de retour ("Ce résultat vous convient ?") se referme
désormais automatiquement après 3 minutes si l'utilisateur n'a RIEN
commencé à y répondre
(retour utilisateur) — jusqu'ici elle ne se
refermait que manuellement ou au lancement d'un nouveau prompt, et pouvait
rester affichée indéfiniment si l'utilisateur ne relançait rien. Ne se
ferme JAMAIS si un début de réponse existe déjà
(note 👍/👎 cliquée et/ou
commentaire déjà tapé, cf. ChatPanel._auto_dismiss_feedback_if_untouched)
— condition explicitement demandée. Un minuteur à usage unique (redémarré à
chaque nouvelle demande de retour) porte ce délai ; le bouton "Ignorer"
passe désormais par dismiss_feedback() (au lieu d'un .hide() direct)
pour arrêter proprement ce minuteur au passage.
- 876 tests pytest passent (2 runs complets consécutifs, ~100-101s chacun,
hors les 2 tests spinner). tests/test_processing_ui.py (+9).

[1.0.2] - 2026-07-24
Corrigé (2026-07-24, troncature systématique — limite de tokens trop juste pour un layout riche)

- Bug réel corrigé, diagnostiqué cette fois grâce à la réponse brute
effectivement journalisée (fix précédent)
: la restructuration de la
slide 2 vers le layout "Frise chronologique" tronquait systématiquement,
aux 2 tentatives, en plein milieu du tableau elements — confirmant que
16000 tokens (ai/settings.py::DEFAULT_MAX_TOKENS) est réellement
insuffisant pour reproduire fidèlement un modèle de charte riche en
éléments (bandeau d'en-tête + plusieurs blocs de phase), pas un simple
problème de mise en forme du JSON. Relevé à 24000 (3e augmentation de ce
réglage dans l'historique du projet, même symptôme à chaque fois : un
patch légitimement volumineux qui dépasse la limite).
- Consigne assouplie en complément : generate/prompts.py::
_slide_type_templates_block
autorise désormais explicitement à condenser
les éléments purement décoratifs/répétitifs du modèle plutôt que d'en
exiger une reproduction exhaustive ("mieux vaut une réponse compacte et
complète qu'une réplique exhaustive tronquée"), avec ids courts et JSON
sans espaces superflus dès la 1re tentative (pas seulement à la relance).
- services/edit_service.py::_retry_hint couvrait jusqu'ici UNIQUEMENT
le cas "ajouter plusieurs nouvelles slides" — totalement hors-sujet pour
une troncature sur la restructuration d'UNE slide déjà existante. Complété
avec une guidance dédiée à ce second cas.
- 868 tests pytest passent (2 runs complets consécutifs, ~100s chacun, hors
les 2 tests spinner). Enrichis : tests/test_edit_service.py (+4).

Corrigé (2026-07-24, "Patch inapplicable : can't replace a non-existent object 'background'")

- Bug réel corrigé — dernier maillon de la série sur l'application d'un
layout de charte nommé ("Frise chronologique") : une fois le parsing JSON
corrigé (fix précédent), le patch échouait à l'étape suivante avec "Patch
inapplicable : can't replace a non-existent object 'background'". Root
cause dans MON PROPRE exemple de prompt (_slide_type_templates_block,
ajouté 2 fixes plus tôt) : RFC 6902 exige qu'une opération "replace"
cible un chemin qui existe DÉJÀ — or background et slide_type sont des
champs OPTIONNELS du schéma (deck.schema.json, absents d'une slide qui
hérite du défaut), et cette slide précise n'avait ni l'un ni l'autre.
Corrigé à 2 niveaux : le prompt utilise désormais "op":"add" (qui, sur un
membre d'objet, fonctionne QUE le champ existe déjà ou non — RFC 6902
§4.1) ; ET, en filet de sécurité indépendant du prompt, core/patch.py
convertit désormais automatiquement tout "replace" ciblant un membre
d'objet absent en "add" avant application — jamais pour un index de
TABLEAU (sémantiques réellement différentes là, volontairement exclu).
- 864 tests pytest passent (2 runs complets consécutifs, ~106s chacun, hors
les 2 tests spinner). Enrichis : tests/test_patch.py (+3), tests/
test_edit_service.py
(+1, reproduction bout-en-bout du cas réel signalé).

Corrigé (2026-07-24, "réponse IA pas exploitable" sur une restructuration de layout)

- **Bug réel corrigé — core/jsonutil.py::parse_json échouait dès qu'un bref
préambule (SANS balise ``fence`) précédait un JSON par ailleurs
valide** (ex : "Voici le patch demandé :\n[...]") : la fonction n'essayait
d'extraire un JSON que si le texte, une fois nettoyé, COMMENÇAIT
littéralement par
{/[ — un mode d'échec devenu bien plus probable une
fois la nouvelle instruction de restructuration complète de layout ajoutée
(tâche plus complexe → un modèle a plus tendance à préfacer sa réponse d'une
phrase malgré la consigne "réponds UNIQUEMENT le JSON"). Reproduit
directement (sans backend IA) et corrigé : le crochet ouvrant PERTINENT
(objet ou tableau) est désormais celui trouvé le PLUS TÔT n'importe où dans
le texte (pas nécessairement au tout premier caractère), en préservant la
détection de troncature existante (patch coupé par la limite de tokens).
- **Diagnostic renforcé** : la réponse brute de l'IA est désormais journalisée
(tronquée à 2000 caractères, niveau WARNING) dans
services/edit_service.py
à chaque échec de parsing — jusqu'ici invisible en dehors du panneau
"Détails" du chat (perdu une fois l'échange refermé), rendant tout
diagnostic a posteriori impossible sans avoir pensé à le capturer en temps
réel (constaté lors de ce même échange avec l'utilisateur).
- **Consigne renforcée en complément** :
generate/prompts.py::
_slide_type_templates_block inclut désormais un exemple JSON CONCRET du
patch à 2-3 opérations attendu pour une restructuration complète de layout
(remplacer
background+elements+slide_type), plutôt qu'une simple
description en prose — réduit le risque qu'un modèle dérive vers une
réponse plus libre/explicative sur cette tâche plus complexe que les
éditions habituelles.
- 860 tests pytest passent (2 runs complets consécutifs, ~99-101s chacun,
hors les 2 tests spinner). Enrichis :
tests/test_patch.py (+3, tolérance
du préambule non fenced + non-régression troncature),
tests/test_edit_
service.py` (+4, tolérance bout-en-bout + journalisation de la réponse
brute, portées slide ET deck).

Corrigé (2026-07-24, catalogue de types de slide périmé — root cause du bug de layout nommé précédent)

- Bug réel corrigé (captures d'écran) — un type de slide personnalisé
("Frise chronologique") était ABSENT à la 1re ouverture de l'éditeur de
charte, puis apparaissait après un aller-retour vers une autre charte dans
le même dialogue
: root cause identifiée par l'utilisateur lui-même —
MainWindow._theme() renvoie le thème EMBARQUÉ DANS LE DECK, un instantané
figé au moment de sa dernière application, qui se périme dès que la charte
est modifiée/enregistrée dans la BIBLIOTHÈQUE (storage/themes_store.py)
sans être réappliquée à ce deck précis (ThemeEditor._on_combo_changed
recharge lui bien depuis la bibliothèque à chaque changement — d'où la
disparition/réapparition observée). C'est aussi la cause profonde du
correctif précédent qui restait insuffisant
: injecter le catalogue des
types dans le prompt d'édition ne servait à rien tant que le thème source
lui-même était périmé — l'IA continuait à répondre que le type "n'existe
pas dans la charte fournie" même après ce premier fix. Corrigé avec 2
nouvelles méthodes MainWindow : _library_theme_or_current(theme_id)
(utilisée par _edit_theme pour ouvrir l'éditeur avec la version À JOUR de
la bibliothèque) et _refresh_slide_types_catalog(deck) (rafraîchit
UNIQUEMENT slide_types — jamais palette/fonts, qui doivent rester
fidèles au rendu réel — sur le clone de travail utilisé pour chaque appel
IA dans _on_prompt).
- 853 tests pytest passent (2 runs complets consécutifs, ~104-109s chacun,
hors les 2 tests spinner). Nouveau fichier tests/test_stale_slide_type_
catalog.py
(9 tests, dont un bout-en-bout via _on_prompt reproduisant le
cas réel signalé).

Corrigé (2026-07-24, édition "Cette slide" bloquée sur un layout nommé + portée d'édition invisible)

- Bug réel corrigé — impossible d'appliquer un layout de charte nommé
explicitement (« Frise chronologique ») à une slide existante
: rejeté ou
ignoré à plusieurs reprises. Root cause en 2 parties : (1) le prompt
d'édition d'UNE slide (generate/prompts.py::system_edit_patch) ne
transmettait JAMAIS le catalogue des types de slides personnalisés de la
charte (contrairement à la génération et à l'édition "tout le deck", qui
l'avaient déjà) — l'IA n'avait donc aucun moyen de savoir qu'un tel layout
existait ; (2) même une fois le nom communiqué manuellement par
l'utilisateur, l'IA n'avait accès qu'au nom, jamais à la STRUCTURE concrète
du modèle (contrairement à la génération, qui reçoit le JSON complet du
template assigné par le plan) — elle ne pouvait donc que deviner. Fix :
nouveau prompts._slide_type_templates_block(theme) injecte le JSON
complet de CHAQUE type personnalisé ayant un modèle défini, avec instruction
explicite de restructurer entièrement background/elements (pas une
modification mineure) quand la demande nomme un type existant — câblé dans
system_edit_patch (uniquement si l'édition n'est PAS déjà contrainte à un
seul élément, incompatible avec une restructuration complète) et
system_edit_patch_deck.
- Bug réel corrigé — une édition "Cette slide" semblait cibler/toucher le
mauvais périmètre sans explication
: MainWindow._on_prompt passait
silencieusement element_id = self._selected_id à edit_service.
edit_by_prompt
dès qu'un élément était sélectionné dans le canevas — même
un simple clic ANTÉRIEUR au prompt, sans rapport avec la demande tapée —
restreignant l'IA à CE SEUL élément (system_edit_patch, "CONTRAINTE
FORTE"), sans que rien dans le fil du chat ne le signale. Une demande
portant sur toute la slide (changer de mise en page) ne pouvait alors
produire qu'un patch minime, à tort perçu comme un refus/bug de l'IA. Le
bandeau "Portée : slide N" mentionne désormais explicitement l'élément
sélectionné quand une contrainte est active.
- 844 tests pytest passent (2 runs complets consécutifs, ~102s chacun, hors
les 2 tests spinner déjà documentés comme intermittents).

Ajouté (2026-07-24, aperçu live de la génération + alignement de texte + auto-ignorer le retour utilisateur)

- Aperçu live de la construction d'un deck (retour utilisateur : « voir
les modifications/ajout sur les slides en live... pour voir instantanément
sa construction ») : pendant une génération multi-slides (« Nouveau depuis
un brief » et « repartir de zéro » en portée « tout le deck »), chaque
slide générée s'affiche immédiatement dans le canevas et la bande de
vignettes (en lecture seule — ce n'est pas encore le deck réel), au lieu
d'attendre la fin de tout le job pour voir quoi que ce soit. Implémenté via
ui/workers.py::LivePreviewRelay, un relais Qt SÉPARÉ du Worker (dont la
signature job(emit_chunk, emit_phase) est utilisée par des dizaines
d'appelants et ne doit pas changer) — instancié sur le thread GUI, émis
depuis le thread de fond, livré automatiquement en file par Qt. Le canevas/
la bande repassent sur le deck RÉEL (MainWindow._restore_canvas_after_
live_preview
) si la génération échoue, est annulée, ou si son résultat est
rejeté dans l'aperçu diff.
- Alignement du texte (gauche/centre/droite/justifié) géré comme
police/taille/couleur
(retour utilisateur) : 4 nouveaux boutons
mutuellement exclusifs dans la mini-barre de mise en forme riche
(ui/rich_text_toolbar.py, nouveau signal alignment_changed), nouvelles
icônes vectorielles (ui/icons.py). Contrairement à police/taille/couleur/
gras/italique/souligné (mise en forme de CARACTÈRE, exige une sélection),
l'alignement est une propriété de PARAGRAPHE : s'applique au paragraphe
sous le simple curseur, sans sélection nécessaire (convention Word/
PowerPoint), via CanvasView._apply_rich_format. Le panneau de propriétés
(ui/properties_panel.py) ne réécrit plus l'alignement PAR PARAGRAPHE au
moindre autre réglage touché (même correctif déjà appliqué à la taille
manuelle) — seul le contrôle Alignement (ou « Appliquer ») l'uniformise
explicitement.
- La demande de retour (« Ce résultat vous convient ? ») encore affichée
est désormais ignorée automatiquement dès qu'un nouveau prompt est lancé

(retour utilisateur), au lieu de rester affichée indéfiniment en arrière-
plan : ui/chat_panel.py::ChatPanel.dismiss_feedback(), appelé en tout
premier dans MainWindow._on_prompt.
- 836 tests pytest passent (2 runs complets consécutifs, ~95-101s chacun,
hors les 2 tests spinner déjà documentés comme intermittents pour une
raison sans rapport).

[1.0.1] - 2026-07-24
Corrigé (2026-07-24, ordre de l'onboarding + logs plus verbeux + confirmation avant d'arrêter un traitement IA)

- Bug réel corrigé — l'accueil de premier lancement s'affichait APRÈS avoir
déjà forcé le choix créer/ouvrir un projet
: ordre illogique (demander
d'agir avant d'avoir expliqué comment l'IA fonctionne). Inversé dans
app.py::main() : l'onboarding vient désormais D'ABORD, le sélecteur de
projet ensuite.
- Logs plus verbeux, avec rotation journalière et purge automatique à
30 jours
: logging_conf.py passe d'une rotation par TAILLE (1 Mo,
5 fichiers — pouvait perdre toute la journée d'un incident si l'activité
précédant le crash avait déjà rempli le quota) à une rotation JOURNALIÈRE
(TimedRotatingFileHandler, backupCount=30 : purge automatique native,
aucune logique de purge à écrire à la main). app.py::main() journalise
désormais chaque étape risquée du démarrage (imports, création de
QApplication/fenêtre, choix du projet…) — même si un crash échappe à tous
les filets de sécurité, la dernière ligne du log indique déjà où chercher.
ui/workers.py::Worker.run() journalise aussi le début/la fin de CHAQUE
opération de fond (génération, édition IA, device flow, analyse de
charte…) : une seule classe partagée par toute l'appli, donc une seule
modification donne de la visibilité à toutes ces opérations.
- Confirmation demandée avant d'arrêter un traitement IA en cours
(retour utilisateur) : un clic accidentel sur Stop interrompait
immédiatement, perdant silencieusement un travail parfois déjà bien
avancé. Ajoutée aux 3 endroits de l'appli où un traitement IA peut être
arrêté : le chat principal (ui/chat_panel.py), l'analyse de charte
(ui/theme_analysis_progress_dialog.py) et l'assistant IA du dialogue
« Modèle de mise en forme de la slide » (ui/slide_template_editor_dialog.py).
- 812 tests pytest passent (2 runs complets consécutifs, ~90s chacun, hors
les 2 tests spinner déjà documentés comme intermittents pour une raison
sans rapport).

[1.0.0] - 2026-07-24
Corrigé (2026-07-24, dialogue de mise à jour trop grand + fiabilité du lancement + onboarding IA)

- Bug réel corrigé (capture d'écran) — le dialogue de mise à jour grandissait
au point de cacher le bouton de validation
: l'ancien QMessageBox.
question
affichait TOUTES les notes de version sans la moindre zone de
défilement, poussant les boutons Oui/Non hors de l'écran sur une longue
liste de changements. Remplacé par ui/update_dialog.py::
UpdateConfirmDialog
, hauteur fixe + zone de notes défilante.
- État d'avancement pendant l'application de la mise à jour (jusque-là
aucun — « impression que rien ne s'est fait ») : services/update_service.
py::apply_update_from_zip
accepte désormais un callback on_progress,
appelé après chaque fichier écrit, câblé sur une barre de progression.
- Redémarrage automatique proposé en fin de mise à jour (jusque-là un
simple message demandant de relancer soi-même) : MainWindow.
_relaunch_application
relance slydeforge.bat (repli sur l'interpréteur
Python courant si absent) puis ferme proprement la fenêtre (autosave +
arrêt des workers déjà gérés par closeEvent).
- Vérification des dépendances à CHAQUE lancement (slydeforge.bat),
retour utilisateur (l'appli ne se relançait plus après une mise à jour,
sans aucune trace de log) : le marqueur .deps_ok_<version> (qui pouvait
rester périmé — désinstallation partielle, plusieurs interpréteurs Python
sur le poste…) est retiré ; un simple import (rapide, largement sous la
seconde) revérifie désormais les dépendances à chaque démarrage, la
réinstallation complète via pip ne se déclenchant que si cet import échoue.
- Filet de log d'urgence (app.py::_emergency_log) pour tout crash trop
précoce pour que logging_conf/crash_reporting aient pu s'installer
(ex. si prefs.data_dir() lui-même échoue) — écrit dans %TEMP%\
slydeforge_emergency.log
(jamais dépendant de %APPDATA%, potentiellement
la cause elle-même du problème). N'explique pas avec certitude le cas
rapporté (non reproduit dans cette session), mais garantit qu'une prochaine
occurrence laisse au moins une trace exploitable.
- Accueil de premier lancement (ui/onboarding_dialog.py, services/
onboarding.py
) : si l'IA n'est pas configurée au tout premier démarrage,
explique comment ça fonctionne (3 backends, connexion GitHub par device
code — ouverture navigateur + code à reporter) plutôt que de laisser
découvrir seul un outil dont l'essentiel de la valeur dépend d'un backend
configuré. Ne réapparaît plus jamais automatiquement après cette première
fois (peu importe le choix fait), mais reste accessible à tout moment
depuis Aide > Configuration initiale de l'IA…. Cas particulier : réseau
Socomec détecté (variable d'environnement Windows USERDNSDOMAIN, jamais
d'appel réseau) → bouton dédié pré-remplissant directement ui/
settings_dialog.py::SettingsDialog
avec l'hôte/modèle GHE de l'entreprise
(SettingsDialog.prefill_ghe, nouveau, réutilisable).
- 787 tests pytest passent (2 runs complets consécutifs, ~93s chacun, hors
les 2 tests spinner déjà documentés comme intermittents pour une raison
sans rapport).

[0.1.5] - 2026-07-24
Corrigé (2026-07-24, flèches de spinbox toujours illisibles + débordement résiduel de l'éditeur inline)

- Bug réel corrigé (capture d'écran) — les flèches +/- restaient de simples
rectangles bleus pleins, MÊME après avoir forcé le style Fusion
(fix
précédent, cf. entrée du 0.1.4) : root cause plus profonde que prévu —
Fusion dessine en réalité tout le contrôle (cadre + boutons + flèches) en
un seul bloc natif (drawComplexControl), sans jamais déléguer les flèches
à un point d'interception QSS séparé, rendant le classique triangle
"CSS pur" fragile/imprévisible selon la plateforme, quel que soit le style
appliqué. Fix définitif et robuste, cohérent avec QPushButton#ZoomButton
(déjà abandonné le rendu natif douteux pour un glyphe peint nous-mêmes) :
nouveau ui/spin_widgets.py::DoubleSpinBox/IntSpinBox — le contrôle
natif est peint normalement (cadre/fond/texte, toujours régis par le QSS),
PUIS deux triangles nets sont dessinés PAR-DESSUS en vecteur (QPainter) au
bon endroit. Le triangle CSS existant est masqué (width/height: 0) plutôt
que supprimé. Utilisés désormais PARTOUT dans l'appli à la place de
QDoubleSpinBox/QSpinBox bruts (6 sites : panneau de propriétés ×2,
paramètres IA, éditeur de charte, image de fond, mini-barre de texte riche)
— un seul fix, appliqué une fois, visible partout. Vérifié visuellement
(export PNG à 3×) : triangles pleins, nets, bien positionnés.
- Bug réel corrigé (captures d'écran) — texte encore partiellement invisible
en édition inline malgré la correction de taille de police
(0.1.4) : un
léger écart de métriques entre le rendu du canevas (QGraphicsScene) et
l'éditeur flottant (widget Qt classique) est en pratique inévitable — marge
interne de document, bordure, barre de défilement absentes du rendu réel.
Plutôt que de chasser ce dernier écart au pixel près (fragile, dépendant de
la police/du DPI réels, invérifiables en sandbox de développement), l'éditeur
grandit désormais TOUT SEUL, en hauteur uniquement, pour toujours montrer
l'intégralité de son contenu (jamais besoin d'agrandir la zone soi-même) —
recalculé à chaque frappe (CanvasView._fit_inline_editor_height, connecté
à textChanged), la mini-barre de mise en forme se repositionnant en
conséquence pour ne jamais chevaucher l'éditeur agrandi. Marge de document
réduite, bordure allégée (2px→1px) et barres de défilement masquées en
complément (espace non consommé inutilement, réduit la fréquence où
l'agrandissement automatique est même nécessaire).
- 759 tests pytest passent (2 runs complets consécutifs, ~87s chacun, hors les
2 tests spinner déjà documentés comme intermittents pour une raison sans
rapport).

[0.1.4] - 2026-07-24
Corrigé (2026-07-24, texte "ultra zoomé" + pinceau sans taille de police + Annuler/Rétablir manquant)

- Bug réel corrigé (capture d'écran) — édition inline « ultra zoomée »,
texte hors-cadre au point de devoir agrandir la zone avant de pouvoir
modifier
: root cause — l'éditeur flottant (_InlineEditor) définissait
la taille de police via QTextCharFormat.setFontPointSize avec la valeur
BRUTE du schéma (ex 40pt), interprétée par Qt comme une taille typographique
RÉELLE indépendante du zoom du canevas — alors que le texte affiché DANS le
canevas (QGraphicsScene) est lui automatiquement mis à l'échelle par le
zoom courant (CanvasView._zoom) ET par le facteur px-de-scène-par-point
du canvas logique 1920×1080 (geometry.px_per_pt, ≈2×). Résultat : à un
zoom "ajusté à la fenêtre" typique (30-50%), le texte de l'éditeur flottant
s'affichait 2 à 6× trop grand par rapport à sa taille réelle dans le
canevas. Fix : render/richtext.py::build_document/_char_format prennent
un paramètre scale (= px_per_pt(fmt) × zoom courant, calculé à la volée
par CanvasView._text_scale()) appliqué à l'affichage — jamais aux
propriétés custom stockées (qui restent la valeur LOGIQUE, non mise à
l'échelle, donc round-trip JSON inchangé). Vérifié quantitativement (script)
et par round-trip : un titre 40pt affiché ~26px réels dans une boîte de
72px de haut à zoom 33% (au lieu de déborder), et size_ref reste intact
après validation.
- Bug réel corrigé — le pinceau ne semblait pas copier la taille de la
police
: root cause trouvée en creusant — CE N'ÉTAIT PAS core.deck.
copy_style
(vérifié isolément : copie bien size_pt/size_ref) mais le
PANNEAU DE PROPRIÉTÉS, dont le combo Taille ne connaît que 5 préréglages
nommés et n'avait AUCUNE représentation d'une taille MANUELLE (size_pt,
posée via la nouvelle mini-barre de texte riche ou copiée par le pinceau)
— il l'affichait à tort comme "body" (repli silencieux), ET pire, la
RÉÉCRIVAIT en size_ref="body" au moindre AUTRE réglage touché dans ce
panneau (gras, couleur…), effaçant la taille fraîchement peinte. Fix
ui/properties_panel.py : item informatif _SIZE_CUSTOM (ajouté/retiré
dynamiquement, jamais un choix permanent) affiché quand size_pt est
présent ; _emit_change ne réécrit désormais la taille QUE si le contrôle
Taille lui-même (ou "Appliquer" explicite) est à l'origine du changement —
tout autre réglage préserve le champ de taille existant du run (size_pt OU
size_ref, priorité à size_pt comme core/theme.py::resolve_size_pt).
Reproduit et vérifié de bout en bout (pinceau → panneau de propriétés).
- Annuler/Rétablir ajoutés dans « Modèle de mise en forme de la slide »
(retour utilisateur — jusque-là volontairement omis, jugé "objet isolé").
Réutilise core.commands.CommandStack tel quel sur un état
{background, elements} générique (non persistant, remis à zéro à chaque
(ré)ouverture du dialogue — même portée que MainWindow, qui ne persiste pas
non plus son historique entre deux projets). Boutons + raccourcis
Ctrl+Z/Ctrl+Y dans la toolbar, disponibles aussi depuis le menu contextuel
(clic droit). Coalescé par élément pour le déplacement/redimensionnement et
l'édition de propriétés, comme dans MainWindow.
- 751 tests pytest passent (2 runs complets consécutifs, ~90s chacun, hors
les 2 tests spinner déjà documentés comme intermittents pour une raison
sans rapport).

[0.1.3] - 2026-07-24
Corrigé (2026-07-24, mini barre de mise en forme du texte illisible)

- Bug réel corrigé (capture d'écran) — boutons Gras/Italique/Souligné
totalement VIDES et champ de taille tronqué
dans la mini barre de mise en
forme (ui/rich_text_toolbar.py, apparue lors de l'ajout du texte riche
par sélection ci-dessous) : root cause des boutons vides — le padding QSS
générique des QPushButton (6px 14px) écrasait presque tout l'espace
d'un bouton fixé à 26×26px (même piège déjà rencontré et corrigé sur le
bouton Zoom) ; root cause du champ de taille tronqué — 56px de large, trop
étroit pour afficher 2 chiffres ET les flèches +/- (qui réservent déjà 20px,
cf. QSS des spinbox). Fixes : objectName dédié RichTextToolButton
(padding réduit) + champ de taille élargi à 72px. Les lettres G/I/S
(ambiguës, dépendantes du rendu de police) sont remplacées par de VRAIES
icônes vectorielles (ui/icons.py::bold/italic/underline, jamais de texte)
et un tooltip explicite sur CHAQUE contrôle (police/taille/gras/italique/
souligné/couleur). Le bouton couleur (simple carré plein auparavant, pas
clairement identifiable) devient une icône dédiée « A » + barre colorée
(ui/icons.py::text_color_icon, convention Word/PowerPoint), elle aussi
tracée en vecteur — jamais de rendu de police pour une icône, cohérent
avec les 8 icônes toolbar déjà en place. Rendu vérifié visuellement
(export PNG à 3×, zoom) avant/après avant de considérer le correctif fini.
- 732 tests pytest passent (suite complète hors les 2 tests déjà documentés
comme intermittents pour une raison sans rapport, cf. entrée précédente).