@handled-ai/design-system 0.20.4 → 0.20.5

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.
@@ -63,8 +63,16 @@ interface ConversationThread {
63
63
  paused?: {
64
64
  playbook: string;
65
65
  } | null;
66
- /** false => operator is not a participant; reply disabled (read-only). */
66
+ /** false => operator cannot reply or create drafts from this thread. */
67
67
  canReply?: boolean;
68
+ /** Explains why reply and draft creation are disabled. */
69
+ replyDisabledReason?: string;
70
+ /** Existing Gmail draft or thread URL. Rendered as a new-tab link when present. */
71
+ openInGmailUrl?: string | null;
72
+ /** Forces the Open in Gmail action into a disabled state. */
73
+ openInGmailDisabled?: boolean;
74
+ /** Tooltip/read-only copy for a disabled Open in Gmail action. */
75
+ openInGmailDisabledReason?: string | null;
68
76
  messages: ConvMessage[];
69
77
  /** Prefilled reply draft body. */
70
78
  draft?: string;
@@ -350,6 +350,47 @@ const STATUS_DOT = {
350
350
  function effectiveStatus(t) {
351
351
  return t.canReply === false ? "viewing" : t.status;
352
352
  }
353
+ function disabledOpenInGmailReason(thread) {
354
+ var _a, _b;
355
+ return ((_a = thread.openInGmailDisabledReason) == null ? void 0 : _a.trim()) || ((_b = thread.replyDisabledReason) == null ? void 0 : _b.trim()) || "Gmail access is not available for this thread.";
356
+ }
357
+ function canOpenInGmail(thread, onOpenInGmail) {
358
+ return thread.openInGmailDisabled !== true && Boolean(thread.openInGmailUrl || onOpenInGmail);
359
+ }
360
+ function OpenInGmailButton({
361
+ thread,
362
+ onOpenInGmail,
363
+ label = "Open in Gmail"
364
+ }) {
365
+ const hasConfiguredAction = Boolean(thread.openInGmailUrl || onOpenInGmail || thread.openInGmailDisabled || thread.openInGmailDisabledReason);
366
+ if (!hasConfiguredAction) return null;
367
+ const disabled = !canOpenInGmail(thread, onOpenInGmail);
368
+ const disabledReason = disabled ? disabledOpenInGmailReason(thread) : void 0;
369
+ if (!disabled && thread.openInGmailUrl) {
370
+ return /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", asChild: true, children: /* @__PURE__ */ jsxs("a", { href: thread.openInGmailUrl, target: "_blank", rel: "noopener noreferrer", children: [
371
+ /* @__PURE__ */ jsx(GmailMark, { size: 14 }),
372
+ " ",
373
+ label
374
+ ] }) });
375
+ }
376
+ return /* @__PURE__ */ jsx("span", { className: "inline-flex", title: disabledReason, children: /* @__PURE__ */ jsxs(
377
+ Button,
378
+ {
379
+ type: "button",
380
+ variant: "ghost",
381
+ size: "sm",
382
+ disabled,
383
+ "aria-disabled": disabled || void 0,
384
+ "aria-label": disabledReason ? `${label}: ${disabledReason}` : label,
385
+ onClick: disabled ? void 0 : () => onOpenInGmail == null ? void 0 : onOpenInGmail(thread.threadId),
386
+ children: [
387
+ /* @__PURE__ */ jsx(GmailMark, { size: 14 }),
388
+ " ",
389
+ label
390
+ ]
391
+ }
392
+ ) });
393
+ }
353
394
  function MessageView({
354
395
  message,
355
396
  expanded,
@@ -462,7 +503,8 @@ function ReplyComposer({
462
503
  onClose,
463
504
  onSend,
464
505
  onDraft,
465
- onPreviewReply
506
+ onPreviewReply,
507
+ draftDisabledReason
466
508
  }) {
467
509
  var _a, _b, _c, _d;
468
510
  const [body, setBody] = React.useState((_a = thread.draft) != null ? _a : "");
@@ -473,6 +515,7 @@ function ReplyComposer({
473
515
  const [sendError, setSendError] = React.useState(null);
474
516
  const ccList = replyAll ? (_b = thread.cc) != null ? _b : [] : [];
475
517
  const subject = /^re:/i.test(thread.subject) ? thread.subject : `Re: ${thread.subject}`;
518
+ const draftDisabled = Boolean(draftDisabledReason);
476
519
  const localPreviewHtml = textToHtml(body) + (sig && thread.signature ? textToHtml(thread.signature) : "");
477
520
  const openPreview = async () => {
478
521
  var _a2, _b2;
@@ -516,6 +559,7 @@ function ReplyComposer({
516
559
  }
517
560
  };
518
561
  const handleDraft = async () => {
562
+ if (draftDisabled) return;
519
563
  setSending(true);
520
564
  setSendError(null);
521
565
  try {
@@ -654,19 +698,20 @@ function ReplyComposer({
654
698
  ),
655
699
  sendError ? /* @__PURE__ */ jsx("p", { role: "alert", className: "text-destructive text-sm", children: sendError }) : null,
656
700
  /* @__PURE__ */ jsxs(DialogFooter, { className: "sm:justify-between", children: [
657
- /* @__PURE__ */ jsxs(
701
+ /* @__PURE__ */ jsx("span", { className: "inline-flex", title: draftDisabledReason != null ? draftDisabledReason : void 0, children: /* @__PURE__ */ jsxs(
658
702
  "button",
659
703
  {
660
704
  type: "button",
661
- disabled: sending || previewState.loading,
705
+ disabled: sending || previewState.loading || draftDisabled,
662
706
  onClick: handleDraft,
707
+ "aria-label": draftDisabledReason ? `Open draft in Gmail: ${draftDisabledReason}` : "Open draft in Gmail",
663
708
  className: "text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-[13px] disabled:pointer-events-none disabled:opacity-50",
664
709
  children: [
665
710
  /* @__PURE__ */ jsx(GmailMark, { size: 14 }),
666
711
  " Open draft in Gmail"
667
712
  ]
668
713
  }
669
- ),
714
+ ) }),
670
715
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
671
716
  /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: sending, onClick: () => {
672
717
  setPreview(false);
@@ -701,7 +746,10 @@ function ThreadBody({
701
746
  onPreviewReply,
702
747
  onOpenInGmail
703
748
  }) {
749
+ var _a;
704
750
  const canReply = thread.canReply !== false;
751
+ const replyDisabledReason = ((_a = thread.replyDisabledReason) == null ? void 0 : _a.trim()) || "You are not a participant on this thread, so replying is disabled here.";
752
+ const draftDisabledReason = onCreateGmailDraft ? null : "Gmail draft creation is not available for this thread.";
705
753
  const hasCc = !!(thread.cc && thread.cc.length);
706
754
  const [mode, setMode] = React.useState("idle");
707
755
  const [replyAll, setReplyAll] = React.useState(false);
@@ -726,12 +774,14 @@ function ThreadBody({
726
774
  ] })
727
775
  ] }) : null,
728
776
  /* @__PURE__ */ jsx("div", { className: "space-y-1", children: thread.messages.map((m) => /* @__PURE__ */ jsx(MessageView, { message: m, expanded: !!expanded[m.id], onToggle: () => toggle(m.id), me }, m.id)) }),
729
- !canReply ? /* @__PURE__ */ jsxs("div", { className: "border-border bg-muted/30 text-muted-foreground flex items-start gap-2 rounded-md border p-2.5 text-[12px]", children: [
777
+ !canReply ? /* @__PURE__ */ jsxs("div", { className: "border-border bg-muted/30 text-muted-foreground flex flex-wrap items-start gap-2 rounded-md border p-2.5 text-[12px]", children: [
730
778
  /* @__PURE__ */ jsx(Eye, { size: 14, className: "mt-0.5 shrink-0" }),
731
- /* @__PURE__ */ jsxs("span", { children: [
779
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
732
780
  /* @__PURE__ */ jsx("b", { children: "Viewing only." }),
733
- " You\u2019re not a participant on this thread, so replying is disabled here."
734
- ] })
781
+ " ",
782
+ replyDisabledReason
783
+ ] }),
784
+ /* @__PURE__ */ jsx(OpenInGmailButton, { thread, onOpenInGmail })
735
785
  ] }) : null,
736
786
  canReply && mode === "idle" ? /* @__PURE__ */ jsxs("div", { "data-slot": "conv-action-row", className: "border-border/70 mt-1 flex flex-wrap items-center gap-x-3 gap-y-2 border-t pt-3", children: [
737
787
  /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: () => {
@@ -748,10 +798,7 @@ function ThreadBody({
748
798
  /* @__PURE__ */ jsx(ReplyAll, { size: 14 }),
749
799
  " Reply all"
750
800
  ] }) : null,
751
- /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenInGmail == null ? void 0 : onOpenInGmail(thread.threadId), children: [
752
- /* @__PURE__ */ jsx(GmailMark, { size: 14 }),
753
- " Open in Gmail"
754
- ] }),
801
+ /* @__PURE__ */ jsx(OpenInGmailButton, { thread, onOpenInGmail }),
755
802
  /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70 ml-auto inline-flex items-center gap-1.5 text-[12px]", children: [
756
803
  /* @__PURE__ */ jsx(GitMerge, { size: 13 }),
757
804
  " Stays on this thread"
@@ -771,9 +818,11 @@ function ThreadBody({
771
818
  setMode("sent");
772
819
  },
773
820
  onDraft: async (body, includeSignature) => {
774
- await (onCreateGmailDraft == null ? void 0 : onCreateGmailDraft({ threadId: thread.threadId, body, includeSignature, replyAll }));
821
+ if (!onCreateGmailDraft) return;
822
+ await onCreateGmailDraft({ threadId: thread.threadId, body, includeSignature, replyAll });
775
823
  setMode("draft");
776
- }
824
+ },
825
+ draftDisabledReason
777
826
  }
778
827
  ) : null,
779
828
  canReply && mode === "sent" ? /* @__PURE__ */ jsxs("div", { className: "border-status-active-border bg-status-active-bg flex items-center gap-2 rounded-md border p-3 text-[13px]", children: [
@@ -800,10 +849,7 @@ function ThreadBody({
800
849
  ] }),
801
850
  " thread; open it there to finish. Nothing was sent."
802
851
  ] }),
803
- /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenInGmail == null ? void 0 : onOpenInGmail(thread.threadId), children: [
804
- /* @__PURE__ */ jsx(GmailMark, { size: 14 }),
805
- " Open in Gmail"
806
- ] }),
852
+ /* @__PURE__ */ jsx(OpenInGmailButton, { thread, onOpenInGmail }),
807
853
  /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setMode("idle"), children: "Done" })
808
854
  ] }) : null
809
855
  ] });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/conversation-panel.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * conversation-panel.tsx — in-case email-thread reader + reply, for the case\n * panel (\"Email response detected\" hub).\n *\n * v1 scope (WIT-853 / WIT-802): INLINE thread reading + reply only.\n * - A collapsible hub header with a pulse badge whose color reflects state:\n * responded (a reply needs you) / awaiting (sent, waiting) / viewing (read-only).\n * - A list of thread rows; clicking one opens a Gmail-style reader inline,\n * right under the row, with collapsible messages + quoted-history toggle.\n * - Reply / Reply-all composer with a signature toggle and two send paths:\n * Preview→Send (in-app) and \"Open draft in Gmail\" (deep link).\n * - A \"playbook stopped\" banner when the customer's reply halted the sequence,\n * and a read-only notice when the operator is not a thread participant.\n *\n * The bottom-right floating dock / side-by-side compare is intentionally OUT of\n * scope here and tracked separately (WIT-855).\n *\n * Presentational: all data + side effects come from the consumer. Email bodies\n * (`bodyHtml`, `quoted.html`) are sanitized before rendering.\n */\n\nimport * as React from \"react\"\nimport {\n ChevronDown,\n ChevronUp,\n CornerUpLeft,\n CheckCheck,\n MailOpen,\n FilePenLine,\n Reply,\n ReplyAll,\n Eye,\n Send,\n Lock,\n Pause,\n GitMerge,\n Check,\n X,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { getInitials } from \"../lib/user-display\"\nimport { BRAND_ICONS } from \"../lib/icons\"\nimport { htmlToTextSnippet, sanitizeHtml } from \"../internal/safe-html\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./avatar\"\nimport { Button } from \"./button\"\nimport { Switch } from \"./switch\"\nimport { Textarea } from \"./textarea\"\nimport { RichTextToolbar } from \"./rich-text-toolbar\"\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogDescription,\n DialogFooter,\n} from \"./dialog\"\n\n/* ── Types ───────────────────────────────────────────────────────────────── */\n\nexport interface ConvParticipant {\n name: string\n email: string\n avatarUrl?: string | null\n role?: string\n}\n\nexport interface ConvMessage {\n id: string\n direction: \"inbound\" | \"outbound\"\n from: ConvParticipant\n to: ConvParticipant\n /** Absolute timestamp label, e.g. \"Jun 1, 2026, 9:12 AM\". */\n date: string\n /** Relative label, e.g. \"2 days ago\". */\n ago?: string\n receipt?: { kind: \"new\" | \"read\" | \"opened\" | \"sent\" | \"draft\"; label: string }\n /** HTML body (preferred). Sanitized by the component before rendering. */\n bodyHtml?: string\n /** Plain-text fallback when `bodyHtml` is absent. */\n body?: string\n /** Quoted prior message, collapsed behind a toggle. Sanitized before rendering. */\n quoted?: { attr: string; html: string }\n}\n\nexport type ConvStatus = \"responded\" | \"awaiting\" | \"viewing\" | \"draft\"\n\nexport interface ConversationThread {\n threadId: string\n subject: string\n status: ConvStatus\n /** Relative label for the most recent activity. */\n lastWhen?: string\n contact: ConvParticipant\n cc?: ConvParticipant[]\n /** Set when this thread's reply halted a playbook (terminal). */\n paused?: { playbook: string } | null\n /** false => operator is not a participant; reply disabled (read-only). */\n canReply?: boolean\n messages: ConvMessage[]\n /** Prefilled reply draft body. */\n draft?: string\n /** Signature text appended to replies (plain text). */\n signature?: string\n}\n\nexport interface ConversationReplyPayload {\n threadId: string\n body: string\n includeSignature: boolean\n replyAll: boolean\n}\n\n/**\n * Result of a server-side reply preview. `htmlBody` is the exact HTML the server\n * prepared for this reply (sanitized by the component before render).\n * `confirmationToken`, when present, identifies the prepared confirmation so a\n * consumer can reuse it for the final send instead of re-preparing the message.\n */\nexport interface ConversationReplyPreview {\n htmlBody: string\n confirmationToken?: string\n}\n\nexport interface ConversationPanelProps {\n threads: ConversationThread[]\n /** Current operator: drives \"to me\" + the reply avatar. */\n me?: ConvParticipant\n /** Deployment brand, used in the paused-playbook copy. */\n tenantName?: string\n onSendReply?: (payload: ConversationReplyPayload) => void | Promise<void>\n onCreateGmailDraft?: (payload: ConversationReplyPayload) => void | Promise<void>\n /**\n * Server-side preview contract. When provided, the reply preview requests the\n * exact send HTML from the server, the component sanitizes it before render,\n * and retains any returned `confirmationToken` in preview state so a consumer\n * can reuse it for the final send. When omitted, the composer falls back to a\n * clearly labeled local draft preview only.\n */\n onPreviewReply?: (payload: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n onOpenInGmail?: (threadId: string) => void\n /** Inline-open this thread initially (defaults to the first responded one). */\n defaultOpenThreadId?: string\n className?: string\n}\n\n/* ── Shared helpers ──────────────────────────────────────────────────────── */\n\n/** Gmail-like reading-pane typography (mirrors timeline-activity's email mode). */\nconst PROSE = cn(\n \"text-sm leading-[1.62] text-foreground/90 break-words\",\n \"[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0\",\n \"[&_a]:text-[#1a73e8] [&_a]:underline-offset-2 hover:[&_a]:underline\",\n \"[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5\",\n \"[&_img]:max-w-full [&_img]:h-auto\"\n)\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\")\n}\n\n/** Plain-text -> simple paragraph HTML for the Preview / sent-message body. */\nfunction textToHtml(text: string): string {\n return text\n .split(/\\n{2,}/)\n .map((p) => p.trim())\n .filter(Boolean)\n .map((p) => `<p>${escapeHtml(p).replace(/\\n/g, \"<br>\")}</p>`)\n .join(\"\")\n}\n\ntype CollapsedHtmlMessage = { bodyHtml: string; detailsHtml: string }\ntype CollapsedTextMessage = { bodyText: string; detailsText: string }\ntype MessageSegment = { html?: string; text?: string; visibleText: string }\n\nconst SPLITTABLE_BLOCK_TAGS = new Set([\"p\", \"div\"])\nconst WHOLE_BLOCK_TAGS = new Set([\"blockquote\", \"table\", \"ul\", \"ol\", \"hr\"])\nconst BLOCK_TAGS = new Set([...SPLITTABLE_BLOCK_TAGS, ...WHOLE_BLOCK_TAGS])\nconst BR_TAG_RE = /<br\\s*\\/?>/gi\nconst HTML_BLOCK_START_RE = /<(p|div|blockquote|table|ul|ol|hr)\\b[^>]*>/i\n\nconst SIGNATURE_DELIMITER_RE = /^--\\s*$/\nconst SIGNOFF_RE = /^(?:thanks,|thank you,|best,|regards,|sincerely,)$/i\nconst DETAILS_START_RE = /^(?:confidentiality notice\\b|this message and any attachments\\b|this email and any attachments\\b|the information contained in this message\\b|this communication may contain\\b|unsubscribe\\b|manage your preferences\\b)/i\nconst CONTACT_DETAIL_RE = /(?:@|https?:\\/\\/|www\\.|\\+?\\d[\\d\\s().-]{6,}\\d|\\b(?:ceo|cfo|cto|coo|founder|co-founder|director|manager|vp|vice president|president|head of|sales|marketing|operations|account|customer success|success|support|engineer|consultant|partner|principal|advisor|associate)\\b|\\b(?:inc|llc|ltd|corp|corporation|company|co\\.)\\b)/i\n\nfunction decodeHtmlText(value: string): string {\n const withoutTags = value\n .replace(BR_TAG_RE, \"\\n\")\n .replace(/<\\/(p|div|blockquote|li|tr|table|ul|ol)>/gi, \"\\n\")\n .replace(/<[^>]*>/g, \"\")\n\n if (typeof document !== \"undefined\") {\n const textarea = document.createElement(\"textarea\")\n textarea.innerHTML = withoutTags\n return textarea.value\n }\n\n return withoutTags\n .replace(/&nbsp;/gi, \" \")\n .replace(/&amp;/gi, \"&\")\n .replace(/&lt;/gi, \"<\")\n .replace(/&gt;/gi, \">\")\n .replace(/&quot;/gi, '\"')\n .replace(/&#39;|&apos;/gi, \"'\")\n}\n\nfunction htmlToVisibleText(html: string): string {\n return decodeHtmlText(html).replace(/\\u00a0/g, \" \").replace(/[ \\t]+/g, \" \").trim()\n}\n\nfunction serializeNode(node: Node): string {\n const host = document.createElement(\"div\")\n host.appendChild(node.cloneNode(true))\n return host.innerHTML\n}\n\nfunction wrapHtmlLike(element: Element, innerHtml: string): string {\n const clone = element.cloneNode(false) as HTMLElement\n clone.innerHTML = innerHtml\n return clone.outerHTML\n}\n\nfunction hasDirectBr(nodes: readonly Node[]): boolean {\n return nodes.some((node) => node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\")\n}\n\nfunction hasDirectBlockChild(element: Element): boolean {\n return Array.from(element.children).some((child) => {\n const tagName = child.tagName.toLowerCase()\n return tagName !== \"br\" && BLOCK_TAGS.has(tagName)\n })\n}\n\nfunction makeHtmlSegment(html: string): MessageSegment | null {\n const visibleText = htmlToVisibleText(html)\n if (!html.trim() || (!visibleText && !/<(?:img|hr)\\b/i.test(html))) return null\n return { html, visibleText }\n}\n\nfunction splitInlineNodes(nodes: readonly Node[], wrapper?: Element): MessageSegment[] {\n const containsBr = hasDirectBr(nodes)\n const chunks: string[][] = [[]]\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\") {\n chunks.push([])\n return\n }\n\n chunks[chunks.length - 1]?.push(serializeNode(node))\n })\n\n return chunks\n .map((chunk) => chunk.join(\"\"))\n .map((innerHtml) => {\n if (wrapper) return wrapHtmlLike(wrapper, innerHtml)\n return containsBr ? `<div>${innerHtml}</div>` : innerHtml\n })\n .map(makeHtmlSegment)\n .filter((segment): segment is MessageSegment => Boolean(segment))\n}\n\nfunction splitElementSegment(element: Element): MessageSegment[] {\n const tagName = element.tagName.toLowerCase()\n\n if (tagName === \"div\" && hasDirectBlockChild(element)) {\n const childSegments = splitHtmlNodes(Array.from(element.childNodes))\n return childSegments.length ? childSegments : [makeHtmlSegment(element.outerHTML)].filter(Boolean) as MessageSegment[]\n }\n\n if (SPLITTABLE_BLOCK_TAGS.has(tagName) && hasDirectBr(Array.from(element.childNodes)) && !hasDirectBlockChild(element)) {\n return splitInlineNodes(Array.from(element.childNodes), element)\n }\n\n const segment = makeHtmlSegment(element.outerHTML)\n return segment ? [segment] : []\n}\n\nfunction splitHtmlNodes(nodes: readonly Node[]): MessageSegment[] {\n const segments: MessageSegment[] = []\n let inlineNodes: Node[] = []\n\n const flushInline = () => {\n if (!inlineNodes.length) return\n segments.push(...splitInlineNodes(inlineNodes))\n inlineNodes = []\n }\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element\n const tagName = element.tagName.toLowerCase()\n if (BLOCK_TAGS.has(tagName)) {\n flushInline()\n segments.push(...splitElementSegment(element))\n return\n }\n }\n\n inlineNodes.push(node)\n })\n\n flushInline()\n return segments\n}\n\nfunction findMatchingCloseTag(html: string, tagName: string, openTagEnd: number): number {\n if (tagName === \"hr\") return openTagEnd + 1\n\n const tagPattern = new RegExp(`</?${tagName}\\\\b[^>]*>`, \"gi\")\n tagPattern.lastIndex = openTagEnd + 1\n let depth = 1\n let match: RegExpExecArray | null\n\n while ((match = tagPattern.exec(html)) !== null) {\n const rawTag = match[0]\n if (/^<\\//.test(rawTag)) depth -= 1\n else if (!/\\/\\s*>$/.test(rawTag)) depth += 1\n if (depth === 0) return tagPattern.lastIndex\n }\n\n return html.length\n}\n\nfunction splitHtmlSegmentsFallback(html: string): MessageSegment[] {\n const segments: MessageSegment[] = []\n let cursor = 0\n\n const pushInline = (inlineHtml: string) => {\n const chunks = inlineHtml.split(BR_TAG_RE)\n const hadBr = chunks.length > 1\n chunks.forEach((chunk) => {\n const segment = makeHtmlSegment(hadBr ? `<div>${chunk}</div>` : chunk)\n if (segment) segments.push(segment)\n })\n }\n\n while (cursor < html.length) {\n const rest = html.slice(cursor)\n const match = HTML_BLOCK_START_RE.exec(rest)\n if (!match || match.index === undefined) {\n pushInline(rest)\n break\n }\n\n if (match.index > 0) pushInline(rest.slice(0, match.index))\n\n const tagStart = cursor + match.index\n const rawOpen = match[0]\n const tagName = match[1].toLowerCase()\n const openTagEnd = tagStart + rawOpen.length - 1\n const segmentEnd = findMatchingCloseTag(html, tagName, openTagEnd)\n const blockHtml = html.slice(tagStart, segmentEnd)\n\n if (SPLITTABLE_BLOCK_TAGS.has(tagName) && BR_TAG_RE.test(blockHtml)) {\n const openTag = rawOpen\n const closeTag = `</${tagName}>`\n const inner = blockHtml.replace(new RegExp(`^${rawOpen.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}`, \"i\"), \"\").replace(new RegExp(`${closeTag}$`, \"i\"), \"\")\n inner.split(BR_TAG_RE).forEach((chunk) => {\n const segment = makeHtmlSegment(`${openTag}${chunk}${closeTag}`)\n if (segment) segments.push(segment)\n })\n } else {\n const segment = makeHtmlSegment(blockHtml)\n if (segment) segments.push(segment)\n }\n\n cursor = segmentEnd\n }\n\n return segments\n}\n\nfunction segmentHtmlMessage(html: string): MessageSegment[] {\n if (typeof document === \"undefined\" || typeof Node === \"undefined\") {\n return splitHtmlSegmentsFallback(html)\n }\n\n const template = document.createElement(\"template\")\n template.innerHTML = html\n return splitHtmlNodes(Array.from(template.content.childNodes))\n}\n\nfunction segmentTextMessage(text: string): MessageSegment[] {\n const lines = text.match(/[^\\n]*(?:\\n|$)/g) ?? []\n return lines\n .filter((line, index) => line.length > 0 && !(index === lines.length - 1 && line === \"\"))\n .map((line) => ({ text: line, visibleText: line.replace(/\\u00a0/g, \" \").trim() }))\n}\n\nfunction firstVisibleLine(text: string): string {\n return text.replace(/\\u00a0/g, \" \").trimStart().split(/\\r?\\n/).find((line) => line.trim())?.trim() ?? \"\"\n}\n\nfunction isLikelySenderNameLine(line: string): boolean {\n if (!line || line.length > 60) return false\n if (/[,@:;!?]|https?:\\/\\/|www\\.|\\d/.test(line)) return false\n\n const words = line.split(/\\s+/).filter(Boolean)\n if (words.length < 1 || words.length > 4) return false\n\n return words.every((word) => /^[A-Z][A-Za-z'.-]*$/.test(word))\n}\n\nfunction nextVisibleSegmentText(segments: MessageSegment[], fromIndex: number): string {\n for (let index = fromIndex + 1; index < segments.length; index += 1) {\n const text = firstVisibleLine(segments[index]?.visibleText ?? \"\")\n if (text) return text\n }\n return \"\"\n}\n\nfunction isFooterBoundary(segments: MessageSegment[], index: number): boolean {\n const line = firstVisibleLine(segments[index]?.visibleText ?? \"\")\n if (!line) return false\n if (SIGNATURE_DELIMITER_RE.test(line) || DETAILS_START_RE.test(line)) return true\n\n const nextText = nextVisibleSegmentText(segments, index)\n if (SIGNOFF_RE.test(line)) return Boolean(nextText && (isLikelySenderNameLine(nextText) || CONTACT_DETAIL_RE.test(nextText)))\n\n return isLikelySenderNameLine(line) && CONTACT_DETAIL_RE.test(nextText)\n}\n\nfunction splitFooterSegments(segments: MessageSegment[]): { bodySegments: MessageSegment[]; detailsSegments: MessageSegment[] } {\n const visibleIndexes = segments\n .map((segment, index) => (segment.visibleText ? index : -1))\n .filter((index) => index >= 0)\n const visibleCount = visibleIndexes.length\n if (visibleCount < 2) return { bodySegments: segments, detailsSegments: [] }\n\n const trailingCount = Math.max(Math.ceil(visibleCount * 0.4), 8)\n const firstTrailingOrdinal = Math.max(1, visibleCount - trailingCount)\n\n for (let ordinal = firstTrailingOrdinal; ordinal < visibleCount; ordinal += 1) {\n const index = visibleIndexes[ordinal]\n if (ordinal > 0 && isFooterBoundary(segments, index)) {\n return { bodySegments: segments.slice(0, index), detailsSegments: segments.slice(index) }\n }\n }\n\n return { bodySegments: segments, detailsSegments: [] }\n}\n\nfunction splitMessageHtml(rawHtml: string): CollapsedHtmlMessage {\n const sanitizedHtml = sanitizeHtml(rawHtml)\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentHtmlMessage(sanitizedHtml))\n\n if (!detailsSegments.length) return { bodyHtml: sanitizedHtml, detailsHtml: \"\" }\n\n return {\n bodyHtml: bodySegments.map((segment) => segment.html ?? \"\").join(\"\"),\n detailsHtml: detailsSegments.map((segment) => segment.html ?? \"\").join(\"\"),\n }\n}\n\nfunction splitMessageText(text: string): CollapsedTextMessage {\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentTextMessage(text))\n\n if (!detailsSegments.length) return { bodyText: text, detailsText: \"\" }\n\n return {\n bodyText: bodySegments.map((segment) => segment.text ?? \"\").join(\"\"),\n detailsText: detailsSegments.map((segment) => segment.text ?? \"\").join(\"\"),\n }\n}\n\nfunction messageBodySnippet(message: Pick<ConvMessage, \"body\" | \"bodyHtml\">, maxLength = 140): string {\n if (message.bodyHtml) {\n return htmlToTextSnippet(splitMessageHtml(message.bodyHtml).bodyHtml, maxLength)\n }\n\n return splitMessageText(message.body ?? \"\").bodyText.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\"\n}\n\nfunction GmailMark({ size = 14 }: { size?: number }) {\n return (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={BRAND_ICONS.gmail.icon}\n alt=\"Gmail\"\n width={size}\n height={size}\n style={{ width: size, height: size, objectFit: \"contain\", display: \"block\" }}\n />\n )\n}\n\nfunction PersonAvatar({ person, size = \"sm\" }: { person: ConvParticipant; size?: \"sm\" | \"default\" }) {\n return (\n <Avatar size={size}>\n {person.avatarUrl ? <AvatarImage src={person.avatarUrl} alt={person.name} /> : null}\n <AvatarFallback className=\"bg-muted text-muted-foreground text-[10px] font-medium uppercase\">\n {getInitials({ name: person.name, email: person.email })}\n </AvatarFallback>\n </Avatar>\n )\n}\n\nfunction firstName(name: string): string {\n return name.split(\" \")[0] || name\n}\n\nconst STATUS_PILL: Record<ConvStatus, { label: string; cls: string }> = {\n responded: { label: \"New reply\", cls: \"bg-status-active-bg text-status-active-fg border-status-active-border\" },\n draft: { label: \"Draft\", cls: \"bg-background text-foreground/80 border-border\" },\n awaiting: { label: \"Awaiting\", cls: \"bg-status-pending-bg text-status-pending-fg border-status-pending-border\" },\n viewing: { label: \"Viewing\", cls: \"bg-muted text-muted-foreground border-border\" },\n}\n\nconst STATUS_DOT: Record<ConvStatus, string> = {\n responded: \"bg-status-active-fg\",\n draft: \"bg-status-pending-fg\",\n awaiting: \"bg-status-pending-fg\",\n viewing: \"bg-muted-foreground/50\",\n}\n\nfunction effectiveStatus(t: ConversationThread): ConvStatus {\n return t.canReply === false ? \"viewing\" : t.status\n}\n\n/* ── One message (collapsible) ──────────────────────────────────────────── */\n\nfunction MessageView({\n message,\n expanded,\n onToggle,\n me,\n}: {\n message: ConvMessage\n expanded: boolean\n onToggle: () => void\n me?: ConvParticipant\n}) {\n const [quoteOpen, setQuoteOpen] = React.useState(false)\n const [detailsOpen, setDetailsOpen] = React.useState(false)\n\n if (!expanded) {\n const snippet = messageBodySnippet(message, 140)\n\n return (\n <button\n type=\"button\"\n data-slot=\"conv-message-collapsed\"\n onClick={onToggle}\n className=\"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left hover:bg-muted/40\"\n >\n <PersonAvatar person={message.from} />\n <span className=\"text-muted-foreground min-w-0 flex-1 truncate text-[13px]\">\n <b className=\"text-foreground\">{firstName(message.from.name)}</b> · {snippet}\n </span>\n <span className=\"text-muted-foreground/60 shrink-0 text-xs\">{message.ago ?? message.date}</span>\n <ChevronDown size={13} className=\"text-muted-foreground shrink-0\" />\n </button>\n )\n }\n\n const toLabel = me && message.to.email === me.email ? \"me\" : firstName(message.to.name)\n const htmlParts = message.bodyHtml ? splitMessageHtml(message.bodyHtml) : null\n const textParts = message.bodyHtml ? null : splitMessageText(message.body ?? \"\")\n const hasDetails = Boolean(htmlParts?.detailsHtml || textParts?.detailsText)\n\n return (\n <div data-slot=\"conv-message\" className=\"rounded-md border border-border bg-background\">\n <button\n type=\"button\"\n onClick={onToggle}\n className=\"flex w-full items-start gap-2 px-3 py-2 text-left\"\n >\n <PersonAvatar person={message.from} size=\"default\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex flex-wrap items-baseline gap-x-1.5\">\n <span className=\"text-[13px] font-semibold\">{message.from.name}</span>\n <span className=\"text-muted-foreground/60 truncate text-xs\">&lt;{message.from.email}&gt;</span>\n </span>\n <span className=\"text-muted-foreground block text-xs\">\n to <b>{toLabel}</b>\n </span>\n </span>\n <span className=\"flex shrink-0 items-center gap-2\">\n {message.receipt ? (\n <span className=\"text-muted-foreground inline-flex items-center gap-1 text-[11px]\">\n {message.receipt.kind === \"new\" ? (\n <CornerUpLeft size={11} />\n ) : message.receipt.kind === \"read\" ? (\n <CheckCheck size={11} />\n ) : message.receipt.kind === \"draft\" ? (\n <FilePenLine size={11} />\n ) : (\n <MailOpen size={11} />\n )}\n {message.receipt.label}\n </span>\n ) : null}\n <span className=\"text-muted-foreground/60 text-xs\">{message.date}</span>\n <ChevronUp size={13} className=\"text-muted-foreground\" />\n </span>\n </button>\n\n <div className=\"px-3 pb-2.5\">\n {htmlParts ? (\n <div data-slot=\"conv-message-body\" className={PROSE} dangerouslySetInnerHTML={{ __html: htmlParts.bodyHtml }} />\n ) : (\n <div data-slot=\"conv-message-body\" className={cn(PROSE, \"whitespace-pre-line\")}>{textParts?.bodyText}</div>\n )}\n\n {hasDetails ? (\n <div className=\"mt-2\">\n <button\n type=\"button\"\n onClick={() => setDetailsOpen((v) => !v)}\n className=\"text-muted-foreground hover:text-foreground hover:bg-muted rounded px-1.5 text-xs leading-5\"\n aria-expanded={detailsOpen}\n >\n {detailsOpen ? \"Hide signature/details\" : \"Show signature/details\"}\n </button>\n {detailsOpen ? (\n <div className=\"border-border text-muted-foreground mt-1 border-l-2 pl-3 text-[13px]\">\n {htmlParts ? (\n <div data-slot=\"conv-message-details\" className={PROSE} dangerouslySetInnerHTML={{ __html: htmlParts.detailsHtml }} />\n ) : (\n <div data-slot=\"conv-message-details\" className={cn(PROSE, \"whitespace-pre-line\")}>{textParts?.detailsText}</div>\n )}\n </div>\n ) : null}\n </div>\n ) : null}\n\n {message.quoted ? (\n <div className=\"mt-2\">\n <button\n type=\"button\"\n onClick={() => setQuoteOpen((v) => !v)}\n className=\"text-muted-foreground hover:bg-muted rounded px-1.5 text-xs leading-5\"\n title={quoteOpen ? \"Hide quoted text\" : \"Show quoted text\"}\n >\n •••\n </button>\n {quoteOpen ? (\n <div className=\"border-border text-muted-foreground mt-1 border-l-2 pl-3 text-[13px]\">\n <p className=\"mb-1\" dangerouslySetInnerHTML={{ __html: sanitizeHtml(message.quoted.attr) }} />\n <div className={PROSE} dangerouslySetInnerHTML={{ __html: sanitizeHtml(message.quoted.html) }} />\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n\n/* ── Reply composer ─────────────────────────────────────────────────────── */\n\ntype PreviewState = {\n loading: boolean\n /** Sanitized HTML ready to render. */\n html: string | null\n /** Confirmation token returned by the server preview, retained for reuse on send. */\n confirmationToken: string | null\n error: string | null\n /** True when `html` came from the local fallback rather than the server. */\n local: boolean\n}\n\nconst IDLE_PREVIEW: PreviewState = { loading: false, html: null, confirmationToken: null, error: null, local: false }\n\nfunction ReplyComposer({\n thread,\n me,\n replyAll,\n tenantName,\n onClose,\n onSend,\n onDraft,\n onPreviewReply,\n}: {\n thread: ConversationThread\n me?: ConvParticipant\n replyAll: boolean\n tenantName?: string\n onClose: () => void\n onSend: (body: string, includeSignature: boolean) => void | Promise<void>\n onDraft: (body: string, includeSignature: boolean) => void | Promise<void>\n onPreviewReply?: (payload: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n}) {\n const [body, setBody] = React.useState(thread.draft ?? \"\")\n const [sig, setSig] = React.useState(true)\n const [preview, setPreview] = React.useState(false)\n const [previewState, setPreviewState] = React.useState<PreviewState>(IDLE_PREVIEW)\n const [sending, setSending] = React.useState(false)\n const [sendError, setSendError] = React.useState<string | null>(null)\n const ccList = replyAll ? thread.cc ?? [] : []\n const subject = /^re:/i.test(thread.subject) ? thread.subject : `Re: ${thread.subject}`\n\n const localPreviewHtml = textToHtml(body) + (sig && thread.signature ? textToHtml(thread.signature) : \"\")\n\n const openPreview = async () => {\n setPreview(true)\n setSendError(null)\n\n if (!onPreviewReply) {\n // No server preview contract: render a sanitized local draft preview only.\n setPreviewState({ loading: false, html: sanitizeHtml(localPreviewHtml), confirmationToken: null, error: null, local: true })\n return\n }\n\n setPreviewState({ loading: true, html: null, confirmationToken: null, error: null, local: false })\n try {\n const result = await onPreviewReply({ threadId: thread.threadId, body, includeSignature: sig, replyAll })\n setPreviewState({\n loading: false,\n html: sanitizeHtml(result.htmlBody ?? \"\"),\n confirmationToken: result.confirmationToken ?? null,\n error: null,\n local: false,\n })\n } catch (error) {\n setPreviewState({\n loading: false,\n html: null,\n confirmationToken: null,\n error: error instanceof Error ? error.message : \"Could not load the preview. Please try again.\",\n local: false,\n })\n }\n }\n\n const handleSend = async () => {\n setSending(true)\n setSendError(null)\n try {\n await onSend(body, sig)\n setPreview(false)\n setPreviewState(IDLE_PREVIEW)\n } catch (error) {\n setSendError(error instanceof Error ? error.message : \"Could not send this reply. Please try again.\")\n } finally {\n setSending(false)\n }\n }\n\n const handleDraft = async () => {\n setSending(true)\n setSendError(null)\n try {\n await onDraft(body, sig)\n setPreview(false)\n setPreviewState(IDLE_PREVIEW)\n } catch (error) {\n setSendError(error instanceof Error ? error.message : \"Could not create the Gmail draft. Please try again.\")\n } finally {\n setSending(false)\n }\n }\n\n return (\n <div data-slot=\"conv-reply\" className=\"border-border bg-muted/20 rounded-md border p-3\">\n <div className=\"mb-2 flex items-center gap-2\">\n {me ? <PersonAvatar person={me} /> : null}\n <span className=\"flex-1 text-[13px] font-medium\">\n {replyAll ? (\n <>\n Reply all{\" \"}\n <span className=\"text-muted-foreground font-normal\">· {1 + ccList.length} recipients</span>\n </>\n ) : (\n <>\n Reply to <b>{firstName(thread.contact.name)}</b>\n </>\n )}\n </span>\n <span className=\"text-muted-foreground inline-flex items-center gap-1 text-[11px]\">\n <GitMerge size={11} /> Same thread\n </span>\n <button type=\"button\" onClick={onClose} title=\"Discard reply\" className=\"text-muted-foreground hover:text-foreground\">\n <X size={15} />\n </button>\n </div>\n\n <div className=\"border-border mb-2 space-y-1 border-b pb-2 text-[13px]\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">To</span>\n <span className=\"font-medium\">{thread.contact.name}</span>\n <span className=\"text-muted-foreground/60 truncate text-xs\">{thread.contact.email}</span>\n </div>\n {replyAll && ccList.length ? (\n <div className=\"flex items-start gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">Cc</span>\n <span className=\"text-muted-foreground text-xs\">\n {ccList.map((c) => c.name).join(\", \")}\n </span>\n </div>\n ) : null}\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">Subject</span>\n <span className=\"truncate\">{subject}</span>\n <span className=\"text-muted-foreground/60 ml-auto inline-flex items-center gap-1 text-[11px]\">\n <Lock size={10} /> on thread\n </span>\n </div>\n </div>\n\n <Textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder=\"Write your reply…\"\n className=\"min-h-28 resize-y text-sm\"\n onKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === \"Enter\") {\n e.preventDefault()\n void openPreview()\n }\n }}\n />\n\n {sig && thread.signature ? (\n <div className=\"text-muted-foreground mt-2 whitespace-pre-line border-l-2 border-border pl-3 text-[13px]\">\n <span className=\"text-muted-foreground/60 mr-1\">--</span>\n {thread.signature}\n </div>\n ) : null}\n\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <RichTextToolbar />\n <label className=\"text-muted-foreground ml-auto inline-flex cursor-pointer items-center gap-1.5 text-[12px]\">\n <Switch checked={sig} onCheckedChange={setSig} aria-label=\"Toggle signature\" />\n Signature\n </label>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={sending} onClick={() => void openPreview()}>\n <Eye size={14} /> Preview\n </Button>\n <Button type=\"button\" size=\"sm\" disabled={sending} onClick={() => void openPreview()}>\n <Send size={14} /> Send\n </Button>\n </div>\n\n <Dialog open={preview} onOpenChange={(open) => { if (!sending) { setPreview(open); if (!open) setPreviewState(IDLE_PREVIEW) } }}>\n <DialogContent className=\"max-w-xl\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-1.5 text-[15px]\">\n <Eye size={15} /> {previewState.local ? \"Local draft preview\" : \"Reply preview\"}\n </DialogTitle>\n <DialogDescription>\n {previewState.local\n ? \"Local draft preview only — the server prepares the exact message on send.\"\n : <>Stays on the {subject.replace(/^Re:\\s*/i, \"\")} thread. Gmail keeps it threaded.</>}\n </DialogDescription>\n </DialogHeader>\n <div className=\"border-border space-y-1 rounded-md border p-3 text-[13px]\">\n <div>\n <span className=\"text-muted-foreground\">To </span>\n <b>{thread.contact.name}</b>{\" \"}\n <span className=\"text-muted-foreground/60\">&lt;{thread.contact.email}&gt;</span>\n </div>\n {replyAll && ccList.length ? (\n <div className=\"text-muted-foreground\">Cc {ccList.map((c) => c.name).join(\", \")}</div>\n ) : null}\n <div>\n <span className=\"text-muted-foreground\">Subject </span>\n {subject}\n </div>\n </div>\n {previewState.loading ? (\n <div data-slot=\"conv-preview-loading\" role=\"status\" className=\"text-muted-foreground flex items-center gap-2 px-1 py-6 text-[13px]\">\n <span className=\"border-muted-foreground/40 border-t-foreground size-4 animate-spin rounded-full border-2\" aria-hidden />\n Loading preview…\n </div>\n ) : previewState.error ? (\n <p role=\"alert\" className=\"text-destructive text-sm\">\n {previewState.error}\n </p>\n ) : (\n <div\n data-slot=\"conv-preview-body\"\n data-confirmation-token={previewState.confirmationToken ?? undefined}\n className={cn(PROSE, \"max-h-72 overflow-auto\")}\n dangerouslySetInnerHTML={{ __html: previewState.html ?? \"\" }}\n />\n )}\n {sendError ? (\n <p role=\"alert\" className=\"text-destructive text-sm\">\n {sendError}\n </p>\n ) : null}\n <DialogFooter className=\"sm:justify-between\">\n <button\n type=\"button\"\n disabled={sending || previewState.loading}\n onClick={handleDraft}\n className=\"text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-[13px] disabled:pointer-events-none disabled:opacity-50\"\n >\n <GmailMark size={14} /> Open draft in Gmail\n </button>\n <span className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={sending} onClick={() => { setPreview(false); setPreviewState(IDLE_PREVIEW) }}>\n Keep editing\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={sending || previewState.loading}\n onClick={handleSend}\n >\n <Send size={14} /> {sending ? \"Sending...\" : \"Send now\"}\n </Button>\n </span>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n {tenantName ? (\n <p className=\"text-muted-foreground/70 mt-2 text-[11px]\">Sends via Gmail · playbooks stay stopped.</p>\n ) : null}\n </div>\n )\n}\n\n/* ── Thread body (messages + footer/composer/done states) ───────────────── */\n\ntype ThreadMode = \"idle\" | \"replying\" | \"sent\" | \"draft\"\n\nfunction ThreadBody({\n thread,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n}: {\n thread: ConversationThread\n me?: ConvParticipant\n tenantName?: string\n onSendReply?: (p: ConversationReplyPayload) => void | Promise<void>\n onCreateGmailDraft?: (p: ConversationReplyPayload) => void | Promise<void>\n onPreviewReply?: (p: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n onOpenInGmail?: (threadId: string) => void\n}) {\n const canReply = thread.canReply !== false\n const hasCc = !!(thread.cc && thread.cc.length)\n const [mode, setMode] = React.useState<ThreadMode>(\"idle\")\n const [replyAll, setReplyAll] = React.useState(false)\n const [expanded, setExpanded] = React.useState<Record<string, boolean>>(() => {\n const o: Record<string, boolean> = {}\n thread.messages.forEach((m, i) => {\n o[m.id] = i === thread.messages.length - 1\n })\n return o\n })\n\n const toggle = (id: string) => setExpanded((e) => ({ ...e, [id]: !e[id] }))\n\n return (\n <div data-slot=\"conv-thread-body\" className=\"space-y-2\">\n {canReply && thread.paused ? (\n <div className=\"border-status-pending-border bg-status-pending-bg text-status-pending-fg flex items-start gap-2 rounded-md border p-2.5 text-[12px]\">\n <Pause size={13} className=\"mt-0.5 shrink-0\" />\n <span>\n <b>Follow-up actions stopped.</b> Your {thread.paused.playbook} next steps won’t send\n automatically while this conversation is live. Continue it in {tenantName ?? \"the app\"} or Gmail.\n </span>\n </div>\n ) : null}\n\n <div className=\"space-y-1\">\n {thread.messages.map((m) => (\n <MessageView key={m.id} message={m} expanded={!!expanded[m.id]} onToggle={() => toggle(m.id)} me={me} />\n ))}\n </div>\n\n {!canReply ? (\n <div className=\"border-border bg-muted/30 text-muted-foreground flex items-start gap-2 rounded-md border p-2.5 text-[12px]\">\n <Eye size={14} className=\"mt-0.5 shrink-0\" />\n <span>\n <b>Viewing only.</b> You’re not a participant on this thread, so replying is disabled here.\n </span>\n </div>\n ) : null}\n\n {canReply && mode === \"idle\" ? (\n <div data-slot=\"conv-action-row\" className=\"border-border/70 mt-1 flex flex-wrap items-center gap-x-3 gap-y-2 border-t pt-3\">\n <Button type=\"button\" size=\"sm\" onClick={() => { setReplyAll(false); setMode(\"replying\") }}>\n <Reply size={15} /> Reply\n </Button>\n {hasCc ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => { setReplyAll(true); setMode(\"replying\") }}>\n <ReplyAll size={14} /> Reply all\n </Button>\n ) : null}\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => onOpenInGmail?.(thread.threadId)}>\n <GmailMark size={14} /> Open in Gmail\n </Button>\n <span className=\"text-muted-foreground/70 ml-auto inline-flex items-center gap-1.5 text-[12px]\">\n <GitMerge size={13} /> Stays on this thread\n </span>\n </div>\n ) : null}\n\n {canReply && mode === \"replying\" ? (\n <ReplyComposer\n thread={thread}\n me={me}\n replyAll={replyAll}\n tenantName={tenantName}\n onPreviewReply={onPreviewReply}\n onClose={() => setMode(\"idle\")}\n onSend={async (body, includeSignature) => {\n await onSendReply?.({ threadId: thread.threadId, body, includeSignature, replyAll })\n setMode(\"sent\")\n }}\n onDraft={async (body, includeSignature) => {\n await onCreateGmailDraft?.({ threadId: thread.threadId, body, includeSignature, replyAll })\n setMode(\"draft\")\n }}\n />\n ) : null}\n\n {canReply && mode === \"sent\" ? (\n <div className=\"border-status-active-border bg-status-active-bg flex items-center gap-2 rounded-md border p-3 text-[13px]\">\n <Check size={16} className=\"text-status-active-fg shrink-0\" />\n <span className=\"flex-1\">\n <b>{replyAll ? \"Reply all sent\" : \"Reply sent\"}</b> · added to the thread. Delivered to{\" \"}\n <b>{thread.contact.name}</b>. This action stays <b>Pending</b>; playbooks remain stopped.\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setMode(\"idle\")}>\n Done\n </Button>\n </div>\n ) : null}\n\n {canReply && mode === \"draft\" ? (\n <div className=\"border-border bg-muted/30 flex items-center gap-2 rounded-md border p-3 text-[13px]\">\n <GmailMark size={16} />\n <span className=\"flex-1\">\n <b>Draft saved to Gmail.</b> Waiting on the <b>Re: {thread.subject}</b> thread; open it there to finish. Nothing was sent.\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => onOpenInGmail?.(thread.threadId)}>\n <GmailMark size={14} /> Open in Gmail\n </Button>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setMode(\"idle\")}>\n Done\n </Button>\n </div>\n ) : null}\n </div>\n )\n}\n\n/* ── A thread row + its inline reader ───────────────────────────────────── */\n\nfunction ThreadRow({\n thread,\n open,\n onToggleOpen,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n}: {\n thread: ConversationThread\n open: boolean\n onToggleOpen: () => void\n} & Pick<ConversationPanelProps, \"me\" | \"tenantName\" | \"onSendReply\" | \"onCreateGmailDraft\" | \"onPreviewReply\" | \"onOpenInGmail\">) {\n const status = effectiveStatus(thread)\n const last = thread.messages[thread.messages.length - 1]\n const who = last?.direction === \"inbound\" ? firstName(last.from.name) : \"You\"\n const lastSnippet = last ? messageBodySnippet(last, 120) : \"\"\n const pill = STATUS_PILL[status]\n\n return (\n <div data-slot=\"conv-thread\" data-open={open ? \"true\" : undefined} className=\"border-border border-b last:border-b-0\">\n <button\n type=\"button\"\n onClick={onToggleOpen}\n aria-expanded={open}\n className=\"flex w-full items-center gap-2.5 px-3 py-2.5 text-left hover:bg-muted/30\"\n >\n <span className={cn(\"size-2 shrink-0 rounded-full\", STATUS_DOT[status])} aria-hidden />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex items-center gap-2\">\n <span className=\"truncate text-sm font-semibold\">{thread.subject}</span>\n <span className={cn(\"shrink-0 rounded-md border px-1.5 py-px text-[10px] font-medium leading-4\", pill.cls)}>\n {pill.label}\n </span>\n </span>\n <span className=\"text-muted-foreground block truncate text-xs\">\n <b className=\"text-foreground/80\">{thread.contact.name}</b> · {who}: {lastSnippet}\n </span>\n </span>\n <span className=\"text-muted-foreground/60 shrink-0 text-xs\">{thread.lastWhen}</span>\n {open ? (\n <ChevronUp size={15} className=\"text-muted-foreground shrink-0\" />\n ) : (\n <ChevronDown size={15} className=\"text-muted-foreground shrink-0\" />\n )}\n </button>\n\n {open ? (\n <div className=\"px-3 pb-3\">\n <ThreadBody\n thread={thread}\n me={me}\n tenantName={tenantName}\n onSendReply={onSendReply}\n onCreateGmailDraft={onCreateGmailDraft}\n onPreviewReply={onPreviewReply}\n onOpenInGmail={onOpenInGmail}\n />\n </div>\n ) : null}\n </div>\n )\n}\n\n/* ── The hub ─────────────────────────────────────────────────────────────── */\n\nfunction ConversationPanel({\n threads,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n defaultOpenThreadId,\n className,\n}: ConversationPanelProps) {\n const responded = threads.filter((t) => t.status === \"responded\" && t.canReply !== false).length\n const draft = threads.filter((t) => effectiveStatus(t) === \"draft\").length\n const awaiting = threads.filter((t) => effectiveStatus(t) === \"awaiting\").length\n const anyPaused = threads.some((t) => t.paused)\n\n const [hubOpen, setHubOpen] = React.useState(true)\n const [openId, setOpenId] = React.useState<string | null>(() => {\n if (defaultOpenThreadId) return defaultOpenThreadId\n const firstActionable = threads.find((t) => [\"responded\", \"draft\"].includes(t.status) && t.canReply !== false)\n return firstActionable ? firstActionable.threadId : null\n })\n\n if (!threads.length) return null\n\n // Header badge state: a responded reply leads, then drafts to finish, then sent mail awaiting a reply.\n const badge =\n responded > 0\n ? { label: \"Email response detected\", dot: \"bg-status-active-fg\", ring: \"bg-status-active-fg/30\" }\n : draft > 0\n ? { label: \"Draft ready\", dot: \"bg-status-pending-fg\", ring: \"bg-status-pending-fg/30\" }\n : awaiting > 0\n ? { label: \"Awaiting response\", dot: \"bg-status-pending-fg\", ring: \"bg-status-pending-fg/30\" }\n : { label: \"Conversations\", dot: \"bg-muted-foreground/50\", ring: \"bg-muted-foreground/20\" }\n\n const headTitle =\n responded > 0\n ? `${responded} ${responded === 1 ? \"reply needs\" : \"replies need\"} your response`\n : draft > 0\n ? `Draft ready on ${draft} ${draft === 1 ? \"thread\" : \"threads\"}`\n : awaiting > 0\n ? `Awaiting response on ${awaiting} ${awaiting === 1 ? \"thread\" : \"threads\"}`\n : `${threads.length} email ${threads.length === 1 ? \"thread\" : \"threads\"}`\n\n return (\n <section\n data-slot=\"conversation-panel\"\n data-responded={responded > 0 ? \"true\" : undefined}\n className={cn(\"border-border bg-background overflow-hidden rounded-xl border\", className)}\n >\n <button\n type=\"button\"\n onClick={() => setHubOpen((v) => !v)}\n aria-expanded={hubOpen}\n className=\"flex w-full items-center gap-3 px-3 py-2.5 text-left\"\n >\n <span\n data-slot=\"conversation-badge\"\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold\",\n responded > 0\n ? \"bg-status-active-bg text-status-active-fg\"\n : draft > 0 || awaiting > 0\n ? \"bg-status-pending-bg text-status-pending-fg\"\n : \"bg-muted text-muted-foreground\"\n )}\n >\n <span className=\"relative inline-flex size-2\">\n <span className={cn(\"absolute inline-flex h-full w-full animate-ping rounded-full opacity-75\", badge.ring)} />\n <span className={cn(\"relative inline-flex size-2 rounded-full\", badge.dot)} />\n </span>\n {badge.label}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-semibold leading-tight\">{headTitle}</span>\n <span className=\"text-muted-foreground mt-0.5 block truncate text-xs\">\n {threads.length} {threads.length === 1 ? \"thread\" : \"threads\"} on this action\n {anyPaused ? <> · <b>playbook stopped</b></> : null}\n </span>\n </span>\n {hubOpen ? (\n <ChevronUp size={16} className=\"text-muted-foreground shrink-0\" />\n ) : (\n <ChevronDown size={16} className=\"text-muted-foreground shrink-0\" />\n )}\n </button>\n\n {hubOpen ? (\n <div className=\"border-border border-t\">\n {threads.map((t) => (\n <ThreadRow\n key={t.threadId}\n thread={t}\n open={openId === t.threadId}\n onToggleOpen={() => setOpenId((cur) => (cur === t.threadId ? null : t.threadId))}\n me={me}\n tenantName={tenantName}\n onSendReply={onSendReply}\n onCreateGmailDraft={onCreateGmailDraft}\n onPreviewReply={onPreviewReply}\n onOpenInGmail={onOpenInGmail}\n />\n ))}\n </div>\n ) : null}\n </section>\n )\n}\n\nexport { ConversationPanel }\n"],"mappings":"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","names":["_a","_b"]}
