state-surgeon 1.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.
@@ -0,0 +1,1181 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import React2, { useMemo, useState, useCallback, useEffect } from 'react';
3
+
4
+ // src/dashboard/components/MutationInspector.tsx
5
+ function MutationInspector({
6
+ mutation,
7
+ className = ""
8
+ }) {
9
+ if (!mutation) {
10
+ return /* @__PURE__ */ jsx("div", { className: `mutation-inspector mutation-inspector--empty ${className}`, children: /* @__PURE__ */ jsx("p", { children: "Select a mutation to inspect" }) });
11
+ }
12
+ const formatDuration = (ms) => {
13
+ if (ms === void 0) return "N/A";
14
+ if (ms < 1) return `${(ms * 1e3).toFixed(2)}\u03BCs`;
15
+ if (ms < 1e3) return `${ms.toFixed(2)}ms`;
16
+ return `${(ms / 1e3).toFixed(2)}s`;
17
+ };
18
+ const formatTimestamp = (ts) => {
19
+ const date = new Date(ts);
20
+ return date.toLocaleString();
21
+ };
22
+ return /* @__PURE__ */ jsxs("div", { className: `mutation-inspector ${className}`, children: [
23
+ /* @__PURE__ */ jsxs("div", { className: "mutation-inspector__header", children: [
24
+ /* @__PURE__ */ jsxs("h3", { className: "mutation-inspector__title", children: [
25
+ /* @__PURE__ */ jsx(
26
+ "span",
27
+ {
28
+ className: "mutation-inspector__source-badge",
29
+ "data-source": mutation.source,
30
+ children: mutation.source
31
+ }
32
+ ),
33
+ mutation.actionType
34
+ ] }),
35
+ /* @__PURE__ */ jsx("span", { className: "mutation-inspector__id", children: mutation.id })
36
+ ] }),
37
+ /* @__PURE__ */ jsxs("div", { className: "mutation-inspector__grid", children: [
38
+ /* @__PURE__ */ jsxs("div", { className: "mutation-inspector__section", children: [
39
+ /* @__PURE__ */ jsx("h4", { children: "Location" }),
40
+ /* @__PURE__ */ jsxs("dl", { className: "mutation-inspector__details", children: [
41
+ mutation.component && /* @__PURE__ */ jsxs(Fragment, { children: [
42
+ /* @__PURE__ */ jsx("dt", { children: "Component" }),
43
+ /* @__PURE__ */ jsx("dd", { children: mutation.component })
44
+ ] }),
45
+ mutation.function && /* @__PURE__ */ jsxs(Fragment, { children: [
46
+ /* @__PURE__ */ jsx("dt", { children: "Function" }),
47
+ /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx("code", { children: mutation.function }) })
48
+ ] }),
49
+ mutation.file && /* @__PURE__ */ jsxs(Fragment, { children: [
50
+ /* @__PURE__ */ jsx("dt", { children: "File" }),
51
+ /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsxs("code", { children: [
52
+ mutation.file,
53
+ mutation.line && `:${mutation.line}`,
54
+ mutation.column && `:${mutation.column}`
55
+ ] }) })
56
+ ] })
57
+ ] })
58
+ ] }),
59
+ /* @__PURE__ */ jsxs("div", { className: "mutation-inspector__section", children: [
60
+ /* @__PURE__ */ jsx("h4", { children: "Timing" }),
61
+ /* @__PURE__ */ jsxs("dl", { className: "mutation-inspector__details", children: [
62
+ /* @__PURE__ */ jsx("dt", { children: "Timestamp" }),
63
+ /* @__PURE__ */ jsx("dd", { children: formatTimestamp(mutation.timestamp) }),
64
+ /* @__PURE__ */ jsx("dt", { children: "Logical Clock" }),
65
+ /* @__PURE__ */ jsx("dd", { children: mutation.logicalClock }),
66
+ mutation.duration !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
67
+ /* @__PURE__ */ jsx("dt", { children: "Duration" }),
68
+ /* @__PURE__ */ jsx("dd", { children: formatDuration(mutation.duration) })
69
+ ] })
70
+ ] })
71
+ ] })
72
+ ] }),
73
+ mutation.actionPayload && /* @__PURE__ */ jsxs("div", { className: "mutation-inspector__section", children: [
74
+ /* @__PURE__ */ jsx("h4", { children: "Action Payload" }),
75
+ /* @__PURE__ */ jsx("pre", { className: "mutation-inspector__code", children: JSON.stringify(mutation.actionPayload, null, 2) })
76
+ ] }),
77
+ mutation.diff && mutation.diff.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mutation-inspector__section", children: [
78
+ /* @__PURE__ */ jsxs("h4", { children: [
79
+ "State Changes (",
80
+ mutation.diff.length,
81
+ ")"
82
+ ] }),
83
+ /* @__PURE__ */ jsx("ul", { className: "mutation-inspector__changes", children: mutation.diff.map((change, index) => /* @__PURE__ */ jsxs("li", { className: `mutation-inspector__change mutation-inspector__change--${change.operation.toLowerCase()}`, children: [
84
+ /* @__PURE__ */ jsx("code", { children: change.path }),
85
+ /* @__PURE__ */ jsx("span", { className: "mutation-inspector__op", children: change.operation })
86
+ ] }, index)) })
87
+ ] }),
88
+ mutation.callStack && mutation.callStack.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mutation-inspector__section", children: [
89
+ /* @__PURE__ */ jsx("h4", { children: "Call Stack" }),
90
+ /* @__PURE__ */ jsx("ul", { className: "mutation-inspector__stack", children: mutation.callStack.map((frame, index) => /* @__PURE__ */ jsx("li", { className: "mutation-inspector__frame", children: /* @__PURE__ */ jsx("code", { children: frame }) }, index)) })
91
+ ] }),
92
+ mutation.causes && mutation.causes.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mutation-inspector__section", children: [
93
+ /* @__PURE__ */ jsx("h4", { children: "Caused By" }),
94
+ /* @__PURE__ */ jsx("ul", { className: "mutation-inspector__causes", children: mutation.causes.map((causeId, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: causeId }) }, index)) })
95
+ ] }),
96
+ /* @__PURE__ */ jsx("style", { children: `
97
+ .mutation-inspector {
98
+ padding: 1rem;
99
+ background: #1a1a2e;
100
+ border-radius: 8px;
101
+ color: #e0e0e0;
102
+ font-size: 0.875rem;
103
+ }
104
+
105
+ .mutation-inspector--empty {
106
+ text-align: center;
107
+ color: #666;
108
+ padding: 2rem;
109
+ }
110
+
111
+ .mutation-inspector__header {
112
+ display: flex;
113
+ justify-content: space-between;
114
+ align-items: center;
115
+ margin-bottom: 1rem;
116
+ padding-bottom: 0.5rem;
117
+ border-bottom: 1px solid #333;
118
+ }
119
+
120
+ .mutation-inspector__title {
121
+ display: flex;
122
+ align-items: center;
123
+ gap: 0.5rem;
124
+ margin: 0;
125
+ font-size: 1rem;
126
+ color: #fff;
127
+ }
128
+
129
+ .mutation-inspector__source-badge {
130
+ padding: 0.125rem 0.5rem;
131
+ border-radius: 4px;
132
+ font-size: 0.75rem;
133
+ font-weight: 600;
134
+ text-transform: uppercase;
135
+ }
136
+
137
+ .mutation-inspector__source-badge[data-source="react"] {
138
+ background: #61dafb;
139
+ color: #000;
140
+ }
141
+
142
+ .mutation-inspector__source-badge[data-source="redux"] {
143
+ background: #764abc;
144
+ color: #fff;
145
+ }
146
+
147
+ .mutation-inspector__source-badge[data-source="zustand"] {
148
+ background: #f59e0b;
149
+ color: #000;
150
+ }
151
+
152
+ .mutation-inspector__source-badge[data-source="api"] {
153
+ background: #3b82f6;
154
+ color: #fff;
155
+ }
156
+
157
+ .mutation-inspector__id {
158
+ color: #666;
159
+ font-family: monospace;
160
+ font-size: 0.75rem;
161
+ }
162
+
163
+ .mutation-inspector__grid {
164
+ display: grid;
165
+ grid-template-columns: 1fr 1fr;
166
+ gap: 1rem;
167
+ margin-bottom: 1rem;
168
+ }
169
+
170
+ .mutation-inspector__section {
171
+ margin-bottom: 1rem;
172
+ }
173
+
174
+ .mutation-inspector__section h4 {
175
+ margin: 0 0 0.5rem 0;
176
+ color: #00d9ff;
177
+ font-size: 0.875rem;
178
+ }
179
+
180
+ .mutation-inspector__details {
181
+ margin: 0;
182
+ display: grid;
183
+ grid-template-columns: auto 1fr;
184
+ gap: 0.25rem 0.5rem;
185
+ }
186
+
187
+ .mutation-inspector__details dt {
188
+ color: #888;
189
+ }
190
+
191
+ .mutation-inspector__details dd {
192
+ margin: 0;
193
+ color: #e0e0e0;
194
+ }
195
+
196
+ .mutation-inspector__code {
197
+ margin: 0;
198
+ padding: 0.75rem;
199
+ background: #0f0f23;
200
+ border-radius: 4px;
201
+ overflow: auto;
202
+ max-height: 200px;
203
+ font-size: 0.8rem;
204
+ }
205
+
206
+ .mutation-inspector__changes,
207
+ .mutation-inspector__stack,
208
+ .mutation-inspector__causes {
209
+ list-style: none;
210
+ margin: 0;
211
+ padding: 0;
212
+ }
213
+
214
+ .mutation-inspector__change {
215
+ display: flex;
216
+ align-items: center;
217
+ gap: 0.5rem;
218
+ padding: 0.25rem 0.5rem;
219
+ border-radius: 4px;
220
+ margin-bottom: 0.25rem;
221
+ }
222
+
223
+ .mutation-inspector__change--add {
224
+ background: rgba(34, 197, 94, 0.1);
225
+ }
226
+
227
+ .mutation-inspector__change--update {
228
+ background: rgba(251, 191, 36, 0.1);
229
+ }
230
+
231
+ .mutation-inspector__change--remove {
232
+ background: rgba(239, 68, 68, 0.1);
233
+ }
234
+
235
+ .mutation-inspector__op {
236
+ font-size: 0.7rem;
237
+ padding: 0.125rem 0.375rem;
238
+ border-radius: 3px;
239
+ background: #333;
240
+ color: #888;
241
+ }
242
+
243
+ .mutation-inspector__frame {
244
+ padding: 0.25rem 0;
245
+ border-bottom: 1px solid #222;
246
+ }
247
+
248
+ .mutation-inspector__frame:last-child {
249
+ border-bottom: none;
250
+ }
251
+
252
+ code {
253
+ font-family: 'Fira Code', 'Monaco', monospace;
254
+ }
255
+ ` })
256
+ ] });
257
+ }
258
+
259
+ // src/core/diff.ts
260
+ function calculateDiff(before, after, path = "") {
261
+ const diffs = [];
262
+ if (before === after) {
263
+ return diffs;
264
+ }
265
+ if (before === null || before === void 0 || typeof before !== "object") {
266
+ if (after === null || after === void 0 || typeof after !== "object") {
267
+ if (before !== after) {
268
+ if (before === void 0) {
269
+ diffs.push({ path: path || "root", operation: "ADD", newValue: after });
270
+ } else if (after === void 0) {
271
+ diffs.push({ path: path || "root", operation: "REMOVE", oldValue: before });
272
+ } else {
273
+ diffs.push({ path: path || "root", operation: "UPDATE", oldValue: before, newValue: after });
274
+ }
275
+ }
276
+ return diffs;
277
+ }
278
+ diffs.push({ path: path || "root", operation: "UPDATE", oldValue: before, newValue: after });
279
+ return diffs;
280
+ }
281
+ if (after === null || after === void 0 || typeof after !== "object") {
282
+ diffs.push({ path: path || "root", operation: "UPDATE", oldValue: before, newValue: after });
283
+ return diffs;
284
+ }
285
+ if (Array.isArray(before) || Array.isArray(after)) {
286
+ if (!Array.isArray(before) || !Array.isArray(after)) {
287
+ diffs.push({ path: path || "root", operation: "UPDATE", oldValue: before, newValue: after });
288
+ return diffs;
289
+ }
290
+ const maxLength = Math.max(before.length, after.length);
291
+ for (let i = 0; i < maxLength; i++) {
292
+ const itemPath = path ? `${path}[${i}]` : `[${i}]`;
293
+ if (i >= before.length) {
294
+ diffs.push({ path: itemPath, operation: "ADD", newValue: after[i] });
295
+ } else if (i >= after.length) {
296
+ diffs.push({ path: itemPath, operation: "REMOVE", oldValue: before[i] });
297
+ } else {
298
+ diffs.push(...calculateDiff(before[i], after[i], itemPath));
299
+ }
300
+ }
301
+ return diffs;
302
+ }
303
+ const beforeObj = before;
304
+ const afterObj = after;
305
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(beforeObj), ...Object.keys(afterObj)]);
306
+ for (const key of allKeys) {
307
+ const keyPath = path ? `${path}.${key}` : key;
308
+ const beforeValue = beforeObj[key];
309
+ const afterValue = afterObj[key];
310
+ if (!(key in beforeObj)) {
311
+ diffs.push({ path: keyPath, operation: "ADD", newValue: afterValue });
312
+ } else if (!(key in afterObj)) {
313
+ diffs.push({ path: keyPath, operation: "REMOVE", oldValue: beforeValue });
314
+ } else {
315
+ diffs.push(...calculateDiff(beforeValue, afterValue, keyPath));
316
+ }
317
+ }
318
+ return diffs;
319
+ }
320
+ function StateDiffViewer({
321
+ previousState,
322
+ nextState,
323
+ diff: preDiff,
324
+ defaultExpandDepth = 3,
325
+ className = ""
326
+ }) {
327
+ const diff = useMemo(
328
+ () => preDiff || calculateDiff(previousState, nextState),
329
+ [previousState, nextState, preDiff]
330
+ );
331
+ const changedPaths = useMemo(
332
+ () => new Set(diff.map((d) => d.path)),
333
+ [diff]
334
+ );
335
+ return /* @__PURE__ */ jsxs("div", { className: `state-diff-viewer ${className}`, children: [
336
+ /* @__PURE__ */ jsxs("div", { className: "state-diff-viewer__panels", children: [
337
+ /* @__PURE__ */ jsxs("div", { className: "state-diff-viewer__panel", children: [
338
+ /* @__PURE__ */ jsx("h3", { className: "state-diff-viewer__title", children: "Before" }),
339
+ /* @__PURE__ */ jsx("div", { className: "state-diff-viewer__content", children: /* @__PURE__ */ jsx(
340
+ StateTree,
341
+ {
342
+ value: previousState,
343
+ path: "",
344
+ changedPaths,
345
+ defaultExpandDepth,
346
+ changeType: "before"
347
+ }
348
+ ) })
349
+ ] }),
350
+ /* @__PURE__ */ jsxs("div", { className: "state-diff-viewer__panel", children: [
351
+ /* @__PURE__ */ jsx("h3", { className: "state-diff-viewer__title", children: "After" }),
352
+ /* @__PURE__ */ jsx("div", { className: "state-diff-viewer__content", children: /* @__PURE__ */ jsx(
353
+ StateTree,
354
+ {
355
+ value: nextState,
356
+ path: "",
357
+ changedPaths,
358
+ defaultExpandDepth,
359
+ changeType: "after"
360
+ }
361
+ ) })
362
+ ] })
363
+ ] }),
364
+ diff.length > 0 && /* @__PURE__ */ jsxs("div", { className: "state-diff-viewer__summary", children: [
365
+ /* @__PURE__ */ jsxs("h4", { children: [
366
+ "Changes (",
367
+ diff.length,
368
+ ")"
369
+ ] }),
370
+ /* @__PURE__ */ jsx("ul", { className: "state-diff-viewer__changes", children: diff.map((change, index) => /* @__PURE__ */ jsxs(
371
+ "li",
372
+ {
373
+ className: `state-diff-viewer__change state-diff-viewer__change--${change.operation.toLowerCase()}`,
374
+ children: [
375
+ /* @__PURE__ */ jsx("code", { children: change.path }),
376
+ /* @__PURE__ */ jsx("span", { className: "state-diff-viewer__change-type", children: change.operation }),
377
+ change.operation === "UPDATE" && /* @__PURE__ */ jsxs(Fragment, { children: [
378
+ /* @__PURE__ */ jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--old", children: formatValue(change.oldValue) }),
379
+ /* @__PURE__ */ jsx("span", { className: "state-diff-viewer__arrow", children: "\u2192" }),
380
+ /* @__PURE__ */ jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--new", children: formatValue(change.newValue) })
381
+ ] }),
382
+ change.operation === "ADD" && /* @__PURE__ */ jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--new", children: formatValue(change.newValue) }),
383
+ change.operation === "REMOVE" && /* @__PURE__ */ jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--old", children: formatValue(change.oldValue) })
384
+ ]
385
+ },
386
+ index
387
+ )) })
388
+ ] }),
389
+ /* @__PURE__ */ jsx("style", { children: `
390
+ .state-diff-viewer {
391
+ font-family: monospace;
392
+ font-size: 0.875rem;
393
+ color: #e0e0e0;
394
+ }
395
+
396
+ .state-diff-viewer__panels {
397
+ display: grid;
398
+ grid-template-columns: 1fr 1fr;
399
+ gap: 1rem;
400
+ }
401
+
402
+ .state-diff-viewer__panel {
403
+ background: #16213e;
404
+ border-radius: 8px;
405
+ overflow: hidden;
406
+ }
407
+
408
+ .state-diff-viewer__title {
409
+ margin: 0;
410
+ padding: 0.75rem 1rem;
411
+ background: #1a1a2e;
412
+ font-size: 0.875rem;
413
+ font-weight: 600;
414
+ color: #00d9ff;
415
+ }
416
+
417
+ .state-diff-viewer__content {
418
+ padding: 1rem;
419
+ max-height: 400px;
420
+ overflow: auto;
421
+ }
422
+
423
+ .state-diff-viewer__summary {
424
+ margin-top: 1rem;
425
+ padding: 1rem;
426
+ background: #16213e;
427
+ border-radius: 8px;
428
+ }
429
+
430
+ .state-diff-viewer__summary h4 {
431
+ margin: 0 0 0.5rem 0;
432
+ color: #00d9ff;
433
+ }
434
+
435
+ .state-diff-viewer__changes {
436
+ list-style: none;
437
+ margin: 0;
438
+ padding: 0;
439
+ }
440
+
441
+ .state-diff-viewer__change {
442
+ display: flex;
443
+ flex-wrap: wrap;
444
+ align-items: center;
445
+ gap: 0.5rem;
446
+ padding: 0.5rem;
447
+ border-radius: 4px;
448
+ margin-bottom: 0.25rem;
449
+ }
450
+
451
+ .state-diff-viewer__change--add {
452
+ background: rgba(34, 197, 94, 0.1);
453
+ border-left: 3px solid #22c55e;
454
+ }
455
+
456
+ .state-diff-viewer__change--update {
457
+ background: rgba(251, 191, 36, 0.1);
458
+ border-left: 3px solid #fbbf24;
459
+ }
460
+
461
+ .state-diff-viewer__change--remove {
462
+ background: rgba(239, 68, 68, 0.1);
463
+ border-left: 3px solid #ef4444;
464
+ }
465
+
466
+ .state-diff-viewer__change-type {
467
+ font-size: 0.7rem;
468
+ padding: 0.125rem 0.375rem;
469
+ border-radius: 3px;
470
+ background: #333;
471
+ color: #888;
472
+ }
473
+
474
+ .state-diff-viewer__value {
475
+ padding: 0.125rem 0.375rem;
476
+ border-radius: 3px;
477
+ }
478
+
479
+ .state-diff-viewer__value--old {
480
+ background: rgba(239, 68, 68, 0.2);
481
+ color: #fca5a5;
482
+ text-decoration: line-through;
483
+ }
484
+
485
+ .state-diff-viewer__value--new {
486
+ background: rgba(34, 197, 94, 0.2);
487
+ color: #86efac;
488
+ }
489
+
490
+ .state-diff-viewer__arrow {
491
+ color: #666;
492
+ }
493
+ ` })
494
+ ] });
495
+ }
496
+ function StateTree({
497
+ value,
498
+ path,
499
+ changedPaths,
500
+ defaultExpandDepth,
501
+ depth = 0,
502
+ changeType
503
+ }) {
504
+ const [isExpanded, setIsExpanded] = useState(depth < defaultExpandDepth);
505
+ const isChanged = path ? changedPaths.has(path) : false;
506
+ const toggle = useCallback(() => setIsExpanded((e) => !e), []);
507
+ if (value === null) {
508
+ return /* @__PURE__ */ jsx("span", { className: "state-tree__null", children: "null" });
509
+ }
510
+ if (value === void 0) {
511
+ return /* @__PURE__ */ jsx("span", { className: "state-tree__undefined", children: "undefined" });
512
+ }
513
+ if (typeof value === "boolean") {
514
+ return /* @__PURE__ */ jsx("span", { className: "state-tree__boolean", children: String(value) });
515
+ }
516
+ if (typeof value === "number") {
517
+ return /* @__PURE__ */ jsx("span", { className: `state-tree__number ${isChanged ? "state-tree--changed" : ""}`, children: String(value) });
518
+ }
519
+ if (typeof value === "string") {
520
+ return /* @__PURE__ */ jsxs("span", { className: `state-tree__string ${isChanged ? "state-tree--changed" : ""}`, children: [
521
+ '"',
522
+ value,
523
+ '"'
524
+ ] });
525
+ }
526
+ if (Array.isArray(value)) {
527
+ if (value.length === 0) {
528
+ return /* @__PURE__ */ jsx("span", { className: "state-tree__empty", children: "[]" });
529
+ }
530
+ return /* @__PURE__ */ jsxs("span", { className: "state-tree__array", children: [
531
+ /* @__PURE__ */ jsxs("span", { className: "state-tree__toggle", onClick: toggle, children: [
532
+ isExpanded ? "\u25BC" : "\u25B6",
533
+ " Array(",
534
+ value.length,
535
+ ")"
536
+ ] }),
537
+ isExpanded && /* @__PURE__ */ jsx("div", { className: "state-tree__children", children: value.map((item, index) => /* @__PURE__ */ jsxs("div", { className: "state-tree__item", children: [
538
+ /* @__PURE__ */ jsxs("span", { className: "state-tree__key", children: [
539
+ index,
540
+ ":"
541
+ ] }),
542
+ /* @__PURE__ */ jsx(
543
+ StateTree,
544
+ {
545
+ value: item,
546
+ path: path ? `${path}[${index}]` : `[${index}]`,
547
+ changedPaths,
548
+ defaultExpandDepth,
549
+ depth: depth + 1,
550
+ changeType
551
+ }
552
+ )
553
+ ] }, index)) })
554
+ ] });
555
+ }
556
+ if (typeof value === "object") {
557
+ const entries = Object.entries(value);
558
+ if (entries.length === 0) {
559
+ return /* @__PURE__ */ jsx("span", { className: "state-tree__empty", children: "{}" });
560
+ }
561
+ return /* @__PURE__ */ jsxs("span", { className: "state-tree__object", children: [
562
+ /* @__PURE__ */ jsxs("span", { className: "state-tree__toggle", onClick: toggle, children: [
563
+ isExpanded ? "\u25BC" : "\u25B6",
564
+ " Object(",
565
+ entries.length,
566
+ ")"
567
+ ] }),
568
+ isExpanded && /* @__PURE__ */ jsx("div", { className: "state-tree__children", children: entries.map(([key, val]) => /* @__PURE__ */ jsxs("div", { className: "state-tree__item", children: [
569
+ /* @__PURE__ */ jsxs("span", { className: "state-tree__key", children: [
570
+ key,
571
+ ":"
572
+ ] }),
573
+ /* @__PURE__ */ jsx(
574
+ StateTree,
575
+ {
576
+ value: val,
577
+ path: path ? `${path}.${key}` : key,
578
+ changedPaths,
579
+ defaultExpandDepth,
580
+ depth: depth + 1,
581
+ changeType
582
+ }
583
+ )
584
+ ] }, key)) })
585
+ ] });
586
+ }
587
+ return /* @__PURE__ */ jsx("span", { children: String(value) });
588
+ }
589
+ function formatValue(value) {
590
+ if (value === void 0) return "undefined";
591
+ if (value === null) return "null";
592
+ if (typeof value === "object") {
593
+ try {
594
+ const str = JSON.stringify(value);
595
+ return str.length > 50 ? str.slice(0, 50) + "..." : str;
596
+ } catch {
597
+ return "[Object]";
598
+ }
599
+ }
600
+ return String(value);
601
+ }
602
+ var sourceColors = {
603
+ react: "#61dafb",
604
+ redux: "#764abc",
605
+ zustand: "#f59e0b",
606
+ express: "#68a063",
607
+ websocket: "#8b5cf6",
608
+ api: "#3b82f6",
609
+ custom: "#6b7280"
610
+ };
611
+ function TimelineScrubber({
612
+ mutations,
613
+ selectedIndex,
614
+ onSelect,
615
+ isPlaying = false,
616
+ onPlay,
617
+ onPause,
618
+ onStepForward,
619
+ onStepBackward,
620
+ className = ""
621
+ }) {
622
+ const handleSliderChange = useCallback(
623
+ (e) => {
624
+ const index = parseInt(e.target.value, 10);
625
+ if (mutations[index]) {
626
+ onSelect(index, mutations[index]);
627
+ }
628
+ },
629
+ [mutations, onSelect]
630
+ );
631
+ const handleMarkerClick = useCallback(
632
+ (index) => {
633
+ if (mutations[index]) {
634
+ onSelect(index, mutations[index]);
635
+ }
636
+ },
637
+ [mutations, onSelect]
638
+ );
639
+ const formatTime = (timestamp) => {
640
+ const date = new Date(timestamp);
641
+ return date.toLocaleTimeString("en-US", {
642
+ hour12: false,
643
+ hour: "2-digit",
644
+ minute: "2-digit",
645
+ second: "2-digit",
646
+ fractionalSecondDigits: 3
647
+ });
648
+ };
649
+ if (mutations.length === 0) {
650
+ return /* @__PURE__ */ jsx("div", { className: `timeline-scrubber timeline-scrubber--empty ${className}`, children: /* @__PURE__ */ jsx("p", { children: "No mutations recorded yet" }) });
651
+ }
652
+ const selectedMutation = mutations[selectedIndex];
653
+ return /* @__PURE__ */ jsxs("div", { className: `timeline-scrubber ${className}`, children: [
654
+ /* @__PURE__ */ jsxs("div", { className: "timeline-scrubber__header", children: [
655
+ /* @__PURE__ */ jsxs("span", { className: "timeline-scrubber__count", children: [
656
+ selectedIndex + 1,
657
+ " / ",
658
+ mutations.length,
659
+ " mutations"
660
+ ] }),
661
+ selectedMutation && /* @__PURE__ */ jsx("span", { className: "timeline-scrubber__time", children: formatTime(selectedMutation.timestamp) })
662
+ ] }),
663
+ /* @__PURE__ */ jsxs("div", { className: "timeline-scrubber__track", children: [
664
+ mutations.map((mutation, index) => /* @__PURE__ */ jsx(
665
+ "div",
666
+ {
667
+ className: `timeline-scrubber__marker ${index === selectedIndex ? "timeline-scrubber__marker--selected" : ""}`,
668
+ style: {
669
+ left: `${index / (mutations.length - 1 || 1) * 100}%`,
670
+ backgroundColor: sourceColors[mutation.source] || sourceColors.custom
671
+ },
672
+ onClick: () => handleMarkerClick(index),
673
+ title: `${mutation.source}: ${mutation.actionType}${mutation.component ? ` @ ${mutation.component}` : ""}`
674
+ },
675
+ mutation.id
676
+ )),
677
+ /* @__PURE__ */ jsx(
678
+ "div",
679
+ {
680
+ className: "timeline-scrubber__position",
681
+ style: {
682
+ left: `${selectedIndex / (mutations.length - 1 || 1) * 100}%`
683
+ }
684
+ }
685
+ )
686
+ ] }),
687
+ /* @__PURE__ */ jsx(
688
+ "input",
689
+ {
690
+ type: "range",
691
+ min: 0,
692
+ max: mutations.length - 1,
693
+ value: selectedIndex,
694
+ onChange: handleSliderChange,
695
+ className: "timeline-scrubber__slider"
696
+ }
697
+ ),
698
+ /* @__PURE__ */ jsxs("div", { className: "timeline-scrubber__controls", children: [
699
+ /* @__PURE__ */ jsx(
700
+ "button",
701
+ {
702
+ onClick: onStepBackward,
703
+ disabled: selectedIndex === 0,
704
+ className: "timeline-scrubber__button",
705
+ title: "Step Backward",
706
+ children: "\u23EE"
707
+ }
708
+ ),
709
+ isPlaying ? /* @__PURE__ */ jsx(
710
+ "button",
711
+ {
712
+ onClick: onPause,
713
+ className: "timeline-scrubber__button timeline-scrubber__button--primary",
714
+ title: "Pause",
715
+ children: "\u23F8"
716
+ }
717
+ ) : /* @__PURE__ */ jsx(
718
+ "button",
719
+ {
720
+ onClick: onPlay,
721
+ disabled: selectedIndex === mutations.length - 1,
722
+ className: "timeline-scrubber__button timeline-scrubber__button--primary",
723
+ title: "Play",
724
+ children: "\u25B6"
725
+ }
726
+ ),
727
+ /* @__PURE__ */ jsx(
728
+ "button",
729
+ {
730
+ onClick: onStepForward,
731
+ disabled: selectedIndex === mutations.length - 1,
732
+ className: "timeline-scrubber__button",
733
+ title: "Step Forward",
734
+ children: "\u23ED"
735
+ }
736
+ )
737
+ ] }),
738
+ /* @__PURE__ */ jsx("div", { className: "timeline-scrubber__legend", children: Object.entries(sourceColors).map(([source, color]) => /* @__PURE__ */ jsxs("span", { className: "timeline-scrubber__legend-item", children: [
739
+ /* @__PURE__ */ jsx(
740
+ "span",
741
+ {
742
+ className: "timeline-scrubber__legend-dot",
743
+ style: { backgroundColor: color }
744
+ }
745
+ ),
746
+ source
747
+ ] }, source)) }),
748
+ /* @__PURE__ */ jsx("style", { children: `
749
+ .timeline-scrubber {
750
+ padding: 1rem;
751
+ background: #1a1a2e;
752
+ border-radius: 8px;
753
+ color: #fff;
754
+ }
755
+
756
+ .timeline-scrubber--empty {
757
+ text-align: center;
758
+ color: #888;
759
+ }
760
+
761
+ .timeline-scrubber__header {
762
+ display: flex;
763
+ justify-content: space-between;
764
+ margin-bottom: 0.5rem;
765
+ font-size: 0.875rem;
766
+ }
767
+
768
+ .timeline-scrubber__count {
769
+ color: #00d9ff;
770
+ font-weight: 600;
771
+ }
772
+
773
+ .timeline-scrubber__time {
774
+ color: #888;
775
+ font-family: monospace;
776
+ }
777
+
778
+ .timeline-scrubber__track {
779
+ position: relative;
780
+ height: 24px;
781
+ background: #16213e;
782
+ border-radius: 4px;
783
+ margin-bottom: 0.5rem;
784
+ }
785
+
786
+ .timeline-scrubber__marker {
787
+ position: absolute;
788
+ width: 4px;
789
+ height: 16px;
790
+ top: 4px;
791
+ border-radius: 2px;
792
+ cursor: pointer;
793
+ transition: transform 0.1s, opacity 0.1s;
794
+ opacity: 0.7;
795
+ }
796
+
797
+ .timeline-scrubber__marker:hover {
798
+ transform: scaleY(1.2);
799
+ opacity: 1;
800
+ }
801
+
802
+ .timeline-scrubber__marker--selected {
803
+ opacity: 1;
804
+ box-shadow: 0 0 8px currentColor;
805
+ }
806
+
807
+ .timeline-scrubber__position {
808
+ position: absolute;
809
+ width: 2px;
810
+ height: 24px;
811
+ top: 0;
812
+ background: #fff;
813
+ border-radius: 1px;
814
+ pointer-events: none;
815
+ }
816
+
817
+ .timeline-scrubber__slider {
818
+ width: 100%;
819
+ margin: 0.5rem 0;
820
+ cursor: pointer;
821
+ }
822
+
823
+ .timeline-scrubber__controls {
824
+ display: flex;
825
+ justify-content: center;
826
+ gap: 0.5rem;
827
+ margin: 0.5rem 0;
828
+ }
829
+
830
+ .timeline-scrubber__button {
831
+ padding: 0.5rem 1rem;
832
+ border: none;
833
+ border-radius: 4px;
834
+ background: #16213e;
835
+ color: #fff;
836
+ cursor: pointer;
837
+ font-size: 1rem;
838
+ transition: background 0.2s;
839
+ }
840
+
841
+ .timeline-scrubber__button:hover:not(:disabled) {
842
+ background: #1f2b4d;
843
+ }
844
+
845
+ .timeline-scrubber__button:disabled {
846
+ opacity: 0.5;
847
+ cursor: not-allowed;
848
+ }
849
+
850
+ .timeline-scrubber__button--primary {
851
+ background: #00d9ff;
852
+ color: #000;
853
+ }
854
+
855
+ .timeline-scrubber__button--primary:hover:not(:disabled) {
856
+ background: #00b8e6;
857
+ }
858
+
859
+ .timeline-scrubber__legend {
860
+ display: flex;
861
+ flex-wrap: wrap;
862
+ gap: 1rem;
863
+ margin-top: 0.5rem;
864
+ font-size: 0.75rem;
865
+ }
866
+
867
+ .timeline-scrubber__legend-item {
868
+ display: flex;
869
+ align-items: center;
870
+ gap: 0.25rem;
871
+ color: #888;
872
+ }
873
+
874
+ .timeline-scrubber__legend-dot {
875
+ width: 8px;
876
+ height: 8px;
877
+ border-radius: 50%;
878
+ }
879
+ ` })
880
+ ] });
881
+ }
882
+ var DashboardContext = React2.createContext(null);
883
+ function DashboardProvider({
884
+ serverUrl = "http://localhost:8080",
885
+ children
886
+ }) {
887
+ const [state, setState] = useState({
888
+ serverUrl,
889
+ sessions: [],
890
+ currentSession: null,
891
+ timeline: [],
892
+ selectedMutation: null,
893
+ isLoading: false,
894
+ error: null
895
+ });
896
+ const loadSessions = async () => {
897
+ setState((s) => ({ ...s, isLoading: true, error: null }));
898
+ try {
899
+ const res = await fetch(`${serverUrl}/api/sessions`);
900
+ const data = await res.json();
901
+ setState((s) => ({ ...s, sessions: data.sessions, isLoading: false }));
902
+ } catch (error) {
903
+ setState((s) => ({ ...s, error: String(error), isLoading: false }));
904
+ }
905
+ };
906
+ const selectSession = async (sessionId) => {
907
+ const session = state.sessions.find((s) => s.id === sessionId);
908
+ if (!session) return;
909
+ setState((s) => ({ ...s, currentSession: session, isLoading: true }));
910
+ try {
911
+ const res = await fetch(`${serverUrl}/api/sessions/${sessionId}/timeline`);
912
+ const data = await res.json();
913
+ setState((s) => ({
914
+ ...s,
915
+ timeline: data.timeline,
916
+ selectedMutation: data.timeline[0] || null,
917
+ isLoading: false
918
+ }));
919
+ } catch (error) {
920
+ setState((s) => ({ ...s, error: String(error), isLoading: false }));
921
+ }
922
+ };
923
+ const selectMutation = (mutation) => {
924
+ setState((s) => ({ ...s, selectedMutation: mutation }));
925
+ };
926
+ const refreshTimeline = async () => {
927
+ if (!state.currentSession) return;
928
+ await selectSession(state.currentSession.id);
929
+ };
930
+ useEffect(() => {
931
+ loadSessions();
932
+ }, [serverUrl]);
933
+ const value = {
934
+ ...state,
935
+ loadSessions,
936
+ selectSession,
937
+ selectMutation,
938
+ refreshTimeline
939
+ };
940
+ return /* @__PURE__ */ jsx(DashboardContext.Provider, { value, children });
941
+ }
942
+ function useDashboard() {
943
+ const context = React2.useContext(DashboardContext);
944
+ if (!context) {
945
+ throw new Error("useDashboard must be used within a DashboardProvider");
946
+ }
947
+ return context;
948
+ }
949
+ function usePlayback(timeline) {
950
+ const [currentIndex, setCurrentIndex] = useState(0);
951
+ const [isPlaying, setIsPlaying] = useState(false);
952
+ const [playbackSpeed, setPlaybackSpeed] = useState(1);
953
+ useEffect(() => {
954
+ if (!isPlaying || timeline.length === 0) return;
955
+ const interval = setInterval(() => {
956
+ setCurrentIndex((i) => {
957
+ if (i >= timeline.length - 1) {
958
+ setIsPlaying(false);
959
+ return i;
960
+ }
961
+ return i + 1;
962
+ });
963
+ }, 500 / playbackSpeed);
964
+ return () => clearInterval(interval);
965
+ }, [isPlaying, timeline.length, playbackSpeed]);
966
+ return {
967
+ currentIndex,
968
+ currentMutation: timeline[currentIndex] || null,
969
+ isPlaying,
970
+ play: () => setIsPlaying(true),
971
+ pause: () => setIsPlaying(false),
972
+ stepForward: () => setCurrentIndex((i) => Math.min(i + 1, timeline.length - 1)),
973
+ stepBackward: () => setCurrentIndex((i) => Math.max(i - 1, 0)),
974
+ goTo: (index) => setCurrentIndex(Math.max(0, Math.min(index, timeline.length - 1))),
975
+ setSpeed: setPlaybackSpeed,
976
+ playbackSpeed
977
+ };
978
+ }
979
+ function StateSurgeonDashboard({ serverUrl = "http://localhost:8080" }) {
980
+ return /* @__PURE__ */ jsx(DashboardProvider, { serverUrl, children: /* @__PURE__ */ jsx(DashboardContent, {}) });
981
+ }
982
+ function DashboardContent() {
983
+ const {
984
+ sessions,
985
+ currentSession,
986
+ timeline,
987
+ selectedMutation,
988
+ isLoading,
989
+ error,
990
+ selectSession,
991
+ selectMutation,
992
+ loadSessions
993
+ } = useDashboard();
994
+ const playback = usePlayback(timeline);
995
+ React2.useEffect(() => {
996
+ if (playback.currentMutation) {
997
+ selectMutation(playback.currentMutation);
998
+ }
999
+ }, [playback.currentMutation, selectMutation]);
1000
+ return /* @__PURE__ */ jsxs("div", { className: "surgeon-dashboard", children: [
1001
+ /* @__PURE__ */ jsxs("header", { className: "surgeon-dashboard__header", children: [
1002
+ /* @__PURE__ */ jsx("h1", { className: "surgeon-dashboard__logo", children: "\u{1F52C} State Surgeon" }),
1003
+ /* @__PURE__ */ jsxs("div", { className: "surgeon-dashboard__session-select", children: [
1004
+ /* @__PURE__ */ jsxs(
1005
+ "select",
1006
+ {
1007
+ value: currentSession?.id || "",
1008
+ onChange: (e) => selectSession(e.target.value),
1009
+ disabled: isLoading,
1010
+ children: [
1011
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select a session..." }),
1012
+ sessions.map((session) => /* @__PURE__ */ jsxs("option", { value: session.id, children: [
1013
+ session.appId,
1014
+ " - ",
1015
+ session.id.slice(0, 16),
1016
+ "... (",
1017
+ session.mutationCount,
1018
+ " mutations)"
1019
+ ] }, session.id))
1020
+ ]
1021
+ }
1022
+ ),
1023
+ /* @__PURE__ */ jsx("button", { onClick: loadSessions, disabled: isLoading, children: "\u{1F504}" })
1024
+ ] })
1025
+ ] }),
1026
+ error && /* @__PURE__ */ jsx("div", { className: "surgeon-dashboard__error", children: /* @__PURE__ */ jsxs("p", { children: [
1027
+ "Error: ",
1028
+ error
1029
+ ] }) }),
1030
+ isLoading && /* @__PURE__ */ jsx("div", { className: "surgeon-dashboard__loading", children: /* @__PURE__ */ jsx("p", { children: "Loading..." }) }),
1031
+ currentSession && !isLoading && /* @__PURE__ */ jsxs("div", { className: "surgeon-dashboard__main", children: [
1032
+ /* @__PURE__ */ jsx("section", { className: "surgeon-dashboard__timeline", children: /* @__PURE__ */ jsx(
1033
+ TimelineScrubber,
1034
+ {
1035
+ mutations: timeline,
1036
+ selectedIndex: playback.currentIndex,
1037
+ onSelect: (index, mutation) => {
1038
+ playback.goTo(index);
1039
+ selectMutation(mutation);
1040
+ },
1041
+ isPlaying: playback.isPlaying,
1042
+ onPlay: playback.play,
1043
+ onPause: playback.pause,
1044
+ onStepForward: playback.stepForward,
1045
+ onStepBackward: playback.stepBackward
1046
+ }
1047
+ ) }),
1048
+ /* @__PURE__ */ jsxs("div", { className: "surgeon-dashboard__content", children: [
1049
+ /* @__PURE__ */ jsx("section", { className: "surgeon-dashboard__diff", children: selectedMutation && /* @__PURE__ */ jsx(
1050
+ StateDiffViewer,
1051
+ {
1052
+ previousState: selectedMutation.previousState,
1053
+ nextState: selectedMutation.nextState,
1054
+ diff: selectedMutation.diff
1055
+ }
1056
+ ) }),
1057
+ /* @__PURE__ */ jsx("section", { className: "surgeon-dashboard__inspector", children: /* @__PURE__ */ jsx(MutationInspector, { mutation: selectedMutation }) })
1058
+ ] })
1059
+ ] }),
1060
+ !currentSession && !isLoading && sessions.length === 0 && /* @__PURE__ */ jsxs("div", { className: "surgeon-dashboard__empty", children: [
1061
+ /* @__PURE__ */ jsx("h2", { children: "No Sessions Yet" }),
1062
+ /* @__PURE__ */ jsx("p", { children: "Connect your application to start capturing mutations." }),
1063
+ /* @__PURE__ */ jsx("pre", { children: `import { StateSurgeonClient, instrumentReact } from 'state-surgeon/instrument';
1064
+
1065
+ // Initialize the client
1066
+ const client = new StateSurgeonClient({
1067
+ serverUrl: 'ws://localhost:8081',
1068
+ appId: 'my-app',
1069
+ });
1070
+
1071
+ // Instrument React
1072
+ instrumentReact(React);` })
1073
+ ] }),
1074
+ /* @__PURE__ */ jsx("style", { children: `
1075
+ .surgeon-dashboard {
1076
+ min-height: 100vh;
1077
+ background: #0f0f23;
1078
+ color: #e0e0e0;
1079
+ font-family: system-ui, -apple-system, sans-serif;
1080
+ }
1081
+
1082
+ .surgeon-dashboard__header {
1083
+ display: flex;
1084
+ justify-content: space-between;
1085
+ align-items: center;
1086
+ padding: 1rem 2rem;
1087
+ background: #1a1a2e;
1088
+ border-bottom: 1px solid #333;
1089
+ }
1090
+
1091
+ .surgeon-dashboard__logo {
1092
+ margin: 0;
1093
+ font-size: 1.5rem;
1094
+ color: #00d9ff;
1095
+ }
1096
+
1097
+ .surgeon-dashboard__session-select {
1098
+ display: flex;
1099
+ gap: 0.5rem;
1100
+ }
1101
+
1102
+ .surgeon-dashboard__session-select select {
1103
+ padding: 0.5rem 1rem;
1104
+ border: 1px solid #333;
1105
+ border-radius: 4px;
1106
+ background: #16213e;
1107
+ color: #e0e0e0;
1108
+ min-width: 300px;
1109
+ }
1110
+
1111
+ .surgeon-dashboard__session-select button {
1112
+ padding: 0.5rem 1rem;
1113
+ border: 1px solid #333;
1114
+ border-radius: 4px;
1115
+ background: #16213e;
1116
+ color: #e0e0e0;
1117
+ cursor: pointer;
1118
+ }
1119
+
1120
+ .surgeon-dashboard__session-select button:hover {
1121
+ background: #1f2b4d;
1122
+ }
1123
+
1124
+ .surgeon-dashboard__error {
1125
+ padding: 1rem 2rem;
1126
+ background: rgba(239, 68, 68, 0.1);
1127
+ border-bottom: 1px solid #ef4444;
1128
+ color: #fca5a5;
1129
+ }
1130
+
1131
+ .surgeon-dashboard__loading {
1132
+ padding: 2rem;
1133
+ text-align: center;
1134
+ color: #888;
1135
+ }
1136
+
1137
+ .surgeon-dashboard__main {
1138
+ padding: 1rem 2rem;
1139
+ }
1140
+
1141
+ .surgeon-dashboard__timeline {
1142
+ margin-bottom: 1rem;
1143
+ }
1144
+
1145
+ .surgeon-dashboard__content {
1146
+ display: grid;
1147
+ grid-template-columns: 2fr 1fr;
1148
+ gap: 1rem;
1149
+ }
1150
+
1151
+ .surgeon-dashboard__empty {
1152
+ padding: 4rem 2rem;
1153
+ text-align: center;
1154
+ }
1155
+
1156
+ .surgeon-dashboard__empty h2 {
1157
+ color: #888;
1158
+ margin-bottom: 1rem;
1159
+ }
1160
+
1161
+ .surgeon-dashboard__empty pre {
1162
+ display: inline-block;
1163
+ text-align: left;
1164
+ padding: 1.5rem;
1165
+ background: #16213e;
1166
+ border-radius: 8px;
1167
+ font-size: 0.875rem;
1168
+ }
1169
+
1170
+ @media (max-width: 1024px) {
1171
+ .surgeon-dashboard__content {
1172
+ grid-template-columns: 1fr;
1173
+ }
1174
+ }
1175
+ ` })
1176
+ ] });
1177
+ }
1178
+
1179
+ export { DashboardProvider, MutationInspector, StateDiffViewer, StateSurgeonDashboard, TimelineScrubber, useDashboard };
1180
+ //# sourceMappingURL=index.mjs.map
1181
+ //# sourceMappingURL=index.mjs.map