@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 CHANGED
@@ -1830,10 +1830,31 @@ async function main() {
1830
1830
  const d = new Date();
1831
1831
  return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${pad3(d.getMilliseconds())}`;
1832
1832
  };
1833
- const logPath = path.join(logDir, `rmfmail-${localDate()}.log`);
1834
- const logStream = fs.createWriteStream(logPath, { flags: "a" });
1835
- console.log = (...a) => { logStream.write(`${ts()} ${a.join(" ")}\n`); };
1836
- console.error = (...a) => { logStream.write(`${ts()} ERROR ${a.join(" ")}\n`); };
1833
+ // Day-aware stream: recompute the filename on every write and swap
1834
+ // the stream at midnight. The old code resolved the path ONCE at
1835
+ // boot, so a daemon that ran past midnight kept appending to the
1836
+ // boot-day file forever while the sync worker's lines (written
1837
+ // through its own channel) landed in the new day's file. Result: the
1838
+ // morning of 2026-07-14 had ALL main-thread diagnostics ([ipc],
1839
+ // [client], [popout]) hiding in rmfmail-2026-07-13.log and the
1840
+ // current file showing only [w] worker lines — which made the
1841
+ // popout-send data-loss incident look untraceable (S68).
1842
+ let logDate = localDate();
1843
+ let logStream = fs.createWriteStream(path.join(logDir, `rmfmail-${logDate}.log`), { flags: "a" });
1844
+ const logWrite = (line) => {
1845
+ const d = localDate();
1846
+ if (d !== logDate) {
1847
+ try {
1848
+ logStream.end();
1849
+ }
1850
+ catch { /* old handle already dead */ }
1851
+ logDate = d;
1852
+ logStream = fs.createWriteStream(path.join(logDir, `rmfmail-${d}.log`), { flags: "a" });
1853
+ }
1854
+ logStream.write(line);
1855
+ };
1856
+ console.log = (...a) => { logWrite(`${ts()} ${a.join(" ")}\n`); };
1857
+ console.error = (...a) => { logWrite(`${ts()} ERROR ${a.join(" ")}\n`); };
1837
1858
  // Redirect daemon's process.stderr to the log too. msger forwards its
1838
1859
  // Rust child's stderr to our process.stderr; in daemon mode that's
1839
1860
  // stdio:"ignore" → /dev/null, which buries diagnostics like
@@ -1843,7 +1864,7 @@ async function main() {
1843
1864
  process.stderr.write = ((chunk, ...rest) => {
1844
1865
  try {
1845
1866
  const s = typeof chunk === "string" ? chunk : chunk.toString();
1846
- logStream.write(`${ts()} STDERR ${s}${s.endsWith("\n") ? "" : "\n"}`);
1867
+ logWrite(`${ts()} STDERR ${s}${s.endsWith("\n") ? "" : "\n"}`);
1847
1868
  }
1848
1869
  catch { /* ignore — best-effort logging */ }
1849
1870
  return origStderrWrite(chunk, ...rest);
@@ -1858,7 +1879,7 @@ async function main() {
1858
1879
  // for nothing — each was independent. Same for node-tcp-transport
1859
1880
  // and mailx-store/file-store.js further down; folded in here so the
1860
1881
  // resolution + module-init cost happens once, in parallel.
1861
- const [{ MailxDB, prewarmParseWorker, storeBus, Store }, { ImapManager }, { MailxService, spawnSyncWorker }, { dispatch }, { loadSettings, loadAccountsAsync, loadAllowlistAsync, getConfigDir, getStorageInfo, getStorePath }, { NodeTcpTransport }, { FileMessageStore },] = await Promise.all([
1882
+ const [{ MailxDB, prewarmParseWorker, storeBus, Store }, { ImapManager }, { MailxService, spawnSyncWorker }, { dispatch, setDebugEvalSink }, { loadSettings, loadAccountsAsync, loadAllowlistAsync, getConfigDir, getStorageInfo, getStorePath }, { NodeTcpTransport }, { FileMessageStore },] = await Promise.all([
1862
1883
  import("@bobfrankston/mailx-store"),
1863
1884
  import("@bobfrankston/mailx-imap"),
1864
1885
  import("@bobfrankston/mailx-service"),
@@ -2089,7 +2110,10 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2089
2110
  // see memory: feedback_cross_platform_strong_rule). Remove on
2090
2111
  // resolution so the file doesn't accumulate stale PIDs.
2091
2112
  svc.setPopupFn(async (opts) => {
2092
- const h = showMessageBoxEx(opts);
2113
+ // Own WebView2 profile so a reminder popup can never share (and
2114
+ // crash) the main window's browser process — see S67 note on the
2115
+ // popout windows below. Service-supplied opts may override.
2116
+ const h = showMessageBoxEx({ profile: "popout", ...opts });
2093
2117
  if (typeof h.pid === "number")
2094
2118
  addChildPid(h.pid);
2095
2119
  try {
@@ -2116,6 +2140,41 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2116
2140
  const { ports } = await import("@bobfrankston/miscinfo");
2117
2141
  const dbgApp = express();
2118
2142
  dbgApp.use(express.json({ limit: "25mb" }));
2143
+ // Live-page eval: POST /api/eval {code} → daemon pushes a
2144
+ // `debugEval` event to the main window → app.ts runs the code and
2145
+ // answers via the `debugEvalResult` action → we reply here. The
2146
+ // expression may be async (it's wrapped in `(async () => (…))()`),
2147
+ // must evaluate as an EXPRESSION, and should return JSON-able
2148
+ // data. Lets an agent inspect the real DOM (elementFromPoint,
2149
+ // computed styles, scroll geometry) without devtools. Registered
2150
+ // BEFORE the generic /api router so it isn't shadowed.
2151
+ const pendingEvals = new Map();
2152
+ setDebugEvalSink((id, result, error) => {
2153
+ const p = pendingEvals.get(id);
2154
+ if (!p)
2155
+ return;
2156
+ pendingEvals.delete(id);
2157
+ clearTimeout(p.timer);
2158
+ p.finish(error ? { error } : { result });
2159
+ });
2160
+ dbgApp.post("/api/eval", (req, res) => {
2161
+ const code = req.body?.code;
2162
+ if (typeof code !== "string" || !code.trim()) {
2163
+ res.status(400).json({ error: "body must be { code: string } — a JS expression" });
2164
+ return;
2165
+ }
2166
+ if (!sendToClient) {
2167
+ res.status(503).json({ error: "main window not connected yet" });
2168
+ return;
2169
+ }
2170
+ const id = `ev-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2171
+ const timer = setTimeout(() => {
2172
+ pendingEvals.delete(id);
2173
+ res.status(504).json({ error: "eval timeout (10s) — window busy, or client bundle predates debugEval" });
2174
+ }, 10_000);
2175
+ pendingEvals.set(id, { finish: (v) => res.json(v), timer });
2176
+ sendToClient({ _event: "debugEval", type: "debugEval", id, code });
2177
+ });
2119
2178
  dbgApp.use("/api", createApiRouter(store, imapManager));
