@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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @sensiblestats/widget-react
2
2
 
3
+ ## 0.21.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Match Preview card: deterministic matchPreviewCard block type (sdk) + renderer (react).
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @sensiblestats/widget-sdk@0.18.0
13
+
14
+ ## 0.20.0
15
+
16
+ ### Minor Changes
17
+
18
+ - Club-dashboard taps (FixtureHero CTA, MatchPreview/TeamDna facts) now send the backend's opaque `actionRef` instead of a prose command, and `useChatStream` gains a client-side fallback timeout so the panel can no longer hang forever waiting for a `turn_complete`/`error` that never arrives.
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies
23
+ - @sensiblestats/widget-sdk@0.17.0
24
+
3
25
  ## 0.19.0
4
26
 
5
27
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -625,21 +625,42 @@ function reducer(state, action) {
625
625
  }
626
626
 
627
627
  // src/useChatStream.ts
628
+ var STREAM_FALLBACK_TIMEOUT_MS = 3e4;
628
629
  function useChatStream(conversation, starterTexts, greetingMessage) {
629
630
  const seed = (0, import_react.useMemo)(() => initialState(starterTexts.map(chipFromText)), []);
630
631
  const [state, dispatch] = (0, import_react.useReducer)(reducer, seed);
631
632
  const idRef = (0, import_react.useRef)(0);
632
633
  const handleRef = (0, import_react.useRef)(null);
634
+ const fallbackTimerRef = (0, import_react.useRef)(null);
633
635
  const stateRef = (0, import_react.useRef)(state);
634
636
  stateRef.current = state;
635
637
  const greetedRef = (0, import_react.useRef)(false);
636
638
  const nextId = () => `m${idRef.current++}`;
639
+ const clearFallbackTimer = (0, import_react.useCallback)(() => {
640
+ if (fallbackTimerRef.current != null) {
641
+ clearTimeout(fallbackTimerRef.current);
642
+ fallbackTimerRef.current = null;
643
+ }
644
+ }, []);
645
+ const armFallbackTimer = (0, import_react.useCallback)((onFire) => {
646
+ clearFallbackTimer();
647
+ fallbackTimerRef.current = setTimeout(() => {
648
+ fallbackTimerRef.current = null;
649
+ handleRef.current?.cancel();
650
+ handleRef.current = null;
651
+ onFire();
652
+ }, STREAM_FALLBACK_TIMEOUT_MS);
653
+ }, [clearFallbackTimer]);
637
654
  const consume = (0, import_react.useCallback)((handle, onError) => {
638
655
  ;
639
656
  (async () => {
640
657
  try {
641
- for await (const event of handle) dispatch({ kind: "EVENT", event });
658
+ for await (const event of handle) {
659
+ dispatch({ kind: "EVENT", event });
660
+ if (event.type === "turn_complete" || event.type === "error") clearFallbackTimer();
661
+ }
642
662
  } catch (err) {
663
+ if (handleRef.current === handle) clearFallbackTimer();
643
664
  if (err instanceof import_widget_sdk3.WidgetSdkError) onError(err);
644
665
  else if (err?.name === "AbortError") {
645
666
  } else throw err;
@@ -647,18 +668,23 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
647
668
  if (handleRef.current === handle) handleRef.current = null;
648
669
  }
649
670
  })();
650
- }, []);
671
+ }, [clearFallbackTimer]);
651
672
  const run = (0, import_react.useCallback)((input, displayText) => {
652
673
  handleRef.current?.cancel();
674
+ clearFallbackTimer();
653
675
  dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId(), displayText });
654
676
  const handle = conversation.send(input);
655
677
  handleRef.current = handle;
678
+ armFallbackTimer(() => {
679
+ dispatch({ kind: "STOP" });
680
+ dispatch({ kind: "STREAM_ERROR", error: new import_widget_sdk3.WidgetSdkError("No response received in time", "timeout") });
681
+ });
656
682
  consume(handle, (err) => dispatch({ kind: "STREAM_ERROR", error: err }));
657
- }, [conversation, consume]);
683
+ }, [conversation, consume, clearFallbackTimer, armFallbackTimer]);
658
684
  const send = (0, import_react.useCallback)((text, opts) => {
659
685
  const trimmed = text.trim();
660
686
  if (!trimmed) return;
661
- run({ message: trimmed, actionId: opts?.actionId, grounding: opts?.grounding }, opts?.displayText);
687
+ run({ message: trimmed, actionId: opts?.actionId, actionRef: opts?.actionRef }, opts?.displayText);
662
688
  }, [run]);
