@ai-react-markdown/engine 2.3.3 → 2.4.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.d.cts CHANGED
@@ -184,7 +184,10 @@ declare const defaultUrlTransform: UrlTransform;
184
184
  * tracked outside fences; while any tag, comment, or raw block
185
185
  * (`<?…?>` / `<!DECL…>` / `<![CDATA[…]]>` — CommonMark html block types
186
186
  * 3–5) is open, candidates are blocked. Line-truncated tag starts
187
- * (`<div` at EOL, attributes wrapping) count as opens.
187
+ * (`<div` at EOL, attributes wrapping) count as opens. Closers that
188
+ * OVERLAP their opener (`<!-->`, `<!--->`, line-start `<?>`) close on
189
+ * the spot — CommonMark and parse5 agree — so the markup after them is
190
+ * scanned, not skipped as construct interior.
188
191
  * 2. **`$$` flow math** — remark-math's flow math swallows blank lines and
189
192
  * runs to EOF when unclosed (verified empirically); its closing fence
190
193
  * must sit at LINE START (a mid-line `$$` does not close it). Math
@@ -215,19 +218,29 @@ declare const defaultUrlTransform: UrlTransform;
215
218
  * whether a sibling node FOLLOWS it. A tail block that flips between
216
219
  * def (no hast output) and paragraph therefore reshapes the frozen
217
220
  * region retroactively (2026-07-31 direction-battery counterexample,
218
- * reproduced on v1.8.0). The candidate adjacent to such a run is
219
- * rejected until a later confirmed content line pins the seam from the
220
- * frozen side; dropping candidates only over-blocks (safe direction).
221
+ * reproduced on v1.8.0). Every candidate emitted while the remnant is
222
+ * the last frozen child is rejected (they stay rejected for good); once
223
+ * a later confirmed content line pins the seam from the frozen side,
224
+ * the candidates AFTER that line are safe again. Dropping candidates
225
+ * only over-blocks (safe direction). Whitespace-only remnant counts.
226
+ * A type 2-5 block that opens AND closes on its first line owns the
227
+ * rest of that line as raw content too (`<!-- c --> tail`) — same seam.
221
228
  * 7. **Phase poison** (`phasePoisonedAt`) — points where this line-level
222
229
  * model may have DIVERGED from micromark and provably cannot resync:
223
230
  * a fence/math open suppressed by `htmlFlowSinceBlank` (only certainly
224
231
  * swallowed at top level — in a container it really opens and the
225
232
  * open/close phase inverts permanently), and a paragraph-inline `<!--`
226
233
  * that fails to close by end of line (literal text to micromark, but
227
- * the comment scan would skip real markup as comment interior). Every
228
- * candidate past the first such point is rejected, sticky; candidates
229
- * at or before it stay valid the ambiguous region then re-parses
230
- * inside the tail (pure over-block).
234
+ * the comment scan would skip real markup as comment interior), and
235
+ * every point where CommonMark's terminator and parse5's tokenizer
236
+ * DISAGREE about where a raw construct ends (`--!>` closes a comment
237
+ * for parse5 only; a `<?…`/`<![CDATA[…` bogus comment ends at its
238
+ * first `>` for parse5 but at `?>`/`]]>` for CommonMark; a paragraph-
239
+ * inline `<?>` is open to micromark, closed to parse5) — the bytes in
240
+ * between are raw text to one grammar and real markup to the other, and
241
+ * the hast is parse5's. Every candidate past the first such point is
242
+ * rejected, sticky; candidates at or before it stay valid — the
243
+ * ambiguous region then re-parses inside the tail (pure over-block).
231
244
  *
232
245
  * ## Incremental scanning (checkpoint resume)
233
246
  *
