Self-hosted emergence-engineering/pcbjam (browser KiCad WASM) — CMMS embed fork. Upstream tracked as remote "upstream".
  • TypeScript 49.1%
  • C++ 36%
  • Shell 4.2%
  • C 2.9%
  • JavaScript 2.6%
  • Other 5.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Istvan Matejcsok 8b45348f71 fix(ci): 3D specs back to headless SwiftShader + GPU hardening; skip real-GPU drags on CI and the models render tail everywhere (product bug)
The headed Mesa-llvmpipe switch (ce44636) was built on a misdiagnosis. With
--use-gl=angle --use-angle=gl under Xvfb, Chromium gets NO WebGL AT ALL on the
GPU-less CI VM: runs 28652367347 + 28664038296 both log
`glcanvas count before opening 3D viewer: 0` (even the pcbnew board's own GAL
canvas fails to create) and every failure is the FIRST viewer-open wait timing
out. "Timeouts instead of crashes" meant no GL context existed to crash, not
that llvmpipe was stable-but-slow. The Mesa/GLX path works for the Firefox
project, but not for Chromium on this image.

Headless SwiftShader is the environment where GL provably works — the
serialized run 28649537489 opened the viewer and rendered/sampled 56 colours
(viewer:26/130/209 all passed single-tab). Its remaining failures split into
two families, each addressed on its own evidence:

