From fcbc97b608569fd4056d88df23028fe416f6b7ff Mon Sep 17 00:00:00 2001 From: karim Date: Tue, 19 May 2026 00:49:36 +0200 Subject: [PATCH] =?UTF-8?q?Ebene-Add=20Race:=20SET=5FVISIBILITY-Roundtrip?= =?UTF-8?q?=20cancelt=20APPLY=20=E2=86=92=20neuer=20Eintrag=20weg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root-Cause für 'Ebene erscheint kurz und verschwindet wieder, kein APPLY im PY-Log': 1. User klickt + → addNew. setEbenen(18 Eintraege). state local = 18. 2. visibilityKey aendert sich (ebenen-Aenderung) → applyVisibility debounced 30ms. 3. structureKey aendert sich → applyAll debounced 200ms. 4. T=30ms: SET_VISIBILITY landet beim Backend ZUERST. 5. `_apply_visibility` liest e_full (17 alte Eintraege) aus doc.Strings, merged Visibility-Flags vom Slim-Payload, schreibt die 17 ALTEN zurueck nach doc.Strings (der neue 18. Eintrag ist im merged-Loop nicht dabei weil iteriert ueber e_full). 6. broadcast STATE_SYNC mit 17 Eintraegen. 7. React-App empfaengt → setEbenen(17) → neue Ebene weg aus state. 8. structureKey wieder == appliedStructureKey → useEffect's clearTimeout cancelt den 200ms-applyAll-Timer. 9. APPLY feuert nie. Backend bleibt auf 17. Fix in _apply_visibility: detect pending structural change (Payload hat IDs/Codes die noch nicht in doc.Strings sind) und in dem Fall das SetString-Save UND den _broadcast_state ueberspringen. apply_visibility (Rhino-Layer-Visibility-Update) laeuft trotzdem mit dem merged-state — die noch nicht gespeicherte Ebene hat eh keinen Rhino-Layer und damit keine Visibility zu setzen. Sobald der 200ms-applyAll feuert: build_layers + Save bringt alles in Sync. Daraufhin broadcastet APPLY normal an beide Panels. Co-Authored-By: Claude Opus 4.7 --- rhino/rhinopanel.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/rhino/rhinopanel.py b/rhino/rhinopanel.py index 11a036b..4d0d5cd 100644 --- a/rhino/rhinopanel.py +++ b/rhino/rhinopanel.py @@ -350,6 +350,20 @@ class EbenenBridge(panel_base.BaseBridge): return payload_z = p.get("zeichnungsebenen") or [] payload_e = p.get("ebenen") or [] + # Strukturelle Aenderung pending? Wenn React-Payload IDs/Codes enthaelt + # die noch nicht in doc.Strings sind (= User hat gerade neue Ebene + # angelegt aber der strukturelle APPLY ist noch in der 200ms-Debounce), + # NICHT speichern. Sonst ueberschreibt die schnellere SET_VISIBILITY + # den geplanten APPLY-Save und die neue Ebene geht in der Race + # verloren. + payload_z_ids = {z.get("id") for z in payload_z if isinstance(z, dict)} + payload_e_codes = {e.get("code") for e in payload_e if isinstance(e, dict)} + existing_z_ids = {z.get("id") for z in z_full if isinstance(z, dict)} + existing_e_codes = {e.get("code") for e in e_full if isinstance(e, dict)} + has_new_structural = ( + bool(payload_z_ids - existing_z_ids - {None}) or + bool(payload_e_codes - existing_e_codes - {None}) + ) z_state = {z["id"]: z for z in payload_z if isinstance(z, dict) and z.get("id")} e_state = {e["code"]: e for e in payload_e if isinstance(e, dict) and e.get("code")} merged_z = [] @@ -369,8 +383,11 @@ class EbenenBridge(panel_base.BaseBridge): m["visible"] = s.get("visible", True) m["locked"] = s.get("locked", False) merged_e.append(m) - doc.Strings.SetString("dossier_zeichnungsebenen", json.dumps(merged_z, ensure_ascii=False)) - doc.Strings.SetString("dossier_ebenen", json.dumps(merged_e, ensure_ascii=False)) + if has_new_structural: + print("[EBENEN] _apply_visibility: structural change pending → skip save (waiting for APPLY)") + else: + doc.Strings.SetString("dossier_zeichnungsebenen", json.dumps(merged_z, ensure_ascii=False)) + doc.Strings.SetString("dossier_ebenen", json.dumps(merged_e, ensure_ascii=False)) # zMode + eMode persistieren, damit bei Split-Send (nur eine # Panel-Slice) der andere Mode aus dem Doc-Storage faellt anstatt # auf den Default zu rutschen. @@ -386,8 +403,11 @@ class EbenenBridge(panel_base.BaseBridge): active_code = p.get("activeCode") or doc.Strings.GetValue("dossier_active_code") layer_builder.apply_visibility( doc, merged_z, merged_e, active_z_id, active_code, z_mode, e_mode) - # Cross-Panel-Sync - _broadcast_state(doc) + # Cross-Panel-Sync NUR wenn wir nicht in einem structural-pending + # State sind — sonst broadcasten wir die unvollstaendige Liste und + # React ueberschreibt die gerade vom User hinzugefuegte Ebene. + if not has_new_structural: + _broadcast_state(doc) def _set_active_zeichnungsebene(self, z): doc = Rhino.RhinoDoc.ActiveDoc