Commit graph pcbjam/scripts
Author SHA1 Message Date
Viktor Vaczi
db819850ee jspi: fix the dead-tools ownership bug, emscripten-6 fallout, and green the full suite on Playwright 1.62
Live-app fix (Place Footprints / routing dead in Chrome): submodule
bumps carry the coroutine ownership fix (kicad 012d95ecb4) and the
handler-exception survival fix (wxwidgets 1b5f0e31f4).

Emscripten-6 fallout:
- occ/ngspice worker wrappers: mainScriptUrlOrBlob was removed
  upstream; pthread children re-run the wrapper blob, so an em-pthread
  realm now importScripts the glue and gets out of the way (before:
  recursive service boots, pool never fills, silent 180s boot hangs —
  every occ spec and ngspice bg_run).
- Makefile.wasm: -sASYNCIFY frankenlinks on the no-wx coroutine repro
  targets ported to -sJSPI (the JSPI-only libcontext crashed at first
  yield under them); mainloop/gl repro pages drive their tick through a
  promising export (emscripten_set_main_loop callbacks cannot suspend);
  retired inject-dyncall-shims lines removed (targets were unbuildable
  since Phase 8); $stringToNewUTF8 force-included (the EM_ASM value
  bridge aborted the runtime on the first decoded exception).
- fiber-park levers: neither embind shape can drive suspending levers
  (plain throws on strict-JSPI Firefox; emscripten::async() re-executes
  its invoker on settle) — kept sync for manual Chromium probing, spec
  coverage moved to the jspi-coroutine harness (18 cases).

Suite work:
- Playwright 1.61.1 -> 1.62.1 (Firefox 153: JSPI on by default).
- fiber-resume-park.spec retired -> coroutine-lifecycle.spec: census
  gate over boot / board load / chooser open / cancel (deterministically
  red on the pre-fix build).
- Blind asyncify-era pins re-keyed: quasimodal-strand + wait-beacons
  beacon regexes, footprint-chooser-close liveness -> wx parking-timer
  heartbeat (scheduler counters idle flat on Firefox).
- occ/ngspice test providers: 60s boot timeout + worker error
  surfacing (a worker death used to be a silent 180s timeout).
- Harness pages: stale 9.99 config dir -> 10.0 (library_manager wxCHECK
  noise, chooser had no libraries).
- gal-webgl harness: missing artifacts rebuilt (boost/glm extracted to
  the host sysroot), PgmOrNull stub added for the rebased GAL.
- jspi-scheduler: clean-shutdown console line restored (app-quit
  contract), quarantine never yanks SP from a live window.

Gates: test:e2e 699 passed / 0 failed (wx-chromium, kicad-firefox,
kicad-chromium, jspi-firefox, coroutine-firefox); web ff/cr/mobile 71
passed; lint:ci-coverage 166, lint:determinism 163, screenshots
manifest 492 current, corpus 7/7, tools:contract green. Offline
screenshot baselines show expected mass drift from the engine bump —
re-baseline (screenshots:noise -> promote) is a follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-13 17:41:28 +02:00
Viktor Vaczi
e14faeca8b jspi: retire the asyncify pipeline — knob, post-link tail, binaryen hooks
Phase 8 in the parent repo. Deleted: asyncify-scheduler.js, apply-asyncify.sh,
apply-finalize.sh, inject-dyncall-shims.sh, asyncify-imports/removelist.txt,
the wasm-opt/finalize stub pair, scripts/binaryen-hoist-pass/ (the fork stays
a dormant submodule; removal is a follow-up), bench/wasm-opt-bench.sh (README
marked historical), wasm/shims/context_sleep.cpp, and the sched-context
harness app + Makefile targets.

PCBJAM_ASYNC_BACKEND is gone: build-wx-wasm.sh hardcodes the jspi stamp
(still force-cleans pre-migration trees), build-kicad-target.sh gives editors
the JSPI link surface and the CLIs nothing (they pin ASYNCIFY=0), the stub
dance is replaced by an unconditional .real-restore, build-wasm-test.sh lost
its whole post-link loop, docker/build.sh's postprocess is the ENV shim only,
and Makefile.wasm links every app JSPI with the scheduler shim as a tracked
prerequisite. pcbjam_async_policy.h keys on __EMSCRIPTEN__.