663
689
  const retry = (0, import_react.useCallback)(() => {
664
690
  const last = stateRef.current.lastUserInput;
@@ -670,25 +696,29 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
670
696
  greetedRef.current = true;
671
697
  try {
672
698
  handleRef.current?.cancel();
699
+ clearFallbackTimer();
673
700
  dispatch({ kind: "GREETING_SENT", assistantId: nextId() });
674
701
  const handle = conversation.send({ message });
675
702
  handleRef.current = handle;
703
+ armFallbackTimer(() => dispatch({ kind: "STOP" }));
676
704
  consume(handle, () => dispatch({ kind: "STOP" }));
677
705
  } catch {
678
706
  dispatch({ kind: "STOP" });
679
707
  }
680
- }, [greetingMessage, conversation, consume]);
708
+ }, [greetingMessage, conversation, consume, clearFallbackTimer, armFallbackTimer]);
681
709
  const open = (0, import_react.useCallback)(() => dispatch({ kind: "OPEN" }), []);
682
710
  const close = (0, import_react.useCallback)(() => {
683
711
  handleRef.current?.cancel();
684
712
  handleRef.current = null;
713
+ clearFallbackTimer();
685
714
  dispatch({ kind: "CLOSE" });
686
- }, []);
715
+ }, [clearFallbackTimer]);
687
716
  const stop = (0, import_react.useCallback)(() => {
688
717
  handleRef.current?.cancel();
689
718
  handleRef.current = null;
719
+ clearFallbackTimer();
690
720
  dispatch({ kind: "STOP" });
691
- }, []);
721
+ }, [clearFallbackTimer]);
692
722
  const toggle = (0, import_react.useCallback)(() => {
693
723
  if (stateRef.current.isOpen) close();
694
724
  else open();
@@ -1353,25 +1383,31 @@ function FixtureHeroBlock({
1353
1383
  block.venue ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "ss-w-cdash-venue", children: block.venue }) : null,
1354
1384
  block.competition ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "ss-w-cdash-comp", children: block.competition }) : null
1355
1385
  ] }),
1356
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaActionValue, void 0, block.ctaLabel), children: block.ctaLabel })
1386
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "ss-w-cdash-cta", onClick: () => onAction(block.ctaLabel, { actionRef: block.ctaActionRef }), children: block.ctaLabel })
1357
1387
  ] });
1358
1388
  }
1359
1389
 
1360
- // src/components/club-dashboard/FormGuideBlock.tsx
1390
+ // src/components/club-dashboard/ResultPills.tsx
1361
1391
  var import_jsx_runtime19 = require("react/jsx-runtime");
1392
+ function ResultPills({ results }) {
1393
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, { children: results.map((r, i) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: `ss-w-cdash-pill ss-w-cdash-pill--${r.toLowerCase()}`, children: r }, i)) });
1394
+ }
1395
+
1396
+ // src/components/club-dashboard/FormGuideBlock.tsx
1397
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1362
1398
  function FormGuideBlock({ block }) {
1363
1399
  if (block.rows.length === 0) return null;
1364
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "ss-w-cdash-form", children: block.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ss-w-cdash-form-row", children: [
1365
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { className: "ss-w-cdash-form-name", children: [
1400
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "ss-w-cdash-form", children: block.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "ss-w-cdash-form-row", children: [
1401
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "ss-w-cdash-form-name", children: [
1366
1402
  row.name,
1367
1403
  row.position != null ? ` #${row.position}` : ""
1368
1404
  ] }),
1369
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "ss-w-cdash-form-pills", children: row.last5.map((r, i) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: `ss-w-cdash-pill ss-w-cdash-pill--${r.toLowerCase()}`, children: r }, i)) })
1405
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-cdash-form-pills", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ResultPills, { results: row.last5 }) })
1370
1406
  ] }, row.name)) });
1371
1407
  }
1372
1408
 
1373
1409
  // src/components/club-dashboard/MatchPreviewBlock.tsx
