@handled-ai/design-system 0.20.9 → 0.20.10

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.
@@ -55,29 +55,34 @@ function CommentComposer({
55
55
  data-slot="comment-composer"
56
56
  data-open={open ? "true" : undefined}
57
57
  className={cn(
58
- "border-border bg-background flex items-start gap-2 rounded-lg border px-2 py-1.5 transition-colors",
59
- open && "ring-ring/30 ring-2",
58
+ "flex items-start gap-4 rounded-xl transition-colors",
60
59
  className
61
60
  )}
62
61
  >
63
- <Avatar size="sm" className="mt-px">
62
+ <Avatar size="sm" className="mt-1">
64
63
  {author?.avatarUrl ? <AvatarImage src={author.avatarUrl} alt={author.name ?? "You"} /> : null}
65
- <AvatarFallback className="bg-muted text-muted-foreground text-[10px] font-medium uppercase">
64
+ <AvatarFallback className="bg-slate-700 text-[10px] font-semibold uppercase text-white dark:bg-slate-200 dark:text-slate-900">
66
65
  {getInitials({ name: author?.name, email: author?.email })}
67
66
  </AvatarFallback>
68
67
  </Avatar>
69
68
 
70
- <div className="min-w-0 flex-1">
69
+ <div
70
+ data-slot="comment-composer-shell"
71
+ className={cn(
72
+ "min-w-0 flex-1 rounded-xl border border-border bg-background transition-[box-shadow,border-color]",
73
+ open ? "overflow-hidden shadow-sm" : "shadow-none"
74
+ )}
75
+ >
71
76
  <Textarea
72
77
  data-slot="comment-composer-input"
73
78
  value={text}
74
79
  onChange={(e) => setText(e.target.value)}
75
80
  onFocus={() => setFocused(true)}
76
81
  placeholder={placeholder}
77
- rows={open ? 3 : 1}
82
+ rows={open ? 4 : 1}
78
83
  className={cn(
79
- "resize-none border-0 bg-transparent px-1 py-0.5 text-sm leading-snug shadow-none focus-visible:ring-0",
80
- !open && "min-h-0"
84
+ "resize-none rounded-none border-0 bg-transparent px-5 py-4 text-[15px] leading-6 shadow-none outline-none placeholder:text-muted-foreground/60 focus-visible:ring-0 focus-visible:ring-offset-0",
85
+ open ? "min-h-32" : "min-h-14"
81
86
  )}
82
87
  onKeyDown={(e) => {
83
88
  if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
@@ -88,15 +93,16 @@ function CommentComposer({
88
93
  />
89
94
 
90
95
  {open ? (
91
- <div className="mt-0.5 flex items-center justify-between gap-2">
92
- <span className="text-muted-foreground inline-flex items-center gap-1 text-[11px]">
93
- <Lock size={12} /> {hint}
96
+ <div className="flex items-center justify-between gap-3 border-t border-border bg-muted/10 px-5 py-4">
97
+ <span className="inline-flex items-center gap-2 text-sm text-muted-foreground">
98
+ <Lock size={16} strokeWidth={1.75} /> {hint}
94
99
  </span>
95
- <span className="flex items-center gap-2">
100
+ <span className="flex items-center gap-3">
96
101
  <Button
97
102
  type="button"
98
103
  variant="ghost"
99
104
  size="sm"
105
+ className="px-2 text-sm font-medium text-muted-foreground hover:bg-transparent hover:text-foreground"
100
106
  onClick={() => {
101
107
  setText("")
102
108
  setFocused(false)
@@ -104,9 +110,15 @@ function CommentComposer({
104
110
  >
105
111
  Cancel
106
112
  </Button>
107
- <Button type="button" size="sm" disabled={!canPost} onClick={post}>
113
+ <Button
114
+ type="button"
115
+ size="sm"
116
+ disabled={!canPost}
117
+ onClick={post}
118
+ className="rounded-lg bg-foreground px-4 text-sm font-semibold text-background shadow-none hover:bg-foreground/90"
119
+ >
108
120
  Comment
109
- <kbd className="bg-primary-foreground/15 ml-1 rounded px-1 text-[10px]">⌘↵</kbd>
121
+ <kbd className="ml-1 rounded px-1 text-[10px] text-background/70">⌘↵</kbd>
110
122
  </Button>
111
123
  </span>
112
124
  </div>
@@ -298,34 +298,19 @@ function PersonAvatar({ person, size = "sm" }: { person: ConvParticipant; size?:
298
298
  }
299
299
 
300
300
  const STATUS_PILL: Record<ConvStatus, { label: string; cls: string }> = {
301
- responded: { label: "NEW REPLY", cls: "bg-status-warning-bg text-status-warning-fg border-status-warning-border" },
301
+ responded: { label: "New reply", cls: "bg-status-active-bg text-status-active-fg border-status-active-border" },
302
302
  draft: { label: "Draft", cls: "bg-background text-foreground/80 border-border" },
303
- awaiting: { label: "SENT", cls: "bg-status-info-bg text-status-info-fg border-status-info-border" },
303
+ awaiting: { label: "Awaiting", cls: "bg-status-pending-bg text-status-pending-fg border-status-pending-border" },
304
304
  viewing: { label: "Viewing", cls: "bg-muted text-muted-foreground border-border" },
305
305
  }
306
306
 
307
307
  const STATUS_DOT: Record<ConvStatus, string> = {
308
- responded: "bg-status-warning-fg",
308
+ responded: "bg-status-active-fg",
309
309
  draft: "bg-status-pending-fg",
310
- awaiting: "bg-status-info-fg",
310
+ awaiting: "bg-status-pending-fg",
311
311
  viewing: "bg-muted-foreground/50",
312
312
  }
313
313
 
314
- const THREAD_ROW_ACCENT: Record<ConvStatus, string> = {
315
- responded: "border-l-4 border-l-status-warning-border bg-status-warning-bg/25",
316
- awaiting: "border-l-4 border-l-status-info-border bg-status-info-bg/25",
317
- draft: "border-l-4 border-l-status-pending-border bg-status-pending-bg/20",
318
- viewing: "",
319
- }
320
-
321
- const RECEIPT_CHIP: Record<NonNullable<ConvMessage["receipt"]>["kind"], string> = {
322
- new: "border-status-warning-border bg-status-warning-bg text-status-warning-fg",
323
- read: "border-status-info-border bg-status-info-bg text-status-info-fg",
324
- opened: "border-status-info-border bg-status-info-bg text-status-info-fg",
325
- sent: "border-status-info-border bg-status-info-bg text-status-info-fg",
326
- draft: "border-status-pending-border bg-status-pending-bg text-status-pending-fg",
327
- }
328
-
329
314
  function effectiveStatus(t: ConversationThread): ConvStatus {
330
315
  return t.canReply === false ? "viewing" : t.status
331
316
  }
@@ -447,10 +432,10 @@ function MessageView({
447
432
  </span>
448
433
  <span className="flex shrink-0 items-center gap-2">
449
434
  {message.receipt ? (
450
- <span className={cn("inline-flex items-center gap-1 rounded-md border px-1.5 py-px text-[10px] font-semibold leading-4", RECEIPT_CHIP[message.receipt.kind])}>
435
+ <span className="text-muted-foreground inline-flex items-center gap-1 text-[11px]">
451
436
  {message.receipt.kind === "new" ? (
452
437
  <CornerUpLeft size={11} />
453
- ) : message.receipt.kind === "read" || message.receipt.kind === "sent" ? (
438
+ ) : message.receipt.kind === "read" ? (
454
439
  <CheckCheck size={11} />
455
440
  ) : message.receipt.kind === "draft" ? (
456
441
  <FilePenLine size={11} />
@@ -826,10 +811,10 @@ function ThreadBody({
826
811
  return (
827
812
  <div data-slot="conv-thread-body" className="space-y-2">
828
813
  {canReply && thread.paused ? (
829
- <div className="border-status-warning-border bg-status-warning-bg text-status-warning-fg flex items-start gap-2 rounded-md border border-l-4 p-2.5 text-[12px]">
814
+ <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]">
830
815
  <Pause size={13} className="mt-0.5 shrink-0" />
831
816
  <span>
832
- <b>Playbook stopped.</b> Follow-up actions for {thread.paused.playbook} won’t send
817
+ <b>Follow-up actions stopped.</b> Your {thread.paused.playbook} next steps won’t send
833
818
  automatically while this conversation is live. Continue it in {tenantName ?? "the app"} or Gmail.
834
819
  </span>
835
820
  </div>
@@ -945,12 +930,7 @@ function ThreadRow({
945
930
  const pill = STATUS_PILL[status]
946
931
 
947
932
  return (
948
- <div
949
- data-slot="conv-thread"
950
- data-status={status}
951
- data-open={open ? "true" : undefined}
952
- className={cn("border-border border-b last:border-b-0", THREAD_ROW_ACCENT[status])}
953
- >
933
+ <div data-slot="conv-thread" data-open={open ? "true" : undefined} className="border-border border-b last:border-b-0">
954
934
  <button
955
935
  type="button"
956
936
  onClick={onToggleOpen}
@@ -1011,13 +991,11 @@ function ConversationPanel({
1011
991
  const draft = threads.filter((t) => effectiveStatus(t) === "draft").length
1012
992
  const awaiting = threads.filter((t) => effectiveStatus(t) === "awaiting").length
1013
993
  const anyPaused = threads.some((t) => t.paused)
1014
- const hubGmailThread = threads.find((t) => canOpenInGmail(t, onOpenInGmail))
1015
- const firstAwaiting = threads.find((t) => effectiveStatus(t) === "awaiting")
1016
994
 
1017
995
  const [hubOpen, setHubOpen] = React.useState(true)
1018
996
  const [openId, setOpenId] = React.useState<string | null>(() => {
1019
997
  if (defaultOpenThreadId) return defaultOpenThreadId
1020
- const firstActionable = threads.find((t) => ["responded", "draft", "awaiting"].includes(t.status) && t.canReply !== false)
998
+ const firstActionable = threads.find((t) => ["responded", "draft"].includes(t.status) && t.canReply !== false)
1021
999
  return firstActionable ? firstActionable.threadId : null
1022
1000
  })
1023
1001
 
@@ -1026,11 +1004,11 @@ function ConversationPanel({
1026
1004
  // Header badge state: a responded reply leads, then drafts to finish, then sent mail awaiting a reply.
1027
1005
  const badge =
1028
1006
  responded > 0
1029
- ? { label: "Email response detected", dot: "bg-status-warning-fg", ring: "bg-status-warning-fg/30" }
1007
+ ? { label: "Email response detected", dot: "bg-status-active-fg", ring: "bg-status-active-fg/30" }
1030
1008
  : draft > 0
1031
1009
  ? { label: "Draft ready", dot: "bg-status-pending-fg", ring: "bg-status-pending-fg/30" }
1032
1010
  : awaiting > 0
1033
- ? { label: "Email sent · awaiting reply", dot: "bg-status-info-fg", ring: "bg-status-info-fg/30" }
1011
+ ? { label: "Awaiting response", dot: "bg-status-pending-fg", ring: "bg-status-pending-fg/30" }
1034
1012
  : { label: "Conversations", dot: "bg-muted-foreground/50", ring: "bg-muted-foreground/20" }
1035
1013
 
1036
1014
  const headTitle =
@@ -1039,56 +1017,30 @@ function ConversationPanel({
1039
1017
  : draft > 0
1040
1018
  ? `Draft ready on ${draft} ${draft === 1 ? "thread" : "threads"}`
1041
1019
  : awaiting > 0
1042
- ? awaiting === 1 && firstAwaiting
1043
- ? `Awaiting a response from ${firstName(displayParticipant(firstAwaiting.contact).name)}`
1044
- : `Awaiting responses on ${awaiting} threads`
1020
+ ? `Awaiting response on ${awaiting} ${awaiting === 1 ? "thread" : "threads"}`
1045
1021
  : `${threads.length} email ${threads.length === 1 ? "thread" : "threads"}`
1046
1022
 
1047
- const panelState = responded > 0 ? "responded" : draft > 0 ? "draft" : awaiting > 0 ? "awaiting" : "viewing"
1048
-
1049
1023
  return (
1050
1024
  <section
1051
1025
  data-slot="conversation-panel"
1052
1026
  data-responded={responded > 0 ? "true" : undefined}
1053
- data-state={panelState}
1054
- className={cn(
1055
- "bg-background overflow-hidden rounded-xl border",
1056
- panelState === "responded"
1057
- ? "border-status-warning-border"
1058
- : panelState === "awaiting"
1059
- ? "border-status-info-border"
1060
- : "border-border",
1061
- className,
1062
- )}
1027
+ className={cn("border-border bg-background overflow-hidden rounded-xl border", className)}
1063
1028
  >
1064
- <div
1065
- data-slot="conversation-panel-header"
1066
- className={cn(
1067
- "flex w-full items-center gap-2 px-3 py-2.5",
1068
- panelState === "responded"
1069
- ? "bg-status-warning-bg/45"
1070
- : panelState === "awaiting"
1071
- ? "bg-status-info-bg/55"
1072
- : "bg-background",
1073
- )}
1029
+ <button
1030
+ type="button"
1031
+ onClick={() => setHubOpen((v) => !v)}
1032
+ aria-expanded={hubOpen}
1033
+ className="flex w-full items-center gap-3 px-3 py-2.5 text-left"
1074
1034
  >
1075
- <button
1076
- type="button"
1077
- onClick={() => setHubOpen((v) => !v)}
1078
- aria-expanded={hubOpen}
1079
- className="flex min-w-0 flex-1 items-center gap-3 text-left"
1080
- >
1081
1035
  <span
1082
1036
  data-slot="conversation-badge"
1083
1037
  className={cn(
1084
1038
  "inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-semibold",
1085
1039
  responded > 0
1086
- ? "bg-status-warning-bg text-status-warning-fg"
1087
- : awaiting > 0
1088
- ? "bg-status-info-bg text-status-info-fg"
1089
- : draft > 0
1090
- ? "bg-status-pending-bg text-status-pending-fg"
1091
- : "bg-muted text-muted-foreground"
1040
+ ? "bg-status-active-bg text-status-active-fg"
1041
+ : draft > 0 || awaiting > 0
1042
+ ? "bg-status-pending-bg text-status-pending-fg"
1043
+ : "bg-muted text-muted-foreground"
1092
1044
  )}
1093
1045
  >
1094
1046
  <span className="relative inline-flex size-2">
@@ -1104,18 +1056,12 @@ function ConversationPanel({
1104
1056
  {anyPaused ? <> · <b>playbook stopped</b></> : null}
1105
1057
  </span>
1106
1058
  </span>
1107
- {hubOpen ? (
1108
- <ChevronUp size={16} className="text-muted-foreground shrink-0" />
1109
- ) : (
1110
- <ChevronDown size={16} className="text-muted-foreground shrink-0" />
1111
- )}
1112
- </button>
1113
- {hubGmailThread ? (
1114
- <div className="shrink-0" onClick={(event) => event.stopPropagation()}>
1115
- <OpenInGmailButton thread={hubGmailThread} onOpenInGmail={onOpenInGmail} />
1116
- </div>
1117
- ) : null}
1118
- </div>
1059
+ {hubOpen ? (
1060
+ <ChevronUp size={16} className="text-muted-foreground shrink-0" />
1061
+ ) : (
1062
+ <ChevronDown size={16} className="text-muted-foreground shrink-0" />
1063
+ )}
1064
+ </button>
1119
1065
 
1120
1066
  {hubOpen ? (
1121
1067
  <div className="border-border border-t">
@@ -58,7 +58,6 @@ export interface TimelineEvent {
58
58
  content?: React.ReactNode
59
59
  source?: {
60
60
  label: string
61
- actionLabel?: string
62
61
  url: string
63
62
  }
64
63
  defaultExpanded?: boolean
@@ -471,8 +470,6 @@ function SourceAction({
471
470
  onSourceClick?: () => void
472
471
  className: string
473
472
  }) {
474
- const actionLabel = source.actionLabel ?? `Open in ${source.label}`
475
-
476
473
  if (onSourceClick) {
477
474
  return (
478
475
  <button
@@ -480,7 +477,7 @@ function SourceAction({
480
477
  onClick={(e) => { e.stopPropagation(); onSourceClick(); }}
481
478
  className={className}
482
479
  >
483
- {actionLabel}
480
+ Open in {source.label}
484
481
  <ExternalLink className="h-3 w-3" />
485
482
  </button>
486
483
  )
@@ -493,7 +490,7 @@ function SourceAction({
493
490
  rel="noreferrer noopener"
494
491
  className={className}
495
492
  >
496
- {actionLabel}
493
+ Open in {source.label}
497
494
  <ExternalLink className="h-3 w-3" />
498
495
  </a>
499
496
  )
@@ -538,28 +535,13 @@ function EmailCard({
538
535
 
539
536
  <TimelineEmailBody email={event.email} />
540
537
 
541
- {event.content ? (
542
- <div className="rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground">
543
- {event.content}
544
- </div>
545
- ) : null}
546
-
547
- <div className="mt-2 flex items-center gap-3">
548
- {event.source ? (
549
- <SourceAction
550
- source={event.source}
551
- onSourceClick={event.onSourceClick}
552
- 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"
553
- />
554
- ) : null}
555
- <ShowLessButton
556
- onClick={(e) => {
557
- e.stopPropagation()
558
- setExpanded(false)
559
- }}
560
- className="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground"
561
- />
562
- </div>
538
+ <ShowLessButton
539
+ onClick={(e) => {
540
+ e.stopPropagation()
541
+ setExpanded(false)
542
+ }}
543
+ className="mt-2 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground transition-colors hover:text-foreground"
544
+ />
563
545
  </div>
564
546
  ) : (
565
547
  <CollapsedEmailPreview
@@ -588,24 +570,9 @@ function EmailCard({
588
570
 
589
571
  <div className={classes.cardBody} data-slot="timeline-card-body">
590
572
  <TimelineEmailBody email={event.email} />
591
- {event.content ? (
592
- <div className="mt-3 rounded-md bg-muted/30 px-2.5 py-2 text-xs text-muted-foreground">
593
- {event.content}
594
- </div>
595
- ) : null}
596
573
  </div>
597
574
 
598
- <div
599
- className={cn(classes.cardFooter, classes.actionLinkRow, event.source ? "justify-between" : "justify-end")}
600
- data-slot="timeline-card-footer"
601
- >
602
- {event.source ? (
603
- <SourceAction
604
- source={event.source}
605
- onSourceClick={event.onSourceClick}
606
- className={classes.actionLink}
607
- />
608
- ) : null}
575
+ <div className={cn(classes.cardFooter, classes.actionLinkRow)} data-slot="timeline-card-footer">
609
576
  <ShowLessButton
610
577
  type="button"
611
578
  onClick={(e) => {
@@ -101,7 +101,7 @@ describe("DetailView case-panel-v2 section layout", () => {
101
101
  screen.getByText("Cash movement"),
102
102
  screen.getByText("Approve action"),
103
103
  screen.getByText("After-score marker"),
104
- screen.getByText("Activity timeline"),
104
+ screen.getByText(/activity timeline/i),
105
105
  screen.getByText("Legacy detail extra marker"),
106
106
  )
107
107
  })
@@ -125,7 +125,7 @@ describe("DetailView case-panel-v2 section layout", () => {
125
125
  screen.getByText("Opportunity marker"),
126
126
  screen.getByText("Primary action marker"),
127
127
  screen.getByText("Comment area marker"),
128
- screen.getByText("Activity timeline"),
128
+ screen.getByText(/activity timeline/i),
129
129
  )
130
130
  })
131
131
 
@@ -148,7 +148,7 @@ describe("DetailView case-panel-v2 section layout", () => {
148
148
 
149
149
  expectInDocumentOrder(
150
150
  screen.getByText("Comment composer marker"),
151
- screen.getByText("Activity timeline"),
151
+ screen.getByText(/activity timeline/i),
152
152
  )
153
153
  })
154
154
 
@@ -209,20 +209,22 @@ describe("DetailView timeline system-events toggle", () => {
209
209
  const badge = container.querySelector('[data-testid="hidden-count-badge"]')
210
210
  expect(badge).not.toBeNull()
211
211
  expect(badge?.textContent).toBe("2")
212
- expect(badge).toHaveClass("min-w-[18px]")
212
+ expect(badge).toHaveClass("min-w-[22px]")
213
213
  })
214
214
 
215
- it("calls localStorage.setItem when toggle changes and shows a stronger pressed style", () => {
215
+ it("calls localStorage.setItem when toggle changes and shows the active pill style", () => {
216
216
  const { container } = render(<DetailView {...baseProps()} />)
217
217
  expandTimeline(container)
218
218
  const toggle = container.querySelector(
219
219
  '[data-testid="system-events-toggle"]',
220
220
  ) as HTMLElement
221
221
  expect(toggle).toHaveAttribute("aria-pressed", "false")
222
+ expect(toggle).toHaveAttribute("title", "Score changes are hidden.")
222
223
  fireEvent.click(toggle)
223
224
  expect(toggle).toHaveAttribute("aria-pressed", "true")
224
- expect(toggle.className).toContain("border-primary/40")
225
- expect(toggle.className).toContain("bg-primary/10")
225
+ expect(toggle).toHaveClass("border-foreground")
226
+ expect(toggle).toHaveClass("bg-foreground")
227
+ expect(toggle).toHaveAttribute("title", "Showing 2 score changes.")
226
228
  expect(localStorageMock.setItem).toHaveBeenCalledWith(
227
229
  "test-show-score-changes",
228
230
  "true",
@@ -312,18 +314,19 @@ describe("DetailView timeline system-events toggle", () => {
312
314
  expect(toggle).toBeNull()
313
315
  })
314
316
 
315
- it("shows footer hint below the case-panel timeline when timeline is expanded and system events are hidden", () => {
317
+ it("does not render a footer hint and uses the hidden hint as toggle help", () => {
316
318
  const { container } = render(<DetailView {...baseProps()} />)
317
319
  expandTimeline(container)
318
320
  const timeline = container.querySelector('[data-variant="case-panel"]')
319
321
  const hint = container.querySelector('[data-testid="timeline-footer-hint"]')
322
+ const toggle = container.querySelector('[data-testid="system-events-toggle"]')
320
323
  expect(timeline).not.toBeNull()
321
- expect(hint).not.toBeNull()
322
- expect(hint?.textContent).toBe("Score changes are hidden.")
323
- expect(timeline?.compareDocumentPosition(hint as Node)).toBe(Node.DOCUMENT_POSITION_FOLLOWING)
324
+ expect(hint).toBeNull()
325
+ expect(toggle).toHaveAttribute("title", "Score changes are hidden.")
326
+ expect(toggle).toHaveAttribute("aria-label", "Score changes are hidden.")
324
327
  })
325
328
 
326
- it("shows visible footer hint with count when system events are shown", () => {
329
+ it("uses visible footer hint text as toggle help with count when system events are shown", () => {
327
330
  const { container } = render(<DetailView {...baseProps()} />)
328
331
  expandTimeline(container)
329
332
  // Toggle on
@@ -332,8 +335,8 @@ describe("DetailView timeline system-events toggle", () => {
332
335
  ) as HTMLElement
333
336
  fireEvent.click(toggle)
334
337
  const hint = container.querySelector('[data-testid="timeline-footer-hint"]')
335
- expect(hint).not.toBeNull()
336
- expect(hint?.textContent).toBe("Showing 2 score changes.")
338
+ expect(hint).toBeNull()
339
+ expect(toggle).toHaveAttribute("title", "Showing 2 score changes.")
337
340
  })
338
341
 
339
342
  // --- Toggle always renders when system-noise events exist (review fix #1) ---
@@ -414,10 +417,10 @@ describe("DetailView timeline system-events toggle", () => {
414
417
  const toggle = container.querySelector('[data-testid="system-events-toggle"]')
415
418
  expect(toggle).not.toBeNull()
416
419
  expect(toggle?.textContent).toContain("Legacy label")
417
- // Footer hint should work too
420
+ // Deprecated hint props are accepted and exposed as toggle help.
418
421
  expandTimeline(container)
419
422
  const hint = container.querySelector('[data-testid="timeline-footer-hint"]')
420
- expect(hint).not.toBeNull()
421
- expect(hint?.textContent).toBe("Legacy hidden hint.")
423
+ expect(hint).toBeNull()
424
+ expect(toggle).toHaveAttribute("title", "Legacy hidden hint.")
422
425
  })
423
426
  })