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

98 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 01–07 are all FIXED** — see
[17](17-fixes-bugs-01-07.md) 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 08–10 and opts 12–14 (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](16-repro-suite-results-and-empirical-findings.md) 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](01-bug-first-tab-listener-never-registered.md) immediately (one line).
2. Then [02](02-bug-footprint-blob-zeroes-pad-nets.md) and
[03](03-bug-child-removal-dangling-slot.md) (small, contained, data-corrupting).
3. Fold [04](04-bug-lossy-change-detection.md) and
[05](05-bug-rebaseline-swallows-local-edits.md) into one change: blob-hash /
dirty-set change detection plus targeted rebaseline. This also delivers most of
[12](12-opt-hot-path-full-model-work.md).
4. [06](06-bug-concurrent-seed-duplicates-layout.md) and
[07](07-bug-sheet-switch-stale-down-hook.md) are race windows — narrow them after
the e2e port ([11](11-miss-no-v2-e2e-coverage.md)) gives regression cover.
## Index
### Bugs
| # | File | One-liner |
|---|------|-----------|
| 01 | [01-bug-first-tab-listener-never-registered.md](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](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](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](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](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](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](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](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](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](10-miss-no-repair-path.md) | Drift is detected and reported but never repaired |
| 11 | [11-miss-no-v2-e2e-coverage.md](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](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](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](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](15-plan-repro-tests-and-v2-e2e.md) | The approved plan: repro tests for bugs 01–07 + the v2 e2e port |
| 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 |
| 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 |
| 20 | [20-fix-miss09-collab-aware-undo.md](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 |