state-surgeon 1.0.0 → 1.1.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.
@@ -323,13 +323,41 @@ function calculateDiff(before, after, path = "") {
323
323
  }
324
324
  return diffs;
325
325
  }
326
+ function detectAnomaly(value) {
327
+ if (typeof value === "number" && isNaN(value)) return "nan";
328
+ if (value === void 0) return "undefined";
329
+ if (value === null) return "null";
330
+ if (value === "") return "empty-string";
331
+ if (typeof value === "number" && value < 0) return "negative";
332
+ return null;
333
+ }
334
+ function getAnomalyBadge(anomaly) {
335
+ switch (anomaly) {
336
+ case "nan":
337
+ return "\u26A0\uFE0F NaN";
338
+ case "undefined":
339
+ return "\u26A0\uFE0F undefined";
340
+ case "null":
341
+ return "\u26A1 null";
342
+ case "empty-string":
343
+ return "\u{1F4ED} empty";
344
+ case "negative":
345
+ return "\u2796 negative";
346
+ default:
347
+ return "";
348
+ }
349
+ }
326
350
  function StateDiffViewer({
327
351
  previousState,
328
352
  nextState,
329
353
  diff: preDiff,
330
354
  defaultExpandDepth = 3,
355
+ viewMode = "split",
331
356
  className = ""
332
357
  }) {
358
+ const [activeTab, setActiveTab] = React2.useState("diff");
359
+ const [searchQuery, setSearchQuery] = React2.useState("");
360
+ const [copiedPath, setCopiedPath] = React2.useState(null);
333
361
  const diff = React2.useMemo(
334
362
  () => preDiff || calculateDiff(previousState, nextState),
335
363
  [previousState, nextState, preDiff]
@@ -338,11 +366,165 @@ function StateDiffViewer({
338
366
  () => new Set(diff.map((d) => d.path)),
339
367
  [diff]
340
368
  );
341
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `state-diff-viewer ${className}`, children: [
342
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-diff-viewer__panels", children: [
343
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-diff-viewer__panel", children: [
344
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "state-diff-viewer__title", children: "Before" }),
345
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "state-diff-viewer__content", children: /* @__PURE__ */ jsxRuntime.jsx(
369
+ const groupedDiffs = React2.useMemo(() => {
370
+ const adds = diff.filter((d) => d.operation === "ADD");
371
+ const updates = diff.filter((d) => d.operation === "UPDATE");
372
+ const removes = diff.filter((d) => d.operation === "REMOVE");
373
+ return { adds, updates, removes };
374
+ }, [diff]);
375
+ const anomalies = React2.useMemo(() => {
376
+ const result = [];
377
+ for (const d of diff) {
378
+ if (d.operation !== "REMOVE") {
379
+ const anomaly = detectAnomaly(d.newValue);
380
+ if (anomaly) {
381
+ result.push({ path: d.path, type: anomaly, value: d.newValue });
382
+ }
383
+ }
384
+ }
385
+ return result;
386
+ }, [diff]);
387
+ const copyToClipboard = React2.useCallback((value, path) => {
388
+ const text = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
389
+ navigator.clipboard.writeText(text);
390
+ setCopiedPath(path);
391
+ setTimeout(() => setCopiedPath(null), 2e3);
392
+ }, []);
393
+ const copyFullState = React2.useCallback((state, label) => {
394
+ navigator.clipboard.writeText(JSON.stringify(state, null, 2));
395
+ setCopiedPath(label);
396
+ setTimeout(() => setCopiedPath(null), 2e3);
397
+ }, []);
398
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `state-diff-viewer-enhanced ${className}`, children: [
399
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-header", children: [
400
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-tabs", children: [
401
+ /* @__PURE__ */ jsxRuntime.jsxs(
402
+ "button",
403
+ {
404
+ className: `sdv-tab ${activeTab === "diff" ? "active" : ""}`,
405
+ onClick: () => setActiveTab("diff"),
406
+ children: [
407
+ "\u{1F4CA} Changes (",
408
+ diff.length,
409
+ ")"
410
+ ]
411
+ }
412
+ ),
413
+ /* @__PURE__ */ jsxRuntime.jsx(
414
+ "button",
415
+ {
416
+ className: `sdv-tab ${activeTab === "before" ? "active" : ""}`,
417
+ onClick: () => setActiveTab("before"),
418
+ children: "\u23EA Before"
419
+ }
420
+ ),
421
+ /* @__PURE__ */ jsxRuntime.jsx(
422
+ "button",
423
+ {
424
+ className: `sdv-tab ${activeTab === "after" ? "active" : ""}`,
425
+ onClick: () => setActiveTab("after"),
426
+ children: "\u23E9 After"
427
+ }
428
+ ),
429
+ /* @__PURE__ */ jsxRuntime.jsx(
430
+ "button",
431
+ {
432
+ className: `sdv-tab ${activeTab === "raw" ? "active" : ""}`,
433
+ onClick: () => setActiveTab("raw"),
434
+ children: "\u{1F4DD} Raw JSON"
435
+ }
436
+ )
437
+ ] }),
438
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
439
+ "input",
440
+ {
441
+ type: "text",
442
+ placeholder: "\u{1F50D} Search paths...",
443
+ value: searchQuery,
444
+ onChange: (e) => setSearchQuery(e.target.value),
445
+ className: "sdv-search"
446
+ }
447
+ ) })
448
+ ] }),
449
+ anomalies.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-anomalies", children: [
450
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-anomaly-header", children: [
451
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-anomaly-icon", children: "\u{1F6A8}" }),
452
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
453
+ "Potential Issues Detected (",
454
+ anomalies.length,
455
+ ")"
456
+ ] })
457
+ ] }),
458
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-anomaly-list", children: anomalies.map((a, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `sdv-anomaly-item sdv-anomaly-${a.type}`, children: [
459
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: a.path }),
460
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-anomaly-badge", children: getAnomalyBadge(a.type) })
461
+ ] }, i)) })
462
+ ] }),
463
+ activeTab === "diff" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-summary", children: [
464
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sdv-summary-item sdv-add", children: [
465
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-dot" }),
466
+ groupedDiffs.adds.length,
467
+ " added"
468
+ ] }),
469
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sdv-summary-item sdv-update", children: [
470
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-dot" }),
471
+ groupedDiffs.updates.length,
472
+ " changed"
473
+ ] }),
474
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sdv-summary-item sdv-remove", children: [
475
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-dot" }),
476
+ groupedDiffs.removes.length,
477
+ " removed"
478
+ ] })
479
+ ] }),
480
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-content", children: [
481
+ activeTab === "diff" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-diff-list", children: diff.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-empty", children: "No changes detected" }) : diff.filter((d) => !searchQuery || d.path.toLowerCase().includes(searchQuery.toLowerCase())).map((change, index) => /* @__PURE__ */ jsxRuntime.jsxs(
482
+ "div",
483
+ {
484
+ className: `sdv-diff-item sdv-diff-${change.operation.toLowerCase()}`,
485
+ children: [
486
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-diff-header", children: [
487
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-diff-op", children: change.operation }),
488
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "sdv-diff-path", children: change.path }),
489
+ /* @__PURE__ */ jsxRuntime.jsx(
490
+ "button",
491
+ {
492
+ className: "sdv-copy-btn",
493
+ onClick: () => copyToClipboard(change.newValue ?? change.oldValue, change.path),
494
+ title: "Copy value",
495
+ children: copiedPath === change.path ? "\u2713" : "\u{1F4CB}"
496
+ }
497
+ )
498
+ ] }),
499
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-diff-values", children: [
500
+ change.operation === "UPDATE" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
501
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-value sdv-value-old", children: [
502
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-value-label", children: "Before:" }),
503
+ /* @__PURE__ */ jsxRuntime.jsx(ValueDisplay, { value: change.oldValue })
504
+ ] }),
505
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-arrow", children: "\u2192" }),
506
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-value sdv-value-new", children: [
507
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sdv-value-label", children: "After:" }),
508
+ /* @__PURE__ */ jsxRuntime.jsx(ValueDisplay, { value: change.newValue })
509
+ ] })
510
+ ] }),
511
+ change.operation === "ADD" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-value sdv-value-new", children: /* @__PURE__ */ jsxRuntime.jsx(ValueDisplay, { value: change.newValue }) }),
512
+ change.operation === "REMOVE" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-value sdv-value-old", children: /* @__PURE__ */ jsxRuntime.jsx(ValueDisplay, { value: change.oldValue }) })
513
+ ] })
514
+ ]
515
+ },
516
+ index
517
+ )) }),
518
+ activeTab === "before" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-state-view", children: [
519
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-state-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
520
+ "button",
521
+ {
522
+ onClick: () => copyFullState(previousState, "before"),
523
+ className: "sdv-copy-full",
524
+ children: copiedPath === "before" ? "\u2713 Copied!" : "\u{1F4CB} Copy State"
525
+ }
526
+ ) }),
527
+ /* @__PURE__ */ jsxRuntime.jsx(
346
528
  StateTree,
347
529
  {
348
530
  value: previousState,
@@ -351,11 +533,18 @@ function StateDiffViewer({
351
533
  defaultExpandDepth,
352
534
  changeType: "before"
353
535
  }
354
- ) })
536
+ )
355
537
  ] }),
356
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-diff-viewer__panel", children: [
357
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "state-diff-viewer__title", children: "After" }),
358
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "state-diff-viewer__content", children: /* @__PURE__ */ jsxRuntime.jsx(
538
+ activeTab === "after" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-state-view", children: [
539
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-state-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
540
+ "button",
541
+ {
542
+ onClick: () => copyFullState(nextState, "after"),
543
+ className: "sdv-copy-full",
544
+ children: copiedPath === "after" ? "\u2713 Copied!" : "\u{1F4CB} Copy State"
545
+ }
546
+ ) }),
547
+ /* @__PURE__ */ jsxRuntime.jsx(
359
548
  StateTree,
360
549
  {
361
550
  value: nextState,
@@ -364,141 +553,404 @@ function StateDiffViewer({
364
553
  defaultExpandDepth,
365
554
  changeType: "after"
366
555
  }
367
- ) })
368
- ] })
369
- ] }),
370
- diff.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-diff-viewer__summary", children: [
371
- /* @__PURE__ */ jsxRuntime.jsxs("h4", { children: [
372
- "Changes (",
373
- diff.length,
374
- ")"
556
+ )
375
557
  ] }),
