@ai-react-markdown/engine 2.11.0 → 2.12.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.d.cts CHANGED
@@ -1106,6 +1106,9 @@ interface RehypeVerifyEngineTagsOptions {
1106
1106
  /** The credential the handlers were given. The empty string accepts
1107
1107
  * nothing (fail closed); it is never a valid credential. */
1108
1108
  provenance: string;
1109
+ /** Capture ancestry only when the final a/img schema needs it. Data is
1110
+ * recorded after verification, never taken from authored attributes. */
1111
+ referenceAncestors?: boolean;
1109
1112
  }
1110
1113
  /**
1111
1114
  * rehype plugin: strip the provenance credential from genuine engine
@@ -1315,6 +1318,21 @@ type UrlAttrTag = 'a' | 'img';
1315
1318
  */
1316
1319
  declare function sanitizeCrossChunkUrl(rawUrl: string, key: UrlAttrKey, tagName: UrlAttrTag, urlTransform: UrlTransform, schema: SanitizeSchema): string | null;
1317
1320
 
1321
+ /** Resolve the final element, not just its URL: placeholders reach React
1322
+ * after the normal rehype passes, so their a/img must apply those passes too.
1323
+ * The marker represents already-rendered link children and distinguishes
1324
+ * sanitizer unwrapping from stripping the element with its contents. */
1325
+ declare function resolveCrossChunkReference(input: {
1326
+ tagName: 'a' | 'img';
1327
+ url: string;
1328
+ title?: string;
1329
+ alt?: string;
1330
+ node?: Pick<Element, 'children' | 'position' | 'data'>;
1331
+ }, schema: SanitizeSchema, urlTransform: UrlTransform, clobberPrefix: string): {
1332
+ element: Element | null;
1333
+ keepChildren: boolean;
1334
+ };
1335
+
1318
1336
  /**
1319
1337
  * The five shipped engine plugins and the default set.
1320
1338
  *
@@ -1930,4 +1948,4 @@ type RemendPreprocessorOptions = Omit<RemendOptions, 'katex' | 'inlineKatex'>;
1930
1948
  */
1931
1949
  declare function createRemendPreprocessor(options?: RemendPreprocessorOptions): AIMDContentPreprocessor;
1932
1950
 
1933
- export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CoreRehypePluginsOptions, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, type DefLabelScanner, type DefLabels, type Deprecation, ENGINE_PLACEHOLDER_TAGS, ENGINE_PROVENANCE_PROPERTY, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RehypeVerifyEngineTagsOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, footnoteSafeId, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastMeaningfulIdx, measureStage, normalizeForMatch, normalizeId, pangu, parseStage, phantomSuffixCloser, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, rehypeVerifyEngineTags, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, subscribeStageTimings, transformStage, withDefs };
1951
+ export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CoreRehypePluginsOptions, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, type DefLabelScanner, type DefLabels, type Deprecation, ENGINE_PLACEHOLDER_TAGS, ENGINE_PROVENANCE_PROPERTY, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RehypeVerifyEngineTagsOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, footnoteSafeId, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastMeaningfulIdx, measureStage, normalizeForMatch, normalizeId, pangu, parseStage, phantomSuffixCloser, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, rehypeVerifyEngineTags, removeComments, resolveCrossChunkReference, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, subscribeStageTimings, transformStage, withDefs };
package/dist/index.d.ts CHANGED
@@ -1106,6 +1106,9 @@ interface RehypeVerifyEngineTagsOptions {
1106
1106
  /** The credential the handlers were given. The empty string accepts
1107
1107
  * nothing (fail closed); it is never a valid credential. */
1108
1108
  provenance: string;
1109
+ /** Capture ancestry only when the final a/img schema needs it. Data is
1110
+ * recorded after verification, never taken from authored attributes. */
1111
+ referenceAncestors?: boolean;
1109
1112
  }
1110
1113
  /**
1111
1114
  * rehype plugin: strip the provenance credential from genuine engine
@@ -1315,6 +1318,21 @@ type UrlAttrTag = 'a' | 'img';
1315
1318
  */
1316
1319
  declare function sanitizeCrossChunkUrl(rawUrl: string, key: UrlAttrKey, tagName: UrlAttrTag, urlTransform: UrlTransform, schema: SanitizeSchema): string | null;
1317
1320
 
