@burdenoff/website-sdk 2026.925.2 → 2026.926.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.
Files changed (67) hide show
  1. package/dist/components/contact.js +8 -0
  2. package/dist/components/contact.js.map +1 -1
  3. package/dist/components/contact.mjs +8 -0
  4. package/dist/components/contact.mjs.map +1 -1
  5. package/dist/components/explore.d.mts +1 -1
  6. package/dist/components/explore.d.ts +1 -1
  7. package/dist/components/explore.js +1101 -518
  8. package/dist/components/explore.js.map +1 -1
  9. package/dist/components/explore.mjs +1103 -521
  10. package/dist/components/explore.mjs.map +1 -1
  11. package/dist/components/newsletter.js +8 -0
  12. package/dist/components/newsletter.js.map +1 -1
  13. package/dist/components/newsletter.mjs +8 -0
  14. package/dist/components/newsletter.mjs.map +1 -1
  15. package/dist/components/partners.js +11 -2
  16. package/dist/components/partners.js.map +1 -1
  17. package/dist/components/partners.mjs +11 -2
  18. package/dist/components/partners.mjs.map +1 -1
  19. package/dist/components/resource-links.js +8 -0
  20. package/dist/components/resource-links.js.map +1 -1
  21. package/dist/components/resource-links.mjs +8 -0
  22. package/dist/components/resource-links.mjs.map +1 -1
  23. package/dist/components/social-links.js +8 -0
  24. package/dist/components/social-links.js.map +1 -1
  25. package/dist/components/social-links.mjs +8 -0
  26. package/dist/components/social-links.mjs.map +1 -1
  27. package/dist/components/waitlist.js +8 -0
  28. package/dist/components/waitlist.js.map +1 -1
  29. package/dist/components/waitlist.mjs +8 -0
  30. package/dist/components/waitlist.mjs.map +1 -1
  31. package/dist/content/index.d.mts +1 -1
  32. package/dist/content/index.d.ts +1 -1
  33. package/dist/content/index.js +11 -2
  34. package/dist/content/index.js.map +1 -1
  35. package/dist/content/index.mjs +11 -2
  36. package/dist/content/index.mjs.map +1 -1
  37. package/dist/{explore-C0RFTKAi.d.ts → explore-Cam_ElSJ.d.ts} +70 -12
  38. package/dist/{explore-DzOf7n4m.d.mts → explore-hTQm-_SF.d.mts} +70 -12
  39. package/dist/hooks/index.js +51 -13
  40. package/dist/hooks/index.js.map +1 -1
  41. package/dist/hooks/index.mjs +51 -13
  42. package/dist/hooks/index.mjs.map +1 -1
  43. package/dist/{index-BMWQNZDX.d.mts → index-gm3pBCJT.d.mts} +8 -2
  44. package/dist/{index-BqHF-G-S.d.ts → index-ySbOLSD5.d.ts} +8 -2
  45. package/dist/index.d.mts +32 -11
  46. package/dist/index.d.ts +32 -11
  47. package/dist/index.js +901 -267
  48. package/dist/index.js.map +1 -1
  49. package/dist/index.mjs +892 -269
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/product/index.d.mts +16 -1
  52. package/dist/product/index.d.ts +16 -1
  53. package/dist/product/index.js +61 -17
  54. package/dist/product/index.js.map +1 -1
  55. package/dist/product/index.mjs +61 -18
  56. package/dist/product/index.mjs.map +1 -1
  57. package/dist/store/index.js +11 -2
  58. package/dist/store/index.js.map +1 -1
  59. package/dist/store/index.mjs +11 -2
  60. package/dist/store/index.mjs.map +1 -1
  61. package/dist/utils/index.d.mts +41 -1
  62. package/dist/utils/index.d.ts +41 -1
  63. package/dist/utils/index.js +42 -0
  64. package/dist/utils/index.js.map +1 -1
  65. package/dist/utils/index.mjs +35 -1
  66. package/dist/utils/index.mjs.map +1 -1
  67. package/package.json +1 -1
