@burdenoff/website-sdk 2026.922.4 → 2026.923.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -17,10 +17,12 @@ export { WebsiteSwitcher, WebsiteSwitcherProps } from './components/website-swit
17
17
  export { LaunchCountdown, LaunchCountdownProps } from './components/launch-countdown.mjs';
18
18
  export { ProblemSolved, ProblemsSolvedSection, ProblemsSolvedSectionProps } from './components/problems-solved.mjs';
19
19
  export { DetectedPlatform, ElectronDownloadLink, ElectronDownloadLinkProps, detectElectronPlatform } from './components/electron-download.mjs';
20
- export { ExplorePage, ExplorePageProps } from './components/explore.mjs';
20
+ import { Components } from 'react-markdown';
21
+ import { A as AnswerRendererStrings } from './explore-D25zoXy9.mjs';
22
+ export { E as ExploreAnswerBody, a as ExploreAnswerBodyProps, b as ExploreAnswerStyles, c as ExplorePage, d as ExplorePageProps, e as answerPlainText, f as answerSpeechText, i as inlineImageOrigins, g as isInlineImageAllowed } from './explore-D25zoXy9.mjs';
21
23
  export { ExploreCta, ExploreCtaProps } from './components/explore-cta.mjs';
22
- export { g as EXPLORE_CATALOG_QUERY, h as EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS, i as EXPLORE_MESSAGE_QUERY, c as ExploreCatalog, j as ExploreCatalogQueryData, k as ExploreConceptHighlight, l as ExploreConversation, m as ExploreCtaKind, n as ExploreCtaLink, o as ExploreErrorCode, b as ExploreLimits, d as ExploreMessage, p as ExploreMessageQueryData, q as ExploreMessageRole, r as ExploreMessageStatus, E as ExploreProduct, a as ExploreReference, S as SEND_EXPLORE_MESSAGE_MUTATION, s as SendExploreMessageInput, t as SendExploreMessageMutationData, u as SendExploreMessageResult } from './explore-types-DvQPiL1t.mjs';
23
- 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';
24
+ export { g as ESCALATE_EXPLORE_CONVERSATION_MUTATION, h as EXPLORE_CATALOG_QUERY, i as EXPLORE_CATALOG_QUERY_WITHOUT_HIGHLIGHTS, j as EXPLORE_ESCALATION_NOTE_SOFT_MAX, k as EXPLORE_MESSAGE_QUERY, l as EscalateExploreConversationInput, m as EscalateExploreConversationResult, c as ExploreCatalog, n as ExploreCatalogQueryData, o as ExploreConceptHighlight, p as ExploreConversation, q as ExploreCtaKind, r as ExploreCtaLink, s as ExploreErrorCode, t as ExploreEscalation, b as ExploreLimits, d as ExploreMessage, u as ExploreMessageQueryData, v as ExploreMessageRole, w as ExploreMessageStatus, E as ExploreProduct, a as ExploreReference, S as SEND_EXPLORE_MESSAGE_MUTATION, x as SendExploreMessageInput, y as SendExploreMessageMutationData, z as SendExploreMessageResult, A as looksLikeDeliverableEmail } from './explore-types-B5sdOC5M.mjs';
25
+ export { D as DEFAULT_EXPLORE_EXAMPLE_PROMPTS, E as ExploreChatError, a as ExploreChatErrorKind, b as ExploreChatPhase, c as ExploreEscalateOutcome, d as ExploreEscalateResult, F as FALLBACK_EXPLORE_LIMITS, U as UseExploreChatOptions, e as UseExploreChatResult, f as UseSpeechOutputOptions, g as UseSpeechOutputResult, h as classifyExploreError, s as stopSpeaking, u as useExploreChat, i as useProgressiveReveal, j as useSpeechOutput } from './use-progressive-reveal-Cmy8bA7L.mjs';
24
26
  export { RybbitAnalytics, RybbitAnalyticsProps } from './analytics/index.mjs';
25
27
  export { PageHead, PageHeadProps } from './seo/index.mjs';
26
28
  export { ProductData, ProductProvider, ProductProviderProps, useProduct, useProductConfig } from './product/index.mjs';