2120
2179
  // Port: `--debug-server=<n>` / `--debug-server <n>` if given,
2121
2180
  // else the reserved `ports.rmfmaildbg` from miscinfo (the named
@@ -2205,6 +2264,13 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2205
2264
  // window shows unactivated BEHIND the focused main window
2206
2265
  // and reads as "nothing opened" (Bob 2026-07-09).
2207
2266
  focusOnCreate: true,
2267
+ // Own WebView2 profile: a window sharing the main window's
2268
+ // user-data dir shares its browser process, and a crash
2269
+ // there blacks out the MAIN window permanently (S67,
2270
+ // 2026-07-14 + 2026-07-15: BROWSER_PROCESS_EXITED during
2271
+ // popout spawn). One shared "popout" profile for all
2272
+ // secondary windows — decoupled from main, bounded disk.
2273
+ profile: "popout",
2208
2274
  });
2209
2275
  popoutWindows.set(key, h);
2210
2276
  if (typeof h.pid === "number")
@@ -2239,6 +2305,8 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2239
2305
  // Same as the message popout: the daemon is background,
2240
2306
  // so an unactivated window lands behind the main window.
2241
2307
  focusOnCreate: true,
2308
+ // Own profile — same S67 rationale as the message popout.
2309
+ profile: "popout",
2242
2310
  // App entries on the NATIVE right-click menu (native stays
2243
2311
  // so spellcheck suggestions survive). compose.ts maps ids
2244
2312
  // to editor commands via window.__msgerContextCommand.
@@ -2294,6 +2362,10 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2294
2362
  aumid: "com.frankston.rmfmail",
2295
2363
  escapeCloses: false,
2296
2364
  focusOnCreate: true,
2365
+ // Own profile — same S67 rationale as the message popout.
2366
+ // Note: localStorage view prefs won't be shared with the
2367
+ // main window; preferences.jsonc (the real store) is.
2368
+ profile: "popout",
2297
2369
  });
2298
2370
  popoutWindows.set(key, h);
2299
2371
  if (typeof h.pid === "number")