@arbidocs/react 0.4.4 → 0.4.6

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 (76) hide show
  1. package/README.md +23 -0
  2. package/dist/artifacts/index.cjs +458 -0
  3. package/dist/artifacts/index.cjs.map +1 -0
  4. package/dist/artifacts/index.d.cts +276 -0
  5. package/dist/artifacts/index.d.ts +276 -0
  6. package/dist/artifacts/index.js +434 -0
  7. package/dist/artifacts/index.js.map +1 -0
  8. package/dist/artifacts/redline-core.cjs +20 -0
  9. package/dist/artifacts/redline-core.cjs.map +1 -0
  10. package/dist/artifacts/redline-core.d.cts +36 -0
  11. package/dist/artifacts/redline-core.d.ts +36 -0
  12. package/dist/artifacts/redline-core.js +3 -0
  13. package/dist/artifacts/redline-core.js.map +1 -0
  14. package/dist/{chunk-MIJRORQL.js → chunk-2ACHNDO6.js} +10 -304
  15. package/dist/chunk-2ACHNDO6.js.map +1 -0
  16. package/dist/chunk-CBJ3WOGD.cjs +317 -0
  17. package/dist/chunk-CBJ3WOGD.cjs.map +1 -0
  18. package/dist/{chunk-EJ2BMHYK.js → chunk-FSMIN5VX.js} +4 -3
  19. package/dist/chunk-FSMIN5VX.js.map +1 -0
  20. package/dist/{chunk-JKO3NR5L.js → chunk-HDCRPPBW.js} +5 -23
  21. package/dist/chunk-HDCRPPBW.js.map +1 -0
  22. package/dist/chunk-J4MX2W4H.js +26 -0
  23. package/dist/chunk-J4MX2W4H.js.map +1 -0
  24. package/dist/chunk-KWCHIAQ5.js +290 -0
  25. package/dist/chunk-KWCHIAQ5.js.map +1 -0
  26. package/dist/{chunk-6ALMBER7.js → chunk-N5SVBDLC.js} +4 -9
  27. package/dist/chunk-N5SVBDLC.js.map +1 -0
  28. package/dist/{chunk-RZQACYNQ.cjs → chunk-OBNTJEEO.cjs} +14 -20
  29. package/dist/chunk-OBNTJEEO.cjs.map +1 -0
  30. package/dist/chunk-OI55JRKT.cjs +334 -0
  31. package/dist/chunk-OI55JRKT.cjs.map +1 -0
  32. package/dist/{chunk-BXP6S34V.cjs → chunk-PGLV23SB.cjs} +13 -12
  33. package/dist/chunk-PGLV23SB.cjs.map +1 -0
  34. package/dist/chunk-RIHMIEXM.js +320 -0
  35. package/dist/chunk-RIHMIEXM.js.map +1 -0
  36. package/dist/chunk-RKWNX32A.js +299 -0
  37. package/dist/chunk-RKWNX32A.js.map +1 -0
  38. package/dist/chunk-S6A6FSG7.cjs +13 -0
  39. package/dist/chunk-S6A6FSG7.cjs.map +1 -0
  40. package/dist/chunk-SN5YV6FF.js +11 -0
  41. package/dist/chunk-SN5YV6FF.js.map +1 -0
  42. package/dist/{chunk-7IOCMCC5.cjs → chunk-TMPDW7J5.cjs} +3 -23
  43. package/dist/chunk-TMPDW7J5.cjs.map +1 -0
  44. package/dist/chunk-VJWAP2HJ.cjs +303 -0
  45. package/dist/chunk-VJWAP2HJ.cjs.map +1 -0
  46. package/dist/{chunk-ORKGCXG3.cjs → chunk-Z5YCRJY3.cjs} +125 -429
  47. package/dist/chunk-Z5YCRJY3.cjs.map +1 -0
  48. package/dist/chunk-ZMTLGO3K.cjs +29 -0
  49. package/dist/chunk-ZMTLGO3K.cjs.map +1 -0
  50. package/dist/index-CC6CvXEs.d.cts +1366 -0
  51. package/dist/index-CC6CvXEs.d.ts +1366 -0
  52. package/dist/index.cjs +1159 -722
  53. package/dist/index.cjs.map +1 -1
  54. package/dist/index.d.cts +588 -28
  55. package/dist/index.d.ts +588 -28
  56. package/dist/index.js +645 -310
  57. package/dist/index.js.map +1 -1
  58. package/dist/shell/index.cjs +8 -7
  59. package/dist/shell/index.js +3 -2
  60. package/dist/ui/index.cjs +158 -152
  61. package/dist/ui/index.d.cts +24 -1324
  62. package/dist/ui/index.d.ts +24 -1324
  63. package/dist/ui/index.js +4 -2
  64. package/dist/viewers/index.cjs +9 -8
  65. package/dist/viewers/index.cjs.map +1 -1
  66. package/dist/viewers/index.js +3 -2
  67. package/dist/viewers/index.js.map +1 -1
  68. package/package.json +19 -3
  69. package/dist/chunk-6ALMBER7.js.map +0 -1
  70. package/dist/chunk-7IOCMCC5.cjs.map +0 -1
  71. package/dist/chunk-BXP6S34V.cjs.map +0 -1
  72. package/dist/chunk-EJ2BMHYK.js.map +0 -1
  73. package/dist/chunk-JKO3NR5L.js.map +0 -1
  74. package/dist/chunk-MIJRORQL.js.map +0 -1
  75. package/dist/chunk-ORKGCXG3.cjs.map +0 -1
  76. package/dist/chunk-RZQACYNQ.cjs.map +0 -1
