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 c14e5e0e45b927dd4dbacabc06ba8b670b2b75d5
parent f801ebecd98f909249acb24c219cf1e5bed57e13
Author: Pi Agent <agent@pi.local>
Date:   Sat, 18 Jul 2026 12:52:35 +0200

review: warn on binary patches with optional raw display

Replace the bare 'Binary file — not reviewable inline.' note with a
binaryBlock that flags binary changes prominently (red left border) and
offers a Display/Hide raw patch toggle showing the verbatim diff text
sliced from the parsed body lines for that file.

Diffstat:
Mextension/review/review.css | 20++++++++++++++++++++
Mextension/review/review.js | 48++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 64 insertions(+), 4 deletions(-)
diff --git a/extension/review/review.css b/extension/review/review.css
@@ -232,6 +232,26 @@ button.primary:hover { color: #fff; opacity: 0.9; }
 
 .file .file-note { padding: 0.4rem 0.8rem; color: var(--muted); }
 
+.binary-note {
+  padding: 0.4rem 0.8rem 0.6rem;
+  color: var(--muted);
+  border-left: 3px solid #d1242f;
+}
+.binary-note .file-note { padding: 0; }
+.binary-note button { margin-top: 0.4rem; }
+.binary-note .binary-raw {
+  margin: 0.5rem 0 0;
+  padding: 0.5rem 0.7rem;
+  max-height: 16rem;
+  overflow: auto;
+  border: 1px solid var(--border);
+  border-radius: 6px;
+  background: var(--bg);
+  font: 11px/1.4 ui-monospace, monospace;
+  white-space: pre-wrap;
+  color: var(--muted);
+}
+
 table.diff {
   width: 100%;
   border-collapse: collapse;
diff --git a/extension/review/review.js b/extension/review/review.js
@@ -149,10 +149,7 @@ function renderPatch() {
     details.appendChild(summary);
 
     if (file.isBinary) {
-      const note = document.createElement("div");
-      note.className = "file-note";
-      note.textContent = "Binary file — not reviewable inline.";
-      details.appendChild(note);
+      details.appendChild(binaryBlock(file, parsed));
     } else {
       details.appendChild(renderFileTable(file, fileIndex));
     }
@@ -174,6 +171,49 @@ function fileLabel(file) {
   return file.displayPath;
 }
 
+function binaryBlock(file, parsed) {
+  const wrap = document.createElement("div");
+  wrap.className = "binary-note";
+
+  const note = document.createElement("div");
+  note.className = "file-note";
+  note.textContent =
+    "Binary file changed — cannot review line-by-line. The patch carries the encoded blob, not reviewable content.";
+  wrap.appendChild(note);
+
+  const show = document.createElement("button");
+  show.textContent = "Display raw patch";
+  show.addEventListener("click", () => {
+    if (wrap.querySelector("pre")) {
+      wrap.querySelector("pre").remove();
+      show.textContent = "Display raw patch";
+      return;
+    }
+    const pre = document.createElement("pre");
+    pre.className = "binary-raw";
+    pre.textContent = rawFilePatch(file, parsed);
+    show.before(pre);
+    show.textContent = "Hide raw patch";
+  });
+  wrap.appendChild(show);
+  return wrap;
+}
+
+function rawFilePatch(file, parsed) {
+  const lines = parsed.bodyLines || [];
+  const start = file.bodyLine ?? 0;
+  if (!lines.length) return "";
+  let end = lines.length;
+  for (let i = start + 1; i < lines.length; i++) {
+    if (/^diff --git /.test(lines[i])) { end = i; break; }
+  }
+  // Trim the trailing git signature if present.
+  for (let i = end - 1; i > start; i--) {
+    if (/^-- ?$/.test(lines[i])) { end = i; break; }
+  }
+  return lines.slice(start, end).join("\n").trimEnd();
+}
+
 function renderFileTable(file, fileIndex) {
   const table = document.createElement("table");
   table.className = "diff";