1374
- var import_jsx_runtime20 = require("react/jsx-runtime");
1410
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1375
1411
  function MatchPreviewBlock({
1376
1412
  block,
1377
1413
  ui,
@@ -1379,31 +1415,65 @@ function MatchPreviewBlock({
1379
1415
  }) {
1380
1416
  if (block.facts.length === 0) return null;
1381
1417
  const heading = ui.clubMatchPreviewHeading;
1382
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "ss-w-cdash-lines", children: [
1383
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1418
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "ss-w-cdash-lines", children: [
1419
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1384
1420
  block.facts.map(
1385
- (f, i) => f.entityRef ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1421
+ (f, i) => f.actionRef ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1386
1422
  "button",
1387
1423
  {
1388
1424
  type: "button",
1389
1425
  className: "ss-w-cdash-fact",
1390
- onClick: () => onAction(f.question ?? f.sentence, { ...f.entityRef, subject: f.sentence, personaHint: "club" }),
1426
+ onClick: () => onAction(f.label ?? f.sentence, { actionRef: f.actionRef }),
1391
1427
  children: [
1392
1428
  f.sentence,
1393
- f.label ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "ss-w-cdash-fact-label", children: [
1429
+ f.label ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "ss-w-cdash-fact-label", children: [
1394
1430
  " ",
1395
1431
  f.label
1396
1432
  ] }) : null
1397
1433
  ]
1398
1434
  },
1399
1435
  i
1400
- ) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { children: f.sentence }, i)
1436
+ ) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { children: f.sentence }, i)
1401
1437
  )
1402
1438
  ] });
1403
1439
  }
1404
1440
 
1441
+ // src/components/club-dashboard/MatchPreviewCardBlock.tsx
1442
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1443
+ function position(p) {
1444
+ return p != null ? `#${p}` : "\u2014";
1445
+ }
1446
+ function MatchPreviewCardBlock({ block }) {
1447
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "ss-w-cdash-preview", children: [
1448
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "ss-w-cdash-preview-header", children: [
1449
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-team", children: block.homeName }),
1450
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-vs", children: "v" }),
1451
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-team", children: block.awayName })
1452
+ ] }),
1453
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "ss-w-cdash-preview-row", children: [
1454
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-preview-value", children: position(block.homePosition) }),
1455
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-preview-label", children: block.tableLabel }),
1456
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-preview-value", children: position(block.awayPosition) })
1457
+ ] }),
1458
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "ss-w-cdash-preview-row", children: [
1459
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-preview-pills", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ResultPills, { results: block.homeLast5 }) }),
1460
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-preview-label", children: block.last5Label }),
1461
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-preview-pills", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ResultPills, { results: block.awayLast5 }) })
1462
+ ] }),
1463
+ block.edgeTeamName ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "ss-w-cdash-preview-edge", children: [
1464
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": "true", children: "\u25B8" }),
1465
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("strong", { children: block.edgeTeamName }),
1466
+ block.edgeReason ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { children: [
1467
+ " \u2014 ",
1468
+ block.edgeReason
1469
+ ] }) : null
1470
+ ] }) : null,
1471
+ block.h2HLines.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "ss-w-cdash-preview-h2h", children: block.h2HLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { children: line }, i)) }) : null
1472
+ ] });
1473
+ }
1474
+
1405
1475
  // src/components/club-dashboard/TeamDnaBlock.tsx
1406
- var import_jsx_runtime21 = require("react/jsx-runtime");
1476
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1407
1477
  function TeamDnaBlock({
1408
1478
  block,
1409
1479
  ui,
@@ -1411,32 +1481,32 @@ function TeamDnaBlock({
1411
1481
  }) {
1412
1482
  if (block.facts.length === 0) return null;
1413
1483
  const heading = ui.clubTeamDnaHeading;
1414
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "ss-w-cdash-lines", children: [
1415
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1484
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "ss-w-cdash-lines", children: [
1485
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "ss-w-cdash-lines-heading", children: heading }),
1416
1486
  block.facts.map(
1417
- (f, i) => f.entityRef ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
1487
+ (f, i) => f.actionRef ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1418
1488
  "button",
1419
1489
  {
1420
1490
  type: "button",
1421
1491
  className: "ss-w-cdash-fact",
1422
- onClick: () => onAction(f.question ?? f.sentence, { ...f.entityRef, subject: f.sentence, personaHint: "club" }),
1492
+ onClick: () => onAction(f.label ?? f.sentence, { actionRef: f.actionRef }),
1423
1493
  children: [
1424
1494
  f.sentence,
1425
- f.label ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "ss-w-cdash-fact-label", children: [
1495
+ f.label ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "ss-w-cdash-fact-label", children: [
1426
1496
  " ",
1427
1497
  f.label
1428
1498
  ] }) : null
1429
1499
  ]
1430
1500
  },
1431
1501
  i
1432
- ) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { children: f.sentence }, i)
1502
+ ) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: f.sentence }, i)
1433
1503
  )
