@burdenoff/website-sdk 2026.922.5 → 2026.923.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.
@@ -0,0 +1,240 @@
1
+ import * as react from 'react';
2
+ import { E as ExploreProduct, a as ExploreReference } from './explore-types-B5sdOC5M.mjs';
3
+ import { Components } from 'react-markdown';
4
+
5
+ /**
6
+ * The answer stylesheet as an element. Safe to mount more than once — the rules are
7
+ * idempotent — so a page that shows answers and nothing else still gets them.
8
+ */
9
+ declare function ExploreAnswerStyles(): react.JSX.Element;
10
+ /**
11
+ * Origins an answer may embed images from: every catalog product's website, apex and
12
+ * `www.` alike (Botlit ships on www.botlit.ai, everything else on the apex, and either can
13
+ * appear in the corpus). https only.
14
+ */
15
+ declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "website">[]): Set<string>;
16
+ /**
17
+ * True when `src` is an https URL on a catalog product site. The backend already strips
18
+ * every image it did not put in the answer; this is the browser's own check, so a model
19
+ * that slips an arbitrary URL past it still cannot make the visitor's browser fetch it.
20
+ */
21
+ declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>): src is string;
22
+ /**
23
+ * An answer as READABLE TEXT, for the clipboard.
24
+ *
25
+ * What counts as "our internal markup artefacts", and why each choice:
26
+ *
27
+ * - **Image embeds go.** `![Concept illustration of …](https://…/01.webp)` is a picture this
28
+ * page injected into the answer; pasted into an email it is a URL and an alt string, and
29
+ * neither is the answer. The caption line the backend writes under it is ordinary prose and
30
+ * stays.
31
+ * - **Links become `label (url)`.** The destination is information the reader may need; the
32
+ * bracket-paren syntax is not. Dropping the URL would quietly strip the page it came from.
33
+ * - **Citation markers `[3]` stay.** They are the only attribution the copied text carries,
34
+ * and they are the answer's own — not something the UI added. The sources they point at are
35
+ * listed right next to the button that copied this.
36
+ * - **Emphasis, headings, blockquote and rule markers go**, because `**` and `###` are
37
+ * instructions to a renderer, not words.
38
+ * - **A table keeps its pipes.** Pasted into a README, an issue or a chat it is a table
39
+ * again; flattened into sentences it is not.
40
+ * - **Fenced code is verbatim**, fences included, minus nothing but the fence lines
41
+ * themselves: stripping `*` or `_` inside a code block would corrupt the code.
42
+ *
43
+ * Built from `message.content` rather than from the rendered DOM: `textContent` on the bubble
44
+ * would fold every paragraph, list item and table cell into one run-on line.
45
+ */
46
+ declare function answerPlainText(markdown: string): string;
47
+ /**
48
+ * The same answer as something to SAY, for read-aloud.
49
+ *
50
+ * It shares every rule above but the two that are right for a paste and wrong for a voice:
51
+ *
52
+ * - **A link is its label.** The clipboard keeps `label (https://…)` because a pasted answer
53
+ * loses the page otherwise. Spoken, that is a URL spelled out character by character in
54
+ * the middle of a sentence — and the prompt asks for two to five links in every answer, so
55
+ * it is not an edge case. The sources are listed under the answer either way.
56
+ * - **A table is read as rows, not as pipes.** `| --- | --- |` is a line of punctuation and
57
+ * the pipes are column rules, neither of which is a word. Each row is read with its cells
58
+ * under their own column headings — the phone layout, out loud — so a plan list still says
59
+ * which number is the price. Comparisons and plan lists are exactly the answers the
60
+ * feature was built for (CONTRACT.md §7), which is what makes this worth a second mode.
61
+ *
62
+ * Everything else stays deliberately identical, including citation markers: they are the
63
+ * answer's own attribution in both, and a listener hearing "two" after a claim is hearing
64
+ * the same thing a reader sees.
65
+ */
66
+ declare function answerSpeechText(markdown: string): string;
67
+ /**
68
+ * The strings the markdown overrides render.
69
+ *
70
+ * Deliberately three fields rather than the whole `ExploreStrings`: the chat and a published
71
+ * answer page carry different chrome vocabularies, and these three are the only words the
72
+ * ANSWER itself needs. `ExploreStrings` satisfies this structurally, so the chat passes its
73
+ * own `T` unchanged.
74
+ */
75
+ interface AnswerRendererStrings {
76
+ /** Accessible name of the scrollable box a table sits in. */
77
+ answerTable: string;
78
+ /** The badge laid over a concept illustration embedded in an answer. */
79
+ conceptIllustration: string;
80
+ /** Read out after a link that opens a new tab. */
81
+ opensInNewTab: string;
82
+ }
83
+ interface ExploreAnswerBodyProps {
84
+ /** The answer, exactly as the harness wrote it: GitHub-flavoured markdown. */
85
+ children: string;
86
+ /** The three strings the overrides render. `ExploreStrings` satisfies this. */
87
+ strings: AnswerRendererStrings;
88
+ /** Origins an inline image may come from — see {@link inlineImageOrigins}. */
89
+ imageOrigins: ReadonlySet<string>;
90
+ /**
91
+ * False while the answer is still streaming, which suppresses its images: a partial is cut
92
+ * mid-token, so an image URL in it may be half-written, and the backend has not yet dropped
93
+ * the images it does not allow. A settled answer — every published one — is `true`.
94
+ */
95
+ enabled?: boolean;
96
+ /**
97
+ * Extra markdown overrides, merged OVER the shared ones.
98
+ *
99
+ * Pass a module-level constant, never an object built during render: react-markdown uses an
100
+ * override as the element TYPE, so a component re-created on every render remounts
101
+ * everything under it — an image would be fetched again, and one that had been hidden for
102
+ * failing to load would come back.
103
+ */
104
+ components?: Components;
105
+ /** Extra classes for the prose wrapper. */
106
+ className?: string;
107
+ }
108
+ /**
109
+ * An answer, rendered.
110
+ *
111
+ * The wrapper carries `boff-prose` and `boff-explore-body`: the first styles ordinary
112
+ * markdown, the second is what every rule in {@link EXPLORE_ANSWER_CSS} is scoped to. A page
113
+ * that renders this must also mount `<ProseStyles />` and `<ExploreAnswerStyles />`, or a
114
+ * wide table has nothing holding it inside the page.
115
+ *
116
+ * The set of images that failed to load is held HERE rather than in each image, so a source
117
+ * that 404s stays hidden even when the markdown re-renders from scratch underneath it.
118
+ */
119
+ declare function ExploreAnswerBody({ children, strings, imageOrigins, enabled, components, className, }: ExploreAnswerBodyProps): react.JSX.Element;
120
+
121
+ interface ExplorePageProps {
122
+ /**
123
+ * SEO metadata. Every field is optional and each one falls back to a default
124
+ * derived from {@link ExplorePageProps.productSlug}, so a site that passes
125
+ * nothing still gets a correct head — see `defaultExploreSeo`.
126
+ *
127
+ * Pass this only to say something the default cannot. All 38 sites used to
128
+ * hard-code the same two sentences here, which is how every one of them ended
129
+ * up announcing "Explore Burdenoff products with AI" — product-first on a page
130
+ * whose whole purpose is to ask the visitor about their problem, and simply
131
+ * the wrong brand on 37 of them.
132
+ */
133
+ seo?: {
134
+ title?: string;
135
+ description?: string;
136
+ keywords?: string;
137
+ image?: string;
138
+ url?: string;
139
+ };
140
+ /** Slug of the product site this page lives on — a soft hint to the harness. */
141
+ productSlug?: string;
142
+ /**
143
+ * Example prompts shown when `exploreCatalog` supplies none. Resolution
144
+ * order: catalog → this prop → the SDK's built-in list.
145
+ */
146
+ examplePrompts?: string[];
147
+ /** Extra classes for the page root. */
148
+ className?: string;
149
+ /**
150
+ * `viewport` (default) pins the chat to the viewport with an internally
151
+ * scrolling transcript — the right feel for a dedicated `/explore` route.
152
+ * `auto` lets the page grow, for embedding inside a longer page.
153
+ *
154
+ * `viewport` also means "this component IS the page": only in that mode does it take over
155
+ * `history.scrollRestoration` and correct a restored scroll offset that has parked the
156
+ * page in the site footer. An `auto` embed never touches its host page's scrolling.
157
+ */
158
+ heightMode?: "viewport" | "auto";
159
+ /**
160
+ * Chrome to subtract in `viewport` mode — the site header height. A number is treated as
161
+ * pixels. Default `0`.
162
+ *
163
+ * Pre-measurement fallback ONLY. Once mounted the pane measures its own top, so an
164
+ * approximate value is fine and the keyboard case is handled; see `measurePane` in
165
+ * `ExplorePage`.
166
+ */
167
+ viewportOffset?: string | number;
168
+ /** SPA navigation for internal CTA paths. Falls back to a plain link. */
169
+ onNavigate?: (path: string) => void;
170
+ /** Fired with the message text every time a send is accepted. */
171
+ onSend?: (message: string) => void;
172
+ /** Overrides `exploreCatalog.welcomeTitle` — also the page `<h1>`. */
173
+ welcomeTitle?: string;
174
+ /** Overrides `exploreCatalog.welcomeBody`. */
175
+ welcomeBody?: string;
176
+ /**
177
+ * BCP-47 locale to render in. Defaults to the document's own `lang`, then the browser.
178
+ * Only the six the products ship are recognised (en, zh, hi, es, ar, ta); anything else
179
+ * falls back to English.
180
+ */
181
+ locale?: string;
182
+ }
183
+ /**
184
+ * The product's own logo, derived from the reference's origin.
185
+ *
186
+ * Every Burdenoff product site serves `/favicon.svg`, so the logo needs no registry and no
187
+ * bundled assets — the reference URL already names the product. Deliberately NOT
188
+ * `/favicon-512.png`: that path returns 200 with `text/html` because the SPA fallback
189
+ * answers unknown paths, so it would render as a broken image rather than 404 into the
190
+ * fallback below.
191
+ */
192
+ declare function logoUrlOf(url: string): string | null;
193
+ /**
194
+ * The kinds of page an answer can cite.
195
+ *
196
+ * `PAGE` is the catch-all for a catalog site's own marketing pages, so the list is total for
197
+ * anything on one of our origins; a reference that is NOT on a catalog origin gets no kind at
198
+ * all (see `referenceKindOf`).
199
+ */
200
+ type ExploreReferenceKind = "CONCEPT" | "DOCS" | "PRICING" | "USE_CASE" | "PAGE";
201
+ /**
202
+ * What KIND of page a reference points at, decided from its URL alone.
203
+ *
204
+ * Deliberately not from the title: titles are model-written prose in six languages, so
205
+ * "Pricing" in one answer is "Precios" in the next and "Plans and pricing" in the one after.
206
+ * The URL is the only part of a reference nobody paraphrases.
207
+ *
208
+ * Reasoned against the catalog origins exactly as `isInlineImageAllowed` is: a URL that is
209
+ * not on a product site we list gets `null` rather than a guessed label, because the whole
210
+ * value of the row is that it says what the evidence IS.
211
+ */
212
+ declare function referenceKindOf(url: string, origins: ReadonlySet<string>): ExploreReferenceKind | null;
213
+ /**
214
+ * Apex origin → the product's display name, built from the catalog the same way
215
+ * `inlineImageOrigins` builds its allow-list.
216
+ *
217
+ * The display name matters: `ExploreReference.product` carries the slug, so a row built from
218
+ * it says "vibecontrols" next to a page that calls itself VibeControls everywhere else.
219
+ */
220
+ declare function productNamesByOrigin(products: readonly Pick<ExploreProduct, "name" | "website">[]): Map<string, string>;
221
+ /** The shape of one answer's evidence, as the chips render it. */
222
+ interface ExploreProvenance {
223
+ /** Every reference the answer cites, including any we could not classify. */
224
+ total: number;
225
+ /** Kinds present, in `REFERENCE_KIND_ORDER`, with how many sources each covers. */
226
+ kinds: Array<{
227
+ kind: ExploreReferenceKind;
228
+ count: number;
229
+ }>;
230
+ /** Product names, in the order the answer first cited them. */
231
+ products: string[];
232
+ }
233
+ /**
234
+ * Summarise what an answer is grounded in. Pure, and derived entirely from `references` —
235
+ * there is no backend field behind this row.
236
+ */
237
+ declare function summariseReferences(references: readonly ExploreReference[], origins: ReadonlySet<string>, productNames: ReadonlyMap<string, string>): ExploreProvenance;
238
+ declare function ExplorePage({ seo, productSlug, examplePrompts, className, heightMode, viewportOffset, onNavigate, onSend, welcomeTitle, welcomeBody, locale, }: ExplorePageProps): react.JSX.Element;
239
+
240
+ export { type AnswerRendererStrings as A, ExploreAnswerBody as E, type ExploreAnswerBodyProps as a, ExploreAnswerStyles as b, ExplorePage as c, type ExplorePageProps as d, answerPlainText as e, answerSpeechText as f, isInlineImageAllowed as g, type ExploreProvenance as h, inlineImageOrigins as i, type ExploreReferenceKind as j, logoUrlOf as l, productNamesByOrigin as p, referenceKindOf as r, summariseReferences as s };
@@ -0,0 +1,240 @@
1
+ import * as react from 'react';
2
+ import { E as ExploreProduct, a as ExploreReference } from './explore-types-B5sdOC5M.js';
3
+ import { Components } from 'react-markdown';
4
+
5
+ /**
6
+ * The answer stylesheet as an element. Safe to mount more than once — the rules are
7
+ * idempotent — so a page that shows answers and nothing else still gets them.
8
+ */
9
+ declare function ExploreAnswerStyles(): react.JSX.Element;
10
+ /**
11
+ * Origins an answer may embed images from: every catalog product's website, apex and
12
+ * `www.` alike (Botlit ships on www.botlit.ai, everything else on the apex, and either can
13
+ * appear in the corpus). https only.
14
+ */
15
+ declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "website">[]): Set<string>;
16
+ /**
17
+ * True when `src` is an https URL on a catalog product site. The backend already strips
18
+ * every image it did not put in the answer; this is the browser's own check, so a model
19
+ * that slips an arbitrary URL past it still cannot make the visitor's browser fetch it.
20
+ */
21
+ declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>): src is string;
22
+ /**
23
+ * An answer as READABLE TEXT, for the clipboard.
24
+ *
25
+ * What counts as "our internal markup artefacts", and why each choice:
26
+ *
27
+ * - **Image embeds go.** `![Concept illustration of …](https://…/01.webp)` is a picture this
28
+ * page injected into the answer; pasted into an email it is a URL and an alt string, and
29
+ * neither is the answer. The caption line the backend writes under it is ordinary prose and
30
+ * stays.
31
+ * - **Links become `label (url)`.** The destination is information the reader may need; the
32
+ * bracket-paren syntax is not. Dropping the URL would quietly strip the page it came from.
33
+ * - **Citation markers `[3]` stay.** They are the only attribution the copied text carries,
34
+ * and they are the answer's own — not something the UI added. The sources they point at are
35
+ * listed right next to the button that copied this.
36
+ * - **Emphasis, headings, blockquote and rule markers go**, because `**` and `###` are
37
+ * instructions to a renderer, not words.
38
+ * - **A table keeps its pipes.** Pasted into a README, an issue or a chat it is a table
39
+ * again; flattened into sentences it is not.
40
+ * - **Fenced code is verbatim**, fences included, minus nothing but the fence lines
41
+ * themselves: stripping `*` or `_` inside a code block would corrupt the code.
42
+ *
43
+ * Built from `message.content` rather than from the rendered DOM: `textContent` on the bubble
44
+ * would fold every paragraph, list item and table cell into one run-on line.
45
+ */
46
+ declare function answerPlainText(markdown: string): string;
47
+ /**
48
+ * The same answer as something to SAY, for read-aloud.
49
+ *
50
+ * It shares every rule above but the two that are right for a paste and wrong for a voice:
51
+ *
52
+ * - **A link is its label.** The clipboard keeps `label (https://…)` because a pasted answer
53
+ * loses the page otherwise. Spoken, that is a URL spelled out character by character in
54
+ * the middle of a sentence — and the prompt asks for two to five links in every answer, so
55
+ * it is not an edge case. The sources are listed under the answer either way.
56
+ * - **A table is read as rows, not as pipes.** `| --- | --- |` is a line of punctuation and
57
+ * the pipes are column rules, neither of which is a word. Each row is read with its cells
58
+ * under their own column headings — the phone layout, out loud — so a plan list still says
59
+ * which number is the price. Comparisons and plan lists are exactly the answers the
60
+ * feature was built for (CONTRACT.md §7), which is what makes this worth a second mode.
61
+ *
62
+ * Everything else stays deliberately identical, including citation markers: they are the
63
+ * answer's own attribution in both, and a listener hearing "two" after a claim is hearing
64
+ * the same thing a reader sees.
65
+ */
66
+ declare function answerSpeechText(markdown: string): string;
67
+ /**
68
+ * The strings the markdown overrides render.
69
+ *
70
+ * Deliberately three fields rather than the whole `ExploreStrings`: the chat and a published
71
+ * answer page carry different chrome vocabularies, and these three are the only words the
72
+ * ANSWER itself needs. `ExploreStrings` satisfies this structurally, so the chat passes its
73
+ * own `T` unchanged.
74
+ */
75
+ interface AnswerRendererStrings {
76
+ /** Accessible name of the scrollable box a table sits in. */
77
+ answerTable: string;
78
+ /** The badge laid over a concept illustration embedded in an answer. */
79
+ conceptIllustration: string;
80
+ /** Read out after a link that opens a new tab. */
81
+ opensInNewTab: string;
82
+ }
83
+ interface ExploreAnswerBodyProps {
84
+ /** The answer, exactly as the harness wrote it: GitHub-flavoured markdown. */
85
+ children: string;
86
+ /** The three strings the overrides render. `ExploreStrings` satisfies this. */
87
+ strings: AnswerRendererStrings;
88
+ /** Origins an inline image may come from — see {@link inlineImageOrigins}. */
89
+ imageOrigins: ReadonlySet<string>;
90
+ /**
91
+ * False while the answer is still streaming, which suppresses its images: a partial is cut
92
+ * mid-token, so an image URL in it may be half-written, and the backend has not yet dropped
93
+ * the images it does not allow. A settled answer — every published one — is `true`.
94
+ */
95
+ enabled?: boolean;
96
+ /**
97
+ * Extra markdown overrides, merged OVER the shared ones.
98
+ *
99
+ * Pass a module-level constant, never an object built during render: react-markdown uses an
100
+ * override as the element TYPE, so a component re-created on every render remounts
101
+ * everything under it — an image would be fetched again, and one that had been hidden for
102
+ * failing to load would come back.
103
+ */
104
+ components?: Components;
105
+ /** Extra classes for the prose wrapper. */
106
+ className?: string;
107
+ }
108
+ /**
109
+ * An answer, rendered.
110
+ *
111
+ * The wrapper carries `boff-prose` and `boff-explore-body`: the first styles ordinary
112
+ * markdown, the second is what every rule in {@link EXPLORE_ANSWER_CSS} is scoped to. A page
113
+ * that renders this must also mount `<ProseStyles />` and `<ExploreAnswerStyles />`, or a
114
+ * wide table has nothing holding it inside the page.
115
+ *
116
+ * The set of images that failed to load is held HERE rather than in each image, so a source
117
+ * that 404s stays hidden even when the markdown re-renders from scratch underneath it.
118
+ */
119
+ declare function ExploreAnswerBody({ children, strings, imageOrigins, enabled, components, className, }: ExploreAnswerBodyProps): react.JSX.Element;
120
+
121
+ interface ExplorePageProps {
122
+ /**
123
+ * SEO metadata. Every field is optional and each one falls back to a default
124
+ * derived from {@link ExplorePageProps.productSlug}, so a site that passes
125
+ * nothing still gets a correct head — see `defaultExploreSeo`.
126
+ *
127
+ * Pass this only to say something the default cannot. All 38 sites used to
128
+ * hard-code the same two sentences here, which is how every one of them ended
129
+ * up announcing "Explore Burdenoff products with AI" — product-first on a page
130
+ * whose whole purpose is to ask the visitor about their problem, and simply
131
+ * the wrong brand on 37 of them.
132
+ */
133
+ seo?: {
134
+ title?: string;
135
+ description?: string;
136
+ keywords?: string;
137
+ image?: string;
138
+ url?: string;
139
+ };
140
+ /** Slug of the product site this page lives on — a soft hint to the harness. */
141
+ productSlug?: string;
142
+ /**
143
+ * Example prompts shown when `exploreCatalog` supplies none. Resolution
144
+ * order: catalog → this prop → the SDK's built-in list.
145
+ */
146
+ examplePrompts?: string[];
147
+ /** Extra classes for the page root. */
148
+ className?: string;
149
+ /**
150
+ * `viewport` (default) pins the chat to the viewport with an internally
151
+ * scrolling transcript — the right feel for a dedicated `/explore` route.
152
+ * `auto` lets the page grow, for embedding inside a longer page.
153
+ *
154
+ * `viewport` also means "this component IS the page": only in that mode does it take over
155
+ * `history.scrollRestoration` and correct a restored scroll offset that has parked the
156
+ * page in the site footer. An `auto` embed never touches its host page's scrolling.
157
+ */
158
+ heightMode?: "viewport" | "auto";
159
+ /**
160
+ * Chrome to subtract in `viewport` mode — the site header height. A number is treated as
161
+ * pixels. Default `0`.
162
+ *
163
+ * Pre-measurement fallback ONLY. Once mounted the pane measures its own top, so an
164
+ * approximate value is fine and the keyboard case is handled; see `measurePane` in
165
+ * `ExplorePage`.
166
+ */
167
+ viewportOffset?: string | number;
168
+ /** SPA navigation for internal CTA paths. Falls back to a plain link. */
169
+ onNavigate?: (path: string) => void;
170
+ /** Fired with the message text every time a send is accepted. */
171
+ onSend?: (message: string) => void;
172
+ /** Overrides `exploreCatalog.welcomeTitle` — also the page `<h1>`. */
173
+ welcomeTitle?: string;
174
+ /** Overrides `exploreCatalog.welcomeBody`. */
175
+ welcomeBody?: string;
176
+ /**
177
+ * BCP-47 locale to render in. Defaults to the document's own `lang`, then the browser.
178
+ * Only the six the products ship are recognised (en, zh, hi, es, ar, ta); anything else
179
+ * falls back to English.
180
+ */
181
+ locale?: string;
182
+ }
183
+ /**
184
+ * The product's own logo, derived from the reference's origin.
185
+ *
186
+ * Every Burdenoff product site serves `/favicon.svg`, so the logo needs no registry and no
187
+ * bundled assets — the reference URL already names the product. Deliberately NOT
188
+ * `/favicon-512.png`: that path returns 200 with `text/html` because the SPA fallback
189
+ * answers unknown paths, so it would render as a broken image rather than 404 into the
190
+ * fallback below.
191
+ */
192
+ declare function logoUrlOf(url: string): string | null;
193
+ /**
194
+ * The kinds of page an answer can cite.
195
+ *
196
+ * `PAGE` is the catch-all for a catalog site's own marketing pages, so the list is total for
197
+ * anything on one of our origins; a reference that is NOT on a catalog origin gets no kind at
198
+ * all (see `referenceKindOf`).
199
+ */
200
+ type ExploreReferenceKind = "CONCEPT" | "DOCS" | "PRICING" | "USE_CASE" | "PAGE";
201
+ /**
202
+ * What KIND of page a reference points at, decided from its URL alone.
203
+ *
204
+ * Deliberately not from the title: titles are model-written prose in six languages, so
205
+ * "Pricing" in one answer is "Precios" in the next and "Plans and pricing" in the one after.
206
+ * The URL is the only part of a reference nobody paraphrases.
207
+ *
208
+ * Reasoned against the catalog origins exactly as `isInlineImageAllowed` is: a URL that is
209
+ * not on a product site we list gets `null` rather than a guessed label, because the whole
210
+ * value of the row is that it says what the evidence IS.
211
+ */
212
+ declare function referenceKindOf(url: string, origins: ReadonlySet<string>): ExploreReferenceKind | null;
213
+ /**
214
+ * Apex origin → the product's display name, built from the catalog the same way
215
+ * `inlineImageOrigins` builds its allow-list.
216
+ *
217
+ * The display name matters: `ExploreReference.product` carries the slug, so a row built from
218
+ * it says "vibecontrols" next to a page that calls itself VibeControls everywhere else.
219
+ */
220
+ declare function productNamesByOrigin(products: readonly Pick<ExploreProduct, "name" | "website">[]): Map<string, string>;
221
+ /** The shape of one answer's evidence, as the chips render it. */
222
+ interface ExploreProvenance {
223
+ /** Every reference the answer cites, including any we could not classify. */
224
+ total: number;
225
+ /** Kinds present, in `REFERENCE_KIND_ORDER`, with how many sources each covers. */
226
+ kinds: Array<{
227
+ kind: ExploreReferenceKind;
228
+ count: number;
229
+ }>;
230
+ /** Product names, in the order the answer first cited them. */
231
+ products: string[];
232
+ }
233
+ /**
234
+ * Summarise what an answer is grounded in. Pure, and derived entirely from `references` —
235
+ * there is no backend field behind this row.
236
+ */
237
+ declare function summariseReferences(references: readonly ExploreReference[], origins: ReadonlySet<string>, productNames: ReadonlyMap<string, string>): ExploreProvenance;
238
+ declare function ExplorePage({ seo, productSlug, examplePrompts, className, heightMode, viewportOffset, onNavigate, onSend, welcomeTitle, welcomeBody, locale, }: ExplorePageProps): react.JSX.Element;
239
+
240
+ export { type AnswerRendererStrings as A, ExploreAnswerBody as E, type ExploreAnswerBodyProps as a, ExploreAnswerStyles as b, ExplorePage as c, type ExplorePageProps as d, answerPlainText as e, answerSpeechText as f, isInlineImageAllowed as g, type ExploreProvenance as h, inlineImageOrigins as i, type ExploreReferenceKind as j, logoUrlOf as l, productNamesByOrigin as p, referenceKindOf as r, summariseReferences as s };
@@ -43,7 +43,7 @@ type ExploreCtaKind = "CONTACT" | "PARTNERS" | "PRICING" | "SERVICES" | "WAITLIS
43
43
  * gateway emits when it rate-limits at the edge; the SDK client also produces
