From b9b07616d846d08b33b4e5f467bc4626b76db1f0 Mon Sep 17 00:00:00 2001 From: makermelissa-piclaw Date: Wed, 6 May 2026 14:02:55 -0700 Subject: [PATCH] Fix Connect/Disconnect button behavior across all workflows Closes #373. Three related problems were causing the Connect/Disconnect button to get stuck in a wrong state when the underlying device went away: 1. The USB read loop / writer surfaced "NetworkError: The device has been lost." as unhandled promise rejections (usb.js:277, script.js:592 in the issue) when a board was unplugged or its USB grant was revoked. The read-loop catch path also called onDisconnected() with the default reconnect=true, which re-entered connect() on a now-dead device and triggered another NetworkError. - serialTransmit() now wraps the writer awaits in a single try/catch and treats any failure as a disconnect (reconnect=false) instead of letting the rejection escape. - The read loop's catch handler now passes reconnect=false. - onDisconnected() is now idempotent (guards against the gatt-style double-fire from "disconnect" event + read-loop rejection) and downgrades reconnect to false if any of the underlying close calls also throw NetworkError. 2. The Web workflow had its keepalive ping commented out and never reacted to websocket errors, so a silent network drop left the UI stuck on "Disconnect". - websocket.onerror now fires onDisconnected(event, false). - serialTransmit() bails early (and disconnects) if the socket is not in OPEN state. - The PING_INTERVAL_MS watchdog is re-enabled in onConnected(). 3. Clicking Connect after a disconnect always re-prompted for a workflow. - The most recently chosen backend is persisted in localStorage ("webeditor.lastBackend") whenever loadWorkflow() runs. - checkConnected() prefers that backend and goes straight to its showConnect() dialog. Each connect dialog now has a "Choose a different workflow" link that re-opens the workflow chooser, so the user is never trapped in the auto-selected workflow. The new chooser-back link is wired generically via Workflow._wireBack ToChooser(), called from each subclass's showConnect(); the markup lives in index.html with the .connect-back class and minimal styling in sass/layout/_layout.scss. --- index.html | 3 ++ js/script.js | 65 ++++++++++++++++++++++++++++++++++++++-- js/workflows/ble.js | 1 + js/workflows/usb.js | 65 +++++++++++++++++++++++++++++++++------- js/workflows/web.js | 40 ++++++++++++++++++++----- js/workflows/workflow.js | 30 +++++++++++++++++++ sass/layout/_layout.scss | 22 ++++++++++++++ 7 files changed, 205 insertions(+), 21 deletions(-) diff --git a/index.html b/index.html index e937f604..3d243ff3 100644 --- a/index.html +++ b/index.html @@ -224,6 +224,7 @@