@sensiblestats/widget-react 0.19.0 → 0.21.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.js CHANGED
@@ -588,21 +588,42 @@ function reducer(state, action) {
588
588
  }
589
589
 
590
590
  // src/useChatStream.ts
591
+ var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
591
592
  function useChatStream(conversation, starterTexts, greetingMessage) {
592
593
  const seed = useMemo(() => initialState(starterTexts.map(chipFromText)), []);
593
594
  const [state, dispatch] = useReducer(reducer, seed);
594
595
  const idRef = useRef(0);
595
596
  const handleRef = useRef(null);
597
+ const fallbackTimerRef = useRef(null);
596
598
  const stateRef = useRef(state);
597
599
  stateRef.current = state;
598
600
  const greetedRef = useRef(false);
599
601
  const nextId = () => `m${idRef.current++}`;
602
+ const clearFallbackTimer = useCallback(() => {
603
+ if (fallbackTimerRef.current != null) {
604
+ clearTimeout(fallbackTimerRef.current);
605
+ fallbackTimerRef.current = null;
606
+ }
607
+ }, []);
608
+ const armFallbackTimer = useCallback((onFire) => {
609
+ clearFallbackTimer();
610
+ fallbackTimerRef.current = setTimeout(() => {
611
+ fallbackTimerRef.current = null;
612
+ handleRef.current?.cancel();
613
+ handleRef.current = null;
614
+ onFire();
615
+ }, STREAM_FALLBACK_TIMEOUT_MS);
616
+ }, [clearFallbackTimer]);
600
617
  const consume = useCallback((handle, onError) => {
601
618
  ;
602
619
  (async () => {
603
620
  try {
604
- for await (const event of handle) dispatch({ kind: "EVENT", event });
621
+ for await (const event of handle) {
622
+ dispatch({ kind: "EVENT", event });
623
+ if (event.type === "turn_complete" || event.type === "error") clearFallbackTimer();
624
+ }
605
625
  } catch (err) {
626
+ if (handleRef.current === handle) clearFallbackTimer();
606
627
  if (err instanceof WidgetSdkError2) onError(err);
607
628
  else if (err?.name === "AbortError") {
608
629
  } else throw err;
@@ -610,18 +631,23 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
610
631
  if (handleRef.current === handle) handleRef.current = null;
611
632
  }
612
633
  })();
613
- }, []);
634
+ }, [clearFallbackTimer]);
614
635
  const run = useCallback((input, displayText) => {
615
636
  handleRef.current?.cancel();
637
+ clearFallbackTimer();
616
638
  dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId(), displayText });
617
639
  const handle = conversation.send(input);
618
640
  handleRef.current = handle;
641
+ armFallbackTimer(() => {
642
+ dispatch({ kind: "STOP" });
643
+ dispatch({ kind: "STREAM_ERROR", error: new WidgetSdkError2("No response received in time", "timeout") });
644
+ });
619
645
  consume(handle, (err) => dispatch({ kind: "STREAM_ERROR", error: err }));
620
- }, [conversation, consume]);
646
+ }, [conversation, consume, clearFallbackTimer, armFallbackTimer]);
621
647
  const send = useCallback((text, opts) => {
622
648
  const trimmed = text.trim();
623
649
  if (!trimmed) return;
624
- run({ message: trimmed, actionId: opts?.actionId, grounding: opts?.grounding }, opts?.displayText);
650
+ run({ message: trimmed, actionId: opts?.actionId, actionRef: opts?.actionRef }, opts?.displayText);
625
651
  }, [run]);
626
652
  const retry = useCallback(() => {
627
653
  const last = stateRef.current.lastUserInput;
@@ -633,25 +659,29 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
633
659
  greetedRef.current = true;
634
660
  try {
635
661
  handleRef.current?.cancel();
662
+ clearFallbackTimer();
636
663
  dispatch({ kind: "GREETING_SENT", assistantId: nextId() });
637
664
  const handle = conversation.send({ message });
638
665
  handleRef.current = handle;
666
+ armFallbackTimer(() => dispatch({ kind: "STOP" }));
639
667
  consume(handle, () => dispatch({ kind: "STOP" }));
640
668
  } catch {
641
669
  dispatch({ kind: "STOP" });
642
670
  }
643
- }, [greetingMessage, conversation, consume]);
671
+ }, [greetingMessage, conversation, consume, clearFallbackTimer, armFallbackTimer]);
644
672
  const open = useCallback(() => dispatch({ kind: "OPEN" }), []);
