A board-level graphic text (Place→Text) lands in Drawings() and would otherwise hit the
same asyncify-fragile (kicad_pcb …) envelope-blob wall as via/zone on add. So PCB_TEXT
reconstructs NATIVELY: itemToJson emits size/thickness/angle plus horizontal & vertical
justification, mirror, and bold/italic (the text string was already emitted for any
EDA_TEXT); makeItem builds a fresh PCB_TEXT and restores all of them. flushDiff skips the
blob for PCB_TEXT_T. Footprint child text is unaffected — it syncs by move, and its add
is carried by the footprint blob.
Justification matters: it anchors the glyphs relative to the text POSITION, so without it
a left-justified text reconstructed centered on the peer and rendered visibly offset even
though GetPosition() matched (which is why the anchor-only headless check missed it).
Verified two-tab in the real app: a left/bottom-justified bold text added in tab A
reconstructs in tab B at the exact position with matching justification + string. Headless:
a board-text round-trip add test (sample text is left/bottom-justified) asserts the
position AND that hjust/vjust/text round-trip. 7 passed, 1 skipped, 0 aborts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Footprint add reconstructs from the bare `(footprint …)` s-expr clipboard blob via
CLIPBOARD_IO (SetWriter/SetReader redirect it to a string for headless/wasm). The
non-footprint `(kicad_pcb …)` envelope parse (parseBOARD) is asyncify-fragile in wasm
— commit.Add of an envelope-parsed item silently stages nothing and subsequent virtual
dispatch traps ("index out of bounds"), the same wall that deferred the eeschema symbol
blob — so vias and zones reconstruct NATIVELY instead: itemToJson emits drill/layer-pair
for a PCB_VIA and the outline polygon for a ZONE, and makeItem builds a fresh PCB_VIA /
ZONE. flushDiff attaches the blob only to types that need it (footprints/board graphics);
tracks/vias/zones skip it. Includes the dangling-parent fix for the envelope path
(SetParent before delete) which footprints don't hit.
Build: kicad_clipboard.h pulls in the generated pcb_lexer.h (emitted into the common
build subdir by make_lexer) — added -I${KICAD_BUILD}/common to the embind include path.
Tests: sample board gains a via + zone; snapshot asserts their native fields are emitted,
and three round-trip add tests (footprint via blob, via/zone native) delete then re-add
each by uuid and confirm it returns at the same position. 6 passed, 1 skipped, 0 aborts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug 1 — footprint child text now syncs. forEachTopItem also visits each footprint's
TEXT children by their own uuid (GetFields() PCB_FIELDs + GraphicalItems() PCB_TEXT),
so moving a silkscreen reference/value/user text — which leaves the footprint origin
unchanged — produces a diff. Apply needed only a guard: the removed loop skips items
with a parent footprint (a footprint delete cascades to its children, so don't
double-remove). ResolveItem already resolves child uuids and BOARD_COMMIT::undoLevelItem
rolls a child Modify up to the footprint, so Modify+SetPosition (in the existing
COROUTINE) just works. itemToJson now carries text for EDA_TEXT items.
Bug 2 — kicadCollabTestMoveFirst no longer no-ops. Its CallAfter now runs the move
inside a COROUTINE fiber (like doApply) so the virtual BOARD_ITEM::Move dispatches
instead of asyncify-no-opping.
Tests: sample board gains a footprint with a Reference field + user fp_text; snapshot
test asserts the footprint and both text children are emitted by uuid; a new test
moves each child by uuid and asserts the footprint origin stays put. 3 pass, 1 skipped.
Verified two-tab in the real app: C1's reference text move syncs A->B, no footprint moves.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pcbnew's half of the unified Yjs collab bridge (4th tool; yjs-bridge commit 4),
a near-verbatim port of the eeschema design. Root-repo only — kicad/wxwidgets
submodules untouched.
- wasm/bindings/pcbnew_embind.cpp: BOARD_LISTENER trigger + post-settle snapshot
diff emit; BOARD_COMMIT apply inside a CallAfter + COROUTINE fiber (so a new
item's GAL view->Add dispatches correctly). Move/delete sync for any top-level
item by uuid; native PCB_TRACK add. Footprint/via/zone add deferred.
- WasmTool.tsx: add pcbnew to COLLAB_TOOLS.
- tests/apps/kicad/pcbnew-collab.html: seeded (wizard-free) harness, leaving
pcbnew.html untouched for its wizard test.
- tests/kicad/pcbnew-collab.spec.ts: snapshot + apply(move/remove/add) — 2 pass,
two-tab skipped headless.
Verified two-tab in the real web app: footprint move applies + syncs A->B.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build improvements:
- Change -O0 to -O1 to fix "local count too large" asyncify error
- Add wasm-emscripten-finalize to host (Docker OOMs on large WASM)
- Use -gseparate-dwarf for smaller main binary with debug info
- Build native protoc for code generation
- Add more functions to asyncify removelist
Runtime fixes:
- Add inject-dyncall-shims.sh to fix "dynCall_* is not defined" in Emscripten 4.x
- Update wxwidgets with browserInfo.name fix
New stubs and bindings:
- Add Embind bindings for JavaScript interop
- Add stubs for scripting, API plugin, PCB frame, navlib
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>