From f6fc3d9906c27d384a105a303f40225742fe7656 Mon Sep 17 00:00:00 2001 From: jsboige Date: Mon, 14 Sep 2026 18:41:18 +0200 Subject: [PATCH] Add: temoin des chevauchements-fantomes eteints par la confirmation element #16188 (residu de #15930, supplantee par #15877) : la porte de confirmation element (#15695) eteignait les effleurings Range en silence. Un correctif muet est indiscernable d'un organe mort -- si la porte disparaissait dans un refactor, aucune mesure ne bougerait. - JS : compteur chevauchementsEteints incrementee au lieu du continue muet, retournee avec le resultat de slide - resultat par slide : chevauchements_eteints - github_annotations : ::notice [CHEVAUCHEMENT-FANTOME] avec compte et reference #15695 (non bloquant, aucun seuil touche) - resume JSON : n_chevauchements_eteints (hors code retour) - tests : 3 tests unitaires sur github_annotations (paire rapportee porte les deux mesures ; eteint -> notice comptee SANS warning ; slide propre -> rien) + seed n_eteints du bloc exec scripts/tests/test_scan_slidev_composition.py : 11 passed ; scripts/notebook_tools/tests/ : 18 passed. Syntaxe JS validee node --check sur le bloc evaluate extrait. Closes #16188 Co-Authored-By: Claude Sonnet 5 --- .../notebook_tools/scan_slidev_composition.py | 21 +++++++- .../tests/test_scan_slidev_composition.py | 2 +- scripts/tests/test_scan_slidev_composition.py | 48 +++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/scripts/notebook_tools/scan_slidev_composition.py b/scripts/notebook_tools/scan_slidev_composition.py index 31be4b2e3d..339a2825df 100644 --- a/scripts/notebook_tools/scan_slidev_composition.py +++ b/scripts/notebook_tools/scan_slidev_composition.py @@ -271,6 +271,11 @@ def measure_slide(page, slide_idx: int, canvas_w: int, canvas_h: int) -> dict: // (RECOUVREMENT ci-après), qui mesure le contenu rendu et // l'ordre de peinture au lieu de la boîte élément naïve. const chevauchements = []; + // #16188 : paires éteintes par la confirmation élément ci-dessous. + // Un correctif muet serait indiscernable d'un organe mort : la + // notice [CHEVAUCHEMENT-FANTOME] rapporte ce compte (#12719 + // acceptance 4 -- dire ce qu'on a refusé de rapporter). + let chevauchementsEteints = 0; const textEls = Array.from( root.querySelectorAll('h1, h2, h3, h4, p, li, blockquote, td, th') ); @@ -332,7 +337,7 @@ def measure_slide(page, slide_idx: int, canvas_w: int, canvas_h: int) -> dict: const eb = b.el.getBoundingClientRect(); const eOverlapX = Math.min(ea.right, eb.right) - Math.max(ea.left, eb.left); const eOverlapY = Math.min(ea.bottom, eb.bottom) - Math.max(ea.top, eb.top); - if (eOverlapX <= 0 || eOverlapY <= 0) continue; + if (eOverlapX <= 0 || eOverlapY <= 0) { chevauchementsEteints++; continue; } chevauchements.push({ a: a.key, b: b.key, a_bbox: [Math.round(a.left), Math.round(a.top), Math.round(a.right), Math.round(a.bottom)], @@ -538,7 +543,7 @@ def measure_slide(page, slide_idx: int, canvas_w: int, canvas_h: int) -> dict: }; } - return { horsCanvas, chevauchements, recouvrements, occupation, contentBottom: Math.round(contentBottom) }; + return { horsCanvas, chevauchements, chevauchementsEteints, recouvrements, occupation, contentBottom: Math.round(contentBottom) }; }""", [canvas_w, canvas_h], ) @@ -554,6 +559,7 @@ def measure_slide(page, slide_idx: int, canvas_w: int, canvas_h: int) -> dict: "hors_canvas": hors, "container_only": bool(hors) and not any(h.get("tag") in CONTENT_TAGS for h in hors), "chevauchements": raw.get("chevauchements", []), + "chevauchements_eteints": raw.get("chevauchementsEteints", 0), "recouvrements": raw.get("recouvrements", []), "occupation": raw.get("occupation"), } @@ -664,6 +670,15 @@ def github_annotations(report: dict, slides_md: Path) -> list[str]: f"::warning file={rel},line={line}::[CHEVAUCHEMENT] slide {r['slide']} ({head}) — " f"{c['a']} × {c['b']} overlap={c['overlap']}px element_overlap={c.get('element_overlap')}px" ) + # #16188 : la porte de confirmation élément (#15695) est un témoin, + # pas un silence -- chaque effleurement Range qu'elle éteint est + # compté et rapporté en notice. Non bloquant par construction. + if r.get("chevauchements_eteints"): + out.append( + f"::notice file={rel},line={line}::[CHEVAUCHEMENT-FANTOME] slide {r['slide']} ({head}) — " + f"{r['chevauchements_eteints']} effleurement(s) Range éteint(s) par la " + f"confirmation élément (#15695) : boîtes élément disjointes, rien à l'écran" + ) for rv in r.get("recouvrements", [])[:3]: out.append( f"::warning file={rel},line={line}::[RECOUVREMENT-TEXTE-IMAGE] slide {r['slide']} — " @@ -759,6 +774,7 @@ def main(): n_total = len(results) n_hors = sum(1 for r in results if content_overflow(r)) n_chev = sum(1 for r in results if r.get("chevauchements")) + n_eteints = sum(r.get("chevauchements_eteints") or 0 for r in results) n_rec = sum(1 for r in results if r.get("recouvrements")) n_occ = sum(1 for r in results if occupation_flagged(r, canvas_h)) @@ -790,6 +806,7 @@ def main(): "n_slides": n_total, "n_hors_canvas": n_hors, "n_chevauchements": n_chev, + "n_chevauchements_eteints": n_eteints, "n_recouvrements": n_rec, "n_occupation_flagged": n_occ, "recouvrement_borne": ( diff --git a/scripts/notebook_tools/tests/test_scan_slidev_composition.py b/scripts/notebook_tools/tests/test_scan_slidev_composition.py index e7da5effa7..e2867e979d 100644 --- a/scripts/notebook_tools/tests/test_scan_slidev_composition.py +++ b/scripts/notebook_tools/tests/test_scan_slidev_composition.py @@ -330,7 +330,7 @@ def _build(baseline_slide): "canvas_w": 980, "canvas_h": 552, "BORNE": "ADVISORY", "ctrl_positif_ok": None, "ctrl_positif_msg": None, - "n_total": 0, "n_hors": 0, "n_chev": 0, "n_rec": 0, "n_occ": 0, + "n_total": 0, "n_hors": 0, "n_chev": 0, "n_eteints": 0, "n_rec": 0, "n_occ": 0, } exec(block, ns) return ns["report"] diff --git a/scripts/tests/test_scan_slidev_composition.py b/scripts/tests/test_scan_slidev_composition.py index 33d999bd3d..36480da1b9 100644 --- a/scripts/tests/test_scan_slidev_composition.py +++ b/scripts/tests/test_scan_slidev_composition.py @@ -126,6 +126,54 @@ def test_hors_canvas_contenu_inchange(self): assert "::warning" in hc and "IMG" in hc +class TestTemoinsChevauchementsFantomes16188: + """#16188 : les effleurements Range eteints par la porte de confirmation + element (#15695) sont comptes et rapportes en notice -- un correctif muet + serait indiscernable d'un organe mort.""" + + def test_paire_rapportee_porte_les_deux_mesures(self): + r = { + "slide": 6, "text_head": "vraie collision", + "hors_canvas": [], "chevauchements": [{ + "a": "P.x", "b": "P.y", "a_bbox": [1, 2, 3, 4], + "b_bbox": [2, 3, 5, 6], "overlap": [12, 8], + "element_overlap": [11.5, 7.25], + }], + "chevauchements_eteints": 0, + "recouvrements": [], "occupation": None, + } + lines = ssc.github_annotations(_report([r]), Path("slides.md")) + chev = next(l for l in lines if "[CHEVAUCHEMENT]" in l) + assert "overlap=[12, 8]px" in chev + assert "element_overlap=[11.5, 7.25]px" in chev + + def test_effleurement_eteint_emet_une_notice_comptee(self): + r = { + "slide": 16, "text_head": "graze code padding", + "hors_canvas": [], "chevauchements": [], + "chevauchements_eteints": 1, + "recouvrements": [], "occupation": None, + } + lines = ssc.github_annotations(_report([r]), Path("slides.md")) + fant = next(l for l in lines if "CHEVAUCHEMENT-FANTOME" in l) + assert "::notice" in fant + assert "1 effleurement" in fant + assert "#15695" in fant + assert not any("[CHEVAUCHEMENT]" in l for l in lines), ( + "eteint = pas de warning CHEVAUCHEMENT" + ) + + def test_slide_propre_sans_eteints_n_emet_rien(self): + r = { + "slide": 2, "text_head": "propre", + "hors_canvas": [], "chevauchements": [], + "chevauchements_eteints": 0, + "recouvrements": [], "occupation": None, + } + lines = ssc.github_annotations(_report([r]), Path("slides.md")) + assert not any("CHEVAUCHEMENT" in l for l in lines) + + class TestBornesAdvisory: def test_borne_documentee_dans_docstring(self): """Le signal est ADVISORY : le docstring du module (charge par