Premier commit d'une fonctionnalité découpée en plusieurs lots (voir le
plan "Bibliothèque de sprites animaux CraftPix") : intègre 14 familles
d'animaux (15 variantes de couleur chacune) comme personnages Forge
sélectionnables, à côté des 6 Kenney existants — réservé au rôle admin,
licence CraftPix oblige (interdiction contractuelle de rendre ces sprites
utilisables par un compte "user" via l'application).
- screens/labels/animal_sprite_library.py (nouveau) : charge un manifest
JSON généré une fois (voir scripts/generate_animal_sprite_manifest.py,
commit suivant) et construit ADMIN_SPRITE_LIBRARY, dans le même format
que l'existant PUBLIC_SPRITE_LIBRARY (screens/labels/sprite_library.py,
ex-SPRITE_LIBRARY, renommé pour distinguer les deux). screens.SPRITE_LIBRARY
reste le catalogue FUSIONNÉ (utilisé par resolve_personnage_animations
pour la résolution runtime, sans filtrage par rôle — voir le constat
d'exploration : le payload de jeu et /jouer/<slug> ne vérifient déjà
aucun rôle nulle part).
- screens/labels/sprite_gallery.py (nouveau) : sprite_gallery_families()
groupe la galerie par famille — un animal n'apparaît qu'une fois (sa
variante "de base"), ses 15 couleurs se choisissent depuis le panneau
de propriétés (render_variant_gallery, templates/screen_edit.html),
répondant à la suggestion de l'utilisateur plutôt que d'encombrer la
galerie d'ajout de 210 tuiles quasi identiques.
- routes/screens/screen_edit.py, routes/scenes/scene_edit_view.py :
la galerie passée au template est filtrée par rôle
(PUBLIC_SPRITE_LIBRARY pour un compte "user", SPRITE_LIBRARY complet
pour un admin) — même idiome que core/auth_guard.py.
- core/sprite_gate.py (nouveau) + 4 routes d'écriture (element_add,
element_set_personnage_data, scene_object_add, scene_object_personnage_data) :
ferme la brèche d'un POST direct qui contournerait la galerie filtrée
(403 si un compte non-admin tente d'assigner un personnage animal).
- tests/conftest.py : nouvelles fixtures user_client/user_game (compte
"user" non-admin avec un projet assigné) pour tester le filtrage par
rôle de bout en bout.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
À la demande de l'utilisateur (validation de la refonte visuelle du
widget Personnage), deux ajustements :
- Bibliothèque de sprites Forge étendue aux 6 personnages du pack Kenney
"Toon Characters" (aventurier/aventurière, personnage homme/femme,
robot, zombie), avec TOUTES leurs poses (45 par personnage) plutôt que
3 — regroupées en 31 animations nommées par personnage (poses
numérotées type walk0..walk7 fusionnées en un seul cycle "walk", les
autres restant des poses figées à une image). ~1,2 Mo au total,
toujours un sous-ensemble curé du pack source (assets/characters/, non
versionné) — HD/Parts/Tilesheet/Vector toujours exclus.
- Import de sprites personnalisés retiré pour le moment : plus de tuile
"➕ Sprite personnalisé" dans la galerie d'ajout, plus de section
d'import dans les propriétés d'un personnage — seuls les personnages
Forge restent proposés. La résolution serveur de _personnage_data
garde son support générique de la source "custom" (aucune migration
requise si cette possibilité revient plus tard), mais plus aucune UI
ne permet de la créer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
L'utilisateur a testé la Phase 7 (sprites pilotés via un menu déroulant
+ zone de texte dans la logique de flow) et l'a rejetée à raison : ce
n'est pas comme ça qu'un moteur de jeu (Phaser, Unity) gère un
personnage. Cette refonte remplace tout le flux d'AUTEURING par un vrai
widget visuel — le moteur d'exécution de la Phase 7 (runSpriteAnimation,
activeSpriteAnimations, orientation, kind="sprite" de la Timeline) reste
inchangé.
Nouveau widget "personnage" (screens/widgets/registry.py) :
- Rendu serveur dédié (special_render, screens/rendering/render_personnage.py)
qui affiche la pose "idle" dès le HTML généré — jamais une image
cassée à configurer après coup.
- Toutes ses données (source Forge ou sprites propres au créateur, quel
personnage/quelles animations) vivent dans une seule clé JSON
_personnage_data (screens/rendering/personnage_data.py), sans aucune
migration de schéma (même patron que c_clause_list).
- Nouvelle échappatoire "custom_panel", symétrique à "special_render" mais
pour le panneau de propriétés : ce widget affiche une galerie/un import
de sprites sur-mesure plutôt que les contrôles génériques.
Galerie visuelle de personnages Forge (VRAIES miniatures, pas un emoji) :
- Panneau gauche "🎭 Personnages" : pose un personnage déjà configuré,
animé immédiatement.
- Panneau droit (propriétés) : change le personnage Forge de l'élément
sélectionné, ou importe les animations d'un sprite personnalisé.
- static/js/screen_edit/personnage-preview.js : lance l'aperçu animé de
CHAQUE personnage du canevas dès le chargement de la page et après
toute sauvegarde — le cœur de la demande ("je dois voir ça bouger").
Sélecteur d'animation à miniatures (nœud de flow "Jouer une animation" et
clip de Timeline "sprite") : remplace le menu déroulant/la zone de texte
par une grille de vraies miniatures, scopée aux SEULES animations du
personnage réellement ciblé (ELEMENT_ANIMATIONS_MAP, résolu côté serveur
à partir des propriétés de cet élément précis) — jamais un catalogue
global. Le format stocké sur le nœud/le clip ({frames, fps, loop}) est
inchangé, seule l'UI d'édition change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Depuis le dernier correctif, supprimer un élément supprime aussi en
silence tout nœud de la Logique de la scène qui le référence (déclencheur/
action, voir delete_element.py) - nécessaire pour éviter le plantage
"FOREIGN KEY constraint failed", mais l'utilisateur n'était jamais prévenu
qu'un bout de sa logique disparaissait en même temps.
Nouvelle route GET .../elements/<id>/delete-impact (element_delete_
impact.py) : calcule, sans rien supprimer, combien de nœuds de la Logique
de la scène référencent cet élément OU l'un de ses descendants (partage
element_descendant_ids.py avec delete_element.py, pour rester exactement
cohérent avec ce qui sera réellement supprimé).
Les deux boutons "supprimer" de screen_edit.html (élément sélectionné, et
onglet d'un widget Onglets) ouvrent maintenant une modale custom
(deleteConfirmModal, même famille que le sélecteur d'icônes) au lieu du
confirm() natif du navigateur : elle interroge cette route juste après
ouverture et affiche un avertissement dédié si le nombre remonté est non
nul, avant que l'utilisateur ne confirme quoi que ce soit - impossible à
faire avec confirm(), dont le texte est figé au moment du rendu de la
page. La confirmation soumet ensuite le formulaire normalement (via
requestSubmit(), intercepté par pjax.js comme n'importe quel autre
formulaire).
Ajoute deux tests pour la nouvelle route (impact nul, impact non nul sans
rien supprimer).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- The "icone" widget no longer appears as a generic tile in "Ajouter un élément sur l'écran" / "Ajouter DANS ce conteneur" — the icon gallery (now living under "Ajouter un élément sur l'écran" in the left panel instead of the right one) is the only way to add one, always pre-set to the icon you picked.
- The element tree now supports dragging an element onto a container row to move it inside (last child), from anywhere in the tree — not just reordering within the same parent. Hovering a container row splits it into before/after/inside zones (thirds) when dragging a sibling, or "inside only" when dragging from elsewhere in the tree. New move_element_to_container() rejects non-container targets and cycles (dropping a container into itself or one of its own descendants) silently, mirroring reorder_element's existing safety pattern.
Verified end to end: gallery renders in the left panel with no icone tile in the widget grids, and the move endpoint correctly reparents, rejects a cycle, and rejects a non-container target. Full suite green (89).
Self-hosting Font Awesome's font files (previous commit) didn't fix it either: every icon in the picker still failed identically. That rules out CDN/network blocking specifically and points to something in the browser blocking custom webfont loading altogether regardless of origin (common with strict anti-fingerprinting protection, e.g. Brave's font blocking or certain privacy extensions — @font-face loading is a well-known fingerprinting vector).
Replaced the whole mechanism: each of the 258 curated icons is now a real downloaded SVG file (static/icons/*.svg, sourced from Font Awesome 5 Free's official SVG package) displayed via CSS mask-image (.icon-svg in style.css) instead of a font glyph. A masked SVG isn't a font resource at all, so it isn't subject to webfont-blocking — and it still colors via the existing "color" style property (background-color:currentColor) and sizes via font-size (1em), so no change to how the widget's other controls work.
The "Icône" widget now stores just the icon's slug (e.g. "trophy") in a dedicated attribute instead of a full CSS class string, rendered by a new special_render (render_icone.py). Updated the add-gallery and the properties-panel picker modal to use the same mask technique for their previews, and element_add.py to validate the slug against the curated list. Removed the now-unused self-hosted Font Awesome CSS/webfont files and the <link> tags — no font dependency left for icons at all.
Verified end to end (gallery renders with real SVG previews, simulated add creates a correctly-attributed element, the SVG file is actually served, the per-element picker reflects the current icon). Full suite green (89).
New "Icône" widget (<i class="...">, color/size customizable exactly like any other widget) plus a curated set of ~250 verified Font Awesome 5 Free solid icon names (fontawesome_icons.py) — not the full ~1500-icon catalog, since an embedded list needs to be guaranteed accurate (a wrong class name silently renders as a blank glyph); any other valid FA5 class still works by typing it directly into the widget's "Icône" setting.
The gallery lives in the (now always-visible) top of the right floating panel, searchable, with each tile both clickable and HTML5-draggable onto the canvas — either action posts to element_add with the chosen icon_class, which now seeds the new element's class instead of leaving it on the generic default. Available in the element-type template editor too, since it reuses screen_edit.html.
Loads Font Awesome 5.15.4 (cdnjs) alongside the existing animate.css/Bulma links, in both the editor and /play.
Verified end to end: gallery renders with real icon glyphs, clicking/simulated-drop creates a correctly-classed <i> element, and the actual /play route renders it with Font Awesome loaded. Full suite green (89).
New widget where each tab is a real "conteneur" element posed as a child
(see screens/elements/add_tab.py) — this reuses everything that already
exists for a normal container (adding a Répéteur/Conteneur/etc. inside via
"Ajouter DANS ce conteneur", renaming to change the tab's visible label,
deleting via the standard trash icon) instead of inventing a separate
storage format for tabs.
The widget's own properties panel gets a dedicated "Onglets" section to
add a tab, rename one, jump to its content, or delete it. Rendering
(render_onglets.py) builds a tab bar + one panel per tab, switched
client-side (forgeShowTab, in both screen_edit.html and play.html) with
only one panel visible at a time.
Distinct from the existing "activer_onglet" flow action (2.3, manual
show-one/hide-siblings) — that stays available for custom show/hide
wiring; this widget is the turnkey version with tab management built in.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The "Éléments de cet écran" tree now renders every level of nesting
(previously stopped after one level of children) as a compact single-line
list, and right-clicking a row opens a context menu to duplicate the
element (and its full subtree) in place, in its current container.
Also: all property panels start collapsed instead of some being open by
default, the redundant nested element list inside "Ajouter DANS ce
conteneur" is removed (it only needs the widget picker), and the
now-unneeded "a container is selected" warning banner is gone.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>