@stream-mdx/react 0.1.0 → 0.2.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 (47) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +4 -0
  3. package/dist/components/index.cjs +497 -163
  4. package/dist/components/index.d.cts +1 -1
  5. package/dist/components/index.d.ts +1 -1
  6. package/dist/components/index.mjs +496 -163
  7. package/dist/{index-Bt1opGCs.d.cts → index-D0akq48G.d.cts} +24 -2
  8. package/dist/{index-Bt1opGCs.d.ts → index-D0akq48G.d.ts} +24 -2
  9. package/dist/index.cjs +3797 -2048
  10. package/dist/index.d.cts +43 -5
  11. package/dist/index.d.ts +43 -5
  12. package/dist/index.mjs +3741 -1990
  13. package/dist/mdx-client.cjs +60 -18
  14. package/dist/mdx-client.d.cts +11 -0
  15. package/dist/mdx-client.d.ts +11 -0
  16. package/dist/mdx-client.mjs +60 -18
  17. package/dist/mdx-coordinator.cjs +60 -18
  18. package/dist/mdx-coordinator.mjs +60 -18
  19. package/dist/renderer/node-views.cjs +481 -130
  20. package/dist/renderer/node-views.d.cts +1 -1
  21. package/dist/renderer/node-views.d.ts +1 -1
  22. package/dist/renderer/node-views.mjs +424 -65
  23. package/dist/renderer/patch-commit-scheduler.cjs +68 -7
  24. package/dist/renderer/patch-commit-scheduler.d.cts +6 -5
  25. package/dist/renderer/patch-commit-scheduler.d.ts +6 -5
  26. package/dist/renderer/patch-commit-scheduler.mjs +68 -7
  27. package/dist/renderer/store.cjs +481 -56
  28. package/dist/renderer/store.d.cts +2 -1
  29. package/dist/renderer/store.d.ts +2 -1
  30. package/dist/renderer/store.mjs +479 -47
  31. package/dist/renderer/virtualized-code.cjs +8 -2
  32. package/dist/renderer/virtualized-code.d.cts +4 -0
  33. package/dist/renderer/virtualized-code.d.ts +4 -0
  34. package/dist/renderer/virtualized-code.mjs +8 -2
  35. package/dist/renderer.cjs +3188 -2172
  36. package/dist/renderer.d.cts +4 -2
  37. package/dist/renderer.d.ts +4 -2
  38. package/dist/renderer.mjs +3009 -1985
  39. package/dist/streaming-markdown-Ch6PwjAa.d.cts +154 -0
  40. package/dist/streaming-markdown-tca-Mf8D.d.ts +154 -0
  41. package/dist/streaming-markdown.cjs +3929 -2202
  42. package/dist/streaming-markdown.d.cts +6 -95
  43. package/dist/streaming-markdown.d.ts +6 -95
  44. package/dist/streaming-markdown.mjs +3943 -2208
  45. package/dist/utils/inline-html.d.cts +1 -1
  46. package/dist/utils/inline-html.d.ts +1 -1
  47. package/package.json +3 -3
@@ -21,13 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/renderer/store.ts
22
22
  var store_exports = {};
23
23
  __export(store_exports, {
24
- createRendererStore: () => createRendererStore
24
+ createRendererStore: () => createRendererStore,
25
+ isListPatchDebugEnabled: () => isListPatchDebugEnabled
25
26
  });
26
27
  module.exports = __toCommonJS(store_exports);
27
28
  var import_core2 = require("@stream-mdx/core");
28
- var import_core3 = require("@stream-mdx/core");
29
- var import_core4 = require("@stream-mdx/core");
30
- var import_core5 = require("@stream-mdx/core");
31
29
 
32
30
  // src/renderer/list-utils.ts
33
31
  function updateNodeDepth(node, depth) {
@@ -52,6 +50,22 @@ function normalizeAllListDepths(map, touched, rootId = "__root__") {
52
50
  }
53
51
  }
54
52
  }
53
+ function normalizeListDepthsForIds(map, touched, listIds, rootId = "__root__") {
54
+ if (listIds.size === 0) {
55
+ normalizeAllListDepths(map, touched, rootId);
56
+ return;
57
+ }
58
+ const visited = /* @__PURE__ */ new Set();
59
+ for (const listId of listIds) {
60
+ const base = map.get(listId);
61
+ if (!base) continue;
62
+ const listNode = base.type === "list" ? base : findNearestListAncestor(map, base);
63
+ if (!listNode || visited.has(listNode.id)) continue;
64
+ visited.add(listNode.id);
65
+ const depth = computeListDepth(map, listNode, rootId);
66
+ normalizeListDepthRecursive(map, listNode, depth, touched);
67
+ }
68
+ }
55
69
  function normalizeListDepthRecursive(map, listNode, depth, touched) {
56
70
  if (!listNode) return;
57
71
  if (updateNodeDepth(listNode, depth)) {
@@ -76,6 +90,28 @@ function normalizeListDepthRecursive(map, listNode, depth, touched) {
76
90
  }
77
91
  }
78
92
  }
93
+ function findNearestListAncestor(map, node) {
94
+ let current = node;
95
+ while (current) {
96
+ if (current.type === "list") return current;
97
+ if (!current.parentId) return void 0;
98
+ current = map.get(current.parentId);
99
+ }
100
+ return void 0;
101
+ }
102
+ function computeListDepth(map, listNode, rootId) {
103
+ let depth = 0;
104
+ let current = listNode;
105
+ while (current && current.parentId && current.parentId !== rootId) {
106
+ const parent = map.get(current.parentId);
107
+ if (!parent) break;
108
+ if (parent.type === "list") {
109
+ depth += 1;
110
+ }
111
+ current = parent;
112
+ }
113
+ return depth;
114
+ }
79
115
 
