@ai-markdown/core 3.2.1 → 3.3.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
@@ -1,116 +1,108 @@
1
- import { Element, RootContent, Root as Root$1, Nodes } from 'hast';
2
- import { Root } from 'mdast';
3
- import { Registry, ChunkData, PipelineOptions, AdvanceOptions } from '@ai-markdown/engine';
4
- export { isFootnoteSection } from '@ai-markdown/engine';
5
-
6
- /** Framework-neutral block planning and dependency fingerprints.
7
- * Cache ownership and conversion to framework nodes belong to adapters.
8
- * See apps/docs/content/guides/architecture.md and packages/core/README.md for the contract.
9
- */
10
-
1
+ import { Element, Nodes, Root, RootContent } from "hast";
2
+ import { Root as Root$1 } from "mdast";
3
+ import { AdvanceOptions, ChunkData, PipelineOptions, Registry, isFootnoteSection } from "@ai-markdown/engine";
11
4
  /** Source-level identity of one renderable hast block. */
12
5
  interface BlockInfo {
13
- raw: string;
14
- startOffset: number;
15
- endOffset: number;
16
- /**
17
- * Line and column at the block's start. Tracked alongside `startOffset`
18
- * because two different source documents can produce the same byte offset
19
- * for the "same" content (`A\n\nB\n\nTarget` vs `ABCD\n\nTarget` — both
20
- * have `Target` starting at offset 6 but on different lines). Custom
21
- * components that read `node.position.start.line` would otherwise see
22
- * stale data from a false-positive cache hit.
23
- */
24
- startLine: number;
25
- startColumn: number;
26
- /** True if the block contains any TAINT_TYPES node — invalidate on globalCtx change. */
27
- hasReference: boolean;
28
- /**
29
- * Structural digest of the block's hast subtree, computed ONLY for blocks
30
- * that originate from raw HTML (mdast `html` nodes, or blocks resolved via
31
- * the range-containment fallback). Undefined for markdown-native blocks.
32
- *
33
- * Why it exists: rehype-raw applies the HTML parsing algorithm, so an
34
- * UNCLOSED container tag (`<details>` mid-stream, before its `</details>`
35
- * arrives) swallows every FOLLOWING top-level sibling into itself —
36
- * including the synthetic `<section data-footnotes>`. The block's mdast
37
- * identity (raw, position, ctx) is byte-identical across all those frames,
38
- * so without this digest the first swallowed snapshot would be a permanent
39
- * cache hit: content rendered inside the container freezes, and when the
40
- * close tag finally arrives the real nodes re-render at top level while the
41
- * stale copies stay trapped in the cached container (the "duplicate
42
- * footnote section inside <details>" bug).
43
- *
44
- * Digest composition — `maxEnd:descendantCount:hasFootnoteSection:swallowedHash`:
45
- * - max end offset over the subtree catches swallowed POSITIONED siblings
46
- * (their offsets lie beyond the mdast node's own range);
47
- * - descendant count catches position-less synthetic content moving in or
48
- * out (the footnote section's own element carries no position, and its
49
- * li contents point BACK at the definitions' offsets, which can be
50
- * smaller than the container's end — maxEnd alone misses that case);
51
- * - the explicit dataFootnotes bit closes the degenerate corner where a
52
- * swallowed section and later real children tie on both numbers;
53
- * - a hash of the swallowed extent's SOURCE (`[ownEnd, maxEnd)`) quickly
54
- * rejects most equal-length, equal-shape replacements. A matching hash
55
- * still requires an exact {@link BlockInfo.swallowedSource} comparison.
56
- *
57
- * Markdown-native blocks (paragraph/list/math/code/…) never receive
58
- * reparented content — every hast descendant derives from their own mdast
59
- * source range — so they skip the subtree walk entirely. This keeps the
60
- * per-frame cost away from huge deterministic subtrees like KaTeX output.
61
- */
62
- hastDigest?: string;
63
- /**
64
- * Exact source in the swallowed interval `[ownEnd, maxEnd)` of a raw-HTML
65
- * block. The FNV-1a 32-bit hash in `hastDigest` can collide for different
66
- * equal-length contents, so a cache hit must compare this string too.
67
- * Empty when the block swallowed no source; absent for markdown-native
68
- * blocks. Optional to keep existing public block-plan producers compatible.
69
- */
70
- swallowedSource?: string;
71
- /** A raw-HTML container that swallowed the synthesized
72
- * `<section data-footnotes>` (rehype-raw reparents the following siblings
73
- * into an unclosed `<details>` / `<div>` mid-stream). The footer is then
74
- * NOT a top-level plan item, so coordinated mode's "skip the local
75
- * footer, the aggregate renders it" rule never fired and the document
76
- * showed two `<section data-footnotes>` with colliding li ids (2026-08-19
77
- * review r2 P2-9). Coordinated renders strip it from this block's subtree;
78
- * standalone renders keep it exactly where the full parse puts it. */
79
- containsFootnoteSection?: boolean;
80
- /** TAINT-block 专属:按节点类型分桶的 label set。Normalized 形态(uppercase)。
81
- * Undefined when hasReference === false. */
82
- taintLabels?: {
83
- footnoteRefLabels: string[];
84
- linkRefLabels: string[];
85
- imageRefLabels: string[];
86
- footnoteDefLabels: string[];
87
- /** Chunk-local footnote state the engine baked into this block's
88
- * `footnote-sup` placeholders (`localOccurrence` / `localNumber` come
89
- * from a per-chunk counter over all PRECEDING refs, customMdastHandlers):
90
- * per ref label, refs-before-this-block count and first-appearance
91
- * rank. Same raw + same registry answers but a different prefix (an
92
- * in-place `[^x]`→`[^w]` edit upstream) must miss the cache, or the
93
- * stale occurrence resolves to a null global occurrence and the mark
94
- * vanishes (2026-08-19 review P2-4). Absent when the block has no refs. */
95
- footnoteRefLocalCtx?: string;
96
- /** Baked `localUrl` / `localTitle` of swallowed `cross-chunk-link` /
97
- * `-image` placeholders, as `[[label, url, title|null]…]` (JSON). The
98
- * render falls back to these when the registry has no canonical def,
99
- * where the fingerprint's `lr:`/`ir:` part is the constant `null` —
100
- * see scanSwallowedSubtree. Absent when nothing was swallowed. */
101
- swallowedDefCtx?: string;
102
- };
6
+ raw: string;
7
+ startOffset: number;
8
+ endOffset: number;
9
+ /**
10
+ * Line and column at the block's start. Tracked alongside `startOffset`
11
+ * because two different source documents can produce the same byte offset
12
+ * for the "same" content (`A\n\nB\n\nTarget` vs `ABCD\n\nTarget` — both
13
+ * have `Target` starting at offset 6 but on different lines). Custom
14
+ * components that read `node.position.start.line` would otherwise see
15
+ * stale data from a false-positive cache hit.
16
+ */
17
+ startLine: number;
18
+ startColumn: number;
19
+ /** True if the block contains any TAINT_TYPES node — invalidate on globalCtx change. */
20
+ hasReference: boolean;
21
+ /**
22
+ * Structural digest of the block's hast subtree, computed ONLY for blocks
23
+ * that originate from raw HTML (mdast `html` nodes, or blocks resolved via
24
+ * the range-containment fallback). Undefined for markdown-native blocks.
25
+ *
26
+ * Why it exists: rehype-raw applies the HTML parsing algorithm, so an
27
+ * UNCLOSED container tag (`<details>` mid-stream, before its `</details>`
28
+ * arrives) swallows every FOLLOWING top-level sibling into itself —
29
+ * including the synthetic `<section data-footnotes>`. The block's mdast
30
+ * identity (raw, position, ctx) is byte-identical across all those frames,
31
+ * so without this digest the first swallowed snapshot would be a permanent
32
+ * cache hit: content rendered inside the container freezes, and when the
33
+ * close tag finally arrives the real nodes re-render at top level while the
34
+ * stale copies stay trapped in the cached container (the "duplicate
35
+ * footnote section inside <details>" bug).
36
+ *
37
+ * Digest composition — `maxEnd:descendantCount:hasFootnoteSection:swallowedHash`:
38
+ * - max end offset over the subtree catches swallowed POSITIONED siblings
39
+ * (their offsets lie beyond the mdast node's own range);
40
+ * - descendant count catches position-less synthetic content moving in or
41
+ * out (the footnote section's own element carries no position, and its
42
+ * li contents point BACK at the definitions' offsets, which can be
43
+ * smaller than the container's end — maxEnd alone misses that case);
44
+ * - the explicit dataFootnotes bit closes the degenerate corner where a
45
+ * swallowed section and later real children tie on both numbers;
46
+ * - a hash of the swallowed extent's SOURCE (`[ownEnd, maxEnd)`) quickly
47
+ * rejects most equal-length, equal-shape replacements. A matching hash
48
+ * still requires an exact {@link BlockInfo.swallowedSource} comparison.
49
+ *
50
+ * Markdown-native blocks (paragraph/list/math/code/…) never receive
51
+ * reparented content — every hast descendant derives from their own mdast
52
+ * source range — so they skip the subtree walk entirely. This keeps the
53
+ * per-frame cost away from huge deterministic subtrees like KaTeX output.
54
+ */
55
+ hastDigest?: string;
56
+ /**
57
+ * Exact source in the swallowed interval `[ownEnd, maxEnd)` of a raw-HTML
58
+ * block. The FNV-1a 32-bit hash in `hastDigest` can collide for different
59
+ * equal-length contents, so a cache hit must compare this string too.
60
+ * Empty when the block swallowed no source; absent for markdown-native
61
+ * blocks. Optional to keep existing public block-plan producers compatible.
62
+ */
63
+ swallowedSource?: string;
64
+ /** A raw-HTML container that swallowed the synthesized
65
+ * `<section data-footnotes>` (rehype-raw reparents the following siblings
66
+ * into an unclosed `<details>` / `<div>` mid-stream). The footer is then
67
+ * NOT a top-level plan item, so coordinated mode's "skip the local
68
+ * footer, the aggregate renders it" rule never fired and the document
69
+ * showed two `<section data-footnotes>` with colliding li ids (2026-08-19
70
+ * review r2 P2-9). Coordinated renders strip it from this block's subtree;
71
+ * standalone renders keep it exactly where the full parse puts it. */
72
+ containsFootnoteSection?: boolean;
73
+ /** TAINT-block 专属:按节点类型分桶的 label set。Normalized 形态(uppercase)。
74
+ * Undefined when hasReference === false. */
75
+ taintLabels?: {
76
+ footnoteRefLabels: string[];
77
+ linkRefLabels: string[];
78
+ imageRefLabels: string[];
79
+ footnoteDefLabels: string[];
80
+ /** Chunk-local footnote state the engine baked into this block's
81
+ * `footnote-sup` placeholders (`localOccurrence` / `localNumber` come
82
+ * from a per-chunk counter over all PRECEDING refs, customMdastHandlers):
83
+ * per ref label, refs-before-this-block count and first-appearance
84
+ * rank. Same raw + same registry answers but a different prefix (an
85
+ * in-place `[^x]`→`[^w]` edit upstream) must miss the cache, or the
86
+ * stale occurrence resolves to a null global occurrence and the mark
87
+ * vanishes (2026-08-19 review P2-4). Absent when the block has no refs. */
88
+ footnoteRefLocalCtx?: string;
89
+ /** Baked `localUrl` / `localTitle` of swallowed `cross-chunk-link` /
90
+ * `-image` placeholders, as `[[label, url, title|null]…]` (JSON). The
91
+ * render falls back to these when the registry has no canonical def,
92
+ * where the fingerprint's `lr:`/`ir:` part is the constant `null` —
93
+ * see scanSwallowedSubtree. Absent when nothing was swallowed. */
94
+ swallowedDefCtx?: string;
95
+ };
103
96
  }