645
673
  const close = useCallback(() => {
646
674
  handleRef.current?.cancel();
647
675
  handleRef.current = null;
676
+ clearFallbackTimer();
648
677
  dispatch({ kind: "CLOSE" });
649
- }, []);
678
+ }, [clearFallbackTimer]);
650
679
  const stop = useCallback(() => {
651
680
  handleRef.current?.cancel();
652
681
  handleRef.current = null;
682
+ clearFallbackTimer();
653
683
  dispatch({ kind: "STOP" });
654
- }, []);
684
+ }, [clearFallbackTimer]);
655
685
  const toggle = useCallback(() => {
656
686
  if (stateRef.current.isOpen) close();
657
687
  else open();
@@ -1316,25 +1346,31 @@ function FixtureHeroBlock({
1316
1346
  block.venue ? /* @__PURE__ */ jsx18("span", { className: "ss-w-cdash-venue", children: block.venue }) : null,
1317
1347
  block.competition ? /* @__PURE__ */ jsx18("span", { className: "ss-w-cdash-comp", children: block.competition }) : null
1318
1348
  ] }),
1319
- /* @__PURE__ */ jsx18("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaActionValue, void 0, block.ctaLabel), children: block.ctaLabel })
1349
+ /* @__PURE__ */ jsx18("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaLabel, { actionRef: block.ctaActionRef }), children: block.ctaLabel })
1320
1350
  ] });
1321
1351
  }
1322
1352
 
1353
+ // src/components/club-dashboard/ResultPills.tsx
1354
+ import { Fragment as Fragment5, jsx as jsx19 } from "react/jsx-runtime";
1355
+ function ResultPills({ results }) {
1356
+ return /* @__PURE__ */ jsx19(Fragment5, { children: results.map((r, i) => /* @__PURE__ */ jsx19("span", { className: `ss-w-cdash-pill ss-w-cdash-pill--${r.toLowerCase()}`, children: r }, i)) });
1357
+ }
1358
+
1323
1359
  // src/components/club-dashboard/FormGuideBlock.tsx
