From c02b14649e3fb9b88c9051662034c4e7a3ff4448 Mon Sep 17 00:00:00 2001 From: Rbanh Date: Wed, 18 Feb 2026 20:34:25 -0500 Subject: [PATCH] Add inline validation feedback for symbol pin row edits --- frontend/app.js | 48 +++++++++++++++++++++++++++++++++++++++++++-- frontend/index.html | 1 + frontend/styles.css | 9 +++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index 24d64e9..ec378a3 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -74,6 +74,7 @@ const el = { symbolHeightInput: document.getElementById("symbolHeightInput"), addSymbolPinBtn: document.getElementById("addSymbolPinBtn"), applySymbolBtn: document.getElementById("applySymbolBtn"), + symbolValidation: document.getElementById("symbolValidation"), symbolPinsList: document.getElementById("symbolPinsList"), pinMeta: document.getElementById("pinMeta"), pinNameInput: document.getElementById("pinNameInput"), @@ -880,10 +881,18 @@ function renderSymbolEditorForRef(ref) { el.symbolCategoryInput.value = String(sym.category ?? ""); el.symbolWidthInput.value = String(Number(sym.body?.width ?? 120)); el.symbolHeightInput.value = String(Number(sym.body?.height ?? 80)); + el.symbolValidation.textContent = ""; + el.symbolValidation.classList.remove("symbolValidationError"); el.symbolPinsList.innerHTML = (sym.pins ?? []).map((pin) => symbolPinRowHtml(pin)).join(""); el.symbolEditor.classList.remove("hidden"); } +function clearSymbolRowValidation(rows) { + for (const row of rows) { + row.classList.remove("invalidRow"); + } +} + function renderPinEditor() { if (!state.selectedPin || !state.model) { el.pinEditor.classList.add("hidden"); @@ -2284,6 +2293,17 @@ function setupEvents() { } }); + el.symbolPinsList.addEventListener("input", (evt) => { + const row = evt.target.closest(".symbolPinRow"); + if (row) { + row.classList.remove("invalidRow"); + } + if (el.symbolValidation.textContent) { + el.symbolValidation.textContent = ""; + el.symbolValidation.classList.remove("symbolValidationError"); + } + }); + el.applySymbolBtn.addEventListener("click", () => { if (!state.selectedRef) { return; @@ -2302,12 +2322,18 @@ function setupEvents() { } const rows = [...el.symbolPinsList.querySelectorAll(".symbolPinRow")]; + clearSymbolRowValidation(rows); + el.symbolValidation.textContent = ""; + el.symbolValidation.classList.remove("symbolValidationError"); if (!rows.length) { el.jsonFeedback.textContent = "Symbol must have at least one pin."; + el.symbolValidation.textContent = "Symbol must contain at least one pin row."; + el.symbolValidation.classList.add("symbolValidationError"); return; } const parsedPins = []; + const rowErrors = []; for (const row of rows) { const name = String(row.querySelector(".pinName")?.value ?? "").trim(); const number = String(row.querySelector(".pinNumber")?.value ?? "").trim(); @@ -2315,8 +2341,9 @@ function setupEvents() { const offset = Number(row.querySelector(".pinOffset")?.value ?? 0); const type = String(row.querySelector(".pinType")?.value ?? ""); if (!name || !number || !PIN_SIDES.includes(side) || !PIN_TYPES.includes(type) || !Number.isFinite(offset) || offset < 0) { - el.jsonFeedback.textContent = "Invalid symbol pin row values."; - return; + row.classList.add("invalidRow"); + rowErrors.push("Each pin row needs name, number, valid side/type, and offset >= 0."); + continue; } parsedPins.push({ oldName: row.getAttribute("data-old-pin") ?? name, @@ -2324,9 +2351,24 @@ function setupEvents() { }); } + if (rowErrors.length) { + el.jsonFeedback.textContent = "Fix invalid symbol pin rows before applying."; + el.symbolValidation.textContent = rowErrors[0]; + el.symbolValidation.classList.add("symbolValidationError"); + return; + } + const unique = new Set(parsedPins.map((p) => p.pin.name)); if (unique.size !== parsedPins.length) { el.jsonFeedback.textContent = "Duplicate pin names are not allowed."; + el.symbolValidation.textContent = "Duplicate pin names detected. Each pin name must be unique."; + el.symbolValidation.classList.add("symbolValidationError"); + for (const row of rows) { + const name = String(row.querySelector(".pinName")?.value ?? "").trim(); + if (name && parsedPins.filter((p) => p.pin.name === name).length > 1) { + row.classList.add("invalidRow"); + } + } return; } @@ -2368,6 +2410,8 @@ function setupEvents() { if (state.selectedPin && !pinExists(state.selectedPin.ref, state.selectedPin.pin)) { state.selectedPin = null; } + el.symbolValidation.textContent = ""; + el.symbolValidation.classList.remove("symbolValidationError"); const removedPinCount = [...beforePins].filter((p) => !allowedPins.has(p)).length; el.jsonFeedback.textContent = removedPinCount ? `Updated symbol ${inst.symbol}. Removed ${removedPinCount} pin mappings from nets/UI metadata.` diff --git a/frontend/index.html b/frontend/index.html index a183d6b..24134bb 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -108,6 +108,7 @@ +