@sensiblestats/widget-react 0.13.1 → 0.14.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,16 @@
1
1
  # @sensiblestats/widget-react
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Render the greeting dashboard carried on `turn_complete`: tile grid, CSS, and tap handling that turns a tile into a follow-up chat message. Older servers that never send `dashboard` render nothing new. `widget-embed` also now generates and persists a first-party anonymous visitor id (localStorage, falls back to in-memory) so greeting dashboards can be tiered for anonymous visitors.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @sensiblestats/widget-sdk@0.12.0
13
+
3
14
  ## 0.13.1
4
15
 
5
16
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/StatsWidget.tsx
39
39
  var import_react10 = require("react");
40
- var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
40
+ var import_widget_sdk5 = require("@sensiblestats/widget-sdk");
41
41
 
42
42
  // src/i18n.ts
43
43
  var STRINGS = {
@@ -205,6 +205,27 @@ function toUserContext(cfg) {
205
205
  return cfg.userContext && Object.keys(cfg.userContext).length > 0 ? cfg.userContext : void 0;
206
206
  }
207
207
 
208
+ // src/dashboardTap.ts
209
+ var import_widget_sdk = require("@sensiblestats/widget-sdk");
210
+ function isSignalEntityType(value) {
211
+ return Object.prototype.hasOwnProperty.call(import_widget_sdk.ENTITY_TYPE_CODES, value);
212
+ }
213
+ function createDashboardTapSignal(sendSignal) {
214
+ return (tile, index, layout) => {
215
+ if (!isSignalEntityType(tile.signal.entityType)) return;
216
+ try {
217
+ void sendSignal({
218
+ signalType: "MarketView",
219
+ entityType: tile.signal.entityType,
220
+ entityId: tile.signal.entityId,
221
+ dashboardLayout: layout,
222
+ tileIndex: index
223
+ }).catch(() => void 0);
224
+ } catch {
225
+ }
226
+ };
227
+ }
228
+
208
229
  // src/theme.ts
209
230
  function themeAttrs(ui) {
210
231
  const style = {
@@ -218,10 +239,10 @@ function themeAttrs(ui) {
218
239
 
219
240
  // src/useChatStream.ts
220
241
  var import_react = require("react");
221
- var import_widget_sdk2 = require("@sensiblestats/widget-sdk");
242
+ var import_widget_sdk3 = require("@sensiblestats/widget-sdk");
222
243
 
223
244
  // src/reducer.ts
224
- var import_widget_sdk = require("@sensiblestats/widget-sdk");
245
+ var import_widget_sdk2 = require("@sensiblestats/widget-sdk");
225
246
 
226
247
  // src/chart.ts
227
248
  var CHART_VIEW_W = 320;
@@ -507,7 +528,11 @@ function applyEvent(state, event) {
507
528
  actionsLoading: false,
508
529
  popupText,
509
530
  conversationId: event.data.conversationId ?? state.conversationId,
510
- messages: mapActive(state, (t) => ({ ...t, done: true }))
531
+ messages: mapActive(state, (t) => ({
532
+ ...t,
533
+ done: true,
534
+ greetingDashboard: event.data.dashboard ?? void 0
535
+ }))
511
536
  };
512
537
  }
513
538
  case "error": {
@@ -559,7 +584,7 @@ function useChatStream(conversation, starterTexts, greetingMessage) {
559
584
  try {
560
585
  for await (const event of handle) dispatch({ kind: "EVENT", event });
561
586
  } catch (err) {
562
- if (err instanceof import_widget_sdk2.WidgetSdkError) onError(err);
587
+ if (err instanceof import_widget_sdk3.WidgetSdkError) onError(err);
563
588
  else if (err?.name === "AbortError") {
564
589
  } else throw err;
565
590
  } finally {
@@ -630,6 +655,89 @@ function SendIcon() {
630
655
  function CloseIcon() {
631
656
  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: "M18 6L6 18M6 6l12 12", stroke: "currentColor", strokeWidth: "2" }) });
632
657
  }
658
+ function FootballIcon() {
659
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
660
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "9", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
661
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M12 7l3.5 2.5-1.3 4.1H9.8L8.5 9.5z" })
662
+ ] });
663
+ }
664
+ function GoalNetIcon() {
665
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
666
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "5", width: "18", height: "12", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
667
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1", d: "M3 9h18M3 13h18M8 5v12M16 5v12" })
668
+ ] });
669
+ }
670
+ function BttsIcon() {
671
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
672
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "12", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "12", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" })
674
+ ] });
675
+ }
676
+ function CardIcon() {
677
+ 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)("rect", { x: "6", y: "3", width: "12", height: "18", rx: "1.5", fill: "currentColor" }) });
678
+ }
679
+ function CornerFlagIcon() {
680
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
681
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1.6", d: "M6 21V4" }),
682
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M6 4l11 3.5L6 11z" })
683
+ ] });
684
+ }
685
+ function PlayerIcon() {
686
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "7", r: "3.2", fill: "currentColor" }),
688
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M5 20c0-4 3-6.5 7-6.5s7 2.5 7 6.5z" })
689
+ ] });
690
+ }
691
+ function TargetIcon() {
692
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
693
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "8.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
694
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }),
695
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "1.4", fill: "currentColor" })
696
+ ] });
697
+ }
698
+ function TrophyIcon() {
699
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: [
700
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M7 3h10v4a5 5 0 0 1-5 5 5 5 0 0 1-5-5zM9 15h6v3H9zM7 19h10v2H7z" }),
701
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1.4", d: "M7 5H4v2a3 3 0 0 0 3 3M17 5h3v2a3 3 0 0 1-3 3" })
702
+ ] });
703
+ }
704
+ function ChartLineIcon() {
705
+ 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: "1.8", strokeLinecap: "round", strokeLinejoin: "round", d: "M4 17l5-6 4 3 7-9" }) });
706
+ }
707
+ function ChartBarIcon() {
708
+ 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: "currentColor", d: "M5 12h3v8H5zM10.5 7h3v13h-3zM16 15h3v5h-3z" }) });
709
+ }
710
+ function ChatIcon() {
711
+ 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: "1.6", d: "M4 5h16v11H9l-4 4z" }) });
712
+ }
713
+ function FireIcon() {
714
+ 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: "currentColor", d: "M12 2c1 3-2 4-2 7a3 3 0 0 0 6 0c1 1 2 2.5 2 4.5A6 6 0 0 1 6 14c0-4 3-5 3-9 1 1 1.5 2 1.5 3C11.5 6 11 4 12 2z" }) });
715
+ }
716
+ function SparkleIcon() {
717
+ 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: "currentColor", d: "M12 2l1.6 6.4L20 10l-6.4 1.6L12 18l-1.6-6.4L4 10l6.4-1.6z" }) });
718
+ }
719
+ function ChevronIcon() {
720
+ 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" }) });
721
+ }
722
+ var DASHBOARD_ICONS = {
723
+ football: FootballIcon,
724
+ "goal-net": GoalNetIcon,
725
+ btts: BttsIcon,
726
+ card: CardIcon,
727
+ "corner-flag": CornerFlagIcon,
728
+ player: PlayerIcon,
729
+ target: TargetIcon,
730
+ trophy: TrophyIcon,
731
+ "chart-line": ChartLineIcon,
732
+ "chart-bar": ChartBarIcon,
733
+ chat: ChatIcon,
734
+ fire: FireIcon,
735
+ sparkle: SparkleIcon
736
+ };
737
+ function DashboardIcon({ name }) {
738
+ const Glyph = DASHBOARD_ICONS[name] ?? TargetIcon;
739
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Glyph, {});
740
+ }
633
741
 