1324
- import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
1360
+ import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
1325
1361
  function FormGuideBlock({ block }) {
1326
1362
  if (block.rows.length === 0) return null;
1327
- return /* @__PURE__ */ jsx19("div", { className: "ss-w-cdash-form", children: block.rows.map((row) => /* @__PURE__ */ jsxs15("div", { className: "ss-w-cdash-form-row", children: [
1363
+ return /* @__PURE__ */ jsx20("div", { className: "ss-w-cdash-form", children: block.rows.map((row) => /* @__PURE__ */ jsxs15("div", { className: "ss-w-cdash-form-row", children: [
1328
1364
  /* @__PURE__ */ jsxs15("span", { className: "ss-w-cdash-form-name", children: [
1329
1365
  row.name,
1330
1366
  row.position != null ? ` #${row.position}` : ""
1331
1367
  ] }),
1332
- /* @__PURE__ */ jsx19("span", { className: "ss-w-cdash-form-pills", children: row.last5.map((r, i) => /* @__PURE__ */ jsx19("span", { className: `ss-w-cdash-pill ss-w-cdash-pill--${r.toLowerCase()}`, children: r }, i)) })
1368
+ /* @__PURE__ */ jsx20("span", { className: "ss-w-cdash-form-pills", children: /* @__PURE__ */ jsx20(ResultPills, { results: row.last5 }) })
1333
1369
  ] }, row.name)) });
1334
1370
  }
1335
1371
 
1336
1372
  // src/components/club-dashboard/MatchPreviewBlock.tsx
1337
- import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
1373
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
1338
1374
  function MatchPreviewBlock({
1339
1375
  block,
1340
1376
  ui,
@@ -1343,14 +1379,14 @@ function MatchPreviewBlock({
1343
1379
  if (block.facts.length === 0) return null;
1344
1380
  const heading = ui.clubMatchPreviewHeading;
1345
1381
  return /* @__PURE__ */ jsxs16("div", { className: "ss-w-cdash-lines", children: [
1346
- /* @__PURE__ */ jsx20("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1382
+ /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1347
1383
  block.facts.map(
1348
- (f, i) => f.entityRef ? /* @__PURE__ */ jsxs16(
1384
+ (f, i) => f.actionRef ? /* @__PURE__ */ jsxs16(
1349
1385
  "button",
1350
1386
  {
1351
1387
  type: "button",
1352
1388
  className: "ss-w-cdash-fact",
1353
- onClick: () => onAction(f.question ?? f.sentence, { ...f.entityRef, subject: f.sentence, personaHint: "club" }),
1389
+ onClick: () => onAction(f.label ?? f.sentence, { actionRef: f.actionRef }),
1354
1390
  children: [
1355
1391
  f.sentence,
1356
1392
  f.label ? /* @__PURE__ */ jsxs16("span", { className: "ss-w-cdash-fact-label", children: [
@@ -1360,13 +1396,47 @@ function MatchPreviewBlock({
1360
1396
  ]
1361
1397
  },
1362
1398
  i
1363
- ) : /* @__PURE__ */ jsx20("p", { children: f.sentence }, i)
1399
+ ) : /* @__PURE__ */ jsx21("p", { children: f.sentence }, i)
1364
1400
  )
1365
1401
  ] });
1366
1402
  }
1367
1403
 
1404
+ // src/components/club-dashboard/MatchPreviewCardBlock.tsx
1405
+ import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
1406
+ function position(p) {
1407
+ return p != null ? `#${p}` : "\u2014";
1408
+ }
1409
+ function MatchPreviewCardBlock({ block }) {
1410
+ return /* @__PURE__ */ jsxs17("div", { className: "ss-w-cdash-preview", children: [
1411
+ /* @__PURE__ */ jsxs17("div", { className: "ss-w-cdash-preview-header", children: [
1412
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-team", children: block.homeName }),
1413
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-vs", children: "v" }),
1414
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-team", children: block.awayName })
1415
+ ] }),
1416
+ /* @__PURE__ */ jsxs17("div", { className: "ss-w-cdash-preview-row", children: [
1417
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-preview-value", children: position(block.homePosition) }),
1418
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-preview-label", children: block.tableLabel }),
1419
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-preview-value", children: position(block.awayPosition) })
1420
+ ] }),
1421
+ /* @__PURE__ */ jsxs17("div", { className: "ss-w-cdash-preview-row", children: [
1422
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-preview-pills", children: /* @__PURE__ */ jsx22(ResultPills, { results: block.homeLast5 }) }),
1423
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-preview-label", children: block.last5Label }),
1424
+ /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-preview-pills", children: /* @__PURE__ */ jsx22(ResultPills, { results: block.awayLast5 }) })
1425
+ ] }),
1426
+ block.edgeTeamName ? /* @__PURE__ */ jsxs17("div", { className: "ss-w-cdash-preview-edge", children: [
1427
+ /* @__PURE__ */ jsx22("span", { "aria-hidden": "true", children: "\u25B8" }),
1428
+ /* @__PURE__ */ jsx22("strong", { children: block.edgeTeamName }),
1429
+ block.edgeReason ? /* @__PURE__ */ jsxs17("span", { children: [
1430
+ " \u2014 ",
1431
+ block.edgeReason
1432
+ ] }) : null
1433
+ ] }) : null,
1434
+ block.h2HLines.length > 0 ? /* @__PURE__ */ jsx22("div", { className: "ss-w-cdash-preview-h2h", children: block.h2HLines.map((line, i) => /* @__PURE__ */ jsx22("p", { children: line }, i)) }) : null
1435
+ ] });
1436
+ }
1437
+
1368
1438
  // src/components/club-dashboard/TeamDnaBlock.tsx
1369
- import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
1439
+ import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
1370
1440
  function TeamDnaBlock({
1371
1441
  block,
1372
1442
  ui,
@@ -1374,32 +1444,32 @@ function TeamDnaBlock({
1374
1444
  }) {
1375
1445
  if (block.facts.length === 0) return null;
1376
1446
  const heading = ui.clubTeamDnaHeading;
1377
- return /* @__PURE__ */ jsxs17("div", { className: "ss-w-cdash-lines", children: [
1378
- /* @__PURE__ */ jsx21("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1447
+ return /* @__PURE__ */ jsxs18("div", { className: "ss-w-cdash-lines", children: [
1448
+ /* @__PURE__ */ jsx23("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1379
1449
  block.facts.map(
1380
- (f, i) => f.entityRef ? /* @__PURE__ */ jsxs17(
1450
+ (f, i) => f.actionRef ? /* @__PURE__ */ jsxs18(
1381
1451
  "button",
1382
1452
  {
1383
1453
  type: "button",
1384
1454
  className: "ss-w-cdash-fact",
1385
- onClick: () => onAction(f.question ?? f.sentence, { ...f.entityRef, subject: f.sentence, personaHint: "club" }),
1455
+ onClick: () => onAction(f.label ?? f.sentence, { actionRef: f.actionRef }),
1386
1456
  children: [
1387
1457
  f.sentence,
1388
- f.label ? /* @__PURE__ */ jsxs17("span", { className: "ss-w-cdash-fact-label", children: [
1458
+ f.label ? /* @__PURE__ */ jsxs18("span", { className: "ss-w-cdash-fact-label", children: [
1389
1459
  " ",
1390
1460
  f.label
1391
1461
  ] }) : null
1392
1462
  ]
1393
1463
  },
1394
1464
  i
1395
- ) : /* @__PURE__ */ jsx21("p", { children: f.sentence }, i)
1465
+ ) : /* @__PURE__ */ jsx23("p", { children: f.sentence }, i)
1396
1466
  )
1397
1467
  ] });
1398
1468
  }
1399
1469
 
1400
1470
  // src/components/club-dashboard/CountdownBlock.tsx
1401
1471
  import { useEffect as useEffect3, useState as useState7 } from "react";
1402
- import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
1472
+ import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
1403
1473
  var DAY_MS = 24 * 3600 * 1e3;
1404
1474
  function pad2(n) {
1405
1475
  return String(n).padStart(2, "0");
@@ -1424,30 +1494,31 @@ function CountdownBlock({ block, ui }) {
1424
1494
  if (remainingMs <= 0) return null;
1425
1495
  if (remainingMs > DAY_MS) return null;
1426
1496
  const { h, m, s } = splitRemaining(remainingMs);
1427
- return /* @__PURE__ */ jsxs18("div", { className: "ss-w-cdash-cd", "aria-label": ui.clubKickoffAria(block.kickoffUnix * 1e3), children: [
1428
- /* @__PURE__ */ jsxs18("div", { className: "ss-w-cdash-cd-clock", "aria-hidden": "true", children: [
1429
- /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-cd-h", children: h }),
1497
+ return /* @__PURE__ */ jsxs19("div", { className: "ss-w-cdash-cd", "aria-label": ui.clubKickoffAria(block.kickoffUnix * 1e3), children: [
1498
+ /* @__PURE__ */ jsxs19("div", { className: "ss-w-cdash-cd-clock", "aria-hidden": "true", children: [
1499
+ /* @__PURE__ */ jsx24("span", { className: "ss-w-cdash-cd-h", children: h }),
1430
1500
  "h",
1431
1501
  " ",
1432
- /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-cd-m", children: m }),
1502
+ /* @__PURE__ */ jsx24("span", { className: "ss-w-cdash-cd-m", children: m }),
1433
1503
  "m",
1434
1504
  " ",
1435
- /* @__PURE__ */ jsx22("span", { className: "ss-w-cdash-cd-s", children: s }),
1505
+ /* @__PURE__ */ jsx24("span", { className: "ss-w-cdash-cd-s", children: s }),
1436
1506
  "s"
1437
1507
  ] }),
1438
- /* @__PURE__ */ jsx22("div", { className: "ss-w-cdash-cd-label", children: ui.clubCountdownLabel })
1508
+ /* @__PURE__ */ jsx24("div", { className: "ss-w-cdash-cd-label", children: ui.clubCountdownLabel })
1439
1509
  ] });
1440
1510
  }
1441
1511
 
1442
1512
  // src/components/club-dashboard/ClubDashboard.tsx
1443
- import { jsx as jsx23 } from "react/jsx-runtime";
1513
+ import { jsx as jsx25 } from "react/jsx-runtime";
1444
1514
  var CLUB_BLOCK_REGISTRY = {
1445
- greetingHeader: ({ block }) => block.type === "greetingHeader" ? /* @__PURE__ */ jsx23(GreetingHeaderBlock, { block }) : null,
1446
- fixtureHero: ({ block, ui, onAction }) => block.type === "fixtureHero" ? /* @__PURE__ */ jsx23(FixtureHeroBlock, { block, ui, onAction }) : null,
1447
- formGuide: ({ block, ui }) => block.type === "formGuide" ? /* @__PURE__ */ jsx23(FormGuideBlock, { block, ui }) : null,
1448
- matchPreview: ({ block, ui, onAction }) => block.type === "matchPreview" ? /* @__PURE__ */ jsx23(MatchPreviewBlock, { block, ui, onAction }) : null,
1449
- teamDna: ({ block, ui, onAction }) => block.type === "teamDna" ? /* @__PURE__ */ jsx23(TeamDnaBlock, { block, ui, onAction }) : null,
1450
- countdown: ({ block, ui }) => block.type === "countdown" ? /* @__PURE__ */ jsx23(CountdownBlock, { block, ui }) : null
1515
+ greetingHeader: ({ block }) => block.type === "greetingHeader" ? /* @__PURE__ */ jsx25(GreetingHeaderBlock, { block }) : null,
1516
+ fixtureHero: ({ block, ui, onAction }) => block.type === "fixtureHero" ? /* @__PURE__ */ jsx25(FixtureHeroBlock, { block, ui, onAction }) : null,
1517
+ formGuide: ({ block, ui }) => block.type === "formGuide" ? /* @__PURE__ */ jsx25(FormGuideBlock, { block, ui }) : null,
1518
+ matchPreview: ({ block, ui, onAction }) => block.type === "matchPreview" ? /* @__PURE__ */ jsx25(MatchPreviewBlock, { block, ui, onAction }) : null,
1519
+ matchPreviewCard: ({ block, ui }) => block.type === "matchPreviewCard" ? /* @__PURE__ */ jsx25(MatchPreviewCardBlock, { block, ui }) : null,
1520
+ teamDna: ({ block, ui, onAction }) => block.type === "teamDna" ? /* @__PURE__ */ jsx25(TeamDnaBlock, { block, ui, onAction }) : null,
1521
+ countdown: ({ block, ui }) => block.type === "countdown" ? /* @__PURE__ */ jsx25(CountdownBlock, { block, ui }) : null
1451
1522
  };
1452
1523
  function ClubDashboard({
1453
1524
  dashboard,
@@ -1455,39 +1526,39 @@ function ClubDashboard({
1455
1526
  onAction
1456
1527
  }) {
1457
1528
  if (!dashboard.blocks.length) return null;
1458
- return /* @__PURE__ */ jsx23("div", { className: "ss-w-cdash", children: dashboard.blocks.map((block, i) => {
1529
+ return /* @__PURE__ */ jsx25("div", { className: "ss-w-cdash", children: dashboard.blocks.map((block, i) => {
1459
1530
  const Comp = Object.prototype.hasOwnProperty.call(CLUB_BLOCK_REGISTRY, block.type) ? CLUB_BLOCK_REGISTRY[block.type] : void 0;
1460
1531
  if (!Comp) {
1461
1532
  console.warn(`[club-dashboard] no renderer for block type "${block.type}"`);
1462
1533
  return null;
1463
1534
  }
1464
- return /* @__PURE__ */ jsx23("div", { className: "ss-w-cdash-block", children: /* @__PURE__ */ jsx23(Comp, { block, ui, onAction }) }, i);
1535
+ return /* @__PURE__ */ jsx25("div", { className: "ss-w-cdash-block", children: /* @__PURE__ */ jsx25(Comp, { block, ui, onAction }) }, i);
1465
1536
  }) });
1466
1537
  }
1467
1538
 
1468
1539
  // src/components/ChatMessage.tsx
1469
- import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
1540
+ import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
1470
1541
  function ChatMessage({ message, disabled, onAct, onTap, onClubAction, ui, slip }) {
1471
1542
  if (message.role === "user") {
1472
- return /* @__PURE__ */ jsx24("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx24("div", { className: "ss-w-bubble", children: message.text }) });
1543
+ return /* @__PURE__ */ jsx26("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ jsx26("div", { className: "ss-w-bubble", children: message.text }) });
1473
1544
  }
1474
- return /* @__PURE__ */ jsxs19("div", { className: "ss-w-msg ss-w-bot", children: [
1475
- message.text && !message.greetingDashboard && !message.clubDashboard ? /* @__PURE__ */ jsx24(Markdown, { text: message.text }) : null,
1476
- message.greetingDashboard ? /* @__PURE__ */ jsx24(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1477
- message.clubDashboard && ui ? /* @__PURE__ */ jsx24(ClubDashboard, { dashboard: message.clubDashboard, ui, onAction: onClubAction }) : null,
1478
- /* @__PURE__ */ jsx24(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1479
- /* @__PURE__ */ jsx24(BettingInsightList, { insights: message.insights, ui, slip }),
1480
- /* @__PURE__ */ jsx24(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1481
- /* @__PURE__ */ jsx24(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1482
- /* @__PURE__ */ jsx24(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1545
+ return /* @__PURE__ */ jsxs20("div", { className: "ss-w-msg ss-w-bot", children: [
1546
+ message.text && !message.greetingDashboard && !message.clubDashboard ? /* @__PURE__ */ jsx26(Markdown, { text: message.text }) : null,
1547
+ message.greetingDashboard ? /* @__PURE__ */ jsx26(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1548
+ message.clubDashboard && ui ? /* @__PURE__ */ jsx26(ClubDashboard, { dashboard: message.clubDashboard, ui, onAction: onClubAction }) : null,
1549
+ /* @__PURE__ */ jsx26(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1550
+ /* @__PURE__ */ jsx26(BettingInsightList, { insights: message.insights, ui, slip }),
1551
+ /* @__PURE__ */ jsx26(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1552
+ /* @__PURE__ */ jsx26(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1553
+ /* @__PURE__ */ jsx26(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1483
1554
  ] });
1484
1555
  }
1485
1556
 
1486
1557
  // src/components/IntentChips.tsx
1487
- import { jsx as jsx25 } from "react/jsx-runtime";
1558
+ import { jsx as jsx27 } from "react/jsx-runtime";
1488
1559
  function IntentChips({ chips, onPick }) {
1489
1560
  if (chips.length === 0) return null;
1490
- return /* @__PURE__ */ jsx25("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx25("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1561
+ return /* @__PURE__ */ jsx27("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ jsx27("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1491
1562
  }
1492
1563
 
1493
1564
  // src/scroll.ts
@@ -1497,7 +1568,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1497
1568
  }
1498
1569
 
1499
1570
  // src/components/MessageList.tsx
1500
- import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
1571
+ import { jsx as jsx28, jsxs as jsxs21 } from "react/jsx-runtime";
1501
1572
  function MessageList({ controller, ui, slip, onDashboardTap }) {
1502
1573
  const { state, send } = controller;
1503
1574
  const listRef = useRef2(null);
@@ -1508,7 +1579,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1508
1579
  }, [state.messages, state.status]);
1509
1580
  const hasUserMessage = state.messages.some((m) => m.role === "user");
1510
1581
  const hasDashboard = state.messages.some((m) => m.role === "assistant" && m.greetingDashboard);
1511
- return /* @__PURE__ */ jsxs20(
1582
+ return /* @__PURE__ */ jsxs21(
1512
1583
  "div",
1513
1584
  {
1514
1585
  className: "ss-w-list",
@@ -1518,7 +1589,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1518
1589
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1519
1590
  },
1520
1591
  children: [
1521
- state.messages.map((m) => /* @__PURE__ */ jsx26(
1592
+ state.messages.map((m) => /* @__PURE__ */ jsx28(
1522
1593
  ChatMessage,
1523
1594
  {
1524
1595
  message: m,
@@ -1528,21 +1599,21 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1528
1599
  send(tile.actionValue, { displayText: tile.title });
1529
1600
  if (m.role === "assistant" && m.greetingDashboard) onDashboardTap?.(tile, index, m.greetingDashboard.layout);
1530
1601
  },
1531
- onClubAction: (message, grounding, displayText) => send(message, { grounding, displayText: displayText ?? message }),
1602
+ onClubAction: (label, opts) => send(label, { actionRef: opts.actionRef }),
1532
1603
  ui,
1533
1604
  slip
1534
1605
  },
1535
1606
  m.id
1536
1607
  )),
1537
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx26(ActionButtons, { actions: [], loading: true, onAct: () => {
1608
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ jsx28(ActionButtons, { actions: [], loading: true, onAct: () => {
1538
1609
  } }) : null,
1539
- state.status ? /* @__PURE__ */ jsx26("div", { className: "ss-w-status", children: state.status }) : null,
1540
- state.error ? /* @__PURE__ */ jsxs20("div", { className: "ss-w-error", role: "alert", children: [
1541
- /* @__PURE__ */ jsx26("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1542
- /* @__PURE__ */ jsx26("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1610
+ state.status ? /* @__PURE__ */ jsx28("div", { className: "ss-w-status", children: state.status }) : null,
1611
+ state.error ? /* @__PURE__ */ jsxs21("div", { className: "ss-w-error", role: "alert", children: [
1612
+ /* @__PURE__ */ jsx28("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1613
+ /* @__PURE__ */ jsx28("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1543
1614
  ] }) : null,
1544
- !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx26(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1545
- /* @__PURE__ */ jsx26("div", { ref: endRef })
1615
+ !hasUserMessage && !hasDashboard ? /* @__PURE__ */ jsx28(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1616
+ /* @__PURE__ */ jsx28("div", { ref: endRef })
1546
1617
  ]
1547
1618
  }
1548
1619
  );
@@ -1550,7 +1621,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1550
1621
 
1551
1622
  // src/components/ChatInput.tsx
1552
1623
  import { useState as useState8 } from "react";
1553
- import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
1624
+ import { jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
1554
1625
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1555
1626
  const [value, setValue] = useState8("");
1556
1627
  const submit = () => {
@@ -1565,14 +1636,14 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1565
1636
  submit();
1566
1637
  }
1567
1638
  };
1568
- return /* @__PURE__ */ jsxs21("div", { className: "ss-w-input", children: [
1569
- /* @__PURE__ */ jsx27("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1570
- streaming ? /* @__PURE__ */ jsx27("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx27("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx27("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx27(SendIcon, {}) })
1639
+ return /* @__PURE__ */ jsxs22("div", { className: "ss-w-input", children: [
1640
+ /* @__PURE__ */ jsx29("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1641
+ streaming ? /* @__PURE__ */ jsx29("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ jsx29("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ jsx29("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ jsx29(SendIcon, {}) })
1571
1642
  ] });
1572
1643
  }
1573
1644
 
1574
1645
  // src/components/ChatPanel.tsx
1575
- import { jsx as jsx28, jsxs as jsxs22 } from "react/jsx-runtime";
1646
+ import { jsx as jsx30, jsxs as jsxs23 } from "react/jsx-runtime";
1576
1647
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1577
1648
  function useIsModal() {
1578
1649
  const [modal, setModal] = useState9(false);
@@ -1596,10 +1667,10 @@ function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1596
1667
  window.addEventListener("keydown", onKey);
1597
1668
  return () => window.removeEventListener("keydown", onKey);
1598
1669
  }, [close]);
1599
- return /* @__PURE__ */ jsxs22("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1600
- /* @__PURE__ */ jsx28(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1601
- /* @__PURE__ */ jsx28(MessageList, { controller, ui, slip, onDashboardTap }),
1602
- /* @__PURE__ */ jsx28(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1670
+ return /* @__PURE__ */ jsxs23("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1671
+ /* @__PURE__ */ jsx30(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1672
+ /* @__PURE__ */ jsx30(MessageList, { controller, ui, slip, onDashboardTap }),
1673
+ /* @__PURE__ */ jsx30(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1603
1674
  ] });
1604
1675
  }
1605
1676
 
@@ -1991,10 +2062,27 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1991
2062
  .ss-w-cdash-cd-clock { display: flex; align-items: baseline; justify-content: center; gap: 2px; font-variant-numeric: tabular-nums; font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; font-weight: 700; font-size: 18px; color: var(--ss-w-ink); }
1992
2063
  .ss-w-cdash-cd-h, .ss-w-cdash-cd-m, .ss-w-cdash-cd-s { color: var(--ss-w-accent); }
1993
2064
  .ss-w-cdash-cd-label { font-size: 11px; font-weight: 600; color: var(--ss-w-faint); }
2065
+
2066
+ .ss-w-cdash-preview { display: flex; flex-direction: column; gap: 8px; background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 12px; padding: 10px 12px; }
2067
+ .ss-w-cdash-preview-header { display: grid; grid-template-columns: 1fr auto 1fr; align-items: baseline; gap: 6px; }
2068
+ .ss-w-cdash-preview-header .ss-w-cdash-team:first-child { justify-self: start; text-align: left; }
2069
+ .ss-w-cdash-preview-header .ss-w-cdash-team:last-child { justify-self: end; text-align: right; }
2070
+ .ss-w-cdash-preview-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 6px; }
2071
+ .ss-w-cdash-preview-label { font-size: 10.5px; font-weight: 600; color: var(--ss-w-faint); text-align: center; text-transform: uppercase; letter-spacing: 0.03em; }
2072
+ .ss-w-cdash-preview-value { font-size: 12px; font-weight: 700; color: var(--ss-w-ink); text-align: center; }
2073
+ .ss-w-cdash-preview-pills { display: flex; align-items: center; justify-content: center; gap: 3px; }
2074
+ .ss-w-cdash-preview-row:first-of-type .ss-w-cdash-preview-value { justify-self: start; }
2075
+ .ss-w-cdash-preview-row:first-of-type .ss-w-cdash-preview-value:last-child { justify-self: end; }
2076
+ .ss-w-cdash-preview-row:last-of-type .ss-w-cdash-preview-pills:first-child { justify-self: start; }
2077
+ .ss-w-cdash-preview-row:last-of-type .ss-w-cdash-preview-pills:last-child { justify-self: end; }
2078
+ .ss-w-cdash-preview-edge { display: flex; align-items: baseline; gap: 4px; font-size: 12px; line-height: 1.4; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 10%, transparent); border-radius: 8px; padding: 6px 8px; }
2079
+ .ss-w-cdash-preview-edge strong { color: var(--ss-w-ink); }
2080
+ .ss-w-cdash-preview-h2h { display: flex; flex-direction: column; gap: 2px; }
2081
+ .ss-w-cdash-preview-h2h p { font-size: 11.5px; line-height: 1.4; color: var(--ss-w-faint); margin: 0; }
1994
2082
  `;
1995
2083
 
1996
2084
  // src/StatsWidget.tsx
1997
- import { jsx as jsx29 } from "react/jsx-runtime";
2085
+ import { jsx as jsx31 } from "react/jsx-runtime";
1998
2086
  function buildClient(props) {
1999
2087
  try {
2000
2088
  if ("client" in props && props.client) {
@@ -2047,7 +2135,7 @@ function StatsWidget(props) {
2047
2135
  }, [built, ui.injectStyles]);
2048
2136
  if (!built) return null;
2049
2137
  const attrs = themeAttrs(ui);
2050
- return /* @__PURE__ */ jsx29("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ jsx29(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx29(
2138
+ return /* @__PURE__ */ jsx31("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ jsx31(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ jsx31(
2051
2139
  ChatFab,
2052
2140
  {
2053
2141
  label: ui.launcherLabel,