@handled-ai/design-system 0.17.2 → 0.18.2
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.
- package/dist/charts/chart.d.ts +1 -1
- package/dist/components/actor-byline.d.ts +3 -0
- package/dist/components/actor-byline.js +5 -0
- package/dist/components/actor-byline.js.map +1 -0
- package/dist/components/feedback-primitives.d.ts +21 -2
- package/dist/components/feedback-primitives.js +90 -6
- package/dist/components/feedback-primitives.js.map +1 -1
- package/dist/components/performance-metrics-table.d.ts +2 -1
- package/dist/components/performance-metrics-table.js +78 -46
- package/dist/components/performance-metrics-table.js.map +1 -1
- package/dist/components/score-why-chips.d.ts +1 -1
- package/dist/components/score-why-chips.js +26 -5
- package/dist/components/score-why-chips.js.map +1 -1
- package/dist/components/signal-priority-popover.d.ts +1 -1
- package/dist/components/signal-priority-popover.js +172 -7
- package/dist/components/signal-priority-popover.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/prototype/index.d.ts +1 -1
- package/dist/prototype/prototype-accounts-view.d.ts +1 -1
- package/dist/prototype/prototype-admin-view.d.ts +1 -1
- package/dist/prototype/prototype-config.d.ts +1 -1
- package/dist/prototype/prototype-inbox-view.d.ts +1 -1
- package/dist/prototype/prototype-inbox-view.js +4 -1
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/dist/prototype/prototype-insights-view.d.ts +1 -1
- package/dist/prototype/prototype-shell.d.ts +1 -1
- package/dist/{signal-priority-popover-DQ_VuHac.d.ts → signal-priority-popover-DWaAMhPI.d.ts} +26 -2
- package/package.json +3 -1
- package/src/components/__tests__/performance-metrics-table.test.tsx +54 -0
- package/src/components/__tests__/user-display.test.tsx +75 -0
- package/src/components/__tests__/wit-636-feedback-states.test.tsx +546 -0
- package/src/components/actor-byline.tsx +1 -0
- package/src/components/feedback-primitives.tsx +148 -26
- package/src/components/performance-metrics-table.tsx +99 -63
- package/src/components/score-why-chips.tsx +28 -2
- package/src/components/signal-priority-popover.tsx +194 -3
- package/src/index.ts +1 -1
- package/src/lib/__tests__/user-display.test.ts +53 -11
- package/src/prototype/prototype-config.ts +11 -1
- package/src/prototype/prototype-inbox-view.tsx +3 -0
|
@@ -17,10 +17,14 @@ import {
|
|
|
17
17
|
ChevronDown,
|
|
18
18
|
ChevronUp,
|
|
19
19
|
Info,
|
|
20
|
+
ThumbsUp,
|
|
21
|
+
ThumbsDown,
|
|
22
|
+
Check,
|
|
23
|
+
Pencil,
|
|
20
24
|
} from "lucide-react"
|
|
21
25
|
import { cn } from "../lib/utils"
|
|
22
26
|
import { FeedbackFooter } from "./feedback-primitives"
|
|
23
|
-
import type { FeedbackChipTree, FeedbackSubmitData } from "./feedback-primitives"
|
|
27
|
+
import type { FeedbackChipTree, FeedbackSubmitData, PersistedFeedbackData } from "./feedback-primitives"
|
|
24
28
|
import type { SignalScoreUrgencyLabel } from "../prototype/prototype-config"
|
|
25
29
|
import { getSignalScoreUrgencyLabel, scoreRangeForUrgency, SIGNAL_TONE_CLASSES } from "./score-why-chips"
|
|
26
30
|
|
|
@@ -58,6 +62,12 @@ export interface SignalPriorityPopoverProps {
|
|
|
58
62
|
feedbackChips?: FeedbackChipTree[]
|
|
59
63
|
onFeedbackSubmit?: (data: FeedbackSubmitData) => void
|
|
60
64
|
className?: string
|
|
65
|
+
/** Persisted factor-level feedback (keyed by factor key). */
|
|
66
|
+
initialFactorFeedback?: Record<string, { type: "up" | "down"; detail: string; ownershipLabel?: string }>
|
|
67
|
+
/** Callback when user submits factor-level feedback. */
|
|
68
|
+
onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void
|
|
69
|
+
/** Persisted priority-level feedback for the footer. */
|
|
70
|
+
initialPriorityFeedback?: PersistedFeedbackData | null
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
// ---------------------------------------------------------------------------
|
|
@@ -152,7 +162,13 @@ function DirectionIcon({ direction }: { direction: PriorityFactor["direction"] }
|
|
|
152
162
|
// PriorityFactorRow
|
|
153
163
|
// ---------------------------------------------------------------------------
|
|
154
164
|
|
|
155
|
-
|
|
165
|
+
interface PriorityFactorRowProps {
|
|
166
|
+
factor: PriorityFactor
|
|
167
|
+
initialFeedback?: { type: "up" | "down"; detail: string; ownershipLabel?: string }
|
|
168
|
+
onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function PriorityFactorRow({ factor, initialFeedback, onFactorFeedback }: PriorityFactorRowProps) {
|
|
156
172
|
const IconComponent = FACTOR_ICONS[factor.icon] ?? Activity
|
|
157
173
|
const toneClasses = TONE_ICON_CLASSES[factor.tone]
|
|
158
174
|
const directionClasses = DIRECTION_CLASSES[factor.direction]
|
|
@@ -163,6 +179,50 @@ function PriorityFactorRow({ factor }: { factor: PriorityFactor }) {
|
|
|
163
179
|
? "Lowers"
|
|
164
180
|
: "Neutral"
|
|
165
181
|
|
|
182
|
+
const [thumbState, setThumbState] = React.useState<"up" | "down" | null>(
|
|
183
|
+
initialFeedback?.type ?? null,
|
|
184
|
+
)
|
|
185
|
+
const [showInput, setShowInput] = React.useState(false)
|
|
186
|
+
const [detailText, setDetailText] = React.useState(initialFeedback?.detail ?? "")
|
|
187
|
+
const [saved, setSaved] = React.useState(!!initialFeedback)
|
|
188
|
+
const [savedDetail, setSavedDetail] = React.useState(initialFeedback?.detail ?? "")
|
|
189
|
+
const ownershipLabel = initialFeedback?.ownershipLabel ?? "Your feedback"
|
|
190
|
+
|
|
191
|
+
// Sync with initialFeedback prop changes
|
|
192
|
+
React.useEffect(() => {
|
|
193
|
+
if (initialFeedback) {
|
|
194
|
+
setThumbState(initialFeedback.type)
|
|
195
|
+
setSaved(true)
|
|
196
|
+
setSavedDetail(initialFeedback.detail)
|
|
197
|
+
}
|
|
198
|
+
}, [initialFeedback])
|
|
199
|
+
|
|
200
|
+
const handleThumbClick = React.useCallback(
|
|
201
|
+
(type: "up" | "down") => {
|
|
202
|
+
if (thumbState === type) {
|
|
203
|
+
// Toggle off
|
|
204
|
+
setThumbState(null)
|
|
205
|
+
setShowInput(false)
|
|
206
|
+
setSaved(false)
|
|
207
|
+
onFactorFeedback?.(factor.key, null)
|
|
208
|
+
} else {
|
|
209
|
+
setThumbState(type)
|
|
210
|
+
setShowInput(true)
|
|
211
|
+
setSaved(false)
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
[thumbState, factor.key, onFactorFeedback],
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
const handleSubmitDetail = React.useCallback(() => {
|
|
218
|
+
if (!thumbState) return
|
|
219
|
+
const text = detailText.trim()
|
|
220
|
+
onFactorFeedback?.(factor.key, thumbState, text)
|
|
221
|
+
setSaved(true)
|
|
222
|
+
setSavedDetail(text)
|
|
223
|
+
setShowInput(false)
|
|
224
|
+
}, [thumbState, detailText, factor.key, onFactorFeedback])
|
|
225
|
+
|
|
166
226
|
return (
|
|
167
227
|
<div
|
|
168
228
|
className="grid grid-cols-[20px_1fr_auto] gap-x-3 gap-y-1 px-4 py-3"
|
|
@@ -224,6 +284,124 @@ function PriorityFactorRow({ factor }: { factor: PriorityFactor }) {
|
|
|
224
284
|
|
|
225
285
|
{/* empty grid cell under score column */}
|
|
226
286
|
<div />
|
|
287
|
+
|
|
288
|
+
{/* Factor-level feedback row (spans icon + content columns) */}
|
|
289
|
+
{onFactorFeedback && (
|
|
290
|
+
<>
|
|
291
|
+
<div />
|
|
292
|
+
<div className="col-span-2 mt-1">
|
|
293
|
+
{saved && !showInput ? (
|
|
294
|
+
/* Persisted / saved indicator */
|
|
295
|
+
<button
|
|
296
|
+
type="button"
|
|
297
|
+
onClick={() => {
|
|
298
|
+
setDetailText(savedDetail)
|
|
299
|
+
setShowInput(true)
|
|
300
|
+
setSaved(false)
|
|
301
|
+
}}
|
|
302
|
+
className="group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors"
|
|
303
|
+
data-testid={`factor-feedback-persisted-${factor.key}`}
|
|
304
|
+
>
|
|
305
|
+
<span className="font-medium">{ownershipLabel}:</span>
|
|
306
|
+
{thumbState === "up" ? (
|
|
307
|
+
<ThumbsUp className="h-[10px] w-[10px]" />
|
|
308
|
+
) : (
|
|
309
|
+
<ThumbsDown className="h-[10px] w-[10px]" />
|
|
310
|
+
)}
|
|
311
|
+
{savedDetail && (
|
|
312
|
+
<span className="max-w-[180px] truncate text-muted-foreground/70">
|
|
313
|
+
{savedDetail}
|
|
314
|
+
</span>
|
|
315
|
+
)}
|
|
316
|
+
<Pencil className="h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
317
|
+
</button>
|
|
318
|
+
) : (
|
|
319
|
+
<div className="flex items-center gap-1.5">
|
|
320
|
+
{/* Inline thumb buttons */}
|
|
321
|
+
<button
|
|
322
|
+
type="button"
|
|
323
|
+
onClick={() => handleThumbClick("up")}
|
|
324
|
+
className={cn(
|
|
325
|
+
"p-1 rounded transition-colors",
|
|
326
|
+
thumbState === "up"
|
|
327
|
+
? "text-foreground bg-muted"
|
|
328
|
+
: "text-muted-foreground/40 hover:text-foreground hover:bg-muted/50",
|
|
329
|
+
)}
|
|
330
|
+
title="This factor is accurate"
|
|
331
|
+
data-testid={`factor-thumb-up-${factor.key}`}
|
|
332
|
+
>
|
|
333
|
+
<ThumbsUp className="h-[10px] w-[10px]" />
|
|
334
|
+
</button>
|
|
335
|
+
<button
|
|
336
|
+
type="button"
|
|
337
|
+
onClick={() => handleThumbClick("down")}
|
|
338
|
+
className={cn(
|
|
339
|
+
"p-1 rounded transition-colors",
|
|
340
|
+
thumbState === "down"
|
|
341
|
+
? "text-red-600 bg-red-50"
|
|
342
|
+
: "text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50",
|
|
343
|
+
)}
|
|
344
|
+
title="Report issue with this factor"
|
|
345
|
+
data-testid={`factor-thumb-down-${factor.key}`}
|
|
346
|
+
>
|
|
347
|
+
<ThumbsDown className="h-[10px] w-[10px]" />
|
|
348
|
+
</button>
|
|
349
|
+
|
|
350
|
+
{/* Transient "Saved" pill */}
|
|
351
|
+
{saved && (
|
|
352
|
+
<span
|
|
353
|
+
className="inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600"
|
|
354
|
+
role="status"
|
|
355
|
+
data-testid={`factor-saved-${factor.key}`}
|
|
356
|
+
>
|
|
357
|
+
<Check className="h-[10px] w-[10px]" />
|
|
358
|
+
Saved
|
|
359
|
+
</span>
|
|
360
|
+
)}
|
|
361
|
+
</div>
|
|
362
|
+
)}
|
|
363
|
+
|
|
364
|
+
{/* Inline detail input */}
|
|
365
|
+
{showInput && thumbState && (
|
|
366
|
+
<div className="mt-1.5">
|
|
367
|
+
<input
|
|
368
|
+
type="text"
|
|
369
|
+
value={detailText}
|
|
370
|
+
onChange={(e) => setDetailText(e.target.value)}
|
|
371
|
+
onKeyDown={(e) => {
|
|
372
|
+
if (e.key === "Enter") handleSubmitDetail()
|
|
373
|
+
if (e.key === "Escape") setShowInput(false)
|
|
374
|
+
}}
|
|
375
|
+
placeholder={
|
|
376
|
+
thumbState === "up"
|
|
377
|
+
? "What\u2019s accurate? (optional)"
|
|
378
|
+
: "What\u2019s wrong? (optional)"
|
|
379
|
+
}
|
|
380
|
+
className="w-full h-6 rounded border border-border bg-background px-2 text-[11px] text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring"
|
|
381
|
+
data-testid={`factor-detail-input-${factor.key}`}
|
|
382
|
+
/>
|
|
383
|
+
<div className="mt-1 flex items-center gap-1.5">
|
|
384
|
+
<button
|
|
385
|
+
type="button"
|
|
386
|
+
onClick={handleSubmitDetail}
|
|
387
|
+
className="bg-foreground text-background rounded px-2 py-0.5 text-[10px] font-semibold"
|
|
388
|
+
data-testid={`factor-submit-${factor.key}`}
|
|
389
|
+
>
|
|
390
|
+
Submit
|
|
391
|
+
</button>
|
|
392
|
+
<button
|
|
393
|
+
type="button"
|
|
394
|
+
onClick={() => setShowInput(false)}
|
|
395
|
+
className="border border-border rounded px-2 py-0.5 text-[10px] font-medium"
|
|
396
|
+
>
|
|
397
|
+
Cancel
|
|
398
|
+
</button>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
)}
|
|
402
|
+
</div>
|
|
403
|
+
</>
|
|
404
|
+
)}
|
|
227
405
|
</div>
|
|
228
406
|
)
|
|
229
407
|
}
|
|
@@ -241,6 +419,9 @@ export function SignalPriorityPopover({
|
|
|
241
419
|
feedbackChips,
|
|
242
420
|
onFeedbackSubmit,
|
|
243
421
|
className,
|
|
422
|
+
initialFactorFeedback,
|
|
423
|
+
onFactorFeedback,
|
|
424
|
+
initialPriorityFeedback,
|
|
244
425
|
}: SignalPriorityPopoverProps) {
|
|
245
426
|
const urgencyLabel = getSignalScoreUrgencyLabel(score, providedLabel)
|
|
246
427
|
const scoreRange = scoreRangeForUrgency(urgencyLabel)
|
|
@@ -252,6 +433,9 @@ export function SignalPriorityPopover({
|
|
|
252
433
|
const triggerHover = URGENCY_TRIGGER_HOVER[urgencyLabel]
|
|
253
434
|
const triggerOpen = URGENCY_TRIGGER_OPEN[urgencyLabel]
|
|
254
435
|
|
|
436
|
+
// Derive a stable feedbackKey for the footer from score + urgencyLabel
|
|
437
|
+
const footerFeedbackKey = `priority-${score}-${urgencyLabel}`
|
|
438
|
+
|
|
255
439
|
return (
|
|
256
440
|
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
|
|
257
441
|
<PopoverPrimitive.Trigger asChild>
|
|
@@ -332,7 +516,12 @@ export function SignalPriorityPopover({
|
|
|
332
516
|
{/* Factor rows */}
|
|
333
517
|
<div className="divide-y divide-border/40">
|
|
334
518
|
{factors.map((factor) => (
|
|
335
|
-
<PriorityFactorRow
|
|
519
|
+
<PriorityFactorRow
|
|
520
|
+
key={factor.key}
|
|
521
|
+
factor={factor}
|
|
522
|
+
initialFeedback={initialFactorFeedback?.[factor.key]}
|
|
523
|
+
onFactorFeedback={onFactorFeedback}
|
|
524
|
+
/>
|
|
336
525
|
))}
|
|
337
526
|
</div>
|
|
338
527
|
</>
|
|
@@ -349,6 +538,8 @@ export function SignalPriorityPopover({
|
|
|
349
538
|
negativeChips={feedbackChips ?? DEFAULT_PRIORITY_FEEDBACK_CHIPS}
|
|
350
539
|
positivePrompt="Thanks. Anything to keep about this score?"
|
|
351
540
|
className="px-4 py-3"
|
|
541
|
+
initialFeedback={initialPriorityFeedback}
|
|
542
|
+
feedbackKey={footerFeedbackKey}
|
|
352
543
|
/>
|
|
353
544
|
</div>
|
|
354
545
|
)}
|
package/src/index.ts
CHANGED
|
@@ -38,7 +38,7 @@ export * from "./components/dropdown-menu"
|
|
|
38
38
|
export * from "./components/empty-state"
|
|
39
39
|
export * from "./components/entity-panel"
|
|
40
40
|
export { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions } from "./components/feedback-primitives"
|
|
41
|
-
export type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData } from "./components/feedback-primitives"
|
|
41
|
+
export type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData, PersistedFeedbackData } from "./components/feedback-primitives"
|
|
42
42
|
export { SignalPriorityPopover } from "./components/signal-priority-popover"
|
|
43
43
|
export type { SignalPriorityPopoverProps, PriorityFactor } from "./components/signal-priority-popover"
|
|
44
44
|
export * from "./components/filter-chip"
|
|
@@ -3,41 +3,83 @@ import { describe, expect, it } from "vitest"
|
|
|
3
3
|
import { displayName, getInitials, shortName } from "../user-display"
|
|
4
4
|
|
|
5
5
|
describe("displayName", () => {
|
|
6
|
-
it("
|
|
6
|
+
it("returns first_name + last_name when both are present", () => {
|
|
7
7
|
expect(
|
|
8
|
-
displayName({ first_name: "Sarah", last_name: "Mitchell", name: "S Mitchell", email: "sarah@example.com" })
|
|
8
|
+
displayName({ first_name: "Sarah", last_name: "Mitchell", name: "S Mitchell", email: "sarah@example.com" })
|
|
9
9
|
).toBe("Sarah Mitchell")
|
|
10
10
|
})
|
|
11
11
|
|
|
12
|
-
it("
|
|
13
|
-
expect(displayName({ first_name: "Sarah", last_name: null, name: null, email: "sarah@example.com" })).toBe(
|
|
14
|
-
|
|
12
|
+
it("returns first_name alone when last_name is missing", () => {
|
|
13
|
+
expect(displayName({ first_name: "Sarah", last_name: null, name: null, email: "sarah@example.com" })).toBe(
|
|
14
|
+
"Sarah"
|
|
15
|
+
)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it("falls back to name when first_name is missing", () => {
|
|
19
|
+
expect(displayName({ first_name: null, last_name: null, name: "Sarah Mitchell", email: "sarah@example.com" })).toBe(
|
|
20
|
+
"Sarah Mitchell"
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it("falls back to email local-part when name fields are missing or empty", () => {
|
|
15
25
|
expect(displayName({ first_name: "", last_name: "", name: "", email: "sarah@example.com" })).toBe("sarah")
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("falls back to Unknown user when profile fields are absent", () => {
|
|
16
29
|
expect(displayName({})).toBe("Unknown user")
|
|
17
30
|
expect(displayName()).toBe("Unknown user")
|
|
18
31
|
})
|
|
32
|
+
|
|
33
|
+
it("ignores last_name if first_name is missing", () => {
|
|
34
|
+
expect(displayName({ first_name: null, last_name: "Mitchell", name: "Old Name", email: "sarah@example.com" })).toBe(
|
|
35
|
+
"Old Name"
|
|
36
|
+
)
|
|
37
|
+
})
|
|
19
38
|
})
|
|
20
39
|
|
|
21
40
|
describe("getInitials", () => {
|
|
22
|
-
it("
|
|
41
|
+
it("returns uppercased initials from first_name and last_name", () => {
|
|
23
42
|
expect(getInitials({ first_name: "joe", last_name: "kim", email: "joe@example.com" })).toBe("JK")
|
|
24
43
|
})
|
|
25
44
|
|
|
26
|
-
it("
|
|
27
|
-
expect(getInitials({ first_name: null, last_name: null, name: "Sarah Mitchell", email: "sarah@example.com" })).toBe(
|
|
45
|
+
it("splits name into two parts for initials when first/last are not set", () => {
|
|
46
|
+
expect(getInitials({ first_name: null, last_name: null, name: "Sarah Mitchell", email: "sarah@example.com" })).toBe(
|
|
47
|
+
"SM"
|
|
48
|
+
)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("uses first two characters of a single-word name", () => {
|
|
28
52
|
expect(getInitials({ first_name: null, last_name: null, name: "Sarah", email: "sarah@example.com" })).toBe("SA")
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it("falls back to first two email local characters when no name fields are set", () => {
|
|
29
56
|
expect(getInitials({ first_name: null, last_name: null, name: null, email: "zz@example.com" })).toBe("ZZ")
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("returns question mark when no initials can be derived", () => {
|
|
30
60
|
expect(getInitials({})).toBe("?")
|
|
31
61
|
expect(getInitials()).toBe("?")
|
|
32
62
|
})
|
|
63
|
+
|
|
64
|
+
it("handles multi-word name taking the first two initials", () => {
|
|
65
|
+
expect(getInitials({ name: "Mary Jane Watson", email: "mj@example.com" })).toBe("MJ")
|
|
66
|
+
})
|
|
33
67
|
})
|
|
34
68
|
|
|
35
69
|
describe("shortName", () => {
|
|
36
|
-
it("returns
|
|
70
|
+
it("returns 'First L.' when first and last are present", () => {
|
|
37
71
|
expect(shortName({ first_name: "Sarah", last_name: "Mitchell", email: "sarah@example.com" })).toBe("Sarah M.")
|
|
38
72
|
})
|
|
39
73
|
|
|
40
|
-
it("falls back to displayName when
|
|
41
|
-
expect(shortName({
|
|
74
|
+
it("falls back to displayName when last_name is unavailable", () => {
|
|
75
|
+
expect(shortName({ first_name: "Sarah", last_name: null, name: null, email: "sarah@example.com" })).toBe("Sarah")
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("falls back to email local-part when all names are missing", () => {
|
|
79
|
+
expect(shortName({ first_name: null, last_name: null, name: null, email: "sarah@example.com" })).toBe("sarah")
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it("falls back to Unknown user when no profile fields are present", () => {
|
|
83
|
+
expect(shortName({})).toBe("Unknown user")
|
|
42
84
|
})
|
|
43
85
|
})
|
|
@@ -16,7 +16,7 @@ import type { TimelineEvent } from "../components/timeline-activity"
|
|
|
16
16
|
import type { ApprovalState } from "../components/signal-feedback-inline"
|
|
17
17
|
import type { LucideIcon } from "lucide-react"
|
|
18
18
|
import type { PriorityFactor } from "../components/signal-priority-popover"
|
|
19
|
-
import type { FeedbackChipTree, FeedbackSubmitData } from "../components/feedback-primitives"
|
|
19
|
+
import type { FeedbackChipTree, FeedbackSubmitData, PersistedFeedbackData } from "../components/feedback-primitives"
|
|
20
20
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
// Shared
|
|
@@ -57,6 +57,10 @@ export interface SignalScoreExplanationSignal {
|
|
|
57
57
|
counterparty?: string
|
|
58
58
|
/** Component breakdown for combined signals. */
|
|
59
59
|
components?: Array<{ type: string; count: number }>
|
|
60
|
+
/** Current balance value (e.g., "$3.0M") for balance context strip. */
|
|
61
|
+
currentBalance?: string
|
|
62
|
+
/** Additional balance context text (e.g., "down from $23M"). */
|
|
63
|
+
balanceContext?: string
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
export interface SignalScoreExplanationBucket {
|
|
@@ -97,6 +101,12 @@ export interface SignalScoreData {
|
|
|
97
101
|
/** @deprecated The compact score UX no longer renders score-level thumbs by default. */
|
|
98
102
|
initialScoreFeedback?: { type: "up" | "down"; pills: string[]; detail: string } | null
|
|
99
103
|
initialFactorFeedback?: Record<string, { type: "up" | "down"; detail: string }>
|
|
104
|
+
/** Factor-level feedback for the priority popover rows (keyed by factor key). */
|
|
105
|
+
initialFactorPopoverFeedback?: Record<string, { type: "up" | "down"; detail: string; ownershipLabel?: string }>
|
|
106
|
+
/** Persisted bucket-level feedback, keyed by bucket key. */
|
|
107
|
+
initialBucketFeedback?: Record<string, PersistedFeedbackData>
|
|
108
|
+
/** Persisted priority-level feedback for the popover footer. */
|
|
109
|
+
initialPriorityFeedback?: PersistedFeedbackData | null
|
|
100
110
|
/** Priority factors for the popover breakdown. */
|
|
101
111
|
priorityFactors?: PriorityFactor[]
|
|
102
112
|
/** Negative feedback chip tree for the priority popover. */
|
|
@@ -272,6 +272,9 @@ export function DetailView({
|
|
|
272
272
|
metaText={undefined}
|
|
273
273
|
feedbackChips={signalData.priorityFeedbackChips}
|
|
274
274
|
onFeedbackSubmit={signalData.onPriorityFeedback}
|
|
275
|
+
initialFactorFeedback={signalData.initialFactorPopoverFeedback}
|
|
276
|
+
onFactorFeedback={signalData.onFactorFeedback}
|
|
277
|
+
initialPriorityFeedback={signalData.initialPriorityFeedback}
|
|
275
278
|
/>
|
|
276
279
|
{signalData.timeChipLabel && (
|
|
277
280
|
<Badge variant="outline" title={signalData.timeChipDetail ?? undefined}>
|