Make every live edit reach the device, and warn when it doesn't

In live mode the device is now written first and the document only takes
what the device accepted. Every live_hooks failure records a reason, and
warn_failure() shows it in a dialog and the status bar instead of failing
silently.

- Move, properties, OX page editor: rejected values are rolled back and listed
- Add/remove link: no longer edits the DOM alone when an end can't be resolved
- Add/delete component: report why the device refused
- Tidy Layout: sends each new position to the device, reverts refusals,
  marks the document dirty, and pushes no undo step in live mode
- User Manager: add (one Sox add carrying cred/perm/prov), edit and delete
  now go to the device
- Binary Buf slots (e.g. User.cred) are sent as Buf bytes, not as Str text
- Bump version to 0.0.0.136

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 20:16:00 +03:00
co-authored by Claude Opus 5.5
parent f15fc42363
commit a2d4be6ea8
8 changed files with 312 additions and 114 deletions
+17 -5
View File
@@ -22,7 +22,7 @@ import ox_page_view
import user_service_view
import wiresheet
from sax_dom import (find_component_by_path, find_parent_element, get_component_path,
read_meta_property, update_meta_property)
read_meta_property, remove_prop, set_prop_value, update_meta_property)
# Tree column measurements, in pixels. The padding covers the expander arrow and the
# cell's own inset, which sit ahead of the text ttk lets us measure.
@@ -620,6 +620,8 @@ def do_tidy_layout(event=None):
return "break"
moved, clamped = wiresheet.tidy_layout()
if moved is None:
return "break"
multiplier = app_state.meta_scale // app_state.GRID_SIZE
if moved == 0:
@@ -1543,11 +1545,21 @@ def on_canvas_release(event):
if orig_x != raw_x or orig_y != raw_y:
meta_before = app_state.drag_data.get("meta_before", meta_str)
update_meta_property(comp_element, meta_str, raw_x, raw_y)
if live_hooks.is_live():
if app_state.live_mode:
new_meta = comp_element.find("prop[@name='meta']")
if new_meta is not None:
live_hooks.live_set_prop_value(
comp_element, "meta", new_meta.get("val", "0"))
if new_meta is None or not live_hooks.live_set_prop_value(
comp_element, "meta", new_meta.get("val", "0")):
# Back where the device still has it, so the sheet tells the truth.
if meta_str is None:
remove_prop(comp_element, "meta")
else:
set_prop_value(comp_element, "meta", meta_str)
app_state.drag_data["tag"] = None
wiresheet.render_current_sheet()
live_hooks.warn_failure(
"Move", f"{comp_element.get('name', 'The component')} was not "
"moved on the device")
return
if not app_state.live_mode:
undo.push(undo.MetaMoveCommand(
[(comp_element, meta_before, raw_x, raw_y)],