104
-
105
97
  /** Dev invariant: every block hast child must retain its mdast `position`. */
106
- declare function hasMdastSource(node: Element): boolean;
98
+ export declare function hasMdastSource(node: Element): boolean;
107
99
  /**
108
100
  * Compute {@link BlockInfo.hastDigest} for a raw-HTML block: iterative walk
109
101
  * over the hast subtree collecting (max end offset, descendant count,
110
102
  * contains-footnote-section). See the field's JSDoc for why each component
111
103
  * is load-bearing. Exported for tests.
112
104
  */
113
- declare function computeHtmlBlockDigest(el: Element, source?: string, ownEnd?: number): string;
105
+ export declare function computeHtmlBlockDigest(el: Element, source?: string, ownEnd?: number): string;
114
106
  /** {@link computeHtmlBlockDigest} plus the subtree's max end offset — the end
115
107
  * of the SWALLOWED extent, which callers need to ask what fell inside.
116
108
  *
@@ -124,9 +116,9 @@ declare function computeHtmlBlockDigest(el: Element, source?: string, ownEnd?: n
124
116
  * opening tag. The descendant walk covers it a second time. Verified in
125
117
  * 2026-08-20 A1 across 162 container/tail shapes: destroying either source
126
118
  * alone changes nothing, destroying both under-taints 130 of them. */
