pcbjam/docs/features/ysync-review/18-miss08-opts-12-13.md
Gergő Törcsvári 62ca571802
feat(ysync): lib_symbols travel + layout save-sync (miss 08), TS hot-path opts (12), diff-on-rebind adopt (13) — doc 18
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
2026-07-06 08:57:58 +02:00

5.6 KiB

Miss 08 + opts 12/13 implemented; opt 14 deliberately deferred (2026-07-03)

Status: miss 08 (both halves), opt 12's TS items, and opt 13 implemented and verified. All TS/shared-side — the C++ already did its part (eeschema's itemBlob emits the lib_symbols context; doApplyItems' findLib was designed to prefer a blob-carried definition), so no WASM rebuild was needed.

08A — lib_symbols channel

  • New kdoc_libsymbols Y.Map (lib id → (symbol …) definition text): concurrent placements of DIFFERENT symbols merge per definition instead of LWW-clobbering one layout slot.
  • docToY extracts the layout's (lib_symbols …) definitions into the map and keeps the layout slot EMPTY (the injection point); yToDoc re-injects the map's definitions sorted by lib id — the order KiCad's own writer emits, so materialization matches editor saves.
  • Emit direction: wireLibSymbols(wire) recovers definitions from the multi-form clipboard blob (unwrapWireItem still strips them from the ITEM payload); the binding stores them in the same transaction as the item delta.
  • Apply direction: deltaToItemsWire(delta, view, libDefs) prefixes a root item carrying (lib_id …) with its definition — exactly the multi-form shape eeschema's paste path parses, so findLib's first branch resolves it and a peer that has never seen the symbol renders it correctly.
  • e2e: tests/kicad/ysync-libsymbols.spec.ts — a joiner whose cold copy lacks the symbol adopts it WITH its definition (chromium two-tab; F2 applies).

08B — coarse layout save-sync

  • New shared syncLayoutToY(fileDoc, ydoc, origin): reconciles non-item layout slots per HEAD KEYWORD group (title block, paper, setup, settings…) from a just-saved file — a changed group is replaced wholesale (LWW at head granularity; different heads merge). {item} slots are never touched.
  • Deliberate freezes: net (pcbnew's root net table — net-creating edits are not possible in the standalone; repeated positional heads can't be reconciled by name) stays seed-frozen; lib_symbols routes to the defs map ADDITIVELY (a save can't know about a peer's not-yet-applied placement).
  • Wiring: registerSaveHook gained onSavedText (decoded saved text); WasmTool routes it — eeschema per sheet via the manager's new syncLayoutFromSave (parked rooms sync too; the dirty mark it causes costs only the real delta now, see opt 13), pcbnew/pl_editor via the single-room doc.
  • Known limitation (unchanged from the doc's "coarse but converging"): a peer's EDITOR does not live-update non-item state — the room doc converges, so materialization/reopen and drift-detect see it; live propagation into an open editor's title block would need a C++ apply for non-item state.

Opt 12 — TS hot-path (items 2 + 4)

  • yToItemUnchecked (no zod) feeds the binding's itemsView() — both hot directions (every local emit, every remote batch). zod stays at the trust boundaries: wire parse, seed, materialize.
  • itemsWireToDelta builds the parent→children index ONCE per conversion (was: full-scan descendants() per wire item and per removed id).
  • NOT taken from doc 12: retiring the C++ scalar scan + legacy emit (the scan still feeds the legacy wire and covers eeschema's unreported post-Push connectivity cleanup — retire it together with the legacy-wire removal, miss 11 follow-up), and the lazy deltaToItemsWire view (item 3) — killing zod removed the dominant cost; revisit if profiling says otherwise.

Opt 13 — diff-on-rebind adopt (option 2)

seed()'s adopt no longer renders and re-applies EVERY doc root. It diffs the editor snapshot against the doc view (itemsWireToDelta + wireItemUuids) and applies only the doc-authoritative difference:

  • doc-only ROOTS → added (their sexprs embed descendants; a doc-only child makes its shared parent differ and rides the parent's re-apply);
  • differing items → the DOC's version re-applied, lifted to their root (the C++ upsert replaces roots; a bare child apply would mis-parent);
  • editor-only ROOTS → removed (editor-only children vanish with their parent's re-apply).

An empty diff degrades to baseline-only — the parked-dirty rebind, the clean revisit, and the cold divergent adopt now share one code path, and the adopt's single commit (and undo entry — the miss-09 "adopt undo bomb") shrinks to the real changed set.

Opt 14 — deferred, on purpose

The slot-level CRDT refinement is a meaningful rewrite of kicad-y's write path plus the whole conversion layer, with real interleaving-anomaly risk. Doc 14's own revisit triggers (measured bandwidth/storage cost; concurrent same-item UX complaints) are unmet, and the flatten already gives per-child granularity. Nothing in this batch forecloses it — body stayed one JSON value.

Verification

  • pcbjam-shared: 107 (9 new in test/layout-libsymbols-sync.test.ts)
  • standalone: 79 + 2 known pre-existing wasm-assets fixture failures (collab suite 42, incl. 3 new diff-adopt + 2 lib_symbols binding tests; save-flow +2)
  • e2e (chromium): full ysync set + ysync-libsymbols.spec.ts green
  • No C++ changes; no WASM rebuild.

Remaining from the review

Misses 09 (collab-aware undo — the adopt shrink here removes its worst case) and 10 (drift repair), opt 12's C++ retirement half (with the legacy-wire removal), opt 14 (triggers above), and doc 17's carried-over follow-ups.