@ai-react-markdown/engine 2.3.3 → 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.cjs CHANGED
@@ -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,
@@ -72,6 +73,7 @@ __export(src_exports, {
72
73
  normalizeId: () => normalizeId,
73
74
  pangu: () => pangu,
74
75
  parseStage: () => parseStage,
76
+ phantomSuffixCloser: () => phantomSuffixCloser,
75
77
  preprocessAIMDContent: () => preprocessAIMDContent,
76
78
  preprocessLaTeX: () => preprocessLaTeX,
77
79
  rehypeFooterAdorn: () => rehypeFooterAdorn,
@@ -289,7 +291,7 @@ var DEF_LIST_DD_RE = /^ {0,3}:[ \t]/;
289
291
  var DEF_RE = /^ {0,3}\[((?:[^[\]\\]|\\.)+)\]:/;
290
292
  var FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
291
293
  var MATH_RUN_RE = /^ {0,3}(\$\$+)/;
292
- var TAG_OR_COMMENT_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)(?=[\s/>])([^>]*)>|<!--|-->/g;
294
+ var TAG_OR_COMMENT_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)(?=[\s/>])([^>]*)>|<!--|-->|--!>/g;
293
295
  var TRUNCATED_TAG_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)([^<>]*)$/;
294
296
  var REF_RE = /!?\[((?:[^[\]\\]|\\.)*)\]/g;