127
- declare function computeHtmlBlockDigestWithExtent(el: Element, source?: string, ownEnd?: number): {
128
- digest: string;
129
- maxEnd: number;
119
+ export declare function computeHtmlBlockDigestWithExtent(el: Element, source?: string, ownEnd?: number): {
120
+ digest: string;
121
+ maxEnd: number;
130
122
  };
131
123
  /**
132
124
  * One step of the document render plan, in source order.
@@ -145,45 +137,45 @@ declare function computeHtmlBlockDigestWithExtent(el: Element, source?: string,
145
137
  * in a single slot keyed by `globalCtx`.
146
138
  */
147
139
  type RenderItem = {
148
- kind: 'block';
149
- el: Element;
150
- key: string;
151
- info: BlockInfo;
140
+ kind: 'block';
141
+ el: Element;
142
+ key: string;
143
+ info: BlockInfo;
152
144
  } | {
153
- kind: 'inline';
154
- el: RootContent;
155
- key: string;
145
+ kind: 'inline';
146
+ el: RootContent;
147
+ key: string;
156
148
  } | {
157
- kind: 'synthetic';
158
- el: Element;
159
- key: string;
149
+ kind: 'synthetic';
150
+ el: Element;
151
+ key: string;
160
152
  };
161
153
  /** Extra render-time facts {@link buildBlocks} cannot read off the trees. */
162
154
  interface BuildBlocksOptions {
163
- /** Full document when planning only a tail. Its ordered reference and
164
- * definition context seeds numbering and invalidation for tail blocks. */
165
- contextMdast?: Root;
166
- /**
167
- * Labels this chunk phantom-injected because another chunk defines them
168
- * (already normalized). The coordinated handlers keep these out of
169
- * `state.footnoteOrder`, so the cache key's rank must keep them out too —
170
- * see the note in the rank pass. Empty / omitted in standalone mode, where
171
- * no phantom injection happened.
172
- */
173
- phantomFootnoteLabels?: ReadonlySet<string>;
155
+ /** Full document when planning only a tail. Its ordered reference and
156
+ * definition context seeds numbering and invalidation for tail blocks. */
157
+ contextMdast?: Root$1;
158
+ /**
159
+ * Labels this chunk phantom-injected because another chunk defines them
160
+ * (already normalized). The coordinated handlers keep these out of
161
+ * `state.footnoteOrder`, so the cache key's rank must keep them out too —
162
+ * see the note in the rank pass. Empty / omitted in standalone mode, where
163
+ * no phantom injection happened.
164
+ */
165
+ phantomFootnoteLabels?: ReadonlySet<string>;
174
166
  }
175
167
  /** Result of {@link buildBlocks}. */
176
168
  interface BuildBlocksResult {
177
- /** Render plan in document order — drives the adapter’s block cache. */
178
- plan: RenderItem[];
179
- /** JSON-stringified ordered list of TAINT-typed nodes in document order. */
180
- globalCtx: string;
181
- /** Flat list of `BlockInfo` for cacheable blocks, in document order. */
182
- blocks: BlockInfo[];
183
- /** 1:1 with `blocks` — the hast Element each BlockInfo refers to. */
184
- blockHasts: Element[];
185
- /** Synthesized footnote section, if present. */
186
- synthetic?: Element;
169
+ /** Render plan in document order — drives the adapter’s block cache. */
170
+ plan: RenderItem[];
171
+ /** JSON-stringified ordered list of TAINT-typed nodes in document order. */
172
+ globalCtx: string;
173
+ /** Flat list of `BlockInfo` for cacheable blocks, in document order. */
174
+ blocks: BlockInfo[];
175
+ /** 1:1 with `blocks` — the hast Element each BlockInfo refers to. */
176
+ blockHasts: Element[];
177
+ /** Synthesized footnote section, if present. */
178
+ synthetic?: Element;
187
179
  }
188
180
  /**
189
181
  * Build the document render plan + ctx digest from a parsed mdast and its
@@ -217,7 +209,7 @@ interface BuildBlocksResult {
217
209
  * numbering), then JSON-stringifies the collected tuples. That string is the
218
210
  * invalidation key for tainted blocks and the synthetic footnote section.
219
211
  */
220
- declare function buildBlocks(mdast: Root, hast: Root$1, source: string, options?: BuildBlocksOptions): BuildBlocksResult;
212
+ export declare function buildBlocks(mdast: Root$1, hast: Root, source: string, options?: BuildBlocksOptions): BuildBlocksResult;
221
213
  /**
222
214
  * Compute a per-block cache fingerprint from the registry slice this block
223
215
  * actually depends on (footnote/link/image refs and footnote defs). Two blocks
@@ -232,9 +224,8 @@ declare function buildBlocks(mdast: Root, hast: Root$1, source: string, options?
232
224
  * @param thisChunkSym- The Symbol of the chunk this rendering belongs to (for canonical-vs-this comparison).
233
225
  * @param clobberPrefix - The documentId-derived id prefix; included so href/id changes invalidate.
234
226
  */
235
- declare function computeBlockFingerprint(taintLabels: NonNullable<BlockInfo['taintLabels']>, registry: Registry, thisChunkSym: symbol, clobberPrefix: string): string;
236
-
237
- type BlockPlanner = (mdast: Root, hast: Root$1, source: string, options?: BuildBlocksOptions) => BuildBlocksResult;
227
+ export declare function computeBlockFingerprint(taintLabels: NonNullable<BlockInfo['taintLabels']>, registry: Registry, thisChunkSym: symbol, clobberPrefix: string): string;
228
+ type BlockPlanner = (mdast: Root$1, hast: Root, source: string, options?: BuildBlocksOptions) => BuildBlocksResult;
238
229
  /** Reuse the engine's retained prefix plans, including reference blocks.
239
230
  * Reference prefixes require full-document context when planning the tail.
240
231
  * Raw HTML and definition regions retain complete planning: their ownership
@@ -242,42 +233,40 @@ type BlockPlanner = (mdast: Root, hast: Root$1, source: string, options?: BuildB
242
233
  * guesses, establish which trees the incremental engine actually retained.
243
234
  * Top-level traversal remains O(blocks); reference context still visits the
244
235
  * full mdast when needed, while per-block HAST planning is tail-only. */
245
- declare function createBlockPlanner(): BlockPlanner;
246
-
236
+ export declare function createBlockPlanner(): BlockPlanner;
247
237
  /** Minimal write capability; registration and cleanup remain with the adapter. */
248
238
  interface ContributionRegistry {
249
- contributeChunkData(symbol: symbol, data: ChunkData): void;
239
+ contributeChunkData(symbol: symbol, data: ChunkData): void;
250
240
  }
251
241
  interface ContributionOptions {
252
- pipeline: {
253
- mdast: Root;
254
- hast: Root$1;
255
- };
256
- ownLabels: {
257
- footnoteLabels: Set<string>;
258
- linkLabels: Set<string>;
259
- };
260
- registry: ContributionRegistry | null;
261
- targetPhantoms: {
262
- missingFootnotes: Set<string>;
263
- missingLinks: Set<string>;
264
- };
265
- sym: symbol | null;
266
- clobberPrefix: string;
267
- /** Stable identity tuple of parse inputs. Source equality alone cannot
268
- * validate definition bodies after a plugin or schema change. */
269
- chain: readonly unknown[];
242
+ pipeline: {
243
+ mdast: Root$1;
244
+ hast: Root;
245
+ };
246
+ ownLabels: {
247
+ footnoteLabels: Set<string>;
248
+ linkLabels: Set<string>;
249
+ };
250
+ registry: ContributionRegistry | null;
251
+ targetPhantoms: {
252
+ missingFootnotes: Set<string>;
253
+ missingLinks: Set<string>;
254
+ };
255
+ sym: symbol | null;
256
+ clobberPrefix: string;
257
+ /** Stable identity tuple of parse inputs. Source equality alone cannot
258
+ * validate definition bodies after a plugin or schema change. */
259
+ chain: readonly unknown[];
270
260
  }
271
261
  interface ContributionSession {
272
- commit(options: ContributionOptions): void;
262
+ commit(options: ContributionOptions): void;
273
263
  }
274
264
  /** Create one contribution publisher per mounted chunk.
275
265
  * Call commit only in the host's commit/effect phase, after registration.
276
266
  * Constructing a session does not register or publish anything. A discarded
277
267
  * render must never call commit. The host owns registration and cleanup.
278
268
  */
279
- declare function createContributionSession(): ContributionSession;
280
-
269
+ export declare function createContributionSession(): ContributionSession;
281
270
  /**
282
271
  * Turn-taking coordinator for smooth-streaming chunks that share a
283
272
  * `documentId` under `<AIMarkdownDocuments>`.
@@ -307,42 +296,42 @@ declare function createContributionSession(): ContributionSession;
307
296
  * @module components/smoothStream/coordinator
308
297
  */
309
298
  interface SmoothCoordinator {
310
- /** Chunk mount-order chunkId list. **Read-only** — mutation corrupts the
311
- * release predicate for every successor. */
312
- readonly order: readonly string[];
313
- /** Chunk IDs that have reported done. Sticky: entries are only removed
314
- * when the chunk unregisters. **Read-only.** */
315
- readonly done: ReadonlySet<string>;
316
- /** Reveal-progress heartbeat, ms timestamps from the reporting chunk's
317
- * clock. Written WITHOUT a version bump (see module doc). */
318
- readonly lastProgressAt: ReadonlyMap<string, number>;
319
- /** Monotonic counter bumped by every gating-relevant mutation
320
- * (register / unregister / done transition). */
321
- readonly version: number;
322
- subscribe(cb: () => void): () => void;
323
- /** Allocate (or refcount-reuse, for Strict Mode remounts) this chunk's
324
- * slot at the current end of the mount order. */
325
- register(chunkId: string): void;
326
- /** Refcount decrement with microtask-deferred reclaim; the last release
327
- * removes the chunk from the order (waking successors) and, when the
328
- * coordinator empties, fires the container's `onEmpty`. */
329
- release(chunkId: string): void;
330
- /** Sticky, idempotent: the first call per chunk bumps the version; the
331
- * transition is never retracted while the chunk stays mounted (a
332
- * predecessor that un-finishes must not re-gate successors). */
333
- markDone(chunkId: string): void;
334
- /** Heartbeat stamp — no version bump, no fanout. */
335
- stampProgress(chunkId: string, at: number): void;
336
- /** True when every registered chunk BEFORE `chunkId` in the order has
337
- * reported done. Unregistered predecessors don't exist here, so they
338
- * never block. A `chunkId` that is not (yet) registered itself is
339
- * treated as sitting AFTER every registered chunk — a pre-registration
340
- * query must not release a chunk whose predecessors are still
341
- * revealing. */
342
- isReleased(chunkId: string): boolean;
343
- /** The earliest not-done registered chunk before `chunkId`, or null.
344
- * Read by the dev-only stuck-flag warning timer. */
345
- earliestBlockerOf(chunkId: string): string | null;
299
+ /** Chunk mount-order chunkId list. **Read-only** — mutation corrupts the
300
+ * release predicate for every successor. */
301
+ readonly order: readonly string[];
302
+ /** Chunk IDs that have reported done. Sticky: entries are only removed
303
+ * when the chunk unregisters. **Read-only.** */
304
+ readonly done: ReadonlySet<string>;
305
+ /** Reveal-progress heartbeat, ms timestamps from the reporting chunk's
306
+ * clock. Written WITHOUT a version bump (see module doc). */
307
+ readonly lastProgressAt: ReadonlyMap<string, number>;
308
+ /** Monotonic counter bumped by every gating-relevant mutation
309
+ * (register / unregister / done transition). */
310
+ readonly version: number;
311
+ subscribe(cb: () => void): () => void;
312
+ /** Allocate (or refcount-reuse, for Strict Mode remounts) this chunk's
313
+ * slot at the current end of the mount order. */
314
+ register(chunkId: string): void;
315
+ /** Refcount decrement with microtask-deferred reclaim; the last release
316
+ * removes the chunk from the order (waking successors) and, when the
317
+ * coordinator empties, fires the container's `onEmpty`. */
318
+ release(chunkId: string): void;
319
+ /** Sticky, idempotent: the first call per chunk bumps the version; the
320
+ * transition is never retracted while the chunk stays mounted (a
321
+ * predecessor that un-finishes must not re-gate successors). */
322
+ markDone(chunkId: string): void;
323
+ /** Heartbeat stamp — no version bump, no fanout. */
324
+ stampProgress(chunkId: string, at: number): void;
325
+ /** True when every registered chunk BEFORE `chunkId` in the order has
326
+ * reported done. Unregistered predecessors don't exist here, so they
327
+ * never block. A `chunkId` that is not (yet) registered itself is
328
+ * treated as sitting AFTER every registered chunk — a pre-registration
329
+ * query must not release a chunk whose predecessors are still
330
+ * revealing. */
331
+ isReleased(chunkId: string): boolean;
332
+ /** The earliest not-done registered chunk before `chunkId`, or null.
333
+ * Read by the dev-only stuck-flag warning timer. */
334
+ earliestBlockerOf(chunkId: string): string | null;
346
335
  }
347
336
  /** Outcome of one stuck-flag warning timer firing (dev-only diagnostics). */
348
337
  type GateWarnVerdict = 'warn' | 'rearm' | 'clear';
@@ -360,54 +349,30 @@ type GateWarnVerdict = 'warn' | 'rearm' | 'clear';
360
349
  * threshold while this chunk stayed gated — the predecessor's
361
350
  * `streaming` flag is likely stuck.
362
351
  */
363
- declare function evaluateGateWarn(coordinator: SmoothCoordinator, chunkId: string, nowMs: number, thresholdMs: number): GateWarnVerdict;
364
-
352
+ export declare function evaluateGateWarn(coordinator: SmoothCoordinator, chunkId: string, nowMs: number, thresholdMs: number): GateWarnVerdict;
365
353
  /** Create one turn-taking scope.
366
354
  * Why: sibling chunks coordinate reveal without a framework or DOM dependency.
367
355
  * Recommended pattern: register after mount, report sticky completion, release on unmount.
368
356
  * Footguns: registration order controls reveal; completion cannot be retracted.
369
357
  */
370
- declare function createSmoothCoordinator(onEmpty?: () => void): SmoothCoordinator;
371
-
372
- /**
373
- * Source-tail classification for the streaming cursor.
374
- *
375
- * The cursor's DOM walk cannot know whether the source tail is in the body
376
- * or inside a footnote definition — mdast-util-to-hast relocates every
377
- * definition into the synthetic end-of-document footer, decoupling the
378
- * footer's DOM position from source position. This module answers the
379
- * question at the mdast level, where micromark has already made every
380
- * grammar decision (lazy continuation lines, container nesting, blank-line
381
- * attachment, label escapes): the renderer derives a signal per frame from
382
- * the tree it is about to render and stamps it on a DOM marker the cursor
383
- * shell reads in the same commit.
384
- *
385
- * Deliberately NOT a text scan of the trailing lines: a hand-rolled
386
- * back-walk would be a third parallel grammar in the repo and provably
387
- * mis-classifies lazy continuations (`[^1]: line\nline2` keeps line2 in
388
- * the definition with zero indentation), resurfacing the exact bug this
389
- * exists to fix.
390
- *
391
- * @module components/streamingCursor/tailSignal
392
- */
393
-
394
- type TailSignal =
358
+ export declare function createSmoothCoordinator(onEmpty?: () => void): SmoothCoordinator;
359
+ type TailSignal =
395
360
  /** The source tail is inside a footnote definition — its text renders in
396
361
  * the footnote footer's `<li>` for `identifier` (mdast's case-folded
397
362
  * form), not at the body tail. */
398
363
  {
399
- kind: 'footnote-def';
400
- identifier: string;
401
- }
364
+ kind: 'footnote-def';
365
+ identifier: string;
366
+ } |
402
367
  /** The source tail is inside a link-reference definition — it renders
403
368
  * nothing at all; the cursor should hide rather than point at stale
404
369
  * body text. */