jspi-scheduler.js: wxWasmMainLoopPump dropped from the wrap census (the
export died with the D5 detach); inert [TRACE] instrumentation removed.

CI: wasm-build.yml rewritten for the single-cache pipeline (one output cache
keyed on compile inputs; post-processed bytes cached after the shim);
opt_level input removed from both callers. wasm-cache-hash.mjs inputs now
cover patch-env-shim.mjs + jspi-scheduler.js + jspi-exports.txt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDeBaKKhQztd8KiVtHuyXr
2026-08-13 08:39:12 +02:00
Viktor Vaczi
3f09a46ff5 jspi: migration phases 0-7 — build knob, scheduler shim, test successor suite
Toolchain: emsdk 6.0.6 (versions.sh; cache-hash keys on it). Build knob
PCBJAM_ASYNC_BACKEND=jspi|asyncify: build-kicad-target.sh links editors with
-sJSPI + -sJSPI_EXPORTS=@scripts/common/jspi-exports.txt + --pre-js
jspi-scheduler.js (no DYNCALLS, no post-link asyncify pipeline); wx build
stamps the backend and forces clean on flip or unknown provenance;
docker/build.sh passes the knob, seeds the emscripten ports cache from the
volume every launch, jspi postprocess = patch-env-shim only.

scripts/common/shims/jspi-scheduler.js: the JSPI successor scheduler —
token-wait registry, resume turnstile (one armed resume between engine
re-entries, SP swaps only at microtask boundaries), green-region spill
stacks (16-aligned tops), S1 embind mutator FIFO lane + parker wraps, S6
shutdown, libctx integration hooks (suspend/end/quarantine + g_current
arm/clear), SuspendError attributor, lost-wake + stuck-window watchdogs,
__wxWaitDump observability.

Embind: PARKER registrations get emscripten::async() under PCBJAM_JSPI
(wasm/bindings/pcbjam_async_policy.h). nanosleep yields route via the shim.

Tests: tests/asyncify -> tests/jspi successor suite (jspi-stack red/green
shadow-stack battery, jspi-coroutine MiniCoro harness, suspend-races
semantic scenarios + __wxWaitDump books coherence); projects jspi-firefox/
jspi-chrome (asyncify-webkit retired — no JSPI in WebKit); unconditional
Firefox JSPI pref; guard-beacons -> wait-beacons (+wxScheduler/libctxJspi
families); Makefile.wasm links test apps against JSPI with the shim as a
tracked link prerequisite.

