thunderbird-patch-review

Easy-to-use patch review interface for Thunderbird

Contribute: ~marcc/thunderbird-review-plugin@lists.sr.ht

git clone git://mccd.space/thunderbird-patch-review

commit 0280f8ffaa21e93d514b04a3cb29bade12bb28a3
parent 96828e747f84883fa26d6320ace855eb047be15d
Author: Pi Agent <agent@pi.local>
Date:   Sat, 18 Jul 2026 13:46:03 +0200

review: confirm before Send review; fold sourcehut status into the confirm dialog

Send review and its drop-down options (Send now / Preview in compose) now\nopen a confirmation modal summarizing how many patches have a draft\ncomment and what will happen on confirm. The sourcehut set-status\ncheckbox and dropdown move out of the top bar into that dialog, so the\nreviewer picks the patchset status only when actually committing to send.

Diffstat:
Mextension/review/review.css | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mextension/review/review.html | 23+++++++++++++++++------
Mextension/review/review.js | 42++++++++++++++++++++++++++++++++++++++++--
3 files changed, 110 insertions(+), 25 deletions(-)
diff --git a/extension/review/review.css b/extension/review/review.css
@@ -82,23 +82,6 @@ body {
   font-family: ui-monospace, monospace;
 }
 
-#srht-status {
-  display: flex;
-  align-items: center;
-  gap: 0.3rem;
-  color: var(--muted);
-  white-space: nowrap;
-}
-
-#srht-status select {
-  padding: 0.2rem 0.3rem;
-  border: 1px solid var(--border);
-  border-radius: 6px;
-  background: var(--bg);
-  color: var(--fg);
-  font: inherit;
-}
-
 button {
   padding: 0.3rem 0.8rem;
   border: 1px solid var(--border);
@@ -361,6 +344,59 @@ tr.comment-row td { padding: 0; }
   border-radius: 0 0 5px 5px;
 }
 
+#send-confirm {
+  position: fixed;
+  inset: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(0, 0, 0, 0.4);
+  z-index: 5;
+}
+
+#send-confirm .modal-card {
+  width: min(32rem, 90vw);
+  border: 1px solid var(--border);
+  border-radius: 8px;
+  background: var(--bg);
+  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
+  padding: 1.2rem 1.4rem;
+}
+
+#send-confirm h2 {
+  margin: 0 0 0.4rem;
+  font-size: 15px;
+}
+
+#send-confirm-summary {
+  margin: 0 0 0.8rem;
+  color: var(--muted);
+  font-size: 12px;
+}
+
+#send-confirm #srht-status {
+  display: flex;
+  align-items: center;
+  gap: 0.4rem;
+  margin: 0 0 0.8rem;
+  color: var(--muted);
+}
+
+#send-confirm #srht-status select {
+  padding: 0.2rem 0.3rem;
+  border: 1px solid var(--border);
+  border-radius: 6px;
+  background: var(--bg);
+  color: var(--fg);
+  font: inherit;
+}
+
+#send-confirm .modal-actions {
+  display: flex;
+  gap: 0.5rem;
+  justify-content: flex-end;
+}
+
 #status {
   position: fixed;
   inset: auto 1rem 1rem auto;
diff --git a/extension/review/review.html b/extension/review/review.html
@@ -20,12 +20,6 @@
           <button data-mode="download">Download patchset…</button>
         </div>
       </div>
-      <label id="srht-status" hidden
-             title="Sourcehut list detected: add an X-Sourcehut-Patchset-Update header to the review">
-        <input id="srht-enable" type="checkbox">
-        set status
-        <select id="srht-select"></select>
-      </label>
       <div class="split" id="send-split">
         <button id="btn-send" class="primary" title="Send one reply per commented patch">Send review</button>
         <button class="menu-toggle primary" title="Send options">â–¾</button>
@@ -40,6 +34,23 @@
 
   <main id="content"></main>
 
+  <div id="send-confirm" hidden>
+    <div class="modal-card">
+      <h2 id="send-confirm-title">Send review</h2>
+      <p id="send-confirm-summary"></p>
+      <label id="srht-status" hidden
+             title="Sourcehut list detected: add an X-Sourcehut-Patchset-Update header to the review">
+        <input id="srht-enable" type="checkbox">
+        set status
+        <select id="srht-select"></select>
+      </label>
+      <div class="modal-actions">
+        <button id="btn-send-cancel">Cancel</button>
+        <button id="btn-send-confirm" class="primary">Send review</button>
+      </div>
+    </div>
+  </div>
+
   <div id="status" hidden>
     <pre id="status-text"></pre>
     <div id="status-buttons">
diff --git a/extension/review/review.js b/extension/review/review.js
@@ -537,9 +537,47 @@ function setupSplit(splitId, onPick) {
   });
 }
 
-setupSplit("send-split", sendReview);
+setupSplit("send-split", (mode) => openSendConfirm(mode));
 setupSplit("apply-split", applySeries);
-$("#btn-send").addEventListener("click", () => sendReview("send"));
+$("#btn-send").addEventListener("click", () => openSendConfirm("send"));
+
+$("#btn-send-cancel").addEventListener("click", closeSendConfirm);
+$("#btn-send-confirm").addEventListener("click", async () => {
+  const mode = state.pendingSendMode;
+  closeSendConfirm();
+  await sendReview(mode);
+});
+
+function openSendConfirm(mode) {
+  closeMenus();
+  state.pendingSendMode = mode;
+  $("#send-confirm").addEventListener("click", onSendConfirmBackdrop);
+  const n = state.series.length;
+  const commented = state.series.filter((_, i) =>
+    Object.keys(state.comments[i]).length
+  ).length;
+  const verb = mode === "preview" ? "open in compose" : "send immediately";
+  $("#send-confirm-title").textContent = mode === "preview" ? "Preview review" : "Send review";
+  $("#btn-send-confirm").textContent = mode === "preview" ? "Open in compose" : "Send review";
+  $("#send-confirm-summary").textContent =
+    `${commented} of ${n} patch${n === 1 ? "" : "es"} has a draft comment; ` +
+    `clicking confirm will ${verb}.`;
+  $("#send-confirm").hidden = false;
+}
+
+function closeSendConfirm() {
+  $("#send-confirm").hidden = true;
+  state.pendingSendMode = null;
+  $("#send-confirm").removeEventListener("click", onSendConfirmBackdrop);
+}
+
+function onSendConfirmBackdrop(e) {
+  if (e.target === $("#send-confirm")) closeSendConfirm();
+}
+
+document.addEventListener("keydown", (e) => {
+  if (e.key === "Escape" && !$("#send-confirm").hidden) closeSendConfirm();
+});
 $("#btn-apply").addEventListener("click", () => applySeries("apply"));
 
 document.addEventListener("click", (e) => {