Corrige la régression du correctif overlay : /elements/<id>/geometry plantait (500)

Le correctif précédent (81c31a9) retirait TOUTE position/taille du cadre
.canvasElement/.playElement d'une "superposition" pour ne plus piéger son
propre z-index:9999 dans un contexte d'empilement local — mais ce même
cadre sert aussi, dans l'ÉDITEUR, de prise pour le glisser-déposer/
redimensionnement de ce widget (screen_edit.html). Sans
position:absolute + left/top/width/height, ce cadre s'effondre à 0×0 (son
contenu réel est en position:fixed, hors flux) : le calcul de geometrie
divise alors par une dimension nulle, produit NaN, et
JSON.stringify(NaN) envoie littéralement "null" au serveur — qui plantait
sur float(None) dans routes/elements/element_geometry.py (500, reproduit
par l'utilisateur en posant un template lié à un objet et en essayant de
repositionner l'overlay dans l'éditeur).

Correctif plus ciblé : le cadre garde position/left/top/width/height comme
n'importe quel widget (l'éditeur redevient fonctionnel) — SEUL le z-index
est omis pour ce widget précis. position:absolute SANS z-index explicite
(donc z-index:auto) ne crée PAS de nouveau contexte d'empilement CSS :
le z-index:9999 posé plus profond (render_overlay.py) continue donc de se
comparer directement aux autres éléments de l'écran, et gagne toujours —
le bug de superposition visuelle (81c31a9) reste corrigé, sans regression
sur l'éditeur cette fois.

