@bobfrankston/mailx-settings 0.1.90 → 0.1.93

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/docs/allowlist.md CHANGED
@@ -80,5 +80,6 @@ how an approval is undone from the viewer.
80
80
  - Multi-client safe: each device's save merges with the cloud copy (set-union), so adds from any device propagate.
81
81
  - Use the message viewer's "allow remote content" button to add a sender/domain to `senders`/`domains` interactively.
82
82
  - Use the right-click menu in the viewer to flag a sender/domain — adds to `flaggedSenders`/`flaggedDomains`.
83
+ - A trusted, proved sender also vouches for its own links: the "a link hides where it goes" warning does not fire on its mail (a Toast receipt linking through Firebase's `toasttakeout.page.link`, 2026-09-11).
83
84
  - "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").
84
85
  - JSONC: `// line comments` and trailing commas are allowed.
package/index.d.ts CHANGED
@@ -235,6 +235,21 @@ export declare function loadAllowlistAsync(): Promise<typeof DEFAULT_ALLOWLIST>;
235
235
  */
236
236
  export declare function mergeAllowlists(local: any, cloud: any, removals?: Record<string, string[]>): any;
237
237
  export declare function saveAllowlist(list: typeof DEFAULT_ALLOWLIST, removals?: Record<string, string[]>): Promise<void>;
238
+ /** Crash-safe text write: tmp file → fsync → rename over the target.
239
+ *
240
+ * 2026-09-13 17:20 EDT — Claude Code (Fable 5.1), at Bob's direction.
241
+ * Added the fsync and exported this for every local-cache writer. Kernel-Power
242
+ * 41 crash at 16:36 today left `~/.rmfmail/accounts.jsonc` as 1932 NUL bytes:
243
+ * NTFS journals metadata (the new size) but not data, so a file rewritten
244
+ * moments before a hard crash comes back zero-filled. A rename alone does not
245
+ * close that window — the tmp file's data can be unflushed too — hence the
246
+ * fsync before the rename. The daemon then booted with 0 accounts. */
247
+ export declare function atomicWriteText(filePath: string, text: string): void;
248
+ /** Write `text` to `filePath` only when it differs from what is already there.
249
+ * Returns true when a write happened. Identical content is skipped so a
250
+ * periodic cloud poll doesn't keep the file in a perpetual just-written
251
+ * window (see atomicWriteText) and doesn't fire fs.watch for nothing. */
252
+ export declare function writeTextIfChanged(filePath: string, text: string): boolean;
238
253
  /** Load user-added dictionary words. Mirrored to GDrive so "Add to dictionary"
239
254
  * on one machine appears on every machine. Returns a deduped string array.
240
255
  * Tries the cloud copy first (so a fresh machine sees prior words), then
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
package/index.js CHANGED
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import * as fs from "node:fs";
19
19
  import * as path from "node:path";
20
- import { parse as parseJsonc } from "jsonc-parser";
20
+ import { parse as parseJsonc, printParseErrorCode } from "jsonc-parser";
21
21
  import { REMINDER_STATE_FILE, normalizeReminderState, mergeReminderStates, reminderStatesEqual } from "@bobfrankston/mailx-types";
22
22
  import { getCloudProvider, gDriveFindOrCreateFolder, gDriveValidateCachedFolder } from "./cloud.js";
23
23
  const __dirname = import.meta.dirname;
@@ -309,12 +309,23 @@ export async function cloudRead(filename) {
309
309
  console.log(` [cloud] ${filename} changed locally during the read — keeping the local copy`);
310
310
  }
311
311
  else {
312
- fs.mkdirSync(LOCAL_DIR, { recursive: true });
312
+ // 2026-09-13 17:20 EDT — Claude Code (Fable 5.1), at Bob's direction.
313
+ // Was an unconditional plain writeFileSync on EVERY cloud read —
314
+ // and pollCloud reads every 3 min — so accounts.jsonc was rewritten
315
+ // all day even when identical (the log showed "fs.watch fired but
316
+ // content unchanged" every 3 minutes). A kernel crash at 16:36
317
+ // today caught one of those rewrites and NTFS left the file
318
+ // zero-filled; the daemon then booted with 0 accounts. Now: skip
319
+ // when identical, and write crash-safe (tmp + fsync + rename).
313
320
  stashPrev(filename, content);
314
- fs.writeFileSync(path.join(LOCAL_DIR, filename), content);
321
+ writeTextIfChanged(path.join(LOCAL_DIR, filename), content);
315
322
  }
316
323
  }
317
- catch { /* ignore cache write failure */ }
324
+ catch (e) {
325
+ // The cloud content is still returned to the caller; only the local
326
+ // offline cache is stale, and the next read retries the write.
327
+ console.error(` [cloud] ${filename}: local cache write failed: ${e?.message || e}`);
328
+ }
318
329
  }
319
330
  // Don't set error for missing files — they may not exist yet (e.g., clients.jsonc on first run)
320
331
  return content;
@@ -493,19 +504,31 @@ function readJsonc(filePath) {
493
504
  return null;
494
505
  }
495
506
  try {
496
- return parseJsonc(fs.readFileSync(actual, "utf-8").replace(/\r/g, ""));
507
+ // 2026-09-13 17:20 EDT — Claude Code (Fable 5.1), at Bob's direction.
508
+ // jsonc-parser's parse() never throws: on garbage it returns undefined
509
+ // and only reports through the errors array. Without the array, the
510
+ // crash-zeroed accounts.jsonc (1932 NUL bytes) read as "no accounts"
511
+ // with not one log line — the daemon started with zero accounts and
512
+ // the only symptom was the "restart to apply" banner 14 s later.
513
+ const text = fs.readFileSync(actual, "utf-8").replace(/\r/g, "");
514
+ const errors = [];
515
+ const data = parseJsonc(text, errors);
516
+ if (errors.length > 0) {
517
+ const first = errors[0];
518
+ console.error(`Failed to parse ${actual}: ${printParseErrorCode(first.error)} at offset ${first.offset} (${errors.length} error(s), ${text.length} chars)`);
519
+ return null;
520
+ }
521
+ return data;
497
522
  }
