From a0bbffe5c087f38b14aad9af2cf6c3d6f2522984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20T=C3=B6rcsv=C3=A1ri?= Date: Mon, 17 Aug 2026 12:33:21 +0200 Subject: [PATCH] fix(editor): via GetWidth layer fix + console tab/copy restore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - itemToJson: pass PADSTACK::ALL_LAYERS for vias — the layerless virtual PCB_VIA::GetWidth() is an assert trap since the padstack refactor, and the collab baseline/snapshot serializer hit it once per via per snapshot (big-board load = assert storm). Values were already correct; wire format unchanged (applyChanged's layerless SetWidth writes the same slot). - console: closed state is a content-width bottom-left tab again (version badge + app bottom edge visible); opened footer panel unchanged. - console: partial-selection copy works — wx's window-level keydown handler preventDefaults Ctrl/Cmd+C, so a capture-phase guard stops propagation to wx when the selection lives in the console; canvas mousedown collapses stale log selections so they can't steal the editor's own Ctrl+C. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HPtPBVLKQzaXTkirYgcVay --- wasm/bindings/pcbnew_embind.cpp | 8 +- web/standalone/src/components/WasmTool.tsx | 93 ++++++++++++++++------ 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/wasm/bindings/pcbnew_embind.cpp b/wasm/bindings/pcbnew_embind.cpp index 0349294..cceefe5 100644 --- a/wasm/bindings/pcbnew_embind.cpp +++ b/wasm/bindings/pcbnew_embind.cpp @@ -276,7 +276,13 @@ json itemToJson( BOARD_ITEM* aItem ) j["sy"] = tr->GetStart().y; j["ex"] = tr->GetEnd().x; j["ey"] = tr->GetEnd().y; - j["width"] = tr->GetWidth(); + + // The layerless PCB_VIA::GetWidth() is an assert trap (padstack refactor); pass the + // whole-stack slot instead. applyChanged's layerless SetWidth writes the same slot. + if( aItem->Type() == PCB_VIA_T ) + j["width"] = static_cast( aItem )->GetWidth( PADSTACK::ALL_LAYERS ); + else + j["width"] = tr->GetWidth(); } // Vias and zones reconstruct NATIVELY on `added` (the s-expr clipboard blob's `(kicad_pcb …)` diff --git a/web/standalone/src/components/WasmTool.tsx b/web/standalone/src/components/WasmTool.tsx index 043ede2..576c5c7 100644 --- a/web/standalone/src/components/WasmTool.tsx +++ b/web/standalone/src/components/WasmTool.tsx @@ -999,6 +999,7 @@ export function WasmTool({ const [status, setStatus] = React.useState("Loading tool…"); const [logs, setLogs] = React.useState([]); const [showLog, setShowLog] = React.useState(false); + const consolePanelRef = React.useRef(null); const [oomExhausted, setOomExhausted] = React.useState(false); // Terminal failure, rendered INDEPENDENTLY of `ready`. The boot overlay only // exists while `!ready`, so anything that killed the runtime after the editor @@ -1096,6 +1097,36 @@ export function WasmTool({ // exposes the style bridge, mounts the floating panel. const [tunerMod, setTunerMod] = React.useState(null); + // wx's window-level keydown handler forwards Ctrl/Cmd+C to the wasm app and + // preventDefaults it, so the browser's native "copy selection" never runs — + // log text could be selected but not copied. When the selection lives in the + // console panel, intercept the chord in the CAPTURE phase (ahead of wx's + // bubble-phase listener) and stop propagation; the default copy still fires. + // A canvas mousedown would normally collapse a selection, but wx + // preventDefaults that too — mirror it, or a stale log selection would keep + // stealing the editor's own Ctrl+C. + React.useEffect(() => { + const selectionInConsole = () => { + const sel = window.getSelection(); + if (!sel || sel.isCollapsed || !sel.anchorNode) return false; + return consolePanelRef.current?.contains(sel.anchorNode) ?? false; + }; + const onKeyDown = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "c" && selectionInConsole()) + e.stopPropagation(); + }; + const onPointerDown = (e: PointerEvent) => { + if (e.target instanceof HTMLCanvasElement && selectionInConsole()) + window.getSelection()?.removeAllRanges(); + }; + window.addEventListener("keydown", onKeyDown, true); + window.addEventListener("pointerdown", onPointerDown, true); + return () => { + window.removeEventListener("keydown", onKeyDown, true); + window.removeEventListener("pointerdown", onPointerDown, true); + }; + }, []); + const append = React.useCallback((msg: string) => { // Mirror into the React-independent fatal-screen ring: if React ever // unmounts itself on a crash, the DOM floor still has the full log. @@ -2481,33 +2512,47 @@ export function WasmTool({ must never end up underneath the thing reporting the failure. Forced visible on a fatal even with chrome hidden, for the same reason. */} {(!effectiveChromeHidden || fatal) && ( -
-
+ /* Closed: a content-width tab pinned bottom-left (no right-0), so the + version badge and the app's bottom edge stay visible/clickable. + Open: the full-width footer panel. */ +
+ {showLog ? ( + <> +
+ + +
+
+                {logs.join("\n")}
+              
+ + ) : ( - {showLog && ( - - )} -
- {showLog && ( -
-              {logs.join("\n")}
-            
)}
)}