@stream-mdx/core 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.
@@ -80,23 +80,59 @@ function isHeavyPatch(patch) {
80
80
  return false;
81
81
  }
82
82
  }
83
+ function getPatchKind(patch) {
84
+ const explicitKind = patch.op === "setHTML" ? patch.patchMeta?.kind : patch.meta?.kind;
85
+ if (explicitKind === "semantic" || explicitKind === "enrichment") {
86
+ return explicitKind;
87
+ }
88
+ switch (patch.op) {
89
+ case "insertChild":
90
+ case "deleteChild":
91
+ case "replaceChild":
92
+ case "finalize":
93
+ case "reorder":
94
+ case "appendLines":
95
+ case "setHTML":
96
+ return "semantic";
97
+ case "setProps":
98
+ case "setPropsBatch":
99
+ return "semantic";
100
+ default:
101
+ return "semantic";
102
+ }
103
+ }
83
104
  function splitPatchBatch(patches, maxLightChunk = DEFAULT_MAX_LIGHT_PATCHES_PER_CHUNK) {
84
105
  if (patches.length === 0) return [];
85
106
  const groups = [];
86
107
  let current = [];
108
+ let currentMode = null;
87
109
  const flush = () => {
88
110
  if (current.length > 0) {
89
111
  groups.push(current);
90
112
  current = [];
113
+ currentMode = null;
91
114
  }
92
115
  };
93
116
  for (const patch of patches) {
117
+ const kind = getPatchKind(patch);
94
118
  const heavy = isHeavyPatch(patch);
119
+ if (kind === "semantic") {
120
+ if (currentMode !== "semantic") {
121
+ flush();
122
+ currentMode = "semantic";
123
+ }
124
+ current.push(patch);
125
+ continue;
126
+ }
95
127
  if (heavy) {
96
128
  flush();
97
129
  groups.push([patch]);
98
130
  continue;
99
131
  }
132
+ if (currentMode !== "enrichment") {
133
+ flush();
134
+ currentMode = "enrichment";
135
+ }
100
136
  current.push(patch);
101
137
  if (current.length >= maxLightChunk) {
102
138
  flush();
@@ -106,6 +142,7 @@ function splitPatchBatch(patches, maxLightChunk = DEFAULT_MAX_LIGHT_PATCHES_PER_
106
142
  return groups;
107
143
  }
108
144
  export {
145
+ getPatchKind,
109
146
  isHeavyPatch,
110
147
  splitPatchBatch
111
148
  };
@@ -289,7 +289,8 @@ function collectSetProps(window, startIndex) {
289
289
  }
290
290
  entries.push({
291
291
  at: cloneNodePath(current.at),
292
- props: mergedProps
292
+ props: mergedProps,
293
+ meta: current.meta ? { ...current.meta } : void 0
293
294
  });
294
295
  j = k;
295
296
  }
@@ -307,7 +308,8 @@ function collectSetProps(window, startIndex) {
307
308
  }
308
309
  const batchPatch = {
309
310
  op: "setPropsBatch",
310
- entries
311
+ entries,
312
+ meta: first.meta ? { ...first.meta } : void 0
311
313
  };
312
314
  return { patches: [batchPatch], nextIndex: j };
313
315
  }
@@ -402,7 +404,8 @@ function coalescePatchesQuadratic(patches, config = DEFAULT_COALESCE_CONFIG) {
402
404
  const batchEntries = [
403
405
  {
404
406
  at: cloneNodePath(current.at),
405
- props: mergedProps
407
+ props: mergedProps,
408
+ meta: current.meta ? { ...current.meta } : void 0
406
409
  }
407
410
  ];
408
411
  let k = j;
@@ -426,14 +429,16 @@ function coalescePatchesQuadratic(patches, config = DEFAULT_COALESCE_CONFIG) {
426
429
  }
427
430
  batchEntries.push({
428
431
  at: cloneNodePath(candidate.at),
429
- props: candidateMergedProps
432
+ props: candidateMergedProps,
433
+ meta: candidate.meta ? { ...candidate.meta } : void 0
430
434
  });
431
435
  k = m;
432
436
  }
433
437
  if (batchEntries.length > 1) {
434
438
  coalesced.push({
435
439
  op: "setPropsBatch",
436
- entries: batchEntries
440
+ entries: batchEntries,
441
+ meta: current.meta ? { ...current.meta } : void 0
437
442
  });
438
443
  i = k;
439
444
  continue;
@@ -261,7 +261,8 @@ function collectSetProps(window, startIndex) {
261
261
  }
262
262
  entries.push({
263
263
  at: cloneNodePath(current.at),
264
- props: mergedProps
264
+ props: mergedProps,
265
+ meta: current.meta ? { ...current.meta } : void 0
265
266
  });
266
267
  j = k;
267
268
  }
@@ -279,7 +280,8 @@ function collectSetProps(window, startIndex) {
279
280
  }
280
281
  const batchPatch = {
281
282
  op: "setPropsBatch",
282
- entries
283
+ entries,
284
+ meta: first.meta ? { ...first.meta } : void 0
283
285
  };
284
286
  return { patches: [batchPatch], nextIndex: j };
285
287
  }
