/** * MarkdownEngine — THE one react-markdown pipeline for every surface * (chat messages, blog, docs, KB articles, legal, releases, admin previews). * * `SimpleMarkdownRenderer` and `RichMarkdownRenderer` are thin compositions * over this engine (see ./simple-markdown-renderer.tsx and ./rich/) — there * is exactly ONE parse path, ONE sanitizer stack, ONE mermaid, ONE * heading-id algorithm, ONE link-click model. Do not fork this pipeline; * extend via `componentOverrides` / `additionalRemarkPlugins` / * `preprocessContent` / `extraAllowedHtmlTags`. * * Pipeline order (each stage documented in ./sanitize.ts): * preprocessContent (composition hook, e.g. shortcodes) * → completeStreamingTail (streaming only — MUST precede the escape pass) * → escapeUnknownHtmlTags (text pre-pass, React 19 crash guard) * → remark: remarkGfm, remarkBreaks, ...additionalRemarkPlugins * → rehype: rehypeRaw → rehypeSanitize(schema) → rehypeStripUnsafe * → rehypeHighlight * → urlTransform: cardAwareUrlTransform * → components: buildBaseComponents(...) spread-last componentOverrides */ import React from 'react'; import { type Components } from 'react-markdown'; import type { PluggableList } from 'unified'; import type { ResolveLinkResult } from '../../../types/doc-source'; import { type TextSizeConfig } from './text-size'; import { type HeadingSection } from './heading-ids'; export type { ResolveLinkResult }; /** * Module-scope empty default for `brokenLinks`. * * A `brokenLinks = []` DEFAULT PARAMETER allocates a fresh array on every * render, so the `components` useMemo (which lists it as a dep) recomputed * every render, which changed `StreamingBlockRenderer`'s `components` prop * identity, which made its `memo` bail EVERY time — every completed block * re-parsed on every streamed token, defeating the entire atomic-block * optimization. Any new default that is an object/array/function MUST be * hoisted here for the same reason. * * Exported so the compositions that apply the SAME default before handing * props down (rich) share this ONE identity instead of declaring a second * module-scope empty array with a copy of this rationale. Deliberately NOT * re-exported from ./index — it is an internal identity contract between the * engine and its compositions, not public API. */ export declare const NO_BROKEN_LINKS: readonly string[]; export interface MarkdownEngineProps { content: string; className?: string; /** Backend-provided heading IDs for deep-link anchors */ sectionIds?: HeadingSection[]; /** When the page already has an H1, render markdown `#` as `