@sensiblestats/widget-react 0.37.3 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -36,7 +36,7 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/StatsWidget.tsx
39
- var import_react23 = require("react");
39
+ var import_react24 = require("react");
40
40
  var import_widget_sdk5 = require("@sensiblestats/widget-sdk");
41
41
 
42
42
  // src/kickoff.ts
@@ -57,6 +57,8 @@ var STRINGS = {
57
57
  launcherLabel: "Chat with SensibleStats",
58
58
  placeholder: "Ask about a team, player or match\u2026",
59
59
  closeChat: "Close chat",
60
+ newConversation: "New conversation",
61
+ moreOptions: "More options",
60
62
  chatDialogLabel: "SensibleStats chat",
61
63
  stopResponse: "Stop response",
62
64
  sendMessage: "Send message",
@@ -129,6 +131,8 @@ var STRINGS = {
129
131
  launcherLabel: "SensibleStats ile sohbet et",
130
132
  placeholder: "Bir tak\u0131m, oyuncu veya ma\xE7 hakk\u0131nda sor\u2026",
131
133
  closeChat: "Sohbeti kapat",
134
+ newConversation: "Yeni sohbet",
135
+ moreOptions: "Di\u011Fer se\xE7enekler",
132
136
  chatDialogLabel: "SensibleStats sohbet",
133
137
  stopResponse: "Yan\u0131t\u0131 durdur",
134
138
  sendMessage: "Mesaj g\xF6nder",
@@ -224,6 +228,8 @@ function normalizeUi(cfg) {
224
228
  starters: cfg.starters ?? [],
225
229
  injectStyles: cfg.injectStyles ?? true,
226
230
  closeChat: strings.closeChat,
231
+ newConversation: strings.newConversation,
232
+ moreOptions: strings.moreOptions,
227
233
  chatDialogLabel: strings.chatDialogLabel,
228
234
  stopResponse: strings.stopResponse,
229
235
  sendMessage: strings.sendMessage,
@@ -642,7 +648,7 @@ function marketOddsRowToSlipSelection(vm, row, conversationId) {
642
648
  // src/reducer.ts
643
649
  var POPUP_MAX = 160;
644
650
  function initialState(starters) {
645
- return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, error: null, lastUserInput: null, lastDisplayText: null, popupText: null, conversationId: null };
651
+ return { isOpen: false, messages: [], isStreaming: false, status: null, actionsLoading: false, starters, configuredStarters: starters, error: null, lastUserInput: null, lastDisplayText: null, popupText: null, conversationId: null };
646
652
  }
647
653
  function activeTurn(state) {
648
654
  const last = state.messages[state.messages.length - 1];
@@ -722,6 +728,19 @@ function reducer(state, action) {
722
728
  }
723
729
  case "DISMISS_POPUP":
724
730
  return { ...state, popupText: null };
731
+ case "RESET":
732
+ return { ...initialState(state.configuredStarters), isOpen: state.isOpen };
733
+ case "RESTORE":
734
+ return {
735
+ ...state,
736
+ messages: action.messages,
737
+ conversationId: action.conversationId,
738
+ isStreaming: false,
739
+ status: null,
740
+ actionsLoading: false,
741
+ starters: action.messages.length ? [] : state.configuredStarters,
742
+ error: null
743
+ };
725
744
  case "EVENT":
726
745
  return applyEvent(state, action.event);
727
746
  case "STREAM_ERROR":
@@ -841,17 +860,82 @@ function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
841
860
  }, [anchorKey, messages, forceUpdate]);
842
861
  }
843
862
 
863
+ // src/conversationStore.ts
864
+ var TTL_MS = 36e5;
865
+ var MAX_MESSAGES = 30;
866
+ function readConversation(storageKey, now) {
867
+ let raw;
868
+ try {
869
+ raw = window.localStorage.getItem(storageKey);
870
+ } catch {
871
+ return null;
872
+ }
873
+ if (!raw) return null;
874
+ let parsed;
875
+ try {
876
+ parsed = JSON.parse(raw);
877
+ } catch {
878
+ return "corrupt";
879
+ }
880
+ if (parsed?.v !== 1 || !Array.isArray(parsed.messages) || typeof parsed.updatedAt !== "number") return "corrupt";
881
+ if (now - parsed.updatedAt > TTL_MS) return "expired";
882
+ return { conversationId: parsed.conversationId ?? null, messages: parsed.messages };
883
+ }
884
+ function writeConversation(storageKey, conversationId, messages, now) {
885
+ let capped = messages.slice(-MAX_MESSAGES);
886
+ for (let attempt = 0; attempt < 2; attempt++) {
887
+ try {
888
+ window.localStorage.setItem(storageKey, JSON.stringify({ v: 1, updatedAt: now, conversationId, messages: capped }));
889
+ return;
890
+ } catch {
891
+ if (capped.length <= 1) return;
892
+ capped = capped.slice(Math.ceil(capped.length / 2));
893
+ }
894
+ }
895
+ }
896
+ function clearConversation(storageKey) {
897
+ try {
898
+ window.localStorage.removeItem(storageKey);
899
+ } catch {
900
+ }
901
+ }
902
+
844
903
  // src/useChatStream.ts
904
+ function hasAnswerContent(t) {
905
+ return !!t.text || t.cards.length > 0 || t.insights.length > 0 || t.marketOdds.length > 0 || t.oddsMovement.length > 0 || !!t.greetingDashboard || !!t.clubFanDashboard;
906
+ }
845
907
  var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
846
- function useChatStream(conversation, starterTexts, greetingMessage) {
847
- const seed = (0, import_react2.useMemo)(() => initialState(starterTexts.map(chipFromText)), []);
848
- const [state, dispatch] = (0, import_react2.useReducer)(reducer, seed);
849
- const idRef = (0, import_react2.useRef)(0);
908
+ function seedIdCounter(messages) {
909
+ let max = -1;
910
+ for (const m of messages) {
911
+ const n = Number(m.id.slice(1));
912
+ if (m.id[0] === "m" && Number.isFinite(n) && n > max) max = n;
913
+ }
914
+ return max + 1;
915
+ }
916
+ function useChatStream(conversation, starterTexts, greetingMessage, storageKey) {
917
+ const boot = (0, import_react2.useMemo)(() => {
918
+ const payload = storageKey ? readConversation(storageKey, Date.now()) : null;
919
+ const restored = payload != null && payload !== "expired" && payload !== "corrupt";
920
+ const base = initialState(starterTexts.map(chipFromText));
921
+ if (restored) {
922
+ return { state: { ...base, messages: payload.messages, conversationId: payload.conversationId, starters: [] }, restored: true, stale: false };
923
+ }
924
+ return { state: base, restored: false, stale: payload === "expired" || payload === "corrupt" };
925
+ }, []);
926
+ const [state, dispatch] = (0, import_react2.useReducer)(reducer, boot.state);
927
+ const idRef = (0, import_react2.useRef)(seedIdCounter(boot.state.messages));
850
928
  const handleRef = (0, import_react2.useRef)(null);
851
929
  const fallbackTimerRef = (0, import_react2.useRef)(null);
852
930
  const stateRef = (0, import_react2.useRef)(state);
853
931
  stateRef.current = state;
854
- const greetedRef = (0, import_react2.useRef)(false);
932
+ const greetedRef = (0, import_react2.useRef)(boot.restored);
933
+ const hydratedRef = (0, import_react2.useRef)(false);
934
+ const restoreSeededRef = (0, import_react2.useRef)(false);
935
+ if (!restoreSeededRef.current) {
936
+ restoreSeededRef.current = true;
937
+ if (boot.restored && boot.state.conversationId) conversation.conversationId = boot.state.conversationId;
938
+ }
855
939
  const nextId = () => `m${idRef.current++}`;
856
940
  const clearFallbackTimer = (0, import_react2.useCallback)(() => {
857
941
  if (fallbackTimerRef.current != null) {
@@ -907,10 +991,9 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
907
991
  const last = stateRef.current.lastUserInput;
908
992
  if (last) run(last, stateRef.current.lastDisplayText ?? void 0);
909
993
  }, [run]);
910
- (0, import_react2.useEffect)(() => {
994
+ const fireGreeting = (0, import_react2.useCallback)(() => {
911
995
  const message = greetingMessage?.trim();
912
- if (!message || greetedRef.current) return;
913
- greetedRef.current = true;
996
+ if (!message) return;
914
997
  try {
915
998
  handleRef.current?.cancel();
916
999
  clearFallbackTimer();
@@ -923,6 +1006,55 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
923
1006
  dispatch({ kind: "STOP" });
924
1007
  }
925
1008
  }, [greetingMessage, conversation, consume, clearFallbackTimer, armFallbackTimer]);
1009
+ (0, import_react2.useEffect)(() => {
1010
+ if (greetedRef.current) return;
1011
+ greetedRef.current = true;
1012
+ fireGreeting();
1013
+ }, [fireGreeting]);
1014
+ (0, import_react2.useEffect)(() => {
1015
+ if (boot.stale && storageKey) clearConversation(storageKey);
1016
+ }, []);
1017
+ (0, import_react2.useEffect)(() => {
1018
+ if (!storageKey || state.isStreaming) return;
1019
+ if (hydratedRef.current) {
1020
+ hydratedRef.current = false;
1021
+ return;
1022
+ }
1023
+ const msgs = state.messages;
1024
+ const persistable = msgs.filter((m, i) => {
1025
+ if (m.role === "user") {
1026
+ const next = msgs[i + 1];
1027
+ return !!next && next.role === "assistant" && next.done && !next.isGreeting && hasAnswerContent(next);
1028
+ }
1029
+ return m.done && !m.isGreeting && hasAnswerContent(m);
1030
+ });
1031
+ if (persistable.length === 0) return;
1032
+ writeConversation(storageKey, state.conversationId, persistable, Date.now());
1033
+ }, [storageKey, state.isStreaming, state.messages, state.conversationId]);
1034
+ (0, import_react2.useEffect)(() => {
1035
+ if (!storageKey) return;
1036
+ const onStorage = (e) => {
1037
+ if (e.key !== storageKey || e.storageArea !== window.localStorage) return;
1038
+ if (stateRef.current.isStreaming) return;
1039
+ if (e.newValue == null) {
1040
+ idRef.current = 0;
1041
+ conversation.conversationId = void 0;
1042
+ hydratedRef.current = true;
1043
+ dispatch({ kind: "RESET" });
1044
+ greetedRef.current = true;
1045
+ fireGreeting();
1046
+ return;
1047
+ }
1048
+ const parsed = readConversation(storageKey, Date.now());
1049
+ if (!parsed || parsed === "expired" || parsed === "corrupt") return;
1050
+ idRef.current = seedIdCounter(parsed.messages);
1051
+ conversation.conversationId = parsed.conversationId ?? void 0;
1052
+ hydratedRef.current = true;
1053
+ dispatch({ kind: "RESTORE", messages: parsed.messages, conversationId: parsed.conversationId });
1054
+ };
1055
+ window.addEventListener("storage", onStorage);
1056
+ return () => window.removeEventListener("storage", onStorage);
1057
+ }, [storageKey, conversation, fireGreeting]);
926
1058
  const open = (0, import_react2.useCallback)(() => dispatch({ kind: "OPEN" }), []);
927
1059
  const close = (0, import_react2.useCallback)(() => {
928
1060
  handleRef.current?.cancel();
@@ -941,9 +1073,20 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
941
1073
  else open();
942
1074
  }, [close, open]);
943
1075
  const dismissPopup = (0, import_react2.useCallback)(() => dispatch({ kind: "DISMISS_POPUP" }), []);
1076
+ const reset = (0, import_react2.useCallback)(() => {
1077
+ handleRef.current?.cancel();
1078
+ handleRef.current = null;
1079
+ clearFallbackTimer();
1080
+ if (storageKey) clearConversation(storageKey);
1081
+ idRef.current = 0;
1082
+ conversation.conversationId = void 0;
1083
+ dispatch({ kind: "RESET" });
1084
+ greetedRef.current = true;
1085
+ fireGreeting();
1086
+ }, [storageKey, conversation, clearFallbackTimer, fireGreeting]);
944
1087
  const getLiveDashboard = (0, import_react2.useCallback)(() => conversation.getLiveDashboard(), [conversation]);
945
1088
  useLiveDashboardPoll({ messages: state.messages, dispatch, getLiveDashboard });
946
- return { state, open, close, stop, toggle, send, retry, dismissPopup };
1089
+ return { state, open, close, stop, toggle, send, retry, dismissPopup, reset };
947
1090
  }
948
1091
 
949
1092
  // src/useLiveMonitor.ts
@@ -1288,6 +1431,16 @@ function CloseIcon() {
1288
1431
  function ChevronIcon() {
1289
1432
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) });
1290
1433
  }
1434
+ function NewChatIcon() {
1435
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", d: "M12 5v14M5 12h14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) });
1436
+ }
1437
+ function KebabIcon() {
1438
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { fill: "currentColor", children: [
1439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "5", r: "1.6" }),
1440
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "1.6" }),
1441
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "19", r: "1.6" })
1442
+ ] }) });
1443
+ }
1291
1444
  var DASHBOARD_EMOJI = {
1292
1445
  football: "\u26BD",
1293
1446
  "goal-net": "\u{1F945}",
@@ -1352,20 +1505,89 @@ function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup,
1352
1505
  }
