@bobfrankston/rmfmail 1.1.134 → 1.1.135

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/.commitmsg CHANGED
@@ -1,27 +1,20 @@
1
- fix: -repair handles FTS5 schema drift + uses correct folder columns
1
+ feat: elapsed-time wait indicator + autocomplete + invalid-addr UX
2
2
 
3
- Symptom: "rmfmail -repair" crashed with "no such column: T.to_text"
4
- on Bob's bobma DB. Root cause is a long-standing FTS5 schema drift —
5
- the messages_fts table is contentless (content=messages, content_rowid=id)
6
- and declares to_text/cc_text, but the live messages schema has
7
- to_json/cc_json. DELETE FROM messages_fts tries to materialize each
8
- row's content via the broken linkage and SQL-error'd before any work
9
- could land.
10
-
11
- Fix:
12
- - Use the FTS5 "delete-all" idiom (INSERT … VALUES('delete-all'))
13
- to clear the index without touching the broken content lookup. Falls
14
- back to DROP + CREATE on whatever exotic failure could survive that.
15
- - folders.total / .unread .total_count / .unread_count (the live
16
- column names; the old names never existed on this schema).
17
- - Reset highest_modseq to NULL so QRESYNC re-bootstraps; otherwise
18
- the post-repair sync trusts a stale ModSeq and skips folders that
19
- are actually empty locally.
20
-
21
- This unblocks the stale-folder_id repair path: bobma's Added2.issues
22
- has 552 ghost local rows tagged to a small folder but carrying
23
- INBOX-sized UIDs. Server FETCH on those UIDs in the wrong folder
24
- returns zero, the prefetch loop logs "0/N bodies" every tick, and
25
- the preview pane shows "Fetching body from server..." forever. Repair
26
- wipes the metadata, re-fetches envelopes, and folder_id gets rebuilt
27
- from the server's actual UID lists.
3
+ - Body-fetch placeholder now renders an elapsed-time counter that ticks
4
+ every second ("Fetching body from server… (12s)"). After 30 s the
5
+ counter flips red+bold so a real wedge is visually distinct from a
6
+ normal slow Dovecot read. Cleared automatically when bodyAvailable
7
+ fires, generation advances, or the placeholder leaves the DOM.
8
+ - Compose address autocomplete: clicking a "Frankston, Bob" suggestion
9
+ against a `bob` token used to produce `bob, "Frankston, Bob" <addr>`
10
+ (the original token preserved instead of replaced) in some edge cases.
11
+ Fix is in replaceCaretToken — strip the trailing comma/whitespace from
12
+ the pre-token segment, re-add a clean ", " separator. tokenSpanAtCaret
13
+ also now skips leading whitespace inside the span so the separator
14
+ belongs to "before", not the token itself.
15
+ - "Invalid address" send-block now uses high-contrast white-on-red
16
+ styling (was muted red text on light grey, easy to miss). Both
17
+ classList wiring and CSS updated: the .compose-status-error class
18
+ gets a saturated red background, white bold text, padding/radius.
19
+ Message text bumped to "Invalid address: '...' — Send blocked" so
20
+ the user knows it's the gating condition, not a transient note.
@@ -1179,8 +1179,25 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
1179
1179
  } catch {
1180
1180
  }
1181
1181
  const previewText = (msg.preview || cached?.preview || "").trim();
1182
- bodyEl.innerHTML = previewText ? `<div class="mv-preview-placeholder">${escapeHtml(previewText)}</div>` : `<div class="mv-empty">Fetching body from server\u2026</div>`;
1182
+ const waitStart = Date.now();
1183
+ const indicatorHtml = `<span class="mv-wait-elapsed" data-start="${waitStart}">(0s)</span>`;
1184
+ bodyEl.innerHTML = previewText ? `<div class="mv-preview-placeholder">${escapeHtml(previewText)}<div class="mv-wait-line">Fetching body from server\u2026 ${indicatorHtml}</div></div>` : `<div class="mv-empty">Fetching body from server\u2026 ${indicatorHtml}</div>`;
1183
1185
  const captureGen = gen;
1186
+ const tick = setInterval(() => {
1187
+ if (captureGen !== showMessageGeneration) {
1188
+ clearInterval(tick);
1189
+ return;
1190
+ }
1191
+ const span = bodyEl.querySelector(".mv-wait-elapsed");
1192
+ if (!span) {
1193
+ clearInterval(tick);
1194
+ return;
1195
+ }
1196
+ const secs = Math.round((Date.now() - waitStart) / 1e3);
1197
+ span.textContent = secs >= 60 ? `(${Math.floor(secs / 60)}m ${secs % 60}s)` : `(${secs}s)`;
1198
+ if (secs === 30)
1199
+ span.classList.add("mv-wait-slow");
1200
+ }, 1e3);
1184
1201
  const off = subscribeStore("*", (ev) => {
1185
1202
  if (ev.kind !== "bodyAvailable")
1186
1203
  return;
@@ -1188,9 +1205,11 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
1188
1205
  return;
1189
1206
  if (captureGen !== showMessageGeneration) {
1190
1207
  off();
1208
+ clearInterval(tick);
1191
1209
  return;
1192
1210
  }
1193
1211
  off();
1212
+ clearInterval(tick);
1194
1213
  showMessage(accountId, uid, folderId, specialUse, true, envelope || cached || void 0).catch(() => {
1195
1214
  });
1196
1215
  });