@ai-react-markdown/engine 2.4.5 → 2.5.0

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) {
@@ -538,7 +661,7 @@ function processConfirmedLine(cp, ln, text) {
538
661
  }
539
662
  };
540
663
  const commentOpenAtLineStart = cp.commentOpen;
541
- const rawOpenAtLineStart = cp.commentOpen || cp.piOpen || cp.declOpen || cp.cdataOpen;
664
+ const rawOpenAtLineStart = cp.commentOpen || cp.piOpen || cp.declOpen || cp.cdataOpen || cp.bogusOpen;
542
665
  if (cp.inFence) {
543
666
  const close = FENCE_RE.exec(ln.text);
544
667
  if (close && close[1][0] === cp.fenceChar && close[1].length >= cp.fenceLen && isMdBlank(ln.text.slice(close[0].length))) {
@@ -621,13 +744,21 @@ function processConfirmedLine(cp, ln, text) {
621
744
  cp.pendingTruncatedTags = [];
622
745
  }
623
746
  cp.pendingTruncatedCloses = [];
747
+ if (cp.tagAcrossLines && (cp.tagAcrossLinesState === '"' || cp.tagAcrossLinesState === "'")) {
748
+ cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start);
749
+ }
624
750
  cp.tagAcrossLines = false;
751
+ cp.tagAcrossLinesState = "outside";
752
+ if (cp.bogusOpen) {
753
+ cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start);
754
+ cp.bogusOpen = false;
755
+ }
625
756
  cp.blankRun += 1;
626
757
  cp.lastBlankStart = ln.start;
627
758
  cp.candidates.push({
628
759
  offset: Math.min(ln.end + 1, text.length),
629
760
  blankRun: cp.blankRun,
630
- htmlBalanced: cp.openTotal === 0 && !cp.commentOpen && !cp.piOpen && !cp.declOpen && !cp.cdataOpen,
761
+ htmlBalanced: cp.openTotal === 0 && !cp.commentOpen && !cp.piOpen && !cp.declOpen && !cp.cdataOpen && !cp.bogusOpen,
631
762
  hazard: cp.hazardVerdict,
632
763
  seamRisk: cp.htmlSeamPending,
633
764
  defListSettled: null
@@ -657,7 +788,7 @@ function processConfirmedLine(cp, ln, text) {
657
788
  const t7 = TYPE7_LINE_RE.exec(t);
658
789
  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
790
  // names (those are type 1 as start tags, paragraph as end tags).
660
- t7 !== null && !cp.prevLineWasText && !TYPE1_NAMES.has(t7[2].toLowerCase())) {
791
+ t7 !== null && !cp.prevLineWasText && !TYPE1_NAMES.has(t7Name(t).toLowerCase())) {
661
792
  cp.htmlFlowReal = true;
662
793
  }
663
794
  }
@@ -759,7 +890,7 @@ ${cont(scanText)}` };
759
890
  pos = c + 3;
760
891
  continue;
761
892
  }
762
- if (cp.declOpen) {
893
+ if (cp.declOpen || cp.bogusOpen) {
763
894
  const c = scanText.indexOf(">", pos);
764
895
  if (c === -1) {
765
896
  rawSpans.push([pos, scanText.length]);
@@ -767,6 +898,7 @@ ${cont(scanText)}` };
767
898
  }
768
899
  rawSpans.push([pos, c + 1]);
769
900
  cp.declOpen = false;
901
+ cp.bogusOpen = false;
770
902
  pos = c + 1;
771
903
  continue;
772
904
  }