376
- /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "state-diff-viewer__changes", children: diff.map((change, index) => /* @__PURE__ */ jsxRuntime.jsxs(
377
- "li",
378
- {
379
- className: `state-diff-viewer__change state-diff-viewer__change--${change.operation.toLowerCase()}`,
380
- children: [
381
- /* @__PURE__ */ jsxRuntime.jsx("code", { children: change.path }),
382
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__change-type", children: change.operation }),
383
- change.operation === "UPDATE" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
384
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--old", children: formatValue(change.oldValue) }),
385
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__arrow", children: "\u2192" }),
386
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--new", children: formatValue(change.newValue) })
387
- ] }),
388
- change.operation === "ADD" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--new", children: formatValue(change.newValue) }),
389
- change.operation === "REMOVE" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-diff-viewer__value state-diff-viewer__value--old", children: formatValue(change.oldValue) })
390
- ]
391
- },
392
- index
393
- )) })
558
+ activeTab === "raw" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sdv-raw-view", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-raw-panels", children: [
559
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-raw-panel", children: [
560
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { children: "Before" }),
561
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { children: JSON.stringify(previousState, null, 2) })
562
+ ] }),
563
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sdv-raw-panel", children: [
564
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { children: "After" }),
565
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { children: JSON.stringify(nextState, null, 2) })
566
+ ] })
567
+ ] }) })
394
568
  ] }),
