@bobfrankston/mailx 1.0.435 → 1.0.436

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
@@ -2219,7 +2219,7 @@ document.getElementById("btn-open-log")?.addEventListener("click", async () => {
2219
2219
  }
2220
2220
  });
2221
2221
  async function openJsoncEditor(initialFile) {
2222
- const { readJsoncFile, writeJsoncFile, readConfigHelp } = await import("./lib/api-client.js");
2222
+ const { readJsoncFile, writeJsoncFile, readConfigHelp, formatJsonc } = await import("./lib/api-client.js");
2223
2223
  const backdrop = document.createElement("div");
2224
2224
  backdrop.className = "mailx-modal-backdrop";
2225
2225
  const panel = document.createElement("div");
@@ -2253,6 +2253,7 @@ async function openJsoncEditor(initialFile) {
2253
2253
  </div>
2254
2254
  <div class="mailx-modal-error" id="jsonc-error" hidden></div>
2255
2255
  <div class="mailx-modal-buttons">
2256
+ <button type="button" class="mailx-modal-btn" data-action="format" title="Reformat indentation while preserving comments and trailing commas">Format</button>
2256
2257
  <span class="mailx-modal-spacer"></span>
2257
2258
  <button type="button" class="mailx-modal-btn" data-action="cancel">Cancel</button>
2258
2259
  <button type="button" class="mailx-modal-btn mailx-modal-btn-primary" data-action="save">Save</button>
@@ -2311,17 +2312,34 @@ async function openJsoncEditor(initialFile) {
2311
2312
  renderGutter();
2312
2313
  };
2313
2314
  const showValidation = (err) => {
2314
- errorEl.textContent = `Line ${err.line}, col ${err.col}: ${err.message}`;
2315
+ // CRITICAL: do NOT move the cursor here. Validation fires every 600ms
2316
+ // while the user types; auto-selecting the error position yanked the
2317
+ // cursor mid-edit and made fixing the error impossible (the user
2318
+ // reported this as a fatal bug — the very mechanism preventing a save
2319
+ // was preventing the fix). Location is shown via the gutter highlight
2320
+ // + the "Line N, col M" message, and the user can click "Jump" to
2321
+ // explicitly navigate.
2322
+ errorEl.innerHTML = "";
2323
+ const text = document.createElement("span");
2324
+ text.textContent = `Line ${err.line}, col ${err.col}: ${err.message} `;
2325
+ const jumpBtn = document.createElement("button");
2326
+ jumpBtn.type = "button";
2327
+ jumpBtn.className = "mailx-modal-btn mailx-modal-btn-link";
2328
+ jumpBtn.textContent = "Jump to error";
2329
+ jumpBtn.addEventListener("click", () => {
2330
+ textarea.focus();
2331
+ try {
2332
+ textarea.setSelectionRange(err.pos, err.pos + 1);
2333
+ }
2334
+ catch { /* */ }
2335
+ });
2336
+ errorEl.appendChild(text);
2337
+ errorEl.appendChild(jumpBtn);
2315
2338
  errorEl.hidden = false;
2316
2339
  textarea.classList.add("mailx-modal-input-error");
2317
2340
  saveBtn.disabled = true;
2318
2341
  errorLine = err.line;
2319
2342
  renderGutter();
2320
- // Select the problem character so the browser draws a visible marker
2321
- try {
2322
- textarea.setSelectionRange(err.pos, err.pos + 1);
2323
- }
2324
- catch { /* out-of-range → ignore */ }
2325
2343
  };
2326
2344
  let validateTimer;
2327
2345
  const scheduleValidate = () => {
@@ -2377,6 +2395,31 @@ async function openJsoncEditor(initialFile) {
2377
2395
  close();
2378
2396
  return;
2379
2397
  }
2398
+ if (action === "format") {
2399
+ // Reformat via the service-side jsonc-parser format() — the
2400
+ // edits are whitespace-only, so `//` and `/* */` comments
2401
+ // survive intact (which JSON.stringify(parse(...)) does not).
2402
+ btn.disabled = true;
2403
+ const orig = btn.textContent;
2404
+ btn.textContent = "Formatting…";
2405
+ try {
2406
+ const r = await formatJsonc(textarea.value);
2407
+ if (r?.content !== undefined) {
2408
+ textarea.value = r.content;
2409
+ renderGutter();
2410
+ scheduleValidate();
2411
+ }
2412
+ }
2413
+ catch (e) {
2414
+ errorEl.textContent = `Format failed: ${e.message}`;
2415
+ errorEl.hidden = false;
2416
+ }
2417
+ finally {
2418
+ btn.disabled = false;
2419
+ btn.textContent = orig || "Format";
2420
+ }
2421
+ return;
2422
+ }
2380
2423
  if (action === "save") {
2381
2424
  // Final sync-check; refuse to save if it doesn't parse
2382
2425
  const err = validateJsonc(textarea.value);
@@ -339,6 +339,9 @@ export function readJsoncFile(name) {
339
339
  export function writeJsoncFile(name, content) {
340
340
  return ipc().writeJsoncFile?.(name, content);
341
341
  }
342
+ export function formatJsonc(content) {
343
+ return ipc().formatJsonc?.(content);
344
+ }
342
345
  export function readConfigHelp(name) {
343
346
  return ipc().readConfigHelp?.(name) ?? Promise.resolve({ content: "" });
344
347
  }
@@ -131,6 +131,9 @@
131
131
  writeJsoncFile: function(name, content) {
132
132
  return callNode("writeJsoncFile", { name: name, content: content });
133
133
  },
134
+ formatJsonc: function(content) {
135
+ return callNode("formatJsonc", { content: content });
136
+ },
134
137
  readConfigHelp: function(name) {
135
138
  return callNode("readConfigHelp", { name: name });
136
139
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.435",
3
+ "version": "1.0.436",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -252,6 +252,7 @@ export declare class MailxService {
252
252
  * Names are whitelisted so the UI can't read arbitrary files.
253
253
  * `config.jsonc` is the local per-machine config (not cloud-synced). */
254
254
  readJsoncFile(name: string): Promise<string | null>;
255
+ formatJsonc(content: string): Promise<string>;
255
256
  /** Return the help section for a named config file, extracted from docs/config-help.md.
256
257
  * Matches a level-2 heading whose text equals the filename. Returns markdown. */
257
258
  readConfigHelp(name: string): Promise<string>;
@@ -1762,6 +1762,17 @@ export class MailxService {
1762
1762
  const { cloudRead } = await import("@bobfrankston/mailx-settings");
1763
1763
  return cloudRead(name);
1764
1764
  }
1765
+ // Reformat JSONC preserving comments — applyEdits returns whitespace-only edits.
1766
+ async formatJsonc(content) {
1767
+ const { format, applyEdits } = await import("jsonc-parser");
1768
+ const edits = format(content, undefined, {
1769
+ tabSize: 2,
1770
+ insertSpaces: true,
1771
+ eol: "\n",
1772
+ insertFinalNewline: true,
1773
+ });
1774
+ return applyEdits(content, edits);
1775
+ }
1765
1776
  /** Return the help section for a named config file, extracted from docs/config-help.md.
1766
1777
  * Matches a level-2 heading whose text equals the filename. Returns markdown. */
1767
1778
  async readConfigHelp(name) {
@@ -169,6 +169,8 @@ async function dispatchAction(svc, action, p) {
169
169
  case "writeJsoncFile":
170
170
  await svc.writeJsoncFile(p.name, p.content);
171
171
  return { ok: true };
172
+ case "formatJsonc":
173
+ return { content: await svc.formatJsonc(p.content) };
172
174
  case "readConfigHelp":
173
175
  return { content: await svc.readConfigHelp(p.name) };
174
176
  case "unsubscribeOneClick":
package/unwedge.cmd CHANGED
@@ -1 +1 @@
1
- rmdir C:\Users\Bob\.claude\session-env\7299facd-d726-4f8a-8e7a-dbd852680c95 /s /q
1
+ rmdir C:\Users\Bob\.claude\session-env\6787e337-1af0-423c-ae58-8d981702aebb /s /q