@handled-ai/design-system 0.9.20 → 0.9.22

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.
@@ -3,13 +3,65 @@
3
3
  import * as React from "react"
4
4
  import { Check, CirclePlus, ExternalLink, Lock, ThumbsDown } from "lucide-react"
5
5
 
6
- const dismissReasons = [
7
- "Bad timing",
8
- "Inaccurate data",
9
- "Wrong account",
10
- "Already handled",
11
- "Not actionable",
12
- "Other",
6
+ interface DismissReasonNode {
7
+ label: string
8
+ subOptions?: string[]
9
+ }
10
+
11
+ const dismissReasonTree: DismissReasonNode[] = [
12
+ {
13
+ label: "Not relevant for this account",
14
+ subOptions: [
15
+ "Business as usual for this account",
16
+ "Account in maintenance mode",
17
+ "Wrong contact for this signal",
18
+ "Other",
19
+ ],
20
+ },
21
+ {
22
+ label: "Bad timing",
23
+ subOptions: [
24
+ "Too early in the relationship",
25
+ "Too soon after last outreach",
26
+ "Wrong time of year for this account",
27
+ "Other",
28
+ ],
29
+ },
30
+ {
31
+ label: "Inaccurate data",
32
+ subOptions: [
33
+ "Wrong amount or number",
34
+ "Stale data",
35
+ "Account info wrong",
36
+ "Other",
37
+ ],
38
+ },
39
+ {
40
+ label: "Wrong account",
41
+ subOptions: [
42
+ "Different account meant",
43
+ "Account not in scope",
44
+ "Other",
45
+ ],
46
+ },
47
+ {
48
+ label: "Already handled",
49
+ subOptions: [
50
+ "Already in conversation",
51
+ "Already an open Opportunity",
52
+ "Already escalated",
53
+ "Other",
54
+ ],
55
+ },
56
+ {
57
+ label: "Not actionable",
58
+ subOptions: [
59
+ "No clear next step",
60
+ "Outside our remit",
61
+ "Other",
62
+ ],
63
+ },
64
+ { label: "Other" },
13
65
  ]
14
66
 
15
67
  const approveReasons = [
@@ -53,7 +105,7 @@ interface SignalApprovalContextValue {
53
105
  approve: () => void
54
106
  submitApproveFeedback: (reasons: string[], detail: string) => void
55
107
  skipApproveFeedback: () => void
56
- dismiss: (reasons: string[], detail: string) => void
108
+ dismiss: (reasons: string[], detail: string, subReason?: string) => void
57
109
  requestApproval: () => void
58
110
  requestDismiss: () => void
59
111
  cancel: () => void
@@ -78,7 +130,7 @@ interface RootProps {
78
130
  hideApproveButton?: boolean
79
131
  onApprove?: () => void
80
132
  onApproveFeedback?: (reasons: string[], detail: string) => void
81
- onDismiss?: (reasons: string[], detail: string) => void
133
+ onDismiss?: (reasons: string[], detail: string, subReason?: string) => void
82
134
  }
83
135
 
84
136
  function Root({ children, companyName, opportunityUrl, scheduledTime, initialApprovalState, labels: labelOverrides, hideApproveButton, onApprove, onApproveFeedback, onDismiss }: RootProps) {
@@ -115,9 +167,9 @@ function Root({ children, companyName, opportunityUrl, scheduledTime, initialApp
115
167
  }, [])
116
168
 
117
169
  const dismiss = React.useCallback(
118
- (reasons: string[], detail: string) => {
170
+ (reasons: string[], detail: string, subReason?: string) => {
119
171
  setApprovalState("dismissed")
120
- onDismiss?.(reasons, detail)
172
+ onDismiss?.(reasons, detail, subReason)
121
173
  },
122
174
  [onDismiss]
123
175
  )
@@ -131,14 +183,128 @@ function Root({ children, companyName, opportunityUrl, scheduledTime, initialApp
131
183
  )
132
184
  }
133
185
 
186
+ /** Shared dismiss reason picker used in both the "editing" and "initial dismiss" paths. */
187
+ function DismissReasonPicker({
188
+ selectedTopReason,
189
+ selectedSubReason,
190
+ selectTopReason,
191
+ selectSubReason,
192
+ detailText,
193
+ setDetailText,
194
+ needsText,
195
+ canSubmitDismiss,
196
+ handleDismissSubmit,
197
+ topNode,
198
+ submitLabel,
199
+ onCancel,
200
+ }: {
201
+ selectedTopReason: string | null
202
+ selectedSubReason: string | null
203
+ selectTopReason: (label: string) => void
204
+ selectSubReason: (label: string) => void
205
+ detailText: string
206
+ setDetailText: (value: string) => void
207
+ needsText: boolean
208
+ canSubmitDismiss: boolean
209
+ handleDismissSubmit: () => void
210
+ topNode: DismissReasonNode | undefined
211
+ submitLabel: string
212
+ onCancel: () => void
213
+ }) {
214
+ return (
215
+ <>
216
+ <div className="flex flex-wrap gap-1.5">
217
+ {dismissReasonTree.map((node) => {
218
+ const selected = selectedTopReason === node.label
219
+ return (
220
+ <button
221
+ key={node.label}
222
+ type="button"
223
+ onClick={() => selectTopReason(node.label)}
224
+ className={`rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors ${
225
+ selected
226
+ ? "border-red-200 bg-red-100 text-red-700"
227
+ : "border-border bg-background text-muted-foreground hover:bg-muted/50 hover:text-foreground"
228
+ }`}
229
+ >
230
+ {node.label}
231
+ </button>
232
+ )
233
+ })}
234
+ </div>
235
+
236
+ {topNode?.subOptions && (
237
+ <div className="ml-3 border-l-2 border-muted pl-3">
238
+ <div className="flex flex-wrap gap-1.5">
239
+ {topNode.subOptions.map((sub) => {
240
+ const selected = selectedSubReason === sub
241
+ return (
242
+ <button
243
+ key={sub}
244
+ type="button"
245
+ onClick={() => selectSubReason(sub)}
246
+ className={`rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors ${
247
+ selected
248
+ ? "border-red-200 bg-red-100 text-red-700"
249
+ : "border-border bg-background text-muted-foreground hover:bg-muted/50 hover:text-foreground"
250
+ }`}
251
+ >
252
+ {sub}
253
+ </button>
254
+ )
255
+ })}
256
+ </div>
257
+ </div>
258
+ )}
259
+
260
+ {selectedTopReason && (
261
+ <input
262
+ type="text"
263
+ value={detailText}
264
+ onChange={(e) => setDetailText(e.target.value)}
265
+ onKeyDown={(e) => {
266
+ if (e.key === "Enter" && canSubmitDismiss) handleDismissSubmit()
267
+ }}
268
+ placeholder={needsText ? "Please describe (required)" : "Add context (optional)"}
269
+ className="h-7 w-full rounded-md border border-border bg-muted/20 px-2.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring"
270
+ />
271
+ )}
272
+
273
+ <div className="flex items-center gap-2">
274
+ <button
275
+ type="button"
276
+ onClick={handleDismissSubmit}
277
+ disabled={!canSubmitDismiss}
278
+ className={`inline-flex h-7 items-center gap-1.5 rounded-md px-3 text-xs font-semibold transition-colors ${
279
+ canSubmitDismiss
280
+ ? "bg-foreground text-background hover:bg-foreground/90"
281
+ : "cursor-not-allowed bg-muted text-muted-foreground"
282
+ }`}
283
+ >
284
+ {submitLabel}
285
+ </button>
286
+ <button
287
+ type="button"
288
+ onClick={onCancel}
289
+ className="inline-flex h-7 items-center rounded-md border border-border px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
290
+ >
291
+ Cancel
292
+ </button>
293
+ </div>
294
+ </>
295
+ )
296
+ }
297
+
134
298
  function SubmittedFeedback({
135
299
  reasons,
136
300
  detail,
301
+ subReason,
137
302
  variant,
138
303
  onEdit,
139
304
  }: {
140
305
  reasons: string[]
141
306
  detail: string
307
+ subReason?: string
142
308
  variant: "approve" | "dismiss"
143
309
  onEdit: () => void
144
310
  }) {
@@ -164,6 +330,13 @@ function SubmittedFeedback({
164
330
  {r}
165
331
  </span>
166
332
  ))}
333
+ {subReason && (
334
+ <span
335
+ className={`rounded-full border px-2 py-0.5 text-[10px] font-medium transition-colors group-hover:opacity-80 ${pillClass}`}
336
+ >
337
+ {subReason}
338
+ </span>
339
+ )}
167
340
  </div>
168
341
  )}
169
342
  {detail && (
@@ -176,15 +349,41 @@ function SubmittedFeedback({
176
349
  function Actions() {
177
350
  const { approvalState, companyName, opportunityUrl, scheduledTime, labels, hideApproveButton, approve, submitApproveFeedback, skipApproveFeedback, dismiss, requestApproval, requestDismiss, cancel } =
178
351
  useSignalApproval()
352
+ const [selectedTopReason, setSelectedTopReason] = React.useState<string | null>(null)
353
+ const [selectedSubReason, setSelectedSubReason] = React.useState<string | null>(null)
179
354
  const [selectedReasons, setSelectedReasons] = React.useState<string[]>([])
180
355
  const [detailText, setDetailText] = React.useState("")
181
- const [submittedFeedback, setSubmittedFeedback] = React.useState<{ reasons: string[]; detail: string } | null>(null)
356
+ const [submittedFeedback, setSubmittedFeedback] = React.useState<{ reasons: string[]; detail: string; subReason?: string } | null>(null)
182
357
  const [isEditing, setIsEditing] = React.useState(false)
183
358
 
184
- const otherSelected = selectedReasons.includes("Other")
185
- const canSubmitDismiss = selectedReasons.length > 0 && (!otherSelected || detailText.trim().length > 0)
359
+ const topNode = dismissReasonTree.find((n) => n.label === selectedTopReason)
360
+ const hasSubOptions = !!(topNode?.subOptions && topNode.subOptions.length > 0)
361
+ const isTopOther = selectedTopReason === "Other" && !hasSubOptions
362
+ const isSubOther = selectedSubReason === "Other"
363
+ const needsText = isTopOther || isSubOther
364
+ const canSubmitDismiss =
365
+ selectedTopReason !== null &&
366
+ (!hasSubOptions || selectedSubReason !== null) &&
367
+ (!needsText || detailText.trim().length > 0)
368
+
186
369
  const canSubmitApprove = selectedReasons.length > 0 || detailText.trim().length > 0
187
370
 
371
+ const selectTopReason = (label: string) => {
372
+ if (selectedTopReason === label) {
373
+ setSelectedTopReason(null)
374
+ setSelectedSubReason(null)
375
+ setDetailText("")
376
+ } else {
377
+ setSelectedTopReason(label)
378
+ setSelectedSubReason(null)
379
+ setDetailText("")
380
+ }
381
+ }
382
+
383
+ const selectSubReason = (label: string) => {
384
+ setSelectedSubReason(selectedSubReason === label ? null : label)
385
+ }
386
+
188
387
  const toggleReason = (reason: string) => {
189
388
  setSelectedReasons((prev) =>
190
389
  prev.includes(reason) ? prev.filter((r) => r !== reason) : [...prev, reason]
@@ -193,6 +392,11 @@ function Actions() {
193
392
 
194
393
  const startEditing = () => {
195
394
  if (submittedFeedback) {
395
+ setSelectedTopReason(submittedFeedback.reasons[0] ?? null)
396
+ setSelectedSubReason(submittedFeedback.subReason ?? null)
397
+ // Note: selectedReasons is only used by the approve editing path.
398
+ // For dismiss feedback this is harmless but unused — the dismiss path
399
+ // reads selectedTopReason/selectedSubReason instead.
196
400
  setSelectedReasons([...submittedFeedback.reasons])
197
401
  setDetailText(submittedFeedback.detail)
198
402
  }
@@ -200,11 +404,12 @@ function Actions() {
200
404
  }
201
405
 
202
406
  const handleDismissSubmit = () => {
203
- if (!canSubmitDismiss) return
204
- const fb = { reasons: [...selectedReasons], detail: detailText.trim() }
407
+ if (!canSubmitDismiss || !selectedTopReason) return
408
+ const fb = { reasons: [selectedTopReason], detail: detailText.trim(), subReason: selectedSubReason ?? undefined }
205
409
  setSubmittedFeedback(fb)
206
- dismiss(selectedReasons, detailText.trim())
207
- setSelectedReasons([])
410
+ dismiss([selectedTopReason], detailText.trim(), selectedSubReason ?? undefined)
411
+ setSelectedTopReason(null)
412
+ setSelectedSubReason(null)
208
413
  setDetailText("")
209
414
  setIsEditing(false)
210
415
  }
@@ -219,6 +424,8 @@ function Actions() {
219
424
  }
220
425
 
221
426
  const handleEditCancel = () => {
427
+ setSelectedTopReason(null)
428
+ setSelectedSubReason(null)
222
429
  setSelectedReasons([])
223
430
  setDetailText("")
224
431
  setIsEditing(false)
@@ -226,6 +433,8 @@ function Actions() {
226
433
 
227
434
  const handleCancel = () => {
228
435
  cancel()
436
+ setSelectedTopReason(null)
437
+ setSelectedSubReason(null)
229
438
  setSelectedReasons([])
230
439
  setDetailText("")
231
440
  }
@@ -403,31 +612,20 @@ function Actions() {
403
612
  <span>{labels.dismissedStatus}</span>
404
613
  </div>
405
614
  <p className="text-xs font-medium text-muted-foreground">Edit your feedback</p>
406
- <div className="flex flex-wrap gap-1.5">
407
- {dismissReasons.map((reason) => {
408
- const selected = selectedReasons.includes(reason)
409
- return (
410
- <button key={reason} type="button" onClick={() => toggleReason(reason)}
411
- className={`rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors ${
412
- selected ? "border-red-200 bg-red-100 text-red-700" : "border-border bg-background text-muted-foreground hover:bg-muted/50 hover:text-foreground"
413
- }`}>{reason}</button>
414
- )
415
- })}
416
- </div>
417
- <input type="text" value={detailText} onChange={(e) => setDetailText(e.target.value)}
418
- onKeyDown={(e) => { if (e.key === "Enter" && canSubmitDismiss) handleDismissSubmit() }}
419
- placeholder={otherSelected ? "Please describe (required)" : "Provide additional feedback..."}
420
- className="h-7 w-full rounded-md border border-border bg-muted/20 px-2.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring" />
421
- <div className="flex items-center gap-2">
422
- <button type="button" onClick={handleDismissSubmit} disabled={!canSubmitDismiss}
423
- className={`inline-flex h-7 items-center gap-1.5 rounded-md px-3 text-xs font-semibold transition-colors ${canSubmitDismiss ? "bg-foreground text-background hover:bg-foreground/90" : "cursor-not-allowed bg-muted text-muted-foreground"}`}>
424
- Save
425
- </button>
426
- <button type="button" onClick={handleEditCancel}
427
- className="inline-flex h-7 items-center rounded-md border border-border px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
428
- Cancel
429
- </button>
430
- </div>
615
+ <DismissReasonPicker
616
+ selectedTopReason={selectedTopReason}
617
+ selectedSubReason={selectedSubReason}
618
+ selectTopReason={selectTopReason}
619
+ selectSubReason={selectSubReason}
620
+ detailText={detailText}
621
+ setDetailText={setDetailText}
622
+ needsText={needsText}
623
+ canSubmitDismiss={canSubmitDismiss}
624
+ handleDismissSubmit={handleDismissSubmit}
625
+ topNode={topNode}
626
+ submitLabel="Save"
627
+ onCancel={handleEditCancel}
628
+ />
431
629
  </div>
432
630
  )
433
631
  }
@@ -442,6 +640,7 @@ function Actions() {
442
640
  <SubmittedFeedback
443
641
  reasons={submittedFeedback.reasons}
444
642
  detail={submittedFeedback.detail}
643
+ subReason={submittedFeedback.subReason}
445
644
  variant="dismiss"
446
645
  onEdit={startEditing}
447
646
  />
@@ -483,60 +682,20 @@ function Actions() {
483
682
  return (
484
683
  <div className="space-y-3">
485
684
  <p className="text-xs font-medium text-muted-foreground">{labels.dismissPrompt}</p>
486
- <div className="flex flex-wrap gap-1.5">
487
- {dismissReasons.map((reason) => {
488
- const selected = selectedReasons.includes(reason)
489
- return (
490
- <button
491
- key={reason}
492
- type="button"
493
- onClick={() => toggleReason(reason)}
494
- className={`rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors ${
495
- selected
496
- ? "border-red-200 bg-red-100 text-red-700"
497
- : "border-border bg-background text-muted-foreground hover:bg-muted/50 hover:text-foreground"
498
- }`}
499
- >
500
- {reason}
501
- </button>
502
- )
503
- })}
504
- </div>
505
-
506
- {(selectedReasons.length > 0 || otherSelected) && (
507
- <input
508
- type="text"
509
- value={detailText}
510
- onChange={(e) => setDetailText(e.target.value)}
511
- onKeyDown={(e) => {
512
- if (e.key === "Enter" && canSubmitDismiss) handleDismissSubmit()
513
- }}
514
- placeholder={otherSelected ? "Please describe (required)" : "Provide additional feedback..."}
515
- className="h-7 w-full rounded-md border border-border bg-muted/20 px-2.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring"
516
- />
517
- )}
518
-
519
- <div className="flex items-center gap-2">
520
- <button
521
- type="button"
522
- onClick={handleDismissSubmit}
523
- disabled={!canSubmitDismiss}
524
- className={`inline-flex h-7 items-center gap-1.5 rounded-md px-3 text-xs font-semibold transition-colors ${
525
- canSubmitDismiss
526
- ? "bg-foreground text-background hover:bg-foreground/90"
527
- : "cursor-not-allowed bg-muted text-muted-foreground"
528
- }`}
529
- >
530
- Submit
531
- </button>
532
- <button
533
- type="button"
534
- onClick={handleCancel}
535
- className="inline-flex h-7 items-center rounded-md border border-border px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
536
- >
537
- Cancel
538
- </button>
539
- </div>
685
+ <DismissReasonPicker
686
+ selectedTopReason={selectedTopReason}
687
+ selectedSubReason={selectedSubReason}
688
+ selectTopReason={selectTopReason}
689
+ selectSubReason={selectSubReason}
690
+ detailText={detailText}
691
+ setDetailText={setDetailText}
692
+ needsText={needsText}
693
+ canSubmitDismiss={canSubmitDismiss}
694
+ handleDismissSubmit={handleDismissSubmit}
695
+ topNode={topNode}
696
+ submitLabel="Submit"
697
+ onCancel={handleCancel}
698
+ />
540
699
  </div>
541
700
  )
542
701
  }
@@ -167,6 +167,7 @@ export interface SuggestedAction {
167
167
  callMeta?: SuggestedActionCallMeta
168
168
  manualMeta?: SuggestedActionManualMeta
169
169
  browserMeta?: SuggestedActionBrowserMeta
170
+ onFeedback?: (type: "up" | "down", pills: string[], detail: string) => void
170
171
  }
171
172
 
172
173
  // ---------------------------------------------------------------------------
@@ -1253,6 +1254,7 @@ function SuggestedActionCard({
1253
1254
  onOpenRecentActivity,
1254
1255
  onMarkComplete,
1255
1256
  onDispatchAgent,
1257
+ onFeedback,
1256
1258
  iconMap,
1257
1259
  sendLabel,
1258
1260
  accountDetailsLabel,
@@ -1269,6 +1271,7 @@ function SuggestedActionCard({
1269
1271
  onOpenRecentActivity?: () => void
1270
1272
  onMarkComplete?: (id: number | string) => void
1271
1273
  onDispatchAgent?: (id: number | string, editedContent?: string, settings?: { aiDisclosureEnabled?: boolean; maxDurationMinutes?: string; callRecordingEnabled?: boolean; recordingNoticeEnabled?: boolean }) => void
1274
+ onFeedback?: (type: "up" | "down", pills: string[], detail: string) => void
1272
1275
  iconMap?: SuggestedActionsIconMap
1273
1276
  sendLabel?: string
1274
1277
  accountDetailsLabel?: string
@@ -1405,10 +1408,14 @@ function SuggestedActionCard({
1405
1408
  {feedbackOpen && (
1406
1409
  <div className="px-5 py-3 border-b border-border/40 animate-in fade-in slide-in-from-top-2 duration-200">
1407
1410
  <DraftFeedbackInline
1408
- onRegenerateRequest={(pills, detail) => console.log("Regenerate:", pills, detail)}
1409
- onSubmitFeedback={(type, pills, detail) => console.log("Feedback:", type, pills, detail)}
1411
+ onRegenerateRequest={(pills, detail) => {
1412
+ onFeedback?.("down", pills, detail)
1413
+ }}
1414
+ onSubmitFeedback={(type, pills, detail) => {
1415
+ onFeedback?.(type, pills, detail)
1416
+ }}
1410
1417
  onDiscardRequest={(pills, detail) => {
1411
- console.log("Discard:", pills, detail)
1418
+ onFeedback?.("down", pills, detail)
1412
1419
  onDismiss?.(action.id)
1413
1420
  }}
1414
1421
  />
@@ -1972,6 +1979,7 @@ export function SuggestedActions({
1972
1979
  onOpenRecentActivity={onOpenRecentActivity}
1973
1980
  onMarkComplete={onMarkComplete}
1974
1981
  onDispatchAgent={onDispatchAgent}
1982
+ onFeedback={action.onFeedback}
1975
1983
  iconMap={iconMap}
1976
1984
  sendLabel={sendLabel}
1977
1985
  accountDetailsLabel={accountDetailsLabel}
@@ -44,7 +44,7 @@ export interface SignalScoreData {
44
44
  confidence: number
45
45
  onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void
46
46
  onApproveFeedback?: (reasons: string[], detail: string) => void
47
- onDismissFeedback?: (reasons: string[], detail: string) => void
47
+ onDismissFeedback?: (reasons: string[], detail: string, subReason?: string) => void
48
48
  }
49
49
 
50
50
  // ---------------------------------------------------------------------------
@@ -72,6 +72,7 @@ export interface InboxViewConfig {
72
72
  hideToolbarActions?: boolean
73
73
  hideHoverActions?: boolean
74
74
  onSuggestedActionFeedback?: (actionId: number | string, feedback: string, actionTitle?: string) => void
75
+ onScoreFeedback?: (type: "up" | "down", pills: string[], detail: string) => void
75
76
  buildEntityChips?: (item: QueueItem) => Array<{ id: string; label: string; avatarLetter: string; onClick?: () => void }>
76
77
  quickFilterTabs?: Array<{ id: string; label: string; matchValue?: string; count?: number }>
77
78
  hideAccountsButton?: boolean
@@ -30,6 +30,7 @@ import {
30
30
  } from "../components/inbox-toolbar"
31
31
  import { GroupedListView, type GroupedListGroup } from "../components/item-list"
32
32
  import { SignalApproval, type ApprovalState } from "../components/signal-feedback-inline"
33
+ import { ScoreFeedback } from "../components/score-feedback"
33
34
  import { ScoreBreakdown } from "../components/score-breakdown"
34
35
  import { Citation, type SourceDef } from "../components/detail-view"
35
36
  import {
@@ -107,6 +108,7 @@ export interface DetailViewProps {
107
108
  hideApproveButton?: boolean
108
109
  signalBriefCopy?: InboxViewConfig["signalBriefCopy"]
109
110
  renderDetailExtra?: (item: QueueItem) => React.ReactNode
111
+ onScoreFeedback?: (type: "up" | "down", pills: string[], detail: string) => void
110
112
  }
111
113
 
112
114
  export function DetailView({
@@ -128,6 +130,7 @@ export function DetailView({
128
130
  hideApproveButton,
129
131
  signalBriefCopy,
130
132
  renderDetailExtra,
133
+ onScoreFeedback,
131
134
  }: DetailViewProps) {
132
135
  const [evidenceExpanded, setEvidenceExpanded] = React.useState(false)
133
136
  const [showTimeline, setShowTimeline] = React.useState(false)
@@ -180,9 +183,8 @@ export function DetailView({
180
183
  signalData.onApproveFeedback?.(reasons, detail)
181
184
  console.log("Approval feedback:", { taskId: item.id, company: item.company, reasons, detail })
182
185
  }}
183
- onDismiss={(reasons, detail) => {
184
- signalData.onDismissFeedback?.(reasons, detail)
185
- console.log("Dismissed signal:", { taskId: item.id, reasons, detail })
186
+ onDismiss={(reasons, detail, subReason) => {
187
+ signalData.onDismissFeedback?.(reasons, detail, subReason)
186
188
  }}
187
189
  >
188
190
  <div className="mx-auto w-full max-w-3xl p-6 pb-12 md:p-8">
@@ -277,14 +279,17 @@ export function DetailView({
277
279
  {signalData.whyNow}
278
280
  </p>
279
281
 
282
+ <ScoreFeedback.Root onSubmitFeedback={(type, pills, detail) => onScoreFeedback?.(type, pills, detail)}>
280
283
  <div className="mb-5 rounded-md border border-border bg-muted/20 p-3">
281
284
  <div className="flex items-center justify-between mb-1.5">
282
285
  <span className="text-[10px] font-bold text-muted-foreground uppercase tracking-wider">Signal Score</span>
283
286
  <div className="flex items-center gap-2">
284
287
  <span className="text-sm font-bold text-foreground">{signalData.score}/100</span>
285
288
  <span className={`text-[10px] font-bold uppercase ${scoreColor}`}>{scoreLabel}</span>
289
+ <ScoreFeedback.Trigger />
286
290
  </div>
287
291
  </div>
292
+ <ScoreFeedback.Panel />
288
293
  <div className="h-1.5 bg-muted rounded-full overflow-hidden mb-2">
289
294
  <div
290
295
  className={`h-full rounded-full transition-all duration-500 ${barColor}`}
@@ -320,6 +325,7 @@ export function DetailView({
320
325
  </div>
321
326
  )}
322
327
  </div>
328
+ </ScoreFeedback.Root>
323
329
 
324
330
  {!evidenceExpanded && <SignalApproval.Actions />}
325
331
  </div>
@@ -398,6 +404,7 @@ export function PrototypeInboxView({
398
404
  hideToolbarActions,
399
405
  hideHoverActions,
400
406
  onSuggestedActionFeedback,
407
+ onScoreFeedback,
401
408
  headerActions,
402
409
  onOpenEntityPanel,
403
410
  onOpenRecentActivity,
@@ -640,6 +647,7 @@ export function PrototypeInboxView({
640
647
  hideApproveButton,
641
648
  signalBriefCopy,
642
649
  renderDetailExtra,
650
+ onScoreFeedback,
643
651
  }
644
652
 
645
653
  return (