395
569
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
396
- .state-diff-viewer {
397
- font-family: monospace;
398
- font-size: 0.875rem;
570
+ .state-diff-viewer-enhanced {
571
+ background: #1a1a2e;
572
+ border-radius: 12px;
573
+ overflow: hidden;
574
+ font-family: system-ui, sans-serif;
399
575
  color: #e0e0e0;
400
576
  }
401
577
 
402
- .state-diff-viewer__panels {
403
- display: grid;
404
- grid-template-columns: 1fr 1fr;
405
- gap: 1rem;
578
+ .sdv-header {
579
+ display: flex;
580
+ justify-content: space-between;
581
+ align-items: center;
582
+ padding: 0.75rem 1rem;
583
+ background: #16213e;
584
+ border-bottom: 1px solid #333;
585
+ flex-wrap: wrap;
586
+ gap: 0.5rem;
406
587
  }
407
588
 
408
- .state-diff-viewer__panel {
409
- background: #16213e;
410
- border-radius: 8px;
411
- overflow: hidden;
589
+ .sdv-tabs {
590
+ display: flex;
591
+ gap: 0.25rem;
412
592
  }
413
593
 
414
- .state-diff-viewer__title {
415
- margin: 0;
416
- padding: 0.75rem 1rem;
417
- background: #1a1a2e;
594
+ .sdv-tab {
595
+ padding: 0.5rem 1rem;
596
+ border: none;
597
+ background: transparent;
598
+ color: #888;
599
+ cursor: pointer;
600
+ border-radius: 6px;
418
601
  font-size: 0.875rem;
419
- font-weight: 600;
602
+ transition: all 0.2s;
603
+ }
604
+
605
+ .sdv-tab:hover {
606
+ background: #1f2b4d;
607
+ color: #ccc;
608
+ }
609
+
610
+ .sdv-tab.active {
611
+ background: #00d9ff20;
420
612
  color: #00d9ff;
421
613
  }
422
614
 
423
- .state-diff-viewer__content {
424
- padding: 1rem;
425
- max-height: 400px;
426
- overflow: auto;
615
+ .sdv-search {
616
+ padding: 0.5rem 1rem;
617
+ border: 1px solid #333;
618
+ border-radius: 6px;
619
+ background: #0f0f23;
620
+ color: #e0e0e0;
621
+ font-size: 0.875rem;
622
+ width: 200px;
427
623
  }
428
624
 
429
- .state-diff-viewer__summary {
430
- margin-top: 1rem;
625
+ .sdv-anomalies {
626
+ margin: 1rem;
431
627
  padding: 1rem;
432
- background: #16213e;
628
+ background: #ef444420;
629
+ border: 1px solid #ef4444;
433
630
  border-radius: 8px;
434
631
  }
435
632
 
436
- .state-diff-viewer__summary h4 {
437
- margin: 0 0 0.5rem 0;
438
- color: #00d9ff;
633
+ .sdv-anomaly-header {
634
+ display: flex;
635
+ align-items: center;
636
+ gap: 0.5rem;
637
+ font-weight: 600;
638
+ margin-bottom: 0.75rem;
639
+ color: #fca5a5;
439
640
  }
440
641
 
441
- .state-diff-viewer__changes {
442
- list-style: none;
443
- margin: 0;
444
- padding: 0;
642
+ .sdv-anomaly-icon {
643
+ font-size: 1.25rem;
445
644
  }
446
645
 
447
- .state-diff-viewer__change {
646
+ .sdv-anomaly-list {
448
647
  display: flex;
449
648
  flex-wrap: wrap;
649
+ gap: 0.5rem;
650
+ }
651
+
652
+ .sdv-anomaly-item {
653
+ display: flex;
450
654
  align-items: center;
451
655
  gap: 0.5rem;
452
- padding: 0.5rem;
656
+ padding: 0.375rem 0.75rem;
657
+ background: #0f0f23;
453
658
  border-radius: 4px;
454
- margin-bottom: 0.25rem;
659
+ font-size: 0.875rem;
455
660
  }
456
661
 
457
- .state-diff-viewer__change--add {
458
- background: rgba(34, 197, 94, 0.1);
459
- border-left: 3px solid #22c55e;
662
+ .sdv-anomaly-item code {
663
+ color: #f472b6;
460
664
  }
461
665
 
462
- .state-diff-viewer__change--update {
463
- background: rgba(251, 191, 36, 0.1);
464
- border-left: 3px solid #fbbf24;
666
+ .sdv-anomaly-badge {
667
+ font-size: 0.75rem;
668
+ padding: 0.125rem 0.375rem;
669
+ background: #ef444440;
670
+ border-radius: 3px;
465
671
  }
466
672
 
467
- .state-diff-viewer__change--remove {
468
- background: rgba(239, 68, 68, 0.1);
469
- border-left: 3px solid #ef4444;
673
+ .sdv-summary {
674
+ display: flex;
675
+ gap: 1.5rem;
676
+ padding: 0.75rem 1rem;
677
+ background: #0f0f23;
678
+ border-bottom: 1px solid #333;
679
+ }
680
+
681
+ .sdv-summary-item {
682
+ display: flex;
683
+ align-items: center;
684
+ gap: 0.5rem;
685
+ font-size: 0.875rem;
686
+ }
687
+
688
+ .sdv-dot {
689
+ width: 8px;
690
+ height: 8px;
691
+ border-radius: 50%;
692
+ }
693
+
694
+ .sdv-add .sdv-dot { background: #22c55e; }
695
+ .sdv-update .sdv-dot { background: #fbbf24; }
696
+ .sdv-remove .sdv-dot { background: #ef4444; }
697
+
698
+ .sdv-content {
699
+ max-height: 500px;
700
+ overflow: auto;
701
+ }
702
+
703
+ .sdv-empty {
704
+ padding: 2rem;
705
+ text-align: center;
706
+ color: #666;
707
+ }
708
+
709
+ .sdv-diff-list {
710
+ padding: 1rem;
711
+ }
712
+
713
+ .sdv-diff-item {
714
+ margin-bottom: 0.75rem;
715
+ padding: 1rem;
716
+ background: #16213e;
717
+ border-radius: 8px;
718
+ border-left: 4px solid;
470
719
  }
471
720
 
472
- .state-diff-viewer__change-type {
721
+ .sdv-diff-add { border-left-color: #22c55e; }
722
+ .sdv-diff-update { border-left-color: #fbbf24; }
723
+ .sdv-diff-remove { border-left-color: #ef4444; }
724
+
725
+ .sdv-diff-header {
726
+ display: flex;
727
+ align-items: center;
728
+ gap: 0.75rem;
729
+ margin-bottom: 0.75rem;
730
+ }
731
+
732
+ .sdv-diff-op {
473
733
  font-size: 0.7rem;
474
- padding: 0.125rem 0.375rem;
475
- border-radius: 3px;
476
- background: #333;
734
+ font-weight: 600;
735
+ padding: 0.25rem 0.5rem;
736
+ border-radius: 4px;
737
+ text-transform: uppercase;
738
+ }
739
+
740
+ .sdv-diff-add .sdv-diff-op { background: #22c55e30; color: #22c55e; }
741
+ .sdv-diff-update .sdv-diff-op { background: #fbbf2430; color: #fbbf24; }
742
+ .sdv-diff-remove .sdv-diff-op { background: #ef444430; color: #ef4444; }
743
+
744
+ .sdv-diff-path {
745
+ flex: 1;
746
+ font-family: 'Fira Code', monospace;
747
+ color: #a78bfa;
748
+ }
749
+
750
+ .sdv-copy-btn {
751
+ padding: 0.25rem 0.5rem;
752
+ border: none;
753
+ background: transparent;
477
754
  color: #888;
755
+ cursor: pointer;
756
+ border-radius: 4px;
757
+ font-size: 0.875rem;
478
758
  }
479
759
 
480
- .state-diff-viewer__value {
481
- padding: 0.125rem 0.375rem;
482
- border-radius: 3px;
760
+ .sdv-copy-btn:hover {
761
+ background: #333;
762
+ color: #fff;
763
+ }
764
+
765
+ .sdv-diff-values {
766
+ display: flex;
767
+ align-items: center;
768
+ gap: 0.75rem;
769
+ flex-wrap: wrap;
770
+ }
771
+
772
+ .sdv-value {
773
+ padding: 0.5rem 0.75rem;
774
+ border-radius: 6px;
775
+ font-family: 'Fira Code', monospace;
776
+ font-size: 0.875rem;
777
+ max-width: 300px;
778
+ overflow: auto;
483
779
  }
484
780
 
485
- .state-diff-viewer__value--old {
486
- background: rgba(239, 68, 68, 0.2);
781
+ .sdv-value-label {
782
+ display: block;
783
+ font-size: 0.7rem;
784
+ color: #666;
785
+ margin-bottom: 0.25rem;
786
+ font-family: system-ui;
787
+ }
788
+
789
+ .sdv-value-old {
790
+ background: #ef444420;
487
791
  color: #fca5a5;
488
- text-decoration: line-through;
489
792
  }
490
793
 
491
- .state-diff-viewer__value--new {
492
- background: rgba(34, 197, 94, 0.2);
794
+ .sdv-value-new {
795
+ background: #22c55e20;
493
796
  color: #86efac;
494
797
  }
495
798
 
496
- .state-diff-viewer__arrow {
799
+ .sdv-arrow {
497
800
  color: #666;
801
+ font-size: 1.25rem;
802
+ }
803
+
804
+ .sdv-state-view {
805
+ padding: 1rem;
806
+ }
807
+
808
+ .sdv-state-actions {
809
+ margin-bottom: 1rem;
810
+ }
811
+
812
+ .sdv-copy-full {
813
+ padding: 0.5rem 1rem;
814
+ border: 1px solid #333;
815
+ background: #16213e;
816
+ color: #e0e0e0;
817
+ border-radius: 6px;
818
+ cursor: pointer;
819
+ font-size: 0.875rem;
820
+ }
821
+
822
+ .sdv-copy-full:hover {
823
+ background: #1f2b4d;
824
+ }
825
+
826
+ .sdv-raw-view {
827
+ padding: 1rem;
828
+ }
829
+
830
+ .sdv-raw-panels {
831
+ display: grid;
832
+ grid-template-columns: 1fr 1fr;
833
+ gap: 1rem;
834
+ }
835
+
836
+ .sdv-raw-panel {
837
+ background: #0f0f23;
838
+ border-radius: 8px;
839
+ overflow: hidden;
840
+ }
841
+
842
+ .sdv-raw-panel h4 {
843
+ margin: 0;
844
+ padding: 0.75rem 1rem;
845
+ background: #16213e;
846
+ color: #00d9ff;
847
+ font-size: 0.875rem;
848
+ }
849
+
850
+ .sdv-raw-panel pre {
851
+ margin: 0;
852
+ padding: 1rem;
853
+ font-size: 0.8rem;
854
+ overflow: auto;
855
+ max-height: 300px;
856
+ }
857
+
858
+ /* State tree styles */
859
+ .state-tree__toggle {
860
+ cursor: pointer;
861
+ color: #888;
862
+ user-select: none;
863
+ }
864
+
865
+ .state-tree__toggle:hover {
866
+ color: #00d9ff;
867
+ }
868
+
869
+ .state-tree__children {
870
+ margin-left: 1.5rem;
871
+ border-left: 1px solid #333;
872
+ padding-left: 0.75rem;
873
+ }
874
+
875
+ .state-tree__item {
876
+ padding: 0.25rem 0;
877
+ }
878
+
879
+ .state-tree__key {
880
+ color: #a78bfa;
881
+ margin-right: 0.5rem;
882
+ }
883
+
884
+ .state-tree__null { color: #f472b6; font-style: italic; }
885
+ .state-tree__undefined { color: #ef4444; font-style: italic; }
886
+ .state-tree__boolean { color: #fb923c; }
887
+ .state-tree__number { color: #22c55e; }
888
+ .state-tree__string { color: #fbbf24; }
889
+ .state-tree__empty { color: #666; }
890
+ .state-tree--changed {
891
+ background: #fbbf2430;
892
+ padding: 0 0.25rem;
893
+ border-radius: 3px;
894
+ }
895
+
896
+ .state-tree__anomaly {
897
+ display: inline-block;
898
+ margin-left: 0.5rem;
899
+ padding: 0.125rem 0.375rem;
900
+ background: #ef444440;
901
+ border-radius: 3px;
902
+ font-size: 0.7rem;
903
+ color: #fca5a5;
498
904
  }
499
905
  ` })
500
906
  ] });
501
907
  }
908
+ function ValueDisplay({ value }) {
909
+ if (value === null) {
910
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__null", children: "null" });
911
+ }
912
+ if (value === void 0) {
913
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__undefined", children: [
914
+ "undefined",
915
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__anomaly", children: "\u26A0\uFE0F" })
916
+ ] });
917
+ }
918
+ if (typeof value === "boolean") {
919
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__boolean", children: String(value) });
920
+ }
921
+ if (typeof value === "number") {
922
+ if (isNaN(value)) {
923
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__undefined", children: [
924
+ "NaN",
925
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__anomaly", children: "\u26A0\uFE0F Bug!" })
926
+ ] });
927
+ }
928
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__number", children: String(value) });
929
+ }
930
+ if (typeof value === "string") {
931
+ if (value === "") {
932
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__empty", children: '""' });
933
+ }
934
+ const display = value.length > 50 ? value.slice(0, 50) + "..." : value;
935
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__string", children: [
936
+ '"',
937
+ display,
938
+ '"'
939
+ ] });
940
+ }
941
+ if (Array.isArray(value)) {
942
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__empty", children: [
943
+ "[",
944
+ value.length,
945
+ " items]"
946
+ ] });
947
+ }
948
+ if (typeof value === "object") {
949
+ const keys = Object.keys(value);
950
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__empty", children: "{" + keys.length + " keys}" });
951
+ }
952
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: String(value) });
953
+ }
502
954
  function StateTree({
503
955
  value,
504
956
  path,
@@ -514,19 +966,29 @@ function StateTree({
514
966
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__null", children: "null" });
515
967
  }
516
968
  if (value === void 0) {
517
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__undefined", children: "undefined" });
969
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__undefined", children: [
970
+ "undefined",
971
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__anomaly", children: "\u26A0\uFE0F" })
972
+ ] });
518
973
  }
519
974
  if (typeof value === "boolean") {
520
975
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__boolean", children: String(value) });
521
976
  }
522
977
  if (typeof value === "number") {
978
+ if (isNaN(value)) {
979
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `state-tree__undefined ${isChanged ? "state-tree--changed" : ""}`, children: [
980
+ "NaN",
981
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__anomaly", children: "\u26A0\uFE0F BUG!" })
982
+ ] });
983
+ }
523
984
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `state-tree__number ${isChanged ? "state-tree--changed" : ""}`, children: String(value) });
524
985
  }
525
986
  if (typeof value === "string") {
526
987
  return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `state-tree__string ${isChanged ? "state-tree--changed" : ""}`, children: [
527
988
  '"',
528
989
  value,
529
- '"'
990
+ '"',
991
+ value === "" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__anomaly", children: "empty" })
530
992
  ] });
531
993
  }
532
994
  if (Array.isArray(value)) {
@@ -571,40 +1033,31 @@ function StateTree({
571
1033
  entries.length,
572
1034
  ")"
573
1035
  ] }),
574
- isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "state-tree__children", children: entries.map(([key, val]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-tree__item", children: [
575
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "state-tree__key", children: [
576
- key,
577
- ":"
578
- ] }),
579
- /* @__PURE__ */ jsxRuntime.jsx(
580
- StateTree,
581
- {
582
- value: val,
583
- path: path ? `${path}.${key}` : key,
584
- changedPaths,
585
- defaultExpandDepth,
586
- depth: depth + 1,
587
- changeType
588
- }
589
- )
590
- ] }, key)) })
1036
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "state-tree__children", children: entries.map(([key, val]) => {
1037
+ const childPath = path ? `${path}.${key}` : key;
1038
+ const childChanged = changedPaths.has(childPath);
1039
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "state-tree__item", children: [
1040
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `state-tree__key ${childChanged ? "state-tree--changed" : ""}`, children: [
1041
+ key,
1042
+ ":"
1043
+ ] }),
1044
+ /* @__PURE__ */ jsxRuntime.jsx(
1045
+ StateTree,
1046
+ {
1047
+ value: val,
1048
+ path: childPath,
1049
+ changedPaths,
1050
+ defaultExpandDepth,
1051
+ depth: depth + 1,
1052
+ changeType
1053
+ }
1054
+ )
1055
+ ] }, key);
1056
+ }) })
591
1057
  ] });
592
1058
  }
593
1059
  return /* @__PURE__ */ jsxRuntime.jsx("span", { children: String(value) });
594
1060
  }
595
- function formatValue(value) {
596
- if (value === void 0) return "undefined";
597
- if (value === null) return "null";
598
- if (typeof value === "object") {
599
- try {
600
- const str = JSON.stringify(value);
601
- return str.length > 50 ? str.slice(0, 50) + "..." : str;
602
- } catch {
603
- return "[Object]";
604
- }
605
- }
606
- return String(value);
607
- }
608
1061
  var sourceColors = {
609
1062
  react: "#61dafb",
610
1063
  redux: "#764abc",
@@ -614,6 +1067,15 @@ var sourceColors = {
614
1067
  api: "#3b82f6",
615
1068
  custom: "#6b7280"
616
1069
  };
1070
+ var sourceEmojis = {
1071
+ react: "\u269B\uFE0F",
1072
+ redux: "\u{1F504}",
1073
+ zustand: "\u{1F43B}",
1074
+ express: "\u{1F682}",
1075
+ websocket: "\u{1F50C}",
1076
+ api: "\u{1F310}",
1077
+ custom: "\u2699\uFE0F"
1078
+ };
617
1079
  function TimelineScrubber({
618
1080
  mutations,
619
1081
  selectedIndex,
@@ -623,8 +1085,12 @@ function TimelineScrubber({
623
1085
  onPause,
624
1086
  onStepForward,
625
1087
  onStepBackward,
1088
+ playbackSpeed = 1,
1089
+ onSpeedChange,
626
1090
  className = ""
627
1091
  }) {
1092
+ const [filter, setFilter] = React2.useState("all");
1093
+ const [hoverIndex, setHoverIndex] = React2.useState(null);
628
1094
  const handleSliderChange = React2.useCallback(
629
1095
  (e) => {
630
1096
  const index = parseInt(e.target.value, 10);
@@ -642,54 +1108,128 @@ function TimelineScrubber({
642
1108
  },
643
1109
  [mutations, onSelect]
644
1110
  );
1111
+ const sourceStats = React2.useMemo(() => {
1112
+ const stats = {};
1113
+ for (const m of mutations) {
1114
+ stats[m.source] = (stats[m.source] || 0) + 1;
1115
+ }
1116
+ return stats;
1117
+ }, [mutations]);
1118
+ React2.useMemo(() => {
1119
+ if (filter === "all") return mutations;
1120
+ return mutations.filter((m) => m.source === filter);
1121
+ }, [mutations, filter]);
1122
+ const anomalyCount = React2.useMemo(() => {
1123
+ let count = 0;
1124
+ for (const m of mutations) {
1125
+ if (m.diff) {
1126
+ for (const d of m.diff) {
1127
+ if (d.newValue === void 0 || typeof d.newValue === "number" && isNaN(d.newValue)) {
1128
+ count++;
1129
+ break;
1130
+ }
1131
+ }
1132
+ }
1133
+ }
1134
+ return count;
1135
+ }, [mutations]);
645
1136
  const formatTime = (timestamp) => {
646
1137
  const date = new Date(timestamp);
647
1138
  return date.toLocaleTimeString("en-US", {
648
1139
  hour12: false,
649
1140
  hour: "2-digit",
650
1141
  minute: "2-digit",
651
- second: "2-digit",
652
- fractionalSecondDigits: 3
1142
+ second: "2-digit"
653
1143
  });
654
1144
  };
1145
+ const formatDuration = (ms) => {
1146
+ if (!ms) return "";
1147
+ if (ms < 1) return `${(ms * 1e3).toFixed(0)}\u03BCs`;
1148
+ if (ms < 1e3) return `${ms.toFixed(0)}ms`;
1149
+ return `${(ms / 1e3).toFixed(1)}s`;
1150
+ };
655
1151
  if (mutations.length === 0) {
656
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `timeline-scrubber timeline-scrubber--empty ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No mutations recorded yet" }) });
1152
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `timeline-scrubber timeline-scrubber--empty ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-empty-state", children: [
1153
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-empty-icon", children: "\u{1F4ED}" }),
1154
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "No Mutations Yet" }),
1155
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Start interacting with your app to capture state changes" })
1156
+ ] }) });
657
1157
  }
658
1158
  const selectedMutation = mutations[selectedIndex];
1159
+ const previewMutation = hoverIndex !== null ? mutations[hoverIndex] : selectedMutation;
659
1160
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `timeline-scrubber ${className}`, children: [
660
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "timeline-scrubber__header", children: [
661
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "timeline-scrubber__count", children: [
662
- selectedIndex + 1,
663
- " / ",
664
- mutations.length,
665
- " mutations"
1161
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-stats", children: [
1162
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-stat-main", children: [
1163
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-stat-value", children: mutations.length }),
1164
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-stat-label", children: "mutations" })
1165
+ ] }),
1166
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ts-stat-sources", children: Object.entries(sourceStats).map(([source, count]) => /* @__PURE__ */ jsxRuntime.jsxs(
1167
+ "button",
1168
+ {
1169
+ className: `ts-source-badge ${filter === source ? "active" : ""}`,
1170
+ style: { "--source-color": sourceColors[source] },
1171
+ onClick: () => setFilter(filter === source ? "all" : source),
1172
+ title: `${source}: ${count} mutations`,
1173
+ children: [
1174
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-source-emoji", children: sourceEmojis[source] }),
1175
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-source-count", children: count })
1176
+ ]
1177
+ },
1178
+ source
1179
+ )) }),
1180
+ anomalyCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-anomaly-badge", children: [
1181
+ "\u26A0\uFE0F ",
1182
+ anomalyCount,
1183
+ " issue",
1184
+ anomalyCount > 1 ? "s" : ""
666
1185
  ] }),
667
- selectedMutation && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "timeline-scrubber__time", children: formatTime(selectedMutation.timestamp) })
1186
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-position", children: [
1187
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-current", children: selectedIndex + 1 }),
1188
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-separator", children: "/" }),
1189
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-total", children: mutations.length })
1190
+ ] })
668
1191
  ] }),
669
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "timeline-scrubber__track", children: [
670
- mutations.map((mutation, index) => /* @__PURE__ */ jsxRuntime.jsx(
1192
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ts-chart", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-chart-track", children: [
1193
+ mutations.map((mutation, index) => {
1194
+ const isSelected = index === selectedIndex;
1195
+ const isHovered = index === hoverIndex;
1196
+ const hasAnomaly = mutation.diff?.some(
1197
+ (d) => d.newValue === void 0 || typeof d.newValue === "number" && isNaN(d.newValue)
1198
+ );
1199
+ return /* @__PURE__ */ jsxRuntime.jsx(
1200
+ "div",
1201
+ {
1202
+ className: `ts-marker ${isSelected ? "selected" : ""} ${isHovered ? "hovered" : ""} ${hasAnomaly ? "anomaly" : ""}`,
1203
+ style: {
1204
+ left: `${index / Math.max(mutations.length - 1, 1) * 100}%`,
1205
+ backgroundColor: sourceColors[mutation.source] || sourceColors.custom
1206
+ },
1207
+ onClick: () => handleMarkerClick(index),
1208
+ onMouseEnter: () => setHoverIndex(index),
1209
+ onMouseLeave: () => setHoverIndex(null)
1210
+ },
1211
+ mutation.id
1212
+ );
1213
+ }),
1214
+ /* @__PURE__ */ jsxRuntime.jsx(
671
1215
  "div",
672
1216
  {
673
- className: `timeline-scrubber__marker ${index === selectedIndex ? "timeline-scrubber__marker--selected" : ""}`,
1217
+ className: "ts-progress",
674
1218
  style: {
675
- left: `${index / (mutations.length - 1 || 1) * 100}%`,
676
- backgroundColor: sourceColors[mutation.source] || sourceColors.custom
677
- },
678
- onClick: () => handleMarkerClick(index),
679
- title: `${mutation.source}: ${mutation.actionType}${mutation.component ? ` @ ${mutation.component}` : ""}`
680
- },
681
- mutation.id
682
- )),
1219
+ width: `${selectedIndex / Math.max(mutations.length - 1, 1) * 100}%`
1220
+ }
1221
+ }
1222
+ ),
683
1223
  /* @__PURE__ */ jsxRuntime.jsx(
684
1224
  "div",
685
1225
  {
686
- className: "timeline-scrubber__position",
1226
+ className: "ts-cursor",
687
1227
  style: {
688
- left: `${selectedIndex / (mutations.length - 1 || 1) * 100}%`
1228
+ left: `${selectedIndex / Math.max(mutations.length - 1, 1) * 100}%`
689
1229
  }
690
1230
  }
691
1231
  )
692
- ] }),
1232
+ ] }) }),
693
1233
  /* @__PURE__ */ jsxRuntime.jsx(
694
1234
  "input",
695
1235
  {
@@ -698,189 +1238,446 @@ function TimelineScrubber({
698
1238
  max: mutations.length - 1,
699
1239
  value: selectedIndex,
700
1240
  onChange: handleSliderChange,
701
- className: "timeline-scrubber__slider"
1241
+ className: "ts-slider"
702
1242
  }
703
1243
  ),
704
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "timeline-scrubber__controls", children: [
705
- /* @__PURE__ */ jsxRuntime.jsx(
706
- "button",
707
- {
708
- onClick: onStepBackward,
709
- disabled: selectedIndex === 0,
710
- className: "timeline-scrubber__button",
711
- title: "Step Backward",
712
- children: "\u23EE"
713
- }
714
- ),
715
- isPlaying ? /* @__PURE__ */ jsxRuntime.jsx(
716
- "button",
717
- {
718
- onClick: onPause,
719
- className: "timeline-scrubber__button timeline-scrubber__button--primary",
720
- title: "Pause",
721
- children: "\u23F8"
722
- }
723
- ) : /* @__PURE__ */ jsxRuntime.jsx(
724
- "button",
725
- {
726
- onClick: onPlay,
727
- disabled: selectedIndex === mutations.length - 1,
728
- className: "timeline-scrubber__button timeline-scrubber__button--primary",
729
- title: "Play",
730
- children: "\u25B6"
731
- }
732
- ),
733
- /* @__PURE__ */ jsxRuntime.jsx(
734
- "button",
735
- {
736
- onClick: onStepForward,
737
- disabled: selectedIndex === mutations.length - 1,
738
- className: "timeline-scrubber__button",
739
- title: "Step Forward",
740
- children: "\u23ED"
741
- }
742
- )
1244
+ previewMutation && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-preview", children: [
1245
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-preview-header", children: [
1246
+ /* @__PURE__ */ jsxRuntime.jsxs(
1247
+ "span",
1248
+ {
1249
+ className: "ts-preview-source",
1250
+ style: { backgroundColor: sourceColors[previewMutation.source] },
1251
+ children: [
1252
+ sourceEmojis[previewMutation.source],
1253
+ " ",
1254
+ previewMutation.source
1255
+ ]
1256
+ }
1257
+ ),
1258
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-preview-action", children: previewMutation.actionType }),
1259
+ previewMutation.duration && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-preview-duration", children: formatDuration(previewMutation.duration) })
1260
+ ] }),
1261
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-preview-details", children: [
1262
+ previewMutation.component && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-preview-component", children: previewMutation.component }),
1263
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ts-preview-time", children: formatTime(previewMutation.timestamp) })
1264
+ ] }),
1265
+ previewMutation.diff && previewMutation.diff.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-preview-changes", children: [
1266
+ previewMutation.diff.slice(0, 3).map((d, i) => /* @__PURE__ */ jsxRuntime.jsx("code", { className: `ts-preview-path ts-preview-${d.operation.toLowerCase()}`, children: d.path }, i)),
1267
+ previewMutation.diff.length > 3 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ts-preview-more", children: [
1268
+ "+",
1269
+ previewMutation.diff.length - 3,
1270
+ " more"
1271
+ ] })
1272
+ ] })
1273
+ ] }),
1274
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-controls", children: [
1275
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-control-group", children: [
1276
+ /* @__PURE__ */ jsxRuntime.jsx(
1277
+ "button",
1278
+ {
1279
+ onClick: onStepBackward,
1280
+ disabled: selectedIndex === 0,
1281
+ className: "ts-btn",
1282
+ title: "Previous mutation (\u2190)",
1283
+ children: "\u23EE\uFE0F"
1284
+ }
1285
+ ),
1286
+ isPlaying ? /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onPause, className: "ts-btn ts-btn-primary", title: "Pause", children: "\u23F8\uFE0F" }) : /* @__PURE__ */ jsxRuntime.jsx(
1287
+ "button",
1288
+ {
1289
+ onClick: onPlay,
1290
+ disabled: selectedIndex === mutations.length - 1,
1291
+ className: "ts-btn ts-btn-primary",
1292
+ title: "Play through timeline",
1293
+ children: "\u25B6\uFE0F"
1294
+ }
1295
+ ),
1296
+ /* @__PURE__ */ jsxRuntime.jsx(
1297
+ "button",
1298
+ {
1299
+ onClick: onStepForward,
1300
+ disabled: selectedIndex === mutations.length - 1,
1301
+ className: "ts-btn",
1302
+ title: "Next mutation (\u2192)",
1303
+ children: "\u23ED\uFE0F"
1304
+ }
1305
+ )
1306
+ ] }),
1307
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-control-group", children: [
1308
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "ts-speed-label", children: "Speed:" }),
1309
+ /* @__PURE__ */ jsxRuntime.jsxs(
1310
+ "select",
1311
+ {
1312
+ value: playbackSpeed,
1313
+ onChange: (e) => onSpeedChange?.(Number(e.target.value)),
1314
+ className: "ts-speed-select",
1315
+ children: [
1316
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 0.5, children: "0.5x" }),
1317
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 1, children: "1x" }),
1318
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 2, children: "2x" }),
1319
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 4, children: "4x" })
1320
+ ]
1321
+ }
1322
+ )
1323
+ ] }),
1324
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ts-control-group", children: [
1325
+ /* @__PURE__ */ jsxRuntime.jsx(
1326
+ "button",
1327
+ {
1328
+ className: "ts-btn ts-btn-text",
1329
+ onClick: () => onSelect(0, mutations[0]),
1330
+ disabled: selectedIndex === 0,
1331
+ children: "\u23EA Start"
1332
+ }
1333
+ ),
1334
+ /* @__PURE__ */ jsxRuntime.jsx(
1335
+ "button",
1336
+ {
1337
+ className: "ts-btn ts-btn-text",
1338
+ onClick: () => onSelect(mutations.length - 1, mutations[mutations.length - 1]),
1339
+ disabled: selectedIndex === mutations.length - 1,
1340
+ children: "End \u23E9"
1341
+ }
1342
+ )
1343
+ ] })
743
1344
  ] }),
744
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "timeline-scrubber__legend", children: Object.entries(sourceColors).map(([source, color]) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "timeline-scrubber__legend-item", children: [
745
- /* @__PURE__ */ jsxRuntime.jsx(
746
- "span",
747
- {
748
- className: "timeline-scrubber__legend-dot",
749
- style: { backgroundColor: color }
750
- }
751
- ),
752
- source
753
- ] }, source)) }),
754
1345
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
755
1346
  .timeline-scrubber {
756
- padding: 1rem;
757
1347
  background: #1a1a2e;
758
- border-radius: 8px;
1348
+ border-radius: 12px;
1349
+ padding: 1.25rem;
759
1350
  color: #fff;
760
1351
  }
761
1352
 
762
1353
  .timeline-scrubber--empty {
1354
+ padding: 3rem;
1355
+ }
1356
+
1357
+ .ts-empty-state {
763
1358
  text-align: center;
1359
+ }
1360
+
1361
+ .ts-empty-icon {
1362
+ font-size: 3rem;
1363
+ display: block;
1364
+ margin-bottom: 1rem;
1365
+ }
1366
+
1367
+ .ts-empty-state h3 {
1368
+ margin: 0 0 0.5rem;
764
1369
  color: #888;
765
1370
  }
766
1371
 
767
- .timeline-scrubber__header {
1372
+ .ts-empty-state p {
1373
+ margin: 0;
1374
+ color: #666;
1375
+ font-size: 0.875rem;
1376
+ }
1377
+
1378
+ .ts-stats {
768
1379
  display: flex;
769
- justify-content: space-between;
770
- margin-bottom: 0.5rem;
1380
+ align-items: center;
1381
+ gap: 1.5rem;
1382
+ margin-bottom: 1rem;
1383
+ flex-wrap: wrap;
1384
+ }
1385
+
1386
+ .ts-stat-main {
1387
+ display: flex;
1388
+ align-items: baseline;
1389
+ gap: 0.5rem;
1390
+ }
1391
+
1392
+ .ts-stat-value {
1393
+ font-size: 1.5rem;
1394
+ font-weight: 700;
1395
+ color: #00d9ff;
1396
+ }
1397
+
1398
+ .ts-stat-label {
1399
+ font-size: 0.875rem;
1400
+ color: #888;
1401
+ }
1402
+
1403
+ .ts-stat-sources {
1404
+ display: flex;
1405
+ gap: 0.5rem;
1406
+ }
1407
+
1408
+ .ts-source-badge {
1409
+ display: flex;
1410
+ align-items: center;
1411
+ gap: 0.25rem;
1412
+ padding: 0.375rem 0.625rem;
1413
+ border: 1px solid transparent;
1414
+ border-radius: 20px;
1415
+ background: var(--source-color, #666)20;
1416
+ color: var(--source-color, #888);
1417
+ cursor: pointer;
1418
+ font-size: 0.75rem;
1419
+ transition: all 0.2s;
1420
+ }
1421
+
1422
+ .ts-source-badge:hover, .ts-source-badge.active {
1423
+ background: var(--source-color, #666)40;
1424
+ border-color: var(--source-color, #666);
1425
+ }
1426
+
1427
+ .ts-source-emoji {
1428
+ font-size: 0.875rem;
1429
+ }
1430
+
1431
+ .ts-anomaly-badge {
1432
+ padding: 0.375rem 0.75rem;
1433
+ background: #ef444430;
1434
+ color: #fca5a5;
1435
+ border-radius: 20px;
1436
+ font-size: 0.75rem;
1437
+ font-weight: 600;
1438
+ }
1439
+
1440
+ .ts-position {
1441
+ margin-left: auto;
1442
+ font-family: 'Fira Code', monospace;
771
1443
  font-size: 0.875rem;
772
1444
  }
773
1445
 
774
- .timeline-scrubber__count {
1446
+ .ts-current {
775
1447
  color: #00d9ff;
776
1448
  font-weight: 600;
777
1449
  }
778
1450
 
779
- .timeline-scrubber__time {
1451
+ .ts-separator {
1452
+ color: #444;
1453
+ margin: 0 0.25rem;
1454
+ }
1455
+
1456
+ .ts-total {
1457
+ color: #666;
1458
+ }
1459
+
1460
+ .ts-chart {
1461
+ margin-bottom: 0.5rem;
1462
+ }
1463
+
1464
+ .ts-chart-track {
1465
+ position: relative;
1466
+ height: 32px;
1467
+ background: #16213e;
1468
+ border-radius: 8px;
1469
+ overflow: hidden;
1470
+ }
1471
+
1472
+ .ts-progress {
1473
+ position: absolute;
1474
+ top: 0;
1475
+ left: 0;
1476
+ height: 100%;
1477
+ background: linear-gradient(90deg, #00d9ff20, #00d9ff10);
1478
+ pointer-events: none;
1479
+ }
1480
+
1481
+ .ts-cursor {
1482
+ position: absolute;
1483
+ top: 0;
1484
+ width: 3px;
1485
+ height: 100%;
1486
+ background: #00d9ff;
1487
+ border-radius: 1.5px;
1488
+ pointer-events: none;
1489
+ box-shadow: 0 0 10px #00d9ff80;
1490
+ }
1491
+
1492
+ .ts-marker {
1493
+ position: absolute;
1494
+ width: 6px;
1495
+ height: 16px;
1496
+ top: 8px;
1497
+ margin-left: -3px;
1498
+ border-radius: 3px;
1499
+ cursor: pointer;
1500
+ opacity: 0.6;
1501
+ transition: all 0.15s;
1502
+ }
1503
+
1504
+ .ts-marker:hover, .ts-marker.hovered {
1505
+ opacity: 1;
1506
+ transform: scaleY(1.3);
1507
+ }
1508
+
1509
+ .ts-marker.selected {
1510
+ opacity: 1;
1511
+ box-shadow: 0 0 8px currentColor;
1512
+ transform: scaleY(1.4);
1513
+ }
1514
+
1515
+ .ts-marker.anomaly {
1516
+ border: 2px solid #ef4444;
1517
+ }
1518
+
1519
+ .ts-slider {
1520
+ width: 100%;
1521
+ height: 4px;
1522
+ margin: 0.5rem 0;
1523
+ cursor: pointer;
1524
+ -webkit-appearance: none;
1525
+ background: transparent;
1526
+ }
1527
+
1528
+ .ts-slider::-webkit-slider-runnable-track {
1529
+ height: 4px;
1530
+ background: #333;
1531
+ border-radius: 2px;
1532
+ }
1533
+
1534
+ .ts-slider::-webkit-slider-thumb {
1535
+ -webkit-appearance: none;
1536
+ width: 16px;
1537
+ height: 16px;
1538
+ margin-top: -6px;
1539
+ background: #00d9ff;
1540
+ border-radius: 50%;
1541
+ cursor: pointer;
1542
+ }
1543
+
1544
+ .ts-preview {
1545
+ margin: 1rem 0;
1546
+ padding: 1rem;
1547
+ background: #16213e;
1548
+ border-radius: 8px;
1549
+ border-left: 3px solid #00d9ff;
1550
+ }
1551
+
1552
+ .ts-preview-header {
1553
+ display: flex;
1554
+ align-items: center;
1555
+ gap: 0.75rem;
1556
+ margin-bottom: 0.5rem;
1557
+ }
1558
+
1559
+ .ts-preview-source {
1560
+ padding: 0.25rem 0.5rem;
1561
+ border-radius: 4px;
1562
+ font-size: 0.75rem;
1563
+ font-weight: 600;
1564
+ color: #000;
1565
+ }
1566
+
1567
+ .ts-preview-action {
1568
+ font-weight: 600;
1569
+ color: #e0e0e0;
1570
+ }
1571
+
1572
+ .ts-preview-duration {
1573
+ margin-left: auto;
1574
+ font-size: 0.75rem;
1575
+ color: #888;
1576
+ font-family: 'Fira Code', monospace;
1577
+ }
1578
+
1579
+ .ts-preview-details {
1580
+ display: flex;
1581
+ gap: 1rem;
1582
+ font-size: 0.875rem;
780
1583
  color: #888;
781
- font-family: monospace;
782
1584
  }
783
1585
 
784
- .timeline-scrubber__track {
785
- position: relative;
786
- height: 24px;
787
- background: #16213e;
788
- border-radius: 4px;
789
- margin-bottom: 0.5rem;
1586
+ .ts-preview-component {
1587
+ color: #a78bfa;
790
1588
  }
791
1589
 
792
- .timeline-scrubber__marker {
793
- position: absolute;
794
- width: 4px;
795
- height: 16px;
796
- top: 4px;
797
- border-radius: 2px;
798
- cursor: pointer;
799
- transition: transform 0.1s, opacity 0.1s;
800
- opacity: 0.7;
1590
+ .ts-preview-changes {
1591
+ display: flex;
1592
+ flex-wrap: wrap;
1593
+ gap: 0.5rem;
1594
+ margin-top: 0.75rem;
801
1595
  }
802
1596
 
803
- .timeline-scrubber__marker:hover {
804
- transform: scaleY(1.2);
805
- opacity: 1;
1597
+ .ts-preview-path {
1598
+ padding: 0.25rem 0.5rem;
1599
+ border-radius: 4px;
1600
+ font-size: 0.75rem;
806
1601
  }
807
1602
 
808
- .timeline-scrubber__marker--selected {
809
- opacity: 1;
810
- box-shadow: 0 0 8px currentColor;
811
- }
1603
+ .ts-preview-add { background: #22c55e20; color: #86efac; }
1604
+ .ts-preview-update { background: #fbbf2420; color: #fcd34d; }
1605
+ .ts-preview-remove { background: #ef444420; color: #fca5a5; }
812
1606
 
813
- .timeline-scrubber__position {
814
- position: absolute;
815
- width: 2px;
816
- height: 24px;
817
- top: 0;
818
- background: #fff;
819
- border-radius: 1px;
820
- pointer-events: none;
1607
+ .ts-preview-more {
1608
+ font-size: 0.75rem;
1609
+ color: #666;
1610
+ padding: 0.25rem;
821
1611
  }
822
1612
 
823
- .timeline-scrubber__slider {
824
- width: 100%;
825
- margin: 0.5rem 0;
826
- cursor: pointer;
1613
+ .ts-controls {
1614
+ display: flex;
1615
+ justify-content: center;
1616
+ align-items: center;
1617
+ gap: 1.5rem;
1618
+ padding-top: 0.5rem;
1619
+ flex-wrap: wrap;
827
1620
  }
828
1621
 
829
- .timeline-scrubber__controls {
1622
+ .ts-control-group {
830
1623
  display: flex;
831
- justify-content: center;
1624
+ align-items: center;
832
1625
  gap: 0.5rem;
833
- margin: 0.5rem 0;
834
1626
  }
835
1627
 
836
- .timeline-scrubber__button {
837
- padding: 0.5rem 1rem;
1628
+ .ts-btn {
1629
+ padding: 0.5rem 0.75rem;
838
1630
  border: none;
839
- border-radius: 4px;
1631
+ border-radius: 8px;
840
1632
  background: #16213e;
841
1633
  color: #fff;
842
1634
  cursor: pointer;
843
1635
  font-size: 1rem;
844
- transition: background 0.2s;
1636
+ transition: all 0.2s;
845
1637
  }
846
1638
 
847
- .timeline-scrubber__button:hover:not(:disabled) {
1639
+ .ts-btn:hover:not(:disabled) {
848
1640
  background: #1f2b4d;
849
1641
  }
850
1642
 
851
- .timeline-scrubber__button:disabled {
852
- opacity: 0.5;
1643
+ .ts-btn:disabled {
1644
+ opacity: 0.4;
853
1645
  cursor: not-allowed;
854
1646
  }
855
1647
 
856
- .timeline-scrubber__button--primary {
1648
+ .ts-btn-primary {
857
1649
  background: #00d9ff;
858
1650
  color: #000;
859
1651
  }
860
1652
 
861
- .timeline-scrubber__button--primary:hover:not(:disabled) {
1653
+ .ts-btn-primary:hover:not(:disabled) {
862
1654
  background: #00b8e6;
863
1655
  }
864
1656
 
865
- .timeline-scrubber__legend {
866
- display: flex;
867
- flex-wrap: wrap;
868
- gap: 1rem;
869
- margin-top: 0.5rem;
870
- font-size: 0.75rem;
1657
+ .ts-btn-text {
1658
+ background: transparent;
1659
+ color: #888;
1660
+ font-size: 0.875rem;
871
1661
  }
872
1662
 
873
- .timeline-scrubber__legend-item {
874
- display: flex;
875
- align-items: center;
876
- gap: 0.25rem;
1663
+ .ts-btn-text:hover:not(:disabled) {
1664
+ color: #fff;
1665
+ background: transparent;
1666
+ }
1667
+
1668
+ .ts-speed-label {
1669
+ font-size: 0.875rem;
877
1670
  color: #888;
878
1671
  }
879
1672
 
880
- .timeline-scrubber__legend-dot {
881
- width: 8px;
882
- height: 8px;
883
- border-radius: 50%;
1673
+ .ts-speed-select {
1674
+ padding: 0.375rem 0.5rem;
1675
+ border: 1px solid #333;
1676
+ border-radius: 6px;
1677
+ background: #16213e;
1678
+ color: #e0e0e0;
1679
+ font-size: 0.875rem;
1680
+ cursor: pointer;
884
1681
  }
885
1682
  ` })
886
1683
  ] });
@@ -998,44 +1795,139 @@ function DashboardContent() {
998
1795
  loadSessions
999
1796
  } = useDashboard();
1000
1797
  const playback = usePlayback(timeline);
1001
- React2__default.default.useEffect(() => {
1798
+ const [activePanel, setActivePanel] = React2.useState("diff");
1799
+ const [autoRefresh, setAutoRefresh] = React2.useState(true);
1800
+ React2.useEffect(() => {
1002
1801
  if (playback.currentMutation) {
1003
1802
  selectMutation(playback.currentMutation);
1004
1803
  }
1005
1804
  }, [playback.currentMutation, selectMutation]);
1805
+ React2.useEffect(() => {
1806
+ if (!autoRefresh) return;
1807
+ const interval = setInterval(loadSessions, 5e3);
1808
+ return () => clearInterval(interval);
1809
+ }, [autoRefresh, loadSessions]);
1810
+ React2.useEffect(() => {
1811
+ const handleKeyDown = (e) => {
1812
+ if (e.target instanceof HTMLInputElement) return;
1813
+ switch (e.key) {
1814
+ case "ArrowLeft":
1815
+ playback.stepBackward();
1816
+ break;
1817
+ case "ArrowRight":
1818
+ playback.stepForward();
1819
+ break;
1820
+ case " ":
1821
+ e.preventDefault();
1822
+ playback.isPlaying ? playback.pause() : playback.play();
1823
+ break;
1824
+ }
1825
+ };
1826
+ window.addEventListener("keydown", handleKeyDown);
1827
+ return () => window.removeEventListener("keydown", handleKeyDown);
1828
+ }, [playback]);
1006
1829
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard", children: [
1007
- /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "surgeon-dashboard__header", children: [
1008
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "surgeon-dashboard__logo", children: "\u{1F52C} State Surgeon" }),
1009
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard__session-select", children: [
1010
- /* @__PURE__ */ jsxRuntime.jsxs(
1011
- "select",
1830
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sd-header", children: [
1831
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd-header-left", children: /* @__PURE__ */ jsxRuntime.jsxs("h1", { className: "sd-logo", children: [
1832
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd-logo-icon", children: "\u{1F52C}" }),
1833
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd-logo-text", children: "State Surgeon" }),
1834
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd-version", children: "v1.1.0" })
1835
+ ] }) }),
1836
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd-header-center", children: /* @__PURE__ */ jsxRuntime.jsxs(
1837
+ "select",
1838
+ {
1839
+ value: currentSession?.id || "",
1840
+ onChange: (e) => selectSession(e.target.value),
1841
+ disabled: isLoading,
1842
+ className: "sd-session-select",
1843
+ children: [
1844
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select a session..." }),
1845
+ sessions.map((session) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: session.id, children: [
1846
+ session.appId,
1847
+ " \u2022 ",
1848
+ session.id.slice(0, 8),
1849
+ "... \u2022 ",
1850
+ session.mutationCount,
1851
+ " mutations"
1852
+ ] }, session.id))
1853
+ ]
1854
+ }
1855
+ ) }),
1856
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-header-right", children: [
1857
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sd-auto-refresh", children: [
1858
+ /* @__PURE__ */ jsxRuntime.jsx(
1859
+ "input",
1860
+ {
1861
+ type: "checkbox",
1862
+ checked: autoRefresh,
1863
+ onChange: (e) => setAutoRefresh(e.target.checked)
1864
+ }
1865
+ ),
1866
+ "Auto-refresh"
1867
+ ] }),
1868
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: loadSessions, disabled: isLoading, className: "sd-refresh-btn", children: isLoading ? "\u23F3" : "\u{1F504}" })
1869
+ ] })
1870
+ ] }),
1871
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-error", children: [
1872
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd-error-icon", children: "\u274C" }),
1873
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: error })
1874
+ ] }),
1875
+ !currentSession && !isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd-welcome", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-welcome-content", children: [
1876
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd-welcome-icon", children: "\u{1F52C}" }),
1877
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Welcome to State Surgeon" }),
1878
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "A forensic debugging platform for JavaScript applications" }),
1879
+ sessions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-welcome-steps", children: [
1880
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Getting Started" }),
1881
+ /* @__PURE__ */ jsxRuntime.jsxs("ol", { children: [
1882
+ /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
1883
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Install the client in your app:" }),
1884
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { children: `import { StateSurgeonClient, instrumentReact } from 'state-surgeon/instrument';
1885
+
1886
+ const client = new StateSurgeonClient({ appId: 'my-app' });
1887
+ instrumentReact(React);` })
1888
+ ] }),
1889
+ /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
1890
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Interact with your application" }),
1891
+ " to generate state mutations"
1892
+ ] }),
1893
+ /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
1894
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Select a session above" }),
1895
+ " to start debugging"
1896
+ ] })
1897
+ ] })
1898
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-welcome-sessions", children: [
1899
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { children: [
1900
+ "Available Sessions (",
1901
+ sessions.length,
1902
+ ")"
1903
+ ] }),
1904
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd-session-list", children: sessions.map((session) => /* @__PURE__ */ jsxRuntime.jsxs(
1905
+ "button",
1012
1906
  {
1013
- value: currentSession?.id || "",
1014
- onChange: (e) => selectSession(e.target.value),
1015
- disabled: isLoading,
1907
+ className: "sd-session-card",
1908
+ onClick: () => selectSession(session.id),
1016
1909
  children: [
1017
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select a session..." }),
1018
- sessions.map((session) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: session.id, children: [
1019
- session.appId,
1020
- " - ",
1910
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd-session-app", children: session.appId }),
1911
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd-session-id", children: [
1021
1912
  session.id.slice(0, 16),
1022
- "... (",
1913
+ "..."
1914
+ ] }),
1915
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd-session-count", children: [
1023
1916
  session.mutationCount,
1024
- " mutations)"
1025
- ] }, session.id))
1917
+ " mutations"
1918
+ ] })
1026
1919
  ]