405
- | {
406
- kind: 'invisible-def';
407
- }
370
+ {
371
+ kind: 'invisible-def';
372
+ } |
408
373
  /** The source tail is ordinary body content — the DOM-tail heuristic is
409
374
  * correct as-is. */
410
- | null;
375
+ null;
411
376
  /**
412
377
  * Classify the source tail of `mdast`.
413
378
  *
@@ -422,114 +387,83 @@ type TailSignal =
422
387
  * several phantoms is a normal shape) and classifies the first real
423
388
  * node.
424
389
  */
425
- declare function deriveTailSignal(mdast: Root, contentLength: number): TailSignal;
426
-
427
- /** Assemble document footnotes as HAST without framework nodes or DOM access. */
428
-
429
- declare function buildAggregateTree(registry: Registry, clobberPrefix: string, preserveOrphanReferences?: boolean): Element | null;
430
-
431
- /**
432
- * Structural clone of a hast subtree for render-time use.
433
- *
434
- * Copies exactly what the render-time mutators touch — every node object,
435
- * its `children` array (the allow/disallow filters splice it), and its
436
- * `properties` object (`urlTransform` overwrites `href`/`src`) — while
437
- * SHARING `position`, arbitrary nested plugin data, string values, and individual property values
438
- * by reference. This is both cheaper than a JSON round-trip (no string
439
- * intermediate; `position` objects, typically over half of a serialized
440
- * hast, are never materialized) and exact for any tree the pipeline
441
- * produces (no lossy JSON semantics to reason about).
442
- *
443
- * @module components/cloneHastForRender
444
- */
445
-
390
+ export declare function deriveTailSignal(mdast: Root$1, contentLength: number): TailSignal;
391
+ export declare function buildAggregateTree(registry: Registry, clobberPrefix: string, preserveOrphanReferences?: boolean): Element | null;
446
392
  /** See module docs. Safe for any hast node union member. */
