From 81c31a9c49c8ab38666f71c8a3d6b43076efaa5d Mon Sep 17 00:00:00 2001 From: william Date: Thu, 27 Aug 2026 18:43:29 +0200 Subject: [PATCH] =?UTF-8?q?Corrige=20la=20bo=C3=AEte=20de=20dialogue=20(su?= =?UTF-8?q?perposition)=20:=20un=20=C3=A9l=C3=A9ment=20pos=C3=A9=20apr?= =?UTF-8?q?=C3=A8s=20elle=20s'affichait=20par-dessus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug visible sur le jeu de démo : le bouton "Clique-moi !" restait visible ET cliquable AU-DESSUS du dialogue de bienvenue censé couvrir tout l'écran, et la boîte de dialogue elle-même s'étirait bord à bord au lieu de rester une boîte centrée lisible. Cause (stacking context CSS) : le widget "superposition" ignore x/y/ width/height et pose lui-même position:fixed; inset:0; z-index:9999 sur SA PROPRE balise (render_overlay.py) — mais le cadre .playElement/ .canvasElement qui l'entoure, PARTAGÉ PAR TOUS LES WIDGETS (filters/ element_style_filter.py), continuait quand même à poser "position:absolute; z-index:" (souvent petit, ex. 1). Un élément positionné avec un z-index explicite crée un NOUVEAU contexte d'empilement CSS : le 9999 posé plus profond ne se comparait alors plus qu'AU SEIN de ce contexte, et perdait face au z-index (plus grand) d'un élément ajouté APRÈS l'overlay sur le canevas — qui s'affichait donc par-dessus le dialogue. Correctif : _element_style ne pose plus aucune position/z-index pour ce widget (position:static — sa place dans le flux est de toute façon invisible, son contenu réel étant en position:fixed). Plus de contexte d'empilement local créé à ce niveau : le z-index:9999 se compare directement à tous les autres éléments de l'écran, et gagne toujours. Profité de l'occasion pour donner à la boîte une largeur par défaut plus raisonnable (render_overlay.py : max-width:min(560px, 90%) au lieu de 90% seul) — sur un écran de jeu large, "90%" donnait une boîte étirée bord à bord peu lisible comme dialogue ; 560px reste confortable, et 90% prend toujours le relais sur un écran étroit (mobile/portrait). Nouveau test de régression (test_overlay_wrapper_does_not_trap_its_own_z_index) : confirmé en échec sur l'ancien code (git stash), au vert avec le correctif. 129 tests au vert au total. Co-Authored-By: Claude Sonnet 5 --- filters/element_style_filter.py | 19 ++++++++++++++++++- screens/rendering/render_overlay.py | 7 ++++++- tests/test_confort.py | 27 +++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/filters/element_style_filter.py b/filters/element_style_filter.py index 0f3a26c9..daf9cbe9 100644 --- a/filters/element_style_filter.py +++ b/filters/element_style_filter.py @@ -15,7 +15,24 @@ def _element_style(el): pour une animation en boucle infinie, en permanence). Voir _element_transform_style, qui porte ce "transform" sur un cadre séparé (.canvasElementInner / .playElementInner), à la taille exacte de celui-ci - (donc visuellement identique).""" + (donc visuellement identique). + + Bug corrigé : le widget "superposition" (render_overlay.py) ignore déjà + x/y/width/height et pose lui-même position:fixed; inset:0; z-index:9999 + sur SA PROPRE balise — mais tant que CE cadre-ci gardait quand même + "position:absolute; z-index:{z_index}" (le z-index de sa place dans le + canevas, souvent petit), il devenait un élément positionné avec z-index + explicite, donc un NOUVEAU contexte d'empilement CSS — le z-index:9999 + posé plus profond ne se comparait alors plus qu'AU SEIN de ce contexte, + et perdait face au z-index (plus grand) d'un frère ajouté après lui sur + le canevas, qui se retrouvait affiché PAR-DESSUS le dialogue censé tout + couvrir. En ne posant ICI aucune position/z-index pour ce widget (sa + place dans le flux normal, invisible puisque son contenu est en + position:fixed de toute façon), plus aucun contexte d'empilement n'est + créé à ce niveau : le z-index:9999 se compare alors directement aux + autres éléments de l'écran, et gagne toujours.""" + if el.get("widget") == "superposition": + return "position:static;" return "; ".join([ "position:absolute", f"left:{el['x']}%", f"top:{el['y']}%", diff --git a/screens/rendering/render_overlay.py b/screens/rendering/render_overlay.py index 1ea33d3b..cff5569d 100644 --- a/screens/rendering/render_overlay.py +++ b/screens/rendering/render_overlay.py @@ -44,7 +44,12 @@ def _render_overlay(el, meta, slug, children_map=None, ctx=None, parent_flex_dir box_style = ( f"background:{html_lib.escape(str(box_color))}; border-radius:{html_lib.escape(str(radius))}px; " - "padding:24px; max-width:90%; max-height:90%; overflow:auto; box-sizing:border-box;" + # max-width en min(...) plutôt qu'un simple pourcentage : sur un + # écran de jeu large (desktop), "90%" seul donnait une boîte étirée + # bord à bord peu lisible comme dialogue — 560px reste une largeur + # de boîte de dialogue confortable, tout en retombant sur 90% sur un + # écran de jeu étroit (mobile/portrait) pour ne jamais déborder. + "padding:24px; width:100%; max-width:min(560px, 90%); max-height:90%; overflow:auto; box-sizing:border-box;" ) child_html = _render_children(el, slug, children_map, ctx) return ( diff --git a/tests/test_confort.py b/tests/test_confort.py index 89608833..4d622083 100644 --- a/tests/test_confort.py +++ b/tests/test_confort.py @@ -181,6 +181,33 @@ def test_overlay_widget_renders_fullscreen_fixed_box(client, game): assert "forgeOverlayBox" in snippet +def test_overlay_wrapper_does_not_trap_its_own_z_index(client, game): + """Régression : le cadre .playElement/.canvasElement partagé par TOUS + les widgets (voir filters/element_style_filter.py) posait quand même + "position:absolute; z-index:" sur la + superposition, MÊME SI son propre contenu (render_overlay.py) ignore + x/y/width/height et pose déjà position:fixed + z-index:9999 lui-même. + Un élément positionné avec un z-index explicite crée un NOUVEAU contexte + d'empilement CSS : le 9999 posé plus profond ne se comparait alors plus + qu'AU SEIN de ce contexte, et perdait face au z-index (plus grand) d'un + élément normal ajouté APRÈS l'overlay sur le canevas — qui s'affichait + donc PAR-DESSUS le dialogue censé tout couvrir. Le cadre ne doit donc + plus poser aucune position/z-index pour ce widget.""" + screen_id = _create_screen(client, game) + overlay_id = _add_element(client, game, screen_id, "superposition") + # Ajouté APRÈS l'overlay -> z_index plus grand que le sien. + _add_element(client, game, screen_id, "bouton") + + resp = client.get(f"/game/{game}/play") + html = resp.data.decode() + idx = html.find(f'data-el-id="{overlay_id}"') + assert idx != -1 + tag_start = html.rfind("