@bobfrankston/mailx-types 0.1.73 → 0.1.78

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.
package/index.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  export { CONTACT_RULES } from "./contact-rules.js";
7
7
  export { assessMessageTrust, spamScoreOf } from "./trust.js";
8
+ export { logIfChanged, clearLogState } from "./log-changes.js";
8
9
  export type { TrustFinding, TrustInput, SpamScore } from "./trust.js";
9
10
  export type { MailxApi } from "./mailx-api.js";
10
11
  export { addContactsDenylistEntry, addContactsPreferredEntry, type PreferredContactEntry, type CloudReadFn, type CloudWriteFn, } from "./contacts-config.js";
@@ -54,11 +55,17 @@ export interface AccountConfig {
54
55
  signature?: string; /** Legacy: HTML signature appended to all outgoing messages (new + reply + forward). Plain text or HTML allowed. Superseded by `sig`. */
55
56
  sig?: AccountSignature; /** Per-account signature object. Initially appended only to NEW messages; later options will cover replies/forwards. */
56
57
  }
57
- /** Signature configuration in accounts.jsonc. Initial shape carries `text`
58
- * only; `html: true` reserved for future support of raw HTML signatures. */
58
+ /** Signature configuration in accounts.jsonc. May be set per-account or once
59
+ * at the top level of the file, where it applies to every account that has
60
+ * no `sig` of its own. Appended by compose to new mail, replies and
61
+ * forwards; drafts already carry it in the saved body.
62
+ *
63
+ * 2026-09-02 — Claude Code (Opus 5), at Bob's direction: `html` is honoured
64
+ * (client/compose/compose.ts), not "reserved for future use" as this comment
65
+ * previously claimed. */
59
66
  export interface AccountSignature {
60
- text: string; /** Plain-text signature body. Newlines preserved. Appended to NEW messages with the standard "-- " RFC 3676 separator. */
61
- html?: boolean; /** Future flag: when true, `text` is treated as raw HTML rather than escaped plain text. Currently ignored. */
67
+ text: string; /** Signature body. Plain text unless `html` is set. Appended to NEW messages with the standard "-- " RFC 3676 separator. */
68
+ html?: boolean; /** When true, `text` is inserted as raw HTML write your own `<br>` for line breaks. When false/absent, `text` is HTML-escaped and newlines become `<br>`. */
62
69
  }
63
70
  /** Standard IMAP special-use folder types */
64
71
  export type SpecialUse = "inbox" | "sent" | "drafts" | "trash" | "junk" | "archive" | "all";
package/index.js CHANGED
@@ -8,6 +8,7 @@
8
8
  // junk-contact filtering on every platform.
9
9
  export { CONTACT_RULES } from "./contact-rules.js";
10
10
  export { assessMessageTrust, spamScoreOf } from "./trust.js";
11
+ export { logIfChanged, clearLogState } from "./log-changes.js";
11
12
  // Shared contacts.jsonc mutations — one implementation for desktop + Android.
12
13
  export { addContactsDenylistEntry, addContactsPreferredEntry, } from "./contacts-config.js";
13
14
  // Group-name expansion for recipient fields. Lets users type a group name
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Log a line only when it says something new.
3
+ *
4
+ * Sync walks ~90 folders every cycle, and a folder in a bad state produces the
5
+ * SAME line every time — "reconcile refused — would delete 250/250 (100%)"
6
+ * once per folder per cycle, forever. On Android each of those is an HTTP
7
+ * request to the log server, and in the file the real news is buried under
8
+ * hundreds of identical repeats (Bob 2026-08-29: "are we overlogging").
9
+ *
10
+ * Deleting the lines is the wrong fix — a refused reconcile is a genuine
11
+ * anomaly and the log is where it surfaces. What is worth reading is the
12
+ * CHANGE: the first time a folder enters that state, and every time the
13
+ * numbers move. So the same key + same text is dropped, a changed text is
14
+ * printed, and a state that persists is re-stated once an hour so a standing
15
+ * problem cannot become invisible.
16
+ *
17
+ * Keyed by caller-supplied string (account + folder + which check), because
18
+ * two folders in the same state are two facts, not one.
19
+ */
20
+ /**
21
+ * @param key identifies the thing being described — "acct/folder:reconcile"
22
+ * @param line the message; identical text for the same key is suppressed
23
+ * @param emit where to write (defaults to console.log; pass a logger in libraries)
24
+ * @returns whether the line was emitted, so callers can count what they skipped
25
+ */
26
+ export declare function logIfChanged(key: string, line: string, emit?: (msg: string) => void): boolean;
27
+ /** Forget a key — call when the condition clears, so its return is news again. */
28
+ export declare function clearLogState(key: string): void;
29
+ //# sourceMappingURL=log-changes.d.ts.map
package/log-changes.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Log a line only when it says something new.
3
+ *
4
+ * Sync walks ~90 folders every cycle, and a folder in a bad state produces the
5
+ * SAME line every time — "reconcile refused — would delete 250/250 (100%)"
6
+ * once per folder per cycle, forever. On Android each of those is an HTTP
7
+ * request to the log server, and in the file the real news is buried under
8
+ * hundreds of identical repeats (Bob 2026-08-29: "are we overlogging").
9
+ *
10
+ * Deleting the lines is the wrong fix — a refused reconcile is a genuine
11
+ * anomaly and the log is where it surfaces. What is worth reading is the
12
+ * CHANGE: the first time a folder enters that state, and every time the
13
+ * numbers move. So the same key + same text is dropped, a changed text is
14
+ * printed, and a state that persists is re-stated once an hour so a standing
15
+ * problem cannot become invisible.
16
+ *
17
+ * Keyed by caller-supplied string (account + folder + which check), because
18
+ * two folders in the same state are two facts, not one.
19
+ */
20
+ /** How long an unchanged line stays suppressed before being restated. */
21
+ const RESTATE_AFTER_MS = 60 * 60 * 1000;
22
+ const lastSeen = new Map();
23
+ /**
24
+ * @param key identifies the thing being described — "acct/folder:reconcile"
25
+ * @param line the message; identical text for the same key is suppressed
26
+ * @param emit where to write (defaults to console.log; pass a logger in libraries)
27
+ * @returns whether the line was emitted, so callers can count what they skipped
28
+ */
29
+ export function logIfChanged(key, line, emit = console.log) {
30
+ const now = Date.now();
31
+ const prev = lastSeen.get(key);
32
+ if (prev && prev.line === line && now - prev.at < RESTATE_AFTER_MS)
33
+ return false;
34
+ lastSeen.set(key, { line, at: now });
35
+ emit(prev && prev.line === line ? `${line} (unchanged for an hour)` : line);
36
+ return true;
37
+ }
38
+ /** Forget a key — call when the condition clears, so its return is news again. */
39
+ export function clearLogState(key) {
40
+ lastSeen.delete(key);
41
+ }
42
+ //# sourceMappingURL=log-changes.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-types",
3
- "version": "0.1.73",
3
+ "version": "0.1.78",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",