1027
- }
1028
- ),
1029
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: loadSessions, disabled: isLoading, children: "\u{1F504}" })
1920
+ },
1921
+ session.id
1922
+ )) })
1030
1923
  ] })
1031
- ] }),
1032
- error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "surgeon-dashboard__error", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1033
- "Error: ",
1034
- error
1035
1924
  ] }) }),
1036
- isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "surgeon-dashboard__loading", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Loading..." }) }),
1037
- currentSession && !isLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard__main", children: [
1038
- /* @__PURE__ */ jsxRuntime.jsx("section", { className: "surgeon-dashboard__timeline", children: /* @__PURE__ */ jsxRuntime.jsx(
1925
+ isLoading && !currentSession && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-loading", children: [
1926
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd-spinner" }),
1927
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Loading sessions..." })
1928
+ ] }),
1929
+ currentSession && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-main", children: [
1930
+ /* @__PURE__ */ jsxRuntime.jsx("section", { className: "sd-timeline", children: /* @__PURE__ */ jsxRuntime.jsx(
1039
1931
  TimelineScrubber,
1040
1932
  {
1041
1933
  mutations: timeline,
@@ -1048,135 +1940,373 @@ function DashboardContent() {
1048
1940
  onPlay: playback.play,
1049
1941
  onPause: playback.pause,
1050
1942
  onStepForward: playback.stepForward,
1051
- onStepBackward: playback.stepBackward
1943
+ onStepBackward: playback.stepBackward,
1944
+ playbackSpeed: playback.playbackSpeed,
1945
+ onSpeedChange: playback.setSpeed
1052
1946
  }
1053
1947
  ) }),
1054
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard__content", children: [
1055
- /* @__PURE__ */ jsxRuntime.jsx("section", { className: "surgeon-dashboard__diff", children: selectedMutation && /* @__PURE__ */ jsxRuntime.jsx(
1056
- StateDiffViewer,
1057
- {
1058
- previousState: selectedMutation.previousState,
1059
- nextState: selectedMutation.nextState,
1060
- diff: selectedMutation.diff
1061
- }
1062
- ) }),
1063
- /* @__PURE__ */ jsxRuntime.jsx("section", { className: "surgeon-dashboard__inspector", children: /* @__PURE__ */ jsxRuntime.jsx(MutationInspector, { mutation: selectedMutation }) })
1948
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-panels", children: [
1949
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-panel-tabs", children: [
1950
+ /* @__PURE__ */ jsxRuntime.jsx(
1951
+ "button",
1952
+ {
1953
+ className: `sd-panel-tab ${activePanel === "diff" ? "active" : ""}`,
1954
+ onClick: () => setActivePanel("diff"),
1955
+ children: "\u{1F4CA} State Changes"
1956
+ }
1957
+ ),
1958
+ /* @__PURE__ */ jsxRuntime.jsx(
1959
+ "button",
1960
+ {
1961
+ className: `sd-panel-tab ${activePanel === "inspector" ? "active" : ""}`,
1962
+ onClick: () => setActivePanel("inspector"),
1963
+ children: "\u{1F50D} Mutation Details"
1964
+ }
1965
+ )
1966
+ ] }),
1967
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-panel-content", children: [
1968
+ activePanel === "diff" && selectedMutation && /* @__PURE__ */ jsxRuntime.jsx(
1969
+ StateDiffViewer,
1970
+ {
1971
+ previousState: selectedMutation.previousState,
1972
+ nextState: selectedMutation.nextState,
1973
+ diff: selectedMutation.diff
1974
+ }
1975
+ ),
1976
+ activePanel === "inspector" && /* @__PURE__ */ jsxRuntime.jsx(MutationInspector, { mutation: selectedMutation }),
1977
+ !selectedMutation && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd-panel-empty", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Select a mutation from the timeline to view details" }) })
1978
+ ] })
1064
1979
  ] })
