@burdenoff/website-sdk 2026.1104.3 → 2026.1104.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ // src/components/explore-fill.ts
4
+ function fill(template, values) {
5
+ return template.replace(/\{(\w+)\}/g, (match, key) => values[key] ?? match);
6
+ }
7
+
8
+ // src/components/explore-answer-text.ts
9
+ var CODE_FENCE = /^\s*(?:```|~~~)/;
10
+ var TABLE_ROW_LINE = /^\s{0,3}\|/;
11
+ var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
12
+ var LINKED_IMAGE = /\[!\[([^[\]]*)\]\([^)\s]*(?:\s+"[^"]*")?\)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
13
+ var IMAGE = /!\[[^[\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g;
14
+ var LINK = /\[([^[\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
15
+ var LIST_MARKER = /^(\s*)(?:[-*+]|\d{1,9}[.)])[ \t]+/;
16
+ function spoken(mode) {
17
+ return mode !== "clipboard";
18
+ }
19
+ function readableLine(raw, mode, item = true) {
20
+ let line = raw;
21
+ if (line.includes("](")) {
22
+ line = line.replace(LINKED_IMAGE, (_match, alt, href) => {
23
+ if (spoken(mode)) return "";
24
+ return alt.trim() === "" ? href : `${alt.trim()} (${href})`;
25
+ });
26
+ line = line.replace(IMAGE, "");
27
+ line = spoken(mode) ? (
28
+ // A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
29
+ // prompt asks for two to five links in every answer. The label IS the words.
30
+ line.replace(LINK, "$1")
31
+ ) : line.replace(LINK, "$1 ($2)");
32
+ }
33
+ line = spoken(mode) ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
34
+ line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
35
+ line = line.replace(/^\s{0,3}>\s?/, "");
36
+ if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
37
+ if (mode === "display" && item) line = line.replace(LIST_MARKER, "$1");
38
+ line = line.replace(/`([^`]+)`/g, "$1");
39
+ line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
40
+ line = line.replace(/__([^_]+)__/g, "$1");
41
+ line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
42
+ line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
43
+ return line.replace(/[ \t]+$/, "");
44
+ }
45
+ function tableCells(row) {
46
+ const trimmed = row.trim();
47
+ const cells = [];
48
+ let cell = "";
49
+ for (let at = 0; at < trimmed.length; at += 1) {
50
+ const char = trimmed[at] ?? "";
51
+ if (char === "\\") {
52
+ cell += char + (trimmed[at + 1] ?? "");
53
+ at += 1;
54
+ continue;
55
+ }
56
+ if (char === "|") {
57
+ cells.push(cell.trim());
58
+ cell = "";
59
+ continue;
60
+ }
61
+ cell += char;
62
+ }
63
+ cells.push(cell.trim());
64
+ if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
65
+ if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
66
+ return cells;
67
+ }
68
+ function asSentence(text) {
69
+ return /[.!?]$/.test(text) ? text : `${text}.`;
70
+ }
71
+ function spokenTable(rows) {
72
+ if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
73
+ const labels = tableCells(rows[0] ?? "");
74
+ const spoken2 = [];
75
+ for (const row of rows.slice(2)) {
76
+ const parts = [];
77
+ tableCells(row).forEach((value, column) => {
78
+ if (value === "") return;
79
+ const label = column === 0 ? "" : labels[column] ?? "";
80
+ parts.push(label === "" ? value : `${label}: ${value}`);
81
+ });
82
+ if (parts.length > 0) spoken2.push(asSentence(parts.join(". ")));
83
+ }
84
+ return spoken2;
85
+ }
86
+ function readableText(markdown, mode) {
87
+ const lines = [];
88
+ let inFence = false;
89
+ let table = [];
90
+ const flushTable = () => {
91
+ if (table.length === 0) return;
92
+ for (const line of spokenTable(table)) {
93
+ lines.push(readableLine(line, mode, false));
94
+ }
95
+ table = [];
96
+ };
97
+ for (const raw of markdown.split("\n")) {
98
+ if (CODE_FENCE.test(raw)) {
99
+ flushTable();
100
+ inFence = !inFence;
101
+ continue;
102
+ }
103
+ if (inFence) {
104
+ lines.push(raw);
105
+ continue;
106
+ }
107
+ if (spoken(mode) && TABLE_ROW_LINE.test(raw)) {
108
+ table.push(raw);
109
+ continue;
110
+ }
111
+ flushTable();
112
+ lines.push(readableLine(raw, mode));
113
+ }
114
+ flushTable();
115
+ return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
116
+ }
117
+ function answerPlainText(markdown) {
118
+ return readableText(markdown, "clipboard");
119
+ }
120
+ function answerSpeechText(markdown) {
121
+ return readableText(markdown, "speech");
122
+ }
123
+ function answerDisplayText(markdown) {
124
+ return readableText(markdown, "display");
125
+ }
126
+
127
+ exports.answerDisplayText = answerDisplayText;
128
+ exports.answerPlainText = answerPlainText;
129
+ exports.answerSpeechText = answerSpeechText;
130
+ exports.fill = fill;
131
+ //# sourceMappingURL=chunk-N7E6Z2GF.js.map
132
+ //# sourceMappingURL=chunk-N7E6Z2GF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/explore-fill.ts","../src/components/explore-answer-text.ts"],"names":["spoken"],"mappings":";;;AAMO,SAAS,IAAA,CAAK,UAAkB,MAAA,EAAwC;AAC7E,EAAA,OAAO,QAAA,CAAS,QAAQ,YAAA,EAAc,CAAC,OAAO,GAAA,KAAQ,MAAA,CAAO,GAAG,CAAA,IAAK,KAAK,CAAA;AAC5E;;;ACMA,IAAM,UAAA,GAAa,iBAAA;AAEnB,IAAM,cAAA,GAAiB,YAAA;AAEvB,IAAM,mBAAA,GAAsB,8BAAA;AAuB5B,IAAM,YAAA,GACJ,2EAAA;AAEF,IAAM,KAAA,GAAQ,yCAAA;AAEd,IAAM,IAAA,GAAO,4CAAA;AAEb,IAAM,WAAA,GAAc,mCAAA;AAGpB,SAAS,OAAO,IAAA,EAA4B;AAC1C,EAAA,OAAO,IAAA,KAAS,WAAA;AAClB;AAOA,SAAS,YAAA,CAAa,GAAA,EAAa,IAAA,EAAmB,IAAA,GAAO,IAAA,EAAc;AACzE,EAAA,IAAI,IAAA,GAAO,GAAA;AAGX,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,EAAG;AAMvB,IAAA,IAAA,GAAO,KAAK,OAAA,CAAQ,YAAA,EAAc,CAAC,MAAA,EAAQ,KAAa,IAAA,KAAiB;AACvE,MAAA,IAAI,MAAA,CAAO,IAAI,CAAA,EAAG,OAAO,EAAA;AACzB,MAAA,OAAO,GAAA,CAAI,IAAA,EAAK,KAAM,EAAA,GAAK,IAAA,GAAO,GAAG,GAAA,CAAI,IAAA,EAAM,CAAA,EAAA,EAAK,IAAI,CAAA,CAAA,CAAA;AAAA,IAC1D,CAAC,CAAA;AAED,IAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAC7B,IAAA,IAAA,GAAO,OAAO,IAAI,CAAA;AAAA;AAAA;AAAA,MAGd,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,IAAI;AAAA,QACvB,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,SAAS,CAAA;AAAA,EAClC;AACA,EAAA,IAAA,GAAO,MAAA,CAAO,IAAI,CAAA,GACd,IAAA,CAAK,OAAA,CAAQ,yBAAA,EAA2B,EAAE,CAAA,GAC1C,IAAA,CAAK,OAAA,CAAQ,yBAAA,EAA2B,IAAI,CAAA;AAChD,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,mBAAA,EAAqB,EAAE,CAAA;AAC3C,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,cAAA,EAAgB,EAAE,CAAA;AAEtC,EAAA,IAAI,wCAAA,CAAyC,IAAA,CAAK,IAAI,CAAA,EAAG,OAAO,EAAA;AAIhE,EAAA,IAAI,SAAS,SAAA,IAAa,IAAA,SAAa,IAAA,CAAK,OAAA,CAAQ,aAAa,IAAI,CAAA;AACrE,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,YAAA,EAAc,IAAI,CAAA;AACtC,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,kBAAA,EAAoB,IAAI,CAAA;AAC5C,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,cAAA,EAAgB,IAAI,CAAA;AAGxC,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,mCAAA,EAAqC,MAAM,CAAA;AAC/D,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,iCAAA,EAAmC,MAAM,CAAA;AAC7D,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA;AACnC;AAQA,SAAS,WAAW,GAAA,EAAuB;AACzC,EAAA,MAAM,OAAA,GAAU,IAAI,IAAA,EAAK;AACzB,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,IAAI,IAAA,GAAO,EAAA;AACX,EAAA,KAAA,IAAS,KAAK,CAAA,EAAG,EAAA,GAAK,OAAA,CAAQ,MAAA,EAAQ,MAAM,CAAA,EAAG;AAC7C,IAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,EAAE,CAAA,IAAK,EAAA;AAC5B,IAAA,IAAI,SAAS,IAAA,EAAM;AAEjB,MAAA,IAAA,IAAQ,IAAA,IAAQ,OAAA,CAAQ,EAAA,GAAK,CAAC,CAAA,IAAK,EAAA,CAAA;AACnC,MAAA,EAAA,IAAM,CAAA;AACN,MAAA;AAAA,IACF;AACA,IAAA,IAAI,SAAS,GAAA,EAAK;AAChB,MAAA,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,CAAA;AACtB,MAAA,IAAA,GAAO,EAAA;AACP,MAAA;AAAA,IACF;AACA,IAAA,IAAA,IAAQ,IAAA;AAAA,EACV;AACA,EAAA,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,CAAA;AAGtB,EAAA,IAAI,OAAA,CAAQ,WAAW,GAAG,CAAA,IAAK,MAAM,CAAC,CAAA,KAAM,EAAA,EAAI,KAAA,CAAM,KAAA,EAAM;AAC5D,EAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,GAAG,CAAA,IAAK,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,KAAM,EAAA,EAAI,KAAA,CAAM,GAAA,EAAI;AACvE,EAAA,OAAO,KAAA;AACT;AAGA,SAAS,WAAW,IAAA,EAAsB;AACxC,EAAA,OAAO,SAAS,IAAA,CAAK,IAAI,CAAA,GAAI,IAAA,GAAO,GAAG,IAAI,CAAA,CAAA,CAAA;AAC7C;AAWA,SAAS,YAAY,IAAA,EAA0B;AAC7C,EAAA,IAAI,IAAA,CAAK,MAAA,GAAS,CAAA,IAAK,CAAC,mBAAA,CAAoB,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,IAAK,EAAE,CAAA,EAAG,OAAO,IAAA;AACxE,EAAA,MAAM,MAAA,GAAS,UAAA,CAAW,IAAA,CAAK,CAAC,KAAK,EAAE,CAAA;AACvC,EAAA,MAAMA,UAAmB,EAAC;AAC1B,EAAA,KAAA,MAAW,GAAA,IAAO,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA,EAAG;AAC/B,IAAA,MAAM,QAAkB,EAAC;AACzB,IAAA,UAAA,CAAW,GAAG,CAAA,CAAE,OAAA,CAAQ,CAAC,OAAO,MAAA,KAAW;AACzC,MAAA,IAAI,UAAU,EAAA,EAAI;AAIlB,MAAA,MAAM,QAAQ,MAAA,KAAW,CAAA,GAAI,EAAA,GAAM,MAAA,CAAO,MAAM,CAAA,IAAK,EAAA;AACrD,MAAA,KAAA,CAAM,IAAA,CAAK,UAAU,EAAA,GAAK,KAAA,GAAQ,GAAG,KAAK,CAAA,EAAA,EAAK,KAAK,CAAA,CAAE,CAAA;AAAA,IACxD,CAAC,CAAA;AACD,IAAA,IAAI,KAAA,CAAM,MAAA,GAAS,CAAA,EAAGA,OAAAA,CAAO,IAAA,CAAK,UAAA,CAAW,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAC,CAAA;AAAA,EAChE;AACA,EAAA,OAAOA,OAAAA;AACT;AAEA,SAAS,YAAA,CAAa,UAAkB,IAAA,EAA2B;AACjE,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,IAAI,QAAkB,EAAC;AAEvB,EAAA,MAAM,aAAa,MAAY;AAC7B,IAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACxB,IAAA,KAAA,MAAW,IAAA,IAAQ,WAAA,CAAY,KAAK,CAAA,EAAG;AACrC,MAAA,KAAA,CAAM,IAAA,CAAK,YAAA,CAAa,IAAA,EAAM,IAAA,EAAM,KAAK,CAAC,CAAA;AAAA,IAC5C;AACA,IAAA,KAAA,GAAQ,EAAC;AAAA,EACX,CAAA;AAEA,EAAA,KAAA,MAAW,GAAA,IAAO,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,EAAG;AACtC,IAAA,IAAI,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA,EAAG;AACxB,MAAA,UAAA,EAAW;AACX,MAAA,OAAA,GAAU,CAAC,OAAA;AACX,MAAA;AAAA,IACF;AACA,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,KAAA,CAAM,KAAK,GAAG,CAAA;AACd,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,OAAO,IAAI,CAAA,IAAK,cAAA,CAAe,IAAA,CAAK,GAAG,CAAA,EAAG;AAC5C,MAAA,KAAA,CAAM,KAAK,GAAG,CAAA;AACd,MAAA;AAAA,IACF;AACA,IAAA,UAAA,EAAW;AACX,IAAA,KAAA,CAAM,IAAA,CAAK,YAAA,CAAa,GAAA,EAAK,IAAI,CAAC,CAAA;AAAA,EACpC;AACA,EAAA,UAAA,EAAW;AAEX,EAAA,OAAO,KAAA,CACJ,KAAK,IAAI,CAAA,CACT,QAAQ,SAAA,EAAW,MAAM,EACzB,IAAA,EAAK;AACV;AA8BO,SAAS,gBAAgB,QAAA,EAA0B;AACxD,EAAA,OAAO,YAAA,CAAa,UAAU,WAAW,CAAA;AAC3C;AAwBO,SAAS,iBAAiB,QAAA,EAA0B;AACzD,EAAA,OAAO,YAAA,CAAa,UAAU,QAAQ,CAAA;AACxC;AAcO,SAAS,kBAAkB,QAAA,EAA0B;AAC1D,EAAA,OAAO,YAAA,CAAa,UAAU,SAAS,CAAA;AACzC","file":"chunk-N7E6Z2GF.js","sourcesContent":["/**\n * Substitute `{product}` and friends without pulling in a formatting library.\n *\n * A module of its own so the live avatar's view, which is loaded on its own (see `explore-avatar-live.tsx`), can use\n * it without carrying `explore-i18n.ts`, the panel's strings. `explore-i18n.ts` re-exports it.\n */\nexport function fill(template: string, values: Record<string, string>): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, key) => values[key] ?? match);\n}\n","/**\n * An answer as readable text: the words of `ExploreMessage.content` (GitHub-flavoured markdown, written by the explore\n * harness: `dev/platform/context/publishing/explore/CONTRACT.md` §7) without the markup that renders them. Three readers,\n * one implementation: the clipboard ({@link answerPlainText}), a voice ({@link answerSpeechText}), and a screen that\n * shows a voice's words as text ({@link answerDisplayText}).\n *\n * Split out of `explore-answer.tsx`, which re-exports the first two where they always were, so that a surface that\n * shows an answer as text can have them without the renderer. The third is the live avatar's view's own, and not part of\n * the package's API. The live avatar's view is a chunk of its own (BOFF-9797), and an\n * import from the renderer's module ties that chunk to the renderer's: the markdown components, the video player and\n * `react-markdown` behind them. This module imports nothing, React included.\n */\n\n/** A ``` or ~~~ fence, which opens and closes a verbatim block. */\nconst CODE_FENCE = /^\\s*(?:```|~~~)/;\n/** A GFM table row. Every row the harness writes starts with a pipe (CONTRACT.md §7). */\nconst TABLE_ROW_LINE = /^\\s{0,3}\\|/;\n/** The `| --- | :-: |` row, without which the rows around it are not a table at all. */\nconst TABLE_DELIMITER_ROW = /^\\s{0,3}\\|[\\s:|-]*-[\\s:|-]*$/;\n\n/**\n * Who the stripped text is for. The clipboard and a voice differ in exactly three places —\n * links, tables and the product-video line — and nowhere else, which is why they are one\n * function and not two. A display is a voice's words shown as text, and differs from a voice\n * in one place more: list markers.\n */\ntype ReadableFor = \"clipboard\" | \"speech\" | \"display\";\n\n/*\n * The inline patterns below. Every bracketed part is `[^[\\]]`, not `[^\\]]`: excluding `[`\n * ends each attempt at the next bracket, where `[^\\]]` ran to the end of the line and back\n * for every `[` in it — quadratic on a line of unclosed `[![`. A label with nested brackets\n * matched neither class (its first `]` is not followed by `(`), and an unclosed `[` before a\n * link is now left as the literal bracket CommonMark renders it as, not folded into the label.\n */\n\n/**\n * A link whose whole label is an image: `[![alt](image)](target)` — the product-video line\n * (CONTRACT.md §7) when the target is a video file, a linked illustration otherwise. Captures\n * the alt text and the target.\n */\nconst LINKED_IMAGE =\n /\\[!\\[([^[\\]]*)\\]\\([^)\\s]*(?:\\s+\"[^\"]*\")?\\)\\]\\(([^)\\s]+)(?:\\s+\"[^\"]*\")?\\)/g;\n/** `![alt](src)`. */\nconst IMAGE = /!\\[[^[\\]]*\\]\\([^)\\s]*(?:\\s+\"[^\"]*\")?\\)/g;\n/** `[label](href)`. Captures both. */\nconst LINK = /\\[([^[\\]]+)\\]\\(([^)\\s]+)(?:\\s+\"[^\"]*\")?\\)/g;\n/** A list item's marker, `- `, `* `, `+ `, `1. ` or `1) `. Captures the indentation before it. */\nconst LIST_MARKER = /^(\\s*)(?:[-*+]|\\d{1,9}[.)])[ \\t]+/;\n\n/** Whether `mode` is a voice's words: read out, or shown as they are read out. */\nfunction spoken(mode: ReadableFor): boolean {\n return mode !== \"clipboard\";\n}\n\n/**\n * One line with its inline markup removed. A thematic break comes back as a blank line. `item`\n * is false for a line that cannot be a list item: a table's row, made a sentence by\n * {@link spokenTable}.\n */\nfunction readableLine(raw: string, mode: ReadableFor, item = true): string {\n let line = raw;\n // Every pattern here needs `](`. Most lines of an answer have none, and skipping them is\n // what keeps a long paragraph of brackets from costing a pass per pattern.\n if (line.includes(\"](\")) {\n // A linked image before images and links, because it is both: taken apart by the two\n // rules below, the image would go as images do and leave `[](https://…)` behind, which\n // no link pattern matches — and a voice would then spell the address out. What is left\n // is a link whose label is the image's alt text: `Planmagnet product video (https://…mp4)`\n // on the clipboard, and nothing in speech, where an image is never read.\n line = line.replace(LINKED_IMAGE, (_match, alt: string, href: string) => {\n if (spoken(mode)) return \"\";\n return alt.trim() === \"\" ? href : `${alt.trim()} (${href})`;\n });\n // Images before links: `![alt](src)` also matches the link pattern.\n line = line.replace(IMAGE, \"\");\n line = spoken(mode)\n ? // A voice reading \"h-t-t-p-s colon slash slash…\" mid-sentence is noise, and the\n // prompt asks for two to five links in every answer. The label IS the words.\n line.replace(LINK, \"$1\")\n : line.replace(LINK, \"$1 ($2)\");\n }\n line = spoken(mode)\n ? line.replace(/<(https?:\\/\\/[^>\\s]+)>/g, \"\")\n : line.replace(/<(https?:\\/\\/[^>\\s]+)>/g, \"$1\");\n line = line.replace(/^\\s{0,3}#{1,6}\\s+/, \"\");\n line = line.replace(/^\\s{0,3}>\\s?/, \"\");\n // A thematic break is a horizontal rule; as text it is three hyphens meaning nothing.\n if (/^\\s{0,3}([-*_])[ \\t]*(?:\\1[ \\t]*){2,}$/.test(line)) return \"\";\n // Shown without a renderer, in a paragraph that keeps its line breaks, a list item is a\n // line of its own already: its marker is a renderer's mark, as `**` is. Its indentation\n // stays, so a nested item still reads as one. After the rule above: `* * *` is a rule.\n if (mode === \"display\" && item) line = line.replace(LIST_MARKER, \"$1\");\n line = line.replace(/`([^`]+)`/g, \"$1\");\n line = line.replace(/\\*\\*([^*]+)\\*\\*/g, \"$1\");\n line = line.replace(/__([^_]+)__/g, \"$1\");\n // Single-delimiter emphasis only when it wraps a run, so a `* ` bullet and a\n // snake_case_identifier are both left alone.\n line = line.replace(/(^|[^*\\w])\\*([^*\\n]+)\\*(?![*\\w])/g, \"$1$2\");\n line = line.replace(/(^|[^_\\w])_([^_\\n]+)_(?![_\\w])/g, \"$1$2\");\n return line.replace(/[ \\t]+$/, \"\");\n}\n\n/**\n * The cells of one table row, in column order.\n *\n * Split by hand rather than with a lookbehind for `\\|`: a lookbehind is a parse error on\n * Safari before 16.4, and a regex literal cannot be caught — the whole bundle fails to load.\n */\nfunction tableCells(row: string): string[] {\n const trimmed = row.trim();\n const cells: string[] = [];\n let cell = \"\";\n for (let at = 0; at < trimmed.length; at += 1) {\n const char = trimmed[at] ?? \"\";\n if (char === \"\\\\\") {\n // An escaped pipe is a pipe in the cell's text, not a column break.\n cell += char + (trimmed[at + 1] ?? \"\");\n at += 1;\n continue;\n }\n if (char === \"|\") {\n cells.push(cell.trim());\n cell = \"\";\n continue;\n }\n cell += char;\n }\n cells.push(cell.trim());\n // `| a | b |` splits to an empty element outside each edge pipe. Only empty ones go: a\n // row whose FIRST cell is genuinely blank keeps it, and so keeps its columns aligned.\n if (trimmed.startsWith(\"|\") && cells[0] === \"\") cells.shift();\n if (trimmed.endsWith(\"|\") && cells[cells.length - 1] === \"\") cells.pop();\n return cells;\n}\n\n/** `a. b` reads as one thought; `a. b.` reads as two finished ones. */\nfunction asSentence(text: string): string {\n return /[.!?]$/.test(text) ? text : `${text}.`;\n}\n\n/**\n * A table as something a voice can say: one sentence per row, every cell under its own\n * column heading, exactly as the phone layout prints it (`AnswerTableRow` in `explore-answer.tsx`,\n * `data-label`).\n * The header row itself is not spoken — it IS the labels — and neither is the `| --- |` row.\n *\n * Rows that are not a table (no delimiter row) are handed back untouched: they render as a\n * paragraph of pipes, and reading them as a table would be reading something nobody can see.\n */\nfunction spokenTable(rows: string[]): string[] {\n if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? \"\")) return rows;\n const labels = tableCells(rows[0] ?? \"\");\n const spoken: string[] = [];\n for (const row of rows.slice(2)) {\n const parts: string[] = [];\n tableCells(row).forEach((value, column) => {\n if (value === \"\") return;\n // The first cell is what the row is ABOUT — the plan, or the product. Saying \"Plan:\"\n // in front of the plan's own name adds a word and no information, which is why the\n // card layout hides that label too.\n const label = column === 0 ? \"\" : (labels[column] ?? \"\");\n parts.push(label === \"\" ? value : `${label}: ${value}`);\n });\n if (parts.length > 0) spoken.push(asSentence(parts.join(\". \")));\n }\n return spoken;\n}\n\nfunction readableText(markdown: string, mode: ReadableFor): string {\n const lines: string[] = [];\n let inFence = false;\n let table: string[] = [];\n\n const flushTable = (): void => {\n if (table.length === 0) return;\n for (const line of spokenTable(table)) {\n lines.push(readableLine(line, mode, false));\n }\n table = [];\n };\n\n for (const raw of markdown.split(\"\\n\")) {\n if (CODE_FENCE.test(raw)) {\n flushTable();\n inFence = !inFence;\n continue;\n }\n if (inFence) {\n lines.push(raw);\n continue;\n }\n // A table has to be read as a block — a cell means nothing without its column heading,\n // which is on another line. Only a voice's words need that; the clipboard wants the table.\n if (spoken(mode) && TABLE_ROW_LINE.test(raw)) {\n table.push(raw);\n continue;\n }\n flushTable();\n lines.push(readableLine(raw, mode));\n }\n flushTable();\n\n return lines\n .join(\"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\n/**\n * An answer as READABLE TEXT, for the clipboard.\n *\n * What counts as \"our internal markup artefacts\", and why each choice:\n *\n * - **Image embeds go.** `![Concept illustration of …](https://…/01.webp)` is a picture this\n * page injected into the answer; pasted into an email it is a URL and an alt string, and\n * neither is the answer. The caption line the backend writes under it is ordinary prose and\n * stays.\n * - **Links become `label (url)`.** The destination is information the reader may need; the\n * bracket-paren syntax is not. Dropping the URL would quietly strip the page it came from.\n * - **A linked image is a link like any other**, labelled by the image's alt text — so the\n * product video is `Planmagnet product video (https://…mp4)`. The image itself goes as\n * every image does; the link is kept because, unlike a bare illustration, it is somewhere\n * the reader can GO — and a pasted answer has no card to click.\n * - **Citation markers `[3]` stay.** They are the only attribution the copied text carries,\n * and they are the answer's own — not something the UI added. The sources they point at are\n * listed right next to the button that copied this.\n * - **Emphasis, headings, blockquote and rule markers go**, because `**` and `###` are\n * instructions to a renderer, not words.\n * - **A table keeps its pipes.** Pasted into a README, an issue or a chat it is a table\n * again; flattened into sentences it is not.\n * - **Fenced code is verbatim**, fences included, minus nothing but the fence lines\n * themselves: stripping `*` or `_` inside a code block would corrupt the code.\n *\n * Built from `message.content` rather than from the rendered DOM: `textContent` on the bubble\n * would fold every paragraph, list item and table cell into one run-on line.\n */\nexport function answerPlainText(markdown: string): string {\n return readableText(markdown, \"clipboard\");\n}\n\n/**\n * The same answer as something to SAY, for read-aloud.\n *\n * It shares every rule above but the three that are right for a paste and wrong for a voice:\n *\n * - **A link is its label.** The clipboard keeps `label (https://…)` because a pasted answer\n * loses the page otherwise. Spoken, that is a URL spelled out character by character in\n * the middle of a sentence — and the prompt asks for two to five links in every answer, so\n * it is not an edge case. The sources are listed under the answer either way.\n * - **A linked image is not read at all** — the product video included. Its label is a\n * picture, which is never read, and its link is an address; the card or illustration is\n * on screen for anyone who can click it.\n * - **A table is read as rows, not as pipes.** `| --- | --- |` is a line of punctuation and\n * the pipes are column rules, neither of which is a word. Each row is read with its cells\n * under their own column headings — the phone layout, out loud — so a plan list still says\n * which number is the price. Comparisons and plan lists are exactly the answers the\n * feature was built for (CONTRACT.md §7), which is what makes this worth a second mode.\n *\n * Everything else stays deliberately identical, including citation markers: they are the\n * answer's own attribution in both, and a listener hearing \"two\" after a claim is hearing\n * the same thing a reader sees.\n */\nexport function answerSpeechText(markdown: string): string {\n return readableText(markdown, \"speech\");\n}\n\n/**\n * The same words SHOWN, where nothing renders the answer: the live avatar's view, which shows\n * it as text in a paragraph that keeps its line breaks (BOFF-9797). In the form a voice says\n * it, as the avatar's captions show an answer: a link is its label, and no address sits in\n * the middle of a sentence.\n *\n * It shares every rule of {@link answerSpeechText} and adds one: **list markers go.** On a\n * screen with no renderer, `- `, `* `, `+ ` and `1. ` are marks like `**`, and each item is a\n * line of its own without them. Neither form above drops them: pasted, the lines are a list\n * again, and Chat's read-aloud says the answer as it always has. A table's rows are sentences\n * already, and fenced code is verbatim, so neither loses anything that looks like a marker.\n */\nexport function answerDisplayText(markdown: string): string {\n return readableText(markdown, \"display\");\n}\n"]}
@@ -1,4 +1,4 @@
1
1
  import 'react';
2
- export { a as EXPLORE_PREFILL_MAX_LENGTH, e as ExplorePage, f as ExplorePageProps, m as ExploreProvenance, n as ExploreReferenceKind, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, o as logoUrlOf, q as productNamesByOrigin, r as referenceKindOf, s as summariseReferences } from '../explore-BgEeGPvY.mjs';
2
+ export { a as EXPLORE_PREFILL_MAX_LENGTH, e as ExplorePage, f as ExplorePageProps, m as ExploreProvenance, n as ExploreReferenceKind, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, o as logoUrlOf, q as productNamesByOrigin, r as referenceKindOf, s as summariseReferences } from '../explore-ei8uoko9.mjs';
3
3
  import '../explore-types-Ol1Mm3dW.mjs';
4
4
  import 'react-markdown';
@@ -1,4 +1,4 @@
1
1
  import 'react';
2
- export { a as EXPLORE_PREFILL_MAX_LENGTH, e as ExplorePage, f as ExplorePageProps, m as ExploreProvenance, n as ExploreReferenceKind, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, o as logoUrlOf, q as productNamesByOrigin, r as referenceKindOf, s as summariseReferences } from '../explore-BV4dRwoC.js';
2
+ export { a as EXPLORE_PREFILL_MAX_LENGTH, e as ExplorePage, f as ExplorePageProps, m as ExploreProvenance, n as ExploreReferenceKind, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, o as logoUrlOf, q as productNamesByOrigin, r as referenceKindOf, s as summariseReferences } from '../explore-Dfru4j4m.js';
3
3
  import '../explore-types-Ol1Mm3dW.js';
4
4
  import 'react-markdown';
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunk22OT6E26_js = require('../chunk-22OT6E26.js');
4
- require('../chunk-O4GS3XPZ.js');
3
+ var chunkMBVXDFVC_js = require('../chunk-MBVXDFVC.js');
4
+ var chunkN7E6Z2GF_js = require('../chunk-N7E6Z2GF.js');
5
5
  require('../chunk-P5GMVPR6.js');
6
6
  require('../chunk-PHXAGQNB.js');
7
7
  require('../chunk-3LFFY7AZ.js');
@@ -18,51 +18,51 @@ require('../chunk-ZSTFX7FV.js');
18
18
 
19
19
  Object.defineProperty(exports, "EXPLORE_PREFILL_MAX_LENGTH", {
20
20
  enumerable: true,
21
- get: function () { return chunk22OT6E26_js.EXPLORE_PREFILL_MAX_LENGTH; }
21
+ get: function () { return chunkMBVXDFVC_js.EXPLORE_PREFILL_MAX_LENGTH; }
22
22
  });
23
23
  Object.defineProperty(exports, "ExplorePage", {
24
24
  enumerable: true,
25
- get: function () { return chunk22OT6E26_js.ExplorePage; }
26
- });
27
- Object.defineProperty(exports, "answerPlainText", {
28
- enumerable: true,
29
- get: function () { return chunk22OT6E26_js.answerPlainText; }
30
- });
31
- Object.defineProperty(exports, "answerSpeechText", {
32
- enumerable: true,
33
- get: function () { return chunk22OT6E26_js.answerSpeechText; }
25
+ get: function () { return chunkMBVXDFVC_js.ExplorePage; }
34
26
  });
35
27
  Object.defineProperty(exports, "exploreQuestionHref", {
36
28
  enumerable: true,
37
- get: function () { return chunk22OT6E26_js.exploreQuestionHref; }
29
+ get: function () { return chunkMBVXDFVC_js.exploreQuestionHref; }
38
30
  });
39
31
  Object.defineProperty(exports, "inlineImageOrigins", {
40
32
  enumerable: true,
41
- get: function () { return chunk22OT6E26_js.inlineImageOrigins; }
33
+ get: function () { return chunkMBVXDFVC_js.inlineImageOrigins; }
42
34
  });
43
35
  Object.defineProperty(exports, "isInlineImageAllowed", {
44
36
  enumerable: true,
45
- get: function () { return chunk22OT6E26_js.isInlineImageAllowed; }
37
+ get: function () { return chunkMBVXDFVC_js.isInlineImageAllowed; }
46
38
  });
47
39
  Object.defineProperty(exports, "isInlineVideoAllowed", {
48
40
  enumerable: true,
49
- get: function () { return chunk22OT6E26_js.isInlineVideoAllowed; }
41
+ get: function () { return chunkMBVXDFVC_js.isInlineVideoAllowed; }
50
42
  });
51
43
  Object.defineProperty(exports, "logoUrlOf", {
52
44
  enumerable: true,
53
- get: function () { return chunk22OT6E26_js.logoUrlOf; }
45
+ get: function () { return chunkMBVXDFVC_js.logoUrlOf; }
54
46
  });
55
47
  Object.defineProperty(exports, "productNamesByOrigin", {
56
48
  enumerable: true,
57
- get: function () { return chunk22OT6E26_js.productNamesByOrigin; }
49
+ get: function () { return chunkMBVXDFVC_js.productNamesByOrigin; }
58
50
  });
59
51
  Object.defineProperty(exports, "referenceKindOf", {
60
52
  enumerable: true,
61
- get: function () { return chunk22OT6E26_js.referenceKindOf; }
53
+ get: function () { return chunkMBVXDFVC_js.referenceKindOf; }
62
54
  });
63
55
  Object.defineProperty(exports, "summariseReferences", {
64
56
  enumerable: true,
65
- get: function () { return chunk22OT6E26_js.summariseReferences; }
57
+ get: function () { return chunkMBVXDFVC_js.summariseReferences; }
58
+ });
59
+ Object.defineProperty(exports, "answerPlainText", {
60
+ enumerable: true,
61
+ get: function () { return chunkN7E6Z2GF_js.answerPlainText; }
62
+ });
63
+ Object.defineProperty(exports, "answerSpeechText", {
64
+ enumerable: true,
65
+ get: function () { return chunkN7E6Z2GF_js.answerSpeechText; }
66
66
  });
67
67
  //# sourceMappingURL=explore.js.map
68
68
  //# sourceMappingURL=explore.js.map
@@ -1,5 +1,5 @@
1
- export { EXPLORE_PREFILL_MAX_LENGTH, ExplorePage, answerPlainText, answerSpeechText, exploreQuestionHref, inlineImageOrigins, isInlineImageAllowed, isInlineVideoAllowed, logoUrlOf, productNamesByOrigin, referenceKindOf, summariseReferences } from '../chunk-5JQGE3XI.mjs';
2
- import '../chunk-TT3MHN42.mjs';
1
+ export { EXPLORE_PREFILL_MAX_LENGTH, ExplorePage, exploreQuestionHref, inlineImageOrigins, isInlineImageAllowed, isInlineVideoAllowed, logoUrlOf, productNamesByOrigin, referenceKindOf, summariseReferences } from '../chunk-7HE33NBS.mjs';
2
+ export { answerPlainText, answerSpeechText } from '../chunk-JZS6FQT6.mjs';
3
3
  import '../chunk-SMD7MEEA.mjs';
4
4
  import '../chunk-SKIYDKIS.mjs';
5
5
  import '../chunk-6VJT3SM4.mjs';
@@ -14,39 +14,17 @@ interface ExploreVideoStrings {
14
14
  }
15
15
 
16
16
  /**
17
- * The answer stylesheet as an element. Safe to mount more than once — the rules are
18
- * idempotent — so a page that shows answers and nothing else still gets them.
19
- */
20
- declare function ExploreAnswerStyles(): react.JSX.Element;
21
- /**
22
- * Origins an answer may embed images from: every catalog product's website, apex and
23
- * `www.` alike (BotManor ships on www.botmanor.com, everything else on the apex, and either can
24
- * appear in the corpus). https only.
25
- */
26
- declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "website">[]): Set<string>;
27
- /**
28
- * True when `src` is an https URL on a catalog product site. The backend already strips
29
- * every image it did not put in the answer; this is the browser's own check, so a model
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)`.
35
- */
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`).
17
+ * An answer as readable text: the words of `ExploreMessage.content` (GitHub-flavoured markdown, written by the explore
18
+ * harness: `dev/platform/context/publishing/explore/CONTRACT.md` §7) without the markup that renders them. Three readers,
19
+ * one implementation: the clipboard ({@link answerPlainText}), a voice ({@link answerSpeechText}), and a screen that
20
+ * shows a voice's words as text ({@link answerDisplayText}).
44
21
  *
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`.
22
+ * Split out of `explore-answer.tsx`, which re-exports the first two where they always were, so that a surface that
23
+ * shows an answer as text can have them without the renderer. The third is the live avatar's view's own, and not part of
24
+ * the package's API. The live avatar's view is a chunk of its own (BOFF-9797), and an
25
+ * import from the renderer's module ties that chunk to the renderer's: the markdown components, the video player and
26
+ * `react-markdown` behind them. This module imports nothing, React included.
48
27
  */