80
116
  // src/renderer/patch-coalescing.ts
81
117
  var import_core = require("@stream-mdx/core");
@@ -90,12 +126,35 @@ function snapshotToBlock(node) {
90
126
  }
91
127
  var nodeSnapshotCache = /* @__PURE__ */ new Map();
92
128
  var childrenSnapshotCache = /* @__PURE__ */ new Map();
129
+ var NODE_SNAPSHOT_CACHE_LIMIT = 5e4;
130
+ var CHILDREN_SNAPSHOT_CACHE_LIMIT = 5e4;
131
+ var NODE_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(NODE_SNAPSHOT_CACHE_LIMIT * 0.1));
132
+ var CHILDREN_SNAPSHOT_CACHE_BUFFER = Math.max(200, Math.floor(CHILDREN_SNAPSHOT_CACHE_LIMIT * 0.1));
133
+ function pruneCache(cache, max, buffer) {
134
+ if (cache.size <= max + buffer) return;
135
+ let remaining = cache.size - max;
136
+ for (const key of cache.keys()) {
137
+ cache.delete(key);
138
+ remaining -= 1;
139
+ if (remaining <= 0) break;
140
+ }
141
+ }
93
142
  var EMPTY_CHILDREN = Object.freeze([]);
94
143
  var EMPTY_NODE_SNAPSHOT = Object.freeze({ version: -1, node: void 0 });
95
144
  var EMPTY_CHILDREN_SNAPSHOT = Object.freeze({
96
145
  version: -1,
97
146
  children: EMPTY_CHILDREN
98
147
  });
