@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
@@ -103,7 +103,7 @@ var init_mdx_client = __esm({
103
103
  if (block.type !== "mdx") {
104
104
  throw new Error("Block is not MDX type");
105
105
  }
106
- const cacheKey = block.id;
106
+ const cacheKey = `${block.id}\0${block.payload.raw ?? ""}`;
107
107
  const cachedPromise = this.cache.get(cacheKey);
108
108
  if (cachedPromise) {
109
109
  return cachedPromise;
@@ -1200,7 +1200,11 @@ var defaultInlineComponents = {
1200
1200
  const n = number ?? "?";
1201
1201
  const href = number ? `#fn:${n}` : void 0;
1202
1202
  const id = number ? `fnref:${n}` : void 0;
1203
- return import_react5.default.createElement("sup", { className: "footnote-ref" }, import_react5.default.createElement("a", { href, id, "data-label": label }, String(n)));
1203
+ return import_react5.default.createElement(
1204
+ "sup",
1205
+ { className: "footnote-ref" },
1206
+ import_react5.default.createElement("a", { href, id, "data-label": label, style: { color: "var(--foreground)", textDecoration: "underline" } }, String(n))
1207
+ );
1204
1208
  }
1205
1209
  };
1206
1210
  var defaultBlockComponents = {
@@ -1343,7 +1347,7 @@ var defaultBlockComponents = {
1343
1347
  dangerouslySetInnerHTML: { __html: htmlString }
1344
1348
  });
1345
1349
  },
1346
- mdx: ({ compiledRef, compiledModule, status, errorMessage, raw }) => {
1350
+ mdx: ({ compiledRef, compiledModule, status, errorMessage }) => {
1347
1351
  const [Comp, setComp] = import_react5.default.useState(null);
1348
1352
  const [internalError, setInternalError] = import_react5.default.useState(null);
1349
1353
  const [renderError, setRenderError] = import_react5.default.useState(null);
@@ -1444,7 +1448,6 @@ var defaultBlockComponents = {
1444
1448
  const waitingForVisibility = deferHydration && !shouldHydrate && Boolean(resolvedId);
1445
1449
  const pendingLabel = waitingForVisibility ? "MDX ready (hydrate on view)\u2026" : compileMode === "worker" ? "Compiling MDX (client)\u2026" : "Compiling MDX (server)\u2026";
1446
1450
  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.");
1447
- const rawFallback = typeof raw === "string" && raw.trim().length > 0 ? import_react5.default.createElement("pre", { className: "markdown-mdx-fallback" }, raw) : null;
1448
1451
  const allowRetry = Boolean(internalError || renderError);
1449
1452
  const retryButton = allowRetry ? import_react5.default.createElement(
1450
1453
  "button",
@@ -1456,9 +1459,10 @@ var defaultBlockComponents = {
1456
1459
  "Retry hydration"
1457
1460
  ) : null;
1458
1461
  const errorPanel = import_react5.default.createElement(
1459
- "div",
1460
- { className: "markdown-mdx-error" },
1461
- import_react5.default.createElement(import_react5.default.Fragment, null, import_react5.default.createElement("strong", null, "MDX failed"), import_react5.default.createElement("div", null, message))
1462
+ import_react5.default.Fragment,
1463
+ null,
1464
+ import_react5.default.createElement("strong", null, "MDX failed"),
1465
+ import_react5.default.createElement("div", null, message)
1462
1466
  );
1463
1467
  if (Comp && !failureMessage) {
1464
1468
  return import_react5.default.createElement(
@@ -1481,14 +1485,13 @@ var defaultBlockComponents = {
1481
1485
  return import_react5.default.createElement(
1482
1486
  "div",
1483
1487
  {
1484
- className: "markdown-mdx",
1488
+ className: "markdown-mdx error",
1485
1489
  ref: containerRef,
1486
1490
  "data-mdx-ref": pendingId,
1487
1491
  "data-mdx-status": "error",
1488
1492
  "data-mdx-mode": compileMode
1489
1493
  },
1490
1494
  errorPanel,
1491
- rawFallback,
1492
1495
  retryButton
1493
1496
  );
1494
1497
  }
@@ -1515,7 +1518,16 @@ var defaultBlockComponents = {
1515
1518
  { key: item.number, id: `fn:${item.number}` },
1516
1519
  renderInlineNodes(item.inlines, defaultInlineComponents),
1517
1520
  " ",
1518
- import_react5.default.createElement("a", { href: `#fnref:${item.number}`, className: "footnote-backref", "aria-label": "Back to content" }, "\u21A9")
1521
+ import_react5.default.createElement(
1522
+ "a",
1523
+ {
1524
+ href: `#fnref:${item.number}`,
1525
+ className: "footnote-backref",
1526
+ "aria-label": "Back to content",
1527
+ style: { color: "var(--foreground)", textDecoration: "underline" }
1528
+ },
1529
+ "\u21A9"
1530
+ )
1519
1531
  )
1520
1532
  );
1521
1533
  return import_react5.default.createElement("section", { className: "footnotes" }, import_react5.default.createElement("hr", {}), import_react5.default.createElement("ol", {}, listItems));
@@ -2063,6 +2075,9 @@ function useCodeHighlightRequester() {
2063
2075
  // src/renderer/patch-batching.ts
2064
2076
  var import_patch_batching = require("@stream-mdx/core/perf/patch-batching");
2065
2077
 
2078
+ // src/renderer/patch-commit-scheduler.ts
2079
+ var import_patch_batching2 = require("@stream-mdx/core/perf/patch-batching");
2080
+
2066
2081
  // src/renderer/patch-coalescing.ts
2067
2082
  var import_core3 = require("@stream-mdx/core");
2068
2083
 
@@ -2073,6 +2088,9 @@ var COALESCING_DURATION_ACTIVATE_MS = 6;
2073
2088
  var COALESCING_DURATION_DEACTIVATE_MS = 4;
2074
2089
  var RETAIN_TOUCHED_IN_HISTORY = typeof process !== "undefined" && process.env ? process.env.NODE_ENV !== "production" : true;
2075
2090
  var EMPTY_TOUCHED_SET = /* @__PURE__ */ new Set();
2091
+ function shouldSkipCoalescing(patches) {
2092
+ return patches.length <= 1;
2093
+ }
2076
2094
  function getDefaultNow() {
2077
2095
  if (typeof performance !== "undefined" && typeof performance.now === "function") {
2078
2096
  return () => performance.now();
@@ -2099,8 +2117,9 @@ function getDefaultCancelRaf() {
2099
2117
  }
2100
2118
  var PatchCommitScheduler = class {
2101
2119
  constructor(params) {
2102
- this.highQueue = [];
2103
- this.lowQueue = [];
2120
+ this.semanticQueue = [];
2121
+ this.enrichmentHighQueue = [];
2122
+ this.enrichmentLowQueue = [];
2104
2123
  this.scheduled = false;
2105
2124
  this.flushing = false;
2106
2125
  this.scheduledHandle = null;
@@ -2112,6 +2131,7 @@ var PatchCommitScheduler = class {
2112
2131
  this.scheduleToken = 0;
2113
2132
  this.coalescingDurationSamples = [];
2114
2133
  this.adaptiveBudgetActive = false;
2134
+ this.flushCount = 0;
2115
2135
  this.store = params.store;
2116
2136
  this.onFlush = params.onFlush;
2117
2137
  this.frameBudgetMs = Math.max(1, params.options?.frameBudgetMs ?? 10);
@@ -2127,6 +2147,8 @@ var PatchCommitScheduler = class {
2127
2147
  this.maxLowPriorityBatchesPerFlush = params.options?.maxLowPriorityBatchesPerFlush ?? 2;
2128
2148
  this.urgentQueueThreshold = Math.max(1, params.options?.urgentQueueThreshold ?? 4);
2129
2149
  this.historyLimit = Math.max(1, params.options?.historyLimit ?? 200);
2150
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(params.options?.startupMicrotaskFlushes ?? 0));
2151
+ this.adaptiveBudgeting = params.options?.adaptiveBudgeting ?? true;
2130
2152
  const requestedBatch = params.options?.batch;
2131
2153
  if (requestedBatch === "microtask") {
2132
2154
  this.batchStrategy = "microtask";
@@ -2153,19 +2175,23 @@ var PatchCommitScheduler = class {
2153
2175
  patches: input.patches,
2154
2176
  meta: input.meta ?? {},
2155
2177
  receivedAt: input.meta?.receivedAt ?? this.now(),
2178
+ kind: input.meta?.kind === "semantic" || input.meta?.kind === "enrichment" ? input.meta.kind : input.patches.some((patch) => (0, import_patch_batching2.getPatchKind)(patch) === "semantic") ? "semantic" : "enrichment",
2156
2179
  priority: input.meta?.priority === "low" ? "low" : "high"
2157
2180
  };
2158
- if (batch.priority === "low") {
2159
- this.lowQueue.push(batch);
2181
+ if (batch.kind === "semantic") {
2182
+ batch.priority = "high";
2183
+ this.semanticQueue.push(batch);
2184
+ } else if (batch.priority === "low") {
2185
+ this.enrichmentLowQueue.push(batch);
2160
2186
  } else {
2161
- this.highQueue.push(batch);
2187
+ this.enrichmentHighQueue.push(batch);
2162
2188
  }
2163
2189
  if (!this.paused) {
2164
2190
  this.schedule();
2165
2191
  }
2166
2192
  }
2167
2193
  flushAll() {
2168
- if (this.highQueue.length === 0 && this.lowQueue.length === 0 && !this.flushing) {
2194
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0 && !this.flushing) {
2169
2195
  return null;
2170
2196
  }
2171
2197
  this.cancelScheduled();
@@ -2175,9 +2201,11 @@ var PatchCommitScheduler = class {
2175
2201
  }
2176
2202
  clear() {
2177
2203
  this.cancelScheduled();
2178
- this.highQueue = [];
2179
- this.lowQueue = [];
2204
+ this.semanticQueue = [];
2205
+ this.enrichmentHighQueue = [];
2206
+ this.enrichmentLowQueue = [];
2180
2207
  this.flushing = false;
2208
+ this.flushCount = 0;
2181
2209
  this.coalescingDurationSamples = [];
2182
2210
  this.adaptiveBudgetActive = false;
2183
2211
  this.adaptiveHighBatchCap = void 0;
@@ -2185,10 +2213,10 @@ var PatchCommitScheduler = class {
2185
2213
  this.maybeResolveIdle();
2186
2214
  }
2187
2215
  getPendingCount() {
2188
- return this.highQueue.length + this.lowQueue.length;
2216
+ return this.semanticQueue.length + this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
2189
2217
  }
2190
2218
  isIdle() {
2191
- return !this.flushing && !this.scheduled && this.highQueue.length === 0 && this.lowQueue.length === 0;
2219
+ return !this.flushing && !this.scheduled && this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0;
2192
2220
  }
2193
2221
  awaitIdle() {
2194
2222
  if (this.isIdle()) {
@@ -2228,7 +2256,8 @@ var PatchCommitScheduler = class {
2228
2256
  this.runFlush(this.frameBudgetMs, false);
2229
2257
  this.maybeResolveIdle();
2230
2258
  };
2231
- if (this.batchStrategy === "microtask" && typeof queueMicrotask === "function") {
2259
+ const useStartupMicrotask = this.batchStrategy !== "microtask" && this.startupMicrotaskFlushes > 0 && this.flushCount < this.startupMicrotaskFlushes && typeof queueMicrotask === "function";
2260
+ if ((this.batchStrategy === "microtask" || useStartupMicrotask) && typeof queueMicrotask === "function") {
2232
2261
  this.scheduledViaRaf = false;
2233
2262
  this.scheduledHandle = token;
2234
2263
  queueMicrotask(execute);
@@ -2258,12 +2287,14 @@ var PatchCommitScheduler = class {
2258
2287
  if (this.paused && !manual) {
2259
2288
  return null;
2260
2289
  }
2261
- if (this.highQueue.length === 0 && this.lowQueue.length === 0) {
2290
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0) {
2262
2291
  return null;
2263
2292
  }
2264
2293
  this.flushing = true;
2265
2294
  const startedAt = this.now();
2266
- const initialQueueSize = this.highQueue.length + this.lowQueue.length;
2295
+ const initialSemanticQueueSize = this.semanticQueue.length;
2296
+ const initialEnrichmentQueueSize = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
2297
+ const initialQueueSize = initialSemanticQueueSize + initialEnrichmentQueueSize;
2267
2298
  const batches = [];
2268
2299
  let totalInputPatches = 0;
2269
2300
  let totalAppliedPatches = 0;
@@ -2278,7 +2309,15 @@ var PatchCommitScheduler = class {
2278
2309
  consumed += 1;
2279
2310
  const applyStart = this.now();
2280
2311
  const queueDelayMs = Math.max(0, applyStart - next.receivedAt);
2281
- const { patches: coalesced, metrics: coalescingMetrics } = (0, import_core3.coalescePatchesWithMetrics)(next.patches, import_core3.DEFAULT_COALESCE_CONFIG);
2312
+ let coalesced;
2313
+ let coalescingMetrics;
2314
+ if (shouldSkipCoalescing(next.patches)) {
2315
+ coalesced = next.patches;
2316
+ } else {
2317
+ const result = (0, import_core3.coalescePatchesWithMetrics)(next.patches, import_core3.DEFAULT_COALESCE_CONFIG);
2318
+ coalesced = result.patches;
2319
+ coalescingMetrics = result.metrics;
2320
+ }
2282
2321
  const touched = this.store.applyPatches(coalesced, {
2283
2322
  coalesced: true,
2284
2323
  metrics: coalescingMetrics
@@ -2298,6 +2337,7 @@ var PatchCommitScheduler = class {
2298
2337
  receivedAt: next.receivedAt,
2299
2338
  appliedAt: applyStart,
2300
2339
  priority: next.priority,
2340
+ kind: next.kind,
2301
2341
  coalescing: coalescingMetrics
2302
2342
  });
2303
2343
  if (maxBatches && batches.length - startingBatchCount >= maxBatches) {
@@ -2314,27 +2354,32 @@ var PatchCommitScheduler = class {
2314
2354
  queue.splice(0, consumed);
2315
2355
  }
2316
2356
  };
2317
- const initialHighQueueSize = this.highQueue.length;
2357
+ processQueue(this.semanticQueue, void 0, Number.POSITIVE_INFINITY);
2358
+ const initialHighQueueSize = this.enrichmentHighQueue.length;
2318
2359
  const configuredHighLimit = manual || !this.maxBatchesPerFlush ? void 0 : initialHighQueueSize > this.maxBatchesPerFlush ? void 0 : this.maxBatchesPerFlush;
2319
- const highBatchLimit = this.adaptiveHighBatchCap ?? configuredHighLimit;
2320
- processQueue(this.highQueue, highBatchLimit, budgetMs);
2360
+ const highBatchLimit = this.adaptiveBudgeting ? this.adaptiveHighBatchCap ?? configuredHighLimit : configuredHighLimit;
2361
+ processQueue(this.enrichmentHighQueue, highBatchLimit, budgetMs);
2321
2362
  if (manual) {
2322
- processQueue(this.lowQueue, void 0, Number.POSITIVE_INFINITY);
2323
- } else if (this.lowQueue.length > 0) {
2363
+ processQueue(this.enrichmentLowQueue, void 0, Number.POSITIVE_INFINITY);
2364
+ } else if (this.enrichmentLowQueue.length > 0) {
2324
2365
  elapsed = this.now() - startedAt;
2325
2366
  const budgetForLow = Math.max(0, this.lowPriorityFrameBudgetMs - elapsed);
2326
- if (budgetForLow > 0) {
2327
- const initialLowQueueSize = this.lowQueue.length;
2367
+ const processedHigherPriorityWork = batches.length > 0;
2368
+ if (budgetForLow > 0 || !processedHigherPriorityWork) {
2369
+ const initialLowQueueSize = this.enrichmentLowQueue.length;
2328
2370
  const configuredLowLimit = manual || !this.maxLowPriorityBatchesPerFlush ? void 0 : initialLowQueueSize > this.maxLowPriorityBatchesPerFlush ? void 0 : this.maxLowPriorityBatchesPerFlush;
2329
- const lowBatchLimit = this.adaptiveLowBatchCap ?? configuredLowLimit;
2330
- processQueue(this.lowQueue, lowBatchLimit, budgetForLow);
2371
+ const lowBatchLimit = this.adaptiveBudgeting ? this.adaptiveLowBatchCap ?? configuredLowLimit : configuredLowLimit;
2372
+ processQueue(this.enrichmentLowQueue, lowBatchLimit, budgetForLow);
2331
2373
  }
2332
2374
  }
2333
2375
  this.flushing = false;
2334
2376
  const completedAt = this.now();
2335
2377
  const totalDurationMs = completedAt - startedAt;
2336
- const remaining = this.highQueue.length + this.lowQueue.length;
2378
+ const remainingSemantic = this.semanticQueue.length;
2379
+ const remainingEnrichment = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
2380
+ const remaining = remainingSemantic + remainingEnrichment;
2337
2381
  if (batches.length > 0) {
2382
+ this.flushCount += 1;
2338
2383
  try {
2339
2384
  this.onFlush({
2340
2385
  batches,
@@ -2342,7 +2387,11 @@ var PatchCommitScheduler = class {
2342
2387
  totalAppliedPatches,
2343
2388
  totalDurationMs,
2344
2389
  remainingQueueSize: remaining,
2390
+ remainingSemanticQueueSize: remainingSemantic,
2391
+ remainingEnrichmentQueueSize: remainingEnrichment,
2345
2392
  queueDepthBefore: initialQueueSize,
2393
+ semanticQueueDepthBefore: initialSemanticQueueSize,
2394
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
2346
2395
  flushStartedAt: startedAt,
2347
2396
  flushCompletedAt: completedAt
2348
2397
  });
@@ -2353,21 +2402,27 @@ var PatchCommitScheduler = class {
2353
2402
  if (remaining > 0 && !this.paused) {
2354
2403
  this.schedule();
2355
2404
  }
2356
- const coalescingStats = this.computeCoalescingDurationStats();
2357
- this.updateAdaptiveBudget(coalescingStats.p95);
2358
- const adaptiveState = this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null);
2405
+ const coalescingStats = this.adaptiveBudgeting ? this.computeCoalescingDurationStats() : { sampleCount: this.coalescingDurationSamples.length, p95: null };
2406
+ if (this.adaptiveBudgeting) {
2407
+ this.updateAdaptiveBudget(coalescingStats.p95);
2408
+ }
2409
+ const adaptiveState = this.adaptiveBudgeting ? this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null) : void 0;
2359
2410
  const flushResult = batches.length > 0 ? {
2360
2411
  batches,
2361
2412
  totalPatches: totalInputPatches,
2362
2413
  totalAppliedPatches,
2363
2414
  totalDurationMs,
2364
2415
  remainingQueueSize: remaining,
2416
+ remainingSemanticQueueSize: remainingSemantic,
2417
+ remainingEnrichmentQueueSize: remainingEnrichment,
2365
2418
  queueDepthBefore: initialQueueSize,
2419
+ semanticQueueDepthBefore: initialSemanticQueueSize,
2420
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
2366
2421
  flushStartedAt: startedAt,
2367
2422
  flushCompletedAt: completedAt,
2368
2423
  coalescingDurationP95: coalescingStats.p95 ?? void 0,
2369
2424
  coalescingDurationSampleCount: coalescingStats.sampleCount,
2370
- adaptiveBudgetActive: adaptiveState.active,
2425
+ adaptiveBudgetActive: adaptiveState?.active,
2371
2426
  adaptiveBudgetState: adaptiveState
2372
2427
  } : null;
2373
2428
  if (flushResult) {
@@ -2403,6 +2458,9 @@ var PatchCommitScheduler = class {
2403
2458
  };
2404
2459
  }
2405
2460
  updateAdaptiveBudget(p95) {
2461
+ if (!this.adaptiveBudgeting) {
2462
+ return;
2463
+ }
2406
2464
  if (p95 !== null && p95 > COALESCING_DURATION_ACTIVATE_MS) {
2407
2465
  if (!this.adaptiveBudgetActive) {
2408
2466
  this.adaptiveBudgetActive = true;
@@ -2476,11 +2534,13 @@ var PatchCommitScheduler = class {
2476
2534
  }
2477
2535
  restart() {
2478
2536
  this.cancelScheduled();
2479
- this.highQueue = [];
2480
- this.lowQueue = [];
2537
+ this.semanticQueue = [];
2538
+ this.enrichmentHighQueue = [];
2539
+ this.enrichmentLowQueue = [];
2481
2540
  this.flushing = false;
2482
2541
  this.sequence = 0;
2483
2542
  this.history = [];
2543
+ this.flushCount = 0;
2484
2544
  this.scheduleToken++;
2485
2545
  this.paused = false;
2486
2546
  this.coalescingDurationSamples = [];
@@ -2527,6 +2587,12 @@ var PatchCommitScheduler = class {
2527
2587
  if (typeof options.historyLimit !== "undefined") {
2528
2588
  this.setHistoryLimit(options.historyLimit);
2529
2589
  }
2590
+ if (typeof options.startupMicrotaskFlushes !== "undefined") {
2591
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(options.startupMicrotaskFlushes));
2592
+ }
2593
+ if (typeof options.adaptiveBudgeting !== "undefined") {
2594
+ this.adaptiveBudgeting = options.adaptiveBudgeting;
2595
+ }
2530
2596
  if (typeof options.batch !== "undefined") {
2531
2597
  const requestedBatch = options.batch;
2532
2598
  if (requestedBatch === "microtask") {
@@ -2553,6 +2619,7 @@ var PatchCommitScheduler = class {
2553
2619
 
2554
2620
  // src/renderer/store.ts
2555
2621
  var import_core4 = require("@stream-mdx/core");
2622
+ var import_patch_batching3 = require("@stream-mdx/core/perf/patch-batching");
2556
2623
 
2557
2624
  // src/renderer/list-utils.ts
2558
2625
  function updateNodeDepth(node, depth) {
@@ -2648,12 +2715,28 @@ function snapshotToBlock(node) {
2648
2715
  }
2649
2716
  return null;
2650
2717
  }
2651
- var nodeSnapshotCache = /* @__PURE__ */ new Map();
2652
- var childrenSnapshotCache = /* @__PURE__ */ new Map();
2718
+ var nodeSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
2719
+ var childrenSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
2653
2720
  var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
2654
2721
  var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
2655
2722
  var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
2656
2723
  var CHILDREN_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(CHILDREN_SNAPSHOT_CACHE_LIMIT * 0.1));
2724
+ function getNodeSnapshotCacheForMap(map) {
2725
+ let cache = nodeSnapshotCacheByMap.get(map);
2726
+ if (!cache) {
2727
+ cache = /* @__PURE__ */ new Map();
2728
+ nodeSnapshotCacheByMap.set(map, cache);
2729
+ }
2730
+ return cache;
2731
+ }
2732
+ function getChildrenSnapshotCacheForMap(map) {
2733
+ let cache = childrenSnapshotCacheByMap.get(map);
2734
+ if (!cache) {
2735
+ cache = /* @__PURE__ */ new Map();
2736
+ childrenSnapshotCacheByMap.set(map, cache);
2737
+ }
2738
+ return cache;
2739
+ }
2657
2740
  function pruneCache(cache, max, buffer) {
2658
2741
  if (cache.size <= max + buffer) return;
2659
2742
  let remaining = cache.size - max;
@@ -2663,6 +2746,14 @@ function pruneCache(cache, max, buffer) {
2663
2746
  if (remaining <= 0) break;
2664
2747
  }
2665
2748
  }
2749
+ function deleteSnapshotCacheEntry(map, id) {
2750
+ getNodeSnapshotCacheForMap(map).delete(id);
2751
+ getChildrenSnapshotCacheForMap(map).delete(id);
2752
+ }
2753
+ function clearSnapshotCaches(map) {
2754
+ getNodeSnapshotCacheForMap(map).clear();
2755
+ getChildrenSnapshotCacheForMap(map).clear();
2756
+ }
2666
2757
  var EMPTY_CHILDREN = Object.freeze([]);
2667
2758
  var EMPTY_NODE_SNAPSHOT = Object.freeze({ version: -1, node: void 0 });
2668
2759
  var EMPTY_CHILDREN_SNAPSHOT = Object.freeze({
@@ -2830,9 +2921,57 @@ function createRootRecord() {
2830
2921
  parentId: null,
2831
2922
  children: [],
2832
2923
  props: {},
2833
- version: 0
2924
+ version: 0,
2925
+ blockEpoch: 0
2834
2926
  };
2835
2927
  }
2928
+ function readNumericEpoch(value) {
2929
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
2930
+ return void 0;
2931
+ }
2932
+ return value;
2933
+ }
2934
+ function readSnapshotBlockEpoch(snapshot) {
2935
+ return readNumericEpoch(snapshot.meta?.blockEpoch);
2936
+ }
2937
+ function readPatchBlockEpoch(patch) {
2938
+ if (patch.op === "setHTML") {
2939
+ return readNumericEpoch(patch.patchMeta?.blockEpoch);
2940
+ }
2941
+ return readNumericEpoch(patch.meta?.blockEpoch);
2942
+ }
2943
+ function readPatchParseEpoch(patch) {
2944
+ if (patch.op === "setHTML") {
2945
+ return readNumericEpoch(patch.patchMeta?.parseEpoch);
2946
+ }
2947
+ return readNumericEpoch(patch.meta?.parseEpoch);
2948
+ }
2949
+ function readPatchTx(patch) {
2950
+ if (patch.op === "setHTML") {
2951
+ return readNumericEpoch(patch.patchMeta?.tx);
2952
+ }
2953
+ return readNumericEpoch(patch.meta?.tx);
2954
+ }
2955
+ function assignSubtreeBlockEpoch(map, rootId, blockEpoch, touched) {
2956
+ const root = map.get(rootId);
2957
+ if (!root) return;
2958
+ const stack = [root];
2959
+ while (stack.length > 0) {
2960
+ const node = stack.pop();
2961
+ if (!node) continue;
2962
+ if (node.blockEpoch !== blockEpoch) {
2963
+ node.blockEpoch = blockEpoch;
2964
+ node.version += 1;
2965
+ touched?.add(node.id);
2966
+ }
2967
+ for (const childId of node.children) {
2968
+ const child = map.get(childId);
2969
+ if (child) {
2970
+ stack.push(child);
2971
+ }
2972
+ }
2973
+ }
2974
+ }
2836
2975
  function ensureArray(value) {
2837
2976
  return Array.isArray(value) ? value : [];
2838
2977
  }
@@ -2892,7 +3031,7 @@ function escapeHtml2(value) {
2892
3031
  }
2893
3032
  function stripOuterLineSpan(html) {
2894
3033
  if (!html) return null;
2895
- const openTagMatch = html.match(/<span[^>]*>/i);
3034
+ const openTagMatch = html.match(/<span[^>]*class="[^"]*\bline\b[^"]*"[^>]*>/i);
2896
3035
  if (!openTagMatch) {
2897
3036
  return null;
2898
3037
  }
@@ -2941,8 +3080,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2941
3080
  if (node.type !== "code-line") {
2942
3081
  anomalies.push(`type-mismatch:${node.id}:${node.type}`);
2943
3082
  nodes.delete(node.id);
2944
- nodeSnapshotCache.delete(node.id);
2945
- childrenSnapshotCache.delete(node.id);
3083
+ deleteSnapshotCacheEntry(nodes, node.id);
2946
3084
  touched.add(node.id);
2947
3085
  continue;
2948
3086
  }
@@ -2986,16 +3124,14 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2986
3124
  const existing = nodes.get(desiredId);
2987
3125
  if (existing && existing !== node) {
2988
3126
  nodes.delete(desiredId);
2989
- nodeSnapshotCache.delete(desiredId);
2990
- childrenSnapshotCache.delete(desiredId);
3127
+ deleteSnapshotCacheEntry(nodes, desiredId);
2991
3128
  touched.add(desiredId);
2992
3129
  mutated = true;
2993
3130
  }
2994
3131
  if (node.id !== desiredId) {
2995
3132
  const previousId = node.id;
2996
3133
  nodes.delete(node.id);
2997
- nodeSnapshotCache.delete(previousId);
2998
- childrenSnapshotCache.delete(previousId);
3134
+ deleteSnapshotCacheEntry(nodes, previousId);
2999
3135
  node.id = desiredId;
3000
3136
  nodes.set(desiredId, node);
3001
3137
  touched.add(desiredId);
@@ -3026,8 +3162,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
3026
3162
  for (const [id, record] of nodes) {
3027
3163
  if (record.parentId === parent.id && record.type === "code-line" && !allowed.has(id)) {
3028
3164
  nodes.delete(id);
3029
- nodeSnapshotCache.delete(id);
3030
- childrenSnapshotCache.delete(id);
3165
+ deleteSnapshotCacheEntry(nodes, id);
3031
3166
  touched.add(id);
3032
3167
  mutated = true;
3033
3168
  }
@@ -3111,8 +3246,10 @@ function normalizeCodeLineProps(incoming, previous) {
3111
3246
  }
3112
3247
  function createCodeLineProps(index, text, highlight, tokens) {
3113
3248
  const safeText = typeof text === "string" ? text : "";
3114
- const sanitized = sanitizeLineInnerHtml(highlight, safeText);
3115
- const html = sanitized && sanitized.length >= safeText.length ? sanitized : escapeHtml2(safeText);
3249
+ const hasIncomingHighlight = typeof highlight === "string" && highlight.trim().length > 0;
3250
+ const sanitized = hasIncomingHighlight ? sanitizeLineInnerHtml(highlight, safeText) : null;
3251
+ const hasRenderableTokens = tokens !== void 0 && tokens !== null;
3252
+ const html = sanitized && sanitized.length >= safeText.length ? sanitized : hasRenderableTokens ? null : escapeHtml2(safeText);
3116
3253
  const props = {
3117
3254
  index,
3118
3255
  text: safeText,
@@ -3223,6 +3360,18 @@ function validateCodeBlockChildren(nodes, parent) {
3223
3360
  issues.push({ kind: "dangling-node", id });
3224
3361
  }
3225
3362
  }
3363
+ if (parent.block?.isFinalized) {
3364
+ const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(parent.block));
3365
+ const expectedChildren = ensureArray(snapshot.children).filter((child) => child.type === "code-line");
3366
+ if (expectedChildren.length !== parent.children.length) {
3367
+ issues.push({
3368
+ kind: "line-count-mismatch",
3369
+ id: parent.id,
3370
+ expected: expectedChildren.length,
3371
+ actual: parent.children.length
3372
+ });
3373
+ }
3374
+ }
3226
3375
  return { ok: issues.length === 0, issues };
3227
3376
  }
3228
3377
  function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
@@ -3239,7 +3388,7 @@ function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
3239
3388
  }
3240
3389
  const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(parent.block));
3241
3390
  removeSubtree(nodes, parent.id, touched);
3242
- const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot);
3391
+ const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot, touched, parent.blockEpoch);
3243
3392
  if (!inserted) {
3244
3393
  return false;
3245
3394
  }
@@ -3262,7 +3411,55 @@ function arraysEqual(a, b) {
3262
3411
  }
3263
3412
  return true;
3264
3413
  }
3265
- function buildNodeFromSnapshot(map, snapshot, parentId) {
3414
+ function expectedCodeLineId(parentId, index) {
3415
+ return `${parentId}::line:${index}`;
3416
+ }
3417
+ function collectStoreInvariantViolations(nodes) {
3418
+ const violations = [];
3419
+ for (const [id, node] of nodes) {
3420
+ if (node.type === "__root__") continue;
3421
+ if (node.children.length > 1) {
3422
+ const seen = /* @__PURE__ */ new Set();
3423
+ for (const childId of node.children) {
3424
+ if (seen.has(childId)) {
3425
+ violations.push(`duplicate child id:${id}:${childId}`);
3426
+ } else {
3427
+ seen.add(childId);
3428
+ }
3429
+ }
3430
+ }
3431
+ if (node.type === "list") {
3432
+ const parent = node.parentId ? nodes.get(node.parentId) : void 0;
3433
+ if (parent?.type === "list-item" && node.children.length === 0) {
3434
+ violations.push(`empty nested list:${id}`);
3435
+ }
3436
+ }
3437
+ if (node.type === "code") {
3438
+ for (let index = 0; index < node.children.length; index += 1) {
3439
+ const childId = node.children[index];
3440
+ const child = nodes.get(childId);
3441
+ if (!child) {
3442
+ violations.push(`missing code-line node:${node.id}:${childId}`);
3443
+ continue;
3444
+ }
3445
+ if (child.type !== "code-line") {
3446
+ violations.push(`non-code child in code block:${node.id}:${child.id}:${child.type}`);
3447
+ continue;
3448
+ }
3449
+ const expectedId = expectedCodeLineId(node.id, index);
3450
+ if (child.id !== expectedId) {
3451
+ violations.push(`code-line id mismatch:${node.id}:${child.id}:${expectedId}`);
3452
+ }
3453
+ const indexProp = typeof child.props?.index === "number" ? child.props.index : null;
3454
+ if (indexProp !== index) {
3455
+ violations.push(`code-line index mismatch:${node.id}:${child.id}:${indexProp}:${index}`);
3456
+ }
3457
+ }
3458
+ }
3459
+ }
3460
+ return violations;
3461
+ }
3462
+ function buildNodeFromSnapshot(map, snapshot, parentId, blockEpoch) {
3266
3463
  const record = {
3267
3464
  id: snapshot.id,
3268
3465
  type: snapshot.type,
@@ -3272,6 +3469,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
3272
3469
  meta: snapshot.meta ? { ...snapshot.meta } : void 0,
3273
3470
  range: snapshot.range ? { ...snapshot.range } : void 0,
3274
3471
  version: 0,
3472
+ blockEpoch,
3275
3473
  block: snapshotToBlock(snapshot) ?? void 0
3276
3474
  };
3277
3475
  if (record.type === "code") {
@@ -3280,7 +3478,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
3280
3478
  map.set(record.id, record);
3281
3479
  const childSnapshots = ensureArray(snapshot.children);
3282
3480
  for (const child of childSnapshots) {
3283
- const childRecord = buildNodeFromSnapshot(map, child, record.id);
3481
+ const childRecord = buildNodeFromSnapshot(map, child, record.id, blockEpoch);
3284
3482
  record.children.push(childRecord.id);
3285
3483
  }
3286
3484
  return record;
@@ -3308,8 +3506,7 @@ function removeSubtree(map, id, touched) {
3308
3506
  }
3309
3507
  }
3310
3508
  map.delete(node.id);
3311
- nodeSnapshotCache.delete(node.id);
3312
- childrenSnapshotCache.delete(node.id);
3509
+ deleteSnapshotCacheEntry(map, node.id);
3313
3510
  touched.add(node.id);
3314
3511
  for (const childId of node.children) {
3315
3512
  const child = map.get(childId);
@@ -3341,13 +3538,13 @@ function resolveParent(map, path) {
3341
3538
  }
3342
3539
  return map.get(path.blockId);
3343
3540
  }
3344
- function insertSnapshotAt(map, parent, index, snapshot, touched) {
3541
+ function insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
3345
3542
  if (!parent) return null;
3346
3543
  const existingIndex = parent.children.indexOf(snapshot.id);
3347
3544
  if (existingIndex !== -1) {
3348
3545
  removeSubtree(map, parent.children[existingIndex], touched ?? /* @__PURE__ */ new Set());
3349
3546
  }
3350
- const record = buildNodeFromSnapshot(map, snapshot, parent.id);
3547
+ const record = buildNodeFromSnapshot(map, snapshot, parent.id, blockEpoch ?? readSnapshotBlockEpoch(snapshot) ?? parent.blockEpoch);
3351
3548
  const insertedIds = [];
3352
3549
  const queue = [record];
3353
3550
  while (queue.length > 0) {
@@ -3365,13 +3562,13 @@ function insertSnapshotAt(map, parent, index, snapshot, touched) {
3365
3562
  ensureUniqueChildren(parent, touched);
3366
3563
  return { record, insertedIds };
3367
3564
  }
3368
- function replaceSnapshotAt(map, parent, index, snapshot, touched) {
3565
+ function replaceSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
3369
3566
  if (!parent) return;
3370
3567
  if (index < 0 || index >= parent.children.length) return;
3371
3568
  const targetId = parent.children[index];
3372
3569
  parent.children.splice(index, 1);
3373
3570
  removeSubtree(map, targetId, touched);
3374
- const inserted = insertSnapshotAt(map, parent, index, snapshot, touched);
3571
+ const inserted = insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch);
3375
3572
  if (inserted) {
3376
3573
  for (const id of inserted.insertedIds) {
3377
3574
  touched.add(id);
@@ -3391,6 +3588,24 @@ function createRendererStore(initialBlocks = []) {
3391
3588
  const listeners = /* @__PURE__ */ new Set();
3392
3589
  let notifyScheduled = false;
3393
3590
  let lastCoalescingMetrics = null;
3591
+ let invariantViolations = [];
3592
+ let transientDiagnostics = [];
3593
+ let nextBlockEpoch = 1;
3594
+ const debugCounters = {
3595
+ appendLineGuardRejected: 0,
3596
+ staleEpochRejected: 0,
3597
+ storeInvariantViolationCount: 0
3598
+ };
3599
+ const allocateBlockEpoch = (preferred) => {
3600
+ const resolved = readNumericEpoch(preferred);
3601
+ if (resolved !== void 0) {
3602
+ nextBlockEpoch = Math.max(nextBlockEpoch, resolved + 1);
3603
+ return resolved;
3604
+ }
3605
+ const epoch = nextBlockEpoch;
3606
+ nextBlockEpoch += 1;
3607
+ return epoch;
3608
+ };
3394
3609
  const getRaf = () => {
3395
3610
  if (typeof globalThis !== "undefined" && typeof globalThis.requestAnimationFrame === "function") {
3396
3611
  return globalThis.requestAnimationFrame;
@@ -3422,9 +3637,12 @@ function createRendererStore(initialBlocks = []) {
3422
3637
  }
3423
3638
  }
3424
3639
  }
3640
+ function appendDiagnostic(message) {
3641
+ transientDiagnostics = [...transientDiagnostics, message];
3642
+ invariantViolations = [...invariantViolations, message];
3643
+ }
3425
3644
  function rebuildFromBlocks(blocks) {
3426
- nodeSnapshotCache.clear();
3427
- childrenSnapshotCache.clear();
3645
+ clearSnapshotCaches(nodes);
3428
3646
  nodes = /* @__PURE__ */ new Map();
3429
3647
  const root = createRootRecord();
3430
3648
  nodes.set(root.id, root);
@@ -3433,9 +3651,12 @@ function createRendererStore(initialBlocks = []) {
3433
3651
  for (let i = 0; i < blocks.length; i++) {
3434
3652
  const block = (0, import_core4.cloneBlock)(blocks[i]);
3435
3653
  const snapshot = (0, import_core4.createBlockSnapshot)(block);
3436
- insertSnapshotAt(nodes, root, root.children.length, snapshot);
3654
+ insertSnapshotAt(nodes, root, root.children.length, snapshot, void 0, allocateBlockEpoch(readSnapshotBlockEpoch(snapshot)));
3437
3655
  }
3438
3656
  normalizeAllListDepths(nodes, /* @__PURE__ */ new Set(), import_core4.PATCH_ROOT_ID);
3657
+ transientDiagnostics = [];
3658
+ invariantViolations = collectStoreInvariantViolations(nodes);
3659
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
3439
3660
  version++;
3440
3661
  blocksDirty = true;
3441
3662
  scheduleNotify();
@@ -3500,11 +3721,57 @@ function createRendererStore(initialBlocks = []) {
3500
3721
  listDepthDirty = true;
3501
3722
  }
3502
3723
  };
3503
- const applyPropsUpdate = (at, props) => {
3724
+ const semanticTxEpochs = /* @__PURE__ */ new Map();
3725
+ const resolveBlockEpochTarget = (patch, at) => {
3726
+ if ((patch.op === "insertChild" || patch.op === "replaceChild") && at.blockId === import_core4.PATCH_ROOT_ID) {
3727
+ const nodeId = patch.node?.id;
3728
+ return nodeId ? nodes.get(nodeId) : void 0;
3729
+ }
3730
+ return nodes.get(at.blockId) ?? (at.nodeId ? nodes.get(at.nodeId) : void 0);
3731
+ };
3732
+ const rejectStaleEpochPatch = (patch, at) => {
3733
+ const expectedBlockEpoch = readPatchBlockEpoch(patch);
3734
+ const patchTx = readPatchTx(patch);
3735
+ if (expectedBlockEpoch === void 0) {
3736
+ return false;
3737
+ }
3738
+ if ((0, import_patch_batching3.getPatchKind)(patch) !== "semantic" || !at) {
3739
+ return false;
3740
+ }
3741
+ const target = resolveBlockEpochTarget(patch, at);
3742
+ if (!target || target.blockEpoch === expectedBlockEpoch) {
3743
+ return false;
3744
+ }
3745
+ const txEpoch = at.blockId && patchTx !== void 0 ? semanticTxEpochs.get(at.blockId) : void 0;
3746
+ if (txEpoch && txEpoch.tx === patchTx && txEpoch.blockEpoch === target.blockEpoch) {
3747
+ return false;
3748
+ }
3749
+ debugCounters.staleEpochRejected += 1;
3750
+ appendDiagnostic(
3751
+ `stale epoch rejected:${patch.op}:${target.id}:expected=${expectedBlockEpoch}:actual=${target.blockEpoch}`
3752
+ );
3753
+ return true;
3754
+ };
3755
+ const advanceBlockEpoch = (blockId, touchedSet, preferredEpoch, tx) => {
3756
+ if (!blockId || blockId === import_core4.PATCH_ROOT_ID) {
3757
+ return null;
3758
+ }
3759
+ const blockRoot = nodes.get(blockId);
3760
+ if (!blockRoot) {
3761
+ return null;
3762
+ }
3763
+ const nextEpoch = allocateBlockEpoch(preferredEpoch);
3764
+ assignSubtreeBlockEpoch(nodes, blockRoot.id, nextEpoch, touchedSet);
3765
+ if (tx !== void 0) {
3766
+ semanticTxEpochs.set(blockRoot.id, { tx, blockEpoch: nextEpoch });
3767
+ }
3768
+ return nextEpoch;
3769
+ };
3770
+ const applyPropsUpdate = (at, props, preferredEpoch, tx, semanticUpdate = false) => {
3504
3771
  const perfStartLocal = perfEnabled ? getPerfNow() : 0;
3505
3772
  const targetId = at.nodeId ?? at.blockId;
3506
3773
  if (!targetId) return;
3507
- const target = nodes.get(targetId);
3774
+ let target = nodes.get(targetId);
3508
3775
  if (!target) return;
3509
3776
  const parentType = target.parentId ? nodes.get(target.parentId)?.type : void 0;
3510
3777
  if (debugListPatches && (isListRelated(target.type) || isListRelated(parentType))) {
@@ -3531,6 +3798,9 @@ function createRendererStore(initialBlocks = []) {
3531
3798
  propsChanged = true;
3532
3799
  }
3533
3800
  if (propsChanged) {
3801
+ if (semanticUpdate) {
3802
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3803
+ }
3534
3804
  target.version++;
3535
3805
  touched.add(target.id);
3536
3806
  const parent = target.parentId ? nodes.get(target.parentId) : void 0;
@@ -3594,16 +3864,56 @@ function createRendererStore(initialBlocks = []) {
3594
3864
  }
3595
3865
  }
3596
3866
  let blockChanged = false;
3867
+ let typeChanged = false;
3868
+ let rebuiltFromFinalizedBlock = false;
3869
+ let blockEpochAdvanced = false;
3597
3870
  const incomingBlock = props?.block;
3598
3871
  if (incomingBlock && typeof incomingBlock === "object") {
3599
3872
  target.block = (0, import_core4.cloneBlock)(incomingBlock);
3873
+ const previousType = target.type;
3874
+ const incomingType = typeof target.block.type === "string" ? target.block.type : target.type;
3875
+ if (incomingType !== target.type) {
3876
+ target.type = incomingType;
3877
+ typeChanged = true;
3878
+ if (isListRelated(previousType) || isListRelated(target.type)) {
3879
+ markListDepthDirty(target.id);
3880
+ }
3881
+ }
3600
3882
  if (target.type === "code") {
3601
3883
  applyCodeBlockMetadata(target);
3602
3884
  }
3885
+ if (semanticUpdate) {
3886
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3887
+ blockEpochAdvanced = true;
3888
+ }
3889
+ const shouldRebuildFinalizedTree = !at.nodeId && target.id === at.blockId && Boolean(target.block?.isFinalized) && target.type !== "code" && Boolean(target.parentId);
3890
+ if (shouldRebuildFinalizedTree) {
3891
+ const parent = target.parentId ? nodes.get(target.parentId) : void 0;
3892
+ const index = parent ? parent.children.indexOf(target.id) : -1;
3893
+ if (parent && index !== -1) {
3894
+ const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(target.block));
3895
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
3896
+ mutatedBlocks = true;
3897
+ blockChanged = true;
3898
+ rebuiltFromFinalizedBlock = true;
3899
+ const rebuilt = nodes.get(target.id);
3900
+ if (isListRelated(previousType) || isListRelated(rebuilt?.type)) {
3901
+ markListDepthDirty(target.id);
3902
+ }
3903
+ target = rebuilt ?? target;
3904
+ }
3905
+ }
3603
3906
  mutatedBlocks = true;
3604
3907
  blockChanged = true;
3605
3908
  }
3606
- if (propsChanged || blockChanged) {
3909
+ if (rebuiltFromFinalizedBlock) {
3910
+ return;
3911
+ }
3912
+ if (propsChanged || blockChanged || typeChanged) {
3913
+ if (semanticUpdate && !blockEpochAdvanced) {
3914
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3915
+ blockEpochAdvanced = true;
3916
+ }
3607
3917
  target.version++;
3608
3918
  touched.add(target.id);
3609
3919
  let parentForNormalization;
@@ -3646,13 +3956,22 @@ function createRendererStore(initialBlocks = []) {
3646
3956
  const applyPatch = (patch) => {
3647
3957
  switch (patch.op) {
3648
3958
  case "insertChild": {
3959
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3649
3960
  const parent = resolveParent(nodes, patch.at);
3650
- const inserted = insertSnapshotAt(nodes, parent, patch.index, patch.node, touched);
3961
+ const inserted = insertSnapshotAt(
3962
+ nodes,
3963
+ parent,
3964
+ patch.index,
3965
+ patch.node,
3966
+ touched,
3967
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
3968
+ );
3651
3969
  if (inserted) {
3652
3970
  for (const id of inserted.insertedIds) {
3653
3971
  touched.add(id);
3654
3972
  }
3655
3973
  mutatedBlocks = true;
3974
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3656
3975
  if (isListRelated(inserted.record.type) || isListRelated(parent?.type)) {
3657
3976
  markListDepthDirty(inserted.record.id);
3658
3977
  if (parent) {
@@ -3672,6 +3991,7 @@ function createRendererStore(initialBlocks = []) {
3672
3991
  break;
3673
3992
  }
3674
3993
  case "deleteChild": {
3994
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3675
3995
  const parent = resolveParent(nodes, patch.at);
3676
3996
  if (!parent) break;
3677
3997
  if (patch.index < 0 || patch.index >= parent.children.length) break;
@@ -3683,6 +4003,7 @@ function createRendererStore(initialBlocks = []) {
3683
4003
  touched.add(parent.id);
3684
4004
  ensureUniqueChildren(parent, touched);
3685
4005
  mutatedBlocks = true;
4006
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3686
4007
  if (isListRelated(parent.type) || isListRelated(removedNode?.type)) {
3687
4008
  markListDepthDirty(parent.id);
3688
4009
  }
@@ -3699,11 +4020,20 @@ function createRendererStore(initialBlocks = []) {
3699
4020
  break;
3700
4021
  }
3701
4022
  case "replaceChild": {
4023
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3702
4024
  const parent = resolveParent(nodes, patch.at);
3703
4025
  if (!parent) break;
3704
- replaceSnapshotAt(nodes, parent, patch.index, patch.node, touched);
4026
+ replaceSnapshotAt(
4027
+ nodes,
4028
+ parent,
4029
+ patch.index,
4030
+ patch.node,
4031
+ touched,
4032
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
4033
+ );
3705
4034
  touched.add(parent.id);
3706
4035
  mutatedBlocks = true;
4036
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3707
4037
  if (isListRelated(parent.type)) {
3708
4038
  markListDepthDirty(parent.id);
3709
4039
  }
@@ -3717,18 +4047,33 @@ function createRendererStore(initialBlocks = []) {
3717
4047
  break;
3718
4048
  }
3719
4049
  case "setProps": {
3720
- applyPropsUpdate(patch.at, patch.props);
4050
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
4051
+ applyPropsUpdate(patch.at, patch.props, readPatchParseEpoch(patch), readPatchTx(patch), (0, import_patch_batching3.getPatchKind)(patch) === "semantic");
3721
4052
  break;
3722
4053
  }
3723
4054
  case "setPropsBatch": {
3724
4055
  const entries2 = Array.isArray(patch.entries) ? patch.entries : [];
3725
4056
  for (const entry of entries2) {
3726
4057
  if (!entry || !entry.at) continue;
3727
- applyPropsUpdate(entry.at, entry.props);
4058
+ const entryPatch = {
4059
+ op: "setProps",
4060
+ at: entry.at,
4061
+ props: entry.props,
4062
+ meta: entry.meta ?? patch.meta
4063
+ };
4064
+ if (rejectStaleEpochPatch(entryPatch, entry.at)) continue;
4065
+ applyPropsUpdate(
4066
+ entry.at,
4067
+ entry.props,
4068
+ readPatchParseEpoch(entryPatch),
4069
+ readPatchTx(entryPatch),
4070
+ (0, import_patch_batching3.getPatchKind)(entryPatch) === "semantic"
4071
+ );
3728
4072
  }
3729
4073
  break;
3730
4074
  }
3731
4075
  case "finalize": {
4076
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3732
4077
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
3733
4078
  if (!target || !target.block) break;
3734
4079
  if (!target.block.isFinalized) {
@@ -3737,6 +4082,17 @@ function createRendererStore(initialBlocks = []) {
3737
4082
  target.version++;
3738
4083
  touched.add(target.id);
3739
4084
  mutatedBlocks = true;
4085
+ if (!patch.at.nodeId && target.id === patch.at.blockId && target.type !== "code" && target.parentId) {
4086
+ const parent = nodes.get(target.parentId);
4087
+ const index = parent ? parent.children.indexOf(target.id) : -1;
4088
+ if (parent && index !== -1) {
4089
+ const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(target.block));
4090
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
4091
+ if (isListRelated(target.type) || isListRelated(snapshot.type)) {
4092
+ markListDepthDirty(target.id);
4093
+ }
4094
+ }
4095
+ }
3740
4096
  }
3741
4097
  if (isListRelated(target.type)) {
3742
4098
  markListDepthDirty(target.id);
@@ -3750,6 +4106,7 @@ function createRendererStore(initialBlocks = []) {
3750
4106
  break;
3751
4107
  }
3752
4108
  case "reorder": {
4109
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3753
4110
  const parent = resolveParent(nodes, patch.at);
3754
4111
  if (!parent) break;
3755
4112
  const length = parent.children.length;
@@ -3764,6 +4121,9 @@ function createRendererStore(initialBlocks = []) {
3764
4121
  parent.children.splice(to, 0, ...moved);
3765
4122
  parent.version++;
3766
4123
  touched.add(parent.id);
4124
+ if ((0, import_patch_batching3.getPatchKind)(patch) === "semantic") {
4125
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
4126
+ }
3767
4127
  if (isListRelated(parent.type)) {
3768
4128
  markListDepthDirty(parent.id);
3769
4129
  }
@@ -3779,15 +4139,31 @@ function createRendererStore(initialBlocks = []) {
3779
4139
  break;
3780
4140
  }
3781
4141
  case "appendLines": {
4142
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3782
4143
  const parent = resolveParent(nodes, patch.at);
3783
4144
  if (!parent) break;
3784
- const startIndex = Math.max(0, Math.min(patch.startIndex, parent.children.length));
4145
+ if (parent.type !== "code") {
4146
+ debugCounters.appendLineGuardRejected += 1;
4147
+ appendDiagnostic(`appendLines target not code:${parent.id}:${parent.type}`);
4148
+ break;
4149
+ }
4150
+ const requestedStartIndex = Math.max(0, patch.startIndex);
4151
+ const startIndex = Math.max(0, Math.min(requestedStartIndex, parent.children.length));
3785
4152
  const lineCount = patch.lines?.length ?? 0;
3786
4153
  if (lineCount === 0) break;
4154
+ const prefixMatches = parent.children.every((childId, index) => childId === expectedCodeLineId(parent.id, index));
4155
+ const isStrictTailAppend = requestedStartIndex === parent.children.length;
4156
+ if (!isStrictTailAppend || !prefixMatches) {
4157
+ debugCounters.appendLineGuardRejected += 1;
4158
+ appendDiagnostic(
4159
+ `appendLines guard rejected:${parent.id}:requestedStart=${requestedStartIndex}:childCount=${parent.children.length}:prefixMatches=${prefixMatches}`
4160
+ );
4161
+ break;
4162
+ }
3787
4163
  const originalChildren = parent.children.slice();
3788
4164
  const insertedIds = [];
3789
4165
  const insertedSet = /* @__PURE__ */ new Set();
3790
- const isTrailingAppend = startIndex >= originalChildren.length;
4166
+ const isTrailingAppend = true;
3791
4167
  let parentChildrenChanged = false;
3792
4168
  let codeLinesMutated = false;
3793
4169
  for (let offset = 0; offset < lineCount; offset++) {
@@ -3829,7 +4205,8 @@ function createRendererStore(initialBlocks = []) {
3829
4205
  parentId: parent.id,
3830
4206
  children: [],
3831
4207
  props: nextLineProps,
3832
- version: 0
4208
+ version: 0,
4209
+ blockEpoch: parent.blockEpoch
3833
4210
  };
3834
4211
  nodes.set(lineId, record);
3835
4212
  touched.add(lineId);
@@ -3879,6 +4256,7 @@ function createRendererStore(initialBlocks = []) {
3879
4256
  break;
3880
4257
  }
3881
4258
  case "setHTML": {
4259
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3882
4260
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
3883
4261
  if (!target) break;
3884
4262
  const sanitized = patch.sanitized ? patch.html : (0, import_core4.sanitizeHTML)(patch.html);
@@ -3921,6 +4299,30 @@ function createRendererStore(initialBlocks = []) {
3921
4299
  applyPatch(patch);
3922
4300
  }
3923
4301
  }
4302
+ const finalizedTableRoots = /* @__PURE__ */ new Set();
4303
+ for (const touchedId of touched) {
4304
+ let cursor = nodes.get(touchedId);
4305
+ while (cursor && cursor.id !== import_core4.PATCH_ROOT_ID) {
4306
+ if (cursor.type === "table" && cursor.block?.isFinalized && cursor.parentId) {
4307
+ finalizedTableRoots.add(cursor.id);
4308
+ break;
4309
+ }
4310
+ cursor = cursor.parentId ? nodes.get(cursor.parentId) : void 0;
4311
+ }
4312
+ }
4313
+ if (finalizedTableRoots.size > 0) {
4314
+ for (const tableId of finalizedTableRoots) {
4315
+ const tableNode = nodes.get(tableId);
4316
+ if (!tableNode || !tableNode.block || !tableNode.parentId) continue;
4317
+ const parent = nodes.get(tableNode.parentId);
4318
+ if (!parent) continue;
4319
+ const index = parent.children.indexOf(tableNode.id);
4320
+ if (index === -1) continue;
4321
+ const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(tableNode.block));
4322
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, tableNode.blockEpoch);
4323
+ mutatedBlocks = true;
4324
+ }
4325
+ }
3924
4326
  if (mutatedBlocks) {
3925
4327
  blocksDirty = true;
3926
4328
  }
@@ -3941,6 +4343,8 @@ function createRendererStore(initialBlocks = []) {
3941
4343
  normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, import_core4.PATCH_ROOT_ID);
3942
4344
  }
3943
4345
  }
4346
+ invariantViolations = [...collectStoreInvariantViolations(nodes), ...transientDiagnostics];
4347
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
3944
4348
  if (touched.size > 0 || mutatedBlocks) {
3945
4349
  version++;
3946
4350
  scheduleNotify();
@@ -3989,6 +4393,7 @@ function createRendererStore(initialBlocks = []) {
3989
4393
  if (!record) {
3990
4394
  return EMPTY_NODE_SNAPSHOT;
3991
4395
  }
4396
+ const nodeSnapshotCache = getNodeSnapshotCacheForMap(nodes);
3992
4397
  const cached = nodeSnapshotCache.get(id);
3993
4398
  if (cached && cached.version === record.version && cached.node === record) {
3994
4399
  return cached;
@@ -4008,6 +4413,7 @@ function createRendererStore(initialBlocks = []) {
4008
4413
  if (!record) {
4009
4414
  return EMPTY_CHILDREN_SNAPSHOT;
4010
4415
  }
4416
+ const childrenSnapshotCache = getChildrenSnapshotCacheForMap(nodes);
4011
4417
  const cached = childrenSnapshotCache.get(id);
4012
4418
  if (cached && cached.version === record.version) {
4013
4419
  return cached;
@@ -4017,6 +4423,12 @@ function createRendererStore(initialBlocks = []) {
4017
4423
  pruneCache(childrenSnapshotCache, CHILDREN_SNAPSHOT_CACHE_LIMIT, CHILDREN_SNAPSHOT_CACHE_BUFFER);
4018
4424
  return snapshot;
4019
4425
  },
4426
+ getInvariantViolations() {
4427
+ return invariantViolations.slice();
4428
+ },
4429
+ getDebugCounters() {
4430
+ return { ...debugCounters };
4431
+ },
4020
4432
  getLastCoalescingMetrics() {
4021
4433
  return lastCoalescingMetrics;
4022
4434
  },
@@ -4501,8 +4913,10 @@ var ListBlockView = import_react10.default.memo(
4501
4913
  isFinalized: block?.isFinalized ?? false
4502
4914
  });
4503
4915
  }, [listDebugEnabled, blockId, depth, ordered, childIds, listItemIds, store, block?.isFinalized]);
4504
- const listStyle = import_react10.default.useMemo(() => {
4505
- if (!ordered || listItemIds.length === 0) return void 0;
4916
+ const { listStyle, markerDigits } = import_react10.default.useMemo(() => {
4917
+ if (!ordered || listItemIds.length === 0) {
4918
+ return { listStyle: void 0, markerDigits: 1 };
4919
+ }
4506
4920
  let maxDigits = 1;
4507
4921
  listItemIds.forEach((childId, index) => {
4508
4922
  const childNode = store.getNode(childId);
@@ -4514,9 +4928,14 @@ var ListBlockView = import_react10.default.memo(
4514
4928
  const digits = digitMatch ? digitMatch.length : text.length;
4515
4929
  if (digits > maxDigits) maxDigits = digits;
4516
4930
  });
4517
- const baseIndent = depth === 0 ? "2.5rem" : depth === 1 ? "2rem" : "1.5rem";
4931
+ const baseIndent = depth === 0 ? "2rem" : depth === 1 ? "1.75rem" : "1.5rem";
4932
+ const extraDigits = Math.max(0, maxDigits - 1);
4518
4933
  return {
4519
- ["--list-indent"]: `calc(${baseIndent} + ${maxDigits}ch)`
4934
+ markerDigits: maxDigits,
4935
+ listStyle: {
4936
+ ["--list-indent"]: extraDigits > 0 ? `calc(${baseIndent} + ${extraDigits}ch)` : baseIndent,
4937
+ ["--list-marker-digits"]: String(maxDigits)
4938
+ }
4520
4939
  };
4521
4940
  }, [ordered, listItemIds, store, depth]);
4522
4941
  if (listItemIds.length === 0) return null;
@@ -4525,6 +4944,7 @@ var ListBlockView = import_react10.default.memo(
4525
4944
  {
4526
4945
  className: `markdown-list ${ordered ? "ordered" : "unordered"}`,
4527
4946
  "data-list-depth": depth,
4947
+ "data-marker-digits": ordered ? markerDigits : void 0,
4528
4948
  "data-list-id": listDebugEnabled ? blockId : void 0,
4529
4949
  style: listStyle,
4530
4950
  children: listItemIds.map((childId, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -4706,6 +5126,7 @@ var CodeBlockView = import_react10.default.memo(({ store, blockId, registry }) =
4706
5126
  deferredConfig ? { ...deferredConfig, enabled: true } : { enabled: false }
4707
5127
  );
4708
5128
  if (!node || !node.block) return null;
5129
+ const blockIsFinalized = node.block.isFinalized;
4709
5130
  const nodeVersion = node.version;
4710
5131
  const lines = import_react10.default.useMemo(() => {
4711
5132
  const seenIds = /* @__PURE__ */ new Set();
@@ -4736,13 +5157,25 @@ var CodeBlockView = import_react10.default.memo(({ store, blockId, registry }) =
4736
5157
  const lang = typeof node.props?.lang === "string" ? node.props?.lang : node.block.payload.meta?.lang;
4737
5158
  const preAttrs = node.props?.preAttrs ?? void 0;
4738
5159
  const codeAttrs = node.props?.codeAttrs ?? void 0;
5160
+ const blockMeta = node.block.payload.meta ?? void 0;
5161
+ const lazyEnabled = Boolean(blockMeta?.lazyTokenization);
5162
+ const lazyTokenizedUntil = typeof blockMeta?.lazyTokenizedUntil === "number" ? blockMeta.lazyTokenizedUntil : 0;
5163
+ const lazyHighlightedLines = Array.isArray(blockMeta?.highlightedLines) ? blockMeta.highlightedLines.length : 0;
5164
+ const lazyTokenLines = Array.isArray(blockMeta?.tokenLines) ? blockMeta.tokenLines.length : 0;
5165
+ const lazySourceTextLineCount = import_react10.default.useMemo(() => {
5166
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
5167
+ const metaCode = typeof blockMeta?.code === "string" ? blockMeta.code : null;
5168
+ const sourceText = resolveCodeSourceText(raw, metaCode);
5169
+ if (sourceText.length === 0) return 0;
5170
+ return sourceText.split("\n").length;
5171
+ }, [blockMeta, node.block?.payload.raw]);
5172
+ const totalLazyLineCount = Math.max(lines.length, lazyHighlightedLines, lazyTokenLines, lazySourceTextLineCount);
4739
5173
  const virtualizationConfig = DEFAULT_VIRTUALIZED_CODE_CONFIG;
4740
5174
  const virtualizationDisabled = typeof process !== "undefined" && typeof process.env === "object" && process.env.STREAM_MDX_DISABLE_VIRTUALIZED_CODE === "true";
4741
- const shouldVirtualize = !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
5175
+ const canVirtualize = node.block.isFinalized && !lazyEnabled;
5176
+ const shouldVirtualize = canVirtualize && !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
4742
5177
  const virtualization = useVirtualizedCode(lines, shouldVirtualize ? virtualizationConfig : { ...virtualizationConfig, enabled: false });
4743
5178
  const highlightRequester = useCodeHighlightRequester();
4744
- const lazyEnabled = Boolean(node.block.payload.meta?.lazyTokenization);
4745
- const lazyTokenizedUntil = typeof node.block.payload.meta?.lazyTokenizedUntil === "number" ? node.block.payload.meta.lazyTokenizedUntil : 0;
4746
5179
  const lastRangeRef = import_react10.default.useRef(null);
4747
5180
  const rafRef = import_react10.default.useRef(null);
4748
5181
  const idleRef = import_react10.default.useRef(null);
@@ -4823,29 +5256,111 @@ var CodeBlockView = import_react10.default.memo(({ store, blockId, registry }) =
4823
5256
  virtualization.window.startIndex,
4824
5257
  virtualization.window.endIndex
4825
5258
  ]);
4826
- const highlightedHtml = import_react10.default.useMemo(() => {
4827
- if (!shouldVirtualize && node.block?.payload.highlightedHtml) {
4828
- return node.block.payload.highlightedHtml;
5259
+ import_react10.default.useEffect(() => {
5260
+ if (shouldVirtualize || !blockIsFinalized || !lazyEnabled || !highlightRequester) return;
5261
+ if (totalLazyLineCount <= 0 || lazyTokenizedUntil >= totalLazyLineCount) return;
5262
+ const nextRange = {
5263
+ visibleStart: 0,
5264
+ visibleEnd: totalLazyLineCount,
5265
+ prefetchStart: 0,
5266
+ prefetchEnd: totalLazyLineCount,
5267
+ tokenizedUntil: lazyTokenizedUntil
5268
+ };
5269
+ const last = lastRangeRef.current;
5270
+ if (last && last.visibleStart === nextRange.visibleStart && last.visibleEnd === nextRange.visibleEnd && last.prefetchStart === nextRange.prefetchStart && last.prefetchEnd === nextRange.prefetchEnd && last.tokenizedUntil === nextRange.tokenizedUntil) {
5271
+ return;
5272
+ }
5273
+ lastRangeRef.current = nextRange;
5274
+ if (rafRef.current !== null && typeof cancelAnimationFrame === "function") {
5275
+ cancelAnimationFrame(rafRef.current);
5276
+ }
5277
+ const requestFullHighlight = () => {
5278
+ highlightRequester({
5279
+ blockId,
5280
+ startLine: 0,
5281
+ endLine: totalLazyLineCount,
5282
+ priority: "visible",
5283
+ reason: "finalize-full"
5284
+ });
5285
+ rafRef.current = null;
5286
+ };
5287
+ if (typeof requestAnimationFrame === "function") {
5288
+ rafRef.current = requestAnimationFrame(requestFullHighlight);
5289
+ } else {
5290
+ requestFullHighlight();
4829
5291
  }
5292
+ }, [blockId, blockIsFinalized, highlightRequester, lazyEnabled, lazyTokenizedUntil, shouldVirtualize, totalLazyLineCount]);
5293
+ const highlightedHtml = import_react10.default.useMemo(() => {
5294
+ const blockHtml = node.block?.payload.highlightedHtml ?? "";
5295
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
5296
+ const metaCode = typeof node.block?.payload.meta?.code === "string" ? (node.block?.payload.meta).code : null;
5297
+ const sourceText = resolveCodeSourceText(raw, metaCode);
5298
+ const sourceEndsWithNewline = sourceText.endsWith("\n");
5299
+ const sourceLines = sourceText.length > 0 ? sourceText.split("\n") : [];
5300
+ const rawFallbackLines = buildCodeFallbackLines(raw);
5301
+ const deterministicWrapperAttrs = (0, import_core5.getDefaultCodeWrapperAttributes)(lang);
5302
+ const composedPreAttrs = deterministicWrapperAttrs.preAttrs;
5303
+ const composedCodeAttrs = deterministicWrapperAttrs.codeAttrs;
5304
+ const shouldAppendTerminalNewline = (renderedLines) => {
5305
+ if (!sourceEndsWithNewline || renderedLines.length === 0 || lines.length === 0) {
5306
+ return false;
5307
+ }
5308
+ const lastRendered = renderedLines[renderedLines.length - 1];
5309
+ const lastKnown = lines[lines.length - 1];
5310
+ if (!lastRendered || !lastKnown || lastRendered.index !== lastKnown.index || lastRendered.text === "") {
5311
+ return false;
5312
+ }
5313
+ const expectedRenderedLineCount = Math.max(0, sourceLines.length - 1);
5314
+ const expectedLastRenderedLine = sourceLines.length >= 2 ? sourceLines[sourceLines.length - 2] ?? "" : "";
5315
+ return renderedLines.length === expectedRenderedLineCount && lastRendered.text === expectedLastRenderedLine;
5316
+ };
4830
5317
  if (lines.length > 0) {
4831
5318
  const hasLineHtml = lines.some((line) => typeof line.html === "string" && line.html.length > 0);
4832
- if (!hasLineHtml && node.block?.payload.highlightedHtml) {
4833
- return node.block.payload.highlightedHtml;
5319
+ if (!hasLineHtml && blockHtml) {
5320
+ return blockHtml;
4834
5321
  }
4835
5322
  if (shouldVirtualize) {
4836
- return composeHighlightedHtml(virtualization.window.visibleLines, preAttrs, codeAttrs);
5323
+ return composeHighlightedHtml(
5324
+ virtualization.window.visibleLines,
5325
+ composedPreAttrs,
5326
+ composedCodeAttrs,
5327
+ shouldAppendTerminalNewline(virtualization.window.visibleLines)
5328
+ );
4837
5329
  }
4838
- return composeHighlightedHtml(lines, preAttrs, codeAttrs);
5330
+ return composeHighlightedHtml(lines, composedPreAttrs, composedCodeAttrs, shouldAppendTerminalNewline(lines));
5331
+ }
5332
+ if (!shouldVirtualize && blockHtml) {
5333
+ const hasHighlightedLines = blockHtml.includes('class="line"');
5334
+ if (!hasHighlightedLines && rawFallbackLines.length > 0) {
5335
+ return composeHighlightedHtml(
5336
+ rawFallbackLines,
5337
+ composedPreAttrs,
5338
+ composedCodeAttrs,
5339
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5340
+ );
5341
+ }
5342
+ return blockHtml;
4839
5343
  }
4840
- const blockHtml = node.block?.payload.highlightedHtml ?? "";
4841
5344
  if (blockHtml?.trim().match(/^```[\w-]*\s*$/)) {
4842
- const raw = node.block?.payload.raw ?? "";
4843
- if (raw.trim().length > 0) {
4844
- return composeHighlightedHtml([{ index: 0, text: raw, html: null, id: "temp" }], preAttrs, codeAttrs);
5345
+ if (rawFallbackLines.length > 0) {
5346
+ return composeHighlightedHtml(
5347
+ rawFallbackLines,
5348
+ composedPreAttrs,
5349
+ composedCodeAttrs,
5350
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5351
+ );
4845
5352
  }
4846
5353
  }
5354
+ if (!blockHtml && rawFallbackLines.length > 0) {
5355
+ return composeHighlightedHtml(
5356
+ rawFallbackLines,
5357
+ composedPreAttrs,
5358
+ composedCodeAttrs,
5359
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5360
+ );
5361
+ }
4847
5362
  return blockHtml;
4848
- }, [shouldVirtualize, node.block?.payload.highlightedHtml, lines, preAttrs, codeAttrs, virtualization.window.visibleLines, node.block?.payload.raw]);
5363
+ }, [blockIsFinalized, shouldVirtualize, node.block?.payload.highlightedHtml, node.block?.payload.meta, lines, lang, virtualization.window.visibleLines, node.block?.payload.raw]);
4849
5364
  const rendered = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4850
5365
  CodeComponent,
4851
5366
  {
@@ -4857,13 +5372,19 @@ var CodeBlockView = import_react10.default.memo(({ store, blockId, registry }) =
4857
5372
  codeAttrs
4858
5373
  }
4859
5374
  );
5375
+ const effectiveLineCount = import_react10.default.useMemo(() => {
5376
+ if (lines.length > 0) {
5377
+ return lines.length;
5378
+ }
5379
+ return 1;
5380
+ }, [lines.length]);
4860
5381
  const codeFrameClass = "not-prose flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
4861
5382
  const codeMetricsAttrs = {
4862
5383
  "data-code-block": "true",
4863
5384
  "data-block-id": blockId,
4864
5385
  "data-code-virtualized": shouldVirtualize ? "true" : "false",
4865
- "data-code-total-lines": String(lines.length),
4866
- "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : lines.length),
5386
+ "data-code-total-lines": String(effectiveLineCount),
5387
+ "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : effectiveLineCount),
4867
5388
  "data-code-window-size": String(virtualization.config.windowSize)
4868
5389
  };
4869
5390
  const codeView = shouldVirtualize ? (() => {
@@ -4909,21 +5430,152 @@ var CodeBlockView = import_react10.default.memo(({ store, blockId, registry }) =
4909
5430
  return codeView;
4910
5431
  });
4911
5432
  CodeBlockView.displayName = "CodeBlockView";
4912
- function composeHighlightedHtml(lines, preAttrs, codeAttrs) {
5433
+ function composeHighlightedHtml(lines, preAttrs, codeAttrs, appendTerminalNewline = false) {
4913
5434
  const lineMarkup = lines.map((line) => {
4914
- const content = line.html ?? escapeHtml3(line.text);
5435
+ const content = line.html ?? renderTokenLineHtml(line.tokens) ?? escapeHtml3(line.text);
4915
5436
  const dataLine = Number.isFinite(line.index) ? ` data-line="${line.index + 1}"` : "";
4916
5437
  return `<span class="line"${dataLine}>${content}</span>`;
4917
5438
  }).join("\n");
4918
5439
  const preAttr = attrsToString(preAttrs);
4919
5440
  const codeAttr = attrsToString(codeAttrs);
4920
- return `<pre${preAttr}><code${codeAttr}>${lineMarkup}
4921
- </code></pre>`;
5441
+ return `<pre${preAttr}><code${codeAttr}>${lineMarkup}${appendTerminalNewline ? "\n" : ""}</code></pre>`;
5442
+ }
5443
+ var TOKEN_FONT_STYLE_ITALIC = 1;
5444
+ var TOKEN_FONT_STYLE_BOLD = 2;
5445
+ var TOKEN_FONT_STYLE_UNDERLINE = 4;
5446
+ var TOKEN_FONT_STYLE_STRIKETHROUGH = 8;
5447
+ function renderTokenLineHtml(tokens) {
5448
+ if (!tokens || !Array.isArray(tokens.spans)) {
5449
+ return null;
5450
+ }
5451
+ return mergeRenderableTokenSpans(tokens.spans).map((span) => renderTokenSpanHtml(span)).join("");
5452
+ }
5453
+ function renderTokenSpanHtml(span) {
5454
+ const dark = span.v?.dark;
5455
+ const light = span.v?.light;
5456
+ const single = span.s;
5457
+ const styles = [];
5458
+ if (dark?.fg) {
5459
+ styles.push(`--shiki-dark:${dark.fg}`);
5460
+ }
5461
+ if (light?.fg) {
5462
+ styles.push(`--shiki-light:${light.fg}`);
5463
+ }
5464
+ if (!light?.fg && !dark?.fg && single?.fg) {
5465
+ styles.push(`color:${single.fg}`);
5466
+ }
5467
+ const fontStyle = light?.fs ?? dark?.fs ?? single?.fs;
5468
+ if (typeof fontStyle === "number" && fontStyle > 0) {
5469
+ if (fontStyle & TOKEN_FONT_STYLE_ITALIC) {
5470
+ styles.push("font-style: italic");
5471
+ }
5472
+ if (fontStyle & TOKEN_FONT_STYLE_BOLD) {
5473
+ styles.push("font-weight: bold");
5474
+ }
5475
+ const decorations = [];
5476
+ if (fontStyle & TOKEN_FONT_STYLE_UNDERLINE) {
5477
+ decorations.push("underline");
5478
+ }
5479
+ if (fontStyle & TOKEN_FONT_STYLE_STRIKETHROUGH) {
5480
+ decorations.push("line-through");
5481
+ }
5482
+ if (decorations.length > 0) {
5483
+ styles.push(`text-decoration: ${decorations.join(" ")}`);
5484
+ }
5485
+ }
5486
+ const content = escapeHtml3(span.t ?? "");
5487
+ if (styles.length === 0) {
5488
+ return content;
5489
+ }
5490
+ return `<span style="${styles.join(";")}">${content}</span>`;
5491
+ }
5492
+ function mergeRenderableTokenSpans(spans) {
5493
+ if (spans.length <= 1) {
5494
+ return spans.slice();
5495
+ }
5496
+ const merged = [];
5497
+ let pendingWhitespace = "";
5498
+ for (const span of spans) {
5499
+ const text = typeof span.t === "string" ? span.t : "";
5500
+ if (/^[\t ]+$/.test(text)) {
5501
+ pendingWhitespace += text;
5502
+ continue;
5503
+ }
5504
+ const nextSpan = pendingWhitespace ? { ...span, t: `${pendingWhitespace}${text}` } : span;
5505
+ pendingWhitespace = "";
5506
+ const previous = merged[merged.length - 1];
5507
+ if (previous && sameTokenSpanStyle(previous, nextSpan)) {
5508
+ previous.t += nextSpan.t;
5509
+ } else {
5510
+ merged.push({ ...nextSpan });
5511
+ }
5512
+ }
5513
+ if (pendingWhitespace) {
5514
+ const previous = merged[merged.length - 1];
5515
+ if (previous) {
5516
+ previous.t += pendingWhitespace;
5517
+ } else {
5518
+ merged.push({ t: pendingWhitespace });
5519
+ }
5520
+ }
5521
+ return merged;
5522
+ }
5523
+ function sameTokenSpanStyle(a, b) {
5524
+ const variantKeys = /* @__PURE__ */ new Set([
5525
+ ...Object.keys(a.v ?? {}),
5526
+ ...Object.keys(b.v ?? {})
5527
+ ]);
5528
+ for (const key of variantKeys) {
5529
+ const aStyle = a.v?.[key];
5530
+ const bStyle = b.v?.[key];
5531
+ if ((aStyle?.fg ?? null) !== (bStyle?.fg ?? null)) return false;
5532
+ if ((aStyle?.bg ?? null) !== (bStyle?.bg ?? null)) return false;
5533
+ if ((aStyle?.fs ?? null) !== (bStyle?.fs ?? null)) return false;
5534
+ }
5535
+ if ((a.s?.fg ?? null) !== (b.s?.fg ?? null)) return false;
5536
+ if ((a.s?.bg ?? null) !== (b.s?.bg ?? null)) return false;
5537
+ if ((a.s?.fs ?? null) !== (b.s?.fs ?? null)) return false;
5538
+ return true;
5539
+ }
5540
+ function resolveCodeSourceText(raw, metaCode) {
5541
+ if (metaCode !== null) {
5542
+ return metaCode.replace(/\r\n?/g, "\n");
5543
+ }
5544
+ if (!raw) return "";
5545
+ const normalized = raw.replace(/\r\n?/g, "\n");
5546
+ const fenced = (0, import_core5.stripCodeFence)(normalized);
5547
+ return fenced.hadFence ? fenced.code : normalized;
4922
5548
  }
4923
5549
  function attrsToString(attrs) {
4924
5550
  if (!attrs) return "";
4925
5551
  return Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
4926
5552
  }
5553
+ function buildCodeFallbackLines(raw) {
5554
+ const normalized = normalizeCodeFallbackRaw(raw);
5555
+ if (normalized.length === 0) {
5556
+ if (/^\s*```[^\n]*\n?\s*$/.test(raw)) {
5557
+ return [{ id: "temp-0", index: 0, text: "", html: null }];
5558
+ }
5559
+ return [];
5560
+ }
5561
+ return normalized.split("\n").map((line, index) => ({
5562
+ id: `temp-${index}`,
5563
+ index,
5564
+ text: line,
5565
+ html: null
5566
+ }));
5567
+ }
5568
+ function normalizeCodeFallbackRaw(raw) {
5569
+ if (!raw) return "";
5570
+ const normalized = raw.replace(/\r\n?/g, "\n");
5571
+ const fenced = (0, import_core5.stripCodeFence)(normalized);
5572
+ if (!fenced.hadFence) {
5573
+ return normalized.replace(/\n+$/, "");
5574
+ }
5575
+ let code = fenced.code;
5576
+ code = code.replace(/\n+$/, "");
5577
+ return code;
5578
+ }
4927
5579
  function escapeHtml3(value) {
4928
5580
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4929
5581
  }
@@ -5047,7 +5699,9 @@ var MarkdownRenderer = class {
5047
5699
  lowPriorityFrameBudgetMs: this.config.performance?.lowPriorityFrameBudgetMs,
5048
5700
  urgentQueueThreshold: this.config.performance?.urgentQueueThreshold,
5049
5701
  batch: this.config.performance?.batch,
5050
- historyLimit: this.config.performance?.historyLimit
5702
+ historyLimit: this.config.performance?.historyLimit,
5703
+ startupMicrotaskFlushes: this.config.performance?.startupMicrotaskFlushes,
5704
+ adaptiveBudgeting: this.config.performance?.adaptiveBudgeting
5051
5705
  }
5052
5706
  });
5053
5707
  }
@@ -5241,18 +5895,8 @@ var MarkdownRenderer = class {
5241
5895
  const receivedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
5242
5896
  const batches = (0, import_patch_batching.splitPatchBatch)(message.patches);
5243
5897
  batches.forEach((batch, index) => {
5244
- const hasStructural = batch.some((patch) => {
5245
- switch (patch.op) {
5246
- case "insertChild":
5247
- case "replaceChild":
5248
- case "deleteChild":
5249
- case "reorder":
5250
- return true;
5251
- default:
5252
- return false;
5253
- }
5254
- });
5255
- const priority = hasStructural ? "high" : batch.some(import_patch_batching.isHeavyPatch) ? "low" : "high";
5898
+ const hasSemantic = batch.some((patch) => (0, import_patch_batching.getPatchKind)(patch) === "semantic");
5899
+ const priority = hasSemantic ? "high" : batch.some(import_patch_batching.isHeavyPatch) ? "low" : "high";
5256
5900
  this.patchScheduler.enqueue({
5257
5901
  patches: batch,
5258
5902
  meta: {
@@ -5397,13 +6041,14 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5397
6041
  const mode = options?.mode ?? "server";
5398
6042
  const [compiledMap, setCompiledMap] = (0, import_react13.useState)(/* @__PURE__ */ new Map());
5399
6043
  const blockSignatureRef = (0, import_react13.useRef)(/* @__PURE__ */ new Map());
6044
+ const failedSignatureRef = (0, import_react13.useRef)(/* @__PURE__ */ new Map());
5400
6045
  const applyCompiledRef = (0, import_react13.useCallback)(
5401
6046
  (block, compiledId) => {
5402
6047
  if (mode === "worker") {
5403
6048
  return;
5404
6049
  }
5405
6050
  if (options?.workerClient) {
5406
- options.workerClient.setMdxCompiled(block.id, compiledId);
6051
+ options.workerClient.setMdxCompiled(block.id, compiledId, block.payload.raw ?? "");
5407
6052
  return;
5408
6053
  }
5409
6054
  const store = options?.store;
@@ -5414,6 +6059,9 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5414
6059
  const index = currentBlocks.findIndex((candidate) => candidate.id === block.id);
5415
6060
  if (index === -1) return;
5416
6061
  const current = currentBlocks[index];
6062
+ if ((current.payload.raw ?? "") !== (block.payload.raw ?? "")) {
6063
+ return;
6064
+ }
5417
6065
  if (current.payload.compiledMdxRef?.id === compiledId) {
5418
6066
  return;
5419
6067
  }
@@ -5443,7 +6091,13 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5443
6091
  if (mode === "worker") {
5444
6092
  return;
5445
6093
  }
5446
- const targets = blocks.filter((b) => b.type === "mdx" && b.isFinalized && !b.payload.compiledMdxRef && !compiledMap.has(b.id));
6094
+ const targets = blocks.filter((b) => {
6095
+ if (b.type !== "mdx" || !b.isFinalized || b.payload.compiledMdxRef || compiledMap.has(b.id)) {
6096
+ return false;
6097
+ }
6098
+ const raw = b.payload.raw ?? "";
6099
+ return failedSignatureRef.current.get(b.id) !== raw;
6100
+ });
5447
6101
  if (targets.length === 0) return;
5448
6102
  let cancelled = false;
5449
6103
  (async () => {
@@ -5462,22 +6116,29 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5462
6116
  next.set(block.id, compiled.id);
5463
6117
  return next;
5464
6118
  });
6119
+ failedSignatureRef.current.delete(block.id);
5465
6120
  applyCompiledRef(block, compiled.id);
5466
6121
  } catch (e) {
5467
6122
  console.warn("MDX compile failed for block", block.id, e);
6123
+ failedSignatureRef.current.set(block.id, block.payload.raw ?? "");
5468
6124
  if (options?.workerClient) {
5469
- options.workerClient.setMdxError(block.id, e instanceof Error ? e.message : String(e));
6125
+ options.workerClient.setMdxError(block.id, e instanceof Error ? e.message : String(e), block.payload.raw ?? "");
5470
6126
  } else if (options?.store) {
6127
+ const currentBlocks = options.store.getBlocks();
6128
+ const current = currentBlocks.find((candidate) => candidate.id === block.id);
6129
+ if (!current || (current.payload.raw ?? "") !== (block.payload.raw ?? "")) {
6130
+ continue;
6131
+ }
5471
6132
  options.store.applyPatches([
5472
6133
  {
5473
6134
  op: "setProps",
5474
6135
  at: { blockId: block.id, nodeId: block.id },
5475
6136
  props: {
5476
6137
  block: {
5477
- ...block,
6138
+ ...current,
5478
6139
  payload: {
5479
- ...block.payload,
5480
- meta: { ...block.payload.meta ?? {}, mdxStatus: "error", mdxError: e instanceof Error ? e.message : String(e) }
6140
+ ...current.payload,
6141
+ meta: { ...current.payload.meta ?? {}, mdxStatus: "error", mdxError: e instanceof Error ? e.message : String(e) }
5481
6142
  }
5482
6143
  }
5483
6144
  }
@@ -5510,6 +6171,7 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5510
6171
  const currentSignature = candidate.payload.raw ?? "";
5511
6172
  if (previousSignature !== void 0 && previousSignature !== currentSignature) {
5512
6173
  next.delete(blockId);
6174
+ failedSignatureRef.current.delete(blockId);
5513
6175
  mutated = true;
5514
6176
  }
5515
6177
  }
@@ -5519,6 +6181,9 @@ function useMdxCoordinator(blocks, compileEndpoint, options) {
5519
6181
  for (const block of blocks) {
5520
6182
  if (block.type === "mdx") {
5521
6183
  signatureMap.set(block.id, block.payload.raw ?? "");
6184
+ if (!block.isFinalized || block.payload.compiledMdxRef) {
6185
+ failedSignatureRef.current.delete(block.id);
6186
+ }
5522
6187
  }
5523
6188
  }
5524
6189
  blockSignatureRef.current = signatureMap;
@@ -5876,7 +6541,9 @@ function StreamingMarkdownComponent({
5876
6541
  lowPriorityFrameBudgetMs: effectiveScheduling.lowPriorityFrameBudgetMs,
5877
6542
  urgentQueueThreshold: effectiveScheduling.urgentQueueThreshold,
5878
6543
  batch: effectiveScheduling.batch,
5879
- historyLimit
6544
+ historyLimit,
6545
+ startupMicrotaskFlushes: effectiveScheduling.startupMicrotaskFlushes,
6546
+ adaptiveBudgeting: effectiveScheduling.adaptiveBudgeting
5880
6547
  },
5881
6548
  mdx: mdxConfig
5882
6549
  };