@ai-react-markdown/engine 2.4.5 → 2.5.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
@@ -177,10 +177,73 @@ import { htmlBlockNames } from "micromark-util-html-tag-name";
177
177
  import { normalizeIdentifier } from "micromark-util-normalize-identifier";
178
178
  var TYPE6_NAMES = new Set(htmlBlockNames);
179
179
  var TABLE_PART_NAMES = /* @__PURE__ */ new Set(["td", "th", "tr", "tbody", "thead", "tfoot", "caption", "col", "colgroup"]);
180
+ var HTML_BREAKOUT_TAGS = /* @__PURE__ */ new Set([
181
+ "b",
182
+ "big",
183
+ "blockquote",
184
+ "body",
185
+ "br",
186
+ "center",
187
+ "code",
188
+ "dd",
189
+ "div",
190
+ "dl",
191
+ "dt",
192
+ "em",
193
+ "embed",
194
+ "h1",
195
+ "h2",
196
+ "h3",
197
+ "h4",
198
+ "h5",
199
+ "h6",
200
+ "head",
201
+ "hr",
202
+ "i",
203
+ "img",
204
+ "li",
205
+ "listing",
206
+ "menu",
207
+ "meta",
208
+ "nobr",
209
+ "ol",
210
+ "p",
211
+ "pre",
212
+ "ruby",
213
+ "s",
214
+ "small",
215
+ "span",
216
+ "strong",
217
+ "strike",
218
+ "sub",
219
+ "sup",
220
+ "table",
221
+ "tt",
222
+ "u",
223
+ "ul",
224
+ "var"
225
+ ]);
226
+ var HTML_INTEGRATION_POINTS = ["foreignobject", "desc", "mi", "mo", "mn", "ms", "mtext", "annotation-xml"];
180
227
  var TYPE1_NAMES = /* @__PURE__ */ new Set(["script", "pre", "style", "textarea"]);
228
+ var RAW_TEXT_ELEMENTS = /* @__PURE__ */ new Set([
229
+ "script",
230
+ "style",
231
+ "textarea",
232
+ "title",
233
+ "xmp",
234
+ "iframe",
235
+ "noembed",
236
+ "noframes",
237
+ // NOT `noscript`: hast-util-raw constructs parse5 with
238
+ // `scriptingEnabled: false`, under which `<noscript>` content is ordinary
239
+ // HTML — modelling it as raw text ignored a `<b>` inside and under-blocked
240
+ // (oracle review of the r2 batch; regression caught before release).
241
+ "plaintext"
242
+ ]);
181
243
  var TYPE6_START_RE = /^<\/?([A-Za-z][A-Za-z0-9-]*)(?:[ \t\r]|\/?>|$)/;
182
244
  var TYPE1_START_RE = /^<(script|pre|style|textarea)(?:[ \t\r]|>|$)/i;
183
- var TYPE7_LINE_RE = /^<(\/?)([A-Za-z][A-Za-z0-9-]*)(?:[ \t\r][^>]*|\/)?>[ \t\r]*$/;
245
+ var TYPE7_LINE_RE = /^(?:<[A-Za-z][A-Za-z0-9-]*(?:[ \t\r][^>]*|\/)?>|<\/[A-Za-z][A-Za-z0-9-]*[ \t\r]*>)[ \t\r]*$/;
246
+ var t7Name = (line) => /^<\/?([A-Za-z][A-Za-z0-9-]*)/.exec(line)[1];
184
247
  var VOID_TAGS = /* @__PURE__ */ new Set([
185
248
  "area",
186
249
  "base",
@@ -204,7 +267,35 @@ var DEF_RE = /^ {0,3}\[((?:[^[\]\\]|\\.)+)\]:/;
204
267
  var FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
205
268
  var MATH_RUN_RE = /^ {0,3}(\$\$+)/;
206
269
  var TAG_OR_COMMENT_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)(?=[\s/>])([^>]*)>|<!--|-->|--!>/g;
207
- var TRUNCATED_TAG_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)([^<>]*)$/;
270
+ var TRUNCATED_TAG_RE = /<(\/?)([A-Za-z][A-Za-z0-9-]*)([^>]*)$/;
271
+ var TAG_START_LT_RE = /<\/?[A-Za-z]/g;
272
+ function scanTagAttrs(text, from, to, out) {
273
+ let st = out.state;
274
+ for (let i = from; i < to; i++) {
275
+ const c = text[i];
276
+ if (st === '"' || st === "'") {
277
+ if (c === st) st = "outside";
278
+ continue;
279
+ }
280
+ const ws = c === " " || c === " " || c === "\n" || c === "\r" || c === "\f";
281
+ if (st === "afterEq") {
282
+ if (ws) continue;
283
+ if (c === '"' || c === "'") st = c;
284
+ else if (c === ">") return i;
285
+ else st = "unquoted";
286
+ continue;
287
+ }
288
+ if (st === "unquoted") {
289
+ if (ws) st = "outside";
290
+ else if (c === ">") return i;
291
+ continue;
292
+ }
293
+ if (c === "=") st = "afterEq";
294
+ else if (c === ">") return i;
295
+ }
296
+ out.state = st;
297
+ return -1;
298
+ }
208
299
  var REF_RE = /!?\[((?:[^[\]\\]|\\.)*)\]/g;
