@ai-react-markdown/engine 2.4.2 → 2.4.5

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
@@ -425,6 +425,49 @@ interface FreezeScanCheckpoint {
425
425
  * tagBalance but not yet confirmed by a later `>` — reverted at the next
426
426
  * blank line (a tag cannot span one). See TRUNCATED_TAG_RE. */
427
427
  pendingTruncatedTags: string[];
428
+ /** Line-truncated CLOSING tags (`</div` + EOL, no `>`) inside an html-flow
429
+ * run, waiting for a `>` on a later line of the same run. Unlike opens
430
+ * they are NOT counted up front: a close tag cannot carry attributes, so
431
+ * `para </style` (prose to micromark, still-open element to parse5 —
432
+ * RAWTEXT waits for the `>`) must not zero the balance (2026-08-19
433
+ * review P1 — the boundary crossed an open `<style>`). Confirmed and
434
+ * applied only when a later line of the run brings the `>` (parse5
435
+ * completes the end tag; micromark's block ends at the blank anyway);
436
+ * dropped unapplied at the blank (element stays counted — over-block).
437
+ * Paragraph-context truncated closes are never pended: a `>` at block
438
+ * indent on the next line is a blockquote to micromark, and the one
439
+ * shape that would complete the inline close (`</b\n >`, a 4+-space
440
+ * lazy continuation) is not modelled — the element stays counted,
441
+ * over-block. */
442
+ pendingTruncatedCloses: string[];
443
+ /** An html-flow line ended inside a tag (`<div`, `</div`, `<br` — open,
444
+ * close or void, with or without attributes): parse5's tokenizer is
445
+ * still in that tag, so on the following lines everything up to the
446
+ * FIRST `>` is attribute garbage — `</div>` there does NOT close
447
+ * anything (oracle review of 2.4.4: `<div>\n<div>\n</div\n</div>` froze
448
+ * past the still-open outer div; pre-existing, 1-char slices). While
449
+ * set: a line without `>` gets no tag scan at all; the line with the
450
+ * `>` completes the pending close (if any), then only its text after
451
+ * that `>` is scanned. Cleared there and at the blank line. */
452
+ tagAcrossLines: boolean;
453
+ /** Indent of the line that set `tagAcrossLines`. A following line that
454
+ * DE-INDENTS below it may have left the container (a list item's html
455
+ * block ends there; hast-util-raw resets the tokenizer at the li/ul
456
+ * boundary, so a `<div>` on that line is a real start tag, not garbage)
457
+ * — or may still be the same html block (root-level ` </div\n</div>`,
458
+ * still garbage). Unknowable here → poison (oracle 3rd pass). */
459
+ tagAcrossLinesIndent: number;
460
+ /** The html-flow run since the last blank REALLY started as a micromark
461
+ * html block (type 6 / type 1 / a paragraph-not-interrupting type 7) —
462
+ * as opposed to `htmlFlowSinceBlank`, which any `<tag` / `</tag` line
463
+ * start sets (over-approximation, fine for its over-blocking uses). Only
464
+ * in a real run are the bytes raw to parse5 across line endings; the
465
+ * cross-line-tag garbage model (`tagAcrossLines`, pended closes) is
466
+ * gated on it — in a paragraph starting `</i` the next line's `<div>` /
467
+ * `<!--` are REAL blocks (oracle re-check of 2.4.4: gating on
468
+ * htmlFlowSinceBlank swallowed them — a new under-block). Sticky to the
469
+ * blank; a type 6/1 start on a later line of a non-real run promotes it. */
470
+ htmlFlowReal: boolean;
428
471
  }
429
472
  declare function computeFreezeBoundary(text: string, options: FreezeBoundaryOptions, resume?: FreezeScanCheckpoint | null): FreezeScanResult;
430
473
 