1) SwiftShader GPU-process kills (deadlock spec's lost canvas/black frame):
   - chromium-ci-3d back to headless + --enable-unsafe-swiftshader, plus
     --disable-gpu-watchdog and --disable-gpu-process-crash-limit: heavy churn
     stalls the software-GL GPU process until the watchdog shoots it
     (CONTEXT_LOST -> removed GL canvas -> "Target crashed"); with the watchdog
     off a slow op completes, and without the crash limit a lost GPU process
     keeps restarting for later tests. Same args locally (no-ops on a healthy
     real GPU).
   - Storm-proof every canvas sampler (3d-viewer, models, deadlock specs): one
     full-frame getImageData on a willReadFrequently (CPU-backed) 2D canvas +
     JS grid sampling, replacing 256 per-pixel getImageData GPU round-trips per
     sample — the "GPU stall due to ReadPixels" trigger, worst in the deadlock
     spec's 1.5s-interval settle polling. Sampled pixels and sig order are
     unchanged.
   - edge-resize spec: keep the open + 5-resize-handles assertions on CI, then
     test.skip the drag — each resize step re-raytraces synchronously on the
     wasm main thread and the 12-step drag blocked mouse.move past the 240s
     budget even single-tab. Real-GPU-paced; still runs locally (deadlock
     spec's CI-skip from d9900fe unchanged, same rationale).

2) KNOWN PRODUCT BUG uncovered in the models spec (NOT SwiftShader, NOT CI):
   raytracing a scene WITH component models kills the Chromium renderer
   process outright ~6s after scene build — silent process death with a FLAT
   wasm heap (531-637 MB watched at 2s intervals; not OOM), no console error,
   no wasm abort, no crash report. Deterministic on a real GPU (5/5),
   fixture-independent (700 KB USB-C and 61 KB USON-8 STEP crash identically);
   the same death hits CI SwiftShader at ~66s. Board-only raytraces complete
   and stay alive, so the bug is specific to the model path. The spec's one
   historical green ended at raytrace-age ~6s — inside the death window by
   luck; every CI run of it ever died. Fix here: front-load the bridge
   assertions (ensure requests fire during scene BUILD, before the raytrace),
   so protocol regressions still fail the suite everywhere, and test.skip the
   render tail unconditionally with the bug documented at the skip site. The
   raytrace->blit pipeline stays gated on CI by 3d-viewer.spec's board-only
   render. Bug tracked for a dedicated fix session.

Correct the now-wrong llvmpipe comments (config, open-wait rationale, deadlock
skip reason). Expected CI matrix: viewer open/render/stacking/titlebar +
models-bridge on SwiftShader serial; edge-resize drag + camera-move deadlock
on real GPUs locally; the Worker-boot deadlock mechanism stays covered on CI
by the standalone wx harnesses.

Locally validated on a real GPU (--project=chromium): 5 passed + 1 skipped
(the documented models render tail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 12:21:04 +02:00
.claude docs(git-workflow): add binaryen to git-feature SKILL.md instructions 2026-06-30 10:21:16 +02:00
.github/workflows ci(3d): build the 3D WebGL harness + parity report in wasm-build 2026-07-03 15:46:23 +02:00
binaryen@1d40cf5a8f docs(hoist): pass-simplification dossier; bump binaryen (FindAll/BranchUtils refactor) 2026-07-01 16:52:17 +02:00
deploy/demo feat(demo): demo.pcbjam.com deploy — versioned WASM CDN + static gallery + tag CI 2026-06-19 11:39:09 +02:00
docker fix(build): stop the builder container when the build exits 2026-07-03 14:42:38 +02:00
docs feat(wasm): occ-split — lazy occ_service worker; kicad_editor drops OCC (−31%) 2026-07-03 12:39:58 +02:00
features feat(site): 🫙 PCBJam landing revisions — branding, accuracy, positioning graphic 2026-06-07 17:14:19 +02:00
kicad@eb13ff3bdc fix(submodule): repoint kicad at the rebased 3d-webgl-port head 2026-07-03 16:20:08 +02:00
logs Added logs .gitignore. 2026-01-03 13:52:34 +01:00
scripts fix(demo): POST waitlist to canonical www host to avoid preflight redirect 2026-07-04 11:39:38 +02:00
site feat(site,demo): OG share cards + demo page meta ahead of the HN post 2026-07-04 09:14:44 +02:00
tests fix(ci): 3D specs back to headless SwiftShader + GPU hardening; skip real-GPU drags on CI and the models render tail everywhere (product bug) 2026-07-04 12:21:04 +02:00
wasm feat(3d): gl1 shim M4+M6+M7 — GLU quadrics, production link, stub retired; 47/47 parity 2026-07-03 15:45:58 +02:00
web fix(demo): POST waitlist to canonical www host to avoid preflight redirect 2026-07-04 11:39:38 +02:00
wxwidgets@b3f0a09d43 fix: 🐛 raytrace deadlock - pre-warm 2N+8 pthread Workers 2026-07-02 13:01:02 +02:00
.ci-cache-epoch ci: cache KiCad WASM build output to skip Docker on unchanged sources 2026-06-19 14:51:33 +02:00
.dockerignore Add Docker build environment for KiCad WASM 2025-12-08 12:07:01 +01:00
.gitignore test(3d): screenshot-baseline TDD suite for the 3D viewer OpenGL->WebGL port — 47 native goldens + red-state WebGL harness 2026-07-03 15:45:58 +02:00
.gitmodules feat(wasm-eh): migrate the WASM build to native wasm exceptions (+ 3D viewer default-on) 2026-06-30 09:40:26 +02:00
CLAUDE.md feat(tests): screenshot regression + Discord review, perf-tracked 2026-07-01 18:17:20 +02:00
LICENSE Add GNU General Public License v3 2026-06-11 12:54:52 +02:00
README.md feat(tests): screenshot regression + Discord review, perf-tracked 2026-07-01 18:17:20 +02:00

KiCad WebAssembly Port

Run KiCad PCBnew in the browser using WebAssembly.

Quick Start

Full Build (KiCad + All Tests)

# 1. Initialize submodules
git submodule update --init --recursive

# 2. Build KiCad WASM (Docker, ~10 min incremental, ~1-2 hours full)
./docker/build.sh

# 3. Build wxWidgets for local testing
./scripts/build-wx-wasm.sh

# 4. Build wxWidgets test apps
./scripts/build-wasm-test.sh

# 5. Run all tests
cd tests && npm install
npm test              # wxWidgets tests (256 tests)
npm run test:kicad    # KiCad tests (2 tests)

wxWidgets Only (No Docker)

# Requires: Node.js 18+ (Emscripten SDK auto-installed on first build)
./scripts/build-wx-wasm.sh
./scripts/build-wasm-test.sh
cd tests && npm install && npm test

Project Structure

kicad-wasm/
├── kicad/                  # KiCad source (git submodule)
├── wxwidgets/              # wxWidgets source (git submodule)
├── wasm/                   # WASM compatibility layer
│   ├── bindings/           # Embind bindings for JavaScript
│   ├── cmake/              # CMake find modules
│   ├── kiplatform/         # Platform abstraction (app, UI, printing)
│   ├── libcontext/         # Coroutine/fiber implementation
│   ├── shims/              # Runtime JavaScript shims
│   └── stubs/              # Stub implementations (libgit2, curl)
├── scripts/                # Build scripts
│   ├── build-wx-wasm.sh   # Build wxWidgets for WASM
│   ├── build-wasm-test.sh          # Build wxWidgets test apps
│   ├── deps/               # Dependency build scripts
│   ├── kicad/              # KiCad build scripts
│   ├── common/             # Shared utilities
│   └── config/             # Build config wrappers
├── docker/                 # Docker build environment
├── tests/                  # Playwright E2E tests
│   ├── e2e/                # Test specs
│   └── apps/               # WASM test applications
├── tools/                  # External tools (binaryen)
└── output/                 # Build output (pcbnew.js, pcbnew.wasm)

Feature Branches

Curated design docs and research notes for each feature live in docs/features/<branch-name>/ (committed).

./scripts/create-feature-patches.sh [branch-name] generates per-branch patches (root.patch, kicad.patch, wxwidgets.patch) into a local features/<branch-name>/ scratch dir. That dir is gitignored — the patches are local history, not committed.

Two Build Workflows

1. KiCad Build (Docker)

Full KiCad PCBnew build using Docker:

# Build KiCad WASM
./docker/build.sh

# Copy output to test directory
./tests/scripts/setup-kicad-wasm.sh

# Run KiCad tests
cd tests && npm install && npm run test:kicad

Output: output/pcbnew.js, output/pcbnew.wasm

See docs/build.md for detailed build documentation.

Creating an isolated worktree (with submodule branches)

For an experiment or feature you can work in a disposable git worktree so the main checkout stays pristine. This repo has four submodules (kicad, wxwidgets, binaryen, web/pcbjam-shared); a new worktree starts with them empty, so initialize and branch each one:

# 1. Create the worktree on a new branch (off main), at a sibling path
git worktree add -b experiment/my-thing ../kicad-wasm-my-thing main

# 2. Check out the submodules INSIDE the worktree (working trees only;
#    git objects are shared with the main checkout)
cd ../kicad-wasm-my-thing
git submodule update --init kicad wxwidgets binaryen web/pcbjam-shared

# 3. Create a matching branch in each submodule (they start at detached HEAD)
git checkout -b experiment/my-thing                 # root already on it via -b above
for sm in kicad wxwidgets binaryen web/pcbjam-shared; do
  git -C "$sm" checkout -b experiment/my-thing
done

Then build from inside the worktree. Use an isolated Docker project — do NOT set COMPOSE_PROJECT_NAME to another branch's project (e.g. kicad-wasm-main), which can collide with other workflows; docker/build.sh auto-derives an isolated project name from the worktree branch. The first build provisions deps (wxWidgets + OCC) from scratch. To keep the machine responsive / bound wasm-opt RAM, cap parallelism and skip the slow release optimization:

KICAD_DOCKER_CPUS=4 BINARYEN_CORES=4 BINARYEN_OPT_LEVEL=-O1 \
  ./docker/build.sh pcbnew -j 4

Tear down afterward with git worktree remove ../kicad-wasm-my-thing (and docker compose -p <project> down -v to drop the isolated volumes).

Fresh worktree provisioning

Some test artifacts are gitignored and are NOT produced by the build pipeline, so they don't carry into a newly-created git worktree — without them the gal-webgl tests 404 ("Loading WASM...") and the collab specs fail with Could not resolve "@pcbjam/shared". After building (docker/build.sh + scripts/build-wx-wasm.sh) and cd tests && npm i, run once per worktree:

./scripts/setup-worktree.sh   # idempotent: sysroot headers, gal-webgl harness, web/ pnpm install, collab bundle

2. wxWidgets Test Apps (Local)

Build standalone wxWidgets test apps for feature testing:

# Build wxWidgets for WASM
./scripts/build-wx-wasm.sh

# Build test apps
./scripts/build-wasm-test.sh

# Run wxWidgets tests
cd tests && npm install && npm test

Output: tests/apps/standalone/

Prerequisites

For KiCad Build (Docker)

  • Docker Desktop with 16GB+ RAM allocated
  • 10+ GB disk space for build cache

For wxWidgets Build (Local)

  • Node.js 18+ (for tests)
  • Emscripten SDK (auto-installed on first build)
# Initialize submodules
git submodule update --init --recursive

# Install Emscripten SDK (auto-runs on first build, or run manually)
./scripts/setup-emsdk.sh

Testing

cd tests
npm install

# Run all tests
npm test

# Run specific tests
npm run test:kicad          # KiCad tests only
npx playwright test menu    # Menu tests only

See tests/README.md for test documentation.

Screenshots

CI's Linux render is the source of truth for baseline screenshots. On each main push, CI compares its render against the committed baselines and posts the diff (plus the runtime-perf numbers) to Discord. To update baselines after an intended render change, promote a CI run's render — only meaningfully-changed images restage, so it stays churn-free:

cd tests
npm run screenshots:check                          # local gate: current vs baselines
npm run screenshots:promote -- --run <ci-run-id>   # adopt a CI run's render, then commit

See tests/tools/screenshots/README.md.

Current Status

  • wxWidgets WASM: Core widgets working (menus, dialogs, grids, trees, OpenGL)
  • KiCad PCBnew: Builds and loads in browser, canvas rendering working
  • In Progress: Testing wxWidgets features used by KiCad

Documentation

See docs/README.md for the full documentation map. Highlights:

Landing page / website

The marketing site and landing page live in site/ (Astro), deployed as static assets to Cloudflare R2.

On every release, bump the build SHA. site/src/components/Footer.astro has a hardcoded BUILD_SHA constant that is shown in the footer and links to the corresponding commit. Because the main-repo commit pins the KiCad and wxWidgets submodule revisions implicitly, this is our GPLv3 corresponding-source pointer (surfaced on /licenses). The site is static, so nothing sets it automatically — update BUILD_SHA by hand to the deployed pcbjam commit each time you release.

License

KiCad is GPL-3.0. This project follows the same license.

The site combines KiCad (GPLv3) with the wxWidgets fork; the wxWidgets WebAssembly port files are LGPL v2 (without the wxWindows binary exception). See the /licenses page (site/src/content/legal/licenses.md) for the full breakdown and the corresponding-source offer.