@stream-mdx/react 0.3.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 -14
  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 +511 -176
  8. package/dist/components/index.d.cts +3 -1
  9. package/dist/components/index.d.ts +3 -1
  10. package/dist/components/index.mjs +358 -26
  11. package/dist/{index-D7px9jug.d.cts → index-BVhp_pcF.d.ts} +4 -1
  12. package/dist/{index-D7px9jug.d.ts → index-EaeouYp2.d.cts} +4 -1
  13. package/dist/index.cjs +2146 -1145
  14. package/dist/index.d.cts +6 -4
  15. package/dist/index.d.ts +6 -4
  16. package/dist/index.mjs +3835 -2836
  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 +273 -24
  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 +274 -25
  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 +3386 -2727
  40. package/dist/renderer.d.cts +15 -12
  41. package/dist/renderer.d.ts +15 -12
  42. package/dist/renderer.mjs +3410 -2751
  43. package/dist/server.cjs +4842 -0
  44. package/dist/server.d.cts +254 -0
  45. package/dist/server.d.ts +254 -0
  46. package/dist/server.mjs +4824 -0
  47. package/dist/{streaming-markdown-Dp1IDgMT.d.ts → streaming-markdown-CEI9eDZm.d.ts} +3 -1
  48. package/dist/{streaming-markdown-DSC4L0xR.d.cts → streaming-markdown-DRisClXb.d.cts} +3 -1
  49. package/dist/streaming-markdown.cjs +3476 -2794
  50. package/dist/streaming-markdown.d.cts +4 -2
  51. package/dist/streaming-markdown.d.ts +4 -2
  52. package/dist/streaming-markdown.mjs +3436 -2754
  53. package/dist/utils/inline-html.d.cts +3 -1
  54. package/dist/utils/inline-html.d.ts +3 -1
  55. package/package.json +19 -3
  56. package/shadcn/README.md +28 -0
  57. package/shadcn/bottom-stick-scroll-area.tsx +358 -0
@@ -26,6 +26,7 @@ __export(store_exports, {
26
26
  });
27
27
  module.exports = __toCommonJS(store_exports);
28
28
  var import_core2 = require("@stream-mdx/core");
29
+ var import_patch_batching = require("@stream-mdx/core/perf/patch-batching");
29
30
 
30
31
  // src/renderer/list-utils.ts
31
32
  function updateNodeDepth(node, depth) {
@@ -124,12 +125,28 @@ function snapshotToBlock(node) {
124
125
  }
125
126
  return null;
126
127
  }
127
- var nodeSnapshotCache = /* @__PURE__ */ new Map();
128
- var childrenSnapshotCache = /* @__PURE__ */ new Map();
128
+ var nodeSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
129
+ var childrenSnapshotCacheByMap = /* @__PURE__ */ new WeakMap();
129
130
  var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
130
131
  var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
131
132
  var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
132
133
  var CHILDREN_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(CHILDREN_SNAPSHOT_CACHE_LIMIT * 0.1));
134
+ function getNodeSnapshotCacheForMap(map) {
135
+ let cache = nodeSnapshotCacheByMap.get(map);
136
+ if (!cache) {
137
+ cache = /* @__PURE__ */ new Map();
138
+ nodeSnapshotCacheByMap.set(map, cache);
139
+ }
140
+ return cache;
141
+ }
142
+ function getChildrenSnapshotCacheForMap(map) {
143
+ let cache = childrenSnapshotCacheByMap.get(map);
144
+ if (!cache) {
145
+ cache = /* @__PURE__ */ new Map();
146
+ childrenSnapshotCacheByMap.set(map, cache);
147
+ }
148
+ return cache;
149
+ }
133
150
  function pruneCache(cache, max, buffer) {
134
151
  if (cache.size <= max + buffer) return;
135
152
  let remaining = cache.size - max;
@@ -139,6 +156,14 @@ function pruneCache(cache, max, buffer) {
139
156
  if (remaining <= 0) break;
140
157
  }
141
158
  }
159
+ function deleteSnapshotCacheEntry(map, id) {
160
+ getNodeSnapshotCacheForMap(map).delete(id);
161
+ getChildrenSnapshotCacheForMap(map).delete(id);
162
+ }
163
+ function clearSnapshotCaches(map) {
164
+ getNodeSnapshotCacheForMap(map).clear();
165
+ getChildrenSnapshotCacheForMap(map).clear();
166
+ }
142
167
  var EMPTY_CHILDREN = Object.freeze([]);
143
168
  var EMPTY_NODE_SNAPSHOT = Object.freeze({ version: -1, node: void 0 });