@@ -353,6 +366,12 @@ interface FreezeScanCheckpoint {
353
366
  inMath: boolean;
354
367
  /** Opening dollar-run length while inMath — the close run must match it. */
355
368
  mathFenceLen: number;
369
+ /** Indent (0-3 spaces) of the line that opened the current fence/math
370
+ * block. Not a blocker input — read by `phantomSuffixCloser` to emit a
371
+ * closer at the SAME indent, which closes the block whether it sits at
372
+ * top level (≤3 spaces are allowed there) or inside a list item whose
373
+ * content indent the opener line already satisfies. */
374
+ openIndent: number;
356
375
  blankRun: number;
357
376
  lastBlankStart: number;
358
377
  /** Rolling blocker-3 verdict ("nearest decisive block start so far"). */
@@ -392,6 +411,10 @@ interface FreezeScanCheckpoint {
392
411
  * ambiguous tag names stays, but it decays at the next decisive block
393
412
  * start — this field is the phase-corruption backstop that does not. */
394
413
  phasePoisonedAt: number;
414
+ /** Tag names of line-truncated opens (`<div` at EOL) counted into
415
+ * tagBalance but not yet confirmed by a later `>` — reverted at the next
416
+ * blank line (a tag cannot span one). See TRUNCATED_TAG_RE. */
417
+ pendingTruncatedTags: string[];
395
418
  }
396
419
  declare function computeFreezeBoundary(text: string, options: FreezeBoundaryOptions, resume?: FreezeScanCheckpoint | null): FreezeScanResult;
397
420
 
@@ -687,6 +710,17 @@ interface DefLabelScanner {
687
710
  */
688
711
  declare function createDefLabelScanner(parse?: (source: string) => DefLabels): DefLabelScanner;
689
712
 
713
+ /**
714
+ * The id fragment mdast-util-to-hast derives from a footnote identifier for
715
+ * `fn-…` / `fnref-…` (`normalizeUri(identifier.toLowerCase())` — percent-
716
+ * encodes non-ASCII, whitespace and URL-unsafe punctuation). Every place
717
+ * that MINTS a footnote id — the standalone footer (upstream), the
718
+ * cross-chunk marks and the aggregate footer — must use this same
719
+ * encoding, or a mark's `href` and its `<li id>` disagree (v2.4.0 review:
720
+ * the fallback mark wrote the raw label while the local footer was
721
+ * encoded; `[^注]` / `[^a%b]` anchors broke).
722
+ */
723
+ declare function footnoteSafeId(identifier: string): string;
690
724
  /**
691
725
  * Extract the SOURCE identifier from a footnote `<li>` id, covering both id
692
726
  * shapes in the codebase — standalone (mdast-util-to-hast `fn-` +
@@ -1196,6 +1230,49 @@ interface PhantomLabels {
1196
1230
  * Labels are expected to already be normalized via normalizeId (uppercase).
1197
1231
  */
1198
1232
  declare function buildPhantomSuffix(phantoms: PhantomLabels): string;
1233
+ /**
1234
+ * The bytes to put BETWEEN a chunk's content and its phantom suffix so the
1235
+ * suffix's definition lines are parsed as definitions.
1236
+ *
1237
+ * `buildPhantomSuffix` is appended (never prepended: the incremental engine
1238
+ * treats the suffix as an always-tail input, and prepending would shift
1239
+ * every source position). But an append lands INSIDE whatever block the
1240
+ * content ends in — and a fenced code block or `$$` flow-math block that is
1241
+ * still open at the end of a streaming frame swallows everything up to EOF:
1242
+ * the sentinel lines render as code/math text and, since no definition was
1243
+ * registered, every cross-chunk reference in the chunk falls back to literal
1244
+ * `[text][label]` for the whole time the block streams (2026-08 project
1245
+ * review, core-render-01). Closing the block first is output-neutral: an
1246
+ * unclosed fence/math block already renders exactly the lines it has, so
1247
+ * `content + closer` yields the same code/math node value (positions
1248
+ * extend past `content.length`, which every consumer already tolerates for
1249
+ * the suffix's own nodes).
1250
+ *
1251
+ * A closer is emitted ONLY for a block whose opener line sits at column 0:
1252
+ * that is provably a top-level fence, which only a top-level closer (same
1253
+ * char, ≥ length, ≤3 spaces indent) can end — exactly what the line scanner
1254
+ * tracks. Openers with any indent are left alone: the line model cannot
1255
+ * see container boundaries, and an indented opener may live in a list item
1256
+ * that later de-indented content already ended (micromark closed the fence
1257
+ * with the item; an emitted closer would OPEN a new fence around the
1258
+ * sentinel lines — v2.4.0 review R1) or be closed by a ≥4-space closer the
1259
+ * `^ {0,3}` scan does not recognize. Those shapes keep the plain append
1260
+ * (swallowed at worst — the pre-2.4.0 behaviour), never a wrong closer.
1261
+ *
1262
+ * Only fences and flow math are closed. Raw-HTML constructs (`<!--`, `<?`,
1263
+ * `<![CDATA[`, `<!X`, `<script>`) that stay open to EOF also swallow the
1264
+ * suffix (invisibly — sanitize strips them), but their closers are not
1265
+ * position-neutral for the paragraph-inline forms and the shapes are rare
1266
+ * in LLM output; they keep the plain-append behaviour. When the scanner's
1267
+ * fence/math phase is untrusted (`phasePoisonedAt` — a suppressed open in a
1268
+ * container, see computeFreezeBoundary blocker 7) no closer is emitted
1269
+ * either: a wrong closer would OPEN a block around the suffix.
1270
+ *
1271
+ * Returns '' when nothing needs closing. Cost: one line scan of `content`
1272
+ * (regex per line; no reference tracking) — only paid by chunks that have a
1273
+ * non-empty phantom suffix.
1274
+ */
1275
+ declare function phantomSuffixCloser(content: string): string;
1199
1276
 
1200
1277
  /**
1201
1278
  * Custom mdast-util-to-hast handlers for cross-chunk label resolution.
@@ -1343,19 +1420,6 @@ declare const defaultEnginePlugins: readonly AIMarkdownEnginePlugin[];
1343
1420
  */
1344
1421
  declare function isFootnoteSection(node: Element): boolean;
1345
1422
 
1346
- /**
1347
- * CommonMark §4.7 label normalization. Used as the single canonical form
1348
- * for all label-keyed structures (registry maps, phantomFootnoteLabels Set,
1349
- * labelSet, etc.) and for handler comparisons against mdast-util-to-hast's
1350
- * internal `state.definitionById` / `state.footnoteById` keys.
1351
- *
1352
- * Direction is uppercase to align with mdast-util-to-hast internals
1353
- * (`String(identifier).toUpperCase()`). Direction is irrelevant once both
1354
- * sides agree; uppercase chosen to match the upstream library to minimize
1355
- * adapter calls.
1356
- *
1357
- * @module components/normalizeId
1358
- */
1359
1423
  declare function normalizeId(s: string): string;
1360
1424
  /**
1361
1425
  * Same as {@link normalizeId} plus resolution of backslash escapes.
@@ -1692,7 +1756,13 @@ interface SmoothStreamController {
1692
1756
  finish(): void;
1693
1757
  /** Jumps to `source` instantly, no animation, and clears any backlog. */
1694
1758
  snap(source: string): void;
1695
- /** Reveals everything pending right now (skip-animation affordance). */
1759
+ /**
1760
+ * Reveals everything pending right now (skip-animation affordance). Keeps
1761
+ * the grapheme discipline: while the stream is not finished, the trailing
1762
+ * grapheme of the source stays held back exactly as the animation would
1763
+ * hold it (a surrogate half or a growing ZWJ sequence must never reach
1764
+ * the parser); {@link finish} or the next {@link update} confirms it.
1765
+ */
1696
1766
  flush(): void;
1697
1767
  /** Subscribes to visible-prefix changes. Returns the unsubscribe. */
1698
1768
  subscribe(listener: () => void): () => void;
@@ -1899,4 +1969,4 @@ type RemendPreprocessorOptions = Omit<RemendOptions, 'katex' | 'inlineKatex'>;
1899
1969
  */
1900
1970
  declare function createRemendPreprocessor(options?: RemendPreprocessorOptions): AIMDContentPreprocessor;
1901
1971
 
1902
- export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, 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 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, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };
1972
+ export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, 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 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, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, footnoteSafeId, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, phantomSuffixCloser, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };
package/dist/index.d.ts CHANGED
@@ -184,7 +184,10 @@ declare const defaultUrlTransform: UrlTransform;
184
184
  * tracked outside fences; while any tag, comment, or raw block