1434
1504
  ] });
1435
1505
  }
1436
1506
 
1437
1507
  // src/components/club-dashboard/CountdownBlock.tsx
1438
1508
  var import_react9 = require("react");
1439
- var import_jsx_runtime22 = require("react/jsx-runtime");
1509
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1440
1510
  var DAY_MS = 24 * 3600 * 1e3;
1441
1511
  function pad2(n) {
1442
1512
  return String(n).padStart(2, "0");
@@ -1461,30 +1531,31 @@ function CountdownBlock({ block, ui }) {
1461
1531
  if (remainingMs <= 0) return null;
1462
1532
  if (remainingMs > DAY_MS) return null;
1463
1533
  const { h, m, s } = splitRemaining(remainingMs);
1464
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "ss-w-cdash-cd", "aria-label": ui.clubKickoffAria(block.kickoffUnix * 1e3), children: [
1465
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "ss-w-cdash-cd-clock", "aria-hidden": "true", children: [
1466
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-cd-h", children: h }),
1534
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "ss-w-cdash-cd", "aria-label": ui.clubKickoffAria(block.kickoffUnix * 1e3), children: [
1535
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "ss-w-cdash-cd-clock", "aria-hidden": "true", children: [
1536
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "ss-w-cdash-cd-h", children: h }),
1467
1537
  "h",
1468
1538
  " ",
1469
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-cd-m", children: m }),
1539
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "ss-w-cdash-cd-m", children: m }),
1470
1540
  "m",
1471
1541
  " ",
1472
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-cdash-cd-s", children: s }),
1542
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "ss-w-cdash-cd-s", children: s }),
1473
1543
  "s"
1474
1544
  ] }),
1475
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "ss-w-cdash-cd-label", children: ui.clubCountdownLabel })
1545
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "ss-w-cdash-cd-label", children: ui.clubCountdownLabel })
1476
1546
  ] });
1477
1547
  }
1478
1548
 
1479
1549
  // src/components/club-dashboard/ClubDashboard.tsx
1480
- var import_jsx_runtime23 = require("react/jsx-runtime");
1550
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1481
1551
  var CLUB_BLOCK_REGISTRY = {
1482
- greetingHeader: ({ block }) => block.type === "greetingHeader" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(GreetingHeaderBlock, { block }) : null,
1483
- fixtureHero: ({ block, ui, onAction }) => block.type === "fixtureHero" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(FixtureHeroBlock, { block, ui, onAction }) : null,
1484
- formGuide: ({ block, ui }) => block.type === "formGuide" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(FormGuideBlock, { block, ui }) : null,
1485
- matchPreview: ({ block, ui, onAction }) => block.type === "matchPreview" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(MatchPreviewBlock, { block, ui, onAction }) : null,
1486
- teamDna: ({ block, ui, onAction }) => block.type === "teamDna" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(TeamDnaBlock, { block, ui, onAction }) : null,
1487
- countdown: ({ block, ui }) => block.type === "countdown" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CountdownBlock, { block, ui }) : null
1552
+ greetingHeader: ({ block }) => block.type === "greetingHeader" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(GreetingHeaderBlock, { block }) : null,
1553
+ fixtureHero: ({ block, ui, onAction }) => block.type === "fixtureHero" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(FixtureHeroBlock, { block, ui, onAction }) : null,
1554
+ formGuide: ({ block, ui }) => block.type === "formGuide" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(FormGuideBlock, { block, ui }) : null,
1555
+ matchPreview: ({ block, ui, onAction }) => block.type === "matchPreview" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MatchPreviewBlock, { block, ui, onAction }) : null,
1556
+ matchPreviewCard: ({ block, ui }) => block.type === "matchPreviewCard" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MatchPreviewCardBlock, { block, ui }) : null,
1557
+ teamDna: ({ block, ui, onAction }) => block.type === "teamDna" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TeamDnaBlock, { block, ui, onAction }) : null,
1558
+ countdown: ({ block, ui }) => block.type === "countdown" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(CountdownBlock, { block, ui }) : null
1488
1559
  };