@@ -374,7 +376,8 @@ function coalescePatchesQuadratic(patches, config = DEFAULT_COALESCE_CONFIG) {
374
376
  const batchEntries = [
375
377
  {
376
378
  at: cloneNodePath(current.at),
377
- props: mergedProps
379
+ props: mergedProps,
380
+ meta: current.meta ? { ...current.meta } : void 0
378
381
  }
379
382
  ];
380
383
  let k = j;
@@ -398,14 +401,16 @@ function coalescePatchesQuadratic(patches, config = DEFAULT_COALESCE_CONFIG) {
398
401
  }
399
402
  batchEntries.push({
400
403
  at: cloneNodePath(candidate.at),
401
- props: candidateMergedProps
404
+ props: candidateMergedProps,
405
+ meta: candidate.meta ? { ...candidate.meta } : void 0
402
406
  });
403
407
  k = m;
404
408
  }
405
409
  if (batchEntries.length > 1) {
406
410
  coalesced.push({
407
411
  op: "setPropsBatch",
408
- entries: batchEntries
412
+ entries: batchEntries,
413
+ meta: current.meta ? { ...current.meta } : void 0
409
414
  });
410
415
  i = k;
411
416
  continue;
@@ -65,6 +65,13 @@ function prepareInlineStreamingContent(content, options) {
65
65
  };
66
66
  let mathDisplayOpen = false;
67
67
  let mathDisplayCrossedNewline = false;
68
+ const shouldOpenInlineMath = (index) => {
69
+ const next = content[index + 1] ?? "";
70
+ if (/\d/.test(next)) {
71
+ return false;
72
+ }
73
+ return true;
74
+ };
68
75
  for (let i = 0; i < content.length; i++) {
69
76
  const code = content.charCodeAt(i);
70
77
  if (code === 10 || code === 13) {
@@ -103,7 +110,10 @@ function prepareInlineStreamingContent(content, options) {
103
110
  }
104
111
  i += 1;
105
112
  } else {
106
- toggleToken("math-inline");
113
+ const mathInlineOpen = stack.includes("math-inline");
114
+ if (mathInlineOpen || shouldOpenInlineMath(i)) {
115
+ toggleToken("math-inline");
116
+ }
107
117
  }
108
118
  }
109
119
  }
@@ -115,7 +125,7 @@ function prepareInlineStreamingContent(content, options) {
115
125
  if (hasIncompleteMathInline && !enableMathInlineAnticipation) {
116
126
  return { kind: "raw", status: "raw", reason: "incomplete-math" };
117
127
  }
118
- if (hasIncompleteMathDisplay && (!enableMathBlockAnticipation || mathDisplayCrossedNewline)) {
128
+ if (hasIncompleteMathDisplay && !enableMathBlockAnticipation) {
119
129
  return { kind: "raw", status: "raw", reason: "incomplete-math" };
120
130
  }
121
131
  }
@@ -138,7 +148,10 @@ function prepareInlineStreamingContent(content, options) {
138
148
  case "math-inline":
139
149
  return "$";
140
150
  case "math-display":
141
- return "$$";
151
+ if (!mathDisplayCrossedNewline) {
152
+ return "$$";
153
+ }
154
+ return content.endsWith("\n") || content.endsWith("\r") ? "$$" : "\n$$";
142
155
  default:
143
156
  return "";
144
157
  }
@@ -40,6 +40,13 @@ function prepareInlineStreamingContent(content, options) {
40
40
  };
41
41
  let mathDisplayOpen = false;
42
42
  let mathDisplayCrossedNewline = false;
43
+ const shouldOpenInlineMath = (index) => {
44
+ const next = content[index + 1] ?? "";
45
+ if (/\d/.test(next)) {
46
+ return false;
47
+ }
48
+ return true;
49
+ };
43
50
  for (let i = 0; i < content.length; i++) {
44
51
  const code = content.charCodeAt(i);
45
52
  if (code === 10 || code === 13) {
@@ -78,7 +85,10 @@ function prepareInlineStreamingContent(content, options) {
78
85
  }
79
86
  i += 1;
80
87
  } else {
81
- toggleToken("math-inline");
88
+ const mathInlineOpen = stack.includes("math-inline");
89
+ if (mathInlineOpen || shouldOpenInlineMath(i)) {
90
+ toggleToken("math-inline");
91
+ }
82
92
  }
83
93
  }
84
94
  }