209
300
  var BACKTICK_RUN_RE = /`+/g;
210
301
  var MD_BLANK_RE = /^[ \t\r]*$/;
@@ -299,6 +390,8 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
299
390
  openTotal: 0,
300
391
  commentOpen: false,
301
392
  piOpen: false,
393
+ bogusOpen: false,
394
+ rawTextOpen: null,
302
395
  declOpen: false,
303
396
  cdataOpen: false,
304
397
  inFence: false,
@@ -323,6 +416,7 @@ function freshCheckpoint(defListEnabled, mathFlow, referenceTaint) {
323
416
  pendingTruncatedCloses: [],
324
417
  tagAcrossLines: false,
325
418
  tagAcrossLinesIndent: 0,
419
+ tagAcrossLinesState: "outside",
326
420
  htmlFlowReal: false
327
421
  };
328
422
  }
@@ -422,11 +516,21 @@ function computeFreezeBoundary(text, options, resume) {
422
516
  let start = cp.confirmedOffset;
423
517
  let tailLine = null;
424
518
  while (start < text.length) {
425
- let end = text.indexOf("\n", start);
426
- if (end === -1) end = text.length;
427
- const confirmed = end < text.length;
428
- const rawLine = text.slice(start, end);
429
- const lineText = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
519
+ let end = text.length;
520
+ let textEnd = text.length;
521
+ {
522
+ const nl = text.indexOf("\n", start);
523
+ const cr = text.indexOf("\r", start);
524
+ if (cr !== -1 && (nl === -1 || cr < nl)) {
525
+ textEnd = cr;
526
+ end = text.charCodeAt(cr + 1) === 10 ? cr + 1 : cr;
527
+ } else if (nl !== -1) {
528
+ textEnd = nl;
529
+ end = nl;
530
+ }
531
+ }
532
+ const confirmed = end < text.length && !(end === text.length - 1 && text.charCodeAt(end) === 13);
533
+ const lineText = text.slice(start, textEnd);
430
534
  const ln = {
431
535
  start,
432
536
  end,
@@ -518,14 +622,33 @@ function processConfirmedLine(cp, ln, text) {
518
622
  newest.defListSettled = ln.blank ? true : !canBecomeDdLine(ln.text, true);
519
623
  }
520
624
  const isBlockStart = cp.prevLineBlank;
521
- if (cp.htmlSeamPending && !ln.blank && !cp.htmlFlowSinceBlank && !(cp.commentOpen || cp.piOpen || cp.declOpen || cp.cdataOpen)) {
625
+ if (cp.htmlSeamPending && !ln.blank && !cp.htmlFlowSinceBlank && !(cp.commentOpen || cp.piOpen || cp.declOpen || cp.cdataOpen || cp.bogusOpen)) {
522
626
  const defShapedLine = DEF_RE.test(ln.text) || FOOTNOTE_DEF_RE.test(ln.text);
523
627
  const commentOnly = ln.text.replace(/<!--[\s\S]*?-->/g, " ").replace(/<!--[\s\S]*$/, " ").replace(/[ \t\r]/g, "") === "";
524
628
  if (!defShapedLine && !commentOnly) {
525
629
  cp.htmlSeamPending = false;
526
630
  }
527
631
  }
632
+ const inForeignContent = () => (cp.tagBalance.get("svg") ?? 0) > 0 || (cp.tagBalance.get("math") ?? 0) > 0;
633
+ const honoursSelfClosing = (tag) => {
634
+ if (tag === "svg" || tag === "math") return true;
635
+ if (!inForeignContent() || HTML_BREAKOUT_TAGS.has(tag)) return false;
636
+ for (const ip of HTML_INTEGRATION_POINTS) if ((cp.tagBalance.get(ip) ?? 0) > 0) return false;
637
+ return true;
638
+ };
639
+ const htmlRulesApply = () => {
640
+ if (!inForeignContent()) return true;
641
+ for (const ip of HTML_INTEGRATION_POINTS) if ((cp.tagBalance.get(ip) ?? 0) > 0) return true;
642
+ return false;
643
+ };
528
644
  const applyTag = (tag, closing) => {
645
+ if (cp.rawTextOpen !== null) {
646
+ if (!(closing && tag === cp.rawTextOpen)) return;
647
+ cp.rawTextOpen = null;
648
+ } else if (!closing && RAW_TEXT_ELEMENTS.has(tag) && htmlRulesApply()) {
649
+ cp.rawTextOpen = tag;
650
+ if (tag === "plaintext") cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start);
651
+ }
529
652
  if (closing) {
530
653
  const count = cp.tagBalance.get(tag) ?? 0;
531
654
  if (count > 0) {
@@ -537,8 +660,9 @@ function processConfirmedLine(cp, ln, text) {
537
660
  cp.openTotal += 1;
538
661
  }
539
662
  };
663
+ const strayTablePart = (tag) => TABLE_PART_NAMES.has(tag) && (cp.tagBalance.get("table") ?? 0) === 0;
540
664
  const commentOpenAtLineStart = cp.commentOpen;
541
- const rawOpenAtLineStart = cp.commentOpen || cp.piOpen || cp.declOpen || cp.cdataOpen;
665
+ const rawOpenAtLineStart = cp.commentOpen || cp.piOpen || cp.declOpen || cp.cdataOpen || cp.bogusOpen;
542
666
  if (cp.inFence) {
543
667
  const close = FENCE_RE.exec(ln.text);
544
668
  if (close && close[1][0] === cp.fenceChar && close[1].length >= cp.fenceLen && isMdBlank(ln.text.slice(close[0].length))) {
@@ -621,13 +745,21 @@ function processConfirmedLine(cp, ln, text) {
621
745
  cp.pendingTruncatedTags = [];
622
746
  }
623
747
  cp.pendingTruncatedCloses = [];
748
+ if (cp.tagAcrossLines && (cp.tagAcrossLinesState === '"' || cp.tagAcrossLinesState === "'")) {
749
+ cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start);
750
+ }
624
751
  cp.tagAcrossLines = false;
752
+ cp.tagAcrossLinesState = "outside";
753
+ if (cp.bogusOpen) {
754
+ cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start);
755
+ cp.bogusOpen = false;
756
+ }
625
757
  cp.blankRun += 1;
626
758
  cp.lastBlankStart = ln.start;
627
759
  cp.candidates.push({
628
760
  offset: Math.min(ln.end + 1, text.length),
629
761
  blankRun: cp.blankRun,
630
- htmlBalanced: cp.openTotal === 0 && !cp.commentOpen && !cp.piOpen && !cp.declOpen && !cp.cdataOpen,
762
+ htmlBalanced: cp.openTotal === 0 && !cp.commentOpen && !cp.piOpen && !cp.declOpen && !cp.cdataOpen && !cp.bogusOpen,
631
763
  hazard: cp.hazardVerdict,
632
764
  seamRisk: cp.htmlSeamPending,
633
765
  defListSettled: null
@@ -657,7 +789,7 @@ function processConfirmedLine(cp, ln, text) {
657
789
  const t7 = TYPE7_LINE_RE.exec(t);
658
790
  if (t6 !== null && TYPE6_NAMES.has(t6[1].toLowerCase()) || TYPE1_START_RE.test(t) || // Type 7 cannot interrupt a paragraph, and excludes the raw-text
659
791
  // names (those are type 1 as start tags, paragraph as end tags).
660
- t7 !== null && !cp.prevLineWasText && !TYPE1_NAMES.has(t7[2].toLowerCase())) {
792
+ t7 !== null && !cp.prevLineWasText && !TYPE1_NAMES.has(t7Name(t).toLowerCase())) {
661
793
  cp.htmlFlowReal = true;
662
794
  }
663
795
  }
@@ -759,7 +891,7 @@ ${cont(scanText)}` };
759
891
  pos = c + 3;