1321
+ /** Resolve the final element, not just its URL: placeholders reach React
1322
+ * after the normal rehype passes, so their a/img must apply those passes too.
1323
+ * The marker represents already-rendered link children and distinguishes
1324
+ * sanitizer unwrapping from stripping the element with its contents. */
1325
+ declare function resolveCrossChunkReference(input: {
1326
+ tagName: 'a' | 'img';
1327
+ url: string;
1328
+ title?: string;
1329
+ alt?: string;
1330
+ node?: Pick<Element, 'children' | 'position' | 'data'>;
1331
+ }, schema: SanitizeSchema, urlTransform: UrlTransform, clobberPrefix: string): {
1332
+ element: Element | null;
1333
+ keepChildren: boolean;
1334
+ };
1335
+
1318
1336
  /**
1319
1337
  * The five shipped engine plugins and the default set.
1320
1338
  *
@@ -1930,4 +1948,4 @@ type RemendPreprocessorOptions = Omit<RemendOptions, 'katex' | 'inlineKatex'>;
1930
1948
  */
1931
1949
  declare function createRemendPreprocessor(options?: RemendPreprocessorOptions): AIMDContentPreprocessor;
1932
1950
 
1933
- export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CoreRehypePluginsOptions, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, type DefLabelScanner, type DefLabels, type Deprecation, ENGINE_PLACEHOLDER_TAGS, ENGINE_PROVENANCE_PROPERTY, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RehypeVerifyEngineTagsOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, footnoteSafeId, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastMeaningfulIdx, measureStage, normalizeForMatch, normalizeId, pangu, parseStage, phantomSuffixCloser, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, rehypeVerifyEngineTags, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, subscribeStageTimings, transformStage, withDefs };
1951
+ export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CoreRehypePluginsOptions, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, type DefLabelScanner, type DefLabels, type Deprecation, ENGINE_PLACEHOLDER_TAGS, ENGINE_PROVENANCE_PROPERTY, type EnginePluginInternals, type EnginePluginStage, type ExtractContributionsOptions, type FootnoteDef, type FreezeBoundaryOptions, type IncrementalParseState, type IncrementalStage, type LinkDef, PIPELINE_STAGES, type ParsedMarkdown, type PhantomLabels, type PipelineOptions, type PipelineStage, type RefKind, type RefRecord, type Registry, type RegistryInternal, type RehypePlugins, type RehypeRebaseHashLinksOptions, type RehypeVerifyEngineTagsOptions, type RemarkPlugins, type RemarkRehypeOptions, type RemendPreprocessorOptions, SENTINEL_FN_CONTENT, SENTINEL_LINK_URL, SMOOTH_STREAM_PACING_PRESETS, STAGE_MEASURE_PREFIX, type SanitizeSchema, type SmoothStreamController, type SmoothStreamOptions, type SmoothStreamPacing, type SmoothStreamPacingParams, type TransformContext, type UrlAttrKey, type UrlAttrTag, type UrlTransform, advanceIncrementalParse, attributeHastChildren, buildCoreRehypePlugins, buildCoreRemarkPlugins, buildCoreRemarkRehypeOptions, buildCrossChunkHandlers, buildPhantomSuffix, buildTransform, codePointSnapshots, collectDefLabels, computeFreezeBoundary, createDefLabelScanner, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, footnoteSafeId, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastMeaningfulIdx, measureStage, normalizeForMatch, normalizeId, pangu, parseStage, phantomSuffixCloser, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, rehypeVerifyEngineTags, removeComments, resolveCrossChunkReference, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, subscribeStageTimings, transformStage, withDefs };
@@ -79,6 +79,7 @@ __export(src_exports, {
79
79
  rehypeRebaseHashLinks: () => rehypeRebaseHashLinks_default,
80
80
  rehypeVerifyEngineTags: () => rehypeVerifyEngineTags,
81
81
  removeComments: () => removeComments,
82
+ resolveCrossChunkReference: () => resolveCrossChunkReference,
82
83
  sanitizeCrossChunkUrl: () => sanitizeCrossChunkUrl,
83
84
  sanitizeSchema: () => sanitizeSchema,
84
85
  shortenDocumentId: () => shortenDocumentId,
@@ -2230,6 +2231,38 @@ function normalizeForMatch(s) {
2230
2231
  return (0, import_micromark_util_normalize_identifier3.normalizeIdentifier)(s);
2231
2232
  }
2232
2233
 
2234
+ // src/components/blankLineScanner.ts
2235
+ function createBlankLineScanner() {
2236
+ let end = 0;
2237
+ let newline = false;
2238
+ let cr = false;
2239
+ return (source, from = 0) => {
2240
+ if (from === 0) {
2241
+ end = 0;
2242
+ newline = false;
2243
+ cr = false;
2244
+ }
2245
+ for (let i = from; i < source.length; i++) {
2246
+ const c = source[i];
2247
+ if (c === "\n") {
2248
+ if (newline) {
2249
+ end = i + 1;
2250
+ newline = false;
2251
+ } else newline = true;
2252
+ cr = false;
2253
+ } else if (newline) {
2254
+ if ((c === " " || c === " ") && !cr) continue;
2255
+ if (c === "\r" && !cr) cr = true;
2256
+ else {
2257
+ newline = false;
2258
+ cr = false;
2259
+ }
2260
+ }
2261
+ }
2262
+ return end;
2263
+ };
2264
+ }
2265
+
2233
2266
  // src/components/collectDefLabels.ts
2234
2267
  var SCANNER_BOUNDARY_PROFILE = { defListEnabled: false, mathFlow: false, referenceTaint: false };
2235
2268
  function buildProcessor() {
@@ -2261,19 +2294,12 @@ var setsEqual = (a, b) => {
2261
2294
  for (const v of a) if (!b.has(v)) return false;
2262
2295
  return true;
2263
2296
  };
2264
- var BLANK_LINE_RE = /\r?\n[ \t]*\r?\n/g;
2265
- function lastRegionStart(source) {
2266
- BLANK_LINE_RE.lastIndex = 0;
2267
- let start = 0;
2268
- for (let m = BLANK_LINE_RE.exec(source); m !== null; m = BLANK_LINE_RE.exec(source)) {
2269
- start = m.index + m[0].length;
2270
- }
2271
- return start;
2272
- }
2273
2297
  var DEF_LINE_START_RE = /^[ \t>*+\d.)-]*\[(?:[^\]\\]|\\[\s\S])*\]:/m;
2274
2298
  function createDefLabelScanner(parse = collectDefLabels) {
2275
2299
  let prevSource = null;
2276
2300
  let prevLabels = null;
2301
+ const scanBlankLines = createBlankLineScanner();
2302
+ let regionStart = 0;
2277
2303
  let frozenEnd = 0;
2278
2304
  let frozenFootnotes = /* @__PURE__ */ new Set();
2279
2305
  let frozenLinks = /* @__PURE__ */ new Set();
@@ -2286,13 +2312,16 @@ function createDefLabelScanner(parse = collectDefLabels) {
2286
2312
  };
2287
2313
  return {
2288
2314
  scan(source) {
2315
+ if (source === prevSource && prevLabels !== null) return prevLabels;
2316
+ const previousRegionStart = regionStart;
2317
+ const appended = prevSource !== null && source.startsWith(prevSource);
2318
+ regionStart = scanBlankLines(source, appended ? prevSource.length : 0);
2289
2319
  let isAppend = false;
2290
2320
  if (prevSource !== null && prevLabels !== null) {
2291
2321
  if (source === prevSource) return prevLabels;
2292
2322
  if (source.startsWith(prevSource)) {
2293
2323
  isAppend = true;
2294
- const regionStart = lastRegionStart(prevSource);
2295
- const region = prevSource.slice(regionStart) + source.slice(prevSource.length);
2324
+ const region = source.slice(previousRegionStart);
2296
2325
  if (!DEF_LINE_START_RE.test(region)) {
2297
2326
  prevSource = source;
2298
2327
  return prevLabels;
@@ -2525,8 +2554,47 @@ function* extractContributions(mdast, options = {}) {
2525
2554
  for (const c of out) yield c;
2526
2555
  }
2527
2556
 
2557
+ // src/components/registryIndex.ts
2558
+ function buildRegistryIndex(registry) {
2559
+ const index = {
2560
+ footnotes: /* @__PURE__ */ new Map(),
2561
+ links: /* @__PURE__ */ new Map(),
2562
+ numbers: /* @__PURE__ */ new Map(),
2563
+ counts: /* @__PURE__ */ new Map(),
2564
+ occurrences: /* @__PURE__ */ new Map()
2565
+ };
2566
+ for (const sym of registry.chunkOrder) {
2567
+ const data = registry.chunkData.get(sym);
2568
+ if (!data) continue;
2569
+ for (const label of data.defs.keys()) if (!index.footnotes.has(label)) index.footnotes.set(label, sym);
2570
+ for (const label of data.linkDefs.keys()) if (!index.links.has(label)) index.links.set(label, sym);
2571
+ const local = /* @__PURE__ */ new Map();
2572
+ index.occurrences.set(sym, local);
2573
+ for (const ref of data.refs) {
2574
+ if (ref.kind !== "footnote") continue;
2575
+ const label = ref.label;
2576
+ if (!index.numbers.has(label)) index.numbers.set(label, index.numbers.size + 1);
2577
+ const total = (index.counts.get(label) ?? 0) + 1;
2578
+ index.counts.set(label, total);
2579
+ const prior = local.get(label);
2580
+ if (prior) prior.count++;
2581
+ else local.set(label, { start: total, count: 1 });
2582
+ }
2583
+ }
2584
+ return index;
2585
+ }
2586
+
2528
2587
  // src/components/documentRegistry.ts
2529
2588
  function createRegistry(onEmpty) {
2589
+ let index;
2590
+ let indexedVersion = -1;
2591
+ const getIndex = () => {
2592
+ if (!index || indexedVersion !== reg.version) {
2593
+ index = buildRegistryIndex(reg);
2594
+ indexedVersion = reg.version;
2595
+ }
2596
+ return index;
2597
+ };
2530
2598
  const reg = {
2531
2599
  chunkOrder: [],
2532
2600
  chunkData: /* @__PURE__ */ new Map(),
@@ -2678,72 +2746,25 @@ function createRegistry(onEmpty) {
2678
2746
  };
2679
2747
  },
2680
2748
  canonicalFootnoteFor(label) {
2681
- const id = normalizeId(label);
2682
- for (const sym of this.chunkOrder) {
2683
- const data = this.chunkData.get(sym);
2684
- if (data?.defs.has(id)) return sym;
2685
- }
2686
- return null;
2749
+ return getIndex().footnotes.get(normalizeId(label)) ?? null;
2687
2750
  },
2688
2751
  canonicalLinkFor(label) {
2689
- const id = normalizeId(label);
2690
- for (const sym of this.chunkOrder) {
2691
- const data = this.chunkData.get(sym);
2692
- if (data?.linkDefs.has(id)) return sym;
2693
- }
2694
- return null;
2752
+ return getIndex().links.get(normalizeId(label)) ?? null;
2695
2753
  },
2696
2754
  globalNumber(label) {
2697
- const id = normalizeId(label);
2698
- let n = 0;
2699
- const seen = /* @__PURE__ */ new Set();
2700
- for (const sym of this.chunkOrder) {
2701
- const data = this.chunkData.get(sym);
2702
- if (!data) continue;
2703
- for (const ref of data.refs) {
2704
- if (ref.kind !== "footnote") continue;
2705
- if (!seen.has(ref.label)) {
2706
- seen.add(ref.label);
2707
- n++;
2708
- if (ref.label === id) return n;
2709
- }
2710
- }
2711
- }
2712
- return null;
2755
+ return getIndex().numbers.get(normalizeId(label)) ?? null;
2713
2756
  },
2714
2757
  resolveLinkDef(label) {
2715
- const sym = this.canonicalLinkFor(label);
2716
- if (!sym) return null;
2717
- return this.chunkData.get(sym)?.linkDefs.get(normalizeId(label)) ?? null;
2758
+ const id = normalizeId(label);
2759
+ const sym = getIndex().links.get(id);
2760
+ return sym ? this.chunkData.get(sym)?.linkDefs.get(id) ?? null : null;
2718
2761
  },
2719
2762
  getRefsForLabel(label) {
2720
- const id = normalizeId(label);
2721
- let n = 0;
2722
- for (const sym of this.chunkOrder) {
2723
- const data = this.chunkData.get(sym);
2724
- if (!data) continue;
2725
- for (const ref of data.refs) {
2726
- if (ref.kind === "footnote" && ref.label === id) n++;
2727
- }
2728
- }
2729
- return n;
2763
+ return getIndex().counts.get(normalizeId(label)) ?? 0;
2730
2764
  },
2731
2765
  globalOccurrenceForRef(chunkSym, label, localOccurrence) {
2732
- const id = normalizeId(label);
2733
- let global2 = 0;
2734
- for (const sym of this.chunkOrder) {
2735
- const data = this.chunkData.get(sym);
2736
- if (!data) continue;
2737
- let localCount = 0;
2738
- for (const ref of data.refs) {
2739
- if (ref.kind !== "footnote") continue;
2740
- if (ref.label !== id) continue;
2741
- localCount++;
2742
- global2++;
2743
- if (sym === chunkSym && localCount === localOccurrence) return global2;
2744
- }
2745
- }
2746
- return null;
2766
+ const range = getIndex().occurrences.get(chunkSym)?.get(normalizeId(label));
2767
+ return range && Number.isInteger(localOccurrence) && localOccurrence > 0 && localOccurrence <= range.count ? range.start + localOccurrence - 1 : null;
2747
2768
  },
2748
2769
  _notify() {
2749
2770
  this.version++;
@@ -2808,7 +2829,7 @@ var ENGINE_PLACEHOLDER_TAGS = /* @__PURE__ */ new Set([
2808
2829
  "cross-chunk-image"
2809
2830
  ]);
2810
2831
  var ENGINE_PROVENANCE_PROPERTY = "engineProvenance";
2811
- function walk(parent, provenance) {
2832
+ function walk(parent, provenance, ancestors) {
2812
2833
  const children = parent.children;
2813
2834
  let i = 0;
2814
2835
  while (i < children.length) {
@@ -2819,21 +2840,32 @@ function walk(parent, provenance) {
2819
2840
  const genuine = provenance !== "" && typeof stamped === "string" && stamped === provenance;
2820
2841
  if (genuine) {
2821
2842
  delete props[ENGINE_PROVENANCE_PROPERTY];
2822
- walk(node, provenance);
2843
+ if (ancestors && (node.tagName === "cross-chunk-link" || node.tagName === "cross-chunk-image")) {
2844
+ (node.data ??= {}).referenceAncestors = ancestors.slice();
2845
+ }
2846
+ ancestors?.push(
2847
+ node.tagName === "cross-chunk-link" ? "a" : node.tagName === "cross-chunk-image" ? "img" : node.tagName
2848
+ );
2849
+ walk(node, provenance, ancestors);
2850
+ ancestors?.pop();
2823
2851
  i += 1;
2824
2852
  } else {
2825
2853
  children.splice(i, 1, ...node.children);
2826
2854
  }
2827
2855
  continue;
2828
2856
  }
2829
- if (node.type === "element") walk(node, provenance);
2857
+ if (node.type === "element") {
2858
+ ancestors?.push(node.tagName);
2859
+ walk(node, provenance, ancestors);
2860
+ ancestors?.pop();
2861
+ }
2830
2862
  i += 1;
2831
2863
  }
2832
2864
  }
2833
2865
  function rehypeVerifyEngineTags(options) {
2834
2866
  const provenance = options?.provenance ?? "";
2835
2867
  return function transformer(tree) {
2836
- walk(tree, provenance);
2868
+ walk(tree, provenance, options?.referenceAncestors ? [] : void 0);
2837
2869
  };
2838
2870
  }
2839
2871
 
@@ -2854,16 +2886,18 @@ var import_remark_remove_comments = __toESM(require("remark-remove-comments"), 1
2854
2886
  // src/components/rehypeRebaseHashLinks.ts
2855
2887
  var import_unist_util_visit5 = require("unist-util-visit");
2856
2888
  var DEFAULT_PREFIX = "user-content-";
2889
+ function rebaseHashHref(href, prefix) {
2890
+ const hashPrefix = "#" + prefix;
2891
+ return href.startsWith("#") && !href.startsWith(hashPrefix) ? hashPrefix + href.slice(1) : href;
2892
+ }
2857
2893
  var rehypeRebaseHashLinks = (options) => {
2858
2894
  const prefix = options?.prefix ?? DEFAULT_PREFIX;
2859
- const hashPrefix = "#" + prefix;
2860
2895
  return (tree) => {
2861
2896
  (0, import_unist_util_visit5.visit)(tree, "element", (node) => {
2862
2897
  if (node.tagName !== "a") return;
2863
2898
  const href = node.properties?.href;
2864
2899
  if (typeof href !== "string" || !href.startsWith("#")) return;
2865
- if (href.startsWith(hashPrefix)) return;
2866
- node.properties.href = hashPrefix + href.slice(1);
2900
+ node.properties.href = rebaseHashHref(href, prefix);
2867
2901
  });
2868
2902
  };
2869
2903
  };
@@ -2949,7 +2983,15 @@ function buildCoreRehypePlugins(sanitizeSchema2, clobberPrefix, options) {
2949
2983
  [import_rehype_raw.default, { passThrough: [] }],
2950
2984
  // Unwrap forged engine placeholders BEFORE sanitize admits their tag
2951
2985
  // names. Only when the caller holds a credential (see the option's doc).
2952
- ...options ? [[rehypeVerifyEngineTags, { provenance: options.provenance }]] : [],
2986
+ ...options ? [
2987
+ [
2988
+ rehypeVerifyEngineTags,
2989
+ {
2990
+ provenance: options.provenance,
2991
+ ...sanitizeSchema2.ancestors?.a || sanitizeSchema2.ancestors?.img ? { referenceAncestors: true } : {}
2992
+ }
2993
+ ]
2994
+ ] : [],
2953
2995
  // Sanitize HTML while allowing <mark> (highlight), KaTeX class names,
2954
2996
  // and any extra protocols the caller permitted via the `sanitizeSchema`
2955
2997
  // prop. Override `clobberPrefix` with the instance-scoped value — the
@@ -3023,11 +3065,9 @@ function buildCrossChunkHandlers() {
3023
3065
  type: "element",
3024
3066
  tagName: "cross-chunk-link",
3025
3067
  properties: {
3026
- // `label` is the ORIGINAL source text (mdast's `label` field), NOT
3027
- // the normalized `identifier`. The placeholder uses it to construct
3028
- // hrefs that line up with mdast-util-to-hast's default `<li id>`
3029
- // which also preserves source case. Registry lookups normalize
3030
- // internally, so cross-chunk case-insensitive matching still works.
3068
+ // Display labels decode escapes; registry identifiers must retain
3069
+ // those bytes. Never use the display label as the lookup key.
3070
+ identifier: node.identifier,
3031
3071
  label: node.label ?? node.identifier,
3032
3072
  referenceType: node.referenceType,
3033
3073
  documentId: s.options.documentId,
@@ -3046,6 +3086,7 @@ function buildCrossChunkHandlers() {
3046
3086
  type: "element",
3047
3087
  tagName: "cross-chunk-image",
3048
3088
  properties: {
3089
+ identifier: node.identifier,
3049
3090
  label: node.label ?? node.identifier,
3050
3091
  referenceType: node.referenceType,
3051
3092
  alt: node.alt ?? "",
@@ -4308,8 +4349,8 @@ var sanitizeSchema = deepFreeze(
4308
4349
  attributes: {
4309
4350
  ...import_rehype_sanitize2.defaultSchema.attributes,
4310
4351
  code: mergeClassNameAllowlist(import_rehype_sanitize2.defaultSchema.attributes?.code, ["math-inline", "math-display"]),
4311
- "cross-chunk-link": ["label", "referenceType", "documentId", "localUrl", "localTitle"],
4312
- "cross-chunk-image": ["label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
4352
+ "cross-chunk-link": ["identifier", "label", "referenceType", "documentId", "localUrl", "localTitle"],
4353
+ "cross-chunk-image": ["identifier", "label", "referenceType", "documentId", "alt", "localUrl", "localTitle"],
4313
4354
  "footnote-sup": ["label", "localOccurrence", "localNumber", "documentId"]
4314
4355
  },
4315
4356
  strip: [.../* @__PURE__ */ new Set([...import_rehype_sanitize2.defaultSchema.strip || [], ...STRIPPED_TAGS])]
@@ -4345,6 +4386,49 @@ function sanitizeCrossChunkUrl(rawUrl, key, tagName, urlTransform, schema) {
4345
4386
  return String(transformed);
4346
4387
  }
4347
4388
 
4389
+ // src/components/resolveCrossChunkReference.ts
4390
+ var import_rehype_sanitize3 = __toESM(require("rehype-sanitize"), 1);
4391
+ var import_micromark_util_sanitize_uri3 = require("micromark-util-sanitize-uri");
4392
+ function resolveCrossChunkReference(input, schema, urlTransform, clobberPrefix) {
4393
+ const key = input.tagName === "a" ? "href" : "src";
4394
+ const element = {
4395
+ type: "element",
4396
+ tagName: input.tagName,
4397
+ properties: {
4398
+ [key]: (0, import_micromark_util_sanitize_uri3.normalizeUri)(input.url),
4399
+ ...input.tagName === "img" ? { alt: input.alt ?? "" } : {},
4400
+ ...input.title !== void 0 ? { title: input.title } : {}
4401
+ },
4402
+ children: input.tagName === "a" ? [{ type: "text", value: "__reference_children__" }] : []
4403
+ };
4404
+ const requiredAncestors = schema.ancestors?.[input.tagName];
4405
+ const recordedAncestors = input.node?.data?.referenceAncestors;
4406
+ let finalSchema = schema;
4407
+ if (requiredAncestors && Array.isArray(recordedAncestors) && requiredAncestors.some((tag) => recordedAncestors.includes(tag))) {
4408
+ const ancestors = { ...schema.ancestors };
4409
+ delete ancestors[input.tagName];
4410
+ finalSchema = { ...schema, ancestors };
4411
+ }
4412
+ const root2 = (0, import_rehype_sanitize3.default)({ ...finalSchema, clobberPrefix })({ type: "root", children: [element] });
4413
+ const node = root2.children[0];
4414
+ if (node?.type !== "element") return { element: null, keepChildren: node?.type === "text" };
4415
+ if (node.tagName === "a" && typeof node.properties.href === "string") {
4416
+ node.properties.href = rebaseHashHref(node.properties.href, clobberPrefix);
4417
+ }
4418
+ node.children = input.node?.children ?? [];
4419
+ if (input.node?.position) node.position = input.node.position;
4420
+ buildTransform({
4421
+ allowedElements: void 0,
4422
+ disallowedElements: void 0,
4423
+ allowElement: void 0,
4424
+ skipHtml: void 0,
4425
+ unwrapDisallowed: void 0,
4426
+ urlTransform
4427
+ })(node, 0, root2);
4428
+ node.children = [];
4429
+ return { element: node, keepChildren: false };
4430
+ }
4431
+
4348
4432
  // src/plugins/defs.ts
4349
4433
  function getEnginePluginInternals(plugin) {
4350
4434
  const candidate = plugin;
@@ -4561,6 +4645,8 @@ var createSmoothStreamController = (options = {}) => {
4561
4645
  let source = "";
4562
4646
  let visibleEnd = 0;
4563
4647
  let pending = [];
4648
+ let pendingHead = 0;
4649
+ const pendingCount = () => pending.length - pendingHead;
4564
4650
  let tentativeEnd = 0;
4565
4651
  let finished = false;
4566
4652
  let seam;
@@ -4605,7 +4691,7 @@ var createSmoothStreamController = (options = {}) => {
4605
4691
  cancelFrame = void 0;
4606
4692
  };
4607
4693
  const ensureScheduled = () => {
4608
- if (disposed || cancelFrame || pending.length === 0) return;
4694
+ if (disposed || cancelFrame || pendingCount() === 0) return;
4609
4695
  lastTickAt = now();
4610
4696
  credit = 0;
4611
4697
  cancelFrame = schedule(tick);
@@ -4619,31 +4705,38 @@ var createSmoothStreamController = (options = {}) => {
4619
4705
  const params = resolveParams();
4620
4706
  let rate;
4621
4707
  if (finished && drainDeadlineAt !== void 0) {
4622
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, drainDeadlineAt - t));
4708
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, drainDeadlineAt - t));
4623
4709
  } else if (gapWindow.length > 0 && lastArrivalAt !== void 0) {
4624
4710
  const gaps = gapWindow.map((s) => s.gap).sort((a, b) => a - b);
4625
4711
  const intervalQ = gaps[Math.min(gaps.length - 1, Math.floor(gaps.length * INTERVAL_QUANTILE))];
4626
4712
  const horizon = Math.max(16, Math.min(params.bufferFactor * intervalQ + HORIZON_PAD_MS, params.maxLagMs));
4627
4713
  const deadline = Math.max(lastArrivalAt + horizon, t + DEADLINE_FLOOR_MS);
4628
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, deadline - t));
4714
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, deadline - t));
4629
4715
  } else {
4630
- rate = Math.max(params.minCharsPerSecond, pending.length * 1e3 / Math.max(1, params.correctionTauMs));
4716
+ rate = Math.max(params.minCharsPerSecond, pendingCount() * 1e3 / Math.max(1, params.correctionTauMs));
4631
4717
  }
4632
4718
  credit += rate * dt / 1e3;
4633
- const reveal = Math.min(Math.floor(credit), pending.length);
4719
+ const reveal = Math.min(Math.floor(credit), pendingCount());
4634
4720
  if (reveal > 0) {
4635
- visibleEnd = pending[reveal - 1];
4636
- pending = pending.slice(reveal);
4637
- credit = pending.length > 0 ? credit - reveal : 0;
4721
+ visibleEnd = pending[pendingHead + reveal - 1];
4722
+ pendingHead += reveal;
4723
+ if (pendingHead === pending.length) {
4724
+ pending = [];
4725
+ pendingHead = 0;
4726
+ } else if (pendingHead >= 1024 && pendingHead * 2 >= pending.length) {
4727
+ pending = pending.slice(pendingHead);
4728
+ pendingHead = 0;
4729
+ }
4730
+ credit = pendingCount() > 0 ? credit - reveal : 0;
4638
4731
  notify();
4639
4732
  }
4640
- if (!disposed && !cancelFrame && pending.length > 0) cancelFrame = schedule(tick);
4733
+ if (!disposed && !cancelFrame && pendingCount() > 0) cancelFrame = schedule(tick);
4641
4734
  };
4642
4735
  const resegmentTail = () => {
4643
- if (seam !== void 0 && seam < source.length && pending[pending.length - 1] === seam) {
4736
+ if (pendingCount() > 0 && seam !== void 0 && seam < source.length && pending[pending.length - 1] === seam) {
4644
4737
  pending.pop();
4645
4738
  }
4646
- const from = pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
4739
+ const from = pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd;
4647
4740
  let anchor = from;
4648
4741
  if (seam !== void 0 && from <= seam) {
4649
4742
  anchor = Math.max(0, from - RESUME_LOOKBACK);
@@ -4677,6 +4770,7 @@ var createSmoothStreamController = (options = {}) => {
4677
4770
  visibleEnd = next.length;
4678
4771
  tentativeEnd = next.length;
4679
4772
  pending = [];
4773
+ pendingHead = 0;
4680
4774
  seam = next.length;
4681
4775
  credit = 0;
4682
4776
  cancelScheduled();
@@ -4708,7 +4802,7 @@ var createSmoothStreamController = (options = {}) => {
4708
4802
  if (finished) return;
4709
4803
  finished = true;
4710
4804
  lastArrivalAt = void 0;
4711
- if (tentativeEnd > (pending.length > 0 ? pending[pending.length - 1] : visibleEnd)) {
4805
+ if (tentativeEnd > (pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd)) {
4712
4806
  pending.push(tentativeEnd);
4713
4807
  }
4714
4808
  const params = resolveParams();
@@ -4734,10 +4828,11 @@ var createSmoothStreamController = (options = {}) => {
4734
4828
  snap,
4735
4829
  flush() {
4736
4830
  disposed = false;
4737
- const target = finished ? source.length : pending.length > 0 ? pending[pending.length - 1] : visibleEnd;
4831
+ const target = finished ? source.length : pendingCount() > 0 ? pending[pending.length - 1] : visibleEnd;
4738
4832
  if (target <= visibleEnd) return;
4739
4833
  visibleEnd = target;
4740
4834
  pending = [];
4835
+ pendingHead = 0;
4741
4836
  credit = 0;
4742
4837
  cancelScheduled();
4743
4838
  notify();
@@ -5588,6 +5683,7 @@ function createRemendPreprocessor(options) {
5588
5683
  rehypeRebaseHashLinks,
5589
5684
  rehypeVerifyEngineTags,
5590
5685
  removeComments,
5686
+ resolveCrossChunkReference,
5591
5687
  sanitizeCrossChunkUrl,
5592
5688
  sanitizeSchema,
5593
5689
  shortenDocumentId,