@handled-ai/design-system 0.20.28 → 0.20.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -140,47 +140,57 @@ function TimelineItem({
140
140
  const toneStyle = event.tone ? TONE_CLASSES[event.tone] : null;
141
141
  const dotClasses = toneStyle ? toneStyle.dot : NEUTRAL_DOT_CLASSES;
142
142
  const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES;
143
- return /* @__PURE__ */ jsxs("div", { className: classes.outerRowGap, children: [
144
- !isLast && /* @__PURE__ */ jsx("div", { className: classes.connector }),
145
- /* @__PURE__ */ jsx("div", { className: classes.dotWrapperSize, children: /* @__PURE__ */ jsx("div", { className: cn(classes.dot, dotClasses, iconClasses), "data-testid": "timeline-dot", children: event.icon }) }),
146
- /* @__PURE__ */ jsxs("div", { className: classes.contentPadding, children: [
147
- /* @__PURE__ */ jsxs("div", { className: classes.titleRowSpacing, children: [
148
- /* @__PURE__ */ jsx("div", { className: classes.title, children: event.title }),
149
- /* @__PURE__ */ jsx("span", { className: classes.time, children: event.time })
150
- ] }),
151
- event.actor && /* @__PURE__ */ jsx(ActorByline, { actor: event.actor, time: event.time }),
152
- (hasContent || hasEmail || hasGongCall) && /* @__PURE__ */ jsx("div", { className: "mt-2", children: event.isInteractive ? hasGongCall ? /* @__PURE__ */ jsx(
153
- GongCallCard,
154
- {
155
- event,
156
- expanded,
157
- setExpanded,
158
- variant,
159
- classes
160
- }
161
- ) : hasEmail ? /* @__PURE__ */ jsx(
162
- EmailCard,
163
- {
164
- event,
165
- expanded,
166
- setExpanded,
167
- showAllRecipients,
168
- setShowAllRecipients,
169
- variant,
170
- classes
171
- }
172
- ) : /* @__PURE__ */ jsx(
173
- ContentCard,
174
- {
175
- event,
176
- expanded,
177
- setExpanded,
178
- variant,
179
- classes
180
- }
181
- ) : /* @__PURE__ */ jsx("div", { className: classes.nonInteractiveContent, children: event.content }) })
182
- ] })
183
- ] });
143
+ return /* @__PURE__ */ jsxs(
144
+ "div",
145
+ {
146
+ id: `timeline-event-${event.id}`,
147
+ "data-testid": `timeline-event-${event.id}`,
148
+ "data-activity-id": event.id,
149
+ tabIndex: -1,
150
+ className: classes.outerRowGap,
151
+ children: [
152
+ !isLast && /* @__PURE__ */ jsx("div", { className: classes.connector }),
153
+ /* @__PURE__ */ jsx("div", { className: classes.dotWrapperSize, children: /* @__PURE__ */ jsx("div", { className: cn(classes.dot, dotClasses, iconClasses), "data-testid": "timeline-dot", children: event.icon }) }),
154
+ /* @__PURE__ */ jsxs("div", { className: classes.contentPadding, children: [
155
+ /* @__PURE__ */ jsxs("div", { className: classes.titleRowSpacing, children: [
156
+ /* @__PURE__ */ jsx("div", { className: classes.title, children: event.title }),
157
+ /* @__PURE__ */ jsx("span", { className: classes.time, children: event.time })
158
+ ] }),
159
+ event.actor && /* @__PURE__ */ jsx(ActorByline, { actor: event.actor, time: event.time }),
160
+ (hasContent || hasEmail || hasGongCall) && /* @__PURE__ */ jsx("div", { className: "mt-2", children: event.isInteractive ? hasGongCall ? /* @__PURE__ */ jsx(
161
+ GongCallCard,
162
+ {
163
+ event,
164
+ expanded,
165
+ setExpanded,
166
+ variant,
167
+ classes
168
+ }
169
+ ) : hasEmail ? /* @__PURE__ */ jsx(
170
+ EmailCard,
171
+ {
172
+ event,
173
+ expanded,
174
+ setExpanded,
175
+ showAllRecipients,
176
+ setShowAllRecipients,
177
+ variant,
178
+ classes
179
+ }
180
+ ) : /* @__PURE__ */ jsx(
181
+ ContentCard,
182
+ {
183
+ event,
184
+ expanded,
185
+ setExpanded,
186
+ variant,
187
+ classes
188
+ }
189
+ ) : /* @__PURE__ */ jsx("div", { className: classes.nonInteractiveContent, children: event.content }) })
190
+ ] })
191
+ ]
192
+ }
193
+ );
184
194
  }
185
195
  function reactNodeToDisplayText(value) {
186
196
  if (typeof value === "string" || typeof value === "number") return decodeEmailDisplayText(String(value));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/timeline-activity.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"../lib/utils\"\nimport { ChevronDown, ChevronUp, ExternalLink, Phone } from \"lucide-react\"\nimport { EmailBody as SharedEmailBody } from \"./email-body\"\nimport {\n decodeEmailDisplayText,\n emailBodySnippet,\n formatAddressList,\n formatEmailTimestamp,\n normalizeEmailSender,\n} from \"./email-display-helpers\"\n\nexport type TimelineActivityVariant = \"default\" | \"case-panel\"\n\nexport type TimelineEventTone =\n | \"red\"\n | \"amber\"\n | \"emerald\"\n | \"violet\"\n | \"blue\"\n | \"slate\"\n | \"salesforce\"\n | \"gmail\"\n\nexport interface TimelineEventActor {\n kind: \"user\" | \"integration\" | \"system\"\n name?: string\n initials?: string\n avatarUrl?: string\n verb?: string\n}\n\nexport interface TimelineEvent {\n id: string\n icon: React.ReactNode\n title: React.ReactNode\n time: string\n preview?: React.ReactNode\n email?: {\n from: string\n fromEmail?: string\n to: string\n cc?: string\n bcc?: string\n date?: string\n subject?: string\n body: React.ReactNode\n /**\n * HTML body. When provided, the card renders formatted, Gmail-like HTML\n * instead of the plain-text `body`. The component sanitizes it before\n * rendering. Opt-in: when absent, the plain-text `body` path is used and\n * existing consumers are unaffected.\n */\n bodyHtml?: string\n /**\n * Sender signature HTML, split out of the main body by the consuming app's\n * email pipeline. When present, it is hidden behind a subtle \"Show\n * signature\" toggle (collapsed by default) so signatures don't add noise to\n * the timeline. Sanitized before rendering. Optional — when absent the body\n * renders exactly as before. Mirrors `signatureHtml` on EmailPreviewCard.\n */\n signatureHtml?: string | null\n /**\n * Quoted / trailing thread content (the \"On <date> X wrote:\" history) split\n * out of the main body. When present, it is hidden behind a subtle \"Show\n * quoted text\" toggle (collapsed by default). Sanitized before rendering.\n * Optional — when absent the body renders exactly as before.\n */\n quotedHtml?: string | null\n }\n /**\n * Gong call payload. When present, the card renders a dedicated, first-class\n * Gong call card (mirroring the `email` treatment) instead of generic\n * content: a collapsed duration + brief-snippet preview, and an expanded\n * card with the call title, start time, direction/outcome chips, the AI\n * brief, key points, next steps, and a \"View in Gong\" link. Opt-in: when\n * absent, existing consumers are unaffected. Every field except `title` is\n * optional/nullable and renders nothing when absent (no empty sections,\n * no \"null\"/\"undefined\", no \"Invalid Date\").\n */\n gongCall?: {\n /** Gong call title. */\n title: string\n /** ISO datetime of the call start. */\n startTime?: string | null\n /** Call length in seconds. Formatted to a human duration when present. */\n durationSeconds?: number | null\n /** Call direction, e.g. \"Outbound\" / \"Inbound\". */\n direction?: string | null\n /** Call outcome, e.g. \"Connected\". */\n outcome?: string | null\n /** Gong AI call brief (plain text, may be multi-paragraph). */\n brief?: string | null\n /** Gong AI key points (plain text, often newline-delimited bullets). */\n keyPoints?: string | null\n /** Gong AI highlights / next steps (plain text). */\n nextSteps?: string | null\n /** Deep link to the call in Gong. */\n url?: string | null\n }\n content?: React.ReactNode\n source?: {\n label: string\n actionLabel?: string\n url: string\n }\n defaultExpanded?: boolean\n isInteractive?: boolean\n onSourceClick?: () => void\n tone?: TimelineEventTone\n actor?: TimelineEventActor\n isSystemNoise?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Tone class map — every class is a complete static string literal so\n// Tailwind's JIT scanner can detect them. NO interpolation.\n// ---------------------------------------------------------------------------\n\nexport const TONE_CLASSES: Record<\n TimelineEventTone,\n { dot: string; icon: string }\n> = {\n red: {\n dot: \"bg-red-50 border-red-200 dark:bg-red-950/30 dark:border-red-900/40\",\n icon: \"text-red-600 dark:text-red-300\",\n },\n amber: {\n dot: \"bg-amber-50 border-amber-200 dark:bg-amber-950/30 dark:border-amber-900/40\",\n icon: \"text-amber-600 dark:text-amber-300\",\n },\n emerald: {\n dot: \"bg-emerald-50 border-emerald-200 dark:bg-emerald-950/30 dark:border-emerald-900/40\",\n icon: \"text-emerald-600 dark:text-emerald-300\",\n },\n violet: {\n dot: \"bg-violet-50 border-violet-200 dark:bg-violet-950/30 dark:border-violet-900/40\",\n icon: \"text-violet-600 dark:text-violet-300\",\n },\n blue: {\n dot: \"bg-blue-50 border-blue-200 dark:bg-blue-950/30 dark:border-blue-900/40\",\n icon: \"text-blue-600 dark:text-blue-300\",\n },\n slate: {\n dot: \"bg-slate-100 border-slate-200 dark:bg-slate-800/50 dark:border-slate-700\",\n icon: \"text-slate-500 dark:text-slate-300\",\n },\n salesforce: {\n dot: \"bg-white border-[#00A1E0]/25 dark:bg-background dark:border-[#00A1E0]/25\",\n icon: \"text-[#00A1E0]\",\n },\n gmail: {\n dot: \"bg-white border-red-200 dark:bg-background dark:border-red-900/40\",\n icon: \"text-red-500 dark:text-red-300\",\n },\n}\n\nconst NEUTRAL_DOT_CLASSES = \"border-border/60 bg-background\"\nconst NEUTRAL_ICON_CLASSES = \"text-muted-foreground\"\n\ntype TimelineVariantClasses = {\n outerRowGap: string\n connector: string\n dotWrapperSize: string\n dot: string\n contentPadding: string\n titleRowSpacing: string\n title: string\n time: string\n cardContainer: string\n cardHeader: string\n cardBody: string\n cardFooter: string\n collapsedPreview: string\n actionLinkRow: string\n actionLink: string\n nonInteractiveContent: string\n}\n\nconst TIMELINE_VARIANT_CLASSES: Record<TimelineActivityVariant, TimelineVariantClasses> = {\n default: {\n outerRowGap: \"group relative flex gap-3.5\",\n connector: \"absolute left-[9px] top-5 bottom-[-6px] w-px bg-border/60\",\n dotWrapperSize: \"relative z-10 mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-4.5 w-4.5 items-center justify-center rounded-full border ring-4 ring-background\",\n contentPadding: \"flex-1 pb-5 pt-0.5\",\n titleRowSpacing: \"flex min-w-0 flex-col gap-1 sm:flex-row sm:items-start sm:justify-between\",\n title: \"pr-4 text-[13px] leading-relaxed text-foreground\",\n time: \"mt-0.5 shrink-0 whitespace-nowrap text-[11px] text-muted-foreground/70\",\n cardContainer: \"overflow-hidden rounded-md border border-border/80 bg-muted/20\",\n cardHeader: \"px-3 pt-2.5\",\n cardBody: \"px-3 py-2.5 text-sm\",\n cardFooter: \"px-3 pb-2.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2.5 text-sm text-muted-foreground\",\n actionLinkRow: \"flex items-center gap-3 px-3 pb-2.5\",\n actionLink: \"inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-sm leading-relaxed text-muted-foreground\",\n },\n \"case-panel\": {\n outerRowGap: \"group relative flex gap-3\",\n connector: \"absolute left-[11px] top-6 bottom-[-2px] w-px bg-border/50\",\n dotWrapperSize: \"relative z-10 mt-0.5 flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-[22px] w-[22px] items-center justify-center rounded-full border ring-4 ring-background [&>svg]:h-3 [&>svg]:w-3\",\n contentPadding: \"flex-1 min-w-0 pb-5 pt-px\",\n titleRowSpacing: \"flex min-w-0 items-start justify-between gap-3\",\n title: \"min-w-0 pr-1 text-[13.5px] font-medium leading-tight text-foreground\",\n time: \"shrink-0 whitespace-nowrap pt-px text-[11px] leading-tight text-muted-foreground/60\",\n cardContainer: \"overflow-hidden rounded-lg border border-border/70 bg-card\",\n cardHeader: \"flex items-start justify-between border-b border-border/60 bg-muted/30 px-3 py-2 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70\",\n cardBody: \"px-3 py-2.5 text-[13px] leading-relaxed\",\n cardFooter: \"border-t border-border/60 bg-muted/10 px-3 py-1.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2 text-[13px] text-muted-foreground\",\n actionLinkRow: \"flex items-center justify-end gap-2 px-3 py-1.5\",\n actionLink: \"inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground/70 transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-[13px] leading-relaxed text-muted-foreground\",\n },\n}\n\nexport interface TimelineActivityProps {\n events: TimelineEvent[]\n className?: string\n variant?: TimelineActivityVariant\n}\n\nexport function TimelineActivity({ events, className, variant = \"default\" }: TimelineActivityProps) {\n return (\n <div className={cn(\"space-y-0\", className)} data-variant={variant}>\n {events.map((event, index) => (\n <TimelineItem\n key={event.id}\n event={event}\n isLast={index === events.length - 1}\n variant={variant}\n />\n ))}\n </div>\n )\n}\n\nfunction ActorByline({ actor, time }: { actor: TimelineEventActor; time: string }) {\n if (actor.kind === \"system\") return null\n\n if (actor.kind === \"integration\") {\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n <span>Integration</span>\n <span className=\"text-muted-foreground/40\">&middot;</span>\n <span>{time}</span>\n </div>\n )\n }\n\n // actor.kind === \"user\"\n const verb = actor.verb ?? \"performed this action\"\n const displayInitials = actor.initials ?? (actor.name ? actor.name.charAt(0).toUpperCase() : \"?\")\n\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n {actor.avatarUrl ? (\n <img\n src={actor.avatarUrl}\n alt={actor.name ?? \"User\"}\n className=\"h-4 w-4 rounded-full object-cover\"\n />\n ) : (\n <span className=\"flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/10 text-[8px] font-semibold text-muted-foreground\">\n {displayInitials}\n </span>\n )}\n {actor.name && <span className=\"text-foreground font-medium\">{actor.name}</span>}\n <span>{verb}</span>\n <span className=\"text-muted-foreground/40\">&middot;</span>\n <span>{time}</span>\n </div>\n )\n}\n\nfunction TimelineItem({\n event,\n isLast,\n variant,\n}: {\n event: TimelineEvent\n isLast: boolean\n variant: TimelineActivityVariant\n}) {\n const [expanded, setExpanded] = React.useState(event.defaultExpanded ?? false)\n const [showAllRecipients, setShowAllRecipients] = React.useState(false)\n const hasContent = !!event.content\n const hasEmail = !!event.email\n const hasGongCall = !!event.gongCall\n const classes = TIMELINE_VARIANT_CLASSES[variant]\n\n const toneStyle = event.tone ? TONE_CLASSES[event.tone] : null\n const dotClasses = toneStyle ? toneStyle.dot : NEUTRAL_DOT_CLASSES\n const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES\n\n return (\n <div className={classes.outerRowGap}>\n {!isLast && (\n <div className={classes.connector} />\n )}\n\n <div className={classes.dotWrapperSize}>\n <div className={cn(classes.dot, dotClasses, iconClasses)} data-testid=\"timeline-dot\">\n {event.icon}\n </div>\n </div>\n\n <div className={classes.contentPadding}>\n <div className={classes.titleRowSpacing}>\n <div className={classes.title}>\n {event.title}\n </div>\n <span className={classes.time}>\n {event.time}\n </span>\n </div>\n\n {event.actor && <ActorByline actor={event.actor} time={event.time} />}\n\n {(hasContent || hasEmail || hasGongCall) && (\n <div className=\"mt-2\">\n {event.isInteractive ? (\n hasGongCall ? (\n <GongCallCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n variant={variant}\n classes={classes}\n />\n ) : hasEmail ? (\n <EmailCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n variant={variant}\n classes={classes}\n />\n ) : (\n <ContentCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n variant={variant}\n classes={classes}\n />\n )\n ) : (\n <div className={classes.nonInteractiveContent}>\n {event.content}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )\n}\n\ntype TimelineEmail = NonNullable<TimelineEvent[\"email\"]>\ntype TimelineGongCall = NonNullable<TimelineEvent[\"gongCall\"]>\ntype TimelineSource = NonNullable<TimelineEvent[\"source\"]>\n\nfunction reactNodeToDisplayText(value: React.ReactNode): string {\n if (typeof value === \"string\" || typeof value === \"number\") return decodeEmailDisplayText(String(value))\n return \"\"\n}\n\n/**\n * Formats a call length in seconds to a compact human duration:\n * \"38 min\", \">= 60 min\" → \"1 h 12 min\". Returns \"\" for absent/invalid\n * input so callers can omit the chip entirely (never render \"0 min\").\n */\nfunction formatCallDuration(durationSeconds?: number | null): string {\n if (durationSeconds == null || !Number.isFinite(durationSeconds) || durationSeconds <= 0) return \"\"\n const totalMinutes = Math.round(durationSeconds / 60)\n if (totalMinutes < 1) return \"< 1 min\"\n if (totalMinutes < 60) return `${totalMinutes} min`\n const hours = Math.floor(totalMinutes / 60)\n const minutes = totalMinutes % 60\n return minutes === 0 ? `${hours} h` : `${hours} h ${minutes} min`\n}\n\nfunction cleanGongText(value?: string | null): string {\n if (!value) return \"\"\n return decodeEmailDisplayText(value).trim()\n}\n\n/**\n * True once the component has mounted on the client. Timestamps render in\n * UTC for SSR + the hydration pass (server and client HTML must match — the\n * server has no idea what timezone the viewer is in), then flip to the\n * viewer's local timezone immediately after mount.\n */\nfunction useHydrated(): boolean {\n const [hydrated, setHydrated] = React.useState(false)\n React.useEffect(() => {\n setHydrated(true)\n }, [])\n return hydrated\n}\n\ntype TimestampDisplayOptions = { utcTimestamps?: boolean }\n\nfunction timestampTimeZone(opts?: TimestampDisplayOptions): { timeZone?: string } {\n return opts?.utcTimestamps ? { timeZone: \"UTC\" } : {}\n}\n\nfunction getTimelineGongCallDisplay(gongCall: TimelineGongCall, opts?: TimestampDisplayOptions) {\n const title = cleanGongText(gongCall.title)\n const startTime = formatEmailTimestamp(gongCall.startTime, timestampTimeZone(opts)) ?? \"\"\n const duration = formatCallDuration(gongCall.durationSeconds)\n const direction = cleanGongText(gongCall.direction)\n const outcome = cleanGongText(gongCall.outcome)\n const brief = cleanGongText(gongCall.brief)\n const keyPoints = cleanGongText(gongCall.keyPoints)\n const nextSteps = cleanGongText(gongCall.nextSteps)\n const url = gongCall.url?.trim() || \"\"\n const snippet = brief.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\"\n\n return { title, startTime, duration, direction, outcome, brief, keyPoints, nextSteps, url, snippet }\n}\n\nfunction getTimelineEmailDisplay(email: TimelineEmail, opts?: TimestampDisplayOptions) {\n const sender = normalizeEmailSender({ name: email.from, email: email.fromEmail })\n const to = formatAddressList(email.to) || decodeEmailDisplayText(email.to ?? \"\")\n const cc = formatAddressList(email.cc) || decodeEmailDisplayText(email.cc ?? \"\")\n const bcc = formatAddressList(email.bcc) || decodeEmailDisplayText(email.bcc ?? \"\")\n const date = formatEmailTimestamp(email.date, timestampTimeZone(opts)) ?? decodeEmailDisplayText(email.date ?? \"\")\n const subject = email.subject ? decodeEmailDisplayText(email.subject) : \"\"\n const bodyText = reactNodeToDisplayText(email.body)\n const snippet = emailBodySnippet({ bodyHtml: email.bodyHtml, body: bodyText }, 140)\n const hasRecipients = Boolean(to || cc || bcc)\n\n return { sender, to, cc, bcc, date, subject, bodyText, snippet, hasRecipients }\n}\n\nfunction EmailMetadata({\n email,\n showAllRecipients,\n setShowAllRecipients,\n}: {\n email: TimelineEmail\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n const hydrated = useHydrated()\n const display = getTimelineEmailDisplay(email, { utcTimestamps: !hydrated })\n const hasExpandableRecipients = Boolean(display.cc || display.bcc)\n\n return (\n <>\n {/* Row 1: sender name + email, with the date right-aligned. The `gap-4`\n guarantees whitespace between the sender block and the date so they\n never visually jam together. */}\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"font-semibold text-foreground text-[13px] whitespace-nowrap\">{display.sender.name}</span>\n {display.sender.email ? (\n <span className=\"text-muted-foreground/60 text-xs truncate\">{display.sender.email}</span>\n ) : null}\n </div>\n {display.date ? (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{display.date}</span>\n ) : null}\n </div>\n {/* Recipient lines live on their own full-width, stacked rows below the\n sender/date row. Each line truncates independently. The To-segment is\n suppressed entirely when there is no recipient data (history emails)\n rather than rendering a meaningless \"no recipient yet\" placeholder. */}\n {display.hasRecipients ? (\n <div className=\"mt-0.5 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-1\">\n <span className=\"min-w-0 flex-1 truncate\">\n {display.to ? (\n <>To {display.to}</>\n ) : (\n <>\n <span className=\"text-muted-foreground/40\">cc</span> {display.cc || display.bcc}\n </>\n )}\n {display.to && !showAllRecipients && hasExpandableRecipients ? (\n <>, &hellip;</>\n ) : null}\n </span>\n {hasExpandableRecipients ? (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setShowAllRecipients((prev) => !prev)\n }}\n className=\"shrink-0 text-muted-foreground/40 hover:text-muted-foreground transition-colors\"\n aria-label={showAllRecipients ? \"Hide cc and bcc\" : \"Show cc and bcc\"}\n >\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", showAllRecipients && \"rotate-180\")} />\n </button>\n ) : null}\n </div>\n {showAllRecipients && display.to && display.cc ? (\n <div className=\"truncate\">\n <span className=\"text-muted-foreground/40\">cc</span> {display.cc}\n </div>\n ) : null}\n {showAllRecipients && display.bcc ? (\n <div className=\"truncate\">\n <span className=\"text-muted-foreground/40\">bcc</span> {display.bcc}\n </div>\n ) : null}\n </div>\n ) : null}\n </>\n )\n}\n\nfunction SuppressedHtmlSection({\n html,\n showLabel,\n hideLabel,\n slot,\n}: {\n html: string\n showLabel: string\n hideLabel: string\n slot: string\n}) {\n const [open, setOpen] = React.useState(false)\n\n return (\n <div className=\"mt-2\" data-slot={slot}>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setOpen((value) => !value)\n }}\n aria-expanded={open}\n className=\"inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground\"\n >\n {open ? hideLabel : showLabel}\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", open && \"rotate-180\")} />\n </button>\n {open ? (\n <div\n data-slot={`${slot}-content`}\n className=\"mt-2 border-l-2 border-border pl-3 text-sm leading-relaxed text-muted-foreground\"\n >\n <SharedEmailBody html={html} variant=\"history\" collapseDetails={false} />\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction TimelineEmailBody({ email }: { email: TimelineEmail }) {\n const display = getTimelineEmailDisplay(email)\n const bodyFallback = display.bodyText || (typeof email.body === \"string\" ? email.body : \"\")\n\n if (!email.bodyHtml && !bodyFallback && email.body) {\n return <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">{email.body}</div>\n }\n\n const body = (\n <SharedEmailBody\n html={email.bodyHtml}\n text={bodyFallback}\n variant=\"history\"\n collapseDetails={true}\n className=\"text-sm leading-relaxed\"\n />\n )\n\n const hasSignature = Boolean(email.signatureHtml && email.signatureHtml.trim())\n const hasQuoted = Boolean(email.quotedHtml && email.quotedHtml.trim())\n\n // Fast path: no signature/quoted slots → identical output to before.\n if (!hasSignature && !hasQuoted) {\n return email.bodyHtml ? <div data-slot=\"timeline-email-html\">{body}</div> : body\n }\n\n return (\n <div data-slot={email.bodyHtml ? \"timeline-email-html\" : undefined}>\n {body}\n {hasSignature ? (\n <SuppressedHtmlSection\n html={email.signatureHtml as string}\n showLabel=\"Show signature\"\n hideLabel=\"Hide signature\"\n slot=\"timeline-email-signature\"\n />\n ) : null}\n {hasQuoted ? (\n <SuppressedHtmlSection\n html={email.quotedHtml as string}\n showLabel=\"Show quoted text\"\n hideLabel=\"Hide quoted text\"\n slot=\"timeline-email-quoted\"\n />\n ) : null}\n </div>\n )\n}\n\nfunction renderDecodedPreview(preview?: React.ReactNode): React.ReactNode {\n if (typeof preview === \"string\" || typeof preview === \"number\") return decodeEmailDisplayText(String(preview))\n return preview\n}\n\nfunction CollapsedEmailPreview({\n email,\n preview,\n className,\n actionClassName,\n onClick,\n}: {\n email?: TimelineEmail\n preview?: React.ReactNode\n className: string\n actionClassName: string\n onClick?: () => void\n}) {\n const display = email ? getTimelineEmailDisplay(email) : null\n const previewContent = display?.snippet || renderDecodedPreview(preview)\n\n return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 min-w-0 flex-1 pr-3 text-[13px]\">\n <span className=\"text-muted-foreground\">{display?.sender.name}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">&middot;</span>\n {display?.subject ? (\n <>\n <span className=\"text-muted-foreground\">{display.subject}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">&middot;</span>\n </>\n ) : null}\n <span className=\"text-muted-foreground\">{previewContent}</span>\n </span>\n <button type=\"button\" className={actionClassName}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )\n}\n\nfunction ShowLessButton({\n className,\n onClick,\n type,\n}: {\n className: string\n onClick: React.MouseEventHandler<HTMLButtonElement>\n type?: \"button\"\n}) {\n return (\n <button type={type} onClick={onClick} className={className}>\n Show less <ChevronUp className=\"h-3 w-3\" />\n </button>\n )\n}\n\n/**\n * Top-of-card collapse affordance. Long expanded emails are tedious to collapse\n * from the bottom alone, so the header row also carries a \"Show less\" control.\n */\nfunction CollapseTopButton({\n onClick,\n className,\n}: {\n onClick: React.MouseEventHandler<HTMLButtonElement>\n className?: string\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label=\"Show less\"\n data-slot=\"timeline-collapse-top\"\n className={cn(\n \"shrink-0 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground/60 transition-colors hover:text-foreground\",\n className,\n )}\n >\n <span className=\"sr-only sm:not-sr-only\">Show less</span>\n <ChevronUp className=\"h-3 w-3\" />\n </button>\n )\n}\n\nfunction SourceAction({\n source,\n onSourceClick,\n className,\n}: {\n source: TimelineSource\n onSourceClick?: () => void\n className: string\n}) {\n const actionLabel = source.actionLabel ?? `Open in ${source.label}`\n\n if (onSourceClick) {\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onSourceClick(); }}\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </button>\n )\n }\n\n return (\n <a\n href={source.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n )\n}\n\nfunction GongLinkAction({\n url,\n className,\n}: {\n url: string\n className: string\n}) {\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className={className}\n data-slot=\"timeline-gong-link\"\n >\n View in Gong\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n )\n}\n\n/**\n * The expanded Gong call card body — call title + start time header, optional\n * direction/outcome chips, the AI brief as primary body text, and labeled\n * Key points / Next steps subsections. Every section renders only when its\n * content is present.\n */\nfunction TimelineGongCallBody({\n display,\n labelClassName,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n labelClassName: string\n}) {\n return (\n <div className=\"space-y-3\" data-slot=\"timeline-gong-body\">\n {display.brief ? (\n <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.brief}\n </div>\n ) : null}\n {display.keyPoints ? (\n <div data-slot=\"timeline-gong-key-points\">\n <div className={labelClassName}>Key points</div>\n <div className=\"mt-1 whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.keyPoints}\n </div>\n </div>\n ) : null}\n {display.nextSteps ? (\n <div data-slot=\"timeline-gong-next-steps\">\n <div className={labelClassName}>Next steps</div>\n <div className=\"mt-1 whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.nextSteps}\n </div>\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction GongCallHeader({\n display,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n}) {\n return (\n <>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Phone className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground/60\" />\n <span className=\"min-w-0 truncate font-semibold text-foreground text-[13px]\">{display.title}</span>\n </div>\n {display.startTime ? (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{display.startTime}</span>\n ) : null}\n </div>\n {display.duration || display.direction || display.outcome ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n {display.duration ? <span data-slot=\"timeline-gong-duration\">{display.duration}</span> : null}\n {display.direction ? (\n <span className=\"rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground\">{display.direction}</span>\n ) : null}\n {display.outcome ? (\n <span className=\"rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground\">{display.outcome}</span>\n ) : null}\n </div>\n ) : null}\n </>\n )\n}\n\nfunction CollapsedGongCallPreview({\n display,\n className,\n actionClassName,\n onClick,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n className: string\n actionClassName: string\n onClick?: () => void\n}) {\n return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 min-w-0 flex-1 pr-3 text-[13px] text-muted-foreground\">\n {display.duration ? (\n <>\n <span className=\"inline-flex items-center gap-1\">\n <Phone className=\"h-3 w-3\" />\n {display.duration}\n </span>\n {display.snippet ? <span className=\"mx-1.5 text-muted-foreground/40\">&middot;</span> : null}\n </>\n ) : null}\n {display.snippet ? <span>{display.snippet}</span> : null}\n </span>\n <button type=\"button\" className={actionClassName}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )\n}\n\nfunction GongCallCard({\n event,\n expanded,\n setExpanded,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n const gongCall = event.gongCall as TimelineGongCall\n const hydrated = useHydrated()\n const display = getTimelineGongCallDisplay(gongCall, { utcTimestamps: !hydrated })\n\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant} data-slot=\"timeline-gong-card\">\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\",\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <GongCallHeader display={display} />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-0.5\"\n />\n </div>\n\n <TimelineGongCallBody\n display={display}\n labelClassName=\"text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/70\"\n />\n\n <div className=\"mt-2 flex items-center gap-3\">\n {display.url ? (\n <GongLinkAction\n url={display.url}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <CollapsedGongCallPreview\n display={display}\n className=\"flex items-center justify-between gap-2 text-muted-foreground\"\n actionClassName=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\"\n />\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant} data-slot=\"timeline-gong-card\">\n {expanded ? (\n <>\n <div className={classes.cardHeader} data-slot=\"timeline-card-header\">\n <div className=\"min-w-0 flex-1 normal-case tracking-normal\">\n <GongCallHeader display={display} />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"ml-3 normal-case tracking-normal\"\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineGongCallBody\n display={display}\n labelClassName=\"text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70\"\n />\n </div>\n\n <div\n className={cn(classes.cardFooter, classes.actionLinkRow, display.url ? \"justify-between\" : \"justify-end\")}\n data-slot=\"timeline-card-footer\"\n >\n {display.url ? <GongLinkAction url={display.url} className={classes.actionLink} /> : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <CollapsedGongCallPreview\n display={display}\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n actionClassName={cn(classes.actionLink, \"shrink-0\")}\n onClick={() => setExpanded(true)}\n />\n )}\n </div>\n )\n}\n\nfunction EmailCard({\n event,\n expanded,\n setExpanded,\n showAllRecipients,\n setShowAllRecipients,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded && event.email ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-0.5\"\n />\n </div>\n\n <TimelineEmailBody email={event.email} />\n\n {event.content ? (\n <div className=\"rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className=\"flex items-center justify-between gap-2 text-muted-foreground\"\n actionClassName=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\"\n />\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded && event.email ? (\n <>\n <div className={classes.cardHeader} data-slot=\"timeline-card-header\">\n <div className=\"min-w-0 flex-1 normal-case tracking-normal\">\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"ml-3 normal-case tracking-normal\"\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineEmailBody email={event.email} />\n {event.content ? (\n <div className=\"mt-3 rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n </div>\n\n <div\n className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")}\n data-slot=\"timeline-card-footer\"\n >\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n actionClassName={cn(classes.actionLink, \"shrink-0\")}\n onClick={() => setExpanded(true)}\n />\n )}\n </div>\n )\n}\n\nfunction ContentCard({\n event,\n expanded,\n setExpanded,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded ? (\n <div className=\"space-y-2\">\n {event.content}\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <div className=\"flex items-center justify-between gap-2 text-muted-foreground\">\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button className=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\">\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded ? (\n <>\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n {event.content}\n </div>\n <div className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")} data-slot=\"timeline-card-footer\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <div\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n onClick={() => setExpanded(true)}\n >\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button type=\"button\" className={cn(classes.actionLink, \"shrink-0\")}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/timeline-activity.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"../lib/utils\"\nimport { ChevronDown, ChevronUp, ExternalLink, Phone } from \"lucide-react\"\nimport { EmailBody as SharedEmailBody } from \"./email-body\"\nimport {\n decodeEmailDisplayText,\n emailBodySnippet,\n formatAddressList,\n formatEmailTimestamp,\n normalizeEmailSender,\n} from \"./email-display-helpers\"\n\nexport type TimelineActivityVariant = \"default\" | \"case-panel\"\n\nexport type TimelineEventTone =\n | \"red\"\n | \"amber\"\n | \"emerald\"\n | \"violet\"\n | \"blue\"\n | \"slate\"\n | \"salesforce\"\n | \"gmail\"\n\nexport interface TimelineEventActor {\n kind: \"user\" | \"integration\" | \"system\"\n name?: string\n initials?: string\n avatarUrl?: string\n verb?: string\n}\n\nexport interface TimelineEvent {\n id: string\n icon: React.ReactNode\n title: React.ReactNode\n time: string\n preview?: React.ReactNode\n email?: {\n from: string\n fromEmail?: string\n to: string\n cc?: string\n bcc?: string\n date?: string\n subject?: string\n body: React.ReactNode\n /**\n * HTML body. When provided, the card renders formatted, Gmail-like HTML\n * instead of the plain-text `body`. The component sanitizes it before\n * rendering. Opt-in: when absent, the plain-text `body` path is used and\n * existing consumers are unaffected.\n */\n bodyHtml?: string\n /**\n * Sender signature HTML, split out of the main body by the consuming app's\n * email pipeline. When present, it is hidden behind a subtle \"Show\n * signature\" toggle (collapsed by default) so signatures don't add noise to\n * the timeline. Sanitized before rendering. Optional — when absent the body\n * renders exactly as before. Mirrors `signatureHtml` on EmailPreviewCard.\n */\n signatureHtml?: string | null\n /**\n * Quoted / trailing thread content (the \"On <date> X wrote:\" history) split\n * out of the main body. When present, it is hidden behind a subtle \"Show\n * quoted text\" toggle (collapsed by default). Sanitized before rendering.\n * Optional — when absent the body renders exactly as before.\n */\n quotedHtml?: string | null\n }\n /**\n * Gong call payload. When present, the card renders a dedicated, first-class\n * Gong call card (mirroring the `email` treatment) instead of generic\n * content: a collapsed duration + brief-snippet preview, and an expanded\n * card with the call title, start time, direction/outcome chips, the AI\n * brief, key points, next steps, and a \"View in Gong\" link. Opt-in: when\n * absent, existing consumers are unaffected. Every field except `title` is\n * optional/nullable and renders nothing when absent (no empty sections,\n * no \"null\"/\"undefined\", no \"Invalid Date\").\n */\n gongCall?: {\n /** Gong call title. */\n title: string\n /** ISO datetime of the call start. */\n startTime?: string | null\n /** Call length in seconds. Formatted to a human duration when present. */\n durationSeconds?: number | null\n /** Call direction, e.g. \"Outbound\" / \"Inbound\". */\n direction?: string | null\n /** Call outcome, e.g. \"Connected\". */\n outcome?: string | null\n /** Gong AI call brief (plain text, may be multi-paragraph). */\n brief?: string | null\n /** Gong AI key points (plain text, often newline-delimited bullets). */\n keyPoints?: string | null\n /** Gong AI highlights / next steps (plain text). */\n nextSteps?: string | null\n /** Deep link to the call in Gong. */\n url?: string | null\n }\n content?: React.ReactNode\n source?: {\n label: string\n actionLabel?: string\n url: string\n }\n defaultExpanded?: boolean\n isInteractive?: boolean\n onSourceClick?: () => void\n tone?: TimelineEventTone\n actor?: TimelineEventActor\n isSystemNoise?: boolean\n}\n\n// ---------------------------------------------------------------------------\n// Tone class map — every class is a complete static string literal so\n// Tailwind's JIT scanner can detect them. NO interpolation.\n// ---------------------------------------------------------------------------\n\nexport const TONE_CLASSES: Record<\n TimelineEventTone,\n { dot: string; icon: string }\n> = {\n red: {\n dot: \"bg-red-50 border-red-200 dark:bg-red-950/30 dark:border-red-900/40\",\n icon: \"text-red-600 dark:text-red-300\",\n },\n amber: {\n dot: \"bg-amber-50 border-amber-200 dark:bg-amber-950/30 dark:border-amber-900/40\",\n icon: \"text-amber-600 dark:text-amber-300\",\n },\n emerald: {\n dot: \"bg-emerald-50 border-emerald-200 dark:bg-emerald-950/30 dark:border-emerald-900/40\",\n icon: \"text-emerald-600 dark:text-emerald-300\",\n },\n violet: {\n dot: \"bg-violet-50 border-violet-200 dark:bg-violet-950/30 dark:border-violet-900/40\",\n icon: \"text-violet-600 dark:text-violet-300\",\n },\n blue: {\n dot: \"bg-blue-50 border-blue-200 dark:bg-blue-950/30 dark:border-blue-900/40\",\n icon: \"text-blue-600 dark:text-blue-300\",\n },\n slate: {\n dot: \"bg-slate-100 border-slate-200 dark:bg-slate-800/50 dark:border-slate-700\",\n icon: \"text-slate-500 dark:text-slate-300\",\n },\n salesforce: {\n dot: \"bg-white border-[#00A1E0]/25 dark:bg-background dark:border-[#00A1E0]/25\",\n icon: \"text-[#00A1E0]\",\n },\n gmail: {\n dot: \"bg-white border-red-200 dark:bg-background dark:border-red-900/40\",\n icon: \"text-red-500 dark:text-red-300\",\n },\n}\n\nconst NEUTRAL_DOT_CLASSES = \"border-border/60 bg-background\"\nconst NEUTRAL_ICON_CLASSES = \"text-muted-foreground\"\n\ntype TimelineVariantClasses = {\n outerRowGap: string\n connector: string\n dotWrapperSize: string\n dot: string\n contentPadding: string\n titleRowSpacing: string\n title: string\n time: string\n cardContainer: string\n cardHeader: string\n cardBody: string\n cardFooter: string\n collapsedPreview: string\n actionLinkRow: string\n actionLink: string\n nonInteractiveContent: string\n}\n\nconst TIMELINE_VARIANT_CLASSES: Record<TimelineActivityVariant, TimelineVariantClasses> = {\n default: {\n outerRowGap: \"group relative flex gap-3.5\",\n connector: \"absolute left-[9px] top-5 bottom-[-6px] w-px bg-border/60\",\n dotWrapperSize: \"relative z-10 mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-4.5 w-4.5 items-center justify-center rounded-full border ring-4 ring-background\",\n contentPadding: \"flex-1 pb-5 pt-0.5\",\n titleRowSpacing: \"flex min-w-0 flex-col gap-1 sm:flex-row sm:items-start sm:justify-between\",\n title: \"pr-4 text-[13px] leading-relaxed text-foreground\",\n time: \"mt-0.5 shrink-0 whitespace-nowrap text-[11px] text-muted-foreground/70\",\n cardContainer: \"overflow-hidden rounded-md border border-border/80 bg-muted/20\",\n cardHeader: \"px-3 pt-2.5\",\n cardBody: \"px-3 py-2.5 text-sm\",\n cardFooter: \"px-3 pb-2.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2.5 text-sm text-muted-foreground\",\n actionLinkRow: \"flex items-center gap-3 px-3 pb-2.5\",\n actionLink: \"inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-sm leading-relaxed text-muted-foreground\",\n },\n \"case-panel\": {\n outerRowGap: \"group relative flex gap-3\",\n connector: \"absolute left-[11px] top-6 bottom-[-2px] w-px bg-border/50\",\n dotWrapperSize: \"relative z-10 mt-0.5 flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded-full bg-background\",\n dot: \"flex h-[22px] w-[22px] items-center justify-center rounded-full border ring-4 ring-background [&>svg]:h-3 [&>svg]:w-3\",\n contentPadding: \"flex-1 min-w-0 pb-5 pt-px\",\n titleRowSpacing: \"flex min-w-0 items-start justify-between gap-3\",\n title: \"min-w-0 pr-1 text-[13.5px] font-medium leading-tight text-foreground\",\n time: \"shrink-0 whitespace-nowrap pt-px text-[11px] leading-tight text-muted-foreground/60\",\n cardContainer: \"overflow-hidden rounded-lg border border-border/70 bg-card\",\n cardHeader: \"flex items-start justify-between border-b border-border/60 bg-muted/30 px-3 py-2 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70\",\n cardBody: \"px-3 py-2.5 text-[13px] leading-relaxed\",\n cardFooter: \"border-t border-border/60 bg-muted/10 px-3 py-1.5\",\n collapsedPreview: \"flex items-center justify-between gap-2 px-3 py-2 text-[13px] text-muted-foreground\",\n actionLinkRow: \"flex items-center justify-end gap-2 px-3 py-1.5\",\n actionLink: \"inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground/70 transition-colors hover:text-foreground\",\n nonInteractiveContent: \"pr-2 text-[13px] leading-relaxed text-muted-foreground\",\n },\n}\n\nexport interface TimelineActivityProps {\n events: TimelineEvent[]\n className?: string\n variant?: TimelineActivityVariant\n}\n\nexport function TimelineActivity({ events, className, variant = \"default\" }: TimelineActivityProps) {\n return (\n <div className={cn(\"space-y-0\", className)} data-variant={variant}>\n {events.map((event, index) => (\n <TimelineItem\n key={event.id}\n event={event}\n isLast={index === events.length - 1}\n variant={variant}\n />\n ))}\n </div>\n )\n}\n\nfunction ActorByline({ actor, time }: { actor: TimelineEventActor; time: string }) {\n if (actor.kind === \"system\") return null\n\n if (actor.kind === \"integration\") {\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n <span>Integration</span>\n <span className=\"text-muted-foreground/40\">&middot;</span>\n <span>{time}</span>\n </div>\n )\n }\n\n // actor.kind === \"user\"\n const verb = actor.verb ?? \"performed this action\"\n const displayInitials = actor.initials ?? (actor.name ? actor.name.charAt(0).toUpperCase() : \"?\")\n\n return (\n <div className=\"mt-1 flex items-center gap-1.5 text-xs text-muted-foreground\" data-testid=\"actor-byline\">\n {actor.avatarUrl ? (\n <img\n src={actor.avatarUrl}\n alt={actor.name ?? \"User\"}\n className=\"h-4 w-4 rounded-full object-cover\"\n />\n ) : (\n <span className=\"flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/10 text-[8px] font-semibold text-muted-foreground\">\n {displayInitials}\n </span>\n )}\n {actor.name && <span className=\"text-foreground font-medium\">{actor.name}</span>}\n <span>{verb}</span>\n <span className=\"text-muted-foreground/40\">&middot;</span>\n <span>{time}</span>\n </div>\n )\n}\n\nfunction TimelineItem({\n event,\n isLast,\n variant,\n}: {\n event: TimelineEvent\n isLast: boolean\n variant: TimelineActivityVariant\n}) {\n const [expanded, setExpanded] = React.useState(event.defaultExpanded ?? false)\n const [showAllRecipients, setShowAllRecipients] = React.useState(false)\n const hasContent = !!event.content\n const hasEmail = !!event.email\n const hasGongCall = !!event.gongCall\n const classes = TIMELINE_VARIANT_CLASSES[variant]\n\n const toneStyle = event.tone ? TONE_CLASSES[event.tone] : null\n const dotClasses = toneStyle ? toneStyle.dot : NEUTRAL_DOT_CLASSES\n const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES\n\n return (\n <div\n id={`timeline-event-${event.id}`}\n data-testid={`timeline-event-${event.id}`}\n data-activity-id={event.id}\n tabIndex={-1}\n className={classes.outerRowGap}\n >\n {!isLast && (\n <div className={classes.connector} />\n )}\n\n <div className={classes.dotWrapperSize}>\n <div className={cn(classes.dot, dotClasses, iconClasses)} data-testid=\"timeline-dot\">\n {event.icon}\n </div>\n </div>\n\n <div className={classes.contentPadding}>\n <div className={classes.titleRowSpacing}>\n <div className={classes.title}>\n {event.title}\n </div>\n <span className={classes.time}>\n {event.time}\n </span>\n </div>\n\n {event.actor && <ActorByline actor={event.actor} time={event.time} />}\n\n {(hasContent || hasEmail || hasGongCall) && (\n <div className=\"mt-2\">\n {event.isInteractive ? (\n hasGongCall ? (\n <GongCallCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n variant={variant}\n classes={classes}\n />\n ) : hasEmail ? (\n <EmailCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n variant={variant}\n classes={classes}\n />\n ) : (\n <ContentCard\n event={event}\n expanded={expanded}\n setExpanded={setExpanded}\n variant={variant}\n classes={classes}\n />\n )\n ) : (\n <div className={classes.nonInteractiveContent}>\n {event.content}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )\n}\n\ntype TimelineEmail = NonNullable<TimelineEvent[\"email\"]>\ntype TimelineGongCall = NonNullable<TimelineEvent[\"gongCall\"]>\ntype TimelineSource = NonNullable<TimelineEvent[\"source\"]>\n\nfunction reactNodeToDisplayText(value: React.ReactNode): string {\n if (typeof value === \"string\" || typeof value === \"number\") return decodeEmailDisplayText(String(value))\n return \"\"\n}\n\n/**\n * Formats a call length in seconds to a compact human duration:\n * \"38 min\", \">= 60 min\" → \"1 h 12 min\". Returns \"\" for absent/invalid\n * input so callers can omit the chip entirely (never render \"0 min\").\n */\nfunction formatCallDuration(durationSeconds?: number | null): string {\n if (durationSeconds == null || !Number.isFinite(durationSeconds) || durationSeconds <= 0) return \"\"\n const totalMinutes = Math.round(durationSeconds / 60)\n if (totalMinutes < 1) return \"< 1 min\"\n if (totalMinutes < 60) return `${totalMinutes} min`\n const hours = Math.floor(totalMinutes / 60)\n const minutes = totalMinutes % 60\n return minutes === 0 ? `${hours} h` : `${hours} h ${minutes} min`\n}\n\nfunction cleanGongText(value?: string | null): string {\n if (!value) return \"\"\n return decodeEmailDisplayText(value).trim()\n}\n\n/**\n * True once the component has mounted on the client. Timestamps render in\n * UTC for SSR + the hydration pass (server and client HTML must match — the\n * server has no idea what timezone the viewer is in), then flip to the\n * viewer's local timezone immediately after mount.\n */\nfunction useHydrated(): boolean {\n const [hydrated, setHydrated] = React.useState(false)\n React.useEffect(() => {\n setHydrated(true)\n }, [])\n return hydrated\n}\n\ntype TimestampDisplayOptions = { utcTimestamps?: boolean }\n\nfunction timestampTimeZone(opts?: TimestampDisplayOptions): { timeZone?: string } {\n return opts?.utcTimestamps ? { timeZone: \"UTC\" } : {}\n}\n\nfunction getTimelineGongCallDisplay(gongCall: TimelineGongCall, opts?: TimestampDisplayOptions) {\n const title = cleanGongText(gongCall.title)\n const startTime = formatEmailTimestamp(gongCall.startTime, timestampTimeZone(opts)) ?? \"\"\n const duration = formatCallDuration(gongCall.durationSeconds)\n const direction = cleanGongText(gongCall.direction)\n const outcome = cleanGongText(gongCall.outcome)\n const brief = cleanGongText(gongCall.brief)\n const keyPoints = cleanGongText(gongCall.keyPoints)\n const nextSteps = cleanGongText(gongCall.nextSteps)\n const url = gongCall.url?.trim() || \"\"\n const snippet = brief.split(\"\\n\").find((line) => line.trim())?.trim() ?? \"\"\n\n return { title, startTime, duration, direction, outcome, brief, keyPoints, nextSteps, url, snippet }\n}\n\nfunction getTimelineEmailDisplay(email: TimelineEmail, opts?: TimestampDisplayOptions) {\n const sender = normalizeEmailSender({ name: email.from, email: email.fromEmail })\n const to = formatAddressList(email.to) || decodeEmailDisplayText(email.to ?? \"\")\n const cc = formatAddressList(email.cc) || decodeEmailDisplayText(email.cc ?? \"\")\n const bcc = formatAddressList(email.bcc) || decodeEmailDisplayText(email.bcc ?? \"\")\n const date = formatEmailTimestamp(email.date, timestampTimeZone(opts)) ?? decodeEmailDisplayText(email.date ?? \"\")\n const subject = email.subject ? decodeEmailDisplayText(email.subject) : \"\"\n const bodyText = reactNodeToDisplayText(email.body)\n const snippet = emailBodySnippet({ bodyHtml: email.bodyHtml, body: bodyText }, 140)\n const hasRecipients = Boolean(to || cc || bcc)\n\n return { sender, to, cc, bcc, date, subject, bodyText, snippet, hasRecipients }\n}\n\nfunction EmailMetadata({\n email,\n showAllRecipients,\n setShowAllRecipients,\n}: {\n email: TimelineEmail\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n const hydrated = useHydrated()\n const display = getTimelineEmailDisplay(email, { utcTimestamps: !hydrated })\n const hasExpandableRecipients = Boolean(display.cc || display.bcc)\n\n return (\n <>\n {/* Row 1: sender name + email, with the date right-aligned. The `gap-4`\n guarantees whitespace between the sender block and the date so they\n never visually jam together. */}\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex min-w-0 items-baseline gap-1.5\">\n <span className=\"font-semibold text-foreground text-[13px] whitespace-nowrap\">{display.sender.name}</span>\n {display.sender.email ? (\n <span className=\"text-muted-foreground/60 text-xs truncate\">{display.sender.email}</span>\n ) : null}\n </div>\n {display.date ? (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{display.date}</span>\n ) : null}\n </div>\n {/* Recipient lines live on their own full-width, stacked rows below the\n sender/date row. Each line truncates independently. The To-segment is\n suppressed entirely when there is no recipient data (history emails)\n rather than rendering a meaningless \"no recipient yet\" placeholder. */}\n {display.hasRecipients ? (\n <div className=\"mt-0.5 text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-1\">\n <span className=\"min-w-0 flex-1 truncate\">\n {display.to ? (\n <>To {display.to}</>\n ) : (\n <>\n <span className=\"text-muted-foreground/40\">cc</span> {display.cc || display.bcc}\n </>\n )}\n {display.to && !showAllRecipients && hasExpandableRecipients ? (\n <>, &hellip;</>\n ) : null}\n </span>\n {hasExpandableRecipients ? (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setShowAllRecipients((prev) => !prev)\n }}\n className=\"shrink-0 text-muted-foreground/40 hover:text-muted-foreground transition-colors\"\n aria-label={showAllRecipients ? \"Hide cc and bcc\" : \"Show cc and bcc\"}\n >\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", showAllRecipients && \"rotate-180\")} />\n </button>\n ) : null}\n </div>\n {showAllRecipients && display.to && display.cc ? (\n <div className=\"truncate\">\n <span className=\"text-muted-foreground/40\">cc</span> {display.cc}\n </div>\n ) : null}\n {showAllRecipients && display.bcc ? (\n <div className=\"truncate\">\n <span className=\"text-muted-foreground/40\">bcc</span> {display.bcc}\n </div>\n ) : null}\n </div>\n ) : null}\n </>\n )\n}\n\nfunction SuppressedHtmlSection({\n html,\n showLabel,\n hideLabel,\n slot,\n}: {\n html: string\n showLabel: string\n hideLabel: string\n slot: string\n}) {\n const [open, setOpen] = React.useState(false)\n\n return (\n <div className=\"mt-2\" data-slot={slot}>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setOpen((value) => !value)\n }}\n aria-expanded={open}\n className=\"inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground\"\n >\n {open ? hideLabel : showLabel}\n <ChevronDown className={cn(\"h-3 w-3 transition-transform\", open && \"rotate-180\")} />\n </button>\n {open ? (\n <div\n data-slot={`${slot}-content`}\n className=\"mt-2 border-l-2 border-border pl-3 text-sm leading-relaxed text-muted-foreground\"\n >\n <SharedEmailBody html={html} variant=\"history\" collapseDetails={false} />\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction TimelineEmailBody({ email }: { email: TimelineEmail }) {\n const display = getTimelineEmailDisplay(email)\n const bodyFallback = display.bodyText || (typeof email.body === \"string\" ? email.body : \"\")\n\n if (!email.bodyHtml && !bodyFallback && email.body) {\n return <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">{email.body}</div>\n }\n\n const body = (\n <SharedEmailBody\n html={email.bodyHtml}\n text={bodyFallback}\n variant=\"history\"\n collapseDetails={true}\n className=\"text-sm leading-relaxed\"\n />\n )\n\n const hasSignature = Boolean(email.signatureHtml && email.signatureHtml.trim())\n const hasQuoted = Boolean(email.quotedHtml && email.quotedHtml.trim())\n\n // Fast path: no signature/quoted slots → identical output to before.\n if (!hasSignature && !hasQuoted) {\n return email.bodyHtml ? <div data-slot=\"timeline-email-html\">{body}</div> : body\n }\n\n return (\n <div data-slot={email.bodyHtml ? \"timeline-email-html\" : undefined}>\n {body}\n {hasSignature ? (\n <SuppressedHtmlSection\n html={email.signatureHtml as string}\n showLabel=\"Show signature\"\n hideLabel=\"Hide signature\"\n slot=\"timeline-email-signature\"\n />\n ) : null}\n {hasQuoted ? (\n <SuppressedHtmlSection\n html={email.quotedHtml as string}\n showLabel=\"Show quoted text\"\n hideLabel=\"Hide quoted text\"\n slot=\"timeline-email-quoted\"\n />\n ) : null}\n </div>\n )\n}\n\nfunction renderDecodedPreview(preview?: React.ReactNode): React.ReactNode {\n if (typeof preview === \"string\" || typeof preview === \"number\") return decodeEmailDisplayText(String(preview))\n return preview\n}\n\nfunction CollapsedEmailPreview({\n email,\n preview,\n className,\n actionClassName,\n onClick,\n}: {\n email?: TimelineEmail\n preview?: React.ReactNode\n className: string\n actionClassName: string\n onClick?: () => void\n}) {\n const display = email ? getTimelineEmailDisplay(email) : null\n const previewContent = display?.snippet || renderDecodedPreview(preview)\n\n return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 min-w-0 flex-1 pr-3 text-[13px]\">\n <span className=\"text-muted-foreground\">{display?.sender.name}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">&middot;</span>\n {display?.subject ? (\n <>\n <span className=\"text-muted-foreground\">{display.subject}</span>\n <span className=\"mx-1.5 text-muted-foreground/40\">&middot;</span>\n </>\n ) : null}\n <span className=\"text-muted-foreground\">{previewContent}</span>\n </span>\n <button type=\"button\" className={actionClassName}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )\n}\n\nfunction ShowLessButton({\n className,\n onClick,\n type,\n}: {\n className: string\n onClick: React.MouseEventHandler<HTMLButtonElement>\n type?: \"button\"\n}) {\n return (\n <button type={type} onClick={onClick} className={className}>\n Show less <ChevronUp className=\"h-3 w-3\" />\n </button>\n )\n}\n\n/**\n * Top-of-card collapse affordance. Long expanded emails are tedious to collapse\n * from the bottom alone, so the header row also carries a \"Show less\" control.\n */\nfunction CollapseTopButton({\n onClick,\n className,\n}: {\n onClick: React.MouseEventHandler<HTMLButtonElement>\n className?: string\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label=\"Show less\"\n data-slot=\"timeline-collapse-top\"\n className={cn(\n \"shrink-0 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground/60 transition-colors hover:text-foreground\",\n className,\n )}\n >\n <span className=\"sr-only sm:not-sr-only\">Show less</span>\n <ChevronUp className=\"h-3 w-3\" />\n </button>\n )\n}\n\nfunction SourceAction({\n source,\n onSourceClick,\n className,\n}: {\n source: TimelineSource\n onSourceClick?: () => void\n className: string\n}) {\n const actionLabel = source.actionLabel ?? `Open in ${source.label}`\n\n if (onSourceClick) {\n return (\n <button\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); onSourceClick(); }}\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </button>\n )\n }\n\n return (\n <a\n href={source.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className={className}\n >\n {actionLabel}\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n )\n}\n\nfunction GongLinkAction({\n url,\n className,\n}: {\n url: string\n className: string\n}) {\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n className={className}\n data-slot=\"timeline-gong-link\"\n >\n View in Gong\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n )\n}\n\n/**\n * The expanded Gong call card body — call title + start time header, optional\n * direction/outcome chips, the AI brief as primary body text, and labeled\n * Key points / Next steps subsections. Every section renders only when its\n * content is present.\n */\nfunction TimelineGongCallBody({\n display,\n labelClassName,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n labelClassName: string\n}) {\n return (\n <div className=\"space-y-3\" data-slot=\"timeline-gong-body\">\n {display.brief ? (\n <div className=\"whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.brief}\n </div>\n ) : null}\n {display.keyPoints ? (\n <div data-slot=\"timeline-gong-key-points\">\n <div className={labelClassName}>Key points</div>\n <div className=\"mt-1 whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.keyPoints}\n </div>\n </div>\n ) : null}\n {display.nextSteps ? (\n <div data-slot=\"timeline-gong-next-steps\">\n <div className={labelClassName}>Next steps</div>\n <div className=\"mt-1 whitespace-pre-line text-sm leading-relaxed text-foreground/90\">\n {display.nextSteps}\n </div>\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction GongCallHeader({\n display,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n}) {\n return (\n <>\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <Phone className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground/60\" />\n <span className=\"min-w-0 truncate font-semibold text-foreground text-[13px]\">{display.title}</span>\n </div>\n {display.startTime ? (\n <span className=\"shrink-0 text-xs text-muted-foreground/50 whitespace-nowrap\">{display.startTime}</span>\n ) : null}\n </div>\n {display.duration || display.direction || display.outcome ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n {display.duration ? <span data-slot=\"timeline-gong-duration\">{display.duration}</span> : null}\n {display.direction ? (\n <span className=\"rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground\">{display.direction}</span>\n ) : null}\n {display.outcome ? (\n <span className=\"rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground\">{display.outcome}</span>\n ) : null}\n </div>\n ) : null}\n </>\n )\n}\n\nfunction CollapsedGongCallPreview({\n display,\n className,\n actionClassName,\n onClick,\n}: {\n display: ReturnType<typeof getTimelineGongCallDisplay>\n className: string\n actionClassName: string\n onClick?: () => void\n}) {\n return (\n <div className={className} onClick={onClick}>\n <span className=\"line-clamp-1 min-w-0 flex-1 pr-3 text-[13px] text-muted-foreground\">\n {display.duration ? (\n <>\n <span className=\"inline-flex items-center gap-1\">\n <Phone className=\"h-3 w-3\" />\n {display.duration}\n </span>\n {display.snippet ? <span className=\"mx-1.5 text-muted-foreground/40\">&middot;</span> : null}\n </>\n ) : null}\n {display.snippet ? <span>{display.snippet}</span> : null}\n </span>\n <button type=\"button\" className={actionClassName}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )\n}\n\nfunction GongCallCard({\n event,\n expanded,\n setExpanded,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n const gongCall = event.gongCall as TimelineGongCall\n const hydrated = useHydrated()\n const display = getTimelineGongCallDisplay(gongCall, { utcTimestamps: !hydrated })\n\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant} data-slot=\"timeline-gong-card\">\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\",\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <GongCallHeader display={display} />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-0.5\"\n />\n </div>\n\n <TimelineGongCallBody\n display={display}\n labelClassName=\"text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/70\"\n />\n\n <div className=\"mt-2 flex items-center gap-3\">\n {display.url ? (\n <GongLinkAction\n url={display.url}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <CollapsedGongCallPreview\n display={display}\n className=\"flex items-center justify-between gap-2 text-muted-foreground\"\n actionClassName=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\"\n />\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant} data-slot=\"timeline-gong-card\">\n {expanded ? (\n <>\n <div className={classes.cardHeader} data-slot=\"timeline-card-header\">\n <div className=\"min-w-0 flex-1 normal-case tracking-normal\">\n <GongCallHeader display={display} />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"ml-3 normal-case tracking-normal\"\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineGongCallBody\n display={display}\n labelClassName=\"text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/70\"\n />\n </div>\n\n <div\n className={cn(classes.cardFooter, classes.actionLinkRow, display.url ? \"justify-between\" : \"justify-end\")}\n data-slot=\"timeline-card-footer\"\n >\n {display.url ? <GongLinkAction url={display.url} className={classes.actionLink} /> : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <CollapsedGongCallPreview\n display={display}\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n actionClassName={cn(classes.actionLink, \"shrink-0\")}\n onClick={() => setExpanded(true)}\n />\n )}\n </div>\n )\n}\n\nfunction EmailCard({\n event,\n expanded,\n setExpanded,\n showAllRecipients,\n setShowAllRecipients,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n showAllRecipients: boolean\n setShowAllRecipients: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded && event.email ? (\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"mt-0.5\"\n />\n </div>\n\n <TimelineEmailBody email={event.email} />\n\n {event.content ? (\n <div className=\"rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className=\"flex items-center justify-between gap-2 text-muted-foreground\"\n actionClassName=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\"\n />\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded && event.email ? (\n <>\n <div className={classes.cardHeader} data-slot=\"timeline-card-header\">\n <div className=\"min-w-0 flex-1 normal-case tracking-normal\">\n <EmailMetadata\n email={event.email}\n showAllRecipients={showAllRecipients}\n setShowAllRecipients={setShowAllRecipients}\n />\n </div>\n <CollapseTopButton\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className=\"ml-3 normal-case tracking-normal\"\n />\n </div>\n\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n <TimelineEmailBody email={event.email} />\n {event.content ? (\n <div className=\"mt-3 rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground\">\n {event.content}\n </div>\n ) : null}\n </div>\n\n <div\n className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")}\n data-slot=\"timeline-card-footer\"\n >\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setExpanded(false)\n }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <CollapsedEmailPreview\n email={event.email}\n preview={event.preview}\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n actionClassName={cn(classes.actionLink, \"shrink-0\")}\n onClick={() => setExpanded(true)}\n />\n )}\n </div>\n )\n}\n\nfunction ContentCard({\n event,\n expanded,\n setExpanded,\n variant,\n classes,\n}: {\n event: TimelineEvent\n expanded: boolean\n setExpanded: React.Dispatch<React.SetStateAction<boolean>>\n variant: TimelineActivityVariant\n classes: TimelineVariantClasses\n}) {\n if (variant === \"default\") {\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n <div\n className={cn(\n \"px-3 py-2.5 text-sm\",\n !expanded && \"cursor-pointer hover:bg-muted/30 transition-colors\"\n )}\n onClick={() => !expanded && setExpanded(true)}\n >\n {expanded ? (\n <div className=\"space-y-2\">\n {event.content}\n <div className=\"mt-2 flex items-center gap-3\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className=\"mr-auto inline-flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n ) : null}\n <ShowLessButton\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className=\"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground\"\n />\n </div>\n </div>\n ) : (\n <div className=\"flex items-center justify-between gap-2 text-muted-foreground\">\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button className=\"flex shrink-0 items-center gap-1 text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground\">\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className={classes.cardContainer} data-variant={variant}>\n {expanded ? (\n <>\n <div className={classes.cardBody} data-slot=\"timeline-card-body\">\n {event.content}\n </div>\n <div className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? \"justify-between\" : \"justify-end\")} data-slot=\"timeline-card-footer\">\n {event.source ? (\n <SourceAction\n source={event.source}\n onSourceClick={event.onSourceClick}\n className={classes.actionLink}\n />\n ) : null}\n <ShowLessButton\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); setExpanded(false); }}\n className={classes.actionLink}\n />\n </div>\n </>\n ) : (\n <div\n className={cn(classes.collapsedPreview, \"cursor-pointer hover:bg-muted/30 transition-colors\")}\n onClick={() => setExpanded(true)}\n >\n <span className=\"line-clamp-1 pr-3\">\n {event.preview ?? event.content}\n </span>\n <button type=\"button\" className={cn(classes.actionLink, \"shrink-0\")}>\n Expand <ChevronDown className=\"h-3 w-3\" />\n </button>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handled-ai/design-system",
3
- "version": "0.20.28",
3
+ "version": "0.20.29",
4
4
  "description": "Handled UI component library (shadcn-style, New York)",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@9.12.0",
@@ -76,4 +76,75 @@ describe("AccountContactsPopover", () => {
76
76
  fireEvent.click(screen.getByRole("button", { name: "Contacts" }))
77
77
  expect(screen.getByRole("button", { name: /replace alex admin/i })).toBeTruthy()
78
78
  })
79
+
80
+ // --- Last activity (WIT-1007) ---
81
+
82
+ const activityContacts: SuggestedContact[] = [
83
+ {
84
+ name: "Cara Customer",
85
+ role: "VP Ops",
86
+ email: "cara@example.com",
87
+ confirmed: true,
88
+ lastActivity: { date: "Jun 8, 2026", type: "email", timelineEventId: "evt-1" },
89
+ },
90
+ ]
91
+
92
+ it("calls onOpenRecentActivity with the contact when the activity chip is a clickable button", () => {
93
+ const onOpenRecentActivity = vi.fn()
94
+
95
+ render(
96
+ <AccountContactsPopover
97
+ contacts={activityContacts}
98
+ onSelect={vi.fn()}
99
+ onOpenRecentActivity={onOpenRecentActivity}
100
+ trigger={<button type="button">Contacts</button>}
101
+ />,
102
+ )
103
+
104
+ fireEvent.click(screen.getByRole("button", { name: "Contacts" }))
105
+ // The activity chip is a real <button> element wrapping the "Last activity" label.
106
+ const activityChip = screen.getByText("Last activity").closest("button")
107
+ expect(activityChip).not.toBeNull()
108
+ fireEvent.click(activityChip!)
109
+
110
+ expect(onOpenRecentActivity).toHaveBeenCalledTimes(1)
111
+ expect(onOpenRecentActivity).toHaveBeenCalledWith(activityContacts[0])
112
+ })
113
+
114
+ it("renders last activity as non-clickable text when no callback is provided", () => {
115
+ render(
116
+ <AccountContactsPopover
117
+ contacts={activityContacts}
118
+ onSelect={vi.fn()}
119
+ trigger={<button type="button">Contacts</button>}
120
+ />,
121
+ )
122
+
123
+ fireEvent.click(screen.getByRole("button", { name: "Contacts" }))
124
+ expect(screen.getByText("Jun 8, 2026")).toBeTruthy()
125
+ // The activity line is plain text (a <div>), not a clickable <button>.
126
+ expect(screen.getByText("Last activity").closest("button")).toBeNull()
127
+ })
128
+
129
+ it("renders last activity as non-clickable text when timelineEventId is missing even with a callback", () => {
130
+ const onOpenRecentActivity = vi.fn()
131
+
132
+ render(
133
+ <AccountContactsPopover
134
+ contacts={[
135
+ {
136
+ ...activityContacts[0],
137
+ lastActivity: { date: "Jun 8, 2026", type: "email" },
138
+ },
139
+ ]}
140
+ onSelect={vi.fn()}
141
+ onOpenRecentActivity={onOpenRecentActivity}
142
+ trigger={<button type="button">Contacts</button>}
143
+ />,
144
+ )
145
+
146
+ fireEvent.click(screen.getByRole("button", { name: "Contacts" }))
147
+ expect(screen.getByText("Jun 8, 2026")).toBeTruthy()
148
+ expect(screen.getByText("Last activity").closest("button")).toBeNull()
149
+ })
79
150
  })
