Add actionable diagnostics with one-click repair actions
This commit is contained in:
parent
486092e884
commit
72ea3609bb
108
frontend/app.js
108
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) =>
|
||||
`<div class="issueRow issueErr" data-issue-id="${issue.id}"><div class="issueTitle">[E] ${issue.message}</div><div class="issueMeta">${issue.code} · ${issue.path ?? "-"}</div><div class="issueMeta">${issue.suggestion ?? ""}</div></div>`
|
||||
(issue) => {
|
||||
const fix = issueFixAction(issue);
|
||||
return `<div class="issueRow issueErr" data-issue-id="${issue.id}"><div class="issueTitle">[E] ${issue.message}</div><div class="issueMeta">${issue.code} · ${issue.path ?? "-"}</div><div class="issueMeta">${issue.suggestion ?? ""}</div>${fix ? `<div class="issueActions"><button type="button" data-fix-issue="${issue.id}">${fix.label}</button></div>` : ""}</div>`;
|
||||
}
|
||||
),
|
||||
...warnings.map(
|
||||
(issue) =>
|
||||
`<div class="issueRow issueWarn" data-issue-id="${issue.id}"><div class="issueTitle">[W] ${issue.message}</div><div class="issueMeta">${issue.code} · ${issue.path ?? "-"}</div><div class="issueMeta">${issue.suggestion ?? ""}</div></div>`
|
||||
(issue) => {
|
||||
const fix = issueFixAction(issue);
|
||||
return `<div class="issueRow issueWarn" data-issue-id="${issue.id}"><div class="issueTitle">[W] ${issue.message}</div><div class="issueMeta">${issue.code} · ${issue.path ?? "-"}</div><div class="issueMeta">${issue.suggestion ?? ""}</div>${fix ? `<div class="issueActions"><button type="button" data-fix-issue="${issue.id}">${fix.label}</button></div>` : ""}</div>`;
|
||||
}
|
||||
)
|
||||
];
|
||||
|
||||
@ -2394,6 +2488,12 @@ function setupEvents() {
|
||||
});
|
||||
|
||||
el.issues.addEventListener("click", (evt) => {
|
||||
const fixBtn = evt.target.closest("[data-fix-issue]");
|
||||
if (fixBtn) {
|
||||
evt.stopPropagation();
|
||||
void applyIssueFix(fixBtn.getAttribute("data-fix-issue"));
|
||||
return;
|
||||
}
|
||||
const row = evt.target.closest("[data-issue-id]");
|
||||
if (!row) {
|
||||
return;
|
||||
|
||||
@ -575,6 +575,15 @@ textarea {
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.issueActions {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.issueActions button {
|
||||
font-size: 0.74rem;
|
||||
padding: 3px 8px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 175 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 174 KiB |
Loading…
Reference in New Issue
Block a user