@@ -29,7 +31,6 @@ import { VariantProps } from 'class-variance-authority';
29
31
  import * as LabelPrimitive from '@radix-ui/react-label';
30
32
  export { B as BrowseStoreResult, G as GroupedStoreProducts, P as ProductCard, a as ProductCategory, R as RatingDistribution, S as SearchSuggestion, b as StarRating, c as StoreBrowsePage, d as StoreBrowsePageProps, e as StoreFacet, f as StoreFacets, g as StoreHomePage, h as StoreHomePageProps, i as StorePageProps, j as StoreProduct, k as StoreProductDetailPage, l as StoreProductDetailPageProps, m as StoreProductDetails, n as StorePublisher, o as StoreReview, p as StoreTerminology, q as StoreTypeFilter, T as TypeBadge, r as formatDownloads, s as formatPrice } from './product-card-CGxETsFa.mjs';
31
33
  export { EXPLORE_RECAPTCHA_ACTION, RecaptchaV3, cn, getRecaptchaV3Token, loadRecaptchaV3 } from './utils/index.mjs';
32
- import 'react-markdown';
33
34
  import 'clsx';
34
35
 
35
36
  interface CareersSubmissionFormProps {
@@ -197,31 +198,241 @@ declare function ConceptsPage({ productName, siteUrl, concepts, categories, cta,
197
198
  */
198
199
  declare function ConceptDetailPage(props: ConceptDetailPageProps): react.JSX.Element;
199
200
 
200
- declare const buttonVariants: (props?: ({
201
- variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
202
- size?: "icon" | "default" | "sm" | "lg" | null | undefined;
203
- } & class_variance_authority_types.ClassProp) | undefined) => string;
204
- interface ButtonProps extends react.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
205
- asChild?: boolean;
206
- }
207
- declare const Button: react.ForwardRefExoticComponent<ButtonProps & react.RefAttributes<HTMLButtonElement>>;
208
-
209
- declare const Input: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref"> & react.RefAttributes<HTMLInputElement>>;
210
-
211
- declare const Label: react.ForwardRefExoticComponent<Omit<LabelPrimitive.LabelProps & react.RefAttributes<HTMLLabelElement>, "ref"> & VariantProps<(props?: class_variance_authority_types.ClassProp | undefined) => string> & react.RefAttributes<HTMLLabelElement>>;
212
-
213
- /** Native `<select>` wrapper matching the `Input` design tokens. */
214
- declare const Select: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.SelectHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>, "ref"> & react.RefAttributes<HTMLSelectElement>>;
201
+ /**
202
+ * Published answers — the shared data contract.
203
+ *
204
+ * ## Where the questions come from, and where they must NEVER come from
205
+ *
206
+ * Every product website publishes a set of question-and-answer pages built from a **curated
207
+ * question list**, answered by the live explore API against the baked corpus. The question
208
+ * list is written by us; the answer is the harness's own output for that question.
209
+ *
210
+ * They are **never** built from stored visitor conversations. `explore_messages` holds every
211
+ * question a real visitor has typed into `/explore`, and publishing those as indexed pages
212
+ * would be a privacy breach. That the rows carry no name does not save it: a question is not
213
+ * anonymous content. "Our farm outside Coimbatore has three staff and we are being billed X"
214
+ * identifies a business with nobody's name attached, and the person who typed it consented to
215
+ * being answered, not to being published. That table is an analytics and answer-quality input,
216
+ * which is what it was built for.
217
+ *
218
+ * So: nothing in this module, in `answers.tsx`, or in whatever generates a site's
219
+ * `src/data/answers.ts` may read `ExploreMessage`, `ExploreConversation` or `ExploreFeedback`
220
+ * rows. A {@link PublishedAnswer} has no conversation id, no message id, no visitor field and
221
+ * no free text that came from one — by construction, not by a denylist, because a denylist
222
+ * leaks the first column nobody thought of.
223
+ *
224
+ * ## The same care downstream
225
+ *
226
+ * A published answer must not contain anything that is not already public on our own
227
+ * websites. The harness only answers from the corpus, which is built from published pages, and
228
+ * every source here is a public URL — {@link isPublishedSourceUrl} is the page's own last
229
+ * check before it renders one as a link.
230
+ *
231
+ * ## Shape
232
+ *
233
+ * Each site ships a generated `src/data/answers.ts` exporting `ANSWERS`, `ANSWER_TOPICS` and
234
+ * `ANSWERS_PRODUCT_NAME`, typed against these interfaces — the same arrangement the concept
235
+ * pages use (`dev/platform/context/concepts/01-architecture.md`). A field renamed here changes
236
+ * every site's generated data module, so change the generator and this file together.
237
+ */
215
238
 
216
- declare const Textarea: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>, "ref"> & react.RefAttributes<HTMLTextAreaElement>>;
239
+ /**
240
+ * One page an answer cited.
241
+ *
242
+ * Always a public URL on one of our own sites: the harness strips every link whose origin is
243
+ * not in its catalog before the answer is ever stored, and this is what the page renders as a
244
+ * real, followable link under the answer.
245
+ */
246
+ interface PublishedAnswerSource {
247
+ /** Absolute `https://` URL of the page. */
248
+ url: string;
249
+ /** The page's own title, as published. */
250
+ title: string;
251
+ /** Product display name, when the page belongs to one product. */
252
+ product?: string;
253
+ }
254
+ /** A curated question, and the answer our assistant produced for it. */
255
+ interface PublishedAnswer {
256
+ /** Kebab-case, unique per site; the `/answer/<slug>` route. */
257
+ slug: string;
258
+ /** 1-based position across the site's answers; drives listing order and previous/next. */
259
+ order: number;
260
+ /** The question, as we wrote it. The page's `<h1>`. */
261
+ question: string;
262
+ /**
263
+ * The answer, as GitHub-flavoured markdown — exactly what the harness returned.
264
+ *
265
+ * It can contain GFM tables (a comparison or a plan list comes back as one; see
266
+ * `dev/platform/context/explore/CONTRACT.md` §7), links and concept illustrations, so it is
267
+ * rendered by the SAME renderer the chat uses and not by a second one.
268
+ */
269
+ answer: string;
270
+ /** Grouping on the index page; matches a {@link PublishedAnswerTopic} name. */
271
+ topic: string;
272
+ /** One line under the question, on the card and in the hero. */
273
+ summary: string;
274
+ /** The `<meta name="description">` for this answer's page. */
275
+ metaDescription: string;
276
+ /** Search keywords, also used by the index page's filter. */
277
+ keywords: string[];
278
+ /** The pages the answer cited, in the order it cited them. */
279
+ sources: PublishedAnswerSource[];
280
+ /** Slugs of related answers on the same site. */
281
+ related: string[];
282
+ /**
283
+ * When the answer was produced, ISO-8601 (`2026-09-22`, or a full timestamp).
284
+ *
285
+ * Shown on the page and put in the structured data. An answer is a snapshot of what the
286
+ * documentation said on a day; publishing it without saying which day would be the one
287
+ * genuinely misleading thing about these pages.
288
+ */
289
+ answeredAt: string;
290
+ }
291
+ /**
292
+ * Every field a {@link PublishedAnswer} has, written out once.
293
+ *
294
+ * Typed as a TOTAL map of the interface's keys: adding a field to {@link PublishedAnswer}
295
+ * does not compile until someone also writes it down here, and removing one does not compile
296
+ * until someone deletes it here. That is the point — the field that would break this feature's
297
+ * one rule is a conversation id, a message id or a visitor's own words, and nobody adds one of
298
+ * those to a published contract by accident twice.
299
+ *
300
+ * It lives in this module, not in a test, because `tsconfig.json` excludes `**\/*.test.ts(x)`
301
+ * from the program: a totality constraint written in a test file is never compiled and can
302
+ * therefore never fail. Here it is compiled by `bun run type-check`, which the PR gate and both
303
+ * publish workflows run, so the constraint is enforced on every path that ships this package.
304
+ *
305
+ * `answers.ssr.test.tsx` then checks the runtime half: that this list still matches the
306
+ * interface field for field, and that no name in it could hold a visitor's conversation. Sites
307
+ * generating `src/data/answers.ts` can use it the same way, to assert a generated row carries
308
+ * these keys and no others.
309
+ */
310
+ declare const PUBLISHED_ANSWER_FIELDS: Record<keyof PublishedAnswer, true>;
311
+ interface PublishedAnswerTopic {
312
+ name: string;
313
+ description: string;
314
+ }
315
+ /** Props the pages pass to a site's link adapter. */
316
+ interface AnswerLinkProps {
317
+ href: string;
318
+ className?: string;
319
+ children: ReactNode;
320
+ "aria-label"?: string;
321
+ }
322
+ /**
323
+ * The link the pages render for every internal URL. Sites pass a small adapter around their
324
+ * router's `Link`, so moving between answers stays a client-side navigation; without one the
325
+ * pages fall back to a plain `<a href>`.
326
+ */
327
+ type AnswerLinkComponent = ComponentType<AnswerLinkProps>;
328
+ /** The index route. */
329
+ declare const ANSWERS_PATH = "/answers";
330
+ /** Prefix of every answer detail route: `/answer/<slug>`. */
331
+ declare const ANSWER_PATH_PREFIX = "/answer";
332
+ /** Site-relative URL of one answer's page. */
333
+ declare function answerPath(slug: string): string;
334
+ /**
335
+ * Resolve a route parameter to its answer. Returns `undefined` for an unknown slug — the site
336
+ * then redirects to `/answers` rather than rendering a soft 404.
337
+ */
338
+ declare function getAnswerBySlug(answers: readonly PublishedAnswer[], slug: string | null | undefined): PublishedAnswer | undefined;
339
+ /**
340
+ * True when a source URL is safe to publish as a link.
341
+ *
342
+ * `https` only, and a real host: these pages are prerendered and crawled, so a `javascript:`
343
+ * or `data:` URL that reached the generated data would ship to every visitor as a live link,
344
+ * and a relative one would resolve against whichever answer page happened to render it. The
345
+ * ORIGIN allow-list belongs in the generator, which knows the catalog; this is the page's own
346
+ * last check, in the same spirit as the inline-image guard in the chat.
347
+ */
348
+ declare function isPublishedSourceUrl(url: string): boolean;
349
+ /**
350
+ * The host shown beside a source, `www.` removed.
351
+ *
352
+ * Same normalisation the chat's reference cards apply, so a source reads the same way whether
353
+ * a visitor met it in a conversation or on one of these pages. Returns `""` for anything
354
+ * unparseable — the label is decorative, and {@link isPublishedSourceUrl} has already decided
355
+ * whether the link renders at all.
356
+ */
357
+ declare function answerSourceHost(url: string): string;
217
358
 
359
+ interface AnswersPageProps {
360
+ /** Product display name, e.g. `"VibeControls"`. */
361
+ productName: string;
362
+ /** Public origin of the site, e.g. `"https://vibecontrols.com"`. Used for absolute URLs. */
363
+ siteUrl: string;
364
+ answers: readonly PublishedAnswer[];
365
+ /** Section order and descriptions. Answers in an unlisted topic still render, last. */
366
+ topics?: readonly PublishedAnswerTopic[];
367
+ /** Call to action rendered in the hero (e.g. the site's primary buttons). */
368
+ cta?: ReactNode;
369
+ /** Router adapter for internal links. Defaults to a plain `<a href>`. */
370
+ linkComponent?: AnswerLinkComponent;
371
+ /** Overrides the `<h1>`. Default: the localised `"<productName> answers"`. */
372
+ title?: string;
373
+ /** Overrides the meta description and the hero lead. */
374
+ description?: string;
375
+ /**
376
+ * BCP-47 locale for the page chrome. Defaults to the document's own `lang`, then the
377
+ * browser. The ANSWERS themselves are English whatever this says — see `answers-i18n.ts`.
378
+ */
379
+ locale?: string;
380
+ /** Extra classes for the page root. */
381
+ className?: string;
382
+ }
383
+ interface AnswerDetailPageProps {
384
+ productName: string;
385
+ siteUrl: string;
386
+ /** The answer to render. The site resolves the slug and redirects when it is unknown. */
387
+ answer: PublishedAnswer;
388
+ /** Every answer on the site — for related questions and previous / next. */
389
+ answers: readonly PublishedAnswer[];
390
+ topics?: readonly PublishedAnswerTopic[];
391
+ /**
392
+ * Extra origins an illustration embedded in the answer may load from.
393
+ *
394
+ * The site's own origin and the origins of this answer's sources are always allowed, which
395
+ * covers every image the harness can legitimately have embedded. Pass more only when an
396
+ * answer cites a page whose images live elsewhere in the catalog, and pass a stable array.
397
+ */
398
+ imageOrigins?: readonly string[];
399
+ /**
400
+ * Where "Ask your own question" goes. `null` removes the link — pass that on a site with no
401
+ * `/explore` route, rather than shipping a link to a page that does not exist.
402
+ */
403
+ askHref?: string | null;
404
+ /** Call to action rendered as a band at the end of the page. */
405
+ cta?: ReactNode;
406
+ linkComponent?: AnswerLinkComponent;
407
+ locale?: string;
408
+ className?: string;
409
+ }
218
410
  /**
219
- * Canonical Burdenoff ecosystem product list used by WebsiteSwitcher.
411
+ * Heading overrides for a PUBLISHED answer, merged over the shared answer components.
220
412
  *
221
- * Sourced from `~/products/websites/burdenoff-website/src/data/products.ts`.
222
- * Keep this in sync when products are added, renamed, or moved.
413
+ * In the chat an answer is one message in a stream and its `#` headings bother nobody. On a
414
+ * crawled page the outline is the page's structure: the question is the `<h1>` and "Answer"
415
+ * is an `<h2>`, so an answer that opens with `## Plans` would put a second `<h2>` inside a
416
+ * section and an answer that opens with `#` would put a SECOND `<h1>` on the page. Both are
417
+ * things a crawler reads as the page changing subject.
418
+ *
419
+ * So every heading the model wrote is pushed below the page's own: `#` and `##` become `<h3>`
420
+ * (the first real level inside the answer section), and the rest follow, floored at `<h6>`.
421
+ * The words are untouched — only the level moves.
422
+ *
423
+ * Module-level on purpose: react-markdown uses an override as the element TYPE, so rebuilding
424
+ * this object during a render would remount everything under it.
223
425
  */
224
- declare const ECOSYSTEM_PRODUCTS: WebsiteSwitcherProduct[];
426
+ declare const ANSWER_HEADING_SHIFT: Components;
427
+ /**
428
+ * `/answers` — every published answer on the site, grouped by topic, with a client-side
429
+ * search and topic filter. Filters start at "All", so the prerendered page lists everything.
430
+ */
431
+ declare function AnswersPage({ productName, siteUrl, answers, topics, cta, linkComponent, title, description, locale, className, }: AnswersPageProps): react.JSX.Element;
432
+ /**
433
+ * `/answer/:slug` — one question, the answer, and the pages it came from.
434
+ */
435
+ declare function AnswerDetailPage(props: AnswerDetailPageProps): react.JSX.Element;
225
436
 
226
437
  /**
227
438
  * Localisation for the `/explore` chat surface.
@@ -296,6 +507,38 @@ interface ExploreStrings {
296
507
  feedbackDismiss: string;
297
508
  feedbackThanks: string;
298
509
  feedbackNotSent: string;
510
+ /**
511
+ * The trigger, in its two homes: `escalateOpen` is the quiet one under an answer we DID
512
+ * give, and the amber "we could not find this" banner reuses `askHuman` so that sentence
513
+ * keeps reading as one sentence.
514
+ */
515
+ escalateOpen: string;
516
+ escalateTitle: string;
517
+ escalateBody: string;
518
+ escalateEmailLabel: string;
519
+ escalateEmailPlaceholder: string;
520
+ escalateNoteLabel: string;
521
+ escalateSubmit: string;
522
+ escalateSending: string;
523
+ escalateCancel: string;
524
+ /**
525
+ * Shown for BOTH the client's own pre-flight and the server's `INVALID_EMAIL`. The two
526
+ * disagree about what is deliverable and the server is right, but the visitor's next
527
+ * action is identical either way: check the address.
528
+ */
529
+ escalateInvalidEmail: string;
530
+ /**
531
+ * The success line. Takes `{email}` — and it is filled with the address the SERVER
532
+ * returned, not the one in the box, because on a repeat handover the reply goes to
533
+ * whichever address the first one used.
534
+ *
535
+ * Used for `created: false` as well: a visitor asking for help is not told "you already
536
+ * did that".
537
+ */
538
+ escalateSent: string;
539
+ escalateRateLimited: string;
540
+ escalateCaptcha: string;
541
+ escalateFailed: string;
299
542
  prevSuggestion: string;
300
543
  nextSuggestion: string;
301
544
  /**
@@ -323,6 +566,21 @@ interface ExploreStrings {
323
566
  /** Label of the per-answer copy control, and its failure state. */
324
567
  copyAnswer: string;
325
568
  copyFailed: string;
569
+ /**
570
+ * Label of the per-answer read-aloud control, in its two states.
571
+ *
572
+ * A toggle, exactly like the composer's dictation button: one control whose label says
573
+ * what pressing it does NOW, never a second button that appears while it is speaking.
574
+ */
575
+ readAloud: string;
576
+ stopReading: string;
577
+ /**
578
+ * Accessible name of the scrollable box a table in an answer sits in.
579
+ *
580
+ * A scrollable region needs a name and a tab stop, or a keyboard visitor cannot reach the
581
+ * columns that are off the edge. The `<table>` inside it keeps its own semantics.
582
+ */
583
+ answerTable: string;
326
584
  /** Accessible name of the provenance row under an answer. */
327
585
  groundedIn: string;
328
586
  /** How many sources the answer cites. `sourceCountMany` takes `{count}`. */
@@ -350,5 +608,155 @@ interface ExploreStrings {
350
608
  capabilities: [string, string, string];
351
609
  prompts: string[];
352
610
  }
611
+ declare function speechLocaleOf(locale: ExploreLocale): string;
612
+
613
+ /**
614
+ * Localisation for the published answer pages.
615
+ *
616
+ * Same arrangement, and the same reasons, as `explore-i18n.ts`: the strings are bundled
617
+ * rather than fetched, because these pages are prerendered and a runtime translation request
618
+ * in front of the first paint would defeat the point of prerendering them. The locale set,
619
+ * the resolver and the `{placeholder}` filler are IMPORTED from there rather than repeated —
620
+ * one locale list for the whole explore surface, so a seventh language is added in one place.
621
+ *
622
+ * ## What is localised, and what is not
623
+ *
624
+ * The chrome — headings, labels, the provenance note, the breadcrumb — is localised into the
625
+ * six locales the products ship. **The answer itself is English**, because that is what the
626
+ * harness produced for the curated English question; nothing here translates it, and nothing
627
+ * here pretends it is translated.
628
+ *
629
+ * That split is also MARKED UP, not merely true: the pages carry `dir="rtl"` on their own
630
+ * subtree in Arabic (`isRtlLocale`, re-exported here so the pages have one locale module), and
631
+ * put `lang="en"` on the English content inside it. A page that declares itself Arabic and
632
+ * then prints an English answer tells a screen reader to read English with Arabic phonemes,
633
+ * and tells a crawler the answer is in a language it is not.
634
+ *
635
+ * ## The three borrowed strings
636
+ *
637
+ * `answerTable`, `conceptIllustration` and `opensInNewTab` come straight from
638
+ * `exploreStrings`. They are the words the ANSWER BODY renders, and the answer body is the
639
+ * same component on both surfaces — two phrasings for the same caveat read as two different
640
+ * caveats, which is the rule the illustration badge already follows.
641
+ */
642
+
643
+ interface AnswerStrings extends AnswerRendererStrings {
644
+ breadcrumbHome: string;
645
+ /** The section's own name: breadcrumb, nav item, back link. */
646
+ answers: string;
647
+ /** The index page's `<h1>`. Takes `{product}`. */
648
+ indexHeading: string;
649
+ /** The lead under it. Takes `{product}`. */
650
+ indexLead: string;
651
+ /** `<title>` of the index page; PageHead appends the product name itself. */
652
+ seoIndexTitle: string;
653
+ /** Its `<meta name="description">`. Takes `{product}`. */
654
+ seoIndexDescription: string;
655
+ searchLabel: string;
656
+ searchPlaceholder: string;
657
+ filterByTopic: string;
658
+ allTopics: string;
659
+ /** Takes `{shown}` and `{total}`. */
660
+ showingCount: string;
661
+ noMatchesTitle: string;
662
+ noMatchesBody: string;
663
+ clearFilters: string;
664
+ noneYetTitle: string;
665
+ /** Takes `{product}`. */
666
+ noneYetBody: string;
667
+ questionCountOne: string;
668
+ /** Takes `{count}`. */
669
+ questionCountMany: string;
670
+ sourceCountOne: string;
671
+ /** Takes `{count}`. */
672
+ sourceCountMany: string;
673
+ answerHeading: string;
674
+ sourcesHeading: string;
675
+ sourcesLead: string;
676
+ aboutTitle: string;
677
+ /**
678
+ * The honest description of what the reader is looking at. Takes `{date}`.
679
+ *
680
+ * Every clause is load-bearing, and the wording was chosen against the alternatives:
681
+ *
682
+ * - **"Produced by our own assistant"** — not "written by our team", which would be false,
683
+ * and not "AI-powered answers", which is marketing language for a disclosure.
684
+ * - **"on {date}"** — an answer is a snapshot of what the documentation said on a day. The
685
+ * single genuinely misleading thing these pages could do is present a year-old answer as
686
+ * current, so the date is in the sentence rather than in small print beside it.
687
+ * - **"The pages it drew on are linked under the answer"** — it tells the reader where to
688
+ * check, which is the only thing that makes the rest of the note actionable.
689
+ * - **"not a statement from a person"** — the sentence people actually need. This is a
690
+ * marketing surface, and a visitor who takes a generated paragraph for a commitment from
691
+ * us has been misled by us.
692
+ * - **"where it and a linked page disagree, the page is right"** — says which source wins,
693
+ * instead of a hedge ("may contain errors") that tells nobody what to do about it.
694
+ *
695
+ * Deliberately NOT said: that the answers are "frequently asked" or "the questions our
696
+ * customers ask most". They are a question set we chose. We have no measurement behind a
697
+ * popularity claim — and the one table that could measure it is the visitor question log,
698
+ * which this feature must never read (see `answers-types.ts`).
699
+ */
700
+ aboutBody: string;
701
+ /** Takes `{date}`. */
702
+ answeredOn: string;
703
+ topicsHeading: string;
704
+ relatedHeading: string;
705
+ /** Takes `{topic}`. */
706
+ moreInTopic: string;
707
+ previousQuestion: string;
708
+ nextQuestion: string;
709
+ /** Takes `{product}`. */
710
+ allAnswers: string;
711
+ askYourOwn: string;
712
+ }
713
+ declare function answerStrings(locale: ExploreLocale): AnswerStrings;
714
+ /**
715
+ * `answeredAt` as a date a person reads.
716
+ *
717
+ * Two things are pinned rather than left to the environment, because these pages are
718
+ * serialised from the post-hydration DOM: the machine that prerenders them and the browser
719
+ * that hydrates them must produce the SAME string, or React replaces the node on hydration.
720
+ *
721
+ * - **An explicit locale tag**, never the ambient default — the prerender runs in headless
722
+ * Chromium on a server whose locale is nobody's.
723
+ * - **An explicit numbering system.** CLDR has changed the default digits for `ar` before;
724
+ * two engines on different CLDR versions would then render the same date differently, and
725
+ * the text React hydrated would not be the text it prerendered. All six locales already
726
+ * default to Latin digits, so this pins today's output rather than changing it.
727
+ * - **`timeZone: "UTC"`**, because `new Date("2026-09-22")` is UTC midnight and formatting it
728
+ * in a negative-offset zone would print the 21st. A published date that moves by a day
729
+ * depending on who looks at it is worse than no date.
730
+ *
731
+ * Returns `""` for an unparseable value so a bad row loses its date line rather than printing
732
+ * "Invalid Date" on a marketing page.
733
+ */
734
+ declare function formatAnswerDate(value: string, locale: ExploreLocale): string;
735
+
736
+ declare const buttonVariants: (props?: ({
737
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
738
+ size?: "default" | "sm" | "lg" | "icon" | null | undefined;
739
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
740
+ interface ButtonProps extends react.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
741
+ asChild?: boolean;
742
+ }
743
+ declare const Button: react.ForwardRefExoticComponent<ButtonProps & react.RefAttributes<HTMLButtonElement>>;
744
+
745
+ declare const Input: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref"> & react.RefAttributes<HTMLInputElement>>;
746
+
747
+ declare const Label: react.ForwardRefExoticComponent<Omit<LabelPrimitive.LabelProps & react.RefAttributes<HTMLLabelElement>, "ref"> & VariantProps<(props?: class_variance_authority_types.ClassProp | undefined) => string> & react.RefAttributes<HTMLLabelElement>>;
748
+
749
+ /** Native `<select>` wrapper matching the `Input` design tokens. */
750
+ declare const Select: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.SelectHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>, "ref"> & react.RefAttributes<HTMLSelectElement>>;
751
+
752
+ declare const Textarea: react.ForwardRefExoticComponent<Omit<react.DetailedHTMLProps<react.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>, "ref"> & react.RefAttributes<HTMLTextAreaElement>>;
753
+
754
+ /**
755
+ * Canonical Burdenoff ecosystem product list used by WebsiteSwitcher.
756
+ *
757
+ * Sourced from `~/products/websites/burdenoff-website/src/data/products.ts`.
758
+ * Keep this in sync when products are added, renamed, or moved.
759
+ */
760
+ declare const ECOSYSTEM_PRODUCTS: WebsiteSwitcherProduct[];
353
761
 
354
- export { Button, type ButtonProps, CONCEPTS_PATH, CONCEPT_ILLUSTRATION_NOTICE, CONCEPT_PATH_PREFIX, CareersPostingOption, CareersSubmissionForm, type CareersSubmissionFormProps, type Concept, type ConceptCategory, ConceptDetailPage, type ConceptDetailPageProps, type ConceptDevice, type ConceptImage, type ConceptLinkComponent, type ConceptLinkProps, ConceptsPage, type ConceptsPageProps, ECOSYSTEM_PRODUCTS, EXPLORE_LOCALES, type ExploreLocale, type ExploreStrings, Input, Label, Select, Textarea, WebsiteSwitcherProduct, buttonVariants, conceptPath, getConceptBySlug, isValidUrl };
762
+ export { ANSWERS_PATH, ANSWER_HEADING_SHIFT, ANSWER_PATH_PREFIX, AnswerDetailPage, type AnswerDetailPageProps, type AnswerLinkComponent, type AnswerLinkProps, AnswerRendererStrings, type AnswerStrings, AnswersPage, type AnswersPageProps, Button, type ButtonProps, CONCEPTS_PATH, CONCEPT_ILLUSTRATION_NOTICE, CONCEPT_PATH_PREFIX, CareersPostingOption, CareersSubmissionForm, type CareersSubmissionFormProps, type Concept, type ConceptCategory, ConceptDetailPage, type ConceptDetailPageProps, type ConceptDevice, type ConceptImage, type ConceptLinkComponent, type ConceptLinkProps, ConceptsPage, type ConceptsPageProps, ECOSYSTEM_PRODUCTS, EXPLORE_LOCALES, type ExploreLocale, type ExploreStrings, Input, Label, PUBLISHED_ANSWER_FIELDS, type PublishedAnswer, type PublishedAnswerSource, type PublishedAnswerTopic, Select, Textarea, WebsiteSwitcherProduct, answerPath, answerSourceHost, answerStrings, buttonVariants, conceptPath, formatAnswerDate, getAnswerBySlug, getConceptBySlug, isPublishedSourceUrl, isValidUrl, speechLocaleOf };