@burdenoff/website-sdk 2026.922.3 → 2026.922.4

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.
@@ -142,6 +142,27 @@ interface ExploreLimits {
142
142
  messagesPerDay: number;
143
143
  historyTurns: number;
144
144
  }
145
+ /**
146
+ * `type ExploreConceptHighlight` — one published concept illustration, offered
147
+ * as a way into the conversation before the visitor has typed anything.
148
+ *
149
+ * Every field is non-null on the wire: a highlight with no picture or no
150
+ * destination is not a highlight, so the server drops it rather than sending a
151
+ * half-built one. `product` is the slug, `productName` the display casing the
152
+ * product's own pages use.
153
+ *
154
+ * The pictures are the published concept mockups — illustrations of an idea,
155
+ * not screenshots of shipping software — which is why every surface that shows
156
+ * one also labels it (`ExploreStrings.conceptIllustration`).
157
+ */
158
+ interface ExploreConceptHighlight {
159
+ product: string;
160
+ productName: string;
161
+ title: string;
162
+ url: string;
163
+ imageUrl: string;
164
+ alt: string;
165
+ }
145
166
  /**
146
167
  * `type ExploreCatalog` — everything the page needs before the first message.
147
168
  *
@@ -157,6 +178,18 @@ interface ExploreCatalog {
157
178
  limits: ExploreLimits;
158
179
  captchaRequired: boolean;
159
180
  recaptchaSiteKey?: string | null;
181
+ /**
182
+ * At most 8, the site's own product first. Non-null and non-nullable per the
183
+ * contract, so a gateway that knows the field always sends an array.
184
+ *
185
+ * A gateway that does NOT know it yet does not answer without it — it refuses
186
+ * the whole document (`GRAPHQL_VALIDATION_FAILED`), which is why the hook
187
+ * retries with `EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS` instead of falling
188
+ * back to the built-in catalog. That retry is the one response that reaches
189
+ * `normalizeCatalog` with this field absent, and the hardening there is what
190
+ * keeps `catalog.conceptHighlights.filter(...)` from throwing in render.
191
+ */
192
+ conceptHighlights: ExploreConceptHighlight[];
160
193
  }
161
194
  /** `input SendExploreMessageInput`. */
