fix(plugin): join plugin-opened undo scopes in XRecord writes

The internal per-call push_undo fractured plugin-composed operations:
history has no nested scopes, so each push finished the plugin's
still-pending scope and opened its own — an operation of several
writes became several undo steps and the plugin's label was lost
(measured: 3 creates + update + delete in one scope = 5 steps).

Follow the entity-write contract instead: a mutation inside a
plugin-opened push_undo scope joins it, so the whole operation is a
single undo step under the plugin's label; a scope-less mutation opens
and finishes its own scope, keeping one undo step per call; refusals
record no undo step in either regime. Document the contract on the
trait methods and the host docs; the composition experiment becomes
the permanent xrecord_writes_join_a_plugin_initiated_undo_scope test.
This commit is contained in:
Sebastian committed 2026-10-11 20:58:41 +02:00
1 parent ca2f0c9857
commit 297a4d3074
4 files changed
+166 -17

No files matched your search

+6 -1
View File
@@ -352,8 +352,13 @@ replaces the entries atomically (handle, owner and name stay, the record's verba
saves emit the new content); delete returns the removed record; list reports only the keys that address
XRecords, in dictionary order. Undo: objects-map mutations are captured by the pending history's full structure
snapshot, so each mutating call is one undo step whose undo restores the objects map byte-exactly, the
auto-created dictionaries included. Evidence: `xrecord_crud_round_trips_refusals_and_list`,
auto-created dictionaries included. Undo composition follows the entity-write contract: a mutation inside a
plugin-opened `push_undo` scope joins that scope (a multi-write operation — entities and XRecords — is a single
undo step under the plugin's label); only a scope-less mutation opens its own step. Evidence:
`xrecord_crud_round_trips_refusals_and_list`,
`xrecord_mutations_are_single_undo_steps_that_restore_objects` (four steps, refusal paths record none),
`xrecord_writes_join_a_plugin_initiated_undo_scope` (3 creates + update + delete + a refusal + an entity write
in one scope = one step, undone and redone whole),
`xrecords_survive_dxf_and_dwg_round_trips` (typed entries reload from both writers), the IPC round-trips
`xrecord_request_and_response_roundtrip` and `xrecord_requests_await_their_typed_responses`. The API is
host-level and stands on its own; a Python mapping (`doc.xrecords`-style) is future work.