295
297
  var BACKTICK_RUN_RE = /`+/g;
@@ -371,6 +373,7 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
371
373
  fenceLen: 0,
372
374
  inMath: false,
373
375
  mathFenceLen: 0,
376
+ openIndent: 0,
374
377
  blankRun: 0,
375
378
  lastBlankStart: -1,
376
379
  hazardVerdict: false,
@@ -381,21 +384,15 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
381
384
  paragraphHasUnpairedRun: false,
382
385
  htmlFlowSinceBlank: false,
383
386
  htmlSeamPending: false,
384
- phasePoisonedAt: Infinity
387
+ phasePoisonedAt: Infinity,
388
+ pendingTruncatedTags: []
385
389
  };
386
390
  }
387
391
  function isPlausibleLinkDefRest(rest) {
388
392
  const t = rest.trim();
389
393
  if (t === "") return false;
390
- let destEnd;
391
- if (t.startsWith("<")) {
392
- const close = t.indexOf(">");
393
- if (close === -1) return false;
394
- destEnd = close + 1;
395
- } else {
396
- const ws = t.search(/[ \t]/);
397
- destEnd = ws === -1 ? t.length : ws;
398
- }
394
+ const destEnd = linkDestinationEnd(t);
395
+ if (destEnd === -1) return false;
399
396
  const after = t.slice(destEnd).trim();
400
397
  if (after === "") return true;
401
398
  const opener = after[0];
@@ -410,6 +407,39 @@ function isPlausibleLinkDefRest(rest) {
410
407
  }
411
408
  return false;
412
409
  }
410
+ function linkDestinationEnd(t) {
411
+ if (t.startsWith("<")) {
412
+ for (let i2 = 1; i2 < t.length; i2++) {
413
+ const ch = t[i2];
414
+ if (ch === "\\" && (t[i2 + 1] === "<" || t[i2 + 1] === ">" || t[i2 + 1] === "\\")) {
415
+ i2 += 1;
416
+ continue;
417
+ }
418
+ if (ch === ">") return i2 + 1;
419
+ if (ch === "<") return -1;
420
+ }
421
+ return -1;
422
+ }
423
+ let balance = 0;
424
+ let i = 0;
425
+ for (; i < t.length; i++) {
426
+ const code = t.charCodeAt(i);
427
+ if (code === 32 || code === 9) break;
428
+ if (code < 32 || code === 127) return -1;
429
+ const ch = t[i];
430
+ if (ch === "\\" && (t[i + 1] === "(" || t[i + 1] === ")" || t[i + 1] === "\\")) {
431
+ i += 1;
432
+ continue;
433
+ }
434
+ if (ch === "(") balance += 1;
435
+ else if (ch === ")") {
436
+ if (balance === 0) break;
437
+ balance -= 1;
438
+ }
439
+ }
440
+ if (balance !== 0 || i === 0) return -1;
441
+ return i;
442
+ }
413
443
  function classifyBlockStart(text, indent, defListEnabled) {
414
444
  if (indent >= 4) return true;
415
445
  if (LIST_MARKER_RE.test(text) || FOOTNOTE_DEF_RE.test(text)) return true;
@@ -471,6 +501,48 @@ function computeFreezeBoundary(text, options, resume) {
471
501
  }
472
502
  return { boundary, checkpoint: cp };
473
503
  }
504
+ function floatingResidue(text, commentOpenAtStart) {
505
+ let out = "";
506
+ let open = commentOpenAtStart;
507
+ let last = 0;
508
+ TAG_OR_COMMENT_RE.lastIndex = 0;
509
+ let m;
510
+ while ((m = TAG_OR_COMMENT_RE.exec(text)) !== null) {
511
+ if (m[0] === "<!--") {
512
+ const next = text.slice(m.index + 4, m.index + 6);
513
+ const overlapLen = next.startsWith(">") ? 5 : next === "->" ? 6 : 0;
514
+ if (open) {
515
+ if (overlapLen) {
516
+ open = false;
517
+ last = m.index + overlapLen;
518
+ TAG_OR_COMMENT_RE.lastIndex = last;
519
+ }
520
+ continue;
521
+ }
522
+ out += text.slice(last, m.index);
523
+ if (overlapLen) {
524
+ last = m.index + overlapLen;
525
+ TAG_OR_COMMENT_RE.lastIndex = last;
526
+ continue;
527
+ }
528
+ open = true;
529
+ continue;
530
+ }
531
+ if (m[0] === "-->") {
532
+ if (open) {
533
+ open = false;
534
+ last = m.index + 3;
535
+ }
536
+ continue;
537
+ }
538
+ if (m[0] === "--!>") continue;
539
+ if (open) continue;
540
+ out += text.slice(last, m.index);
541
+ last = m.index + m[0].length;
542
+ }
543
+ if (!open) out += text.slice(last);
544
+ return out;
545
+ }
474
546
  function processConfirmedLine(cp, ln, text) {
475
547
  const newest = cp.candidates[cp.candidates.length - 1];
476
548
  if (newest && newest.defListSettled === null) {
@@ -525,6 +597,7 @@ function processConfirmedLine(cp, ln, text) {
525
597
  cp.inFence = true;
526
598
  cp.fenceChar = open[1][0];
527
599
  cp.fenceLen = open[1].length;
600
+ cp.openIndent = ln.indent;
528
601
  cp.blankRun = 0;
529
602
  cp.paragraphHasUnpairedRun = false;
530
603
  cp.prevLineBlank = false;
@@ -559,6 +632,7 @@ function processConfirmedLine(cp, ln, text) {
559
632
  }
560
633
  cp.inMath = true;
561
634
  cp.mathFenceLen = mathRun[1].length;
635
+ cp.openIndent = ln.indent;
562
636
  cp.blankRun = 0;
563
637
  cp.paragraphHasUnpairedRun = false;
564
638
  cp.prevLineBlank = false;
@@ -569,6 +643,10 @@ function processConfirmedLine(cp, ln, text) {
569
643
  }
570
644
  }
571
645
  if (ln.blank) {
646
+ if (cp.pendingTruncatedTags.length > 0) {
647
+ for (const tag of cp.pendingTruncatedTags) applyTag(tag, true);
648
+ cp.pendingTruncatedTags = [];
649
+ }
572
650
  cp.blankRun += 1;
573
651
  cp.lastBlankStart = ln.start;
574
652
  cp.candidates.push({
@@ -598,6 +676,7 @@ function processConfirmedLine(cp, ln, text) {
598
676
  if (!TYPE6_NAMES.has(tagStart[1].toLowerCase())) cp.hazardVerdict = true;
599
677
  }
600
678
  const inRawText = cp.htmlFlowSinceBlank || rawOpenAtLineStart;
679
+ const rawFlowStart = ln.indent <= 3 && /^<(?:!--|\?|![A-Za-z]|!\[CDATA\[)/.test(ln.text.trimStart());
601
680
  const { masked, unpaired } = inRawText ? { masked: null, unpaired: false } : maskIntraLineCodeSpans(ln.text, cp.paragraphHasUnpairedRun);
602
681
  if (unpaired) cp.paragraphHasUnpairedRun = true;
603
682
  const scanText = masked ?? ln.text;
@@ -639,12 +718,16 @@ function processConfirmedLine(cp, ln, text) {
639
718
  cp.unresolvedRefs.push({ offset: ln.start + m.index, label, footnote });
640
719
  }
641
720
  }
642
- const rawOpenAtStart = cp.piOpen || cp.declOpen || cp.cdataOpen;
643
721
  const rawSpans = [];
644
722
  let pos = 0;
723
+ const poisonRawDivergence = () => {
724
+ cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start);
725
+ };
645
726
  while (pos < scanText.length) {
646
727
  if (cp.piOpen) {
647
728
  const c = scanText.indexOf("?>", pos);
729
+ const gt = scanText.indexOf(">", pos);
730
+ if (gt !== -1 && (c === -1 || gt !== c + 1)) poisonRawDivergence();
648
731
  if (c === -1) {
649
732
  rawSpans.push([pos, scanText.length]);
650
733
  break;
@@ -656,6 +739,8 @@ function processConfirmedLine(cp, ln, text) {
656
739
  }
657
740
  if (cp.cdataOpen) {
658
741
  const c = scanText.indexOf("]]>", pos);
742
+ const gt = scanText.indexOf(">", pos);
743
+ if (gt !== -1 && (c === -1 || gt !== c + 2)) poisonRawDivergence();
659
744
  if (c === -1) {
660
745
  rawSpans.push([pos, scanText.length]);
661
746
  break;
@@ -688,6 +773,12 @@ function processConfirmedLine(cp, ln, text) {
688
773
  cp.cdataOpen = true;
689
774
  pos = cd + 9;
690
775
  } else if (first === pi) {
776
+ if (scanText[pi + 2] === ">") {
777
+ rawSpans.push([pi, pi + 3]);
778
+ pos = pi + 3;
779
+ if (scanText.slice(0, pi).trim() !== "" || ln.indent > 3) poisonRawDivergence();
780
+ continue;
781
+ }
691
782
  rawSpans.push([pi, pi + 2]);
692
783
  cp.piOpen = true;
693
784
  pos = pi + 2;
@@ -697,13 +788,25 @@ function processConfirmedLine(cp, ln, text) {
697
788
  pos = decl + 2;
698
789
  }
699
790
  }
700
- const rawOpenAtEnd = cp.piOpen || cp.declOpen || cp.cdataOpen;
701
- 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
+ {
702
796
  TAG_OR_COMMENT_RE.lastIndex = 0;
703
797
  let m;
704
798
  let lastCommentOpenerIdx = -1;
705
- while ((m = TAG_OR_COMMENT_RE.exec(scanText)) !== null) {
799
+ while ((m = TAG_OR_COMMENT_RE.exec(tagText)) !== null) {
706
800
  if (m[0] === "<!--") {
801
+ const next = tagText.slice(m.index + 4, m.index + 6);
802
+ if (cp.commentOpen) {
803
+ if (next.startsWith(">") || next === "->") cp.commentOpen = false;
804
+ else if (next === "!>" || next === "-!") poisonRawDivergence();
805
+ continue;
806
+ }
807
+ if (next.startsWith(">") || next === "->") {
808
+ continue;
809
+ }
707
810
  cp.commentOpen = true;
708
811
  lastCommentOpenerIdx = m.index;
709
812
  continue;
@@ -712,6 +815,10 @@ function processConfirmedLine(cp, ln, text) {
712
815
  cp.commentOpen = false;
713
816
  continue;
714
817
  }
818
+ if (m[0] === "--!>") {
819
+ if (cp.commentOpen) poisonRawDivergence();
820
+ continue;
821
+ }
715
822
  if (cp.commentOpen) continue;
716
823
  const closing = m[1] === "/";
717
824
  const tag = m[2].toLowerCase();
@@ -720,33 +827,56 @@ function processConfirmedLine(cp, ln, text) {
720
827
  applyTag(tag, closing);
721
828
  }
722
829
  if (cp.commentOpen && lastCommentOpenerIdx !== -1 && !inRawText) {
723
- if (scanText.slice(0, lastCommentOpenerIdx).trim() !== "") {
830
+ if (tagText.slice(0, lastCommentOpenerIdx).trim() !== "") {
724
831
  cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastCommentOpenerIdx);
725
832
  }
726
833
  }
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(">")) {
851
+ cp.pendingTruncatedTags = [];
852
+ }
727
853
  if (!cp.commentOpen) {
728
- const lastLt = scanText.lastIndexOf("<");
729
- if (lastLt !== -1 && !scanText.includes(">", lastLt)) {
730
- 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));
731
857
  if (m2) {
732
858
  const closing = m2[1] === "/";
733
859
  const tag = m2[2].toLowerCase();
734
- if (!VOID_TAGS.has(tag)) applyTag(tag, closing);
860
+ if (!VOID_TAGS.has(tag)) {
861
+ applyTag(tag, closing);
862
+ const rawLastLt = ln.text.lastIndexOf("<");
863
+ const rawTruncated = rawLastLt !== -1 && !ln.text.includes(">", rawLastLt);
864
+ if (!closing && !inRawText && rawTruncated) cp.pendingTruncatedTags.push(tag);
865
+ }
735
866
  }
736
867
  }
737
868
  }
738
869
  }
739
- if (inRawText && cp.openTotal === 0) {
740
- let residue = scanText;
870
+ const effectiveOpen = cp.openTotal - cp.pendingTruncatedTags.length;
871
+ if ((inRawText || rawFlowStart) && effectiveOpen <= 0) {
872
+ let masked2 = "";
873
+ let cursor = 0;
741
874
  for (const [from, to] of rawSpans) {
742
- residue = residue.slice(0, from) + " ".repeat(to - from) + residue.slice(to);
875
+ masked2 += scanText.slice(cursor, from);
876
+ cursor = to;
743
877
  }
744
- residue = residue.replace(/<!--[\s\S]*?-->/g, " ");
745
- if (commentOpenAtLineStart) {
746
- residue = residue.includes("-->") ? residue.replace(/[\s\S]*?-->/, " ") : "";
747
- }
748
- residue = residue.replace(TAG_OR_COMMENT_RE, "");
749
- if (residue.trim() !== "") {
878
+ masked2 += scanText.slice(cursor);
879
+ if (floatingResidue(masked2, commentOpenAtLineStart).length > 0) {
750
880
  cp.htmlSeamPending = true;
751
881
  }
752
882
  }
@@ -756,6 +886,9 @@ function processConfirmedLine(cp, ln, text) {
756
886
  cp.prevLineWasValidDef = validDef && !def[1].startsWith("^");
757
887
  }
758
888
 
889
+ // src/components/incrementalParse/spliceParse.ts
890
+ var import_micromark_util_normalize_identifier2 = require("micromark-util-normalize-identifier");
891
+
759
892
  // src/components/hastPredicates.ts
760
893
  function isFootnoteSection(node) {
761
894
  if (node.tagName !== "section") return false;
@@ -806,7 +939,7 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
806
939
  if (last && last.kind === "refs") last.tokens.push(token);
807
940
  else events.push({ kind: "refs", tokens: [token] });
808
941
  };
809
- const visit6 = (node, nested) => {
942
+ const visit7 = (node, nested) => {
810
943
  const type = node.type;
811
944
  if (type === "definition") {
812
945
  const start = node.position?.start?.offset;
@@ -847,20 +980,24 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
847
980
  }
848
981
  const children = node.children;
849
982
  if (children) {
850
- for (const child of children) visit6(child, true);
983
+ for (const child of children) visit7(child, true);
851
984
  }
852
985
  };
986
+ let lastStart = -1;
853
987
  for (const child of mdast.children) {
854
988
  const start = child.position?.start?.offset;
989
+ if (start !== void 0) {
990
+ if (start < lastStart) return { events: [], uninjectable: true, cacheable: false };
991
+ lastStart = start;
992
+ }
855
993
  if (start === void 0) {
856
994
  if (resumeAt > 0) return collectPrefixInjection(mdast, content, boundary, null);
857
995
  cacheable = false;
858
- visit6(child, false);
996
+ visit7(child, false);
859
997
  continue;
860
998
  }
861
- if (start >= boundary) break;
862
- if (start < resumeAt) continue;
863
- visit6(child, false);
999
+ if (start >= boundary || start < resumeAt) continue;
1000
+ visit7(child, false);
864
1001
  }
865
1002
  return { events, uninjectable, cacheable };
866
1003
  }
@@ -873,7 +1010,7 @@ function cloneEventsForAppend(events) {
873
1010
  var TERMINATOR_LABEL = "__aimd_injection_terminator__";
874
1011
  var INJECTION_TERMINATOR = `[${TERMINATOR_LABEL}]: __aimd_sentinel_link__`;
875
1012
  function tailMentionsTerminator(tailSource) {
876
- return tailSource.includes(`[${TERMINATOR_LABEL}`);
1013
+ return (0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(tailSource).replace(/\[ /g, "[").includes(`[${(0, import_micromark_util_normalize_identifier2.normalizeIdentifier)(TERMINATOR_LABEL)}`);
877
1014
  }
878
1015
  function buildInjectionPrefix(events) {
879
1016
  if (events.length === 0) return { text: "", segments: [] };
@@ -953,12 +1090,25 @@ function spliceTrees(input) {
953
1090
  if (isTrailingLiteralText(node2)) {
954
1091
  const prev = i > 0 ? prevHast.children[i - 1] : void 0;
955
1092
  if (!prev || prev.type !== "element" || prev.position === void 0) return null;
1093
+ if (!ownsTrailingLiteral(prev, node2, prefixMdast))
1094
+ return null;
956
1095
  }
957
1096
  cutRegion.push(node2);
958
1097
  continue;
959
1098
  }
960
1099
  const node = prevHast.children[i];
961
- 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)) {
962
1112
  cutRegion.push(node);
963
1113
  break;
964
1114
  }
@@ -1173,8 +1323,16 @@ function stripInjectedHast(tailMdast, tailHast, injectedLen, tailWrapVisible) {
1173
1323
  }
1174
1324
  function tailLeadingTextIsHoist(tailMdastChildren, tailHastChildren) {
1175
1325
  const firstText = tailHastChildren[0];
1176
- if (!firstText || !isSeparatorText(firstText)) return false;
1326
+ if (!firstText) return false;
1177
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
+ }
1178
1336
  if (!firstVisible) return false;
1179
1337
  const firstContent = tailHastChildren.find((c) => !isSeparatorText(c));
1180
1338
  if (firstContent) {
@@ -1196,6 +1354,14 @@ function isCompleteRawConstruct(value) {
1196
1354
  function isSeparatorText(node) {
1197
1355
  return node.type === "text" && node.position === void 0 && node.value.trim() === "";
1198
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
+ }
1199
1365
  function isTrailingLiteralText(node) {
1200
1366
  return node.type === "text" && node.position === void 0 && node.value.trim() !== "";
1201
1367
  }
@@ -1321,11 +1487,12 @@ var import_remark_gfm = __toESM(require("remark-gfm"), 1);
1321
1487
  var import_unist_util_visit = require("unist-util-visit");
1322
1488
 
1323
1489
  // src/components/normalizeId.ts
1490
+ var import_micromark_util_normalize_identifier3 = require("micromark-util-normalize-identifier");
1324
1491
  function normalizeId(s) {
1325
- return s.replace(/\s+/g, " ").toUpperCase();
1492
+ return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s);
1326
1493
  }
1327
1494
  function normalizeForMatch(s) {
1328
- return s.replace(/\\(.)/g, "$1").replace(/\s+/g, " ").toUpperCase();
1495
+ return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s.replace(/\\(.)/g, "$1"));
1329
1496
  }
1330
1497
 
1331
1498
  // src/components/collectDefLabels.ts
@@ -1429,7 +1596,11 @@ function createDefLabelScanner(parse = collectDefLabels) {
1429
1596
 
1430
1597
  // src/components/extractDefBodiesFromHast.ts
1431
1598
  var import_unist_util_visit2 = require("unist-util-visit");
1599
+ var import_micromark_util_sanitize_uri = require("micromark-util-sanitize-uri");
1432
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
+ }
1433
1604
  function sourceIdFromFootnoteLiId(idProp, clobberPrefix) {
1434
1605
  let raw = null;
1435
1606
  if (clobberPrefix !== void 0) {
@@ -1580,6 +1751,18 @@ function buildPhantomSuffix(phantoms) {
1580
1751
  }
1581
1752
  return suffix;
1582
1753
  }
1754
+ function phantomSuffixCloser(content) {
1755
+ if (content === "") return "";
1756
+ const endsWithNewline = content.endsWith("\n");
1757
+ const confirmed = endsWithNewline ? content : content + "\n";
1758
+ const { checkpoint } = computeFreezeBoundary(confirmed, { defListEnabled: false, referenceTaint: false });
1759
+ if (checkpoint.phasePoisonedAt !== Infinity) return "";
1760
+ if (checkpoint.openIndent !== 0) return "";
1761
+ const nl = endsWithNewline ? "" : "\n";
1762
+ if (checkpoint.inFence) return `${nl}${checkpoint.fenceChar.repeat(checkpoint.fenceLen)}`;
1763
+ if (checkpoint.inMath) return `${nl}${"$".repeat(checkpoint.mathFenceLen)}`;
1764
+ return "";
1765
+ }
1583
1766
 
1584
1767
  // src/components/extractContributions.ts
1585
1768
  function fakeAnchorElement(url) {
@@ -1658,6 +1841,14 @@ function createRegistry(onEmpty) {
1658
1841
  releaseSymbol(reactId) {
1659
1842
  const entry = this._reactIdMap.get(reactId);
1660
1843
  if (!entry) return;
1844
+ if (entry.refcount <= 0) {
1845
+ if (false) {
1846
+ console.warn(
1847
+ `[ai-react-markdown] Registry.releaseSymbol("${reactId}") called with no matching allocateSymbol \u2014 ignoring (unbalanced release).`
1848
+ );
1849
+ }
1850
+ return;
1851
+ }
1661
1852
  entry.refcount--;
1662
1853
  if (entry.refcount === 0) {
1663
1854
  queueMicrotask(() => {
@@ -1808,6 +1999,41 @@ function createRegistry(onEmpty) {
1808
1999
  var import_rehype_katex = __toESM(require("rehype-katex"), 1);
1809
2000
  var import_rehype_raw = __toESM(require("rehype-raw"), 1);
1810
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
1811
2037
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
1812
2038
  var import_remark_breaks = __toESM(require("remark-breaks"), 1);
1813
2039
  var import_remark_cjk_friendly = __toESM(require("remark-cjk-friendly"), 1);
@@ -1823,13 +2049,13 @@ var import_remark_pangu = __toESM(require("remark-pangu"), 1);
1823
2049
  var import_remark_remove_comments = __toESM(require("remark-remove-comments"), 1);
1824
2050
 
1825
2051
  // src/components/rehypeRebaseHashLinks.ts
1826
- var import_unist_util_visit4 = require("unist-util-visit");
2052
+ var import_unist_util_visit5 = require("unist-util-visit");
1827
2053
  var DEFAULT_PREFIX = "user-content-";
1828
2054
  var rehypeRebaseHashLinks = (options) => {
1829
2055
  const prefix = options?.prefix ?? DEFAULT_PREFIX;
1830
2056
  const hashPrefix = "#" + prefix;
1831
2057
  return (tree) => {
1832
- (0, import_unist_util_visit4.visit)(tree, "element", (node) => {
2058
+ (0, import_unist_util_visit5.visit)(tree, "element", (node) => {
1833
2059
  if (node.tagName !== "a") return;
1834
2060
  const href = node.properties?.href;
1835
2061
  if (typeof href !== "string" || !href.startsWith("#")) return;
@@ -1841,7 +2067,7 @@ var rehypeRebaseHashLinks = (options) => {
1841
2067
  var rehypeRebaseHashLinks_default = rehypeRebaseHashLinks;
1842
2068
 
1843
2069
  // src/components/rehypeFooterAdorn.ts
1844
- var import_unist_util_visit5 = require("unist-util-visit");
2070
+ var import_unist_util_visit6 = require("unist-util-visit");
1845
2071
  var FOOTNOTE_LABEL_ID_RE = /(?:^|-)footnote-label$/;
1846
2072
  function isFootnoteLabelH2(node) {
1847
2073
  if (node.type !== "element") return false;
@@ -1856,7 +2082,7 @@ function isHr(node) {
1856
2082
  }
1857
2083
  function rehypeFooterAdorn() {
1858
2084
  return (tree) => {
1859
- (0, import_unist_util_visit5.visit)(tree, "element", (n) => {
2085
+ (0, import_unist_util_visit6.visit)(tree, "element", (n) => {
1860
2086
  const el = n;
1861
2087
  if (el.tagName !== "section") return;
1862
2088
  if (!(el.properties && "dataFootnotes" in el.properties)) return;
@@ -1934,7 +2160,10 @@ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix) {
1934
2160
  // above.
1935
2161
  [rehypeRebaseHashLinks_default, { prefix: clobberPrefix }],
1936
2162
  import_rehype_katex.default,
1937
- 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
1938
2167
  ];
1939
2168
  }
1940
2169
  function buildCoreRemarkRehypeOptions(enableDefinitionList) {
@@ -1954,6 +2183,11 @@ function buildCoreRemarkRehypeOptions(enableDefinitionList) {
1954
2183
  }
1955
2184
 
1956
2185
  // src/components/customMdastHandlers.ts
2186
+ function localDefProps(s, id) {
2187
+ const def = s.definitionById.get(id);
2188
+ if (!def || typeof def.url !== "string" || def.url === SENTINEL_LINK_URL) return {};
2189
+ return def.title ? { localUrl: def.url, localTitle: def.title } : { localUrl: def.url };
2190
+ }
1957
2191
  function buildCrossChunkHandlers() {
1958
2192
  return {
1959
2193
  footnoteDefinition: (state, node) => {
@@ -1986,7 +2220,8 @@ function buildCrossChunkHandlers() {
1986
2220
  // internally, so cross-chunk case-insensitive matching still works.
1987
2221
  label: node.label ?? node.identifier,
1988
2222
  referenceType: node.referenceType,
1989
- documentId: s.options.documentId
2223
+ documentId: s.options.documentId,
2224
+ ...localDefProps(s, id)
1990
2225
  },
1991
2226
  children: s.all(node)
1992
2227
  };
@@ -2003,7 +2238,8 @@ function buildCrossChunkHandlers() {
2003
2238
  label: node.label ?? node.identifier,
2004
2239
  referenceType: node.referenceType,
2005
2240
  alt: node.alt ?? "",
2006
- documentId: s.options.documentId
2241
+ documentId: s.options.documentId,
2242
+ ...localDefProps(s, id)
2007
2243
  },
2008
2244
  children: []
2009
2245
  };
@@ -2032,6 +2268,12 @@ function buildCrossChunkHandlers() {
2032
2268
  properties: {
2033
2269
  label: node.identifier,
2034
2270
  localOccurrence,
2271
+ // The number mdast-util-to-hast would give this reference in a
2272
+ // standalone render (footnoteOrder position) — the placeholder's
2273
+ // fallback while the registry has no global number (server render /
2274
+ // first client frame), where the local synthetic footer is what
2275
+ // renders, so marks and footer agree (core-render-02).
2276
+ localNumber: s.footnoteOrder.indexOf(id) + 1,
2035
2277
  documentId: s.options.documentId
2036
2278
  },
2037
2279
  children: []
@@ -2040,6 +2282,9 @@ function buildCrossChunkHandlers() {
2040
2282
  };
2041
2283
  }
2042
2284
 
2285
+ // src/components/crossChunkUrlSanitize.ts
2286
+ var import_micromark_util_sanitize_uri2 = require("micromark-util-sanitize-uri");
2287
+
2043
2288
  // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_listCacheClear.js
2044
2289
  function listCacheClear() {
2045
2290
  this.__data__ = [];
@@ -3240,9 +3485,9 @@ var sanitizeSchema = cloneDeep_default({
3240
3485
  attributes: {
3241
3486
  ...import_rehype_sanitize2.defaultSchema.attributes,
3242
3487
  code: mergeClassNameAllowlist(import_rehype_sanitize2.defaultSchema.attributes?.code, ["math-inline", "math-display"]),
3243
- "cross-chunk-link": ["label", "referenceType", "documentId"],
3244
- "cross-chunk-image": ["label", "referenceType", "documentId", "alt"],
3245
- "footnote-sup": ["label", "localOccurrence", "documentId"]
3488
+ "cross-chunk-link": ["label", "referenceType", "documentId", "localUrl", "localTitle"],
3489
+ "cross-chunk-image": ["label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
3490
+ "footnote-sup": ["label", "localOccurrence", "localNumber", "documentId"]
3246
3491
  }
3247
3492
  });
3248
3493
 
@@ -3267,6 +3512,7 @@ function isProtocolAllowed(url, allowed) {
3267
3512
  return allowed.some((p) => p === protocol);
3268
3513
  }
3269
3514
  function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
3515
+ rawUrl = (0, import_micromark_util_sanitize_uri2.normalizeUri)(rawUrl);
3270
3516
  const transformed = urlTransform(rawUrl, key, fakeElement(tagName, key, rawUrl));
3271
3517
  if (transformed == null) return "";
3272
3518
  const stringUrl = String(transformed);
@@ -3611,9 +3857,9 @@ var createSmoothStreamController = (options = {}) => {
3611
3857
  snap,
3612
3858
  flush() {
3613
3859
  disposed = false;
3614
- if (visibleEnd === source.length) return;
3615
- visibleEnd = source.length;
3616
- tentativeEnd = source.length;
3860
+ const target = finished ? source.length : pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
3861
+ if (target <= visibleEnd) return;
3862
+ visibleEnd = target;
3617
3863
  pending = [];
3618
3864
  credit = 0;
3619
3865
  cancelScheduled();
@@ -4129,6 +4375,7 @@ function createRemendPreprocessor(options) {
4129
4375
  extendSanitizeSchema,
4130
4376
  extractContributions,
4131
4377
  extractDefBodiesFromHast,
4378
+ footnoteSafeId,
4132
4379
  getEnginePluginInternals,
4133
4380
  hasLoneSurrogate,
4134
4381
  highlight,
@@ -4140,6 +4387,7 @@ function createRemendPreprocessor(options) {
4140
4387
  normalizeId,
4141
4388
  pangu,
4142
4389
  parseStage,
4390
+ phantomSuffixCloser,
4143
4391
  preprocessAIMDContent,
4144
4392
  preprocessLaTeX,
4145
4393
  rehypeFooterAdorn,