@bobfrankston/rmfmail 1.2.165 → 1.2.167

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 (30) hide show
  1. package/TODO.md +3 -0
  2. package/client/android-bootstrap.bundle.js +31 -9
  3. package/client/android-bootstrap.bundle.js.map +2 -2
  4. package/client/app.bundle.js +17 -0
  5. package/client/app.bundle.js.map +2 -2
  6. package/client/components/folder-tree.js +25 -1
  7. package/client/components/folder-tree.js.map +1 -1
  8. package/client/components/folder-tree.ts +21 -1
  9. package/client/compose/compose.bundle.js +8 -0
  10. package/client/compose/compose.bundle.js.map +2 -2
  11. package/client/compose/compose.js +14 -0
  12. package/client/compose/compose.js.map +1 -1
  13. package/client/compose/compose.ts +12 -0
  14. package/package.json +5 -5
  15. package/packages/mailx-imap/index.d.ts.map +1 -1
  16. package/packages/mailx-imap/index.js +32 -7
  17. package/packages/mailx-imap/index.js.map +1 -1
  18. package/packages/mailx-imap/index.ts +33 -7
  19. package/packages/mailx-imap/package-lock.json +2 -2
  20. package/packages/mailx-imap/package.json +1 -1
  21. package/packages/mailx-store-web/android-bootstrap.d.ts.map +1 -1
  22. package/packages/mailx-store-web/android-bootstrap.js +47 -10
  23. package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
  24. package/packages/mailx-store-web/android-bootstrap.ts +48 -10
  25. package/packages/mailx-store-web/package.json +1 -1
  26. package/packages/mailx-store-web/sync-manager.d.ts.map +1 -1
  27. package/packages/mailx-store-web/sync-manager.js +20 -5
  28. package/packages/mailx-store-web/sync-manager.js.map +1 -1
  29. package/packages/mailx-store-web/sync-manager.ts +21 -5
  30. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-52720 → node_modules.npmglobalize-stash-28172}/.package-lock.json +0 -0
package/TODO.md CHANGED
@@ -18,6 +18,9 @@ These items don't need user input — source-only changes, compile-verified befo
18
18
  | ~~4~~ | ~~**C122 — Recent-unread count overlay on taskbar icon**~~ — **DONE** (shipped ~v1.1.x 2026-05-29; TODO entry was stale). `updateBadge` in `client/app.ts` renders the red count pill on a 32×32 canvas (badge-only, transparent field) and pushes it via `mailxapi.setTaskbarOverlay`; hooked to `folderCountsChanged` + startup via `updateNewMessageCount`. Badge shows NEW-since-last-seen, matching the "recent-unread" spec. S65 (badge while not running) remains open/blocked. | S | |
19
19
  | ~~5~~ | ~~**C126 — Cold-start latency (~28s observed)**~~ — **MOSTLY DONE** (2026-05-11). Root cause IDed from boot log: 43 s of cascading ES module imports through msger's custom protocol IPC (one roundtrip per file). Fixes shipped: (a) bundling via esbuild (`bin/build-bundles.mjs`) collapses the cascade to one fetch per entry point — `client/app.bundle.js` 345 kb, `client/compose/compose.bundle.js` 99 kb; (b) boot-snapshot hydration — bundled app saves `folder-tree` + `ml-body` innerHTML to localStorage every 30 s; inline script in `index.html` restores at cold start before the bundle has fetched. Sub-bullet (c) [500 ms placeholder pause] not in current code at cited line — skipped as stale. Expected cold start now 3-5 s (full WebView2 + bundle parse), with near-instant *feel* via hydrated snapshot after the first run. | M | |
20
20
  | **6** | **C125 — Unify desktop + Android IMAP code paths** | M | Today's split (`mailx-imap` Node-only + `mailx-store-web` browser-only) duplicates orchestration that's protocol-identical. Differences boil down to (a) transport factory choice (b) whether clients are persistent — both parameters, not architectural divides. Plan: extract `mailx-imap-core` with ImapManager + ops queue + fast-lane + sync orchestration over a `Transport` and a `Storage` interface, no Node-specific imports. `mailx-imap` and `mailx-store-web` become thin shims that wire engine-specific Storage + Transport. `{persistent: true\|false}` flag on ImapManager covers the lifetime split (no fast-lane queue when ephemeral). Pre-req for Android non-Gmail IMAP without forking sync code. |