@@ -2,6 +2,17 @@ import * as react from 'react';
2
2
  import { E as ExploreProduct, a as ExploreReference } from './explore-types-B5sdOC5M.js';
3
3
  import { Components } from 'react-markdown';
4
4
 
5
+ /** The words the player needs. `AnswerRendererStrings`, and so `ExploreStrings`, carry them. */
6
+ interface ExploreVideoStrings {
7
+ /** Accessible name of the Close button. */
8
+ closeVideo: string;
9
+ /** The full-screen toggle's label in each state — it says what pressing it does NOW. */
10
+ enterFullScreen: string;
11
+ exitFullScreen: string;
12
+ /** Shown in place of the player when the file cannot be played. */
13
+ videoUnavailable: string;
14
+ }
15
+
5
16
  /**
6
17
  * The answer stylesheet as an element. Safe to mount more than once — the rules are
7
18
  * idempotent — so a page that shows answers and nothing else still gets them.
@@ -17,8 +28,25 @@ declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "web
17
28
  * True when `src` is an https URL on a catalog product site. The backend already strips
18
29
  * every image it did not put in the answer; this is the browser's own check, so a model
19
30
  * that slips an arbitrary URL past it still cannot make the visitor's browser fetch it.
31
+ *
32
+ * A video or caption file is refused even on a catalog site: as an `<img src>` it downloads
33
+ * video bytes into a transcript that promises never to preload one (§7), and draws nothing.
34
+ * That covers a product-video card's poster as well as a plain `![..](..mp4)`.
20
35
  */
21
36
  declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>): src is string;
37
+ /**
38
+ * True when `href` may become a product-video card: the SAME origin rule as an inline image
39
+ * (https, no credentials, a catalog product site), and a path that is a playable video file.
40
+ */
41
+ declare function isInlineVideoAllowed(href: unknown, origins: ReadonlySet<string>): href is string;
42
+ /**
43
+ * Apex origin → catalog slug, so a video card can say WHICH product it is (`data-product`).
44
+ *
45
+ * The file's host is not the answer on its own: HealthyBowl ships on gethealthybowl.com and
46
+ * Workspaces on burdenoff.com, and an e2e selector written against the slug would miss both.
47
+ * First entry wins, as in `productNamesByOrigin`.
48
+ */
49
+ declare function productSlugsByOrigin(products: readonly Pick<ExploreProduct, "slug" | "website">[]): Map<string, string>;
22
50
  /**
23
51
  * An answer as READABLE TEXT, for the clipboard.
24
52
  *
@@ -30,6 +58,10 @@ declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>
30
58
  * stays.
31
59
  * - **Links become `label (url)`.** The destination is information the reader may need; the
32
60
  * bracket-paren syntax is not. Dropping the URL would quietly strip the page it came from.
61
+ * - **A linked image is a link like any other**, labelled by the image's alt text — so the
62
+ * product video is `Planmagnet product video (https://…mp4)`. The image itself goes as
63
+ * every image does; the link is kept because, unlike a bare illustration, it is somewhere
64
+ * the reader can GO — and a pasted answer has no card to click.
33
65
  * - **Citation markers `[3]` stay.** They are the only attribution the copied text carries,
34
66
  * and they are the answer's own — not something the UI added. The sources they point at are
35
67
  * listed right next to the button that copied this.
@@ -47,12 +79,15 @@ declare function answerPlainText(markdown: string): string;
47
79
  /**
48
80
  * The same answer as something to SAY, for read-aloud.
49
81
  *
50
- * It shares every rule above but the two that are right for a paste and wrong for a voice:
82
+ * It shares every rule above but the three that are right for a paste and wrong for a voice:
51
83
  *
52
84
  * - **A link is its label.** The clipboard keeps `label (https://…)` because a pasted answer
53
85
  * loses the page otherwise. Spoken, that is a URL spelled out character by character in
54
86
  * the middle of a sentence — and the prompt asks for two to five links in every answer, so
55
87
  * it is not an edge case. The sources are listed under the answer either way.
88
+ * - **A linked image is not read at all** — the product video included. Its label is a
89
+ * picture, which is never read, and its link is an address; the card or illustration is
90
+ * on screen for anyone who can click it.
56
91
  * - **A table is read as rows, not as pipes.** `| --- | --- |` is a line of punctuation and
57
92
  * the pipes are column rules, neither of which is a word. Each row is read with its cells
58
93
  * under their own column headings — the phone layout, out loud — so a plan list still says
@@ -67,30 +102,53 @@ declare function answerSpeechText(markdown: string): string;
67
102
  /**
68
103
  * The strings the markdown overrides render.
69
104
  *
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.
105
+ * Deliberately a handful of fields rather than the whole `ExploreStrings`: the chat and a
106
+ * published answer page carry different chrome vocabularies, and these are the only words
107
+ * the ANSWER itself needs — including the product-video card and the player it opens.
108
+ * `ExploreStrings` satisfies this structurally, so the chat passes its own `T` unchanged.
74
109
  */
