Symbolized (HOIST_KEEP_NAMES=1): the trap is on the asyncify REWIND re-entering
the fiber — stack-local COROUTINE+body were destroyed when Call() returned
early on a park, so the rewind called through freed objects (latent UB in the
ORIGINAL fire-and-forget runOnFiber too). Heap-pinned FiberSlot + explicit
done flag + fiber-tail re-drain. Layer 2 (rewind interplay) still open —
fuzz stays fixme'd; pageerror stacks now captured in fuzz artifacts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5cAM9M6q34n5X4dbrfVvi
runOnFiber now runs bodies strictly one-at-a-time through a park-safe FIFO
(collab_common.h): the per-body fire-and-forget coroutine interleaved under
load — an asyncify park inside commit.Push let the event loop start the next
body, so a local commit and a remote apply ran interleaved on shared state
(s_applyingRemote is one global), silently losing applies on the actively-
editing receiver (fuzz finding #10a; B now fuzzes clean; 39-test suite green).
kicadCollabFiberBusy embind probe (merged + standalone registrations): a
bare-embind-stack scratch save during a parked fiber mis-dispatches (table
index OOB) — trio.ts modelText/drift and production drift-detect now defer
while fiber work is in flight (#10b hardening; the trap's root cause is still
open and needs a symbolized stack — fuzz stays fixme'd).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5cAM9M6q34n5X4dbrfVvi
drift-trio-scenarios.spec.ts: disjoint ping-pong, same-item interleave (no
settle between bursts), conflict pairs (move-vs-delete / value-vs-value /
move-vs-move — winner is CRDT policy, asserted only as convergence + drift
silence), undo storm, 12-edit burst churn, late-joiner adopt, and Ctrl+S mid
peer burst; per-tool adapters, marker-waits before every sweep (finding #7).
S4 exposed finding #9: mutation hooks resolved item pointers at call time and
committed later on the fiber — a remote remove in between frees the pointer
(doApplyItems) and the commit resurrects the deleted item. Phase-B mutation
hooks now re-resolve by uuid ON the fiber; a vanished item makes the mutation
lose silently. applyDeltaToY's concurrent update/delete verified coherent
(full resurrect or full remove) — no shared change needed. trio.ts: TabSet
oracles + exported startV2 for duo/late-join composition.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5cAM9M6q34n5X4dbrfVvi
17 kicadCollabTest* action primitives (eeschema: wire/junction/no-connect/
label/symbol/move/mirror/duplicate; pcbnew: track/via/text/zone/flip/
footprint-field/lock/move/duplicate), each a real SCH_/BOARD_COMMIT on the
fiber so the listener → flushDiff emit runs as for UI edits; tool-unique
names, merged-image safe. Fixes surfaced by the catalogs (0008 §10 #4–#8):
eeschema adds SetParent before staging (Push silently skips listener
notifications for unparented items), and pcbnew blobForItem now Formats
non-footprints with the FILE writer + wrapInBoardEnvelope — SaveSelection's
transfer copy cleared the locked flag, so (locked yes) never reached the doc.
drift-trio.spec.ts gains full A/B-alternating catalogs with per-step landed
gate + oracle sweep. Bumps kicad for the Duplicate child-uuid re-roll.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5cAM9M6q34n5X4dbrfVvi
The Y.Doc is the source of truth for the FILE, but both live wires wrote
KiCad's CLIPBOARD dialect — a lossy, paste-oriented format. Every
difference was permanent, unfixable drift.
- pcbnew: serialize footprint blobs with CTL_FOR_BOARD, not CLIPBOARD_IO's
CTL_FOR_CLIPBOARD, which emitted (version)(generator)(generator_version)
inside every (footprint …). Keep (locked yes).
- eeschema: aForClipboard=false — clipboard mode collapsed every symbol's
(instances … (path "/sheet")) to (path "").
- Re-supply (version) at PARSE time only (withFootprintVersion): the token
is invalid file content but load-bearing on decode — without it the
parser starts at m_requiredVersion=0 and stamps (hide yes) on every
mandatory field.
- FOOTPRINT copy ctor: restore mandatory-field uuids (EDA_ITEM::operator=
keeps the target's const m_Uuid, so Clone() rerolled all four).
- drift: classify order-only diffs as `reordered` — y-sexpr v2 reorders
legitimately; excluded from counts, report-worthiness and dedupe hashes.
Migration 0017.
- fpedit from eeschema: AsyncLoad()+BlockUntilLoaded() in initLibraryTree —
FACE_PCB starts lazily there and never preloaded its libraries.
Guards: wire-vs-file round-trip tests (pcbnew + eeschema),
fpedit-from-eeschema (verified red without the fix), symedit-from-eeschema.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016p9kjdGBdcpwUSjJ3q5xg2
Squash of experiment/ff-big-modules vs main.
Big-module routing removed: native-EH shrank kicad_editor below
SpiderMonkey's x86-64 code budget (runs 29355049705/29356152413 green on
stock Firefox), so BIG_MODULE_SPECS routing and the baseline-only-JIT
crutch are gone — kicad-firefox and kicad-chromium both run the full
suite, with the module compiled the way real users' browsers compile it.
Per-engine screenshots end to end: stableShot/shotPath write
test-results/<engine>/<name>.png; baselines move to
baseline-screenshots/{chromium,firefox}/ and the whole tools/screenshots
pipeline (compare/promote/manifest/spec-map/changelog/Discord) keys on
<engine>/<name>. Previously Firefox and Chromium renders of one spec
overwrote each other and Firefox renders were never actually gated.
Seeded from CI run 29421380806 (92 new firefox baselines, +24 chromium
web-suite shots); manifest generated from the baseline tree.
One merged playwright.config.ts (kicad/asyncify/coroutine/perf as
projects); ~25 dead npm scripts dropped. The web suite is gated in CI for
the first time ever (4 rotted specs fixed, 5 broken lib-bridge specs
triaged as fixme in docs/features/web-e2e-rot/); cheap lint step after
npm ci; last 26 blind-sleep violations fixed.
SwiftShader retired: CI Chromium renders WebGL on ANGLE → Mesa llvmpipe
(--use-gl=angle --use-angle=gl --ignore-gpu-blocklist; the blocklist flag
is mandatory — llvmpipe is blocklisted and WebGL is silently unavailable
without it) in BOTH configs. Under WORKERS=4 congestion SwiftShader
transiently failed the first post-board-load draw and the recovery
cascade ended in a silent permanent Cairo fallback — that engine flip was
the "~1.2% changedRatio both directions" occ-export baseline flake.
Validated 160/160 across two 80-repeat rigs; full analysis in
docs/features/wx-parity-bugs/occ-export-context-eviction.md. Chromium
baselines shift slightly on llvmpipe — promote once from the first green
run. Deflakes the new coverage exposed: presence baselines settle before
capture; presence fixtures declare current file formats; perf gets its
own outputDir so CI evidence survives; occ-export settles the board paint
before the export dialog; menu-item waits (waitForRenderedByLabel before
clickMenuItem) in 4 specs + the TESTING.md rule.
Web suite runs the PROD build, in parallel: webServer becomes backend
`start` + the standalone's e2e:preview (build-preview.mjs: link-wasm →
stash the public/wasm symlink aside during vite build, build-demo.mjs's
move — then vite preview as the persistent server). The wasm middleware
serves /wasm/* in preview and emits COOP/COEP/CORP itself (a pthread
worker script's own response must carry COEP or Chrome kills it with
ERR_BLOCKED_BY_RESPONSE). VITE_* flags bake at build time;
VITE_ALLOW_USER_OVERRIDE joins turbo globalEnv. fullyParallel + default
workers: 5.2m → 1.4m. Determinism fixes the parallel run exposed:
shared-page specs become serial groups; locks.spec grabs alice's exact
item via the new kicadCollabTestSelectByUuid hook (cross-tab "first
footprint" order is not a ysync invariant); quit specs poll page.url()
(quit supersedes its own navigation — NS_BINDING_ABORTED on Firefox).
Suite: 51 passed / 12 skipped / 0 failed in 1.6m.
CI-coverage gate (lint:ci-coverage): every tests/**/*.spec.ts must be
reachable from the npm scripts the workflows invoke — scraped from
.github/workflows/, resolved through package.json, coverage asked from
playwright --list itself. Rules: uncovered-spec + orphan-project (with a
documented LOCAL_ONLY_PROJECTS allowlist). Gating next to
lint:determinism; 138 spec files / 13 projects accounted for.
Product fixes kept from the investigations (reachable on real GPUs too):
wx 7799fd1be5 — paint flags clear before dispatch + Invalidate always
propagates; kicad 3dcfea5e45 — SwiftShader pass-boundary flush +
per-instance font texture + first-frame GL-error drain (GAL recovery
recovers instead of falling back to Cairo) + the user-facing eeschema
switch navigates again under __EMSCRIPTEN__ (project-sync's
FaceRegistered gate had rerouted it into the hidden sync player; caught
by the newly-gated web suite).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018eUxiPApHgGiu9NFyQfhAq
New --resave <file> <outdir> subcommand (root docs/features/kicad-validity/
0001): .kicad_sch one file per sheet mirroring the hierarchy layout,
.kicad_pcb via pcbToolsResaveBoard callback (mirror of the lint callback),
.kicad_sym/.lib via ConvertLibrary. Exit 0 ok / 2 usage / 4 input invalid /
5 write failed — only 4 marks the input invalid for the upload gate.
The s-expr schematic save stub is now #ifndef KICAD_TOOLS_COMBINED: the
merged image links the real writer (+65 KB), standalone sym_convert keeps
its diet. Board save needed no un-stubbing (pcb_io/ survives the CMake
prune).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HLua64PCVwkQ1hpWdaf1Gm
--gerbers <pcb> [outdir]: one .gbr per enabled layer (stackup plot order,
kicad-cli defaults via default JOB_EXPORT_PCB_GERBERS + PlotJobToPlotOpts)
plus the .gbrjob file — mirrors JobExportGerbers minus the zone re-check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADzCSeN3Q9DJL3YW2FZTXB
The occ_service export worker has its own MEMFS — the editor's lazily-fetched
lib models were invisible there, so every export was a bare board (54
"Could not add 3D model" warnings on pic_programmer, 2 STEP products).
- models-bridge: collectBoardModelFiles(boardText) — scan refs, ensure via
the 0004 sparse source (IDB/R2, wrl->step fallback), read staged bytes
back, dedupe by real staged path.
- occ-service.ts: attach the collected models to every export request
(best-effort — prefetch failure still exports, misses reported by the
exporter); transfer the body buffers.
- occ-worker.js (shared app/harness): pass req.models through to occExport.
- occ_service_main.cpp: occExport(board, params, models) stages each entry
under PCBJAM_3D::MODELS_MEMFS_ROOT (path-sanitized) for the exporter's
staged-model probe (kicad 83645275ac), removed again after the export.
- tests: harness occ stub mirrors the prefetch against the page kicadLibs
provider + captures report/productCount; new occ-export-models.spec.ts
guards the delivery (green companion pins preconditions; guard asserts 0
missing lib models + component PRODUCTs). pic_programmer: 17/17 staged,
87 products @ 13.3 MB (was 2 @ 402 KB). models-bridge unit tests 13/13.
Known remainder (0007 step 4): project-local ${KIPRJMOD} refs still drop.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017UjpnviP3ZDxTM1Ap63Sqv
synced-source subscribes to its SyncStack: remote changes (self-save echoes
consumed via a selfPushed flag) debounce per kind into kicadLibsReload — a
new embind export (pcbjam_libs_reload.h, all three TUs) that drops the lib's
plugin cache (LIBRARY_MANAGER::ReloadLibraryEntry), reloads it, and mails
MAIL_RELOAD_LIB with the nickname so the symbol tree force-refreshes (the
plugin's modify hash is a pinned constant, so a plain sync would skip it).
After the reload, kicadLibsSymbolUsage (new eeschema embind: placed
SCH_SYMBOL count across unique screens) gates LIB_ITEM_UPDATED_EVENT, and
WasmTool shows an amber toast when a PLACED symbol changed — placed copies
keep the previous version until updated from the library.
syncedScopeLibsSource gives PROJECT sessions the synced source under
VITE_LIBS_SOURCE=synced (remote contract for lib listing/createLib, lazy
per-lib SyncStacks for item ops/presync) so realtime reaches open
schematics; previously project sessions silently fell back to the per-item
remote source. Unit tests cover reload debounce, self-echo skip, per-kind
routing, usage-gated event, and the no-Module no-op.
Bumps kicad (MAIL_RELOAD_LIB force-refresh payload).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QRWoXiM9uuo1enXGhAYku
emitViewportIfChanged deduped on scale+center only, so a canvas SIZE change
(boot layout settling after the bind-time seed, window resize) never re-pushed
{w,h} to JS. CommentLayer's worldToScreen maps through h/2, so every DOM pin
hit target (and its hover ring) sat vertically offset from its GAL dot by
exactly delta-h/2 css-px until the next pan/zoom finally passed the dedupe.
Size now participates in the dedupe and wxEVT_SIZE re-pushes post-layout
(CallAfter, after the GAL's own onSize). New regression e2e
comments-viewport-resize.spec.ts asserts the DOM pin re-aligns with GAL truth
(fresh kicadCollabGetViewport) across a window resize with no pan/zoom.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011x3h5AzkWDbHmCkeVYAuzU
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
Follow-user: click a peer's roster avatar to mirror their viewport until
local input breaks it.
- collab_presence_core.h: CORE::fitViewport(cx, cy, halfW, halfH) — fit the
leader's world rect with CONTAIN semantics (zoom derived from the
follower's own canvas via the ToScreen ratio; GetScale is the zoom, not
px/IU). Exported as kicadCollabFitViewport from both editor TUs + the
merged dispatcher.
- presence-kicad.ts: publish the visible world rect (viewportRect) into
awareness, 100 ms trailing throttle; guarded for pre-0008 handles.
- follow-user.ts: createFollow — follows an awareness CLIENT (a tab, not a
user); applies leader rect changes via FitViewport, dedupes unchanged
republishes; break-on-interact compares local onViewport echoes against
the last applied rect (2% rel tolerance, echo-grace before the first fit
lands); unfollows on leader-left; pauses on eeschema sheet mismatch.
- PresenceRoster: avatars are follow toggles (ring on the followed peer);
WasmTool renders the "Following <name> — move to stop" banner.
- tests: 7 controller units (85/85 collab), fitViewport round-trip e2e in
both kicad presence specs (20/20), two-tab tests/web/follow.spec.ts
(converge → track → wheel-zoom breaks → subsequent moves ignored).
Chip depth-layer fix (user-reported): name chips washed out inside
low-alpha selection fills — chip rects shared the shapes overlay's single
depth, and same-depth fragments drawn LATER lose the depth test, so an
earlier-painted fill rejected the chip's pixels. Now three layers via the
fork's VIEW_OVERLAY::SetDepthOffset: text (0) < chips + pin dots (1) <
selection shapes (2). drawLabel/drawCursor/drawSelectionBox take the chip
overlay explicitly; comment-pin dots move to the chip layer too (the 0005
"drawn last so pins sit above" comment had the rule backwards). Verified
with a chip-inside-30%-fill pixel repro + the full presence suite.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013u9h8fkQktH7KRECaHJmUG
The eeschema/pcbnew binding TUs had ~1000 lines of copy-pasted collab code.
Factored into two header-only shared files (zero build-script changes — the
collab_presence_style.h precedent):
- collab_common.h (pcbjam_collab): toUtf8, runOnFiber (the CallAfter+COROUTINE
fiber idiom — was ~25 inline copies), the window.kicadCollab wire emitters
(onDelta/onItems/onCursor/onSelection/onViewport), frame-generic undo test
hooks.
- collab_presence_core.h (pcbjam_presence::CORE): PEER/PIN + all presence
state and machinery (start/canvas binds/lock query, setRemote/setPins/
setStyle, selection check + dedupe, overlay redraw loop, viewport push/pull,
releaseSelection, locks probe), written against the EDA_DRAW_FRAME +
SELECTION_TOOL base classes. Per-editor hooks: frame, selectionTool,
selectionEmitPayload, resolveItem, drawPeerShapes. One CORE instance per TU
(anonymous-namespace presenceCore()) so the merged image keeps per-editor
state separation.
- NEW per-editor resolveXsel(frame, peer): ONE cross-app resolver shared by
the ghost render AND kicadCollabTestGetCrossMapped — the mapping loop was
duplicated within each TU, letting the test probe drift from the pixels.
Deliberately NOT factored: the Yjs differ/apply halves (itemToJson/makeItem/
flushDiff/doApply*) — structurally parallel but the bodies encode per-editor
sync semantics and editor-specific asyncify devirtualization workarounds that
must stay visible. kicadOpenFile/kicadCollabOnSave keep the existing
KICAD_MERGED_EMBIND mechanism. TestClearSelection stays editor-typed
(ClearSelection is not on the SELECTION_TOOL base).
eeschema_embind 2203→1721 lines, pcbnew_embind 2518→1993. JS-facing names,
signatures and the kicad_editor_embind.cpp dispatcher are unchanged.
Verified: kicad_editor image builds clean; tests/kicad presence+locks 18/18
(incl. ghost-render pixel compares), collab+ysync-repros 31 passed/2 skipped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013u9h8fkQktH7KRECaHJmUG
Chrome visibility is now a runtime toggle on any device instead of being
device-wired: mobile defaults to canvas-only, desktop to full UI, and the
floating top-right button (or Figma's Cmd/Ctrl+\ chord — free in KiCad,
only bare \ is bound) flips between them live.
- chrome-visibility.ts: module-global store (default isMobileMode(),
session-only) + pure hotkey matcher (rejects AltGr backslash + repeats)
- WasmTool: capture-phase hotkey (stopped before the wx layer), floating
toggle pill (matches the comment FAB design), useLayoutEffect apply with
sync first call + retry; overlays follow the toggle, capability-gated on
the kicad_editor bundle's kicadSetChrome export
- boot.ts mobile opt now installs touch gestures only
- kicadSetChrome: frame-keyed hide-time snapshot so restore re-shows ONLY
what hide took away (blanket Show(true) surfaced KiCad's default-hidden
Search/Properties/Net-Inspector panes); toolbars-only fallback otherwise
- tests: chrome-toggle.spec.ts (desktop hide/restore + geometric
restore-exactness ±3px), mobile toggle round-trip, 12 new unit tests,
test:web:mobile npm script
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On a mobile device (or ?mobile=1) the editors run canvas-only with touch
gestures driving the view:
- touch-gestures.ts: pure recognizer (unit-tested) + DOM shim installed in
boot preRun — one-finger drag → synthetic middle-drag (pan), pinch →
synthetic wheel at the centroid (zoom-to-cursor), tap → left click.
preRun registration order is what lets stopImmediatePropagation suppress
the wx layer's single-finger→LEFT-drag touch mapping.
- kicadSetChrome(bool) embind: hides all AUI panes except DrawFrame + the
menubar/status bar via generic wx APIs (kicad fork untouched); boot polls
it after runtime init. Pairs with the wxwidgets IsShown layout fix.
- mobile-mode.ts: ?mobile=1/0 override or UA-CH/coarse-pointer autodetect;
shell hides its overlays and the inherent-to-mobile preflight warnings.
- e2e: mobile-chromium project (Pixel 7) + 4 specs (chrome-less, tap,
pinch, pan) with screenshot-invertibility assertions; also fixes
tool-switch.spec's stale pre-scope-refactor URLs (was broken on main).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ctrl+Z after a peer's edit no longer reverts (and re-broadcasts) the
peer's work, and the adopt undo-bomb is gone:
- doApply/doApplyItems (both editors) Push with SKIP_UNDO; the emit path
is unaffected (suppression keys off s_applyingRemote, not undo).
- With SKIP_UNDO no picker owns removed items — the bindings free them
after Push (explicit removals + upsert's remove-before-re-add; fields
excluded: CHT_REMOVE hides them, parent keeps ownership). Freeing stays
out of the fork commit classes so DRC's SKIP_UNDO callers can't
double-free.
- Test hooks kicadCollabTestUndo/UndoDepth, registered per-editor AND in
the kicad_editor dispatcher (merged image compiles out per-app
registrations).
- kicad pointer: eeschema UUID undo guard + SKIP_UNDO connectivity split
+ quiet stale-entry drop (ca8877324c).
- tests/kicad/collab-undo.spec.ts: 5 scenarios (no undo entry from remote
applies; selective undo; stranded replaced/deleted entries) — 5/5, plus
collab/ysync regression 30 pass.
- docs: ysync-review 20 fix record; 09 marked FIXED; overview indexed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ejJEvS7ogef2o9gVTXjmp
While a peer has an item selected, local users can still select it for
inspection but move/drag/rotate/delete skip it with an infobar naming the
holder (native locked-item UX; enforced via the fork's PCBJAM_REMOTE_LOCK
query — kicad 81f9cd80fd, the epic's first fork-touching phase). Overlapping
holds (both grabbed inside the awareness propagation window) tie-break
deterministically: lowest (user.id, clientID) keeps the item, every losing
client auto-releases it.
- lock-tiebreak.ts: pure policy — beats(), remoteLocks() (union of ALL other
clients' selections incl. own user's other tabs, minus own-held-and-winning
uuids so the winner isn't blocked mid-release), contestedReleases()
- presence.ts: clients() (per-client view, no user dedupe) + self(); FIX for
a pre-existing flaky stack overflow — resolveCollision re-entered itself
synchronously via its own patch's awareness 'change' and could ping-pong on
stale same-user states (~1-in-3 unit runs); re-entrancy guard defers
re-resolution to the next genuine delivery
- presence-kicad.ts: locks ride the kicadCollabSetRemote snapshot
(`locks:[{uuid,name}]`); losing overlaps call kicadCollabReleaseSelection
- wasm bindings (both TUs + merged dispatch): g_locks map + fork query
install; kicadCollabReleaseSelection (cancelInteractive only when a tool
stack is live — bare ESC would clear the whole selection — then selective
RemoveItemFromSel + infobar + forced re-emit); kicadCollabTestGetLocked
- tests: lock-tiebreak unit suite; presence-locks e2e for both editors (real
move veto — pcbnew click+M hotkey since its default left-drag is
rubber-band select, eeschema real drag — each with an unlocked control);
two-tab tests/web/locks.spec.ts (lock propagation + deterministic tiebreak
release + unlock on clear, passing vs real partykit)
Spec: docs/features/collab-presence/0007 (closed repo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
Selecting a symbol in eeschema ghost-highlights the linked footprint(s) in
every pcbnew tab of the project, and vice versa — across users AND one
user's own two tabs. Native KIWAY cross-probe is inert in WASM (one frame
per page); this rides the presence layer instead.
- cross-app.ts: project-wide awareness-only room (presenceRoomId), publishes
full PresenceState at selection rate (cursor always null); peers() = other-
TOOL clients incl. own user's other tabs; window.__pcbjamCrossApp test handle
- presence-kicad.ts: parseSelectionEmit (bare array | {uuids,fpPaths}),
xselFromPeerState (pcbnew paths → symbol uuids; eeschema uuids verbatim),
cross peers appended to the kicadCollabSetRemote snapshot as
{id "<user>#x<client>", name "<user> · sch|pcb", xsel}
- C++ (zero fork changes): pcbnew emits {uuids, fpPaths} (FOOTPRINT::GetPath)
and ghost-renders xsel via path-tail suffix scan; eeschema resolves xsel via
ResolveItem gated to the CURRENT sheet (xsel arrives project-wide, unlike
room-scoped selections); ghostStyle = alphas × xselAlphaScale (0.55, tuner-
patchable); new exports kicadCollabGetSelectionFull / TestGetCrossMapped /
TestSelectComponent (skips power symbols — PWR_FLAG has no footprint) +
merged-image dispatch
- tests: presence suites extended (payload shape, ghost render pixel tests,
13/13) + new two-tab tests/web/cross-probe.spec.ts (passing vs real
partykit); eeschema pixel compares now target the #glcanvas-* GAL panel
(the whole-window #canvas compare flaked on the auto-dismissing version
infobar — also fixes the long-known presence-eeschema restore flake);
cross-app + presence-kicad vitest suites
Spec: docs/features/collab-presence/0006 (closed repo).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lg5jwWuhFH5dL8hEcBDuP2
The full root-cause chain of the empty/mangled badges:
- GAL text justify is painter-residue → PRESENCE_TEXT_OVERLAY pins TOP-LEFT.
- VIEW_OVERLAY::ViewDraw hard-sets EVERY overlay to GetMinDepth(), so the
shapes and text overlays always collided at one depth, where later-drawn
fragments lose (and bitmap glyphs are textured quads whose transparent
cells also write depth — punch-through produced cell-shaped holes instead).
Fix rides the new fork VIEW_OVERLAY::SetDepthOffset: shapes at min+1,
labels at min — 'rect first, text on top' now holds regardless of paint
order. Verified: chips contain crisp names (dark-on-light, white-on-dark)
for selection tags and cursor labels; presence suites 10/10.
- kicad pointer bump (fork 24c5854d5b).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
Two root causes of the wandering/invisible badge text:
- A plain VIEW_OVERLAY draws with whatever text justify the LAST painter left
in the GAL (CENTER is only the reset default) — anchoring was
nondeterministic. PRESENCE_OVERLAY (VIEW_OVERLAY subclass, replaces
MakeOverlay) pins TOP-LEFT justify before executing its commands; the label
math is written against that.
- The whole overlay draws at ONE depth and same-depth fragments drawn later
LOSE the depth test — a chip rect over its text erased the text. Draw the
text FIRST, the chip rect AFTER: the rect is rejected exactly on the glyph
pixels, punching the text through.
Verified live: chips contain their names on light (dark text) and dark
(white text) user colors, for both selection tags and cursor labels.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
- presence.ts: colors claimed by ARRIVAL ORDER — each client takes the lowest
free palette slot (no birthday-problem hash collisions; the best palette
colors go first; N ≤ palette-size editors always distinct). Coordination-
free: claims ride awareness; simultaneous-join collisions converge (lower
clientID keeps, the other re-claims), same-user tabs adopt one color,
claims are sticky across eeschema sheet rebinds (skeletons reuse them).
colorOf(userId) resolves live colors; comment pins/popovers follow it
(offline authors fall back to the hash). +2 unit tests (verified live:
alice=slot0, bob=slot1, roster/pins consistent).
- CommentLayer: single comment icon expanding into a horizontal bar — new
comment · list · show/hide all (eye empties the GAL pin set + DOM targets;
re-shown on new-comment/panel-jump). Pins are DRAGGABLE: live LWW anchor
writes while dragging (peers + the GAL dot follow), nearest-item re-snap on
drop, click-vs-drag by 4px threshold (verified live: drag synced to the
peer tab exactly). Shared setThreadAnchor + controller moveThread/
setPinsVisible/colorFor.
- collab_presence_style.h: chip text color by background luminance (dark on
light chips, white on dark — BitmapText draws with the stroke color).
- comments/presence-roster e2e updated for the bar + passing; collab units
56/56; shared 122/122; shared pointer bump.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
- collab_presence_style.h: GAL BitmapText CENTERS on its position (confirmed
in GAL::ResetTextAttributes — the mispositioned nameplates); labels/chips
now hand GAL the block center. New selection shape 5 'exact outline':
pcbnew hugs real geometry (footprint bounding hull, TransformShapeToPolygon
for the rest, padding inflates the polygon); eeschema falls back to rect.
- kicadCollabTestDemoSet (both TUs + merged): labeled demo groups — smallest
+ largest footprint and the two busiest nets' segments (symbols + wire
bundles on sch) — so the style preview covers the real range of shapes.
- PresenceTuner: Colors section rebuilt as explicit modes (per-user / fixed /
palette) with preset palettes (default, pastel, vivid, okabe-ito), buffered
hex editing + Apply (the old always-filtering textarea ate keystrokes), an
'overlay only' hint; demo injection consumes the varied demo set; 'exact
outline (pcb)' in the shape list.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
Parametrizes every visual knob of the presence overlay so we can pick the
shipped look live, then wire the winners into the defaults:
- collab_presence_style.h: shared STYLE struct + drawing (now used by BOTH
editor TUs — no more duplicated overlay code): selection shape (rect /
corner brackets / underline / rounded rect / filled-only), border width +
alpha, infill alpha, padding, corner radius; name tag show/size/chip-
background/inside-outside/top-bottom/start-end-center/offset; cursor shape
(cross / pointer / circle+dot), size/width/alpha + label knobs; fixed-color
and palette-by-name-hash overrides (try palettes without changing what
senders publish); pin radius/ring/alphas. Defaults == shipped look.
- kicadCollabSetStyle(json) live-patch export + kicadCollabTestListItems(n)
(real KIIDs for synthetic previews); merged dispatch; pins now carry the
author name so palette overrides recolor them consistently.
- PresenceTuner.tsx: floating dev panel (env-gated, tree-shaken otherwise) —
grouped sliders/selects, demo peers+pins injection for SOLO tuning,
localStorage persistence across reloads, Copy JSON export, reset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
Hybrid pins: the wasm draws the dot (kicadCollabSetPins rides the presence
VIEW_OVERLAY, author color + white ring, drawn above selections; zero
kicad-fork changes), the DOM owns interaction —
- comments.ts: controller gluing the MIT kdoc_comments helpers to the editor:
anchor resolution per tool IU (pins track item moves via kdoc_items
observation), throttled pin snapshots, anchorAt nearest-item snap, jumpTo
via new kicadCollabSetViewport; rebinds per sheet like presence.
- CommentLayer.tsx: comment mode (click catcher + composer), pin hit targets
over the GAL dots, thread popover (reply/edit/delete own, resolve/reopen,
delete thread), panel with resolved filter + jump-to (popover centers when
the pin is off-screen). Resolved pins drop figma-style.
- WasmTool: controller lifecycle beside presence; live viewport feed;
window.__pcbjamComments test handle (threads persist in the room ydoc).
- e2e tests/web/comments.spec.ts: two-tab create → reply → resolve → panel
filter → delete, passing vs real partykit; presence suites + collab units
stay green; shared pointer bump (0004 model).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
Each remote selection rectangle now carries the selector's name above its
top-left corner (9px glyph, peer color) and the outline width goes 1.5→2.5px.
Verified live two-tab + presence-pcbnew.spec.ts 5/5.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
Zero kicad-fork changes — all in the embind layer:
- pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/
leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection
check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld;
viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom
and sized the first cut's overlay nm-small); kicadCollabSetRemote renders
peers' cursors (cross + name) and selection bbox outlines into one
per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local
selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection.
- kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003).
- presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001)
and pushes trailing-throttled peer snapshots into the wasm; wired from
WasmTool.startPresence (pcbnew-gated). +5 unit tests.
- tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select
emit, throttled cursor, remote render with no-leak + pixel restore,
viewport unit band. Existing pcbnew-collab/items-bridge suites stay green.
Verified live: two tabs over partykit — peer cursor cross + label + selection
outline visible on the other tab's canvas.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
All seven ysync-review bugs fixed and verified; every repro's expected-fail
marker removed (they now run as regression tests). Also fixes two bugs found
while verifying (doc 17 F5/F6): file-seeded Y bodies are re-upserted in the
editor's serialization (doc-16 F4 was an artifact of bug 01), and the
0008-era "asyncify-fragile envelope parse" was really wrapInBoardEnvelope
emitting display layer names — canonical LSET::Name() fixes track/via/zone
v2 applies; makeFromBlob now logs parse errors instead of swallowing them.
Verified: shared 98, standalone collab 37, ysync e2e 20/20 (chromium),
collab regression set 21 passed / 3 pre-existing skips (firefox).
Bumps: kicad (board_commit child-removal listener notification),
web/pcbjam-shared (slot prune + arbitrated seed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgThWXtdvrYLK47EDFoGdq
The 2026-07-02 sync review (docs/features/ysync-review, ysync-review branch)
found 7 bugs and that the two-tab e2e only exercised the DEAD legacy scalar
wire. This lands plan doc 15 in full; results + empirical findings in doc 16.
- tests/collab/browser-entry-v2.ts (+build.mjs): the PRODUCTION v2 stack
bundled for e2e (connectKicadDoc + attachKicadCollab, kdoc_* keys), with
in-page renderActiveDoc/singleSeedRender/driftReport helpers and yjs forced
to ONE copy (the two web pnpm workspaces otherwise bundle two
instanceof-incompatible instances).
- tests/kicad/ysync-two-tab.spec.ts: pl_editor green baseline (A↔B edits,
ITEM-level drift silence) + divergent-uuid adopt; bug-01 pcb/ee fresh-room
repros (Chromium-only: two kicad_editor tabs exceed Firefox's per-process
wasm budget); bug-06 concurrent-seed race; bug-03 Y-half.
- tests/kicad/ysync-repros-{pcbnew,eeschema}.spec.ts: bugs 02/03/05 + the
bug-04 matrix (anchor-centred fp rotation, pad resize, endpoint drag,
symbol rotation, Value-field edit), each with green landed-preconditions;
the "local move emits" controls double as headless-emit probes — GREEN on
both tools, so every emit-dependent repro is a live test.fail.
- wasm/bindings: 7 local-edit test hooks via real commits
(CallAfter+COROUTINE) — TestRemoveItem/TestRotateItem (both tools,
dispatched in the merged image), TestSetPadSize/TestMoveEndpoint (pcbnew),
TestSetFieldText (eeschema).
- web/standalone ysync-repros.test.ts: bug-01 units (C++-faithful fake gating
emit on ensureBridge) + bug-07a/b (stale DOWN hook, real sheet-manager gap).
- web/pcbjam-shared bump: bug-03/06 unit repros.
Convention: every repro asserts the CORRECT behavior and is expected-fail
(test.fail/it.fails) naming its bug doc; a fix flips it to "unexpected pass",
forcing marker removal — the repro becomes the regression test. Every
expected failure verified (JSON reporter) to fail at its documented assert.
Suite state: 39 passed / 0 failed / 0 flaky / 5 skipped (2 firefox guards,
2 pre-existing legacy two-tab skips, 1 pre-existing roundtrip fixme).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPfrVhfYgPPgtawjSssZfn
- 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>
- display lists: literal command replay through the shim state machine
(state leaks + between-list material changes get GL semantics for free);
recorded glDrawArrays EAGERLY snapshots enabled client-memory arrays at
compile time (the renderer frees them right after glEndList) and records
buffer+offset for VBO-backed pointers
- client-array draws: shared AttribSource path packs client-memory arrays
into one scratch-VBO upload and binds user VBOs with original offsets
(GL_BYTE normals / GL_UNSIGNED_BYTE colors normalized per GL1)
- glDrawElements over user VBO/IBO passes indices through untouched
(ELEMENT_ARRAY_BUFFER never touched by the shim)
- state mutators (enable/bindTexture/blendFunc/lineWidth/alphaFunc) shared
between the __wrap interceptors and list replay
Parity: 44/47 under the 0.02 floor (was 20). Verified visually: mini-board
Tier-3 composite (mask translucency + stencil-punched TH holes), stencil
hole subtraction, alpha-tested seg-end discs, COMBINE const-alpha model
transparency, and post-machining reproducing the upstream countersink bug
(walls absent, matching the native golden). Remaining 3: camera-* trio
needing the M4 GLU quadrics (+5 small-geometry false-greens they share).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wasm/gl1: the GL1.x fixed-function emulation layer replacing the
gl_ffp_stub.c no-ops in the 3d-regression harness link. This batch:
- symbol split: 52 FFP-only entry points implemented; 10 Emscripten-owned
names intercepted via wasm-ld --wrap (sources.txt/wrapped_symbols.txt are
the shared manifests for both link sites; production hookup lands in M7)
- matrix stacks (MODELVIEW/PROJECTION, glGetFloatv readback), immediate
mode with all 8 GL1 primitive conversions, GL1-default state mirror
- full GL 1.5 Gouraud lighting uber-shader (eye-space light capture at
glLightfv time, color-material, two-side, COMBINE evaluator + alpha test
wired but inert until M3/M5)
- draw routing: FFP traffic identified by GL_VERTEX_ARRAY client state;
blit/2D-GAL draws pass through untouched
- display lists: correct glGenLists/glIsList existing-empty semantics;
recorder itself is M3 (recorded commands drop with a one-time warning)
Parity: 20/47 under the 0.02 floor, of which 12 verified genuinely
rendering (bg-gradient x2, bounding-box, half-open-cylinder, segment x2,
material x3, light x3 — eyeballed against baselines); the other 8 are
small-geometry scenarios whose missing GLU/display-list content sits under
the floor (become real in M3/M4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
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>
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>
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>
Provides wasm/stubs/model_substitution_stub.cpp — no-op IsWrlExtension /
STEP_CATALOG::Build / FindMatchFor — so pcbnew and footprint_editor link when the
3D viewer is off (KICAD_BUILD_3D_VIEWER_WASM=OFF). The KiCad 10 rebase made
dialog_migrate_3d_models.cpp reference these, which live in the unlinked 3D
library. The browser build has no 3D viewer, so model migration finds no
substitutions. Bumps kicad to the matching CMake wiring (fef0343).
Verified: pcbnew + footprint_editor compile and link clean (eeschema/symbol_editor
unaffected).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>