Web: WasmTool setRo await + __wxWaitDump forensics, open-flow contained
promise, scheduler-shim.test.ts retargeted (8 green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDeBaKKhQztd8KiVtHuyXr
2026-08-13 07:06:24 +02:00
Gergő Törcsvári
1d8237bc08
Phase F fix: open-lane token was lost across the dispatch-context swap — mint it in JS; repro spec added
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Td4ujboGuAw26jvbDQzehj
2026-08-10 10:14:21 +02:00
Gergő Törcsvári
747bf5ecf4
Phase F: kicadOpenFile runs on a dispatch context — the awaited-ccall class retired for the open (fcsTotal 1800→100/load)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Td4ujboGuAw26jvbDQzehj
2026-08-10 10:14:21 +02:00
Gergő Törcsvári
7122cdc461
Phase F F2/F3: quarantine NOT deletable — the lever proved the drop is load-bearing; registry keeps the fact
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Td4ujboGuAw26jvbDQzehj
2026-08-10 10:14:20 +02:00
Gergő Törcsvári
b9986d7909
doc 22: D-on probe clean after the bridge set; Phase E telemetry + prod-provider smoke recorded
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2SU74acXyviwSxBhMunFe
2026-08-10 10:14:19 +02:00
Gergő Törcsvári
75209dc07e
Phase E retry: K1 lands — early-resolve retention, not the lossy guard; pthread-ondemand link fixed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2SU74acXyviwSxBhMunFe
2026-08-10 10:14:19 +02:00
Gergő Törcsvári
80468e7c5b
Phase B: main-thread sleep parks its context; the real D blocker is DOM entries
wasm/shims/context_sleep.cpp: a main-thread nanosleep whose frame stands on a
scheduler context that OWNS the stack arms a mailbox wake and yield_parks that
context instead of suspending the stack in place. It lives in the sleep
primitive rather than in tool_manager.cpp on purpose - KiCad and the wx core
stay untouched (CLAUDE.md's fork rule) and the whole K7 class moves at once,
not just TOOL_MANAGER::RunSynchronousAction's spin loop.

MEASURED AT D-ON, and it is NOT what unblocks Phase D. The four canvas-tool
specs still fail, but the trace now names a different cause: the fatal swap is
old=<libcontext ROOT> new=<tool coroutine> with mouseEventHandlerFunc above it
- a DOM mouse handler entering wasm DIRECTLY on the main stack, bypassing the
tick. So one coroutine is entered two ways: by the tick through the dispatch
context as a STAR TRANSFER, and by DOM handlers as a DIRECT SYMMETRIC SWAP. A
capture written by one path cannot be rewound by the other -> index out of
bounds in doRewind. That is section 7 rule 5 (partial migration is worse than
none) in its purest measured form, and it is why the harness stays green: its
coroutines are only ever entered from one place.

So the next increment is the DOM event entries (mouse/key/wheel/resize must
hand their events to the dispatch context as the tick does), not another park
site. It subsumes the one-root work too: with no dispatch on the main stack,
resolve_root_identity() always answers "the running context".

Landing state: STAR_DISPATCH=0, kicad 139 passed / 1 (pre-existing occ-probe)
= baseline, with the sleep shim in and inert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBjomQfKyRa3jBdeAKpmTw
2026-08-10 10:14:19 +02:00
Gergő Törcsvári
78ec52ea14
docs 22 Phase B: gaps 1+2 closed, and the D-on boundary measured on KiCad
Phase B increment recorded in §10. The wx battery is GREEN at D-on (395/1,
the 1 pre-existing) with dispatch contexts, context waits and star transfers
all live - the first clean battery of the migration. Gaps 1 and 2 from the
D-on probe are closed (terminal coroutine finish; wake/refusal semantics),
and a third containment was found and added: an exception escaping a handler
propagates out through drain()'s fiber swap and would otherwise leave the
registry mid-transition, dead-pumping every later wait. Shim carries the new
abandon call; .ci-cache-epoch -> 12.

THE BOUNDARY: on the full KiCad suite D-on loses four canvas-tool specs
(draw-wires, draw-lines, move-with-m, presence-locks move) to `index out of
bounds` in doRewind - the blue screen itself. Real tool coroutines park IN
PLACE inside their bodies, and a star transfer over an already-parked stack
rewinds state the fiber layer cannot see; the harness's coroutines yield
cleanly, so it goes green while KiCad does not (the doc-19 lesson again:
the harness models the shape, not the parks). So D cannot carry KiCad until
the tool-body park sites are contexts too - C+E completion, which §5 already
ordered before the flip. This measurement makes that ordering non-negotiable.

Landing state verified: STAR_DISPATCH=0, kicad 139 passed / 1 (pre-existing
occ-probe glb) = the Phase A baseline exactly, wx battery 395/1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LBjomQfKyRa3jBdeAKpmTw
2026-08-10 10:14:18 +02:00
Gergő Törcsvári
41d49bdacc
WIP star-flip: shim routes context waits; docs 22 records that D forces D5
NOT GREEN. resolveWait marks a context-parked waiter ready and arms a pump
instead of resolving a promise nobody awaits.

The doc records the measured correction: DoRun parks the MAIN stack every
frame in wxWasmYieldToBrowser, which doc 21 called safe-by-construction only
because dispatch also ran there. With the scheduler swapping contexts from the
tick, those interleave over one currData - overlapped-wake, the exact class
this work exists to remove. So the main loop must become a context (D5) and
the bridges (E) join the same flip: D5+D+C+B+E land together.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EarUW9DS1c1sSW4ZNrkGQS
2026-08-10 10:14:18 +02:00
Gergő Törcsvári
d519ac89c0
design-b D-1 gate: battery green scheduler-only; startModal import scrub + work log
Gate results (single battery, scheduler-only): wx app battery +
asyncify + coroutine 363 passed / 3 skipped / 0 failed; full kicad
suite 138 passed / 30 skipped / 1 failed — the one failure is the
pre-existing local occ-probe glb case (predates D-1, unrelated).

Post-gate scrubs: vestigial 'startModal' removed from ASYNCIFY_IMPORTS
(tests/apps/Makefile.wasm) and env.startModal from
scripts/common/asyncify-imports.txt (the import no longer exists in any
wasm; both lists are boundary supersets so behavior is identical).
Stale comment pointers to the deleted legacy modal machinery updated.
Doc 20: D-1 work log added, status flipped to IN PROGRESS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEHGiiXMShNXbBr7gSJ7iz
2026-08-10 10:14:15 +02:00
Gergő Törcsvári
de27ecf327
design-b D-1: bump wxwidgets (gating + legacy twins deleted) + shim cleanup
wxwidgets c44c684f7d (D-1c: scheduler lanes unconditional,
wxWasmMailboxEnabled gone, fail-fast shim assert) + 24843897e8 (D-1d:
startModal / wxWasmRunNestedLoop / popup pump / resolver stacks / bare
emscripten_async_call timer entries deleted).

pcbjam side of D-1d: the scheduler shim's delivery-tick error path uses
wait-registry containment (resolveTopWait nested+modal) instead of the
deleted _wxNestedLoopExit stack, and diagnostics.js tracks modal
lifecycle via pendingWaits('modal') instead of the deleted
Module._endModal hook.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEHGiiXMShNXbBr7gSJ7iz
2026-08-10 10:14:15 +02:00
Gergő Törcsvári
4906b9fb56
design-b D-1b: delete the legacy handlesleep runtime from the injector
Doc 20 D-1, second slice: the injector now injects asyncify-scheduler.js
unconditionally — the WX_SCHEDULER=0 legacy opt-out, the
SHIM_DISABLE_HANDLESLEEP ablation skip, and the
SHIM_DISABLE_TRAMPOLINE_HEAL ablation skip are gone, and
scripts/common/shims/handlesleep.js is deleted (the scheduler subsumed
its capture/restore, fiber guard, and trampoline-heal duties in S2).
Comment-only scrubs point the remaining references at the scheduler
shim. .ci-cache-epoch bumped (shim/injector behavior changed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEHGiiXMShNXbBr7gSJ7iz
2026-08-10 10:14:14 +02:00
Gergő Törcsvári
9bc4da89ff
mailbox S6: shutdown() in the shim + unit gates; bump wxwidgets
Shim shutdown: dead latch, queue rejection/drop with beacons, pump
stops, idempotent. Gates: shim units 11/11, asyncify 9/9, coroutine
39/39, wx modal-heavy 45/45, kicad 6/6 — all on DEFAULT-injected glue
(docker postprocess -> setup:kicad now yields scheduler builds
without manual conversion).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfxKn5utcntBSnxz4ZnYKs
2026-08-10 10:14:14 +02:00
Gergő Törcsvári
a17d87a4bd
mailbox S5: scheduler is the default build + demolition ledger
Injector defaults to asyncify-scheduler.js (WX_SCHEDULER=0 = explicit
legacy opt-out); .ci-cache-epoch 9->10. Doc 17 S5 corrected: the
interlock/busy-gate deletions assumed handler-fibers that S1-S4 never
built — they stay as load-bearing second lines; each real deletion is
ledgered with its unlock condition. Flip gate: full kicad suite 136
passed on BOTH variants (occ-probe glb fails identically on both =
pre-existing; ngspice bg_run = rerun-passes flake).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfxKn5utcntBSnxz4ZnYKs
2026-08-10 10:14:13 +02:00
Gergő Törcsvári
5a8b0a3279
mailbox S4: wait registry in the shim + work log; bump wxwidgets
Per-kind LIFO wait stacks (beginWait/waitPromise/resolveWait/
resolveTopWait), resolve-before-yield safe, S2 deferred-wake compliant.
Gates: asyncify 9/9, coroutine 39/39, wx modal-heavy 45/45, kicad 6/6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfxKn5utcntBSnxz4ZnYKs
2026-08-10 10:14:13 +02:00
Gergő Törcsvári
603e260885
mailbox S2: scheduler core — deferred wakes + N1 single-writer tripwire
asyncify-scheduler.js REPLACES handlesleep.js on WX_SCHEDULER=1 builds
(injector either-or): ports capture/restore, fiber consume-once/
quarantine guard, wake-window flags, recorder, trampoline heal — and
adds deferred wakes (a wake mid-transition queues and drains from a
clean macrotask) plus the N1 currData accessor (pure-JS writes need
scheduler authorization; strict mode throws; meta-tested). Gates:
races 9/9 with NO legacy shim (subsumption), coroutine 39/39,
wx-chromium 30/30, kicad trio 3/3 on the C-lane build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfxKn5utcntBSnxz4ZnYKs
2026-08-10 10:14:13 +02:00
Gergő Törcsvári
af1eb5fc58
mailbox S1 complete: embind lane, wheel lane, dual-contract specs
Shim embind lane wraps the doc-18 production mutators at the Module
boundary (busy-window calls queue + deliver post-settle; time-boxed
unkillable pump). N2 un-fixme'd and green; collab-load-fuzz carries the
variant contract (drop on legacy, deliver-in-order on scheduler, capped
hammer on the scheduler lane); timer-park's timerRetry silence tripwire
arms on shim+export and is green on the C-lane kicad build. Bump
wxwidgets for the wheel lane. CI both-EH matrix deliberately deferred.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfxKn5utcntBSnxz4ZnYKs
2026-08-10 10:14:13 +02:00
Gergő Törcsvári
61b5f266fb
mailbox S1: shim delivery tick, embind audit, app-side WasmMailbox
Shim: mailbox FIFO + self-armed delivery tick calling wxWasmMailboxTick
(plain export — never inside a pump's awaited ccall); injector sentinel
fixed (the old marker also matched evtloop's EM_JS probe text). Doc 18:
79-export embind audit (14+3 production mutators to wrap, 20 pure-read
allowlist, asymmetries). web/standalone WasmMailbox: FIFO defer-until-
settled keyed on the proxy-safe kicadOpenFileBusy probe, 7 vitest green.
Dual-variant wx battery green (28+39+7 both variants). Bump wxwidgets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfxKn5utcntBSnxz4ZnYKs
2026-08-10 10:14:12 +02:00
Gergő Törcsvári
94ae4a8a41
mailbox S0: dual-glue flag, beacon counters, N2 red spec
Doc 17 step S0 scaffolding: WX_SCHEDULER=1 injector path with an
observation-only asyncify-scheduler.js skeleton (legacy shim stays
authoritative until S2), guard-beacon extraction with occurrence
recovery for rate-limited beacons, and the fixme'd N2 ordering spec
(add-then-move probe; un-fixme at S1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfxKn5utcntBSnxz4ZnYKs
2026-08-10 10:14:12 +02:00
Istvan Matejcsok
f90cf7d2da ci: deploy isolated GPL staging stack 2026-08-07 11:52:25 +02:00
Istvan Matejcsok
1b08a5eb06 feat(editor): report uncaught errors to Better Stack
The editor reported nothing when a session died. Evidence lived only in-tab —
an 800-line React array behind a "Show console" button — so diagnosis meant
asking a user to paste a screenshot.

Better Stack's Error Tracking ingests the Sentry wire protocol, so this runs
the stock @sentry/browser against a Better Stack DSN. Sentry.init installs its
own window error/unhandledrejection handlers, so uncaught main-thread errors
and the wasm traps that escape emscripten's DOM event handlers are captured
with no instrumentation at the throw sites. Not their JS tag: it has no
beforeSend or fingerprint hooks, its runtime spawns workers from cross-origin
CDN hosts (this page is COEP: require-corp), and it ships session replay on by
default — which on a CAD canvas records customers' board geometry.

@sentry/browser is imported in exactly one file so the vendor stays swappable,
mirroring how lib/analytics.ts isolates Plausible.

Also replaces the terminal-signature regex with a shared, unit-tested predicate
(wasm/terminal-error.ts) used by BOTH the fatal overlay and the reporter, so
they cannot disagree. The regex was a type check written as a string match and
had three live holes: `RuntimeError` was listed but never appears IN
`.message`; Chrome's bare "unreachable" and "null function" matched nothing
(the v0.1.20 prod log is exactly those); and narrowing "table index is out of
bounds" to `\bindex out of bounds` for Firefox in 197f317 silently stopped
matching Chrome's spelling. Checking the TYPE — every trap in this family is a
WebAssembly.RuntimeError — covers all engines and ends the spelling chase; the
message patterns remain as a fallback for paths that lose the Error object,
such as a worker ErrorEvent crossing the realm boundary with error: null.
197f317's pthread-worker tap, promote() and Firefox findings are kept as-is.

Notes:
- Off unless VITE_ERRORS_DSN is set AND VITE_ALLOW_USER_OVERRIDE !== "1" (dev
  servers and every Playwright harness set the latter, and production builds
  never do), so a production DSN in a local .env still cannot report. With no
  DSN the whole SDK is const-folded out: 1,193,080 vs 1,282,463 bytes of JS.
- browserApiErrors integration removed. It wraps setTimeout/rAF/addEventListener
  in try/catch, which is exactly how KiCad-on-Emscripten drives its main loop.
- Console breadcrumbs off (collab/debug.ts's clog fires per Yjs update and would
  evict the ring before any crash); dom/fetch/navigation breadcrumbs kept.
- beforeSend redacts token/apiKey/Bearer — collab/provider.ts puts the collab
  token in the y-partyserver URL, so a connection-failure string carries a live
  credential — and guards the cascade: one wedge produced 8 errors in prod, and
  after the first terminal event the rest are dropped into cascade_count.

Verified end to end against the real EU host from a cross-origin-isolated page:
POST /api/<id>/envelope/ -> 200, and 4 terminal throws produce 1 event
(control: 1 throw, same count).

Privacy policy 9, cookie policy 6 and the licenses page are updated: Better
Stack is disclosed as an EU processor, and the licenses page now describes the
browser app's own JS dependencies, which it never did.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 12:24:30 +02:00
Istvan Matejcsok
20e5eb941c fix: point GPL corresponding-source URLs at the PCBJam org
Every repo moved from emergence-engineering/ to PCBJam/, but the published
source pointers still named the old org. GitHub's transfer redirect resolves
them (all six checked, 301 -> 200), so nothing is broken today — but it stops
working the moment anyone creates a repo at an old path, and a GPLv3
corresponding-source pointer is a poor thing to leave depending on a redirect.

CI never passes --repo, so the hardcoded default is what actually ships: the
editor's version badge on editor.pcbjam.com has been linking users to the old
org for their source.

Covers the source pointers (licenses.md, terms.md 12.4, REPO_URL and its doc
comment, the three build-script --repo defaults, the site footer's build-commit
link) and the two "our GitHub" org links. Bumps the pcbjam-shared pointer for
the same fix there.

Deliberately untouched: emergence-engineering.com, the company domain behind
contact@ and the EE credit block — the trailing slash in the substitution keeps
it out. And docs/security-audit-glm/, which describes a finding rather than
linking anywhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 12:19:54 +02:00
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