Standalone Astro 6 site in /site (decoupled from the /web app monorepo):
landing page, /blog with one post (Content Layer), and terms/privacy/cookie
legal pages. Pure Astro, server-rendered to static HTML.
- Static by default via @astrojs/vercel adapter; any route can opt into SSR
with `export const prerender = false` (deploys as a Vercel Function).
- SPA-style navigation with <ClientRouter />, transition:persist on the header
(no icon flash), and viewport prefetch — ~6 KB gzip JS, no React.
- Deploy on Vercel by setting Root Directory to 'site' (no vercel.json needed).
See site/README.md for dev and deploy instructions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The batched-emit fix still lost segments on a large connected drag (peer
dropped the P3-C1 wire). Deeper cause: the SCHEMATIC_LISTENER fires in
pushSchEdit BEFORE RecalculateConnections (sch_commit.cpp ~402 vs ~430), so
every emit was pre-cleanup RAW geometry; the cleanup that follows (merge
collinear wires, drop/split junctions) was never broadcast. The peer rebuilt
the raw edit and ran its own cleanup over a different dirty scope, so the two
peers cleaned up differently and the peer lost segments.
Replace the listener-list emit with a post-settle full-model snapshot DIFF
(snapshotByUuid), flushed via CallAfter once Push (cleanup included) returns —
capturing tab A's final, already-clean geometry. The peer applies that and
re-cleaning already-clean geometry is idempotent, so they converge. The native
listener is now just a change trigger. g_baseline holds the last-broadcast
state; doApply and kicadCollabSnapshot rebaseline so applied/seed items aren't
re-broadcast (echo). Mirrors pl_editor's snapshot-differ; no kicad-fork change.
Verified two-tab, rigorously (real edit: tabA state changed AND tabA===tabB
byte-for-byte): a wire reroute plus U1A/U1B/C2 symbol drags all converge
exactly. eeschema-collab + eeschema-ui suites green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A single SCH_COMMIT::Push fires OnItemsAdded/Removed/Changed separately and
synchronously, then RecalculateConnections once. COLLAB_LISTENER emitted each
category as its own delta, so the peer applied one atomic edit as three separate
commits, each with its own connectivity recompute. On a large connected drag the
junction at the wires' new crossing (added) was applied before the wires moved
(changed) -> dangling junction -> the peer's cleanup deleted it (lost segments).
COLLAB_LISTENER now buffers added/changed/removed (serialized in each synchronous
callback) and flushes one combined delta after Push returns, coalesced via
CallAfter. doApply applies it atomically removed->changed->added in a single
commit with one recompute, so the junction survives. Verified two-tab: a G-drag
of U1A that previously left the peer at 74 items / 7 junctions now emits one
delta {a:1,c:4,r:1} and both tabs converge at 75 / 8.
Root-repo only; kicad and wxwidgets untouched.
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>
After the body-move devirtualization, moving a symbol synced the body to the peer but
left its reference/value text behind: SCH_SYMBOL::Move()/SCH_LABEL_BASE::Move() move the
child fields via an inner virtual field.Move() that also mis-dispatches in the apply
context. Move the fields explicitly with a devirtualized SCH_FIELD::Move (moveFields).
Verified: a moved symbol's text label now follows the body on the peer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The changed-path used the virtual SCH_ITEM::Move(), which silently no-ops from the
apply/CallAfter context (asyncify call_indirect mis-dispatch) for every non-wire item —
so moving a symbol synced on the sender but not the peer. Devirtualize Move() with an
explicit class-qualified call (moveItemTo), which is statically bound (a plain call, not
call_indirect) and executes. Verified: a symbol move now propagates. Also bumps wxwidgets
to ea599f7 (toolbar clicks no longer steal canvas keyboard focus).
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>
Captures milestone-3 state + the known in-app bugs (text-add freeze, deletes not
applying, SCH_SHAPE no sync, partial wire-move convergence) + the build/test/verify
workflow, so a fresh session can pick up the eeschema collab apply work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend doApply added-item construction beyond wires: SCH_TEXT, SCH_LABEL /
SCH_GLOBALLABEL / SCH_HIERLABEL (position + text + label shape), and SCH_NO_CONNECT.
Serialize text (any EDA_TEXT) and label shape in itemToJson. Moving/deleting existing
items of any type already worked (generic changed->Move and removed->Remove); this adds
their reconstruction on add.
Still uncovered: SCH_SYMBOL (needs lib-symbol + fields/orientation) and graphic shapes
(SCH_SHAPE). Known issues to fix next: adding text freezes the app; circles (SCH_SHAPE)
don't sync; deletes don't apply; wire moves only partially converge.
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>
Update 0002 to note the bridge lives in wasm/bindings/pl_editor_embind.cpp (not the
kicad fork) per CLAUDE.md, keeping fork divergence to the single OnModify hook; CRDT
is a uuid-keyed Y.Map; verification is the two-tab BroadcastChannel e2e.
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>
Add an e2e suite that drives the real React web app (not the standalone
harness): tests/playwright-web.config.ts + tests/web/tools-open.spec.ts navigate
/p/demo/<tool>/<file> for all five tools and assert each boots, opens its demo
file (title drops "untitled"), shows no first-run wizard, and emits no WASM abort
or URL-regex modal. global-setup-web.ts re-seeds the demo project through the API
if missing, so the suite is self-sufficient against a running dev stack. Wired as
`npm run test:web`.
Also at the harness level:
- pl_editor-load.spec.ts: prove the pl_editor kicadOpenFile embind hook opens a
.kicad_wks (mirrors eeschema-load.spec.ts).
- seed KiCad config in pl_editor.html / symbol_editor.html (matching eeschema.html
and the web app's boot.ts) so the harness boots wizard-free; repurpose
pl_editor.spec.ts's stale "wizard completes" test into a wizard-skip regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A freshly cloned + migrated install had no projects, so there was nothing to
click on. Add seedDemoProject() (run from db:migrate after seedDefaultOwner,
idempotent) that creates a "demo" project and loads three committed fixtures
from web/apps/server/seed-data/ — covering one openable file per editor:
demo.kicad_sch -> eeschema (Schematic Editor)
demo.kicad_pcb -> pcbnew (PCB Editor)
demo.kicad_wks -> pl_editor (Drawing Sheet Editor)
The sch/pcb are the self-contained ecc83 push-pull demo (version-compatible
with this build); the wks is a minimal hand-written drawing sheet. Bytes are
committed so seeding needs no submodule checkout at runtime.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the two newly WASM-ported editors to the web app the same way as the
existing tools:
- pl_editor (drawing-sheet, .kicad_wks): PL_EDITOR_FRAME overrides
OpenProjectFiles, so it gets the generic kicadOpenFile embind hook
(wasm/bindings/pl_editor_embind.cpp) for deterministic open. Mapped
.kicad_wks -> pl_editor in EXTENSION_TOOL.
- symbol_editor (symbol library): SYMBOL_EDIT_FRAME does NOT override
OpenProjectFiles, so it's treated as file-less (boot standalone, opens
libraries via its own UI). Added to FILELESS_TOOLS.
Both boot through single_top.cpp's STARTWIZARD, so both seed config to skip the
first-run wizard (TOOL_NEEDS_CONFIG_SEED) and get a /usr/bin/<binary> argv0.
contract: add to TOOLS, plus a TOOL_LABELS map for friendly names. The project
UI now renders file-less launch links generically from FILELESS_TOOLS and
per-file "Open in <label>" links from EXTENSION_TOOL (auto file-type detection),
so adding a tool needs no UI edits.
Verified in-browser: pl_editor opens a .kicad_wks (renders the sheet),
symbol_editor boots wizard-free; both with 0 console errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every standalone tool (eeschema, pcbnew, calculator) boots through
common/single_top.cpp, which runs STARTWIZARD::CheckAndRun() — the first-run
"KiCad Setup" wizard. It shows whenever any provider (SETTINGS / LIBRARIES /
PRIVACY) reports NeedsUserInput(), which is always true on our ephemeral MEMFS
with no config, and its modal loop crashes Asyncify. Only eeschema was seeding
config, so pcbnew and the calculator hit the wizard.
Flip TOOL_NEEDS_CONFIG_SEED to true for pcbnew and calculator so seedKicadConfig
runs in preRun for all three (it writes the kicad_common.json privacy flags and
the sym/fp/design-block lib-tables the providers check), making NeedsUserInput()
false and skipping the wizard. Verified in-browser: pcbnew renders a board at
/p/mytest/pcbnew/bottom.kicad_pcb and the calculator loads, both wizard-free.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add tests/kicad/eeschema-url-regex.spec.ts: opens a text_box-bearing
schematic with a real URL via Module.kicadOpenFile and asserts the
URL-detection wxRegEx compile no longer fails — no "Invalid regular
expression" in the console and no error dialog. A pre-fix build renders the
same text_box, hits IsURL() -> the failing static regex, and fires both
signals.
Bumps the wxwidgets submodule to the wxConvLibc->UTF-8 emscripten fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the same-origin iframe in WasmTool with a direct in-document boot
(src/wasm/boot.ts): build the global Emscripten Module + preRun steps and
inject wx.js + <tool>.js into the page, the same artifacts the e2e harness
uses. The build is non-modularized (global Module/FS) and pthread-based, so
locateFile/mainScriptUrlOrBlob are set so the wasm + worker load regardless
of the SPA route, and only one tool runs per page load.
Two bugs found during in-browser verification:
- This build does not export Module.FS (touching it aborts); use the global
window.FS like the harness does.
- The wasm reads top-level frame geometry from a global `mainWindow`
(offsetWidth/offsetHeight/offsetTop), falling back to a hardcoded 1280x720
when undefined. The harness sets it via `var mainWindow = ...`; we must too,
or the frame mismatches the viewport and the whole AUI layout breaks
(missing toolbars, transparent/ghosted panels). Expose the #main-window
element as window.mainWindow.
Verified: eeschema renders the full UI (menus, toolbars, panels, schematic)
matching the e2e baseline. pcbnew remains pre-existing-broken at the build
level (raw pcbnew.html harness is equally broken: empty registry, dynCall
"ii signature" errors), independent of this change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The emscripten fiber glue gates Fibers.trampoline() on Fibers.trampolineRunning
and resets it at the end of its loop. At startup emscripten_set_main_loop(...,1)
throws "unwind" to establish the main loop, and KiCad does so from inside a tool
coroutine, so the throw propagates THROUGH the trampoline and skips the reset —
leaving the flag stuck true. Every fiber swap after startup then becomes a silent
no-op, so opening a schematic (SetScreen -> RunAction(selectionClear) -> fiber
swap) hangs forever with the editor stuck on "untitled".
Wrap the trampoline loop in try/finally (inject-dyncall-shims.sh section "3c") so
the flag is always reset. Add tests/kicad/eeschema-load.spec.ts, which opens a
small wires/junctions schematic via Module.kicadOpenFile and asserts the editor
title switches away from "untitled": it times out (RED) without the shim and
passes (GREEN) with it. Also add features/web-init/0002-url-regex-modal-followup.md
capturing the unrelated URL-detection wxRegEx modal surfaced once loading works.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- inject-dyncall-shims.sh: add a dynCallLegacy -> wasmExports fallback so embind's
generic dynCall path finds the DYNCALLS=1 trampolines. Without it, an Asyncify
unwind/rewind through an embind call (kicadOpenFile -> OpenProjectFiles) died
with "f is not a function".
- open-flow.ts: kicadOpenFile runs OpenProjectFiles under Asyncify, so its sync
return is a falsy placeholder. Invoke it and poll the frame title for the load
instead of trusting the return value, and never fall back to UI automation
while the hook is in flight (it would re-enter the suspended Asyncify call).
- .gitignore: ignore .playwright-mcp scratch.
- bump kicad + wxwidgets submodules to the wasm schematic-open fixes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All four git-feature-* skills hardcoded /Users/torcsi/dev/kicad-wasm/ for
the helper-script invocations and one documentation example. That path
only resolved on the original author's machine — anyone else picking up
/git-feature-{start,commit,sync,finish} would hit "No such file or
directory" before the first pre-flight check ran.
Replaces the absolute paths with relative ones (`bash scripts/...`) and
rewrites the rebase-conflict handoff message in git-feature-sync.md to use
`git -C kicad ...` style instead of a hardcoded cd.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a 600ms wait between the wxFileDialog appearing in the registry and
the screenshot in tests/kicad/pl_editor.spec.ts. Without it, the dialog
object exists but its MEMFS readdir (asyncified) hasn't returned yet, so
the screenshot caught a half-painted black rectangle — fine for the
registry-based assertion but useless as a pixel baseline.
Commits the 10 pl_editor screenshots produced by the spec as the baseline
reference under tests/baseline-screenshots/.
Bumps the wxwidgets submodule pointer to pick up the expanded comment on
the wxGenericFileDialog::OnOk fix (explains why the patch lives in the
generic dialog instead of src/wasm/ — OnOk is the join point for both the
OK button and <Enter> via the dialog's compile-time event table, so
subclassing in wasm/ wouldn't intercept either).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Brings up KiCad's pagelayout_editor (drawing-sheet editor) in the
browser, to roughly the same "boots, canvas visible, partially usable
in-session" level as the existing pcbnew/eeschema/calculator ports.
Build:
- docker/build.sh: add pl_editor to the unified app dispatch (case,
subdir map, all-loop).
- scripts/kicad/build-kicad-target.sh: add pl_editor to the case;
upstream target name pl_editor under source subdir pagelayout_editor.
- scripts/kicad/build-pl_editor.sh: 7-line thin wrapper matching the
pcbnew/eeschema/calculator pattern.
- tests/scripts/setup-kicad-wasm.sh: copy_app pl_editor.
App glue:
- wasm/stubs/nl_pl_editor_plugin_stub.cpp: no-op SpaceMouse plugin so
pl_editor_frame.cpp's NL_PL_EDITOR_PLUGIN symbols resolve. Mirrors
nl_pcbnew_plugin_stub.cpp.
- tests/apps/kicad/pl_editor.html: browser shell. preRun creates
/home/kicad and FS.chdir there so file dialogs land somewhere
friendly instead of MEMFS root (/dev/, /proc/, etc.).
E2E coverage:
- tests/kicad/pl_editor.spec.ts: 5 tests — smoke (canvas, no abort),
wizard, File menu has Open/Save As, file-dialog folder-navigation
regression, canvas + toolbar metrics.
- tests/e2e/filedialog-folder-nav.spec.ts: wxWidgets-level twin of
the regression test (exercises the underlying widget directly via
the standalone filedialog_test app).
Submodule bumps:
- kicad → feature/pl-editor (WASM gating in pagelayout_editor's
CMakeLists + navlib stub).
- wxwidgets → feature/pl-editor (wxGenericFileDialog::OnOk navigates
into selected directories; wasm/mouse.cpp emits wxEVT_LEFT_DCLICK
via timestamp-based double-click detection — the latter benefits
every wxWidgets-WASM app).
See features/pl-editor/ for the design doc + per-repo diff patches.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
§6 (Worked example): document the May 28 second instance of the V8 stall
family (libcontext::wasm_fcontext_entry on line-tool fiber entry), why
removelist'ing it broke runtime (it's on the asyncify-suspend chain), and
that the systemic -O2 fix now covers both instances.
§7 (Debug vs production): the recipe stops describing -O2-after-asyncify
as an optional production tweak — it's now the committed default in
apply-asyncify.sh. Bundle size + parse-speed measurements updated.
Adds an explicit 'do/don't' on adding entries to ASYNCIFY_REMOVE (only
non-suspending functions; never coroutine trampolines or fiber_swap
callers).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two related fixes for Chrome-specific WASM-runtime issues reported when
running a manually-loaded session (line tool wouldn't even toggle on click;
log filled with 'Uncaught (in promise) unwind' and stderr-tagged
[WASM_FCONTEXT]/[DIAG_*] spam):
1) scripts/common/apply-asyncify.sh — run 'wasm-opt -O2' as a separate
pass after '--asyncify'. Without this, large asyncify-instrumented
coroutine-entry trampolines (notably libcontext's wasm_fcontext_entry
and COROUTINE<int,TOOL_EVENT const&>::callerStub) exceed V8's
per-function locals limit and silently stall on first fiber entry,
leaving the toolbar click dispatched in C++ but the tool never
activating its 'running=1'/[checked] state in the user's Chrome.
Firefox tolerates the unoptimised version, so tests on Firefox passed
while real Chrome stalled. The -O2 pass shrinks every instrumented
function back under the threshold, fixing the family of stalls
systemically (no more per-function removelist whack-a-mole).
The removelist still contains setupUIConditions() etc. as a safety
net — they're now redundant under -O2 but harmless.
Bundle: 338 MB -> 187 MB raw (~45% smaller); test runtime nearly
halves because parse is faster. See DEBUG.md §7 and
memory/bundle-size-asyncify-optimization.md.
2) wxwidgets submodule bump (d1d1627 -> a998a8d) — wasm/dialog.cpp:
startModal()'s setTimeout-based runEventLoop now awaits
ccall('ProcessEvents', ..., {async:true}) so the Promise rejection
from an asyncify-suspended ProcessEvents is caught by the existing
try/catch instead of escaping as an 'Uncaught (in promise) unwind'
page error.
Verified: npm run test:kicad:chrome and test:kicad:firefox both pass on
the rebuilt wasm; zero pageerror events; user-reported manual flow now
selects the Draw Lines tool and draws successfully.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
repo-status.sh derives up_to_date_with_main from the local origin/<main>
ref without fetching, so a stale snapshot could mark a repo "up to date"
when its origin had actually moved. sync would then report "all up to
date" and skip a needed rebase; finish would pass pre-flight and only
discover the staleness mid-merge.
Both skills now run `for-each-repo.sh fetch origin` as a mandatory first
step before the snapshot. Dropped the redundant per-repo fetch from sync's
execute step; kept finish's `pull --ff-only` as defense-in-depth.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Convert the calculator port (commit 0464470) from the parallel/copy-paste
pattern to the dispatch pattern used by pcbnew + eeschema.
- docker/build.sh: <app> is now required (no pcbnew default); missing,
unknown, and --help paths all print "pcbnew | eeschema | calculator | all".
Adds calculator to build_app() via kicad_subdir_for() (pcb_calculator
inner subdir, calculator.* output basename). Also fixes the
EMSDK=/emsdkkicad-wasm-builder typo that snuck in during the calc merge.
- scripts/kicad/build-kicad-target.sh: accepts calculator; introduces
KICAD_TARGET (pcb_calculator for calc, == APP_NAME otherwise) used for
the make target, embind include path, and final-log line.
- scripts/kicad/build-calculator.sh: 305-line copy of build-pcbnew.sh
collapsed to a 7-line wrapper around build-kicad-target.sh.
- tests/scripts/setup-kicad-wasm.sh: copy_app calculator added with the
same pcb_calculator subdir mapping for the docker-volume fallback path.
- tests/package.json: test:calculator* routed through the shared
playwright-kicad.config.ts kicad/calculator.spec.ts (mirrors eeschema).
- Delete: docker/build-calculator.sh, tests/scripts/setup-calculator-wasm.sh,
tests/playwright-calculator.config.ts.
Bumps wxwidgets d1d1627 -> 6fb2eac (origin/wasm-port). The new sha includes
"unic/combobox: add GetCurrentSelection() inline default" which calc's
kicad/pcb_calculator/widgets/unit_selector.cpp needs to compile. Without
this bump the unified dispatch would expose calc as a buildable target but
the build itself would fail. Verified: build.sh all completes clean across
all three apps; pcbnew/eeschema e2e pass; calc compiles and launches (test
stability separate from this refactor).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an end-to-end test that drives File→Open in pcbnew, injects
the .kicad_pcb and .kicad_pro files into MEMFS at the dialog's
default starting directory, drives the menu + filename text input
+ Enter accept path, and screenshots the loaded board. Parametrized
for both kicad/demos/microwave (RF polygon footprints) and
kicad/demos/pic_programmer (full multi-IC layout).
Without the rtree fix bumped in via the kicad submodule, the load
would abort on every PCB at rtree.h:1771 Classify; the test asserts
no [RTREE-DIAG] line and no Aborted(. The post-load clipboard
RuntimeError in __asyncjs__js_clipboardHasText is a separate,
pre-existing wasm-port limitation that we explicitly do not regress
on here.
- tests/kicad/load-pcb.spec.ts: serial-mode parametrized spec
- tests/kicad/load-pcb-probe.spec.ts: one-shot diagnostic probe
for inspecting wxFileDialog state on the canvas
- tests/kicad/utils/fs-inject.ts: FS.writeFile bridge from Node fs
- tests/kicad/utils/board-ready.ts: poll-for-no-dialogs readiness
- tests/baseline-screenshots/load-pcb-*.png: 6 baselines covering
both demos at pcbnew-ready / dialog-open / loaded states
- features/.../rtree-debug-findings.md: full diagnosis trail with
an upstream-reportable summary the maintainer can lift verbatim
- kicad submodule bumped to 07d8130d44 (shape_poly_set rtree fix)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- build-pcbnew.sh: add --diag=<gal,coroutine,ctor,all> -> -DKICAD_DIAG_*,
off by default (forwarded by docker/build.sh)
- diagnostics.js: emit at console.log level (no longer error/warn); still
gated by SHIM_DIAGNOSTICS=1
- apply-asyncify.sh: exclude PCB_EDIT_FRAME::setupUIConditions() from
asyncify instrumentation (V8 cannot run the instrumented huge function
on the rewound ctor stack -> Chrome startup stall; Firefox unaffected)
- DEBUG.md: reusable WASM/asyncify/browser debugging guide, diagnostic
flag docs, and a production-build (release + -O2 asyncify) recipe
- tests: standalone coroutine vcall/gl repro probes
- bump kicad + wxwidgets submodules (diagnostic gating / debug cleanup)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A clean dependency build failed immediately at the GLM step with
`unzip: command not found`. build-glm.sh extracts glm-0.9.9.8.zip with unzip,
but the Ubuntu-based image (introduced when the emsdk image was replaced) never
installed it. Add unzip to the apt-get list.
Verified: GLM, zstd and protobuf now build and stamp, and the deps build
proceeds into boost.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The wxWidgets autoconf build inside Docker failed with `emmake: command not
found` on a clean build, while the KiCad CMake build survived (CMake caches the
absolute compiler path). Two compounding causes:
1. `docker compose exec` bypasses the ENTRYPOINT, so it never sourced
emsdk_env.sh and EMSDK was unset. env.sh then fell back to the local
tools/emsdk.
2. The entrypoint's rsync from the host bind mount excluded only build-wasm and
output, so the host's macOS-arm64 tools/emsdk got copied over the container's
Linux emsdk. The macOS Mach-O python can't exec on Linux, so
`emsdk construct_env` failed ("Exec format error") and emcc/emmake never
landed on PATH.
Fixes:
- Set `ENV EMSDK=/emsdk` in the image so every process (including
`docker compose exec`) resolves env.sh's EMSDK branch to the container's own
emsdk and never falls back to tools/emsdk.
- Exclude `tools/emsdk` from the entrypoint rsync so the host emsdk can no
longer leak into the container.
Validated with a clean wxWidgets build (`build-wxuniversal-wasm.sh --clean`):
reconfigures and compiles all 42 wx libs against /emsdk with no missing-tool
errors.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Investigation scaffolding for the Chrome-only KiCad coroutine renderer crash.
Adds isolated reproduction probes exercising the coroutine/Asyncify/fiber layer
under KiCad-like conditions, runnable in BOTH Firefox and system Chrome.
- tests/playwright-coroutine.config.ts + test:coroutine:firefox|chrome npm
scripts: run the coroutine specs in Firefox AND system Chrome (the old e2e
config only used bundled Chromium, which never reproduced the crash).
- tests/apps/standalone/coroutine-pthread/: no-wx + pthreads reproduction probes
(fiber-in-main, nested invoke_/dynCall boundaries, RunMainStack, embind,
main-loop/rAF activation) + worker_dom_stub.js for wx+pthreads builds.
- tests/apps/Makefile.wasm: coroutine-pthread{,-main,-nested,-nested-ex,-wx,
-embind,-mainloop} targets.
- scripts/common/shims/diagnostics.js: add EM_ASYNC_JS handleSleep enter/wake
tracking (DIAG_SLEEP) to detect nested-async at the crash.
Findings (details in research notes): every isolated factor so far — direct /
nested / RunMainStack fiber, wx event loop + all 13 scenarios incl EM_ASYNC_JS,
pthreads, and main-loop/rAF activation — runs CLEAN in system Chrome. The
coroutine/Asyncify layer is exonerated; GL/WebGL is the remaining untested factor
(next). The reliable FF-pass/Chrome-fail repro is still the KiCad pcbnew e2e.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>