@@ -764,34 +807,6 @@ interface ExtractContributionsOptions {
764
807
  * Defs matching these are skipped to avoid leaking sentinel rows into
765
808
  * registry.chunkData. */
766
809
  phantomFootnoteLabels?: Set<string>;
767
- /**
768
- * Caller's resolved URL transform (typically `props.urlTransform ??
769
- * defaultUrlTransform`). Applied to every emitted `linkDef.url` so the
770
- * registry stores already-sanitized URLs.
771
- *
772
- * Cross-chunk link/image references render through the registry rather
773
- * than the in-tree hast (which is where react-markdown's transform pass
774
- * normally enforces `urlTransform`). Without this, a chunk defining
775
- * `[evil]: javascript:alert(1)` could XSS a sibling chunk that uses
776
- * `[click][evil]` — the standalone path strips the protocol; the cross-
777
- * chunk path would have rendered `<a href="javascript:…">`. Sanitizing at
778
- * contribute time also benefits any future consumer that reads
779
- * `Registry.resolveLinkDef` directly.
780
- *
781
- * Invocation contract mirrors react-markdown's hast-pass call site
782
- * (`buildTransform` in `./markdown/transform.ts`): a synthetic
783
- * `<a href={url}>` element stands in for the node argument since
784
- * mdast `definition` nodes have no hast counterpart. The key is `'href'`
785
- * — link defs are far more common than image defs, and protocol-allowlist
786
- * transforms (including `defaultUrlTransform`) are key-agnostic anyway.
787
- * A `null` return collapses to the empty string, matching how
788
- * `transform.ts` would render a blocked attribute.
789
- *
790
- * Omitting this option preserves v1 behavior (URLs stored raw). Library
791
- * callers should always supply it; the option stays optional so unit-test
792
- * fixtures that don't care about URL safety can construct minimal calls.
793
- */
794
- urlTransform?: UrlTransform;
795
810
  }
796
811
  declare function extractContributions(mdast: Root, options?: ExtractContributionsOptions): Generator<Contribution>;
797
812
 
