@burdenoff/website-sdk 2026.828.7 → 2026.829.2

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.
@@ -489,6 +489,7 @@ var EXPLORE_MESSAGE_FIELDS = (
489
489
  description
490
490
  product
491
491
  imageUrl
492
+ index
492
493
  }
493
494
  ctas {
494
495
  kind
@@ -496,6 +497,8 @@ var EXPLORE_MESSAGE_FIELDS = (
496
497
  url
497
498
  product
498
499
  }
500
+ followUps
501
+ answered
499
502
  errorCode
500
503
  createdAt
501
504
  completedAt
@@ -561,6 +564,16 @@ var SEND_EXPLORE_MESSAGE_MUTATION = (
561
564
  }
562
565
  `
563
566
  );
567
+ var RECORD_EXPLORE_CLICK_MUTATION = (
568
+ /* GraphQL */
569
+ `
570
+ mutation RecordExploreClick($input: RecordExploreClickInput!) {
571
+ recordExploreClick(input: $input) {
572
+ recorded
573
+ }
574
+ }
575
+ `
576
+ );
564
577
 
565
578
  // src/data/products.ts
566
579
  var ECOSYSTEM_PRODUCTS = [
@@ -1493,6 +1506,20 @@ function useExploreChat(options = {}) {
1493
1506
  return null;
1494
1507
  }, [messages]);
1495
1508
  const canSend = (phase === "ready" || phase === "error") && catalog.enabled && turnCount < catalog.limits.maxTurnsPerConversation;
1509
+ const recordClick = React.useCallback(
1510
+ (messageId, kind, url) => {
1511
+ const token = conversationTokenRef.current;
1512
+ if (!token) return;
1513
+ try {
1514
+ void client.mutate(RECORD_EXPLORE_CLICK_MUTATION, {
1515
+ input: { conversationToken: token, messageId, kind, url }
1516
+ }).catch(() => {
1517
+ });
1518
+ } catch {
1519
+ }
1520
+ },
1521
+ [client]
1522
+ );
1496
1523
  return {
1497
1524
  phase,
1498
1525
  catalog,
@@ -1510,7 +1537,8 @@ function useExploreChat(options = {}) {
1510
1537
  history: archive,
1511
1538
  openConversation,
1512
1539
  deleteConversation,
1513
- clearHistory
1540
+ clearHistory,
1541
+ recordClick
1514
1542
  };
1515
1543
  }
1516
1544
  var optimisticCounter = 0;
@@ -1523,21 +1551,37 @@ function getRecognitionCtor() {
1523
1551
  const w = window;
1524
1552
  return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
1525
1553
  }
1554
+ function isTouchDevice() {
1555
+ if (typeof window === "undefined" || !window.matchMedia) return false;
1556
+ try {
1557
+ return window.matchMedia("(pointer: coarse)").matches;
1558
+ } catch {
1559
+ return false;
1560
+ }
1561
+ }
1526
1562
  var MIC_DENIED_MESSAGE = "Microphone access was blocked. Allow it in your browser's site settings to dictate.";
1563
+ var MIC_UNAVAILABLE_MESSAGE = "Dictation isn't available in this browser. You can type instead.";
1564
+ var RELEASE_SETTLE_MS = 250;
1565
+ var RETRY_DELAY_MS = 350;
1566
+ var sleep = (ms) => new Promise((resolve) => {
1567
+ setTimeout(resolve, ms);
1568
+ });
1527
1569
  async function ensureMicrophoneAccess() {
1528
1570
  const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
1529
- if (!media?.getUserMedia) return { ok: true, confirmed: false };
1571
+ if (!media?.getUserMedia)
1572
+ return { ok: true, confirmed: false, primed: false };
1530
1573
  try {
1531
1574
  const status = await navigator.permissions?.query({
1532
1575
  name: "microphone"
1533
1576
  });
1534
- if (status?.state === "granted") return { ok: true, confirmed: true };
1577
+ if (status?.state === "granted")
1578
+ return { ok: true, confirmed: true, primed: false };
1535
1579
  } catch {
1536
1580
  }
1537
1581
  try {
1538
1582
  const stream = await media.getUserMedia({ audio: true });
1539
1583
  for (const track of stream.getTracks()) track.stop();
1540
- return { ok: true, confirmed: true };
1584
+ return { ok: true, confirmed: true, primed: true };
1541
1585
  } catch (error) {
1542
1586
  const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
1543
1587
  if (name === "NotAllowedError" || name === "SecurityError") {
@@ -1581,19 +1625,17 @@ function useSpeechInput({
1581
1625
  const recognitionRef = React.useRef(null);
1582
1626
  const startTokenRef = React.useRef(0);
1583
1627
  const micConfirmedRef = React.useRef(false);
1584
- const beginRecognitionRef = React.useRef(null);
1585
- const beginRecognition = React.useCallback(
1586
- (Ctor, token) => {
1587
- beginRecognitionRef.current?.(Ctor, token);
1588
- },
1589
- []
1590
- );
1628
+ const wantsListeningRef = React.useRef(false);
1629
+ const retriedRef = React.useRef(false);
1591
1630
  const finalRef = React.useRef(onFinalTranscript);
1592
1631
  const errorRef = React.useRef(onError);
1593
1632
  finalRef.current = onFinalTranscript;
1594
1633
  errorRef.current = onError;
1634
+ const langRef = React.useRef(lang);
1635
+ langRef.current = lang;
1595
1636
  const stop = React.useCallback(() => {
1596
1637
  startTokenRef.current += 1;
1638
+ wantsListeningRef.current = false;
1597
1639
  setListening(false);
1598
1640
  setInterim("");
1599
1641
  const recognition = recognitionRef.current;
@@ -1602,83 +1644,111 @@ function useSpeechInput({
1602
1644
  recognition.stop();
1603
1645
  } catch {
1604
1646
  }
1605
- setListening(false);
1606
- setInterim("");
1607
1647
  }, []);
1608
- const start = React.useCallback(() => {
1648
+ const openSession = React.useCallback((token) => {
1609
1649
  const Ctor = getRecognitionCtor();
1610
- if (!Ctor) return;
1611
- if (recognitionRef.current) stop();
1612
- setError(null);
1613
- setListening(true);
1650
+ if (!Ctor || token !== startTokenRef.current) return;
1651
+ const recognition = new Ctor();
1652
+ recognition.lang = langRef.current ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1653
+ recognition.continuous = !isTouchDevice();
1654
+ recognition.interimResults = true;
1655
+ recognition.maxAlternatives = 1;
1656
+ recognition.onstart = () => {
1657
+ if (token !== startTokenRef.current) return;
1658
+ setError(null);
1659
+ setListening(true);
1660
+ };
1661
+ recognition.onresult = (event) => {
1662
+ if (token !== startTokenRef.current) return;
1663
+ let settled = "";
1664
+ let pending = "";
1665
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
1666
+ const result = event.results[i];
1667
+ if (!result) continue;
1668
+ const text = result[0]?.transcript ?? "";
1669
+ if (result.isFinal) settled += text;
1670
+ else pending += text;
1671
+ }
1672
+ setInterim(pending);
1673
+ if (settled.trim() !== "") finalRef.current(settled);
1674
+ };
1675
+ recognition.onerror = (event) => {
1676
+ if (token !== startTokenRef.current) return;
1677
+ if ((event.error === "not-allowed" || event.error === "service-not-allowed") && micConfirmedRef.current && !retriedRef.current) {
1678
+ retriedRef.current = true;
1679
+ void sleep(RETRY_DELAY_MS).then(() => {
1680
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
1681
+ return;
1682
+ openSessionRef.current?.(token);
1683
+ });
1684
+ return;
1685
+ }
1686
+ const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? MIC_UNAVAILABLE_MESSAGE : describeError(event.error);
1687
+ wantsListeningRef.current = false;
1688
+ setListening(false);
1689
+ setInterim("");
1690
+ if (message !== "") {
1691
+ setError(message);
1692
+ errorRef.current?.(message);
1693
+ }
1694
+ };
1695
+ recognition.onend = () => {
1696
+ if (token !== startTokenRef.current) return;
1697
+ setInterim("");
1698
+ if (wantsListeningRef.current && isTouchDevice()) {
1699
+ void sleep(120).then(() => {
1700
+ if (token !== startTokenRef.current || !wantsListeningRef.current)
1701
+ return;
1702
+ openSessionRef.current?.(token);
1703
+ });
1704
+ return;
1705
+ }
1706
+ setListening(false);
1707
+ };
1708
+ recognitionRef.current = recognition;
1709
+ try {
1710
+ recognition.start();
1711
+ } catch {
1712
+ wantsListeningRef.current = false;
1713
+ setListening(false);
1714
+ }
1715
+ }, []);
1716
+ const openSessionRef = React.useRef(null);
1717
+ openSessionRef.current = openSession;
1718
+ const start = React.useCallback(() => {
1719
+ if (!getRecognitionCtor()) return;
1614
1720
  startTokenRef.current += 1;
1615
1721
  const token = startTokenRef.current;
1616
- void ensureMicrophoneAccess().then((access) => {
1722
+ wantsListeningRef.current = true;
1723
+ retriedRef.current = false;
1724
+ micConfirmedRef.current = false;
1725
+ setError(null);
1726
+ setListening(true);
1727
+ void ensureMicrophoneAccess().then(async (access) => {
1617
1728
  if (token !== startTokenRef.current) return;
1618
- micConfirmedRef.current = access.ok && access.confirmed;
1619
1729
  if (!access.ok) {
1730
+ wantsListeningRef.current = false;
1620
1731
  setListening(false);
1621
1732
  setError(access.message);
1622
1733
  errorRef.current?.(access.message);
1623
1734
  return;
1624
1735
  }
1625
- beginRecognition(Ctor, token);
1626
- });
1627
- }, [beginRecognition, stop]);
1628
- const beginRecognitionImpl = React.useCallback(
1629
- (Ctor, token) => {
1630
- const recognition = new Ctor();
1631
- recognition.lang = lang ?? (typeof navigator === "undefined" ? "en-US" : navigator.language || "en-US");
1632
- recognition.continuous = true;
1633
- recognition.interimResults = true;
1634
- recognition.maxAlternatives = 1;
1635
- recognition.onstart = () => {
1736
+ micConfirmedRef.current = access.confirmed;
1737
+ if (access.primed) {
1738
+ await sleep(RELEASE_SETTLE_MS);
1636
1739
  if (token !== startTokenRef.current) return;
1637
- setError(null);
1638
- setListening(true);
1639
- };
1640
- recognition.onresult = (event) => {
1641
- let settled = "";
1642
- let pending = "";
1643
- for (let i = event.resultIndex; i < event.results.length; i += 1) {
1644
- const result = event.results[i];
1645
- if (!result) continue;
1646
- const text = result[0]?.transcript ?? "";
1647
- if (result.isFinal) settled += text;
1648
- else pending += text;
1649
- }
1650
- setInterim(pending);
1651
- if (settled.trim() !== "") finalRef.current(settled);
1652
- };
1653
- recognition.onerror = (event) => {
1654
- const message = micConfirmedRef.current && (event.error === "not-allowed" || event.error === "service-not-allowed") ? "Dictation isn't available in this browser. You can type instead." : describeError(event.error);
1655
- setListening(false);
1656
- setInterim("");
1657
- if (message !== "") {
1658
- setError(message);
1659
- errorRef.current?.(message);
1660
- }
1661
- };
1662
- recognition.onend = () => {
1663
- setListening(false);
1664
- setInterim("");
1665
- };
1666
- recognitionRef.current = recognition;
1667
- try {
1668
- recognition.start();
1669
- } catch {
1670
- setListening(false);
1671
1740
  }
1672
- },
1673
- [lang]
1674
- );
1675
- beginRecognitionRef.current = beginRecognitionImpl;
1741
+ openSessionRef.current?.(token);
1742
+ });
1743
+ }, []);
1676
1744
  const toggle = React.useCallback(() => {
1677
1745
  if (listening) stop();
1678
1746
  else start();
1679
1747
  }, [listening, start, stop]);
1680
1748
  React.useEffect(
1681
1749
  () => () => {
1750
+ startTokenRef.current += 1;
1751
+ wantsListeningRef.current = false;
1682
1752
  const recognition = recognitionRef.current;
1683
1753
  if (!recognition) return;
1684
1754
  recognition.onresult = null;
@@ -1862,6 +1932,50 @@ var EXPLORE_CSS = `
1862
1932
  border-radius: 9999px;
1863
1933
  background-clip: content-box;
1864
1934
  }
