@bobfrankston/mailx 1.0.15 → 1.0.16

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/client/app.js CHANGED
@@ -505,7 +505,8 @@ fetch("/api/version").then(r => r.json()).then(d => {
505
505
  startupStatus.textContent = "Server offline — start with: node packages/mailx-server/index.js";
506
506
  }
507
507
  });
508
- // ── Sync pending indicator ──
508
+ // ── Sync pending indicator + server health check ──
509
+ let serverDown = false;
509
510
  setInterval(async () => {
510
511
  try {
511
512
  const res = await fetch("/api/sync/pending");
@@ -517,8 +518,25 @@ setInterval(async () => {
517
518
  el.textContent = data.pending > 0 ? `↻ ${data.pending} pending` : "";
518
519
  el.style.color = data.pending > 0 ? "oklch(0.75 0.15 60)" : "";
519
520
  }
521
+ // Server is back — reload if it was down
522
+ if (serverDown) {
523
+ serverDown = false;
524
+ const statusEl = document.getElementById("status-sync");
525
+ if (statusEl)
526
+ statusEl.textContent = "Server reconnected";
527
+ location.reload();
528
+ }
529
+ }
530
+ catch {
531
+ if (!serverDown) {
532
+ serverDown = true;
533
+ const statusEl = document.getElementById("status-sync");
534
+ if (statusEl) {
535
+ statusEl.textContent = "SERVER OFFLINE";
536
+ statusEl.style.color = "oklch(0.65 0.2 25)";
537
+ }
538
+ }
520
539
  }
521
- catch { /* offline */ }
522
540
  }, 5000);
523
541
  console.log("mailx client initialized, location:", location.href);
524
542
  // Diagnostic: test API connectivity (helps debug WebView2 blank screen)
@@ -6,13 +6,16 @@ import { getMessage, updateFlags } from "../lib/api-client.js";
6
6
  let currentMessage = null;
7
7
  let currentAccountId = "";
8
8
  let showMessageGeneration = 0; // Cancel stale fetches
9
+ let retryCount = 0;
9
10
  export function getCurrentMessage() {
10
11
  if (!currentMessage)
11
12
  return null;
12
13
  return { accountId: currentAccountId, message: currentMessage };
13
14
  }
14
- export async function showMessage(accountId, uid, folderId, specialUse) {
15
+ export async function showMessage(accountId, uid, folderId, specialUse, isRetry = false) {
15
16
  const gen = ++showMessageGeneration;
17
+ if (!isRetry)
18
+ retryCount = 0;
16
19
  const headerEl = document.getElementById("mv-header");
17
20
  const bodyEl = document.getElementById("mv-body");
18
21
  const attEl = document.getElementById("mv-attachments");
@@ -221,9 +224,17 @@ export async function showMessage(accountId, uid, folderId, specialUse) {
221
224
  }
222
225
  }
223
226
  catch (e) {
224
- const msg = e.message || "Unknown error";
225
- bodyEl.innerHTML = `<div class="mv-empty">Failed to load message: ${msg}<br><button onclick="location.reload()">Retry</button></div>`;
227
+ const err = e.message || "Unknown error";
226
228
  console.error("showMessage error:", e);
229
+ if (retryCount < 3) {
230
+ retryCount++;
231
+ bodyEl.innerHTML = `<div class="mv-empty">Loading failed: ${err} — retrying (${retryCount}/3)...</div>`;
232
+ setTimeout(() => { if (gen === showMessageGeneration)
233
+ showMessage(accountId, uid, folderId, specialUse, true); }, 3000);
234
+ }
235
+ else {
236
+ bodyEl.innerHTML = `<div class="mv-empty">Failed to load message: ${err}</div>`;
237
+ }
227
238
  }
228
239
  }
229
240
  function formatAddr(addr) {
@@ -19,10 +19,19 @@ function newMessageListSignal() {
19
19
  return messageListAbort.signal;
20
20
  }
21
21
  async function api(path, options) {
22
- const res = await fetch(`/api${path}`, {
23
- headers: { "Content-Type": "application/json" },
24
- ...options
25
- });
22
+ let res;
23
+ try {
24
+ res = await fetch(`/api${path}`, {
25
+ headers: { "Content-Type": "application/json" },
26
+ ...options
27
+ });
28
+ }
29
+ catch (e) {
30
+ // Network error — server is down
31
+ if (e.name === "AbortError")
32
+ throw e;
33
+ throw new Error("Server offline — restart with launch.ps1");
34
+ }
26
35
  if (!res.ok) {
27
36
  const err = await res.json().catch(() => ({ error: res.statusText }));
28
37
  throw new Error(err.error || res.statusText);
@@ -92,7 +92,7 @@
92
92
  background: var(--color-bg-surface);
93
93
  color: var(--color-text);
94
94
  font-size: var(--font-size-sm);
95
- width: 350px;
95
+ width: 500px;
96
96
 
97
97
  &::placeholder { color: var(--color-text-muted); }
98
98
  &:focus { outline: 1px solid var(--color-accent); border-color: var(--color-accent); }
@@ -1,21 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * mailx postinstall — delegates to @bobfrankston/rust-builder
3
+ * mailx postinstall — sets binary permissions on Linux/Mac.
4
+ * Tries shared rust-builder; falls back to inline logic if not available.
4
5
  */
5
- import { runPostinstall } from "@bobfrankston/rust-builder/postinstall";
6
- import path from "path";
7
- import { fileURLToPath } from "url";
6
+ try {
7
+ const { runPostinstall } = await import("@bobfrankston/rust-builder/postinstall");
8
+ const path = await import("path");
9
+ const { fileURLToPath } = await import("url");
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
11
 
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
-
11
- runPostinstall({
12
- binaryName: "mailx-app",
13
- binDir: path.join(__dirname, "..", "bin"),
14
- binaries: {
15
- win32: "mailx-app.exe",
16
- darwin: "mailx-app",
17
- darwinArm64: "mailx-app-arm64",
18
- linux: "mailx-app-linux",
19
- linuxArm64: "mailx-app-linux-aarch64",
20
- },
21
- });
12
+ runPostinstall({
13
+ binaryName: "mailx-app",
14
+ binDir: path.join(__dirname, "..", "bin"),
15
+ binaries: {
16
+ win32: "mailx-app.exe",
17
+ darwin: "mailx-app",
18
+ darwinArm64: "mailx-app-arm64",
19
+ linux: "mailx-app-linux",
20
+ linuxArm64: "mailx-app-linux-aarch64",
21
+ },
22
+ });
23
+ } catch {
24
+ // rust-builder not available (e.g., local dev with file: deps)
25
+ // On Windows, nothing to do. On Linux/Mac, try chmod directly.
26
+ if (process.platform !== "win32") {
27
+ const fs = await import("fs");
28
+ const path = await import("path");
29
+ const { fileURLToPath } = await import("url");
30
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
31
+ const arch = process.arch;
32
+ const name = process.platform === "darwin"
33
+ ? (arch === "arm64" ? "mailx-app-arm64" : "mailx-app")
34
+ : (arch === "arm64" ? "mailx-app-linux-aarch64" : "mailx-app-linux");
35
+ const bin = path.join(__dirname, "..", "bin", name);
36
+ try { fs.chmodSync(bin, 0o755); } catch { /* binary may not exist */ }
37
+ }
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -44,6 +44,9 @@
44
44
  ],
45
45
  "author": "Bob Frankston",
46
46
  "license": "MIT",
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
47
50
  "repository": {
48
51
  "type": "git",
49
52
  "url": "https://github.com/BobFrankston/mailx.git"
@@ -233,6 +233,14 @@ process.on("SIGTERM", shutdown);
233
233
  process.on("unhandledRejection", (err) => {
234
234
  console.error("Unhandled rejection:", err?.message || err);
235
235
  });
236
+ process.on("uncaughtException", (err) => {
237
+ console.error("FATAL uncaught exception:", err.message);
238
+ console.error(err.stack);
239
+ // Don't exit — let node --watch handle restart
240
+ });
241
+ process.on("exit", (code) => {
242
+ console.log(`Process exiting with code ${code}`);
243
+ });
236
244
  // ── Entry Point ──
237
245
  await start();
238
246
  export { app, start };