@ai-react-markdown/engine 2.10.0 → 2.11.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.
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
33
  DEFAULT_PAYLOAD: () => DEFAULT_PAYLOAD,
34
+ ENGINE_PLACEHOLDER_TAGS: () => ENGINE_PLACEHOLDER_TAGS,
35
+ ENGINE_PROVENANCE_PROPERTY: () => ENGINE_PROVENANCE_PROPERTY,
34
36
  PIPELINE_STAGES: () => PIPELINE_STAGES,
35
37
  SENTINEL_FN_CONTENT: () => SENTINEL_FN_CONTENT,
36
38
  SENTINEL_LINK_URL: () => SENTINEL_LINK_URL,
@@ -75,6 +77,7 @@ __export(src_exports, {
75
77
  preprocessLaTeX: () => preprocessLaTeX,
76
78
  rehypeFooterAdorn: () => rehypeFooterAdorn,
77
79
  rehypeRebaseHashLinks: () => rehypeRebaseHashLinks_default,
80
+ rehypeVerifyEngineTags: () => rehypeVerifyEngineTags,
78
81
  removeComments: () => removeComments,
79
82
  sanitizeCrossChunkUrl: () => sanitizeCrossChunkUrl,
80
83
  sanitizeSchema: () => sanitizeSchema,
@@ -2797,6 +2800,44 @@ function rehypeUnwrapCrossChunkImages() {
2797
2800
 
2798
2801
  // src/components/pluginChain.ts
2799
2802
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
2803
+
2804
+ // src/components/rehypeVerifyEngineTags.ts
2805
+ var ENGINE_PLACEHOLDER_TAGS = /* @__PURE__ */ new Set([
2806
+ "footnote-sup",
2807
+ "cross-chunk-link",
2808
+ "cross-chunk-image"
2809
+ ]);
2810
+ var ENGINE_PROVENANCE_PROPERTY = "engineProvenance";
2811
+ function walk(parent, provenance) {
2812
+ const children = parent.children;
2813
+ let i = 0;
2814
+ while (i < children.length) {
2815
+ const node = children[i];
2816
+ if (node.type === "element" && ENGINE_PLACEHOLDER_TAGS.has(node.tagName)) {
2817
+ const props = node.properties ?? {};
2818
+ const stamped = props[ENGINE_PROVENANCE_PROPERTY];
2819
+ const genuine = provenance !== "" && typeof stamped === "string" && stamped === provenance;
2820
+ if (genuine) {
2821
+ delete props[ENGINE_PROVENANCE_PROPERTY];
2822
+ walk(node, provenance);
2823
+ i += 1;
2824
+ } else {
2825
+ children.splice(i, 1, ...node.children);
2826
+ }
2827
+ continue;
2828
+ }
2829
+ if (node.type === "element") walk(node, provenance);
2830
+ i += 1;
2831
+ }
2832
+ }
2833
+ function rehypeVerifyEngineTags(options) {
2834
+ const provenance = options?.provenance ?? "";
2835
+ return function transformer(tree) {
2836
+ walk(tree, provenance);
2837
+ };
2838
+ }
2839
+
2840
+ // src/components/pluginChain.ts
2800
2841
  var import_remark_breaks = __toESM(require("remark-breaks"), 1);
2801
2842
  var import_remark_cjk_friendly = __toESM(require("remark-cjk-friendly"), 1);
2802
2843
  var import_remark_cjk_friendly_gfm_strikethrough = __toESM(require("remark-cjk-friendly-gfm-strikethrough"), 1);
@@ -2902,10 +2943,13 @@ function buildCoreRemarkPlugins(enginePlugins) {
2902
2943
  ...DISPLAY_OPTIMIZE_CHAIN.filter(([name]) => selected.has(name)).map(([, plugin]) => plugin)
2903
2944
  ];
2904
2945
  }
2905
- function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix) {
2946
+ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix, options) {
2906
2947
  return [
2907
2948
  // Allow raw HTML through so rehype-sanitize can handle it.
2908
2949
  [import_rehype_raw.default, { passThrough: [] }],
2950
+ // Unwrap forged engine placeholders BEFORE sanitize admits their tag
2951
+ // names. Only when the caller holds a credential (see the option's doc).
2952
+ ...options ? [[rehypeVerifyEngineTags, { provenance: options.provenance }]] : [],
2909
2953
  // Sanitize HTML while allowing <mark> (highlight), KaTeX class names,
2910
2954
  // and any extra protocols the caller permitted via the `sanitizeSchema`
2911
2955
  // prop. Override `clobberPrefix` with the instance-scoped value — the
@@ -2945,6 +2989,10 @@ function buildCoreRemarkRehypeOptions(enableDefinitionList) {
2945
2989
  }
2946
2990
 
2947
2991
  // src/components/customMdastHandlers.ts
2992
+ function provenanceProps(s) {
2993
+ const provenance = s.options.provenance;
2994
+ return typeof provenance === "string" ? { engineProvenance: provenance } : {};
2995
+ }
2948
2996
  function localDefProps(s, id) {
2949
2997
  const def = s.definitionById.get(id);
2950
2998
  if (!def || typeof def.url !== "string" || def.url === SENTINEL_LINK_URL) return {};
@@ -2983,7 +3031,8 @@ function buildCrossChunkHandlers() {
2983
3031
  label: node.label ?? node.identifier,
2984
3032
  referenceType: node.referenceType,
2985
3033
  documentId: s.options.documentId,
2986
- ...localDefProps(s, id)
3034
+ ...localDefProps(s, id),
3035
+ ...provenanceProps(s)
2987
3036
  },
2988
3037
  children: s.all(node)
2989
3038
  };
@@ -3001,7 +3050,8 @@ function buildCrossChunkHandlers() {
3001
3050
  referenceType: node.referenceType,
3002
3051
  alt: node.alt ?? "",
3003
3052
  documentId: s.options.documentId,
3004
- ...localDefProps(s, id)
3053
+ ...localDefProps(s, id),
3054
+ ...provenanceProps(s)
3005
3055
  },
3006
3056
  children: []
3007
3057
  };
@@ -3018,7 +3068,8 @@ function buildCrossChunkHandlers() {
3018
3068
  properties: {
3019
3069
  label: node.identifier,
3020
3070
  localOccurrence,
3021
- documentId: s.options.documentId
3071
+ documentId: s.options.documentId,
3072
+ ...provenanceProps(s)
3022
3073
  },
3023
3074
  children: []
3024
3075
  };
@@ -3036,7 +3087,8 @@ function buildCrossChunkHandlers() {
3036
3087
  // first client frame), where the local synthetic footer is what
3037
3088
  // renders, so marks and footer agree (core-render-02).
3038
3089
  localNumber: s.footnoteOrder.indexOf(id) + 1,
3039
- documentId: s.options.documentId
3090
+ documentId: s.options.documentId,
3091
+ ...provenanceProps(s)
3040
3092
  },
3041
3093
  children: []
3042
3094
  };
@@ -4708,6 +4760,12 @@ var createSmoothStreamController = (options = {}) => {
4708
4760
  };
4709
4761
 
4710
4762
  // src/preprocessors/latex.ts
4763
+ function isHardBoundary(kind) {
4764
+ return kind === "code" || kind === "literal" || kind === "multilineTag";
4765
+ }
4766
+ function hasLineEnding(text) {
4767
+ return text.includes("\n") || text.includes("\r");
4768
+ }
4711
4769
  function getRepeatedMarkerLength(content, start, marker) {
4712
4770
  let end = start;
4713
4771
  while (end < content.length && content[end] === marker) {
@@ -4775,11 +4833,11 @@ function splitByProtectedRegions(content) {
4775
4833
  let multilineFenceMarker = null;
4776
4834
  let multilineFenceLength = 0;
4777
4835
  let multilineFenceIndent = 0;
4778
- function pushProtected(start, end) {
4836
+ function pushProtected(start, end, kind) {
4779
4837
  if (start > lastIndex) {
4780
- segments.push({ text: content.substring(lastIndex, start), isCode: false });
4838
+ segments.push({ kind: "text", text: content.substring(lastIndex, start) });
4781
4839
  }
4782
- segments.push({ text: content.substring(start, end), isCode: true });
4840
+ segments.push({ kind, text: content.substring(start, end) });
4783
4841
  lastIndex = end;
4784
4842
  }
4785
4843
  let i = 0;
@@ -4790,7 +4848,7 @@ function splitByProtectedRegions(content) {
4790
4848
  const runLen = getRepeatedMarkerLength(content, i, multilineFenceMarker);
4791
4849
  const closerIndent = lineIndentBefore(content, i);
4792
4850
  if (runLen >= multilineFenceLength && closerIndent !== -1 && closerIndent <= multilineFenceIndent + 3 && restOfLineIsBlank(content, i + runLen)) {
4793
- pushProtected(multilineStart, i + runLen);
4851
+ pushProtected(multilineStart, i + runLen, "code");
4794
4852
  multilineStart = -1;
4795
4853
  multilineFenceMarker = null;
4796
4854
  multilineFenceLength = 0;
@@ -4817,7 +4875,7 @@ function splitByProtectedRegions(content) {
4817
4875
  if (char === "`") {
4818
4876
  const closeIdx = findClosingBacktickRun(content, i + runLen, runLen);
4819
4877
  if (closeIdx !== -1) {
4820
- pushProtected(i, closeIdx + runLen);
4878
+ pushProtected(i, closeIdx + runLen, "code");
4821
4879
  i = closeIdx + runLen;
4822
4880
  continue;
4823
4881
  }
@@ -4842,7 +4900,11 @@ function splitByProtectedRegions(content) {
4842
4900
  endIndex = content.length;
4843
4901
  }
4844
4902
  }
4845
- pushProtected(i, endIndex);
4903
+ pushProtected(
4904
+ i,
4905
+ endIndex,
4906
+ isOpeningPairedTag ? "literal" : hasLineEnding(content.substring(i, endIndex)) ? "multilineTag" : "tag"
4907
+ );
4846
4908
  i = endIndex;
4847
4909
  continue;
4848
4910
  }
@@ -4850,10 +4912,10 @@ function splitByProtectedRegions(content) {
4850
4912
  i += 1;
4851
4913
  }
4852
4914
  if (multilineStart !== -1) {
4853
- pushProtected(multilineStart, content.length);
4915
+ pushProtected(multilineStart, content.length, "code");
4854
4916
  }
4855
4917
  if (lastIndex < content.length) {
4856
- segments.push({ text: content.substring(lastIndex), isCode: false });
4918
+ segments.push({ kind: "text", text: content.substring(lastIndex) });
4857
4919
  }
4858
4920
  return segments;
4859
4921
  }
@@ -5007,8 +5069,22 @@ function escapeLatexPipesInUnclosed(text) {
5007
5069
  const tail = text.substring(unclosedStart + delimLen);
5008
5070
  return before + delim + replaceUnescapedPipes(tail);
5009
5071
  }
5010
- function truncateUnclosedLatexBlock(text, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
5072
+ function opensMathFlow(text, pos, runStartsAtLineStart) {
5073
+ let i = pos;
5074
+ let spaces = 0;
5075
+ while (i > 0) {
5076
+ const prev = text[i - 1];
5077
+ if (prev === "\n" || prev === "\r") return true;
5078
+ i -= 1;
5079
+ if (text[i] !== " ") return false;
5080
+ spaces += 1;
5081
+ if (spaces > 3) return false;
5082
+ }
5083
+ return runStartsAtLineStart;
5084
+ }
5085
+ function truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedStart = findUnclosedDelimiterStart(text, "double-only")) {
5011
5086
  if (unclosedStart === -1) return text;
5087
+ if (!opensMathFlow(text, unclosedStart, runStartsAtLineStart)) return text;
5012
5088
  return text.substring(0, unclosedStart).trimEnd();
5013
5089
  }
5014
5090
  function escapeTextUnderscores(text) {
@@ -5053,7 +5129,8 @@ function convertSingleToDoubleDollar(text) {
5053
5129
  }
5054
5130
  function preprocessLaTeX(str) {
5055
5131
  if (!hasLatexTrigger(str)) return str;
5056
- return processSlice(str, false).out;
5132
+ const mask = selectMask(str);
5133
+ return (mask === null ? processSlice(str, { legacy: true, probe: false }) : processSlice(str, { probe: false, mask })).out;
5057
5134
  }
5058
5135
  function hasLatexTrigger(str) {
5059
5136
  return str.includes("$") || str.includes("\\[") || str.includes("\\(");
@@ -5082,42 +5159,240 @@ function hasUnclosedTextCommand(text) {
5082
5159
  }
5083
5160
  var RESIDUAL_OPEN_BRACKET_RE = /(?<!!)\\\[/;
5084
5161
  var LEADING_DOUBLE_DOLLAR_RE = /^\s*\$\$/;
5085
- function processSlice(slice, probe = true) {
5162
+ var PUA_START = 57344;
5163
+ var PUA_END = 63743;
5164
+ var PUA_SIZE = PUA_END - PUA_START + 1;
5165
+ function selectMask(source) {
5166
+ const first = String.fromCharCode(PUA_START);
5167
+ if (source.indexOf(first) === -1) return first;
5168
+ const seen = new Uint8Array(PUA_SIZE);
5169
+ for (let i = 0; i < source.length; i++) {
5170
+ const c = source.charCodeAt(i);
5171
+ if (c >= PUA_START && c <= PUA_END) seen[c - PUA_START] = 1;
5172
+ }
5173
+ for (let k = 0; k < PUA_SIZE; k++) if (seen[k] === 0) return String.fromCharCode(PUA_START + k);
5174
+ return null;
5175
+ }
5176
+ var PuaPresence = class {
5177
+ bits = new Uint8Array(PUA_SIZE);
5178
+ distinct = 0;
5179
+ reset() {
5180
+ this.bits.fill(0);
5181
+ this.distinct = 0;
5182
+ }
5183
+ add(text, from) {
5184
+ for (let i = from; i < text.length; i++) {
5185
+ const c = text.charCodeAt(i);
5186
+ if (c >= PUA_START && c <= PUA_END) {
5187
+ const k = c - PUA_START;
5188
+ if (this.bits[k] === 0) {
5189
+ this.bits[k] = 1;
5190
+ this.distinct += 1;
5191
+ }
5192
+ }
5193
+ }
5194
+ }
5195
+ select() {
5196
+ if (this.distinct === 0) return String.fromCharCode(PUA_START);
5197
+ if (this.distinct >= PUA_SIZE) return null;
5198
+ for (let k = 0; k < PUA_SIZE; k++) if (this.bits[k] === 0) return String.fromCharCode(PUA_START + k);
5199
+ return null;
5200
+ }
5201
+ };
5202
+ function transformRun(input, probe, runStartsAtLineStart, seamEligible) {
5203
+ let text = input;
5204
+ let tailSensitive = false;
5205
+ let truncatedAtSeamStart = false;
5206
+ text = escapeMhchemCommands(text);
5207
+ text = escapeCurrencyDollarSigns(text);
5208
+ text = convertLatexDelimiters(text);
5209
+ if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) tailSensitive = true;
5210
+ text = escapeLatexPipes(text);
5211
+ if (probe && findUnclosedDelimiterStart(text, "both") !== -1) tailSensitive = true;
5212
+ text = escapeLatexPipesInUnclosed(text);
5213
+ if (probe && hasUnclosedTextCommand(text)) tailSensitive = true;
5214
+ text = escapeTextUnderscores(text);
5215
+ text = convertSingleToDoubleDollar(text);
5216
+ let unclosedDouble;
5217
+ if (probe || seamEligible && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5218
+ unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5219
+ if (unclosedDouble !== -1) {
5220
+ tailSensitive = true;
5221
+ if (seamEligible && opensMathFlow(text, unclosedDouble, runStartsAtLineStart) && text.slice(0, unclosedDouble).trim() === "") {
5222
+ truncatedAtSeamStart = true;
5223
+ }
5224
+ }
5225
+ }
5226
+ text = truncateUnclosedLatexBlock(text, runStartsAtLineStart, unclosedDouble);
5227
+ return { out: text, tailSensitive, truncatedAtSeamStart };
5228
+ }
5229
+ function processSliceLegacy(slice, probe) {
5086
5230
  const segments = splitByProtectedRegions(slice);
5087
5231
  const parts = [];
5088
5232
  let quiescent = true;
5089
5233
  let truncatedAtSeamStart = false;
5090
5234
  for (let index = 0; index < segments.length; index++) {
5091
5235
  const segment = segments[index];
5092
- if (segment.isCode) {
5236
+ if (segment.kind !== "text") {
5093
5237
  parts.push(segment.text);
5094
5238
  continue;
5095
5239
  }
5096
- let text = segment.text;
5097
- text = escapeMhchemCommands(text);
5098
- text = escapeCurrencyDollarSigns(text);
5099
- text = convertLatexDelimiters(text);
5100
- if (probe && RESIDUAL_OPEN_BRACKET_RE.test(text)) quiescent = false;
5101
- text = escapeLatexPipes(text);
5102
- if (probe && findUnclosedDelimiterStart(text, "both") !== -1) quiescent = false;
5103
- text = escapeLatexPipesInUnclosed(text);
5104
- if (probe && hasUnclosedTextCommand(text)) quiescent = false;
5105
- text = escapeTextUnderscores(text);
5106
- text = convertSingleToDoubleDollar(text);
5107
- let unclosedDouble;
5108
- if (probe || index === 0 && LEADING_DOUBLE_DOLLAR_RE.test(text)) {
5109
- unclosedDouble = findUnclosedDelimiterStart(text, "double-only");
5110
- if (unclosedDouble !== -1) {
5111
- quiescent = false;
5112
- if (index === 0 && text.slice(0, unclosedDouble).trim() === "") {
5113
- truncatedAtSeamStart = true;
5240
+ const r = transformRun(segment.text, probe, true, index === 0);
5241
+ if (r.tailSensitive) quiescent = false;
5242
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5243
+ parts.push(r.out);
5244
+ }
5245
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5246
+ }
5247
+ var SCOPE_DEPTH_CAP = 8;
5248
+ var VOID_TAGS2 = /* @__PURE__ */ new Set(["br", "hr", "img", "wbr", "input", "source"]);
5249
+ var TAG_NAME_RE = /^<(\/?)([A-Za-z][A-Za-z0-9]*)/;
5250
+ function tagInfo(tag) {
5251
+ const m = TAG_NAME_RE.exec(tag);
5252
+ const closing = m?.[1] === "/";
5253
+ const name = (m?.[2] ?? "").toLowerCase();
5254
+ const opensScope = !closing && !tag.endsWith("/>") && !VOID_TAGS2.has(name);
5255
+ return { name, closing, opensScope };
5256
+ }
5257
+ function buildRunTree(segments) {
5258
+ const root2 = [];
5259
+ const stack = [];
5260
+ const current = () => stack.length > 0 ? stack[stack.length - 1].children : root2;
5261
+ const unwind = () => {
5262
+ while (stack.length > 0) {
5263
+ const frame = stack.pop();
5264
+ current().push({ type: "atom", text: frame.open }, ...frame.children);
5265
+ }
5266
+ };
5267
+ for (const segment of segments) {
5268
+ if (segment.kind === "tag") {
5269
+ const info = tagInfo(segment.text);
5270
+ if (info.closing) {
5271
+ const top = stack[stack.length - 1];
5272
+ if (top !== void 0 && top.name === info.name) {
5273
+ stack.pop();
5274
+ const parent = current();
5275
+ if (top.suppressed) {
5276
+ parent.push({ type: "atom", text: top.open }, ...top.children, { type: "atom", text: segment.text });
5277
+ } else {
5278
+ parent.push({ type: "scope", open: top.open, close: segment.text, children: top.children });
5279
+ }
5280
+ } else {
5281
+ current().push({ type: "atom", text: segment.text });
5114
5282
  }
5283
+ } else if (info.opensScope) {
5284
+ stack.push({ name: info.name, open: segment.text, suppressed: stack.length >= SCOPE_DEPTH_CAP, children: [] });
5285
+ } else {
5286
+ current().push({ type: "atom", text: segment.text });
5115
5287
  }
5288
+ continue;
5116
5289
  }
5117
- text = truncateUnclosedLatexBlock(text, unclosedDouble);
5118
- parts.push(text);
5290
+ const t = segment.text;
5291
+ let start = 0;
5292
+ for (let i = 0; i < t.length; i++) {
5293
+ const c = t.charCodeAt(i);
5294
+ if (c !== 10 && c !== 13) continue;
5295
+ if (i > start) current().push({ type: "text", text: t.slice(start, i) });
5296
+ const end = c === 13 && t.charCodeAt(i + 1) === 10 ? i + 2 : i + 1;
5297
+ unwind();
5298
+ root2.push({ type: "text", text: t.slice(i, end) });
5299
+ start = end;
5300
+ i = end - 1;
5301
+ }
5302
+ if (start < t.length) current().push({ type: "text", text: t.slice(start) });
5303
+ }
5304
+ unwind();
5305
+ return root2;
5306
+ }
5307
+ var restoreFailureInjector = null;
5308
+ function restore(out, atoms, mask) {
5309
+ if (restoreFailureInjector !== null && restoreFailureInjector(atoms)) return null;
5310
+ let result = "";
5311
+ let k = 0;
5312
+ let last = 0;
5313
+ for (; ; ) {
5314
+ const idx = out.indexOf(mask, last);
5315
+ if (idx === -1) break;
5316
+ if (k >= atoms.length) return null;
5317
+ result += out.slice(last, idx) + atoms[k];
5318
+ k += 1;
5319
+ last = idx + 1;
5320
+ }
5321
+ return result + out.slice(last);
5322
+ }
5323
+ function emitRun(nodes, mask) {
5324
+ let text = "";
5325
+ const atoms = [];
5326
+ for (const node of nodes) {
5327
+ if (node.type === "text") {
5328
+ text += node.text;
5329
+ } else if (node.type === "atom") {
5330
+ atoms.push(node.text);
5331
+ text += mask;
5332
+ } else {
5333
+ const inner = emitRun(node.children, mask);
5334
+ if (inner === null) return null;
5335
+ const transformed = transformRun(inner.text, false, false, false);
5336
+ const restored = restore(transformed.out, inner.atoms, mask);
5337
+ if (restored === null) return null;
5338
+ atoms.push(node.open + restored + node.close);
5339
+ text += mask;
5340
+ }
5341
+ }
5342
+ return { text, atoms };
5343
+ }
5344
+ function reportRestoreViolation() {
5345
+ if (true) {
5346
+ console.error(
5347
+ "[ai-react-markdown] LaTeX preprocessor: atom restoration violated its invariant (more masks in the output than atoms); the slice was re-processed on the legacy path. This is an engine defect \u2014 please report it."
5348
+ );
5119
5349
  }
5120
- return { out: parts.join(""), quiescent, truncatedAtSeamStart };
5350
+ }
5351
+ function processSliceDefault(slice, probe, mask) {
5352
+ const segments = splitByProtectedRegions(slice);
5353
+ const parts = [];
5354
+ let quiescent = true;
5355
+ let truncatedAtSeamStart = false;
5356
+ let offset = 0;
5357
+ let i = 0;
5358
+ while (i < segments.length) {
5359
+ const segment = segments[i];
5360
+ if (isHardBoundary(segment.kind)) {
5361
+ parts.push(segment.text);
5362
+ offset += segment.text.length;
5363
+ i += 1;
5364
+ continue;
5365
+ }
5366
+ const runStart = offset;
5367
+ const runSegments = [];
5368
+ while (i < segments.length && !isHardBoundary(segments[i].kind)) {
5369
+ runSegments.push(segments[i]);
5370
+ offset += segments[i].text.length;
5371
+ i += 1;
5372
+ }
5373
+ const before = runStart === 0 ? -1 : slice.charCodeAt(runStart - 1);
5374
+ const runStartsAtLineStart = before === -1 || before === 10 || before === 13;
5375
+ const seamEligible = runStart === 0;
5376
+ const emitted = emitRun(buildRunTree(runSegments), mask);
5377
+ if (emitted === null) {
5378
+ reportRestoreViolation();
5379
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5380
+ }
5381
+ const r = transformRun(emitted.text, probe, runStartsAtLineStart, seamEligible);
5382
+ const restored = restore(r.out, emitted.atoms, mask);
5383
+ if (restored === null) {
5384
+ reportRestoreViolation();
5385
+ return { ...processSliceLegacy(slice, probe), degradedReason: "restore-invariant" };
5386
+ }
5387
+ if (r.tailSensitive) quiescent = false;
5388
+ if (r.truncatedAtSeamStart) truncatedAtSeamStart = true;
5389
+ parts.push(restored);
5390
+ }
5391
+ return { out: parts.join(""), quiescent, truncatedAtSeamStart, degradedReason: null };
5392
+ }
5393
+ function processSlice(slice, options) {
5394
+ if (options.legacy === true) return processSliceLegacy(slice, options.probe);
5395
+ return processSliceDefault(slice, options.probe, options.mask);
5121
5396
  }
5122
5397
  function isBlankRawLine(text, from, to) {
5123
5398
  for (let i = from; i < to; i++) {
@@ -5133,7 +5408,7 @@ function findRawSafeCut(active) {
5133
5408
  let backtickHazard = false;
5134
5409
  let latentLt = false;
5135
5410
  for (const segment of segments) {
5136
- if (segment.isCode) {
5411
+ if (segment.kind !== "text") {
5137
5412
  if (segment.text.includes(">")) latentLt = false;
5138
5413
  offset += segment.text.length;
5139
5414
  continue;
@@ -5168,12 +5443,28 @@ function createIncrementalLatexPreprocessor(options) {
5168
5443
  const freezeThreshold = options?.freezeThreshold ?? DEFAULT_FREEZE_ATTEMPT_THRESHOLD;
5169
5444
  const onAttempt = options?.onAttempt;
5170
5445
  const backoff = options?.backoff ?? true;
5446
+ const onDegrade = options?.onDegrade;
5171
5447
  let prevSource = "";
5172
5448
  let prevOutput = "";
5173
5449
  let frozenSrcEnd = 0;
5174
5450
  let frozenOut = "";
5175
5451
  let triggered = false;
5176
5452
  let nextAttemptLen = 0;
5453
+ let lineageDegraded = false;
5454
+ const presence = new PuaPresence();
5455
+ const commit = (source, out) => {
5456
+ prevSource = source;
5457
+ prevOutput = out;
5458
+ return out;
5459
+ };
5460
+ const legacyWhole = (source) => processSlice(source, { legacy: true, probe: false }).out;
5461
+ const degrade = (source, reason) => {
5462
+ lineageDegraded = true;
5463
+ frozenSrcEnd = 0;
5464
+ frozenOut = "";
5465
+ onDegrade?.(reason);
5466
+ return commit(source, legacyWhole(source));
5467
+ };
5177
5468
  return function incrementalPreprocessLaTeX(source) {
5178
5469
  if (source === prevSource) return prevOutput;
5179
5470
  const isAppend = source.length > prevSource.length && source.startsWith(prevSource);
@@ -5182,16 +5473,20 @@ function createIncrementalLatexPreprocessor(options) {
5182
5473
  frozenOut = "";
5183
5474
  triggered = false;
5184
5475
  nextAttemptLen = 0;
5476
+ lineageDegraded = false;
5477
+ presence.reset();
5478
+ presence.add(source, 0);
5479
+ } else {
5480
+ presence.add(source, prevSource.length);
5185
5481
  }
5186
5482
  if (!triggered) {
5187
5483
  const checkFrom = isAppend ? Math.max(0, prevSource.length - 1) : 0;
5188
- if (!hasLatexTrigger(source.slice(checkFrom))) {
5189
- prevSource = source;
5190
- prevOutput = source;
5191
- return source;
5192
- }
5484
+ if (!hasLatexTrigger(source.slice(checkFrom))) return commit(source, source);
5193
5485
  triggered = true;
5194
5486
  }
5487
+ if (lineageDegraded) return commit(source, legacyWhole(source));
5488
+ const mask = presence.select();
5489
+ if (mask === null) return degrade(source, "mask-exhausted");
5195
5490
  let active = source.slice(frozenSrcEnd);
5196
5491
  if (active.length > freezeThreshold && active.length >= nextAttemptLen) {
5197
5492
  const activeLength = active.length;
@@ -5206,18 +5501,20 @@ function createIncrementalLatexPreprocessor(options) {
5206
5501
  };
5207
5502
  const cut = findRawSafeCut(active);
5208
5503
  if (cut > 0) {
5209
- const candidate = processSlice(active.slice(0, cut));
5504
+ const candidate = processSlice(active.slice(0, cut), { probe: true, mask });
5505
+ if (candidate.degradedReason !== null) {
5506
+ onAttempt?.({ activeLength, frozenBytes: 0 });
5507
+ return degrade(source, candidate.degradedReason);
5508
+ }
5210
5509
  if (candidate.quiescent) freeze(cut, candidate);
5211
5510
  }
5212
5511
  nextAttemptLen = advanced || !backoff ? 0 : active.length * 2;
5213
5512
  onAttempt?.({ activeLength, frozenBytes });
5214
5513
  }
5215
- const tail = processSlice(active, false);
5514
+ const tail = processSlice(active, { probe: false, mask });
5515
+ if (tail.degradedReason !== null) return degrade(source, tail.degradedReason);
5216
5516
  const head = tail.truncatedAtSeamStart ? frozenOut.replace(/\s+$/, "") : frozenOut;
5217
- const out = head + tail.out;
5218
- prevSource = source;
5219
- prevOutput = out;
5220
- return out;
5517
+ return commit(source, head + tail.out);
5221
5518
  };
5222
5519
  }
5223
5520
 
@@ -5243,6 +5540,8 @@ function createRemendPreprocessor(options) {
5243
5540
  // Annotate the CommonJS export names for ESM import in node:
5244
5541
  0 && (module.exports = {
5245
5542
  DEFAULT_PAYLOAD,
5543
+ ENGINE_PLACEHOLDER_TAGS,
5544
+ ENGINE_PROVENANCE_PROPERTY,
5246
5545
  PIPELINE_STAGES,
5247
5546
  SENTINEL_FN_CONTENT,
5248
5547
  SENTINEL_LINK_URL,
@@ -5287,6 +5586,7 @@ function createRemendPreprocessor(options) {
5287
5586
  preprocessLaTeX,
5288
5587
  rehypeFooterAdorn,
5289
5588
  rehypeRebaseHashLinks,
5589
+ rehypeVerifyEngineTags,
5290
5590
  removeComments,
5291
5591
  sanitizeCrossChunkUrl,
5292
5592
  sanitizeSchema,