162
195
  interface SendExploreMessageInput {
@@ -202,7 +235,30 @@ interface SendExploreMessageResult {
202
235
  remainingToday?: number | null;
203
236
  }
204
237
  /** Loaded once per page. Safe to serve from the SDK's 5-minute query cache. */
205
- declare const EXPLORE_CATALOG_QUERY = "\n query ExploreCatalog($productSlug: String) {\n exploreCatalog(productSlug: $productSlug) {\n enabled\n products {\n slug\n name\n tagline\n website\n }\n examplePrompts\n welcomeTitle\n welcomeBody\n limits {\n maxMessageLength\n maxTurnsPerConversation\n messagesPerDay\n historyTurns\n }\n captchaRequired\n recaptchaSiteKey\n }\n }\n";
238
+ declare const EXPLORE_CATALOG_QUERY: string;
239
+ /**
240
+ * The same catalog WITHOUT `conceptHighlights`, for a gateway whose supergraph
241
+ * predates the field.
242
+ *
243
+ * This is not belt-and-braces. GraphQL validation is all-or-nothing: a gateway
244
+ * that does not know the field rejects the ENTIRE document and returns no
245
+ * `data` at all, so selecting `conceptHighlights` against an older supergraph
246
+ * costs the whole catalog — `enabled`, `captchaRequired`, `recaptchaSiteKey`,
247
+ * the welcome copy and `limits` included — and not merely the strip. Measured
248
+ * against production:
249
+ *
250
+ * {"errors":[{"message":"Cannot query field \\"conceptHighlights\\" on type
251
+ * \\"ExploreCatalog\\".","extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}]}
252
+ *
253
+ * That window is guaranteed even with perfect deploy ordering, because the prod
254
+ * gateway's CDN edge serves a stale supergraph for ~30-40 minutes after the
255
+ * subgraph publishes. `useExploreChat` therefore retries with this document
256
+ * (see `catalogRejectedTheHighlightField`) rather than dropping to the built-in
257
+ * fallback, whose hardcoded `enabled: true` would ignore the server-side kill
258
+ * switch and whose `captchaRequired: false` would let every send be rejected
259
+ * server-side while the composer looked fine.
260
+ */
261
+ declare const EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS: string;
206
262
  /**
207
263
  * The poll. MUST be issued with `client.query(..., { cache: false })` — the
208
264
  * default 5-minute cache would return the first PENDING snapshot forever and
@@ -221,4 +277,4 @@ interface SendExploreMessageMutationData {
221
277
  sendExploreMessage: SendExploreMessageResult;
222
278
  }
223
279
 
224
- export { type ExploreProduct as E, SEND_EXPLORE_MESSAGE_MUTATION as S, type ExploreLimits as a, type ExploreCatalog as b, type ExploreMessage as c, type ExploreFeedbackRating as d, type ExploreFeedbackReason as e, EXPLORE_CATALOG_QUERY as f, EXPLORE_MESSAGE_QUERY as g, type ExploreCatalogQueryData as h, type ExploreConversation as i, type ExploreCtaKind as j, type ExploreCtaLink as k, type ExploreErrorCode as l, type ExploreMessageQueryData as m, type ExploreMessageRole as n, type ExploreMessageStatus as o, type ExploreReference as p, type SendExploreMessageInput as q, type SendExploreMessageMutationData as r, type SendExploreMessageResult as s };
280
+ export { type ExploreProduct as E, SEND_EXPLORE_MESSAGE_MUTATION as S, type ExploreReference as a, type ExploreLimits as b, type ExploreCatalog as c, type ExploreMessage as d, type ExploreFeedbackRating as e, type ExploreFeedbackReason as f, EXPLORE_CATALOG_QUERY as g, EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS as h, EXPLORE_MESSAGE_QUERY as i, type ExploreCatalogQueryData as j, type ExploreConceptHighlight as k, type ExploreConversation as l, type ExploreCtaKind as m, type ExploreCtaLink as n, type ExploreErrorCode as o, type ExploreMessageQueryData as p, type ExploreMessageRole as q, type ExploreMessageStatus as r, type SendExploreMessageInput as s, type SendExploreMessageMutationData as t, type SendExploreMessageResult as u };
@@ -142,6 +142,27 @@ interface ExploreLimits {
142
142
  messagesPerDay: number;
143
143
  historyTurns: number;
144
144
  }
145
+ /**
146
+ * `type ExploreConceptHighlight` — one published concept illustration, offered
147
+ * as a way into the conversation before the visitor has typed anything.
148
+ *
149
+ * Every field is non-null on the wire: a highlight with no picture or no
150
+ * destination is not a highlight, so the server drops it rather than sending a
151
+ * half-built one. `product` is the slug, `productName` the display casing the
152
+ * product's own pages use.
153
+ *
154
+ * The pictures are the published concept mockups — illustrations of an idea,
155
+ * not screenshots of shipping software — which is why every surface that shows
156
+ * one also labels it (`ExploreStrings.conceptIllustration`).
157
+ */
158
+ interface ExploreConceptHighlight {
159
+ product: string;
160
+ productName: string;
161
+ title: string;
162
+ url: string;
163
+ imageUrl: string;
164
+ alt: string;
165
+ }
145
166
  /**
146
167
  * `type ExploreCatalog` — everything the page needs before the first message.
147
168
  *
@@ -157,6 +178,18 @@ interface ExploreCatalog {
157
178
  limits: ExploreLimits;
158
179
  captchaRequired: boolean;
159
180
  recaptchaSiteKey?: string | null;
181
+ /**
182
+ * At most 8, the site's own product first. Non-null and non-nullable per the
183
+ * contract, so a gateway that knows the field always sends an array.
184
+ *
185
+ * A gateway that does NOT know it yet does not answer without it — it refuses
186
+ * the whole document (`GRAPHQL_VALIDATION_FAILED`), which is why the hook
187
+ * retries with `EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS` instead of falling
188
+ * back to the built-in catalog. That retry is the one response that reaches
189
+ * `normalizeCatalog` with this field absent, and the hardening there is what
190
+ * keeps `catalog.conceptHighlights.filter(...)` from throwing in render.
191
+ */
192
+ conceptHighlights: ExploreConceptHighlight[];
160
193
  }
