@bobfrankston/rmfmail 1.2.49 → 1.2.65

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.
Files changed (90) hide show
  1. package/client/android-bootstrap.bundle.js +63 -2
  2. package/client/android-bootstrap.bundle.js.map +4 -4
  3. package/client/app.bundle.js +417 -63
  4. package/client/app.bundle.js.map +3 -3
  5. package/client/app.js +182 -24
  6. package/client/app.js.map +1 -1
  7. package/client/app.ts +166 -21
  8. package/client/components/calendar-sidebar.js +5 -5
  9. package/client/components/calendar-sidebar.js.map +1 -1
  10. package/client/components/calendar-sidebar.ts +5 -5
  11. package/client/components/folder-tree.js +81 -0
  12. package/client/components/folder-tree.js.map +1 -1
  13. package/client/components/folder-tree.ts +70 -0
  14. package/client/components/message-list.js +282 -38
  15. package/client/components/message-list.js.map +1 -1
  16. package/client/components/message-list.ts +264 -36
  17. package/client/compose/compose.bundle.js +70 -14
  18. package/client/compose/compose.bundle.js.map +2 -2
  19. package/client/compose/compose.css +28 -7
  20. package/client/compose/compose.js +64 -4
  21. package/client/compose/compose.js.map +1 -1
  22. package/client/compose/compose.ts +66 -4
  23. package/client/compose/editor.js +23 -7
  24. package/client/compose/editor.js.map +1 -1
  25. package/client/compose/editor.ts +23 -7
  26. package/client/help/search-help.js +4 -3
  27. package/client/help/search-help.js.map +1 -1
  28. package/client/help/search-help.ts +4 -3
  29. package/client/index.html +5 -1
  30. package/client/lib/api-client.js +4 -2
  31. package/client/lib/api-client.js.map +1 -1
  32. package/client/lib/api-client.ts +4 -2
  33. package/client/lib/mailxapi.js +2 -2
  34. package/client/lib/rmf-tiny.js +39 -6
  35. package/client/styles/components.css +6 -0
  36. package/docs/azure.md +58 -0
  37. package/docs/config-help.md +131 -0
  38. package/docs/edit-in-word-docx.md +98 -0
  39. package/docs/host-abstraction-plan.md +169 -0
  40. package/docs/local-first-plan.md +303 -0
  41. package/docs/npmglobalize-transitive-workspace-deps.md +107 -0
  42. package/docs/outlook.md +80 -18
  43. package/docs/outlook.txt +35 -0
  44. package/docs/rules-design.md +172 -0
  45. package/package.json +7 -7
  46. package/packages/mailx-imap/index.d.ts +48 -1
  47. package/packages/mailx-imap/index.d.ts.map +1 -1
  48. package/packages/mailx-imap/index.js +332 -70
  49. package/packages/mailx-imap/index.js.map +1 -1
  50. package/packages/mailx-imap/index.ts +323 -67
  51. package/packages/mailx-imap/microsoft-credentials.json +8 -0
  52. package/packages/mailx-imap/package-lock.json +2 -2
  53. package/packages/mailx-imap/package.json +1 -1
  54. package/packages/mailx-imap/providers/outlook-api.d.ts +7 -0
  55. package/packages/mailx-imap/providers/outlook-api.d.ts.map +1 -0
  56. package/packages/mailx-imap/providers/outlook-api.js +7 -0
  57. package/packages/mailx-imap/providers/outlook-api.js.map +1 -0
  58. package/packages/mailx-service/index.d.ts +7 -1
  59. package/packages/mailx-service/index.d.ts.map +1 -1
  60. package/packages/mailx-service/index.js +8 -8
  61. package/packages/mailx-service/index.js.map +1 -1
  62. package/packages/mailx-service/index.ts +8 -7
  63. package/packages/mailx-service/jsonrpc.js +1 -1
  64. package/packages/mailx-service/jsonrpc.js.map +1 -1
  65. package/packages/mailx-service/jsonrpc.ts +1 -1
  66. package/packages/mailx-settings/docs/azure.md +58 -0
  67. package/packages/mailx-settings/docs/config-help.md +131 -0
  68. package/packages/mailx-settings/docs/edit-in-word-docx.md +98 -0
  69. package/packages/mailx-settings/docs/host-abstraction-plan.md +169 -0
  70. package/packages/mailx-settings/docs/local-first-plan.md +303 -0
  71. package/packages/mailx-settings/docs/npmglobalize-transitive-workspace-deps.md +107 -0
  72. package/packages/mailx-settings/docs/outlook.md +80 -18
  73. package/packages/mailx-settings/docs/rules-design.md +172 -0
  74. package/packages/mailx-settings/index.d.ts.map +1 -1
  75. package/packages/mailx-settings/index.js +9 -8
  76. package/packages/mailx-settings/index.js.map +1 -1
  77. package/packages/mailx-settings/index.ts +9 -8
  78. package/packages/mailx-settings/package.json +1 -1
  79. package/packages/mailx-store/db.d.ts +50 -1
  80. package/packages/mailx-store/db.d.ts.map +1 -1
  81. package/packages/mailx-store/db.js +120 -6
  82. package/packages/mailx-store/db.js.map +1 -1
  83. package/packages/mailx-store/db.ts +126 -6
  84. package/packages/mailx-store/package.json +1 -1
  85. package/packages/mailx-types/contact-rules.d.ts +1 -1
  86. package/packages/mailx-types/contact-rules.js +1 -1
  87. package/packages/mailx-types/contact-rules.js.map +1 -1
  88. package/packages/mailx-types/contact-rules.jsonc +1 -1
  89. package/packages/mailx-types/contact-rules.ts +1 -1
  90. package/packages/mailx-types/package.json +1 -1
