From 94f141450664e0cf6889bbb22836a919ac31feb2 Mon Sep 17 00:00:00 2001 From: william Date: Tue, 25 Aug 2026 15:54:06 +0200 Subject: [PATCH] =?UTF-8?q?Corrige=20un=20bug=20important=20:=20le=20style?= =?UTF-8?q?=20de=20TOUT=20=C3=A9l=C3=A9ment=20neuf=20=C3=A9tait=20cass?= =?UTF-8?q?=C3=A9=20depuis=20l'ajout=20de=20l'Ombre?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug remonté ("toutes les propriétés ne sont pas prises en compte") : la vraie cause n'avait rien à voir avec les éléments de jeu — le contrôle "Ombre" (voir le tour précédent) a une valeur par défaut composite (un dict Python, {"x":0,"y":4,...}, pas une chaîne CSS). default_style_for_ widget.py, qui fige les réglages "dont la valeur par défaut a un effet visuel voulu dès la création" pour chaque widget neuf, n'excluait pas ce nouveau type de contrôle — il écrivait donc ce dict TEL QUEL (repr Python) dans le style de CHAQUE élément fraîchement créé depuis ce commit, quel que soit son widget. Une valeur CSS invalide au milieu du style pouvait donner l'impression que "plein de propriétés" ne s'appliquaient plus. Deux correctifs : 1. default_style_for_widget.py exclut maintenant "shadow" du gel à la création (même raisonnement déjà appliqué à "color" juste au-dessus : la valeur par défaut n'est qu'une suggestion affichée dans le panneau, pas un réglage neutre à figer - le neutre CSS est "pas d'ombre"). 2. style_string.py ignore désormais toute valeur non scalaire (dict/liste) au moment de construire l'attribut style - filet de sécurité pour les éléments déjà créés AVANT ce correctif, qui portent encore ce dict figé en base et continueraient sinon à s'afficher cassés. Ajoute deux tests de régression dans test_shadow_controls.py (aucune ombre au premier rendu d'un élément neuf ; un élément déjà corrompu avant ce correctif continue de s'afficher normalement). Co-Authored-By: Claude Sonnet 5 --- screens/rendering/style_string.py | 10 +++++ screens/widgets/default_style_for_widget.py | 10 +++++ tests/test_shadow_controls.py | 46 +++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/screens/rendering/style_string.py b/screens/rendering/style_string.py index df479aa8..75d9d8ad 100644 --- a/screens/rendering/style_string.py +++ b/screens/rendering/style_string.py @@ -38,6 +38,16 @@ def _style_string(el, extra_base="", parent_flex_direction=None, widget_key=None nonlocal has_size_override if v in (None, ""): return False + if isinstance(v, (dict, list)): + # Ne devrait jamais arriver (chaque contrôle composite — ex. + # "shadow", voir c_shadow.py — est censé combiner ses sous- + # valeurs en une seule chaîne CSS avant d'être stocké), mais + # sert de filet de sécurité : un dict/une liste écrits tels + # quels ici donneraient une valeur CSS invalide (repr Python) + # qui cassait TOUT le style de l'élément (bug déjà corrigé côté + # écriture — voir default_style_for_widget.py — pour des + # éléments déjà créés avant ce correctif). + return False if k == "transform" and not is_child: # Un élément posé directement sur un écran a déjà ce "transform" # posé sur son cadre .canvasElementInner / .playElementInner diff --git a/screens/widgets/default_style_for_widget.py b/screens/widgets/default_style_for_widget.py index 2b2d04a5..abe9b686 100644 --- a/screens/widgets/default_style_for_widget.py +++ b/screens/widgets/default_style_for_widget.py @@ -73,6 +73,16 @@ def default_style_and_attributes(widget_key): # type de réglage (voir size_override_controls.py) — jamais une # vraie valeur par défaut à figer à la création. continue + if ctype == "shadow": + # "default" est un dict composite (voir c_shadow.py), pas une + # valeur CSS directement écrivable — sans cette exclusion, il + # était écrit TEL QUEL (repr Python d'un dict) dans le style de + # TOUT élément fraîchement créé, une valeur CSS invalide qui + # cassait le "box-shadow" de tout élément neuf. Comme pour + # "color" ci-dessus, la valeur par défaut n'est qu'une + # suggestion affichée dans le panneau, pas un réglage neutre à + # figer à la création (le neutre CSS est "pas d'ombre"). + continue if is_native_class_widget and control["key"] in _ZERO_DEFAULT_RESET_KEYS: continue target = control.get("target") diff --git a/tests/test_shadow_controls.py b/tests/test_shadow_controls.py index 253952a8..6055a818 100644 --- a/tests/test_shadow_controls.py +++ b/tests/test_shadow_controls.py @@ -14,6 +14,52 @@ def _create_element(client, slug, widget="conteneur"): return screen_id, element_id +def test_freshly_created_element_has_no_shadow_baked_in(client, game): + """Régression : le réglage par défaut du contrôle "shadow" est un dict + composite (voir c_shadow.py), pas une valeur CSS directement écrivable + — sans une exclusion dédiée dans default_style_for_widget.py, il était + écrit TEL QUEL (repr Python d'un dict) dans le style de TOUT élément + fraîchement créé, une valeur CSS invalide qui cassait "box-shadow" (et + donnait l'impression que "les propriétés ne sont pas prises en compte") + sur chaque nouvel élément, quel que soit son widget.""" + _screen_id, element_id = _create_element(client, game) + el = screens.get_element(game, element_id) + assert "box-shadow" not in el["style"] + + html = client.get(f"/game/{game}/runtime-payload").get_json() + rendered_html = None + for s in html["screens"]: + for e in s["elements"]: + if e["id"] == element_id: + rendered_html = e["rendered_html"] + assert rendered_html is not None + assert "{" not in rendered_html.split("style=")[1].split(">")[0] + + +def test_already_corrupted_style_does_not_break_the_whole_style_attribute(client, game): + """Filet de sécurité (voir style_string.py) : un élément déjà touché par + le bug ci-dessus avant son correctif (un dict resté tel quel dans son + style stocké) doit continuer à s'afficher — sans lui, une seule valeur + invalide au milieu de l'attribut style pouvait tout casser.""" + import json + + import db + + _screen_id, element_id = _create_element(client, game, "texte") + el = screens.get_element(game, element_id) + corrupted_style = dict(el["style"]) + corrupted_style["box-shadow"] = {"x": 0, "y": 4, "blur": 12, "spread": 0, "color": "#000000", "opacity": 0.4} + conn = db.connect(game) + conn.execute("UPDATE _screen_elements SET style = ? WHERE id = ?", (json.dumps(corrupted_style), element_id)) + conn.commit() + conn.close() + + html = client.get(f"/game/{game}/screens/{_screen_id}/edit").data.decode() + idx = html.find(f'id="elt-{element_id}"') + style_attr = html[max(0, idx - 400):idx] + assert "{" not in style_attr.split('style="')[-1] + + def test_shadow_controls_appear_in_the_properties_panel(client, game): screen_id, element_id = _create_element(client, game) html = client.get(f"/game/{game}/screens/{screen_id}/edit?selected={element_id}").data.decode()