@bobfrankston/rmfmail 1.2.312 → 1.2.313

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1381,22 +1381,35 @@ export async function showMessage(accountId: string, uid: number, folderId?: num
1381
1381
  // Offered from the chip's own advice, which is the only
1382
1382
  // thing an unflagged list post shows; the banner has its
1383
1383
  // own button.
1384
+ //
1385
+ // Offered on every proved post, trusted or not: the write
1386
+ // is a toggle, and once the list is trusted the chip and
1387
+ // banner are gone, so this menu is the only place an
1388
+ // approval can be undone without editing allowlist.jsonc
1389
+ // by hand (Bob 2026-09-09: "how would I undo an approval").
1384
1390
  const listSpam = (msg as any).spamScore as { listId?: string; listProved?: boolean; listTrusted?: boolean };
1385
- if (listSpam?.listProved && listSpam.listId && !listSpam.listTrusted) {
1391
+ if (listSpam?.listProved && listSpam.listId) {
1386
1392
  const listId = listSpam.listId;
1393
+ const untrust = !!listSpam.listTrusted;
1387
1394
  items.push({
1388
- label: `✓ Trust the ${listId} mailing list`,
1395
+ label: untrust ? `✗ Stop trusting the ${listId} mailing list` : `✓ Trust the ${listId} mailing list`,
1389
1396
  action: async () => {
1390
1397
  const st = document.getElementById("status-sync");
1391
1398
  try {
1392
- // The action only ever ADDS, so anything but
1393
- // `trusted: true` means the write did not land.
1399
+ // A toggle: the answer says which way it went,
1400
+ // and it must match what was asked for — a
1401
+ // mismatch means the write did not land.
1394
1402
  const res = await trustSenderOrDomain("list", listId);
1395
- if (!res?.trusted) throw new Error("the list did not save");
1396
- listSpam.listTrusted = true;
1397
- document.querySelector(".mv-spamscore")?.remove();
1398
- document.querySelector(".mv-trust")?.remove();
1399
- if (st) st.textContent = `Trusting the ${listId} mailing list`;
1403
+ if (!res || !!res.trusted === untrust) throw new Error("the list did not save");
1404
+ listSpam.listTrusted = res.trusted;
1405
+ if (res.trusted) {
1406
+ document.querySelector(".mv-spamscore")?.remove();
1407
+ document.querySelector(".mv-spamscore-action")?.remove();
1408
+ document.querySelector(".mv-trust")?.remove();
1409
+ }
1410
+ if (st) st.textContent = res.trusted
1411
+ ? `Trusting the ${listId} mailing list`
1412
+ : `No longer trusting the ${listId} mailing list — reopen the message to see its score again`;
1400
1413
  } catch (e: any) {
1401
1414
  // The reader chose an action and must know whether it took.
1402
1415
  if (st) {
@@ -1763,6 +1776,37 @@ export async function showMessage(accountId: string, uid: number, folderId?: num
1763
1776
  : "");
1764
1777
  chip.textContent = `spam score ${spam.score} of ${spam.threshold}`;
1765
1778
  bodyEl.appendChild(chip);
1779
+ // The action beside the warning it answers (Bob 2026-09-09: "it
1780
+ // would be nice to have an [approve this list] on the red warning
1781
+ // banner"). An unflagged list post has no banner — the chip IS
1782
+ // the warning — so the button sits next to it. Same write as the
1783
+ // From-menu item and the banner button: trustedLists by List-Id.
1784
+ if (spam.listProved && spam.listId && !spam.listTrusted) {
1785
+ const listId = spam.listId;
1786
+ const b = document.createElement("button");
1787
+ b.className = "mv-trust-action mv-spamscore-action";
1788
+ b.textContent = `Trust the ${listId} list`;
1789
+ b.title = `Delivered by the ${listId} mailing list, which your server proved sent it. Trusting the list hides this score on every post the list delivers.`;
1790
+ b.addEventListener("click", async () => {
1791
+ b.disabled = true;
1792
+ try {
1793
+ // Only ever ADDS, so anything but `trusted: true` means
1794
+ // the write did not land — never hide the chip on a
1795
+ // failed save.
1796
+ const res = await trustSenderOrDomain("list", listId);
1797
+ if (!res?.trusted) throw new Error("the list did not save");
1798
+ spam.listTrusted = true;
1799
+ chip.remove();
1800
+ b.remove();
1801
+ document.querySelector(".mv-trust")?.remove();
1802
+ } catch (e: any) {
1803
+ // The reader pressed a button and must know whether it took.
1804
+ b.disabled = false;
1805
+ b.textContent = `Could not save: ${e?.message || e}`;
1806
+ }
1807
+ });
1808
+ bodyEl.appendChild(b);
1809
+ }
1766
1810
  }
1767
1811
 
1768
1812
  const trust = ((msg as any).trust || []) as { id: string; severity: string; text: string; detail: string; via?: string; list?: string }[];
@@ -3055,3 +3055,6 @@ body.calendar-sidebar-on .calendar-sidebar { display: flex; }
3055
3055
  /* Scored by bulk-mail or blocklist rules with the sender proved — the number
3056
3056
  is real and stays visible, but it is not an identity finding. */
3057
3057
  .mv-spamscore-bulk { background: oklch(0.55 0.02 250); color: white; font-weight: 600; }
3058
+ /* "Trust the ‹list› list" beside the chip — an unflagged list post has no banner,
3059
+ so the chip is the warning and the action sits next to it (2026-09-09). */
3060
+ .mv-spamscore-action { margin: 0 0 var(--gap-sm) var(--gap-sm); vertical-align: baseline; }
package/docs/allowlist.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # allowlist.jsonc
2
2
 
3
- > **Owned by rmfmail. Do not edit this file your changes will be overwritten the next time the app starts.** This is reference documentation. To change allow-list data, edit `allowlist.jsonc` itself.
3
+ > **This documentation file (allowlist.md) is owned by rmfmaildo not edit it; it is rewritten every time the app starts.** The data file `allowlist.jsonc` next to it is yours: hand-editing it is fine and is one of the two ways to undo an approval (the other is the same viewer action again, see Notes).
4
4
 
5
5
  ## What this file documents
6
6
 
@@ -54,9 +54,11 @@ doing; a trusted list then quiets the spam note and hides the score chip. SPF an
54
54
  failures, and phishing or malware URL listings, are never silenced: a list member can post
55
55
  a bad link like anyone else.
56
56
 
57
- The viewer offers **"Trust the ‹List-Id› list"** under a spam-verdict banner, and
58
- **"Trust the ‹List-Id› mailing list"** in the From address's right-click menu (the chip's
59
- tooltip says so an unflagged post shows only the chip).
57
+ The viewer offers **"Trust the ‹List-Id› list"** under a spam-verdict banner and beside the
58
+ spam-score chip (an unflagged post shows only the chip), and **"Trust the ‹List-Id› mailing
59
+ list"** in the From address's right-click menu. Once the list is trusted the chip and banner
60
+ are gone, so the menu item becomes **"Stop trusting the ‹List-Id› mailing list"** — that is
61
+ how an approval is undone from the viewer.
60
62
 
61
63
  ## Notes
62
64
 
@@ -64,5 +66,5 @@ tooltip says so — an unflagged post shows only the chip).
64
66
  - Multi-client safe: each device's save merges with the cloud copy (set-union), so adds from any device propagate.
65
67
  - Use the message viewer's "allow remote content" button to add a sender/domain to `senders`/`domains` interactively.
66
68
  - Use the right-click menu in the viewer to flag a sender/domain — adds to `flaggedSenders`/`flaggedDomains`.
67
- - "Stop warning about …" under a spam-score note adds to `trustedSenders`/`trustedDomains`; "Trust … as an intermediary" adds to `trustedIntermediaries`; "Trust the … list" adds to `trustedLists`. Each is a toggle: the same action again removes the entry.
69
+ - "Stop warning about …" under a spam-score note adds to `trustedSenders`/`trustedDomains`; "Trust … as an intermediary" adds to `trustedIntermediaries`; "Trust the … list" adds to `trustedLists`. Each is a toggle: the same action again removes the entry. The banner buttons disappear with the banner once trust is written, so to undo a sender or intermediary approval edit `allowlist.jsonc` (remove the entry from its list); a list approval can also be undone from the From address's right-click menu ("Stop trusting the … mailing list").
68
70
  - JSONC: `// line comments` and trailing commas are allowed.
@@ -228,7 +228,7 @@ pass for the Sender:/bounce domain, or DKIM pass for the list domain; ISOC's Mai
228
228
  and the From-vs-path rules (`LIST_RELAY_RULES`) are neutral: the list is the explanation.
229
229
  Trusted + proved silences the verdict and hides the chip, like a trusted intermediary. SPF
230
230
  and DMARC failures and URL/phishing listings are never silenced — a list member can post a
231
- bad link like anyone else. Shipped v1.2.311; see `docs/allowlist.md`.
231
+ bad link like anyone else. Shipped v1.2.312; see `docs/allowlist.md`.
232
232
 
233
233
  ## Follow-ups, 2026-08-27 evening
234
234
 
package/npmchanges.md CHANGED
@@ -1712,3 +1712,45 @@ proof (DMARC pass, or DKIM_VALID_AU / ALL_TRUSTED per the existing proof
1712
1712
  logic); an unproved message with the same links stays flagged. mailx-types
1713
1713
  0.1.87.
1714
1714
 
1715
+ ## v1.2.312 — 2026-09-10
1716
+
1717
+ Trusted mailing lists: "how do I say that this is a valid mailing list message"
1718
+
1719
+ Bob 2026-09-09, on an IEEE-CS meeting announcement through MIT's Mailman.
1720
+ The list keeps the poster's own From (gmail.com publishes no DMARC policy,
1721
+ so Mailman does not rewrite it), re-sends from its own host with its own
1722
+ bounce address, and tags the subject and appends a footer — which breaks
1723
+ the gmail.com and mitprod signatures. iecc.com recorded both as dkim=fail,
1724
+ SpamAssassin fired POSSIBLE_GMAIL_PHISHER / FREEMAIL_FORGED_FROMDOMAIN /
1725
+ DKIM_ADSP_CUSTOM_MED, and the chip went red on a talk announcement. The
1726
+ author can never be proved on list mail, so per-sender trust had nothing
1727
+ to hold on to and the banner (correctly) offered no button.
1728
+
1729
+ The LIST is proved: spf=pass for the mit.edu bounce address, the same host
1730
+ as Sender:. New allowlist.jsonc `trustedLists`, keyed on List-Id — the one
1731
+ header identical on every post whoever wrote it. An entry counts only on a
1732
+ message the topmost Authentication-Results proves the list host sent
1733
+ (spf=pass for the Sender:/bounce domain, or dkim=pass for the list domain,
1734
+ which ISOC's Mailman adds). On a proved list, trusted or not, the author's
1735
+ DKIM failure and the From-vs-path rules (LIST_RELAY_RULES in trust.ts) are
1736
+ set aside as the list's doing; trusted + proved silences the verdict and
1737
+ hides the chip, exactly as a trusted intermediary does. SPF and DMARC
1738
+ failures and URL/phishing listings are never silenced — a list member can
1739
+ post a bad link like anyone else.
1740
+
1741
+ Actions: "Trust the ‹List-Id› list" under a spam-verdict banner; "Trust
1742
+ the ‹List-Id› mailing list" in the From address's right-click menu, which
1743
+ the chip's tooltip now points to (an unflagged post shows only the chip).
1744
+ trustSenderOrDomain gains type "list"; TRUST_LIST_KEYS in mailx-types is
1745
+ now the single table mapping trust type → allowlist key for the desktop
1746
+ service and the Android web-service, which each carried their own ternary.
1747
+ Android web-settings DEFAULT_ALLOWLIST gains trustedLists.
1748
+
1749
+ Tests: IEEE-CS headers verbatim (chip kind no longer forgery; trusted hides
1750
+ it; flagged variant cautions naming the list and the action, then goes
1751
+ quiet), ISOC (DKIM-proved list), an unproved List-Id (just a header), a
1752
+ phish listing and an SPF failure on a trusted list (never silenced).
1753
+ mailx-types 0.1.89, mailx-settings 0.1.82, mailx-store 0.1.119,
1754
+ mailx-service 0.1.35, mailx-store-web 0.1.111. docs/allowlist.md and
1755
+ docs/spam-false-positives.md updated; TODO C165.
1756
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/rmfmail",
3
- "version": "1.2.312",
3
+ "version": "1.2.313",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -34,8 +34,8 @@
34
34
  "dependencies": {
35
35
  "@bobfrankston/iflow-direct": "^0.1.68",
36
36
  "@bobfrankston/mailx-host": "^0.1.15",
37
- "@bobfrankston/mailx-imap": "^0.1.178",
38
- "@bobfrankston/mailx-store-web": "^0.1.111",
37
+ "@bobfrankston/mailx-imap": "^0.1.179",
38
+ "@bobfrankston/mailx-store-web": "^0.1.112",
39
39
  "@bobfrankston/mailx-sync": "^0.1.29",
40
40
  "@bobfrankston/miscinfo": "^1.0.21",
41
41
  "@bobfrankston/msger": "^0.1.431",
@@ -119,8 +119,8 @@
119
119
  "dependencies": {
120
120
  "@bobfrankston/iflow-direct": "^0.1.68",
121
121
  "@bobfrankston/mailx-host": "^0.1.15",
122
- "@bobfrankston/mailx-imap": "^0.1.178",
123
- "@bobfrankston/mailx-store-web": "^0.1.111",
122
+ "@bobfrankston/mailx-imap": "^0.1.179",
123
+ "@bobfrankston/mailx-store-web": "^0.1.112",
124
124
  "@bobfrankston/mailx-sync": "^0.1.29",
125
125
  "@bobfrankston/miscinfo": "^1.0.21",
126
126
  "@bobfrankston/msger": "^0.1.431",
@@ -1,6 +1,6 @@
1
1
  # allowlist.jsonc
2
2
 
3
- > **Owned by rmfmail. Do not edit this file your changes will be overwritten the next time the app starts.** This is reference documentation. To change allow-list data, edit `allowlist.jsonc` itself.
3
+ > **This documentation file (allowlist.md) is owned by rmfmaildo not edit it; it is rewritten every time the app starts.** The data file `allowlist.jsonc` next to it is yours: hand-editing it is fine and is one of the two ways to undo an approval (the other is the same viewer action again, see Notes).
4
4
 
5
5
  ## What this file documents
6
6
 
@@ -54,9 +54,11 @@ doing; a trusted list then quiets the spam note and hides the score chip. SPF an
54
54
  failures, and phishing or malware URL listings, are never silenced: a list member can post
55
55
  a bad link like anyone else.
56
56
 
57
- The viewer offers **"Trust the ‹List-Id› list"** under a spam-verdict banner, and
58
- **"Trust the ‹List-Id› mailing list"** in the From address's right-click menu (the chip's
59
- tooltip says so an unflagged post shows only the chip).
57
+ The viewer offers **"Trust the ‹List-Id› list"** under a spam-verdict banner and beside the
58
+ spam-score chip (an unflagged post shows only the chip), and **"Trust the ‹List-Id› mailing
59
+ list"** in the From address's right-click menu. Once the list is trusted the chip and banner
60
+ are gone, so the menu item becomes **"Stop trusting the ‹List-Id› mailing list"** — that is
61
+ how an approval is undone from the viewer.
60
62
 
61
63
  ## Notes
62
64
 
@@ -64,5 +66,5 @@ tooltip says so — an unflagged post shows only the chip).
64
66
  - Multi-client safe: each device's save merges with the cloud copy (set-union), so adds from any device propagate.
65
67
  - Use the message viewer's "allow remote content" button to add a sender/domain to `senders`/`domains` interactively.
66
68
  - Use the right-click menu in the viewer to flag a sender/domain — adds to `flaggedSenders`/`flaggedDomains`.
67
- - "Stop warning about …" under a spam-score note adds to `trustedSenders`/`trustedDomains`; "Trust … as an intermediary" adds to `trustedIntermediaries`; "Trust the … list" adds to `trustedLists`. Each is a toggle: the same action again removes the entry.
69
+ - "Stop warning about …" under a spam-score note adds to `trustedSenders`/`trustedDomains`; "Trust … as an intermediary" adds to `trustedIntermediaries`; "Trust the … list" adds to `trustedLists`. Each is a toggle: the same action again removes the entry. The banner buttons disappear with the banner once trust is written, so to undo a sender or intermediary approval edit `allowlist.jsonc` (remove the entry from its list); a list approval can also be undone from the From address's right-click menu ("Stop trusting the … mailing list").
68
70
  - JSONC: `// line comments` and trailing commas are allowed.