634
742
  // src/components/GreetingBubble.tsx
635
743
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -788,7 +896,7 @@ var import_react5 = require("react");
788
896
 
789
897
  // src/components/AddToSlipButton.tsx
790
898
  var import_react4 = require("react");
791
- var import_widget_sdk3 = require("@sensiblestats/widget-sdk");
899
+ var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
792
900
 
793
901
  // src/slip.ts
794
902
  var ADD_TO_SLIP_TIMEOUT_MS = 1e4;
@@ -851,7 +959,7 @@ function SlipButton({ selection, shownOdds, marketName, slip, ui }) {
851
959
  setPending(true);
852
960
  try {
853
961
  const raw = await withTimeout(Promise.resolve(handler(selection)));
854
- if (raw != null && !(0, import_widget_sdk3.isAddToSlipResult)(raw)) throw new Error("malformed add-to-slip result");
962
+ if (raw != null && !(0, import_widget_sdk4.isAddToSlipResult)(raw)) throw new Error("malformed add-to-slip result");
855
963
  const result = raw ?? void 0;
856
964
  setToast(resultToToast(result, shownOdds || selection.oddsDecimal.toFixed(2), ui));
857
965
  setSettled(isTerminal(result));
@@ -1101,27 +1209,75 @@ function ActionButtons({ actions, loading, disabled, onAct }) {
1101
1209
  ] });