1065
1980
  ] }),
1066
- !currentSession && !isLoading && sessions.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard__empty", children: [
1067
- /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "No Sessions Yet" }),
1068
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Connect your application to start capturing mutations." }),
1069
- /* @__PURE__ */ jsxRuntime.jsx("pre", { children: `import { StateSurgeonClient, instrumentReact } from 'state-surgeon/instrument';
1070
-
1071
- // Initialize the client
1072
- const client = new StateSurgeonClient({
1073
- serverUrl: 'ws://localhost:8081',
1074
- appId: 'my-app',
1075
- });
1076
-
1077
- // Instrument React
1078
- instrumentReact(React);` })
1981
+ /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "sd-footer", children: [
1982
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "State Surgeon v1.1.0" }),
1983
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd-footer-sep", children: "\u2022" }),
1984
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Press \u2190 \u2192 to navigate, Space to play/pause" })
1079
1985
  ] }),
1080
1986
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
1081
1987
  .surgeon-dashboard {
1082
1988
  min-height: 100vh;
1083
- background: #0f0f23;
1989
+ display: flex;
1990
+ flex-direction: column;
1991
+ background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%);
1084
1992
  color: #e0e0e0;
1085
1993
  font-family: system-ui, -apple-system, sans-serif;
1086
1994
  }