@@ -0,0 +1,334 @@
1
+ 'use strict';
2
+
3
+ var chunkS6A6FSG7_cjs = require('./chunk-S6A6FSG7.cjs');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var React = require('react');
6
+ var Markdown = require('react-markdown');
7
+ var remarkGfm = require('remark-gfm');
8
+ var rehypeRaw = require('rehype-raw');
9
+ var lucideReact = require('lucide-react');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var React__default = /*#__PURE__*/_interopDefault(React);
14
+ var Markdown__default = /*#__PURE__*/_interopDefault(Markdown);
15
+ var remarkGfm__default = /*#__PURE__*/_interopDefault(remarkGfm);
16
+ var rehypeRaw__default = /*#__PURE__*/_interopDefault(rehypeRaw);
17
+
18
+ var markdownBaseComponents = {
19
+ h1: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-lg font-semibold", "data-testid": "markdown-heading-1", ...props, children }),
20
+ h2: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "font-semibold text-base", "data-testid": "markdown-heading-2", ...props, children }),
21
+ h3: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-base", "data-testid": "markdown-heading-3", ...props, children }),
22
+ h4: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-semibold text-base", ...props, children }),
23
+ h5: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("h5", { className: "font-medium", ...props, children }),
24
+ strong: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "font-semibold", ...props, children }),
25
+ blockquote: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("blockquote", { className: "border-l-2 border-primary pl-4", ...props, children }),
26
+ ol: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "list-decimal space-y-2 pl-6", "data-testid": "markdown-ordered-list", ...props, children }),
27
+ ul: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "list-none space-y-2 pl-6", "data-testid": "markdown-unordered-list", ...props, children }),
28
+ li: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("li", { className: "my-1.5", ...props, children }),
29
+ table: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("table", { "data-testid": "markdown-table", ...props, children }),
30
+ th: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("th", { ...props, children }),
31
+ td: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("td", { ...props, children }),
32
+ tr: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("tr", { ...props, children }),
33
+ p: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap", ...props, children }),
34
+ hr: (props) => /* @__PURE__ */ jsxRuntime.jsx("hr", { className: "border-foreground/20", ...props }),
35
+ sup: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("sup", { className: "text-xs", ...props, children }),
36
+ sub: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("sub", { className: "text-xs", ...props, children }),
37
+ div: ({ children, className, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className, ...props, children }),
38
+ br: (props) => /* @__PURE__ */ jsxRuntime.jsx("br", { ...props }),
39
+ kbd: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(
40
+ "kbd",
41
+ {
42
+ className: "px-1.5 py-0.5 text-xs font-mono bg-muted border border-border rounded",
43
+ ...props,
44
+ children
45
+ }
46
+ ),
47
+ abbr: ({ children, title, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("abbr", { title, className: "underline decoration-dotted cursor-help", ...props, children }),
48
+ cite: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("cite", { className: "italic", ...props, children })
49
+ };
50
+ var MarkdownStreamingContext = React.createContext(false);
51
+
52
+ // src/ai/split-markdown-blocks.ts
53
+ var LIST_ITEM_RE = /^\s*([-*+]|\d+[.)])\s/;
54
+ var BLOCKQUOTE_RE = /^\s*>/;
55
+ function splitMarkdownBlocks(content) {
56
+ if (!content) return [];
57
+ const blocks = [];
58
+ let start = 0;
59
+ let inFence = false;
60
+ let i = 0;
61
+ while (i < content.length) {
62
+ if (content.startsWith("```", i)) {
63
+ inFence = !inFence;
64
+ i += 3;
65
+ continue;
66
+ }
67
+ if (!inFence && content[i] === "\n" && content[i + 1] === "\n") {
68
+ const before = content.slice(start, i);
69
+ const lastLine = before.slice(before.lastIndexOf("\n") + 1);
70
+ const afterStart = i + 2;
71
+ const nextNewline = content.indexOf("\n", afterStart);
72
+ const nextLine = content.slice(afterStart, nextNewline === -1 ? void 0 : nextNewline);
73
+ const bothListItems = LIST_ITEM_RE.test(lastLine) && LIST_ITEM_RE.test(nextLine);
74
+ const bothBlockquotes = BLOCKQUOTE_RE.test(lastLine) && BLOCKQUOTE_RE.test(nextLine);
75
+ if (bothListItems || bothBlockquotes) {
76
+ i += 2;
77
+ continue;
78
+ }
79
+ blocks.push(content.slice(start, afterStart));
80
+ start = afterStart;
81
+ i = afterStart;
82
+ continue;
83
+ }
84
+ i++;
85
+ }
86
+ if (start < content.length) blocks.push(content.slice(start));
87
+ return blocks;
88
+ }
89
+
90
+ // src/ai/normalize-citations.ts
91
+ var SOURCE_RESIDUE = /\(\*+\s*(Sources?\s*:[^\n]*?)\s*\*+\)/g;
92
+ var CITE_LINK = /\[([^\]\n]+)\]\((#cite-[\w-]+)\)/g;
93
+ var BLOCK_MARKER = /^(?:[-*+]|\d{1,9}[.)]|#{1,6})[ \t]+/;
94
+ var LEADING_EMPHASIS = /^(?:\*\*|__|\*|_)+/;
95
+ var TRAILING_EMPHASIS = /(\*\*|__|\*|_)(:?)$/;
96
+ function countToken(text, token) {
97
+ const double = token + token;
98
+ if (token.length === 2) return text.split(token).length - 1;
99
+ const withoutDouble = text.split(double).join("");
100
+ return withoutDouble.split(token).length - 1;
101
+ }
102
+ function emphasisTokens(run) {
103
+ const char = run[0];
104
+ const tokens = [];
105
+ let rest = run;
106
+ while (rest.length > 0) {
107
+ const size = rest.length >= 2 ? 2 : 1;
108
+ tokens.push(char.repeat(size));
109
+ rest = rest.slice(size);
110
+ }
111
+ return tokens;
112
+ }
113
+ function hoistFromLink(text, before, after) {
114
+ let body = text;
115
+ let prefix = "";
116
+ let suffix = "";
117
+ if (before.trim() === "") {
118
+ const marker = BLOCK_MARKER.exec(body);
119
+ if (marker) {
120
+ prefix += marker[0];
121
+ body = body.slice(marker[0].length);
122
+ }
123
+ }
124
+ const leading = LEADING_EMPHASIS.exec(body);
125
+ if (leading) {
126
+ let kept = "";
127
+ let rest = body.slice(leading[0].length);
128
+ for (const token of emphasisTokens(leading[0]).reverse()) {
129
+ if (countToken(rest, token) % 2 === 1) {
130
+ kept = token + kept;
131
+ continue;
132
+ }
133
+ if (countToken(after, token) % 2 === 1) prefix += token;
134
+ else kept = token + kept;
135
+ }
136
+ body = kept + rest;
137
+ rest = "";
138
+ }
139
+ const trailing = TRAILING_EMPHASIS.exec(body);
140
+ if (trailing) {
141
+ const token = trailing[1];
142
+ const inner = body.slice(0, body.length - trailing[0].length);
143
+ const openedBefore = countToken(before + prefix, token) % 2 === 1;
144
+ if (countToken(inner, token) % 2 === 0 && openedBefore) {
145
+ body = inner;
146
+ suffix = trailing[0];
147
+ }
148
+ }
149
+ return [prefix, body, suffix];
150
+ }
151
+ function normalizeLine(line) {
152
+ if (!line.includes("](#cite-")) return line;
153
+ return line.replace(CITE_LINK, (match, text, href, offset) => {
154
+ const before = line.slice(0, offset);
155
+ const after = line.slice(offset + match.length);
156
+ const [prefix, body, suffix] = hoistFromLink(text, before, after);
157
+ if (!prefix && !suffix && body === text) return match;
158
+ return `${prefix}[${body}](${href})${suffix}`;
159
+ });
160
+ }
161
+ function normalizeSourceResidue(line) {
162
+ return line.includes("(*") ? line.replace(SOURCE_RESIDUE, "*($1)*") : line;
163
+ }
164
+ function normalizeCitationMarkup(markdown) {
165
+ if (!markdown.includes("](#cite-") && !markdown.includes("(*")) return markdown;
166
+ let inFence = false;
167
+ return markdown.split("\n").map((line) => {
168
+ if (/^\s*(```|~~~)/.test(line)) {
169
+ inFence = !inFence;
170
+ return line;
171
+ }
172
+ return inFence ? line : normalizeSourceResidue(normalizeLine(line));
173
+ }).join("\n");
174
+ }
175
+ var OPEN_CITATION_TAIL = /\[[^\]\n]*(?:\](?:\((?:#(?:c(?:i(?:t(?:e(?:-[\w-]*)?)?)?)?)?)?)?)?$/;
176
+ function trimOpenCitation(markdown) {
177
+ return markdown.replace(OPEN_CITATION_TAIL, "");
178
+ }
179
+ function CodeBlock({ language, code }) {
180
+ const [copied, setCopied] = React.useState(false);
181
+ const handleCopy = async () => {
182
+ try {
183
+ await navigator.clipboard.writeText(code);
184
+ setCopied(true);
185
+ setTimeout(() => setCopied(false), 2e3);
186
+ } catch {
187
+ }
188
+ };
189
+ return /* @__PURE__ */ jsxRuntime.jsxs(
190
+ "div",
191
+ {
192
+ className: "code-block relative my-3 overflow-hidden rounded-md border border-current/20 bg-current/5",
193
+ "data-testid": "markdown-code-block",
194
+ children: [
195
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b border-current/20 px-3 py-1.5", children: [
196
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-xs text-muted-foreground", children: language || "code" }),
197
+ /* @__PURE__ */ jsxRuntime.jsxs(
198
+ "button",
199
+ {
200
+ type: "button",
201
+ onClick: handleCopy,
202
+ className: "flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground",
203
+ title: copied ? "Copied!" : "Copy code",
204
+ children: [
205
+ copied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 12 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { size: 12 }),
206
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: copied ? "Copied" : "Copy" })
207
+ ]
208
+ }
209
+ )
210
+ ] }),
211
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "overflow-x-auto p-3 text-sm leading-relaxed", children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: chunkS6A6FSG7_cjs.cn("font-mono", language && `language-${language}`), children: code }) })
212
+ ]
213
+ }
214
+ );
215
+ }
216
+ function MarkdownLink({ href, children, ...props }) {
217
+ if (href?.startsWith("#cite-")) {
218
+ return /* @__PURE__ */ jsxRuntime.jsx(
219
+ "span",
220
+ {
221
+ className: "cited-text after:ml-px after:align-super after:text-[0.7em] after:font-medium after:text-muted-foreground after:content-[attr(data-cite)]",
222
+ "data-cite": href.slice(6),
223
+ children
224
+ }
225
+ );
226
+ }
227
+ const isInternal = !href || href.startsWith("#") || href.startsWith("cite:");
228
+ return /* @__PURE__ */ jsxRuntime.jsx(
229
+ "a",
230
+ {
231
+ className: "text-primary underline underline-offset-2",
232
+ href,
233
+ ...isInternal ? {} : { target: "_blank", rel: "noopener noreferrer" },
234
+ ...props,
235
+ children
236
+ }
237
+ );
238
+ }
239
+ var defaultComponents = {
240
+ ...markdownBaseComponents,
241
+ a: MarkdownLink,
242
+ em: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("em", { className: "italic", ...props, children }),
243
+ code: function MarkdownCode({
244
+ className,
245
+ children,
246
+ ...props
247
+ }) {
248
+ const match = /language-(\w+)/.exec(className || "");
249
+ if (!match) {
250
+ return /* @__PURE__ */ jsxRuntime.jsx(
251
+ "code",
252
+ {
253
+ className: "rounded bg-current/10 px-1 py-0.5 font-mono text-[0.9em]",
254
+ "data-testid": "markdown-inline-code",
255
+ ...props,
256
+ children
257
+ }
258
+ );
259
+ }
260
+ return /* @__PURE__ */ jsxRuntime.jsx(CodeBlock, { language: match[1], code: String(children).replace(/\n$/, "") });
261
+ },
262
+ // The CodeBlock renders its own <pre>; unwrap react-markdown's wrapper so we
263
+ // don't nest <pre><pre>.
264
+ pre: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children }),
265
+ blockquote: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("blockquote", { className: "border-l-2 border-border pl-4 italic text-muted-foreground", ...props, children }),
266
+ table: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-3 overflow-x-auto rounded-lg border border-border", children: /* @__PURE__ */ jsxRuntime.jsx("table", { "data-testid": "markdown-table", className: "w-full border-collapse text-sm", ...props, children }) }),
267
+ thead: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-current/10", ...props, children }),
268
+ th: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("th", { className: "border-b border-border px-3 py-2 text-left font-semibold", ...props, children }),
269
+ td: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("td", { className: "border-b border-border px-3 py-2 align-top", ...props, children }),
270
+ // General emphasis marks (GFM `~~strikethrough~~` → <del>, raw <ins>/<mark>).
271
+ del: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("del", { className: "text-muted-foreground line-through", ...props, children }),
272
+ ins: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("ins", { className: "underline decoration-primary/60", ...props, children }),
273
+ mark: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx("mark", { className: "rounded bg-primary/15 px-0.5 text-inherit", ...props, children })
274
+ };
275
+ var remarkPlugins = [remarkGfm__default.default];
276
+ var rehypePlugins = [rehypeRaw__default.default];
277
+ var MemoizedBlock = React__default.default.memo(function MarkdownBlock({
278
+ content,
279
+ components
280
+ }) {
281
+ return /* @__PURE__ */ jsxRuntime.jsx(Markdown__default.default, { remarkPlugins, rehypePlugins, components, children: content });
282
+ });
283
+ var MarkdownErrorBoundary = class extends React__default.default.Component {
284
+ state = { hasError: false };
285
+ static getDerivedStateFromError() {
286
+ return { hasError: true };
287
+ }
288
+ render() {
289
+ return this.state.hasError ? this.props.fallback : this.props.children;
290
+ }
291
+ };
292
+ function MarkdownRendererComponent({
293
+ children,
294
+ components: customComponents,
295
+ renderCitation,
296
+ isStreaming = false,
297
+ className,
298
+ testId = "markdown-renderer"
299
+ }) {
300
+ const components = React.useMemo(() => {
301
+ const base = customComponents ? { ...defaultComponents, ...customComponents } : defaultComponents;
302
+ if (!renderCitation) return base;
303
+ const Link = base.a ?? MarkdownLink;
304
+ return {
305
+ ...base,
306
+ a: (props) => props.href?.startsWith("#cite-") ? renderCitation({ num: props.href.slice(6), children: props.children }) : React__default.default.createElement(Link, props)
307
+ };
308
+ }, [customComponents, renderCitation]);
309
+ const blocks = React.useMemo(() => splitMarkdownBlocks(normalizeCitationMarkup(children)), [children]);
310
+ return /* @__PURE__ */ jsxRuntime.jsx(
311
+ MarkdownErrorBoundary,
312
+ {
313
+ fallback: /* @__PURE__ */ jsxRuntime.jsx("div", { className: chunkS6A6FSG7_cjs.cn("whitespace-pre-wrap break-words", className), children }),
314
+ children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownStreamingContext.Provider, { value: isStreaming, children: /* @__PURE__ */ jsxRuntime.jsx(
315
+ "div",
316
+ {
317
+ className: chunkS6A6FSG7_cjs.cn("markdown-content space-y-3 break-words leading-relaxed", className),
318
+ "data-testid": testId,
319
+ children: blocks.map((block, index) => /* @__PURE__ */ jsxRuntime.jsx(MemoizedBlock, { content: block, components }, index))
320
+ }
321
+ ) })
322
+ }
323
+ );
324
+ }
325
+ var MarkdownRenderer = React__default.default.memo(MarkdownRendererComponent);
326
+
327
+ exports.MarkdownRenderer = MarkdownRenderer;
328
+ exports.MarkdownStreamingContext = MarkdownStreamingContext;
329
+ exports.markdownBaseComponents = markdownBaseComponents;
330
+ exports.normalizeCitationMarkup = normalizeCitationMarkup;
331
+ exports.splitMarkdownBlocks = splitMarkdownBlocks;
332
+ exports.trimOpenCitation = trimOpenCitation;
333
+ //# sourceMappingURL=chunk-OI55JRKT.cjs.map
334
+ //# sourceMappingURL=chunk-OI55JRKT.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ai/markdown-base-components.tsx","../src/ai/markdown-streaming-context.ts","../src/ai/split-markdown-blocks.ts","../src/ai/normalize-citations.ts","../src/ai/MarkdownRenderer.tsx"],"names":["jsx","createContext","useState","jsxs","Check","Copy","cn","Fragment","remarkGfm","rehypeRaw","React","Markdown","useMemo"],"mappings":";;;;;;;;;;;;;;;;;AAeO,IAAM,sBAAA,GAA8C;AAAA,EACzD,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAA,CAAC,IAAA,EAAA,EAAG,WAAU,uBAAA,EAAwB,aAAA,EAAY,oBAAA,EAAsB,GAAG,OACxE,QAAA,EACH,CAAA;AAAA,EAEF,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAA,CAAC,IAAA,EAAA,EAAG,WAAU,yBAAA,EAA0B,aAAA,EAAY,oBAAA,EAAsB,GAAG,OAC1E,QAAA,EACH,CAAA;AAAA,EAEF,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAA,CAAC,IAAA,EAAA,EAAG,WAAU,yBAAA,EAA0B,aAAA,EAAY,oBAAA,EAAsB,GAAG,OAC1E,QAAA,EACH,CAAA;AAAA,EAEF,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,yBAAA,EAA2B,GAAG,OACzC,QAAA,EACH,CAAA;AAAA,EAEF,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,aAAA,EAAe,GAAG,OAC7B,QAAA,EACH,CAAA;AAAA,EAEF,MAAA,EAAQ,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBAC5BA,cAAA,CAAC,QAAA,EAAA,EAAO,SAAA,EAAU,eAAA,EAAiB,GAAG,OACnC,QAAA,EACH,CAAA;AAAA,EAEF,UAAA,EAAY,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBAChCA,cAAA,CAAC,YAAA,EAAA,EAAW,SAAA,EAAU,gCAAA,EAAkC,GAAG,OACxD,QAAA,EACH,CAAA;AAAA,EAEF,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAA,CAAC,IAAA,EAAA,EAAG,WAAU,6BAAA,EAA8B,aAAA,EAAY,uBAAA,EAAyB,GAAG,OACjF,QAAA,EACH,CAAA;AAAA,EAEF,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAA,CAAC,IAAA,EAAA,EAAG,WAAU,0BAAA,EAA2B,aAAA,EAAY,yBAAA,EAA2B,GAAG,OAChF,QAAA,EACH,CAAA;AAAA,EAEF,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,QAAA,EAAU,GAAG,OACxB,QAAA,EACH,CAAA;AAAA,EAEF,KAAA,EAAO,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBAC3BA,cAAA,CAAC,OAAA,EAAA,EAAM,aAAA,EAAY,gBAAA,EAAkB,GAAG,OACrC,QAAA,EACH,CAAA;AAAA,EAEF,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAA,CAAC,IAAA,EAAA,EAAI,GAAG,KAAA,EAAQ,QAAA,EAAS,CAAA;AAAA,EAE3B,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAA,CAAC,IAAA,EAAA,EAAI,GAAG,KAAA,EAAQ,QAAA,EAAS,CAAA;AAAA,EAE3B,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAA,CAAC,IAAA,EAAA,EAAI,GAAG,KAAA,EAAQ,QAAA,EAAS,CAAA;AAAA,EAE3B,CAAA,EAAG,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACvBA,cAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,qBAAA,EAAuB,GAAG,OACpC,QAAA,EACH,CAAA;AAAA,EAEF,EAAA,EAAI,CAAC,KAAA,qBACHA,cAAA,CAAC,QAAG,SAAA,EAAU,sBAAA,EAAwB,GAAG,KAAA,EAAO,CAAA;AAAA,EAElD,GAAA,EAAK,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACzBA,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,SAAA,EAAW,GAAG,OAC1B,QAAA,EACH,CAAA;AAAA,EAEF,GAAA,EAAK,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACzBA,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,SAAA,EAAW,GAAG,OAC1B,QAAA,EACH,CAAA;AAAA,EAEF,GAAA,EAAK,CAAC,EAAE,QAAA,EAAU,SAAA,EAAW,GAAG,KAAA,EAAM,qBACpCA,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAuB,GAAG,OAC5B,QAAA,EACH,CAAA;AAAA,EAEF,IAAI,CAAC,KAAA,qBAAgDA,cAAA,CAAC,IAAA,EAAA,EAAI,GAAG,KAAA,EAAO,CAAA;AAAA,EACpE,KAAK,CAAC,EAAE,QAAA,EAAU,GAAG,OAAM,qBACzBA,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,uEAAA;AAAA,MACT,GAAG,KAAA;AAAA,MAEH;AAAA;AAAA,GACH;AAAA,EAEF,IAAA,EAAM,CAAC,EAAE,QAAA,EAAU,OAAO,GAAG,KAAA,EAAM,qBACjCA,cAAA,CAAC,UAAK,KAAA,EAAc,SAAA,EAAU,yCAAA,EAA2C,GAAG,OACzE,QAAA,EACH,CAAA;AAAA,EAEF,IAAA,EAAM,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBAC1BA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,QAAA,EAAU,GAAG,OAC1B,QAAA,EACH;AAEJ;AC/GO,IAAM,wBAAA,GAA2BC,oBAAc,KAAK;;;ACE3D,IAAM,YAAA,GAAe,uBAAA;AACrB,IAAM,aAAA,GAAgB,OAAA;AAEf,SAAS,oBAAoB,OAAA,EAA2B;AAC7D,EAAA,IAAI,CAAC,OAAA,EAAS,OAAO,EAAC;AACtB,EAAA,MAAM,SAAmB,EAAC;AAC1B,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,IAAI,CAAA,GAAI,CAAA;AACR,EAAA,OAAO,CAAA,GAAI,QAAQ,MAAA,EAAQ;AACzB,IAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,KAAA,EAAO,CAAC,CAAA,EAAG;AAChC,MAAA,OAAA,GAAU,CAAC,OAAA;AACX,MAAA,CAAA,IAAK,CAAA;AACL,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAC,OAAA,IAAW,OAAA,CAAQ,CAAC,CAAA,KAAM,QAAQ,OAAA,CAAQ,CAAA,GAAI,CAAC,CAAA,KAAM,IAAA,EAAM;AAC9D,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,KAAA,CAAM,KAAA,EAAO,CAAC,CAAA;AACrC,MAAA,MAAM,WAAW,MAAA,CAAO,KAAA,CAAM,OAAO,WAAA,CAAY,IAAI,IAAI,CAAC,CAAA;AAC1D,MAAA,MAAM,aAAa,CAAA,GAAI,CAAA;AACvB,MAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,OAAA,CAAQ,IAAA,EAAM,UAAU,CAAA;AACpD,MAAA,MAAM,WAAW,OAAA,CAAQ,KAAA,CAAM,YAAY,WAAA,KAAgB,EAAA,GAAK,SAAY,WAAW,CAAA;AACvF,MAAA,MAAM,gBAAgB,YAAA,CAAa,IAAA,CAAK,QAAQ,CAAA,IAAK,YAAA,CAAa,KAAK,QAAQ,CAAA;AAC/E,MAAA,MAAM,kBAAkB,aAAA,CAAc,IAAA,CAAK,QAAQ,CAAA,IAAK,aAAA,CAAc,KAAK,QAAQ,CAAA;AACnF,MAAA,IAAI,iBAAiB,eAAA,EAAiB;AACpC,QAAA,CAAA,IAAK,CAAA;AACL,QAAA;AAAA,MACF;AACA,MAAA,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,KAAA,CAAM,KAAA,EAAO,UAAU,CAAC,CAAA;AAC5C,MAAA,KAAA,GAAQ,UAAA;AACR,MAAA,CAAA,GAAI,UAAA;AACJ,MAAA;AAAA,IACF;AACA,IAAA,CAAA,EAAA;AAAA,EACF;AACA,EAAA,IAAI,KAAA,GAAQ,QAAQ,MAAA,EAAQ,MAAA,CAAO,KAAK,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAC,CAAA;AAC5D,EAAA,OAAO,MAAA;AACT;;;ACzBA,IAAM,cAAA,GAAiB,wCAAA;AAEvB,IAAM,SAAA,GAAY,mCAAA;AAClB,IAAM,YAAA,GAAe,qCAAA;AACrB,IAAM,gBAAA,GAAmB,oBAAA;AACzB,IAAM,iBAAA,GAAoB,qBAAA;AAG1B,SAAS,UAAA,CAAW,MAAc,KAAA,EAAuB;AACvD,EAAA,MAAM,SAAS,KAAA,GAAQ,KAAA;AACvB,EAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG,OAAO,KAAK,KAAA,CAAM,KAAK,EAAE,MAAA,GAAS,CAAA;AAC1D,EAAA,MAAM,gBAAgB,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,CAAE,KAAK,EAAE,CAAA;AAChD,EAAA,OAAO,aAAA,CAAc,KAAA,CAAM,KAAK,CAAA,CAAE,MAAA,GAAS,CAAA;AAC7C;AAGA,SAAS,eAAe,GAAA,EAAuB;AAC7C,EAAA,MAAM,IAAA,GAAO,IAAI,CAAC,CAAA;AAClB,EAAA,MAAM,SAAmB,EAAC;AAC1B,EAAA,IAAI,IAAA,GAAO,GAAA;AACX,EAAA,OAAO,IAAA,CAAK,SAAS,CAAA,EAAG;AACtB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,MAAA,IAAU,CAAA,GAAI,CAAA,GAAI,CAAA;AACpC,IAAA,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,IAAI,CAAC,CAAA;AAC7B,IAAA,IAAA,GAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACxB;AACA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,aAAA,CAAc,IAAA,EAAc,MAAA,EAAgB,KAAA,EAAyC;AAC5F,EAAA,IAAI,IAAA,GAAO,IAAA;AACX,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,IAAI,MAAA,GAAS,EAAA;AAEb,EAAA,IAAI,MAAA,CAAO,IAAA,EAAK,KAAM,EAAA,EAAI;AACxB,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,IAAA,CAAK,IAAI,CAAA;AACrC,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,MAAA,IAAU,OAAO,CAAC,CAAA;AAClB,MAAA,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,CAAC,EAAE,MAAM,CAAA;AAAA,IACpC;AAAA,EACF;AAEA,EAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,IAAA,CAAK,IAAI,CAAA;AAC1C,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,IAAI,IAAA,GAAO,EAAA;AACX,IAAA,IAAI,OAAO,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,CAAC,EAAE,MAAM,CAAA;AACvC,IAAA,KAAA,MAAW,SAAS,cAAA,CAAe,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAE,SAAQ,EAAG;AAExD,MAAA,IAAI,UAAA,CAAW,IAAA,EAAM,KAAK,CAAA,GAAI,MAAM,CAAA,EAAG;AACrC,QAAA,IAAA,GAAO,KAAA,GAAQ,IAAA;AACf,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,WAAW,KAAA,EAAO,KAAK,CAAA,GAAI,CAAA,KAAM,GAAG,MAAA,IAAU,KAAA;AAAA,kBACtC,KAAA,GAAQ,IAAA;AAAA,IACtB;AACA,IAAA,IAAA,GAAO,IAAA,GAAO,IAAA;AACd,IAAA,IAAA,GAAO,EAAA;AAAA,EACT;AAEA,EAAA,MAAM,QAAA,GAAW,iBAAA,CAAkB,IAAA,CAAK,IAAI,CAAA;AAC5C,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,KAAA,GAAQ,SAAS,CAAC,CAAA;AACxB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,CAAM,CAAA,EAAG,KAAK,MAAA,GAAS,QAAA,CAAS,CAAC,CAAA,CAAE,MAAM,CAAA;AAC5D,IAAA,MAAM,eAAe,UAAA,CAAW,MAAA,GAAS,MAAA,EAAQ,KAAK,IAAI,CAAA,KAAM,CAAA;AAChE,IAAA,IAAI,WAAW,KAAA,EAAO,KAAK,CAAA,GAAI,CAAA,KAAM,KAAK,YAAA,EAAc;AACtD,MAAA,IAAA,GAAO,KAAA;AACP,MAAA,MAAA,GAAS,SAAS,CAAC,CAAA;AAAA,IACrB;AAAA,EACF;AAEA,EAAA,OAAO,CAAC,MAAA,EAAQ,IAAA,EAAM,MAAM,CAAA;AAC9B;AAEA,SAAS,cAAc,IAAA,EAAsB;AAC3C,EAAA,IAAI,CAAC,IAAA,CAAK,QAAA,CAAS,UAAU,GAAG,OAAO,IAAA;AACvC,EAAA,OAAO,KAAK,OAAA,CAAQ,SAAA,EAAW,CAAC,KAAA,EAAO,IAAA,EAAc,MAAc,MAAA,KAAmB;AACpF,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,MAAM,CAAA;AACnC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,MAAM,MAAM,CAAA;AAC9C,IAAA,MAAM,CAAC,QAAQ,IAAA,EAAM,MAAM,IAAI,aAAA,CAAc,IAAA,EAAM,QAAQ,KAAK,CAAA;AAChE,IAAA,IAAI,CAAC,MAAA,IAAU,CAAC,MAAA,IAAU,IAAA,KAAS,MAAM,OAAO,KAAA;AAChD,IAAA,OAAO,GAAG,MAAM,CAAA,CAAA,EAAI,IAAI,CAAA,EAAA,EAAK,IAAI,IAAI,MAAM,CAAA,CAAA;AAAA,EAC7C,CAAC,CAAA;AACH;AAEA,SAAS,uBAAuB,IAAA,EAAsB;AACpD,EAAA,OAAO,IAAA,CAAK,SAAS,IAAI,CAAA,GAAI,KAAK,OAAA,CAAQ,cAAA,EAAgB,QAAQ,CAAA,GAAI,IAAA;AACxE;AAEO,SAAS,wBAAwB,QAAA,EAA0B;AAChE,EAAA,IAAI,CAAC,QAAA,CAAS,QAAA,CAAS,UAAU,CAAA,IAAK,CAAC,QAAA,CAAS,QAAA,CAAS,IAAI,CAAA,EAAG,OAAO,QAAA;AACvE,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,OAAO,SACJ,KAAA,CAAM,IAAI,CAAA,CACV,GAAA,CAAI,CAAC,IAAA,KAAS;AACb,IAAA,IAAI,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,EAAG;AAC9B,MAAA,OAAA,GAAU,CAAC,OAAA;AACX,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,OAAA,GAAU,IAAA,GAAO,sBAAA,CAAuB,aAAA,CAAc,IAAI,CAAC,CAAA;AAAA,EACpE,CAAC,CAAA,CACA,IAAA,CAAK,IAAI,CAAA;AACd;AAGA,IAAM,kBAAA,GAAqB,qEAAA;AAOpB,SAAS,iBAAiB,QAAA,EAA0B;AACzD,EAAA,OAAO,QAAA,CAAS,OAAA,CAAQ,kBAAA,EAAoB,EAAE,CAAA;AAChD;ACvGA,SAAS,SAAA,CAAU,EAAE,QAAA,EAAU,IAAA,EAAK,EAAuC;AACzE,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIC,eAAS,KAAK,CAAA;AAE1C,EAAA,MAAM,aAAa,YAAY;AAC7B,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,CAAU,SAAA,CAAU,SAAA,CAAU,IAAI,CAAA;AACxC,MAAA,SAAA,CAAU,IAAI,CAAA;AACd,MAAA,UAAA,CAAW,MAAM,SAAA,CAAU,KAAK,CAAA,EAAG,GAAI,CAAA;AAAA,IACzC,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF,CAAA;AAEA,EAAA,uBACEC,eAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,2FAAA;AAAA,MACV,aAAA,EAAY,qBAAA;AAAA,MAEZ,QAAA,EAAA;AAAA,wBAAAA,eAAA,CAAC,KAAA,EAAA,EAAI,WAAU,0EAAA,EACb,QAAA,EAAA;AAAA,0BAAAH,cAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yCAAA,EAA2C,sBAAY,MAAA,EAAO,CAAA;AAAA,0BAC9EG,eAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,OAAA,EAAS,UAAA;AAAA,cACT,SAAA,EAAU,+FAAA;AAAA,cACV,KAAA,EAAO,SAAS,SAAA,GAAY,WAAA;AAAA,cAE3B,QAAA,EAAA;AAAA,gBAAA,MAAA,mBAASH,cAAAA,CAACI,iBAAA,EAAA,EAAM,IAAA,EAAM,EAAA,EAAI,oBAAKJ,cAAAA,CAACK,gBAAA,EAAA,EAAK,IAAA,EAAM,EAAA,EAAI,CAAA;AAAA,gCAChDL,cAAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,MAAA,GAAS,WAAW,MAAA,EAAO;AAAA;AAAA;AAAA;AACpC,SAAA,EACF,CAAA;AAAA,wBACAA,cAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6CAAA,EACb,0BAAAA,cAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAWM,oBAAA,CAAG,aAAa,QAAA,IAAY,CAAA,SAAA,EAAY,QAAQ,CAAA,CAAE,CAAA,EAAI,gBAAK,CAAA,EAC9E;AAAA;AAAA;AAAA,GACF;AAEJ;AASA,SAAS,aAAa,EAAE,IAAA,EAAM,QAAA,EAAU,GAAG,OAAM,EAAwC;AACvF,EAAA,IAAI,IAAA,EAAM,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC9B,IAAA,uBACEN,cAAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,2IAAA;AAAA,QACV,WAAA,EAAW,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA;AAAA,QAEtB;AAAA;AAAA,KACH;AAAA,EAEJ;AACA,EAAA,MAAM,UAAA,GAAa,CAAC,IAAA,IAAQ,IAAA,CAAK,WAAW,GAAG,CAAA,IAAK,IAAA,CAAK,UAAA,CAAW,OAAO,CAAA;AAC3E,EAAA,uBACEA,cAAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,2CAAA;AAAA,MACV,IAAA;AAAA,MACC,GAAI,aAAa,EAAC,GAAI,EAAE,MAAA,EAAQ,QAAA,EAAU,KAAK,qBAAA,EAAsB;AAAA,MACrE,GAAG,KAAA;AAAA,MAEH;AAAA;AAAA,GACH;AAEJ;AASA,IAAM,iBAAA,GAAyC;AAAA,EAC7C,GAAG,sBAAA;AAAA,EACH,CAAA,EAAG,YAAA;AAAA,EACH,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,QAAA,EAAU,GAAG,OACxB,QAAA,EACH,CAAA;AAAA,EAEF,IAAA,EAAM,SAAS,YAAA,CAAa;AAAA,IAC1B,SAAA;AAAA,IACA,QAAA;AAAA,IACA,GAAG;AAAA,GACL,EAA2C;AACzC,IAAA,MAAM,KAAA,GAAQ,gBAAA,CAAiB,IAAA,CAAK,SAAA,IAAa,EAAE,CAAA;AACnD,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,uBACEA,cAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAU,0DAAA;AAAA,UACV,aAAA,EAAY,sBAAA;AAAA,UACX,GAAG,KAAA;AAAA,UAEH;AAAA;AAAA,OACH;AAAA,IAEJ;AACA,IAAA,uBAAOA,cAAAA,CAAC,SAAA,EAAA,EAAU,QAAA,EAAU,MAAM,CAAC,CAAA,EAAG,IAAA,EAAM,MAAA,CAAO,QAAQ,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,EAAG,CAAA;AAAA,EACnF,CAAA;AAAA;AAAA;AAAA,EAGA,GAAA,EAAK,CAAC,EAAE,QAAA,uBAAsDA,cAAAA,CAAAO,uBAAG,QAAA,EAAS,CAAA;AAAA,EAC1E,UAAA,EAAY,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBAChCP,cAAAA,CAAC,YAAA,EAAA,EAAW,SAAA,EAAU,4DAAA,EAA8D,GAAG,OACpF,QAAA,EACH,CAAA;AAAA,EAEF,KAAA,EAAO,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBAC3BA,cAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wDACb,QAAA,kBAAAA,cAAAA,CAAC,WAAM,aAAA,EAAY,gBAAA,EAAiB,WAAU,gCAAA,EAAkC,GAAG,KAAA,EAChF,QAAA,EACH,CAAA,EACF,CAAA;AAAA,EAEF,KAAA,EAAO,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBAC3BA,cAAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,eAAA,EAAiB,GAAG,OAClC,QAAA,EACH,CAAA;AAAA,EAEF,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,0DAAA,EAA4D,GAAG,OAC1E,QAAA,EACH,CAAA;AAAA,EAEF,EAAA,EAAI,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACxBA,cAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,4CAAA,EAA8C,GAAG,OAC5D,QAAA,EACH,CAAA;AAAA;AAAA,EAGF,GAAA,EAAK,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACzBA,cAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,oCAAA,EAAsC,GAAG,OACrD,QAAA,EACH,CAAA;AAAA,EAEF,GAAA,EAAK,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBACzBA,cAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iCAAA,EAAmC,GAAG,OAClD,QAAA,EACH,CAAA;AAAA,EAEF,IAAA,EAAM,CAAC,EAAE,QAAA,EAAU,GAAG,KAAA,EAAM,qBAC1BA,cAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,2CAAA,EAA6C,GAAG,OAC7D,QAAA,EACH;AAEJ,CAAA;AAMA,IAAM,aAAA,GAAgB,CAACQ,0BAAS,CAAA;AAChC,IAAM,aAAA,GAAgB,CAACC,0BAAS,CAAA;AAOhC,IAAM,aAAA,GAAgBC,sBAAA,CAAM,IAAA,CAAK,SAAS,aAAA,CAAc;AAAA,EACtD,OAAA;AAAA,EACA;AACF,CAAA,EAGG;AACD,EAAA,uBACEV,cAAAA,CAACW,yBAAA,EAAA,EAAS,aAAA,EAA8B,aAAA,EAA8B,YACnE,QAAA,EAAA,OAAA,EACH,CAAA;AAEJ,CAAC,CAAA;AAGD,IAAM,qBAAA,GAAN,cAAoCD,sBAAA,CAAM,SAAA,CAGxC;AAAA,EACA,KAAA,GAAQ,EAAE,QAAA,EAAU,KAAA,EAAM;AAAA,EAC1B,OAAO,wBAAA,GAA2B;AAChC,IAAA,OAAO,EAAE,UAAU,IAAA,EAAK;AAAA,EAC1B;AAAA,EACA,MAAA,GAAS;AACP,IAAA,OAAO,KAAK,KAAA,CAAM,QAAA,GAAW,KAAK,KAAA,CAAM,QAAA,GAAW,KAAK,KAAA,CAAM,QAAA;AAAA,EAChE;AACF,CAAA;AAuBA,SAAS,yBAAA,CAA0B;AAAA,EACjC,QAAA;AAAA,EACA,UAAA,EAAY,gBAAA;AAAA,EACZ,cAAA;AAAA,EACA,WAAA,GAAc,KAAA;AAAA,EACd,SAAA;AAAA,EACA,MAAA,GAAS;AACX,CAAA,EAA0B;AACxB,EAAA,MAAM,UAAA,GAAaE,cAAQ,MAAM;AAC/B,IAAA,MAAM,OAAO,gBAAA,GACT,EAAE,GAAG,iBAAA,EAAmB,GAAG,kBAAiB,GAC5C,iBAAA;AACJ,IAAA,IAAI,CAAC,gBAAgB,OAAO,IAAA;AAC5B,IAAA,MAAM,IAAA,GAAO,KAAK,CAAA,IAAK,YAAA;AACvB,IAAA,OAAO;AAAA,MACL,GAAG,IAAA;AAAA,MACH,CAAA,EAAG,CAAC,KAAA,KACF,KAAA,CAAM,IAAA,EAAM,WAAW,QAAQ,CAAA,GAC3B,cAAA,CAAe,EAAE,GAAA,EAAK,KAAA,CAAM,KAAK,KAAA,CAAM,CAAC,CAAA,EAAG,QAAA,EAAU,KAAA,CAAM,QAAA,EAAU,CAAA,GACrEF,sBAAA,CAAM,aAAA,CAAc,IAAA,EAA2B,KAAK;AAAA,KAC5D;AAAA,EACF,CAAA,EAAG,CAAC,gBAAA,EAAkB,cAAc,CAAC,CAAA;AAErC,EAAA,MAAM,MAAA,GAASE,aAAA,CAAQ,MAAM,mBAAA,CAAoB,uBAAA,CAAwB,QAAQ,CAAC,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAE/F,EAAA,uBACEZ,cAAAA;AAAA,IAAC,qBAAA;AAAA,IAAA;AAAA,MACC,QAAA,kBAAUA,cAAAA,CAAC,KAAA,EAAA,EAAI,WAAWM,oBAAA,CAAG,iCAAA,EAAmC,SAAS,CAAA,EAAI,QAAA,EAAS,CAAA;AAAA,MAEtF,0BAAAN,cAAAA,CAAC,wBAAA,CAAyB,UAAzB,EAAkC,KAAA,EAAO,aACxC,QAAA,kBAAAA,cAAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAWM,oBAAA,CAAG,wDAAA,EAA0D,SAAS,CAAA;AAAA,UACjF,aAAA,EAAa,MAAA;AAAA,UAEZ,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,EAAO,KAAA,qBAClBN,cAAAA,CAAC,aAAA,EAAA,EAA0B,OAAA,EAAS,KAAA,EAAO,UAAA,EAAA,EAAvB,KAA+C,CACpE;AAAA;AAAA,OACH,EACF;AAAA;AAAA,GACF;AAEJ;AAOO,IAAM,gBAAA,GAAmBU,sBAAA,CAAM,IAAA,CAAK,yBAAyB","file":"chunk-OI55JRKT.cjs","sourcesContent":["import React from 'react'\nimport type { Components } from 'react-markdown'\n\n/**\n * Brand-neutral presentational component overrides for react-markdown, shared by\n * every markdown surface in the workspace (the monolith's `MarkdownRenderer` and\n * the AI kit's `AiMarkdown`). One house style for headings, lists, tables and the\n * inline text elements — token-based, so it themes per app.\n *\n * Deliberately excluded (kept per-consumer): `a` (external-link confirm vs\n * plain), `code`/`pre` (rich CodeBlock + Mermaid vs inline), `span` (doc-entity\n * pills), and `del`/`ins`/`mark` (general emphasis vs redline tracked-changes) —\n * these differ by intent or carry heavy/coupled behaviour, so each renderer owns\n * them and spreads this base underneath.\n */\nexport const markdownBaseComponents: Partial<Components> = {\n h1: ({ children, ...props }: React.ComponentPropsWithoutRef<'h1'>) => (\n <h1 className=\"text-lg font-semibold\" data-testid=\"markdown-heading-1\" {...props}>\n {children}\n </h1>\n ),\n h2: ({ children, ...props }: React.ComponentPropsWithoutRef<'h2'>) => (\n <h2 className=\"font-semibold text-base\" data-testid=\"markdown-heading-2\" {...props}>\n {children}\n </h2>\n ),\n h3: ({ children, ...props }: React.ComponentPropsWithoutRef<'h3'>) => (\n <h3 className=\"font-semibold text-base\" data-testid=\"markdown-heading-3\" {...props}>\n {children}\n </h3>\n ),\n h4: ({ children, ...props }: React.ComponentPropsWithoutRef<'h4'>) => (\n <h4 className=\"font-semibold text-base\" {...props}>\n {children}\n </h4>\n ),\n h5: ({ children, ...props }: React.ComponentPropsWithoutRef<'h5'>) => (\n <h5 className=\"font-medium\" {...props}>\n {children}\n </h5>\n ),\n strong: ({ children, ...props }: React.ComponentPropsWithoutRef<'strong'>) => (\n <strong className=\"font-semibold\" {...props}>\n {children}\n </strong>\n ),\n blockquote: ({ children, ...props }: React.ComponentPropsWithoutRef<'blockquote'>) => (\n <blockquote className=\"border-l-2 border-primary pl-4\" {...props}>\n {children}\n </blockquote>\n ),\n ol: ({ children, ...props }: React.ComponentPropsWithoutRef<'ol'>) => (\n <ol className=\"list-decimal space-y-2 pl-6\" data-testid=\"markdown-ordered-list\" {...props}>\n {children}\n </ol>\n ),\n ul: ({ children, ...props }: React.ComponentPropsWithoutRef<'ul'>) => (\n <ul className=\"list-none space-y-2 pl-6\" data-testid=\"markdown-unordered-list\" {...props}>\n {children}\n </ul>\n ),\n li: ({ children, ...props }: React.ComponentPropsWithoutRef<'li'>) => (\n <li className=\"my-1.5\" {...props}>\n {children}\n </li>\n ),\n table: ({ children, ...props }: React.ComponentPropsWithoutRef<'table'>) => (\n <table data-testid=\"markdown-table\" {...props}>\n {children}\n </table>\n ),\n th: ({ children, ...props }: React.ComponentPropsWithoutRef<'th'>) => (\n <th {...props}>{children}</th>\n ),\n td: ({ children, ...props }: React.ComponentPropsWithoutRef<'td'>) => (\n <td {...props}>{children}</td>\n ),\n tr: ({ children, ...props }: React.ComponentPropsWithoutRef<'tr'>) => (\n <tr {...props}>{children}</tr>\n ),\n p: ({ children, ...props }: React.ComponentPropsWithoutRef<'p'>) => (\n <p className=\"whitespace-pre-wrap\" {...props}>\n {children}\n </p>\n ),\n hr: (props: React.ComponentPropsWithoutRef<'hr'>) => (\n <hr className=\"border-foreground/20\" {...props} />\n ),\n sup: ({ children, ...props }: React.ComponentPropsWithoutRef<'sup'>) => (\n <sup className=\"text-xs\" {...props}>\n {children}\n </sup>\n ),\n sub: ({ children, ...props }: React.ComponentPropsWithoutRef<'sub'>) => (\n <sub className=\"text-xs\" {...props}>\n {children}\n </sub>\n ),\n div: ({ children, className, ...props }: React.ComponentPropsWithoutRef<'div'>) => (\n <div className={className} {...props}>\n {children}\n </div>\n ),\n br: (props: React.ComponentPropsWithoutRef<'br'>) => <br {...props} />,\n kbd: ({ children, ...props }: React.ComponentPropsWithoutRef<'kbd'>) => (\n <kbd\n className=\"px-1.5 py-0.5 text-xs font-mono bg-muted border border-border rounded\"\n {...props}\n >\n {children}\n </kbd>\n ),\n abbr: ({ children, title, ...props }: React.ComponentPropsWithoutRef<'abbr'>) => (\n <abbr title={title} className=\"underline decoration-dotted cursor-help\" {...props}>\n {children}\n </abbr>\n ),\n cite: ({ children, ...props }: React.ComponentPropsWithoutRef<'cite'>) => (\n <cite className=\"italic\" {...props}>\n {children}\n </cite>\n ),\n}\n","import { createContext } from 'react'\n\n/**\n * Signals to `MarkdownRenderer`'s component overrides that the surrounding\n * message is still streaming tokens. Heavy renderers can defer their work while\n * this is true to avoid burning CPU on partially complete fences and to keep the\n * UI responsive during long generations.\n *\n * Default `false` means non-streaming usages (parsed documents, static answers,\n * DM bubbles) get the full rendering with no behaviour change.\n */\nexport const MarkdownStreamingContext = createContext(false)\n","/**\n * Splits a markdown string into top-level blocks at `\\n\\n` boundaries.\n *\n * Streaming AI responses arrive token-by-token; passing the full string to\n * react-markdown re-parses the entire AST on every flush. By splitting into\n * blocks and memoizing each, only the trailing (still-growing) block re-parses\n * — completed blocks stay frozen in the DOM.\n *\n * The split avoids three correctness pitfalls:\n * - never splits inside an open ``` code fence (would corrupt the language)\n * - never splits between two adjacent list items (would render as two lists)\n * - never splits between two blockquote lines (would render as two quotes)\n */\nconst LIST_ITEM_RE = /^\\s*([-*+]|\\d+[.)])\\s/\nconst BLOCKQUOTE_RE = /^\\s*>/\n\nexport function splitMarkdownBlocks(content: string): string[] {\n if (!content) return []\n const blocks: string[] = []\n let start = 0\n let inFence = false\n let i = 0\n while (i < content.length) {\n if (content.startsWith('```', i)) {\n inFence = !inFence\n i += 3\n continue\n }\n if (!inFence && content[i] === '\\n' && content[i + 1] === '\\n') {\n const before = content.slice(start, i)\n const lastLine = before.slice(before.lastIndexOf('\\n') + 1)\n const afterStart = i + 2\n const nextNewline = content.indexOf('\\n', afterStart)\n const nextLine = content.slice(afterStart, nextNewline === -1 ? undefined : nextNewline)\n const bothListItems = LIST_ITEM_RE.test(lastLine) && LIST_ITEM_RE.test(nextLine)\n const bothBlockquotes = BLOCKQUOTE_RE.test(lastLine) && BLOCKQUOTE_RE.test(nextLine)\n if (bothListItems || bothBlockquotes) {\n i += 2\n continue\n }\n blocks.push(content.slice(start, afterStart))\n start = afterStart\n i = afterStart\n continue\n }\n i++\n }\n if (start < content.length) blocks.push(content.slice(start))\n return blocks\n}\n","/**\n * Citation markup normaliser.\n *\n * The backend marks a cited statement as a markdown link, `[statement](#cite-N)`, and the\n * statement can include the markdown that surrounds it: a list marker, a heading marker or the\n * opener of a bold/italic span whose closer sits after the link. Inside link text those tokens\n * are not parsed as block or inline syntax, so they show literally and break lists and bold:\n *\n * [- **Claimed quantum (by Claimant)](#cite-1):** **USD 47 million**\n *\n * `normalizeCitationMarkup` hoists the leading tokens out of the link text (and a trailing\n * emphasis closer after it), so the link only wraps the words:\n *\n * - **[Claimed quantum (by Claimant)](#cite-1):** **USD 47 million**\n *\n * It is conservative: emphasis that opens and closes inside the link text is valid markdown and\n * is left alone, an opener is only hoisted when its closer follows on the same line, and fenced\n * code blocks are never touched. The result is idempotent.\n *\n * It also tidies the residue the model leaves when it cites in prose as well: a literal\n * `(*Source: Award.pdf, p. 12*)` shows its asterisks, because `(*` does not open emphasis. It\n * becomes the italic note `*(Source: Award.pdf, p. 12)*`.\n */\n\nconst SOURCE_RESIDUE = /\\(\\*+\\s*(Sources?\\s*:[^\\n]*?)\\s*\\*+\\)/g\n\nconst CITE_LINK = /\\[([^\\]\\n]+)\\]\\((#cite-[\\w-]+)\\)/g\nconst BLOCK_MARKER = /^(?:[-*+]|\\d{1,9}[.)]|#{1,6})[ \\t]+/\nconst LEADING_EMPHASIS = /^(?:\\*\\*|__|\\*|_)+/\nconst TRAILING_EMPHASIS = /(\\*\\*|__|\\*|_)(:?)$/\n\n/** Occurrences of `token` in `text`; a single `*` is counted outside any `**` pair. */\nfunction countToken(text: string, token: string): number {\n const double = token + token\n if (token.length === 2) return text.split(token).length - 1\n const withoutDouble = text.split(double).join('')\n return withoutDouble.split(token).length - 1\n}\n\n/** Split a run of emphasis characters (`***`, `**`, `*`...) into its parser-level tokens. */\nfunction emphasisTokens(run: string): string[] {\n const char = run[0]\n const tokens: string[] = []\n let rest = run\n while (rest.length > 0) {\n const size = rest.length >= 2 ? 2 : 1\n tokens.push(char.repeat(size))\n rest = rest.slice(size)\n }\n return tokens\n}\n\nfunction hoistFromLink(text: string, before: string, after: string): [string, string, string] {\n let body = text\n let prefix = ''\n let suffix = ''\n\n if (before.trim() === '') {\n const marker = BLOCK_MARKER.exec(body)\n if (marker) {\n prefix += marker[0]\n body = body.slice(marker[0].length)\n }\n }\n\n const leading = LEADING_EMPHASIS.exec(body)\n if (leading) {\n let kept = ''\n let rest = body.slice(leading[0].length)\n for (const token of emphasisTokens(leading[0]).reverse()) {\n // Balanced inside the link text: valid as written.\n if (countToken(rest, token) % 2 === 1) {\n kept = token + kept\n continue\n }\n // Unmatched here: hoist only if the line closes it after the link.\n if (countToken(after, token) % 2 === 1) prefix += token\n else kept = token + kept\n }\n body = kept + rest\n rest = ''\n }\n\n const trailing = TRAILING_EMPHASIS.exec(body)\n if (trailing) {\n const token = trailing[1]\n const inner = body.slice(0, body.length - trailing[0].length)\n const openedBefore = countToken(before + prefix, token) % 2 === 1\n if (countToken(inner, token) % 2 === 0 && openedBefore) {\n body = inner\n suffix = trailing[0]\n }\n }\n\n return [prefix, body, suffix]\n}\n\nfunction normalizeLine(line: string): string {\n if (!line.includes('](#cite-')) return line\n return line.replace(CITE_LINK, (match, text: string, href: string, offset: number) => {\n const before = line.slice(0, offset)\n const after = line.slice(offset + match.length)\n const [prefix, body, suffix] = hoistFromLink(text, before, after)\n if (!prefix && !suffix && body === text) return match\n return `${prefix}[${body}](${href})${suffix}`\n })\n}\n\nfunction normalizeSourceResidue(line: string): string {\n return line.includes('(*') ? line.replace(SOURCE_RESIDUE, '*($1)*') : line\n}\n\nexport function normalizeCitationMarkup(markdown: string): string {\n if (!markdown.includes('](#cite-') && !markdown.includes('(*')) return markdown\n let inFence = false\n return markdown\n .split('\\n')\n .map((line) => {\n if (/^\\s*(```|~~~)/.test(line)) {\n inFence = !inFence\n return line\n }\n return inFence ? line : normalizeSourceResidue(normalizeLine(line))\n })\n .join('\\n')\n}\n\n/** A trailing, still-open `[words](#cite-N` marker (or an unclosed `[words`) on the last line. */\nconst OPEN_CITATION_TAIL = /\\[[^\\]\\n]*(?:\\](?:\\((?:#(?:c(?:i(?:t(?:e(?:-[\\w-]*)?)?)?)?)?)?)?)?$/\n\n/**\n * Drops an unfinished citation marker from the end of text that is still streaming (or was\n * stopped mid-marker), so `[Change of control](#cite-` never shows as raw markup: the words appear\n * once the marker is complete. Complete markers and everything else are left alone.\n */\nexport function trimOpenCitation(markdown: string): string {\n return markdown.replace(OPEN_CITATION_TAIL, '')\n}\n","import React, { useMemo, useState } from 'react'\nimport Markdown, { type Components } from 'react-markdown'\nimport remarkGfm from 'remark-gfm'\nimport rehypeRaw from 'rehype-raw'\nimport { Copy, Check } from 'lucide-react'\nimport { cn } from '../lib/utils'\nimport { markdownBaseComponents } from './markdown-base-components'\nimport { MarkdownStreamingContext } from './markdown-streaming-context'\nimport { splitMarkdownBlocks } from './split-markdown-blocks'\nimport { normalizeCitationMarkup } from './normalize-citations'\n\n/**\n * MarkdownRenderer — the single, full-featured markdown surface for the kit.\n *\n * Renders AI answers, chat/DM messages and parsed-document panes with one house\n * style: headings, paragraphs, bold/italic, ordered/unordered lists, GFM tables,\n * fenced code blocks (monospace + copy button), inline code, blockquotes, safe\n * links (`rel=\"noopener\"`, new tab) and horizontal rules.\n *\n * Styling is 100% semantic Tailwind tokens (`text-foreground`,\n * `text-muted-foreground`, `border-border`, `bg-muted`, `text-primary`) so it\n * themes per firm — no hardcoded brand colours. It sizes to its container, so it\n * looks right in a compact chat bubble and in a full-width document pane alike.\n *\n * Streaming: the content is split into block-level chunks (see\n * `splitMarkdownBlocks`) and each chunk is memoised, so streaming token updates\n * only re-parse the trailing block instead of the whole message. Pass\n * `isStreaming` to let heavy custom components defer via `MarkdownStreamingContext`.\n *\n * Extending: pass `components` to override any element (the kit's `AiMarkdown`\n * and `CitationRenderer` layer their accent links / citation popovers on top).\n */\n\n/** Fenced code block with a language label and copy-to-clipboard button. */\nfunction CodeBlock({ language, code }: { language: string; code: string }) {\n const [copied, setCopied] = useState(false)\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(code)\n setCopied(true)\n setTimeout(() => setCopied(false), 2000)\n } catch {\n // Clipboard unavailable (insecure context / permissions) — no-op.\n }\n }\n\n return (\n <div\n className=\"code-block relative my-3 overflow-hidden rounded-md border border-current/20 bg-current/5\"\n data-testid=\"markdown-code-block\"\n >\n <div className=\"flex items-center justify-between border-b border-current/20 px-3 py-1.5\">\n <span className=\"font-mono text-xs text-muted-foreground\">{language || 'code'}</span>\n <button\n type=\"button\"\n onClick={handleCopy}\n className=\"flex items-center gap-1 text-xs text-muted-foreground transition-colors hover:text-foreground\"\n title={copied ? 'Copied!' : 'Copy code'}\n >\n {copied ? <Check size={12} /> : <Copy size={12} />}\n <span>{copied ? 'Copied' : 'Copy'}</span>\n </button>\n </div>\n <pre className=\"overflow-x-auto p-3 text-sm leading-relaxed\">\n <code className={cn('font-mono', language && `language-${language}`)}>{code}</code>\n </pre>\n </div>\n )\n}\n\n/**\n * Links. Internal hash/anchor links stay in-page; everything else opens safely in a new tab\n * (`rel=\"noopener noreferrer\"` prevents reverse-tabnabbing). A backend citation marker\n * (`#cite-N`) has no popover here: the cited words stay in the surface's own colour with a quiet\n * superscript number after them (no underline, so a cited sentence does not read as a link).\n * Pass `renderCitation` to `MarkdownRenderer` to swap in a real source popover.\n */\nfunction MarkdownLink({ href, children, ...props }: React.ComponentPropsWithoutRef<'a'>) {\n if (href?.startsWith('#cite-')) {\n return (\n <span\n className=\"cited-text after:ml-px after:align-super after:text-[0.7em] after:font-medium after:text-muted-foreground after:content-[attr(data-cite)]\"\n data-cite={href.slice(6)}\n >\n {children}\n </span>\n )\n }\n const isInternal = !href || href.startsWith('#') || href.startsWith('cite:')\n return (\n <a\n className=\"text-primary underline underline-offset-2\"\n href={href}\n {...(isInternal ? {} : { target: '_blank', rel: 'noopener noreferrer' })}\n {...props}\n >\n {children}\n </a>\n )\n}\n\n/**\n * Default component overrides. Spreads the shared brand-neutral base\n * (`markdownBaseComponents`: headings, lists, paragraphs, emphasis, hr…) and adds\n * the elements that carry behaviour or need self-contained styling: safe links,\n * code (inline + fenced), bordered tables and blockquotes. Module-scoped so the\n * reference is stable across renders and react-markdown can skip re-parsing.\n */\nconst defaultComponents: Partial<Components> = {\n ...markdownBaseComponents,\n a: MarkdownLink,\n em: ({ children, ...props }: React.ComponentPropsWithoutRef<'em'>) => (\n <em className=\"italic\" {...props}>\n {children}\n </em>\n ),\n code: function MarkdownCode({\n className,\n children,\n ...props\n }: React.ComponentPropsWithoutRef<'code'>) {\n const match = /language-(\\w+)/.exec(className || '')\n if (!match) {\n return (\n <code\n className=\"rounded bg-current/10 px-1 py-0.5 font-mono text-[0.9em]\"\n data-testid=\"markdown-inline-code\"\n {...props}\n >\n {children}\n </code>\n )\n }\n return <CodeBlock language={match[1]} code={String(children).replace(/\\n$/, '')} />\n },\n // The CodeBlock renders its own <pre>; unwrap react-markdown's wrapper so we\n // don't nest <pre><pre>.\n pre: ({ children }: React.ComponentPropsWithoutRef<'pre'>) => <>{children}</>,\n blockquote: ({ children, ...props }: React.ComponentPropsWithoutRef<'blockquote'>) => (\n <blockquote className=\"border-l-2 border-border pl-4 italic text-muted-foreground\" {...props}>\n {children}\n </blockquote>\n ),\n table: ({ children, ...props }: React.ComponentPropsWithoutRef<'table'>) => (\n <div className=\"my-3 overflow-x-auto rounded-lg border border-border\">\n <table data-testid=\"markdown-table\" className=\"w-full border-collapse text-sm\" {...props}>\n {children}\n </table>\n </div>\n ),\n thead: ({ children, ...props }: React.ComponentPropsWithoutRef<'thead'>) => (\n <thead className=\"bg-current/10\" {...props}>\n {children}\n </thead>\n ),\n th: ({ children, ...props }: React.ComponentPropsWithoutRef<'th'>) => (\n <th className=\"border-b border-border px-3 py-2 text-left font-semibold\" {...props}>\n {children}\n </th>\n ),\n td: ({ children, ...props }: React.ComponentPropsWithoutRef<'td'>) => (\n <td className=\"border-b border-border px-3 py-2 align-top\" {...props}>\n {children}\n </td>\n ),\n // General emphasis marks (GFM `~~strikethrough~~` → <del>, raw <ins>/<mark>).\n del: ({ children, ...props }: React.ComponentPropsWithoutRef<'del'>) => (\n <del className=\"text-muted-foreground line-through\" {...props}>\n {children}\n </del>\n ),\n ins: ({ children, ...props }: React.ComponentPropsWithoutRef<'ins'>) => (\n <ins className=\"underline decoration-primary/60\" {...props}>\n {children}\n </ins>\n ),\n mark: ({ children, ...props }: React.ComponentPropsWithoutRef<'mark'>) => (\n <mark className=\"rounded bg-primary/15 px-0.5 text-inherit\" {...props}>\n {children}\n </mark>\n ),\n}\n\n// Stable module-scope plugin arrays so react-markdown compares them by reference\n// and skips re-running the remark/rehype pipeline when nothing else changed.\n// `rehype-raw` enables inline HTML (e.g. redline <del>/<ins>); no math/mermaid\n// plugins are bundled to keep the kit light.\nconst remarkPlugins = [remarkGfm]\nconst rehypePlugins = [rehypeRaw]\n\n/**\n * One parsed markdown block, memoised on (content, components) so already-rendered\n * blocks don't re-parse when a later block streams new tokens. This is the core\n * of the streaming-rendering optimisation.\n */\nconst MemoizedBlock = React.memo(function MarkdownBlock({\n content,\n components,\n}: {\n content: string\n components: Partial<Components>\n}) {\n return (\n <Markdown remarkPlugins={remarkPlugins} rehypePlugins={rehypePlugins} components={components}>\n {content}\n </Markdown>\n )\n})\n\n/** Prevents a malformed-markdown render error from crashing the whole surface. */\nclass MarkdownErrorBoundary extends React.Component<\n { fallback: React.ReactNode; children: React.ReactNode },\n { hasError: boolean }\n> {\n state = { hasError: false }\n static getDerivedStateFromError() {\n return { hasError: true }\n }\n render() {\n return this.state.hasError ? this.props.fallback : this.props.children\n }\n}\n\nexport interface MarkdownRendererProps {\n /** Markdown content to render. */\n children: string\n /** Custom component overrides merged on top of the defaults. */\n components?: Partial<Components>\n /**\n * When true, signals heavy custom components (via `MarkdownStreamingContext`)\n * to defer their work while tokens are still arriving. Defaults to false.\n */\n isStreaming?: boolean\n /**\n * Renders a citation marker `[words](#cite-N)`: `children` are the cited words, `num` the N.\n * Keep it referentially stable (`useCallback`/`useMemo`), or every block re-parses.\n */\n renderCitation?: (citation: { num: string; children: React.ReactNode }) => React.ReactNode\n /** Extra classes on the wrapper (font-size, prose width, etc.). */\n className?: string\n /** Overrides the `data-testid` on the wrapper (default `markdown-renderer`). */\n testId?: string\n}\n\nfunction MarkdownRendererComponent({\n children,\n components: customComponents,\n renderCitation,\n isStreaming = false,\n className,\n testId = 'markdown-renderer',\n}: MarkdownRendererProps) {\n const components = useMemo(() => {\n const base = customComponents\n ? { ...defaultComponents, ...customComponents }\n : defaultComponents\n if (!renderCitation) return base\n const Link = base.a ?? MarkdownLink\n return {\n ...base,\n a: (props: React.ComponentPropsWithoutRef<'a'>) =>\n props.href?.startsWith('#cite-')\n ? renderCitation({ num: props.href.slice(6), children: props.children })\n : React.createElement(Link as React.ElementType, props),\n }\n }, [customComponents, renderCitation])\n\n const blocks = useMemo(() => splitMarkdownBlocks(normalizeCitationMarkup(children)), [children])\n\n return (\n <MarkdownErrorBoundary\n fallback={<div className={cn('whitespace-pre-wrap break-words', className)}>{children}</div>}\n >\n <MarkdownStreamingContext.Provider value={isStreaming}>\n <div\n className={cn('markdown-content space-y-3 break-words leading-relaxed', className)}\n data-testid={testId}\n >\n {blocks.map((block, index) => (\n <MemoizedBlock key={index} content={block} components={components} />\n ))}\n </div>\n </MarkdownStreamingContext.Provider>\n </MarkdownErrorBoundary>\n )\n}\n\n/**\n * Memoised so a stable message in a long thread doesn't re-parse when unrelated\n * tokens arrive elsewhere; a streaming message gets a fresh `children` each flush\n * and re-renders (only its trailing block re-parses, thanks to `MemoizedBlock`).\n */\nexport const MarkdownRenderer = React.memo(MarkdownRendererComponent)\n"]}
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkRZQACYNQ_cjs = require('./chunk-RZQACYNQ.cjs');
3
+ var chunkOBNTJEEO_cjs = require('./chunk-OBNTJEEO.cjs');
4
+ var chunkS6A6FSG7_cjs = require('./chunk-S6A6FSG7.cjs');
4
5
  var sonner = require('sonner');
5
6
  var zustand = require('zustand');
6
7
  var lucideReact = require('lucide-react');
@@ -117,8 +118,8 @@ function NotificationBell({
117
118
  const markRead = useNotify((s) => s.markRead);
118
119
  const tone = (v) => toneClassNames?.[v] ?? DEFAULT_NOTIFY_TONE[v];
119
120
  const visible = notifications.filter((n) => !n.ambient);
120
- return /* @__PURE__ */ jsxRuntime.jsxs(chunkRZQACYNQ_cjs.DropdownMenu, { children: [
121
- /* @__PURE__ */ jsxRuntime.jsx(chunkRZQACYNQ_cjs.DropdownMenuTrigger, { asChild: true, children: renderTrigger ? renderTrigger({
121
+ return /* @__PURE__ */ jsxRuntime.jsxs(chunkOBNTJEEO_cjs.DropdownMenu, { children: [
122
+ /* @__PURE__ */ jsxRuntime.jsx(chunkOBNTJEEO_cjs.DropdownMenuTrigger, { asChild: true, children: renderTrigger ? renderTrigger({
122
123
  unread,
123
124
  ariaLabel: `Notifications${unread ? `, ${unread} unread` : ""}`,
124
125
  testId
@@ -143,7 +144,7 @@ function NotificationBell({
143
144
  }
144
145
  ) }),
145
146
  /* @__PURE__ */ jsxRuntime.jsxs(
146
- chunkRZQACYNQ_cjs.DropdownMenuContent,
147
+ chunkOBNTJEEO_cjs.DropdownMenuContent,
147
148
  {
148
149
  align,
149
150
  side,
@@ -178,7 +179,7 @@ function NotificationBell({
178
179
  "aria-pressed": muted,
179
180
  title: muted ? "Unmute notifications" : "Mute notifications",
180
181
  "aria-label": muted ? "Unmute notifications" : "Mute notifications",
181
- className: chunkRZQACYNQ_cjs.cn(
182
+ className: chunkS6A6FSG7_cjs.cn(
182
183
  "flex size-6 items-center justify-center rounded-md transition-colors hover:bg-accent pointer-coarse:size-11",
183
184
  muted ? "text-foreground" : "text-muted-foreground hover:text-foreground"
184
185
  ),
@@ -187,7 +188,7 @@ function NotificationBell({
187
188
  }
188
189
  ),
189
190
  /* @__PURE__ */ jsxRuntime.jsx(
190
- chunkRZQACYNQ_cjs.DropdownMenuItem,
191
+ chunkOBNTJEEO_cjs.DropdownMenuItem,
191
192
  {
192
193
  className: "flex size-6 items-center justify-center rounded-md p-0 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus:bg-accent pointer-coarse:size-11",
193
194
  "aria-label": "Close notifications",
@@ -205,7 +206,7 @@ function NotificationBell({
205
206
  children: "Toasts are muted \u2014 everything still lands here."
206
207
  }
207
208
  ),
208
- visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3.5 py-10 text-center text-sm text-muted-foreground", children: emptyText }) : /* @__PURE__ */ jsxRuntime.jsx(chunkRZQACYNQ_cjs.ScrollArea, { className: "max-h-[380px]", children: /* @__PURE__ */ jsxRuntime.jsx("ul", { "data-testid": `${testId}-list`, children: visible.map((n) => {
209
+ visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3.5 py-10 text-center text-sm text-muted-foreground", children: emptyText }) : /* @__PURE__ */ jsxRuntime.jsx(chunkOBNTJEEO_cjs.ScrollArea, { className: "max-h-[380px]", children: /* @__PURE__ */ jsxRuntime.jsx("ul", { "data-testid": `${testId}-list`, children: visible.map((n) => {
209
210
  const Glyph = GLYPH[n.variant];
210
211
  return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
211
212
  "button",
@@ -216,13 +217,13 @@ function NotificationBell({
216
217
  if (n.onSelect) n.onSelect();
217
218
  else if (n.href) onNavigate?.(n.href);
218
219
  },
219
- className: chunkRZQACYNQ_cjs.cn(
220
+ className: chunkS6A6FSG7_cjs.cn(
220
221
  "flex w-full items-start gap-3 border-b border-border/60 px-3.5 py-3 text-left transition-colors hover:bg-accent",
221
222
  !n.read && "bg-accent/40"
222
223
  ),
223
224
  "data-testid": `${testId}-item`,
224
225
  children: [
225
- /* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: chunkRZQACYNQ_cjs.cn("mt-0.5 size-4 shrink-0", tone(n.variant)) }),
226
+ /* @__PURE__ */ jsxRuntime.jsx(Glyph, { className: chunkS6A6FSG7_cjs.cn("mt-0.5 size-4 shrink-0", tone(n.variant)) }),
226
227
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
227
228
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium leading-snug text-foreground", children: n.title }),
228
229
  n.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 line-clamp-2 text-xs leading-relaxed text-muted-foreground", children: n.description }),
@@ -236,7 +237,7 @@ function NotificationBell({
236
237
  onViewAll && // A real menu item, not a bare button: selecting it must CLOSE the
237
238
  // dropdown on its way to the full surface.
238
239
  /* @__PURE__ */ jsxRuntime.jsx(
239
- chunkRZQACYNQ_cjs.DropdownMenuItem,
240
+ chunkOBNTJEEO_cjs.DropdownMenuItem,
240
241
  {
241
242
  onClick: onViewAll,
242
243
  className: "justify-center rounded-none border-t border-border px-3.5 py-2.5 text-xs font-medium text-primary focus:text-primary pointer-coarse:min-h-11",
@@ -280,5 +281,5 @@ exports.notify = notify;
280
281
  exports.seedNotifications = seedNotifications;
281
282
  exports.selectUnread = selectUnread;
282
283
  exports.useNotify = useNotify;
283
- //# sourceMappingURL=chunk-BXP6S34V.cjs.map
284
- //# sourceMappingURL=chunk-BXP6S34V.cjs.map
284
+ //# sourceMappingURL=chunk-PGLV23SB.cjs.map
285
+ //# sourceMappingURL=chunk-PGLV23SB.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/notifications/notify-store.ts","../src/lib/format-relative-time.ts","../src/notifications/NotificationBell.tsx","../src/notifications/Toaster.tsx"],"names":["create","toast","CheckCircle2","XCircle","AlertTriangle","Info","DropdownMenu","jsx","DropdownMenuTrigger","jsxs","Bell","DropdownMenuContent","CheckCheck","cn","BellOff","DropdownMenuItem","X","ScrollArea","Sonner"],"mappings":"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","file":"chunk-PGLV23SB.cjs","sourcesContent":["/**\n * App notification store — the one shared toast + notification-centre. A single\n * `notify.*` call pops a transient toast (rendered by <Toaster/>, on sonner) and,\n * unless `{ record: false }`, records a persistent entry for the topbar bell.\n *\n * notify.success('Saved') // toast + centre entry\n * notify.error('Upload failed', { record: false }) // toast only\n *\n * sonner owns the transient toast lifecycle (stacking, timing, dismissal); this\n * store owns only the persistent centre history (what the <NotificationBell/>\n * shows). Brand-neutral: no seed data (inject via `seedNotifications`), no app\n * imports. For full sonner power (custom content, `toast.dismiss`, …) the raw\n * `toast` is re-exported.\n */\nimport { type ReactNode } from 'react'\n// This module IS the sanctioned toast engine home — the app-level rule that\n// steers callers to sendNotification()/notify() doesn't apply here.\n// eslint-disable-next-line no-restricted-imports\nimport { toast } from 'sonner'\nimport { create } from 'zustand'\n\nexport { toast }\n\nexport type NotifyVariant = 'success' | 'error' | 'info' | 'warning'\n\nexport interface AppNotification {\n id: string\n title: string\n description?: string\n variant: NotifyVariant\n /** Epoch ms the entry was recorded. */\n ts: number\n read: boolean\n /** Optional in-app link the entry deep-links to (passed to the bell's onNavigate). */\n href?: string\n /** Optional click action — wins over `href` when both are present. Session\n * history only; entries are never serialized, so a closure is safe here. */\n onSelect?: () => void\n /** Routine session activity (\"Opening workspace…\", \"Saved\") — kept for the\n * log but not something that demands attention: excluded from the unread\n * badge and from the bell's default list (shown under \"All activity\"). */\n ambient?: boolean\n /** Where the entry comes from. Session entries (`record`) have none; a\n * backend bridge stamps its own (e.g. 'server') so {@link syncSource} can\n * replace exactly its slice on every refetch without touching the rest. */\n source?: string\n}\n\nexport interface NotifyOptions {\n description?: string\n /** Also record a persistent notification-centre entry (default true). */\n record?: boolean\n href?: string\n duration?: number\n /** A toast action button (sonner). */\n action?: { label: ReactNode; onClick: () => void }\n /** Override the toast glyph (sonner). */\n icon?: ReactNode\n /** Record as routine session activity (see {@link AppNotification.ambient}). */\n ambient?: boolean\n}\n\ninterface NotifyState {\n notifications: AppNotification[]\n markRead: (id: string) => void\n markAllRead: () => void\n removeNotification: (id: string) => void\n clearNotifications: () => void\n /** Replace the centre's history (e.g. seed a demo/initial set). */\n seed: (items: AppNotification[]) => void\n /** Record a centre entry (used by `notify.*`; sonner renders the toast). */\n record: (note: AppNotification) => void\n /** Reconcile one source's slice with fresh truth (e.g. a backend list on\n * every refetch): entries stamped with `source` are replaced wholesale —\n * including their read state, which the source owns — while entries from\n * other sources (session activity) stay untouched. Newest first. */\n syncSource: (source: string, items: AppNotification[]) => void\n}\n\nlet seq = 0\nconst uid = () => `n${Date.now().toString(36)}${(seq++).toString(36)}`\n\nexport const useNotify = create<NotifyState>((set) => ({\n notifications: [],\n markRead: (id) =>\n set((s) => ({\n notifications: s.notifications.map((n) => (n.id === id ? { ...n, read: true } : n)),\n })),\n markAllRead: () =>\n set((s) => ({ notifications: s.notifications.map((n) => ({ ...n, read: true })) })),\n removeNotification: (id) =>\n set((s) => ({ notifications: s.notifications.filter((n) => n.id !== id) })),\n clearNotifications: () => set({ notifications: [] }),\n seed: (items) => set({ notifications: items }),\n record: (note) => set((s) => ({ notifications: [note, ...s.notifications].slice(0, 50) })),\n syncSource: (source, items) =>\n set((s) => ({\n notifications: [\n ...items.map((n) => ({ ...n, source })),\n ...s.notifications.filter((n) => n.source !== source),\n ]\n .sort((a, b) => b.ts - a.ts)\n .slice(0, 100),\n })),\n}))\n\n/** Ergonomic helper — `notify.success('Saved')` — toast (sonner) + optional centre entry. */\nfunction make(variant: NotifyVariant) {\n return (title: string, opts: NotifyOptions = {}): string => {\n toast[variant](title, {\n description: opts.description,\n duration: opts.duration,\n action: opts.action,\n icon: opts.icon,\n })\n const id = uid()\n if (opts.record !== false) {\n useNotify.getState().record({\n id,\n title,\n description: opts.description,\n variant,\n ts: Date.now(),\n read: false,\n href: opts.href,\n ambient: opts.ambient,\n })\n }\n return id\n }\n}\nexport const notify = {\n success: make('success'),\n error: make('error'),\n info: make('info'),\n warning: make('warning'),\n}\n\n/** Replace the notification centre's history — inject a seed/initial set. */\nexport function seedNotifications(items: AppNotification[]): void {\n useNotify.getState().seed(items)\n}\n\n/** Unread count selector for the topbar bell — ambient session activity never\n * demands attention, so it never counts. */\nexport const selectUnread = (s: NotifyState): number =>\n s.notifications.filter((n) => !n.read && !n.ambient).length\n\n/** Default glyph colour per variant (raw palette, override via `toneClassNames`). */\nexport const DEFAULT_NOTIFY_TONE: Record<NotifyVariant, string> = {\n success: 'text-green-600',\n error: 'text-destructive',\n warning: 'text-amber-500',\n info: 'text-blue-500',\n}\n","/** Relative-time formatting shared by the notification bell and the DM kit (no type imports, so the\n * SDK-free `/shell` entry can use it). */\n\nconst RELATIVE = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' })\nconst DIVISIONS: Array<{ amount: number; unit: Intl.RelativeTimeFormatUnit }> = [\n { amount: 60, unit: 'seconds' },\n { amount: 60, unit: 'minutes' },\n { amount: 24, unit: 'hours' },\n { amount: 7, unit: 'days' },\n { amount: 4.34524, unit: 'weeks' },\n { amount: 12, unit: 'months' },\n { amount: Number.POSITIVE_INFINITY, unit: 'years' },\n]\n\n/** Compact relative time, e.g. \"2 hours ago\", \"just now\". */\nexport function formatRelativeTime(iso: string): string {\n const then = Date.parse(iso)\n if (Number.isNaN(then)) return ''\n let duration = (then - Date.now()) / 1000\n if (Math.abs(duration) < 45) return 'just now'\n for (const division of DIVISIONS) {\n if (Math.abs(duration) < division.amount) {\n return RELATIVE.format(Math.round(duration), division.unit)\n }\n duration /= division.amount\n }\n return ''\n}\n","/**\n * NotificationBell — the topbar notification centre: a bell with an unread badge\n * that opens a dropdown of recorded entries (newest first) with an unread dot,\n * relative time and optional deep-link. \"Mark all read\" clears the badge. Backed\n * by the shared {@link useNotify} store (the same one the <Toaster/> renders).\n *\n * Firm-agnostic: navigation is a callback (`onNavigate`), styling tracks semantic\n * tokens, and per-variant glyph colours are overridable via `toneClassNames`.\n */\nimport type { ReactElement } from 'react'\nimport {\n Bell,\n BellOff,\n CheckCheck,\n CheckCircle2,\n AlertTriangle,\n XCircle,\n Info,\n X,\n} from 'lucide-react'\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n} from '../ui/dropdown-menu'\nimport { ScrollArea } from '../ui/scroll-area'\nimport { cn } from '../lib/utils'\nimport { formatRelativeTime } from '../lib/format-relative-time'\nimport { useNotify, selectUnread, DEFAULT_NOTIFY_TONE, type NotifyVariant } from './notify-store'\n\nconst GLYPH: Record<NotifyVariant, typeof Info> = {\n success: CheckCircle2,\n error: XCircle,\n warning: AlertTriangle,\n info: Info,\n}\n\n/** What a custom bell trigger needs to render itself. */\nexport interface NotificationBellTriggerState {\n /** Unread count (0 when none). */\n unread: number\n /** Accessible name, including the count: \"Notifications, 2 unread\". */\n ariaLabel: string\n testId: string\n}\n\nexport interface NotificationBellProps {\n /**\n * Replaces the default icon button with the host's own trigger (for example a labelled sidebar\n * row). It must forward its ref and rest props: it is mounted as the dropdown trigger.\n */\n renderTrigger?: (state: NotificationBellTriggerState) => ReactElement\n /** Called with an entry's `href` when it's clicked (e.g. router navigate). */\n onNavigate?: (href: string) => void\n /** Called after an entry is locally marked read — the hook for a backend\n * bridge to propagate read state upstream (filter by the ids it owns). */\n onMarkRead?: (id: string) => void\n /** Called after \"Mark all read\" — same upstream-propagation hook. */\n onMarkAllRead?: () => void\n /** Toast muting — the bell is where notification behaviour is controlled, so\n * the mute switch lives in its header. Omit `onToggleMute` to hide it. */\n muted?: boolean\n onToggleMute?: () => void\n /** Override per-variant glyph colours (e.g. brand emerald for success). */\n toneClassNames?: Partial<Record<NotifyVariant, string>>\n emptyText?: string\n /** Footer link to the app's full notification surface. Omit to hide. */\n onViewAll?: () => void\n viewAllLabel?: string\n testId?: string\n /** Where the panel opens relative to the bell (a sidebar-footer bell opens upward). */\n side?: 'top' | 'right' | 'bottom' | 'left'\n align?: 'start' | 'center' | 'end'\n}\n\nexport function NotificationBell({\n renderTrigger,\n onNavigate,\n onMarkRead,\n onMarkAllRead,\n muted = false,\n onToggleMute,\n toneClassNames,\n emptyText = \"You're all caught up.\",\n onViewAll,\n viewAllLabel = 'View all',\n testId = 'notifications',\n side = 'bottom',\n align = 'end',\n}: NotificationBellProps) {\n const notifications = useNotify((s) => s.notifications)\n const unread = useNotify(selectUnread)\n const markAllRead = useNotify((s) => s.markAllRead)\n const markRead = useNotify((s) => s.markRead)\n const tone = (v: NotifyVariant) => toneClassNames?.[v] ?? DEFAULT_NOTIFY_TONE[v]\n\n // Ambient entries (routine session activity — \"Opening workspace…\") are kept\n // for the log but never surfaced in the bell panel: they never badge and are\n // always filtered out here.\n const visible = notifications.filter((n) => !n.ambient)\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n {renderTrigger ? (\n renderTrigger({\n unread,\n ariaLabel: `Notifications${unread ? `, ${unread} unread` : ''}`,\n testId,\n })\n ) : (\n <button\n className=\"relative flex size-9 pointer-coarse:size-11 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\"\n data-testid={testId}\n aria-label={`Notifications${unread ? `, ${unread} unread` : ''}`}\n title=\"Notifications\"\n >\n <Bell className=\"size-5\" />\n {unread > 0 && (\n <span\n className=\"absolute -right-0.5 -top-0.5 flex min-w-4 items-center justify-center rounded-full bg-primary px-1 text-xs font-semibold leading-4 text-primary-foreground\"\n data-testid={`${testId}-badge`}\n >\n {unread > 9 ? '9+' : unread}\n </span>\n )}\n </button>\n )}\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align={align}\n side={side}\n sideOffset={8}\n collisionPadding={12}\n className=\"w-[360px] rounded-lg border-border/60 p-0 shadow-lg\"\n data-testid={`${testId}-panel`}\n data-topbar-popover=\"\"\n >\n <div className=\"flex items-center justify-between gap-3 border-b border-border px-3.5 py-2.5 pointer-coarse:py-0\">\n <p className=\"text-sm font-semibold text-foreground\">Notifications</p>\n <div className=\"flex items-center gap-3 pointer-coarse:gap-1\">\n {unread > 0 && (\n <button\n onClick={() => {\n markAllRead()\n onMarkAllRead?.()\n }}\n className=\"flex items-center gap-1 text-xs font-medium text-primary transition-colors hover:opacity-80 pointer-coarse:min-h-11 pointer-coarse:px-2\"\n data-testid={`${testId}-mark-all`}\n >\n <CheckCheck className=\"size-3.5\" /> Mark all read\n </button>\n )}\n {onToggleMute && (\n <button\n onClick={onToggleMute}\n aria-pressed={muted}\n title={muted ? 'Unmute notifications' : 'Mute notifications'}\n aria-label={muted ? 'Unmute notifications' : 'Mute notifications'}\n className={cn(\n 'flex size-6 items-center justify-center rounded-md transition-colors hover:bg-accent pointer-coarse:size-11',\n muted ? 'text-foreground' : 'text-muted-foreground hover:text-foreground'\n )}\n data-testid={`${testId}-mute`}\n >\n <BellOff className=\"size-3.5\" />\n </button>\n )}\n {/* Explicit close — the dropdown otherwise only dismisses via\n tap-outside/Escape, which isn't discoverable on touch and (at\n mobile width, see blocks.css) the panel covers most\n of the screen so there's nothing to tap outside of. Radix\n closes a DropdownMenu on any item select by default, so a\n plain item with no other effect doubles as a close button. */}\n <DropdownMenuItem\n className=\"flex size-6 items-center justify-center rounded-md p-0 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus:bg-accent pointer-coarse:size-11\"\n aria-label=\"Close notifications\"\n data-testid={`${testId}-close`}\n >\n <X className=\"size-3.5\" />\n </DropdownMenuItem>\n </div>\n </div>\n {muted && (\n <p\n className=\"border-b border-border/60 bg-amber-500/10 px-3.5 py-1.5 text-xs text-muted-foreground\"\n data-testid={`${testId}-muted-note`}\n >\n Toasts are muted — everything still lands here.\n </p>\n )}\n\n {visible.length === 0 ? (\n <div className=\"px-3.5 py-10 text-center text-sm text-muted-foreground\">{emptyText}</div>\n ) : (\n <ScrollArea className=\"max-h-[380px]\">\n <ul data-testid={`${testId}-list`}>\n {visible.map((n) => {\n const Glyph = GLYPH[n.variant]\n return (\n <li key={n.id}>\n <button\n onClick={() => {\n markRead(n.id)\n if (!n.read) onMarkRead?.(n.id)\n // The specific action wins over the generic deep link.\n if (n.onSelect) n.onSelect()\n else if (n.href) onNavigate?.(n.href)\n }}\n className={cn(\n 'flex w-full items-start gap-3 border-b border-border/60 px-3.5 py-3 text-left transition-colors hover:bg-accent',\n !n.read && 'bg-accent/40'\n )}\n data-testid={`${testId}-item`}\n >\n <Glyph className={cn('mt-0.5 size-4 shrink-0', tone(n.variant))} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium leading-snug text-foreground\">\n {n.title}\n </p>\n {n.description && (\n <p className=\"mt-0.5 line-clamp-2 text-xs leading-relaxed text-muted-foreground\">\n {n.description}\n </p>\n )}\n <p className=\"mt-1 text-xs text-muted-foreground/80\">\n {formatRelativeTime(new Date(n.ts).toISOString())}\n </p>\n </div>\n {!n.read && (\n <span className=\"mt-1.5 size-2 shrink-0 rounded-full bg-primary\" />\n )}\n </button>\n </li>\n )\n })}\n </ul>\n </ScrollArea>\n )}\n {onViewAll && (\n // A real menu item, not a bare button: selecting it must CLOSE the\n // dropdown on its way to the full surface.\n <DropdownMenuItem\n onClick={onViewAll}\n className=\"justify-center rounded-none border-t border-border px-3.5 py-2.5 text-xs font-medium text-primary focus:text-primary pointer-coarse:min-h-11\"\n data-testid={`${testId}-view-all`}\n >\n {viewAllLabel}\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n","/**\n * Toaster — the one shared toast surface, built on sonner (the mature engine:\n * stacking, swipe-to-dismiss, a11y, timing). Mount once near the app root; any\n * code then calls `notify.*(...)` (or the re-exported `toast`) from anywhere.\n *\n * Firm-agnostic: every sonner `ToasterProps` passes through, so a consumer sets\n * its own `position` / `theme` / `toastOptions`. The defaults give a neutral\n * shadcn-token card that both apps can override.\n */\nimport { Toaster as Sonner, type ToasterProps } from 'sonner'\n\nexport type { ToasterProps }\n\nconst DEFAULT_TOAST_OPTIONS: ToasterProps['toastOptions'] = {\n classNames: {\n toast:\n 'group toast group-[.toaster]:bg-card group-[.toaster]:text-foreground group-[.toaster]:border group-[.toaster]:border-border group-[.toaster]:shadow-lg group-[.toaster]:rounded-lg',\n description: 'group-[.toast]:text-muted-foreground',\n actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',\n cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',\n },\n}\n\nexport function Toaster({ toastOptions, ...props }: ToasterProps) {\n return (\n <Sonner\n className=\"toaster group\"\n toastOptions={toastOptions ?? DEFAULT_TOAST_OPTIONS}\n {...props}\n />\n )\n}\n"]}