@bobfrankston/rmfmail 1.2.136 → 1.2.137

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"),
@@ -2116,6 +2137,41 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
2116
2137
  const { ports } = await import("@bobfrankston/miscinfo");
2117
2138
  const dbgApp = express();
2118
2139
  dbgApp.use(express.json({ limit: "25mb" }));
2140
+ // Live-page eval: POST /api/eval {code} → daemon pushes a
2141
+ // `debugEval` event to the main window → app.ts runs the code and
2142
+ // answers via the `debugEvalResult` action → we reply here. The
2143
+ // expression may be async (it's wrapped in `(async () => (…))()`),
2144
+ // must evaluate as an EXPRESSION, and should return JSON-able
2145
+ // data. Lets an agent inspect the real DOM (elementFromPoint,
2146
+ // computed styles, scroll geometry) without devtools. Registered
2147
+ // BEFORE the generic /api router so it isn't shadowed.
2148
+ const pendingEvals = new Map();
2149
+ setDebugEvalSink((id, result, error) => {
2150
+ const p = pendingEvals.get(id);
2151
+ if (!p)
2152
+ return;
2153
+ pendingEvals.delete(id);
2154
+ clearTimeout(p.timer);
2155
+ p.finish(error ? { error } : { result });
2156
+ });
2157
+ dbgApp.post("/api/eval", (req, res) => {
2158
+ const code = req.body?.code;
2159
+ if (typeof code !== "string" || !code.trim()) {
2160
+ res.status(400).json({ error: "body must be { code: string } — a JS expression" });
2161
+ return;
2162
+ }
2163
+ if (!sendToClient) {
2164
+ res.status(503).json({ error: "main window not connected yet" });
2165
+ return;
2166
+ }
2167
+ const id = `ev-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2168
+ const timer = setTimeout(() => {
2169
+ pendingEvals.delete(id);
2170
+ res.status(504).json({ error: "eval timeout (10s) — window busy, or client bundle predates debugEval" });
2171
+ }, 10_000);
2172
+ pendingEvals.set(id, { finish: (v) => res.json(v), timer });
2173
+ sendToClient({ _event: "debugEval", type: "debugEval", id, code });
2174
+ });
2119
2175
  dbgApp.use("/api", createApiRouter(store, imapManager));
2120
2176
  // Port: `--debug-server=<n>` / `--debug-server <n>` if given,
2121
2177
  // else the reserved `ports.rmfmaildbg` from miscinfo (the named