@@ -774,10 +906,16 @@ ${cont(scanText)}` };
774
906
  const cd = scanText.indexOf("<![CDATA[", pos);
775
907
  const dm = scanText.slice(pos).search(/<![A-Za-z]/);
776
908
  const decl = dm === -1 ? -1 : pos + dm;
777
- const starts = [pi, cd, decl].filter((x) => x !== -1);
909
+ const bm = cp.htmlFlowReal ? scanText.slice(pos).search(/<!(?!--|[A-Za-z]|\[CDATA\[)|<\/(?![A-Za-z])/) : -1;
910
+ const bogus = bm === -1 ? -1 : pos + bm;
911
+ const starts = [pi, cd, decl, bogus].filter((x) => x !== -1);
778
912
  if (starts.length === 0) break;
779
913
  const first = Math.min(...starts);
780
- if (first === cd) {
914
+ if (first === bogus) {
915
+ rawSpans.push([bogus, bogus + 2]);
916
+ cp.bogusOpen = true;
917
+ pos = bogus + 2;
918
+ } else if (first === cd) {
781
919
  rawSpans.push([cd, cd + 9]);
782
920
  cp.cdataOpen = true;
783
921
  pos = cd + 9;
@@ -804,22 +942,27 @@ ${cont(scanText)}` };
804
942
  let skipTagScan = false;
