@bobfrankston/rmfmail 1.2.136 → 1.2.139
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 +79 -7
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +75 -7
- package/client/app.bundle.js +40 -5
- package/client/app.bundle.js.map +3 -3
- package/client/app.js +31 -0
- package/client/app.js.map +1 -1
- package/client/app.ts +24 -0
- package/client/components/message-list.js +30 -4
- package/client/components/message-list.js.map +1 -1
- package/client/components/message-list.ts +30 -4
- package/client/compose/compose.bundle.js +4 -0
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/lib/api-client.js +5 -0
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +6 -0
- package/client/lib/mailxapi.js +4 -0
- package/package.json +1 -1
- package/packages/mailx-service/jsonrpc.d.ts +1 -0
- package/packages/mailx-service/jsonrpc.d.ts.map +1 -1
- package/packages/mailx-service/jsonrpc.js +15 -0
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +17 -0
- package/packages/mailx-store/db.d.ts.map +1 -1
- package/packages/mailx-store/db.js +28 -2
- package/packages/mailx-store/db.js.map +1 -1
- package/packages/mailx-store/db.ts +27 -2
package/bin/mailx.ts
CHANGED
|
@@ -1768,10 +1768,28 @@ async function main(): Promise<void> {
|
|
|
1768
1768
|
const d = new Date();
|
|
1769
1769
|
return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${pad3(d.getMilliseconds())}`;
|
|
1770
1770
|
};
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1771
|
+
// Day-aware stream: recompute the filename on every write and swap
|
|
1772
|
+
// the stream at midnight. The old code resolved the path ONCE at
|
|
1773
|
+
// boot, so a daemon that ran past midnight kept appending to the
|
|
1774
|
+
// boot-day file forever — while the sync worker's lines (written
|
|
1775
|
+
// through its own channel) landed in the new day's file. Result: the
|
|
1776
|
+
// morning of 2026-07-14 had ALL main-thread diagnostics ([ipc],
|
|
1777
|
+
// [client], [popout]) hiding in rmfmail-2026-07-13.log and the
|
|
1778
|
+
// current file showing only [w] worker lines — which made the
|
|
1779
|
+
// popout-send data-loss incident look untraceable (S68).
|
|
1780
|
+
let logDate = localDate();
|
|
1781
|
+
let logStream = fs.createWriteStream(path.join(logDir, `rmfmail-${logDate}.log`), { flags: "a" });
|
|
1782
|
+
const logWrite = (line: string): void => {
|
|
1783
|
+
const d = localDate();
|
|
1784
|
+
if (d !== logDate) {
|
|
1785
|
+
try { logStream.end(); } catch { /* old handle already dead */ }
|
|
1786
|
+
logDate = d;
|
|
1787
|
+
logStream = fs.createWriteStream(path.join(logDir, `rmfmail-${d}.log`), { flags: "a" });
|
|
1788
|
+
}
|
|
1789
|
+
logStream.write(line);
|
|
1790
|
+
};
|
|
1791
|
+
console.log = (...a: any[]) => { logWrite(`${ts()} ${a.join(" ")}\n`); };
|
|
1792
|
+
console.error = (...a: any[]) => { logWrite(`${ts()} ERROR ${a.join(" ")}\n`); };
|
|
1775
1793
|
// Redirect daemon's process.stderr to the log too. msger forwards its
|
|
1776
1794
|
// Rust child's stderr to our process.stderr; in daemon mode that's
|
|
1777
1795
|
// stdio:"ignore" → /dev/null, which buries diagnostics like
|
|
@@ -1781,7 +1799,7 @@ async function main(): Promise<void> {
|
|
|
1781
1799
|
process.stderr.write = ((chunk: any, ...rest: any[]): boolean => {
|
|
1782
1800
|
try {
|
|
1783
1801
|
const s = typeof chunk === "string" ? chunk : chunk.toString();
|
|
1784
|
-
|
|
1802
|
+
logWrite(`${ts()} STDERR ${s}${s.endsWith("\n") ? "" : "\n"}`);
|
|
1785
1803
|
} catch { /* ignore — best-effort logging */ }
|
|
1786
1804
|
return origStderrWrite(chunk, ...rest);
|
|
1787
1805
|
}) as typeof process.stderr.write;
|
|
@@ -1800,7 +1818,7 @@ async function main(): Promise<void> {
|
|
|
1800
1818
|
{ MailxDB, prewarmParseWorker, storeBus, Store },
|
|
1801
1819
|
{ ImapManager },
|
|
1802
1820
|
{ MailxService, spawnSyncWorker },
|
|
1803
|
-
{ dispatch },
|
|
1821
|
+
{ dispatch, setDebugEvalSink },
|
|
1804
1822
|
{ loadSettings, loadAccountsAsync, loadAllowlistAsync, getConfigDir, getStorageInfo, getStorePath },
|
|
1805
1823
|
{ NodeTcpTransport },
|
|
1806
1824
|
{ FileMessageStore },
|
|
@@ -2026,7 +2044,10 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2026
2044
|
// see memory: feedback_cross_platform_strong_rule). Remove on
|
|
2027
2045
|
// resolution so the file doesn't accumulate stale PIDs.
|
|
2028
2046
|
svc.setPopupFn(async (opts: any) => {
|
|
2029
|
-
|
|
2047
|
+
// Own WebView2 profile so a reminder popup can never share (and
|
|
2048
|
+
// crash) the main window's browser process — see S67 note on the
|
|
2049
|
+
// popout windows below. Service-supplied opts may override.
|
|
2050
|
+
const h = showMessageBoxEx({ profile: "popout", ...opts });
|
|
2030
2051
|
if (typeof h.pid === "number") addChildPid(h.pid);
|
|
2031
2052
|
try {
|
|
2032
2053
|
const r = await h.result;
|
|
@@ -2051,6 +2072,40 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2051
2072
|
const { ports } = await import("@bobfrankston/miscinfo");
|
|
2052
2073
|
const dbgApp = express();
|
|
2053
2074
|
dbgApp.use(express.json({ limit: "25mb" }));
|
|
2075
|
+
// Live-page eval: POST /api/eval {code} → daemon pushes a
|
|
2076
|
+
// `debugEval` event to the main window → app.ts runs the code and
|
|
2077
|
+
// answers via the `debugEvalResult` action → we reply here. The
|
|
2078
|
+
// expression may be async (it's wrapped in `(async () => (…))()`),
|
|
2079
|
+
// must evaluate as an EXPRESSION, and should return JSON-able
|
|
2080
|
+
// data. Lets an agent inspect the real DOM (elementFromPoint,
|
|
2081
|
+
// computed styles, scroll geometry) without devtools. Registered
|
|
2082
|
+
// BEFORE the generic /api router so it isn't shadowed.
|
|
2083
|
+
const pendingEvals = new Map<string, { finish: (v: any) => void; timer: NodeJS.Timeout }>();
|
|
2084
|
+
setDebugEvalSink((id, result, error) => {
|
|
2085
|
+
const p = pendingEvals.get(id);
|
|
2086
|
+
if (!p) return;
|
|
2087
|
+
pendingEvals.delete(id);
|
|
2088
|
+
clearTimeout(p.timer);
|
|
2089
|
+
p.finish(error ? { error } : { result });
|
|
2090
|
+
});
|
|
2091
|
+
dbgApp.post("/api/eval", (req, res) => {
|
|
2092
|
+
const code = req.body?.code;
|
|
2093
|
+
if (typeof code !== "string" || !code.trim()) {
|
|
2094
|
+
res.status(400).json({ error: "body must be { code: string } — a JS expression" });
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
if (!sendToClient) {
|
|
2098
|
+
res.status(503).json({ error: "main window not connected yet" });
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
const id = `ev-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2102
|
+
const timer = setTimeout(() => {
|
|
2103
|
+
pendingEvals.delete(id);
|
|
2104
|
+
res.status(504).json({ error: "eval timeout (10s) — window busy, or client bundle predates debugEval" });
|
|
2105
|
+
}, 10_000);
|
|
2106
|
+
pendingEvals.set(id, { finish: (v) => res.json(v), timer });
|
|
2107
|
+
sendToClient({ _event: "debugEval", type: "debugEval", id, code });
|
|
2108
|
+
});
|
|
2054
2109
|
dbgApp.use("/api", createApiRouter(store, imapManager));
|
|
2055
2110
|
// Port: `--debug-server=<n>` / `--debug-server <n>` if given,
|
|
2056
2111
|
// else the reserved `ports.rmfmaildbg` from miscinfo (the named
|
|
@@ -2141,6 +2196,13 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2141
2196
|
// window shows unactivated BEHIND the focused main window
|
|
2142
2197
|
// and reads as "nothing opened" (Bob 2026-07-09).
|
|
2143
2198
|
focusOnCreate: true,
|
|
2199
|
+
// Own WebView2 profile: a window sharing the main window's
|
|
2200
|
+
// user-data dir shares its browser process, and a crash
|
|
2201
|
+
// there blacks out the MAIN window permanently (S67,
|
|
2202
|
+
// 2026-07-14 + 2026-07-15: BROWSER_PROCESS_EXITED during
|
|
2203
|
+
// popout spawn). One shared "popout" profile for all
|
|
2204
|
+
// secondary windows — decoupled from main, bounded disk.
|
|
2205
|
+
profile: "popout",
|
|
2144
2206
|
});
|
|
2145
2207
|
popoutWindows.set(key, h);
|
|
2146
2208
|
if (typeof h.pid === "number") addChildPid(h.pid);
|
|
@@ -2173,6 +2235,8 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2173
2235
|
// Same as the message popout: the daemon is background,
|
|
2174
2236
|
// so an unactivated window lands behind the main window.
|
|
2175
2237
|
focusOnCreate: true,
|
|
2238
|
+
// Own profile — same S67 rationale as the message popout.
|
|
2239
|
+
profile: "popout",
|
|
2176
2240
|
// App entries on the NATIVE right-click menu (native stays
|
|
2177
2241
|
// so spellcheck suggestions survive). compose.ts maps ids
|
|
2178
2242
|
// to editor commands via window.__msgerContextCommand.
|
|
@@ -2221,6 +2285,10 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2221
2285
|
aumid: "com.frankston.rmfmail",
|
|
2222
2286
|
escapeCloses: false,
|
|
2223
2287
|
focusOnCreate: true,
|
|
2288
|
+
// Own profile — same S67 rationale as the message popout.
|
|
2289
|
+
// Note: localStorage view prefs won't be shared with the
|
|
2290
|
+
// main window; preferences.jsonc (the real store) is.
|
|
2291
|
+
profile: "popout",
|
|
2224
2292
|
});
|
|
2225
2293
|
popoutWindows.set(key, h);
|
|
2226
2294
|
if (typeof h.pid === "number") addChildPid(h.pid);
|
package/client/app.bundle.js
CHANGED
|
@@ -37,6 +37,7 @@ __export(api_client_exports, {
|
|
|
37
37
|
createCalendarEvent: () => createCalendarEvent,
|
|
38
38
|
createFolder: () => createFolder,
|
|
39
39
|
createTask: () => createTask,
|
|
40
|
+
debugEvalResult: () => debugEvalResult,
|
|
40
41
|
deleteCalendarEvent: () => deleteCalendarEvent,
|
|
41
42
|
deleteContact: () => deleteContact,
|
|
42
43
|
deleteDraft: () => deleteDraft,
|
|
@@ -496,6 +497,9 @@ function logClientEvent(tag, data) {
|
|
|
496
497
|
function sendMessage(body) {
|
|
497
498
|
return ipc().sendMessage?.(body);
|
|
498
499
|
}
|
|
500
|
+
function debugEvalResult(id, result, error) {
|
|
501
|
+
return ipc().debugEvalResult?.(id, result, error);
|
|
502
|
+
}
|
|
499
503
|
function saveDraft(body) {
|
|
500
504
|
return ipc().saveDraft?.(body);
|
|
501
505
|
}
|
|
@@ -3849,11 +3853,16 @@ function clearSearchMode() {
|
|
|
3849
3853
|
}
|
|
3850
3854
|
async function loadUnifiedInbox(autoSelect = true) {
|
|
3851
3855
|
const myGen = ++loadGen;
|
|
3856
|
+
const wasUnified = unifiedMode;
|
|
3852
3857
|
unifiedMode = true;
|
|
3853
3858
|
searchMode = false;
|
|
3854
3859
|
currentSpecialUse = "";
|
|
3855
3860
|
showToInsteadOfFrom = false;
|
|
3856
|
-
|
|
3861
|
+
const loadedPages = !autoSelect && wasUnified ? Math.min(Math.max(1, currentPage), MAX_REFRESH_PAGES) : 1;
|
|
3862
|
+
if (!autoSelect && wasUnified && currentPage > MAX_REFRESH_PAGES) {
|
|
3863
|
+
console.log(` [ml-refresh] depth capped: ${currentPage} loaded pages, refreshing only ${MAX_REFRESH_PAGES}`);
|
|
3864
|
+
}
|
|
3865
|
+
currentPage = loadedPages;
|
|
3857
3866
|
totalMessages = 0;
|
|
3858
3867
|
const body = document.getElementById("ml-body");
|
|
3859
3868
|
if (!body)
|
|
@@ -3886,7 +3895,7 @@ async function loadUnifiedInbox(autoSelect = true) {
|
|
|
3886
3895
|
body.innerHTML = `<div class="ml-empty">Loading...</div>`;
|
|
3887
3896
|
}
|
|
3888
3897
|
try {
|
|
3889
|
-
const result = await getUnifiedInbox(1, 50, flaggedOnly, dateBasis);
|
|
3898
|
+
const result = await getUnifiedInbox(1, loadedPages * 50, flaggedOnly, dateBasis);
|
|
3890
3899
|
if (myGen !== loadGen)
|
|
3891
3900
|
return;
|
|
3892
3901
|
totalMessages = result.total;
|
|
@@ -4016,9 +4025,14 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
|
|
|
4016
4025
|
currentSpecialUse = specialUse;
|
|
4017
4026
|
const su = currentSpecialUse.toLowerCase();
|
|
4018
4027
|
showToInsteadOfFrom = su === "sent" || su === "drafts" || su === "outbox" || su.endsWith("sent") || su.endsWith("drafts") || su.endsWith("outbox") || su === "sent items" || su === "sent mail" || su.endsWith("/sent items") || su.endsWith(".sent items");
|
|
4028
|
+
const sameView = accountId === currentAccountId2 && folderId === currentFolderId;
|
|
4019
4029
|
currentAccountId2 = accountId;
|
|
4020
4030
|
currentFolderId = folderId;
|
|
4021
|
-
|
|
4031
|
+
const loadedPages = !autoSelect && sameView ? Math.min(Math.max(1, currentPage), MAX_REFRESH_PAGES) : 1;
|
|
4032
|
+
if (!autoSelect && sameView && currentPage > MAX_REFRESH_PAGES) {
|
|
4033
|
+
console.log(` [ml-refresh] depth capped: ${currentPage} loaded pages, refreshing only ${MAX_REFRESH_PAGES}`);
|
|
4034
|
+
}
|
|
4035
|
+
currentPage = loadedPages;
|
|
4022
4036
|
totalMessages = 0;
|
|
4023
4037
|
const body = document.getElementById("ml-body");
|
|
4024
4038
|
if (!body)
|
|
@@ -4058,7 +4072,7 @@ async function loadMessages(accountId, folderId, page = 1, specialUse = "", auto
|
|
|
4058
4072
|
body.innerHTML = `<div class="ml-empty">Loading...</div>`;
|
|
4059
4073
|
}
|
|
4060
4074
|
try {
|
|
4061
|
-
const result = await getMessages(accountId, folderId, 1, 50, flaggedOnly, currentSort, currentSortDir, dateBasis);
|
|
4075
|
+
const result = await getMessages(accountId, folderId, 1, loadedPages * 50, flaggedOnly, currentSort, currentSortDir, dateBasis);
|
|
4062
4076
|
if (myGen !== loadGen)
|
|
4063
4077
|
return;
|
|
4064
4078
|
totalMessages = result.total;
|
|
@@ -4353,7 +4367,7 @@ function escapeHtml3(s) {
|
|
|
4353
4367
|
div.textContent = s;
|
|
4354
4368
|
return div.innerHTML;
|
|
4355
4369
|
}
|
|
4356
|
-
var onMessageSelect, currentAccountId2, currentFolderId, currentSpecialUse, lastClickedRow, currentPage, totalMessages, loading, unifiedMode, searchMode, liveFilterText, currentSearchQuery, wasUnifiedBeforeSearch, showToInsteadOfFrom, touchWasScroll, lastPointerWasTouch, currentSort, currentSortDir, dateBasis, loadGen, listCache, CACHE_KEY_UNIFIED, positionMemory, POSITION_STORAGE_KEY, focusedRow, rowByKey, prioritySenders, priorityDomains, timeFmt, dateFmt, dateFmtSameYear, MessageRow;
|
|
4370
|
+
var onMessageSelect, currentAccountId2, currentFolderId, currentSpecialUse, lastClickedRow, currentPage, totalMessages, loading, MAX_REFRESH_PAGES, unifiedMode, searchMode, liveFilterText, currentSearchQuery, wasUnifiedBeforeSearch, showToInsteadOfFrom, touchWasScroll, lastPointerWasTouch, currentSort, currentSortDir, dateBasis, loadGen, listCache, CACHE_KEY_UNIFIED, positionMemory, POSITION_STORAGE_KEY, focusedRow, rowByKey, prioritySenders, priorityDomains, timeFmt, dateFmt, dateFmtSameYear, MessageRow;
|
|
4357
4371
|
var init_message_list = __esm({
|
|
4358
4372
|
"client/components/message-list.js"() {
|
|
4359
4373
|
"use strict";
|
|
@@ -4366,6 +4380,7 @@ var init_message_list = __esm({
|
|
|
4366
4380
|
currentSpecialUse = "";
|
|
4367
4381
|
lastClickedRow = null;
|
|
4368
4382
|
loading = false;
|
|
4383
|
+
MAX_REFRESH_PAGES = 20;
|
|
4369
4384
|
unifiedMode = false;
|
|
4370
4385
|
searchMode = false;
|
|
4371
4386
|
liveFilterText = "";
|
|
@@ -10256,6 +10271,26 @@ onWsEvent((event) => {
|
|
|
10256
10271
|
}).catch((e) => console.error("openComposeFromMailto failed:", e?.message || e));
|
|
10257
10272
|
break;
|
|
10258
10273
|
}
|
|
10274
|
+
case "debugEval": {
|
|
10275
|
+
const { id, code } = event;
|
|
10276
|
+
if (!id || typeof code !== "string") break;
|
|
10277
|
+
(async () => {
|
|
10278
|
+
const { debugEvalResult: debugEvalResult2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
10279
|
+
try {
|
|
10280
|
+
const fn = new Function(`return (async () => (${code}))();`);
|
|
10281
|
+
let v = await fn();
|
|
10282
|
+
try {
|
|
10283
|
+
JSON.stringify(v);
|
|
10284
|
+
} catch {
|
|
10285
|
+
v = String(v);
|
|
10286
|
+
}
|
|
10287
|
+
await debugEvalResult2(id, v);
|
|
10288
|
+
} catch (e) {
|
|
10289
|
+
await debugEvalResult2(id, void 0, e?.message || String(e));
|
|
10290
|
+
}
|
|
10291
|
+
})().catch((e) => console.error(`[debug-eval] reply failed: ${e?.message || e}`));
|
|
10292
|
+
break;
|
|
10293
|
+
}
|
|
10259
10294
|
case "popoutAction": {
|
|
10260
10295
|
const { action, accountId, uid, folderId } = event;
|
|
10261
10296
|
if (!action || !accountId || !uid) break;
|