@bobfrankston/mailx 1.0.154 → 1.0.155

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/bin/mailx.js CHANGED
@@ -657,13 +657,10 @@ async function main() {
657
657
  // Pass server version to dispatch so getVersion returns it
658
658
  const rootPkg = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, "..", "package.json"), "utf-8"));
659
659
  handle.onRequest(async (req) => {
660
- if (verbose)
661
- console.error(`[ipc] ← ${req._action} (${req._cbid})`);
662
- req._version = rootPkg.version;
660
+ console.log(`[ipc] ← ${req._action} (${req._cbid})`);
663
661
  try {
664
662
  const response = await dispatch(svc, req);
665
- if (verbose)
666
- console.error(`[ipc] → ${req._action} (${req._cbid}) ok`);
663
+ console.log(`[ipc] → ${req._action} (${req._cbid}) ok`);
667
664
  handle.send(response);
668
665
  }
669
666
  catch (e) {
@@ -671,12 +668,34 @@ async function main() {
671
668
  handle.send({ _cbid: req._cbid, error: e.message });
672
669
  }
673
670
  });
674
- // Wire IMAP events → push to WebView
671
+ // Wire IMAP events → push to WebView (throttled to avoid flooding stdin)
672
+ let pendingSyncProgress = {};
673
+ let syncProgressTimer = null;
675
674
  imapManager.on("syncProgress", (accountId, phase, progress) => {
676
- handle.send({ _event: "syncProgress", type: "syncProgress", accountId, phase, progress });
675
+ pendingSyncProgress[accountId] = { phase, progress };
676
+ if (!syncProgressTimer) {
677
+ syncProgressTimer = setTimeout(() => {
678
+ syncProgressTimer = null;
679
+ for (const [id, p] of Object.entries(pendingSyncProgress)) {
680
+ handle.send({ _event: "syncProgress", type: "syncProgress", accountId: id, phase: p.phase, progress: p.progress });
681
+ }
682
+ pendingSyncProgress = {};
683
+ }, 500); // batch sync events every 500ms
684
+ }
677
685
  });
686
+ let pendingCounts = {};
687
+ let countsTimer = null;
678
688
  imapManager.on("folderCountsChanged", (accountId, counts) => {
679
- handle.send({ _event: "folderCountsChanged", type: "folderCountsChanged", accountId, counts });
689
+ pendingCounts[accountId] = counts;
690
+ if (!countsTimer) {
691
+ countsTimer = setTimeout(() => {
692
+ countsTimer = null;
693
+ for (const [id, c] of Object.entries(pendingCounts)) {
694
+ handle.send({ _event: "folderCountsChanged", type: "folderCountsChanged", accountId: id, ...c });
695
+ }
696
+ pendingCounts = {};
697
+ }, 1000); // batch count updates every 1s
698
+ }
680
699
  });
681
700
  imapManager.on("syncError", (accountId, error) => {
682
701
  handle.send({ _event: "error", type: "error", message: `${accountId}: ${error}` });
@@ -603,11 +603,16 @@ async function loadFolderTree(container) {
603
603
  setTimeout(() => overlay?.remove(), 400);
604
604
  }
605
605
  catch (e) {
606
- const errEl = document.createElement("div");
607
- errEl.className = "folder-loading";
608
- errEl.textContent = `Error loading folders: ${e.message}`;
609
- container.replaceChildren(errEl);
610
- // Dismiss overlay on error too — show the error, not a spinner
606
+ // Don't destroy existing folder tree on error — just log it
607
+ console.error(`Folder tree error: ${e.message}`);
608
+ // Only show error if tree is completely empty (first load failure)
609
+ if (container.children.length === 0 || container.querySelector(".folder-loading")) {
610
+ const errEl = document.createElement("div");
611
+ errEl.className = "folder-loading";
612
+ errEl.textContent = `Error loading folders: ${e.message}`;
613
+ container.replaceChildren(errEl);
614
+ }
615
+ // Dismiss overlay on error too
611
616
  const overlay = document.getElementById("startup-overlay");
612
617
  if (overlay) {
613
618
  const status = document.getElementById("startup-status");
package/client/index.html CHANGED
@@ -128,9 +128,9 @@
128
128
 
129
129
  <footer class="status-bar" id="status-bar">
130
130
  <span id="status-accounts"></span>
131
- <span id="status-sync"></span>
131
+ <span id="status-sync">Syncing...</span>
132
132
  <span id="status-pending"></span>
133
- <span id="status-queue">Queue: empty</span>
133
+ <span id="status-queue"></span>
134
134
  </footer>
135
135
 
136
136
  <div id="startup-overlay" class="startup-overlay">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.154",
3
+ "version": "1.0.155",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -23,7 +23,7 @@
23
23
  "@bobfrankston/iflow": "^1.0.53",
24
24
  "@bobfrankston/miscinfo": "^1.0.7",
25
25
  "@bobfrankston/oauthsupport": "^1.0.20",
26
- "@bobfrankston/msger": "^0.1.204",
26
+ "@bobfrankston/msger": "^0.1.205",
27
27
  "@capacitor/android": "^8.3.0",
28
28
  "@capacitor/cli": "^8.3.0",
29
29
  "@capacitor/core": "^8.3.0",
@@ -636,7 +636,8 @@ export class ImapManager extends EventEmitter {
636
636
  if (inbox) {
637
637
  try {
638
638
  client = await this.createClientWithLimit(accountId);
639
- await withTimeout(this.syncFolder(accountId, inbox.id, client), 60000, client, "Inbox sync");
639
+ const inboxTimeout = this.db.getHighestUid(accountId, inbox.id) === 0 ? 300000 : 60000;
640
+ await withTimeout(this.syncFolder(accountId, inbox.id, client), inboxTimeout, client, "Inbox sync");
640
641
  await client.logout();
641
642
  client = null;
642
643
  }
@@ -885,8 +886,10 @@ export class ImapManager extends EventEmitter {
885
886
  this.syncIntervals.set(`quick:${accountId}`, timer);
886
887
  console.log(` [periodic] ${accountId}: STATUS check every ${interval / 1000}s (${this.isOAuthAccount(accountId) ? "OAuth" : "password"})`);
887
888
  }
888
- // Sync actions (sends + flags/deletes/moves) every 30 seconds
889
+ // Sync actions (sends + flags/deletes/moves) every 30 seconds — skip during active sync
889
890
  const actionsInterval = setInterval(async () => {
891
+ if (this.syncing)
892
+ return;
890
893
  for (const [accountId] of this.configs) {
891
894
  this.processSendActions(accountId).catch(() => { });
892
895
  this.processSyncActions(accountId).catch(() => { });
@@ -1136,7 +1139,8 @@ export class ImapManager extends EventEmitter {
1136
1139
  async updateFlagsLocal(accountId, uid, folderId, flags) {
1137
1140
  this.db.updateMessageFlags(accountId, uid, flags);
1138
1141
  this.db.queueSyncAction(accountId, "flags", uid, folderId, { flags });
1139
- this.processSyncActions(accountId).catch(() => { });
1142
+ // Don't process immediately — let the 30s timer batch actions
1143
+ // (immediate processing during sync causes connection churn)
1140
1144
  }
1141
1145
  /** Process pending sync actions for an account */
1142
1146
  async processSyncActions(accountId) {