diff --git a/frontend/src/components/editor/EditorToolbar.tsx b/frontend/src/components/editor/EditorToolbar.tsx index 52c7f01e..11a785ac 100644 --- a/frontend/src/components/editor/EditorToolbar.tsx +++ b/frontend/src/components/editor/EditorToolbar.tsx @@ -163,6 +163,10 @@ export const EditorToolbar = ({ setCompiling(true); setMessage(null); setConsoleOpen(true); + // Wipe the previous build's output before we append anything new. + // Issue #209: lingering logs from prior compiles made it impossible + // to tell the latest errors / warnings apart from stale ones. + setCompileLogs([]); trackCompileCode(); // ── Chip-program path ─────────────────────────────────────────────── @@ -380,6 +384,13 @@ export const EditorToolbar = ({ } else { const errText = result.error || result.stderr || 'Compile failed'; setMessage({ type: 'error', text: errText }); + // Issue #208: drop the previous successful program from this + // board so a subsequent Run cannot silently execute stale code + // that doesn't match the editor any more. The Run button gates + // on `!compiledProgram` and will refuse + force a re-compile. + if (activeBoardId) { + updateBoard(activeBoardId, { compiledProgram: null }); + } // Detect missing library errors — common patterns: // "No such file or directory" for #include, "fatal error: XXX.h" const looksLikeMissingLib = diff --git a/frontend/src/components/simulator/SimulatorCanvas.tsx b/frontend/src/components/simulator/SimulatorCanvas.tsx index 5b8d0321..7b064749 100644 --- a/frontend/src/components/simulator/SimulatorCanvas.tsx +++ b/frontend/src/components/simulator/SimulatorCanvas.tsx @@ -259,9 +259,16 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => { // their own state on click instead of opening the property dialog. // We treat board-less + un-paused as "interaction-running" so the same // gating logic that suppresses the dialog for MCU-running mode also - // covers board-less circuits. + // covers board-less circuits — BUT only when SPICE has actually + // engaged (submittedNetlist != ''). Without that extra check, deleting + // the only board on a normal Arduino+LED circuit dropped boards to 0 + // and silently flipped every remaining component to "running" mode, + // which suppresses the property dialog and makes them appear + // unresponsive to clicks (issue #211). const electricalPaused = useElectricalStore((s) => s.paused); - const interactionRunning = running || (boards.length === 0 && !electricalPaused); + const electricalEngaged = useElectricalStore((s) => s.submittedNetlist !== ''); + const interactionRunning = + running || (boards.length === 0 && electricalEngaged && !electricalPaused); // Refs that mirror state/props for use inside touch event closures // (touch listeners are added imperatively and can't access current React state) diff --git a/frontend/src/desktop/Esp32QemuPrompt.tsx b/frontend/src/desktop/Esp32QemuPrompt.tsx index fe746099..ece53183 100644 --- a/frontend/src/desktop/Esp32QemuPrompt.tsx +++ b/frontend/src/desktop/Esp32QemuPrompt.tsx @@ -104,7 +104,22 @@ export const Esp32QemuPrompt = () => { setStatus(fresh); if (fresh.installed) setOpen(false); } catch (e) { - setErr(e instanceof Error ? e.message : String(e)); + // Issue #212: the backend returns 404 when the velxio team + // hasn't published an ESP32 QEMU build for the user's platform + // yet (Windows / Linux x86_64 / macOS aarch64). The raw + // "download HTTP 404" string is opaque - reword to something + // the user can actually act on (or at least understand isn't + // their fault). + const raw = e instanceof Error ? e.message : String(e); + if (/HTTP\s*404|not found/i.test(raw)) { + setErr( + 'ESP32 support is not yet available for your platform. ' + + 'The Velxio team is preparing this build - try again in a ' + + 'few days, or use Arduino/RP2040 boards in the meantime.', + ); + } else { + setErr(raw); + } } finally { setInstalling(false); setProgress(null); diff --git a/frontend/src/desktop/menu.ts b/frontend/src/desktop/menu.ts index cb54f49a..4916e73e 100644 --- a/frontend/src/desktop/menu.ts +++ b/frontend/src/desktop/menu.ts @@ -26,6 +26,9 @@ import { listen } from './tauriBridge'; import { dlog } from './log'; import { triggerDownloadVlx, importVlxFile } from '../utils/vlxFile'; import { useSimulatorStore } from '../store/useSimulatorStore'; +import { useEditorStore } from '../store/useEditorStore'; +import { useProjectStore } from '../store/useProjectStore'; +import { useCompileLogsStore } from '../store/useCompileLogsStore'; import { switchLocale } from '../i18n/path'; import { LOCALES, type Locale } from '../i18n/config'; @@ -74,6 +77,8 @@ async function handle(action: MenuAction, payload?: MenuEventPayload): Promise 0 || + sim.components.length > 0 || + sim.wires.length > 0 || + editor.files.some((f) => f.modified) || + project.currentProject !== null; + + if (hasWork) { + // eslint-disable-next-line no-alert + const ok = window.confirm( + 'Start a new project? Any unsaved changes will be lost.', + ); + if (!ok) return; + } + + // Stop any running simulation first so workers / bridges shut down + // cleanly. Idempotent — no-op if nothing is running. + if (sim.running) { + sim.stopSimulation(); + } + + // Drop every board (also disconnects its bridges + removes wires + // touching it). Iterate over a snapshot copy since removeBoard + // mutates the array. + for (const board of [...sim.boards]) { + sim.removeBoard(board.id); + } + + // Any non-board components + wires that weren't connected to a + // board still need to go. + sim.setComponents([]); + sim.setWires([]); + + // Reset the editor to the default Blink sketch. loadFiles takes + // a {name, content}[] and rebuilds the file list, picking the + // first .ino as active. + editor.loadFiles([ + { + name: 'sketch.ino', + content: + '// Arduino Blink Example\nvoid setup() {\n pinMode(LED_BUILTIN, OUTPUT);\n}\n\nvoid loop() {\n digitalWrite(LED_BUILTIN, HIGH);\n delay(1000);\n digitalWrite(LED_BUILTIN, LOW);\n delay(1000);\n}\n', + }, + ]); + + // Drop project metadata so the next Save .vlx doesn't reuse the + // previous project's slug / name. + project.clearCurrentProject(); + + // Clear the compile output panel so old build logs don't carry over. + compileLogs.clear(); +} + async function checkForUpdates(): Promise { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any