Implement live device add/delete component and app actions

Fix add component from palette in live mode: resolve <app> element
as comp_id 0, fix type_id lookup to use type_name_map, set type_name
on newly added SoxComponent, sync device values into DOM after add,
write meta position back to device, and subscribe new component to COV.

Fix delete component in live mode: clean up subscriptions for deleted
component and all its descendants.

Add tree root context menu with device app actions (save, hibernate,
quit, restart, reboot) in live mode. Device save clears dirty state.

Clean up connection lost handling to do a full disconnect.
Fix slot registry to copy base slots and renumber IDs correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 16:30:42 +03:00
co-authored by Claude Opus 4.6
parent 9a483a0b0e
commit 2530bbd92c
7 changed files with 102 additions and 19 deletions
+30 -2
View File
@@ -96,13 +96,17 @@ class DeviceSession:
if kit_name == "sys":
base_slots = self._extract_component_base_slots(manifest)
self._register_kit_types(registry, kit_id, kit_name, manifest)
# Prepend inherited Component base slots (meta, etc.) to every type
# Prepend inherited Component base slots (meta, etc.) to every type,
# then renumber IDs to match the wire slot index (positional in the
# flattened inherited + own list — see sedona Type.java resolveSlots).
if base_slots:
for key in registry:
existing_names = {s["name"] for s in registry[key]}
for bs in base_slots:
if bs["name"] not in existing_names:
registry[key].insert(0, bs)
registry[key].insert(0, dict(bs))
for i, slot in enumerate(registry[key]):
slot["id"] = i
self.sox_client.slot_registry = registry
def _extract_component_base_slots(self, manifest):
@@ -238,6 +242,30 @@ class DeviceSession:
pass
self._current_subs.clear()
# ── Post-add / Post-delete bookkeeping ─────────────────────
def after_add(self, comp_id):
"""Subscribes a newly added component and reads its initial values."""
comp = self.sox_client.cache_get(comp_id)
if comp is None or not self.is_connected:
return
try:
self.sox_client.update([comp], CONFIG | RUNTIME)
except Exception:
pass
try:
self.sox_client.subscribe([comp], ALL)
self._current_subs.add(comp_id)
except Exception:
pass
def after_delete(self, comp_id, descendant_ids=None):
"""Removes a deleted component (and its descendants) from the subscription set."""
self._current_subs.discard(comp_id)
if descendant_ids:
for did in descendant_ids:
self._current_subs.discard(did)
# ── DOM Synthesis ───────────────────────────────────────────
def build_dom_tree(self, root_comp):