@@ -890,18 +905,15 @@ interface Registry {
890
905
  canonicalLinkFor(label: string): symbol | null;
891
906
  globalNumber(label: string): number | null;
892
907
  /**
893
- * Resolve a cross-chunk link definition by label. The returned `url` is the
894
- * value the contributing chunk's `urlTransform` produced cross-chunk
895
- * link/image references run a second, per-attribute sanitization pass
896
- * (`urlTransform` + `sanitizeSchema.protocols`) at render time, so the
897
- * placeholder components themselves never trust this value blindly.
908
+ * Resolve a cross-chunk link definition by label. The returned `url` is
909
+ * the RAW destination from the contributing chunk's sourcethe registry
910
+ * does not sanitize. The library's cross-chunk link/image placeholders run
911
+ * the full per-attribute sanitization (`sanitizeSchema.protocols` +
912
+ * `urlTransform`, correct key) at render time.
898
913
  *
899
914
  * Consumers reading `def.url` directly (custom backlink panels, analytics,
900
- * dev tooling) receive a defense-in-depth-filtered string but should still
901
- * pipe it through their own `urlTransform` if they intend to render it as
902
- * an `href`/`src` — the contribute-time pass uses the `'href'` key and a
903
- * synthetic `<a>` node, so a key-aware policy may treat the value
904
- * differently when used as an `<img src>`.
915
+ * dev tooling) MUST run their own policy before rendering it as an
916
+ * `href`/`src` a chunk can define `[evil]: javascript:alert(1)`.
905
917
  */
906
918
  resolveLinkDef(label: string): LinkDef | null;
907
919
  getRefsForLabel(label: string): number;
@@ -1692,7 +1704,11 @@ declare const sanitizeSchema: Schema;
1692
1704
  * and the trailing grapheme of the source is held back until it is
1693
1705
  * confirmed — by more text arriving or by `finish()` — so a surrogate
1694
1706
  * half or a still-growing emoji ZWJ sequence is never revealed to the
1695
- * parser mid-cluster.
1707
+ * parser mid-cluster. `snap()` (a replacement, or the very first `update`)
1708
+ * is outside this promise by design: it shows the whole replacement text
1709
+ * at once, trailing half-cluster included — a hold-back there would leave
1710
+ * a controller that never drains when nothing follows (v2.4.2 review
1711
+ * P3-1). Hosts that seed a stream mid-cluster get one frame of U+FFFD.
1696
1712
  *
1697
1713
  * @module components/smoothStream/controller
1698
1714
  */
@@ -1776,7 +1792,8 @@ interface SmoothStreamController {
1776
1792
  * a later {@link update} resumes animation.
1777
1793
  */
1778
1794
  finish(): void;
1779
- /** Jumps to `source` instantly, no animation, and clears any backlog. */
1795
+ /** Jumps to `source` instantly, no animation, and clears any backlog.
1796
+ * No trailing-grapheme hold-back (see the module docs). */
1780
1797
  snap(source: string): void;
1781
1798
  /**
1782
1799
  * Reveals everything pending right now (skip-animation affordance). Keeps
@@ -1890,7 +1907,12 @@ interface Segment {
1890
1907
  * Split content into alternating text and protected segments.
1891
1908
  * Protected segments (isCode: true) are excluded from LaTeX processing:
1892
1909
  * - fenced multiline code blocks: 3+ backticks or tildes at the *start of a
1893
- * line* (≤3 space indent). Mid-line runs are never fence openers.
1910
+ * line* (any indentation container-relative limits are not modelled).
1911
+ * Mid-line runs are never fence openers. INDENTED code blocks (4+ spaces
1912
+ * after a blank line, outside any container) are NOT modelled: without a
1913
+ * container model they cannot be told from a list item's continuation
1914
+ * paragraph, and protecting them would silence math in nested lists.
1915
+ * Known limitation — `$` inside an indented code block may be rewritten.
1894
1916
  * - inline code spans: a run of N backticks closed by another run of exactly
1895
1917
  * N backticks. May span newlines. Multi-backtick forms (e.g. `` `` `x` ``)
1896
1918
  * are supported so literal backtick characters can appear inside.
@@ -1923,6 +1945,20 @@ declare function preprocessLaTeX(str: string): string;
1923
1945
  */
1924
1946
  declare function createIncrementalLatexPreprocessor(options?: {
1925
1947
  freezeThreshold?: number;
1948
+ /** Failure backoff (default on). Tests that rely on `freezeThreshold: 0`
1949
+ * to attempt a freeze on EVERY call turn it off, so 1-char chunkings keep
1950
+ * exercising the cut rules instead of skipping most attempts; backoff
1951
+ * can only freeze LESS, so passing without it implies passing with it.
1952
+ * @internal */
1953
+ backoff?: boolean;
1954
+ /** Test hook: called once per freeze ATTEMPT (a scan of the active
1955
+ * region) with the region's length and how many source bytes the
1956
+ * attempt froze (0 = failed), so the backoff bound and freeze progress
1957
+ * can be pinned. @internal */
1958
+ onAttempt?: (info: {
1959
+ activeLength: number;
1960
+ frozenBytes: number;
1961
+ }) => void;
1926
1962
  }): (content: string) => string;
1927
1963
 
1928
1964
  /**
package/dist/index.d.ts CHANGED
@@ -425,6 +425,49 @@ interface FreezeScanCheckpoint {
425
425
  * tagBalance but not yet confirmed by a later `>` — reverted at the next
426
426
  * blank line (a tag cannot span one). See TRUNCATED_TAG_RE. */
427
427
  pendingTruncatedTags: string[];
428
+ /** Line-truncated CLOSING tags (`</div` + EOL, no `>`) inside an html-flow
429
+ * run, waiting for a `>` on a later line of the same run. Unlike opens
430
+ * they are NOT counted up front: a close tag cannot carry attributes, so
431
+ * `para </style` (prose to micromark, still-open element to parse5 —
432
+ * RAWTEXT waits for the `>`) must not zero the balance (2026-08-19
433
+ * review P1 — the boundary crossed an open `<style>`). Confirmed and
434
+ * applied only when a later line of the run brings the `>` (parse5
435
+ * completes the end tag; micromark's block ends at the blank anyway);
436
+ * dropped unapplied at the blank (element stays counted — over-block).
437
+ * Paragraph-context truncated closes are never pended: a `>` at block
438
+ * indent on the next line is a blockquote to micromark, and the one
439
+ * shape that would complete the inline close (`</b\n >`, a 4+-space
440
+ * lazy continuation) is not modelled — the element stays counted,
441
+ * over-block. */
442
+ pendingTruncatedCloses: string[];
443
+ /** An html-flow line ended inside a tag (`<div`, `</div`, `<br` — open,
444
+ * close or void, with or without attributes): parse5's tokenizer is
445
+ * still in that tag, so on the following lines everything up to the
446
+ * FIRST `>` is attribute garbage — `</div>` there does NOT close
447
+ * anything (oracle review of 2.4.4: `<div>\n<div>\n</div\n</div>` froze
448
+ * past the still-open outer div; pre-existing, 1-char slices). While
449
+ * set: a line without `>` gets no tag scan at all; the line with the
450
+ * `>` completes the pending close (if any), then only its text after
451
+ * that `>` is scanned. Cleared there and at the blank line. */
452
+ tagAcrossLines: boolean;
453
+ /** Indent of the line that set `tagAcrossLines`. A following line that
454
+ * DE-INDENTS below it may have left the container (a list item's html
455
+ * block ends there; hast-util-raw resets the tokenizer at the li/ul
456
+ * boundary, so a `<div>` on that line is a real start tag, not garbage)
457
+ * — or may still be the same html block (root-level ` </div\n</div>`,
458
+ * still garbage). Unknowable here → poison (oracle 3rd pass). */
459
+ tagAcrossLinesIndent: number;
460
+ /** The html-flow run since the last blank REALLY started as a micromark
461
+ * html block (type 6 / type 1 / a paragraph-not-interrupting type 7) —
462
+ * as opposed to `htmlFlowSinceBlank`, which any `<tag` / `</tag` line
463
+ * start sets (over-approximation, fine for its over-blocking uses). Only
464
+ * in a real run are the bytes raw to parse5 across line endings; the
465
+ * cross-line-tag garbage model (`tagAcrossLines`, pended closes) is
466
+ * gated on it — in a paragraph starting `</i` the next line's `<div>` /
467
+ * `<!--` are REAL blocks (oracle re-check of 2.4.4: gating on
468
+ * htmlFlowSinceBlank swallowed them — a new under-block). Sticky to the
469
+ * blank; a type 6/1 start on a later line of a non-real run promotes it. */
470
+ htmlFlowReal: boolean;
428
471
  }
429
472
  declare function computeFreezeBoundary(text: string, options: FreezeBoundaryOptions, resume?: FreezeScanCheckpoint | null): FreezeScanResult;
430
473
 
@@ -764,34 +807,6 @@ interface ExtractContributionsOptions {
764
807
  * Defs matching these are skipped to avoid leaking sentinel rows into
765
808
  * registry.chunkData. */
766
809
  phantomFootnoteLabels?: Set<string>;
767
- /**
768
- * Caller's resolved URL transform (typically `props.urlTransform ??
769
- * defaultUrlTransform`). Applied to every emitted `linkDef.url` so the
770
- * registry stores already-sanitized URLs.
771
- *
772
- * Cross-chunk link/image references render through the registry rather
773
- * than the in-tree hast (which is where react-markdown's transform pass
774
- * normally enforces `urlTransform`). Without this, a chunk defining
775
- * `[evil]: javascript:alert(1)` could XSS a sibling chunk that uses
776
- * `[click][evil]` — the standalone path strips the protocol; the cross-
777
- * chunk path would have rendered `<a href="javascript:…">`. Sanitizing at
778
- * contribute time also benefits any future consumer that reads
779
- * `Registry.resolveLinkDef` directly.
780
- *
781
- * Invocation contract mirrors react-markdown's hast-pass call site
782
- * (`buildTransform` in `./markdown/transform.ts`): a synthetic
783
- * `<a href={url}>` element stands in for the node argument since
784
- * mdast `definition` nodes have no hast counterpart. The key is `'href'`
785
- * — link defs are far more common than image defs, and protocol-allowlist
786
- * transforms (including `defaultUrlTransform`) are key-agnostic anyway.
787
- * A `null` return collapses to the empty string, matching how
788
- * `transform.ts` would render a blocked attribute.
789
- *
790
- * Omitting this option preserves v1 behavior (URLs stored raw). Library
791
- * callers should always supply it; the option stays optional so unit-test
792
- * fixtures that don't care about URL safety can construct minimal calls.
793
- */
794
- urlTransform?: UrlTransform;
795
810
  }
796
811
  declare function extractContributions(mdast: Root, options?: ExtractContributionsOptions): Generator<Contribution>;
797
812
 
@@ -890,18 +905,15 @@ interface Registry {
890
905
  canonicalLinkFor(label: string): symbol | null;
891
906
  globalNumber(label: string): number | null;
892
907
  /**
893
- * Resolve a cross-chunk link definition by label. The returned `url` is the
894
- * value the contributing chunk's `urlTransform` produced cross-chunk
895
- * link/image references run a second, per-attribute sanitization pass
896
- * (`urlTransform` + `sanitizeSchema.protocols`) at render time, so the
897
- * placeholder components themselves never trust this value blindly.
908
+ * Resolve a cross-chunk link definition by label. The returned `url` is
909
+ * the RAW destination from the contributing chunk's sourcethe registry
910
+ * does not sanitize. The library's cross-chunk link/image placeholders run
911
+ * the full per-attribute sanitization (`sanitizeSchema.protocols` +
912
+ * `urlTransform`, correct key) at render time.
898
913
  *
899
914
  * Consumers reading `def.url` directly (custom backlink panels, analytics,
900
- * dev tooling) receive a defense-in-depth-filtered string but should still
901
- * pipe it through their own `urlTransform` if they intend to render it as
902
- * an `href`/`src` — the contribute-time pass uses the `'href'` key and a
903
- * synthetic `<a>` node, so a key-aware policy may treat the value
904
- * differently when used as an `<img src>`.
915
+ * dev tooling) MUST run their own policy before rendering it as an
916
+ * `href`/`src` a chunk can define `[evil]: javascript:alert(1)`.
905
917
  */
906
918
  resolveLinkDef(label: string): LinkDef | null;
907
919
  getRefsForLabel(label: string): number;
@@ -1692,7 +1704,11 @@ declare const sanitizeSchema: Schema;
1692
1704
  * and the trailing grapheme of the source is held back until it is
1693
1705
  * confirmed — by more text arriving or by `finish()` — so a surrogate
1694
1706
  * half or a still-growing emoji ZWJ sequence is never revealed to the
1695
- * parser mid-cluster.
1707
+ * parser mid-cluster. `snap()` (a replacement, or the very first `update`)
1708
+ * is outside this promise by design: it shows the whole replacement text
1709
+ * at once, trailing half-cluster included — a hold-back there would leave
1710
+ * a controller that never drains when nothing follows (v2.4.2 review
1711
+ * P3-1). Hosts that seed a stream mid-cluster get one frame of U+FFFD.
1696
1712
  *
1697
1713
  * @module components/smoothStream/controller
1698
1714
  */
@@ -1776,7 +1792,8 @@ interface SmoothStreamController {
1776
1792
  * a later {@link update} resumes animation.
1777
1793
  */
1778
1794
  finish(): void;
1779
- /** Jumps to `source` instantly, no animation, and clears any backlog. */
1795
+ /** Jumps to `source` instantly, no animation, and clears any backlog.
1796
+ * No trailing-grapheme hold-back (see the module docs). */
1780
1797
  snap(source: string): void;
1781
1798
  /**
1782
1799
  * Reveals everything pending right now (skip-animation affordance). Keeps
@@ -1890,7 +1907,12 @@ interface Segment {
1890
1907
  * Split content into alternating text and protected segments.
1891
1908
  * Protected segments (isCode: true) are excluded from LaTeX processing:
1892
1909
  * - fenced multiline code blocks: 3+ backticks or tildes at the *start of a
1893
- * line* (≤3 space indent). Mid-line runs are never fence openers.
1910
+ * line* (any indentation container-relative limits are not modelled).
1911
+ * Mid-line runs are never fence openers. INDENTED code blocks (4+ spaces
1912
+ * after a blank line, outside any container) are NOT modelled: without a
1913
+ * container model they cannot be told from a list item's continuation
1914
+ * paragraph, and protecting them would silence math in nested lists.
1915
+ * Known limitation — `$` inside an indented code block may be rewritten.
1894
1916
  * - inline code spans: a run of N backticks closed by another run of exactly
1895
1917
  * N backticks. May span newlines. Multi-backtick forms (e.g. `` `` `x` ``)
1896
1918
  * are supported so literal backtick characters can appear inside.
@@ -1923,6 +1945,20 @@ declare function preprocessLaTeX(str: string): string;
1923
1945
  */
1924
1946
  declare function createIncrementalLatexPreprocessor(options?: {
1925
1947
  freezeThreshold?: number;
1948
+ /** Failure backoff (default on). Tests that rely on `freezeThreshold: 0`
1949
+ * to attempt a freeze on EVERY call turn it off, so 1-char chunkings keep
1950
+ * exercising the cut rules instead of skipping most attempts; backoff
1951
+ * can only freeze LESS, so passing without it implies passing with it.
1952
+ * @internal */
1953
+ backoff?: boolean;
1954
+ /** Test hook: called once per freeze ATTEMPT (a scan of the active
1955
+ * region) with the region's length and how many source bytes the
1956
+ * attempt froze (0 = failed), so the backoff bound and freeze progress
1957
+ * can be pinned. @internal */
1958
+ onAttempt?: (info: {
1959
+ activeLength: number;
1960
+ frozenBytes: number;
1961
+ }) => void;
1926
1962
  }): (content: string) => string;
1927
1963
 
1928
1964
  /**