75
- interface AnswerRendererStrings {
110
+ interface AnswerRendererStrings extends ExploreVideoStrings {
76
111
  /** Accessible name of the scrollable box a table sits in. */
77
112
  answerTable: string;
78
113
  /** The badge laid over a concept illustration embedded in an answer. */
79
114
  conceptIllustration: string;
80
115
  /** Read out after a link that opens a new tab. */
81
116
  opensInNewTab: string;
117
+ /** The badge on a product-video card, and its name when the answer gave the video none. */
118
+ productVideo: string;
119
+ /**
120
+ * A product video's name, in the page's language. Takes `{product}`, read out of the
121
+ * English `<Name> product video` alt text the canonical line always carries.
122
+ */
123
+ productVideoTitle: string;
124
+ /** Accessible name of the card's play button. Takes `{title}`. */
125
+ playVideo: string;
82
126
  }
83
127
  interface ExploreAnswerBodyProps {
84
128
  /** The answer, exactly as the harness wrote it: GitHub-flavoured markdown. */
85
129
  children: string;
86
- /** The three strings the overrides render. `ExploreStrings` satisfies this. */
130
+ /** The strings the overrides render. `ExploreStrings` satisfies this. */
87
131
  strings: AnswerRendererStrings;
88
132
  /** Origins an inline image may come from — see {@link inlineImageOrigins}. */
89
133
  imageOrigins: ReadonlySet<string>;
90
134
  /**
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`.
135
+ * Origins a product video and its poster may come from. Defaults to `imageOrigins`, which
136
+ * in the chat is already every catalog site. A published answer page allows fewer images
137
+ * than that and passes the whole catalog here: the video is the product's own file, and is
138
+ * never one of the sources the page's image list is built from. Pass a stable set.
139
+ */
140
+ videoOrigins?: ReadonlySet<string>;
141
+ /**
142
+ * Apex origin → catalog slug (see {@link productSlugsByOrigin}), so a video card can name
143
+ * its product in `data-product`. Without it the card names the file's host instead. Pass a
144
+ * stable map.
145
+ */
146
+ productSlugs?: ReadonlyMap<string, string>;
147
+ /**
148
+ * False while the answer is still streaming, which suppresses its images and its video
149
+ * card: a partial is cut mid-token, so a URL in it may be half-written, and the backend has
150
+ * not yet dropped what it does not allow. A settled answer — every published one — is
151
+ * `true`.
94
152
  */
95
153
  enabled?: boolean;
96
154
  /**
@@ -116,7 +174,7 @@ interface ExploreAnswerBodyProps {
116
174
  * The set of images that failed to load is held HERE rather than in each image, so a source
117
175
  * that 404s stays hidden even when the markdown re-renders from scratch underneath it.
118
176
  */
119
- declare function ExploreAnswerBody({ children, strings, imageOrigins, enabled, components, className, }: ExploreAnswerBodyProps): react.JSX.Element;
177
+ declare function ExploreAnswerBody({ children, strings, imageOrigins, videoOrigins, productSlugs, enabled, components, className, }: ExploreAnswerBodyProps): react.JSX.Element;
120
178
 
121
179
  interface ExplorePageProps {
122
180
  /**
@@ -248,4 +306,4 @@ declare const EXPLORE_PREFILL_MAX_LENGTH = 500;
248
306
  declare function exploreQuestionHref(question: string, base?: string): string;
249
307
  declare function ExplorePage({ seo, productSlug, examplePrompts, className, heightMode, viewportOffset, onNavigate, onSend, welcomeTitle, welcomeBody, locale, }: ExplorePageProps): react.JSX.Element;
250
308
 
251
- export { type AnswerRendererStrings as A, EXPLORE_PREFILL_MAX_LENGTH as E, ExploreAnswerBody as a, type ExploreAnswerBodyProps as b, ExploreAnswerStyles as c, ExplorePage as d, type ExplorePageProps as e, answerPlainText as f, answerSpeechText as g, exploreQuestionHref as h, inlineImageOrigins as i, isInlineImageAllowed as j, type ExploreProvenance as k, type ExploreReferenceKind as l, logoUrlOf as m, productNamesByOrigin as p, referenceKindOf as r, summariseReferences as s };
309
+ export { type AnswerRendererStrings as A, EXPLORE_PREFILL_MAX_LENGTH as E, ExploreAnswerBody as a, type ExploreAnswerBodyProps as b, ExploreAnswerStyles as c, ExplorePage as d, type ExplorePageProps as e, answerPlainText as f, answerSpeechText as g, exploreQuestionHref as h, inlineImageOrigins as i, isInlineImageAllowed as j, isInlineVideoAllowed as k, type ExploreProvenance as l, type ExploreReferenceKind as m, logoUrlOf as n, productNamesByOrigin as o, productSlugsByOrigin as p, referenceKindOf as r, summariseReferences as s };
@@ -2,6 +2,17 @@ import * as react from 'react';
2
2
  import { E as ExploreProduct, a as ExploreReference } from './explore-types-B5sdOC5M.mjs';
3
3
  import { Components } from 'react-markdown';
4
4
 
5
+ /** The words the player needs. `AnswerRendererStrings`, and so `ExploreStrings`, carry them. */
6
+ interface ExploreVideoStrings {
7
+ /** Accessible name of the Close button. */
8
+ closeVideo: string;
9
+ /** The full-screen toggle's label in each state — it says what pressing it does NOW. */
10
+ enterFullScreen: string;
11
+ exitFullScreen: string;
12
+ /** Shown in place of the player when the file cannot be played. */
13
+ videoUnavailable: string;
14
+ }
15
+
5
16
  /**
6
17
  * The answer stylesheet as an element. Safe to mount more than once — the rules are
7
18
  * idempotent — so a page that shows answers and nothing else still gets them.
@@ -17,8 +28,25 @@ declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "web
17
28
  * True when `src` is an https URL on a catalog product site. The backend already strips
18
29
  * every image it did not put in the answer; this is the browser's own check, so a model
19
30
  * that slips an arbitrary URL past it still cannot make the visitor's browser fetch it.
31
+ *
32
+ * A video or caption file is refused even on a catalog site: as an `<img src>` it downloads
33
+ * video bytes into a transcript that promises never to preload one (§7), and draws nothing.
34
+ * That covers a product-video card's poster as well as a plain `![..](..mp4)`.
20
35
  */
21
36
  declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>): src is string;
37
+ /**
38
+ * True when `href` may become a product-video card: the SAME origin rule as an inline image
39
+ * (https, no credentials, a catalog product site), and a path that is a playable video file.
40
+ */
41
+ declare function isInlineVideoAllowed(href: unknown, origins: ReadonlySet<string>): href is string;
42
+ /**
43
+ * Apex origin → catalog slug, so a video card can say WHICH product it is (`data-product`).
44
+ *
45
+ * The file's host is not the answer on its own: HealthyBowl ships on gethealthybowl.com and
46
+ * Workspaces on burdenoff.com, and an e2e selector written against the slug would miss both.
47
+ * First entry wins, as in `productNamesByOrigin`.
48
+ */
49
+ declare function productSlugsByOrigin(products: readonly Pick<ExploreProduct, "slug" | "website">[]): Map<string, string>;
22
50
  /**
23
51
  * An answer as READABLE TEXT, for the clipboard.
24
52
  *
@@ -30,6 +58,10 @@ declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>
30
58
  * stays.
31
59
  * - **Links become `label (url)`.** The destination is information the reader may need; the
32
60
  * bracket-paren syntax is not. Dropping the URL would quietly strip the page it came from.
61
+ * - **A linked image is a link like any other**, labelled by the image's alt text — so the
62
+ * product video is `Planmagnet product video (https://…mp4)`. The image itself goes as
63
+ * every image does; the link is kept because, unlike a bare illustration, it is somewhere
64
+ * the reader can GO — and a pasted answer has no card to click.
33
65
  * - **Citation markers `[3]` stay.** They are the only attribution the copied text carries,
34
66
  * and they are the answer's own — not something the UI added. The sources they point at are
35
67
  * listed right next to the button that copied this.
@@ -47,12 +79,15 @@ declare function answerPlainText(markdown: string): string;
47
79
  /**
48
80
  * The same answer as something to SAY, for read-aloud.
49
81
  *
50
- * It shares every rule above but the two that are right for a paste and wrong for a voice:
82
+ * It shares every rule above but the three that are right for a paste and wrong for a voice:
51
83
  *
52
84
  * - **A link is its label.** The clipboard keeps `label (https://…)` because a pasted answer
53
85
  * loses the page otherwise. Spoken, that is a URL spelled out character by character in
54
86
  * the middle of a sentence — and the prompt asks for two to five links in every answer, so
55
87
  * it is not an edge case. The sources are listed under the answer either way.
88
+ * - **A linked image is not read at all** — the product video included. Its label is a
89
+ * picture, which is never read, and its link is an address; the card or illustration is
90
+ * on screen for anyone who can click it.
56
91
  * - **A table is read as rows, not as pipes.** `| --- | --- |` is a line of punctuation and
57
92
  * the pipes are column rules, neither of which is a word. Each row is read with its cells
58
93
  * under their own column headings — the phone layout, out loud — so a plan list still says
@@ -67,30 +102,53 @@ declare function answerSpeechText(markdown: string): string;
67
102
  /**
68
103
  * The strings the markdown overrides render.
69
104
  *
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.
105
+ * Deliberately a handful of fields rather than the whole `ExploreStrings`: the chat and a
106
+ * published answer page carry different chrome vocabularies, and these are the only words
107
+ * the ANSWER itself needs — including the product-video card and the player it opens.
108
+ * `ExploreStrings` satisfies this structurally, so the chat passes its own `T` unchanged.
74
109
  */
75
- interface AnswerRendererStrings {
110
+ interface AnswerRendererStrings extends ExploreVideoStrings {
76
111
  /** Accessible name of the scrollable box a table sits in. */
77
112
  answerTable: string;
78
113
  /** The badge laid over a concept illustration embedded in an answer. */
79
114
  conceptIllustration: string;
80
115
  /** Read out after a link that opens a new tab. */
81
116
  opensInNewTab: string;
117
+ /** The badge on a product-video card, and its name when the answer gave the video none. */
118
+ productVideo: string;
119
+ /**
120
+ * A product video's name, in the page's language. Takes `{product}`, read out of the
121
+ * English `<Name> product video` alt text the canonical line always carries.
122
+ */
123
+ productVideoTitle: string;
124
+ /** Accessible name of the card's play button. Takes `{title}`. */
125
+ playVideo: string;
82
126
  }
83
127
  interface ExploreAnswerBodyProps {
84
128
  /** The answer, exactly as the harness wrote it: GitHub-flavoured markdown. */
85
129
  children: string;
86
- /** The three strings the overrides render. `ExploreStrings` satisfies this. */
130
+ /** The strings the overrides render. `ExploreStrings` satisfies this. */
87
131
  strings: AnswerRendererStrings;
88
132
  /** Origins an inline image may come from — see {@link inlineImageOrigins}. */
89
133
  imageOrigins: ReadonlySet<string>;
90
134
  /**
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`.
135
+ * Origins a product video and its poster may come from. Defaults to `imageOrigins`, which
136
+ * in the chat is already every catalog site. A published answer page allows fewer images
137
+ * than that and passes the whole catalog here: the video is the product's own file, and is
138
+ * never one of the sources the page's image list is built from. Pass a stable set.
139
+ */
140
+ videoOrigins?: ReadonlySet<string>;
141
+ /**
142
+ * Apex origin → catalog slug (see {@link productSlugsByOrigin}), so a video card can name
143
+ * its product in `data-product`. Without it the card names the file's host instead. Pass a
144
+ * stable map.
145
+ */
146
+ productSlugs?: ReadonlyMap<string, string>;
147
+ /**
148
+ * False while the answer is still streaming, which suppresses its images and its video
149
+ * card: a partial is cut mid-token, so a URL in it may be half-written, and the backend has
150
+ * not yet dropped what it does not allow. A settled answer — every published one — is
151
+ * `true`.
94
152
  */
95
153
  enabled?: boolean;
96
154
  /**
@@ -116,7 +174,7 @@ interface ExploreAnswerBodyProps {
116
174
  * The set of images that failed to load is held HERE rather than in each image, so a source
117
175
  * that 404s stays hidden even when the markdown re-renders from scratch underneath it.
118
176
  */
119
- declare function ExploreAnswerBody({ children, strings, imageOrigins, enabled, components, className, }: ExploreAnswerBodyProps): react.JSX.Element;
177
+ declare function ExploreAnswerBody({ children, strings, imageOrigins, videoOrigins, productSlugs, enabled, components, className, }: ExploreAnswerBodyProps): react.JSX.Element;
120
178
 
121
179
  interface ExplorePageProps {
122
180
  /**
@@ -248,4 +306,4 @@ declare const EXPLORE_PREFILL_MAX_LENGTH = 500;
248
306
  declare function exploreQuestionHref(question: string, base?: string): string;
249
307
  declare function ExplorePage({ seo, productSlug, examplePrompts, className, heightMode, viewportOffset, onNavigate, onSend, welcomeTitle, welcomeBody, locale, }: ExplorePageProps): react.JSX.Element;
250
308
 
251
- export { type AnswerRendererStrings as A, EXPLORE_PREFILL_MAX_LENGTH as E, ExploreAnswerBody as a, type ExploreAnswerBodyProps as b, ExploreAnswerStyles as c, ExplorePage as d, type ExplorePageProps as e, answerPlainText as f, answerSpeechText as g, exploreQuestionHref as h, inlineImageOrigins as i, isInlineImageAllowed as j, type ExploreProvenance as k, type ExploreReferenceKind as l, logoUrlOf as m, productNamesByOrigin as p, referenceKindOf as r, summariseReferences as s };
309
+ export { type AnswerRendererStrings as A, EXPLORE_PREFILL_MAX_LENGTH as E, ExploreAnswerBody as a, type ExploreAnswerBodyProps as b, ExploreAnswerStyles as c, ExplorePage as d, type ExplorePageProps as e, answerPlainText as f, answerSpeechText as g, exploreQuestionHref as h, inlineImageOrigins as i, isInlineImageAllowed as j, isInlineVideoAllowed as k, type ExploreProvenance as l, type ExploreReferenceKind as m, logoUrlOf as n, productNamesByOrigin as o, productSlugsByOrigin as p, referenceKindOf as r, summariseReferences as s };
@@ -426,6 +426,30 @@ var EXPLORE_SHARED_CONVERSATION_QUERY = (
426
426
  `
427
427
  );
428
428
 
429
+ // src/utils/product-slug.ts
430
+ var PRODUCT_SLUG_RENAMES = Object.freeze(
431
+ [Object.freeze({ legacy: "botlit", current: "botmanor" })]
432
+ );
433
+ Object.freeze(
434
+ Object.fromEntries(PRODUCT_SLUG_RENAMES.map((r) => [r.legacy, r.current]))
435
+ );
436
+ var PRODUCT_SLUG_EMIT = Object.freeze({ productSlug: "legacy" });
437
+ function findRename(slug) {
438
+ const wanted = slug.toLowerCase();
439
+ return PRODUCT_SLUG_RENAMES.find(
440
+ (r) => r.legacy === wanted || r.current === wanted
441
+ );
442
+ }
443
+ function normalizeProductSlug(slug) {
444
+ return findRename(slug)?.current ?? slug;
445
+ }
446
+ function legacyProductSlug(slug) {
447
+ return findRename(slug)?.legacy ?? slug;
448
+ }
449
+ function emitProductSlug(slug) {
450
+ return PRODUCT_SLUG_EMIT.productSlug === "current" ? normalizeProductSlug(slug) : legacyProductSlug(slug);
451
+ }
452
+
429
453
  // src/data/products.ts
430
454
  var ECOSYSTEM_PRODUCTS = [
431
455
  {
@@ -448,7 +472,12 @@ var ECOSYSTEM_PRODUCTS = [
448
472
  },
449
473
  // BotManor ships on www.botmanor.com (the apex redirects there; the old botlit.ai domain
450
474
  // still serves the same site but is no longer the name we link to).
451
- { slug: "botlit", name: "BotManor", websiteUrl: "https://www.botmanor.com" },
475
+ // The slug follows the botlit → botmanor rename switch (utils/product-slug.ts).
476
+ {
477
+ slug: emitProductSlug("botmanor"),
478
+ name: "BotManor",
479
+ websiteUrl: "https://www.botmanor.com"
480
+ },
452
481
  {
453
482
  slug: "bigconsole",
454
483
  name: "BigConsole",
@@ -2072,7 +2101,7 @@ function isHighSurrogate(code) {
2072
2101
  }
2073
2102
  function safeInlineEnd(line) {
2074
2103
  let codeSpan = null;
2075
- let link = null;
2104
+ const links = [];
2076
2105
  const emphasis = [];
2077
2106
  let at = 0;
2078
2107
  while (at < line.length) {
@@ -2081,6 +2110,13 @@ function safeInlineEnd(line) {
2081
2110
  at += 2;
2082
2111
  continue;
2083
2112
  }
2113
+ const innermost = links[links.length - 1];
2114
+ if (innermost !== void 0 && innermost.kind !== "label") {
2115
+ if (char === "(") links.push({ kind: "paren", start: innermost.start });
2116
+ else if (char === ")") links.pop();
2117
+ at += 1;
2118
+ continue;
2119
+ }
2084
2120
  if (char === "`") {
2085
2121
  let run = 1;
2086
2122
  while (line[at + run] === "`") run += 1;
@@ -2093,17 +2129,17 @@ function safeInlineEnd(line) {
2093
2129
  continue;
2094
2130
  }
2095
2131
  if (char === "[") {
2096
- if (link === null) link = line[at - 1] === "!" ? at - 1 : at;
2132
+ links.push({ kind: "label", start: line[at - 1] === "!" ? at - 1 : at });
2097
2133
  at += 1;
2098
2134
  continue;
2099
2135
  }
2100
- if (char === "]") {
2101
- if (link !== null && line[at + 1] !== "(") link = null;
2102
- at += 1;
2103
- continue;
2104
- }
2105
- if (char === ")") {
2106
- link = null;
2136
+ if (char === "]" && innermost !== void 0) {
2137
+ links.pop();
2138
+ if (line[at + 1] === "(") {
2139
+ links.push({ kind: "destination", start: innermost.start });
2140
+ at += 2;
2141
+ continue;
2142
+ }
2107
2143
  at += 1;
2108
2144
  continue;
2109
2145
  }
@@ -2130,9 +2166,11 @@ function safeInlineEnd(line) {
2130
2166
  }
2131
2167
  at += 1;
2132
2168
  }
2133
- const open = [codeSpan, link, emphasis[0]?.start ?? null].filter(
2134
- (value) => value !== null
2135
- );
2169
+ const open = [
2170
+ codeSpan,
2171
+ links[0]?.start ?? null,
2172
+ emphasis[0]?.start ?? null
2173
+ ].filter((value) => value !== null);
2136
2174
  return open.length === 0 ? line.length : Math.min(...open);
2137
2175
  }
2138
2176
  function revealBoundary(text, limit) {