@handled-ai/design-system 0.19.2 → 0.20.1

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.
@@ -0,0 +1,97 @@
1
+ import { cleanup, render, screen } from "@testing-library/react"
2
+ import { afterEach, describe, expect, it } from "vitest"
3
+
4
+ import { DetailView } from "../prototype-inbox-view"
5
+ import type { DetailViewProps } from "../prototype-inbox-view"
6
+ import type { SignalScoreData } from "../prototype-config"
7
+
8
+ const baseItem = {
9
+ id: "case-1",
10
+ title: "Churn risk case title",
11
+ details: "Case details",
12
+ statusColor: "red",
13
+ time: "1h ago",
14
+ company: "Acme Corp",
15
+ tag1: "Signal",
16
+ }
17
+
18
+ afterEach(() => {
19
+ cleanup()
20
+ })
21
+
22
+ function renderDetailView(
23
+ overrides: Partial<DetailViewProps> = {},
24
+ scoreOverrides: Partial<SignalScoreData> = {},
25
+ ) {
26
+ const props: DetailViewProps = {
27
+ item: baseItem,
28
+ sections: { signalBrief: true, suggestedActions: false, timeline: false },
29
+ getSignalScore: () => ({
30
+ score: 72,
31
+ factors: [],
32
+ whyNow: "Why now",
33
+ evidence: [],
34
+ confidence: 80,
35
+ urgencyLabel: "High",
36
+ urgencyExplanation: "High priority",
37
+ signalBrief: "BRIEF BODY TEXT",
38
+ timeChipLabel: "3 days left",
39
+ ...scoreOverrides,
40
+ }),
41
+ buildSuggestedActions: () => [],
42
+ buildSourceItems: () => [],
43
+ accountContacts: [],
44
+ emailSignature: "",
45
+ iconMap: {},
46
+ renderMetadataExtra: () => <span data-testid="metadata-extra">Owner: Lee</span>,
47
+ ...overrides,
48
+ }
49
+
50
+ return render(<DetailView {...props} />)
51
+ }
52
+
53
+ const PRECEDES = Node.DOCUMENT_POSITION_PRECEDING
54
+ const FOLLOWS = Node.DOCUMENT_POSITION_FOLLOWING
55
+
56
+ describe("DetailView metadataLayout", () => {
57
+ it("renders the metadata chips above the brief by default", () => {
58
+ renderDetailView()
59
+
60
+ const brief = screen.getByText("BRIEF BODY TEXT")
61
+ const meta = screen.getByTestId("metadata-extra")
62
+ // metadata-extra comes before the brief body in document order
63
+ expect(brief.compareDocumentPosition(meta) & PRECEDES).toBeTruthy()
64
+ expect(screen.getByTestId("priority-popover-trigger")).toBeTruthy()
65
+ })
66
+
67
+ it("relocates the metadata chips below the brief when metadataLayout='below-brief'", () => {
68
+ renderDetailView({ metadataLayout: "below-brief" })
69
+
70
+ const brief = screen.getByText("BRIEF BODY TEXT")
71
+ const meta = screen.getByTestId("metadata-extra")
72
+ // metadata-extra now follows the brief body
73
+ expect(brief.compareDocumentPosition(meta) & FOLLOWS).toBeTruthy()
74
+ // chips themselves (priority + account + deadline) render exactly once
75
+ expect(screen.getAllByTestId("priority-popover-trigger")).toHaveLength(1)
76
+ expect(screen.getByText("3 days left")).toBeTruthy()
77
+ })
78
+ })
79
+
80
+ describe("DetailView deadline chip tone", () => {
81
+ it("applies the red tone when timeChipTone='red'", () => {
82
+ renderDetailView({}, { timeChipTone: "red" })
83
+ expect(screen.getByText("3 days left").className).toContain("text-red-700")
84
+ })
85
+
86
+ it("applies the amber tone when timeChipTone='amber'", () => {
87
+ renderDetailView({}, { timeChipTone: "amber" })
88
+ expect(screen.getByText("3 days left").className).toContain("text-amber-700")
89
+ })
90
+
91
+ it("uses a neutral tone when timeChipTone is omitted", () => {
92
+ renderDetailView()
93
+ const chip = screen.getByText("3 days left")
94
+ expect(chip.className).not.toContain("text-red-700")
95
+ expect(chip.className).not.toContain("text-amber-700")
96
+ })
97
+ })
@@ -1,5 +1,5 @@
1
- import { render, screen } from "@testing-library/react"
2
- import { describe, expect, it, vi } from "vitest"
1
+ import { cleanup, render, screen } from "@testing-library/react"
2
+ import { afterEach, describe, expect, it, vi } from "vitest"
3
3
 
