Files
Forge-Engine/docs/plan/AUDIT_MISE_EN_FORME.md
T
williamandClaude Sonnet 5 9ad50c58b8 Audit complet de mise en forme — Image (2e élément du tableau)
Implémente toutes les options manquantes identifiées pour l'élément
Image : dimensionnement/ratio/object-fit, filtres CSS, upload de
fichier (en plus de l'URL), lien/plein écran au clic, chargement
différé, légende, et tous les attributs de boîte partagés déjà créés
pour Titre/Paragraphe (padding/margin/fond/bordure/ombre/opacité/
position du bloc).

Système de pages : un support peut désormais avoir 0 page (un nouveau
support démarre vide), suppression de toutes les pages en un clic, et
la pagination automatique insère intelligemment la nouvelle page juste
après celle qui déborde plutôt qu'en toute fin de liste.

Bugs réels trouvés et corrigés en cours de route : le style de bloc
(dont align-self) ciblait l'élément interne au lieu de son enveloppe
(légende/lien) ; une image à sa taille native pouvait déclencher une
pagination infinie ; upload/mise à jour d'attribut ne déclenchaient
jamais le contrôle de débordement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-26 12:06:44 +02:00

20 KiB

Audit des options de mise en forme — état des lieux et suivi

Document de travail (retour utilisateur du 26/09/2026) : liste tout ce qui manque en CSS, élément par élément, et sert de suivi pendant l'implémentation (un élément à la fois, dans l'ordre du tableau — implémentation → test utilisateur → validation → audit final → commit → élément suivant).

Méthode de travail (rappel)

  1. Un élément du tableau à la fois, dans l'ordre.
  2. J'implémente tout ce qui manque pour cet élément.
  3. Je préviens l'utilisateur, qui teste et valide/invalide.
  4. Dernier audit sur l'élément pour vérifier que rien n'est oublié.
  5. Si tout est ok : commit, puis élément suivant.

Règle transversale (retour utilisateur) : jamais de champ de texte libre pour une valeur CSS — uniquement des boutons, des curseurs, des listes déroulantes, des sélecteurs de couleur natifs. Les valeurs numériques optionnelles utilisent un curseur + une case "activer" (un <input type="range"> ne peut pas représenter "aucune valeur").

Tableau d'audit (état au 26/09/2026, avant implémentation)

