@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.dev.js CHANGED
@@ -198,7 +198,7 @@ var DEF_LIST_DD_RE = /^ {0,3}:[ \t]/;
198
198
  var DEF_RE = /^ {0,3}\[((?:[^[\]\\]|\\.)+)\]:/;
199
199
  var FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
200
200
  var MATH_RUN_RE = /^ {0,3}(\$\$+)/;
201
- var TAG_OR_COMMENT_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)(?=[\s/>])([^>]*)>|<!--|-->/g;
201
+ var TAG_OR_COMMENT_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)(?=[\s/>])([^>]*)>|<!--|-->|--!>/g;
202
202
  var TRUNCATED_TAG_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)([^<>]*)$/;
203
203
  var REF_RE = /!?\[((?:[^[\]\\]|\\.)*)\]/g;
204
204
  var BACKTICK_RUN_RE = /`+/g;
@@ -280,6 +280,7 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
280
280
  fenceLen: 0,
281
281
  inMath: false,
282
282
  mathFenceLen: 0,
283
+ openIndent: 0,
283
284
  blankRun: 0,
284
285
  lastBlankStart: -1,
285
286
  hazardVerdict: false,
@@ -290,21 +291,15 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
290
291
  paragraphHasUnpairedRun: false,
291
292
  htmlFlowSinceBlank: false,
292
293
  htmlSeamPending: false,
293
- phasePoisonedAt: Infinity
294
+ phasePoisonedAt: Infinity,
295
+ pendingTruncatedTags: []
294
296
  };
295
297
  }
296
298
  function isPlausibleLinkDefRest(rest) {
297
299
  const t = rest.trim();
298
300
  if (t === "") return false;
299
- let destEnd;
300
- if (t.startsWith("<")) {
301
- const close = t.indexOf(">");
302
- if (close === -1) return false;
303
- destEnd = close + 1;
304
- } else {
305
- const ws = t.search(/[ \t]/);
306
- destEnd = ws === -1 ? t.length : ws;
307
- }
301
+ const destEnd = linkDestinationEnd(t);
302
+ if (destEnd === -1) return false;
308
303
  const after = t.slice(destEnd).trim();
309
304
  if (after === "") return true;
310
305
  const opener = after[0];
@@ -319,6 +314,39 @@ function isPlausibleLinkDefRest(rest) {
319
314
  }
320
315
  return false;
321
316
  }
317
+ function linkDestinationEnd(t) {
318
+ if (t.startsWith("<")) {
319
+ for (let i2 = 1; i2 < t.length; i2++) {
320
+ const ch = t[i2];
321
+ if (ch === "\\" && (t[i2 + 1] === "<" || t[i2 + 1] === ">" || t[i2 + 1] === "\\")) {
322
+ i2 += 1;
323
+ continue;
324
+ }
325
+ if (ch === ">") return i2 + 1;
326
+ if (ch === "<") return -1;
327
+ }
328
+ return -1;
329
+ }
330
+ let balance = 0;
331
+ let i = 0;
332
+ for (; i < t.length; i++) {
333
+ const code = t.charCodeAt(i);
334
+ if (code === 32 || code === 9) break;
335
+ if (code < 32 || code === 127) return -1;
336
+ const ch = t[i];
337
+ if (ch === "\\" && (t[i + 1] === "(" || t[i + 1] === ")" || t[i + 1] === "\\")) {
338
+ i += 1;
339
+ continue;
340
+ }
341
+ if (ch === "(") balance += 1;
342
+ else if (ch === ")") {
343
+ if (balance === 0) break;
344
+ balance -= 1;
345
+ }
346
+ }
347
+ if (balance !== 0 || i === 0) return -1;
348
+ return i;
349
+ }
322
350
  function classifyBlockStart(text, indent, defListEnabled) {
323
351
  if (indent >= 4) return true;
324
352
  if (LIST_MARKER_RE.test(text) || FOOTNOTE_DEF_RE.test(text)) return true;
@@ -380,6 +408,48 @@ function computeFreezeBoundary(text, options, resume) {
380
408
  }
381
409
  return { boundary, checkpoint: cp };
382
410
  }
411
+ function floatingResidue(text, commentOpenAtStart) {
412
+ let out = "";
413
+ let open = commentOpenAtStart;
414
+ let last = 0;
415
+ TAG_OR_COMMENT_RE.lastIndex = 0;
416
+ let m;
417
+ while ((m = TAG_OR_COMMENT_RE.exec(text)) !== null) {
418
+ if (m[0] === "<!--") {
419
+ const next = text.slice(m.index + 4, m.index + 6);
420
+ const overlapLen = next.startsWith(">") ? 5 : next === "->" ? 6 : 0;
421
+ if (open) {
422
+ if (overlapLen) {
423
+ open = false;
424
+ last = m.index + overlapLen;
425
+ TAG_OR_COMMENT_RE.lastIndex = last;
426
+ }
427
+ continue;
428
+ }
429
+ out += text.slice(last, m.index);
430
+ if (overlapLen) {
431
+ last = m.index + overlapLen;
432
+ TAG_OR_COMMENT_RE.lastIndex = last;
433
+ continue;
434
+ }
435
+ open = true;
436
+ continue;
437
+ }
438
+ if (m[0] === "-->") {
439
+ if (open) {
440
+ open = false;
441
+ last = m.index + 3;
442
+ }
443
+ continue;
444
+ }
445
+ if (m[0] === "--!>") continue;
446
+ if (open) continue;
447
+ out += text.slice(last, m.index);
448
+ last = m.index + m[0].length;
449
+ }
450
+ if (!open) out += text.slice(last);
451
+ return out;
452
+ }
383
453
  function processConfirmedLine(cp, ln, text) {
384
454
  const newest = cp.candidates[cp.candidates.length - 1];
385
455
  if (newest && newest.defListSettled === null) {
@@ -434,6 +504,7 @@ function processConfirmedLine(cp, ln, text) {
434
504
  cp.inFence = true;
435
505
  cp.fenceChar = open[1][0];
436
506
  cp.fenceLen = open[1].length;
507
+ cp.openIndent = ln.indent;
437
508
  cp.blankRun = 0;
438
509
  cp.paragraphHasUnpairedRun = false;
439
510
  cp.prevLineBlank = false;
@@ -468,6 +539,7 @@ function processConfirmedLine(cp, ln, text) {
468
539
  }
469
540
  cp.inMath = true;
470
541
  cp.mathFenceLen = mathRun[1].length;
542
+ cp.openIndent = ln.indent;
471
543
  cp.blankRun = 0;
472
544
  cp.paragraphHasUnpairedRun = false;
473
545
  cp.prevLineBlank = false;
@@ -478,6 +550,10 @@ function processConfirmedLine(cp, ln, text) {
478
550
  }
479
551
  }
480
552
  if (ln.blank) {
553
+ if (cp.pendingTruncatedTags.length > 0) {
554
+ for (const tag of cp.pendingTruncatedTags) applyTag(tag, true);
555
+ cp.pendingTruncatedTags = [];
556
+ }
481
557
  cp.blankRun += 1;
482
558
  cp.lastBlankStart = ln.start;
483
559
  cp.candidates.push({
@@ -507,6 +583,7 @@ function processConfirmedLine(cp, ln, text) {
507
583
  if (!TYPE6_NAMES.has(tagStart[1].toLowerCase())) cp.hazardVerdict = true;
508
584
  }
509
585
  const inRawText = cp.htmlFlowSinceBlank || rawOpenAtLineStart;
586
+ const rawFlowStart = ln.indent <= 3 && /^<(?:!--|\?|![A-Za-z]|!\[CDATA\[)/.test(ln.text.trimStart());
510
587
  const { masked, unpaired } = inRawText ? { masked: null, unpaired: false } : maskIntraLineCodeSpans(ln.text, cp.paragraphHasUnpairedRun);
511
588
  if (unpaired) cp.paragraphHasUnpairedRun = true;
512
589
  const scanText = masked ?? ln.text;
@@ -548,12 +625,16 @@ function processConfirmedLine(cp, ln, text) {
548
625
  cp.unresolvedRefs.push({ offset: ln.start + m.index, label, footnote });
549
626
  }
550
627
  }
551
- const rawOpenAtStart = cp.piOpen || cp.declOpen || cp.cdataOpen;
552
628
  const rawSpans = [];
553
629
  let pos = 0;
630
+ const poisonRawDivergence = () => {
631
+ cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start);
632
+ };
554
633
  while (pos < scanText.length) {
555
634
  if (cp.piOpen) {
556
635
  const c = scanText.indexOf("?>", pos);
636
+ const gt = scanText.indexOf(">", pos);
637
+ if (gt !== -1 && (c === -1 || gt !== c + 1)) poisonRawDivergence();
557
638
  if (c === -1) {
558
639
  rawSpans.push([pos, scanText.length]);
559
640
  break;
@@ -565,6 +646,8 @@ function processConfirmedLine(cp, ln, text) {
565
646
  }
566
647
  if (cp.cdataOpen) {
567
648
  const c = scanText.indexOf("]]>", pos);
649
+ const gt = scanText.indexOf(">", pos);
650
+ if (gt !== -1 && (c === -1 || gt !== c + 2)) poisonRawDivergence();
568
651
  if (c === -1) {
569
652
  rawSpans.push([pos, scanText.length]);
570
653
  break;
@@ -597,6 +680,12 @@ function processConfirmedLine(cp, ln, text) {
597
680
  cp.cdataOpen = true;
598
681
  pos = cd + 9;
599
682
  } else if (first === pi) {
683
+ if (scanText[pi + 2] === ">") {
684
+ rawSpans.push([pi, pi + 3]);
685
+ pos = pi + 3;
686
+ if (scanText.slice(0, pi).trim() !== "" || ln.indent > 3) poisonRawDivergence();
687
+ continue;
688
+ }
600
689
  rawSpans.push([pi, pi + 2]);
601
690
  cp.piOpen = true;
602
691
  pos = pi + 2;
@@ -606,13 +695,25 @@ function processConfirmedLine(cp, ln, text) {
606
695
  pos = decl + 2;
607
696
  }
608
697
  }
609
- const rawOpenAtEnd = cp.piOpen || cp.declOpen || cp.cdataOpen;
610
- if (!rawOpenAtStart && !rawOpenAtEnd) {
698
+ let tagText = scanText;
699
+ for (const [from, to] of rawSpans) {
700
+ tagText = tagText.slice(0, from) + " ".repeat(to - from) + tagText.slice(to);
701
+ }
702
+ {
611
703
  TAG_OR_COMMENT_RE.lastIndex = 0;
612
704
  let m;
613
705
  let lastCommentOpenerIdx = -1;
614
- while ((m = TAG_OR_COMMENT_RE.exec(scanText)) !== null) {
706
+ while ((m = TAG_OR_COMMENT_RE.exec(tagText)) !== null) {
615
707
  if (m[0] === "<!--") {
708
+ const next = tagText.slice(m.index + 4, m.index + 6);
709
+ if (cp.commentOpen) {
710
+ if (next.startsWith(">") || next === "->") cp.commentOpen = false;
711
+ else if (next === "!>" || next === "-!") poisonRawDivergence();
712
+ continue;
713
+ }
714
+ if (next.startsWith(">") || next === "->") {
715
+ continue;
716
+ }
616
717
  cp.commentOpen = true;
617
718
  lastCommentOpenerIdx = m.index;
618
719
  continue;
@@ -621,6 +722,10 @@ function processConfirmedLine(cp, ln, text) {
621
722
  cp.commentOpen = false;
622
723
  continue;
623
724
  }
725
+ if (m[0] === "--!>") {
726
+ if (cp.commentOpen) poisonRawDivergence();
727
+ continue;
728
+ }
624
729
  if (cp.commentOpen) continue;
625
730
  const closing = m[1] === "/";
626
731
  const tag = m[2].toLowerCase();
@@ -629,33 +734,56 @@ function processConfirmedLine(cp, ln, text) {
629
734
  applyTag(tag, closing);
630
735
  }
631
736
  if (cp.commentOpen && lastCommentOpenerIdx !== -1 && !inRawText) {
632
- if (scanText.slice(0, lastCommentOpenerIdx).trim() !== "") {
737
+ if (tagText.slice(0, lastCommentOpenerIdx).trim() !== "") {
633
738
  cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastCommentOpenerIdx);
634
739
  }
635
740
  }
741
+ if (masked !== null && masked !== ln.text) {
742
+ const inRaw = (i) => rawSpans.some(([from, to]) => i >= from && i < to);
743
+ TAG_OR_COMMENT_RE.lastIndex = 0;
744
+ let mr;
745
+ while ((mr = TAG_OR_COMMENT_RE.exec(ln.text)) !== null) {
746
+ if (mr[0] === "<!--" || mr[0] === "-->" || mr[0] === "--!>") continue;
747
+ const startMasked = masked[mr.index] !== ln.text[mr.index];
748
+ const wholeVisible = masked.slice(mr.index, mr.index + mr[0].length) === mr[0];
749
+ if (startMasked || wholeVisible || inRaw(mr.index) || cp.commentOpen) continue;
750
+ const closing = mr[1] === "/";
751
+ const tag = mr[2].toLowerCase();
752
+ const selfClosing = mr[3] !== void 0 && /\/\s*$/.test(mr[3]);
753
+ if (VOID_TAGS.has(tag) || selfClosing) continue;
754
+ applyTag(tag, closing);
755
+ }
756
+ }
757
+ if (cp.pendingTruncatedTags.length > 0 && ln.text.includes(">")) {
758
+ cp.pendingTruncatedTags = [];
759
+ }
636
760
  if (!cp.commentOpen) {
637
- const lastLt = scanText.lastIndexOf("<");
638
- if (lastLt !== -1 && !scanText.includes(">", lastLt)) {
639
- const m2 = TRUNCATED_TAG_RE.exec(scanText.slice(lastLt));
761
+ const lastLt = tagText.lastIndexOf("<");
762
+ if (lastLt !== -1 && !tagText.includes(">", lastLt)) {
763
+ const m2 = TRUNCATED_TAG_RE.exec(tagText.slice(lastLt));
640
764
  if (m2) {
641
765
  const closing = m2[1] === "/";
642
766
  const tag = m2[2].toLowerCase();
643
- if (!VOID_TAGS.has(tag)) applyTag(tag, closing);
767
+ if (!VOID_TAGS.has(tag)) {
768
+ applyTag(tag, closing);
769
+ const rawLastLt = ln.text.lastIndexOf("<");
770
+ const rawTruncated = rawLastLt !== -1 && !ln.text.includes(">", rawLastLt);
771
+ if (!closing && !inRawText && rawTruncated) cp.pendingTruncatedTags.push(tag);
772
+ }
644
773
  }
645
774
  }
646
775
  }
647
776
  }
648
- if (inRawText && cp.openTotal === 0) {
649
- let residue = scanText;
777
+ const effectiveOpen = cp.openTotal - cp.pendingTruncatedTags.length;
778
+ if ((inRawText || rawFlowStart) && effectiveOpen <= 0) {
779
+ let masked2 = "";
780
+ let cursor = 0;
650
781
  for (const [from, to] of rawSpans) {
651
- residue = residue.slice(0, from) + " ".repeat(to - from) + residue.slice(to);
652
- }
653
- residue = residue.replace(/<!--[\s\S]*?-->/g, " ");
654
- if (commentOpenAtLineStart) {
655
- residue = residue.includes("-->") ? residue.replace(/[\s\S]*?-->/, " ") : "";
782
+ masked2 += scanText.slice(cursor, from);
783
+ cursor = to;
656
784
  }
657
- residue = residue.replace(TAG_OR_COMMENT_RE, "");
658
- if (residue.trim() !== "") {
785
+ masked2 += scanText.slice(cursor);
786
+ if (floatingResidue(masked2, commentOpenAtLineStart).length > 0) {
659
787
  cp.htmlSeamPending = true;
660
788
  }
661
789
  }
@@ -665,6 +793,9 @@ function processConfirmedLine(cp, ln, text) {
665
793
  cp.prevLineWasValidDef = validDef && !def[1].startsWith("^");
666
794
  }
667
795
 
796
+ // src/components/incrementalParse/spliceParse.ts
797
+ import { normalizeIdentifier as normalizeIdentifier2 } from "micromark-util-normalize-identifier";
798
+
668
799
  // src/components/hastPredicates.ts
669
800
  function isFootnoteSection(node) {
670
801
  if (node.tagName !== "section") return false;
@@ -715,7 +846,7 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
715
846
  if (last && last.kind === "refs") last.tokens.push(token);
716
847
  else events.push({ kind: "refs", tokens: [token] });
717
848
  };
718
- const visit6 = (node, nested) => {
849
+ const visit7 = (node, nested) => {
719
850
  const type = node.type;
720
851
  if (type === "definition") {
721
852
  const start = node.position?.start?.offset;
@@ -756,20 +887,24 @@ function collectPrefixInjection(mdast, content, boundary, resume) {
756
887
  }
757
888
  const children = node.children;
758
889
  if (children) {
759
- for (const child of children) visit6(child, true);
890
+ for (const child of children) visit7(child, true);
760
891
  }
761
892
  };
893
+ let lastStart = -1;
762
894
  for (const child of mdast.children) {
763
895
  const start = child.position?.start?.offset;
896
+ if (start !== void 0) {
897
+ if (start < lastStart) return { events: [], uninjectable: true, cacheable: false };
898
+ lastStart = start;
899
+ }
764
900
  if (start === void 0) {
765
901
  if (resumeAt > 0) return collectPrefixInjection(mdast, content, boundary, null);
766
902
  cacheable = false;
767
- visit6(child, false);
903
+ visit7(child, false);
768
904
  continue;
769
905
  }
770
- if (start >= boundary) break;
771
- if (start < resumeAt) continue;
772
- visit6(child, false);
906
+ if (start >= boundary || start < resumeAt) continue;
907
+ visit7(child, false);
773
908
  }
774
909
  return { events, uninjectable, cacheable };
775
910
  }
@@ -782,7 +917,7 @@ function cloneEventsForAppend(events) {
782
917
  var TERMINATOR_LABEL = "__aimd_injection_terminator__";
783
918
  var INJECTION_TERMINATOR = `[${TERMINATOR_LABEL}]: __aimd_sentinel_link__`;
784
919
  function tailMentionsTerminator(tailSource) {
785
- return tailSource.includes(`[${TERMINATOR_LABEL}`);
920
+ return normalizeIdentifier2(tailSource).replace(/\[ /g, "[").includes(`[${normalizeIdentifier2(TERMINATOR_LABEL)}`);
786
921
  }
787
922
  function buildInjectionPrefix(events) {
788
923
  if (events.length === 0) return { text: "", segments: [] };
@@ -862,12 +997,25 @@ function spliceTrees(input) {
862
997
  if (isTrailingLiteralText(node2)) {
863
998
  const prev = i > 0 ? prevHast.children[i - 1] : void 0;
864
999
  if (!prev || prev.type !== "element" || prev.position === void 0) return null;
1000
+ if (!ownsTrailingLiteral(prev, node2, prefixMdast))
1001
+ return null;
865
1002
  }
866
1003
  cutRegion.push(node2);
867
1004
  continue;
868
1005
  }
869
1006
  const node = prevHast.children[i];
870
- if (i > 0 && attrs[i - 1] < boundary && prevHast.children[i - 1].type === "element" && isTrailingLiteralText(node)) {
1007
+ 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
1008
+ // raw literal can only trail an `html` mdast node. A position-less
1009
+ // text after a `<p>` is the NEXT block's remnant — a stray end tag
1010
+ // (`</t>\na`) parse5 dropped, whose text merged with the wrap
1011
+ // separator — owned by the tail, which re-parses it; freezing it
1012
+ // here duplicated it (v2.4.0 review P3). Falls through to the
1013
+ // remnant look-ahead below, which bails to a full parse. And the
1014
+ // literal must really be THAT block's trailing text: the block's raw
1015
+ // source ends with it. A dropped-tag block right after a frozen html
1016
+ // element (`</details>\n\n</t>\ntext`) puts its remnant in the same
1017
+ // position, and freezing it duplicated it (release soak of the fix).
1018
+ ownsTrailingLiteral(prevHast.children[i - 1], node, prefixMdast)) {
871
1019
  cutRegion.push(node);
872
1020
  break;
873
1021
  }
@@ -1082,8 +1230,16 @@ function stripInjectedHast(tailMdast, tailHast, injectedLen, tailWrapVisible) {
1082
1230
  }
1083
1231
  function tailLeadingTextIsHoist(tailMdastChildren, tailHastChildren) {
1084
1232
  const firstText = tailHastChildren[0];
1085
- if (!firstText || !isSeparatorText(firstText)) return false;
1233
+ if (!firstText) return false;
1086
1234
  const firstVisible = tailMdastChildren.find((c) => !isWrapInvisible(c));
1235
+ if (!isSeparatorText(firstText)) {
1236
+ if (firstText.type === "text" && firstText.position === void 0 && firstVisible?.type === "html") {
1237
+ if (/^\s*<\/[A-Za-z][A-Za-z0-9-]*\s*>/.test(firstVisible.value)) return true;
1238
+ if (isCompleteRawConstruct(firstVisible.value)) return false;
1239
+ return null;
1240
+ }
1241
+ return false;
1242
+ }
1087
1243
  if (!firstVisible) return false;
1088
1244
  const firstContent = tailHastChildren.find((c) => !isSeparatorText(c));
1089
1245
  if (firstContent) {
@@ -1105,6 +1261,14 @@ function isCompleteRawConstruct(value) {
1105
1261
  function isSeparatorText(node) {
1106
1262
  return node.type === "text" && node.position === void 0 && node.value.trim() === "";
1107
1263
  }
1264
+ function ownsTrailingLiteral(el, literal, prefixMdast) {
1265
+ const start = el.position?.start?.offset;
1266
+ if (start === void 0) return false;
1267
+ const owner = prefixMdast.find((c) => c.type === "html" && c.position?.start?.offset === start);
1268
+ if (!owner || owner.type !== "html") return false;
1269
+ const text = literal.value.trim();
1270
+ return text !== "" && owner.value.trimEnd().endsWith(text);
1271
+ }
1108
1272
  function isTrailingLiteralText(node) {
1109
1273
  return node.type === "text" && node.position === void 0 && node.value.trim() !== "";
1110
1274
  }
@@ -1230,11 +1394,12 @@ import remarkGfm from "remark-gfm";
1230
1394
  import { visit } from "unist-util-visit";
1231
1395
 
1232
1396
  // src/components/normalizeId.ts
1397
+ import { normalizeIdentifier as normalizeIdentifier3 } from "micromark-util-normalize-identifier";
1233
1398
  function normalizeId(s) {
1234
- return s.replace(/\s+/g, " ").toUpperCase();
1399
+ return normalizeIdentifier3(s);
1235
1400
  }
1236
1401
  function normalizeForMatch(s) {
1237
- return s.replace(/\\(.)/g, "$1").replace(/\s+/g, " ").toUpperCase();
1402
+ return normalizeIdentifier3(s.replace(/\\(.)/g, "$1"));
1238
1403
  }
1239
1404
 
1240
1405
  // src/components/collectDefLabels.ts
@@ -1338,7 +1503,11 @@ function createDefLabelScanner(parse = collectDefLabels) {
1338
1503
 
1339
1504
  // src/components/extractDefBodiesFromHast.ts
1340
1505
  import { SKIP, visit as visit2 } from "unist-util-visit";
1506
+ import { normalizeUri } from "micromark-util-sanitize-uri";
1341
1507
  var FN_LI_ID_RE = /(?:^|-)user-content-fn-(.+)$/;
1508
+ function footnoteSafeId(identifier) {
1509
+ return normalizeUri(identifier.toLowerCase());
1510
+ }
1342
1511
  function sourceIdFromFootnoteLiId(idProp, clobberPrefix) {
1343
1512
  let raw = null;
1344
1513
  if (clobberPrefix !== void 0) {
@@ -1489,6 +1658,18 @@ function buildPhantomSuffix(phantoms) {
1489
1658
  }
1490
1659
  return suffix;
1491
1660
  }
1661
+ function phantomSuffixCloser(content) {
1662
+ if (content === "") return "";
1663
+ const endsWithNewline = content.endsWith("\n");
1664
+ const confirmed = endsWithNewline ? content : content + "\n";
1665
+ const { checkpoint } = computeFreezeBoundary(confirmed, { defListEnabled: false, referenceTaint: false });
1666
+ if (checkpoint.phasePoisonedAt !== Infinity) return "";
1667
+ if (checkpoint.openIndent !== 0) return "";
1668
+ const nl = endsWithNewline ? "" : "\n";
1669
+ if (checkpoint.inFence) return `${nl}${checkpoint.fenceChar.repeat(checkpoint.fenceLen)}`;
1670
+ if (checkpoint.inMath) return `${nl}${"$".repeat(checkpoint.mathFenceLen)}`;
1671
+ return "";
1672
+ }
1492
1673
 
1493
1674
  // src/components/extractContributions.ts
1494
1675
  function fakeAnchorElement(url) {
@@ -1567,6 +1748,14 @@ function createRegistry(onEmpty) {
1567
1748
  releaseSymbol(reactId) {
1568
1749
  const entry = this._reactIdMap.get(reactId);
1569
1750
  if (!entry) return;
1751
+ if (entry.refcount <= 0) {
1752
+ if (true) {
1753
+ console.warn(
1754
+ `[ai-react-markdown] Registry.releaseSymbol("${reactId}") called with no matching allocateSymbol \u2014 ignoring (unbalanced release).`
1755
+ );
1756
+ }
1757
+ return;
1758
+ }
1570
1759
  entry.refcount--;
1571
1760
  if (entry.refcount === 0) {
1572
1761
  queueMicrotask(() => {
@@ -1717,6 +1906,41 @@ function createRegistry(onEmpty) {
1717
1906
  import rehypeKatex from "rehype-katex";
1718
1907
  import rehypeRaw from "rehype-raw";
1719
1908
  import rehypeUnwrapImages from "rehype-unwrap-images";
1909
+
1910
+ // src/components/rehypeUnwrapCrossChunkImages.ts
1911
+ import { SKIP as SKIP3, visit as visit4 } from "unist-util-visit";
1912
+ var IMAGE_TAGS = /* @__PURE__ */ new Set(["img", "cross-chunk-image"]);
1913
+ var LINK_TAGS = /* @__PURE__ */ new Set(["a", "cross-chunk-link"]);
1914
+ function applicable(node, inLink) {
1915
+ let image = 0 /* Unknown */;
1916
+ for (const child of node.children) {
1917
+ if (child.type === "text" && /^\s*$/.test(child.value)) continue;
1918
+ if (child.type === "element" && IMAGE_TAGS.has(child.tagName)) {
1919
+ image = 1 /* ContainsImage */;
1920
+ } else if (!inLink && child.type === "element" && LINK_TAGS.has(child.tagName)) {
1921
+ const inner = applicable(child, true);
1922
+ if (inner === 2 /* ContainsOther */) return 2 /* ContainsOther */;
1923
+ if (inner === 1 /* ContainsImage */) image = 1 /* ContainsImage */;
1924
+ } else {
1925
+ return 2 /* ContainsOther */;
1926
+ }
1927
+ }
1928
+ return image;
1929
+ }
1930
+ function rehypeUnwrapCrossChunkImages() {
1931
+ return function transform(tree) {
1932
+ visit4(tree, "element", (node, index, parent) => {
1933
+ if (node.tagName === "p" && parent && typeof index === "number" && applicable(node, false) === 1 /* ContainsImage */ && // Only paragraphs that actually hold a placeholder — plain <img>
1934
+ // paragraphs were already unwrapped by rehype-unwrap-images.
1935
+ JSON.stringify(node.children).includes('"cross-chunk-image"')) {
1936
+ parent.children.splice(index, 1, ...node.children);
1937
+ return [SKIP3, index];
1938
+ }
1939
+ });
1940
+ };
1941
+ }
1942
+
1943
+ // src/components/pluginChain.ts
1720
1944
  import rehypeSanitize from "rehype-sanitize";
1721
1945
  import remarkBreaks from "remark-breaks";
1722
1946
  import remarkCjkFriendly from "remark-cjk-friendly";
@@ -1732,13 +1956,13 @@ import remarkPangu from "remark-pangu";
1732
1956
  import remarkRemoveComments from "remark-remove-comments";
1733
1957
 
1734
1958
  // src/components/rehypeRebaseHashLinks.ts
1735
- import { visit as visit4 } from "unist-util-visit";
1959
+ import { visit as visit5 } from "unist-util-visit";
1736
1960
  var DEFAULT_PREFIX = "user-content-";
1737
1961
  var rehypeRebaseHashLinks = (options) => {
1738
1962
  const prefix = options?.prefix ?? DEFAULT_PREFIX;
1739
1963
  const hashPrefix = "#" + prefix;
1740
1964
  return (tree) => {
1741
- visit4(tree, "element", (node) => {
1965
+ visit5(tree, "element", (node) => {
1742
1966
  if (node.tagName !== "a") return;
1743
1967
  const href = node.properties?.href;
1744
1968
  if (typeof href !== "string" || !href.startsWith("#")) return;
@@ -1750,7 +1974,7 @@ var rehypeRebaseHashLinks = (options) => {
1750
1974
  var rehypeRebaseHashLinks_default = rehypeRebaseHashLinks;
1751
1975
 
1752
1976
  // src/components/rehypeFooterAdorn.ts
1753
- import { visit as visit5 } from "unist-util-visit";
1977
+ import { visit as visit6 } from "unist-util-visit";
1754
1978
  var FOOTNOTE_LABEL_ID_RE = /(?:^|-)footnote-label$/;
1755
1979
  function isFootnoteLabelH2(node) {
1756
1980
  if (node.type !== "element") return false;
@@ -1765,7 +1989,7 @@ function isHr(node) {
1765
1989
  }
1766
1990
  function rehypeFooterAdorn() {
1767
1991
  return (tree) => {
1768
- visit5(tree, "element", (n) => {
1992
+ visit6(tree, "element", (n) => {
1769
1993
  const el = n;
1770
1994
  if (el.tagName !== "section") return;
1771
1995
  if (!(el.properties && "dataFootnotes" in el.properties)) return;
@@ -1843,7 +2067,10 @@ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix) {
1843
2067
  // above.
1844
2068
  [rehypeRebaseHashLinks_default, { prefix: clobberPrefix }],
1845
2069
  rehypeKatex,
1846
- rehypeUnwrapImages
2070
+ rehypeUnwrapImages,
2071
+ // Same unwrap for `<cross-chunk-image>` placeholders (coordinated mode);
2072
+ // no-op on standalone documents.
2073
+ rehypeUnwrapCrossChunkImages
1847
2074
  ];
1848
2075
  }
1849
2076
  function buildCoreRemarkRehypeOptions(enableDefinitionList) {
@@ -1863,6 +2090,11 @@ function buildCoreRemarkRehypeOptions(enableDefinitionList) {
1863
2090
  }
1864
2091
 
1865
2092
  // src/components/customMdastHandlers.ts
2093
+ function localDefProps(s, id) {
2094
+ const def = s.definitionById.get(id);
2095
+ if (!def || typeof def.url !== "string" || def.url === SENTINEL_LINK_URL) return {};
2096
+ return def.title ? { localUrl: def.url, localTitle: def.title } : { localUrl: def.url };
2097
+ }
1866
2098
  function buildCrossChunkHandlers() {
1867
2099
  return {
1868
2100
  footnoteDefinition: (state, node) => {
@@ -1895,7 +2127,8 @@ function buildCrossChunkHandlers() {
1895
2127
  // internally, so cross-chunk case-insensitive matching still works.
1896
2128
  label: node.label ?? node.identifier,
1897
2129
  referenceType: node.referenceType,
1898
- documentId: s.options.documentId
2130
+ documentId: s.options.documentId,
2131
+ ...localDefProps(s, id)
1899
2132
  },
1900
2133
  children: s.all(node)
1901
2134
  };
@@ -1912,7 +2145,8 @@ function buildCrossChunkHandlers() {
1912
2145
  label: node.label ?? node.identifier,
1913
2146
  referenceType: node.referenceType,
1914
2147
  alt: node.alt ?? "",
1915
- documentId: s.options.documentId
2148
+ documentId: s.options.documentId,
2149
+ ...localDefProps(s, id)
1916
2150
  },
1917
2151
  children: []
1918
2152
  };
@@ -1941,6 +2175,12 @@ function buildCrossChunkHandlers() {
1941
2175
  properties: {
1942
2176
  label: node.identifier,
1943
2177
  localOccurrence,
2178
+ // The number mdast-util-to-hast would give this reference in a
2179
+ // standalone render (footnoteOrder position) — the placeholder's
2180
+ // fallback while the registry has no global number (server render /
2181
+ // first client frame), where the local synthetic footer is what
2182
+ // renders, so marks and footer agree (core-render-02).
2183
+ localNumber: s.footnoteOrder.indexOf(id) + 1,
1944
2184
  documentId: s.options.documentId
1945
2185
  },
1946
2186
  children: []
@@ -1949,6 +2189,9 @@ function buildCrossChunkHandlers() {
1949
2189
  };
1950
2190
  }
1951
2191
 
2192
+ // src/components/crossChunkUrlSanitize.ts
2193
+ import { normalizeUri as normalizeUri2 } from "micromark-util-sanitize-uri";
2194
+
1952
2195
  // ../../node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_listCacheClear.js
1953
2196
  function listCacheClear() {
1954
2197
  this.__data__ = [];
@@ -3149,9 +3392,9 @@ var sanitizeSchema = cloneDeep_default({
3149
3392
  attributes: {
3150
3393
  ...defaultSchema.attributes,
3151
3394
  code: mergeClassNameAllowlist(defaultSchema.attributes?.code, ["math-inline", "math-display"]),
3152
- "cross-chunk-link": ["label", "referenceType", "documentId"],
3153
- "cross-chunk-image": ["label", "referenceType", "documentId", "alt"],
3154
- "footnote-sup": ["label", "localOccurrence", "documentId"]
3395
+ "cross-chunk-link": ["label", "referenceType", "documentId", "localUrl", "localTitle"],
3396
+ "cross-chunk-image": ["label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
3397
+ "footnote-sup": ["label", "localOccurrence", "localNumber", "documentId"]
3155
3398
  }
3156
3399
  });
3157
3400
 
@@ -3176,6 +3419,7 @@ function isProtocolAllowed(url, allowed) {
3176
3419
  return allowed.some((p) => p === protocol);
3177
3420
  }
3178
3421
  function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
3422
+ rawUrl = normalizeUri2(rawUrl);
3179
3423
  const transformed = urlTransform(rawUrl, key, fakeElement(tagName, key, rawUrl));
3180
3424
  if (transformed == null) return "";
3181
3425
  const stringUrl = String(transformed);
@@ -3532,9 +3776,9 @@ var createSmoothStreamController = (options = {}) => {
3532
3776
  snap,
3533
3777
  flush() {
3534
3778
  disposed = false;
3535
- if (visibleEnd === source.length) return;
3536
- visibleEnd = source.length;
3537
- tentativeEnd = source.length;
3779
+ const target = finished ? source.length : pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
3780
+ if (target <= visibleEnd) return;
3781
+ visibleEnd = target;
3538
3782
  pending = [];
3539
3783
  credit = 0;
3540
3784
  cancelScheduled();
@@ -4049,6 +4293,7 @@ export {
4049
4293
  extendSanitizeSchema,
4050
4294
  extractContributions,
4051
4295
  extractDefBodiesFromHast,
4296
+ footnoteSafeId,
4052
4297
  getEnginePluginInternals,
4053
4298
  hasLoneSurrogate,
4054
4299
  highlight,
@@ -4060,6 +4305,7 @@ export {
4060
4305
  normalizeId,
4061
4306
  pangu,
4062
4307
  parseStage,
4308
+ phantomSuffixCloser,
4063
4309
  preprocessAIMDContent,
4064
4310
  preprocessLaTeX,
4065
4311
  rehypeFooterAdorn,