Commit graph pcbjam/.github/workflows
Author SHA1 Message Date
Istvan Matejcsok
4fd41a31d5 ci(3d): build the 3D WebGL harness + parity report in wasm-build
- build 3d-webgl test app after gal-webgl (same recipe: wx build + docker
  sysroot headers); the capture spec already lives in the default e2e suite
  and self-skips when the app is missing, so it now runs
- report-only parity + webgl-self step after the e2e suites
  (continue-on-error: the committed baseline-webgl set is Mac-Chromium-
  sourced — promote CI renders and flip gating if SwiftShader drifts past
  the 0.005 floor)
- exclude tests/apps/3d-webgl from the test-apps cache (rebuilt every run,
  like gal-webgl) and upload tests/3d-regression/output/** (renders, diffs,
  the 3d:review triptych gallery) with the e2e artifacts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:46:23 +02:00
Istvan Matejcsok
bbeef6d20e 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 17:56:02 +02:00
Viktor Vaczi
c005e1ba73 feat(ci): e2e failure no longer suppresses the screenshot report
An e2e failure used to skip everything downstream: a wx failure skipped the
asyncify suite (&&-chain) and the kicad suite, and any failure skipped the
screenshot check + Discord report entirely — leaving only the bare text notice
even though the rendered screenshots were already on disk.

Now the three suites run as separate steps gated on !cancelled() + "previous
stage wasn't skipped" (build failures still skip all tests), and the screenshot
report runs whenever the suites ran, posting with an --e2e pass/fail badge
computed from the step outcomes — so a wrong or MISSING screenshot (spec died
before page.screenshot() => classified "removed") is visible on Discord on red
builds too. The text-only failure notice becomes a fallback for when the rich
report didn't post (build broke before tests, or the report errored) — no
duplicate ping. A failing suite still fails the job; gating is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTxTrY9on6A8gLvogFbGiy
2026-07-02 17:30:15 +02:00
Viktor Vaczi
e01256b54d fix(ci): binaryen tools cache also needs lib/ — bin-only restore broke every post-process
The binopt cache from b4cb10b saved only binaryen-hoist-build/bin, but the
tools dynamically link lib/libbinaryen.so, so the first cache HIT (run
28585074335) failed all 7 tools' finalize with a loader error. Cache bin/ +
lib/, bump the key to binopt-v2 (Ubicloud never re-saves an exact-key hit, so
the poisoned v1 entry can't be repaired in place), and make the
BINARYEN_TRUST_PREBUILT guard exec both tools with --version instead of -x
existence checks so an incomplete restore falls through to a source build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTxTrY9on6A8gLvogFbGiy
2026-07-02 14:29:20 +02:00
Viktor Vaczi
b4cb10b1c6 perf(ci): cache the per-run toolchain + test-app work (binaryen, emsdk, apps, browsers)
Four new caches in wasm-build.yml, keyed on their real inputs:

- Binaryen post-process tools (bin/ only, keyed on the submodule SHA): on a
  hit BINARYEN_TRUST_PREBUILT=1 tells build-wasm-opt.sh to skip cmake+ninja
  and trust the restored binaries (~46s/run). The guard is env-gated so local
  pass iteration (uncommitted sources, same SHA) is unaffected.
- Built wx test apps (tests/apps minus kicad/ staging + gal-webgl/): skips
  the whole build step on a hit (~3 min — the post-link hoist+asyncify over
  ~74 apps dominates). Key covers wx SHA + kicad SHA (some apps compile real
  KiCad sources: thread_pool.cpp, libcontext), binaryen SHA, tracked app
  sources, and the build/post-link scripts + shims. Gated on a wx cache HIT:
  the app build creates the libwx_*.a symlinks the GAL link needs, so on a
  wx rebuild the apps must rebuild too.
- Host emsdk (tools/emsdk, keyed on the pinned EMSCRIPTEN_VERSION): mostly
  availability insurance — a fresh install is only ~23s but pulls ~340 MB
  from github.com + storage.googleapis.com on every run. downloads/ tarballs
  are pruned before the post-job save.
- Playwright browsers (~/.cache/ms-playwright keyed on tests/package-lock).

Warm-cache runs should drop from ~12 min to ~8-8.5 min (e2e suites are the
floor). Measured baselines from runs 28577824366 / 28520845242.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeiSRRScdaox5jBueJNcyG
2026-07-02 12:40:22 +02:00
Viktor Vaczi
0702192fed fix(ci): screenshot report is report-only again — never fail the build on drift
Per request: a screenshot difference shouldn't fail CI; the Discord post is the
signal. Revert the enforcing gate (3efcdcf): drop `--fail-on-change` + the
exit-status gating and restore `continue-on-error` on the on-success report step
(compare.ts exits 0 without the flag). Remove the now-moot fail-notice guard.
Build/e2e failures still post the "CI failed" notice; changed/added/removed still
post the captioned Discord report + perf.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 12:22:19 +02:00
Gergő Törcsvári
4330c9344e
feat(3d): live models CDN — 10.0.3 publish wiring + wrl→step fallback
- kicad-packages3D is STEP-only from the 10.x tags: ensureModelInMemfs
  falls back from a missing .wrl to the same-stem .step, written and
  answered under the .step path (the returned path's extension picks
  the parsing plugin — no C++ awareness). Verified against the live
  CDN with the gallery demo board (4/4 wrl refs served as step).
- upload-models-r2.sh: rclone/S3 bulk upload of a local publish layout
  (wrangler-per-object can't move ~14k blobs); published 10.0.3 —
  105 libs / 7,238 models, 3.4GB raw → 500MB brotli — to pcbjam-cdn.
- deploy-demo.yml MODELS_TAG=10.0.3 → build-demo --models-tag →
  VITE_MODELS_MANIFEST_URL (matches LIB_TAG: model refs come from the
  footprints at that release).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014AT7gVHRktDYoQ68S4x6A4
2026-07-02 11:35:37 +02:00
Viktor Vaczi
3efcdcf91c fix(tests): enforce screenshot gate; drop shadow dir, flaky retinascale, 12 stale
Finalize the screenshot review system after the first Linux re-baseline
(that run showed 355/356 stable — the placeholder floor is fine as-is):

- ENFORCE the gate: `screenshots:check --fail-on-change` now fails the build on any
  changed/added/removed vs baselines (it still posts the drift report first, so the
  failure is actionable; a real render change → re-promote). The generic "CI failed"
  notice is suppressed for gate failures so we don't double-post on drift.
- Exclude retinascale-01-loaded: a fullPage HiDPI test whose captured height + DPR
  scaling vary run-to-run (~60% inter-run diff) — a flaky test, not render noise.
  IGNORE_SCREENSHOTS in config.ts; compare/promote/gen-manifest skip it; baseline removed.
- Kill the baseline-dir shadowing: drop e2e/baseline-screenshots/ from BASELINE_DIRS +
  delete its 3 files (grid-tab-final, wxgrid-controls, wxgrid-dedicated-page) that
  duplicated names in baseline-screenshots/ with different bytes.
- Prune 12 stale baselines (renamed/removed specs: wizard-01..04, gerbview-wizard-01..04,
  zoom-pl_editor-*, popup-03-palette).
- Delete the dead compare-screenshots.sh / update-baseline-screenshots.sh.
- Regenerate screenshot-manifest.json (355 entries).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 11:30:33 +02:00
Viktor Vaczi
f49d536f95 fix(build): wx 'zlib.h not found' CI flake — self-heal zlib port + deterministic cache touch
Two fixes for the intermittent wx-build failure on CI (zipstrm.cpp: fatal
error: 'zlib.h' file not found, surviving the serial retry):

- build-wx-wasm.sh: --with-zlib=sys resolves to the Emscripten zlib PORT,
  which only `embuilder build zlib` installs — and that ran only in the
  configure branch. A pre-configured build dir (CI cache) + fresh emsdk
  therefore died on any recompile of a zlib-using TU. Ensure the port
  before every make; --force because embuilder stamps the port on libz.a
  alone, so a half-populated cache would no-op.

- wasm-build.yml: the cache-restore `touch {} +` stamped files ns-apart in
  readdir order; GNU make 4.x compares ns mtimes, so objects touched before
  a generated header they depend on (wx/setup.h, pcre2.h via .deps/*.d)
  recompiled — a random subset per run, which is what made the failure
  flaky. Use one shared timestamp instead: equal mtimes = up to date.

Reproduced + validated locally (remove sysroot zlib.h + one .o → exact CI
failure; with fix, self-heals in 0.5s; Linux make ns/equal-mtime behavior
verified in a container).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeiSRRScdaox5jBueJNcyG
2026-07-02 09:59:32 +02:00
Viktor Vaczi
b270507505 fix(tests): keep test-results across CI suites; loud pipeline alert
The screenshot report went silent on green CI runs. CI runs four sequential
`playwright test` invocations (wx, asyncify, kicad, perf) that all default their
outputDir to `test-results`; Playwright clears the whole outputDir at the start
of every run, so each suite wiped the previous suite's screenshots. By the time
`compare` ran, only perf-*.json remained -> all 354 baselines read as "removed"
-> the no-render guard stayed silent.

- Gate Playwright's outputDir off test-results/ in CI only
  (process.env.CI ? 'pw-artifacts/<suite>' : 'test-results') in the wx / asyncify
  / kicad configs, so the committed-baseline screenshots (page.screenshot to
  'test-results/...') accumulate in test-results/ across the suites for compare.
  Local single-suite behavior is unchanged.
- post-discord: replace the silent no-render guard with a loud, image-less
  pipeline alert ("No screenshots produced this run") when 0 render; genuine
  partial removals still post as the normal capped REMOVED list.
- gitignore pw-artifacts/; upload tests/pw-artifacts/** as a CI artifact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 09:20:35 +02:00
Gergő Törcsvári
9b41d7321f feat(site): link the live demo + serve the gerber demo from the versioned CDN
Refer to demo.pcbjam.com from the landing: an accent "Live demo" link in the
header nav, an "Open the live editor" hero link, a note in the gerber section,
and a "Try the live demo now" line in the final CTA.

Rework the embedded gerber viewer (public/gerber-demo/boot.js) to source the
WASM from the deploy pipeline's versioned CDN instead of the hand-synced
assets.pcbjam.com bucket: resolve gerbview's content-addressed folder at runtime
from the release manifest (manifest-latest -> tag -> manifest-<tag> -> gerbview),
load all assets (glue + wasm + images.tar.gz) from cdn.pcbjam.com, and load the
cross-origin pthread worker via a same-origin blob importScripts shim (mirrors
web/standalone/src/wasm/boot.ts). Bump the config-seed KICAD_VERSION_DIR
9.99 -> 10.0 to match the deployed build. Drop the now-obsolete committed glue
mirror and the old assets.pcbjam.com sync/r2-deploy scripts.

Collapse the header nav to the hamburger below 1025px — the added demo link no
longer fits the 1024px-capped bar on a single row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 07:40:30 +02:00
Viktor Vaczi
13551f4f22 feat(tests): screenshot regression + Discord review, perf-tracked
New tooling in tests/tools/screenshots/ (TypeScript via tsx):
- compare.ts: one pixelmatch engine (AA-excluded), connected-component
  "where to look" boxes, old|new+boxes|heatmap triptych, per-engine floors.
- promote.ts: churn-free updater — overwrite a baseline only when decoded
  pixels differ beyond the floor, copying CI bytes verbatim (no re-encode
  churn); pulls a CI run via `gh run download` or a local --from dir.
- post-discord.ts: always-on CI-on-main report (SHA + e2e status + the
  track-only runtime-perf table), then screenshot triptychs, batched +
  size-capped + flood-collapsed + 429-aware.
- perf-report.ts: perf table with Δ vs the previous main run (via gh).
- changelog.ts: no-build git-history baseline differ (Discord trigger B).
- noise.ts / gen-manifest.ts: calibration + manifest generation.

CI wiring:
- wasm-build.yml: post-test step runs the gate + report on the already-
  produced test-results (no extra build); report-only (continue-on-error),
  posts only on push to main, inert without DISCORD_WEBHOOK_URL.
- ci-ubicloud.yml: secrets: inherit (pass the webhook through).
- screenshot-changelog.yml: ~30s no-build changelog on baseline changes.

screenshot-manifest.json: canonical 354-name set + best-effort engine tags
(313 chromium-swiftshader / 41 firefox-llvmpipe).

Normalize scale:'device'->'css' across 18 spec files (no-op at CI DSF=1)
so committed baselines are uniformly css-scaled.

Design: CI's Linux render is the single source of truth; no pinned
container (accept rare env drift -> re-promote); dev commits via promote.
Replaces the byte-cmp compare-screenshots.sh + file-size-proxy
update-baseline-screenshots.sh (kept for now until the first re-baseline).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:17:20 +02:00
Viktor Vaczi
3e5791774b test(perf): track-only runtime-perf E2E for eeschema + pcbnew
Measures the current build's cold load, open+render, and pan/zoom FPS
(1x/4x/6x CPU-throttle sweep) for both editors, writing
tests/test-results/perf-*.json. Track-only: asserts only that the app booted
and the document opened — no perf thresholds, so it never gates CI.

Runs on a new Chromium `perf` Playwright project (CDP throttling; pcbnew needs
V8 anyway) via `npm run test:perf`, and is wired into wasm-build.yml as a
continue-on-error step so the numbers are captured/uploaded without flaking the
gate. Reuses the existing fixtures, ready-signal, fs-inject, and board-ready
helpers; FPS counter is cancel-able so a throttle sweep on one page doesn't
accumulate rAF loops.

.gitignore: ignore /benchmark-builds/ (disposable prebuilt-WASM + standalone
harness bundle used to reproduce the native-vs-JS-EH comparison locally).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:43:54 +02:00
Gergő Törcsvári
b5ee3da92c
perf(cdn): publish demo WASM as brotli-q11 + no-transform
release.yml's publish-wasm job now uploads .wasm/.js with
`--compress br --quality 11` instead of gzip-6, and IMMUTABLE
content-addressed blobs carry `no-transform` so Cloudflare's edge
can't decompress + re-serve them at its on-the-fly br-4 (and the
original Content-Length passes through).

~15-20% smaller wasm on cdn.pcbjam.com. Forward-only: tool folders
already published stay gzip until their source changes (identity is
hashed over uncompressed bytes, so the compressor switch never
re-versions or re-uploads existing folders).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 12:21:54 +02:00
Gergő Törcsvári
e7640d0a97
build(wasm): standardize asyncify wasm-opt tail to -O1 everywhere
The Binaryen wasm-opt shrink after Asyncify was the only -O2 in the
pipeline: -O2 by default (local + tag release) but -O1 in main CI. The
C++/wx/deps compile is already -O1 everywhere (DEBUG_BUILD defaults to 1;
nothing passes --release in CI/release). Pin the tail to -O1 too:

- apply-asyncify.sh: BINARYEN_OPT_LEVEL default -O2 -> -O1
- release.yml: opt_level -O2 -> -O1 (demo now ships -O1)
- ci-ubicloud.yml / wasm-build.yml / docker/build.sh: refresh stale -O2 comments

Because both callers now build at -O1, the FINAL cache key converges, so a
tag release FINAL-cache-hits main's build and skips the asyncify-tail
rebuild entirely (previously the ~1-2h -O2 wasm-opt was rerun per release).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 12:18:57 +02:00
Viktor Vaczi
c1ef489cfa feat(wasm-eh): migrate the WASM build to native wasm exceptions (+ 3D viewer default-on)
Replace the legacy Emscripten JS-exceptions model with native wasm-EH (legacy
encoding) across the whole build, keeping Asyncify coroutines working via a
from-source Binaryen --hoist-cpp-catches pre-pass. Net result: native-EH is the
only build mode, the 3D viewer is on by default, and pcbnew shrinks substantially.

Highlights:
- Binaryen submodule everywhere + --hoist-cpp-catches integration in apply-asyncify;
  post-link Asyncify covers every app wasm (not just standalone test wasm).
- Build deps (incl. OpenCASCADE without OCC_CONVERT_SIGNALS) and all KiCad apps
  with -fwasm-exceptions; emscripten_sleep added to the post-link asyncify-imports.
- libcontext fiber entry wired under native exceptions; while-loop main loop +
  currData shim injected into all wx apps.
- Native-EH collab apply fixed: DEBUG-define the embind TU + match all out-of-CMake
  C++ TUs' ABI flags to the core, fixing the vtable-layout skew / mis-dispatch.
- 3D viewer enabled by default (real raytracer linked, not the stub).
- Retire the EH-spike scaffolding; flip the asyncify-races ablation pins to
  shim-redundancy pins (native-EH stays clean with the legacy shims ablated).
- Fix the asyncify-races quiescence check to not require Asyncify.currData==0:
  under the native-EH per-frame-yield top loop the main stack is asyncify-suspended
  every frame, so currData legitimately churns (a freed-but-not-yet-nulled buffer,
  not a leak). Refresh the pcbnew toolbar screenshot baseline for the new kicad.
- CI: drop the obsolete binaryen_version input/env (the build uses the binaryen
  submodule fork's wasm-opt, not a version download); key the wasm-output cache on
  the binaryen submodule SHA instead.

Bumps the wxwidgets + binaryen submodules to their squashed feature commits.

Validated green: all 7 apps native-EH (real 3D in pcbnew); KiCad e2e 63/63
Firefox + Chromium (3D viewer renders); wx 336; coroutine 34/34 both engines;
asyncify 7/7 both engines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:40:26 +02:00
Gergő Törcsvári
39cb04b127
feat(standalone): demo follow-ups — waitlist, analytics, move-to-local, file tree, load progress
- Waitlist signup form on the home page (between Projects and Tools),
  cross-posting to the landing site's /api/waitlist (CORS + OPTIONS added there,
  gated by WAITLIST_ALLOWED_ORIGINS).
- Version badge gains a pcbjam.com landing-page link (VITE_LANDING_URL).
- Optional Plausible analytics from VITE_PLAUSIBLE_DOMAIN (off by default);
  wired into build-demo + both deploy workflows via vars.PLAUSIBLE_DOMAIN.
- Opening a read-only gallery project auto-"moves to local": it forks into a
  writable browser-local project at the same slug, shadowing the gallery row.
- Project + local-folder views use the same iconed tool launcher (ToolGrid)
  and a navigable directory FileTree instead of a flat file list.
- Boot overlay shows real wasm download progress (Module.instantiateWasm +
  streaming byte counter) and a "taking too long" state after 60s.
- Home Libraries section lists the active libs source (the read-only R2/CDN
  set in the demo) with a name filter; useLibs now reads libsSourceConfig.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPJYx4urFVhdMjEmYfMCMD
2026-06-27 07:59:09 +02:00
Gergő Törcsvári
913bc90fa0
ci: single build recipe (reusable) + ordered tag release pipeline; two-tier opt cache
Fixes two coupled bugs: (1) a tag deployed the demo by snapshotting the STALE registry with no build/publish, so v0.1.1 shipped v0.1.0-era wasm; (2) publish-wasm built -O1/3D-off while ci-ubicloud built -O1/3D-on — two diverging recipes.

wasm-build.yml (reusable workflow_call): THE single build+test recipe, parameterized by opt_level / build_3d_viewer / run_tests / no_cache / upload_output. Two-tier output cache around build.sh's new --compile-only/--postprocess-only split: a BASE cache keyed opt-INDEPENDENTLY (compile output + sysroot headers, shared across -O1/-O2) and a FINAL cache keyed opt-SPECIFICALLY. final-hit ⇒ skip all; final-miss+base-hit ⇒ restore base, run only the asyncify/-O tail; full-miss ⇒ compile then postprocess. Both keys now include the 3D flag, closing the cache-poisoning divergence.

ci-ubicloud.yml: thin caller (main/PR/dispatch) → wasm-build at -O1. release.yml (tag v*): meta → build (wasm-build at -O2 + e2e gate, uploads output, reuses main's base cache so only the -O2 tail rebuilds) → publish-wasm (content-addressed push + manifest-<tag>.json) → deploy-demo (build-demo pinned to that manifest + libs/kicad/$LIB_TAG → Pages). So the shipped build is exactly the tested build, and the demo can never point at stale wasm.

deploy-demo.yml: demoted to manual dispatch-only re-deploy (keeps the --from-registry snapshot for re-shipping an already-published tag). publish-wasm.yml: deleted (folded into release.yml + wasm-build.yml).

Validated: actionlint clean (bar the known ubicloud custom-label note), YAML + needs-graph + reusable-path checks pass. NOT runtime-tested — the build can't run locally; needs a CI smoke (a PR exercises ci-ubicloud→wasm-build; a throwaway tag exercises release.yml) before relying on it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 16:23:33 +02:00
Gergő Törcsvári
6b737f95ed
feat(deploy): publish-libs CI + full clone (C1.3)
publish-libs.ts: --clone <dir> shallow-clones kicad-symbols + kicad-footprints at --lib-tag (full, all libs) and uses them as sources, so CI publishes the whole set with one command.

publish-libs.yml: decoupled workflow_dispatch (mirrors publish-wasm.yml) — lib_tag input (default 10.0.3) + force; clones + publishes to R2, skip-if-exists keyed by the tag. Dependency-free import chain ⇒ no workspace install (just node + npx tsx + git + wrangler).

deploy-demo.yml: LIB_TAG=10.0.3 → build-demo --lib-tag, so the demo serves the full library set from libs/kicad/<LIB_TAG>. Validated locally over .cache: publish then skip-if-exists; both workflows valid YAML.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 12:47:38 +02:00
Gergő Törcsvári
e94f5b31be ci: cache KiCad WASM build output to skip Docker on unchanged sources
Add an output-cache layer to ci-ubicloud.yml so a build whose inputs are
unchanged (typical when only tests/ or web/ change) skips the in-Docker
compile + the 1-2h host asyncify/wasm-opt chain — the bulk of the ~1h52m
run. On a hit, output/ and the GAL sysroot headers are restored from cache
and the deps restore/seed, build.sh, and sysroot export are all gated on a
miss, so Docker is never started.

Key: kwasm-<os>-bin<ver><opt>-k<kicad-sha>-wx<wx-sha>-sc<hash>-e<epoch>.
The "sc" hash (scripts/deploy/wasm-cache-hash.mjs) folds in just the
build-logic files that shape the wasm bytes (host post-processing,
per-tool compile recipes, scripts/deps, docker/Dockerfile+build.sh) plus
itself; it is content-based, order-independent, and identical on macOS/CI.
*.wasm.debug.wasm (5+ GB, unused by tests) is excluded -> ~0.5 GB entry on
Ubicloud's transparent 30 GB/repo/week cache.

Invalidation:
- bump .ci-cache-epoch in a commit for durable busts (inputs the sc-hash
  can't see: base-image/apt drift, a bad cache);
- [no-cache] or [rebuild-wasm] in the commit message / PR title, or
  workflow_dispatch no_cache=true, for a one-off rebuild (split
  restore/save so the bypass still refreshes the entry).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:51:33 +02:00
Gergő Törcsvári
8069688b52
fix(demo): resolve tag from input first (workflow_dispatch ref_name is the branch)
GITHUB_REF_NAME is 'main' on workflow_dispatch, so the old ${REF:-input} order
labeled every dispatched deploy 'main'. Prefer github.event.inputs.tag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:45:41 +02:00
Gergő Törcsvári
f3683bda39
ci(demo): auto-create Pages project on first deploy (idempotent)
Project not found errors when pcbjam-demo doesn't exist yet; create it (with the
production branch matching --branch) before deploying so the pipeline is
self-contained.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:37:43 +02:00
Gergő Törcsvári
4261d90683
ci(demo): pin pnpm 10.33.0 in deploy-demo
The repo root has no package.json, so pnpm/action-setup can't infer the version
(it lives in web/package.json). Pin it explicitly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:34:28 +02:00
Istvan Matejcsok
e8cef07f7f ci: 🎡 add 3d viewer on 2026-06-19 12:14:52 +02:00
Gergő Törcsvári
5aae2a0d16
feat(demo): demo.pcbjam.com deploy — versioned WASM CDN + static gallery + tag CI
Cross-origin WASM CDN (cdn.pcbjam.com, R2): per-tool content-addressed,
immutable folders + a per-release runtime manifest (snapshot from registry).
boot.ts loads pthread workers cross-origin via a same-origin blob shim.
Static no-backend project source (demo gallery; Save downloads to local),
api.uploadFileBytes kept and config-gated. demo.pcbjam.com on Cloudflare Pages.
deploy-demo.yml (tag v*) snapshots WASM + content + builds + deploys;
publish-wasm.yml builds on Ubicloud. Design/spec docs live in pcbjam-private.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:39:09 +02:00
Istvan Matejcsok
3ad33a10ca ci: 🎡 fix pcb firefox & queue ci runs 2026-06-17 08:56:03 +02:00
Istvan Matejcsok
5df7f40b3c ci: 🎡 -o1 & test workers 2026-06-16 14:46:18 +02:00
Istvan Matejcsok
e203c74a68 ci: 🎡 exclude -o2 and CI PRs 2026-06-16 14:46:18 +02:00
Viktor Vaczi
dcb8d69e91 ci(ubicloud): install web/ workspace so the collab bundle resolves @pcbjam/shared
The KiCad e2e collab specs rebuild apps/kicad/collab-bundle.js with esbuild
(collab/build.mjs), bundling web/standalone/src/wasm/collab/* and its
@pcbjam/shared import. That package is the web/pcbjam-shared submodule, resolved
through the web/ pnpm workspace — which CI never installed, so the build failed
with: [ERROR] Could not resolve "@pcbjam/shared". Install the web/ workspace
(links @pcbjam/shared + pulls the bundle's runtime deps zod/@ts-rest/core/yjs)
before the e2e step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 08:37:57 +02:00
Viktor Vaczi
8dbca8a48b ci: fix stale build-wxuniversal-wasm.sh reference after rename
Commit 7dc1bf2 renamed scripts/build-wxuniversal-wasm.sh to
build-wx-wasm.sh but missed the CI workflow, so the "Build wxWidgets
(wxUniversal WASM)" step ran a script that no longer exists. Update the
run command in ci-ubicloud.yml plus the two stale comments referencing
the old name (ci-ubicloud.yml:43, scripts/common/stages.sh:7).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 19:50:23 +02:00
Istvan Matejcsok
e932530afb ci: Ubicloud is the main CI — retire the Hetzner VM workflows
ci-ubicloud.yml (cherry-picked from the ubicloud-ci experiment branch, green
on run 27375556034: 291 wx + 38 kicad tests in 1h52m) now triggers on main
pushes and replaces ci-full-build.yml and ci.yml. The Hetzner flow needed
create/delete-VM jobs, a PAT + hcloud token, and a shared one-slot
concurrency lock; the Ubicloud runner is a plain runs-on label and finished
faster end-to-end. wasm-opt-bench.yml stays (dispatch-only Hetzner bench
harness, unrelated to CI).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:43:15 +02:00
Istvan Matejcsok
c7e507ced6 ci(ubicloud): copy sysroot headers out of the docker volume for the GAL build
build-gal-webgl-test.sh compiles kicad headers that need boost from
build-wasm/sysroot/include — a host dir on dev machines but inside the
kicad-build-cache docker volume on CI (run 27370316200: ptr_vector.hpp not
found). The Makefile uses only $(SYSROOT)/include, so headers suffice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:42:32 +02:00
Istvan Matejcsok
94e88a15d9 ci(ubicloud): build the GAL WebGL test app — 29 gal-webgl scenarios 404'd
First wx-suite run (27359020746): 262/262 non-GL tests passed; all 29
gal-webgl failures were galTest.isReady() timeouts because
gal_webgl_test.{js,wasm} are gitignored artifacts built by their own script
(build-gal-webgl-test.sh), which the workflow never ran — the page loaded
but the wasm 404'd. The script self-sources the local emsdk like
build-wasm-test.sh. Those 29 timeouts x3 attempts were also ~1.4h of the
1.7h e2e wall-clock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:42:32 +02:00
Istvan Matejcsok
282994e2f2 ci(ubicloud): add the wxWidgets e2e suite to the run
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>
2026-06-12 12:42:32 +02:00
Istvan Matejcsok
3ce2684da8 ci(ubicloud): e2e step uses test:kicad:ci (firefox + chromium-ci projects)
Mirrors the main workflows for e913beb's pcbnew-on-Chromium routing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:42:32 +02:00
Istvan Matejcsok
c6a002bf2b ci(ubicloud): run kicad e2e under xvfb — headed firefox for software WebGL
Same rationale as the Hetzner workflow change in 3168eff: headless Firefox
cannot create any GL context on the GPU-less runner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:42:32 +02:00
Istvan Matejcsok
775b657b89 fix(ci): ubicloud docker rejects cpus limit == host CPUs — cap at nproc-1
Run 27330725978 died at container create: "range of CPUs is from 0.01 to
30.00, as there are only 30 CPUs available" with KICAD_DOCKER_CPUS=30 on the
30-vcpu standard-30. The Hetzner box (fresh docker-ce) accepts cpus == nproc;
Ubicloud's preinstalled daemon doesn't. A 29/30 CFS quota is noise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:42:32 +02:00
Istvan Matejcsok
2e1b261b98 ci: Ubicloud full build + e2e experiment — triggers only on ubicloud-ci
Same recipe as ci-full-build.yml (all 6 tools, Binaryen 130 from source,
pipelined wasm-opt, KiCad e2e) but on ubicloud-standard-30: a plain runs-on
label, so no create/delete-Hetzner-VM jobs and no shared one-slot concurrency
lock. Push-triggered ONLY on this branch (plus workflow_dispatch) so it never
competes with the Hetzner main CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:42:32 +02:00
Istvan Matejcsok
e913bebdeb fix(e2e): route pcbnew-family specs to bundled Chromium on CI
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>
2026-06-11 16:12:01 +02:00
Istvan Matejcsok
f37ddc454d fix(e2e): make the firefox kicad suite pass on GPU-less CI runners
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>
2026-06-11 13:25:02 +02:00
Istvan Matejcsok
6953fbd894 ci: ephemeral Hetzner CI — full build + e2e on main push, all else dispatch-only
Per run, a fresh ccx53 VM is created (Cyclenerd/hcloud-github-runner),
builds + tests run on it, and it is always torn down — no persistent
infra, no lingering cost. Secrets: HCLOUD_TOKEN, HETZNER_RUNNER_PAT.

- ci-full-build.yml: the main-branch CI and the only push-triggered
  workflow. Full build of all 6 tools (pipelined wasm-opt, self-built
  Binaryen 130, ~1h15m) + KiCad e2e; docs-only pushes excluded via
  paths-ignore.
- ci.yml (eeschema-only smoke) and wasm-opt-bench.yml (config sweeps):
  workflow_dispatch-only, since every push-run costs a paid VM.
- Shared one-Hetzner-slot concurrency group across the workflows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 07:50:47 +02:00
Istvan Matejcsok
d677ffca51 ci: 🎡 add ci 2026-06-05 14:12:09 +02:00