1
+ {"version":3,"sources":["../../src/components/conversation-panel.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * conversation-panel.tsx — in-case email-thread reader + reply, for the case\n * panel (\"Email response detected\" hub).\n *\n * v1 scope (WIT-853 / WIT-802): INLINE thread reading + reply only.\n * - A collapsible hub header with a pulse badge whose color reflects state:\n * responded (a reply needs you) / awaiting (sent, waiting) / viewing (read-only).\n * - A list of thread rows; clicking one opens a Gmail-style reader inline,\n * right under the row, with collapsible messages + quoted-history toggle.\n * - Reply / Reply-all composer with a signature toggle and two send paths:\n * Preview→Send (in-app) and \"Open draft in Gmail\" (deep link).\n * - A \"playbook stopped\" banner when the customer's reply halted the sequence,\n * and a read-only notice when the operator is not a thread participant.\n *\n * The bottom-right floating dock / side-by-side compare is intentionally OUT of\n * scope here and tracked separately (WIT-855).\n *\n * Presentational: all data + side effects come from the consumer. Email bodies\n * (`bodyHtml`, `quoted.html`) are sanitized before rendering.\n */\n\nimport * as React from \"react\"\nimport {\n ChevronDown,\n ChevronUp,\n CornerUpLeft,\n CheckCheck,\n MailOpen,\n FilePenLine,\n Reply,\n ReplyAll,\n Eye,\n Send,\n Lock,\n Pause,\n GitMerge,\n Check,\n X,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { getInitials } from \"../lib/user-display\"\nimport { BRAND_ICONS } from \"../lib/icons\"\nimport { htmlToTextSnippet, sanitizeHtml } from \"../internal/safe-html\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./avatar\"\nimport { Button } from \"./button\"\nimport { Switch } from \"./switch\"\nimport { Textarea } from \"./textarea\"\nimport { RichTextToolbar } from \"./rich-text-toolbar\"\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogDescription,\n DialogFooter,\n} from \"./dialog\"\n\n/* ── Types ───────────────────────────────────────────────────────────────── */\n\nexport interface ConvParticipant {\n name: string\n email: string\n avatarUrl?: string | null\n role?: string\n}\n\nexport interface ConvMessage {\n id: string\n direction: \"inbound\" | \"outbound\"\n from: ConvParticipant\n to: ConvParticipant\n /** Absolute timestamp label, e.g. \"Jun 1, 2026, 9:12 AM\". */\n date: string\n /** Relative label, e.g. \"2 days ago\". */\n ago?: string\n receipt?: { kind: \"new\" | \"read\" | \"opened\" | \"sent\" | \"draft\"; label: string }\n /** HTML body (preferred). Sanitized by the component before rendering. */\n bodyHtml?: string\n /** Plain-text fallback when `bodyHtml` is absent. */\n body?: string\n /** Quoted prior message, collapsed behind a toggle. Sanitized before rendering. */\n quoted?: { attr: string; html: string }\n}\n\nexport type ConvStatus = \"responded\" | \"awaiting\" | \"viewing\" | \"draft\"\n\nexport interface ConversationThread {\n threadId: string\n subject: string\n status: ConvStatus\n /** Relative label for the most recent activity. */\n lastWhen?: string\n contact: ConvParticipant\n cc?: ConvParticipant[]\n /** Set when this thread's reply halted a playbook (terminal). */\n paused?: { playbook: string } | null\n /** false => operator cannot reply or create drafts from this thread. */\n canReply?: boolean\n /** Explains why reply and draft creation are disabled. */\n replyDisabledReason?: string\n /** Existing Gmail draft or thread URL. Rendered as a new-tab link when present. */\n openInGmailUrl?: string | null\n /** Forces the Open in Gmail action into a disabled state. */\n openInGmailDisabled?: boolean\n /** Tooltip/read-only copy for a disabled Open in Gmail action. */\n openInGmailDisabledReason?: string | null\n messages: ConvMessage[]\n /** Prefilled reply draft body. */\n draft?: string\n /** Signature text appended to replies (plain text). */\n signature?: string\n}\n\nexport interface ConversationReplyPayload {\n threadId: string\n body: string\n includeSignature: boolean\n replyAll: boolean\n}\n\n/**\n * Result of a server-side reply preview. `htmlBody` is the exact HTML the server\n * prepared for this reply (sanitized by the component before render).\n * `confirmationToken`, when present, identifies the prepared confirmation so a\n * consumer can reuse it for the final send instead of re-preparing the message.\n */\nexport interface ConversationReplyPreview {\n htmlBody: string\n confirmationToken?: string\n}\n\nexport interface ConversationPanelProps {\n threads: ConversationThread[]\n /** Current operator: drives \"to me\" + the reply avatar. */\n me?: ConvParticipant\n /** Deployment brand, used in the paused-playbook copy. */\n tenantName?: string\n onSendReply?: (payload: ConversationReplyPayload) => void | Promise<void>\n onCreateGmailDraft?: (payload: ConversationReplyPayload) => void | Promise<void>\n /**\n * Server-side preview contract. When provided, the reply preview requests the\n * exact send HTML from the server, the component sanitizes it before render,\n * and retains any returned `confirmationToken` in preview state so a consumer\n * can reuse it for the final send. When omitted, the composer falls back to a\n * clearly labeled local draft preview only.\n */\n onPreviewReply?: (payload: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n onOpenInGmail?: (threadId: string) => void\n /** Inline-open this thread initially (defaults to the first responded one). */\n defaultOpenThreadId?: string\n className?: string\n}\n\n/* ── Shared helpers ──────────────────────────────────────────────────────── */\n\n/** Gmail-like reading-pane typography (mirrors timeline-activity's email mode). */\nconst PROSE = cn(\n \"text-sm leading-[1.62] text-foreground/90 break-words\",\n \"[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0\",\n \"[&_a]:text-[#1a73e8] [&_a]:underline-offset-2 hover:[&_a]:underline\",\n \"[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5\",\n \"[&_img]:max-w-full [&_img]:h-auto\"\n)\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\")\n}\n\n/** Plain-text -> simple paragraph HTML for the Preview / sent-message body. */\nfunction textToHtml(text: string): string {\n return text\n .split(/\\n{2,}/)\n .map((p) => p.trim())\n .filter(Boolean)\n .map((p) => `<p>${escapeHtml(p).replace(/\\n/g, \"<br>\")}</p>`)\n .join(\"\")\n}\n\ntype CollapsedHtmlMessage = { bodyHtml: string; detailsHtml: string }\ntype CollapsedTextMessage = { bodyText: string; detailsText: string }\ntype MessageSegment = { html?: string; text?: string; visibleText: string }\n\nconst SPLITTABLE_BLOCK_TAGS = new Set([\"p\", \"div\"])\nconst WHOLE_BLOCK_TAGS = new Set([\"blockquote\", \"table\", \"ul\", \"ol\", \"hr\"])\nconst BLOCK_TAGS = new Set([...SPLITTABLE_BLOCK_TAGS, ...WHOLE_BLOCK_TAGS])\nconst BR_TAG_RE = /<br\\s*\\/?>/gi\nconst HTML_BLOCK_START_RE = /<(p|div|blockquote|table|ul|ol|hr)\\b[^>]*>/i\n\nconst SIGNATURE_DELIMITER_RE = /^--\\s*$/\nconst SIGNOFF_RE = /^(?:thanks,|thank you,|best,|regards,|sincerely,)$/i\nconst DETAILS_START_RE = /^(?:confidentiality notice\\b|this message and any attachments\\b|this email and any attachments\\b|the information contained in this message\\b|this communication may contain\\b|unsubscribe\\b|manage your preferences\\b)/i\nconst CONTACT_DETAIL_RE = /(?:@|https?:\\/\\/|www\\.|\\+?\\d[\\d\\s().-]{6,}\\d|\\b(?:ceo|cfo|cto|coo|founder|co-founder|director|manager|vp|vice president|president|head of|sales|marketing|operations|account|customer success|success|support|engineer|consultant|partner|principal|advisor|associate)\\b|\\b(?:inc|llc|ltd|corp|corporation|company|co\\.)\\b)/i\n\nfunction decodeHtmlText(value: string): string {\n const withoutTags = value\n .replace(BR_TAG_RE, \"\\n\")\n .replace(/<\\/(p|div|blockquote|li|tr|table|ul|ol)>/gi, \"\\n\")\n .replace(/<[^>]*>/g, \"\")\n\n if (typeof document !== \"undefined\") {\n const textarea = document.createElement(\"textarea\")\n textarea.innerHTML = withoutTags\n return textarea.value\n }\n\n return withoutTags\n .replace(/&nbsp;/gi, \" \")\n .replace(/&amp;/gi, \"&\")\n .replace(/&lt;/gi, \"<\")\n .replace(/&gt;/gi, \">\")\n .replace(/&quot;/gi, '\"')\n .replace(/&#39;|&apos;/gi, \"'\")\n}\n\nfunction htmlToVisibleText(html: string): string {\n return decodeHtmlText(html).replace(/\\u00a0/g, \" \").replace(/[ \\t]+/g, \" \").trim()\n}\n\nfunction serializeNode(node: Node): string {\n const host = document.createElement(\"div\")\n host.appendChild(node.cloneNode(true))\n return host.innerHTML\n}\n\nfunction wrapHtmlLike(element: Element, innerHtml: string): string {\n const clone = element.cloneNode(false) as HTMLElement\n clone.innerHTML = innerHtml\n return clone.outerHTML\n}\n\nfunction hasDirectBr(nodes: readonly Node[]): boolean {\n return nodes.some((node) => node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\")\n}\n\nfunction hasDirectBlockChild(element: Element): boolean {\n return Array.from(element.children).some((child) => {\n const tagName = child.tagName.toLowerCase()\n return tagName !== \"br\" && BLOCK_TAGS.has(tagName)\n })\n}\n\nfunction makeHtmlSegment(html: string): MessageSegment | null {\n const visibleText = htmlToVisibleText(html)\n if (!html.trim() || (!visibleText && !/<(?:img|hr)\\b/i.test(html))) return null\n return { html, visibleText }\n}\n\nfunction splitInlineNodes(nodes: readonly Node[], wrapper?: Element): MessageSegment[] {\n const containsBr = hasDirectBr(nodes)\n const chunks: string[][] = [[]]\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === \"br\") {\n chunks.push([])\n return\n }\n\n chunks[chunks.length - 1]?.push(serializeNode(node))\n })\n\n return chunks\n .map((chunk) => chunk.join(\"\"))\n .map((innerHtml) => {\n if (wrapper) return wrapHtmlLike(wrapper, innerHtml)\n return containsBr ? `<div>${innerHtml}</div>` : innerHtml\n })\n .map(makeHtmlSegment)\n .filter((segment): segment is MessageSegment => Boolean(segment))\n}\n\nfunction splitElementSegment(element: Element): MessageSegment[] {\n const tagName = element.tagName.toLowerCase()\n\n if (tagName === \"div\" && hasDirectBlockChild(element)) {\n const childSegments = splitHtmlNodes(Array.from(element.childNodes))\n return childSegments.length ? childSegments : [makeHtmlSegment(element.outerHTML)].filter(Boolean) as MessageSegment[]\n }\n\n if (SPLITTABLE_BLOCK_TAGS.has(tagName) && hasDirectBr(Array.from(element.childNodes)) && !hasDirectBlockChild(element)) {\n return splitInlineNodes(Array.from(element.childNodes), element)\n }\n\n const segment = makeHtmlSegment(element.outerHTML)\n return segment ? [segment] : []\n}\n\nfunction splitHtmlNodes(nodes: readonly Node[]): MessageSegment[] {\n const segments: MessageSegment[] = []\n let inlineNodes: Node[] = []\n\n const flushInline = () => {\n if (!inlineNodes.length) return\n segments.push(...splitInlineNodes(inlineNodes))\n inlineNodes = []\n }\n\n nodes.forEach((node) => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element\n const tagName = element.tagName.toLowerCase()\n if (BLOCK_TAGS.has(tagName)) {\n flushInline()\n segments.push(...splitElementSegment(element))\n return\n }\n }\n\n inlineNodes.push(node)\n })\n\n flushInline()\n return segments\n}\n\nfunction findMatchingCloseTag(html: string, tagName: string, openTagEnd: number): number {\n if (tagName === \"hr\") return openTagEnd + 1\n\n const tagPattern = new RegExp(`</?${tagName}\\\\b[^>]*>`, \"gi\")\n tagPattern.lastIndex = openTagEnd + 1\n let depth = 1\n let match: RegExpExecArray | null\n\n while ((match = tagPattern.exec(html)) !== null) {\n const rawTag = match[0]\n if (/^<\\//.test(rawTag)) depth -= 1\n else if (!/\\/\\s*>$/.test(rawTag)) depth += 1\n if (depth === 0) return tagPattern.lastIndex\n }\n\n return html.length\n}\n\nfunction splitHtmlSegmentsFallback(html: string): MessageSegment[] {\n const segments: MessageSegment[] = []\n let cursor = 0\n\n const pushInline = (inlineHtml: string) => {\n const chunks = inlineHtml.split(BR_TAG_RE)\n const hadBr = chunks.length > 1\n chunks.forEach((chunk) => {\n const segment = makeHtmlSegment(hadBr ? `<div>${chunk}</div>` : chunk)\n if (segment) segments.push(segment)\n })\n }\n\n while (cursor < html.length) {\n const rest = html.slice(cursor)\n const match = HTML_BLOCK_START_RE.exec(rest)\n if (!match || match.index === undefined) {\n pushInline(rest)\n break\n }\n\n if (match.index > 0) pushInline(rest.slice(0, match.index))\n\n const tagStart = cursor + match.index\n const rawOpen = match[0]\n const tagName = match[1].toLowerCase()\n const openTagEnd = tagStart + rawOpen.length - 1\n const segmentEnd = findMatchingCloseTag(html, tagName, openTagEnd)\n const blockHtml = html.slice(tagStart, segmentEnd)\n\n if (SPLITTABLE_BLOCK_TAGS.has(tagName) && BR_TAG_RE.test(blockHtml)) {\n const openTag = rawOpen\n const closeTag = `</${tagName}>`\n const inner = blockHtml.replace(new RegExp(`^${rawOpen.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}`, \"i\"), \"\").replace(new RegExp(`${closeTag}$`, \"i\"), \"\")\n inner.split(BR_TAG_RE).forEach((chunk) => {\n const segment = makeHtmlSegment(`${openTag}${chunk}${closeTag}`)\n if (segment) segments.push(segment)\n })\n } else {\n const segment = makeHtmlSegment(blockHtml)\n if (segment) segments.push(segment)\n }\n\n cursor = segmentEnd\n }\n\n return segments\n}\n\nfunction segmentHtmlMessage(html: string): MessageSegment[] {\n if (typeof document === \"undefined\" || typeof Node === \"undefined\") {\n return splitHtmlSegmentsFallback(html)\n }\n\n const template = document.createElement(\"template\")\n template.innerHTML = html\n return splitHtmlNodes(Array.from(template.content.childNodes))\n}\n\nfunction segmentTextMessage(text: string): MessageSegment[] {\n const lines = text.match(/[^\\n]*(?:\\n|$)/g) ?? []\n return lines\n .filter((line, index) => line.length > 0 && !(index === lines.length - 1 && line === \"\"))\n .map((line) => ({ text: line, visibleText: line.replace(/\\u00a0/g, \" \").trim() }))\n}\n\nfunction firstVisibleLine(text: string): string {\n return text.replace(/\\u00a0/g, \" \").trimStart().split(/\\r?\\n/).find((line) => line.trim())?.trim() ?? \"\"\n}\n\nfunction isLikelySenderNameLine(line: string): boolean {\n if (!line || line.length > 60) return false\n if (/[,@:;!?]|https?:\\/\\/|www\\.|\\d/.test(line)) return false\n\n const words = line.split(/\\s+/).filter(Boolean)\n if (words.length < 1 || words.length > 4) return false\n\n return words.every((word) => /^[A-Z][A-Za-z'.-]*$/.test(word))\n}\n\nfunction nextVisibleSegmentText(segments: MessageSegment[], fromIndex: number): string {\n for (let index = fromIndex + 1; index < segments.length; index += 1) {\n const text = firstVisibleLine(segments[index]?.visibleText ?? \"\")\n if (text) return text\n }\n return \"\"\n}\n\nfunction isFooterBoundary(segments: MessageSegment[], index: number): boolean {\n const line = firstVisibleLine(segments[index]?.visibleText ?? \"\")\n if (!line) return false\n if (SIGNATURE_DELIMITER_RE.test(line) || DETAILS_START_RE.test(line)) return true\n\n const nextText = nextVisibleSegmentText(segments, index)\n if (SIGNOFF_RE.test(line)) return Boolean(nextText && (isLikelySenderNameLine(nextText) || CONTACT_DETAIL_RE.test(nextText)))\n\n return isLikelySenderNameLine(line) && CONTACT_DETAIL_RE.test(nextText)\n}\n\nfunction splitFooterSegments(segments: MessageSegment[]): { bodySegments: MessageSegment[]; detailsSegments: MessageSegment[] } {\n const visibleIndexes = segments\n .map((segment, index) => (segment.visibleText ? index : -1))\n .filter((index) => index >= 0)\n const visibleCount = visibleIndexes.length\n if (visibleCount < 2) return { bodySegments: segments, detailsSegments: [] }\n\n const trailingCount = Math.max(Math.ceil(visibleCount * 0.4), 8)\n const firstTrailingOrdinal = Math.max(1, visibleCount - trailingCount)\n\n for (let ordinal = firstTrailingOrdinal; ordinal < visibleCount; ordinal += 1) {\n const index = visibleIndexes[ordinal]\n if (ordinal > 0 && isFooterBoundary(segments, index)) {\n return { bodySegments: segments.slice(0, index), detailsSegments: segments.slice(index) }\n }\n }\n\n return { bodySegments: segments, detailsSegments: [] }\n}\n\nfunction splitMessageHtml(rawHtml: string): CollapsedHtmlMessage {\n const sanitizedHtml = sanitizeHtml(rawHtml)\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentHtmlMessage(sanitizedHtml))\n\n if (!detailsSegments.length) return { bodyHtml: sanitizedHtml, detailsHtml: \"\" }\n\n return {\n bodyHtml: bodySegments.map((segment) => segment.html ?? \"\").join(\"\"),\n detailsHtml: detailsSegments.map((segment) => segment.html ?? \"\").join(\"\"),\n }\n}\n\nfunction splitMessageText(text: string): CollapsedTextMessage {\n const { bodySegments, detailsSegments } = splitFooterSegments(segmentTextMessage(text))\n\n if (!detailsSegments.length) return { bodyText: text, detailsText: \"\" }\n\n return {\n bodyText: bodySegments.map((segment) => segment.text ?? \"\").join(\"\"),\n detailsText: detailsSegments.map((segment) => segment.text ?? \"\").join(\"\"),\n }\n}\n\nfunction messageBodySnippet(message: Pick<ConvMessage, \"body\" | \"bodyHtml\">, maxLength = 140): string {\n if (message.bodyHtml) {\n return htmlToTextSnippet(splitMessageHtml(message.bodyHtml).bodyHtml, maxLength)\n }\n\n return splitMessageText(message.body ?? \"\").bodyText.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\"\n}\n\nfunction GmailMark({ size = 14 }: { size?: number }) {\n return (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={BRAND_ICONS.gmail.icon}\n alt=\"Gmail\"\n width={size}\n height={size}\n style={{ width: size, height: size, objectFit: \"contain\", display: \"block\" }}\n />\n )\n}\n\nfunction PersonAvatar({ person, size = \"sm\" }: { person: ConvParticipant; size?: \"sm\" | \"default\" }) {\n return (\n <Avatar size={size}>\n {person.avatarUrl ? <AvatarImage src={person.avatarUrl} alt={person.name} /> : null}\n <AvatarFallback className=\"bg-muted text-muted-foreground text-[10px] font-medium uppercase\">\n {getInitials({ name: person.name, email: person.email })}\n </AvatarFallback>\n </Avatar>\n )\n}\n\nfunction firstName(name: string): string {\n return name.split(\" \")[0] || name\n}\n\nconst STATUS_PILL: Record<ConvStatus, { label: string; cls: string }> = {\n responded: { label: \"New reply\", cls: \"bg-status-active-bg text-status-active-fg border-status-active-border\" },\n draft: { label: \"Draft\", cls: \"bg-background text-foreground/80 border-border\" },\n awaiting: { label: \"Awaiting\", cls: \"bg-status-pending-bg text-status-pending-fg border-status-pending-border\" },\n viewing: { label: \"Viewing\", cls: \"bg-muted text-muted-foreground border-border\" },\n}\n\nconst STATUS_DOT: Record<ConvStatus, string> = {\n responded: \"bg-status-active-fg\",\n draft: \"bg-status-pending-fg\",\n awaiting: \"bg-status-pending-fg\",\n viewing: \"bg-muted-foreground/50\",\n}\n\nfunction effectiveStatus(t: ConversationThread): ConvStatus {\n return t.canReply === false ? \"viewing\" : t.status\n}\n\nfunction disabledOpenInGmailReason(thread: ConversationThread): string {\n return (\n thread.openInGmailDisabledReason?.trim() ||\n thread.replyDisabledReason?.trim() ||\n \"Gmail access is not available for this thread.\"\n )\n}\n\nfunction canOpenInGmail(thread: ConversationThread, onOpenInGmail?: (threadId: string) => void): boolean {\n return thread.openInGmailDisabled !== true && Boolean(thread.openInGmailUrl || onOpenInGmail)\n}\n\nfunction OpenInGmailButton({\n thread,\n onOpenInGmail,\n label = \"Open in Gmail\",\n}: {\n thread: ConversationThread\n onOpenInGmail?: (threadId: string) => void\n label?: string\n}) {\n const hasConfiguredAction = Boolean(thread.openInGmailUrl || onOpenInGmail || thread.openInGmailDisabled || thread.openInGmailDisabledReason)\n if (!hasConfiguredAction) return null\n\n const disabled = !canOpenInGmail(thread, onOpenInGmail)\n const disabledReason = disabled\n ? disabledOpenInGmailReason(thread)\n : undefined\n\n if (!disabled && thread.openInGmailUrl) {\n return (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" asChild>\n <a href={thread.openInGmailUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n <GmailMark size={14} /> {label}\n </a>\n </Button>\n )\n }\n\n return (\n <span className=\"inline-flex\" title={disabledReason}>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={disabled}\n aria-disabled={disabled || undefined}\n aria-label={disabledReason ? `${label}: ${disabledReason}` : label}\n onClick={disabled ? undefined : () => onOpenInGmail?.(thread.threadId)}\n >\n <GmailMark size={14} /> {label}\n </Button>\n </span>\n )\n}\n\n/* ── One message (collapsible) ──────────────────────────────────────────── */\n\nfunction MessageView({\n message,\n expanded,\n onToggle,\n me,\n}: {\n message: ConvMessage\n expanded: boolean\n onToggle: () => void\n me?: ConvParticipant\n}) {\n const [quoteOpen, setQuoteOpen] = React.useState(false)\n const [detailsOpen, setDetailsOpen] = React.useState(false)\n\n if (!expanded) {\n const snippet = messageBodySnippet(message, 140)\n\n return (\n <button\n type=\"button\"\n data-slot=\"conv-message-collapsed\"\n onClick={onToggle}\n className=\"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left hover:bg-muted/40\"\n >\n <PersonAvatar person={message.from} />\n <span className=\"text-muted-foreground min-w-0 flex-1 truncate text-[13px]\">\n <b className=\"text-foreground\">{firstName(message.from.name)}</b> · {snippet}\n </span>\n <span className=\"text-muted-foreground/60 shrink-0 text-xs\">{message.ago ?? message.date}</span>\n <ChevronDown size={13} className=\"text-muted-foreground shrink-0\" />\n </button>\n )\n }\n\n const toLabel = me && message.to.email === me.email ? \"me\" : firstName(message.to.name)\n const htmlParts = message.bodyHtml ? splitMessageHtml(message.bodyHtml) : null\n const textParts = message.bodyHtml ? null : splitMessageText(message.body ?? \"\")\n const hasDetails = Boolean(htmlParts?.detailsHtml || textParts?.detailsText)\n\n return (\n <div data-slot=\"conv-message\" className=\"rounded-md border border-border bg-background\">\n <button\n type=\"button\"\n onClick={onToggle}\n className=\"flex w-full items-start gap-2 px-3 py-2 text-left\"\n >\n <PersonAvatar person={message.from} size=\"default\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex flex-wrap items-baseline gap-x-1.5\">\n <span className=\"text-[13px] font-semibold\">{message.from.name}</span>\n <span className=\"text-muted-foreground/60 truncate text-xs\">&lt;{message.from.email}&gt;</span>\n </span>\n <span className=\"text-muted-foreground block text-xs\">\n to <b>{toLabel}</b>\n </span>\n </span>\n <span className=\"flex shrink-0 items-center gap-2\">\n {message.receipt ? (\n <span className=\"text-muted-foreground inline-flex items-center gap-1 text-[11px]\">\n {message.receipt.kind === \"new\" ? (\n <CornerUpLeft size={11} />\n ) : message.receipt.kind === \"read\" ? (\n <CheckCheck size={11} />\n ) : message.receipt.kind === \"draft\" ? (\n <FilePenLine size={11} />\n ) : (\n <MailOpen size={11} />\n )}\n {message.receipt.label}\n </span>\n ) : null}\n <span className=\"text-muted-foreground/60 text-xs\">{message.date}</span>\n <ChevronUp size={13} className=\"text-muted-foreground\" />\n </span>\n </button>\n\n <div className=\"px-3 pb-2.5\">\n {htmlParts ? (\n <div data-slot=\"conv-message-body\" className={PROSE} dangerouslySetInnerHTML={{ __html: htmlParts.bodyHtml }} />\n ) : (\n <div data-slot=\"conv-message-body\" className={cn(PROSE, \"whitespace-pre-line\")}>{textParts?.bodyText}</div>\n )}\n\n {hasDetails ? (\n <div className=\"mt-2\">\n <button\n type=\"button\"\n onClick={() => setDetailsOpen((v) => !v)}\n className=\"text-muted-foreground hover:text-foreground hover:bg-muted rounded px-1.5 text-xs leading-5\"\n aria-expanded={detailsOpen}\n >\n {detailsOpen ? \"Hide signature/details\" : \"Show signature/details\"}\n </button>\n {detailsOpen ? (\n <div className=\"border-border text-muted-foreground mt-1 border-l-2 pl-3 text-[13px]\">\n {htmlParts ? (\n <div data-slot=\"conv-message-details\" className={PROSE} dangerouslySetInnerHTML={{ __html: htmlParts.detailsHtml }} />\n ) : (\n <div data-slot=\"conv-message-details\" className={cn(PROSE, \"whitespace-pre-line\")}>{textParts?.detailsText}</div>\n )}\n </div>\n ) : null}\n </div>\n ) : null}\n\n {message.quoted ? (\n <div className=\"mt-2\">\n <button\n type=\"button\"\n onClick={() => setQuoteOpen((v) => !v)}\n className=\"text-muted-foreground hover:bg-muted rounded px-1.5 text-xs leading-5\"\n title={quoteOpen ? \"Hide quoted text\" : \"Show quoted text\"}\n >\n •••\n </button>\n {quoteOpen ? (\n <div className=\"border-border text-muted-foreground mt-1 border-l-2 pl-3 text-[13px]\">\n <p className=\"mb-1\" dangerouslySetInnerHTML={{ __html: sanitizeHtml(message.quoted.attr) }} />\n <div className={PROSE} dangerouslySetInnerHTML={{ __html: sanitizeHtml(message.quoted.html) }} />\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n\n/* ── Reply composer ─────────────────────────────────────────────────────── */\n\ntype PreviewState = {\n loading: boolean\n /** Sanitized HTML ready to render. */\n html: string | null\n /** Confirmation token returned by the server preview, retained for reuse on send. */\n confirmationToken: string | null\n error: string | null\n /** True when `html` came from the local fallback rather than the server. */\n local: boolean\n}\n\nconst IDLE_PREVIEW: PreviewState = { loading: false, html: null, confirmationToken: null, error: null, local: false }\n\nfunction ReplyComposer({\n thread,\n me,\n replyAll,\n tenantName,\n onClose,\n onSend,\n onDraft,\n onPreviewReply,\n draftDisabledReason,\n}: {\n thread: ConversationThread\n me?: ConvParticipant\n replyAll: boolean\n tenantName?: string\n onClose: () => void\n onSend: (body: string, includeSignature: boolean) => void | Promise<void>\n onDraft: (body: string, includeSignature: boolean) => void | Promise<void>\n onPreviewReply?: (payload: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n draftDisabledReason?: string | null\n}) {\n const [body, setBody] = React.useState(thread.draft ?? \"\")\n const [sig, setSig] = React.useState(true)\n const [preview, setPreview] = React.useState(false)\n const [previewState, setPreviewState] = React.useState<PreviewState>(IDLE_PREVIEW)\n const [sending, setSending] = React.useState(false)\n const [sendError, setSendError] = React.useState<string | null>(null)\n const ccList = replyAll ? thread.cc ?? [] : []\n const subject = /^re:/i.test(thread.subject) ? thread.subject : `Re: ${thread.subject}`\n const draftDisabled = Boolean(draftDisabledReason)\n\n const localPreviewHtml = textToHtml(body) + (sig && thread.signature ? textToHtml(thread.signature) : \"\")\n\n const openPreview = async () => {\n setPreview(true)\n setSendError(null)\n\n if (!onPreviewReply) {\n // No server preview contract: render a sanitized local draft preview only.\n setPreviewState({ loading: false, html: sanitizeHtml(localPreviewHtml), confirmationToken: null, error: null, local: true })\n return\n }\n\n setPreviewState({ loading: true, html: null, confirmationToken: null, error: null, local: false })\n try {\n const result = await onPreviewReply({ threadId: thread.threadId, body, includeSignature: sig, replyAll })\n setPreviewState({\n loading: false,\n html: sanitizeHtml(result.htmlBody ?? \"\"),\n confirmationToken: result.confirmationToken ?? null,\n error: null,\n local: false,\n })\n } catch (error) {\n setPreviewState({\n loading: false,\n html: null,\n confirmationToken: null,\n error: error instanceof Error ? error.message : \"Could not load the preview. Please try again.\",\n local: false,\n })\n }\n }\n\n const handleSend = async () => {\n setSending(true)\n setSendError(null)\n try {\n await onSend(body, sig)\n setPreview(false)\n setPreviewState(IDLE_PREVIEW)\n } catch (error) {\n setSendError(error instanceof Error ? error.message : \"Could not send this reply. Please try again.\")\n } finally {\n setSending(false)\n }\n }\n\n const handleDraft = async () => {\n if (draftDisabled) return\n setSending(true)\n setSendError(null)\n try {\n await onDraft(body, sig)\n setPreview(false)\n setPreviewState(IDLE_PREVIEW)\n } catch (error) {\n setSendError(error instanceof Error ? error.message : \"Could not create the Gmail draft. Please try again.\")\n } finally {\n setSending(false)\n }\n }\n\n return (\n <div data-slot=\"conv-reply\" className=\"border-border bg-muted/20 rounded-md border p-3\">\n <div className=\"mb-2 flex items-center gap-2\">\n {me ? <PersonAvatar person={me} /> : null}\n <span className=\"flex-1 text-[13px] font-medium\">\n {replyAll ? (\n <>\n Reply all{\" \"}\n <span className=\"text-muted-foreground font-normal\">· {1 + ccList.length} recipients</span>\n </>\n ) : (\n <>\n Reply to <b>{firstName(thread.contact.name)}</b>\n </>\n )}\n </span>\n <span className=\"text-muted-foreground inline-flex items-center gap-1 text-[11px]\">\n <GitMerge size={11} /> Same thread\n </span>\n <button type=\"button\" onClick={onClose} title=\"Discard reply\" className=\"text-muted-foreground hover:text-foreground\">\n <X size={15} />\n </button>\n </div>\n\n <div className=\"border-border mb-2 space-y-1 border-b pb-2 text-[13px]\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">To</span>\n <span className=\"font-medium\">{thread.contact.name}</span>\n <span className=\"text-muted-foreground/60 truncate text-xs\">{thread.contact.email}</span>\n </div>\n {replyAll && ccList.length ? (\n <div className=\"flex items-start gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">Cc</span>\n <span className=\"text-muted-foreground text-xs\">\n {ccList.map((c) => c.name).join(\", \")}\n </span>\n </div>\n ) : null}\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-muted-foreground w-12 shrink-0 text-[11px] font-medium\">Subject</span>\n <span className=\"truncate\">{subject}</span>\n <span className=\"text-muted-foreground/60 ml-auto inline-flex items-center gap-1 text-[11px]\">\n <Lock size={10} /> on thread\n </span>\n </div>\n </div>\n\n <Textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n placeholder=\"Write your reply…\"\n className=\"min-h-28 resize-y text-sm\"\n onKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === \"Enter\") {\n e.preventDefault()\n void openPreview()\n }\n }}\n />\n\n {sig && thread.signature ? (\n <div className=\"text-muted-foreground mt-2 whitespace-pre-line border-l-2 border-border pl-3 text-[13px]\">\n <span className=\"text-muted-foreground/60 mr-1\">--</span>\n {thread.signature}\n </div>\n ) : null}\n\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <RichTextToolbar />\n <label className=\"text-muted-foreground ml-auto inline-flex cursor-pointer items-center gap-1.5 text-[12px]\">\n <Switch checked={sig} onCheckedChange={setSig} aria-label=\"Toggle signature\" />\n Signature\n </label>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={sending} onClick={() => void openPreview()}>\n <Eye size={14} /> Preview\n </Button>\n <Button type=\"button\" size=\"sm\" disabled={sending} onClick={() => void openPreview()}>\n <Send size={14} /> Send\n </Button>\n </div>\n\n <Dialog open={preview} onOpenChange={(open) => { if (!sending) { setPreview(open); if (!open) setPreviewState(IDLE_PREVIEW) } }}>\n <DialogContent className=\"max-w-xl\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-1.5 text-[15px]\">\n <Eye size={15} /> {previewState.local ? \"Local draft preview\" : \"Reply preview\"}\n </DialogTitle>\n <DialogDescription>\n {previewState.local\n ? \"Local draft preview only — the server prepares the exact message on send.\"\n : <>Stays on the {subject.replace(/^Re:\\s*/i, \"\")} thread. Gmail keeps it threaded.</>}\n </DialogDescription>\n </DialogHeader>\n <div className=\"border-border space-y-1 rounded-md border p-3 text-[13px]\">\n <div>\n <span className=\"text-muted-foreground\">To </span>\n <b>{thread.contact.name}</b>{\" \"}\n <span className=\"text-muted-foreground/60\">&lt;{thread.contact.email}&gt;</span>\n </div>\n {replyAll && ccList.length ? (\n <div className=\"text-muted-foreground\">Cc {ccList.map((c) => c.name).join(\", \")}</div>\n ) : null}\n <div>\n <span className=\"text-muted-foreground\">Subject </span>\n {subject}\n </div>\n </div>\n {previewState.loading ? (\n <div data-slot=\"conv-preview-loading\" role=\"status\" className=\"text-muted-foreground flex items-center gap-2 px-1 py-6 text-[13px]\">\n <span className=\"border-muted-foreground/40 border-t-foreground size-4 animate-spin rounded-full border-2\" aria-hidden />\n Loading preview…\n </div>\n ) : previewState.error ? (\n <p role=\"alert\" className=\"text-destructive text-sm\">\n {previewState.error}\n </p>\n ) : (\n <div\n data-slot=\"conv-preview-body\"\n data-confirmation-token={previewState.confirmationToken ?? undefined}\n className={cn(PROSE, \"max-h-72 overflow-auto\")}\n dangerouslySetInnerHTML={{ __html: previewState.html ?? \"\" }}\n />\n )}\n {sendError ? (\n <p role=\"alert\" className=\"text-destructive text-sm\">\n {sendError}\n </p>\n ) : null}\n <DialogFooter className=\"sm:justify-between\">\n <span className=\"inline-flex\" title={draftDisabledReason ?? undefined}>\n <button\n type=\"button\"\n disabled={sending || previewState.loading || draftDisabled}\n onClick={handleDraft}\n aria-label={draftDisabledReason ? `Open draft in Gmail: ${draftDisabledReason}` : \"Open draft in Gmail\"}\n className=\"text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-[13px] disabled:pointer-events-none disabled:opacity-50\"\n >\n <GmailMark size={14} /> Open draft in Gmail\n </button>\n </span>\n <span className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={sending} onClick={() => { setPreview(false); setPreviewState(IDLE_PREVIEW) }}>\n Keep editing\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={sending || previewState.loading}\n onClick={handleSend}\n >\n <Send size={14} /> {sending ? \"Sending...\" : \"Send now\"}\n </Button>\n </span>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n {tenantName ? (\n <p className=\"text-muted-foreground/70 mt-2 text-[11px]\">Sends via Gmail · playbooks stay stopped.</p>\n ) : null}\n </div>\n )\n}\n\n/* ── Thread body (messages + footer/composer/done states) ───────────────── */\n\ntype ThreadMode = \"idle\" | \"replying\" | \"sent\" | \"draft\"\n\nfunction ThreadBody({\n thread,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n}: {\n thread: ConversationThread\n me?: ConvParticipant\n tenantName?: string\n onSendReply?: (p: ConversationReplyPayload) => void | Promise<void>\n onCreateGmailDraft?: (p: ConversationReplyPayload) => void | Promise<void>\n onPreviewReply?: (p: ConversationReplyPayload) => Promise<ConversationReplyPreview>\n onOpenInGmail?: (threadId: string) => void\n}) {\n const canReply = thread.canReply !== false\n const replyDisabledReason = thread.replyDisabledReason?.trim() || \"You are not a participant on this thread, so replying is disabled here.\"\n const draftDisabledReason = onCreateGmailDraft ? null : \"Gmail draft creation is not available for this thread.\"\n const hasCc = !!(thread.cc && thread.cc.length)\n const [mode, setMode] = React.useState<ThreadMode>(\"idle\")\n const [replyAll, setReplyAll] = React.useState(false)\n const [expanded, setExpanded] = React.useState<Record<string, boolean>>(() => {\n const o: Record<string, boolean> = {}\n thread.messages.forEach((m, i) => {\n o[m.id] = i === thread.messages.length - 1\n })\n return o\n })\n\n const toggle = (id: string) => setExpanded((e) => ({ ...e, [id]: !e[id] }))\n\n return (\n <div data-slot=\"conv-thread-body\" className=\"space-y-2\">\n {canReply && thread.paused ? (\n <div className=\"border-status-pending-border bg-status-pending-bg text-status-pending-fg flex items-start gap-2 rounded-md border p-2.5 text-[12px]\">\n <Pause size={13} className=\"mt-0.5 shrink-0\" />\n <span>\n <b>Follow-up actions stopped.</b> Your {thread.paused.playbook} next steps won’t send\n automatically while this conversation is live. Continue it in {tenantName ?? \"the app\"} or Gmail.\n </span>\n </div>\n ) : null}\n\n <div className=\"space-y-1\">\n {thread.messages.map((m) => (\n <MessageView key={m.id} message={m} expanded={!!expanded[m.id]} onToggle={() => toggle(m.id)} me={me} />\n ))}\n </div>\n\n {!canReply ? (\n <div className=\"border-border bg-muted/30 text-muted-foreground flex flex-wrap items-start gap-2 rounded-md border p-2.5 text-[12px]\">\n <Eye size={14} className=\"mt-0.5 shrink-0\" />\n <span className=\"min-w-0 flex-1\">\n <b>Viewing only.</b> {replyDisabledReason}\n </span>\n <OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />\n </div>\n ) : null}\n\n {canReply && mode === \"idle\" ? (\n <div data-slot=\"conv-action-row\" className=\"border-border/70 mt-1 flex flex-wrap items-center gap-x-3 gap-y-2 border-t pt-3\">\n <Button type=\"button\" size=\"sm\" onClick={() => { setReplyAll(false); setMode(\"replying\") }}>\n <Reply size={15} /> Reply\n </Button>\n {hasCc ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => { setReplyAll(true); setMode(\"replying\") }}>\n <ReplyAll size={14} /> Reply all\n </Button>\n ) : null}\n <OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />\n <span className=\"text-muted-foreground/70 ml-auto inline-flex items-center gap-1.5 text-[12px]\">\n <GitMerge size={13} /> Stays on this thread\n </span>\n </div>\n ) : null}\n\n {canReply && mode === \"replying\" ? (\n <ReplyComposer\n thread={thread}\n me={me}\n replyAll={replyAll}\n tenantName={tenantName}\n onPreviewReply={onPreviewReply}\n onClose={() => setMode(\"idle\")}\n onSend={async (body, includeSignature) => {\n await onSendReply?.({ threadId: thread.threadId, body, includeSignature, replyAll })\n setMode(\"sent\")\n }}\n onDraft={async (body, includeSignature) => {\n if (!onCreateGmailDraft) return\n await onCreateGmailDraft({ threadId: thread.threadId, body, includeSignature, replyAll })\n setMode(\"draft\")\n }}\n draftDisabledReason={draftDisabledReason}\n />\n ) : null}\n\n {canReply && mode === \"sent\" ? (\n <div className=\"border-status-active-border bg-status-active-bg flex items-center gap-2 rounded-md border p-3 text-[13px]\">\n <Check size={16} className=\"text-status-active-fg shrink-0\" />\n <span className=\"flex-1\">\n <b>{replyAll ? \"Reply all sent\" : \"Reply sent\"}</b> · added to the thread. Delivered to{\" \"}\n <b>{thread.contact.name}</b>. This action stays <b>Pending</b>; playbooks remain stopped.\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setMode(\"idle\")}>\n Done\n </Button>\n </div>\n ) : null}\n\n {canReply && mode === \"draft\" ? (\n <div className=\"border-border bg-muted/30 flex items-center gap-2 rounded-md border p-3 text-[13px]\">\n <GmailMark size={16} />\n <span className=\"flex-1\">\n <b>Draft saved to Gmail.</b> Waiting on the <b>Re: {thread.subject}</b> thread; open it there to finish. Nothing was sent.\n </span>\n <OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setMode(\"idle\")}>\n Done\n </Button>\n </div>\n ) : null}\n </div>\n )\n}\n\n/* ── A thread row + its inline reader ───────────────────────────────────── */\n\nfunction ThreadRow({\n thread,\n open,\n onToggleOpen,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n}: {\n thread: ConversationThread\n open: boolean\n onToggleOpen: () => void\n} & Pick<ConversationPanelProps, \"me\" | \"tenantName\" | \"onSendReply\" | \"onCreateGmailDraft\" | \"onPreviewReply\" | \"onOpenInGmail\">) {\n const status = effectiveStatus(thread)\n const last = thread.messages[thread.messages.length - 1]\n const who = last?.direction === \"inbound\" ? firstName(last.from.name) : \"You\"\n const lastSnippet = last ? messageBodySnippet(last, 120) : \"\"\n const pill = STATUS_PILL[status]\n\n return (\n <div data-slot=\"conv-thread\" data-open={open ? \"true\" : undefined} className=\"border-border border-b last:border-b-0\">\n <button\n type=\"button\"\n onClick={onToggleOpen}\n aria-expanded={open}\n className=\"flex w-full items-center gap-2.5 px-3 py-2.5 text-left hover:bg-muted/30\"\n >\n <span className={cn(\"size-2 shrink-0 rounded-full\", STATUS_DOT[status])} aria-hidden />\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex items-center gap-2\">\n <span className=\"truncate text-sm font-semibold\">{thread.subject}</span>\n <span className={cn(\"shrink-0 rounded-md border px-1.5 py-px text-[10px] font-medium leading-4\", pill.cls)}>\n {pill.label}\n </span>\n </span>\n <span className=\"text-muted-foreground block truncate text-xs\">\n <b className=\"text-foreground/80\">{thread.contact.name}</b> · {who}: {lastSnippet}\n </span>\n </span>\n <span className=\"text-muted-foreground/60 shrink-0 text-xs\">{thread.lastWhen}</span>\n {open ? (\n <ChevronUp size={15} className=\"text-muted-foreground shrink-0\" />\n ) : (\n <ChevronDown size={15} className=\"text-muted-foreground shrink-0\" />\n )}\n </button>\n\n {open ? (\n <div className=\"px-3 pb-3\">\n <ThreadBody\n thread={thread}\n me={me}\n tenantName={tenantName}\n onSendReply={onSendReply}\n onCreateGmailDraft={onCreateGmailDraft}\n onPreviewReply={onPreviewReply}\n onOpenInGmail={onOpenInGmail}\n />\n </div>\n ) : null}\n </div>\n )\n}\n\n/* ── The hub ─────────────────────────────────────────────────────────────── */\n\nfunction ConversationPanel({\n threads,\n me,\n tenantName,\n onSendReply,\n onCreateGmailDraft,\n onPreviewReply,\n onOpenInGmail,\n defaultOpenThreadId,\n className,\n}: ConversationPanelProps) {\n const responded = threads.filter((t) => t.status === \"responded\" && t.canReply !== false).length\n const draft = threads.filter((t) => effectiveStatus(t) === \"draft\").length\n const awaiting = threads.filter((t) => effectiveStatus(t) === \"awaiting\").length\n const anyPaused = threads.some((t) => t.paused)\n\n const [hubOpen, setHubOpen] = React.useState(true)\n const [openId, setOpenId] = React.useState<string | null>(() => {\n if (defaultOpenThreadId) return defaultOpenThreadId\n const firstActionable = threads.find((t) => [\"responded\", \"draft\"].includes(t.status) && t.canReply !== false)\n return firstActionable ? firstActionable.threadId : null\n })\n\n if (!threads.length) return null\n\n // Header badge state: a responded reply leads, then drafts to finish, then sent mail awaiting a reply.\n const badge =\n responded > 0\n ? { label: \"Email response detected\", dot: \"bg-status-active-fg\", ring: \"bg-status-active-fg/30\" }\n : draft > 0\n ? { label: \"Draft ready\", dot: \"bg-status-pending-fg\", ring: \"bg-status-pending-fg/30\" }\n : awaiting > 0\n ? { label: \"Awaiting response\", dot: \"bg-status-pending-fg\", ring: \"bg-status-pending-fg/30\" }\n : { label: \"Conversations\", dot: \"bg-muted-foreground/50\", ring: \"bg-muted-foreground/20\" }\n\n const headTitle =\n responded > 0\n ? `${responded} ${responded === 1 ? \"reply needs\" : \"replies need\"} your response`\n : draft > 0\n ? `Draft ready on ${draft} ${draft === 1 ? \"thread\" : \"threads\"}`\n : awaiting > 0\n ? `Awaiting response on ${awaiting} ${awaiting === 1 ? \"thread\" : \"threads\"}`\n : `${threads.length} email ${threads.length === 1 ? \"thread\" : \"threads\"}`\n\n return (\n <section\n data-slot=\"conversation-panel\"\n data-responded={responded > 0 ? \"true\" : undefined}\n className={cn(\"border-border bg-background overflow-hidden rounded-xl border\", className)}\n >\n <button\n type=\"button\"\n onClick={() => setHubOpen((v) => !v)}\n aria-expanded={hubOpen}\n className=\"flex w-full items-center gap-3 px-3 py-2.5 text-left\"\n >\n <span\n data-slot=\"conversation-badge\"\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold\",\n responded > 0\n ? \"bg-status-active-bg text-status-active-fg\"\n : draft > 0 || awaiting > 0\n ? \"bg-status-pending-bg text-status-pending-fg\"\n : \"bg-muted text-muted-foreground\"\n )}\n >\n <span className=\"relative inline-flex size-2\">\n <span className={cn(\"absolute inline-flex h-full w-full animate-ping rounded-full opacity-75\", badge.ring)} />\n <span className={cn(\"relative inline-flex size-2 rounded-full\", badge.dot)} />\n </span>\n {badge.label}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-semibold leading-tight\">{headTitle}</span>\n <span className=\"text-muted-foreground mt-0.5 block truncate text-xs\">\n {threads.length} {threads.length === 1 ? \"thread\" : \"threads\"} on this action\n {anyPaused ? <> · <b>playbook stopped</b></> : null}\n </span>\n </span>\n {hubOpen ? (\n <ChevronUp size={16} className=\"text-muted-foreground shrink-0\" />\n ) : (\n <ChevronDown size={16} className=\"text-muted-foreground shrink-0\" />\n )}\n </button>\n\n {hubOpen ? (\n <div className=\"border-border border-t\">\n {threads.map((t) => (\n <ThreadRow\n key={t.threadId}\n thread={t}\n open={openId === t.threadId}\n onToggleOpen={() => setOpenId((cur) => (cur === t.threadId ? null : t.threadId))}\n me={me}\n tenantName={tenantName}\n onSendReply={onSendReply}\n onCreateGmailDraft={onCreateGmailDraft}\n onPreviewReply={onPreviewReply}\n onOpenInGmail={onOpenInGmail}\n />\n ))}\n </div>\n ) : null}\n </section>\n )\n}\n\nexport { ConversationPanel }\n"],"mappings":"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","names":["_a","_b"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handled-ai/design-system",
3
- "version": "0.20.4",
3
+ "version": "0.20.5",
4
4
  "description": "Handled UI component library (shadcn-style, New York)",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@9.12.0",
@@ -86,6 +86,54 @@ describe("ConversationPanel", () => {
86
86
  expect(screen.getByText("Draft copy")).toBeDefined();
87
87
  });
88
88
 
89
+
90
+ it("uses the custom read-only reason and disables Open in Gmail when access is unavailable", () => {
91
+ const onOpenInGmail = vi.fn();
92
+
93
+ render(
94
+ <ConversationPanel
95
+ threads={[
96
+ thread({
97
+ canReply: false,
98
+ replyDisabledReason: "Only the case owner can open Gmail drafts for this case.",
99
+ openInGmailDisabled: true,
100
+ openInGmailDisabledReason: "Only the case owner can open this draft in Gmail.",
101
+ }),
102
+ ]}
103
+ me={me}
104
+ onOpenInGmail={onOpenInGmail}
105
+ defaultOpenThreadId="t1"
106
+ />,
107
+ );
108
+
109
+ expect(screen.getByText(/Only the case owner can open Gmail drafts for this case\./)).toBeDefined();
110
+ const button = screen.getByRole("button", {
111
+ name: "Open in Gmail: Only the case owner can open this draft in Gmail.",
112
+ });
113
+ expect(button).toHaveProperty("disabled", true);
114
+
115
+ fireEvent.click(button);
116
+ expect(onOpenInGmail).not.toHaveBeenCalled();
117
+ });
118
+
119
+ it("renders Open in Gmail as a new-tab link when a URL is provided", () => {
120
+ render(
121
+ <ConversationPanel
122
+ threads={[
123
+ thread({
124
+ openInGmailUrl: "https://mail.google.com/mail/?authuser=dana%40handled.ai#drafts/msg-1",
125
+ }),
126
+ ]}
127
+ me={me}
128
+ />,
129
+ );
130
+
131
+ const link = screen.getByRole("link", { name: /Open in Gmail/ });
132
+ expect(link.getAttribute("href")).toBe("https://mail.google.com/mail/?authuser=dana%40handled.ai#drafts/msg-1");
133
+ expect(link.getAttribute("target")).toBe("_blank");
134
+ expect(link.getAttribute("rel")).toBe("noopener noreferrer");
135
+ });
136
+
89
137
  it("auto-opens the first responded thread and renders its newest message as HTML", () => {
90
138
  const { container } = render(<ConversationPanel threads={[thread()]} me={me} />);
91
139
  // newest (inbound) message expanded -> its anchor is in the DOM
@@ -97,8 +97,16 @@ export interface ConversationThread {
97
97
  cc?: ConvParticipant[]
98
98
  /** Set when this thread's reply halted a playbook (terminal). */
99
99
  paused?: { playbook: string } | null
100
- /** false => operator is not a participant; reply disabled (read-only). */
100
+ /** false => operator cannot reply or create drafts from this thread. */
101
101
  canReply?: boolean
102
+ /** Explains why reply and draft creation are disabled. */
103
+ replyDisabledReason?: string
104
+ /** Existing Gmail draft or thread URL. Rendered as a new-tab link when present. */
105
+ openInGmailUrl?: string | null
106
+ /** Forces the Open in Gmail action into a disabled state. */
107
+ openInGmailDisabled?: boolean
108
+ /** Tooltip/read-only copy for a disabled Open in Gmail action. */
109
+ openInGmailDisabledReason?: string | null
102
110
  messages: ConvMessage[]
103
111
  /** Prefilled reply draft body. */
104
112
  draft?: string
@@ -521,6 +529,62 @@ function effectiveStatus(t: ConversationThread): ConvStatus {
521
529
  return t.canReply === false ? "viewing" : t.status
522
530
  }
523
531
 
532
+ function disabledOpenInGmailReason(thread: ConversationThread): string {
533
+ return (
534
+ thread.openInGmailDisabledReason?.trim() ||
535
+ thread.replyDisabledReason?.trim() ||
536
+ "Gmail access is not available for this thread."
537
+ )
538
+ }
539
+
540
+ function canOpenInGmail(thread: ConversationThread, onOpenInGmail?: (threadId: string) => void): boolean {
541
+ return thread.openInGmailDisabled !== true && Boolean(thread.openInGmailUrl || onOpenInGmail)
542
+ }
543
+
544
+ function OpenInGmailButton({
545
+ thread,
546
+ onOpenInGmail,
547
+ label = "Open in Gmail",
548
+ }: {
549
+ thread: ConversationThread
550
+ onOpenInGmail?: (threadId: string) => void
551
+ label?: string
552
+ }) {
553
+ const hasConfiguredAction = Boolean(thread.openInGmailUrl || onOpenInGmail || thread.openInGmailDisabled || thread.openInGmailDisabledReason)
554
+ if (!hasConfiguredAction) return null
555
+
556
+ const disabled = !canOpenInGmail(thread, onOpenInGmail)
557
+ const disabledReason = disabled
558
+ ? disabledOpenInGmailReason(thread)
559
+ : undefined
560
+
561
+ if (!disabled && thread.openInGmailUrl) {
562
+ return (
563
+ <Button type="button" variant="ghost" size="sm" asChild>
564
+ <a href={thread.openInGmailUrl} target="_blank" rel="noopener noreferrer">
565
+ <GmailMark size={14} /> {label}
566
+ </a>
567
+ </Button>
568
+ )
569
+ }
570
+
571
+ return (
572
+ <span className="inline-flex" title={disabledReason}>
573
+ <Button
574
+ type="button"
575
+ variant="ghost"
576
+ size="sm"
577
+ disabled={disabled}
578
+ aria-disabled={disabled || undefined}
579
+ aria-label={disabledReason ? `${label}: ${disabledReason}` : label}
580
+ onClick={disabled ? undefined : () => onOpenInGmail?.(thread.threadId)}
581
+ >
582
+ <GmailMark size={14} /> {label}
583
+ </Button>
584
+ </span>
585
+ )
586
+ }
587
+
524
588
  /* ── One message (collapsible) ──────────────────────────────────────────── */
525
589
 
526
590
  function MessageView({
@@ -675,6 +739,7 @@ function ReplyComposer({
675
739
  onSend,
676
740
  onDraft,
677
741
  onPreviewReply,
742
+ draftDisabledReason,
678
743
  }: {
679
744
  thread: ConversationThread
680
745
  me?: ConvParticipant
@@ -684,6 +749,7 @@ function ReplyComposer({
684
749
  onSend: (body: string, includeSignature: boolean) => void | Promise<void>
685
750
  onDraft: (body: string, includeSignature: boolean) => void | Promise<void>
686
751
  onPreviewReply?: (payload: ConversationReplyPayload) => Promise<ConversationReplyPreview>
752
+ draftDisabledReason?: string | null
687
753
  }) {
688
754
  const [body, setBody] = React.useState(thread.draft ?? "")
689
755
  const [sig, setSig] = React.useState(true)
@@ -693,6 +759,7 @@ function ReplyComposer({
693
759
  const [sendError, setSendError] = React.useState<string | null>(null)
694
760
  const ccList = replyAll ? thread.cc ?? [] : []
695
761
  const subject = /^re:/i.test(thread.subject) ? thread.subject : `Re: ${thread.subject}`
762
+ const draftDisabled = Boolean(draftDisabledReason)
696
763
 
697
764
  const localPreviewHtml = textToHtml(body) + (sig && thread.signature ? textToHtml(thread.signature) : "")
698
765
 
@@ -742,6 +809,7 @@ function ReplyComposer({
742
809
  }
743
810
 
744
811
  const handleDraft = async () => {
812
+ if (draftDisabled) return
745
813
  setSending(true)
746
814
  setSendError(null)
747
815
  try {
@@ -885,14 +953,17 @@ function ReplyComposer({
885
953
  </p>
886
954
  ) : null}
887
955
  <DialogFooter className="sm:justify-between">
888
- <button
889
- type="button"
890
- disabled={sending || previewState.loading}
891
- onClick={handleDraft}
892
- className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-[13px] disabled:pointer-events-none disabled:opacity-50"
893
- >
894
- <GmailMark size={14} /> Open draft in Gmail
895
- </button>
956
+ <span className="inline-flex" title={draftDisabledReason ?? undefined}>
957
+ <button
958
+ type="button"
959
+ disabled={sending || previewState.loading || draftDisabled}
960
+ onClick={handleDraft}
961
+ aria-label={draftDisabledReason ? `Open draft in Gmail: ${draftDisabledReason}` : "Open draft in Gmail"}
962
+ className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1.5 text-[13px] disabled:pointer-events-none disabled:opacity-50"
963
+ >
964
+ <GmailMark size={14} /> Open draft in Gmail
965
+ </button>
966
+ </span>
896
967
  <span className="flex items-center gap-2">
897
968
  <Button type="button" variant="outline" size="sm" disabled={sending} onClick={() => { setPreview(false); setPreviewState(IDLE_PREVIEW) }}>
898
969
  Keep editing
@@ -939,6 +1010,8 @@ function ThreadBody({
939
1010
  onOpenInGmail?: (threadId: string) => void
940
1011
  }) {
941
1012
  const canReply = thread.canReply !== false
1013
+ const replyDisabledReason = thread.replyDisabledReason?.trim() || "You are not a participant on this thread, so replying is disabled here."
1014
+ const draftDisabledReason = onCreateGmailDraft ? null : "Gmail draft creation is not available for this thread."
942
1015
  const hasCc = !!(thread.cc && thread.cc.length)
943
1016
  const [mode, setMode] = React.useState<ThreadMode>("idle")
944
1017
  const [replyAll, setReplyAll] = React.useState(false)
@@ -971,11 +1044,12 @@ function ThreadBody({
971
1044
  </div>
972
1045
 
973
1046
  {!canReply ? (
974
- <div className="border-border bg-muted/30 text-muted-foreground flex items-start gap-2 rounded-md border p-2.5 text-[12px]">
1047
+ <div className="border-border bg-muted/30 text-muted-foreground flex flex-wrap items-start gap-2 rounded-md border p-2.5 text-[12px]">
975
1048
  <Eye size={14} className="mt-0.5 shrink-0" />
976
- <span>
977
- <b>Viewing only.</b> You’re not a participant on this thread, so replying is disabled here.
1049
+ <span className="min-w-0 flex-1">
1050
+ <b>Viewing only.</b> {replyDisabledReason}
978
1051
  </span>
1052
+ <OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />
979
1053
  </div>
980
1054
  ) : null}
981
1055
 
@@ -989,9 +1063,7 @@ function ThreadBody({
989
1063
  <ReplyAll size={14} /> Reply all
990
1064
  </Button>
991
1065
  ) : null}
992
- <Button type="button" variant="ghost" size="sm" onClick={() => onOpenInGmail?.(thread.threadId)}>
993
- <GmailMark size={14} /> Open in Gmail
994
- </Button>
1066
+ <OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />
995
1067
  <span className="text-muted-foreground/70 ml-auto inline-flex items-center gap-1.5 text-[12px]">
996
1068
  <GitMerge size={13} /> Stays on this thread
997
1069
  </span>
@@ -1011,9 +1083,11 @@ function ThreadBody({
1011
1083
  setMode("sent")
1012
1084
  }}
1013
1085
  onDraft={async (body, includeSignature) => {
1014
- await onCreateGmailDraft?.({ threadId: thread.threadId, body, includeSignature, replyAll })
1086
+ if (!onCreateGmailDraft) return
1087
+ await onCreateGmailDraft({ threadId: thread.threadId, body, includeSignature, replyAll })
1015
1088
  setMode("draft")
1016
1089
  }}
1090
+ draftDisabledReason={draftDisabledReason}
1017
1091
  />
1018
1092
  ) : null}
1019
1093
 
@@ -1036,9 +1110,7 @@ function ThreadBody({
1036
1110
  <span className="flex-1">
1037
1111
  <b>Draft saved to Gmail.</b> Waiting on the <b>Re: {thread.subject}</b> thread; open it there to finish. Nothing was sent.
1038
1112
  </span>
1039
- <Button type="button" variant="ghost" size="sm" onClick={() => onOpenInGmail?.(thread.threadId)}>
1040
- <GmailMark size={14} /> Open in Gmail
1041
- </Button>
1113
+ <OpenInGmailButton thread={thread} onOpenInGmail={onOpenInGmail} />
1042
1114
  <Button type="button" variant="ghost" size="sm" onClick={() => setMode("idle")}>
1043
1115
  Done
1044
1116
  </Button>