1102
1210
  }
1103
1211
 
1104
- // src/components/ChatMessage.tsx
1212
+ // src/components/dashboard/DashboardTile.tsx
1105
1213
  var import_jsx_runtime14 = require("react/jsx-runtime");
1106
- function ChatMessage({ message, disabled, onAct, ui, slip }) {
1214
+ var MAX_CRESTS = 3;
1215
+ function DashboardTile({ tile, onTap }) {
1216
+ const spanClass = tile.span === "full" ? "ss-w-dash-tile--full" : "ss-w-dash-tile--half";
1217
+ const crests = (tile.imageUrls ?? []).slice(0, MAX_CRESTS);
1218
+ const hasMeta = Boolean(tile.badgeText) || crests.length > 0;
1219
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", className: `ss-w-dash-tile ${spanClass}`, onClick: onTap, children: [
1220
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-icon", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DashboardIcon, { name: tile.icon }) }),
1221
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "ss-w-dash-tile-body", children: [
1222
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-title", children: tile.title }),
1223
+ tile.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-subtitle", children: tile.subtitle }) : null,
1224
+ hasMeta ? (
1225
+ // Badge + crests sit on their own row, below the title, instead of sharing the
1226
+ // icon/chevron row -- at the half-span tile's ~180px width a nowrap badge alone
1227
+ // (~70px) left the title column at ~22px and forced character-by-character wrapping.
1228
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "ss-w-dash-tile-meta", children: [
1229
+ tile.badgeText ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-badge", children: tile.badgeText }) : null,
1230
+ crests.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-crests", children: crests.map((url, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-crest", style: { backgroundImage: `url(${url})` }, "aria-hidden": "true" }, `${url}-${i}`)) }) : null
1231
+ ] })
1232
+ ) : null
1233
+ ] }),
1234
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "ss-w-dash-tile-chevron", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ChevronIcon, {}) })
1235
+ ] });
1236
+ }
1237
+
1238
+ // src/components/dashboard/GreetingDashboard.tsx
1239
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1240
+ var LAYOUT_CLASSES = {
1241
+ market: "ss-w-dash--market",
1242
+ goal: "ss-w-dash--goal",
1243
+ discovery: "ss-w-dash--discovery"
1244
+ };
1245
+ function layoutClass(layout) {
1246
+ return Object.prototype.hasOwnProperty.call(LAYOUT_CLASSES, layout) ? LAYOUT_CLASSES[layout] : LAYOUT_CLASSES.goal;
1247
+ }
1248
+ function GreetingDashboard({ dashboard, onTap }) {
1249
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `ss-w-dash ${layoutClass(dashboard.layout)}`, children: [
1250
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ss-w-dash-head", children: [
1251
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ss-w-dash-title", children: dashboard.title }),
1252
+ dashboard.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ss-w-dash-subtitle", children: dashboard.subtitle }) : null
1253
+ ] }),
1254
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "ss-w-dash-grid", children: dashboard.tiles.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DashboardTile, { tile: t, onTap: () => onTap(t, i) }, `${t.title}-${i}`)) }),
1255
+ dashboard.footerTip ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ss-w-dash-foot", children: dashboard.footerTip }) : null
1256
+ ] });
1257
+ }
1258
+
1259
+ // src/components/ChatMessage.tsx
1260
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1261
+ function ChatMessage({ message, disabled, onAct, onTap, ui, slip }) {
1107
1262
  if (message.role === "user") {
1108
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
1263
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-msg ss-w-user", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-bubble", children: message.text }) });
1109
1264
  }
1110
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
1111
- message.text ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Markdown, { text: message.text }) : null,
1112
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1113
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
1114
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1115
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1116
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1265
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-msg ss-w-bot", children: [
1266
+ message.text ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Markdown, { text: message.text }) : null,
1267
+ message.greetingDashboard ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GreetingDashboard, { dashboard: message.greetingDashboard, onTap }) : null,
1268
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EntityCardList, { cards: message.cards, disabled, showLessLabel: ui?.showLess, showMoreLabel: ui?.showMore }),
1269
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(BettingInsightList, { insights: message.insights, ui, slip }),
1270
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MarketOddsCardList, { cards: message.marketOdds, ui, slip }),
1271
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OddsMovementCardList, { cards: message.oddsMovement, ui }),
1272
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ActionButtons, { actions: message.actions, loading: false, disabled, onAct })
1117
1273
  ] });