805
943
  if (cp.tagAcrossLines) {
806
944
  if (ln.indent < cp.tagAcrossLinesIndent) poisonRawDivergence();
807
- const gt = ln.text.indexOf(">");
945
+ const attrs = { state: cp.tagAcrossLinesState };
946
+ const gt = scanTagAttrs(ln.text, 0, ln.text.length, attrs);
808
947
  if (gt === -1) {
948
+ scanTagAttrs("\n", 0, 1, attrs);
949
+ cp.tagAcrossLinesState = attrs.state;
809
950
  skipTagScan = true;
810
951
  } else {
811
- if (/["']/.test(ln.text.slice(0, gt))) poisonRawDivergence();
812
952
  for (const tag of cp.pendingTruncatedCloses) applyTag(tag, true);
813
953
  cp.pendingTruncatedCloses = [];
814
954
  cp.tagAcrossLines = false;
955
+ cp.tagAcrossLinesState = "outside";
815
956
  tagText = " ".repeat(gt + 1) + tagText.slice(gt + 1);
816
957
  }
817
958
  }
959
+ let tagHandledAsTruncated = false;
818
960
  if (!skipTagScan) {
819
961
  TAG_OR_COMMENT_RE.lastIndex = 0;
820
962
  let m;
821
963
  let lastCommentOpenerIdx = -1;
822
964
  while ((m = TAG_OR_COMMENT_RE.exec(tagText)) !== null) {
965
+ if (cp.rawTextOpen !== null && (m[0] === "<!--" || m[0] === "-->" || m[0] === "--!>")) continue;
823
966
  if (m[0] === "<!--") {
824
967
  const next = tagText.slice(m.index + 4, m.index + 6);
825
968
  if (cp.commentOpen) {
@@ -846,8 +989,31 @@ ${cont(scanText)}` };
846
989
  const closing = m[1] === "/";
847
990
  const tag = m[2].toLowerCase();
848
991
  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
+ let attrs = m[3] ?? "";
993
+ if (cp.htmlFlowReal && (cp.rawTextOpen === null || closing && tag === cp.rawTextOpen)) {
994
+ const attrStart = m.index + 1 + (closing ? 1 : 0) + m[2].length;
995
+ const st = { state: "outside" };
996
+ const gt = scanTagAttrs(tagText, attrStart, tagText.length, st);
997
+ if (gt === -1) {
998
+ if (!VOID_TAGS.has(tag)) {
999
+ if (closing) cp.pendingTruncatedCloses.push(tag);
1000
+ else applyTag(tag, false);
1001
+ }
1002
+ scanTagAttrs("\n", 0, 1, st);
1003
+ cp.tagAcrossLines = true;
1004
+ cp.tagAcrossLinesIndent = ln.indent;
1005
+ cp.tagAcrossLinesState = st.state;
1006
+ tagHandledAsTruncated = true;
1007
+ break;
1008
+ }
1009
+ if (gt + 1 !== m.index + m[0].length) {
1010
+ attrs = tagText.slice(attrStart, gt);
1011
+ TAG_OR_COMMENT_RE.lastIndex = gt + 1;
1012
+ }
1013
+ }
1014
+ if (closing && !cp.htmlFlowReal && !/^\s*$/.test(attrs)) continue;
1015
+ const selfClosing = /\/\s*$/.test(attrs);
1016
+ if (VOID_TAGS.has(tag) || selfClosing && honoursSelfClosing(tag)) continue;
851
1017
  applyTag(tag, closing);
852
1018
  }
853
1019
  if (cp.commentOpen && lastCommentOpenerIdx !== -1 && !inRawText) {
@@ -867,25 +1033,39 @@ ${cont(scanText)}` };
867
1033
  const closing = mr[1] === "/";
868
1034
  const tag = mr[2].toLowerCase();
869
1035
  if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + mr.index);
1036
+ if (closing && mr[3] !== void 0 && !/^\s*$/.test(mr[3])) continue;
870
1037
  const selfClosing = mr[3] !== void 0 && /\/\s*$/.test(mr[3]);
871
- if (VOID_TAGS.has(tag) || selfClosing) continue;
1038
+ if (VOID_TAGS.has(tag) || selfClosing && honoursSelfClosing(tag)) continue;
872
1039
  applyTag(tag, closing);
873
1040
  }
874
1041
  }
875
1042
  if (cp.pendingTruncatedTags.length > 0 && ln.text.includes(">")) {
1043
+ if (cp.pendingTruncatedTags.some((t) => TABLE_PART_NAMES.has(t))) {
1044
+ cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start);
1045
+ }
876
1046
  cp.pendingTruncatedTags = [];
877
1047
  }
878
- if (!cp.commentOpen) {
879
- const lastLt = tagText.lastIndexOf("<");
1048
+ if (!cp.commentOpen && !tagHandledAsTruncated) {
1049
+ let lastLt = -1;
1050
+ TAG_START_LT_RE.lastIndex = 0;
1051
+ for (let ms = TAG_START_LT_RE.exec(tagText); ms !== null; ms = TAG_START_LT_RE.exec(tagText)) {
1052
+ lastLt = ms.index;
1053
+ TAG_START_LT_RE.lastIndex = ms.index + 1;
1054
+ }
880
1055
  if (lastLt !== -1 && !tagText.includes(">", lastLt)) {
881
1056
  const m2 = TRUNCATED_TAG_RE.exec(tagText.slice(lastLt));
882
1057
  if (m2) {
883
1058
  const closing = m2[1] === "/";
884
1059
  const tag = m2[2].toLowerCase();
885
- if (TABLE_PART_NAMES.has(tag)) cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastLt);
1060
+ if (TABLE_PART_NAMES.has(tag) && cp.htmlFlowReal) {
1061
+ cp.phasePoisonedAt = Math.min(cp.phasePoisonedAt, ln.start + lastLt);
1062
+ }
886
1063
  if (cp.htmlFlowReal) {
887
1064
  cp.tagAcrossLines = true;
888
1065
  cp.tagAcrossLinesIndent = ln.indent;
1066
+ const attrs = { state: "outside" };
1067
+ scanTagAttrs(m2[3] + "\n", 0, m2[3].length + 1, attrs);
1068
+ cp.tagAcrossLinesState = attrs.state;
889
1069
  }
890
1070
  if (closing) {
891
1071
  if (!VOID_TAGS.has(tag) && cp.htmlFlowReal) cp.pendingTruncatedCloses.push(tag);
@@ -1554,7 +1734,7 @@ function normalizeId(s) {
1554
1734
  return normalizeIdentifier3(s);
1555
1735
  }
1556
1736
  function normalizeForMatch(s) {
1557
- return normalizeIdentifier3(s.replace(/\\([!-/:-@[-`{-~])/g, "$1"));
1737
+ return normalizeIdentifier3(s);
1558
1738
  }
1559
1739
 
1560
1740
  // src/components/collectDefLabels.ts
@@ -1872,22 +2052,68 @@ function createRegistry(onEmpty) {
1872
2052
  labelSet: { footnoteLabels: /* @__PURE__ */ new Set(), linkLabels: /* @__PURE__ */ new Set() },
1873
2053
  version: 0,
1874
2054
  _reactIdMap: /* @__PURE__ */ new Map(),
2055
+ /** Symbol → its `documentIndex`, for chunks that supplied one. */
2056
+ _chunkIndex: /* @__PURE__ */ new Map(),
1875
2057
  _subscribers: /* @__PURE__ */ new Set(),
1876
2058
  _notifyScheduled: false,
1877
- allocateSymbol(reactId) {
2059
+ allocateSymbol(reactId, rawDocumentIndex) {
2060
+ const documentIndex = rawDocumentIndex !== void 0 && Number.isFinite(rawDocumentIndex) ? rawDocumentIndex : void 0;
2061
+ if (false) {
2062
+ console.warn(
2063
+ `[ai-react-markdown] documentIndex must be a finite number \u2014 received ${String(rawDocumentIndex)}; ignoring it for this chunk.`
2064
+ );
2065
+ }
1878
2066
  const existing = this._reactIdMap.get(reactId);
1879
2067
  if (existing) {
1880
2068
  existing.refcount++;
2069
+ const moved = documentIndex !== void 0 ? this._chunkIndex.get(existing.symbol) !== documentIndex : this._chunkIndex.has(existing.symbol);
2070
+ if (moved) {
2071
+ const at = this.chunkOrder.indexOf(existing.symbol);
2072
+ if (at !== -1) this.chunkOrder.splice(at, 1);
2073
+ this._placeChunk(existing.symbol, documentIndex);
2074
+ this._notify();
2075
+ }
1881
2076
  return existing.symbol;
1882
2077
  }
1883
2078
  const sym = Symbol(reactId);
1884
2079
  this._reactIdMap.set(reactId, { symbol: sym, refcount: 1 });
1885
- this.chunkOrder.push(sym);
2080
+ this._placeChunk(sym, documentIndex);
1886
2081
  this._notify();
1887
2082
  return sym;
1888
2083
  },
1889
- registerChunk(reactId, footnotes, links) {
1890
- const sym = this.allocateSymbol(reactId);
2084
+ /** Put `sym` into `chunkOrder` at its document position. Without an index
2085
+ * it goes last (mount order — the historical behaviour); with one it
2086
+ * sorts before the first chunk that sits later, where a chunk WITHOUT an
2087
+ * index counts as "later" so an indexed chunk never lands behind one
2088
+ * whose position is unknown. */
2089
+ _placeChunk(sym, documentIndex) {
2090
+ if (false) {
2091
+ const anyIndexed = documentIndex !== void 0 || this.chunkOrder.some((s) => this._chunkIndex.has(s));
2092
+ const anyPlain = documentIndex === void 0 || this.chunkOrder.some((s) => !this._chunkIndex.has(s));
2093
+ if (anyIndexed && anyPlain) {
2094
+ console.warn(
2095
+ "[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."
2096
+ );
2097
+ }
2098
+ }
2099
+ if (documentIndex === void 0) {
2100
+ this._chunkIndex.delete(sym);
2101
+ this.chunkOrder.push(sym);
2102
+ return;
2103
+ }
2104
+ this._chunkIndex.set(sym, documentIndex);
2105
+ let at = this.chunkOrder.length;
2106
+ for (let i = 0; i < this.chunkOrder.length; i++) {
2107
+ const other = this._chunkIndex.get(this.chunkOrder[i]);
2108
+ if (other === void 0 || other > documentIndex) {
2109
+ at = i;
2110
+ break;
2111
+ }
2112
+ }
2113
+ this.chunkOrder.splice(at, 0, sym);
2114
+ },
2115
+ registerChunk(reactId, footnotes, links, documentIndex) {
2116
+ const sym = this.allocateSymbol(reactId, documentIndex);
1891
2117
  this.contributeLabels(sym, footnotes, links);
1892
2118
  return sym;
1893
2119
  },
@@ -1910,6 +2136,7 @@ function createRegistry(onEmpty) {
1910
2136
  this._reactIdMap.delete(reactId);
1911
2137
  const idx = this.chunkOrder.indexOf(entry.symbol);
1912
2138
  if (idx !== -1) this.chunkOrder.splice(idx, 1);
2139
+ this._chunkIndex.delete(entry.symbol);
1913
2140
  this.chunkData.delete(entry.symbol);
1914
2141
  const nextFn = /* @__PURE__ */ new Set();
1915
2142
  const nextLink = /* @__PURE__ */ new Set();
@@ -3536,17 +3763,28 @@ function mergeClassNameAllowlist(existing, extraClassNames) {
3536
3763
  return entries;
3537
3764
  }
3538
3765
  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
- });
3766
+ function deepFreeze(value) {
3767
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
3768
+ Object.freeze(value);
3769
+ for (const key of Object.keys(value)) deepFreeze(value[key]);
3770
+ }
3771
+ return value;
3772
+ }
3773
+ var STRIPPED_TAGS = ["script", "style", "title", "textarea", "noframes", "noembed", "xmp", "iframe", "plaintext"];
3774
+ var sanitizeSchema = deepFreeze(
3775
+ cloneDeep_default({
3776
+ ...defaultSchema,
3777
+ tagNames: [...defaultSchema.tagNames || [], "mark", ...crossChunkTags],
3778
+ attributes: {
3779
+ ...defaultSchema.attributes,
3780
+ code: mergeClassNameAllowlist(defaultSchema.attributes?.code, ["math-inline", "math-display"]),
3781
+ "cross-chunk-link": ["label", "referenceType", "documentId", "localUrl", "localTitle"],
3782
+ "cross-chunk-image": ["label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
3783
+ "footnote-sup": ["label", "localOccurrence", "localNumber", "documentId"]
3784
+ },
3785
+ strip: [.../* @__PURE__ */ new Set([...defaultSchema.strip || [], ...STRIPPED_TAGS])]
3786
+ })
3787
+ );
3550
3788
 
3551
3789
  // src/components/crossChunkUrlSanitize.ts
3552
3790
  function fakeElement(tagName, key, url) {
@@ -3857,7 +4095,12 @@ var createSmoothStreamController = (options = {}) => {
3857
4095
  return;
3858
4096
  }
3859
4097
  tentativeEnd = ends[ends.length - 1];
3860
- const confirmedEnds = finished ? ends : ends.slice(0, -1);
4098
+ let hold = 1;
4099
+ if (!finished && ends.length > 1) {
4100
+ const last = source.charCodeAt(source.length - 1);
4101
+ if (last >= 55296 && last <= 56319) hold = 2;
4102
+ }
4103
+ const confirmedEnds = finished ? ends : ends.slice(0, -hold);
3861
4104
  for (const end of confirmedEnds) pending.push(end);
3862
4105
  };
3863
4106
  const snap = (next) => {
@@ -3985,10 +4228,11 @@ function findClosingBacktickRun(content, start, n) {
3985
4228
  const runLen = getRepeatedMarkerLength(content, i, "`");
3986
4229
  if (runLen === n) return i;
3987
4230
  i += runLen;
3988
- } else if (ch === "\n") {
4231
+ } else if (ch === "\n" || ch === "\r") {
3989
4232
  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;
4233
+ if (ch === "\r" && content[j] === "\n") j += 1;
4234
+ while (j < content.length && (content[j] === " " || content[j] === " ")) j += 1;
4235
+ if (j >= content.length || content[j] === "\n" || content[j] === "\r") return -1;
3992
4236
  i += 1;
3993
4237
  } else {
3994
4238
  i += 1;
@@ -4089,26 +4333,54 @@ function escapeMhchemCommands(text) {
4089
4333
  return text.replaceAll("$\\ce{", "$\\\\ce{").replaceAll("$\\pu{", "$\\\\pu{");
4090
4334
  }
4091
4335
  var CURRENCY_REGEX = /(?<![\\$])\$(?!\$)(?=\d+(?:,\d{3})*(?:\.\d+)?(?:[KMBkmb])?(?:\s|$|[^a-zA-Z\d]))/g;
4092
- var NO_ESCAPED_DOLLAR_REGEX = /(?<![\\$])\$(?!\$)/g;
4093
4336
  var DELIMITERS_REGEX = /(?<!!)\\\[([\S\s]*?[^\\])\\](?!\()|\\\((.*?)\\\)/g;
4094
4337
  var ARRAY_COL_SPEC_OR_PIPE_REGEX = /(\\begin\{(?:array|tabular[x*]?)\}\{[^}]*\})|(?<!\\)\|/g;
4095
- var LATEX_BLOCK_REGEX = /\$\$([\S\s]*?)\$\$|(?<![\\$])\$(?!\$)((?:[^$\n]|\\\$)*?)(?<![\\`])\$(?!\$)/g;
4338
+ var EVEN_BACKSLASHES = String.raw`(?<=(?:^|[^\\])(?:\\\\)*)`;
4339
+ var LATEX_BLOCK_REGEX = new RegExp(
4340
+ String.raw`${EVEN_BACKSLASHES}\$\$([\S\s]*?)${EVEN_BACKSLASHES}\$\$|(?<![\\$])\$(?!\$)((?:[^$\n]|\\\$)*?)(?<![\\` + "`" + String.raw`])\$(?!\$)`,
4341
+ "g"
4342
+ );
4096
4343
  var TEXT_COMMAND = "\\text{";
4097
4344
  var SINGLE_DOLLAR_REGEX = /(?<![\\$])\$(?!\$)((?:[^$\n]|\\[$])+?)(?<!\\)(?<!`)\$(?!\$)/g;
4345
+ function countBareDollars(str, from, to, prev, next) {
4346
+ let n = 0;
4347
+ for (let j = from; j < to; j++) {
4348
+ if (str.charCodeAt(j) !== 36) continue;
4349
+ const before = j > from ? str[j - 1] : prev;
4350
+ const after = j + 1 < to ? str[j + 1] : next;
4351
+ if (before !== "\\" && before !== "$" && after !== "$") n += 1;
4352
+ }
4353
+ return n;
4354
+ }
4098
4355
  function escapeCurrencyDollarSigns(text) {
4099
4356
  const parts = [];
4100
4357
  let lastIndex = 0;
4101
4358
  const currencyMatches = Array.from(text.matchAll(CURRENCY_REGEX));
4102
4359
  let currentLineProcessed = "";
4360
+ let currentLineDollars = 0;
4361
+ const appendToLine = (piece) => {
4362
+ if (piece.length === 0) return;
4363
+ const prevLast = currentLineProcessed.length > 0 ? currentLineProcessed[currentLineProcessed.length - 1] : "";
4364
+ const prevBeforeLast = currentLineProcessed.length > 1 ? currentLineProcessed[currentLineProcessed.length - 2] : "";
4365
+ const prevLastCounted = prevLast === "$" && prevBeforeLast !== "\\" && prevBeforeLast !== "$";
4366
+ if (prevLastCounted && piece[0] === "$") currentLineDollars -= 1;
4367
+ currentLineDollars += countBareDollars(piece, 0, piece.length, prevLast, "");
4368
+ currentLineProcessed += piece;
4369
+ };
4370
+ const resetLine = (rest) => {
4371
+ currentLineProcessed = "";
4372
+ currentLineDollars = 0;
4373
+ appendToLine(rest);
4374
+ };
4103
4375
  for (let i = 0; i < currencyMatches.length; i++) {
4104
4376
  const match = currencyMatches[i];
4105
4377
  const segment = text.substring(lastIndex, match.index);
4106
4378
  parts.push(segment);
4107
4379
  const newlineIdx = Math.max(segment.lastIndexOf("\n"), segment.lastIndexOf("\r"));
4108
4380
  if (newlineIdx !== -1) {
4109
- currentLineProcessed = segment.substring(newlineIdx + 1);
4381
+ resetLine(segment.substring(newlineIdx + 1));
4110
4382
  } else {
4111
- currentLineProcessed += segment;
4383
+ appendToLine(segment);
4112
4384
  }
4113
4385
  let needEscape = true;
4114
4386
  const restStart = match.index + 1;
@@ -4125,15 +4397,21 @@ function escapeCurrencyDollarSigns(text) {
4125
4397
  }
4126
4398
  firstLineBeforeNextMatch = text.substring(restStart, eol);
4127
4399
  }
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
- }
4400
+ const restDollars = countBareDollars(firstLineBeforeNextMatch, 0, firstLineBeforeNextMatch.length, "", "");
4401
+ if (restDollars % 2 !== 0) {
4402
+ const L = currentLineProcessed;
4403
+ const lLast = L.length > 0 ? L[L.length - 1] : "";
4404
+ const lBeforeLast = L.length > 1 ? L[L.length - 2] : "";
4405
+ const lLastCounted = lLast === "$" && lBeforeLast !== "\\" && lBeforeLast !== "$";
4406
+ const f0 = firstLineBeforeNextMatch[0];
4407
+ let whole = currentLineDollars + restDollars;
4408
+ if (lLastCounted && f0 === "$") whole -= 1;
4409
+ if (f0 === "$" && (lLast === "\\" || lLast === "$") && firstLineBeforeNextMatch[1] !== "$") whole -= 1;
4410
+ if (whole % 2 !== 0) needEscape = false;
4133
4411
  }
4134
4412
  const replacement = needEscape ? "\\$" : "$";
4135
4413
  parts.push(replacement);
4136
- currentLineProcessed += replacement;
4414
+ appendToLine(replacement);
4137
4415
  lastIndex = match.index + 1;
4138
4416
  }
4139
4417
  parts.push(text.substring(lastIndex));
@@ -4201,8 +4479,7 @@ function escapeLatexPipesInUnclosed(text) {
4201
4479
  const tail = text.substring(unclosedStart + delimLen);
4202
4480
  return before + delim + replaceUnescapedPipes(tail);
4203
4481
  }
4204
- function truncateUnclosedLatexBlock(text) {
4205
- const unclosedStart = findUnclosedDelimiterStart(text, "double-only");
4482
+ function truncateUnclosedLatexBlock(text, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
4206
4483
  if (unclosedStart === -1) return text;
4207
4484
  return text.substring(0, unclosedStart).trimEnd();
4208
4485
  }
@@ -4313,8 +4590,9 @@ function processSliceInstrumented(slice, probe = true) {
4313
4590
  if (probe && hasUnclosedTextCommand(text)) quiescent = false;
4314
4591
  text = escapeTextUnderscores(text);
4315
4592
  text = convertSingleToDoubleDollar(text);
4593
+ let unclosedDouble;
4316
4594
  if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
4317
- const unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4595
+ unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
4318
4596
  if (unclosedDouble !== -1) {
4319
4597
  quiescent = false;
4320
4598
  if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
@@ -4322,7 +4600,7 @@ function processSliceInstrumented(slice, probe = true) {
4322
4600
  }
4323
4601
  }
4324
4602
  }
4325
- text = truncateUnclosedLatexBlock(text);
4603
+ text = truncateUnclosedLatexBlock(text, unclosedDouble);
4326
4604
  parts.push(text);
4327
4605
  }
4328
4606
  return { out: parts.join(""), quiescent, truncatedAtSeamStart };
@@ -4450,7 +4728,6 @@ function createRemendPreprocessor(options) {
4450
4728
  }
4451
4729
  export {
4452
4730
  DEFAULT_PAYLOAD,
4453
- DEF_LINE_START_RE,
4454
4731
  PIPELINE_STAGES,
4455
4732
  SENTINEL_FN_CONTENT,
4456
4733
  SENTINEL_LINK_URL,
@@ -4485,7 +4762,6 @@ export {
4485
4762
  hasLoneSurrogate,
4486
4763
  highlight,
4487
4764
  isFootnoteSection,
4488
- lastRegionStart,
4489
4765
  measureStage,
4490
4766
  mergeClassNameAllowlist,
4491
4767
  normalizeForMatch,