@handled-ai/design-system 0.18.2 → 0.18.3

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.
Files changed (37) hide show
  1. package/dist/charts/chart.d.ts +1 -1
  2. package/dist/components/feedback-primitives.d.ts +2 -21
  3. package/dist/components/feedback-primitives.js +6 -90
  4. package/dist/components/feedback-primitives.js.map +1 -1
  5. package/dist/components/score-why-chips.d.ts +1 -1
  6. package/dist/components/score-why-chips.js +5 -26
  7. package/dist/components/score-why-chips.js.map +1 -1
  8. package/dist/components/signal-priority-popover.d.ts +1 -1
  9. package/dist/components/signal-priority-popover.js +7 -172
  10. package/dist/components/signal-priority-popover.js.map +1 -1
  11. package/dist/components/timeline-activity.d.ts +16 -1
  12. package/dist/components/timeline-activity.js +69 -1
  13. package/dist/components/timeline-activity.js.map +1 -1
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.js.map +1 -1
  16. package/dist/prototype/index.d.ts +1 -1
  17. package/dist/prototype/prototype-accounts-view.d.ts +1 -1
  18. package/dist/prototype/prototype-admin-view.d.ts +1 -1
  19. package/dist/prototype/prototype-config.d.ts +1 -1
  20. package/dist/prototype/prototype-inbox-view.d.ts +12 -2
  21. package/dist/prototype/prototype-inbox-view.js +102 -37
  22. package/dist/prototype/prototype-inbox-view.js.map +1 -1
  23. package/dist/prototype/prototype-insights-view.d.ts +1 -1
  24. package/dist/prototype/prototype-shell.d.ts +1 -1
  25. package/dist/{signal-priority-popover-DWaAMhPI.d.ts → signal-priority-popover-DQ_VuHac.d.ts} +2 -26
  26. package/package.json +1 -3
  27. package/src/components/__tests__/timeline-activity.test.tsx +137 -0
  28. package/src/components/feedback-primitives.tsx +26 -148
  29. package/src/components/score-why-chips.tsx +2 -28
  30. package/src/components/signal-priority-popover.tsx +3 -194
  31. package/src/components/timeline-activity.tsx +112 -1
  32. package/src/index.ts +1 -1
  33. package/src/prototype/__tests__/detail-view-attention.test.tsx +2 -2
  34. package/src/prototype/__tests__/detail-view-timeline-system-events.test.tsx +322 -0
  35. package/src/prototype/prototype-config.ts +1 -11
  36. package/src/prototype/prototype-inbox-view.tsx +131 -33
  37. package/src/components/__tests__/wit-636-feedback-states.test.tsx +0 -546
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { PersistedFeedbackData, FeedbackChipTree, FeedbackSubmitData } from './components/feedback-primitives.js';
2
+ import { FeedbackChipTree, FeedbackSubmitData } from './components/feedback-primitives.js';
3
3
  import { SidebarNavSection } from './components/quick-action-sidebar-nav.js';
4
4
  import { ScoreFactor } from './components/score-breakdown.js';
5
5
  import { SuggestedContact, SuggestedAction } from './components/suggested-actions.js';
@@ -48,10 +48,6 @@ interface SignalScoreExplanationSignal {
48
48
  type: string;
49
49
  count: number;
50
50
  }>;
51
- /** Current balance value (e.g., "$3.0M") for balance context strip. */
52
- currentBalance?: string;
53
- /** Additional balance context text (e.g., "down from $23M"). */
54
- balanceContext?: string;
55
51
  }