Élément Mise en forme possible aujourd'hui Ce qui manque (audit CSS complet)
Titre / Paragraphe (titre/paragraphe) Gras, italique, souligné, couleur du texte, alignement horizontal du texte, largeur maximale Typo : barré, surligné (overline), police de caractère, taille de police (actuellement figée par le style titre1/titre2/paragraphe/légende), espacement des lettres/mots, hauteur de ligne, transformation (majuscules/minuscules/capitales), indentation de la 1ʳᵉ ligne, ombre portée du texte, retour à la ligne (white-space), troncature avec "…" (text-overflow), sens d'écriture (RTL)
Boîte : padding, margin (y compris négatif/auto pour centrer), hauteur, min/max-height, min-width
Bordure/ombre : style (pointillé/tireté/double…), épaisseur, couleur, par côté, arrondi par coin, ombre portée du bloc (box-shadow)
Fond : couleur de fond, dégradé, image de fond, opacité du bloc
Position/affichage : position du bloc dans son conteneur (pas juste le texte dedans), overflow, z-index
Effets : transition/animation au survol, curseur, filtre (flou, contraste…)
Responsive : aucune valeur ne peut différer entre Bureau/Tablette/Mobile
Image (image) URL, texte alternatif, SVG inline Dimensionnement : largeur/hauteur explicites, min/max, ratio (aspect-ratio), object-fit (cover/contain/fill), object-position
Bordure/ombre : style/couleur/épaisseur/par côté, arrondi par coin (actuellement fixe à 10px, non réglable), ombre portée
Effets : filtre CSS (niveaux de gris, sépia, luminosité, flou), mix-blend-mode, découpe (clip-path, ex. cercle/hexagone), overlay couleur/dégradé au survol
Comportement : lien cliquable, ouverture en plein écran/lightbox au clic, chargement différé (loading=lazy), légende (caption) affichée sous l'image
Position : alignement horizontal dans son conteneur, margin, padding autour
Responsive : image différente ou recadrage différent par taille d'écran
Bouton (bouton) Texte, cible (URL/ancre), fichier joint Typo : police, taille, gras/italique, transformation (majuscules), espacement des lettres
Boîte : padding, margin, largeur/hauteur, alignement du bloc
Bordure/ombre : style/couleur/épaisseur/par côté, arrondi par coin, ombre portée
Fond : couleur, dégradé, image
États interactifs : styles distincts survol/actif/désactivé (aucune notion d'état n'existe)
Icône : aucune icône à côté du texte du bouton (contrairement au badge)
Effets : transition au survol, curseur
Liste à puces / numérotée (liste_puces/liste_numerotee) Contenu des éléments uniquement Typo : tout (police, taille, gras/italique/souligné, couleur, interligne)
Puces/numéros : style de puce (list-style-type), image de puce personnalisée, position (intérieure/extérieure), couleur/taille des puces indépendante du texte
Boîte : padding/margin globaux ET par élément de liste, espacement entre éléments réglable, indentation
Bordure/fond : par élément de liste ou sur la liste entière
Listes imbriquées : aucune notion de sous-liste
Étiquette (badge) Icône SVG, largeur, arrondi (uniforme), gras, majuscules Typo : italique, souligné, barré, police, taille de police, couleur du texte, espacement des lettres
Boîte : hauteur, padding, margin, min/max-width
Bordure : style (actuellement toujours plein), couleur, épaisseur, par côté (demandé explicitement), arrondi par coin (actuellement un seul rayon pour les 4 coins)
Fond : couleur (actuellement figée par le thème), dégradé
Ombre : box-shadow
Position de l'icône : avant/après le texte, taille de l'icône réglable
Carte (carte) Contenu uniquement (repère/titre/description) Tout : typo, boîte, bordure, fond, ombre, dimensionnement — rien n'est réglable
Rangée (row, conteneur flex) gap, justify-content, align-items Boîte : padding, margin, largeur/hauteur explicites avec poignées de redimensionnement
Bordure/fond/ombre : rien
Flex avancé : flex-wrap (retour à la ligne), flex-direction (actuellement toujours en ligne, jamais en colonne), align-content, ordre des enfants, flex-grow/flex-basis par enfant (chacun a la même part aujourd'hui)
Position : overflow, position (sticky/absolute), z-index
Responsive : passage automatique en colonne sous un seuil de largeur
Mini-jeux (quiz/association/memory/mots/scenario/zones) theme_color (accent) + contenu propre à chaque jeu Tout le reste : bordure, fond, dimensions, police, espacement, ombre — entièrement figés par le CSS du thème, aucun réglage par instance

Catégories transversales oubliées, valables pour tous les éléments : opacity (transparence du bloc), cursor, transitions/animations CSS, styles conditionnels par état (survol/focus/actif/désactivé), et le responsive (aucun réglage ne peut varier entre Bureau/Tablette/Mobile alors que ce sélecteur existe déjà dans l'éditeur).

Suivi par élément

1. Titre / Paragraphe — ✅ audité et validé (commité)

Implémenté : gras/italique/souligné/barré, majuscules/minuscules/ capitales, police (liste déroulante de polices web-safe), taille de police, hauteur de ligne, espacement des lettres, ombre du texte (3 préréglages), couleur du texte (sélecteur natif), largeur maximale, padding, margin, couleur de fond (+ bouton "Transparent"), arrondi des bords, hauteur/hauteur min/hauteur max/largeur min, ombre portée du bloc (3 préréglages), opacité, position du bloc (gauche/centré/droite/ pleine largeur), bordure par côté (style/épaisseur/couleur, indépendants sur les 4 côtés).

Mécanisme partagé créé pour l'occasion : document_engine/rendering/ box_style.py (render_box_style, default_border, BOX_DEFAULTS) — réutilisé tel quel par tous les éléments suivants du tableau.

Volontairement laissé de côté (rarement utile pour du contenu de formation, à ajouter si besoin) : surlignage (overline), indentation de 1ʳᵉ ligne, white-space/troncature "…", sens d'écriture RTL, dégradé/ image de fond, overflow/z-index/position absolue, transitions au survol, réglages différents par taille d'écran (responsive), arrondi par coin (un seul rayon pour les 4 coins, comme l'étiquette).

Changement rétroactif (au moment de l'Image, ci-dessous) : width (largeur fixe) a été ajouté à box_style.py partagé — Titre/Paragraphe gagne donc aussi ce réglage a posteriori (en plus de la largeur MAXIMALE déjà là), sans repasser par une validation dédiée puisque c'est un ajout pur (aucun comportement existant modifié).

Bugs transversaux trouvés et corrigés pendant ce chantier (concernent TOUT l'éditeur, pas seulement Titre/Paragraphe) :

  • Panneau Propriétés jamais reconstruit après un clic (boutons/segments ne reflétaient leur nouvel état qu'après rechargement de la page) — corrigé dans forgeDocUpdateAttributes, point d'entrée unique de toute mise à jour d'attribut.
  • Champs .docField input (padding/bordure/fond/largeur 100%) hérités à tort par les cases à cocher et curseurs, cassant leur apparence native.

2. Image — ✅ audité et validé (commité)

Implémenté : légende (caption, enveloppe dans <figure>/<figcaption>), ajustement dans son cadre (object_fit : taille réelle/couvrir/ contenir/étirer), format/ratio (aspect_ratio : libre/carré/4:3/16:9), filtre (3 préréglages : noir et blanc/sépia/flouté), chargement différé (loading="lazy"), comportement au clic mutuellement exclusif (aucun / lien externe dans un nouvel onglet / plein écran — un vrai overlay plein écran côté client, forgeDocOpenImageLightbox), tous les attributs de boîte partagés (dont la nouvelle largeur fixe, ajoutée à box_style.py à cette occasion).

Comme pour les mini-jeux et la pièce jointe d'un bouton, le lien et le plein écran ne sont réellement cliquables qu'en Mode Aperçu — en édition, le clic sélectionne l'élément (la navigation native du lien est bloquée pour ne pas quitter l'éditeur par accident).

Ajout (retour utilisateur : "il manque la possibilité d'uploader une image") : téléversement d'un fichier depuis l'ordinateur (PNG/JPG/ GIF/WEBP/SVG), en plus du champ URL externe déjà là — mirroir exact du mécanisme déjà en place pour la pièce jointe d'un bouton : routes/document/document_element_upload_image.py (stocke sous uploads/, jamais attachments/ — pas de as_attachment, l'image doit s'afficher, pas se télécharger) et routes/document/ document_uploaded_file.py (route de service dédiée). Le fichier téléversé vide svg_markup au passage (les deux modes ne coexistent jamais). Le panneau propose maintenant les deux : sélecteur de fichier (natif, pas de champ texte) en premier, champ URL externe en second pour une image déjà hébergée ailleurs.

Volontairement laissé de côté (complexité/valeur douteuse pour du contenu de formation) : mix-blend-mode, découpe clip-path, overlay au survol, arrondi par coin, réglages responsive par taille d'écran.

Bug pré-existant corrigé au passage : docImageSrc/docImageAlt n'étaient pas échappés avant insertion dans l'attribut value du panneau Propriétés (mineur, mais corrigé puisque cette fonction était déjà réécrite).

Bug réel corrigé (retour utilisateur : "la position de bloc ne fonctionne pas sur l'image") : render_box_style (padding/margin/ fond/bordure/largeur/position du bloc, dont align_self) était appliqué à l'<img>/<div> INTERNE, jamais à son enveloppe (<figure>/<a class="docImageLink">/<div class= "docImageLightboxTrigger">) quand une légende ou un comportement au clic en ajoutait une — align-self posé sur un simple descendant du flex-item n'a aucun effet CSS, d'où le bouton "position du bloc" sans effet visible dès qu'une légende ou un lien était configuré. Corrigé dans _render_image (document_engine/rendering/ render_document_element.py) : le style de bloc cible désormais toujours l'élément réellement top-niveau (enfant direct de .docPageContent), quel que soit l'emboîtement. Changement de comportement réel sur du contenu existant : une image avec légende/lien et un fond/une bordure/un padding déjà réglés les verra désormais appliqués à TOUT le bloc (image + légende), pas seulement à l'image — c'est le comportement correct/attendu, mais je le signale car ça change le rendu visuel d'éléments déjà créés.

Bug réel corrigé (retour utilisateur : "quand j'ajoute une image ça crée des pages à l'infini") : une photo importée à sa taille native pouvait dépasser une page entière à elle seule ; la pagination automatique (forgeDocCheckPageOverflow, static/document/js/ document-editor.js) la déplaçait alors sans fin vers une page neuve, qui débordait identiquement. Corrigé une première fois par un garde-fou générique (si tout le contenu de la page déborde déjà, rien à répartir, on arrête) + une hauteur maximale par défaut sur img.docImage (plafonnée à la hauteur intérieure de la page, static/document/document-editor.css).

Affiné ensuite (retour utilisateur : "si l'image uploadée est trop grande je préfère qu'elle soit redimensionnée plutôt que bouger sur une autre page") : forgeDocCheckPageOverflow (static/document/js/ document-editor.js) évite de paginer une image qui déborde quand il reste assez de place pour un résultat encore utilisable — elle est rétrécie SUR PLACE (max-height posé en style inline, calculé à partir de l'espace réellement disponible sous elle sur SA page actuelle, légende comprise) à chaque rafraîchissement du canevas. Purement visuel, jamais persisté en attribut (l'espace disponible dépend du contenu au-dessus, qui change en éditant). Un réglage explicite de hauteur maximale via le panneau reste prioritaire (une valeur inline posée par un attribut serait recalculée par-dessus à l'affichage suivant si elle déborde encore). Le garde-fou anti-boucle et le défaut CSS restent en place pour les AUTRES kinds et comme filet de sécurité au tout premier rendu (avant que le JS n'ait tourné).

Changement de conception (retour utilisateur : "ce cadre ne devrait pas changer de taille en fonction de la taille de l'image mais être fixe et contraindre l'image dedans") : plutôt que de rétrécir dynamiquement une image trop grande selon l'espace disponible (approche fragile, source des deux bugs ci-dessus), un nouvel élément image a maintenant un cadre FIXE dès sa création — object_fit="cover" + height="220px" par défaut au lieu de vides (document_engine/labels/ element_kind_labels.py) — une photo importée est donc TOUJOURS rognée pour remplir ce cadre, quelle que soit sa résolution native. "Taille réelle" reste un choix explicite possible dans le panneau (segmented "Ajustement dans son cadre"). Ne s'applique qu'aux NOUVEAUX éléments image — un élément déjà créé avant ce changement garde ses attributs object_fit/height existants (jamais re-migré automatiquement, sanitize_element_attributes ne touche pas au kind "image", voir sa docstring) ; pour en faire bénéficier une image déjà présente, régler manuellement "Couvrir" + une hauteur via le panneau. Le rétrécissement dynamique (garde-fou anti-boucle + forgeDocCheckPageOverflow appelé après upload/mise à jour d'attribut) reste en place comme filet de sécurité pour une hauteur explicite déraisonnablement grande.

Bug réel corrigé une seconde fois (retour utilisateur : "ce n'est pas redimensionner") : le rétrécissement ci-dessus vit dans forgeDocCheckPageOverflow, appelée uniquement par forgeDocRefreshCanvas (ajout/déplacement/suppression/Annuler- Rétablir) — mais téléverser un fichier dans une image existante (forgeDocApiUploadImage) et changer un attribut quelconque (forgeDocUpdateAttributes, le point d'entrée central de tous les panneaux) patchent chacun le DOM directement, SANS jamais passer par ce chemin : le rétrécissement ne se déclenchait donc jamais après un upload. Corrigé en appelant explicitement forgeDocCheckPageOverflow() à la fin de ces deux fonctions. Pas de test automatisé possible ici : aucune suite de tests n'existe pour document-editor.js (package.json ne couvre que static/game/js/), vérification manuelle uniquement.

Affiné une troisième fois (retour utilisateur : "l'ajout de page quand le contenu déborde dois rester et même être plus intelligent, la nouvelle page dois etre sous la page qui deborde meme si ya d'autre page") : la pagination automatique (pour tout kind, pas seulement les images désormais épargnées ci-dessus) insérait toujours la page neuve en toute fin de la bande d'onglets (add_document_page l'ajoute toujours à la fin), même si d'autres pages existaient déjà après celle qui déborde — déborder sur la page 2 d'un support qui en compte 5 ajoutait la nouvelle page en position 6 au lieu de 3. Corrigé dans forgeDocCheckPageOverflow : la page est créée puis immédiatement déplacée (forgeDocApiPageMove, mécanisme déjà existant pour le glisser-déposer du panneau Pages) juste après la page active, décalant les pages suivantes d'un cran. Extraction d'un helper partagé forgeDocReorderLocalPage (état client après un déplacement) réutilisé par le glisser-déposer ET par ce nouveau cas, pour ne pas dupliquer ce calcul. Vérification côté serveur déjà couverte par les tests existants de move_document_page/document_page_move (l'insertion "au milieu" de la bande d'onglets y est déjà testée) ; le déclenchement côté client reste manuel faute de suite de tests JS.

Affiné une quatrième fois : sous un seuil de place restante (FORGE_DOC_MIN_IMAGE_HEIGHT, 60px) il n'y a plus de place RÉELLE sur la page (pas seulement pour cette image) — l'image bascule alors dans la pagination normale ci-dessus au lieu d'être rétrécie à une taille inutilisable, ce qui empêchait sinon une page pleine d'images d'enchaîner sur une nouvelle page. Vérifié par un test de bout en bout en conditions réelles (navigateur automatisé Playwright contre le serveur local, sur un support jetable créé puis supprimé pour l'occasion) : 5 images vides ajoutées à la suite produisent bien 3 pages, chacune avec le cadre fixe "Couvrir" actif par défaut.

Audit final : chaque ligne du tableau d'audit initial pour l'élément Image est couverte — implémentée (dimensionnement/ratio/ object-fit, bordure+ombre via box_style.py, filtre, lien/lightbox/ lazy-load/légende, position du bloc dont l'alignement, upload de fichier) ou explicitement différée ci-dessus avec sa raison (object-position, mix-blend-mode, clip-path, overlay au survol, arrondi par coin, responsive par taille d'écran — mêmes exclusions que Titre/Paragraphe, valeur douteuse pour du contenu de formation). Rien d'oublié constaté à cette relecture. Validé par l'utilisateur, prêt à committer.

3. Bouton — à faire

4. Liste à puces / numérotée — à faire

5. Étiquette (badge) — à compléter

Déjà fait (session précédente) : icône SVG, largeur, arrondi uniforme, gras, majuscules. Restant selon le tableau ci-dessus : italique, souligné, barré, police, taille de police, couleur du texte, espacement des lettres, hauteur, padding, margin, bordure par côté, ombre portée, position de l'icône. Pourra réutiliser directement box_style.py.

6. Carte — à faire

7. Rangée (row) — à faire

8. Mini-jeux — à faire

Décisions transversales actées pendant ce chantier

  • Un seul rayon d'arrondi pour les 4 coins (pas par coin) — cohérent avec l'étiquette déjà en place, à revoir si demandé explicitement.
  • Padding/margin : une valeur UNIFORME sur les 4 côtés (curseur unique), pas par côté — la bordure, elle, reste par côté (demande explicite).
  • Ombres (texte et bloc) : 3 préréglages (Aucune/Légère/Marquée) plutôt que des curseurs séparés offset/flou/couleur — reste simple à utiliser, ajustable si besoin de plus de finesse plus tard.
  • Polices : liste fermée de polices web-safe (aucun chargement dynamique de Google Fonts depuis l'éditeur) — évite d'introduire un mécanisme de chargement de police, hors périmètre de cet audit.