@ai-markdown/core 3.2.2 → 3.3.0

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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/blockPlan.ts","../src/blockPlanner.ts","../src/contribution.ts","../src/smoothCoordinator.ts","../src/coordinator.ts","../src/tailSignal.ts","../src/aggregateFootnotes.ts","../src/cloneHastForRender.ts","../src/pipelineSession.ts","../src/coordinationPreparation.ts"],"sourcesContent":["/** Framework-neutral block planning and dependency fingerprints.\n * Cache ownership and conversion to framework nodes belong to adapters.\n * See apps/docs/content/guides/architecture.md and packages/core/README.md for the contract.\n */\nimport type { Element as HastElement, Root as HastRoot, RootContent as HastChild } from 'hast';\nimport type { Root as MdastRoot, RootContent as MdastContent, Nodes as MdastNodes } from 'mdast';\nimport { SKIP, visit } from 'unist-util-visit';\nimport { isFootnoteSection, normalizeId, type Registry } from '@ai-markdown/engine';\n\n/**\n * mdast types whose presence in a block makes that block dependent on\n * cross-block syntax (footnote/link/image references and definitions).\n * Tainted blocks invalidate via the document `globalCtx` digest; non-tainted\n * blocks invalidate only on raw + position change.\n */\nconst TAINT_TYPES: ReadonlySet<string> = new Set([\n 'footnoteReference',\n 'footnoteDefinition',\n 'linkReference',\n 'imageReference',\n 'definition',\n]);\n\n/**\n * mdast types that contribute to the document-wide `globalCtx` digest.\n * Currently identical to {@link TAINT_TYPES} — refs/defs are simultaneously\n * \"things that make a block tainted\" and \"things that change a tainted\n * block's render output\". Kept as a separate constant so the two roles can\n * diverge later (e.g. a future plugin that introduces a new node type which\n * is a ctx contributor but not a per-block taint source).\n */\nconst CTX_TYPES: ReadonlySet<string> = TAINT_TYPES;\n\n/** Source-level identity of one renderable hast block. */\nexport interface BlockInfo {\n raw: string;\n startOffset: number;\n endOffset: number;\n /**\n * Line and column at the block's start. Tracked alongside `startOffset`\n * because two different source documents can produce the same byte offset\n * for the \"same\" content (`A\\n\\nB\\n\\nTarget` vs `ABCD\\n\\nTarget` — both\n * have `Target` starting at offset 6 but on different lines). Custom\n * components that read `node.position.start.line` would otherwise see\n * stale data from a false-positive cache hit.\n */\n startLine: number;\n startColumn: number;\n /** True if the block contains any TAINT_TYPES node — invalidate on globalCtx change. */\n hasReference: boolean;\n /**\n * Structural digest of the block's hast subtree, computed ONLY for blocks\n * that originate from raw HTML (mdast `html` nodes, or blocks resolved via\n * the range-containment fallback). Undefined for markdown-native blocks.\n *\n * Why it exists: rehype-raw applies the HTML parsing algorithm, so an\n * UNCLOSED container tag (`<details>` mid-stream, before its `</details>`\n * arrives) swallows every FOLLOWING top-level sibling into itself —\n * including the synthetic `<section data-footnotes>`. The block's mdast\n * identity (raw, position, ctx) is byte-identical across all those frames,\n * so without this digest the first swallowed snapshot would be a permanent\n * cache hit: content rendered inside the container freezes, and when the\n * close tag finally arrives the real nodes re-render at top level while the\n * stale copies stay trapped in the cached container (the \"duplicate\n * footnote section inside <details>\" bug).\n *\n * Digest composition — `maxEnd:descendantCount:hasFootnoteSection:swallowedHash`:\n * - max end offset over the subtree catches swallowed POSITIONED siblings\n * (their offsets lie beyond the mdast node's own range);\n * - descendant count catches position-less synthetic content moving in or\n * out (the footnote section's own element carries no position, and its\n * li contents point BACK at the definitions' offsets, which can be\n * smaller than the container's end — maxEnd alone misses that case);\n * - the explicit dataFootnotes bit closes the degenerate corner where a\n * swallowed section and later real children tie on both numbers;\n * - a hash of the swallowed extent's SOURCE (`[ownEnd, maxEnd)`) quickly\n * rejects most equal-length, equal-shape replacements. A matching hash\n * still requires an exact {@link BlockInfo.swallowedSource} comparison.\n *\n * Markdown-native blocks (paragraph/list/math/code/…) never receive\n * reparented content — every hast descendant derives from their own mdast\n * source range — so they skip the subtree walk entirely. This keeps the\n * per-frame cost away from huge deterministic subtrees like KaTeX output.\n */\n hastDigest?: string;\n /**\n * Exact source in the swallowed interval `[ownEnd, maxEnd)` of a raw-HTML\n * block. The FNV-1a 32-bit hash in `hastDigest` can collide for different\n * equal-length contents, so a cache hit must compare this string too.\n * Empty when the block swallowed no source; absent for markdown-native\n * blocks. Optional to keep existing public block-plan producers compatible.\n */\n swallowedSource?: string;\n /** A raw-HTML container that swallowed the synthesized\n * `<section data-footnotes>` (rehype-raw reparents the following siblings\n * into an unclosed `<details>` / `<div>` mid-stream). The footer is then\n * NOT a top-level plan item, so coordinated mode's \"skip the local\n * footer, the aggregate renders it\" rule never fired and the document\n * showed two `<section data-footnotes>` with colliding li ids (2026-08-19\n * review r2 P2-9). Coordinated renders strip it from this block's subtree;\n * standalone renders keep it exactly where the full parse puts it. */\n containsFootnoteSection?: boolean;\n /** TAINT-block 专属:按节点类型分桶的 label set。Normalized 形态(uppercase)。\n * Undefined when hasReference === false. */\n taintLabels?: {\n footnoteRefLabels: string[];\n linkRefLabels: string[];\n imageRefLabels: string[];\n footnoteDefLabels: string[];\n /** Chunk-local footnote state the engine baked into this block's\n * `footnote-sup` placeholders (`localOccurrence` / `localNumber` come\n * from a per-chunk counter over all PRECEDING refs, customMdastHandlers):\n * per ref label, refs-before-this-block count and first-appearance\n * rank. Same raw + same registry answers but a different prefix (an\n * in-place `[^x]`→`[^w]` edit upstream) must miss the cache, or the\n * stale occurrence resolves to a null global occurrence and the mark\n * vanishes (2026-08-19 review P2-4). Absent when the block has no refs. */\n footnoteRefLocalCtx?: string;\n /** Baked `localUrl` / `localTitle` of swallowed `cross-chunk-link` /\n * `-image` placeholders, as `[[label, url, title|null]…]` (JSON). The\n * render falls back to these when the registry has no canonical def,\n * where the fingerprint's `lr:`/`ir:` part is the constant `null` —\n * see scanSwallowedSubtree. Absent when nothing was swallowed. */\n swallowedDefCtx?: string;\n };\n}\n\n/**\n * Slice the source string for the given mdast node's position. mdast nodes\n * produced by remark-parse always carry `position`; the empty-string fallback\n * is purely defensive.\n */\nfunction extractRaw(node: MdastNodes, source: string): string {\n if (!node.position) return '';\n return source.slice(node.position.start.offset, node.position.end.offset);\n}\n\n// isFootnoteSection lives in the engine now (boundary action ①): the\n// incremental-parse engine's attributeHastChildren needs it too, and\n// engine→core imports are forbidden. Re-exported so this module's export\n// surface (and its tests) stay unchanged.\nexport { isFootnoteSection };\n\n/** Dev invariant: every block hast child must retain its mdast `position`. */\nexport function hasMdastSource(node: HastElement): boolean {\n return node.position !== undefined;\n}\n\n/**\n * Compute {@link BlockInfo.hastDigest} for a raw-HTML block: iterative walk\n * over the hast subtree collecting (max end offset, descendant count,\n * contains-footnote-section). See the field's JSDoc for why each component\n * is load-bearing. Exported for tests.\n */\nexport function computeHtmlBlockDigest(el: HastElement, source?: string, ownEnd?: number): string {\n return computeHtmlBlockDigestWithExtent(el, source, ownEnd).digest;\n}\n\n/** {@link computeHtmlBlockDigest} plus the subtree's max end offset — the end\n * of the SWALLOWED extent, which callers need to ask what fell inside.\n *\n * Both position sources here are HAST offsets, and every use of the extent\n * is unsafe in the SHORT direction (too small and a swallowed reference\n * escapes the taint test, so an edit to a definition outside the container\n * leaves a stale href in the cache). They are redundant on purpose: the\n * container's own end already reaches past the swallowed blocks, because\n * `rehype-raw` reparses the raw string and closes the element wherever\n * parse5 does — NOT where the mdast `html` node ends, which is just the\n * opening tag. The descendant walk covers it a second time. Verified in\n * 2026-08-20 A1 across 162 container/tail shapes: destroying either source\n * alone changes nothing, destroying both under-taints 130 of them. */\nexport function computeHtmlBlockDigestWithExtent(\n el: HastElement,\n source?: string,\n ownEnd?: number\n): { digest: string; maxEnd: number } {\n let maxEnd = el.position?.end?.offset ?? 0;\n let count = 0;\n let hasFootnoteSection = false;\n const stack: HastElement['children'][number][] = [...el.children];\n while (stack.length > 0) {\n const node = stack.pop()!;\n count++;\n const end = node.position?.end?.offset;\n if (end !== undefined && end > maxEnd) {\n maxEnd = end;\n }\n if (node.type === 'element') {\n if (isFootnoteSection(node)) {\n hasFootnoteSection = true;\n }\n for (const child of node.children) {\n stack.push(child);\n }\n }\n }\n // Swallowed SOURCE hash: `maxEnd:count` are blind to an equal-length,\n // equal-shape replacement of the swallowed siblings (`Answer: 42` →\n // `Answer: 43` inside an unclosed <details>, single-frame — v2.4.2 review\n // P2-1); the container's own `raw` is unchanged, so the cache hit kept\n // rendering the old text. The hash is a fast rejection; buildBlocks also\n // retains the exact swallowed source for collision-safe cache validation.\n const swallowed = source !== undefined && ownEnd !== undefined && maxEnd > ownEnd ? source.slice(ownEnd, maxEnd) : '';\n return { digest: `${maxEnd}:${count}:${hasFootnoteSection ? 1 : 0}:${fnv1a(swallowed)}`, maxEnd };\n}\n\n/** FNV-1a 32-bit over UTF-16 code units — cheap, no allocation. */\nfunction fnv1a(text: string): string {\n let h = 0x811c9dc5;\n for (let i = 0; i < text.length; i++) {\n h ^= text.charCodeAt(i);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return h.toString(36);\n}\n\n/**\n * One step of the document render plan, in source order.\n *\n * The plan covers EVERY top-level hast child so that the adapter’s block cache\n * can preserve react-markdown's byte-equivalent output — including the whitespace\n * `text` nodes that `mdast-util-to-hast` inserts between block elements.\n *\n * - `block`: a hast element with an mdast counterpart, eligible for caching by\n * `(raw, occurrence index, ctx, startOffset)`.\n * - `inline`: a non-element top-level child (whitespace text, comment, …) or\n * an element that has no mdast counterpart but is still safe to render.\n * These are rendered every frame without caching — they are typically\n * single whitespace characters.\n * - `synthetic`: the synthesized footnote `<section data-footnotes>`, cached\n * in a single slot keyed by `globalCtx`.\n */\nexport type RenderItem =\n | { kind: 'block'; el: HastElement; key: string; info: BlockInfo }\n | { kind: 'inline'; el: HastChild; key: string }\n | { kind: 'synthetic'; el: HastElement; key: string };\n\nconst EMPTY_LABELS: ReadonlySet<string> = new Set<string>();\n\n/** Extra render-time facts {@link buildBlocks} cannot read off the trees. */\nexport interface BuildBlocksOptions {\n /** Full document when planning only a tail. Its ordered reference and\n * definition context seeds numbering and invalidation for tail blocks. */\n contextMdast?: MdastRoot;\n /**\n * Labels this chunk phantom-injected because another chunk defines them\n * (already normalized). The coordinated handlers keep these out of\n * `state.footnoteOrder`, so the cache key's rank must keep them out too —\n * see the note in the rank pass. Empty / omitted in standalone mode, where\n * no phantom injection happened.\n */\n phantomFootnoteLabels?: ReadonlySet<string>;\n}\n\n/** Result of {@link buildBlocks}. */\nexport interface BuildBlocksResult {\n /** Render plan in document order — drives the adapter’s block cache. */\n plan: RenderItem[];\n /** JSON-stringified ordered list of TAINT-typed nodes in document order. */\n globalCtx: string;\n // ── Derived views (kept for tests and convenience) ─────────────────────\n /** Flat list of `BlockInfo` for cacheable blocks, in document order. */\n blocks: BlockInfo[];\n /** 1:1 with `blocks` — the hast Element each BlockInfo refers to. */\n blockHasts: HastElement[];\n /** Synthesized footnote section, if present. */\n synthetic?: HastElement;\n}\n\nconst FOOTNOTE_SECTION_KEY = '__footnote_section__';\n\n/**\n * Build the document render plan + ctx digest from a parsed mdast and its\n * rendered hast.\n *\n * Plan construction is driven by `hast.children` (NOT `mdast.children`):\n * pipeline transformers like `remarkSqueezeParagraphs` and\n * `remarkRemoveComments` drop blocks, so the rendered hast may have fewer\n * top-level children than the parsed mdast. Driving by hast guarantees the\n * plan covers exactly what will be rendered.\n *\n * Each hast top-level child becomes one plan item:\n * - `<section data-footnotes>` → `synthetic` (cached in a single slot)\n * - element with mdast counterpart → `block` (cacheable by raw + occurrence + ctx + offset)\n * - text / comment / element-without-counterpart → `inline` (rendered fresh every frame)\n *\n * The mdast counterpart of a block element is found by source offset using\n * a two-tier lookup: an exact-offset Map (mdast-util-to-hast's default 1:1\n * propagation), then range-containment `findLast` fallback for cases like\n * `rehype-raw` splitting one mdast `html` node into multiple hast siblings\n * (e.g. ` <div>Hi</div>` — leading spaces shift the `<div>` offset\n * inside the parent html node's source range).\n *\n * In dev, a hast block whose offset matches no mdast counterpart at all\n * throws — that means a rehype plugin synthesized positions outside the\n * source range, which is a bug worth surfacing. In production it falls\n * through to an `inline` plan item so user content is never silently lost.\n *\n * `globalCtx` walks the full mdast for footnote/link/image refs and\n * definitions in document order (no dedupe — order matters for footnote\n * numbering), then JSON-stringifies the collected tuples. That string is the\n * invalidation key for tainted blocks and the synthetic footnote section.\n */\nexport function buildBlocks(\n mdast: MdastRoot,\n hast: HastRoot,\n source: string,\n options: BuildBlocksOptions = {}\n): BuildBlocksResult {\n const phantomFootnotes = options.phantomFootnoteLabels ?? EMPTY_LABELS;\n const mdastByOffset = new Map<number, MdastContent>();\n // Sorted [start, end, node] table for the range-containment fallback used\n // when a hast block's offset is INSIDE an mdast node's source range\n // (`rehype-raw` splitting one mdast `html` node into multiple hast\n // siblings is the canonical case). Pre-sorted because mdast children come\n // out of remark-parse in source order; we collect positioned ones in\n // place, preserving order. Binary search at lookup time replaces the\n // previous O(N) `findLast` — without it, M hast blocks × N mdast children\n // degrades to O(N×M) on pathological streams with many splits.\n type Range = { start: number; end: number; node: MdastContent };\n const mdastRanges: Range[] = [];\n for (const child of mdast.children) {\n const off = child.position?.start.offset;\n const endOff = child.position?.end.offset;\n if (off !== undefined) {\n mdastByOffset.set(off, child);\n }\n if (off !== undefined && endOff !== undefined) {\n mdastRanges.push({ start: off, end: endOff, node: child });\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n for (let i = 1; i < mdastRanges.length; i++) {\n if (mdastRanges[i].start < mdastRanges[i - 1].start) {\n // Should never trip: mdast.children is source-order. If it does\n // trip, the binary search below is wrong — surface it loudly so\n // we sort defensively rather than silently misroute hast blocks.\n throw new Error(\n 'block-memo: mdast.children not sorted by source offset — ' +\n 'a remark plugin is reordering top-level children.'\n );\n }\n }\n }\n function findContainingMdast(offset: number): MdastContent | undefined {\n // Upper-bound binary search: find first index where range.start > offset.\n // The candidate is the immediate predecessor (largest start <= offset).\n let lo = 0;\n let hi = mdastRanges.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (mdastRanges[mid].start <= offset) lo = mid + 1;\n else hi = mid;\n }\n const idx = lo - 1;\n if (idx < 0) return undefined;\n const r = mdastRanges[idx];\n // Top-level mdast children have non-overlapping source ranges, so the\n // single largest-start candidate is the unique container (if any).\n return offset < r.end ? r.node : undefined;\n }\n\n const ctxParts: unknown[] = [];\n // Start offsets of every taint node, in document order — used to tell\n // whether a raw-HTML container swallowed a reference whose RENDERING\n // depends on a definition outside it (see the isRawHtmlBlock branch).\n const taintOffsets: number[] = [];\n /** First index in `taintOffsets` at or after `offset`. The offsets are\n * pushed by a document-order `visit`, so the array is already sorted —\n * the same property `findContainingMdast` above relies on. */\n const firstTaintAtOrAfter = (offset: number): number => {\n let lo = 0;\n let hi = taintOffsets.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (taintOffsets[mid] < offset) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n };\n visit(options.contextMdast ?? mdast, (n) => {\n if (!CTX_TYPES.has(n.type)) return;\n const at = n.position?.start?.offset;\n if (at !== undefined) taintOffsets.push(at);\n if (n.type === 'footnoteReference') ctxParts.push(['fr', n.identifier]);\n else if (n.type === 'footnoteDefinition') ctxParts.push(['fd', n.identifier, extractRaw(n, source)]);\n else if (n.type === 'linkReference') ctxParts.push(['lr', n.identifier]);\n else if (n.type === 'imageReference') ctxParts.push(['ir', n.identifier]);\n else if (n.type === 'definition') ctxParts.push(['d', n.identifier, n.url, n.title ?? null]);\n });\n if (process.env.NODE_ENV !== 'production') {\n // A preorder walk yields non-decreasing start offsets on any well-formed\n // tree, and `firstTaintAtOrAfter` is a binary search over exactly that.\n // Same guard, same reason, and the same shape as the one on\n // `mdastRanges`: the env read stays OUT of the per-node path, which cost\n // more than the search saved when it was inside it.\n for (let i = 1; i < taintOffsets.length; i++) {\n if (taintOffsets[i] < taintOffsets[i - 1]) {\n throw new Error(\n 'block-memo: taint node start offsets are not source-ordered — ' +\n 'a remark plugin is reordering nodes or emitting bad positions.'\n );\n }\n }\n }\n const globalCtx = JSON.stringify(ctxParts);\n\n const plan: RenderItem[] = [];\n const blocks: BlockInfo[] = [];\n const blockHasts: HastElement[] = [];\n let synthetic: HastElement | undefined;\n // Mirror of the engine's per-chunk footnote counter (customMdastHandlers:\n // one bump per footnoteReference in mdast-util-to-hast's preorder walk):\n // computed ONCE over the mdast in document order, per ref NODE, so the\n // hast-driven block loop below cannot skew it — a range-fallback block\n // resolves several hast siblings to one mdast node (its refs would be\n // counted twice), and refs inside a footnote definition are rendered by\n // the footer AFTER every body ref (never in a body block) — skipped.\n const refLocalCtx = new Map<MdastNodes, readonly [prior: number, rank: number]>();\n {\n const counts = new Map<string, number>();\n const ranks = new Map<string, number>();\n const rankOf = (id: string): number => {\n // A PHANTOM label never enters the engine's footnoteOrder — the\n // coordinated handlers return early for both its definition and its\n // references, and its `footnote-sup` gets no `localNumber` at all. It\n // must not take a rank here either, or the model drifts from the value\n // actually baked into the placeholder: with `[^A]` cross-chunk and\n // `[^B]` local, B's baked number is 1 while A is phantom and 2 once A's\n // definition arrives — and B's rank, counting A both times, stayed 1.\n // Same raw, same position, same rank: the cache served a superscript\n // reading 1 under a footer numbering it 2 (2026-08-20 B2).\n if (phantomFootnotes.has(id)) return -1;\n let rank = ranks.get(id);\n if (rank === undefined) {\n rank = ranks.size;\n ranks.set(id, rank);\n }\n return rank;\n };\n visit(options.contextMdast ?? mdast, (n) => {\n if (n.type === 'footnoteDefinition') {\n // A definition met before the label's first ref can enter the\n // engine's footnoteOrder first (preserveOrphan) — it takes part in\n // the first-appearance rank; its body refs are footer-rendered.\n //\n // Modelled as `preserveOrphan: true` unconditionally, which is not\n // always what the engine does. Deliberate: with orphan protection\n // OFF the two orderings can disagree, but they disagree the SAME way\n // in every frame, and the flag only changes by a prop change — which\n // re-parses the whole pipeline anyway. Phantom status is different:\n // it flips mid-stream, which is what made it a stale-cache bug.\n rankOf(normalizeId(String(n.identifier)));\n return SKIP;\n }\n if (n.type !== 'footnoteReference') return;\n const id = normalizeId(String(n.identifier));\n const prior = counts.get(id) ?? 0;\n refLocalCtx.set(n, [prior, rankOf(id)]);\n counts.set(id, prior + 1);\n });\n }\n\n for (let i = 0; i < hast.children.length; i++) {\n const hastChild = hast.children[i];\n\n if (hastChild.type !== 'element') {\n // Top-level non-element child (whitespace text inserted by\n // mdast-util-to-hast between block elements, sanitized comment, …).\n // Render inline without caching to preserve byte-equivalent output.\n const off = hastChild.position?.start.offset;\n const key = off !== undefined ? `inline-${off}` : `inline-i${i}`;\n plan.push({ kind: 'inline', el: hastChild, key });\n continue;\n }\n\n const el = hastChild;\n\n if (isFootnoteSection(el)) {\n synthetic = el;\n plan.push({ kind: 'synthetic', el, key: FOOTNOTE_SECTION_KEY });\n continue;\n }\n const hastOffset = el.position?.start.offset;\n if (hastOffset === undefined) {\n // Element without position (synthesized by some plugin) — preserve in\n // document order, rendered as inline (no cache key available).\n plan.push({ kind: 'inline', el, key: `inline-i${i}` });\n continue;\n }\n\n let mdastNode = mdastByOffset.get(hastOffset);\n let viaRangeFallback = false;\n\n if (!mdastNode) {\n mdastNode = findContainingMdast(hastOffset);\n viaRangeFallback = mdastNode !== undefined;\n }\n\n if (!mdastNode) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n `block-memo: hast block at offset ${hastOffset} has no mdast counterpart. ` +\n `A rehype plugin may have synthesized positions outside source. tagName=${el.tagName}`\n );\n }\n // Production fallback: render inline, no cache.\n plan.push({ kind: 'inline', el, key: `inline-${hastOffset}` });\n continue;\n }\n\n if (mdastNode.type === 'footnoteDefinition') {\n // Footnote definitions are hoisted into the synthetic section by\n // mdast-util-to-hast — they should not appear as top-level hast blocks\n // in any current plugin chain. If the range fallback ever resolves an\n // unrelated hast block to a footnoteDefinition's mdast source range,\n // preserve it as `inline` rather than silently dropping it (defensive\n // parity with the other no-counterpart fallbacks above).\n plan.push({ kind: 'inline', el, key: `inline-${hastOffset}` });\n continue;\n }\n\n // Only a raw-HTML block can have foreign content reparented into it by\n // rehype-raw (an unclosed container tag mid-stream); markdown-native\n // blocks derive their whole subtree from their own source range.\n const isRawHtmlBlock = mdastNode.type === 'html' || viaRangeFallback;\n let hasReference = false;\n const swallowed = isRawHtmlBlock ? scanSwallowedSubtree(el) : null;\n const footnoteRefLabels: string[] = [];\n const linkRefLabels: string[] = [];\n const imageRefLabels: string[] = [];\n const footnoteDefLabels: string[] = [];\n const local: [string, number, number][] = [];\n visit(mdastNode, (n) => {\n if (!TAINT_TYPES.has(n.type)) return;\n hasReference = true;\n const id = 'identifier' in n ? normalizeId(String(n.identifier)) : null;\n if (id === null) return;\n if (n.type === 'footnoteReference') {\n footnoteRefLabels.push(id);\n // Refs inside a nested footnote definition have no entry (footer-\n // rendered, not part of this block's output).\n const ctx = refLocalCtx.get(n);\n if (ctx) local.push([id, ctx[0], ctx[1]]);\n } else if (n.type === 'linkReference') linkRefLabels.push(id);\n else if (n.type === 'imageReference') imageRefLabels.push(id);\n else if (n.type === 'footnoteDefinition') footnoteDefLabels.push(id);\n // 'definition' nodes don't carry per-block fingerprint significance\n // (they're metadata, not visible); intentionally not bucketed.\n });\n // Merge what the container swallowed (see scanSwallowedSubtree): the\n // mdast walk above saw only the empty `html` node.\n if (swallowed !== null && swallowed.hasReference) {\n hasReference = true;\n footnoteRefLabels.push(...swallowed.refLabels);\n linkRefLabels.push(...swallowed.linkLabels);\n imageRefLabels.push(...swallowed.imageLabels);\n local.push(...swallowed.local);\n }\n // JSON-encoded: labels may hold any separator character.\n const footnoteRefLocalCtx = local.length > 0 ? JSON.stringify(local) : undefined;\n const swallowedDefCtx =\n swallowed !== null && swallowed.bakedDefs.length > 0 ? JSON.stringify(swallowed.bakedDefs) : undefined;\n\n const mdastPos = mdastNode.position;\n if (!mdastPos || mdastPos.start?.offset === undefined || mdastPos.end?.offset === undefined) {\n // A third-party remark plugin emitting a half-built position: the\n // block cannot be cached, but the user's content is never silently\n // lost — render it uncached like the other no-counterpart fallbacks\n // above (v2.4.1 review: this path used to drop the hast element).\n plan.push({ kind: 'inline', el, key: `inline-${hastOffset}` });\n continue;\n }\n\n // Raw-HTML blocks are the only ones rehype-raw can reparent following\n // siblings into (unclosed container tags mid-stream). Two origins:\n // a top-level mdast `html` node, or a block resolved via the\n // range-containment fallback (raw HTML embedded in another node, split\n // into extra hast siblings). Digest their subtree so the cache\n // invalidates when the swallowed extent changes; markdown-native blocks\n // skip the walk (their subtrees derive purely from their own source\n // range, already covered by `raw` + position).\n let hastDigest: string | undefined;\n let swallowedSource: string | undefined;\n let swallowedTaint = false;\n if (isRawHtmlBlock) {\n const d = computeHtmlBlockDigestWithExtent(el, source, mdastPos.end.offset);\n hastDigest = d.digest;\n swallowedSource = source.slice(mdastPos.end.offset, d.maxEnd);\n // Did the container swallow a reference or definition? The hast scan\n // above sees coordinated placeholders and standalone footnote marks,\n // but a STANDALONE link/image reference renders as a plain `<a href>` /\n // `<img src>` — indistinguishable from an inline link. Ask the mdast\n // instead: any taint node whose source offset lies inside the swallowed\n // extent. Without this a definition sitting BEFORE the container (thus\n // outside the digest's `[ownEnd, maxEnd)` hash) could change the\n // rendered href while every cache-key component stayed equal.\n //\n // What this buys, per mode: in STANDALONE mode the flag switches the\n // key to `globalCtx`, which encodes every definition's url/title —\n // strictly stronger. In COORDINATED mode the labels come from the\n // placeholder scan above (every resolvable reference becomes a\n // `cross-chunk-*` element), so this test is belt-and-braces there.\n // `[ownEnd, maxEnd)` mixes offset systems on purpose: `ownEnd` is the\n // MDAST end (where the opening tag stops), `maxEnd` the HAST end\n // (where parse5 closed the element, past everything it swallowed).\n // Half-open at the low end because anything below `ownEnd` is already\n // covered by `raw`, and `maxEnd` erring high only over-taints.\n // Binary search, not a scan: the linear form was O(taint nodes ×\n // raw-HTML blocks) on the streaming hot path, and the sortedness it\n // needs is the same one `findContainingMdast` already assumes\n // (2026-08-20 B5 — measured at 14% of buildBlocks on a document with\n // 200 definitions and 120 containers).\n const first = firstTaintAtOrAfter(mdastPos.end.offset);\n swallowedTaint = first < taintOffsets.length && taintOffsets[first] < d.maxEnd;\n }\n\n // A standalone link/image reference renders as a plain `<a href>` /\n // `<img src>`, so only the mdast range test above can see it — fold that\n // in now that the extent is known.\n if (swallowedTaint) hasReference = true;\n\n const info: BlockInfo = {\n raw: extractRaw(mdastNode, source),\n startOffset: mdastPos.start.offset,\n endOffset: mdastPos.end.offset,\n startLine: mdastPos.start.line,\n startColumn: mdastPos.start.column,\n hasReference,\n ...(hastDigest !== undefined ? { hastDigest } : {}),\n ...(swallowedSource !== undefined ? { swallowedSource } : {}),\n ...(swallowed?.hasFootnoteSection ? { containsFootnoteSection: true } : {}),\n ...(hasReference\n ? {\n taintLabels: {\n footnoteRefLabels,\n linkRefLabels,\n imageRefLabels,\n footnoteDefLabels,\n ...(footnoteRefLocalCtx !== undefined ? { footnoteRefLocalCtx } : {}),\n ...(swallowedDefCtx !== undefined ? { swallowedDefCtx } : {}),\n },\n }\n : {}),\n };\n blocks.push(info);\n blockHasts.push(el);\n // render key is keyed off the HAST element's source offset (not the mdast\n // node's), because multi-root raw HTML produces multiple hast siblings\n // that share one mdast `html` node — same `mdastPos.start.offset` for\n // both, which would collide as a render key. Hast positions are unique\n // per element in the rendered tree.\n plan.push({ kind: 'block', el, info, key: `block-${hastOffset}` });\n }\n\n return { plan, globalCtx, blocks, blockHasts, synthetic };\n}\n\n/**\n * Compute a per-block cache fingerprint from the registry slice this block\n * actually depends on (footnote/link/image refs and footnote defs). Two blocks\n * with the same fingerprint render byte-equal output; if any encoded value\n * differs, the block must re-render.\n *\n * Encoding format (deterministic, stable across versions):\n * `<clobberPrefix>|fn:<L>=<globalNumber>|fl:<[[L,prior,rank]…]>|lr:<L>=<url>|<title>|ir:<L>=<url>|fd:<L>=<canonical>/<refCount>`\n *\n * @param taintLabels - Per-block label dependency footprint (from BlockInfo.taintLabels).\n * @param registry - Shared cross-chunk registry.\n * @param thisChunkSym- The Symbol of the chunk this rendering belongs to (for canonical-vs-this comparison).\n * @param clobberPrefix - The documentId-derived id prefix; included so href/id changes invalidate.\n */\nexport function computeBlockFingerprint(\n taintLabels: NonNullable<BlockInfo['taintLabels']>,\n registry: Registry,\n thisChunkSym: symbol,\n clobberPrefix: string\n): string {\n const parts: string[] = [clobberPrefix];\n for (const label of taintLabels.footnoteRefLabels) {\n parts.push(`fn:${label}=${registry.globalNumber(label) ?? 'null'}`);\n }\n // Chunk-local occurrence/rank baked into the placeholders (P2-4) — see\n // buildBlocks; JSON-encoded there, so label bytes cannot collide with `|`.\n if (taintLabels.footnoteRefLocalCtx !== undefined) parts.push(`fl:${taintLabels.footnoteRefLocalCtx}`);\n if (taintLabels.swallowedDefCtx !== undefined) parts.push(`sd:${taintLabels.swallowedDefCtx}`);\n // url/title are JSON-encoded so a `|` inside them cannot collide with the\n // part separator (url `a|b` + no title ≡ url `a` + title `b` would be a\n // stale cache hit — v2.4.1 review).\n for (const label of taintLabels.linkRefLabels) {\n const def = registry.resolveLinkDef(label);\n parts.push(`lr:${label}=${def ? JSON.stringify([def.url, def.title ?? null]) : 'null'}`);\n }\n for (const label of taintLabels.imageRefLabels) {\n const def = registry.resolveLinkDef(label);\n parts.push(`ir:${label}=${def ? JSON.stringify([def.url, def.title ?? null]) : 'null'}`);\n }\n for (const label of taintLabels.footnoteDefLabels) {\n const isCanonical = registry.canonicalFootnoteFor(label) === thisChunkSym ? 1 : 0;\n parts.push(`fd:${label}=${isCanonical}/${registry.getRefsForLabel(label)}`);\n }\n return parts.join('|');\n}\n\nconst PLACEHOLDER_TAGS = new Set(['footnote-sup', 'cross-chunk-link', 'cross-chunk-image']);\n\n/**\n * What a raw-HTML container swallowed, read from the HAST subtree rather\n * than from the container's mdast node — that node is an empty `html` node\n * whose own range says nothing about the reparented siblings, so every\n * mdast-derived taint fact came out empty and the block fell back to the\n * un-fingerprinted cache key (2026-08-19 review r2 P2-8: an equal-length\n * edit BEFORE the container changed a `localOccurrence` baked inside it and\n * the stale subtree stayed cached).\n *\n * Reads what is actually baked into the cached render output: the placeholders'\n * `label` / `localOccurrence` (coordinated), the standalone footnote marks\n * (`a[data-footnote-ref]`), and whether the synthesized footnote section\n * landed inside (see {@link BlockInfo.containsFootnoteSection}).\n */\nfunction scanSwallowedSubtree(el: HastElement): {\n hasReference: boolean;\n refLabels: string[];\n linkLabels: string[];\n imageLabels: string[];\n local: [string, number, number][];\n bakedDefs: [string, string, string | null][];\n hasFootnoteSection: boolean;\n} {\n const refLabels: string[] = [];\n const linkLabels: string[] = [];\n const imageLabels: string[] = [];\n const local: [string, number, number][] = [];\n const bakedDefs: [string, string, string | null][] = [];\n let hasReference = false;\n let hasFootnoteSection = false;\n visit(el, 'element', (node: HastElement) => {\n if (isFootnoteSection(node)) {\n hasFootnoteSection = true;\n hasReference = true;\n return;\n }\n const props = (node.properties ?? {}) as Record<string, unknown>;\n if (PLACEHOLDER_TAGS.has(node.tagName)) {\n hasReference = true;\n const identifier = props.identifier ?? props.label;\n const label = identifier === undefined ? null : normalizeId(String(identifier));\n if (label === null) return;\n if (node.tagName === 'footnote-sup') {\n refLabels.push(label);\n // Chunk-local occurrence as BAKED into this placeholder — the exact\n // value a stale cache hit would keep serving.\n const occ = Number(props.localOccurrence);\n const num = Number(props.localNumber);\n local.push([label, Number.isFinite(occ) ? occ : -1, Number.isFinite(num) ? num : -1]);\n return;\n }\n if (node.tagName === 'cross-chunk-link') linkLabels.push(label);\n else imageLabels.push(label);\n // `resolveDef` falls back to the placeholder's BAKED `localUrl` /\n // `localTitle` when the registry has no canonical def — and the\n // fingerprint writes `lr:LABEL=null` in exactly that case, a constant,\n // so the rendered href could change while the key did not. Carry the\n // baked pair SEPARATELY (never folded into the label, which must stay\n // resolvable by `resolveLinkDef` so a canonical change is still\n // tracked) — the same argument that put `footnote-sup`'s occurrence in\n // the footprint; link/image were left asymmetric (oracle review, 2.5.0).\n if (typeof props.localUrl === 'string') {\n bakedDefs.push([label, props.localUrl, typeof props.localTitle === 'string' ? props.localTitle : null]);\n }\n return;\n }\n // Standalone mode bakes footnote numbering into plain marks; their ctx\n // is the whole-document globalCtx, so the flag alone is enough.\n if (node.tagName === 'a' && props.dataFootnoteRef !== undefined) hasReference = true;\n });\n return { hasReference, refLabels, linkLabels, imageLabels, local, bakedDefs, hasFootnoteSection };\n}\n","import type { Root as MdastRoot, RootContent as MdastNode } from 'mdast';\nimport type { Root as HastRoot } from 'hast';\nimport { visit } from 'unist-util-visit';\nimport { buildBlocks, type BuildBlocksOptions, type BuildBlocksResult, type RenderItem } from './blockPlan';\n\nexport type BlockPlanner = (\n mdast: MdastRoot,\n hast: HastRoot,\n source: string,\n options?: BuildBlocksOptions\n) => BuildBlocksResult;\n\nconst UNPAIRED_TYPES = new Set(['html', 'definition', 'footnoteDefinition']);\n\n/** Reuse the engine's retained prefix plans, including reference blocks.\n * Reference prefixes require full-document context when planning the tail.\n * Raw HTML and definition regions retain complete planning: their ownership\n * need not be one-to-one. Identity checks, not source-text\n * guesses, establish which trees the incremental engine actually retained.\n * Top-level traversal remains O(blocks); reference context still visits the\n * full mdast when needed, while per-block HAST planning is tail-only. */\nexport function createBlockPlanner(): BlockPlanner {\n let previous:\n | {\n mdast: MdastRoot;\n hast: HastRoot;\n source: string;\n result: BuildBlocksResult;\n phantoms: BuildBlocksOptions['phantomFootnoteLabels'];\n }\n | undefined;\n const eligible = new WeakMap<MdastNode, boolean>();\n const isEligible = (node: MdastNode) => {\n let result = eligible.get(node);\n if (result === undefined) {\n result = true;\n visit(node, (child) => {\n if (UNPAIRED_TYPES.has(child.type)) result = false;\n });\n eligible.set(node, result);\n }\n return result;\n };\n return (mdast: MdastRoot, hast: HastRoot, source: string, options: BuildBlocksOptions = {}): BuildBlocksResult => {\n let m = 0;\n let h = 0;\n let reusedContext = false;\n if (previous && source.startsWith(previous.source) && options.phantomFootnoteLabels === previous.phantoms) {\n const prevHast = previous.hast.children;\n const prevPlan = previous.result.plan;\n /** Identity-retained whitespace text between two block outputs. */\n const isRetainedGap = (i: number): boolean => {\n const gap = hast.children[i];\n return gap === prevHast[i] && gap.type === 'text' && !/\\S/.test(gap.value);\n };\n while (m < mdast.children.length) {\n const node = mdast.children[m];\n const el = hast.children[h];\n const item = prevPlan[h];\n if (\n node !== previous.mdast.children[m] ||\n el !== prevHast[h] ||\n !el ||\n el.type !== 'element' ||\n !item ||\n item.kind !== 'block' ||\n item.info.startOffset !== node.position?.start.offset ||\n !isEligible(node)\n )\n break;\n // One mdast node can own several top-level hast siblings: an\n // image-only paragraph with two images on one line is unwrapped to\n // `img, \" \", img`, and every sibling's block info carries the\n // paragraph's start offset. Consume all of them with the node —\n // leaving one behind put its offset inside the reused prefix range,\n // where the tail's mdast slice has no counterpart (the dev build\n // threw, production planned an uncached inline item and remounted\n // the image on every frame). A sibling that is not identity-retained\n // means the node's output was not carried over whole: give the node\n // back and stop the prefix here.\n const startOffset = item.info.startOffset;\n let next = h + 1;\n let complete = true;\n for (;;) {\n let probe = next;\n while (probe < hast.children.length && isRetainedGap(probe)) probe++;\n const sibling = prevPlan[probe];\n if (!sibling || sibling.kind !== 'block' || sibling.info.startOffset !== startOffset) break;\n if (hast.children[probe] !== prevHast[probe]) {\n complete = false;\n break;\n }\n next = probe + 1;\n }\n if (!complete) break;\n reusedContext ||= item.info.hasReference;\n m++;\n h = next;\n while (h < hast.children.length && isRetainedGap(h)) h++;\n }\n // The tail is planned against `mdast.children.slice(m)`, so every tail\n // block must start at or past the reused prefix's end. An output node\n // that still points inside the prefix (a sibling shape the loop above\n // does not model) cannot be attributed from the tail slice; plan the\n // whole document instead, so the dev-only \"no mdast counterpart\"\n // invariant keeps firing only for positions synthesized outside the\n // source.\n if (m > 0) {\n const prefixEnd = mdast.children[m - 1].position?.end.offset ?? 0;\n for (let i = h; i < hast.children.length; i++) {\n const child = hast.children[i];\n const at = child.type === 'element' ? child.position?.start.offset : undefined;\n if (at !== undefined && at < prefixEnd) {\n m = 0;\n h = 0;\n reusedContext = false;\n break;\n }\n }\n }\n }\n let result: BuildBlocksResult;\n if (m > 0 && previous) {\n const tail = buildBlocks(\n { ...mdast, children: mdast.children.slice(m) },\n { ...hast, children: hast.children.slice(h) },\n source,\n reusedContext ? { ...options, contextMdast: options.contextMdast ?? mdast } : options\n );\n const prefix = previous.result.plan.slice(0, h);\n const prefixBlocks = prefix.filter(\n (item): item is Extract<RenderItem, { kind: 'block' }> => item.kind === 'block'\n );\n result = {\n ...tail,\n plan: [\n ...prefix,\n ...tail.plan.map((item) =>\n /^inline-i\\d+$/.test(item.key) ? { ...item, key: `inline-i${Number(item.key.slice(8)) + h}` } : item\n ),\n ],\n blocks: [...prefixBlocks.map((item) => item.info), ...tail.blocks],\n blockHasts: [...prefixBlocks.map((item) => item.el), ...tail.blockHasts],\n };\n } else result = buildBlocks(mdast, hast, source, options);\n previous = { mdast, hast, source, result, phantoms: options.phantomFootnoteLabels };\n return result;\n };\n}\n","import type { Root as MdastRoot } from 'mdast';\nimport type { Root as HastRoot, ElementContent as HastElementContent } from 'hast';\nimport { extractContributions, extractDefBodiesFromHast, footnoteSafeId, type ChunkData } from '@ai-markdown/engine';\n\n/** Minimal write capability; registration and cleanup remain with the adapter. */\nexport interface ContributionRegistry {\n contributeChunkData(symbol: symbol, data: ChunkData): void;\n}\n\nexport interface ContributionOptions {\n pipeline: { mdast: MdastRoot; hast: HastRoot };\n ownLabels: { footnoteLabels: Set<string>; linkLabels: Set<string> };\n registry: ContributionRegistry | null;\n targetPhantoms: { missingFootnotes: Set<string>; missingLinks: Set<string> };\n sym: symbol | null;\n clobberPrefix: string;\n /** Stable identity tuple of parse inputs. Source equality alone cannot\n * validate definition bodies after a plugin or schema change. */\n chain: readonly unknown[];\n}\n\nexport interface ContributionSession {\n commit(options: ContributionOptions): void;\n}\n\n/** Create one contribution publisher per mounted chunk.\n * Call commit only in the host's commit/effect phase, after registration.\n * Constructing a session does not register or publish anything. A discarded\n * render must never call commit. The host owns registration and cleanup.\n */\nexport function createContributionSession(): ContributionSession {\n let lastContribution: {\n registry: ContributionRegistry;\n symbol: symbol;\n fp: string;\n chain: readonly unknown[];\n } | null = null;\n return {\n commit({ pipeline, ownLabels, registry, targetPhantoms, sym, clobberPrefix, chain }: ContributionOptions): void {\n if (!registry || !sym) return;\n const refs: ChunkData['refs'] = [];\n // Collect def metadata first so the fingerprint compares only cheap\n // fields. bodyHast is sourced from the post-pipeline hast (not from a\n // bare mdast→hast walk) so def bodies inside the cross-chunk aggregate\n // render with full plugin output (math, raw HTML, defLists, …).\n const defMeta = new Map<string, { identifier: string; sourceIdentifier: string; contentSource: string }>();\n const linkDefs = new Map<string, { identifier: string; url: string; title?: string }>();\n // Link-definition URLs enter the registry RAW: the render-time\n // `resolveCrossChunkReference` gate in the placeholders is the single point\n // of enforcement (schema + hash rebasing + urlTransform, per attribute\n // key). A contribute-time pre-pass used to collapse a blocked URL to ''\n // — indistinguishable from a legal empty destination — and applied a\n // rewriting urlTransform twice (v2.4.2 review P1-4).\n for (const node of extractContributions(pipeline.mdast, {\n phantomFootnoteLabels: targetPhantoms.missingFootnotes,\n })) {\n if (node.kind === 'ref') {\n // `nestedIn` marks a footnote ref inside a definition body: it is\n // numbered after the flow refs but never counted as an occurrence.\n refs.push({\n label: node.label,\n kind: node.refKind,\n referenceType: node.referenceType,\n ...(node.nestedIn !== undefined ? { nestedIn: node.nestedIn } : {}),\n });\n } else if (node.kind === 'fnDef') {\n defMeta.set(node.label, {\n identifier: node.label,\n sourceIdentifier: node.sourceIdentifier,\n contentSource: node.content,\n });\n } else if (node.kind === 'linkDef') {\n linkDefs.set(node.label, { identifier: node.label, url: node.url, title: node.title });\n }\n }\n const fp = JSON.stringify({\n r: refs,\n d: Array.from(defMeta.entries()).map(([k, v]) => [k, v.sourceIdentifier, v.contentSource]),\n l: Array.from(linkDefs.entries()).map(([k, v]) => [k, v.url, v.title ?? '']),\n ofn: Array.from(ownLabels.footnoteLabels).sort(),\n ol: Array.from(ownLabels.linkLabels).sort(),\n // Include targetPhantoms in the fingerprint: a phantom→resolved\n // transition (another chunk publishes a def for a label this chunk\n // references inside one of its OWN def bodies) changes the rendered\n // hast — the `<cross-chunk-link>` / `<cross-chunk-image>` placeholder\n // disappears and a real `<a>` / `<img>` takes its place — without\n // touching this chunk's refs / defMeta / linkDefs / ownLabels. Without\n // including the phantom snapshot in the fingerprint, the fp check\n // would short-circuit and the registry would keep stale bodyHast\n // forever, leaving the aggregate footer rendering the placeholder\n // long after the label was resolved.\n tpfn: Array.from(targetPhantoms.missingFootnotes).sort(),\n tpl: Array.from(targetPhantoms.missingLinks).sort(),\n });\n const last = lastContribution;\n if (\n last?.registry === registry &&\n last.symbol === sym &&\n last.fp === fp &&\n last.chain.length === chain.length &&\n last.chain.every((dep, i) => dep === chain[i])\n ) {\n return;\n }\n // Fingerprint changed → harvest bodyHast from the post-pipeline hast\n // and publish. Missing entries are defensive: after allocation,\n // preserveForBodyHarvest keeps real local defs in the synthetic footer\n // even when visible orphan rendering is disabled.\n // Harvested bodies are keyed by the encoded `<li id>` fragment, which\n // is `footnoteSafeId(sourceIdentifier)` for the matching definition.\n // Both sides derive the key from the same identifier through the same\n // encoder, so a label that contains a valid percent-escape (`[^a%41]`,\n // minted verbatim as `fn-a%41`) matches too; decoding the id instead\n // would turn it into `aA` and leave the aggregate `<li>` empty.\n const bodiesBySafeId = extractDefBodiesFromHast(pipeline.hast, clobberPrefix);\n const defs = new Map<\n string,\n { identifier: string; sourceIdentifier: string; contentSource: string; bodyHast: HastElementContent[] }\n >();\n for (const [label, meta] of defMeta) {\n defs.set(label, {\n identifier: meta.identifier,\n sourceIdentifier: meta.sourceIdentifier,\n contentSource: meta.contentSource,\n bodyHast: (bodiesBySafeId.get(footnoteSafeId(meta.sourceIdentifier)) ?? []) as HastElementContent[],\n });\n }\n lastContribution = { registry, symbol: sym, fp, chain };\n registry.contributeChunkData(sym, {\n refs,\n defs,\n linkDefs,\n ownFootnoteLabels: ownLabels.footnoteLabels,\n ownLinkLabels: ownLabels.linkLabels,\n });\n },\n };\n}\n","/**\n * Turn-taking coordinator for smooth-streaming chunks that share a\n * `documentId` under `<AIMarkdownDocuments>`.\n *\n * Each `useDocumentSmoothStream` chunk registers here in mount order and\n * reports a sticky `done` (source stopped AND reveal drained). A chunk that\n * mounted with empty content stays gated until every REGISTERED chunk\n * earlier in the order is done — so the chunks read as one typewriter:\n * chunk N reveals completely, then chunk N+1 starts.\n *\n * The structure deliberately mirrors `documentRegistry`'s proven patterns\n * (chunkId-keyed refcount with microtask-deferred reclaim for Strict Mode,\n * monotonic version + microtask-coalesced subscriber fanout, `onEmpty`\n * container eviction) without touching the registry itself: the def\n * registry and this coordinator share only the `documentId` scope. Chunks\n * that never register (non-smooth chunks, opted-out chunks) simply do not\n * exist here and can never block a successor; an unmounting chunk leaves\n * the order, so a virtualized-away predecessor releases its successors by\n * construction.\n *\n * `lastProgressAt` is the reveal-progress heartbeat behind the dev-only\n * stuck-flag warning. Writes to it intentionally do NOT bump `version` and\n * do NOT fan out — stamping happens once per reveal frame, and a fanout to\n * every sibling chunk per frame would be a performance bug. Nothing\n * subscribes to it; the pending chunk's warning timer reads it on demand.\n *\n * @module components/smoothStream/coordinator\n */\n\nexport interface SmoothCoordinator {\n /** Chunk mount-order chunkId list. **Read-only** — mutation corrupts the\n * release predicate for every successor. */\n readonly order: readonly string[];\n /** Chunk IDs that have reported done. Sticky: entries are only removed\n * when the chunk unregisters. **Read-only.** */\n readonly done: ReadonlySet<string>;\n /** Reveal-progress heartbeat, ms timestamps from the reporting chunk's\n * clock. Written WITHOUT a version bump (see module doc). */\n readonly lastProgressAt: ReadonlyMap<string, number>;\n /** Monotonic counter bumped by every gating-relevant mutation\n * (register / unregister / done transition). */\n readonly version: number;\n\n subscribe(cb: () => void): () => void;\n\n /** Allocate (or refcount-reuse, for Strict Mode remounts) this chunk's\n * slot at the current end of the mount order. */\n register(chunkId: string): void;\n /** Refcount decrement with microtask-deferred reclaim; the last release\n * removes the chunk from the order (waking successors) and, when the\n * coordinator empties, fires the container's `onEmpty`. */\n release(chunkId: string): void;\n /** Sticky, idempotent: the first call per chunk bumps the version; the\n * transition is never retracted while the chunk stays mounted (a\n * predecessor that un-finishes must not re-gate successors). */\n markDone(chunkId: string): void;\n /** Heartbeat stamp — no version bump, no fanout. */\n stampProgress(chunkId: string, at: number): void;\n /** True when every registered chunk BEFORE `chunkId` in the order has\n * reported done. Unregistered predecessors don't exist here, so they\n * never block. A `chunkId` that is not (yet) registered itself is\n * treated as sitting AFTER every registered chunk — a pre-registration\n * query must not release a chunk whose predecessors are still\n * revealing. */\n isReleased(chunkId: string): boolean;\n /** The earliest not-done registered chunk before `chunkId`, or null.\n * Read by the dev-only stuck-flag warning timer. */\n earliestBlockerOf(chunkId: string): string | null;\n}\n\n/** Widened internal view (parallel to `RegistryInternal`): implementation-\n * private fields, exported for tests but not from the package barrel. */\nexport interface SmoothCoordinatorInternal extends SmoothCoordinator {\n _refcounts: Map<string, number>;\n _subscribers: Set<() => void>;\n _notifyScheduled: boolean;\n _notify(): void;\n}\n\n/**\n * Construct a coordinator. `onEmpty` fires synchronously from inside the\n * deferred-release microtask when the last chunk just left, so the\n * container can evict its `documentId → coordinator` map entry while the\n * state is quiescent (same contract as the registry's `onEmpty`).\n */\nexport function createSmoothCoordinator(onEmpty?: () => void): SmoothCoordinatorInternal {\n const order: string[] = [];\n const done = new Set<string>();\n const lastProgressAt = new Map<string, number>();\n\n const coord: SmoothCoordinatorInternal = {\n order,\n done,\n lastProgressAt,\n version: 0,\n _refcounts: new Map(),\n _subscribers: new Set(),\n _notifyScheduled: false,\n\n register(chunkId: string): void {\n const count = this._refcounts.get(chunkId) ?? 0;\n this._refcounts.set(chunkId, count + 1);\n if (count === 0 && !order.includes(chunkId)) {\n order.push(chunkId);\n this._notify();\n }\n },\n\n release(chunkId: string): void {\n const count = this._refcounts.get(chunkId);\n if (count === undefined) return;\n if (count > 1) {\n this._refcounts.set(chunkId, count - 1);\n return;\n }\n this._refcounts.set(chunkId, 0);\n queueMicrotask(() => {\n if (this._refcounts.get(chunkId) !== 0) return; // Strict Mode revived it\n this._refcounts.delete(chunkId);\n const idx = order.indexOf(chunkId);\n if (idx !== -1) order.splice(idx, 1);\n done.delete(chunkId);\n lastProgressAt.delete(chunkId);\n // Successors gated behind this chunk may now be releasable.\n this._notify();\n if (order.length === 0 && onEmpty) onEmpty();\n });\n },\n\n markDone(chunkId: string): void {\n if (done.has(chunkId)) return; // idempotent — repeat reports don't bump version\n done.add(chunkId);\n this._notify();\n },\n\n stampProgress(chunkId: string, at: number): void {\n lastProgressAt.set(chunkId, at);\n },\n\n isReleased(chunkId: string): boolean {\n for (const id of order) {\n if (id === chunkId) return true;\n if (!done.has(id)) return false;\n }\n // Not registered (yet): treated as after every registered chunk, so\n // reaching here means all of them are done.\n return true;\n },\n\n earliestBlockerOf(chunkId: string): string | null {\n for (const id of order) {\n if (id === chunkId) return null;\n if (!done.has(id)) return id;\n }\n return null;\n },\n\n subscribe(cb: () => void): () => void {\n this._subscribers.add(cb);\n return () => {\n this._subscribers.delete(cb);\n };\n },\n\n _notify(): void {\n (this as { version: number }).version++;\n if (this._notifyScheduled) return;\n this._notifyScheduled = true;\n queueMicrotask(() => {\n this._notifyScheduled = false;\n for (const cb of [...this._subscribers]) cb();\n });\n },\n };\n return coord;\n}\n\n/** Outcome of one stuck-flag warning timer firing (dev-only diagnostics). */\nexport type GateWarnVerdict = 'warn' | 'rearm' | 'clear';\n\n/**\n * The warn / re-arm / clear decision behind the dev-only stuck-flag\n * warning, extracted as a pure function so the judgment is unit-testable\n * in node — the `setTimeout` wiring in `useDocumentSmoothStream` reduces\n * to \"call this when the timer fires\".\n *\n * - `'clear'`: no blocker left — the release is propagating; stop.\n * - `'rearm'`: the earliest blocker showed reveal progress within the\n * threshold — a slow model still emitting, not a stuck flag; check\n * again later.\n * - `'warn'`: the blocker has made no visible progress for a full\n * threshold while this chunk stayed gated — the predecessor's\n * `streaming` flag is likely stuck.\n */\nexport function evaluateGateWarn(\n coordinator: SmoothCoordinator,\n chunkId: string,\n nowMs: number,\n thresholdMs: number\n): GateWarnVerdict {\n const blocker = coordinator.earliestBlockerOf(chunkId);\n if (!blocker) return 'clear';\n const progressedAt = coordinator.lastProgressAt.get(blocker) ?? 0;\n return nowMs - progressedAt < thresholdMs ? 'rearm' : 'warn';\n}\n","import { createSmoothCoordinator as createInternalCoordinator, type SmoothCoordinator } from './smoothCoordinator';\n\n/** Create one turn-taking scope.\n * Why: sibling chunks coordinate reveal without a framework or DOM dependency.\n * Recommended pattern: register after mount, report sticky completion, release on unmount.\n * Footguns: registration order controls reveal; completion cannot be retracted.\n */\nexport function createSmoothCoordinator(onEmpty?: () => void): SmoothCoordinator {\n return createInternalCoordinator(onEmpty);\n}\n","/**\n * Source-tail classification for the streaming cursor.\n *\n * The cursor's DOM walk cannot know whether the source tail is in the body\n * or inside a footnote definition — mdast-util-to-hast relocates every\n * definition into the synthetic end-of-document footer, decoupling the\n * footer's DOM position from source position. This module answers the\n * question at the mdast level, where micromark has already made every\n * grammar decision (lazy continuation lines, container nesting, blank-line\n * attachment, label escapes): the renderer derives a signal per frame from\n * the tree it is about to render and stamps it on a DOM marker the cursor\n * shell reads in the same commit.\n *\n * Deliberately NOT a text scan of the trailing lines: a hand-rolled\n * back-walk would be a third parallel grammar in the repo and provably\n * mis-classifies lazy continuations (`[^1]: line\\nline2` keeps line2 in\n * the definition with zero indentation), resurfacing the exact bug this\n * exists to fix.\n *\n * @module components/streamingCursor/tailSignal\n */\n\nimport type { Parent, Root, RootContent } from 'mdast';\n\nexport type TailSignal =\n /** The source tail is inside a footnote definition — its text renders in\n * the footnote footer's `<li>` for `identifier` (mdast's case-folded\n * form), not at the body tail. */\n | { kind: 'footnote-def'; identifier: string }\n /** The source tail is inside a link-reference definition — it renders\n * nothing at all; the cursor should hide rather than point at stale\n * body text. */\n | { kind: 'invisible-def' }\n /** The source tail is ordinary body content — the DOM-tail heuristic is\n * correct as-is. */\n | null;\n\n/** Node types the descent passes THROUGH looking for the tail definition.\n * Definitions legally nest inside all of these (`> [^a]: x`,\n * `- [^a]: x`, and a def inside another def's body), and their rendered\n * text still relocates to the footer — so the descent is required for\n * correctness, not an optimization. Nothing else can contain a def. */\nconst DESCEND_THROUGH = new Set(['blockquote', 'list', 'listItem', 'footnoteDefinition']);\n\n/**\n * Classify the source tail of `mdast`.\n *\n * @param mdast - The tree the renderer is about to render (post-pipeline\n * parse output; the incremental path's spliced tree carries correct\n * positions on real nodes).\n * @param contentLength - Length of the PREPROCESSED content string the\n * renderer parsed (not the caller's raw prop). Phantom definitions are\n * injected as an appended suffix before parsing, so every node whose\n * start offset sits at or past the real content's end is a phantom — the\n * scan walks backwards past ALL of them (a real trailing def plus\n * several phantoms is a normal shape) and classifies the first real\n * node.\n */\nexport function deriveTailSignal(mdast: Root, contentLength: number): TailSignal {\n const children = mdast.children;\n let index = children.length - 1;\n while (index >= 0) {\n const offset = children[index].position?.start?.offset;\n if (offset !== undefined && offset >= contentLength) {\n index--;\n continue;\n }\n break;\n }\n if (index < 0) return null;\n\n let node: RootContent = children[index];\n let deepestFootnoteDef: { identifier: string } | null = null;\n for (;;) {\n if (node.type === 'footnoteDefinition') deepestFootnoteDef = node;\n if (node.type === 'definition') {\n // A link-reference definition renders nothing wherever it sits —\n // including as the last child of a footnote definition's body.\n return { kind: 'invisible-def' };\n }\n if (!DESCEND_THROUGH.has(node.type)) break;\n const kids = (node as Parent).children;\n if (!kids || kids.length === 0) break;\n node = kids[kids.length - 1] as RootContent;\n }\n // The DEEPEST definition on the last-child chain is where text is\n // streaming (a def nested in another def's body renders in ITS OWN li).\n return deepestFootnoteDef ? { kind: 'footnote-def', identifier: deepestFootnoteDef.identifier } : null;\n}\n","/** Assemble document footnotes as HAST without framework nodes or DOM access. */\nimport type { Element as HastElement, ElementContent as HastElementContent } from 'hast';\nimport { footnoteSafeId, lastMeaningfulIdx, type Registry } from '@ai-markdown/engine';\nimport { cloneHastForRender } from './cloneHastForRender';\n\ninterface OrderedDef {\n /** Uppercase-normalized label, used to query `registry.getRefsForLabel`. */\n normalizedLabel: string;\n /** mdast's case-folded identifier — used in HTML ids so the aggregate\n * footer's `<li id>` matches the inline sup's anchor href exactly. */\n sourceIdentifier: string;\n bodyHast: HastElementContent[];\n n: number | null;\n withBackref: boolean;\n}\n\n/**\n * Build a backref anchor for a specific occurrence of a footnote ref.\n * Matches mdast-util-to-hast's default `defaultFootnoteBackContent`:\n * - Anchor content is always the text `↩`.\n * - When occurrence > 1, a child `<sup>${N}</sup>` is appended so the\n * digit renders as a superscript (visually `↩²`, `↩³`, …) instead of\n * a flat string like `↩2`.\n * - The 1st occurrence href is bare `#fnref-${id}`; the N-th (N > 1)\n * href is `#fnref-${id}-${N}` to match `FootnoteSupNumber`'s\n * suffix-disambiguated id on the inline sup side.\n */\nfunction buildBackref(href: string, occurrence: number, globalNumber: number): HastElement {\n const children: HastElement['children'] = [{ type: 'text', value: '↩' }];\n if (occurrence > 1) {\n children.push({\n type: 'element',\n tagName: 'sup',\n properties: {},\n children: [{ type: 'text', value: String(occurrence) }],\n });\n }\n const ariaLabel =\n occurrence === 1 ? `Back to reference ${globalNumber}` : `Back to reference ${globalNumber}-${occurrence}`;\n return {\n type: 'element',\n tagName: 'a',\n properties: {\n href,\n dataFootnoteBackref: '',\n className: ['data-footnote-backref'],\n ariaLabel,\n },\n children,\n };\n}\n\nexport function buildAggregateTree(\n registry: Registry,\n clobberPrefix: string,\n preserveOrphanReferences = false\n): HastElement | null {\n // Walk chunkOrder; collect every FOOTNOTE label that is referenced\n // somewhere — in flow text, or inside another footnote's body (a nested\n // ref, `nestedIn` set) — and order the entries by `registry.globalNumber`,\n // the same selector the inline `<sup>` marks read. The registry numbers\n // nested-only footnotes after every flow reference, in the order the\n // bodies render, which is where a standalone footer lists them; walking\n // refs in source order alone would put such a footnote at the position of\n // the body that mentions it. Link/image refs share the `refs` array but\n // belong to a disjoint namespace — they must be skipped here, otherwise a\n // link-ref to a label sharing text with a footnote-def would cause a\n // duplicate `<li>` in the aggregate footer.\n const seen = new Set<string>();\n const ordered: OrderedDef[] = [];\n for (const sym of registry.chunkOrder) {\n const data = registry.chunkData.get(sym);\n if (!data) continue;\n for (const ref of data.refs) {\n if (ref.kind !== 'footnote') continue;\n if (seen.has(ref.label)) continue;\n seen.add(ref.label);\n const canonicalSym = registry.canonicalFootnoteFor(ref.label);\n if (!canonicalSym) continue;\n const def = registry.chunkData.get(canonicalSym)?.defs.get(ref.label);\n if (!def) continue;\n const n = registry.globalNumber(ref.label);\n if (n === null) continue;\n // Fall back to the uppercase-normalized label when sourceIdentifier is\n // absent (older fixtures); production data always carries the field.\n const sourceIdentifier = def.sourceIdentifier ?? ref.label;\n ordered.push({\n normalizedLabel: ref.label,\n sourceIdentifier,\n bodyHast: def.bodyHast ?? [],\n n,\n withBackref: true,\n });\n }\n }\n ordered.sort((a, b) => (a.n ?? 0) - (b.n ?? 0));\n\n if (preserveOrphanReferences) {\n for (const sym of registry.chunkOrder) {\n const data = registry.chunkData.get(sym);\n if (!data) continue;\n for (const [label, def] of data.defs) {\n if (seen.has(label)) continue;\n if (registry.canonicalFootnoteFor(label) !== sym) continue;\n seen.add(label);\n ordered.push({\n normalizedLabel: label,\n sourceIdentifier: def.sourceIdentifier ?? label,\n bodyHast: def.bodyHast ?? [],\n n: null,\n withBackref: false,\n });\n }\n }\n }\n\n if (ordered.length === 0) return null;\n\n const liElements: HastElement[] = ordered.map(({ normalizedLabel, sourceIdentifier, bodyHast, n, withBackref }) => {\n // Same id encoding as mdast-util-to-hast's footer / the marks (percent-\n // encoded non-ASCII etc.); the body harvest keys by this same fragment\n // and the cursor anchor decodes it through `sourceIdFromFootnoteLiId`.\n const safeId = footnoteSafeId(sourceIdentifier);\n // Assembly-time clone: the backref logic below pushes anchors into these\n // children, which must never mutate the registry-held `bodyHast`.\n const liChildren = bodyHast.map((c) => cloneHastForRender(c));\n if (withBackref) {\n // Emit one backref anchor per global occurrence of this label. The\n // first href uses the bare `fnref-${id}` (matches FootnoteSupNumber's\n // unsuffixed first ref); the Nth (N > 1) uses `fnref-${id}-${N}`\n // (matches FootnoteSupNumber's `-N` suffix on subsequent refs).\n //\n // A single space text node precedes each anchor so adjacent backrefs\n // don't run together visually (and so the first backref doesn't butt\n // up against the def body's trailing text). This mirrors what\n // mdast-util-to-hast's default footer naturally produces: the trailing\n // newline in the def's source becomes a text \" \" between body and\n // backref, plus an explicit space separator between consecutive\n // backrefs (see mdast-util-to-hast `lib/footer.js`).\n const totalRefs = registry.getRefsForLabel(normalizedLabel);\n // Match mdast-util-to-hast's append rule: if the TAIL of the def\n // body (skipping wrap-emitted `\\n`) is `<p>`, push backrefs into\n // it; otherwise push them at the end of the `<li>` directly. The\n // previous logic searched for the last `<p>` anywhere in the body,\n // which mis-placed the backref BEFORE a trailing `<pre>` /\n // `<blockquote>` / etc. for multi-block defs.\n const tailIdx = lastMeaningfulIdx(liChildren);\n const tail = tailIdx !== -1 ? liChildren[tailIdx] : null;\n const dest =\n tail && tail.type === 'element' && (tail as HastElement).tagName === 'p' ? (tail as HastElement) : null;\n const appended: HastElementContent[] = [];\n for (let i = 1; i <= Math.max(totalRefs, 1); i++) {\n appended.push({ type: 'text', value: ' ' });\n const href = i === 1 ? `#${clobberPrefix}fnref-${safeId}` : `#${clobberPrefix}fnref-${safeId}-${i}`;\n appended.push(buildBackref(href, i, n ?? 0));\n }\n if (dest) {\n dest.children = [...dest.children, ...appended];\n } else if (tailIdx !== -1) {\n // Splice after the meaningful tail so any subsequent trailing\n // whitespace (`\\n` from state.wrap) survives at the end of the\n // <li>, matching mdast-util-to-hast's post-wrap shape:\n // [..., <tail-block>, ...backrefs, \"\\n\"]\n liChildren.splice(tailIdx + 1, 0, ...appended);\n } else {\n liChildren.push(...appended);\n }\n }\n return {\n type: 'element',\n tagName: 'li',\n properties: {\n id: `${clobberPrefix}fn-${safeId}`,\n // Stringified: @types/hast (≥3.0.5) types li's `value` as string;\n // React serializes value={3} and value=\"3\" to identical markup.\n ...(n !== null ? { value: String(n) } : {}),\n },\n children: liChildren,\n };\n });\n\n return {\n type: 'element',\n tagName: 'section',\n properties: {\n className: ['footnotes'],\n dataFootnotes: true,\n // a11y: name the section landmark so screen readers still announce it\n // even though the visible \"Footnotes\" h2 is omitted in coordinated mode.\n ariaLabel: 'Footnotes',\n },\n children: [\n // Conventional separator above the footnote list. The default\n // mdast-util-to-hast footer doesn't emit one (GitHub renders the\n // separation via CSS `border-top` on `.footnotes`); we render a literal\n // `<hr>` so the visual break is preserved without sanitize-friendly CSS.\n {\n type: 'element',\n tagName: 'hr',\n properties: {},\n children: [],\n },\n {\n type: 'element',\n tagName: 'ol',\n properties: {},\n children: liElements,\n },\n ],\n };\n}\n","/**\n * Structural clone of a hast subtree for render-time use.\n *\n * Copies exactly what the render-time mutators touch — every node object,\n * its `children` array (the allow/disallow filters splice it), and its\n * `properties` object (`urlTransform` overwrites `href`/`src`) — while\n * SHARING `position`, arbitrary nested plugin data, string values, and individual property values\n * by reference. This is both cheaper than a JSON round-trip (no string\n * intermediate; `position` objects, typically over half of a serialized\n * hast, are never materialized) and exact for any tree the pipeline\n * produces (no lossy JSON semantics to reason about).\n *\n * @module components/cloneHastForRender\n */\n\nimport type { Nodes as HastNodes } from 'hast';\n\n/** See module docs. Safe for any hast node union member. */\nexport function cloneHastForRender<T extends HastNodes>(node: T): T {\n const copy = { ...node } as T;\n const withChildren = copy as { children?: HastNodes[] };\n if (Array.isArray(withChildren.children)) {\n withChildren.children = withChildren.children.map((child) => cloneHastForRender(child));\n }\n const withProperties = copy as { properties?: Record<string, unknown> };\n if (withProperties.properties) {\n withProperties.properties = { ...withProperties.properties };\n }\n // buildTransform records original URL values in data.originalUrls. Both\n // containers must belong to this render even when plugins supplied data.\n if (copy.data) {\n copy.data = { ...copy.data };\n const data = copy.data as { originalUrls?: Record<string, unknown> };\n if (data.originalUrls) data.originalUrls = { ...data.originalUrls };\n }\n return copy;\n}\n","/** Per-consumer parse state and full/incremental fallback policy.\n * The host decides whether a next frame is expected (false for SSR), owns\n * this session, and resets it when render policy invalidates retained trees.\n * Parsing does not register chunks or publish document contributions.\n * Fallback order: incremental → full pipeline → plain-text frame, the last\n * step ONLY for the engine's raw-depth signal (see plainTextTrees); any\n * other full-pipeline throw, a consumer plugin's included, escapes `parse`.\n */\nimport {\n EngineRawHtmlDepthError,\n advanceIncrementalParse,\n buildPhantomSuffix,\n phantomSuffixCloser,\n parseStage,\n transformStage,\n type IncrementalParseState,\n type PipelineOptions,\n type AdvanceOptions,\n} from '@ai-markdown/engine';\n\nimport type { Root as MdastRoot } from 'mdast';\nimport type { Root as HastRoot } from 'hast';\n\nexport interface PipelineTrees {\n mdast: MdastRoot;\n hast: HastRoot;\n}\nexport interface PipelineSession {\n /** Drop retained incremental state. Does not dispose host subscriptions. */\n reset(): void;\n /** Read-only borrowed trees: clone before destructive rendering. */\n parse(options: PipelineFrameOptions): PipelineTrees;\n}\n\ntype RemarkRehypeOptions = NonNullable<PipelineOptions['remarkRehypeOptions']>;\nexport interface PipelineFrameOptions {\n content: string;\n targetPhantoms: { missingFootnotes: Set<string>; missingLinks: Set<string> };\n remarkPlugins: PipelineOptions['remarkPlugins'];\n rehypePlugins: PipelineOptions['rehypePlugins'];\n remarkRehypeOptions: PipelineOptions['remarkRehypeOptions'];\n handlers?: RemarkRehypeOptions['handlers'];\n preserveForBodyHarvest: boolean;\n documentId: string;\n provenance: string;\n /** False for a one-shot server render; no browser-global probe is needed. */\n incrementalParse: boolean;\n defListEnabled: boolean;\n /** Whether `remarkPlugins` parses GFM task-list items. Default `false`\n * (the boundary scanner keeps every `[x]` as reference taint). The\n * adapters pass `true` because they build the chain with\n * `buildCoreRemarkPlugins`, which always includes remark-gfm. A box\n * whose paragraph a `$$` opener closed is released only when\n * `mathFlow` is `true` as well. */\n gfmTaskListItems?: boolean;\n /** Whether `remarkPlugins` parses `$$` flow math (remark-math). Passed\n * through to the engine in all three states: `true` declares it (a\n * `$$` region is verbatim math), `false` declares its absence (`$$`\n * lines are paragraph text), and omitted leaves it unknown — the\n * scanner then takes the union of both grammars, which is correct for\n * either chain and freezes less around `$$`. The adapters pass `true`\n * because `buildCoreRemarkPlugins` always includes remark-math; a\n * caller with its own chain should declare the real value and may\n * leave it out when unsure. */\n mathFlow?: boolean;\n measure?: AdvanceOptions['measure'];\n}\nconst unmeasured: NonNullable<AdvanceOptions['measure']> = (_stage, fn) => fn();\n\n/** Line/column/offset of the end of `text`, in the parser's convention\n * (1-based line and column, 0-based offset). Counts what micromark counts:\n * `\\n`, `\\r\\n` and a lone `\\r` are one line ending each. */\nfunction endPoint(text: string): { line: number; column: number; offset: number } {\n let line = 1;\n let lineStart = 0;\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n if (code === 10 /* \\n */ || (code === 13 /* \\r */ && text.charCodeAt(i + 1) !== 10)) {\n line += 1;\n lineStart = i + 1;\n }\n }\n return { line, column: text.length - lineStart + 1, offset: text.length };\n}\n\n/**\n * Last-resort trees for a frame whose FULL parse threw the engine's\n * `EngineRawHtmlDepthError`: the whole source as one plain-text paragraph,\n * positions covering the entire content so the planner, cache keys and\n * custom components see well-formed nodes. The text is a hast text node — it\n * renders escaped, never as markup.\n *\n * Why this exists: the incremental path already falls back to the full\n * pipeline, but the full pipeline itself can throw — a few thousand nested\n * raw `<div>` tags exhaust the call stack of a recursive walker after the\n * raw-HTML step (the walk itself, or an adapter renderer further on) — and\n * nothing above the session caught it, so one hostile message took the\n * adapter subtree down. The engine's guarded raw step bounds element depth\n * and reports a deeper frame as a typed error; nothing else is degraded,\n * because a throwing consumer plugin or handler is a bug the host must see,\n * not a frame to render as text. The next frame is parsed normally again\n * (retained state is cleared).\n */\nfunction plainTextTrees(content: string): PipelineTrees {\n const position = { start: { line: 1, column: 1, offset: 0 }, end: endPoint(content) };\n const text = { type: 'text' as const, value: content, position };\n return {\n mdast: { type: 'root', children: [{ type: 'paragraph', children: [text], position }], position },\n hast: {\n type: 'root',\n children: [{ type: 'element', tagName: 'p', properties: {}, children: [text], position }],\n position,\n },\n };\n}\n\nexport function createPipelineSession(): PipelineSession {\n let state: IncrementalParseState | null = null;\n return {\n reset(): void {\n state = null;\n },\n parse({\n content,\n targetPhantoms,\n remarkPlugins,\n rehypePlugins,\n remarkRehypeOptions,\n handlers,\n preserveForBodyHarvest,\n documentId,\n provenance,\n incrementalParse,\n defListEnabled,\n gfmTaskListItems = false,\n // No default: omitted is its own (undeclared) state at the engine.\n mathFlow,\n measure: measureHere = unmeasured,\n }: PipelineFrameOptions) {\n // The suffix is APPENDED (the engine treats it as an always-tail\n // input; prepending would shift every source position). A frame that\n // ends inside an open fence / `$$` block would swallow it — sentinel\n // lines rendered as code, every cross-chunk ref falling back to\n // literal text for the block's whole streaming lifetime — so the\n // engine first emits an output-neutral closer for that block (see\n // phantomSuffixCloser; '' when nothing is open or the phase is\n // untrusted). Only chunks with a non-empty suffix pay the line scan.\n const phantomDefs = buildPhantomSuffix(targetPhantoms);\n const phantomSuffix = phantomDefs === '' ? '' : phantomSuffixCloser(content ?? '') + phantomDefs;\n const augmented = (content ?? '') + phantomSuffix;\n const baseHandlers = remarkRehypeOptions?.handlers ?? {};\n const mergedRemarkRehypeOptions = (\n handlers\n ? {\n ...remarkRehypeOptions,\n handlers: { ...baseHandlers, ...handlers },\n // Phantom label sets are empty in standalone mode (no PASS 0.5\n // injection happened); the footnoteDefinition handler still reads\n // them via `state.options.phantomFootnoteLabels.has(id)`, which\n // returns false for every id → orphan-protect path proceeds.\n phantomFootnoteLabels: targetPhantoms.missingFootnotes,\n phantomLinkLabels: targetPhantoms.missingLinks,\n preserveOrphan: preserveForBodyHarvest,\n documentId,\n // The SAME value `buildCoreRehypePlugins` received — the\n // verifier unwraps every placeholder stamped with anything else.\n provenance,\n }\n : {\n ...remarkRehypeOptions,\n }\n ) as RemarkRehypeOptions;\n\n // Coordinated (registry) mode is incremental-eligible since v2: the\n // engine takes the phantom suffix as a separate always-tail input (its\n // frame-to-frame churn re-parses only the tail — the reference taint\n // keeps every phantom-resolved ref out of the frozen prefix), and the\n // contribute effect's inputs are covered by splice equivalence (mdast)\n // plus the replay-regenerated footer (hast). When the flag is off, the\n // state is CLEARED — a later eligible frame must never splice against\n // trees parsed under different conditions.\n //\n // SSR takes this branch too: the engine's first-frame scan exists to\n // seed the NEXT frame's checkpoint, and a per-request server render\n // has no next frame — routing through the engine would pay a dead\n // O(document) line-lex per request. Hydration is unaffected (the\n // client's first frame rebuilds from null either way).\n // The ordinary full pipeline, with the plain-text last resort around\n // it for the engine's raw-depth signal only (see plainTextTrees).\n // Dev-only stage telemetry (`ai-markdown:stage:*` performance\n // measures; no-op in production) wraps only the stage calls — the\n // surrounding option assembly is trivial.\n const fullPipeline = (): PipelineTrees => {\n try {\n const parsed = measureHere('parse', () =>\n parseStage({\n children: augmented,\n remarkPlugins,\n rehypePlugins,\n remarkRehypeOptions: mergedRemarkRehypeOptions,\n })\n );\n const hastRoot = measureHere('transform', () => transformStage(parsed));\n return { mdast: parsed.mdast, hast: hastRoot };\n } catch (error) {\n // Only the engine's own signal for raw HTML nested past its depth\n // bound is degraded. Everything else — a consumer's remark/rehype\n // plugin or handler throwing, a RangeError that is not that\n // signal — propagates exactly as it did before the fallback\n // existed.\n if (!(error instanceof EngineRawHtmlDepthError)) throw error;\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n '[ai-react-markdown] raw HTML nested past the engine depth bound — rendering this frame as plain text:',\n error\n );\n }\n return plainTextTrees(content ?? '');\n }\n };\n\n if (!incrementalParse) {\n state = null;\n return fullPipeline();\n }\n\n try {\n const result = advanceIncrementalParse(state, content ?? '', {\n remarkPlugins,\n rehypePlugins,\n remarkRehypeOptions: mergedRemarkRehypeOptions,\n // Identity tuple over every parse input beyond the content itself.\n // Deliberately covers MORE than the G3 flush's 12 fields (handlers /\n // preserveForBodyHarvest / documentId can change without touching\n // any G3 field — e.g. a `preserveOrphanReferences` flip). The\n // phantom label sets are deliberately NOT here: their churn tracks\n // the suffix (always re-parsed with the tail), never the prefix.\n depsKey: [\n remarkPlugins,\n rehypePlugins,\n remarkRehypeOptions,\n handlers,\n preserveForBodyHarvest,\n documentId,\n // Explicit even though `rehypePlugins` already carries it: a\n // credential change must never reuse trees stamped under the\n // old one.\n provenance,\n // The boundary scanner's grammar profile. The scan checkpoint\n // already refuses to resume under a different profile, but the\n // retained TREES were still spliced against — a prefix frozen\n // under one profile must not survive into frames under the other.\n defListEnabled,\n gfmTaskListItems,\n mathFlow,\n ],\n defListEnabled,\n gfmTaskListItems,\n mathFlow,\n phantomSuffix,\n measure: measureHere,\n });\n state = result.nextState;\n return { mdast: result.mdast, hast: result.hast };\n } catch (error) {\n // The engine mutates prev's scan checkpoint IN PLACE before the tail\n // parse/splice — a throw mid-frame (an engine bug, or a plugin\n // choking on the synthetic tail source) leaves the retained state's\n // checkpoint describing content the state's trees do not. Clearing\n // the ref restores the \"state is CLEARED when unusable\" discipline;\n // the frame then renders via the ordinary full pipeline so one bad\n // frame cannot take the surface down.\n state = null;\n if (process.env.NODE_ENV !== 'production') {\n console.error('[ai-react-markdown] incremental parse failed — full parse fallback for this frame:', error);\n }\n return fullPipeline();\n }\n },\n };\n}\n","/** Shared decisions only: no registration, subscriptions or publication.\n * Previous outputs are optional identity hints owned by one host consumer.\n * Input label sets and returned objects must be treated as immutable snapshots.\n */\nimport { buildCrossChunkHandlers, normalizeForMatch } from '@ai-markdown/engine';\nimport type { PipelineFrameOptions } from './pipelineSession';\n\nexport interface DefinitionLabels {\n footnoteLabels: ReadonlySet<string>;\n linkLabels: ReadonlySet<string>;\n}\nexport type PhantomTargets = PipelineFrameOptions['targetPhantoms'];\n\nexport function derivePhantomTargets(\n { content, ownLabels, labels }: { content: string; ownLabels: DefinitionLabels; labels: DefinitionLabels | null },\n previous?: PhantomTargets\n): PhantomTargets {\n const missingFootnotes = new Set<string>();\n const missingLinks = new Set<string>();\n if (labels) {\n const footnotes = [...labels.footnoteLabels].filter((label) => !ownLabels.footnoteLabels.has(label));\n const links = [...labels.linkLabels].filter((label) => !ownLabels.linkLabels.has(label));\n // Avoid two full-content normalization passes when there are no candidates.\n if (footnotes.length || links.length) {\n const normalized = normalizeForMatch(content);\n for (const label of footnotes) if (normalized.includes(label)) missingFootnotes.add(label);\n for (const label of links) if (normalized.includes(label)) missingLinks.add(label);\n }\n }\n if (\n previous &&\n missingFootnotes.size === previous.missingFootnotes.size &&\n missingLinks.size === previous.missingLinks.size &&\n [...missingFootnotes].every((label) => previous.missingFootnotes.has(label)) &&\n [...missingLinks].every((label) => previous.missingLinks.has(label))\n )\n return previous;\n return { missingFootnotes, missingLinks };\n}\n\nexport interface CoordinationPolicy {\n mode: 'coordinated' | 'orphan' | 'standalone';\n handlers: PipelineFrameOptions['handlers'];\n preserveForBodyHarvest: boolean;\n}\n\nexport function deriveCoordinationPolicy(\n {\n coordinated,\n registered,\n preserveOrphanReferences,\n }: {\n coordinated: boolean;\n registered: boolean;\n /** The host has already resolved any wrapper-level override. */\n preserveOrphanReferences: boolean;\n },\n previous?: CoordinationPolicy\n): CoordinationPolicy {\n const mode = coordinated ? 'coordinated' : preserveOrphanReferences ? 'orphan' : 'standalone';\n const preserveForBodyHarvest = preserveOrphanReferences || (coordinated && registered);\n if (previous?.mode === mode && previous.preserveForBodyHarvest === preserveForBodyHarvest) return previous;\n // Registration can change body harvesting without changing handler identity.\n const handlers =\n previous?.mode === mode\n ? previous.handlers\n : mode === 'coordinated'\n ? buildCrossChunkHandlers()\n : mode === 'orphan'\n ? { footnoteDefinition: buildCrossChunkHandlers().footnoteDefinition }\n : undefined;\n return { mode, handlers, preserveForBodyHarvest };\n}\n\nexport type ContributionPolicyInputs = Pick<\n PipelineFrameOptions,\n | 'remarkPlugins'\n | 'rehypePlugins'\n | 'remarkRehypeOptions'\n | 'handlers'\n | 'preserveForBodyHarvest'\n | 'documentId'\n | 'provenance'\n> & { clobberPrefix: string };\n\n/** Source/phantom/registry/symbol changes are compared by the publisher itself.\n * This tuple covers the remaining inputs that can alter harvested bodies.\n */\nexport function buildContributionChain(inputs: ContributionPolicyInputs): readonly unknown[] {\n return [\n inputs.remarkPlugins,\n inputs.rehypePlugins,\n inputs.remarkRehypeOptions,\n inputs.handlers,\n inputs.preserveForBodyHarvest,\n inputs.clobberPrefix,\n inputs.documentId,\n inputs.provenance,\n ];\n}\n"],"mappings":"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","names":["visit","visit","createSmoothCoordinator","footnoteSafeId","footnoteSafeId"]}
1
+ {"version":3,"file":"index.js","names":["createSmoothCoordinator","createInternalCoordinator"],"sources":["../src/blockPlan.ts","../src/blockPlanner.ts","../src/contribution.ts","../src/smoothCoordinator.ts","../src/coordinator.ts","../src/tailSignal.ts","../src/cloneHastForRender.ts","../src/aggregateFootnotes.ts","../src/pipelineSession.ts","../src/coordinationPreparation.ts"],"sourcesContent":["/** Framework-neutral block planning and dependency fingerprints.\n * Cache ownership and conversion to framework nodes belong to adapters.\n * See apps/docs/content/guides/architecture.md and packages/core/README.md for the contract.\n */\nimport type { Element as HastElement, Root as HastRoot, RootContent as HastChild } from 'hast';\nimport type { Root as MdastRoot, RootContent as MdastContent, Nodes as MdastNodes } from 'mdast';\nimport { SKIP, visit } from 'unist-util-visit';\nimport { isFootnoteSection, normalizeId, type Registry } from '@ai-markdown/engine';\n\n/**\n * mdast types whose presence in a block makes that block dependent on\n * cross-block syntax (footnote/link/image references and definitions).\n * Tainted blocks invalidate via the document `globalCtx` digest; non-tainted\n * blocks invalidate only on raw + position change.\n */\nconst TAINT_TYPES: ReadonlySet<string> = new Set([\n 'footnoteReference',\n 'footnoteDefinition',\n 'linkReference',\n 'imageReference',\n 'definition',\n]);\n\n/**\n * mdast types that contribute to the document-wide `globalCtx` digest.\n * Currently identical to {@link TAINT_TYPES} — refs/defs are simultaneously\n * \"things that make a block tainted\" and \"things that change a tainted\n * block's render output\". Kept as a separate constant so the two roles can\n * diverge later (e.g. a future plugin that introduces a new node type which\n * is a ctx contributor but not a per-block taint source).\n */\nconst CTX_TYPES: ReadonlySet<string> = TAINT_TYPES;\n\n/** Source-level identity of one renderable hast block. */\nexport interface BlockInfo {\n raw: string;\n startOffset: number;\n endOffset: number;\n /**\n * Line and column at the block's start. Tracked alongside `startOffset`\n * because two different source documents can produce the same byte offset\n * for the \"same\" content (`A\\n\\nB\\n\\nTarget` vs `ABCD\\n\\nTarget` — both\n * have `Target` starting at offset 6 but on different lines). Custom\n * components that read `node.position.start.line` would otherwise see\n * stale data from a false-positive cache hit.\n */\n startLine: number;\n startColumn: number;\n /** True if the block contains any TAINT_TYPES node — invalidate on globalCtx change. */\n hasReference: boolean;\n /**\n * Structural digest of the block's hast subtree, computed ONLY for blocks\n * that originate from raw HTML (mdast `html` nodes, or blocks resolved via\n * the range-containment fallback). Undefined for markdown-native blocks.\n *\n * Why it exists: rehype-raw applies the HTML parsing algorithm, so an\n * UNCLOSED container tag (`<details>` mid-stream, before its `</details>`\n * arrives) swallows every FOLLOWING top-level sibling into itself —\n * including the synthetic `<section data-footnotes>`. The block's mdast\n * identity (raw, position, ctx) is byte-identical across all those frames,\n * so without this digest the first swallowed snapshot would be a permanent\n * cache hit: content rendered inside the container freezes, and when the\n * close tag finally arrives the real nodes re-render at top level while the\n * stale copies stay trapped in the cached container (the \"duplicate\n * footnote section inside <details>\" bug).\n *\n * Digest composition — `maxEnd:descendantCount:hasFootnoteSection:swallowedHash`:\n * - max end offset over the subtree catches swallowed POSITIONED siblings\n * (their offsets lie beyond the mdast node's own range);\n * - descendant count catches position-less synthetic content moving in or\n * out (the footnote section's own element carries no position, and its\n * li contents point BACK at the definitions' offsets, which can be\n * smaller than the container's end — maxEnd alone misses that case);\n * - the explicit dataFootnotes bit closes the degenerate corner where a\n * swallowed section and later real children tie on both numbers;\n * - a hash of the swallowed extent's SOURCE (`[ownEnd, maxEnd)`) quickly\n * rejects most equal-length, equal-shape replacements. A matching hash\n * still requires an exact {@link BlockInfo.swallowedSource} comparison.\n *\n * Markdown-native blocks (paragraph/list/math/code/…) never receive\n * reparented content — every hast descendant derives from their own mdast\n * source range — so they skip the subtree walk entirely. This keeps the\n * per-frame cost away from huge deterministic subtrees like KaTeX output.\n */\n hastDigest?: string;\n /**\n * Exact source in the swallowed interval `[ownEnd, maxEnd)` of a raw-HTML\n * block. The FNV-1a 32-bit hash in `hastDigest` can collide for different\n * equal-length contents, so a cache hit must compare this string too.\n * Empty when the block swallowed no source; absent for markdown-native\n * blocks. Optional to keep existing public block-plan producers compatible.\n */\n swallowedSource?: string;\n /** A raw-HTML container that swallowed the synthesized\n * `<section data-footnotes>` (rehype-raw reparents the following siblings\n * into an unclosed `<details>` / `<div>` mid-stream). The footer is then\n * NOT a top-level plan item, so coordinated mode's \"skip the local\n * footer, the aggregate renders it\" rule never fired and the document\n * showed two `<section data-footnotes>` with colliding li ids (2026-08-19\n * review r2 P2-9). Coordinated renders strip it from this block's subtree;\n * standalone renders keep it exactly where the full parse puts it. */\n containsFootnoteSection?: boolean;\n /** TAINT-block 专属:按节点类型分桶的 label set。Normalized 形态(uppercase)。\n * Undefined when hasReference === false. */\n taintLabels?: {\n footnoteRefLabels: string[];\n linkRefLabels: string[];\n imageRefLabels: string[];\n footnoteDefLabels: string[];\n /** Chunk-local footnote state the engine baked into this block's\n * `footnote-sup` placeholders (`localOccurrence` / `localNumber` come\n * from a per-chunk counter over all PRECEDING refs, customMdastHandlers):\n * per ref label, refs-before-this-block count and first-appearance\n * rank. Same raw + same registry answers but a different prefix (an\n * in-place `[^x]`→`[^w]` edit upstream) must miss the cache, or the\n * stale occurrence resolves to a null global occurrence and the mark\n * vanishes (2026-08-19 review P2-4). Absent when the block has no refs. */\n footnoteRefLocalCtx?: string;\n /** Baked `localUrl` / `localTitle` of swallowed `cross-chunk-link` /\n * `-image` placeholders, as `[[label, url, title|null]…]` (JSON). The\n * render falls back to these when the registry has no canonical def,\n * where the fingerprint's `lr:`/`ir:` part is the constant `null` —\n * see scanSwallowedSubtree. Absent when nothing was swallowed. */\n swallowedDefCtx?: string;\n };\n}\n\n/**\n * Slice the source string for the given mdast node's position. mdast nodes\n * produced by remark-parse always carry `position`; the empty-string fallback\n * is purely defensive.\n */\nfunction extractRaw(node: MdastNodes, source: string): string {\n if (!node.position) return '';\n return source.slice(node.position.start.offset, node.position.end.offset);\n}\n\n// isFootnoteSection lives in the engine now (boundary action ①): the\n// incremental-parse engine's attributeHastChildren needs it too, and\n// engine→core imports are forbidden. Re-exported so this module's export\n// surface (and its tests) stay unchanged.\nexport { isFootnoteSection };\n\n/** Dev invariant: every block hast child must retain its mdast `position`. */\nexport function hasMdastSource(node: HastElement): boolean {\n return node.position !== undefined;\n}\n\n/**\n * Compute {@link BlockInfo.hastDigest} for a raw-HTML block: iterative walk\n * over the hast subtree collecting (max end offset, descendant count,\n * contains-footnote-section). See the field's JSDoc for why each component\n * is load-bearing. Exported for tests.\n */\nexport function computeHtmlBlockDigest(el: HastElement, source?: string, ownEnd?: number): string {\n return computeHtmlBlockDigestWithExtent(el, source, ownEnd).digest;\n}\n\n/** {@link computeHtmlBlockDigest} plus the subtree's max end offset — the end\n * of the SWALLOWED extent, which callers need to ask what fell inside.\n *\n * Both position sources here are HAST offsets, and every use of the extent\n * is unsafe in the SHORT direction (too small and a swallowed reference\n * escapes the taint test, so an edit to a definition outside the container\n * leaves a stale href in the cache). They are redundant on purpose: the\n * container's own end already reaches past the swallowed blocks, because\n * `rehype-raw` reparses the raw string and closes the element wherever\n * parse5 does — NOT where the mdast `html` node ends, which is just the\n * opening tag. The descendant walk covers it a second time. Verified in\n * 2026-08-20 A1 across 162 container/tail shapes: destroying either source\n * alone changes nothing, destroying both under-taints 130 of them. */\nexport function computeHtmlBlockDigestWithExtent(\n el: HastElement,\n source?: string,\n ownEnd?: number\n): { digest: string; maxEnd: number } {\n let maxEnd = el.position?.end?.offset ?? 0;\n let count = 0;\n let hasFootnoteSection = false;\n const stack: HastElement['children'][number][] = [...el.children];\n while (stack.length > 0) {\n const node = stack.pop()!;\n count++;\n const end = node.position?.end?.offset;\n if (end !== undefined && end > maxEnd) {\n maxEnd = end;\n }\n if (node.type === 'element') {\n if (isFootnoteSection(node)) {\n hasFootnoteSection = true;\n }\n for (const child of node.children) {\n stack.push(child);\n }\n }\n }\n // Swallowed SOURCE hash: `maxEnd:count` are blind to an equal-length,\n // equal-shape replacement of the swallowed siblings (`Answer: 42` →\n // `Answer: 43` inside an unclosed <details>, single-frame — v2.4.2 review\n // P2-1); the container's own `raw` is unchanged, so the cache hit kept\n // rendering the old text. The hash is a fast rejection; buildBlocks also\n // retains the exact swallowed source for collision-safe cache validation.\n const swallowed = source !== undefined && ownEnd !== undefined && maxEnd > ownEnd ? source.slice(ownEnd, maxEnd) : '';\n return { digest: `${maxEnd}:${count}:${hasFootnoteSection ? 1 : 0}:${fnv1a(swallowed)}`, maxEnd };\n}\n\n/** FNV-1a 32-bit over UTF-16 code units — cheap, no allocation. */\nfunction fnv1a(text: string): string {\n let h = 0x811c9dc5;\n for (let i = 0; i < text.length; i++) {\n h ^= text.charCodeAt(i);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return h.toString(36);\n}\n\n/**\n * One step of the document render plan, in source order.\n *\n * The plan covers EVERY top-level hast child so that the adapter’s block cache\n * can preserve react-markdown's byte-equivalent output — including the whitespace\n * `text` nodes that `mdast-util-to-hast` inserts between block elements.\n *\n * - `block`: a hast element with an mdast counterpart, eligible for caching by\n * `(raw, occurrence index, ctx, startOffset)`.\n * - `inline`: a non-element top-level child (whitespace text, comment, …) or\n * an element that has no mdast counterpart but is still safe to render.\n * These are rendered every frame without caching — they are typically\n * single whitespace characters.\n * - `synthetic`: the synthesized footnote `<section data-footnotes>`, cached\n * in a single slot keyed by `globalCtx`.\n */\nexport type RenderItem =\n | { kind: 'block'; el: HastElement; key: string; info: BlockInfo }\n | { kind: 'inline'; el: HastChild; key: string }\n | { kind: 'synthetic'; el: HastElement; key: string };\n\nconst EMPTY_LABELS: ReadonlySet<string> = new Set<string>();\n\n/** Extra render-time facts {@link buildBlocks} cannot read off the trees. */\nexport interface BuildBlocksOptions {\n /** Full document when planning only a tail. Its ordered reference and\n * definition context seeds numbering and invalidation for tail blocks. */\n contextMdast?: MdastRoot;\n /**\n * Labels this chunk phantom-injected because another chunk defines them\n * (already normalized). The coordinated handlers keep these out of\n * `state.footnoteOrder`, so the cache key's rank must keep them out too —\n * see the note in the rank pass. Empty / omitted in standalone mode, where\n * no phantom injection happened.\n */\n phantomFootnoteLabels?: ReadonlySet<string>;\n}\n\n/** Result of {@link buildBlocks}. */\nexport interface BuildBlocksResult {\n /** Render plan in document order — drives the adapter’s block cache. */\n plan: RenderItem[];\n /** JSON-stringified ordered list of TAINT-typed nodes in document order. */\n globalCtx: string;\n // ── Derived views (kept for tests and convenience) ─────────────────────\n /** Flat list of `BlockInfo` for cacheable blocks, in document order. */\n blocks: BlockInfo[];\n /** 1:1 with `blocks` — the hast Element each BlockInfo refers to. */\n blockHasts: HastElement[];\n /** Synthesized footnote section, if present. */\n synthetic?: HastElement;\n}\n\nconst FOOTNOTE_SECTION_KEY = '__footnote_section__';\n\n/**\n * Build the document render plan + ctx digest from a parsed mdast and its\n * rendered hast.\n *\n * Plan construction is driven by `hast.children` (NOT `mdast.children`):\n * pipeline transformers like `remarkSqueezeParagraphs` and\n * `remarkRemoveComments` drop blocks, so the rendered hast may have fewer\n * top-level children than the parsed mdast. Driving by hast guarantees the\n * plan covers exactly what will be rendered.\n *\n * Each hast top-level child becomes one plan item:\n * - `<section data-footnotes>` → `synthetic` (cached in a single slot)\n * - element with mdast counterpart → `block` (cacheable by raw + occurrence + ctx + offset)\n * - text / comment / element-without-counterpart → `inline` (rendered fresh every frame)\n *\n * The mdast counterpart of a block element is found by source offset using\n * a two-tier lookup: an exact-offset Map (mdast-util-to-hast's default 1:1\n * propagation), then range-containment `findLast` fallback for cases like\n * `rehype-raw` splitting one mdast `html` node into multiple hast siblings\n * (e.g. ` <div>Hi</div>` — leading spaces shift the `<div>` offset\n * inside the parent html node's source range).\n *\n * In dev, a hast block whose offset matches no mdast counterpart at all\n * throws — that means a rehype plugin synthesized positions outside the\n * source range, which is a bug worth surfacing. In production it falls\n * through to an `inline` plan item so user content is never silently lost.\n *\n * `globalCtx` walks the full mdast for footnote/link/image refs and\n * definitions in document order (no dedupe — order matters for footnote\n * numbering), then JSON-stringifies the collected tuples. That string is the\n * invalidation key for tainted blocks and the synthetic footnote section.\n */\nexport function buildBlocks(\n mdast: MdastRoot,\n hast: HastRoot,\n source: string,\n options: BuildBlocksOptions = {}\n): BuildBlocksResult {\n const phantomFootnotes = options.phantomFootnoteLabels ?? EMPTY_LABELS;\n const mdastByOffset = new Map<number, MdastContent>();\n // Sorted [start, end, node] table for the range-containment fallback used\n // when a hast block's offset is INSIDE an mdast node's source range\n // (`rehype-raw` splitting one mdast `html` node into multiple hast\n // siblings is the canonical case). Pre-sorted because mdast children come\n // out of remark-parse in source order; we collect positioned ones in\n // place, preserving order. Binary search at lookup time replaces the\n // previous O(N) `findLast` — without it, M hast blocks × N mdast children\n // degrades to O(N×M) on pathological streams with many splits.\n type Range = { start: number; end: number; node: MdastContent };\n const mdastRanges: Range[] = [];\n for (const child of mdast.children) {\n const off = child.position?.start.offset;\n const endOff = child.position?.end.offset;\n if (off !== undefined) {\n mdastByOffset.set(off, child);\n }\n if (off !== undefined && endOff !== undefined) {\n mdastRanges.push({ start: off, end: endOff, node: child });\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n for (let i = 1; i < mdastRanges.length; i++) {\n if (mdastRanges[i].start < mdastRanges[i - 1].start) {\n // Should never trip: mdast.children is source-order. If it does\n // trip, the binary search below is wrong — surface it loudly so\n // we sort defensively rather than silently misroute hast blocks.\n throw new Error(\n 'block-memo: mdast.children not sorted by source offset — ' +\n 'a remark plugin is reordering top-level children.'\n );\n }\n }\n }\n function findContainingMdast(offset: number): MdastContent | undefined {\n // Upper-bound binary search: find first index where range.start > offset.\n // The candidate is the immediate predecessor (largest start <= offset).\n let lo = 0;\n let hi = mdastRanges.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (mdastRanges[mid].start <= offset) lo = mid + 1;\n else hi = mid;\n }\n const idx = lo - 1;\n if (idx < 0) return undefined;\n const r = mdastRanges[idx];\n // Top-level mdast children have non-overlapping source ranges, so the\n // single largest-start candidate is the unique container (if any).\n return offset < r.end ? r.node : undefined;\n }\n\n const ctxParts: unknown[] = [];\n // Start offsets of every taint node, in document order — used to tell\n // whether a raw-HTML container swallowed a reference whose RENDERING\n // depends on a definition outside it (see the isRawHtmlBlock branch).\n const taintOffsets: number[] = [];\n /** First index in `taintOffsets` at or after `offset`. The offsets are\n * pushed by a document-order `visit`, so the array is already sorted —\n * the same property `findContainingMdast` above relies on. */\n const firstTaintAtOrAfter = (offset: number): number => {\n let lo = 0;\n let hi = taintOffsets.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (taintOffsets[mid] < offset) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n };\n visit(options.contextMdast ?? mdast, (n) => {\n if (!CTX_TYPES.has(n.type)) return;\n const at = n.position?.start?.offset;\n if (at !== undefined) taintOffsets.push(at);\n if (n.type === 'footnoteReference') ctxParts.push(['fr', n.identifier]);\n else if (n.type === 'footnoteDefinition') ctxParts.push(['fd', n.identifier, extractRaw(n, source)]);\n else if (n.type === 'linkReference') ctxParts.push(['lr', n.identifier]);\n else if (n.type === 'imageReference') ctxParts.push(['ir', n.identifier]);\n else if (n.type === 'definition') ctxParts.push(['d', n.identifier, n.url, n.title ?? null]);\n });\n if (process.env.NODE_ENV !== 'production') {\n // A preorder walk yields non-decreasing start offsets on any well-formed\n // tree, and `firstTaintAtOrAfter` is a binary search over exactly that.\n // Same guard, same reason, and the same shape as the one on\n // `mdastRanges`: the env read stays OUT of the per-node path, which cost\n // more than the search saved when it was inside it.\n for (let i = 1; i < taintOffsets.length; i++) {\n if (taintOffsets[i] < taintOffsets[i - 1]) {\n throw new Error(\n 'block-memo: taint node start offsets are not source-ordered — ' +\n 'a remark plugin is reordering nodes or emitting bad positions.'\n );\n }\n }\n }\n const globalCtx = JSON.stringify(ctxParts);\n\n const plan: RenderItem[] = [];\n const blocks: BlockInfo[] = [];\n const blockHasts: HastElement[] = [];\n let synthetic: HastElement | undefined;\n // Mirror of the engine's per-chunk footnote counter (customMdastHandlers:\n // one bump per footnoteReference in mdast-util-to-hast's preorder walk):\n // computed ONCE over the mdast in document order, per ref NODE, so the\n // hast-driven block loop below cannot skew it — a range-fallback block\n // resolves several hast siblings to one mdast node (its refs would be\n // counted twice), and refs inside a footnote definition are rendered by\n // the footer AFTER every body ref (never in a body block) — skipped.\n const refLocalCtx = new Map<MdastNodes, readonly [prior: number, rank: number]>();\n {\n const counts = new Map<string, number>();\n const ranks = new Map<string, number>();\n const rankOf = (id: string): number => {\n // A PHANTOM label never enters the engine's footnoteOrder — the\n // coordinated handlers return early for both its definition and its\n // references, and its `footnote-sup` gets no `localNumber` at all. It\n // must not take a rank here either, or the model drifts from the value\n // actually baked into the placeholder: with `[^A]` cross-chunk and\n // `[^B]` local, B's baked number is 1 while A is phantom and 2 once A's\n // definition arrives — and B's rank, counting A both times, stayed 1.\n // Same raw, same position, same rank: the cache served a superscript\n // reading 1 under a footer numbering it 2 (2026-08-20 B2).\n if (phantomFootnotes.has(id)) return -1;\n let rank = ranks.get(id);\n if (rank === undefined) {\n rank = ranks.size;\n ranks.set(id, rank);\n }\n return rank;\n };\n visit(options.contextMdast ?? mdast, (n) => {\n if (n.type === 'footnoteDefinition') {\n // A definition met before the label's first ref can enter the\n // engine's footnoteOrder first (preserveOrphan) — it takes part in\n // the first-appearance rank; its body refs are footer-rendered.\n //\n // Modelled as `preserveOrphan: true` unconditionally, which is not\n // always what the engine does. Deliberate: with orphan protection\n // OFF the two orderings can disagree, but they disagree the SAME way\n // in every frame, and the flag only changes by a prop change — which\n // re-parses the whole pipeline anyway. Phantom status is different:\n // it flips mid-stream, which is what made it a stale-cache bug.\n rankOf(normalizeId(String(n.identifier)));\n return SKIP;\n }\n if (n.type !== 'footnoteReference') return;\n const id = normalizeId(String(n.identifier));\n const prior = counts.get(id) ?? 0;\n refLocalCtx.set(n, [prior, rankOf(id)]);\n counts.set(id, prior + 1);\n });\n }\n\n for (let i = 0; i < hast.children.length; i++) {\n const hastChild = hast.children[i];\n\n if (hastChild.type !== 'element') {\n // Top-level non-element child (whitespace text inserted by\n // mdast-util-to-hast between block elements, sanitized comment, …).\n // Render inline without caching to preserve byte-equivalent output.\n const off = hastChild.position?.start.offset;\n const key = off !== undefined ? `inline-${off}` : `inline-i${i}`;\n plan.push({ kind: 'inline', el: hastChild, key });\n continue;\n }\n\n const el = hastChild;\n\n if (isFootnoteSection(el)) {\n synthetic = el;\n plan.push({ kind: 'synthetic', el, key: FOOTNOTE_SECTION_KEY });\n continue;\n }\n const hastOffset = el.position?.start.offset;\n if (hastOffset === undefined) {\n // Element without position (synthesized by some plugin) — preserve in\n // document order, rendered as inline (no cache key available).\n plan.push({ kind: 'inline', el, key: `inline-i${i}` });\n continue;\n }\n\n let mdastNode = mdastByOffset.get(hastOffset);\n let viaRangeFallback = false;\n\n if (!mdastNode) {\n mdastNode = findContainingMdast(hastOffset);\n viaRangeFallback = mdastNode !== undefined;\n }\n\n if (!mdastNode) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n `block-memo: hast block at offset ${hastOffset} has no mdast counterpart. ` +\n `A rehype plugin may have synthesized positions outside source. tagName=${el.tagName}`\n );\n }\n // Production fallback: render inline, no cache.\n plan.push({ kind: 'inline', el, key: `inline-${hastOffset}` });\n continue;\n }\n\n if (mdastNode.type === 'footnoteDefinition') {\n // Footnote definitions are hoisted into the synthetic section by\n // mdast-util-to-hast — they should not appear as top-level hast blocks\n // in any current plugin chain. If the range fallback ever resolves an\n // unrelated hast block to a footnoteDefinition's mdast source range,\n // preserve it as `inline` rather than silently dropping it (defensive\n // parity with the other no-counterpart fallbacks above).\n plan.push({ kind: 'inline', el, key: `inline-${hastOffset}` });\n continue;\n }\n\n // Only a raw-HTML block can have foreign content reparented into it by\n // rehype-raw (an unclosed container tag mid-stream); markdown-native\n // blocks derive their whole subtree from their own source range.\n const isRawHtmlBlock = mdastNode.type === 'html' || viaRangeFallback;\n let hasReference = false;\n const swallowed = isRawHtmlBlock ? scanSwallowedSubtree(el) : null;\n const footnoteRefLabels: string[] = [];\n const linkRefLabels: string[] = [];\n const imageRefLabels: string[] = [];\n const footnoteDefLabels: string[] = [];\n const local: [string, number, number][] = [];\n visit(mdastNode, (n) => {\n if (!TAINT_TYPES.has(n.type)) return;\n hasReference = true;\n const id = 'identifier' in n ? normalizeId(String(n.identifier)) : null;\n if (id === null) return;\n if (n.type === 'footnoteReference') {\n footnoteRefLabels.push(id);\n // Refs inside a nested footnote definition have no entry (footer-\n // rendered, not part of this block's output).\n const ctx = refLocalCtx.get(n);\n if (ctx) local.push([id, ctx[0], ctx[1]]);\n } else if (n.type === 'linkReference') linkRefLabels.push(id);\n else if (n.type === 'imageReference') imageRefLabels.push(id);\n else if (n.type === 'footnoteDefinition') footnoteDefLabels.push(id);\n // 'definition' nodes don't carry per-block fingerprint significance\n // (they're metadata, not visible); intentionally not bucketed.\n });\n // Merge what the container swallowed (see scanSwallowedSubtree): the\n // mdast walk above saw only the empty `html` node.\n if (swallowed !== null && swallowed.hasReference) {\n hasReference = true;\n footnoteRefLabels.push(...swallowed.refLabels);\n linkRefLabels.push(...swallowed.linkLabels);\n imageRefLabels.push(...swallowed.imageLabels);\n local.push(...swallowed.local);\n }\n // JSON-encoded: labels may hold any separator character.\n const footnoteRefLocalCtx = local.length > 0 ? JSON.stringify(local) : undefined;\n const swallowedDefCtx =\n swallowed !== null && swallowed.bakedDefs.length > 0 ? JSON.stringify(swallowed.bakedDefs) : undefined;\n\n const mdastPos = mdastNode.position;\n if (!mdastPos || mdastPos.start?.offset === undefined || mdastPos.end?.offset === undefined) {\n // A third-party remark plugin emitting a half-built position: the\n // block cannot be cached, but the user's content is never silently\n // lost — render it uncached like the other no-counterpart fallbacks\n // above (v2.4.1 review: this path used to drop the hast element).\n plan.push({ kind: 'inline', el, key: `inline-${hastOffset}` });\n continue;\n }\n\n // Raw-HTML blocks are the only ones rehype-raw can reparent following\n // siblings into (unclosed container tags mid-stream). Two origins:\n // a top-level mdast `html` node, or a block resolved via the\n // range-containment fallback (raw HTML embedded in another node, split\n // into extra hast siblings). Digest their subtree so the cache\n // invalidates when the swallowed extent changes; markdown-native blocks\n // skip the walk (their subtrees derive purely from their own source\n // range, already covered by `raw` + position).\n let hastDigest: string | undefined;\n let swallowedSource: string | undefined;\n let swallowedTaint = false;\n if (isRawHtmlBlock) {\n const d = computeHtmlBlockDigestWithExtent(el, source, mdastPos.end.offset);\n hastDigest = d.digest;\n swallowedSource = source.slice(mdastPos.end.offset, d.maxEnd);\n // Did the container swallow a reference or definition? The hast scan\n // above sees coordinated placeholders and standalone footnote marks,\n // but a STANDALONE link/image reference renders as a plain `<a href>` /\n // `<img src>` — indistinguishable from an inline link. Ask the mdast\n // instead: any taint node whose source offset lies inside the swallowed\n // extent. Without this a definition sitting BEFORE the container (thus\n // outside the digest's `[ownEnd, maxEnd)` hash) could change the\n // rendered href while every cache-key component stayed equal.\n //\n // What this buys, per mode: in STANDALONE mode the flag switches the\n // key to `globalCtx`, which encodes every definition's url/title —\n // strictly stronger. In COORDINATED mode the labels come from the\n // placeholder scan above (every resolvable reference becomes a\n // `cross-chunk-*` element), so this test is belt-and-braces there.\n // `[ownEnd, maxEnd)` mixes offset systems on purpose: `ownEnd` is the\n // MDAST end (where the opening tag stops), `maxEnd` the HAST end\n // (where parse5 closed the element, past everything it swallowed).\n // Half-open at the low end because anything below `ownEnd` is already\n // covered by `raw`, and `maxEnd` erring high only over-taints.\n // Binary search, not a scan: the linear form was O(taint nodes ×\n // raw-HTML blocks) on the streaming hot path, and the sortedness it\n // needs is the same one `findContainingMdast` already assumes\n // (2026-08-20 B5 — measured at 14% of buildBlocks on a document with\n // 200 definitions and 120 containers).\n const first = firstTaintAtOrAfter(mdastPos.end.offset);\n swallowedTaint = first < taintOffsets.length && taintOffsets[first] < d.maxEnd;\n }\n\n // A standalone link/image reference renders as a plain `<a href>` /\n // `<img src>`, so only the mdast range test above can see it — fold that\n // in now that the extent is known.\n if (swallowedTaint) hasReference = true;\n\n const info: BlockInfo = {\n raw: extractRaw(mdastNode, source),\n startOffset: mdastPos.start.offset,\n endOffset: mdastPos.end.offset,\n startLine: mdastPos.start.line,\n startColumn: mdastPos.start.column,\n hasReference,\n ...(hastDigest !== undefined ? { hastDigest } : {}),\n ...(swallowedSource !== undefined ? { swallowedSource } : {}),\n ...(swallowed?.hasFootnoteSection ? { containsFootnoteSection: true } : {}),\n ...(hasReference\n ? {\n taintLabels: {\n footnoteRefLabels,\n linkRefLabels,\n imageRefLabels,\n footnoteDefLabels,\n ...(footnoteRefLocalCtx !== undefined ? { footnoteRefLocalCtx } : {}),\n ...(swallowedDefCtx !== undefined ? { swallowedDefCtx } : {}),\n },\n }\n : {}),\n };\n blocks.push(info);\n blockHasts.push(el);\n // render key is keyed off the HAST element's source offset (not the mdast\n // node's), because multi-root raw HTML produces multiple hast siblings\n // that share one mdast `html` node — same `mdastPos.start.offset` for\n // both, which would collide as a render key. Hast positions are unique\n // per element in the rendered tree.\n plan.push({ kind: 'block', el, info, key: `block-${hastOffset}` });\n }\n\n return { plan, globalCtx, blocks, blockHasts, synthetic };\n}\n\n/**\n * Compute a per-block cache fingerprint from the registry slice this block\n * actually depends on (footnote/link/image refs and footnote defs). Two blocks\n * with the same fingerprint render byte-equal output; if any encoded value\n * differs, the block must re-render.\n *\n * Encoding format (deterministic, stable across versions):\n * `<clobberPrefix>|fn:<L>=<globalNumber>|fl:<[[L,prior,rank]…]>|lr:<L>=<url>|<title>|ir:<L>=<url>|fd:<L>=<canonical>/<refCount>`\n *\n * @param taintLabels - Per-block label dependency footprint (from BlockInfo.taintLabels).\n * @param registry - Shared cross-chunk registry.\n * @param thisChunkSym- The Symbol of the chunk this rendering belongs to (for canonical-vs-this comparison).\n * @param clobberPrefix - The documentId-derived id prefix; included so href/id changes invalidate.\n */\nexport function computeBlockFingerprint(\n taintLabels: NonNullable<BlockInfo['taintLabels']>,\n registry: Registry,\n thisChunkSym: symbol,\n clobberPrefix: string\n): string {\n const parts: string[] = [clobberPrefix];\n for (const label of taintLabels.footnoteRefLabels) {\n parts.push(`fn:${label}=${registry.globalNumber(label) ?? 'null'}`);\n }\n // Chunk-local occurrence/rank baked into the placeholders (P2-4) — see\n // buildBlocks; JSON-encoded there, so label bytes cannot collide with `|`.\n if (taintLabels.footnoteRefLocalCtx !== undefined) parts.push(`fl:${taintLabels.footnoteRefLocalCtx}`);\n if (taintLabels.swallowedDefCtx !== undefined) parts.push(`sd:${taintLabels.swallowedDefCtx}`);\n // url/title are JSON-encoded so a `|` inside them cannot collide with the\n // part separator (url `a|b` + no title ≡ url `a` + title `b` would be a\n // stale cache hit — v2.4.1 review).\n for (const label of taintLabels.linkRefLabels) {\n const def = registry.resolveLinkDef(label);\n parts.push(`lr:${label}=${def ? JSON.stringify([def.url, def.title ?? null]) : 'null'}`);\n }\n for (const label of taintLabels.imageRefLabels) {\n const def = registry.resolveLinkDef(label);\n parts.push(`ir:${label}=${def ? JSON.stringify([def.url, def.title ?? null]) : 'null'}`);\n }\n for (const label of taintLabels.footnoteDefLabels) {\n const isCanonical = registry.canonicalFootnoteFor(label) === thisChunkSym ? 1 : 0;\n parts.push(`fd:${label}=${isCanonical}/${registry.getRefsForLabel(label)}`);\n }\n return parts.join('|');\n}\n\nconst PLACEHOLDER_TAGS = new Set(['footnote-sup', 'cross-chunk-link', 'cross-chunk-image']);\n\n/**\n * What a raw-HTML container swallowed, read from the HAST subtree rather\n * than from the container's mdast node — that node is an empty `html` node\n * whose own range says nothing about the reparented siblings, so every\n * mdast-derived taint fact came out empty and the block fell back to the\n * un-fingerprinted cache key (2026-08-19 review r2 P2-8: an equal-length\n * edit BEFORE the container changed a `localOccurrence` baked inside it and\n * the stale subtree stayed cached).\n *\n * Reads what is actually baked into the cached render output: the placeholders'\n * `label` / `localOccurrence` (coordinated), the standalone footnote marks\n * (`a[data-footnote-ref]`), and whether the synthesized footnote section\n * landed inside (see {@link BlockInfo.containsFootnoteSection}).\n */\nfunction scanSwallowedSubtree(el: HastElement): {\n hasReference: boolean;\n refLabels: string[];\n linkLabels: string[];\n imageLabels: string[];\n local: [string, number, number][];\n bakedDefs: [string, string, string | null][];\n hasFootnoteSection: boolean;\n} {\n const refLabels: string[] = [];\n const linkLabels: string[] = [];\n const imageLabels: string[] = [];\n const local: [string, number, number][] = [];\n const bakedDefs: [string, string, string | null][] = [];\n let hasReference = false;\n let hasFootnoteSection = false;\n visit(el, 'element', (node: HastElement) => {\n if (isFootnoteSection(node)) {\n hasFootnoteSection = true;\n hasReference = true;\n return;\n }\n const props = (node.properties ?? {}) as Record<string, unknown>;\n if (PLACEHOLDER_TAGS.has(node.tagName)) {\n hasReference = true;\n const identifier = props.identifier ?? props.label;\n const label = identifier === undefined ? null : normalizeId(String(identifier));\n if (label === null) return;\n if (node.tagName === 'footnote-sup') {\n refLabels.push(label);\n // Chunk-local occurrence as BAKED into this placeholder — the exact\n // value a stale cache hit would keep serving.\n const occ = Number(props.localOccurrence);\n const num = Number(props.localNumber);\n local.push([label, Number.isFinite(occ) ? occ : -1, Number.isFinite(num) ? num : -1]);\n return;\n }\n if (node.tagName === 'cross-chunk-link') linkLabels.push(label);\n else imageLabels.push(label);\n // `resolveDef` falls back to the placeholder's BAKED `localUrl` /\n // `localTitle` when the registry has no canonical def — and the\n // fingerprint writes `lr:LABEL=null` in exactly that case, a constant,\n // so the rendered href could change while the key did not. Carry the\n // baked pair SEPARATELY (never folded into the label, which must stay\n // resolvable by `resolveLinkDef` so a canonical change is still\n // tracked) — the same argument that put `footnote-sup`'s occurrence in\n // the footprint; link/image were left asymmetric (oracle review, 2.5.0).\n if (typeof props.localUrl === 'string') {\n bakedDefs.push([label, props.localUrl, typeof props.localTitle === 'string' ? props.localTitle : null]);\n }\n return;\n }\n // Standalone mode bakes footnote numbering into plain marks; their ctx\n // is the whole-document globalCtx, so the flag alone is enough.\n if (node.tagName === 'a' && props.dataFootnoteRef !== undefined) hasReference = true;\n });\n return { hasReference, refLabels, linkLabels, imageLabels, local, bakedDefs, hasFootnoteSection };\n}\n","import type { Root as MdastRoot, RootContent as MdastNode } from 'mdast';\nimport type { Root as HastRoot } from 'hast';\nimport { visit } from 'unist-util-visit';\nimport { buildBlocks, type BuildBlocksOptions, type BuildBlocksResult, type RenderItem } from './blockPlan';\n\nexport type BlockPlanner = (\n mdast: MdastRoot,\n hast: HastRoot,\n source: string,\n options?: BuildBlocksOptions\n) => BuildBlocksResult;\n\nconst UNPAIRED_TYPES = new Set(['html', 'definition', 'footnoteDefinition']);\n\n/** Reuse the engine's retained prefix plans, including reference blocks.\n * Reference prefixes require full-document context when planning the tail.\n * Raw HTML and definition regions retain complete planning: their ownership\n * need not be one-to-one. Identity checks, not source-text\n * guesses, establish which trees the incremental engine actually retained.\n * Top-level traversal remains O(blocks); reference context still visits the\n * full mdast when needed, while per-block HAST planning is tail-only. */\nexport function createBlockPlanner(): BlockPlanner {\n let previous:\n | {\n mdast: MdastRoot;\n hast: HastRoot;\n source: string;\n result: BuildBlocksResult;\n phantoms: BuildBlocksOptions['phantomFootnoteLabels'];\n }\n | undefined;\n const eligible = new WeakMap<MdastNode, boolean>();\n const isEligible = (node: MdastNode) => {\n let result = eligible.get(node);\n if (result === undefined) {\n result = true;\n visit(node, (child) => {\n if (UNPAIRED_TYPES.has(child.type)) result = false;\n });\n eligible.set(node, result);\n }\n return result;\n };\n return (mdast: MdastRoot, hast: HastRoot, source: string, options: BuildBlocksOptions = {}): BuildBlocksResult => {\n let m = 0;\n let h = 0;\n let reusedContext = false;\n if (previous && source.startsWith(previous.source) && options.phantomFootnoteLabels === previous.phantoms) {\n const prevHast = previous.hast.children;\n const prevPlan = previous.result.plan;\n /** Identity-retained whitespace text between two block outputs. */\n const isRetainedGap = (i: number): boolean => {\n const gap = hast.children[i];\n return gap === prevHast[i] && gap.type === 'text' && !/\\S/.test(gap.value);\n };\n while (m < mdast.children.length) {\n const node = mdast.children[m];\n const el = hast.children[h];\n const item = prevPlan[h];\n if (\n node !== previous.mdast.children[m] ||\n el !== prevHast[h] ||\n !el ||\n el.type !== 'element' ||\n !item ||\n item.kind !== 'block' ||\n item.info.startOffset !== node.position?.start.offset ||\n !isEligible(node)\n )\n break;\n // One mdast node can own several top-level hast siblings: an\n // image-only paragraph with two images on one line is unwrapped to\n // `img, \" \", img`, and every sibling's block info carries the\n // paragraph's start offset. Consume all of them with the node —\n // leaving one behind put its offset inside the reused prefix range,\n // where the tail's mdast slice has no counterpart (the dev build\n // threw, production planned an uncached inline item and remounted\n // the image on every frame). A sibling that is not identity-retained\n // means the node's output was not carried over whole: give the node\n // back and stop the prefix here.\n const startOffset = item.info.startOffset;\n let next = h + 1;\n let complete = true;\n for (;;) {\n let probe = next;\n while (probe < hast.children.length && isRetainedGap(probe)) probe++;\n const sibling = prevPlan[probe];\n if (!sibling || sibling.kind !== 'block' || sibling.info.startOffset !== startOffset) break;\n if (hast.children[probe] !== prevHast[probe]) {\n complete = false;\n break;\n }\n next = probe + 1;\n }\n if (!complete) break;\n reusedContext ||= item.info.hasReference;\n m++;\n h = next;\n while (h < hast.children.length && isRetainedGap(h)) h++;\n }\n // The tail is planned against `mdast.children.slice(m)`, so every tail\n // block must start at or past the reused prefix's end. An output node\n // that still points inside the prefix (a sibling shape the loop above\n // does not model) cannot be attributed from the tail slice; plan the\n // whole document instead, so the dev-only \"no mdast counterpart\"\n // invariant keeps firing only for positions synthesized outside the\n // source.\n if (m > 0) {\n const prefixEnd = mdast.children[m - 1].position?.end.offset ?? 0;\n for (let i = h; i < hast.children.length; i++) {\n const child = hast.children[i];\n const at = child.type === 'element' ? child.position?.start.offset : undefined;\n if (at !== undefined && at < prefixEnd) {\n m = 0;\n h = 0;\n reusedContext = false;\n break;\n }\n }\n }\n }\n let result: BuildBlocksResult;\n if (m > 0 && previous) {\n const tail = buildBlocks(\n { ...mdast, children: mdast.children.slice(m) },\n { ...hast, children: hast.children.slice(h) },\n source,\n reusedContext ? { ...options, contextMdast: options.contextMdast ?? mdast } : options\n );\n const prefix = previous.result.plan.slice(0, h);\n const prefixBlocks = prefix.filter(\n (item): item is Extract<RenderItem, { kind: 'block' }> => item.kind === 'block'\n );\n result = {\n ...tail,\n plan: [\n ...prefix,\n ...tail.plan.map((item) =>\n /^inline-i\\d+$/.test(item.key) ? { ...item, key: `inline-i${Number(item.key.slice(8)) + h}` } : item\n ),\n ],\n blocks: [...prefixBlocks.map((item) => item.info), ...tail.blocks],\n blockHasts: [...prefixBlocks.map((item) => item.el), ...tail.blockHasts],\n };\n } else result = buildBlocks(mdast, hast, source, options);\n previous = { mdast, hast, source, result, phantoms: options.phantomFootnoteLabels };\n return result;\n };\n}\n","import type { Root as MdastRoot } from 'mdast';\nimport type { Root as HastRoot, ElementContent as HastElementContent } from 'hast';\nimport { extractContributions, extractDefBodiesFromHast, footnoteSafeId, type ChunkData } from '@ai-markdown/engine';\n\n/** Minimal write capability; registration and cleanup remain with the adapter. */\nexport interface ContributionRegistry {\n contributeChunkData(symbol: symbol, data: ChunkData): void;\n}\n\nexport interface ContributionOptions {\n pipeline: { mdast: MdastRoot; hast: HastRoot };\n ownLabels: { footnoteLabels: Set<string>; linkLabels: Set<string> };\n registry: ContributionRegistry | null;\n targetPhantoms: { missingFootnotes: Set<string>; missingLinks: Set<string> };\n sym: symbol | null;\n clobberPrefix: string;\n /** Stable identity tuple of parse inputs. Source equality alone cannot\n * validate definition bodies after a plugin or schema change. */\n chain: readonly unknown[];\n}\n\nexport interface ContributionSession {\n commit(options: ContributionOptions): void;\n}\n\n/** Create one contribution publisher per mounted chunk.\n * Call commit only in the host's commit/effect phase, after registration.\n * Constructing a session does not register or publish anything. A discarded\n * render must never call commit. The host owns registration and cleanup.\n */\nexport function createContributionSession(): ContributionSession {\n let lastContribution: {\n registry: ContributionRegistry;\n symbol: symbol;\n fp: string;\n chain: readonly unknown[];\n } | null = null;\n return {\n commit({ pipeline, ownLabels, registry, targetPhantoms, sym, clobberPrefix, chain }: ContributionOptions): void {\n if (!registry || !sym) return;\n const refs: ChunkData['refs'] = [];\n // Collect def metadata first so the fingerprint compares only cheap\n // fields. bodyHast is sourced from the post-pipeline hast (not from a\n // bare mdast→hast walk) so def bodies inside the cross-chunk aggregate\n // render with full plugin output (math, raw HTML, defLists, …).\n const defMeta = new Map<string, { identifier: string; sourceIdentifier: string; contentSource: string }>();\n const linkDefs = new Map<string, { identifier: string; url: string; title?: string }>();\n // Link-definition URLs enter the registry RAW: the render-time\n // `resolveCrossChunkReference` gate in the placeholders is the single point\n // of enforcement (schema + hash rebasing + urlTransform, per attribute\n // key). A contribute-time pre-pass used to collapse a blocked URL to ''\n // — indistinguishable from a legal empty destination — and applied a\n // rewriting urlTransform twice (v2.4.2 review P1-4).\n for (const node of extractContributions(pipeline.mdast, {\n phantomFootnoteLabels: targetPhantoms.missingFootnotes,\n })) {\n if (node.kind === 'ref') {\n // `nestedIn` marks a footnote ref inside a definition body: it is\n // numbered after the flow refs but never counted as an occurrence.\n refs.push({\n label: node.label,\n kind: node.refKind,\n referenceType: node.referenceType,\n ...(node.nestedIn !== undefined ? { nestedIn: node.nestedIn } : {}),\n });\n } else if (node.kind === 'fnDef') {\n defMeta.set(node.label, {\n identifier: node.label,\n sourceIdentifier: node.sourceIdentifier,\n contentSource: node.content,\n });\n } else if (node.kind === 'linkDef') {\n linkDefs.set(node.label, { identifier: node.label, url: node.url, title: node.title });\n }\n }\n const fp = JSON.stringify({\n r: refs,\n d: Array.from(defMeta.entries()).map(([k, v]) => [k, v.sourceIdentifier, v.contentSource]),\n l: Array.from(linkDefs.entries()).map(([k, v]) => [k, v.url, v.title ?? '']),\n ofn: Array.from(ownLabels.footnoteLabels).sort(),\n ol: Array.from(ownLabels.linkLabels).sort(),\n // Include targetPhantoms in the fingerprint: a phantom→resolved\n // transition (another chunk publishes a def for a label this chunk\n // references inside one of its OWN def bodies) changes the rendered\n // hast — the `<cross-chunk-link>` / `<cross-chunk-image>` placeholder\n // disappears and a real `<a>` / `<img>` takes its place — without\n // touching this chunk's refs / defMeta / linkDefs / ownLabels. Without\n // including the phantom snapshot in the fingerprint, the fp check\n // would short-circuit and the registry would keep stale bodyHast\n // forever, leaving the aggregate footer rendering the placeholder\n // long after the label was resolved.\n tpfn: Array.from(targetPhantoms.missingFootnotes).sort(),\n tpl: Array.from(targetPhantoms.missingLinks).sort(),\n });\n const last = lastContribution;\n if (\n last?.registry === registry &&\n last.symbol === sym &&\n last.fp === fp &&\n last.chain.length === chain.length &&\n last.chain.every((dep, i) => dep === chain[i])\n ) {\n return;\n }\n // Fingerprint changed → harvest bodyHast from the post-pipeline hast\n // and publish. Missing entries are defensive: after allocation,\n // preserveForBodyHarvest keeps real local defs in the synthetic footer\n // even when visible orphan rendering is disabled.\n // Harvested bodies are keyed by the encoded `<li id>` fragment, which\n // is `footnoteSafeId(sourceIdentifier)` for the matching definition.\n // Both sides derive the key from the same identifier through the same\n // encoder, so a label that contains a valid percent-escape (`[^a%41]`,\n // minted verbatim as `fn-a%41`) matches too; decoding the id instead\n // would turn it into `aA` and leave the aggregate `<li>` empty.\n const bodiesBySafeId = extractDefBodiesFromHast(pipeline.hast, clobberPrefix);\n const defs = new Map<\n string,\n { identifier: string; sourceIdentifier: string; contentSource: string; bodyHast: HastElementContent[] }\n >();\n for (const [label, meta] of defMeta) {\n defs.set(label, {\n identifier: meta.identifier,\n sourceIdentifier: meta.sourceIdentifier,\n contentSource: meta.contentSource,\n bodyHast: (bodiesBySafeId.get(footnoteSafeId(meta.sourceIdentifier)) ?? []) as HastElementContent[],\n });\n }\n lastContribution = { registry, symbol: sym, fp, chain };\n registry.contributeChunkData(sym, {\n refs,\n defs,\n linkDefs,\n ownFootnoteLabels: ownLabels.footnoteLabels,\n ownLinkLabels: ownLabels.linkLabels,\n });\n },\n };\n}\n","/**\n * Turn-taking coordinator for smooth-streaming chunks that share a\n * `documentId` under `<AIMarkdownDocuments>`.\n *\n * Each `useDocumentSmoothStream` chunk registers here in mount order and\n * reports a sticky `done` (source stopped AND reveal drained). A chunk that\n * mounted with empty content stays gated until every REGISTERED chunk\n * earlier in the order is done — so the chunks read as one typewriter:\n * chunk N reveals completely, then chunk N+1 starts.\n *\n * The structure deliberately mirrors `documentRegistry`'s proven patterns\n * (chunkId-keyed refcount with microtask-deferred reclaim for Strict Mode,\n * monotonic version + microtask-coalesced subscriber fanout, `onEmpty`\n * container eviction) without touching the registry itself: the def\n * registry and this coordinator share only the `documentId` scope. Chunks\n * that never register (non-smooth chunks, opted-out chunks) simply do not\n * exist here and can never block a successor; an unmounting chunk leaves\n * the order, so a virtualized-away predecessor releases its successors by\n * construction.\n *\n * `lastProgressAt` is the reveal-progress heartbeat behind the dev-only\n * stuck-flag warning. Writes to it intentionally do NOT bump `version` and\n * do NOT fan out — stamping happens once per reveal frame, and a fanout to\n * every sibling chunk per frame would be a performance bug. Nothing\n * subscribes to it; the pending chunk's warning timer reads it on demand.\n *\n * @module components/smoothStream/coordinator\n */\n\nexport interface SmoothCoordinator {\n /** Chunk mount-order chunkId list. **Read-only** — mutation corrupts the\n * release predicate for every successor. */\n readonly order: readonly string[];\n /** Chunk IDs that have reported done. Sticky: entries are only removed\n * when the chunk unregisters. **Read-only.** */\n readonly done: ReadonlySet<string>;\n /** Reveal-progress heartbeat, ms timestamps from the reporting chunk's\n * clock. Written WITHOUT a version bump (see module doc). */\n readonly lastProgressAt: ReadonlyMap<string, number>;\n /** Monotonic counter bumped by every gating-relevant mutation\n * (register / unregister / done transition). */\n readonly version: number;\n\n subscribe(cb: () => void): () => void;\n\n /** Allocate (or refcount-reuse, for Strict Mode remounts) this chunk's\n * slot at the current end of the mount order. */\n register(chunkId: string): void;\n /** Refcount decrement with microtask-deferred reclaim; the last release\n * removes the chunk from the order (waking successors) and, when the\n * coordinator empties, fires the container's `onEmpty`. */\n release(chunkId: string): void;\n /** Sticky, idempotent: the first call per chunk bumps the version; the\n * transition is never retracted while the chunk stays mounted (a\n * predecessor that un-finishes must not re-gate successors). */\n markDone(chunkId: string): void;\n /** Heartbeat stamp — no version bump, no fanout. */\n stampProgress(chunkId: string, at: number): void;\n /** True when every registered chunk BEFORE `chunkId` in the order has\n * reported done. Unregistered predecessors don't exist here, so they\n * never block. A `chunkId` that is not (yet) registered itself is\n * treated as sitting AFTER every registered chunk — a pre-registration\n * query must not release a chunk whose predecessors are still\n * revealing. */\n isReleased(chunkId: string): boolean;\n /** The earliest not-done registered chunk before `chunkId`, or null.\n * Read by the dev-only stuck-flag warning timer. */\n earliestBlockerOf(chunkId: string): string | null;\n}\n\n/** Widened internal view (parallel to `RegistryInternal`): implementation-\n * private fields, exported for tests but not from the package barrel. */\nexport interface SmoothCoordinatorInternal extends SmoothCoordinator {\n _refcounts: Map<string, number>;\n _subscribers: Set<() => void>;\n _notifyScheduled: boolean;\n _notify(): void;\n}\n\n/**\n * Construct a coordinator. `onEmpty` fires synchronously from inside the\n * deferred-release microtask when the last chunk just left, so the\n * container can evict its `documentId → coordinator` map entry while the\n * state is quiescent (same contract as the registry's `onEmpty`).\n */\nexport function createSmoothCoordinator(onEmpty?: () => void): SmoothCoordinatorInternal {\n const order: string[] = [];\n const done = new Set<string>();\n const lastProgressAt = new Map<string, number>();\n\n const coord: SmoothCoordinatorInternal = {\n order,\n done,\n lastProgressAt,\n version: 0,\n _refcounts: new Map(),\n _subscribers: new Set(),\n _notifyScheduled: false,\n\n register(chunkId: string): void {\n const count = this._refcounts.get(chunkId) ?? 0;\n this._refcounts.set(chunkId, count + 1);\n if (count === 0 && !order.includes(chunkId)) {\n order.push(chunkId);\n this._notify();\n }\n },\n\n release(chunkId: string): void {\n const count = this._refcounts.get(chunkId);\n if (count === undefined) return;\n if (count > 1) {\n this._refcounts.set(chunkId, count - 1);\n return;\n }\n this._refcounts.set(chunkId, 0);\n queueMicrotask(() => {\n if (this._refcounts.get(chunkId) !== 0) return; // Strict Mode revived it\n this._refcounts.delete(chunkId);\n const idx = order.indexOf(chunkId);\n if (idx !== -1) order.splice(idx, 1);\n done.delete(chunkId);\n lastProgressAt.delete(chunkId);\n // Successors gated behind this chunk may now be releasable.\n this._notify();\n if (order.length === 0 && onEmpty) onEmpty();\n });\n },\n\n markDone(chunkId: string): void {\n if (done.has(chunkId)) return; // idempotent — repeat reports don't bump version\n done.add(chunkId);\n this._notify();\n },\n\n stampProgress(chunkId: string, at: number): void {\n lastProgressAt.set(chunkId, at);\n },\n\n isReleased(chunkId: string): boolean {\n for (const id of order) {\n if (id === chunkId) return true;\n if (!done.has(id)) return false;\n }\n // Not registered (yet): treated as after every registered chunk, so\n // reaching here means all of them are done.\n return true;\n },\n\n earliestBlockerOf(chunkId: string): string | null {\n for (const id of order) {\n if (id === chunkId) return null;\n if (!done.has(id)) return id;\n }\n return null;\n },\n\n subscribe(cb: () => void): () => void {\n this._subscribers.add(cb);\n return () => {\n this._subscribers.delete(cb);\n };\n },\n\n _notify(): void {\n (this as { version: number }).version++;\n if (this._notifyScheduled) return;\n this._notifyScheduled = true;\n queueMicrotask(() => {\n this._notifyScheduled = false;\n for (const cb of [...this._subscribers]) cb();\n });\n },\n };\n return coord;\n}\n\n/** Outcome of one stuck-flag warning timer firing (dev-only diagnostics). */\nexport type GateWarnVerdict = 'warn' | 'rearm' | 'clear';\n\n/**\n * The warn / re-arm / clear decision behind the dev-only stuck-flag\n * warning, extracted as a pure function so the judgment is unit-testable\n * in node — the `setTimeout` wiring in `useDocumentSmoothStream` reduces\n * to \"call this when the timer fires\".\n *\n * - `'clear'`: no blocker left — the release is propagating; stop.\n * - `'rearm'`: the earliest blocker showed reveal progress within the\n * threshold — a slow model still emitting, not a stuck flag; check\n * again later.\n * - `'warn'`: the blocker has made no visible progress for a full\n * threshold while this chunk stayed gated — the predecessor's\n * `streaming` flag is likely stuck.\n */\nexport function evaluateGateWarn(\n coordinator: SmoothCoordinator,\n chunkId: string,\n nowMs: number,\n thresholdMs: number\n): GateWarnVerdict {\n const blocker = coordinator.earliestBlockerOf(chunkId);\n if (!blocker) return 'clear';\n const progressedAt = coordinator.lastProgressAt.get(blocker) ?? 0;\n return nowMs - progressedAt < thresholdMs ? 'rearm' : 'warn';\n}\n","import { createSmoothCoordinator as createInternalCoordinator, type SmoothCoordinator } from './smoothCoordinator';\n\n/** Create one turn-taking scope.\n * Why: sibling chunks coordinate reveal without a framework or DOM dependency.\n * Recommended pattern: register after mount, report sticky completion, release on unmount.\n * Footguns: registration order controls reveal; completion cannot be retracted.\n */\nexport function createSmoothCoordinator(onEmpty?: () => void): SmoothCoordinator {\n return createInternalCoordinator(onEmpty);\n}\n","/**\n * Source-tail classification for the streaming cursor.\n *\n * The cursor's DOM walk cannot know whether the source tail is in the body\n * or inside a footnote definition — mdast-util-to-hast relocates every\n * definition into the synthetic end-of-document footer, decoupling the\n * footer's DOM position from source position. This module answers the\n * question at the mdast level, where micromark has already made every\n * grammar decision (lazy continuation lines, container nesting, blank-line\n * attachment, label escapes): the renderer derives a signal per frame from\n * the tree it is about to render and stamps it on a DOM marker the cursor\n * shell reads in the same commit.\n *\n * Deliberately NOT a text scan of the trailing lines: a hand-rolled\n * back-walk would be a third parallel grammar in the repo and provably\n * mis-classifies lazy continuations (`[^1]: line\\nline2` keeps line2 in\n * the definition with zero indentation), resurfacing the exact bug this\n * exists to fix.\n *\n * @module components/streamingCursor/tailSignal\n */\n\nimport type { Parent, Root, RootContent } from 'mdast';\n\nexport type TailSignal =\n /** The source tail is inside a footnote definition — its text renders in\n * the footnote footer's `<li>` for `identifier` (mdast's case-folded\n * form), not at the body tail. */\n | { kind: 'footnote-def'; identifier: string }\n /** The source tail is inside a link-reference definition — it renders\n * nothing at all; the cursor should hide rather than point at stale\n * body text. */\n | { kind: 'invisible-def' }\n /** The source tail is ordinary body content — the DOM-tail heuristic is\n * correct as-is. */\n | null;\n\n/** Node types the descent passes THROUGH looking for the tail definition.\n * Definitions legally nest inside all of these (`> [^a]: x`,\n * `- [^a]: x`, and a def inside another def's body), and their rendered\n * text still relocates to the footer — so the descent is required for\n * correctness, not an optimization. Nothing else can contain a def. */\nconst DESCEND_THROUGH = new Set(['blockquote', 'list', 'listItem', 'footnoteDefinition']);\n\n/**\n * Classify the source tail of `mdast`.\n *\n * @param mdast - The tree the renderer is about to render (post-pipeline\n * parse output; the incremental path's spliced tree carries correct\n * positions on real nodes).\n * @param contentLength - Length of the PREPROCESSED content string the\n * renderer parsed (not the caller's raw prop). Phantom definitions are\n * injected as an appended suffix before parsing, so every node whose\n * start offset sits at or past the real content's end is a phantom — the\n * scan walks backwards past ALL of them (a real trailing def plus\n * several phantoms is a normal shape) and classifies the first real\n * node.\n */\nexport function deriveTailSignal(mdast: Root, contentLength: number): TailSignal {\n const children = mdast.children;\n let index = children.length - 1;\n while (index >= 0) {\n const offset = children[index].position?.start?.offset;\n if (offset !== undefined && offset >= contentLength) {\n index--;\n continue;\n }\n break;\n }\n if (index < 0) return null;\n\n let node: RootContent = children[index];\n let deepestFootnoteDef: { identifier: string } | null = null;\n for (;;) {\n if (node.type === 'footnoteDefinition') deepestFootnoteDef = node;\n if (node.type === 'definition') {\n // A link-reference definition renders nothing wherever it sits —\n // including as the last child of a footnote definition's body.\n return { kind: 'invisible-def' };\n }\n if (!DESCEND_THROUGH.has(node.type)) break;\n const kids = (node as Parent).children;\n if (!kids || kids.length === 0) break;\n node = kids[kids.length - 1] as RootContent;\n }\n // The DEEPEST definition on the last-child chain is where text is\n // streaming (a def nested in another def's body renders in ITS OWN li).\n return deepestFootnoteDef ? { kind: 'footnote-def', identifier: deepestFootnoteDef.identifier } : null;\n}\n","/**\n * Structural clone of a hast subtree for render-time use.\n *\n * Copies exactly what the render-time mutators touch — every node object,\n * its `children` array (the allow/disallow filters splice it), and its\n * `properties` object (`urlTransform` overwrites `href`/`src`) — while\n * SHARING `position`, arbitrary nested plugin data, string values, and individual property values\n * by reference. This is both cheaper than a JSON round-trip (no string\n * intermediate; `position` objects, typically over half of a serialized\n * hast, are never materialized) and exact for any tree the pipeline\n * produces (no lossy JSON semantics to reason about).\n *\n * @module components/cloneHastForRender\n */\n\nimport type { Nodes as HastNodes } from 'hast';\n\n/** See module docs. Safe for any hast node union member. */\nexport function cloneHastForRender<T extends HastNodes>(node: T): T {\n const copy = { ...node } as T;\n const withChildren = copy as { children?: HastNodes[] };\n if (Array.isArray(withChildren.children)) {\n withChildren.children = withChildren.children.map((child) => cloneHastForRender(child));\n }\n const withProperties = copy as { properties?: Record<string, unknown> };\n if (withProperties.properties) {\n withProperties.properties = { ...withProperties.properties };\n }\n // buildTransform records original URL values in data.originalUrls. Both\n // containers must belong to this render even when plugins supplied data.\n if (copy.data) {\n copy.data = { ...copy.data };\n const data = copy.data as { originalUrls?: Record<string, unknown> };\n if (data.originalUrls) data.originalUrls = { ...data.originalUrls };\n }\n return copy;\n}\n","/** Assemble document footnotes as HAST without framework nodes or DOM access. */\nimport type { Element as HastElement, ElementContent as HastElementContent } from 'hast';\nimport { footnoteSafeId, lastMeaningfulIdx, type Registry } from '@ai-markdown/engine';\nimport { cloneHastForRender } from './cloneHastForRender';\n\ninterface OrderedDef {\n /** Uppercase-normalized label, used to query `registry.getRefsForLabel`. */\n normalizedLabel: string;\n /** mdast's case-folded identifier — used in HTML ids so the aggregate\n * footer's `<li id>` matches the inline sup's anchor href exactly. */\n sourceIdentifier: string;\n bodyHast: HastElementContent[];\n n: number | null;\n withBackref: boolean;\n}\n\n/**\n * Build a backref anchor for a specific occurrence of a footnote ref.\n * Matches mdast-util-to-hast's default `defaultFootnoteBackContent`:\n * - Anchor content is always the text `↩`.\n * - When occurrence > 1, a child `<sup>${N}</sup>` is appended so the\n * digit renders as a superscript (visually `↩²`, `↩³`, …) instead of\n * a flat string like `↩2`.\n * - The 1st occurrence href is bare `#fnref-${id}`; the N-th (N > 1)\n * href is `#fnref-${id}-${N}` to match `FootnoteSupNumber`'s\n * suffix-disambiguated id on the inline sup side.\n */\nfunction buildBackref(href: string, occurrence: number, globalNumber: number): HastElement {\n const children: HastElement['children'] = [{ type: 'text', value: '↩' }];\n if (occurrence > 1) {\n children.push({\n type: 'element',\n tagName: 'sup',\n properties: {},\n children: [{ type: 'text', value: String(occurrence) }],\n });\n }\n const ariaLabel =\n occurrence === 1 ? `Back to reference ${globalNumber}` : `Back to reference ${globalNumber}-${occurrence}`;\n return {\n type: 'element',\n tagName: 'a',\n properties: {\n href,\n dataFootnoteBackref: '',\n className: ['data-footnote-backref'],\n ariaLabel,\n },\n children,\n };\n}\n\nexport function buildAggregateTree(\n registry: Registry,\n clobberPrefix: string,\n preserveOrphanReferences = false\n): HastElement | null {\n // Walk chunkOrder; collect every FOOTNOTE label that is referenced\n // somewhere — in flow text, or inside another footnote's body (a nested\n // ref, `nestedIn` set) — and order the entries by `registry.globalNumber`,\n // the same selector the inline `<sup>` marks read. The registry numbers\n // nested-only footnotes after every flow reference, in the order the\n // bodies render, which is where a standalone footer lists them; walking\n // refs in source order alone would put such a footnote at the position of\n // the body that mentions it. Link/image refs share the `refs` array but\n // belong to a disjoint namespace — they must be skipped here, otherwise a\n // link-ref to a label sharing text with a footnote-def would cause a\n // duplicate `<li>` in the aggregate footer.\n const seen = new Set<string>();\n const ordered: OrderedDef[] = [];\n for (const sym of registry.chunkOrder) {\n const data = registry.chunkData.get(sym);\n if (!data) continue;\n for (const ref of data.refs) {\n if (ref.kind !== 'footnote') continue;\n if (seen.has(ref.label)) continue;\n seen.add(ref.label);\n const canonicalSym = registry.canonicalFootnoteFor(ref.label);\n if (!canonicalSym) continue;\n const def = registry.chunkData.get(canonicalSym)?.defs.get(ref.label);\n if (!def) continue;\n const n = registry.globalNumber(ref.label);\n if (n === null) continue;\n // Fall back to the uppercase-normalized label when sourceIdentifier is\n // absent (older fixtures); production data always carries the field.\n const sourceIdentifier = def.sourceIdentifier ?? ref.label;\n ordered.push({\n normalizedLabel: ref.label,\n sourceIdentifier,\n bodyHast: def.bodyHast ?? [],\n n,\n withBackref: true,\n });\n }\n }\n ordered.sort((a, b) => (a.n ?? 0) - (b.n ?? 0));\n\n if (preserveOrphanReferences) {\n for (const sym of registry.chunkOrder) {\n const data = registry.chunkData.get(sym);\n if (!data) continue;\n for (const [label, def] of data.defs) {\n if (seen.has(label)) continue;\n if (registry.canonicalFootnoteFor(label) !== sym) continue;\n seen.add(label);\n ordered.push({\n normalizedLabel: label,\n sourceIdentifier: def.sourceIdentifier ?? label,\n bodyHast: def.bodyHast ?? [],\n n: null,\n withBackref: false,\n });\n }\n }\n }\n\n if (ordered.length === 0) return null;\n\n const liElements: HastElement[] = ordered.map(({ normalizedLabel, sourceIdentifier, bodyHast, n, withBackref }) => {\n // Same id encoding as mdast-util-to-hast's footer / the marks (percent-\n // encoded non-ASCII etc.); the body harvest keys by this same fragment\n // and the cursor anchor decodes it through `sourceIdFromFootnoteLiId`.\n const safeId = footnoteSafeId(sourceIdentifier);\n // Assembly-time clone: the backref logic below pushes anchors into these\n // children, which must never mutate the registry-held `bodyHast`.\n const liChildren = bodyHast.map((c) => cloneHastForRender(c));\n if (withBackref) {\n // Emit one backref anchor per global occurrence of this label. The\n // first href uses the bare `fnref-${id}` (matches FootnoteSupNumber's\n // unsuffixed first ref); the Nth (N > 1) uses `fnref-${id}-${N}`\n // (matches FootnoteSupNumber's `-N` suffix on subsequent refs).\n //\n // A single space text node precedes each anchor so adjacent backrefs\n // don't run together visually (and so the first backref doesn't butt\n // up against the def body's trailing text). This mirrors what\n // mdast-util-to-hast's default footer naturally produces: the trailing\n // newline in the def's source becomes a text \" \" between body and\n // backref, plus an explicit space separator between consecutive\n // backrefs (see mdast-util-to-hast `lib/footer.js`).\n const totalRefs = registry.getRefsForLabel(normalizedLabel);\n // Match mdast-util-to-hast's append rule: if the TAIL of the def\n // body (skipping wrap-emitted `\\n`) is `<p>`, push backrefs into\n // it; otherwise push them at the end of the `<li>` directly. The\n // previous logic searched for the last `<p>` anywhere in the body,\n // which mis-placed the backref BEFORE a trailing `<pre>` /\n // `<blockquote>` / etc. for multi-block defs.\n const tailIdx = lastMeaningfulIdx(liChildren);\n const tail = tailIdx !== -1 ? liChildren[tailIdx] : null;\n const dest =\n tail && tail.type === 'element' && (tail as HastElement).tagName === 'p' ? (tail as HastElement) : null;\n const appended: HastElementContent[] = [];\n for (let i = 1; i <= Math.max(totalRefs, 1); i++) {\n appended.push({ type: 'text', value: ' ' });\n const href = i === 1 ? `#${clobberPrefix}fnref-${safeId}` : `#${clobberPrefix}fnref-${safeId}-${i}`;\n appended.push(buildBackref(href, i, n ?? 0));\n }\n if (dest) {\n dest.children = [...dest.children, ...appended];\n } else if (tailIdx !== -1) {\n // Splice after the meaningful tail so any subsequent trailing\n // whitespace (`\\n` from state.wrap) survives at the end of the\n // <li>, matching mdast-util-to-hast's post-wrap shape:\n // [..., <tail-block>, ...backrefs, \"\\n\"]\n liChildren.splice(tailIdx + 1, 0, ...appended);\n } else {\n liChildren.push(...appended);\n }\n }\n return {\n type: 'element',\n tagName: 'li',\n properties: {\n id: `${clobberPrefix}fn-${safeId}`,\n // Stringified: @types/hast (≥3.0.5) types li's `value` as string;\n // React serializes value={3} and value=\"3\" to identical markup.\n ...(n !== null ? { value: String(n) } : {}),\n },\n children: liChildren,\n };\n });\n\n return {\n type: 'element',\n tagName: 'section',\n properties: {\n className: ['footnotes'],\n dataFootnotes: true,\n // a11y: name the section landmark so screen readers still announce it\n // even though the visible \"Footnotes\" h2 is omitted in coordinated mode.\n ariaLabel: 'Footnotes',\n },\n children: [\n // Conventional separator above the footnote list. The default\n // mdast-util-to-hast footer doesn't emit one (GitHub renders the\n // separation via CSS `border-top` on `.footnotes`); we render a literal\n // `<hr>` so the visual break is preserved without sanitize-friendly CSS.\n {\n type: 'element',\n tagName: 'hr',\n properties: {},\n children: [],\n },\n {\n type: 'element',\n tagName: 'ol',\n properties: {},\n children: liElements,\n },\n ],\n };\n}\n","/** Per-consumer parse state and full/incremental fallback policy.\n * The host decides whether a next frame is expected (false for SSR), owns\n * this session, and resets it when render policy invalidates retained trees.\n * Parsing does not register chunks or publish document contributions.\n * Fallback order: incremental → full pipeline → plain-text frame, the last\n * step ONLY for the engine's raw-depth signal (see plainTextTrees); any\n * other full-pipeline throw, a consumer plugin's included, escapes `parse`.\n */\nimport {\n EngineRawHtmlDepthError,\n advanceIncrementalParse,\n buildPhantomSuffix,\n phantomSuffixCloser,\n parseStage,\n transformStage,\n type IncrementalParseState,\n type PipelineOptions,\n type AdvanceOptions,\n} from '@ai-markdown/engine';\n\nimport type { Root as MdastRoot } from 'mdast';\nimport type { Root as HastRoot } from 'hast';\n\nexport interface PipelineTrees {\n mdast: MdastRoot;\n hast: HastRoot;\n}\nexport interface PipelineSession {\n /** Drop retained incremental state. Does not dispose host subscriptions. */\n reset(): void;\n /** Read-only borrowed trees: clone before destructive rendering. */\n parse(options: PipelineFrameOptions): PipelineTrees;\n}\n\ntype RemarkRehypeOptions = NonNullable<PipelineOptions['remarkRehypeOptions']>;\nexport interface PipelineFrameOptions {\n content: string;\n targetPhantoms: { missingFootnotes: Set<string>; missingLinks: Set<string> };\n remarkPlugins: PipelineOptions['remarkPlugins'];\n rehypePlugins: PipelineOptions['rehypePlugins'];\n remarkRehypeOptions: PipelineOptions['remarkRehypeOptions'];\n handlers?: RemarkRehypeOptions['handlers'];\n preserveForBodyHarvest: boolean;\n documentId: string;\n provenance: string;\n /** False for a one-shot server render; no browser-global probe is needed. */\n incrementalParse: boolean;\n defListEnabled: boolean;\n /** Whether `remarkPlugins` parses GFM task-list items. Default `false`\n * (the boundary scanner keeps every `[x]` as reference taint). The\n * adapters pass `true` because they build the chain with\n * `buildCoreRemarkPlugins`, which always includes remark-gfm. A box\n * whose paragraph a `$$` opener closed is released only when\n * `mathFlow` is `true` as well. */\n gfmTaskListItems?: boolean;\n /** Whether `remarkPlugins` parses `$$` flow math (remark-math). Passed\n * through to the engine in all three states: `true` declares it (a\n * `$$` region is verbatim math), `false` declares its absence (`$$`\n * lines are paragraph text), and omitted leaves it unknown — the\n * scanner then takes the union of both grammars, which is correct for\n * either chain and freezes less around `$$`. The adapters pass `true`\n * because `buildCoreRemarkPlugins` always includes remark-math; a\n * caller with its own chain should declare the real value and may\n * leave it out when unsure. */\n mathFlow?: boolean;\n measure?: AdvanceOptions['measure'];\n}\nconst unmeasured: NonNullable<AdvanceOptions['measure']> = (_stage, fn) => fn();\n\n/** Line/column/offset of the end of `text`, in the parser's convention\n * (1-based line and column, 0-based offset). Counts what micromark counts:\n * `\\n`, `\\r\\n` and a lone `\\r` are one line ending each. */\nfunction endPoint(text: string): { line: number; column: number; offset: number } {\n let line = 1;\n let lineStart = 0;\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n if (code === 10 /* \\n */ || (code === 13 /* \\r */ && text.charCodeAt(i + 1) !== 10)) {\n line += 1;\n lineStart = i + 1;\n }\n }\n return { line, column: text.length - lineStart + 1, offset: text.length };\n}\n\n/**\n * Last-resort trees for a frame whose FULL parse threw the engine's\n * `EngineRawHtmlDepthError`: the whole source as one plain-text paragraph,\n * positions covering the entire content so the planner, cache keys and\n * custom components see well-formed nodes. The text is a hast text node — it\n * renders escaped, never as markup.\n *\n * Why this exists: the incremental path already falls back to the full\n * pipeline, but the full pipeline itself can throw — a few thousand nested\n * raw `<div>` tags exhaust the call stack of a recursive walker after the\n * raw-HTML step (the walk itself, or an adapter renderer further on) — and\n * nothing above the session caught it, so one hostile message took the\n * adapter subtree down. The engine's guarded raw step bounds element depth\n * and reports a deeper frame as a typed error; nothing else is degraded,\n * because a throwing consumer plugin or handler is a bug the host must see,\n * not a frame to render as text. The next frame is parsed normally again\n * (retained state is cleared).\n */\nfunction plainTextTrees(content: string): PipelineTrees {\n const position = { start: { line: 1, column: 1, offset: 0 }, end: endPoint(content) };\n const text = { type: 'text' as const, value: content, position };\n return {\n mdast: { type: 'root', children: [{ type: 'paragraph', children: [text], position }], position },\n hast: {\n type: 'root',\n children: [{ type: 'element', tagName: 'p', properties: {}, children: [text], position }],\n position,\n },\n };\n}\n\nexport function createPipelineSession(): PipelineSession {\n let state: IncrementalParseState | null = null;\n return {\n reset(): void {\n state = null;\n },\n parse({\n content,\n targetPhantoms,\n remarkPlugins,\n rehypePlugins,\n remarkRehypeOptions,\n handlers,\n preserveForBodyHarvest,\n documentId,\n provenance,\n incrementalParse,\n defListEnabled,\n gfmTaskListItems = false,\n // No default: omitted is its own (undeclared) state at the engine.\n mathFlow,\n measure: measureHere = unmeasured,\n }: PipelineFrameOptions) {\n // The suffix is APPENDED (the engine treats it as an always-tail\n // input; prepending would shift every source position). A frame that\n // ends inside an open fence / `$$` block would swallow it — sentinel\n // lines rendered as code, every cross-chunk ref falling back to\n // literal text for the block's whole streaming lifetime — so the\n // engine first emits an output-neutral closer for that block (see\n // phantomSuffixCloser; '' when nothing is open or the phase is\n // untrusted). Only chunks with a non-empty suffix pay the line scan.\n const phantomDefs = buildPhantomSuffix(targetPhantoms);\n const phantomSuffix = phantomDefs === '' ? '' : phantomSuffixCloser(content ?? '') + phantomDefs;\n const augmented = (content ?? '') + phantomSuffix;\n const baseHandlers = remarkRehypeOptions?.handlers ?? {};\n const mergedRemarkRehypeOptions = (\n handlers\n ? {\n ...remarkRehypeOptions,\n handlers: { ...baseHandlers, ...handlers },\n // Phantom label sets are empty in standalone mode (no PASS 0.5\n // injection happened); the footnoteDefinition handler still reads\n // them via `state.options.phantomFootnoteLabels.has(id)`, which\n // returns false for every id → orphan-protect path proceeds.\n phantomFootnoteLabels: targetPhantoms.missingFootnotes,\n phantomLinkLabels: targetPhantoms.missingLinks,\n preserveOrphan: preserveForBodyHarvest,\n documentId,\n // The SAME value `buildCoreRehypePlugins` received — the\n // verifier unwraps every placeholder stamped with anything else.\n provenance,\n }\n : {\n ...remarkRehypeOptions,\n }\n ) as RemarkRehypeOptions;\n\n // Coordinated (registry) mode is incremental-eligible since v2: the\n // engine takes the phantom suffix as a separate always-tail input (its\n // frame-to-frame churn re-parses only the tail — the reference taint\n // keeps every phantom-resolved ref out of the frozen prefix), and the\n // contribute effect's inputs are covered by splice equivalence (mdast)\n // plus the replay-regenerated footer (hast). When the flag is off, the\n // state is CLEARED — a later eligible frame must never splice against\n // trees parsed under different conditions.\n //\n // SSR takes this branch too: the engine's first-frame scan exists to\n // seed the NEXT frame's checkpoint, and a per-request server render\n // has no next frame — routing through the engine would pay a dead\n // O(document) line-lex per request. Hydration is unaffected (the\n // client's first frame rebuilds from null either way).\n // The ordinary full pipeline, with the plain-text last resort around\n // it for the engine's raw-depth signal only (see plainTextTrees).\n // Dev-only stage telemetry (`ai-markdown:stage:*` performance\n // measures; no-op in production) wraps only the stage calls — the\n // surrounding option assembly is trivial.\n const fullPipeline = (): PipelineTrees => {\n try {\n const parsed = measureHere('parse', () =>\n parseStage({\n children: augmented,\n remarkPlugins,\n rehypePlugins,\n remarkRehypeOptions: mergedRemarkRehypeOptions,\n })\n );\n const hastRoot = measureHere('transform', () => transformStage(parsed));\n return { mdast: parsed.mdast, hast: hastRoot };\n } catch (error) {\n // Only the engine's own signal for raw HTML nested past its depth\n // bound is degraded. Everything else — a consumer's remark/rehype\n // plugin or handler throwing, a RangeError that is not that\n // signal — propagates exactly as it did before the fallback\n // existed.\n if (!(error instanceof EngineRawHtmlDepthError)) throw error;\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n '[ai-react-markdown] raw HTML nested past the engine depth bound — rendering this frame as plain text:',\n error\n );\n }\n return plainTextTrees(content ?? '');\n }\n };\n\n if (!incrementalParse) {\n state = null;\n return fullPipeline();\n }\n\n try {\n const result = advanceIncrementalParse(state, content ?? '', {\n remarkPlugins,\n rehypePlugins,\n remarkRehypeOptions: mergedRemarkRehypeOptions,\n // Identity tuple over every parse input beyond the content itself.\n // Deliberately covers MORE than the G3 flush's 12 fields (handlers /\n // preserveForBodyHarvest / documentId can change without touching\n // any G3 field — e.g. a `preserveOrphanReferences` flip). The\n // phantom label sets are deliberately NOT here: their churn tracks\n // the suffix (always re-parsed with the tail), never the prefix.\n depsKey: [\n remarkPlugins,\n rehypePlugins,\n remarkRehypeOptions,\n handlers,\n preserveForBodyHarvest,\n documentId,\n // Explicit even though `rehypePlugins` already carries it: a\n // credential change must never reuse trees stamped under the\n // old one.\n provenance,\n // The boundary scanner's grammar profile. The scan checkpoint\n // already refuses to resume under a different profile, but the\n // retained TREES were still spliced against — a prefix frozen\n // under one profile must not survive into frames under the other.\n defListEnabled,\n gfmTaskListItems,\n mathFlow,\n ],\n defListEnabled,\n gfmTaskListItems,\n mathFlow,\n phantomSuffix,\n measure: measureHere,\n });\n state = result.nextState;\n return { mdast: result.mdast, hast: result.hast };\n } catch (error) {\n // The engine mutates prev's scan checkpoint IN PLACE before the tail\n // parse/splice — a throw mid-frame (an engine bug, or a plugin\n // choking on the synthetic tail source) leaves the retained state's\n // checkpoint describing content the state's trees do not. Clearing\n // the ref restores the \"state is CLEARED when unusable\" discipline;\n // the frame then renders via the ordinary full pipeline so one bad\n // frame cannot take the surface down.\n state = null;\n if (process.env.NODE_ENV !== 'production') {\n console.error('[ai-react-markdown] incremental parse failed — full parse fallback for this frame:', error);\n }\n return fullPipeline();\n }\n },\n };\n}\n","/** Shared decisions only: no registration, subscriptions or publication.\n * Previous outputs are optional identity hints owned by one host consumer.\n * Input label sets and returned objects must be treated as immutable snapshots.\n */\nimport { buildCrossChunkHandlers, normalizeForMatch } from '@ai-markdown/engine';\nimport type { PipelineFrameOptions } from './pipelineSession';\n\nexport interface DefinitionLabels {\n footnoteLabels: ReadonlySet<string>;\n linkLabels: ReadonlySet<string>;\n}\nexport type PhantomTargets = PipelineFrameOptions['targetPhantoms'];\n\nexport function derivePhantomTargets(\n { content, ownLabels, labels }: { content: string; ownLabels: DefinitionLabels; labels: DefinitionLabels | null },\n previous?: PhantomTargets\n): PhantomTargets {\n const missingFootnotes = new Set<string>();\n const missingLinks = new Set<string>();\n if (labels) {\n const footnotes = [...labels.footnoteLabels].filter((label) => !ownLabels.footnoteLabels.has(label));\n const links = [...labels.linkLabels].filter((label) => !ownLabels.linkLabels.has(label));\n // Avoid two full-content normalization passes when there are no candidates.\n if (footnotes.length || links.length) {\n const normalized = normalizeForMatch(content);\n for (const label of footnotes) if (normalized.includes(label)) missingFootnotes.add(label);\n for (const label of links) if (normalized.includes(label)) missingLinks.add(label);\n }\n }\n if (\n previous &&\n missingFootnotes.size === previous.missingFootnotes.size &&\n missingLinks.size === previous.missingLinks.size &&\n [...missingFootnotes].every((label) => previous.missingFootnotes.has(label)) &&\n [...missingLinks].every((label) => previous.missingLinks.has(label))\n )\n return previous;\n return { missingFootnotes, missingLinks };\n}\n\nexport interface CoordinationPolicy {\n mode: 'coordinated' | 'orphan' | 'standalone';\n handlers: PipelineFrameOptions['handlers'];\n preserveForBodyHarvest: boolean;\n}\n\nexport function deriveCoordinationPolicy(\n {\n coordinated,\n registered,\n preserveOrphanReferences,\n }: {\n coordinated: boolean;\n registered: boolean;\n /** The host has already resolved any wrapper-level override. */\n preserveOrphanReferences: boolean;\n },\n previous?: CoordinationPolicy\n): CoordinationPolicy {\n const mode = coordinated ? 'coordinated' : preserveOrphanReferences ? 'orphan' : 'standalone';\n const preserveForBodyHarvest = preserveOrphanReferences || (coordinated && registered);\n if (previous?.mode === mode && previous.preserveForBodyHarvest === preserveForBodyHarvest) return previous;\n // Registration can change body harvesting without changing handler identity.\n const handlers =\n previous?.mode === mode\n ? previous.handlers\n : mode === 'coordinated'\n ? buildCrossChunkHandlers()\n : mode === 'orphan'\n ? { footnoteDefinition: buildCrossChunkHandlers().footnoteDefinition }\n : undefined;\n return { mode, handlers, preserveForBodyHarvest };\n}\n\nexport type ContributionPolicyInputs = Pick<\n PipelineFrameOptions,\n | 'remarkPlugins'\n | 'rehypePlugins'\n | 'remarkRehypeOptions'\n | 'handlers'\n | 'preserveForBodyHarvest'\n | 'documentId'\n | 'provenance'\n> & { clobberPrefix: string };\n\n/** Source/phantom/registry/symbol changes are compared by the publisher itself.\n * This tuple covers the remaining inputs that can alter harvested bodies.\n */\nexport function buildContributionChain(inputs: ContributionPolicyInputs): readonly unknown[] {\n return [\n inputs.remarkPlugins,\n inputs.rehypePlugins,\n inputs.remarkRehypeOptions,\n inputs.handlers,\n inputs.preserveForBodyHarvest,\n inputs.clobberPrefix,\n inputs.documentId,\n inputs.provenance,\n ];\n}\n"],"mappings":"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"}