- Standalone wx test apps (contextmenu, scrollbar) + e2e specs proving the
wxScrollBar control, the wxScrolledWindow gutter, and DoPopupMenu.
- KiCad in-app specs: pl_editor + pcbnew canvas right-click context menus and
scrolled-panel scrollbar gutters, with baseline screenshots.
- build-wx-wasm.sh: serial -j1 fallback when the parallel build trips the
intermittent clean-build race (a generated header read mid-rewrite by
concurrent compiles; never happens at -j1). Recovery resumes from built
objects; a genuine error still fails the serial pass.
- Bump wxwidgets submodule (context menus + draggable scrollbars).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump wxwidgets (8814ddb) for the two DOM-port fixes and add their reproductions:
- tests/apps/standalone/{textctrl-reentry,tooltip-lifetime}: standalone wx repro
apps + Makefile.wasm targets (textctrl links -fexceptions to throw from a
wxEVT_TEXT handler), driven by tests/e2e/dom-port-bugs.spec.ts. Each app is
deterministic and self-contained (no UB, ASAN, or timing dependence).
- docs/features/wx-dom-port/branch-review.md: branch review with findings #2/#3
marked fixed and a "Bug reproductions and fixes" section, including the
asyncify + legacy-EH gotcha (catch/destructor landing pads are unreliable
while unwinding through asyncify frames).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Submodule pointer bump for the wxMenuBar dangling-child fix that resolved the 18 DOM-port kicad e2e failures. Also adds the async-debug dossier (docs/features/async/08-10) root-causing the 'indirect call to null' / 'signature mismatch' family to the menu-bar use-after-free, and folds the asyncify harness into 'npm test' (new test:wx for wx-only); tests/README + WHATWORKS updated to match.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- wxwidgets -> 25281178d2: wxClipboard::IsSupported answered via a 2 s
Asyncify suspend on KiCad's idle path; overlapping suspensions
corrupted Asyncify.currData ('indirect call to null'/'signature
mismatch' storms after every document load — bug 2 of features/async).
It now answers synchronously. The storms and 2 s UI stalls are gone.
- 58 kicad screenshot baselines regenerated from the green run (the old
set was canvas-only and died with the canvas port).
- visual-notes: bug 29 + the known-red list — the 10 file-loading kicad
specs (load-pcb, collab bridges, eeschema-ui keyboard/dialog) remain
red on the unfixed asyncify substrate (bug 3, per-context currData
authority). Bisect-verified pre-existing: binaries built from the
pre-consolidation wx tree fault identically. Greening them is the
async feature's exit criterion, deliberately out of scope here.
Gates: wx e2e 292/0/1 skipped; kicad 32 passed / 10 known-red / 2
skipped (parity +1 with pre-consolidation, minus the timeout storms).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One-time refresh from a fully green run (292/0/1): the client-origin
projection fix moved every notebook's tab strip to its true position
(content shifted by the strip height), and many baselines predated the
DOM-native notebook entirely. 10 new screenshots (gal/menu/spinctrl
flows that previously never ran green), 159 updated, 100 untouched.
compare-screenshots.sh is clean against the gate run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure file moves: the canvas baseline set (331 PNGs) is deleted, the DOM
set becomes tests/baseline-screenshots/, and the DOM pcbnew reference
image becomes wizard-04-finish-headless.png. 73 kicad-flow baselines
existed only on the canvas side; they are regenerated from a green DOM
run in a follow-up commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The canvas (wxUniversal) mode is gone (wxwidgets submodule); remove every
piece of side-by-side plumbing so there is exactly one build and one test
flow:
- scripts/build-wxuniversal-wasm.sh -> scripts/build-wx-wasm.sh; no
--dom/--enable-universal; builds into build-wasm/wxwidgets
- build-wasm-test.sh: no DOM_BUILD / apps-dom rsync mirror / PORT=dom;
apps build straight into tests/apps (Makefile.wasm PORT conditionals
collapsed; wx.js + wx-dom.js always pre-js)
- docker/build.sh, build-kicad-target.sh, env.sh: WX_PORT / -dom /
-universal suffixes removed; kicad builds to kicad-<app>, outputs to
output/; wx.js/wx-dom.js copied from the real source path
(/workspace/wxwidgets/build/wasm — the old build-wasm path never
existed and silently failed)
- setup-kicad-wasm.sh: single target dir; the perl wx-dom.js injection is
gone — the 7 checked-in kicad pages now reference wx-dom.js directly
- playwright configs serve apps/; fixtures drop the test-results/dom and
logs/wxwidgets/dom namespacing; boot.spec asserts wxDomPort
unconditionally; pcbnew.spec uses one reference image;
appearance.spec assertions unconditional
- compare/update-baseline-screenshots.sh: --port removed
- tests/gal-regression/wasm/Makefile: links build-wasm/wxwidgets and
carries wx-dom.js as a second pre-js — the gal-webgl suite (30 specs)
now actually builds and runs here (it needed host-side boost+glm via
scripts/deps; the bundle had been missing, timing the whole spec out)
- tests: clickCanvas() dispatches via page.mouse (DOM widgets
legitimately cover the canvas; locator actionability refused the
click); the comprehensive spec drives wxChoice through its native
<select> (browser-owned popup cannot be coordinate-clicked)
- docs: README/CLAUDE.md/build.md script names and dirs;
features/wx-dom-port README reframed (DOM is THE port), visual-notes
bugs 26-28; FindwxWidgets.cmake config label drops 'wasmuniv'
- wxwidgets submodule -> 9dbacc9448 (DOM-only port, fork diff shrunk)
Gate: full wx e2e suite 292 passed / 1 skipped / 0 failed — first run
ever with the gal-webgl specs green (28 scenarios + load + sequential).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New tests/kicad/appearance.spec.ts: tab cycle through Layers/Objects/
Nets and back (with blank-rows regression assertion), wheel scrolling
in Layers (movement + exact scroll-back restoration) and Objects, with
screenshots per state. All 3 pass against the DOM pcbnew.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The wx test-app mirror (build-wasm-test.sh --dom, rsync --delete) wiped
the kicad bundles setup-kicad-wasm.sh syncs into apps-dom/kicad —
apps/kicad only carries the checked-in HTML. Exclude kicad/ (that dir is
owned by the setup script). Add 'npm run serve:dom' for browsing the
DOM-port apps; plain 'npm run serve' serves the canvas dir, which has no
kicad bundles in this worktree (canvas KiCad was never built here).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pcbnew.spec.ts picks wizard-04-finish-headless-dom.png under WX_PORT=dom
(native chrome styling differs from univ pixels by design); reference
seeded from the verified loaded-editor state incl. the DOM menubar.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pipeline: lexer generation (pcbcommon) ordered before the embind
pre-compile — a fresh-build-tree chicken-and-egg any new volume would
hit (generated pcb_lexer.h was included via -I${KICAD_BUILD}/common
before anything generated it). DOM flavor outputs flow through the
host-mounted output/dom/. pcbnew e2e on DOM: editor loads fully (layer
manager, toolbars, WebGL viewport, registry metrics), draw-lines test
passes; the header pixel-reference assertion fails as expected (native
DOM styling vs univ pixels) — port-aware references pending.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WX_PORT=dom flows host → docker container → build scripts:
build-kicad-target.sh selects wxwidgets-dom + kicad-<app>-dom trees;
docker/build.sh passes the env through, copies outputs to output-dom/
incl. wx-dom.js, and post-processes there; build-wxuniversal-wasm.sh
honors WX_PORT and accepts --no-clean again (the kicad pipeline calls it
with that flag — Phase 1's stricter parser had broken the canvas docker
path). setup-kicad-wasm.sh syncs DOM bundles into tests/apps-dom/kicad
and injects the wx-dom.js script tag into the kicad pages;
playwright-kicad.config.ts serves apps-dom under WX_PORT=dom.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tests/apps/Makefile.wasm: pre-js shims are link dependencies (editing
wx.js/wx-dom.js now relinks apps); $^ link rules filter to %.o/%.a so
the new deps stay out of wasm-ld inputs. Seed baseline-screenshots-dom
(251 screenshots) from the green WX_PORT=dom full-suite run (251/0).
Cross-port visual comparison notes in docs/features/wx-dom-port/
(10 bugs found by the protocol, all fixed in wxwidgets).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Build/test plumbing for the second wxWidgets mode: --dom on
build-wxuniversal-wasm.sh (build-wasm/wxwidgets-dom, no universal,
--disable-tooltips, libwx_wasmu_* stubs) and build-wasm-test.sh
(mirrors sources into tests/apps-dom, PORT=dom). Build script now
reconfigures when Makefile.in or autoconf_inc.m4 regenerate (bakefile
chain). Harness: WX_PORT=dom serves apps-dom, namespaces screenshots
into test-results/dom and logs into logs/wxwidgets/dom; compare/update
screenshot scripts take --port dom with baseline-screenshots-dom.
New port-agnostic boot.spec.ts (green on both ports).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The asyncify single-slot work, executed red-green (full ledger:
docs/features/asyncify-arbiter/redgreen.md; decisions record:
docs/features/async/07-decisions-and-outcome.md):
- tests/apps/standalone/asyncify-races/ + tests/asyncify/ + dedicated
playwright config: 8 scenarios reproducing the KiCad asyncify failure
family with the kicad-faithful startup topology (pre-park fiber swap →
park throw through the live trampoline). Built in 3 variants; the
SHIM_DISABLE_TRAMPOLINE_HEAL / SHIM_DISABLE_HANDLESLEEP ablation builds
keep the historical hang and index-out-of-bounds crash reproducible
forever (mutation-style pins for the existing shims).
- scripts/common/shims/handlesleep.js: catch the "unwind" park sentinel
in the wakeUp path — when main's last pre-park suspension was a sleep,
the main-loop park throw escaped through that sleep's promise reaction
as an uncaught rejection (the calculator/gerbview console errors).
- scripts/common/inject-dyncall-shims.sh: SHIM_DISABLE_* ablation knobs.
- Spec tightening (the acceptance bar): 'uncaught exception: unwind'
tolerance DELETED from pcbnew/eeschema specs; load-pcb gained a hard
clean-console gate over 5 asyncify corruption signatures.
- wxwidgets pointer bump: modal LIFO resolvers, pump resolve-on-error,
sync clipboard IsSupported (014f67e6c1).
Final state: asyncify suite 7/7, wx e2e 291/292 (1 skip), KiCad e2e 40
passed / 2 skipped with ZERO corruption signatures in any log across all
six apps.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- WasmTool: hard cutover to startKicadCollab (gates on
kicadCollabSnapshotItems; scalar reconciler no longer runs in the app)
- file-seed (ysync 0005 tie-in): empty rooms are seeded from the opened
file via fileToDoc/docToY (meta+layout+items — file recoverable from the
Y.Doc alone); populated rooms still adopt (seed-once unchanged)
- binding: gate UP applies until seed() — the provider's initial state
sync otherwise streams the full doc into an editor that already holds
the file (trapped eeschema's paste path in the real app)
- pcbnew blobForItem: footprints Format a uuid-corrected copy directly —
SaveSelection's copy regenerated mandatory-field uuids (FOOTPRINT copy
ctor assigns into fresh fields), breaking wire identity (rebuild)
- roundtrip.spec on the v2 items wire; new passing pcbnew
footprint-containment round trip (the 0004 containment win); full
fixture stays fixme on the tracked envelope-parse limit
- real-app two-tab emit verified for eeschema + pcbnew (see 0008 doc)
Full kicad suite: 45 passed, 3 skipped (known), 0 failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add kicadCollabSnapshotItems / kicadCollabApplyItems / window.kicadCollab.onItems
to pl_editor, eeschema, pcbnew — per-item native-blob payloads
({added/changed:[{sexpr,parent}], removed:[uuid]}) alongside the untouched scalar
wire (legacy collab specs stay green).
- pl_editor: itemBlob per item; apply = SetPageLayout(append) + replace-by-uuid
(pointer-snapshot safe); bare payloads get the kicad_wks envelope. Snapshot +
apply + emit verified headless.
- eeschema: clipboard Format per item (symbols carry their lib_symbols); apply
mirrors the native paste — LoadContent into a throwaway sheet → detach →
replace-by-uuid → symbol lib relink (blob's lib_symbols first, live screen's
second) → SCH_COMMIT, in the CallAfter+COROUTINE context. Snapshot + apply
verified headless (a "lost" lone junction turned out to be correct connection
cleanup — test uses text).
- pcbnew: blobForItem per ROOT item with child→footprint lifting in flushDiff;
apply = makeFromBlob + commit replace-by-uuid on the fiber; bare non-footprint
payloads get wrapInBoardEnvelope (live board layer table). Snapshot + footprint
replace/add WITH children (the 0004 containment gap, closed) + removal verified
headless. Track/via/zone/text blob-apply hits the documented asyncify-fragile
envelope parse (reconfirmed empirically — a verbatim SaveSelection segment
envelope dies silently in the commit) and stays on the legacy scalar apply;
tracked in ysync 0008 status.
- eeschema/pcbnew scheduleFlush now runs flushDiff inside a COROUTINE: the
per-item Format in the v2 emit needs the fiber stack (0007 lesson). Their emit
remains unverifiable headless (both legacy two-tab tests are test.skip:
"open=false → SCH_COMMIT no-ops" / "harness can't PAINT") — verify in the real
app at Stage D; pl_editor's emit IS verified.
- tests/kicad/items-bridge.spec.ts: per-tool suite (snapshot uuids → local-edit
emit (where drivable) → apply changed/added/removed via save-readback → no
apply echo). 3/3 pass; roundtrip + collab suites unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- standalone wasm/collab/types.ts: the CollabItem/CollabDelta data shapes now
come from MIT @pcbjam/shared (collab-wire zod schemas) via re-export; the
RUNTIME adapter interface (CollabBridge) stays here. emptyDelta/isEmptyDelta
alias the shared helpers — reconciler untouched.
- tests/collab/browser-entry.ts: fix the import path stale since the repo
restructure (web/apps/frontend → web/standalone) — the collab bundle had been
silently unbuildable; its opts type now tracks startCollab's real signature.
- tests/kicad/*-collab.spec.ts: update KicadCollab.start calls from the
pre-ysync-0004 { channel, settleMs } API to { provider: { kind:
"broadcastchannel", settleMs }, room } — the drift the broken bundle hid.
Verified: pl_editor 2/2, eeschema 2 passed, pcbnew 7 passed (skips pre-existing);
shared 47 unit tests; both typechecks clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Block A save embind exports + the round-trip integration harness.
Save exports (wasm/bindings, GPL):
- pcbnew: kicadSaveBoard(path) via PCB_IO_KICAD_SEXPR::SaveBoard(GetBoard()).
- eeschema: kicadSaveSchematic(path) via SCH_IO_KICAD_SEXPR::SaveSchematicFile.
Saves GetCurrentSheet().Last() (NOT Schematic().Root()): the wasm open-flow
nests the opened doc under an auto-created project root, so Root()'s screen
holds only a child-sheet symbol, not the loaded items.
(pl_editor already had kicadSaveDrawingSheet.)
Round-trip harness (tests/kicad/roundtrip.spec.ts):
- load fixture → save (ORIG) → kicadCollabSnapshot → reload (fresh wasm) →
open empty → kicadCollabApply → save (REGEN) → assert sexprDiff(ORIG,REGEN).equal.
- Two separate pages (extract closed before rebuild opens) so the process-global
wasm heap frees between boots (pcbnew ~190MB). Open both sides from the same
filename (eeschema embeds it as the root Sheetfile property). pcbnew boots the
seeded pcbnew-collab.html (plain pcbnew.html's first-run wizard blocks boot).
- pl_editor + eeschema round trips PASS (lossless). pcbnew is test.fixme with
tracked apply-coverage findings (footprints/zones not reconstructed; segment
width + via size lost; fp_text→gr_text) — bridge gaps for follow-up, not test bugs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Host-side wxUniversal build + wx test apps (build-wxuniversal-wasm.sh /
build-wasm-test.sh, mirroring the dispatch-only ci.yml) followed by
`npm run test` before the KiCad e2e. The wx suite runs on bundled headless
Chromium; several specs use WebGL (gal-webgl.spec.ts), so the chromium
project gains a CI-gated --enable-unsafe-swiftshader launch arg — without
it newer headless Chromium refuses software WebGL on the GPU-less runner.
First time this suite runs on a working CI runner; treat as experimental.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The xvfb/WebGL fix (f37ddc4) took CI from 22 to 11 failures (identical on
Hetzner 27343415331 and Ubicloud 27343416511) — everything left is the
pcbnew family, still dying at wasm compile: pcbnew's ~190M debug module
exceeds SpiderMonkey's per-process code budget on x86-64 even with the
baseline-only JIT (the same module compiles on arm64, whose denser code
fits — which is why the local arm64 repro passed). No pref raises that
limit; V8 handles the module fine.
New CI-only 'chromium-ci' project (bundled Chromium, headless, SwiftShader
via --enable-unsafe-swiftshader) carries pcbnew/pcbnew-collab/load-pcb/
load-pcb-probe; the firefox project ignores them on CI. Workflows call the
new test:kicad:ci script which runs both projects. Local test:kicad is
unchanged (firefox everywhere, system Chrome for headed runs).
Validated under x86 emulation (linux/amd64 playwright image on Rosetta):
the pcbnew wizard test passes on chromium-ci in 23s against the fresh
v130-postprocessed 192M module.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI runs (Hetzner 27329612719, Ubicloud 27330989479) failed 22/41 identically,
in two buckets, both firefox-environment issues (the suite is firefox-only:
test:kicad -> --project=firefox):
1. No WebGL: headless Firefox can't create ANY GL context on a GPU-less VM
(blocklist bypass still dies with FEATURE_FAILURE_WEBGL_EXHAUSTED_DRIVERS),
so the GAL canvas failed and an error dialog blocked every UI flow. Fix:
run headed under xvfb (GLX + Mesa llvmpipe software GL) with
webgl.force-enabled — workflows now wrap the e2e step in xvfb-run.
2. pcbnew wasm OOM: the ~190M module OOMs Firefox's optimizing wasm JIT at
compile ("InternalError: out of memory") and the app never boots. Fix:
javascript.options.wasm_optimizingjit=false (baseline-only compile).
Both prefs are CI-gated in playwright-kicad.config.ts; local runs keep stock
behavior. Boot got slower (baseline JIT), so the wizard helpers now wait for
the registry to have actual UI ENTRIES (the registry OBJECT exists pre-boot)
instead of clicking into a fixed 10x500ms window.
Validated in a local docker repro of the CI env (playwright:v1.57.0 image,
no GPU): pl_editor canvas, gerbview wizard+canvas, and pcbnew wizard (on a
properly asyncified 187M module) all pass; before the fix they reproduced the
exact CI signatures.
Known-fail left on CI: zoom-cursor — the zoom-in anchors at ~ -P in headed
Firefox (screen-vs-client coordinate mix-up in the wheel->GAL path, headed
mode only); marked test.fixme(CI) pending a wx-wasm-layer investigation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Suite went 64 failed → 291 passed / 0 failed:
- Makefile.wasm: compile with -MMD -MP and include .d files — stale objects
relinked against a newer wx lib crashed apps at startup with "function
signature mismatch" after any header/vtable change
- playwright(.coroutine).config.ts: pin the server port for the whole run
(resolvePort from playwright-kicad.config.ts); the 60s freshness window made
workers restarted after a failure rotate to a dead port (ERR_CONNECTION_REFUSED
cascade across ~50 tests)
- drop legacy-GL testing: KiCad renders via WebGL GAL (gal-webgl.spec.ts);
remove minimal_test's OpenGL tab/GLTestCanvas, opengl.spec.ts, GL z-order
describes, -sLEGACY_GL_EMULATION + gl_immediate_shim.js from the test build,
and the orphaned GL baselines
- coroutine-pthread repro apps join the `all` target (make clean used to delete
them while all never rebuilt them); clean no longer eats checked-in JS like
worker_dom_stub.js
- bump wxwidgets: guard module-eval document access so wx+pthread apps
(threadpool tests) survive Web Worker eval
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reduce web/ to a single generic editor app plus a minimal example backend,
moving all project-specific/app code out to the closed root repo:
- apps/frontend -> standalone (@pcbjam/standalone): the GPL editor. Strips the
project-management UI (ProjectsPage/ProjectDetailPage/UploadDropzone and the
write half of lib/api.ts). WasmTool now takes fetchBytes + assetBaseUrl as
injected props (decoupled from the API client) so it can be driven by either
a backend or a local folder. New HomePage (local-folder loader + backend
project list) and read-only ProjectView. BroadcastChannel collab unchanged.
- backend (@pcbjam/backend-example): thin Fastify+ts-rest reference impl of the
@pcbjam/shared contract — serves a single project off the local filesystem
(PROJECT_DIR), no DB/auth/uploads.
- packages/contract -> web/pcbjam-shared (git submodule, MIT @pcbjam/shared).
- Remove apps/server, packages/storage, packages/contract, docker-compose
(server + storage move to the closed root repo; preserved via subtree
branches). Rewrite pnpm-workspace/turbo/.env for the frontend-only layout.
- Add tests/fixtures/demo so the editor + example backend run out of the box.
Standalone typechecks + builds; backend serves the contract end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a layer-preservation check to the footprint/via/zone/text round-trip add tests,
documenting + verifying that an item's own serialized layer is applied by makeItem and
the peer editor's active layer is never consulted. 4 add tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Thrust A — dialogs render top-left with OK clipped in the WEB app (not the
test harness): root cause was the React shell missing the .window /
.window-canvas CSS that wx.js relies on (it positions each dialog div via
inline left/top, which need position:absolute). Added the rules to
web/apps/frontend/src/index.css. Native draw-text now works end-to-end;
symbol/power choosers render (placing still blocked by absent libraries).
Thrust B — collab apply of a newly-added SCH_SHAPE trapped in KiCad core
(SCH_COMMIT::Push CHT_ADD -> GAL view->Add, an asyncify invoke_* mis-dispatch)
because doApply ran off a fiber stack. doApply now runs inside a COROUTINE so
it executes on a libcontext fiber, the same context native draws use; the add
dispatches correctly. Re-enabled the SCH_SHAPE converter (rect/circle). Added
thirdparty/libcontext to the embind include path (tool/coroutine.h needs it).
Verified two-tab: rectangle + circle drawn in tab A sync + render in tab B.
Extended eeschema-collab.spec.ts apply test with a SCH_SHAPE add.
All changes root-repo only; kicad and wxwidgets forks untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump kicad + wxwidgets submodules with two wasm eeschema UI fixes, and add the
regression test tests/kicad/eeschema-ui.spec.ts (Delete + Backspace delete; the
text-tool properties dialog opens and closes without freezing).
- wxwidgets c27fe8b: nested (quasi-modal) event loops pump via Asyncify instead of
re-entering emscripten_set_main_loop (which threw an un-resumable 'unwind').
- kicad 4132395: bind Backspace to delete under emscripten + apply default alt hotkeys.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two things, both verified in the real web app (two-tab eeschema collab).
1. dynCall crash fix (all apps) — scripts/common/shims/dyncall-binding.js.tmpl.
Programmatic editor edits trapped with 'indirect call signature mismatch': the
asyncify-instrumented wasmExports[dynCall_<sig>] trampoline does call_indirect with a
stale type for some table indices (post-asyncify+O2) even though the table entry is
valid. Proven by patching the built js: at the trap getWasmTableEntry(index) SUCCEEDS
where the trampoline fails. Fix: the shim now catches the 'signature mismatch'
RuntimeError and falls back to getWasmTableEntry; the Asyncify unwind sentinel and real
exceptions re-throw, so instrumentation/unwind is untouched for normal calls. This
unblocks ALL programmatic edits, not just collab (e.g. eeschema SCH_ITEM::Move).
2. eeschema collab apply converters (wasm/bindings/eeschema_embind.cpp).
doApply now handles added-item construction (build the SCH_ITEM with the delta's uuid
via const_cast — as the s-expr parser does — + commit.Add) and richer SCH_LINE
serialization (start/end/layer) so wire edits reconstruct on the peer. Implemented for
SCH_LINE (wires) + SCH_JUNCTION; other types log 'no converter for added type' and are
skipped (next batch). eeschema re-enabled in the web app collab gate.
Tests: eeschema-collab.spec snapshot (green); apply/two-tab skipped — they no-op headless
because the e2e harness's kicadOpenFile returns false (OpenProjectFiles bails before
building the connectivity graph), so SCH_COMMIT::Push doesn't persist. Verified in-app.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
eeschema's half of the Yjs collaborative bridge, reusing the generic reconciler /
BroadcastChannel transport unchanged. Zero kicad-fork change: native SCH_ITEM uuid +
native SCHEMATIC_LISTENER. All in the wasm layer (wasm/bindings/eeschema_embind.cpp).
Working (verified in the web app):
- kicadCollabSnapshot(): enumerate sch.Hierarchy() -> LastScreen()->Items() as
{id,type,x,y}; registers the listener on first call
- emit: SCHEMATIC_LISTENER subclass -> per-item delta via window.kicadCollab.onDelta;
fires on real SCH_COMMIT::Push (a real wire move broadcasts added/removed/changed)
Apply is a documented follow-up (gated off so a peer tab can't crash): SCH_ITEM::Move
traps with 'indirect call signature mismatch' when invoked outside a KiCad tool
coroutine (Asyncify+fiber+exception-trampoline). Modify/Clone/GetPosition all work;
only the virtual Move write traps. Fix direction: route apply through TOOL_MANAGER.
Also: build-kicad-target.sh now force-relinks when only <app>_embind.cpp changed (the
embind .o isn't a make dep, so new bindings silently vanished), and adds the
expected/rtree/fmt thirdparty includes the eeschema bindings need.
Tests: eeschema-collab.spec.ts covers snapshot (green); apply/two-tab skipped with the
blocker noted. WasmTool gates collab to pl_editor only until eeschema apply works.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump kicad submodule to the per-item uuid identity change, plus the wasm/test
infra to verify it:
- wasm/bindings/pl_editor_embind.cpp: test-only kicadSaveDrawingSheet(path) hook
that serializes the singleton DS_DATA_MODEL to MEMFS (also a building block for
the bridge's later materialize-to-file path)
- tests/kicad/pl_editor-uuid.spec.ts: open->save->read-back e2e proving (uuid …)
backfill (4 distinct uuids) and load->save round-trip preservation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Register the newly WASM-ported Gerber Viewer in the web app the same way as
the other tools — registry entries only, no UI edits (ProjectDetailPage renders
launch links generically from FILELESS_TOOLS).
GERBVIEW_FRAME opens gerber/drill files through its own File→Open UI and projects
carry no gerber files to auto-open, so it's treated as file-less (boot standalone),
mirroring symbol_editor. It boots through single_top.cpp's STARTWIZARD, so it seeds
config to skip the first-run wizard (TOOL_NEEDS_CONFIG_SEED) and gets a
/usr/bin/gerbview argv0.
- contract: add "gerbview" to TOOLS, TOOL_LABELS ("Gerber Viewer"), FILELESS_TOOLS
- frontend: add gerbview to TOOL_ARGV0 and TOOL_NEEDS_CONFIG_SEED
- e2e: add file-less gerbview case to tools-open.spec.ts (title "Gerber Viewer",
canvas painted, wizard-free, no WASM abort)
Verified in-browser via npm run test:web: gerbview boots wizard-free with the
viewer chrome (toolbars + layers manager), 0 console errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>