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
+25 -7
View File
@@ -35,7 +35,7 @@ import app_state
import live_hooks
import schema
import undo
from sax_dom import (get_component_path, read_meta_property, set_prop_value,
from sax_dom import (get_component_path, read_meta_property, remove_prop, set_prop_value,
update_meta_groups, update_meta_property)
# Sedona slot types, grouped by how a value of that type is parsed and bounded.
@@ -1167,13 +1167,22 @@ class PropertiesPanel:
self.status_var.set("No changes to save.")
return
# In live mode the device is written first and a value only lands in the document
# once the device took it, so the panel never shows what the device does not hold.
live_failures = []
for element, prop_name, _before, after in changes:
for element, prop_name, before, after in changes:
if app_state.live_mode and not live_hooks.live_set_prop_value(
element, prop_name, after):
live_failures.append(f"{prop_name}: {live_hooks.last_error}")
if prop_name == "meta": # applied above, ahead of the write
if before is None:
remove_prop(element, "meta")
else:
set_prop_value(element, "meta", before)
continue
if prop_name != "meta":
set_prop_value(element, prop_name, after)
if live_hooks.is_live():
if not live_hooks.live_set_prop_value(element, prop_name, after):
live_failures.append(prop_name)
written = len(changes) - len(live_failures)
if not app_state.live_mode:
undo.push(undo.SlotValueCommand(list(changes)))
@@ -1183,11 +1192,20 @@ class PropertiesPanel:
if self.on_change is not None:
self.on_change()
count = len(changes)
count = written
name = self.comp.get('name', '?')
if live_failures:
self.status_var.set(
f"Written locally but failed on device: {', '.join(live_failures)}"
f"{written} written, {len(live_failures)} rejected by the device — "
"those still show the device's value."
)
live_hooks.warn_failure(
"Properties",
f"{len(live_failures)} of {len(changes)} change(s) to {name} did not reach "
"the device and were not applied",
"\n".join(f" • {failure}" for failure in live_failures),
)
return
elif app_state.live_mode:
self.status_var.set(
f"{count} slot{'' if count == 1 else 's'} written to device."