@ai-markdown/core 3.2.2 → 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.dev.js CHANGED
@@ -1,990 +1,953 @@
1
- // src/blockPlan.ts
2
1
  import { SKIP, visit } from "unist-util-visit";
3
- import { isFootnoteSection, normalizeId } from "@ai-markdown/engine";
4
- var TAINT_TYPES = /* @__PURE__ */ new Set([
5
- "footnoteReference",
6
- "footnoteDefinition",
7
- "linkReference",
8
- "imageReference",
9
- "definition"
2
+ import { EngineRawHtmlDepthError, advanceIncrementalParse, buildCrossChunkHandlers, buildPhantomSuffix, extractContributions, extractDefBodiesFromHast, footnoteSafeId, isFootnoteSection, lastMeaningfulIdx, normalizeForMatch, normalizeId, parseStage, phantomSuffixCloser, transformStage } from "@ai-markdown/engine";
3
+ const TAINT_TYPES = /* @__PURE__ */ new Set([
4
+ "footnoteReference",
5
+ "footnoteDefinition",
6
+ "linkReference",
7
+ "imageReference",
8
+ "definition"
10
9
  ]);
11
- var CTX_TYPES = TAINT_TYPES;
10
+ const CTX_TYPES = TAINT_TYPES;
12
11
  function extractRaw(node, source) {
13
- if (!node.position) return "";
14
- return source.slice(node.position.start.offset, node.position.end.offset);
12
+ if (!node.position) return "";
13
+ return source.slice(node.position.start.offset, node.position.end.offset);
15
14
  }
16
15
  function hasMdastSource(node) {
17
- return node.position !== void 0;
16
+ return node.position !== void 0;
18
17
  }
19
18
  function computeHtmlBlockDigest(el, source, ownEnd) {
20
- return computeHtmlBlockDigestWithExtent(el, source, ownEnd).digest;
19
+ return computeHtmlBlockDigestWithExtent(el, source, ownEnd).digest;
21
20
  }
22
21
  function computeHtmlBlockDigestWithExtent(el, source, ownEnd) {
23
- let maxEnd = el.position?.end?.offset ?? 0;
24
- let count = 0;
25
- let hasFootnoteSection = false;
26
- const stack = [...el.children];
27
- while (stack.length > 0) {
28
- const node = stack.pop();
29
- count++;
30
- const end = node.position?.end?.offset;
31
- if (end !== void 0 && end > maxEnd) {
32
- maxEnd = end;
33
- }
34
- if (node.type === "element") {
35
- if (isFootnoteSection(node)) {
36
- hasFootnoteSection = true;
37
- }
38
- for (const child of node.children) {
39
- stack.push(child);
40
- }
41
- }
42
- }
43
- const swallowed = source !== void 0 && ownEnd !== void 0 && maxEnd > ownEnd ? source.slice(ownEnd, maxEnd) : "";
44
- return { digest: `${maxEnd}:${count}:${hasFootnoteSection ? 1 : 0}:${fnv1a(swallowed)}`, maxEnd };
22
+ let maxEnd = el.position?.end?.offset ?? 0;
23
+ let count = 0;
24
+ let hasFootnoteSection = false;
25
+ const stack = [...el.children];
26
+ while (stack.length > 0) {
27
+ const node = stack.pop();
28
+ count++;
29
+ const end = node.position?.end?.offset;
30
+ if (end !== void 0 && end > maxEnd) maxEnd = end;
31
+ if (node.type === "element") {
32
+ if (isFootnoteSection(node)) hasFootnoteSection = true;
33
+ for (const child of node.children) stack.push(child);
34
+ }
35
+ }
36
+ const swallowed = source !== void 0 && ownEnd !== void 0 && maxEnd > ownEnd ? source.slice(ownEnd, maxEnd) : "";
37
+ return {
38
+ digest: `${maxEnd}:${count}:${hasFootnoteSection ? 1 : 0}:${fnv1a(swallowed)}`,
39
+ maxEnd
40
+ };
45
41
  }
46
42
  function fnv1a(text) {
47
- let h = 2166136261;
48
- for (let i = 0; i < text.length; i++) {
49
- h ^= text.charCodeAt(i);
50
- h = Math.imul(h, 16777619) >>> 0;
51
- }
52
- return h.toString(36);
43
+ let h = 2166136261;
44
+ for (let i = 0; i < text.length; i++) {
45
+ h ^= text.charCodeAt(i);
46
+ h = Math.imul(h, 16777619) >>> 0;
47
+ }
48
+ return h.toString(36);
53
49
  }
54
- var EMPTY_LABELS = /* @__PURE__ */ new Set();
55
- var FOOTNOTE_SECTION_KEY = "__footnote_section__";
50
+ const EMPTY_LABELS = /* @__PURE__ */ new Set();
51
+ const FOOTNOTE_SECTION_KEY = "__footnote_section__";
56
52
  function buildBlocks(mdast, hast, source, options = {}) {
57
- const phantomFootnotes = options.phantomFootnoteLabels ?? EMPTY_LABELS;
58
- const mdastByOffset = /* @__PURE__ */ new Map();
59
- const mdastRanges = [];
60
- for (const child of mdast.children) {
61
- const off = child.position?.start.offset;
62
- const endOff = child.position?.end.offset;
63
- if (off !== void 0) {
64
- mdastByOffset.set(off, child);
65
- }
66
- if (off !== void 0 && endOff !== void 0) {
67
- mdastRanges.push({ start: off, end: endOff, node: child });
68
- }
69
- }
70
- if (true) {
71
- for (let i = 1; i < mdastRanges.length; i++) {
72
- if (mdastRanges[i].start < mdastRanges[i - 1].start) {
73
- throw new Error(
74
- "block-memo: mdast.children not sorted by source offset \u2014 a remark plugin is reordering top-level children."
75
- );
76
- }
77
- }
78
- }
79
- function findContainingMdast(offset) {
80
- let lo = 0;
81
- let hi = mdastRanges.length;
82
- while (lo < hi) {
83
- const mid = lo + hi >>> 1;
84
- if (mdastRanges[mid].start <= offset) lo = mid + 1;
85
- else hi = mid;
86
- }
87
- const idx = lo - 1;
88
- if (idx < 0) return void 0;
89
- const r = mdastRanges[idx];
90
- return offset < r.end ? r.node : void 0;
91
- }
92
- const ctxParts = [];
93
- const taintOffsets = [];
94
- const firstTaintAtOrAfter = (offset) => {
95
- let lo = 0;
96
- let hi = taintOffsets.length;
97
- while (lo < hi) {
98
- const mid = lo + hi >>> 1;
99
- if (taintOffsets[mid] < offset) lo = mid + 1;
100
- else hi = mid;
101
- }
102
- return lo;
103
- };
104
- visit(options.contextMdast ?? mdast, (n) => {
105
- if (!CTX_TYPES.has(n.type)) return;
106
- const at = n.position?.start?.offset;
107
- if (at !== void 0) taintOffsets.push(at);
108
- if (n.type === "footnoteReference") ctxParts.push(["fr", n.identifier]);
109
- else if (n.type === "footnoteDefinition") ctxParts.push(["fd", n.identifier, extractRaw(n, source)]);
110
- else if (n.type === "linkReference") ctxParts.push(["lr", n.identifier]);
111
- else if (n.type === "imageReference") ctxParts.push(["ir", n.identifier]);
112
- else if (n.type === "definition") ctxParts.push(["d", n.identifier, n.url, n.title ?? null]);
113
- });
114
- if (true) {
115
- for (let i = 1; i < taintOffsets.length; i++) {
116
- if (taintOffsets[i] < taintOffsets[i - 1]) {
117
- throw new Error(
118
- "block-memo: taint node start offsets are not source-ordered \u2014 a remark plugin is reordering nodes or emitting bad positions."
119
- );
120
- }
121
- }
122
- }
123
- const globalCtx = JSON.stringify(ctxParts);
124
- const plan = [];
125
- const blocks = [];
126
- const blockHasts = [];
127
- let synthetic;
128
- const refLocalCtx = /* @__PURE__ */ new Map();
129
- {
130
- const counts = /* @__PURE__ */ new Map();
131
- const ranks = /* @__PURE__ */ new Map();
132
- const rankOf = (id) => {
133
- if (phantomFootnotes.has(id)) return -1;
134
- let rank = ranks.get(id);
135
- if (rank === void 0) {
136
- rank = ranks.size;
137
- ranks.set(id, rank);
138
- }
139
- return rank;
140
- };
141
- visit(options.contextMdast ?? mdast, (n) => {
142
- if (n.type === "footnoteDefinition") {
143
- rankOf(normalizeId(String(n.identifier)));
144
- return SKIP;
145
- }
146
- if (n.type !== "footnoteReference") return;
147
- const id = normalizeId(String(n.identifier));
148
- const prior = counts.get(id) ?? 0;
149
- refLocalCtx.set(n, [prior, rankOf(id)]);
150
- counts.set(id, prior + 1);
151
- });
152
- }
153
- for (let i = 0; i < hast.children.length; i++) {
154
- const hastChild = hast.children[i];
155
- if (hastChild.type !== "element") {
156
- const off = hastChild.position?.start.offset;
157
- const key = off !== void 0 ? `inline-${off}` : `inline-i${i}`;
158
- plan.push({ kind: "inline", el: hastChild, key });
159
- continue;
160
- }
161
- const el = hastChild;
162
- if (isFootnoteSection(el)) {
163
- synthetic = el;
164
- plan.push({ kind: "synthetic", el, key: FOOTNOTE_SECTION_KEY });
165
- continue;
166
- }
167
- const hastOffset = el.position?.start.offset;
168
- if (hastOffset === void 0) {
169
- plan.push({ kind: "inline", el, key: `inline-i${i}` });
170
- continue;
171
- }
172
- let mdastNode = mdastByOffset.get(hastOffset);
173
- let viaRangeFallback = false;
174
- if (!mdastNode) {
175
- mdastNode = findContainingMdast(hastOffset);
176
- viaRangeFallback = mdastNode !== void 0;
177
- }
178
- if (!mdastNode) {
179
- if (true) {
180
- throw new Error(
181
- `block-memo: hast block at offset ${hastOffset} has no mdast counterpart. A rehype plugin may have synthesized positions outside source. tagName=${el.tagName}`
182
- );
183
- }
184
- plan.push({ kind: "inline", el, key: `inline-${hastOffset}` });
185
- continue;
186
- }
187
- if (mdastNode.type === "footnoteDefinition") {
188
- plan.push({ kind: "inline", el, key: `inline-${hastOffset}` });
189
- continue;
190
- }
191
- const isRawHtmlBlock = mdastNode.type === "html" || viaRangeFallback;
192
- let hasReference = false;
193
- const swallowed = isRawHtmlBlock ? scanSwallowedSubtree(el) : null;
194
- const footnoteRefLabels = [];
195
- const linkRefLabels = [];
196
- const imageRefLabels = [];
197
- const footnoteDefLabels = [];
198
- const local = [];
199
- visit(mdastNode, (n) => {
200
- if (!TAINT_TYPES.has(n.type)) return;
201
- hasReference = true;
202
- const id = "identifier" in n ? normalizeId(String(n.identifier)) : null;
203
- if (id === null) return;
204
- if (n.type === "footnoteReference") {
205
- footnoteRefLabels.push(id);
206
- const ctx = refLocalCtx.get(n);
207
- if (ctx) local.push([id, ctx[0], ctx[1]]);
208
- } else if (n.type === "linkReference") linkRefLabels.push(id);
209
- else if (n.type === "imageReference") imageRefLabels.push(id);
210
- else if (n.type === "footnoteDefinition") footnoteDefLabels.push(id);
211
- });
212
- if (swallowed !== null && swallowed.hasReference) {
213
- hasReference = true;
214
- footnoteRefLabels.push(...swallowed.refLabels);
215
- linkRefLabels.push(...swallowed.linkLabels);
216
- imageRefLabels.push(...swallowed.imageLabels);
217
- local.push(...swallowed.local);
218
- }
219
- const footnoteRefLocalCtx = local.length > 0 ? JSON.stringify(local) : void 0;
220
- const swallowedDefCtx = swallowed !== null && swallowed.bakedDefs.length > 0 ? JSON.stringify(swallowed.bakedDefs) : void 0;
221
- const mdastPos = mdastNode.position;
222
- if (!mdastPos || mdastPos.start?.offset === void 0 || mdastPos.end?.offset === void 0) {
223
- plan.push({ kind: "inline", el, key: `inline-${hastOffset}` });
224
- continue;
225
- }
226
- let hastDigest;
227
- let swallowedSource;
228
- let swallowedTaint = false;
229
- if (isRawHtmlBlock) {
230
- const d = computeHtmlBlockDigestWithExtent(el, source, mdastPos.end.offset);
231
- hastDigest = d.digest;
232
- swallowedSource = source.slice(mdastPos.end.offset, d.maxEnd);
233
- const first = firstTaintAtOrAfter(mdastPos.end.offset);
234
- swallowedTaint = first < taintOffsets.length && taintOffsets[first] < d.maxEnd;
235
- }
236
- if (swallowedTaint) hasReference = true;
237
- const info = {
238
- raw: extractRaw(mdastNode, source),
239
- startOffset: mdastPos.start.offset,
240
- endOffset: mdastPos.end.offset,
241
- startLine: mdastPos.start.line,
242
- startColumn: mdastPos.start.column,
243
- hasReference,
244
- ...hastDigest !== void 0 ? { hastDigest } : {},
245
- ...swallowedSource !== void 0 ? { swallowedSource } : {},
246
- ...swallowed?.hasFootnoteSection ? { containsFootnoteSection: true } : {},
247
- ...hasReference ? {
248
- taintLabels: {
249
- footnoteRefLabels,
250
- linkRefLabels,
251
- imageRefLabels,
252
- footnoteDefLabels,
253
- ...footnoteRefLocalCtx !== void 0 ? { footnoteRefLocalCtx } : {},
254
- ...swallowedDefCtx !== void 0 ? { swallowedDefCtx } : {}
255
- }
256
- } : {}
257
- };
258
- blocks.push(info);
259
- blockHasts.push(el);
260
- plan.push({ kind: "block", el, info, key: `block-${hastOffset}` });
261
- }
262
- return { plan, globalCtx, blocks, blockHasts, synthetic };
53
+ const phantomFootnotes = options.phantomFootnoteLabels ?? EMPTY_LABELS;
54
+ const mdastByOffset = /* @__PURE__ */ new Map();
55
+ const mdastRanges = [];
56
+ for (const child of mdast.children) {
57
+ const off = child.position?.start.offset;
58
+ const endOff = child.position?.end.offset;
59
+ if (off !== void 0) mdastByOffset.set(off, child);
60
+ if (off !== void 0 && endOff !== void 0) mdastRanges.push({
61
+ start: off,
62
+ end: endOff,
63
+ node: child
64
+ });
65
+ }
66
+ for (let i = 1; i < mdastRanges.length; i++) if (mdastRanges[i].start < mdastRanges[i - 1].start) throw new Error("block-memo: mdast.children not sorted by source offset — a remark plugin is reordering top-level children.");
67
+ function findContainingMdast(offset) {
68
+ let lo = 0;
69
+ let hi = mdastRanges.length;
70
+ while (lo < hi) {
71
+ const mid = lo + hi >>> 1;
72
+ if (mdastRanges[mid].start <= offset) lo = mid + 1;
73
+ else hi = mid;
74
+ }
75
+ const idx = lo - 1;
76
+ if (idx < 0) return void 0;
77
+ const r = mdastRanges[idx];
78
+ return offset < r.end ? r.node : void 0;
79
+ }
80
+ const ctxParts = [];
81
+ const taintOffsets = [];
82
+ const firstTaintAtOrAfter = (offset) => {
83
+ let lo = 0;
84
+ let hi = taintOffsets.length;
85
+ while (lo < hi) {
86
+ const mid = lo + hi >>> 1;
87
+ if (taintOffsets[mid] < offset) lo = mid + 1;
88
+ else hi = mid;
89
+ }
90
+ return lo;
91
+ };
92
+ visit(options.contextMdast ?? mdast, (n) => {
93
+ if (!CTX_TYPES.has(n.type)) return;
94
+ const at = n.position?.start?.offset;
95
+ if (at !== void 0) taintOffsets.push(at);
96
+ if (n.type === "footnoteReference") ctxParts.push(["fr", n.identifier]);
97
+ else if (n.type === "footnoteDefinition") ctxParts.push([
98
+ "fd",
99
+ n.identifier,
100
+ extractRaw(n, source)
101
+ ]);
102
+ else if (n.type === "linkReference") ctxParts.push(["lr", n.identifier]);
103
+ else if (n.type === "imageReference") ctxParts.push(["ir", n.identifier]);
104
+ else if (n.type === "definition") ctxParts.push([
105
+ "d",
106
+ n.identifier,
107
+ n.url,
108
+ n.title ?? null
109
+ ]);
110
+ });
111
+ for (let i = 1; i < taintOffsets.length; i++) if (taintOffsets[i] < taintOffsets[i - 1]) throw new Error("block-memo: taint node start offsets are not source-ordered — a remark plugin is reordering nodes or emitting bad positions.");
112
+ const globalCtx = JSON.stringify(ctxParts);
113
+ const plan = [];
114
+ const blocks = [];
115
+ const blockHasts = [];
116
+ let synthetic;
117
+ const refLocalCtx = /* @__PURE__ */ new Map();
118
+ {
119
+ const counts = /* @__PURE__ */ new Map();
120
+ const ranks = /* @__PURE__ */ new Map();
121
+ const rankOf = (id) => {
122
+ if (phantomFootnotes.has(id)) return -1;
123
+ let rank = ranks.get(id);
124
+ if (rank === void 0) {
125
+ rank = ranks.size;
126
+ ranks.set(id, rank);
127
+ }
128
+ return rank;
129
+ };
130
+ visit(options.contextMdast ?? mdast, (n) => {
131
+ if (n.type === "footnoteDefinition") {
132
+ rankOf(normalizeId(String(n.identifier)));
133
+ return SKIP;
134
+ }
135
+ if (n.type !== "footnoteReference") return;
136
+ const id = normalizeId(String(n.identifier));
137
+ const prior = counts.get(id) ?? 0;
138
+ refLocalCtx.set(n, [prior, rankOf(id)]);
139
+ counts.set(id, prior + 1);
140
+ });
141
+ }
142
+ for (let i = 0; i < hast.children.length; i++) {
143
+ const hastChild = hast.children[i];
144
+ if (hastChild.type !== "element") {
145
+ const off = hastChild.position?.start.offset;
146
+ const key = off !== void 0 ? `inline-${off}` : `inline-i${i}`;
147
+ plan.push({
148
+ kind: "inline",
149
+ el: hastChild,
150
+ key
151
+ });
152
+ continue;
153
+ }
154
+ const el = hastChild;
155
+ if (isFootnoteSection(el)) {
156
+ synthetic = el;
157
+ plan.push({
158
+ kind: "synthetic",
159
+ el,
160
+ key: FOOTNOTE_SECTION_KEY
161
+ });
162
+ continue;
163
+ }
164
+ const hastOffset = el.position?.start.offset;
165
+ if (hastOffset === void 0) {
166
+ plan.push({
167
+ kind: "inline",
168
+ el,
169
+ key: `inline-i${i}`
170
+ });
171
+ continue;
172
+ }
173
+ let mdastNode = mdastByOffset.get(hastOffset);
174
+ let viaRangeFallback = false;
175
+ if (!mdastNode) {
176
+ mdastNode = findContainingMdast(hastOffset);
177
+ viaRangeFallback = mdastNode !== void 0;
178
+ }
179
+ if (!mdastNode) throw new Error(`block-memo: hast block at offset ${hastOffset} has no mdast counterpart. A rehype plugin may have synthesized positions outside source. tagName=${el.tagName}`);
180
+ if (mdastNode.type === "footnoteDefinition") {
181
+ plan.push({
182
+ kind: "inline",
183
+ el,
184
+ key: `inline-${hastOffset}`
185
+ });
186
+ continue;
187
+ }
188
+ const isRawHtmlBlock = mdastNode.type === "html" || viaRangeFallback;
189
+ let hasReference = false;
190
+ const swallowed = isRawHtmlBlock ? scanSwallowedSubtree(el) : null;
191
+ const footnoteRefLabels = [];
192
+ const linkRefLabels = [];
193
+ const imageRefLabels = [];
194
+ const footnoteDefLabels = [];
195
+ const local = [];
196
+ visit(mdastNode, (n) => {
197
+ if (!TAINT_TYPES.has(n.type)) return;
198
+ hasReference = true;
199
+ const id = "identifier" in n ? normalizeId(String(n.identifier)) : null;
200
+ if (id === null) return;
201
+ if (n.type === "footnoteReference") {
202
+ footnoteRefLabels.push(id);
203
+ const ctx = refLocalCtx.get(n);
204
+ if (ctx) local.push([
205
+ id,
206
+ ctx[0],
207
+ ctx[1]
208
+ ]);
209
+ } else if (n.type === "linkReference") linkRefLabels.push(id);
210
+ else if (n.type === "imageReference") imageRefLabels.push(id);
211
+ else if (n.type === "footnoteDefinition") footnoteDefLabels.push(id);
212
+ });
213
+ if (swallowed !== null && swallowed.hasReference) {
214
+ hasReference = true;
215
+ footnoteRefLabels.push(...swallowed.refLabels);
216
+ linkRefLabels.push(...swallowed.linkLabels);
217
+ imageRefLabels.push(...swallowed.imageLabels);
218
+ local.push(...swallowed.local);
219
+ }
220
+ const footnoteRefLocalCtx = local.length > 0 ? JSON.stringify(local) : void 0;
221
+ const swallowedDefCtx = swallowed !== null && swallowed.bakedDefs.length > 0 ? JSON.stringify(swallowed.bakedDefs) : void 0;
222
+ const mdastPos = mdastNode.position;
223
+ if (!mdastPos || mdastPos.start?.offset === void 0 || mdastPos.end?.offset === void 0) {
224
+ plan.push({
225
+ kind: "inline",
226
+ el,
227
+ key: `inline-${hastOffset}`
228
+ });
229
+ continue;
230
+ }
231
+ let hastDigest;
232
+ let swallowedSource;
233
+ let swallowedTaint = false;
234
+ if (isRawHtmlBlock) {
235
+ const d = computeHtmlBlockDigestWithExtent(el, source, mdastPos.end.offset);
236
+ hastDigest = d.digest;
237
+ swallowedSource = source.slice(mdastPos.end.offset, d.maxEnd);
238
+ const first = firstTaintAtOrAfter(mdastPos.end.offset);
239
+ swallowedTaint = first < taintOffsets.length && taintOffsets[first] < d.maxEnd;
240
+ }
241
+ if (swallowedTaint) hasReference = true;
242
+ const info = {
243
+ raw: extractRaw(mdastNode, source),
244
+ startOffset: mdastPos.start.offset,
245
+ endOffset: mdastPos.end.offset,
246
+ startLine: mdastPos.start.line,
247
+ startColumn: mdastPos.start.column,
248
+ hasReference,
249
+ ...hastDigest !== void 0 ? { hastDigest } : {},
250
+ ...swallowedSource !== void 0 ? { swallowedSource } : {},
251
+ ...swallowed?.hasFootnoteSection ? { containsFootnoteSection: true } : {},
252
+ ...hasReference ? { taintLabels: {
253
+ footnoteRefLabels,
254
+ linkRefLabels,
255
+ imageRefLabels,
256
+ footnoteDefLabels,
257
+ ...footnoteRefLocalCtx !== void 0 ? { footnoteRefLocalCtx } : {},
258
+ ...swallowedDefCtx !== void 0 ? { swallowedDefCtx } : {}
259
+ } } : {}
260
+ };
261
+ blocks.push(info);
262
+ blockHasts.push(el);
263
+ plan.push({
264
+ kind: "block",
265
+ el,
266
+ info,
267
+ key: `block-${hastOffset}`
268
+ });
269
+ }
270
+ return {
271
+ plan,
272
+ globalCtx,
273
+ blocks,
274
+ blockHasts,
275
+ synthetic
276
+ };
263
277
  }
264
278
  function computeBlockFingerprint(taintLabels, registry, thisChunkSym, clobberPrefix) {
265
- const parts = [clobberPrefix];
266
- for (const label of taintLabels.footnoteRefLabels) {
267
- parts.push(`fn:${label}=${registry.globalNumber(label) ?? "null"}`);
268
- }
269
- if (taintLabels.footnoteRefLocalCtx !== void 0) parts.push(`fl:${taintLabels.footnoteRefLocalCtx}`);
270
- if (taintLabels.swallowedDefCtx !== void 0) parts.push(`sd:${taintLabels.swallowedDefCtx}`);
271
- for (const label of taintLabels.linkRefLabels) {
272
- const def = registry.resolveLinkDef(label);
273
- parts.push(`lr:${label}=${def ? JSON.stringify([def.url, def.title ?? null]) : "null"}`);
274
- }
275
- for (const label of taintLabels.imageRefLabels) {
276
- const def = registry.resolveLinkDef(label);
277
- parts.push(`ir:${label}=${def ? JSON.stringify([def.url, def.title ?? null]) : "null"}`);
278
- }
279
- for (const label of taintLabels.footnoteDefLabels) {
280
- const isCanonical = registry.canonicalFootnoteFor(label) === thisChunkSym ? 1 : 0;
281
- parts.push(`fd:${label}=${isCanonical}/${registry.getRefsForLabel(label)}`);
282
- }
283
- return parts.join("|");
279
+ const parts = [clobberPrefix];
280
+ for (const label of taintLabels.footnoteRefLabels) parts.push(`fn:${label}=${registry.globalNumber(label) ?? "null"}`);
281
+ if (taintLabels.footnoteRefLocalCtx !== void 0) parts.push(`fl:${taintLabels.footnoteRefLocalCtx}`);
282
+ if (taintLabels.swallowedDefCtx !== void 0) parts.push(`sd:${taintLabels.swallowedDefCtx}`);
283
+ for (const label of taintLabels.linkRefLabels) {
284
+ const def = registry.resolveLinkDef(label);
285
+ parts.push(`lr:${label}=${def ? JSON.stringify([def.url, def.title ?? null]) : "null"}`);
286
+ }
287
+ for (const label of taintLabels.imageRefLabels) {
288
+ const def = registry.resolveLinkDef(label);
289
+ parts.push(`ir:${label}=${def ? JSON.stringify([def.url, def.title ?? null]) : "null"}`);
290
+ }
291
+ for (const label of taintLabels.footnoteDefLabels) {
292
+ const isCanonical = registry.canonicalFootnoteFor(label) === thisChunkSym ? 1 : 0;
293
+ parts.push(`fd:${label}=${isCanonical}/${registry.getRefsForLabel(label)}`);
294
+ }
295
+ return parts.join("|");
284
296
  }
285
- var PLACEHOLDER_TAGS = /* @__PURE__ */ new Set(["footnote-sup", "cross-chunk-link", "cross-chunk-image"]);
297
+ const PLACEHOLDER_TAGS = /* @__PURE__ */ new Set([
298
+ "footnote-sup",
299
+ "cross-chunk-link",
300
+ "cross-chunk-image"
301
+ ]);
286
302
  function scanSwallowedSubtree(el) {
287
- const refLabels = [];
288
- const linkLabels = [];
289
- const imageLabels = [];
290
- const local = [];
291
- const bakedDefs = [];
292
- let hasReference = false;
293
- let hasFootnoteSection = false;
294
- visit(el, "element", (node) => {
295
- if (isFootnoteSection(node)) {
296
- hasFootnoteSection = true;
297
- hasReference = true;
298
- return;
299
- }
300
- const props = node.properties ?? {};
301
- if (PLACEHOLDER_TAGS.has(node.tagName)) {
302
- hasReference = true;
303
- const identifier = props.identifier ?? props.label;
304
- const label = identifier === void 0 ? null : normalizeId(String(identifier));
305
- if (label === null) return;
306
- if (node.tagName === "footnote-sup") {
307
- refLabels.push(label);
308
- const occ = Number(props.localOccurrence);
309
- const num = Number(props.localNumber);
310
- local.push([label, Number.isFinite(occ) ? occ : -1, Number.isFinite(num) ? num : -1]);
311
- return;
312
- }
313
- if (node.tagName === "cross-chunk-link") linkLabels.push(label);
314
- else imageLabels.push(label);
315
- if (typeof props.localUrl === "string") {
316
- bakedDefs.push([label, props.localUrl, typeof props.localTitle === "string" ? props.localTitle : null]);
317
- }
318
- return;
319
- }
320
- if (node.tagName === "a" && props.dataFootnoteRef !== void 0) hasReference = true;
321
- });
322
- return { hasReference, refLabels, linkLabels, imageLabels, local, bakedDefs, hasFootnoteSection };
303
+ const refLabels = [];
304
+ const linkLabels = [];
305
+ const imageLabels = [];
306
+ const local = [];
307
+ const bakedDefs = [];
308
+ let hasReference = false;
309
+ let hasFootnoteSection = false;
310
+ visit(el, "element", (node) => {
311
+ if (isFootnoteSection(node)) {
312
+ hasFootnoteSection = true;
313
+ hasReference = true;
314
+ return;
315
+ }
316
+ const props = node.properties ?? {};
317
+ if (PLACEHOLDER_TAGS.has(node.tagName)) {
318
+ hasReference = true;
319
+ const identifier = props.identifier ?? props.label;
320
+ const label = identifier === void 0 ? null : normalizeId(String(identifier));
321
+ if (label === null) return;
322
+ if (node.tagName === "footnote-sup") {
323
+ refLabels.push(label);
324
+ const occ = Number(props.localOccurrence);
325
+ const num = Number(props.localNumber);
326
+ local.push([
327
+ label,
328
+ Number.isFinite(occ) ? occ : -1,
329
+ Number.isFinite(num) ? num : -1
330
+ ]);
331
+ return;
332
+ }
333
+ if (node.tagName === "cross-chunk-link") linkLabels.push(label);
334
+ else imageLabels.push(label);
335
+ if (typeof props.localUrl === "string") bakedDefs.push([
336
+ label,
337
+ props.localUrl,
338
+ typeof props.localTitle === "string" ? props.localTitle : null
339
+ ]);
340
+ return;
341
+ }
342
+ if (node.tagName === "a" && props.dataFootnoteRef !== void 0) hasReference = true;
343
+ });
344
+ return {
345
+ hasReference,
346
+ refLabels,
347
+ linkLabels,
348
+ imageLabels,
349
+ local,
350
+ bakedDefs,
351
+ hasFootnoteSection
352
+ };
323
353
  }
324
-
325
- // src/blockPlanner.ts
326
- import { visit as visit2 } from "unist-util-visit";
327
- var UNPAIRED_TYPES = /* @__PURE__ */ new Set(["html", "definition", "footnoteDefinition"]);
354
+ const UNPAIRED_TYPES = /* @__PURE__ */ new Set([
355
+ "html",
356
+ "definition",
357
+ "footnoteDefinition"
358
+ ]);
328
359
  function createBlockPlanner() {
329
- let previous;
330
- const eligible = /* @__PURE__ */ new WeakMap();
331
- const isEligible = (node) => {
332
- let result = eligible.get(node);
333
- if (result === void 0) {
334
- result = true;
335
- visit2(node, (child) => {
336
- if (UNPAIRED_TYPES.has(child.type)) result = false;
337
- });
338
- eligible.set(node, result);
339
- }
340
- return result;
341
- };
342
- return (mdast, hast, source, options = {}) => {
343
- let m = 0;
344
- let h = 0;
345
- let reusedContext = false;
346
- if (previous && source.startsWith(previous.source) && options.phantomFootnoteLabels === previous.phantoms) {
347
- const prevHast = previous.hast.children;
348
- const prevPlan = previous.result.plan;
349
- const isRetainedGap = (i) => {
350
- const gap = hast.children[i];
351
- return gap === prevHast[i] && gap.type === "text" && !/\S/.test(gap.value);
352
- };
353
- while (m < mdast.children.length) {
354
- const node = mdast.children[m];
355
- const el = hast.children[h];
356
- const item = prevPlan[h];
357
- if (node !== previous.mdast.children[m] || el !== prevHast[h] || !el || el.type !== "element" || !item || item.kind !== "block" || item.info.startOffset !== node.position?.start.offset || !isEligible(node))
358
- break;
359
- const startOffset = item.info.startOffset;
360
- let next = h + 1;
361
- let complete = true;
362
- for (; ; ) {
363
- let probe = next;
364
- while (probe < hast.children.length && isRetainedGap(probe)) probe++;
365
- const sibling = prevPlan[probe];
366
- if (!sibling || sibling.kind !== "block" || sibling.info.startOffset !== startOffset) break;
367
- if (hast.children[probe] !== prevHast[probe]) {
368
- complete = false;
369
- break;
370
- }
371
- next = probe + 1;
372
- }
373
- if (!complete) break;
374
- reusedContext ||= item.info.hasReference;
375
- m++;
376
- h = next;
377
- while (h < hast.children.length && isRetainedGap(h)) h++;
378
- }
379
- if (m > 0) {
380
- const prefixEnd = mdast.children[m - 1].position?.end.offset ?? 0;
381
- for (let i = h; i < hast.children.length; i++) {
382
- const child = hast.children[i];
383
- const at = child.type === "element" ? child.position?.start.offset : void 0;
384
- if (at !== void 0 && at < prefixEnd) {
385
- m = 0;
386
- h = 0;
387
- reusedContext = false;
388
- break;
389
- }
390
- }
391
- }
392
- }
393
- let result;
394
- if (m > 0 && previous) {
395
- const tail = buildBlocks(
396
- { ...mdast, children: mdast.children.slice(m) },
397
- { ...hast, children: hast.children.slice(h) },
398
- source,
399
- reusedContext ? { ...options, contextMdast: options.contextMdast ?? mdast } : options
400
- );
401
- const prefix = previous.result.plan.slice(0, h);
402
- const prefixBlocks = prefix.filter(
403
- (item) => item.kind === "block"
404
- );
405
- result = {
406
- ...tail,
407
- plan: [
408
- ...prefix,
409
- ...tail.plan.map(
410
- (item) => /^inline-i\d+$/.test(item.key) ? { ...item, key: `inline-i${Number(item.key.slice(8)) + h}` } : item
411
- )
412
- ],
413
- blocks: [...prefixBlocks.map((item) => item.info), ...tail.blocks],
414
- blockHasts: [...prefixBlocks.map((item) => item.el), ...tail.blockHasts]
415
- };
416
- } else result = buildBlocks(mdast, hast, source, options);
417
- previous = { mdast, hast, source, result, phantoms: options.phantomFootnoteLabels };
418
- return result;
419
- };
360
+ let previous;
361
+ const eligible = /* @__PURE__ */ new WeakMap();
362
+ const isEligible = (node) => {
363
+ let result = eligible.get(node);
364
+ if (result === void 0) {
365
+ result = true;
366
+ visit(node, (child) => {
367
+ if (UNPAIRED_TYPES.has(child.type)) result = false;
368
+ });
369
+ eligible.set(node, result);
370
+ }
371
+ return result;
372
+ };
373
+ return (mdast, hast, source, options = {}) => {
374
+ let m = 0;
375
+ let h = 0;
376
+ let reusedContext = false;
377
+ if (previous && source.startsWith(previous.source) && options.phantomFootnoteLabels === previous.phantoms) {
378
+ const prevHast = previous.hast.children;
379
+ const prevPlan = previous.result.plan;
380
+ const isRetainedGap = (i) => {
381
+ const gap = hast.children[i];
382
+ return gap === prevHast[i] && gap.type === "text" && !/\S/.test(gap.value);
383
+ };
384
+ while (m < mdast.children.length) {
385
+ const node = mdast.children[m];
386
+ const el = hast.children[h];
387
+ const item = prevPlan[h];
388
+ if (node !== previous.mdast.children[m] || el !== prevHast[h] || !el || el.type !== "element" || !item || item.kind !== "block" || item.info.startOffset !== node.position?.start.offset || !isEligible(node)) break;
389
+ const startOffset = item.info.startOffset;
390
+ let next = h + 1;
391
+ let complete = true;
392
+ for (;;) {
393
+ let probe = next;
394
+ while (probe < hast.children.length && isRetainedGap(probe)) probe++;
395
+ const sibling = prevPlan[probe];
396
+ if (!sibling || sibling.kind !== "block" || sibling.info.startOffset !== startOffset) break;
397
+ if (hast.children[probe] !== prevHast[probe]) {
398
+ complete = false;
399
+ break;
400
+ }
401
+ next = probe + 1;
402
+ }
403
+ if (!complete) break;
404
+ reusedContext ||= item.info.hasReference;
405
+ m++;
406
+ h = next;
407
+ while (h < hast.children.length && isRetainedGap(h)) h++;
408
+ }
409
+ if (m > 0) {
410
+ const prefixEnd = mdast.children[m - 1].position?.end.offset ?? 0;
411
+ for (let i = h; i < hast.children.length; i++) {
412
+ const child = hast.children[i];
413
+ const at = child.type === "element" ? child.position?.start.offset : void 0;
414
+ if (at !== void 0 && at < prefixEnd) {
415
+ m = 0;
416
+ h = 0;
417
+ reusedContext = false;
418
+ break;
419
+ }
420
+ }
421
+ }
422
+ }
423
+ let result;
424
+ if (m > 0 && previous) {
425
+ const tail = buildBlocks({
426
+ ...mdast,
427
+ children: mdast.children.slice(m)
428
+ }, {
429
+ ...hast,
430
+ children: hast.children.slice(h)
431
+ }, source, reusedContext ? {
432
+ ...options,
433
+ contextMdast: options.contextMdast ?? mdast
434
+ } : options);
435
+ const prefix = previous.result.plan.slice(0, h);
436
+ const prefixBlocks = prefix.filter((item) => item.kind === "block");
437
+ result = {
438
+ ...tail,
439
+ plan: [...prefix, ...tail.plan.map((item) => /^inline-i\d+$/.test(item.key) ? {
440
+ ...item,
441
+ key: `inline-i${Number(item.key.slice(8)) + h}`
442
+ } : item)],
443
+ blocks: [...prefixBlocks.map((item) => item.info), ...tail.blocks],
444
+ blockHasts: [...prefixBlocks.map((item) => item.el), ...tail.blockHasts]
445
+ };
446
+ } else result = buildBlocks(mdast, hast, source, options);
447
+ previous = {
448
+ mdast,
449
+ hast,
450
+ source,
451
+ result,
452
+ phantoms: options.phantomFootnoteLabels
453
+ };
454
+ return result;
455
+ };
420
456
  }
421
-
422
- // src/contribution.ts
423
- import { extractContributions, extractDefBodiesFromHast, footnoteSafeId } from "@ai-markdown/engine";
424
457
  function createContributionSession() {
425
- let lastContribution = null;
426
- return {
427
- commit({ pipeline, ownLabels, registry, targetPhantoms, sym, clobberPrefix, chain }) {
428
- if (!registry || !sym) return;
429
- const refs = [];
430
- const defMeta = /* @__PURE__ */ new Map();
431
- const linkDefs = /* @__PURE__ */ new Map();
432
- for (const node of extractContributions(pipeline.mdast, {
433
- phantomFootnoteLabels: targetPhantoms.missingFootnotes
434
- })) {
435
- if (node.kind === "ref") {
436
- refs.push({
437
- label: node.label,
438
- kind: node.refKind,
439
- referenceType: node.referenceType,
440
- ...node.nestedIn !== void 0 ? { nestedIn: node.nestedIn } : {}
441
- });
442
- } else if (node.kind === "fnDef") {
443
- defMeta.set(node.label, {
444
- identifier: node.label,
445
- sourceIdentifier: node.sourceIdentifier,
446
- contentSource: node.content
447
- });
448
- } else if (node.kind === "linkDef") {
449
- linkDefs.set(node.label, { identifier: node.label, url: node.url, title: node.title });
450
- }
451
- }
452
- const fp = JSON.stringify({
453
- r: refs,
454
- d: Array.from(defMeta.entries()).map(([k, v]) => [k, v.sourceIdentifier, v.contentSource]),
455
- l: Array.from(linkDefs.entries()).map(([k, v]) => [k, v.url, v.title ?? ""]),
456
- ofn: Array.from(ownLabels.footnoteLabels).sort(),
457
- ol: Array.from(ownLabels.linkLabels).sort(),
458
- // Include targetPhantoms in the fingerprint: a phantom→resolved
459
- // transition (another chunk publishes a def for a label this chunk
460
- // references inside one of its OWN def bodies) changes the rendered
461
- // hast — the `<cross-chunk-link>` / `<cross-chunk-image>` placeholder
462
- // disappears and a real `<a>` / `<img>` takes its place — without
463
- // touching this chunk's refs / defMeta / linkDefs / ownLabels. Without
464
- // including the phantom snapshot in the fingerprint, the fp check
465
- // would short-circuit and the registry would keep stale bodyHast
466
- // forever, leaving the aggregate footer rendering the placeholder
467
- // long after the label was resolved.
468
- tpfn: Array.from(targetPhantoms.missingFootnotes).sort(),
469
- tpl: Array.from(targetPhantoms.missingLinks).sort()
470
- });
471
- const last = lastContribution;
472
- if (last?.registry === registry && last.symbol === sym && last.fp === fp && last.chain.length === chain.length && last.chain.every((dep, i) => dep === chain[i])) {
473
- return;
474
- }
475
- const bodiesBySafeId = extractDefBodiesFromHast(pipeline.hast, clobberPrefix);
476
- const defs = /* @__PURE__ */ new Map();
477
- for (const [label, meta] of defMeta) {
478
- defs.set(label, {
479
- identifier: meta.identifier,
480
- sourceIdentifier: meta.sourceIdentifier,
481
- contentSource: meta.contentSource,
482
- bodyHast: bodiesBySafeId.get(footnoteSafeId(meta.sourceIdentifier)) ?? []
483
- });
484
- }
485
- lastContribution = { registry, symbol: sym, fp, chain };
486
- registry.contributeChunkData(sym, {
487
- refs,
488
- defs,
489
- linkDefs,
490
- ownFootnoteLabels: ownLabels.footnoteLabels,
491
- ownLinkLabels: ownLabels.linkLabels
492
- });
493
- }
494
- };
458
+ let lastContribution = null;
459
+ return { commit({ pipeline, ownLabels, registry, targetPhantoms, sym, clobberPrefix, chain }) {
460
+ if (!registry || !sym) return;
461
+ const refs = [];
462
+ const defMeta = /* @__PURE__ */ new Map();
463
+ const linkDefs = /* @__PURE__ */ new Map();
464
+ for (const node of extractContributions(pipeline.mdast, { phantomFootnoteLabels: targetPhantoms.missingFootnotes })) if (node.kind === "ref") refs.push({
465
+ label: node.label,
466
+ kind: node.refKind,
467
+ referenceType: node.referenceType,
468
+ ...node.nestedIn !== void 0 ? { nestedIn: node.nestedIn } : {}
469
+ });
470
+ else if (node.kind === "fnDef") defMeta.set(node.label, {
471
+ identifier: node.label,
472
+ sourceIdentifier: node.sourceIdentifier,
473
+ contentSource: node.content
474
+ });
475
+ else if (node.kind === "linkDef") linkDefs.set(node.label, {
476
+ identifier: node.label,
477
+ url: node.url,
478
+ title: node.title
479
+ });
480
+ const fp = JSON.stringify({
481
+ r: refs,
482
+ d: Array.from(defMeta.entries()).map(([k, v]) => [
483
+ k,
484
+ v.sourceIdentifier,
485
+ v.contentSource
486
+ ]),
487
+ l: Array.from(linkDefs.entries()).map(([k, v]) => [
488
+ k,
489
+ v.url,
490
+ v.title ?? ""
491
+ ]),
492
+ ofn: Array.from(ownLabels.footnoteLabels).sort(),
493
+ ol: Array.from(ownLabels.linkLabels).sort(),
494
+ tpfn: Array.from(targetPhantoms.missingFootnotes).sort(),
495
+ tpl: Array.from(targetPhantoms.missingLinks).sort()
496
+ });
497
+ const last = lastContribution;
498
+ if (last?.registry === registry && last.symbol === sym && last.fp === fp && last.chain.length === chain.length && last.chain.every((dep, i) => dep === chain[i])) return;
499
+ const bodiesBySafeId = extractDefBodiesFromHast(pipeline.hast, clobberPrefix);
500
+ const defs = /* @__PURE__ */ new Map();
501
+ for (const [label, meta] of defMeta) defs.set(label, {
502
+ identifier: meta.identifier,
503
+ sourceIdentifier: meta.sourceIdentifier,
504
+ contentSource: meta.contentSource,
505
+ bodyHast: bodiesBySafeId.get(footnoteSafeId(meta.sourceIdentifier)) ?? []
506
+ });
507
+ lastContribution = {
508
+ registry,
509
+ symbol: sym,
510
+ fp,
511
+ chain
512
+ };
513
+ registry.contributeChunkData(sym, {
514
+ refs,
515
+ defs,
516
+ linkDefs,
517
+ ownFootnoteLabels: ownLabels.footnoteLabels,
518
+ ownLinkLabels: ownLabels.linkLabels
519
+ });
520
+ } };
495
521
  }
496
-
497
- // src/smoothCoordinator.ts
498
- function createSmoothCoordinator(onEmpty) {
499
- const order = [];
500
- const done = /* @__PURE__ */ new Set();
501
- const lastProgressAt = /* @__PURE__ */ new Map();
502
- const coord = {
503
- order,
504
- done,
505
- lastProgressAt,
506
- version: 0,
507
- _refcounts: /* @__PURE__ */ new Map(),
508
- _subscribers: /* @__PURE__ */ new Set(),
509
- _notifyScheduled: false,
510
- register(chunkId) {
511
- const count = this._refcounts.get(chunkId) ?? 0;
512
- this._refcounts.set(chunkId, count + 1);
513
- if (count === 0 && !order.includes(chunkId)) {
514
- order.push(chunkId);
515
- this._notify();
516
- }
517
- },
518
- release(chunkId) {
519
- const count = this._refcounts.get(chunkId);
520
- if (count === void 0) return;
521
- if (count > 1) {
522
- this._refcounts.set(chunkId, count - 1);
523
- return;
524
- }
525
- this._refcounts.set(chunkId, 0);
526
- queueMicrotask(() => {
527
- if (this._refcounts.get(chunkId) !== 0) return;
528
- this._refcounts.delete(chunkId);
529
- const idx = order.indexOf(chunkId);
530
- if (idx !== -1) order.splice(idx, 1);
531
- done.delete(chunkId);
532
- lastProgressAt.delete(chunkId);
533
- this._notify();
534
- if (order.length === 0 && onEmpty) onEmpty();
535
- });
536
- },
537
- markDone(chunkId) {
538
- if (done.has(chunkId)) return;
539
- done.add(chunkId);
540
- this._notify();
541
- },
542
- stampProgress(chunkId, at) {
543
- lastProgressAt.set(chunkId, at);
544
- },
545
- isReleased(chunkId) {
546
- for (const id of order) {
547
- if (id === chunkId) return true;
548
- if (!done.has(id)) return false;
549
- }
550
- return true;
551
- },
552
- earliestBlockerOf(chunkId) {
553
- for (const id of order) {
554
- if (id === chunkId) return null;
555
- if (!done.has(id)) return id;
556
- }
557
- return null;
558
- },
559
- subscribe(cb) {
560
- this._subscribers.add(cb);
561
- return () => {
562
- this._subscribers.delete(cb);
563
- };
564
- },
565
- _notify() {
566
- this.version++;
567
- if (this._notifyScheduled) return;
568
- this._notifyScheduled = true;
569
- queueMicrotask(() => {
570
- this._notifyScheduled = false;
571
- for (const cb of [...this._subscribers]) cb();
572
- });
573
- }
574
- };
575
- return coord;
522
+ function createSmoothCoordinator$1(onEmpty) {
523
+ const order = [];
524
+ const done = /* @__PURE__ */ new Set();
525
+ const lastProgressAt = /* @__PURE__ */ new Map();
526
+ return {
527
+ order,
528
+ done,
529
+ lastProgressAt,
530
+ version: 0,
531
+ _refcounts: /* @__PURE__ */ new Map(),
532
+ _subscribers: /* @__PURE__ */ new Set(),
533
+ _notifyScheduled: false,
534
+ register(chunkId) {
535
+ const count = this._refcounts.get(chunkId) ?? 0;
536
+ this._refcounts.set(chunkId, count + 1);
537
+ if (count === 0 && !order.includes(chunkId)) {
538
+ order.push(chunkId);
539
+ this._notify();
540
+ }
541
+ },
542
+ release(chunkId) {
543
+ const count = this._refcounts.get(chunkId);
544
+ if (count === void 0) return;
545
+ if (count > 1) {
546
+ this._refcounts.set(chunkId, count - 1);
547
+ return;
548
+ }
549
+ this._refcounts.set(chunkId, 0);
550
+ queueMicrotask(() => {
551
+ if (this._refcounts.get(chunkId) !== 0) return;
552
+ this._refcounts.delete(chunkId);
553
+ const idx = order.indexOf(chunkId);
554
+ if (idx !== -1) order.splice(idx, 1);
555
+ done.delete(chunkId);
556
+ lastProgressAt.delete(chunkId);
557
+ this._notify();
558
+ if (order.length === 0 && onEmpty) onEmpty();
559
+ });
560
+ },
561
+ markDone(chunkId) {
562
+ if (done.has(chunkId)) return;
563
+ done.add(chunkId);
564
+ this._notify();
565
+ },
566
+ stampProgress(chunkId, at) {
567
+ lastProgressAt.set(chunkId, at);
568
+ },
569
+ isReleased(chunkId) {
570
+ for (const id of order) {
571
+ if (id === chunkId) return true;
572
+ if (!done.has(id)) return false;
573
+ }
574
+ return true;
575
+ },
576
+ earliestBlockerOf(chunkId) {
577
+ for (const id of order) {
578
+ if (id === chunkId) return null;
579
+ if (!done.has(id)) return id;
580
+ }
581
+ return null;
582
+ },
583
+ subscribe(cb) {
584
+ this._subscribers.add(cb);
585
+ return () => {
586
+ this._subscribers.delete(cb);
587
+ };
588
+ },
589
+ _notify() {
590
+ this.version++;
591
+ if (this._notifyScheduled) return;
592
+ this._notifyScheduled = true;
593
+ queueMicrotask(() => {
594
+ this._notifyScheduled = false;
595
+ for (const cb of [...this._subscribers]) cb();
596
+ });
597
+ }
598
+ };
576
599
  }
577
600
  function evaluateGateWarn(coordinator, chunkId, nowMs, thresholdMs) {
578
- const blocker = coordinator.earliestBlockerOf(chunkId);
579
- if (!blocker) return "clear";
580
- const progressedAt = coordinator.lastProgressAt.get(blocker) ?? 0;
581
- return nowMs - progressedAt < thresholdMs ? "rearm" : "warn";
601
+ const blocker = coordinator.earliestBlockerOf(chunkId);
602
+ if (!blocker) return "clear";
603
+ return nowMs - (coordinator.lastProgressAt.get(blocker) ?? 0) < thresholdMs ? "rearm" : "warn";
582
604
  }
583
-
584
- // src/coordinator.ts
585
- function createSmoothCoordinator2(onEmpty) {
586
- return createSmoothCoordinator(onEmpty);
605
+ function createSmoothCoordinator(onEmpty) {
606
+ return createSmoothCoordinator$1(onEmpty);
587
607
  }
588
-
589
- // src/tailSignal.ts
590
- var DESCEND_THROUGH = /* @__PURE__ */ new Set(["blockquote", "list", "listItem", "footnoteDefinition"]);
608
+ const DESCEND_THROUGH = /* @__PURE__ */ new Set([
609
+ "blockquote",
610
+ "list",
611
+ "listItem",
612
+ "footnoteDefinition"
613
+ ]);
591
614
  function deriveTailSignal(mdast, contentLength) {
592
- const children = mdast.children;
593
- let index = children.length - 1;
594
- while (index >= 0) {
595
- const offset = children[index].position?.start?.offset;
596
- if (offset !== void 0 && offset >= contentLength) {
597
- index--;
598
- continue;
599
- }
600
- break;
601
- }
602
- if (index < 0) return null;
603
- let node = children[index];
604
- let deepestFootnoteDef = null;
605
- for (; ; ) {
606
- if (node.type === "footnoteDefinition") deepestFootnoteDef = node;
607
- if (node.type === "definition") {
608
- return { kind: "invisible-def" };
609
- }
610
- if (!DESCEND_THROUGH.has(node.type)) break;
611
- const kids = node.children;
612
- if (!kids || kids.length === 0) break;
613
- node = kids[kids.length - 1];
614
- }
615
- return deepestFootnoteDef ? { kind: "footnote-def", identifier: deepestFootnoteDef.identifier } : null;
615
+ const children = mdast.children;
616
+ let index = children.length - 1;
617
+ while (index >= 0) {
618
+ const offset = children[index].position?.start?.offset;
619
+ if (offset !== void 0 && offset >= contentLength) {
620
+ index--;
621
+ continue;
622
+ }
623
+ break;
624
+ }
625
+ if (index < 0) return null;
626
+ let node = children[index];
627
+ let deepestFootnoteDef = null;
628
+ for (;;) {
629
+ if (node.type === "footnoteDefinition") deepestFootnoteDef = node;
630
+ if (node.type === "definition") return { kind: "invisible-def" };
631
+ if (!DESCEND_THROUGH.has(node.type)) break;
632
+ const kids = node.children;
633
+ if (!kids || kids.length === 0) break;
634
+ node = kids[kids.length - 1];
635
+ }
636
+ return deepestFootnoteDef ? {
637
+ kind: "footnote-def",
638
+ identifier: deepestFootnoteDef.identifier
639
+ } : null;
616
640
  }
617
-
618
- // src/aggregateFootnotes.ts
619
- import { footnoteSafeId as footnoteSafeId2, lastMeaningfulIdx } from "@ai-markdown/engine";
620
-
621
- // src/cloneHastForRender.ts
622
641
  function cloneHastForRender(node) {
623
- const copy = { ...node };
624
- const withChildren = copy;
625
- if (Array.isArray(withChildren.children)) {
626
- withChildren.children = withChildren.children.map((child) => cloneHastForRender(child));
627
- }
628
- const withProperties = copy;
629
- if (withProperties.properties) {
630
- withProperties.properties = { ...withProperties.properties };
631
- }
632
- if (copy.data) {
633
- copy.data = { ...copy.data };
634
- const data = copy.data;
635
- if (data.originalUrls) data.originalUrls = { ...data.originalUrls };
636
- }
637
- return copy;
642
+ const copy = { ...node };
643
+ const withChildren = copy;
644
+ if (Array.isArray(withChildren.children)) withChildren.children = withChildren.children.map((child) => cloneHastForRender(child));
645
+ const withProperties = copy;
646
+ if (withProperties.properties) withProperties.properties = { ...withProperties.properties };
647
+ if (copy.data) {
648
+ copy.data = { ...copy.data };
649
+ const data = copy.data;
650
+ if (data.originalUrls) data.originalUrls = { ...data.originalUrls };
651
+ }
652
+ return copy;
638
653
  }
639
-
640
- // src/aggregateFootnotes.ts
641
654
  function buildBackref(href, occurrence, globalNumber) {
642
- const children = [{ type: "text", value: "\u21A9" }];
643
- if (occurrence > 1) {
644
- children.push({
645
- type: "element",
646
- tagName: "sup",
647
- properties: {},
648
- children: [{ type: "text", value: String(occurrence) }]
649
- });
650
- }
651
- const ariaLabel = occurrence === 1 ? `Back to reference ${globalNumber}` : `Back to reference ${globalNumber}-${occurrence}`;
652
- return {
653
- type: "element",
654
- tagName: "a",
655
- properties: {
656
- href,
657
- dataFootnoteBackref: "",
658
- className: ["data-footnote-backref"],
659
- ariaLabel
660
- },
661
- children
662
- };
655
+ const children = [{
656
+ type: "text",
657
+ value: "↩"
658
+ }];
659
+ if (occurrence > 1) children.push({
660
+ type: "element",
661
+ tagName: "sup",
662
+ properties: {},
663
+ children: [{
664
+ type: "text",
665
+ value: String(occurrence)
666
+ }]
667
+ });
668
+ return {
669
+ type: "element",
670
+ tagName: "a",
671
+ properties: {
672
+ href,
673
+ dataFootnoteBackref: "",
674
+ className: ["data-footnote-backref"],
675
+ ariaLabel: occurrence === 1 ? `Back to reference ${globalNumber}` : `Back to reference ${globalNumber}-${occurrence}`
676
+ },
677
+ children
678
+ };
663
679
  }
664
680
  function buildAggregateTree(registry, clobberPrefix, preserveOrphanReferences = false) {
665
- const seen = /* @__PURE__ */ new Set();
666
- const ordered = [];
667
- for (const sym of registry.chunkOrder) {
668
- const data = registry.chunkData.get(sym);
669
- if (!data) continue;
670
- for (const ref of data.refs) {
671
- if (ref.kind !== "footnote") continue;
672
- if (seen.has(ref.label)) continue;
673
- seen.add(ref.label);
674
- const canonicalSym = registry.canonicalFootnoteFor(ref.label);
675
- if (!canonicalSym) continue;
676
- const def = registry.chunkData.get(canonicalSym)?.defs.get(ref.label);
677
- if (!def) continue;
678
- const n = registry.globalNumber(ref.label);
679
- if (n === null) continue;
680
- const sourceIdentifier = def.sourceIdentifier ?? ref.label;
681
- ordered.push({
682
- normalizedLabel: ref.label,
683
- sourceIdentifier,
684
- bodyHast: def.bodyHast ?? [],
685
- n,
686
- withBackref: true
687
- });
688
- }
689
- }
690
- ordered.sort((a, b) => (a.n ?? 0) - (b.n ?? 0));
691
- if (preserveOrphanReferences) {
692
- for (const sym of registry.chunkOrder) {
693
- const data = registry.chunkData.get(sym);
694
- if (!data) continue;
695
- for (const [label, def] of data.defs) {
696
- if (seen.has(label)) continue;
697
- if (registry.canonicalFootnoteFor(label) !== sym) continue;
698
- seen.add(label);
699
- ordered.push({
700
- normalizedLabel: label,
701
- sourceIdentifier: def.sourceIdentifier ?? label,
702
- bodyHast: def.bodyHast ?? [],
703
- n: null,
704
- withBackref: false
705
- });
706
- }
707
- }
708
- }
709
- if (ordered.length === 0) return null;
710
- const liElements = ordered.map(({ normalizedLabel, sourceIdentifier, bodyHast, n, withBackref }) => {
711
- const safeId = footnoteSafeId2(sourceIdentifier);
712
- const liChildren = bodyHast.map((c) => cloneHastForRender(c));
713
- if (withBackref) {
714
- const totalRefs = registry.getRefsForLabel(normalizedLabel);
715
- const tailIdx = lastMeaningfulIdx(liChildren);
716
- const tail = tailIdx !== -1 ? liChildren[tailIdx] : null;
717
- const dest = tail && tail.type === "element" && tail.tagName === "p" ? tail : null;
718
- const appended = [];
719
- for (let i = 1; i <= Math.max(totalRefs, 1); i++) {
720
- appended.push({ type: "text", value: " " });
721
- const href = i === 1 ? `#${clobberPrefix}fnref-${safeId}` : `#${clobberPrefix}fnref-${safeId}-${i}`;
722
- appended.push(buildBackref(href, i, n ?? 0));
723
- }
724
- if (dest) {
725
- dest.children = [...dest.children, ...appended];
726
- } else if (tailIdx !== -1) {
727
- liChildren.splice(tailIdx + 1, 0, ...appended);
728
- } else {
729
- liChildren.push(...appended);
730
- }
731
- }
732
- return {
733
- type: "element",
734
- tagName: "li",
735
- properties: {
736
- id: `${clobberPrefix}fn-${safeId}`,
737
- // Stringified: @types/hast (≥3.0.5) types li's `value` as string;
738
- // React serializes value={3} and value="3" to identical markup.
739
- ...n !== null ? { value: String(n) } : {}
740
- },
741
- children: liChildren
742
- };
743
- });
744
- return {
745
- type: "element",
746
- tagName: "section",
747
- properties: {
748
- className: ["footnotes"],
749
- dataFootnotes: true,
750
- // a11y: name the section landmark so screen readers still announce it
751
- // even though the visible "Footnotes" h2 is omitted in coordinated mode.
752
- ariaLabel: "Footnotes"
753
- },
754
- children: [
755
- // Conventional separator above the footnote list. The default
756
- // mdast-util-to-hast footer doesn't emit one (GitHub renders the
757
- // separation via CSS `border-top` on `.footnotes`); we render a literal
758
- // `<hr>` so the visual break is preserved without sanitize-friendly CSS.
759
- {
760
- type: "element",
761
- tagName: "hr",
762
- properties: {},
763
- children: []
764
- },
765
- {
766
- type: "element",
767
- tagName: "ol",
768
- properties: {},
769
- children: liElements
770
- }
771
- ]
772
- };
681
+ const seen = /* @__PURE__ */ new Set();
682
+ const ordered = [];
683
+ for (const sym of registry.chunkOrder) {
684
+ const data = registry.chunkData.get(sym);
685
+ if (!data) continue;
686
+ for (const ref of data.refs) {
687
+ if (ref.kind !== "footnote") continue;
688
+ if (seen.has(ref.label)) continue;
689
+ seen.add(ref.label);
690
+ const canonicalSym = registry.canonicalFootnoteFor(ref.label);
691
+ if (!canonicalSym) continue;
692
+ const def = registry.chunkData.get(canonicalSym)?.defs.get(ref.label);
693
+ if (!def) continue;
694
+ const n = registry.globalNumber(ref.label);
695
+ if (n === null) continue;
696
+ const sourceIdentifier = def.sourceIdentifier ?? ref.label;
697
+ ordered.push({
698
+ normalizedLabel: ref.label,
699
+ sourceIdentifier,
700
+ bodyHast: def.bodyHast ?? [],
701
+ n,
702
+ withBackref: true
703
+ });
704
+ }
705
+ }
706
+ ordered.sort((a, b) => (a.n ?? 0) - (b.n ?? 0));
707
+ if (preserveOrphanReferences) for (const sym of registry.chunkOrder) {
708
+ const data = registry.chunkData.get(sym);
709
+ if (!data) continue;
710
+ for (const [label, def] of data.defs) {
711
+ if (seen.has(label)) continue;
712
+ if (registry.canonicalFootnoteFor(label) !== sym) continue;
713
+ seen.add(label);
714
+ ordered.push({
715
+ normalizedLabel: label,
716
+ sourceIdentifier: def.sourceIdentifier ?? label,
717
+ bodyHast: def.bodyHast ?? [],
718
+ n: null,
719
+ withBackref: false
720
+ });
721
+ }
722
+ }
723
+ if (ordered.length === 0) return null;
724
+ return {
725
+ type: "element",
726
+ tagName: "section",
727
+ properties: {
728
+ className: ["footnotes"],
729
+ dataFootnotes: true,
730
+ ariaLabel: "Footnotes"
731
+ },
732
+ children: [{
733
+ type: "element",
734
+ tagName: "hr",
735
+ properties: {},
736
+ children: []
737
+ }, {
738
+ type: "element",
739
+ tagName: "ol",
740
+ properties: {},
741
+ children: ordered.map(({ normalizedLabel, sourceIdentifier, bodyHast, n, withBackref }) => {
742
+ const safeId = footnoteSafeId(sourceIdentifier);
743
+ const liChildren = bodyHast.map((c) => cloneHastForRender(c));
744
+ if (withBackref) {
745
+ const totalRefs = registry.getRefsForLabel(normalizedLabel);
746
+ const tailIdx = lastMeaningfulIdx(liChildren);
747
+ const tail = tailIdx !== -1 ? liChildren[tailIdx] : null;
748
+ const dest = tail && tail.type === "element" && tail.tagName === "p" ? tail : null;
749
+ const appended = [];
750
+ for (let i = 1; i <= Math.max(totalRefs, 1); i++) {
751
+ appended.push({
752
+ type: "text",
753
+ value: " "
754
+ });
755
+ const href = i === 1 ? `#${clobberPrefix}fnref-${safeId}` : `#${clobberPrefix}fnref-${safeId}-${i}`;
756
+ appended.push(buildBackref(href, i, n ?? 0));
757
+ }
758
+ if (dest) dest.children = [...dest.children, ...appended];
759
+ else if (tailIdx !== -1) liChildren.splice(tailIdx + 1, 0, ...appended);
760
+ else liChildren.push(...appended);
761
+ }
762
+ return {
763
+ type: "element",
764
+ tagName: "li",
765
+ properties: {
766
+ id: `${clobberPrefix}fn-${safeId}`,
767
+ ...n !== null ? { value: String(n) } : {}
768
+ },
769
+ children: liChildren
770
+ };
771
+ })
772
+ }]
773
+ };
773
774
  }
774
-
775
- // src/pipelineSession.ts
776
- import {
777
- EngineRawHtmlDepthError,
778
- advanceIncrementalParse,
779
- buildPhantomSuffix,
780
- phantomSuffixCloser,
781
- parseStage,
782
- transformStage
783
- } from "@ai-markdown/engine";
784
- var unmeasured = (_stage, fn) => fn();
775
+ const unmeasured = (_stage, fn) => fn();
785
776
  function endPoint(text) {
786
- let line = 1;
787
- let lineStart = 0;
788
- for (let i = 0; i < text.length; i++) {
789
- const code = text.charCodeAt(i);
790
- if (code === 10 || code === 13 && text.charCodeAt(i + 1) !== 10) {
791
- line += 1;
792
- lineStart = i + 1;
793
- }
794
- }
795
- return { line, column: text.length - lineStart + 1, offset: text.length };
777
+ let line = 1;
778
+ let lineStart = 0;
779
+ for (let i = 0; i < text.length; i++) {
780
+ const code = text.charCodeAt(i);
781
+ if (code === 10 || code === 13 && text.charCodeAt(i + 1) !== 10) {
782
+ line += 1;
783
+ lineStart = i + 1;
784
+ }
785
+ }
786
+ return {
787
+ line,
788
+ column: text.length - lineStart + 1,
789
+ offset: text.length
790
+ };
796
791
  }
797
792
  function plainTextTrees(content) {
798
- const position = { start: { line: 1, column: 1, offset: 0 }, end: endPoint(content) };
799
- const text = { type: "text", value: content, position };
800
- return {
801
- mdast: { type: "root", children: [{ type: "paragraph", children: [text], position }], position },
802
- hast: {
803
- type: "root",
804
- children: [{ type: "element", tagName: "p", properties: {}, children: [text], position }],
805
- position
806
- }
807
- };
793
+ const position = {
794
+ start: {
795
+ line: 1,
796
+ column: 1,
797
+ offset: 0
798
+ },
799
+ end: endPoint(content)
800
+ };
801
+ const text = {
802
+ type: "text",
803
+ value: content,
804
+ position
805
+ };
806
+ return {
807
+ mdast: {
808
+ type: "root",
809
+ children: [{
810
+ type: "paragraph",
811
+ children: [text],
812
+ position
813
+ }],
814
+ position
815
+ },
816
+ hast: {
817
+ type: "root",
818
+ children: [{
819
+ type: "element",
820
+ tagName: "p",
821
+ properties: {},
822
+ children: [text],
823
+ position
824
+ }],
825
+ position
826
+ }
827
+ };
808
828
  }
809
829
  function createPipelineSession() {
810
- let state = null;
811
- return {
812
- reset() {
813
- state = null;
814
- },
815
- parse({
816
- content,
817
- targetPhantoms,
818
- remarkPlugins,
819
- rehypePlugins,
820
- remarkRehypeOptions,
821
- handlers,
822
- preserveForBodyHarvest,
823
- documentId,
824
- provenance,
825
- incrementalParse,
826
- defListEnabled,
827
- gfmTaskListItems = false,
828
- // No default: omitted is its own (undeclared) state at the engine.
829
- mathFlow,
830
- measure: measureHere = unmeasured
831
- }) {
832
- const phantomDefs = buildPhantomSuffix(targetPhantoms);
833
- const phantomSuffix = phantomDefs === "" ? "" : phantomSuffixCloser(content ?? "") + phantomDefs;
834
- const augmented = (content ?? "") + phantomSuffix;
835
- const baseHandlers = remarkRehypeOptions?.handlers ?? {};
836
- const mergedRemarkRehypeOptions = handlers ? {
837
- ...remarkRehypeOptions,
838
- handlers: { ...baseHandlers, ...handlers },
839
- // Phantom label sets are empty in standalone mode (no PASS 0.5
840
- // injection happened); the footnoteDefinition handler still reads
841
- // them via `state.options.phantomFootnoteLabels.has(id)`, which
842
- // returns false for every id → orphan-protect path proceeds.
843
- phantomFootnoteLabels: targetPhantoms.missingFootnotes,
844
- phantomLinkLabels: targetPhantoms.missingLinks,
845
- preserveOrphan: preserveForBodyHarvest,
846
- documentId,
847
- // The SAME value `buildCoreRehypePlugins` received — the
848
- // verifier unwraps every placeholder stamped with anything else.
849
- provenance
850
- } : {
851
- ...remarkRehypeOptions
852
- };
853
- const fullPipeline = () => {
854
- try {
855
- const parsed = measureHere(
856
- "parse",
857
- () => parseStage({
858
- children: augmented,
859
- remarkPlugins,
860
- rehypePlugins,
861
- remarkRehypeOptions: mergedRemarkRehypeOptions
862
- })
863
- );
864
- const hastRoot = measureHere("transform", () => transformStage(parsed));
865
- return { mdast: parsed.mdast, hast: hastRoot };
866
- } catch (error) {
867
- if (!(error instanceof EngineRawHtmlDepthError)) throw error;
868
- if (true) {
869
- console.error(
870
- "[ai-react-markdown] raw HTML nested past the engine depth bound \u2014 rendering this frame as plain text:",
871
- error
872
- );
873
- }
874
- return plainTextTrees(content ?? "");
875
- }
876
- };
877
- if (!incrementalParse) {
878
- state = null;
879
- return fullPipeline();
880
- }
881
- try {
882
- const result = advanceIncrementalParse(state, content ?? "", {
883
- remarkPlugins,
884
- rehypePlugins,
885
- remarkRehypeOptions: mergedRemarkRehypeOptions,
886
- // Identity tuple over every parse input beyond the content itself.
887
- // Deliberately covers MORE than the G3 flush's 12 fields (handlers /
888
- // preserveForBodyHarvest / documentId can change without touching
889
- // any G3 field — e.g. a `preserveOrphanReferences` flip). The
890
- // phantom label sets are deliberately NOT here: their churn tracks
891
- // the suffix (always re-parsed with the tail), never the prefix.
892
- depsKey: [
893
- remarkPlugins,
894
- rehypePlugins,
895
- remarkRehypeOptions,
896
- handlers,
897
- preserveForBodyHarvest,
898
- documentId,
899
- // Explicit even though `rehypePlugins` already carries it: a
900
- // credential change must never reuse trees stamped under the
901
- // old one.
902
- provenance,
903
- // The boundary scanner's grammar profile. The scan checkpoint
904
- // already refuses to resume under a different profile, but the
905
- // retained TREES were still spliced against — a prefix frozen
906
- // under one profile must not survive into frames under the other.
907
- defListEnabled,
908
- gfmTaskListItems,
909
- mathFlow
910
- ],
911
- defListEnabled,
912
- gfmTaskListItems,
913
- mathFlow,
914
- phantomSuffix,
915
- measure: measureHere
916
- });
917
- state = result.nextState;
918
- return { mdast: result.mdast, hast: result.hast };
919
- } catch (error) {
920
- state = null;
921
- if (true) {
922
- console.error("[ai-react-markdown] incremental parse failed \u2014 full parse fallback for this frame:", error);
923
- }
924
- return fullPipeline();
925
- }
926
- }
927
- };
830
+ let state = null;
831
+ return {
832
+ reset() {
833
+ state = null;
834
+ },
835
+ parse({ content, targetPhantoms, remarkPlugins, rehypePlugins, remarkRehypeOptions, handlers, preserveForBodyHarvest, documentId, provenance, incrementalParse, defListEnabled, gfmTaskListItems = false, mathFlow, measure: measureHere = unmeasured }) {
836
+ const phantomDefs = buildPhantomSuffix(targetPhantoms);
837
+ const phantomSuffix = phantomDefs === "" ? "" : phantomSuffixCloser(content ?? "") + phantomDefs;
838
+ const augmented = (content ?? "") + phantomSuffix;
839
+ const baseHandlers = remarkRehypeOptions?.handlers ?? {};
840
+ const mergedRemarkRehypeOptions = handlers ? {
841
+ ...remarkRehypeOptions,
842
+ handlers: {
843
+ ...baseHandlers,
844
+ ...handlers
845
+ },
846
+ phantomFootnoteLabels: targetPhantoms.missingFootnotes,
847
+ phantomLinkLabels: targetPhantoms.missingLinks,
848
+ preserveOrphan: preserveForBodyHarvest,
849
+ documentId,
850
+ provenance
851
+ } : { ...remarkRehypeOptions };
852
+ const fullPipeline = () => {
853
+ try {
854
+ const parsed = measureHere("parse", () => parseStage({
855
+ children: augmented,
856
+ remarkPlugins,
857
+ rehypePlugins,
858
+ remarkRehypeOptions: mergedRemarkRehypeOptions
859
+ }));
860
+ const hastRoot = measureHere("transform", () => transformStage(parsed));
861
+ return {
862
+ mdast: parsed.mdast,
863
+ hast: hastRoot
864
+ };
865
+ } catch (error) {
866
+ if (!(error instanceof EngineRawHtmlDepthError)) throw error;
867
+ console.error("[ai-react-markdown] raw HTML nested past the engine depth bound — rendering this frame as plain text:", error);
868
+ return plainTextTrees(content ?? "");
869
+ }
870
+ };
871
+ if (!incrementalParse) {
872
+ state = null;
873
+ return fullPipeline();
874
+ }
875
+ try {
876
+ const result = advanceIncrementalParse(state, content ?? "", {
877
+ remarkPlugins,
878
+ rehypePlugins,
879
+ remarkRehypeOptions: mergedRemarkRehypeOptions,
880
+ depsKey: [
881
+ remarkPlugins,
882
+ rehypePlugins,
883
+ remarkRehypeOptions,
884
+ handlers,
885
+ preserveForBodyHarvest,
886
+ documentId,
887
+ provenance,
888
+ defListEnabled,
889
+ gfmTaskListItems,
890
+ mathFlow
891
+ ],
892
+ defListEnabled,
893
+ gfmTaskListItems,
894
+ mathFlow,
895
+ phantomSuffix,
896
+ measure: measureHere
897
+ });
898
+ state = result.nextState;
899
+ return {
900
+ mdast: result.mdast,
901
+ hast: result.hast
902
+ };
903
+ } catch (error) {
904
+ state = null;
905
+ console.error("[ai-react-markdown] incremental parse failed — full parse fallback for this frame:", error);
906
+ return fullPipeline();
907
+ }
908
+ }
909
+ };
928
910
  }
929
-
930
- // src/coordinationPreparation.ts
931
- import { buildCrossChunkHandlers, normalizeForMatch } from "@ai-markdown/engine";
932
911
  function derivePhantomTargets({ content, ownLabels, labels }, previous) {
933
- const missingFootnotes = /* @__PURE__ */ new Set();
934
- const missingLinks = /* @__PURE__ */ new Set();
935
- if (labels) {
936
- const footnotes = [...labels.footnoteLabels].filter((label) => !ownLabels.footnoteLabels.has(label));
937
- const links = [...labels.linkLabels].filter((label) => !ownLabels.linkLabels.has(label));
938
- if (footnotes.length || links.length) {
939
- const normalized = normalizeForMatch(content);
940
- for (const label of footnotes) if (normalized.includes(label)) missingFootnotes.add(label);
941
- for (const label of links) if (normalized.includes(label)) missingLinks.add(label);
942
- }
943
- }
944
- if (previous && missingFootnotes.size === previous.missingFootnotes.size && missingLinks.size === previous.missingLinks.size && [...missingFootnotes].every((label) => previous.missingFootnotes.has(label)) && [...missingLinks].every((label) => previous.missingLinks.has(label)))
945
- return previous;
946
- return { missingFootnotes, missingLinks };
912
+ const missingFootnotes = /* @__PURE__ */ new Set();
913
+ const missingLinks = /* @__PURE__ */ new Set();
914
+ if (labels) {
915
+ const footnotes = [...labels.footnoteLabels].filter((label) => !ownLabels.footnoteLabels.has(label));
916
+ const links = [...labels.linkLabels].filter((label) => !ownLabels.linkLabels.has(label));
917
+ if (footnotes.length || links.length) {
918
+ const normalized = normalizeForMatch(content);
919
+ for (const label of footnotes) if (normalized.includes(label)) missingFootnotes.add(label);
920
+ for (const label of links) if (normalized.includes(label)) missingLinks.add(label);
921
+ }
922
+ }
923
+ if (previous && missingFootnotes.size === previous.missingFootnotes.size && missingLinks.size === previous.missingLinks.size && [...missingFootnotes].every((label) => previous.missingFootnotes.has(label)) && [...missingLinks].every((label) => previous.missingLinks.has(label))) return previous;
924
+ return {
925
+ missingFootnotes,
926
+ missingLinks
927
+ };
947
928
  }
948
- function deriveCoordinationPolicy({
949
- coordinated,
950
- registered,
951
- preserveOrphanReferences
952
- }, previous) {
953
- const mode = coordinated ? "coordinated" : preserveOrphanReferences ? "orphan" : "standalone";
954
- const preserveForBodyHarvest = preserveOrphanReferences || coordinated && registered;
955
- if (previous?.mode === mode && previous.preserveForBodyHarvest === preserveForBodyHarvest) return previous;
956
- const handlers = previous?.mode === mode ? previous.handlers : mode === "coordinated" ? buildCrossChunkHandlers() : mode === "orphan" ? { footnoteDefinition: buildCrossChunkHandlers().footnoteDefinition } : void 0;
957
- return { mode, handlers, preserveForBodyHarvest };
929
+ function deriveCoordinationPolicy({ coordinated, registered, preserveOrphanReferences }, previous) {
930
+ const mode = coordinated ? "coordinated" : preserveOrphanReferences ? "orphan" : "standalone";
931
+ const preserveForBodyHarvest = preserveOrphanReferences || coordinated && registered;
932
+ if (previous?.mode === mode && previous.preserveForBodyHarvest === preserveForBodyHarvest) return previous;
933
+ return {
934
+ mode,
935
+ handlers: previous?.mode === mode ? previous.handlers : mode === "coordinated" ? buildCrossChunkHandlers() : mode === "orphan" ? { footnoteDefinition: buildCrossChunkHandlers().footnoteDefinition } : void 0,
936
+ preserveForBodyHarvest
937
+ };
958
938
  }
959
939
  function buildContributionChain(inputs) {
960
- return [
961
- inputs.remarkPlugins,
962
- inputs.rehypePlugins,
963
- inputs.remarkRehypeOptions,
964
- inputs.handlers,
965
- inputs.preserveForBodyHarvest,
966
- inputs.clobberPrefix,
967
- inputs.documentId,
968
- inputs.provenance
969
- ];
940
+ return [
941
+ inputs.remarkPlugins,
942
+ inputs.rehypePlugins,
943
+ inputs.remarkRehypeOptions,
944
+ inputs.handlers,
945
+ inputs.preserveForBodyHarvest,
946
+ inputs.clobberPrefix,
947
+ inputs.documentId,
948
+ inputs.provenance
949
+ ];
970
950
  }
971
- export {
972
- buildAggregateTree,
973
- buildBlocks,
974
- buildContributionChain,
975
- cloneHastForRender,
976
- computeBlockFingerprint,
977
- computeHtmlBlockDigest,
978
- computeHtmlBlockDigestWithExtent,
979
- createBlockPlanner,
980
- createContributionSession,
981
- createPipelineSession,
982
- createSmoothCoordinator2 as createSmoothCoordinator,
983
- deriveCoordinationPolicy,
984
- derivePhantomTargets,
985
- deriveTailSignal,
986
- evaluateGateWarn,
987
- hasMdastSource,
988
- isFootnoteSection
989
- };
951
+ export { buildAggregateTree, buildBlocks, buildContributionChain, cloneHastForRender, computeBlockFingerprint, computeHtmlBlockDigest, computeHtmlBlockDigestWithExtent, createBlockPlanner, createContributionSession, createPipelineSession, createSmoothCoordinator, deriveCoordinationPolicy, derivePhantomTargets, deriveTailSignal, evaluateGateWarn, hasMdastSource, isFootnoteSection };
952
+
990
953
  //# sourceMappingURL=index.dev.js.map