@agenticmail/api 0.9.28 → 0.9.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/api",
3
- "version": "0.9.28",
3
+ "version": "0.9.29",
4
4
  "description": "REST API server for AgenticMail — email and SMS endpoints for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -324,6 +324,17 @@ function renderBodyWithThreading(src, audienceLookup = null) {
324
324
  i++;
325
325
  continue;
326
326
  }
327
+ // Strip any trailing blank lines from the prose buffer before
328
+ // flushing. Reply builders typically pad the new content with
329
+ // `\n\n` before the `On <date>, <addr> wrote:` header so the
330
+ // quote stands apart in plaintext clients. In the web view those
331
+ // blank lines render as empty <p> blocks with their own margins,
332
+ // producing the huge gap operators flagged between the latest
333
+ // message and the quoted thread. Trimming here keeps plain-text
334
+ // readers happy and tightens the rendered HTML at the same time.
335
+ while (prose.length > 0 && prose[prose.length - 1].trim() === '') {
336
+ prose.pop();
337
+ }
327
338
  flushProse();
328
339
  const dateRaw = m[1];
329
340
  const sender = m[2];
@@ -361,16 +372,27 @@ function renderBodyWithThreading(src, audienceLookup = null) {
361
372
  }
362
373
  break;
363
374
  }
364
- // Fall back to cross-message lookup if the body didn't carry
365
- // audience lines (older replies pre-0.9.32). The lookup uses
366
- // the surrounding inbox-list messages to find the original
367
- // message by sender + date and lift its To/Cc/Bcc.
368
- if (!toAddrs && !ccAddrs && !bccAddrs && typeof audienceLookup === 'function') {
375
+ // Fall back to cross-message lookup to fill ANY missing audience
376
+ // field not just the all-empty case.
377
+ //
378
+ // The previous gate (`!toAddrs && !ccAddrs && !bccAddrs`) was wrong:
379
+ // it treated a body that had a `To:` line but no `Cc:` line as
380
+ // "audience encoded, lookup not needed", silently dropping the Cc
381
+ // info that state.messages had. This surfaced as quoted blocks
382
+ // showing only `TO:` even when the original message had Cc'd
383
+ // multiple agents — operators correctly flagged this as "the
384
+ // earlier email's CCs aren't being shown".
385
+ //
386
+ // Now: run the lookup whenever any field is missing, and only
387
+ // backfill the fields that weren't already in the body. The
388
+ // body-encoded value always wins (it's authoritative); the
389
+ // lookup is purely a backfill.
390
+ if ((!toAddrs || !ccAddrs || !bccAddrs) && typeof audienceLookup === 'function') {
369
391
  const fromLookup = audienceLookup(sender, dateRaw);
370
392
  if (fromLookup) {
371
- toAddrs = fromLookup.to;
372
- ccAddrs = fromLookup.cc;
373
- bccAddrs = fromLookup.bcc;
393
+ if (!toAddrs) toAddrs = fromLookup.to || '';
394
+ if (!ccAddrs) ccAddrs = fromLookup.cc || '';
395
+ if (!bccAddrs) bccAddrs = fromLookup.bcc || '';
374
396
  }
375
397
  }
376
398
  out.push(renderThreadQuote(dateRaw, sender, quoted.join('\n'), { to: toAddrs, cc: ccAddrs, bcc: bccAddrs }, audienceLookup));
package/public/styles.css CHANGED
@@ -1019,14 +1019,41 @@ 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. Two issues
1023
+ * users flagged on the rendered view before:
1024
+ *
1025
+ * 1. Huge vertical gap before the quote — was 16px margin-top here
1026
+ * PLUS empty <p> blocks from trailing newlines in the parent
1027
+ * message body (now also fixed in message-view.js by trimming
1028
+ * trailing whitespace before the quote). Dropped to a tight 4px
1029
+ * since the horizontal divider below carries the separation.
1030
+ * 2. The pink left rule looked like a stub. We add a full-width
1031
+ * hairline divider on ::before so the visual break between the
1032
+ * parent message and the quote is unambiguous, and pin the rule
1033
+ * so it reads as a thread spine continuing down through the
1034
+ * whole quoted block.
1035
+ *
1036
+ * Nested quotes (replies-of-replies) get progressively warmer rule
1037
+ * colours (pink → purple → amber) so the depth is visible at a
1038
+ * glance without staring at indentation. */
1022
1039
  .thread-quote {
1023
- margin: 16px 0;
1040
+ position: relative;
1041
+ margin: 4px 0 8px;
1042
+ padding: 14px 0 4px 16px;
1024
1043
  border-left: 3px solid var(--pink-rule);
1025
- padding: 0 0 0 16px;
1044
+ }
1045
+ .thread-quote::before {
1046
+ content: '';
1047
+ position: absolute;
1048
+ top: 0;
1049
+ left: -3px;
1050
+ right: 0;
1051
+ height: 1px;
1052
+ background: var(--line, rgba(255,255,255,0.08));
1026
1053
  }
1027
1054
  .thread-quote .thread-quote {
1028
1055
  border-left-color: #c084fc;
1029
- margin: 12px 0;
1056
+ margin: 4px 0 8px;
1030
1057
  }
1031
1058
  .thread-quote .thread-quote .thread-quote { border-left-color: #f59e0b; }
1032
1059
  .thread-quote-head {