44
44
  * `TIMEOUT` / `NETWORK_ERROR` for transport failures.
45
45
  */
46
- type ExploreErrorCode = "EXPLORE_DISABLED" | "RATE_LIMITED" | "EXPLORE_GLOBAL_LIMIT" | "CAPTCHA_REQUIRED" | "CAPTCHA_FAILED" | "MESSAGE_TOO_LONG" | "MESSAGE_EMPTY" | "CONVERSATION_LIMIT" | "CONVERSATION_BUSY" | "UPSTREAM_UNAVAILABLE" | "NOT_FOUND" | "HTTP_429" | "TIMEOUT" | "NETWORK_ERROR";
46
+ type ExploreErrorCode = "EXPLORE_DISABLED" | "RATE_LIMITED" | "EXPLORE_GLOBAL_LIMIT" | "CAPTCHA_REQUIRED" | "CAPTCHA_FAILED" | "MESSAGE_TOO_LONG" | "MESSAGE_EMPTY" | "CONVERSATION_LIMIT" | "CONVERSATION_BUSY" | "UPSTREAM_UNAVAILABLE" | "NOT_FOUND" | "INVALID_EMAIL" | "ESCALATION_FAILED" | "HTTP_429" | "TIMEOUT" | "NETWORK_ERROR";
47
47
  /** `type ExploreReference` — a cited source page for an answer. */