185
185
  * (`<?…?>` / `<!DECL…>` / `<![CDATA[…]]>` — CommonMark html block types
186
186
  * 3–5) is open, candidates are blocked. Line-truncated tag starts
187
- * (`<div` at EOL, attributes wrapping) count as opens.
187
+ * (`<div` at EOL, attributes wrapping) count as opens. Closers that
188
+ * OVERLAP their opener (`<!-->`, `<!--->`, line-start `<?>`) close on
189
+ * the spot — CommonMark and parse5 agree — so the markup after them is
190
+ * scanned, not skipped as construct interior.
188
191
  * 2. **`$$` flow math** — remark-math's flow math swallows blank lines and
189
192
  * runs to EOF when unclosed (verified empirically); its closing fence
190
193
  * must sit at LINE START (a mid-line `$$` does not close it). Math
@@ -215,19 +218,29 @@ declare const defaultUrlTransform: UrlTransform;
215
218
  * whether a sibling node FOLLOWS it. A tail block that flips between
216
219
  * def (no hast output) and paragraph therefore reshapes the frozen
217
220
  * region retroactively (2026-07-31 direction-battery counterexample,
218
- * reproduced on v1.8.0). The candidate adjacent to such a run is
219
- * rejected until a later confirmed content line pins the seam from the
220
- * frozen side; dropping candidates only over-blocks (safe direction).
221
+ * reproduced on v1.8.0). Every candidate emitted while the remnant is
222
+ * the last frozen child is rejected (they stay rejected for good); once
223
+ * a later confirmed content line pins the seam from the frozen side,
224
+ * the candidates AFTER that line are safe again. Dropping candidates
225
+ * only over-blocks (safe direction). Whitespace-only remnant counts.
226
+ * A type 2-5 block that opens AND closes on its first line owns the
227
+ * rest of that line as raw content too (`<!-- c --> tail`) — same seam.
221
228
  * 7. **Phase poison** (`phasePoisonedAt`) — points where this line-level
