docs(ysync-review): 19 — miss-09 option 1 (local-ops-only undo) feasibility research
Verdict: ~3-5 days. pcbnew undo already UUID-guards stale pointers; core work = porting that guard to eeschema, splitting connectivity out of the SCH SKIP_UNDO gate, plugging the SKIP_UNDO removed-item leak, silencing the incomplete-undo modal. Cross-linked from 09 and the overview index. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ejJEvS7ogef2o9gVTXjmp
This commit is contained in:
parent
c30bcd4531
commit
cd68114cdc
3 changed files with 130 additions and 0 deletions
|
|
@ -94,3 +94,4 @@ sending half emits NOTHING, not the bare removal the doc predicted).
|
||||||
| 16 | [16-repro-suite-results-and-empirical-findings.md](16-repro-suite-results-and-empirical-findings.md) | Plan 15 executed (2026-07-03): suite map, verified failure sites, empirical findings F1–F4 |
|
| 16 | [16-repro-suite-results-and-empirical-findings.md](16-repro-suite-results-and-empirical-findings.md) | Plan 15 executed (2026-07-03): suite map, verified failure sites, empirical findings F1–F4 |
|
||||||
| 17 | [17-fixes-bugs-01-07.md](17-fixes-bugs-01-07.md) | Bugs 01–07 fixed & verified (2026-07-03); findings F5–F6; remaining follow-ups |
|
| 17 | [17-fixes-bugs-01-07.md](17-fixes-bugs-01-07.md) | Bugs 01–07 fixed & verified (2026-07-03); findings F5–F6; remaining follow-ups |
|
||||||
| 18 | [18-miss08-opts-12-13.md](18-miss08-opts-12-13.md) | Miss 08 (lib_symbols + layout save-sync) + opts 12/13 implemented; opt 14 deferred (2026-07-03) |
|
| 18 | [18-miss08-opts-12-13.md](18-miss08-opts-12-13.md) | Miss 08 (lib_symbols + layout save-sync) + opts 12/13 implemented; opt 14 deferred (2026-07-03) |
|
||||||
|
| 19 | [19-undo-option1-feasibility.md](19-undo-option1-feasibility.md) | Miss-09 option 1 (local-ops-only undo) feasibility research (2026-07-07): ~3–5 days, eeschema UUID-guard port is the core |
|
||||||
|
|
|
||||||
|
|
@ -54,3 +54,9 @@ keystroke, so it deserves an explicit decision rather than the current default.
|
||||||
|
|
||||||
Recommendation: 3 now (one call at adopt time), 1 as the eventual model, evaluated
|
Recommendation: 3 now (one call at adopt time), 1 as the eventual model, evaluated
|
||||||
against how invasive pointer-invalidation is in `PICKED_ITEMS_LIST`.
|
against how invasive pointer-invalidation is in `PICKED_ITEMS_LIST`.
|
||||||
|
|
||||||
|
**Update 2026-07-07:** option 1 feasibility researched in
|
||||||
|
[19-undo-option1-feasibility.md](19-undo-option1-feasibility.md) — verdict: ~3–5
|
||||||
|
days; pcbnew undo already UUID-guards stale pointers, the core work is porting that
|
||||||
|
guard to eeschema plus two small `SKIP_UNDO` fork fixes (SCH connectivity gate,
|
||||||
|
removed-item leak). Option 3 becomes largely moot under it.
|
||||||
|
|
|
||||||
123
docs/features/ysync-review/19-undo-option1-feasibility.md
Normal file
123
docs/features/ysync-review/19-undo-option1-feasibility.md
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
# Research 19 — Feasibility of miss-09 option 1: exclude remote applies from undo
|
||||||
|
|
||||||
|
**Status:** research (2026-07-07), not implemented
|
||||||
|
**Verdict:** moderately hard but tractable — ~3–5 days. The pointer-invalidation risk
|
||||||
|
[09](09-miss-undo-not-collab-aware.md) flagged is already solved on the pcbnew side;
|
||||||
|
the core work is porting that resilience to eeschema, plus two small fork fixes to
|
||||||
|
make `SKIP_UNDO` safe for our use.
|
||||||
|
|
||||||
|
## Question
|
||||||
|
|
||||||
|
Miss 09's option 1 is "local-ops-only undo": remote applies stop landing on the
|
||||||
|
receiving editor's undo stack, so Ctrl+Z can never revert a peer's work. Both commit
|
||||||
|
classes support a `SKIP_UNDO` push flag — but 09 warned that KiCad's undo machinery
|
||||||
|
assumes the stack mirrors model history, and that `PICKED_ITEMS_LIST` holds raw
|
||||||
|
pointers which remote applies could strand. How bad is that really?
|
||||||
|
|
||||||
|
## What makes it easier than 09 feared
|
||||||
|
|
||||||
|
**The flag exists on both sides and skips only what we want (almost — see fix 2).**
|
||||||
|
`SKIP_UNDO` is honored by both commit classes (`pcbnew/board_commit.h:40`,
|
||||||
|
`eeschema/sch_commit.h:40`; base signature `include/commit.h:132`). Under it the
|
||||||
|
commit still does everything remote applies go through a real commit *for*:
|
||||||
|
board/screen add/remove, `view->Add/Remove/Update`, connectivity dirty-marking and
|
||||||
|
ratsnest (`board_commit.cpp:463-467,516`), teardrops, listener callbacks
|
||||||
|
(`OnItemsCompositeUpdate` / `OnSchItems*`), and the model-change events. Only the
|
||||||
|
undo-picker creation and the final `SaveCopyInUndoList` are gated
|
||||||
|
(`board_commit.cpp:325,356,455,606`; `sch_commit.cpp:266,295,363,448`).
|
||||||
|
|
||||||
|
**Exactly four Push sites to change, each already remote-tagged.** All remote data
|
||||||
|
funnels through `doApply` / `doApplyItems` per editor
|
||||||
|
(`wasm/bindings/eeschema_embind.cpp:1093,1218`;
|
||||||
|
`wasm/bindings/pcbnew_embind.cpp:965,1061`), every one bracketed by the
|
||||||
|
`s_applyingRemote` guard — the exact local/remote signal, available at the Push call.
|
||||||
|
Deletes, adopt, and the parked-dirty catch-up all funnel through `doApplyItems`;
|
||||||
|
there is no fifth production path.
|
||||||
|
|
||||||
|
**Change detection is unaffected.** The local-edit→yjs emit path is the
|
||||||
|
listener-`trigger()` + post-settle `flushDiff`, suppressed during remote applies by
|
||||||
|
`s_applyingRemote` (`eeschema_embind.cpp:652`, `pcbnew_embind.cpp:871`) — entirely
|
||||||
|
independent of whether the commit is undoable. Skipping undo disturbs neither the
|
||||||
|
suppression during applies nor the emit on genuine local edits (including the emit of
|
||||||
|
a local undo itself, which is what makes undo propagate today).
|
||||||
|
|
||||||
|
**Pcbnew undo already survives items vanishing behind its back.** Our fork's
|
||||||
|
`PutDataInPreviousState` checks every picker against `BOARD::ResolveItem( uuid )` and
|
||||||
|
drops entries whose items no longer exist (`pcbnew/undo_redo.cpp:428-437`), and even
|
||||||
|
re-resolves stale `CHANGED` pointers by UUID before swapping the clone image back in
|
||||||
|
(`:504`, comment cites ExchangeFootprint-style swaps). Group parent/member pointers
|
||||||
|
are rebuilt from KIIDs cached in the picker itself
|
||||||
|
(`undo_redo_container.h:114-115`, rebuilt at `undo_redo.cpp:633-658`). So on the PCB
|
||||||
|
side, a remote apply deleting or replacing an item referenced by a local undo entry
|
||||||
|
degrades gracefully (entry dropped) instead of crashing.
|
||||||
|
|
||||||
|
## The actual work
|
||||||
|
|
||||||
|
1. **Port the UUID guard to eeschema — the core task.**
|
||||||
|
`eeschema/schematic_undo_redo.cpp::PutDataInPreviousState` (`:246`) has *no*
|
||||||
|
existence or re-resolution check: it dereferences the stored raw `EDA_ITEM*`
|
||||||
|
directly (`:270,399,431`); only the group-relink post-pass uses
|
||||||
|
`Schematic().ResolveItem` (`:485,504`). Since `doApplyItems` upserts full item
|
||||||
|
subtrees as remove-by-uuid + re-add, a remote **change** (not just a delete) to an
|
||||||
|
item referenced by a local undo entry means a dangling pointer → crash on Ctrl+Z.
|
||||||
|
Fix: mirror pcbnew's pattern — existence-check each picker via
|
||||||
|
`SCHEMATIC::ResolveItem` (`eeschema/schematic.h:129`), drop missing entries,
|
||||||
|
re-resolve `CHANGED` pointers before `SwapItemData`. ~50–100 lines, follows an
|
||||||
|
in-tree precedent, plausibly upstreamable. This is the bulk of the effort.
|
||||||
|
|
||||||
|
2. **Un-bundle connectivity from `SKIP_UNDO` on the SCH side.** Unlike pcbnew,
|
||||||
|
`SCH_COMMIT::pushSchEdit` gates `RecalculateConnections` inside the same
|
||||||
|
`if( !( aCommitFlags & SKIP_UNDO ) )` block as `SaveCopyInUndoList`
|
||||||
|
(`sch_commit.cpp:448-463`). Remote applies need that recalc — it is the reason
|
||||||
|
they go through real commits. Small fork change: split the gate (or run the recalc
|
||||||
|
from the binding after Push).
|
||||||
|
|
||||||
|
3. **Plug the removed-item leak under `SKIP_UNDO`.** In the REMOVE branch the live
|
||||||
|
item is flagged `UR_TRANSIENT` (`board_commit.cpp:447`) but with `SKIP_UNDO` no
|
||||||
|
picker takes ownership and the clone is deleted (`:490`), so the removed item
|
||||||
|
leaks; same shape on the SCH side (`sch_commit.cpp:409-410`). Existing `SKIP_UNDO`
|
||||||
|
callers (DRC-marker flows, `drc_tool.cpp:208` etc.) never remove user items, so
|
||||||
|
upstream never hit this. Fix: delete removed items in the commit when the flag is
|
||||||
|
set (or in the binding, which knows the removed set).
|
||||||
|
|
||||||
|
4. **Silence the "incomplete undo" modal.** When pcbnew drops a stranded entry it
|
||||||
|
pops a blocking `wxMessageBox( "Incomplete undo/redo operation: some items not
|
||||||
|
found" )` (`pcbnew/undo_redo.cpp:628`). In a collab session that is routine, not
|
||||||
|
exceptional — downgrade to a log line (or toast) in the fork, and use the same
|
||||||
|
quiet path in the eeschema port.
|
||||||
|
|
||||||
|
Plus wasm-side flag changes at the four Push sites (~one line each) and multi-client
|
||||||
|
e2e coverage for undo-after-remote scenarios (delete-under-undo, change-under-undo,
|
||||||
|
adopt-while-history-nonempty), which given the two-tab harness is roughly another day.
|
||||||
|
|
||||||
|
## Residual semantic gaps (accepted, not blockers)
|
||||||
|
|
||||||
|
- **Full-clone restore clobbers concurrent field edits.** Undoing a local move of an
|
||||||
|
item restores the whole old item image (`CHANGED` swaps the full clone), so a
|
||||||
|
peer's concurrent edit to another property of the same item is reverted too.
|
||||||
|
Last-writer-wins collab undo — same as Figma; converges; not an operational rebase.
|
||||||
|
Same granularity trade-off as [14](14-opt-item-granularity-bandwidth.md).
|
||||||
|
- **Duplicate-KIID re-add edge.** Local delete parks the item on the local undo stack
|
||||||
|
(owned, `DELETED` pickers skip the existence check — `undo_redo.cpp:423`); if a
|
||||||
|
peer re-adds the same uuid and the local user then undoes the delete, the stored
|
||||||
|
item is re-added alongside the peer's copy with the same KIID (`:569-572`). Rare;
|
||||||
|
the differ keys by uuid so it likely reconciles to one, but needs a test.
|
||||||
|
- **Pre-existing local entries at adopt time** mostly reference replaced items and
|
||||||
|
get dropped by the UUID guard on first undo — acceptable, and strictly better than
|
||||||
|
the current adopt undo bomb.
|
||||||
|
|
||||||
|
## Effort summary
|
||||||
|
|
||||||
|
| Piece | Where | Size |
|
||||||
|
|---|---|---|
|
||||||
|
| `SKIP_UNDO` at the 4 remote Push sites | wasm bindings | trivial |
|
||||||
|
| SCH connectivity/`SKIP_UNDO` split | kicad fork (sch_commit) | small |
|
||||||
|
| Removed-item leak fix | kicad fork (both commits) | small |
|
||||||
|
| eeschema UUID guard port | kicad fork (schematic_undo_redo) | 1–2 days, core |
|
||||||
|
| Modal → quiet drop | kicad fork (undo_redo) | trivial |
|
||||||
|
| Two-client undo e2e | tests/ | ~1 day |
|
||||||
|
|
||||||
|
All fork touches are small and upstream-plausible, consistent with keeping the fork
|
||||||
|
close to upstream. Miss 09's option 3 (clear undo stack on adopt) becomes largely
|
||||||
|
moot: with `SKIP_UNDO`, adopt creates no undo entry at all, and stranded pre-adopt
|
||||||
|
entries are handled by the UUID guard.
|
||||||
Loading…
Reference in a new issue