@stream-mdx/core 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -201,10 +201,12 @@ type WorkerIn = {
201
201
  type: "MDX_COMPILED";
202
202
  blockId: string;
203
203
  compiledId: string;
204
+ rawSignature?: string;
204
205
  } | {
205
206
  type: "MDX_ERROR";
206
207
  blockId: string;
207
208
  error?: string;
209
+ rawSignature?: string;
208
210
  } | {
209
211
  type: "SET_CREDITS";
210
212
  credits: number;
@@ -356,6 +358,25 @@ interface NodePath {
356
358
  nodeId?: string;
357
359
  indexPath?: number[];
358
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
+ }
359
380
  interface TocHeading {
360
381
  id: string;
361
382
  text: string;
@@ -367,31 +388,38 @@ type Patch = {
367
388
  at: NodePath;
368
389
  index: number;
369
390
  node: NodeSnapshot;
391
+ meta?: PatchMeta;
370
392
  } | {
371
393
  op: "deleteChild";
372
394
  at: NodePath;
373
395
  index: number;
396
+ meta?: PatchMeta;
374
397
  } | {
375
398
  op: "replaceChild";
376
399
  at: NodePath;
377
400
  index: number;
378
401
  node: NodeSnapshot;
402
+ meta?: PatchMeta;
379
403
  } | {
380
404
  op: "setProps";
381
405
  at: NodePath;
382
406
  props: Record<string, unknown>;
407
+ meta?: PatchMeta;
383
408
  } | {
384
409
  op: "setPropsBatch";
385
410
  entries: SetPropsBatchEntry[];
411
+ meta?: PatchMeta;
386
412
  } | {
387
413
  op: "finalize";
388
414
  at: NodePath;
415
+ meta?: PatchMeta;
389
416
  } | {
390
417
  op: "reorder";
391
418
  at: NodePath;
392
419
  from: number;
393
420
  to: number;
394
421
  count: number;
422
+ meta?: PatchMeta;
395
423
  } | {
396
424
  op: "appendLines";
397
425
  at: NodePath;
@@ -402,6 +430,7 @@ type Patch = {
402
430
  diffKind?: Array<DiffKind | null>;
403
431
  oldNo?: Array<number | null>;
404
432
  newNo?: Array<number | null>;
433
+ meta?: PatchMeta;
405
434
  } | {
406
435
  op: "setHTML";
407
436
  at: NodePath;
@@ -410,6 +439,7 @@ type Patch = {
410
439
  block?: Block;
411
440
  meta?: Record<string, unknown>;
412
441
  sanitized?: boolean;
442
+ patchMeta?: PatchMeta;
413
443
  };
414
444
  interface PatchMetrics {
415
445
  patchCount: number;
@@ -426,6 +456,7 @@ interface PatchMetrics {
426
456
  interface SetPropsBatchEntry {
427
457
  at: NodePath;
428
458
  props: Record<string, unknown>;
459
+ meta?: PatchMeta;
429
460
  }
430
461
  interface CoalescingMetrics {
431
462
  inputPatchCount: number;
@@ -437,4 +468,4 @@ interface CoalescingMetrics {
437
468
  insertChildCoalesced: number;
438
469
  }
439
470
 
440
- 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 TocHeading, 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
@@ -201,10 +201,12 @@ type WorkerIn = {
201
201
  type: "MDX_COMPILED";
202
202
  blockId: string;
203
203
  compiledId: string;
204
+ rawSignature?: string;
204
205
  } | {
205
206
  type: "MDX_ERROR";
206
207
  blockId: string;
207
208
  error?: string;
209
+ rawSignature?: string;
208
210
  } | {
209
211
  type: "SET_CREDITS";
210
212
  credits: number;
@@ -356,6 +358,25 @@ interface NodePath {
356
358
  nodeId?: string;
357
359
  indexPath?: number[];
358
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
+ }
359
380
  interface TocHeading {
360
381
  id: string;
361
382
  text: string;
@@ -367,31 +388,38 @@ type Patch = {
367
388
  at: NodePath;
368
389
  index: number;
369
390
  node: NodeSnapshot;
391
+ meta?: PatchMeta;
370
392
  } | {
371
393
  op: "deleteChild";
372
394
  at: NodePath;
373
395
  index: number;
396
+ meta?: PatchMeta;
374
397
  } | {
375
398
  op: "replaceChild";
376
399
  at: NodePath;
377
400
  index: number;
378
401
  node: NodeSnapshot;
402
+ meta?: PatchMeta;
379
403
  } | {
380
404
  op: "setProps";
381
405
  at: NodePath;
382
406
  props: Record<string, unknown>;
407
+ meta?: PatchMeta;
383
408
  } | {
384
409
  op: "setPropsBatch";
385
410
  entries: SetPropsBatchEntry[];
411
+ meta?: PatchMeta;
386
412
  } | {
387
413
  op: "finalize";
388
414
  at: NodePath;
415
+ meta?: PatchMeta;
389
416
  } | {
390
417
  op: "reorder";
391
418
  at: NodePath;
392
419
  from: number;
393
420
  to: number;
394
421
  count: number;
422
+ meta?: PatchMeta;
395
423
  } | {
396
424
  op: "appendLines";
397
425
  at: NodePath;
@@ -402,6 +430,7 @@ type Patch = {
402
430
  diffKind?: Array<DiffKind | null>;
403
431
  oldNo?: Array<number | null>;
404
432
  newNo?: Array<number | null>;
433
+ meta?: PatchMeta;
405
434
  } | {
406
435
  op: "setHTML";
407
436
  at: NodePath;
@@ -410,6 +439,7 @@ type Patch = {
410
439
  block?: Block;
411
440
  meta?: Record<string, unknown>;
412
441
  sanitized?: boolean;
442
+ patchMeta?: PatchMeta;
413
443
  };
414
444
  interface PatchMetrics {
415
445
  patchCount: number;
@@ -426,6 +456,7 @@ interface PatchMetrics {
426
456
  interface SetPropsBatchEntry {
427
457
  at: NodePath;
428
458
  props: Record<string, unknown>;
459
+ meta?: PatchMeta;
429
460
  }
430
461
  interface CoalescingMetrics {
431
462
  inputPatchCount: number;
@@ -437,4 +468,4 @@ interface CoalescingMetrics {
437
468
  insertChildCoalesced: number;
438
469
  }
439
470
 
440
- 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 TocHeading, 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.4.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": {