@bobfrankston/mailx 1.0.9 → 1.0.13
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 +47 -29
- package/bin/postinstall.js +41 -0
- package/client/app.js +93 -13
- package/client/components/folder-tree.js +84 -3
- package/client/components/message-list.js +134 -8
- package/client/components/message-viewer.js +130 -13
- package/client/compose/compose.html +4 -4
- package/client/compose/compose.js +53 -34
- package/client/index.html +33 -9
- package/client/lib/api-client.js +102 -30
- package/client/lib/mailxapi.js +123 -0
- package/client/package.json +5 -1
- package/client/styles/components.css +188 -15
- package/client/styles/layout.css +2 -1
- package/killmail.cmd +6 -0
- package/launch.ps1 +48 -3
- package/launcher/bin/mailx-app.exe +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Breadcrumbs +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/metadata +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/settings.dat +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Crashpad/throttle_store.dat +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/CrashpadMetrics-active.pma +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/BrowsingTopicsSiteData +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Cache/No_Vary_Search/journal.baj +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DIPS +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/DashTrackerDatabase +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/EdgeJourneys/EdgeJourneys.db +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Rules/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension Scripts/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Extension State/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityComp +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ExtensionActivityEdge +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Favicons +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/History-journal +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/IndexedDB/devtools_devtools_0.indexeddb.leveldb/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Local Storage/leveldb/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Login Data For Account +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Cookies +0 -0
- NEL +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network/Trust Tokens +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Network Action Predictor +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Preferences +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Safe Browsing Network/Safe Browsing Cookies +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/ServerCertificate +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Session Storage/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shared Dictionary/db +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/SharedStorage +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Shortcuts +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Site Characteristics Database/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Sync Data/LevelDB/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Top Sites +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Vpn Tokens +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/Web Data-journal +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/WebStorage/QuotaManager-journal +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/heavy_ad_intervention_opt_out.db +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOCK +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/LOG +3 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Default/shared_proto_db/metadata/MANIFEST-000001 +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/DeferredBrowserMetrics/BrowserMetrics-69CAD063-BE24.pma +0 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Local State +1 -0
- package/launcher/bin/mailx-app.exe.WebView2/EBWebView/Variations +1 -0
- package/launcher/bin/mailx-app.old.exe +0 -0
- package/launcher/mailx.ico +0 -0
- package/npmg.bat +6 -0
- package/package.json +9 -3
- package/packages/mailx-api/index.js +79 -26
- package/packages/mailx-api/package.json +5 -1
- package/packages/mailx-compose/package.json +5 -1
- package/packages/mailx-core/index.d.ts +129 -0
- package/packages/mailx-core/index.js +323 -0
- package/packages/mailx-core/ipc.d.ts +13 -0
- package/packages/mailx-core/ipc.js +56 -0
- package/packages/mailx-core/package.json +18 -0
- package/packages/mailx-imap/index.d.ts +5 -1
- package/packages/mailx-imap/index.js +76 -14
- package/packages/mailx-imap/package.json +5 -1
- package/packages/mailx-send/package.json +12 -2
- package/packages/mailx-server/index.js +42 -31
- package/packages/mailx-server/package.json +5 -2
- package/packages/mailx-settings/index.d.ts +1 -1
- package/packages/mailx-settings/index.js +21 -12
- package/packages/mailx-settings/package.json +5 -1
- package/packages/mailx-store/db.d.ts +5 -1
- package/packages/mailx-store/db.js +64 -12
- package/packages/mailx-store/file-store.d.ts +2 -8
- package/packages/mailx-store/file-store.js +7 -31
- package/packages/mailx-store/package.json +5 -1
- package/packages/mailx-types/index.d.ts +3 -1
- package/packages/mailx-types/package.json +6 -2
- package/.tswalk.json +0 -3366
- package/mailx.json +0 -9
- package/packages/mailx-api/node_modules/nodemailer/.ncurc.js +0 -9
- package/packages/mailx-api/node_modules/nodemailer/.prettierignore +0 -8
- package/packages/mailx-api/node_modules/nodemailer/.prettierrc +0 -12
- package/packages/mailx-api/node_modules/nodemailer/.prettierrc.js +0 -10
- package/packages/mailx-api/node_modules/nodemailer/.release-please-config.json +0 -9
- package/packages/mailx-api/node_modules/nodemailer/LICENSE +0 -16
- package/packages/mailx-api/node_modules/nodemailer/README.md +0 -86
- package/packages/mailx-api/node_modules/nodemailer/SECURITY.txt +0 -22
- package/packages/mailx-api/node_modules/nodemailer/eslint.config.js +0 -88
- package/packages/mailx-api/node_modules/nodemailer/lib/addressparser/index.js +0 -383
- package/packages/mailx-api/node_modules/nodemailer/lib/base64/index.js +0 -139
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/index.js +0 -253
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
- package/packages/mailx-api/node_modules/nodemailer/lib/dkim/sign.js +0 -117
- package/packages/mailx-api/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
- package/packages/mailx-api/node_modules/nodemailer/lib/fetch/index.js +0 -280
- package/packages/mailx-api/node_modules/nodemailer/lib/json-transport/index.js +0 -82
- package/packages/mailx-api/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
- package/packages/mailx-api/node_modules/nodemailer/lib/mailer/index.js +0 -441
- package/packages/mailx-api/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
- package/packages/mailx-api/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
- package/packages/mailx-api/node_modules/nodemailer/lib/nodemailer.js +0 -157
- package/packages/mailx-api/node_modules/nodemailer/lib/punycode/index.js +0 -460
- package/packages/mailx-api/node_modules/nodemailer/lib/qp/index.js +0 -227
- package/packages/mailx-api/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
- package/packages/mailx-api/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
- package/packages/mailx-api/node_modules/nodemailer/lib/shared/index.js +0 -754
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
- package/packages/mailx-api/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
- package/packages/mailx-api/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
- package/packages/mailx-api/node_modules/nodemailer/lib/well-known/index.js +0 -47
- package/packages/mailx-api/node_modules/nodemailer/lib/well-known/services.json +0 -611
- package/packages/mailx-api/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
- package/packages/mailx-api/node_modules/nodemailer/package.json +0 -47
- package/packages/mailx-imap/node_modules/nodemailer/.ncurc.js +0 -9
- package/packages/mailx-imap/node_modules/nodemailer/.prettierignore +0 -8
- package/packages/mailx-imap/node_modules/nodemailer/.prettierrc +0 -12
- package/packages/mailx-imap/node_modules/nodemailer/.prettierrc.js +0 -10
- package/packages/mailx-imap/node_modules/nodemailer/.release-please-config.json +0 -9
- package/packages/mailx-imap/node_modules/nodemailer/LICENSE +0 -16
- package/packages/mailx-imap/node_modules/nodemailer/README.md +0 -86
- package/packages/mailx-imap/node_modules/nodemailer/SECURITY.txt +0 -22
- package/packages/mailx-imap/node_modules/nodemailer/eslint.config.js +0 -88
- package/packages/mailx-imap/node_modules/nodemailer/lib/addressparser/index.js +0 -383
- package/packages/mailx-imap/node_modules/nodemailer/lib/base64/index.js +0 -139
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/index.js +0 -253
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
- package/packages/mailx-imap/node_modules/nodemailer/lib/dkim/sign.js +0 -117
- package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
- package/packages/mailx-imap/node_modules/nodemailer/lib/fetch/index.js +0 -280
- package/packages/mailx-imap/node_modules/nodemailer/lib/json-transport/index.js +0 -82
- package/packages/mailx-imap/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
- package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/index.js +0 -441
- package/packages/mailx-imap/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
- package/packages/mailx-imap/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
- package/packages/mailx-imap/node_modules/nodemailer/lib/nodemailer.js +0 -157
- package/packages/mailx-imap/node_modules/nodemailer/lib/punycode/index.js +0 -460
- package/packages/mailx-imap/node_modules/nodemailer/lib/qp/index.js +0 -227
- package/packages/mailx-imap/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
- package/packages/mailx-imap/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
- package/packages/mailx-imap/node_modules/nodemailer/lib/shared/index.js +0 -754
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
- package/packages/mailx-imap/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
- package/packages/mailx-imap/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
- package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/index.js +0 -47
- package/packages/mailx-imap/node_modules/nodemailer/lib/well-known/services.json +0 -611
- package/packages/mailx-imap/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
- package/packages/mailx-imap/node_modules/nodemailer/package.json +0 -47
- package/packages/mailx-send/node_modules/nodemailer/.ncurc.js +0 -9
- package/packages/mailx-send/node_modules/nodemailer/.prettierignore +0 -8
- package/packages/mailx-send/node_modules/nodemailer/.prettierrc +0 -12
- package/packages/mailx-send/node_modules/nodemailer/.prettierrc.js +0 -10
- package/packages/mailx-send/node_modules/nodemailer/.release-please-config.json +0 -9
- package/packages/mailx-send/node_modules/nodemailer/LICENSE +0 -16
- package/packages/mailx-send/node_modules/nodemailer/README.md +0 -86
- package/packages/mailx-send/node_modules/nodemailer/SECURITY.txt +0 -22
- package/packages/mailx-send/node_modules/nodemailer/eslint.config.js +0 -88
- package/packages/mailx-send/node_modules/nodemailer/lib/addressparser/index.js +0 -383
- package/packages/mailx-send/node_modules/nodemailer/lib/base64/index.js +0 -139
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/index.js +0 -253
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/message-parser.js +0 -155
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
- package/packages/mailx-send/node_modules/nodemailer/lib/dkim/sign.js +0 -117
- package/packages/mailx-send/node_modules/nodemailer/lib/fetch/cookies.js +0 -281
- package/packages/mailx-send/node_modules/nodemailer/lib/fetch/index.js +0 -280
- package/packages/mailx-send/node_modules/nodemailer/lib/json-transport/index.js +0 -82
- package/packages/mailx-send/node_modules/nodemailer/lib/mail-composer/index.js +0 -629
- package/packages/mailx-send/node_modules/nodemailer/lib/mailer/index.js +0 -441
- package/packages/mailx-send/node_modules/nodemailer/lib/mailer/mail-message.js +0 -316
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/index.js +0 -625
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-funcs/mime-types.js +0 -2113
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/index.js +0 -1316
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/last-newline.js +0 -33
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-unix.js +0 -43
- package/packages/mailx-send/node_modules/nodemailer/lib/mime-node/le-windows.js +0 -52
- package/packages/mailx-send/node_modules/nodemailer/lib/nodemailer.js +0 -157
- package/packages/mailx-send/node_modules/nodemailer/lib/punycode/index.js +0 -460
- package/packages/mailx-send/node_modules/nodemailer/lib/qp/index.js +0 -227
- package/packages/mailx-send/node_modules/nodemailer/lib/sendmail-transport/index.js +0 -210
- package/packages/mailx-send/node_modules/nodemailer/lib/ses-transport/index.js +0 -234
- package/packages/mailx-send/node_modules/nodemailer/lib/shared/index.js +0 -754
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -108
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +0 -143
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-connection/index.js +0 -1870
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/index.js +0 -652
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +0 -259
- package/packages/mailx-send/node_modules/nodemailer/lib/smtp-transport/index.js +0 -421
- package/packages/mailx-send/node_modules/nodemailer/lib/stream-transport/index.js +0 -135
- package/packages/mailx-send/node_modules/nodemailer/lib/well-known/index.js +0 -47
- package/packages/mailx-send/node_modules/nodemailer/lib/well-known/services.json +0 -611
- package/packages/mailx-send/node_modules/nodemailer/lib/xoauth2/index.js +0 -427
- package/packages/mailx-send/node_modules/nodemailer/package.json +0 -47
package/bin/mailx.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* mailx — email client
|
|
3
|
+
* mailx — email client
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* mailx
|
|
7
|
-
* mailx --server Start
|
|
8
|
-
* mailx --no-browser
|
|
9
|
-
* mailx --external Bind to all interfaces (
|
|
6
|
+
* mailx Launch native app (IPC, no server)
|
|
7
|
+
* mailx --server Start Express server + open browser
|
|
8
|
+
* mailx --no-browser Server mode, don't open browser
|
|
9
|
+
* mailx --external Bind to all interfaces (server mode only)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
12
14
|
import net from "node:net";
|
|
13
15
|
|
|
14
16
|
const PORT = 9333;
|
|
15
17
|
const args = process.argv.slice(2);
|
|
18
|
+
const serverMode = args.includes("--server");
|
|
19
|
+
const noBrowser = args.includes("--no-browser");
|
|
16
20
|
|
|
17
21
|
function isPortInUse(port) {
|
|
18
22
|
return new Promise((resolve) => {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
server.listen(port, "127.0.0.1");
|
|
23
|
+
const socket = net.createConnection({ port, host: "127.0.0.1" });
|
|
24
|
+
socket.once("connect", () => { socket.destroy(); resolve(true); });
|
|
25
|
+
socket.once("error", () => resolve(false));
|
|
23
26
|
});
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -32,33 +35,48 @@ function openBrowser(url) {
|
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
async function main() {
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
if (serverMode) {
|
|
39
|
+
// Server mode — Express + WebSocket, open browser
|
|
40
|
+
const inUse = await isPortInUse(PORT);
|
|
41
|
+
if (inUse) {
|
|
42
|
+
console.log("mailx server already running");
|
|
43
|
+
if (!noBrowser) openBrowser(`http://localhost:${PORT}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log("Starting mailx server...");
|
|
48
|
+
if (args.includes("--external")) process.argv.push("--external");
|
|
49
|
+
await import("../packages/mailx-server/index.js");
|
|
37
50
|
|
|
38
|
-
const inUse = await isPortInUse(PORT);
|
|
39
|
-
if (inUse) {
|
|
40
|
-
console.log("mailx is already running");
|
|
41
51
|
if (!noBrowser) {
|
|
52
|
+
for (let i = 0; i < 30; i++) {
|
|
53
|
+
await new Promise(r => setTimeout(r, 200));
|
|
54
|
+
if (await isPortInUse(PORT)) break;
|
|
55
|
+
}
|
|
42
56
|
openBrowser(`http://localhost:${PORT}`);
|
|
43
|
-
console.log("mailx opened");
|
|
57
|
+
console.log("mailx opened (browser)");
|
|
44
58
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
} else {
|
|
60
|
+
// Default: launch native WebView app with IPC
|
|
61
|
+
const launcherPaths = [
|
|
62
|
+
path.join(import.meta.dirname, "..", "launcher", "bin", "mailx-app.exe"),
|
|
63
|
+
path.join(import.meta.dirname, "..", "launcher", "target", "debug", "mailx-app.exe"),
|
|
64
|
+
];
|
|
51
65
|
|
|
52
|
-
|
|
53
|
-
await import("../packages/mailx-server/index.js");
|
|
66
|
+
let launcherPath = launcherPaths.find(p => fs.existsSync(p));
|
|
54
67
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
if (launcherPath) {
|
|
69
|
+
console.log("Starting mailx...");
|
|
70
|
+
const { spawn } = await import("node:child_process");
|
|
71
|
+
const child = spawn(launcherPath, args, { detached: true, stdio: "ignore" });
|
|
72
|
+
child.unref();
|
|
73
|
+
console.log("mailx launched");
|
|
74
|
+
} else {
|
|
75
|
+
// No native launcher — fall back to server mode + browser
|
|
76
|
+
console.log("Native launcher not found, falling back to server mode");
|
|
77
|
+
process.argv.push("--server");
|
|
78
|
+
await main(); // recurse with --server
|
|
59
79
|
}
|
|
60
|
-
openBrowser(`http://localhost:${PORT}`);
|
|
61
|
-
console.log("mailx opened");
|
|
62
80
|
}
|
|
63
81
|
}
|
|
64
82
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Post-install script: creates symlinks for workspace packages
|
|
4
|
+
* so they resolve as @bobfrankston/mailx-* in node_modules.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
const root = path.resolve(import.meta.dirname, "..");
|
|
11
|
+
const packagesDir = path.join(root, "packages");
|
|
12
|
+
const nmDir = path.join(root, "node_modules", "@bobfrankston");
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(packagesDir)) process.exit(0); // not in workspace layout
|
|
15
|
+
|
|
16
|
+
fs.mkdirSync(nmDir, { recursive: true });
|
|
17
|
+
|
|
18
|
+
for (const dir of fs.readdirSync(packagesDir)) {
|
|
19
|
+
const pkgPath = path.join(packagesDir, dir, "package.json");
|
|
20
|
+
if (!fs.existsSync(pkgPath)) continue;
|
|
21
|
+
|
|
22
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
23
|
+
const name = pkg.name?.split("/")[1]; // e.g., "mailx-store" from "@bobfrankston/mailx-store"
|
|
24
|
+
if (!name) continue;
|
|
25
|
+
|
|
26
|
+
const linkPath = path.join(nmDir, name);
|
|
27
|
+
const targetPath = path.join(packagesDir, dir);
|
|
28
|
+
|
|
29
|
+
if (fs.existsSync(linkPath)) continue; // already linked
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Use junction on Windows (no admin needed), symlink on Unix
|
|
33
|
+
if (process.platform === "win32") {
|
|
34
|
+
fs.symlinkSync(targetPath, linkPath, "junction");
|
|
35
|
+
} else {
|
|
36
|
+
fs.symlinkSync(targetPath, linkPath, "dir");
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error(`Failed to link ${name}: ${e.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
package/client/app.js
CHANGED
|
@@ -8,16 +8,19 @@ import { showMessage, getCurrentMessage } from "./components/message-viewer.js";
|
|
|
8
8
|
import { connectWebSocket, onWsEvent, triggerSync, getAccounts } from "./lib/api-client.js";
|
|
9
9
|
// ── Wire up components ──
|
|
10
10
|
const folderTree = document.getElementById("folder-tree");
|
|
11
|
+
let currentFolderSpecialUse = "";
|
|
11
12
|
initFolderTree(folderTree, (accountId, folderId, folderName, specialUse) => {
|
|
13
|
+
currentFolderSpecialUse = specialUse;
|
|
12
14
|
loadMessages(accountId, folderId, 1, specialUse);
|
|
13
15
|
document.title = `mailx - ${folderName}`;
|
|
14
16
|
}, () => {
|
|
15
17
|
// Unified inbox handler
|
|
18
|
+
currentFolderSpecialUse = "inbox";
|
|
16
19
|
loadUnifiedInbox();
|
|
17
20
|
document.title = "mailx - All Inboxes";
|
|
18
21
|
});
|
|
19
|
-
initMessageList((accountId, uid) => {
|
|
20
|
-
showMessage(accountId, uid);
|
|
22
|
+
initMessageList((accountId, uid, folderId) => {
|
|
23
|
+
showMessage(accountId, uid, folderId, currentFolderSpecialUse);
|
|
21
24
|
// Enable action buttons when a message is selected
|
|
22
25
|
for (const id of ["btn-reply", "btn-reply-all", "btn-forward", "btn-delete", "btn-flag"]) {
|
|
23
26
|
const btn = document.getElementById(id);
|
|
@@ -144,14 +147,27 @@ async function deleteCurrentMessage() {
|
|
|
144
147
|
if (statusSync?.textContent?.includes("Ctrl+Z"))
|
|
145
148
|
statusSync.textContent = "";
|
|
146
149
|
}, 30000);
|
|
147
|
-
//
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
// Remove the deleted row and select the next message
|
|
151
|
+
const mlBody = document.getElementById("ml-body");
|
|
152
|
+
if (mlBody) {
|
|
153
|
+
const deletedRow = mlBody.querySelector(`.ml-row[data-uid="${message.uid}"]`);
|
|
154
|
+
if (deletedRow) {
|
|
155
|
+
const nextRow = (deletedRow.nextElementSibling || deletedRow.previousElementSibling);
|
|
156
|
+
deletedRow.remove();
|
|
157
|
+
if (nextRow?.classList.contains("ml-row")) {
|
|
158
|
+
nextRow.click();
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// No more messages — clear preview
|
|
162
|
+
const bodyEl = document.getElementById("mv-body");
|
|
163
|
+
const headerEl = document.getElementById("mv-header");
|
|
164
|
+
if (bodyEl)
|
|
165
|
+
bodyEl.innerHTML = `<div class="mv-empty">Select a message to read</div>`;
|
|
166
|
+
if (headerEl)
|
|
167
|
+
headerEl.hidden = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
155
171
|
}
|
|
156
172
|
catch (e) {
|
|
157
173
|
console.error(`Delete failed: ${e.message}`);
|
|
@@ -220,6 +236,52 @@ searchInput?.addEventListener("keydown", (e) => {
|
|
|
220
236
|
reloadCurrentFolder();
|
|
221
237
|
}
|
|
222
238
|
});
|
|
239
|
+
// ── Reload message list after drag-move ──
|
|
240
|
+
document.addEventListener("mailx-message-moved", () => reloadCurrentFolder());
|
|
241
|
+
// ── Folder filter ──
|
|
242
|
+
const ftFilterInput = document.getElementById("ft-filter-input");
|
|
243
|
+
if (ftFilterInput) {
|
|
244
|
+
ftFilterInput.addEventListener("input", () => {
|
|
245
|
+
const query = ftFilterInput.value.toLowerCase();
|
|
246
|
+
const tree = document.getElementById("folder-tree");
|
|
247
|
+
if (!tree)
|
|
248
|
+
return;
|
|
249
|
+
if (!query) {
|
|
250
|
+
// Clear filter — show everything
|
|
251
|
+
tree.querySelectorAll(".ft-filter-hidden").forEach(el => el.classList.remove("ft-filter-hidden"));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Hide all folders first, then show matches + their parent accounts
|
|
255
|
+
const folders = tree.querySelectorAll(".ft-folder");
|
|
256
|
+
const accounts = tree.querySelectorAll(".ft-account");
|
|
257
|
+
for (const acct of accounts)
|
|
258
|
+
acct.classList.add("ft-filter-hidden");
|
|
259
|
+
for (const f of folders)
|
|
260
|
+
f.classList.add("ft-filter-hidden");
|
|
261
|
+
for (const f of folders) {
|
|
262
|
+
const name = f.querySelector(".ft-folder-name")?.textContent?.toLowerCase() || "";
|
|
263
|
+
if (name.includes(query)) {
|
|
264
|
+
f.classList.remove("ft-filter-hidden");
|
|
265
|
+
// Show parent account
|
|
266
|
+
const acct = f.closest(".ft-account");
|
|
267
|
+
if (acct)
|
|
268
|
+
acct.classList.remove("ft-filter-hidden");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Also show unified inbox if it matches
|
|
272
|
+
const unified = tree.querySelector(".ft-unified");
|
|
273
|
+
if (unified) {
|
|
274
|
+
const text = unified.textContent?.toLowerCase() || "";
|
|
275
|
+
unified.classList.toggle("ft-filter-hidden", !text.includes(query));
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
ftFilterInput.addEventListener("keydown", (e) => {
|
|
279
|
+
if (e.key === "Escape") {
|
|
280
|
+
ftFilterInput.value = "";
|
|
281
|
+
ftFilterInput.dispatchEvent(new Event("input"));
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
223
285
|
// ── Open links from email body in system browser ──
|
|
224
286
|
window.addEventListener("message", (e) => {
|
|
225
287
|
if (e.data?.type === "openLink" && e.data.url) {
|
|
@@ -260,16 +322,20 @@ if (splitter) {
|
|
|
260
322
|
connectWebSocket();
|
|
261
323
|
onWsEvent((event) => {
|
|
262
324
|
const statusSync = document.getElementById("status-sync");
|
|
325
|
+
const startupStatus = document.getElementById("startup-status");
|
|
263
326
|
switch (event.type) {
|
|
264
327
|
case "connected":
|
|
265
328
|
if (statusSync)
|
|
266
329
|
statusSync.textContent = "Connected";
|
|
267
|
-
|
|
268
|
-
|
|
330
|
+
if (startupStatus)
|
|
331
|
+
startupStatus.textContent = "Loading accounts...";
|
|
332
|
+
// Don't refresh folder tree on connect — it's already loaded by initFolderTree
|
|
269
333
|
break;
|
|
270
334
|
case "syncProgress":
|
|
271
335
|
if (statusSync)
|
|
272
336
|
statusSync.textContent = `Sync: ${event.phase} ${event.progress}%`;
|
|
337
|
+
if (startupStatus)
|
|
338
|
+
startupStatus.textContent = `Syncing ${event.accountId}: ${event.phase}`;
|
|
273
339
|
break;
|
|
274
340
|
case "folderCountsChanged": {
|
|
275
341
|
refreshFolderTree();
|
|
@@ -402,7 +468,10 @@ fetch("/api/version").then(r => r.json()).then(d => {
|
|
|
402
468
|
el.textContent = `mailx s${d.server}/c${d.client}${isApp ? "" : " [browser]"}`;
|
|
403
469
|
}).catch(async () => {
|
|
404
470
|
// Server not running — try to start it if we're in the app
|
|
471
|
+
const startupStatus = document.getElementById("startup-status");
|
|
405
472
|
if (isApp) {
|
|
473
|
+
if (startupStatus)
|
|
474
|
+
startupStatus.textContent = "Starting server...";
|
|
406
475
|
await mailxapi.ensureServer();
|
|
407
476
|
location.reload();
|
|
408
477
|
}
|
|
@@ -410,6 +479,8 @@ fetch("/api/version").then(r => r.json()).then(d => {
|
|
|
410
479
|
const el = document.getElementById("app-version");
|
|
411
480
|
if (el)
|
|
412
481
|
el.textContent = "mailx [server offline]";
|
|
482
|
+
if (startupStatus)
|
|
483
|
+
startupStatus.textContent = "Server offline — start with: node packages/mailx-server/index.js";
|
|
413
484
|
}
|
|
414
485
|
});
|
|
415
486
|
// ── Sync pending indicator ──
|
|
@@ -427,5 +498,14 @@ setInterval(async () => {
|
|
|
427
498
|
}
|
|
428
499
|
catch { /* offline */ }
|
|
429
500
|
}, 5000);
|
|
430
|
-
console.log("mailx client initialized");
|
|
501
|
+
console.log("mailx client initialized, location:", location.href);
|
|
502
|
+
// Diagnostic: test API connectivity (helps debug WebView2 blank screen)
|
|
503
|
+
fetch("/api/version").then(r => r.json()).then(d => {
|
|
504
|
+
console.log("API reachable:", d);
|
|
505
|
+
}).catch(e => {
|
|
506
|
+
console.error("API unreachable:", e.message);
|
|
507
|
+
const status = document.getElementById("startup-status");
|
|
508
|
+
if (status)
|
|
509
|
+
status.textContent = `Cannot reach server API: ${e.message}`;
|
|
510
|
+
});
|
|
431
511
|
//# sourceMappingURL=app.js.map
|
|
@@ -123,7 +123,16 @@ function renderNode(node, container, depth) {
|
|
|
123
123
|
nameSpan.className = "ft-folder-name";
|
|
124
124
|
nameSpan.textContent = node.name;
|
|
125
125
|
folderEl.appendChild(nameSpan);
|
|
126
|
-
|
|
126
|
+
const isOutbox = node.specialUse === "outbox" || node.path.toLowerCase() === "outbox";
|
|
127
|
+
if (isOutbox && node.totalCount > 0) {
|
|
128
|
+
// Outbox: show total (pending) count with warning style
|
|
129
|
+
const badge = document.createElement("span");
|
|
130
|
+
badge.className = "ft-badge ft-badge-outbox";
|
|
131
|
+
badge.textContent = String(node.totalCount);
|
|
132
|
+
badge.title = `${node.totalCount} pending`;
|
|
133
|
+
folderEl.appendChild(badge);
|
|
134
|
+
}
|
|
135
|
+
else if (node.unreadCount > 0) {
|
|
127
136
|
const badge = document.createElement("span");
|
|
128
137
|
badge.className = "ft-badge";
|
|
129
138
|
badge.textContent = String(node.unreadCount);
|
|
@@ -147,6 +156,58 @@ function renderNode(node, container, depth) {
|
|
|
147
156
|
selectedFolderId = node.id;
|
|
148
157
|
onFolderSelect(node.accountId, node.id, node.name, node.specialUse || node.path.toLowerCase());
|
|
149
158
|
});
|
|
159
|
+
// ── Drop target for message drag-and-drop ──
|
|
160
|
+
if (node.id !== -1) {
|
|
161
|
+
folderEl.addEventListener("dragover", (e) => {
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
e.dataTransfer.dropEffect = e.ctrlKey ? "copy" : "move";
|
|
164
|
+
folderEl.classList.add("drop-target");
|
|
165
|
+
});
|
|
166
|
+
folderEl.addEventListener("dragleave", () => folderEl.classList.remove("drop-target"));
|
|
167
|
+
folderEl.addEventListener("drop", async (e) => {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
folderEl.classList.remove("drop-target");
|
|
170
|
+
// Multi-message or single-message drop
|
|
171
|
+
const multiData = e.dataTransfer.getData("application/x-mailx-messages");
|
|
172
|
+
const singleData = e.dataTransfer.getData("application/x-mailx-message");
|
|
173
|
+
const messages = multiData ? JSON.parse(multiData) : singleData ? [JSON.parse(singleData)] : [];
|
|
174
|
+
// Filter: not already in target folder
|
|
175
|
+
const toMove = messages.filter(m => m.folderId !== node.id || m.accountId !== node.accountId);
|
|
176
|
+
if (toMove.length === 0)
|
|
177
|
+
return;
|
|
178
|
+
const statusEl = document.getElementById("status-sync");
|
|
179
|
+
const crossAccount = toMove.some(m => m.accountId !== node.accountId);
|
|
180
|
+
try {
|
|
181
|
+
let moved = 0;
|
|
182
|
+
for (const msg of toMove) {
|
|
183
|
+
const body = { targetFolderId: node.id };
|
|
184
|
+
if (msg.accountId !== node.accountId)
|
|
185
|
+
body.targetAccountId = node.accountId;
|
|
186
|
+
const res = await fetch(`/api/message/${msg.accountId}/${msg.uid}/move`, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: { "Content-Type": "application/json" },
|
|
189
|
+
body: JSON.stringify(body),
|
|
190
|
+
});
|
|
191
|
+
if (!res.ok) {
|
|
192
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
193
|
+
throw new Error(err.error);
|
|
194
|
+
}
|
|
195
|
+
moved++;
|
|
196
|
+
}
|
|
197
|
+
if (statusEl)
|
|
198
|
+
statusEl.textContent = `Moved ${moved} message${moved > 1 ? "s" : ""} to ${node.name}`;
|
|
199
|
+
const treeContainer = document.getElementById("folder-tree");
|
|
200
|
+
if (treeContainer)
|
|
201
|
+
loadFolderTree(treeContainer);
|
|
202
|
+
document.dispatchEvent(new CustomEvent("mailx-message-moved"));
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
console.error(`Move failed: ${err.message}`);
|
|
206
|
+
if (statusEl)
|
|
207
|
+
statusEl.textContent = `Move failed: ${err.message}`;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
150
211
|
container.appendChild(folderEl);
|
|
151
212
|
// Render children if expanded
|
|
152
213
|
if (hasChildren && isExpanded) {
|
|
@@ -161,13 +222,19 @@ export function initFolderTree(container, handler, unifiedHandler) {
|
|
|
161
222
|
loadFolderTree(container);
|
|
162
223
|
}
|
|
163
224
|
async function loadFolderTree(container) {
|
|
164
|
-
|
|
225
|
+
// Show loading state while preserving existing tree (if any) on refresh
|
|
226
|
+
const hadContent = container.children.length > 0 && !container.querySelector(".folder-loading");
|
|
227
|
+
if (!hadContent) {
|
|
228
|
+
container.innerHTML = `<div class="folder-loading">Loading accounts...</div>`;
|
|
229
|
+
}
|
|
165
230
|
try {
|
|
166
231
|
const accounts = await getAccounts();
|
|
167
232
|
if (accounts.length === 0) {
|
|
168
233
|
container.innerHTML = `<div class="folder-loading">No accounts configured.<br>Edit ~/.mailx/settings.jsonc</div>`;
|
|
169
234
|
return;
|
|
170
235
|
}
|
|
236
|
+
// Clear loading state now that we have data
|
|
237
|
+
container.innerHTML = "";
|
|
171
238
|
// Unified Inbox (if multiple accounts)
|
|
172
239
|
if (accounts.length > 1) {
|
|
173
240
|
const unifiedEl = document.createElement("div");
|
|
@@ -192,7 +259,7 @@ async function loadFolderTree(container) {
|
|
|
192
259
|
const accountExpanded = expandState[accountKey] !== false; // accounts default expanded
|
|
193
260
|
const header = document.createElement("div");
|
|
194
261
|
header.className = "ft-account-header";
|
|
195
|
-
header.textContent = `${accountExpanded ? "▾" : "▸"} ${account.name}`;
|
|
262
|
+
header.textContent = `${accountExpanded ? "▾" : "▸"} ${account.label || account.name}`;
|
|
196
263
|
header.addEventListener("click", () => {
|
|
197
264
|
expandState[accountKey] = !accountExpanded;
|
|
198
265
|
saveExpandState();
|
|
@@ -272,9 +339,23 @@ async function loadFolderTree(container) {
|
|
|
272
339
|
if (target)
|
|
273
340
|
target.click();
|
|
274
341
|
}
|
|
342
|
+
// Dismiss startup overlay once tree is loaded
|
|
343
|
+
const overlay = document.getElementById("startup-overlay");
|
|
344
|
+
if (overlay)
|
|
345
|
+
overlay.classList.add("hidden");
|
|
346
|
+
// Remove from DOM after transition
|
|
347
|
+
setTimeout(() => overlay?.remove(), 400);
|
|
275
348
|
}
|
|
276
349
|
catch (e) {
|
|
277
350
|
container.innerHTML = `<div class="folder-loading">Error loading folders: ${e.message}</div>`;
|
|
351
|
+
// Dismiss overlay on error too — show the error, not a spinner
|
|
352
|
+
const overlay = document.getElementById("startup-overlay");
|
|
353
|
+
if (overlay) {
|
|
354
|
+
const status = document.getElementById("startup-status");
|
|
355
|
+
if (status)
|
|
356
|
+
status.textContent = `Error: ${e.message}`;
|
|
357
|
+
setTimeout(() => { overlay.classList.add("hidden"); setTimeout(() => overlay.remove(), 400); }, 2000);
|
|
358
|
+
}
|
|
278
359
|
}
|
|
279
360
|
}
|
|
280
361
|
/** Refresh folder tree (e.g., after sync) */
|