@bobfrankston/mailx 1.0.128 → 1.0.130

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/client/app.js CHANGED
@@ -116,6 +116,17 @@ alertDismiss?.addEventListener("click", hideAlert);
116
116
  // ── Wire up components ──
117
117
  const folderTree = document.getElementById("folder-tree");
118
118
  let currentFolderSpecialUse = "";
119
+ function clearViewer() {
120
+ const body = document.getElementById("mv-body");
121
+ const header = document.getElementById("mv-header");
122
+ const att = document.getElementById("mv-attachments");
123
+ if (body)
124
+ body.innerHTML = "";
125
+ if (header)
126
+ header.hidden = true;
127
+ if (att)
128
+ att.hidden = true;
129
+ }
119
130
  initFolderTree(folderTree, (accountId, folderId, folderName, specialUse) => {
120
131
  currentFolderSpecialUse = specialUse;
121
132
  currentAccountId = accountId;
@@ -123,11 +134,13 @@ initFolderTree(folderTree, (accountId, folderId, folderName, specialUse) => {
123
134
  if (searchInput)
124
135
  searchInput.value = "";
125
136
  markAsSeen();
137
+ clearViewer();
126
138
  loadMessages(accountId, folderId, 1, specialUse);
127
139
  setTitle(`mailx - ${folderName}`);
128
140
  }, () => {
129
141
  // Unified inbox handler
130
142
  currentFolderSpecialUse = "inbox";
143
+ clearViewer();
131
144
  loadUnifiedInbox();
132
145
  setTitle("mailx - All Inboxes");
133
146
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.128",
3
+ "version": "1.0.130",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -315,29 +315,30 @@ export class ImapManager extends EventEmitter {
315
315
  let messages;
316
316
  const firstSync = highestUid === 0;
317
317
  const historyDays = getHistoryDays(accountId);
318
- const startDate = historyDays > 0
319
- ? new Date(Date.now() - historyDays * 86400000)
318
+ // For first sync with unlimited history, start with 30 days — backfill extends later
319
+ const effectiveDays = (historyDays === 0 && firstSync) ? 30 : historyDays;
320
+ const startDate = effectiveDays > 0
321
+ ? new Date(Date.now() - effectiveDays * 86400000)
320
322
  : new Date(0);
321
323
  if (highestUid > 0) {
322
324
  // Incremental: fetch new messages — metadata only for speed, bodies on demand
323
325
  const fetched = await client.fetchMessagesSinceUid(folder.path, highestUid, { source: false });
324
326
  // Filter out the last known message (IMAP * always returns at least one)
325
327
  messages = fetched.filter(m => m.uid > highestUid);
326
- // Gap detection: compare DB count vs IMAP count to catch interrupted syncs
327
- const dbCount = this.db.getMessageCount(accountId, folderId);
328
- try {
329
- const imapCount = await client.getMessagesCount(folder.path);
330
- const gap = imapCount - dbCount - messages.length;
331
- if (gap > 0) {
332
- console.log(` ${folder.path}: gap detected IMAP has ${imapCount}, DB has ${dbCount}, ${messages.length} new → ${gap} missing`);
333
- // Full UID reconciliation: get all IMAP UIDs, find missing ones
334
- const existingUids = new Set(this.db.getUidsForFolder(accountId, folderId));
335
- const newUids = new Set(messages.map(m => m.uid));
336
- const allImapUids = await client.getUids(folder.path);
337
- const missingUids = allImapUids.filter(uid => !existingUids.has(uid) && !newUids.has(uid));
338
- if (missingUids.length > 0) {
339
- console.log(` ${folder.path}: reconciling ${missingUids.length} missing messages`);
340
- // Fetch in chunks to avoid huge commands
328
+ // Gap detection: check for missing UIDs within the range we've already synced
329
+ // Only reconcile between our lowest and highest UID — don't try to fetch the entire folder history
330
+ const existingUids = this.db.getUidsForFolder(accountId, folderId);
331
+ if (existingUids.length > 0) {
332
+ try {
333
+ const lowestUid = Math.min(...existingUids);
334
+ // Fetch UIDs in our known range from IMAP
335
+ const rangeUids = await client.getUids(folder.path);
336
+ const rangeInScope = rangeUids.filter((uid) => uid >= lowestUid && uid <= highestUid);
337
+ const existingSet = new Set(existingUids);
338
+ const newSet = new Set(messages.map(m => m.uid));
339
+ const missingUids = rangeInScope.filter((uid) => !existingSet.has(uid) && !newSet.has(uid));
340
+ if (missingUids.length > 0 && missingUids.length <= 5000) {
341
+ console.log(` ${folder.path}: gap detected — ${missingUids.length} missing UIDs in range ${lowestUid}..${highestUid}`);
341
342
  const chunkSize = 500;
342
343
  for (let i = 0; i < missingUids.length; i += chunkSize) {
343
344
  const chunk = missingUids.slice(i, i + chunkSize);
@@ -346,10 +347,13 @@ export class ImapManager extends EventEmitter {
346
347
  messages.push(...recovered);
347
348
  }
348
349
  }
350
+ else if (missingUids.length > 5000) {
351
+ console.log(` ${folder.path}: ${missingUids.length} missing UIDs — too many, skipping reconciliation (delete DB to force full re-sync)`);
352
+ }
353
+ }
354
+ catch (e) {
355
+ console.error(` ${folder.path}: gap detection failed: ${e.message}`);
349
356
  }
350
- }
351
- catch (e) {
352
- console.error(` ${folder.path}: gap detection failed: ${e.message}`);
353
357
  }
354
358
  // Backfill: if historyDays extends further back than our oldest message, fetch the gap
355
359
  const oldestDate = this.db.getOldestDate(accountId, folderId);