@arbidocs/react 0.5.122 → 0.5.123

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.
@@ -91,6 +91,62 @@ function splitMarkdownBlocks(content) {
91
91
  if (start < content.length) blocks.push(content.slice(start));
92
92
  return blocks;
93
93
  }
94
+
95
+ // src/ai/remark-ai-caption.ts
96
+ var AI_CAPTION_MARKER = "[!ai-caption]";
97
+ var AI_CAPTION_LABEL = "Generated image caption";
98
+ function takeMarker(quote) {
99
+ const paragraph = quote.children?.[0];
100
+ const text = paragraph?.type === "paragraph" ? paragraph.children?.[0] : void 0;
101
+ if (text?.type !== "text" || !text.value?.trimStart().startsWith(AI_CAPTION_MARKER)) return false;
102
+ text.value = text.value.trimStart().slice(AI_CAPTION_MARKER.length).replace(/^\s+/, "");
103
+ const rest = paragraph.children;
104
+ if (!text.value) rest.shift();
105
+ if (rest[0]?.type === "break") rest.shift();
106
+ if (!rest.length) quote.children.shift();
107
+ return true;
108
+ }
109
+ function walk(node) {
110
+ for (const child of node.children ?? []) {
111
+ if (child.type === "blockquote" && takeMarker(child)) {
112
+ child.data = {
113
+ hName: "aside",
114
+ hProperties: {
115
+ className: [
116
+ "my-2",
117
+ "rounded-md",
118
+ "border",
119
+ "border-border",
120
+ "bg-muted/40",
121
+ "px-3",
122
+ "py-2"
123
+ ],
124
+ dataAiCaption: "",
125
+ ariaLabel: AI_CAPTION_LABEL
126
+ }
127
+ };
128
+ child.children = [
129
+ {
130
+ type: "paragraph",
131
+ data: {
132
+ hName: "div",
133
+ hProperties: {
134
+ className: ["mb-1", "text-xs", "font-medium", "text-muted-foreground"],
135
+ dataTestid: "ai-caption-label"
136
+ }
137
+ },
138
+ children: [{ type: "text", value: AI_CAPTION_LABEL }]
139
+ },
140
+ ...child.children ?? []
141
+ ];
142
+ } else {
143
+ walk(child);
144
+ }
145
+ }
146
+ }
147
+ function remarkAiCaption() {
148
+ return (tree) => walk(tree);
149
+ }
94
150
  var EDGE_FADE = "1.5rem";
