@ai-react-markdown/engine 2.4.0 → 2.4.1

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.d.cts CHANGED
@@ -218,9 +218,11 @@ declare const defaultUrlTransform: UrlTransform;
218
218
  * whether a sibling node FOLLOWS it. A tail block that flips between
219
219
  * def (no hast output) and paragraph therefore reshapes the frozen
220
220
  * region retroactively (2026-07-31 direction-battery counterexample,
221
- * reproduced on v1.8.0). The candidate adjacent to such a run is
222
- * rejected until a later confirmed content line pins the seam from the
223
- * frozen side; dropping candidates only over-blocks (safe direction).
221
+ * reproduced on v1.8.0). Every candidate emitted while the remnant is
222
+ * the last frozen child is rejected (they stay rejected for good); once
223
+ * a later confirmed content line pins the seam from the frozen side,
224
+ * the candidates AFTER that line are safe again. Dropping candidates
225
+ * only over-blocks (safe direction). Whitespace-only remnant counts.
224
226
  * A type 2-5 block that opens AND closes on its first line owns the
225
227
  * rest of that line as raw content too (`<!-- c --> tail`) — same seam.
226
228
  * 7. **Phase poison** (`phasePoisonedAt`) — points where this line-level
@@ -708,6 +710,17 @@ interface DefLabelScanner {
708
710
  */
709
711
  declare function createDefLabelScanner(parse?: (source: string) => DefLabels): DefLabelScanner;
710
712
 
713
+ /**
714
+ * The id fragment mdast-util-to-hast derives from a footnote identifier for
715
+ * `fn-…` / `fnref-…` (`normalizeUri(identifier.toLowerCase())` — percent-
716
+ * encodes non-ASCII, whitespace and URL-unsafe punctuation). Every place
717
+ * that MINTS a footnote id — the standalone footer (upstream), the
718
+ * cross-chunk marks and the aggregate footer — must use this same
719
+ * encoding, or a mark's `href` and its `<li id>` disagree (v2.4.0 review:
720
+ * the fallback mark wrote the raw label while the local footer was
721
+ * encoded; `[^注]` / `[^a%b]` anchors broke).
722
+ */
723
+ declare function footnoteSafeId(identifier: string): string;
711
724
  /**
712
725
  * Extract the SOURCE identifier from a footnote `<li>` id, covering both id
713
726
  * shapes in the codebase — standalone (mdast-util-to-hast `fn-` +
@@ -1235,11 +1248,16 @@ declare function buildPhantomSuffix(phantoms: PhantomLabels): string;
1235
1248
  * extend past `content.length`, which every consumer already tolerates for
1236
1249
  * the suffix's own nodes).
1237
1250
  *
1238
- * The closer is emitted at the opener line's indent, which closes the block
1239
- * both at top level (≤3 spaces are permitted there) and inside a list item
1240
- * whose content indent the opener already satisfies (a column-0 closer
1241
- * would END the item and open a NEW block that swallows the suffix worse
1242
- * than not closing).
1251
+ * A closer is emitted ONLY for a block whose opener line sits at column 0:
1252
+ * that is provably a top-level fence, which only a top-level closer (same
1253
+ * char, length, ≤3 spaces indent) can end exactly what the line scanner
1254
+ * tracks. Openers with any indent are left alone: the line model cannot
1255
+ * see container boundaries, and an indented opener may live in a list item
1256
+ * that later de-indented content already ended (micromark closed the fence
1257
+ * with the item; an emitted closer would OPEN a new fence around the
1258
+ * sentinel lines — v2.4.0 review R1) or be closed by a ≥4-space closer the
1259
+ * `^ {0,3}` scan does not recognize. Those shapes keep the plain append
1260
+ * (swallowed at worst — the pre-2.4.0 behaviour), never a wrong closer.
1243
1261
  *
1244
1262
  * Only fences and flow math are closed. Raw-HTML constructs (`<!--`, `<?`,
1245
1263
  * `<![CDATA[`, `<!X`, `<script>`) that stay open to EOF also swallow the
@@ -1951,4 +1969,4 @@ type RemendPreprocessorOptions = Omit<RemendOptions, 'katex' | 'inlineKatex'>;
1951
1969
  */
1952
1970
  declare function createRemendPreprocessor(options?: RemendPreprocessorOptions): AIMDContentPreprocessor;
1953
1971
 
1954
- export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, phantomSuffixCloser, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };
1972
+ export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, footnoteSafeId, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, phantomSuffixCloser, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };
package/dist/index.d.ts CHANGED
@@ -218,9 +218,11 @@ declare const defaultUrlTransform: UrlTransform;
218
218
  * whether a sibling node FOLLOWS it. A tail block that flips between
