diff --git a/frontend/app.js b/frontend/app.js index df9e3c6..fd92129 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -1322,6 +1322,96 @@ function renderSelected() { el.netEditor.classList.add("hidden"); } +function issueById(issueId) { + const issues = [...(state.compile?.errors ?? []), ...(state.compile?.warnings ?? [])]; + return issues.find((i) => i.id === issueId) ?? null; +} + +function parseRefPinFromIssueMessage(issue) { + const m = /'([A-Za-z][A-Za-z0-9_]*)\.([A-Za-z0-9_]+)'/.exec(String(issue?.message ?? "")); + if (m) { + return { ref: m[1], pin: m[2] }; + } + return null; +} + +function ensureNet(name, netClass = "signal") { + if (!state.model) { + return null; + } + let net = netByName(name); + if (!net) { + net = { name, class: netClass, nodes: [] }; + state.model.nets.push(net); + } + return net; +} + +function issueFixAction(issue) { + if (!issue?.code) { + return null; + } + if (issue.code === "ground_net_missing") { + return { label: "Create GND Net", action: "create_ground" }; + } + if (issue.code === "floating_input") { + const rp = parseRefPinFromIssueMessage(issue); + if (rp) { + return { label: "Create Signal Net", action: "connect_signal", ref: rp.ref, pin: rp.pin }; + } + } + if (issue.code === "required_power_unconnected") { + const rp = parseRefPinFromIssueMessage(issue); + if (rp) { + return { label: "Connect Power", action: "connect_power", ref: rp.ref, pin: rp.pin }; + } + } + return null; +} + +async function applyIssueFix(issueId) { + if (!state.model) { + return; + } + const issue = issueById(issueId); + const fix = issueFixAction(issue); + if (!issue || !fix) { + el.jsonFeedback.textContent = "No automatic fix available for this issue."; + return; + } + + pushHistory("issue-fix"); + let applied = false; + + if (fix.action === "create_ground") { + ensureNet("GND", "ground"); + applied = true; + } else if (fix.action === "connect_signal") { + const name = normalizeNetName(`NET_${fix.ref}_${fix.pin}`); + applied = connectPinToNet(fix.ref, fix.pin, name || nextAutoNetName(), { netClass: "signal" }).ok; + } else if (fix.action === "connect_power") { + const upperPin = String(fix.pin).toUpperCase(); + if (upperPin.includes("GND")) { + applied = connectPinToNet(fix.ref, fix.pin, "GND", { netClass: "ground" }).ok; + } else if (netByName("3V3")) { + applied = connectPinToNet(fix.ref, fix.pin, "3V3", { netClass: "power" }).ok; + } else if (netByName("5V")) { + applied = connectPinToNet(fix.ref, fix.pin, "5V", { netClass: "power" }).ok; + } else { + applied = connectPinToNet(fix.ref, fix.pin, normalizeNetName(`PWR_${fix.ref}_${fix.pin}`), { netClass: "power" }).ok; + } + } + + if (!applied) { + el.jsonFeedback.textContent = "Automatic fix could not be applied safely."; + return; + } + + await compileModel(state.model, { keepView: true, source: "issue-fix" }); + el.jsonFeedback.textContent = `Applied fix for ${issue.code}.`; + focusIssue(issueId); +} + function renderIssues() { const errors = state.compile?.errors ?? []; const warnings = state.compile?.warnings ?? []; @@ -1333,12 +1423,16 @@ function renderIssues() { const rows = [ ...errors.map( - (issue) => - `