Commit graph pcbjam/scripts
Author SHA1 Message Date
Gergő Törcsvári
84a40d4492
diag(asyncify): write-time instrumentation + local warm-load repro findings
The prod differential ladder finished: staged byte VOLUME on a warm load is
the only trigger left (V1a siblings-without-lib-tables dies, V1b +120 files
survives, V1c sibling KiCad files renamed byte-for-byte dies, V1d Leonardo +
123MB of inert markdown dies on loads 3-4; 14MB never dies). 3D models,
collab/ydoc/presence, lib tables, sibling KiCad handling and file count are
all exonerated — volume only loads the dice on the underlying race.

That made the crash reproducible locally for the first time in six campaigns:
a persistent browser profile + a 110MB project fails every warm load with the
exact prod signature. Iteration is now ~12 minutes instead of a release cycle.

Shim: every fiber switch now records the departing side's remaining asyncify
buffer and its recorded rewind entry (rem=/rf=), which is what identified the
unrewindable capture and disproved buffer overflow. The deferral family is
closed for good — a microtask-deferred retry on a clean empty stack died
identically to the nested rewind, because the suspension is broken at write
time, not by nesting.

Shell: log the origin stack when wx reports the top window destroyed. That
notification fires from ~wxTopLevelWindowWasm for ANY top-level window, so a
transient frame dying mid-load navigates the user out of the editor — a real
bug in its own right, found while chasing the empty flight-recorder dumps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
2026-08-03 11:05:10 +02:00
Gergő Törcsvári
00b80a923f
feat(asyncify): cumulative collision counters — fcsTotal + rootHotTotal in the STATE dump
The 96-event recorder ring holds under a second of history at idle tick rate
(~110Hz), so settle-time collision pressure scrolls out before any poll can
read it — the prod dumps only caught the kill because the trap froze the
moment. Scroll-proof totals since boot: every finishContextSwitch increments
fcsTotal; every root entry inside a sleep-wake window increments rootHotTotal
(the fatal precondition). Both appear in the [wx-asyncify] STATE line, i.e.
in every trap auto-dump, every __wxAsyncifyDump() call, and every blue-screen
console — turning ANY prod load (crashing or clean) into a dose measurement
for the differential-repro experiment (which project ingredient generates
collision windows: siblings, 3D models, libs).

.ci-cache-epoch 7→8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
2026-08-02 08:55:41 +02:00
Gergő Törcsvári
f734d700a2
fix(asyncify): retire the deferral family — guard-layer road closed
v0.1.24 in prod, doubly convicted the same morning: the Leonardo open
crawled/hung (open:settled result=failed at the 60s escape, heap never past
256MB — every main-loop iteration runs INSIDE its yield-wake extent, so the
"root-owned wake" scope matched thousands of legitimate nested coroutine
Call/returns per open, each paying a deferred macrotask, throttled to ≥1s in
a background tab), AND the Nano crashed 22ms after deferrals=1 fired.
Harmful and insufficient: the fatal nested-rewind interleave and the benign
bulk are observationally identical at this layer — no discriminator exists.

Retired (second and final retraction, async/16 round 5). What stays shipped
and clean: consume-once root suspensions, the internally-parked quarantine +
laundering check, the flight recorder + beacons, the WSOD floor, the
pendingSleeps leak fix (confirmed by pendingSleeps=[] in the Nano dump). The
rare nested-rewind crash is ACCEPTED and fully observable until the
structural fix — the design-B fiber-first runtime (async/06,12,13), where
one scheduler owns every suspension and this interleave cannot exist.

.ci-cache-epoch 6→7.

Local: fiber 2/2 (one refusal beacon) + timer + firefox sweep 21 passed,
chromium scenarios 11 passed/4 quarantine-skips, web fatal+follow 2/2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
2026-08-02 08:18:08 +02:00
Gergő Törcsvári
210b079ed9
fix(asyncify): ownership-scoped root deferral — the recorded nested self-rewind, cured
The v0.1.23 flight recorder caught the kill live (console-export-2026-8-1_19-16-8):
dozens of benign fiber round-trips at w=0, the yield cycling healthily on its
buffer — then "fcs … ROOT w=1" and the trap, state frozen at Rewinding with
currData=root+20. The fatal condition, observed rather than inferred: a fiber
round-trip inside the ROOT's OWN sleep-wake continuation re-suspends and
re-rewinds the root nested inside its live wake rewind. Consume-once passed
correctly — it guards a different corruption and stays.