95
151
  function MarkdownTable({ children, ...props }) {
96
152
  const ref = React.useRef(null);
@@ -242,11 +298,13 @@ var defaultComponents = {
242
298
  };
243
299
  var markdownSanitizeSchema = {
244
300
  ...rehypeSanitize.defaultSchema,
245
- tagNames: [...rehypeSanitize.defaultSchema.tagNames ?? [], "mark"],
301
+ tagNames: [...rehypeSanitize.defaultSchema.tagNames ?? [], "mark", "aside"],
246
302
  attributes: {
247
303
  ...rehypeSanitize.defaultSchema.attributes,
248
304
  span: [...rehypeSanitize.defaultSchema.attributes?.span ?? [], "className"],
249
- div: [...rehypeSanitize.defaultSchema.attributes?.div ?? [], "className"],
305
+ div: [...rehypeSanitize.defaultSchema.attributes?.div ?? [], "className", "dataTestid"],
306
+ // A figure's generated caption (`remark-ai-caption`): its box and label.
307
+ aside: ["className", "dataAiCaption", "ariaLabel"],
250
308
  code: [...rehypeSanitize.defaultSchema.attributes?.code ?? [], "className"],
251
309
  pre: [...rehypeSanitize.defaultSchema.attributes?.pre ?? [], "className"],
252
310
  ins: [...rehypeSanitize.defaultSchema.attributes?.ins ?? [], "dataCid"],
@@ -291,7 +349,7 @@ var NO_PLUGINS = [];
291
349
  function usePlugins(allowHtml, mathReady, extra) {
292
350
  return React.useMemo(() => {
293
351
  const math = mathReady && loadedMath ? loadedMath : null;
294
- const remarkPlugins = math ? [remarkGfm__default.default, math.remarkMath] : [remarkGfm__default.default];
352
+ const remarkPlugins = math ? [remarkGfm__default.default, remarkAiCaption, math.remarkMath] : [remarkGfm__default.default, remarkAiCaption];
295
353
  const rehypePlugins = [
296
354
  // Text someone else wrote (a Word comment from any .docx): raw HTML stays text.
297
355
  ...allowHtml ? [rehypeRaw__default.default, [rehypeSanitize__default.default, markdownSanitizeSchema]] : [],
@@ -380,5 +438,5 @@ exports.MarkdownStreamingContext = MarkdownStreamingContext;
380
438
  exports.markdownBaseComponents = markdownBaseComponents;
381
439
  exports.markdownSanitizeSchema = markdownSanitizeSchema;
382
440
  exports.splitMarkdownBlocks = splitMarkdownBlocks;
383
- //# sourceMappingURL=chunk-MXP4O4DI.cjs.map
384
- //# sourceMappingURL=chunk-MXP4O4DI.cjs.map
441
+ //# sourceMappingURL=chunk-EM6DP62E.cjs.map
442
+ //# sourceMappingURL=chunk-EM6DP62E.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/remark-ai-caption.ts","../src/ai/MarkdownRenderer.tsx"],"names":["jsx","createContext","useRef","useState","useCallback","useEffect","jsxs","Check","Copy","cn","Fragment","defaultSchema","useMemo","useReducer","remarkGfm","rehypeRaw","rehypeSanitize","rehypeSlug","React","Markdown","normalizeCitationMarkup","closeOpenEmphasis","trimOpenCitation"],"mappings":"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","file":"chunk-EM6DP62E.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 * A figure passage's text is not the document's words: the backend stores the caption a vision\n * model wrote as a callout,\n *\n * > [!ai-caption]\n * > Bar chart of damages claimed by year ...\n *\n * and that callout is its only marker. This remark step renders it as a labelled aside\n * (\"Generated image caption\") with the marker removed, wherever a passage is shown.\n */\n\nexport const AI_CAPTION_MARKER = '[!ai-caption]'\nexport const AI_CAPTION_LABEL = 'Generated image caption'\n\ninterface MdNode {\n type: string\n value?: string\n children?: MdNode[]\n data?: { hName?: string; hProperties?: Record<string, unknown> }\n}\n\n/** Strip the marker from the blockquote's first text; true when it was there. */\nfunction takeMarker(quote: MdNode): boolean {\n const paragraph = quote.children?.[0]\n const text = paragraph?.type === 'paragraph' ? paragraph.children?.[0] : undefined\n if (text?.type !== 'text' || !text.value?.trimStart().startsWith(AI_CAPTION_MARKER)) return false\n text.value = text.value.trimStart().slice(AI_CAPTION_MARKER.length).replace(/^\\s+/, '')\n // The marker had a line of its own: drop the break or empty paragraph it leaves behind.\n const rest = paragraph!.children!\n if (!text.value) rest.shift()\n if (rest[0]?.type === 'break') rest.shift()\n if (!rest.length) quote.children!.shift()\n return true\n}\n\nfunction walk(node: MdNode) {\n for (const child of node.children ?? []) {\n if (child.type === 'blockquote' && takeMarker(child)) {\n child.data = {\n hName: 'aside',\n hProperties: {\n className: [\n 'my-2',\n 'rounded-md',\n 'border',\n 'border-border',\n 'bg-muted/40',\n 'px-3',\n 'py-2',\n ],\n dataAiCaption: '',\n ariaLabel: AI_CAPTION_LABEL,\n },\n }\n child.children = [\n {\n type: 'paragraph',\n data: {\n hName: 'div',\n hProperties: {\n className: ['mb-1', 'text-xs', 'font-medium', 'text-muted-foreground'],\n dataTestid: 'ai-caption-label',\n },\n },\n children: [{ type: 'text', value: AI_CAPTION_LABEL }],\n },\n ...(child.children ?? []),\n ]\n } else {\n walk(child)\n }\n }\n}\n\nexport function remarkAiCaption() {\n return (tree: MdNode) => walk(tree)\n}\n","import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'\nimport Markdown, { type Components, type Options as MarkdownOptions } from 'react-markdown'\nimport remarkGfm from 'remark-gfm'\nimport rehypeRaw from 'rehype-raw'\nimport rehypeSanitize, { defaultSchema, type Options as SanitizeSchema } from 'rehype-sanitize'\nimport rehypeSlug from 'rehype-slug'\nimport { Copy, Check } from 'lucide-react'\nimport { cn } from '../lib/utils'\nimport { markdownBaseComponents } from './markdown-base-components'\nimport { remarkAiCaption } from './remark-ai-caption'\nimport { MarkdownStreamingContext } from './markdown-streaming-context'\nimport { splitMarkdownBlocks } from './split-markdown-blocks'\nimport { closeOpenEmphasis, normalizeCitationMarkup, trimOpenCitation } from './normalize-citations'\n\n/** remark/rehype plugin lists, as react-markdown takes them. */\ntype PluggableList = NonNullable<MarkdownOptions['rehypePlugins']>\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 * Safety: inline HTML (redlines, `<mark>` highlights, parser tables) is parsed and then sanitised\n * (`markdownSanitizeSchema`), so a model or a document cannot inject scripts, handlers or styles.\n * Headings get slug ids, so `#section` links and bookmarks land.\n *\n * Math: `$…$` and `$$…$$` are typeset with KaTeX. remark-math, rehype-katex and KaTeX's CSS\n * (~90 kB gzip) load only once some content actually contains math. A `$` before a digit is a\n * currency sign, never a formula delimiter.\n *\n * Extending: pass `components` to override any element (the kit's `AiMarkdown`\n * and `CitationRenderer` layer their accent links / citation popovers on top; a host composes\n * `MarkdownLink` / `MarkdownCode` into its own overrides), and `rehypePlugins` for trusted\n * transforms that run after sanitising (e.g. turning entity ids into pills).\n */\n\nconst EDGE_FADE = '1.5rem'\n\n/**\n * A GFM table in its own horizontal scroller. The table keeps a minimum width per column\n * (`min-w-[7rem]` cells, words never broken mid-word), so on a narrow screen it scrolls sideways\n * inside this wrapper instead of squeezing cells to a letter per line. The edge(s) with more\n * content to scroll to fade out, which is the scroll hint. Wide containers are unchanged: the\n * table is `w-full` and nothing overflows.\n */\nfunction MarkdownTable({ children, ...props }: React.ComponentPropsWithoutRef<'table'>) {\n const ref = useRef<HTMLDivElement>(null)\n const [edges, setEdges] = useState({ start: false, end: false })\n const measure = useCallback(() => {\n const el = ref.current\n if (!el) return\n const start = el.scrollLeft > 1\n const end = el.scrollLeft + el.clientWidth < el.scrollWidth - 1\n setEdges((prev) => (prev.start === start && prev.end === end ? prev : { start, end }))\n }, [])\n useEffect(() => {\n const el = ref.current\n if (!el) return\n measure()\n if (typeof ResizeObserver === 'undefined') return\n const observer = new ResizeObserver(measure)\n observer.observe(el)\n if (el.firstElementChild) observer.observe(el.firstElementChild)\n return () => observer.disconnect()\n }, [measure])\n const mask =\n edges.start || edges.end\n ? `linear-gradient(to right, ${edges.start ? 'transparent' : '#000'}, #000 ${EDGE_FADE}, #000 calc(100% - ${EDGE_FADE}), ${edges.end ? 'transparent' : '#000'})`\n : undefined\n return (\n <div\n ref={ref}\n onScroll={measure}\n data-testid=\"markdown-table-scroller\"\n data-scroll-start={edges.start || undefined}\n data-scroll-end={edges.end || undefined}\n className=\"my-3 overflow-x-auto rounded-lg border border-border\"\n style={mask ? { maskImage: mask, WebkitMaskImage: mask } : undefined}\n >\n <table\n data-testid=\"markdown-table\"\n className=\"w-full border-collapse text-sm [overflow-wrap:normal] [word-break:normal]\"\n {...props}\n >\n {children}\n </table>\n </div>\n )\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 */\nexport function 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-[max(0.8em,var(--text-xs))] after:font-semibold 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 // A `cite:` reference is not a place to go: the click must not navigate.\n onClick={href?.startsWith('cite:') ? (e) => e.preventDefault() : undefined}\n {...(isInternal ? {} : { target: '_blank', rel: 'noopener noreferrer' })}\n {...props}\n >\n {children}\n </a>\n )\n}\n\n/**\n * Inline code, and fenced code as a `CodeBlock` (language label + copy). Exported so a host's own\n * `code` override can handle its special cases (a diagram language, an id shown as a pill) and\n * hand everything else back here.\n */\nexport 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\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: MarkdownCode,\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: MarkdownTable,\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\n className=\"min-w-[7rem] border-b border-border px-3 py-2 text-left font-semibold\"\n {...props}\n >\n {children}\n </th>\n ),\n td: ({ children, ...props }: React.ComponentPropsWithoutRef<'td'>) => (\n <td className=\"min-w-[7rem] 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/**\n * What survives sanitising inline HTML: GitHub's schema, plus the classes the downstream steps and\n * renderers read (KaTeX's `math-*` placeholders, `language-*` on code), `<mark>` for search\n * highlights, the generated-caption `<aside>` (`remark-ai-caption`), and `data-cid` on `<ins>`/`<del>` (the redline's per-change id, minted from diff\n * positions, never from content). Scripts, event handlers, inline styles and `javascript:` URLs\n * stay out.\n */\nexport const markdownSanitizeSchema: SanitizeSchema = {\n ...defaultSchema,\n tagNames: [...(defaultSchema.tagNames ?? []), 'mark', 'aside'],\n attributes: {\n ...defaultSchema.attributes,\n span: [...(defaultSchema.attributes?.span ?? []), 'className'],\n div: [...(defaultSchema.attributes?.div ?? []), 'className', 'dataTestid'],\n // A figure's generated caption (`remark-ai-caption`): its box and label.\n aside: ['className', 'dataAiCaption', 'ariaLabel'],\n code: [...(defaultSchema.attributes?.code ?? []), 'className'],\n pre: [...(defaultSchema.attributes?.pre ?? []), 'className'],\n ins: [...(defaultSchema.attributes?.ins ?? []), 'dataCid'],\n del: [...(defaultSchema.attributes?.del ?? []), 'dataCid'],\n },\n}\n\n// ── Lazy KaTeX (math) ──────────────────────────────────────────────────────\n// Loaded once, module-wide, the first time content with math renders; mounted renderers that\n// were waiting re-render when it lands.\ntype MathPlugins = { remarkMath: PluggableList[number]; rehypeKatex: PluggableList[number] }\nlet loadedMath: MathPlugins | null = null\nlet mathLoadStarted = false\nconst mathSubscribers = new Set<() => void>()\n\nfunction loadMath() {\n if (mathLoadStarted) return\n mathLoadStarted = true\n void Promise.all([\n import('remark-math'),\n import('rehype-katex'),\n import('katex/dist/katex.min.css'),\n ])\n .then(([remarkMath, rehypeKatex]) => {\n loadedMath = { remarkMath: remarkMath.default, rehypeKatex: rehypeKatex.default }\n mathSubscribers.forEach((notify) => notify())\n })\n .catch(() => {\n // Retry on the next math-bearing render if the chunk failed to load.\n mathLoadStarted = false\n })\n}\n\n// remark-math's inline rule: `$…$` with no whitespace just inside, or a `$$…$$` block. A lone\n// price (\"$5,000\") never matches.\nconst MATH_RE = /\\$\\$[\\s\\S]+?\\$\\$|(?<!\\\\)\\$(?![\\s$])(?:\\\\\\$|[^$\\n])+?(?<![\\s\\\\])\\$/\n\n/**\n * A `$` straight before a digit is a currency sign (\"$2,000\", \"≈$50,000\"), not the start of inline\n * math: two in one paragraph would otherwise pair up and typeset everything between as a formula.\n * Escape them (outside code, where a backslash would show) so they stay text.\n */\nfunction escapeCurrencyDollars(markdown: string): string {\n return markdown\n .split(/(```[\\s\\S]*?```|`[^`\\n]*`)/)\n .map((part, i) => (i % 2 === 1 ? part : part.replace(/(?<!\\\\)\\$(?=\\d)/g, '\\\\$')))\n .join('')\n}\n\n/** True once the math plugins are loaded for content that contains math. */\nfunction useMathReady(content: string): boolean {\n const hasMath = useMemo(() => MATH_RE.test(content), [content])\n const [, rerender] = useReducer((n: number) => n + 1, 0)\n useEffect(() => {\n if (!hasMath || loadedMath) return\n mathSubscribers.add(rerender)\n loadMath()\n return () => {\n mathSubscribers.delete(rerender)\n }\n }, [hasMath])\n return hasMath && loadedMath !== null\n}\n\nconst NO_PLUGINS: PluggableList = []\n\n/**\n * The remark/rehype pipelines. Raw HTML (when allowed) is parsed and sanitised before the trusted\n * steps (KaTeX, slugs, the host's plugins), so their output is not stripped again. Memoised on\n * its inputs so react-markdown sees stable arrays and memoised blocks do not re-parse.\n */\nfunction usePlugins(allowHtml: boolean, mathReady: boolean, extra: PluggableList) {\n return useMemo(() => {\n const math = mathReady && loadedMath ? loadedMath : null\n const remarkPlugins: PluggableList = math\n ? [remarkGfm, remarkAiCaption, math.remarkMath]\n : [remarkGfm, remarkAiCaption]\n const rehypePlugins: PluggableList = [\n // Text someone else wrote (a Word comment from any .docx): raw HTML stays text.\n ...(allowHtml ? [rehypeRaw, [rehypeSanitize, markdownSanitizeSchema]] : []),\n ...(math ? [math.rehypeKatex] : []),\n rehypeSlug,\n ...extra,\n ] as PluggableList\n return { remarkPlugins, rehypePlugins }\n }, [allowHtml, mathReady, extra])\n}\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 remarkPlugins,\n rehypePlugins,\n}: {\n content: string\n components: Partial<Components>\n remarkPlugins: PluggableList\n rehypePlugins: PluggableList\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 * Inline HTML in the markdown becomes elements, sanitised (default). False keeps it as text: for\n * content from outside the app, such as a comment in a Word file anyone could have written.\n */\n allowHtml?: boolean\n /**\n * Trusted rehype plugins run after sanitising, KaTeX and heading slugs (e.g. a pass that turns\n * entity ids into elements the host's `components` render). Keep the array referentially\n * stable (module scope), or every block re-parses.\n */\n rehypePlugins?: PluggableList\n}\n\nfunction MarkdownRendererComponent({\n children,\n components: customComponents,\n renderCitation,\n isStreaming = false,\n className,\n testId = 'markdown-renderer',\n allowHtml = true,\n rehypePlugins: extraRehypePlugins = NO_PLUGINS,\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 text = useMemo(() => {\n const escaped = escapeCurrencyDollars(children)\n return normalizeCitationMarkup(\n isStreaming ? closeOpenEmphasis(trimOpenCitation(escaped)) : escaped\n )\n }, [children, isStreaming])\n const blocks = useMemo(() => splitMarkdownBlocks(text), [text])\n const mathReady = useMathReady(text)\n const { remarkPlugins, rehypePlugins } = usePlugins(allowHtml, mathReady, extraRehypePlugins)\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\n key={index}\n content={block}\n components={components}\n remarkPlugins={remarkPlugins}\n rehypePlugins={rehypePlugins}\n />\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"]}