From 09eaf00c552562ccd3125e52d617b65f51d7bfc5 Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Sun, 14 Jun 2026 23:28:34 -0300 Subject: [PATCH] fix(canvas): don't treat the active board as a delete target on Delete/Backspace The canvas had two independent window keydown listeners. The wire handler removed the selected wire and returned, but that return cannot stop the separate component/board handler, which fell into 'else if (activeBoardId)' and popped the board-removal confirmation. activeBoardId is not a visual selection -- it is just the board whose code is open in the editor, so it is effectively always set. Pressing Delete to remove a wire (or after deleting a component) therefore always asked to remove the board. Remove the keyboard board-delete branch entirely: Delete/Backspace now only removes the selected component. Board removal stays on its deliberate paths (right-click Remove board, touch pin-picker delete). Also add the text-field guard (input/textarea/select/contenteditable) to the wire handler so Backspace while typing in the AI chat no longer deletes a selected wire. --- .../components/simulator/SimulatorCanvas.tsx | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/simulator/SimulatorCanvas.tsx b/frontend/src/components/simulator/SimulatorCanvas.tsx index e2682e13..1b5bd6ce 100644 --- a/frontend/src/components/simulator/SimulatorCanvas.tsx +++ b/frontend/src/components/simulator/SimulatorCanvas.tsx @@ -1222,12 +1222,12 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => { return () => cleanups.forEach((fn) => fn()); }, [components, wires, boards]); - // Handle keyboard delete for components and boards + // Handle keyboard delete for the selected component useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Skip when the user is typing in an input/textarea/contenteditable — // otherwise Backspace inside the AI chat (or any future text field) - // also asks to delete the active board. + // would also delete the selected component. const t = e.target as HTMLElement | null; if (t) { const tag = t.tagName; @@ -1240,15 +1240,20 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => { // Recorded so the user can Ctrl+Z this back. Cascades wire removal too. recordRemoveComponent(selectedComponentId); setSelectedComponentId(null); - } else if (activeBoardId) { - setBoardToRemove(activeBoardId); } + // The board is intentionally NOT deletable via Delete/Backspace. It is + // always the "active" board (its code is shown in the editor), so keying + // off activeBoardId here popped the board-removal confirmation whenever + // the user pressed Delete to remove a wire, or after they had just + // deleted a component. Board removal stays on the explicit, deliberate + // paths: the right-click "Remove board" context menu and the touch + // pin-picker delete action. } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); - }, [selectedComponentId, recordRemoveComponent, activeBoardId]); + }, [selectedComponentId, recordRemoveComponent]); // Handle component selection from modal const handleSelectComponent = (metadata: ComponentMetadata) => { @@ -1782,11 +1787,22 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => { // Keyboard handlers for wires useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { - // Escape → cancel in-progress wire + // Escape → cancel in-progress wire (works even while a field is focused). if (e.key === 'Escape' && wireInProgress) { cancelWireCreation(); return; } + // Skip the rest when the user is typing in an input/textarea/select/ + // contenteditable — otherwise Backspace in a text field (e.g. the AI chat) + // would delete the selected wire, and the color-shortcut keys below would + // hijack normal typing. + const t = e.target as HTMLElement | null; + if (t) { + const tag = t.tagName; + if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || t.isContentEditable) { + return; + } + } // Delete / Backspace → remove selected wire (recorded for undo). if ((e.key === 'Delete' || e.key === 'Backspace') && selectedWireId) { recordRemoveWire(selectedWireId);