state-surgeon 1.1.0 → 2.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.
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
- var React2 = require('react');
4
+ var React4 = require('react');
5
5
 
6
6
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
7
 
8
- var React2__default = /*#__PURE__*/_interopDefault(React2);
8
+ var React4__default = /*#__PURE__*/_interopDefault(React4);
9
9
 
10
10
  // src/dashboard/components/MutationInspector.tsx
11
11
  function MutationInspector({
@@ -355,24 +355,24 @@ function StateDiffViewer({
355
355
  viewMode = "split",
356
356
  className = ""
357
357
  }) {
358
- const [activeTab, setActiveTab] = React2.useState("diff");
359
- const [searchQuery, setSearchQuery] = React2.useState("");
360
- const [copiedPath, setCopiedPath] = React2.useState(null);
361
- const diff = React2.useMemo(
358
+ const [activeTab, setActiveTab] = React4.useState("diff");
359
+ const [searchQuery, setSearchQuery] = React4.useState("");
360
+ const [copiedPath, setCopiedPath] = React4.useState(null);
361
+ const diff = React4.useMemo(
362
362
  () => preDiff || calculateDiff(previousState, nextState),
363
363
  [previousState, nextState, preDiff]
364
364
  );
365
- const changedPaths = React2.useMemo(
365
+ const changedPaths = React4.useMemo(
366
366
  () => new Set(diff.map((d) => d.path)),
367
367
  [diff]
368
368
  );
369
- const groupedDiffs = React2.useMemo(() => {
369
+ const groupedDiffs = React4.useMemo(() => {
370
370
  const adds = diff.filter((d) => d.operation === "ADD");
371
371
  const updates = diff.filter((d) => d.operation === "UPDATE");
372
372
  const removes = diff.filter((d) => d.operation === "REMOVE");
373
373
  return { adds, updates, removes };
374
374
  }, [diff]);
375
- const anomalies = React2.useMemo(() => {
375
+ const anomalies = React4.useMemo(() => {
376
376
  const result = [];
377
377
  for (const d of diff) {
378
378
  if (d.operation !== "REMOVE") {
@@ -384,13 +384,13 @@ function StateDiffViewer({
384
384
  }
385
385
  return result;
386
386
  }, [diff]);
387
- const copyToClipboard = React2.useCallback((value, path) => {
387
+ const copyToClipboard = React4.useCallback((value, path) => {
388
388
  const text = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
389
389
  navigator.clipboard.writeText(text);
390
390
  setCopiedPath(path);
391
391
  setTimeout(() => setCopiedPath(null), 2e3);
392
392
  }, []);
393
- const copyFullState = React2.useCallback((state, label) => {
393
+ const copyFullState = React4.useCallback((state, label) => {
394
394
  navigator.clipboard.writeText(JSON.stringify(state, null, 2));
395
395
  setCopiedPath(label);
396
396
  setTimeout(() => setCopiedPath(null), 2e3);
@@ -959,9 +959,9 @@ function StateTree({
959
959
  depth = 0,
960
960
  changeType
961
961
  }) {
962
- const [isExpanded, setIsExpanded] = React2.useState(depth < defaultExpandDepth);
962
+ const [isExpanded, setIsExpanded] = React4.useState(depth < defaultExpandDepth);
963
963
  const isChanged = path ? changedPaths.has(path) : false;
964
- const toggle = React2.useCallback(() => setIsExpanded((e) => !e), []);
964
+ const toggle = React4.useCallback(() => setIsExpanded((e) => !e), []);
965
965
  if (value === null) {
966
966
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "state-tree__null", children: "null" });
967
967
  }
@@ -1089,9 +1089,9 @@ function TimelineScrubber({
1089
1089
  onSpeedChange,
1090
1090
  className = ""
1091
1091
  }) {
1092
- const [filter, setFilter] = React2.useState("all");
1093
- const [hoverIndex, setHoverIndex] = React2.useState(null);
1094
- const handleSliderChange = React2.useCallback(
1092
+ const [filter, setFilter] = React4.useState("all");
1093
+ const [hoverIndex, setHoverIndex] = React4.useState(null);
1094
+ const handleSliderChange = React4.useCallback(
1095
1095
  (e) => {
1096
1096
  const index = parseInt(e.target.value, 10);
1097
1097
  if (mutations[index]) {
@@ -1100,7 +1100,7 @@ function TimelineScrubber({
1100
1100
  },
1101
1101
  [mutations, onSelect]
1102
1102
  );
1103
- const handleMarkerClick = React2.useCallback(
1103
+ const handleMarkerClick = React4.useCallback(
1104
1104
  (index) => {
1105
1105
  if (mutations[index]) {
1106
1106
  onSelect(index, mutations[index]);
@@ -1108,18 +1108,18 @@ function TimelineScrubber({
1108
1108
  },
1109
1109
  [mutations, onSelect]
1110
1110
  );
1111
- const sourceStats = React2.useMemo(() => {
1111
+ const sourceStats = React4.useMemo(() => {
1112
1112
  const stats = {};
1113
1113
  for (const m of mutations) {
1114
1114
  stats[m.source] = (stats[m.source] || 0) + 1;
1115
1115
  }
1116
1116
  return stats;
1117
1117
  }, [mutations]);
1118
- React2.useMemo(() => {
1118
+ React4.useMemo(() => {
1119
1119
  if (filter === "all") return mutations;
1120
1120
  return mutations.filter((m) => m.source === filter);
1121
1121
  }, [mutations, filter]);
1122
- const anomalyCount = React2.useMemo(() => {
1122
+ const anomalyCount = React4.useMemo(() => {
1123
1123
  let count = 0;
1124
1124
  for (const m of mutations) {
1125
1125
  if (m.diff) {
@@ -1682,12 +1682,1679 @@ function TimelineScrubber({
1682
1682
  ` })
1683
1683
  ] });
1684
1684
  }
1685
- var DashboardContext = React2__default.default.createContext(null);
1685
+
1686
+ // src/core/analyzer.ts
1687
+ var StateAnalyzer = class {
1688
+ constructor() {
1689
+ this.invariants = [];
1690
+ this.updateTimestamps = /* @__PURE__ */ new Map();
1691
+ }
1692
+ /**
1693
+ * Register custom invariant rules
1694
+ */
1695
+ addInvariant(invariant) {
1696
+ this.invariants.push(invariant);
1697
+ }
1698
+ /**
1699
+ * Analyze a single mutation for issues
1700
+ */
1701
+ analyzeMutation(mutation, index, timeline) {
1702
+ const issues = [];
1703
+ if (mutation.diff) {
1704
+ for (const diff of mutation.diff) {
1705
+ if (diff.operation === "REMOVE") {
1706
+ issues.push({
1707
+ id: `issue_${mutation.id}_${diff.path}_loss`,
1708
+ category: "state-loss",
1709
+ severity: "critical",
1710
+ title: `State field removed: ${diff.path}`,
1711
+ description: `The field "${diff.path}" was removed from state. This may indicate an overwrite instead of a merge.`,
1712
+ mutationId: mutation.id,
1713
+ mutationIndex: index,
1714
+ path: diff.path,
1715
+ previousValue: diff.oldValue,
1716
+ currentValue: void 0,
1717
+ suggestion: "Use spread operator to preserve existing fields: setState(prev => ({ ...prev, newField }))",
1718
+ timestamp: mutation.timestamp
1719
+ });
1720
+ }
1721
+ if (diff.operation !== "REMOVE") {
1722
+ const invalidIssue = this.checkInvalidValue(diff, mutation, index);
1723
+ if (invalidIssue) {
1724
+ issues.push(invalidIssue);
1725
+ }
1726
+ }
1727
+ if (diff.operation === "UPDATE") {
1728
+ const typeIssue = this.checkTypeChange(diff, mutation, index);
1729
+ if (typeIssue) {
1730
+ issues.push(typeIssue);
1731
+ }
1732
+ }
1733
+ }
1734
+ }
1735
+ if (this.isNoOpUpdate(mutation)) {
1736
+ issues.push({
1737
+ id: `issue_${mutation.id}_noop`,
1738
+ category: "no-op-update",
1739
+ severity: "info",
1740
+ title: "Redundant state update",
1741
+ description: "This mutation did not change any values. Consider memoizing or adding conditions.",
1742
+ mutationId: mutation.id,
1743
+ mutationIndex: index,
1744
+ suggestion: "Add a condition before updating: if (newValue !== currentValue) setState(newValue)",
1745
+ timestamp: mutation.timestamp
1746
+ });
1747
+ }
1748
+ const excessiveIssue = this.checkExcessiveUpdates(mutation, index);
1749
+ if (excessiveIssue) {
1750
+ issues.push(excessiveIssue);
1751
+ }
1752
+ for (const invariant of this.invariants) {
1753
+ const violation = this.checkInvariant(invariant, mutation, index);
1754
+ if (violation) {
1755
+ issues.push(violation);
1756
+ }
1757
+ }
1758
+ return issues;
1759
+ }
1760
+ /**
1761
+ * Check for invalid values (NaN, unexpected undefined/null)
1762
+ */
1763
+ checkInvalidValue(diff, mutation, index) {
1764
+ const value = diff.newValue;
1765
+ if (typeof value === "number" && isNaN(value)) {
1766
+ return {
1767
+ id: `issue_${mutation.id}_${diff.path}_nan`,
1768
+ category: "invalid-value",
1769
+ severity: "critical",
1770
+ title: `NaN value at: ${diff.path}`,
1771
+ description: `The value at "${diff.path}" became NaN. This usually indicates a calculation with undefined/null.`,
1772
+ mutationId: mutation.id,
1773
+ mutationIndex: index,
1774
+ path: diff.path,
1775
+ previousValue: diff.oldValue,
1776
+ currentValue: value,
1777
+ suggestion: "Check for undefined/null values before calculation. Use default values: (value ?? 0)",
1778
+ timestamp: mutation.timestamp
1779
+ };
1780
+ }
1781
+ if (value === void 0 && diff.oldValue !== void 0 && diff.operation === "UPDATE") {
1782
+ return {
1783
+ id: `issue_${mutation.id}_${diff.path}_undefined`,
1784
+ category: "invalid-value",
1785
+ severity: "critical",
1786
+ title: `Value became undefined: ${diff.path}`,
1787
+ description: `The field "${diff.path}" changed from a defined value to undefined.`,
1788
+ mutationId: mutation.id,
1789
+ mutationIndex: index,
1790
+ path: diff.path,
1791
+ previousValue: diff.oldValue,
1792
+ currentValue: void 0,
1793
+ suggestion: "Ensure the value is always defined or explicitly handle undefined cases.",
1794
+ timestamp: mutation.timestamp
1795
+ };
1796
+ }
1797
+ return null;
1798
+ }
1799
+ /**
1800
+ * Check for unexpected type changes
1801
+ */
1802
+ checkTypeChange(diff, mutation, index) {
1803
+ if (diff.oldValue === void 0 || diff.oldValue === null) return null;
1804
+ if (diff.newValue === void 0 || diff.newValue === null) return null;
1805
+ const oldType = typeof diff.oldValue;
1806
+ const newType = typeof diff.newValue;
1807
+ if (oldType !== newType) {
1808
+ return {
1809
+ id: `issue_${mutation.id}_${diff.path}_type`,
1810
+ category: "type-change",
1811
+ severity: "warning",
1812
+ title: `Type changed: ${diff.path}`,
1813
+ description: `The type of "${diff.path}" changed from ${oldType} to ${newType}.`,
1814
+ mutationId: mutation.id,
1815
+ mutationIndex: index,
1816
+ path: diff.path,
1817
+ previousValue: diff.oldValue,
1818
+ currentValue: diff.newValue,
1819
+ suggestion: "Ensure consistent types. Use TypeScript or runtime validation.",
1820
+ timestamp: mutation.timestamp
1821
+ };
1822
+ }
1823
+ return null;
1824
+ }
1825
+ /**
1826
+ * Check if mutation is a no-op (no actual changes)
1827
+ */
1828
+ isNoOpUpdate(mutation) {
1829
+ if (!mutation.diff || mutation.diff.length === 0) {
1830
+ return true;
1831
+ }
1832
+ return mutation.diff.every((d) => {
1833
+ if (d.operation !== "UPDATE") return false;
1834
+ return JSON.stringify(d.oldValue) === JSON.stringify(d.newValue);
1835
+ });
1836
+ }
1837
+ /**
1838
+ * Check for excessive updates in short time period
1839
+ */
1840
+ checkExcessiveUpdates(mutation, index) {
1841
+ const key = mutation.component || mutation.source;
1842
+ const timestamps = this.updateTimestamps.get(key) || [];
1843
+ timestamps.push(mutation.timestamp);
1844
+ const cutoff = mutation.timestamp - 100;
1845
+ const recentTimestamps = timestamps.filter((t) => t >= cutoff);
1846
+ this.updateTimestamps.set(key, recentTimestamps);
1847
+ if (recentTimestamps.length > 5) {
1848
+ return {
1849
+ id: `issue_${mutation.id}_excessive`,
1850
+ category: "excessive-updates",
1851
+ severity: "warning",
1852
+ title: `Excessive updates from: ${key}`,
1853
+ description: `${recentTimestamps.length} mutations in 100ms from "${key}". This may cause performance issues.`,
1854
+ mutationId: mutation.id,
1855
+ mutationIndex: index,
1856
+ suggestion: "Use debouncing, batching, or memoization to reduce update frequency.",
1857
+ timestamp: mutation.timestamp
1858
+ };
1859
+ }
1860
+ return null;
1861
+ }
1862
+ /**
1863
+ * Check custom invariant rule
1864
+ */
1865
+ checkInvariant(invariant, mutation, index) {
1866
+ if (!mutation.nextState) return null;
1867
+ const value = getValueAtPath(mutation.nextState, invariant.path);
1868
+ try {
1869
+ const isValid = invariant.rule(value);
1870
+ if (!isValid) {
1871
+ return {
1872
+ id: `issue_${mutation.id}_invariant_${invariant.name}`,
1873
+ category: "broken-invariant",
1874
+ severity: "critical",
1875
+ title: `Invariant violated: ${invariant.name}`,
1876
+ description: invariant.message,
1877
+ mutationId: mutation.id,
1878
+ mutationIndex: index,
1879
+ path: invariant.path,
1880
+ currentValue: value,
1881
+ suggestion: `Ensure the invariant "${invariant.name}" is always maintained.`,
1882
+ timestamp: mutation.timestamp
1883
+ };
1884
+ }
1885
+ } catch (e) {
1886
+ return null;
1887
+ }
1888
+ return null;
1889
+ }
1890
+ /**
1891
+ * Analyze entire timeline for issues
1892
+ */
1893
+ analyzeTimeline(timeline) {
1894
+ const allIssues = [];
1895
+ this.updateTimestamps.clear();
1896
+ for (let i = 0; i < timeline.length; i++) {
1897
+ const issues = this.analyzeMutation(timeline[i], i, timeline);
1898
+ allIssues.push(...issues);
1899
+ }
1900
+ return allIssues;
1901
+ }
1902
+ /**
1903
+ * Build dependency graph showing which components touch which state paths
1904
+ */
1905
+ buildDependencyGraph(timeline) {
1906
+ const nodes = /* @__PURE__ */ new Map();
1907
+ for (const mutation of timeline) {
1908
+ if (!mutation.diff) continue;
1909
+ const component = mutation.component || mutation.source || "unknown";
1910
+ for (const diff of mutation.diff) {
1911
+ const existing = nodes.get(diff.path);
1912
+ if (existing) {
1913
+ existing.components.add(component);
1914
+ existing.mutationCount++;
1915
+ existing.lastMutationId = mutation.id;
1916
+ } else {
1917
+ nodes.set(diff.path, {
1918
+ path: diff.path,
1919
+ components: /* @__PURE__ */ new Set([component]),
1920
+ mutationCount: 1,
1921
+ lastMutationId: mutation.id
1922
+ });
1923
+ }
1924
+ }
1925
+ }
1926
+ const couplings = [];
1927
+ for (const [path, node] of nodes) {
1928
+ if (node.components.size > 1) {
1929
+ couplings.push({
1930
+ path,
1931
+ components: Array.from(node.components),
1932
+ severity: node.components.size > 2 ? "critical" : "warning"
1933
+ });
1934
+ }
1935
+ }
1936
+ return { nodes, couplings };
1937
+ }
1938
+ /**
1939
+ * Find causal chain - which mutations caused downstream effects
1940
+ */
1941
+ findCausalChain(mutationId, timeline) {
1942
+ const rootIndex = timeline.findIndex((m) => m.id === mutationId);
1943
+ if (rootIndex === -1) return null;
1944
+ const root = timeline[rootIndex];
1945
+ const effects = [];
1946
+ if (!root.diff) return { rootMutation: root, effects };
1947
+ const changedPaths = new Set(root.diff.map((d) => d.path));
1948
+ for (let i = rootIndex + 1; i < timeline.length; i++) {
1949
+ const mutation = timeline[i];
1950
+ if (!mutation.diff) continue;
1951
+ for (const diff of mutation.diff) {
1952
+ for (const changedPath of changedPaths) {
1953
+ if (diff.path.startsWith(changedPath) || changedPath.startsWith(diff.path)) {
1954
+ effects.push({
1955
+ mutation,
1956
+ causedBy: root.id,
1957
+ reason: `Uses path "${diff.path}" which was affected by "${changedPath}"`
1958
+ });
1959
+ break;
1960
+ }
1961
+ }
1962
+ }
1963
+ }
1964
+ return { rootMutation: root, effects };
1965
+ }
1966
+ /**
1967
+ * Find the first mutation that corrupted state
1968
+ * Uses binary search for efficiency
1969
+ */
1970
+ findCorruptionPoint(timeline, validator) {
1971
+ let left = 0;
1972
+ let right = timeline.length - 1;
1973
+ let result = null;
1974
+ while (left <= right) {
1975
+ const mid = Math.floor((left + right) / 2);
1976
+ const mutation = timeline[mid];
1977
+ if (mutation.nextState && !validator(mutation.nextState)) {
1978
+ result = { mutation, index: mid };
1979
+ right = mid - 1;
1980
+ } else {
1981
+ left = mid + 1;
1982
+ }
1983
+ }
1984
+ return result;
1985
+ }
1986
+ };
1987
+ function getValueAtPath(obj, path) {
1988
+ if (!path) return obj;
1989
+ const parts = path.split(".");
1990
+ let current = obj;
1991
+ for (const part of parts) {
1992
+ if (current === null || current === void 0) return void 0;
1993
+ if (typeof current !== "object") return void 0;
1994
+ current = current[part];
1995
+ }
1996
+ return current;
1997
+ }
1998
+ function DependencyGraph({
1999
+ graph,
2000
+ onPathSelect,
2001
+ selectedPath,
2002
+ className = ""
2003
+ }) {
2004
+ const [viewMode, setViewMode] = React4.useState("couplings");
2005
+ const [hoveredPath, setHoveredPath] = React4.useState(null);
2006
+ const sortedNodes = React4.useMemo(() => {
2007
+ const nodes = Array.from(graph.nodes.values());
2008
+ return nodes.sort((a, b) => b.mutationCount - a.mutationCount);
2009
+ }, [graph.nodes]);
2010
+ const allComponents = React4.useMemo(() => {
2011
+ const components = /* @__PURE__ */ new Set();
2012
+ for (const node of graph.nodes.values()) {
2013
+ for (const c of node.components) {
2014
+ components.add(c);
2015
+ }
2016
+ }
2017
+ return Array.from(components);
2018
+ }, [graph.nodes]);
2019
+ const componentColors = React4.useMemo(() => {
2020
+ const colors = {};
2021
+ const palette = [
2022
+ "#61dafb",
2023
+ "#764abc",
2024
+ "#f59e0b",
2025
+ "#22c55e",
2026
+ "#ef4444",
2027
+ "#a78bfa",
2028
+ "#f472b6",
2029
+ "#06b6d4",
2030
+ "#84cc16",
2031
+ "#f97316"
2032
+ ];
2033
+ allComponents.forEach((comp, i) => {
2034
+ colors[comp] = palette[i % palette.length];
2035
+ });
2036
+ return colors;
2037
+ }, [allComponents]);
2038
+ const handlePathClick = React4.useCallback((path) => {
2039
+ onPathSelect?.(path);
2040
+ }, [onPathSelect]);
2041
+ const displayNodes = viewMode === "couplings" ? sortedNodes.filter((n) => n.components.size > 1) : sortedNodes;
2042
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `dependency-graph ${className}`, children: [
2043
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-header", children: [
2044
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dg-title", children: "\u{1F517} State Dependencies" }),
2045
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-controls", children: [
2046
+ /* @__PURE__ */ jsxRuntime.jsxs(
2047
+ "button",
2048
+ {
2049
+ className: `dg-view-btn ${viewMode === "couplings" ? "active" : ""}`,
2050
+ onClick: () => setViewMode("couplings"),
2051
+ children: [
2052
+ "\u26A0\uFE0F Couplings Only (",
2053
+ graph.couplings.length,
2054
+ ")"
2055
+ ]
2056
+ }
2057
+ ),
2058
+ /* @__PURE__ */ jsxRuntime.jsxs(
2059
+ "button",
2060
+ {
2061
+ className: `dg-view-btn ${viewMode === "all" ? "active" : ""}`,
2062
+ onClick: () => setViewMode("all"),
2063
+ children: [
2064
+ "\u{1F4CA} All Paths (",
2065
+ graph.nodes.size,
2066
+ ")"
2067
+ ]
2068
+ }
2069
+ )
2070
+ ] })
2071
+ ] }),
2072
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "dg-legend", children: allComponents.map((comp) => /* @__PURE__ */ jsxRuntime.jsxs(
2073
+ "span",
2074
+ {
2075
+ className: "dg-legend-item",
2076
+ style: { "--comp-color": componentColors[comp] },
2077
+ children: [
2078
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dg-legend-dot" }),
2079
+ comp
2080
+ ]
2081
+ },
2082
+ comp
2083
+ )) }),
2084
+ graph.couplings.length > 0 && viewMode === "couplings" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-alert", children: [
2085
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dg-alert-icon", children: "\u26A0\uFE0F" }),
2086
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-alert-content", children: [
2087
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Hidden State Coupling Detected" }),
2088
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
2089
+ graph.couplings.length,
2090
+ " state path",
2091
+ graph.couplings.length > 1 ? "s are" : " is",
2092
+ " accessed by multiple components. This can cause unexpected state conflicts."
2093
+ ] })
2094
+ ] })
2095
+ ] }),
2096
+ displayNodes.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-empty", children: [
2097
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dg-empty-icon", children: "\u2705" }),
2098
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No state coupling detected" })
2099
+ ] }),
2100
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "dg-paths", children: displayNodes.map((node) => {
2101
+ const isCoupled = node.components.size > 1;
2102
+ const coupling = graph.couplings.find((c) => c.path === node.path);
2103
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2104
+ "div",
2105
+ {
2106
+ className: `dg-path ${isCoupled ? "dg-path--coupled" : ""} ${selectedPath === node.path ? "selected" : ""} ${hoveredPath === node.path ? "hovered" : ""}`,
2107
+ onClick: () => handlePathClick(node.path),
2108
+ onMouseEnter: () => setHoveredPath(node.path),
2109
+ onMouseLeave: () => setHoveredPath(null),
2110
+ children: [
2111
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-path-header", children: [
2112
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "dg-path-name", children: node.path }),
2113
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "dg-path-count", children: [
2114
+ node.mutationCount,
2115
+ " mutation",
2116
+ node.mutationCount > 1 ? "s" : ""
2117
+ ] })
2118
+ ] }),
2119
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "dg-path-components", children: Array.from(node.components).map((comp) => /* @__PURE__ */ jsxRuntime.jsx(
2120
+ "span",
2121
+ {
2122
+ className: "dg-component-tag",
2123
+ style: {
2124
+ backgroundColor: componentColors[comp] + "30",
2125
+ borderColor: componentColors[comp],
2126
+ color: componentColors[comp]
2127
+ },
2128
+ children: comp
2129
+ },
2130
+ comp
2131
+ )) }),
2132
+ isCoupled && coupling && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `dg-coupling-warning dg-coupling-${coupling.severity}`, children: [
2133
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "dg-warning-icon", children: coupling.severity === "critical" ? "\u{1F534}" : "\u{1F7E1}" }),
2134
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "dg-warning-text", children: [
2135
+ coupling.components.length,
2136
+ " components access this path"
2137
+ ] })
2138
+ ] })
2139
+ ]
2140
+ },
2141
+ node.path
2142
+ );
2143
+ }) }),
2144
+ displayNodes.length > 0 && displayNodes.length <= 20 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dg-visual", children: [
2145
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "dg-visual-title", children: "Dependency Visualization" }),
2146
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "dg-svg", viewBox: "0 0 400 300", children: [
2147
+ allComponents.map((comp, i) => {
2148
+ const x = 50 + i * (350 / Math.max(allComponents.length, 1));
2149
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
2150
+ /* @__PURE__ */ jsxRuntime.jsx(
2151
+ "circle",
2152
+ {
2153
+ cx: x,
2154
+ cy: 40,
2155
+ r: 20,
2156
+ fill: componentColors[comp],
2157
+ opacity: 0.8
2158
+ }
2159
+ ),
2160
+ /* @__PURE__ */ jsxRuntime.jsx(
2161
+ "text",
2162
+ {
2163
+ x,
2164
+ y: 75,
2165
+ textAnchor: "middle",
2166
+ fill: "#888",
2167
+ fontSize: "10",
2168
+ children: comp.length > 10 ? comp.slice(0, 10) + "..." : comp
2169
+ }
2170
+ )
2171
+ ] }, comp);
2172
+ }),
2173
+ displayNodes.slice(0, 8).map((node, i) => {
2174
+ const x = 50 + i * (350 / Math.max(displayNodes.length, 1));
2175
+ const isCoupled = node.components.size > 1;
2176
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
2177
+ Array.from(node.components).map((comp) => {
2178
+ const compIndex = allComponents.indexOf(comp);
2179
+ const compX = 50 + compIndex * (350 / Math.max(allComponents.length, 1));
2180
+ return /* @__PURE__ */ jsxRuntime.jsx(
2181
+ "line",
2182
+ {
2183
+ x1: compX,
2184
+ y1: 60,
2185
+ x2: x,
2186
+ y2: 180,
2187
+ stroke: componentColors[comp],
2188
+ strokeWidth: isCoupled ? 2 : 1,
2189
+ opacity: isCoupled ? 0.8 : 0.3
2190
+ },
2191
+ comp
2192
+ );
2193
+ }),
2194
+ /* @__PURE__ */ jsxRuntime.jsx(
2195
+ "rect",
2196
+ {
2197
+ x: x - 25,
2198
+ y: 180,
2199
+ width: 50,
2200
+ height: 24,
2201
+ rx: 4,
2202
+ fill: isCoupled ? "#ef444430" : "#16213e",
2203
+ stroke: isCoupled ? "#ef4444" : "#333"
2204
+ }
2205
+ ),
2206
+ /* @__PURE__ */ jsxRuntime.jsx(
2207
+ "text",
2208
+ {
2209
+ x,
2210
+ y: 196,
2211
+ textAnchor: "middle",
2212
+ fill: isCoupled ? "#fca5a5" : "#888",
2213
+ fontSize: "8",
2214
+ children: node.path.length > 8 ? node.path.slice(-8) : node.path
2215
+ }
2216
+ )
2217
+ ] }, node.path);
2218
+ }),
2219
+ displayNodes.length > 8 && /* @__PURE__ */ jsxRuntime.jsxs("text", { x: 200, y: 250, textAnchor: "middle", fill: "#666", fontSize: "12", children: [
2220
+ "+",
2221
+ displayNodes.length - 8,
2222
+ " more paths..."
2223
+ ] })
2224
+ ] })
2225
+ ] }),
2226
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: styles })
2227
+ ] });
2228
+ }
2229
+ var styles = `
2230
+ .dependency-graph {
2231
+ background: #1a1a2e;
2232
+ color: #e0e0e0;
2233
+ font-family: system-ui, sans-serif;
2234
+ height: 100%;
2235
+ display: flex;
2236
+ flex-direction: column;
2237
+ }
2238
+
2239
+ .dg-header {
2240
+ display: flex;
2241
+ justify-content: space-between;
2242
+ align-items: center;
2243
+ padding: 0.75rem 1rem;
2244
+ background: #16213e;
2245
+ border-bottom: 1px solid #333;
2246
+ flex-wrap: wrap;
2247
+ gap: 0.5rem;
2248
+ }
2249
+
2250
+ .dg-title {
2251
+ font-weight: 600;
2252
+ color: #00d9ff;
2253
+ }
2254
+
2255
+ .dg-controls {
2256
+ display: flex;
2257
+ gap: 0.5rem;
2258
+ }
2259
+
2260
+ .dg-view-btn {
2261
+ padding: 0.375rem 0.75rem;
2262
+ border: 1px solid #333;
2263
+ border-radius: 6px;
2264
+ background: transparent;
2265
+ color: #888;
2266
+ font-size: 0.8rem;
2267
+ cursor: pointer;
2268
+ transition: all 0.2s;
2269
+ }
2270
+
2271
+ .dg-view-btn:hover {
2272
+ background: #16213e;
2273
+ color: #ccc;
2274
+ }
2275
+
2276
+ .dg-view-btn.active {
2277
+ background: #00d9ff20;
2278
+ border-color: #00d9ff;
2279
+ color: #00d9ff;
2280
+ }
2281
+
2282
+ .dg-legend {
2283
+ display: flex;
2284
+ flex-wrap: wrap;
2285
+ gap: 0.75rem;
2286
+ padding: 0.75rem 1rem;
2287
+ background: #0f0f23;
2288
+ border-bottom: 1px solid #333;
2289
+ }
2290
+
2291
+ .dg-legend-item {
2292
+ display: flex;
2293
+ align-items: center;
2294
+ gap: 0.375rem;
2295
+ font-size: 0.8rem;
2296
+ color: #888;
2297
+ }
2298
+
2299
+ .dg-legend-dot {
2300
+ width: 10px;
2301
+ height: 10px;
2302
+ border-radius: 50%;
2303
+ background: var(--comp-color);
2304
+ }
2305
+
2306
+ .dg-alert {
2307
+ display: flex;
2308
+ gap: 0.75rem;
2309
+ padding: 1rem;
2310
+ margin: 0.75rem;
2311
+ background: #ef444420;
2312
+ border: 1px solid #ef4444;
2313
+ border-radius: 8px;
2314
+ }
2315
+
2316
+ .dg-alert-icon {
2317
+ font-size: 1.25rem;
2318
+ }
2319
+
2320
+ .dg-alert-content strong {
2321
+ color: #fca5a5;
2322
+ display: block;
2323
+ margin-bottom: 0.25rem;
2324
+ }
2325
+
2326
+ .dg-alert-content p {
2327
+ margin: 0;
2328
+ font-size: 0.875rem;
2329
+ color: #ccc;
2330
+ }
2331
+
2332
+ .dg-empty {
2333
+ flex: 1;
2334
+ display: flex;
2335
+ flex-direction: column;
2336
+ align-items: center;
2337
+ justify-content: center;
2338
+ color: #888;
2339
+ }
2340
+
2341
+ .dg-empty-icon {
2342
+ font-size: 2.5rem;
2343
+ margin-bottom: 0.5rem;
2344
+ }
2345
+
2346
+ .dg-paths {
2347
+ flex: 1;
2348
+ overflow: auto;
2349
+ padding: 0.75rem;
2350
+ }
2351
+
2352
+ .dg-path {
2353
+ background: #16213e;
2354
+ border-radius: 8px;
2355
+ padding: 0.875rem 1rem;
2356
+ margin-bottom: 0.625rem;
2357
+ cursor: pointer;
2358
+ border: 1px solid transparent;
2359
+ transition: all 0.2s;
2360
+ }
2361
+
2362
+ .dg-path:hover, .dg-path.hovered {
2363
+ border-color: #333;
2364
+ background: #1a2744;
2365
+ }
2366
+
2367
+ .dg-path.selected {
2368
+ border-color: #00d9ff;
2369
+ box-shadow: 0 0 0 2px #00d9ff30;
2370
+ }
2371
+
2372
+ .dg-path--coupled {
2373
+ border-left: 3px solid #ef4444;
2374
+ }
2375
+
2376
+ .dg-path-header {
2377
+ display: flex;
2378
+ justify-content: space-between;
2379
+ align-items: center;
2380
+ margin-bottom: 0.5rem;
2381
+ }
2382
+
2383
+ .dg-path-name {
2384
+ font-family: 'Fira Code', monospace;
2385
+ color: #a78bfa;
2386
+ font-size: 0.875rem;
2387
+ }
2388
+
2389
+ .dg-path-count {
2390
+ font-size: 0.75rem;
2391
+ color: #666;
2392
+ }
2393
+
2394
+ .dg-path-components {
2395
+ display: flex;
2396
+ flex-wrap: wrap;
2397
+ gap: 0.375rem;
2398
+ }
2399
+
2400
+ .dg-component-tag {
2401
+ padding: 0.25rem 0.5rem;
2402
+ border-radius: 4px;
2403
+ font-size: 0.75rem;
2404
+ border: 1px solid;
2405
+ }
2406
+
2407
+ .dg-coupling-warning {
2408
+ display: flex;
2409
+ align-items: center;
2410
+ gap: 0.375rem;
2411
+ margin-top: 0.625rem;
2412
+ padding: 0.375rem 0.5rem;
2413
+ border-radius: 4px;
2414
+ font-size: 0.75rem;
2415
+ }
2416
+
2417
+ .dg-coupling-critical {
2418
+ background: #ef444420;
2419
+ color: #fca5a5;
2420
+ }
2421
+
2422
+ .dg-coupling-warning {
2423
+ background: #fbbf2420;
2424
+ color: #fcd34d;
2425
+ }
2426
+
2427
+ .dg-visual {
2428
+ padding: 1rem;
2429
+ border-top: 1px solid #333;
2430
+ }
2431
+
2432
+ .dg-visual-title {
2433
+ font-size: 0.8rem;
2434
+ color: #888;
2435
+ margin-bottom: 0.5rem;
2436
+ }
2437
+
2438
+ .dg-svg {
2439
+ width: 100%;
2440
+ max-height: 200px;
2441
+ background: #0f0f23;
2442
+ border-radius: 8px;
2443
+ }
2444
+ `;
2445
+ var severityIcons = {
2446
+ critical: "\u{1F534}",
2447
+ warning: "\u{1F7E1}",
2448
+ info: "\u{1F7E2}"
2449
+ };
2450
+ var categoryIcons = {
2451
+ "state-loss": "\u{1F4E4}",
2452
+ "invalid-value": "\u26A0\uFE0F",
2453
+ "type-change": "\u{1F504}",
2454
+ "no-op-update": "\u267B\uFE0F",
2455
+ "excessive-updates": "\u26A1",
2456
+ "broken-invariant": "\u{1F6AB}",
2457
+ "hidden-coupling": "\u{1F517}",
2458
+ "temporal-anomaly": "\u23F0",
2459
+ "state-corruption": "\u{1F4A5}"
2460
+ };
2461
+ var categoryLabels = {
2462
+ "state-loss": "State Loss",
2463
+ "invalid-value": "Invalid Value",
2464
+ "type-change": "Type Change",
2465
+ "no-op-update": "Redundant Update",
2466
+ "excessive-updates": "Excessive Updates",
2467
+ "broken-invariant": "Broken Invariant",
2468
+ "hidden-coupling": "Hidden Coupling",
2469
+ "temporal-anomaly": "Temporal Anomaly",
2470
+ "state-corruption": "State Corruption"
2471
+ };
2472
+ function IssuesPanel({
2473
+ issues,
2474
+ onJumpToMutation,
2475
+ onSelectIssue,
2476
+ selectedIssueId,
2477
+ className = ""
2478
+ }) {
2479
+ const [severityFilter, setSeverityFilter] = React4.useState("all");
2480
+ const [categoryFilter, setCategoryFilter] = React4.useState("all");
2481
+ const [expandedIssue, setExpandedIssue] = React4.useState(null);
2482
+ const filteredIssues = React4.useMemo(() => {
2483
+ return issues.filter((issue) => {
2484
+ if (severityFilter !== "all" && issue.severity !== severityFilter) return false;
2485
+ if (categoryFilter !== "all" && issue.category !== categoryFilter) return false;
2486
+ return true;
2487
+ });
2488
+ }, [issues, severityFilter, categoryFilter]);
2489
+ const issueSummary = React4.useMemo(() => {
2490
+ const summary = { critical: 0, warning: 0, info: 0 };
2491
+ for (const issue of issues) {
2492
+ summary[issue.severity]++;
2493
+ }
2494
+ return summary;
2495
+ }, [issues]);
2496
+ const availableCategories = React4.useMemo(() => {
2497
+ return Array.from(new Set(issues.map((i) => i.category)));
2498
+ }, [issues]);
2499
+ const handleIssueClick = (issue) => {
2500
+ if (expandedIssue === issue.id) {
2501
+ setExpandedIssue(null);
2502
+ } else {
2503
+ setExpandedIssue(issue.id);
2504
+ onSelectIssue?.(issue);
2505
+ }
2506
+ };
2507
+ if (issues.length === 0) {
2508
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `issues-panel issues-panel--empty ${className}`, children: [
2509
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-empty", children: [
2510
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-empty-icon", children: "\u2705" }),
2511
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "No Issues Detected" }),
2512
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your state timeline looks clean!" })
2513
+ ] }),
2514
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: styles2 })
2515
+ ] });
2516
+ }
2517
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `issues-panel ${className}`, children: [
2518
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-summary", children: [
2519
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-summary-title", children: [
2520
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-summary-icon", children: "\u{1F6A8}" }),
2521
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2522
+ issues.length,
2523
+ " Issue",
2524
+ issues.length !== 1 ? "s" : "",
2525
+ " Detected"
2526
+ ] })
2527
+ ] }),
2528
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-summary-counts", children: [
2529
+ issueSummary.critical > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ip-count ip-count--critical", children: [
2530
+ severityIcons.critical,
2531
+ " ",
2532
+ issueSummary.critical
2533
+ ] }),
2534
+ issueSummary.warning > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ip-count ip-count--warning", children: [
2535
+ severityIcons.warning,
2536
+ " ",
2537
+ issueSummary.warning
2538
+ ] }),
2539
+ issueSummary.info > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ip-count ip-count--info", children: [
2540
+ severityIcons.info,
2541
+ " ",
2542
+ issueSummary.info
2543
+ ] })
2544
+ ] })
2545
+ ] }),
2546
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-filters", children: [
2547
+ /* @__PURE__ */ jsxRuntime.jsxs(
2548
+ "select",
2549
+ {
2550
+ value: severityFilter,
2551
+ onChange: (e) => setSeverityFilter(e.target.value),
2552
+ className: "ip-filter",
2553
+ children: [
2554
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Severities" }),
2555
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "critical", children: "\u{1F534} Critical" }),
2556
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "warning", children: "\u{1F7E1} Warning" }),
2557
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "info", children: "\u{1F7E2} Info" })
2558
+ ]
2559
+ }
2560
+ ),
2561
+ /* @__PURE__ */ jsxRuntime.jsxs(
2562
+ "select",
2563
+ {
2564
+ value: categoryFilter,
2565
+ onChange: (e) => setCategoryFilter(e.target.value),
2566
+ className: "ip-filter",
2567
+ children: [
2568
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All Categories" }),
2569
+ availableCategories.map((cat) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: cat, children: [
2570
+ categoryIcons[cat],
2571
+ " ",
2572
+ categoryLabels[cat]
2573
+ ] }, cat))
2574
+ ]
2575
+ }
2576
+ )
2577
+ ] }),
2578
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ip-list", children: filteredIssues.map((issue) => /* @__PURE__ */ jsxRuntime.jsxs(
2579
+ "div",
2580
+ {
2581
+ className: `ip-issue ip-issue--${issue.severity} ${selectedIssueId === issue.id ? "selected" : ""} ${expandedIssue === issue.id ? "expanded" : ""}`,
2582
+ onClick: () => handleIssueClick(issue),
2583
+ children: [
2584
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-header", children: [
2585
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-issue-severity", children: severityIcons[issue.severity] }),
2586
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-issue-category", children: categoryIcons[issue.category] }),
2587
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-issue-title", children: issue.title }),
2588
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ip-issue-index", children: [
2589
+ "#",
2590
+ issue.mutationIndex + 1
2591
+ ] })
2592
+ ] }),
2593
+ expandedIssue === issue.id && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-details", children: [
2594
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ip-issue-description", children: issue.description }),
2595
+ issue.path && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-field", children: [
2596
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-field-label", children: "Path:" }),
2597
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "ip-field-value", children: issue.path })
2598
+ ] }),
2599
+ issue.previousValue !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-field", children: [
2600
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-field-label", children: "Before:" }),
2601
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "ip-field-value ip-value-old", children: formatValue(issue.previousValue) })
2602
+ ] }),
2603
+ issue.currentValue !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-field", children: [
2604
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-field-label", children: "After:" }),
2605
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: `ip-field-value ip-value-new ${issue.severity === "critical" ? "ip-value-bad" : ""}`, children: formatValue(issue.currentValue) })
2606
+ ] }),
2607
+ issue.suggestion && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ip-issue-suggestion", children: [
2608
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ip-suggestion-icon", children: "\u{1F4A1}" }),
2609
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: issue.suggestion })
2610
+ ] }),
2611
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ip-issue-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
2612
+ "button",
2613
+ {
2614
+ className: "ip-action-btn",
2615
+ onClick: (e) => {
2616
+ e.stopPropagation();
2617
+ onJumpToMutation(issue.mutationId, issue.mutationIndex);
2618
+ },
2619
+ children: "\u{1F3AF} Jump to Mutation"
2620
+ }
2621
+ ) })
2622
+ ] })
2623
+ ]
2624
+ },
2625
+ issue.id
2626
+ )) }),
2627
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: styles2 })
2628
+ ] });
2629
+ }
2630
+ function formatValue(value) {
2631
+ if (value === void 0) return "undefined";
2632
+ if (value === null) return "null";
2633
+ if (typeof value === "number" && isNaN(value)) return "NaN";
2634
+ if (typeof value === "object") {
2635
+ const str = JSON.stringify(value);
2636
+ return str.length > 50 ? str.slice(0, 50) + "..." : str;
2637
+ }
2638
+ return String(value);
2639
+ }
2640
+ var styles2 = `
2641
+ .issues-panel {
2642
+ background: #1a1a2e;
2643
+ color: #e0e0e0;
2644
+ font-family: system-ui, sans-serif;
2645
+ height: 100%;
2646
+ display: flex;
2647
+ flex-direction: column;
2648
+ }
2649
+
2650
+ .issues-panel--empty {
2651
+ align-items: center;
2652
+ justify-content: center;
2653
+ }
2654
+
2655
+ .ip-empty {
2656
+ text-align: center;
2657
+ padding: 3rem;
2658
+ }
2659
+
2660
+ .ip-empty-icon {
2661
+ font-size: 4rem;
2662
+ display: block;
2663
+ margin-bottom: 1rem;
2664
+ }
2665
+
2666
+ .ip-empty h3 {
2667
+ margin: 0 0 0.5rem;
2668
+ color: #22c55e;
2669
+ }
2670
+
2671
+ .ip-empty p {
2672
+ margin: 0;
2673
+ color: #888;
2674
+ }
2675
+
2676
+ .ip-summary {
2677
+ display: flex;
2678
+ justify-content: space-between;
2679
+ align-items: center;
2680
+ padding: 1rem 1.25rem;
2681
+ background: linear-gradient(135deg, #ef444420, #fbbf2410);
2682
+ border-bottom: 1px solid #333;
2683
+ }
2684
+
2685
+ .ip-summary-title {
2686
+ display: flex;
2687
+ align-items: center;
2688
+ gap: 0.5rem;
2689
+ font-weight: 600;
2690
+ color: #fca5a5;
2691
+ }
2692
+
2693
+ .ip-summary-icon {
2694
+ font-size: 1.25rem;
2695
+ }
2696
+
2697
+ .ip-summary-counts {
2698
+ display: flex;
2699
+ gap: 0.75rem;
2700
+ }
2701
+
2702
+ .ip-count {
2703
+ padding: 0.25rem 0.75rem;
2704
+ border-radius: 12px;
2705
+ font-size: 0.875rem;
2706
+ font-weight: 600;
2707
+ }
2708
+
2709
+ .ip-count--critical { background: #ef444430; color: #fca5a5; }
2710
+ .ip-count--warning { background: #fbbf2430; color: #fcd34d; }
2711
+ .ip-count--info { background: #22c55e30; color: #86efac; }
2712
+
2713
+ .ip-filters {
2714
+ display: flex;
2715
+ gap: 0.75rem;
2716
+ padding: 0.75rem 1.25rem;
2717
+ background: #16213e;
2718
+ border-bottom: 1px solid #333;
2719
+ }
2720
+
2721
+ .ip-filter {
2722
+ flex: 1;
2723
+ padding: 0.5rem 0.75rem;
2724
+ border: 1px solid #333;
2725
+ border-radius: 6px;
2726
+ background: #0f0f23;
2727
+ color: #e0e0e0;
2728
+ font-size: 0.875rem;
2729
+ cursor: pointer;
2730
+ }
2731
+
2732
+ .ip-list {
2733
+ flex: 1;
2734
+ overflow: auto;
2735
+ padding: 0.75rem;
2736
+ }
2737
+
2738
+ .ip-issue {
2739
+ background: #16213e;
2740
+ border-radius: 8px;
2741
+ margin-bottom: 0.75rem;
2742
+ border-left: 4px solid;
2743
+ cursor: pointer;
2744
+ transition: all 0.2s;
2745
+ }
2746
+
2747
+ .ip-issue:hover {
2748
+ background: #1a2744;
2749
+ }
2750
+
2751
+ .ip-issue--critical { border-left-color: #ef4444; }
2752
+ .ip-issue--warning { border-left-color: #fbbf24; }
2753
+ .ip-issue--info { border-left-color: #22c55e; }
2754
+
2755
+ .ip-issue.selected {
2756
+ box-shadow: 0 0 0 2px #00d9ff40;
2757
+ }
2758
+
2759
+ .ip-issue-header {
2760
+ display: flex;
2761
+ align-items: center;
2762
+ gap: 0.625rem;
2763
+ padding: 0.875rem 1rem;
2764
+ }
2765
+
2766
+ .ip-issue-severity {
2767
+ font-size: 0.875rem;
2768
+ }
2769
+
2770
+ .ip-issue-category {
2771
+ font-size: 1rem;
2772
+ }
2773
+
2774
+ .ip-issue-title {
2775
+ flex: 1;
2776
+ font-size: 0.875rem;
2777
+ font-weight: 500;
2778
+ }
2779
+
2780
+ .ip-issue-index {
2781
+ font-size: 0.75rem;
2782
+ color: #666;
2783
+ font-family: 'Fira Code', monospace;
2784
+ }
2785
+
2786
+ .ip-issue-details {
2787
+ padding: 0 1rem 1rem;
2788
+ border-top: 1px solid #333;
2789
+ margin-top: 0.5rem;
2790
+ padding-top: 0.75rem;
2791
+ }
2792
+
2793
+ .ip-issue-description {
2794
+ margin: 0 0 1rem;
2795
+ font-size: 0.875rem;
2796
+ color: #ccc;
2797
+ line-height: 1.5;
2798
+ }
2799
+
2800
+ .ip-issue-field {
2801
+ display: flex;
2802
+ align-items: flex-start;
2803
+ gap: 0.75rem;
2804
+ margin-bottom: 0.5rem;
2805
+ font-size: 0.875rem;
2806
+ }
2807
+
2808
+ .ip-field-label {
2809
+ color: #888;
2810
+ min-width: 50px;
2811
+ }
2812
+
2813
+ .ip-field-value {
2814
+ font-family: 'Fira Code', monospace;
2815
+ padding: 0.25rem 0.5rem;
2816
+ background: #0f0f23;
2817
+ border-radius: 4px;
2818
+ font-size: 0.8rem;
2819
+ }
2820
+
2821
+ .ip-value-old {
2822
+ color: #fca5a5;
2823
+ background: #ef444420;
2824
+ }
2825
+
2826
+ .ip-value-new {
2827
+ color: #86efac;
2828
+ background: #22c55e20;
2829
+ }
2830
+
2831
+ .ip-value-bad {
2832
+ color: #fca5a5 !important;
2833
+ background: #ef444420 !important;
2834
+ }
2835
+
2836
+ .ip-issue-suggestion {
2837
+ display: flex;
2838
+ align-items: flex-start;
2839
+ gap: 0.5rem;
2840
+ margin-top: 1rem;
2841
+ padding: 0.75rem;
2842
+ background: #0f0f23;
2843
+ border-radius: 6px;
2844
+ font-size: 0.8rem;
2845
+ color: #a78bfa;
2846
+ }
2847
+
2848
+ .ip-suggestion-icon {
2849
+ flex-shrink: 0;
2850
+ }
2851
+
2852
+ .ip-issue-actions {
2853
+ margin-top: 1rem;
2854
+ display: flex;
2855
+ gap: 0.5rem;
2856
+ }
2857
+
2858
+ .ip-action-btn {
2859
+ padding: 0.5rem 1rem;
2860
+ border: 1px solid #333;
2861
+ border-radius: 6px;
2862
+ background: #0f0f23;
2863
+ color: #e0e0e0;
2864
+ font-size: 0.8rem;
2865
+ cursor: pointer;
2866
+ transition: all 0.2s;
2867
+ }
2868
+
2869
+ .ip-action-btn:hover {
2870
+ background: #16213e;
2871
+ border-color: #00d9ff;
2872
+ color: #00d9ff;
2873
+ }
2874
+ `;
2875
+ function getValueType(value) {
2876
+ if (value === null) return "null";
2877
+ if (value === void 0) return "undefined";
2878
+ if (typeof value === "number" && isNaN(value)) return "nan";
2879
+ if (Array.isArray(value)) return "array";
2880
+ if (typeof value === "function") return "function";
2881
+ return typeof value;
2882
+ }
2883
+ var typeIcons = {
2884
+ null: "\u2298",
2885
+ undefined: "\u2753",
2886
+ boolean: "\u25C9",
2887
+ number: "#",
2888
+ string: '"',
2889
+ array: "[]",
2890
+ object: "{}",
2891
+ nan: "\u26A0\uFE0F",
2892
+ function: "\u0192"
2893
+ };
2894
+ var typeColors = {
2895
+ null: "#f472b6",
2896
+ undefined: "#ef4444",
2897
+ boolean: "#fb923c",
2898
+ number: "#22c55e",
2899
+ string: "#fbbf24",
2900
+ array: "#a78bfa",
2901
+ object: "#60a5fa",
2902
+ nan: "#ef4444",
2903
+ function: "#888"
2904
+ };
2905
+ function StateTreeViewer({
2906
+ state,
2907
+ issuePaths = /* @__PURE__ */ new Set(),
2908
+ changedPaths = /* @__PURE__ */ new Set(),
2909
+ removedPaths = /* @__PURE__ */ new Set(),
2910
+ defaultExpandDepth = 3,
2911
+ searchQuery = "",
2912
+ onPathClick,
2913
+ className = ""
2914
+ }) {
2915
+ const [expandedPaths, setExpandedPaths] = React4.useState(/* @__PURE__ */ new Set());
2916
+ const [copiedPath, setCopiedPath] = React4.useState(null);
2917
+ const toggleExpand = React4.useCallback((path) => {
2918
+ setExpandedPaths((prev) => {
2919
+ const next = new Set(prev);
2920
+ if (next.has(path)) {
2921
+ next.delete(path);
2922
+ } else {
2923
+ next.add(path);
2924
+ }
2925
+ return next;
2926
+ });
2927
+ }, []);
2928
+ const copyPath = React4.useCallback((path, e) => {
2929
+ e.stopPropagation();
2930
+ navigator.clipboard.writeText(path);
2931
+ setCopiedPath(path);
2932
+ setTimeout(() => setCopiedPath(null), 2e3);
2933
+ }, []);
2934
+ const copyValue = React4.useCallback((value, e) => {
2935
+ e.stopPropagation();
2936
+ const text = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
2937
+ navigator.clipboard.writeText(text);
2938
+ }, []);
2939
+ const matchingPaths = React4.useMemo(() => {
2940
+ if (!searchQuery) return /* @__PURE__ */ new Set();
2941
+ const matches = /* @__PURE__ */ new Set();
2942
+ function traverse(obj, path) {
2943
+ if (!obj || typeof obj !== "object") return;
2944
+ for (const key of Object.keys(obj)) {
2945
+ const fullPath = path ? `${path}.${key}` : key;
2946
+ if (fullPath.toLowerCase().includes(searchQuery.toLowerCase())) {
2947
+ matches.add(fullPath);
2948
+ const parts = fullPath.split(".");
2949
+ for (let i = 1; i < parts.length; i++) {
2950
+ matches.add(parts.slice(0, i).join("."));
2951
+ }
2952
+ }
2953
+ traverse(obj[key], fullPath);
2954
+ }
2955
+ }
2956
+ traverse(state, "");
2957
+ return matches;
2958
+ }, [state, searchQuery]);
2959
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `state-tree-viewer ${className}`, children: [
2960
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "stv-header", children: [
2961
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-title", children: "\u{1F333} State Tree" }),
2962
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "stv-legend", children: [
2963
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-legend-item stv-legend-changed", children: "\u25CF Changed" }),
2964
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-legend-item stv-legend-removed", children: "\u25CF Removed" }),
2965
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-legend-item stv-legend-issue", children: "\u25CF Issue" })
2966
+ ] })
2967
+ ] }),
2968
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stv-tree", children: /* @__PURE__ */ jsxRuntime.jsx(
2969
+ TreeNode,
2970
+ {
2971
+ value: state,
2972
+ path: "",
2973
+ depth: 0,
2974
+ issuePaths,
2975
+ changedPaths,
2976
+ removedPaths,
2977
+ expandedPaths,
2978
+ matchingPaths,
2979
+ defaultExpandDepth,
2980
+ searchQuery,
2981
+ copiedPath,
2982
+ onToggle: toggleExpand,
2983
+ onCopyPath: copyPath,
2984
+ onCopyValue: copyValue,
2985
+ onPathClick
2986
+ }
2987
+ ) }),
2988
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: styles3 })
2989
+ ] });
2990
+ }
2991
+ function TreeNode({
2992
+ value,
2993
+ path,
2994
+ depth,
2995
+ keyName,
2996
+ issuePaths,
2997
+ changedPaths,
2998
+ removedPaths,
2999
+ expandedPaths,
3000
+ matchingPaths,
3001
+ defaultExpandDepth,
3002
+ searchQuery,
3003
+ copiedPath,
3004
+ onToggle,
3005
+ onCopyPath,
3006
+ onCopyValue,
3007
+ onPathClick
3008
+ }) {
3009
+ const valueType = getValueType(value);
3010
+ const hasIssue = path ? issuePaths.has(path) : false;
3011
+ const isChanged = path ? changedPaths.has(path) : false;
3012
+ const isRemoved = path ? removedPaths.has(path) : false;
3013
+ const isMatch = path ? matchingPaths.has(path) : false;
3014
+ const isExpandable = valueType === "object" || valueType === "array";
3015
+ const isExpanded = expandedPaths.has(path) || depth < defaultExpandDepth && !expandedPaths.has(path) || isMatch;
3016
+ const entries = isExpandable && value ? Object.entries(value) : [];
3017
+ const handleClick = () => {
3018
+ if (isExpandable) {
3019
+ onToggle(path);
3020
+ } else if (onPathClick) {
3021
+ onPathClick(path, value);
3022
+ }
3023
+ };
3024
+ const highlightKey = (key) => {
3025
+ if (!searchQuery) return key;
3026
+ const idx = key.toLowerCase().indexOf(searchQuery.toLowerCase());
3027
+ if (idx === -1) return key;
3028
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3029
+ key.slice(0, idx),
3030
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-highlight", children: key.slice(idx, idx + searchQuery.length) }),
3031
+ key.slice(idx + searchQuery.length)
3032
+ ] });
3033
+ };
3034
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `stv-node ${depth === 0 ? "stv-root" : ""}`, children: [
3035
+ /* @__PURE__ */ jsxRuntime.jsxs(
3036
+ "div",
3037
+ {
3038
+ className: `stv-row ${isExpandable ? "stv-expandable" : ""} ${hasIssue ? "stv-has-issue" : ""} ${isChanged ? "stv-changed" : ""} ${isRemoved ? "stv-removed" : ""} ${isMatch ? "stv-match" : ""}`,
3039
+ onClick: handleClick,
3040
+ children: [
3041
+ isExpandable ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-toggle", children: isExpanded ? "\u25BC" : "\u25B6" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-toggle stv-toggle-spacer" }),
3042
+ keyName !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stv-key", children: [
3043
+ highlightKey(keyName),
3044
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-colon", children: ":" })
3045
+ ] }),
3046
+ /* @__PURE__ */ jsxRuntime.jsx(
3047
+ "span",
3048
+ {
3049
+ className: "stv-type-icon",
3050
+ style: { color: typeColors[valueType] },
3051
+ title: valueType,
3052
+ children: typeIcons[valueType]
3053
+ }
3054
+ ),
3055
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `stv-value stv-value-${valueType}`, children: renderValue(value, valueType, isExpanded) }),
3056
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stv-badges", children: [
3057
+ hasIssue && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-badge stv-badge-issue", title: "Has issue", children: "\u26A0\uFE0F" }),
3058
+ isChanged && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-badge stv-badge-changed", title: "Changed", children: "\u25CF" }),
3059
+ isRemoved && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-badge stv-badge-removed", title: "Removed", children: "\u2715" }),
3060
+ valueType === "nan" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-badge stv-badge-nan", children: "NaN!" }),
3061
+ valueType === "undefined" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-badge stv-badge-undefined", children: "undef" })
3062
+ ] }),
3063
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "stv-actions", children: [
3064
+ /* @__PURE__ */ jsxRuntime.jsx(
3065
+ "button",
3066
+ {
3067
+ className: "stv-action",
3068
+ onClick: (e) => onCopyPath(path, e),
3069
+ title: "Copy path",
3070
+ children: copiedPath === path ? "\u2713" : "\u{1F4CB}"
3071
+ }
3072
+ ),
3073
+ /* @__PURE__ */ jsxRuntime.jsx(
3074
+ "button",
3075
+ {
3076
+ className: "stv-action",
3077
+ onClick: (e) => onCopyValue(value, e),
3078
+ title: "Copy value",
3079
+ children: "\u{1F4C4}"
3080
+ }
3081
+ )
3082
+ ] })
3083
+ ]
3084
+ }
3085
+ ),
3086
+ isExpandable && isExpanded && entries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stv-children", children: entries.map(([childKey, childValue]) => {
3087
+ const childPath = path ? `${path}.${childKey}` : childKey;
3088
+ return /* @__PURE__ */ jsxRuntime.jsx(
3089
+ TreeNode,
3090
+ {
3091
+ value: childValue,
3092
+ path: childPath,
3093
+ depth: depth + 1,
3094
+ keyName: childKey,
3095
+ issuePaths,
3096
+ changedPaths,
3097
+ removedPaths,
3098
+ expandedPaths,
3099
+ matchingPaths,
3100
+ defaultExpandDepth,
3101
+ searchQuery,
3102
+ copiedPath,
3103
+ onToggle,
3104
+ onCopyPath,
3105
+ onCopyValue,
3106
+ onPathClick
3107
+ },
3108
+ childPath
3109
+ );
3110
+ }) }),
3111
+ isExpandable && isExpanded && entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "stv-children", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "stv-empty", children: "(empty)" }) })
3112
+ ] });
3113
+ }
3114
+ function renderValue(value, valueType, isExpanded) {
3115
+ switch (valueType) {
3116
+ case "null":
3117
+ return "null";
3118
+ case "undefined":
3119
+ return "undefined";
3120
+ case "nan":
3121
+ return "NaN";
3122
+ case "boolean":
3123
+ return String(value);
3124
+ case "number":
3125
+ return String(value);
3126
+ case "string":
3127
+ const str = value;
3128
+ const display = str.length > 50 ? str.slice(0, 50) + "..." : str;
3129
+ return `"${display}"`;
3130
+ case "array":
3131
+ if (!isExpanded) {
3132
+ const arr = value;
3133
+ return `Array(${arr.length})`;
3134
+ }
3135
+ return "";
3136
+ case "object":
3137
+ if (!isExpanded) {
3138
+ const obj = value;
3139
+ const keys = Object.keys(obj);
3140
+ return `{${keys.length} keys}`;
3141
+ }
3142
+ return "";
3143
+ case "function":
3144
+ return "function()";
3145
+ default:
3146
+ return String(value);
3147
+ }
3148
+ }
3149
+ var styles3 = `
3150
+ .state-tree-viewer {
3151
+ background: #1a1a2e;
3152
+ color: #e0e0e0;
3153
+ font-family: system-ui, sans-serif;
3154
+ height: 100%;
3155
+ display: flex;
3156
+ flex-direction: column;
3157
+ }
3158
+
3159
+ .stv-header {
3160
+ display: flex;
3161
+ justify-content: space-between;
3162
+ align-items: center;
3163
+ padding: 0.75rem 1rem;
3164
+ background: #16213e;
3165
+ border-bottom: 1px solid #333;
3166
+ }
3167
+
3168
+ .stv-title {
3169
+ font-weight: 600;
3170
+ color: #00d9ff;
3171
+ }
3172
+
3173
+ .stv-legend {
3174
+ display: flex;
3175
+ gap: 1rem;
3176
+ font-size: 0.75rem;
3177
+ }
3178
+
3179
+ .stv-legend-item {
3180
+ opacity: 0.7;
3181
+ }
3182
+
3183
+ .stv-legend-changed { color: #fbbf24; }
3184
+ .stv-legend-removed { color: #ef4444; }
3185
+ .stv-legend-issue { color: #f472b6; }
3186
+
3187
+ .stv-tree {
3188
+ flex: 1;
3189
+ overflow: auto;
3190
+ padding: 0.75rem;
3191
+ font-family: 'Fira Code', 'Consolas', monospace;
3192
+ font-size: 0.875rem;
3193
+ }
3194
+
3195
+ .stv-node {
3196
+ line-height: 1.6;
3197
+ }
3198
+
3199
+ .stv-row {
3200
+ display: flex;
3201
+ align-items: center;
3202
+ padding: 0.125rem 0.25rem;
3203
+ border-radius: 4px;
3204
+ cursor: default;
3205
+ }
3206
+
3207
+ .stv-row:hover {
3208
+ background: #16213e;
3209
+ }
3210
+
3211
+ .stv-expandable {
3212
+ cursor: pointer;
3213
+ }
3214
+
3215
+ .stv-toggle {
3216
+ width: 1rem;
3217
+ font-size: 0.7rem;
3218
+ color: #666;
3219
+ }
3220
+
3221
+ .stv-toggle-spacer {
3222
+ visibility: hidden;
3223
+ }
3224
+
3225
+ .stv-key {
3226
+ color: #a78bfa;
3227
+ margin-right: 0.375rem;
3228
+ }
3229
+
3230
+ .stv-colon {
3231
+ color: #666;
3232
+ }
3233
+
3234
+ .stv-type-icon {
3235
+ font-size: 0.75rem;
3236
+ width: 1.25rem;
3237
+ text-align: center;
3238
+ opacity: 0.7;
3239
+ }
3240
+
3241
+ .stv-value {
3242
+ flex: 1;
3243
+ }
3244
+
3245
+ .stv-value-null { color: #f472b6; font-style: italic; }
3246
+ .stv-value-undefined { color: #ef4444; font-style: italic; }
3247
+ .stv-value-nan { color: #ef4444; font-weight: bold; }
3248
+ .stv-value-boolean { color: #fb923c; }
3249
+ .stv-value-number { color: #22c55e; }
3250
+ .stv-value-string { color: #fbbf24; }
3251
+ .stv-value-array { color: #a78bfa; }
3252
+ .stv-value-object { color: #60a5fa; }
3253
+ .stv-value-function { color: #888; font-style: italic; }
3254
+
3255
+ .stv-badges {
3256
+ display: flex;
3257
+ gap: 0.25rem;
3258
+ margin-left: 0.5rem;
3259
+ }
3260
+
3261
+ .stv-badge {
3262
+ font-size: 0.7rem;
3263
+ padding: 0 0.25rem;
3264
+ border-radius: 3px;
3265
+ }
3266
+
3267
+ .stv-badge-issue {
3268
+ background: #f472b640;
3269
+ }
3270
+
3271
+ .stv-badge-changed {
3272
+ color: #fbbf24;
3273
+ }
3274
+
3275
+ .stv-badge-removed {
3276
+ color: #ef4444;
3277
+ background: #ef444430;
3278
+ padding: 0 0.375rem;
3279
+ }
3280
+
3281
+ .stv-badge-nan, .stv-badge-undefined {
3282
+ background: #ef444440;
3283
+ color: #fca5a5;
3284
+ padding: 0.125rem 0.375rem;
3285
+ font-weight: 600;
3286
+ }
3287
+
3288
+ .stv-actions {
3289
+ display: none;
3290
+ gap: 0.25rem;
3291
+ margin-left: 0.5rem;
3292
+ }
3293
+
3294
+ .stv-row:hover .stv-actions {
3295
+ display: flex;
3296
+ }
3297
+
3298
+ .stv-action {
3299
+ padding: 0.125rem 0.375rem;
3300
+ border: none;
3301
+ background: transparent;
3302
+ color: #666;
3303
+ cursor: pointer;
3304
+ font-size: 0.75rem;
3305
+ border-radius: 3px;
3306
+ }
3307
+
3308
+ .stv-action:hover {
3309
+ background: #333;
3310
+ color: #fff;
3311
+ }
3312
+
3313
+ .stv-children {
3314
+ margin-left: 1.25rem;
3315
+ border-left: 1px solid #333;
3316
+ padding-left: 0.5rem;
3317
+ }
3318
+
3319
+ .stv-empty {
3320
+ color: #666;
3321
+ font-style: italic;
3322
+ font-size: 0.8rem;
3323
+ padding: 0.25rem 0;
3324
+ }
3325
+
3326
+ .stv-has-issue {
3327
+ background: #f472b620 !important;
3328
+ }
3329
+
3330
+ .stv-changed {
3331
+ background: #fbbf2420 !important;
3332
+ }
3333
+
3334
+ .stv-removed {
3335
+ background: #ef444420 !important;
3336
+ text-decoration: line-through;
3337
+ opacity: 0.7;
3338
+ }
3339
+
3340
+ .stv-match .stv-key {
3341
+ background: #fbbf2440;
3342
+ padding: 0 0.25rem;
3343
+ border-radius: 2px;
3344
+ }
3345
+
3346
+ .stv-highlight {
3347
+ background: #fbbf2480;
3348
+ padding: 0 0.125rem;
3349
+ border-radius: 2px;
3350
+ }
3351
+ `;
3352
+ var DashboardContext = React4__default.default.createContext(null);
1686
3353
  function DashboardProvider({
1687
3354
  serverUrl = "http://localhost:8080",
1688
3355
  children
1689
3356
  }) {
1690
- const [state, setState] = React2.useState({
3357
+ const [state, setState] = React4.useState({
1691
3358
  serverUrl,
1692
3359
  sessions: [],
1693
3360
  currentSession: null,
@@ -1730,7 +3397,7 @@ function DashboardProvider({
1730
3397
  if (!state.currentSession) return;
1731
3398
  await selectSession(state.currentSession.id);
1732
3399
  };
1733
- React2.useEffect(() => {
3400
+ React4.useEffect(() => {
1734
3401
  loadSessions();
1735
3402
  }, [serverUrl]);
1736
3403
  const value = {
@@ -1743,17 +3410,17 @@ function DashboardProvider({
1743
3410
  return /* @__PURE__ */ jsxRuntime.jsx(DashboardContext.Provider, { value, children });
1744
3411
  }
1745
3412
  function useDashboard() {
1746
- const context = React2__default.default.useContext(DashboardContext);
3413
+ const context = React4__default.default.useContext(DashboardContext);
1747
3414
  if (!context) {
1748
3415
  throw new Error("useDashboard must be used within a DashboardProvider");
1749
3416
  }
1750
3417
  return context;
1751
3418
  }
1752
3419
  function usePlayback(timeline) {
1753
- const [currentIndex, setCurrentIndex] = React2.useState(0);
1754
- const [isPlaying, setIsPlaying] = React2.useState(false);
1755
- const [playbackSpeed, setPlaybackSpeed] = React2.useState(1);
1756
- React2.useEffect(() => {
3420
+ const [currentIndex, setCurrentIndex] = React4.useState(0);
3421
+ const [isPlaying, setIsPlaying] = React4.useState(false);
3422
+ const [playbackSpeed, setPlaybackSpeed] = React4.useState(1);
3423
+ React4.useEffect(() => {
1757
3424
  if (!isPlaying || timeline.length === 0) return;
1758
3425
  const interval = setInterval(() => {
1759
3426
  setCurrentIndex((i) => {
@@ -1795,19 +3462,48 @@ function DashboardContent() {
1795
3462
  loadSessions
1796
3463
  } = useDashboard();
1797
3464
  const playback = usePlayback(timeline);
1798
- const [activePanel, setActivePanel] = React2.useState("diff");
1799
- const [autoRefresh, setAutoRefresh] = React2.useState(true);
1800
- React2.useEffect(() => {
3465
+ const [mainTab, setMainTab] = React4.useState("issues");
3466
+ const [detailTab, setDetailTab] = React4.useState("tree");
3467
+ const [autoRefresh, setAutoRefresh] = React4.useState(true);
3468
+ const [searchQuery, setSearchQuery] = React4.useState("");
3469
+ const analyzer2 = React4.useMemo(() => new StateAnalyzer(), []);
3470
+ const issues = React4.useMemo(() => {
3471
+ return analyzer2.analyzeTimeline(timeline);
3472
+ }, [analyzer2, timeline]);
3473
+ const dependencyGraph = React4.useMemo(() => {
3474
+ return analyzer2.buildDependencyGraph(timeline);
3475
+ }, [analyzer2, timeline]);
3476
+ const issuePaths = React4.useMemo(() => {
3477
+ return new Set(issues.filter((i) => i.path).map((i) => i.path));
3478
+ }, [issues]);
3479
+ const changedPaths = React4.useMemo(() => {
3480
+ if (!selectedMutation?.diff) return /* @__PURE__ */ new Set();
3481
+ return new Set(selectedMutation.diff.map((d) => d.path));
3482
+ }, [selectedMutation]);
3483
+ const removedPaths = React4.useMemo(() => {
3484
+ if (!selectedMutation?.diff) return /* @__PURE__ */ new Set();
3485
+ return new Set(
3486
+ selectedMutation.diff.filter((d) => d.operation === "REMOVE").map((d) => d.path)
3487
+ );
3488
+ }, [selectedMutation]);
3489
+ const issueCounts = React4.useMemo(() => {
3490
+ const counts = { critical: 0, warning: 0, info: 0 };
3491
+ for (const issue of issues) {
3492
+ counts[issue.severity]++;
3493
+ }
3494
+ return counts;
3495
+ }, [issues]);
3496
+ React4.useEffect(() => {
1801
3497
  if (playback.currentMutation) {
1802
3498
  selectMutation(playback.currentMutation);
1803
3499
  }
1804
3500
  }, [playback.currentMutation, selectMutation]);
1805
- React2.useEffect(() => {
3501
+ React4.useEffect(() => {
1806
3502
  if (!autoRefresh) return;
1807
3503
  const interval = setInterval(loadSessions, 5e3);
1808
3504
  return () => clearInterval(interval);
1809
3505
  }, [autoRefresh, loadSessions]);
1810
- React2.useEffect(() => {
3506
+ React4.useEffect(() => {
1811
3507
  const handleKeyDown = (e) => {
1812
3508
  if (e.target instanceof HTMLInputElement) return;
1813
3509
  switch (e.key) {
@@ -1821,25 +3517,58 @@ function DashboardContent() {
1821
3517
  e.preventDefault();
1822
3518
  playback.isPlaying ? playback.pause() : playback.play();
1823
3519
  break;
3520
+ case "1":
3521
+ setMainTab("issues");
3522
+ break;
3523
+ case "2":
3524
+ setMainTab("timeline");
3525
+ break;
3526
+ case "3":
3527
+ setMainTab("graph");
3528
+ break;
1824
3529
  }
1825
3530
  };
1826
3531
  window.addEventListener("keydown", handleKeyDown);
1827
3532
  return () => window.removeEventListener("keydown", handleKeyDown);
1828
3533
  }, [playback]);
1829
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard", children: [
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(
3534
+ const handleJumpToMutation = (mutationId, index) => {
3535
+ playback.goTo(index);
3536
+ const mutation = timeline[index];
3537
+ if (mutation) {
3538
+ selectMutation(mutation);
3539
+ }
3540
+ setMainTab("timeline");
3541
+ };
3542
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "surgeon-dashboard-v2", children: [
3543
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sd2-header", children: [
3544
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-header-left", children: [
3545
+ /* @__PURE__ */ jsxRuntime.jsxs("h1", { className: "sd2-logo", children: [
3546
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-logo-icon", children: "\u{1F52C}" }),
3547
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-logo-text", children: "State Surgeon" }),
3548
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-version", children: "v2.0" })
3549
+ ] }),
3550
+ issues.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-issue-badges", children: [
3551
+ issueCounts.critical > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd2-badge sd2-badge-critical", children: [
3552
+ "\u{1F534} ",
3553
+ issueCounts.critical
3554
+ ] }),
3555
+ issueCounts.warning > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd2-badge sd2-badge-warning", children: [
3556
+ "\u{1F7E1} ",
3557
+ issueCounts.warning
3558
+ ] }),
3559
+ issueCounts.info > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd2-badge sd2-badge-info", children: [
3560
+ "\u{1F7E2} ",
3561
+ issueCounts.info
3562
+ ] })
3563
+ ] })
3564
+ ] }),
3565
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-header-center", children: /* @__PURE__ */ jsxRuntime.jsxs(
1837
3566
  "select",
1838
3567
  {
1839
3568
  value: currentSession?.id || "",
1840
3569
  onChange: (e) => selectSession(e.target.value),
1841
3570
  disabled: isLoading,
1842
- className: "sd-session-select",
3571
+ className: "sd2-session-select",
1843
3572
  children: [
1844
3573
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select a session..." }),
1845
3574
  sessions.map((session) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: session.id, children: [
@@ -1853,8 +3582,18 @@ function DashboardContent() {
1853
3582
  ]
1854
3583
  }
1855
3584
  ) }),
1856
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-header-right", children: [
1857
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sd-auto-refresh", children: [
3585
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-header-right", children: [
3586
+ /* @__PURE__ */ jsxRuntime.jsx(
3587
+ "input",
3588
+ {
3589
+ type: "text",
3590
+ placeholder: "\u{1F50D} Search state paths...",
3591
+ value: searchQuery,
3592
+ onChange: (e) => setSearchQuery(e.target.value),
3593
+ className: "sd2-search"
3594
+ }
3595
+ ),
3596
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sd2-auto-refresh", children: [
1858
3597
  /* @__PURE__ */ jsxRuntime.jsx(
1859
3598
  "input",
1860
3599
  {
@@ -1863,109 +3602,177 @@ function DashboardContent() {
1863
3602
  onChange: (e) => setAutoRefresh(e.target.checked)
1864
3603
  }
1865
3604
  ),
1866
- "Auto-refresh"
3605
+ "Auto"
1867
3606
  ] }),
1868
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: loadSessions, disabled: isLoading, className: "sd-refresh-btn", children: isLoading ? "\u23F3" : "\u{1F504}" })
3607
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: loadSessions, disabled: isLoading, className: "sd2-refresh-btn", children: isLoading ? "\u23F3" : "\u{1F504}" })
1869
3608
  ] })
1870
3609
  ] }),
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 })
3610
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-error", children: [
3611
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u274C" }),
3612
+ " ",
3613
+ error
1874
3614
  ] }),
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
- ] })
3615
+ !currentSession && !isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-welcome", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-welcome-content", children: [
3616
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-welcome-icon", children: "\u{1F52C}" }),
3617
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "State Surgeon v2.0" }),
3618
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Forensic debugging platform for JavaScript applications" }),
3619
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-features", children: [
3620
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-feature", children: [
3621
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-feature-icon", children: "\u{1F6A8}" }),
3622
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Issue Detection" }),
3623
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Find state loss, invalid values, broken invariants" })
3624
+ ] }),
3625
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-feature", children: [
3626
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-feature-icon", children: "\u{1F333}" }),
3627
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "State Tree" }),
3628
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Visual tree with inline anomaly badges" })
3629
+ ] }),
3630
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-feature", children: [
3631
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-feature-icon", children: "\u{1F517}" }),
3632
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Dependency Graph" }),
3633
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Detect hidden coupling between components" })
3634
+ ] }),
3635
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-feature", children: [
3636
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-feature-icon", children: "\u23F1\uFE0F" }),
3637
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Timeline Analysis" }),
3638
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "See exactly what your code did" })
1897
3639
  ] })
1898
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-welcome-sessions", children: [
3640
+ ] }),
3641
+ sessions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-session-list", children: [
1899
3642
  /* @__PURE__ */ jsxRuntime.jsxs("h3", { children: [
1900
- "Available Sessions (",
3643
+ "Sessions (",
1901
3644
  sessions.length,
1902
3645
  ")"
1903
3646
  ] }),
1904
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd-session-list", children: sessions.map((session) => /* @__PURE__ */ jsxRuntime.jsxs(
3647
+ sessions.map((session) => /* @__PURE__ */ jsxRuntime.jsxs(
1905
3648
  "button",
1906
3649
  {
1907
- className: "sd-session-card",
3650
+ className: "sd2-session-card",
1908
3651
  onClick: () => selectSession(session.id),
1909
3652
  children: [
1910
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd-session-app", children: session.appId }),
1911
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd-session-id", children: [
1912
- session.id.slice(0, 16),
1913
- "..."
1914
- ] }),
1915
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd-session-count", children: [
3653
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-session-app", children: session.appId }),
3654
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "sd2-session-count", children: [
1916
3655
  session.mutationCount,
1917
3656
  " mutations"
1918
3657
  ] })
1919
3658
  ]
1920
3659
  },
1921
3660
  session.id
1922
- )) })
3661
+ ))
1923
3662
  ] })
1924
3663
  ] }) }),
1925
- isLoading && !currentSession && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-loading", children: [
1926
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd-spinner" }),
3664
+ isLoading && !currentSession && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-loading", children: [
3665
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-spinner" }),
1927
3666
  /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Loading sessions..." })
1928
3667
  ] }),
1929
- currentSession && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-main", children: [
1930
- /* @__PURE__ */ jsxRuntime.jsx("section", { className: "sd-timeline", children: /* @__PURE__ */ jsxRuntime.jsx(
1931
- TimelineScrubber,
1932
- {
1933
- mutations: timeline,
1934
- selectedIndex: playback.currentIndex,
1935
- onSelect: (index, mutation) => {
1936
- playback.goTo(index);
1937
- selectMutation(mutation);
1938
- },
1939
- isPlaying: playback.isPlaying,
1940
- onPlay: playback.play,
1941
- onPause: playback.pause,
1942
- onStepForward: playback.stepForward,
1943
- onStepBackward: playback.stepBackward,
1944
- playbackSpeed: playback.playbackSpeed,
1945
- onSpeedChange: playback.setSpeed
1946
- }
1947
- ) }),
1948
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-panels", children: [
1949
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-panel-tabs", children: [
3668
+ currentSession && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-main", children: [
3669
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-left-panel", children: [
3670
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-tabs", children: [
3671
+ /* @__PURE__ */ jsxRuntime.jsxs(
3672
+ "button",
3673
+ {
3674
+ className: `sd2-tab ${mainTab === "issues" ? "active" : ""}`,
3675
+ onClick: () => setMainTab("issues"),
3676
+ children: [
3677
+ "\u{1F6A8} Issues ",
3678
+ issues.length > 0 && `(${issues.length})`
3679
+ ]
3680
+ }
3681
+ ),
3682
+ /* @__PURE__ */ jsxRuntime.jsx(
3683
+ "button",
3684
+ {
3685
+ className: `sd2-tab ${mainTab === "timeline" ? "active" : ""}`,
3686
+ onClick: () => setMainTab("timeline"),
3687
+ children: "\u{1F4CA} Timeline"
3688
+ }
3689
+ ),
3690
+ /* @__PURE__ */ jsxRuntime.jsxs(
3691
+ "button",
3692
+ {
3693
+ className: `sd2-tab ${mainTab === "graph" ? "active" : ""}`,
3694
+ onClick: () => setMainTab("graph"),
3695
+ children: [
3696
+ "\u{1F517} Graph ",
3697
+ dependencyGraph.couplings.length > 0 && `(${dependencyGraph.couplings.length})`
3698
+ ]
3699
+ }
3700
+ )
3701
+ ] }),
3702
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-tab-content", children: [
3703
+ mainTab === "issues" && /* @__PURE__ */ jsxRuntime.jsx(
3704
+ IssuesPanel,
3705
+ {
3706
+ issues,
3707
+ onJumpToMutation: handleJumpToMutation
3708
+ }
3709
+ ),
3710
+ mainTab === "timeline" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-timeline-panel", children: /* @__PURE__ */ jsxRuntime.jsx(
3711
+ TimelineScrubber,
3712
+ {
3713
+ mutations: timeline,
3714
+ selectedIndex: playback.currentIndex,
3715
+ onSelect: (index, mutation) => {
3716
+ playback.goTo(index);
3717
+ selectMutation(mutation);
3718
+ },
3719
+ isPlaying: playback.isPlaying,
3720
+ onPlay: playback.play,
3721
+ onPause: playback.pause,
3722
+ onStepForward: playback.stepForward,
3723
+ onStepBackward: playback.stepBackward,
3724
+ playbackSpeed: playback.playbackSpeed,
3725
+ onSpeedChange: playback.setSpeed
3726
+ }
3727
+ ) }),
3728
+ mainTab === "graph" && /* @__PURE__ */ jsxRuntime.jsx(
3729
+ DependencyGraph,
3730
+ {
3731
+ graph: dependencyGraph,
3732
+ onPathSelect: (path) => setSearchQuery(path)
3733
+ }
3734
+ )
3735
+ ] })
3736
+ ] }),
3737
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-right-panel", children: [
3738
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-tabs", children: [
1950
3739
  /* @__PURE__ */ jsxRuntime.jsx(
1951
3740
  "button",
1952
3741
  {
1953
- className: `sd-panel-tab ${activePanel === "diff" ? "active" : ""}`,
1954
- onClick: () => setActivePanel("diff"),
1955
- children: "\u{1F4CA} State Changes"
3742
+ className: `sd2-tab ${detailTab === "tree" ? "active" : ""}`,
3743
+ onClick: () => setDetailTab("tree"),
3744
+ children: "\u{1F333} Tree"
1956
3745
  }
1957
3746
  ),
1958
3747
  /* @__PURE__ */ jsxRuntime.jsx(
1959
3748
  "button",
1960
3749
  {
1961
- className: `sd-panel-tab ${activePanel === "inspector" ? "active" : ""}`,
1962
- onClick: () => setActivePanel("inspector"),
1963
- children: "\u{1F50D} Mutation Details"
3750
+ className: `sd2-tab ${detailTab === "diff" ? "active" : ""}`,
3751
+ onClick: () => setDetailTab("diff"),
3752
+ children: "\u{1F4CA} Diff"
3753
+ }
3754
+ ),
3755
+ /* @__PURE__ */ jsxRuntime.jsx(
3756
+ "button",
3757
+ {
3758
+ className: `sd2-tab ${detailTab === "inspector" ? "active" : ""}`,
3759
+ onClick: () => setDetailTab("inspector"),
3760
+ children: "\u{1F50D} Details"
1964
3761
  }
1965
3762
  )
1966
3763
  ] }),
1967
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd-panel-content", children: [
1968
- activePanel === "diff" && selectedMutation && /* @__PURE__ */ jsxRuntime.jsx(
3764
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sd2-tab-content", children: [
3765
+ detailTab === "tree" && selectedMutation && /* @__PURE__ */ jsxRuntime.jsx(
3766
+ StateTreeViewer,
3767
+ {
3768
+ state: selectedMutation.nextState,
3769
+ issuePaths,
3770
+ changedPaths,
3771
+ removedPaths,
3772
+ searchQuery
3773
+ }
3774
+ ),
3775
+ detailTab === "diff" && selectedMutation && /* @__PURE__ */ jsxRuntime.jsx(
1969
3776
  StateDiffViewer,
1970
3777
  {
1971
3778
  previousState: selectedMutation.previousState,
@@ -1973,344 +3780,339 @@ instrumentReact(React);` })
1973
3780
  diff: selectedMutation.diff
1974
3781
  }
1975
3782
  ),
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" }) })
3783
+ detailTab === "inspector" && /* @__PURE__ */ jsxRuntime.jsx(MutationInspector, { mutation: selectedMutation }),
3784
+ !selectedMutation && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sd2-panel-empty", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Select a mutation to view state details" }) })
1978
3785
  ] })
1979
3786
  ] })
1980
3787
  ] }),
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" })
3788
+ /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "sd2-footer", children: [
3789
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "State Surgeon v2.0" }),
3790
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sd2-sep", children: "\u2022" }),
3791
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\u2190 \u2192 navigate \u2022 Space play/pause \u2022 1-3 switch tabs" })
1985
3792
  ] }),
1986
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
1987
- .surgeon-dashboard {
1988
- min-height: 100vh;
1989
- display: flex;
1990
- flex-direction: column;
1991
- background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%);
1992
- color: #e0e0e0;
1993
- font-family: system-ui, -apple-system, sans-serif;
1994
- }
1995
-
1996
- .sd-header {
1997
- display: flex;
1998
- justify-content: space-between;
1999
- align-items: center;
2000
- padding: 1rem 2rem;
2001
- background: rgba(22, 33, 62, 0.8);
2002
- backdrop-filter: blur(10px);
2003
- border-bottom: 1px solid #333;
2004
- position: sticky;
2005
- top: 0;
2006
- z-index: 100;
2007
- }
2008
-
2009
- .sd-header-left, .sd-header-right {
2010
- display: flex;
2011
- align-items: center;
2012
- gap: 1rem;
2013
- }
3793
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: dashboardStyles })
3794
+ ] });
3795
+ }
3796
+ var dashboardStyles = `
3797
+ .surgeon-dashboard-v2 {
3798
+ min-height: 100vh;
3799
+ display: flex;
3800
+ flex-direction: column;
3801
+ background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%);
3802
+ color: #e0e0e0;
3803
+ font-family: system-ui, -apple-system, sans-serif;
3804
+ }
2014
3805
 
2015
- .sd-logo {
2016
- display: flex;
2017
- align-items: center;
2018
- gap: 0.5rem;
2019
- margin: 0;
2020
- font-size: 1.25rem;
2021
- }
3806
+ /* Header */
3807
+ .sd2-header {
3808
+ display: flex;
3809
+ justify-content: space-between;
3810
+ align-items: center;
3811
+ padding: 0.75rem 1.5rem;
3812
+ background: rgba(22, 33, 62, 0.95);
3813
+ backdrop-filter: blur(10px);
3814
+ border-bottom: 1px solid #333;
3815
+ position: sticky;
3816
+ top: 0;
3817
+ z-index: 100;
3818
+ flex-wrap: wrap;
3819
+ gap: 0.75rem;
3820
+ }
2022
3821
 
2023
- .sd-logo-icon {
2024
- font-size: 1.5rem;
2025
- }
3822
+ .sd2-header-left, .sd2-header-right {
3823
+ display: flex;
3824
+ align-items: center;
3825
+ gap: 1rem;
3826
+ }
2026
3827
 
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
- }
3828
+ .sd2-logo {
3829
+ display: flex;
3830
+ align-items: center;
3831
+ gap: 0.5rem;
3832
+ margin: 0;
3833
+ font-size: 1.125rem;
3834
+ }
2034
3835
 
2035
- .sd-version {
2036
- font-size: 0.7rem;
2037
- padding: 0.2rem 0.4rem;
2038
- background: #333;
2039
- border-radius: 4px;
2040
- color: #888;
2041
- }
3836
+ .sd2-logo-icon { font-size: 1.25rem; }
2042
3837
 
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;
2049
- color: #e0e0e0;
2050
- font-size: 0.875rem;
2051
- cursor: pointer;
2052
- }
3838
+ .sd2-logo-text {
3839
+ background: linear-gradient(135deg, #00d9ff, #a78bfa);
3840
+ -webkit-background-clip: text;
3841
+ -webkit-text-fill-color: transparent;
3842
+ background-clip: text;
3843
+ font-weight: 700;
3844
+ }
2053
3845
 
2054
- .sd-session-select:focus {
2055
- outline: none;
2056
- border-color: #00d9ff;
2057
- }
3846
+ .sd2-version {
3847
+ font-size: 0.65rem;
3848
+ padding: 0.15rem 0.35rem;
3849
+ background: linear-gradient(135deg, #22c55e, #06b6d4);
3850
+ border-radius: 4px;
3851
+ color: #000;
3852
+ font-weight: 600;
3853
+ }
2058
3854
 
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
- }
3855
+ .sd2-issue-badges {
3856
+ display: flex;
3857
+ gap: 0.5rem;
3858
+ }
2067
3859
 
2068
- .sd-refresh-btn {
2069
- padding: 0.5rem;
2070
- border: 1px solid #333;
2071
- border-radius: 8px;
2072
- background: transparent;
2073
- color: #e0e0e0;
2074
- cursor: pointer;
2075
- font-size: 1rem;
2076
- }
3860
+ .sd2-badge {
3861
+ padding: 0.25rem 0.5rem;
3862
+ border-radius: 12px;
3863
+ font-size: 0.75rem;
3864
+ font-weight: 600;
3865
+ }
2077
3866
 
2078
- .sd-refresh-btn:hover {
2079
- background: #16213e;
2080
- }
3867
+ .sd2-badge-critical { background: #ef444430; color: #fca5a5; }
3868
+ .sd2-badge-warning { background: #fbbf2430; color: #fcd34d; }
3869
+ .sd2-badge-info { background: #22c55e30; color: #86efac; }
3870
+
3871
+ .sd2-session-select {
3872
+ padding: 0.5rem 1rem;
3873
+ min-width: 300px;
3874
+ border: 1px solid #333;
3875
+ border-radius: 8px;
3876
+ background: #0f0f23;
3877
+ color: #e0e0e0;
3878
+ font-size: 0.875rem;
3879
+ cursor: pointer;
3880
+ }
2081
3881
 
2082
- .sd-error {
2083
- display: flex;
2084
- align-items: center;
2085
- gap: 0.75rem;
2086
- padding: 1rem 2rem;
2087
- background: rgba(239, 68, 68, 0.1);
2088
- border-bottom: 1px solid #ef4444;
2089
- }
3882
+ .sd2-search {
3883
+ padding: 0.5rem 1rem;
3884
+ width: 200px;
3885
+ border: 1px solid #333;
3886
+ border-radius: 8px;
3887
+ background: #0f0f23;
3888
+ color: #e0e0e0;
3889
+ font-size: 0.875rem;
3890
+ }
2090
3891
 
2091
- .sd-error-icon {
2092
- font-size: 1.25rem;
2093
- }
3892
+ .sd2-auto-refresh {
3893
+ display: flex;
3894
+ align-items: center;
3895
+ gap: 0.375rem;
3896
+ font-size: 0.8rem;
3897
+ color: #888;
3898
+ cursor: pointer;
3899
+ }
2094
3900
 
2095
- .sd-error p {
2096
- margin: 0;
2097
- color: #fca5a5;
2098
- }
3901
+ .sd2-refresh-btn {
3902
+ padding: 0.375rem 0.625rem;
3903
+ border: 1px solid #333;
3904
+ border-radius: 6px;
3905
+ background: transparent;
3906
+ color: #e0e0e0;
3907
+ cursor: pointer;
3908
+ font-size: 1rem;
3909
+ }
2099
3910
 
2100
- .sd-welcome {
2101
- flex: 1;
2102
- display: flex;
2103
- align-items: center;
2104
- justify-content: center;
2105
- padding: 2rem;
2106
- }
3911
+ .sd2-refresh-btn:hover { background: #16213e; }
2107
3912
 
2108
- .sd-welcome-content {
2109
- text-align: center;
2110
- max-width: 600px;
2111
- }
3913
+ .sd2-error {
3914
+ padding: 0.75rem 1.5rem;
3915
+ background: rgba(239, 68, 68, 0.1);
3916
+ border-bottom: 1px solid #ef4444;
3917
+ color: #fca5a5;
3918
+ }
2112
3919
 
2113
- .sd-welcome-icon {
2114
- font-size: 5rem;
2115
- margin-bottom: 1rem;
2116
- animation: float 3s ease-in-out infinite;
2117
- }
3920
+ /* Welcome */
3921
+ .sd2-welcome {
3922
+ flex: 1;
3923
+ display: flex;
3924
+ align-items: center;
3925
+ justify-content: center;
3926
+ padding: 2rem;
3927
+ }
2118
3928
 
2119
- @keyframes float {
2120
- 0%, 100% { transform: translateY(0); }
2121
- 50% { transform: translateY(-10px); }
2122
- }
3929
+ .sd2-welcome-content {
3930
+ text-align: center;
3931
+ max-width: 700px;
3932
+ }
2123
3933
 
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;
2131
- }
3934
+ .sd2-welcome-icon {
3935
+ font-size: 4rem;
3936
+ margin-bottom: 1rem;
3937
+ animation: float 3s ease-in-out infinite;
3938
+ }
2132
3939
 
2133
- .sd-welcome > p {
2134
- margin: 0 0 2rem;
2135
- color: #888;
2136
- }
3940
+ @keyframes float {
3941
+ 0%, 100% { transform: translateY(0); }
3942
+ 50% { transform: translateY(-10px); }
3943
+ }
2137
3944
 
2138
- .sd-welcome-steps {
2139
- text-align: left;
2140
- background: #16213e;
2141
- border-radius: 12px;
2142
- padding: 1.5rem;
2143
- }
3945
+ .sd2-welcome h2 {
3946
+ margin: 0 0 0.5rem;
3947
+ font-size: 1.75rem;
3948
+ background: linear-gradient(135deg, #00d9ff, #a78bfa);
3949
+ -webkit-background-clip: text;
3950
+ -webkit-text-fill-color: transparent;
3951
+ background-clip: text;
3952
+ }
2144
3953
 
2145
- .sd-welcome-steps h3 {
2146
- margin: 0 0 1rem;
2147
- color: #00d9ff;
2148
- font-size: 1rem;
2149
- }
3954
+ .sd2-welcome > div > p {
3955
+ color: #888;
3956
+ margin-bottom: 2rem;
3957
+ }
2150
3958
 
2151
- .sd-welcome-steps ol {
2152
- margin: 0;
2153
- padding-left: 1.25rem;
2154
- }
3959
+ .sd2-features {
3960
+ display: grid;
3961
+ grid-template-columns: repeat(2, 1fr);
3962
+ gap: 1rem;
3963
+ margin-bottom: 2rem;
3964
+ }
2155
3965
 
2156
- .sd-welcome-steps li {
2157
- margin-bottom: 1rem;
2158
- color: #ccc;
2159
- }
3966
+ .sd2-feature {
3967
+ display: flex;
3968
+ flex-direction: column;
3969
+ align-items: center;
3970
+ padding: 1.25rem;
3971
+ background: #16213e;
3972
+ border-radius: 12px;
3973
+ text-align: center;
3974
+ }
2160
3975
 
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
- }
3976
+ .sd2-feature-icon {
3977
+ font-size: 2rem;
3978
+ margin-bottom: 0.5rem;
3979
+ }
2169
3980
 
2170
- .sd-welcome-sessions h3 {
2171
- margin: 0 0 1rem;
2172
- color: #00d9ff;
2173
- }
3981
+ .sd2-feature strong {
3982
+ color: #00d9ff;
3983
+ margin-bottom: 0.25rem;
3984
+ }
2174
3985
 
2175
- .sd-session-list {
2176
- display: grid;
2177
- gap: 0.75rem;
2178
- }
3986
+ .sd2-feature span:last-child {
3987
+ font-size: 0.8rem;
3988
+ color: #888;
3989
+ }
2179
3990
 
2180
- .sd-session-card {
2181
- display: flex;
2182
- justify-content: space-between;
2183
- align-items: center;
2184
- padding: 1rem 1.25rem;
2185
- background: #16213e;
2186
- border: 1px solid #333;
2187
- border-radius: 8px;
2188
- cursor: pointer;
2189
- transition: all 0.2s;
2190
- text-align: left;
2191
- color: inherit;
2192
- }
3991
+ .sd2-session-list h3 {
3992
+ color: #00d9ff;
3993
+ margin-bottom: 0.75rem;
3994
+ }
2193
3995
 
2194
- .sd-session-card:hover {
2195
- border-color: #00d9ff;
2196
- background: #1a2744;
2197
- }
3996
+ .sd2-session-card {
3997
+ display: flex;
3998
+ justify-content: space-between;
3999
+ width: 100%;
4000
+ padding: 0.875rem 1.25rem;
4001
+ margin-bottom: 0.5rem;
4002
+ background: #16213e;
4003
+ border: 1px solid #333;
4004
+ border-radius: 8px;
4005
+ cursor: pointer;
4006
+ color: inherit;
4007
+ transition: all 0.2s;
4008
+ }
2198
4009
 
2199
- .sd-session-app {
2200
- font-weight: 600;
2201
- color: #a78bfa;
2202
- }
4010
+ .sd2-session-card:hover {
4011
+ border-color: #00d9ff;
4012
+ background: #1a2744;
4013
+ }
2203
4014
 
2204
- .sd-session-id {
2205
- font-family: monospace;
2206
- font-size: 0.8rem;
2207
- color: #666;
2208
- }
4015
+ .sd2-session-app { color: #a78bfa; font-weight: 600; }
4016
+ .sd2-session-count { color: #00d9ff; }
2209
4017
 
2210
- .sd-session-count {
2211
- font-size: 0.875rem;
2212
- color: #00d9ff;
2213
- }
4018
+ .sd2-loading {
4019
+ flex: 1;
4020
+ display: flex;
4021
+ flex-direction: column;
4022
+ align-items: center;
4023
+ justify-content: center;
4024
+ gap: 1rem;
4025
+ }
2214
4026
 
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
- }
4027
+ .sd2-spinner {
4028
+ width: 36px;
4029
+ height: 36px;
4030
+ border: 3px solid #333;
4031
+ border-top-color: #00d9ff;
4032
+ border-radius: 50%;
4033
+ animation: spin 1s linear infinite;
4034
+ }
2223
4035
 
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
- }
4036
+ @keyframes spin { to { transform: rotate(360deg); } }
2232
4037
 
2233
- @keyframes spin {
2234
- to { transform: rotate(360deg); }
2235
- }
4038
+ /* Main content */
4039
+ .sd2-main {
4040
+ flex: 1;
4041
+ display: grid;
4042
+ grid-template-columns: 1fr 1fr;
4043
+ gap: 1rem;
4044
+ padding: 1rem;
4045
+ min-height: 0;
4046
+ }
2236
4047
 
2237
- .sd-main {
2238
- flex: 1;
2239
- padding: 1.5rem 2rem;
2240
- display: flex;
2241
- flex-direction: column;
2242
- gap: 1.5rem;
2243
- }
4048
+ @media (max-width: 1200px) {
4049
+ .sd2-main { grid-template-columns: 1fr; }
4050
+ }
2244
4051
 
2245
- .sd-timeline {
2246
- flex-shrink: 0;
2247
- }
4052
+ .sd2-left-panel, .sd2-right-panel {
4053
+ display: flex;
4054
+ flex-direction: column;
4055
+ background: #1a1a2e;
4056
+ border-radius: 12px;
4057
+ overflow: hidden;
4058
+ min-height: 500px;
4059
+ }
2248
4060
 
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
- }
4061
+ .sd2-tabs {
4062
+ display: flex;
4063
+ background: #16213e;
4064
+ border-bottom: 1px solid #333;
4065
+ }
2258
4066
 
2259
- .sd-panel-tabs {
2260
- display: flex;
2261
- background: #16213e;
2262
- border-bottom: 1px solid #333;
2263
- }
4067
+ .sd2-tab {
4068
+ flex: 1;
4069
+ padding: 0.75rem 1rem;
4070
+ border: none;
4071
+ background: transparent;
4072
+ color: #888;
4073
+ cursor: pointer;
4074
+ font-size: 0.875rem;
4075
+ transition: all 0.2s;
4076
+ border-bottom: 2px solid transparent;
4077
+ }
2264
4078
 
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
- }
4079
+ .sd2-tab:hover { color: #ccc; background: #1a2744; }
2274
4080
 
2275
- .sd-panel-tab:hover {
2276
- color: #ccc;
2277
- background: #1a2744;
2278
- }
4081
+ .sd2-tab.active {
4082
+ color: #00d9ff;
4083
+ background: #1a1a2e;
4084
+ border-bottom-color: #00d9ff;
4085
+ }
2279
4086
 
2280
- .sd-panel-tab.active {
2281
- color: #00d9ff;
2282
- background: #1a1a2e;
2283
- border-bottom: 2px solid #00d9ff;
2284
- }
4087
+ .sd2-tab-content {
4088
+ flex: 1;
4089
+ overflow: auto;
4090
+ }
2285
4091
 
2286
- .sd-panel-content {
2287
- flex: 1;
2288
- overflow: auto;
2289
- }
4092
+ .sd2-timeline-panel {
4093
+ padding: 1rem;
4094
+ }
2290
4095
 
2291
- .sd-panel-empty {
2292
- height: 100%;
2293
- display: flex;
2294
- align-items: center;
2295
- justify-content: center;
2296
- color: #666;
2297
- }
4096
+ .sd2-panel-empty {
4097
+ height: 100%;
4098
+ display: flex;
4099
+ align-items: center;
4100
+ justify-content: center;
4101
+ color: #666;
4102
+ }
2298
4103
 
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
- }
4104
+ /* Footer */
4105
+ .sd2-footer {
4106
+ padding: 0.625rem 1.5rem;
4107
+ background: rgba(22, 33, 62, 0.5);
4108
+ border-top: 1px solid #333;
4109
+ font-size: 0.75rem;
4110
+ color: #666;
4111
+ text-align: center;
4112
+ }
2307
4113
 
2308
- .sd-footer-sep {
2309
- margin: 0 0.5rem;
2310
- }
2311
- ` })
2312
- ] });
2313
- }
4114
+ .sd2-sep { margin: 0 0.5rem; }
4115
+ `;
2314
4116
 
2315
4117
  exports.DashboardProvider = DashboardProvider;
2316
4118
  exports.MutationInspector = MutationInspector;