@agenticmail/cli 0.9.6 → 0.9.8

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.
@@ -95,6 +95,19 @@ async function bootstrap() {
95
95
  const initial = (lastId && state.agents.find(a => a.id === lastId))
96
96
  ?? state.agents.find(isBridgeAgent)
97
97
  ?? state.agents[0];
98
+ // Seed the URL hash BEFORE selectAgent so selectAgent's loadList
99
+ // call lands on the right folder. We use history.replaceState
100
+ // (NOT `location.hash = ...`) so this does NOT fire a hashchange
101
+ // event — that would trigger a second route() → loadList() in
102
+ // parallel with selectAgent's, doubling the work on every
103
+ // bootstrap. Read the hash first so a deep-link refresh
104
+ // (e.g. /#/folder/sent) still wins.
105
+ const folderMatch = location.hash.match(/^#\/folder\/([a-z]+)$/);
106
+ if (folderMatch) {
107
+ state.selectedFolder = folderMatch[1];
108
+ } else if (!location.hash) {
109
+ history.replaceState(null, '', `${location.pathname}${location.search}#/folder/inbox`);
110
+ }
98
111
  if (initial) await selectAgent(initial);
99
112
  renderProfile();
100
113
  populateComposeFrom();
@@ -105,10 +118,14 @@ async function bootstrap() {
105
118
  // rendering. Idempotent — safe to call after bootstrap reruns.
106
119
  subscribeToActivity();
107
120
  maybeRequestNotificationPermission();
108
- // Initial route: if the URL already has a hash (e.g. a refresh
109
- // on /#/folder/sent), respect it; otherwise default to inbox.
110
- if (!location.hash) location.hash = '#/folder/inbox';
111
- else route();
121
+ // If the URL points at a message (not a folder), open it now —
122
+ // the folder list selectAgent already loaded stays in the
123
+ // background. Folder hashes need no extra work; selectAgent's
124
+ // loadList already handled them above.
125
+ const hash = location.hash;
126
+ const msgMatch = hash.match(/^#\/m\/(\d+)$/);
127
+ const draftMatch = hash.match(/^#\/d\/([a-zA-Z0-9-]+)$/);
128
+ if (msgMatch || draftMatch) route();
112
129
  } catch (err) {
113
130
  toast(`Failed to load agents: ${err.message}`, true);
114
131
  }
@@ -176,14 +193,18 @@ function route() {
176
193
  }
177
194
  const folderMatch = hash.match(/^#\/folder\/([a-z]+)$/);
178
195
  const folder = folderMatch ? folderMatch[1] : 'inbox';
179
- if (state.selectedFolder !== folder) {
180
- state.selectedFolder = folder;
181
- // Reset pagination on every folder switcha fresh folder
182
- // starts at page 1. Preserved across silent SSE refreshes so
183
- // a new arrival doesn't yank the user back from page 3.
184
- state.pagination = { offset: 0, limit: 50, total: 0 };
185
- renderSidebar(onFolderSelect);
186
- }
196
+ // Only do work if the folder actually changed. Re-firing loadList
197
+ // for the SAME folder on every hashchange (e.g. closing a message
198
+ // detail back to the list) makes the UI feel sluggish the list
199
+ // is already rendered, and a second digest fetch just churns
200
+ // through 50 messages on the IMAP server for no visible change.
201
+ if (state.selectedFolder === folder) return;
202
+ state.selectedFolder = folder;
203
+ // Reset pagination on every folder switch — a fresh folder
204
+ // starts at page 1. Preserved across silent SSE refreshes so
205
+ // a new arrival doesn't yank the user back from page 3.
206
+ state.pagination = { offset: 0, limit: 50, total: 0 };
207
+ renderSidebar(onFolderSelect);
187
208
  if (state.selectedAgent) loadList(state.selectedAgent, folder);
188
209
  }
189
210
  window.addEventListener('hashchange', route);
@@ -359,7 +359,15 @@ export function renderList() {
359
359
  const prevBtn = document.getElementById('pager-prev');
360
360
  const nextBtn = document.getElementById('pager-next');
361
361
  if (prevBtn) prevBtn.disabled = offset <= 0;
362
- if (nextBtn) nextBtn.disabled = pageEnd >= total || state.messages.length < limit;
362
+ // Drive Next purely from the server-reported total. The previous
363
+ // `state.messages.length < limit` clause was meant as a "we hit the
364
+ // end" heuristic for the no-total fallback case, but it backfired
365
+ // on every folder that legitimately had fewer-than-`limit` items on
366
+ // a page (e.g. trailing partial page after deletions, or a folder
367
+ // whose IMAP STATUS returned a stale low count) — Next stayed
368
+ // permanently disabled. The new digest endpoint always returns an
369
+ // authoritative SEARCH-derived total, so a single check is enough.
370
+ if (nextBtn) nextBtn.disabled = pageEnd >= total;
363
371
 
364
372
  if (filtered.length === 0) {
365
373
  root.innerHTML = q
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/cli",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "description": "Email and SMS infrastructure for AI agents — the first platform to give agents real email addresses and phone numbers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,12 +29,12 @@
29
29
  "prepublishOnly": "npm run build"
30
30
  },
31
31
  "dependencies": {
32
- "@agenticmail/api": "^0.9.5",
32
+ "@agenticmail/api": "^0.9.6",
33
33
  "@agenticmail/core": "^0.9.2",
34
34
  "json5": "^2.2.3"
35
35
  },
36
36
  "optionalDependencies": {
37
- "@agenticmail/claudecode": "^0.2.5"
37
+ "@agenticmail/claudecode": "^0.2.7"
38
38
  },
39
39
  "devDependencies": {
40
40
  "tsup": "^8.4.0",