49
- declare function productSlugsByOrigin(products: readonly Pick<ExploreProduct, "slug" | "website">[]): Map<string, string>;
50
28
  /**
51
29
  * An answer as READABLE TEXT, for the clipboard.
52
30
  *
@@ -99,6 +77,42 @@ declare function answerPlainText(markdown: string): string;
99
77
  * the same thing a reader sees.
100
78
  */
101
79
  declare function answerSpeechText(markdown: string): string;
80
+
81
+ /**
82
+ * The answer stylesheet as an element. Safe to mount more than once — the rules are
83
+ * idempotent — so a page that shows answers and nothing else still gets them.
84
+ */
85
+ declare function ExploreAnswerStyles(): react.JSX.Element;
86
+ /**
87
+ * Origins an answer may embed images from: every catalog product's website, apex and
88
+ * `www.` alike (BotManor ships on www.botmanor.com, everything else on the apex, and either can
89
+ * appear in the corpus). https only.
90
+ */
91
+ declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "website">[]): Set<string>;
92
+ /**
93
+ * True when `src` is an https URL on a catalog product site. The backend already strips
94
+ * every image it did not put in the answer; this is the browser's own check, so a model
95
+ * that slips an arbitrary URL past it still cannot make the visitor's browser fetch it.
96
+ *
97
+ * A video or caption file is refused even on a catalog site: as an `<img src>` it downloads
98
+ * video bytes into a transcript that promises never to preload one (§7), and draws nothing.
99
+ * That covers a product-video card's poster as well as a plain `![..](..mp4)`.
100
+ */
101
+ declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>): src is string;
102
+ /**
103
+ * True when `href` may become a product-video card: the SAME origin rule as an inline image
104
+ * (https, no credentials, a catalog product site), and a path that is a playable video file.
105
+ */
106
+ declare function isInlineVideoAllowed(href: unknown, origins: ReadonlySet<string>): href is string;
107
+ /**
108
+ * Apex origin → catalog slug, so a video card can say WHICH product it is (`data-product`).
109
+ *
110
+ * The file's host is not the answer on its own: HealthyBowl ships on gethealthybowl.com and
111
+ * Workspaces on burdenoff.com, and an e2e selector written against the slug would miss both.
112
+ * First entry wins, as in `productNamesByOrigin`.
113
+ */
114
+ declare function productSlugsByOrigin(products: readonly Pick<ExploreProduct, "slug" | "website">[]): Map<string, string>;
115
+
102
116
  /**
103
117
  * The strings the markdown overrides render.
104
118
  *
@@ -14,39 +14,17 @@ interface ExploreVideoStrings {
14
14
  }
15
15
 
16
16
  /**
17
- * The answer stylesheet as an element. Safe to mount more than once — the rules are
18
- * idempotent — so a page that shows answers and nothing else still gets them.
19
- */
20
- declare function ExploreAnswerStyles(): react.JSX.Element;
21
- /**
22
- * Origins an answer may embed images from: every catalog product's website, apex and
23
- * `www.` alike (BotManor ships on www.botmanor.com, everything else on the apex, and either can
24
- * appear in the corpus). https only.
25
- */
26
- declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "website">[]): Set<string>;
27
- /**
28
- * True when `src` is an https URL on a catalog product site. The backend already strips
29
- * every image it did not put in the answer; this is the browser's own check, so a model
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)`.
35
- */
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`).
17
+ * An answer as readable text: the words of `ExploreMessage.content` (GitHub-flavoured markdown, written by the explore
18
+ * harness: `dev/platform/context/publishing/explore/CONTRACT.md` §7) without the markup that renders them. Three readers,
19
+ * one implementation: the clipboard ({@link answerPlainText}), a voice ({@link answerSpeechText}), and a screen that
20
+ * shows a voice's words as text ({@link answerDisplayText}).
44
21
  *
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`.
22
+ * Split out of `explore-answer.tsx`, which re-exports the first two where they always were, so that a surface that
23
+ * shows an answer as text can have them without the renderer. The third is the live avatar's view's own, and not part of
24
+ * the package's API. The live avatar's view is a chunk of its own (BOFF-9797), and an
25
+ * import from the renderer's module ties that chunk to the renderer's: the markdown components, the video player and
26
+ * `react-markdown` behind them. This module imports nothing, React included.
48
27
  */