1353
1506
 
1354
1507
  // src/components/ChatPanel.tsx
1355
- var import_react22 = require("react");
1508
+ var import_react23 = require("react");
1356
1509
 
1357
1510
  // src/components/ChatHeader.tsx
1511
+ var import_react6 = require("react");
1358
1512
  var import_jsx_runtime5 = require("react/jsx-runtime");
1359
- function ChatHeader({ onClose, closeLabel = "Close chat", logoUrl, headerTitle }) {
1513
+ function ChatHeader({
1514
+ onClose,
1515
+ closeLabel = "Close chat",
1516
+ logoUrl,
1517
+ headerTitle,
1518
+ onNewConversation,
1519
+ canReset,
1520
+ newConversationLabel = "New conversation",
1521
+ moreOptionsLabel = "More options"
1522
+ }) {
1523
+ const [menuOpen, setMenuOpen] = (0, import_react6.useState)(false);
1524
+ const wrapRef = (0, import_react6.useRef)(null);
1525
+ const kebabRef = (0, import_react6.useRef)(null);
1526
+ const firstItemRef = (0, import_react6.useRef)(null);
1527
+ const hasMenu = !!(canReset && onNewConversation);
1528
+ (0, import_react6.useEffect)(() => {
1529
+ if (menuOpen) firstItemRef.current?.focus();
1530
+ }, [menuOpen]);
1531
+ (0, import_react6.useEffect)(() => {
1532
+ if (!menuOpen) return;
1533
+ const onPointer = (e) => {
1534
+ if (!wrapRef.current?.contains(e.target)) setMenuOpen(false);
1535
+ };
1536
+ const onKey = (e) => {
1537
+ if (e.key === "Escape") {
1538
+ setMenuOpen(false);
1539
+ kebabRef.current?.focus();
1540
+ }
1541
+ };
1542
+ document.addEventListener("mousedown", onPointer);
1543
+ document.addEventListener("keydown", onKey);
1544
+ return () => {
1545
+ document.removeEventListener("mousedown", onPointer);
1546
+ document.removeEventListener("keydown", onKey);
1547
+ };
1548
+ }, [menuOpen]);
1549
+ const runNewConversation = () => {
1550
+ setMenuOpen(false);
1551
+ onNewConversation?.();
1552
+ };
1360
1553
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "ss-w-header", children: [
1361
1554
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BrandMark, { logoUrl, className: "ss-w-header-mark" }),
1362
1555
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "ss-w-header-title", children: headerTitle ?? "SensibleStats" }),
1556
+ hasMenu ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ss-w-menu-wrap", ref: wrapRef, children: [
1557
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1558
+ "button",
1559
+ {
1560
+ type: "button",
1561
+ className: "ss-w-kebab",
1562
+ ref: kebabRef,
1563
+ "aria-haspopup": "menu",
1564
+ "aria-expanded": menuOpen,
1565
+ "aria-label": moreOptionsLabel,
1566
+ onClick: () => setMenuOpen((open) => !open),
1567
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(KebabIcon, {})
1568
+ }
1569
+ ),
1570
+ menuOpen ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "ss-w-menu", role: "menu", "aria-label": moreOptionsLabel, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1571
+ "button",
1572
+ {
1573
+ type: "button",
1574
+ className: "ss-w-menu-item",
1575
+ role: "menuitem",
1576
+ ref: firstItemRef,
1577
+ onClick: runNewConversation,
1578
+ children: [
1579
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NewChatIcon, {}),
1580
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: newConversationLabel })
1581
+ ]
1582
+ }
1583
+ ) }) : null
1584
+ ] }) : null,
1363
1585
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "ss-w-close", "aria-label": closeLabel, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, {}) })
1364
1586
  ] });