148
+ function isListPatchDebugEnabled() {
149
+ if (typeof globalThis !== "undefined") {
150
+ const flag = globalThis.__STREAMING_LIST_DEBUG__;
151
+ if (flag === true) return true;
152
+ }
153
+ if (typeof process !== "undefined" && process.env.NEXT_PUBLIC_STREAMING_LIST_DEBUG === "true") {
154
+ return true;
155
+ }
156
+ return false;
157
+ }
99
158
  var CODE_BLOCK_DEBUG_ENABLED = (() => {
100
159
  try {
101
160
  if (typeof process !== "undefined" && process.env && process.env.NEXT_PUBLIC_STREAMING_DEBUG_CODELINES === "1") {
@@ -111,6 +170,128 @@ var CODE_BLOCK_DEBUG_ENABLED = (() => {
111
170
  }
112
171
  return false;
113
172
  })() || false;
173
+ var CODE_BLOCK_VALIDATION_ENABLED = typeof process !== "undefined" && process.env ? process.env.NODE_ENV !== "production" : true;
174
+ var PATCH_PERF_HISTORY_LIMIT = 120;
175
+ var getPerfNow = (() => {
176
+ if (typeof performance !== "undefined" && typeof performance.now === "function") {
177
+ return () => performance.now();
178
+ }
179
+ return () => Date.now();
180
+ })();
181
+ function isPatchPerfDebugEnabled() {
182
+ try {
183
+ if (typeof process !== "undefined" && process.env) {
184
+ const value = process.env.NEXT_PUBLIC_STREAMING_DEBUG_PATCH_PERF;
185
+ if (value === "1" || value === "true") {
186
+ return true;
187
+ }
188
+ }
189
+ } catch {
190
+ }
191
+ try {
192
+ const debug = globalThis.__STREAMING_DEBUG__;
193
+ if (debug?.patchPerf) {
194
+ return true;
195
+ }
196
+ } catch {
197
+ }
198
+ return false;
199
+ }
200
+ function recordPatchPerf(summary) {
201
+ try {
202
+ if (typeof globalThis === "undefined") return;
203
+ const root = globalThis;
204
+ const history = root.__STREAM_MDX_PATCH_STATS__ ?? [];
205
+ history.push(summary);
206
+ if (history.length > PATCH_PERF_HISTORY_LIMIT) {
207
+ history.splice(0, history.length - PATCH_PERF_HISTORY_LIMIT);
208
+ }
209
+ root.__STREAM_MDX_PATCH_STATS__ = history;
210
+ root.__STREAM_MDX_PATCH_STATS_LAST__ = summary;
211
+ const totals = root.__STREAM_MDX_PATCH_STATS_TOTALS__ ?? {
212
+ calls: 0,
213
+ totalMs: 0,
214
+ applyMs: 0,
215
+ coalesceMs: 0,
216
+ listNormalizeMs: 0,
217
+ patches: 0,
218
+ coalescedPatches: 0,
219
+ ops: {},
220
+ setPropsByType: {},
221
+ setPropsBySize: {}
222
+ };
223
+ totals.calls += 1;
224
+ totals.totalMs += summary.totalMs;
225
+ totals.applyMs += summary.applyMs;
226
+ totals.coalesceMs += summary.coalesceMs;
227
+ totals.listNormalizeMs += summary.listNormalizeMs;
228
+ totals.patches += summary.patches;
229
+ totals.coalescedPatches += summary.coalescedPatches;
230
+ for (const [op, stats] of Object.entries(summary.ops)) {
231
+ const entry = totals.ops[op] ?? { count: 0, durationMs: 0 };
232
+ entry.count += stats.count;
233
+ entry.durationMs += stats.durationMs;
234
+ totals.ops[op] = entry;
235
+ }
236
+ if (summary.setPropsByType) {
237
+ for (const [key, stats] of Object.entries(summary.setPropsByType)) {
238
+ const entry = totals.setPropsByType[key] ?? { count: 0, durationMs: 0 };
239
+ entry.count += stats.count;
240
+ entry.durationMs += stats.durationMs;
241
+ totals.setPropsByType[key] = entry;
242
+ }
243
+ }
244
+ if (summary.setPropsBySize) {
245
+ for (const [key, stats] of Object.entries(summary.setPropsBySize)) {
246
+ const entry = totals.setPropsBySize[key] ?? { count: 0, durationMs: 0 };
247
+ entry.count += stats.count;
248
+ entry.durationMs += stats.durationMs;
249
+ totals.setPropsBySize[key] = entry;
250
+ }
251
+ }
252
+ root.__STREAM_MDX_PATCH_STATS_TOTALS__ = totals;
253
+ } catch {
254
+ }
255
+ }
256
+ function estimateBlockPayloadSize(block) {
257
+ if (!block || !block.payload) return 0;
258
+ let size = 0;
259
+ const payload = block.payload;
260
+ if (typeof payload.raw === "string") size += payload.raw.length;
261
+ if (typeof payload.highlightedHtml === "string") size += payload.highlightedHtml.length;
262
+ if (typeof payload.sanitizedHtml === "string") size += payload.sanitizedHtml.length;
263
+ if (typeof payload.compiledMdxModule?.code === "string") size += payload.compiledMdxModule.code.length;
264
+ if (Array.isArray(payload.inline)) size += payload.inline.length * 8;
265
+ return size;
266
+ }
267
+ function estimatePropsSize(props, block) {
268
+ let size = 0;
269
+ if (props) {
270
+ for (const [key, value] of Object.entries(props)) {
271
+ if (key === "block") continue;
272
+ if (typeof value === "string") {
273
+ size += value.length;
274
+ } else if (typeof value === "number") {
275
+ size += 8;
276
+ } else if (typeof value === "boolean") {
277
+ size += 4;
278
+ } else if (Array.isArray(value)) {
279
+ size += value.length * 4;
280
+ }
281
+ }
282
+ }
283
+ if (block) {
284
+ size += estimateBlockPayloadSize(block);
285
+ }
286
+ return size;
287
+ }
288
+ function bucketBySize(size) {
289
+ if (size < 1024) return "<1k";
290
+ if (size < 10 * 1024) return "1-10k";
291
+ if (size < 50 * 1024) return "10-50k";
292
+ if (size < 200 * 1024) return "50-200k";
293
+ return ">=200k";
294
+ }
114
295
  function debugCodeBlock(event, payload) {
115
296
  if (!CODE_BLOCK_DEBUG_ENABLED) return;
116
297
  try {
@@ -120,7 +301,7 @@ function debugCodeBlock(event, payload) {
120
301
  }
121
302
  function createRootRecord() {
122
303
  return {
123
- id: import_core5.PATCH_ROOT_ID,
304
+ id: import_core2.PATCH_ROOT_ID,
124
305
  type: "__root__",
125
306
  parentId: null,
126
307
  children: [],
@@ -187,7 +368,7 @@ function escapeHtml(value) {
187
368
  }
188
369
  function stripOuterLineSpan(html) {
189
370
  if (!html) return null;
190
- const openTagMatch = html.match(/<span[^>]*class="[^"]*\bline\b[^"]*"[^>]*>/i);
371
+ const openTagMatch = html.match(/<span[^>]*>/i);
191
372
  if (!openTagMatch) {
192
373
  return null;
193
374
  }
@@ -206,7 +387,7 @@ function stripOuterLineSpan(html) {
206
387
  function sanitizeLineInnerHtml(innerHtml, fallbackText) {
207
388
  if (innerHtml && innerHtml.trim().length > 0) {
208
389
  const wrapped = `<span class="line">${innerHtml}</span>`;
209
- const sanitized = (0, import_core4.sanitizeCodeHTML)(wrapped);
390
+ const sanitized = (0, import_core2.sanitizeCodeHTML)(wrapped);
210
391
  const inner = stripOuterLineSpan(typeof sanitized === "string" ? sanitized : String(sanitized));
211
392
  if (inner !== null) {
212
393
  return inner;
@@ -304,8 +485,12 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
304
485
  touched.add(node.id);
305
486
  mutated = true;
306
487
  }
307
- const normalizedProps = normalizeCodeLineProps({ index: idx, text: node.props?.text, html: node.props?.html }, node.props);
308
- if (node.props?.index !== normalizedProps.index || node.props?.text !== normalizedProps.text || node.props?.html !== normalizedProps.html) {
488
+ const incoming = { index: idx, text: node.props?.text, html: node.props?.html };
489
+ if (Object.prototype.hasOwnProperty.call(node.props ?? {}, "tokens")) {
490
+ incoming.tokens = node.props.tokens;
491
+ }
492
+ const normalizedProps = normalizeCodeLineProps(incoming, node.props);
493
+ if (normalizedProps !== node.props) {
309
494
  node.props = normalizedProps;
310
495
  node.version++;
311
496
  touched.add(node.id);
@@ -351,43 +536,68 @@ function normalizeCodeBlockChildren(nodes, parent, touched) {
351
536
  children: parent.children.slice()
352
537
  });
353
538
  }
354
- const validation = validateCodeBlockChildren(nodes, parent);
355
- if (!validation.ok) {
356
- debugCodeBlock("code-block-validation-failed", {
357
- parentId: parent.id,
358
- issues: validation.issues
359
- });
360
- if (!rebuildCodeBlockFromSnapshot(nodes, parent, touched, validation.issues)) {
361
- console.warn("[renderer-store] unable to fully normalize code block", {
539
+ if (CODE_BLOCK_VALIDATION_ENABLED) {
540
+ const validation = validateCodeBlockChildren(nodes, parent);
541
+ if (!validation.ok) {
542
+ debugCodeBlock("code-block-validation-failed", {
362
543
  parentId: parent.id,
363
544
  issues: validation.issues
364
545
  });
546
+ if (!rebuildCodeBlockFromSnapshot(nodes, parent, touched, validation.issues)) {
547
+ console.warn("[renderer-store] unable to fully normalize code block", {
548
+ parentId: parent.id,
549
+ issues: validation.issues
550
+ });
551
+ }
365
552
  }
366
553
  }
367
554
  }
368
555
  function normalizeCodeLineProps(incoming, previous) {
369
- const index = typeof incoming.index === "number" ? incoming.index : typeof previous?.index === "number" ? previous?.index : 0;
556
+ const previousIndex = typeof previous?.index === "number" ? previous?.index : 0;
557
+ const index = typeof incoming.index === "number" ? incoming.index : previousIndex;
370
558
  const previousText = typeof previous?.text === "string" ? previous?.text : "";
371
559
  const previousHtml = typeof previous?.html === "string" ? previous?.html : null;
560
+ const previousTokens = Object.prototype.hasOwnProperty.call(previous ?? {}, "tokens") ? previous?.tokens : void 0;
372
561
  const hasIncomingText = typeof incoming.text === "string";
562
+ const hasIncomingHtml = typeof incoming.html === "string";
563
+ const hasIncomingTokens = Object.prototype.hasOwnProperty.call(incoming, "tokens");
564
+ const incomingTokens = hasIncomingTokens ? incoming.tokens : void 0;
373
565
  const rawText = hasIncomingText ? incoming.text : previousText;
566
+ if (previous && !hasIncomingHtml && !hasIncomingTokens && index === previousIndex && (!hasIncomingText || rawText === previousText)) {
567
+ return previous;
568
+ }
569
+ if (previous && hasIncomingHtml && !hasIncomingTokens && incoming.html === previousHtml && index === previousIndex && (!hasIncomingText || rawText === previousText)) {
570
+ return previous;
571
+ }
374
572
  let highlight = null;
375
- if (typeof incoming.html === "string") {
573
+ if (hasIncomingHtml) {
376
574
  highlight = incoming.html;
377
575
  } else if (!hasIncomingText || rawText === previousText) {
378
576
  highlight = previousHtml;
379
577
  }
380
- return createCodeLineProps(index, rawText, highlight);
578
+ let tokens = void 0;
579
+ if (hasIncomingTokens) {
580
+ tokens = incomingTokens ?? null;
581
+ } else if (!hasIncomingText || rawText === previousText) {
582
+ tokens = previousTokens;
583
+ } else {
584
+ tokens = null;
585
+ }
586
+ return createCodeLineProps(index, rawText, highlight, tokens);
381
587
  }
382
- function createCodeLineProps(index, text, highlight) {
588
+ function createCodeLineProps(index, text, highlight, tokens) {
383
589
  const safeText = typeof text === "string" ? text : "";
384
590
  const sanitized = sanitizeLineInnerHtml(highlight, safeText);
385
591
  const html = sanitized && sanitized.length >= safeText.length ? sanitized : escapeHtml(safeText);
386
- return {
592
+ const props = {
387
593
  index,
388
594
  text: safeText,
389
595
  html
390
596
  };
597
+ if (tokens !== void 0) {
598
+ props.tokens = tokens;
599
+ }
600
+ return props;
391
601
  }
392
602
  function clampIndex(index, length) {
393
603
  if (!Number.isFinite(index)) return 0;
@@ -429,8 +639,13 @@ function mergeInlineSegmentProps(previous, incoming) {
429
639
  function applyCodeBlockMetadata(record) {
430
640
  const block = record.block;
431
641
  const highlightedHtml = block?.payload.highlightedHtml ?? "";
432
- const { preAttrs, codeAttrs } = (0, import_core3.extractCodeWrapperAttributes)(highlightedHtml);
433
642
  const lang = typeof block?.payload.meta?.lang === "string" ? block?.payload.meta?.lang : record.props?.lang;
643
+ let { preAttrs, codeAttrs } = (0, import_core2.extractCodeWrapperAttributes)(highlightedHtml);
644
+ if (!preAttrs || !codeAttrs) {
645
+ const defaults = (0, import_core2.getDefaultCodeWrapperAttributes)(typeof lang === "string" ? lang : void 0);
646
+ preAttrs = preAttrs ?? defaults.preAttrs;
647
+ codeAttrs = codeAttrs ?? defaults.codeAttrs;
648
+ }
434
649
  record.props = {
435
650
  ...record.props ?? {},
436
651
  lang,
@@ -443,7 +658,8 @@ function normalizeNodeProps(snapshot) {
443
658
  const index = typeof snapshot.props?.index === "number" ? snapshot.props?.index : 0;
444
659
  const text = typeof snapshot.props?.text === "string" ? snapshot.props?.text : "";
445
660
  const html = typeof snapshot.props?.html === "string" ? snapshot.props?.html : null;
446
- return createCodeLineProps(index, text, html);
661
+ const tokens = Object.prototype.hasOwnProperty.call(snapshot.props ?? {}, "tokens") ? snapshot.props?.tokens : void 0;
662
+ return createCodeLineProps(index, text, html, tokens);
447
663
  }
448
664
  if (isInlineSegmentType(snapshot.type)) {
449
665
  const incoming = snapshot.props ? { ...snapshot.props } : {};
@@ -596,8 +812,8 @@ function resolveParent(map, path) {
596
812
  if (path.nodeId) {
597
813
  return map.get(path.nodeId);
598
814
  }
599
- if (path.blockId === import_core5.PATCH_ROOT_ID) {
600
- return map.get(import_core5.PATCH_ROOT_ID);
815
+ if (path.blockId === import_core2.PATCH_ROOT_ID) {
816
+ return map.get(import_core2.PATCH_ROOT_ID);
601
817
  }
602
818
  return map.get(path.blockId);
603
819
  }
@@ -695,7 +911,7 @@ function createRendererStore(initialBlocks = []) {
695
911
  const snapshot = (0, import_core2.createBlockSnapshot)(block);
696
912
  insertSnapshotAt(nodes, root, root.children.length, snapshot);
697
913
  }
698
- normalizeAllListDepths(nodes, /* @__PURE__ */ new Set(), import_core5.PATCH_ROOT_ID);
914
+ normalizeAllListDepths(nodes, /* @__PURE__ */ new Set(), import_core2.PATCH_ROOT_ID);
699
915
  version++;
700
916
  blocksDirty = true;
701
917
  scheduleNotify();
@@ -711,6 +927,13 @@ function createRendererStore(initialBlocks = []) {
711
927
  lastCoalescingMetrics = options?.metrics ?? null;
712
928
  return touched;
713
929
  }
930
+ const perfEnabled = isPatchPerfDebugEnabled();
931
+ const perfStart = perfEnabled ? getPerfNow() : 0;
932
+ const opStats = perfEnabled ? {} : {};
933
+ const setPropsByType = perfEnabled ? {} : {};
934
+ const setPropsBySize = perfEnabled ? {} : {};
935
+ let applyMs = 0;
936
+ let listNormalizeMs = 0;
714
937
  let coalescedPatches = patches;
715
938
  let metrics = options?.metrics ?? null;
716
939
  if (!options?.coalesced) {
@@ -719,38 +942,117 @@ function createRendererStore(initialBlocks = []) {
719
942
  metrics = result.metrics;
720
943
  }
721
944
  lastCoalescingMetrics = metrics;
722
- const root = nodes.get(import_core5.PATCH_ROOT_ID);
945
+ const root = nodes.get(import_core2.PATCH_ROOT_ID);
723
946
  if (!root) {
724
- nodes.set(import_core5.PATCH_ROOT_ID, createRootRecord());
947
+ nodes.set(import_core2.PATCH_ROOT_ID, createRootRecord());
725
948
  }
726
949
  let mutatedBlocks = false;
727
950
  let listDepthDirty = false;
951
+ const listDepthDirtyNodes = /* @__PURE__ */ new Set();
952
+ const debugListPatches = isListPatchDebugEnabled();
953
+ const isListRelated = (type) => type === "list" || type === "list-item";
954
+ const logListPatch = (label, details) => {
955
+ if (!debugListPatches) return;
956
+ try {
957
+ console.debug(`[list-patch] ${label}`, details);
958
+ } catch {
959
+ }
960
+ };
961
+ const findNearestListNode = (nodeId) => {
962
+ if (!nodeId) return void 0;
963
+ let current = nodes.get(nodeId);
964
+ while (current) {
965
+ if (current.type === "list") return current;
966
+ if (!current.parentId) return void 0;
967
+ current = nodes.get(current.parentId);
968
+ }
969
+ return void 0;
970
+ };
971
+ const markListDepthDirty = (nodeId) => {
972
+ const listNode = findNearestListNode(nodeId);
973
+ if (listNode) {
974
+ listDepthDirtyNodes.add(listNode.id);
975
+ } else {
976
+ listDepthDirty = true;
977
+ }
978
+ };
728
979
  const applyPropsUpdate = (at, props) => {
980
+ const perfStartLocal = perfEnabled ? getPerfNow() : 0;
729
981
  const targetId = at.nodeId ?? at.blockId;
730
982
  if (!targetId) return;
731
983
  const target = nodes.get(targetId);
732
984
  if (!target) return;
733
- const nextProps = props ? { ...props } : {};
734
- const incomingBlock = nextProps.block;
735
- if (incomingBlock !== void 0) {
736
- nextProps.block = void 0;
985
+ const parentType = target.parentId ? nodes.get(target.parentId)?.type : void 0;
986
+ if (debugListPatches && (isListRelated(target.type) || isListRelated(parentType))) {
987
+ logListPatch("setProps", {
988
+ id: target.id,
989
+ type: target.type,
990
+ parentType,
991
+ keys: props ? Object.keys(props) : []
992
+ });
737
993
  }
738
994
  let propsChanged = false;
739
995
  if (target.type === "code-line") {
740
- const normalized = normalizeCodeLineProps(nextProps, target.props);
741
- if (!shallowEqualRecords(target.props, normalized)) {
996
+ const incoming = {
997
+ index: props?.index,
998
+ text: props?.text,
999
+ html: props?.html
1000
+ };
1001
+ if (Object.prototype.hasOwnProperty.call(props ?? {}, "tokens")) {
1002
+ incoming.tokens = props.tokens;
1003
+ }
1004
+ const normalized = normalizeCodeLineProps(incoming, target.props);
1005
+ if (normalized !== target.props) {
742
1006
  target.props = normalized;
743
1007
  propsChanged = true;
744
1008
  }
1009
+ if (propsChanged) {
1010
+ target.version++;
1011
+ touched.add(target.id);
1012
+ const parent = target.parentId ? nodes.get(target.parentId) : void 0;
1013
+ if (parent) {
1014
+ parent.version++;
1015
+ touched.add(parent.id);
1016
+ if (parent.type === "code") {
1017
+ normalizeCodeBlockChildren(nodes, parent, touched);
1018
+ }
1019
+ }
1020
+ }
1021
+ if (perfEnabled) {
1022
+ const duration = getPerfNow() - perfStartLocal;
1023
+ const typeKey = target.type || "unknown";
1024
+ const sizeKey = bucketBySize(estimatePropsSize(props, void 0));
1025
+ const typeEntry = setPropsByType[typeKey] ?? { count: 0, durationMs: 0 };
1026
+ typeEntry.count += 1;
1027
+ typeEntry.durationMs += duration;
1028
+ setPropsByType[typeKey] = typeEntry;
1029
+ const sizeEntry = setPropsBySize[sizeKey] ?? { count: 0, durationMs: 0 };
1030
+ sizeEntry.count += 1;
1031
+ sizeEntry.durationMs += duration;
1032
+ setPropsBySize[sizeKey] = sizeEntry;
1033
+ }
1034
+ return;
745
1035
  } else if (isInlineSegmentType(target.type)) {
1036
+ const nextProps = props ? { ...props } : {};
1037
+ if ("block" in nextProps) {
1038
+ delete nextProps.block;
1039
+ }
746
1040
  const mergedSegments = mergeInlineSegmentProps(target.props, nextProps);
747
1041
  if (!shallowEqualRecords(target.props, mergedSegments)) {
748
1042
  target.props = mergedSegments;
749
1043
  propsChanged = true;
750
1044
  }
751
1045
  } else {
1046
+ const nextProps = props ? { ...props } : {};
1047
+ if ("block" in nextProps) {
1048
+ delete nextProps.block;
1049
+ }
752
1050
  const base = target.props ? { ...target.props } : {};
753
1051
  let localChanged = false;
1052
+ if (Object.prototype.hasOwnProperty.call(base, "block")) {
1053
+ delete base.block;
1054
+ localChanged = true;
1055
+ }
754
1056
  for (const [key, value] of Object.entries(nextProps)) {
755
1057
  if (value === void 0) {
756
1058
  if (Object.prototype.hasOwnProperty.call(base, key)) {
@@ -768,6 +1070,7 @@ function createRendererStore(initialBlocks = []) {
768
1070
  }
769
1071
  }
770
1072
  let blockChanged = false;
1073
+ const incomingBlock = props?.block;
771
1074
  if (incomingBlock && typeof incomingBlock === "object") {
772
1075
  target.block = (0, import_core2.cloneBlock)(incomingBlock);
773
1076
  if (target.type === "code") {
@@ -797,13 +1100,26 @@ function createRendererStore(initialBlocks = []) {
797
1100
  } else if (parentForNormalization) {
798
1101
  normalizeCodeBlockChildren(nodes, parentForNormalization, touched);
799
1102
  }
800
- const parentType = target.parentId ? nodes.get(target.parentId)?.type : void 0;
801
- if (target.type === "list" || target.type === "list-item" || parentType === "list" || parentType === "list-item") {
802
- listDepthDirty = true;
1103
+ const parentType2 = target.parentId ? nodes.get(target.parentId)?.type : void 0;
1104
+ if (isListRelated(target.type) || isListRelated(parentType2)) {
1105
+ markListDepthDirty(target.id);
803
1106
  }
804
1107
  }
1108
+ if (perfEnabled) {
1109
+ const duration = getPerfNow() - perfStartLocal;
1110
+ const typeKey = target.type || "unknown";
1111
+ const sizeKey = bucketBySize(estimatePropsSize(props, incomingBlock && typeof incomingBlock === "object" ? incomingBlock : void 0));
1112
+ const typeEntry = setPropsByType[typeKey] ?? { count: 0, durationMs: 0 };
1113
+ typeEntry.count += 1;
1114
+ typeEntry.durationMs += duration;
1115
+ setPropsByType[typeKey] = typeEntry;
1116
+ const sizeEntry = setPropsBySize[sizeKey] ?? { count: 0, durationMs: 0 };
1117
+ sizeEntry.count += 1;
1118
+ sizeEntry.durationMs += duration;
1119
+ setPropsBySize[sizeKey] = sizeEntry;
1120
+ }
805
1121
  };
806
- for (const patch of coalescedPatches) {
1122
+ const applyPatch = (patch) => {
807
1123
  switch (patch.op) {
808
1124
  case "insertChild": {
809
1125
  const parent = resolveParent(nodes, patch.at);
@@ -813,8 +1129,20 @@ function createRendererStore(initialBlocks = []) {
813
1129
  touched.add(id);
814
1130
  }
815
1131
  mutatedBlocks = true;
816
- if (inserted.record.type === "list" || inserted.record.type === "list-item" || parent && (parent.type === "list" || parent.type === "list-item")) {
817
- listDepthDirty = true;
1132
+ if (isListRelated(inserted.record.type) || isListRelated(parent?.type)) {
1133
+ markListDepthDirty(inserted.record.id);
1134
+ if (parent) {
1135
+ markListDepthDirty(parent.id);
1136
+ }
1137
+ }
1138
+ if (debugListPatches && (isListRelated(inserted.record.type) || isListRelated(parent?.type))) {
1139
+ logListPatch("insertChild", {
1140
+ id: inserted.record.id,
1141
+ type: inserted.record.type,
1142
+ parentId: parent?.id,
1143
+ parentType: parent?.type,
1144
+ index: patch.index
1145
+ });
818
1146
  }
819
1147
  }
820
1148
  break;
@@ -831,8 +1159,17 @@ function createRendererStore(initialBlocks = []) {
831
1159
  touched.add(parent.id);
832
1160
  ensureUniqueChildren(parent, touched);
833
1161
  mutatedBlocks = true;
834
- if (parent.type === "list" || parent.type === "list-item" || removedNode?.type === "list" || removedNode?.type === "list-item") {
835
- listDepthDirty = true;
1162
+ if (isListRelated(parent.type) || isListRelated(removedNode?.type)) {
1163
+ markListDepthDirty(parent.id);
1164
+ }
1165
+ if (debugListPatches && (isListRelated(parent.type) || isListRelated(removedNode?.type))) {
1166
+ logListPatch("deleteChild", {
1167
+ id: childId,
1168
+ type: removedNode?.type,
1169
+ parentId: parent.id,
1170
+ parentType: parent.type,
1171
+ index: patch.index
1172
+ });
836
1173
  }
837
1174
  }
838
1175
  break;
@@ -843,8 +1180,15 @@ function createRendererStore(initialBlocks = []) {
843
1180
  replaceSnapshotAt(nodes, parent, patch.index, patch.node, touched);
844
1181
  touched.add(parent.id);
845
1182
  mutatedBlocks = true;
846
- if (parent.type === "list" || parent.type === "list-item") {
847
- listDepthDirty = true;
1183
+ if (isListRelated(parent.type)) {
1184
+ markListDepthDirty(parent.id);
1185
+ }
1186
+ if (debugListPatches && isListRelated(parent.type)) {
1187
+ logListPatch("replaceChild", {
1188
+ parentId: parent.id,
1189
+ parentType: parent.type,
1190
+ index: patch.index
1191
+ });
848
1192
  }
849
1193
  break;
850
1194
  }
@@ -870,6 +1214,15 @@ function createRendererStore(initialBlocks = []) {
870
1214
  touched.add(target.id);
871
1215
  mutatedBlocks = true;
872
1216
  }
1217
+ if (isListRelated(target.type)) {
1218
+ markListDepthDirty(target.id);
1219
+ }
1220
+ if (debugListPatches && isListRelated(target.type)) {
1221
+ logListPatch("finalize", {
1222
+ id: target.id,
1223
+ type: target.type
1224
+ });
1225
+ }
873
1226
  break;
874
1227
  }
875
1228
  case "reorder": {
@@ -887,8 +1240,17 @@ function createRendererStore(initialBlocks = []) {
887
1240
  parent.children.splice(to, 0, ...moved);
888
1241
  parent.version++;
889
1242
  touched.add(parent.id);
890
- if (parent.type === "list" || parent.type === "list-item") {
891
- listDepthDirty = true;
1243
+ if (isListRelated(parent.type)) {
1244
+ markListDepthDirty(parent.id);
1245
+ }
1246
+ if (debugListPatches && isListRelated(parent.type)) {
1247
+ logListPatch("reorder", {
1248
+ parentId: parent.id,
1249
+ parentType: parent.type,
1250
+ from: patch.from,
1251
+ to: patch.to,
1252
+ count: patch.count
1253
+ });
892
1254
  }
893
1255
  break;
894
1256
  }
@@ -909,7 +1271,14 @@ function createRendererStore(initialBlocks = []) {
909
1271
  const lineId = `${parent.id}::line:${absoluteIndex}`;
910
1272
  insertedIds.push(lineId);
911
1273
  insertedSet.add(lineId);
912
- const nextLineProps = createCodeLineProps(absoluteIndex, patch.lines[offset] ?? "", patch.highlight?.[offset] ?? null);
1274
+ const hasTokens = Object.prototype.hasOwnProperty.call(patch, "tokens");
1275
+ const nextTokens = hasTokens ? patch.tokens?.[offset] ?? null : void 0;
1276
+ const nextLineProps = createCodeLineProps(
1277
+ absoluteIndex,
1278
+ patch.lines[offset] ?? "",
1279
+ patch.highlight?.[offset] ?? null,
1280
+ nextTokens
1281
+ );
913
1282
  const existing = nodes.get(lineId);
914
1283
  if (existing && existing.type === "code-line") {
915
1284
  const existingIndex = parent.children.indexOf(lineId);
@@ -962,8 +1331,12 @@ function createRendererStore(initialBlocks = []) {
962
1331
  const childId = parent.children[idx];
963
1332
  const child = nodes.get(childId);
964
1333
  if (child && child.type === "code-line") {
965
- const normalized = normalizeCodeLineProps({ index: idx, text: child.props?.text, html: child.props?.html }, child.props);
966
- if (!shallowEqualRecords(child.props, normalized)) {
1334
+ const incoming = { index: idx, text: child.props?.text, html: child.props?.html };
1335
+ if (Object.prototype.hasOwnProperty.call(child.props ?? {}, "tokens")) {
1336
+ incoming.tokens = child.props.tokens;
1337
+ }
1338
+ const normalized = normalizeCodeLineProps(incoming, child.props);
1339
+ if (normalized !== child.props) {
967
1340
  child.props = normalized;
968
1341
  child.version++;
969
1342
  touched.add(child.id);
@@ -984,7 +1357,7 @@ function createRendererStore(initialBlocks = []) {
984
1357
  case "setHTML": {
985
1358
  const target = nodes.get(patch.at.nodeId ?? patch.at.blockId);
986
1359
  if (!target) break;
987
- const sanitized = patch.sanitized ? patch.html : (0, import_core4.sanitizeHTML)(patch.html);
1360
+ const sanitized = patch.sanitized ? patch.html : (0, import_core2.sanitizeHTML)(patch.html);
988
1361
  target.props = {
989
1362
  ...target.props ?? {},
990
1363
  html: sanitized,
@@ -1006,24 +1379,73 @@ function createRendererStore(initialBlocks = []) {
1006
1379
  default:
1007
1380
  break;
1008
1381
  }
1382
+ };
1383
+ if (perfEnabled) {
1384
+ const loopStart = getPerfNow();
1385
+ for (const patch of coalescedPatches) {
1386
+ const opStart = getPerfNow();
1387
+ applyPatch(patch);
1388
+ const opDuration = getPerfNow() - opStart;
1389
+ const entry = opStats[patch.op] ?? { count: 0, durationMs: 0 };
1390
+ entry.count += 1;
1391
+ entry.durationMs += opDuration;
1392
+ opStats[patch.op] = entry;
1393
+ }
1394
+ applyMs = getPerfNow() - loopStart;
1395
+ } else {
1396
+ for (const patch of coalescedPatches) {
1397
+ applyPatch(patch);
1398
+ }
1009
1399
  }
1010
1400
  if (mutatedBlocks) {
1011
1401
  blocksDirty = true;
1012
1402
  }
1013
1403
  if (listDepthDirty) {
1014
- normalizeAllListDepths(nodes, touched, import_core5.PATCH_ROOT_ID);
1404
+ if (perfEnabled) {
1405
+ const start = getPerfNow();
1406
+ normalizeAllListDepths(nodes, touched, import_core2.PATCH_ROOT_ID);
1407
+ listNormalizeMs = getPerfNow() - start;
1408
+ } else {
1409
+ normalizeAllListDepths(nodes, touched, import_core2.PATCH_ROOT_ID);
1410
+ }
1411
+ } else if (listDepthDirtyNodes.size > 0) {
1412
+ if (perfEnabled) {
1413
+ const start = getPerfNow();
1414
+ normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, import_core2.PATCH_ROOT_ID);
1415
+ listNormalizeMs = getPerfNow() - start;
1416
+ } else {
1417
+ normalizeListDepthsForIds(nodes, touched, listDepthDirtyNodes, import_core2.PATCH_ROOT_ID);
1418
+ }
1015
1419
  }
1016
1420
  if (touched.size > 0 || mutatedBlocks) {
1017
1421
  version++;
1018
1422
  scheduleNotify();
1019
1423
  }
1424
+ if (perfEnabled) {
1425
+ const totalMs = getPerfNow() - perfStart;
1426
+ const coalesceMs = typeof metrics?.durationMs === "number" ? metrics.durationMs : 0;
1427
+ recordPatchPerf({
1428
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1429
+ totalMs,
1430
+ applyMs,
1431
+ coalesceMs,
1432
+ listNormalizeMs,
1433
+ patches: patches.length,
1434
+ coalescedPatches: coalescedPatches.length,
1435
+ touchedCount: touched.size,
1436
+ mutatedBlocks,
1437
+ ops: opStats,
1438
+ setPropsByType,
1439
+ setPropsBySize
1440
+ });
1441
+ }
1020
1442
  return touched;
1021
1443
  },
1022
1444
  getBlocks() {
1023
1445
  if (!blocksDirty) {
1024
1446
  return blocksCache;
1025
1447
  }
1026
- const root = nodes.get(import_core5.PATCH_ROOT_ID);
1448
+ const root = nodes.get(import_core2.PATCH_ROOT_ID);
1027
1449
  if (!root) {
1028
1450
  blocksCache = [];
1029
1451
  } else {
@@ -1049,6 +1471,7 @@ function createRendererStore(initialBlocks = []) {
1049
1471
  }
1050
1472
  const snapshot = { version: record.version, node: record };
1051
1473
  nodeSnapshotCache.set(id, snapshot);
1474
+ pruneCache(nodeSnapshotCache, NODE_SNAPSHOT_CACHE_LIMIT, NODE_SNAPSHOT_CACHE_BUFFER);
1052
1475
  return snapshot;
1053
1476
  },
1054
1477
  getChildren(id) {
@@ -1062,11 +1485,12 @@ function createRendererStore(initialBlocks = []) {
1062
1485
  return EMPTY_CHILDREN_SNAPSHOT;
1063
1486
  }
1064
1487
  const cached = childrenSnapshotCache.get(id);
1065
- if (cached && cached.version === record.version && cached.children === record.children) {
1488
+ if (cached && cached.version === record.version) {
1066
1489
  return cached;
1067
1490
  }
1068
- const snapshot = { version: record.version, children: record.children };
1491
+ const snapshot = { version: record.version, children: record.children.slice() };
1069
1492
  childrenSnapshotCache.set(id, snapshot);
1493
+ pruneCache(childrenSnapshotCache, CHILDREN_SNAPSHOT_CACHE_LIMIT, CHILDREN_SNAPSHOT_CACHE_BUFFER);
1070
1494
  return snapshot;
1071
1495
  },
1072
1496
  getLastCoalescingMetrics() {
@@ -1083,5 +1507,6 @@ function createRendererStore(initialBlocks = []) {
1083
1507
  }
1084
1508
  // Annotate the CommonJS export names for ESM import in node:
1085
1509
  0 && (module.exports = {
1086
- createRendererStore
1510
+ createRendererStore,
1511
+ isListPatchDebugEnabled
1087
1512
  });