test_confort.py mis à jour (position:absolute attendu, plus d'assertion
"position:static") + nouveau test qui appelle directement la route
/geometry sur une superposition pour confirmer qu'elle répond 200.
137 tests au vert au total.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-28 06:07:08 +02:00
co-authored by Claude Sonnet 5
parent 5c069ae1fe
commit 7c0f1ed427
2 changed files with 61 additions and 27 deletions
+27 -16
View File
@@ -20,25 +20,36 @@ def _element_style(el):
Bug corrigé : le widget "superposition" (render_overlay.py) ignore déjà 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 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 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 "z-index:{z_index}" (le z-index de sa place dans le canevas, souvent
canevas, souvent petit), il devenait un élément positionné avec z-index petit), un élément positionné avec un z-index EXPLICITE (même sur ce
explicite, donc un NOUVEAU contexte d'empilement CSS — le z-index:9999 cadre, pas sur son contenu) devient un NOUVEAU contexte d'empilement
posé plus profond ne se comparait alors plus qu'AU SEIN de ce contexte, CSS — le z-index:9999 posé plus profond ne se comparait alors plus
et perdait face au z-index (plus grand) d'un frère ajouté après lui sur qu'AU SEIN de ce contexte, et perdait face au z-index (plus grand) d'un
le canevas, qui se retrouvait affiché PAR-DESSUS le dialogue censé tout frère ajouté après lui sur le canevas, qui se retrouvait affiché
couvrir. En ne posant ICI aucune position/z-index pour ce widget (sa PAR-DESSUS le dialogue censé tout couvrir.
place dans le flux normal, invisible puisque son contenu est en
position:fixed de toute façon), plus aucun contexte d'empilement n'est Première correction tentée (retirer aussi position/left/top/width/
créé à ce niveau : le z-index:9999 se compare alors directement aux height ici) : régression dans l'ÉDITEUR — ce même cadre (.canvasElement)
autres éléments de l'écran, et gagne toujours.""" sert aussi de prise pour glisser-déposer/redimensionner CE widget dans
if el.get("widget") == "superposition": le canevas (voir screen_edit.html), dont le calcul se base sur ses
return "position:static;" dimensions réelles ; sans position:absolute + left/top/width/height, ce
return "; ".join([ cadre s'effondre à 0×0 (son contenu réel est en position:fixed, donc
hors flux), le calcul division par zéro produit NaN, et
JSON.stringify(NaN) donne "null" — element_geometry.py plantait alors
sur float(None). left/top/width/height restent donc posés comme pour
n'importe quel widget (l'éditeur continue de fonctionner normalement) :
SEUL le z-index est omis pour ce widget précis. position:absolute SANS
z-index explicite (donc z-index:auto) ne crée PAS de nouveau contexte
d'empilement — le z-index:9999 posé plus profond se compare alors
directement aux autres éléments de l'écran, et gagne toujours."""
parts = [
"position:absolute", "position:absolute",
f"left:{el['x']}%", f"top:{el['y']}%", f"left:{el['x']}%", f"top:{el['y']}%",
f"width:{el['width']}%", f"height:{el['height']}%", f"width:{el['width']}%", f"height:{el['height']}%",
f"z-index:{el['z_index']}", ]
]) + ";" if el.get("widget") != "superposition":
parts.append(f"z-index:{el['z_index']}")
return "; ".join(parts) + ";"
def _element_transform_style(el): def _element_transform_style(el):
+34 -11
View File
@@ -184,15 +184,19 @@ def test_overlay_widget_renders_fullscreen_fixed_box(client, game):
def test_overlay_wrapper_does_not_trap_its_own_z_index(client, game): def test_overlay_wrapper_does_not_trap_its_own_z_index(client, game):
"""Régression : le cadre .playElement/.canvasElement partagé par TOUS """Régression : le cadre .playElement/.canvasElement partagé par TOUS
les widgets (voir filters/element_style_filter.py) posait quand même les widgets (voir filters/element_style_filter.py) posait quand même
"position:absolute; z-index:<sa place dans le canevas>" sur la "z-index:<sa place dans le canevas>" sur la superposition, MÊME SI son
superposition, MÊME SI son propre contenu (render_overlay.py) ignore propre contenu (render_overlay.py) ignore x/y/width/height et pose déjà
x/y/width/height et pose déjà position:fixed + z-index:9999 lui-même. position:fixed + z-index:9999 lui-même. Un élément positionné avec un
Un élément positionné avec un z-index explicite crée un NOUVEAU contexte z-index EXPLICITE crée un NOUVEAU contexte d'empilement CSS : le 9999
d'empilement CSS : le 9999 posé plus profond ne se comparait alors plus posé plus profond ne se comparait alors plus qu'AU SEIN de ce contexte,
qu'AU SEIN de ce contexte, et perdait face au z-index (plus grand) d'un et perdait face au z-index (plus grand) d'un élément normal ajouté
élément normal ajouté APRÈS l'overlay sur le canevas — qui s'affichait APRÈS l'overlay sur le canevas — qui s'affichait donc PAR-DESSUS le
donc PAR-DESSUS le dialogue censé tout couvrir. Le cadre ne doit donc dialogue censé tout couvrir. Le cadre garde position/left/top/width/
plus poser aucune position/z-index pour ce widget.""" height comme tout widget (l'éditeur en a besoin pour glisser-déposer/
redimensionner ce cadre — les retirer a fait planter element_geometry
en régression), mais n'écrit plus DU TOUT de z-index pour ce widget :
position:absolute avec z-index:auto (omis) ne crée pas de contexte
d'empilement, donc le 9999 se compare directement aux autres éléments."""
screen_id = _create_screen(client, game) screen_id = _create_screen(client, game)
overlay_id = _add_element(client, game, screen_id, "superposition") overlay_id = _add_element(client, game, screen_id, "superposition")
# Ajouté APRÈS l'overlay -> z_index plus grand que le sien. # Ajouté APRÈS l'overlay -> z_index plus grand que le sien.
@@ -204,8 +208,27 @@ def test_overlay_wrapper_does_not_trap_its_own_z_index(client, game):
assert idx != -1 assert idx != -1
tag_start = html.rfind("<div", 0, idx) tag_start = html.rfind("<div", 0, idx)
wrapper_tag = html[tag_start:idx + 200] wrapper_tag = html[tag_start:idx + 200]
assert "position:static" in wrapper_tag assert "position:absolute" in wrapper_tag
assert "position:absolute" not in wrapper_tag assert "z-index" not in wrapper_tag
def test_overlay_geometry_can_still_be_saved_from_the_editor(client, game):
"""Régression : la première version du correctif ci-dessus retirait
AUSSI position/left/top/width/height du cadre d'une superposition, ce
qui effondrait ce cadre à 0×0 dans l'ÉDITEUR (son contenu réel est en
position:fixed, hors flux) — le calcul de glisser-déposer/
redimensionnement (screen_edit.html) divise alors par une dimension
nulle, produit NaN, et JSON.stringify(NaN) envoie "null" : le serveur
plantait sur float(None) dans element_geometry.py. Le cadre doit donc
continuer à porter une position/taille en % normale pour ce widget."""
screen_id = _create_screen(client, game)
overlay_id = _add_element(client, game, screen_id, "superposition")
resp = client.post(
f"/game/{game}/elements/{overlay_id}/geometry",
data=json.dumps({"x": 12.5, "y": 20, "width": 55, "height": 45}),
content_type="application/json",
)
assert resp.status_code == 200
def test_overlay_starts_hidden_by_default(client, game): def test_overlay_starts_hidden_by_default(client, game):