@bobfrankston/rmfmail 1.2.112 → 1.2.114
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/TODO.md +5 -5
- package/bin/mailx.js +72 -3
- package/bin/mailx.js.map +1 -1
- package/bin/mailx.ts +62 -3
- package/client/android-bootstrap.bundle.js +60 -3
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +42 -0
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +58 -0
- package/client/app.js.map +1 -1
- package/client/app.ts +60 -0
- package/client/compose/compose.bundle.js +50 -5
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/compose/compose.ts +13 -5
- package/client/compose/spellcheck-core.js +59 -3
- package/client/compose/spellcheck-core.js.map +1 -1
- package/client/compose/spellcheck-core.ts +58 -3
- package/client/compose/spellcheck.js +15 -6
- package/client/compose/spellcheck.js.map +1 -1
- package/client/compose/spellcheck.ts +15 -6
- package/package.json +1 -1
- package/packages/mailx-imap/index.d.ts +4 -1
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +59 -5
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +55 -5
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-store-web/android-bootstrap.js +48 -3
- package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
- package/packages/mailx-store-web/android-bootstrap.ts +47 -4
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-store-web/web-service.d.ts +21 -0
- package/packages/mailx-store-web/web-service.d.ts.map +1 -1
- package/packages/mailx-store-web/web-service.js +11 -0
- package/packages/mailx-store-web/web-service.js.map +1 -1
- package/packages/mailx-store-web/web-service.ts +11 -0
- package/packages/mailx-types/mailx-api.d.ts +22 -0
- package/packages/mailx-types/mailx-api.d.ts.map +1 -1
- package/packages/mailx-types/mailx-api.ts +7 -0
- package/packages/mailx-types/package.json +1 -1
package/TODO.md
CHANGED
|
@@ -13,13 +13,13 @@ These items don't need user input — source-only changes, compile-verified befo
|
|
|
13
13
|
| Order | Item | Scope | Status |
|
|
14
14
|
|---|---|---|---|
|
|
15
15
|
| ~~1~~ | ~~**C118 — STATUS-before-SELECT in syncFolder**~~ — **DONE** (already implemented at `mailx-imap/index.ts:1079`; TODO entry was stale). | S | |
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
16
|
+
| ~~2~~ | ~~**C119 — Lazy folder sync**~~ — **DONE** (shipped 2026-06-01; TODO entry was stale). `isLazyEligible` in `mailx-imap/index.ts` syncAccount: never-touched folders (highestUid=0, non-special) are not auto-swept at all — they sync on-demand via `syncFolderNow` when opened, then join the periodic set. Previously-seen folders stay in the sweep but C118's STATUS-before-SELECT makes an unchanged folder cost one round-trip. | M | |
|
|
17
|
+
| ~~3~~ | ~~**C121 — Counting timer for server connect**~~ — **DONE v1** (2026-07-08, v1.2.111). `newClient` in `mailx-imap/index.ts` wraps the injected transport + `client.connect` and emits `connectProgress` (phases `socket` → `handshake` → `done`/`failed`); bin forwards as an event; `client/app.ts handleConnectProgress` renders `Connecting to <host> (<phase> <purpose>, 3.2s)` with a 100 ms ticker, gated to attempts >1.5 s so healthy connects never flicker. Connects >3 s log per-phase timings (`[conn-slow]`). *Residual*: splitting `socket` into dns/tcp/tls needs an `onConnectPhase` hook inside iflow-node's NodeTransport — do when next touching iflow. | S | |
|
|
18
|
+
| ~~4~~ | ~~**C122 — Recent-unread count overlay on taskbar icon**~~ — **DONE** (shipped ~v1.1.x 2026-05-29; TODO entry was stale). `updateBadge` in `client/app.ts` renders the red count pill on a 32×32 canvas (badge-only, transparent field) and pushes it via `mailxapi.setTaskbarOverlay`; hooked to `folderCountsChanged` + startup via `updateNewMessageCount`. Badge shows NEW-since-last-seen, matching the "recent-unread" spec. S65 (badge while not running) remains open/blocked. | S | |
|
|
19
19
|
| ~~5~~ | ~~**C126 — Cold-start latency (~28s observed)**~~ — **MOSTLY DONE** (2026-05-11). Root cause IDed from boot log: 43 s of cascading ES module imports through msger's custom protocol IPC (one roundtrip per file). Fixes shipped: (a) bundling via esbuild (`bin/build-bundles.mjs`) collapses the cascade to one fetch per entry point — `client/app.bundle.js` 345 kb, `client/compose/compose.bundle.js` 99 kb; (b) boot-snapshot hydration — bundled app saves `folder-tree` + `ml-body` innerHTML to localStorage every 30 s; inline script in `index.html` restores at cold start before the bundle has fetched. Sub-bullet (c) [500 ms placeholder pause] not in current code at cited line — skipped as stale. Expected cold start now 3-5 s (full WebView2 + bundle parse), with near-instant *feel* via hydrated snapshot after the first run. | M | |
|
|
20
20
|
| **6** | **C125 — Unify desktop + Android IMAP code paths** | M | Today's split (`mailx-imap` Node-only + `mailx-store-web` browser-only) duplicates orchestration that's protocol-identical. Differences boil down to (a) transport factory choice (b) whether clients are persistent — both parameters, not architectural divides. Plan: extract `mailx-imap-core` with ImapManager + ops queue + fast-lane + sync orchestration over a `Transport` and a `Storage` interface, no Node-specific imports. `mailx-imap` and `mailx-store-web` become thin shims that wire engine-specific Storage + Transport. `{persistent: true\|false}` flag on ImapManager covers the lifetime split (no fast-lane queue when ephemeral). Pre-req for Android non-Gmail IMAP without forking sync code. |
|
|
21
|
-
| **5** | **C124 — mailto handler on Linux
|
|
22
|
-
|
|
|
21
|
+
| **5** | **C124 — mailto handler on macOS** (Linux ✅ 2026-07-08 v1.2.111) | S | Linux half shipped: `--register-mailto` on linux writes `~/.local/share/applications/rmfmail.desktop` (`MimeType=x-scheme-handler/mailto;`, quoted node+mailx.js Exec, bare `%u`), runs `xdg-mime default` + `update-desktop-database`; `--unregister-mailto` removes it. **Remaining: macOS** — real `.app` bundle (LaunchServices won't register a bare script) with `Info.plist` `CFBundleURLTypes` declaring `mailto` + a tiny launcher binary; `LSSetDefaultHandlerForURLScheme` programmatically sets the default. Rust `rmfmailto-src/` crate already portable — drop the Win32 imports and `cargo build --target x86_64-apple-darwin` produces the macOS launcher. Needs a Mac to build/verify. |
|
|
22
|
+
| ~~7~~ | ~~**C154 — Audit Android bridge vs MailxApi contract**~~ — **DONE** (2026-07-08, v1.2.111). Bridge now has a generic contract-backstop: every service method not explicitly adapted gets a positional pass-through, so a missing method can never again fail as `parent bridge has no method "X"`. Wire-shape mismatches adapted explicitly (`readJsoncFile`/`writeJsoncFile`/`formatJsonc`/`readConfigHelp` → `{content}`/`{ok}`, `drainStoreSync`/`syncFolderNow` → `{ok}`). Contract gained the missing entries (`getMessageSource`, `copyMessages`, `syncFolderNow`, `cancelServerSearch`, `getReminderSound`, `getCalendars`, optional `popoutWindow`) with web-service stubs per the policy (reads → graceful default, writes/OS → notImpl). Bonus fixes: bridge `getMessages` was dropping sort/sortDir/search and `searchMessages` was dropping scope/accountId/folderId — Android sort toggles and scoped search silently didn't work. | S | |
|
|
23
23
|
~~done — C120 read transport diagnostics~~ shipped v1.0.583 (per-folder timeout error now includes `[conn#X r=YB w=ZB writes=N sinceLastRead=Tms]` snapshot for the doomed socket).
|
|
24
24
|
| 11 | **[P116](#ext116) — Signature edit in Settings menu** | M | Deferred v1.0.401 — turns out to be M (multi-account modal + save path + JSONC round-trip), not S. Real work when Settings UI gets its proper pass. |
|
|
25
25
|
| 13 | **S56 full AbortController plumbing** | L | Thread `AbortSignal` through `getMessage → fetchMessageBody → provider.fetchOne`. Touches IMAP + Gmail + Outlook providers. |
|
package/bin/mailx.js
CHANGED
|
@@ -180,11 +180,61 @@ const isDaemon = hasFlag("daemon"); // internal: re-spawned detached process
|
|
|
180
180
|
}
|
|
181
181
|
if (hasFlag("register-mailto") || hasFlag("unregister-mailto")) {
|
|
182
182
|
const unregister = hasFlag("unregister-mailto");
|
|
183
|
+
// C124: Linux registration — a .desktop entry claiming the mailto:
|
|
184
|
+
// scheme + xdg-mime default. No exe-launcher needed (unlike Windows):
|
|
185
|
+
// Linux pickers read Name= from the .desktop file directly.
|
|
186
|
+
if (process.platform === "linux") {
|
|
187
|
+
const appsDir = path.join(os.homedir(), ".local", "share", "applications");
|
|
188
|
+
const desktopPath = path.join(appsDir, "rmfmail.desktop");
|
|
189
|
+
if (unregister) {
|
|
190
|
+
try {
|
|
191
|
+
fs.unlinkSync(desktopPath);
|
|
192
|
+
}
|
|
193
|
+
catch { /* not registered */ }
|
|
194
|
+
try {
|
|
195
|
+
execSync(`update-desktop-database "${appsDir}"`);
|
|
196
|
+
}
|
|
197
|
+
catch { /* optional tool */ }
|
|
198
|
+
console.log("Unregistered rmfmail mailto: handler.");
|
|
199
|
+
process.exit(0);
|
|
200
|
+
}
|
|
201
|
+
const __mailtoJs = path.join(import.meta.dirname, "mailx.js");
|
|
202
|
+
const __iconPng = path.join(import.meta.dirname, "..", "client", "icon.png");
|
|
203
|
+
// Exec quoting follows the Desktop Entry spec: quoted absolute paths,
|
|
204
|
+
// bare %u field code (quoting %u breaks argument expansion).
|
|
205
|
+
const desktopEntry = [
|
|
206
|
+
"[Desktop Entry]",
|
|
207
|
+
"Type=Application",
|
|
208
|
+
"Name=rmfmail",
|
|
209
|
+
"Comment=rmfmail email client",
|
|
210
|
+
`Exec="${process.execPath}" "${__mailtoJs}" --mailto %u`,
|
|
211
|
+
`Icon=${__iconPng}`,
|
|
212
|
+
"Terminal=false",
|
|
213
|
+
"MimeType=x-scheme-handler/mailto;",
|
|
214
|
+
"Categories=Network;Email;",
|
|
215
|
+
"",
|
|
216
|
+
].join("\n");
|
|
217
|
+
fs.mkdirSync(appsDir, { recursive: true });
|
|
218
|
+
fs.writeFileSync(desktopPath, desktopEntry);
|
|
219
|
+
try {
|
|
220
|
+
execSync("xdg-mime default rmfmail.desktop x-scheme-handler/mailto");
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
console.error(`xdg-mime failed (${e.message}) — handler installed but not set as default. Set it in your DE's default-apps settings.`);
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
execSync(`update-desktop-database "${appsDir}"`);
|
|
227
|
+
}
|
|
228
|
+
catch { /* optional tool */ }
|
|
229
|
+
console.log(`Registered rmfmail as mailto: handler (${desktopPath}).`);
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
183
232
|
if (process.platform !== "win32") {
|
|
184
|
-
|
|
233
|
+
// macOS needs a real .app bundle with CFBundleURLTypes — LaunchServices
|
|
234
|
+
// won't register a bare script. Tracked as the macOS half of C124.
|
|
235
|
+
console.error("--register-mailto / --unregister-mailto: macOS registration not implemented yet (needs an .app bundle — TODO C124).");
|
|
185
236
|
process.exit(1);
|
|
186
237
|
}
|
|
187
|
-
const { execSync } = await import("node:child_process");
|
|
188
238
|
// Registered command points at the rmfmailto.exe launcher (per-app
|
|
189
239
|
// binary at %LOCALAPPDATA%\rmfmail\bin\rmfmailto.exe). The exe has its
|
|
190
240
|
// own VERSIONINFO with FileDescription="rmfmail" so the Win11 "Select
|
|
@@ -1973,6 +2023,10 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
1973
2023
|
let popoutInfo = null;
|
|
1974
2024
|
let sendToClient = null;
|
|
1975
2025
|
const popoutWindows = new Map();
|
|
2026
|
+
// Timestamp of the most recent popout close — reported as a delta in the
|
|
2027
|
+
// main-window shutdown log line to prove/refute the "closing a popout
|
|
2028
|
+
// closes the main window" coupling (Bob 2026-07-06, unreproduced).
|
|
2029
|
+
let lastPopoutClose = null;
|
|
1976
2030
|
const popoutKey = (a, f, u) => `${a}|${f ?? ""}|${u}`;
|
|
1977
2031
|
try {
|
|
1978
2032
|
const { startPopoutServer } = await import("./popout-server.js");
|
|
@@ -2133,6 +2187,7 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2133
2187
|
popoutWindows.delete(key);
|
|
2134
2188
|
if (typeof h.pid === "number")
|
|
2135
2189
|
removeChildPid(h.pid);
|
|
2190
|
+
lastPopoutClose = { at: Date.now(), key };
|
|
2136
2191
|
console.log(` [popout] window CLOSED pid=${h.pid ?? "?"} key=${key} — main daemon still pid=${process.pid}`);
|
|
2137
2192
|
});
|
|
2138
2193
|
return { ok: true };
|
|
@@ -2339,6 +2394,13 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2339
2394
|
}, 1000);
|
|
2340
2395
|
}
|
|
2341
2396
|
});
|
|
2397
|
+
// C121: connect-phase progress → status-bar "Connecting to <host>
|
|
2398
|
+
// (<phase>, N.Ns)" ticker. Low-rate (a handful of events per connection
|
|
2399
|
+
// attempt), so no batching — the phase TRANSITIONS are the signal and
|
|
2400
|
+
// coalescing them away would defeat the point.
|
|
2401
|
+
imapManager.on("connectProgress", (accountId, info) => {
|
|
2402
|
+
handle.send({ _event: "connectProgress", type: "connectProgress", accountId, ...info });
|
|
2403
|
+
});
|
|
2342
2404
|
imapManager.on("syncError", (accountId, error) => {
|
|
2343
2405
|
handle.send({ _event: "error", type: "error", message: `${accountId}: ${error}` });
|
|
2344
2406
|
});
|
|
@@ -2807,8 +2869,15 @@ RFC 5322 with CRLF line endings. Bodies are quoted-printable encoded (readable i
|
|
|
2807
2869
|
// window ONLY — popout windows are separate showMessageBoxEx children and
|
|
2808
2870
|
// must never resolve this. Log open popouts at shutdown so if a popout
|
|
2809
2871
|
// close ever coincides with a main-window close we can see the timing.
|
|
2872
|
+
// The 2026-07-06 incident (log 13:59:56) shows the main child's own Rust
|
|
2873
|
+
// process emitting the close result 123ms after a popout interaction —
|
|
2874
|
+
// the correlation delta below is what will prove/refute the coupling
|
|
2875
|
+
// next time it fires.
|
|
2810
2876
|
await handle.closed;
|
|
2811
|
-
|
|
2877
|
+
const popoutDelta = lastPopoutClose
|
|
2878
|
+
? `; last popout closed ${Date.now() - lastPopoutClose.at}ms ago (${lastPopoutClose.key})`
|
|
2879
|
+
: "; no popout closed this session";
|
|
2880
|
+
await gracefulShutdown(`Window closed (main service child; ${popoutWindows.size} popout window(s) still tracked${popoutDelta})`);
|
|
2812
2881
|
}
|
|
2813
2882
|
main().catch(console.error);
|
|
2814
2883
|
//# sourceMappingURL=mailx.js.map
|