144
169
  var EMPTY_CHILDREN_SNAPSHOT = Object.freeze({
@@ -306,9 +331,57 @@ function createRootRecord() {
306
331
  parentId: null,
307
332
  children: [],
308
333
  props: {},
309
- version: 0
334
+ version: 0,
335
+ blockEpoch: 0
310
336
  };
311
337
  }
338
+ function readNumericEpoch(value) {
339
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
340
+ return void 0;
341
+ }
342
+ return value;
343
+ }
344
+ function readSnapshotBlockEpoch(snapshot) {
345
+ return readNumericEpoch(snapshot.meta?.blockEpoch);
346
+ }
347
+ function readPatchBlockEpoch(patch) {
348
+ if (patch.op === "setHTML") {
349
+ return readNumericEpoch(patch.patchMeta?.blockEpoch);
350
+ }
351
+ return readNumericEpoch(patch.meta?.blockEpoch);
352
+ }
353
+ function readPatchParseEpoch(patch) {
354
+ if (patch.op === "setHTML") {
355
+ return readNumericEpoch(patch.patchMeta?.parseEpoch);
356
+ }
357
+ return readNumericEpoch(patch.meta?.parseEpoch);
358
+ }
359
+ function readPatchTx(patch) {
360
+ if (patch.op === "setHTML") {
361
+ return readNumericEpoch(patch.patchMeta?.tx);
362
+ }
363
+ return readNumericEpoch(patch.meta?.tx);
364
+ }
365
+ function assignSubtreeBlockEpoch(map, rootId, blockEpoch, touched) {
366
+ const root = map.get(rootId);
367
+ if (!root) return;
368
+ const stack = [root];
369
+ while (stack.length > 0) {
370
+ const node = stack.pop();
371
+ if (!node) continue;
372
+ if (node.blockEpoch !== blockEpoch) {
373
+ node.blockEpoch = blockEpoch;
374
+ node.version += 1;
375
+ touched?.add(node.id);
376
+ }
377
+ for (const childId of node.children) {
378
+ const child = map.get(childId);
379
+ if (child) {
380
+ stack.push(child);
381
+ }
382
+ }
383
+ }
384
+ }
312
385
  function ensureArray(value) {
313
386
  return Array.isArray(value) ? value : [];
314
387
  }
@@ -368,7 +441,7 @@ function escapeHtml(value) {
368
441
  }
