diff --git a/screens/rendering/render_element_html.py b/screens/rendering/render_element_html.py
index a569f8c0..240df2df 100644
--- a/screens/rendering/render_element_html.py
+++ b/screens/rendering/render_element_html.py
@@ -56,25 +56,34 @@ def render_element_html(el, slug=None, children_map=None, ctx=None, parent_flex_
bound_ctx = _resolve_bound_row_ctx(slug, attrs_raw)
if bound_ctx:
ctx = {**(ctx or {}), **bound_ctx}
+ # Chaque branche "special_render" passe elle aussi par _mark() : sans
+ # ça, un widget de ce genre (Icône, Tableau, Superposition...) actuellement
+ # VISIBLE sous condition ne portait aucun marqueur, et refreshRuntimeData()
+ # (play.html) ne savait donc jamais qu'il fallait le réévaluer après une
+ # action "Modifier une donnée/variable" — il restait figé dans son état
+ # de l'affichage précédent (ex: une icône "enveloppe fermée" restée
+ # visible après l'ouverture du mail, en même temps que "enveloppe
+ # ouverte" qui apparaît, elle, correctement puisqu'un élément CACHÉ
+ # porte toujours son marqueur via le placeholder ci-dessus).
special = meta.get("special_render")
if special == "table":
- return _render_table(el, meta, slug, children_map, ctx, parent_flex_direction)
+ return _mark(_render_table(el, meta, slug, children_map, ctx, parent_flex_direction))
if special == "select_options":
- return _render_select(el, meta, slug, children_map, ctx, parent_flex_direction)
+ return _mark(_render_select(el, meta, slug, children_map, ctx, parent_flex_direction))
if special == "fieldset_legend":
- return _render_fieldset(el, meta, slug, children_map, ctx, parent_flex_direction)
+ return _mark(_render_fieldset(el, meta, slug, children_map, ctx, parent_flex_direction))
if special == "checkbox_label":
- return _render_checkbox_or_radio(el, meta, slug, children_map, ctx, parent_flex_direction)
+ return _mark(_render_checkbox_or_radio(el, meta, slug, children_map, ctx, parent_flex_direction))
if special == "repeater":
- return _render_repeater(el, meta, slug, children_map, ctx, parent_flex_direction)
+ return _mark(_render_repeater(el, meta, slug, children_map, ctx, parent_flex_direction))
if special == "jauge":
- return _render_jauge(el, meta, slug, children_map, ctx, parent_flex_direction)
+ return _mark(_render_jauge(el, meta, slug, children_map, ctx, parent_flex_direction))
if special == "overlay":
- return _render_overlay(el, meta, slug, children_map, ctx, parent_flex_direction)
+ return _mark(_render_overlay(el, meta, slug, children_map, ctx, parent_flex_direction))
if special == "onglets":
- return _render_onglets(el, meta, slug, children_map, ctx, parent_flex_direction)
+ return _mark(_render_onglets(el, meta, slug, children_map, ctx, parent_flex_direction))
if special == "icone":
- return _render_icone(el, meta, slug, children_map, ctx, parent_flex_direction)
+ return _mark(_render_icone(el, meta, slug, children_map, ctx, parent_flex_direction))
tag = meta.get("tag")
if tag is None: # widget à balise dynamique (ex: "titre" -> h1/h2/h3)
diff --git a/tests/test_visibility_condition.py b/tests/test_visibility_condition.py
index 9b4cc325..7ed63bc4 100644
--- a/tests/test_visibility_condition.py
+++ b/tests/test_visibility_condition.py
@@ -79,6 +79,30 @@ def test_objet_mode_uses_most_recent_row_outside_a_repeater(client, game):
assert "display:none" not in _rendered_html(client, game)
+def test_visible_special_render_widget_carries_refresh_marker(client, game):
+ """Régression : une icône (special_render, pas le chemin générique)
+ actuellement VISIBLE sous condition doit quand même porter le
+ marqueur "visibilityGated" — sinon refreshRuntimeData() (play.html) ne
+ sait jamais qu'il faut la réévaluer après une action "Modifier une
+ donnée/variable", et elle reste figée dans son état précédent. Cas
+ concret remonté : une icône "enveloppe fermée" (visible tant que
+ is_opened=0) reste affichée EN MÊME TEMPS que l'icône "enveloppe
+ ouverte" (qui, elle, se met à jour normalement puisqu'un élément
+ CACHÉ porte toujours son marqueur via son placeholder)."""
+ screen_id = _create_screen(client, game)
+ element_id = _add_element(client, game, screen_id, "icone")
+ client.post(f"/game/{game}/variables/new", data={"name": "porte_ouverte", "type": "booleen", "default_value": "0"})
+ client.post(f"/game/{game}/elements/{element_id}/save", data={
+ "ctrl_visibilite_cond_mode": "variable",
+ "ctrl_visibilite_cond_variable": "porte_ouverte",
+ "ctrl_visibilite_cond_operateur": "egal",
+ "ctrl_visibilite_cond_valeur": "0",
+ })
+ html = _rendered_html(client, game)
+ assert "display:none" not in html
+ assert "visibilityGated" in html
+
+
def test_objet_mode_inside_a_repeater_evaluates_each_row_independently(client, game):
"""Régression : une "Mail card" (élément de jeu réutilisable posé dans
un Répéteur d'e-mails) doit afficher l'icône "ouvert"/"fermé" qui