1365
1587
  }
1366
1588
 
1367
1589
  // src/components/MessageList.tsx
1368
- var import_react20 = require("react");
1590
+ var import_react21 = require("react");
1369
1591
 
1370
1592
  // src/components/Markdown.tsx
1371
1593
  var import_react_markdown = __toESM(require("react-markdown"), 1);
@@ -1383,7 +1605,7 @@ function Markdown({ text }) {
1383
1605
  }
1384
1606
 
1385
1607
  // src/components/EntityCardList.tsx
1386
- var import_react6 = require("react");
1608
+ var import_react7 = require("react");
1387
1609
  var import_jsx_runtime7 = require("react/jsx-runtime");
1388
1610
  var MAX_VISIBLE = 3;
1389
1611
  var RECORD_RE = /^(\d+)-(\d+)-(\d+)$/;
@@ -1467,7 +1689,7 @@ function ScopeTabs({ scopes, active, onSelect }) {
1467
1689
  )) });
1468
1690
  }
1469
1691
  function EntityCardItem({ c }) {
1470
- const [active, setActive] = (0, import_react6.useState)(0);
1692
+ const [active, setActive] = (0, import_react7.useState)(0);
1471
1693
  const hasBody = c.scopes.length > 0;
1472
1694
  if (!hasBody) {
1473
1695
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "ss-w-ecard ss-w-ecard-bare", "data-kind": c.kind, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CardHeader, { c, showScopeCrest: false }) });
@@ -1481,7 +1703,7 @@ function EntityCardItem({ c }) {
1481
1703
  ] });
