@ai-markdown/core 3.0.0 → 3.0.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
@@ -341,19 +341,49 @@ function createBlockPlanner() {
341
341
  let h = 0;
342
342
  let reusedContext = false;
343
343
  if (previous && source.startsWith(previous.source) && options.phantomFootnoteLabels === previous.phantoms) {
344
+ const prevHast = previous.hast.children;
345
+ const prevPlan = previous.result.plan;
346
+ const isRetainedGap = (i) => {
347
+ const gap = hast.children[i];
348
+ return gap === prevHast[i] && gap.type === "text" && !/\S/.test(gap.value);
349
+ };
344
350
  while (m < mdast.children.length) {
345
351
  const node = mdast.children[m];
346
352
  const el = hast.children[h];
347
- const item = previous.result.plan[h];
348
- if (node !== previous.mdast.children[m] || el !== previous.hast.children[h] || !el || el.type !== "element" || !item || item.kind !== "block" || item.info.startOffset !== node.position?.start.offset || !isEligible(node))
353
+ const item = prevPlan[h];
354
+ if (node !== previous.mdast.children[m] || el !== prevHast[h] || !el || el.type !== "element" || !item || item.kind !== "block" || item.info.startOffset !== node.position?.start.offset || !isEligible(node))
349
355
  break;
356
+ const startOffset = item.info.startOffset;
357
+ let next = h + 1;
358
+ let complete = true;
359
+ for (; ; ) {
360
+ let probe = next;
361
+ while (probe < hast.children.length && isRetainedGap(probe)) probe++;
362
+ const sibling = prevPlan[probe];
363
+ if (!sibling || sibling.kind !== "block" || sibling.info.startOffset !== startOffset) break;
364
+ if (hast.children[probe] !== prevHast[probe]) {
365
+ complete = false;
366
+ break;
367
+ }
368
+ next = probe + 1;
369
+ }
370
+ if (!complete) break;
350
371
  reusedContext ||= item.info.hasReference;
351
372
  m++;
352
- h++;
353
- while (h < hast.children.length) {
354
- const gap = hast.children[h];
355
- if (gap !== previous.hast.children[h] || gap.type !== "text" || /\S/.test(gap.value)) break;
356
- h++;
373
+ h = next;
374
+ while (h < hast.children.length && isRetainedGap(h)) h++;
375
+ }
376
+ if (m > 0) {
377
+ const prefixEnd = mdast.children[m - 1].position?.end.offset ?? 0;
378
+ for (let i = h; i < hast.children.length; i++) {
379
+ const child = hast.children[i];
380
+ const at = child.type === "element" ? child.position?.start.offset : void 0;
381
+ if (at !== void 0 && at < prefixEnd) {
382
+ m = 0;
383
+ h = 0;
384
+ reusedContext = false;
385
+ break;
386
+ }
357
387
  }
358
388
  }
359
389
  }
@@ -387,7 +417,7 @@ function createBlockPlanner() {
387
417
  }
388
418
 
389
419
  // src/contribution.ts
390
- import { extractContributions, extractDefBodiesFromHast } from "@ai-markdown/engine";
420
+ import { extractContributions, extractDefBodiesFromHast, footnoteSafeId } from "@ai-markdown/engine";
391
421
  function createContributionSession() {
392
422
  let lastContribution = null;
393
423
  return {
@@ -400,7 +430,12 @@ function createContributionSession() {
400
430
  phantomFootnoteLabels: targetPhantoms.missingFootnotes
401
431
  })) {
402
432
  if (node.kind === "ref") {
403
- refs.push({ label: node.label, kind: node.refKind, referenceType: node.referenceType });
433
+ refs.push({
434
+ label: node.label,
435
+ kind: node.refKind,
436
+ referenceType: node.referenceType,
437
+ ...node.nestedIn !== void 0 ? { nestedIn: node.nestedIn } : {}
438
+ });
404
439
  } else if (node.kind === "fnDef") {
405
440
  defMeta.set(node.label, {
406
441
  identifier: node.label,
@@ -434,14 +469,14 @@ function createContributionSession() {
434
469
  if (last?.registry === registry && last.symbol === sym && last.fp === fp && last.chain.length === chain.length && last.chain.every((dep, i) => dep === chain[i])) {
435
470
  return;
436
471
  }
437
- const bodiesByLabel = extractDefBodiesFromHast(pipeline.hast, clobberPrefix);
472
+ const bodiesBySafeId = extractDefBodiesFromHast(pipeline.hast, clobberPrefix);
438
473
  const defs = /* @__PURE__ */ new Map();
439
474
  for (const [label, meta] of defMeta) {
440
475
  defs.set(label, {
441
476
  identifier: meta.identifier,
442
477
  sourceIdentifier: meta.sourceIdentifier,
443
478
  contentSource: meta.contentSource,
444
- bodyHast: bodiesByLabel.get(label) ?? []
479
+ bodyHast: bodiesBySafeId.get(footnoteSafeId(meta.sourceIdentifier)) ?? []
445
480
  });
446
481
  }
447
482
  lastContribution = { registry, symbol: sym, fp, chain };
@@ -578,7 +613,7 @@ function deriveTailSignal(mdast, contentLength) {
578
613
  }
579
614
 
580
615
  // src/aggregateFootnotes.ts
581
- import { footnoteSafeId, lastMeaningfulIdx } from "@ai-markdown/engine";
616
+ import { footnoteSafeId as footnoteSafeId2, lastMeaningfulIdx } from "@ai-markdown/engine";
582
617
 
583
618
  // src/cloneHastForRender.ts
584
619
  function cloneHastForRender(node) {
@@ -649,6 +684,7 @@ function buildAggregateTree(registry, clobberPrefix, preserveOrphanReferences =
649
684
  });
650
685
  }
651
686
  }
687
+ ordered.sort((a, b) => (a.n ?? 0) - (b.n ?? 0));
652
688
  if (preserveOrphanReferences) {
653
689
  for (const sym of registry.chunkOrder) {
654
690
  const data = registry.chunkData.get(sym);
@@ -669,7 +705,7 @@ function buildAggregateTree(registry, clobberPrefix, preserveOrphanReferences =
669
705
  }
670
706
  if (ordered.length === 0) return null;
671
707
  const liElements = ordered.map(({ normalizedLabel, sourceIdentifier, bodyHast, n, withBackref }) => {
672
- const safeId = footnoteSafeId(sourceIdentifier);
708
+ const safeId = footnoteSafeId2(sourceIdentifier);
673
709
  const liChildren = bodyHast.map((c) => cloneHastForRender(c));
674
710
  if (withBackref) {
675
711
  const totalRefs = registry.getRefsForLabel(normalizedLabel);
@@ -735,6 +771,7 @@ function buildAggregateTree(registry, clobberPrefix, preserveOrphanReferences =
735
771
 
736
772
  // src/pipelineSession.ts
737
773
  import {
774
+ EngineRawHtmlDepthError,
738
775
  advanceIncrementalParse,
739
776
  buildPhantomSuffix,
740
777
  phantomSuffixCloser,
@@ -742,6 +779,30 @@ import {
742
779
  transformStage
743
780
  } from "@ai-markdown/engine";
744
781
  var unmeasured = (_stage, fn) => fn();
782
+ function endPoint(text) {
783
+ let line = 1;
784
+ let lineStart = 0;
785
+ for (let i = 0; i < text.length; i++) {
786
+ const code = text.charCodeAt(i);
787
+ if (code === 10 || code === 13 && text.charCodeAt(i + 1) !== 10) {
788
+ line += 1;
789
+ lineStart = i + 1;
790
+ }
791
+ }
792
+ return { line, column: text.length - lineStart + 1, offset: text.length };
793
+ }
794
+ function plainTextTrees(content) {
795
+ const position = { start: { line: 1, column: 1, offset: 0 }, end: endPoint(content) };
796
+ const text = { type: "text", value: content, position };
797
+ return {
798
+ mdast: { type: "root", children: [{ type: "paragraph", children: [text], position }], position },
799
+ hast: {
800
+ type: "root",
801
+ children: [{ type: "element", tagName: "p", properties: {}, children: [text], position }],
802
+ position
803
+ }
804
+ };
805
+ }
745
806
  function createPipelineSession() {
746
807
  let state = null;
747
808
  return {
@@ -783,19 +844,33 @@ function createPipelineSession() {
783
844
  } : {
784
845
  ...remarkRehypeOptions
785
846
  };
847
+ const fullPipeline = () => {
848
+ try {
849
+ const parsed = measureHere(
850
+ "parse",
851
+ () => parseStage({
852
+ children: augmented,
853
+ remarkPlugins,
854
+ rehypePlugins,
855
+ remarkRehypeOptions: mergedRemarkRehypeOptions
856
+ })
857
+ );
858
+ const hastRoot = measureHere("transform", () => transformStage(parsed));
859
+ return { mdast: parsed.mdast, hast: hastRoot };
860
+ } catch (error) {
861
+ if (!(error instanceof EngineRawHtmlDepthError)) throw error;
862
+ if (false) {
863
+ console.error(
864
+ "[ai-react-markdown] raw HTML nested past the engine depth bound \u2014 rendering this frame as plain text:",
865
+ error
866
+ );
867
+ }
868
+ return plainTextTrees(content ?? "");
869
+ }
870
+ };
786
871
  if (!incrementalParse) {
787
872
  state = null;
788
- const parsed = measureHere(
789
- "parse",
790
- () => parseStage({
791
- children: augmented,
792
- remarkPlugins,
793
- rehypePlugins,
794
- remarkRehypeOptions: mergedRemarkRehypeOptions
795
- })
796
- );
797
- const hastRoot = measureHere("transform", () => transformStage(parsed));
798
- return { mdast: parsed.mdast, hast: hastRoot };
873
+ return fullPipeline();
799
874
  }
800
875
  try {
801
876
  const result = advanceIncrementalParse(state, content ?? "", {
@@ -818,7 +893,12 @@ function createPipelineSession() {
818
893
  // Explicit even though `rehypePlugins` already carries it: a
819
894
  // credential change must never reuse trees stamped under the
820
895
  // old one.
821
- provenance
896
+ provenance,
897
+ // The boundary scanner's grammar profile. The scan checkpoint
898
+ // already refuses to resume under a different profile, but the
899
+ // retained TREES were still spliced against — a prefix frozen
900
+ // under one profile must not survive into frames under the other.
901
+ defListEnabled
822
902
  ],
823
903
  defListEnabled,
824
904
  phantomSuffix,
@@ -831,17 +911,7 @@ function createPipelineSession() {
831
911
  if (false) {
832
912
  console.error("[ai-react-markdown] incremental parse failed \u2014 full parse fallback for this frame:", error);
833
913
  }
834
- const parsed = measureHere(
835
- "parse",
836
- () => parseStage({
837
- children: augmented,
838
- remarkPlugins,
839
- rehypePlugins,
840
- remarkRehypeOptions: mergedRemarkRehypeOptions
841
- })
842
- );
843
- const hastRoot = measureHere("transform", () => transformStage(parsed));
844
- return { mdast: parsed.mdast, hast: hastRoot };
914
+ return fullPipeline();
845
915
  }
846
916
  }
847
917
  };