Miss 08A: the binding stores wire-carried (lib_symbols …) definitions in kdoc_libsymbols and prefixes them on apply wires — a joiner that never saw a symbol adopts it WITH its definition (new e2e ysync-libsymbols.spec.ts). Miss 08B: registerSaveHook gains onSavedText; WasmTool routes saved-file text to syncLayoutToY (per sheet room via the manager's syncLayoutFromSave, or the single-room doc) so title block / paper / setup edits converge instead of drifting. Opt 12 (TS half): zod off the observer hot path (yToItemUnchecked), children index built once per conversion. Opt 13: seed()'s adopt diffs the editor snapshot against the doc view and applies only the doc-authoritative difference — clean rebinds apply nothing, the adopt undo entry shrinks to the real changed set. Opt 14 deliberately deferred (doc 18). All TS-side; no wasm rebuild (the C++ blob/findLib sides already carried definitions). Verified: shared 107, standalone 79 (+2 known pre-existing wasm-assets), ysync e2e 21/21 chromium, collab regression 21/3-skip firefox. Bumps: web/pcbjam-shared (lib_symbols channel + syncLayoutToY + opts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JgThWXtdvrYLK47EDFoGdq
6.7 KiB
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 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 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).
- Fix 01 immediately (one line).
- Then 02 and 03 (small, contained, data-corrupting).
- Fold 04 and 05 into one change: blob-hash / dirty-set change detection plus targeted rebaseline. This also delivers most of 12.
- 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 01–07 + 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 F1–F4 |
| 17 | 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 | Miss 08 (lib_symbols + layout save-sync) + opts 12/13 implemented; opt 14 deferred (2026-07-03) |