The round-3 deferral was aimed right but unscoped (taxed fiber-owned wakes,
flaked S4). Final form: every fresh sleep is tagged root- or fiber-owned
(fiber ⇔ started inside a finishContextSwitch fiber slice or a fiber-owned
wake; root entries don't count as slices); finishContextSwitch(root) defers
one macrotask ONLY while a root-owned wake is live (Asyncify.__wakingRoot).
Beacon: root-entry-deferred. Verified inert where it must be: zero beacons
across all 13 drift-trio-scenarios logs (26/26 + 25/26-then-26/26 stress —
the single miss carried no beacons, i.e. the pre-existing under-load flake).

Also: resume re-entries no longer push sleep contexts (the v0.1.23 dump
carried ~380 leaked zero-linked entries), and wake events in the recorder are
tagged R/f for ownership.

.ci-cache-epoch 5→6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
2026-08-01 19:49:38 +02:00
Gergő Törcsvári
ae33a100c2
fix(asyncify): consume-once root suspensions — replaces the wake-window deferral
The deferral (9ca2ac1) modeled the wrong condition and taxed every parked
fiber completion with a macrotask hop: under CI load that stretched
three-client apply chains and flaked drift-trio S4 twice consecutively
(26/26 green locally under stress) — retracted.

The actual fatal state, readable in all four prod stacks once seen: a SECOND
rewind of the same root suspension. Root suspends once per fiber_swap out of
it; two parked fibers completing against one root suspension epoch (a tool
fiber + a collab fiber both waking around open:settled) each drive
finishContextSwitch(root) — the second rewinds already-consumed data →
"unreachable executed" → poisoned runtime, with the wake-side "index out of
bounds" as the sibling symptom.

Cure: stop exempting root from the validity check the shim already keeps.
First consumption proceeds synchronously — zero added latency anywhere; the
second is refused ([wx-asyncify] "root suspension already consumed") — the
yielded fiber stays properly suspended and resumable, root continues via its
real pending resume, libcontext's ghost-epoch contract enforced one layer
lower. Root remains exempt only from the internally-parked quarantine (its
yield park is routine).

.ci-cache-epoch 4→5 (the epoch-4 cache holds the retracted deferral shim).

Local: fiber 2/2 + timer 1/1, firefox sweep 20 passed, drift-trio-scenarios
kicad-chromium 26/26 under 3-worker stress, web fatal+follow 2/2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
2026-08-01 15:53:18 +02:00
Gergő Törcsvári
9ca2ac1e52
fix(asyncify): layer 3 — serialize root re-entry out of sleep-wake windows + flight recorder
v0.1.22 still trapped with BOTH guards silent: the fatal rewind's target is
the ROOT context, which layer 2 exempted. All four prod stacks are the same
collision — a fiber completes its yield-back to main while main's sleep-wake
rewind is still on the stack (maybeStopUnwind → trampoline →
finishContextSwitch → doRewind(root) → unreachable), two "resume main" paths
interleaved in one tick; the 8ms-earlier "index out of bounds" is the wake
side of the same event.

Root entry is legal and constant in healthy flow; only the wake-window
overlap is fatal. So: serialize, don't refuse. The shim marks the
synchronous wake window (Asyncify.__inSleepWake around wakeUp) and DEFERS a
root finishContextSwitch landing inside it by one macrotask
([wx-asyncify] root-entry-deferred beacon, trampoline retry) — an ordering
change only, nothing dropped. Suspension recording happens before the
deferral branch, so the yielding fiber's validity survives the wake chain
nulling currData.

Plus a flight recorder: a 96-entry ring of asyncify/fiber events (sleeps,
wakes, every context switch with ROOT/wake-depth, refusals, deferrals),
silent in normal operation, auto-dumped with full machine state next to the
first trap signature in the console; window.__wxAsyncifyDump() on demand.
The next prod export reads like a black box, not a stack-shape puzzle.

.ci-cache-epoch 3→4 (wasm cache key omits scripts/**).

Local: fiber-resume-park 2/2 (one refusal beacon), timer-park 1/1, sweep 20
passed, web fatal+follow 2/2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
2026-08-01 14:06:53 +02:00
Gergő Törcsvári
a8adfa9843
fix(asyncify): stale-fiber-rewind guard — layer 2, attribution-proof
v0.1.21 still trapped with ZERO jump-refused beacons: the fatal swap PASSED
the C++ swap_suspended guard. Mechanism (async/16 round 2): a fresh JS entry
executing while g_current_context still points at a parked fiber gets
attributed to that fiber — fiber_swap writes a fresh, valid-LOOKING foreign
suspension into the parked fiber's struct and re-marks the flag. The flag
lies; the resume rewinds garbage.

This guard tracks truth at the emscripten-fiber layer (handlesleep.js wraps
Fibers.finishContextSwitch):
- valid suspensions = real swap-outs (currData == oldFiber+20 when the
  trampoline runs), consumed on rewind;
- internally-parked = an entered slice that ended in a handleSleep park
  (currData set, no nextFiber) — quarantined until a GENUINE swap-out,
  where genuine means the fiber's pending sleep has resolved
  (__pendingSleepContexts), so a laundering write cannot lift it;
- entering a quarantined or suspension-less fiber is REFUSED
  ([wx-asyncify] fiber-resume-refused, ghost contract).

The ROOT context is exempt from quarantine and refusal: its rewound
continuation runs the whole main loop, whose routine yield park says nothing
about a fiber body — the first build of this guard quarantined main off that
signal and starved every coroutine return (19 collab e2e reds, empty
results). Root = the old side of the first switch ever.

.ci-cache-epoch 2→3: the wasm output cache key omits scripts/**.

Red/green: fiber-resume-park.spec.ts scenario 2 (laundered resume → exactly
one refusal beacon, both coroutines complete); full fiber-heavy sweep green
(21 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
2026-08-01 10:05:27 +02:00
Gergő Törcsvári
7cfb07a99e
chore(asyncify): report the handlesleep shim's silent concurrent-park repairs
The shim has been silently restoring Asyncify.currData across overlapping
parks since it was written — so a prod console dump could never say whether
the #9153 family fired, mislinked, or was bypassed. Rate-limited [wx-asyncify]
reporting, calibrated against the timer-park e2e (2 rounds, 2026-07-31):

- concurrent-park: a FRESH park (state 0) starting while another chain's
  currData is live. state-2 entries are NOT reported — every legal resume
  re-enters handleSleep while Rewinding (~100/s measured on a healthy run).
- reentrant-state: handleSleep entered mid-unwind (state 1) — never observed;
  if it ever fires it IS the bug.
- aliased-wake-live / overlapped-wake: the restore actually repairing —
  dangerous (other chain still parked) vs benign (other chain done). The
  e2e observed 8 live cross-restores in one run, all survived.

Console.log only, no printf plumbing needed; stacks name the parker
(__asyncjs__wxWasmYieldToBrowser vs _emscripten_sleep etc.).

.ci-cache-epoch 1→2: the wasm output cache key omits scripts/**.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
2026-07-31 20:34:14 +02:00
Gergő Törcsvári
da556f6202
ci: bump the wx build-cache epoch (poisoned pcre chartables symlink)
The v0.1.14.1 release gate died in "Build wxWidgets (wxUniversal WASM)" with
"No rule to make target 3rdparty/pcre/src/pcre2_chartables.c". Nothing in the
tree changed wx — the submodule is the same SHA that released fine as v0.1.13
— and the two main CI runs on this tree built wx and went on to run e2e. The
restored build-wasm/wxwidgets cache is the variable: pcre's makefile rm's that
path and re-links it to pcre2_chartables.c.dist at configure time, so a cache
captured around that window restores a tree whose pcre rule cannot be
satisfied from a clean checkout.

CI's wx cache key hashes scripts/build-wx-wasm.sh, so a header edit is the
lever that discards it. Documented as an explicit epoch counter there, since
the next person hitting this will not guess that editing a build script is how
you evict a cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H8jo7zz1ZwzYpjJ64UZKN4
2026-07-29 17:42:45 +02:00
Gergő Törcsvári
5f5af14e8b
fix(deploy): retry transient CF API failures in the r2 store (4 attempts, backoff)
Two consecutive publish-libs runs died on runner-side CF API flakiness (a 502
mid-put, then 'terminated' on the first probe). Gets and puts now retry with
backoff; a definitive missing-object error still returns null immediately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011CC8aAnUUHcnHy3QCJtUwb
2026-07-29 09:18:46 +02:00
Gergő Törcsvári
6bbef2cb52
fix(deploy): r2 getJSON must not read transient API errors as missing objects
A CF API 502 on the manifest.json probe made publish-libs misdetect an
already-published tag as unpublished and start a full republish (byte-identical
immutable content, so harmless — but ~30 min of redundant uploads before a
second 502 killed it). Only wrangler's definitive missing-object error now
reads as absent; anything else throws.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011CC8aAnUUHcnHy3QCJtUwb
2026-07-29 09:11:45 +02:00
Gergő Törcsvári
8ee8db69e6
feat: standalone download-consent gate + truthful loading states (standalone-load-ux 0001/0002)
Cold loads on versioned CDN deploys now show a consent card (editor MB +
symbol/footprint lib figures, downloaded-now vs on-demand) and wait for OK
before any big fetch; warm loads skip it and show truthful stages (loading
from cache / Compiling / Starting KiCad) instead of the first-download line.

- wasm-assets: resolveWasmMeta (bundle/ver/sizes), download-completion marker
  keyed by content-addressed bundle/ver, update wording, auto-download opt-out,
  HEAD size fallback
- boot: manifest raw size as the progress total (fixes the br/gzip
  Content-Length mismatch), marker written after download+instantiate succeed
- cdn-source: syncState() — IDB warmth peek + sizes.json cold sums
- synced-source: syncState() from the backend envelope's sync refs (private
  platform); remote-source passes libSchema.sync through
- publish-wasm: manifest schema 2 with per-bundle sizes (registry-persisted,
  reuse + snapshot modes); publish-libs: sizes.json sibling key + top-up mode
- fixed 4 stale unit tests (bundle mapping, session-identity email)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011CC8aAnUUHcnHy3QCJtUwb
2026-07-29 07:48:25 +02:00
Gergő Törcsvári
f8ae378645
perf(deploy): parallelize publish-wasm brotli (q11) across files
brotli q11 over the ~320MB wasm set ran serially on one core (~0.4MB/s),
dominating the release publish job. Compress every to-be-uploaded file
concurrently on the libuv threadpool (compressBytesAsync + UV_THREADPOOL_SIZE
sized to the machine), so wall time drops from sum-of-files to roughly the
largest single file (kicad_editor.wasm): 329s for the full set locally vs
~11.5min of CPU. Upload ordering invariants unchanged: meta.json still last
per tool, registry last overall; moved-tag guard and reuse path untouched.
Also adds BROTLI_PARAM_SIZE_HINT and moves the publish-wasm job to
ubicloud-standard-8 so there is a core per file.

Verified byte-identical CDN layout vs the old script (local driver, pinned
builtAt), blob roundtrip to source sha, reuse + --from-registry modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQ9npuB6uLvkv6gzBV5GPh
2026-07-28 12:56:10 +02:00
Gergő Törcsvári
0b1e9d3d54
chore: repoint .gitmodules at the PCBJam org
Redirects from emergence-engineering still work, but fresh clones and CI
checkouts should reference the real home.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y32etYBCKV6t1qDoLoGmgF
2026-07-28 12:52:01 +02:00
Viktor Vaczi
df38ebafb7 feat(deploy/site): serve the apex from the same Pages project, no redirect rule
Vercel was doing the apex->www 308 itself (its "redirect to www" project
setting), so nothing about Cloudflare requires a redirect — the behaviour
just disappears with Vercel. Rather than rebuild it with a zone Redirect
Rule plus a proxied placeholder record, attach pcbjam.com as a SECOND
custom domain on pcbjam-site. Both hosts serve the site and the pages
already emit canonical=www, which is what consolidates them for search.

That drops the riskiest artefact in the migration. Redirect Rules are
zone-scoped and run BEFORE Workers/Pages routing, so a `contains` match
instead of `eq` would 308 app./editor./demo./api. to www — breaking the
product API, not just a marketing page. The sibling hosts are also the
reason this was worth avoiding rather than merely guarding.

APEX_MODE (lib/common.sh) selects the topology, defaulting to `serve`.
08-verify-prod.sh now dispatches through assert_apex: in serve mode it
requires the apex to answer 200 with no hop, to not be a stale Vercel
response, to declare canonical=www, and to expose /api/waitlist. The
`redirect` mode and 07's rules/apex phases are kept for the alternative.

08 also checks the attached domains via wrangler rather than the REST API,
so the whole serve-mode path needs only `wrangler login` — no zone scopes
at all.

Comments that explained themselves via the old redirect are corrected:
astro.config.mjs, web/standalone/src/lib/config.ts and
scripts/deploy/build-demo.mjs. The demo keeps posting to www — not because
the apex redirects, but because a CORS preflight cannot follow one, so
aiming at a host that might ever redirect is a latent breakage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAmkjM7okPdScp9XLW1JVr
2026-07-27 14:34:16 +02:00
Gergő Törcsvári
f1ea6b3965
plugins 0002: kicad_tools --ipc356 + --fab-components fab exporters
Two generic (manufacturer-agnostic) pcbnew-side subcommands for platform
manufacturing plugins: --ipc356 (IPC-D-356 netlist via IPC356D_WRITER) and
--fab-components (board metrics + per-footprint placement/BOM JSON —
absolute board coords, consumers apply their own conventions). Bumps the
kicad submodule for the diet keep-back of export_d356.cpp.

Also closes a CI cache gap: wasm-cache-hash never hashed wasm/**, so a
pure wasm/cli change would cache-hit stale output — { dir: "wasm" } is
now an input (busts the cache once on landing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mr8PQ34yCfvEtEcwSUvLAV
2026-07-27 14:01:30 +02:00
Viktor Vaczi
703cb010b7 eeschema simulator: lazy ngspice_service worker — static sharedspice (XSPICE registry + CIDER), init_dll ifdef, e2e both engines
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:59:21 +02:00
Gergő Törcsvári
44f9373a15
cvpcb-wasm: link CvPcb as third kiface — eeschema Assign Footprints opens in WASM; serial-inline footprint list load fixes pool-task-vs-JS-bridge deadlock
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y8of27UBjJmwY7JkALit2j
2026-07-17 18:00:13 +02:00
Gergő Törcsvári
abb0923f70
standalone-hardening 0006: redirect non-editor routes to the mgmt app
VITE_APP_URL (build-editor --app-base, set to app.pcbjam.com in release CI)
turns on a redirect gate in App: home, project overview and mgmt-only paths
bounce to the mgmt app preserving path+search; tool boots, file deep-links,
lib editors and the @local pseudo-scope stay local. Unset (dev/demo) keeps
today's behavior. Policy is a pure helper (lib/redirect.ts) + vitest table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A42xfPFNdfsUt9eowkC9eM
2026-07-16 17:24:37 +02:00
Istvan Matejcsok
f8ab901772 bench VM: use SSH key auth only, drop the shared password
The wasm-opt bench VM cloud-init seed carried a committed console/SSH password
and enabled SSH password auth. setup-vm.sh already requires and injects an SSH
pubkey, so lock the password (lock_passwd: true) and disable SSH password auth
(ssh_pwauth: false).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 12:46:17 +02:00
Istvan Matejcsok
40b53ad530 analytics: consolidate everything onto one shared Plausible pa- script
All pcbjam properties now load the same new-format Plausible script
(https://plausible.io/js/pa-KjNS9YmidydULZTstsjRg.js — one dashboard,
segment by hostname) instead of per-domain data-domain sites:

- site: replace Vercel Analytics with the pa- snippet in BaseLayout
  (is:inline, prod-only, crossorigin="anonymous" for the COEP
  require-corp routes in vercel.json); drop @vercel/analytics dep.
  Legal pages (cookies/privacy) rewritten Vercel -> Plausible.
- standalone: initAnalytics() rewritten for the new script format
  (queue shim + plausible.init(), async, no data-domain). The gate is
  now VITE_PLAUSIBLE_SRC (script URL, deploy-time opt-in) — unset means
  no tracking, so dev checkouts and third-party GPL builds stay clean.
  VITE_PLAUSIBLE_DOMAIN is gone.
- deploy: build-demo/build-editor --plausible now takes the script URL;
  release.yml/deploy-demo.yml hardcode the (public) pa- URL, so the
  repo Actions variable PLAUSIBLE_DOMAIN is no longer used.

Verified: standalone tsc + vite build (pa- URL present with env, absent
without, crossOrigin kept); astro build has the snippet on all layout
pages and no vercel/insights references; astro dev stays untracked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 19:34:09 +02:00
Gergő Törcsvári
ad9c4675f9
feat: kicad_tools headless CLI
One merged node-WASM CLI (wasm/tools, 26.5MB) supersedes sym_convert +
pcb_convert: --convert-lib (absolutizes paths — fixes the legacy plugin's
silent empty output on relative paths), --lint (now full-parses .kicad_pcb
via the linked pcbnew parser), --erc, --netlist, --bom, --plot, --drc.
Registered as the only headless CLI app; ASYNCIFY=0 CLIs skip the
nanosleep→Asyncify yield shim and exit via _exit (dieted static dtors trap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADzCSeN3Q9DJL3YW2FZTXB
2026-07-10 19:09:01 +02:00
Gergő Törcsvári
28388990ee
ysync 0009 phase 4: sym_convert --lint mode (kicad_lint folded into the converter)
sym_convert --lint [--strict] <file...>: full parse for .kicad_sch (restored
LoadSchematicFile + minimal headless PGM/SETTINGS_MANAGER runtime,
aSetActive=false) and .kicad_sym/.lib; structure-only for other s-expr files;
dup-uuid identity lints (walker) on every s-expr input; errors as
file:line:offset, exit 0/1/2. Convert mode byte-identical (qa gate).

Fixes en route: 64KB stack local overflowed the default wasm stack (mimalloc
corruption); pre-js now inherits process.env into getenv (KICAD_CONFIG_HOME /
SYM_CONVERT_TRACE); font abort stub retired (SCH_SCREEN::Append bbox resolves
draw fonts) -> 18.0MB; six GetMsgPanelInfo typeinfo stubs; BUILD_3D_VIEWER
defaults OFF for headless CLIs (gl1 shim needs glm).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142yAmMGZ3Ejrq3wwVr2Yq2
2026-07-10 13:13:51 +02:00
Gergő Törcsvári
f8da88472c
sym_convert wasm diet: 31.3->11.05 MB via kiface prune + link-order shims (ysync 0009)
New wasm/cli/sym_convert_stubs.cpp: first-definition-wins overrides
(--allow-multiple-definition) severing schematic load/save, the KIFONT
factory (drops newstroke + freetype/harfbuzz), and the four UI virtuals
whose bodies reference pruned typeinfo/data. New
wasm/bindings/sym_convert_embind.cpp: no-op kicadCollabOnSave so the
converter stops linking eeschema's embind object (and --bind) that rooted
the editor surface from .init_array. build-kicad-target.sh: per-app
EMBIND_LINK_FLAG + sym_convert now uses its own embind TU. Bumps kicad
(kiface prune, gated). Output byte-identical on the qa corpus.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H58sC87w12FotXEmxrJrQV
2026-07-10 09:25:14 +02:00
Istvan Matejcsok
a0df92bfd1 deploy: backed editor at editor.pcbjam.com (remote mode, same CDN WASM)
New release.yml job `deploy-editor` (parallel to deploy-demo, same per-tag
WASM manifest): builds the standalone in REMOTE mode via the new
scripts/deploy/build-editor.mjs — projects/libs/auth from the closed API
(VITE_API_BASE_URL=https://api.pcbjam.com, VITE_LIBS_SOURCE=synced), Yjs
board rooms through the API host's path route (VITE_YJS_ENDPOINT=api origin,
doc source ydoc) — and ships it to the pcbjam-editor Pages project. The
demo deploy is unchanged. The closed stack itself (api./app.pcbjam.com)
deploys from pcbjam-private; when both sides change, that repo tags first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dasZuqo6FStgT3rkC85im
2026-07-08 11:42:38 +02:00
Gergő Törcsvári
6fc6a64cd0
feat(libs): eeschema symbol-chooser footprint selector + preview via publish-time fp-index
The merged kicad_editor bundle made the chooser's footprint side reachable
from a schematic session; this wires up the data:

- boot.ts/constants.ts: every kicad_editor frame seeds BOTH sym-lib-table and
  fp-lib-table (+ placeholder files; a created user lib joins both lists) —
  the eeschema frame used to write fp-lib-table empty, leaving the selector
  dead. TOOL_LIB_KIND remains only the presync/primary-kind lever.
- publish-libs.ts + kicad-pretty.ts: publish fp-index.json per tag —
  [name, uniquePadCount] per footprint (countUniquePads mirrors KiCad's
  GetUniquePadCount(DO_NOT_INCLUDE_NPTH)); index-only top-up mode for
  already-published immutable tags.
- source.ts/cdn-source.ts: new bridge op "index" (source-global, dispatched
  before the lib-id parse) + LibsSource.getFpIndex; the CDN source fetches
  <tag>/fp-index.json once (404 ⇒ null ⇒ C++ default-only fallback).
- dev-demo.mjs: --libs-local serves a local publish-libs layout same-origin
  at /libs-cdn (mirrors --models-local).
- tests/web/eeschema-fp-selector.spec.ts: e2e — chooser opens in --frame=sch,
  selector fills from ONE index crossing, clicking a row per-item-gets the
  body and the cross-face GAL preview renders; adaptive for index-less
  sources (asserts crash-free default-only selector).

Submodule bumps: kicad (index-backed filterFootprints + preview AsyncLoad
fix + modal-pump crash guard), wxwidgets (modal pump logs e.stack).

Doc: pcbjam-private docs/features/libs/0014-eeschema-footprint-selector.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCWVaCRM9T847PdPwYYajX
2026-07-07 21:09:21 +02:00
Viktor Vaczi
34d2602132 fix(demo): POST waitlist to canonical www host to avoid preflight redirect
The demo (demo.pcbjam.com) cross-posts the waitlist to the marketing site's
serverless endpoint. It targeted the apex pcbjam.com, which 308-redirects to
www on Vercel; a CORS preflight can't follow redirects, so the OPTIONS failed
("Redirect is not allowed for a preflight request") and the POST never landed.

Point the waitlist target at the canonical www host. www.pcbjam.com already
returns 204 with access-control-allow-origin: https://demo.pcbjam.com, so the
preflight passes. Landing/version-badge link stays on the apex.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REwWZGkAz1AZUJw8yqxkCN
2026-07-04 11:39:38 +02:00
Istvan Matejcsok
27992f4f18 feat(3d): gl1 shim M4+M6+M7 — GLU quadrics, production link, stub retired; 47/47 parity
- gl1_glu: gluCylinder/gluDisk/gluSphere per the SGI GLU reference
  tessellation (sin/cos phase, strip emission order, pole fans — Apple's
  GLU derives from the same source, so vertices match the goldens);
  emission goes through the public glBegin/glVertex entry points so
  quadrics record into display lists
- glLineWidth>1 one-time warning (browsers clamp to 1; affected coverage
  stays under the parity floor — zero floors.json overrides needed)
- production link site: build-kicad-target.sh now compiles wasm/gl1 from
  sources.txt and applies wrapped_symbols.txt --wrap flags (same manifests
  as the test harness); kicad_editor's RENDER_3D_OPENGL links against the
  real emulation layer instead of no-ops
- wasm/stubs/gl_ffp_stub.c deleted (role fully superseded; history in git)
- docs: 3d-regression README port-status section (floor-blind small
  geometry + line-width caveats), harness comments de-red-stated

Parity: 47/47 under the 0.02 floor, zero overrides. Native golden path
byte-identical to bb0e238 (whose gate ran green) — scenarios/, native/,
baseline/, manifest, floors, spec all untouched by the port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:45:58 +02:00
Istvan Matejcsok
ce1473c9ab test(3d): screenshot-baseline TDD suite for the 3D viewer OpenGL->WebGL port — 47 native goldens + red-state WebGL harness
tests/3d-regression mirrors the gal-regression pattern at renderer scale:
shared C++ scenarios call real KiCad 3D-viewer code (opengl_utils, display
lists + DrawCulled stencil subtraction, MODEL_3D VBOs, private generators via
a rob-template accessor, and full reload()+Redraw() composites over a
synthetic BOARD_ADAPTER). A native macOS harness renders them on real OpenGL
into 47 committed goldens (bit-deterministic, FBO capture); the wasm harness
compiles the same TUs against wasm/stubs/gl_ffp_stub.c no-ops so every
scenario renders blank — the TDD red state (parity meter: 47/47 changed).
Comparisons use the CI pixelmatch engine via the new generic compare-dirs.ts
(floors.json levels; manifest.json cmp-guards registry drift).

Documents an upstream bug: appendPostMachiningGeometry's countersink path
adds middle quads without normals, silently erasing the walls of any
display list it is batched into (3d-post-machining.png keeps the lists
separate to record it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:45:58 +02:00
Viktor Vaczi
db9d6ee04b feat(wasm): occ-split — lazy occ_service worker; kicad_editor drops OCC (−31%)
Move OpenCASCADE out of the merged editor image into occ_service: a separate
emscripten module (-sASYNCIFY=0, MODULARIZE, in-container -Oz finalize, 2N+8
pre-warmed pthread pool) booted lazily in a dedicated Web Worker on the first
STEP export or STEP/IGES model parse. kicad_editor.wasm ~190 MB -> 130 MB;
sessions that never touch OCC never fetch its 57 MB. STEP export works in the
browser for the first time: the unchanged desktop dialog runs EXPORTER_STEP,
whose wasm shadow suspends into globalThis.occService and the export bytes go
straight to a browser download (never entering the editor heap). STEP/IGES 3D
models parse in the worker via the oce shadow (S3D WriteCache/ReadCache wire).

- wasm/occ-service/: service CMake target (hooked from the kicad fork's
  top-level CMakeLists, wasm/editor pattern), embind entry
  (occExport/occLoadModel), wxConfig pre-js.
- wasm/stubs/{exporter_step,oce_plugin}_stub.cpp: EM_ASYNC_JS worker bridges
  (callee-shadowing; no caller #ifdefs).
- web/standalone: provider installed whenever the kicad_editor bundle boots
  (cross-face safe); ONE shared worker-boot source occ-worker.js (vite ?raw;
  the e2e stub reads the same file) — blob worker with locateFile absolutized
  against the glue URL; export download-name guard.
- deps: OCC builds with RapidJSON so its glTF/GLB writer exists — pinned to
  the vcpkg master snapshot 2025-02-26 (24b5e7a8b27f), the same code official
  KiCad consumes via vcpkg.json's opencascade[rapidjson]; rapidjson's latest
  tag (v1.1.0, 2016) is ill-formed under modern clang.
- tests: occ-export dialog e2e (lazy-fetch boundary + STEP download bytes),
  occ-probe incl. a 9-format matrix (step/stpz/brep/xao/ply/stl/glb/u3d/pdf),
  3d-viewer-models hard-asserts the worker parse; occ provider stub installed
  ambiently by the kicad fixtures.

Validated against desktop kicad-cli 10.0.4: geometric exact equality (bbox
delta 0 um, volume delta 0.0000%) for STEP/GLB/STL/BREP/STPZ across three
boards and option sweeps — with desktop OCC 7.9 vs wasm OCC 7.8; PLY/XAO/PDF
structurally equal; U3D same-size (quantizer float LSBs differ). Full kicad
e2e green on Firefox and Chromium; standalone verified end to end (lazy fetch
only on the Export click; export.step 60,628 B ISO-10303-21; loadModel 700 KB
STEP -> 569 KB scenegraph cache).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:39:58 +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
Istvan Matejcsok
c6716e6b60 feat(wasm): unify editor builds — footprint/symbol editors via runtime --frame
The footprint and symbol editors are no longer separate WASM bundles: the frontend loads the parent pcbnew/eeschema bundle and passes --frame=fpedit / --frame=symedit (TOOL_BUNDLE + TOOL_FRAME -> Module.arguments in boot). Drops the two duplicate build+deploy targets and their wrapper scripts + vestigial embind; adds low-level harnesses (footprint_editor.html, symbol_editor.html) and a runtime-frame spec. Bumps the kicad submodule to the runtime --frame launcher.

The frame-runtime spec is listed in PCBNEW_FAMILY_SPECS so CI routes it to the chromium-ci (V8) project — its footprint case boots the pcbnew module, which OOMs SpiderMonkey on x86 CI. Includes the editor-unification dossier (research docs 01-04 + the as-built implementation record 05).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 17:56:02 +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
Istvan Matejcsok
7630c7e521 fix: 🐛 raytrace deadlock - pre-warm 2N+8 pthread Workers 2026-07-02 13:01:02 +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
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
Gergő Törcsvári
6f3f3bcf00
feat(3d): lazy 3D model delivery R2→IDB→WASM
- cdnModelsSource: sparse per-lib stacks over the models CDN layout
  (libs/kicad-models/<tag>/<lib>/manifest + content-addressed blobs)
- models-bridge: board prescan ((model …) scan → prefetch, 4/4 in 74ms
  on the demo board) + the kind=model3d 'ensure' op answering absolute
  MEMFS paths; only board-referenced bodies ever enter IDB
- boot/runner/config/WasmTool wiring (VITE_MODELS_MANIFEST_URL,
  KICAD*_3DMODEL_DIR seeding, prefetch badge)
- StorageUsageCard on HomePage: per-kind cached sizes + delete-3D-cache
- publish-models.ts (brotli content-addressed publish, 606→93MB for the
  pic_programmer lib set) + dev-demo --models-tag/--models-local
- e2e: 3d-viewer-models.spec.ts (bridge normalize/dedup + STEP/WRL
  render); submodule bumps: kicad (static 3D plugins + ensure hook),
  pcbjam-shared (sparse sync layer)

Spec + findings: docs/features/3d-models (private repo).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014AT7gVHRktDYoQ68S4x6A4
2026-07-02 11:35:36 +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
348596c515 perf(build): use all cores in host builds — wx lib, test apps, gal-webgl
env.sh exports a docker-safe JOBS=1 default, and every host build silently
inherited it (the docker kicad path dodges it by passing -j explicitly):

- build-wx-wasm.sh: the intended "${JOBS:-nproc}" fallback sat BELOW the
  env.sh source, so it was dead code — full wx builds ran make -j1 on the
  Mac and on CI. Compute the all-cores default before env.sh instead
  (explicit JOBS/PARALLEL_JOBS still wins); also -j the PCRE pre-build.
- build-wasm-test.sh: same default fix, plus fan the post-link
  hoist+asyncify loop out across JOBS with xargs -P. Per-app wasm-opt
  can't feed many cores (small modules), so serial stays ~4min even with
  BINARYEN_CORES=16; fanning across the 74 independent apps is what
  scales. Safe: apply-asyncify is in-place per wasm, injector tmp is
  per-js, HOIST_WASMOPT resolved once up front.
- build-gal-webgl-test.sh: make had no -j at all.

Measured on a 16-core M4 Max, clean test-app build: 15m36s (-j1) → 2m08s,
peak RAM 10.7 GB summed across all build processes (64 GB machine; CI
runners have 120 GB). Core counts are always derived from nproc at
runtime — nothing hardcoded, CI workers differ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeiSRRScdaox5jBueJNcyG
2026-07-02 10:54:14 +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
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
Gergő Törcsvári
89a433fda9
fix(trace): make ?trace= work by merging Module.ENV in the glue
The emscripten glue's `var ENV = {};` never merged Module.ENV, so boot.ts's
Module.ENV.KICAD_TRACE was a silent no-op — and since environ_get on a pthread
proxies to the main thread, the app/UI thread's getenv read the (empty) main
ENV. Net: KICAD_TRACE never arrived and every KI_TRACE was a no-op.

scripts/common/patch-env-shim.mjs post-processes each generated <app>.js to
merge Module.ENV into ENV (idempotent; runtime no-op when unset), wired into
docker/build.sh's per-app host post-process next to the dyncall-shim inject.
Replaces a fragile manual glue edit re-applied after every build. Verified:
symbol-editor eager load now emits KI_TRACE_SYM_CHOOSER fatLoad timing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:01:10 +02:00
Gergő Törcsvári
88aaba99c9
feat(libs): react load overlay + progress bar; mimalloc mallinfo stub; framed tests
- WasmTool/source.ts: LIB_LOADING_EVENT brackets the fat-load; full-cover
  overlay ('Loading <kind> libraries…') with a per-lib progress bar (done/total
  from listLibs), show-on-first / hide-after-last coalescing.
- mallinfo stub (wasm/shims/mallinfo_stub.c + build-kicad-target.sh): -sMALLOC=
  mimalloc doesn't export glibc mallinfo() that OpenCASCADE OSD_MemInfo (pcbnew
  3D) needs; zeroed no-op unblocks the pcbnew link.
- source/cdn-source tests: updated to the framed Uint8Array 'bodies' contract.
- bump kicad -> e8db3d3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 09:56:32 +02:00
Gergő Törcsvári
4025b05218
feat(libs): parallel s-expr parse + mimalloc + copy-as-is transport — symbol chooser cold 274s→44s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 07:58:30 +02:00
Viktor Vaczi
71807db734 feat(wasm,3d-viewer): run upstream multi-threaded CPU raytracer
Bumps the kicad submodule to the threading revert (the 3D-viewer raytracer's
WASM single-threaded #ifdef fallbacks + the bs_thread_pool inline shim are gone;
those four files are now byte-identical to upstream KiCad).

Links wasm/shims/nanosleep_yield.c into every app (mirrors the gl_ffp_stub
pattern): the now-upstream raw-thread raytracer joins via a main-thread
sleep_for busy-wait, and this shim makes that nanosleep Asyncify-yield to the JS
event loop instead of deadlocking on on-demand pthread-Worker creation. Its
EM_ASYNC_JS yield is already covered by env.__asyncjs__* in asyncify-imports.txt.
This is what lets upstream's multi-threaded code run on WASM under native-EH.

Also documents isolated-worktree + submodule-branch creation in the README.

Result: ~11x faster 3D raytrace (21.3s -> 1.9s, 16 threads), pixel-correct
render, full kicad e2e green (63 passed / 0 failed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:17:56 +02:00
Viktor Vaczi
714b0a816b fix(wasm-eh): build wasm-emscripten-finalize from the Binaryen submodule (unbreak CI)
The native-EH migration pointed apply-finalize.sh at emsdk's bundled
wasm-emscripten-finalize (${EMSDK:-tools/emsdk}/upstream/bin), which exists on a
dev machine (tools/emsdk persists from a prior build) but never on the ephemeral
CI host — emsdk lives only inside the Docker image, and nothing on the host
post-process path provisions it. So the host post-process died at finalize for
every app once the binaryen-SHA cache re-key forced it to actually run. (Not the
missing-ninja theory — ninja was installed; the run never reached the wasm-opt
build, which is downstream of finalize.)

Finish the "Binaryen submodule everywhere" migration: build-wasm-opt.sh now also
builds wasm-emscripten-finalize, and apply-finalize.sh takes it from that build
(next to wasm-opt). The host post-process is now emsdk-free (dyncall=node,
finalize+asyncify=submodule v130) and finalize/wasm-opt share one Binaryen
version (previously finalize was emsdk's v121, wasm-opt the submodule's v130).
get-wasm-opt.sh stays for bench only; dropped it from the wasm cache key and
added build-wasm-opt.sh.

Also surface the from-source Binaryen build as a "Build Binaryen" stage in
build-monitor.sh (it had no marker), emitted at the pipelined pre-warm sites.

Validated: a clean from-scratch `docker/build.sh calculator --build-deps` (deps +
compile + finalize via the submodule binary + asyncify + -O2) succeeded
end-to-end; finalize resolved to build-wasm/tools/binaryen-hoist-build/bin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:55:52 +02:00
Gergő Törcsvári
6414036580
chore: add binaryen submodule to git-workflow
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:06:34 +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