@ai-matrx/content-ir 0.16.1 → 0.17.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/source.d.cts CHANGED
@@ -420,21 +420,36 @@ declare function parseFenceOpener(line: string): {
420
420
  } | null;
421
421
  declare function classifyInnerFenceLine(trimmed: string, openTicks: number, nests: boolean, nestedDepth: number): InnerFenceLine;
422
422
 
423
+ interface CodeRange {
424
+ /** UTF-16 offset of the first character (the fence line start, or the opening backtick run). */
425
+ start: number;
426
+ /** Offset just past the closing fence line's last character / closing backtick run (end of text when unclosed). */
427
+ end: number;
428
+ kind: "fence" | "span";
429
+ }
423
430
  /**
424
- * `$$` pairing — the core's rule for which `$$` delimiters are real math.
425
- *
426
- * Ported from `@ai-matrx/kit/delimiter-guard` (`guardMathDelimiters`), which
427
- * every math-capable renderer runs before remark-math. Its decisions:
428
- * - `$$` tokens outside code (``` / ~~~ fences, `inline code`) pair in
429
- * document order, open → close;
430
- * - a pair whose inside reads as prose or carries markdown structure is NOT
431
- * math: the renderer neutralizes its opener and the closer is retried as
432
- * the next opener;
433
- * - an unpaired `$$` is inert — it renders as literal text.
434
- * So the tokenizer protects exactly the pairs the renderer shows as math, and
435
- * a lone `$$` (a stray closer, or an opener that never closed) never locks
436
- * anything. Linear time: one regex pass per code shape, one sweep.
431
+ * Where code is, by CommonMark — THE ONE code-range rule for every package.
432
+ * Math pairing (`pairDisplayMath`), the tokenizer's math islands, print's math
433
+ * lifting and kit's delimiter guard all ask this function; a second copy of
434
+ * code detection anywhere in `apps/shared` fails
435
+ * `__tests__/one-code-range-rule.test.ts`.
436
+ * - A fence opens only at the START of a line (≤ 3 spaces of indent): a
437
+ * mid-sentence "```python" is text, not a fence that swallows everything
438
+ * after it (verify-RC-B9: one inline ``` turned math off for the rest of
439
+ * the answer). Closing follows THE ONE nested-fence rule
440
+ * (`classifyInnerFenceLine`); an unclosed fence runs to the end.
441
+ * - An inline code span closes only at a backtick run of the SAME length in
442
+ * the same paragraph; an unmatched run is literal text.
443
+ * Linear: one pass over lines, one over each paragraph's backtick runs.
444
+ */
445
+ declare function findCodeRanges(text: string): CodeRange[];
446
+
447
+ /**
448
+ * `$$...$$`, `$$…$$`, `$$ · $$`: a PLACEHOLDER — prose describing the syntax,
449
+ * never a formula. The pair is consumed whole as literal text (neither `$$`
450
+ * may open the next span).
437
451
  */
452
+ declare function isMathPlaceholder(inner: string): boolean;
438
453
  /**
439
454
  * The guard's `looksLikeMath`. `flow` = the opener is alone on its line: a
440
455
  * flow block may span blank lines (like a fence); a `$$` with text beside it
@@ -442,7 +457,7 @@ declare function classifyInnerFenceLine(trimmed: string, openTicks: number, nest
442
457
  * pairing with the next paragraph's formula.
443
458
  */
444
459
  declare function looksLikeDisplayMath(inner: string, flow?: boolean): boolean;
445
- /** Map of `$$` opener offset → closer offset for every pair the renderer shows as math. */
460
+
446
461
  /**
447
462
  * STREAMING: the last `$$` token that is still unpaired AND could still pair
448
463
  * with a `$$` not yet arrived — its span so far is short enough, carries no
@@ -451,6 +466,7 @@ declare function looksLikeDisplayMath(inner: string, flow?: boolean): boolean;
451
466
  * streaming tokenizer treats it as provisional. -1 when there is none.
452
467
  */
