From 0e507a95d3357fc58267921b4c858e1be4666aa2 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 24 Apr 2026 14:57:36 -0400 Subject: [PATCH] Fix wire property persistence and enable server auto-reload Wire options (twisted pair, shielded, stripe, bundle) were not persisting because the backend server had stale Python modules loaded from before the WireUpdate schema was updated with these fields. Pydantic silently ignored unrecognized fields, so only pre-existing fields like color_stripe were saved. Frontend fixes applied alongside the schema work: - _patchWire now shows visible error on save failure instead of swallowing errors and falsely flashing Saved - Bundle dropdown rebuild is guarded with _updatingBundleDropdown flag to prevent spurious change events from triggering unwanted PATCHes - Enable uvicorn reload=True so server auto-restarts on code changes, preventing stale module issues in the future Co-Authored-By: Claude Sonnet 4.6 --- backend/run.py | 2 +- frontend/js/app.js | 28 ++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/backend/run.py b/backend/run.py index 655973b..4753965 100644 --- a/backend/run.py +++ b/backend/run.py @@ -1,4 +1,4 @@ import uvicorn if __name__ == "__main__": - uvicorn.run("app.main:app", host="127.0.0.1", port=8000, reload=False) + uvicorn.run("app.main:app", host="127.0.0.1", port=8000, reload=True) diff --git a/frontend/js/app.js b/frontend/js/app.js index b6cd3ed..b6b68c8 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -10,6 +10,7 @@ class WiringApp { this._preDrag = null; // snapshot captured at dragstart for undo this._octopartReady = null; // null=unknown, true/false after status check this._bundles = []; // wire bundles for current diagram + this._updatingBundleDropdown = false; this.canvas = new DiagramCanvas("canvas-container", { onDeviceSelected: (d) => this._showDeviceProps(d), @@ -62,11 +63,26 @@ class WiringApp { this._savedTimer = setTimeout(() => { const el = document.getElementById("saved-indicator"); if (!el) return; + el.textContent = "✓ Saved"; + el.style.color = ""; el.classList.add("show"); setTimeout(() => el.classList.remove("show"), 1400); }, 600); } + _flashError() { + clearTimeout(this._savedTimer); + const el = document.getElementById("saved-indicator"); + if (!el) return; + el.textContent = "✗ Save failed"; + el.style.color = "#e06c6c"; + el.classList.add("show"); + this._savedTimer = setTimeout(() => { + el.classList.remove("show"); + el.style.color = ""; + }, 3000); + } + // ── Undo ────────────────────────────────────────────────────────────────────── _pushUndo(fn) { @@ -599,6 +615,7 @@ class WiringApp { }); document.getElementById("wire-bundle-select")?.addEventListener("change", (e) => { + if (this._updatingBundleDropdown) return; const bundleId = e.target.value ? parseInt(e.target.value) : null; this._patchWire({ bundle_id: bundleId }, true); this._refreshBundleEditPanel(bundleId); @@ -708,8 +725,13 @@ class WiringApp { Object.assign(w, data); if (redraw) this.canvas.updateWire(w); } - await api.wires.update(id, data).catch(e => console.error("Wire save failed:", e)); - this._flashSaved(); + try { + await api.wires.update(id, data); + this._flashSaved(); + } catch (e) { + console.error("Wire save failed:", e); + this._flashError(); + } } // ── Diagram management ──────────────────────────────────────────────────────── @@ -1338,6 +1360,7 @@ class WiringApp { _refreshBundleDropdown(selectedId) { const sel = document.getElementById("wire-bundle-select"); if (!sel) return; + this._updatingBundleDropdown = true; sel.innerHTML = ''; this._bundles.forEach(b => { const opt = document.createElement("option"); @@ -1346,6 +1369,7 @@ class WiringApp { if (b.id === selectedId) opt.selected = true; sel.appendChild(opt); }); + this._updatingBundleDropdown = false; } _refreshBundleEditPanel(bundleId) {