@stream-mdx/react 0.4.0 → 0.5.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +14 -4
  2. package/README.md +65 -15
  3. package/dist/components/bottom-stick-scroll-area.cjs +344 -0
  4. package/dist/components/bottom-stick-scroll-area.d.cts +36 -0
  5. package/dist/components/bottom-stick-scroll-area.d.ts +36 -0
  6. package/dist/components/bottom-stick-scroll-area.mjs +320 -0
  7. package/dist/components/index.cjs +503 -172
  8. package/dist/components/index.d.cts +3 -1
  9. package/dist/components/index.d.ts +3 -1
  10. package/dist/components/index.mjs +350 -22
  11. package/dist/{index-D2MYusAM.d.cts → index-BVhp_pcF.d.ts} +3 -0
  12. package/dist/{index-D2MYusAM.d.ts → index-EaeouYp2.d.cts} +3 -0
  13. package/dist/index.cjs +1472 -486
  14. package/dist/index.d.cts +6 -4
  15. package/dist/index.d.ts +6 -4
  16. package/dist/index.mjs +1256 -272
  17. package/dist/mdx-client.cjs +1 -1
  18. package/dist/mdx-client.mjs +1 -1
  19. package/dist/mdx-coordinator.cjs +28 -7
  20. package/dist/mdx-coordinator.d.cts +2 -2
  21. package/dist/mdx-coordinator.d.ts +2 -2
  22. package/dist/mdx-coordinator.mjs +28 -7
  23. package/dist/renderer/node-views.cjs +271 -26
  24. package/dist/renderer/node-views.d.cts +3 -1
  25. package/dist/renderer/node-views.d.ts +3 -1
  26. package/dist/renderer/node-views.mjs +272 -27
  27. package/dist/renderer/patch-batching.cjs +2 -0
  28. package/dist/renderer/patch-batching.d.cts +1 -1
  29. package/dist/renderer/patch-batching.d.ts +1 -1
  30. package/dist/renderer/patch-batching.mjs +2 -1
  31. package/dist/renderer/patch-commit-scheduler.cjs +82 -30
  32. package/dist/renderer/patch-commit-scheduler.d.cts +15 -3
  33. package/dist/renderer/patch-commit-scheduler.d.ts +15 -3
  34. package/dist/renderer/patch-commit-scheduler.mjs +84 -30
  35. package/dist/renderer/store.cjs +382 -36
  36. package/dist/renderer/store.d.cts +3 -0
  37. package/dist/renderer/store.d.ts +3 -0
  38. package/dist/renderer/store.mjs +382 -36
  39. package/dist/renderer.cjs +758 -114
  40. package/dist/renderer.d.cts +3 -1
  41. package/dist/renderer.d.ts +3 -1
  42. package/dist/renderer.mjs +760 -116
  43. package/dist/server.cjs +669 -71
  44. package/dist/server.d.cts +5 -0
  45. package/dist/server.d.ts +5 -0
  46. package/dist/server.mjs +670 -72
  47. package/dist/{streaming-markdown-DOc2uLiP.d.ts → streaming-markdown-CEI9eDZm.d.ts} +3 -1
  48. package/dist/{streaming-markdown-D-JcByd9.d.cts → streaming-markdown-DRisClXb.d.cts} +3 -1
  49. package/dist/streaming-markdown.cjs +788 -121
  50. package/dist/streaming-markdown.d.cts +4 -2
  51. package/dist/streaming-markdown.d.ts +4 -2
  52. package/dist/streaming-markdown.mjs +790 -123
  53. package/dist/utils/inline-html.d.cts +3 -1
  54. package/dist/utils/inline-html.d.ts +3 -1
  55. package/package.json +14 -3
  56. package/shadcn/README.md +28 -0
  57. package/shadcn/bottom-stick-scroll-area.tsx +358 -0
