Squash of experiment/ff-big-modules vs main.
Big-module routing removed: native-EH shrank kicad_editor below
SpiderMonkey's x86-64 code budget (runs 29355049705/29356152413 green on
stock Firefox), so BIG_MODULE_SPECS routing and the baseline-only-JIT
crutch are gone — kicad-firefox and kicad-chromium both run the full
suite, with the module compiled the way real users' browsers compile it.
Per-engine screenshots end to end: stableShot/shotPath write
test-results/<engine>/<name>.png; baselines move to
baseline-screenshots/{chromium,firefox}/ and the whole tools/screenshots
pipeline (compare/promote/manifest/spec-map/changelog/Discord) keys on
<engine>/<name>. Previously Firefox and Chromium renders of one spec
overwrote each other and Firefox renders were never actually gated.
Seeded from CI run 29421380806 (92 new firefox baselines, +24 chromium
web-suite shots); manifest generated from the baseline tree.
One merged playwright.config.ts (kicad/asyncify/coroutine/perf as
projects); ~25 dead npm scripts dropped. The web suite is gated in CI for
the first time ever (4 rotted specs fixed, 5 broken lib-bridge specs
triaged as fixme in docs/features/web-e2e-rot/); cheap lint step after
npm ci; last 26 blind-sleep violations fixed.
SwiftShader retired: CI Chromium renders WebGL on ANGLE → Mesa llvmpipe
(--use-gl=angle --use-angle=gl --ignore-gpu-blocklist; the blocklist flag
is mandatory — llvmpipe is blocklisted and WebGL is silently unavailable
without it) in BOTH configs. Under WORKERS=4 congestion SwiftShader
transiently failed the first post-board-load draw and the recovery
cascade ended in a silent permanent Cairo fallback — that engine flip was
the "~1.2% changedRatio both directions" occ-export baseline flake.
Validated 160/160 across two 80-repeat rigs; full analysis in
docs/features/wx-parity-bugs/occ-export-context-eviction.md. Chromium
baselines shift slightly on llvmpipe — promote once from the first green
run. Deflakes the new coverage exposed: presence baselines settle before
capture; presence fixtures declare current file formats; perf gets its
own outputDir so CI evidence survives; occ-export settles the board paint
before the export dialog; menu-item waits (waitForRenderedByLabel before
clickMenuItem) in 4 specs + the TESTING.md rule.
Web suite runs the PROD build, in parallel: webServer becomes backend
`start` + the standalone's e2e:preview (build-preview.mjs: link-wasm →
stash the public/wasm symlink aside during vite build, build-demo.mjs's
move — then vite preview as the persistent server). The wasm middleware
serves /wasm/* in preview and emits COOP/COEP/CORP itself (a pthread
worker script's own response must carry COEP or Chrome kills it with
ERR_BLOCKED_BY_RESPONSE). VITE_* flags bake at build time;
VITE_ALLOW_USER_OVERRIDE joins turbo globalEnv. fullyParallel + default
workers: 5.2m → 1.4m. Determinism fixes the parallel run exposed:
shared-page specs become serial groups; locks.spec grabs alice's exact
item via the new kicadCollabTestSelectByUuid hook (cross-tab "first
footprint" order is not a ysync invariant); quit specs poll page.url()
(quit supersedes its own navigation — NS_BINDING_ABORTED on Firefox).
Suite: 51 passed / 12 skipped / 0 failed in 1.6m.
CI-coverage gate (lint:ci-coverage): every tests/**/*.spec.ts must be
reachable from the npm scripts the workflows invoke — scraped from
.github/workflows/, resolved through package.json, coverage asked from
playwright --list itself. Rules: uncovered-spec + orphan-project (with a
documented LOCAL_ONLY_PROJECTS allowlist). Gating next to
lint:determinism; 138 spec files / 13 projects accounted for.
Product fixes kept from the investigations (reachable on real GPUs too):
wx 7799fd1be5 — paint flags clear before dispatch + Invalidate always
propagates; kicad 3dcfea5e45 — SwiftShader pass-boundary flush +
per-instance font texture + first-frame GL-error drain (GAL recovery
recovers instead of falling back to Cairo) + the user-facing eeschema
switch navigates again under __EMSCRIPTEN__ (project-sync's
FaceRegistered gate had rerouted it into the hidden sync player; caught
by the newly-gated web suite).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018eUxiPApHgGiu9NFyQfhAq
docker/build.sh "all" now includes kicad_tools (finalizes in-container, so
it never contends with the editor's wasm-opt critical path); .ci-cache-epoch
bumped — the cached FINAL output set changes. wasm-build.yml gains the
corpus-lint + CLI-contract gate step (run_tests leg): with the artifact now
built in CI, the skip-when-unbuilt scripts bite. Release runs
(upload_output) start shipping kicad_tools in the wasm-output artifact —
the closed repo's runner-image workflow consumes it from there.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
- 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>
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>
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
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
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
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>
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>
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
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>
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>
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>
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>
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>
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>
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>