@@ -513,4 +513,127 @@ describe("EmailRecipientField", () => {
513
513
  // trigger another search.
514
514
  expect(secondOnSearch).not.toHaveBeenCalled()
515
515
  })
516
+
517
+ // --- Last activity (WIT-1007) ---
518
+
519
+ const activityContact: SuggestedContact = {
520
+ name: "Cara Customer",
521
+ role: "VP Ops",
522
+ email: "cara@example.com",
523
+ confirmed: true,
524
+ lastActivity: { date: "Jun 8, 2026", type: "email", timelineEventId: "evt-1" },
525
+ }
526
+
527
+ it("renders last activity as non-clickable text when no onOpenRecentActivity callback is provided", () => {
528
+ render(
529
+ <EmailRecipientField
530
+ label="To"
531
+ recipients={[]}
532
+ onRecipientsChange={vi.fn()}
533
+ showPicker
534
+ contacts={[activityContact]}
535
+ />,
536
+ )
537
+
538
+ fireEvent.click(screen.getByRole("button", { name: /Contacts/ }))
539
+ expect(screen.getByText("Jun 8, 2026")).toBeTruthy()
540
+ // No clickable activity button exists.
541
+ expect(screen.queryByRole("button", { name: /Last activity/i })).toBeNull()
542
+ })
543
+
544
+ it("renders last activity as non-clickable text when timelineEventId is missing even with a callback", () => {
545
+ const onOpenRecentActivity = vi.fn()
546
+ render(
547
+ <EmailRecipientField
548
+ label="To"
549
+ recipients={[]}
550
+ onRecipientsChange={vi.fn()}
551
+ showPicker
552
+ contacts={[
553
+ {
554
+ ...activityContact,
555
+ lastActivity: { date: "Jun 8, 2026", type: "email" },
556
+ },
557
+ ]}
558
+ onOpenRecentActivity={onOpenRecentActivity}
559
+ />,
560
+ )
561
+
562
+ fireEvent.click(screen.getByRole("button", { name: /Contacts/ }))
563
+ expect(screen.getByText("Jun 8, 2026")).toBeTruthy()
564
+ expect(screen.queryByRole("button", { name: /Last activity/i })).toBeNull()
565
+ })
566
+
567
+ it("renders last activity as a clickable button only when callback and timelineEventId both exist", () => {
568
+ const onOpenRecentActivity = vi.fn()
569
+ render(
570
+ <EmailRecipientField
571
+ label="To"
572
+ recipients={[]}
573
+ onRecipientsChange={vi.fn()}
574
+ showPicker
575
+ contacts={[activityContact]}
576
+ onOpenRecentActivity={onOpenRecentActivity}
577
+ />,
578
+ )
579
+
580
+ fireEvent.click(screen.getByRole("button", { name: /Contacts/ }))
581
+ const activityButton = screen.getByRole("button", { name: /Last activity/i })
582
+ expect(activityButton.tagName).toBe("BUTTON")
583
+ })
584
+
585
+ it("invokes onOpenRecentActivity with the contact and does NOT add a recipient on activity click", () => {
586
+ const onOpenRecentActivity = vi.fn()
587
+ const onChange = vi.fn()
588
+ render(
589
+ <EmailRecipientField
590
+ label="To"
591
+ recipients={[]}
592
+ onRecipientsChange={onChange}
593
+ showPicker
594
+ contacts={[activityContact]}
595
+ onOpenRecentActivity={onOpenRecentActivity}
596
+ />,
597
+ )
598
+
599
+ fireEvent.click(screen.getByRole("button", { name: /Contacts/ }))
600
+ fireEvent.click(screen.getByRole("button", { name: /Last activity/i }))
601
+
602
+ expect(onOpenRecentActivity).toHaveBeenCalledTimes(1)
603
+ expect(onOpenRecentActivity).toHaveBeenCalledWith(activityContact)
604
+ // Clicking the activity must not select/add the recipient.
605
+ expect(onChange).not.toHaveBeenCalled()
606
+ })
607
+
608
+ it("keeps the last-activity button clickable for already-added contacts without adding the recipient", () => {
609
+ const onOpenRecentActivity = vi.fn()
610
+ const onChange = vi.fn()
611
+ render(
612
+ <EmailRecipientField
613
+ label="To"
614
+ recipients={[]}
615
+ onRecipientsChange={onChange}
616
+ showPicker
617
+ contacts={[activityContact]}
618
+ addedEmails={new Set([activityContact.email!.toLowerCase()])}
619
+ onOpenRecentActivity={onOpenRecentActivity}
620
+ />,
621
+ )
622
+
623
+ fireEvent.click(screen.getByRole("button", { name: /Contacts/ }))
624
+
625
+ // The row is disabled (already added) but its activity button stays clickable.
626
+ const option = screen
627
+ .getAllByRole("option")
628
+ .find((o) => o.textContent?.includes("Cara Customer"))!
629
+ expect(option.className).toContain("pointer-events-none")
630
+ const activityButton = within(option).getByRole("button", { name: /Last activity/i })
631
+ expect(activityButton.className).toContain("pointer-events-auto")
632
+
633
+ fireEvent.click(activityButton)
634
+
635
+ expect(onOpenRecentActivity).toHaveBeenCalledTimes(1)
636
+ expect(onOpenRecentActivity).toHaveBeenCalledWith(activityContact)
637
+ expect(onChange).not.toHaveBeenCalled()
638
+ })
516
639
  })