1482
1704
  }
1483
1705
  function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMoreLabel = (n) => `Show ${n} more` }) {
1484
- const [expanded, setExpanded] = (0, import_react6.useState)(false);
1706
+ const [expanded, setExpanded] = (0, import_react7.useState)(false);
1485
1707
  if (cards.length === 0) return null;
1486
1708
  const visible = expanded ? cards : cards.slice(0, MAX_VISIBLE);
1487
1709
  const overflow = cards.length - MAX_VISIBLE;
@@ -1492,18 +1714,18 @@ function EntityCardList({ cards, disabled, showLessLabel = "Show less", showMore
1492
1714
  }
1493
1715
 
1494
1716
  // src/components/BettingInsightList.tsx
1495
- var import_react11 = require("react");
1717
+ var import_react12 = require("react");
1496
1718
 
1497
1719
  // src/components/AddToSlipButton.tsx
1498
- var import_react8 = require("react");
1720
+ var import_react9 = require("react");
1499
1721
  var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
1500
1722
 
1501
1723
  // src/components/Toast.tsx
1502
- var import_react7 = require("react");
1724
+ var import_react8 = require("react");
1503
1725
  var import_jsx_runtime8 = require("react/jsx-runtime");
1504
1726
  var DISMISS_MS = 4e3;
1505
1727
  function Toast({ toast, onDismiss }) {
1506
- (0, import_react7.useEffect)(() => {
1728
+ (0, import_react8.useEffect)(() => {
1507
1729
  if (!toast) return;
1508
1730
  const timer = setTimeout(onDismiss, DISMISS_MS);
1509
1731
  return () => clearTimeout(timer);
@@ -1521,10 +1743,10 @@ function withTimeout(value) {
1521
1743
  ]);
1522
1744
  }
1523
1745
  function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
1524
- const [pending, setPending] = (0, import_react8.useState)(false);
1525
- const [settled, setSettled] = (0, import_react8.useState)(false);
1526
- const [toast, setToast] = (0, import_react8.useState)(null);
1527
- const dismiss = (0, import_react8.useCallback)(() => setToast(null), []);
1746
+ const [pending, setPending] = (0, import_react9.useState)(false);
1747
+ const [settled, setSettled] = (0, import_react9.useState)(false);
1748
+ const [toast, setToast] = (0, import_react9.useState)(null);
1749
+ const dismiss = (0, import_react9.useCallback)(() => setToast(null), []);
1528
1750
  const handler = slip.onAddToSlip;
1529
1751
  if (!ui || !handler || !selection) return null;
1530
1752
  const onClick = async () => {
@@ -1588,14 +1810,14 @@ function OddsMovementBadge({
1588
1810
  }
1589
1811
 
1590
1812
  // src/components/Crest.tsx
1591
- var import_react9 = require("react");
1813
+ var import_react10 = require("react");
1592
1814
  var import_jsx_runtime11 = require("react/jsx-runtime");
1593
1815
  function initials2(name) {
1594
1816
  return (name ?? "").trim().slice(0, 1).toUpperCase() || "?";
1595
1817
  }
1596
1818
  function Crest2({ url, name, kind }) {
1597
1819
  const cls = kind === "comp" ? "ss-w-bi-comp-logo" : "ss-w-bi-crest";
1598
- const [failed, setFailed] = (0, import_react9.useState)(false);
1820
+ const [failed, setFailed] = (0, import_react10.useState)(false);
1599
1821
  if (url && !failed) {
1600
1822
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
1601
1823
  }
@@ -1603,7 +1825,7 @@ function Crest2({ url, name, kind }) {
1603
1825
  }
1604
1826
 
1605
1827
  // src/components/PinToggle.tsx
1606
- var import_react10 = require("react");
1828
+ var import_react11 = require("react");
1607
1829
  var import_jsx_runtime12 = require("react/jsx-runtime");
1608
1830
  var PIN_GLYPHS = {
1609
1831
  pin: { viewBox: "0 0 24 24", strokeWidth: 1.4, transform: "scale(1.5)", d: "M9.5 1.5 14.5 6.5 13 8l-.5-.5-3 3 .3 2.2-1.4 1.4L5.6 11.3l-3.3 3.3-1-1 3.3-3.3L1.9 7.5l1.4-1.4 2.2.3 3-3L8 2.9z" },
@@ -1611,8 +1833,8 @@ var PIN_GLYPHS = {
1611
1833
  bookmark: { viewBox: "0 0 24 24", strokeWidth: 2, d: "M7 3h10a1.5 1.5 0 0 1 1.5 1.5V21l-6.5-3.6L5.5 21V4.5A1.5 1.5 0 0 1 7 3z" }
1612
1834
  };
1613
1835
  function PinToggle({ pin, monitor, ui }) {
1614
- const [toast, setToast] = (0, import_react10.useState)(null);
1615
- const dismiss = (0, import_react10.useCallback)(() => setToast(null), []);
1836
+ const [toast, setToast] = (0, import_react11.useState)(null);
1837
+ const dismiss = (0, import_react11.useCallback)(() => setToast(null), []);
1616
1838
  if (!monitor || !monitor.enabled) return null;
1617
1839
  const key = pinKey(pin);
1618
1840
  const pinned = monitor.isPinned(key);
@@ -1664,7 +1886,7 @@ function StatRows({ stats }) {
1664
1886
  ] }, `${s.label}:${s.value}`)) });
1665
1887
  }
1666
1888
  function ScopedStats({ scopes }) {
1667
- const [active, setActive] = (0, import_react11.useState)(0);
1889
+ const [active, setActive] = (0, import_react12.useState)(0);
1668
1890
  const current = scopes[active] ?? scopes[0] ?? { key: "", label: "", stats: [] };
1669
1891
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ss-w-bi-scoped", children: [
1670
1892
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ss-w-bi-scopes", role: "group", children: scopes.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", className: "ss-w-bi-chip", "aria-pressed": i === active, onClick: () => setActive(i), children: s.label }, s.key)) }),
@@ -1675,7 +1897,7 @@ function hasStatsContent(it) {
1675
1897
  return !!it.scopes && it.scopes.length >= 2 || it.stats.length > 0;
1676
1898
  }
