@bobfrankston/rmfmail 1.2.219 → 1.2.221

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 (33) hide show
  1. package/bin/mailx.js +93 -5
  2. package/bin/mailx.js.map +1 -1
  3. package/bin/mailx.ts +83 -5
  4. package/client/app.bundle.js +2 -2
  5. package/client/app.bundle.js.map +2 -2
  6. package/client/compose/compose.bundle.js +9 -3
  7. package/client/compose/compose.bundle.js.map +2 -2
  8. package/client/compose/compose.js +17 -1
  9. package/client/compose/compose.js.map +1 -1
  10. package/client/compose/compose.ts +15 -1
  11. package/client/lib/api-client.js +2 -2
  12. package/client/lib/api-client.js.map +1 -1
  13. package/client/lib/api-client.ts +2 -2
  14. package/client/lib/mailxapi.js +2 -2
  15. package/client/prewarm.html +16 -0
  16. package/package.json +5 -5
  17. package/packages/mailx-imap/index.d.ts +9 -0
  18. package/packages/mailx-imap/index.d.ts.map +1 -1
  19. package/packages/mailx-imap/index.js +55 -1
  20. package/packages/mailx-imap/index.js.map +1 -1
  21. package/packages/mailx-imap/index.ts +50 -1
  22. package/packages/mailx-imap/package-lock.json +2 -2
  23. package/packages/mailx-imap/package.json +1 -1
  24. package/packages/mailx-service/index.d.ts +1 -1
  25. package/packages/mailx-service/index.d.ts.map +1 -1
  26. package/packages/mailx-service/index.js +102 -26
  27. package/packages/mailx-service/index.js.map +1 -1
  28. package/packages/mailx-service/index.ts +89 -23
  29. package/packages/mailx-service/jsonrpc.js +1 -1
  30. package/packages/mailx-service/jsonrpc.js.map +1 -1
  31. package/packages/mailx-service/jsonrpc.ts +1 -1
  32. package/packages/mailx-service/package.json +1 -1
  33. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-24032 → node_modules.npmglobalize-stash-39780}/.package-lock.json +0 -0
package/bin/mailx.js CHANGED
@@ -2166,6 +2166,16 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2166
2166
  // main-window shutdown log line to prove/refute the "closing a popout
2167
2167
  // closes the main window" coupling (Bob 2026-07-06, unreproduced).
2168
2168
  let lastPopoutClose = null;
2169
+ /** Hidden window that keeps the "popout" WebView2 profile's browser
2170
+ * process warm — see the prewarm block below showService. Deliberately
2171
+ * NOT in popoutWindows: it is infrastructure, not a user window, and must
2172
+ * not show up in the shutdown popout census. */
2173
+ let prewarmHandle = null;
2174
+ /** How long after boot to warm the popout profile. Late enough that it
2175
+ * isn't racing the MAIN window's own WebView creation for the same CPU,
2176
+ * early enough to beat a user reaching for ^N (the 2026-08-05 case was at
2177
+ * boot + 18s). */
2178
+ const PREWARM_DELAY_MS = 6000;
2169
2179
  const popoutKey = (a, f, u) => `${a}|${f ?? ""}|${u}`;
2170
2180
  try {
2171
2181
  const { startPopoutServer } = await import("./popout-server.js");
@@ -2571,6 +2581,54 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2571
2581
  return { ok: true };
2572
2582
  });
2573
2583
  }
2584
+ // Pre-warm the "popout" WebView2 profile.
2585
+ //
2586
+ // Every secondary window (compose, message popout, extra main window) runs
2587
+ // on profile "popout" — deliberately NOT the main window's profile, so a
2588
+ // crash there can't black out the main window (S67). The cost is that the
2589
+ // FIRST popout of a session pays for cold-starting an entire WebView2
2590
+ // browser process for that profile, and if the daemon is busy with initial
2591
+ // sync at the time, that cold start is not cheap: ^N 18 seconds after boot
2592
+ // on 2026-08-05 sat on a blank window for 59.4 SECONDS, all of it inside
2593
+ // WebViewBuilder::build() before a line of compose JS could run (the page
2594
+ // itself then rendered in 397ms). Nothing the page can do about it — there
2595
+ // is no WebView yet to draw a spinner into.
2596
+ //
2597
+ // So pay it up front, off the user's critical path: one hidden window on
2598
+ // that profile, opened a few seconds after boot (late enough not to
2599
+ // compete with the main window's own WebView creation), held open for the
2600
+ // life of the daemon so the browser process stays warm. Hidden windows
2601
+ // still create their WebView, which is the whole point.
2602
+ if (popoutInfo) {
2603
+ const pi3 = popoutInfo;
2604
+ setTimeout(() => {
2605
+ const t0 = Date.now();
2606
+ try {
2607
+ const h = showMessageBoxEx({
2608
+ title: "rmfmail (prewarm)",
2609
+ url: `http://127.0.0.1:${pi3.port}/app/${pi3.token}/client/prewarm.html`,
2610
+ size: { width: 200, height: 100 },
2611
+ hidden: true,
2612
+ focusOnCreate: false,
2613
+ aumid: "com.frankston.rmfmail",
2614
+ profile: "popout",
2615
+ });
2616
+ prewarmHandle = h;
2617
+ if (typeof h.pid === "number")
2618
+ addChildPid(h.pid);
2619
+ console.log(` [prewarm] popout WebView2 profile warming (pid=${h.pid ?? "?"})`);
2620
+ void h.result.catch(() => { }).finally(() => {
2621
+ if (typeof h.pid === "number")
2622
+ removeChildPid(h.pid);
2623
+ console.log(` [prewarm] popout profile window closed after ${Date.now() - t0}ms`);
2624
+ });
2625
+ }
2626
+ catch (e) {
2627
+ // Never fatal — a cold first popout is slow, not broken.
2628
+ console.log(` [prewarm] skipped: ${e?.message || e}`);
2629
+ }
2630
+ }, PREWARM_DELAY_MS);
2631
+ }
2574
2632
  _tick("calling showService (WebView opens here)");