56
52
  interface SignalScoreExplanationBucket {
57
53
  key: string;
@@ -97,16 +93,6 @@ interface SignalScoreData {
97
93
  type: "up" | "down";
98
94
  detail: string;
99
95
  }>;
100
- /** Factor-level feedback for the priority popover rows (keyed by factor key). */
101
- initialFactorPopoverFeedback?: Record<string, {
102
- type: "up" | "down";
103
- detail: string;
104
- ownershipLabel?: string;
105
- }>;
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;
110
96
  /** Priority factors for the popover breakdown. */
111
97
  priorityFactors?: PriorityFactor[];
112
98
  /** Negative feedback chip tree for the priority popover. */
@@ -398,17 +384,7 @@ interface SignalPriorityPopoverProps {
398
384
  feedbackChips?: FeedbackChipTree[];
399
385
  onFeedbackSubmit?: (data: FeedbackSubmitData) => void;
400
386
  className?: string;
401
- /** Persisted factor-level feedback (keyed by factor key). */
402
- initialFactorFeedback?: Record<string, {
403
- type: "up" | "down";
404
- detail: string;
405
- ownershipLabel?: string;
406
- }>;
407
- /** Callback when user submits factor-level feedback. */
408
- onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void;
409
- /** Persisted priority-level feedback for the footer. */
410
- initialPriorityFeedback?: PersistedFeedbackData | null;
411
387
  }
412
- declare function SignalPriorityPopover({ score, urgencyLabel: providedLabel, urgencyExplanation, factors, metaText, feedbackChips, onFeedbackSubmit, className, initialFactorFeedback, onFactorFeedback, initialPriorityFeedback, }: SignalPriorityPopoverProps): React.JSX.Element;
388
+ declare function SignalPriorityPopover({ score, urgencyLabel: providedLabel, urgencyExplanation, factors, metaText, feedbackChips, onFeedbackSubmit, className, }: SignalPriorityPopoverProps): React.JSX.Element;
413
389
 
414
390
  export { type AccountFilterTab as A, type BriefStyleVariant as B, type EntityPanelConfig as E, type InboxDetailSections as I, type PriorityFactor as P, type QueueItem as Q, SignalPriorityPopover as S, type WorkQueueViewConfig as W, type AccountsViewConfig as a, type AdminTab as b, type AdminViewConfig as c, type EntityPanelSection as d, type InboxSortOption as e, type InboxViewConfig as f, type InsightsCustomTab as g, type InsightsViewConfig as h, type PrototypeBrandConfig as i, type PrototypeConfig as j, type SignalPriorityPopoverProps as k, type SignalScoreData as l, type SignalScoreExplanationBucket as m, type SignalScoreExplanationSignal as n, type SignalScoreUrgencyLabel as o };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handled-ai/design-system",
3
- "version": "0.18.2",
3
+ "version": "0.18.3",
4
4
  "description": "Handled UI component library (shadcn-style, New York)",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@9.12.0",
@@ -171,11 +171,9 @@
171
171
  "eslint": "^9.32.0",
172
172
  "eslint-config-next": "15.3.1",
173
173
  "happy-dom": "^20.9.0",
174
- "lucide-react": "^1.16.0",
175
174
  "next": "15.5.9",
176
175
  "react": "19.1.0",
177
176
  "react-dom": "19.1.0",
178
- "recharts": "^3.8.1",
179
177
  "shadcn": "^3.0.0",
180
178
  "tailwindcss": "^4.1.11",
181
179
  "three": "^0.183.1",
@@ -0,0 +1,137 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import React from "react"
3
+ import { render, screen } from "@testing-library/react"
4
+ import {
5
+ TimelineActivity,
6
+ TONE_CLASSES,
7
+ type TimelineEvent,
8
+ } from "../timeline-activity"
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+
14
+ function minimal(overrides: Partial<TimelineEvent> = {}): TimelineEvent {
15
+ return {
16
+ id: "e1",
17
+ icon: React.createElement("span", { "data-testid": "icon" }, "⚡"),
18
+ title: "Test event",
19
+ time: "2h ago",
20
+ ...overrides,
21
+ }
22
+ }
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Tests
26
+ // ---------------------------------------------------------------------------
27
+
28
+ describe("TimelineActivity", () => {
29
+ // --- Tone rendering ---
30
+
31
+ it("renders red dot classes when tone is 'red'", () => {
32
+ const event = minimal({ tone: "red" })
33
+ const { container } = render(<TimelineActivity events={[event]} />)
34
+ const dot = container.querySelector('[data-testid="timeline-dot"]')!
35
+ expect(dot).not.toBeNull()
36
+ const cls = dot.className
37
+ // Should contain all the red tone dot classes
38
+ expect(cls).toContain("bg-red-50")
39
+ expect(cls).toContain("border-red-200")
40
+ // Should contain the red icon classes
41
+ expect(cls).toContain("text-red-600")
42
+ // Should NOT contain neutral classes
43
+ expect(cls).not.toContain("border-border/60")
44
+ expect(cls).not.toContain("text-muted-foreground")
45
+ })
46
+
47
+ it("renders neutral dot classes when tone is absent", () => {
48
+ const event = minimal()
49
+ const { container } = render(<TimelineActivity events={[event]} />)
50
+ const dot = container.querySelector('[data-testid="timeline-dot"]')!
51
+ expect(dot).not.toBeNull()
52
+ const cls = dot.className
53
+ expect(cls).toContain("border-border/60")
54
+ expect(cls).toContain("bg-background")
55
+ expect(cls).toContain("text-muted-foreground")
56
+ })
57
+
58
+ // --- Actor byline ---
59
+
60
+ it("renders actor byline with name when actor.kind is 'user'", () => {
61
+ const event = minimal({
62
+ actor: { kind: "user", name: "Alice" },
63
+ })
64
+ render(<TimelineActivity events={[event]} />)
65
+ const byline = screen.getByTestId("actor-byline")
66
+ expect(byline).not.toBeNull()
67
+ expect(byline.textContent).toContain("Alice")
68
+ expect(byline.textContent).toContain("performed this action")
69
+ })
70
+
71
+ it("renders no byline when actor.kind is 'system'", () => {
72
+ const event = minimal({
73
+ actor: { kind: "system" },
74
+ })
75
+ const { container } = render(<TimelineActivity events={[event]} />)
76
+ const byline = container.querySelector('[data-testid="actor-byline"]')
77
+ expect(byline).toBeNull()
78
+ })
79
+
80
+ it("renders 'Integration' text when actor.kind is 'integration'", () => {
81
+ const event = minimal({
82
+ actor: { kind: "integration" },
83
+ })
84
+ render(<TimelineActivity events={[event]} />)
85
+ const byline = screen.getByTestId("actor-byline")
86
+ expect(byline).not.toBeNull()
87
+ expect(byline.textContent).toContain("Integration")
88
+ })
89
+
90
+ it("renders custom verb for user actor", () => {
91
+ const event = minimal({
92
+ actor: { kind: "user", name: "Bob", verb: "approved this case" },
93
+ })
94
+ render(<TimelineActivity events={[event]} />)
95
+ const byline = screen.getByTestId("actor-byline")
96
+ expect(byline.textContent).toContain("approved this case")
97
+ })
98
+
99
+ it("renders no byline when actor is absent", () => {
100
+ const event = minimal()
101
+ const { container } = render(<TimelineActivity events={[event]} />)
102
+ const byline = container.querySelector('[data-testid="actor-byline"]')
103
+ expect(byline).toBeNull()
104
+ })
105
+
106
+ // --- Backwards compatibility ---
107
+
108
+ it("renders correctly with minimal TimelineEvent (only id, icon, title, time)", () => {
109
+ const event: TimelineEvent = {
110
+ id: "min-1",
111
+ icon: React.createElement("span", null, "📌"),
112
+ title: "Minimal event",
113
+ time: "5m ago",
114
+ }
115
+ const { container } = render(<TimelineActivity events={[event]} />)
116
+ // Should render without errors
117
+ expect(container.textContent).toContain("Minimal event")
118
+ expect(container.textContent).toContain("5m ago")
119
+ // Dot should have neutral classes
120
+ const dot = container.querySelector('[data-testid="timeline-dot"]')!
121
+ expect(dot.className).toContain("border-border/60")
122
+ // No byline
123
+ const byline = container.querySelector('[data-testid="actor-byline"]')
124
+ expect(byline).toBeNull()
125
+ })
126
+
127
+ // --- TONE_CLASSES export ---
128
+
129
+ it("exports TONE_CLASSES with all expected tones", () => {
130
+ const tones = ["red", "amber", "emerald", "violet", "blue", "slate", "salesforce", "gmail"] as const
131
+ for (const tone of tones) {
132
+ expect(TONE_CLASSES[tone]).toBeDefined()
133
+ expect(TONE_CLASSES[tone].dot).toBeTruthy()
134
+ expect(TONE_CLASSES[tone].icon).toBeTruthy()
135
+ }
136
+ })
137
+ })
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from "react"
4
- import { ThumbsUp, ThumbsDown, Check, Pencil } from "lucide-react"
4
+ import { ThumbsUp, ThumbsDown } from "lucide-react"
5
5
  import { cn } from "../lib/utils"
6
6
 
7
7
  // ---------------------------------------------------------------------------
@@ -25,19 +25,6 @@ export interface FeedbackSubmitData {
25
25
  detail: string
26
26
  }
27
27
 
28
- /**
29
- * Persisted feedback data from a previous submission, used to hydrate the
30
- * footer into its "already submitted" visual state.
31
- */
32
- export interface PersistedFeedbackData {
33
- sentiment: "positive" | "negative"
34
- reasonTop?: string
35
- reasonSub?: string
36
- pills?: string[]
37
- detail?: string
38
- ownershipLabel: "Your feedback" | "Team feedback"
39
- }
40
-
41
28
  /**
42
29
  * Defines a tier-1 chip that may have tier-2 sub-chips.
43
30
  */
@@ -198,13 +185,6 @@ export interface FeedbackFooterProps {
198
185
  negativeChips?: FeedbackChipTree[]
199
186
  positiveChips?: string[]
200
187
  className?: string
201
- /** Pre-existing feedback to hydrate from (e.g. after page reload). */
202
- initialFeedback?: PersistedFeedbackData | null
203
- /** Label shown in the transient confirmation pill after submit. */
204
- submittedLabel?: string
205
- /** Stable key for syncing initialFeedback into local state. When this
206
- * changes, the component resets to the new initialFeedback value. */
207
- feedbackKey?: string
208
188
  }
209
189
 
210
190
  const SENTIMENT_BUTTON_ACTIVE: Record<"positive" | "negative", string> = {
@@ -225,9 +205,6 @@ export function FeedbackFooter({
225
205
  negativeChips = [],
226
206
  positiveChips = [],
227
207
  className,
228
- initialFeedback,
229
- submittedLabel = "Saved",
230
- feedbackKey,
231
208
  }: FeedbackFooterProps) {
232
209
  const [expanded, setExpanded] = React.useState(false)
233
210
  const [selectedTier1, setSelectedTier1] = React.useState<string | null>(null)
@@ -237,43 +214,6 @@ export function FeedbackFooter({
237
214
  const [activeTreeIndex, setActiveTreeIndex] = React.useState<number | null>(
238
215
  null,
239
216
  )
240
- /** Transient "Saved" confirmation — shown after successful submit. */
241
- const [submitted, setSubmitted] = React.useState(false)
242
- /** Persisted feedback shown as a clickable indicator (survives reload). */
243
- const [persisted, setPersisted] = React.useState<PersistedFeedbackData | null>(
244
- initialFeedback ?? null,
245
- )
246
- /** Tracks whether the user is actively editing (to guard against prop overwrites). */
247
- const [isEditing, setIsEditing] = React.useState(false)
248
- /** Track the last synced feedbackKey to detect key changes. */
249
- const lastKeyRef = React.useRef<string | undefined>(feedbackKey)
250
-
251
- // Sync initialFeedback into local state via useEffect keyed on feedbackKey.
252
- // When feedbackKey changes, reset to new target. Preserve active edits
253
- // when feedbackKey stays the same.
254
- React.useEffect(() => {
255
- const keyChanged = feedbackKey !== lastKeyRef.current
256
- lastKeyRef.current = feedbackKey
257
-
258
- if (keyChanged) {
259
- // Key changed — full reset to new target
260
- setPersisted(initialFeedback ?? null)
261
- setSubmitted(false)
262
- setExpanded(false)
263
- setIsEditing(false)
264
- if (initialFeedback) {
265
- onFeedbackChange(initialFeedback.sentiment)
266
- } else {
267
- onFeedbackChange(null)
268
- }
269
- } else if (!isEditing) {
270
- // Same key, not actively editing — safe to sync
271
- setPersisted(initialFeedback ?? null)
272
- if (initialFeedback) {
273
- onFeedbackChange(initialFeedback.sentiment)
274
- }
275
- }
276
- }, [initialFeedback, feedbackKey]) // eslint-disable-line react-hooks/exhaustive-deps -- reads isEditing as guard, not trigger
277
217
 
278
218
  // Reset state when feedback collapses
279
219
  const resetState = React.useCallback(() => {
@@ -283,7 +223,6 @@ export function FeedbackFooter({
283
223
  setAdditionalPills([])
284
224
  setDetailText("")
285
225
  setActiveTreeIndex(null)
286
- setIsEditing(false)
287
226
  }, [])
288
227
 
289
228
  const handleSentimentClick = React.useCallback(
@@ -292,26 +231,10 @@ export function FeedbackFooter({
292
231
  // Reset chip state when switching sentiment, then expand
293
232
  resetState()
294
233
  setExpanded(true)
295
- setSubmitted(false)
296
- setPersisted(null)
297
- setIsEditing(true)
298
234
  },
299
235
  [onFeedbackChange, resetState],
300
236
  )
301
237
 
302
- /** Open the persisted indicator for editing. */
303
- const handlePersistedClick = React.useCallback(() => {
304
- if (!persisted) return
305
- onFeedbackChange(persisted.sentiment)
306
- setSelectedTier1(persisted.reasonTop ?? null)
307
- setSelectedTier2(persisted.reasonSub ?? null)
308
- setAdditionalPills(persisted.pills ?? [])
309
- setDetailText(persisted.detail ?? "")
310
- setExpanded(true)
311
- setSubmitted(false)
312
- setIsEditing(true)
313
- }, [persisted, onFeedbackChange])
314
-
315
238
  const handleTier1Toggle = React.useCallback(
316
239
  (chipLabel: string) => {
317
240
  if (selectedTier1 === chipLabel) {
@@ -372,16 +295,7 @@ export function FeedbackFooter({
372
295
  pills: additionalPills,
373
296
  detail: detailText,
374
297
  })
375
- // Show transient "Saved" confirmation
376
- setSubmitted(true)
377
- // Collapse expansion but keep sentiment visible
378
- setExpanded(false)
379
- setSelectedTier1(null)
380
- setSelectedTier2(null)
381
- setAdditionalPills([])
382
- setDetailText("")
383
- setActiveTreeIndex(null)
384
- setIsEditing(false)
298
+ resetState()
385
299
  }, [
386
300
  feedback,
387
301
  selectedTier1,
@@ -389,6 +303,7 @@ export function FeedbackFooter({
389
303
  additionalPills,
390
304
  detailText,
391
305
  onSubmit,
306
+ resetState,
392
307
  ])
393
308
 
394
309
  const handleCancel = React.useCallback(() => {
@@ -408,75 +323,38 @@ export function FeedbackFooter({
408
323
  const activeTree =
409
324
  activeTreeIndex !== null ? negativeChips[activeTreeIndex] : null
410
325
 
411
- // Determine if we should show the persisted indicator instead of bare buttons
412
- const showPersistedIndicator = persisted && !expanded && !submitted
413
-
414
326
  return (
415
327
  <div className={cn("space-y-3", className)}>
416
328
  {/* Sentiment buttons + meta text bar */}
417
329
  <div className="flex items-center justify-between">
418
- {showPersistedIndicator ? (
419
- /* Persisted feedback indicator — clickable to reopen editor */
330
+ <div className="flex items-center gap-3">
420
331
  <button
421
332
  type="button"
422
- onClick={handlePersistedClick}
423
- className="group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors"
424
- data-testid="persisted-feedback-indicator"
425
- >
426
- <span className="font-medium">{persisted.ownershipLabel}:</span>
427
- {persisted.sentiment === "positive" ? (
428
- <ThumbsUp className="h-[11px] w-[11px]" />
429
- ) : (
430
- <ThumbsDown className="h-[11px] w-[11px]" />
431
- )}
432
- {persisted.detail && (
433
- <span className="max-w-[200px] truncate text-muted-foreground/70">
434
- {persisted.detail}
435
- </span>
333
+ onClick={() => handleSentimentClick("positive")}
334
+ className={cn(
335
+ "flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors",
336
+ feedback === "positive"
337
+ ? SENTIMENT_BUTTON_ACTIVE.positive
338
+ : SENTIMENT_BUTTON_IDLE,
436
339
  )}
437
- <Pencil className="h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity" />
340
+ >
341
+ <ThumbsUp className="h-[11px] w-[11px]" />
342
+ Helpful
438
343
  </button>
439
- ) : (
440
- <div className="flex items-center gap-3">
441
- <button
442
- type="button"
443
- onClick={() => handleSentimentClick("positive")}
444
- className={cn(
445
- "flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors",
446
- feedback === "positive"
447
- ? SENTIMENT_BUTTON_ACTIVE.positive
448
- : SENTIMENT_BUTTON_IDLE,
449
- )}
450
- >
451
- <ThumbsUp className="h-[11px] w-[11px]" />
452
- Helpful
453
- </button>
454
- <button
455
- type="button"
456
- onClick={() => handleSentimentClick("negative")}
457
- className={cn(
458
- "flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors",
459
- feedback === "negative"
460
- ? SENTIMENT_BUTTON_ACTIVE.negative
461
- : SENTIMENT_BUTTON_IDLE,
462
- )}
463
- >
464
- <ThumbsDown className="h-[11px] w-[11px]" />
465
- Not helpful
466
- </button>
467
- {/* Transient "Saved" confirmation pill */}
468
- {submitted && feedback && (
469
- <span
470
- className="inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600"
471
- role="status"
472
- data-testid="feedback-submitted-pill"
473
- >
474
- <Check className="h-[11px] w-[11px]" />
475
- {submittedLabel}
476
- </span>
344
+ <button
345
+ type="button"
346
+ onClick={() => handleSentimentClick("negative")}
347
+ className={cn(
348
+ "flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors",
349
+ feedback === "negative"
350
+ ? SENTIMENT_BUTTON_ACTIVE.negative
351
+ : SENTIMENT_BUTTON_IDLE,
477
352
  )}
478
- </div>
479
- )}
353
+ >
354
+ <ThumbsDown className="h-[11px] w-[11px]" />
355
+ Not helpful
356
+ </button>
357
+ </div>
480
358
  {metaText && (
481
359
  <span className="text-[11px] text-muted-foreground">{metaText}</span>
482
360
  )}
@@ -15,7 +15,7 @@ import {
15
15
  } from "lucide-react"
16
16
  import type { LucideIcon } from "lucide-react"
17
17
  import { FeedbackFooter } from "./feedback-primitives"
18
- import type { FeedbackChipTree, FeedbackSubmitData, PersistedFeedbackData } from "./feedback-primitives"
18
+ import type { FeedbackChipTree, FeedbackSubmitData } from "./feedback-primitives"
19
19
  import { cn } from "../lib/utils"
20
20
  import type {
21
21
  QueueItem,
@@ -266,7 +266,6 @@ function StructuredSignalRow({ item, bucketKey, signal, tone, onOpenSignalBucket
266
266
  const IconComponent = resolveIcon(signal.signalTypeName)
267
267
  const toneClass = tone ? (SIGNAL_TONE_CLASSES[tone] ?? DEFAULT_TONE_CLASS) : DEFAULT_TONE_CLASS
268
268
  const isCombined = signal.signalTypeName === "combined_signal" && signal.components && signal.components.length > 0
269
- const hasBalance = Boolean(signal.currentBalance || signal.balanceContext)
270
269
 
271
270
  const rowContent = (
272
271
  <>
@@ -305,26 +304,6 @@ function StructuredSignalRow({ item, bucketKey, signal, tone, onOpenSignalBucket
305
304
 
306
305
  {/* Slot 5: Chevron */}
307
306
  <ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/60 transition-transform group-hover:translate-x-0.5 group-hover:text-foreground/50" />
308
-
309
- {/* Balance context strip — spans full row below grid columns */}
310
- {hasBalance && (
311
- <div
312
- className="col-span-full mt-0.5 text-[10px] text-muted-foreground/70"
313
- data-testid="balance-context-strip"
314
- >
315
- {signal.currentBalance && (
316
- <span>
317
- Current balance <span className="font-medium text-muted-foreground">{signal.currentBalance}</span>
318
- </span>
319
- )}
320
- {signal.balanceContext && (
321
- <span>
322
- {signal.currentBalance ? " · " : ""}
323
- {signal.balanceContext}
324
- </span>
325
- )}
326
- </div>
327
- )}
328
307
  </>
329
308
  )
330
309
 
@@ -426,11 +405,9 @@ interface WhyCardProps {
426
405
  panelId: string
427
406
  onOpenSignalBucket?: ScoreWhyChipsProps["onOpenSignalBucket"]
428
407
  onBucketFeedback?: (bucketKey: string, data: FeedbackSubmitData) => void
429
- /** Persisted bucket-level feedback to hydrate from. */
430
- initialBucketFeedback?: PersistedFeedbackData | null
431
408
  }
432
409
 
433
- function WhyCard({ bucket, signals, item, panelId, onOpenSignalBucket, onBucketFeedback, initialBucketFeedback }: WhyCardProps) {
410
+ function WhyCard({ bucket, signals, item, panelId, onOpenSignalBucket, onBucketFeedback }: WhyCardProps) {
434
411
  const [showAll, setShowAll] = React.useState(false)
435
412
  const [bucketFeedback, setBucketFeedback] = React.useState<"positive" | "negative" | null>(null)
436
413
  const totalCount = bucket.signalCount ?? signals.length
@@ -511,8 +488,6 @@ function WhyCard({ bucket, signals, item, panelId, onOpenSignalBucket, onBucketF
511
488
  negativeChips={BUCKET_NEGATIVE_CHIPS}
512
489
  negativePrompt="Was this bucket useful?"
513
490
  positivePrompt="Thanks! What was useful about this bucket?"
514
- initialFeedback={initialBucketFeedback}
515
- feedbackKey={bucket.key}
516
491
  />
517
492
  </div>
518
493
  )}
@@ -586,7 +561,6 @@ export function ScoreWhyChips({
586
561
  panelId={selectedPanelId}
587
562
  onOpenSignalBucket={onOpenSignalBucket}
588
563
  onBucketFeedback={signalData.onBucketFeedback}
589
- initialBucketFeedback={signalData.initialBucketFeedback?.[selectedBucket.key]}
590
564
  />
591
565
  )}
592
566
  </div>