1087
1995
 
1088
- .surgeon-dashboard__header {
1996
+ .sd-header {
1089
1997
  display: flex;
1090
1998
  justify-content: space-between;
1091
1999
  align-items: center;
1092
2000
  padding: 1rem 2rem;
1093
- background: #1a1a2e;
2001
+ background: rgba(22, 33, 62, 0.8);
2002
+ backdrop-filter: blur(10px);
1094
2003
  border-bottom: 1px solid #333;
2004
+ position: sticky;
2005
+ top: 0;
2006
+ z-index: 100;
1095
2007
  }
1096
2008
 
1097
- .surgeon-dashboard__logo {
1098
- margin: 0;
1099
- font-size: 1.5rem;
1100
- color: #00d9ff;
2009
+ .sd-header-left, .sd-header-right {
2010
+ display: flex;
2011
+ align-items: center;
2012
+ gap: 1rem;
1101
2013
  }
1102
2014
 
1103
- .surgeon-dashboard__session-select {
2015
+ .sd-logo {
1104
2016
  display: flex;
2017
+ align-items: center;
1105
2018
  gap: 0.5rem;
2019
+ margin: 0;
2020
+ font-size: 1.25rem;
1106
2021
  }
1107
2022
 
1108
- .surgeon-dashboard__session-select select {
1109
- padding: 0.5rem 1rem;
1110
- border: 1px solid #333;
2023
+ .sd-logo-icon {
2024
+ font-size: 1.5rem;
2025
+ }
2026
+
2027
+ .sd-logo-text {
2028
+ background: linear-gradient(135deg, #00d9ff, #a78bfa);
2029
+ -webkit-background-clip: text;
2030
+ -webkit-text-fill-color: transparent;
2031
+ background-clip: text;
2032
+ font-weight: 700;
2033
+ }
2034
+
2035
+ .sd-version {
2036
+ font-size: 0.7rem;
2037
+ padding: 0.2rem 0.4rem;
2038
+ background: #333;
1111
2039
  border-radius: 4px;
1112
- background: #16213e;
2040
+ color: #888;
2041
+ }
2042
+
2043
+ .sd-session-select {
2044
+ padding: 0.625rem 1.25rem;
2045
+ min-width: 350px;
2046
+ border: 1px solid #333;
2047
+ border-radius: 8px;
2048
+ background: #0f0f23;
1113
2049
  color: #e0e0e0;
1114
- min-width: 300px;
2050
+ font-size: 0.875rem;
2051
+ cursor: pointer;
1115
2052
  }
1116
2053
 
1117
- .surgeon-dashboard__session-select button {
1118
- padding: 0.5rem 1rem;
2054
+ .sd-session-select:focus {
2055
+ outline: none;
2056
+ border-color: #00d9ff;
2057
+ }
2058
+
2059
+ .sd-auto-refresh {
2060
+ display: flex;
2061
+ align-items: center;
2062
+ gap: 0.5rem;
2063
+ font-size: 0.875rem;
2064
+ color: #888;
2065
+ cursor: pointer;
2066
+ }
2067
+
2068
+ .sd-refresh-btn {
2069
+ padding: 0.5rem;
1119
2070
  border: 1px solid #333;
1120
- border-radius: 4px;
1121
- background: #16213e;
2071
+ border-radius: 8px;
2072
+ background: transparent;
1122
2073
  color: #e0e0e0;
1123
2074
  cursor: pointer;
2075
+ font-size: 1rem;
1124
2076
  }
1125
2077
 
1126
- .surgeon-dashboard__session-select button:hover {
1127
- background: #1f2b4d;
2078
+ .sd-refresh-btn:hover {
2079
+ background: #16213e;
1128
2080
  }
1129
2081
 
1130
- .surgeon-dashboard__error {
2082
+ .sd-error {
2083
+ display: flex;
2084
+ align-items: center;
2085
+ gap: 0.75rem;
1131
2086
  padding: 1rem 2rem;
1132
2087
  background: rgba(239, 68, 68, 0.1);
1133
2088
  border-bottom: 1px solid #ef4444;
2089
+ }
2090
+
2091
+ .sd-error-icon {
2092
+ font-size: 1.25rem;
2093
+ }
2094
+
2095
+ .sd-error p {
2096
+ margin: 0;
1134
2097
  color: #fca5a5;
1135
2098
  }
1136
2099
 
1137
- .surgeon-dashboard__loading {
2100
+ .sd-welcome {
2101
+ flex: 1;
2102
+ display: flex;
2103
+ align-items: center;
2104
+ justify-content: center;
1138
2105
  padding: 2rem;
1139
- text-align: center;
1140
- color: #888;
1141
2106
  }
1142
2107
 
1143
- .surgeon-dashboard__main {
1144
- padding: 1rem 2rem;
2108
+ .sd-welcome-content {
2109
+ text-align: center;
2110
+ max-width: 600px;
1145
2111
  }
1146
2112
 
1147
- .surgeon-dashboard__timeline {
2113
+ .sd-welcome-icon {
2114
+ font-size: 5rem;
1148
2115
  margin-bottom: 1rem;
2116
+ animation: float 3s ease-in-out infinite;
1149
2117
  }
1150
2118
 
1151
- .surgeon-dashboard__content {
1152
- display: grid;
1153
- grid-template-columns: 2fr 1fr;
1154
- gap: 1rem;
2119
+ @keyframes float {
2120
+ 0%, 100% { transform: translateY(0); }
2121
+ 50% { transform: translateY(-10px); }
1155
2122
  }
1156
2123
 
1157
- .surgeon-dashboard__empty {
1158
- padding: 4rem 2rem;
1159
- text-align: center;
2124
+ .sd-welcome h2 {
2125
+ margin: 0 0 0.5rem;
2126
+ font-size: 2rem;
2127
+ background: linear-gradient(135deg, #00d9ff, #a78bfa);
2128
+ -webkit-background-clip: text;
2129
+ -webkit-text-fill-color: transparent;
2130
+ background-clip: text;
1160
2131
  }
1161
2132
 
1162
- .surgeon-dashboard__empty h2 {
2133
+ .sd-welcome > p {
2134
+ margin: 0 0 2rem;
1163
2135
  color: #888;
1164
- margin-bottom: 1rem;
1165
2136
  }
1166
2137
 
1167
- .surgeon-dashboard__empty pre {
1168
- display: inline-block;
2138
+ .sd-welcome-steps {
1169
2139
  text-align: left;
2140
+ background: #16213e;
2141
+ border-radius: 12px;
1170
2142
  padding: 1.5rem;
2143
+ }
2144
+
2145
+ .sd-welcome-steps h3 {
2146
+ margin: 0 0 1rem;
2147
+ color: #00d9ff;
2148
+ font-size: 1rem;
2149
+ }
2150
+
2151
+ .sd-welcome-steps ol {
2152
+ margin: 0;
2153
+ padding-left: 1.25rem;
2154
+ }
2155
+
2156
+ .sd-welcome-steps li {
2157
+ margin-bottom: 1rem;
2158
+ color: #ccc;
2159
+ }
2160
+
2161
+ .sd-welcome-steps pre {
2162
+ margin: 0.5rem 0 0;
2163
+ padding: 1rem;
2164
+ background: #0f0f23;
2165
+ border-radius: 8px;
2166
+ font-size: 0.8rem;
2167
+ overflow-x: auto;
2168
+ }
2169
+
2170
+ .sd-welcome-sessions h3 {
2171
+ margin: 0 0 1rem;
2172
+ color: #00d9ff;
2173
+ }
2174
+
2175
+ .sd-session-list {
2176
+ display: grid;
2177
+ gap: 0.75rem;
2178
+ }
2179
+
2180
+ .sd-session-card {
2181
+ display: flex;
2182
+ justify-content: space-between;
2183
+ align-items: center;
2184
+ padding: 1rem 1.25rem;
1171
2185
  background: #16213e;
2186
+ border: 1px solid #333;
1172
2187
  border-radius: 8px;
2188
+ cursor: pointer;
2189
+ transition: all 0.2s;
2190
+ text-align: left;
2191
+ color: inherit;
2192
+ }
2193
+
2194
+ .sd-session-card:hover {
2195
+ border-color: #00d9ff;
2196
+ background: #1a2744;
2197
+ }
2198
+
2199
+ .sd-session-app {
2200
+ font-weight: 600;
2201
+ color: #a78bfa;
2202
+ }
2203
+
2204
+ .sd-session-id {
2205
+ font-family: monospace;
2206
+ font-size: 0.8rem;
2207
+ color: #666;
2208
+ }
2209
+
2210
+ .sd-session-count {
1173
2211
  font-size: 0.875rem;
2212
+ color: #00d9ff;
1174
2213
  }
1175
2214
 
1176
- @media (max-width: 1024px) {
1177
- .surgeon-dashboard__content {
1178
- grid-template-columns: 1fr;
1179
- }
2215
+ .sd-loading {
2216
+ flex: 1;
2217
+ display: flex;
2218
+ flex-direction: column;
2219
+ align-items: center;
2220
+ justify-content: center;
2221
+ gap: 1rem;
2222
+ }
2223
+
2224
+ .sd-spinner {
2225
+ width: 40px;
2226
+ height: 40px;
2227
+ border: 3px solid #333;
2228
+ border-top-color: #00d9ff;
2229
+ border-radius: 50%;
2230
+ animation: spin 1s linear infinite;
2231
+ }
2232
+
2233
+ @keyframes spin {
2234
+ to { transform: rotate(360deg); }
2235
+ }
2236
+
2237
+ .sd-main {
2238
+ flex: 1;
2239
+ padding: 1.5rem 2rem;
2240
+ display: flex;
2241
+ flex-direction: column;
2242
+ gap: 1.5rem;
2243
+ }
2244
+
2245
+ .sd-timeline {
2246
+ flex-shrink: 0;
2247
+ }
2248
+
2249
+ .sd-panels {
2250
+ flex: 1;
2251
+ background: #1a1a2e;
2252
+ border-radius: 12px;
2253
+ overflow: hidden;
2254
+ display: flex;
2255
+ flex-direction: column;
2256
+ min-height: 400px;
2257
+ }
2258
+
2259
+ .sd-panel-tabs {
2260
+ display: flex;
2261
+ background: #16213e;
2262
+ border-bottom: 1px solid #333;
2263
+ }
2264
+
2265
+ .sd-panel-tab {
2266
+ padding: 1rem 1.5rem;
2267
+ border: none;
2268
+ background: transparent;
2269
+ color: #888;
2270
+ cursor: pointer;
2271
+ font-size: 0.875rem;
2272
+ transition: all 0.2s;
2273
+ }
2274
+
2275
+ .sd-panel-tab:hover {
2276
+ color: #ccc;
2277
+ background: #1a2744;
2278
+ }
2279
+
2280
+ .sd-panel-tab.active {
2281
+ color: #00d9ff;
2282
+ background: #1a1a2e;
2283
+ border-bottom: 2px solid #00d9ff;
2284
+ }
2285
+
2286
+ .sd-panel-content {
2287
+ flex: 1;
2288
+ overflow: auto;
2289
+ }
2290
+
2291
+ .sd-panel-empty {
2292
+ height: 100%;
2293
+ display: flex;
2294
+ align-items: center;
2295
+ justify-content: center;
2296
+ color: #666;
2297
+ }
2298
+
2299
+ .sd-footer {
2300
+ padding: 0.75rem 2rem;
2301
+ background: rgba(22, 33, 62, 0.5);
2302
+ border-top: 1px solid #333;
2303
+ font-size: 0.75rem;
2304
+ color: #666;
2305
+ text-align: center;
2306
+ }
2307
+
2308
+ .sd-footer-sep {
2309
+ margin: 0 0.5rem;
1180
2310
  }
1181
2311
  ` })
1182
2312
  ] });