All seven ysync-review bugs fixed and verified; every repro's expected-fail marker removed (they now run as regression tests). Also fixes two bugs found while verifying (doc 17 F5/F6): file-seeded Y bodies are re-upserted in the editor's serialization (doc-16 F4 was an artifact of bug 01), and the 0008-era "asyncify-fragile envelope parse" was really wrapInBoardEnvelope emitting display layer names — canonical LSET::Name() fixes track/via/zone v2 applies; makeFromBlob now logs parse errors instead of swallowing them. Verified: shared 98, standalone collab 37, ysync e2e 20/20 (chromium), collab regression set 21 passed / 3 pre-existing skips (firefox). Bumps: kicad (board_commit child-removal listener notification), web/pcbjam-shared (slot prune + arbitrated seed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JgThWXtdvrYLK47EDFoGdq
126 lines
7.5 KiB
Markdown
126 lines
7.5 KiB
Markdown
# Fixes for bugs 01–07 (2026-07-03)
|
||
|
||
**Status:** all seven review bugs FIXED and verified — every repro from plan
|
||
[15](15-plan-repro-tests-and-v2-e2e.md) had its expected-fail marker removed and now
|
||
runs as a plain regression test. Final state: ysync unit suites green
|
||
(pcbjam-shared 98, standalone collab 37), ysync e2e **20/20** on chromium
|
||
(two-tab incl. both bug-01 tools, concurrent seed, bare child removal), collab
|
||
regression set (items-bridge, roundtrip, legacy collab specs, save-hook) 21
|
||
passed / 3 pre-existing skips on firefox.
|
||
|
||
The fixes were done in the review's suggested batches; fixing them also surfaced
|
||
and fixed **two bugs the review missed** (F5, F6 below).
|
||
|
||
## Batch 1 — bug 01 (one line, as advertised)
|
||
|
||
`kicad-binding.ts` seed()'s file-seed branch now calls `bridge.snapshotItems()`
|
||
— `ensureBridge()` registers the C++ change listener, and the differ gets a
|
||
baseline. See F5 for why the snapshot's *result* is now also used.
|
||
|
||
## Batch 2 — bugs 02 + 03
|
||
|
||
- **02:** dropped `blobForItem`'s footprint `pad->SetNetCode(0)` loop
|
||
(`pcbnew_embind.cpp`). KiCad 10 formats pad nets by NAME and the parser
|
||
resolves names against the receiving board (creating the net if absent), so
|
||
the doc's "remap by name on apply" concern is satisfied by the format itself.
|
||
The repro's `(net 1 "SIG")` assertion was updated to accept the KiCad 10
|
||
name-only form.
|
||
- **03 (root cause, F1):** `BOARD_COMMIT::Push`'s CHT_REMOVE branch for footprint
|
||
children never touched any listener vector — `OnItemsCompositeUpdate` was
|
||
simply never called for a child-only delete. The kicad fork now pushes the
|
||
parent footprint into `itemsChanged` there (a genuine upstream notification
|
||
gap; candidate for upstreaming).
|
||
- **03 (emit):** `itemToJson` carries a `parent` uuid; `flushDiff`'s removal loop
|
||
lifts a removed child whose parent survives to a parent re-blob on the v2 wire
|
||
(`wRemoved` now diverges from the legacy `removed`).
|
||
- **03 (receive):** `doApplyItems`' removed loop only skips a child uuid when its
|
||
parent footprint is itself in the same wire's removed set; a BARE child
|
||
removal now removes the child.
|
||
- **03 (Y defense):** `applyDeltaToY` deep-prunes `{item: uuid}` slots from a
|
||
surviving parent's body before deleting a child item — "file recoverable from
|
||
the Y.Doc alone" holds regardless of emitter behavior.
|
||
|
||
## Batch 3 — bugs 04 + 05 (one change, both tools)
|
||
|
||
- The `COLLAB_LISTENER`s now CAPTURE the touched items (uuid strings at callback
|
||
time; children lifted to their root — `GetParentFootprint()` on pcbnew, the
|
||
symbol/sheet/label/table parent chain on eeschema) into a `g_dirty` set.
|
||
- `flushDiff` emits every dirty root's v2 blob unconditionally (deduped against
|
||
the scalar-diff emits). Rotations, field text edits, pad/zone property edits,
|
||
endpoint drags — anything serializer-visible — now reach the wire. The
|
||
full-model scalar scan REMAINS: it feeds the legacy wire and catches
|
||
eeschema's post-`Push` connectivity cleanup, which never reaches listeners.
|
||
A no-op commit costs one blob serialization; the TS layer drops unchanged
|
||
bodies, so nothing echoes.
|
||
- **05:** `doApplyItems` replaced the global `rebaseline()` with
|
||
`rebaselineTouched(applied uuids)` (pcbnew also refreshes/erases the child
|
||
entries via the baseline's `parent` field) and ends with `scheduleFlush()`.
|
||
A local edit committed while the apply was queued keeps its pre-edit baseline
|
||
and still emits; receiver-side cleanup broadcasts (idempotent on the sender).
|
||
- Global `rebaseline()` clears `g_dirty` (a sheet switch must not leak the old
|
||
sheet's dirty marks into the new room).
|
||
- Legacy `doApply` keeps the global rebaseline (dead in production, apply-leg
|
||
tests only).
|
||
|
||
## Batch 4 — bugs 06 + 07
|
||
|
||
- **06:** new shared primitive `seedDocToY(doc, ydoc, origin, nonce)` writes the
|
||
seed nonce into `kdoc_meta` inside the seed transaction and returns a
|
||
retractor that deletes exactly the layout slots that seed inserted (matched by
|
||
insertion id — this client, this clock window — so slots appended by later
|
||
edits survive; unit-covered). The binding's file-seed branch arbitrates via a
|
||
`kdoc_meta` observer: if a foreign nonce wins the LWW merge, the loser
|
||
retracts, leaving the winner's single clean layout. `kdoc_items` converges per
|
||
key on its own. (Server-side seeding — fix direction 1 — remains the cleaner
|
||
long-term design; this closes the race client-side without backend changes.)
|
||
- **07a:** `bindKicadCollab` gates the DOWN hook with a `destroyed` flag —
|
||
`destroy()` makes the binding inert both ways. Gap emits during a sheet switch
|
||
are dropped; C++ has already rebaselined to the new sheet, so applying them to
|
||
the old room was pure contamination.
|
||
- **07b:** failed `switchTo` now retries with backoff (2s doubling to 30s,
|
||
event-driven, reset on success) instead of leaving the editor unbound forever.
|
||
- Fix direction 2 (generation-tagging the apply envelope for the UP-side
|
||
sub-frame window) was NOT done — still open, low priority.
|
||
|
||
## New findings while verifying (beyond docs 01–16)
|
||
|
||
### F5 — F4 was an artifact of bug 01; file-seeded bodies must be editor-normalized
|
||
|
||
Doc 16's F4 ("drift-detect is item-silent on the green path") only held because
|
||
bug 01 left the differ with NO baseline: the seeder's first edit re-emitted the
|
||
FULL model, silently overwriting the file-seeded Y bodies with the editor's
|
||
serialization. With bug 01 fixed, the pl_editor baseline drifted on every
|
||
file-seeded item (`(name border)` vs `(name "border")`, normalized geometry…).
|
||
Fix: the file-seed branch now uses `snapshotItems()`' RESULT — item bodies are
|
||
re-upserted in the editor's serialization (the steady-state form every future
|
||
emit produces; also what makes `upsertYItem`'s no-op skip effective). Meta +
|
||
layout stay file-derived. Consequence: a room's materialization is
|
||
editor-normalized, not file-verbatim — the bug-06 e2e was updated accordingly
|
||
(convergence + no-duplication, not byte-equality with the raw file render).
|
||
|
||
### F6 — the "asyncify-fragile envelope parse" was actually a layer-name bug
|
||
|
||
The 0008-era decision to keep track/via/zone/text APPLY off the v2 wire cited an
|
||
"asyncify-fragile `(kicad_pcb …)` envelope parse". Root cause found while fixing
|
||
the pcbnew bug-01 two-tab repro (whose apply leg it blocked):
|
||
`wrapInBoardEnvelope` wrote the envelope's layer table with `GetLayerName()`
|
||
(user-visible names), but the parser validates canonical names against the fixed
|
||
layer hash — any board with a layer whose display name differs from its
|
||
canonical one (every KiCad 10 board: "F.Courtyard" vs "F.CrtYd") threw
|
||
`Layer 'B.Courtyard' … is not in fixed layer hash`. Fixed with `LSET::Name()`.
|
||
`makeFromBlob` also no longer swallows parse errors — it logs
|
||
`[collab] pcbnew blob parse error: <IO_ERROR.What()>`, which is how this was
|
||
found. Bare track/via/zone/text changes now apply fine through the envelope
|
||
(verified: via moves converge across two tabs); the items-bridge PCB config's
|
||
"changed footprint only" scope note is stale and those types can gain coverage.
|
||
|
||
## Follow-ups (unchanged from 16, minus what this closed)
|
||
|
||
- Misses 08–10 (non-item state sync, collab-aware undo, drift repair) and
|
||
optimizations 12–14 (beyond what the dirty set already delivers on the emit
|
||
hot path) remain open.
|
||
- Retire (or un-skip) the legacy two-tab specs; add `localEdit` + track/via
|
||
changed coverage to items-bridge (F3 + F6).
|
||
- Bug 07 fix direction 2 (envelope generation tag) if presence/multi-sheet work
|
||
widens the UP-side window.
|
||
- Upstream the `board_commit.cpp` child-removal notification.
|