1935
+
1936
+ /* The product strip scrolls inside itself; a visible scrollbar would read as a broken
1937
+ layout rather than an affordance, and reserving its gutter shifts the chips. */
1938
+ .boff-explore-strip {
1939
+ scrollbar-width: none;
1940
+ -ms-overflow-style: none;
1941
+ }
1942
+ .boff-explore-strip::-webkit-scrollbar { display: none; }
1943
+
1944
+ @keyframes boff-explore-fade-in {
1945
+ from { opacity: 0; transform: translateY(2px); }
1946
+ to { opacity: 1; transform: none; }
1947
+ }
1948
+ .boff-explore-fade { animation: boff-explore-fade-in 220ms ease-out; }
1949
+ @media (prefers-reduced-motion: reduce) {
1950
+ .boff-explore-fade { animation: none; }
1951
+ }
1952
+
1953
+ /* Nothing inside an answer may widen the page. Model output carries long URLs, code and
1954
+ tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
1955
+ WHOLE document horizontally scrollable, not just the bubble. */
1956
+ .boff-explore-body,
1957
+ .boff-explore-body p,
1958
+ .boff-explore-body li,
1959
+ .boff-explore-body a,
1960
+ .boff-explore-body h1,
1961
+ .boff-explore-body h2,
1962
+ .boff-explore-body h3 {
1963
+ overflow-wrap: anywhere;
1964
+ word-break: break-word;
1965
+ }
1966
+ .boff-explore-body pre {
1967
+ overflow-x: auto;
1968
+ max-width: 100%;
1969
+ }
1970
+ .boff-explore-body table {
1971
+ display: block;
1972
+ overflow-x: auto;
1973
+ max-width: 100%;
1974
+ }
1975
+ .boff-explore-body img {
1976
+ max-width: 100%;
1977
+ height: auto;
1978
+ }
1865
1979
  `;
1866
1980
  function ExploreStyles() {
1867
1981
  return /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
@@ -1883,7 +1997,6 @@ var CAPABILITIES = [
1883
1997
  body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
1884
1998
  }
1885
1999
  ];
1886
- var PRODUCT_CHIP_PREVIEW = 12;
1887
2000
  var CTA_VARIANTS = {
1888
2001
  CONTACT: "default",
1889
2002
  WAITLIST: "default",
@@ -1922,7 +2035,10 @@ function logoUrlOf(url) {
1922
2035
  return null;
1923
2036
  }
1924
2037
  }
1925
- function ReferenceCard({ reference }) {
2038
+ function ReferenceCard({
2039
+ reference,
2040
+ onFollow
2041
+ }) {
1926
2042
  const [imageFailed, setImageFailed] = React.useState(false);
1927
2043
  const [logoFailed, setLogoFailed] = React.useState(false);
1928
2044
  const host = hostnameOf(reference.url);
@@ -1944,9 +2060,11 @@ function ReferenceCard({ reference }) {
1944
2060
  "a",
1945
2061
  {
1946
2062
  "data-boff-explore": "reference",
2063
+ "data-index": reference.index ?? void 0,
1947
2064
  href: reference.url,
1948
2065
  target: "_blank",
1949
2066
  rel: "noopener noreferrer",
2067
+ onClick: () => onFollow?.(reference.url),
1950
2068
  className: "group flex flex-row items-center gap-3 overflow-hidden rounded-xl border border-border bg-card p-2 transition-colors hover:border-primary/40 hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:flex-col sm:items-stretch sm:gap-0 sm:p-0",
1951
2069
  children: [
1952
2070
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-11 w-11 shrink-0 overflow-hidden rounded-lg bg-muted sm:hidden", children: logoImg }),
@@ -1970,7 +2088,10 @@ function ReferenceCard({ reference }) {
1970
2088
  }
1971
2089
  ) }) : letterPlate }),
1972
2090
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5 sm:gap-1.5 sm:p-3", children: [
1973
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: reference.title || host || reference.url }),
2091
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: [
2092
+ reference.index ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1.5 inline-flex h-4 min-w-4 items-center justify-center rounded bg-primary/10 px-1 text-[10px] font-bold text-primary", children: reference.index }) : null,
2093
+ reference.title || host || reference.url
2094
+ ] }),
1974
2095
  reference.description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground max-sm:hidden", children: reference.description }) : null,
1975
2096
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 sm:mt-auto sm:pt-2", children: [
1976
2097
  reference.product ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground max-sm:hidden", children: reference.product }) : null,
@@ -1986,7 +2107,8 @@ function ReferenceCard({ reference }) {
1986
2107
  }
1987
2108
  function CtaButton({
1988
2109
  cta,
1989
- onNavigate
2110
+ onNavigate,
2111
+ onFollow
1990
2112
  }) {
1991
2113
  const variant = CTA_VARIANTS[cta.kind] ?? "outline";
1992
2114
  const internal = isInternalPath(cta.url);
@@ -1999,7 +2121,10 @@ function CtaButton({
1999
2121
  type: "button",
2000
2122
  size: "sm",
2001
2123
  variant,
2002
- onClick: () => onNavigate(cta.url),
2124
+ onClick: () => {
2125
+ onFollow?.(cta.url);
2126
+ onNavigate(cta.url);
2127
+ },
2003
2128
  children: cta.label
2004
2129
  }
2005
2130
  );
@@ -2010,6 +2135,7 @@ function CtaButton({
2010
2135
  "data-boff-explore": "cta",
2011
2136
  "data-kind": cta.kind,
2012
2137
  href: cta.url,
2138
+ onClick: () => onFollow?.(cta.url),
2013
2139
  ...internal ? {} : { target: "_blank", rel: "noopener noreferrer" },
2014
2140
  children: [
2015
2141
  cta.label,
@@ -2061,7 +2187,9 @@ function AssistantBubble({
2061
2187
  message,
2062
2188
  activity,
2063
2189
  onNavigate,
2064
- anchorRef
2190
+ anchorRef,
2191
+ onFollow,
2192
+ onFollowUp
2065
2193
  }) {
2066
2194
  const streaming = message.status === "PENDING" || message.status === "RUNNING";
2067
2195
  const body = message.content || message.partialContent || "";
@@ -2083,24 +2211,77 @@ function AssistantBubble({
2083
2211
  }
2084
2212
  ),
2085
2213
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
2214
+ message.status === "COMPLETED" && message.answered === false ? /* @__PURE__ */ jsxRuntime.jsxs(
2215
+ "div",
2216
+ {
2217
+ "data-boff-explore": "no-answer",
2218
+ className: "flex items-start gap-2 rounded-xl border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs leading-relaxed text-foreground",
2219
+ children: [
2220
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TriangleAlert, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-amber-600 dark:text-amber-400" }),
2221
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2222
+ "I couldn't find this in our documentation, so the answer below is not grounded in a source. Try rephrasing, or",
2223
+ " ",
2224
+ /* @__PURE__ */ jsxRuntime.jsx(
2225
+ "a",
2226
+ {
2227
+ href: "/contact",
2228
+ className: "underline underline-offset-2 hover:text-primary",
2229
+ onClick: (event) => {
2230
+ if (!onNavigate) return;
2231
+ event.preventDefault();
2232
+ onNavigate("/contact");
2233
+ },
2234
+ children: "ask a human"
2235
+ }
2236
+ ),
2237
+ "."
2238
+ ] })
2239
+ ]
2240
+ }
2241
+ ) : null,
2086
2242
  body ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
2087
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { children: body }) }),
2243
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm boff-explore-body min-w-0 text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { children: body }) }),
2088
2244
  streaming ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
2089
2245
  ] }) : null,
2090
2246
  streaming ? /* @__PURE__ */ jsxRuntime.jsx(ThinkingIndicator, { activity }) : null,
2091
2247
  failed && !body ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-2xl rounded-tl-md border border-border bg-muted px-4 py-3 text-sm text-muted-foreground", children: "That answer didn't come through. Try asking again." }) : null,
2092
2248
  message.references.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2093
2249
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Sources" }),
2094
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: message.references.map((reference) => /* @__PURE__ */ jsxRuntime.jsx(ReferenceCard, { reference }, reference.url)) })
2250
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: message.references.map((reference) => /* @__PURE__ */ jsxRuntime.jsx(
2251
+ ReferenceCard,
2252
+ {
2253
+ reference,
2254
+ onFollow: (url) => onFollow?.("REFERENCE", url)
2255
+ },
2256
+ reference.url
2257
+ )) })
2095
2258
  ] }) : null,
2096
2259
  message.ctas.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 pt-0.5", children: message.ctas.map((cta) => /* @__PURE__ */ jsxRuntime.jsx(
2097
2260
  CtaButton,
2098
2261
  {
2099
2262
  cta,
2100
- onNavigate
2263
+ onNavigate,
2264
+ onFollow: (url) => onFollow?.("CTA", url)
2101
2265
  },
2102
2266
  `${cta.kind}-${cta.url}`
2103
- )) }) : null
2267
+ )) }) : null,
2268
+ message.status === "COMPLETED" && (message.followUps?.length ?? 0) > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5 pt-0.5", children: [
2269
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Next you could ask" }),
2270
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5", children: (message.followUps ?? []).map((question) => /* @__PURE__ */ jsxRuntime.jsxs(
2271
+ "button",
2272
+ {
2273
+ "data-boff-explore": "follow-up",
2274
+ type: "button",
2275
+ onClick: () => onFollowUp?.(question),
2276
+ className: "group inline-flex max-w-full items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1 text-left text-xs text-card-foreground transition-colors hover:border-primary/40 hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2277
+ children: [
2278
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: question }),
2279
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
2280
+ ]
2281
+ },
2282
+ question
2283
+ )) })
2284
+ ] }) : null
2104
2285
  ] })
2105
2286
  ]
2106
2287
  }
@@ -2146,6 +2327,256 @@ function ErrorBanner({
2146
2327
  }
2147
2328
  );
2148
2329
  }
2330
+ function PromptCarousel({
2331
+ prompts,
2332
+ onPrompt,
2333
+ disabled
2334
+ }) {
2335
+ const [index, setIndex] = React.useState(0);
2336
+ const [paused, setPaused] = React.useState(false);
2337
+ const count = prompts.length;
2338
+ const current = prompts[index % count] ?? prompts[0] ?? "";
2339
+ const go = React.useCallback(
2340
+ (delta) => {
2341
+ setIndex((i) => (i + delta + count) % count);
2342
+ },
2343
+ [count]
2344
+ );
2345
+ React.useEffect(() => {
2346
+ if (paused || disabled || count < 2) return;
2347
+ if (typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
2348
+ return;
2349
+ }
2350
+ const timer = setInterval(() => setIndex((i) => (i + 1) % count), 4500);
2351
+ return () => clearInterval(timer);
2352
+ }, [paused, disabled, count]);
2353
+ if (count === 0) return null;
2354
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2355
+ "div",
2356
+ {
2357
+ className: "flex min-w-0 items-center gap-1.5",
2358
+ role: "group",
2359
+ "aria-roledescription": "carousel",
2360
+ "aria-label": "Example questions",
2361
+ onMouseEnter: () => setPaused(true),
2362
+ onMouseLeave: () => setPaused(false),
2363
+ onFocusCapture: () => setPaused(true),
2364
+ onBlurCapture: () => setPaused(false),
2365
+ children: [
2366
+ count > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
2367
+ "button",
2368
+ {
2369
+ type: "button",
2370
+ "aria-label": "Previous suggestion",
2371
+ onClick: () => go(-1),
2372
+ className: "hidden h-8 w-8 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:inline-flex",
2373
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" })
2374
+ }
2375
+ ) : null,
2376
+ /* @__PURE__ */ jsxRuntime.jsxs(
2377
+ "button",
2378
+ {
2379
+ "data-boff-explore": "chip",
2380
+ "data-chip-kind": "prompt",
2381
+ type: "button",
2382
+ disabled,
2383
+ "aria-label": `Ask: ${current}`,
2384
+ "aria-live": paused ? "polite" : "off",
2385
+ onClick: () => onPrompt(current),
2386
+ className: "boff-explore-fade group flex min-w-0 flex-1 items-center gap-2 rounded-full border border-border bg-card px-4 py-2 text-left text-sm text-card-foreground shadow-sm transition-colors hover:border-primary/50 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2387
+ children: [
2388
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-3.5 w-3.5 shrink-0 text-primary" }),
2389
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: current }),
2390
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "ml-auto h-3.5 w-3.5 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
2391
+ ]
2392
+ },
2393
+ current
2394
+ ),
2395
+ count > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
2396
+ "button",
2397
+ {
2398
+ type: "button",
2399
+ "aria-label": "Next suggestion",
2400
+ onClick: () => go(1),
2401
+ className: "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2402
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })
2403
+ }
2404
+ ) : null
2405
+ ]
2406
+ }
2407
+ );
2408
+ }
2409
+ function ProductStrip({
2410
+ products,
2411
+ focusProduct,
2412
+ onFocus
2413
+ }) {
2414
+ const [expanded, setExpanded] = React.useState(false);
2415
+ const chipClass = (active) => cn(
2416
+ "shrink-0 rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2417
+ active ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2418
+ );
2419
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2420
+ "div",
2421
+ {
2422
+ className: "flex min-w-0 items-center gap-2",
2423
+ role: "group",
2424
+ "aria-label": "Focus the assistant on one product",
2425
+ children: [
2426
+ /* @__PURE__ */ jsxRuntime.jsxs(
2427
+ "div",
2428
+ {
2429
+ className: cn(
2430
+ "boff-explore-strip flex min-w-0 flex-1 gap-2",
2431
+ expanded ? "flex-wrap" : "flex-nowrap overflow-x-auto"
2432
+ ),
2433
+ children: [
2434
+ /* @__PURE__ */ jsxRuntime.jsx(
2435
+ "button",
2436
+ {
2437
+ "data-boff-explore": "chip",
2438
+ "data-chip-kind": "product",
2439
+ "data-product": "all",
2440
+ type: "button",
2441
+ "aria-pressed": focusProduct === null,
2442
+ onClick: () => onFocus(null),
2443
+ className: chipClass(focusProduct === null),
2444
+ children: "All products"
2445
+ }
2446
+ ),
2447
+ products.map((product) => /* @__PURE__ */ jsxRuntime.jsx(
2448
+ "button",
2449
+ {
2450
+ "data-boff-explore": "chip",
2451
+ "data-chip-kind": "product",
2452
+ "data-product": product.slug,
2453
+ type: "button",
2454
+ "aria-pressed": focusProduct === product.slug,
2455
+ title: product.tagline ?? product.name,
2456
+ onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
2457
+ className: chipClass(focusProduct === product.slug),
2458
+ children: product.name
2459
+ },
2460
+ product.slug
2461
+ ))
2462
+ ]
2463
+ }
2464
+ ),
2465
+ products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
2466
+ "button",
2467
+ {
2468
+ type: "button",
2469
+ "data-boff-explore": "products-toggle",
2470
+ "aria-expanded": expanded,
2471
+ onClick: () => setExpanded((v) => !v),
2472
+ className: "shrink-0 whitespace-nowrap rounded-full px-2 py-1 text-xs font-medium text-primary underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2473
+ children: expanded ? "Show less" : `All ${products.length}`
2474
+ }
2475
+ ) : null
2476
+ ]
2477
+ }
2478
+ );
2479
+ }
2480
+ function DisclaimerDialog({
2481
+ open,
2482
+ onClose,
2483
+ captchaOn
2484
+ }) {
2485
+ const panelRef = React.useRef(null);
2486
+ const closeRef = React.useRef(null);
2487
+ const titleId = "boff-explore-disclaimer-title";
2488
+ React.useEffect(() => {
2489
+ if (!open) return;
2490
+ const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2491
+ const focusable = () => Array.from(
2492
+ panelRef.current?.querySelectorAll(
2493
+ 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
2494
+ ) ?? []
2495
+ );
2496
+ const onKey = (event) => {
2497
+ if (event.key === "Escape") {
2498
+ onClose();
2499
+ return;
2500
+ }
2501
+ if (event.key !== "Tab") return;
2502
+ const items = focusable();
2503
+ if (items.length === 0) return;
2504
+ const first = items[0];
2505
+ const last = items[items.length - 1];
2506
+ const active = document.activeElement;
2507
+ if (event.shiftKey && (active === first || !panelRef.current?.contains(active))) {
2508
+ event.preventDefault();
2509
+ last?.focus();
2510
+ } else if (!event.shiftKey && active === last) {
2511
+ event.preventDefault();
2512
+ first?.focus();
2513
+ }
2514
+ };
2515
+ document.addEventListener("keydown", onKey);
2516
+ closeRef.current?.focus();
2517
+ return () => {
2518
+ document.removeEventListener("keydown", onKey);
2519
+ opener?.focus();
2520
+ };
2521
+ }, [open, onClose]);
2522
+ if (!open) return null;
2523
+ return /* @__PURE__ */ jsxRuntime.jsx(
2524
+ "div",
2525
+ {
2526
+ "data-boff-explore": "disclaimer-dialog",
2527
+ role: "dialog",
2528
+ "aria-modal": "true",
2529
+ "aria-labelledby": titleId,
2530
+ className: "fixed inset-0 z-50 flex items-end justify-center bg-foreground/40 p-4 backdrop-blur-sm sm:items-center",
2531
+ onClick: onClose,
2532
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2533
+ "div",
2534
+ {
2535
+ ref: panelRef,
2536
+ className: "w-full max-w-md rounded-2xl border border-border bg-card p-5 shadow-xl",
2537
+ onClick: (event) => event.stopPropagation(),
2538
+ children: [
2539
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
2540
+ /* @__PURE__ */ jsxRuntime.jsx(
2541
+ "span",
2542
+ {
2543
+ "aria-hidden": "true",
2544
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary",
2545
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TriangleAlert, { className: "h-4 w-4" })
2546
+ }
2547
+ ),
2548
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
2549
+ /* @__PURE__ */ jsxRuntime.jsx(
2550
+ "p",
2551
+ {
2552
+ id: titleId,
2553
+ className: "text-sm font-semibold text-card-foreground",
2554
+ children: "How this assistant works"
2555
+ }
2556
+ ),
2557
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 space-y-2 text-xs leading-relaxed text-muted-foreground", children: [
2558
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources." }),
2559
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Conversations are saved in this browser so you can come back to them, and are also stored on our servers to help us improve these answers. Please don't share personal or confidential information." }),
2560
+ captchaOn ? /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Protected by reCAPTCHA \u2014 the Google Privacy Policy and Terms of Service apply." }) : null
2561
+ ] })
2562
+ ] })
2563
+ ] }),
2564
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
2565
+ Button,
2566
+ {
2567
+ ref: closeRef,
2568
+ size: "sm",
2569
+ variant: "secondary",
2570
+ onClick: onClose,
2571
+ children: "Got it"
2572
+ }
2573
+ ) })
2574
+ ]
2575
+ }
2576
+ )
2577
+ }
2578
+ );
2579
+ }
2149
2580
  function WelcomeState({
2150
2581
  title,
2151
2582
  body,
@@ -2156,21 +2587,19 @@ function WelcomeState({
2156
2587
  onFocus,
2157
2588
  disabled
2158
2589
  }) {
2159
- const [showAllProducts, setShowAllProducts] = React.useState(false);
2160
- const visibleProducts = showAllProducts ? products : products.slice(0, PRODUCT_CHIP_PREVIEW);
2161
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-10 sm:py-14", children: [
2590
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-8 sm:py-12", children: [
2162
2591
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
2163
2592
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-muted px-3 py-1 text-xs font-medium text-muted-foreground", children: [
2164
2593
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-3.5 w-3.5" }),
2165
2594
  "AI answers, grounded in our documentation"
2166
2595
  ] }),
2167
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mt-5 text-balance text-3xl font-bold tracking-tight sm:text-4xl", children: title }),
2168
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mx-auto mt-3 max-w-2xl text-pretty text-base leading-relaxed text-muted-foreground sm:text-lg", children: body })
2596
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mt-4 text-balance break-words text-2xl font-bold tracking-tight sm:text-4xl", children: title }),
2597
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mx-auto mt-2.5 max-w-2xl text-pretty text-sm leading-relaxed text-muted-foreground sm:text-lg", children: body })
2169
2598
  ] }),
2170
- /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-9 grid gap-3 sm:grid-cols-3", children: CAPABILITIES.map(({ icon: Icon, title: capTitle, body: capBody }) => /* @__PURE__ */ jsxRuntime.jsxs(
2599
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-7 grid gap-2.5 sm:grid-cols-3", children: CAPABILITIES.map(({ icon: Icon, title: capTitle, body: capBody }) => /* @__PURE__ */ jsxRuntime.jsxs(
2171
2600
  "li",
2172
2601
  {
2173
- className: "rounded-xl border border-border bg-card p-4 text-left",
2602
+ className: "rounded-xl border border-border/70 bg-card/60 p-3.5 text-left transition-colors hover:border-border",
2174
2603
  children: [
2175
2604
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-2.5 flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4" }) }),
2176
2605
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-card-foreground", children: capTitle }),
@@ -2179,72 +2608,27 @@ function WelcomeState({
2179
2608
  },
2180
2609
  capTitle
2181
2610
  )) }),
2182
- prompts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-9", children: [
2183
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
2184
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ jsxRuntime.jsxs(
2185
- "button",
2611
+ prompts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8", children: [
2612
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
2613
+ /* @__PURE__ */ jsxRuntime.jsx(
2614
+ PromptCarousel,
2186
2615
  {
2187
- "data-boff-explore": "chip",
2188
- "data-chip-kind": "prompt",
2189
- type: "button",
2190
- disabled,
2191
- onClick: () => onPrompt(prompt),
2192
- className: "group inline-flex max-w-full items-center gap-1.5 rounded-full border border-border bg-card px-3.5 py-1.5 text-left text-sm text-card-foreground transition-colors hover:border-primary/40 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2193
- children: [
2194
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: prompt }),
2195
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
2196
- ]
2197
- },
2198
- prompt
2199
- )) })
2616
+ prompts,
2617
+ onPrompt,
2618
+ disabled
2619
+ }
2620
+ )
2200
2621
  ] }) : null,
2201
- products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8", children: [
2202
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
2203
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
2204
- /* @__PURE__ */ jsxRuntime.jsx(
2205
- "button",
2206
- {
2207
- "data-boff-explore": "chip",
2208
- "data-chip-kind": "product",
2209
- "data-product": "all",
2210
- type: "button",
2211
- "aria-pressed": focusProduct === null,
2212
- onClick: () => onFocus(null),
2213
- className: cn(
2214
- "rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2215
- focusProduct === null ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2216
- ),
2217
- children: "All products"
2218
- }
2219
- ),
2220
- visibleProducts.map((product) => /* @__PURE__ */ jsxRuntime.jsx(
2221
- "button",
2222
- {
2223
- "data-boff-explore": "chip",
2224
- "data-chip-kind": "product",
2225
- "data-product": product.slug,
2226
- type: "button",
2227
- "aria-pressed": focusProduct === product.slug,
2228
- title: product.tagline ?? product.name,
2229
- onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
2230
- className: cn(
2231
- "rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2232
- focusProduct === product.slug ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2233
- ),
2234
- children: product.name
2235
- },
2236
- product.slug
2237
- )),
2238
- products.length > PRODUCT_CHIP_PREVIEW ? /* @__PURE__ */ jsxRuntime.jsx(
2239
- "button",
2240
- {
2241
- type: "button",
2242
- onClick: () => setShowAllProducts((v) => !v),
2243
- className: "rounded-full px-3 py-1 text-xs font-medium text-primary underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2244
- children: showAllProducts ? "Show fewer" : `Show all ${products.length}`
2245
- }
2246
- ) : null
2247
- ] })
2622
+ products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6", children: [
2623
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
2624
+ /* @__PURE__ */ jsxRuntime.jsx(
2625
+ ProductStrip,
2626
+ {
2627
+ products,
2628
+ focusProduct,
2629
+ onFocus
2630
+ }
2631
+ )
2248
2632
  ] }) : null
2249
2633
  ] });
2250
2634
  }
@@ -2281,7 +2665,8 @@ function ExplorePage({
2281
2665
  history,
2282
2666
  openConversation,
2283
2667
  deleteConversation,
2284
- clearHistory
2668
+ clearHistory,
2669
+ recordClick
2285
2670
  } = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
2286
2671
  const [draft, setDraft] = React.useState("");
2287
2672
  const textareaRef = React.useRef(null);
@@ -2300,6 +2685,7 @@ function ExplorePage({
2300
2685
  }
2301
2686
  });
2302
2687
  speechStopRef.current = speech.stop;
2688
+ const [disclaimerOpen, setDisclaimerOpen] = React.useState(false);
2303
2689
  const scrollRef = React.useRef(null);
2304
2690
  const latestAssistantRef = React.useRef(null);
2305
2691
  const alignedForRef = React.useRef(null);
@@ -2392,7 +2778,7 @@ function ExplorePage({
2392
2778
  "data-boff-explore": "page",
2393
2779
  "data-phase": phase,
2394
2780
  className: cn(
2395
- "flex w-full flex-col bg-background text-foreground",
2781
+ "flex w-full min-w-0 flex-col overflow-x-hidden bg-background text-foreground",
2396
2782
  heightMode === "auto" && "min-h-[70vh]",
2397
2783
  className
2398
2784
  ),
@@ -2534,7 +2920,7 @@ function ExplorePage({
2534
2920
  role: "log",
2535
2921
  "aria-live": "polite",
2536
2922
  "aria-label": "Explore conversation",
2537
- className: "boff-explore-scroll min-h-0 flex-1 overflow-y-auto",
2923
+ className: "boff-explore-scroll min-h-0 min-w-0 flex-1 overflow-y-auto overflow-x-hidden",
2538
2924
  children: showWelcome ? /* @__PURE__ */ jsxRuntime.jsx(
2539
2925
  WelcomeState,
2540
2926
  {
@@ -2547,21 +2933,31 @@ function ExplorePage({
2547
2933
  onFocus: setFocusProduct,
2548
2934
  disabled: !canSend || busy
2549
2935
  }
2550
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full max-w-3xl space-y-6 px-4 py-6", children: messages.map(
2936
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full min-w-0 max-w-3xl space-y-6 px-4 py-6", children: messages.map(
2551
2937
  (message) => message.role === "USER" ? /* @__PURE__ */ jsxRuntime.jsx(UserBubble, { message }, message.id) : /* @__PURE__ */ jsxRuntime.jsx(
2552
2938
  AssistantBubble,
2553
2939
  {
2554
2940
  message,
2555
2941
  activity,
2556
2942
  onNavigate,
2557
- anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0
2943
+ anchorRef: message.id === latestAssistantId ? latestAssistantRef : void 0,
2944
+ onFollow: (kind, url) => recordClick(message.id, kind, url),
2945
+ onFollowUp: sendPrompt
2558
2946
  },
2559
2947
  message.id
2560
2948
  )
2561
2949
  ) })
2562
2950
  }
2563
2951
  ),
2564
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border bg-background px-4 pb-4 pt-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
2952
+ /* @__PURE__ */ jsxRuntime.jsx(
2953
+ DisclaimerDialog,
2954
+ {
2955
+ open: disclaimerOpen,
2956
+ onClose: () => setDisclaimerOpen(false),
2957
+ captchaOn
2958
+ }
2959
+ ),
2960
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border bg-background/85 px-4 pb-[max(1rem,env(safe-area-inset-bottom))] pt-3 backdrop-blur-md", children: /* @__PURE__ */ jsxRuntime.jsxs(
2565
2961
  "form",
2566
2962
  {
2567
2963
  "data-boff-explore": "composer",
@@ -2576,7 +2972,7 @@ function ExplorePage({
2576
2972
  "div",
2577
2973
  {
2578
2974
  className: cn(
2579
- "flex items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-colors",
2975
+ "flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
2580
2976
  overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
2581
2977
  ),
2582
2978
  children: [
@@ -2597,7 +2993,7 @@ function ExplorePage({
2597
2993
  },
2598
2994
  "aria-label": "Ask a question",
2599
2995
  placeholder: composerDisabled ? phase === "limited" ? "Message limit reached \u2014 please try again later" : "Explore is unavailable right now" : focusedProductName ? `Ask about ${focusedProductName}\u2026` : "Ask anything about our products\u2026",
2600
- className: "max-h-[200px] min-h-[44px] flex-1 resize-none border-0 bg-transparent px-2 py-2.5 text-sm shadow-none focus-visible:ring-0 md:text-sm"
2996
+ className: "max-h-[200px] min-h-[44px] w-full min-w-0 flex-1 resize-none border-0 bg-transparent px-2 py-2.5 text-sm shadow-none focus-visible:ring-0 md:text-sm"
2601
2997
  }
2602
2998
  ),
2603
2999
  speech.supported ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -2688,48 +3084,19 @@ function ExplorePage({
2688
3084
  }
2689
3085
  )
2690
3086
  ] }),
2691
- /* @__PURE__ */ jsxRuntime.jsxs(
2692
- "p",
2693
- {
2694
- "data-boff-explore": "disclaimer",
2695
- className: "mt-2 text-xs leading-relaxed text-muted-foreground",
2696
- children: [
2697
- "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
2698
- " ",
2699
- "Conversations are saved in this browser so you can come back to them, and are also stored on our servers to help us improve these answers. Please don't share personal or confidential information.",
2700
- captchaOn ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2701
- " ",
2702
- "This site is protected by reCAPTCHA; the Google",
2703
- " ",
2704
- /* @__PURE__ */ jsxRuntime.jsx(
2705
- "a",
2706
- {
2707
- href: "https://policies.google.com/privacy",
2708
- target: "_blank",
2709
- rel: "noopener noreferrer",
2710
- className: "underline underline-offset-2 hover:text-foreground",
2711
- children: "Privacy Policy"
2712
- }
2713
- ),
2714
- " ",
2715
- "and",
2716
- " ",
2717
- /* @__PURE__ */ jsxRuntime.jsx(
2718
- "a",
2719
- {
2720
- href: "https://policies.google.com/terms",
2721
- target: "_blank",
2722
- rel: "noopener noreferrer",
2723
- className: "underline underline-offset-2 hover:text-foreground",
2724
- children: "Terms of Service"
2725
- }
2726
- ),
2727
- " ",
2728
- "apply."
2729
- ] }) : null
2730
- ]
2731
- }
2732
- )
3087
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
3088
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: "Grounded in our docs \u2014 check anything important." }),
3089
+ /* @__PURE__ */ jsxRuntime.jsx(
3090
+ "button",
3091
+ {
3092
+ type: "button",
3093
+ "data-boff-explore": "disclaimer",
3094
+ onClick: () => setDisclaimerOpen(true),
3095
+ className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
3096
+ children: "Disclaimer"
3097
+ }
3098
+ )
3099
+ ] })
2733
3100
  ]
2734
3101
  }
2735
3102
  ) })