219
219
  * def (no hast output) and paragraph therefore reshapes the frozen
220
220
  * region retroactively (2026-07-31 direction-battery counterexample,
221
- * reproduced on v1.8.0). The candidate adjacent to such a run is
222
- * rejected until a later confirmed content line pins the seam from the
223
- * frozen side; dropping candidates only over-blocks (safe direction).
221
+ * reproduced on v1.8.0). Every candidate emitted while the remnant is
222
+ * the last frozen child is rejected (they stay rejected for good); once
223
+ * a later confirmed content line pins the seam from the frozen side,
224
+ * the candidates AFTER that line are safe again. Dropping candidates
225
+ * only over-blocks (safe direction). Whitespace-only remnant counts.
224
226
  * A type 2-5 block that opens AND closes on its first line owns the
225
227
  * rest of that line as raw content too (`<!-- c --> tail`) — same seam.
226
228
  * 7. **Phase poison** (`phasePoisonedAt`) — points where this line-level
@@ -708,6 +710,17 @@ interface DefLabelScanner {
708
710
  */
709
711
  declare function createDefLabelScanner(parse?: (source: string) => DefLabels): DefLabelScanner;
710
712
 
713
+ /**
714
+ * The id fragment mdast-util-to-hast derives from a footnote identifier for
715
+ * `fn-…` / `fnref-…` (`normalizeUri(identifier.toLowerCase())` — percent-
716
+ * encodes non-ASCII, whitespace and URL-unsafe punctuation). Every place
717
+ * that MINTS a footnote id — the standalone footer (upstream), the
718
+ * cross-chunk marks and the aggregate footer — must use this same
719
+ * encoding, or a mark's `href` and its `<li id>` disagree (v2.4.0 review:
720
+ * the fallback mark wrote the raw label while the local footer was
721
+ * encoded; `[^注]` / `[^a%b]` anchors broke).
722
+ */
723
+ declare function footnoteSafeId(identifier: string): string;
711
724
  /**
712
725
  * Extract the SOURCE identifier from a footnote `<li>` id, covering both id
713
726
  * shapes in the codebase — standalone (mdast-util-to-hast `fn-` +
@@ -1235,11 +1248,16 @@ declare function buildPhantomSuffix(phantoms: PhantomLabels): string;
1235
1248
  * extend past `content.length`, which every consumer already tolerates for
1236
1249
  * the suffix's own nodes).
1237
1250
  *
1238
- * The closer is emitted at the opener line's indent, which closes the block
1239
- * both at top level (≤3 spaces are permitted there) and inside a list item
1240
- * whose content indent the opener already satisfies (a column-0 closer
1241
- * would END the item and open a NEW block that swallows the suffix worse
1242
- * than not closing).
1251
+ * A closer is emitted ONLY for a block whose opener line sits at column 0:
1252
+ * that is provably a top-level fence, which only a top-level closer (same
1253
+ * char, length, ≤3 spaces indent) can end exactly what the line scanner
1254
+ * tracks. Openers with any indent are left alone: the line model cannot
1255
+ * see container boundaries, and an indented opener may live in a list item
1256
+ * that later de-indented content already ended (micromark closed the fence
1257
+ * with the item; an emitted closer would OPEN a new fence around the
1258
+ * sentinel lines — v2.4.0 review R1) or be closed by a ≥4-space closer the
1259
+ * `^ {0,3}` scan does not recognize. Those shapes keep the plain append
1260
+ * (swallowed at worst — the pre-2.4.0 behaviour), never a wrong closer.
1243
1261
  *
1244
1262
  * Only fences and flow math are closed. Raw-HTML constructs (`<!--`, `<?`,
1245
1263
  * `<![CDATA[`, `<!X`, `<script>`) that stay open to EOF also swallow the
@@ -1951,4 +1969,4 @@ type RemendPreprocessorOptions = Omit<RemendOptions, 'katex' | 'inlineKatex'>;
1951
1969
  */
1952
1970
  declare function createRemendPreprocessor(options?: RemendPreprocessorOptions): AIMDContentPreprocessor;
1953
1971
 
1954
- export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, phantomSuffixCloser, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };
1972
+ export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, footnoteSafeId, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, phantomSuffixCloser, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };
@@ -61,6 +61,7 @@ __export(src_exports, {
61
61
  extendSanitizeSchema: () => extendSanitizeSchema,
62
62
  extractContributions: () => extractContributions,
63
63
  extractDefBodiesFromHast: () => extractDefBodiesFromHast,
64
+ footnoteSafeId: () => footnoteSafeId,
64
65
  getEnginePluginInternals: () => getEnginePluginInternals,
65
66
  hasLoneSurrogate: () => hasLoneSurrogate,
66
67
  highlight: () => highlight,
@@ -717,7 +718,6 @@ function processConfirmedLine(cp, ln, text) {
717
718
  cp.unresolvedRefs.push({ offset: ln.start + m.index, label, footnote });
718
719
  }
719
720
  }
720
- const rawOpenAtStart = cp.piOpen || cp.declOpen || cp.cdataOpen;
721
721
  const rawSpans = [];
722
722
  let pos = 0;
723
723
  const poisonRawDivergence = () => {
@@ -788,14 +788,17 @@ function processConfirmedLine(cp, ln, text) {
788
788
  pos = decl + 2;
789
789
  }
790
790
  }
791
- const rawOpenAtEnd = cp.piOpen || cp.declOpen || cp.cdataOpen;
792
- if (!rawOpenAtStart && !rawOpenAtEnd) {
791
+ let tagText = scanText;
792
+ for (const [from, to] of rawSpans) {
793
+ tagText = tagText.slice(0, from) + " ".repeat(to - from) + tagText.slice(to);
794
+ }
795
+ {
793
796
  TAG_OR_COMMENT_RE.lastIndex = 0;
794
797
  let m;
795
798
  let lastCommentOpenerIdx = -1;
796
- while ((m = TAG_OR_COMMENT_RE.exec(scanText)) !== null) {
799
+ while ((m = TAG_OR_COMMENT_RE.exec(tagText)) !== null) {
797
800
  if (m[0] === "<!--") {
798
- const next = scanText.slice(m.index + 4, m.index + 6);
801
+ const next = tagText.slice(m.index + 4, m.index + 6);
799
802
  if (cp.commentOpen) {
800
803
  if (next.startsWith(">") || next === "->") cp.commentOpen = false;
801
804
  else if (next === "!>" || next === "-!") poisonRawDivergence();
@@ -824,34 +827,56 @@ function processConfirmedLine(cp, ln, text) {
824
827
  applyTag(tag, closing);
825
828
  }
826
829
  if (cp.commentOpen && lastCommentOpenerIdx !== -1 && !inRawText) {
827
- if (scanText.slice(0, lastCommentOpenerIdx).trim() !== "") {
830
+ if (tagText.slice(0, lastCommentOpenerIdx).trim() !== "") {
828
831
  cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastCommentOpenerIdx);
829
832
  }
830
833
  }
831
- if (cp.pendingTruncatedTags.length > 0 && scanText.includes(">")) {
834
+ if (masked !== null && masked !== ln.text) {
835
+ const inRaw = (i) => rawSpans.some(([from, to]) => i >= from && i < to);
836
+ TAG_OR_COMMENT_RE.lastIndex = 0;
837
+ let mr;
838
+ while ((mr = TAG_OR_COMMENT_RE.exec(ln.text)) !== null) {
839
+ if (mr[0] === "<!--" || mr[0] === "-->" || mr[0] === "--!>") continue;
840
+ const startMasked = masked[mr.index] !== ln.text[mr.index];
841
+ const wholeVisible = masked.slice(mr.index, mr.index + mr[0].length) === mr[0];
842
+ if (startMasked || wholeVisible || inRaw(mr.index) || cp.commentOpen) continue;
843
+ const closing = mr[1] === "/";
844
+ const tag = mr[2].toLowerCase();
845
+ const selfClosing = mr[3] !== void 0 && /\/\s*$/.test(mr[3]);
846
+ if (VOID_TAGS.has(tag) || selfClosing) continue;
847
+ applyTag(tag, closing);
848
+ }
849
+ }
850
+ if (cp.pendingTruncatedTags.length > 0 && ln.text.includes(">")) {
832
851
  cp.pendingTruncatedTags = [];
833
852
  }
834
853
  if (!cp.commentOpen) {
835
- const lastLt = scanText.lastIndexOf("<");
836
- if (lastLt !== -1 && !scanText.includes(">", lastLt)) {
837
- const m2 = TRUNCATED_TAG_RE.exec(scanText.slice(lastLt));
854
+ const lastLt = tagText.lastIndexOf("<");
855
+ if (lastLt !== -1 && !tagText.includes(">", lastLt)) {
856
+ const m2 = TRUNCATED_TAG_RE.exec(tagText.slice(lastLt));
838
857
  if (m2) {
839
858
  const closing = m2[1] === "/";
840
859
  const tag = m2[2].toLowerCase();
841
860
  if (!VOID_TAGS.has(tag)) {
842
861
  applyTag(tag, closing);
843
- if (!closing && !inRawText) cp.pendingTruncatedTags.push(tag);
862
+ const rawLastLt = ln.text.lastIndexOf("<");
863
+ const rawTruncated = rawLastLt !== -1 && !ln.text.includes(">", rawLastLt);
864
+ if (!closing && !inRawText && rawTruncated) cp.pendingTruncatedTags.push(tag);
844
865
  }
845
866
  }
846
867
  }
847
868
  }
848
869
  }
849
- if ((inRawText || rawFlowStart) && cp.openTotal === 0) {
850
- let masked2 = scanText;
870
+ const effectiveOpen = cp.openTotal - cp.pendingTruncatedTags.length;
871
+ if ((inRawText || rawFlowStart) && effectiveOpen <= 0) {
872
+ let masked2 = "";
873
+ let cursor = 0;
851
874
  for (const [from, to] of rawSpans) {
852
- masked2 = masked2.slice(0, from) + " ".repeat(to - from) + masked2.slice(to);
875
+ masked2 += scanText.slice(cursor, from);
876
+ cursor = to;
853
877
  }
854
- if (floatingResidue(masked2, commentOpenAtLineStart).trim() !== "") {
878
+ masked2 += scanText.slice(cursor);
879
+ if (floatingResidue(masked2, commentOpenAtLineStart).length > 0) {
855
880
  cp.htmlSeamPending = true;
856
881
  }
857
882
  }
@@ -914,7 +939,7 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
914
939
  if (last && last.kind === "refs") last.tokens.push(token);
915
940
  else events.push({ kind: "refs", tokens: [token] });
916
941
  };
917
- const visit6 = (node, nested) => {
942
+ const visit7 = (node, nested) => {
918
943
  const type = node.type;
919
944
  if (type === "definition") {
920
945
  const start = node.position?.start?.offset;
@@ -955,7 +980,7 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
955
980
  }
956
981
  const children = node.children;
957
982
  if (children) {
958
- for (const child of children) visit6(child, true);
983
+ for (const child of children) visit7(child, true);
959
984
  }
960
985
  };
961
986
  let lastStart = -1;
@@ -968,11 +993,11 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
968
993
  if (start === void 0) {
969
994
  if (resumeAt > 0) return collectPrefixInjection(mdast, content, boundary, null);
970
995
  cacheable = false;
971
- visit6(child, false);
996
+ visit7(child, false);
972
997
  continue;
973
998
  }
974
999
  if (start >= boundary || start < resumeAt) continue;
975
- visit6(child, false);
1000
+ visit7(child, false);
976
1001
  }
977
1002
  return { events, uninjectable, cacheable };
978
1003
  }
@@ -985,7 +1010,7 @@ function cloneEventsForAppend(events) {
985
1010
  var TERMINATOR_LABEL = "__aimd_injection_terminator__";
986
1011
  var INJECTION_TERMINATOR = `[${TERMINATOR_LABEL}]: __aimd_sentinel_link__`;
987
1012
  function tailMentionsTerminator(tailSource) {
988
- return (0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(tailSource).includes(`[${(0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(TERMINATOR_LABEL)}`);
1013
+ return (0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(tailSource).replace(/\[ /g, "[").includes(`[${(0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(TERMINATOR_LABEL)}`);
989
1014
  }
990
1015
  function buildInjectionPrefix(events) {
991
1016
  if (events.length === 0) return { text: "", segments: [] };
@@ -1065,12 +1090,25 @@ function spliceTrees(input) {
1065
1090
  if (isTrailingLiteralText(node2)) {
1066
1091
  const prev = i > 0 ? prevHast.children[i - 1] : void 0;
1067
1092
  if (!prev || prev.type !== "element" || prev.position === void 0) return null;
1093
+ if (!ownsTrailingLiteral(prev, node2, prefixMdast))
1094
+ return null;
1068
1095
  }
1069
1096
  cutRegion.push(node2);
1070
1097
  continue;
1071
1098
  }
1072
1099
  const node = prevHast.children[i];
1073
- if (i > 0 && attrs[i - 1] < boundary && prevHast.children[i - 1].type === "element" && isTrailingLiteralText(node)) {
1100
+ if (i > 0 && attrs[i - 1] < boundary && prevHast.children[i - 1].type === "element" && isTrailingLiteralText(node) && // …and only when that element really IS an html block's output: a
1101
+ // raw literal can only trail an `html` mdast node. A position-less
1102
+ // text after a `<p>` is the NEXT block's remnant — a stray end tag
1103
+ // (`</t>\na`) parse5 dropped, whose text merged with the wrap
1104
+ // separator — owned by the tail, which re-parses it; freezing it
1105
+ // here duplicated it (v2.4.0 review P3). Falls through to the
1106
+ // remnant look-ahead below, which bails to a full parse. And the
1107
+ // literal must really be THAT block's trailing text: the block's raw
1108
+ // source ends with it. A dropped-tag block right after a frozen html
1109
+ // element (`</details>\n\n</t>\ntext`) puts its remnant in the same
1110
+ // position, and freezing it duplicated it (release soak of the fix).
1111
+ ownsTrailingLiteral(prevHast.children[i - 1], node, prefixMdast)) {
1074
1112
  cutRegion.push(node);
1075
1113
  break;
1076
1114
  }
@@ -1285,8 +1323,16 @@ function stripInjectedHast(tailMdast, tailHast, injectedLen, tailWrapVisible) {
1285
1323
  }
1286
1324
  function tailLeadingTextIsHoist(tailMdastChildren, tailHastChildren) {
1287
1325
  const firstText = tailHastChildren[0];
1288
- if (!firstText || !isSeparatorText(firstText)) return false;
1326
+ if (!firstText) return false;
1289
1327
  const firstVisible = tailMdastChildren.find((c) => !isWrapInvisible(c));
1328
+ if (!isSeparatorText(firstText)) {
1329
+ if (firstText.type === "text" && firstText.position === void 0 && firstVisible?.type === "html") {
1330
+ if (/^\s*<\/[A-Za-z][A-Za-z0-9-]*\s*>/.test(firstVisible.value)) return true;
1331
+ if (isCompleteRawConstruct(firstVisible.value)) return false;
1332
+ return null;
1333
+ }
1334
+ return false;
1335
+ }
1290
1336
  if (!firstVisible) return false;
1291
1337
  const firstContent = tailHastChildren.find((c) => !isSeparatorText(c));
1292
1338
  if (firstContent) {
@@ -1308,6 +1354,14 @@ function isCompleteRawConstruct(value) {
1308
1354
  function isSeparatorText(node) {
1309
1355
  return node.type === "text" && node.position === void 0 && node.value.trim() === "";
1310
1356
  }
1357
+ function ownsTrailingLiteral(el, literal, prefixMdast) {
1358
+ const start = el.position?.start?.offset;
1359
+ if (start === void 0) return false;
1360
+ const owner = prefixMdast.find((c) => c.type === "html" && c.position?.start?.offset === start);
1361
+ if (!owner || owner.type !== "html") return false;
1362
+ const text = literal.value.trim();
1363
+ return text !== "" && owner.value.trimEnd().endsWith(text);
1364
+ }
1311
1365
  function isTrailingLiteralText(node) {
1312
1366
  return node.type === "text" && node.position === void 0 && node.value.trim() !== "";
1313
1367
  }
@@ -1542,7 +1596,11 @@ function createDefLabelScanner(parse = collectDefLabels) {
1542
1596
 
1543
1597
  // src/components/extractDefBodiesFromHast.ts
1544
1598
  var import_unist_util_visit2 = require("unist-util-visit");
1599
+ var import_micromark_util_sanitize_uri = require("micromark-util-sanitize-uri");
1545
1600
  var FN_LI_ID_RE = /(?:^|-)user-content-fn-(.+)$/;
1601
+ function footnoteSafeId(identifier) {
1602
+ return (0, import_micromark_util_sanitize_uri.normalizeUri)(identifier.toLowerCase());
1603
+ }
1546
1604
  function sourceIdFromFootnoteLiId(idProp, clobberPrefix) {
1547
1605
  let raw = null;
1548
1606
  if (clobberPrefix !== void 0) {
@@ -1699,10 +1757,10 @@ function phantomSuffixCloser(content) {
1699
1757
  const confirmed = endsWithNewline ? content : content + "\n";
1700
1758
  const { checkpoint } = computeFreezeBoundary(confirmed, { defListEnabled: false, referenceTaint: false });
1701
1759
  if (checkpoint.phasePoisonedAt !== Infinity) return "";
1760
+ if (checkpoint.openIndent !== 0) return "";
1702
1761
  const nl = endsWithNewline ? "" : "\n";
1703
- const indent = " ".repeat(checkpoint.openIndent);
1704
- if (checkpoint.inFence) return `${nl}${indent}${checkpoint.fenceChar.repeat(checkpoint.fenceLen)}`;
1705
- if (checkpoint.inMath) return `${nl}${indent}${"$".repeat(checkpoint.mathFenceLen)}`;
1762
+ if (checkpoint.inFence) return `${nl}${checkpoint.fenceChar.repeat(checkpoint.fenceLen)}`;
1763
+ if (checkpoint.inMath) return `${nl}${"$".repeat(checkpoint.mathFenceLen)}`;
1706
1764
  return "";
1707
1765
  }
1708
1766
 
@@ -1941,6 +1999,41 @@ function createRegistry(onEmpty) {
1941
1999
  var import_rehype_katex = __toESM(require("rehype-katex"), 1);
1942
2000
  var import_rehype_raw = __toESM(require("rehype-raw"), 1);
1943
2001
  var import_rehype_unwrap_images = __toESM(require("rehype-unwrap-images"), 1);
2002
+
2003
+ // src/components/rehypeUnwrapCrossChunkImages.ts
2004
+ var import_unist_util_visit4 = require("unist-util-visit");
2005
+ var IMAGE_TAGS = /* @__PURE__ */ new Set(["img", "cross-chunk-image"]);
2006
+ var LINK_TAGS = /* @__PURE__ */ new Set(["a", "cross-chunk-link"]);
2007
+ function applicable(node, inLink) {
2008
+ let image = 0 /* Unknown */;
2009
+ for (const child of node.children) {
2010
+ if (child.type === "text" && /^\s*$/.test(child.value)) continue;
2011
+ if (child.type === "element" && IMAGE_TAGS.has(child.tagName)) {
2012
+ image = 1 /* ContainsImage */;
2013
+ } else if (!inLink && child.type === "element" && LINK_TAGS.has(child.tagName)) {
2014
+ const inner = applicable(child, true);
2015
+ if (inner === 2 /* ContainsOther */) return 2 /* ContainsOther */;
2016
+ if (inner === 1 /* ContainsImage */) image = 1 /* ContainsImage */;
2017
+ } else {
2018
+ return 2 /* ContainsOther */;
2019
+ }
2020
+ }
2021
+ return image;
2022
+ }
2023
+ function rehypeUnwrapCrossChunkImages() {
2024
+ return function transform(tree) {
2025
+ (0, import_unist_util_visit4.visit)(tree, "element", (node, index, parent) => {
2026
+ if (node.tagName === "p" && parent && typeof index === "number" && applicable(node, false) === 1 /* ContainsImage */ && // Only paragraphs that actually hold a placeholder — plain <img>
2027
+ // paragraphs were already unwrapped by rehype-unwrap-images.
2028
+ JSON.stringify(node.children).includes('"cross-chunk-image"')) {
2029
+ parent.children.splice(index, 1, ...node.children);
2030
+ return [import_unist_util_visit4.SKIP, index];
2031
+ }
2032
+ });
2033
+ };
2034
+ }
2035
+
2036
+ // src/components/pluginChain.ts
1944
2037
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
1945
2038
  var import_remark_breaks = __toESM(require("remark-breaks"), 1);
1946
2039
  var import_remark_cjk_friendly = __toESM(require("remark-cjk-friendly"), 1);
@@ -1956,13 +2049,13 @@ var import_remark_pangu = __toESM(require("remark-pangu"), 1);
1956
2049
  var import_remark_remove_comments = __toESM(require("remark-remove-comments"), 1);
1957
2050
 
1958
2051
  // src/components/rehypeRebaseHashLinks.ts
1959
- var import_unist_util_visit4 = require("unist-util-visit");
2052
+ var import_unist_util_visit5 = require("unist-util-visit");
1960
2053
  var DEFAULT_PREFIX = "user-content-";
1961
2054
  var rehypeRebaseHashLinks = (options) => {
1962
2055
  const prefix = options?.prefix ?? DEFAULT_PREFIX;
1963
2056
  const hashPrefix = "#" + prefix;
1964
2057
  return (tree) => {
1965
- (0, import_unist_util_visit4.visit)(tree, "element", (node) => {
2058
+ (0, import_unist_util_visit5.visit)(tree, "element", (node) => {
1966
2059
  if (node.tagName !== "a") return;
1967
2060
  const href = node.properties?.href;
1968
2061
  if (typeof href !== "string" || !href.startsWith("#")) return;
@@ -1974,7 +2067,7 @@ var rehypeRebaseHashLinks = (options) => {
1974
2067
  var rehypeRebaseHashLinks_default = rehypeRebaseHashLinks;
1975
2068
 
1976
2069
  // src/components/rehypeFooterAdorn.ts
1977
- var import_unist_util_visit5 = require("unist-util-visit");
2070
+ var import_unist_util_visit6 = require("unist-util-visit");
1978
2071
  var FOOTNOTE_LABEL_ID_RE = /(?:^|-)footnote-label$/;
1979
2072
  function isFootnoteLabelH2(node) {
1980
2073
  if (node.type !== "element") return false;
@@ -1989,7 +2082,7 @@ function isHr(node) {
1989
2082
  }
1990
2083
  function rehypeFooterAdorn() {
1991
2084
  return (tree) => {
1992
- (0, import_unist_util_visit5.visit)(tree, "element", (n) => {
2085
+ (0, import_unist_util_visit6.visit)(tree, "element", (n) => {
1993
2086
  const el = n;
1994
2087
  if (el.tagName !== "section") return;
1995
2088
  if (!(el.properties && "dataFootnotes" in el.properties)) return;
@@ -2067,7 +2160,10 @@ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix) {
2067
2160
  // above.
2068
2161
  [rehypeRebaseHashLinks_default, { prefix: clobberPrefix }],
2069
2162
  import_rehype_katex.default,
2070
- import_rehype_unwrap_images.default
2163
+ import_rehype_unwrap_images.default,
2164
+ // Same unwrap for `<cross-chunk-image>` placeholders (coordinated mode);
2165
+ // no-op on standalone documents.
2166
+ rehypeUnwrapCrossChunkImages
2071
2167
  ];
2072
2168
  }
2073
2169
  function buildCoreRemarkRehypeOptions(enableDefinitionList) {
@@ -2186,6 +2282,9 @@ function buildCrossChunkHandlers() {
2186
2282
  };
2187
2283
  }
2188
2284
 
2285
+ // src/components/crossChunkUrlSanitize.ts
2286
+ var import_micromark_util_sanitize_uri2 = require("micromark-util-sanitize-uri");
2287
+
2189
2288
  // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_listCacheClear.js
2190
2289
  function listCacheClear() {
2191
2290
  this.__data__ = [];
@@ -3413,6 +3512,7 @@ function isProtocolAllowed(url, allowed) {
3413
3512
  return allowed.some((p) => p === protocol);
3414
3513
  }
3415
3514
  function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
3515
+ rawUrl = (0, import_micromark_util_sanitize_uri2.normalizeUri)(rawUrl);
3416
3516
  const transformed = urlTransform(rawUrl, key, fakeElement(tagName, key, rawUrl));
3417
3517
  if (transformed == null) return "";
3418
3518
  const stringUrl = String(transformed);
@@ -4287,6 +4387,7 @@ function createRemendPreprocessor(options) {
4287
4387
  extendSanitizeSchema,
4288
4388
  extractContributions,
4289
4389
  extractDefBodiesFromHast,
4390
+ footnoteSafeId,
4290
4391
  getEnginePluginInternals,
4291
4392
  hasLoneSurrogate,
4292
4393
  highlight,