1118
1274
  }
1119
1275
 
1120
1276
  // src/components/IntentChips.tsx
1121
- var import_jsx_runtime15 = require("react/jsx-runtime");
1277
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1122
1278
  function IntentChips({ chips, onPick }) {
1123
1279
  if (chips.length === 0) return null;
1124
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1280
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "ss-w-chips", children: chips.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "ss-w-chip", onClick: () => onPick(c.text), children: c.text }, `${c.text}-${i}`)) });
1125
1281
  }
1126
1282
 
1127
1283
  // src/scroll.ts
@@ -1131,8 +1287,8 @@ function isPinnedToBottom(el, threshold = PIN_THRESHOLD) {
1131
1287
  }
1132
1288
 
1133
1289
  // src/components/MessageList.tsx
1134
- var import_jsx_runtime16 = require("react/jsx-runtime");
1135
- function MessageList({ controller, ui, slip }) {
1290
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1291
+ function MessageList({ controller, ui, slip, onDashboardTap }) {
1136
1292
  const { state, send } = controller;
1137
1293
  const listRef = (0, import_react7.useRef)(null);
1138
1294
  const endRef = (0, import_react7.useRef)(null);
@@ -1140,8 +1296,9 @@ function MessageList({ controller, ui, slip }) {
1140
1296
  (0, import_react7.useEffect)(() => {
1141
1297
  if (pinnedRef.current) endRef.current?.scrollIntoView({ block: "end" });
1142
1298
  }, [state.messages, state.status]);
1143
- const empty = state.messages.length === 0;
1144
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
1299
+ const hasUserMessage = state.messages.some((m) => m.role === "user");
1300
+ const hasDashboard = state.messages.some((m) => m.role === "assistant" && m.greetingDashboard);
1301
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1145
1302
  "div",
1146
1303
  {
1147
1304
  className: "ss-w-list",
@@ -1151,26 +1308,30 @@ function MessageList({ controller, ui, slip }) {
1151
1308
  if (listRef.current) pinnedRef.current = isPinnedToBottom(listRef.current);
1152
1309
  },
1153
1310
  children: [
1154
- state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1311
+ state.messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1155
1312
  ChatMessage,
1156
1313
  {
1157
1314
  message: m,
1158
1315
  disabled: state.isStreaming,
1159
1316
  onAct: (a) => send(a.message, { actionId: a.actionId, displayText: a.label }),
1317
+ onTap: (tile, index) => {
1318
+ send(tile.actionValue, { displayText: tile.title });
1319
+ if (m.role === "assistant" && m.greetingDashboard) onDashboardTap?.(tile, index, m.greetingDashboard.layout);
1320
+ },
1160
1321
  ui,
1161
1322
  slip
1162
1323
  },
1163
1324
  m.id
1164
1325
  )),
1165
- state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
1326
+ state.isStreaming && state.actionsLoading ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ActionButtons, { actions: [], loading: true, onAct: () => {
1166
1327
  } }) : null,
1167
- state.status ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
1168
- state.error ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
1169
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1170
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1328
+ state.status ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "ss-w-status", children: state.status }) : null,
1329
+ state.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ss-w-error", role: "alert", children: [
1330
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { children: "message" in state.error ? state.error.message : ui.somethingWentWrong }),
1331
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: "ss-w-retry", onClick: () => controller.retry(), children: ui.retry })
1171
1332
  ] }) : null,
1172
- empty ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1173
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: endRef })
1333
+ !hasUserMessage && !hasDashboard ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IntentChips, { chips: state.starters, onPick: (t) => send(t) }) : null,
1334
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: endRef })
1174
1335
  ]
1175
1336
  }
1176
1337
  );
@@ -1178,7 +1339,7 @@ function MessageList({ controller, ui, slip }) {
1178
1339
 
1179
1340
  // src/components/ChatInput.tsx
1180
1341
  var import_react8 = require("react");
1181
- var import_jsx_runtime17 = require("react/jsx-runtime");
1342
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1182
1343
  function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message" }) {
1183
1344
  const [value, setValue] = (0, import_react8.useState)("");
1184
1345
  const submit = () => {
@@ -1193,14 +1354,14 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
1193
1354
  submit();
1194
1355
  }
1195
1356
  };
