pcbjam/wasm/bindings/kicad_editor_embind.cpp

445 lines
16 KiB
C++
Raw Normal View History

feat(wasm): kicad_editor — merge the pcbnew+eeschema kifaces into ONE bundle (Part 2) All four editors (PCB / Footprint / Schematic / Symbol) are now runtime --frame choices of a single kicad_editor.wasm (178 MB at -O1 vs 147+82 separate; shared wx/common/boost linked once). One editor per page load, as before; frames pcb / fpedit / sch / symedit. - wasm/editor/: the merged executable target (single_top + both kiface library sets, whole-archive pcbcommon) + the safety-net focus-walk Kiface() dispatch TU. Gated by KICAD_WASM_MERGED_EDITOR (kicad submodule bump carries the fork side: per-engine Kiface/getter binding + ODR renames + dual-kiface launcher). - wasm/bindings/: per-editor collab entries renamed pcbCollab*/schCollab* (JS names unchanged); duplicate kicadOpenFile/kicadCollabOnSave + shared-name registrations guarded behind KICAD_MERGED_EMBIND; new kicad_editor_embind.cpp registers each shared JS name once, dispatching on the live frame. - Build: kicad_editor app (build wrapper, target case arms, 3-object embind compile with the ABI-critical flags, STUB_APP=pcbnew); docker/build.sh "all" = kicad_editor calculator pl_editor gerbview (pcbnew/eeschema stay as explicit debug apps); scripts/kicad/audit-merged-symbols.sh = repeatable ODR-collision audit (run on kicad bumps). - Frontend: Bundle type (bundle ≠ tool); TOOL_BUNDLE maps all four editors to kicad_editor; explicit --frame tokens for pcbnew (pcb) and eeschema (sch); publish list = the 4 real bundles. - Tests/CI: five harnesses load kicad_editor.js with explicit frame tokens; PCBNEW_FAMILY_SPECS renamed BIG_MODULE_SPECS + the 8 eeschema-family specs (they now boot the merged module — SpiderMonkey x86 CI OOM routing); frame-runtime spec covers all four frames from the one bundle. Validated so far: frame-runtime 4/4 (each frame boots with the right title, no aborts, no duplicate embind registration); 24-spec merged-module regression green; 3D raytracer renders. Known pre-existing failure: 3d-viewer title-bar drag deadlock, fixed on main by 7630c7e (2N+8 pthread pre-warm) — picked up by the follow-up rebase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:48:11 +02:00
/*
* Embind dispatcher for the merged kicad_editor WASM image (editor-unification Part 2).
*
* The pcbnew and eeschema binding TUs each implement the collab bridge for their own
* frame and, standalone, register the SAME JS-facing names. In the merged image both
* TUs are compiled with -DKICAD_MERGED_EMBIND, which
* - compiles out their duplicate frame-agnostic definitions (kicadOpenFile,
* extern "C" kicadCollabOnSave) the single definitions live HERE, and
* - compiles out their shared-name EMSCRIPTEN_BINDINGS registrations registered
* once HERE, dispatching to the renamed per-editor entries (pcbCollab and
* schCollab) on whichever editor frame is live. Per-editor unique names
* (kicadSaveBoard, kicadSaveSchematic, kicadCollabTestItemBlob, Board_) keep
* flowing from the per-editor blocks unchanged.
*
* With the one-frame-per-page-load model exactly one of pcbEditorActive() /
* schEditorActive() is true the same dynamic_cast probe every per-editor entry
* already starts with. JS-facing names and signatures are IDENTICAL to the standalone
* bundles, so the web app and tests need no per-bundle API differences.
*
* Deliberately header-light: no pcbnew/eeschema headers (avoids mixing both include
* roots in one TU); only the generic KIWAY_PLAYER surface + the extern declarations.
*/
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/bind.h>
#include <algorithm>
feat(wasm): kicad_editor — merge the pcbnew+eeschema kifaces into ONE bundle (Part 2) All four editors (PCB / Footprint / Schematic / Symbol) are now runtime --frame choices of a single kicad_editor.wasm (178 MB at -O1 vs 147+82 separate; shared wx/common/boost linked once). One editor per page load, as before; frames pcb / fpedit / sch / symedit. - wasm/editor/: the merged executable target (single_top + both kiface library sets, whole-archive pcbcommon) + the safety-net focus-walk Kiface() dispatch TU. Gated by KICAD_WASM_MERGED_EDITOR (kicad submodule bump carries the fork side: per-engine Kiface/getter binding + ODR renames + dual-kiface launcher). - wasm/bindings/: per-editor collab entries renamed pcbCollab*/schCollab* (JS names unchanged); duplicate kicadOpenFile/kicadCollabOnSave + shared-name registrations guarded behind KICAD_MERGED_EMBIND; new kicad_editor_embind.cpp registers each shared JS name once, dispatching on the live frame. - Build: kicad_editor app (build wrapper, target case arms, 3-object embind compile with the ABI-critical flags, STUB_APP=pcbnew); docker/build.sh "all" = kicad_editor calculator pl_editor gerbview (pcbnew/eeschema stay as explicit debug apps); scripts/kicad/audit-merged-symbols.sh = repeatable ODR-collision audit (run on kicad bumps). - Frontend: Bundle type (bundle ≠ tool); TOOL_BUNDLE maps all four editors to kicad_editor; explicit --frame tokens for pcbnew (pcb) and eeschema (sch); publish list = the 4 real bundles. - Tests/CI: five harnesses load kicad_editor.js with explicit frame tokens; PCBNEW_FAMILY_SPECS renamed BIG_MODULE_SPECS + the 8 eeschema-family specs (they now boot the merged module — SpiderMonkey x86 CI OOM routing); frame-runtime spec covers all four frames from the one bundle. Validated so far: frame-runtime 4/4 (each frame boots with the right title, no aborts, no duplicate embind registration); 24-spec merged-module regression green; 3D raytracer renders. Known pre-existing failure: 3d-viewer title-bar drag deadlock, fixed on main by 7630c7e (2N+8 pthread pre-warm) — picked up by the follow-up rebase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:48:11 +02:00
#include <string>
#include <vector>
#include <wx/app.h>
#include <wx/string.h>
#include <wx/window.h>
#include <wx/frame.h>
#include <wx/menu.h>
#include <wx/statusbr.h>
#include <wx/aui/framemanager.h>
feat(wasm): kicad_editor — merge the pcbnew+eeschema kifaces into ONE bundle (Part 2) All four editors (PCB / Footprint / Schematic / Symbol) are now runtime --frame choices of a single kicad_editor.wasm (178 MB at -O1 vs 147+82 separate; shared wx/common/boost linked once). One editor per page load, as before; frames pcb / fpedit / sch / symedit. - wasm/editor/: the merged executable target (single_top + both kiface library sets, whole-archive pcbcommon) + the safety-net focus-walk Kiface() dispatch TU. Gated by KICAD_WASM_MERGED_EDITOR (kicad submodule bump carries the fork side: per-engine Kiface/getter binding + ODR renames + dual-kiface launcher). - wasm/bindings/: per-editor collab entries renamed pcbCollab*/schCollab* (JS names unchanged); duplicate kicadOpenFile/kicadCollabOnSave + shared-name registrations guarded behind KICAD_MERGED_EMBIND; new kicad_editor_embind.cpp registers each shared JS name once, dispatching on the live frame. - Build: kicad_editor app (build wrapper, target case arms, 3-object embind compile with the ABI-critical flags, STUB_APP=pcbnew); docker/build.sh "all" = kicad_editor calculator pl_editor gerbview (pcbnew/eeschema stay as explicit debug apps); scripts/kicad/audit-merged-symbols.sh = repeatable ODR-collision audit (run on kicad bumps). - Frontend: Bundle type (bundle ≠ tool); TOOL_BUNDLE maps all four editors to kicad_editor; explicit --frame tokens for pcbnew (pcb) and eeschema (sch); publish list = the 4 real bundles. - Tests/CI: five harnesses load kicad_editor.js with explicit frame tokens; PCBNEW_FAMILY_SPECS renamed BIG_MODULE_SPECS + the 8 eeschema-family specs (they now boot the merged module — SpiderMonkey x86 CI OOM routing); frame-runtime spec covers all four frames from the one bundle. Validated so far: frame-runtime 4/4 (each frame boots with the right title, no aborts, no duplicate embind registration); 24-spec merged-module regression green; 3D raytracer renders. Known pre-existing failure: 3d-viewer title-bar drag deadlock, fixed on main by 7630c7e (2N+8 pthread pre-warm) — picked up by the follow-up rebase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:48:11 +02:00
#include <kiway.h>
#include <kiway_player.h>
using namespace emscripten;
// Per-editor entry points and frame probes — defined (with external linkage) in
// pcbnew_embind.cpp / eeschema_embind.cpp.
bool pcbEditorActive();
void pcbCollabApply( std::string aJson );
void pcbCollabApplyItems( std::string aJson );
std::string pcbCollabSnapshot();
std::string pcbCollabSnapshotItems();
std::string pcbCollabTestMoveFirst( int aDx, int aDy );
std::string pcbCollabGetPos( std::string aId );
test(ysync): repro tests for review bugs 01-07 + v2 items-wire e2e port (miss 11) The 2026-07-02 sync review (docs/features/ysync-review, ysync-review branch) found 7 bugs and that the two-tab e2e only exercised the DEAD legacy scalar wire. This lands plan doc 15 in full; results + empirical findings in doc 16. - tests/collab/browser-entry-v2.ts (+build.mjs): the PRODUCTION v2 stack bundled for e2e (connectKicadDoc + attachKicadCollab, kdoc_* keys), with in-page renderActiveDoc/singleSeedRender/driftReport helpers and yjs forced to ONE copy (the two web pnpm workspaces otherwise bundle two instanceof-incompatible instances). - tests/kicad/ysync-two-tab.spec.ts: pl_editor green baseline (A↔B edits, ITEM-level drift silence) + divergent-uuid adopt; bug-01 pcb/ee fresh-room repros (Chromium-only: two kicad_editor tabs exceed Firefox's per-process wasm budget); bug-06 concurrent-seed race; bug-03 Y-half. - tests/kicad/ysync-repros-{pcbnew,eeschema}.spec.ts: bugs 02/03/05 + the bug-04 matrix (anchor-centred fp rotation, pad resize, endpoint drag, symbol rotation, Value-field edit), each with green landed-preconditions; the "local move emits" controls double as headless-emit probes — GREEN on both tools, so every emit-dependent repro is a live test.fail. - wasm/bindings: 7 local-edit test hooks via real commits (CallAfter+COROUTINE) — TestRemoveItem/TestRotateItem (both tools, dispatched in the merged image), TestSetPadSize/TestMoveEndpoint (pcbnew), TestSetFieldText (eeschema). - web/standalone ysync-repros.test.ts: bug-01 units (C++-faithful fake gating emit on ensureBridge) + bug-07a/b (stale DOWN hook, real sheet-manager gap). - web/pcbjam-shared bump: bug-03/06 unit repros. Convention: every repro asserts the CORRECT behavior and is expected-fail (test.fail/it.fails) naming its bug doc; a fix flips it to "unexpected pass", forcing marker removal — the repro becomes the regression test. Every expected failure verified (JSON reporter) to fail at its documented assert. Suite state: 39 passed / 0 failed / 0 flaky / 5 skipped (2 firefox guards, 2 pre-existing legacy two-tab skips, 1 pre-existing roundtrip fixme). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPfrVhfYgPPgtawjSssZfn
2026-07-03 12:43:35 +02:00
bool pcbCollabTestRemoveItem( std::string aId );
bool pcbCollabTestRotateItem( std::string aId, double aDeg );
// Collab-aware undo (ysync miss 09).
bool pcbCollabTestUndo();
int pcbCollabTestUndoDepth();
// Presence (collab-presence 0002) + comment pins/panning (0005).
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
void pcbCollabPresenceStart();
void pcbCollabSetRemote( std::string aJson );
void pcbCollabSetPins( std::string aJson );
void pcbCollabSetViewport( double aCx, double aCy );
void pcbCollabSetStyle( std::string aJson );
std::string pcbCollabTestListItems( int aCount );
std::string pcbCollabTestDemoSet();
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
std::string pcbCollabGetViewport();
std::string pcbCollabGetSelection();
feat(collab): cross-app selection — eeschema symbol ⇄ pcbnew footprint ghost highlight (collab-presence 0006) Selecting a symbol in eeschema ghost-highlights the linked footprint(s) in every pcbnew tab of the project, and vice versa — across users AND one user's own two tabs. Native KIWAY cross-probe is inert in WASM (one frame per page); this rides the presence layer instead. - cross-app.ts: project-wide awareness-only room (presenceRoomId), publishes full PresenceState at selection rate (cursor always null); peers() = other- TOOL clients incl. own user's other tabs; window.__pcbjamCrossApp test handle - presence-kicad.ts: parseSelectionEmit (bare array | {uuids,fpPaths}), xselFromPeerState (pcbnew paths → symbol uuids; eeschema uuids verbatim), cross peers appended to the kicadCollabSetRemote snapshot as {id "<user>#x<client>", name "<user> · sch|pcb", xsel} - C++ (zero fork changes): pcbnew emits {uuids, fpPaths} (FOOTPRINT::GetPath) and ghost-renders xsel via path-tail suffix scan; eeschema resolves xsel via ResolveItem gated to the CURRENT sheet (xsel arrives project-wide, unlike room-scoped selections); ghostStyle = alphas × xselAlphaScale (0.55, tuner- patchable); new exports kicadCollabGetSelectionFull / TestGetCrossMapped / TestSelectComponent (skips power symbols — PWR_FLAG has no footprint) + merged-image dispatch - tests: presence suites extended (payload shape, ghost render pixel tests, 13/13) + new two-tab tests/web/cross-probe.spec.ts (passing vs real partykit); eeschema pixel compares now target the #glcanvas-* GAL panel (the whole-window #canvas compare flaked on the auto-dismissing version infobar — also fixes the long-known presence-eeschema restore flake); cross-app + presence-kicad vitest suites Spec: docs/features/collab-presence/0006 (closed repo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 19:30:18 +02:00
// Cross-app selection (collab-presence 0006).
std::string pcbCollabGetSelectionFull();
std::string pcbCollabTestGetCrossMapped();
std::string pcbCollabTestSelectComponent();
feat(collab): selection soft-locks — remote-selected items can't be dragged locally (collab-presence 0007) While a peer has an item selected, local users can still select it for inspection but move/drag/rotate/delete skip it with an infobar naming the holder (native locked-item UX; enforced via the fork's PCBJAM_REMOTE_LOCK query — kicad 81f9cd80fd, the epic's first fork-touching phase). Overlapping holds (both grabbed inside the awareness propagation window) tie-break deterministically: lowest (user.id, clientID) keeps the item, every losing client auto-releases it. - lock-tiebreak.ts: pure policy — beats(), remoteLocks() (union of ALL other clients' selections incl. own user's other tabs, minus own-held-and-winning uuids so the winner isn't blocked mid-release), contestedReleases() - presence.ts: clients() (per-client view, no user dedupe) + self(); FIX for a pre-existing flaky stack overflow — resolveCollision re-entered itself synchronously via its own patch's awareness 'change' and could ping-pong on stale same-user states (~1-in-3 unit runs); re-entrancy guard defers re-resolution to the next genuine delivery - presence-kicad.ts: locks ride the kicadCollabSetRemote snapshot (`locks:[{uuid,name}]`); losing overlaps call kicadCollabReleaseSelection - wasm bindings (both TUs + merged dispatch): g_locks map + fork query install; kicadCollabReleaseSelection (cancelInteractive only when a tool stack is live — bare ESC would clear the whole selection — then selective RemoveItemFromSel + infobar + forced re-emit); kicadCollabTestGetLocked - tests: lock-tiebreak unit suite; presence-locks e2e for both editors (real move veto — pcbnew click+M hotkey since its default left-drag is rubber-band select, eeschema real drag — each with an unlocked control); two-tab tests/web/locks.spec.ts (lock propagation + deterministic tiebreak release + unlock on clear, passing vs real partykit) Spec: docs/features/collab-presence/0007 (closed repo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 20:35:26 +02:00
// Selection soft-locks (collab-presence 0007).
void pcbCollabReleaseSelection( std::string aUuidsJson, std::string aHolder );
std::string pcbCollabTestGetLocked();
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
std::string pcbCollabTestSelectFirst();
bool pcbCollabTestClearSelection();
feat(wasm): kicad_editor — merge the pcbnew+eeschema kifaces into ONE bundle (Part 2) All four editors (PCB / Footprint / Schematic / Symbol) are now runtime --frame choices of a single kicad_editor.wasm (178 MB at -O1 vs 147+82 separate; shared wx/common/boost linked once). One editor per page load, as before; frames pcb / fpedit / sch / symedit. - wasm/editor/: the merged executable target (single_top + both kiface library sets, whole-archive pcbcommon) + the safety-net focus-walk Kiface() dispatch TU. Gated by KICAD_WASM_MERGED_EDITOR (kicad submodule bump carries the fork side: per-engine Kiface/getter binding + ODR renames + dual-kiface launcher). - wasm/bindings/: per-editor collab entries renamed pcbCollab*/schCollab* (JS names unchanged); duplicate kicadOpenFile/kicadCollabOnSave + shared-name registrations guarded behind KICAD_MERGED_EMBIND; new kicad_editor_embind.cpp registers each shared JS name once, dispatching on the live frame. - Build: kicad_editor app (build wrapper, target case arms, 3-object embind compile with the ABI-critical flags, STUB_APP=pcbnew); docker/build.sh "all" = kicad_editor calculator pl_editor gerbview (pcbnew/eeschema stay as explicit debug apps); scripts/kicad/audit-merged-symbols.sh = repeatable ODR-collision audit (run on kicad bumps). - Frontend: Bundle type (bundle ≠ tool); TOOL_BUNDLE maps all four editors to kicad_editor; explicit --frame tokens for pcbnew (pcb) and eeschema (sch); publish list = the 4 real bundles. - Tests/CI: five harnesses load kicad_editor.js with explicit frame tokens; PCBNEW_FAMILY_SPECS renamed BIG_MODULE_SPECS + the 8 eeschema-family specs (they now boot the merged module — SpiderMonkey x86 CI OOM routing); frame-runtime spec covers all four frames from the one bundle. Validated so far: frame-runtime 4/4 (each frame boots with the right title, no aborts, no duplicate embind registration); 24-spec merged-module regression green; 3D raytracer renders. Known pre-existing failure: 3d-viewer title-bar drag deadlock, fixed on main by 7630c7e (2N+8 pthread pre-warm) — picked up by the follow-up rebase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:48:11 +02:00
bool schEditorActive();
void schCollabApply( std::string aJson );
void schCollabApplyItems( std::string aJson );
std::string schCollabSnapshot();
std::string schCollabSnapshotItems();
std::string schCollabTestMoveFirst( int aDx, int aDy );
std::string schCollabGetPos( std::string aId );
test(ysync): repro tests for review bugs 01-07 + v2 items-wire e2e port (miss 11) The 2026-07-02 sync review (docs/features/ysync-review, ysync-review branch) found 7 bugs and that the two-tab e2e only exercised the DEAD legacy scalar wire. This lands plan doc 15 in full; results + empirical findings in doc 16. - tests/collab/browser-entry-v2.ts (+build.mjs): the PRODUCTION v2 stack bundled for e2e (connectKicadDoc + attachKicadCollab, kdoc_* keys), with in-page renderActiveDoc/singleSeedRender/driftReport helpers and yjs forced to ONE copy (the two web pnpm workspaces otherwise bundle two instanceof-incompatible instances). - tests/kicad/ysync-two-tab.spec.ts: pl_editor green baseline (A↔B edits, ITEM-level drift silence) + divergent-uuid adopt; bug-01 pcb/ee fresh-room repros (Chromium-only: two kicad_editor tabs exceed Firefox's per-process wasm budget); bug-06 concurrent-seed race; bug-03 Y-half. - tests/kicad/ysync-repros-{pcbnew,eeschema}.spec.ts: bugs 02/03/05 + the bug-04 matrix (anchor-centred fp rotation, pad resize, endpoint drag, symbol rotation, Value-field edit), each with green landed-preconditions; the "local move emits" controls double as headless-emit probes — GREEN on both tools, so every emit-dependent repro is a live test.fail. - wasm/bindings: 7 local-edit test hooks via real commits (CallAfter+COROUTINE) — TestRemoveItem/TestRotateItem (both tools, dispatched in the merged image), TestSetPadSize/TestMoveEndpoint (pcbnew), TestSetFieldText (eeschema). - web/standalone ysync-repros.test.ts: bug-01 units (C++-faithful fake gating emit on ensureBridge) + bug-07a/b (stale DOWN hook, real sheet-manager gap). - web/pcbjam-shared bump: bug-03/06 unit repros. Convention: every repro asserts the CORRECT behavior and is expected-fail (test.fail/it.fails) naming its bug doc; a fix flips it to "unexpected pass", forcing marker removal — the repro becomes the regression test. Every expected failure verified (JSON reporter) to fail at its documented assert. Suite state: 39 passed / 0 failed / 0 flaky / 5 skipped (2 firefox guards, 2 pre-existing legacy two-tab skips, 1 pre-existing roundtrip fixme). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPfrVhfYgPPgtawjSssZfn
2026-07-03 12:43:35 +02:00
bool schCollabTestRemoveItem( std::string aId );
bool schCollabTestRotateItem( std::string aId, double aDeg );
// Collab-aware undo (ysync miss 09).
bool schCollabTestUndo();
int schCollabTestUndoDepth();
// Presence (collab-presence 0003 — eeschema counterparts) + pins (0005).
void schCollabPresenceStart();
void schCollabSetRemote( std::string aJson );
void schCollabSetPins( std::string aJson );
void schCollabSetViewport( double aCx, double aCy );
void schCollabSetStyle( std::string aJson );
std::string schCollabTestListItems( int aCount );
std::string schCollabTestDemoSet();
std::string schCollabGetViewport();
std::string schCollabGetSelection();
feat(collab): cross-app selection — eeschema symbol ⇄ pcbnew footprint ghost highlight (collab-presence 0006) Selecting a symbol in eeschema ghost-highlights the linked footprint(s) in every pcbnew tab of the project, and vice versa — across users AND one user's own two tabs. Native KIWAY cross-probe is inert in WASM (one frame per page); this rides the presence layer instead. - cross-app.ts: project-wide awareness-only room (presenceRoomId), publishes full PresenceState at selection rate (cursor always null); peers() = other- TOOL clients incl. own user's other tabs; window.__pcbjamCrossApp test handle - presence-kicad.ts: parseSelectionEmit (bare array | {uuids,fpPaths}), xselFromPeerState (pcbnew paths → symbol uuids; eeschema uuids verbatim), cross peers appended to the kicadCollabSetRemote snapshot as {id "<user>#x<client>", name "<user> · sch|pcb", xsel} - C++ (zero fork changes): pcbnew emits {uuids, fpPaths} (FOOTPRINT::GetPath) and ghost-renders xsel via path-tail suffix scan; eeschema resolves xsel via ResolveItem gated to the CURRENT sheet (xsel arrives project-wide, unlike room-scoped selections); ghostStyle = alphas × xselAlphaScale (0.55, tuner- patchable); new exports kicadCollabGetSelectionFull / TestGetCrossMapped / TestSelectComponent (skips power symbols — PWR_FLAG has no footprint) + merged-image dispatch - tests: presence suites extended (payload shape, ghost render pixel tests, 13/13) + new two-tab tests/web/cross-probe.spec.ts (passing vs real partykit); eeschema pixel compares now target the #glcanvas-* GAL panel (the whole-window #canvas compare flaked on the auto-dismissing version infobar — also fixes the long-known presence-eeschema restore flake); cross-app + presence-kicad vitest suites Spec: docs/features/collab-presence/0006 (closed repo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 19:30:18 +02:00
// Cross-app selection (collab-presence 0006).
std::string schCollabGetSelectionFull();
std::string schCollabTestGetCrossMapped();
std::string schCollabTestSelectComponent();
feat(collab): selection soft-locks — remote-selected items can't be dragged locally (collab-presence 0007) While a peer has an item selected, local users can still select it for inspection but move/drag/rotate/delete skip it with an infobar naming the holder (native locked-item UX; enforced via the fork's PCBJAM_REMOTE_LOCK query — kicad 81f9cd80fd, the epic's first fork-touching phase). Overlapping holds (both grabbed inside the awareness propagation window) tie-break deterministically: lowest (user.id, clientID) keeps the item, every losing client auto-releases it. - lock-tiebreak.ts: pure policy — beats(), remoteLocks() (union of ALL other clients' selections incl. own user's other tabs, minus own-held-and-winning uuids so the winner isn't blocked mid-release), contestedReleases() - presence.ts: clients() (per-client view, no user dedupe) + self(); FIX for a pre-existing flaky stack overflow — resolveCollision re-entered itself synchronously via its own patch's awareness 'change' and could ping-pong on stale same-user states (~1-in-3 unit runs); re-entrancy guard defers re-resolution to the next genuine delivery - presence-kicad.ts: locks ride the kicadCollabSetRemote snapshot (`locks:[{uuid,name}]`); losing overlaps call kicadCollabReleaseSelection - wasm bindings (both TUs + merged dispatch): g_locks map + fork query install; kicadCollabReleaseSelection (cancelInteractive only when a tool stack is live — bare ESC would clear the whole selection — then selective RemoveItemFromSel + infobar + forced re-emit); kicadCollabTestGetLocked - tests: lock-tiebreak unit suite; presence-locks e2e for both editors (real move veto — pcbnew click+M hotkey since its default left-drag is rubber-band select, eeschema real drag — each with an unlocked control); two-tab tests/web/locks.spec.ts (lock propagation + deterministic tiebreak release + unlock on clear, passing vs real partykit) Spec: docs/features/collab-presence/0007 (closed repo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 20:35:26 +02:00
// Selection soft-locks (collab-presence 0007).
void schCollabReleaseSelection( std::string aUuidsJson, std::string aHolder );
std::string schCollabTestGetLocked();
std::string schCollabTestSelectFirst();
bool schCollabTestClearSelection();
feat(wasm): kicad_editor — merge the pcbnew+eeschema kifaces into ONE bundle (Part 2) All four editors (PCB / Footprint / Schematic / Symbol) are now runtime --frame choices of a single kicad_editor.wasm (178 MB at -O1 vs 147+82 separate; shared wx/common/boost linked once). One editor per page load, as before; frames pcb / fpedit / sch / symedit. - wasm/editor/: the merged executable target (single_top + both kiface library sets, whole-archive pcbcommon) + the safety-net focus-walk Kiface() dispatch TU. Gated by KICAD_WASM_MERGED_EDITOR (kicad submodule bump carries the fork side: per-engine Kiface/getter binding + ODR renames + dual-kiface launcher). - wasm/bindings/: per-editor collab entries renamed pcbCollab*/schCollab* (JS names unchanged); duplicate kicadOpenFile/kicadCollabOnSave + shared-name registrations guarded behind KICAD_MERGED_EMBIND; new kicad_editor_embind.cpp registers each shared JS name once, dispatching on the live frame. - Build: kicad_editor app (build wrapper, target case arms, 3-object embind compile with the ABI-critical flags, STUB_APP=pcbnew); docker/build.sh "all" = kicad_editor calculator pl_editor gerbview (pcbnew/eeschema stay as explicit debug apps); scripts/kicad/audit-merged-symbols.sh = repeatable ODR-collision audit (run on kicad bumps). - Frontend: Bundle type (bundle ≠ tool); TOOL_BUNDLE maps all four editors to kicad_editor; explicit --frame tokens for pcbnew (pcb) and eeschema (sch); publish list = the 4 real bundles. - Tests/CI: five harnesses load kicad_editor.js with explicit frame tokens; PCBNEW_FAMILY_SPECS renamed BIG_MODULE_SPECS + the 8 eeschema-family specs (they now boot the merged module — SpiderMonkey x86 CI OOM routing); frame-runtime spec covers all four frames from the one bundle. Validated so far: frame-runtime 4/4 (each frame boots with the right title, no aborts, no duplicate embind registration); 24-spec merged-module regression green; 3D raytracer renders. Known pre-existing failure: 3d-viewer title-bar drag deadlock, fixed on main by 7630c7e (2N+8 pthread pre-warm) — picked up by the follow-up rebase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:48:11 +02:00
// Programmatically open a project file in the running editor frame, without UI
// automation. Frame-agnostic (any KIWAY_PLAYER); byte-identical to the definition the
// standalone bundles compile from their own binding TU.
static bool kicadOpenFile( std::string path )
{
KIWAY_PLAYER* frame =
wxTheApp ? static_cast<KIWAY_PLAYER*>( wxTheApp->GetTopWindow() ) : nullptr;
if( !frame )
return false;
if( wxWindow* blocking = frame->Kiway().GetBlockingDialog() )
blocking->Close( true );
return frame->OpenProjectFiles(
std::vector<wxString>( 1, wxString::FromUTF8( path.c_str() ) ) );
}
// Canvas-only chrome toggle (features/mobile): hide/show every AUI pane
// except the central draw canvas, plus the menubar and status bar, so the GAL
// canvas fills the frame. Generic wxFrame/wxAui surface only (keeps this TU
// header-light and serves both editor frames). Hidden bars release their space
// because the wasm port's frame client-area math skips !IsShown() bars (native
// parity, see wxwidgets/src/wasm/frame.cpp). Returns false until the editor
// frame exists — main() builds it after runtime init — so JS polls this.
// Hide-time visibility snapshot. KiCad keeps several panes hidden by default
// (Search, Properties, Net Inspector, …), so a blanket Show(true) on restore
// would surface panes the user never had open — restore only what the hide
// actually took away. Keyed to the frame so a snapshot never leaks onto a
// different frame's wxAuiManager.
static struct
{
wxFrame* frame = nullptr;
bool valid = false;
bool menuShown = false;
bool statusShown = false;
std::vector<wxString> paneNames;
} s_chromeSnap;
static bool chromeSkipsPane( const wxAuiPaneInfo& aPane )
{
// keep the central editor canvas (named "DrawFrame" in both editors)
return aPane.dock_direction == wxAUI_DOCK_CENTER || aPane.name == wxT( "DrawFrame" );
}
static bool kicadSetChrome( bool aShow )
{
wxFrame* frame =
wxTheApp ? dynamic_cast<wxFrame*>( wxTheApp->GetTopWindow() ) : nullptr;
if( !frame )
return false;
wxMenuBar* menuBar = frame->GetMenuBar();
wxStatusBar* statusBar = frame->GetStatusBar();
wxAuiManager* mgr = wxAuiManager::GetManager( frame );
if( !aShow )
{
// A repeated hide keeps the original snapshot (idempotent).
if( !s_chromeSnap.valid || s_chromeSnap.frame != frame )
{
s_chromeSnap.frame = frame;
s_chromeSnap.menuShown = menuBar && menuBar->IsShown();
s_chromeSnap.statusShown = statusBar && statusBar->IsShown();
s_chromeSnap.paneNames.clear();
if( mgr )
{
wxAuiPaneInfoArray& panes = mgr->GetAllPanes();
for( size_t i = 0; i < panes.GetCount(); ++i )
{
wxAuiPaneInfo& pane = panes.Item( i );
if( !chromeSkipsPane( pane ) && pane.IsShown() )
s_chromeSnap.paneNames.push_back( pane.name );
}
}
s_chromeSnap.valid = true;
}
}
// A show with no snapshot (or one taken on a different frame) falls back
// to revealing the standard chrome instead of obeying stale state.
const bool haveSnap = s_chromeSnap.valid && s_chromeSnap.frame == frame;
if( menuBar )
menuBar->Show( aShow && ( haveSnap ? s_chromeSnap.menuShown : true ) );
// Kept alive rather than detached: KiCad SetStatusText()s on every cursor
// move, and wxFrameBase wxCHECKs a null status bar.
if( statusBar )
statusBar->Show( aShow && ( haveSnap ? s_chromeSnap.statusShown : true ) );
if( mgr )
{
wxAuiPaneInfoArray& panes = mgr->GetAllPanes();
for( size_t i = 0; i < panes.GetCount(); ++i )
{
wxAuiPaneInfo& pane = panes.Item( i );
if( chromeSkipsPane( pane ) )
continue;
if( !aShow )
{
pane.Show( false );
}
else if( haveSnap )
{
if( std::find( s_chromeSnap.paneNames.begin(), s_chromeSnap.paneNames.end(),
pane.name )
!= s_chromeSnap.paneNames.end() )
{
pane.Show( true );
}
}
else if( pane.IsToolbar() )
{
// Show with no (or a stale, other-frame) snapshot: reveal the
// toolbars only — blanket-showing plain panels would surface
// the default-hidden ones.
pane.Show( true );
}
}
mgr->Update();
}
if( aShow )
s_chromeSnap.valid = false;
frame->SendSizeEvent();
return true;
}
feat(wasm): kicad_editor — merge the pcbnew+eeschema kifaces into ONE bundle (Part 2) All four editors (PCB / Footprint / Schematic / Symbol) are now runtime --frame choices of a single kicad_editor.wasm (178 MB at -O1 vs 147+82 separate; shared wx/common/boost linked once). One editor per page load, as before; frames pcb / fpedit / sch / symedit. - wasm/editor/: the merged executable target (single_top + both kiface library sets, whole-archive pcbcommon) + the safety-net focus-walk Kiface() dispatch TU. Gated by KICAD_WASM_MERGED_EDITOR (kicad submodule bump carries the fork side: per-engine Kiface/getter binding + ODR renames + dual-kiface launcher). - wasm/bindings/: per-editor collab entries renamed pcbCollab*/schCollab* (JS names unchanged); duplicate kicadOpenFile/kicadCollabOnSave + shared-name registrations guarded behind KICAD_MERGED_EMBIND; new kicad_editor_embind.cpp registers each shared JS name once, dispatching on the live frame. - Build: kicad_editor app (build wrapper, target case arms, 3-object embind compile with the ABI-critical flags, STUB_APP=pcbnew); docker/build.sh "all" = kicad_editor calculator pl_editor gerbview (pcbnew/eeschema stay as explicit debug apps); scripts/kicad/audit-merged-symbols.sh = repeatable ODR-collision audit (run on kicad bumps). - Frontend: Bundle type (bundle ≠ tool); TOOL_BUNDLE maps all four editors to kicad_editor; explicit --frame tokens for pcbnew (pcb) and eeschema (sch); publish list = the 4 real bundles. - Tests/CI: five harnesses load kicad_editor.js with explicit frame tokens; PCBNEW_FAMILY_SPECS renamed BIG_MODULE_SPECS + the 8 eeschema-family specs (they now boot the merged module — SpiderMonkey x86 CI OOM routing); frame-runtime spec covers all four frames from the one bundle. Validated so far: frame-runtime 4/4 (each frame boots with the right title, no aborts, no duplicate embind registration); 24-spec merged-module regression green; 3D raytracer renders. Known pre-existing failure: 3d-viewer title-bar drag deadlock, fixed on main by 7630c7e (2N+8 pthread pre-warm) — picked up by the follow-up rebase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:48:11 +02:00
// C++ → JS save notification. Called from BOTH fork save chokepoints
// (PCB_EDIT_FRAME::SavePcbFile and SCH_EDIT_FRAME::saveSchematicFile) — one shared
// definition serves the merged image. No-op without a JS listener.
extern "C" void kicadCollabOnSave( const char* aPath )
{
EM_ASM( {
if( window.kicadCollab && window.kicadCollab.onSave )
window.kicadCollab.onSave( UTF8ToString( $0 ) );
}, aPath );
}
// Dispatch shims: route each shared JS name to the live editor's implementation.
// The sch path is the fallback arm so a JS call with NO frame up behaves like the
// standalone bundles (the per-editor impls no-op / return empty on a null frame).
static void collabApply( std::string aJson )
{
pcbEditorActive() ? pcbCollabApply( aJson ) : schCollabApply( aJson );
}
static void collabApplyItems( std::string aJson )
{
pcbEditorActive() ? pcbCollabApplyItems( aJson ) : schCollabApplyItems( aJson );
}
static std::string collabSnapshot()
{
return pcbEditorActive() ? pcbCollabSnapshot() : schCollabSnapshot();
}
static std::string collabSnapshotItems()
{
return pcbEditorActive() ? pcbCollabSnapshotItems() : schCollabSnapshotItems();
}
static std::string collabTestMoveFirst( int aDx, int aDy )
{
return pcbEditorActive() ? pcbCollabTestMoveFirst( aDx, aDy )
: schCollabTestMoveFirst( aDx, aDy );
}
static std::string collabGetPos( std::string aId )
{
return pcbEditorActive() ? pcbCollabGetPos( aId ) : schCollabGetPos( aId );
}
test(ysync): repro tests for review bugs 01-07 + v2 items-wire e2e port (miss 11) The 2026-07-02 sync review (docs/features/ysync-review, ysync-review branch) found 7 bugs and that the two-tab e2e only exercised the DEAD legacy scalar wire. This lands plan doc 15 in full; results + empirical findings in doc 16. - tests/collab/browser-entry-v2.ts (+build.mjs): the PRODUCTION v2 stack bundled for e2e (connectKicadDoc + attachKicadCollab, kdoc_* keys), with in-page renderActiveDoc/singleSeedRender/driftReport helpers and yjs forced to ONE copy (the two web pnpm workspaces otherwise bundle two instanceof-incompatible instances). - tests/kicad/ysync-two-tab.spec.ts: pl_editor green baseline (A↔B edits, ITEM-level drift silence) + divergent-uuid adopt; bug-01 pcb/ee fresh-room repros (Chromium-only: two kicad_editor tabs exceed Firefox's per-process wasm budget); bug-06 concurrent-seed race; bug-03 Y-half. - tests/kicad/ysync-repros-{pcbnew,eeschema}.spec.ts: bugs 02/03/05 + the bug-04 matrix (anchor-centred fp rotation, pad resize, endpoint drag, symbol rotation, Value-field edit), each with green landed-preconditions; the "local move emits" controls double as headless-emit probes — GREEN on both tools, so every emit-dependent repro is a live test.fail. - wasm/bindings: 7 local-edit test hooks via real commits (CallAfter+COROUTINE) — TestRemoveItem/TestRotateItem (both tools, dispatched in the merged image), TestSetPadSize/TestMoveEndpoint (pcbnew), TestSetFieldText (eeschema). - web/standalone ysync-repros.test.ts: bug-01 units (C++-faithful fake gating emit on ensureBridge) + bug-07a/b (stale DOWN hook, real sheet-manager gap). - web/pcbjam-shared bump: bug-03/06 unit repros. Convention: every repro asserts the CORRECT behavior and is expected-fail (test.fail/it.fails) naming its bug doc; a fix flips it to "unexpected pass", forcing marker removal — the repro becomes the regression test. Every expected failure verified (JSON reporter) to fail at its documented assert. Suite state: 39 passed / 0 failed / 0 flaky / 5 skipped (2 firefox guards, 2 pre-existing legacy two-tab skips, 1 pre-existing roundtrip fixme). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPfrVhfYgPPgtawjSssZfn
2026-07-03 12:43:35 +02:00
static bool collabTestRemoveItem( std::string aId )
{
return pcbEditorActive() ? pcbCollabTestRemoveItem( aId ) : schCollabTestRemoveItem( aId );
}
static bool collabTestRotateItem( std::string aId, double aDeg )
{
return pcbEditorActive() ? pcbCollabTestRotateItem( aId, aDeg )
: schCollabTestRotateItem( aId, aDeg );
}
static bool collabTestUndo()
{
return pcbEditorActive() ? pcbCollabTestUndo() : schCollabTestUndo();
}
static int collabTestUndoDepth()
{
return pcbEditorActive() ? pcbCollabTestUndoDepth() : schCollabTestUndoDepth();
}
// Presence shims (collab-presence 0002 pcbnew / 0003 eeschema): route to the live
// editor's implementation, same pattern as the collab bridge shims above.
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
static void collabPresenceStart()
{
pcbEditorActive() ? pcbCollabPresenceStart() : schCollabPresenceStart();
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
}
static void collabSetRemote( std::string aJson )
{
pcbEditorActive() ? pcbCollabSetRemote( aJson ) : schCollabSetRemote( aJson );
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
}
static void collabSetPins( std::string aJson )
{
pcbEditorActive() ? pcbCollabSetPins( aJson ) : schCollabSetPins( aJson );
}
static void collabSetViewport( double aCx, double aCy )
{
pcbEditorActive() ? pcbCollabSetViewport( aCx, aCy ) : schCollabSetViewport( aCx, aCy );
}
static void collabSetStyle( std::string aJson )
{
pcbEditorActive() ? pcbCollabSetStyle( aJson ) : schCollabSetStyle( aJson );
}
static std::string collabTestListItems( int aCount )
{
return pcbEditorActive() ? pcbCollabTestListItems( aCount ) : schCollabTestListItems( aCount );
}
static std::string collabTestDemoSet()
{
return pcbEditorActive() ? pcbCollabTestDemoSet() : schCollabTestDemoSet();
}
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
static std::string collabGetViewport()
{
return pcbEditorActive() ? pcbCollabGetViewport() : schCollabGetViewport();
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
}
static std::string collabGetSelection()
{
return pcbEditorActive() ? pcbCollabGetSelection() : schCollabGetSelection();
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
}
feat(collab): cross-app selection — eeschema symbol ⇄ pcbnew footprint ghost highlight (collab-presence 0006) Selecting a symbol in eeschema ghost-highlights the linked footprint(s) in every pcbnew tab of the project, and vice versa — across users AND one user's own two tabs. Native KIWAY cross-probe is inert in WASM (one frame per page); this rides the presence layer instead. - cross-app.ts: project-wide awareness-only room (presenceRoomId), publishes full PresenceState at selection rate (cursor always null); peers() = other- TOOL clients incl. own user's other tabs; window.__pcbjamCrossApp test handle - presence-kicad.ts: parseSelectionEmit (bare array | {uuids,fpPaths}), xselFromPeerState (pcbnew paths → symbol uuids; eeschema uuids verbatim), cross peers appended to the kicadCollabSetRemote snapshot as {id "<user>#x<client>", name "<user> · sch|pcb", xsel} - C++ (zero fork changes): pcbnew emits {uuids, fpPaths} (FOOTPRINT::GetPath) and ghost-renders xsel via path-tail suffix scan; eeschema resolves xsel via ResolveItem gated to the CURRENT sheet (xsel arrives project-wide, unlike room-scoped selections); ghostStyle = alphas × xselAlphaScale (0.55, tuner- patchable); new exports kicadCollabGetSelectionFull / TestGetCrossMapped / TestSelectComponent (skips power symbols — PWR_FLAG has no footprint) + merged-image dispatch - tests: presence suites extended (payload shape, ghost render pixel tests, 13/13) + new two-tab tests/web/cross-probe.spec.ts (passing vs real partykit); eeschema pixel compares now target the #glcanvas-* GAL panel (the whole-window #canvas compare flaked on the auto-dismissing version infobar — also fixes the long-known presence-eeschema restore flake); cross-app + presence-kicad vitest suites Spec: docs/features/collab-presence/0006 (closed repo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 19:30:18 +02:00
static std::string collabGetSelectionFull()
{
return pcbEditorActive() ? pcbCollabGetSelectionFull() : schCollabGetSelectionFull();
}
static std::string collabTestGetCrossMapped()
{
return pcbEditorActive() ? pcbCollabTestGetCrossMapped() : schCollabTestGetCrossMapped();
}
static std::string collabTestSelectComponent()
{
return pcbEditorActive() ? pcbCollabTestSelectComponent() : schCollabTestSelectComponent();
}
feat(collab): selection soft-locks — remote-selected items can't be dragged locally (collab-presence 0007) While a peer has an item selected, local users can still select it for inspection but move/drag/rotate/delete skip it with an infobar naming the holder (native locked-item UX; enforced via the fork's PCBJAM_REMOTE_LOCK query — kicad 81f9cd80fd, the epic's first fork-touching phase). Overlapping holds (both grabbed inside the awareness propagation window) tie-break deterministically: lowest (user.id, clientID) keeps the item, every losing client auto-releases it. - lock-tiebreak.ts: pure policy — beats(), remoteLocks() (union of ALL other clients' selections incl. own user's other tabs, minus own-held-and-winning uuids so the winner isn't blocked mid-release), contestedReleases() - presence.ts: clients() (per-client view, no user dedupe) + self(); FIX for a pre-existing flaky stack overflow — resolveCollision re-entered itself synchronously via its own patch's awareness 'change' and could ping-pong on stale same-user states (~1-in-3 unit runs); re-entrancy guard defers re-resolution to the next genuine delivery - presence-kicad.ts: locks ride the kicadCollabSetRemote snapshot (`locks:[{uuid,name}]`); losing overlaps call kicadCollabReleaseSelection - wasm bindings (both TUs + merged dispatch): g_locks map + fork query install; kicadCollabReleaseSelection (cancelInteractive only when a tool stack is live — bare ESC would clear the whole selection — then selective RemoveItemFromSel + infobar + forced re-emit); kicadCollabTestGetLocked - tests: lock-tiebreak unit suite; presence-locks e2e for both editors (real move veto — pcbnew click+M hotkey since its default left-drag is rubber-band select, eeschema real drag — each with an unlocked control); two-tab tests/web/locks.spec.ts (lock propagation + deterministic tiebreak release + unlock on clear, passing vs real partykit) Spec: docs/features/collab-presence/0007 (closed repo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 20:35:26 +02:00
static void collabReleaseSelection( std::string aUuidsJson, std::string aHolder )
{
pcbEditorActive() ? pcbCollabReleaseSelection( aUuidsJson, aHolder )
: schCollabReleaseSelection( aUuidsJson, aHolder );
}
static std::string collabTestGetLocked()
{
return pcbEditorActive() ? pcbCollabTestGetLocked() : schCollabTestGetLocked();
}
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
static std::string collabTestSelectFirst()
{
return pcbEditorActive() ? pcbCollabTestSelectFirst() : schCollabTestSelectFirst();
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
}
static bool collabTestClearSelection()
{
return pcbEditorActive() ? pcbCollabTestClearSelection() : schCollabTestClearSelection();
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
}
feat(wasm): kicad_editor — merge the pcbnew+eeschema kifaces into ONE bundle (Part 2) All four editors (PCB / Footprint / Schematic / Symbol) are now runtime --frame choices of a single kicad_editor.wasm (178 MB at -O1 vs 147+82 separate; shared wx/common/boost linked once). One editor per page load, as before; frames pcb / fpedit / sch / symedit. - wasm/editor/: the merged executable target (single_top + both kiface library sets, whole-archive pcbcommon) + the safety-net focus-walk Kiface() dispatch TU. Gated by KICAD_WASM_MERGED_EDITOR (kicad submodule bump carries the fork side: per-engine Kiface/getter binding + ODR renames + dual-kiface launcher). - wasm/bindings/: per-editor collab entries renamed pcbCollab*/schCollab* (JS names unchanged); duplicate kicadOpenFile/kicadCollabOnSave + shared-name registrations guarded behind KICAD_MERGED_EMBIND; new kicad_editor_embind.cpp registers each shared JS name once, dispatching on the live frame. - Build: kicad_editor app (build wrapper, target case arms, 3-object embind compile with the ABI-critical flags, STUB_APP=pcbnew); docker/build.sh "all" = kicad_editor calculator pl_editor gerbview (pcbnew/eeschema stay as explicit debug apps); scripts/kicad/audit-merged-symbols.sh = repeatable ODR-collision audit (run on kicad bumps). - Frontend: Bundle type (bundle ≠ tool); TOOL_BUNDLE maps all four editors to kicad_editor; explicit --frame tokens for pcbnew (pcb) and eeschema (sch); publish list = the 4 real bundles. - Tests/CI: five harnesses load kicad_editor.js with explicit frame tokens; PCBNEW_FAMILY_SPECS renamed BIG_MODULE_SPECS + the 8 eeschema-family specs (they now boot the merged module — SpiderMonkey x86 CI OOM routing); frame-runtime spec covers all four frames from the one bundle. Validated so far: frame-runtime 4/4 (each frame boots with the right title, no aborts, no duplicate embind registration); 24-spec merged-module regression green; 3D raytracer renders. Known pre-existing failure: 3d-viewer title-bar drag deadlock, fixed on main by 7630c7e (2N+8 pthread pre-warm) — picked up by the follow-up rebase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:48:11 +02:00
EMSCRIPTEN_BINDINGS(kicad_editor) {
// Programmatic file open (preferred over UI automation from the web app).
function("kicadOpenFile", &kicadOpenFile);
// Canvas-only mobile mode (features/mobile).
function("kicadSetChrome", &kicadSetChrome);
feat(wasm): kicad_editor — merge the pcbnew+eeschema kifaces into ONE bundle (Part 2) All four editors (PCB / Footprint / Schematic / Symbol) are now runtime --frame choices of a single kicad_editor.wasm (178 MB at -O1 vs 147+82 separate; shared wx/common/boost linked once). One editor per page load, as before; frames pcb / fpedit / sch / symedit. - wasm/editor/: the merged executable target (single_top + both kiface library sets, whole-archive pcbcommon) + the safety-net focus-walk Kiface() dispatch TU. Gated by KICAD_WASM_MERGED_EDITOR (kicad submodule bump carries the fork side: per-engine Kiface/getter binding + ODR renames + dual-kiface launcher). - wasm/bindings/: per-editor collab entries renamed pcbCollab*/schCollab* (JS names unchanged); duplicate kicadOpenFile/kicadCollabOnSave + shared-name registrations guarded behind KICAD_MERGED_EMBIND; new kicad_editor_embind.cpp registers each shared JS name once, dispatching on the live frame. - Build: kicad_editor app (build wrapper, target case arms, 3-object embind compile with the ABI-critical flags, STUB_APP=pcbnew); docker/build.sh "all" = kicad_editor calculator pl_editor gerbview (pcbnew/eeschema stay as explicit debug apps); scripts/kicad/audit-merged-symbols.sh = repeatable ODR-collision audit (run on kicad bumps). - Frontend: Bundle type (bundle ≠ tool); TOOL_BUNDLE maps all four editors to kicad_editor; explicit --frame tokens for pcbnew (pcb) and eeschema (sch); publish list = the 4 real bundles. - Tests/CI: five harnesses load kicad_editor.js with explicit frame tokens; PCBNEW_FAMILY_SPECS renamed BIG_MODULE_SPECS + the 8 eeschema-family specs (they now boot the merged module — SpiderMonkey x86 CI OOM routing); frame-runtime spec covers all four frames from the one bundle. Validated so far: frame-runtime 4/4 (each frame boots with the right title, no aborts, no duplicate embind registration); 24-spec merged-module regression green; 3D raytracer renders. Known pre-existing failure: 3d-viewer title-bar drag deadlock, fixed on main by 7630c7e (2N+8 pthread pre-warm) — picked up by the follow-up rebase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:48:11 +02:00
// Yjs collaborative bridge entry points — same JS contract as the standalone
// bundles, dispatched on the active editor frame.
function("kicadCollabApply", &collabApply);
function("kicadCollabSnapshot", &collabSnapshot);
function("kicadCollabApplyItems", &collabApplyItems);
function("kicadCollabSnapshotItems", &collabSnapshotItems);
function("kicadCollabTestMoveFirst", &collabTestMoveFirst);
function("kicadCollabGetPos", &collabGetPos);
test(ysync): repro tests for review bugs 01-07 + v2 items-wire e2e port (miss 11) The 2026-07-02 sync review (docs/features/ysync-review, ysync-review branch) found 7 bugs and that the two-tab e2e only exercised the DEAD legacy scalar wire. This lands plan doc 15 in full; results + empirical findings in doc 16. - tests/collab/browser-entry-v2.ts (+build.mjs): the PRODUCTION v2 stack bundled for e2e (connectKicadDoc + attachKicadCollab, kdoc_* keys), with in-page renderActiveDoc/singleSeedRender/driftReport helpers and yjs forced to ONE copy (the two web pnpm workspaces otherwise bundle two instanceof-incompatible instances). - tests/kicad/ysync-two-tab.spec.ts: pl_editor green baseline (A↔B edits, ITEM-level drift silence) + divergent-uuid adopt; bug-01 pcb/ee fresh-room repros (Chromium-only: two kicad_editor tabs exceed Firefox's per-process wasm budget); bug-06 concurrent-seed race; bug-03 Y-half. - tests/kicad/ysync-repros-{pcbnew,eeschema}.spec.ts: bugs 02/03/05 + the bug-04 matrix (anchor-centred fp rotation, pad resize, endpoint drag, symbol rotation, Value-field edit), each with green landed-preconditions; the "local move emits" controls double as headless-emit probes — GREEN on both tools, so every emit-dependent repro is a live test.fail. - wasm/bindings: 7 local-edit test hooks via real commits (CallAfter+COROUTINE) — TestRemoveItem/TestRotateItem (both tools, dispatched in the merged image), TestSetPadSize/TestMoveEndpoint (pcbnew), TestSetFieldText (eeschema). - web/standalone ysync-repros.test.ts: bug-01 units (C++-faithful fake gating emit on ensureBridge) + bug-07a/b (stale DOWN hook, real sheet-manager gap). - web/pcbjam-shared bump: bug-03/06 unit repros. Convention: every repro asserts the CORRECT behavior and is expected-fail (test.fail/it.fails) naming its bug doc; a fix flips it to "unexpected pass", forcing marker removal — the repro becomes the regression test. Every expected failure verified (JSON reporter) to fail at its documented assert. Suite state: 39 passed / 0 failed / 0 flaky / 5 skipped (2 firefox guards, 2 pre-existing legacy two-tab skips, 1 pre-existing roundtrip fixme). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DPfrVhfYgPPgtawjSssZfn
2026-07-03 12:43:35 +02:00
// ysync-review repro hooks (shared names; per-editor-only hooks — pad size,
// endpoint, field text — flow from the per-editor blocks unchanged).
function("kicadCollabTestRemoveItem", &collabTestRemoveItem);
function("kicadCollabTestRotateItem", &collabTestRotateItem);
// Collab-aware undo (ysync miss 09).
function("kicadCollabTestUndo", &collabTestUndo);
function("kicadCollabTestUndoDepth", &collabTestUndoDepth);
// Presence (collab-presence 0002/0003) + comment pins/panning (0005).
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
function("kicadCollabPresenceStart", &collabPresenceStart);
function("kicadCollabSetRemote", &collabSetRemote);
function("kicadCollabSetPins", &collabSetPins);
function("kicadCollabSetViewport", &collabSetViewport);
function("kicadCollabSetStyle", &collabSetStyle);
function("kicadCollabTestListItems", &collabTestListItems);
function("kicadCollabTestDemoSet", &collabTestDemoSet);
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
function("kicadCollabGetViewport", &collabGetViewport);
function("kicadCollabGetSelection", &collabGetSelection);
feat(collab): cross-app selection — eeschema symbol ⇄ pcbnew footprint ghost highlight (collab-presence 0006) Selecting a symbol in eeschema ghost-highlights the linked footprint(s) in every pcbnew tab of the project, and vice versa — across users AND one user's own two tabs. Native KIWAY cross-probe is inert in WASM (one frame per page); this rides the presence layer instead. - cross-app.ts: project-wide awareness-only room (presenceRoomId), publishes full PresenceState at selection rate (cursor always null); peers() = other- TOOL clients incl. own user's other tabs; window.__pcbjamCrossApp test handle - presence-kicad.ts: parseSelectionEmit (bare array | {uuids,fpPaths}), xselFromPeerState (pcbnew paths → symbol uuids; eeschema uuids verbatim), cross peers appended to the kicadCollabSetRemote snapshot as {id "<user>#x<client>", name "<user> · sch|pcb", xsel} - C++ (zero fork changes): pcbnew emits {uuids, fpPaths} (FOOTPRINT::GetPath) and ghost-renders xsel via path-tail suffix scan; eeschema resolves xsel via ResolveItem gated to the CURRENT sheet (xsel arrives project-wide, unlike room-scoped selections); ghostStyle = alphas × xselAlphaScale (0.55, tuner- patchable); new exports kicadCollabGetSelectionFull / TestGetCrossMapped / TestSelectComponent (skips power symbols — PWR_FLAG has no footprint) + merged-image dispatch - tests: presence suites extended (payload shape, ghost render pixel tests, 13/13) + new two-tab tests/web/cross-probe.spec.ts (passing vs real partykit); eeschema pixel compares now target the #glcanvas-* GAL panel (the whole-window #canvas compare flaked on the auto-dismissing version infobar — also fixes the long-known presence-eeschema restore flake); cross-app + presence-kicad vitest suites Spec: docs/features/collab-presence/0006 (closed repo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 19:30:18 +02:00
// Cross-app selection (collab-presence 0006).
function("kicadCollabGetSelectionFull", &collabGetSelectionFull);
function("kicadCollabTestGetCrossMapped", &collabTestGetCrossMapped);
function("kicadCollabTestSelectComponent", &collabTestSelectComponent);
feat(collab): selection soft-locks — remote-selected items can't be dragged locally (collab-presence 0007) While a peer has an item selected, local users can still select it for inspection but move/drag/rotate/delete skip it with an infobar naming the holder (native locked-item UX; enforced via the fork's PCBJAM_REMOTE_LOCK query — kicad 81f9cd80fd, the epic's first fork-touching phase). Overlapping holds (both grabbed inside the awareness propagation window) tie-break deterministically: lowest (user.id, clientID) keeps the item, every losing client auto-releases it. - lock-tiebreak.ts: pure policy — beats(), remoteLocks() (union of ALL other clients' selections incl. own user's other tabs, minus own-held-and-winning uuids so the winner isn't blocked mid-release), contestedReleases() - presence.ts: clients() (per-client view, no user dedupe) + self(); FIX for a pre-existing flaky stack overflow — resolveCollision re-entered itself synchronously via its own patch's awareness 'change' and could ping-pong on stale same-user states (~1-in-3 unit runs); re-entrancy guard defers re-resolution to the next genuine delivery - presence-kicad.ts: locks ride the kicadCollabSetRemote snapshot (`locks:[{uuid,name}]`); losing overlaps call kicadCollabReleaseSelection - wasm bindings (both TUs + merged dispatch): g_locks map + fork query install; kicadCollabReleaseSelection (cancelInteractive only when a tool stack is live — bare ESC would clear the whole selection — then selective RemoveItemFromSel + infobar + forced re-emit); kicadCollabTestGetLocked - tests: lock-tiebreak unit suite; presence-locks e2e for both editors (real move veto — pcbnew click+M hotkey since its default left-drag is rubber-band select, eeschema real drag — each with an unlocked control); two-tab tests/web/locks.spec.ts (lock propagation + deterministic tiebreak release + unlock on clear, passing vs real partykit) Spec: docs/features/collab-presence/0007 (closed repo). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
2026-07-07 20:35:26 +02:00
// Selection soft-locks (collab-presence 0007).
function("kicadCollabReleaseSelection", &collabReleaseSelection);
function("kicadCollabTestGetLocked", &collabTestGetLocked);
feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002) Zero kicad-fork changes — all in the embind layer: - pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/ leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld; viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom and sized the first cut's overlay nm-small); kicadCollabSetRemote renders peers' cursors (cross + name) and selection bbox outlines into one per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection. - kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003). - presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001) and pushes trailing-throttled peer snapshots into the wasm; wired from WasmTool.startPresence (pcbnew-gated). +5 unit tests. - tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select emit, throttled cursor, remote render with no-leak + pixel restore, viewport unit band. Existing pcbnew-collab/items-bridge suites stay green. Verified live: two tabs over partykit — peer cursor cross + label + selection outline visible on the other tab's canvas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
2026-07-06 15:59:51 +02:00
function("kicadCollabTestSelectFirst", &collabTestSelectFirst);
function("kicadCollabTestClearSelection", &collabTestClearSelection);
feat(wasm): kicad_editor — merge the pcbnew+eeschema kifaces into ONE bundle (Part 2) All four editors (PCB / Footprint / Schematic / Symbol) are now runtime --frame choices of a single kicad_editor.wasm (178 MB at -O1 vs 147+82 separate; shared wx/common/boost linked once). One editor per page load, as before; frames pcb / fpedit / sch / symedit. - wasm/editor/: the merged executable target (single_top + both kiface library sets, whole-archive pcbcommon) + the safety-net focus-walk Kiface() dispatch TU. Gated by KICAD_WASM_MERGED_EDITOR (kicad submodule bump carries the fork side: per-engine Kiface/getter binding + ODR renames + dual-kiface launcher). - wasm/bindings/: per-editor collab entries renamed pcbCollab*/schCollab* (JS names unchanged); duplicate kicadOpenFile/kicadCollabOnSave + shared-name registrations guarded behind KICAD_MERGED_EMBIND; new kicad_editor_embind.cpp registers each shared JS name once, dispatching on the live frame. - Build: kicad_editor app (build wrapper, target case arms, 3-object embind compile with the ABI-critical flags, STUB_APP=pcbnew); docker/build.sh "all" = kicad_editor calculator pl_editor gerbview (pcbnew/eeschema stay as explicit debug apps); scripts/kicad/audit-merged-symbols.sh = repeatable ODR-collision audit (run on kicad bumps). - Frontend: Bundle type (bundle ≠ tool); TOOL_BUNDLE maps all four editors to kicad_editor; explicit --frame tokens for pcbnew (pcb) and eeschema (sch); publish list = the 4 real bundles. - Tests/CI: five harnesses load kicad_editor.js with explicit frame tokens; PCBNEW_FAMILY_SPECS renamed BIG_MODULE_SPECS + the 8 eeschema-family specs (they now boot the merged module — SpiderMonkey x86 CI OOM routing); frame-runtime spec covers all four frames from the one bundle. Validated so far: frame-runtime 4/4 (each frame boots with the right title, no aborts, no duplicate embind registration); 24-spec merged-module regression green; 3D raytracer renders. Known pre-existing failure: 3d-viewer title-bar drag deadlock, fixed on main by 7630c7e (2N+8 pthread pre-warm) — picked up by the follow-up rebase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:48:11 +02:00
}
#endif // __EMSCRIPTEN__