447
- declare function cloneHastForRender<T extends Nodes>(node: T): T;
448
-
449
- /** Per-consumer parse state and full/incremental fallback policy.
450
- * The host decides whether a next frame is expected (false for SSR), owns
451
- * this session, and resets it when render policy invalidates retained trees.
452
- * Parsing does not register chunks or publish document contributions.
453
- * Fallback order: incremental → full pipeline → plain-text frame, the last
454
- * step ONLY for the engine's raw-depth signal (see plainTextTrees); any
455
- * other full-pipeline throw, a consumer plugin's included, escapes `parse`.
456
- */
457
-
393
+ export declare function cloneHastForRender<T extends Nodes>(node: T): T;
458
394
  interface PipelineTrees {
459
- mdast: Root;
460
- hast: Root$1;
395
+ mdast: Root$1;
396
+ hast: Root;
461
397
  }
462
398
  interface PipelineSession {
463
- /** Drop retained incremental state. Does not dispose host subscriptions. */
464
- reset(): void;
465
- /** Read-only borrowed trees: clone before destructive rendering. */
466
- parse(options: PipelineFrameOptions): PipelineTrees;
399
+ /** Drop retained incremental state. Does not dispose host subscriptions. */
400
+ reset(): void;
401
+ /** Read-only borrowed trees: clone before destructive rendering. */
402
+ parse(options: PipelineFrameOptions): PipelineTrees;
467
403
  }