1196
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ss-w-input", children: [
1197
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1198
- streaming ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SendIcon, {}) })
1357
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ss-w-input", children: [
1358
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("textarea", { className: "ss-w-textarea", rows: 1, placeholder, value, disabled: streaming, onChange: (e) => setValue(e.target.value), onKeyDown }),
1359
+ streaming ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(SendIcon, {}) })
1199
1360
  ] });
1200
1361
  }
1201
1362
 
1202
1363
  // src/components/ChatPanel.tsx
1203
- var import_jsx_runtime18 = require("react/jsx-runtime");
1364
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1204
1365
  var FULLSCREEN_QUERY = "(max-width: 639px)";
1205
1366
  function useIsModal() {
1206
1367
  const [modal, setModal] = (0, import_react9.useState)(false);
@@ -1214,7 +1375,7 @@ function useIsModal() {
1214
1375
  }, []);
1215
1376
  return modal;
1216
1377
  }
1217
- function ChatPanel({ controller, ui, slip }) {
1378
+ function ChatPanel({ controller, ui, slip, onDashboardTap }) {
1218
1379
  const { state, close, stop, send } = controller;
1219
1380
  const isModal = useIsModal();
1220
1381
  (0, import_react9.useEffect)(() => {
@@ -1224,10 +1385,10 @@ function ChatPanel({ controller, ui, slip }) {
1224
1385
  window.addEventListener("keydown", onKey);
1225
1386
  return () => window.removeEventListener("keydown", onKey);
1226
1387
  }, [close]);
1227
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1228
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1229
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MessageList, { controller, ui, slip }),
1230
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1388
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
1389
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ChatHeader, { onClose: close, closeLabel: ui.closeChat }),
1390
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(MessageList, { controller, ui, slip, onDashboardTap }),
1391
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ChatInput, { placeholder: ui.placeholder, streaming: state.isStreaming, onSend: (t) => send(t), onStop: stop, stopLabel: ui.stopResponse, sendLabel: ui.sendMessage })
1231
1392
  ] });
1232
1393
  }
1233
1394
 
@@ -1484,17 +1645,72 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
1484
1645
  @media (prefers-reduced-motion: reduce) {
1485
1646
  .ss-w-abtn-shimmer { animation: none; }
1486
1647
  }
