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");
|
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() {
|
function renderIssues() {
|
||||||
const errors = state.compile?.errors ?? [];
|
const errors = state.compile?.errors ?? [];
|
||||||
const warnings = state.compile?.warnings ?? [];
|
const warnings = state.compile?.warnings ?? [];
|
||||||
@ -1333,12 +1423,16 @@ function renderIssues() {
|
|||||||
|
|
||||||
const rows = [
|
const rows = [
|
||||||
...errors.map(
|
...errors.map(
|
||||||
(issue) =>
|
(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>`
|
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(
|
...warnings.map(
|
||||||
(issue) =>
|
(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>`
|
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) => {
|
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]");
|
const row = evt.target.closest("[data-issue-id]");
|
||||||
if (!row) {
|
if (!row) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -575,6 +575,15 @@ textarea {
|
|||||||
color: var(--ink-muted);
|
color: var(--ink-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.issueActions {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issueActions button {
|
||||||
|
font-size: 0.74rem;
|
||||||
|
padding: 3px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
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