thunderbird-patch-review

Simple email patch review tool for Thunderbird

git clone git://mccd.space/thunderbird-patch-review
commit d06b0c473b7b85908977d548d89ff6b5cd596d29
parent 9f8e985c2dbe55902916148a494d8b3a9c2d21db
Author: Pi Agent <agent@pi.local>
Date:   Mon, 20 Jul 2026 11:08:46 +0200

Add Refresh: retry glyph on the Apply warning, Refresh… in the dropdown

When the apply probe reports a warning state (dirty tree or conflict),
the Apply button is now repurposed as a retry instead of sitting
disabled: the existing ✖/⚠ marker is followed by a 🗘 glyph and
clicking the button re-runs refreshApplyState() rather than applying,
so the user can re-check after committing/stashing or resolving the
conflict without re-picking the repository. Modify-and-apply still
applies, so it stays disabled in those states; the "applied" success
state stays disabled (not a warning). A new Refresh… item is added to
the apply dropdown and wired to the same probe, bypassing the
data-mode dispatch so it works in any state.

Diffstat:
Mextension/review/review.html | 1+
Mextension/review/review.js | 44++++++++++++++++++++++++++++++++++----------
2 files changed, 35 insertions(+), 10 deletions(-)
diff --git a/extension/review/review.html b/extension/review/review.html
@@ -17,6 +17,7 @@
           <div class="menu" hidden>
             <button data-mode="modify">Modify and apply</button>
             <button data-mode="download">Download patchset…</button>
+            <button data-bypass="1" id="btn-refresh">Refresh…</button>
           </div>
         </div>
       </div>
diff --git a/extension/review/review.js b/extension/review/review.js
@@ -566,6 +566,14 @@ function setupSplit(splitId, onPick) {
 
 setupSplit("send-split", (mode) => openSendConfirm(mode));
 setupSplit("apply-split", applySeries);
+// Refresh re-runs the apply-state probe without applying. It bypasses
+// setupSplit's data-mode dispatch (no data-mode) and is wired here
+// directly so it works in any state, including the disabled-button
+// warning states where the user has just fixed the cause.
+$("#btn-refresh").addEventListener("click", () => {
+  closeMenus();
+  refreshApplyState();
+});
 $("#btn-send").addEventListener("click", () => openSendConfirm("send"));
 
 $("#btn-send-cancel").addEventListener("click", closeSendConfirm);
@@ -665,30 +673,46 @@ async function refreshApplyState() {
     if (modifyBtn) modifyBtn.disabled = true;
   } else if (state.applyStatus === "conflict") {
     apply.classList.add("state-conflict");
-    apply.disabled = true;
-    apply.textContent = "✖ Apply";
+    // Keep the button enabled: in a warning state its click re-runs the
+    // probe (see the btn-apply handler) instead of applying, so the user
+    // can retry after fixing the cause without re-picking the repo. The
+    // 🗘 glyph signals that. Modify still applies, so leave it disabled.
+    apply.disabled = false;
+    apply.textContent = "✖ Apply \u{1F5D9}";
     apply.title =
       "This series does not apply cleanly to the repository.\n\n" +
-      (response.output || "(no detail)");
+      (response.output || "(no detail)") +
+      "\n\nClick to re-check after resolving the conflict.";
     if (modifyBtn) modifyBtn.disabled = true;
   } else if (state.applyStatus === "dirty") {
     // The working tree has uncommitted tracked changes, so git am will
     // refuse before even reading the patches. Warn in yellow — distinct
     // from conflict (the patch content doesn't fit HEAD): this is a
-    // local, fixable condition. Leave Apply and Modify disabled; the
-    // tooltip tells the user to commit or stash and the next probe
-    // (after Browse re-pick or a successful apply elsewhere) re-checks.
+    // local, fixable condition. Leave the button enabled as a retry
+    // (click re-checks); Modify still applies, so it stays disabled.
     apply.classList.add("state-dirty");
-    apply.disabled = true;
-    apply.textContent = "⚠ Apply";
+    apply.disabled = false;
+    apply.textContent = "⚠ Apply \u{1F5D9}";
     apply.title =
       "The repository has uncommitted changes; git am will refuse until you commit or stash them.\n\n" +
-      (response.output || "(no detail)");
+      (response.output || "(no detail)") +
+      "\n\nClick to re-check after committing or stashing.";
     if (modifyBtn) modifyBtn.disabled = true;
   }
 }
 
-$("#btn-apply").addEventListener("click", () => applySeries("apply"));
+// In a warning state (dirty/conflict) the Apply button is repurposed as a
+// retry: clicking it re-runs the probe instead of applying, so the user can
+// refresh after fixing the cause (commit/stash, resolve the conflict) without
+// re-picking the repo. Otherwise it applies as normal. refreshApplyState()
+// is also reachable from the dropdown's "Refresh…" item.
+$("#btn-apply").addEventListener("click", () => {
+  if (state.applyStatus === "dirty" || state.applyStatus === "conflict") {
+    refreshApplyState();
+    return;
+  }
+  applySeries("apply");
+});
 
 document.addEventListener("click", (e) => {
   if (!e.target.closest(".split")) {