222
229
  * model may have DIVERGED from micromark and provably cannot resync:
223
230
  * a fence/math open suppressed by `htmlFlowSinceBlank` (only certainly
224
231
  * swallowed at top level — in a container it really opens and the
225
232
  * open/close phase inverts permanently), and a paragraph-inline `<!--`
226
233
  * that fails to close by end of line (literal text to micromark, but
227
- * the comment scan would skip real markup as comment interior). Every
228
- * candidate past the first such point is rejected, sticky; candidates
229
- * at or before it stay valid the ambiguous region then re-parses
230
- * inside the tail (pure over-block).
234
+ * the comment scan would skip real markup as comment interior), and
235
+ * every point where CommonMark's terminator and parse5's tokenizer
236
+ * DISAGREE about where a raw construct ends (`--!>` closes a comment
237
+ * for parse5 only; a `<?…`/`<![CDATA[…` bogus comment ends at its
238
+ * first `>` for parse5 but at `?>`/`]]>` for CommonMark; a paragraph-
239
+ * inline `<?>` is open to micromark, closed to parse5) — the bytes in
240
+ * between are raw text to one grammar and real markup to the other, and
241
+ * the hast is parse5's. Every candidate past the first such point is
242
+ * rejected, sticky; candidates at or before it stay valid — the
243
+ * ambiguous region then re-parses inside the tail (pure over-block).
231
244
  *
232
245
  * ## Incremental scanning (checkpoint resume)
233
246
  *