@@ -81,7 +81,7 @@ var init_mdx_client = __esm({
81
81
  if (block.type !== "mdx") {
82
82
  throw new Error("Block is not MDX type");
83
83
  }
84
- const cacheKey = block.id;
84
+ const cacheKey = `${block.id}\0${block.payload.raw ?? ""}`;
85
85
  const cachedPromise = this.cache.get(cacheKey);
86
86
  if (cachedPromise) {
87
87
  return cachedPromise;
@@ -1173,7 +1173,11 @@ var defaultInlineComponents = {
1173
1173
  const n = number ?? "?";
1174
1174
  const href = number ? `#fn:${n}` : void 0;
1175
1175
  const id = number ? `fnref:${n}` : void 0;
1176
- return React4.createElement("sup", { className: "footnote-ref" }, React4.createElement("a", { href, id, "data-label": label }, String(n)));
1176
+ return React4.createElement(
1177
+ "sup",
1178
+ { className: "footnote-ref" },
1179
+ React4.createElement("a", { href, id, "data-label": label, style: { color: "var(--foreground)", textDecoration: "underline" } }, String(n))
1180
+ );
1177
1181
  }
1178
1182
  };
1179
1183
  var defaultBlockComponents = {
@@ -1316,7 +1320,7 @@ var defaultBlockComponents = {
1316
1320
  dangerouslySetInnerHTML: { __html: htmlString }
1317
1321
  });
1318
1322
  },
1319
- mdx: ({ compiledRef, compiledModule, status, errorMessage, raw }) => {
1323
+ mdx: ({ compiledRef, compiledModule, status, errorMessage }) => {
1320
1324
  const [Comp, setComp] = React4.useState(null);
1321
1325
  const [internalError, setInternalError] = React4.useState(null);
1322
1326
  const [renderError, setRenderError] = React4.useState(null);
@@ -1417,7 +1421,6 @@ var defaultBlockComponents = {
1417
1421
  const waitingForVisibility = deferHydration && !shouldHydrate && Boolean(resolvedId);
1418
1422
  const pendingLabel = waitingForVisibility ? "MDX ready (hydrate on view)\u2026" : compileMode === "worker" ? "Compiling MDX (client)\u2026" : "Compiling MDX (server)\u2026";
1419
1423
  const message = failureMessage ?? (compileMode === "worker" ? "MDX compilation failed in client mode. Try switching back to server rendering or restarting the stream." : "MDX compilation failed. Try restarting the stream.");
1420
- const rawFallback = typeof raw === "string" && raw.trim().length > 0 ? React4.createElement("pre", { className: "markdown-mdx-fallback" }, raw) : null;
1421
1424
  const allowRetry = Boolean(internalError || renderError);
1422
1425
  const retryButton = allowRetry ? React4.createElement(
1423
1426
  "button",
@@ -1429,9 +1432,10 @@ var defaultBlockComponents = {
1429
1432
  "Retry hydration"
1430
1433
  ) : null;
1431
1434
  const errorPanel = React4.createElement(
1432
- "div",
1433
- { className: "markdown-mdx-error" },
1434
- React4.createElement(React4.Fragment, null, React4.createElement("strong", null, "MDX failed"), React4.createElement("div", null, message))
1435
+ React4.Fragment,
1436
+ null,
1437
+ React4.createElement("strong", null, "MDX failed"),
1438
+ React4.createElement("div", null, message)
1435
1439
  );
1436
1440
  if (Comp && !failureMessage) {
1437
1441
  return React4.createElement(
@@ -1454,14 +1458,13 @@ var defaultBlockComponents = {
1454
1458
  return React4.createElement(
1455
1459
  "div",
1456
1460
  {
1457
- className: "markdown-mdx",
1461
+ className: "markdown-mdx error",
1458
1462
  ref: containerRef,
1459
1463
  "data-mdx-ref": pendingId,
1460
1464
  "data-mdx-status": "error",
1461
1465
  "data-mdx-mode": compileMode
1462
1466
  },
1463
1467
  errorPanel,
1464
- rawFallback,
1465
1468
  retryButton
1466
1469
  );
1467
1470
  }
@@ -1488,7 +1491,16 @@ var defaultBlockComponents = {
1488
1491
  { key: item.number, id: `fn:${item.number}` },
1489
1492
  renderInlineNodes(item.inlines, defaultInlineComponents),
1490
1493
  " ",
1491
- React4.createElement("a", { href: `#fnref:${item.number}`, className: "footnote-backref", "aria-label": "Back to content" }, "\u21A9")
1494
+ React4.createElement(
1495
+ "a",
1496
+ {
1497
+ href: `#fnref:${item.number}`,
1498
+ className: "footnote-backref",
1499
+ "aria-label": "Back to content",
1500
+ style: { color: "var(--foreground)", textDecoration: "underline" }
1501
+ },
1502
+ "\u21A9"
1503
+ )
1492
1504
  )
1493
1505
  );
1494
1506
  return React4.createElement("section", { className: "footnotes" }, React4.createElement("hr", {}), React4.createElement("ol", {}, listItems));
@@ -2034,7 +2046,10 @@ function useCodeHighlightRequester() {
2034
2046
  }
2035
2047
 
2036
2048
  // src/renderer/patch-batching.ts
2037
- import { isHeavyPatch, splitPatchBatch } from "@stream-mdx/core/perf/patch-batching";
2049
+ import { getPatchKind, isHeavyPatch, splitPatchBatch } from "@stream-mdx/core/perf/patch-batching";
2050
+
2051
+ // src/renderer/patch-commit-scheduler.ts
2052
+ import { getPatchKind as getPatchKind2 } from "@stream-mdx/core/perf/patch-batching";
2038
2053
 
2039
2054
  // src/renderer/patch-coalescing.ts
2040
2055
  import {
@@ -2052,6 +2067,9 @@ var COALESCING_DURATION_ACTIVATE_MS = 6;
2052
2067
  var COALESCING_DURATION_DEACTIVATE_MS = 4;
2053
2068
  var RETAIN_TOUCHED_IN_HISTORY = typeof process !== "undefined" && process.env ? process.env.NODE_ENV !== "production" : true;
2054
2069
  var EMPTY_TOUCHED_SET = /* @__PURE__ */ new Set();
2070
+ function shouldSkipCoalescing(patches) {
2071
+ return patches.length <= 1;
2072
+ }
2055
2073
  function getDefaultNow() {
2056
2074
  if (typeof performance !== "undefined" && typeof performance.now === "function") {
2057
2075
  return () => performance.now();
@@ -2078,8 +2096,9 @@ function getDefaultCancelRaf() {
2078
2096
  }
2079
2097
  var PatchCommitScheduler = class {
2080
2098
  constructor(params) {
2081
- this.highQueue = [];
2082
- this.lowQueue = [];
2099
+ this.semanticQueue = [];
2100
+ this.enrichmentHighQueue = [];
2101
+ this.enrichmentLowQueue = [];
2083
2102
  this.scheduled = false;
2084
2103
  this.flushing = false;
2085
2104
  this.scheduledHandle = null;
@@ -2091,6 +2110,7 @@ var PatchCommitScheduler = class {
2091
2110
  this.scheduleToken = 0;
2092
2111
  this.coalescingDurationSamples = [];
2093
2112
  this.adaptiveBudgetActive = false;
2113
+ this.flushCount = 0;
2094
2114
  this.store = params.store;
2095
2115
  this.onFlush = params.onFlush;
2096
2116
  this.frameBudgetMs = Math.max(1, params.options?.frameBudgetMs ?? 10);
@@ -2106,6 +2126,8 @@ var PatchCommitScheduler = class {
2106
2126
  this.maxLowPriorityBatchesPerFlush = params.options?.maxLowPriorityBatchesPerFlush ?? 2;
2107
2127
  this.urgentQueueThreshold = Math.max(1, params.options?.urgentQueueThreshold ?? 4);
2108
2128
  this.historyLimit = Math.max(1, params.options?.historyLimit ?? 200);
2129
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(params.options?.startupMicrotaskFlushes ?? 0));
2130
+ this.adaptiveBudgeting = params.options?.adaptiveBudgeting ?? true;
2109
2131
  const requestedBatch = params.options?.batch;
2110
2132
  if (requestedBatch === "microtask") {
2111
2133
  this.batchStrategy = "microtask";
@@ -2132,19 +2154,23 @@ var PatchCommitScheduler = class {
2132
2154
  patches: input.patches,
2133
2155
  meta: input.meta ?? {},
2134
2156
  receivedAt: input.meta?.receivedAt ?? this.now(),
2157
+ kind: input.meta?.kind === "semantic" || input.meta?.kind === "enrichment" ? input.meta.kind : input.patches.some((patch) => getPatchKind2(patch) === "semantic") ? "semantic" : "enrichment",
2135
2158
  priority: input.meta?.priority === "low" ? "low" : "high"
2136
2159
  };
2137
- if (batch.priority === "low") {
2138
- this.lowQueue.push(batch);
2160
+ if (batch.kind === "semantic") {
2161
+ batch.priority = "high";
2162
+ this.semanticQueue.push(batch);
2163
+ } else if (batch.priority === "low") {
2164
+ this.enrichmentLowQueue.push(batch);
2139
2165
  } else {
2140
- this.highQueue.push(batch);
2166
+ this.enrichmentHighQueue.push(batch);
2141
2167
  }
2142
2168
  if (!this.paused) {
2143
2169
  this.schedule();
2144
2170
  }
2145
2171
  }
2146
2172
  flushAll() {
2147
- if (this.highQueue.length === 0 && this.lowQueue.length === 0 && !this.flushing) {
2173
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0 && !this.flushing) {
2148
2174
  return null;
2149
2175
  }
2150
2176
  this.cancelScheduled();
@@ -2154,9 +2180,11 @@ var PatchCommitScheduler = class {
2154
2180
  }
2155
2181
  clear() {
2156
2182
  this.cancelScheduled();
2157
- this.highQueue = [];
2158
- this.lowQueue = [];
2183
+ this.semanticQueue = [];
2184
+ this.enrichmentHighQueue = [];
2185
+ this.enrichmentLowQueue = [];
2159
2186
  this.flushing = false;
2187
+ this.flushCount = 0;
2160
2188
  this.coalescingDurationSamples = [];
2161
2189
  this.adaptiveBudgetActive = false;
2162
2190
  this.adaptiveHighBatchCap = void 0;
@@ -2164,10 +2192,10 @@ var PatchCommitScheduler = class {
2164
2192
  this.maybeResolveIdle();
2165
2193
  }
2166
2194
  getPendingCount() {
2167
- return this.highQueue.length + this.lowQueue.length;
2195
+ return this.semanticQueue.length + this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
2168
2196
  }
2169
2197
  isIdle() {
2170
- return !this.flushing && !this.scheduled && this.highQueue.length === 0 && this.lowQueue.length === 0;
2198
+ return !this.flushing && !this.scheduled && this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0;
2171
2199
  }
2172
2200
  awaitIdle() {
2173
2201
  if (this.isIdle()) {
@@ -2207,7 +2235,8 @@ var PatchCommitScheduler = class {
2207
2235
  this.runFlush(this.frameBudgetMs, false);
2208
2236
  this.maybeResolveIdle();
2209
2237
  };
2210
- if (this.batchStrategy === "microtask" && typeof queueMicrotask === "function") {
2238
+ const useStartupMicrotask = this.batchStrategy !== "microtask" && this.startupMicrotaskFlushes > 0 && this.flushCount < this.startupMicrotaskFlushes && typeof queueMicrotask === "function";
2239
+ if ((this.batchStrategy === "microtask" || useStartupMicrotask) && typeof queueMicrotask === "function") {
2211
2240
  this.scheduledViaRaf = false;
2212
2241
  this.scheduledHandle = token;
2213
2242
  queueMicrotask(execute);
@@ -2237,12 +2266,14 @@ var PatchCommitScheduler = class {
2237
2266
  if (this.paused && !manual) {
2238
2267
  return null;
2239
2268
  }
2240
- if (this.highQueue.length === 0 && this.lowQueue.length === 0) {
2269
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0) {
2241
2270
  return null;
2242
2271
  }
2243
2272
  this.flushing = true;
2244
2273
  const startedAt = this.now();
2245
- const initialQueueSize = this.highQueue.length + this.lowQueue.length;
2274
+ const initialSemanticQueueSize = this.semanticQueue.length;
2275
+ const initialEnrichmentQueueSize = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
2276
+ const initialQueueSize = initialSemanticQueueSize + initialEnrichmentQueueSize;
2246
2277
  const batches = [];
2247
2278
  let totalInputPatches = 0;
2248
2279
  let totalAppliedPatches = 0;
@@ -2257,7 +2288,15 @@ var PatchCommitScheduler = class {
2257
2288
  consumed += 1;
2258
2289
  const applyStart = this.now();
2259
2290
  const queueDelayMs = Math.max(0, applyStart - next.receivedAt);
2260
- const { patches: coalesced, metrics: coalescingMetrics } = coalescePatchesWithMetrics(next.patches, DEFAULT_COALESCE_CONFIG);
2291
+ let coalesced;
2292
+ let coalescingMetrics;
2293
+ if (shouldSkipCoalescing(next.patches)) {
2294
+ coalesced = next.patches;
2295
+ } else {
2296
+ const result = coalescePatchesWithMetrics(next.patches, DEFAULT_COALESCE_CONFIG);
2297
+ coalesced = result.patches;
2298
+ coalescingMetrics = result.metrics;
2299
+ }
2261
2300
  const touched = this.store.applyPatches(coalesced, {
2262
2301
  coalesced: true,
2263
2302
  metrics: coalescingMetrics
@@ -2277,6 +2316,7 @@ var PatchCommitScheduler = class {
2277
2316
  receivedAt: next.receivedAt,
2278
2317
  appliedAt: applyStart,
2279
2318
  priority: next.priority,
2319
+ kind: next.kind,
2280
2320
  coalescing: coalescingMetrics
2281
2321
  });
2282
2322
  if (maxBatches && batches.length - startingBatchCount >= maxBatches) {
@@ -2293,27 +2333,32 @@ var PatchCommitScheduler = class {
2293
2333
  queue.splice(0, consumed);
2294
2334
  }
2295
2335
  };
2296
- const initialHighQueueSize = this.highQueue.length;
2336
+ processQueue(this.semanticQueue, void 0, Number.POSITIVE_INFINITY);
2337
+ const initialHighQueueSize = this.enrichmentHighQueue.length;
2297
2338
  const configuredHighLimit = manual || !this.maxBatchesPerFlush ? void 0 : initialHighQueueSize > this.maxBatchesPerFlush ? void 0 : this.maxBatchesPerFlush;
2298
- const highBatchLimit = this.adaptiveHighBatchCap ?? configuredHighLimit;
2299
- processQueue(this.highQueue, highBatchLimit, budgetMs);
2339
+ const highBatchLimit = this.adaptiveBudgeting ? this.adaptiveHighBatchCap ?? configuredHighLimit : configuredHighLimit;
2340
+ processQueue(this.enrichmentHighQueue, highBatchLimit, budgetMs);
2300
2341
  if (manual) {
2301
- processQueue(this.lowQueue, void 0, Number.POSITIVE_INFINITY);
2302
- } else if (this.lowQueue.length > 0) {
2342
+ processQueue(this.enrichmentLowQueue, void 0, Number.POSITIVE_INFINITY);
2343
+ } else if (this.enrichmentLowQueue.length > 0) {
2303
2344
  elapsed = this.now() - startedAt;
2304
2345
  const budgetForLow = Math.max(0, this.lowPriorityFrameBudgetMs - elapsed);
2305
- if (budgetForLow > 0) {
2306
- const initialLowQueueSize = this.lowQueue.length;
2346
+ const processedHigherPriorityWork = batches.length > 0;
2347
+ if (budgetForLow > 0 || !processedHigherPriorityWork) {
2348
+ const initialLowQueueSize = this.enrichmentLowQueue.length;
2307
2349
  const configuredLowLimit = manual || !this.maxLowPriorityBatchesPerFlush ? void 0 : initialLowQueueSize > this.maxLowPriorityBatchesPerFlush ? void 0 : this.maxLowPriorityBatchesPerFlush;
2308
- const lowBatchLimit = this.adaptiveLowBatchCap ?? configuredLowLimit;
2309
- processQueue(this.lowQueue, lowBatchLimit, budgetForLow);
2350
+ const lowBatchLimit = this.adaptiveBudgeting ? this.adaptiveLowBatchCap ?? configuredLowLimit : configuredLowLimit;
2351
+ processQueue(this.enrichmentLowQueue, lowBatchLimit, budgetForLow);
2310
2352
  }
2311
2353
  }
2312
2354
  this.flushing = false;
2313
2355
  const completedAt = this.now();
2314
2356
  const totalDurationMs = completedAt - startedAt;
2315
- const remaining = this.highQueue.length + this.lowQueue.length;
2357
+ const remainingSemantic = this.semanticQueue.length;
2358
+ const remainingEnrichment = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
2359
+ const remaining = remainingSemantic + remainingEnrichment;
2316
2360
  if (batches.length > 0) {
2361
+ this.flushCount += 1;
2317
2362
  try {
2318
2363
  this.onFlush({
2319
2364
  batches,
@@ -2321,7 +2366,11 @@ var PatchCommitScheduler = class {
2321
2366
  totalAppliedPatches,
2322
2367
  totalDurationMs,
2323
2368
  remainingQueueSize: remaining,
2369
+ remainingSemanticQueueSize: remainingSemantic,
2370
+ remainingEnrichmentQueueSize: remainingEnrichment,
2324
2371
  queueDepthBefore: initialQueueSize,
2372
+ semanticQueueDepthBefore: initialSemanticQueueSize,
2373
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
2325
2374
  flushStartedAt: startedAt,
2326
2375
  flushCompletedAt: completedAt
2327
2376
  });
@@ -2332,21 +2381,27 @@ var PatchCommitScheduler = class {
2332
2381
  if (remaining > 0 && !this.paused) {
2333
2382
  this.schedule();
2334
2383
  }
2335
- const coalescingStats = this.computeCoalescingDurationStats();
2336
- this.updateAdaptiveBudget(coalescingStats.p95);
2337
- const adaptiveState = this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null);
2384
+ const coalescingStats = this.adaptiveBudgeting ? this.computeCoalescingDurationStats() : { sampleCount: this.coalescingDurationSamples.length, p95: null };
2385
+ if (this.adaptiveBudgeting) {
2386
+ this.updateAdaptiveBudget(coalescingStats.p95);
2387
+ }
2388
+ const adaptiveState = this.adaptiveBudgeting ? this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null) : void 0;
2338
2389
  const flushResult = batches.length > 0 ? {
2339
2390
  batches,
2340
2391
  totalPatches: totalInputPatches,
2341
2392
  totalAppliedPatches,
2342
2393
  totalDurationMs,
2343
2394
  remainingQueueSize: remaining,
2395
+ remainingSemanticQueueSize: remainingSemantic,
2396
+ remainingEnrichmentQueueSize: remainingEnrichment,
2344
2397
  queueDepthBefore: initialQueueSize,
2398
+ semanticQueueDepthBefore: initialSemanticQueueSize,
2399
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
2345
2400
  flushStartedAt: startedAt,
2346
2401
  flushCompletedAt: completedAt,
2347
2402
  coalescingDurationP95: coalescingStats.p95 ?? void 0,
2348
2403
  coalescingDurationSampleCount: coalescingStats.sampleCount,
2349
- adaptiveBudgetActive: adaptiveState.active,
2404
+ adaptiveBudgetActive: adaptiveState?.active,
2350
2405
  adaptiveBudgetState: adaptiveState
2351
2406
  } : null;
2352
2407
  if (flushResult) {
@@ -2382,6 +2437,9 @@ var PatchCommitScheduler = class {
2382
2437
  };
2383
2438
  }
2384
2439
  updateAdaptiveBudget(p95) {
2440
+ if (!this.adaptiveBudgeting) {
2441
+ return;
2442
+ }
2385
2443
  if (p95 !== null && p95 > COALESCING_DURATION_ACTIVATE_MS) {
2386
2444
  if (!this.adaptiveBudgetActive) {
2387
2445
  this.adaptiveBudgetActive = true;
@@ -2455,11 +2513,13 @@ var PatchCommitScheduler = class {
2455
2513
  }
2456
2514
  restart() {
2457
2515
  this.cancelScheduled();
2458
- this.highQueue = [];
2459
- this.lowQueue = [];
2516
+ this.semanticQueue = [];
2517
+ this.enrichmentHighQueue = [];
2518
+ this.enrichmentLowQueue = [];
2460
2519
  this.flushing = false;
2461
2520
  this.sequence = 0;
2462
2521
  this.history = [];
2522
+ this.flushCount = 0;
2463
2523
  this.scheduleToken++;
2464
2524
  this.paused = false;
2465
2525
  this.coalescingDurationSamples = [];
@@ -2506,6 +2566,12 @@ var PatchCommitScheduler = class {
2506
2566
  if (typeof options.historyLimit !== "undefined") {
2507
2567
  this.setHistoryLimit(options.historyLimit);
2508
2568
  }
2569
+ if (typeof options.startupMicrotaskFlushes !== "undefined") {
2570
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(options.startupMicrotaskFlushes));
2571
+ }
2572
+ if (typeof options.adaptiveBudgeting !== "undefined") {
2573
+ this.adaptiveBudgeting = options.adaptiveBudgeting;
2574
+ }
2509
2575
  if (typeof options.batch !== "undefined") {
2510
2576
  const requestedBatch = options.batch;
2511
2577
  if (requestedBatch === "microtask") {
@@ -2540,6 +2606,7 @@ import {
2540
2606
  sanitizeHTML as sanitizeHTML2,
2541
2607
  PATCH_ROOT_ID
2542
2608
  } from "@stream-mdx/core";
2609
+ import { getPatchKind as getPatchKind3 } from "@stream-mdx/core/perf/patch-batching";
2543
2610
 
2544
2611
  // src/renderer/list-utils.ts
2545
2612
  function updateNodeDepth(node, depth) {
@@ -2635,12 +2702,28 @@ function snapshotToBlock(node) {
2635
2702
  }
2636
2703
  return null;
2637
2704
  }
2638
- var nodeSnapshotCache = /* @__PURE__ */ new Map();
2639
- var childrenSnapshotCache = /* @__PURE__ */ new Map();
2705
+ var nodeSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
2706
+ var childrenSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
2640
2707
  var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
2641
2708
  var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
2642
2709
  var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
2643
2710
  var CHILDREN_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(CHILDREN_SNAPSHOT_CACHE_LIMIT * 0.1));
2711
+ function getNodeSnapshotCacheForMap(map) {
2712
+ let cache = nodeSnapshotCacheByMap.get(map);
2713
+ if (!cache) {
2714
+ cache = /* @__PURE__ */ new Map();
2715
+ nodeSnapshotCacheByMap.set(map, cache);
2716
+ }
2717
+ return cache;
2718
+ }
2719
+ function getChildrenSnapshotCacheForMap(map) {
2720
+ let cache = childrenSnapshotCacheByMap.get(map);
2721
+ if (!cache) {
2722
+ cache = /* @__PURE__ */ new Map();
2723
+ childrenSnapshotCacheByMap.set(map, cache);
2724
+ }
2725
+ return cache;
2726
+ }
2644
2727
  function pruneCache(cache, max, buffer) {
2645
2728
  if (cache.size <= max + buffer) return;
2646
2729
  let remaining = cache.size - max;
@@ -2650,6 +2733,14 @@ function pruneCache(cache, max, buffer) {
2650
2733
  if (remaining <= 0) break;
2651
2734
  }
2652
2735
  }
2736
+ function deleteSnapshotCacheEntry(map, id) {
2737
+ getNodeSnapshotCacheForMap(map).delete(id);
2738
+ getChildrenSnapshotCacheForMap(map).delete(id);
2739
+ }
2740
+ function clearSnapshotCaches(map) {
2741
+ getNodeSnapshotCacheForMap(map).clear();
2742
+ getChildrenSnapshotCacheForMap(map).clear();
2743
+ }
2653
2744
  var EMPTY_CHILDREN = Object.freeze([]);
2654
2745
  var EMPTY_NODE_SNAPSHOT = Object.freeze({ version: -1, node: void 0 });
2655
2746
  var EMPTY_CHILDREN_SNAPSHOT = Object.freeze({
@@ -2817,9 +2908,57 @@ function createRootRecord() {
2817
2908
  parentId: null,
2818
2909
  children: [],
2819
2910
  props: {},
2820
- version: 0
2911
+ version: 0,
2912
+ blockEpoch: 0
2821
2913
  };
2822
2914
  }
2915
+ function readNumericEpoch(value) {
2916
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
2917
+ return void 0;
2918
+ }
2919
+ return value;
2920
+ }
2921
+ function readSnapshotBlockEpoch(snapshot) {
2922
+ return readNumericEpoch(snapshot.meta?.blockEpoch);
2923
+ }
2924
+ function readPatchBlockEpoch(patch) {
2925
+ if (patch.op === "setHTML") {
2926
+ return readNumericEpoch(patch.patchMeta?.blockEpoch);
2927
+ }
2928
+ return readNumericEpoch(patch.meta?.blockEpoch);
2929
+ }
2930
+ function readPatchParseEpoch(patch) {
2931
+ if (patch.op === "setHTML") {
2932
+ return readNumericEpoch(patch.patchMeta?.parseEpoch);
2933
+ }
2934
+ return readNumericEpoch(patch.meta?.parseEpoch);
2935
+ }
2936
+ function readPatchTx(patch) {
2937
+ if (patch.op === "setHTML") {
2938
+ return readNumericEpoch(patch.patchMeta?.tx);
2939
+ }
2940
+ return readNumericEpoch(patch.meta?.tx);
2941
+ }
2942
+ function assignSubtreeBlockEpoch(map, rootId, blockEpoch, touched) {
2943
+ const root = map.get(rootId);
2944
+ if (!root) return;
2945
+ const stack = [root];
2946
+ while (stack.length > 0) {
2947
+ const node = stack.pop();
2948
+ if (!node) continue;
2949
+ if (node.blockEpoch !== blockEpoch) {
2950
+ node.blockEpoch = blockEpoch;
2951
+ node.version += 1;
2952
+ touched?.add(node.id);
2953
+ }
2954
+ for (const childId of node.children) {
2955
+ const child = map.get(childId);
2956
+ if (child) {
2957
+ stack.push(child);
2958
+ }
2959
+ }
2960
+ }
2961
+ }
2823
2962
  function ensureArray(value) {
2824
2963
  return Array.isArray(value) ? value : [];
2825
2964
  }
@@ -2879,7 +3018,7 @@ function escapeHtml2(value) {
2879
3018
  }
2880
3019
  function stripOuterLineSpan(html) {
2881
3020
  if (!html) return null;
2882
- const openTagMatch = html.match(/<span[^>]*>/i);
3021
+ const openTagMatch = html.match(/<span[^>]*class="[^"]*\bline\b[^"]*"[^>]*>/i);
2883
3022
  if (!openTagMatch) {
2884
3023
  return null;
2885
3024
  }
@@ -2928,8 +3067,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2928
3067
  if (node.type !== "code-line") {
2929
3068
  anomalies.push(`type-mismatch:${node.id}:${node.type}`);
2930
3069
  nodes.delete(node.id);
2931
- nodeSnapshotCache.delete(node.id);
2932
- childrenSnapshotCache.delete(node.id);
3070
+ deleteSnapshotCacheEntry(nodes, node.id);
2933
3071
  touched.add(node.id);
2934
3072
  continue;
2935
3073
  }
@@ -2973,16 +3111,14 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2973
3111
  const existing = nodes.get(desiredId);
2974
3112
  if (existing && existing !== node) {
2975
3113
  nodes.delete(desiredId);
2976
- nodeSnapshotCache.delete(desiredId);
2977
- childrenSnapshotCache.delete(desiredId);
3114
+ deleteSnapshotCacheEntry(nodes, desiredId);
2978
3115
  touched.add(desiredId);
2979
3116
  mutated = true;
2980
3117
  }
2981
3118
  if (node.id !== desiredId) {
2982
3119
  const previousId = node.id;
2983
3120
  nodes.delete(node.id);
2984
- nodeSnapshotCache.delete(previousId);
2985
- childrenSnapshotCache.delete(previousId);
3121
+ deleteSnapshotCacheEntry(nodes, previousId);
2986
3122
  node.id = desiredId;
2987
3123
  nodes.set(desiredId, node);
2988
3124
  touched.add(desiredId);
@@ -3013,8 +3149,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
3013
3149
  for (const [id, record] of nodes) {
3014
3150
  if (record.parentId === parent.id && record.type === "code-line" && !allowed.has(id)) {
3015
3151
  nodes.delete(id);
3016
- nodeSnapshotCache.delete(id);
3017
- childrenSnapshotCache.delete(id);
3152
+ deleteSnapshotCacheEntry(nodes, id);
3018
3153
  touched.add(id);
3019
3154
  mutated = true;
3020
3155
  }
@@ -3098,8 +3233,10 @@ function normalizeCodeLineProps(incoming, previous) {
3098
3233
  }
3099
3234
  function createCodeLineProps(index, text, highlight, tokens) {
3100
3235
  const safeText = typeof text === "string" ? text : "";
3101
- const sanitized = sanitizeLineInnerHtml(highlight, safeText);
3102
- const html = sanitized && sanitized.length >= safeText.length ? sanitized : escapeHtml2(safeText);
3236
+ const hasIncomingHighlight = typeof highlight === "string" && highlight.trim().length > 0;
3237
+ const sanitized = hasIncomingHighlight ? sanitizeLineInnerHtml(highlight, safeText) : null;
3238
+ const hasRenderableTokens = tokens !== void 0 && tokens !== null;
3239
+ const html = sanitized && sanitized.length >= safeText.length ? sanitized : hasRenderableTokens ? null : escapeHtml2(safeText);
3103
3240
  const props = {
3104
3241
  index,
3105
3242
  text: safeText,
@@ -3210,6 +3347,18 @@ function validateCodeBlockChildren(nodes, parent) {
3210
3347
  issues.push({ kind: "dangling-node", id });
3211
3348
  }
3212
3349
  }
3350
+ if (parent.block?.isFinalized) {
3351
+ const snapshot = createBlockSnapshot(cloneBlock(parent.block));
3352
+ const expectedChildren = ensureArray(snapshot.children).filter((child) => child.type === "code-line");
3353
+ if (expectedChildren.length !== parent.children.length) {
3354
+ issues.push({
3355
+ kind: "line-count-mismatch",
3356
+ id: parent.id,
3357
+ expected: expectedChildren.length,
3358
+ actual: parent.children.length
3359
+ });
3360
+ }
3361
+ }
3213
3362
  return { ok: issues.length === 0, issues };
3214
3363
  }
3215
3364
  function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
@@ -3226,7 +3375,7 @@ function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
3226
3375
  }
3227
3376
  const snapshot = createBlockSnapshot(cloneBlock(parent.block));
3228
3377
  removeSubtree(nodes, parent.id, touched);
3229
- const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot);
3378
+ const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot, touched, parent.blockEpoch);
3230
3379
  if (!inserted) {
3231
3380
  return false;
3232
3381
  }
@@ -3249,7 +3398,55 @@ function arraysEqual(a, b) {
3249
3398
  }
3250
3399
  return true;
3251
3400
  }
3252
- function buildNodeFromSnapshot(map, snapshot, parentId) {
3401
+ function expectedCodeLineId(parentId, index) {
3402
+ return `${parentId}::line:${index}`;
3403
+ }
3404
+ function collectStoreInvariantViolations(nodes) {
3405
+ const violations = [];
3406
+ for (const [id, node] of nodes) {
3407
+ if (node.type === "__root__") continue;
3408
+ if (node.children.length > 1) {
3409
+ const seen = /* @__PURE__ */ new Set();
3410
+ for (const childId of node.children) {
3411
+ if (seen.has(childId)) {
3412
+ violations.push(`duplicate child id:${id}:${childId}`);
3413
+ } else {
3414
+ seen.add(childId);
3415
+ }
3416
+ }
3417
+ }
3418
+ if (node.type === "list") {
3419
+ const parent = node.parentId ? nodes.get(node.parentId) : void 0;
3420
+ if (parent?.type === "list-item" && node.children.length === 0) {
3421
+ violations.push(`empty nested list:${id}`);
3422
+ }
3423
+ }
3424
+ if (node.type === "code") {
3425
+ for (let index = 0; index < node.children.length; index += 1) {
3426
+ const childId = node.children[index];
3427
+ const child = nodes.get(childId);
3428
+ if (!child) {
3429
+ violations.push(`missing code-line node:${node.id}:${childId}`);
3430
+ continue;
3431
+ }
3432
+ if (child.type !== "code-line") {
3433
+ violations.push(`non-code child in code block:${node.id}:${child.id}:${child.type}`);
3434
+ continue;
3435
+ }
3436
+ const expectedId = expectedCodeLineId(node.id, index);
3437
+ if (child.id !== expectedId) {
3438
+ violations.push(`code-line id mismatch:${node.id}:${child.id}:${expectedId}`);
3439
+ }
3440
+ const indexProp = typeof child.props?.index === "number" ? child.props.index : null;
3441
+ if (indexProp !== index) {
3442
+ violations.push(`code-line index mismatch:${node.id}:${child.id}:${indexProp}:${index}`);
3443
+ }
3444
+ }
3445
+ }
3446
+ }
3447
+ return violations;
3448
+ }
3449
+ function buildNodeFromSnapshot(map, snapshot, parentId, blockEpoch) {
3253
3450
  const record = {
3254
3451
  id: snapshot.id,
3255
3452
  type: snapshot.type,
@@ -3259,6 +3456,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
3259
3456
  meta: snapshot.meta ? { ...snapshot.meta } : void 0,
3260
3457
  range: snapshot.range ? { ...snapshot.range } : void 0,
3261
3458
  version: 0,
3459
+ blockEpoch,
3262
3460
  block: snapshotToBlock(snapshot) ?? void 0
3263
3461
  };
3264
3462
  if (record.type === "code") {
@@ -3267,7 +3465,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
3267
3465
  map.set(record.id, record);
3268
3466
  const childSnapshots = ensureArray(snapshot.children);
3269
3467
  for (const child of childSnapshots) {
3270
- const childRecord = buildNodeFromSnapshot(map, child, record.id);
3468
+ const childRecord = buildNodeFromSnapshot(map, child, record.id, blockEpoch);
3271
3469
  record.children.push(childRecord.id);
3272
3470
  }
3273
3471
  return record;
@@ -3295,8 +3493,7 @@ function removeSubtree(map, id, touched) {
3295
3493
  }
3296
3494
  }
3297
3495
  map.delete(node.id);
3298
- nodeSnapshotCache.delete(node.id);
3299
- childrenSnapshotCache.delete(node.id);
3496
+ deleteSnapshotCacheEntry(map, node.id);
3300
3497
  touched.add(node.id);
3301
3498
  for (const childId of node.children) {
3302
3499
  const child = map.get(childId);
@@ -3328,13 +3525,13 @@ function resolveParent(map, path) {
3328
3525
  }
3329
3526
  return map.get(path.blockId);
3330
3527
  }
3331
- function insertSnapshotAt(map, parent, index, snapshot, touched) {
3528
+ function insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
3332
3529
  if (!parent) return null;
3333
3530
  const existingIndex = parent.children.indexOf(snapshot.id);
3334
3531
  if (existingIndex !== -1) {
3335
3532
  removeSubtree(map, parent.children[existingIndex], touched ?? /* @__PURE__ */ new Set());
3336
3533
  }
3337
- const record = buildNodeFromSnapshot(map, snapshot, parent.id);
3534
+ const record = buildNodeFromSnapshot(map, snapshot, parent.id, blockEpoch ?? readSnapshotBlockEpoch(snapshot) ?? parent.blockEpoch);
3338
3535
  const insertedIds = [];
3339
3536
  const queue = [record];
3340
3537
  while (queue.length > 0) {
@@ -3352,13 +3549,13 @@ function insertSnapshotAt(map, parent, index, snapshot, touched) {
3352
3549
  ensureUniqueChildren(parent, touched);
3353
3550
  return { record, insertedIds };
3354
3551
  }
3355
- function replaceSnapshotAt(map, parent, index, snapshot, touched) {
3552
+ function replaceSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
3356
3553
  if (!parent) return;
3357
3554
  if (index < 0 || index >= parent.children.length) return;
3358
3555
  const targetId = parent.children[index];
3359
3556
  parent.children.splice(index, 1);
3360
3557
  removeSubtree(map, targetId, touched);
3361
- const inserted = insertSnapshotAt(map, parent, index, snapshot, touched);
3558
+ const inserted = insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch);
3362
3559
  if (inserted) {
3363
3560
  for (const id of inserted.insertedIds) {
3364
3561
  touched.add(id);
@@ -3378,6 +3575,24 @@ function createRendererStore(initialBlocks = []) {
3378
3575
  const listeners = /* @__PURE__ */ new Set();
3379
3576
  let notifyScheduled = false;
3380
3577
  let lastCoalescingMetrics = null;
3578
+ let invariantViolations = [];
3579
+ let transientDiagnostics = [];
3580
+ let nextBlockEpoch = 1;
3581
+ const debugCounters = {
3582
+ appendLineGuardRejected: 0,
3583
+ staleEpochRejected: 0,
3584
+ storeInvariantViolationCount: 0
3585
+ };
3586
+ const allocateBlockEpoch = (preferred) => {
3587
+ const resolved = readNumericEpoch(preferred);
3588
+ if (resolved !== void 0) {
3589
+ nextBlockEpoch = Math.max(nextBlockEpoch, resolved + 1);
3590
+ return resolved;
3591
+ }
3592
+ const epoch = nextBlockEpoch;
3593
+ nextBlockEpoch += 1;
3594
+ return epoch;
3595
+ };
3381
3596
  const getRaf = () => {
3382
3597
  if (typeof globalThis !== "undefined" && typeof globalThis.requestAnimationFrame === "function") {
3383
3598
  return globalThis.requestAnimationFrame;
@@ -3409,9 +3624,12 @@ function createRendererStore(initialBlocks = []) {
3409
3624
  }
3410
3625
  }
3411
3626
  }
3627
+ function appendDiagnostic(message) {
3628
+ transientDiagnostics = [...transientDiagnostics, message];
3629
+ invariantViolations = [...invariantViolations, message];
3630
+ }
3412
3631
  function rebuildFromBlocks(blocks) {
3413
- nodeSnapshotCache.clear();
3414
- childrenSnapshotCache.clear();
3632
+ clearSnapshotCaches(nodes);
3415
3633
  nodes = /* @__PURE__ */ new Map();
3416
3634
  const root = createRootRecord();
3417
3635
  nodes.set(root.id, root);
@@ -3420,9 +3638,12 @@ function createRendererStore(initialBlocks = []) {
3420
3638
  for (let i = 0; i < blocks.length; i++) {
3421
3639
  const block = cloneBlock(blocks[i]);
3422
3640
  const snapshot = createBlockSnapshot(block);
3423
- insertSnapshotAt(nodes, root, root.children.length, snapshot);
3641
+ insertSnapshotAt(nodes, root, root.children.length, snapshot, void 0, allocateBlockEpoch(readSnapshotBlockEpoch(snapshot)));
3424
3642
  }
3425
3643
  normalizeAllListDepths(nodes, /* @__PURE__ */ new Set(), PATCH_ROOT_ID);
3644
+ transientDiagnostics = [];
3645
+ invariantViolations = collectStoreInvariantViolations(nodes);
3646
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
3426
3647
  version++;
3427
3648
  blocksDirty = true;
3428
3649
  scheduleNotify();
@@ -3487,11 +3708,57 @@ function createRendererStore(initialBlocks = []) {
3487
3708
  listDepthDirty = true;
3488
3709
  }
3489
3710
  };
3490
- const applyPropsUpdate = (at, props) => {
3711
+ const semanticTxEpochs = /* @__PURE__ */ new Map();
3712
+ const resolveBlockEpochTarget = (patch, at) => {
3713
+ if ((patch.op === "insertChild" || patch.op === "replaceChild") && at.blockId === PATCH_ROOT_ID) {
3714
+ const nodeId = patch.node?.id;
3715
+ return nodeId ? nodes.get(nodeId) : void 0;
3716
+ }
3717
+ return nodes.get(at.blockId) ?? (at.nodeId ? nodes.get(at.nodeId) : void 0);
3718
+ };
3719
+ const rejectStaleEpochPatch = (patch, at) => {
3720
+ const expectedBlockEpoch = readPatchBlockEpoch(patch);
3721
+ const patchTx = readPatchTx(patch);
3722
+ if (expectedBlockEpoch === void 0) {
3723
+ return false;
3724
+ }
3725
+ if (getPatchKind3(patch) !== "semantic" || !at) {
3726
+ return false;
3727
+ }
3728
+ const target = resolveBlockEpochTarget(patch, at);
3729
+ if (!target || target.blockEpoch === expectedBlockEpoch) {
3730
+ return false;
3731
+ }
3732
+ const txEpoch = at.blockId && patchTx !== void 0 ? semanticTxEpochs.get(at.blockId) : void 0;
3733
+ if (txEpoch && txEpoch.tx === patchTx && txEpoch.blockEpoch === target.blockEpoch) {
3734
+ return false;
3735
+ }
3736
+ debugCounters.staleEpochRejected += 1;
3737
+ appendDiagnostic(
3738
+ `stale epoch rejected:${patch.op}:${target.id}:expected=${expectedBlockEpoch}:actual=${target.blockEpoch}`
3739
+ );
3740
+ return true;
3741
+ };
3742
+ const advanceBlockEpoch = (blockId, touchedSet, preferredEpoch, tx) => {
3743
+ if (!blockId || blockId === PATCH_ROOT_ID) {
3744
+ return null;
3745
+ }
3746
+ const blockRoot = nodes.get(blockId);
3747
+ if (!blockRoot) {
3748
+ return null;
3749
+ }
3750
+ const nextEpoch = allocateBlockEpoch(preferredEpoch);
3751
+ assignSubtreeBlockEpoch(nodes, blockRoot.id, nextEpoch, touchedSet);
3752
+ if (tx !== void 0) {
3753
+ semanticTxEpochs.set(blockRoot.id, { tx, blockEpoch: nextEpoch });
3754
+ }
3755
+ return nextEpoch;
3756
+ };
3757
+ const applyPropsUpdate = (at, props, preferredEpoch, tx, semanticUpdate = false) => {
3491
3758
  const perfStartLocal = perfEnabled ? getPerfNow() : 0;
3492
3759
  const targetId = at.nodeId ?? at.blockId;
3493
3760
  if (!targetId) return;
3494
- const target = nodes.get(targetId);
3761
+ let target = nodes.get(targetId);
3495
3762
  if (!target) return;
3496
3763
  const parentType = target.parentId ? nodes.get(target.parentId)?.type : void 0;
3497
3764
  if (debugListPatches && (isListRelated(target.type) || isListRelated(parentType))) {
@@ -3518,6 +3785,9 @@ function createRendererStore(initialBlocks = []) {
3518
3785
  propsChanged = true;
3519
3786
  }
3520
3787
  if (propsChanged) {
3788
+ if (semanticUpdate) {
3789
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3790
+ }
3521
3791
  target.version++;
3522
3792
  touched.add(target.id);
3523
3793
  const parent = target.parentId ? nodes.get(target.parentId) : void 0;
@@ -3581,16 +3851,56 @@ function createRendererStore(initialBlocks = []) {
3581
3851
  }
3582
3852
  }
3583
3853
  let blockChanged = false;
3854
+ let typeChanged = false;
3855
+ let rebuiltFromFinalizedBlock = false;
3856
+ let blockEpochAdvanced = false;
3584
3857
  const incomingBlock = props?.block;
3585
3858
  if (incomingBlock && typeof incomingBlock === "object") {
3586
3859
  target.block = cloneBlock(incomingBlock);
3860
+ const previousType = target.type;
3861
+ const incomingType = typeof target.block.type === "string" ? target.block.type : target.type;
3862
+ if (incomingType !== target.type) {
3863
+ target.type = incomingType;
3864
+ typeChanged = true;
3865
+ if (isListRelated(previousType) || isListRelated(target.type)) {
3866
+ markListDepthDirty(target.id);
3867
+ }
3868
+ }
3587
3869
  if (target.type === "code") {
3588
3870
  applyCodeBlockMetadata(target);
3589
3871
  }
3872
+ if (semanticUpdate) {
3873
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3874
+ blockEpochAdvanced = true;
3875
+ }
3876
+ const shouldRebuildFinalizedTree = !at.nodeId && target.id === at.blockId && Boolean(target.block?.isFinalized) && target.type !== "code" && Boolean(target.parentId);
3877
+ if (shouldRebuildFinalizedTree) {
3878
+ const parent = target.parentId ? nodes.get(target.parentId) : void 0;
3879
+ const index = parent ? parent.children.indexOf(target.id) : -1;
3880
+ if (parent && index !== -1) {
3881
+ const snapshot = createBlockSnapshot(cloneBlock(target.block));
3882
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
3883
+ mutatedBlocks = true;
3884
+ blockChanged = true;
3885
+ rebuiltFromFinalizedBlock = true;
3886
+ const rebuilt = nodes.get(target.id);
3887
+ if (isListRelated(previousType) || isListRelated(rebuilt?.type)) {
3888
+ markListDepthDirty(target.id);
3889
+ }
3890
+ target = rebuilt ?? target;
3891
+ }
3892
+ }
3590
3893
  mutatedBlocks = true;
3591
3894
  blockChanged = true;
3592
3895
  }
3593
- if (propsChanged || blockChanged) {
3896
+ if (rebuiltFromFinalizedBlock) {
3897
+ return;
3898
+ }
3899
+ if (propsChanged || blockChanged || typeChanged) {
3900
+ if (semanticUpdate && !blockEpochAdvanced) {
3901
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3902
+ blockEpochAdvanced = true;
3903
+ }
3594
3904
  target.version++;
3595
3905
  touched.add(target.id);
3596
3906
  let parentForNormalization;
@@ -3633,13 +3943,22 @@ function createRendererStore(initialBlocks = []) {
3633
3943
  const applyPatch = (patch) => {
3634
3944
  switch (patch.op) {
3635
3945
  case "insertChild": {
3946
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3636
3947
  const parent = resolveParent(nodes, patch.at);
3637
- const inserted = insertSnapshotAt(nodes, parent, patch.index, patch.node, touched);
3948
+ const inserted = insertSnapshotAt(
3949
+ nodes,
3950
+ parent,
3951
+ patch.index,
3952
+ patch.node,
3953
+ touched,
3954
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
3955
+ );
3638
3956
  if (inserted) {
3639
3957
  for (const id of inserted.insertedIds) {
3640
3958
  touched.add(id);
3641
3959
  }
3642
3960
  mutatedBlocks = true;
3961
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3643
3962
  if (isListRelated(inserted.record.type) || isListRelated(parent?.type)) {
3644
3963
  markListDepthDirty(inserted.record.id);
3645
3964
  if (parent) {
@@ -3659,6 +3978,7 @@ function createRendererStore(initialBlocks = []) {
3659
3978
  break;
3660
3979
  }
3661
3980
  case "deleteChild": {
3981
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3662
3982
  const parent = resolveParent(nodes, patch.at);
3663
3983
  if (!parent) break;
3664
3984
  if (patch.index < 0 || patch.index >= parent.children.length) break;
@@ -3670,6 +3990,7 @@ function createRendererStore(initialBlocks = []) {
3670
3990
  touched.add(parent.id);
3671
3991
  ensureUniqueChildren(parent, touched);
3672
3992
  mutatedBlocks = true;
3993
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3673
3994
  if (isListRelated(parent.type) || isListRelated(removedNode?.type)) {
3674
3995
  markListDepthDirty(parent.id);
3675
3996
  }
@@ -3686,11 +4007,20 @@ function createRendererStore(initialBlocks = []) {
3686
4007
  break;
3687
4008
  }
3688
4009
  case "replaceChild": {
4010
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3689
4011
  const parent = resolveParent(nodes, patch.at);
3690
4012
  if (!parent) break;
3691
- replaceSnapshotAt(nodes, parent, patch.index, patch.node, touched);
4013
+ replaceSnapshotAt(
4014
+ nodes,
4015
+ parent,
4016
+ patch.index,
4017
+ patch.node,
4018
+ touched,
4019
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
4020
+ );
3692
4021
  touched.add(parent.id);
3693
4022
  mutatedBlocks = true;
4023
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3694
4024
  if (isListRelated(parent.type)) {
3695
4025
  markListDepthDirty(parent.id);
3696
4026
  }
@@ -3704,18 +4034,33 @@ function createRendererStore(initialBlocks = []) {
3704
4034
  break;
3705
4035
  }
3706
4036
  case "setProps": {
3707
- applyPropsUpdate(patch.at, patch.props);
4037
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
4038
+ applyPropsUpdate(patch.at, patch.props, readPatchParseEpoch(patch), readPatchTx(patch), getPatchKind3(patch) === "semantic");
3708
4039
  break;
3709
4040
  }
3710
4041
  case "setPropsBatch": {
3711
4042
  const entries2 = Array.isArray(patch.entries) ? patch.entries : [];
3712
4043
  for (const entry of entries2) {
3713
4044
  if (!entry || !entry.at) continue;
3714
- applyPropsUpdate(entry.at, entry.props);
4045
+ const entryPatch = {
4046
+ op: "setProps",
4047
+ at: entry.at,
4048
+ props: entry.props,
4049
+ meta: entry.meta ?? patch.meta
4050
+ };
4051
+ if (rejectStaleEpochPatch(entryPatch, entry.at)) continue;
4052
+ applyPropsUpdate(
4053
+ entry.at,
4054
+ entry.props,
4055
+ readPatchParseEpoch(entryPatch),
4056
+ readPatchTx(entryPatch),
4057
+ getPatchKind3(entryPatch) === "semantic"
4058
+ );
3715
4059
  }
3716
4060
  break;
3717
4061
  }
3718
4062
  case "finalize": {
4063
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3719
4064
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
3720
4065
  if (!target || !target.block) break;
3721
4066
  if (!target.block.isFinalized) {
@@ -3724,6 +4069,17 @@ function createRendererStore(initialBlocks = []) {
3724
4069
  target.version++;
3725
4070
  touched.add(target.id);
3726
4071
  mutatedBlocks = true;
4072
+ if (!patch.at.nodeId && target.id === patch.at.blockId && target.type !== "code" && target.parentId) {
4073
+ const parent = nodes.get(target.parentId);
4074
+ const index = parent ? parent.children.indexOf(target.id) : -1;
4075
+ if (parent && index !== -1) {
4076
+ const snapshot = createBlockSnapshot(cloneBlock(target.block));
4077
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
4078
+ if (isListRelated(target.type) || isListRelated(snapshot.type)) {
4079
+ markListDepthDirty(target.id);
4080
+ }
4081
+ }
4082
+ }
3727
4083
  }
3728
4084
  if (isListRelated(target.type)) {
3729
4085
  markListDepthDirty(target.id);
@@ -3737,6 +4093,7 @@ function createRendererStore(initialBlocks = []) {
3737
4093
  break;
3738
4094
  }
3739
4095
  case "reorder": {
4096
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3740
4097
  const parent = resolveParent(nodes, patch.at);
3741
4098
  if (!parent) break;
3742
4099
  const length = parent.children.length;
@@ -3751,6 +4108,9 @@ function createRendererStore(initialBlocks = []) {
3751
4108
  parent.children.splice(to, 0, ...moved);
3752
4109
  parent.version++;
3753
4110
  touched.add(parent.id);
4111
+ if (getPatchKind3(patch) === "semantic") {
4112
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
4113
+ }
3754
4114
  if (isListRelated(parent.type)) {
3755
4115
  markListDepthDirty(parent.id);
3756
4116
  }
@@ -3766,15 +4126,31 @@ function createRendererStore(initialBlocks = []) {
3766
4126
  break;
3767
4127
  }
3768
4128
  case "appendLines": {
4129
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3769
4130
  const parent = resolveParent(nodes, patch.at);
3770
4131
  if (!parent) break;
3771
- const startIndex = Math.max(0, Math.min(patch.startIndex, parent.children.length));
4132
+ if (parent.type !== "code") {
4133
+ debugCounters.appendLineGuardRejected += 1;
4134
+ appendDiagnostic(`appendLines target not code:${parent.id}:${parent.type}`);
4135
+ break;
4136
+ }
4137
+ const requestedStartIndex = Math.max(0, patch.startIndex);
4138
+ const startIndex = Math.max(0, Math.min(requestedStartIndex, parent.children.length));
3772
4139
  const lineCount = patch.lines?.length ?? 0;
3773
4140
  if (lineCount === 0) break;
4141
+ const prefixMatches = parent.children.every((childId, index) => childId === expectedCodeLineId(parent.id, index));
4142
+ const isStrictTailAppend = requestedStartIndex === parent.children.length;
4143
+ if (!isStrictTailAppend || !prefixMatches) {
4144
+ debugCounters.appendLineGuardRejected += 1;
4145
+ appendDiagnostic(
4146
+ `appendLines guard rejected:${parent.id}:requestedStart=${requestedStartIndex}:childCount=${parent.children.length}:prefixMatches=${prefixMatches}`
4147
+ );
4148
+ break;
4149
+ }
3774
4150
  const originalChildren = parent.children.slice();
3775
4151
  const insertedIds = [];
3776
4152
  const insertedSet = /* @__PURE__ */ new Set();
3777
- const isTrailingAppend = startIndex >= originalChildren.length;
4153
+ const isTrailingAppend = true;
3778
4154
  let parentChildrenChanged = false;
3779
4155
  let codeLinesMutated = false;
3780
4156
  for (let offset = 0; offset < lineCount; offset++) {
@@ -3816,7 +4192,8 @@ function createRendererStore(initialBlocks = []) {
3816
4192
  parentId: parent.id,
3817
4193
  children: [],
3818
4194
  props: nextLineProps,
3819
- version: 0
4195
+ version: 0,
4196
+ blockEpoch: parent.blockEpoch
3820
4197
  };
3821
4198
  nodes.set(lineId, record);
3822
4199
  touched.add(lineId);
@@ -3866,6 +4243,7 @@ function createRendererStore(initialBlocks = []) {
3866
4243
  break;
3867
4244
  }
3868
4245
  case "setHTML": {
4246
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3869
4247
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
3870
4248
  if (!target) break;
3871
4249
  const sanitized = patch.sanitized ? patch.html : sanitizeHTML2(patch.html);
@@ -3908,6 +4286,30 @@ function createRendererStore(initialBlocks = []) {
3908
4286
  applyPatch(patch);
3909
4287
  }
3910
4288
  }
4289
+ const finalizedTableRoots = /* @__PURE__ */ new Set();
4290
+ for (const touchedId of touched) {
4291
+ let cursor = nodes.get(touchedId);
4292
+ while (cursor && cursor.id !== PATCH_ROOT_ID) {
4293
+ if (cursor.type === "table" && cursor.block?.isFinalized && cursor.parentId) {
4294
+ finalizedTableRoots.add(cursor.id);
4295
+ break;
4296
+ }
4297
+ cursor = cursor.parentId ? nodes.get(cursor.parentId) : void 0;
4298
+ }
4299
+ }
4300
+ if (finalizedTableRoots.size > 0) {
4301
+ for (const tableId of finalizedTableRoots) {
4302
+ const tableNode = nodes.get(tableId);
4303
+ if (!tableNode || !tableNode.block || !tableNode.parentId) continue;
4304
+ const parent = nodes.get(tableNode.parentId);
4305
+ if (!parent) continue;
4306
+ const index = parent.children.indexOf(tableNode.id);
4307
+ if (index === -1) continue;
4308
+ const snapshot = createBlockSnapshot(cloneBlock(tableNode.block));
4309
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, tableNode.blockEpoch);
4310
+ mutatedBlocks = true;
4311
+ }
4312
+ }
3911
4313
  if (mutatedBlocks) {
3912
4314
  blocksDirty = true;
3913
4315
  }
@@ -3928,6 +4330,8 @@ function createRendererStore(initialBlocks = []) {
3928
4330
  normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, PATCH_ROOT_ID);
3929
4331
  }
3930
4332
  }
4333
+ invariantViolations = [...collectStoreInvariantViolations(nodes), ...transientDiagnostics];
4334
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
3931
4335
  if (touched.size > 0 || mutatedBlocks) {
3932
4336
  version++;
3933
4337
  scheduleNotify();
@@ -3976,6 +4380,7 @@ function createRendererStore(initialBlocks = []) {
3976
4380
  if (!record) {
3977
4381
  return EMPTY_NODE_SNAPSHOT;
3978
4382
  }
4383
+ const nodeSnapshotCache = getNodeSnapshotCacheForMap(nodes);
3979
4384
  const cached = nodeSnapshotCache.get(id);
3980
4385
  if (cached && cached.version === record.version && cached.node === record) {
3981
4386
  return cached;
@@ -3995,6 +4400,7 @@ function createRendererStore(initialBlocks = []) {
3995
4400
  if (!record) {
3996
4401
  return EMPTY_CHILDREN_SNAPSHOT;
3997
4402
  }
4403
+ const childrenSnapshotCache = getChildrenSnapshotCacheForMap(nodes);
3998
4404
  const cached = childrenSnapshotCache.get(id);
3999
4405
  if (cached && cached.version === record.version) {
4000
4406
  return cached;
@@ -4004,6 +4410,12 @@ function createRendererStore(initialBlocks = []) {
4004
4410
  pruneCache(childrenSnapshotCache, CHILDREN_SNAPSHOT_CACHE_LIMIT, CHILDREN_SNAPSHOT_CACHE_BUFFER);
4005
4411
  return snapshot;
4006
4412
  },
4413
+ getInvariantViolations() {
4414
+ return invariantViolations.slice();
4415
+ },
4416
+ getDebugCounters() {
4417
+ return { ...debugCounters };
4418
+ },
4007
4419
  getLastCoalescingMetrics() {
4008
4420
  return lastCoalescingMetrics;
4009
4421
  },
@@ -4046,7 +4458,7 @@ function useRendererChildren(store, id) {
4046
4458
  }
4047
4459
 
4048
4460
  // src/renderer/node-views.tsx
4049
- import { stripCodeFence } from "@stream-mdx/core";
4461
+ import { getDefaultCodeWrapperAttributes as getDefaultCodeWrapperAttributes2, stripCodeFence } from "@stream-mdx/core";
4050
4462
  import React7 from "react";
4051
4463
 
4052
4464
  // src/renderer/deferred-render-context.tsx
@@ -4488,8 +4900,10 @@ var ListBlockView = React7.memo(
4488
4900
  isFinalized: block?.isFinalized ?? false
4489
4901
  });
4490
4902
  }, [listDebugEnabled, blockId, depth, ordered, childIds, listItemIds, store, block?.isFinalized]);
4491
- const listStyle = React7.useMemo(() => {
4492
- if (!ordered || listItemIds.length === 0) return void 0;
4903
+ const { listStyle, markerDigits } = React7.useMemo(() => {
4904
+ if (!ordered || listItemIds.length === 0) {
4905
+ return { listStyle: void 0, markerDigits: 1 };
4906
+ }
4493
4907
  let maxDigits = 1;
4494
4908
  listItemIds.forEach((childId, index) => {
4495
4909
  const childNode = store.getNode(childId);
@@ -4501,9 +4915,14 @@ var ListBlockView = React7.memo(
4501
4915
  const digits = digitMatch ? digitMatch.length : text.length;
4502
4916
  if (digits > maxDigits) maxDigits = digits;
4503
4917
  });
4504
- const baseIndent = depth === 0 ? "2.5rem" : depth === 1 ? "2rem" : "1.5rem";
4918
+ const baseIndent = depth === 0 ? "2rem" : depth === 1 ? "1.75rem" : "1.5rem";
4919
+ const extraDigits = Math.max(0, maxDigits - 1);
4505
4920
  return {
4506
- ["--list-indent"]: `calc(${baseIndent} + ${maxDigits}ch)`
4921
+ markerDigits: maxDigits,
4922
+ listStyle: {
4923
+ ["--list-indent"]: extraDigits > 0 ? `calc(${baseIndent} + ${extraDigits}ch)` : baseIndent,
4924
+ ["--list-marker-digits"]: String(maxDigits)
4925
+ }
4507
4926
  };
4508
4927
  }, [ordered, listItemIds, store, depth]);
4509
4928
  if (listItemIds.length === 0) return null;
@@ -4512,6 +4931,7 @@ var ListBlockView = React7.memo(
4512
4931
  {
4513
4932
  className: `markdown-list ${ordered ? "ordered" : "unordered"}`,
4514
4933
  "data-list-depth": depth,
4934
+ "data-marker-digits": ordered ? markerDigits : void 0,
4515
4935
  "data-list-id": listDebugEnabled ? blockId : void 0,
4516
4936
  style: listStyle,
4517
4937
  children: listItemIds.map((childId, index) => /* @__PURE__ */ jsx2(
@@ -4693,6 +5113,7 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4693
5113
  deferredConfig ? { ...deferredConfig, enabled: true } : { enabled: false }
4694
5114
  );
4695
5115
  if (!node || !node.block) return null;
5116
+ const blockIsFinalized = node.block.isFinalized;
4696
5117
  const nodeVersion = node.version;
4697
5118
  const lines = React7.useMemo(() => {
4698
5119
  const seenIds = /* @__PURE__ */ new Set();
@@ -4723,13 +5144,25 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4723
5144
  const lang = typeof node.props?.lang === "string" ? node.props?.lang : node.block.payload.meta?.lang;
4724
5145
  const preAttrs = node.props?.preAttrs ?? void 0;
4725
5146
  const codeAttrs = node.props?.codeAttrs ?? void 0;
5147
+ const blockMeta = node.block.payload.meta ?? void 0;
5148
+ const lazyEnabled = Boolean(blockMeta?.lazyTokenization);
5149
+ const lazyTokenizedUntil = typeof blockMeta?.lazyTokenizedUntil === "number" ? blockMeta.lazyTokenizedUntil : 0;
5150
+ const lazyHighlightedLines = Array.isArray(blockMeta?.highlightedLines) ? blockMeta.highlightedLines.length : 0;
5151
+ const lazyTokenLines = Array.isArray(blockMeta?.tokenLines) ? blockMeta.tokenLines.length : 0;
5152
+ const lazySourceTextLineCount = React7.useMemo(() => {
5153
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
5154
+ const metaCode = typeof blockMeta?.code === "string" ? blockMeta.code : null;
5155
+ const sourceText = resolveCodeSourceText(raw, metaCode);
5156
+ if (sourceText.length === 0) return 0;
5157
+ return sourceText.split("\n").length;
5158
+ }, [blockMeta, node.block?.payload.raw]);
5159
+ const totalLazyLineCount = Math.max(lines.length, lazyHighlightedLines, lazyTokenLines, lazySourceTextLineCount);
4726
5160
  const virtualizationConfig = DEFAULT_VIRTUALIZED_CODE_CONFIG;
4727
5161
  const virtualizationDisabled = typeof process !== "undefined" && typeof process.env === "object" && process.env.STREAM_MDX_DISABLE_VIRTUALIZED_CODE === "true";
4728
- const shouldVirtualize = !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
5162
+ const canVirtualize = node.block.isFinalized && !lazyEnabled;
5163
+ const shouldVirtualize = canVirtualize && !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
4729
5164
  const virtualization = useVirtualizedCode(lines, shouldVirtualize ? virtualizationConfig : { ...virtualizationConfig, enabled: false });
4730
5165
  const highlightRequester = useCodeHighlightRequester();
4731
- const lazyEnabled = Boolean(node.block.payload.meta?.lazyTokenization);
4732
- const lazyTokenizedUntil = typeof node.block.payload.meta?.lazyTokenizedUntil === "number" ? node.block.payload.meta.lazyTokenizedUntil : 0;
4733
5166
  const lastRangeRef = React7.useRef(null);
4734
5167
  const rafRef = React7.useRef(null);
4735
5168
  const idleRef = React7.useRef(null);
@@ -4810,29 +5243,111 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4810
5243
  virtualization.window.startIndex,
4811
5244
  virtualization.window.endIndex
4812
5245
  ]);
4813
- const highlightedHtml = React7.useMemo(() => {
4814
- if (!shouldVirtualize && node.block?.payload.highlightedHtml) {
4815
- return node.block.payload.highlightedHtml;
5246
+ React7.useEffect(() => {
5247
+ if (shouldVirtualize || !blockIsFinalized || !lazyEnabled || !highlightRequester) return;
5248
+ if (totalLazyLineCount <= 0 || lazyTokenizedUntil >= totalLazyLineCount) return;
5249
+ const nextRange = {
5250
+ visibleStart: 0,
5251
+ visibleEnd: totalLazyLineCount,
5252
+ prefetchStart: 0,
5253
+ prefetchEnd: totalLazyLineCount,
5254
+ tokenizedUntil: lazyTokenizedUntil
5255
+ };
5256
+ const last = lastRangeRef.current;
5257
+ if (last && last.visibleStart === nextRange.visibleStart && last.visibleEnd === nextRange.visibleEnd && last.prefetchStart === nextRange.prefetchStart && last.prefetchEnd === nextRange.prefetchEnd && last.tokenizedUntil === nextRange.tokenizedUntil) {
5258
+ return;
5259
+ }
5260
+ lastRangeRef.current = nextRange;
5261
+ if (rafRef.current !== null && typeof cancelAnimationFrame === "function") {
5262
+ cancelAnimationFrame(rafRef.current);
5263
+ }
5264
+ const requestFullHighlight = () => {
5265
+ highlightRequester({
5266
+ blockId,
5267
+ startLine: 0,
5268
+ endLine: totalLazyLineCount,
5269
+ priority: "visible",
5270
+ reason: "finalize-full"
5271
+ });
5272
+ rafRef.current = null;
5273
+ };
5274
+ if (typeof requestAnimationFrame === "function") {
5275
+ rafRef.current = requestAnimationFrame(requestFullHighlight);
5276
+ } else {
5277
+ requestFullHighlight();
4816
5278
  }
5279
+ }, [blockId, blockIsFinalized, highlightRequester, lazyEnabled, lazyTokenizedUntil, shouldVirtualize, totalLazyLineCount]);
5280
+ const highlightedHtml = React7.useMemo(() => {
5281
+ const blockHtml = node.block?.payload.highlightedHtml ?? "";
5282
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
5283
+ const metaCode = typeof node.block?.payload.meta?.code === "string" ? (node.block?.payload.meta).code : null;
5284
+ const sourceText = resolveCodeSourceText(raw, metaCode);
5285
+ const sourceEndsWithNewline = sourceText.endsWith("\n");
5286
+ const sourceLines = sourceText.length > 0 ? sourceText.split("\n") : [];
5287
+ const rawFallbackLines = buildCodeFallbackLines(raw);
5288
+ const deterministicWrapperAttrs = getDefaultCodeWrapperAttributes2(lang);
5289
+ const composedPreAttrs = deterministicWrapperAttrs.preAttrs;
5290
+ const composedCodeAttrs = deterministicWrapperAttrs.codeAttrs;
5291
+ const shouldAppendTerminalNewline = (renderedLines) => {
5292
+ if (!sourceEndsWithNewline || renderedLines.length === 0 || lines.length === 0) {
5293
+ return false;
5294
+ }
5295
+ const lastRendered = renderedLines[renderedLines.length - 1];
5296
+ const lastKnown = lines[lines.length - 1];
5297
+ if (!lastRendered || !lastKnown || lastRendered.index !== lastKnown.index || lastRendered.text === "") {
5298
+ return false;
5299
+ }
5300
+ const expectedRenderedLineCount = Math.max(0, sourceLines.length - 1);
5301
+ const expectedLastRenderedLine = sourceLines.length >= 2 ? sourceLines[sourceLines.length - 2] ?? "" : "";
5302
+ return renderedLines.length === expectedRenderedLineCount && lastRendered.text === expectedLastRenderedLine;
5303
+ };
4817
5304
  if (lines.length > 0) {
4818
5305
  const hasLineHtml = lines.some((line) => typeof line.html === "string" && line.html.length > 0);
4819
- if (!hasLineHtml && node.block?.payload.highlightedHtml) {
4820
- return node.block.payload.highlightedHtml;
5306
+ if (!hasLineHtml && blockHtml) {
5307
+ return blockHtml;
4821
5308
  }
4822
5309
  if (shouldVirtualize) {
4823
- return composeHighlightedHtml(virtualization.window.visibleLines, preAttrs, codeAttrs);
5310
+ return composeHighlightedHtml(
5311
+ virtualization.window.visibleLines,
5312
+ composedPreAttrs,
5313
+ composedCodeAttrs,
5314
+ shouldAppendTerminalNewline(virtualization.window.visibleLines)
5315
+ );
4824
5316
  }
4825
- return composeHighlightedHtml(lines, preAttrs, codeAttrs);
5317
+ return composeHighlightedHtml(lines, composedPreAttrs, composedCodeAttrs, shouldAppendTerminalNewline(lines));
5318
+ }
5319
+ if (!shouldVirtualize && blockHtml) {
5320
+ const hasHighlightedLines = blockHtml.includes('class="line"');
5321
+ if (!hasHighlightedLines && rawFallbackLines.length > 0) {
5322
+ return composeHighlightedHtml(
5323
+ rawFallbackLines,
5324
+ composedPreAttrs,
5325
+ composedCodeAttrs,
5326
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5327
+ );
5328
+ }
5329
+ return blockHtml;
4826
5330
  }
4827
- const blockHtml = node.block?.payload.highlightedHtml ?? "";
4828
5331
  if (blockHtml?.trim().match(/^```[\w-]*\s*$/)) {
4829
- const raw = node.block?.payload.raw ?? "";
4830
- if (raw.trim().length > 0) {
4831
- return composeHighlightedHtml([{ index: 0, text: raw, html: null, id: "temp" }], preAttrs, codeAttrs);
5332
+ if (rawFallbackLines.length > 0) {
5333
+ return composeHighlightedHtml(
5334
+ rawFallbackLines,
5335
+ composedPreAttrs,
5336
+ composedCodeAttrs,
5337
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5338
+ );
4832
5339
  }
4833
5340
  }
5341
+ if (!blockHtml && rawFallbackLines.length > 0) {
5342
+ return composeHighlightedHtml(
5343
+ rawFallbackLines,
5344
+ composedPreAttrs,
5345
+ composedCodeAttrs,
5346
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5347
+ );
5348
+ }
4834
5349
  return blockHtml;
4835
- }, [shouldVirtualize, node.block?.payload.highlightedHtml, lines, preAttrs, codeAttrs, virtualization.window.visibleLines, node.block?.payload.raw]);
5350
+ }, [blockIsFinalized, shouldVirtualize, node.block?.payload.highlightedHtml, node.block?.payload.meta, lines, lang, virtualization.window.visibleLines, node.block?.payload.raw]);
4836
5351
  const rendered = /* @__PURE__ */ jsx2(
4837
5352
  CodeComponent,
4838
5353
  {
@@ -4844,13 +5359,19 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4844
5359
  codeAttrs
4845
5360
  }
4846
5361
  );
5362
+ const effectiveLineCount = React7.useMemo(() => {
5363
+ if (lines.length > 0) {
5364
+ return lines.length;
5365
+ }
5366
+ return 1;
5367
+ }, [lines.length]);
4847
5368
  const codeFrameClass = "not-prose flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
4848
5369
  const codeMetricsAttrs = {
4849
5370
  "data-code-block": "true",
4850
5371
  "data-block-id": blockId,
4851
5372
  "data-code-virtualized": shouldVirtualize ? "true" : "false",
4852
- "data-code-total-lines": String(lines.length),
4853
- "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : lines.length),
5373
+ "data-code-total-lines": String(effectiveLineCount),
5374
+ "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : effectiveLineCount),
4854
5375
  "data-code-window-size": String(virtualization.config.windowSize)
4855
5376
  };
4856
5377
  const codeView = shouldVirtualize ? (() => {
@@ -4896,21 +5417,152 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4896
5417
  return codeView;
4897
5418
  });
4898
5419
  CodeBlockView.displayName = "CodeBlockView";
4899
- function composeHighlightedHtml(lines, preAttrs, codeAttrs) {
5420
+ function composeHighlightedHtml(lines, preAttrs, codeAttrs, appendTerminalNewline = false) {
4900
5421
  const lineMarkup = lines.map((line) => {
4901
- const content = line.html ?? escapeHtml3(line.text);
5422
+ const content = line.html ?? renderTokenLineHtml(line.tokens) ?? escapeHtml3(line.text);
4902
5423
  const dataLine = Number.isFinite(line.index) ? ` data-line="${line.index + 1}"` : "";
4903
5424
  return `<span class="line"${dataLine}>${content}</span>`;
4904
5425
  }).join("\n");
4905
5426
  const preAttr = attrsToString(preAttrs);
4906
5427
  const codeAttr = attrsToString(codeAttrs);
4907
- return `<pre${preAttr}><code${codeAttr}>${lineMarkup}
4908
- </code></pre>`;
5428
+ return `<pre${preAttr}><code${codeAttr}>${lineMarkup}${appendTerminalNewline ? "\n" : ""}</code></pre>`;
5429
+ }
5430
+ var TOKEN_FONT_STYLE_ITALIC = 1;
5431
+ var TOKEN_FONT_STYLE_BOLD = 2;
5432
+ var TOKEN_FONT_STYLE_UNDERLINE = 4;
5433
+ var TOKEN_FONT_STYLE_STRIKETHROUGH = 8;
5434
+ function renderTokenLineHtml(tokens) {
5435
+ if (!tokens || !Array.isArray(tokens.spans)) {
5436
+ return null;
5437
+ }
5438
+ return mergeRenderableTokenSpans(tokens.spans).map((span) => renderTokenSpanHtml(span)).join("");
5439
+ }
5440
+ function renderTokenSpanHtml(span) {
5441
+ const dark = span.v?.dark;
5442
+ const light = span.v?.light;
5443
+ const single = span.s;
5444
+ const styles = [];
5445
+ if (dark?.fg) {
5446
+ styles.push(`--shiki-dark:${dark.fg}`);
5447
+ }
5448
+ if (light?.fg) {
5449
+ styles.push(`--shiki-light:${light.fg}`);
5450
+ }
5451
+ if (!light?.fg && !dark?.fg && single?.fg) {
5452
+ styles.push(`color:${single.fg}`);
5453
+ }
5454
+ const fontStyle = light?.fs ?? dark?.fs ?? single?.fs;
5455
+ if (typeof fontStyle === "number" && fontStyle > 0) {
5456
+ if (fontStyle & TOKEN_FONT_STYLE_ITALIC) {
5457
+ styles.push("font-style: italic");
5458
+ }
5459
+ if (fontStyle & TOKEN_FONT_STYLE_BOLD) {
5460
+ styles.push("font-weight: bold");
5461
+ }
5462
+ const decorations = [];
5463
+ if (fontStyle & TOKEN_FONT_STYLE_UNDERLINE) {
5464
+ decorations.push("underline");
5465
+ }
5466
+ if (fontStyle & TOKEN_FONT_STYLE_STRIKETHROUGH) {
5467
+ decorations.push("line-through");
5468
+ }
5469
+ if (decorations.length > 0) {
5470
+ styles.push(`text-decoration: ${decorations.join(" ")}`);
5471
+ }
5472
+ }
5473
+ const content = escapeHtml3(span.t ?? "");
5474
+ if (styles.length === 0) {
5475
+ return content;
5476
+ }
5477
+ return `<span style="${styles.join(";")}">${content}</span>`;
5478
+ }
5479
+ function mergeRenderableTokenSpans(spans) {
5480
+ if (spans.length <= 1) {
5481
+ return spans.slice();
5482
+ }
5483
+ const merged = [];
5484
+ let pendingWhitespace = "";
5485
+ for (const span of spans) {
5486
+ const text = typeof span.t === "string" ? span.t : "";
5487
+ if (/^[\t ]+$/.test(text)) {
5488
+ pendingWhitespace += text;
5489
+ continue;
5490
+ }
5491
+ const nextSpan = pendingWhitespace ? { ...span, t: `${pendingWhitespace}${text}` } : span;
5492
+ pendingWhitespace = "";
5493
+ const previous = merged[merged.length - 1];
5494
+ if (previous && sameTokenSpanStyle(previous, nextSpan)) {
5495
+ previous.t += nextSpan.t;
5496
+ } else {
5497
+ merged.push({ ...nextSpan });
5498
+ }
5499
+ }
5500
+ if (pendingWhitespace) {
5501
+ const previous = merged[merged.length - 1];
5502
+ if (previous) {
5503
+ previous.t += pendingWhitespace;
5504
+ } else {
5505
+ merged.push({ t: pendingWhitespace });
5506
+ }
5507
+ }
5508
+ return merged;
5509
+ }
5510
+ function sameTokenSpanStyle(a, b) {
5511
+ const variantKeys = /* @__PURE__ */ new Set([
5512
+ ...Object.keys(a.v ?? {}),
5513
+ ...Object.keys(b.v ?? {})
5514
+ ]);
5515
+ for (const key of variantKeys) {
5516
+ const aStyle = a.v?.[key];
5517
+ const bStyle = b.v?.[key];
5518
+ if ((aStyle?.fg ?? null) !== (bStyle?.fg ?? null)) return false;
5519
+ if ((aStyle?.bg ?? null) !== (bStyle?.bg ?? null)) return false;
5520
+ if ((aStyle?.fs ?? null) !== (bStyle?.fs ?? null)) return false;
5521
+ }
5522
+ if ((a.s?.fg ?? null) !== (b.s?.fg ?? null)) return false;
5523
+ if ((a.s?.bg ?? null) !== (b.s?.bg ?? null)) return false;
5524
+ if ((a.s?.fs ?? null) !== (b.s?.fs ?? null)) return false;
5525
+ return true;
5526
+ }
5527
+ function resolveCodeSourceText(raw, metaCode) {
5528
+ if (metaCode !== null) {
5529
+ return metaCode.replace(/\r\n?/g, "\n");
5530
+ }
5531
+ if (!raw) return "";
5532
+ const normalized = raw.replace(/\r\n?/g, "\n");
5533
+ const fenced = stripCodeFence(normalized);
5534
+ return fenced.hadFence ? fenced.code : normalized;
4909
5535
  }
4910
5536
  function attrsToString(attrs) {
4911
5537
  if (!attrs) return "";
4912
5538
  return Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
4913
5539
  }
5540
+ function buildCodeFallbackLines(raw) {
5541
+ const normalized = normalizeCodeFallbackRaw(raw);
5542
+ if (normalized.length === 0) {
5543
+ if (/^\s*```[^\n]*\n?\s*$/.test(raw)) {
5544
+ return [{ id: "temp-0", index: 0, text: "", html: null }];
5545
+ }
5546
+ return [];
5547
+ }
5548
+ return normalized.split("\n").map((line, index) => ({
5549
+ id: `temp-${index}`,
5550
+ index,
5551
+ text: line,
5552
+ html: null
5553
+ }));
5554
+ }
5555
+ function normalizeCodeFallbackRaw(raw) {
5556
+ if (!raw) return "";
5557
+ const normalized = raw.replace(/\r\n?/g, "\n");
5558
+ const fenced = stripCodeFence(normalized);
5559
+ if (!fenced.hadFence) {
5560
+ return normalized.replace(/\n+$/, "");
5561
+ }
5562
+ let code = fenced.code;
5563
+ code = code.replace(/\n+$/, "");
5564
+ return code;
5565
+ }
4914
5566
  function escapeHtml3(value) {
4915
5567
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4916
5568
  }
@@ -5034,7 +5686,9 @@ var MarkdownRenderer = class {
5034
5686
  lowPriorityFrameBudgetMs: this.config.performance?.lowPriorityFrameBudgetMs,
5035
5687
  urgentQueueThreshold: this.config.performance?.urgentQueueThreshold,
5036
5688
  batch: this.config.performance?.batch,
5037
- historyLimit: this.config.performance?.historyLimit
5689
+ historyLimit: this.config.performance?.historyLimit,
5690
+ startupMicrotaskFlushes: this.config.performance?.startupMicrotaskFlushes,
5691
+ adaptiveBudgeting: this.config.performance?.adaptiveBudgeting
5038
5692
  }
5039
5693
  });
5040
5694
  }
@@ -5228,18 +5882,8 @@ var MarkdownRenderer = class {
5228
5882
  const receivedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
5229
5883
  const batches = splitPatchBatch(message.patches);
5230
5884
  batches.forEach((batch, index) => {
5231
- const hasStructural = batch.some((patch) => {
5232
- switch (patch.op) {
5233
- case "insertChild":
5234
- case "replaceChild":
5235
- case "deleteChild":
5236
- case "reorder":
5237
- return true;
5238
- default:
5239
- return false;
5240
- }
5241
- });
5242
- const priority = hasStructural ? "high" : batch.some(isHeavyPatch) ? "low" : "high";
5885
+ const hasSemantic = batch.some((patch) => getPatchKind(patch) === "semantic");
5886
+ const priority = hasSemantic ? "high" : batch.some(isHeavyPatch) ? "low" : "high";
5243
5887
  this.patchScheduler.enqueue({
5244
5888
  patches: batch,
5245
5889
  meta: {
@@ -5384,13 +6028,14 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5384
6028
  const mode = options?.mode ?? "server";
5385
6029
  const [compiledMap, setCompiledMap] = useState4(/* @__PURE__ */ new Map());
5386
6030
  const blockSignatureRef = useRef3(/* @__PURE__ */ new Map());
6031
+ const failedSignatureRef = useRef3(/* @__PURE__ */ new Map());
5387
6032
  const applyCompiledRef = useCallback3(
5388
6033
  (block, compiledId) => {
5389
6034
  if (mode === "worker") {
5390
6035
  return;
5391
6036
  }
5392
6037
  if (options?.workerClient) {
5393
- options.workerClient.setMdxCompiled(block.id, compiledId);
6038
+ options.workerClient.setMdxCompiled(block.id, compiledId, block.payload.raw ?? "");
5394
6039
  return;
5395
6040
  }
5396
6041
  const store = options?.store;
@@ -5401,6 +6046,9 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5401
6046
  const index = currentBlocks.findIndex((candidate) => candidate.id === block.id);
5402
6047
  if (index === -1) return;
5403
6048
  const current = currentBlocks[index];
6049
+ if ((current.payload.raw ?? "") !== (block.payload.raw ?? "")) {
6050
+ return;
6051
+ }
5404
6052
  if (current.payload.compiledMdxRef?.id === compiledId) {
5405
6053
  return;
5406
6054
  }
@@ -5430,7 +6078,13 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5430
6078
  if (mode === "worker") {
5431
6079
  return;
5432
6080
  }
5433
- const targets = blocks.filter((b) => b.type === "mdx" && b.isFinalized && !b.payload.compiledMdxRef && !compiledMap.has(b.id));
6081
+ const targets = blocks.filter((b) => {
6082
+ if (b.type !== "mdx" || !b.isFinalized || b.payload.compiledMdxRef || compiledMap.has(b.id)) {
6083
+ return false;
6084
+ }
6085
+ const raw = b.payload.raw ?? "";
6086
+ return failedSignatureRef.current.get(b.id) !== raw;
6087
+ });
5434
6088
  if (targets.length === 0) return;
5435
6089
  let cancelled = false;
5436
6090
  (async () => {
@@ -5449,22 +6103,29 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5449
6103
  next.set(block.id, compiled.id);
5450
6104
  return next;
5451
6105
  });
6106
+ failedSignatureRef.current.delete(block.id);
5452
6107
  applyCompiledRef(block, compiled.id);
5453
6108
  } catch (e) {
5454
6109
  console.warn("MDX compile failed for block", block.id, e);
6110
+ failedSignatureRef.current.set(block.id, block.payload.raw ?? "");
5455
6111
  if (options?.workerClient) {
5456
- options.workerClient.setMdxError(block.id, e instanceof Error ? e.message : String(e));
6112
+ options.workerClient.setMdxError(block.id, e instanceof Error ? e.message : String(e), block.payload.raw ?? "");
5457
6113
  } else if (options?.store) {
6114
+ const currentBlocks = options.store.getBlocks();
6115
+ const current = currentBlocks.find((candidate) => candidate.id === block.id);
6116
+ if (!current || (current.payload.raw ?? "") !== (block.payload.raw ?? "")) {
6117
+ continue;
6118
+ }
5458
6119
  options.store.applyPatches([
5459
6120
  {
5460
6121
  op: "setProps",
5461
6122
  at: { blockId: block.id, nodeId: block.id },
5462
6123
  props: {
5463
6124
  block: {
5464
- ...block,
6125
+ ...current,
5465
6126
  payload: {
5466
- ...block.payload,
5467
- meta: { ...block.payload.meta ?? {}, mdxStatus: "error", mdxError: e instanceof Error ? e.message : String(e) }
6127
+ ...current.payload,
6128
+ meta: { ...current.payload.meta ?? {}, mdxStatus: "error", mdxError: e instanceof Error ? e.message : String(e) }
5468
6129
  }
5469
6130
  }
5470
6131
  }
@@ -5497,6 +6158,7 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5497
6158
  const currentSignature = candidate.payload.raw ?? "";
5498
6159
  if (previousSignature !== void 0 && previousSignature !== currentSignature) {
5499
6160
  next.delete(blockId);
6161
+ failedSignatureRef.current.delete(blockId);
5500
6162
  mutated = true;
5501
6163
  }
5502
6164
  }
@@ -5506,6 +6168,9 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5506
6168
  for (const block of blocks) {
5507
6169
  if (block.type === "mdx") {
5508
6170
  signatureMap.set(block.id, block.payload.raw ?? "");
6171
+ if (!block.isFinalized || block.payload.compiledMdxRef) {
6172
+ failedSignatureRef.current.delete(block.id);
6173
+ }
5509
6174
  }
5510
6175
  }
5511
6176
  blockSignatureRef.current = signatureMap;
@@ -5863,7 +6528,9 @@ function StreamingMarkdownComponent({
5863
6528
  lowPriorityFrameBudgetMs: effectiveScheduling.lowPriorityFrameBudgetMs,
5864
6529
  urgentQueueThreshold: effectiveScheduling.urgentQueueThreshold,
5865
6530
  batch: effectiveScheduling.batch,
5866
- historyLimit
6531
+ historyLimit,
6532
+ startupMicrotaskFlushes: effectiveScheduling.startupMicrotaskFlushes,
6533
+ adaptiveBudgeting: effectiveScheduling.adaptiveBudgeting
5867
6534
  },
5868
6535
  mdx: mdxConfig
5869
6536
  };