pcbjam/docs/features/ysync-review/00-overview.md
Gergő Törcsvári 574284c486
feat(collab): local-ops-only undo — remote applies skip the undo stack (ysync miss 09)
Ctrl+Z after a peer's edit no longer reverts (and re-broadcasts) the
peer's work, and the adopt undo-bomb is gone:

- doApply/doApplyItems (both editors) Push with SKIP_UNDO; the emit path
  is unaffected (suppression keys off s_applyingRemote, not undo).
- With SKIP_UNDO no picker owns removed items — the bindings free them
  after Push (explicit removals + upsert's remove-before-re-add; fields
  excluded: CHT_REMOVE hides them, parent keeps ownership). Freeing stays
  out of the fork commit classes so DRC's SKIP_UNDO callers can't
  double-free.
- Test hooks kicadCollabTestUndo/UndoDepth, registered per-editor AND in
  the kicad_editor dispatcher (merged image compiles out per-app
  registrations).
- kicad pointer: eeschema UUID undo guard + SKIP_UNDO connectivity split
  + quiet stale-entry drop (ca8877324c).
- tests/kicad/collab-undo.spec.ts: 5 scenarios (no undo entry from remote
  applies; selective undo; stranded replaced/deleted entries) — 5/5, plus
  collab/ysync regression 30 pass.
- docs: ysync-review 20 fix record; 09 marked FIXED; overview indexed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ejJEvS7ogef2o9gVTXjmp
2026-07-08 11:09:26 +02:00

7.1 KiB
Raw Permalink Blame History

Yjs ⇄ KiCad sync review — overview and index

Full-stack review (2026-07-02) of the collaborative-editing synchronization between the Y.Doc and the internal state of eeschema / pcbnew. Scope read end to end:

  • Shared Slot model: web/pcbjam-shared/src/{kicad-doc,kicad-y,items-wire,kicad-delta,sexpr}.ts
  • Runtime binding: web/standalone/src/wasm/collab/{kicad-binding,sheet-manager,provider,drift-detect,index}.ts
  • App wiring: web/standalone/src/components/WasmTool.tsx
  • C++ bridges: wasm/bindings/{eeschema,pcbnew}_embind.cpp
  • Tests: web/standalone/src/wasm/collab/*.test.ts, tests/kicad/*-collab.spec.ts, tests/collab/browser-entry.ts

How the sync works (context for every finding)

The production path is the v2 "items" wire (ysync 0008). C++ keeps a per-uuid scalar snapshot (itemToJson) as a diff baseline; a BOARD_LISTENER / SCHEMATIC_LISTENER treats commits as "something changed" triggers, and a post-settle flushDiff (CallAfter + COROUTINE) compares snapshots and emits each changed item as a full s-expr blob. The TS side re-flattens each blob into uuid-keyed items with parent links (itemsWireToDelta), diffs against the Y.Doc's kdoc_items map, and writes item-level updates (applyDeltaToY). Remote Y events go the other way: deltaFromYEvents → render the item's full subtree s-expr → kicadCollabApplyItems, which does an idempotent remove-by-uuid + re-add through a real commit. Seeding is seed-once: the first tab writes fileToDoc(file) into the empty room; joiners adopt the doc wholesale. Drift detection periodically compares a scratch save against yToDoc and reports (never repairs) divergence.

The overall shape — post-settle diff, idempotent full-item upserts, echo suppression by origin tag plus C++ rebaseline — is sound and a good fit for the asyncify/wasm constraints. The findings below are the places where it breaks or leaks.

Verdict / suggested order of attack

UPDATE 2026-07-03: bugs 0107 are all FIXED — see 17 for the fix record (done in the batch order below), two additional findings (F5: doc 16's F4 was an artifact of bug 01, file-seeded bodies are now editor-normalized; F6: the 0008 "asyncify-fragile envelope parse" was a layer-name bug, now fixed), and the remaining follow-ups. Every repro below now runs as a plain regression test. Misses 0810 and opts 1214 (beyond what the dirty-set emit already delivers) remain open.

Every bug below has a runnable expected-fail reproduction test, and the v2 e2e port (miss 11) is DONE — see 16 for the suite map and four empirical findings the running system added (notably: bug 03's sending half emits NOTHING, not the bare removal the doc predicted).

  1. Fix 01 immediately (one line).
  2. Then 02 and 03 (small, contained, data-corrupting).
  3. Fold 04 and 05 into one change: blob-hash / dirty-set change detection plus targeted rebaseline. This also delivers most of 12.
  4. 06 and 07 are race windows — narrow them after the e2e port (11) gives regression cover.

Index

Bugs

# File One-liner
01 01-bug-first-tab-listener-never-registered.md Fresh-room seeding tab never registers the C++ change listener → its edits never sync
02 02-bug-footprint-blob-zeroes-pad-nets.md Footprint blobs strip pad net codes → net data loss propagates to peers, doc, and files
03 03-bug-child-removal-dangling-slot.md Child-only deletion leaves a dangling {item} slot in the parent's Y body → renders throw
04 04-bug-lossy-change-detection.md Change detection is a lossy scalar projection → rotations, field text edits, pad edits never sync
05 05-bug-rebaseline-swallows-local-edits.md Global rebaseline after apply can silently drop concurrent local edits and receiver-side cleanup
06 06-bug-concurrent-seed-duplicates-layout.md Two clients seeding an empty room concurrently duplicate kdoc_layout → corrupt materialization
07 07-bug-sheet-switch-stale-down-hook.md Sheet switch leaves onItems pointing at the old room → cross-room contamination window

Design misses

# File One-liner
08 08-miss-layout-state-never-syncs.md Non-item state (title block, settings, lib_symbols) only syncs at seed
09 09-miss-undo-not-collab-aware.md Ctrl+Z reverts remote work and re-broadcasts it; adopt commits are undo bombs
10 10-miss-no-repair-path.md Drift is detected and reported but never repaired
11 11-miss-no-v2-e2e-coverage.md Two-tab e2e exercises the legacy wire; the production v2 stack has no end-to-end test

Optimizations

# File One-liner
12 12-opt-hot-path-full-model-work.md O(full-model) work (incl. zod) on every edit/apply/remote batch
13 13-opt-parked-dirty-full-sheet-replace.md Parked-dirty sheet rebind re-applies the whole sheet instead of the delta
14 14-opt-item-granularity-bandwidth.md Item-level body granularity ships the whole item per nudge; LWW drops concurrent property edits

Plan & results

# File One-liner
15 15-plan-repro-tests-and-v2-e2e.md The approved plan: repro tests for bugs 0107 + the v2 e2e port
16 16-repro-suite-results-and-empirical-findings.md Plan 15 executed (2026-07-03): suite map, verified failure sites, empirical findings F1F4
17 17-fixes-bugs-01-07.md Bugs 0107 fixed & verified (2026-07-03); findings F5F6; remaining follow-ups
18 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 Miss-09 option 1 (local-ops-only undo) feasibility research (2026-07-07): ~35 days, eeschema UUID-guard port is the core
20 20-fix-miss09-collab-aware-undo.md Miss 09 FIXED (2026-07-07): SKIP_UNDO remote applies + eeschema UUID guard + binding-owned removed-item lifetime; 5/5 e2e