← Commit history

demo: evidence is the FINAL frame only (in-progress shots showed the previous part and read as a wrong-part failure), and retry once when the bridge restarts KiCad to rebuild its cache

John Lauer ·1e7a5590c6 ·2mo ago ·parent dad2a9e
1 file changed +45−4
web-control-demo.html+45−4
@@ -828,8 +828,14 @@ for (const w of mine) {           var ss = d.stepShot || (d.result && d.result.stepShot);           if (ss && (ss.image || ss.base64)) {             c._bridgeShots = true;-            addShot(c.card, ss.image || ss.base64, "step: " + (ss.step || ss.title || d.stepLabel || "step"));-            clog(c, "  [proof] bridge evidence for \"" + (ss.step || ss.title || d.stepLabel) + "\"");+            // ⛔ Do NOT render an in-progress screenshot as evidence.+            // A step shot is the app MID-OPERATION, so it usually still shows the+            // PREVIOUS part - which reads as "you opened the wrong thing" even+            // when the final result is correct. John, 2026-08-20: "i keep+            // thinking you had it wrong, when possibly you didn't."+            // The step TEXT is kept because it is useful; only the misleading+            // picture is dropped. Evidence = the FINAL frame, nothing else.+            clog(c, "  step: " + (ss.step || ss.title || d.stepLabel || "step"));           }           // Log + snap only on CHANGE - a 2.5s poll of a 30s step used to spam           // the same line eight times.@@ -1009,7 +1015,8 @@ for (const w of mine) {             var key = (ss.step || "") + "|" + (ss.title || "") + "|" + String(img).length;             if (key !== c._lastShotKey) {               c._lastShotKey = key;-              addShot(c.card, img, "step: " + (ss.step || ss.title || "step"));+              // in-progress frame: log it, never render it (see note above)+              clog(c, "  step: " + (ss.step || ss.title || "step"));             }           }         }@@ -1258,7 +1265,8 @@ for (const w of mine) {             var key = (ss.step || "") + "|" + (ss.title || "") + "|" + String(img).length;             if (key !== c._lastShotKey) {               c._lastShotKey = key;-              addShot(c.card, img, "step: " + (ss.step || ss.title || label || "step"));+              // in-progress frame: log it, never render it (see note above)+              clog(c, "  step: " + (ss.step || ss.title || label || "step"));             }           }         }@@ -1606,6 +1614,39 @@ for (const w of mine) {             var jj = r.j || {};             var ok = r.s === 200 && jj.success !== false && !jj.errorCode &&               !(typeof jj.error === "string" && jj.error.trim() !== "");+            // A KiCad restart is a SUCCESSFUL remedy that reports itself as a+            // failure. The bridge notices its footprint cache predates the part,+            // restarts KiCad, rebuilds the cache - and then runs out of time+            // waiting for the instance it just started, so the caller is told+            // "failed" about work that has already been done. Measured: the same+            // part that failed this way passed on a later call with no+            // intervention.+            //+            // So retry ONCE. "This will never work" and "this works in ten+            // seconds" need opposite responses, and only one of them should be+            // reported to a human as a broken library.+            // Asked kicad-bridge#44 for a retryable errorCode + retryAfterMs so+            // this can key on a field instead of on prose.+            var msg = ((jj.error || "") + " " + (jj._hint || "")).toLowerCase();+            var restarted = !ok && /cache predated|restarted kicad|ran out of time waiting/.test(msg);+            if (restarted && !st._retried) {+              st._retried = true;+              clog(c, "  " + st.label + ": the bridge restarted KiCad to rebuild its cache and timed out waiting for it. That work is DONE, so retrying once ...");+              return new Promise(function (res2) { setTimeout(res2, 6000); }).then(function () {+                return postVerb(st.cmd, st.args()).then(function (r2) {+                  var j2 = r2.j || {};+                  var ok2 = r2.s === 200 && j2.success !== false && !j2.errorCode &&+                    !(typeof j2.error === "string" && j2.error.trim() !== "");+                  results.push({ label: st.label, ok: ok2, retried: true });+                  if (!ok2) { clog(c, "  " + st.label + " FAILED after the retry: " + (j2.error || j2.errorCode || ("HTTP " + r2.s))); return; }+                  clog(c, "  " + st.label + " opened on the retry (the restart had already fixed it)");+                  return settleThenCapture(c, "kicad_state").then(function (sr) {+                    if (!(sr.s === 200 && sr.j && appendPicked(c, sr.j, st.pick, st.label)))+                      clog(c, "  (" + st.label + " opened but no capture came back)");+                  });+                });+              });+            }             results.push({ label: st.label, ok: ok });             if (!ok) {               clog(c, "  " + st.label + " FAILED: " + (jj.error || jj.errorCode || ("HTTP " + r.s)));