@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.mjs CHANGED
@@ -81,7 +81,7 @@ var init_mdx_client = __esm({
81
81
  if (block.type !== "mdx") {
82
82
  throw new Error("Block is not MDX type");
83
83
  }
84
- const cacheKey = block.id;
84
+ const cacheKey = `${block.id}\0${block.payload.raw ?? ""}`;
85
85
  const cachedPromise = this.cache.get(cacheKey);
86
86
  if (cachedPromise) {
87
87
  return cachedPromise;
@@ -921,7 +921,11 @@ var defaultInlineComponents = {
921
921
  const n = number ?? "?";
922
922
  const href = number ? `#fn:${n}` : void 0;
923
923
  const id = number ? `fnref:${n}` : void 0;
924
- return React4.createElement("sup", { className: "footnote-ref" }, React4.createElement("a", { href, id, "data-label": label }, String(n)));
924
+ return React4.createElement(
925
+ "sup",
926
+ { className: "footnote-ref" },
927
+ React4.createElement("a", { href, id, "data-label": label, style: { color: "var(--foreground)", textDecoration: "underline" } }, String(n))
928
+ );
925
929
  }
926
930
  };
927
931
  var defaultBlockComponents = {
@@ -1064,7 +1068,7 @@ var defaultBlockComponents = {
1064
1068
  dangerouslySetInnerHTML: { __html: htmlString }
1065
1069
  });
1066
1070
  },
1067
- mdx: ({ compiledRef, compiledModule, status, errorMessage, raw }) => {
1071
+ mdx: ({ compiledRef, compiledModule, status, errorMessage }) => {
1068
1072
  const [Comp, setComp] = React4.useState(null);
1069
1073
  const [internalError, setInternalError] = React4.useState(null);
1070
1074
  const [renderError, setRenderError] = React4.useState(null);
@@ -1165,7 +1169,6 @@ var defaultBlockComponents = {
1165
1169
  const waitingForVisibility = deferHydration && !shouldHydrate && Boolean(resolvedId);
1166
1170
  const pendingLabel = waitingForVisibility ? "MDX ready (hydrate on view)\u2026" : compileMode === "worker" ? "Compiling MDX (client)\u2026" : "Compiling MDX (server)\u2026";
1167
1171
  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.");
1168
- const rawFallback = typeof raw === "string" && raw.trim().length > 0 ? React4.createElement("pre", { className: "markdown-mdx-fallback" }, raw) : null;
1169
1172
  const allowRetry = Boolean(internalError || renderError);
1170
1173
  const retryButton = allowRetry ? React4.createElement(
1171
1174
  "button",
@@ -1177,9 +1180,10 @@ var defaultBlockComponents = {
1177
1180
  "Retry hydration"
1178
1181
  ) : null;
1179
1182
  const errorPanel = React4.createElement(
1180
- "div",
1181
- { className: "markdown-mdx-error" },
1182
- React4.createElement(React4.Fragment, null, React4.createElement("strong", null, "MDX failed"), React4.createElement("div", null, message))
1183
+ React4.Fragment,
1184
+ null,
1185
+ React4.createElement("strong", null, "MDX failed"),
1186
+ React4.createElement("div", null, message)
1183
1187
  );
1184
1188
  if (Comp && !failureMessage) {
1185
1189
  return React4.createElement(
@@ -1202,14 +1206,13 @@ var defaultBlockComponents = {
1202
1206
  return React4.createElement(
1203
1207
  "div",
1204
1208
  {
1205
- className: "markdown-mdx",
1209
+ className: "markdown-mdx error",
1206
1210
  ref: containerRef,
1207
1211
  "data-mdx-ref": pendingId,
1208
1212
  "data-mdx-status": "error",
1209
1213
  "data-mdx-mode": compileMode
1210
1214
  },
1211
1215
  errorPanel,
1212
- rawFallback,
1213
1216
  retryButton
1214
1217
  );
1215
1218
  }
@@ -1236,7 +1239,16 @@ var defaultBlockComponents = {
1236
1239
  { key: item.number, id: `fn:${item.number}` },
1237
1240
  renderInlineNodes(item.inlines, defaultInlineComponents),
1238
1241
  " ",
1239
- React4.createElement("a", { href: `#fnref:${item.number}`, className: "footnote-backref", "aria-label": "Back to content" }, "\u21A9")
1242
+ React4.createElement(
1243
+ "a",
1244
+ {
1245
+ href: `#fnref:${item.number}`,
1246
+ className: "footnote-backref",
1247
+ "aria-label": "Back to content",
1248
+ style: { color: "var(--foreground)", textDecoration: "underline" }
1249
+ },
1250
+ "\u21A9"
1251
+ )
1240
1252
  )
1241
1253
  );
1242
1254
  return React4.createElement("section", { className: "footnotes" }, React4.createElement("hr", {}), React4.createElement("ol", {}, listItems));
@@ -1782,7 +1794,10 @@ function useCodeHighlightRequester() {
1782
1794
  }
1783
1795
 
1784
1796
  // src/renderer/patch-batching.ts
1785
- import { isHeavyPatch, splitPatchBatch } from "@stream-mdx/core/perf/patch-batching";
1797
+ import { getPatchKind, isHeavyPatch, splitPatchBatch } from "@stream-mdx/core/perf/patch-batching";
1798
+
1799
+ // src/renderer/patch-commit-scheduler.ts
1800
+ import { getPatchKind as getPatchKind2 } from "@stream-mdx/core/perf/patch-batching";
1786
1801
 
1787
1802
  // src/renderer/patch-coalescing.ts
1788
1803
  import {
@@ -1800,6 +1815,9 @@ var COALESCING_DURATION_ACTIVATE_MS = 6;
1800
1815
  var COALESCING_DURATION_DEACTIVATE_MS = 4;
1801
1816
  var RETAIN_TOUCHED_IN_HISTORY = typeof process !== "undefined" && process.env ? process.env.NODE_ENV !== "production" : true;
1802
1817
  var EMPTY_TOUCHED_SET = /* @__PURE__ */ new Set();
1818
+ function shouldSkipCoalescing(patches) {
1819
+ return patches.length <= 1;
1820
+ }
1803
1821
  function getDefaultNow() {
1804
1822
  if (typeof performance !== "undefined" && typeof performance.now === "function") {
1805
1823
  return () => performance.now();
@@ -1826,8 +1844,9 @@ function getDefaultCancelRaf() {
1826
1844
  }
1827
1845
  var PatchCommitScheduler = class {
1828
1846
  constructor(params) {
1829
- this.highQueue = [];
1830
- this.lowQueue = [];
1847
+ this.semanticQueue = [];
1848
+ this.enrichmentHighQueue = [];
1849
+ this.enrichmentLowQueue = [];
1831
1850
  this.scheduled = false;
1832
1851
  this.flushing = false;
1833
1852
  this.scheduledHandle = null;
@@ -1839,6 +1858,7 @@ var PatchCommitScheduler = class {
1839
1858
  this.scheduleToken = 0;
1840
1859
  this.coalescingDurationSamples = [];
1841
1860
  this.adaptiveBudgetActive = false;
1861
+ this.flushCount = 0;
1842
1862
  this.store = params.store;
1843
1863
  this.onFlush = params.onFlush;
1844
1864
  this.frameBudgetMs = Math.max(1, params.options?.frameBudgetMs ?? 10);
@@ -1854,6 +1874,8 @@ var PatchCommitScheduler = class {
1854
1874
  this.maxLowPriorityBatchesPerFlush = params.options?.maxLowPriorityBatchesPerFlush ?? 2;
1855
1875
  this.urgentQueueThreshold = Math.max(1, params.options?.urgentQueueThreshold ?? 4);
1856
1876
  this.historyLimit = Math.max(1, params.options?.historyLimit ?? 200);
1877
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(params.options?.startupMicrotaskFlushes ?? 0));
1878
+ this.adaptiveBudgeting = params.options?.adaptiveBudgeting ?? true;
1857
1879
  const requestedBatch = params.options?.batch;
1858
1880
  if (requestedBatch === "microtask") {
1859
1881
  this.batchStrategy = "microtask";
@@ -1880,19 +1902,23 @@ var PatchCommitScheduler = class {
1880
1902
  patches: input.patches,
1881
1903
  meta: input.meta ?? {},
1882
1904
  receivedAt: input.meta?.receivedAt ?? this.now(),
1905
+ kind: input.meta?.kind === "semantic" || input.meta?.kind === "enrichment" ? input.meta.kind : input.patches.some((patch) => getPatchKind2(patch) === "semantic") ? "semantic" : "enrichment",
1883
1906
  priority: input.meta?.priority === "low" ? "low" : "high"
1884
1907
  };
1885
- if (batch.priority === "low") {
1886
- this.lowQueue.push(batch);
1908
+ if (batch.kind === "semantic") {
1909
+ batch.priority = "high";
1910
+ this.semanticQueue.push(batch);
1911
+ } else if (batch.priority === "low") {
1912
+ this.enrichmentLowQueue.push(batch);
1887
1913
  } else {
1888
- this.highQueue.push(batch);
1914
+ this.enrichmentHighQueue.push(batch);
1889
1915
  }
1890
1916
  if (!this.paused) {
1891
1917
  this.schedule();
1892
1918
  }
1893
1919
  }
1894
1920
  flushAll() {
1895
- if (this.highQueue.length === 0 && this.lowQueue.length === 0 && !this.flushing) {
1921
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0 && !this.flushing) {
1896
1922
  return null;
1897
1923
  }
1898
1924
  this.cancelScheduled();
@@ -1902,9 +1928,11 @@ var PatchCommitScheduler = class {
1902
1928
  }
1903
1929
  clear() {
1904
1930
  this.cancelScheduled();
1905
- this.highQueue = [];
1906
- this.lowQueue = [];
1931
+ this.semanticQueue = [];
1932
+ this.enrichmentHighQueue = [];
1933
+ this.enrichmentLowQueue = [];
1907
1934
  this.flushing = false;
1935
+ this.flushCount = 0;
1908
1936
  this.coalescingDurationSamples = [];
1909
1937
  this.adaptiveBudgetActive = false;
1910
1938
  this.adaptiveHighBatchCap = void 0;
@@ -1912,10 +1940,10 @@ var PatchCommitScheduler = class {
1912
1940
  this.maybeResolveIdle();
1913
1941
  }
1914
1942
  getPendingCount() {
1915
- return this.highQueue.length + this.lowQueue.length;
1943
+ return this.semanticQueue.length + this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
1916
1944
  }
1917
1945
  isIdle() {
1918
- return !this.flushing && !this.scheduled && this.highQueue.length === 0 && this.lowQueue.length === 0;
1946
+ return !this.flushing && !this.scheduled && this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0;
1919
1947
  }
1920
1948
  awaitIdle() {
1921
1949
  if (this.isIdle()) {
@@ -1955,7 +1983,8 @@ var PatchCommitScheduler = class {
1955
1983
  this.runFlush(this.frameBudgetMs, false);
1956
1984
  this.maybeResolveIdle();
1957
1985
  };
1958
- if (this.batchStrategy === "microtask" && typeof queueMicrotask === "function") {
1986
+ const useStartupMicrotask = this.batchStrategy !== "microtask" && this.startupMicrotaskFlushes > 0 && this.flushCount < this.startupMicrotaskFlushes && typeof queueMicrotask === "function";
1987
+ if ((this.batchStrategy === "microtask" || useStartupMicrotask) && typeof queueMicrotask === "function") {
1959
1988
  this.scheduledViaRaf = false;
1960
1989
  this.scheduledHandle = token;
1961
1990
  queueMicrotask(execute);
@@ -1985,12 +2014,14 @@ var PatchCommitScheduler = class {
1985
2014
  if (this.paused && !manual) {
1986
2015
  return null;
1987
2016
  }
1988
- if (this.highQueue.length === 0 && this.lowQueue.length === 0) {
2017
+ if (this.semanticQueue.length === 0 && this.enrichmentHighQueue.length === 0 && this.enrichmentLowQueue.length === 0) {
1989
2018
  return null;
1990
2019
  }
1991
2020
  this.flushing = true;
1992
2021
  const startedAt = this.now();
1993
- const initialQueueSize = this.highQueue.length + this.lowQueue.length;
2022
+ const initialSemanticQueueSize = this.semanticQueue.length;
2023
+ const initialEnrichmentQueueSize = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
2024
+ const initialQueueSize = initialSemanticQueueSize + initialEnrichmentQueueSize;
1994
2025
  const batches = [];
1995
2026
  let totalInputPatches = 0;
1996
2027
  let totalAppliedPatches = 0;
@@ -2005,7 +2036,15 @@ var PatchCommitScheduler = class {
2005
2036
  consumed += 1;
2006
2037
  const applyStart = this.now();
2007
2038
  const queueDelayMs = Math.max(0, applyStart - next.receivedAt);
2008
- const { patches: coalesced, metrics: coalescingMetrics } = coalescePatchesWithMetrics(next.patches, DEFAULT_COALESCE_CONFIG);
2039
+ let coalesced;
2040
+ let coalescingMetrics;
2041
+ if (shouldSkipCoalescing(next.patches)) {
2042
+ coalesced = next.patches;
2043
+ } else {
2044
+ const result = coalescePatchesWithMetrics(next.patches, DEFAULT_COALESCE_CONFIG);
2045
+ coalesced = result.patches;
2046
+ coalescingMetrics = result.metrics;
2047
+ }
2009
2048
  const touched = this.store.applyPatches(coalesced, {
2010
2049
  coalesced: true,
2011
2050
  metrics: coalescingMetrics
@@ -2025,6 +2064,7 @@ var PatchCommitScheduler = class {
2025
2064
  receivedAt: next.receivedAt,
2026
2065
  appliedAt: applyStart,
2027
2066
  priority: next.priority,
2067
+ kind: next.kind,
2028
2068
  coalescing: coalescingMetrics
2029
2069
  });
2030
2070
  if (maxBatches && batches.length - startingBatchCount >= maxBatches) {
@@ -2041,27 +2081,32 @@ var PatchCommitScheduler = class {
2041
2081
  queue.splice(0, consumed);
2042
2082
  }
2043
2083
  };
2044
- const initialHighQueueSize = this.highQueue.length;
2084
+ processQueue(this.semanticQueue, void 0, Number.POSITIVE_INFINITY);
2085
+ const initialHighQueueSize = this.enrichmentHighQueue.length;
2045
2086
  const configuredHighLimit = manual || !this.maxBatchesPerFlush ? void 0 : initialHighQueueSize > this.maxBatchesPerFlush ? void 0 : this.maxBatchesPerFlush;
2046
- const highBatchLimit = this.adaptiveHighBatchCap ?? configuredHighLimit;
2047
- processQueue(this.highQueue, highBatchLimit, budgetMs);
2087
+ const highBatchLimit = this.adaptiveBudgeting ? this.adaptiveHighBatchCap ?? configuredHighLimit : configuredHighLimit;
2088
+ processQueue(this.enrichmentHighQueue, highBatchLimit, budgetMs);
2048
2089
  if (manual) {
2049
- processQueue(this.lowQueue, void 0, Number.POSITIVE_INFINITY);
2050
- } else if (this.lowQueue.length > 0) {
2090
+ processQueue(this.enrichmentLowQueue, void 0, Number.POSITIVE_INFINITY);
2091
+ } else if (this.enrichmentLowQueue.length > 0) {
2051
2092
  elapsed = this.now() - startedAt;
2052
2093
  const budgetForLow = Math.max(0, this.lowPriorityFrameBudgetMs - elapsed);
2053
- if (budgetForLow > 0) {
2054
- const initialLowQueueSize = this.lowQueue.length;
2094
+ const processedHigherPriorityWork = batches.length > 0;
2095
+ if (budgetForLow > 0 || !processedHigherPriorityWork) {
2096
+ const initialLowQueueSize = this.enrichmentLowQueue.length;
2055
2097
  const configuredLowLimit = manual || !this.maxLowPriorityBatchesPerFlush ? void 0 : initialLowQueueSize > this.maxLowPriorityBatchesPerFlush ? void 0 : this.maxLowPriorityBatchesPerFlush;
2056
- const lowBatchLimit = this.adaptiveLowBatchCap ?? configuredLowLimit;
2057
- processQueue(this.lowQueue, lowBatchLimit, budgetForLow);
2098
+ const lowBatchLimit = this.adaptiveBudgeting ? this.adaptiveLowBatchCap ?? configuredLowLimit : configuredLowLimit;
2099
+ processQueue(this.enrichmentLowQueue, lowBatchLimit, budgetForLow);
2058
2100
  }
2059
2101
  }
2060
2102
  this.flushing = false;
2061
2103
  const completedAt = this.now();
2062
2104
  const totalDurationMs = completedAt - startedAt;
2063
- const remaining = this.highQueue.length + this.lowQueue.length;
2105
+ const remainingSemantic = this.semanticQueue.length;
2106
+ const remainingEnrichment = this.enrichmentHighQueue.length + this.enrichmentLowQueue.length;
2107
+ const remaining = remainingSemantic + remainingEnrichment;
2064
2108
  if (batches.length > 0) {
2109
+ this.flushCount += 1;
2065
2110
  try {
2066
2111
  this.onFlush({
2067
2112
  batches,
@@ -2069,7 +2114,11 @@ var PatchCommitScheduler = class {
2069
2114
  totalAppliedPatches,
2070
2115
  totalDurationMs,
2071
2116
  remainingQueueSize: remaining,
2117
+ remainingSemanticQueueSize: remainingSemantic,
2118
+ remainingEnrichmentQueueSize: remainingEnrichment,
2072
2119
  queueDepthBefore: initialQueueSize,
2120
+ semanticQueueDepthBefore: initialSemanticQueueSize,
2121
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
2073
2122
  flushStartedAt: startedAt,
2074
2123
  flushCompletedAt: completedAt
2075
2124
  });
@@ -2080,21 +2129,27 @@ var PatchCommitScheduler = class {
2080
2129
  if (remaining > 0 && !this.paused) {
2081
2130
  this.schedule();
2082
2131
  }
2083
- const coalescingStats = this.computeCoalescingDurationStats();
2084
- this.updateAdaptiveBudget(coalescingStats.p95);
2085
- const adaptiveState = this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null);
2132
+ const coalescingStats = this.adaptiveBudgeting ? this.computeCoalescingDurationStats() : { sampleCount: this.coalescingDurationSamples.length, p95: null };
2133
+ if (this.adaptiveBudgeting) {
2134
+ this.updateAdaptiveBudget(coalescingStats.p95);
2135
+ }
2136
+ const adaptiveState = this.adaptiveBudgeting ? this.snapshotAdaptiveBudget(coalescingStats.p95 ?? null) : void 0;
2086
2137
  const flushResult = batches.length > 0 ? {
2087
2138
  batches,
2088
2139
  totalPatches: totalInputPatches,
2089
2140
  totalAppliedPatches,
2090
2141
  totalDurationMs,
2091
2142
  remainingQueueSize: remaining,
2143
+ remainingSemanticQueueSize: remainingSemantic,
2144
+ remainingEnrichmentQueueSize: remainingEnrichment,
2092
2145
  queueDepthBefore: initialQueueSize,
2146
+ semanticQueueDepthBefore: initialSemanticQueueSize,
2147
+ enrichmentQueueDepthBefore: initialEnrichmentQueueSize,
2093
2148
  flushStartedAt: startedAt,
2094
2149
  flushCompletedAt: completedAt,
2095
2150
  coalescingDurationP95: coalescingStats.p95 ?? void 0,
2096
2151
  coalescingDurationSampleCount: coalescingStats.sampleCount,
2097
- adaptiveBudgetActive: adaptiveState.active,
2152
+ adaptiveBudgetActive: adaptiveState?.active,
2098
2153
  adaptiveBudgetState: adaptiveState
2099
2154
  } : null;
2100
2155
  if (flushResult) {
@@ -2130,6 +2185,9 @@ var PatchCommitScheduler = class {
2130
2185
  };
2131
2186
  }
2132
2187
  updateAdaptiveBudget(p95) {
2188
+ if (!this.adaptiveBudgeting) {
2189
+ return;
2190
+ }
2133
2191
  if (p95 !== null && p95 > COALESCING_DURATION_ACTIVATE_MS) {
2134
2192
  if (!this.adaptiveBudgetActive) {
2135
2193
  this.adaptiveBudgetActive = true;
@@ -2203,11 +2261,13 @@ var PatchCommitScheduler = class {
2203
2261
  }
2204
2262
  restart() {
2205
2263
  this.cancelScheduled();
2206
- this.highQueue = [];
2207
- this.lowQueue = [];
2264
+ this.semanticQueue = [];
2265
+ this.enrichmentHighQueue = [];
2266
+ this.enrichmentLowQueue = [];
2208
2267
  this.flushing = false;
2209
2268
  this.sequence = 0;
2210
2269
  this.history = [];
2270
+ this.flushCount = 0;
2211
2271
  this.scheduleToken++;
2212
2272
  this.paused = false;
2213
2273
  this.coalescingDurationSamples = [];
@@ -2254,6 +2314,12 @@ var PatchCommitScheduler = class {
2254
2314
  if (typeof options.historyLimit !== "undefined") {
2255
2315
  this.setHistoryLimit(options.historyLimit);
2256
2316
  }
2317
+ if (typeof options.startupMicrotaskFlushes !== "undefined") {
2318
+ this.startupMicrotaskFlushes = Math.max(0, Math.floor(options.startupMicrotaskFlushes));
2319
+ }
2320
+ if (typeof options.adaptiveBudgeting !== "undefined") {
2321
+ this.adaptiveBudgeting = options.adaptiveBudgeting;
2322
+ }
2257
2323
  if (typeof options.batch !== "undefined") {
2258
2324
  const requestedBatch = options.batch;
2259
2325
  if (requestedBatch === "microtask") {
@@ -2288,6 +2354,7 @@ import {
2288
2354
  sanitizeHTML as sanitizeHTML2,
2289
2355
  PATCH_ROOT_ID
2290
2356
  } from "@stream-mdx/core";
2357
+ import { getPatchKind as getPatchKind3 } from "@stream-mdx/core/perf/patch-batching";
2291
2358
 
2292
2359
  // src/renderer/list-utils.ts
2293
2360
  function updateNodeDepth(node, depth) {
@@ -2383,12 +2450,28 @@ function snapshotToBlock(node) {
2383
2450
  }
2384
2451
  return null;
2385
2452
  }
2386
- var nodeSnapshotCache = /* @__PURE__ */ new Map();
2387
- var childrenSnapshotCache = /* @__PURE__ */ new Map();
2453
+ var nodeSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
2454
+ var childrenSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
2388
2455
  var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
2389
2456
  var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
2390
2457
  var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
2391
2458
  var CHILDREN_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(CHILDREN_SNAPSHOT_CACHE_LIMIT * 0.1));
2459
+ function getNodeSnapshotCacheForMap(map) {
2460
+ let cache = nodeSnapshotCacheByMap.get(map);
2461
+ if (!cache) {
2462
+ cache = /* @__PURE__ */ new Map();
2463
+ nodeSnapshotCacheByMap.set(map, cache);
2464
+ }
2465
+ return cache;
2466
+ }
2467
+ function getChildrenSnapshotCacheForMap(map) {
2468
+ let cache = childrenSnapshotCacheByMap.get(map);
2469
+ if (!cache) {
2470
+ cache = /* @__PURE__ */ new Map();
2471
+ childrenSnapshotCacheByMap.set(map, cache);
2472
+ }
2473
+ return cache;
2474
+ }
2392
2475
  function pruneCache(cache, max, buffer) {
2393
2476
  if (cache.size <= max + buffer) return;
2394
2477
  let remaining = cache.size - max;
@@ -2398,6 +2481,14 @@ function pruneCache(cache, max, buffer) {
2398
2481
  if (remaining <= 0) break;
2399
2482
  }
2400
2483
  }
2484
+ function deleteSnapshotCacheEntry(map, id) {
2485
+ getNodeSnapshotCacheForMap(map).delete(id);
2486
+ getChildrenSnapshotCacheForMap(map).delete(id);
2487
+ }
2488
+ function clearSnapshotCaches(map) {
2489
+ getNodeSnapshotCacheForMap(map).clear();
2490
+ getChildrenSnapshotCacheForMap(map).clear();
2491
+ }
2401
2492
  var EMPTY_CHILDREN = Object.freeze([]);
2402
2493
  var EMPTY_NODE_SNAPSHOT = Object.freeze({ version: -1, node: void 0 });
2403
2494
  var EMPTY_CHILDREN_SNAPSHOT = Object.freeze({
@@ -2565,9 +2656,57 @@ function createRootRecord() {
2565
2656
  parentId: null,
2566
2657
  children: [],
2567
2658
  props: {},
2568
- version: 0
2659
+ version: 0,
2660
+ blockEpoch: 0
2569
2661
  };
2570
2662
  }
2663
+ function readNumericEpoch(value) {
2664
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
2665
+ return void 0;
2666
+ }
2667
+ return value;
2668
+ }
2669
+ function readSnapshotBlockEpoch(snapshot) {
2670
+ return readNumericEpoch(snapshot.meta?.blockEpoch);
2671
+ }
2672
+ function readPatchBlockEpoch(patch) {
2673
+ if (patch.op === "setHTML") {
2674
+ return readNumericEpoch(patch.patchMeta?.blockEpoch);
2675
+ }
2676
+ return readNumericEpoch(patch.meta?.blockEpoch);
2677
+ }
2678
+ function readPatchParseEpoch(patch) {
2679
+ if (patch.op === "setHTML") {
2680
+ return readNumericEpoch(patch.patchMeta?.parseEpoch);
2681
+ }
2682
+ return readNumericEpoch(patch.meta?.parseEpoch);
2683
+ }
2684
+ function readPatchTx(patch) {
2685
+ if (patch.op === "setHTML") {
2686
+ return readNumericEpoch(patch.patchMeta?.tx);
2687
+ }
2688
+ return readNumericEpoch(patch.meta?.tx);
2689
+ }
2690
+ function assignSubtreeBlockEpoch(map, rootId, blockEpoch, touched) {
2691
+ const root = map.get(rootId);
2692
+ if (!root) return;
2693
+ const stack = [root];
2694
+ while (stack.length > 0) {
2695
+ const node = stack.pop();
2696
+ if (!node) continue;
2697
+ if (node.blockEpoch !== blockEpoch) {
2698
+ node.blockEpoch = blockEpoch;
2699
+ node.version += 1;
2700
+ touched?.add(node.id);
2701
+ }
2702
+ for (const childId of node.children) {
2703
+ const child = map.get(childId);
2704
+ if (child) {
2705
+ stack.push(child);
2706
+ }
2707
+ }
2708
+ }
2709
+ }
2571
2710
  function ensureArray(value) {
2572
2711
  return Array.isArray(value) ? value : [];
2573
2712
  }
@@ -2627,7 +2766,7 @@ function escapeHtml2(value) {
2627
2766
  }
2628
2767
  function stripOuterLineSpan(html) {
2629
2768
  if (!html) return null;
2630
- const openTagMatch = html.match(/<span[^>]*>/i);
2769
+ const openTagMatch = html.match(/<span[^>]*class="[^"]*\bline\b[^"]*"[^>]*>/i);
2631
2770
  if (!openTagMatch) {
2632
2771
  return null;
2633
2772
  }
@@ -2676,8 +2815,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2676
2815
  if (node.type !== "code-line") {
2677
2816
  anomalies.push(`type-mismatch:${node.id}:${node.type}`);
2678
2817
  nodes.delete(node.id);
2679
- nodeSnapshotCache.delete(node.id);
2680
- childrenSnapshotCache.delete(node.id);
2818
+ deleteSnapshotCacheEntry(nodes, node.id);
2681
2819
  touched.add(node.id);
2682
2820
  continue;
2683
2821
  }
@@ -2721,16 +2859,14 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2721
2859
  const existing = nodes.get(desiredId);
2722
2860
  if (existing && existing !== node) {
2723
2861
  nodes.delete(desiredId);
2724
- nodeSnapshotCache.delete(desiredId);
2725
- childrenSnapshotCache.delete(desiredId);
2862
+ deleteSnapshotCacheEntry(nodes, desiredId);
2726
2863
  touched.add(desiredId);
2727
2864
  mutated = true;
2728
2865
  }
2729
2866
  if (node.id !== desiredId) {
2730
2867
  const previousId = node.id;
2731
2868
  nodes.delete(node.id);
2732
- nodeSnapshotCache.delete(previousId);
2733
- childrenSnapshotCache.delete(previousId);
2869
+ deleteSnapshotCacheEntry(nodes, previousId);
2734
2870
  node.id = desiredId;
2735
2871
  nodes.set(desiredId, node);
2736
2872
  touched.add(desiredId);
@@ -2761,8 +2897,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2761
2897
  for (const [id, record] of nodes) {
2762
2898
  if (record.parentId === parent.id && record.type === "code-line" && !allowed.has(id)) {
2763
2899
  nodes.delete(id);
2764
- nodeSnapshotCache.delete(id);
2765
- childrenSnapshotCache.delete(id);
2900
+ deleteSnapshotCacheEntry(nodes, id);
2766
2901
  touched.add(id);
2767
2902
  mutated = true;
2768
2903
  }
@@ -2846,8 +2981,10 @@ function normalizeCodeLineProps(incoming, previous) {
2846
2981
  }
2847
2982
  function createCodeLineProps(index, text, highlight, tokens) {
2848
2983
  const safeText = typeof text === "string" ? text : "";
2849
- const sanitized = sanitizeLineInnerHtml(highlight, safeText);
2850
- const html = sanitized && sanitized.length >= safeText.length ? sanitized : escapeHtml2(safeText);
2984
+ const hasIncomingHighlight = typeof highlight === "string" && highlight.trim().length > 0;
2985
+ const sanitized = hasIncomingHighlight ? sanitizeLineInnerHtml(highlight, safeText) : null;
2986
+ const hasRenderableTokens = tokens !== void 0 && tokens !== null;
2987
+ const html = sanitized && sanitized.length >= safeText.length ? sanitized : hasRenderableTokens ? null : escapeHtml2(safeText);
2851
2988
  const props = {
2852
2989
  index,
2853
2990
  text: safeText,
@@ -2958,6 +3095,18 @@ function validateCodeBlockChildren(nodes, parent) {
2958
3095
  issues.push({ kind: "dangling-node", id });
2959
3096
  }
2960
3097
  }
3098
+ if (parent.block?.isFinalized) {
3099
+ const snapshot = createBlockSnapshot(cloneBlock(parent.block));
3100
+ const expectedChildren = ensureArray(snapshot.children).filter((child) => child.type === "code-line");
3101
+ if (expectedChildren.length !== parent.children.length) {
3102
+ issues.push({
3103
+ kind: "line-count-mismatch",
3104
+ id: parent.id,
3105
+ expected: expectedChildren.length,
3106
+ actual: parent.children.length
3107
+ });
3108
+ }
3109
+ }
2961
3110
  return { ok: issues.length === 0, issues };
2962
3111
  }
2963
3112
  function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
@@ -2974,7 +3123,7 @@ function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
2974
3123
  }
2975
3124
  const snapshot = createBlockSnapshot(cloneBlock(parent.block));
2976
3125
  removeSubtree(nodes, parent.id, touched);
2977
- const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot);
3126
+ const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot, touched, parent.blockEpoch);
2978
3127
  if (!inserted) {
2979
3128
  return false;
2980
3129
  }
@@ -2997,7 +3146,55 @@ function arraysEqual(a, b) {
2997
3146
  }
2998
3147
  return true;
2999
3148
  }
3000
- function buildNodeFromSnapshot(map, snapshot, parentId) {
3149
+ function expectedCodeLineId(parentId, index) {
3150
+ return `${parentId}::line:${index}`;
3151
+ }
3152
+ function collectStoreInvariantViolations(nodes) {
3153
+ const violations = [];
3154
+ for (const [id, node] of nodes) {
3155
+ if (node.type === "__root__") continue;
3156
+ if (node.children.length > 1) {
3157
+ const seen = /* @__PURE__ */ new Set();
3158
+ for (const childId of node.children) {
3159
+ if (seen.has(childId)) {
3160
+ violations.push(`duplicate child id:${id}:${childId}`);
3161
+ } else {
3162
+ seen.add(childId);
3163
+ }
3164
+ }
3165
+ }
3166
+ if (node.type === "list") {
3167
+ const parent = node.parentId ? nodes.get(node.parentId) : void 0;
3168
+ if (parent?.type === "list-item" && node.children.length === 0) {
3169
+ violations.push(`empty nested list:${id}`);
3170
+ }
3171
+ }
3172
+ if (node.type === "code") {
3173
+ for (let index = 0; index < node.children.length; index += 1) {
3174
+ const childId = node.children[index];
3175
+ const child = nodes.get(childId);
3176
+ if (!child) {
3177
+ violations.push(`missing code-line node:${node.id}:${childId}`);
3178
+ continue;
3179
+ }
3180
+ if (child.type !== "code-line") {
3181
+ violations.push(`non-code child in code block:${node.id}:${child.id}:${child.type}`);
3182
+ continue;
3183
+ }
3184
+ const expectedId = expectedCodeLineId(node.id, index);
3185
+ if (child.id !== expectedId) {
3186
+ violations.push(`code-line id mismatch:${node.id}:${child.id}:${expectedId}`);
3187
+ }
3188
+ const indexProp = typeof child.props?.index === "number" ? child.props.index : null;
3189
+ if (indexProp !== index) {
3190
+ violations.push(`code-line index mismatch:${node.id}:${child.id}:${indexProp}:${index}`);
3191
+ }
3192
+ }
3193
+ }
3194
+ }
3195
+ return violations;
3196
+ }
3197
+ function buildNodeFromSnapshot(map, snapshot, parentId, blockEpoch) {
3001
3198
  const record = {
3002
3199
  id: snapshot.id,
3003
3200
  type: snapshot.type,
@@ -3007,6 +3204,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
3007
3204
  meta: snapshot.meta ? { ...snapshot.meta } : void 0,
3008
3205
  range: snapshot.range ? { ...snapshot.range } : void 0,
3009
3206
  version: 0,
3207
+ blockEpoch,
3010
3208
  block: snapshotToBlock(snapshot) ?? void 0
3011
3209
  };
3012
3210
  if (record.type === "code") {
@@ -3015,7 +3213,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
3015
3213
  map.set(record.id, record);
3016
3214
  const childSnapshots = ensureArray(snapshot.children);
3017
3215
  for (const child of childSnapshots) {
3018
- const childRecord = buildNodeFromSnapshot(map, child, record.id);
3216
+ const childRecord = buildNodeFromSnapshot(map, child, record.id, blockEpoch);
3019
3217
  record.children.push(childRecord.id);
3020
3218
  }
3021
3219
  return record;
@@ -3043,8 +3241,7 @@ function removeSubtree(map, id, touched) {
3043
3241
  }
3044
3242
  }
3045
3243
  map.delete(node.id);
3046
- nodeSnapshotCache.delete(node.id);
3047
- childrenSnapshotCache.delete(node.id);
3244
+ deleteSnapshotCacheEntry(map, node.id);
3048
3245
  touched.add(node.id);
3049
3246
  for (const childId of node.children) {
3050
3247
  const child = map.get(childId);
@@ -3076,13 +3273,13 @@ function resolveParent(map, path) {
3076
3273
  }
3077
3274
  return map.get(path.blockId);
3078
3275
  }
3079
- function insertSnapshotAt(map, parent, index, snapshot, touched) {
3276
+ function insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
3080
3277
  if (!parent) return null;
3081
3278
  const existingIndex = parent.children.indexOf(snapshot.id);
3082
3279
  if (existingIndex !== -1) {
3083
3280
  removeSubtree(map, parent.children[existingIndex], touched ?? /* @__PURE__ */ new Set());
3084
3281
  }
3085
- const record = buildNodeFromSnapshot(map, snapshot, parent.id);
3282
+ const record = buildNodeFromSnapshot(map, snapshot, parent.id, blockEpoch ?? readSnapshotBlockEpoch(snapshot) ?? parent.blockEpoch);
3086
3283
  const insertedIds = [];
3087
3284
  const queue = [record];
3088
3285
  while (queue.length > 0) {
@@ -3100,13 +3297,13 @@ function insertSnapshotAt(map, parent, index, snapshot, touched) {
3100
3297
  ensureUniqueChildren(parent, touched);
3101
3298
  return { record, insertedIds };
3102
3299
  }
3103
- function replaceSnapshotAt(map, parent, index, snapshot, touched) {
3300
+ function replaceSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
3104
3301
  if (!parent) return;
3105
3302
  if (index < 0 || index >= parent.children.length) return;
3106
3303
  const targetId = parent.children[index];
3107
3304
  parent.children.splice(index, 1);
3108
3305
  removeSubtree(map, targetId, touched);
3109
- const inserted = insertSnapshotAt(map, parent, index, snapshot, touched);
3306
+ const inserted = insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch);
3110
3307
  if (inserted) {
3111
3308
  for (const id of inserted.insertedIds) {
3112
3309
  touched.add(id);
@@ -3126,6 +3323,24 @@ function createRendererStore(initialBlocks = []) {
3126
3323
  const listeners = /* @__PURE__ */ new Set();
3127
3324
  let notifyScheduled = false;
3128
3325
  let lastCoalescingMetrics = null;
3326
+ let invariantViolations = [];
3327
+ let transientDiagnostics = [];
3328
+ let nextBlockEpoch = 1;
3329
+ const debugCounters = {
3330
+ appendLineGuardRejected: 0,
3331
+ staleEpochRejected: 0,
3332
+ storeInvariantViolationCount: 0
3333
+ };
3334
+ const allocateBlockEpoch = (preferred) => {
3335
+ const resolved = readNumericEpoch(preferred);
3336
+ if (resolved !== void 0) {
3337
+ nextBlockEpoch = Math.max(nextBlockEpoch, resolved + 1);
3338
+ return resolved;
3339
+ }
3340
+ const epoch = nextBlockEpoch;
3341
+ nextBlockEpoch += 1;
3342
+ return epoch;
3343
+ };
3129
3344
  const getRaf = () => {
3130
3345
  if (typeof globalThis !== "undefined" && typeof globalThis.requestAnimationFrame === "function") {
3131
3346
  return globalThis.requestAnimationFrame;
@@ -3157,9 +3372,12 @@ function createRendererStore(initialBlocks = []) {
3157
3372
  }
3158
3373
  }
3159
3374
  }
3375
+ function appendDiagnostic(message) {
3376
+ transientDiagnostics = [...transientDiagnostics, message];
3377
+ invariantViolations = [...invariantViolations, message];
3378
+ }
3160
3379
  function rebuildFromBlocks(blocks) {
3161
- nodeSnapshotCache.clear();
3162
- childrenSnapshotCache.clear();
3380
+ clearSnapshotCaches(nodes);
3163
3381
  nodes = /* @__PURE__ */ new Map();
3164
3382
  const root = createRootRecord();
3165
3383
  nodes.set(root.id, root);
@@ -3168,9 +3386,12 @@ function createRendererStore(initialBlocks = []) {
3168
3386
  for (let i = 0; i < blocks.length; i++) {
3169
3387
  const block = cloneBlock(blocks[i]);
3170
3388
  const snapshot = createBlockSnapshot(block);
3171
- insertSnapshotAt(nodes, root, root.children.length, snapshot);
3389
+ insertSnapshotAt(nodes, root, root.children.length, snapshot, void 0, allocateBlockEpoch(readSnapshotBlockEpoch(snapshot)));
3172
3390
  }
3173
3391
  normalizeAllListDepths(nodes, /* @__PURE__ */ new Set(), PATCH_ROOT_ID);
3392
+ transientDiagnostics = [];
3393
+ invariantViolations = collectStoreInvariantViolations(nodes);
3394
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
3174
3395
  version++;
3175
3396
  blocksDirty = true;
3176
3397
  scheduleNotify();
@@ -3235,11 +3456,57 @@ function createRendererStore(initialBlocks = []) {
3235
3456
  listDepthDirty = true;
3236
3457
  }
3237
3458
  };
3238
- const applyPropsUpdate = (at, props) => {
3459
+ const semanticTxEpochs = /* @__PURE__ */ new Map();
3460
+ const resolveBlockEpochTarget = (patch, at) => {
3461
+ if ((patch.op === "insertChild" || patch.op === "replaceChild") && at.blockId === PATCH_ROOT_ID) {
3462
+ const nodeId = patch.node?.id;
3463
+ return nodeId ? nodes.get(nodeId) : void 0;
3464
+ }
3465
+ return nodes.get(at.blockId) ?? (at.nodeId ? nodes.get(at.nodeId) : void 0);
3466
+ };
3467
+ const rejectStaleEpochPatch = (patch, at) => {
3468
+ const expectedBlockEpoch = readPatchBlockEpoch(patch);
3469
+ const patchTx = readPatchTx(patch);
3470
+ if (expectedBlockEpoch === void 0) {
3471
+ return false;
3472
+ }
3473
+ if (getPatchKind3(patch) !== "semantic" || !at) {
3474
+ return false;
3475
+ }
3476
+ const target = resolveBlockEpochTarget(patch, at);
3477
+ if (!target || target.blockEpoch === expectedBlockEpoch) {
3478
+ return false;
3479
+ }
3480
+ const txEpoch = at.blockId && patchTx !== void 0 ? semanticTxEpochs.get(at.blockId) : void 0;
3481
+ if (txEpoch && txEpoch.tx === patchTx && txEpoch.blockEpoch === target.blockEpoch) {
3482
+ return false;
3483
+ }
3484
+ debugCounters.staleEpochRejected += 1;
3485
+ appendDiagnostic(
3486
+ `stale epoch rejected:${patch.op}:${target.id}:expected=${expectedBlockEpoch}:actual=${target.blockEpoch}`
3487
+ );
3488
+ return true;
3489
+ };
3490
+ const advanceBlockEpoch = (blockId, touchedSet, preferredEpoch, tx) => {
3491
+ if (!blockId || blockId === PATCH_ROOT_ID) {
3492
+ return null;
3493
+ }
3494
+ const blockRoot = nodes.get(blockId);
3495
+ if (!blockRoot) {
3496
+ return null;
3497
+ }
3498
+ const nextEpoch = allocateBlockEpoch(preferredEpoch);
3499
+ assignSubtreeBlockEpoch(nodes, blockRoot.id, nextEpoch, touchedSet);
3500
+ if (tx !== void 0) {
3501
+ semanticTxEpochs.set(blockRoot.id, { tx, blockEpoch: nextEpoch });
3502
+ }
3503
+ return nextEpoch;
3504
+ };
3505
+ const applyPropsUpdate = (at, props, preferredEpoch, tx, semanticUpdate = false) => {
3239
3506
  const perfStartLocal = perfEnabled ? getPerfNow() : 0;
3240
3507
  const targetId = at.nodeId ?? at.blockId;
3241
3508
  if (!targetId) return;
3242
- const target = nodes.get(targetId);
3509
+ let target = nodes.get(targetId);
3243
3510
  if (!target) return;
3244
3511
  const parentType = target.parentId ? nodes.get(target.parentId)?.type : void 0;
3245
3512
  if (debugListPatches && (isListRelated(target.type) || isListRelated(parentType))) {
@@ -3266,6 +3533,9 @@ function createRendererStore(initialBlocks = []) {
3266
3533
  propsChanged = true;
3267
3534
  }
3268
3535
  if (propsChanged) {
3536
+ if (semanticUpdate) {
3537
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3538
+ }
3269
3539
  target.version++;
3270
3540
  touched.add(target.id);
3271
3541
  const parent = target.parentId ? nodes.get(target.parentId) : void 0;
@@ -3329,16 +3599,56 @@ function createRendererStore(initialBlocks = []) {
3329
3599
  }
3330
3600
  }
3331
3601
  let blockChanged = false;
3602
+ let typeChanged = false;
3603
+ let rebuiltFromFinalizedBlock = false;
3604
+ let blockEpochAdvanced = false;
3332
3605
  const incomingBlock = props?.block;
3333
3606
  if (incomingBlock && typeof incomingBlock === "object") {
3334
3607
  target.block = cloneBlock(incomingBlock);
3608
+ const previousType = target.type;
3609
+ const incomingType = typeof target.block.type === "string" ? target.block.type : target.type;
3610
+ if (incomingType !== target.type) {
3611
+ target.type = incomingType;
3612
+ typeChanged = true;
3613
+ if (isListRelated(previousType) || isListRelated(target.type)) {
3614
+ markListDepthDirty(target.id);
3615
+ }
3616
+ }
3335
3617
  if (target.type === "code") {
3336
3618
  applyCodeBlockMetadata(target);
3337
3619
  }
3620
+ if (semanticUpdate) {
3621
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3622
+ blockEpochAdvanced = true;
3623
+ }
3624
+ const shouldRebuildFinalizedTree = !at.nodeId && target.id === at.blockId && Boolean(target.block?.isFinalized) && target.type !== "code" && Boolean(target.parentId);
3625
+ if (shouldRebuildFinalizedTree) {
3626
+ const parent = target.parentId ? nodes.get(target.parentId) : void 0;
3627
+ const index = parent ? parent.children.indexOf(target.id) : -1;
3628
+ if (parent && index !== -1) {
3629
+ const snapshot = createBlockSnapshot(cloneBlock(target.block));
3630
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
3631
+ mutatedBlocks = true;
3632
+ blockChanged = true;
3633
+ rebuiltFromFinalizedBlock = true;
3634
+ const rebuilt = nodes.get(target.id);
3635
+ if (isListRelated(previousType) || isListRelated(rebuilt?.type)) {
3636
+ markListDepthDirty(target.id);
3637
+ }
3638
+ target = rebuilt ?? target;
3639
+ }
3640
+ }
3338
3641
  mutatedBlocks = true;
3339
3642
  blockChanged = true;
3340
3643
  }
3341
- if (propsChanged || blockChanged) {
3644
+ if (rebuiltFromFinalizedBlock) {
3645
+ return;
3646
+ }
3647
+ if (propsChanged || blockChanged || typeChanged) {
3648
+ if (semanticUpdate && !blockEpochAdvanced) {
3649
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3650
+ blockEpochAdvanced = true;
3651
+ }
3342
3652
  target.version++;
3343
3653
  touched.add(target.id);
3344
3654
  let parentForNormalization;
@@ -3381,13 +3691,22 @@ function createRendererStore(initialBlocks = []) {
3381
3691
  const applyPatch = (patch) => {
3382
3692
  switch (patch.op) {
3383
3693
  case "insertChild": {
3694
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3384
3695
  const parent = resolveParent(nodes, patch.at);
3385
- const inserted = insertSnapshotAt(nodes, parent, patch.index, patch.node, touched);
3696
+ const inserted = insertSnapshotAt(
3697
+ nodes,
3698
+ parent,
3699
+ patch.index,
3700
+ patch.node,
3701
+ touched,
3702
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
3703
+ );
3386
3704
  if (inserted) {
3387
3705
  for (const id of inserted.insertedIds) {
3388
3706
  touched.add(id);
3389
3707
  }
3390
3708
  mutatedBlocks = true;
3709
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3391
3710
  if (isListRelated(inserted.record.type) || isListRelated(parent?.type)) {
3392
3711
  markListDepthDirty(inserted.record.id);
3393
3712
  if (parent) {
@@ -3407,6 +3726,7 @@ function createRendererStore(initialBlocks = []) {
3407
3726
  break;
3408
3727
  }
3409
3728
  case "deleteChild": {
3729
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3410
3730
  const parent = resolveParent(nodes, patch.at);
3411
3731
  if (!parent) break;
3412
3732
  if (patch.index < 0 || patch.index >= parent.children.length) break;
@@ -3418,6 +3738,7 @@ function createRendererStore(initialBlocks = []) {
3418
3738
  touched.add(parent.id);
3419
3739
  ensureUniqueChildren(parent, touched);
3420
3740
  mutatedBlocks = true;
3741
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3421
3742
  if (isListRelated(parent.type) || isListRelated(removedNode?.type)) {
3422
3743
  markListDepthDirty(parent.id);
3423
3744
  }
@@ -3434,11 +3755,20 @@ function createRendererStore(initialBlocks = []) {
3434
3755
  break;
3435
3756
  }
3436
3757
  case "replaceChild": {
3758
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3437
3759
  const parent = resolveParent(nodes, patch.at);
3438
3760
  if (!parent) break;
3439
- replaceSnapshotAt(nodes, parent, patch.index, patch.node, touched);
3761
+ replaceSnapshotAt(
3762
+ nodes,
3763
+ parent,
3764
+ patch.index,
3765
+ patch.node,
3766
+ touched,
3767
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
3768
+ );
3440
3769
  touched.add(parent.id);
3441
3770
  mutatedBlocks = true;
3771
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3442
3772
  if (isListRelated(parent.type)) {
3443
3773
  markListDepthDirty(parent.id);
3444
3774
  }
@@ -3452,18 +3782,33 @@ function createRendererStore(initialBlocks = []) {
3452
3782
  break;
3453
3783
  }
3454
3784
  case "setProps": {
3455
- applyPropsUpdate(patch.at, patch.props);
3785
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3786
+ applyPropsUpdate(patch.at, patch.props, readPatchParseEpoch(patch), readPatchTx(patch), getPatchKind3(patch) === "semantic");
3456
3787
  break;
3457
3788
  }
3458
3789
  case "setPropsBatch": {
3459
3790
  const entries2 = Array.isArray(patch.entries) ? patch.entries : [];
3460
3791
  for (const entry of entries2) {
3461
3792
  if (!entry || !entry.at) continue;
3462
- applyPropsUpdate(entry.at, entry.props);
3793
+ const entryPatch = {
3794
+ op: "setProps",
3795
+ at: entry.at,
3796
+ props: entry.props,
3797
+ meta: entry.meta ?? patch.meta
3798
+ };
3799
+ if (rejectStaleEpochPatch(entryPatch, entry.at)) continue;
3800
+ applyPropsUpdate(
3801
+ entry.at,
3802
+ entry.props,
3803
+ readPatchParseEpoch(entryPatch),
3804
+ readPatchTx(entryPatch),
3805
+ getPatchKind3(entryPatch) === "semantic"
3806
+ );
3463
3807
  }
3464
3808
  break;
3465
3809
  }
3466
3810
  case "finalize": {
3811
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3467
3812
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
3468
3813
  if (!target || !target.block) break;
3469
3814
  if (!target.block.isFinalized) {
@@ -3472,6 +3817,17 @@ function createRendererStore(initialBlocks = []) {
3472
3817
  target.version++;
3473
3818
  touched.add(target.id);
3474
3819
  mutatedBlocks = true;
3820
+ if (!patch.at.nodeId && target.id === patch.at.blockId && target.type !== "code" && target.parentId) {
3821
+ const parent = nodes.get(target.parentId);
3822
+ const index = parent ? parent.children.indexOf(target.id) : -1;
3823
+ if (parent && index !== -1) {
3824
+ const snapshot = createBlockSnapshot(cloneBlock(target.block));
3825
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
3826
+ if (isListRelated(target.type) || isListRelated(snapshot.type)) {
3827
+ markListDepthDirty(target.id);
3828
+ }
3829
+ }
3830
+ }
3475
3831
  }
3476
3832
  if (isListRelated(target.type)) {
3477
3833
  markListDepthDirty(target.id);
@@ -3485,6 +3841,7 @@ function createRendererStore(initialBlocks = []) {
3485
3841
  break;
3486
3842
  }
3487
3843
  case "reorder": {
3844
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3488
3845
  const parent = resolveParent(nodes, patch.at);
3489
3846
  if (!parent) break;
3490
3847
  const length = parent.children.length;
@@ -3499,6 +3856,9 @@ function createRendererStore(initialBlocks = []) {
3499
3856
  parent.children.splice(to, 0, ...moved);
3500
3857
  parent.version++;
3501
3858
  touched.add(parent.id);
3859
+ if (getPatchKind3(patch) === "semantic") {
3860
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3861
+ }
3502
3862
  if (isListRelated(parent.type)) {
3503
3863
  markListDepthDirty(parent.id);
3504
3864
  }
@@ -3514,15 +3874,31 @@ function createRendererStore(initialBlocks = []) {
3514
3874
  break;
3515
3875
  }
3516
3876
  case "appendLines": {
3877
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3517
3878
  const parent = resolveParent(nodes, patch.at);
3518
3879
  if (!parent) break;
3519
- const startIndex = Math.max(0, Math.min(patch.startIndex, parent.children.length));
3880
+ if (parent.type !== "code") {
3881
+ debugCounters.appendLineGuardRejected += 1;
3882
+ appendDiagnostic(`appendLines target not code:${parent.id}:${parent.type}`);
3883
+ break;
3884
+ }
3885
+ const requestedStartIndex = Math.max(0, patch.startIndex);
3886
+ const startIndex = Math.max(0, Math.min(requestedStartIndex, parent.children.length));
3520
3887
  const lineCount = patch.lines?.length ?? 0;
3521
3888
  if (lineCount === 0) break;
3889
+ const prefixMatches = parent.children.every((childId, index) => childId === expectedCodeLineId(parent.id, index));
3890
+ const isStrictTailAppend = requestedStartIndex === parent.children.length;
3891
+ if (!isStrictTailAppend || !prefixMatches) {
3892
+ debugCounters.appendLineGuardRejected += 1;
3893
+ appendDiagnostic(
3894
+ `appendLines guard rejected:${parent.id}:requestedStart=${requestedStartIndex}:childCount=${parent.children.length}:prefixMatches=${prefixMatches}`
3895
+ );
3896
+ break;
3897
+ }
3522
3898
  const originalChildren = parent.children.slice();
3523
3899
  const insertedIds = [];
3524
3900
  const insertedSet = /* @__PURE__ */ new Set();
3525
- const isTrailingAppend = startIndex >= originalChildren.length;
3901
+ const isTrailingAppend = true;
3526
3902
  let parentChildrenChanged = false;
3527
3903
  let codeLinesMutated = false;
3528
3904
  for (let offset = 0; offset < lineCount; offset++) {
@@ -3564,7 +3940,8 @@ function createRendererStore(initialBlocks = []) {
3564
3940
  parentId: parent.id,
3565
3941
  children: [],
3566
3942
  props: nextLineProps,
3567
- version: 0
3943
+ version: 0,
3944
+ blockEpoch: parent.blockEpoch
3568
3945
  };
3569
3946
  nodes.set(lineId, record);
3570
3947
  touched.add(lineId);
@@ -3614,6 +3991,7 @@ function createRendererStore(initialBlocks = []) {
3614
3991
  break;
3615
3992
  }
3616
3993
  case "setHTML": {
3994
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3617
3995
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
3618
3996
  if (!target) break;
3619
3997
  const sanitized = patch.sanitized ? patch.html : sanitizeHTML2(patch.html);
@@ -3656,6 +4034,30 @@ function createRendererStore(initialBlocks = []) {
3656
4034
  applyPatch(patch);
3657
4035
  }
3658
4036
  }
4037
+ const finalizedTableRoots = /* @__PURE__ */ new Set();
4038
+ for (const touchedId of touched) {
4039
+ let cursor = nodes.get(touchedId);
4040
+ while (cursor && cursor.id !== PATCH_ROOT_ID) {
4041
+ if (cursor.type === "table" && cursor.block?.isFinalized && cursor.parentId) {
4042
+ finalizedTableRoots.add(cursor.id);
4043
+ break;
4044
+ }
4045
+ cursor = cursor.parentId ? nodes.get(cursor.parentId) : void 0;
4046
+ }
4047
+ }
4048
+ if (finalizedTableRoots.size > 0) {
4049
+ for (const tableId of finalizedTableRoots) {
4050
+ const tableNode = nodes.get(tableId);
4051
+ if (!tableNode || !tableNode.block || !tableNode.parentId) continue;
4052
+ const parent = nodes.get(tableNode.parentId);
4053
+ if (!parent) continue;
4054
+ const index = parent.children.indexOf(tableNode.id);
4055
+ if (index === -1) continue;
4056
+ const snapshot = createBlockSnapshot(cloneBlock(tableNode.block));
4057
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, tableNode.blockEpoch);
4058
+ mutatedBlocks = true;
4059
+ }
4060
+ }
3659
4061
  if (mutatedBlocks) {
3660
4062
  blocksDirty = true;
3661
4063
  }
@@ -3676,6 +4078,8 @@ function createRendererStore(initialBlocks = []) {
3676
4078
  normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, PATCH_ROOT_ID);
3677
4079
  }
3678
4080
  }
4081
+ invariantViolations = [...collectStoreInvariantViolations(nodes), ...transientDiagnostics];
4082
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
3679
4083
  if (touched.size > 0 || mutatedBlocks) {
3680
4084
  version++;
3681
4085
  scheduleNotify();
@@ -3724,6 +4128,7 @@ function createRendererStore(initialBlocks = []) {
3724
4128
  if (!record) {
3725
4129
  return EMPTY_NODE_SNAPSHOT;
3726
4130
  }
4131
+ const nodeSnapshotCache = getNodeSnapshotCacheForMap(nodes);
3727
4132
  const cached = nodeSnapshotCache.get(id);
3728
4133
  if (cached && cached.version === record.version && cached.node === record) {
3729
4134
  return cached;
@@ -3743,6 +4148,7 @@ function createRendererStore(initialBlocks = []) {
3743
4148
  if (!record) {
3744
4149
  return EMPTY_CHILDREN_SNAPSHOT;
3745
4150
  }
4151
+ const childrenSnapshotCache = getChildrenSnapshotCacheForMap(nodes);
3746
4152
  const cached = childrenSnapshotCache.get(id);
3747
4153
  if (cached && cached.version === record.version) {
3748
4154
  return cached;
@@ -3752,6 +4158,12 @@ function createRendererStore(initialBlocks = []) {
3752
4158
  pruneCache(childrenSnapshotCache, CHILDREN_SNAPSHOT_CACHE_LIMIT, CHILDREN_SNAPSHOT_CACHE_BUFFER);
3753
4159
  return snapshot;
3754
4160
  },
4161
+ getInvariantViolations() {
4162
+ return invariantViolations.slice();
4163
+ },
4164
+ getDebugCounters() {
4165
+ return { ...debugCounters };
4166
+ },
3755
4167
  getLastCoalescingMetrics() {
3756
4168
  return lastCoalescingMetrics;
3757
4169
  },
@@ -3787,7 +4199,7 @@ function useRendererChildren(store, id) {
3787
4199
  }
3788
4200
 
3789
4201
  // src/renderer/node-views.tsx
3790
- import { stripCodeFence } from "@stream-mdx/core";
4202
+ import { getDefaultCodeWrapperAttributes as getDefaultCodeWrapperAttributes2, stripCodeFence } from "@stream-mdx/core";
3791
4203
  import React7 from "react";
3792
4204
 
3793
4205
  // src/renderer/deferred-render-context.tsx
@@ -4229,8 +4641,10 @@ var ListBlockView = React7.memo(
4229
4641
  isFinalized: block?.isFinalized ?? false
4230
4642
  });
4231
4643
  }, [listDebugEnabled, blockId, depth, ordered, childIds, listItemIds, store, block?.isFinalized]);
4232
- const listStyle = React7.useMemo(() => {
4233
- if (!ordered || listItemIds.length === 0) return void 0;
4644
+ const { listStyle, markerDigits } = React7.useMemo(() => {
4645
+ if (!ordered || listItemIds.length === 0) {
4646
+ return { listStyle: void 0, markerDigits: 1 };
4647
+ }
4234
4648
  let maxDigits = 1;
4235
4649
  listItemIds.forEach((childId, index) => {
4236
4650
  const childNode = store.getNode(childId);
@@ -4242,9 +4656,14 @@ var ListBlockView = React7.memo(
4242
4656
  const digits = digitMatch ? digitMatch.length : text.length;
4243
4657
  if (digits > maxDigits) maxDigits = digits;
4244
4658
  });
4245
- const baseIndent = depth === 0 ? "2.5rem" : depth === 1 ? "2rem" : "1.5rem";
4659
+ const baseIndent = depth === 0 ? "2rem" : depth === 1 ? "1.75rem" : "1.5rem";
4660
+ const extraDigits = Math.max(0, maxDigits - 1);
4246
4661
  return {
4247
- ["--list-indent"]: `calc(${baseIndent} + ${maxDigits}ch)`
4662
+ markerDigits: maxDigits,
4663
+ listStyle: {
4664
+ ["--list-indent"]: extraDigits > 0 ? `calc(${baseIndent} + ${extraDigits}ch)` : baseIndent,
4665
+ ["--list-marker-digits"]: String(maxDigits)
4666
+ }
4248
4667
  };
4249
4668
  }, [ordered, listItemIds, store, depth]);
4250
4669
  if (listItemIds.length === 0) return null;
@@ -4253,6 +4672,7 @@ var ListBlockView = React7.memo(
4253
4672
  {
4254
4673
  className: `markdown-list ${ordered ? "ordered" : "unordered"}`,
4255
4674
  "data-list-depth": depth,
4675
+ "data-marker-digits": ordered ? markerDigits : void 0,
4256
4676
  "data-list-id": listDebugEnabled ? blockId : void 0,
4257
4677
  style: listStyle,
4258
4678
  children: listItemIds.map((childId, index) => /* @__PURE__ */ jsx(
@@ -4434,6 +4854,7 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4434
4854
  deferredConfig ? { ...deferredConfig, enabled: true } : { enabled: false }
4435
4855
  );
4436
4856
  if (!node || !node.block) return null;
4857
+ const blockIsFinalized = node.block.isFinalized;
4437
4858
  const nodeVersion = node.version;
4438
4859
  const lines = React7.useMemo(() => {
4439
4860
  const seenIds = /* @__PURE__ */ new Set();
@@ -4464,13 +4885,25 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4464
4885
  const lang = typeof node.props?.lang === "string" ? node.props?.lang : node.block.payload.meta?.lang;
4465
4886
  const preAttrs = node.props?.preAttrs ?? void 0;
4466
4887
  const codeAttrs = node.props?.codeAttrs ?? void 0;
4888
+ const blockMeta = node.block.payload.meta ?? void 0;
4889
+ const lazyEnabled = Boolean(blockMeta?.lazyTokenization);
4890
+ const lazyTokenizedUntil = typeof blockMeta?.lazyTokenizedUntil === "number" ? blockMeta.lazyTokenizedUntil : 0;
4891
+ const lazyHighlightedLines = Array.isArray(blockMeta?.highlightedLines) ? blockMeta.highlightedLines.length : 0;
4892
+ const lazyTokenLines = Array.isArray(blockMeta?.tokenLines) ? blockMeta.tokenLines.length : 0;
4893
+ const lazySourceTextLineCount = React7.useMemo(() => {
4894
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
4895
+ const metaCode = typeof blockMeta?.code === "string" ? blockMeta.code : null;
4896
+ const sourceText = resolveCodeSourceText(raw, metaCode);
4897
+ if (sourceText.length === 0) return 0;
4898
+ return sourceText.split("\n").length;
4899
+ }, [blockMeta, node.block?.payload.raw]);
4900
+ const totalLazyLineCount = Math.max(lines.length, lazyHighlightedLines, lazyTokenLines, lazySourceTextLineCount);
4467
4901
  const virtualizationConfig = DEFAULT_VIRTUALIZED_CODE_CONFIG;
4468
4902
  const virtualizationDisabled = typeof process !== "undefined" && typeof process.env === "object" && process.env.STREAM_MDX_DISABLE_VIRTUALIZED_CODE === "true";
4469
- const shouldVirtualize = !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
4903
+ const canVirtualize = node.block.isFinalized && !lazyEnabled;
4904
+ const shouldVirtualize = canVirtualize && !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
4470
4905
  const virtualization = useVirtualizedCode(lines, shouldVirtualize ? virtualizationConfig : { ...virtualizationConfig, enabled: false });
4471
4906
  const highlightRequester = useCodeHighlightRequester();
4472
- const lazyEnabled = Boolean(node.block.payload.meta?.lazyTokenization);
4473
- const lazyTokenizedUntil = typeof node.block.payload.meta?.lazyTokenizedUntil === "number" ? node.block.payload.meta.lazyTokenizedUntil : 0;
4474
4907
  const lastRangeRef = React7.useRef(null);
4475
4908
  const rafRef = React7.useRef(null);
4476
4909
  const idleRef = React7.useRef(null);
@@ -4551,29 +4984,111 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4551
4984
  virtualization.window.startIndex,
4552
4985
  virtualization.window.endIndex
4553
4986
  ]);
4554
- const highlightedHtml = React7.useMemo(() => {
4555
- if (!shouldVirtualize && node.block?.payload.highlightedHtml) {
4556
- return node.block.payload.highlightedHtml;
4987
+ React7.useEffect(() => {
4988
+ if (shouldVirtualize || !blockIsFinalized || !lazyEnabled || !highlightRequester) return;
4989
+ if (totalLazyLineCount <= 0 || lazyTokenizedUntil >= totalLazyLineCount) return;
4990
+ const nextRange = {
4991
+ visibleStart: 0,
4992
+ visibleEnd: totalLazyLineCount,
4993
+ prefetchStart: 0,
4994
+ prefetchEnd: totalLazyLineCount,
4995
+ tokenizedUntil: lazyTokenizedUntil
4996
+ };
4997
+ const last = lastRangeRef.current;
4998
+ if (last && last.visibleStart === nextRange.visibleStart && last.visibleEnd === nextRange.visibleEnd && last.prefetchStart === nextRange.prefetchStart && last.prefetchEnd === nextRange.prefetchEnd && last.tokenizedUntil === nextRange.tokenizedUntil) {
4999
+ return;
5000
+ }
5001
+ lastRangeRef.current = nextRange;
5002
+ if (rafRef.current !== null && typeof cancelAnimationFrame === "function") {
5003
+ cancelAnimationFrame(rafRef.current);
4557
5004
  }
5005
+ const requestFullHighlight = () => {
5006
+ highlightRequester({
5007
+ blockId,
5008
+ startLine: 0,
5009
+ endLine: totalLazyLineCount,
5010
+ priority: "visible",
5011
+ reason: "finalize-full"
5012
+ });
5013
+ rafRef.current = null;
5014
+ };
5015
+ if (typeof requestAnimationFrame === "function") {
5016
+ rafRef.current = requestAnimationFrame(requestFullHighlight);
5017
+ } else {
5018
+ requestFullHighlight();
5019
+ }
5020
+ }, [blockId, blockIsFinalized, highlightRequester, lazyEnabled, lazyTokenizedUntil, shouldVirtualize, totalLazyLineCount]);
5021
+ const highlightedHtml = React7.useMemo(() => {
5022
+ const blockHtml = node.block?.payload.highlightedHtml ?? "";
5023
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
5024
+ const metaCode = typeof node.block?.payload.meta?.code === "string" ? (node.block?.payload.meta).code : null;
5025
+ const sourceText = resolveCodeSourceText(raw, metaCode);
5026
+ const sourceEndsWithNewline = sourceText.endsWith("\n");
5027
+ const sourceLines = sourceText.length > 0 ? sourceText.split("\n") : [];
5028
+ const rawFallbackLines = buildCodeFallbackLines(raw);
5029
+ const deterministicWrapperAttrs = getDefaultCodeWrapperAttributes2(lang);
5030
+ const composedPreAttrs = deterministicWrapperAttrs.preAttrs;
5031
+ const composedCodeAttrs = deterministicWrapperAttrs.codeAttrs;
5032
+ const shouldAppendTerminalNewline = (renderedLines) => {
5033
+ if (!sourceEndsWithNewline || renderedLines.length === 0 || lines.length === 0) {
5034
+ return false;
5035
+ }
5036
+ const lastRendered = renderedLines[renderedLines.length - 1];
5037
+ const lastKnown = lines[lines.length - 1];
5038
+ if (!lastRendered || !lastKnown || lastRendered.index !== lastKnown.index || lastRendered.text === "") {
5039
+ return false;
5040
+ }
5041
+ const expectedRenderedLineCount = Math.max(0, sourceLines.length - 1);
5042
+ const expectedLastRenderedLine = sourceLines.length >= 2 ? sourceLines[sourceLines.length - 2] ?? "" : "";
5043
+ return renderedLines.length === expectedRenderedLineCount && lastRendered.text === expectedLastRenderedLine;
5044
+ };
4558
5045
  if (lines.length > 0) {
4559
5046
  const hasLineHtml = lines.some((line) => typeof line.html === "string" && line.html.length > 0);
4560
- if (!hasLineHtml && node.block?.payload.highlightedHtml) {
4561
- return node.block.payload.highlightedHtml;
5047
+ if (!hasLineHtml && blockHtml) {
5048
+ return blockHtml;
4562
5049
  }
4563
5050
  if (shouldVirtualize) {
4564
- return composeHighlightedHtml(virtualization.window.visibleLines, preAttrs, codeAttrs);
5051
+ return composeHighlightedHtml(
5052
+ virtualization.window.visibleLines,
5053
+ composedPreAttrs,
5054
+ composedCodeAttrs,
5055
+ shouldAppendTerminalNewline(virtualization.window.visibleLines)
5056
+ );
5057
+ }
5058
+ return composeHighlightedHtml(lines, composedPreAttrs, composedCodeAttrs, shouldAppendTerminalNewline(lines));
5059
+ }
5060
+ if (!shouldVirtualize && blockHtml) {
5061
+ const hasHighlightedLines = blockHtml.includes('class="line"');
5062
+ if (!hasHighlightedLines && rawFallbackLines.length > 0) {
5063
+ return composeHighlightedHtml(
5064
+ rawFallbackLines,
5065
+ composedPreAttrs,
5066
+ composedCodeAttrs,
5067
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5068
+ );
4565
5069
  }
4566
- return composeHighlightedHtml(lines, preAttrs, codeAttrs);
5070
+ return blockHtml;
4567
5071
  }
4568
- const blockHtml = node.block?.payload.highlightedHtml ?? "";
4569
5072
  if (blockHtml?.trim().match(/^```[\w-]*\s*$/)) {
4570
- const raw = node.block?.payload.raw ?? "";
4571
- if (raw.trim().length > 0) {
4572
- return composeHighlightedHtml([{ index: 0, text: raw, html: null, id: "temp" }], preAttrs, codeAttrs);
5073
+ if (rawFallbackLines.length > 0) {
5074
+ return composeHighlightedHtml(
5075
+ rawFallbackLines,
5076
+ composedPreAttrs,
5077
+ composedCodeAttrs,
5078
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5079
+ );
4573
5080
  }
4574
5081
  }
5082
+ if (!blockHtml && rawFallbackLines.length > 0) {
5083
+ return composeHighlightedHtml(
5084
+ rawFallbackLines,
5085
+ composedPreAttrs,
5086
+ composedCodeAttrs,
5087
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
5088
+ );
5089
+ }
4575
5090
  return blockHtml;
4576
- }, [shouldVirtualize, node.block?.payload.highlightedHtml, lines, preAttrs, codeAttrs, virtualization.window.visibleLines, node.block?.payload.raw]);
5091
+ }, [blockIsFinalized, shouldVirtualize, node.block?.payload.highlightedHtml, node.block?.payload.meta, lines, lang, virtualization.window.visibleLines, node.block?.payload.raw]);
4577
5092
  const rendered = /* @__PURE__ */ jsx(
4578
5093
  CodeComponent,
4579
5094
  {
@@ -4585,13 +5100,19 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4585
5100
  codeAttrs
4586
5101
  }
4587
5102
  );
5103
+ const effectiveLineCount = React7.useMemo(() => {
5104
+ if (lines.length > 0) {
5105
+ return lines.length;
5106
+ }
5107
+ return 1;
5108
+ }, [lines.length]);
4588
5109
  const codeFrameClass = "not-prose flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
4589
5110
  const codeMetricsAttrs = {
4590
5111
  "data-code-block": "true",
4591
5112
  "data-block-id": blockId,
4592
5113
  "data-code-virtualized": shouldVirtualize ? "true" : "false",
4593
- "data-code-total-lines": String(lines.length),
4594
- "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : lines.length),
5114
+ "data-code-total-lines": String(effectiveLineCount),
5115
+ "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : effectiveLineCount),
4595
5116
  "data-code-window-size": String(virtualization.config.windowSize)
4596
5117
  };
4597
5118
  const codeView = shouldVirtualize ? (() => {
@@ -4637,21 +5158,152 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4637
5158
  return codeView;
4638
5159
  });
4639
5160
  CodeBlockView.displayName = "CodeBlockView";
4640
- function composeHighlightedHtml(lines, preAttrs, codeAttrs) {
5161
+ function composeHighlightedHtml(lines, preAttrs, codeAttrs, appendTerminalNewline = false) {
4641
5162
  const lineMarkup = lines.map((line) => {
4642
- const content = line.html ?? escapeHtml3(line.text);
5163
+ const content = line.html ?? renderTokenLineHtml(line.tokens) ?? escapeHtml3(line.text);
4643
5164
  const dataLine = Number.isFinite(line.index) ? ` data-line="${line.index + 1}"` : "";
4644
5165
  return `<span class="line"${dataLine}>${content}</span>`;
4645
5166
  }).join("\n");
4646
5167
  const preAttr = attrsToString(preAttrs);
4647
5168
  const codeAttr = attrsToString(codeAttrs);
4648
- return `<pre${preAttr}><code${codeAttr}>${lineMarkup}
4649
- </code></pre>`;
5169
+ return `<pre${preAttr}><code${codeAttr}>${lineMarkup}${appendTerminalNewline ? "\n" : ""}</code></pre>`;
5170
+ }
5171
+ var TOKEN_FONT_STYLE_ITALIC = 1;
5172
+ var TOKEN_FONT_STYLE_BOLD = 2;
5173
+ var TOKEN_FONT_STYLE_UNDERLINE = 4;
5174
+ var TOKEN_FONT_STYLE_STRIKETHROUGH = 8;
5175
+ function renderTokenLineHtml(tokens) {
5176
+ if (!tokens || !Array.isArray(tokens.spans)) {
5177
+ return null;
5178
+ }
5179
+ return mergeRenderableTokenSpans(tokens.spans).map((span) => renderTokenSpanHtml(span)).join("");
5180
+ }
5181
+ function renderTokenSpanHtml(span) {
5182
+ const dark = span.v?.dark;
5183
+ const light = span.v?.light;
5184
+ const single = span.s;
5185
+ const styles = [];
5186
+ if (dark?.fg) {
5187
+ styles.push(`--shiki-dark:${dark.fg}`);
5188
+ }
5189
+ if (light?.fg) {
5190
+ styles.push(`--shiki-light:${light.fg}`);
5191
+ }
5192
+ if (!light?.fg && !dark?.fg && single?.fg) {
5193
+ styles.push(`color:${single.fg}`);
5194
+ }
5195
+ const fontStyle = light?.fs ?? dark?.fs ?? single?.fs;
5196
+ if (typeof fontStyle === "number" && fontStyle > 0) {
5197
+ if (fontStyle & TOKEN_FONT_STYLE_ITALIC) {
5198
+ styles.push("font-style: italic");
5199
+ }
5200
+ if (fontStyle & TOKEN_FONT_STYLE_BOLD) {
5201
+ styles.push("font-weight: bold");
5202
+ }
5203
+ const decorations = [];
5204
+ if (fontStyle & TOKEN_FONT_STYLE_UNDERLINE) {
5205
+ decorations.push("underline");
5206
+ }
5207
+ if (fontStyle & TOKEN_FONT_STYLE_STRIKETHROUGH) {
5208
+ decorations.push("line-through");
5209
+ }
5210
+ if (decorations.length > 0) {
5211
+ styles.push(`text-decoration: ${decorations.join(" ")}`);
5212
+ }
5213
+ }
5214
+ const content = escapeHtml3(span.t ?? "");
5215
+ if (styles.length === 0) {
5216
+ return content;
5217
+ }
5218
+ return `<span style="${styles.join(";")}">${content}</span>`;
5219
+ }
5220
+ function mergeRenderableTokenSpans(spans) {
5221
+ if (spans.length <= 1) {
5222
+ return spans.slice();
5223
+ }
5224
+ const merged = [];
5225
+ let pendingWhitespace = "";
5226
+ for (const span of spans) {
5227
+ const text = typeof span.t === "string" ? span.t : "";
5228
+ if (/^[\t ]+$/.test(text)) {
5229
+ pendingWhitespace += text;
5230
+ continue;
5231
+ }
5232
+ const nextSpan = pendingWhitespace ? { ...span, t: `${pendingWhitespace}${text}` } : span;
5233
+ pendingWhitespace = "";
5234
+ const previous = merged[merged.length - 1];
5235
+ if (previous && sameTokenSpanStyle(previous, nextSpan)) {
5236
+ previous.t += nextSpan.t;
5237
+ } else {
5238
+ merged.push({ ...nextSpan });
5239
+ }
5240
+ }
5241
+ if (pendingWhitespace) {
5242
+ const previous = merged[merged.length - 1];
5243
+ if (previous) {
5244
+ previous.t += pendingWhitespace;
5245
+ } else {
5246
+ merged.push({ t: pendingWhitespace });
5247
+ }
5248
+ }
5249
+ return merged;
5250
+ }
5251
+ function sameTokenSpanStyle(a, b) {
5252
+ const variantKeys = /* @__PURE__ */ new Set([
5253
+ ...Object.keys(a.v ?? {}),
5254
+ ...Object.keys(b.v ?? {})
5255
+ ]);
5256
+ for (const key of variantKeys) {
5257
+ const aStyle = a.v?.[key];
5258
+ const bStyle = b.v?.[key];
5259
+ if ((aStyle?.fg ?? null) !== (bStyle?.fg ?? null)) return false;
5260
+ if ((aStyle?.bg ?? null) !== (bStyle?.bg ?? null)) return false;
5261
+ if ((aStyle?.fs ?? null) !== (bStyle?.fs ?? null)) return false;
5262
+ }
5263
+ if ((a.s?.fg ?? null) !== (b.s?.fg ?? null)) return false;
5264
+ if ((a.s?.bg ?? null) !== (b.s?.bg ?? null)) return false;
5265
+ if ((a.s?.fs ?? null) !== (b.s?.fs ?? null)) return false;
5266
+ return true;
5267
+ }
5268
+ function resolveCodeSourceText(raw, metaCode) {
5269
+ if (metaCode !== null) {
5270
+ return metaCode.replace(/\r\n?/g, "\n");
5271
+ }
5272
+ if (!raw) return "";
5273
+ const normalized = raw.replace(/\r\n?/g, "\n");
5274
+ const fenced = stripCodeFence(normalized);
5275
+ return fenced.hadFence ? fenced.code : normalized;
4650
5276
  }
4651
5277
  function attrsToString(attrs) {
4652
5278
  if (!attrs) return "";
4653
5279
  return Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
4654
5280
  }
5281
+ function buildCodeFallbackLines(raw) {
5282
+ const normalized = normalizeCodeFallbackRaw(raw);
5283
+ if (normalized.length === 0) {
5284
+ if (/^\s*```[^\n]*\n?\s*$/.test(raw)) {
5285
+ return [{ id: "temp-0", index: 0, text: "", html: null }];
5286
+ }
5287
+ return [];
5288
+ }
5289
+ return normalized.split("\n").map((line, index) => ({
5290
+ id: `temp-${index}`,
5291
+ index,
5292
+ text: line,
5293
+ html: null
5294
+ }));
5295
+ }
5296
+ function normalizeCodeFallbackRaw(raw) {
5297
+ if (!raw) return "";
5298
+ const normalized = raw.replace(/\r\n?/g, "\n");
5299
+ const fenced = stripCodeFence(normalized);
5300
+ if (!fenced.hadFence) {
5301
+ return normalized.replace(/\n+$/, "");
5302
+ }
5303
+ let code = fenced.code;
5304
+ code = code.replace(/\n+$/, "");
5305
+ return code;
5306
+ }
4655
5307
  function escapeHtml3(value) {
4656
5308
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4657
5309
  }
@@ -4775,7 +5427,9 @@ var MarkdownRenderer = class {
4775
5427
  lowPriorityFrameBudgetMs: this.config.performance?.lowPriorityFrameBudgetMs,
4776
5428
  urgentQueueThreshold: this.config.performance?.urgentQueueThreshold,
4777
5429
  batch: this.config.performance?.batch,
4778
- historyLimit: this.config.performance?.historyLimit
5430
+ historyLimit: this.config.performance?.historyLimit,
5431
+ startupMicrotaskFlushes: this.config.performance?.startupMicrotaskFlushes,
5432
+ adaptiveBudgeting: this.config.performance?.adaptiveBudgeting
4779
5433
  }
4780
5434
  });
4781
5435
  }
@@ -4969,18 +5623,8 @@ var MarkdownRenderer = class {
4969
5623
  const receivedAt = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
4970
5624
  const batches = splitPatchBatch(message.patches);
4971
5625
  batches.forEach((batch, index) => {
4972
- const hasStructural = batch.some((patch) => {
4973
- switch (patch.op) {
4974
- case "insertChild":
4975
- case "replaceChild":
4976
- case "deleteChild":
4977
- case "reorder":
4978
- return true;
4979
- default:
4980
- return false;
4981
- }
4982
- });
4983
- const priority = hasStructural ? "high" : batch.some(isHeavyPatch) ? "low" : "high";
5626
+ const hasSemantic = batch.some((patch) => getPatchKind(patch) === "semantic");
5627
+ const priority = hasSemantic ? "high" : batch.some(isHeavyPatch) ? "low" : "high";
4984
5628
  this.patchScheduler.enqueue({
4985
5629
  patches: batch,
4986
5630
  meta: {