1489
1560
  function ClubDashboard({
1490
1561
  dashboard,
@@ -1492,39 +1563,39 @@ function ClubDashboard({
1492
1563
  onAction
1493
1564
  }) {
1494
1565
  if (!dashboard.blocks.length) return null;
1495
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "ss-w-cdash", children: dashboard.blocks.map((block, i) => {
1566
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "ss-w-cdash", children: dashboard.blocks.map((block, i) => {
1496
1567
  const Comp = Object.prototype.hasOwnProperty.call(CLUB_BLOCK_REGISTRY, block.type) ? CLUB_BLOCK_REGISTRY[block.type] : void 0;
1497
1568
  if (!Comp) {
1498
1569
  console.warn(`[club-dashboard] no renderer for block type "${block.type}"`);
1499
1570
  return null;
1500
1571
  }
1501
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "ss-w-cdash-block", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Comp, { block, ui, onAction }) }, i);
1572
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "ss-w-cdash-block", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Comp, { block, ui, onAction }) }, i);
1502
1573
  }) });
1503
1574
  }
1504
1575
 
1505
1576
  // src/components/ChatMessage.tsx
1506
- var import_jsx_runtime24 = require("react/jsx-runtime");
1577
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1507
1578
  function ChatMessage({ message, disabled, onAct, onTap, onClubAction, ui, slip }) {
1508
1579
  if (message.role === "user") {
1509
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
1580
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
1510
1581
  }
1511
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
1512
- message.text && !message.greetingDashboard && !message.clubDashboard ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Markdown, { text: message.text }) : null,
1513
- message.greetingDashboard ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1514
- message.clubDashboard && ui ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ClubDashboard, { dashboard: message.clubDashboard, ui, onAction: onClubAction }) : null,
1515
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1516
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
1517
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1518
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1519
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1582
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
1583
+ message.text && !message.greetingDashboard && !message.clubDashboard ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Markdown, { text: message.text }) : null,
1584
+ message.greetingDashboard ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1585
+ message.clubDashboard && ui ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ClubDashboard, { dashboard: message.clubDashboard, ui, onAction: onClubAction }) : null,
1586
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1587
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
1588
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1589
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1590
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1520
1591
  ] });
1521
1592
  }
1522
1593
 
1523
1594
  // src/components/IntentChips.tsx
1524
- var import_jsx_runtime25 = require("react/jsx-runtime");
1595
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1525
1596
  function IntentChips({ chips, onPick }) {
1526
1597
  if (chips.length === 0) return null;
1527
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1598
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1528
1599
  }
1529
1600
 
1530
1601
  // src/scroll.ts
@@ -1534,7 +1605,7 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1534
1605
  }
1535
1606
 
1536
1607
  // src/components/MessageList.tsx
1537
- var import_jsx_runtime26 = require("react/jsx-runtime");
1608
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1538
1609
  function MessageList({ controller, ui, slip, onDashboardTap }) {
1539
1610
  const { state, send } = controller;
1540
1611
  const listRef = (0, import_react10.useRef)(null);
@@ -1545,7 +1616,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1545
1616
  }, [state.messages, state.status]);
1546
1617
  const hasUserMessage = state.messages.some((m) => m.role === "user");
1547
1618
  const hasDashboard = state.messages.some((m) => m.role === "assistant" && m.greetingDashboard);
1548
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
1619
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1549
1620
  "div",
1550
1621
  {
1551
1622
  className: "ss-w-list",
@@ -1555,7 +1626,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1555
1626
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1556
1627
  },
1557
1628
  children: [
1558
- state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1629
+ state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1559
1630
  ChatMessage,
1560
1631
  {
1561
1632
  message: m,
@@ -1565,21 +1636,21 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1565
1636
  send(tile.actionValue, { displayText: tile.title });
1566
1637
  if (m.role === "assistant" && m.greetingDashboard) onDashboardTap?.(tile, index, m.greetingDashboard.layout);
1567
1638
  },
1568
- onClubAction: (message, grounding, displayText) => send(message, { grounding, displayText: displayText ?? message }),
1639
+ onClubAction: (label, opts) => send(label, { actionRef: opts.actionRef }),
1569
1640
  ui,
1570
1641
  slip
1571
1642
  },
1572
1643
  m.id
1573
1644
  )),
1574
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
1645
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
1575
1646
  } }) : null,
1576
- state.status ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
1577
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
1578
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1579
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1647
+ state.status ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
1648
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
1649
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1650
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1580
1651
  ] }) : null,
1581
- !hasUserMessage && !hasDashboard ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1582
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { ref: endRef })
1652
+ !hasUserMessage && !hasDashboard ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1653
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { ref: endRef })
1583
1654
  ]
1584
1655
  }
1585
1656
  );
