@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/server.mjs CHANGED
@@ -80,7 +80,7 @@ var init_mdx_client = __esm({
80
80
  if (block.type !== "mdx") {
81
81
  throw new Error("Block is not MDX type");
82
82
  }
83
- const cacheKey = block.id;
83
+ const cacheKey = `${block.id}\0${block.payload.raw ?? ""}`;
84
84
  const cachedPromise = this.cache.get(cacheKey);
85
85
  if (cachedPromise) {
86
86
  return cachedPromise;
@@ -405,7 +405,7 @@ function useRendererChildren(store, id) {
405
405
  }
406
406
 
407
407
  // src/renderer/node-views.tsx
408
- import { stripCodeFence } from "@stream-mdx/core";
408
+ import { getDefaultCodeWrapperAttributes as getDefaultCodeWrapperAttributes2, stripCodeFence } from "@stream-mdx/core";
409
409
  import React7 from "react";
410
410
 
411
411
  // src/components/index.ts
@@ -941,7 +941,11 @@ var defaultInlineComponents = {
941
941
  const n = number ?? "?";
942
942
  const href = number ? `#fn:${n}` : void 0;
943
943
  const id = number ? `fnref:${n}` : void 0;
944
- return React4.createElement("sup", { className: "footnote-ref" }, React4.createElement("a", { href, id, "data-label": label }, String(n)));
944
+ return React4.createElement(
945
+ "sup",
946
+ { className: "footnote-ref" },
947
+ React4.createElement("a", { href, id, "data-label": label, style: { color: "var(--foreground)", textDecoration: "underline" } }, String(n))
948
+ );
945
949
  }
946
950
  };
947
951
  var defaultBlockComponents = {
@@ -1084,7 +1088,7 @@ var defaultBlockComponents = {
1084
1088
  dangerouslySetInnerHTML: { __html: htmlString }
1085
1089
  });
1086
1090
  },
1087
- mdx: ({ compiledRef, compiledModule, status, errorMessage, raw }) => {
1091
+ mdx: ({ compiledRef, compiledModule, status, errorMessage }) => {
1088
1092
  const [Comp, setComp] = React4.useState(null);
1089
1093
  const [internalError, setInternalError] = React4.useState(null);
1090
1094
  const [renderError, setRenderError] = React4.useState(null);
@@ -1185,7 +1189,6 @@ var defaultBlockComponents = {
1185
1189
  const waitingForVisibility = deferHydration && !shouldHydrate && Boolean(resolvedId);
1186
1190
  const pendingLabel = waitingForVisibility ? "MDX ready (hydrate on view)\u2026" : compileMode === "worker" ? "Compiling MDX (client)\u2026" : "Compiling MDX (server)\u2026";
1187
1191
  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.");
1188
- const rawFallback = typeof raw === "string" && raw.trim().length > 0 ? React4.createElement("pre", { className: "markdown-mdx-fallback" }, raw) : null;
1189
1192
  const allowRetry = Boolean(internalError || renderError);
1190
1193
  const retryButton = allowRetry ? React4.createElement(
1191
1194
  "button",
@@ -1197,9 +1200,10 @@ var defaultBlockComponents = {
1197
1200
  "Retry hydration"
1198
1201
  ) : null;
1199
1202
  const errorPanel = React4.createElement(
1200
- "div",
1201
- { className: "markdown-mdx-error" },
1202
- React4.createElement(React4.Fragment, null, React4.createElement("strong", null, "MDX failed"), React4.createElement("div", null, message))
1203
+ React4.Fragment,
1204
+ null,
1205
+ React4.createElement("strong", null, "MDX failed"),
1206
+ React4.createElement("div", null, message)
1203
1207
  );
1204
1208
  if (Comp && !failureMessage) {
1205
1209
  return React4.createElement(
@@ -1222,14 +1226,13 @@ var defaultBlockComponents = {
1222
1226
  return React4.createElement(
1223
1227
  "div",
1224
1228
  {
1225
- className: "markdown-mdx",
1229
+ className: "markdown-mdx error",
1226
1230
  ref: containerRef,
1227
1231
  "data-mdx-ref": pendingId,
1228
1232
  "data-mdx-status": "error",
1229
1233
  "data-mdx-mode": compileMode
1230
1234
  },
1231
1235
  errorPanel,
1232
- rawFallback,
1233
1236
  retryButton
1234
1237
  );
1235
1238
  }
@@ -1256,7 +1259,16 @@ var defaultBlockComponents = {
1256
1259
  { key: item.number, id: `fn:${item.number}` },
1257
1260
  renderInlineNodes(item.inlines, defaultInlineComponents),
1258
1261
  " ",
1259
- React4.createElement("a", { href: `#fnref:${item.number}`, className: "footnote-backref", "aria-label": "Back to content" }, "\u21A9")
1262
+ React4.createElement(
1263
+ "a",
1264
+ {
1265
+ href: `#fnref:${item.number}`,
1266
+ className: "footnote-backref",
1267
+ "aria-label": "Back to content",
1268
+ style: { color: "var(--foreground)", textDecoration: "underline" }
1269
+ },
1270
+ "\u21A9"
1271
+ )
1260
1272
  )
1261
1273
  );
1262
1274
  return React4.createElement("section", { className: "footnotes" }, React4.createElement("hr", {}), React4.createElement("ol", {}, listItems));
@@ -1812,6 +1824,7 @@ import {
1812
1824
  sanitizeHTML as sanitizeHTML2,
1813
1825
  PATCH_ROOT_ID
1814
1826
  } from "@stream-mdx/core";
1827
+ import { getPatchKind } from "@stream-mdx/core/perf/patch-batching";
1815
1828
 
1816
1829
  // src/renderer/list-utils.ts
1817
1830
  function updateNodeDepth(node, depth) {
@@ -1916,12 +1929,28 @@ function snapshotToBlock(node) {
1916
1929
  }
1917
1930
  return null;
1918
1931
  }
1919
- var nodeSnapshotCache = /* @__PURE__ */ new Map();
1920
- var childrenSnapshotCache = /* @__PURE__ */ new Map();
1932
+ var nodeSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
1933
+ var childrenSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
1921
1934
  var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
1922
1935
  var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
1923
1936
  var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
1924
1937
  var CHILDREN_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(CHILDREN_SNAPSHOT_CACHE_LIMIT * 0.1));
1938
+ function getNodeSnapshotCacheForMap(map) {
1939
+ let cache = nodeSnapshotCacheByMap.get(map);
1940
+ if (!cache) {
1941
+ cache = /* @__PURE__ */ new Map();
1942
+ nodeSnapshotCacheByMap.set(map, cache);
1943
+ }
1944
+ return cache;
1945
+ }
1946
+ function getChildrenSnapshotCacheForMap(map) {
1947
+ let cache = childrenSnapshotCacheByMap.get(map);
1948
+ if (!cache) {
1949
+ cache = /* @__PURE__ */ new Map();
1950
+ childrenSnapshotCacheByMap.set(map, cache);
1951
+ }
1952
+ return cache;
1953
+ }
1925
1954
  function pruneCache(cache, max, buffer) {
1926
1955
  if (cache.size <= max + buffer) return;
1927
1956
  let remaining = cache.size - max;
@@ -1931,6 +1960,14 @@ function pruneCache(cache, max, buffer) {
1931
1960
  if (remaining <= 0) break;
1932
1961
  }
1933
1962
  }
1963
+ function deleteSnapshotCacheEntry(map, id) {
1964
+ getNodeSnapshotCacheForMap(map).delete(id);
1965
+ getChildrenSnapshotCacheForMap(map).delete(id);
1966
+ }
1967
+ function clearSnapshotCaches(map) {
1968
+ getNodeSnapshotCacheForMap(map).clear();
1969
+ getChildrenSnapshotCacheForMap(map).clear();
1970
+ }
1934
1971
  var EMPTY_CHILDREN = Object.freeze([]);
1935
1972
  var EMPTY_NODE_SNAPSHOT = Object.freeze({ version: -1, node: void 0 });
1936
1973
  var EMPTY_CHILDREN_SNAPSHOT = Object.freeze({
@@ -2098,9 +2135,57 @@ function createRootRecord() {
2098
2135
  parentId: null,
2099
2136
  children: [],
2100
2137
  props: {},
2101
- version: 0
2138
+ version: 0,
2139
+ blockEpoch: 0
2102
2140
  };
2103
2141
  }
2142
+ function readNumericEpoch(value) {
2143
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
2144
+ return void 0;
2145
+ }
2146
+ return value;
2147
+ }
2148
+ function readSnapshotBlockEpoch(snapshot) {
2149
+ return readNumericEpoch(snapshot.meta?.blockEpoch);
2150
+ }
2151
+ function readPatchBlockEpoch(patch) {
2152
+ if (patch.op === "setHTML") {
2153
+ return readNumericEpoch(patch.patchMeta?.blockEpoch);
2154
+ }
2155
+ return readNumericEpoch(patch.meta?.blockEpoch);
2156
+ }
2157
+ function readPatchParseEpoch(patch) {
2158
+ if (patch.op === "setHTML") {
2159
+ return readNumericEpoch(patch.patchMeta?.parseEpoch);
2160
+ }
2161
+ return readNumericEpoch(patch.meta?.parseEpoch);
2162
+ }
2163
+ function readPatchTx(patch) {
2164
+ if (patch.op === "setHTML") {
2165
+ return readNumericEpoch(patch.patchMeta?.tx);
2166
+ }
2167
+ return readNumericEpoch(patch.meta?.tx);
2168
+ }
2169
+ function assignSubtreeBlockEpoch(map, rootId, blockEpoch, touched) {
2170
+ const root = map.get(rootId);
2171
+ if (!root) return;
2172
+ const stack = [root];
2173
+ while (stack.length > 0) {
2174
+ const node = stack.pop();
2175
+ if (!node) continue;
2176
+ if (node.blockEpoch !== blockEpoch) {
2177
+ node.blockEpoch = blockEpoch;
2178
+ node.version += 1;
2179
+ touched?.add(node.id);
2180
+ }
2181
+ for (const childId of node.children) {
2182
+ const child = map.get(childId);
2183
+ if (child) {
2184
+ stack.push(child);
2185
+ }
2186
+ }
2187
+ }
2188
+ }
2104
2189
  function ensureArray(value) {
2105
2190
  return Array.isArray(value) ? value : [];
2106
2191
  }
@@ -2160,7 +2245,7 @@ function escapeHtml2(value) {
2160
2245
  }
2161
2246
  function stripOuterLineSpan(html) {
2162
2247
  if (!html) return null;
2163
- const openTagMatch = html.match(/<span[^>]*>/i);
2248
+ const openTagMatch = html.match(/<span[^>]*class="[^"]*\bline\b[^"]*"[^>]*>/i);
2164
2249
  if (!openTagMatch) {
2165
2250
  return null;
2166
2251
  }
@@ -2209,8 +2294,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2209
2294
  if (node.type !== "code-line") {
2210
2295
  anomalies.push(`type-mismatch:${node.id}:${node.type}`);
2211
2296
  nodes.delete(node.id);
2212
- nodeSnapshotCache.delete(node.id);
2213
- childrenSnapshotCache.delete(node.id);
2297
+ deleteSnapshotCacheEntry(nodes, node.id);
2214
2298
  touched.add(node.id);
2215
2299
  continue;
2216
2300
  }
@@ -2254,16 +2338,14 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2254
2338
  const existing = nodes.get(desiredId);
2255
2339
  if (existing && existing !== node) {
2256
2340
  nodes.delete(desiredId);
2257
- nodeSnapshotCache.delete(desiredId);
2258
- childrenSnapshotCache.delete(desiredId);
2341
+ deleteSnapshotCacheEntry(nodes, desiredId);
2259
2342
  touched.add(desiredId);
2260
2343
  mutated = true;
2261
2344
  }
2262
2345
  if (node.id !== desiredId) {
2263
2346
  const previousId = node.id;
2264
2347
  nodes.delete(node.id);
2265
- nodeSnapshotCache.delete(previousId);
2266
- childrenSnapshotCache.delete(previousId);
2348
+ deleteSnapshotCacheEntry(nodes, previousId);
2267
2349
  node.id = desiredId;
2268
2350
  nodes.set(desiredId, node);
2269
2351
  touched.add(desiredId);
@@ -2294,8 +2376,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
2294
2376
  for (const [id, record] of nodes) {
2295
2377
  if (record.parentId === parent.id && record.type === "code-line" && !allowed.has(id)) {
2296
2378
  nodes.delete(id);
2297
- nodeSnapshotCache.delete(id);
2298
- childrenSnapshotCache.delete(id);
2379
+ deleteSnapshotCacheEntry(nodes, id);
2299
2380
  touched.add(id);
2300
2381
  mutated = true;
2301
2382
  }
@@ -2379,8 +2460,10 @@ function normalizeCodeLineProps(incoming, previous) {
2379
2460
  }
2380
2461
  function createCodeLineProps(index, text, highlight, tokens) {
2381
2462
  const safeText = typeof text === "string" ? text : "";
2382
- const sanitized = sanitizeLineInnerHtml(highlight, safeText);
2383
- const html = sanitized && sanitized.length >= safeText.length ? sanitized : escapeHtml2(safeText);
2463
+ const hasIncomingHighlight = typeof highlight === "string" && highlight.trim().length > 0;
2464
+ const sanitized = hasIncomingHighlight ? sanitizeLineInnerHtml(highlight, safeText) : null;
2465
+ const hasRenderableTokens = tokens !== void 0 && tokens !== null;
2466
+ const html = sanitized && sanitized.length >= safeText.length ? sanitized : hasRenderableTokens ? null : escapeHtml2(safeText);
2384
2467
  const props = {
2385
2468
  index,
2386
2469
  text: safeText,
@@ -2491,6 +2574,18 @@ function validateCodeBlockChildren(nodes, parent) {
2491
2574
  issues.push({ kind: "dangling-node", id });
2492
2575
  }
2493
2576
  }
2577
+ if (parent.block?.isFinalized) {
2578
+ const snapshot = createBlockSnapshot(cloneBlock(parent.block));
2579
+ const expectedChildren = ensureArray(snapshot.children).filter((child) => child.type === "code-line");
2580
+ if (expectedChildren.length !== parent.children.length) {
2581
+ issues.push({
2582
+ kind: "line-count-mismatch",
2583
+ id: parent.id,
2584
+ expected: expectedChildren.length,
2585
+ actual: parent.children.length
2586
+ });
2587
+ }
2588
+ }
2494
2589
  return { ok: issues.length === 0, issues };
2495
2590
  }
2496
2591
  function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
@@ -2507,7 +2602,7 @@ function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
2507
2602
  }
2508
2603
  const snapshot = createBlockSnapshot(cloneBlock(parent.block));
2509
2604
  removeSubtree(nodes, parent.id, touched);
2510
- const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot);
2605
+ const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot, touched, parent.blockEpoch);
2511
2606
  if (!inserted) {
2512
2607
  return false;
2513
2608
  }
@@ -2530,7 +2625,55 @@ function arraysEqual(a, b) {
2530
2625
  }
2531
2626
  return true;
2532
2627
  }
2533
- function buildNodeFromSnapshot(map, snapshot, parentId) {
2628
+ function expectedCodeLineId(parentId, index) {
2629
+ return `${parentId}::line:${index}`;
2630
+ }
2631
+ function collectStoreInvariantViolations(nodes) {
2632
+ const violations = [];
2633
+ for (const [id, node] of nodes) {
2634
+ if (node.type === "__root__") continue;
2635
+ if (node.children.length > 1) {
2636
+ const seen = /* @__PURE__ */ new Set();
2637
+ for (const childId of node.children) {
2638
+ if (seen.has(childId)) {
2639
+ violations.push(`duplicate child id:${id}:${childId}`);
2640
+ } else {
2641
+ seen.add(childId);
2642
+ }
2643
+ }
2644
+ }
2645
+ if (node.type === "list") {
2646
+ const parent = node.parentId ? nodes.get(node.parentId) : void 0;
2647
+ if (parent?.type === "list-item" && node.children.length === 0) {
2648
+ violations.push(`empty nested list:${id}`);
2649
+ }
2650
+ }
2651
+ if (node.type === "code") {
2652
+ for (let index = 0; index < node.children.length; index += 1) {
2653
+ const childId = node.children[index];
2654
+ const child = nodes.get(childId);
2655
+ if (!child) {
2656
+ violations.push(`missing code-line node:${node.id}:${childId}`);
2657
+ continue;
2658
+ }
2659
+ if (child.type !== "code-line") {
2660
+ violations.push(`non-code child in code block:${node.id}:${child.id}:${child.type}`);
2661
+ continue;
2662
+ }
2663
+ const expectedId = expectedCodeLineId(node.id, index);
2664
+ if (child.id !== expectedId) {
2665
+ violations.push(`code-line id mismatch:${node.id}:${child.id}:${expectedId}`);
2666
+ }
2667
+ const indexProp = typeof child.props?.index === "number" ? child.props.index : null;
2668
+ if (indexProp !== index) {
2669
+ violations.push(`code-line index mismatch:${node.id}:${child.id}:${indexProp}:${index}`);
2670
+ }
2671
+ }
2672
+ }
2673
+ }
2674
+ return violations;
2675
+ }
2676
+ function buildNodeFromSnapshot(map, snapshot, parentId, blockEpoch) {
2534
2677
  const record = {
2535
2678
  id: snapshot.id,
2536
2679
  type: snapshot.type,
@@ -2540,6 +2683,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
2540
2683
  meta: snapshot.meta ? { ...snapshot.meta } : void 0,
2541
2684
  range: snapshot.range ? { ...snapshot.range } : void 0,
2542
2685
  version: 0,
2686
+ blockEpoch,
2543
2687
  block: snapshotToBlock(snapshot) ?? void 0
2544
2688
  };
2545
2689
  if (record.type === "code") {
@@ -2548,7 +2692,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
2548
2692
  map.set(record.id, record);
2549
2693
  const childSnapshots = ensureArray(snapshot.children);
2550
2694
  for (const child of childSnapshots) {
2551
- const childRecord = buildNodeFromSnapshot(map, child, record.id);
2695
+ const childRecord = buildNodeFromSnapshot(map, child, record.id, blockEpoch);
2552
2696
  record.children.push(childRecord.id);
2553
2697
  }
2554
2698
  return record;
@@ -2576,8 +2720,7 @@ function removeSubtree(map, id, touched) {
2576
2720
  }
2577
2721
  }
2578
2722
  map.delete(node.id);
2579
- nodeSnapshotCache.delete(node.id);
2580
- childrenSnapshotCache.delete(node.id);
2723
+ deleteSnapshotCacheEntry(map, node.id);
2581
2724
  touched.add(node.id);
2582
2725
  for (const childId of node.children) {
2583
2726
  const child = map.get(childId);
@@ -2609,13 +2752,13 @@ function resolveParent(map, path) {
2609
2752
  }
2610
2753
  return map.get(path.blockId);
2611
2754
  }
2612
- function insertSnapshotAt(map, parent, index, snapshot, touched) {
2755
+ function insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
2613
2756
  if (!parent) return null;
2614
2757
  const existingIndex = parent.children.indexOf(snapshot.id);
2615
2758
  if (existingIndex !== -1) {
2616
2759
  removeSubtree(map, parent.children[existingIndex], touched ?? /* @__PURE__ */ new Set());
2617
2760
  }
2618
- const record = buildNodeFromSnapshot(map, snapshot, parent.id);
2761
+ const record = buildNodeFromSnapshot(map, snapshot, parent.id, blockEpoch ?? readSnapshotBlockEpoch(snapshot) ?? parent.blockEpoch);
2619
2762
  const insertedIds = [];
2620
2763
  const queue = [record];
2621
2764
  while (queue.length > 0) {
@@ -2633,13 +2776,13 @@ function insertSnapshotAt(map, parent, index, snapshot, touched) {
2633
2776
  ensureUniqueChildren(parent, touched);
2634
2777
  return { record, insertedIds };
2635
2778
  }
2636
- function replaceSnapshotAt(map, parent, index, snapshot, touched) {
2779
+ function replaceSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
2637
2780
  if (!parent) return;
2638
2781
  if (index < 0 || index >= parent.children.length) return;
2639
2782
  const targetId = parent.children[index];
2640
2783
  parent.children.splice(index, 1);
2641
2784
  removeSubtree(map, targetId, touched);
2642
- const inserted = insertSnapshotAt(map, parent, index, snapshot, touched);
2785
+ const inserted = insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch);
2643
2786
  if (inserted) {
2644
2787
  for (const id of inserted.insertedIds) {
2645
2788
  touched.add(id);
@@ -2659,6 +2802,24 @@ function createRendererStore(initialBlocks = []) {
2659
2802
  const listeners = /* @__PURE__ */ new Set();
2660
2803
  let notifyScheduled = false;
2661
2804
  let lastCoalescingMetrics = null;
2805
+ let invariantViolations = [];
2806
+ let transientDiagnostics = [];
2807
+ let nextBlockEpoch = 1;
2808
+ const debugCounters = {
2809
+ appendLineGuardRejected: 0,
2810
+ staleEpochRejected: 0,
2811
+ storeInvariantViolationCount: 0
2812
+ };
2813
+ const allocateBlockEpoch = (preferred) => {
2814
+ const resolved = readNumericEpoch(preferred);
2815
+ if (resolved !== void 0) {
2816
+ nextBlockEpoch = Math.max(nextBlockEpoch, resolved + 1);
2817
+ return resolved;
2818
+ }
2819
+ const epoch = nextBlockEpoch;
2820
+ nextBlockEpoch += 1;
2821
+ return epoch;
2822
+ };
2662
2823
  const getRaf = () => {
2663
2824
  if (typeof globalThis !== "undefined" && typeof globalThis.requestAnimationFrame === "function") {
2664
2825
  return globalThis.requestAnimationFrame;
@@ -2690,9 +2851,12 @@ function createRendererStore(initialBlocks = []) {
2690
2851
  }
2691
2852
  }
2692
2853
  }
2854
+ function appendDiagnostic(message) {
2855
+ transientDiagnostics = [...transientDiagnostics, message];
2856
+ invariantViolations = [...invariantViolations, message];
2857
+ }
2693
2858
  function rebuildFromBlocks(blocks) {
2694
- nodeSnapshotCache.clear();
2695
- childrenSnapshotCache.clear();
2859
+ clearSnapshotCaches(nodes);
2696
2860
  nodes = /* @__PURE__ */ new Map();
2697
2861
  const root = createRootRecord();
2698
2862
  nodes.set(root.id, root);
@@ -2701,9 +2865,12 @@ function createRendererStore(initialBlocks = []) {
2701
2865
  for (let i = 0; i < blocks.length; i++) {
2702
2866
  const block = cloneBlock(blocks[i]);
2703
2867
  const snapshot = createBlockSnapshot(block);
2704
- insertSnapshotAt(nodes, root, root.children.length, snapshot);
2868
+ insertSnapshotAt(nodes, root, root.children.length, snapshot, void 0, allocateBlockEpoch(readSnapshotBlockEpoch(snapshot)));
2705
2869
  }
2706
2870
  normalizeAllListDepths(nodes, /* @__PURE__ */ new Set(), PATCH_ROOT_ID);
2871
+ transientDiagnostics = [];
2872
+ invariantViolations = collectStoreInvariantViolations(nodes);
2873
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
2707
2874
  version++;
2708
2875
  blocksDirty = true;
2709
2876
  scheduleNotify();
@@ -2768,11 +2935,57 @@ function createRendererStore(initialBlocks = []) {
2768
2935
  listDepthDirty = true;
2769
2936
  }
2770
2937
  };
2771
- const applyPropsUpdate = (at, props) => {
2938
+ const semanticTxEpochs = /* @__PURE__ */ new Map();
2939
+ const resolveBlockEpochTarget = (patch, at) => {
2940
+ if ((patch.op === "insertChild" || patch.op === "replaceChild") && at.blockId === PATCH_ROOT_ID) {
2941
+ const nodeId = patch.node?.id;
2942
+ return nodeId ? nodes.get(nodeId) : void 0;
2943
+ }
2944
+ return nodes.get(at.blockId) ?? (at.nodeId ? nodes.get(at.nodeId) : void 0);
2945
+ };
2946
+ const rejectStaleEpochPatch = (patch, at) => {
2947
+ const expectedBlockEpoch = readPatchBlockEpoch(patch);
2948
+ const patchTx = readPatchTx(patch);
2949
+ if (expectedBlockEpoch === void 0) {
2950
+ return false;
2951
+ }
2952
+ if (getPatchKind(patch) !== "semantic" || !at) {
2953
+ return false;
2954
+ }
2955
+ const target = resolveBlockEpochTarget(patch, at);
2956
+ if (!target || target.blockEpoch === expectedBlockEpoch) {
2957
+ return false;
2958
+ }
2959
+ const txEpoch = at.blockId && patchTx !== void 0 ? semanticTxEpochs.get(at.blockId) : void 0;
2960
+ if (txEpoch && txEpoch.tx === patchTx && txEpoch.blockEpoch === target.blockEpoch) {
2961
+ return false;
2962
+ }
2963
+ debugCounters.staleEpochRejected += 1;
2964
+ appendDiagnostic(
2965
+ `stale epoch rejected:${patch.op}:${target.id}:expected=${expectedBlockEpoch}:actual=${target.blockEpoch}`
2966
+ );
2967
+ return true;
2968
+ };
2969
+ const advanceBlockEpoch = (blockId, touchedSet, preferredEpoch, tx) => {
2970
+ if (!blockId || blockId === PATCH_ROOT_ID) {
2971
+ return null;
2972
+ }
2973
+ const blockRoot = nodes.get(blockId);
2974
+ if (!blockRoot) {
2975
+ return null;
2976
+ }
2977
+ const nextEpoch = allocateBlockEpoch(preferredEpoch);
2978
+ assignSubtreeBlockEpoch(nodes, blockRoot.id, nextEpoch, touchedSet);
2979
+ if (tx !== void 0) {
2980
+ semanticTxEpochs.set(blockRoot.id, { tx, blockEpoch: nextEpoch });
2981
+ }
2982
+ return nextEpoch;
2983
+ };
2984
+ const applyPropsUpdate = (at, props, preferredEpoch, tx, semanticUpdate = false) => {
2772
2985
  const perfStartLocal = perfEnabled ? getPerfNow() : 0;
2773
2986
  const targetId = at.nodeId ?? at.blockId;
2774
2987
  if (!targetId) return;
2775
- const target = nodes.get(targetId);
2988
+ let target = nodes.get(targetId);
2776
2989
  if (!target) return;
2777
2990
  const parentType = target.parentId ? nodes.get(target.parentId)?.type : void 0;
2778
2991
  if (debugListPatches && (isListRelated(target.type) || isListRelated(parentType))) {
@@ -2799,6 +3012,9 @@ function createRendererStore(initialBlocks = []) {
2799
3012
  propsChanged = true;
2800
3013
  }
2801
3014
  if (propsChanged) {
3015
+ if (semanticUpdate) {
3016
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3017
+ }
2802
3018
  target.version++;
2803
3019
  touched.add(target.id);
2804
3020
  const parent = target.parentId ? nodes.get(target.parentId) : void 0;
@@ -2862,16 +3078,56 @@ function createRendererStore(initialBlocks = []) {
2862
3078
  }
2863
3079
  }
2864
3080
  let blockChanged = false;
3081
+ let typeChanged = false;
3082
+ let rebuiltFromFinalizedBlock = false;
3083
+ let blockEpochAdvanced = false;
2865
3084
  const incomingBlock = props?.block;
2866
3085
  if (incomingBlock && typeof incomingBlock === "object") {
2867
3086
  target.block = cloneBlock(incomingBlock);
3087
+ const previousType = target.type;
3088
+ const incomingType = typeof target.block.type === "string" ? target.block.type : target.type;
3089
+ if (incomingType !== target.type) {
3090
+ target.type = incomingType;
3091
+ typeChanged = true;
3092
+ if (isListRelated(previousType) || isListRelated(target.type)) {
3093
+ markListDepthDirty(target.id);
3094
+ }
3095
+ }
2868
3096
  if (target.type === "code") {
2869
3097
  applyCodeBlockMetadata(target);
2870
3098
  }
3099
+ if (semanticUpdate) {
3100
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3101
+ blockEpochAdvanced = true;
3102
+ }
3103
+ const shouldRebuildFinalizedTree = !at.nodeId && target.id === at.blockId && Boolean(target.block?.isFinalized) && target.type !== "code" && Boolean(target.parentId);
3104
+ if (shouldRebuildFinalizedTree) {
3105
+ const parent = target.parentId ? nodes.get(target.parentId) : void 0;
3106
+ const index = parent ? parent.children.indexOf(target.id) : -1;
3107
+ if (parent && index !== -1) {
3108
+ const snapshot = createBlockSnapshot(cloneBlock(target.block));
3109
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
3110
+ mutatedBlocks = true;
3111
+ blockChanged = true;
3112
+ rebuiltFromFinalizedBlock = true;
3113
+ const rebuilt = nodes.get(target.id);
3114
+ if (isListRelated(previousType) || isListRelated(rebuilt?.type)) {
3115
+ markListDepthDirty(target.id);
3116
+ }
3117
+ target = rebuilt ?? target;
3118
+ }
3119
+ }
2871
3120
  mutatedBlocks = true;
2872
3121
  blockChanged = true;
2873
3122
  }
2874
- if (propsChanged || blockChanged) {
3123
+ if (rebuiltFromFinalizedBlock) {
3124
+ return;
3125
+ }
3126
+ if (propsChanged || blockChanged || typeChanged) {
3127
+ if (semanticUpdate && !blockEpochAdvanced) {
3128
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
3129
+ blockEpochAdvanced = true;
3130
+ }
2875
3131
  target.version++;
2876
3132
  touched.add(target.id);
2877
3133
  let parentForNormalization;
@@ -2914,13 +3170,22 @@ function createRendererStore(initialBlocks = []) {
2914
3170
  const applyPatch = (patch) => {
2915
3171
  switch (patch.op) {
2916
3172
  case "insertChild": {
3173
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
2917
3174
  const parent = resolveParent(nodes, patch.at);
2918
- const inserted = insertSnapshotAt(nodes, parent, patch.index, patch.node, touched);
3175
+ const inserted = insertSnapshotAt(
3176
+ nodes,
3177
+ parent,
3178
+ patch.index,
3179
+ patch.node,
3180
+ touched,
3181
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
3182
+ );
2919
3183
  if (inserted) {
2920
3184
  for (const id of inserted.insertedIds) {
2921
3185
  touched.add(id);
2922
3186
  }
2923
3187
  mutatedBlocks = true;
3188
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
2924
3189
  if (isListRelated(inserted.record.type) || isListRelated(parent?.type)) {
2925
3190
  markListDepthDirty(inserted.record.id);
2926
3191
  if (parent) {
@@ -2940,6 +3205,7 @@ function createRendererStore(initialBlocks = []) {
2940
3205
  break;
2941
3206
  }
2942
3207
  case "deleteChild": {
3208
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
2943
3209
  const parent = resolveParent(nodes, patch.at);
2944
3210
  if (!parent) break;
2945
3211
  if (patch.index < 0 || patch.index >= parent.children.length) break;
@@ -2951,6 +3217,7 @@ function createRendererStore(initialBlocks = []) {
2951
3217
  touched.add(parent.id);
2952
3218
  ensureUniqueChildren(parent, touched);
2953
3219
  mutatedBlocks = true;
3220
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
2954
3221
  if (isListRelated(parent.type) || isListRelated(removedNode?.type)) {
2955
3222
  markListDepthDirty(parent.id);
2956
3223
  }
@@ -2967,11 +3234,20 @@ function createRendererStore(initialBlocks = []) {
2967
3234
  break;
2968
3235
  }
2969
3236
  case "replaceChild": {
3237
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
2970
3238
  const parent = resolveParent(nodes, patch.at);
2971
3239
  if (!parent) break;
2972
- replaceSnapshotAt(nodes, parent, patch.index, patch.node, touched);
3240
+ replaceSnapshotAt(
3241
+ nodes,
3242
+ parent,
3243
+ patch.index,
3244
+ patch.node,
3245
+ touched,
3246
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
3247
+ );
2973
3248
  touched.add(parent.id);
2974
3249
  mutatedBlocks = true;
3250
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
2975
3251
  if (isListRelated(parent.type)) {
2976
3252
  markListDepthDirty(parent.id);
2977
3253
  }
@@ -2985,18 +3261,33 @@ function createRendererStore(initialBlocks = []) {
2985
3261
  break;
2986
3262
  }
2987
3263
  case "setProps": {
2988
- applyPropsUpdate(patch.at, patch.props);
3264
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3265
+ applyPropsUpdate(patch.at, patch.props, readPatchParseEpoch(patch), readPatchTx(patch), getPatchKind(patch) === "semantic");
2989
3266
  break;
2990
3267
  }
2991
3268
  case "setPropsBatch": {
2992
3269
  const entries2 = Array.isArray(patch.entries) ? patch.entries : [];
2993
3270
  for (const entry of entries2) {
2994
3271
  if (!entry || !entry.at) continue;
2995
- applyPropsUpdate(entry.at, entry.props);
3272
+ const entryPatch = {
3273
+ op: "setProps",
3274
+ at: entry.at,
3275
+ props: entry.props,
3276
+ meta: entry.meta ?? patch.meta
3277
+ };
3278
+ if (rejectStaleEpochPatch(entryPatch, entry.at)) continue;
3279
+ applyPropsUpdate(
3280
+ entry.at,
3281
+ entry.props,
3282
+ readPatchParseEpoch(entryPatch),
3283
+ readPatchTx(entryPatch),
3284
+ getPatchKind(entryPatch) === "semantic"
3285
+ );
2996
3286
  }
2997
3287
  break;
2998
3288
  }
2999
3289
  case "finalize": {
3290
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3000
3291
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
3001
3292
  if (!target || !target.block) break;
3002
3293
  if (!target.block.isFinalized) {
@@ -3005,6 +3296,17 @@ function createRendererStore(initialBlocks = []) {
3005
3296
  target.version++;
3006
3297
  touched.add(target.id);
3007
3298
  mutatedBlocks = true;
3299
+ if (!patch.at.nodeId && target.id === patch.at.blockId && target.type !== "code" && target.parentId) {
3300
+ const parent = nodes.get(target.parentId);
3301
+ const index = parent ? parent.children.indexOf(target.id) : -1;
3302
+ if (parent && index !== -1) {
3303
+ const snapshot = createBlockSnapshot(cloneBlock(target.block));
3304
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
3305
+ if (isListRelated(target.type) || isListRelated(snapshot.type)) {
3306
+ markListDepthDirty(target.id);
3307
+ }
3308
+ }
3309
+ }
3008
3310
  }
3009
3311
  if (isListRelated(target.type)) {
3010
3312
  markListDepthDirty(target.id);
@@ -3018,6 +3320,7 @@ function createRendererStore(initialBlocks = []) {
3018
3320
  break;
3019
3321
  }
3020
3322
  case "reorder": {
3323
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3021
3324
  const parent = resolveParent(nodes, patch.at);
3022
3325
  if (!parent) break;
3023
3326
  const length = parent.children.length;
@@ -3032,6 +3335,9 @@ function createRendererStore(initialBlocks = []) {
3032
3335
  parent.children.splice(to, 0, ...moved);
3033
3336
  parent.version++;
3034
3337
  touched.add(parent.id);
3338
+ if (getPatchKind(patch) === "semantic") {
3339
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
3340
+ }
3035
3341
  if (isListRelated(parent.type)) {
3036
3342
  markListDepthDirty(parent.id);
3037
3343
  }
@@ -3047,15 +3353,31 @@ function createRendererStore(initialBlocks = []) {
3047
3353
  break;
3048
3354
  }
3049
3355
  case "appendLines": {
3356
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3050
3357
  const parent = resolveParent(nodes, patch.at);
3051
3358
  if (!parent) break;
3052
- const startIndex = Math.max(0, Math.min(patch.startIndex, parent.children.length));
3359
+ if (parent.type !== "code") {
3360
+ debugCounters.appendLineGuardRejected += 1;
3361
+ appendDiagnostic(`appendLines target not code:${parent.id}:${parent.type}`);
3362
+ break;
3363
+ }
3364
+ const requestedStartIndex = Math.max(0, patch.startIndex);
3365
+ const startIndex = Math.max(0, Math.min(requestedStartIndex, parent.children.length));
3053
3366
  const lineCount = patch.lines?.length ?? 0;
3054
3367
  if (lineCount === 0) break;
3368
+ const prefixMatches = parent.children.every((childId, index) => childId === expectedCodeLineId(parent.id, index));
3369
+ const isStrictTailAppend = requestedStartIndex === parent.children.length;
3370
+ if (!isStrictTailAppend || !prefixMatches) {
3371
+ debugCounters.appendLineGuardRejected += 1;
3372
+ appendDiagnostic(
3373
+ `appendLines guard rejected:${parent.id}:requestedStart=${requestedStartIndex}:childCount=${parent.children.length}:prefixMatches=${prefixMatches}`
3374
+ );
3375
+ break;
3376
+ }
3055
3377
  const originalChildren = parent.children.slice();
3056
3378
  const insertedIds = [];
3057
3379
  const insertedSet = /* @__PURE__ */ new Set();
3058
- const isTrailingAppend = startIndex >= originalChildren.length;
3380
+ const isTrailingAppend = true;
3059
3381
  let parentChildrenChanged = false;
3060
3382
  let codeLinesMutated = false;
3061
3383
  for (let offset = 0; offset < lineCount; offset++) {
@@ -3097,7 +3419,8 @@ function createRendererStore(initialBlocks = []) {
3097
3419
  parentId: parent.id,
3098
3420
  children: [],
3099
3421
  props: nextLineProps,
3100
- version: 0
3422
+ version: 0,
3423
+ blockEpoch: parent.blockEpoch
3101
3424
  };
3102
3425
  nodes.set(lineId, record);
3103
3426
  touched.add(lineId);
@@ -3147,6 +3470,7 @@ function createRendererStore(initialBlocks = []) {
3147
3470
  break;
3148
3471
  }
3149
3472
  case "setHTML": {
3473
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
3150
3474
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
3151
3475
  if (!target) break;
3152
3476
  const sanitized = patch.sanitized ? patch.html : sanitizeHTML2(patch.html);
@@ -3189,6 +3513,30 @@ function createRendererStore(initialBlocks = []) {
3189
3513
  applyPatch(patch);
3190
3514
  }
3191
3515
  }
3516
+ const finalizedTableRoots = /* @__PURE__ */ new Set();
3517
+ for (const touchedId of touched) {
3518
+ let cursor = nodes.get(touchedId);
3519
+ while (cursor && cursor.id !== PATCH_ROOT_ID) {
3520
+ if (cursor.type === "table" && cursor.block?.isFinalized && cursor.parentId) {
3521
+ finalizedTableRoots.add(cursor.id);
3522
+ break;
3523
+ }
3524
+ cursor = cursor.parentId ? nodes.get(cursor.parentId) : void 0;
3525
+ }
3526
+ }
3527
+ if (finalizedTableRoots.size > 0) {
3528
+ for (const tableId of finalizedTableRoots) {
3529
+ const tableNode = nodes.get(tableId);
3530
+ if (!tableNode || !tableNode.block || !tableNode.parentId) continue;
3531
+ const parent = nodes.get(tableNode.parentId);
3532
+ if (!parent) continue;
3533
+ const index = parent.children.indexOf(tableNode.id);
3534
+ if (index === -1) continue;
3535
+ const snapshot = createBlockSnapshot(cloneBlock(tableNode.block));
3536
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, tableNode.blockEpoch);
3537
+ mutatedBlocks = true;
3538
+ }
3539
+ }
3192
3540
  if (mutatedBlocks) {
3193
3541
  blocksDirty = true;
3194
3542
  }
@@ -3209,6 +3557,8 @@ function createRendererStore(initialBlocks = []) {
3209
3557
  normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, PATCH_ROOT_ID);
3210
3558
  }
3211
3559
  }
3560
+ invariantViolations = [...collectStoreInvariantViolations(nodes), ...transientDiagnostics];
3561
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
3212
3562
  if (touched.size > 0 || mutatedBlocks) {
3213
3563
  version++;
3214
3564
  scheduleNotify();
@@ -3257,6 +3607,7 @@ function createRendererStore(initialBlocks = []) {
3257
3607
  if (!record) {
3258
3608
  return EMPTY_NODE_SNAPSHOT;
3259
3609
  }
3610
+ const nodeSnapshotCache = getNodeSnapshotCacheForMap(nodes);
3260
3611
  const cached = nodeSnapshotCache.get(id);
3261
3612
  if (cached && cached.version === record.version && cached.node === record) {
3262
3613
  return cached;
@@ -3276,6 +3627,7 @@ function createRendererStore(initialBlocks = []) {
3276
3627
  if (!record) {
3277
3628
  return EMPTY_CHILDREN_SNAPSHOT;
3278
3629
  }
3630
+ const childrenSnapshotCache = getChildrenSnapshotCacheForMap(nodes);
3279
3631
  const cached = childrenSnapshotCache.get(id);
3280
3632
  if (cached && cached.version === record.version) {
3281
3633
  return cached;
@@ -3285,6 +3637,12 @@ function createRendererStore(initialBlocks = []) {
3285
3637
  pruneCache(childrenSnapshotCache, CHILDREN_SNAPSHOT_CACHE_LIMIT, CHILDREN_SNAPSHOT_CACHE_BUFFER);
3286
3638
  return snapshot;
3287
3639
  },
3640
+ getInvariantViolations() {
3641
+ return invariantViolations.slice();
3642
+ },
3643
+ getDebugCounters() {
3644
+ return { ...debugCounters };
3645
+ },
3288
3646
  getLastCoalescingMetrics() {
3289
3647
  return lastCoalescingMetrics;
3290
3648
  },
@@ -3733,8 +4091,10 @@ var ListBlockView = React7.memo(
3733
4091
  isFinalized: block?.isFinalized ?? false
3734
4092
  });
3735
4093
  }, [listDebugEnabled, blockId, depth, ordered, childIds, listItemIds, store, block?.isFinalized]);
3736
- const listStyle = React7.useMemo(() => {
3737
- if (!ordered || listItemIds.length === 0) return void 0;
4094
+ const { listStyle, markerDigits } = React7.useMemo(() => {
4095
+ if (!ordered || listItemIds.length === 0) {
4096
+ return { listStyle: void 0, markerDigits: 1 };
4097
+ }
3738
4098
  let maxDigits = 1;
3739
4099
  listItemIds.forEach((childId, index) => {
3740
4100
  const childNode = store.getNode(childId);
@@ -3746,9 +4106,14 @@ var ListBlockView = React7.memo(
3746
4106
  const digits = digitMatch ? digitMatch.length : text.length;
3747
4107
  if (digits > maxDigits) maxDigits = digits;
3748
4108
  });
3749
- const baseIndent = depth === 0 ? "2.5rem" : depth === 1 ? "2rem" : "1.5rem";
4109
+ const baseIndent = depth === 0 ? "2rem" : depth === 1 ? "1.75rem" : "1.5rem";
4110
+ const extraDigits = Math.max(0, maxDigits - 1);
3750
4111
  return {
3751
- ["--list-indent"]: `calc(${baseIndent} + ${maxDigits}ch)`
4112
+ markerDigits: maxDigits,
4113
+ listStyle: {
4114
+ ["--list-indent"]: extraDigits > 0 ? `calc(${baseIndent} + ${extraDigits}ch)` : baseIndent,
4115
+ ["--list-marker-digits"]: String(maxDigits)
4116
+ }
3752
4117
  };
3753
4118
  }, [ordered, listItemIds, store, depth]);
3754
4119
  if (listItemIds.length === 0) return null;
@@ -3757,6 +4122,7 @@ var ListBlockView = React7.memo(
3757
4122
  {
3758
4123
  className: `markdown-list ${ordered ? "ordered" : "unordered"}`,
3759
4124
  "data-list-depth": depth,
4125
+ "data-marker-digits": ordered ? markerDigits : void 0,
3760
4126
  "data-list-id": listDebugEnabled ? blockId : void 0,
3761
4127
  style: listStyle,
3762
4128
  children: listItemIds.map((childId, index) => /* @__PURE__ */ jsx(
@@ -3938,6 +4304,7 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
3938
4304
  deferredConfig ? { ...deferredConfig, enabled: true } : { enabled: false }
3939
4305
  );
3940
4306
  if (!node || !node.block) return null;
4307
+ const blockIsFinalized = node.block.isFinalized;
3941
4308
  const nodeVersion = node.version;
3942
4309
  const lines = React7.useMemo(() => {
3943
4310
  const seenIds = /* @__PURE__ */ new Set();
@@ -3968,13 +4335,25 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
3968
4335
  const lang = typeof node.props?.lang === "string" ? node.props?.lang : node.block.payload.meta?.lang;
3969
4336
  const preAttrs = node.props?.preAttrs ?? void 0;
3970
4337
  const codeAttrs = node.props?.codeAttrs ?? void 0;
4338
+ const blockMeta = node.block.payload.meta ?? void 0;
4339
+ const lazyEnabled = Boolean(blockMeta?.lazyTokenization);
4340
+ const lazyTokenizedUntil = typeof blockMeta?.lazyTokenizedUntil === "number" ? blockMeta.lazyTokenizedUntil : 0;
4341
+ const lazyHighlightedLines = Array.isArray(blockMeta?.highlightedLines) ? blockMeta.highlightedLines.length : 0;
4342
+ const lazyTokenLines = Array.isArray(blockMeta?.tokenLines) ? blockMeta.tokenLines.length : 0;
4343
+ const lazySourceTextLineCount = React7.useMemo(() => {
4344
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
4345
+ const metaCode = typeof blockMeta?.code === "string" ? blockMeta.code : null;
4346
+ const sourceText = resolveCodeSourceText(raw, metaCode);
4347
+ if (sourceText.length === 0) return 0;
4348
+ return sourceText.split("\n").length;
4349
+ }, [blockMeta, node.block?.payload.raw]);
4350
+ const totalLazyLineCount = Math.max(lines.length, lazyHighlightedLines, lazyTokenLines, lazySourceTextLineCount);
3971
4351
  const virtualizationConfig = DEFAULT_VIRTUALIZED_CODE_CONFIG;
3972
4352
  const virtualizationDisabled = typeof process !== "undefined" && typeof process.env === "object" && process.env.STREAM_MDX_DISABLE_VIRTUALIZED_CODE === "true";
3973
- const shouldVirtualize = !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
4353
+ const canVirtualize = node.block.isFinalized && !lazyEnabled;
4354
+ const shouldVirtualize = canVirtualize && !virtualizationDisabled && virtualizationConfig.enabled && lines.length >= virtualizationConfig.virtualizeThreshold;
3974
4355
  const virtualization = useVirtualizedCode(lines, shouldVirtualize ? virtualizationConfig : { ...virtualizationConfig, enabled: false });
3975
4356
  const highlightRequester = useCodeHighlightRequester();
3976
- const lazyEnabled = Boolean(node.block.payload.meta?.lazyTokenization);
3977
- const lazyTokenizedUntil = typeof node.block.payload.meta?.lazyTokenizedUntil === "number" ? node.block.payload.meta.lazyTokenizedUntil : 0;
3978
4357
  const lastRangeRef = React7.useRef(null);
3979
4358
  const rafRef = React7.useRef(null);
3980
4359
  const idleRef = React7.useRef(null);
@@ -4055,29 +4434,111 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4055
4434
  virtualization.window.startIndex,
4056
4435
  virtualization.window.endIndex
4057
4436
  ]);
4058
- const highlightedHtml = React7.useMemo(() => {
4059
- if (!shouldVirtualize && node.block?.payload.highlightedHtml) {
4060
- return node.block.payload.highlightedHtml;
4437
+ React7.useEffect(() => {
4438
+ if (shouldVirtualize || !blockIsFinalized || !lazyEnabled || !highlightRequester) return;
4439
+ if (totalLazyLineCount <= 0 || lazyTokenizedUntil >= totalLazyLineCount) return;
4440
+ const nextRange = {
4441
+ visibleStart: 0,
4442
+ visibleEnd: totalLazyLineCount,
4443
+ prefetchStart: 0,
4444
+ prefetchEnd: totalLazyLineCount,
4445
+ tokenizedUntil: lazyTokenizedUntil
4446
+ };
4447
+ const last = lastRangeRef.current;
4448
+ if (last && last.visibleStart === nextRange.visibleStart && last.visibleEnd === nextRange.visibleEnd && last.prefetchStart === nextRange.prefetchStart && last.prefetchEnd === nextRange.prefetchEnd && last.tokenizedUntil === nextRange.tokenizedUntil) {
4449
+ return;
4061
4450
  }
4451
+ lastRangeRef.current = nextRange;
4452
+ if (rafRef.current !== null && typeof cancelAnimationFrame === "function") {
4453
+ cancelAnimationFrame(rafRef.current);
4454
+ }
4455
+ const requestFullHighlight = () => {
4456
+ highlightRequester({
4457
+ blockId,
4458
+ startLine: 0,
4459
+ endLine: totalLazyLineCount,
4460
+ priority: "visible",
4461
+ reason: "finalize-full"
4462
+ });
4463
+ rafRef.current = null;
4464
+ };
4465
+ if (typeof requestAnimationFrame === "function") {
4466
+ rafRef.current = requestAnimationFrame(requestFullHighlight);
4467
+ } else {
4468
+ requestFullHighlight();
4469
+ }
4470
+ }, [blockId, blockIsFinalized, highlightRequester, lazyEnabled, lazyTokenizedUntil, shouldVirtualize, totalLazyLineCount]);
4471
+ const highlightedHtml = React7.useMemo(() => {
4472
+ const blockHtml = node.block?.payload.highlightedHtml ?? "";
4473
+ const raw = typeof node.block?.payload.raw === "string" ? node.block.payload.raw : "";
4474
+ const metaCode = typeof node.block?.payload.meta?.code === "string" ? (node.block?.payload.meta).code : null;
4475
+ const sourceText = resolveCodeSourceText(raw, metaCode);
4476
+ const sourceEndsWithNewline = sourceText.endsWith("\n");
4477
+ const sourceLines = sourceText.length > 0 ? sourceText.split("\n") : [];
4478
+ const rawFallbackLines = buildCodeFallbackLines(raw);
4479
+ const deterministicWrapperAttrs = getDefaultCodeWrapperAttributes2(lang);
4480
+ const composedPreAttrs = deterministicWrapperAttrs.preAttrs;
4481
+ const composedCodeAttrs = deterministicWrapperAttrs.codeAttrs;
4482
+ const shouldAppendTerminalNewline = (renderedLines) => {
4483
+ if (!sourceEndsWithNewline || renderedLines.length === 0 || lines.length === 0) {
4484
+ return false;
4485
+ }
4486
+ const lastRendered = renderedLines[renderedLines.length - 1];
4487
+ const lastKnown = lines[lines.length - 1];
4488
+ if (!lastRendered || !lastKnown || lastRendered.index !== lastKnown.index || lastRendered.text === "") {
4489
+ return false;
4490
+ }
4491
+ const expectedRenderedLineCount = Math.max(0, sourceLines.length - 1);
4492
+ const expectedLastRenderedLine = sourceLines.length >= 2 ? sourceLines[sourceLines.length - 2] ?? "" : "";
4493
+ return renderedLines.length === expectedRenderedLineCount && lastRendered.text === expectedLastRenderedLine;
4494
+ };
4062
4495
  if (lines.length > 0) {
4063
4496
  const hasLineHtml = lines.some((line) => typeof line.html === "string" && line.html.length > 0);
4064
- if (!hasLineHtml && node.block?.payload.highlightedHtml) {
4065
- return node.block.payload.highlightedHtml;
4497
+ if (!hasLineHtml && blockHtml) {
4498
+ return blockHtml;
4066
4499
  }
4067
4500
  if (shouldVirtualize) {
4068
- return composeHighlightedHtml(virtualization.window.visibleLines, preAttrs, codeAttrs);
4501
+ return composeHighlightedHtml(
4502
+ virtualization.window.visibleLines,
4503
+ composedPreAttrs,
4504
+ composedCodeAttrs,
4505
+ shouldAppendTerminalNewline(virtualization.window.visibleLines)
4506
+ );
4069
4507
  }
4070
- return composeHighlightedHtml(lines, preAttrs, codeAttrs);
4508
+ return composeHighlightedHtml(lines, composedPreAttrs, composedCodeAttrs, shouldAppendTerminalNewline(lines));
4509
+ }
4510
+ if (!shouldVirtualize && blockHtml) {
4511
+ const hasHighlightedLines = blockHtml.includes('class="line"');
4512
+ if (!hasHighlightedLines && rawFallbackLines.length > 0) {
4513
+ return composeHighlightedHtml(
4514
+ rawFallbackLines,
4515
+ composedPreAttrs,
4516
+ composedCodeAttrs,
4517
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
4518
+ );
4519
+ }
4520
+ return blockHtml;
4071
4521
  }
4072
- const blockHtml = node.block?.payload.highlightedHtml ?? "";
4073
4522
  if (blockHtml?.trim().match(/^```[\w-]*\s*$/)) {
4074
- const raw = node.block?.payload.raw ?? "";
4075
- if (raw.trim().length > 0) {
4076
- return composeHighlightedHtml([{ index: 0, text: raw, html: null, id: "temp" }], preAttrs, codeAttrs);
4523
+ if (rawFallbackLines.length > 0) {
4524
+ return composeHighlightedHtml(
4525
+ rawFallbackLines,
4526
+ composedPreAttrs,
4527
+ composedCodeAttrs,
4528
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
4529
+ );
4077
4530
  }
4078
4531
  }
4532
+ if (!blockHtml && rawFallbackLines.length > 0) {
4533
+ return composeHighlightedHtml(
4534
+ rawFallbackLines,
4535
+ composedPreAttrs,
4536
+ composedCodeAttrs,
4537
+ sourceEndsWithNewline && rawFallbackLines[rawFallbackLines.length - 1]?.text !== ""
4538
+ );
4539
+ }
4079
4540
  return blockHtml;
4080
- }, [shouldVirtualize, node.block?.payload.highlightedHtml, lines, preAttrs, codeAttrs, virtualization.window.visibleLines, node.block?.payload.raw]);
4541
+ }, [blockIsFinalized, shouldVirtualize, node.block?.payload.highlightedHtml, node.block?.payload.meta, lines, lang, virtualization.window.visibleLines, node.block?.payload.raw]);
4081
4542
  const rendered = /* @__PURE__ */ jsx(
4082
4543
  CodeComponent,
4083
4544
  {
@@ -4089,13 +4550,19 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4089
4550
  codeAttrs
4090
4551
  }
4091
4552
  );
4553
+ const effectiveLineCount = React7.useMemo(() => {
4554
+ if (lines.length > 0) {
4555
+ return lines.length;
4556
+ }
4557
+ return 1;
4558
+ }, [lines.length]);
4092
4559
  const codeFrameClass = "not-prose flex flex-col rounded-lg border border-input pt-1 font-mono text-sm";
4093
4560
  const codeMetricsAttrs = {
4094
4561
  "data-code-block": "true",
4095
4562
  "data-block-id": blockId,
4096
4563
  "data-code-virtualized": shouldVirtualize ? "true" : "false",
4097
- "data-code-total-lines": String(lines.length),
4098
- "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : lines.length),
4564
+ "data-code-total-lines": String(effectiveLineCount),
4565
+ "data-code-mounted-lines": String(shouldVirtualize ? virtualization.window.mountedLines : effectiveLineCount),
4099
4566
  "data-code-window-size": String(virtualization.config.windowSize)
4100
4567
  };
4101
4568
  const codeView = shouldVirtualize ? (() => {
@@ -4141,21 +4608,152 @@ var CodeBlockView = React7.memo(({ store, blockId, registry }) => {
4141
4608
  return codeView;
4142
4609
  });
4143
4610
  CodeBlockView.displayName = "CodeBlockView";
4144
- function composeHighlightedHtml(lines, preAttrs, codeAttrs) {
4611
+ function composeHighlightedHtml(lines, preAttrs, codeAttrs, appendTerminalNewline = false) {
4145
4612
  const lineMarkup = lines.map((line) => {
4146
- const content = line.html ?? escapeHtml3(line.text);
4613
+ const content = line.html ?? renderTokenLineHtml(line.tokens) ?? escapeHtml3(line.text);
4147
4614
  const dataLine = Number.isFinite(line.index) ? ` data-line="${line.index + 1}"` : "";
4148
4615
  return `<span class="line"${dataLine}>${content}</span>`;
4149
4616
  }).join("\n");
4150
4617
  const preAttr = attrsToString(preAttrs);
4151
4618
  const codeAttr = attrsToString(codeAttrs);
4152
- return `<pre${preAttr}><code${codeAttr}>${lineMarkup}
4153
- </code></pre>`;
4619
+ return `<pre${preAttr}><code${codeAttr}>${lineMarkup}${appendTerminalNewline ? "\n" : ""}</code></pre>`;
4620
+ }
4621
+ var TOKEN_FONT_STYLE_ITALIC = 1;
4622
+ var TOKEN_FONT_STYLE_BOLD = 2;
4623
+ var TOKEN_FONT_STYLE_UNDERLINE = 4;
4624
+ var TOKEN_FONT_STYLE_STRIKETHROUGH = 8;
4625
+ function renderTokenLineHtml(tokens) {
4626
+ if (!tokens || !Array.isArray(tokens.spans)) {
4627
+ return null;
4628
+ }
4629
+ return mergeRenderableTokenSpans(tokens.spans).map((span) => renderTokenSpanHtml(span)).join("");
4630
+ }
4631
+ function renderTokenSpanHtml(span) {
4632
+ const dark = span.v?.dark;
4633
+ const light = span.v?.light;
4634
+ const single = span.s;
4635
+ const styles = [];
4636
+ if (dark?.fg) {
4637
+ styles.push(`--shiki-dark:${dark.fg}`);
4638
+ }
4639
+ if (light?.fg) {
4640
+ styles.push(`--shiki-light:${light.fg}`);
4641
+ }
4642
+ if (!light?.fg && !dark?.fg && single?.fg) {
4643
+ styles.push(`color:${single.fg}`);
4644
+ }
4645
+ const fontStyle = light?.fs ?? dark?.fs ?? single?.fs;
4646
+ if (typeof fontStyle === "number" && fontStyle > 0) {
4647
+ if (fontStyle & TOKEN_FONT_STYLE_ITALIC) {
4648
+ styles.push("font-style: italic");
4649
+ }
4650
+ if (fontStyle & TOKEN_FONT_STYLE_BOLD) {
4651
+ styles.push("font-weight: bold");
4652
+ }
4653
+ const decorations = [];
4654
+ if (fontStyle & TOKEN_FONT_STYLE_UNDERLINE) {
4655
+ decorations.push("underline");
4656
+ }
4657
+ if (fontStyle & TOKEN_FONT_STYLE_STRIKETHROUGH) {
4658
+ decorations.push("line-through");
4659
+ }
4660
+ if (decorations.length > 0) {
4661
+ styles.push(`text-decoration: ${decorations.join(" ")}`);
4662
+ }
4663
+ }
4664
+ const content = escapeHtml3(span.t ?? "");
4665
+ if (styles.length === 0) {
4666
+ return content;
4667
+ }
4668
+ return `<span style="${styles.join(";")}">${content}</span>`;
4669
+ }
4670
+ function mergeRenderableTokenSpans(spans) {
4671
+ if (spans.length <= 1) {
4672
+ return spans.slice();
4673
+ }
4674
+ const merged = [];
4675
+ let pendingWhitespace = "";
4676
+ for (const span of spans) {
4677
+ const text = typeof span.t === "string" ? span.t : "";
4678
+ if (/^[\t ]+$/.test(text)) {
4679
+ pendingWhitespace += text;
4680
+ continue;
4681
+ }
4682
+ const nextSpan = pendingWhitespace ? { ...span, t: `${pendingWhitespace}${text}` } : span;
4683
+ pendingWhitespace = "";
4684
+ const previous = merged[merged.length - 1];
4685
+ if (previous && sameTokenSpanStyle(previous, nextSpan)) {
4686
+ previous.t += nextSpan.t;
4687
+ } else {
4688
+ merged.push({ ...nextSpan });
4689
+ }
4690
+ }
4691
+ if (pendingWhitespace) {
4692
+ const previous = merged[merged.length - 1];
4693
+ if (previous) {
4694
+ previous.t += pendingWhitespace;
4695
+ } else {
4696
+ merged.push({ t: pendingWhitespace });
4697
+ }
4698
+ }
4699
+ return merged;
4700
+ }
4701
+ function sameTokenSpanStyle(a, b) {
4702
+ const variantKeys = /* @__PURE__ */ new Set([
4703
+ ...Object.keys(a.v ?? {}),
4704
+ ...Object.keys(b.v ?? {})
4705
+ ]);
4706
+ for (const key of variantKeys) {
4707
+ const aStyle = a.v?.[key];
4708
+ const bStyle = b.v?.[key];
4709
+ if ((aStyle?.fg ?? null) !== (bStyle?.fg ?? null)) return false;
4710
+ if ((aStyle?.bg ?? null) !== (bStyle?.bg ?? null)) return false;
4711
+ if ((aStyle?.fs ?? null) !== (bStyle?.fs ?? null)) return false;
4712
+ }
4713
+ if ((a.s?.fg ?? null) !== (b.s?.fg ?? null)) return false;
4714
+ if ((a.s?.bg ?? null) !== (b.s?.bg ?? null)) return false;
4715
+ if ((a.s?.fs ?? null) !== (b.s?.fs ?? null)) return false;
4716
+ return true;
4717
+ }
4718
+ function resolveCodeSourceText(raw, metaCode) {
4719
+ if (metaCode !== null) {
4720
+ return metaCode.replace(/\r\n?/g, "\n");
4721
+ }
4722
+ if (!raw) return "";
4723
+ const normalized = raw.replace(/\r\n?/g, "\n");
4724
+ const fenced = stripCodeFence(normalized);
4725
+ return fenced.hadFence ? fenced.code : normalized;
4154
4726
  }
4155
4727
  function attrsToString(attrs) {
4156
4728
  if (!attrs) return "";
4157
4729
  return Object.entries(attrs).map(([key, value]) => ` ${key}="${escapeAttribute(value)}"`).join("");
4158
4730
  }
4731
+ function buildCodeFallbackLines(raw) {
4732
+ const normalized = normalizeCodeFallbackRaw(raw);
4733
+ if (normalized.length === 0) {
4734
+ if (/^\s*```[^\n]*\n?\s*$/.test(raw)) {
4735
+ return [{ id: "temp-0", index: 0, text: "", html: null }];
4736
+ }
4737
+ return [];
4738
+ }
4739
+ return normalized.split("\n").map((line, index) => ({
4740
+ id: `temp-${index}`,
4741
+ index,
4742
+ text: line,
4743
+ html: null
4744
+ }));
4745
+ }
4746
+ function normalizeCodeFallbackRaw(raw) {
4747
+ if (!raw) return "";
4748
+ const normalized = raw.replace(/\r\n?/g, "\n");
4749
+ const fenced = stripCodeFence(normalized);
4750
+ if (!fenced.hadFence) {
4751
+ return normalized.replace(/\n+$/, "");
4752
+ }
4753
+ let code = fenced.code;
4754
+ code = code.replace(/\n+$/, "");
4755
+ return code;
4756
+ }
4159
4757
  function escapeHtml3(value) {
4160
4758
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4161
4759
  }