@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.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 (false) {
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 (false) {
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) {
@@ -3759,6 +4016,14 @@ var graphemeEnds = (text, base) => {
3759
4016
  }
3760
4017
  return ends;
3761
4018
  };
4019
+ var RESUME_LOOKBACK = 64;
4020
+ var isMidSurrogatePair = (text, at) => at > 0 && at < text.length && (text.charCodeAt(at - 1) & 64512) === 55296 && (text.charCodeAt(at) & 64512) === 56320;
4021
+ var RI_FIRST = 127462;
4022
+ var RI_LAST = 127487;
4023
+ var isRegionalIndicatorAt = (text, at) => {
4024
+ const cp = text.codePointAt(at);
4025
+ return cp !== void 0 && cp >= RI_FIRST && cp <= RI_LAST;
4026
+ };
3762
4027
  var createSmoothStreamController = (options = {}) => {
3763
4028
  const now = options.now ?? defaultNow;
3764
4029
  const schedule = options.schedule ?? defaultSchedule;
@@ -3767,6 +4032,7 @@ var createSmoothStreamController = (options = {}) => {
3767
4032
  let pending = [];
3768
4033
  let tentativeEnd = 0;
3769
4034
  let finished = false;
4035
+ let seam;
3770
4036
  let drainDeadlineAt;
3771
4037
  let initialized = false;
3772
4038
  let visibleCache = "";
@@ -3850,15 +4116,31 @@ var createSmoothStreamController = (options = {}) => {
3850
4116
  if (!disposed && !cancelFrame && pending.length > 0) cancelFrame = schedule(tick);
3851
4117
  };
3852
4118
  const resegmentTail = () => {
4119
+ if (seam !== void 0 && seam < source.length && pending[pending.length - 1] === seam) {
4120
+ pending.pop();
4121
+ }
3853
4122
  const from = pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
3854
- const ends = graphemeEnds(source.slice(from), from);
4123
+ let anchor = from;
4124
+ if (seam !== void 0 && from <= seam) {
4125
+ anchor = Math.max(0, from - RESUME_LOOKBACK);
4126
+ if (isMidSurrogatePair(source, anchor)) anchor -= 1;
4127
+ while (anchor >= 2 && isRegionalIndicatorAt(source, anchor - 2)) anchor -= 2;
4128
+ } else {
4129
+ seam = void 0;
4130
+ }
4131
+ const ends = graphemeEnds(source.slice(anchor), anchor);
3855
4132
  if (ends.length === 0) {
3856
4133
  tentativeEnd = from;
3857
4134
  return;
3858
4135
  }
3859
4136
  tentativeEnd = ends[ends.length - 1];
3860
- const confirmedEnds = finished ? ends : ends.slice(0, -1);
3861
- for (const end of confirmedEnds) pending.push(end);
4137
+ let hold = 1;
4138
+ if (!finished && ends.length > 1) {
4139
+ const last = source.charCodeAt(source.length - 1);
4140
+ if (last >= 55296 && last <= 56319) hold = 2;
4141
+ }
4142
+ const confirmedEnds = finished ? ends : ends.slice(0, -hold);
4143
+ for (const end of confirmedEnds) if (end > from) pending.push(end);
3862
4144
  };
3863
4145
  const snap = (next) => {
3864
4146
  disposed = false;
@@ -3872,6 +4154,7 @@ var createSmoothStreamController = (options = {}) => {
3872
4154
  visibleEnd = next.length;
3873
4155
  tentativeEnd = next.length;
3874
4156
  pending = [];
4157
+ seam = next.length;
3875
4158
  credit = 0;
3876
4159
  cancelScheduled();
3877
4160
  if (visibleCache !== next) notify();
@@ -3906,6 +4189,7 @@ var createSmoothStreamController = (options = {}) => {
3906
4189
  if (tentativeEnd > (pending.length > 0 ? pending[pending.length - 1] : visibleEnd)) {
3907
4190
  pending.push(tentativeEnd);
3908
4191
  }
4192
+ seam = source.length;
3909
4193
  ensureScheduled();
3910
4194
  },
3911
4195
  snap,
@@ -3985,10 +4269,11 @@ function findClosingBacktickRun(content, start, n) {
3985
4269
  const runLen = getRepeatedMarkerLength(content, i, "`");
3986
4270
  if (runLen === n) return i;
3987
4271
  i += runLen;
3988
- } else if (ch === "\n") {
4272
+ } else if (ch === "\n" || ch === "\r") {
3989
4273
  let j = i + 1;
3990
- while (j < content.length && (content[j] === " " || content[j] === " " || content[j] === "\r")) j += 1;
3991
- if (j >= content.length || content[j] === "\n") return -1;
4274
+ if (ch === "\r" && content[j] === "\n") j += 1;
4275
+ while (j < content.length && (content[j] === " " || content[j] === " ")) j += 1;
4276
+ if (j >= content.length || content[j] === "\n" || content[j] === "\r") return -1;
3992
4277
  i += 1;
3993
4278
  } else {
3994
4279
  i += 1;
@@ -4089,26 +4374,54 @@ function escapeMhchemCommands(text) {
4089
4374
  return text.replaceAll("$\\ce{", "$\\\\ce{").replaceAll("$\\pu{", "$\\\\pu{");
4090
4375
  }
4091
4376
  var CURRENCY_REGEX = /(?<![\\$])\$(?!\$)(?=\d+(?:,\d{3})*(?:\.\d+)?(?:[KMBkmb])?(?:\s|$|[^a-zA-Z\d]))/g;
4092
- var NO_ESCAPED_DOLLAR_REGEX = /(?<![\\$])\$(?!\$)/g;
4093
4377
  var DELIMITERS_REGEX = /(?<!!)\\\[([\S\s]*?[^\\])\\](?!\()|\\\((.*?)\\\)/g;
4094
4378
  var ARRAY_COL_SPEC_OR_PIPE_REGEX = /(\\begin\{(?:array|tabular[x*]?)\}\{[^}]*\})|(?<!\\)\|/g;
4095
- var LATEX_BLOCK_REGEX = /\$\$([\S\s]*?)\$\$|(?<![\\$])\$(?!\$)((?:[^$\n]|\\\$)*?)(?<![\\`])\$(?!\$)/g;
4379
+ var EVEN_BACKSLASHES = String.raw`(?<=(?:^|[^\\])(?:\\\\)*)`;
4380
+ var LATEX_BLOCK_REGEX = new RegExp(
4381
+ String.raw`${EVEN_BACKSLASHES}\$\$([\S\s]*?)${EVEN_BACKSLASHES}\$\$|(?<![\\$])\$(?!\$)((?:[^$\n]|\\\$)*?)(?<![\\` + "`" + String.raw`])\$(?!\$)`,
4382
+ "g"
4383
+ );
4096
4384
  var TEXT_COMMAND = "\\text{";
4097
4385
  var SINGLE_DOLLAR_REGEX = /(?<![\\$])\$(?!\$)((?:[^$\n]|\\[$])+?)(?<!\\)(?<!`)\$(?!\$)/g;
4386
+ function countBareDollars(str, from, to, prev, next) {
4387
+ let n = 0;
4388
+ for (let j = from; j < to; j++) {
4389
+ if (str.charCodeAt(j) !== 36) continue;
4390
+ const before = j > from ? str[j - 1] : prev;
4391
+ const after = j + 1 < to ? str[j + 1] : next;
4392
+ if (before !== "\\" && before !== "$" && after !== "$") n += 1;
4393
+ }
4394
+ return n;
4395
+ }
4098
4396
  function escapeCurrencyDollarSigns(text) {
4099
4397
  const parts = [];
4100
4398
  let lastIndex = 0;
4101
4399
  const currencyMatches = Array.from(text.matchAll(CURRENCY_REGEX));
4102
4400
  let currentLineProcessed = "";
4401
+ let currentLineDollars = 0;
4402
+ const appendToLine = (piece) => {
4403
+ if (piece.length === 0) return;
4404
+ const prevLast = currentLineProcessed.length > 0 ? currentLineProcessed[currentLineProcessed.length - 1] : "";
4405
+ const prevBeforeLast = currentLineProcessed.length > 1 ? currentLineProcessed[currentLineProcessed.length - 2] : "";
4406
+ const prevLastCounted = prevLast === "$" && prevBeforeLast !== "\\" && prevBeforeLast !== "$";
4407
+ if (prevLastCounted && piece[0] === "$") currentLineDollars -= 1;
4408
+ currentLineDollars += countBareDollars(piece, 0, piece.length, prevLast, "");
4409
+ currentLineProcessed += piece;
4410
+ };
4411
+ const resetLine = (rest) => {
4412
+ currentLineProcessed = "";
4413
+ currentLineDollars = 0;
4414
+ appendToLine(rest);
4415
+ };
4103
4416
  for (let i = 0; i < currencyMatches.length; i++) {
4104
4417
  const match = currencyMatches[i];
4105
4418
  const segment = text.substring(lastIndex, match.index);
4106
4419
  parts.push(segment);
4107
4420
  const newlineIdx = Math.max(segment.lastIndexOf("\n"), segment.lastIndexOf("\r"));
4108
4421
  if (newlineIdx !== -1) {
4109
- currentLineProcessed = segment.substring(newlineIdx + 1);
4422
+ resetLine(segment.substring(newlineIdx + 1));
4110
4423
  } else {
4111
- currentLineProcessed += segment;
4424
+ appendToLine(segment);
4112
4425
  }
4113
4426
  let needEscape = true;
4114
4427
  const restStart = match.index + 1;
@@ -4125,15 +4438,21 @@ function escapeCurrencyDollarSigns(text) {
4125
4438
  }
4126
4439
  firstLineBeforeNextMatch = text.substring(restStart, eol);
4127
4440
  }
4128
- if (Array.from(firstLineBeforeNextMatch.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
4129
- const wholeLineBeforeNextMatchWithoutCurrentDollar = currentLineProcessed + firstLineBeforeNextMatch;
4130
- if (Array.from(wholeLineBeforeNextMatchWithoutCurrentDollar.matchAll(NO_ESCAPED_DOLLAR_REGEX)).length % 2 !== 0) {
4131
- needEscape = false;
4132
- }
4441
+ const restDollars = countBareDollars(firstLineBeforeNextMatch, 0, firstLineBeforeNextMatch.length, "", "");
4442
+ if (restDollars % 2 !== 0) {
4443
+ const L = currentLineProcessed;
4444
+ const lLast = L.length > 0 ? L[L.length - 1] : "";
4445
+ const lBeforeLast = L.length > 1 ? L[L.length - 2] : "";
4446
+ const lLastCounted = lLast === "$" && lBeforeLast !== "\\" && lBeforeLast !== "$";
4447
+ const f0 = firstLineBeforeNextMatch[0];
4448
+ let whole = currentLineDollars + restDollars;
4449
+ if (lLastCounted && f0 === "$") whole -= 1;
4450
+ if (f0 === "$" && (lLast === "\\" || lLast === "$") && firstLineBeforeNextMatch[1] !== "$") whole -= 1;
4451
+ if (whole % 2 !== 0) needEscape = false;
4133
4452
  }
4134
4453
  const replacement = needEscape ? "\\$" : "$";
4135
4454
  parts.push(replacement);
4136
- currentLineProcessed += replacement;
4455
+ appendToLine(replacement);
4137
4456
  lastIndex = match.index + 1;
4138
4457
  }
4139
4458
  parts.push(text.substring(lastIndex));
@@ -4201,8 +4520,7 @@ function escapeLatexPipesInUnclosed(text) {
4201
4520
  const tail = text.substring(unclosedStart + delimLen);
4202
4521
  return before + delim + replaceUnescapedPipes(tail);
4203
4522
  }
4204
- function truncateUnclosedLatexBlock(text) {
4205
- const unclosedStart = findUnclosedDelimiterStart(text, "double-only");
4523
+ function truncateUnclosedLatexBlock(text, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4206
4524
  if (unclosedStart === -1) return text;
4207
4525
  return text.substring(0, unclosedStart).trimEnd();
4208
4526
  }
@@ -4313,8 +4631,9 @@ function processSliceInstrumented(slice, probe = true) {
4313
4631
  if (probe && hasUnclosedTextCommand(text)) quiescent = false;
4314
4632
  text = escapeTextUnderscores(text);
4315
4633
  text = convertSingleToDoubleDollar(text);
4634
+ let unclosedDouble;
4316
4635
  if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
4317
- const unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4636
+ unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4318
4637
  if (unclosedDouble !== -1) {
4319
4638
  quiescent = false;
4320
4639
  if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
@@ -4322,7 +4641,7 @@ function processSliceInstrumented(slice, probe = true) {
4322
4641
  }
4323
4642
  }
4324
4643
  }
4325
- text = truncateUnclosedLatexBlock(text);
4644
+ text = truncateUnclosedLatexBlock(text, unclosedDouble);
4326
4645
  parts.push(text);
4327
4646
  }
4328
4647
  return { out: parts.join(""), quiescent, truncatedAtSeamStart };
@@ -4450,7 +4769,6 @@ function createRemendPreprocessor(options) {
4450
4769
  }
4451
4770
  export {
4452
4771
  DEFAULT_PAYLOAD,
4453
- DEF_LINE_START_RE,
4454
4772
  PIPELINE_STAGES,
4455
4773
  SENTINEL_FN_CONTENT,
4456
4774
  SENTINEL_LINK_URL,
@@ -4485,7 +4803,6 @@ export {
4485
4803
  hasLoneSurrogate,
4486
4804
  highlight,
4487
4805
  isFootnoteSection,
4488
- lastRegionStart,
4489
4806
  measureStage,
4490
4807
  mergeClassNameAllowlist,
4491
4808
  normalizeForMatch,