@@ -0,0 +1,131 @@
1
+ # Config files
2
+
3
+ Help text shown in-app next to each JSONC config file in the **Settings → Edit config files...** dialog. Each `##` section is matched by filename.
4
+
5
+ Comments (`//`, `/* */`) and trailing commas are allowed — mailx parses these files with a JSONC parser.
6
+
7
+ ## accounts.jsonc
8
+
9
+ Defines the mail accounts mailx syncs. Stored on Google Drive so it's shared across all your devices.
10
+
11
+ ```jsonc
12
+ {
13
+ "accounts": [
14
+ {
15
+ "id": "gmail", // short, unique, lowercase — used as folder name on disk
16
+ "name": "Bob", // display name used as the From: name
17
+ "email": "bob@gmail.com", // primary address for this account
18
+ "defaultSend": true, // pre-select this account in compose
19
+ "identityDomains": ["bob.ma"], // extra domains to recognize as "you" for reply-from detection
20
+ "sig": { // signature appended to NEW messages
21
+ "text": "Bob Frankston\nhttps://frankston.com"
22
+ }
23
+ },
24
+ {
25
+ "id": "work",
26
+ "name": "Bob",
27
+ "email": "bob@example.com",
28
+ "imap": { // explicit IMAP/SMTP for non-auto-detected providers
29
+ "host": "imap.example.com",
30
+ "port": 993,
31
+ "tls": true
32
+ },
33
+ "smtp": {
34
+ "host": "smtp.example.com",
35
+ "port": 465,
36
+ "tls": true
37
+ },
38
+ "spam": "Junk" // override the spam folder name for this account
39
+ }
40
+ ]
41
+ }
42
+ ```
43
+
44
+ **Fields:**
45
+
46
+ - `id` — unique, lowercase, no spaces. Changing it renames the local cache folder.
47
+ - `name` — display name used in the From: header.
48
+ - `email` — primary identity address.
49
+ - `defaultSend` — if multiple accounts, the one with `defaultSend: true` is pre-selected in compose.
50
+ - `identityDomains` — additional domains to recognize as yours (aliases, plus-addressing, forwarded domains). Used for reply auto-From detection.
51
+ - `imap` / `smtp` — server config. Omit for Gmail/Outlook/Yahoo/iCloud (auto-detected from the email domain).
52
+ - `spam` — override the spam/junk folder name if auto-detection picks the wrong one.
53
+ - `sig` — signature object. `text` is appended to NEW messages with the standard `-- ` separator (newlines preserved). Only applied to brand-new messages today; replies/forwards skip it. Future options will cover replies, per-account toggles, and a `"html": true` flag for raw HTML signatures.
54
+
55
+ Gmail accounts automatically use the Gmail REST API (no IMAP). Everything else uses IMAP via iflow-direct.
56
+
57
+ Restart mailx after editing to pick up changes.
58
+
59
+ ## allowlist.jsonc
60
+
61
+ Controls which remote images/content are allowed in the message viewer without the "Show images" banner.
62
+
63
+ ```jsonc
64
+ {
65
+ "domains": [
66
+ "github.com", // wildcard subdomains implied
67
+ "stripe.com",
68
+ "list-manage.com" // Mailchimp tracking — remove if you want to block
69
+ ],
70
+ "senders": [
71
+ "notifications@github.com", // full address match
72
+ "*@anthropic.com" // wildcard: anything from this domain
73
+ ]
74
+ }
75
+ ```
76
+
77
+ **Fields:**
78
+
79
+ - `domains` — remote images are fetched if their host matches (with subdomain wildcarding).
80
+ - `senders` — messages from these senders get images auto-loaded regardless of image host.
81
+
82
+ Content-Security-Policy still blocks scripts regardless — this only affects image/CSS loading.
83
+
84
+ ## clients.jsonc
85
+
86
+ Per-device registrations. Each mailx install writes its hostname here so you can see which devices have synced recently.
87
+
88
+ ```jsonc
89
+ {
90
+ "clients": [
91
+ {
92
+ "id": "desktop-abc123", // auto-generated on first run
93
+ "hostname": "MYPC",
94
+ "platform": "win32",
95
+ "lastSeen": "2026-04-18T14:00:00Z"
96
+ }
97
+ ]
98
+ }
99
+ ```
100
+
101
+ Normally there's no reason to edit this by hand. Useful for removing stale device entries, or migrating a device to a new ID.
102
+
103
+ ## config.jsonc
104
+
105
+ **Local per-machine configuration**, stored at `~/.mailx/config.jsonc`. This file is NOT synced to the cloud — it's how each machine knows where the shared config lives and which store path to use.
106
+
107
+ ```jsonc
108
+ {
109
+ "sharedDir": {
110
+ "provider": "gdrive", // cloud provider (only "gdrive" supported)
111
+ "path": "mailx", // folder name on Google Drive
112
+ "folderId": "1ABC...xyz" // GDrive folder ID (API-resolved, don't edit)
113
+ },
114
+ "storePath": "C:/Users/Bob/.mailx/store", // where .eml message bodies live locally
115
+
116
+ "accountOverrides": { // optional: per-machine overrides to accounts.jsonc
117
+ "work": {
118
+ "imap": { "host": "127.0.0.1", "port": 1143 } // e.g., tunnel on this machine only
119
+ }
120
+ }
121
+ }
122
+ ```
123
+
124
+ **Fields:**
125
+
126
+ - `sharedDir.provider` — cloud storage. Currently only `gdrive`.
127
+ - `sharedDir.path` / `folderId` — the folder on the cloud where shared config + cache metadata live.
128
+ - `storePath` — where `.eml` bodies are cached locally. Changing requires moving existing files.
129
+ - `accountOverrides` — override parts of an account's config on this machine only (different IMAP host when behind a VPN, for example). Keys match the `id` in accounts.jsonc.
130
+
131
+ Delete and re-run `mailx -setup` to reconfigure from scratch.
@@ -0,0 +1,98 @@
1
+ # Edit-in-Word .docx round-trip — design
2
+
3
+ > Replaces the current .html round-trip so saving in Word "just works" without
4
+ > Save-As-Web-Page gymnastics. Adds a focus-back hint on save so the user
5
+ > doesn't have to Alt-Tab manually.
6
+
7
+ ## Why
8
+
9
+ Today, Edit-in-Word writes `<editId>.html` and watches it. Word opens it but
10
+ when the user hits Ctrl+S, Word's default save format is **.docx**, not .html.
11
+ Word writes a *new* `<editId>.docx` file; the watcher misses the save; the
12
+ user's edits never reload into rmfmail.
13
+
14
+ Two options previously discussed (B watch-both-extensions; C inline hint).
15
+ Picking **A** here: pre-convert to .docx so Word's natural save flow lands in
16
+ the file we're watching.
17
+
18
+ ## Pieces
19
+
20
+ 1. **HTML → DOCX** at edit-start. Library: `html-to-docx` (Node, ~80KB,
21
+ actively maintained). Output is a Node `Buffer`. Write to
22
+ `~/.rmfmail/external-edit/<editId>.docx`.
23
+ 2. **Watch `<editId>.docx`** instead of `.html`. Same `fs.watch(dir)` mechanic;
24
+ filter by `<editId>.docx` rather than `<editId>.html`.
25
+ 3. **DOCX → HTML** on save. Library: `mammoth` (Node, ~250KB, the de facto
26
+ DOCX-to-HTML converter). Output is HTML string. Strip its outer wrapper.
27
+ Emit `wordEditUpdated` as today.
28
+ 4. **Focus rmfmail on save**. After parsing the docx, post a host-level
29
+ "focus this window" message via msger. On Windows that's
30
+ `SetForegroundWindow` on the WebView2 hwnd — msger already has the hwnd
31
+ from window create. Add a `focusWindow` IPC method.
32
+ 5. **In-compose external-edit indicator**. While `wordEditId` is set, the
33
+ compose body editor area shows a non-editable status panel:
34
+
35
+ ```
36
+ ╭───────────────────────────────────────╮
37
+ │ Editing in Word… │
38
+ │ Save in Word → reloads here. │
39
+ │ │
40
+ │ [ Reload now ] [ Send ] [ Discard ] │
41
+ ╰───────────────────────────────────────╯
42
+ ```
43
+
44
+ - **Reload now**: read the .docx and parse manually (in case the watcher
45
+ missed an event)
46
+ - **Send**: parse current .docx, set as body, fire send (skips the
47
+ intermediate "switch back to compose, click Send" step)
48
+ - **Discard**: stop the watcher, delete the temp file, restore original body
49
+
50
+ ## Library deps
51
+
52
+ | Library | Direction | Size | Notes |
53
+ |---|---|---|---|
54
+ | `html-to-docx` | HTML → DOCX | ~80KB | Pure JS Node. `await asBlob(html)` returns a Buffer. |
55
+ | `mammoth` | DOCX → HTML | ~250KB | De facto standard. Lossy on some Word features (text effects, complex tables) but covers email-shaped content. |
56
+
57
+ Total ~330KB added to mailx-service. Acceptable; only loaded on Edit-in-Word
58
+ click via dynamic `import()` so cold start cost is zero.
59
+
60
+ ## What we lose
61
+
62
+ - Round-trip fidelity isn't perfect. Word's docx supports things mammoth
63
+ doesn't (footnotes, comments, advanced styling). For email composition this
64
+ is fine — users aren't writing footnotes — but worth knowing.
65
+ - Rich images: html-to-docx handles inline images (data: URIs) reasonably;
66
+ mammoth round-trips them as `<img src="data:...">`. Should work but needs
67
+ testing.
68
+
69
+ ## Migration
70
+
71
+ - Existing watchers at `<editId>.html` keep working until the user reopens an
72
+ Edit-in-Word session. New session uses .docx.
73
+ - Optionally clean up old `external-edit/*.html` files at startup.
74
+
75
+ ## Implementation order
76
+
77
+ 1. `npm install html-to-docx mammoth` in mailx-service.
78
+ 2. Replace `.html` write at `mailx-service/index.ts:660` with .docx via
79
+ html-to-docx.
80
+ 3. Replace watcher target at line 745 with `<editId>.docx`.
81
+ 4. Replace read+parse at line 755 with mammoth → HTML.
82
+ 5. Add `focusWindow` IPC + msger hook.
83
+ 6. Wire in-compose external-edit indicator panel (compose.ts).
84
+ 7. Test with a Word install on Windows.
85
+ 8. Skip Word path on Mac (Mac Word's docx behavior is the same; should work
86
+ without changes — but verify).
87
+
88
+ ## Open questions
89
+
90
+ - Does Mac Word treat .docx the same as Windows Word? (Should — same file
91
+ format. Verify after impl.)
92
+ - LibreOffice fallback: opens .docx natively, saves to same .docx by default.
93
+ No special handling needed.
94
+ - Default editor on Linux is usually LibreOffice; Word path n/a.
95
+
96
+ ## Status
97
+
98
+ Design captured; implementation deferred until user confirms scope.
@@ -0,0 +1,169 @@
1
+ # mailx Host Abstraction + msgview Parity Plan
2
+
3
+ **Status:** step 1 (abstraction package) implemented 2026-04-20. msgview adapter (step 2+) deferred until a Mac client or arm64-without-webkit fallback is actually needed. Resume cold from this doc.
4
+
5
+ ## Why this exists
6
+
7
+ 1. **msger covers most of what we need.** Windows + mainstream x86_64 Linux run msger fine today. The Rust/wry binary links against `libwebkit2gtk-4.1.so.0`; on Debian trixie / Raspberry Pi OS arm64 you install it once (`sudo apt install libwebkit2gtk-4.1-0`) and msger works. Not a regression, just a missing system dep. Older distros with only webkit2gtk 4.0 would need an msger rebuild.
8
+ 2. **msgview fills specific gaps.** Electron's bundled Chromium is the natural path for Mac (where msger would need separate webkit work) and a fallback for niche Linux systems where webkit2gtk-4.1 isn't available and can't be installed. It also sidesteps two msger pain points — multi-monitor drag bugs and WebView2 Evergreen auto-update on Windows — but those aren't load-bearing reasons to move existing Windows/Linux users off msger.
9
+ 3. **We want mailx to run on both without the call sites caring.** mailx imports a host abstraction; the abstraction picks msger or msgview at runtime. Step 1 (done 2026-04-20) puts the seam in place even before msgview lands, so future platform work is an adapter drop-in instead of a call-site rewrite.
10
+ 4. **msger and msgview must stay app-agnostic.** Architectural rule from mailx/CLAUDE.md: *"msger MUST have ZERO knowledge of mailx, iflow, or any application code."* Same rule extends to msgview. Any mailx-specific glue living in the wrappers today must move to mailx. The 2026-04-20 `_msgapi*` service-callback rename in msger Rust was the prerequisite that made both hosts share one generic JS contract.
11
+
12
+ ## Current coupling (survey)
13
+
14
+ Searched msger + msgview for `mailx` references:
15
+
16
+ - **msger/shower.ts:125** — doc comment on `setAppName()` says `e.g. setAppName("mailx")`. That's a doc reference only; `setAppName` itself is generic (app passes its own name for per-user bin dir + AUMID). OK to leave, or reword the example to `"myapp"`.
17
+ - **msger/msger-plan.md:70** — explicit rule `"mailxapi.js content (belongs in mailx)"`. Already correct.
18
+ - **msger Rust src** — no mailx/iflow/oauth references. Clean.
19
+ - **msgview** — no mailx references. Pristine.
20
+
21
+ **Conclusion:** the wrappers are already app-agnostic in code. The factoring work is on the mailx side: today mailx calls `showMessageBox`/`showService` directly from `@bobfrankston/msger`. We introduce a host abstraction so the import swap is one line, and document the contract so the next app (msga, etc.) can use the same interface.
22
+
23
+ ## Current mailx → msger surface
24
+
25
+ From `bin/mailx.ts:825-900` and `client/lib/mailxapi.js`:
26
+
27
+ ```ts
28
+ import { showMessageBox, showService, setAppName } from "@bobfrankston/msger";
29
+
30
+ // Desktop launch:
31
+ const handle = showService({
32
+ title, url, contentDir, initScript, icon, aumid,
33
+ size: { width, height }, pos: { x, y },
34
+ escapeCloses: false,
35
+ });
36
+
37
+ handle.onRequest(async (req) => { /* dispatch req._action → MailxService */ });
38
+ handle.send({ _cbid, result }); // responses and push events
39
+ // handle.close(); handle.result (promise resolving on window close)
40
+ ```
41
+
42
+ **In-WebView side** (`client/lib/mailxapi.js` injected via `initScript`):
43
+ - Exposes `window.callNode(action, args) → Promise<result>` built on `window.msgapi.sendToHost(...)`
44
+ - Receives server-push events via the IPC channel
45
+ - `api-client.ts` auto-detects IPC vs HTTP and routes through `callNode` when IPC is present
46
+
47
+ **That's the entire surface.** Small and stable.
48
+
49
+ ## The `MailxHost` interface
50
+
51
+ One interface, two implementations. Lives in mailx (not in msger/msgview).
52
+
53
+ ```ts
54
+ // packages/mailx-host/index.ts
55
+ export interface MailxHostOptions {
56
+ title: string;
57
+ url: string; // relative to contentDir (e.g. "index.html")
58
+ contentDir: string; // served via custom protocol
59
+ initScript: string; // mailxapi.js contents, injected pre-page
60
+ icon?: string;
61
+ aumid?: string; // Windows taskbar identity
62
+ size?: { width: number; height: number };
63
+ pos?: { x: number; y: number };
64
+ escapeCloses?: boolean;
65
+ }
66
+
67
+ export interface MailxHostHandle {
68
+ onRequest(fn: (req: any) => void | Promise<void>): void;
69
+ send(msg: any): void; // push to WebView
70
+ close(): void;
71
+ result: Promise<{ closed: boolean }>;
72
+ }
73
+
74
+ export interface MailxHost {
75
+ setAppName(name: string): void;
76
+ showService(opts: MailxHostOptions): MailxHostHandle;
77
+ }
78
+ ```
79
+
80
+ Two impls:
81
+
82
+ - **`packages/mailx-host-msger/index.ts`** — wraps `@bobfrankston/msger`, delegates 1:1. ~30 LOC.
83
+ - **`packages/mailx-host-msgview/index.ts`** — wraps Electron. Creates `BrowserWindow`, installs `protocol.handle()` for `contentDir`, sets `webPreferences.preload` pointing at a shim that loads the `initScript` string and exposes the same `window.msgapi` / `window.callNode` surface, wires `ipcMain.handle` ↔ `handle.onRequest` / `webContents.send` ↔ `handle.send`. ~150 LOC including the preload shim.
84
+
85
+ Selection at runtime in `bin/mailx.ts`: env var `MAILX_HOST=msger|msgview` (default msger on Windows, msgview on Linux/Mac), or `--host=` flag.
86
+
87
+ ## msgview parity checklist (from msgx/parity.md)
88
+
89
+ Mapping each msger capability to Electron:
90
+
91
+ | msger feature | Electron equivalent | Work needed |
92
+ |---|---|---|
93
+ | Service mode (bidir IPC via stdin/stdout) | `ipcMain.handle` / `webContents.send` | Wire in host shim. Electron's IPC is in-process, strictly better than JSON-line stdio. |
94
+ | `initScript` injection | `webPreferences.preload` | Preload shim evaluates the injected script string in page context before load. |
95
+ | `msger.localhost` custom protocol | `protocol.handle()` (Electron 25+) | Register in host shim on `app.whenReady()`. |
96
+ | `window.msgapi` (TCP/UDP/HTTP, UI control) | Expose via preload + `contextBridge` | Re-implement the subset mailx uses — TCP for iflow transport, UI control (resize, minimize, etc.). Node APIs available in main process, expose narrow surface. |
97
+ | AUMID for taskbar pin | `app.setAppUserModelId(aumid)` | One line. |
98
+ | Navigation gating (same-origin stays, cross-origin external) | `webContents.setWindowOpenHandler` + `will-navigate` | Reuse msger's `url_origin()` logic. |
99
+ | Multi-monitor drag | Chromium native | Disappears for free — msger's `ScaleFactorChanged` bug doesn't exist here. |
100
+ | WebView2 Evergreen auto-update killing windows | N/A | Chromium bundled, doesn't apply. |
101
+ | Stable exe path / timestamped-copy dance | N/A | Electron installer handles this differently (Squirrel/NSIS); no hardlink trick needed. |
102
+ | EPERM cleanup on npm upgrade | N/A | Different install mechanism. |
103
+ | **Detach mode** (child outlives caller) | Harder | See below. |
104
+ | Log path option | Electron `app.setPath("logs", ...)` | Trivial. |
105
+
106
+ ### Detach mode — the one real design decision
107
+
108
+ msger's detach mode lets the mailx launcher exit while the window keeps running in a forked msger process. Electron doesn't have a natural equivalent — Electron apps are one process tree rooted at the Electron main process.
109
+
110
+ **Option A: launch Electron as a separate binary.** Install `msgview` as a CLI (`msgview --url=… --content-dir=… --init-script=…`), mailx's host-msgview shim spawns it detached. Closest analog to msger's current model. Downside: two processes (mailx service + Electron), and the service must manage IPC over stdio again.
111
+
112
+ **Option B: run mailx service INSIDE the Electron main process.** mailx becomes an Electron app. `bin/mailx.ts` bootstraps Electron, creates `MailxService` + `ImapManager` in the main process, opens `BrowserWindow` with preload. Single process, IPC is in-process calls. No detach needed — the window IS the service. This is cleaner and matches how msgview is already structured for its own use case.
113
+
114
+ **Recommendation: Option B.** Requires `bin/mailx.ts` to detect host mode and, when msgview is selected, re-exec under Electron (or use Electron as the node runtime from the start). The `mailx` CLI entry still works — it just launches Electron instead of plain node. For `--server` mode we keep plain node (no window needed, so no Electron).
115
+
116
+ ## Factoring plan — keep msger/msgview independent of mailx
117
+
118
+ Where mailx-specific code lives today and where it needs to end up:
119
+
120
+ | Code | Today | Target |
121
+ |---|---|---|
122
+ | `client/lib/mailxapi.js` | mailx | mailx (unchanged — already correct) |
123
+ | `packages/mailx-service/jsonrpc.ts` | mailx | mailx (unchanged) |
124
+ | `bin/mailx.ts` direct `import "@bobfrankston/msger"` | mailx | Replace with `import { getHost } from "@mailx/host"` |
125
+ | `showService` options shape (title, url, initScript, aumid, etc.) | msger-specific type | Move to `packages/mailx-host/` — msger stays generic, mailx owns the mailx-flavored option shape |
126
+ | `setAppName("mailx")` | called from mailx | Still called from mailx, but through the host abstraction |
127
+ | `window.msgapi` surface | msger defines it | Keep as msger's native API. `mailx-host-msgview` re-implements the same shape via Electron preload so mailx code in the WebView doesn't care which host it's under. |
128
+ | Per-user bin dir `%LOCALAPPDATA%\mailx\bin\` | msger's `getUserBinDir(_appName)` | Unchanged — `setAppName("mailx")` already drives this generically |
129
+
130
+ **Net effect:** msger and msgview keep zero mailx references. mailx gains three small packages (`mailx-host`, `mailx-host-msger`, `mailx-host-msgview`) that encapsulate the coupling. Next app (msga, etc.) can either copy the `mailx-host-*` pattern or use msger/msgview directly.
131
+
132
+ ### The `window.msgapi` question
133
+
134
+ mailx code running in the WebView currently calls `window.msgapi.sendToHost(...)`. That's a msger-defined global. Two paths:
135
+
136
+ - **Keep the name.** `mailx-host-msgview`'s preload exposes `window.msgapi` with the same shape. mailx WebView code is unchanged. msger owns the de-facto standard; msgview implements it.
137
+ - **Rename to `window.mailxHost`.** mailx owns the name; msger and msgview each expose it via their respective injection mechanisms. Cleaner separation but more churn.
138
+
139
+ **Recommendation: keep `window.msgapi`** — it's already documented as a generic host API, msger defines the canonical shape, and msgview implementing it keeps the WebView code host-agnostic without rename churn.
140
+
141
+ ## Migration sequence
142
+
143
+ 1. **Create `packages/mailx-host`** with the interface + msger impl. Swap `bin/mailx.ts` to use it. Behavior identical; this is pure refactor. **Done 2026-04-20.** The package re-exports `showMessageBox` / `showService` / `setAppName` from msger and exposes a `selectHost()` seam for future impl dispatch. `MAILX_HOST=msger|msgview` env var honored; default is msger on Windows + Linux, would-be msgview on Mac (throws today until step 2 lands).
144
+ 2. **Create `packages/mailx-host-msgview`** (deferred until Mac or arm64-fallback need). Add Electron as a dependency. Implement the shim. Add `msgview` package to `y:/dev/utils/msgx/msgview/` if it doesn't already expose a programmatic API (needs check — msgview currently is a standalone JSON viewer, may need its guts extracted into a library).
145
+ 3. **Detach decision** (deferred). Pick Option B. Add Electron bootstrap path to `bin/mailx.ts`: when `MAILX_HOST=msgview`, re-exec under Electron. Test on Linux (pi5a) and Windows.
146
+ 4. **Port `window.msgapi` surface** used by mailx into the msgview preload. Audit `client/lib/mailxapi.js` for every `msgapi.*` call — that's the minimum surface to re-implement.
147
+ 5. **Iflow transport under Electron main.** `NodeTcpTransport` already uses `node:net` / `node:tls`; Electron main has full node, so this should be drop-in. Verify TLS works (Electron bundles OpenSSL, not system).
148
+ 6. **Build + packaging.** Decide: separate npm packages for `mailx-host-msger` and `mailx-host-msgview`, or one package with lazy-loaded impls? Probably lazy-load so Electron isn't dragged in for Windows users. Host selection picks impl; unused impl is never required.
149
+ 7. **Documentation** in msgx/parity.md: update with msgview side. (Step 1 doc updates done 2026-04-20.)
150
+
151
+ ## Open questions
152
+
153
+ - **Is msgview already a library, or is it a standalone app?** Need to check `y:/dev/utils/msgx/msgview/index.ts` — if it's CLI-only, we either (a) extract its core into a library `@bobfrankston/msgview-core` or (b) just build the Electron shell directly inside `mailx-host-msgview`. (b) is simpler if msgview's current feature set doesn't include anything we need to reuse.
154
+ - **Windows: keep msger-only.** msger on Windows works well. msgview on Windows stays available via explicit `MAILX_HOST=msgview` for users who want to trade WebView2 Evergreen risk for Electron's bundle size.
155
+ - **Auto-update.** msger has timestamped-exe + stable-hardlink. Electron has Squirrel auto-update. Different mechanisms — do we care about parity here or run two update strategies?
156
+ - **AUMID across hosts.** `com.frankston.mailx` should be the same on both so taskbar pins work whichever host is running.
157
+ - **pi5a as a build host.** User mentioned moving compile tooling from pi4c to pi5a. That's a separate decision, but relevant context: Rust cross-compile for msger aarch64 is still the primary Linux arm64 path (apt-install the webkit2gtk-4.1 system dep is cheap). msgview arm64 only matters if a specific user ends up on a distro that can't provide webkit2gtk-4.1.
158
+
159
+ ## Files to read when resuming
160
+
161
+ - `y:/dev/utils/msgx/parity.md` — the authoritative msger feature list
162
+ - `y:/dev/utils/msgx/msger/shower.ts` — current msger API surface
163
+ - `y:/dev/utils/msgx/msger/msger-plan.md` — msger's independence rule (line 3)
164
+ - `y:/dev/utils/msgx/msgview/index.ts` + `main.ts` — see what msgview exposes today
165
+ - `y:/dev/email/mailx/bin/mailx.ts:820-900` — mailx's current msger usage
166
+ - `y:/dev/email/mailx/client/lib/mailxapi.js` — the in-WebView IPC bridge (full `window.msgapi` surface used by mailx)
167
+ - `y:/dev/email/mailx/client/lib/api-client.ts` — IPC vs HTTP auto-detection
168
+ - `y:/dev/email/mailx/packages/mailx-service/jsonrpc.ts` — dispatcher (stays unchanged)
169
+ - `y:/dev/email/mailx/CLAUDE.md` — architectural rules (msger independence, transport injection)