453
468
  declare function pendingDisplayMathOpener(text: string): number;
469
+ /** Map of `$$` opener offset → closer offset for every pair the renderer shows as math. */
454
470
  declare function pairDisplayMath(text: string): Map<number, number>;
455
471
 
456
- export { type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type DisturbedIsland, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, type TokenizeOptions, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, pendingDisplayMathOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
472
+ export { type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type CodeRange, type DisturbedIsland, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, type TokenizeOptions, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, findCodeRanges, isMathPlaceholder, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, pendingDisplayMathOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
package/dist/source.d.ts CHANGED
@@ -420,21 +420,36 @@ declare function parseFenceOpener(line: string): {
420
420
  } | null;
421
421
  declare function classifyInnerFenceLine(trimmed: string, openTicks: number, nests: boolean, nestedDepth: number): InnerFenceLine;
422
422
 
423
+ interface CodeRange {
424
+ /** UTF-16 offset of the first character (the fence line start, or the opening backtick run). */
425
+ start: number;
426
+ /** Offset just past the closing fence line's last character / closing backtick run (end of text when unclosed). */
427
+ end: number;
428
+ kind: "fence" | "span";
429
+ }
423
430
  /**
424
- * `$$` pairing — the core's rule for which `$$` delimiters are real math.
425
- *
426
- * Ported from `@ai-matrx/kit/delimiter-guard` (`guardMathDelimiters`), which
427
- * every math-capable renderer runs before remark-math. Its decisions:
428
- * - `$$` tokens outside code (``` / ~~~ fences, `inline code`) pair in
429
- * document order, open → close;
430
- * - a pair whose inside reads as prose or carries markdown structure is NOT
431
- * math: the renderer neutralizes its opener and the closer is retried as
432
- * the next opener;
433
- * - an unpaired `$$` is inert — it renders as literal text.
434
- * So the tokenizer protects exactly the pairs the renderer shows as math, and
435
- * a lone `$$` (a stray closer, or an opener that never closed) never locks
436
- * anything. Linear time: one regex pass per code shape, one sweep.
431
+ * Where code is, by CommonMark — THE ONE code-range rule for every package.
432
+ * Math pairing (`pairDisplayMath`), the tokenizer's math islands, print's math
433
+ * lifting and kit's delimiter guard all ask this function; a second copy of
434
+ * code detection anywhere in `apps/shared` fails
435
+ * `__tests__/one-code-range-rule.test.ts`.
436
+ * - A fence opens only at the START of a line (≤ 3 spaces of indent): a
437
+ * mid-sentence "```python" is text, not a fence that swallows everything
438
+ * after it (verify-RC-B9: one inline ``` turned math off for the rest of
439
+ * the answer). Closing follows THE ONE nested-fence rule
440
+ * (`classifyInnerFenceLine`); an unclosed fence runs to the end.
441
+ * - An inline code span closes only at a backtick run of the SAME length in
442
+ * the same paragraph; an unmatched run is literal text.
443
+ * Linear: one pass over lines, one over each paragraph's backtick runs.
444
+ */
445
+ declare function findCodeRanges(text: string): CodeRange[];
446
+
447
+ /**
448
+ * `$$...$$`, `$$…$$`, `$$ · $$`: a PLACEHOLDER — prose describing the syntax,
449
+ * never a formula. The pair is consumed whole as literal text (neither `$$`
450
+ * may open the next span).
437
451
  */
452
+ declare function isMathPlaceholder(inner: string): boolean;
438
453
  /**
439
454
  * The guard's `looksLikeMath`. `flow` = the opener is alone on its line: a
440
455
  * flow block may span blank lines (like a fence); a `$$` with text beside it
@@ -442,7 +457,7 @@ declare function classifyInnerFenceLine(trimmed: string, openTicks: number, nest
442
457
  * pairing with the next paragraph's formula.
443
458
  */
444
459
  declare function looksLikeDisplayMath(inner: string, flow?: boolean): boolean;
445
- /** Map of `$$` opener offset → closer offset for every pair the renderer shows as math. */
460
+
446
461
  /**
447
462
  * STREAMING: the last `$$` token that is still unpaired AND could still pair
448
463
  * with a `$$` not yet arrived — its span so far is short enough, carries no
@@ -451,6 +466,7 @@ declare function looksLikeDisplayMath(inner: string, flow?: boolean): boolean;
451
466
  * streaming tokenizer treats it as provisional. -1 when there is none.
452
467
  */
453
468
  declare function pendingDisplayMathOpener(text: string): number;
469
+ /** Map of `$$` opener offset → closer offset for every pair the renderer shows as math. */
454
470
  declare function pairDisplayMath(text: string): Map<number, number>;
455
471
 
456
- export { type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type DisturbedIsland, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, type TokenizeOptions, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, pendingDisplayMathOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
472
+ export { type BlockIslandType, CALLOUT_NAMES, type CodePointIndex, type CodeRange, type DisturbedIsland, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, type InlineIslandType, type InnerFenceLine, type IslandMeta, type IslandType, type MapOptions, type MappedPosition, type MappedRange, NESTING_FENCE_LANGUAGES, type ReadXmlTag, type SourceBlock, type SourceBlockKind, type SourceChange, type SourceEdit, type SourceIsland, type SourceSpan, SourceSpliceError, type SourceSpliceErrorCode, type SpliceIntegrity, type SpliceOptions, type SpliceResult, type TokenizeOptions, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, findCodeRanges, isMathPlaceholder, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, pendingDisplayMathOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
package/dist/source.js CHANGED
@@ -313,12 +313,167 @@ function singleDollarMathEnd(text, open, limit = text.length) {
313
313
  return isSingleDollarMath(content, text[close + 1]) ? close + 1 : -1;
314
314
  }
315
315
 
316
+ // source/code-ranges.ts
317
+ function findCodeRanges(text) {
318
+ const ranges = [];
319
+ const n = text.length;
320
+ let fence = null;
321
+ let para = -1;
322
+ const spans = (from, to) => {
323
+ const runs = [];
324
+ for (let k = from; k < to; ) {
325
+ if (text.charCodeAt(k) !== 96) {
326
+ k++;
327
+ continue;
328
+ }
329
+ let len = 1;
330
+ while (k + len < to && text.charCodeAt(k + len) === 96) len++;
331
+ runs.push([k, len]);
332
+ k += len;
333
+ }
334
+ const nextSame = new Array(runs.length).fill(-1);
335
+ const last = /* @__PURE__ */ new Map();
336
+ for (let r = runs.length - 1; r >= 0; r--) {
337
+ const len = runs[r][1];
338
+ nextSame[r] = last.get(len) ?? -1;
339
+ last.set(len, r);
340
+ }
341
+ for (let r = 0; r < runs.length; ) {
342
+ const j = nextSame[r];
343
+ if (j === -1) {
344
+ r++;
345
+ continue;
346
+ }
347
+ const [s0] = runs[r];
348
+ const [s1, l1] = runs[j];
349
+ ranges.push({ start: s0, end: s1 + l1, kind: "span" });
350
+ r = j + 1;
351
+ }
352
+ };
353
+ let pos = 0;
354
+ while (pos <= n) {
355
+ const nl = text.indexOf("\n", pos);
356
+ const lineEnd = nl === -1 ? n : nl;
357
+ const line = text.slice(pos, lineEnd);
358
+ let indent = 0;
359
+ while (indent < line.length && line[indent] === " ") indent++;
360
+ const body = line.slice(indent);
361
+ if (fence) {
362
+ const trimmed = trimFenceLine(line);
363
+ let closes = false;
364
+ if (fence.char === "`") {
365
+ const kind = classifyInnerFenceLine(trimmed, fence.len, fence.nests, fence.depth);
366
+ if (kind === "open-nested") fence.depth++;
367
+ else if (kind === "close-nested") fence.depth--;
368
+ else if (kind === "close-outer") closes = indent <= 3;
369
+ } else {
370
+ let t = 0;
371
+ while (t < trimmed.length && trimmed[t] === "~") t++;
372
+ closes = indent <= 3 && t >= fence.len && trimFenceLine(trimmed.slice(t)) === "";
373
+ }
374
+ if (closes) {
375
+ ranges.push({ start: fence.start, end: lineEnd, kind: "fence" });
376
+ fence = null;
377
+ }
378
+ } else {
379
+ const opener = indent <= 3 ? /^(`{3,}|~{3,})(.*)$/.exec(body) : null;
380
+ const info = opener ? opener[2] : "";
381
+ if (opener && !(opener[1][0] === "`" && info.includes("`"))) {
382
+ if (para !== -1) spans(para, pos);
383
+ para = -1;
384
+ const char = opener[1][0];
385
+ const parsed = char === "`" ? parseFenceOpener(line) : null;
386
+ fence = { char, len: opener[1].length, start: pos, nests: fenceNestsInnerFences(parsed?.lang), depth: 0 };
387
+ } else if (body.trim() === "") {
388
+ if (para !== -1) spans(para, pos);
389
+ para = -1;
390
+ } else if (para === -1) para = pos;
391
+ }
392
+ if (nl === -1) break;
393
+ pos = nl + 1;
394
+ }
395
+ if (fence) ranges.push({ start: fence.start, end: n, kind: "fence" });
396
+ else if (para !== -1) spans(para, n);
397
+ return ranges.sort((a, b) => a.start - b.start);
398
+ }
399
+ function pendingCodeSpanStart(text) {
400
+ const n = text.length;
401
+ if (n === 0 || /\n[ \t]*\n[ \t]*$/.test(text)) return -1;
402
+ let fence = null;
403
+ let para = -1;
404
+ let pos = 0;
405
+ while (pos <= n) {
406
+ const nl = text.indexOf("\n", pos);
407
+ const lineEnd = nl === -1 ? n : nl;
408
+ const line = text.slice(pos, lineEnd);
409
+ let indent = 0;
410
+ while (indent < line.length && line[indent] === " ") indent++;
411
+ const body = line.slice(indent);
412
+ if (fence) {
413
+ const trimmed = trimFenceLine(line);
414
+ let closes = false;
415
+ if (fence.char === "`") {
416
+ const kind = classifyInnerFenceLine(trimmed, fence.len, fence.nests, fence.depth);
417
+ if (kind === "open-nested") fence.depth++;
418
+ else if (kind === "close-nested") fence.depth--;
419
+ else if (kind === "close-outer") closes = indent <= 3;
420
+ } else {
421
+ let t = 0;
422
+ while (t < trimmed.length && trimmed[t] === "~") t++;
423
+ closes = indent <= 3 && t >= fence.len && trimFenceLine(trimmed.slice(t)) === "";
424
+ }
425
+ if (closes) fence = null;
426
+ } else {
427
+ const opener = indent <= 3 ? /^(`{3,}|~{3,})(.*)$/.exec(body) : null;
428
+ const info = opener ? opener[2] : "";
429
+ if (opener && !(opener[1][0] === "`" && info.includes("`"))) {
430
+ para = -1;
431
+ const char = opener[1][0];
432
+ const parsed = char === "`" ? parseFenceOpener(line) : null;
433
+ fence = { char, len: opener[1].length, nests: fenceNestsInnerFences(parsed?.lang), depth: 0 };
434
+ } else if (body.trim() === "") {
435
+ if (!(nl === -1 && line === "")) para = -1;
436
+ } else if (para === -1) para = pos;
437
+ }
438
+ if (nl === -1) break;
439
+ pos = nl + 1;
440
+ }
441
+ if (fence || para === -1) return -1;
442
+ const runs = [];
443
+ for (let k = para; k < n; ) {
444
+ if (text.charCodeAt(k) !== 96) {
445
+ k++;
446
+ continue;
447
+ }
448
+ let len = 1;
449
+ while (k + len < n && text.charCodeAt(k + len) === 96) len++;
450
+ runs.push([k, len]);
451
+ k += len;
452
+ }
453
+ const growing = (r) => {
454
+ const [start, len] = runs[r];
455
+ return start + len === n;
456
+ };
457
+ for (let r = 0; r < runs.length; ) {
458
+ if (growing(r)) return runs[r][0];
459
+ const len = runs[r][1];
460
+ let j = r + 1;
461
+ while (j < runs.length && (runs[j][1] !== len || growing(j))) j++;
462
+ if (j >= runs.length) return runs[r][0];
463
+ r = j + 1;
464
+ }
465
+ return -1;
466
+ }
467
+
316
468
  // source/math-pairs.ts
317
469
  var MAX_MATH_SPAN = 600;
318
470
  var STRUCTURAL_MARKDOWN = /\]\(|https?:\/\/|\*\*|(?:^|\n)[ \t]{0,3}#{1,6}[ \t]|(?:^|\n)[ \t]*[-*+][ \t]+|(?:^|\n)[ \t]*\d+[.)][ \t]/;
319
471
  var LATEX_COMMAND = /\\[a-zA-Z]/;
320
472
  var PROSE_WORD = /[A-Za-z]{3,}/g;
321
473
  var PROSE_WORD_LIMIT = 6;
474
+ function isMathPlaceholder(inner) {
475
+ return /^[\s.…·⋯_-]*$/.test(inner) && /[.…·⋯]/.test(inner);
476
+ }
322
477
  function opensFlowBlock(text, open) {
323
478
  for (let k = open - 1; k >= 0 && text[k] !== "\n"; k--) if (text[k] !== " " && text[k] !== " ") return false;
324
479
  for (let k = open + 2; k < text.length && text[k] !== "\n"; k++) if (text[k] !== " " && text[k] !== " " && text[k] !== "\r") return false;
@@ -336,15 +491,7 @@ function looksLikeDisplayMath(inner, flow = false) {
336
491
  return (s.match(PROSE_WORD) ?? []).length < PROSE_WORD_LIMIT;
337
492
  }
338
493
  function codeRanges(text) {
339
- const ranges = [];
340
- for (const re of [/```[\s\S]*?(?:```|$)/g, /~~~[\s\S]*?(?:~~~|$)/g, /`[^`\n]*`/g]) {
341
- let m;
342
- while ((m = re.exec(text)) !== null) {
343
- ranges.push([m.index, m.index + m[0].length]);
344
- if (m[0].length === 0) re.lastIndex++;
345
- }
346
- }
347
- return ranges.sort((a, b) => a[0] - b[0]);
494
+ return findCodeRanges(text).map((r) => [r.start, r.end]);
348
495
  }
349
496
  function pendingDisplayMathOpener(text) {
350
497
  if (!text.includes("$$")) return -1;
@@ -366,6 +513,10 @@ function pendingDisplayMathOpener(text) {
366
513
  const open = tokens[j];
367
514
  const close = tokens[j + 1];
368
515
  if (close === void 0) break;
516
+ if (isMathPlaceholder(text.slice(open + 2, close))) {
517
+ j += 2;
518
+ continue;
519
+ }
369
520
  if (looksLikeDisplayMath(text.slice(open + 2, close), opensFlowBlock(text, open))) {
370
521
  paired.add(open);
371
522
  paired.add(close);
@@ -410,6 +561,10 @@ function pairDisplayMath(text) {
410
561
  const open = tokens[j];
411
562
  const close = tokens[j + 1];
412
563
  if (close === void 0) break;
564
+ if (isMathPlaceholder(text.slice(open + 2, close))) {
565
+ j += 2;
566
+ continue;
567
+ }
413
568
  if (looksLikeDisplayMath(text.slice(open + 2, close), opensFlowBlock(text, open))) {
414
569
  pairs.set(open, close);
415
570
  j += 2;
@@ -747,6 +902,27 @@ var Tokenizer = class {
747
902
  this.mathPairs = pairDisplayMath(text);
748
903
  this.provisionalFrom = streaming ? pendingDisplayMathOpener(text) : -1;
749
904
  if (this.provisionalFrom === -1) this.provisionalFrom = Number.POSITIVE_INFINITY;
905
+ if (streaming && text.includes("$$")) {
906
+ const span = pendingCodeSpanStart(text);
907
+ if (span !== -1) {
908
+ const firstToken = text.indexOf("$$", span);
909
+ if (firstToken !== -1) this.provisionalFrom = Math.min(this.provisionalFrom, firstToken);
910
+ for (const [open, close] of this.mathPairs) {
911
+ if (close >= span) this.provisionalFrom = Math.min(this.provisionalFrom, open);
912
+ }
913
+ }
914
+ for (const pad of ["`", "``", "```", "~~~"]) {
915
+ const alt = pairDisplayMath(text + pad);
916
+ for (const [open, close] of this.mathPairs) {
917
+ if (alt.get(open) !== close) this.provisionalFrom = Math.min(this.provisionalFrom, open);
918
+ }
919
+ for (const [open, close] of alt) {
920
+ if (close < text.length && this.mathPairs.get(open) !== close) {
921
+ this.provisionalFrom = Math.min(this.provisionalFrom, open);
922
+ }
923
+ }
924
+ }
925
+ }
750
926
  let start = 0;
751
927
  for (let i = 0; i < text.length; i++) {
752
928
  if (text.charCodeAt(i) === 10) {
@@ -787,13 +963,17 @@ var Tokenizer = class {
787
963
  /**
788
964
  * Streaming: an open last line that begins with `{` is undecided — each new
789
965
  * byte can balance or unbalance its braces and flip the whole line between
790
- * prose and a JSON island — so it is provisional until it ends (G5).
966
+ * prose and a JSON island — and so is a line that is exactly one allowed
967
+ * HTML tag so far; both are provisional until they end (G5).
791
968
  */
792
969
  markOpenJsonTail() {
793
970
  const last = this.lineStarts.length - 1;
794
971
  if (!this.isOpenTail(last)) return;
795
972
  const start = this.lineStarts[last];
796
- if (this.lineText(last).trimStart().startsWith("{")) {
973
+ const t = this.lineText(last).trim();
974
+ const oneTag = t.startsWith("<") ? readXmlTag(t, 0) : null;
975
+ const loneTag = !!oneTag && oneTag.raw === t && ALLOWED_RAW_HTML_TAGS.has(oneTag.tagName.toLowerCase());
976
+ if (t.startsWith("{") || loneTag) {
797
977
  this.provisionalFrom = Math.min(this.provisionalFrom, start);
798
978
  }
799
979
  }
@@ -1086,11 +1266,17 @@ var Tokenizer = class {
1086
1266
  last = k;
1087
1267
  continue;
1088
1268
  }
1269
+ if (this.isOpenTail(k)) return this.heldFootnote(p, open[1]);
1089
1270
  if (FOOTNOTE_DEF.test(line.trimStart()) || this.detectBlock(this.lineStarts[k], k, false)) break;
1090
- if (this.isOpenTail(k)) break;
1091
1271
  last = k;
1092
1272
  }
1093
- return this.island("footnote_def", p, this.contentEnds[last], true, { id: open[1] });
1273
+ const end = this.contentEnds[last];
1274
+ if (this.paragraphMayGrow(end)) return this.heldFootnote(p, open[1]);
1275
+ return this.island("footnote_def", p, end, true, { id: open[1] });
1276
+ }
1277
+ /** A footnote whose continuation is still arriving: open to the end of the text. */
1278
+ heldFootnote(p, id) {
1279
+ return this.island("footnote_def", p, this.n, false, { id });
1094
1280
  }
1095
1281
  /**
1096
1282
  * Backtick fence — the renderer splitter's close rules: JSON-string aware for
@@ -1334,7 +1520,12 @@ var Tokenizer = class {
1334
1520
  return this.island("html_block", p, close.index + close[0].length, true, { tag: name });
1335
1521
  }
1336
1522
  const typeSix = HTML_BLOCK_LEVEL_TAGS.has(name);
1337
- const typeSeven = atParaStart && t.trimEnd() === tag.raw && !this.isOpenTail(li);
1523
+ const loneTag = atParaStart && t.trimEnd() === tag.raw;
1524
+ if (loneTag && this.isOpenTail(li)) {
1525
+ this.provisionalFrom = Math.min(this.provisionalFrom, p);
1526
+ return null;
1527
+ }
1528
+ const typeSeven = loneTag;
1338
1529
  if (!typeSix && !typeSeven) return null;
1339
1530
  let end = this.contentEnds[li];
1340
1531
  for (let k = li + 1; k < this.lineCount && !this.lineIsBlank(k); k++) {
@@ -1426,10 +1617,6 @@ var Tokenizer = class {
1426
1617
  let breakLine = -1;
1427
1618
  for (let k = li + 1; k < this.lineCount; k++) {
1428
1619
  if (this.lineIsBlank(k)) break;
1429
- if (this.lineStarts[k] >= this.provisionalFrom) {
1430
- end = this.n;
1431
- break;
1432
- }
1433
1620
  if (this.detectBlock(this.lineStarts[k], k, false)) {
1434
1621
  breakLine = k;
1435
1622
  break;
@@ -1572,7 +1759,11 @@ var Tokenizer = class {
1572
1759
  }
1573
1760
  const end = singleDollarMathEnd(text, i, pe);
1574
1761
  this.tick(end > 0 ? end - i : Math.min(403, pe - i));
1575
- if (end > 0 && !(this.streaming && end === this.n)) {
1762
+ if (end > 0 && this.streaming && end === this.n) {
1763
+ this.provisionalFrom = Math.min(this.provisionalFrom, i);
1764
+ return { inlines, breakIsland: null, orphan: false };
1765
+ }
1766
+ if (end > 0) {
1576
1767
  inlines.push(this.island("math_inline", i, end, true, { delimiter: "$" }));
1577
1768
  i = end;
1578
1769
  continue;
@@ -2063,6 +2254,6 @@ function mapRange(changes, start, end, unit = "utf16") {
2063
2254
  return { start: newStart, end: newEnd, touched, collapsed: newEnd === newStart && end > start };
2064
2255
  }
2065
2256
 
2066
- export { CALLOUT_NAMES, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, NESTING_FENCE_LANGUAGES, SourceSpliceError, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, pendingDisplayMathOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
2257
+ export { CALLOUT_NAMES, FENCE_OPENER_WHITESPACE, FENCE_WHITESPACE, NESTING_FENCE_LANGUAGES, SourceSpliceError, XmlContainerTracker, blockAt, blockEdit, blockRangeEdit, buildCodePointIndex, classifyInnerFenceLine, fenceNestsInnerFences, findCodeRanges, isMathPlaceholder, isSingleDollarMath, islandEdit, joinSource, listIslands, looksLikeDisplayMath, mapPosition, mapRange, pairDisplayMath, parseFenceOpener, pendingDisplayMathOpener, readXmlTag, singleDollarMathEnd, spliceSave, splitsSurrogatePair, toCodePointOffset, toUtf16Offset, tokenizeSource, trimFenceLine };
2067
2258
  //# sourceMappingURL=source.js.map
2068
2259
  //# sourceMappingURL=source.js.map