760
892
  continue;
761
893
  }
762
- if (cp.declOpen) {
894
+ if (cp.declOpen || cp.bogusOpen) {
763
895
  const c = scanText.indexOf(">", pos);
764
896
  if (c === -1) {
765
897
  rawSpans.push([pos, scanText.length]);
@@ -767,6 +899,7 @@ ${cont(scanText)}` };
767
899
  }
768
900
  rawSpans.push([pos, c + 1]);
769
901
  cp.declOpen = false;
902
+ cp.bogusOpen = false;
770
903
  pos = c + 1;
771
904
  continue;
772
905
  }
@@ -774,10 +907,16 @@ ${cont(scanText)}` };
774
907
  const cd = scanText.indexOf("<![CDATA[", pos);
775
908
  const dm = scanText.slice(pos).search(/<![A-Za-z]/);
776
909
  const decl = dm === -1 ? -1 : pos + dm;
777
- const starts = [pi, cd, decl].filter((x) => x !== -1);
910
+ const bm = cp.htmlFlowReal ? scanText.slice(pos).search(/<!(?!--|[A-Za-z]|\[CDATA\[)|<\/(?![A-Za-z])/) : -1;
911
+ const bogus = bm === -1 ? -1 : pos + bm;
912
+ const starts = [pi, cd, decl, bogus].filter((x) => x !== -1);
778
913
  if (starts.length === 0) break;
779
914
  const first = Math.min(...starts);
780
- if (first === cd) {
915
+ if (first === bogus) {
916
+ rawSpans.push([bogus, bogus + 2]);
917
+ cp.bogusOpen = true;
918
+ pos = bogus + 2;
919
+ } else if (first === cd) {
781
920
  rawSpans.push([cd, cd + 9]);
782
921
  cp.cdataOpen = true;
783
922
  pos = cd + 9;
@@ -804,22 +943,27 @@ ${cont(scanText)}` };
804
943
  let skipTagScan = false;
805
944
  if (cp.tagAcrossLines) {
806
945
  if (ln.indent < cp.tagAcrossLinesIndent) poisonRawDivergence();
807
- const gt = ln.text.indexOf(">");
946
+ const attrs = { state: cp.tagAcrossLinesState };
947
+ const gt = scanTagAttrs(ln.text, 0, ln.text.length, attrs);
808
948
  if (gt === -1) {
949
+ scanTagAttrs("\n", 0, 1, attrs);
950
+ cp.tagAcrossLinesState = attrs.state;
809
951
  skipTagScan = true;
810
952
  } else {
811
- if (/["']/.test(ln.text.slice(0, gt))) poisonRawDivergence();
812
953
  for (const tag of cp.pendingTruncatedCloses) applyTag(tag, true);
813
954
  cp.pendingTruncatedCloses = [];
814
955
  cp.tagAcrossLines = false;
956
+ cp.tagAcrossLinesState = "outside";
815
957
  tagText = " ".repeat(gt + 1) + tagText.slice(gt + 1);
816
958
  }
817
959
  }
960
+ let tagHandledAsTruncated = false;
818
961
  if (!skipTagScan) {
819
962
  TAG_OR_COMMENT_RE.lastIndex = 0;
820
963
  let m;
821
964
  let lastCommentOpenerIdx = -1;
822
965
  while ((m = TAG_OR_COMMENT_RE.exec(tagText)) !== null) {
966
+ if (cp.rawTextOpen !== null && (m[0] === "<!--" || m[0] === "-->" || m[0] === "--!>")) continue;
823
967
  if (m[0] === "<!--") {
824
968
  const next = tagText.slice(m.index + 4, m.index + 6);
825
969
  if (cp.commentOpen) {
@@ -845,9 +989,32 @@ ${cont(scanText)}` };
845
989
  if (cp.commentOpen) continue;
846
990
  const closing = m[1] === "/";
847
991
  const tag = m[2].toLowerCase();
848
- if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + m.index);
849
- const selfClosing = m[3] !== void 0 && /\/\s*$/.test(m[3]);
850
- if (VOID_TAGS.has(tag) || selfClosing) continue;
992
+ if (strayTablePart(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + m.index);
993
+ let attrs = m[3] ?? "";
994
+ if (cp.htmlFlowReal && (cp.rawTextOpen === null || closing && tag === cp.rawTextOpen)) {
995
+ const attrStart = m.index + 1 + (closing ? 1 : 0) + m[2].length;
996
+ const st = { state: "outside" };
997
+ const gt = scanTagAttrs(tagText, attrStart, tagText.length, st);
998
+ if (gt === -1) {
999
+ if (!VOID_TAGS.has(tag)) {
1000
+ if (closing) cp.pendingTruncatedCloses.push(tag);
1001
+ else applyTag(tag, false);
1002
+ }
1003
+ scanTagAttrs("\n", 0, 1, st);
1004
+ cp.tagAcrossLines = true;
1005
+ cp.tagAcrossLinesIndent = ln.indent;
1006
+ cp.tagAcrossLinesState = st.state;
1007
+ tagHandledAsTruncated = true;
1008
+ break;
1009
+ }
1010
+ if (gt + 1 !== m.index + m[0].length) {
1011
+ attrs = tagText.slice(attrStart, gt);
1012
+ TAG_OR_COMMENT_RE.lastIndex = gt + 1;
1013
+ }
1014
+ }
1015
+ if (closing && !cp.htmlFlowReal && !/^\s*$/.test(attrs)) continue;
1016
+ const selfClosing = /\/\s*$/.test(attrs);
1017
+ if (VOID_TAGS.has(tag) || selfClosing && honoursSelfClosing(tag)) continue;
851
1018
  applyTag(tag, closing);
852
1019
  }
853
1020
  if (cp.commentOpen && lastCommentOpenerIdx !== -1 && !inRawText) {
@@ -866,26 +1033,40 @@ ${cont(scanText)}` };
866
1033
  if (startMasked || wholeVisible || inRaw(mr.index) || cp.commentOpen) continue;
867
1034
  const closing = mr[1] === "/";
868
1035
  const tag = mr[2].toLowerCase();
869
- if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + mr.index);
1036
+ if (strayTablePart(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + mr.index);
1037
+ if (closing && mr[3] !== void 0 && !/^\s*$/.test(mr[3])) continue;
870
1038
  const selfClosing = mr[3] !== void 0 && /\/\s*$/.test(mr[3]);
871
- if (VOID_TAGS.has(tag) || selfClosing) continue;
1039
+ if (VOID_TAGS.has(tag) || selfClosing && honoursSelfClosing(tag)) continue;
872
1040
  applyTag(tag, closing);
873
1041
  }
874
1042
  }
875
1043
  if (cp.pendingTruncatedTags.length > 0 && ln.text.includes(">")) {
1044
+ if (cp.pendingTruncatedTags.some((t) => strayTablePart(t))) {
1045
+ cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start);
1046
+ }
876
1047
  cp.pendingTruncatedTags = [];
877
1048
  }
878
- if (!cp.commentOpen) {
879
- const lastLt = tagText.lastIndexOf("<");
1049
+ if (!cp.commentOpen && !tagHandledAsTruncated) {
1050
+ let lastLt = -1;
1051
+ TAG_START_LT_RE.lastIndex = 0;
1052
+ for (let ms = TAG_START_LT_RE.exec(tagText); ms !== null; ms = TAG_START_LT_RE.exec(tagText)) {
1053
+ lastLt = ms.index;
1054
+ TAG_START_LT_RE.lastIndex = ms.index + 1;
1055
+ }
880
1056
  if (lastLt !== -1 && !tagText.includes(">", lastLt)) {
881
1057
  const m2 = TRUNCATED_TAG_RE.exec(tagText.slice(lastLt));
882
1058
  if (m2) {
883
1059
  const closing = m2[1] === "/";
884
1060
  const tag = m2[2].toLowerCase();
885
- if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastLt);
1061
+ if (strayTablePart(tag) && cp.htmlFlowReal) {
1062
+ cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastLt);
1063
+ }
886
1064
  if (cp.htmlFlowReal) {
887
1065
  cp.tagAcrossLines = true;
888
1066
  cp.tagAcrossLinesIndent = ln.indent;
1067
+ const attrs = { state: "outside" };
1068
+ scanTagAttrs(m2[3] + "\n", 0, m2[3].length + 1, attrs);
1069
+ cp.tagAcrossLinesState = attrs.state;
889
1070
  }
890
1071
  if (closing) {
891
1072
  if (!VOID_TAGS.has(tag) && cp.htmlFlowReal) cp.pendingTruncatedCloses.push(tag);
@@ -1102,6 +1283,24 @@ function rebaseDualWalk(node, segments, maxEnd, offsetDelta, lineDelta) {
1102
1283
  }
1103
1284
  }
1104
1285
  var TABLE_PART_TAG_RE = /<(?:td|th|tr|tbody|thead|tfoot|caption|col|colgroup)\b/i;
1286
+ var TABLE_TOKEN_RE = /<(\/?)(table|td|th|tr|tbody|thead|tfoot|caption|col|colgroup)\b/gi;
1287
+ function hasStrayTablePart(values) {
1288
+ let depth = 0;
1289
+ for (const value of values) {
1290
+ TABLE_TOKEN_RE.lastIndex = 0;
1291
+ let m;
1292
+ while ((m = TABLE_TOKEN_RE.exec(value)) !== null) {
1293
+ const closing = m[1] === "/";
1294
+ const tag = m[2].toLowerCase();
1295
+ if (tag === "table") {
1296
+ depth = closing ? Math.max(0, depth - 1) : depth + 1;
1297
+ continue;
1298
+ }
1299
+ if (depth === 0) return true;
1300
+ }
1301
+ }
1302
+ return false;
1303
+ }
1105
1304
  var STRAY_SYNTHESIZED_END_TAG_RE = /<\/(?:br|p)\b/i;
1106
1305
  function spliceTrees(input) {
1107
1306
  const { prevMdast, prevHast, tailMdast, tailHast, content, boundary, injectionPrefix, injectedSegments } = input;
@@ -1159,7 +1358,7 @@ function spliceTrees(input) {
1159
1358
  return !(start !== void 0 && start < injectedLen);
1160
1359
  });
1161
1360
  const tailWrapVisible = tailMdastChildren.some((child) => !isWrapInvisible(child));
1162
- if (prefixMdast.some((c) => c.type === "html" && TABLE_PART_TAG_RE.test(c.value))) return null;
1361
+ if (hasStrayTablePart(prefixMdast.flatMap((c) => c.type === "html" ? [c.value] : []))) return null;
1163
1362
  for (const child of tailMdastChildren) {
1164
1363
  if (isWrapInvisible(child)) continue;
1165
1364
  if (child.type !== "html") break;
@@ -1554,7 +1753,7 @@ function normalizeId(s) {
1554
1753
  return normalizeIdentifier3(s);
1555
1754
  }
1556
1755
  function normalizeForMatch(s) {
1557
- return normalizeIdentifier3(s.replace(/\\([!-/:-@[-`{-~])/g, "$1"));
1756
+ return normalizeIdentifier3(s);
1558
1757
  }
1559
1758
 
1560
1759
  // src/components/collectDefLabels.ts
@@ -1872,22 +2071,68 @@ function createRegistry(onEmpty) {
1872
2071
  labelSet: { footnoteLabels: /* @__PURE__ */ new Set(), linkLabels: /* @__PURE__ */ new Set() },
1873
2072
  version: 0,
1874
2073
  _reactIdMap: /* @__PURE__ */ new Map(),
2074
+ /** Symbol → its `documentIndex`, for chunks that supplied one. */
2075
+ _chunkIndex: /* @__PURE__ */ new Map(),
1875
2076
  _subscribers: /* @__PURE__ */ new Set(),
1876
2077
  _notifyScheduled: false,
1877
- allocateSymbol(reactId) {
2078
+ allocateSymbol(reactId, rawDocumentIndex) {
2079
+ const documentIndex = rawDocumentIndex !== void 0 && Number.isFinite(rawDocumentIndex) ? rawDocumentIndex : void 0;
2080
+ if (rawDocumentIndex !== void 0 && documentIndex === void 0) {
2081
+ console.warn(
2082
+ `[ai-react-markdown] documentIndex must be a finite number \u2014 received ${String(rawDocumentIndex)}; ignoring it for this chunk.`
2083
+ );
2084
+ }
1878
2085
  const existing = this._reactIdMap.get(reactId);
1879
2086
  if (existing) {
1880
2087
  existing.refcount++;
2088
+ const moved = documentIndex !== void 0 ? this._chunkIndex.get(existing.symbol) !== documentIndex : this._chunkIndex.has(existing.symbol);
2089
+ if (moved) {
2090
+ const at = this.chunkOrder.indexOf(existing.symbol);
2091
+ if (at !== -1) this.chunkOrder.splice(at, 1);
2092
+ this._placeChunk(existing.symbol, documentIndex);
2093
+ this._notify();
2094
+ }
1881
2095
  return existing.symbol;
1882
2096
  }
1883
2097
  const sym = Symbol(reactId);
1884
2098
  this._reactIdMap.set(reactId, { symbol: sym, refcount: 1 });
1885
- this.chunkOrder.push(sym);
2099
+ this._placeChunk(sym, documentIndex);
1886
2100
  this._notify();
1887
2101
  return sym;
1888
2102
  },
1889
- registerChunk(reactId, footnotes, links) {
1890
- const sym = this.allocateSymbol(reactId);
2103
+ /** Put `sym` into `chunkOrder` at its document position. Without an index
2104
+ * it goes last (mount order — the historical behaviour); with one it
2105
+ * sorts before the first chunk that sits later, where a chunk WITHOUT an
2106
+ * index counts as "later" so an indexed chunk never lands behind one
2107
+ * whose position is unknown. */
2108
+ _placeChunk(sym, documentIndex) {
2109
+ if (this.chunkOrder.length > 0) {
2110
+ const anyIndexed = documentIndex !== void 0 || this.chunkOrder.some((s) => this._chunkIndex.has(s));
2111
+ const anyPlain = documentIndex === void 0 || this.chunkOrder.some((s) => !this._chunkIndex.has(s));
2112
+ if (anyIndexed && anyPlain) {
2113
+ console.warn(
2114
+ "[ai-react-markdown] Some chunks of this document supply `documentIndex` and some do not. Indexed chunks always sort ahead of un-indexed ones, so footnote numbering and aggregate-footer placement will not follow document order. Pass `documentIndex` to every chunk, or to none."
2115
+ );
2116
+ }
2117
+ }
2118
+ if (documentIndex === void 0) {
2119
+ this._chunkIndex.delete(sym);
2120
+ this.chunkOrder.push(sym);
2121
+ return;
2122
+ }
2123
+ this._chunkIndex.set(sym, documentIndex);
2124
+ let at = this.chunkOrder.length;
2125
+ for (let i = 0; i < this.chunkOrder.length; i++) {
2126
+ const other = this._chunkIndex.get(this.chunkOrder[i]);
2127
+ if (other === void 0 || other > documentIndex) {
2128
+ at = i;
2129
+ break;
2130
+ }
2131
+ }
2132
+ this.chunkOrder.splice(at, 0, sym);
2133
+ },
2134
+ registerChunk(reactId, footnotes, links, documentIndex) {
2135
+ const sym = this.allocateSymbol(reactId, documentIndex);
1891
2136
  this.contributeLabels(sym, footnotes, links);
1892
2137
  return sym;
1893
2138
  },
@@ -1910,6 +2155,7 @@ function createRegistry(onEmpty) {
1910
2155
  this._reactIdMap.delete(reactId);
1911
2156
  const idx = this.chunkOrder.indexOf(entry.symbol);
1912
2157
  if (idx !== -1) this.chunkOrder.splice(idx, 1);
2158
+ this._chunkIndex.delete(entry.symbol);
1913
2159
  this.chunkData.delete(entry.symbol);
1914
2160
  const nextFn = /* @__PURE__ */ new Set();
1915
2161
  const nextLink = /* @__PURE__ */ new Set();
@@ -3536,17 +3782,28 @@ function mergeClassNameAllowlist(existing, extraClassNames) {
3536
3782
  return entries;
3537
3783
  }
3538
3784
  var crossChunkTags = ["cross-chunk-link", "cross-chunk-image", "footnote-sup"];
3539
- var sanitizeSchema = cloneDeep_default({
3540
- ...defaultSchema,
3541
- tagNames: [...defaultSchema.tagNames || [], "mark", ...crossChunkTags],
3542
- attributes: {
3543
- ...defaultSchema.attributes,
3544
- code: mergeClassNameAllowlist(defaultSchema.attributes?.code, ["math-inline", "math-display"]),
3545
- "cross-chunk-link": ["label", "referenceType", "documentId", "localUrl", "localTitle"],
3546
- "cross-chunk-image": ["label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
3547
- "footnote-sup": ["label", "localOccurrence", "localNumber", "documentId"]
3548
- }
3549
- });
3785
+ function deepFreeze(value) {
3786
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
3787
+ Object.freeze(value);
3788
+ for (const key of Object.keys(value)) deepFreeze(value[key]);
3789
+ }
3790
+ return value;
3791
+ }
3792
+ var STRIPPED_TAGS = ["script", "style", "title", "textarea", "noframes", "noembed", "xmp", "iframe", "plaintext"];
3793
+ var sanitizeSchema = deepFreeze(
3794
+ cloneDeep_default({
3795
+ ...defaultSchema,
3796
+ tagNames: [...defaultSchema.tagNames || [], "mark", ...crossChunkTags],
3797
+ attributes: {
3798
+ ...defaultSchema.attributes,
3799
+ code: mergeClassNameAllowlist(defaultSchema.attributes?.code, ["math-inline", "math-display"]),
3800
+ "cross-chunk-link": ["label", "referenceType", "documentId", "localUrl", "localTitle"],
3801
+ "cross-chunk-image": ["label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
3802
+ "footnote-sup": ["label", "localOccurrence", "localNumber", "documentId"]
3803
+ },
3804
+ strip: [.../* @__PURE__ */ new Set([...defaultSchema.strip || [], ...STRIPPED_TAGS])]
3805
+ })
3806
+ );
3550
3807
 
3551
3808
  // src/components/crossChunkUrlSanitize.ts
3552
3809
  function fakeElement(tagName, key, url) {
@@ -3771,6 +4028,14 @@ var graphemeEnds = (text, base) => {
3771
4028
  }
3772
4029
  return ends;
3773
4030
  };
4031
+ var RESUME_LOOKBACK = 64;
4032
+ var isMidSurrogatePair = (text, at) => at > 0 && at < text.length && (text.charCodeAt(at - 1) & 64512) === 55296 && (text.charCodeAt(at) & 64512) === 56320;
4033
+ var RI_FIRST = 127462;
4034
+ var RI_LAST = 127487;
4035
+ var isRegionalIndicatorAt = (text, at) => {
4036
+ const cp = text.codePointAt(at);
4037
+ return cp !== void 0 && cp >= RI_FIRST && cp <= RI_LAST;
4038
+ };
3774
4039
  var createSmoothStreamController = (options = {}) => {
3775
4040
  const now = options.now ?? defaultNow;
3776
4041
  const schedule = options.schedule ?? defaultSchedule;
@@ -3779,6 +4044,7 @@ var createSmoothStreamController = (options = {}) => {
3779
4044
  let pending = [];
3780
4045
  let tentativeEnd = 0;
3781
4046
  let finished = false;
4047
+ let seam;
3782
4048
  let drainDeadlineAt;
3783
4049
  let initialized = false;
3784
4050
  let visibleCache = "";
@@ -3862,15 +4128,31 @@ var createSmoothStreamController = (options = {}) => {
3862
4128
  if (!disposed && !cancelFrame && pending.length > 0) cancelFrame = schedule(tick);
3863
4129
  };
3864
4130
  const resegmentTail = () => {
4131
+ if (seam !== void 0 && seam < source.length && pending[pending.length - 1] === seam) {
4132
+ pending.pop();
4133
+ }
3865
4134
  const from = pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
3866
- const ends = graphemeEnds(source.slice(from), from);
4135
+ let anchor = from;
4136
+ if (seam !== void 0 && from <= seam) {
4137
+ anchor = Math.max(0, from - RESUME_LOOKBACK);
4138
+ if (isMidSurrogatePair(source, anchor)) anchor -= 1;
4139
+ while (anchor >= 2 && isRegionalIndicatorAt(source, anchor - 2)) anchor -= 2;
4140
+ } else {
4141
+ seam = void 0;
4142
+ }
4143
+ const ends = graphemeEnds(source.slice(anchor), anchor);
3867
4144
  if (ends.length === 0) {
3868
4145
  tentativeEnd = from;
3869
4146
  return;
3870
4147
  }
3871
4148
  tentativeEnd = ends[ends.length - 1];
3872
- const confirmedEnds = finished ? ends : ends.slice(0, -1);
3873
- for (const end of confirmedEnds) pending.push(end);
4149
+ let hold = 1;
4150
+ if (!finished && ends.length > 1) {
4151
+ const last = source.charCodeAt(source.length - 1);
4152
+ if (last >= 55296 && last <= 56319) hold = 2;
4153
+ }
4154
+ const confirmedEnds = finished ? ends : ends.slice(0, -hold);
4155
+ for (const end of confirmedEnds) if (end > from) pending.push(end);
3874
4156
  };
3875
4157
  const snap = (next) => {
3876
4158
  disposed = false;
@@ -3884,6 +4166,7 @@ var createSmoothStreamController = (options = {}) => {
3884
4166
  visibleEnd = next.length;
3885
4167
  tentativeEnd = next.length;
3886
4168
  pending = [];
4169
+ seam = next.length;
3887
4170
  credit = 0;
3888
4171
  cancelScheduled();
3889
4172
  if (visibleCache !== next) notify();
@@ -3918,6 +4201,7 @@ var createSmoothStreamController = (options = {}) => {
3918
4201
  if (tentativeEnd > (pending.length > 0 ? pending[pending.length - 1] : visibleEnd)) {
3919
4202
  pending.push(tentativeEnd);
3920
4203
  }
4204
+ seam = source.length;
3921
4205
  ensureScheduled();
3922
4206
  },
3923
4207
  snap,
@@ -3997,10 +4281,11 @@ function findClosingBacktickRun(content, start, n) {
3997
4281
  const runLen = getRepeatedMarkerLength(content, i, "`");
3998
4282
  if (runLen === n) return i;
3999
4283
  i += runLen;
4000
- } else if (ch === "\n") {
4284
+ } else if (ch === "\n" || ch === "\r") {
4001
4285
  let j = i + 1;
4002
- while (j < content.length && (content[j] === " " || content[j] === " " || content[j] === "\r")) j += 1;
4003
- if (j >= content.length || content[j] === "\n") return -1;
4286
+ if (ch === "\r" && content[j] === "\n") j += 1;
4287
+ while (j < content.length && (content[j] === " " || content[j] === " ")) j += 1;
4288
+ if (j >= content.length || content[j] === "\n" || content[j] === "\r") return -1;
4004
4289
  i += 1;
4005
4290
  } else {
4006
4291
  i += 1;
@@ -4101,26 +4386,54 @@ function escapeMhchemCommands(text) {
4101
4386
  return text.replaceAll("$\\ce{", "$\\\\ce{").replaceAll("$\\pu{", "$\\\\pu{");
4102
4387
  }
4103
4388
  var CURRENCY_REGEX = /(?<![\\$])\$(?!\$)(?=\d+(?:,\d{3})*(?:\.\d+)?(?:[KMBkmb])?(?:\s|$|[^a-zA-Z\d]))/g;
4104
- var NO_ESCAPED_DOLLAR_REGEX = /(?<![\\$])\$(?!\$)/g;
4105
4389
  var DELIMITERS_REGEX = /(?<!!)\\\[([\S\s]*?[^\\])\\](?!\()|\\\((.*?)\\\)/g;
4106
4390
  var ARRAY_COL_SPEC_OR_PIPE_REGEX = /(\\begin\{(?:array|tabular[x*]?)\}\{[^}]*\})|(?<!\\)\|/g;
4107
- var LATEX_BLOCK_REGEX = /\$\$([\S\s]*?)\$\$|(?<![\\$])\$(?!\$)((?:[^$\n]|\\\$)*?)(?<![\\`])\$(?!\$)/g;
4391
+ var EVEN_BACKSLASHES = String.raw`(?<=(?:^|[^\\])(?:\\\\)*)`;
4392
+ var LATEX_BLOCK_REGEX = new RegExp(
4393
+ String.raw`${EVEN_BACKSLASHES}\$\$([\S\s]*?)${EVEN_BACKSLASHES}\$\$|(?<![\\$])\$(?!\$)((?:[^$\n]|\\\$)*?)(?<![\\` + "`" + String.raw`])\$(?!\$)`,
4394
+ "g"
4395
+ );
4108
4396
  var TEXT_COMMAND = "\\text{";
4109
4397
  var SINGLE_DOLLAR_REGEX = /(?<![\\$])\$(?!\$)((?:[^$\n]|\\[$])+?)(?<!\\)(?<!`)\$(?!\$)/g;
4398
+ function countBareDollars(str, from, to, prev, next) {
4399
+ let n = 0;
4400
+ for (let j = from; j < to; j++) {
4401
+ if (str.charCodeAt(j) !== 36) continue;
4402
+ const before = j > from ? str[j - 1] : prev;
4403
+ const after = j + 1 < to ? str[j + 1] : next;
4404
+ if (before !== "\\" && before !== "$" && after !== "$") n += 1;
4405
+ }
4406
+ return n;
4407
+ }
4110
4408
  function escapeCurrencyDollarSigns(text) {
4111
4409
  const parts = [];
4112
4410
  let lastIndex = 0;
4113
4411
  const currencyMatches = Array.from(text.matchAll(CURRENCY_REGEX));
4114
4412
  let currentLineProcessed = "";
4413
+ let currentLineDollars = 0;
4414
+ const appendToLine = (piece) => {
4415
+ if (piece.length === 0) return;
4416
+ const prevLast = currentLineProcessed.length > 0 ? currentLineProcessed[currentLineProcessed.length - 1] : "";
4417
+ const prevBeforeLast = currentLineProcessed.length > 1 ? currentLineProcessed[currentLineProcessed.length - 2] : "";
4418
+ const prevLastCounted = prevLast === "$" && prevBeforeLast !== "\\" && prevBeforeLast !== "$";
4419
+ if (prevLastCounted && piece[0] === "$") currentLineDollars -= 1;
4420
+ currentLineDollars += countBareDollars(piece, 0, piece.length, prevLast, "");
4421
+ currentLineProcessed += piece;
4422
+ };
4423
+ const resetLine = (rest) => {
4424
+ currentLineProcessed = "";
4425
+ currentLineDollars = 0;
4426
+ appendToLine(rest);
4427
+ };
4115
4428
  for (let i = 0; i < currencyMatches.length; i++) {
4116
4429
  const match = currencyMatches[i];
4117
4430
  const segment = text.substring(lastIndex, match.index);
4118
4431
  parts.push(segment);
4119
4432
  const newlineIdx = Math.max(segment.lastIndexOf("\n"), segment.lastIndexOf("\r"));
4120
4433
  if (newlineIdx !== -1) {
4121
- currentLineProcessed = segment.substring(newlineIdx + 1);
4434
+ resetLine(segment.substring(newlineIdx + 1));
4122
4435
  } else {
4123
- currentLineProcessed += segment;
4436
+ appendToLine(segment);
4124
4437
  }
4125
4438
  let needEscape = true;
4126
4439
  const restStart = match.index + 1;
@@ -4137,15 +4450,21 @@ function escapeCurrencyDollarSigns(text) {
4137
4450
  }
4138
4451
  firstLineBeforeNextMatch = text.substring(restStart, eol);
4139
4452
  }
4140
- if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
4141
- const wholeLineBeforeNextMatchWithoutCurrentDollar = currentLineProcessed + firstLineBeforeNextMatch;
4142
- if (Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
4143
- needEscape = false;
4144
- }
4453
+ const restDollars = countBareDollars(firstLineBeforeNextMatch, 0, firstLineBeforeNextMatch.length, "", "");
4454
+ if (restDollars % 2 !== 0) {
4455
+ const L = currentLineProcessed;
4456
+ const lLast = L.length > 0 ? L[L.length - 1] : "";
4457
+ const lBeforeLast = L.length > 1 ? L[L.length - 2] : "";
4458
+ const lLastCounted = lLast === "$" && lBeforeLast !== "\\" && lBeforeLast !== "$";
4459
+ const f0 = firstLineBeforeNextMatch[0];
4460
+ let whole = currentLineDollars + restDollars;
4461
+ if (lLastCounted && f0 === "$") whole -= 1;
4462
+ if (f0 === "$" && (lLast === "\\" || lLast === "$") && firstLineBeforeNextMatch[1] !== "$") whole -= 1;
4463
+ if (whole % 2 !== 0) needEscape = false;
4145
4464
  }
4146
4465
  const replacement = needEscape ? "\\$" : "$";
4147
4466
  parts.push(replacement);
4148
- currentLineProcessed += replacement;
4467
+ appendToLine(replacement);
4149
4468
  lastIndex = match.index + 1;
4150
4469
  }
4151
4470
  parts.push(text.substring(lastIndex));
@@ -4213,8 +4532,7 @@ function escapeLatexPipesInUnclosed(text) {
4213
4532
  const tail = text.substring(unclosedStart + delimLen);
4214
4533
  return before + delim + replaceUnescapedPipes(tail);
4215
4534
  }
4216
- function truncateUnclosedLatexBlock(text) {
4217
- const unclosedStart = findUnclosedDelimiterStart(text, "double-only");
4535
+ function truncateUnclosedLatexBlock(text, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4218
4536
  if (unclosedStart === -1) return text;
4219
4537
  return text.substring(0, unclosedStart).trimEnd();
4220
4538
  }
@@ -4325,8 +4643,9 @@ function processSliceInstrumented(slice, probe = true) {
4325
4643
  if (probe && hasUnclosedTextCommand(text)) quiescent = false;
4326
4644
  text = escapeTextUnderscores(text);
4327
4645
  text = convertSingleToDoubleDollar(text);
4646
+ let unclosedDouble;
4328
4647
  if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
4329
- const unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4648
+ unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4330
4649
  if (unclosedDouble !== -1) {
4331
4650
  quiescent = false;
4332
4651
  if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
@@ -4334,7 +4653,7 @@ function processSliceInstrumented(slice, probe = true) {
4334
4653
  }
4335
4654
  }
4336
4655
  }
4337
- text = truncateUnclosedLatexBlock(text);
4656
+ text = truncateUnclosedLatexBlock(text, unclosedDouble);
4338
4657
  parts.push(text);
4339
4658
  }
4340
4659
  return { out: parts.join(""), quiescent, truncatedAtSeamStart };
@@ -4462,7 +4781,6 @@ function createRemendPreprocessor(options) {
4462
4781
  }
4463
4782
  export {
4464
4783
  DEFAULT_PAYLOAD,
4465
- DEF_LINE_START_RE,
4466
4784
  PIPELINE_STAGES,
4467
4785
  SENTINEL_FN_CONTENT,
4468
4786
  SENTINEL_LINK_URL,
@@ -4497,7 +4815,6 @@ export {
4497
4815
  hasLoneSurrogate,
4498
4816
  highlight,
4499
4817
  isFootnoteSection,
4500
- lastRegionStart,
4501
4818
  measureStage,
4502
4819
  mergeClassNameAllowlist,
4503
4820
  normalizeForMatch,