468
404
  type RemarkRehypeOptions = NonNullable<PipelineOptions['remarkRehypeOptions']>;
469
405
  interface PipelineFrameOptions {
470
- content: string;
471
- targetPhantoms: {
472
- missingFootnotes: Set<string>;
473
- missingLinks: Set<string>;
474
- };
475
- remarkPlugins: PipelineOptions['remarkPlugins'];
476
- rehypePlugins: PipelineOptions['rehypePlugins'];
477
- remarkRehypeOptions: PipelineOptions['remarkRehypeOptions'];
478
- handlers?: RemarkRehypeOptions['handlers'];
479
- preserveForBodyHarvest: boolean;
480
- documentId: string;
481
- provenance: string;
482
- /** False for a one-shot server render; no browser-global probe is needed. */
483
- incrementalParse: boolean;
484
- defListEnabled: boolean;
485
- /** Whether `remarkPlugins` parses GFM task-list items. Default `false`
486
- * (the boundary scanner keeps every `[x]` as reference taint). The
487
- * adapters pass `true` because they build the chain with
488
- * `buildCoreRemarkPlugins`, which always includes remark-gfm. A box
489
- * whose paragraph a `$$` opener closed is released only when
490
- * `mathFlow` is `true` as well. */
491
- gfmTaskListItems?: boolean;
492
- /** Whether `remarkPlugins` parses `$$` flow math (remark-math). Passed
493
- * through to the engine in all three states: `true` declares it (a
494
- * `$$` region is verbatim math), `false` declares its absence (`$$`
495
- * lines are paragraph text), and omitted leaves it unknown — the
496
- * scanner then takes the union of both grammars, which is correct for
497
- * either chain and freezes less around `$$`. The adapters pass `true`
498
- * because `buildCoreRemarkPlugins` always includes remark-math; a
499
- * caller with its own chain should declare the real value and may
500
- * leave it out when unsure. */
501
- mathFlow?: boolean;
502
- measure?: AdvanceOptions['measure'];
406
+ content: string;
407
+ targetPhantoms: {
408
+ missingFootnotes: Set<string>;
409
+ missingLinks: Set<string>;
410
+ };
411
+ remarkPlugins: PipelineOptions['remarkPlugins'];
412
+ rehypePlugins: PipelineOptions['rehypePlugins'];
413
+ remarkRehypeOptions: PipelineOptions['remarkRehypeOptions'];
414
+ handlers?: RemarkRehypeOptions['handlers'];
415
+ preserveForBodyHarvest: boolean;
416
+ documentId: string;
417
+ provenance: string;
418
+ /** False for a one-shot server render; no browser-global probe is needed. */
419
+ incrementalParse: boolean;
420
+ defListEnabled: boolean;
421
+ /** Whether `remarkPlugins` parses GFM task-list items. Default `false`
422
+ * (the boundary scanner keeps every `[x]` as reference taint). The
423
+ * adapters pass `true` because they build the chain with
424
+ * `buildCoreRemarkPlugins`, which always includes remark-gfm. A box
425
+ * whose paragraph a `$$` opener closed is released only when
426
+ * `mathFlow` is `true` as well. */
427
+ gfmTaskListItems?: boolean;
428
+ /** Whether `remarkPlugins` parses `$$` flow math (remark-math). Passed
429
+ * through to the engine in all three states: `true` declares it (a
430
+ * `$$` region is verbatim math), `false` declares its absence (`$$`
431
+ * lines are paragraph text), and omitted leaves it unknown — the
432
+ * scanner then takes the union of both grammars, which is correct for
433
+ * either chain and freezes less around `$$`. The adapters pass `true`
434
+ * because `buildCoreRemarkPlugins` always includes remark-math; a
435
+ * caller with its own chain should declare the real value and may
436
+ * leave it out when unsure. */
437
+ mathFlow?: boolean;
438
+ measure?: AdvanceOptions['measure'];
503
439
  }