1677
1899
  function ReasonStrip({ it, ui }) {
1678
- const [open, setOpen] = (0, import_react11.useState)(false);
1900
+ const [open, setOpen] = (0, import_react12.useState)(false);
1679
1901
  const hasScopes = !!it.scopes && it.scopes.length >= 2;
1680
1902
  const hasStats = hasStatsContent(it);
1681
1903
  const hasText = !!it.text;
@@ -1785,7 +2007,7 @@ function BettingInsightList({ insights, ui, slip, monitor }) {
1785
2007
  }
1786
2008
 
1787
2009
  // src/components/MarketOddsCardList.tsx
1788
- var import_react12 = require("react");
2010
+ var import_react13 = require("react");
1789
2011
  var import_jsx_runtime14 = require("react/jsx-runtime");
1790
2012
  function OddsRow({ vm, row, ui, slip, inSlipIds }) {
1791
2013
  const selection = vm.addToSlipEnabled ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
@@ -1802,7 +2024,7 @@ function OddsRow({ vm, row, ui, slip, inSlipIds }) {
1802
2024
  ] });
1803
2025
  }
1804
2026
  function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader, pinRef }) {
1805
- const [open, setOpen] = (0, import_react12.useState)(false);
2027
+ const [open, setOpen] = (0, import_react13.useState)(false);
1806
2028
  const kickoff = vm.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(vm.kickoffUtc) : void 0;
1807
2029
  const hasNamedTeams = !!vm.homeTeamName && !!vm.awayTeamName;
1808
2030
  const showPin = !!monitor && vm.pinEnabled;
@@ -2097,7 +2319,7 @@ function FixtureCard({ card }) {
2097
2319
  }
2098
2320
 
2099
2321
  // src/components/dashboard/CountdownCard.tsx
2100
- var import_react13 = require("react");
2322
+ var import_react14 = require("react");
2101
2323
  var import_jsx_runtime20 = require("react/jsx-runtime");
2102
2324
  var DAY_MS = 864e5;
2103
2325
  function remainingSegments(kickoffUnix, nowMs, withSeconds) {
@@ -2116,12 +2338,12 @@ function remainingSegments(kickoffUnix, nowMs, withSeconds) {
2116
2338
  function CountdownCard({ card }) {
2117
2339
  const { data } = card;
2118
2340
  const unitLabel = { d: data.daysUnit, h: data.hoursUnit, m: data.minutesUnit, s: data.secondsUnit };
2119
- const [nowMs, setNowMs] = (0, import_react13.useState)(() => Date.now());
2120
- const [reduced] = (0, import_react13.useState)(
2341
+ const [nowMs, setNowMs] = (0, import_react14.useState)(() => Date.now());
2342
+ const [reduced] = (0, import_react14.useState)(
2121
2343
  () => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
2122
2344
  );
2123
2345
  const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
2124
- (0, import_react13.useEffect)(() => {
2346
+ (0, import_react14.useEffect)(() => {
2125
2347
  if (reduced) return;
2126
2348
  const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
2127
2349
  return () => window.clearInterval(id);
@@ -2129,7 +2351,7 @@ function CountdownCard({ card }) {
2129
2351
  const segments = remainingSegments(data.kickoffUnix, nowMs, withinDay && !reduced);
2130
2352
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "ss-w-fandash-card ss-w-fandash-countdown", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "ss-w-fandash-countdown-top", children: [
2131
2353
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-eyebrow", children: card.title ?? "Kick-off in" }),
2132
- segments ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-clock", children: segments.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react13.Fragment, { children: [
2354
+ segments ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-clock", children: segments.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_react14.Fragment, { children: [
2133
2355
  i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-colon", children: ":" }) : null,
2134
2356
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "ss-w-fandash-countdown-seg", children: [
2135
2357
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-fandash-countdown-value", children: s.value }),
@@ -2140,7 +2362,7 @@ function CountdownCard({ card }) {
2140
2362
  }
2141
2363
 
2142
2364
  // src/components/dashboard/InjuriesCard.tsx
2143
- var import_react14 = require("react");
2365
+ var import_react15 = require("react");
2144
2366
  var import_jsx_runtime21 = require("react/jsx-runtime");
2145
2367
  var SHOWN = 3;
2146
2368
  function ColHead({ name, crestUrl }) {
@@ -2163,7 +2385,7 @@ function InjuryList({ rows, emptyLabel, limit }) {
2163
2385
  }
2164
2386
  function InjuriesCard({ card, ui }) {
2165
2387
  const { data } = card;
2166
- const [expanded, setExpanded] = (0, import_react14.useState)(false);
2388
+ const [expanded, setExpanded] = (0, import_react15.useState)(false);
2167
2389
  const hidden = Math.max(0, data.ours.length - SHOWN) + Math.max(0, data.theirs.length - SHOWN);
2168
2390
  const limit = expanded ? void 0 : SHOWN;
2169
2391
  const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
@@ -2267,7 +2489,7 @@ function LinksCard({ card }) {
2267
2489
  }
2268
2490
 
2269
2491
  // src/components/dashboard/PostMatchResultCard.tsx
2270
- var import_react15 = require("react");
2492
+ var import_react16 = require("react");
2271
2493
  var import_jsx_runtime27 = require("react/jsx-runtime");
2272
2494
  function initials3(name) {
2273
2495
  return name.trim().slice(0, 3).toUpperCase();
@@ -2277,7 +2499,7 @@ function PostMatchCrest({
2277
2499
  name,
2278
2500
  side
2279
2501
  }) {
2280
- const [failed, setFailed] = (0, import_react15.useState)(false);
2502
+ const [failed, setFailed] = (0, import_react16.useState)(false);
2281
2503
  const cls = `ss-w-fandash-postmatch-crest ss-w-fandash-postmatch-crest--${side}`;
2282
2504
  if (url && !failed) {
2283
2505
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
@@ -2476,10 +2698,10 @@ function RestingFormCard({ card }) {
2476
2698
  }
2477
2699
 
2478
2700
  // src/components/dashboard/LiveScoreCard.tsx
2479
- var import_react17 = require("react");
2701
+ var import_react18 = require("react");
2480
2702
 
2481
2703
  // src/useLiveClock.ts
2482
- var import_react16 = require("react");
2704
+ var import_react17 = require("react");
2483
2705
  var TICK_MS = 6e4;
2484
2706
  var MAX_LIVE_TICK_MINUTES = 15;
2485
2707
  function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
@@ -2488,10 +2710,10 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
2488
2710
  return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
2489
2711
  }
2490
2712
  function useLiveClock(minute, clockRunning, receivedAtMs) {
2491
- const [displayMinute, setDisplayMinute] = (0, import_react16.useState)(
2713
+ const [displayMinute, setDisplayMinute] = (0, import_react17.useState)(
2492
2714
  () => computeDisplayMinute(minute, clockRunning, receivedAtMs)
2493
2715
  );
2494
- (0, import_react16.useEffect)(() => {
2716
+ (0, import_react17.useEffect)(() => {
2495
2717
  setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
2496
2718
  if (!clockRunning) return;
2497
2719
  const id = window.setInterval(() => {
@@ -2508,7 +2730,7 @@ function initials5(name) {
2508
2730
  return name.trim().slice(0, 3).toUpperCase();
2509
2731
  }
2510
2732
  function LiveCrest({ url, name, side }) {
2511
- const [failed, setFailed] = (0, import_react17.useState)(false);
2733
+ const [failed, setFailed] = (0, import_react18.useState)(false);
2512
2734
  const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
2513
2735
  if (url && !failed) {
2514
2736
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
@@ -2527,7 +2749,7 @@ function LiveScoreCard({
2527
2749
  }) {
2528
2750
  const { data } = card;
2529
2751
  const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
2530
- const seedRef = (0, import_react17.useRef)(null);
2752
+ const seedRef = (0, import_react18.useRef)(null);
2531
2753
  if (!seedRef.current || seedRef.current.key !== seedKey) {
2532
2754
  seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
2533
2755
  }
@@ -2567,7 +2789,7 @@ function LiveScoreCard({
2567
2789
  }
2568
2790
 
2569
2791
  // src/components/dashboard/LiveTimelineCard.tsx
2570
- var import_react18 = require("react");
2792
+ var import_react19 = require("react");
2571
2793
  var import_jsx_runtime37 = require("react/jsx-runtime");
2572
2794
  var SHOWN2 = 3;
2573
2795
  function EventIcon({ type }) {
@@ -2609,7 +2831,7 @@ function EventRow({ e }) {
2609
2831
  ] });
2610
2832
  }
2611
2833
  function LiveTimelineCard({ card, ui }) {
2612
- const [expanded, setExpanded] = (0, import_react18.useState)(false);
2834
+ const [expanded, setExpanded] = (0, import_react19.useState)(false);
2613
2835
  const events = card.data.events;
2614
2836
  if (!events || events.length === 0) return null;
2615
2837
  const sorted = [...events].sort((a, b) => b.minute - a.minute);
@@ -2634,7 +2856,7 @@ function LiveTimelineCard({ card, ui }) {
2634
2856
  }
2635
2857
 
2636
2858
  // src/components/dashboard/MomentumWave.tsx
2637
- var import_react19 = require("react");
2859
+ var import_react20 = require("react");
2638
2860
  var import_jsx_runtime38 = require("react/jsx-runtime");
2639
2861
  var WIDTH = 270;
2640
2862
  var HEIGHT = 56;
@@ -2658,7 +2880,7 @@ function MomentumWave({
2658
2880
  }) {
2659
2881
  if (points.length === 0) return null;
2660
2882
  const { area, line, lastLeftPct, lastTopPx } = buildPaths(points);
2661
- const clipId = (0, import_react19.useId)();
2883
+ const clipId = (0, import_react20.useId)();
2662
2884
  const topClipId = `${clipId}-top`;
2663
2885
  const bottomClipId = `${clipId}-bottom`;
2664
2886
  const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
@@ -2849,11 +3071,11 @@ function turnAnchor(messages) {
2849
3071
  }
2850
3072
  function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
2851
3073
  const { state, send } = controller;
2852
- const listRef = (0, import_react20.useRef)(null);
2853
- const endRef = (0, import_react20.useRef)(null);
2854
- const pinnedRef = (0, import_react20.useRef)(true);
2855
- const anchoredTurnRef = (0, import_react20.useRef)(null);
2856
- (0, import_react20.useEffect)(() => {
3074
+ const listRef = (0, import_react21.useRef)(null);
3075
+ const endRef = (0, import_react21.useRef)(null);
3076
+ const pinnedRef = (0, import_react21.useRef)(true);
3077
+ const anchoredTurnRef = (0, import_react21.useRef)(null);
3078
+ (0, import_react21.useEffect)(() => {
2857
3079
  const anchor = turnAnchor(state.messages);
2858
3080
  const list = listRef.current;
2859
3081
  if (anchor && anchor.turnId !== anchoredTurnRef.current) {
@@ -2909,10 +3131,10 @@ function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
2909
3131
  }
2910
3132
 
2911
3133
  // src/components/ChatInput.tsx
2912
- var import_react21 = require("react");
3134
+ var import_react22 = require("react");
2913
3135
  var import_jsx_runtime44 = require("react/jsx-runtime");
2914
3136
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
2915
- const [value, setValue] = (0, import_react21.useState)("");
3137
+ const [value, setValue] = (0, import_react22.useState)("");
2916
3138
  const submit = () => {
2917
3139
  const t = value.trim();
2918
3140
  if (!t) return;
@@ -3219,8 +3441,8 @@ function OddsPanel({ monitor, ui, slip }) {
3219
3441
  var import_jsx_runtime49 = require("react/jsx-runtime");
3220
3442
  var FULLSCREEN_QUERY = "(max-width: 639px)";
3221
3443
  function useIsModal() {
3222
- const [modal, setModal] = (0, import_react22.useState)(false);
3223
- (0, import_react22.useEffect)(() => {
3444
+ const [modal, setModal] = (0, import_react23.useState)(false);
3445
+ (0, import_react23.useEffect)(() => {
3224
3446
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
3225
3447
  const mql = window.matchMedia(FULLSCREEN_QUERY);
3226
3448
  const sync = () => setModal(mql.matches);
@@ -3231,9 +3453,9 @@ function useIsModal() {
3231
3453
  return modal;
3232
3454
  }
3233
3455
  function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
3234
- const { state, close, stop, send } = controller;
3456
+ const { state, close, stop, send, reset } = controller;
3235
3457
  const isModal = useIsModal();
3236
- (0, import_react22.useEffect)(() => {
3458
+ (0, import_react23.useEffect)(() => {
3237
3459
  const onKey = (e) => {
3238
3460
  if (e.key === "Escape") close();
3239
3461
  };
@@ -3244,11 +3466,23 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor }) {
3244
3466
  monitor?.setActiveTab("chat");
3245
3467
  send(text);
3246
3468
  };
3247
- (0, import_react22.useEffect)(() => {
3469
+ (0, import_react23.useEffect)(() => {
3248
3470
  if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
3249
3471
  }, [monitor, state.isStreaming, state.messages.length]);
3250
3472
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
3251
- /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat, logoUrl: ui.logoUrl, headerTitle: ui.headerTitle }),
3473
+ /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3474
+ ChatHeader,
3475
+ {
3476
+ onClose: close,
3477
+ closeLabel: ui.closeChat,
3478
+ logoUrl: ui.logoUrl,
3479
+ headerTitle: ui.headerTitle,
3480
+ onNewConversation: reset,
3481
+ canReset: state.messages.length > 0,
3482
+ newConversationLabel: ui.newConversation,
3483
+ moreOptionsLabel: ui.moreOptions
3484
+ }
3485
+ ),
3252
3486
  monitor && /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
3253
3487
  TabBar,
3254
3488
  {
@@ -3363,8 +3597,14 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
3363
3597
  }
3364
3598
  .ss-w-header { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--ss-w-line); }
3365
3599
  .ss-w-header-mark { width: 18px; height: 18px; display: block; object-fit: contain; }
3366
- .ss-w-header-title { font-weight: 660; }
3367
- .ss-w-close { margin-left: auto; background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
3600
+ .ss-w-header-title { font-weight: 660; flex: 1; }
3601
+ .ss-w-close { background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
3602
+ .ss-w-menu-wrap { position: relative; display: grid; place-items: center; }
3603
+ .ss-w-kebab { background: none; border: none; color: var(--ss-w-faint); cursor: pointer; font-size: 16px; display: grid; place-items: center; }
3604
+ .ss-w-menu { position: absolute; top: calc(100% + 6px); right: 0; z-index: 5; min-width: 184px; padding: 6px; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,.18); }
3605
+ .ss-w-menu-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px; background: none; border: none; border-radius: 8px; color: var(--ss-w-ink); font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
3606
+ .ss-w-menu-item:hover, .ss-w-menu-item:focus-visible { background: var(--ss-w-bubble); }
3607
+ .ss-w-menu-item svg { color: var(--ss-w-faint); font-size: 15px; flex: none; }
3368
3608
 
3369
3609
  /* messages */
3370
3610
  /* .ss-w-mon-list (Matches/Odds tab scrollers) shares .ss-w-list's box/scroll rules by design --
@@ -4104,21 +4344,22 @@ function injectStyles(root) {
4104
4344
  function StatsWidget(props) {
4105
4345
  const injectedClient = "client" in props ? props.client : void 0;
4106
4346
  const cfg = props.config;
4107
- const built = (0, import_react23.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
4108
- const ui = (0, import_react23.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
4109
- const rootRef = (0, import_react23.useRef)(null);
4110
- const introDoneRef = (0, import_react23.useRef)(false);
4111
- const markIntroDone = (0, import_react23.useCallback)(() => {
4347
+ const built = (0, import_react24.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
4348
+ const ui = (0, import_react24.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
4349
+ const rootRef = (0, import_react24.useRef)(null);
4350
+ const introDoneRef = (0, import_react24.useRef)(false);
4351
+ const markIntroDone = (0, import_react24.useCallback)(() => {
4112
4352
  introDoneRef.current = true;
4113
4353
  }, []);
4114
- const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0);
4354
+ const convStorageKey = `ss-w-conv:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`;
4355
+ const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0, convStorageKey);
4115
4356
  const monitor = useLiveMonitor({
4116
4357
  getLiveMonitor: (pins) => built ? built.conversation.getLiveMonitor(pins) : Promise.resolve(null),
4117
4358
  getMarketOdds: (matchId, dev) => built ? built.conversation.getMarketOdds(matchId, dev) : Promise.resolve(null),
4118
4359
  storageKey: `ss-w-pins:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`,
4119
4360
  isOpen: controller.state.isOpen
4120
4361
  });
4121
- const slip = (0, import_react23.useMemo)(
4362
+ const slip = (0, import_react24.useMemo)(
4122
4363
  () => {
4123
4364
  const handler = props.config?.onAddToSlip;
4124
4365
  return {
@@ -4141,11 +4382,11 @@ function StatsWidget(props) {
4141
4382
  },
4142
4383
  [props.config?.onAddToSlip, controller.state.conversationId, built, monitor.markInSlip]
4143
4384
  );
4144
- const onDashboardTap = (0, import_react23.useMemo)(
4385
+ const onDashboardTap = (0, import_react24.useMemo)(
4145
4386
  () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
4146
4387
  [built]
4147
4388
  );
4148
- (0, import_react23.useEffect)(() => {
4389
+ (0, import_react24.useEffect)(() => {
4149
4390
  if (!built || !ui.injectStyles) return;
4150
4391
  const node = rootRef.current?.getRootNode();
4151
4392
  if (node) injectStyles(node);