369
442
  function stripOuterLineSpan(html) {
370
443
  if (!html) return null;
371
- const openTagMatch = html.match(/<span[^>]*>/i);
444
+ const openTagMatch = html.match(/<span[^>]*class="[^"]*\bline\b[^"]*"[^>]*>/i);
372
445
  if (!openTagMatch) {
373
446
  return null;
374
447
  }
@@ -417,8 +490,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
417
490
  if (node.type !== "code-line") {
418
491
  anomalies.push(`type-mismatch:${node.id}:${node.type}`);
419
492
  nodes.delete(node.id);
420
- nodeSnapshotCache.delete(node.id);
421
- childrenSnapshotCache.delete(node.id);
493
+ deleteSnapshotCacheEntry(nodes, node.id);
422
494
  touched.add(node.id);
423
495
  continue;
424
496
  }
@@ -462,16 +534,14 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
462
534
  const existing = nodes.get(desiredId);
463
535
  if (existing && existing !== node) {
464
536
  nodes.delete(desiredId);
465
- nodeSnapshotCache.delete(desiredId);
466
- childrenSnapshotCache.delete(desiredId);
537
+ deleteSnapshotCacheEntry(nodes, desiredId);
467
538
  touched.add(desiredId);
468
539
  mutated = true;
469
540
  }
470
541
  if (node.id !== desiredId) {
471
542
  const previousId = node.id;
472
543
  nodes.delete(node.id);
473
- nodeSnapshotCache.delete(previousId);
474
- childrenSnapshotCache.delete(previousId);
544
+ deleteSnapshotCacheEntry(nodes, previousId);
475
545
  node.id = desiredId;
476
546
  nodes.set(desiredId, node);
477
547
  touched.add(desiredId);
@@ -502,8 +572,7 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
502
572
  for (const [id, record] of nodes) {
503
573
  if (record.parentId === parent.id && record.type === "code-line" && !allowed.has(id)) {
504
574
  nodes.delete(id);
505
- nodeSnapshotCache.delete(id);
506
- childrenSnapshotCache.delete(id);
575
+ deleteSnapshotCacheEntry(nodes, id);
507
576
  touched.add(id);
508
577
  mutated = true;
509
578
  }
@@ -587,8 +656,10 @@ function normalizeCodeLineProps(incoming, previous) {
587
656
  }
588
657
  function createCodeLineProps(index, text, highlight, tokens) {
589
658
  const safeText = typeof text === "string" ? text : "";
590
- const sanitized = sanitizeLineInnerHtml(highlight, safeText);
591
- const html = sanitized && sanitized.length >= safeText.length ? sanitized : escapeHtml(safeText);
659
+ const hasIncomingHighlight = typeof highlight === "string" && highlight.trim().length > 0;
660
+ const sanitized = hasIncomingHighlight ? sanitizeLineInnerHtml(highlight, safeText) : null;
661
+ const hasRenderableTokens = tokens !== void 0 && tokens !== null;
662
+ const html = sanitized && sanitized.length >= safeText.length ? sanitized : hasRenderableTokens ? null : escapeHtml(safeText);
592
663
  const props = {
593
664
  index,
594
665
  text: safeText,
@@ -699,6 +770,18 @@ function validateCodeBlockChildren(nodes, parent) {
699
770
  issues.push({ kind: "dangling-node", id });
700
771
  }
701
772
  }
773
+ if (parent.block?.isFinalized) {
774
+ const snapshot = (0, import_core2.createBlockSnapshot)((0, import_core2.cloneBlock)(parent.block));
775
+ const expectedChildren = ensureArray(snapshot.children).filter((child) => child.type === "code-line");
776
+ if (expectedChildren.length !== parent.children.length) {
777
+ issues.push({
778
+ kind: "line-count-mismatch",
779
+ id: parent.id,
780
+ expected: expectedChildren.length,
781
+ actual: parent.children.length
782
+ });
783
+ }
784
+ }
702
785
  return { ok: issues.length === 0, issues };
703
786
  }
704
787
  function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
@@ -715,7 +798,7 @@ function rebuildCodeBlockFromSnapshot(nodes, parent, touched, issues) {
715
798
  }
716
799
  const snapshot = (0, import_core2.createBlockSnapshot)((0, import_core2.cloneBlock)(parent.block));
717
800
  removeSubtree(nodes, parent.id, touched);
718
- const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot);
801
+ const inserted = insertSnapshotAt(nodes, ancestor, index, snapshot, touched, parent.blockEpoch);
719
802
  if (!inserted) {
720
803
  return false;
721
804
  }
@@ -738,7 +821,55 @@ function arraysEqual(a, b) {
738
821
  }
739
822
  return true;
740
823
  }
741
- function buildNodeFromSnapshot(map, snapshot, parentId) {
824
+ function expectedCodeLineId(parentId, index) {
825
+ return `${parentId}::line:${index}`;
826
+ }
827
+ function collectStoreInvariantViolations(nodes) {
828
+ const violations = [];
829
+ for (const [id, node] of nodes) {
830
+ if (node.type === "__root__") continue;
831
+ if (node.children.length > 1) {
832
+ const seen = /* @__PURE__ */ new Set();
833
+ for (const childId of node.children) {
834
+ if (seen.has(childId)) {
835
+ violations.push(`duplicate child id:${id}:${childId}`);
836
+ } else {
837
+ seen.add(childId);
838
+ }
839
+ }
840
+ }
841
+ if (node.type === "list") {
842
+ const parent = node.parentId ? nodes.get(node.parentId) : void 0;
843
+ if (parent?.type === "list-item" && node.children.length === 0) {
844
+ violations.push(`empty nested list:${id}`);
845
+ }
846
+ }
847
+ if (node.type === "code") {
848
+ for (let index = 0; index < node.children.length; index += 1) {
849
+ const childId = node.children[index];
850
+ const child = nodes.get(childId);
851
+ if (!child) {
852
+ violations.push(`missing code-line node:${node.id}:${childId}`);
853
+ continue;
854
+ }
855
+ if (child.type !== "code-line") {
856
+ violations.push(`non-code child in code block:${node.id}:${child.id}:${child.type}`);
857
+ continue;
858
+ }
859
+ const expectedId = expectedCodeLineId(node.id, index);
860
+ if (child.id !== expectedId) {
861
+ violations.push(`code-line id mismatch:${node.id}:${child.id}:${expectedId}`);
862
+ }
863
+ const indexProp = typeof child.props?.index === "number" ? child.props.index : null;
864
+ if (indexProp !== index) {
865
+ violations.push(`code-line index mismatch:${node.id}:${child.id}:${indexProp}:${index}`);
866
+ }
867
+ }
868
+ }
869
+ }
870
+ return violations;
871
+ }
872
+ function buildNodeFromSnapshot(map, snapshot, parentId, blockEpoch) {
742
873
  const record = {
743
874
  id: snapshot.id,
744
875
  type: snapshot.type,
@@ -748,6 +879,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
748
879
  meta: snapshot.meta ? { ...snapshot.meta } : void 0,
749
880
  range: snapshot.range ? { ...snapshot.range } : void 0,
750
881
  version: 0,
882
+ blockEpoch,
751
883
  block: snapshotToBlock(snapshot) ?? void 0
752
884
  };
753
885
  if (record.type === "code") {
@@ -756,7 +888,7 @@ function buildNodeFromSnapshot(map, snapshot, parentId) {
756
888
  map.set(record.id, record);
757
889
  const childSnapshots = ensureArray(snapshot.children);
758
890
  for (const child of childSnapshots) {
759
- const childRecord = buildNodeFromSnapshot(map, child, record.id);
891
+ const childRecord = buildNodeFromSnapshot(map, child, record.id, blockEpoch);
760
892
  record.children.push(childRecord.id);
761
893
  }
762
894
  return record;
@@ -784,8 +916,7 @@ function removeSubtree(map, id, touched) {
784
916
  }
785
917
  }
786
918
  map.delete(node.id);
787
- nodeSnapshotCache.delete(node.id);
788
- childrenSnapshotCache.delete(node.id);
919
+ deleteSnapshotCacheEntry(map, node.id);
789
920
  touched.add(node.id);
790
921
  for (const childId of node.children) {
791
922
  const child = map.get(childId);
@@ -817,13 +948,13 @@ function resolveParent(map, path) {
817
948
  }
818
949
  return map.get(path.blockId);
819
950
  }
820
- function insertSnapshotAt(map, parent, index, snapshot, touched) {
951
+ function insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
821
952
  if (!parent) return null;
822
953
  const existingIndex = parent.children.indexOf(snapshot.id);
823
954
  if (existingIndex !== -1) {
824
955
  removeSubtree(map, parent.children[existingIndex], touched ?? /* @__PURE__ */ new Set());
825
956
  }
826
- const record = buildNodeFromSnapshot(map, snapshot, parent.id);
957
+ const record = buildNodeFromSnapshot(map, snapshot, parent.id, blockEpoch ?? readSnapshotBlockEpoch(snapshot) ?? parent.blockEpoch);
827
958
  const insertedIds = [];
828
959
  const queue = [record];
829
960
  while (queue.length > 0) {
@@ -841,13 +972,13 @@ function insertSnapshotAt(map, parent, index, snapshot, touched) {
841
972
  ensureUniqueChildren(parent, touched);
842
973
  return { record, insertedIds };
843
974
  }
844
- function replaceSnapshotAt(map, parent, index, snapshot, touched) {
975
+ function replaceSnapshotAt(map, parent, index, snapshot, touched, blockEpoch) {
845
976
  if (!parent) return;
846
977
  if (index < 0 || index >= parent.children.length) return;
847
978
  const targetId = parent.children[index];
848
979
  parent.children.splice(index, 1);
849
980
  removeSubtree(map, targetId, touched);
850
- const inserted = insertSnapshotAt(map, parent, index, snapshot, touched);
981
+ const inserted = insertSnapshotAt(map, parent, index, snapshot, touched, blockEpoch);
851
982
  if (inserted) {
852
983
  for (const id of inserted.insertedIds) {
853
984
  touched.add(id);
@@ -867,6 +998,24 @@ function createRendererStore(initialBlocks = []) {
867
998
  const listeners = /* @__PURE__ */ new Set();
868
999
  let notifyScheduled = false;
869
1000
  let lastCoalescingMetrics = null;
1001
+ let invariantViolations = [];
1002
+ let transientDiagnostics = [];
1003
+ let nextBlockEpoch = 1;
1004
+ const debugCounters = {
1005
+ appendLineGuardRejected: 0,
1006
+ staleEpochRejected: 0,
1007
+ storeInvariantViolationCount: 0
1008
+ };
1009
+ const allocateBlockEpoch = (preferred) => {
1010
+ const resolved = readNumericEpoch(preferred);
1011
+ if (resolved !== void 0) {
1012
+ nextBlockEpoch = Math.max(nextBlockEpoch, resolved + 1);
1013
+ return resolved;
1014
+ }
1015
+ const epoch = nextBlockEpoch;
1016
+ nextBlockEpoch += 1;
1017
+ return epoch;
1018
+ };
870
1019
  const getRaf = () => {
871
1020
  if (typeof globalThis !== "undefined" && typeof globalThis.requestAnimationFrame === "function") {
872
1021
  return globalThis.requestAnimationFrame;
@@ -898,9 +1047,12 @@ function createRendererStore(initialBlocks = []) {
898
1047
  }
899
1048
  }
900
1049
  }
1050
+ function appendDiagnostic(message) {
1051
+ transientDiagnostics = [...transientDiagnostics, message];
1052
+ invariantViolations = [...invariantViolations, message];
1053
+ }
901
1054
  function rebuildFromBlocks(blocks) {
902
- nodeSnapshotCache.clear();
903
- childrenSnapshotCache.clear();
1055
+ clearSnapshotCaches(nodes);
904
1056
  nodes = /* @__PURE__ */ new Map();
905
1057
  const root = createRootRecord();
906
1058
  nodes.set(root.id, root);
@@ -909,9 +1061,12 @@ function createRendererStore(initialBlocks = []) {
909
1061
  for (let i = 0; i < blocks.length; i++) {
910
1062
  const block = (0, import_core2.cloneBlock)(blocks[i]);
911
1063
  const snapshot = (0, import_core2.createBlockSnapshot)(block);
912
- insertSnapshotAt(nodes, root, root.children.length, snapshot);
1064
+ insertSnapshotAt(nodes, root, root.children.length, snapshot, void 0, allocateBlockEpoch(readSnapshotBlockEpoch(snapshot)));
913
1065
  }
914
1066
  normalizeAllListDepths(nodes, /* @__PURE__ */ new Set(), import_core2.PATCH_ROOT_ID);
1067
+ transientDiagnostics = [];
1068
+ invariantViolations = collectStoreInvariantViolations(nodes);
1069
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
915
1070
  version++;
916
1071
  blocksDirty = true;
917
1072
  scheduleNotify();
@@ -976,11 +1131,57 @@ function createRendererStore(initialBlocks = []) {
976
1131
  listDepthDirty = true;
977
1132
  }
978
1133
  };
979
- const applyPropsUpdate = (at, props) => {
1134
+ const semanticTxEpochs = /* @__PURE__ */ new Map();
1135
+ const resolveBlockEpochTarget = (patch, at) => {
1136
+ if ((patch.op === "insertChild" || patch.op === "replaceChild") && at.blockId === import_core2.PATCH_ROOT_ID) {
1137
+ const nodeId = patch.node?.id;
1138
+ return nodeId ? nodes.get(nodeId) : void 0;
1139
+ }
1140
+ return nodes.get(at.blockId) ?? (at.nodeId ? nodes.get(at.nodeId) : void 0);
1141
+ };
1142
+ const rejectStaleEpochPatch = (patch, at) => {
1143
+ const expectedBlockEpoch = readPatchBlockEpoch(patch);
1144
+ const patchTx = readPatchTx(patch);
1145
+ if (expectedBlockEpoch === void 0) {
1146
+ return false;
1147
+ }
1148
+ if ((0, import_patch_batching.getPatchKind)(patch) !== "semantic" || !at) {
1149
+ return false;
1150
+ }
1151
+ const target = resolveBlockEpochTarget(patch, at);
1152
+ if (!target || target.blockEpoch === expectedBlockEpoch) {
1153
+ return false;
1154
+ }
1155
+ const txEpoch = at.blockId && patchTx !== void 0 ? semanticTxEpochs.get(at.blockId) : void 0;
1156
+ if (txEpoch && txEpoch.tx === patchTx && txEpoch.blockEpoch === target.blockEpoch) {
1157
+ return false;
1158
+ }
1159
+ debugCounters.staleEpochRejected += 1;
1160
+ appendDiagnostic(
1161
+ `stale epoch rejected:${patch.op}:${target.id}:expected=${expectedBlockEpoch}:actual=${target.blockEpoch}`
1162
+ );
1163
+ return true;
1164
+ };
1165
+ const advanceBlockEpoch = (blockId, touchedSet, preferredEpoch, tx) => {
1166
+ if (!blockId || blockId === import_core2.PATCH_ROOT_ID) {
1167
+ return null;
1168
+ }
1169
+ const blockRoot = nodes.get(blockId);
1170
+ if (!blockRoot) {
1171
+ return null;
1172
+ }
1173
+ const nextEpoch = allocateBlockEpoch(preferredEpoch);
1174
+ assignSubtreeBlockEpoch(nodes, blockRoot.id, nextEpoch, touchedSet);
1175
+ if (tx !== void 0) {
1176
+ semanticTxEpochs.set(blockRoot.id, { tx, blockEpoch: nextEpoch });
1177
+ }
1178
+ return nextEpoch;
1179
+ };
1180
+ const applyPropsUpdate = (at, props, preferredEpoch, tx, semanticUpdate = false) => {
980
1181
  const perfStartLocal = perfEnabled ? getPerfNow() : 0;
981
1182
  const targetId = at.nodeId ?? at.blockId;
982
1183
  if (!targetId) return;
983
- const target = nodes.get(targetId);
1184
+ let target = nodes.get(targetId);
984
1185
  if (!target) return;
985
1186
  const parentType = target.parentId ? nodes.get(target.parentId)?.type : void 0;
986
1187
  if (debugListPatches && (isListRelated(target.type) || isListRelated(parentType))) {
@@ -1007,6 +1208,9 @@ function createRendererStore(initialBlocks = []) {
1007
1208
  propsChanged = true;
1008
1209
  }
1009
1210
  if (propsChanged) {
1211
+ if (semanticUpdate) {
1212
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
1213
+ }
1010
1214
  target.version++;
1011
1215
  touched.add(target.id);
1012
1216
  const parent = target.parentId ? nodes.get(target.parentId) : void 0;
@@ -1070,16 +1274,56 @@ function createRendererStore(initialBlocks = []) {
1070
1274
  }
1071
1275
  }
1072
1276
  let blockChanged = false;
1277
+ let typeChanged = false;
1278
+ let rebuiltFromFinalizedBlock = false;
1279
+ let blockEpochAdvanced = false;
1073
1280
  const incomingBlock = props?.block;
1074
1281
  if (incomingBlock && typeof incomingBlock === "object") {
1075
1282
  target.block = (0, import_core2.cloneBlock)(incomingBlock);
1283
+ const previousType = target.type;
1284
+ const incomingType = typeof target.block.type === "string" ? target.block.type : target.type;
1285
+ if (incomingType !== target.type) {
1286
+ target.type = incomingType;
1287
+ typeChanged = true;
1288
+ if (isListRelated(previousType) || isListRelated(target.type)) {
1289
+ markListDepthDirty(target.id);
1290
+ }
1291
+ }
1076
1292
  if (target.type === "code") {
1077
1293
  applyCodeBlockMetadata(target);
1078
1294
  }
1295
+ if (semanticUpdate) {
1296
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
1297
+ blockEpochAdvanced = true;
1298
+ }
1299
+ const shouldRebuildFinalizedTree = !at.nodeId && target.id === at.blockId && Boolean(target.block?.isFinalized) && target.type !== "code" && Boolean(target.parentId);
1300
+ if (shouldRebuildFinalizedTree) {
1301
+ const parent = target.parentId ? nodes.get(target.parentId) : void 0;
1302
+ const index = parent ? parent.children.indexOf(target.id) : -1;
1303
+ if (parent && index !== -1) {
1304
+ const snapshot = (0, import_core2.createBlockSnapshot)((0, import_core2.cloneBlock)(target.block));
1305
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
1306
+ mutatedBlocks = true;
1307
+ blockChanged = true;
1308
+ rebuiltFromFinalizedBlock = true;
1309
+ const rebuilt = nodes.get(target.id);
1310
+ if (isListRelated(previousType) || isListRelated(rebuilt?.type)) {
1311
+ markListDepthDirty(target.id);
1312
+ }
1313
+ target = rebuilt ?? target;
1314
+ }
1315
+ }
1079
1316
  mutatedBlocks = true;
1080
1317
  blockChanged = true;
1081
1318
  }
1082
- if (propsChanged || blockChanged) {
1319
+ if (rebuiltFromFinalizedBlock) {
1320
+ return;
1321
+ }
1322
+ if (propsChanged || blockChanged || typeChanged) {
1323
+ if (semanticUpdate && !blockEpochAdvanced) {
1324
+ advanceBlockEpoch(at.blockId, touched, preferredEpoch, tx);
1325
+ blockEpochAdvanced = true;
1326
+ }
1083
1327
  target.version++;
1084
1328
  touched.add(target.id);
1085
1329
  let parentForNormalization;
@@ -1122,13 +1366,22 @@ function createRendererStore(initialBlocks = []) {
1122
1366
  const applyPatch = (patch) => {
1123
1367
  switch (patch.op) {
1124
1368
  case "insertChild": {
1369
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
1125
1370
  const parent = resolveParent(nodes, patch.at);
1126
- const inserted = insertSnapshotAt(nodes, parent, patch.index, patch.node, touched);
1371
+ const inserted = insertSnapshotAt(
1372
+ nodes,
1373
+ parent,
1374
+ patch.index,
1375
+ patch.node,
1376
+ touched,
1377
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
1378
+ );
1127
1379
  if (inserted) {
1128
1380
  for (const id of inserted.insertedIds) {
1129
1381
  touched.add(id);
1130
1382
  }
1131
1383
  mutatedBlocks = true;
1384
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
1132
1385
  if (isListRelated(inserted.record.type) || isListRelated(parent?.type)) {
1133
1386
  markListDepthDirty(inserted.record.id);
1134
1387
  if (parent) {
@@ -1148,6 +1401,7 @@ function createRendererStore(initialBlocks = []) {
1148
1401
  break;
1149
1402
  }
1150
1403
  case "deleteChild": {
1404
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
1151
1405
  const parent = resolveParent(nodes, patch.at);
1152
1406
  if (!parent) break;
1153
1407
  if (patch.index < 0 || patch.index >= parent.children.length) break;
@@ -1159,6 +1413,7 @@ function createRendererStore(initialBlocks = []) {
1159
1413
  touched.add(parent.id);
1160
1414
  ensureUniqueChildren(parent, touched);
1161
1415
  mutatedBlocks = true;
1416
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
1162
1417
  if (isListRelated(parent.type) || isListRelated(removedNode?.type)) {
1163
1418
  markListDepthDirty(parent.id);
1164
1419
  }
@@ -1175,11 +1430,20 @@ function createRendererStore(initialBlocks = []) {
1175
1430
  break;
1176
1431
  }
1177
1432
  case "replaceChild": {
1433
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
1178
1434
  const parent = resolveParent(nodes, patch.at);
1179
1435
  if (!parent) break;
1180
- replaceSnapshotAt(nodes, parent, patch.index, patch.node, touched);
1436
+ replaceSnapshotAt(
1437
+ nodes,
1438
+ parent,
1439
+ patch.index,
1440
+ patch.node,
1441
+ touched,
1442
+ allocateBlockEpoch(readPatchParseEpoch(patch) ?? readSnapshotBlockEpoch(patch.node))
1443
+ );
1181
1444
  touched.add(parent.id);
1182
1445
  mutatedBlocks = true;
1446
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
1183
1447
  if (isListRelated(parent.type)) {
1184
1448
  markListDepthDirty(parent.id);
1185
1449
  }
@@ -1193,18 +1457,33 @@ function createRendererStore(initialBlocks = []) {
1193
1457
  break;
1194
1458
  }
1195
1459
  case "setProps": {
1196
- applyPropsUpdate(patch.at, patch.props);
1460
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
1461
+ applyPropsUpdate(patch.at, patch.props, readPatchParseEpoch(patch), readPatchTx(patch), (0, import_patch_batching.getPatchKind)(patch) === "semantic");
1197
1462
  break;
1198
1463
  }
1199
1464
  case "setPropsBatch": {
1200
1465
  const entries = Array.isArray(patch.entries) ? patch.entries : [];
1201
1466
  for (const entry of entries) {
1202
1467
  if (!entry || !entry.at) continue;
1203
- applyPropsUpdate(entry.at, entry.props);
1468
+ const entryPatch = {
1469
+ op: "setProps",
1470
+ at: entry.at,
1471
+ props: entry.props,
1472
+ meta: entry.meta ?? patch.meta
1473
+ };
1474
+ if (rejectStaleEpochPatch(entryPatch, entry.at)) continue;
1475
+ applyPropsUpdate(
1476
+ entry.at,
1477
+ entry.props,
1478
+ readPatchParseEpoch(entryPatch),
1479
+ readPatchTx(entryPatch),
1480
+ (0, import_patch_batching.getPatchKind)(entryPatch) === "semantic"
1481
+ );
1204
1482
  }
1205
1483
  break;
1206
1484
  }
1207
1485
  case "finalize": {
1486
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
1208
1487
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
1209
1488
  if (!target || !target.block) break;
1210
1489
  if (!target.block.isFinalized) {
@@ -1213,6 +1492,17 @@ function createRendererStore(initialBlocks = []) {
1213
1492
  target.version++;
1214
1493
  touched.add(target.id);
1215
1494
  mutatedBlocks = true;
1495
+ if (!patch.at.nodeId && target.id === patch.at.blockId && target.type !== "code" && target.parentId) {
1496
+ const parent = nodes.get(target.parentId);
1497
+ const index = parent ? parent.children.indexOf(target.id) : -1;
1498
+ if (parent && index !== -1) {
1499
+ const snapshot = (0, import_core2.createBlockSnapshot)((0, import_core2.cloneBlock)(target.block));
1500
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, target.blockEpoch);
1501
+ if (isListRelated(target.type) || isListRelated(snapshot.type)) {
1502
+ markListDepthDirty(target.id);
1503
+ }
1504
+ }
1505
+ }
1216
1506
  }
1217
1507
  if (isListRelated(target.type)) {
1218
1508
  markListDepthDirty(target.id);
@@ -1226,6 +1516,7 @@ function createRendererStore(initialBlocks = []) {
1226
1516
  break;
1227
1517
  }
1228
1518
  case "reorder": {
1519
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
1229
1520
  const parent = resolveParent(nodes, patch.at);
1230
1521
  if (!parent) break;
1231
1522
  const length = parent.children.length;
@@ -1240,6 +1531,9 @@ function createRendererStore(initialBlocks = []) {
1240
1531
  parent.children.splice(to, 0, ...moved);
1241
1532
  parent.version++;
1242
1533
  touched.add(parent.id);
1534
+ if ((0, import_patch_batching.getPatchKind)(patch) === "semantic") {
1535
+ advanceBlockEpoch(patch.at.blockId, touched, readPatchParseEpoch(patch), readPatchTx(patch));
1536
+ }
1243
1537
  if (isListRelated(parent.type)) {
1244
1538
  markListDepthDirty(parent.id);
1245
1539
  }
@@ -1255,15 +1549,31 @@ function createRendererStore(initialBlocks = []) {
1255
1549
  break;
1256
1550
  }
1257
1551
  case "appendLines": {
1552
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
1258
1553
  const parent = resolveParent(nodes, patch.at);
1259
1554
  if (!parent) break;
1260
- const startIndex = Math.max(0, Math.min(patch.startIndex, parent.children.length));
1555
+ if (parent.type !== "code") {
1556
+ debugCounters.appendLineGuardRejected += 1;
1557
+ appendDiagnostic(`appendLines target not code:${parent.id}:${parent.type}`);
1558
+ break;
1559
+ }
1560
+ const requestedStartIndex = Math.max(0, patch.startIndex);
1561
+ const startIndex = Math.max(0, Math.min(requestedStartIndex, parent.children.length));
1261
1562
  const lineCount = patch.lines?.length ?? 0;
1262
1563
  if (lineCount === 0) break;
1564
+ const prefixMatches = parent.children.every((childId, index) => childId === expectedCodeLineId(parent.id, index));
1565
+ const isStrictTailAppend = requestedStartIndex === parent.children.length;
1566
+ if (!isStrictTailAppend || !prefixMatches) {
1567
+ debugCounters.appendLineGuardRejected += 1;
1568
+ appendDiagnostic(
1569
+ `appendLines guard rejected:${parent.id}:requestedStart=${requestedStartIndex}:childCount=${parent.children.length}:prefixMatches=${prefixMatches}`
1570
+ );
1571
+ break;
1572
+ }
1263
1573
  const originalChildren = parent.children.slice();
1264
1574
  const insertedIds = [];
1265
1575
  const insertedSet = /* @__PURE__ */ new Set();
1266
- const isTrailingAppend = startIndex >= originalChildren.length;
1576
+ const isTrailingAppend = true;
1267
1577
  let parentChildrenChanged = false;
1268
1578
  let codeLinesMutated = false;
1269
1579
  for (let offset = 0; offset < lineCount; offset++) {
@@ -1305,7 +1615,8 @@ function createRendererStore(initialBlocks = []) {
1305
1615
  parentId: parent.id,
1306
1616
  children: [],
1307
1617
  props: nextLineProps,
1308
- version: 0
1618
+ version: 0,
1619
+ blockEpoch: parent.blockEpoch
1309
1620
  };
1310
1621
  nodes.set(lineId, record);
1311
1622
  touched.add(lineId);
@@ -1355,6 +1666,7 @@ function createRendererStore(initialBlocks = []) {
1355
1666
  break;
1356
1667
  }
1357
1668
  case "setHTML": {
1669
+ if (rejectStaleEpochPatch(patch, patch.at)) break;
1358
1670
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
1359
1671
  if (!target) break;
1360
1672
  const sanitized = patch.sanitized ? patch.html : (0, import_core2.sanitizeHTML)(patch.html);
@@ -1397,6 +1709,30 @@ function createRendererStore(initialBlocks = []) {
1397
1709
  applyPatch(patch);
1398
1710
  }
1399
1711
  }
1712
+ const finalizedTableRoots = /* @__PURE__ */ new Set();
1713
+ for (const touchedId of touched) {
1714
+ let cursor = nodes.get(touchedId);
1715
+ while (cursor && cursor.id !== import_core2.PATCH_ROOT_ID) {
1716
+ if (cursor.type === "table" && cursor.block?.isFinalized && cursor.parentId) {
1717
+ finalizedTableRoots.add(cursor.id);
1718
+ break;
1719
+ }
1720
+ cursor = cursor.parentId ? nodes.get(cursor.parentId) : void 0;
1721
+ }
1722
+ }
1723
+ if (finalizedTableRoots.size > 0) {
1724
+ for (const tableId of finalizedTableRoots) {
1725
+ const tableNode = nodes.get(tableId);
1726
+ if (!tableNode || !tableNode.block || !tableNode.parentId) continue;
1727
+ const parent = nodes.get(tableNode.parentId);
1728
+ if (!parent) continue;
1729
+ const index = parent.children.indexOf(tableNode.id);
1730
+ if (index === -1) continue;
1731
+ const snapshot = (0, import_core2.createBlockSnapshot)((0, import_core2.cloneBlock)(tableNode.block));
1732
+ replaceSnapshotAt(nodes, parent, index, snapshot, touched, tableNode.blockEpoch);
1733
+ mutatedBlocks = true;
1734
+ }
1735
+ }
1400
1736
  if (mutatedBlocks) {
1401
1737
  blocksDirty = true;
1402
1738
  }
@@ -1417,6 +1753,8 @@ function createRendererStore(initialBlocks = []) {
1417
1753
  normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, import_core2.PATCH_ROOT_ID);
1418
1754
  }
1419
1755
  }
1756
+ invariantViolations = [...collectStoreInvariantViolations(nodes), ...transientDiagnostics];
1757
+ debugCounters.storeInvariantViolationCount += invariantViolations.length;
1420
1758
  if (touched.size > 0 || mutatedBlocks) {
1421
1759
  version++;
1422
1760
  scheduleNotify();
@@ -1465,6 +1803,7 @@ function createRendererStore(initialBlocks = []) {
1465
1803
  if (!record) {
1466
1804
  return EMPTY_NODE_SNAPSHOT;
1467
1805
  }
1806
+ const nodeSnapshotCache = getNodeSnapshotCacheForMap(nodes);
1468
1807
  const cached = nodeSnapshotCache.get(id);
1469
1808
  if (cached && cached.version === record.version && cached.node === record) {
1470
1809
  return cached;
@@ -1484,6 +1823,7 @@ function createRendererStore(initialBlocks = []) {
1484
1823
  if (!record) {
1485
1824
  return EMPTY_CHILDREN_SNAPSHOT;
1486
1825
  }
1826
+ const childrenSnapshotCache = getChildrenSnapshotCacheForMap(nodes);
1487
1827
  const cached = childrenSnapshotCache.get(id);
1488
1828
  if (cached && cached.version === record.version) {
1489
1829
  return cached;
@@ -1493,6 +1833,12 @@ function createRendererStore(initialBlocks = []) {
1493
1833
  pruneCache(childrenSnapshotCache, CHILDREN_SNAPSHOT_CACHE_LIMIT, CHILDREN_SNAPSHOT_CACHE_BUFFER);
1494
1834
  return snapshot;
1495
1835
  },
1836
+ getInvariantViolations() {
1837
+ return invariantViolations.slice();
1838
+ },
1839
+ getDebugCounters() {
1840
+ return { ...debugCounters };
1841
+ },
1496
1842
  getLastCoalescingMetrics() {
1497
1843
  return lastCoalescingMetrics;
1498
1844
  },