@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.
- package/bin/mailx.js +93 -5
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +83 -5
- package/client/app.bundle.js +2 -2
- package/client/app.bundle.js.map +2 -2
- package/client/compose/compose.bundle.js +9 -3
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/compose/compose.js +17 -1
- package/client/compose/compose.js.map +1 -1
- package/client/compose/compose.ts +15 -1
- package/client/lib/api-client.js +2 -2
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +2 -2
- package/client/lib/mailxapi.js +2 -2
- package/client/prewarm.html +16 -0
- package/package.json +5 -5
- package/packages/mailx-imap/index.d.ts +9 -0
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +55 -1
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +50 -1
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts +1 -1
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +102 -26
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +89 -23
- package/packages/mailx-service/jsonrpc.js +1 -1
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +1 -1
- package/packages/mailx-service/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-24032 → node_modules.npmglobalize-stash-39780}/.package-lock.json +0 -0
package/bin/mailx.ts
CHANGED
|
@@ -2095,6 +2095,16 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2095
2095
|
// main-window shutdown log line to prove/refute the "closing a popout
|
|
2096
2096
|
// closes the main window" coupling (Bob 2026-07-06, unreproduced).
|
|
2097
2097
|
let lastPopoutClose: { at: number; key: string } | null = null;
|
|
2098
|
+
/** Hidden window that keeps the "popout" WebView2 profile's browser
|
|
2099
|
+
* process warm — see the prewarm block below showService. Deliberately
|
|
2100
|
+
* NOT in popoutWindows: it is infrastructure, not a user window, and must
|
|
2101
|
+
* not show up in the shutdown popout census. */
|
|
2102
|
+
let prewarmHandle: ReturnType<typeof showMessageBoxEx> | null = null;
|
|
2103
|
+
/** How long after boot to warm the popout profile. Late enough that it
|
|
2104
|
+
* isn't racing the MAIN window's own WebView creation for the same CPU,
|
|
2105
|
+
* early enough to beat a user reaching for ^N (the 2026-08-05 case was at
|
|
2106
|
+
* boot + 18s). */
|
|
2107
|
+
const PREWARM_DELAY_MS = 6000;
|
|
2098
2108
|
const popoutKey = (a: string, f: number | undefined, u: number): string => `${a}|${f ?? ""}|${u}`;
|
|
2099
2109
|
try {
|
|
2100
2110
|
const { startPopoutServer } = await import("./popout-server.js");
|
|
@@ -2473,6 +2483,51 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2473
2483
|
return { ok: true };
|
|
2474
2484
|
});
|
|
2475
2485
|
}
|
|
2486
|
+
// Pre-warm the "popout" WebView2 profile.
|
|
2487
|
+
//
|
|
2488
|
+
// Every secondary window (compose, message popout, extra main window) runs
|
|
2489
|
+
// on profile "popout" — deliberately NOT the main window's profile, so a
|
|
2490
|
+
// crash there can't black out the main window (S67). The cost is that the
|
|
2491
|
+
// FIRST popout of a session pays for cold-starting an entire WebView2
|
|
2492
|
+
// browser process for that profile, and if the daemon is busy with initial
|
|
2493
|
+
// sync at the time, that cold start is not cheap: ^N 18 seconds after boot
|
|
2494
|
+
// on 2026-08-05 sat on a blank window for 59.4 SECONDS, all of it inside
|
|
2495
|
+
// WebViewBuilder::build() before a line of compose JS could run (the page
|
|
2496
|
+
// itself then rendered in 397ms). Nothing the page can do about it — there
|
|
2497
|
+
// is no WebView yet to draw a spinner into.
|
|
2498
|
+
//
|
|
2499
|
+
// So pay it up front, off the user's critical path: one hidden window on
|
|
2500
|
+
// that profile, opened a few seconds after boot (late enough not to
|
|
2501
|
+
// compete with the main window's own WebView creation), held open for the
|
|
2502
|
+
// life of the daemon so the browser process stays warm. Hidden windows
|
|
2503
|
+
// still create their WebView, which is the whole point.
|
|
2504
|
+
if (popoutInfo) {
|
|
2505
|
+
const pi3 = popoutInfo;
|
|
2506
|
+
setTimeout(() => {
|
|
2507
|
+
const t0 = Date.now();
|
|
2508
|
+
try {
|
|
2509
|
+
const h = showMessageBoxEx({
|
|
2510
|
+
title: "rmfmail (prewarm)",
|
|
2511
|
+
url: `http://127.0.0.1:${pi3.port}/app/${pi3.token}/client/prewarm.html`,
|
|
2512
|
+
size: { width: 200, height: 100 },
|
|
2513
|
+
hidden: true,
|
|
2514
|
+
focusOnCreate: false,
|
|
2515
|
+
aumid: "com.frankston.rmfmail",
|
|
2516
|
+
profile: "popout",
|
|
2517
|
+
});
|
|
2518
|
+
prewarmHandle = h;
|
|
2519
|
+
if (typeof h.pid === "number") addChildPid(h.pid);
|
|
2520
|
+
console.log(` [prewarm] popout WebView2 profile warming (pid=${h.pid ?? "?"})`);
|
|
2521
|
+
void h.result.catch(() => { /* closed with the daemon */ }).finally(() => {
|
|
2522
|
+
if (typeof h.pid === "number") removeChildPid(h.pid);
|
|
2523
|
+
console.log(` [prewarm] popout profile window closed after ${Date.now() - t0}ms`);
|
|
2524
|
+
});
|
|
2525
|
+
} catch (e: any) {
|
|
2526
|
+
// Never fatal — a cold first popout is slow, not broken.
|
|
2527
|
+
console.log(` [prewarm] skipped: ${e?.message || e}`);
|
|
2528
|
+
}
|
|
2529
|
+
}, PREWARM_DELAY_MS);
|
|
2530
|
+
}
|
|
2476
2531
|
_tick("calling showService (WebView opens here)");
|
|
2477
2532
|
const handle = showService({
|
|
2478
2533
|
title: `rmfmail v${rootPkgVersion}`,
|
|
@@ -2549,6 +2604,9 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2549
2604
|
const inst = readInstanceFile();
|
|
2550
2605
|
if (inst && inst.pid === process.pid) clearInstanceFile();
|
|
2551
2606
|
try { handle.close(); } catch { /* already gone */ }
|
|
2607
|
+
// Hidden and therefore invisible to the user if orphaned — close it on
|
|
2608
|
+
// every exit path, not just gracefulShutdown.
|
|
2609
|
+
try { prewarmHandle?.close?.(); } catch { /* already gone */ }
|
|
2552
2610
|
};
|
|
2553
2611
|
process.once("exit", __cleanupInstance);
|
|
2554
2612
|
process.once("SIGINT", () => { __cleanupInstance(); process.exit(0); });
|
|
@@ -2733,10 +2791,21 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2733
2791
|
// and emits this when Word writes; compose.ts listens and reloads Quill.
|
|
2734
2792
|
imapManager.on("wordEditUpdated", (payload: { editId: string; html: string }) => {
|
|
2735
2793
|
handle.send({ _event: "wordEditUpdated", type: "wordEditUpdated", ...payload });
|
|
2736
|
-
//
|
|
2737
|
-
//
|
|
2738
|
-
//
|
|
2739
|
-
|
|
2794
|
+
// Deliberately NO { _msgerWindow: "focus" } here. The autosave sidecar
|
|
2795
|
+
// saves every ~2s while the user types, and each save lands in this
|
|
2796
|
+
// handler — so this line pulled the foreground off Word mid-sentence,
|
|
2797
|
+
// every couple of seconds, for the whole editing session (Bob
|
|
2798
|
+
// 2026-08-06: "keeps flipping back to rmfmail on any change"). The
|
|
2799
|
+
// focus now happens once, on wordEditClosed, below.
|
|
2800
|
+
});
|
|
2801
|
+
// Word (or the document) closed — the external edit is over. THIS is the
|
|
2802
|
+
// moment to bring rmfmail forward, replacing the old per-save focus grab.
|
|
2803
|
+
imapManager.on("wordEditClosed", (payload: { editId: string; fromPopout?: boolean }) => {
|
|
2804
|
+
handle.send({ _event: "wordEditClosed", type: "wordEditClosed", ...payload });
|
|
2805
|
+
// Only raise the MAIN window when the compose lives IN it. A popout
|
|
2806
|
+
// compose is a separate window with no service channel, so focusing
|
|
2807
|
+
// main would just cover it up.
|
|
2808
|
+
if (!payload.fromPopout) handle.send({ _msgerWindow: "focus" });
|
|
2740
2809
|
});
|
|
2741
2810
|
// Cloud-write/read failures from mailx-settings → push to UI as a banner so
|
|
2742
2811
|
// silent fall-back-to-local can no longer swallow Drive errors.
|
|
@@ -3158,6 +3227,10 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
3158
3227
|
if (shuttingDown) return;
|
|
3159
3228
|
shuttingDown = true;
|
|
3160
3229
|
console.log(`${reason} — shutting down`);
|
|
3230
|
+
// The prewarm window is hidden, so an orphaned one is invisible AND
|
|
3231
|
+
// unkillable by the user — close it explicitly rather than relying on
|
|
3232
|
+
// child-process cascade.
|
|
3233
|
+
try { prewarmHandle?.close?.(); } catch { /* already gone */ }
|
|
3161
3234
|
imapManager.stopPeriodicSync();
|
|
3162
3235
|
imapManager.stopOutboxWorker();
|
|
3163
3236
|
// 3s hard timeout — don't hang on broken IMAP connections
|
|
@@ -3210,7 +3283,12 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
3210
3283
|
const popoutDelta = lastPopoutClose
|
|
3211
3284
|
? `; last popout closed ${Date.now() - lastPopoutClose.at}ms ago (${lastPopoutClose.key})`
|
|
3212
3285
|
: "; no popout closed this session";
|
|
3213
|
-
|
|
3286
|
+
// Name the still-tracked popouts, not just the count. "2 popout window(s)
|
|
3287
|
+
// still tracked" (2026-08-05) was unactionable: two legitimately-open
|
|
3288
|
+
// compose windows and two entries whose close handler never fired look
|
|
3289
|
+
// identical from a bare count.
|
|
3290
|
+
const popoutKeys = popoutWindows.size > 0 ? ` [${[...popoutWindows.keys()].join(", ")}]` : "";
|
|
3291
|
+
await gracefulShutdown(`Window closed (main service child; ${popoutWindows.size} popout window(s) still tracked${popoutKeys}${popoutDelta})`);
|
|
3214
3292
|
}
|
|
3215
3293
|
|
|
3216
3294
|
main().catch(console.error);
|
package/client/app.bundle.js
CHANGED
|
@@ -614,8 +614,8 @@ function readConfigHelp(name) {
|
|
|
614
614
|
function unsubscribeOneClick(url) {
|
|
615
615
|
return ipc().unsubscribeOneClick?.(url);
|
|
616
616
|
}
|
|
617
|
-
function openInWord(editId, html) {
|
|
618
|
-
return ipc().openInWord?.(editId, html) ?? Promise.resolve({ ok: false, path: "", opener: "none" });
|
|
617
|
+
function openInWord(editId, html, fromPopout = false) {
|
|
618
|
+
return ipc().openInWord?.(editId, html, fromPopout) ?? Promise.resolve({ ok: false, path: "", opener: "none" });
|
|
619
619
|
}
|
|
620
620
|
function closeWordEdit(editId) {
|
|
621
621
|
return ipc().closeWordEdit?.(editId) ?? Promise.resolve();
|