@bobfrankston/mailx-imap 0.1.116 → 0.1.118

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.
Files changed (2) hide show
  1. package/index.js +24 -4
  2. package/package.json +3 -3
package/index.js CHANGED
@@ -2216,10 +2216,17 @@ export class ImapManager extends EventEmitter {
2216
2216
  // to dest), the (acc, folder, uid) lookup at fire time
2217
2217
  // won't match — skip. If still at original (truly gone
2218
2218
  // from server, not just moved), commit the delete.
2219
+ // Drafts/Sent never receive server-side Sieve moves — the long
2220
+ // grace only helps INBOX-class folders rebind moves. Worse, the
2221
+ // 30-min timer dies on every daemon restart and re-detection
2222
+ // restarts the clock, so on a restart-heavy day a sent-and-
2223
+ // deleted draft's local orphan NEVER got reaped (Bob 2026-07-13
2224
+ // "still getting sent messages left in drafts"). Short grace there.
2225
+ const quickReap = folder.specialUse === "drafts" || folder.specialUse === "sent" || folder.specialUse === "outbox";
2219
2226
  for (const uid of toDelete) {
2220
- this.scheduleDeferredReconcileDelete(accountId, folderId, uid, folder.path);
2227
+ this.scheduleDeferredReconcileDelete(accountId, folderId, uid, folder.path, quickReap ? 60_000 : undefined);
2221
2228
  }
2222
- console.log(` [reconcile-defer] ${accountId}/${folder.path}: scheduled ${toDelete.length} deletes (${ImapManager.RECONCILE_DELETE_GRACE_MS / 60_000}min grace for move-detect)`);
2229
+ console.log(` [reconcile-defer] ${accountId}/${folder.path}: scheduled ${toDelete.length} deletes (${quickReap ? "60s" : `${ImapManager.RECONCILE_DELETE_GRACE_MS / 60_000}min`} grace)`);
2223
2230
  }
2224
2231
  }
2225
2232
  catch (e) {
@@ -3303,7 +3310,7 @@ export class ImapManager extends EventEmitter {
3303
3310
  * in production — the grace expired and rows were committed-deleted
3304
3311
  * before _Spam / Sent / archive folders had a chance to rebind. */
3305
3312
  static RECONCILE_DELETE_GRACE_MS = 30 * 60_000;
3306
- scheduleDeferredReconcileDelete(accountId, folderId, uid, folderPath) {
3313
+ scheduleDeferredReconcileDelete(accountId, folderId, uid, folderPath, graceMs) {
3307
3314
  const key = `${accountId}:${folderId}:${uid}`;
3308
3315
  // If already pending, don't reset — the grace clock runs from the
3309
3316
  // FIRST detection so a flapping server can't keep deferring.
@@ -3341,7 +3348,7 @@ export class ImapManager extends EventEmitter {
3341
3348
  }
3342
3349
  this.db.recalcFolderCounts(folderId);
3343
3350
  this.emit("folderCountsChanged", accountId, {});
3344
- }, ImapManager.RECONCILE_DELETE_GRACE_MS);
3351
+ }, graceMs ?? ImapManager.RECONCILE_DELETE_GRACE_MS);
3345
3352
  this.deferredDeletes.set(key, t);
3346
3353
  }
3347
3354
  /** Delete local rows whose uid >= the server's UIDNEXT for this folder.
@@ -4501,9 +4508,11 @@ export class ImapManager extends EventEmitter {
4501
4508
  // seem to still leave it in draft so I don't know if it has been
4502
4509
  // sent or not. Once you hand it off it must leave draft."
4503
4510
  let localDeletedUid = 0;
4511
+ let localDeletedUuid;
4504
4512
  if (draftUid) {
4505
4513
  const existing = this.db.getMessageByUid(accountId, draftUid, drafts.id);
4506
4514
  if (existing) {
4515
+ localDeletedUuid = existing.uuid;
4507
4516
  this.unlinkBodyFile(accountId, draftUid, drafts.id).catch(() => { });
4508
4517
  this.db.deleteMessage(accountId, drafts.id, draftUid, "user sent the message (draft cleanup)", "mailx-imap deleteDraft (local)");
4509
4518
  localDeletedUid = draftUid;
@@ -4520,6 +4529,17 @@ export class ImapManager extends EventEmitter {
4520
4529
  if (localDeletedUid > 0) {
4521
4530
  this.db.recalcFolderCounts(drafts.id);
4522
4531
  this.emit("folderCountsChanged", accountId, {});
4532
+ // messageRemoved drives the list's remove-stale flow AND the
4533
+ // viewer clear. Without it, discarding a draft that was open in
4534
+ // the preview pane left it displayed after the row vanished
4535
+ // (Bob 2026-07-18: "I discarded a message but it is still
4536
+ // showing in preview"). Same shape the reconciler publishes on
4537
+ // server-side EXPUNGE.
4538
+ storeBus.publish({
4539
+ topic: localDeletedUuid ? `message:${localDeletedUuid}` : `folder:${drafts.id}`,
4540
+ kind: "messageRemoved",
4541
+ accountId, folderId: drafts.id, uid: localDeletedUid, msgUuid: localDeletedUuid,
4542
+ });
4523
4543
  }
4524
4544
  let succeeded = false;
4525
4545
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.116",
3
+ "version": "0.1.118",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -12,7 +12,7 @@
12
12
  "@bobfrankston/mailx-types": "^0.1.24",
13
13
  "@bobfrankston/mailx-settings": "^0.1.35",
14
14
  "@bobfrankston/mailx-store": "^0.1.58",
15
- "@bobfrankston/iflow-direct": "^0.1.56",
15
+ "@bobfrankston/iflow-direct": "^0.1.59",
16
16
  "@bobfrankston/tcp-transport": "^0.1.8",
17
17
  "@bobfrankston/smtp-direct": "^0.1.9",
18
18
  "@bobfrankston/mailx-sync": "^0.1.27",
@@ -40,7 +40,7 @@
40
40
  "@bobfrankston/mailx-types": "^0.1.24",
41
41
  "@bobfrankston/mailx-settings": "^0.1.35",
42
42
  "@bobfrankston/mailx-store": "^0.1.58",
43
- "@bobfrankston/iflow-direct": "^0.1.56",
43
+ "@bobfrankston/iflow-direct": "^0.1.59",
44
44
  "@bobfrankston/tcp-transport": "^0.1.8",
45
45
  "@bobfrankston/smtp-direct": "^0.1.9",
46
46
  "@bobfrankston/mailx-sync": "^0.1.27",