@bobfrankston/rmfmail 1.2.303 → 1.2.304

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,47 +1,49 @@
1
- The phone's star filter was never asked for, and the chip announcing it landed in a 28px column
2
-
3
- "it then says no flagged messages. but there are message flagged" (Bob
4
- 2026-09-04, on Android, with the screenshot). There are 260 257 in bobma's
5
- INBOX alone, and the desktop's own SQL finds every one of them.
6
-
7
- `WebMailxService.getUnifiedInbox` took two parameters. api-client has always
8
- called it with four: `(page, pageSize, flaggedOnly, dateBasis)`. `getMessages`
9
- took seven and is called with nine, `flaggedOnly` at position 8. The extras
10
- fell off the end and JavaScript said nothing and neither did TypeScript,
11
- because an implementation with FEWER parameters satisfies an interface
12
- declaring more. Both dropped arguments are on `MessageQuery`, documented, and
13
- honoured by the desktop store.
14
-
15
- What was left on the phone was the client's `.flagged-only` CSS rule, which
16
- hides unflagged rows in the loaded page. That is precisely the arrangement the
17
- desktop comment has warned about since 2026-06-05: a paginated list whose
18
- newest 50 messages happen to carry no star shows nothing at all, and every
19
- starred message deeper in the mailbox is unreachable. The overlay then counted
20
- the rows it was hiding and reported the truth from where it stood — "A 'flagged
21
- only' filter is hiding 50 messages" while the filter that could have found
22
- the other 260 had never run.
23
-
24
- Both parameters are now threaded through, and the `\Flagged` predicate lives in
25
- one named constant matching desktop's character for character: two backslashes
26
- in the SQL literal, because `flags_json` is JSON and the flag is stored
27
- escaped. `dateBasis` was dropped by the same omission on the same line, so the
28
- phone has been ordering by arrival time while the desktop ordered by the Date:
29
- header; it now honours what it is handed. That is a visible change to list
30
- order on the phone, and it is the order the client has been asking for all
31
- along say so if you want it left as it was.
32
-
33
- Three tests assert the threading, which is where the bug was. They use a spy
34
- db, since the failure was an argument that never arrived, not SQL that got it
35
- wrong.
36
-
37
- **The chip, separately.** `#ml-filter-chip` said `display: block; width: 100%`
38
- and never said `grid-column: 1 / -1`. `.message-list` is a five-column grid,
39
- every other full-width child declares the span, and this one auto-placed into
40
- column 1 the 28px avatar track so "Showing flagged (★) messages only —
41
- click to show everything" came out one word per line down the left edge of the
42
- header. `width: 100%` cannot help there; inside a grid it means 100% of a 28px
43
- track.
44
-
45
- Not changed: the ★ button sits in the search row, next to Server and
46
- Trash/Spam, which is why reaching for search found a list filter. That is a
47
- placement judgement, not a bug, so it is yours to call.
1
+ Ask at the pointer, not in the host's dialog
2
+
3
+ "clicking the x is supposed [to] delete the task but seems to do nothing
4
+ it eventually puts up a message far from where I clicked" (Bob 2026-09-04,
5
+ with both screenshots).
6
+
7
+ The × handler called the native `confirm()`. In the WebView that is the host's
8
+ own dialog: titled "msger.localhost says", centred in the window rather than
9
+ under the pointer, arriving late enough that the click reads as having done
10
+ nothing and blocking the WebView event loop the whole time it is up, which is
11
+ the hazard message-viewer.ts already refuses `alert()` for. Compose reached the
12
+ same conclusion in its own window and wrote an in-page modal; the main window
13
+ never got one, so thirteen `confirm()` calls still go to the host.
14
+
15
+ `confirmAt(x, y, label)` in context-menu.ts asks the question as a two-item
16
+ menu at the pointer. Built on showContextMenu rather than as a new modal, so
17
+ there is one menu, one dismissal path, and one piece of stay-on-screen clamping
18
+ that clamping in particular was already solved here for narrow Android
19
+ screens, and a second implementation would have had to learn it again.
20
+
21
+ The label NAMES its target`Delete "Records. Berkowitz"`, never "OK". By the
22
+ time a confirmation is read the pointer has moved and the row it came from is
23
+ no longer obvious; a named action can be checked, and "OK" cannot. The
24
+ explanation the old dialog text carried ("removes it from Google Tasks — to
25
+ mark done use the checkbox") is now the item's tooltip: worth having, not worth
26
+ a dialog.
27
+
28
+ Three call sites move: the row ×, the row context menu's Delete, and the batch
29
+ delete behind the Delete key / trash button. The batch one has no click to
30
+ anchor to, so it asks over the first selected row where the tasks are and
31
+ where the user is looking.
32
+
33
+ Two supporting changes:
34
+
35
+ `ContextMenuOptions.onClose` — fires whichever way the menu goes away, so a
36
+ promise-shaped menu can answer "cancelled" instead of hanging forever on a
37
+ dismissal. An item's click calls closeContextMenu() BEFORE item.action(), so
38
+ onClose always runs first; confirmAt defers its cancel by one microtask, and
39
+ since the action is synchronous and completes within the same task, a chosen
40
+ item has always settled the promise by the time that microtask drains.
41
+
42
+ `.ctx-emphasized` now renders. `MenuItem.emphasized` has been passed since the
43
+ right-drag menu was written ("Move here" as the default action) and no CSS rule
44
+ ever matched it, so it has never once looked different from its neighbours.
45
+
46
+ The other ten `confirm()` calls rebuild, factory reset, permanent delete,
47
+ folder move/delete, contact delete, discard draft are untouched. They are
48
+ the same dialog with the same problems and they should follow, but each is a
49
+ destructive action worth reading before it moves.
@@ -1015,6 +1015,7 @@ var init_edit_menu = __esm({
1015
1015
  var context_menu_exports = {};
1016
1016
  __export(context_menu_exports, {
1017
1017
  closeContextMenu: () => closeContextMenu,
1018
+ confirmAt: () => confirmAt,
1018
1019
  showContextMenu: () => showContextMenu
1019
1020
  });
1020
1021
  function closeSubmenu() {
@@ -1029,6 +1030,11 @@ function closeContextMenu() {
1029
1030
  activeMenu.remove();
1030
1031
  activeMenu = null;
1031
1032
  }
1033
+ if (closeHandler) {
1034
+ const fn = closeHandler;
1035
+ closeHandler = null;
1036
+ fn();
1037
+ }
1032
1038
  if (dismissListener) {
1033
1039
  document.removeEventListener("pointerdown", dismissListener, true);
1034
1040
  dismissListener = null;
@@ -1079,6 +1085,7 @@ function openSubmenu(parentRow, items) {
1079
1085
  }
1080
1086
  function showContextMenu(x, y, items, opts = {}) {
1081
1087
  closeContextMenu();
1088
+ closeHandler = opts.onClose ?? null;
1082
1089
  if (opts.editTarget !== void 0 || opts.editEngine) {
1083
1090
  const edit = editMenuItems({ target: opts.editTarget, engine: opts.editEngine }, { onError: opts.onEditError });
1084
1091
  if (edit.length > 0) {
@@ -1168,7 +1175,29 @@ function showContextMenu(x, y, items, opts = {}) {
1168
1175
  document.addEventListener("keydown", escapeListener, true);
1169
1176
  });
1170
1177
  }
1171
- var activeMenu, dismissListener, escapeListener, activeSubmenu;
1178
+ function confirmAt(x, y, confirmLabel, opts = {}) {
1179
+ return new Promise((resolve) => {
1180
+ let settled = false;
1181
+ const done = (v) => {
1182
+ if (!settled) {
1183
+ settled = true;
1184
+ resolve(v);
1185
+ }
1186
+ };
1187
+ showContextMenu(x, y, [
1188
+ { label: confirmLabel, action: () => done(true), emphasized: true, tooltip: opts.tooltip },
1189
+ { label: "Cancel", action: () => done(false) }
1190
+ ], {
1191
+ // An item's click calls closeContextMenu() BEFORE item.action(), so
1192
+ // onClose always runs first. Defer the "cancelled" answer by one
1193
+ // microtask: the action is synchronous and finishes within this
1194
+ // task, so by the time the microtask drains, a chosen item has
1195
+ // already settled the promise and this call is a no-op.
1196
+ onClose: () => queueMicrotask(() => done(false))
1197
+ });
1198
+ });
1199
+ }
1200
+ var activeMenu, dismissListener, escapeListener, activeSubmenu, closeHandler;
1172
1201
  var init_context_menu = __esm({
1173
1202
  "client/components/context-menu.js"() {
1174
1203
  "use strict";
@@ -1177,6 +1206,7 @@ var init_context_menu = __esm({
1177
1206
  dismissListener = null;
1178
1207
  escapeListener = null;
1179
1208
  activeSubmenu = null;
1209
+ closeHandler = null;
1180
1210
  document.addEventListener("scroll", closeContextMenu, true);
1181
1211
  document.addEventListener("contextmenu", () => {
1182
1212
  });
@@ -6626,7 +6656,10 @@ async function deleteSelectedTasks() {
6626
6656
  const uuids = [...selectedTaskUuids];
6627
6657
  if (uuids.length === 0)
6628
6658
  return;
6629
- if (!confirm(`Delete ${uuids.length} task${uuids.length === 1 ? "" : "s"}? This removes ${uuids.length === 1 ? "it" : "them"} from Google Tasks \u2014 to mark done, use the checkbox instead.`))
6659
+ const anchorEl = document.querySelector(".cal-side-task.selected") || document.getElementById("cal-side-tasks");
6660
+ const r = anchorEl?.getBoundingClientRect();
6661
+ const label = uuids.length === 1 ? `Delete "${document.querySelector(".cal-side-task.selected .cal-side-task-title")?.textContent || "this task"}"` : `Delete ${uuids.length} tasks`;
6662
+ if (!await confirmAt(r ? r.left + 24 : 80, r ? r.bottom : 80, label, { tooltip: TASK_DELETE_NOTE }))
6630
6663
  return;
6631
6664
  selectedTaskUuids.clear();
6632
6665
  for (const uuid of uuids) {
@@ -6914,7 +6947,7 @@ async function renderTasks(prefetched) {
6914
6947
  row.querySelector(".cal-side-task-delete")?.addEventListener("click", async (e) => {
6915
6948
  e.stopPropagation();
6916
6949
  const title = tasks.find((t) => t.uuid === uuid)?.title || "this task";
6917
- if (!confirm(`Delete task "${title}"? This removes it from Google Tasks \u2014 to mark done, use the checkbox instead.`))
6950
+ if (!await confirmAt(e.clientX, e.clientY, `Delete "${title}"`, { tooltip: TASK_DELETE_NOTE }))
6918
6951
  return;
6919
6952
  await deleteTask(uuid);
6920
6953
  selectedTaskUuids.delete(uuid);
@@ -6941,9 +6974,9 @@ async function renderTasks(prefetched) {
6941
6974
  { label: "View in Google Tasks", action: () => openInBrowser("https://tasks.google.com/") },
6942
6975
  { label: "", action: () => {
6943
6976
  }, separator: true },
6944
- { label: "Delete task", action: async () => {
6977
+ { label: "Delete task", tooltip: TASK_DELETE_NOTE, action: async () => {
6945
6978
  const title = tasks.find((t) => t.uuid === uuid)?.title || "this task";
6946
- if (!confirm(`Delete task "${title}"? This removes it from Google Tasks \u2014 to mark done, use the checkbox instead.`))
6979
+ if (!await confirmAt(e.clientX, e.clientY, `Delete "${title}"`, { tooltip: TASK_DELETE_NOTE }))
6947
6980
  return;
6948
6981
  await deleteTask(uuid);
6949
6982
  renderTasks();
@@ -7296,7 +7329,7 @@ function initCalendarSidebar() {
7296
7329
  }
7297
7330
  }
7298
7331
  }
7299
- var SIDEBAR_PREF, SHOW_RECURRING_PREF, SHOW_DONE_PREF, HORIZON_DAYS_PREF, HORIZON_DEFAULT_DAYS, HOLIDAY_HORIZON_MS, viewYear, viewMonth, viewDay, lastEvents, lastOverdueTasks, calById, calendarList, hiddenCalendars, selectedTaskUuids;
7332
+ var SIDEBAR_PREF, SHOW_RECURRING_PREF, SHOW_DONE_PREF, HORIZON_DAYS_PREF, HORIZON_DEFAULT_DAYS, HOLIDAY_HORIZON_MS, viewYear, viewMonth, viewDay, lastEvents, lastOverdueTasks, calById, calendarList, hiddenCalendars, selectedTaskUuids, TASK_DELETE_NOTE;
7300
7333
  var init_calendar_sidebar = __esm({
7301
7334
  "client/components/calendar-sidebar.js"() {
7302
7335
  "use strict";
@@ -7317,6 +7350,7 @@ var init_calendar_sidebar = __esm({
7317
7350
  calendarList = [];
7318
7351
  hiddenCalendars = /* @__PURE__ */ new Set();
7319
7352
  selectedTaskUuids = /* @__PURE__ */ new Set();
7353
+ TASK_DELETE_NOTE = "Removes it from Google Tasks. To mark it done instead, use the checkbox.";
7320
7354
  }
7321
7355
  });
7322
7356