49
- declare function productSlugsByOrigin(products: readonly Pick<ExploreProduct, "slug" | "website">[]): Map<string, string>;
50
28
  /**
51
29
  * An answer as READABLE TEXT, for the clipboard.
52
30
  *
@@ -99,6 +77,42 @@ declare function answerPlainText(markdown: string): string;
99
77
  * the same thing a reader sees.
100
78
  */
101
79
  declare function answerSpeechText(markdown: string): string;
80
+
81
+ /**
82
+ * The answer stylesheet as an element. Safe to mount more than once — the rules are
83
+ * idempotent — so a page that shows answers and nothing else still gets them.
84
+ */
85
+ declare function ExploreAnswerStyles(): react.JSX.Element;
86
+ /**
87
+ * Origins an answer may embed images from: every catalog product's website, apex and
88
+ * `www.` alike (BotManor ships on www.botmanor.com, everything else on the apex, and either can
89
+ * appear in the corpus). https only.
90
+ */
91
+ declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "website">[]): Set<string>;
92
+ /**
93
+ * True when `src` is an https URL on a catalog product site. The backend already strips
94
+ * every image it did not put in the answer; this is the browser's own check, so a model
95
+ * that slips an arbitrary URL past it still cannot make the visitor's browser fetch it.
96
+ *
97
+ * A video or caption file is refused even on a catalog site: as an `<img src>` it downloads
98
+ * video bytes into a transcript that promises never to preload one (§7), and draws nothing.
99
+ * That covers a product-video card's poster as well as a plain `![..](..mp4)`.
100
+ */
101
+ declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>): src is string;
102
+ /**
103
+ * True when `href` may become a product-video card: the SAME origin rule as an inline image
104
+ * (https, no credentials, a catalog product site), and a path that is a playable video file.
105
+ */
106
+ declare function isInlineVideoAllowed(href: unknown, origins: ReadonlySet<string>): href is string;
107
+ /**
108
+ * Apex origin → catalog slug, so a video card can say WHICH product it is (`data-product`).
109
+ *
110
+ * The file's host is not the answer on its own: HealthyBowl ships on gethealthybowl.com and
111
+ * Workspaces on burdenoff.com, and an e2e selector written against the slug would miss both.
112
+ * First entry wins, as in `productNamesByOrigin`.
113
+ */
114
+ declare function productSlugsByOrigin(products: readonly Pick<ExploreProduct, "slug" | "website">[]): Map<string, string>;
115
+
102
116
  /**
103
117
  * The strings the markdown overrides render.
104
118
  *
package/dist/index.d.mts CHANGED
@@ -20,8 +20,8 @@ export { DetectedPlatform, ElectronDownloadLink, ElectronDownloadLinkProps, dete
20
20
  import { Components } from 'react-markdown';
21
21
  import { a as ExploreLocale, E as ExplorePanelAvatarLive } from './explore-avatar-live-config-B6_p0q1c.mjs';
22
22
  export { b as EXPLORE_LOCALES, c as ExploreStrings, s as speechLocaleOf } from './explore-avatar-live-config-B6_p0q1c.mjs';
23
- import { A as AnswerRendererStrings, E as ExploreScope } from './explore-BgEeGPvY.mjs';
24
- export { a as EXPLORE_PREFILL_MAX_LENGTH, b as ExploreAnswerBody, c as ExploreAnswerBodyProps, d as ExploreAnswerStyles, e as ExplorePage, f as ExplorePageProps, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, p as productSlugsByOrigin } from './explore-BgEeGPvY.mjs';
23
+ import { A as AnswerRendererStrings, E as ExploreScope } from './explore-ei8uoko9.mjs';
24
+ export { a as EXPLORE_PREFILL_MAX_LENGTH, b as ExploreAnswerBody, c as ExploreAnswerBodyProps, d as ExploreAnswerStyles, e as ExplorePage, f as ExplorePageProps, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, p as productSlugsByOrigin } from './explore-ei8uoko9.mjs';
25
25
  export { ExploreCta, ExploreCtaProps } from './components/explore-cta.mjs';
26
26
  import { h as UseExploreChatResult, S as StagedAttachment } from './use-explore-chat-CbfohA9O.mjs';
27
27
  export { D as DEFAULT_EXPLORE_EXAMPLE_PROMPTS, E as ExploreChatError, a as ExploreChatErrorKind, b as ExploreChatPhase, c as ExploreEscalateOutcome, d as ExploreEscalateResult, e as ExploreResent, f as ExploreSendOptions, g as ExploreSent, F as FALLBACK_EXPLORE_LIMITS, U as UseExploreChatOptions, i as classifyExploreError, u as useExploreChat } from './use-explore-chat-CbfohA9O.mjs';
package/dist/index.d.ts CHANGED
@@ -20,8 +20,8 @@ export { DetectedPlatform, ElectronDownloadLink, ElectronDownloadLinkProps, dete
20
20
  import { Components } from 'react-markdown';
21
21
  import { a as ExploreLocale, E as ExplorePanelAvatarLive } from './explore-avatar-live-config-DRpS-mqQ.js';
22
22
  export { b as EXPLORE_LOCALES, c as ExploreStrings, s as speechLocaleOf } from './explore-avatar-live-config-DRpS-mqQ.js';
23
- import { A as AnswerRendererStrings, E as ExploreScope } from './explore-BV4dRwoC.js';
24
- export { a as EXPLORE_PREFILL_MAX_LENGTH, b as ExploreAnswerBody, c as ExploreAnswerBodyProps, d as ExploreAnswerStyles, e as ExplorePage, f as ExplorePageProps, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, p as productSlugsByOrigin } from './explore-BV4dRwoC.js';
23
+ import { A as AnswerRendererStrings, E as ExploreScope } from './explore-Dfru4j4m.js';
24
+ export { a as EXPLORE_PREFILL_MAX_LENGTH, b as ExploreAnswerBody, c as ExploreAnswerBodyProps, d as ExploreAnswerStyles, e as ExplorePage, f as ExplorePageProps, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, p as productSlugsByOrigin } from './explore-Dfru4j4m.js';
25
25
  export { ExploreCta, ExploreCtaProps } from './components/explore-cta.js';
26
26
  import { h as UseExploreChatResult, S as StagedAttachment } from './use-explore-chat-B3dOOvJN.js';
27
27
  export { D as DEFAULT_EXPLORE_EXAMPLE_PROMPTS, E as ExploreChatError, a as ExploreChatErrorKind, b as ExploreChatPhase, c as ExploreEscalateOutcome, d as ExploreEscalateResult, e as ExploreResent, f as ExploreSendOptions, g as ExploreSent, F as FALLBACK_EXPLORE_LIMITS, U as UseExploreChatOptions, i as classifyExploreError, u as useExploreChat } from './use-explore-chat-B3dOOvJN.js';