@elabs-ai/components-process 4.2.0 → 5.0.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 (144) hide show
  1. package/README.md +8 -1
  2. package/dist/core/index.d.ts +801 -3
  3. package/dist/core/index.js +1334 -0
  4. package/dist/core/index.js.map +1 -1
  5. package/dist/index.d.ts +1889 -34
  6. package/dist/index.js +5512 -196
  7. package/dist/index.js.map +1 -1
  8. package/dist/test/index.d.ts +223 -5
  9. package/dist/test/index.js +346 -191
  10. package/dist/test/index.js.map +1 -1
  11. package/package.json +14 -13
  12. package/src/__contract__/case-table.contract.test.tsx +49 -0
  13. package/src/__contract__/compare-kpi-strip.contract.test.tsx +49 -0
  14. package/src/__contract__/conformance-overlay.contract.test.tsx +49 -0
  15. package/src/__contract__/happy-path-editor.contract.test.tsx +49 -0
  16. package/src/__contract__/violation-list.contract.test.tsx +49 -0
  17. package/src/abstraction-controls/abstraction-controls-per-type.test.tsx +80 -0
  18. package/src/abstraction-controls/abstraction-controls.stories.tsx +43 -1
  19. package/src/abstraction-controls/abstraction-controls.tsx +198 -5
  20. package/src/case-table/case-table.stories.tsx +89 -0
  21. package/src/case-table/case-table.test.tsx +148 -0
  22. package/src/case-table/case-table.tsx +144 -0
  23. package/src/case-table/columns.ts +116 -0
  24. package/src/case-table/index.ts +11 -0
  25. package/src/case-timeline/case-timeline-model.test.ts +72 -0
  26. package/src/case-timeline/case-timeline-model.ts +112 -0
  27. package/src/case-timeline/case-timeline.stories.tsx +94 -0
  28. package/src/case-timeline/case-timeline.test.tsx +51 -0
  29. package/src/case-timeline/case-timeline.tsx +109 -0
  30. package/src/case-timeline/index.ts +9 -0
  31. package/src/conformance-overlay/conformance-fixture.ts +59 -0
  32. package/src/conformance-overlay/conformance-legend.tsx +109 -0
  33. package/src/conformance-overlay/conformance-overlay.stories.tsx +116 -0
  34. package/src/conformance-overlay/conformance-overlay.test.tsx +88 -0
  35. package/src/conformance-overlay/conformance-overlay.tsx +107 -0
  36. package/src/conformance-overlay/conformance-state.test.ts +79 -0
  37. package/src/conformance-overlay/conformance-state.ts +220 -0
  38. package/src/conformance-overlay/index.ts +4 -0
  39. package/src/core/activity-color-scale.test.ts +107 -0
  40. package/src/core/activity-color-scale.ts +133 -0
  41. package/src/core/adapters/ocel.test.ts +112 -0
  42. package/src/core/adapters/ocel.ts +359 -0
  43. package/src/core/adapters/xes.test.ts +293 -0
  44. package/src/core/adapters/xes.ts +384 -0
  45. package/src/core/cases-from-log.test.ts +72 -0
  46. package/src/core/cases-from-log.ts +85 -0
  47. package/src/core/conformance.test.ts +80 -0
  48. package/src/core/conformance.ts +91 -0
  49. package/src/core/diff-graphs.test.ts +151 -0
  50. package/src/core/diff-graphs.ts +118 -0
  51. package/src/core/discover-object-centric-graph.test.ts +94 -0
  52. package/src/core/discover-object-centric-graph.ts +296 -0
  53. package/src/core/fixtures/ocel-sample.ts +82 -0
  54. package/src/core/fixtures/sample.xes +68 -0
  55. package/src/core/index.ts +113 -0
  56. package/src/core/reference-model.test.ts +43 -0
  57. package/src/core/reference-model.ts +116 -0
  58. package/src/core/replay-timeline.test.ts +161 -0
  59. package/src/core/replay-timeline.ts +260 -0
  60. package/src/core/segments.test.ts +185 -0
  61. package/src/core/segments.ts +153 -0
  62. package/src/core/token-replay.test.ts +218 -0
  63. package/src/core/token-replay.ts +456 -0
  64. package/src/core/types.ts +2 -2
  65. package/src/dotted-chart/compute-dots.test.ts +176 -0
  66. package/src/dotted-chart/compute-dots.ts +241 -0
  67. package/src/dotted-chart/dotted-chart-labels.ts +93 -0
  68. package/src/dotted-chart/dotted-chart.stories.tsx +182 -0
  69. package/src/dotted-chart/dotted-chart.test.tsx +135 -0
  70. package/src/dotted-chart/dotted-chart.tsx +841 -0
  71. package/src/dotted-chart/index.ts +23 -0
  72. package/src/dotted-chart/use-element-size.ts +33 -0
  73. package/src/happy-path-editor/happy-path-editor-context.ts +81 -0
  74. package/src/happy-path-editor/happy-path-editor.stories.tsx +116 -0
  75. package/src/happy-path-editor/happy-path-editor.test.tsx +142 -0
  76. package/src/happy-path-editor/happy-path-editor.tsx +239 -0
  77. package/src/happy-path-editor/happy-path-step-node.tsx +175 -0
  78. package/src/happy-path-editor/index.ts +4 -0
  79. package/src/index.ts +51 -1
  80. package/src/performance-spectrum/aggregate-segments.test.ts +107 -0
  81. package/src/performance-spectrum/aggregate-segments.ts +174 -0
  82. package/src/performance-spectrum/index.ts +25 -0
  83. package/src/performance-spectrum/performance-spectrum-context.tsx +116 -0
  84. package/src/performance-spectrum/performance-spectrum.stories.tsx +128 -0
  85. package/src/performance-spectrum/performance-spectrum.test.tsx +190 -0
  86. package/src/performance-spectrum/performance-spectrum.tsx +870 -0
  87. package/src/process-compare/compare-kpi-strip.stories.tsx +48 -0
  88. package/src/process-compare/compare-kpi-strip.tsx +94 -0
  89. package/src/process-compare/compare-model.ts +83 -0
  90. package/src/process-compare/compare-side.tsx +42 -0
  91. package/src/process-compare/diff-to-graph.ts +104 -0
  92. package/src/process-compare/index.ts +23 -0
  93. package/src/process-compare/process-compare.stories.tsx +184 -0
  94. package/src/process-compare/process-compare.test.tsx +224 -0
  95. package/src/process-compare/process-compare.tsx +251 -0
  96. package/src/process-explorer.stories.tsx +1 -1
  97. package/src/process-filter-bar/index.ts +2 -0
  98. package/src/process-filter-bar/process-filter-bar.stories.tsx +156 -0
  99. package/src/process-filter-bar/process-filter-bar.test.tsx +201 -0
  100. package/src/process-filter-bar/process-filter-bar.tsx +167 -0
  101. package/src/process-kpi-strip/process-kpi-strip.stories.tsx +47 -0
  102. package/src/process-kpi-strip/process-kpi-strip.test.tsx +67 -0
  103. package/src/process-kpi-strip/process-kpi-strip.tsx +148 -8
  104. package/src/process-map/activity-accent.ts +25 -0
  105. package/src/process-map/index.ts +1 -0
  106. package/src/process-map/map-model.test.ts +16 -0
  107. package/src/process-map/map-model.ts +323 -1
  108. package/src/process-map/object-centric-map.test.tsx +132 -0
  109. package/src/process-map/process-activity-node.tsx +152 -14
  110. package/src/process-map/process-map-object-centric.stories.tsx +219 -0
  111. package/src/process-map/process-map.stories.tsx +64 -0
  112. package/src/process-map/process-map.tsx +240 -16
  113. package/src/process-map/process-transition-edge.test.tsx +47 -0
  114. package/src/process-map/process-transition-edge.tsx +134 -7
  115. package/src/process-map/use-process-layout.ts +30 -9
  116. package/src/process-replay/congestion-heat.tsx +107 -0
  117. package/src/process-replay/index.ts +14 -0
  118. package/src/process-replay/process-replay.stories.tsx +168 -0
  119. package/src/process-replay/process-replay.test.tsx +170 -0
  120. package/src/process-replay/process-replay.tsx +285 -0
  121. package/src/process-replay/replay-controls.tsx +147 -0
  122. package/src/process-replay/replay-format.ts +83 -0
  123. package/src/process-replay/replay-tokens-context.ts +30 -0
  124. package/src/process-replay/use-controllable-value.ts +30 -0
  125. package/src/templates-process-explorer.stories.tsx +1304 -0
  126. package/src/test/contract.test.ts +66 -0
  127. package/src/test/contract.ts +107 -6
  128. package/src/test/doubles.test.tsx +87 -1
  129. package/src/test/doubles.tsx +174 -3
  130. package/src/test/index.ts +25 -1
  131. package/src/use-process-explorer/use-process-explorer.test.ts +44 -0
  132. package/src/use-process-explorer/use-process-explorer.ts +34 -2
  133. package/src/variant-explorer/coverage-bar.tsx +36 -0
  134. package/src/variant-explorer/index.ts +16 -0
  135. package/src/variant-explorer/sequence-chips.tsx +103 -0
  136. package/src/variant-explorer/variant-explorer-model.ts +42 -0
  137. package/src/variant-explorer/variant-explorer.stories.tsx +226 -0
  138. package/src/variant-explorer/variant-explorer.test.tsx +302 -0
  139. package/src/variant-explorer/variant-explorer.tsx +567 -0
  140. package/src/variant-explorer/variant-row.tsx +137 -0
  141. package/src/violation-list/index.ts +2 -0
  142. package/src/violation-list/violation-list.stories.tsx +73 -0
  143. package/src/violation-list/violation-list.test.tsx +84 -0
  144. package/src/violation-list/violation-list.tsx +259 -0
