@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
package/dist/renderer.cjs CHANGED
@@ -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;
@@ -952,7 +952,11 @@ var defaultInlineComponents = {
952
952
  const n = number ?? "?";
953
953
  const href = number ? `#fn:${n}` : void 0;
954
954
  const id = number ? `fnref:${n}` : void 0;
955
- return import_react5.default.createElement("sup", { className: "footnote-ref" }, import_react5.default.createElement("a", { href, id, "data-label": label }, String(n)));
955
+ return import_react5.default.createElement(
956
+ "sup",
957
+ { className: "footnote-ref" },
958
+ import_react5.default.createElement("a", { href, id, "data-label": label, style: { color: "var(--foreground)", textDecoration: "underline" } }, String(n))
959
+ );
956
960
  }
957
961
  };
958
962
  var defaultBlockComponents = {
@@ -1095,7 +1099,7 @@ var defaultBlockComponents = {
1095
1099
  dangerouslySetInnerHTML: { __html: htmlString }
1096
1100
  });
1097
1101
  },
1098
- mdx: ({ compiledRef, compiledModule, status, errorMessage, raw }) => {
1102
+ mdx: ({ compiledRef, compiledModule, status, errorMessage }) => {
1099
1103
  const [Comp, setComp] = import_react5.default.useState(null);
1100
1104
  const [internalError, setInternalError] = import_react5.default.useState(null);
1101
1105
  const [renderError, setRenderError] = import_react5.default.useState(null);
@@ -1196,7 +1200,6 @@ var defaultBlockComponents = {
1196
1200
  const waitingForVisibility = deferHydration && !shouldHydrate && Boolean(resolvedId);
1197
1201
  const pendingLabel = waitingForVisibility ? "MDX ready (hydrate on view)\u2026" : compileMode === "worker" ? "Compiling MDX (client)\u2026" : "Compiling MDX (server)\u2026";
1198
1202
  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.");
1199
- const rawFallback = typeof raw === "string" && raw.trim().length > 0 ? import_react5.default.createElement("pre", { className: "markdown-mdx-fallback" }, raw) : null;
1200
1203
  const allowRetry = Boolean(internalError || renderError);
1201
1204
  const retryButton = allowRetry ? import_react5.default.createElement(
1202
1205
  "button",
@@ -1208,9 +1211,10 @@ var defaultBlockComponents = {
1208
1211
  "Retry hydration"
1209
1212
  ) : null;
1210
1213
  const errorPanel = import_react5.default.createElement(
1211
- "div",
1212
- { className: "markdown-mdx-error" },
1213
- import_react5.default.createElement(import_react5.default.Fragment, null, import_react5.default.createElement("strong", null, "MDX failed"), import_react5.default.createElement("div", null, message))
1214
+ import_react5.default.Fragment,
1215
+ null,
1216
+ import_react5.default.createElement("strong", null, "MDX failed"),
1217
+ import_react5.default.createElement("div", null, message)
1214
1218
  );
1215
1219
  if (Comp && !failureMessage) {
1216
1220
  return import_react5.default.createElement(
@@ -1233,14 +1237,13 @@ var defaultBlockComponents = {
1233
1237
  return import_react5.default.createElement(
1234
1238
  "div",
1235
1239
  {
1236
- className: "markdown-mdx",
1240
+ className: "markdown-mdx error",
1237
1241
  ref: containerRef,
1238
1242
  "data-mdx-ref": pendingId,
1239
1243
  "data-mdx-status": "error",
1240
1244
  "data-mdx-mode": compileMode
1241
1245
  },
1242
1246
  errorPanel,
1243
- rawFallback,
1244
1247
  retryButton
1245
1248
  );
1246
1249
  }
@@ -1267,7 +1270,16 @@ var defaultBlockComponents = {
1267
1270
  { key: item.number, id: `fn:${item.number}` },
1268
1271
  renderInlineNodes(item.inlines, defaultInlineComponents),
1269
1272
  " ",
1270
- import_react5.default.createElement("a", { href: `#fnref:${item.number}`, className: "footnote-backref", "aria-label": "Back to content" }, "\u21A9")
1273
+ import_react5.default.createElement(
1274
+ "a",
1275
+ {
1276
+ href: `#fnref:${item.number}`,
1277
+ className: "footnote-backref",
1278
+ "aria-label": "Back to content",
1279
+ style: { color: "var(--foreground)", textDecoration: "underline" }
1280
+ },
1281
+ "\u21A9"
1282
+ )
1271
1283
  )
1272
1284
  );
1273
1285
  return import_react5.default.createElement("section", { className: "footnotes" }, import_react5.default.createElement("hr", {}), import_react5.default.createElement("ol", {}, listItems));
@@ -1815,6 +1827,9 @@ function useCodeHighlightRequester() {
1815
1827
  // src/renderer/patch-batching.ts
1816
1828
  var import_patch_batching = require("@stream-mdx/core/perf/patch-batching");
1817
1829
 
1830
+ // src/renderer/patch-commit-scheduler.ts
1831
+ var import_patch_batching2 = require("@stream-mdx/core/perf/patch-batching");
1832
+
1818
1833
  // src/renderer/patch-coalescing.ts
1819
1834
  var import_core3 = require("@stream-mdx/core");
1820
1835
 
@@ -1825,6 +1840,9 @@ var COALESCING_DURATION_ACTIVATE_MS = 6;
1825
1840
  var COALESCING_DURATION_DEACTIVATE_MS = 4;
1826
1841
  var RETAIN_TOUCHED_IN_HISTORY = typeof process !== "undefined" && process.env ? process.env.NODE_ENV !== "production" : true;
1827
1842
  var EMPTY_TOUCHED_SET = /* @__PURE__ */ new Set();
1843
+ function shouldSkipCoalescing(patches) {
1844
+ return patches.length <= 1;
1845
+ }
1828
1846
  function getDefaultNow() {
1829
1847
  if (typeof performance !== "undefined" && typeof performance.now === "function") {
1830
1848
  return () => performance.now();
@@ -1851,8 +1869,9 @@ function getDefaultCancelRaf() {
1851
1869
  }
1852
1870
  var PatchCommitScheduler = class {
1853
1871
  constructor(params) {
1854
- this.highQueue = [];
1855
- this.lowQueue = [];
1872
+ this.semanticQueue = [];
1873
+ this.enrichmentHighQueue = [];
1874
+ this.enrichmentLowQueue = [];
1856
1875
  this.scheduled = false;
1857
1876
  this.flushing = false;
1858
1877
  this.scheduledHandle = null;
@@ -1864,6 +1883,7 @@ var PatchCommitScheduler = class {
1864
1883
  this.scheduleToken = 0;
1865
1884
  this.coalescingDurationSamples = [];
1866
1885
  this.adaptiveBudgetActive = false;
1886
+ this.flushCount = 0;
1867
1887
  this.store = params.store;
1868
1888
  this.onFlush = params.onFlush;
1869
1889
  this.frameBudgetMs = Math.max(1, params.options?.frameBudgetMs ?? 10);
@@ -1879,6 +1899,8 @@ var PatchCommitScheduler = class {
1879
1899
  this.maxLowPriorityBatchesPerFlush = params.options?.maxLowPriorityBatchesPerFlush ?? 2;
1880
1900
  this.urgentQueueThreshold = Math.max(1, params.options?.urgentQueueThreshold ?? 4);
1881
1901
  this.historyLimit = Math.max(1, params.options?.historyLimit ?? 200);
1902
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(params.options?.startupMicrotaskFlushes ?? 0));
1903
+ this.adaptiveBudgeting = params.options?.adaptiveBudgeting ?? true;
1882
1904
  const requestedBatch = params.options?.batch;
1883
1905
  if (requestedBatch === "microtask") {
1884
1906
  this.batchStrategy = "microtask";
@@ -1905,19 +1927,23 @@ var PatchCommitScheduler = class {
1905
1927
  patches: input.patches,
1906
1928
  meta: input.meta ?? {},
1907
1929
  receivedAt: input.meta?.receivedAt ?? this.now(),
1930
+ 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",
1908
1931
  priority: input.meta?.priority === "low" ? "low" : "high"
1909
1932
  };
1910
- if (batch.priority === "low") {
1911
- this.lowQueue.push(batch);
1933
+ if (batch.kind === "semantic") {
1934
+ batch.priority = "high";
1935
+ this.semanticQueue.push(batch);
1936
+ } else if (batch.priority === "low") {
1937
+ this.enrichmentLowQueue.push(batch);
1912
1938
  } else {
1913
- this.highQueue.push(batch);
1939
+ this.enrichmentHighQueue.push(batch);
1914
1940
  }
1915
1941
  if (!this.paused) {
1916
1942
  this.schedule();
1917
1943
  }
1918
1944
  }
1919
1945
  flushAll() {
1920
- if (this.highQueue.length === 0 && this.lowQueue.length === 0 && !this.flushing) {
1946
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0 && !this.flushing) {
1921
1947
  return null;
1922
1948
  }
1923
1949
  this.cancelScheduled();
@@ -1927,9 +1953,11 @@ var PatchCommitScheduler = class {
1927
1953
  }
1928
1954
  clear() {
1929
1955
  this.cancelScheduled();
1930
- this.highQueue = [];
1931
- this.lowQueue = [];
1956
+ this.semanticQueue = [];
1957
+ this.enrichmentHighQueue = [];
1958
+ this.enrichmentLowQueue = [];
1932
1959
  this.flushing = false;
1960
+ this.flushCount = 0;
1933
1961
  this.coalescingDurationSamples = [];
1934
1962
  this.adaptiveBudgetActive = false;
1935
1963
  this.adaptiveHighBatchCap = void 0;
@@ -1937,10 +1965,10 @@ var PatchCommitScheduler = class {
1937
1965
  this.maybeResolveIdle();
1938
1966
  }
1939
1967
  getPendingCount() {
1940
- return this.highQueue.length + this.lowQueue.length;
1968
+ return this.semanticQueue.length + this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
1941
1969
  }
1942
1970
  isIdle() {
1943
- return !this.flushing && !this.scheduled && this.highQueue.length === 0 && this.lowQueue.length === 0;
1971
+ return !this.flushing && !this.scheduled && this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0;
1944
1972
  }
1945
1973
  awaitIdle() {
1946
1974
  if (this.isIdle()) {
@@ -1980,7 +2008,8 @@ var PatchCommitScheduler = class {
1980
2008
  this.runFlush(this.frameBudgetMs, false);
1981
2009
  this.maybeResolveIdle();
1982
2010
  };
1983
- if (this.batchStrategy === "microtask" && typeof queueMicrotask === "function") {
2011
+ const useStartupMicrotask = this.batchStrategy !== "microtask" && this.startupMicrotaskFlushes > 0 && this.flushCount < this.startupMicrotaskFlushes && typeof queueMicrotask === "function";
2012
+ if ((this.batchStrategy === "microtask" || useStartupMicrotask) && typeof queueMicrotask === "function") {
1984
2013
  this.scheduledViaRaf = false;
1985
2014
  this.scheduledHandle = token;
1986
2015
  queueMicrotask(execute);
@@ -2010,12 +2039,14 @@ var PatchCommitScheduler = class {
2010
2039
  if (this.paused && !manual) {
2011
2040
  return null;
2012
2041
  }
2013
- if (this.highQueue.length === 0 && this.lowQueue.length === 0) {
2042
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0) {
2014
2043
  return null;
2015
2044
  }
2016
2045
  this.flushing = true;
2017
2046
  const startedAt = this.now();
2018
- const initialQueueSize = this.highQueue.length + this.lowQueue.length;
2047
+ const initialSemanticQueueSize = this.semanticQueue.length;
2048
+ const initialEnrichmentQueueSize = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
2049
+ const initialQueueSize = initialSemanticQueueSize + initialEnrichmentQueueSize;
2019
2050
  const batches = [];
2020
2051
  let totalInputPatches = 0;
2021
2052
  let totalAppliedPatches = 0;
@@ -2030,7 +2061,15 @@ var PatchCommitScheduler = class {
2030
2061
  consumed += 1;
2031
2062
  const applyStart = this.now();
2032
2063
  const queueDelayMs = Math.max(0, applyStart - next.receivedAt);
2033
- const { patches: coalesced, metrics: coalescingMetrics } = (0, import_core3.coalescePatchesWithMetrics)(next.patches, import_core3.DEFAULT_COALESCE_CONFIG);
2064
+ let coalesced;
2065
+ let coalescingMetrics;
2066
+ if (shouldSkipCoalescing(next.patches)) {
2067
+ coalesced = next.patches;
2068
+ } else {
2069
+ const result = (0, import_core3.coalescePatchesWithMetrics)(next.patches, import_core3.DEFAULT_COALESCE_CONFIG);
2070
+ coalesced = result.patches;
2071
+ coalescingMetrics = result.metrics;
2072
+ }
2034
2073
  const touched = this.store.applyPatches(coalesced, {
2035
2074
  coalesced: true,
2036
2075
  metrics: coalescingMetrics
@@ -2050,6 +2089,7 @@ var PatchCommitScheduler = class {
2050
2089
  receivedAt: next.receivedAt,
2051
2090
  appliedAt: applyStart,
2052
2091
  priority: next.priority,
2092
+ kind: next.kind,
2053
2093
  coalescing: coalescingMetrics
2054
2094
  });
2055
2095
  if (maxBatches && batches.length - startingBatchCount >= maxBatches) {
@@ -2066,27 +2106,32 @@ var PatchCommitScheduler = class {
2066
2106
  queue.splice(0, consumed);
2067
2107
  }
2068
2108
  };
2069
- const initialHighQueueSize = this.highQueue.length;
2109
+ processQueue(this.semanticQueue, void 0, Number.POSITIVE_INFINITY);
2110
+ const initialHighQueueSize = this.enrichmentHighQueue.length;
2070
2111
  const configuredHighLimit = manual || !this.maxBatchesPerFlush ? void 0 : initialHighQueueSize > this.maxBatchesPerFlush ? void 0 : this.maxBatchesPerFlush;
2071
- const highBatchLimit = this.adaptiveHighBatchCap ?? configuredHighLimit;
2072
- processQueue(this.highQueue, highBatchLimit, budgetMs);
2112
+ const highBatchLimit = this.adaptiveBudgeting ? this.adaptiveHighBatchCap ?? configuredHighLimit : configuredHighLimit;
2113
+ processQueue(this.enrichmentHighQueue, highBatchLimit, budgetMs);
2073
2114
  if (manual) {
2074
- processQueue(this.lowQueue, void 0, Number.POSITIVE_INFINITY);
2075
- } else if (this.lowQueue.length > 0) {
2115
+ processQueue(this.enrichmentLowQueue, void 0, Number.POSITIVE_INFINITY);
2116
+ } else if (this.enrichmentLowQueue.length > 0) {
2076
2117
  elapsed = this.now() - startedAt;
2077
2118
  const budgetForLow = Math.max(0, this.lowPriorityFrameBudgetMs - elapsed);
2078
- if (budgetForLow > 0) {
2079
- const initialLowQueueSize = this.lowQueue.length;
2119
+ const processedHigherPriorityWork = batches.length > 0;
2120
+ if (budgetForLow > 0 || !processedHigherPriorityWork) {
2121
+ const initialLowQueueSize = this.enrichmentLowQueue.length;
2080
2122
  const configuredLowLimit = manual || !this.maxLowPriorityBatchesPerFlush ? void 0 : initialLowQueueSize > this.maxLowPriorityBatchesPerFlush ? void 0 : this.maxLowPriorityBatchesPerFlush;
2081
- const lowBatchLimit = this.adaptiveLowBatchCap ?? configuredLowLimit;
2082
- processQueue(this.lowQueue, lowBatchLimit, budgetForLow);
2123
+ const lowBatchLimit = this.adaptiveBudgeting ? this.adaptiveLowBatchCap ?? configuredLowLimit : configuredLowLimit;
2124
+ processQueue(this.enrichmentLowQueue, lowBatchLimit, budgetForLow);
2083
2125
  }
2084
2126
  }
2085
2127
  this.flushing = false;
2086
2128
  const completedAt = this.now();
2087
2129
  const totalDurationMs = completedAt - startedAt;
2088
- const remaining = this.highQueue.length + this.lowQueue.length;
2130
+ const remainingSemantic = this.semanticQueue.length;
2131
+ const remainingEnrichment = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
2132
+ const remaining = remainingSemantic + remainingEnrichment;
2089
2133
  if (batches.length > 0) {
2134
+ this.flushCount += 1;
2090
2135
  try {
2091
2136
  this.onFlush({
2092
2137
  batches,
@@ -2094,7 +2139,11 @@ var PatchCommitScheduler = class {
2094
2139
  totalAppliedPatches,
2095
2140
  totalDurationMs,
2096
2141
  remainingQueueSize: remaining,
2142
+ remainingSemanticQueueSize: remainingSemantic,
2143
+ remainingEnrichmentQueueSize: remainingEnrichment,
2097
2144
  queueDepthBefore: initialQueueSize,
2145
+ semanticQueueDepthBefore: initialSemanticQueueSize,
2146
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
2098
2147
  flushStartedAt: startedAt,
2099
2148
  flushCompletedAt: completedAt
2100
2149
  });
@@ -2105,21 +2154,27 @@ var PatchCommitScheduler = class {
2105
2154
  if (remaining > 0 && !this.paused) {
2106
2155
  this.schedule();
2107
2156
  }
2108
- const coalescingStats = this.computeCoalescingDurationStats();
2109
- this.updateAdaptiveBudget(coalescingStats.p95);
2110
- const adaptiveState = this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null);
2157
+ const coalescingStats = this.adaptiveBudgeting ? this.computeCoalescingDurationStats() : { sampleCount: this.coalescingDurationSamples.length, p95: null };
2158
+ if (this.adaptiveBudgeting) {
2159
+ this.updateAdaptiveBudget(coalescingStats.p95);
2160
+ }
2161
+ const adaptiveState = this.adaptiveBudgeting ? this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null) : void 0;
2111
2162
  const flushResult = batches.length > 0 ? {
2112
2163
  batches,
2113
2164
  totalPatches: totalInputPatches,
2114
2165
  totalAppliedPatches,
2115
2166
  totalDurationMs,
2116
2167
  remainingQueueSize: remaining,
2168
+ remainingSemanticQueueSize: remainingSemantic,
2169
+ remainingEnrichmentQueueSize: remainingEnrichment,
2117
2170
  queueDepthBefore: initialQueueSize,
2171
+ semanticQueueDepthBefore: initialSemanticQueueSize,
2172
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
2118
2173
  flushStartedAt: startedAt,
2119
2174
  flushCompletedAt: completedAt,
2120
2175
  coalescingDurationP95: coalescingStats.p95 ?? void 0,
2121
2176
  coalescingDurationSampleCount: coalescingStats.sampleCount,
2122
- adaptiveBudgetActive: adaptiveState.active,
2177
+ adaptiveBudgetActive: adaptiveState?.active,
2123
2178
  adaptiveBudgetState: adaptiveState
2124
2179
  } : null;
2125
2180
  if (flushResult) {
@@ -2155,6 +2210,9 @@ var PatchCommitScheduler = class {
2155
2210
  };
2156
2211
  }
2157
2212
  updateAdaptiveBudget(p95) {
2213
+ if (!this.adaptiveBudgeting) {
2214
+ return;
2215
+ }
2158
2216
  if (p95 !== null && p95 > COALESCING_DURATION_ACTIVATE_MS) {
2159
2217
  if (!this.adaptiveBudgetActive) {
2160
2218
  this.adaptiveBudgetActive = true;
@@ -2228,11 +2286,13 @@ var PatchCommitScheduler = class {
2228
2286
  }
2229
2287
  restart() {
2230
2288
  this.cancelScheduled();
2231
- this.highQueue = [];
2232
- this.lowQueue = [];
2289
+ this.semanticQueue = [];
2290
+ this.enrichmentHighQueue = [];
2291
+ this.enrichmentLowQueue = [];
2233
2292
  this.flushing = false;
2234
2293
  this.sequence = 0;
2235
2294
  this.history = [];
2295
+ this.flushCount = 0;
2236
2296
  this.scheduleToken++;
2237
2297
  this.paused = false;
2238
2298
  this.coalescingDurationSamples = [];
@@ -2279,6 +2339,12 @@ var PatchCommitScheduler = class {
2279
2339
  if (typeof options.historyLimit !== "undefined") {
2280
2340
  this.setHistoryLimit(options.historyLimit);
2281
2341
  }
2342
+ if (typeof options.startupMicrotaskFlushes !== "undefined") {
2343
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(options.startupMicrotaskFlushes));
2344
+ }
2345
+ if (typeof options.adaptiveBudgeting !== "undefined") {
2346
+ this.adaptiveBudgeting = options.adaptiveBudgeting;
2347
+ }
2282
2348
  if (typeof options.batch !== "undefined") {
2283
2349
  const requestedBatch = options.batch;
2284
2350
  if (requestedBatch === "microtask") {
@@ -2305,6 +2371,7 @@ var PatchCommitScheduler = class {
2305
2371
 
2306
2372
  // src/renderer/store.ts
2307
2373
  var import_core4 = require("@stream-mdx/core");
2374
+ var import_patch_batching3 = require("@stream-mdx/core/perf/patch-batching");
2308
2375
 
2309
2376
  // src/renderer/list-utils.ts
2310
2377
  function updateNodeDepth(node, depth) {
@@ -2400,12 +2467,28 @@ function snapshotToBlock(node) {
2400
2467
  }
2401
2468
  return null;
2402
2469
  }
2403
- var nodeSnapshotCache = /* @__PURE__ */ new Map();
2404
- var childrenSnapshotCache = /* @__PURE__ */ new Map();
2470
+ var nodeSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
2471
+ var childrenSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
2405
2472
  var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
2406
2473
  var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
2407
2474
  var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
2408
2475
  var CHILDREN_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(CHILDREN_SNAPSHOT_CACHE_LIMIT * 0.1));
2476
+ function getNodeSnapshotCacheForMap(map) {
2477
+ let cache = nodeSnapshotCacheByMap.get(map);
2478
+ if (!cache) {
2479
+ cache = /* @__PURE__ */ new Map();
2480
+ nodeSnapshotCacheByMap.set(map, cache);
2481
+ }
2482
+ return cache;
2483
+ }
2484
+ function getChildrenSnapshotCacheForMap(map) {
2485
+ let cache = childrenSnapshotCacheByMap.get(map);
2486
+ if (!cache) {
2487
+ cache = /* @__PURE__ */ new Map();
2488
+ childrenSnapshotCacheByMap.set(map, cache);
2489
+ }
2490
+ return cache;
2491
+ }
2409
2492
  function pruneCache(cache, max, buffer) {
2410
2493
  if (cache.size <= max + buffer) return;
2411
2494
  let remaining = cache.size - max;
@@ -2415,6 +2498,14 @@ function pruneCache(cache, max, buffer) {
2415
2498
  if (remaining <= 0) break;
2416
2499
  }
2417
2500
  }
2501
+ function deleteSnapshotCacheEntry(map, id) {
2502
+ getNodeSnapshotCacheForMap(map).delete(id);
2503
+ getChildrenSnapshotCacheForMap(map).delete(id);
2504
+ }
2505
+ function clearSnapshotCaches(map) {
2506
+ getNodeSnapshotCacheForMap(map).clear();
2507
+ getChildrenSnapshotCacheForMap(map).clear();
2508
+ }
2418
2509
  var EMPTY_CHILDREN = Object.freeze([]);
2419
2510
  var EMPTY_NODE_SNAPSHOT = Object.freeze({ version: -1, node: void 0 });
2420
2511
  var EMPTY_CHILDREN_SNAPSHOT = Object.freeze({
@@ -2582,9 +2673,57 @@ function createRootRecord() {
2582
2673
  parentId: null,
2583
2674
  children: [],
2584
2675
  props: {},
2585
- version: 0
2676
+ version: 0,
2677
+ blockEpoch: 0
2586
2678
  };
2587
2679
  }
2680
+ function readNumericEpoch(value) {
2681
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
2682
+ return void 0;
2683
+ }
2684
+ return value;
2685
+ }
2686
+ function readSnapshotBlockEpoch(snapshot) {
2687
+ return readNumericEpoch(snapshot.meta?.blockEpoch);
2688
+ }
2689
+ function readPatchBlockEpoch(patch) {
2690
+ if (patch.op === "setHTML") {
2691
+ return readNumericEpoch(patch.patchMeta?.blockEpoch);
2692
+ }
2693
+ return readNumericEpoch(patch.meta?.blockEpoch);
2694
+ }
2695
+ function readPatchParseEpoch(patch) {
2696
+ if (patch.op === "setHTML") {
2697
+ return readNumericEpoch(patch.patchMeta?.parseEpoch);
2698
+ }
2699
+ return readNumericEpoch(patch.meta?.parseEpoch);
2700
+ }
2701
+ function readPatchTx(patch) {
2702
+ if (patch.op === "setHTML") {
2703
+ return readNumericEpoch(patch.patchMeta?.tx);
2704
+ }
2705
+ return readNumericEpoch(patch.meta?.tx);
2706
+ }
2707
+ function assignSubtreeBlockEpoch(map, rootId, blockEpoch, touched) {
2708
+ const root = map.get(rootId);
2709
+ if (!root) return;
2710
+ const stack = [root];
2711
+ while (stack.length > 0) {
2712
+ const node = stack.pop();
2713
+ if (!node) continue;
2714
+ if (node.blockEpoch !== blockEpoch) {
2715
+ node.blockEpoch = blockEpoch;
2716
+ node.version += 1;
2717
+ touched?.add(node.id);
2718
+ }
2719
+ for (const childId of node.children) {
2720
+ const child = map.get(childId);
2721
+ if (child) {
2722
+ stack.push(child);
2723
+ }
2724
+ }
2725
+ }
2726
+ }
2588
2727
  function ensureArray(value) {
2589
2728
  return Array.isArray(value) ? value : [];
2590
2729
  }
@@ -2644,7 +2783,7 @@ function escapeHtml2(value) {
2644
2783
  }
2645
2784
  function stripOuterLineSpan(html) {
2646
2785
  if (!html) return null;
2647
- const openTagMatch = html.match(/<span[^>]*>/i);
2786
+ const openTagMatch = html.match(/<span[^>]*class="[^"]*\bline\b[^"]*"[^>]*>/i);
2648
2787
  if (!openTagMatch) {
2649
2788
  return null;
2650
2789
  }
@@ -2693,8 +2832,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2693
2832
  if (node.type !== "code-line") {
2694
2833
  anomalies.push(`type-mismatch:${node.id}:${node.type}`);
2695
2834
  nodes.delete(node.id);
2696
- nodeSnapshotCache.delete(node.id);
2697
- childrenSnapshotCache.delete(node.id);
2835
+ deleteSnapshotCacheEntry(nodes, node.id);
2698
2836
  touched.add(node.id);
2699
2837
  continue;
2700
2838
  }
@@ -2738,16 +2876,14 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2738
2876
  const existing = nodes.get(desiredId);
2739
2877
  if (existing && existing !== node) {
2740
2878
  nodes.delete(desiredId);
2741
- nodeSnapshotCache.delete(desiredId);
2742
- childrenSnapshotCache.delete(desiredId);
2879
+ deleteSnapshotCacheEntry(nodes, desiredId);
2743
2880
  touched.add(desiredId);
2744
2881
  mutated = true;
2745
2882
  }
2746
2883
  if (node.id !== desiredId) {
2747
2884
  const previousId = node.id;
2748
2885
  nodes.delete(node.id);
2749
- nodeSnapshotCache.delete(previousId);
2750
- childrenSnapshotCache.delete(previousId);
2886
+ deleteSnapshotCacheEntry(nodes, previousId);
2751
2887
  node.id = desiredId;
2752
2888
  nodes.set(desiredId, node);
2753
2889
  touched.add(desiredId);
@@ -2778,8 +2914,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2778
2914
  for (const [id, record] of nodes) {
2779
2915
  if (record.parentId === parent.id && record.type === "code-line" && !allowed.has(id)) {
2780
2916
  nodes.delete(id);
2781
- nodeSnapshotCache.delete(id);
2782
- childrenSnapshotCache.delete(id);
2917
+ deleteSnapshotCacheEntry(nodes, id);
2783
2918
  touched.add(id);
2784
2919
  mutated = true;
2785
2920
  }
@@ -2863,8 +2998,10 @@ function normalizeCodeLineProps(incoming, previous) {
2863
2998
  }
2864
2999
  function createCodeLineProps(index, text, highlight, tokens) {
2865
3000
  const safeText = typeof text === "string" ? text : "";
2866
- const sanitized = sanitizeLineInnerHtml(highlight, safeText);
2867
- const html = sanitized && sanitized.length >= safeText.length ? sanitized : escapeHtml2(safeText);
3001
+ const hasIncomingHighlight = typeof highlight === "string" && highlight.trim().length > 0;
3002
+ const sanitized = hasIncomingHighlight ? sanitizeLineInnerHtml(highlight, safeText) : null;
3003
+ const hasRenderableTokens = tokens !== void 0 && tokens !== null;
3004
+ const html = sanitized && sanitized.length >= safeText.length ? sanitized : hasRenderableTokens ? null : escapeHtml2(safeText);
2868
3005
  const props = {
2869
3006
  index,
2870
3007
  text: safeText,
@@ -2975,6 +3112,18 @@ function validateCodeBlockChildren(nodes, parent) {
2975
3112
  issues.push({ kind: "dangling-node", id });
2976
3113
  }
2977
3114
  }
3115
+ if (parent.block?.isFinalized) {
3116
+ const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(parent.block));
3117
+ const expectedChildren = ensureArray(snapshot.children).filter((child) => child.type === "code-line");
3118
+ if (expectedChildren.length !== parent.children.length) {
3119
+ issues.push({
3120
+ kind: "line-count-mismatch",
3121
+ id: parent.id,
3122
+ expected: expectedChildren.length,
3123
+ actual: parent.children.length
3124
+ });
3125
+ }
3126
+ }
2978
3127
  return { ok: issues.length === 0, issues };
2979
3128
  }
2980
3129
  function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
@@ -2991,7 +3140,7 @@ function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
2991
3140
  }
2992
3141
  const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(parent.block));
2993
3142
  removeSubtree(nodes, parent.id, touched);
2994
- const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot);
3143
+ const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot, touched, parent.blockEpoch);
2995
3144
  if (!inserted) {
2996
3145
  return false;
2997
3146
  }
@@ -3014,7 +3163,55 @@ function arraysEqual(a, b) {
3014
3163
  }
3015
3164
  return true;
3016
3165
  }
3017
- function buildNodeFromSnapshot(map, snapshot, parentId) {
3166
+ function expectedCodeLineId(parentId, index) {
3167
+ return `${parentId}::line:${index}`;
3168
+ }
3169
+ function collectStoreInvariantViolations(nodes) {
3170
+ const violations = [];
3171
+ for (const [id, node] of nodes) {
3172
+ if (node.type === "__root__") continue;
3173
+ if (node.children.length > 1) {
3174
+ const seen = /* @__PURE__ */ new Set();
3175
+ for (const childId of node.children) {
3176
+ if (seen.has(childId)) {
3177
+ violations.push(`duplicate child id:${id}:${childId}`);
3178
+ } else {
3179
+ seen.add(childId);
3180
+ }
3181
+ }
3182
+ }
3183
+ if (node.type === "list") {
3184
+ const parent = node.parentId ? nodes.get(node.parentId) : void 0;
3185
+ if (parent?.type === "list-item" && node.children.length === 0) {
3186
+ violations.push(`empty nested list:${id}`);
3187
+ }
3188
+ }
3189
+ if (node.type === "code") {
3190
+ for (let index = 0; index < node.children.length; index += 1) {
3191
+ const childId = node.children[index];
3192
+ const child = nodes.get(childId);
3193
+ if (!child) {
3194
+ violations.push(`missing code-line node:${node.id}:${childId}`);
3195
+ continue;
3196
+ }
3197
+ if (child.type !== "code-line") {
3198
+ violations.push(`non-code child in code block:${node.id}:${child.id}:${child.type}`);
3199
+ continue;
3200
+ }
3201
+ const expectedId = expectedCodeLineId(node.id, index);
3202
+ if (child.id !== expectedId) {
3203
+ violations.push(`code-line id mismatch:${node.id}:${child.id}:${expectedId}`);
3204
+ }
3205
+ const indexProp = typeof child.props?.index === "number" ? child.props.index : null;
3206
+ if (indexProp !== index) {
3207
+ violations.push(`code-line index mismatch:${node.id}:${child.id}:${indexProp}:${index}`);
3208
+ }
3209
+ }
3210
+ }
3211
+ }
3212
+ return violations;
3213
+ }
3214
+ function buildNodeFromSnapshot(map, snapshot, parentId, blockEpoch) {
3018
3215
  const record = {
3019
3216
  id: snapshot.id,
3020
3217
  type: snapshot.type,
@@ -3024,6 +3221,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
3024
3221
  meta: snapshot.meta ? { ...snapshot.meta } : void 0,
3025
3222
  range: snapshot.range ? { ...snapshot.range } : void 0,
3026
3223
  version: 0,
3224
+ blockEpoch,
3027
3225
  block: snapshotToBlock(snapshot) ?? void 0
3028
3226
  };
3029
3227
  if (record.type === "code") {
@@ -3032,7 +3230,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
3032
3230
  map.set(record.id, record);
3033
3231
  const childSnapshots = ensureArray(snapshot.children);
3034
3232
  for (const child of childSnapshots) {
3035
- const childRecord = buildNodeFromSnapshot(map, child, record.id);
3233
+ const childRecord = buildNodeFromSnapshot(map, child, record.id, blockEpoch);
3036
3234
  record.children.push(childRecord.id);
3037
3235
  }
3038
3236
  return record;
@@ -3060,8 +3258,7 @@ function removeSubtree(map, id, touched) {
3060
3258
  }
3061
3259
  }
3062
3260
  map.delete(node.id);
3063
- nodeSnapshotCache.delete(node.id);
3064
- childrenSnapshotCache.delete(node.id);
3261
+ deleteSnapshotCacheEntry(map, node.id);
3065
3262
  touched.add(node.id);
3066
3263
  for (const childId of node.children) {
3067
3264
  const child = map.get(childId);
@@ -3093,13 +3290,13 @@ function resolveParent(map, path) {
3093
3290
  }
3094
3291
  return map.get(path.blockId);
3095
3292
  }
3096
- function insertSnapshotAt(map, parent, index, snapshot, touched) {
3293
+ function insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
3097
3294
  if (!parent) return null;
3098
3295
  const existingIndex = parent.children.indexOf(snapshot.id);
3099
3296
  if (existingIndex !== -1) {
3100
3297
  removeSubtree(map, parent.children[existingIndex], touched ?? /* @__PURE__ */ new Set());
3101
3298
  }
3102
- const record = buildNodeFromSnapshot(map, snapshot, parent.id);
3299
+ const record = buildNodeFromSnapshot(map, snapshot, parent.id, blockEpoch ?? readSnapshotBlockEpoch(snapshot) ?? parent.blockEpoch);
3103
3300
  const insertedIds = [];
3104
3301
  const queue = [record];
3105
3302
  while (queue.length > 0) {
@@ -3117,13 +3314,13 @@ function insertSnapshotAt(map, parent, index, snapshot, touched) {
3117
3314
  ensureUniqueChildren(parent, touched);
3118
3315
  return { record, insertedIds };
3119
3316
  }
3120
- function replaceSnapshotAt(map, parent, index, snapshot, touched) {
3317
+ function replaceSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
3121
3318
  if (!parent) return;
3122
3319
  if (index < 0 || index >= parent.children.length) return;
3123
3320
  const targetId = parent.children[index];
3124
3321
  parent.children.splice(index, 1);
3125
3322
  removeSubtree(map, targetId, touched);
3126
- const inserted = insertSnapshotAt(map, parent, index, snapshot, touched);
3323
+ const inserted = insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch);
3127
3324
  if (inserted) {
3128
3325
  for (const id of inserted.insertedIds) {
3129
3326
  touched.add(id);
@@ -3143,6 +3340,24 @@ function createRendererStore(initialBlocks = []) {
3143
3340
  const listeners = /* @__PURE__ */ new Set();
3144
3341
  let notifyScheduled = false;
3145
3342
  let lastCoalescingMetrics = null;
3343
+ let invariantViolations = [];
3344
+ let transientDiagnostics = [];
3345
+ let nextBlockEpoch = 1;
3346
+ const debugCounters = {
3347
+ appendLineGuardRejected: 0,
3348
+ staleEpochRejected: 0,
3349
+ storeInvariantViolationCount: 0
3350
+ };
3351
+ const allocateBlockEpoch = (preferred) => {
3352
+ const resolved = readNumericEpoch(preferred);
3353
+ if (resolved !== void 0) {
3354
+ nextBlockEpoch = Math.max(nextBlockEpoch, resolved + 1);
3355
+ return resolved;
3356
+ }
3357
+ const epoch = nextBlockEpoch;
3358
+ nextBlockEpoch += 1;
3359
+ return epoch;
3360
+ };
3146
3361
  const getRaf = () => {
3147
3362
  if (typeof globalThis !== "undefined" && typeof globalThis.requestAnimationFrame === "function") {
3148
3363
  return globalThis.requestAnimationFrame;
@@ -3174,9 +3389,12 @@ function createRendererStore(initialBlocks = []) {
3174
3389
  }
3175
3390
  }
3176
3391
  }
3392
+ function appendDiagnostic(message) {
3393
+ transientDiagnostics = [...transientDiagnostics, message];
3394
+ invariantViolations = [...invariantViolations, message];
3395
+ }
3177
3396
  function rebuildFromBlocks(blocks) {
3178
- nodeSnapshotCache.clear();
3179
- childrenSnapshotCache.clear();
3397
+ clearSnapshotCaches(nodes);
3180
3398
  nodes = /* @__PURE__ */ new Map();
3181
3399
  const root = createRootRecord();
3182
3400
  nodes.set(root.id, root);
@@ -3185,9 +3403,12 @@ function createRendererStore(initialBlocks = []) {
3185
3403
  for (let i = 0; i < blocks.length; i++) {
3186
3404
  const block = (0, import_core4.cloneBlock)(blocks[i]);
3187
3405
  const snapshot = (0, import_core4.createBlockSnapshot)(block);
3188
- insertSnapshotAt(nodes, root, root.children.length, snapshot);
3406
+ insertSnapshotAt(nodes, root, root.children.length, snapshot, void 0, allocateBlockEpoch(readSnapshotBlockEpoch(snapshot)));
3189
3407
  }
3190
3408
  normalizeAllListDepths(nodes, /* @__PURE__ */ new Set(), import_core4.PATCH_ROOT_ID);
3409
+ transientDiagnostics = [];
3410
+ invariantViolations = collectStoreInvariantViolations(nodes);
3411
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
3191
3412
  version++;
3192
3413
  blocksDirty = true;
3193
3414
  scheduleNotify();
@@ -3252,11 +3473,57 @@ function createRendererStore(initialBlocks = []) {
3252
3473
  listDepthDirty = true;
3253
3474
  }
3254
3475
  };
3255
- const applyPropsUpdate = (at, props) => {
3476
+ const semanticTxEpochs = /* @__PURE__ */ new Map();
3477
+ const resolveBlockEpochTarget = (patch, at) => {
3478
+ if ((patch.op === "insertChild" || patch.op === "replaceChild") && at.blockId === import_core4.PATCH_ROOT_ID) {
3479
+ const nodeId = patch.node?.id;
3480
+ return nodeId ? nodes.get(nodeId) : void 0;
3481
+ }
3482
+ return nodes.get(at.blockId) ?? (at.nodeId ? nodes.get(at.nodeId) : void 0);
3483
+ };
3484
+ const rejectStaleEpochPatch = (patch, at) => {
3485
+ const expectedBlockEpoch = readPatchBlockEpoch(patch);
3486
+ const patchTx = readPatchTx(patch);
3487
+ if (expectedBlockEpoch === void 0) {
3488
+ return false;
3489
+ }
3490
+ if ((0, import_patch_batching3.getPatchKind)(patch) !== "semantic" || !at) {
3491
+ return false;
3492
+ }
3493
+ const target = resolveBlockEpochTarget(patch, at);
3494
+ if (!target || target.blockEpoch === expectedBlockEpoch) {
3495
+ return false;
3496
+ }
3497
+ const txEpoch = at.blockId && patchTx !== void 0 ? semanticTxEpochs.get(at.blockId) : void 0;
3498
+ if (txEpoch && txEpoch.tx === patchTx && txEpoch.blockEpoch === target.blockEpoch) {
3499
+ return false;
3500
+ }
3501
+ debugCounters.staleEpochRejected += 1;
3502
+ appendDiagnostic(
3503
+ `stale epoch rejected:${patch.op}:${target.id}:expected=${expectedBlockEpoch}:actual=${target.blockEpoch}`
3504
+ );
3505
+ return true;
3506
+ };
3507
+ const advanceBlockEpoch = (blockId, touchedSet, preferredEpoch, tx) => {
3508
+ if (!blockId || blockId === import_core4.PATCH_ROOT_ID) {
3509
+ return null;
3510
+ }
3511
+ const blockRoot = nodes.get(blockId);
3512
+ if (!blockRoot) {
3513
+ return null;
3514
+ }
3515
+ const nextEpoch = allocateBlockEpoch(preferredEpoch);
3516
+ assignSubtreeBlockEpoch(nodes, blockRoot.id, nextEpoch, touchedSet);
3517
+ if (tx !== void 0) {
3518
+ semanticTxEpochs.set(blockRoot.id, { tx, blockEpoch: nextEpoch });
3519
+ }
3520
+ return nextEpoch;
3521
+ };
3522
+ const applyPropsUpdate = (at, props, preferredEpoch, tx, semanticUpdate = false) => {
3256
3523
  const perfStartLocal = perfEnabled ? getPerfNow() : 0;
3257
3524
  const targetId = at.nodeId ?? at.blockId;
3258
3525
  if (!targetId) return;
3259
- const target = nodes.get(targetId);
3526
+ let target = nodes.get(targetId);
3260
3527
  if (!target) return;
3261
3528
  const parentType = target.parentId ? nodes.get(target.parentId)?.type : void 0;
3262
3529
  if (debugListPatches && (isListRelated(target.type) || isListRelated(parentType))) {
@@ -3283,6 +3550,9 @@ function createRendererStore(initialBlocks = []) {
3283
3550
  propsChanged = true;
3284
3551
  }
3285
3552
  if (propsChanged) {
3553
+ if (semanticUpdate) {
3554
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3555
+ }
3286
3556
  target.version++;
3287
3557
  touched.add(target.id);
3288
3558
  const parent = target.parentId ? nodes.get(target.parentId) : void 0;
@@ -3346,16 +3616,56 @@ function createRendererStore(initialBlocks = []) {
3346
3616
  }
3347
3617
  }
3348
3618
  let blockChanged = false;
3619
+ let typeChanged = false;
3620
+ let rebuiltFromFinalizedBlock = false;
3621
+ let blockEpochAdvanced = false;
3349
3622
  const incomingBlock = props?.block;
3350
3623
  if (incomingBlock && typeof incomingBlock === "object") {
3351
3624
  target.block = (0, import_core4.cloneBlock)(incomingBlock);
3625
+ const previousType = target.type;
3626
+ const incomingType = typeof target.block.type === "string" ? target.block.type : target.type;
3627
+ if (incomingType !== target.type) {
3628
+ target.type = incomingType;
3629
+ typeChanged = true;
3630
+ if (isListRelated(previousType) || isListRelated(target.type)) {
3631
+ markListDepthDirty(target.id);
3632
+ }
3633
+ }
3352
3634
  if (target.type === "code") {
3353
3635
  applyCodeBlockMetadata(target);
3354
3636
  }
3637
+ if (semanticUpdate) {
3638
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3639
+ blockEpochAdvanced = true;
3640
+ }
3641
+ const shouldRebuildFinalizedTree = !at.nodeId && target.id === at.blockId && Boolean(target.block?.isFinalized) && target.type !== "code" && Boolean(target.parentId);
3642
+ if (shouldRebuildFinalizedTree) {
3643
+ const parent = target.parentId ? nodes.get(target.parentId) : void 0;
3644
+ const index = parent ? parent.children.indexOf(target.id) : -1;
3645
+ if (parent && index !== -1) {
3646
+ const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(target.block));
3647
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
3648
+ mutatedBlocks = true;
3649
+ blockChanged = true;
3650
+ rebuiltFromFinalizedBlock = true;
3651
+ const rebuilt = nodes.get(target.id);
3652
+ if (isListRelated(previousType) || isListRelated(rebuilt?.type)) {
3653
+ markListDepthDirty(target.id);
3654
+ }
3655
+ target = rebuilt ?? target;
3656
+ }
3657
+ }
3355
3658
  mutatedBlocks = true;
3356
3659
  blockChanged = true;
3357
3660
  }
3358
- if (propsChanged || blockChanged) {
3661
+ if (rebuiltFromFinalizedBlock) {
3662
+ return;
3663
+ }
3664
+ if (propsChanged || blockChanged || typeChanged) {
3665
+ if (semanticUpdate && !blockEpochAdvanced) {
3666
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3667
+ blockEpochAdvanced = true;
3668
+ }
3359
3669
  target.version++;
3360
3670
  touched.add(target.id);
3361
3671
  let parentForNormalization;
@@ -3398,13 +3708,22 @@ function createRendererStore(initialBlocks = []) {
3398
3708
  const applyPatch = (patch) => {
3399
3709
  switch (patch.op) {
3400
3710
  case "insertChild": {
3711
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3401
3712
  const parent = resolveParent(nodes, patch.at);
3402
- const inserted = insertSnapshotAt(nodes, parent, patch.index, patch.node, touched);
3713
+ const inserted = insertSnapshotAt(
3714
+ nodes,
3715
+ parent,
3716
+ patch.index,
3717
+ patch.node,
3718
+ touched,
3719
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
3720
+ );
3403
3721
  if (inserted) {
3404
3722
  for (const id of inserted.insertedIds) {
3405
3723
  touched.add(id);
3406
3724
  }
3407
3725
  mutatedBlocks = true;
3726
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3408
3727
  if (isListRelated(inserted.record.type) || isListRelated(parent?.type)) {
3409
3728
  markListDepthDirty(inserted.record.id);
3410
3729
  if (parent) {
@@ -3424,6 +3743,7 @@ function createRendererStore(initialBlocks = []) {
3424
3743
  break;
3425
3744
  }
3426
3745
  case "deleteChild": {
3746
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3427
3747
  const parent = resolveParent(nodes, patch.at);
3428
3748
  if (!parent) break;
3429
3749
  if (patch.index < 0 || patch.index >= parent.children.length) break;
@@ -3435,6 +3755,7 @@ function createRendererStore(initialBlocks = []) {
3435
3755
  touched.add(parent.id);
3436
3756
  ensureUniqueChildren(parent, touched);
3437
3757
  mutatedBlocks = true;
3758
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3438
3759
  if (isListRelated(parent.type) || isListRelated(removedNode?.type)) {
3439
3760
  markListDepthDirty(parent.id);
3440
3761
  }
@@ -3451,11 +3772,20 @@ function createRendererStore(initialBlocks = []) {
3451
3772
  break;
3452
3773
  }
3453
3774
  case "replaceChild": {
3775
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3454
3776
  const parent = resolveParent(nodes, patch.at);
3455
3777
  if (!parent) break;
3456
- replaceSnapshotAt(nodes, parent, patch.index, patch.node, touched);
3778
+ replaceSnapshotAt(
3779
+ nodes,
3780
+ parent,
3781
+ patch.index,
3782
+ patch.node,
3783
+ touched,
3784
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
3785
+ );
3457
3786
  touched.add(parent.id);
3458
3787
  mutatedBlocks = true;
3788
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3459
3789
  if (isListRelated(parent.type)) {
3460
3790
  markListDepthDirty(parent.id);
3461
3791
  }
@@ -3469,18 +3799,33 @@ function createRendererStore(initialBlocks = []) {
3469
3799
  break;
3470
3800
  }
3471
3801
  case "setProps": {
3472
- applyPropsUpdate(patch.at, patch.props);
3802
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3803
+ applyPropsUpdate(patch.at, patch.props, readPatchParseEpoch(patch), readPatchTx(patch), (0, import_patch_batching3.getPatchKind)(patch) === "semantic");
3473
3804
  break;
3474
3805
  }
3475
3806
  case "setPropsBatch": {
3476
3807
  const entries2 = Array.isArray(patch.entries) ? patch.entries : [];
3477
3808
  for (const entry of entries2) {
3478
3809
  if (!entry || !entry.at) continue;
3479
- applyPropsUpdate(entry.at, entry.props);
3810
+ const entryPatch = {
3811
+ op: "setProps",
3812
+ at: entry.at,
3813
+ props: entry.props,
3814
+ meta: entry.meta ?? patch.meta
3815
+ };
3816
+ if (rejectStaleEpochPatch(entryPatch, entry.at)) continue;
3817
+ applyPropsUpdate(
3818
+ entry.at,
3819
+ entry.props,
3820
+ readPatchParseEpoch(entryPatch),
3821
+ readPatchTx(entryPatch),
3822
+ (0, import_patch_batching3.getPatchKind)(entryPatch) === "semantic"
3823
+ );
3480
3824
  }
3481
3825
  break;
3482
3826
  }
3483
3827
  case "finalize": {
3828
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3484
3829
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
3485
3830
  if (!target || !target.block) break;
3486
3831
  if (!target.block.isFinalized) {
@@ -3489,6 +3834,17 @@ function createRendererStore(initialBlocks = []) {
3489
3834
  target.version++;
3490
3835
  touched.add(target.id);
3491
3836
  mutatedBlocks = true;
3837
+ if (!patch.at.nodeId && target.id === patch.at.blockId && target.type !== "code" && target.parentId) {
3838
+ const parent = nodes.get(target.parentId);
3839
+ const index = parent ? parent.children.indexOf(target.id) : -1;
3840
+ if (parent && index !== -1) {
3841
+ const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(target.block));
3842
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
3843
+ if (isListRelated(target.type) || isListRelated(snapshot.type)) {
3844
+ markListDepthDirty(target.id);
3845
+ }
3846
+ }
3847
+ }
3492
3848
  }
3493
3849
  if (isListRelated(target.type)) {
3494
3850
  markListDepthDirty(target.id);
@@ -3502,6 +3858,7 @@ function createRendererStore(initialBlocks = []) {
3502
3858
  break;
3503
3859
  }
3504
3860
  case "reorder": {
3861
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3505
3862
  const parent = resolveParent(nodes, patch.at);
3506
3863
  if (!parent) break;
3507
3864
  const length = parent.children.length;
@@ -3516,6 +3873,9 @@ function createRendererStore(initialBlocks = []) {
3516
3873
  parent.children.splice(to, 0, ...moved);
3517
3874
  parent.version++;
3518
3875
  touched.add(parent.id);
3876
+ if ((0, import_patch_batching3.getPatchKind)(patch) === "semantic") {
3877
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3878
+ }
3519
3879
  if (isListRelated(parent.type)) {
3520
3880
  markListDepthDirty(parent.id);
3521
3881
  }
@@ -3531,15 +3891,31 @@ function createRendererStore(initialBlocks = []) {
3531
3891
  break;
3532
3892
  }
3533
3893
  case "appendLines": {
3894
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3534
3895
  const parent = resolveParent(nodes, patch.at);
3535
3896
  if (!parent) break;
3536
- const startIndex = Math.max(0, Math.min(patch.startIndex, parent.children.length));
3897
+ if (parent.type !== "code") {
3898
+ debugCounters.appendLineGuardRejected += 1;
3899
+ appendDiagnostic(`appendLines target not code:${parent.id}:${parent.type}`);
3900
+ break;
3901
+ }
3902
+ const requestedStartIndex = Math.max(0, patch.startIndex);
3903
+ const startIndex = Math.max(0, Math.min(requestedStartIndex, parent.children.length));
3537
3904
  const lineCount = patch.lines?.length ?? 0;
3538
3905
  if (lineCount === 0) break;
3906
+ const prefixMatches = parent.children.every((childId, index) => childId === expectedCodeLineId(parent.id, index));
3907
+ const isStrictTailAppend = requestedStartIndex === parent.children.length;
3908
+ if (!isStrictTailAppend || !prefixMatches) {
3909
+ debugCounters.appendLineGuardRejected += 1;
3910
+ appendDiagnostic(
3911
+ `appendLines guard rejected:${parent.id}:requestedStart=${requestedStartIndex}:childCount=${parent.children.length}:prefixMatches=${prefixMatches}`
3912
+ );
3913
+ break;
3914
+ }
3539
3915
  const originalChildren = parent.children.slice();
3540
3916
  const insertedIds = [];
3541
3917
  const insertedSet = /* @__PURE__ */ new Set();
3542
- const isTrailingAppend = startIndex >= originalChildren.length;
3918
+ const isTrailingAppend = true;
3543
3919
  let parentChildrenChanged = false;
3544
3920
  let codeLinesMutated = false;
3545
3921
  for (let offset = 0; offset < lineCount; offset++) {
@@ -3581,7 +3957,8 @@ function createRendererStore(initialBlocks = []) {
3581
3957
  parentId: parent.id,
3582
3958
  children: [],
3583
3959
  props: nextLineProps,
3584
- version: 0
3960
+ version: 0,
3961
+ blockEpoch: parent.blockEpoch
3585
3962
  };
3586
3963
  nodes.set(lineId, record);
3587
3964
  touched.add(lineId);
@@ -3631,6 +4008,7 @@ function createRendererStore(initialBlocks = []) {
3631
4008
  break;
3632
4009
  }
3633
4010
  case "setHTML": {
4011
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3634
4012
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
3635
4013
  if (!target) break;
3636
4014
  const sanitized = patch.sanitized ? patch.html : (0, import_core4.sanitizeHTML)(patch.html);
@@ -3673,6 +4051,30 @@ function createRendererStore(initialBlocks = []) {
3673
4051
  applyPatch(patch);
3674
4052
  }
3675
4053
  }
4054
+ const finalizedTableRoots = /* @__PURE__ */ new Set();
4055
+ for (const touchedId of touched) {
4056
+ let cursor = nodes.get(touchedId);
4057
+ while (cursor && cursor.id !== import_core4.PATCH_ROOT_ID) {
4058
+ if (cursor.type === "table" && cursor.block?.isFinalized && cursor.parentId) {
4059
+ finalizedTableRoots.add(cursor.id);
4060
+ break;
4061
+ }
4062
+ cursor = cursor.parentId ? nodes.get(cursor.parentId) : void 0;
4063
+ }
4064
+ }
4065
+ if (finalizedTableRoots.size > 0) {
4066
+ for (const tableId of finalizedTableRoots) {
4067
+ const tableNode = nodes.get(tableId);
4068
+ if (!tableNode || !tableNode.block || !tableNode.parentId) continue;
4069
+ const parent = nodes.get(tableNode.parentId);
4070
+ if (!parent) continue;
4071
+ const index = parent.children.indexOf(tableNode.id);
4072
+ if (index === -1) continue;
4073
+ const snapshot = (0, import_core4.createBlockSnapshot)((0, import_core4.cloneBlock)(tableNode.block));
4074
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, tableNode.blockEpoch);
4075
+ mutatedBlocks = true;
4076
+ }
4077
+ }
3676
4078
  if (mutatedBlocks) {
3677
4079
  blocksDirty = true;
3678
4080
  }
@@ -3693,6 +4095,8 @@ function createRendererStore(initialBlocks = []) {
3693
4095
  normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, import_core4.PATCH_ROOT_ID);
3694
4096
  }
3695
4097
  }
4098
+ invariantViolations = [...collectStoreInvariantViolations(nodes), ...transientDiagnostics];
4099
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
3696
4100
  if (touched.size > 0 || mutatedBlocks) {
3697
4101
  version++;
3698
4102
  scheduleNotify();
@@ -3741,6 +4145,7 @@ function createRendererStore(initialBlocks = []) {
3741
4145
  if (!record) {
3742
4146
  return EMPTY_NODE_SNAPSHOT;
3743
4147
  }
4148
+ const nodeSnapshotCache = getNodeSnapshotCacheForMap(nodes);
3744
4149
  const cached = nodeSnapshotCache.get(id);
3745
4150
  if (cached && cached.version === record.version && cached.node === record) {
3746
4151
  return cached;
@@ -3760,6 +4165,7 @@ function createRendererStore(initialBlocks = []) {
3760
4165
  if (!record) {
3761
4166
  return EMPTY_CHILDREN_SNAPSHOT;
3762
4167
  }
4168
+ const childrenSnapshotCache = getChildrenSnapshotCacheForMap(nodes);
3763
4169
  const cached = childrenSnapshotCache.get(id);
3764
4170
  if (cached && cached.version === record.version) {
3765
4171
  return cached;
@@ -3769,6 +4175,12 @@ function createRendererStore(initialBlocks = []) {
3769
4175
  pruneCache(childrenSnapshotCache, CHILDREN_SNAPSHOT_CACHE_LIMIT, CHILDREN_SNAPSHOT_CACHE_BUFFER);
3770
4176
  return snapshot;
3771
4177
  },
4178
+ getInvariantViolations() {
4179
+ return invariantViolations.slice();
4180
+ },
4181
+ getDebugCounters() {
4182
+ return { ...debugCounters };
4183
+ },
3772
4184
  getLastCoalescingMetrics() {
3773
4185
  return lastCoalescingMetrics;
3774
4186
  },
@@ -4246,8 +4658,10 @@ var ListBlockView = import_react10.default.memo(
4246
4658
  isFinalized: block?.isFinalized ?? false
4247
4659
  });
4248
4660
  }, [listDebugEnabled, blockId, depth, ordered, childIds, listItemIds, store, block?.isFinalized]);
4249
- const listStyle = import_react10.default.useMemo(() => {
4250
- if (!ordered || listItemIds.length === 0) return void 0;
4661
+ const { listStyle, markerDigits } = import_react10.default.useMemo(() => {
4662
+ if (!ordered || listItemIds.length === 0) {
4663
+ return { listStyle: void 0, markerDigits: 1 };
4664
+ }
4251
4665
  let maxDigits = 1;
4252
4666
  listItemIds.forEach((childId, index) => {
4253
4667
  const childNode = store.getNode(childId);
@@ -4259,9 +4673,14 @@ var ListBlockView = import_react10.default.memo(
4259
4673
  const digits = digitMatch ? digitMatch.length : text.length;
4260
4674
  if (digits > maxDigits) maxDigits = digits;
4261
4675
  });
4262
- const baseIndent = depth === 0 ? "2.5rem" : depth === 1 ? "2rem" : "1.5rem";
4676
+ const baseIndent = depth === 0 ? "2rem" : depth === 1 ? "1.75rem" : "1.5rem";
4677
+ const extraDigits = Math.max(0, maxDigits - 1);
4263
4678
  return {
4264
- ["--list-indent"]: `calc(${baseIndent} + ${maxDigits}ch)`
4679
+ markerDigits: maxDigits,
4680
+ listStyle: {
4681
+ ["--list-indent"]: extraDigits > 0 ? `calc(${baseIndent} + ${extraDigits}ch)` : baseIndent,
4682
+ ["--list-marker-digits"]: String(maxDigits)
4683
+ }
4265
4684
  };
4266
4685
  }, [ordered, listItemIds, store, depth]);
4267
4686
  if (listItemIds.length === 0) return null;
@@ -4270,6 +4689,7 @@ var ListBlockView = import_react10.default.memo(
4270
4689
  {
4271
4690
  className: `markdown-list ${ordered ? "ordered" : "unordered"}`,
4272
4691
  "data-list-depth": depth,
4692
+ "data-marker-digits": ordered ? markerDigits : void 0,
4273
4693
  "data-list-id": listDebugEnabled ? blockId : void 0,
4274
4694
  style: listStyle,
4275
4695
  children: listItemIds.map((childId, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -4451,6 +4871,7 @@ var CodeBlockView = import_react10.default.memo(({ store, blockId, registry }) =
4451
4871
  deferredConfig ? { ...deferredConfig, enabled: true } : { enabled: false }
4452
4872
  );
4453
4873
  if (!node || !node.block) return null;
4874
+ const blockIsFinalized = node.block.isFinalized;
4454
4875
  const nodeVersion = node.version;
4455
4876
  const lines = import_react10.default.useMemo(() => {
4456
4877
  const seenIds = /* @__PURE__ */ new Set();
@@ -4481,13 +4902,25 @@ var CodeBlockView = import_react10.default.memo(({ store, blockId, registry }) =
4481
4902
  const lang = typeof node.props?.lang === "string" ? node.props?.lang : node.block.payload.meta?.lang;
4482
4903
  const preAttrs = node.props?.preAttrs ?? void 0;
4483
4904
  const codeAttrs = node.props?.codeAttrs ?? void 0;
4905
+ const blockMeta = node.block.payload.meta ?? void 0;
4906
+ const lazyEnabled = Boolean(blockMeta?.lazyTokenization);
4907
+ const lazyTokenizedUntil = typeof blockMeta?.lazyTokenizedUntil === "number" ? blockMeta.lazyTokenizedUntil : 0;
4908
+ const lazyHighlightedLines = Array.isArray(blockMeta?.highlightedLines) ? blockMeta.highlightedLines.length : 0;
4909
+ const lazyTokenLines = Array.isArray(blockMeta?.tokenLines) ? blockMeta.tokenLines.length : 0;
4910
+ const lazySourceTextLineCount = import_react10.default.useMemo(() => {
4911
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
4912
+ const metaCode = typeof blockMeta?.code === "string" ? blockMeta.code : null;
4913
+ const sourceText = resolveCodeSourceText(raw, metaCode);
4914
+ if (sourceText.length === 0) return 0;
4915
+ return sourceText.split("\n").length;
4916
+ }, [blockMeta, node.block?.payload.raw]);
4917
+ const totalLazyLineCount = Math.max(lines.length, lazyHighlightedLines, lazyTokenLines, lazySourceTextLineCount);
4484
4918
  const virtualizationConfig = DEFAULT_VIRTUALIZED_CODE_CONFIG;
4485
4919
  const virtualizationDisabled = typeof process !== "undefined" && typeof process.env === "object" && process.env.STREAM_MDX_DISABLE_VIRTUALIZED_CODE === "true";
4486
- const shouldVirtualize = !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
4920
+ const canVirtualize = node.block.isFinalized && !lazyEnabled;
4921
+ const shouldVirtualize = canVirtualize && !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
4487
4922
  const virtualization = useVirtualizedCode(lines, shouldVirtualize ? virtualizationConfig : { ...virtualizationConfig, enabled: false });
4488
4923
  const highlightRequester = useCodeHighlightRequester();
4489
- const lazyEnabled = Boolean(node.block.payload.meta?.lazyTokenization);
4490
- const lazyTokenizedUntil = typeof node.block.payload.meta?.lazyTokenizedUntil === "number" ? node.block.payload.meta.lazyTokenizedUntil : 0;
4491
4924
  const lastRangeRef = import_react10.default.useRef(null);
4492
4925
  const rafRef = import_react10.default.useRef(null);
4493
4926
  const idleRef = import_react10.default.useRef(null);
@@ -4568,29 +5001,111 @@ var CodeBlockView = import_react10.default.memo(({ store, blockId, registry }) =
4568
5001
  virtualization.window.startIndex,
4569
5002
  virtualization.window.endIndex
4570
5003
  ]);
4571
- const highlightedHtml = import_react10.default.useMemo(() => {
4572
- if (!shouldVirtualize && node.block?.payload.highlightedHtml) {
4573
- return node.block.payload.highlightedHtml;
5004
+ import_react10.default.useEffect(() => {
5005
+ if (shouldVirtualize || !blockIsFinalized || !lazyEnabled || !highlightRequester) return;
5006
+ if (totalLazyLineCount <= 0 || lazyTokenizedUntil >= totalLazyLineCount) return;
5007
+ const nextRange = {
5008
+ visibleStart: 0,
5009
+ visibleEnd: totalLazyLineCount,
5010
+ prefetchStart: 0,
5011
+ prefetchEnd: totalLazyLineCount,
5012
+ tokenizedUntil: lazyTokenizedUntil
5013
+ };
5014
+ const last = lastRangeRef.current;
5015
+ if (last && last.visibleStart === nextRange.visibleStart && last.visibleEnd === nextRange.visibleEnd && last.prefetchStart === nextRange.prefetchStart && last.prefetchEnd === nextRange.prefetchEnd && last.tokenizedUntil === nextRange.tokenizedUntil) {
5016
+ return;
5017
+ }
5018
+ lastRangeRef.current = nextRange;
5019
+ if (rafRef.current !== null && typeof cancelAnimationFrame === "function") {
5020
+ cancelAnimationFrame(rafRef.current);
4574
5021
  }
5022
+ const requestFullHighlight = () => {
5023
+ highlightRequester({
5024
+ blockId,
5025
+ startLine: 0,
5026
+ endLine: totalLazyLineCount,
5027
+ priority: "visible",
5028
+ reason: "finalize-full"
5029
+ });
5030
+ rafRef.current = null;
5031
+ };
5032
+ if (typeof requestAnimationFrame === "function") {
5033
+ rafRef.current = requestAnimationFrame(requestFullHighlight);
5034
+ } else {
5035
+ requestFullHighlight();
5036
+ }
5037
+ }, [blockId, blockIsFinalized, highlightRequester, lazyEnabled, lazyTokenizedUntil, shouldVirtualize, totalLazyLineCount]);
5038
+ const highlightedHtml = import_react10.default.useMemo(() => {
5039
+ const blockHtml = node.block?.payload.highlightedHtml ?? "";
5040
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
5041
+ const metaCode = typeof node.block?.payload.meta?.code === "string" ? (node.block?.payload.meta).code : null;
5042
+ const sourceText = resolveCodeSourceText(raw, metaCode);
5043
+ const sourceEndsWithNewline = sourceText.endsWith("\n");
5044
+ const sourceLines = sourceText.length > 0 ? sourceText.split("\n") : [];
5045
+ const rawFallbackLines = buildCodeFallbackLines(raw);
5046
+ const deterministicWrapperAttrs = (0, import_core5.getDefaultCodeWrapperAttributes)(lang);
5047
+ const composedPreAttrs = deterministicWrapperAttrs.preAttrs;
5048
+ const composedCodeAttrs = deterministicWrapperAttrs.codeAttrs;
5049
+ const shouldAppendTerminalNewline = (renderedLines) => {
5050
+ if (!sourceEndsWithNewline || renderedLines.length === 0 || lines.length === 0) {
5051
+ return false;
5052
+ }
5053
+ const lastRendered = renderedLines[renderedLines.length - 1];
5054
+ const lastKnown = lines[lines.length - 1];
5055
+ if (!lastRendered || !lastKnown || lastRendered.index !== lastKnown.index || lastRendered.text === "") {
5056
+ return false;
5057
+ }
5058
+ const expectedRenderedLineCount = Math.max(0, sourceLines.length - 1);
5059
+ const expectedLastRenderedLine = sourceLines.length >= 2 ? sourceLines[sourceLines.length - 2] ?? "" : "";
5060
+ return renderedLines.length === expectedRenderedLineCount && lastRendered.text === expectedLastRenderedLine;
5061
+ };
4575
5062
  if (lines.length > 0) {
4576
5063
  const hasLineHtml = lines.some((line) => typeof line.html === "string" && line.html.length > 0);
4577
- if (!hasLineHtml && node.block?.payload.highlightedHtml) {
4578
- return node.block.payload.highlightedHtml;
5064
+ if (!hasLineHtml && blockHtml) {
5065
+ return blockHtml;
4579
5066
  }
4580
5067
  if (shouldVirtualize) {
4581
- return composeHighlightedHtml(virtualization.window.visibleLines, preAttrs, codeAttrs);
5068
+ return composeHighlightedHtml(
5069
+ virtualization.window.visibleLines,
5070
+ composedPreAttrs,
5071
+ composedCodeAttrs,
5072
+ shouldAppendTerminalNewline(virtualization.window.visibleLines)
5073
+ );
5074
+ }
5075
+ return composeHighlightedHtml(lines, composedPreAttrs, composedCodeAttrs, shouldAppendTerminalNewline(lines));
5076
+ }
5077
+ if (!shouldVirtualize && blockHtml) {
5078
+ const hasHighlightedLines = blockHtml.includes('class="line"');
5079
+ if (!hasHighlightedLines && rawFallbackLines.length > 0) {
5080
+ return composeHighlightedHtml(
5081
+ rawFallbackLines,
5082
+ composedPreAttrs,
5083
+ composedCodeAttrs,
5084
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5085
+ );
4582
5086
  }
4583
- return composeHighlightedHtml(lines, preAttrs, codeAttrs);
5087
+ return blockHtml;
4584
5088
  }
4585
- const blockHtml = node.block?.payload.highlightedHtml ?? "";
4586
5089
  if (blockHtml?.trim().match(/^```[\w-]*\s*$/)) {
4587
- const raw = node.block?.payload.raw ?? "";
4588
- if (raw.trim().length > 0) {
4589
- return composeHighlightedHtml([{ index: 0, text: raw, html: null, id: "temp" }], preAttrs, codeAttrs);
5090
+ if (rawFallbackLines.length > 0) {
5091
+ return composeHighlightedHtml(
5092
+ rawFallbackLines,
5093
+ composedPreAttrs,
5094
+ composedCodeAttrs,
5095
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5096
+ );
4590
5097
  }
4591
5098
  }
5099
+ if (!blockHtml && rawFallbackLines.length > 0) {
5100
+ return composeHighlightedHtml(
5101
+ rawFallbackLines,
5102
+ composedPreAttrs,
5103
+ composedCodeAttrs,
5104
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5105
+ );
5106
+ }
4592
5107
  return blockHtml;
4593
- }, [shouldVirtualize, node.block?.payload.highlightedHtml, lines, preAttrs, codeAttrs, virtualization.window.visibleLines, node.block?.payload.raw]);
5108
+ }, [blockIsFinalized, shouldVirtualize, node.block?.payload.highlightedHtml, node.block?.payload.meta, lines, lang, virtualization.window.visibleLines, node.block?.payload.raw]);
4594
5109
  const rendered = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
4595
5110
  CodeComponent,
4596
5111
  {
@@ -4602,13 +5117,19 @@ var CodeBlockView = import_react10.default.memo(({ store, blockId, registry }) =
4602
5117
  codeAttrs
4603
5118
  }
4604
5119
  );
5120
+ const effectiveLineCount = import_react10.default.useMemo(() => {
5121
+ if (lines.length > 0) {
5122
+ return lines.length;
5123
+ }
5124
+ return 1;
5125
+ }, [lines.length]);
4605
5126
  const codeFrameClass = "not-prose flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
4606
5127
  const codeMetricsAttrs = {
4607
5128
  "data-code-block": "true",
4608
5129
  "data-block-id": blockId,
4609
5130
  "data-code-virtualized": shouldVirtualize ? "true" : "false",
4610
- "data-code-total-lines": String(lines.length),
4611
- "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : lines.length),
5131
+ "data-code-total-lines": String(effectiveLineCount),
5132
+ "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : effectiveLineCount),
4612
5133
  "data-code-window-size": String(virtualization.config.windowSize)
4613
5134
  };
4614
5135
  const codeView = shouldVirtualize ? (() => {
@@ -4654,21 +5175,152 @@ var CodeBlockView = import_react10.default.memo(({ store, blockId, registry }) =
4654
5175
  return codeView;
4655
5176
  });
4656
5177
  CodeBlockView.displayName = "CodeBlockView";
4657
- function composeHighlightedHtml(lines, preAttrs, codeAttrs) {
5178
+ function composeHighlightedHtml(lines, preAttrs, codeAttrs, appendTerminalNewline = false) {
4658
5179
  const lineMarkup = lines.map((line) => {
4659
- const content = line.html ?? escapeHtml3(line.text);
5180
+ const content = line.html ?? renderTokenLineHtml(line.tokens) ?? escapeHtml3(line.text);
4660
5181
  const dataLine = Number.isFinite(line.index) ? ` data-line="${line.index + 1}"` : "";
4661
5182
  return `<span class="line"${dataLine}>${content}</span>`;
4662
5183
  }).join("\n");
4663
5184
  const preAttr = attrsToString(preAttrs);
4664
5185
  const codeAttr = attrsToString(codeAttrs);
4665
- return `<pre${preAttr}><code${codeAttr}>${lineMarkup}
4666
- </code></pre>`;
5186
+ return `<pre${preAttr}><code${codeAttr}>${lineMarkup}${appendTerminalNewline ? "\n" : ""}</code></pre>`;
5187
+ }
5188
+ var TOKEN_FONT_STYLE_ITALIC = 1;
5189
+ var TOKEN_FONT_STYLE_BOLD = 2;
5190
+ var TOKEN_FONT_STYLE_UNDERLINE = 4;
5191
+ var TOKEN_FONT_STYLE_STRIKETHROUGH = 8;
5192
+ function renderTokenLineHtml(tokens) {
5193
+ if (!tokens || !Array.isArray(tokens.spans)) {
5194
+ return null;
5195
+ }
5196
+ return mergeRenderableTokenSpans(tokens.spans).map((span) => renderTokenSpanHtml(span)).join("");
5197
+ }
5198
+ function renderTokenSpanHtml(span) {
5199
+ const dark = span.v?.dark;
5200
+ const light = span.v?.light;
5201
+ const single = span.s;
5202
+ const styles = [];
5203
+ if (dark?.fg) {
5204
+ styles.push(`--shiki-dark:${dark.fg}`);
5205
+ }
5206
+ if (light?.fg) {
5207
+ styles.push(`--shiki-light:${light.fg}`);
5208
+ }
5209
+ if (!light?.fg && !dark?.fg && single?.fg) {
5210
+ styles.push(`color:${single.fg}`);
5211
+ }
5212
+ const fontStyle = light?.fs ?? dark?.fs ?? single?.fs;
5213
+ if (typeof fontStyle === "number" && fontStyle > 0) {
5214
+ if (fontStyle & TOKEN_FONT_STYLE_ITALIC) {
5215
+ styles.push("font-style: italic");
5216
+ }
5217
+ if (fontStyle & TOKEN_FONT_STYLE_BOLD) {
5218
+ styles.push("font-weight: bold");
5219
+ }
5220
+ const decorations = [];
5221
+ if (fontStyle & TOKEN_FONT_STYLE_UNDERLINE) {
5222
+ decorations.push("underline");
5223
+ }
5224
+ if (fontStyle & TOKEN_FONT_STYLE_STRIKETHROUGH) {
5225
+ decorations.push("line-through");
5226
+ }
5227
+ if (decorations.length > 0) {
5228
+ styles.push(`text-decoration: ${decorations.join(" ")}`);
5229
+ }
5230
+ }
5231
+ const content = escapeHtml3(span.t ?? "");
5232
+ if (styles.length === 0) {
5233
+ return content;
5234
+ }
5235
+ return `<span style="${styles.join(";")}">${content}</span>`;
5236
+ }
5237
+ function mergeRenderableTokenSpans(spans) {
5238
+ if (spans.length <= 1) {
5239
+ return spans.slice();
5240
+ }
5241
+ const merged = [];
5242
+ let pendingWhitespace = "";
5243
+ for (const span of spans) {
5244
+ const text = typeof span.t === "string" ? span.t : "";
5245
+ if (/^[\t ]+$/.test(text)) {
5246
+ pendingWhitespace += text;
5247
+ continue;
5248
+ }
5249
+ const nextSpan = pendingWhitespace ? { ...span, t: `${pendingWhitespace}${text}` } : span;
5250
+ pendingWhitespace = "";
5251
+ const previous = merged[merged.length - 1];
5252
+ if (previous && sameTokenSpanStyle(previous, nextSpan)) {
5253
+ previous.t += nextSpan.t;
5254
+ } else {
5255
+ merged.push({ ...nextSpan });
5256
+ }
5257
+ }
5258
+ if (pendingWhitespace) {
5259
+ const previous = merged[merged.length - 1];
5260
+ if (previous) {
5261
+ previous.t += pendingWhitespace;
5262
+ } else {
5263
+ merged.push({ t: pendingWhitespace });
5264
+ }
5265
+ }
5266
+ return merged;
5267
+ }
5268
+ function sameTokenSpanStyle(a, b) {
5269
+ const variantKeys = /* @__PURE__ */ new Set([
5270
+ ...Object.keys(a.v ?? {}),
5271
+ ...Object.keys(b.v ?? {})
5272
+ ]);
5273
+ for (const key of variantKeys) {
5274
+ const aStyle = a.v?.[key];
5275
+ const bStyle = b.v?.[key];
5276
+ if ((aStyle?.fg ?? null) !== (bStyle?.fg ?? null)) return false;
5277
+ if ((aStyle?.bg ?? null) !== (bStyle?.bg ?? null)) return false;
5278
+ if ((aStyle?.fs ?? null) !== (bStyle?.fs ?? null)) return false;
5279
+ }
5280
+ if ((a.s?.fg ?? null) !== (b.s?.fg ?? null)) return false;
5281
+ if ((a.s?.bg ?? null) !== (b.s?.bg ?? null)) return false;
5282
+ if ((a.s?.fs ?? null) !== (b.s?.fs ?? null)) return false;
5283
+ return true;
5284
+ }
5285
+ function resolveCodeSourceText(raw, metaCode) {
5286
+ if (metaCode !== null) {
5287
+ return metaCode.replace(/\r\n?/g, "\n");
5288
+ }
5289
+ if (!raw) return "";
5290
+ const normalized = raw.replace(/\r\n?/g, "\n");
5291
+ const fenced = (0, import_core5.stripCodeFence)(normalized);
5292
+ return fenced.hadFence ? fenced.code : normalized;
4667
5293
  }
4668
5294
  function attrsToString(attrs) {
4669
5295
  if (!attrs) return "";
4670
5296
  return Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
4671
5297
  }
5298
+ function buildCodeFallbackLines(raw) {
5299
+ const normalized = normalizeCodeFallbackRaw(raw);
5300
+ if (normalized.length === 0) {
5301
+ if (/^\s*```[^\n]*\n?\s*$/.test(raw)) {
5302
+ return [{ id: "temp-0", index: 0, text: "", html: null }];
5303
+ }
5304
+ return [];
5305
+ }
5306
+ return normalized.split("\n").map((line, index) => ({
5307
+ id: `temp-${index}`,
5308
+ index,
5309
+ text: line,
5310
+ html: null
5311
+ }));
5312
+ }
5313
+ function normalizeCodeFallbackRaw(raw) {
5314
+ if (!raw) return "";
5315
+ const normalized = raw.replace(/\r\n?/g, "\n");
5316
+ const fenced = (0, import_core5.stripCodeFence)(normalized);
5317
+ if (!fenced.hadFence) {
5318
+ return normalized.replace(/\n+$/, "");
5319
+ }
5320
+ let code = fenced.code;
5321
+ code = code.replace(/\n+$/, "");
5322
+ return code;
5323
+ }
4672
5324
  function escapeHtml3(value) {
4673
5325
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4674
5326
  }
@@ -4792,7 +5444,9 @@ var MarkdownRenderer = class {
4792
5444
  lowPriorityFrameBudgetMs: this.config.performance?.lowPriorityFrameBudgetMs,
4793
5445
  urgentQueueThreshold: this.config.performance?.urgentQueueThreshold,
4794
5446
  batch: this.config.performance?.batch,
4795
- historyLimit: this.config.performance?.historyLimit
5447
+ historyLimit: this.config.performance?.historyLimit,
5448
+ startupMicrotaskFlushes: this.config.performance?.startupMicrotaskFlushes,
5449
+ adaptiveBudgeting: this.config.performance?.adaptiveBudgeting
4796
5450
  }
4797
5451
  });
4798
5452
  }
@@ -4986,18 +5640,8 @@ var MarkdownRenderer = class {
4986
5640
  const receivedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
4987
5641
  const batches = (0, import_patch_batching.splitPatchBatch)(message.patches);
4988
5642
  batches.forEach((batch, index) => {
4989
- const hasStructural = batch.some((patch) => {
4990
- switch (patch.op) {
4991
- case "insertChild":
4992
- case "replaceChild":
4993
- case "deleteChild":
4994
- case "reorder":
4995
- return true;
4996
- default:
4997
- return false;
4998
- }
4999
- });
5000
- const priority = hasStructural ? "high" : batch.some(import_patch_batching.isHeavyPatch) ? "low" : "high";
5643
+ const hasSemantic = batch.some((patch) => (0, import_patch_batching.getPatchKind)(patch) === "semantic");
5644
+ const priority = hasSemantic ? "high" : batch.some(import_patch_batching.isHeavyPatch) ? "low" : "high";
5001
5645
  this.patchScheduler.enqueue({
5002
5646
  patches: batch,
5003
5647
  meta: {