4
4
  import { DetailView } from "../prototype-inbox-view"
5
5
  import type { DetailViewProps } from "../prototype-inbox-view"
@@ -14,6 +14,10 @@ const baseItem = {
14
14
  tag1: "Signal",
15
15
  }
16
16
 
17
+ afterEach(() => {
18
+ cleanup()
19
+ })
20
+
17
21
  function renderDetailView(overrides: Partial<DetailViewProps> = {}) {
18
22
  const props: DetailViewProps = {
19
23
  item: baseItem,
@@ -39,6 +43,30 @@ function renderDetailView(overrides: Partial<DetailViewProps> = {}) {
39
43
  }
40
44
 
41
45
  describe("DetailView title slots", () => {
46
+ it("does not render the inert detail Back button while preserving title and metadata slots", () => {
47
+ renderDetailView({
48
+ renderTitleActionRow: () => (
49
+ <button type="button">Full-width quick action</button>
50
+ ),
51
+ renderMetadataExtra: () => <span data-testid="metadata-extra">Owner: Lee</span>,
52
+ accountDetailsButtonLabel: (item) => (
53
+ <>
54
+ <span>View account details</span>
55
+ <span>{item.company}</span>
56
+ </>
57
+ ),
58
+ getAccountDetailsButtonAriaLabel: (item) => `View account details for ${item.company}`,
59
+ })
60
+
61
+ expect(screen.queryByRole("button", { name: "Back" })).toBeNull()
62
+ expect(screen.getByRole("heading", { name: "Churn risk case title" })).toBeTruthy()
63
+ expect(screen.getAllByText("Acme Corp").length).toBeGreaterThan(0)
64
+ expect(screen.getByRole("button", { name: "Full-width quick action" })).toBeTruthy()
65
+ expect(screen.getByTestId("metadata-extra").textContent).toBe("Owner: Lee")
66
+ expect(screen.getByTestId("priority-popover-trigger")).toBeTruthy()
67
+ expect(screen.getByRole("button", { name: "View account details for Acme Corp" })).toBeTruthy()
68
+ })
69
+
42
70
  it("renders supporting title text below the main title", () => {
43
71
  renderDetailView({
44
72
  renderTitleSubtext: (item) => <p data-testid="title-subtext">Full title: {item.title}</p>,
@@ -142,6 +142,11 @@ export interface SignalScoreData {
142
142
  timeChipLabel?: string
143
143
  /** Expanded detail for time chip popover (e.g., "Day 2 of 14 · Event detected on May 1"). */
144
144
  timeChipDetail?: string
145
+ /**
146
+ * Visual urgency tone for the deadline chip. The consumer computes this from the live deadline
147
+ * (e.g. amber when a few days remain, red when due/overdue). Defaults to neutral when omitted.
148
+ */
149
+ timeChipTone?: "neutral" | "amber" | "red"
145
150
  }
146
151
 
147
152
  // ---------------------------------------------------------------------------
@@ -219,12 +224,18 @@ export interface InboxViewConfig {
219
224
  * - "prominent": standard foreground color, text-base size for the brief
220
225
  */
221
226
  briefStyleVariant?: BriefStyleVariant
227
+ /** Opt-in detail panel section ordering. Defaults to the existing layout. */
228
+ sectionLayout?: "default" | "case-panel-v2"
222
229
  /** Render extra content at the end of the detail view, below the suggested actions section. */
223
230
  renderDetailExtra?: (item: QueueItem) => React.ReactNode
224
231
  /** Render content between the signal brief text and the signal score bar (e.g. "Signals on Case" chips). */
225
232
  renderBeforeScore?: (item: QueueItem) => React.ReactNode
226
233
  /** Render content between the signal score section and the activity timeline (e.g. OpportunityPanel). */
227
234
  renderAfterScore?: (item: QueueItem) => React.ReactNode
235
+ /** Render primary case-panel content between the opportunity section and comment area. */
236
+ renderPrimaryAction?: (item: QueueItem) => React.ReactNode
237
+ /** Render case-panel comment content before the activity timeline. */
238
+ renderCommentArea?: (item: QueueItem) => React.ReactNode
228
239
  /** Formatted string for "Last activity X ago" in the collapsed timeline header. If omitted, falls back to the first event's time. */
229
240
  lastActivityTime?: string
230
241
  /** Configuration for the system-noise events toggle (score changes, etc.). */
@@ -155,11 +155,17 @@ export interface DetailViewProps {
155
155
  hideApproveButton?: boolean
156
156
  signalBriefCopy?: InboxViewConfig["signalBriefCopy"]
157
157
  briefStyleVariant?: BriefStyleVariant
158
+ /** Opt-in detail panel section ordering. Defaults to the existing layout. */
159
+ sectionLayout?: InboxViewConfig["sectionLayout"]
158
160
  renderDetailExtra?: (item: QueueItem) => React.ReactNode
159
161
  /** Render content between the signal brief text and the signal score bar (e.g. "Signals on Case" chips). */
160
162
  renderBeforeScore?: (item: QueueItem) => React.ReactNode
161
163
  /** Render content between the signal score section and the activity timeline. */
162
164
  renderAfterScore?: (item: QueueItem) => React.ReactNode
165
+ /** Render primary case-panel content between the opportunity section and comment area. */
166
+ renderPrimaryAction?: (item: QueueItem) => React.ReactNode
167
+ /** Render case-panel comment content before the activity timeline. */
168
+ renderCommentArea?: (item: QueueItem) => React.ReactNode
163
169
  lastActivityTime?: string
164
170
  /** Render extra content inline with the detail title. */
165
171
  renderTitleExtra?: (item: QueueItem) => React.ReactNode
@@ -169,6 +175,13 @@ export interface DetailViewProps {
169
175
  renderTitleSubtext?: (item: QueueItem) => React.ReactNode
170
176
  /** Render extra metadata chips (e.g. assignee) inside the chips row below the title. */
171
177
  renderMetadataExtra?: (item: QueueItem) => React.ReactNode
178
+ /**
179
+ * Where the metadata chips row (priority · deadline · account · renderMetadataExtra) sits.
180
+ * - "default": above the signal brief (legacy layout, unchanged for existing consumers).
181
+ * - "below-brief": moved beneath the brief text, just before `renderBeforeScore`
182
+ * (case-panel redesign — chips + owners read as one block under the brief).
183
+ */
184
+ metadataLayout?: "default" | "below-brief"
172
185
  /** Override the built-in account details metadata button label. */
173
186
  accountDetailsButtonLabel?: (item: QueueItem) => React.ReactNode
174
187
  /** Accessible label for the built-in account details metadata button. */
@@ -344,14 +357,18 @@ export function DetailView({
344
357
  hideApproveButton,
345
358
  signalBriefCopy,
346
359
  briefStyleVariant = "default",
360
+ sectionLayout = "default",
347
361
  renderDetailExtra,
348
362
  renderBeforeScore,
349
363
  renderAfterScore,
364
+ renderPrimaryAction,
365
+ renderCommentArea,
350
366
  lastActivityTime,
351
367
  renderTitleExtra,
352
368
  renderTitleActionRow,
353
369
  renderTitleSubtext,
354
370
  renderMetadataExtra,
371
+ metadataLayout = "default",
355
372
  accountDetailsButtonLabel,
356
373
  getAccountDetailsButtonAriaLabel,
357
374
  onOpenSignalBucket,
@@ -465,6 +482,121 @@ export function DetailView({
465
482
  [suggestedActions],
466
483
  )
467
484
 
485
+ // Deadline chip tone (case-panel redesign): amber as the deadline nears, red when due/overdue.
486
+ const timeChipToneClass =
487
+ signalData.timeChipTone === "red"
488
+ ? "border-red-300 bg-red-50 text-red-700 hover:bg-red-50"
489
+ : signalData.timeChipTone === "amber"
490
+ ? "border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-50"
491
+ : "hover:bg-muted/50"
492
+
493
+ const isCasePanelV2 = sectionLayout === "case-panel-v2"
494
+ const v2WhyText = signalData.signalBrief
495
+ ? signalData.whyNow || signalData.urgencyExplanation
496
+ : undefined
497
+
498
+ // The metadata chips row (priority · deadline · account · renderMetadataExtra). Rendered above
499
+ // the brief by default, or beneath it when `metadataLayout === "below-brief"` (case-panel redesign).
500
+ const metadataChips = (
501
+ <>
502
+ <SignalPriorityPopover
503
+ score={signalData.score}
504
+ urgencyLabel={signalData.urgencyLabel}
505
+ urgencyExplanation={signalData.urgencyExplanation ?? signalData.signalBrief}
506
+ scoreDisplay={signalData.priorityScoreDisplay}
507
+ formulaLabel={signalData.priorityFormulaLabel}
508
+ factors={signalData.priorityFactors ?? []}
509
+ metaText={undefined}
510
+ feedbackChips={signalData.priorityFeedbackChips}
511
+ onFeedbackSubmit={signalData.onPriorityFeedback}
512
+ initialFactorFeedback={signalData.initialFactorPopoverFeedback}
513
+ onFactorFeedback={signalData.onFactorFeedback}
514
+ initialPriorityFeedback={signalData.initialPriorityFeedback}
515
+ />
516
+ {signalData.timeChipLabel && (
517
+ <Badge
518
+ variant="outline"
519
+ title={signalData.timeChipDetail ?? undefined}
520
+ className={`transition-colors ${timeChipToneClass}`}
521
+ >
522
+ {signalData.timeChipLabel}
523
+ </Badge>
524
+ )}
525
+ <TooltipProvider delayDuration={300}>
526
+ <Tooltip>
527
+ <TooltipTrigger asChild>
528
+ <button
529
+ type="button"
530
+ onClick={onOpenEntityPanel}
531
+ className="group/account ml-1 inline-flex max-w-full items-center gap-2 rounded-md border border-border/60 bg-background px-2 py-1 transition-colors hover:border-border hover:bg-muted/50"
532
+ aria-label={getAccountDetailsButtonAriaLabel?.(item) ?? `View account details for ${item.company}`}
533
+ >
534
+ <span className="flex h-5 w-5 shrink-0 items-center justify-center rounded bg-foreground text-[9px] font-semibold uppercase leading-none text-background">
535
+ {getCompanyInitials(item.company)}
536
+ </span>
537
+ {accountDetailsButtonLabel ? (
538
+ <span className="inline-flex min-w-0 items-center gap-1.5 text-xs font-medium text-foreground">
539
+ {accountDetailsButtonLabel(item)}
540
+ </span>
541
+ ) : (
542
+ <span className="truncate text-xs font-medium text-foreground">{item.company}</span>
543
+ )}
544
+ <span className="flex shrink-0 items-center gap-1.5">
545
+ <span
546
+ aria-hidden
547
+ className={`h-1.5 w-1.5 shrink-0 rounded-full ${STATUS_DOT_CLASS[item.statusColor] ?? "bg-muted-foreground/40"}`}
548
+ />
549
+ {/* "View account" expands in on hover via an animated grid column */}
550
+ <span className="-ml-1.5 grid grid-cols-[0fr] transition-all duration-200 ease-out group-hover/account:ml-0 group-hover/account:grid-cols-[1fr]">
551
+ <span className="overflow-hidden">
552
+ <span className="block whitespace-nowrap text-xs font-medium text-muted-foreground/80">
553
+ View account
554
+ </span>
555
+ </span>
556
+ </span>
557
+ <ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/50 transition-transform duration-150 group-hover/account:translate-x-0.5 group-hover/account:text-muted-foreground" />
558
+ </span>
559
+ </button>
560
+ </TooltipTrigger>
561
+ <TooltipContent>Open account panel</TooltipContent>
562
+ </Tooltip>
563
+ </TooltipProvider>
564
+ {renderMetadataExtra?.(item)}
565
+ </>
566
+ )
567
+
568
+ const timelineSection = sections.timeline && timelineEvents.length > 0 ? (
569
+ <TimelineSection
570
+ timelineEvents={timelineEvents}
571
+ showTimeline={showTimeline}
572
+ setShowTimeline={setShowTimeline}
573
+ showSystemEvents={showSystemEvents}
574
+ setShowSystemEvents={setShowSystemEvents}
575
+ attentionCount={attentionCount}
576
+ sysEvtConfig={sysEvtConfig}
577
+ lastActivityTime={lastActivityTime}
578
+ />
579
+ ) : null
580
+
581
+ const suggestedActionsSection = sections.suggestedActions ? (
582
+ <SignalApproval.Gate>
583
+ <SuggestedActions
584
+ actions={suggestedActions}
585
+ accountContacts={accountContacts}
586
+ signature={emailSignature}
587
+ iconMap={iconMap}
588
+ onDismiss={(id) => console.log("Dismiss action:", id)}
589
+ onSend={(id) => console.log("Send action:", id)}
590
+ onSaveDraft={(id) => console.log("Save draft:", id)}
591
+ onDuplicate={handleDuplicate}
592
+ onOpenAccountDetails={onOpenEntityPanel}
593
+ onOpenRecentActivity={onOpenRecentActivity}
594
+ onMarkComplete={(id) => console.log("Mark complete:", id)}
595
+ onDispatchAgent={(id) => console.log("Dispatch agent:", id)}
596
+ />
597
+ </SignalApproval.Gate>
598
+ ) : null
599
+
468
600
  return (
469
601
  <SignalApproval.Root
470
602
  key={item.id}
@@ -488,14 +620,6 @@ export function DetailView({
488
620
  <div className="pb-8">
489
621
  {/* Header */}
490
622
  <div className="mb-4 flex items-center gap-2">
491
- <button
492
- type="button"
493
- className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
494
- >
495
- <ArrowLeft className="h-3.5 w-3.5" />
496
- Back
497
- </button>
498
- <span className="text-muted-foreground/40">&middot;</span>
499
623
  <span className="text-xs text-muted-foreground">{item.company}</span>
500
624
  </div>
501
625
 
@@ -514,71 +638,9 @@ export function DetailView({
514
638
  ) : null}
515
639
  </div>
516
640
 
517
- <div className="mb-6 flex flex-wrap items-center gap-2">
518
- <SignalPriorityPopover
519
- score={signalData.score}
520
- urgencyLabel={signalData.urgencyLabel}
521
- urgencyExplanation={signalData.urgencyExplanation ?? signalData.signalBrief}
522
- scoreDisplay={signalData.priorityScoreDisplay}
523
- formulaLabel={signalData.priorityFormulaLabel}
524
- factors={signalData.priorityFactors ?? []}
525
- metaText={undefined}
526
- feedbackChips={signalData.priorityFeedbackChips}
527
- onFeedbackSubmit={signalData.onPriorityFeedback}
528
- initialFactorFeedback={signalData.initialFactorPopoverFeedback}
529
- onFactorFeedback={signalData.onFactorFeedback}
530
- initialPriorityFeedback={signalData.initialPriorityFeedback}
531
- />
532
- {signalData.timeChipLabel && (
533
- <Badge
534
- variant="outline"
535
- title={signalData.timeChipDetail ?? undefined}
536
- className="transition-colors hover:bg-muted/50"
537
- >
538
- {signalData.timeChipLabel}
539
- </Badge>
540
- )}
541
- <TooltipProvider delayDuration={300}>
542
- <Tooltip>
543
- <TooltipTrigger asChild>
544
- <button
545
- type="button"
546
- onClick={onOpenEntityPanel}
547
- className="group/account ml-1 inline-flex max-w-full items-center gap-2 rounded-md border border-border/60 bg-background px-2 py-1 transition-colors hover:border-border hover:bg-muted/50"
548
- aria-label={getAccountDetailsButtonAriaLabel?.(item) ?? `View account details for ${item.company}`}
549
- >
550
- <span className="flex h-5 w-5 shrink-0 items-center justify-center rounded bg-foreground text-[9px] font-semibold uppercase leading-none text-background">
551
- {getCompanyInitials(item.company)}
552
- </span>
553
- {accountDetailsButtonLabel ? (
554
- <span className="inline-flex min-w-0 items-center gap-1.5 text-xs font-medium text-foreground">
555
- {accountDetailsButtonLabel(item)}
556
- </span>
557
- ) : (
558
- <span className="truncate text-xs font-medium text-foreground">{item.company}</span>
559
- )}
560
- <span className="flex shrink-0 items-center gap-1.5">
561
- <span
562
- aria-hidden
563
- className={`h-1.5 w-1.5 shrink-0 rounded-full ${STATUS_DOT_CLASS[item.statusColor] ?? "bg-muted-foreground/40"}`}
564
- />
565
- {/* "View account" expands in on hover via an animated grid column */}
566
- <span className="-ml-1.5 grid grid-cols-[0fr] transition-all duration-200 ease-out group-hover/account:ml-0 group-hover/account:grid-cols-[1fr]">
567
- <span className="overflow-hidden">
568
- <span className="block whitespace-nowrap text-xs font-medium text-muted-foreground/80">
569
- View account
570
- </span>
571
- </span>
572
- </span>
573
- <ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/50 transition-transform duration-150 group-hover/account:translate-x-0.5 group-hover/account:text-muted-foreground" />
574
- </span>
575
- </button>
576
- </TooltipTrigger>
577
- <TooltipContent>Open account panel</TooltipContent>
578
- </Tooltip>
579
- </TooltipProvider>
580
- {renderMetadataExtra?.(item)}
581
- </div>
641
+ {!isCasePanelV2 && metadataLayout === "default" ? (
642
+ <div className="mb-6 flex flex-wrap items-center gap-2">{metadataChips}</div>
643
+ ) : null}
582
644
 
583
645
  {/* Signal Brief */}
584
646
  {sections.signalBrief && (() => {
@@ -620,59 +682,78 @@ export function DetailView({
620
682
  </p>
621
683
  )}
622
684
 
623
- {/* Before-score content slot (e.g. "Signals on Case" chips) */}
624
- {renderBeforeScore?.(item)}
685
+ {/* Metadata chips relocated beneath the brief (case-panel redesign). */}
686
+ {isCasePanelV2 || metadataLayout === "below-brief" ? (
687
+ <div className="mb-4 flex flex-wrap items-center gap-2">{metadataChips}</div>
688
+ ) : null}
625
689
 
626
- <ScoreWhyChips
627
- item={item}
628
- signalData={signalData}
629
- onOpenSignalBucket={onOpenSignalBucket}
630
- />
631
- <div className="mt-4">
632
- <SignalApproval.Actions />
633
- </div>
690
+ {!isCasePanelV2 ? (
691
+ <>
692
+ {/* Before-score content slot (e.g. "Signals on Case" chips) */}
693
+ {renderBeforeScore?.(item)}
694
+
695
+ <ScoreWhyChips
696
+ item={item}
697
+ signalData={signalData}
698
+ onOpenSignalBucket={onOpenSignalBucket}
699
+ />
700
+ <div className="mt-4">
701
+ <SignalApproval.Actions />
702
+ </div>
703
+ </>
704
+ ) : null}
634
705
  </div>
635
706
  )
636
707
  })()}
637
708
 
638
- {/* After-score content slot (e.g. OpportunityPanel) */}
639
- {renderAfterScore?.(item)}
640
-
641
- {/* Activity Timeline */}
642
- {sections.timeline && timelineEvents.length > 0 && (
643
- <TimelineSection
644
- timelineEvents={timelineEvents}
645
- showTimeline={showTimeline}
646
- setShowTimeline={setShowTimeline}
647
- showSystemEvents={showSystemEvents}
648
- setShowSystemEvents={setShowSystemEvents}
649
- attentionCount={attentionCount}
650
- sysEvtConfig={sysEvtConfig}
651
- lastActivityTime={lastActivityTime}
652
- />
709
+ {isCasePanelV2 ? (
710
+ <>
711
+ {sections.signalBrief ? (
712
+ <>
713
+ {/* Before-score content slot (e.g. status/attention pills) */}
714
+ {renderBeforeScore?.(item)}
715
+
716
+ {v2WhyText ? (
717
+ <div className="mb-8">
718
+ <h3 className="text-xs font-bold text-muted-foreground uppercase tracking-wider mb-3">The why</h3>
719
+ <p className="text-sm text-foreground/90 leading-relaxed mb-4">
720
+ {v2WhyText}
721
+ </p>
722
+ </div>
723
+ ) : null}
724
+
725
+ <div className="mb-8">
726
+ <ScoreWhyChips
727
+ item={item}
728
+ signalData={signalData}
729
+ onOpenSignalBucket={onOpenSignalBucket}
730
+ />
731
+ <div className="mt-4">
732
+ <SignalApproval.Actions />
733
+ </div>
734
+ </div>
735
+ </>
736
+ ) : null}
737
+
738
+ {/* After-score content slot (e.g. OpportunityPanel) */}
739
+ {renderAfterScore?.(item)}
740
+ {renderPrimaryAction?.(item)}
741
+ {renderCommentArea?.(item)}
742
+ {timelineSection}
743
+ </>
744
+ ) : (
745
+ <>
746
+ {/* After-score content slot (e.g. OpportunityPanel) */}
747
+ {renderAfterScore?.(item)}
748
+
749
+ {/* Activity Timeline */}
750
+ {timelineSection}
751
+ </>
653
752
  )}
654
753
  </div>
655
754
 
656
- {/* Suggested Actions */}
657
- {sections.suggestedActions && (
658
- <SignalApproval.Gate>
659
- <SuggestedActions
660
- actions={suggestedActions}
661
- accountContacts={accountContacts}
662
- signature={emailSignature}
663
- iconMap={iconMap}
664
- onDismiss={(id) => console.log("Dismiss action:", id)}
665
- onSend={(id) => console.log("Send action:", id)}
666
- onSaveDraft={(id) => console.log("Save draft:", id)}
667
- onDuplicate={handleDuplicate}
668
- onOpenAccountDetails={onOpenEntityPanel}
669
- onOpenRecentActivity={onOpenRecentActivity}
670
- onMarkComplete={(id) => console.log("Mark complete:", id)}
671
- onDispatchAgent={(id) => console.log("Dispatch agent:", id)}
672
- />
673
- </SignalApproval.Gate>
674
- )}
675
- {renderDetailExtra?.(item)}
755
+ {!isCasePanelV2 ? suggestedActionsSection : null}
756
+ {!isCasePanelV2 ? renderDetailExtra?.(item) : null}
676
757
  </div>
677
758
  </SignalApproval.Root>
678
759
  )
@@ -714,9 +795,12 @@ export function PrototypeInboxView({
714
795
  hideApproveButton,
715
796
  signalBriefCopy,
716
797
  briefStyleVariant,
798
+ sectionLayout,
717
799
  renderDetailExtra,
718
800
  renderBeforeScore,
719
801
  renderAfterScore,
802
+ renderPrimaryAction,
803
+ renderCommentArea,
720
804
  lastActivityTime,
721
805
  timelineSystemEventsConfig,
722
806
  attentionCount,
@@ -959,9 +1043,12 @@ export function PrototypeInboxView({
959
1043
  hideApproveButton,
960
1044
  signalBriefCopy,
961
1045
  briefStyleVariant,
1046
+ sectionLayout,
962
1047
  renderDetailExtra,
963
1048
  renderBeforeScore,
964
1049
  renderAfterScore,
1050
+ renderPrimaryAction,
1051
+ renderCommentArea,
965
1052
  lastActivityTime,
966
1053
  timelineSystemEventsConfig,
967
1054
  attentionCount,