@@ -1,88 +1,121 @@
1
1
  "use client";
2
2
 
3
- // src/test/contract.ts
4
- var ProcessContractError = class extends Error {
5
- constructor(componentName, message) {
6
- super(`${componentName}: ${message}`);
7
- this.name = "ProcessContractError";
8
- }
9
- };
10
- function isProcessGraph(value) {
11
- return !!value && typeof value === "object" && Array.isArray(value.activities) && Array.isArray(value.transitions);
3
+ // src/core/event-log.ts
4
+ function isNormalizedLog(log) {
5
+ return Array.isArray(log.cases);
12
6
  }
13
- function isVariantArray(value) {
14
- return Array.isArray(value) && value.every((entry) => typeof entry === "object" && entry !== null && "sequence" in entry);
7
+ function asNormalizedLog(log) {
8
+ return isNormalizedLog(log) ? log : normalizeLog(log);
15
9
  }
16
- function assertProcessContract(componentName, props, spec) {
17
- const data = props[spec.dataProp];
18
- if (spec.dataProp === "graph" && !isProcessGraph(data)) {
19
- throw new ProcessContractError(
20
- componentName,
21
- `"graph" prop must be a ProcessGraph, got ${typeof data}`
22
- );
23
- }
24
- if (spec.dataProp === "variants" && !isVariantArray(data)) {
25
- throw new ProcessContractError(
26
- componentName,
27
- `"variants" prop must be a Variant[], got ${typeof data}`
28
- );
10
+ function toEpochMs(value) {
11
+ if (value === void 0 || value === null) return Number.NaN;
12
+ if (typeof value === "number") return Number.isFinite(value) ? value : Number.NaN;
13
+ if (value instanceof Date) return value.getTime();
14
+ const parsed = Date.parse(value);
15
+ if (!Number.isNaN(parsed)) return parsed;
16
+ const asNumber = Number(value);
17
+ return Number.isFinite(asNumber) && value.trim() !== "" ? asNumber : Number.NaN;
18
+ }
19
+ function byTimestamp(a, b) {
20
+ if (Number.isNaN(a.at) || Number.isNaN(b.at)) return 0;
21
+ return a.at - b.at;
22
+ }
23
+ function normalizeLog(log) {
24
+ const staged = /* @__PURE__ */ new Map();
25
+ const order = [];
26
+ for (const row of log.events) {
27
+ if (!row || typeof row.caseId !== "string" || row.caseId === "") continue;
28
+ if (typeof row.activity !== "string" || row.activity === "") continue;
29
+ let bucket = staged.get(row.caseId);
30
+ if (bucket === void 0) {
31
+ bucket = [];
32
+ staged.set(row.caseId, bucket);
33
+ order.push(row.caseId);
34
+ }
35
+ bucket.push({ at: toEpochMs(row.timestamp), row });
29
36
  }
30
- for (const key of spec.requiredProps ?? []) {
31
- if (props[key] === void 0) {
32
- throw new ProcessContractError(componentName, `missing required prop "${key}"`);
37
+ const cases = [];
38
+ let events = 0;
39
+ for (const caseId of order) {
40
+ const bucket = staged.get(caseId);
41
+ bucket.sort(byTimestamp);
42
+ const instances = [];
43
+ const open = /* @__PURE__ */ new Map();
44
+ for (const { at, row } of bucket) {
45
+ if (row.lifecycle === "start") {
46
+ const index = instances.length;
47
+ instances.push(makeInstance(row, at, at, true));
48
+ const queue = open.get(row.activity);
49
+ if (queue === void 0) open.set(row.activity, [index]);
50
+ else queue.push(index);
51
+ continue;
52
+ }
53
+ if (row.lifecycle === "complete") {
54
+ const queue = open.get(row.activity);
55
+ const index = queue?.shift();
56
+ if (index !== void 0) {
57
+ const instance = instances[index];
58
+ instance.end = at;
59
+ instance.duration = durationOf(instance.start, at);
60
+ instance.isOpen = false;
61
+ if (instance.resource === void 0 && row.resource !== void 0) {
62
+ instance.resource = row.resource;
63
+ }
64
+ if (instance.attributes === void 0 && row.attributes !== void 0) {
65
+ instance.attributes = row.attributes;
66
+ }
67
+ continue;
68
+ }
69
+ }
70
+ const end = at;
71
+ const explicitStart = toEpochMs(row.startTimestamp);
72
+ const start = Number.isNaN(explicitStart) ? end : explicitStart;
73
+ instances.push(makeInstance(row, start, end, false));
33
74
  }
75
+ if (instances.length === 0) continue;
76
+ instances.sort(byStart);
77
+ let caseStart = Number.POSITIVE_INFINITY;
78
+ let caseEnd = Number.NEGATIVE_INFINITY;
79
+ for (const instance of instances) {
80
+ if (Number.isFinite(instance.start) && instance.start < caseStart) caseStart = instance.start;
81
+ if (Number.isFinite(instance.end) && instance.end > caseEnd) caseEnd = instance.end;
82
+ }
83
+ const hasExtent = caseStart !== Number.POSITIVE_INFINITY && caseEnd !== Number.NEGATIVE_INFINITY;
84
+ const normalizedCase = {
85
+ caseId,
86
+ events: instances,
87
+ start: hasExtent ? caseStart : Number.NaN,
88
+ end: hasExtent ? caseEnd : Number.NaN,
89
+ duration: hasExtent ? durationOf(caseStart, caseEnd) : Number.NaN
90
+ };
91
+ const caseAttributes = log.caseAttributes?.[caseId];
92
+ if (caseAttributes !== void 0) normalizedCase.attributes = caseAttributes;
93
+ cases.push(normalizedCase);
94
+ events += instances.length;
34
95
  }
96
+ return { cases, totals: { cases: cases.length, events } };
35
97
  }
36
- function buildProcessDoublePayload(componentName, props, spec) {
37
- const data = props[spec.dataProp];
38
- const dataLength = spec.dataProp === "graph" && isProcessGraph(data) ? data.activities.length : Array.isArray(data) ? data.length : 0;
39
- const payload = { component: componentName, dataLength };
40
- if ("selection" in props) payload.selection = props.selection;
41
- return payload;
98
+ function byStart(a, b) {
99
+ if (Number.isNaN(a.start) || Number.isNaN(b.start)) return 0;
100
+ return a.start - b.start;
42
101
  }
43
- function readProcessDoubleProps(el) {
44
- const raw = el.getAttribute("data-process-props");
45
- return raw ? JSON.parse(raw) : null;
102
+ function durationOf(start, end) {
103
+ const delta = end - start;
104
+ if (!Number.isFinite(delta)) return Number.NaN;
105
+ return delta < 0 ? 0 : delta;
46
106
  }
47
-
48
- // src/test/doubles.tsx
49
- import { forwardRef } from "react";
50
- import { jsx } from "react/jsx-runtime";
51
- var PROCESS_MAP_SPEC = { dataProp: "graph" };
52
- var VARIANT_EXPLORER_SPEC = { dataProp: "variants" };
53
- var PROCESS_KPI_STRIP_SPEC = { dataProp: "graph" };
54
- function createProcessDouble(name, spec) {
55
- const Double = forwardRef(function ProcessTestDouble(props, ref) {
56
- const record = props;
57
- assertProcessContract(name, record, spec);
58
- const payload = buildProcessDoublePayload(name, record, spec);
59
- return /* @__PURE__ */ jsx(
60
- "div",
61
- {
62
- ref,
63
- "data-slot": "process-test-double",
64
- "data-process-double": name,
65
- "data-process-props": JSON.stringify(payload),
66
- className: props.className,
67
- style: props.style
68
- }
69
- );
70
- });
71
- Double.displayName = name;
72
- return Double;
107
+ function makeInstance(row, start, end, isOpen) {
108
+ const instance = {
109
+ activity: row.activity,
110
+ start,
111
+ end,
112
+ duration: durationOf(start, end),
113
+ isOpen
114
+ };
115
+ if (row.resource !== void 0) instance.resource = row.resource;
116
+ if (row.attributes !== void 0) instance.attributes = row.attributes;
117
+ return instance;
73
118
  }
74
- var ProcessMapDouble = createProcessDouble(
75
- "ProcessMapDouble",
76
- PROCESS_MAP_SPEC
77
- );
78
- var VariantExplorerDouble = createProcessDouble(
79
- "VariantExplorerDouble",
80
- VARIANT_EXPLORER_SPEC
81
- );
82
- var ProcessKpiStripDouble = createProcessDouble(
83
- "ProcessKpiStripDouble",
84
- PROCESS_KPI_STRIP_SPEC
85
- );
86
119
 
87
120
  // src/core/scale.ts
88
121
  function ascending(a, b) {
@@ -205,123 +238,6 @@ var DurationSampler = class {
205
238
  }
206
239
  };
207
240
 
208
- // src/core/event-log.ts
209
- function isNormalizedLog(log) {
210
- return Array.isArray(log.cases);
211
- }
212
- function asNormalizedLog(log) {
213
- return isNormalizedLog(log) ? log : normalizeLog(log);
214
- }
215
- function toEpochMs(value) {
216
- if (value === void 0 || value === null) return Number.NaN;
217
- if (typeof value === "number") return Number.isFinite(value) ? value : Number.NaN;
218
- if (value instanceof Date) return value.getTime();
219
- const parsed = Date.parse(value);
220
- if (!Number.isNaN(parsed)) return parsed;
221
- const asNumber = Number(value);
222
- return Number.isFinite(asNumber) && value.trim() !== "" ? asNumber : Number.NaN;
223
- }
224
- function byTimestamp(a, b) {
225
- if (Number.isNaN(a.at) || Number.isNaN(b.at)) return 0;
226
- return a.at - b.at;
227
- }
228
- function normalizeLog(log) {
229
- const staged = /* @__PURE__ */ new Map();
230
- const order = [];
231
- for (const row of log.events) {
232
- if (!row || typeof row.caseId !== "string" || row.caseId === "") continue;
233
- if (typeof row.activity !== "string" || row.activity === "") continue;
234
- let bucket = staged.get(row.caseId);
235
- if (bucket === void 0) {
236
- bucket = [];
237
- staged.set(row.caseId, bucket);
238
- order.push(row.caseId);
239
- }
240
- bucket.push({ at: toEpochMs(row.timestamp), row });
241
- }
242
- const cases = [];
243
- let events = 0;
244
- for (const caseId of order) {
245
- const bucket = staged.get(caseId);
246
- bucket.sort(byTimestamp);
247
- const instances = [];
248
- const open = /* @__PURE__ */ new Map();
249
- for (const { at, row } of bucket) {
250
- if (row.lifecycle === "start") {
251
- const index = instances.length;
252
- instances.push(makeInstance(row, at, at, true));
253
- const queue = open.get(row.activity);
254
- if (queue === void 0) open.set(row.activity, [index]);
255
- else queue.push(index);
256
- continue;
257
- }
258
- if (row.lifecycle === "complete") {
259
- const queue = open.get(row.activity);
260
- const index = queue?.shift();
261
- if (index !== void 0) {
262
- const instance = instances[index];
263
- instance.end = at;
264
- instance.duration = durationOf(instance.start, at);
265
- instance.isOpen = false;
266
- if (instance.resource === void 0 && row.resource !== void 0) {
267
- instance.resource = row.resource;
268
- }
269
- if (instance.attributes === void 0 && row.attributes !== void 0) {
270
- instance.attributes = row.attributes;
271
- }
272
- continue;
273
- }
274
- }
275
- const end = at;
276
- const explicitStart = toEpochMs(row.startTimestamp);
277
- const start = Number.isNaN(explicitStart) ? end : explicitStart;
278
- instances.push(makeInstance(row, start, end, false));
279
- }
280
- if (instances.length === 0) continue;
281
- instances.sort(byStart);
282
- let caseStart = Number.POSITIVE_INFINITY;
283
- let caseEnd = Number.NEGATIVE_INFINITY;
284
- for (const instance of instances) {
285
- if (Number.isFinite(instance.start) && instance.start < caseStart) caseStart = instance.start;
286
- if (Number.isFinite(instance.end) && instance.end > caseEnd) caseEnd = instance.end;
287
- }
288
- const hasExtent = caseStart !== Number.POSITIVE_INFINITY && caseEnd !== Number.NEGATIVE_INFINITY;
289
- const normalizedCase = {
290
- caseId,
291
- events: instances,
292
- start: hasExtent ? caseStart : Number.NaN,
293
- end: hasExtent ? caseEnd : Number.NaN,
294
- duration: hasExtent ? durationOf(caseStart, caseEnd) : Number.NaN
295
- };
296
- const caseAttributes = log.caseAttributes?.[caseId];
297
- if (caseAttributes !== void 0) normalizedCase.attributes = caseAttributes;
298
- cases.push(normalizedCase);
299
- events += instances.length;
300
- }
301
- return { cases, totals: { cases: cases.length, events } };
302
- }
303
- function byStart(a, b) {
304
- if (Number.isNaN(a.start) || Number.isNaN(b.start)) return 0;
305
- return a.start - b.start;
306
- }
307
- function durationOf(start, end) {
308
- const delta = end - start;
309
- if (!Number.isFinite(delta)) return Number.NaN;
310
- return delta < 0 ? 0 : delta;
311
- }
312
- function makeInstance(row, start, end, isOpen) {
313
- const instance = {
314
- activity: row.activity,
315
- start,
316
- end,
317
- duration: durationOf(start, end),
318
- isOpen
319
- };
320
- if (row.resource !== void 0) instance.resource = row.resource;
321
- if (row.attributes !== void 0) instance.attributes = row.attributes;
322
- return instance;
323
- }
324
-
325
241
  // src/core/extract-variants.ts
326
242
  var VARIANT_KEY_SEPARATOR = "";
327
243
  function variantKey(sequence) {
@@ -510,15 +426,254 @@ function toSortedRecord(counts) {
510
426
  return out;
511
427
  }
512
428
 
429
+ // src/core/segments.ts
430
+ function segmentKey(from, to) {
431
+ return `${from}${EDGE_KEY_SEPARATOR}${to}`;
432
+ }
433
+ function segmentsFor(log, order) {
434
+ const wanted = /* @__PURE__ */ new Set();
435
+ for (const def of order) wanted.add(segmentKey(def.from, def.to));
436
+ const out = [];
437
+ if (wanted.size === 0) return out;
438
+ for (const kase of asNormalizedLog(log).cases) {
439
+ const trace = kase.events;
440
+ for (let i = 1; i < trace.length; i += 1) {
441
+ const previous = trace[i - 1];
442
+ const event = trace[i];
443
+ const key = segmentKey(previous.activity, event.activity);
444
+ if (!wanted.has(key)) continue;
445
+ const start = previous.end;
446
+ if (!Number.isFinite(start) || !Number.isFinite(event.start)) continue;
447
+ const end = Math.max(start, event.start);
448
+ out.push({ segment: key, caseId: kase.caseId, start, end, duration: end - start });
449
+ }
450
+ }
451
+ return out;
452
+ }
453
+ function segmentOrderByFrequency(graph, limit = Number.POSITIVE_INFINITY) {
454
+ const n = Math.max(0, Math.floor(limit));
455
+ return graph.transitions.slice(0, n).map((t) => ({ from: t.source, to: t.target }));
456
+ }
457
+ function segmentOrderForVariant(variant) {
458
+ const seen = /* @__PURE__ */ new Set();
459
+ const out = [];
460
+ for (let i = 1; i < variant.sequence.length; i += 1) {
461
+ const from = variant.sequence[i - 1];
462
+ const to = variant.sequence[i];
463
+ const key = segmentKey(from, to);
464
+ if (seen.has(key)) continue;
465
+ seen.add(key);
466
+ out.push({ from, to });
467
+ }
468
+ return out;
469
+ }
470
+
471
+ // src/test/contract.ts
472
+ var ProcessContractError = class extends Error {
473
+ constructor(componentName, message) {
474
+ super(`${componentName}: ${message}`);
475
+ this.name = "ProcessContractError";
476
+ }
477
+ };
478
+ function isProcessGraph(value) {
479
+ return !!value && typeof value === "object" && Array.isArray(value.activities) && Array.isArray(value.transitions);
480
+ }
481
+ function isEventLog(value) {
482
+ return !!value && typeof value === "object" && Array.isArray(value.events);
483
+ }
484
+ function countSpectrumOccurrences(log, order, limit) {
485
+ const cap = typeof limit === "number" ? Math.max(0, Math.floor(limit)) : 12;
486
+ let definitions = [];
487
+ if (order === void 0 || order === "frequency") {
488
+ definitions = segmentOrderByFrequency(discoverGraph(log), cap);
489
+ } else if (Array.isArray(order)) {
490
+ definitions = order.slice(0, cap);
491
+ } else if (order && typeof order === "object" && "variantId" in order) {
492
+ const id = order.variantId;
493
+ const variant = extractVariants(log).find((v) => v.id === id);
494
+ definitions = variant ? segmentOrderForVariant(variant).slice(0, cap) : [];
495
+ }
496
+ return segmentsFor(log, definitions).length;
497
+ }
498
+ function isVariantArray(value) {
499
+ return Array.isArray(value) && value.every((entry) => typeof entry === "object" && entry !== null && "sequence" in entry);
500
+ }
501
+ function isTimedEventLog(value) {
502
+ const events = value?.events;
503
+ return Array.isArray(events) && events.length > 0 && events.every(
504
+ (row) => !!row && typeof row === "object" && Number.isFinite(toEpochMs(row.timestamp))
505
+ );
506
+ }
507
+ function isConformanceResult(value) {
508
+ return !!value && typeof value === "object" && Array.isArray(value.traces) && typeof value.deviationCounts === "object";
509
+ }
510
+ function isHappyPath(value) {
511
+ return !!value && typeof value === "object" && Array.isArray(value.steps);
512
+ }
513
+ function assertProcessContract(componentName, props, spec) {
514
+ const data = props[spec.dataProp];
515
+ if (spec.dataProp === "graph" && !isProcessGraph(data)) {
516
+ throw new ProcessContractError(
517
+ componentName,
518
+ `"graph" prop must be a ProcessGraph, got ${typeof data}`
519
+ );
520
+ }
521
+ if (spec.dataProp === "variants" && !isVariantArray(data)) {
522
+ throw new ProcessContractError(
523
+ componentName,
524
+ `"variants" prop must be a Variant[], got ${typeof data}`
525
+ );
526
+ }
527
+ if (spec.dataProp === "log" && !isTimedEventLog(data)) {
528
+ throw new ProcessContractError(
529
+ componentName,
530
+ `"log" prop must be an EventLog with non-empty events and parsable timestamps`
531
+ );
532
+ }
533
+ if (spec.segmentOrder && isEventLog(data) && data.events.length > 0 && countSpectrumOccurrences(data, props.order, props.segmentLimit) === 0) {
534
+ throw new ProcessContractError(
535
+ componentName,
536
+ `"order" resolves to no segment that occurs in "log"`
537
+ );
538
+ }
539
+ if (spec.dataProp === "conformance" && !isConformanceResult(data)) {
540
+ throw new ProcessContractError(
541
+ componentName,
542
+ `"conformance" prop must be a ConformanceResult, got ${typeof data}`
543
+ );
544
+ }
545
+ if (spec.dataProp === "value" && !isHappyPath(data)) {
546
+ throw new ProcessContractError(
547
+ componentName,
548
+ `"value" prop must be a HappyPath, got ${typeof data}`
549
+ );
550
+ }
551
+ for (const key of spec.requiredProps ?? []) {
552
+ if (props[key] === void 0) {
553
+ throw new ProcessContractError(componentName, `missing required prop "${key}"`);
554
+ }
555
+ }
556
+ }
557
+ function buildProcessDoublePayload(componentName, props, spec) {
558
+ const data = props[spec.dataProp];
559
+ const dataLength = spec.dataProp === "graph" && isProcessGraph(data) ? data.activities.length : spec.dataProp === "log" && isTimedEventLog(data) ? data.events.length : spec.dataProp === "conformance" && isConformanceResult(data) ? data.traces.length : spec.dataProp === "value" && isHappyPath(data) ? data.steps.length : Array.isArray(data) ? data.length : 0;
560
+ const payload = { component: componentName, dataLength };
561
+ if ("selection" in props) payload.selection = props.selection;
562
+ return payload;
563
+ }
564
+ function readProcessDoubleProps(el) {
565
+ const raw = el.getAttribute("data-process-props");
566
+ return raw ? JSON.parse(raw) : null;
567
+ }
568
+
569
+ // src/test/doubles.tsx
570
+ import { forwardRef } from "react";
571
+ import { jsx } from "react/jsx-runtime";
572
+ var PROCESS_MAP_SPEC = { dataProp: "graph" };
573
+ var VARIANT_EXPLORER_SPEC = { dataProp: "variants" };
574
+ var PROCESS_KPI_STRIP_SPEC = { dataProp: "graph" };
575
+ function createProcessDouble(name, spec) {
576
+ const Double = forwardRef(function ProcessTestDouble(props, ref) {
577
+ const record = props;
578
+ assertProcessContract(name, record, spec);
579
+ const payload = buildProcessDoublePayload(name, record, spec);
580
+ return /* @__PURE__ */ jsx(
581
+ "div",
582
+ {
583
+ ref,
584
+ "data-slot": "process-test-double",
585
+ "data-process-double": name,
586
+ "data-process-props": JSON.stringify(payload),
587
+ className: props.className,
588
+ style: props.style
589
+ }
590
+ );
591
+ });
592
+ Double.displayName = name;
593
+ return Double;
594
+ }
595
+ var ProcessMapDouble = createProcessDouble(
596
+ "ProcessMapDouble",
597
+ PROCESS_MAP_SPEC
598
+ );
599
+ var VariantExplorerDouble = createProcessDouble(
600
+ "VariantExplorerDouble",
601
+ VARIANT_EXPLORER_SPEC
602
+ );
603
+ var ProcessKpiStripDouble = createProcessDouble(
604
+ "ProcessKpiStripDouble",
605
+ PROCESS_KPI_STRIP_SPEC
606
+ );
607
+ var DottedChartDouble = createProcessDouble("DottedChartDouble", {
608
+ dataProp: "log"
609
+ });
610
+ var PERFORMANCE_SPECTRUM_SPEC = { dataProp: "log", segmentOrder: true };
611
+ var PerformanceSpectrumDouble = createProcessDouble(
612
+ "PerformanceSpectrumDouble",
613
+ PERFORMANCE_SPECTRUM_SPEC
614
+ );
615
+ var ConformanceOverlayDouble = createProcessDouble(
616
+ "ConformanceOverlayDouble",
617
+ { dataProp: "conformance", requiredProps: ["graph"] }
618
+ );
619
+ var ViolationListDouble = createProcessDouble(
620
+ "ViolationListDouble",
621
+ { dataProp: "conformance" }
622
+ );
623
+ var HappyPathEditorDouble = createProcessDouble(
624
+ "HappyPathEditorDouble",
625
+ { dataProp: "value", requiredProps: ["onChange"] }
626
+ );
627
+ var ProcessReplayDouble = createProcessDouble(
628
+ "ProcessReplayDouble",
629
+ { dataProp: "log", requiredProps: ["graph"] }
630
+ );
631
+ var ProcessCompareDouble = forwardRef(
632
+ function ProcessCompareDouble2({ a, b, mode = "side-by-side", className, style }, ref) {
633
+ if (!a || typeof a.label !== "string") {
634
+ throw new ProcessContractError("ProcessCompareDouble", 'missing required prop "a.label"');
635
+ }
636
+ if (!b || typeof b.label !== "string") {
637
+ throw new ProcessContractError("ProcessCompareDouble", 'missing required prop "b.label"');
638
+ }
639
+ const payload = {
640
+ component: "ProcessCompareDouble",
641
+ mode,
642
+ aLabel: a.label,
643
+ bLabel: b.label,
644
+ aActivityCount: a.graph?.activities.length ?? 0,
645
+ bActivityCount: b.graph?.activities.length ?? 0
646
+ };
647
+ return /* @__PURE__ */ jsx(
648
+ "div",
649
+ {
650
+ ref,
651
+ "data-slot": "process-test-double",
652
+ "data-process-double": "ProcessCompareDouble",
653
+ "data-process-props": JSON.stringify(payload),
654
+ className,
655
+ style
656
+ }
657
+ );
658
+ }
659
+ );
660
+
513
661
  // src/test/primitives.tsx
514
662
  function withProcessFixture(log) {
515
663
  return { log, graph: discoverGraph(log), variants: extractVariants(log) };
516
664
  }
517
665
  export {
666
+ ConformanceOverlayDouble,
667
+ DottedChartDouble,
668
+ HappyPathEditorDouble,
669
+ PerformanceSpectrumDouble,
670
+ ProcessCompareDouble,
518
671
  ProcessContractError,
519
672
  ProcessKpiStripDouble,
520
673
  ProcessMapDouble,
674
+ ProcessReplayDouble,
521
675
  VariantExplorerDouble,
676
+ ViolationListDouble,
522
677
  assertProcessContract,
523
678
  buildProcessDoublePayload,
524
679
  readProcessDoubleProps,