pcbjam/wasm/bindings/collab_common.h

289 lines
10 KiB
C
Raw Normal View History

refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
/*
* Shared plumbing for the per-editor collab binding TUs (eeschema_embind.cpp,
* pcbnew_embind.cpp) the frame-type-free half of the bridge: string/JSON
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
* wire emitters to window.kicadCollab, the CallAfter+COROUTINE apply queue,
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
* and the frame-generic test hooks. Header-only (the collab_presence_style.h
* pattern), so the build script needs no extra objects and the merged
* kicad_editor image links it without ODR issues.
*/
#pragma once
#ifdef __EMSCRIPTEN__
#include <deque>
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
#include <emscripten.h>
#include <functional>
#include <string>
#include <nlohmann/json.hpp>
#include <wx/event.h>
#include <wx/string.h>
#include <eda_base_frame.h>
#include <tool/actions.h>
#include <tool/coroutine.h>
#include <tool/tool_manager.h>
namespace pcbjam_collab {
inline std::string toUtf8( const wxString& s ) { return std::string( s.utf8_str() ); }
/**
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
* Run a body on the editor's main loop AND inside a COROUTINE the exact
* context native tool edits run in. CallAfter queues onto the app's
* pending-event list (drained every frame by the wasm main loop,
* src/wasm/evtloop.cpp); COROUTINE::Call moves the body onto its own
* coroutine stack. Commits, GAL overlay work and the s-expr formatters run
* through this.
*
* SERIALIZED (drift-trio finding #10, standalone-hardening 0008 §10): bodies
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
* run strictly one-at-a-time through a FIFO. A body that SUSPENDS (a JSPI
* suspension inside commit.Push connectivity/GAL work) returns early from
* COROUTINE::Call while its coroutine is still in flight. Without the queue
* the main loop kept draining pending events and started the NEXT body a
* local commit and a remote apply then ran interleaved on shared
* commit/listener state (s_applyingRemote is a single global), silently
* losing applies on the actively-editing receiver and, in the worst case,
* corrupting memory (fuzz S10: wasm OOB on an observer). The busy flag is
* suspension-safe: while a suspended body is in flight every other drain
* invocation no-ops on the flag, and the body's own coroutine tail
* re-schedules the drain when it completes.
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
*/
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
inline std::deque<std::function<void()>>& applyQueue()
{
static std::deque<std::function<void()>> q;
return q;
}
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
inline bool& applyBusy()
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
{
static bool busy = false;
return busy;
}
/* The in-flight body. HEAP-allocated and pinned for the body's whole life:
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
* when a body SUSPENDS (a JSPI suspension inside commit.Push), COROUTINE::Call
* RETURNS EARLY the later resume re-enters the coroutine through the SAME
* callable at the SAME addresses. Stack-local cor/body (the original
* runOnCoroutine AND the first serialized version) were destroyed on that
* early return, so the resume ran through freed objects memory corruption
* downstream (finding #10b's symbolized stack). `done` is the ONLY completion
* signal; Call() returning is not. */
struct ApplySlot
{
COROUTINE<int, int>* cor = nullptr;
std::function<void()>* body = nullptr;
bool done = false;
};
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
inline ApplySlot& activeApplySlot()
{
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
static ApplySlot s;
return s;
}
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
inline wxEvtHandler*& applyHandler()
{
static wxEvtHandler* h = nullptr;
return h;
}
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
inline void drainApplies();
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
inline void reapApply()
{
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
ApplySlot& slot = activeApplySlot();
delete slot.cor;
delete slot.body;
slot.cor = nullptr;
slot.body = nullptr;
slot.done = false;
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
applyBusy() = false;
}
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
inline void drainApplies()
{
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
ApplySlot& slot = activeApplySlot();
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
if( applyBusy() )
{
if( !slot.done )
staging CI green-up: P-1 apply-slot wedge under JSPI, popup P-4 regression, ysync double-seed header, spec fixes - P-1 (findings-p.spec P-1c red on CI + locally): a JS exception thrown from a window.kicadCollab wire callback does NOT reach the C++ catch(...) — under JSPI it REJECTS the promising coroutine entry, slot.done never flips and the apply slot is busy for the page's life. Three layers: libcontext flags the rejected record dead (kicad → d0afbc4bd3), drainApplies() reaps a !done && !CanResume() body, and every EM_ASM window.kicadCollab.onX(...) call is try/catch-guarded (collab_common.h, kicad_editor/eeschema/pl_editor embind). - popup.spec 'dismisses on outside click': wx P-4 SetFocus delegation excluded for wxPopupWindow (wxwidgets → a7436d1052). - ysync-two-tab concurrent seed: repairLayoutY + seedNonce retraction deleted both headers (pcbjam-shared → 8dabf2e). - dark-mode.spec: stale light-mode reference (pre wx fa46f1822c DOM re-projection) replaced with the CI chromium render. - items-wire-batch-loss.spec: precondition obsolete after P-5 — poisoned entry is now synthetic, plus a P-5 contract assertion (field blob == ''). - presence-ghost-peer.spec: skip early when no /parties/ socket (CI web e2e runs the BroadcastChannel provider); findings-p1.spec: polled click-select precondition. Local: kicad-chromium findings/presence/ysync/collab 59/59, popup 7/7, dark-mode 1/1, pcbjam-shared vitest 249/249. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvvARNMxQrq17vogn3REMU
2026-08-28 22:25:21 +02:00
{
if( slot.cor && slot.cor->CanResume() )
return; // suspended body still in flight — its tail re-drains
// P-1: the body's activation died without finishing (its promising entry
// REJECTED — a JS exception out of a wire callback, or a trap — and libcontext
// flagged the record dead). Its tail never runs, so nothing else would ever
// reap it: release the slot here and keep draining.
EM_ASM( { console.error( '[pcbjam collab] apply body died mid-flight slot released' ); } );
}
staging CI green-up: P-1 apply-slot wedge under JSPI, popup P-4 regression, ysync double-seed header, spec fixes - P-1 (findings-p.spec P-1c red on CI + locally): a JS exception thrown from a window.kicadCollab wire callback does NOT reach the C++ catch(...) — under JSPI it REJECTS the promising coroutine entry, slot.done never flips and the apply slot is busy for the page's life. Three layers: libcontext flags the rejected record dead (kicad → d0afbc4bd3), drainApplies() reaps a !done && !CanResume() body, and every EM_ASM window.kicadCollab.onX(...) call is try/catch-guarded (collab_common.h, kicad_editor/eeschema/pl_editor embind). - popup.spec 'dismisses on outside click': wx P-4 SetFocus delegation excluded for wxPopupWindow (wxwidgets → a7436d1052). - ysync-two-tab concurrent seed: repairLayoutY + seedNonce retraction deleted both headers (pcbjam-shared → 8dabf2e). - dark-mode.spec: stale light-mode reference (pre wx fa46f1822c DOM re-projection) replaced with the CI chromium render. - items-wire-batch-loss.spec: precondition obsolete after P-5 — poisoned entry is now synthetic, plus a P-5 contract assertion (field blob == ''). - presence-ghost-peer.spec: skip early when no /parties/ socket (CI web e2e runs the BroadcastChannel provider); findings-p1.spec: polled click-select precondition. Local: kicad-chromium findings/presence/ysync/collab 59/59, popup 7/7, dark-mode 1/1, pcbjam-shared vitest 249/249. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvvARNMxQrq17vogn3REMU
2026-08-28 22:25:21 +02:00
reapApply(); // completed (or died) since the last drain
}
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
auto& q = applyQueue();
while( !q.empty() )
{
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
applyBusy() = true;
slot.done = false;
slot.body = new std::function<void()>( std::move( q.front() ) );
q.pop_front();
slot.cor = new COROUTINE<int, int>( []( int ) -> int
{
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
ApplySlot& sl = activeApplySlot();
// P-1 hardening (findings group P): `done` is the ONLY completion signal, so a
staging CI green-up: P-1 apply-slot wedge under JSPI, popup P-4 regression, ysync double-seed header, spec fixes - P-1 (findings-p.spec P-1c red on CI + locally): a JS exception thrown from a window.kicadCollab wire callback does NOT reach the C++ catch(...) — under JSPI it REJECTS the promising coroutine entry, slot.done never flips and the apply slot is busy for the page's life. Three layers: libcontext flags the rejected record dead (kicad → d0afbc4bd3), drainApplies() reaps a !done && !CanResume() body, and every EM_ASM window.kicadCollab.onX(...) call is try/catch-guarded (collab_common.h, kicad_editor/eeschema/pl_editor embind). - popup.spec 'dismisses on outside click': wx P-4 SetFocus delegation excluded for wxPopupWindow (wxwidgets → a7436d1052). - ysync-two-tab concurrent seed: repairLayoutY + seedNonce retraction deleted both headers (pcbjam-shared → 8dabf2e). - dark-mode.spec: stale light-mode reference (pre wx fa46f1822c DOM re-projection) replaced with the CI chromium render. - items-wire-batch-loss.spec: precondition obsolete after P-5 — poisoned entry is now synthetic, plus a P-5 contract assertion (field blob == ''). - presence-ghost-peer.spec: skip early when no /parties/ socket (CI web e2e runs the BroadcastChannel provider); findings-p1.spec: polled click-select precondition. Local: kicad-chromium findings/presence/ysync/collab 59/59, popup 7/7, dark-mode 1/1, pcbjam-shared vitest 249/249. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvvARNMxQrq17vogn3REMU
2026-08-28 22:25:21 +02:00
// body that unwinds on a C++ exception would leave applyBusy() true for the
// page's life and silently queue every later job behind it: fit/pan from JS,
// remote applies AND the local-edit flushDiff (local edits stop syncing).
// Always mark the slot done. NOTE: a JS exception thrown out of an EM_ASM wire
// callback does NOT surface here — under JSPI it rejects the promising entry
// and this frame is simply abandoned; that case is covered by the wire
// emitters' own try/catch plus the dead-body reap in drainApplies().
try
{
( *sl.body )();
}
catch( ... )
{
EM_ASM( { console.error( '[pcbjam collab] apply body threw slot released' ); } );
}
sl.done = true;
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
// If we suspended, no drain is pending by the time the body
// completes — schedule the reap + next body from the coroutine
// tail (CallAfter only queues; safe here).
if( wxEvtHandler* h = applyHandler() )
h->CallAfter( []() { drainApplies(); } );
return 0;
} );
slot.cor->Call( 0 );
if( !slot.done )
staging CI green-up: P-1 apply-slot wedge under JSPI, popup P-4 regression, ysync double-seed header, spec fixes - P-1 (findings-p.spec P-1c red on CI + locally): a JS exception thrown from a window.kicadCollab wire callback does NOT reach the C++ catch(...) — under JSPI it REJECTS the promising coroutine entry, slot.done never flips and the apply slot is busy for the page's life. Three layers: libcontext flags the rejected record dead (kicad → d0afbc4bd3), drainApplies() reaps a !done && !CanResume() body, and every EM_ASM window.kicadCollab.onX(...) call is try/catch-guarded (collab_common.h, kicad_editor/eeschema/pl_editor embind). - popup.spec 'dismisses on outside click': wx P-4 SetFocus delegation excluded for wxPopupWindow (wxwidgets → a7436d1052). - ysync-two-tab concurrent seed: repairLayoutY + seedNonce retraction deleted both headers (pcbjam-shared → 8dabf2e). - dark-mode.spec: stale light-mode reference (pre wx fa46f1822c DOM re-projection) replaced with the CI chromium render. - items-wire-batch-loss.spec: precondition obsolete after P-5 — poisoned entry is now synthetic, plus a P-5 contract assertion (field blob == ''). - presence-ghost-peer.spec: skip early when no /parties/ socket (CI web e2e runs the BroadcastChannel provider); findings-p1.spec: polled click-select precondition. Local: kicad-chromium findings/presence/ysync/collab 59/59, popup 7/7, dark-mode 1/1, pcbjam-shared vitest 249/249. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvvARNMxQrq17vogn3REMU
2026-08-28 22:25:21 +02:00
{
if( slot.cor->CanResume() )
return; // suspended — cor/body stay pinned for the resume
EM_ASM( { console.error( '[pcbjam collab] apply body died at entry slot released' ); } );
}
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
reapApply();
}
}
/** Test/diagnostic probe (P-1): is the apply slot busy and how many bodies wait behind it.
* A `busy` that never clears while `queued` grows is the wedge signature. */
inline std::string applyQueueStateJson()
{
return "{\"busy\":" + std::string( applyBusy() ? "true" : "false" )
+ ",\"queued\":" + std::to_string( applyQueue().size() ) + "}";
}
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
inline void runOnCoroutine( wxEvtHandler* aHandler, std::function<void()> aBody )
{
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
applyHandler() = aHandler;
applyQueue().push_back( std::move( aBody ) );
aHandler->CallAfter( []() { drainApplies(); } );
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
}
// ── C++ → JS wire emitters (no-ops without a JS listener) ───────────────────
/** Legacy scalar delta wire: window.kicadCollab.onDelta. */
inline void emitDelta( const nlohmann::json& aDelta )
{
std::string s = aDelta.dump();
EM_ASM( {
if( window.kicadCollab && window.kicadCollab.onDelta )
staging CI green-up: P-1 apply-slot wedge under JSPI, popup P-4 regression, ysync double-seed header, spec fixes - P-1 (findings-p.spec P-1c red on CI + locally): a JS exception thrown from a window.kicadCollab wire callback does NOT reach the C++ catch(...) — under JSPI it REJECTS the promising coroutine entry, slot.done never flips and the apply slot is busy for the page's life. Three layers: libcontext flags the rejected record dead (kicad → d0afbc4bd3), drainApplies() reaps a !done && !CanResume() body, and every EM_ASM window.kicadCollab.onX(...) call is try/catch-guarded (collab_common.h, kicad_editor/eeschema/pl_editor embind). - popup.spec 'dismisses on outside click': wx P-4 SetFocus delegation excluded for wxPopupWindow (wxwidgets → a7436d1052). - ysync-two-tab concurrent seed: repairLayoutY + seedNonce retraction deleted both headers (pcbjam-shared → 8dabf2e). - dark-mode.spec: stale light-mode reference (pre wx fa46f1822c DOM re-projection) replaced with the CI chromium render. - items-wire-batch-loss.spec: precondition obsolete after P-5 — poisoned entry is now synthetic, plus a P-5 contract assertion (field blob == ''). - presence-ghost-peer.spec: skip early when no /parties/ socket (CI web e2e runs the BroadcastChannel provider); findings-p1.spec: polled click-select precondition. Local: kicad-chromium findings/presence/ysync/collab 59/59, popup 7/7, dark-mode 1/1, pcbjam-shared vitest 249/249. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvvARNMxQrq17vogn3REMU
2026-08-28 22:25:21 +02:00
{
// A throwing listener must never unwind the wasm frame that called it: under
// JSPI that rejects the running coroutine's entry (findings P-1).
try { window.kicadCollab.onDelta( UTF8ToString( $0 ) ); }
catch( e ) { console.error( '[pcbjam collab] onDelta listener threw', e ); }
}
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
}, s.c_str() );
}
/** v2 per-item s-expr blob wire (ysync 0008): window.kicadCollab.onItems. */
inline void emitItemsWire( const nlohmann::json& aWire )
{
std::string s = aWire.dump();
EM_ASM( {
if( window.kicadCollab && window.kicadCollab.onItems )
staging CI green-up: P-1 apply-slot wedge under JSPI, popup P-4 regression, ysync double-seed header, spec fixes - P-1 (findings-p.spec P-1c red on CI + locally): a JS exception thrown from a window.kicadCollab wire callback does NOT reach the C++ catch(...) — under JSPI it REJECTS the promising coroutine entry, slot.done never flips and the apply slot is busy for the page's life. Three layers: libcontext flags the rejected record dead (kicad → d0afbc4bd3), drainApplies() reaps a !done && !CanResume() body, and every EM_ASM window.kicadCollab.onX(...) call is try/catch-guarded (collab_common.h, kicad_editor/eeschema/pl_editor embind). - popup.spec 'dismisses on outside click': wx P-4 SetFocus delegation excluded for wxPopupWindow (wxwidgets → a7436d1052). - ysync-two-tab concurrent seed: repairLayoutY + seedNonce retraction deleted both headers (pcbjam-shared → 8dabf2e). - dark-mode.spec: stale light-mode reference (pre wx fa46f1822c DOM re-projection) replaced with the CI chromium render. - items-wire-batch-loss.spec: precondition obsolete after P-5 — poisoned entry is now synthetic, plus a P-5 contract assertion (field blob == ''). - presence-ghost-peer.spec: skip early when no /parties/ socket (CI web e2e runs the BroadcastChannel provider); findings-p1.spec: polled click-select precondition. Local: kicad-chromium findings/presence/ysync/collab 59/59, popup 7/7, dark-mode 1/1, pcbjam-shared vitest 249/249. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvvARNMxQrq17vogn3REMU
2026-08-28 22:25:21 +02:00
{
// A throwing listener must never unwind the wasm frame that called it: under
// JSPI that rejects the running coroutine's entry (findings P-1).
try { window.kicadCollab.onItems( UTF8ToString( $0 ) ); }
catch( e ) { console.error( '[pcbjam collab] onItems listener threw', e ); }
}
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
}, s.c_str() );
}
/** Local cursor position (presence): window.kicadCollab.onCursor. */
inline void emitCursor( double aX, double aY, bool aActive )
{
EM_ASM( {
if( window.kicadCollab && window.kicadCollab.onCursor )
staging CI green-up: P-1 apply-slot wedge under JSPI, popup P-4 regression, ysync double-seed header, spec fixes - P-1 (findings-p.spec P-1c red on CI + locally): a JS exception thrown from a window.kicadCollab wire callback does NOT reach the C++ catch(...) — under JSPI it REJECTS the promising coroutine entry, slot.done never flips and the apply slot is busy for the page's life. Three layers: libcontext flags the rejected record dead (kicad → d0afbc4bd3), drainApplies() reaps a !done && !CanResume() body, and every EM_ASM window.kicadCollab.onX(...) call is try/catch-guarded (collab_common.h, kicad_editor/eeschema/pl_editor embind). - popup.spec 'dismisses on outside click': wx P-4 SetFocus delegation excluded for wxPopupWindow (wxwidgets → a7436d1052). - ysync-two-tab concurrent seed: repairLayoutY + seedNonce retraction deleted both headers (pcbjam-shared → 8dabf2e). - dark-mode.spec: stale light-mode reference (pre wx fa46f1822c DOM re-projection) replaced with the CI chromium render. - items-wire-batch-loss.spec: precondition obsolete after P-5 — poisoned entry is now synthetic, plus a P-5 contract assertion (field blob == ''). - presence-ghost-peer.spec: skip early when no /parties/ socket (CI web e2e runs the BroadcastChannel provider); findings-p1.spec: polled click-select precondition. Local: kicad-chromium findings/presence/ysync/collab 59/59, popup 7/7, dark-mode 1/1, pcbjam-shared vitest 249/249. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvvARNMxQrq17vogn3REMU
2026-08-28 22:25:21 +02:00
{
// A throwing listener must never unwind the wasm frame that called it: under
// JSPI that rejects the running coroutine's entry (findings P-1).
try { window.kicadCollab.onCursor( $0, $1, $2 ); }
catch( e ) { console.error( '[pcbjam collab] onCursor listener threw', e ); }
}
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
}, aX, aY, aActive ? 1 : 0 );
}
/** Local selection payload (presence): window.kicadCollab.onSelection. */
inline void emitSelection( const std::string& aJson )
{
EM_ASM( {
if( window.kicadCollab && window.kicadCollab.onSelection )
staging CI green-up: P-1 apply-slot wedge under JSPI, popup P-4 regression, ysync double-seed header, spec fixes - P-1 (findings-p.spec P-1c red on CI + locally): a JS exception thrown from a window.kicadCollab wire callback does NOT reach the C++ catch(...) — under JSPI it REJECTS the promising coroutine entry, slot.done never flips and the apply slot is busy for the page's life. Three layers: libcontext flags the rejected record dead (kicad → d0afbc4bd3), drainApplies() reaps a !done && !CanResume() body, and every EM_ASM window.kicadCollab.onX(...) call is try/catch-guarded (collab_common.h, kicad_editor/eeschema/pl_editor embind). - popup.spec 'dismisses on outside click': wx P-4 SetFocus delegation excluded for wxPopupWindow (wxwidgets → a7436d1052). - ysync-two-tab concurrent seed: repairLayoutY + seedNonce retraction deleted both headers (pcbjam-shared → 8dabf2e). - dark-mode.spec: stale light-mode reference (pre wx fa46f1822c DOM re-projection) replaced with the CI chromium render. - items-wire-batch-loss.spec: precondition obsolete after P-5 — poisoned entry is now synthetic, plus a P-5 contract assertion (field blob == ''). - presence-ghost-peer.spec: skip early when no /parties/ socket (CI web e2e runs the BroadcastChannel provider); findings-p1.spec: polled click-select precondition. Local: kicad-chromium findings/presence/ysync/collab 59/59, popup 7/7, dark-mode 1/1, pcbjam-shared vitest 249/249. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvvARNMxQrq17vogn3REMU
2026-08-28 22:25:21 +02:00
{
// A throwing listener must never unwind the wasm frame that called it: under
// JSPI that rejects the running coroutine's entry (findings P-1).
try { window.kicadCollab.onSelection( UTF8ToString( $0 ) ); }
catch( e ) { console.error( '[pcbjam collab] onSelection listener threw', e ); }
}
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
}, aJson.c_str() );
}
/** Viewport transform for the DOM layers (0005): window.kicadCollab.onViewport. */
inline void emitViewport( double aCx, double aCy, double aPxPerIu, int aW, int aH )
{
EM_ASM( {
if( window.kicadCollab && window.kicadCollab.onViewport )
staging CI green-up: P-1 apply-slot wedge under JSPI, popup P-4 regression, ysync double-seed header, spec fixes - P-1 (findings-p.spec P-1c red on CI + locally): a JS exception thrown from a window.kicadCollab wire callback does NOT reach the C++ catch(...) — under JSPI it REJECTS the promising coroutine entry, slot.done never flips and the apply slot is busy for the page's life. Three layers: libcontext flags the rejected record dead (kicad → d0afbc4bd3), drainApplies() reaps a !done && !CanResume() body, and every EM_ASM window.kicadCollab.onX(...) call is try/catch-guarded (collab_common.h, kicad_editor/eeschema/pl_editor embind). - popup.spec 'dismisses on outside click': wx P-4 SetFocus delegation excluded for wxPopupWindow (wxwidgets → a7436d1052). - ysync-two-tab concurrent seed: repairLayoutY + seedNonce retraction deleted both headers (pcbjam-shared → 8dabf2e). - dark-mode.spec: stale light-mode reference (pre wx fa46f1822c DOM re-projection) replaced with the CI chromium render. - items-wire-batch-loss.spec: precondition obsolete after P-5 — poisoned entry is now synthetic, plus a P-5 contract assertion (field blob == ''). - presence-ghost-peer.spec: skip early when no /parties/ socket (CI web e2e runs the BroadcastChannel provider); findings-p1.spec: polled click-select precondition. Local: kicad-chromium findings/presence/ysync/collab 59/59, popup 7/7, dark-mode 1/1, pcbjam-shared vitest 249/249. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvvARNMxQrq17vogn3REMU
2026-08-28 22:25:21 +02:00
{
// A throwing listener must never unwind the wasm frame that called it: under
// JSPI that rejects the running coroutine's entry (findings P-1).
try { window.kicadCollab.onViewport( $0, $1, $2, $3, $4 ); }
catch( e ) { console.error( '[pcbjam collab] onViewport listener threw', e ); }
}
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
}, aCx, aCy, aPxPerIu, aW, aH );
}
// ── frame-generic test hooks (ysync miss 09) ────────────────────────────────
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
/** Run Edit>Undo exactly like the UI would (main loop + apply coroutine) —
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
* exercises the local-ops-only undo policy and the stale-picker UUID guard. */
inline bool testUndo( EDA_BASE_FRAME* aFrame )
{
if( !aFrame )
return false;
jspi cleanup: remove the asyncify-era residue — dead code, conditionals, pipeline scaffolding, stale prose The runtime is JSPI-only; this removes everything that still pretended otherwise. Three exhaustive sweeps (C++/JS+build+CI/tests+docs) drove the inventory; every deletion verified by grep closure + full gates. Broken-right-now fixes: - deploy-staging.yml passed the retired opt_level input — the workflow could not even start. Removed. - env.sh carried dead exports with a live -sASYNCIFY=1 inside (WASM_LDFLAGS/PTHREAD_LDFLAGS, zero consumers). Removed; the WASM_LEGACY_EXCEPTIONS rationale rewritten to the real reason. - docker/build.sh exported PCBJAM_ASYNC_BACKEND (read nowhere). Gone. Dead weight removed: - binaryen submodule (nothing builds or invokes it), wasm-opt-bench workflow + scripts/bench/, get-wasm-opt.sh, diagnostics.js (242 lines of Asyncify-API-only code), the KICAD_PIPELINE background-postprocess scaffolding (existed to parallelize the deleted wasm-opt phase; the postprocess is a seconds-long node script and now runs inline), build-monitor's dead asyncify rows, sched-context orphan build output, dead .gitignore entries, the .jspi-assets spike dir (the two wf-result research JSONs moved to docs/features/async/migration-evidence/). - bindings: fiber_park.h + its 12 embind registrations (broken-if- called under JSPI), the kicadOpenFileStart/OPEN_JOB starter route, main_stack_runner.h + 5 includes, the always-null context-sleep weak hook in nanosleep_yield.c. - shim: the backend field (installed-flag idempotency instead), noteContextWait (dead both sides), the __wxAsyncifyDump alias (+ the WasmTool fallback and string-dump normalize branch). - web: the emscripten-6-ignored mainScriptUrlOrBlob option in boot.ts (gerber-demo keeps it: it loads the deployed CDN release, which predates emscripten 6 — noted inline). Conditionals: all 'backend === jspi' checks reduced to scheduler- presence checks; races_quiescent re-keyed from Asyncify.state (vacuous) to real backlog quiescence (resumeReady/mutatorQueue — NOT _windowLive, which is the probing activation's own window by definition). Renames (identifiers only, no file renames): ASYNC_LINK_FLAGS→ JSPI_LINK_FLAGS and Makefile ASYNC_LDFLAGS→JSPI_LDFLAGS, kicadCollabFiberBusy→kicadCollabBusy (embind + web + tests), collab_common.h fiber*→apply*/coroutine naming, asyncifySignatures→ wasmTrapSignatures (lists byte-identical). Tests: the two remaining vacuous [wx-asyncify]/fiber-resume-refused asserts re-keyed to live JSPI beacons; eeschema-load's failure message no longer sends the developer to a deleted script; wait-beacons' dead families/parser deleted; lane-0 legacy-glue guards removed (lane 0 is unconstructible); the embind test.fail re-gated with the JSPI reason (plain embind invokers cannot suspend — verified still failing); lint-determinism now scans tests/jspi (166 files clean); eeschema-collab local-move gated to chromium (~50% flaky on FF even solo; pcbnew twin covers both engines). Docs: DEBUG.md rewritten as the JSPI debugging guide; build.md describes the single-phase build; docs/features/async/README.md banner-marked historical and repointed at the NEW 23-jspi-runtime.md (current architecture: export census, turnstile, libcontext ownership + refusal contract, embind call shapes, the em-pthread service-wrapper trick, exception policy, known gaps). Gates on the cleaned tree: test:e2e 725 passed / 0 failed (after the quiescence-probe fix; the 3 other reds were verified contention flakes solo-green or the documented FF gate), web 76/0, jspi 18/18 both engines, vitest 295/295 + 17/17, all lints green, live-app census clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016X9eh1s5sTx1o9Em9KBuwR
2026-08-14 09:25:32 +02:00
runOnCoroutine( aFrame, [aFrame]() { aFrame->GetToolManager()->RunAction( ACTIONS::undo ); } );
refactor(collab): dedup embind collab/presence layer into shared headers (collab_common.h + collab_presence_core.h) 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
2026-07-08 15:18:58 +02:00
return true;
}
/** Local undo stack depth — remote applies must not grow it (miss 09). */
inline int testUndoDepth( EDA_BASE_FRAME* aFrame )
{
return aFrame ? aFrame->GetUndoCommandCount() : -1;
}
} // namespace pcbjam_collab
#endif // __EMSCRIPTEN__