Ebene-Add Race: SET_VISIBILITY-Roundtrip cancelt APPLY → neuer Eintrag weg
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 <noreply@anthropic.com>
This commit is contained in:
+21
-1
@@ -350,6 +350,20 @@ class EbenenBridge(panel_base.BaseBridge):
|
|||||||
return
|
return
|
||||||
payload_z = p.get("zeichnungsebenen") or []
|
payload_z = p.get("zeichnungsebenen") or []
|
||||||
payload_e = p.get("ebenen") 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")}
|
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")}
|
e_state = {e["code"]: e for e in payload_e if isinstance(e, dict) and e.get("code")}
|
||||||
merged_z = []
|
merged_z = []
|
||||||
@@ -369,6 +383,9 @@ class EbenenBridge(panel_base.BaseBridge):
|
|||||||
m["visible"] = s.get("visible", True)
|
m["visible"] = s.get("visible", True)
|
||||||
m["locked"] = s.get("locked", False)
|
m["locked"] = s.get("locked", False)
|
||||||
merged_e.append(m)
|
merged_e.append(m)
|
||||||
|
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_zeichnungsebenen", json.dumps(merged_z, ensure_ascii=False))
|
||||||
doc.Strings.SetString("dossier_ebenen", json.dumps(merged_e, ensure_ascii=False))
|
doc.Strings.SetString("dossier_ebenen", json.dumps(merged_e, ensure_ascii=False))
|
||||||
# zMode + eMode persistieren, damit bei Split-Send (nur eine
|
# zMode + eMode persistieren, damit bei Split-Send (nur eine
|
||||||
@@ -386,7 +403,10 @@ class EbenenBridge(panel_base.BaseBridge):
|
|||||||
active_code = p.get("activeCode") or doc.Strings.GetValue("dossier_active_code")
|
active_code = p.get("activeCode") or doc.Strings.GetValue("dossier_active_code")
|
||||||
layer_builder.apply_visibility(
|
layer_builder.apply_visibility(
|
||||||
doc, merged_z, merged_e, active_z_id, active_code, z_mode, e_mode)
|
doc, merged_z, merged_e, active_z_id, active_code, z_mode, e_mode)
|
||||||
# Cross-Panel-Sync
|
# 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)
|
_broadcast_state(doc)
|
||||||
|
|
||||||
def _set_active_zeichnungsebene(self, z):
|
def _set_active_zeichnungsebene(self, z):
|
||||||
|
|||||||
Reference in New Issue
Block a user