504
- declare function createPipelineSession(): PipelineSession;
505
-
440
+ export declare function createPipelineSession(): PipelineSession;
506
441
  interface DefinitionLabels {
507
- footnoteLabels: ReadonlySet<string>;
508
- linkLabels: ReadonlySet<string>;
442
+ footnoteLabels: ReadonlySet<string>;
443
+ linkLabels: ReadonlySet<string>;
509
444
  }
510
445
  type PhantomTargets = PipelineFrameOptions['targetPhantoms'];
511
- declare function derivePhantomTargets({ content, ownLabels, labels }: {
512
- content: string;
513
- ownLabels: DefinitionLabels;
514
- labels: DefinitionLabels | null;
446
+ export declare function derivePhantomTargets({ content, ownLabels, labels }: {
447
+ content: string;
448
+ ownLabels: DefinitionLabels;
449
+ labels: DefinitionLabels | null;
515
450
  }, previous?: PhantomTargets): PhantomTargets;
516
451
  interface CoordinationPolicy {
517
- mode: 'coordinated' | 'orphan' | 'standalone';
518
- handlers: PipelineFrameOptions['handlers'];
519
- preserveForBodyHarvest: boolean;
452
+ mode: 'coordinated' | 'orphan' | 'standalone';
453
+ handlers: PipelineFrameOptions['handlers'];
454
+ preserveForBodyHarvest: boolean;
520
455
  }
521
- declare function deriveCoordinationPolicy({ coordinated, registered, preserveOrphanReferences, }: {
522
- coordinated: boolean;
523
- registered: boolean;
524
- /** The host has already resolved any wrapper-level override. */
525
- preserveOrphanReferences: boolean;
456
+ export declare function deriveCoordinationPolicy({ coordinated, registered, preserveOrphanReferences }: {
457
+ coordinated: boolean;
458
+ registered: boolean;
459
+ /** The host has already resolved any wrapper-level override. */
460
+ preserveOrphanReferences: boolean;
526
461
  }, previous?: CoordinationPolicy): CoordinationPolicy;
527
462
  type ContributionPolicyInputs = Pick<PipelineFrameOptions, 'remarkPlugins' | 'rehypePlugins' | 'remarkRehypeOptions' | 'handlers' | 'preserveForBodyHarvest' | 'documentId' | 'provenance'> & {
528
- clobberPrefix: string;
463
+ clobberPrefix: string;
529
464
  };
530
465
  /** Source/phantom/registry/symbol changes are compared by the publisher itself.
531
466
  * This tuple covers the remaining inputs that can alter harvested bodies.
532
467
  */
533
- declare function buildContributionChain(inputs: ContributionPolicyInputs): readonly unknown[];
534
-
535
- export { type BlockInfo, type BlockPlanner, type BuildBlocksOptions, type BuildBlocksResult, type ContributionOptions, type ContributionPolicyInputs, type ContributionRegistry, type ContributionSession, type CoordinationPolicy, type DefinitionLabels, type GateWarnVerdict, type PhantomTargets, type PipelineFrameOptions, type PipelineSession, type PipelineTrees, type RenderItem, type SmoothCoordinator, type TailSignal, buildAggregateTree, buildBlocks, buildContributionChain, cloneHastForRender, computeBlockFingerprint, computeHtmlBlockDigest, computeHtmlBlockDigestWithExtent, createBlockPlanner, createContributionSession, createPipelineSession, createSmoothCoordinator, deriveCoordinationPolicy, derivePhantomTargets, deriveTailSignal, evaluateGateWarn, hasMdastSource };
468
+ export declare function buildContributionChain(inputs: ContributionPolicyInputs): readonly unknown[];
469
+ export { type BlockInfo, type BlockPlanner, type BuildBlocksOptions, type BuildBlocksResult, type ContributionOptions, type ContributionPolicyInputs, type ContributionRegistry, type ContributionSession, type CoordinationPolicy, type DefinitionLabels, type GateWarnVerdict, type PhantomTargets, type PipelineFrameOptions, type PipelineSession, type PipelineTrees, type RenderItem, type SmoothCoordinator, type TailSignal, isFootnoteSection };