@handled-ai/design-system 0.20.15 → 0.20.17

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.
@@ -20,12 +20,10 @@ interface CommentComposerProps {
20
20
  avatarUrl?: string | null;
21
21
  };
22
22
  placeholder?: string;
23
- /** Compact spacing for the case-panel timeline surface. Defaults preserve the standard composer sizing. */
24
- density?: "default" | "case-panel";
25
23
  /** Hint shown in the footer; defaults to the internal-note reassurance. */
26
24
  hint?: string;
27
25
  className?: string;
28
26
  }
29
- declare function CommentComposer({ onPost, author, placeholder, density, hint, className, }: CommentComposerProps): React.JSX.Element;
27
+ declare function CommentComposer({ onPost, author, placeholder, hint, className, }: CommentComposerProps): React.JSX.Element;
30
28
 
31
29
  export { CommentComposer, type CommentComposerProps };
@@ -13,7 +13,6 @@ function CommentComposer({
13
13
  onPost,
14
14
  author,
15
15
  placeholder = "Add a comment or internal note\u2026",
16
- density = "default",
17
16
  hint = "Internal note: only your team sees this",
18
17
  className
19
18
  }) {
@@ -22,8 +21,6 @@ function CommentComposer({
22
21
  const [focused, setFocused] = React.useState(false);
23
22
  const open = focused || text.length > 0;
24
23
  const canPost = text.trim().length > 0;
25
- const hasDraft = text.length > 0;
26
- const compact = density === "case-panel";
27
24
  const post = () => {
28
25
  const value = text.trim();
29
26
  if (!value) return;
@@ -62,11 +59,10 @@ function CommentComposer({
62
59
  onChange: (e) => setText(e.target.value),
63
60
  onFocus: () => setFocused(true),
64
61
  placeholder,
65
- rows: compact ? hasDraft ? 3 : 1 : open ? 4 : 1,
62
+ rows: open ? 4 : 1,
66
63
  className: cn(
67
- "resize-none rounded-none border-0 bg-transparent px-5 text-[15px] leading-6 shadow-none outline-none placeholder:text-muted-foreground/60 focus-visible:ring-0 focus-visible:ring-offset-0",
68
- compact ? open ? "py-4" : "py-3" : "py-4",
69
- compact ? hasDraft ? "!min-h-28" : open ? "!min-h-[76px]" : "!min-h-12" : open ? "min-h-32" : "min-h-14"
64
+ "resize-none rounded-none border-0 bg-transparent px-5 py-4 text-[15px] leading-6 shadow-none outline-none placeholder:text-muted-foreground/60 focus-visible:ring-0 focus-visible:ring-offset-0",
65
+ open ? "min-h-32" : "min-h-14"
70
66
  ),
71
67
  onKeyDown: (e) => {
72
68
  if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
@@ -76,7 +72,7 @@ function CommentComposer({
76
72
  }
77
73
  }
78
74
  ),
79
- open ? /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-3 border-t border-border bg-muted/10 px-5", compact ? "py-3.5" : "py-4"), children: [
75
+ open ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-border bg-muted/10 px-5 py-4", children: [
80
76
  /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2 text-sm text-muted-foreground", children: [
81
77
  /* @__PURE__ */ jsx(Lock, { size: 16, strokeWidth: 1.75 }),
82
78
  " ",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/comment-composer.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * comment-composer.tsx — an internal-note composer for the case activity\n * timeline. Posting a comment prepends an `operatorNote` event to the log\n * (wired by the consumer). Collapses to a single line; expands on focus or\n * when it has text. ⌘↵ / Ctrl↵ posts.\n *\n * Presentational: `onPost` does the work (the consumer persists the note and\n * adds it to the timeline). Reuses Avatar / Button / Textarea primitives.\n */\n\nimport * as React from \"react\"\nimport { Lock } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { getInitials } from \"../lib/user-display\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./avatar\"\nimport { Button } from \"./button\"\nimport { Textarea } from \"./textarea\"\n\nexport interface CommentComposerProps {\n /** Called with the trimmed note text when the operator posts. */\n onPost: (text: string) => void\n /** Current operator (for the avatar). */\n author?: { name?: string; email?: string; avatarUrl?: string | null }\n placeholder?: string\n /** Compact spacing for the case-panel timeline surface. Defaults preserve the standard composer sizing. */\n density?: \"default\" | \"case-panel\"\n /** Hint shown in the footer; defaults to the internal-note reassurance. */\n hint?: string\n className?: string\n}\n\nfunction CommentComposer({\n onPost,\n author,\n placeholder = \"Add a comment or internal note…\",\n density = \"default\",\n hint = \"Internal note: only your team sees this\",\n className,\n}: CommentComposerProps) {\n const [text, setText] = React.useState(\"\")\n const [focused, setFocused] = React.useState(false)\n const open = focused || text.length > 0\n const canPost = text.trim().length > 0\n const hasDraft = text.length > 0\n const compact = density === \"case-panel\"\n\n const post = () => {\n const value = text.trim()\n if (!value) return\n onPost(value)\n setText(\"\")\n setFocused(false)\n }\n\n return (\n <div\n data-slot=\"comment-composer\"\n data-open={open ? \"true\" : undefined}\n className={cn(\n \"flex items-start gap-4 rounded-xl transition-colors\",\n className\n )}\n >\n <Avatar size=\"sm\" className=\"mt-1\">\n {author?.avatarUrl ? <AvatarImage src={author.avatarUrl} alt={author.name ?? \"You\"} /> : null}\n <AvatarFallback className=\"bg-slate-700 text-[10px] font-semibold uppercase text-white dark:bg-slate-200 dark:text-slate-900\">\n {getInitials({ name: author?.name, email: author?.email })}\n </AvatarFallback>\n </Avatar>\n\n <div\n data-slot=\"comment-composer-shell\"\n className={cn(\n \"min-w-0 flex-1 rounded-xl border border-border bg-background transition-[box-shadow,border-color]\",\n open ? \"overflow-hidden shadow-sm\" : \"shadow-none\"\n )}\n >\n <Textarea\n data-slot=\"comment-composer-input\"\n value={text}\n onChange={(e) => setText(e.target.value)}\n onFocus={() => setFocused(true)}\n placeholder={placeholder}\n rows={compact ? (hasDraft ? 3 : 1) : (open ? 4 : 1)}\n className={cn(\n \"resize-none rounded-none border-0 bg-transparent px-5 text-[15px] leading-6 shadow-none outline-none placeholder:text-muted-foreground/60 focus-visible:ring-0 focus-visible:ring-offset-0\",\n compact ? (open ? \"py-4\" : \"py-3\") : \"py-4\",\n compact\n ? hasDraft\n ? \"!min-h-28\"\n : open\n ? \"!min-h-[76px]\"\n : \"!min-h-12\"\n : open\n ? \"min-h-32\"\n : \"min-h-14\"\n )}\n onKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === \"Enter\") {\n e.preventDefault()\n post()\n }\n }}\n />\n\n {open ? (\n <div className={cn(\"flex items-center justify-between gap-3 border-t border-border bg-muted/10 px-5\", compact ? \"py-3.5\" : \"py-4\")}>\n <span className=\"inline-flex items-center gap-2 text-sm text-muted-foreground\">\n <Lock size={16} strokeWidth={1.75} /> {hint}\n </span>\n <span className=\"flex items-center gap-3\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"px-2 text-sm font-medium text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={() => {\n setText(\"\")\n setFocused(false)\n }}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={!canPost}\n onClick={post}\n className=\"rounded-lg bg-foreground px-4 text-sm font-semibold text-background shadow-none hover:bg-foreground/90\"\n >\n Comment\n <kbd className=\"ml-1 rounded px-1 text-[10px] text-background/70\">⌘↵</kbd>\n </Button>\n </span>\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n\nexport { CommentComposer }\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/comment-composer.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * comment-composer.tsx — an internal-note composer for the case activity\n * timeline. Posting a comment prepends an `operatorNote` event to the log\n * (wired by the consumer). Collapses to a single line; expands on focus or\n * when it has text. ⌘↵ / Ctrl↵ posts.\n *\n * Presentational: `onPost` does the work (the consumer persists the note and\n * adds it to the timeline). Reuses Avatar / Button / Textarea primitives.\n */\n\nimport * as React from \"react\"\nimport { Lock } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { getInitials } from \"../lib/user-display\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./avatar\"\nimport { Button } from \"./button\"\nimport { Textarea } from \"./textarea\"\n\nexport interface CommentComposerProps {\n /** Called with the trimmed note text when the operator posts. */\n onPost: (text: string) => void\n /** Current operator (for the avatar). */\n author?: { name?: string; email?: string; avatarUrl?: string | null }\n placeholder?: string\n /** Hint shown in the footer; defaults to the internal-note reassurance. */\n hint?: string\n className?: string\n}\n\nfunction CommentComposer({\n onPost,\n author,\n placeholder = \"Add a comment or internal note…\",\n hint = \"Internal note: only your team sees this\",\n className,\n}: CommentComposerProps) {\n const [text, setText] = React.useState(\"\")\n const [focused, setFocused] = React.useState(false)\n const open = focused || text.length > 0\n const canPost = text.trim().length > 0\n\n const post = () => {\n const value = text.trim()\n if (!value) return\n onPost(value)\n setText(\"\")\n setFocused(false)\n }\n\n return (\n <div\n data-slot=\"comment-composer\"\n data-open={open ? \"true\" : undefined}\n className={cn(\n \"flex items-start gap-4 rounded-xl transition-colors\",\n className\n )}\n >\n <Avatar size=\"sm\" className=\"mt-1\">\n {author?.avatarUrl ? <AvatarImage src={author.avatarUrl} alt={author.name ?? \"You\"} /> : null}\n <AvatarFallback className=\"bg-slate-700 text-[10px] font-semibold uppercase text-white dark:bg-slate-200 dark:text-slate-900\">\n {getInitials({ name: author?.name, email: author?.email })}\n </AvatarFallback>\n </Avatar>\n\n <div\n data-slot=\"comment-composer-shell\"\n className={cn(\n \"min-w-0 flex-1 rounded-xl border border-border bg-background transition-[box-shadow,border-color]\",\n open ? \"overflow-hidden shadow-sm\" : \"shadow-none\"\n )}\n >\n <Textarea\n data-slot=\"comment-composer-input\"\n value={text}\n onChange={(e) => setText(e.target.value)}\n onFocus={() => setFocused(true)}\n placeholder={placeholder}\n rows={open ? 4 : 1}\n className={cn(\n \"resize-none rounded-none border-0 bg-transparent px-5 py-4 text-[15px] leading-6 shadow-none outline-none placeholder:text-muted-foreground/60 focus-visible:ring-0 focus-visible:ring-offset-0\",\n open ? \"min-h-32\" : \"min-h-14\"\n )}\n onKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === \"Enter\") {\n e.preventDefault()\n post()\n }\n }}\n />\n\n {open ? (\n <div className=\"flex items-center justify-between gap-3 border-t border-border bg-muted/10 px-5 py-4\">\n <span className=\"inline-flex items-center gap-2 text-sm text-muted-foreground\">\n <Lock size={16} strokeWidth={1.75} /> {hint}\n </span>\n <span className=\"flex items-center gap-3\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"px-2 text-sm font-medium text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={() => {\n setText(\"\")\n setFocused(false)\n }}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={!canPost}\n onClick={post}\n className=\"rounded-lg bg-foreground px-4 text-sm font-semibold text-background shadow-none hover:bg-foreground/90\"\n >\n Comment\n <kbd className=\"ml-1 rounded px-1 text-[10px] text-background/70\">⌘↵</kbd>\n </Button>\n </span>\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n\nexport { CommentComposer }\n"],"mappings":";AA6DM,SACuB,KADvB;AAjDN,YAAY,WAAW;AACvB,SAAS,YAAY;AAErB,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAC5B,SAAS,QAAQ,gBAAgB,mBAAmB;AACpD,SAAS,cAAc;AACvB,SAAS,gBAAgB;AAazB,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,OAAO;AAAA,EACP;AACF,GAAyB;AAtCzB;AAuCE,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,EAAE;AACzC,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,OAAO,WAAW,KAAK,SAAS;AACtC,QAAM,UAAU,KAAK,KAAK,EAAE,SAAS;AAErC,QAAM,OAAO,MAAM;AACjB,UAAM,QAAQ,KAAK,KAAK;AACxB,QAAI,CAAC,MAAO;AACZ,WAAO,KAAK;AACZ,YAAQ,EAAE;AACV,eAAW,KAAK;AAAA,EAClB;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,aAAW,OAAO,SAAS;AAAA,MAC3B,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,6BAAC,UAAO,MAAK,MAAK,WAAU,QACzB;AAAA,4CAAQ,aAAY,oBAAC,eAAY,KAAK,OAAO,WAAW,MAAK,YAAO,SAAP,YAAe,OAAO,IAAK;AAAA,UACzF,oBAAC,kBAAe,WAAU,qGACvB,sBAAY,EAAE,MAAM,iCAAQ,MAAM,OAAO,iCAAQ,MAAM,CAAC,GAC3D;AAAA,WACF;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,OAAO,8BAA8B;AAAA,YACvC;AAAA,YAEA;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,OAAO;AAAA,kBACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,kBACvC,SAAS,MAAM,WAAW,IAAI;AAAA,kBAC9B;AAAA,kBACA,MAAM,OAAO,IAAI;AAAA,kBACjB,WAAW;AAAA,oBACT;AAAA,oBACA,OAAO,aAAa;AAAA,kBACtB;AAAA,kBACA,WAAW,CAAC,MAAM;AAChB,yBAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,SAAS;AACjD,wBAAE,eAAe;AACjB,2BAAK;AAAA,oBACP;AAAA,kBACF;AAAA;AAAA,cACF;AAAA,cAEC,OACC,qBAAC,SAAI,WAAU,wFACb;AAAA,qCAAC,UAAK,WAAU,gEACd;AAAA,sCAAC,QAAK,MAAM,IAAI,aAAa,MAAM;AAAA,kBAAE;AAAA,kBAAE;AAAA,mBACzC;AAAA,gBACA,qBAAC,UAAK,WAAU,2BACd;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,SAAQ;AAAA,sBACR,MAAK;AAAA,sBACL,WAAU;AAAA,sBACV,SAAS,MAAM;AACb,gCAAQ,EAAE;AACV,mCAAW,KAAK;AAAA,sBAClB;AAAA,sBACD;AAAA;AAAA,kBAED;AAAA,kBACA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,MAAK;AAAA,sBACL,UAAU,CAAC;AAAA,sBACX,SAAS;AAAA,sBACT,WAAU;AAAA,sBACX;AAAA;AAAA,wBAEC,oBAAC,SAAI,WAAU,oDAAmD,0BAAE;AAAA;AAAA;AAAA,kBACtE;AAAA,mBACF;AAAA,iBACF,IACE;AAAA;AAAA;AAAA,QACN;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -1,9 +1,16 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  type RichTextAction = "undo" | "redo" | "font" | "bold" | "italic" | "underline" | "align" | "list" | "delete";
4
+ interface RichTextFontOption {
5
+ label: string;
6
+ value: string;
7
+ }
4
8
  interface RichTextToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
5
9
  onAction?: (action: RichTextAction) => void;
10
+ fontOptions?: RichTextFontOption[];
11
+ selectedFontFamily?: string;
12
+ onFontFamilyChange?: (fontFamily: string) => void;
6
13
  }
7
- declare function RichTextToolbar({ onAction, className, ...rest }: RichTextToolbarProps): React.JSX.Element;
14
+ declare function RichTextToolbar({ onAction, className, fontOptions, selectedFontFamily, onFontFamilyChange, ...rest }: RichTextToolbarProps): React.JSX.Element;
8
15
 
9
- export { type RichTextAction, RichTextToolbar, type RichTextToolbarProps };
16
+ export { type RichTextAction, type RichTextFontOption, RichTextToolbar, type RichTextToolbarProps };
@@ -33,6 +33,7 @@ var __objRest = (source, exclude) => {
33
33
  return target;
34
34
  };
35
35
  import { jsx, jsxs } from "react/jsx-runtime";
36
+ import * as React from "react";
36
37
  import { Undo2, Redo2, Bold, Italic, Underline, AlignLeft, List, Trash2, ChevronDown } from "lucide-react";
37
38
  import { cn } from "../lib/utils.js";
38
39
  function ToolbarButton({
@@ -55,7 +56,35 @@ function ToolbarButton({
55
56
  );
56
57
  }
57
58
  function RichTextToolbar(_a) {
58
- var _b = _a, { onAction, className } = _b, rest = __objRest(_b, ["onAction", "className"]);
59
+ var _b = _a, {
60
+ onAction,
61
+ className,
62
+ fontOptions,
63
+ selectedFontFamily,
64
+ onFontFamilyChange
65
+ } = _b, rest = __objRest(_b, [
66
+ "onAction",
67
+ "className",
68
+ "fontOptions",
69
+ "selectedFontFamily",
70
+ "onFontFamilyChange"
71
+ ]);
72
+ var _a2, _b2;
73
+ const [fontMenuOpen, setFontMenuOpen] = React.useState(false);
74
+ const fontMenuRef = React.useRef(null);
75
+ const selectedFontOption = (_a2 = fontOptions == null ? void 0 : fontOptions.find((option) => option.value === selectedFontFamily)) != null ? _a2 : fontOptions == null ? void 0 : fontOptions[0];
76
+ const fontLabel = (_b2 = selectedFontOption == null ? void 0 : selectedFontOption.label) != null ? _b2 : "Sans Serif";
77
+ const hasFontMenu = Boolean((fontOptions == null ? void 0 : fontOptions.length) && onFontFamilyChange);
78
+ React.useEffect(() => {
79
+ if (!fontMenuOpen) return;
80
+ function handleDocumentMouseDown(event) {
81
+ var _a3;
82
+ if ((_a3 = fontMenuRef.current) == null ? void 0 : _a3.contains(event.target)) return;
83
+ setFontMenuOpen(false);
84
+ }
85
+ document.addEventListener("mousedown", handleDocumentMouseDown);
86
+ return () => document.removeEventListener("mousedown", handleDocumentMouseDown);
87
+ }, [fontMenuOpen]);
59
88
  return /* @__PURE__ */ jsxs(
60
89
  "div",
61
90
  __spreadProps(__spreadValues({
@@ -69,21 +98,51 @@ function RichTextToolbar(_a) {
69
98
  /* @__PURE__ */ jsx(ToolbarButton, { action: "undo", icon: Undo2, label: "Undo", onAction }),
70
99
  /* @__PURE__ */ jsx(ToolbarButton, { action: "redo", icon: Redo2, label: "Redo", onAction }),
71
100
  /* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-border mx-1", "aria-hidden": "true" }),
72
- /* @__PURE__ */ jsxs(
73
- "button",
74
- {
75
- type: "button",
76
- "data-slot": "rich-text-toolbar-button",
77
- onClick: () => onAction == null ? void 0 : onAction("font"),
78
- "aria-label": "Font family",
79
- "aria-haspopup": "true",
80
- className: "text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1",
81
- children: [
82
- "Sans Serif",
83
- /* @__PURE__ */ jsx(ChevronDown, { size: 10 })
84
- ]
85
- }
86
- ),
101
+ /* @__PURE__ */ jsxs("div", { className: "relative", ref: fontMenuRef, children: [
102
+ /* @__PURE__ */ jsxs(
103
+ "button",
104
+ {
105
+ type: "button",
106
+ "data-slot": "rich-text-toolbar-button",
107
+ onClick: () => {
108
+ onAction == null ? void 0 : onAction("font");
109
+ if (hasFontMenu) setFontMenuOpen((open) => !open);
110
+ },
111
+ "aria-label": "Font family",
112
+ "aria-haspopup": "menu",
113
+ "aria-expanded": hasFontMenu ? fontMenuOpen : void 0,
114
+ className: "text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1",
115
+ children: [
116
+ fontLabel,
117
+ /* @__PURE__ */ jsx(ChevronDown, { size: 10 })
118
+ ]
119
+ }
120
+ ),
121
+ hasFontMenu && fontMenuOpen ? /* @__PURE__ */ jsx(
122
+ "div",
123
+ {
124
+ role: "menu",
125
+ "aria-label": "Font family",
126
+ className: "absolute left-0 bottom-full z-50 mb-1 min-w-32 overflow-hidden rounded-md border border-border bg-background py-1 shadow-md",
127
+ children: fontOptions.map((option) => /* @__PURE__ */ jsx(
128
+ "button",
129
+ {
130
+ type: "button",
131
+ role: "menuitemradio",
132
+ "aria-checked": option.value === selectedFontFamily,
133
+ className: "block w-full px-2.5 py-1.5 text-left text-xs text-foreground hover:bg-muted/60",
134
+ style: { fontFamily: option.value },
135
+ onClick: () => {
136
+ onFontFamilyChange == null ? void 0 : onFontFamilyChange(option.value);
137
+ setFontMenuOpen(false);
138
+ },
139
+ children: option.label
140
+ },
141
+ option.value
142
+ ))
143
+ }
144
+ ) : null
145
+ ] }),
87
146
  /* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-border mx-1", "aria-hidden": "true" }),
88
147
  /* @__PURE__ */ jsx(ToolbarButton, { action: "bold", icon: Bold, label: "Bold", onAction }),
89
148
  /* @__PURE__ */ jsx(ToolbarButton, { action: "italic", icon: Italic, label: "Italic", onAction }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/rich-text-toolbar.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport type { LucideIcon } from \"lucide-react\"\nimport { Undo2, Redo2, Bold, Italic, Underline, AlignLeft, List, Trash2, ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\n\ntype RichTextAction =\n | \"undo\" | \"redo\"\n | \"font\"\n | \"bold\" | \"italic\" | \"underline\"\n | \"align\" | \"list\"\n | \"delete\"\n\ninterface RichTextToolbarProps extends React.HTMLAttributes<HTMLDivElement> {\n onAction?: (action: RichTextAction) => void\n}\n\nfunction ToolbarButton({\n action,\n icon: Icon,\n label,\n extraClassName,\n onAction,\n}: {\n action: RichTextAction\n icon: LucideIcon\n label: string\n extraClassName?: string\n onAction?: (action: RichTextAction) => void\n}) {\n return (\n <button\n type=\"button\"\n data-slot=\"rich-text-toolbar-button\"\n onClick={() => onAction?.(action)}\n aria-label={label}\n className={cn(\"p-1.5 rounded hover:bg-muted/50 cursor-pointer text-muted-foreground\", extraClassName)}\n >\n <Icon size={14} />\n </button>\n )\n}\n\nfunction RichTextToolbar({ onAction, className, ...rest }: RichTextToolbarProps) {\n return (\n <div\n data-slot=\"rich-text-toolbar\"\n role=\"toolbar\"\n aria-label=\"Rich text formatting\"\n className={cn(\"px-3 py-1.5 flex items-center justify-between\", className)}\n {...rest}\n >\n <div className=\"flex items-center gap-0.5\">\n <ToolbarButton action=\"undo\" icon={Undo2} label=\"Undo\" onAction={onAction} />\n <ToolbarButton action=\"redo\" icon={Redo2} label=\"Redo\" onAction={onAction} />\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n <button\n type=\"button\"\n data-slot=\"rich-text-toolbar-button\"\n onClick={() => onAction?.(\"font\")}\n aria-label=\"Font family\"\n aria-haspopup=\"true\"\n className=\"text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1\"\n >\n Sans Serif\n <ChevronDown size={10} />\n </button>\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n <ToolbarButton action=\"bold\" icon={Bold} label=\"Bold\" onAction={onAction} />\n <ToolbarButton action=\"italic\" icon={Italic} label=\"Italic\" onAction={onAction} />\n <ToolbarButton action=\"underline\" icon={Underline} label=\"Underline\" onAction={onAction} />\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n <ToolbarButton action=\"align\" icon={AlignLeft} label=\"Align left\" onAction={onAction} />\n <ToolbarButton action=\"list\" icon={List} label=\"List\" onAction={onAction} />\n </div>\n\n <ToolbarButton action=\"delete\" icon={Trash2} label=\"Delete\" extraClassName=\"hover:text-destructive\" onAction={onAction} />\n </div>\n )\n}\n\nexport { RichTextToolbar, type RichTextToolbarProps, type RichTextAction }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCM,cAoBE,YApBF;AApCN,SAAS,OAAO,OAAO,MAAM,QAAQ,WAAW,WAAW,MAAM,QAAQ,mBAAmB;AAE5F,SAAS,UAAU;AAanB,SAAS,cAAc;AAAA,EACrB;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,aAAU;AAAA,MACV,SAAS,MAAM,qCAAW;AAAA,MAC1B,cAAY;AAAA,MACZ,WAAW,GAAG,wEAAwE,cAAc;AAAA,MAEpG,8BAAC,QAAK,MAAM,IAAI;AAAA;AAAA,EAClB;AAEJ;AAEA,SAAS,gBAAgB,IAAwD;AAAxD,eAAE,YAAU,UA7CrC,IA6CyB,IAA0B,iBAA1B,IAA0B,CAAxB,YAAU;AACnC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,MAAK;AAAA,MACL,cAAW;AAAA,MACX,WAAW,GAAG,iDAAiD,SAAS;AAAA,OACpE,OALL;AAAA,MAOC;AAAA,6BAAC,SAAI,WAAU,6BACb;AAAA,8BAAC,iBAAc,QAAO,QAAO,MAAM,OAAO,OAAM,QAAO,UAAoB;AAAA,UAC3E,oBAAC,iBAAc,QAAO,QAAO,MAAM,OAAO,OAAM,QAAO,UAAoB;AAAA,UAE3E,oBAAC,SAAI,WAAU,2BAA0B,eAAY,QAAO;AAAA,UAE5D;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,aAAU;AAAA,cACV,SAAS,MAAM,qCAAW;AAAA,cAC1B,cAAW;AAAA,cACX,iBAAc;AAAA,cACd,WAAU;AAAA,cACX;AAAA;AAAA,gBAEC,oBAAC,eAAY,MAAM,IAAI;AAAA;AAAA;AAAA,UACzB;AAAA,UAEA,oBAAC,SAAI,WAAU,2BAA0B,eAAY,QAAO;AAAA,UAE5D,oBAAC,iBAAc,QAAO,QAAO,MAAM,MAAM,OAAM,QAAO,UAAoB;AAAA,UAC1E,oBAAC,iBAAc,QAAO,UAAS,MAAM,QAAQ,OAAM,UAAS,UAAoB;AAAA,UAChF,oBAAC,iBAAc,QAAO,aAAY,MAAM,WAAW,OAAM,aAAY,UAAoB;AAAA,UAEzF,oBAAC,SAAI,WAAU,2BAA0B,eAAY,QAAO;AAAA,UAE5D,oBAAC,iBAAc,QAAO,SAAQ,MAAM,WAAW,OAAM,cAAa,UAAoB;AAAA,UACtF,oBAAC,iBAAc,QAAO,QAAO,MAAM,MAAM,OAAM,QAAO,UAAoB;AAAA,WAC5E;AAAA,QAEA,oBAAC,iBAAc,QAAO,UAAS,MAAM,QAAQ,OAAM,UAAS,gBAAe,0BAAyB,UAAoB;AAAA;AAAA;AAAA,EAC1H;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/rich-text-toolbar.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport type { LucideIcon } from \"lucide-react\"\nimport { Undo2, Redo2, Bold, Italic, Underline, AlignLeft, List, Trash2, ChevronDown } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\n\ntype RichTextAction =\n | \"undo\" | \"redo\"\n | \"font\"\n | \"bold\" | \"italic\" | \"underline\"\n | \"align\" | \"list\"\n | \"delete\"\n\ninterface RichTextFontOption {\n label: string\n value: string\n}\n\ninterface RichTextToolbarProps extends React.HTMLAttributes<HTMLDivElement> {\n onAction?: (action: RichTextAction) => void\n fontOptions?: RichTextFontOption[]\n selectedFontFamily?: string\n onFontFamilyChange?: (fontFamily: string) => void\n}\n\nfunction ToolbarButton({\n action,\n icon: Icon,\n label,\n extraClassName,\n onAction,\n}: {\n action: RichTextAction\n icon: LucideIcon\n label: string\n extraClassName?: string\n onAction?: (action: RichTextAction) => void\n}) {\n return (\n <button\n type=\"button\"\n data-slot=\"rich-text-toolbar-button\"\n onClick={() => onAction?.(action)}\n aria-label={label}\n className={cn(\"p-1.5 rounded hover:bg-muted/50 cursor-pointer text-muted-foreground\", extraClassName)}\n >\n <Icon size={14} />\n </button>\n )\n}\n\nfunction RichTextToolbar({\n onAction,\n className,\n fontOptions,\n selectedFontFamily,\n onFontFamilyChange,\n ...rest\n}: RichTextToolbarProps) {\n const [fontMenuOpen, setFontMenuOpen] = React.useState(false)\n const fontMenuRef = React.useRef<HTMLDivElement | null>(null)\n const selectedFontOption = fontOptions?.find((option) => option.value === selectedFontFamily) ?? fontOptions?.[0]\n const fontLabel = selectedFontOption?.label ?? \"Sans Serif\"\n const hasFontMenu = Boolean(fontOptions?.length && onFontFamilyChange)\n\n React.useEffect(() => {\n if (!fontMenuOpen) return\n\n function handleDocumentMouseDown(event: MouseEvent) {\n if (fontMenuRef.current?.contains(event.target as Node)) return\n setFontMenuOpen(false)\n }\n\n document.addEventListener(\"mousedown\", handleDocumentMouseDown)\n return () => document.removeEventListener(\"mousedown\", handleDocumentMouseDown)\n }, [fontMenuOpen])\n\n return (\n <div\n data-slot=\"rich-text-toolbar\"\n role=\"toolbar\"\n aria-label=\"Rich text formatting\"\n className={cn(\"px-3 py-1.5 flex items-center justify-between\", className)}\n {...rest}\n >\n <div className=\"flex items-center gap-0.5\">\n <ToolbarButton action=\"undo\" icon={Undo2} label=\"Undo\" onAction={onAction} />\n <ToolbarButton action=\"redo\" icon={Redo2} label=\"Redo\" onAction={onAction} />\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n <div className=\"relative\" ref={fontMenuRef}>\n <button\n type=\"button\"\n data-slot=\"rich-text-toolbar-button\"\n onClick={() => {\n onAction?.(\"font\")\n if (hasFontMenu) setFontMenuOpen((open) => !open)\n }}\n aria-label=\"Font family\"\n aria-haspopup=\"menu\"\n aria-expanded={hasFontMenu ? fontMenuOpen : undefined}\n className=\"text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1\"\n >\n {fontLabel}\n <ChevronDown size={10} />\n </button>\n\n {hasFontMenu && fontMenuOpen ? (\n <div\n role=\"menu\"\n aria-label=\"Font family\"\n className=\"absolute left-0 bottom-full z-50 mb-1 min-w-32 overflow-hidden rounded-md border border-border bg-background py-1 shadow-md\"\n >\n {fontOptions!.map((option) => (\n <button\n key={option.value}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={option.value === selectedFontFamily}\n className=\"block w-full px-2.5 py-1.5 text-left text-xs text-foreground hover:bg-muted/60\"\n style={{ fontFamily: option.value }}\n onClick={() => {\n onFontFamilyChange?.(option.value)\n setFontMenuOpen(false)\n }}\n >\n {option.label}\n </button>\n ))}\n </div>\n ) : null}\n </div>\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n <ToolbarButton action=\"bold\" icon={Bold} label=\"Bold\" onAction={onAction} />\n <ToolbarButton action=\"italic\" icon={Italic} label=\"Italic\" onAction={onAction} />\n <ToolbarButton action=\"underline\" icon={Underline} label=\"Underline\" onAction={onAction} />\n\n <div className=\"w-px h-4 bg-border mx-1\" aria-hidden=\"true\" />\n\n <ToolbarButton action=\"align\" icon={AlignLeft} label=\"Align left\" onAction={onAction} />\n <ToolbarButton action=\"list\" icon={List} label=\"List\" onAction={onAction} />\n </div>\n\n <ToolbarButton action=\"delete\" icon={Trash2} label=\"Delete\" extraClassName=\"hover:text-destructive\" onAction={onAction} />\n </div>\n )\n}\n\nexport { RichTextToolbar, type RichTextToolbarProps, type RichTextAction, type RichTextFontOption }\n"],"mappings":"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","names":["_a","_b"]}
package/dist/index.d.ts CHANGED
@@ -70,7 +70,7 @@ export { QuickActionModal, QuickActionPriority, QuickActionTaskDraft, QuickActio
70
70
  export { ActiveVariant, QuickActionSidebarNav, SidebarNavItem, SidebarNavSection, SidebarUserProfile, UserMenuItem } from './components/quick-action-sidebar-nav.js';
71
71
  export { RecommendedAction, RecommendedActionsSection } from './components/recommended-actions-section.js';
72
72
  export { ReportCard, ReportCardProps } from './components/report-card.js';
73
- export { RichTextAction, RichTextToolbar, RichTextToolbarProps } from './components/rich-text-toolbar.js';
73
+ export { RichTextAction, RichTextFontOption, RichTextToolbar, RichTextToolbarProps } from './components/rich-text-toolbar.js';
74
74
  export { ScoreAnalysisModal, ScoreAnalysisModalProps, ScoreAnalysisPanel, getScoreLabel } from './components/score-analysis-modal.js';
75
75
  export { ScoreBreakdown, ScoreBreakdownProps, ScoreFactor } from './components/score-breakdown.js';
76
76
  export { ScoreFeedback, useScoreFeedback } from './components/score-feedback.js';
@@ -172,9 +172,8 @@ function TimelineSection({
172
172
  type: "button",
173
173
  onClick: () => setShowSystemEvents((prev) => !prev),
174
174
  className: cn(
175
- "inline-flex shrink-0 cursor-pointer items-center rounded-full border transition-colors",
176
- isCasePanel ? "gap-2 px-2.5 py-1 text-xs font-medium" : "gap-3 px-3.5 py-2 text-sm font-semibold",
177
- showSystemEvents ? isCasePanel ? "border-border bg-muted text-foreground shadow-sm hover:bg-muted/80" : "border-foreground bg-foreground text-background shadow-sm hover:bg-foreground/90" : "border-border bg-background text-muted-foreground shadow-sm hover:bg-muted/40 hover:text-foreground"
175
+ "inline-flex shrink-0 cursor-pointer items-center gap-3 rounded-full border px-3.5 py-2 text-sm font-semibold transition-colors",
176
+ showSystemEvents ? "border-foreground bg-foreground text-background shadow-sm hover:bg-foreground/90" : "border-border bg-background text-muted-foreground shadow-sm hover:bg-muted/40 hover:text-foreground"
178
177
  ),
179
178
  "aria-pressed": showSystemEvents,
180
179
  "aria-label": toggleHelp,
@@ -185,9 +184,8 @@ function TimelineSection({
185
184
  "span",
186
185
  {
187
186
  className: cn(
188
- "relative inline-flex shrink-0 items-center rounded-full p-0.5 transition-colors",
189
- isCasePanel ? "h-3.5 w-7" : "h-4 w-8",
190
- showSystemEvents ? "bg-teal-600" : "bg-muted-foreground/25"
187
+ "relative inline-flex h-4 w-8 shrink-0 items-center rounded-full p-0.5 transition-colors",
188
+ showSystemEvents ? "bg-teal-600" : "bg-muted-foreground/30"
191
189
  ),
192
190
  "aria-hidden": "true",
193
191
  "data-testid": "system-events-indicator",
@@ -195,9 +193,8 @@ function TimelineSection({
195
193
  "span",
196
194
  {
197
195
  className: cn(
198
- "block rounded-full bg-white shadow-sm transition-transform",
199
- isCasePanel ? "h-2.5 w-2.5" : "h-3 w-3",
200
- showSystemEvents ? isCasePanel ? "translate-x-3.5" : "translate-x-4" : "translate-x-0"
196
+ "block h-3 w-3 rounded-full bg-white shadow-sm transition-transform",
197
+ showSystemEvents ? "translate-x-4" : "translate-x-0"
201
198
  )
202
199
  }
203
200
  )
@@ -207,7 +204,7 @@ function TimelineSection({
207
204
  !showSystemEvents ? /* @__PURE__ */ jsx(
208
205
  "span",
209
206
  {
210
- className: cn("inline-flex items-center justify-center rounded-full bg-muted px-1.5 font-bold tabular-nums text-muted-foreground", isCasePanel ? "min-w-5 text-[11px]" : "min-w-[22px] text-xs"),
207
+ className: "inline-flex min-w-[22px] items-center justify-center rounded-full bg-muted px-1.5 text-xs font-bold tabular-nums text-muted-foreground",
211
208
  "data-testid": "hidden-count-badge",
212
209
  children: hiddenCount
213
210
  }
@@ -307,7 +304,6 @@ function DetailView({
307
304
  timelineSystemEventsHiddenHint,
308
305
  timelineSystemEventsVisibleHint
309
306
  ]);
310
- const isCasePanelV2 = sectionLayout === "case-panel-v2";
311
307
  const [showTimeline, setShowTimeline] = React.useState(false);
312
308
  const [extraActions, setExtraActions] = React.useState([]);
313
309
  const sysEvtDefaultVisible = (_a = sysEvtConfig == null ? void 0 : sysEvtConfig.defaultVisible) != null ? _a : false;
@@ -315,8 +311,7 @@ function DetailView({
315
311
  const [showSystemEvents, setShowSystemEvents] = React.useState(sysEvtDefaultVisible);
316
312
  const initialReadDoneRef = React.useRef(false);
317
313
  React.useEffect(() => {
318
- if (!sysEvtStorageKey || isCasePanelV2) {
319
- setShowSystemEvents(sysEvtDefaultVisible);
314
+ if (!sysEvtStorageKey) {
320
315
  initialReadDoneRef.current = true;
321
316
  return;
322
317
  }
@@ -324,22 +319,19 @@ function DetailView({
324
319
  const stored = localStorage.getItem(sysEvtStorageKey);
325
320
  if (stored !== null) {
326
321
  setShowSystemEvents(stored === "true");
327
- } else {
328
- setShowSystemEvents(sysEvtDefaultVisible);
329
322
  }
330
323
  } catch (e) {
331
- setShowSystemEvents(sysEvtDefaultVisible);
332
324
  }
333
325
  initialReadDoneRef.current = true;
334
- }, [isCasePanelV2, sysEvtDefaultVisible, sysEvtStorageKey]);
326
+ }, [sysEvtStorageKey]);
335
327
  React.useEffect(() => {
336
- if (!sysEvtStorageKey || isCasePanelV2) return;
328
+ if (!sysEvtStorageKey) return;
337
329
  if (!initialReadDoneRef.current) return;
338
330
  try {
339
331
  localStorage.setItem(sysEvtStorageKey, String(showSystemEvents));
340
332
  } catch (e) {
341
333
  }
342
- }, [isCasePanelV2, showSystemEvents, sysEvtStorageKey]);
334
+ }, [showSystemEvents, sysEvtStorageKey]);
343
335
  React.useEffect(() => {
344
336
  setShowTimeline(false);
345
337
  setExtraActions([]);
@@ -372,6 +364,7 @@ function DetailView({
372
364
  [suggestedActions]
373
365
  );
374
366
  const timeChipToneClass = signalData.timeChipTone === "red" ? "border-red-300 bg-red-50 text-red-700 hover:bg-red-50" : signalData.timeChipTone === "amber" ? "border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-50" : "hover:bg-muted/50";
367
+ const isCasePanelV2 = sectionLayout === "case-panel-v2";
375
368
  const metadataChips = /* @__PURE__ */ jsxs(Fragment, { children: [
376
369
  /* @__PURE__ */ jsx(
377
370
  SignalPriorityPopover,
@@ -533,11 +526,9 @@ function DetailView({
533
526
  /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(SignalApproval.Actions, {}) })
534
527
  ] })
535
528
  ] }) : null,
536
- /* @__PURE__ */ jsxs("div", { "data-slot": "case-panel-workflow-stack", className: "space-y-10", children: [
537
- renderAfterScore == null ? void 0 : renderAfterScore(item),
538
- renderPrimaryAction == null ? void 0 : renderPrimaryAction(item),
539
- renderCommentArea ? /* @__PURE__ */ jsx("div", { "data-slot": "case-panel-comment-area", children: renderCommentArea(item) }) : null
540
- ] }),
529
+ renderAfterScore == null ? void 0 : renderAfterScore(item),
530
+ renderPrimaryAction == null ? void 0 : renderPrimaryAction(item),
531
+ renderCommentArea == null ? void 0 : renderCommentArea(item),
541
532
  timelineSection
542
533
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
543
534
  renderAfterScore == null ? void 0 : renderAfterScore(item),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/prototype/prototype-inbox-view.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n ArrowLeft,\n ArrowUpDown,\n ChevronDown,\n ChevronRight,\n Filter,\n FileText,\n Clock,\n CheckSquare,\n Eye,\n Plus,\n\n Building,\n LayoutList,\n Columns2,\n Square,\n Tag,\n} from \"lucide-react\"\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n} from \"../components/dropdown-menu\"\nimport { Button } from \"../components/button\"\nimport { Badge } from \"../components/badge\"\nimport { Input } from \"../components/input\"\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../components/tooltip\"\nimport { ViewModeToggle } from \"../components/view-mode-toggle\"\nimport {\n InboxToolbar,\n type AssigneeFilter,\n type InboxFilterCategory,\n} from \"../components/inbox-toolbar\"\nimport { GroupedListView, type GroupedListGroup } from \"../components/item-list\"\nimport { SignalApproval, type ApprovalState, type OpportunityDraft, type OpportunityPreview } from \"../components/signal-feedback-inline\"\nimport { ScoreWhyChips } from \"../components/score-why-chips\"\nimport { SignalPriorityPopover } from \"../components/signal-priority-popover\"\nimport { type SourceDef } from \"../components/detail-view\"\nimport {\n SuggestedActions,\n type SuggestedAction,\n type SuggestedContact,\n} from \"../components/suggested-actions\"\nimport { TimelineActivity, type TimelineEvent } from \"../components/timeline-activity\"\nimport { cn } from \"../lib/utils\"\nimport type {\n QueueItem,\n InboxViewConfig,\n InboxDetailSections,\n SignalScoreData,\n BriefStyleVariant,\n TimelineSystemEventsConfig,\n} from \"./prototype-config\"\n\n// ---------------------------------------------------------------------------\n// Dot color utility — maps statusColor to a Tailwind background class.\n// Centralised here so the list row and split-view card stay in sync and\n// future color additions are a single-edit change.\n// ---------------------------------------------------------------------------\n\nfunction dotColorClass(statusColor: string): string {\n if (statusColor === \"red\") return \"bg-[#f43f5e]\"\n if (statusColor === \"yellow\" || statusColor === \"amber\" || statusColor === \"orange\") return \"bg-[#eab308]\"\n if (statusColor === \"green\") return \"bg-[#22c55e]\"\n return \"bg-[#9ca3af]\"\n}\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface PrototypeInboxViewProps extends InboxViewConfig {\n /** Extra ReactNode rendered at the end of the header bar (e.g. exit button). */\n headerActions?: React.ReactNode\n onOpenEntityPanel?: () => void\n onOpenRecentActivity?: () => void\n onNavigateToInbox?: () => void\n onItemSelect?: (item: QueueItem) => void\n defaultViewMode?: \"list\" | \"split\"\n}\n\n// ---------------------------------------------------------------------------\n// Default detail sections\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_DETAIL_SECTIONS: InboxDetailSections = {\n signalBrief: true,\n suggestedActions: true,\n timeline: true,\n}\n\nconst DEFAULT_SIGNAL_SCORE: SignalScoreData = {\n score: 65,\n factors: [\n { key: \"trigger\", label: \"Trigger strength\", score: 70, why: \"Moderate signal detected based on account activity\" },\n { key: \"fit\", label: \"Company fit\", score: 65, why: \"Reasonable fit based on company profile\" },\n { key: \"timing\", label: \"Timing\", score: 58, why: \"Within general evaluation window\" },\n ],\n whyNow: \"Moderate signals detected that warrant review and potential outreach.\",\n evidence: [\n \"Activity patterns suggest potential opportunity\",\n \"Company profile aligns with target segment\",\n ],\n confidence: 72,\n}\n\n// ---------------------------------------------------------------------------\n// Detail View\n// ---------------------------------------------------------------------------\n\n/** Up to 2-character uppercase initials for a company name (e.g. \"Northwind Systems\" -> \"NW\"). */\nfunction getCompanyInitials(company: string): string {\n const words = company.trim().split(/\\s+/).filter(Boolean)\n if (words.length === 0) return \"?\"\n if (words.length === 1) return words[0].slice(0, 2).toUpperCase()\n return (words[0][0] + words[1][0]).toUpperCase()\n}\n\n/** Maps a QueueItem.statusColor to a Tailwind dot background class. */\nconst STATUS_DOT_CLASS: Record<string, string> = {\n red: \"bg-red-500\",\n orange: \"bg-orange-500\",\n amber: \"bg-amber-500\",\n yellow: \"bg-yellow-500\",\n green: \"bg-emerald-500\",\n emerald: \"bg-emerald-500\",\n blue: \"bg-blue-500\",\n gray: \"bg-gray-300\",\n grey: \"bg-gray-300\",\n}\n\nexport interface DetailViewProps {\n item: QueueItem\n sections: InboxDetailSections\n getSignalScore: (company: string, item?: QueueItem) => SignalScoreData\n buildSuggestedActions: (item: QueueItem) => SuggestedAction[]\n buildSourceItems: (item: QueueItem) => SourceDef[]\n getTimelineEvents?: (item: QueueItem) => TimelineEvent[]\n accountContacts: SuggestedContact[]\n emailSignature: string | React.ReactNode\n iconMap: Record<string, string>\n onOpenEntityPanel?: () => void\n onOpenRecentActivity?: () => void\n onSuggestedActionFeedback?: (actionId: number | string, feedback: string, actionTitle?: string) => void\n /** @deprecated The compact score UX no longer renders score-level thumbs by default. */\n onScoreFeedback?: (type: \"up\" | \"down\", pills: string[], detail: string) => void\n onSignalApprove?: (item: QueueItem, draft?: OpportunityDraft) => void | Promise<boolean>\n getSignalApprovalState?: (item: QueueItem) => ApprovalState | undefined\n signalLabels?: InboxViewConfig[\"signalLabels\"]\n hideApproveButton?: boolean\n signalBriefCopy?: InboxViewConfig[\"signalBriefCopy\"]\n briefStyleVariant?: BriefStyleVariant\n /** Opt-in detail panel section ordering. Defaults to the existing layout. */\n sectionLayout?: InboxViewConfig[\"sectionLayout\"]\n renderDetailExtra?: (item: QueueItem) => React.ReactNode\n /** Render content between the signal brief text and the signal score bar (e.g. \"Signals on Case\" chips). */\n renderBeforeScore?: (item: QueueItem) => React.ReactNode\n /** Render content between the signal score section and the activity timeline. */\n renderAfterScore?: (item: QueueItem) => React.ReactNode\n /** Render primary case-panel content between the opportunity section and comment area. */\n renderPrimaryAction?: (item: QueueItem) => React.ReactNode\n /** Render case-panel comment content before the activity timeline. */\n renderCommentArea?: (item: QueueItem) => React.ReactNode\n lastActivityTime?: string\n /** Render extra content inline with the detail title. */\n renderTitleExtra?: (item: QueueItem) => React.ReactNode\n /** Render a full-width action row below the detail title row. */\n renderTitleActionRow?: (item: QueueItem) => React.ReactNode\n /** Render supporting content below the detail title. */\n renderTitleSubtext?: (item: QueueItem) => React.ReactNode\n /** Render extra metadata chips (e.g. assignee) inside the chips row below the title. */\n renderMetadataExtra?: (item: QueueItem) => React.ReactNode\n /**\n * Where the metadata chips row (priority · deadline · account · renderMetadataExtra) sits.\n * - \"default\": above the signal brief (legacy layout, unchanged for existing consumers).\n * - \"below-brief\": moved beneath the brief text, just before `renderBeforeScore`\n * (case-panel redesign — chips + owners read as one block under the brief).\n */\n metadataLayout?: \"default\" | \"below-brief\"\n /** Override the built-in account details metadata button label. */\n accountDetailsButtonLabel?: (item: QueueItem) => React.ReactNode\n /** Accessible label for the built-in account details metadata button. */\n getAccountDetailsButtonAriaLabel?: (item: QueueItem) => string\n onOpenSignalBucket?: (args: { item: QueueItem; bucketKey: string; signalId: string }) => void\n approveButtonIconUrl?: string\n opportunityPreview?: OpportunityPreview\n onRequestApproval?: () => Promise<void>\n /** Number of important/attention-worthy events to highlight on the collapsed timeline header. */\n attentionCount?: number\n /** Configuration for the system-noise events toggle (score changes, etc.). */\n timelineSystemEventsConfig?: TimelineSystemEventsConfig\n\n // ── Deprecated individual props (use timelineSystemEventsConfig instead) ──\n /** @deprecated Use `timelineSystemEventsConfig.toggleLabel`. */\n timelineSystemEventsToggleLabel?: string\n /** @deprecated Use `timelineSystemEventsConfig.storageKey`. */\n timelineSystemEventsStorageKey?: string\n /** @deprecated Use `timelineSystemEventsConfig.defaultVisible`. */\n timelineSystemEventsDefaultVisible?: boolean\n /** @deprecated Use `timelineSystemEventsConfig.hiddenHint`. */\n timelineSystemEventsHiddenHint?: string\n /** @deprecated Use `timelineSystemEventsConfig.visibleHint`. */\n timelineSystemEventsVisibleHint?: string\n}\n\n// ---------------------------------------------------------------------------\n// TimelineSection — extracted from the IIFE in DetailView for readability\n// ---------------------------------------------------------------------------\n\nfunction TimelineSection({\n timelineEvents,\n showTimeline,\n setShowTimeline,\n showSystemEvents,\n setShowSystemEvents,\n attentionCount,\n sysEvtConfig,\n lastActivityTime,\n isCasePanel = false,\n}: {\n timelineEvents: TimelineEvent[]\n showTimeline: boolean\n setShowTimeline: React.Dispatch<React.SetStateAction<boolean>>\n showSystemEvents: boolean\n setShowSystemEvents: React.Dispatch<React.SetStateAction<boolean>>\n attentionCount?: number\n sysEvtConfig?: TimelineSystemEventsConfig\n lastActivityTime?: string\n isCasePanel?: boolean\n}) {\n // Single-pass partition: compute visibleEvents and hiddenCount together\n const visibleEvents: TimelineEvent[] = []\n let hiddenCount = 0\n for (const e of timelineEvents) {\n if (e.isSystemNoise) hiddenCount++\n if (!e.isSystemNoise || showSystemEvents) visibleEvents.push(e)\n }\n const hasSystemNoise = hiddenCount > 0\n\n // The toggle renders whenever there are system-noise events — even if no\n // config was provided — so consumers that emit `isSystemNoise: true` always\n // give users a way to reveal those events.\n const toggleLabel = sysEvtConfig?.toggleLabel ?? \"System events\"\n const toggleHelp = showSystemEvents\n ? sysEvtConfig?.visibleHint?.replace(\"{count}\", String(hiddenCount)) ?? \"Hide system events\"\n : sysEvtConfig?.hiddenHint ?? \"Show system events\"\n\n // Derive \"Last activity\" from the first *visible* event so the collapsed\n // header never points at a hidden score-update. The caller-supplied\n // `lastActivityTime` is only used when system-noise filtering is NOT active\n // (i.e. all events are visible) since it may come from an unfiltered source\n // such as `case.last_activity_at`.\n const firstVisibleTime =\n (!hasSystemNoise || showSystemEvents) && lastActivityTime\n ? lastActivityTime\n : visibleEvents.length > 0\n ? visibleEvents[0].time\n : \"\"\n\n const visibleCount = visibleEvents.length\n const eventCountLabel = `${visibleCount} ${visibleCount === 1 ? \"event\" : \"events\"}`\n\n return (\n <div\n className={cn(\n isCasePanel ? \"mt-8 border-t border-border pt-8 pb-8\" : \"mb-8\"\n )}\n >\n {/* Header — outer non-interactive container */}\n <div\n className={cn(\n \"flex w-full items-center justify-between\",\n isCasePanel\n ? \"gap-4 border-b border-border pb-5\"\n : \"group/timeline gap-2 rounded-md py-2 transition-colors hover:bg-muted/40 -mx-2 px-2\"\n )}\n data-testid=\"timeline-header\"\n >\n {/* Left: collapse/expand button */}\n <button\n type=\"button\"\n onClick={() => setShowTimeline((prev) => !prev)}\n className=\"flex min-w-0 cursor-pointer items-center gap-2 border-0 bg-transparent p-0 text-left\"\n data-testid=\"timeline-collapse-btn\"\n >\n <h3 className=\"text-xs font-bold uppercase tracking-[0.16em] text-muted-foreground transition-colors group-hover/timeline:text-foreground\">ACTIVITY TIMELINE</h3>\n {!showTimeline && attentionCount != null && attentionCount > 0 && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-destructive/10 px-1.5 py-0.5 text-[10px] font-semibold text-destructive border border-destructive/20\">\n {attentionCount} new\n </span>\n )}\n {!isCasePanel && !showTimeline && firstVisibleTime && (\n <span className=\"text-[11px] text-muted-foreground/60\" data-testid=\"last-activity-hint\">\n &middot; Last activity {firstVisibleTime}\n </span>\n )}\n </button>\n\n {/* Right: system-events toggle, event count, and collapse affordance */}\n <div className=\"flex shrink-0 items-center gap-4\">\n {hasSystemNoise && (\n <button\n type=\"button\"\n onClick={() => setShowSystemEvents((prev) => !prev)}\n className={cn(\n \"inline-flex shrink-0 cursor-pointer items-center rounded-full border transition-colors\",\n isCasePanel ? \"gap-2 px-2.5 py-1 text-xs font-medium\" : \"gap-3 px-3.5 py-2 text-sm font-semibold\",\n showSystemEvents\n ? isCasePanel\n ? \"border-border bg-muted text-foreground shadow-sm hover:bg-muted/80\"\n : \"border-foreground bg-foreground text-background shadow-sm hover:bg-foreground/90\"\n : \"border-border bg-background text-muted-foreground shadow-sm hover:bg-muted/40 hover:text-foreground\"\n )}\n aria-pressed={showSystemEvents}\n aria-label={toggleHelp}\n title={toggleHelp}\n data-testid=\"system-events-toggle\"\n >\n <span\n className={cn(\n \"relative inline-flex shrink-0 items-center rounded-full p-0.5 transition-colors\",\n isCasePanel ? \"h-3.5 w-7\" : \"h-4 w-8\",\n showSystemEvents ? \"bg-teal-600\" : \"bg-muted-foreground/25\"\n )}\n aria-hidden=\"true\"\n data-testid=\"system-events-indicator\"\n >\n <span\n className={cn(\n \"block rounded-full bg-white shadow-sm transition-transform\",\n isCasePanel ? \"h-2.5 w-2.5\" : \"h-3 w-3\",\n showSystemEvents ? (isCasePanel ? \"translate-x-3.5\" : \"translate-x-4\") : \"translate-x-0\"\n )}\n />\n </span>\n <span>{toggleLabel}</span>\n {!showSystemEvents ? (\n <span\n className={cn(\"inline-flex items-center justify-center rounded-full bg-muted px-1.5 font-bold tabular-nums text-muted-foreground\", isCasePanel ? \"min-w-5 text-[11px]\" : \"min-w-[22px] text-xs\")}\n data-testid=\"hidden-count-badge\"\n >\n {hiddenCount}\n </span>\n ) : null}\n </button>\n )}\n\n <button\n type=\"button\"\n onClick={() => setShowTimeline((prev) => !prev)}\n className={cn(\n \"inline-flex shrink-0 cursor-pointer items-center border-0 bg-transparent p-0 text-muted-foreground transition-colors hover:text-foreground\",\n isCasePanel ? \"gap-3 text-sm\" : \"gap-1.5 text-[11px]\"\n )}\n aria-label={showTimeline ? \"Collapse activity timeline\" : \"Expand activity timeline\"}\n >\n <span className=\"font-medium\" data-testid=\"event-count\">{eventCountLabel}</span>\n <ChevronDown className={`h-3.5 w-3.5 transition-transform duration-200 ${showTimeline ? \"rotate-180\" : \"\"}`} />\n </button>\n </div>\n </div>\n\n {/* Timeline body */}\n {showTimeline && visibleEvents.length > 0 && (\n <div className=\"mt-6\">\n <TimelineActivity events={visibleEvents} variant=\"case-panel\" />\n </div>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Detail View\n// ---------------------------------------------------------------------------\n\nexport function DetailView({\n item,\n sections,\n getSignalScore,\n buildSuggestedActions,\n buildSourceItems: _buildSourceItems,\n getTimelineEvents,\n accountContacts,\n emailSignature,\n iconMap,\n onOpenEntityPanel,\n onOpenRecentActivity,\n onSuggestedActionFeedback: _onSuggestedActionFeedback,\n onScoreFeedback: _onScoreFeedback,\n onSignalApprove,\n getSignalApprovalState,\n signalLabels,\n hideApproveButton,\n signalBriefCopy,\n briefStyleVariant = \"default\",\n sectionLayout = \"default\",\n renderDetailExtra,\n renderBeforeScore,\n renderAfterScore,\n renderPrimaryAction,\n renderCommentArea,\n lastActivityTime,\n renderTitleExtra,\n renderTitleActionRow,\n renderTitleSubtext,\n renderMetadataExtra,\n metadataLayout = \"default\",\n accountDetailsButtonLabel,\n getAccountDetailsButtonAriaLabel,\n onOpenSignalBucket,\n approveButtonIconUrl,\n opportunityPreview,\n onRequestApproval,\n attentionCount,\n timelineSystemEventsConfig: configProp,\n timelineSystemEventsToggleLabel,\n timelineSystemEventsStorageKey,\n timelineSystemEventsDefaultVisible,\n timelineSystemEventsHiddenHint,\n timelineSystemEventsVisibleHint,\n}: DetailViewProps) {\n // Resolve system-events config: prefer the config object, fall back to deprecated individual props.\n const sysEvtConfig = React.useMemo<TimelineSystemEventsConfig | undefined>(() => {\n if (configProp) return configProp\n // Build from deprecated individual props if any are provided\n if (\n timelineSystemEventsToggleLabel ||\n timelineSystemEventsStorageKey ||\n timelineSystemEventsDefaultVisible !== undefined ||\n timelineSystemEventsHiddenHint ||\n timelineSystemEventsVisibleHint\n ) {\n return {\n toggleLabel: timelineSystemEventsToggleLabel,\n storageKey: timelineSystemEventsStorageKey,\n defaultVisible: timelineSystemEventsDefaultVisible,\n hiddenHint: timelineSystemEventsHiddenHint,\n visibleHint: timelineSystemEventsVisibleHint,\n }\n }\n return undefined\n }, [\n configProp,\n timelineSystemEventsToggleLabel,\n timelineSystemEventsStorageKey,\n timelineSystemEventsDefaultVisible,\n timelineSystemEventsHiddenHint,\n timelineSystemEventsVisibleHint,\n ])\n\n const isCasePanelV2 = sectionLayout === \"case-panel-v2\"\n\n const [showTimeline, setShowTimeline] = React.useState(false)\n const [extraActions, setExtraActions] = React.useState<SuggestedAction[]>([])\n\n // ---- System-noise toggle state ----\n const sysEvtDefaultVisible = sysEvtConfig?.defaultVisible ?? false\n const sysEvtStorageKey = sysEvtConfig?.storageKey\n const [showSystemEvents, setShowSystemEvents] = React.useState(sysEvtDefaultVisible)\n const initialReadDoneRef = React.useRef(false)\n\n // Read persisted value from localStorage on mount. Case-panel timelines always\n // start from the configured default so stale local state cannot make system\n // events prominent by default in the inbox detail panel.\n React.useEffect(() => {\n if (!sysEvtStorageKey || isCasePanelV2) {\n setShowSystemEvents(sysEvtDefaultVisible)\n initialReadDoneRef.current = true\n return\n }\n try {\n const stored = localStorage.getItem(sysEvtStorageKey)\n if (stored !== null) {\n setShowSystemEvents(stored === \"true\")\n } else {\n setShowSystemEvents(sysEvtDefaultVisible)\n }\n } catch {\n // localStorage unavailable — ignore\n setShowSystemEvents(sysEvtDefaultVisible)\n }\n initialReadDoneRef.current = true\n }, [isCasePanelV2, sysEvtDefaultVisible, sysEvtStorageKey])\n\n // Write to localStorage when the toggle changes (skip case-panel timelines so\n // they stay off by default on the next case/session).\n React.useEffect(() => {\n if (!sysEvtStorageKey || isCasePanelV2) return\n if (!initialReadDoneRef.current) return\n try {\n localStorage.setItem(sysEvtStorageKey, String(showSystemEvents))\n } catch {\n // localStorage unavailable — ignore\n }\n }, [isCasePanelV2, showSystemEvents, sysEvtStorageKey])\n\n React.useEffect(() => {\n setShowTimeline(false)\n setExtraActions([])\n }, [item.id])\n\n const signalData = React.useMemo(\n () => getSignalScore(item.company, item),\n [getSignalScore, item],\n )\n\n const suggestedActions = React.useMemo(\n () => [...buildSuggestedActions(item), ...extraActions],\n [buildSuggestedActions, item, extraActions],\n )\n const timelineEvents = React.useMemo(\n () => getTimelineEvents?.(item) ?? [],\n [getTimelineEvents, item],\n )\n\n const handleDuplicate = React.useCallback(\n (id: number | string) => {\n const base = suggestedActions.find((a) => a.id === id)\n if (!base || base.type !== \"email\") return\n const clone: SuggestedAction = {\n ...base,\n id: `${base.id}-dup-${Date.now()}`,\n emailMeta: base.emailMeta ? { ...base.emailMeta, to: undefined } : undefined,\n }\n setExtraActions((prev) => [...prev, clone])\n },\n [suggestedActions],\n )\n\n // Deadline chip tone (case-panel redesign): amber as the deadline nears, red when due/overdue.\n const timeChipToneClass =\n signalData.timeChipTone === \"red\"\n ? \"border-red-300 bg-red-50 text-red-700 hover:bg-red-50\"\n : signalData.timeChipTone === \"amber\"\n ? \"border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-50\"\n : \"hover:bg-muted/50\"\n\n // The metadata chips row (priority · deadline · account · renderMetadataExtra). Rendered above\n // the brief by default, or beneath it when `metadataLayout === \"below-brief\"` (case-panel redesign).\n const metadataChips = (\n <>\n <SignalPriorityPopover\n score={signalData.score}\n urgencyLabel={signalData.urgencyLabel}\n urgencyExplanation={signalData.urgencyExplanation ?? signalData.signalBrief}\n scoreDisplay={signalData.priorityScoreDisplay}\n formulaLabel={signalData.priorityFormulaLabel}\n factors={signalData.priorityFactors ?? []}\n metaText={undefined}\n feedbackChips={signalData.priorityFeedbackChips}\n onFeedbackSubmit={signalData.onPriorityFeedback}\n initialFactorFeedback={signalData.initialFactorPopoverFeedback}\n onFactorFeedback={signalData.onFactorFeedback}\n initialPriorityFeedback={signalData.initialPriorityFeedback}\n />\n {signalData.timeChipLabel && (\n <Badge\n variant=\"outline\"\n title={signalData.timeChipDetail ?? undefined}\n className={`transition-colors ${timeChipToneClass}`}\n >\n {signalData.timeChipLabel}\n </Badge>\n )}\n <TooltipProvider delayDuration={300}>\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n onClick={onOpenEntityPanel}\n 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\"\n aria-label={getAccountDetailsButtonAriaLabel?.(item) ?? `View account details for ${item.company}`}\n >\n <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\">\n {getCompanyInitials(item.company)}\n </span>\n {accountDetailsButtonLabel ? (\n <span className=\"inline-flex min-w-0 items-center gap-1.5 text-xs font-medium text-foreground\">\n {accountDetailsButtonLabel(item)}\n </span>\n ) : (\n <span className=\"truncate text-xs font-medium text-foreground\">{item.company}</span>\n )}\n <span className=\"flex shrink-0 items-center gap-1.5\">\n <span\n aria-hidden\n className={`h-1.5 w-1.5 shrink-0 rounded-full ${STATUS_DOT_CLASS[item.statusColor] ?? \"bg-muted-foreground/40\"}`}\n />\n {/* \"View account\" expands in on hover via an animated grid column */}\n <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]\">\n <span className=\"overflow-hidden\">\n <span className=\"block whitespace-nowrap text-xs font-medium text-muted-foreground/80\">\n View account\n </span>\n </span>\n </span>\n <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\" />\n </span>\n </button>\n </TooltipTrigger>\n <TooltipContent>Open account panel</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n {renderMetadataExtra?.(item)}\n </>\n )\n\n const timelineSection = sections.timeline && timelineEvents.length > 0 ? (\n <TimelineSection\n timelineEvents={timelineEvents}\n showTimeline={showTimeline}\n setShowTimeline={setShowTimeline}\n showSystemEvents={showSystemEvents}\n setShowSystemEvents={setShowSystemEvents}\n attentionCount={attentionCount}\n sysEvtConfig={sysEvtConfig}\n lastActivityTime={lastActivityTime}\n isCasePanel={isCasePanelV2}\n />\n ) : null\n\n const suggestedActionsSection = sections.suggestedActions ? (\n <SignalApproval.Gate>\n <SuggestedActions\n actions={suggestedActions}\n accountContacts={accountContacts}\n signature={emailSignature}\n iconMap={iconMap}\n onDismiss={(id) => console.log(\"Dismiss action:\", id)}\n onSend={(id) => console.log(\"Send action:\", id)}\n onSaveDraft={(id) => console.log(\"Save draft:\", id)}\n onDuplicate={handleDuplicate}\n onOpenAccountDetails={onOpenEntityPanel}\n onOpenRecentActivity={onOpenRecentActivity}\n onMarkComplete={(id) => console.log(\"Mark complete:\", id)}\n onDispatchAgent={(id) => console.log(\"Dispatch agent:\", id)}\n />\n </SignalApproval.Gate>\n ) : null\n\n return (\n <SignalApproval.Root\n key={item.id}\n companyName={item.company}\n labels={signalLabels}\n hideApproveButton={hideApproveButton}\n approveButtonIconUrl={approveButtonIconUrl}\n opportunityPreview={opportunityPreview}\n onRequestApproval={onRequestApproval}\n initialApprovalState={getSignalApprovalState?.(item)}\n onApprove={(draft) => onSignalApprove?.(item, draft)}\n onApproveFeedback={(reasons, detail) => {\n signalData.onApproveFeedback?.(reasons, detail)\n console.log(\"Approval feedback:\", { taskId: item.id, company: item.company, reasons, detail })\n }}\n onDismiss={(reasons, detail, subReason) => {\n signalData.onDismissFeedback?.(reasons, detail, subReason)\n }}\n >\n <div className=\"mx-auto w-full max-w-3xl p-6 pb-12 md:p-8\">\n <div className=\"pb-8\">\n {/* Header */}\n <div className=\"mb-4 flex items-center gap-2\">\n <span className=\"text-xs text-muted-foreground\">{item.company}</span>\n </div>\n\n <div className=\"mb-3 space-y-3\">\n <div className=\"flex flex-wrap items-start gap-x-3 gap-y-2\">\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"text-2xl font-bold tracking-tight text-foreground\">{item.title}</h1>\n {renderTitleSubtext?.(item)}\n </div>\n {renderTitleExtra?.(item)}\n </div>\n {renderTitleActionRow ? (\n <div className=\"flex w-full flex-wrap items-center gap-2\">\n {renderTitleActionRow(item)}\n </div>\n ) : null}\n </div>\n\n {!isCasePanelV2 && metadataLayout === \"default\" ? (\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">{metadataChips}</div>\n ) : null}\n\n {/* Signal Brief */}\n {sections.signalBrief && (() => {\n const briefHeading = signalBriefCopy?.heading ?? \"Signal brief\"\n const introOpt = signalBriefCopy?.intro\n const briefIntro =\n introOpt === null\n ? null\n : typeof introOpt === \"function\"\n ? introOpt(item)\n : introOpt ?? `Signals indicate a potential opportunity for ${item.company}.`\n const isProminent = briefStyleVariant === \"prominent\";\n return (\n <div className=\"mb-8\">\n {briefHeading ? (\n <h3 className=\"text-xs font-bold text-muted-foreground uppercase tracking-wider mb-3\">{briefHeading}</h3>\n ) : null}\n {briefIntro && !signalData.signalBrief ? (\n <p className={isProminent\n ? \"text-sm text-foreground/70 leading-relaxed mb-2\"\n : \"text-sm text-muted-foreground leading-relaxed mb-2\"\n }>\n {briefIntro}\n </p>\n ) : null}\n {signalData.signalBrief ? (\n <p className={isProminent\n ? \"text-base text-foreground leading-relaxed mb-4\"\n : \"text-sm text-foreground/90 leading-relaxed mb-4\"\n }>\n {signalData.signalBrief}\n </p>\n ) : (\n <p className={isProminent\n ? \"text-base text-foreground leading-relaxed mb-4\"\n : \"text-sm text-foreground/90 leading-relaxed mb-4\"\n }>\n {signalData.whyNow}\n </p>\n )}\n\n {/* Metadata chips relocated beneath the brief (case-panel redesign). */}\n {isCasePanelV2 || metadataLayout === \"below-brief\" ? (\n <div className=\"mb-4 flex flex-wrap items-center gap-2\">{metadataChips}</div>\n ) : null}\n\n {!isCasePanelV2 ? (\n <>\n {/* Before-score content slot (e.g. \"Signals on Case\" chips) */}\n {renderBeforeScore?.(item)}\n\n <ScoreWhyChips\n item={item}\n signalData={signalData}\n onOpenSignalBucket={onOpenSignalBucket}\n />\n <div className=\"mt-4\">\n <SignalApproval.Actions />\n </div>\n </>\n ) : null}\n </div>\n )\n })()}\n\n {isCasePanelV2 ? (\n <>\n {sections.signalBrief ? (\n <>\n {/* Before-score content slot (e.g. status/attention pills) */}\n {renderBeforeScore?.(item)}\n\n <div className=\"mb-8\">\n <ScoreWhyChips\n item={item}\n signalData={signalData}\n onOpenSignalBucket={onOpenSignalBucket}\n />\n <div className=\"mt-4\">\n <SignalApproval.Actions />\n </div>\n </div>\n </>\n ) : null}\n\n <div data-slot=\"case-panel-workflow-stack\" className=\"space-y-10\">\n {/* After-score content slot (e.g. OpportunityPanel) */}\n {renderAfterScore?.(item)}\n {renderPrimaryAction?.(item)}\n {renderCommentArea ? (\n <div data-slot=\"case-panel-comment-area\">\n {renderCommentArea(item)}\n </div>\n ) : null}\n </div>\n {timelineSection}\n </>\n ) : (\n <>\n {/* After-score content slot (e.g. OpportunityPanel) */}\n {renderAfterScore?.(item)}\n\n {/* Activity Timeline */}\n {timelineSection}\n </>\n )}\n </div>\n\n {!isCasePanelV2 ? suggestedActionsSection : null}\n {!isCasePanelV2 ? renderDetailExtra?.(item) : null}\n </div>\n </SignalApproval.Root>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Main Component\n// ---------------------------------------------------------------------------\n\nexport function PrototypeInboxView({\n items,\n filterCategories,\n detailSections,\n accountContacts = [],\n buildAccountContacts,\n emailSignature = \"\",\n buildSuggestedActions: buildSuggestedActionsProp,\n buildSourceItems: buildSourceItemsProp,\n getSignalScore: getSignalScoreProp,\n getTimelineEvents,\n iconMap = {},\n hideToolbarActions,\n hideHoverActions,\n onSuggestedActionFeedback,\n onScoreFeedback,\n onOpenSignalBucket,\n headerActions,\n onOpenEntityPanel,\n onOpenRecentActivity,\n onItemSelect,\n defaultViewMode,\n buildEntityChips,\n quickFilterTabs,\n hideAccountsButton,\n accountDetailsLabel,\n onSignalApprove,\n getSignalApprovalState,\n signalLabels,\n hideApproveButton,\n signalBriefCopy,\n briefStyleVariant,\n sectionLayout,\n renderDetailExtra,\n renderBeforeScore,\n renderAfterScore,\n renderPrimaryAction,\n renderCommentArea,\n lastActivityTime,\n timelineSystemEventsConfig,\n attentionCount,\n renderTitleExtra,\n renderTitleActionRow,\n renderTitleSubtext,\n accountDetailsButtonLabel,\n getAccountDetailsButtonAriaLabel,\n sortOptions,\n activeSortId,\n onSortChange,\n}: PrototypeInboxViewProps) {\n const [inboxViewMode, setInboxViewMode] = React.useState<\"inbox\" | \"list\" | \"detail\">(\n defaultViewMode === \"list\" ? \"list\" : defaultViewMode === \"split\" ? \"inbox\" : \"inbox\"\n )\n const [previousViewMode, setPreviousViewMode] = React.useState<\"inbox\" | \"list\">(\"inbox\")\n const [selectedTask, setSelectedTask] = React.useState(items[0])\n const [inboxAssignee, setInboxAssignee] = React.useState<AssigneeFilter>(\"me\")\n const [inboxFilters, setInboxFilters] = React.useState<Record<string, string>>({})\n const [activeQuickFilter, setActiveQuickFilter] = React.useState<string>(\"all\")\n const [splitViewSearch, setSplitViewSearch] = React.useState(\"\")\n\n const sections = React.useMemo(\n () => ({ ...DEFAULT_DETAIL_SECTIONS, ...detailSections }),\n [detailSections],\n )\n\n const resolvedFilterCategories: InboxFilterCategory[] = React.useMemo(\n () =>\n filterCategories ?? [\n {\n id: \"category\",\n label: \"Category\",\n icon: <Tag className=\"h-3.5 w-3.5 text-muted-foreground\" />,\n options: [...new Set(items.map((i) => i.tag1))],\n },\n {\n id: \"account\",\n label: \"Account\",\n icon: <Building className=\"h-3.5 w-3.5 text-muted-foreground\" />,\n options: [...new Set(items.map((i) => i.company))],\n },\n ],\n [filterCategories, items],\n )\n\n const buildSuggestedActions = React.useMemo(\n () => buildSuggestedActionsProp ?? (() => []),\n [buildSuggestedActionsProp],\n )\n\n const buildSourceItems = React.useMemo(\n () => buildSourceItemsProp ?? (() => []),\n [buildSourceItemsProp],\n )\n\n const getSignalScore = React.useMemo(\n () => getSignalScoreProp ?? (() => DEFAULT_SIGNAL_SCORE),\n [getSignalScoreProp],\n )\n\n // Build a map from filter category id → QueueItem field for targeted filtering.\n // Known category ids are mapped explicitly; unknown categories fall back to a\n // broad search across display fields so consumer-defined filters still work.\n const filterFieldMap = React.useMemo<\n Record<string, (item: QueueItem, value: string) => boolean>\n >(() => {\n const map: Record<string, (item: QueueItem, value: string) => boolean> = {}\n for (const cat of resolvedFilterCategories) {\n switch (cat.id) {\n case \"category\":\n case \"signalType\":\n map[cat.id] = (item, v) => item.tag1.toLowerCase() === v.toLowerCase()\n break\n case \"account\":\n map[cat.id] = (item, v) => item.company.toLowerCase() === v.toLowerCase()\n break\n default:\n // Fallback: check all display fields\n map[cat.id] = (item, v) => {\n const lv = v.toLowerCase()\n return (\n item.tag1.toLowerCase() === lv ||\n item.company.toLowerCase() === lv ||\n item.title.toLowerCase().includes(lv) ||\n item.details.toLowerCase().includes(lv)\n )\n }\n }\n }\n return map\n }, [resolvedFilterCategories])\n\n // Filter items for list view based on toolbar filters\n const filteredItems = React.useMemo(() => {\n const activeFilters = Object.entries(inboxFilters).filter(\n ([, value]) => value && value !== \"all\"\n )\n if (activeFilters.length === 0) return items\n return items.filter((item) =>\n activeFilters.every(([key, value]) => {\n const matcher = filterFieldMap[key]\n return matcher ? matcher(item, value) : true\n })\n )\n }, [items, inboxFilters, filterFieldMap])\n\n // Resolve quick filter tabs once — used by both the split view filter and\n // the tab bar render. Each tab's `matchValue` (falling back to `label`) is\n // compared against `item.tag1` so consumer labels can differ from data values.\n type QuickFilterTab = { id: string; label: string; matchValue?: string; count?: number }\n const resolvedQuickFilterTabs = React.useMemo<QuickFilterTab[]>(() => {\n if (quickFilterTabs) return quickFilterTabs\n // Derive default tabs from the actual item tag1 values\n const uniqueTags = [...new Set(items.map((i) => i.tag1))]\n return uniqueTags.map((tag) => ({\n id: tag.toLowerCase().replace(/\\s+/g, \"-\"),\n label: tag,\n }))\n }, [quickFilterTabs, items])\n\n // Compute per-tab counts once so they can be displayed in the tab bar\n const quickFilterTabCounts = React.useMemo(() => {\n const counts: Record<string, number> = {}\n for (const tab of resolvedQuickFilterTabs) {\n const match = (tab.matchValue ?? tab.label).toLowerCase()\n counts[tab.id] = items.filter((i) => i.tag1.toLowerCase() === match).length\n }\n return counts\n }, [resolvedQuickFilterTabs, items])\n\n // Filter items for split view based on quick filter tabs and search\n const splitViewItems = React.useMemo(() => {\n let filtered = items\n // Apply quick filter tab\n if (activeQuickFilter !== \"all\") {\n const activeTab = resolvedQuickFilterTabs.find((t) => t.id === activeQuickFilter)\n if (activeTab) {\n const match = (activeTab.matchValue ?? activeTab.label).toLowerCase()\n filtered = filtered.filter(\n (item) => item.tag1.toLowerCase() === match\n )\n }\n }\n // Apply search input\n if (splitViewSearch.trim()) {\n const q = splitViewSearch.trim().toLowerCase()\n filtered = filtered.filter(\n (item) =>\n item.tag1.toLowerCase().includes(q) ||\n item.company.toLowerCase().includes(q) ||\n item.title.toLowerCase().includes(q)\n )\n }\n return filtered\n }, [items, activeQuickFilter, resolvedQuickFilterTabs, splitViewSearch])\n\n // Grouped items for list view — one group per severity tier so items\n // are labelled correctly (e.g. \"Urgent\", \"High\", \"Active\").\n const inboxGroups = React.useMemo<GroupedListGroup<QueueItem>[]>(() => {\n const urgent = filteredItems.filter((i) => i.statusColor === \"red\")\n const high = filteredItems.filter((i) => i.statusColor === \"orange\")\n const active = filteredItems.filter((i) => i.statusColor !== \"red\" && i.statusColor !== \"orange\")\n return [\n { key: \"urgent\", label: \"Urgent\", items: urgent },\n { key: \"high\", label: \"High\", items: high },\n { key: \"active\", label: \"Active\", items: active },\n ].filter((g) => g.items.length > 0)\n }, [filteredItems])\n\n const renderInboxRow = React.useCallback(\n (item: QueueItem) => (\n <>\n <span className={`h-2 w-2 shrink-0 rounded-full ${dotColorClass(item.statusColor)}`} />\n <span className=\"w-[80px] shrink-0 font-mono text-xs text-muted-foreground/80\">{item.id}</span>\n <span className=\"shrink-0 rounded-md border border-border bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground whitespace-nowrap\">{item.tag1}</span>\n <span className=\"min-w-0 flex-1 truncate text-sm font-semibold text-foreground\">{item.title}</span>\n <span className=\"w-[120px] shrink-0 truncate text-xs font-medium text-foreground\">{item.company}</span>\n <span className=\"w-[80px] shrink-0 text-right text-xs text-muted-foreground\">{item.time}</span>\n </>\n ),\n [],\n )\n\n const handleInboxItemSelect = React.useCallback(\n (item: QueueItem) => {\n setSelectedTask(item)\n if (onItemSelect) {\n onItemSelect(item)\n } else if (inboxViewMode === \"list\") {\n setPreviousViewMode(\"list\")\n setInboxViewMode(\"detail\")\n }\n },\n [inboxViewMode, onItemSelect],\n )\n\n const handleBackFromDetail = React.useCallback(() => {\n setInboxViewMode(previousViewMode)\n }, [previousViewMode])\n\n const handleViewModeChange = React.useCallback((id: string) => {\n const mode = id as \"inbox\" | \"list\" | \"detail\"\n if (mode !== \"detail\") {\n setPreviousViewMode(mode)\n }\n setInboxViewMode(mode)\n }, [])\n\n React.useEffect(() => {\n const mql = window.matchMedia(\"(max-width: 768px)\")\n function handleChange(e: MediaQueryListEvent | MediaQueryList) {\n if (e.matches && inboxViewMode === \"inbox\") {\n setPreviousViewMode(\"inbox\")\n setInboxViewMode(\"detail\")\n }\n }\n handleChange(mql)\n mql.addEventListener(\"change\", handleChange)\n return () => mql.removeEventListener(\"change\", handleChange)\n }, [inboxViewMode])\n\n const detailViewProps: DetailViewProps = {\n item: selectedTask,\n sections,\n getSignalScore,\n buildSuggestedActions,\n buildSourceItems,\n getTimelineEvents,\n accountContacts: buildAccountContacts?.(selectedTask) ?? accountContacts,\n emailSignature,\n iconMap,\n onOpenEntityPanel,\n onOpenRecentActivity,\n onSuggestedActionFeedback,\n onScoreFeedback,\n onSignalApprove,\n getSignalApprovalState,\n signalLabels,\n hideApproveButton,\n signalBriefCopy,\n briefStyleVariant,\n sectionLayout,\n renderDetailExtra,\n renderBeforeScore,\n renderAfterScore,\n renderPrimaryAction,\n renderCommentArea,\n lastActivityTime,\n timelineSystemEventsConfig,\n attentionCount,\n renderTitleExtra,\n renderTitleActionRow,\n renderTitleSubtext,\n accountDetailsButtonLabel,\n getAccountDetailsButtonAriaLabel,\n onOpenSignalBucket,\n }\n\n return (\n <div className=\"flex h-full w-full flex-col\">\n {/* Toolbar */}\n <div className=\"flex items-center justify-between border-b border-border bg-background px-4 py-3 shrink-0\">\n <div className=\"flex items-center gap-3\">\n {inboxViewMode === \"detail\" ? (\n <button\n type=\"button\"\n onClick={handleBackFromDetail}\n className=\"flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Back\n </button>\n ) : null}\n <h2 className=\"text-lg font-semibold text-foreground\">Inbox</h2>\n <Badge variant=\"secondary\" className=\"bg-muted text-muted-foreground hover:bg-muted font-medium text-[11px] px-2 py-0.5 rounded-md\">\n {items.length}\n </Badge>\n </div>\n <div className=\"flex items-center gap-3\">\n <ViewModeToggle\n modes={[\n { id: \"inbox\", icon: <Columns2 className=\"h-3.5 w-3.5\" />, label: \"Split View\" },\n { id: \"list\", icon: <LayoutList className=\"h-3.5 w-3.5\" />, label: \"List View\" },\n { id: \"detail\", icon: <Square className=\"h-3.5 w-3.5\" />, label: \"Detail View\" },\n ]}\n activeMode={inboxViewMode}\n onModeChange={handleViewModeChange}\n />\n {headerActions}\n </div>\n </div>\n\n {/* View modes */}\n {inboxViewMode === \"detail\" ? (\n <div className=\"flex h-full flex-1 flex-col overflow-hidden bg-background\">\n <div className=\"flex-1 overflow-y-auto\">\n <DetailView {...detailViewProps} />\n </div>\n </div>\n ) : inboxViewMode === \"list\" ? (\n <div className=\"flex-1 overflow-y-auto bg-background\">\n <InboxToolbar\n assignee={inboxAssignee}\n onAssigneeChange={setInboxAssignee}\n filterCategories={resolvedFilterCategories}\n selectedFilters={inboxFilters}\n onFilterChange={(catId, val) =>\n setInboxFilters((prev) => ({ ...prev, [catId]: val }))\n }\n onClearFilters={() => setInboxFilters({})}\n />\n <GroupedListView<QueueItem>\n groups={inboxGroups}\n renderRow={renderInboxRow}\n getItemKey={(item) => item.id}\n selectedKey={selectedTask.id}\n onItemClick={handleInboxItemSelect}\n emptyMessage=\"No inbox items\"\n />\n </div>\n ) : (\n /* Split view */\n <div className=\"flex h-full min-h-0 w-full flex-1\">\n <div className=\"flex h-full min-w-[380px] w-[380px] flex-col border-r border-border bg-background shadow-sm z-10\">\n <div className=\"flex flex-col gap-4 border-b border-border p-4 shrink-0\">\n {!hideToolbarActions && (\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-1\">\n <Button variant=\"outline\" size=\"icon\" className=\"h-8 w-8 text-muted-foreground\"><Eye className=\"w-4 h-4\" /></Button>\n <Button variant=\"outline\" size=\"icon\" className=\"h-8 w-8 text-muted-foreground\"><FileText className=\"w-4 h-4\" /></Button>\n <Button variant=\"outline\" size=\"icon\" className=\"h-8 w-8 text-muted-foreground\"><Clock className=\"w-4 h-4\" /></Button>\n <Button variant=\"outline\" size=\"icon\" className=\"h-8 w-8 text-muted-foreground\"><CheckSquare className=\"w-4 h-4\" /></Button>\n </div>\n <Button size=\"sm\" className=\"h-8 px-4 bg-foreground text-background hover:bg-foreground/90 text-xs font-semibold gap-1.5 rounded-md\">\n <Plus className=\"w-4 h-4\" /> Add Task\n </Button>\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1\">\n <Filter className=\"absolute left-2.5 top-1.5 w-4 h-4 text-muted-foreground\" />\n <Input\n className=\"h-8 pl-8 text-xs bg-background border-border rounded-md shadow-none\"\n placeholder=\"Filter by category...\"\n value={splitViewSearch}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSplitViewSearch(e.target.value)}\n />\n </div>\n {sortOptions && sortOptions.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs font-medium rounded-md shadow-none gap-1.5\">\n <ArrowUpDown className=\"w-3.5 h-3.5\" />\n {sortOptions.find(o => o.id === activeSortId)?.label ?? 'Sort'}\n <ChevronDown className=\"w-3 h-3\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuRadioGroup value={activeSortId ?? ''} onValueChange={(val) => onSortChange?.(val)}>\n {sortOptions.map(option => (\n <DropdownMenuRadioItem key={option.id} value={option.id}>\n {option.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n {!hideAccountsButton && (\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs font-medium rounded-md shadow-none\">\n <Building className=\"w-3.5 h-3.5 mr-1.5\" /> {accountDetailsLabel ?? \"Accounts\"}\n </Button>\n )}\n </div>\n <div className=\"flex items-center gap-1.5 overflow-x-auto pb-1 mt-1 scrollbar-hide\">\n <Button\n size=\"sm\"\n variant={activeQuickFilter === \"all\" ? \"default\" : \"outline\"}\n className={`h-7 rounded-full px-3.5 text-[11px] font-semibold shadow-none ${\n activeQuickFilter === \"all\"\n ? \"bg-foreground text-background hover:bg-foreground/90\"\n : \"bg-transparent border-border text-muted-foreground hover:text-foreground\"\n }`}\n onClick={() => setActiveQuickFilter(\"all\")}\n >\n All\n </Button>\n {resolvedQuickFilterTabs.map((tab) => {\n const count = tab.count ?? quickFilterTabCounts[tab.id]\n return (\n <Button\n key={tab.id}\n size=\"sm\"\n variant={activeQuickFilter === tab.id ? \"default\" : \"outline\"}\n className={`h-7 rounded-full px-3.5 text-[11px] font-medium shadow-none ${\n activeQuickFilter === tab.id\n ? \"bg-foreground text-background hover:bg-foreground/90\"\n : \"bg-transparent border-border text-muted-foreground hover:text-foreground\"\n }`}\n onClick={() => setActiveQuickFilter(tab.id)}\n >\n {tab.label}{count != null && count > 0 ? ` (${count})` : \"\"}\n </Button>\n )\n })}\n </div>\n </div>\n\n <div className=\"flex-1 overflow-y-auto\">\n {splitViewItems.map((item) => (\n <div\n key={item.id}\n onClick={() => { setSelectedTask(item); onItemSelect?.(item) }}\n className={`cursor-pointer border-b border-border p-4 transition-colors group relative border-l-2 ${\n selectedTask.id === item.id\n ? \"bg-muted/30 border-l-brand-purple\"\n : \"bg-transparent border-l-transparent hover:bg-muted/10\"\n }`}\n >\n <div className=\"mb-1.5 flex items-center gap-2\">\n <span className=\"min-w-0 truncate text-[13px] font-semibold text-foreground leading-tight\">{item.title}</span>\n {selectedTask.id !== item.id && item.tag1 && (\n <span className=\"shrink-0 rounded-md border border-border bg-muted/60 px-2 py-0.5 text-[10px] font-medium text-muted-foreground\">\n {item.tag1}\n </span>\n )}\n <span className=\"ml-auto shrink-0 text-[10px] font-medium text-muted-foreground/80\">{item.time}</span>\n </div>\n <div className=\"flex items-start gap-2 mt-2\">\n <span className={`w-1.5 h-1.5 rounded-full shrink-0 mt-1.5 ${dotColorClass(item.statusColor)}`} />\n <span className=\"text-xs text-muted-foreground leading-tight\">{item.details}</span>\n </div>\n {buildEntityChips && (() => {\n const chips = buildEntityChips(item)\n if (!chips.length) return null\n return (\n <div className=\"flex items-center gap-1.5 mt-2 flex-wrap\">\n {chips.map((chip) => (\n <button\n key={chip.id}\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); chip.onClick?.() }}\n className=\"inline-flex items-center gap-1 rounded-md border border-border/60 bg-muted/30 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground\"\n >\n <span className=\"flex h-3.5 w-3.5 items-center justify-center rounded bg-muted-foreground/10 text-[8px] font-semibold\">{chip.avatarLetter}</span>\n {chip.label}\n </button>\n ))}\n </div>\n )\n })()}\n {!hideHoverActions && (\n <div className={`absolute right-4 bottom-4 flex items-center gap-1.5 bg-background shadow-sm rounded-md px-1 py-0.5 border border-border ${\n selectedTask.id === item.id ? \"opacity-100\" : \"opacity-0 group-hover:opacity-100 transition-opacity\"\n }`}>\n <Button variant=\"ghost\" size=\"icon\" className=\"h-6 w-6 rounded text-muted-foreground hover:text-foreground\"><CheckSquare className=\"w-3.5 h-3.5\" /></Button>\n <Button variant=\"ghost\" size=\"icon\" className=\"h-6 w-6 rounded text-muted-foreground hover:text-foreground\"><Clock className=\"w-3.5 h-3.5\" /></Button>\n </div>\n )}\n </div>\n ))}\n <div className=\"p-4\">\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs font-semibold rounded-md shadow-none\">See more</Button>\n </div>\n </div>\n </div>\n\n <div className=\"flex h-full flex-1 flex-col overflow-hidden bg-background\">\n <div className=\"flex-1 overflow-y-auto\">\n <DetailView {...detailViewProps} />\n </div>\n </div>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":["_a"]}
1
+ {"version":3,"sources":["../../src/prototype/prototype-inbox-view.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n ArrowLeft,\n ArrowUpDown,\n ChevronDown,\n ChevronRight,\n Filter,\n FileText,\n Clock,\n CheckSquare,\n Eye,\n Plus,\n\n Building,\n LayoutList,\n Columns2,\n Square,\n Tag,\n} from \"lucide-react\"\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n} from \"../components/dropdown-menu\"\nimport { Button } from \"../components/button\"\nimport { Badge } from \"../components/badge\"\nimport { Input } from \"../components/input\"\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"../components/tooltip\"\nimport { ViewModeToggle } from \"../components/view-mode-toggle\"\nimport {\n InboxToolbar,\n type AssigneeFilter,\n type InboxFilterCategory,\n} from \"../components/inbox-toolbar\"\nimport { GroupedListView, type GroupedListGroup } from \"../components/item-list\"\nimport { SignalApproval, type ApprovalState, type OpportunityDraft, type OpportunityPreview } from \"../components/signal-feedback-inline\"\nimport { ScoreWhyChips } from \"../components/score-why-chips\"\nimport { SignalPriorityPopover } from \"../components/signal-priority-popover\"\nimport { type SourceDef } from \"../components/detail-view\"\nimport {\n SuggestedActions,\n type SuggestedAction,\n type SuggestedContact,\n} from \"../components/suggested-actions\"\nimport { TimelineActivity, type TimelineEvent } from \"../components/timeline-activity\"\nimport { cn } from \"../lib/utils\"\nimport type {\n QueueItem,\n InboxViewConfig,\n InboxDetailSections,\n SignalScoreData,\n BriefStyleVariant,\n TimelineSystemEventsConfig,\n} from \"./prototype-config\"\n\n// ---------------------------------------------------------------------------\n// Dot color utility — maps statusColor to a Tailwind background class.\n// Centralised here so the list row and split-view card stay in sync and\n// future color additions are a single-edit change.\n// ---------------------------------------------------------------------------\n\nfunction dotColorClass(statusColor: string): string {\n if (statusColor === \"red\") return \"bg-[#f43f5e]\"\n if (statusColor === \"yellow\" || statusColor === \"amber\" || statusColor === \"orange\") return \"bg-[#eab308]\"\n if (statusColor === \"green\") return \"bg-[#22c55e]\"\n return \"bg-[#9ca3af]\"\n}\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface PrototypeInboxViewProps extends InboxViewConfig {\n /** Extra ReactNode rendered at the end of the header bar (e.g. exit button). */\n headerActions?: React.ReactNode\n onOpenEntityPanel?: () => void\n onOpenRecentActivity?: () => void\n onNavigateToInbox?: () => void\n onItemSelect?: (item: QueueItem) => void\n defaultViewMode?: \"list\" | \"split\"\n}\n\n// ---------------------------------------------------------------------------\n// Default detail sections\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_DETAIL_SECTIONS: InboxDetailSections = {\n signalBrief: true,\n suggestedActions: true,\n timeline: true,\n}\n\nconst DEFAULT_SIGNAL_SCORE: SignalScoreData = {\n score: 65,\n factors: [\n { key: \"trigger\", label: \"Trigger strength\", score: 70, why: \"Moderate signal detected based on account activity\" },\n { key: \"fit\", label: \"Company fit\", score: 65, why: \"Reasonable fit based on company profile\" },\n { key: \"timing\", label: \"Timing\", score: 58, why: \"Within general evaluation window\" },\n ],\n whyNow: \"Moderate signals detected that warrant review and potential outreach.\",\n evidence: [\n \"Activity patterns suggest potential opportunity\",\n \"Company profile aligns with target segment\",\n ],\n confidence: 72,\n}\n\n// ---------------------------------------------------------------------------\n// Detail View\n// ---------------------------------------------------------------------------\n\n/** Up to 2-character uppercase initials for a company name (e.g. \"Northwind Systems\" -> \"NW\"). */\nfunction getCompanyInitials(company: string): string {\n const words = company.trim().split(/\\s+/).filter(Boolean)\n if (words.length === 0) return \"?\"\n if (words.length === 1) return words[0].slice(0, 2).toUpperCase()\n return (words[0][0] + words[1][0]).toUpperCase()\n}\n\n/** Maps a QueueItem.statusColor to a Tailwind dot background class. */\nconst STATUS_DOT_CLASS: Record<string, string> = {\n red: \"bg-red-500\",\n orange: \"bg-orange-500\",\n amber: \"bg-amber-500\",\n yellow: \"bg-yellow-500\",\n green: \"bg-emerald-500\",\n emerald: \"bg-emerald-500\",\n blue: \"bg-blue-500\",\n gray: \"bg-gray-300\",\n grey: \"bg-gray-300\",\n}\n\nexport interface DetailViewProps {\n item: QueueItem\n sections: InboxDetailSections\n getSignalScore: (company: string, item?: QueueItem) => SignalScoreData\n buildSuggestedActions: (item: QueueItem) => SuggestedAction[]\n buildSourceItems: (item: QueueItem) => SourceDef[]\n getTimelineEvents?: (item: QueueItem) => TimelineEvent[]\n accountContacts: SuggestedContact[]\n emailSignature: string | React.ReactNode\n iconMap: Record<string, string>\n onOpenEntityPanel?: () => void\n onOpenRecentActivity?: () => void\n onSuggestedActionFeedback?: (actionId: number | string, feedback: string, actionTitle?: string) => void\n /** @deprecated The compact score UX no longer renders score-level thumbs by default. */\n onScoreFeedback?: (type: \"up\" | \"down\", pills: string[], detail: string) => void\n onSignalApprove?: (item: QueueItem, draft?: OpportunityDraft) => void | Promise<boolean>\n getSignalApprovalState?: (item: QueueItem) => ApprovalState | undefined\n signalLabels?: InboxViewConfig[\"signalLabels\"]\n hideApproveButton?: boolean\n signalBriefCopy?: InboxViewConfig[\"signalBriefCopy\"]\n briefStyleVariant?: BriefStyleVariant\n /** Opt-in detail panel section ordering. Defaults to the existing layout. */\n sectionLayout?: InboxViewConfig[\"sectionLayout\"]\n renderDetailExtra?: (item: QueueItem) => React.ReactNode\n /** Render content between the signal brief text and the signal score bar (e.g. \"Signals on Case\" chips). */\n renderBeforeScore?: (item: QueueItem) => React.ReactNode\n /** Render content between the signal score section and the activity timeline. */\n renderAfterScore?: (item: QueueItem) => React.ReactNode\n /** Render primary case-panel content between the opportunity section and comment area. */\n renderPrimaryAction?: (item: QueueItem) => React.ReactNode\n /** Render case-panel comment content before the activity timeline. */\n renderCommentArea?: (item: QueueItem) => React.ReactNode\n lastActivityTime?: string\n /** Render extra content inline with the detail title. */\n renderTitleExtra?: (item: QueueItem) => React.ReactNode\n /** Render a full-width action row below the detail title row. */\n renderTitleActionRow?: (item: QueueItem) => React.ReactNode\n /** Render supporting content below the detail title. */\n renderTitleSubtext?: (item: QueueItem) => React.ReactNode\n /** Render extra metadata chips (e.g. assignee) inside the chips row below the title. */\n renderMetadataExtra?: (item: QueueItem) => React.ReactNode\n /**\n * Where the metadata chips row (priority · deadline · account · renderMetadataExtra) sits.\n * - \"default\": above the signal brief (legacy layout, unchanged for existing consumers).\n * - \"below-brief\": moved beneath the brief text, just before `renderBeforeScore`\n * (case-panel redesign — chips + owners read as one block under the brief).\n */\n metadataLayout?: \"default\" | \"below-brief\"\n /** Override the built-in account details metadata button label. */\n accountDetailsButtonLabel?: (item: QueueItem) => React.ReactNode\n /** Accessible label for the built-in account details metadata button. */\n getAccountDetailsButtonAriaLabel?: (item: QueueItem) => string\n onOpenSignalBucket?: (args: { item: QueueItem; bucketKey: string; signalId: string }) => void\n approveButtonIconUrl?: string\n opportunityPreview?: OpportunityPreview\n onRequestApproval?: () => Promise<void>\n /** Number of important/attention-worthy events to highlight on the collapsed timeline header. */\n attentionCount?: number\n /** Configuration for the system-noise events toggle (score changes, etc.). */\n timelineSystemEventsConfig?: TimelineSystemEventsConfig\n\n // ── Deprecated individual props (use timelineSystemEventsConfig instead) ──\n /** @deprecated Use `timelineSystemEventsConfig.toggleLabel`. */\n timelineSystemEventsToggleLabel?: string\n /** @deprecated Use `timelineSystemEventsConfig.storageKey`. */\n timelineSystemEventsStorageKey?: string\n /** @deprecated Use `timelineSystemEventsConfig.defaultVisible`. */\n timelineSystemEventsDefaultVisible?: boolean\n /** @deprecated Use `timelineSystemEventsConfig.hiddenHint`. */\n timelineSystemEventsHiddenHint?: string\n /** @deprecated Use `timelineSystemEventsConfig.visibleHint`. */\n timelineSystemEventsVisibleHint?: string\n}\n\n// ---------------------------------------------------------------------------\n// TimelineSection — extracted from the IIFE in DetailView for readability\n// ---------------------------------------------------------------------------\n\nfunction TimelineSection({\n timelineEvents,\n showTimeline,\n setShowTimeline,\n showSystemEvents,\n setShowSystemEvents,\n attentionCount,\n sysEvtConfig,\n lastActivityTime,\n isCasePanel = false,\n}: {\n timelineEvents: TimelineEvent[]\n showTimeline: boolean\n setShowTimeline: React.Dispatch<React.SetStateAction<boolean>>\n showSystemEvents: boolean\n setShowSystemEvents: React.Dispatch<React.SetStateAction<boolean>>\n attentionCount?: number\n sysEvtConfig?: TimelineSystemEventsConfig\n lastActivityTime?: string\n isCasePanel?: boolean\n}) {\n // Single-pass partition: compute visibleEvents and hiddenCount together\n const visibleEvents: TimelineEvent[] = []\n let hiddenCount = 0\n for (const e of timelineEvents) {\n if (e.isSystemNoise) hiddenCount++\n if (!e.isSystemNoise || showSystemEvents) visibleEvents.push(e)\n }\n const hasSystemNoise = hiddenCount > 0\n\n // The toggle renders whenever there are system-noise events — even if no\n // config was provided — so consumers that emit `isSystemNoise: true` always\n // give users a way to reveal those events.\n const toggleLabel = sysEvtConfig?.toggleLabel ?? \"System events\"\n const toggleHelp = showSystemEvents\n ? sysEvtConfig?.visibleHint?.replace(\"{count}\", String(hiddenCount)) ?? \"Hide system events\"\n : sysEvtConfig?.hiddenHint ?? \"Show system events\"\n\n // Derive \"Last activity\" from the first *visible* event so the collapsed\n // header never points at a hidden score-update. The caller-supplied\n // `lastActivityTime` is only used when system-noise filtering is NOT active\n // (i.e. all events are visible) since it may come from an unfiltered source\n // such as `case.last_activity_at`.\n const firstVisibleTime =\n (!hasSystemNoise || showSystemEvents) && lastActivityTime\n ? lastActivityTime\n : visibleEvents.length > 0\n ? visibleEvents[0].time\n : \"\"\n\n const visibleCount = visibleEvents.length\n const eventCountLabel = `${visibleCount} ${visibleCount === 1 ? \"event\" : \"events\"}`\n\n return (\n <div\n className={cn(\n isCasePanel ? \"mt-8 border-t border-border pt-8 pb-8\" : \"mb-8\"\n )}\n >\n {/* Header — outer non-interactive container */}\n <div\n className={cn(\n \"flex w-full items-center justify-between\",\n isCasePanel\n ? \"gap-4 border-b border-border pb-5\"\n : \"group/timeline gap-2 rounded-md py-2 transition-colors hover:bg-muted/40 -mx-2 px-2\"\n )}\n data-testid=\"timeline-header\"\n >\n {/* Left: collapse/expand button */}\n <button\n type=\"button\"\n onClick={() => setShowTimeline((prev) => !prev)}\n className=\"flex min-w-0 cursor-pointer items-center gap-2 border-0 bg-transparent p-0 text-left\"\n data-testid=\"timeline-collapse-btn\"\n >\n <h3 className=\"text-xs font-bold uppercase tracking-[0.16em] text-muted-foreground transition-colors group-hover/timeline:text-foreground\">ACTIVITY TIMELINE</h3>\n {!showTimeline && attentionCount != null && attentionCount > 0 && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-destructive/10 px-1.5 py-0.5 text-[10px] font-semibold text-destructive border border-destructive/20\">\n {attentionCount} new\n </span>\n )}\n {!isCasePanel && !showTimeline && firstVisibleTime && (\n <span className=\"text-[11px] text-muted-foreground/60\" data-testid=\"last-activity-hint\">\n &middot; Last activity {firstVisibleTime}\n </span>\n )}\n </button>\n\n {/* Right: system-events toggle, event count, and collapse affordance */}\n <div className=\"flex shrink-0 items-center gap-4\">\n {hasSystemNoise && (\n <button\n type=\"button\"\n onClick={() => setShowSystemEvents((prev) => !prev)}\n className={cn(\n \"inline-flex shrink-0 cursor-pointer items-center gap-3 rounded-full border px-3.5 py-2 text-sm font-semibold transition-colors\",\n showSystemEvents\n ? \"border-foreground bg-foreground text-background shadow-sm hover:bg-foreground/90\"\n : \"border-border bg-background text-muted-foreground shadow-sm hover:bg-muted/40 hover:text-foreground\"\n )}\n aria-pressed={showSystemEvents}\n aria-label={toggleHelp}\n title={toggleHelp}\n data-testid=\"system-events-toggle\"\n >\n <span\n className={cn(\n \"relative inline-flex h-4 w-8 shrink-0 items-center rounded-full p-0.5 transition-colors\",\n showSystemEvents ? \"bg-teal-600\" : \"bg-muted-foreground/30\"\n )}\n aria-hidden=\"true\"\n data-testid=\"system-events-indicator\"\n >\n <span\n className={cn(\n \"block h-3 w-3 rounded-full bg-white shadow-sm transition-transform\",\n showSystemEvents ? \"translate-x-4\" : \"translate-x-0\"\n )}\n />\n </span>\n <span>{toggleLabel}</span>\n {!showSystemEvents ? (\n <span\n className=\"inline-flex min-w-[22px] items-center justify-center rounded-full bg-muted px-1.5 text-xs font-bold tabular-nums text-muted-foreground\"\n data-testid=\"hidden-count-badge\"\n >\n {hiddenCount}\n </span>\n ) : null}\n </button>\n )}\n\n <button\n type=\"button\"\n onClick={() => setShowTimeline((prev) => !prev)}\n className={cn(\n \"inline-flex shrink-0 cursor-pointer items-center border-0 bg-transparent p-0 text-muted-foreground transition-colors hover:text-foreground\",\n isCasePanel ? \"gap-3 text-sm\" : \"gap-1.5 text-[11px]\"\n )}\n aria-label={showTimeline ? \"Collapse activity timeline\" : \"Expand activity timeline\"}\n >\n <span className=\"font-medium\" data-testid=\"event-count\">{eventCountLabel}</span>\n <ChevronDown className={`h-3.5 w-3.5 transition-transform duration-200 ${showTimeline ? \"rotate-180\" : \"\"}`} />\n </button>\n </div>\n </div>\n\n {/* Timeline body */}\n {showTimeline && visibleEvents.length > 0 && (\n <div className=\"mt-6\">\n <TimelineActivity events={visibleEvents} variant=\"case-panel\" />\n </div>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Detail View\n// ---------------------------------------------------------------------------\n\nexport function DetailView({\n item,\n sections,\n getSignalScore,\n buildSuggestedActions,\n buildSourceItems: _buildSourceItems,\n getTimelineEvents,\n accountContacts,\n emailSignature,\n iconMap,\n onOpenEntityPanel,\n onOpenRecentActivity,\n onSuggestedActionFeedback: _onSuggestedActionFeedback,\n onScoreFeedback: _onScoreFeedback,\n onSignalApprove,\n getSignalApprovalState,\n signalLabels,\n hideApproveButton,\n signalBriefCopy,\n briefStyleVariant = \"default\",\n sectionLayout = \"default\",\n renderDetailExtra,\n renderBeforeScore,\n renderAfterScore,\n renderPrimaryAction,\n renderCommentArea,\n lastActivityTime,\n renderTitleExtra,\n renderTitleActionRow,\n renderTitleSubtext,\n renderMetadataExtra,\n metadataLayout = \"default\",\n accountDetailsButtonLabel,\n getAccountDetailsButtonAriaLabel,\n onOpenSignalBucket,\n approveButtonIconUrl,\n opportunityPreview,\n onRequestApproval,\n attentionCount,\n timelineSystemEventsConfig: configProp,\n timelineSystemEventsToggleLabel,\n timelineSystemEventsStorageKey,\n timelineSystemEventsDefaultVisible,\n timelineSystemEventsHiddenHint,\n timelineSystemEventsVisibleHint,\n}: DetailViewProps) {\n // Resolve system-events config: prefer the config object, fall back to deprecated individual props.\n const sysEvtConfig = React.useMemo<TimelineSystemEventsConfig | undefined>(() => {\n if (configProp) return configProp\n // Build from deprecated individual props if any are provided\n if (\n timelineSystemEventsToggleLabel ||\n timelineSystemEventsStorageKey ||\n timelineSystemEventsDefaultVisible !== undefined ||\n timelineSystemEventsHiddenHint ||\n timelineSystemEventsVisibleHint\n ) {\n return {\n toggleLabel: timelineSystemEventsToggleLabel,\n storageKey: timelineSystemEventsStorageKey,\n defaultVisible: timelineSystemEventsDefaultVisible,\n hiddenHint: timelineSystemEventsHiddenHint,\n visibleHint: timelineSystemEventsVisibleHint,\n }\n }\n return undefined\n }, [\n configProp,\n timelineSystemEventsToggleLabel,\n timelineSystemEventsStorageKey,\n timelineSystemEventsDefaultVisible,\n timelineSystemEventsHiddenHint,\n timelineSystemEventsVisibleHint,\n ])\n\n const [showTimeline, setShowTimeline] = React.useState(false)\n const [extraActions, setExtraActions] = React.useState<SuggestedAction[]>([])\n\n // ---- System-noise toggle state ----\n const sysEvtDefaultVisible = sysEvtConfig?.defaultVisible ?? false\n const sysEvtStorageKey = sysEvtConfig?.storageKey\n const [showSystemEvents, setShowSystemEvents] = React.useState(sysEvtDefaultVisible)\n const initialReadDoneRef = React.useRef(false)\n\n // Read persisted value from localStorage on mount\n React.useEffect(() => {\n if (!sysEvtStorageKey) {\n initialReadDoneRef.current = true\n return\n }\n try {\n const stored = localStorage.getItem(sysEvtStorageKey)\n if (stored !== null) {\n setShowSystemEvents(stored === \"true\")\n }\n } catch {\n // localStorage unavailable — ignore\n }\n initialReadDoneRef.current = true\n }, [sysEvtStorageKey])\n\n // Write to localStorage when the toggle changes (skip initial if matching default)\n React.useEffect(() => {\n if (!sysEvtStorageKey) return\n if (!initialReadDoneRef.current) return\n try {\n localStorage.setItem(sysEvtStorageKey, String(showSystemEvents))\n } catch {\n // localStorage unavailable — ignore\n }\n }, [showSystemEvents, sysEvtStorageKey])\n\n React.useEffect(() => {\n setShowTimeline(false)\n setExtraActions([])\n }, [item.id])\n\n const signalData = React.useMemo(\n () => getSignalScore(item.company, item),\n [getSignalScore, item],\n )\n\n const suggestedActions = React.useMemo(\n () => [...buildSuggestedActions(item), ...extraActions],\n [buildSuggestedActions, item, extraActions],\n )\n const timelineEvents = React.useMemo(\n () => getTimelineEvents?.(item) ?? [],\n [getTimelineEvents, item],\n )\n\n const handleDuplicate = React.useCallback(\n (id: number | string) => {\n const base = suggestedActions.find((a) => a.id === id)\n if (!base || base.type !== \"email\") return\n const clone: SuggestedAction = {\n ...base,\n id: `${base.id}-dup-${Date.now()}`,\n emailMeta: base.emailMeta ? { ...base.emailMeta, to: undefined } : undefined,\n }\n setExtraActions((prev) => [...prev, clone])\n },\n [suggestedActions],\n )\n\n // Deadline chip tone (case-panel redesign): amber as the deadline nears, red when due/overdue.\n const timeChipToneClass =\n signalData.timeChipTone === \"red\"\n ? \"border-red-300 bg-red-50 text-red-700 hover:bg-red-50\"\n : signalData.timeChipTone === \"amber\"\n ? \"border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-50\"\n : \"hover:bg-muted/50\"\n\n const isCasePanelV2 = sectionLayout === \"case-panel-v2\"\n\n // The metadata chips row (priority · deadline · account · renderMetadataExtra). Rendered above\n // the brief by default, or beneath it when `metadataLayout === \"below-brief\"` (case-panel redesign).\n const metadataChips = (\n <>\n <SignalPriorityPopover\n score={signalData.score}\n urgencyLabel={signalData.urgencyLabel}\n urgencyExplanation={signalData.urgencyExplanation ?? signalData.signalBrief}\n scoreDisplay={signalData.priorityScoreDisplay}\n formulaLabel={signalData.priorityFormulaLabel}\n factors={signalData.priorityFactors ?? []}\n metaText={undefined}\n feedbackChips={signalData.priorityFeedbackChips}\n onFeedbackSubmit={signalData.onPriorityFeedback}\n initialFactorFeedback={signalData.initialFactorPopoverFeedback}\n onFactorFeedback={signalData.onFactorFeedback}\n initialPriorityFeedback={signalData.initialPriorityFeedback}\n />\n {signalData.timeChipLabel && (\n <Badge\n variant=\"outline\"\n title={signalData.timeChipDetail ?? undefined}\n className={`transition-colors ${timeChipToneClass}`}\n >\n {signalData.timeChipLabel}\n </Badge>\n )}\n <TooltipProvider delayDuration={300}>\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n onClick={onOpenEntityPanel}\n 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\"\n aria-label={getAccountDetailsButtonAriaLabel?.(item) ?? `View account details for ${item.company}`}\n >\n <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\">\n {getCompanyInitials(item.company)}\n </span>\n {accountDetailsButtonLabel ? (\n <span className=\"inline-flex min-w-0 items-center gap-1.5 text-xs font-medium text-foreground\">\n {accountDetailsButtonLabel(item)}\n </span>\n ) : (\n <span className=\"truncate text-xs font-medium text-foreground\">{item.company}</span>\n )}\n <span className=\"flex shrink-0 items-center gap-1.5\">\n <span\n aria-hidden\n className={`h-1.5 w-1.5 shrink-0 rounded-full ${STATUS_DOT_CLASS[item.statusColor] ?? \"bg-muted-foreground/40\"}`}\n />\n {/* \"View account\" expands in on hover via an animated grid column */}\n <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]\">\n <span className=\"overflow-hidden\">\n <span className=\"block whitespace-nowrap text-xs font-medium text-muted-foreground/80\">\n View account\n </span>\n </span>\n </span>\n <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\" />\n </span>\n </button>\n </TooltipTrigger>\n <TooltipContent>Open account panel</TooltipContent>\n </Tooltip>\n </TooltipProvider>\n {renderMetadataExtra?.(item)}\n </>\n )\n\n const timelineSection = sections.timeline && timelineEvents.length > 0 ? (\n <TimelineSection\n timelineEvents={timelineEvents}\n showTimeline={showTimeline}\n setShowTimeline={setShowTimeline}\n showSystemEvents={showSystemEvents}\n setShowSystemEvents={setShowSystemEvents}\n attentionCount={attentionCount}\n sysEvtConfig={sysEvtConfig}\n lastActivityTime={lastActivityTime}\n isCasePanel={isCasePanelV2}\n />\n ) : null\n\n const suggestedActionsSection = sections.suggestedActions ? (\n <SignalApproval.Gate>\n <SuggestedActions\n actions={suggestedActions}\n accountContacts={accountContacts}\n signature={emailSignature}\n iconMap={iconMap}\n onDismiss={(id) => console.log(\"Dismiss action:\", id)}\n onSend={(id) => console.log(\"Send action:\", id)}\n onSaveDraft={(id) => console.log(\"Save draft:\", id)}\n onDuplicate={handleDuplicate}\n onOpenAccountDetails={onOpenEntityPanel}\n onOpenRecentActivity={onOpenRecentActivity}\n onMarkComplete={(id) => console.log(\"Mark complete:\", id)}\n onDispatchAgent={(id) => console.log(\"Dispatch agent:\", id)}\n />\n </SignalApproval.Gate>\n ) : null\n\n return (\n <SignalApproval.Root\n key={item.id}\n companyName={item.company}\n labels={signalLabels}\n hideApproveButton={hideApproveButton}\n approveButtonIconUrl={approveButtonIconUrl}\n opportunityPreview={opportunityPreview}\n onRequestApproval={onRequestApproval}\n initialApprovalState={getSignalApprovalState?.(item)}\n onApprove={(draft) => onSignalApprove?.(item, draft)}\n onApproveFeedback={(reasons, detail) => {\n signalData.onApproveFeedback?.(reasons, detail)\n console.log(\"Approval feedback:\", { taskId: item.id, company: item.company, reasons, detail })\n }}\n onDismiss={(reasons, detail, subReason) => {\n signalData.onDismissFeedback?.(reasons, detail, subReason)\n }}\n >\n <div className=\"mx-auto w-full max-w-3xl p-6 pb-12 md:p-8\">\n <div className=\"pb-8\">\n {/* Header */}\n <div className=\"mb-4 flex items-center gap-2\">\n <span className=\"text-xs text-muted-foreground\">{item.company}</span>\n </div>\n\n <div className=\"mb-3 space-y-3\">\n <div className=\"flex flex-wrap items-start gap-x-3 gap-y-2\">\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"text-2xl font-bold tracking-tight text-foreground\">{item.title}</h1>\n {renderTitleSubtext?.(item)}\n </div>\n {renderTitleExtra?.(item)}\n </div>\n {renderTitleActionRow ? (\n <div className=\"flex w-full flex-wrap items-center gap-2\">\n {renderTitleActionRow(item)}\n </div>\n ) : null}\n </div>\n\n {!isCasePanelV2 && metadataLayout === \"default\" ? (\n <div className=\"mb-6 flex flex-wrap items-center gap-2\">{metadataChips}</div>\n ) : null}\n\n {/* Signal Brief */}\n {sections.signalBrief && (() => {\n const briefHeading = signalBriefCopy?.heading ?? \"Signal brief\"\n const introOpt = signalBriefCopy?.intro\n const briefIntro =\n introOpt === null\n ? null\n : typeof introOpt === \"function\"\n ? introOpt(item)\n : introOpt ?? `Signals indicate a potential opportunity for ${item.company}.`\n const isProminent = briefStyleVariant === \"prominent\";\n return (\n <div className=\"mb-8\">\n {briefHeading ? (\n <h3 className=\"text-xs font-bold text-muted-foreground uppercase tracking-wider mb-3\">{briefHeading}</h3>\n ) : null}\n {briefIntro && !signalData.signalBrief ? (\n <p className={isProminent\n ? \"text-sm text-foreground/70 leading-relaxed mb-2\"\n : \"text-sm text-muted-foreground leading-relaxed mb-2\"\n }>\n {briefIntro}\n </p>\n ) : null}\n {signalData.signalBrief ? (\n <p className={isProminent\n ? \"text-base text-foreground leading-relaxed mb-4\"\n : \"text-sm text-foreground/90 leading-relaxed mb-4\"\n }>\n {signalData.signalBrief}\n </p>\n ) : (\n <p className={isProminent\n ? \"text-base text-foreground leading-relaxed mb-4\"\n : \"text-sm text-foreground/90 leading-relaxed mb-4\"\n }>\n {signalData.whyNow}\n </p>\n )}\n\n {/* Metadata chips relocated beneath the brief (case-panel redesign). */}\n {isCasePanelV2 || metadataLayout === \"below-brief\" ? (\n <div className=\"mb-4 flex flex-wrap items-center gap-2\">{metadataChips}</div>\n ) : null}\n\n {!isCasePanelV2 ? (\n <>\n {/* Before-score content slot (e.g. \"Signals on Case\" chips) */}\n {renderBeforeScore?.(item)}\n\n <ScoreWhyChips\n item={item}\n signalData={signalData}\n onOpenSignalBucket={onOpenSignalBucket}\n />\n <div className=\"mt-4\">\n <SignalApproval.Actions />\n </div>\n </>\n ) : null}\n </div>\n )\n })()}\n\n {isCasePanelV2 ? (\n <>\n {sections.signalBrief ? (\n <>\n {/* Before-score content slot (e.g. status/attention pills) */}\n {renderBeforeScore?.(item)}\n\n <div className=\"mb-8\">\n <ScoreWhyChips\n item={item}\n signalData={signalData}\n onOpenSignalBucket={onOpenSignalBucket}\n />\n <div className=\"mt-4\">\n <SignalApproval.Actions />\n </div>\n </div>\n </>\n ) : null}\n\n {/* After-score content slot (e.g. OpportunityPanel) */}\n {renderAfterScore?.(item)}\n {renderPrimaryAction?.(item)}\n {renderCommentArea?.(item)}\n {timelineSection}\n </>\n ) : (\n <>\n {/* After-score content slot (e.g. OpportunityPanel) */}\n {renderAfterScore?.(item)}\n\n {/* Activity Timeline */}\n {timelineSection}\n </>\n )}\n </div>\n\n {!isCasePanelV2 ? suggestedActionsSection : null}\n {!isCasePanelV2 ? renderDetailExtra?.(item) : null}\n </div>\n </SignalApproval.Root>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Main Component\n// ---------------------------------------------------------------------------\n\nexport function PrototypeInboxView({\n items,\n filterCategories,\n detailSections,\n accountContacts = [],\n buildAccountContacts,\n emailSignature = \"\",\n buildSuggestedActions: buildSuggestedActionsProp,\n buildSourceItems: buildSourceItemsProp,\n getSignalScore: getSignalScoreProp,\n getTimelineEvents,\n iconMap = {},\n hideToolbarActions,\n hideHoverActions,\n onSuggestedActionFeedback,\n onScoreFeedback,\n onOpenSignalBucket,\n headerActions,\n onOpenEntityPanel,\n onOpenRecentActivity,\n onItemSelect,\n defaultViewMode,\n buildEntityChips,\n quickFilterTabs,\n hideAccountsButton,\n accountDetailsLabel,\n onSignalApprove,\n getSignalApprovalState,\n signalLabels,\n hideApproveButton,\n signalBriefCopy,\n briefStyleVariant,\n sectionLayout,\n renderDetailExtra,\n renderBeforeScore,\n renderAfterScore,\n renderPrimaryAction,\n renderCommentArea,\n lastActivityTime,\n timelineSystemEventsConfig,\n attentionCount,\n renderTitleExtra,\n renderTitleActionRow,\n renderTitleSubtext,\n accountDetailsButtonLabel,\n getAccountDetailsButtonAriaLabel,\n sortOptions,\n activeSortId,\n onSortChange,\n}: PrototypeInboxViewProps) {\n const [inboxViewMode, setInboxViewMode] = React.useState<\"inbox\" | \"list\" | \"detail\">(\n defaultViewMode === \"list\" ? \"list\" : defaultViewMode === \"split\" ? \"inbox\" : \"inbox\"\n )\n const [previousViewMode, setPreviousViewMode] = React.useState<\"inbox\" | \"list\">(\"inbox\")\n const [selectedTask, setSelectedTask] = React.useState(items[0])\n const [inboxAssignee, setInboxAssignee] = React.useState<AssigneeFilter>(\"me\")\n const [inboxFilters, setInboxFilters] = React.useState<Record<string, string>>({})\n const [activeQuickFilter, setActiveQuickFilter] = React.useState<string>(\"all\")\n const [splitViewSearch, setSplitViewSearch] = React.useState(\"\")\n\n const sections = React.useMemo(\n () => ({ ...DEFAULT_DETAIL_SECTIONS, ...detailSections }),\n [detailSections],\n )\n\n const resolvedFilterCategories: InboxFilterCategory[] = React.useMemo(\n () =>\n filterCategories ?? [\n {\n id: \"category\",\n label: \"Category\",\n icon: <Tag className=\"h-3.5 w-3.5 text-muted-foreground\" />,\n options: [...new Set(items.map((i) => i.tag1))],\n },\n {\n id: \"account\",\n label: \"Account\",\n icon: <Building className=\"h-3.5 w-3.5 text-muted-foreground\" />,\n options: [...new Set(items.map((i) => i.company))],\n },\n ],\n [filterCategories, items],\n )\n\n const buildSuggestedActions = React.useMemo(\n () => buildSuggestedActionsProp ?? (() => []),\n [buildSuggestedActionsProp],\n )\n\n const buildSourceItems = React.useMemo(\n () => buildSourceItemsProp ?? (() => []),\n [buildSourceItemsProp],\n )\n\n const getSignalScore = React.useMemo(\n () => getSignalScoreProp ?? (() => DEFAULT_SIGNAL_SCORE),\n [getSignalScoreProp],\n )\n\n // Build a map from filter category id → QueueItem field for targeted filtering.\n // Known category ids are mapped explicitly; unknown categories fall back to a\n // broad search across display fields so consumer-defined filters still work.\n const filterFieldMap = React.useMemo<\n Record<string, (item: QueueItem, value: string) => boolean>\n >(() => {\n const map: Record<string, (item: QueueItem, value: string) => boolean> = {}\n for (const cat of resolvedFilterCategories) {\n switch (cat.id) {\n case \"category\":\n case \"signalType\":\n map[cat.id] = (item, v) => item.tag1.toLowerCase() === v.toLowerCase()\n break\n case \"account\":\n map[cat.id] = (item, v) => item.company.toLowerCase() === v.toLowerCase()\n break\n default:\n // Fallback: check all display fields\n map[cat.id] = (item, v) => {\n const lv = v.toLowerCase()\n return (\n item.tag1.toLowerCase() === lv ||\n item.company.toLowerCase() === lv ||\n item.title.toLowerCase().includes(lv) ||\n item.details.toLowerCase().includes(lv)\n )\n }\n }\n }\n return map\n }, [resolvedFilterCategories])\n\n // Filter items for list view based on toolbar filters\n const filteredItems = React.useMemo(() => {\n const activeFilters = Object.entries(inboxFilters).filter(\n ([, value]) => value && value !== \"all\"\n )\n if (activeFilters.length === 0) return items\n return items.filter((item) =>\n activeFilters.every(([key, value]) => {\n const matcher = filterFieldMap[key]\n return matcher ? matcher(item, value) : true\n })\n )\n }, [items, inboxFilters, filterFieldMap])\n\n // Resolve quick filter tabs once — used by both the split view filter and\n // the tab bar render. Each tab's `matchValue` (falling back to `label`) is\n // compared against `item.tag1` so consumer labels can differ from data values.\n type QuickFilterTab = { id: string; label: string; matchValue?: string; count?: number }\n const resolvedQuickFilterTabs = React.useMemo<QuickFilterTab[]>(() => {\n if (quickFilterTabs) return quickFilterTabs\n // Derive default tabs from the actual item tag1 values\n const uniqueTags = [...new Set(items.map((i) => i.tag1))]\n return uniqueTags.map((tag) => ({\n id: tag.toLowerCase().replace(/\\s+/g, \"-\"),\n label: tag,\n }))\n }, [quickFilterTabs, items])\n\n // Compute per-tab counts once so they can be displayed in the tab bar\n const quickFilterTabCounts = React.useMemo(() => {\n const counts: Record<string, number> = {}\n for (const tab of resolvedQuickFilterTabs) {\n const match = (tab.matchValue ?? tab.label).toLowerCase()\n counts[tab.id] = items.filter((i) => i.tag1.toLowerCase() === match).length\n }\n return counts\n }, [resolvedQuickFilterTabs, items])\n\n // Filter items for split view based on quick filter tabs and search\n const splitViewItems = React.useMemo(() => {\n let filtered = items\n // Apply quick filter tab\n if (activeQuickFilter !== \"all\") {\n const activeTab = resolvedQuickFilterTabs.find((t) => t.id === activeQuickFilter)\n if (activeTab) {\n const match = (activeTab.matchValue ?? activeTab.label).toLowerCase()\n filtered = filtered.filter(\n (item) => item.tag1.toLowerCase() === match\n )\n }\n }\n // Apply search input\n if (splitViewSearch.trim()) {\n const q = splitViewSearch.trim().toLowerCase()\n filtered = filtered.filter(\n (item) =>\n item.tag1.toLowerCase().includes(q) ||\n item.company.toLowerCase().includes(q) ||\n item.title.toLowerCase().includes(q)\n )\n }\n return filtered\n }, [items, activeQuickFilter, resolvedQuickFilterTabs, splitViewSearch])\n\n // Grouped items for list view — one group per severity tier so items\n // are labelled correctly (e.g. \"Urgent\", \"High\", \"Active\").\n const inboxGroups = React.useMemo<GroupedListGroup<QueueItem>[]>(() => {\n const urgent = filteredItems.filter((i) => i.statusColor === \"red\")\n const high = filteredItems.filter((i) => i.statusColor === \"orange\")\n const active = filteredItems.filter((i) => i.statusColor !== \"red\" && i.statusColor !== \"orange\")\n return [\n { key: \"urgent\", label: \"Urgent\", items: urgent },\n { key: \"high\", label: \"High\", items: high },\n { key: \"active\", label: \"Active\", items: active },\n ].filter((g) => g.items.length > 0)\n }, [filteredItems])\n\n const renderInboxRow = React.useCallback(\n (item: QueueItem) => (\n <>\n <span className={`h-2 w-2 shrink-0 rounded-full ${dotColorClass(item.statusColor)}`} />\n <span className=\"w-[80px] shrink-0 font-mono text-xs text-muted-foreground/80\">{item.id}</span>\n <span className=\"shrink-0 rounded-md border border-border bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground whitespace-nowrap\">{item.tag1}</span>\n <span className=\"min-w-0 flex-1 truncate text-sm font-semibold text-foreground\">{item.title}</span>\n <span className=\"w-[120px] shrink-0 truncate text-xs font-medium text-foreground\">{item.company}</span>\n <span className=\"w-[80px] shrink-0 text-right text-xs text-muted-foreground\">{item.time}</span>\n </>\n ),\n [],\n )\n\n const handleInboxItemSelect = React.useCallback(\n (item: QueueItem) => {\n setSelectedTask(item)\n if (onItemSelect) {\n onItemSelect(item)\n } else if (inboxViewMode === \"list\") {\n setPreviousViewMode(\"list\")\n setInboxViewMode(\"detail\")\n }\n },\n [inboxViewMode, onItemSelect],\n )\n\n const handleBackFromDetail = React.useCallback(() => {\n setInboxViewMode(previousViewMode)\n }, [previousViewMode])\n\n const handleViewModeChange = React.useCallback((id: string) => {\n const mode = id as \"inbox\" | \"list\" | \"detail\"\n if (mode !== \"detail\") {\n setPreviousViewMode(mode)\n }\n setInboxViewMode(mode)\n }, [])\n\n React.useEffect(() => {\n const mql = window.matchMedia(\"(max-width: 768px)\")\n function handleChange(e: MediaQueryListEvent | MediaQueryList) {\n if (e.matches && inboxViewMode === \"inbox\") {\n setPreviousViewMode(\"inbox\")\n setInboxViewMode(\"detail\")\n }\n }\n handleChange(mql)\n mql.addEventListener(\"change\", handleChange)\n return () => mql.removeEventListener(\"change\", handleChange)\n }, [inboxViewMode])\n\n const detailViewProps: DetailViewProps = {\n item: selectedTask,\n sections,\n getSignalScore,\n buildSuggestedActions,\n buildSourceItems,\n getTimelineEvents,\n accountContacts: buildAccountContacts?.(selectedTask) ?? accountContacts,\n emailSignature,\n iconMap,\n onOpenEntityPanel,\n onOpenRecentActivity,\n onSuggestedActionFeedback,\n onScoreFeedback,\n onSignalApprove,\n getSignalApprovalState,\n signalLabels,\n hideApproveButton,\n signalBriefCopy,\n briefStyleVariant,\n sectionLayout,\n renderDetailExtra,\n renderBeforeScore,\n renderAfterScore,\n renderPrimaryAction,\n renderCommentArea,\n lastActivityTime,\n timelineSystemEventsConfig,\n attentionCount,\n renderTitleExtra,\n renderTitleActionRow,\n renderTitleSubtext,\n accountDetailsButtonLabel,\n getAccountDetailsButtonAriaLabel,\n onOpenSignalBucket,\n }\n\n return (\n <div className=\"flex h-full w-full flex-col\">\n {/* Toolbar */}\n <div className=\"flex items-center justify-between border-b border-border bg-background px-4 py-3 shrink-0\">\n <div className=\"flex items-center gap-3\">\n {inboxViewMode === \"detail\" ? (\n <button\n type=\"button\"\n onClick={handleBackFromDetail}\n className=\"flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Back\n </button>\n ) : null}\n <h2 className=\"text-lg font-semibold text-foreground\">Inbox</h2>\n <Badge variant=\"secondary\" className=\"bg-muted text-muted-foreground hover:bg-muted font-medium text-[11px] px-2 py-0.5 rounded-md\">\n {items.length}\n </Badge>\n </div>\n <div className=\"flex items-center gap-3\">\n <ViewModeToggle\n modes={[\n { id: \"inbox\", icon: <Columns2 className=\"h-3.5 w-3.5\" />, label: \"Split View\" },\n { id: \"list\", icon: <LayoutList className=\"h-3.5 w-3.5\" />, label: \"List View\" },\n { id: \"detail\", icon: <Square className=\"h-3.5 w-3.5\" />, label: \"Detail View\" },\n ]}\n activeMode={inboxViewMode}\n onModeChange={handleViewModeChange}\n />\n {headerActions}\n </div>\n </div>\n\n {/* View modes */}\n {inboxViewMode === \"detail\" ? (\n <div className=\"flex h-full flex-1 flex-col overflow-hidden bg-background\">\n <div className=\"flex-1 overflow-y-auto\">\n <DetailView {...detailViewProps} />\n </div>\n </div>\n ) : inboxViewMode === \"list\" ? (\n <div className=\"flex-1 overflow-y-auto bg-background\">\n <InboxToolbar\n assignee={inboxAssignee}\n onAssigneeChange={setInboxAssignee}\n filterCategories={resolvedFilterCategories}\n selectedFilters={inboxFilters}\n onFilterChange={(catId, val) =>\n setInboxFilters((prev) => ({ ...prev, [catId]: val }))\n }\n onClearFilters={() => setInboxFilters({})}\n />\n <GroupedListView<QueueItem>\n groups={inboxGroups}\n renderRow={renderInboxRow}\n getItemKey={(item) => item.id}\n selectedKey={selectedTask.id}\n onItemClick={handleInboxItemSelect}\n emptyMessage=\"No inbox items\"\n />\n </div>\n ) : (\n /* Split view */\n <div className=\"flex h-full min-h-0 w-full flex-1\">\n <div className=\"flex h-full min-w-[380px] w-[380px] flex-col border-r border-border bg-background shadow-sm z-10\">\n <div className=\"flex flex-col gap-4 border-b border-border p-4 shrink-0\">\n {!hideToolbarActions && (\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-1\">\n <Button variant=\"outline\" size=\"icon\" className=\"h-8 w-8 text-muted-foreground\"><Eye className=\"w-4 h-4\" /></Button>\n <Button variant=\"outline\" size=\"icon\" className=\"h-8 w-8 text-muted-foreground\"><FileText className=\"w-4 h-4\" /></Button>\n <Button variant=\"outline\" size=\"icon\" className=\"h-8 w-8 text-muted-foreground\"><Clock className=\"w-4 h-4\" /></Button>\n <Button variant=\"outline\" size=\"icon\" className=\"h-8 w-8 text-muted-foreground\"><CheckSquare className=\"w-4 h-4\" /></Button>\n </div>\n <Button size=\"sm\" className=\"h-8 px-4 bg-foreground text-background hover:bg-foreground/90 text-xs font-semibold gap-1.5 rounded-md\">\n <Plus className=\"w-4 h-4\" /> Add Task\n </Button>\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1\">\n <Filter className=\"absolute left-2.5 top-1.5 w-4 h-4 text-muted-foreground\" />\n <Input\n className=\"h-8 pl-8 text-xs bg-background border-border rounded-md shadow-none\"\n placeholder=\"Filter by category...\"\n value={splitViewSearch}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSplitViewSearch(e.target.value)}\n />\n </div>\n {sortOptions && sortOptions.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs font-medium rounded-md shadow-none gap-1.5\">\n <ArrowUpDown className=\"w-3.5 h-3.5\" />\n {sortOptions.find(o => o.id === activeSortId)?.label ?? 'Sort'}\n <ChevronDown className=\"w-3 h-3\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuRadioGroup value={activeSortId ?? ''} onValueChange={(val) => onSortChange?.(val)}>\n {sortOptions.map(option => (\n <DropdownMenuRadioItem key={option.id} value={option.id}>\n {option.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n {!hideAccountsButton && (\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs font-medium rounded-md shadow-none\">\n <Building className=\"w-3.5 h-3.5 mr-1.5\" /> {accountDetailsLabel ?? \"Accounts\"}\n </Button>\n )}\n </div>\n <div className=\"flex items-center gap-1.5 overflow-x-auto pb-1 mt-1 scrollbar-hide\">\n <Button\n size=\"sm\"\n variant={activeQuickFilter === \"all\" ? \"default\" : \"outline\"}\n className={`h-7 rounded-full px-3.5 text-[11px] font-semibold shadow-none ${\n activeQuickFilter === \"all\"\n ? \"bg-foreground text-background hover:bg-foreground/90\"\n : \"bg-transparent border-border text-muted-foreground hover:text-foreground\"\n }`}\n onClick={() => setActiveQuickFilter(\"all\")}\n >\n All\n </Button>\n {resolvedQuickFilterTabs.map((tab) => {\n const count = tab.count ?? quickFilterTabCounts[tab.id]\n return (\n <Button\n key={tab.id}\n size=\"sm\"\n variant={activeQuickFilter === tab.id ? \"default\" : \"outline\"}\n className={`h-7 rounded-full px-3.5 text-[11px] font-medium shadow-none ${\n activeQuickFilter === tab.id\n ? \"bg-foreground text-background hover:bg-foreground/90\"\n : \"bg-transparent border-border text-muted-foreground hover:text-foreground\"\n }`}\n onClick={() => setActiveQuickFilter(tab.id)}\n >\n {tab.label}{count != null && count > 0 ? ` (${count})` : \"\"}\n </Button>\n )\n })}\n </div>\n </div>\n\n <div className=\"flex-1 overflow-y-auto\">\n {splitViewItems.map((item) => (\n <div\n key={item.id}\n onClick={() => { setSelectedTask(item); onItemSelect?.(item) }}\n className={`cursor-pointer border-b border-border p-4 transition-colors group relative border-l-2 ${\n selectedTask.id === item.id\n ? \"bg-muted/30 border-l-brand-purple\"\n : \"bg-transparent border-l-transparent hover:bg-muted/10\"\n }`}\n >\n <div className=\"mb-1.5 flex items-center gap-2\">\n <span className=\"min-w-0 truncate text-[13px] font-semibold text-foreground leading-tight\">{item.title}</span>\n {selectedTask.id !== item.id && item.tag1 && (\n <span className=\"shrink-0 rounded-md border border-border bg-muted/60 px-2 py-0.5 text-[10px] font-medium text-muted-foreground\">\n {item.tag1}\n </span>\n )}\n <span className=\"ml-auto shrink-0 text-[10px] font-medium text-muted-foreground/80\">{item.time}</span>\n </div>\n <div className=\"flex items-start gap-2 mt-2\">\n <span className={`w-1.5 h-1.5 rounded-full shrink-0 mt-1.5 ${dotColorClass(item.statusColor)}`} />\n <span className=\"text-xs text-muted-foreground leading-tight\">{item.details}</span>\n </div>\n {buildEntityChips && (() => {\n const chips = buildEntityChips(item)\n if (!chips.length) return null\n return (\n <div className=\"flex items-center gap-1.5 mt-2 flex-wrap\">\n {chips.map((chip) => (\n <button\n key={chip.id}\n type=\"button\"\n onClick={(e) => { e.stopPropagation(); chip.onClick?.() }}\n className=\"inline-flex items-center gap-1 rounded-md border border-border/60 bg-muted/30 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground\"\n >\n <span className=\"flex h-3.5 w-3.5 items-center justify-center rounded bg-muted-foreground/10 text-[8px] font-semibold\">{chip.avatarLetter}</span>\n {chip.label}\n </button>\n ))}\n </div>\n )\n })()}\n {!hideHoverActions && (\n <div className={`absolute right-4 bottom-4 flex items-center gap-1.5 bg-background shadow-sm rounded-md px-1 py-0.5 border border-border ${\n selectedTask.id === item.id ? \"opacity-100\" : \"opacity-0 group-hover:opacity-100 transition-opacity\"\n }`}>\n <Button variant=\"ghost\" size=\"icon\" className=\"h-6 w-6 rounded text-muted-foreground hover:text-foreground\"><CheckSquare className=\"w-3.5 h-3.5\" /></Button>\n <Button variant=\"ghost\" size=\"icon\" className=\"h-6 w-6 rounded text-muted-foreground hover:text-foreground\"><Clock className=\"w-3.5 h-3.5\" /></Button>\n </div>\n )}\n </div>\n ))}\n <div className=\"p-4\">\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs font-semibold rounded-md shadow-none\">See more</Button>\n </div>\n </div>\n </div>\n\n <div className=\"flex h-full flex-1 flex-col overflow-hidden bg-background\">\n <div className=\"flex-1 overflow-y-auto\">\n <DetailView {...detailViewProps} />\n </div>\n </div>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":["_a"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handled-ai/design-system",
3
- "version": "0.20.15",
3
+ "version": "0.20.17",
4
4
  "description": "Handled UI component library (shadcn-style, New York)",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@9.12.0",
@@ -37,21 +37,6 @@ describe("CommentComposer", () => {
37
37
  expect(screen.getByText("Comment")).toBeDefined();
38
38
  });
39
39
 
40
- it("uses compact case-panel sizing only when requested", () => {
41
- render(<CommentComposer density="case-panel" onPost={() => {}} />);
42
- const input = screen.getByRole("textbox") as HTMLTextAreaElement;
43
- expect(Number(input.rows)).toBe(1);
44
- expect(input.className).toContain("!min-h-12");
45
-
46
- fireEvent.focus(input);
47
- expect(Number(input.rows)).toBe(1);
48
- expect(input.className).toContain("!min-h-[76px]");
49
-
50
- fireEvent.change(input, { target: { value: "draft" } });
51
- expect(Number(input.rows)).toBe(3);
52
- expect(input.className).toContain("!min-h-28");
53
- });
54
-
55
40
  it("collapses back to single-row structure on Cancel", () => {
56
41
  render(<CommentComposer onPost={() => {}} />);
57
42
  const input = screen.getByRole("textbox") as HTMLTextAreaElement;
@@ -20,7 +20,7 @@ describe("RichTextToolbar", () => {
20
20
  it("font button has aria-haspopup", () => {
21
21
  render(<RichTextToolbar />);
22
22
  const fontBtn = screen.getByLabelText("Font family");
23
- expect(fontBtn.getAttribute("aria-haspopup")).toBe("true");
23
+ expect(fontBtn.getAttribute("aria-haspopup")).toBe("menu");
24
24
  });
25
25
 
26
26
  it("renders all toolbar buttons with data-slot='rich-text-toolbar-button'", () => {
@@ -54,6 +54,33 @@ describe("RichTextToolbar", () => {
54
54
  expect(handler).toHaveBeenCalledWith("font");
55
55
  });
56
56
 
57
+ it("opens a font menu and emits the selected font family when options are provided", () => {
58
+ const handler = vi.fn();
59
+ render(
60
+ <RichTextToolbar
61
+ selectedFontFamily="Arial, Helvetica, sans-serif"
62
+ onFontFamilyChange={handler}
63
+ fontOptions={[
64
+ { label: "Sans Serif", value: "Arial, Helvetica, sans-serif" },
65
+ { label: "Serif", value: "Georgia, serif" },
66
+ ]}
67
+ />,
68
+ );
69
+
70
+ fireEvent.click(screen.getByLabelText("Font family"));
71
+ expect(screen.getByRole("menu", { name: "Font family" })).toBeDefined();
72
+ fireEvent.click(screen.getByRole("menuitemradio", { name: "Serif" }));
73
+
74
+ expect(handler).toHaveBeenCalledWith("Georgia, serif");
75
+ expect(screen.queryByRole("menu", { name: "Font family" })).toBeNull();
76
+ });
77
+
78
+ it("keeps the font button backward compatible when no menu options are supplied", () => {
79
+ render(<RichTextToolbar />);
80
+ fireEvent.click(screen.getByLabelText("Font family"));
81
+ expect(screen.queryByRole("menu", { name: "Font family" })).toBeNull();
82
+ });
83
+
57
84
  it("fires onAction with 'delete' when Delete button clicked", () => {
58
85
  const handler = vi.fn();
59
86
  render(<RichTextToolbar onAction={handler} />);
@@ -25,8 +25,6 @@ export interface CommentComposerProps {
25
25
  /** Current operator (for the avatar). */
26
26
  author?: { name?: string; email?: string; avatarUrl?: string | null }
27
27
  placeholder?: string
28
- /** Compact spacing for the case-panel timeline surface. Defaults preserve the standard composer sizing. */
29
- density?: "default" | "case-panel"
30
28
  /** Hint shown in the footer; defaults to the internal-note reassurance. */
31
29
  hint?: string
32
30
  className?: string
@@ -36,7 +34,6 @@ function CommentComposer({
36
34
  onPost,
37
35
  author,
38
36
  placeholder = "Add a comment or internal note…",
39
- density = "default",
40
37
  hint = "Internal note: only your team sees this",
41
38
  className,
42
39
  }: CommentComposerProps) {
@@ -44,8 +41,6 @@ function CommentComposer({
44
41
  const [focused, setFocused] = React.useState(false)
45
42
  const open = focused || text.length > 0
46
43
  const canPost = text.trim().length > 0
47
- const hasDraft = text.length > 0
48
- const compact = density === "case-panel"
49
44
 
50
45
  const post = () => {
51
46
  const value = text.trim()
@@ -84,19 +79,10 @@ function CommentComposer({
84
79
  onChange={(e) => setText(e.target.value)}
85
80
  onFocus={() => setFocused(true)}
86
81
  placeholder={placeholder}
87
- rows={compact ? (hasDraft ? 3 : 1) : (open ? 4 : 1)}
82
+ rows={open ? 4 : 1}
88
83
  className={cn(
89
- "resize-none rounded-none border-0 bg-transparent px-5 text-[15px] leading-6 shadow-none outline-none placeholder:text-muted-foreground/60 focus-visible:ring-0 focus-visible:ring-offset-0",
90
- compact ? (open ? "py-4" : "py-3") : "py-4",
91
- compact
92
- ? hasDraft
93
- ? "!min-h-28"
94
- : open
95
- ? "!min-h-[76px]"
96
- : "!min-h-12"
97
- : open
98
- ? "min-h-32"
99
- : "min-h-14"
84
+ "resize-none rounded-none border-0 bg-transparent px-5 py-4 text-[15px] leading-6 shadow-none outline-none placeholder:text-muted-foreground/60 focus-visible:ring-0 focus-visible:ring-offset-0",
85
+ open ? "min-h-32" : "min-h-14"
100
86
  )}
101
87
  onKeyDown={(e) => {
102
88
  if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
@@ -107,7 +93,7 @@ function CommentComposer({
107
93
  />
108
94
 
109
95
  {open ? (
110
- <div className={cn("flex items-center justify-between gap-3 border-t border-border bg-muted/10 px-5", compact ? "py-3.5" : "py-4")}>
96
+ <div className="flex items-center justify-between gap-3 border-t border-border bg-muted/10 px-5 py-4">
111
97
  <span className="inline-flex items-center gap-2 text-sm text-muted-foreground">
112
98
  <Lock size={16} strokeWidth={1.75} /> {hint}
113
99
  </span>
@@ -13,8 +13,16 @@ type RichTextAction =
13
13
  | "align" | "list"
14
14
  | "delete"
15
15
 
16
+ interface RichTextFontOption {
17
+ label: string
18
+ value: string
19
+ }
20
+
16
21
  interface RichTextToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
17
22
  onAction?: (action: RichTextAction) => void
23
+ fontOptions?: RichTextFontOption[]
24
+ selectedFontFamily?: string
25
+ onFontFamilyChange?: (fontFamily: string) => void
18
26
  }
19
27
 
20
28
  function ToolbarButton({
@@ -43,7 +51,32 @@ function ToolbarButton({
43
51
  )
44
52
  }
45
53
 
46
- function RichTextToolbar({ onAction, className, ...rest }: RichTextToolbarProps) {
54
+ function RichTextToolbar({
55
+ onAction,
56
+ className,
57
+ fontOptions,
58
+ selectedFontFamily,
59
+ onFontFamilyChange,
60
+ ...rest
61
+ }: RichTextToolbarProps) {
62
+ const [fontMenuOpen, setFontMenuOpen] = React.useState(false)
63
+ const fontMenuRef = React.useRef<HTMLDivElement | null>(null)
64
+ const selectedFontOption = fontOptions?.find((option) => option.value === selectedFontFamily) ?? fontOptions?.[0]
65
+ const fontLabel = selectedFontOption?.label ?? "Sans Serif"
66
+ const hasFontMenu = Boolean(fontOptions?.length && onFontFamilyChange)
67
+
68
+ React.useEffect(() => {
69
+ if (!fontMenuOpen) return
70
+
71
+ function handleDocumentMouseDown(event: MouseEvent) {
72
+ if (fontMenuRef.current?.contains(event.target as Node)) return
73
+ setFontMenuOpen(false)
74
+ }
75
+
76
+ document.addEventListener("mousedown", handleDocumentMouseDown)
77
+ return () => document.removeEventListener("mousedown", handleDocumentMouseDown)
78
+ }, [fontMenuOpen])
79
+
47
80
  return (
48
81
  <div
49
82
  data-slot="rich-text-toolbar"
@@ -58,17 +91,48 @@ function RichTextToolbar({ onAction, className, ...rest }: RichTextToolbarProps)
58
91
 
59
92
  <div className="w-px h-4 bg-border mx-1" aria-hidden="true" />
60
93
 
61
- <button
62
- type="button"
63
- data-slot="rich-text-toolbar-button"
64
- onClick={() => onAction?.("font")}
65
- aria-label="Font family"
66
- aria-haspopup="true"
67
- className="text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1"
68
- >
69
- Sans Serif
70
- <ChevronDown size={10} />
71
- </button>
94
+ <div className="relative" ref={fontMenuRef}>
95
+ <button
96
+ type="button"
97
+ data-slot="rich-text-toolbar-button"
98
+ onClick={() => {
99
+ onAction?.("font")
100
+ if (hasFontMenu) setFontMenuOpen((open) => !open)
101
+ }}
102
+ aria-label="Font family"
103
+ aria-haspopup="menu"
104
+ aria-expanded={hasFontMenu ? fontMenuOpen : undefined}
105
+ className="text-[11px] text-muted-foreground px-1.5 py-0.5 rounded hover:bg-muted/50 cursor-pointer flex items-center gap-1"
106
+ >
107
+ {fontLabel}
108
+ <ChevronDown size={10} />
109
+ </button>
110
+
111
+ {hasFontMenu && fontMenuOpen ? (
112
+ <div
113
+ role="menu"
114
+ aria-label="Font family"
115
+ className="absolute left-0 bottom-full z-50 mb-1 min-w-32 overflow-hidden rounded-md border border-border bg-background py-1 shadow-md"
116
+ >
117
+ {fontOptions!.map((option) => (
118
+ <button
119
+ key={option.value}
120
+ type="button"
121
+ role="menuitemradio"
122
+ aria-checked={option.value === selectedFontFamily}
123
+ className="block w-full px-2.5 py-1.5 text-left text-xs text-foreground hover:bg-muted/60"
124
+ style={{ fontFamily: option.value }}
125
+ onClick={() => {
126
+ onFontFamilyChange?.(option.value)
127
+ setFontMenuOpen(false)
128
+ }}
129
+ >
130
+ {option.label}
131
+ </button>
132
+ ))}
133
+ </div>
134
+ ) : null}
135
+ </div>
72
136
 
73
137
  <div className="w-px h-4 bg-border mx-1" aria-hidden="true" />
74
138
 
@@ -87,4 +151,4 @@ function RichTextToolbar({ onAction, className, ...rest }: RichTextToolbarProps)
87
151
  )
88
152
  }
89
153
 
90
- export { RichTextToolbar, type RichTextToolbarProps, type RichTextAction }
154
+ export { RichTextToolbar, type RichTextToolbarProps, type RichTextAction, type RichTextFontOption }
@@ -152,24 +152,6 @@ describe("DetailView case-panel-v2 section layout", () => {
152
152
  )
153
153
  })
154
154
 
155
- it("adds a consistent stack around the case-panel workflow sections", () => {
156
- const { container } = renderDetailView({
157
- sectionLayout: "case-panel-v2",
158
- renderPrimaryAction: () => <section>Primary action marker</section>,
159
- renderCommentArea: () => <section>Comment area marker</section>,
160
- })
161
-
162
- const stack = container.querySelector('[data-slot="case-panel-workflow-stack"]')
163
- const commentArea = container.querySelector('[data-slot="case-panel-comment-area"]')
164
- expect(stack?.className).toContain("space-y-10")
165
- expect(commentArea).not.toBeNull()
166
- expectInDocumentOrder(
167
- screen.getByText("Primary action marker"),
168
- screen.getByText("Comment area marker"),
169
- screen.getByText(/activity timeline/i),
170
- )
171
- })
172
-
173
155
  it("renders signal brief and chip-backed Why section without a separate The why block", () => {
174
156
  renderDetailView({ sectionLayout: "case-panel-v2" })
175
157
 
@@ -142,31 +142,6 @@ describe("DetailView timeline system-events toggle", () => {
142
142
  expect(container.textContent).not.toContain("Score updated -1")
143
143
  })
144
144
 
145
- it("keeps case-panel system events off by default even when localStorage has a stale visible value", async () => {
146
- store["test-show-score-changes"] = "true"
147
- const { container } = render(<DetailView {...baseProps({ sectionLayout: "case-panel-v2" })} />)
148
- expandTimeline(container)
149
-
150
- const toggle = container.querySelector('[data-testid="system-events-toggle"]') as HTMLElement
151
- const eventCount = container.querySelector('[data-testid="event-count"]')
152
- expect(toggle).toHaveAttribute("aria-pressed", "false")
153
- expect(eventCount?.textContent).toBe("2 events")
154
- expect(container.textContent).not.toContain("Score updated +3")
155
- expect(localStorageMock.setItem).not.toHaveBeenCalledWith("test-show-score-changes", "false")
156
- })
157
-
158
- it("uses a shorter, quieter system-events control in the case-panel variant", () => {
159
- const { container } = render(<DetailView {...baseProps({ sectionLayout: "case-panel-v2" })} />)
160
- const toggle = container.querySelector('[data-testid="system-events-toggle"]') as HTMLElement
161
- const indicator = container.querySelector('[data-testid="system-events-indicator"]') as HTMLElement
162
- const count = container.querySelector('[data-testid="hidden-count-badge"]') as HTMLElement
163
-
164
- expect(toggle.className).toContain("py-1")
165
- expect(toggle.className).toContain("text-xs")
166
- expect(indicator.className).toContain("h-3.5")
167
- expect(count.className).toContain("text-[11px]")
168
- })
169
-
170
145
  it("reveals system-noise events when toggle is clicked", () => {
171
146
  const { container } = render(<DetailView {...baseProps()} />)
172
147
  expandTimeline(container)
@@ -309,12 +309,9 @@ function TimelineSection({
309
309
  type="button"
310
310
  onClick={() => setShowSystemEvents((prev) => !prev)}
311
311
  className={cn(
312
- "inline-flex shrink-0 cursor-pointer items-center rounded-full border transition-colors",
313
- isCasePanel ? "gap-2 px-2.5 py-1 text-xs font-medium" : "gap-3 px-3.5 py-2 text-sm font-semibold",
312
+ "inline-flex shrink-0 cursor-pointer items-center gap-3 rounded-full border px-3.5 py-2 text-sm font-semibold transition-colors",
314
313
  showSystemEvents
315
- ? isCasePanel
316
- ? "border-border bg-muted text-foreground shadow-sm hover:bg-muted/80"
317
- : "border-foreground bg-foreground text-background shadow-sm hover:bg-foreground/90"
314
+ ? "border-foreground bg-foreground text-background shadow-sm hover:bg-foreground/90"
318
315
  : "border-border bg-background text-muted-foreground shadow-sm hover:bg-muted/40 hover:text-foreground"
319
316
  )}
320
317
  aria-pressed={showSystemEvents}
@@ -324,25 +321,23 @@ function TimelineSection({
324
321
  >
325
322
  <span
326
323
  className={cn(
327
- "relative inline-flex shrink-0 items-center rounded-full p-0.5 transition-colors",
328
- isCasePanel ? "h-3.5 w-7" : "h-4 w-8",
329
- showSystemEvents ? "bg-teal-600" : "bg-muted-foreground/25"
324
+ "relative inline-flex h-4 w-8 shrink-0 items-center rounded-full p-0.5 transition-colors",
325
+ showSystemEvents ? "bg-teal-600" : "bg-muted-foreground/30"
330
326
  )}
331
327
  aria-hidden="true"
332
328
  data-testid="system-events-indicator"
333
329
  >
334
330
  <span
335
331
  className={cn(
336
- "block rounded-full bg-white shadow-sm transition-transform",
337
- isCasePanel ? "h-2.5 w-2.5" : "h-3 w-3",
338
- showSystemEvents ? (isCasePanel ? "translate-x-3.5" : "translate-x-4") : "translate-x-0"
332
+ "block h-3 w-3 rounded-full bg-white shadow-sm transition-transform",
333
+ showSystemEvents ? "translate-x-4" : "translate-x-0"
339
334
  )}
340
335
  />
341
336
  </span>
342
337
  <span>{toggleLabel}</span>
343
338
  {!showSystemEvents ? (
344
339
  <span
345
- className={cn("inline-flex items-center justify-center rounded-full bg-muted px-1.5 font-bold tabular-nums text-muted-foreground", isCasePanel ? "min-w-5 text-[11px]" : "min-w-[22px] text-xs")}
340
+ className="inline-flex min-w-[22px] items-center justify-center rounded-full bg-muted px-1.5 text-xs font-bold tabular-nums text-muted-foreground"
346
341
  data-testid="hidden-count-badge"
347
342
  >
348
343
  {hiddenCount}
@@ -455,8 +450,6 @@ export function DetailView({
455
450
  timelineSystemEventsVisibleHint,
456
451
  ])
457
452
 
458
- const isCasePanelV2 = sectionLayout === "case-panel-v2"
459
-
460
453
  const [showTimeline, setShowTimeline] = React.useState(false)
461
454
  const [extraActions, setExtraActions] = React.useState<SuggestedAction[]>([])
462
455
 
@@ -466,12 +459,9 @@ export function DetailView({
466
459
  const [showSystemEvents, setShowSystemEvents] = React.useState(sysEvtDefaultVisible)
467
460
  const initialReadDoneRef = React.useRef(false)
468
461
 
469
- // Read persisted value from localStorage on mount. Case-panel timelines always
470
- // start from the configured default so stale local state cannot make system
471
- // events prominent by default in the inbox detail panel.
462
+ // Read persisted value from localStorage on mount
472
463
  React.useEffect(() => {
473
- if (!sysEvtStorageKey || isCasePanelV2) {
474
- setShowSystemEvents(sysEvtDefaultVisible)
464
+ if (!sysEvtStorageKey) {
475
465
  initialReadDoneRef.current = true
476
466
  return
477
467
  }
@@ -479,27 +469,23 @@ export function DetailView({
479
469
  const stored = localStorage.getItem(sysEvtStorageKey)
480
470
  if (stored !== null) {
481
471
  setShowSystemEvents(stored === "true")
482
- } else {
483
- setShowSystemEvents(sysEvtDefaultVisible)
484
472
  }
485
473
  } catch {
486
474
  // localStorage unavailable — ignore
487
- setShowSystemEvents(sysEvtDefaultVisible)
488
475
  }
489
476
  initialReadDoneRef.current = true
490
- }, [isCasePanelV2, sysEvtDefaultVisible, sysEvtStorageKey])
477
+ }, [sysEvtStorageKey])
491
478
 
492
- // Write to localStorage when the toggle changes (skip case-panel timelines so
493
- // they stay off by default on the next case/session).
479
+ // Write to localStorage when the toggle changes (skip initial if matching default)
494
480
  React.useEffect(() => {
495
- if (!sysEvtStorageKey || isCasePanelV2) return
481
+ if (!sysEvtStorageKey) return
496
482
  if (!initialReadDoneRef.current) return
497
483
  try {
498
484
  localStorage.setItem(sysEvtStorageKey, String(showSystemEvents))
499
485
  } catch {
500
486
  // localStorage unavailable — ignore
501
487
  }
502
- }, [isCasePanelV2, showSystemEvents, sysEvtStorageKey])
488
+ }, [showSystemEvents, sysEvtStorageKey])
503
489
 
504
490
  React.useEffect(() => {
505
491
  setShowTimeline(false)
@@ -542,6 +528,8 @@ export function DetailView({
542
528
  ? "border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-50"
543
529
  : "hover:bg-muted/50"
544
530
 
531
+ const isCasePanelV2 = sectionLayout === "case-panel-v2"
532
+
545
533
  // The metadata chips row (priority · deadline · account · renderMetadataExtra). Rendered above
546
534
  // the brief by default, or beneath it when `metadataLayout === "below-brief"` (case-panel redesign).
547
535
  const metadataChips = (
@@ -774,16 +762,10 @@ export function DetailView({
774
762
  </>
775
763
  ) : null}
776
764
 
777
- <div data-slot="case-panel-workflow-stack" className="space-y-10">
778
- {/* After-score content slot (e.g. OpportunityPanel) */}
779
- {renderAfterScore?.(item)}
780
- {renderPrimaryAction?.(item)}
781
- {renderCommentArea ? (
782
- <div data-slot="case-panel-comment-area">
783
- {renderCommentArea(item)}
784
- </div>
785
- ) : null}
786
- </div>
765
+ {/* After-score content slot (e.g. OpportunityPanel) */}
766
+ {renderAfterScore?.(item)}
767
+ {renderPrimaryAction?.(item)}
768
+ {renderCommentArea?.(item)}
787
769
  {timelineSection}
788
770
  </>
789
771
  ) : (