@bobfrankston/mailx-imap 0.1.36 → 0.1.38

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 +51 -6
  2. package/package.json +9 -9
package/index.js CHANGED
@@ -3140,6 +3140,26 @@ export class ImapManager extends EventEmitter {
3140
3140
  }
3141
3141
  }
3142
3142
  const uid = typeof result === "number" ? result : result?.uid || null;
3143
+ // Insert a local DB row + body file immediately so the user's
3144
+ // Drafts folder shows the draft without waiting for the next
3145
+ // full Drafts sync. Bob 2026-05-12: "I started a reply which
3146
+ // I saved as a draft somewhere but I can't find it by looking
3147
+ // in drafts nor by searching." The IMAP APPEND just returned,
3148
+ // so we have both the source bytes and the real server UID —
3149
+ // same pattern copyToSent uses. recalcFolderCounts +
3150
+ // folderCountsChanged inside insertLocalRowFromSource bump
3151
+ // the Drafts badge as well.
3152
+ if (uid !== null && uid > 0) {
3153
+ try {
3154
+ const sourceStr = typeof rawMessage === "string"
3155
+ ? rawMessage
3156
+ : rawMessage.toString("utf-8");
3157
+ await this.insertLocalRowFromSource(accountId, drafts, uid, sourceStr, ["\\Draft", "\\Seen"]);
3158
+ }
3159
+ catch (e) {
3160
+ console.error(` [drafts] local-insert after APPEND failed: ${e?.message || e}`);
3161
+ }
3162
+ }
3143
3163
  return uid;
3144
3164
  });
3145
3165
  }
@@ -3152,6 +3172,34 @@ export class ImapManager extends EventEmitter {
3152
3172
  return;
3153
3173
  if (!draftUid && !draftId)
3154
3174
  return;
3175
+ // Local-first: drop the draft from the local DB FIRST so the user's
3176
+ // Drafts view reflects the Send click instantly. The IMAP delete is
3177
+ // queued for the background reconciler. Without this, the draft
3178
+ // stayed visible in Drafts until the IMAP round-trip completed (or
3179
+ // forever if it failed) — Bob 2026-05-12: "when I send a letter you
3180
+ // seem to still leave it in draft so I don't know if it has been
3181
+ // sent or not. Once you hand it off it must leave draft."
3182
+ let localDeletedUid = 0;
3183
+ if (draftUid) {
3184
+ const existing = this.db.getMessageByUid(accountId, draftUid, drafts.id);
3185
+ if (existing) {
3186
+ this.unlinkBodyFile(accountId, draftUid, drafts.id).catch(() => { });
3187
+ this.db.deleteMessage(accountId, draftUid, "user sent the message (draft cleanup)", "mailx-imap deleteDraft (local)");
3188
+ localDeletedUid = draftUid;
3189
+ }
3190
+ }
3191
+ if (draftId && !localDeletedUid) {
3192
+ // Look up any local draft rows by X-Mailx-Draft-ID. The DB
3193
+ // doesn't index that header directly, but locally-saved drafts
3194
+ // share a stable header set; rely on the UID path 99% of the
3195
+ // time. This branch covers the "draft was saved on another
3196
+ // device, came in via sync, no UID known yet" edge case — for
3197
+ // now the IMAP path below handles it.
3198
+ }
3199
+ if (localDeletedUid > 0) {
3200
+ this.db.recalcFolderCounts(drafts.id);
3201
+ this.emit("folderCountsChanged", accountId, {});
3202
+ }
3155
3203
  let succeeded = false;
3156
3204
  try {
3157
3205
  await this.withConnection(accountId, async (client) => {
@@ -3190,12 +3238,9 @@ export class ImapManager extends EventEmitter {
3190
3238
  console.error(` [drafts] withConnection failed for ${accountId}: ${e?.message || e}`);
3191
3239
  }
3192
3240
  // Reliable cleanup: if the inline attempt didn't actually delete the
3193
- // draft, queue a sync_action so the regular processSyncActions loop
3194
- // retries it later. Without this, transient IMAP failures left stale
3195
- // drafts in the folder ("draft droppings" that pile up after sends).
3196
- // Only the UID path can be queued — searchByHeader retries on every
3197
- // future call anyway, and queuing a header-lookup isn't a real
3198
- // sync_action shape.
3241
+ // draft on the server, queue a sync_action so the regular
3242
+ // processSyncActions loop retries it later. The local row is already
3243
+ // gone (above), so the user doesn't see stale UI either way.
3199
3244
  if (!succeeded && draftUid) {
3200
3245
  console.log(` [drafts] Queueing sync_action for retry: delete UID ${draftUid} in ${drafts.path}`);
3201
3246
  this.db.queueSyncAction(accountId, "delete", draftUid, drafts.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -9,10 +9,10 @@
9
9
  },
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
- "@bobfrankston/mailx-types": "^0.1.10",
13
- "@bobfrankston/mailx-settings": "^0.1.14",
14
- "@bobfrankston/mailx-store": "^0.1.18",
15
- "@bobfrankston/iflow-direct": "^0.1.40",
12
+ "@bobfrankston/mailx-types": "^0.1.11",
13
+ "@bobfrankston/mailx-settings": "^0.1.16",
14
+ "@bobfrankston/mailx-store": "^0.1.19",
15
+ "@bobfrankston/iflow-direct": "^0.1.41",
16
16
  "@bobfrankston/tcp-transport": "^0.1.6",
17
17
  "@bobfrankston/smtp-direct": "^0.1.8",
18
18
  "@bobfrankston/mailx-sync": "^0.1.16",
@@ -37,10 +37,10 @@
37
37
  },
38
38
  ".transformedSnapshot": {
39
39
  "dependencies": {
40
- "@bobfrankston/mailx-types": "^0.1.10",
41
- "@bobfrankston/mailx-settings": "^0.1.14",
42
- "@bobfrankston/mailx-store": "^0.1.18",
43
- "@bobfrankston/iflow-direct": "^0.1.40",
40
+ "@bobfrankston/mailx-types": "^0.1.11",
41
+ "@bobfrankston/mailx-settings": "^0.1.16",
42
+ "@bobfrankston/mailx-store": "^0.1.19",
43
+ "@bobfrankston/iflow-direct": "^0.1.41",
44
44
  "@bobfrankston/tcp-transport": "^0.1.6",
45
45
  "@bobfrankston/smtp-direct": "^0.1.8",
46
46
  "@bobfrankston/mailx-sync": "^0.1.16",