498
523
  catch (e) {
499
524
  console.error(`Failed to read ${actual}: ${e.message}`);
500
525
  return null;
501
526
  }
502
527
  }
528
+ // 2026-09-13 17:20 EDT — Claude Code (Fable 5.1): one write mechanism; the
529
+ // fsync lives in atomicWriteText (see its note).
503
530
  function atomicWrite(filePath, data) {
504
- const dir = path.dirname(filePath);
505
- fs.mkdirSync(dir, { recursive: true });
506
- const tmp = filePath + ".tmp";
507
- fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
508
- fs.renameSync(tmp, filePath);
531
+ atomicWriteText(filePath, JSON.stringify(data, null, 2));
509
532
  }
510
533
  /** Read a config file: shared first, local fallback, merge local overrides */
511
534
  function loadFile(filename, defaults) {
@@ -827,8 +850,9 @@ export function loadAccounts() {
827
850
  // semantic change, and that triggered the spurious banner.
828
851
  const norm = (s) => s.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n").replace(/[ \t\r\n]+$/, "");
829
852
  if (norm(sharedContent) !== norm(localContent)) {
830
- fs.mkdirSync(LOCAL_DIR, { recursive: true });
831
- fs.writeFileSync(localPath, sharedContent);
853
+ // 2026-09-13 17:20 EDT — Claude Code (Fable 5.1): crash-safe
854
+ // write (tmp + fsync + rename); see atomicWriteText.
855
+ atomicWriteText(localPath, sharedContent);
832
856
  }
833
857
  }
834
858
  catch { /* ignore */ }
@@ -1318,13 +1342,46 @@ function buildDictCsv(words) {
1318
1342
  const sorted = [...new Set(words)].filter(Boolean).sort((a, b) => a.localeCompare(b));
1319
1343
  return "# rmfmail user dictionary — one word per line\n" + sorted.join("\n") + "\n";
1320
1344
  }
1321
- function atomicWriteText(filePath, text) {
1345
+ /** Crash-safe text write: tmp file → fsync → rename over the target.
1346
+ *
1347
+ * 2026-09-13 17:20 EDT — Claude Code (Fable 5.1), at Bob's direction.
1348
+ * Added the fsync and exported this for every local-cache writer. Kernel-Power
1349
+ * 41 crash at 16:36 today left `~/.rmfmail/accounts.jsonc` as 1932 NUL bytes:
1350
+ * NTFS journals metadata (the new size) but not data, so a file rewritten
1351
+ * moments before a hard crash comes back zero-filled. A rename alone does not
1352
+ * close that window — the tmp file's data can be unflushed too — hence the
1353
+ * fsync before the rename. The daemon then booted with 0 accounts. */
1354
+ export function atomicWriteText(filePath, text) {
1322
1355
  const dir = path.dirname(filePath);
1323
1356
  fs.mkdirSync(dir, { recursive: true });
1324
1357
  const tmp = filePath + ".tmp";
1325
- fs.writeFileSync(tmp, text);
1358
+ const fd = fs.openSync(tmp, "w");
1359
+ try {
1360
+ fs.writeSync(fd, text);
1361
+ fs.fsyncSync(fd);
1362
+ }
1363
+ finally {
1364
+ fs.closeSync(fd);
1365
+ }
1326
1366
  fs.renameSync(tmp, filePath);
1327
1367
  }
1368
+ /** Write `text` to `filePath` only when it differs from what is already there.
1369
+ * Returns true when a write happened. Identical content is skipped so a
1370
+ * periodic cloud poll doesn't keep the file in a perpetual just-written
1371
+ * window (see atomicWriteText) and doesn't fire fs.watch for nothing. */
1372
+ export function writeTextIfChanged(filePath, text) {
1373
+ let existing = null;
1374
+ try {
1375
+ existing = fs.readFileSync(filePath, "utf-8");
1376
+ }
1377
+ catch {
1378
+ existing = null; /* missing or unreadable → write it */
1379
+ }
1380
+ if (existing === text)
1381
+ return false;
1382
+ atomicWriteText(filePath, text);
1383
+ return true;
1384
+ }
1328
1385
  /** Load user-added dictionary words. Mirrored to GDrive so "Add to dictionary"
1329
1386
  * on one machine appears on every machine. Returns a deduped string array.
1330
1387
  * Tries the cloud copy first (so a fresh machine sees prior words), then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-settings",
3
- "version": "0.1.90",
3
+ "version": "0.1.93",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "license": "ISC",
19
19
  "dependencies": {
20
- "@bobfrankston/mailx-types": "^0.1.98",
20
+ "@bobfrankston/mailx-types": "^0.1.100",
21
21
  "jsonc-parser": "^3.3.1"
22
22
  },
23
23
  "repository": {
@@ -33,7 +33,7 @@
33
33
  },
34
34
  ".transformedSnapshot": {
35
35
  "dependencies": {
36
- "@bobfrankston/mailx-types": "^0.1.98",
36
+ "@bobfrankston/mailx-types": "^0.1.100",
37
37
  "jsonc-parser": "^3.3.1"
38
38
  }
39
39
  }