@bobfrankston/mailx 1.0.445 → 1.0.447

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.
@@ -140,10 +140,25 @@ if (!window.__mailxMultiSelectWired) {
140
140
  if (!body?.classList.contains("multi-select-on"))
141
141
  return;
142
142
  const target = e.target;
143
- // A tap on a row is handled by the row's own click listener; only
144
- // exit when the tap is on neutral ground (outside the list entirely).
145
- if (!target.closest(".ml-row"))
146
- exitMultiSelect();
143
+ // A tap on a row is handled by the row's own click listener.
144
+ // The toolbar must also be exempt: its trash / spam / etc.
145
+ // buttons operate ON the current multi-selection, so a tap on
146
+ // them should NOT clear selection before the button's click
147
+ // handler runs (otherwise getSelectedMessages returns empty
148
+ // and the action no-ops — Android-reported 2026-04-30: "press
149
+ // multiple circles, press trashcan, checks vanish, nothing
150
+ // deleted"). Same logic for the folder-tree (drop targets,
151
+ // future: bulk move). Exit only on a tap to genuine neutral
152
+ // ground.
153
+ // Exempt: rows (handled by their own listener), toolbar buttons
154
+ // (delete/spam/etc. operate ON the selection — clearing it here
155
+ // empties the selection before the click runs), folder-tree
156
+ // (drop targets / future bulk move), and the context menu
157
+ // (right-click → "mark read" / "move to" / etc. all need the
158
+ // selection intact when the menu item runs).
159
+ if (target.closest(".ml-row, .toolbar, .folder-tree, .ctx-menu, #btn-tb-delete, #btn-tb-spam"))
160
+ return;
161
+ exitMultiSelect();
147
162
  }, true);
148
163
  }
149
164
  function selectRange(from, to) {
@@ -549,6 +564,15 @@ function renderMessages(body, accountId, items) {
549
564
  body.replaceChildren(fragment);
550
565
  }
551
566
  function selectFirst(body) {
567
+ // Narrow viewports (Android, phone-sized): don't auto-select. The
568
+ // click handler in app.ts switches the layout to "narrow-active" on
569
+ // any list-row click, which on a phone means the message viewer takes
570
+ // over the screen and hides the list. Auto-selecting at startup
571
+ // therefore lands the user in the LAST letter they read instead of
572
+ // the inbox summary they wanted. Desktop unchanged — auto-select
573
+ // remains useful when the list and viewer are side-by-side.
574
+ if (window.innerWidth <= 768)
575
+ return;
552
576
  const firstRow = body.querySelector(".ml-row");
553
577
  if (firstRow)
554
578
  firstRow.click();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.445",
3
+ "version": "1.0.447",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -1067,11 +1067,17 @@ export async function initAndroid() {
1067
1067
  }
1068
1068
  syncManager.syncAll().catch(e => console.error(`[android] Periodic sync error: ${e.message}`));
1069
1069
  }, SYNC_INTERVAL_MS);
1070
- // Immediate sync when app comes back to foreground (e.g. user switches from
1071
- // another app). Without this, new messages wait up to 2 minutes after resume.
1070
+ // Immediate sync + send-queue drain when app comes back to foreground
1071
+ // (e.g. user switches from another app). Without the send-queue drain,
1072
+ // a message queued while offline waits up to 2 minutes after resume
1073
+ // before retrying — long enough for the user to think it's stuck.
1072
1074
  document.addEventListener("visibilitychange", () => {
1073
1075
  if (document.visibilityState === "visible") {
1074
1076
  console.log("[sync] resume poll");
1077
+ for (const account of db.getAccounts()) {
1078
+ syncManager.processSendQueue(account.id)
1079
+ .catch(e => console.error(`[android] resume send-drain ${account.id}: ${e.message}`));
1080
+ }
1075
1081
  syncManager.syncAll().catch(e => console.error(`[android] Resume sync error: ${e.message}`));
1076
1082
  }
1077
1083
  });
package/rebuild.cmd CHANGED
@@ -1,12 +1,12 @@
1
1
  @echo off
2
- REM ─────────────────────────────────────────────────────────────────────────
2
+ REM -------------------------------------------------------------------------
3
3
  REM rebuild.cmd — one-shot release: npmglobalize + APK.
4
4
  REM Runs the desktop release workflow (npmglobalize commits, tags, bumps,
5
5
  REM publishes the npm package) and, only if that succeeds, rebuilds the
6
6
  REM Android MAUI APK + copies it into download/apks/ + updates versions.json.
7
7
  REM Anything non-zero from npmglobalize aborts before the APK stage — no
8
8
  REM point building an APK against a failed publish.
9
- REM ─────────────────────────────────────────────────────────────────────────
9
+ REM -------------------------------------------------------------------------
10
10
 
11
11
  cls
12
12
  setlocal
@@ -1,4 +0,0 @@
1
- setlocal
2
- cd %~dp0android-maui
3
- call build-apk.cmd
4
- cls &