@@ -1587,7 +1658,7 @@ function MessageList({ controller, ui, slip, onDashboardTap }) {
1587
1658
 
1588
1659
  // src/components/ChatInput.tsx
1589
1660
  var import_react11 = require("react");
1590
- var import_jsx_runtime27 = require("react/jsx-runtime");
1661
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1591
1662
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1592
1663
  const [value, setValue] = (0, import_react11.useState)("");
1593
1664
  const submit = () => {
@@ -1602,14 +1673,14 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1602
1673
  submit();
1603
1674
  }
1604
1675
  };
1605
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "ss-w-input", children: [
1606
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1607
- streaming ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SendIcon, {}) })
1676
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "ss-w-input", children: [
1677
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1678
+ streaming ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(SendIcon, {}) })
1608
1679
  ] });
1609
1680
  }
1610
1681
 
1611
1682
  // src/components/ChatPanel.tsx
1612
- var import_jsx_runtime28 = require("react/jsx-runtime");
1683
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1613
1684
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1614
1685
  function useIsModal() {
1615
1686
  const [modal, setModal] = (0, import_react12.useState)(false);
@@ -1633,10 +1704,10 @@ function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1633
1704
  window.addEventListener("keydown", onKey);
1634
1705
  return () => window.removeEventListener("keydown", onKey);
1635
1706
  }, [close]);
1636
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1637
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1638
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(MessageList, { controller, ui, slip, onDashboardTap }),
1639
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1707
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1708
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1709
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(MessageList, { controller, ui, slip, onDashboardTap }),
1710
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1640
1711
  ] });
1641
1712
  }
1642
1713
 
@@ -2028,10 +2099,27 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
2028
2099
  .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); }
2029
2100
  .ss-w-cdash-cd-h, .ss-w-cdash-cd-m, .ss-w-cdash-cd-s { color: var(--ss-w-accent); }
2030
2101
  .ss-w-cdash-cd-label { font-size: 11px; font-weight: 600; color: var(--ss-w-faint); }
2102
+
2103
+ .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; }
2104
+ .ss-w-cdash-preview-header { display: grid; grid-template-columns: 1fr auto 1fr; align-items: baseline; gap: 6px; }
2105
+ .ss-w-cdash-preview-header .ss-w-cdash-team:first-child { justify-self: start; text-align: left; }
2106
+ .ss-w-cdash-preview-header .ss-w-cdash-team:last-child { justify-self: end; text-align: right; }
2107
+ .ss-w-cdash-preview-row { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 6px; }
2108
+ .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; }
2109
+ .ss-w-cdash-preview-value { font-size: 12px; font-weight: 700; color: var(--ss-w-ink); text-align: center; }
2110
+ .ss-w-cdash-preview-pills { display: flex; align-items: center; justify-content: center; gap: 3px; }
2111
+ .ss-w-cdash-preview-row:first-of-type .ss-w-cdash-preview-value { justify-self: start; }
2112
+ .ss-w-cdash-preview-row:first-of-type .ss-w-cdash-preview-value:last-child { justify-self: end; }
2113
+ .ss-w-cdash-preview-row:last-of-type .ss-w-cdash-preview-pills:first-child { justify-self: start; }
2114
+ .ss-w-cdash-preview-row:last-of-type .ss-w-cdash-preview-pills:last-child { justify-self: end; }
2115
+ .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; }
2116
+ .ss-w-cdash-preview-edge strong { color: var(--ss-w-ink); }
2117
+ .ss-w-cdash-preview-h2h { display: flex; flex-direction: column; gap: 2px; }
2118
+ .ss-w-cdash-preview-h2h p { font-size: 11.5px; line-height: 1.4; color: var(--ss-w-faint); margin: 0; }
2031
2119
  `;
2032
2120
 
2033
2121
  // src/StatsWidget.tsx
2034
- var import_jsx_runtime29 = require("react/jsx-runtime");
2122
+ var import_jsx_runtime31 = require("react/jsx-runtime");
2035
2123
  function buildClient(props) {
2036
2124
  try {
2037
2125
  if ("client" in props && props.client) {
@@ -2084,7 +2172,7 @@ function StatsWidget(props) {
2084
2172
  }, [built, ui.injectStyles]);
2085
2173
  if (!built) return null;
2086
2174
  const attrs = themeAttrs(ui);
2087
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("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__ */ (0, import_jsx_runtime29.jsx)(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2175
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("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__ */ (0, import_jsx_runtime31.jsx)(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
2088
2176
  ChatFab,
2089
2177
  {
2090
2178
  label: ui.launcherLabel,