48
48
  interface ExploreReference {
49
49
  url: string;
@@ -276,5 +276,78 @@ interface ExploreMessageQueryData {
276
276
  interface SendExploreMessageMutationData {
277
277
  sendExploreMessage: SendExploreMessageResult;
278
278
  }
279
+ /**
280
+ * `input EscalateExploreConversationInput`.
281
+ *
282
+ * `email` is the only thing the visitor is asked for. It is **personal data**: it belongs in
283
+ * this input and nowhere else — never in `localStorage`, never in an `ExploreMessage`, never
284
+ * in an analytics call. See the note on {@link ESCALATE_EXPLORE_CONVERSATION_MUTATION}.
285
+ */
286
+ interface EscalateExploreConversationInput {
287
+ /** The thread being handed over. A share (`?c=`) secret is NOT one. */
288
+ conversationToken: string;
289
+ /** Validated strictly server-side; a bad address comes back as `INVALID_EMAIL`. */
290
+ email: string;
291
+ /** Optional free text. Hard-capped server-side at 2000 and TRUNCATED, never rejected. */
292
+ note?: string | null;
293
+ /** Only when `exploreCatalog.captchaRequired`. Same v3 action as a chat message. */
294
+ recaptchaToken?: string;
295
+ /** `PlatformProduct` id of the site, so the ticket reaches that product's support inbox. */
296
+ productId?: string;
297
+ }
298
+ /** `type ExploreEscalation` — the handover on record, as the SERVER holds it. */
299
+ interface ExploreEscalation {
300
+ /**
301
+ * Where the reply is actually going.
302
+ *
303
+ * On a repeat submit this is the address the FIRST handover used, not the one just typed
304
+ * into the box — so this is the value to show back, never the local draft.
305
+ */
306
+ email: string;
307
+ createdAt: string;
308
+ }
309
+ /** `type EscalateExploreConversationResult`. */
310
+ interface EscalateExploreConversationResult {
311
+ /**
312
+ * False when a handover already existed — the double-clicked button, the retried request,
313
+ * the reloaded page.
314
+ *
315
+ * **Not an error.** The conversation is with a human either way, and the UI must read as
316
+ * success on both values. The server enforces one handover per conversation with a UNIQUE
317
+ * index rather than a check-then-insert, so two concurrent submits produce one ticket and
318
+ * one of them gets this.
319
+ */
320
+ created: boolean;
321
+ escalation: ExploreEscalation;
322
+ }
323
+ /**
324
+ * Hand this conversation to a person.
325
+ *
326
+ * Awaited and NOT fire-and-forget (unlike {@link RECORD_EXPLORE_CLICK_MUTATION}): it is the
327
+ * one explore write that throws, because it ends with a visitor being told a person has
328
+ * their question. "We passed it on" must never be shown for a handover that did not happen.
329
+ *
330
+ * **The address the visitor types is personal data.** It travels in these variables and
331
+ * stops there. Do not persist it, do not fold it into the transcript (`messages` is what the
332
+ * hook writes to localStorage), and do not pass it to `onSend` or any analytics call — the
333
+ * server already has everything a support agent needs.
334
+ */
335
+ declare const ESCALATE_EXPLORE_CONVERSATION_MUTATION = "\n mutation EscalateExploreConversation(\n $input: EscalateExploreConversationInput!\n ) {\n escalateExploreConversation(input: $input) {\n created\n escalation {\n email\n createdAt\n }\n }\n }\n";
336
+ /**
337
+ * How many characters the note field accepts before it stops taking more.
338
+ *
339
+ * Mirrors the server's hard cap so the box does not invite text it will silently truncate.
340
+ * Same status as {@link EXPLORE_FEEDBACK_COMMENT_SOFT_MAX}: a courtesy, not the limit that
341
+ * holds.
342
+ */
343
+ declare const EXPLORE_ESCALATION_NOTE_SOFT_MAX = 2000;
344
+ /**
345
+ * Whether `value` is worth sending to the mutation at all.
346
+ *
347
+ * A local pre-flight, never a promise of deliverability. Keep it a SUBSET of what the server
348
+ * accepts: a client that rejects something the server would have taken costs the visitor
349
+ * their answer with nobody able to see why.
350
+ */
351
+ declare function looksLikeDeliverableEmail(value: string): boolean;
279
352
 
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 };
353
+ export { looksLikeDeliverableEmail as A, 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, ESCALATE_EXPLORE_CONVERSATION_MUTATION as g, EXPLORE_CATALOG_QUERY as h, EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS as i, EXPLORE_ESCALATION_NOTE_SOFT_MAX as j, EXPLORE_MESSAGE_QUERY as k, type EscalateExploreConversationInput as l, type EscalateExploreConversationResult as m, type ExploreCatalogQueryData as n, type ExploreConceptHighlight as o, type ExploreConversation as p, type ExploreCtaKind as q, type ExploreCtaLink as r, type ExploreErrorCode as s, type ExploreEscalation as t, type ExploreMessageQueryData as u, type ExploreMessageRole as v, type ExploreMessageStatus as w, type SendExploreMessageInput as x, type SendExploreMessageMutationData as y, type SendExploreMessageResult as z };
@@ -43,7 +43,7 @@ type ExploreCtaKind = "CONTACT" | "PARTNERS" | "PRICING" | "SERVICES" | "WAITLIS
43
43
  * gateway emits when it rate-limits at the edge; the SDK client also produces
44
44
  * `TIMEOUT` / `NETWORK_ERROR` for transport failures.
45
45
  */
46
- type ExploreErrorCode = "EXPLORE_DISABLED" | "RATE_LIMITED" | "EXPLORE_GLOBAL_LIMIT" | "CAPTCHA_REQUIRED" | "CAPTCHA_FAILED" | "MESSAGE_TOO_LONG" | "MESSAGE_EMPTY" | "CONVERSATION_LIMIT" | "CONVERSATION_BUSY" | "UPSTREAM_UNAVAILABLE" | "NOT_FOUND" | "HTTP_429" | "TIMEOUT" | "NETWORK_ERROR";
46
+ type ExploreErrorCode = "EXPLORE_DISABLED" | "RATE_LIMITED" | "EXPLORE_GLOBAL_LIMIT" | "CAPTCHA_REQUIRED" | "CAPTCHA_FAILED" | "MESSAGE_TOO_LONG" | "MESSAGE_EMPTY" | "CONVERSATION_LIMIT" | "CONVERSATION_BUSY" | "UPSTREAM_UNAVAILABLE" | "NOT_FOUND" | "INVALID_EMAIL" | "ESCALATION_FAILED" | "HTTP_429" | "TIMEOUT" | "NETWORK_ERROR";
47
47
  /** `type ExploreReference` — a cited source page for an answer. */
48
48
  interface ExploreReference {
49
49
  url: string;
@@ -276,5 +276,78 @@ interface ExploreMessageQueryData {
276
276
  interface SendExploreMessageMutationData {
277
277
  sendExploreMessage: SendExploreMessageResult;
278
278
  }
279
+ /**
280
+ * `input EscalateExploreConversationInput`.
281
+ *
282
+ * `email` is the only thing the visitor is asked for. It is **personal data**: it belongs in
283
+ * this input and nowhere else — never in `localStorage`, never in an `ExploreMessage`, never
284
+ * in an analytics call. See the note on {@link ESCALATE_EXPLORE_CONVERSATION_MUTATION}.
285
+ */
286
+ interface EscalateExploreConversationInput {
287
+ /** The thread being handed over. A share (`?c=`) secret is NOT one. */
288
+ conversationToken: string;
289
+ /** Validated strictly server-side; a bad address comes back as `INVALID_EMAIL`. */
290
+ email: string;
291
+ /** Optional free text. Hard-capped server-side at 2000 and TRUNCATED, never rejected. */
292
+ note?: string | null;
293
+ /** Only when `exploreCatalog.captchaRequired`. Same v3 action as a chat message. */
294
+ recaptchaToken?: string;
295
+ /** `PlatformProduct` id of the site, so the ticket reaches that product's support inbox. */
296
+ productId?: string;
297
+ }
298
+ /** `type ExploreEscalation` — the handover on record, as the SERVER holds it. */
299
+ interface ExploreEscalation {
300
+ /**
301
+ * Where the reply is actually going.
302
+ *
303
+ * On a repeat submit this is the address the FIRST handover used, not the one just typed
304
+ * into the box — so this is the value to show back, never the local draft.
305
+ */
306
+ email: string;
307
+ createdAt: string;
308
+ }
309
+ /** `type EscalateExploreConversationResult`. */
310
+ interface EscalateExploreConversationResult {
311
+ /**
312
+ * False when a handover already existed — the double-clicked button, the retried request,
313
+ * the reloaded page.
314
+ *
315
+ * **Not an error.** The conversation is with a human either way, and the UI must read as
316
+ * success on both values. The server enforces one handover per conversation with a UNIQUE
317
+ * index rather than a check-then-insert, so two concurrent submits produce one ticket and
318
+ * one of them gets this.
319
+ */
320
+ created: boolean;
321
+ escalation: ExploreEscalation;
322
+ }
323
+ /**
324
+ * Hand this conversation to a person.
325
+ *
326
+ * Awaited and NOT fire-and-forget (unlike {@link RECORD_EXPLORE_CLICK_MUTATION}): it is the
327
+ * one explore write that throws, because it ends with a visitor being told a person has
328
+ * their question. "We passed it on" must never be shown for a handover that did not happen.
329
+ *
330
+ * **The address the visitor types is personal data.** It travels in these variables and
331
+ * stops there. Do not persist it, do not fold it into the transcript (`messages` is what the
332
+ * hook writes to localStorage), and do not pass it to `onSend` or any analytics call — the
333
+ * server already has everything a support agent needs.
334
+ */
335
+ declare const ESCALATE_EXPLORE_CONVERSATION_MUTATION = "\n mutation EscalateExploreConversation(\n $input: EscalateExploreConversationInput!\n ) {\n escalateExploreConversation(input: $input) {\n created\n escalation {\n email\n createdAt\n }\n }\n }\n";
336
+ /**
337
+ * How many characters the note field accepts before it stops taking more.
338
+ *
339
+ * Mirrors the server's hard cap so the box does not invite text it will silently truncate.
340
+ * Same status as {@link EXPLORE_FEEDBACK_COMMENT_SOFT_MAX}: a courtesy, not the limit that
341
+ * holds.
342
+ */
343
+ declare const EXPLORE_ESCALATION_NOTE_SOFT_MAX = 2000;
344
+ /**
345
+ * Whether `value` is worth sending to the mutation at all.
346
+ *
347
+ * A local pre-flight, never a promise of deliverability. Keep it a SUBSET of what the server
348
+ * accepts: a client that rejects something the server would have taken costs the visitor
349
+ * their answer with nobody able to see why.
350
+ */
351
+ declare function looksLikeDeliverableEmail(value: string): boolean;
279
352
 
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 };
353
+ export { looksLikeDeliverableEmail as A, 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, ESCALATE_EXPLORE_CONVERSATION_MUTATION as g, EXPLORE_CATALOG_QUERY as h, EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS as i, EXPLORE_ESCALATION_NOTE_SOFT_MAX as j, EXPLORE_MESSAGE_QUERY as k, type EscalateExploreConversationInput as l, type EscalateExploreConversationResult as m, type ExploreCatalogQueryData as n, type ExploreConceptHighlight as o, type ExploreConversation as p, type ExploreCtaKind as q, type ExploreCtaLink as r, type ExploreErrorCode as s, type ExploreEscalation as t, type ExploreMessageQueryData as u, type ExploreMessageRole as v, type ExploreMessageStatus as w, type SendExploreMessageInput as x, type SendExploreMessageMutationData as y, type SendExploreMessageResult as z };