1648
+
1649
+ /* greeting dashboard */
1650
+ .ss-w-dash { display: flex; flex-direction: column; gap: 12px; padding: 4px 0; }
1651
+ .ss-w-dash-head { display: flex; flex-direction: column; }
1652
+ .ss-w-dash-title { font-size: 18px; font-weight: 700; line-height: 1.25; color: var(--ss-w-ink); }
1653
+ .ss-w-dash-subtitle { font-size: 13px; color: var(--ss-w-faint); margin-top: 4px; }
1654
+ .ss-w-dash-foot { font-size: 11px; color: var(--ss-w-faint); margin-top: 2px; }
1655
+
1656
+ .ss-w-dash-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
1657
+ .ss-w-dash-tile--half { grid-column: span 1; }
1658
+ .ss-w-dash-tile--full { grid-column: 1 / -1; }
1659
+
1660
+ .ss-w-dash-tile {
1661
+ display: flex; align-items: center; gap: 10px;
1662
+ padding: 12px; border-radius: 14px; border: 1px solid var(--ss-w-line);
1663
+ background: var(--ss-w-panel); text-align: left; cursor: pointer; width: 100%;
1664
+ color: inherit; font: inherit;
1665
+ min-width: 0; /* the panel is min(400px, 100vw - 40px): without this,
1666
+ a long Turkish title blows the column out */
1667
+ }
1668
+ .ss-w-dash-tile-icon { flex: none; display: grid; place-items: center; width: 20px; height: 20px; color: var(--ss-w-accent); font-size: 18px; }
1669
+ .ss-w-dash-tile-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; flex: 1; }
1670
+ .ss-w-dash-tile-title { min-width: 0; font-weight: 600; font-size: 14px; overflow-wrap: anywhere; }
1671
+ .ss-w-dash-tile-subtitle { min-width: 0; font-size: 11.5px; color: var(--ss-w-faint); overflow-wrap: anywhere; }
1672
+ .ss-w-dash-tile-chevron { flex: none; margin-left: auto; width: 14px; height: 14px; color: var(--ss-w-faint); }
1673
+
1674
+ /* Badge + crests share their own row, below the title -- NOT the icon/chevron row. A
1675
+ nowrap badge (~70px) sitting beside icon+chevron in a ~180px half-span tile left the
1676
+ title column at ~22px wide, wrapping every title character-by-character. Here the row
1677
+ gets the body's full width, so a badge this size is comfortably inside it. */
1678
+ .ss-w-dash-tile-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
1679
+ .ss-w-dash-tile-badge { flex: none; font-size: 10.5px; font-weight: 700; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 13%, var(--ss-w-panel)); border-radius: 999px; padding: 3px 9px; white-space: nowrap; }
1680
+
1681
+ .ss-w-dash-tile-crests { flex: none; display: flex; align-items: center; }
1682
+ .ss-w-dash-tile-crest {
1683
+ width: 18px; height: 18px; border-radius: 50%;
1684
+ background-size: cover; background-position: center; background-color: var(--ss-w-bubble);
1685
+ margin-left: -6px; border: 2px solid var(--ss-w-panel); /* overlapping stack, capped at 3 in DashboardTile */
1686
+ }
1687
+ .ss-w-dash-tile-crest:first-child { margin-left: 0; }
1688
+
1689
+ /* Market: default 2-column grid, tiles get the same accent rail as the other card
1690
+ families (.ss-w-binsight, .ss-w-mocard, .ss-w-omcard) so a market tile reads as
1691
+ part of that visual language rather than a plain unstyled button. */
1692
+ .ss-w-dash--market .ss-w-dash-tile { border-left: 3px solid var(--ss-w-accent); }
1693
+
1694
+ /* Goal: one column of pill rows. */
1695
+ .ss-w-dash--goal .ss-w-dash-grid { grid-template-columns: 1fr; }
1696
+ .ss-w-dash--goal .ss-w-dash-tile { border-radius: 999px; padding: 10px 14px; }
1697
+
1698
+ /* Discovery: tinted bento cards. */
1699
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(1) { background: #eef8f1; }
1700
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(2) { background: #eef3fb; }
1701
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(3) { background: #fdf6ec; }
1702
+ .ss-w-dash--discovery .ss-w-dash-tile:nth-child(4) { background: #f5eefb; }
1487
1703
  `;
1488
1704
 
1489
1705
  // src/StatsWidget.tsx
1490
- var import_jsx_runtime19 = require("react/jsx-runtime");
1706
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1491
1707
  function buildClient(props) {
1492
1708
  try {
1493
1709
  if ("client" in props && props.client) {
1494
1710
  return { client: props.client, conversation: props.client.conversation() };
1495
1711
  }
1496
1712
  const cfg = props.config;
1497
- const client = new import_widget_sdk4.StatsWidgetClient(toClientOptions(cfg));
1713
+ const client = new import_widget_sdk5.StatsWidgetClient(toClientOptions(cfg));
1498
1714
  return { client, conversation: client.conversation(toUserContext(cfg)) };
1499
1715
  } catch (err) {
1500
1716
  console.error("[StatsWidget] failed to initialize:", err instanceof Error ? err.message : err);
@@ -1529,6 +1745,10 @@ function StatsWidget(props) {
1529
1745
  }),
1530
1746
  [props.config?.onAddToSlip, controller.state.conversationId, built]
1531
1747
  );
1748
+ const onDashboardTap = (0, import_react10.useMemo)(
1749
+ () => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input)) : void 0,
1750
+ [built]
1751
+ );
1532
1752
  (0, import_react10.useEffect)(() => {
1533
1753
  if (!built || !ui.injectStyles) return;
1534
1754
  const node = rootRef.current?.getRootNode();
@@ -1536,7 +1756,7 @@ function StatsWidget(props) {
1536
1756
  }, [built, ui.injectStyles]);
1537
1757
  if (!built) return null;
1538
1758
  const attrs = themeAttrs(ui);
1539
- return /* @__PURE__ */ (0, import_jsx_runtime19.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_runtime19.jsx)(ChatPanel, { controller, ui, slip }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
1759
+ return /* @__PURE__ */ (0, import_jsx_runtime21.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_runtime21.jsx)(ChatPanel, { controller, ui, slip, onDashboardTap }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1540
1760
  ChatFab,
1541
1761
  {
1542
1762
  label: ui.launcherLabel,