@@ -90,7 +100,7 @@ function prepareInlineStreamingContent(content, options) {
90
100
  if (hasIncompleteMathInline && !enableMathInlineAnticipation) {
91
101
  return { kind: "raw", status: "raw", reason: "incomplete-math" };
92
102
  }
93
- if (hasIncompleteMathDisplay && (!enableMathBlockAnticipation || mathDisplayCrossedNewline)) {
103
+ if (hasIncompleteMathDisplay && !enableMathBlockAnticipation) {
94
104
  return { kind: "raw", status: "raw", reason: "incomplete-math" };
95
105
  }
96
106
  }
@@ -113,7 +123,10 @@ function prepareInlineStreamingContent(content, options) {
113
123
  case "math-inline":
114
124
  return "$";
115
125
  case "math-display":
116
- return "$$";
126
+ if (!mathDisplayCrossedNewline) {
127
+ return "$$";
128
+ }
129
+ return content.endsWith("\n") || content.endsWith("\r") ? "$$" : "\n$$";
117
130
  default:
118
131
  return "";
119
132
  }
package/dist/types.d.cts CHANGED
@@ -189,6 +189,8 @@ type WorkerIn = {
189
189
  type: "FINALIZE";
190
190
  } | {
191
191
  type: "DEBUG_STATE";
192
+ } | {
193
+ type: "DUMP_BLOCKS";
192
194
  } | {
193
195
  type: "TOKENIZE_RANGE";
194
196
  blockId: string;
@@ -199,10 +201,12 @@ type WorkerIn = {
199
201
  type: "MDX_COMPILED";
200
202
  blockId: string;
201
203
  compiledId: string;
204
+ rawSignature?: string;
202
205
  } | {
203
206
  type: "MDX_ERROR";
204
207
  blockId: string;
205
208
  error?: string;
209
+ rawSignature?: string;
206
210
  } | {
207
211
  type: "SET_CREDITS";
208
212
  credits: number;
@@ -216,6 +220,8 @@ interface WorkerErrorPayload {
216
220
  type WorkerOut = {
217
221
  type: "INITIALIZED";
218
222
  blocks: Block[];
223
+ } | {
224
+ type: "FINALIZED";
219
225
  } | {
220
226
  type: "PATCH";
221
227
  tx: number;
@@ -227,6 +233,10 @@ type WorkerOut = {
227
233
  } | {
228
234
  type: "METRICS";
229
235
  metrics: PerformanceMetrics;
236
+ } | {
237
+ type: "DUMP_BLOCKS";
238
+ blocks: Block[];
239
+ tocHeadings?: TocHeading[];
230
240
  } | {
231
241
  type: "DEBUG_STATE";
232
242
  state: {
@@ -348,36 +358,68 @@ interface NodePath {
348
358
  nodeId?: string;
349
359
  indexPath?: number[];
350
360
  }
361
+ type PatchKind = "semantic" | "enrichment";
362
+ interface PatchMeta {
363
+ /**
364
+ * Explicit correctness class for this patch.
365
+ * - `semantic`: changes visible meaning, structure, ordering, or finalized content
366
+ * - `enrichment`: decorates already-correct semantic output
367
+ *
368
+ * Ambiguous patches should default to `semantic`.
369
+ */
370
+ kind?: PatchKind;
371
+ /**
372
+ * Reserved for epoch-aware targeting and stale-patch rejection.
373
+ * These fields are optional until the semantic-envelope workstream is complete.
374
+ */
375
+ streamSeq?: number;
376
+ parseEpoch?: number;
377
+ tx?: number;
378
+ blockEpoch?: number;
379
+ }
380
+ interface TocHeading {
381
+ id: string;
382
+ text: string;
383
+ level: number;
384
+ blockId: string;
385
+ }
351
386
  type Patch = {
352
387
  op: "insertChild";
353
388
  at: NodePath;
354
389
  index: number;
355
390
  node: NodeSnapshot;
391
+ meta?: PatchMeta;
356
392
  } | {
357
393
  op: "deleteChild";
358
394
  at: NodePath;
359
395
  index: number;
396
+ meta?: PatchMeta;
360
397
  } | {
361
398
  op: "replaceChild";
362
399
  at: NodePath;
363
400
  index: number;
364
401
  node: NodeSnapshot;
402
+ meta?: PatchMeta;
365
403
  } | {
366
404
  op: "setProps";
367
405
  at: NodePath;
368
406
  props: Record<string, unknown>;
407
+ meta?: PatchMeta;
369
408
  } | {
370
409
  op: "setPropsBatch";
371
410
  entries: SetPropsBatchEntry[];
411
+ meta?: PatchMeta;
372
412
  } | {
373
413
  op: "finalize";
374
414
  at: NodePath;
415
+ meta?: PatchMeta;
375
416
  } | {
376
417
  op: "reorder";
377
418
  at: NodePath;
378
419
  from: number;
379
420
  to: number;
380
421
  count: number;
422
+ meta?: PatchMeta;
381
423
  } | {
382
424
  op: "appendLines";
383
425
  at: NodePath;
@@ -388,6 +430,7 @@ type Patch = {
388
430
  diffKind?: Array<DiffKind | null>;
389
431
  oldNo?: Array<number | null>;
390
432
  newNo?: Array<number | null>;
433
+ meta?: PatchMeta;
391
434
  } | {
392
435
  op: "setHTML";
393
436
  at: NodePath;
@@ -396,6 +439,7 @@ type Patch = {
396
439
  block?: Block;
397
440
  meta?: Record<string, unknown>;
398
441
  sanitized?: boolean;
442
+ patchMeta?: PatchMeta;
399
443
  };
400
444
  interface PatchMetrics {
401
445
  patchCount: number;
@@ -412,6 +456,7 @@ interface PatchMetrics {
412
456
  interface SetPropsBatchEntry {
413
457
  at: NodePath;
414
458
  props: Record<string, unknown>;
459
+ meta?: PatchMeta;
415
460
  }
416
461
  interface CoalescingMetrics {
417
462
  inputPatchCount: number;
@@ -423,4 +468,4 @@ interface CoalescingMetrics {
423
468
  insertChildCoalesced: number;
424
469
  }
425
470
 
426
- export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CodeHighlightOutputMode, type CodeHighlightingMode, type CompiledMdxModule, type DiffBlock, type DiffKind, type DiffLine, type DiffLineKind, type FormatAnticipationConfig, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type LazyTokenizationPriority, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexAnticipationPattern, type RegexInlinePlugin, type SetPropsBatchEntry, type ThemedLine, type ThemedToken, type TokenLineV1, type TokenSpan, type TokenStyle, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
471
+ export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CodeHighlightOutputMode, type CodeHighlightingMode, type CompiledMdxModule, type DiffBlock, type DiffKind, type DiffLine, type DiffLineKind, type FormatAnticipationConfig, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type LazyTokenizationPriority, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchKind, type PatchMeta, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexAnticipationPattern, type RegexInlinePlugin, type SetPropsBatchEntry, type ThemedLine, type ThemedToken, type TocHeading, type TokenLineV1, type TokenSpan, type TokenStyle, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
package/dist/types.d.ts CHANGED
@@ -189,6 +189,8 @@ type WorkerIn = {
189
189
  type: "FINALIZE";
190
190
  } | {
191
191
  type: "DEBUG_STATE";
192
+ } | {
193
+ type: "DUMP_BLOCKS";
192
194
  } | {
193
195
  type: "TOKENIZE_RANGE";
194
196
  blockId: string;
@@ -199,10 +201,12 @@ type WorkerIn = {
199
201
  type: "MDX_COMPILED";
200
202
  blockId: string;
201
203
  compiledId: string;
204
+ rawSignature?: string;
202
205
  } | {
203
206
  type: "MDX_ERROR";
204
207
  blockId: string;
205
208
  error?: string;
209
+ rawSignature?: string;
206
210
  } | {
207
211
  type: "SET_CREDITS";
208
212
  credits: number;
@@ -216,6 +220,8 @@ interface WorkerErrorPayload {
216
220
  type WorkerOut = {
217
221
  type: "INITIALIZED";
218
222
  blocks: Block[];
223
+ } | {
224
+ type: "FINALIZED";
219
225
  } | {
220
226
  type: "PATCH";
221
227
  tx: number;
@@ -227,6 +233,10 @@ type WorkerOut = {
227
233
  } | {
228
234
  type: "METRICS";
229
235
  metrics: PerformanceMetrics;
236
+ } | {
237
+ type: "DUMP_BLOCKS";
238
+ blocks: Block[];
239
+ tocHeadings?: TocHeading[];
230
240
  } | {
231
241
  type: "DEBUG_STATE";
232
242
  state: {
@@ -348,36 +358,68 @@ interface NodePath {
348
358
  nodeId?: string;
349
359
  indexPath?: number[];
350
360
  }
361
+ type PatchKind = "semantic" | "enrichment";
362
+ interface PatchMeta {
363
+ /**
364
+ * Explicit correctness class for this patch.
365
+ * - `semantic`: changes visible meaning, structure, ordering, or finalized content
366
+ * - `enrichment`: decorates already-correct semantic output
367
+ *
368
+ * Ambiguous patches should default to `semantic`.
369
+ */
370
+ kind?: PatchKind;
371
+ /**
372
+ * Reserved for epoch-aware targeting and stale-patch rejection.
373
+ * These fields are optional until the semantic-envelope workstream is complete.
374
+ */
375
+ streamSeq?: number;
376
+ parseEpoch?: number;
377
+ tx?: number;
378
+ blockEpoch?: number;
379
+ }
380
+ interface TocHeading {
381
+ id: string;
382
+ text: string;
383
+ level: number;
384
+ blockId: string;
385
+ }
351
386
  type Patch = {
352
387
  op: "insertChild";
353
388
  at: NodePath;
354
389
  index: number;
355
390
  node: NodeSnapshot;
391
+ meta?: PatchMeta;
356
392
  } | {
357
393
  op: "deleteChild";
358
394
  at: NodePath;
359
395
  index: number;
396
+ meta?: PatchMeta;
360
397
  } | {
361
398
  op: "replaceChild";
362
399
  at: NodePath;
363
400
  index: number;
364
401
  node: NodeSnapshot;
402
+ meta?: PatchMeta;
365
403
  } | {
366
404
  op: "setProps";
367
405
  at: NodePath;
368
406
  props: Record<string, unknown>;
407
+ meta?: PatchMeta;
369
408
  } | {
370
409
  op: "setPropsBatch";
371
410
  entries: SetPropsBatchEntry[];
411
+ meta?: PatchMeta;
372
412
  } | {
373
413
  op: "finalize";
374
414
  at: NodePath;
415
+ meta?: PatchMeta;
375
416
  } | {
376
417
  op: "reorder";
377
418
  at: NodePath;
378
419
  from: number;
379
420
  to: number;
380
421
  count: number;
422
+ meta?: PatchMeta;
381
423
  } | {
382
424
  op: "appendLines";
383
425
  at: NodePath;
@@ -388,6 +430,7 @@ type Patch = {
388
430
  diffKind?: Array<DiffKind | null>;
389
431
  oldNo?: Array<number | null>;
390
432
  newNo?: Array<number | null>;
433
+ meta?: PatchMeta;
391
434
  } | {
392
435
  op: "setHTML";
393
436
  at: NodePath;
@@ -396,6 +439,7 @@ type Patch = {
396
439
  block?: Block;
397
440
  meta?: Record<string, unknown>;
398
441
  sanitized?: boolean;
442
+ patchMeta?: PatchMeta;
399
443
  };
400
444
  interface PatchMetrics {
401
445
  patchCount: number;
@@ -412,6 +456,7 @@ interface PatchMetrics {
412
456
  interface SetPropsBatchEntry {
413
457
  at: NodePath;
414
458
  props: Record<string, unknown>;
459
+ meta?: PatchMeta;
415
460
  }
416
461
  interface CoalescingMetrics {
417
462
  inputPatchCount: number;
@@ -423,4 +468,4 @@ interface CoalescingMetrics {
423
468
  insertChildCoalesced: number;
424
469
  }
425
470
 
426
- export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CodeHighlightOutputMode, type CodeHighlightingMode, type CompiledMdxModule, type DiffBlock, type DiffKind, type DiffLine, type DiffLineKind, type FormatAnticipationConfig, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type LazyTokenizationPriority, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexAnticipationPattern, type RegexInlinePlugin, type SetPropsBatchEntry, type ThemedLine, type ThemedToken, type TokenLineV1, type TokenSpan, type TokenStyle, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
471
+ export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CodeHighlightOutputMode, type CodeHighlightingMode, type CompiledMdxModule, type DiffBlock, type DiffKind, type DiffLine, type DiffLineKind, type FormatAnticipationConfig, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type LazyTokenizationPriority, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchKind, type PatchMeta, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexAnticipationPattern, type RegexInlinePlugin, type SetPropsBatchEntry, type ThemedLine, type ThemedToken, type TocHeading, type TokenLineV1, type TokenSpan, type TokenStyle, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-mdx/core",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Core types, snapshot utilities, and perf helpers for the Streaming Markdown V2 stack",
5
5
  "license": "MIT",
6
6
  "repository": {