@@ -353,6 +366,12 @@ interface FreezeScanCheckpoint {
353
366
  inMath: boolean;
354
367
  /** Opening dollar-run length while inMath — the close run must match it. */
355
368
  mathFenceLen: number;
369
+ /** Indent (0-3 spaces) of the line that opened the current fence/math
370
+ * block. Not a blocker input — read by `phantomSuffixCloser` to emit a
371
+ * closer at the SAME indent, which closes the block whether it sits at
372
+ * top level (≤3 spaces are allowed there) or inside a list item whose
373
+ * content indent the opener line already satisfies. */
374
+ openIndent: number;
356
375
  blankRun: number;
357
376
  lastBlankStart: number;
358
377
  /** Rolling blocker-3 verdict ("nearest decisive block start so far"). */
@@ -392,6 +411,10 @@ interface FreezeScanCheckpoint {
392
411
  * ambiguous tag names stays, but it decays at the next decisive block
393
412
  * start — this field is the phase-corruption backstop that does not. */
394
413
  phasePoisonedAt: number;
414
+ /** Tag names of line-truncated opens (`<div` at EOL) counted into
415
+ * tagBalance but not yet confirmed by a later `>` — reverted at the next
416
+ * blank line (a tag cannot span one). See TRUNCATED_TAG_RE. */
417
+ pendingTruncatedTags: string[];
395
418
  }
396
419
  declare function computeFreezeBoundary(text: string, options: FreezeBoundaryOptions, resume?: FreezeScanCheckpoint | null): FreezeScanResult;
397
420
 
@@ -687,6 +710,17 @@ interface DefLabelScanner {
687
710
  */
688
711
  declare function createDefLabelScanner(parse?: (source: string) => DefLabels): DefLabelScanner;
689
712
 
713
+ /**
714
+ * The id fragment mdast-util-to-hast derives from a footnote identifier for
715
+ * `fn-…` / `fnref-…` (`normalizeUri(identifier.toLowerCase())` — percent-
716
+ * encodes non-ASCII, whitespace and URL-unsafe punctuation). Every place
717
+ * that MINTS a footnote id — the standalone footer (upstream), the
718
+ * cross-chunk marks and the aggregate footer — must use this same
719
+ * encoding, or a mark's `href` and its `<li id>` disagree (v2.4.0 review:
720
+ * the fallback mark wrote the raw label while the local footer was
721
+ * encoded; `[^注]` / `[^a%b]` anchors broke).
722
+ */
723
+ declare function footnoteSafeId(identifier: string): string;
690
724
  /**
691
725
  * Extract the SOURCE identifier from a footnote `<li>` id, covering both id
692
726
  * shapes in the codebase — standalone (mdast-util-to-hast `fn-` +
@@ -1196,6 +1230,49 @@ interface PhantomLabels {
1196
1230
  * Labels are expected to already be normalized via normalizeId (uppercase).
1197
1231
  */
1198
1232
  declare function buildPhantomSuffix(phantoms: PhantomLabels): string;
1233
+ /**
1234
+ * The bytes to put BETWEEN a chunk's content and its phantom suffix so the
1235
+ * suffix's definition lines are parsed as definitions.
1236
+ *
1237
+ * `buildPhantomSuffix` is appended (never prepended: the incremental engine
1238
+ * treats the suffix as an always-tail input, and prepending would shift
1239
+ * every source position). But an append lands INSIDE whatever block the
1240
+ * content ends in — and a fenced code block or `$$` flow-math block that is
1241
+ * still open at the end of a streaming frame swallows everything up to EOF:
1242
+ * the sentinel lines render as code/math text and, since no definition was
1243
+ * registered, every cross-chunk reference in the chunk falls back to literal
1244
+ * `[text][label]` for the whole time the block streams (2026-08 project
1245
+ * review, core-render-01). Closing the block first is output-neutral: an
1246
+ * unclosed fence/math block already renders exactly the lines it has, so
1247
+ * `content + closer` yields the same code/math node value (positions
1248
+ * extend past `content.length`, which every consumer already tolerates for
1249
+ * the suffix's own nodes).
1250
+ *
1251
+ * A closer is emitted ONLY for a block whose opener line sits at column 0:
1252
+ * that is provably a top-level fence, which only a top-level closer (same
1253
+ * char, ≥ length, ≤3 spaces indent) can end — exactly what the line scanner
1254
+ * tracks. Openers with any indent are left alone: the line model cannot
1255
+ * see container boundaries, and an indented opener may live in a list item
1256
+ * that later de-indented content already ended (micromark closed the fence
1257
+ * with the item; an emitted closer would OPEN a new fence around the
1258
+ * sentinel lines — v2.4.0 review R1) or be closed by a ≥4-space closer the
1259
+ * `^ {0,3}` scan does not recognize. Those shapes keep the plain append
1260
+ * (swallowed at worst — the pre-2.4.0 behaviour), never a wrong closer.
1261
+ *
1262
+ * Only fences and flow math are closed. Raw-HTML constructs (`<!--`, `<?`,
1263
+ * `<![CDATA[`, `<!X`, `<script>`) that stay open to EOF also swallow the
1264
+ * suffix (invisibly — sanitize strips them), but their closers are not
1265
+ * position-neutral for the paragraph-inline forms and the shapes are rare
1266
+ * in LLM output; they keep the plain-append behaviour. When the scanner's
1267
+ * fence/math phase is untrusted (`phasePoisonedAt` — a suppressed open in a
1268
+ * container, see computeFreezeBoundary blocker 7) no closer is emitted
1269
+ * either: a wrong closer would OPEN a block around the suffix.
1270
+ *
1271
+ * Returns '' when nothing needs closing. Cost: one line scan of `content`
1272
+ * (regex per line; no reference tracking) — only paid by chunks that have a
1273
+ * non-empty phantom suffix.
1274
+ */
1275
+ declare function phantomSuffixCloser(content: string): string;
1199
1276
 
1200
1277
  /**
1201
1278
  * Custom mdast-util-to-hast handlers for cross-chunk label resolution.
@@ -1343,19 +1420,6 @@ declare const defaultEnginePlugins: readonly AIMarkdownEnginePlugin[];
1343
1420
  */
1344
1421
  declare function isFootnoteSection(node: Element): boolean;
1345
1422
 
1346
- /**
1347
- * CommonMark §4.7 label normalization. Used as the single canonical form
1348
- * for all label-keyed structures (registry maps, phantomFootnoteLabels Set,
1349
- * labelSet, etc.) and for handler comparisons against mdast-util-to-hast's
1350
- * internal `state.definitionById` / `state.footnoteById` keys.
1351
- *
1352
- * Direction is uppercase to align with mdast-util-to-hast internals
1353
- * (`String(identifier).toUpperCase()`). Direction is irrelevant once both
1354
- * sides agree; uppercase chosen to match the upstream library to minimize
1355
- * adapter calls.
1356
- *
1357
- * @module components/normalizeId
1358
- */
1359
1423
  declare function normalizeId(s: string): string;
1360
1424
  /**
1361
1425
  * Same as {@link normalizeId} plus resolution of backslash escapes.
@@ -1692,7 +1756,13 @@ interface SmoothStreamController {
1692
1756
  finish(): void;
1693
1757
  /** Jumps to `source` instantly, no animation, and clears any backlog. */
1694
1758
  snap(source: string): void;
1695
- /** Reveals everything pending right now (skip-animation affordance). */
1759
+ /**
1760
+ * Reveals everything pending right now (skip-animation affordance). Keeps
1761
+ * the grapheme discipline: while the stream is not finished, the trailing
1762
+ * grapheme of the source stays held back exactly as the animation would
1763
+ * hold it (a surrogate half or a growing ZWJ sequence must never reach
1764
+ * the parser); {@link finish} or the next {@link update} confirms it.
1765
+ */
1696
1766
  flush(): void;
1697
1767
  /** Subscribes to visible-prefix changes. Returns the unsubscribe. */
1698
1768
  subscribe(listener: () => void): () => void;
@@ -1899,4 +1969,4 @@ type RemendPreprocessorOptions = Omit<RemendOptions, 'katex' | 'inlineKatex'>;
1899
1969
  */
1900
1970
  declare function createRemendPreprocessor(options?: RemendPreprocessorOptions): AIMDContentPreprocessor;
1901
1971
 
1902
- export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, 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 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, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };
1972
+ export { type AIMDContentPreprocessor, type AIMarkdownEnginePlugin, type AIMarkdownEnginePluginName, type AdvanceOptions, type AdvanceResult, type AllowElement, type ChunkData, type Contribution, type CrossChunkHandlerOptions, DEFAULT_PAYLOAD, DEF_LINE_START_RE, type DefLabelScanner, type DefLabels, type Deprecation, 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 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, createFile, createIncrementalLatexPreprocessor, createProcessor, createRegistry, createRemendPreprocessor, createSmoothStreamController, defaultEnginePlugins, defaultUrlTransform, definitionList, extendSanitizeSchema, extractContributions, extractDefBodiesFromHast, footnoteSafeId, getEnginePluginInternals, hasLoneSurrogate, highlight, isFootnoteSection, lastRegionStart, measureStage, mergeClassNameAllowlist, normalizeForMatch, normalizeId, pangu, parseStage, phantomSuffixCloser, preprocessAIMDContent, preprocessLaTeX, rehypeFooterAdorn, rehypeRebaseHashLinks, removeComments, sanitizeCrossChunkUrl, sanitizeSchema, shortenDocumentId, smartypants, sourceIdFromFootnoteLiId, splitByProtectedRegions, subscribeStageTimings, transformStage, withDefs };