2575
2633
  const handle = showService({
2576
2634
  title: `rmfmail v${rootPkgVersion}`,
@@ -2656,6 +2714,12 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2656
2714
  handle.close();
2657
2715
  }
2658
2716
  catch { /* already gone */ }
2717
+ // Hidden and therefore invisible to the user if orphaned — close it on
2718
+ // every exit path, not just gracefulShutdown.
2719
+ try {
2720
+ prewarmHandle?.close?.();
2721
+ }
2722
+ catch { /* already gone */ }
2659
2723
  };
2660
2724
  process.once("exit", __cleanupInstance);
2661
2725
  process.once("SIGINT", () => { __cleanupInstance(); process.exit(0); });
@@ -2841,10 +2905,22 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2841
2905
  // and emits this when Word writes; compose.ts listens and reloads Quill.
2842
2906
  imapManager.on("wordEditUpdated", (payload) => {
2843
2907
  handle.send({ _event: "wordEditUpdated", type: "wordEditUpdated", ...payload });
2844
- // Bring compose back to the front after a Word save — the service's
2845
- // old mailx-host focusMainWindow call was a silent no-op (the helper
2846
- // never existed); this native msger command is the real mechanism.
2847
- handle.send({ _msgerWindow: "focus" });
2908
+ // Deliberately NO { _msgerWindow: "focus" } here. The autosave sidecar
2909
+ // saves every ~2s while the user types, and each save lands in this
2910
+ // handler so this line pulled the foreground off Word mid-sentence,
2911
+ // every couple of seconds, for the whole editing session (Bob
2912
+ // 2026-08-06: "keeps flipping back to rmfmail on any change"). The
2913
+ // focus now happens once, on wordEditClosed, below.
2914
+ });
2915
+ // Word (or the document) closed — the external edit is over. THIS is the
2916
+ // moment to bring rmfmail forward, replacing the old per-save focus grab.
2917
+ imapManager.on("wordEditClosed", (payload) => {
2918
+ handle.send({ _event: "wordEditClosed", type: "wordEditClosed", ...payload });
2919
+ // Only raise the MAIN window when the compose lives IN it. A popout
2920
+ // compose is a separate window with no service channel, so focusing
2921
+ // main would just cover it up.
2922
+ if (!payload.fromPopout)
2923
+ handle.send({ _msgerWindow: "focus" });
2848
2924
  });
2849
2925
  // Cloud-write/read failures from mailx-settings → push to UI as a banner so
2850
2926
  // silent fall-back-to-local can no longer swallow Drive errors.
@@ -3289,6 +3365,13 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
3289
3365
  return;
3290
3366
  shuttingDown = true;
3291
3367
  console.log(`${reason} — shutting down`);
3368
+ // The prewarm window is hidden, so an orphaned one is invisible AND
3369
+ // unkillable by the user — close it explicitly rather than relying on
3370
+ // child-process cascade.
3371
+ try {
3372
+ prewarmHandle?.close?.();
3373
+ }
3374
+ catch { /* already gone */ }
3292
3375
  imapManager.stopPeriodicSync();
3293
3376
  imapManager.stopOutboxWorker();
3294
3377
  // 3s hard timeout — don't hang on broken IMAP connections
@@ -3345,7 +3428,12 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
3345
3428
  const popoutDelta = lastPopoutClose
3346
3429
  ? `; last popout closed ${Date.now() - lastPopoutClose.at}ms ago (${lastPopoutClose.key})`
3347
3430
  : "; no popout closed this session";
3348
- await gracefulShutdown(`Window closed (main service child; ${popoutWindows.size} popout window(s) still tracked${popoutDelta})`);
3431
+ // Name the still-tracked popouts, not just the count. "2 popout window(s)
3432
+ // still tracked" (2026-08-05) was unactionable: two legitimately-open
3433
+ // compose windows and two entries whose close handler never fired look
3434
+ // identical from a bare count.
3435
+ const popoutKeys = popoutWindows.size > 0 ? ` [${[...popoutWindows.keys()].join(", ")}]` : "";
3436
+ await gracefulShutdown(`Window closed (main service child; ${popoutWindows.size} popout window(s) still tracked${popoutKeys}${popoutDelta})`);
3349
3437
  }
3350
3438
  main().catch(console.error);
3351
3439
  //# sourceMappingURL=mailx.js.map