@agenticmail/cli 0.9.42 → 0.9.44
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/dist/public/js/message-view.js +51 -14
- package/dist/public/styles.css +36 -12
- package/package.json +4 -4
|
@@ -171,7 +171,7 @@ function renderMessage(msg) {
|
|
|
171
171
|
<div class="message-date">${escapeHtml(formatDateFull(msg.date))}</div>
|
|
172
172
|
</div>
|
|
173
173
|
</div>
|
|
174
|
-
<div class="message-body">${renderBodyWithThreading(bodyText, buildAudienceLookup())}</div>
|
|
174
|
+
<div class="message-body">${renderBodyWithThreading(bodyText, buildAudienceLookup(msg))}</div>
|
|
175
175
|
${attachmentsHtml}
|
|
176
176
|
`;
|
|
177
177
|
|
|
@@ -239,12 +239,43 @@ function formatBytes(bytes) {
|
|
|
239
239
|
* header serialises the date with second precision and stripped
|
|
240
240
|
* timezone info.
|
|
241
241
|
*/
|
|
242
|
-
function buildAudienceLookup() {
|
|
242
|
+
function buildAudienceLookup(currentMessage = null) {
|
|
243
|
+
// Two layered sources, server-authoritative wins:
|
|
244
|
+
//
|
|
245
|
+
// 1. currentMessage.quotedAudiences — populated by the API when
|
|
246
|
+
// it parses the body, scans for `On <date>, <addr> wrote:`
|
|
247
|
+
// headers, and matches them against the IMAP envelopes
|
|
248
|
+
// (which carry Cc/Bcc on the wire — we just weren't surfacing
|
|
249
|
+
// them before 0.9.30). This works even on direct deep links
|
|
250
|
+
// where state.messages is empty.
|
|
251
|
+
//
|
|
252
|
+
// 2. state.messages — the inbox list view the operator is
|
|
253
|
+
// currently looking at. Fallback for older API responses
|
|
254
|
+
// that don't yet include the server-side resolution.
|
|
255
|
+
//
|
|
256
|
+
// Layer 1 takes precedence because the server actually has access
|
|
257
|
+
// to the full mailbox envelopes; the client only sees what was
|
|
258
|
+
// paginated into state.messages.
|
|
259
|
+
const serverIndex = new Map();
|
|
260
|
+
if (currentMessage && Array.isArray(currentMessage.quotedAudiences)) {
|
|
261
|
+
for (const a of currentMessage.quotedAudiences) {
|
|
262
|
+
if (!a || !a.sender) continue;
|
|
263
|
+
// Index by sender alone AND by sender::dateIso for fuzzy date match
|
|
264
|
+
const senderKey = String(a.sender).toLowerCase();
|
|
265
|
+
const t = a.dateIso ? new Date(a.dateIso).getTime() : NaN;
|
|
266
|
+
serverIndex.set(senderKey + '::' + (Number.isFinite(t) ? t : ''), { to: a.to, cc: a.cc, bcc: a.bcc, timeMs: t });
|
|
267
|
+
// Also keep a "most recent for sender" entry as last-resort fallback
|
|
268
|
+
const allKey = senderKey + '::*';
|
|
269
|
+
const prev = serverIndex.get(allKey);
|
|
270
|
+
if (!prev || (Number.isFinite(t) && t > prev.timeMs)) {
|
|
271
|
+
serverIndex.set(allKey, { to: a.to, cc: a.cc, bcc: a.bcc, timeMs: t });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
243
275
|
// Defensive: state.messages may be empty if the operator navigated
|
|
244
|
-
// straight to /#/m/N. Lookup degrades to
|
|
245
|
-
// falls back to sender-only.
|
|
276
|
+
// straight to /#/m/N. Lookup degrades to layer 1 only.
|
|
246
277
|
const list = Array.isArray(state.messages) ? state.messages : [];
|
|
247
|
-
if (list.length === 0) return () => null;
|
|
278
|
+
if (list.length === 0 && serverIndex.size === 0) return () => null;
|
|
248
279
|
// Flatten to a normalised array we can scan. Each entry carries
|
|
249
280
|
// the same shape the renderer expects.
|
|
250
281
|
const entries = list.map(m => {
|
|
@@ -265,23 +296,29 @@ function buildAudienceLookup() {
|
|
|
265
296
|
if (!senderAddr || !dateStr) return null;
|
|
266
297
|
const senderL = senderAddr.toLowerCase();
|
|
267
298
|
const t = new Date(dateStr).getTime();
|
|
299
|
+
|
|
300
|
+
// Layer 1: server-resolved index. Exact date match first, then
|
|
301
|
+
// sender-only fallback. The server already matched against IMAP
|
|
302
|
+
// envelopes so this is the authoritative source.
|
|
303
|
+
if (serverIndex.size > 0) {
|
|
304
|
+
const exactKey = senderL + '::' + (Number.isFinite(t) ? t : '');
|
|
305
|
+
const exact = serverIndex.get(exactKey);
|
|
306
|
+
if (exact) return { to: exact.to, cc: exact.cc, bcc: exact.bcc };
|
|
307
|
+
const senderOnly = serverIndex.get(senderL + '::*');
|
|
308
|
+
if (senderOnly) return { to: senderOnly.to, cc: senderOnly.cc, bcc: senderOnly.bcc };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Layer 2: state.messages — surrounding inbox list. Bounded by
|
|
312
|
+
// what the operator's folder view happens to have loaded.
|
|
268
313
|
if (!Number.isFinite(t)) return null;
|
|
269
|
-
// Match by sender + nearest date within 2 s. Quote headers
|
|
270
|
-
// typically lose timezone info during render so wall-clock can
|
|
271
|
-
// shift by hours; the inbox-list date is the canonical UTC.
|
|
272
|
-
// We prefer the closest match by absolute delta.
|
|
273
314
|
let best = null;
|
|
274
|
-
let bestDelta = 2000;
|
|
315
|
+
let bestDelta = 2000;
|
|
275
316
|
for (const e of entries) {
|
|
276
317
|
if (e.from !== senderL) continue;
|
|
277
318
|
const delta = Math.abs(e.timeMs - t);
|
|
278
319
|
if (delta < bestDelta) { best = e; bestDelta = delta; }
|
|
279
320
|
}
|
|
280
321
|
if (!best) {
|
|
281
|
-
// Fallback: if the date didn't parse close to anything, just
|
|
282
|
-
// take the most recent message from that sender. Better than
|
|
283
|
-
// nothing for the common case of a reply to the most recent
|
|
284
|
-
// prior reply.
|
|
285
322
|
for (const e of entries) {
|
|
286
323
|
if (e.from !== senderL) continue;
|
|
287
324
|
if (!best || e.timeMs > best.timeMs) best = e;
|
package/dist/public/styles.css
CHANGED
|
@@ -1019,19 +1019,20 @@ mark.search-hl {
|
|
|
1019
1019
|
styled card with proper avatar + name + friendly date instead
|
|
1020
1020
|
of leaving the raw ISO timestamp visible. Nested replies recurse
|
|
1021
1021
|
so a 3-deep thread gets 3 nested cards. */
|
|
1022
|
-
/* Thread-quote: the boxed-up older email a reply quotes.
|
|
1023
|
-
*
|
|
1022
|
+
/* Thread-quote: the boxed-up older email a reply quotes. Issues we
|
|
1023
|
+
* sequentially squashed:
|
|
1024
1024
|
*
|
|
1025
|
-
* 1. Huge vertical gap before the quote —
|
|
1026
|
-
* PLUS empty <p> blocks from
|
|
1027
|
-
*
|
|
1028
|
-
*
|
|
1029
|
-
*
|
|
1030
|
-
*
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
1025
|
+
* 1. Huge vertical gap before the quote — initially 16px margin-top
|
|
1026
|
+
* on .thread-quote PLUS empty <p>/<ul> blocks from the markdown
|
|
1027
|
+
* renderer treating reply sig-lines like `- marlow` as bullet
|
|
1028
|
+
* lists. message-view.js now trims trailing blank lines from
|
|
1029
|
+
* the prose buffer; we tighten the rendered block margins below
|
|
1030
|
+
* so the last <p>/<ul>/<ol> before a quote collapses its
|
|
1031
|
+
* bottom margin and the gap reads as a single hairline.
|
|
1032
|
+
* 2. The pink left rule looked like a stub. ::before lays a 1px
|
|
1033
|
+
* full-width divider tucked behind the border so the visual
|
|
1034
|
+
* break is unambiguous and the rule reads as a continuous
|
|
1035
|
+
* thread spine through the quoted block.
|
|
1035
1036
|
*
|
|
1036
1037
|
* Nested quotes (replies-of-replies) get progressively warmer rule
|
|
1037
1038
|
* colours (pink → purple → amber) so the depth is visible at a
|
|
@@ -1051,6 +1052,29 @@ mark.search-hl {
|
|
|
1051
1052
|
height: 1px;
|
|
1052
1053
|
background: var(--line, rgba(255,255,255,0.08));
|
|
1053
1054
|
}
|
|
1055
|
+
/* Collapse the margin-bottom on whichever block lands immediately
|
|
1056
|
+
* before a thread-quote. Markdown's default 1em paragraph/list
|
|
1057
|
+
* margins were stacking with .thread-quote's margin-top, producing
|
|
1058
|
+
* a ~50px dead zone above quoted replies. With :has() we target
|
|
1059
|
+
* only the preceding sibling (other blocks elsewhere keep their
|
|
1060
|
+
* default margins). Modern browsers all support :has() now. */
|
|
1061
|
+
.message-body p:has(+ .thread-quote),
|
|
1062
|
+
.message-body ul:has(+ .thread-quote),
|
|
1063
|
+
.message-body ol:has(+ .thread-quote),
|
|
1064
|
+
.message-body h1:has(+ .thread-quote),
|
|
1065
|
+
.message-body h2:has(+ .thread-quote),
|
|
1066
|
+
.message-body h3:has(+ .thread-quote),
|
|
1067
|
+
.message-body pre:has(+ .thread-quote),
|
|
1068
|
+
.message-body blockquote:has(+ .thread-quote) {
|
|
1069
|
+
margin-bottom: 0;
|
|
1070
|
+
}
|
|
1071
|
+
/* Same tightening on the LAST child of a thread-quote-body — keeps
|
|
1072
|
+
* nested quote ladders from accumulating spacing as they descend. */
|
|
1073
|
+
.thread-quote-body > p:last-child,
|
|
1074
|
+
.thread-quote-body > ul:last-child,
|
|
1075
|
+
.thread-quote-body > ol:last-child {
|
|
1076
|
+
margin-bottom: 0;
|
|
1077
|
+
}
|
|
1054
1078
|
.thread-quote .thread-quote {
|
|
1055
1079
|
border-left-color: #c084fc;
|
|
1056
1080
|
margin: 4px 0 8px;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenticmail/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.44",
|
|
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",
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
"prepublishOnly": "npm run build"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@agenticmail/api": "^0.9.
|
|
35
|
-
"@agenticmail/core": "^0.9.
|
|
34
|
+
"@agenticmail/api": "^0.9.30",
|
|
35
|
+
"@agenticmail/core": "^0.9.8",
|
|
36
36
|
"json5": "^2.2.3"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
39
|
"@agenticmail/claudecode": "^0.2.22",
|
|
40
|
-
"@agenticmail/codex": "^0.1.
|
|
40
|
+
"@agenticmail/codex": "^0.1.18"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"tsup": "^8.4.0",
|