161
194
  /** `input SendExploreMessageInput`. */
162
195
  interface SendExploreMessageInput {
@@ -202,7 +235,30 @@ interface SendExploreMessageResult {
202
235
  remainingToday?: number | null;
203
236
  }
204
237
  /** Loaded once per page. Safe to serve from the SDK's 5-minute query cache. */
205
- declare const EXPLORE_CATALOG_QUERY = "\n query ExploreCatalog($productSlug: String) {\n exploreCatalog(productSlug: $productSlug) {\n enabled\n products {\n slug\n name\n tagline\n website\n }\n examplePrompts\n welcomeTitle\n welcomeBody\n limits {\n maxMessageLength\n maxTurnsPerConversation\n messagesPerDay\n historyTurns\n }\n captchaRequired\n recaptchaSiteKey\n }\n }\n";
238
+ declare const EXPLORE_CATALOG_QUERY: string;
239
+ /**
240
+ * The same catalog WITHOUT `conceptHighlights`, for a gateway whose supergraph
241
+ * predates the field.
242
+ *
243
+ * This is not belt-and-braces. GraphQL validation is all-or-nothing: a gateway
244
+ * that does not know the field rejects the ENTIRE document and returns no
245
+ * `data` at all, so selecting `conceptHighlights` against an older supergraph
246
+ * costs the whole catalog — `enabled`, `captchaRequired`, `recaptchaSiteKey`,
247
+ * the welcome copy and `limits` included — and not merely the strip. Measured
248
+ * against production:
249
+ *
250
+ * {"errors":[{"message":"Cannot query field \\"conceptHighlights\\" on type
251
+ * \\"ExploreCatalog\\".","extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}]}
252
+ *
253
+ * That window is guaranteed even with perfect deploy ordering, because the prod
254
+ * gateway's CDN edge serves a stale supergraph for ~30-40 minutes after the
255
+ * subgraph publishes. `useExploreChat` therefore retries with this document
256
+ * (see `catalogRejectedTheHighlightField`) rather than dropping to the built-in
257
+ * fallback, whose hardcoded `enabled: true` would ignore the server-side kill
258
+ * switch and whose `captchaRequired: false` would let every send be rejected
259
+ * server-side while the composer looked fine.
260
+ */
261
+ declare const EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS: string;
206
262
  /**
207
263
  * The poll. MUST be issued with `client.query(..., { cache: false })` — the
208
264
  * default 5-minute cache would return the first PENDING snapshot forever and
@@ -221,4 +277,4 @@ interface SendExploreMessageMutationData {
221
277
  sendExploreMessage: SendExploreMessageResult;
222
278
  }
223
279
 
224
- export { type ExploreProduct as E, SEND_EXPLORE_MESSAGE_MUTATION as S, type ExploreLimits as a, type ExploreCatalog as b, type ExploreMessage as c, type ExploreFeedbackRating as d, type ExploreFeedbackReason as e, EXPLORE_CATALOG_QUERY as f, EXPLORE_MESSAGE_QUERY as g, type ExploreCatalogQueryData as h, type ExploreConversation as i, type ExploreCtaKind as j, type ExploreCtaLink as k, type ExploreErrorCode as l, type ExploreMessageQueryData as m, type ExploreMessageRole as n, type ExploreMessageStatus as o, type ExploreReference as p, type SendExploreMessageInput as q, type SendExploreMessageMutationData as r, type SendExploreMessageResult as s };
280
+ export { type ExploreProduct as E, SEND_EXPLORE_MESSAGE_MUTATION as S, type ExploreReference as a, type ExploreLimits as b, type ExploreCatalog as c, type ExploreMessage as d, type ExploreFeedbackRating as e, type ExploreFeedbackReason as f, EXPLORE_CATALOG_QUERY as g, EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS as h, EXPLORE_MESSAGE_QUERY as i, type ExploreCatalogQueryData as j, type ExploreConceptHighlight as k, type ExploreConversation as l, type ExploreCtaKind as m, type ExploreCtaLink as n, type ExploreErrorCode as o, type ExploreMessageQueryData as p, type ExploreMessageRole as q, type ExploreMessageStatus as r, type SendExploreMessageInput as s, type SendExploreMessageMutationData as t, type SendExploreMessageResult as u };
@@ -1,5 +1,5 @@
1
- export { D as DEFAULT_EXPLORE_EXAMPLE_PROMPTS, E as ExploreChatError, a as ExploreChatErrorKind, b as ExploreChatPhase, F as FALLBACK_EXPLORE_LIMITS, U as UseExploreChatOptions, c as UseExploreChatResult, d as classifyExploreError, u as useExploreChat } from '../use-explore-chat-C6Y8JQMj.mjs';
2
- import '../explore-types-CPF1QoMn.mjs';
1
+ export { D as DEFAULT_EXPLORE_EXAMPLE_PROMPTS, E as ExploreChatError, a as ExploreChatErrorKind, b as ExploreChatPhase, F as FALLBACK_EXPLORE_LIMITS, U as UseExploreChatOptions, c as UseExploreChatResult, d as classifyExploreError, u as useExploreChat } from '../use-explore-chat-Dgm-jf96.mjs';
2
+ import '../explore-types-DvQPiL1t.mjs';
3
3
 
4
4
  interface UseSpeechInputOptions {
5
5
  /** Receives settled text as it is recognised. Append it to the draft. */
@@ -1,5 +1,5 @@
1
- export { D as DEFAULT_EXPLORE_EXAMPLE_PROMPTS, E as ExploreChatError, a as ExploreChatErrorKind, b as ExploreChatPhase, F as FALLBACK_EXPLORE_LIMITS, U as UseExploreChatOptions, c as UseExploreChatResult, d as classifyExploreError, u as useExploreChat } from '../use-explore-chat--CxNtkrJ.js';
2
- import '../explore-types-CPF1QoMn.js';
1
+ export { D as DEFAULT_EXPLORE_EXAMPLE_PROMPTS, E as ExploreChatError, a as ExploreChatErrorKind, b as ExploreChatPhase, F as FALLBACK_EXPLORE_LIMITS, U as UseExploreChatOptions, c as UseExploreChatResult, d as classifyExploreError, u as useExploreChat } from '../use-explore-chat-CNEdKBc1.js';
2
+ import '../explore-types-DvQPiL1t.js';
3
3
 
4
4
  interface UseSpeechInputOptions {
5
5
  /** Receives settled text as it is recognised. Append it to the draft. */
@@ -277,11 +277,9 @@ var EXPLORE_OWN_MESSAGE_FIELDS = (
277
277
  }
278
278
  `
279
279
  );
280
- var EXPLORE_CATALOG_QUERY = (
280
+ var EXPLORE_CATALOG_ESTABLISHED_FIELDS = (
281
281
  /* GraphQL */
282
282
  `
283
- query ExploreCatalog($productSlug: String) {
284
- exploreCatalog(productSlug: $productSlug) {
285
283
  enabled
286
284
  products {
287
285
  slug
@@ -299,11 +297,35 @@ var EXPLORE_CATALOG_QUERY = (
299
297
  historyTurns
300
298
  }
301
299
  captchaRequired
302
- recaptchaSiteKey
300
+ recaptchaSiteKey`
301
+ );
302
+ var EXPLORE_CATALOG_HIGHLIGHT_FIELD = (
303
+ /* GraphQL */
304
+ `
305
+ conceptHighlights {
306
+ product
307
+ productName
308
+ title
309
+ url
310
+ imageUrl
311
+ alt
312
+ }`
313
+ );
314
+ function exploreCatalogDocument(extraFields) {
315
+ return (
316
+ /* GraphQL */
317
+ `
318
+ query ExploreCatalog($productSlug: String) {
319
+ exploreCatalog(productSlug: $productSlug) {${EXPLORE_CATALOG_ESTABLISHED_FIELDS}${extraFields}
303
320
  }
304
321
  }
305
322
  `
323
+ );
324
+ }
325
+ var EXPLORE_CATALOG_QUERY = exploreCatalogDocument(
326
+ EXPLORE_CATALOG_HIGHLIGHT_FIELD
306
327
  );
328
+ var EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS = exploreCatalogDocument("");
307
329
  var EXPLORE_MESSAGE_QUERY = (
308
330
  /* GraphQL */
309
331
  `
@@ -667,7 +689,12 @@ function buildFallbackCatalog(examplePrompts, welcome) {
667
689
  welcomeBody: welcome?.body ?? FALLBACK_WELCOME_BODY,
668
690
  limits: FALLBACK_EXPLORE_LIMITS,
669
691
  captchaRequired: false,
670
- recaptchaSiteKey: null
692
+ recaptchaSiteKey: null,
693
+ // Empty, and deliberately not synthesised from `ECOSYSTEM_PRODUCTS`: a concept highlight
694
+ // needs a real published illustration URL, and this SDK has no registry of them. An
695
+ // invented path would render 38 broken frames on the one screen every prerender captures.
696
+ // The strip renders nothing at all when the list is empty, which is the honest fallback.
697
+ conceptHighlights: []
671
698
  };
672
699
  }
673
700
  function normalizeCatalog(raw, fallback) {
@@ -684,9 +711,27 @@ function normalizeCatalog(raw, fallback) {
684
711
  historyTurns: raw.limits?.historyTurns || fallback.limits.historyTurns
685
712
  },
686
713
  captchaRequired: raw.captchaRequired === true,
687
- recaptchaSiteKey: raw.recaptchaSiteKey ?? null
714
+ recaptchaSiteKey: raw.recaptchaSiteKey ?? null,
715
+ // `[ExploreConceptHighlight!]!` on the wire, so a gateway that KNOWS the field always
716
+ // sends an array. The one response that arrives without it is the retry against an older
717
+ // supergraph (`EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS`) — that gateway does not answer
718
+ // the full document at all, it refuses it. Either way
719
+ // `catalog.conceptHighlights.filter(...)` on `undefined` would throw inside render,
720
+ // blanking the welcome screen over a decorative strip.
721
+ conceptHighlights: raw.conceptHighlights?.length ? raw.conceptHighlights : fallback.conceptHighlights
688
722
  };
689
723
  }
724
+ var CATALOG_DOCUMENT_REJECTED_CODES = /* @__PURE__ */ new Set([
725
+ "GRAPHQL_VALIDATION_FAILED",
726
+ "HTTP_400"
727
+ ]);
728
+ function catalogRejectedTheHighlightField(res) {
729
+ if (res.data?.exploreCatalog) return false;
730
+ return (res.errors ?? []).some((error) => {
731
+ const code = error.extensions?.code;
732
+ return typeof code === "string" && CATALOG_DOCUMENT_REJECTED_CODES.has(code) || error.message.includes("conceptHighlights");
733
+ });
734
+ }
690
735
  var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
691
736
  "RATE_LIMITED",
692
737
  "HTTP_429",
@@ -1026,11 +1071,19 @@ function useExploreChat(options = {}) {
1026
1071
  react.useEffect(() => {
1027
1072
  let cancelled = false;
1028
1073
  const load = async () => {
1029
- const res = await client.query(
1074
+ const variables = { productSlug: productSlug ?? null };
1075
+ let res = await client.query(
1030
1076
  EXPLORE_CATALOG_QUERY,
1031
- { productSlug: productSlug ?? null }
1077
+ variables
1032
1078
  );
1033
1079
  if (cancelled || !mountedRef.current) return;
1080
+ if (catalogRejectedTheHighlightField(res)) {
1081
+ res = await client.query(
1082
+ EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS,
1083
+ variables
1084
+ );
1085
+ if (cancelled || !mountedRef.current) return;
1086
+ }
1034
1087
  const raw = res.data?.exploreCatalog;
1035
1088
  if (res.errors?.length || !raw) {
1036
1089
  console.warn(