21
+ | **7** | **C156 — Android first-boot/setup progress narration** [M] | M | New-device install looked frozen (Bob 2026-07-22): no indication of "Signing into <gmail>…", "Fetching settings from Google Drive…", "Syncing INBOX (120/2000)…". Surface the android-bootstrap stage breadcrumbs (already vlog'd since v1.2.167) in the startup status area / folder-tree loading slot. |
22
+ | **8** | **C157 — Android fresh install: paint folder list before message sync** [S/M] | S/M | First sync ran minutes with an empty tree. Prioritize per-account folder-LIST fetch + paint ahead of message sync so the skeleton appears in seconds; message counts stream in behind. |
23
+ | **9** | **C158 — Android double-init after fold-triggered reload** [M] | M | 2026-07-22 13:07:49 logit trail: ONE page reload after unfolding produced TWO module loads, duplicate "bridge installed", duplicate GDrive lookups, and THREE "Starting OAuth flow" launches. Activity did NOT recreate (ConfigChanges already declared; no OnCreate in trail) — find what reloads the WebView on fold and make android-bootstrap init idempotent (single-flight guard on the module-level init, dedupe OAuth intent launches). |
21
24
  | **5** | **C124 — mailto handler on macOS** (Linux ✅ 2026-07-08 v1.2.115) | S | Linux half shipped: `--register-mailto` on linux writes `~/.local/share/applications/rmfmail.desktop` (`MimeType=x-scheme-handler/mailto;`, quoted node+mailx.js Exec, bare `%u`), runs `xdg-mime default` + `update-desktop-database`; `--unregister-mailto` removes it. **Remaining: macOS** — real `.app` bundle (LaunchServices won't register a bare script) with `Info.plist` `CFBundleURLTypes` declaring `mailto` + a tiny launcher binary; `LSSetDefaultHandlerForURLScheme` programmatically sets the default. Rust `rmfmailto-src/` crate already portable — drop the Win32 imports and `cargo build --target x86_64-apple-darwin` produces the macOS launcher. Needs a Mac to build/verify. |
22
25
  | ~~7~~ | ~~**C154 — Audit Android bridge vs MailxApi contract**~~ — **DONE** (2026-07-08, v1.2.115). Bridge now has a generic contract-backstop: every service method not explicitly adapted gets a positional pass-through, so a missing method can never again fail as `parent bridge has no method "X"`. Wire-shape mismatches adapted explicitly (`readJsoncFile`/`writeJsoncFile`/`formatJsonc`/`readConfigHelp` → `{content}`/`{ok}`, `drainStoreSync`/`syncFolderNow` → `{ok}`). Contract gained the missing entries (`getMessageSource`, `copyMessages`, `syncFolderNow`, `cancelServerSearch`, `getReminderSound`, `getCalendars`, optional `popoutWindow`) with web-service stubs per the policy (reads → graceful default, writes/OS → notImpl). Bonus fixes: bridge `getMessages` was dropping sort/sortDir/search and `searchMessages` was dropping scope/accountId/folderId — Android sort toggles and scoped search silently didn't work. | S | |
23
26
  ~~done — C120 read transport diagnostics~~ shipped v1.0.583 (per-folder timeout error now includes `[conn#X r=YB w=ZB writes=N sinceLastRead=Tms]` snapshot for the doomed socket).
@@ -10593,10 +10593,12 @@ var AndroidSyncManager = class {
10593
10593
  auth = { method: "XOAUTH2", user: smtpUser, token };
10594
10594
  }
10595
10595
  const parseAddrs = (s) => s.match(/[\w.+-]+@[\w.-]+/g) || [];
10596
- const toMatch = raw.match(/^To:\s*(.+)$/mi);
10597
- const ccMatch = raw.match(/^Cc:\s*(.+)$/mi);
10598
- const bccMatch = raw.match(/^Bcc:\s*(.+)$/mi);
10599
- const fromMatch = raw.match(/^From:\s*(.+)$/mi);
10596
+ const headerEnd = raw.search(/\r?\n\r?\n/);
10597
+ const headerSec = (headerEnd === -1 ? raw : raw.slice(0, headerEnd)).replace(/\r?\n[ \t]+/g, " ");
10598
+ const toMatch = headerSec.match(/^To:\s*(.+)$/mi);
10599
+ const ccMatch = headerSec.match(/^Cc:\s*(.+)$/mi);
10600
+ const bccMatch = headerSec.match(/^Bcc:\s*(.+)$/mi);
10601
+ const fromMatch = headerSec.match(/^From:\s*(.+)$/mi);
10600
10602
  const recipients = [
10601
10603
  ...toMatch ? parseAddrs(toMatch[1]) : [],
10602
10604
  ...ccMatch ? parseAddrs(ccMatch[1]) : [],
@@ -10604,7 +10606,8 @@ var AndroidSyncManager = class {
10604
10606
  ];
10605
10607
  const sender = fromMatch ? parseAddrs(fromMatch[1])[0] || account.email : account.email;
10606
10608
  if (recipients.length === 0) throw new Error("No recipients");
10607
- const rawToSend = raw.replace(/^Bcc:.*\r?\n/mi, "");
10609
+ const stripBcc = (h) => h.replace(/^Bcc:[^\n]*(\r?\n[ \t][^\n]*)*(\r?\n|$)/mi, "").replace(/\r?\n$/, "");
10610
+ const rawToSend = headerEnd === -1 ? stripBcc(raw) : stripBcc(raw.slice(0, headerEnd)) + raw.slice(headerEnd);
10608
10611
  const client = new SmtpClient({
10609
10612
  host: smtpHost,
10610
10613
  port: smtpPort,
@@ -11019,10 +11022,20 @@ async function initAndroid() {
11019
11022
  }, 1e3);
11020
11023
  if (gmailTokenProvider) {
11021
11024
  const tp = gmailTokenProvider;
11022
- (async () => {
11025
+ const RECONCILE_RETRY_MS = 3e4;
11026
+ const RECONCILE_MAX_TRIES = 5;
11027
+ let reconcileTry = 0;
11028
+ const reconcile = async () => {
11029
+ reconcileTry++;
11023
11030
  setGDriveTokenProvider(tp);
11024
11031
  try {
11025
- console.log("[android] Looking up GDrive .rmfmail folder\u2026");
11032
+ console.log(`[android] Looking up GDrive .rmfmail folder\u2026 (try ${reconcileTry}/${RECONCILE_MAX_TRIES})`);
11033
+ vlog(`gdrive-reconcile try ${reconcileTry}: token fetch`);
11034
+ await Promise.race([
11035
+ tp(),
11036
+ new Promise((_, rej) => setTimeout(() => rej(new Error("token fetch timed out (20s)")), 2e4))
11037
+ ]);
11038
+ vlog(`gdrive-reconcile try ${reconcileTry}: folder lookup`);
11026
11039
  const folder = await findGDriveMailxFolder(tp);
11027
11040
  if (!folder) {
11028
11041
  emitEvent({
@@ -11072,13 +11085,22 @@ async function initAndroid() {
11072
11085
  await registerDeviceInGDrive(tp, folderId, accounts.map((a) => a.id));
11073
11086
  }
11074
11087
  } catch (e) {
11088
+ vlog(`gdrive-reconcile try ${reconcileTry} FAILED: ${e.message}`);
11089
+ if (reconcileTry < RECONCILE_MAX_TRIES) {
11090
+ console.warn(`[android] GDrive reconcile failed (${e.message}) \u2014 retrying in ${RECONCILE_RETRY_MS / 1e3}s`);
11091
+ setTimeout(() => {
11092
+ void reconcile();
11093
+ }, RECONCILE_RETRY_MS);
11094
+ return;
11095
+ }
11075
11096
  emitEvent({
11076
11097
  type: "fatal",
11077
11098
  key: "gdrive-access-failed",
11078
- message: `GDrive access failed \u2014 app cannot start: ${e.message}`
11099
+ message: `GDrive access failed after ${RECONCILE_MAX_TRIES} attempts: ${e.message}. Mail from this device's local accounts still works; restart the app to retry cloud settings.`
11079
11100
  });
11080
11101
  }
11081
- })();
11102
+ };
11103
+ void reconcile();
11082
11104
  }
11083
11105
  service.loadContactsConfig().catch((e) => console.error(`[android] startup contacts.jsonc load: ${e?.message || e}`));
11084
11106
  for (const account of db.getAccounts()) {