@aiready/visualizer 0.1.37 → 0.1.40

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.
package/web/src/App.tsx CHANGED
@@ -24,12 +24,15 @@ function App() {
24
24
  const [selectedNode, setSelectedNode] = useState<FileNode | null>(null);
25
25
  const [loading, setLoading] = useState(true);
26
26
  const [error, setError] = useState<string | null>(null);
27
- const [truncatedWarning, setTruncatedWarning] = useState<{ nodes: boolean; edges: boolean } | null>(null);
27
+ const [truncatedWarning, setTruncatedWarning] = useState<{
28
+ nodes: boolean;
29
+ edges: boolean;
30
+ } | null>(null);
28
31
 
29
32
  // Filter state - start with all visible
30
- const [visibleSeverities, setVisibleSeverities] = useState<Set<SeverityLevel>>(
31
- new Set(ALL_SEVERITIES)
32
- );
33
+ const [visibleSeverities, setVisibleSeverities] = useState<
34
+ Set<SeverityLevel>
35
+ >(new Set(ALL_SEVERITIES));
33
36
  const [visibleEdgeTypes, setVisibleEdgeTypes] = useState<Set<EdgeType>>(
34
37
  new Set(ALL_EDGE_TYPES)
35
38
  );
@@ -56,7 +59,7 @@ function App() {
56
59
  const visualizerConfig = (reportData as any).visualizerConfig;
57
60
  const graphData = transformReportToGraph(reportData, visualizerConfig);
58
61
  setData(graphData);
59
-
62
+
60
63
  // Show warning if graph was truncated
61
64
  if (graphData.truncated?.nodes || graphData.truncated?.edges) {
62
65
  setTruncatedWarning({
@@ -64,7 +67,7 @@ function App() {
64
67
  edges: graphData.truncated.edges,
65
68
  });
66
69
  }
67
-
70
+
68
71
  setLoading(false);
69
72
  };
70
73
 
@@ -73,7 +76,7 @@ function App() {
73
76
 
74
77
  // Toggle severity visibility
75
78
  const handleToggleSeverity = (severity: SeverityLevel) => {
76
- setVisibleSeverities(prev => {
79
+ setVisibleSeverities((prev) => {
77
80
  const next = new Set(prev);
78
81
  if (next.has(severity)) {
79
82
  next.delete(severity);
@@ -86,7 +89,7 @@ function App() {
86
89
 
87
90
  // Toggle edge type visibility
88
91
  const handleToggleEdgeType = (edgeType: EdgeType) => {
89
- setVisibleEdgeTypes(prev => {
92
+ setVisibleEdgeTypes((prev) => {
90
93
  const next = new Set(prev);
91
94
  if (next.has(edgeType)) {
92
95
  next.delete(edgeType);
@@ -105,15 +108,15 @@ function App() {
105
108
  // Get set of visible node IDs
106
109
  const visibleNodeIds = new Set(
107
110
  data.nodes
108
- .filter(node => {
111
+ .filter((node) => {
109
112
  const severity = (node.severity || 'default') as SeverityLevel;
110
113
  return visibleSeverities.has(severity);
111
114
  })
112
- .map(node => node.id)
115
+ .map((node) => node.id)
113
116
  );
114
117
 
115
118
  // Filter nodes: keep if severity is visible
116
- const filteredNodes = data.nodes.filter(node => {
119
+ const filteredNodes = data.nodes.filter((node) => {
117
120
  const severity = (node.severity || 'default') as SeverityLevel;
118
121
  return visibleSeverities.has(severity);
119
122
  });
@@ -121,15 +124,17 @@ function App() {
121
124
  // Filter edges: keep if:
122
125
  // 1. Edge type is visible AND
123
126
  // 2. Both source and target nodes are visible
124
- const filteredEdges = data.edges.filter(edge => {
127
+ const filteredEdges = data.edges.filter((edge) => {
125
128
  // Check edge type visibility
126
129
  const edgeType = (edge.type || 'default') as EdgeType;
127
130
  if (!visibleEdgeTypes.has(edgeType)) {
128
131
  return false;
129
132
  }
130
133
  // Check that both connected nodes are visible
131
- const sourceId = typeof edge.source === 'string' ? edge.source : edge.source.id;
132
- const targetId = typeof edge.target === 'string' ? edge.target : edge.target.id;
134
+ const sourceId =
135
+ typeof edge.source === 'string' ? edge.source : edge.source.id;
136
+ const targetId =
137
+ typeof edge.target === 'string' ? edge.target : edge.target.id;
133
138
  return visibleNodeIds.has(sourceId) && visibleNodeIds.has(targetId);
134
139
  });
135
140
 
@@ -163,32 +168,38 @@ function App() {
163
168
  />
164
169
 
165
170
  {/* Truncation warning banner */}
166
- {truncatedWarning && (truncatedWarning.nodes || truncatedWarning.edges) && (
167
- <div
168
- className="px-4 py-3 bg-yellow-50 border-b flex items-center justify-between"
169
- style={{ borderColor: colors.panelBorder }}
170
- >
171
- <div className="flex-1">
172
- <p className="text-sm text-yellow-800 font-medium">
173
- ⚠️ Graph visualization truncated at display limits.
174
- {truncatedWarning.nodes && ' Too many nodes.'}
175
- {truncatedWarning.edges && ' Too many edges.'}
176
- </p>
177
- <p className="text-xs text-yellow-700 mt-1">
178
- To show more data, increase graph limits in aiready.json:
179
- <code className="bg-yellow-100 px-1 rounded" style={{ marginLeft: '4px' }}>
180
- {'"visualizer": { "graph": { "maxNodes": 2000, "maxEdges": 5000 } }'}
181
- </code>
182
- </p>
183
- </div>
184
- <button
185
- onClick={() => setTruncatedWarning(null)}
186
- className="text-yellow-600 hover:text-yellow-800 ml-4 flex-shrink-0"
171
+ {truncatedWarning &&
172
+ (truncatedWarning.nodes || truncatedWarning.edges) && (
173
+ <div
174
+ className="px-4 py-3 bg-yellow-50 border-b flex items-center justify-between"
175
+ style={{ borderColor: colors.panelBorder }}
187
176
  >
188
-
189
- </button>
190
- </div>
191
- )}
177
+ <div className="flex-1">
178
+ <p className="text-sm text-yellow-800 font-medium">
179
+ ⚠️ Graph visualization truncated at display limits.
180
+ {truncatedWarning.nodes && ' Too many nodes.'}
181
+ {truncatedWarning.edges && ' Too many edges.'}
182
+ </p>
183
+ <p className="text-xs text-yellow-700 mt-1">
184
+ To show more data, increase graph limits in aiready.json:
185
+ <code
186
+ className="bg-yellow-100 px-1 rounded"
187
+ style={{ marginLeft: '4px' }}
188
+ >
189
+ {
190
+ '"visualizer": { "graph": { "maxNodes": 2000, "maxEdges": 5000 } }'
191
+ }
192
+ </code>
193
+ </p>
194
+ </div>
195
+ <button
196
+ onClick={() => setTruncatedWarning(null)}
197
+ className="text-yellow-600 hover:text-yellow-800 ml-4 flex-shrink-0"
198
+ >
199
+
200
+ </button>
201
+ </div>
202
+ )}
192
203
 
193
204
  <div className="flex flex-1 overflow-hidden">
194
205
  <div ref={containerRef} className="flex-1 relative">
@@ -204,19 +215,22 @@ function App() {
204
215
  </div>
205
216
 
206
217
  {/* Right panel: Legend OR NodeDetails */}
207
- <div
208
- className="w-80 border-l flex flex-col h-full"
209
- style={{ backgroundColor: colors.panel, borderColor: colors.panelBorder }}
218
+ <div
219
+ className="w-80 border-l flex flex-col h-full"
220
+ style={{
221
+ backgroundColor: colors.panel,
222
+ borderColor: colors.panelBorder,
223
+ }}
210
224
  >
211
225
  {selectedNode ? (
212
- <NodeDetails
213
- colors={colors}
214
- selectedNode={selectedNode}
215
- onClose={() => setSelectedNode(null)}
226
+ <NodeDetails
227
+ colors={colors}
228
+ selectedNode={selectedNode}
229
+ onClose={() => setSelectedNode(null)}
216
230
  />
217
231
  ) : (
218
232
  <div className="flex-1 overflow-y-auto">
219
- <LegendPanel
233
+ <LegendPanel
220
234
  colors={colors}
221
235
  visibleSeverities={visibleSeverities}
222
236
  visibleEdgeTypes={visibleEdgeTypes}
@@ -7,31 +7,45 @@ interface ErrorDisplayProps {
7
7
 
8
8
  export function ErrorDisplay({ colors, error }: ErrorDisplayProps) {
9
9
  return (
10
- <div className="flex h-screen items-center justify-center" style={{ backgroundColor: colors.bg, color: colors.text }}>
10
+ <div
11
+ className="flex h-screen items-center justify-center"
12
+ style={{ backgroundColor: colors.bg, color: colors.text }}
13
+ >
11
14
  <div className="text-center max-w-md p-6">
12
- <svg
13
- className="w-16 h-16 mx-auto text-amber-500 mb-4"
14
- fill="none"
15
- viewBox="0 0 24 24"
15
+ <svg
16
+ className="w-16 h-16 mx-auto text-amber-500 mb-4"
17
+ fill="none"
18
+ viewBox="0 0 24 24"
16
19
  stroke="currentColor"
17
20
  >
18
- <path
19
- strokeLinecap="round"
20
- strokeLinejoin="round"
21
- strokeWidth={2}
22
- d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
21
+ <path
22
+ strokeLinecap="round"
23
+ strokeLinejoin="round"
24
+ strokeWidth={2}
25
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
23
26
  />
24
27
  </svg>
25
- <h2 className="text-xl font-semibold mb-2 text-amber-400">No Scan Data Found</h2>
26
- <p className="mb-4" style={{ color: colors.textMuted }}>{error}</p>
27
- <div
28
- className="text-left p-4 rounded-lg text-sm font-mono"
28
+ <h2 className="text-xl font-semibold mb-2 text-amber-400">
29
+ No Scan Data Found
30
+ </h2>
31
+ <p className="mb-4" style={{ color: colors.textMuted }}>
32
+ {error}
33
+ </p>
34
+ <div
35
+ className="text-left p-4 rounded-lg text-sm font-mono"
29
36
  style={{ backgroundColor: colors.cardBg }}
30
37
  >
31
38
  <p className="text-cyan-400"># Step 1: Run aiready scan</p>
32
- <p className="mb-2" style={{ color: colors.textMuted }}>aiready scan . --output json</p>
33
- <p className="text-cyan-400"># Step 2: Copy latest report to visualizer</p>
34
- <p style={{ color: colors.textMuted }}>cp .aiready/aiready-report-*.json packages/visualizer/web/public/report-data.json</p>
39
+ <p className="mb-2" style={{ color: colors.textMuted }}>
40
+ aiready scan . --output json
41
+ </p>
42
+ <p className="text-cyan-400">
43
+ # Step 2: Copy latest report to visualizer
44
+ </p>
45
+ <p style={{ color: colors.textMuted }}>
46
+ cp .aiready/aiready-report-*.json
47
+ packages/visualizer/web/public/report-data.json
48
+ </p>
35
49
  </div>
36
50
  </div>
37
51
  </div>
@@ -2,7 +2,12 @@ import { useEffect, useRef } from 'react';
2
2
  import * as d3 from 'd3';
3
3
  import { FileNode, GraphData, ThemeColors, EffectiveTheme } from '../types';
4
4
  import { severityColors, edgeColors, GRAPH_CONFIG } from '../constants';
5
- import { getEdgeDistance, getEdgeStrength, getEdgeOpacity, getEdgeStrokeWidth } from '../utils';
5
+ import {
6
+ getEdgeDistance,
7
+ getEdgeStrength,
8
+ getEdgeOpacity,
9
+ getEdgeStrokeWidth,
10
+ } from '../utils';
6
11
 
7
12
  interface GraphCanvasProps {
8
13
  data: GraphData;
@@ -26,7 +31,7 @@ export function GraphCanvas({
26
31
  if (!data || !svgRef.current || !data.nodes.length) return;
27
32
 
28
33
  const svg = d3.select(svgRef.current);
29
-
34
+
30
35
  // Get actual SVG dimensions from the DOM element
31
36
  const svgRect = svgRef.current.getBoundingClientRect();
32
37
  const width = svgRect.width;
@@ -37,7 +42,8 @@ export function GraphCanvas({
37
42
  const svgGroup = svg.append('g');
38
43
 
39
44
  // Setup zoom
40
- const zoom = d3.zoom<SVGSVGElement, unknown>()
45
+ const zoom = d3
46
+ .zoom<SVGSVGElement, unknown>()
41
47
  .scaleExtent([GRAPH_CONFIG.zoomMin, GRAPH_CONFIG.zoomMax])
42
48
  .on('zoom', (event) => {
43
49
  svgGroup.attr('transform', event.transform);
@@ -57,22 +63,40 @@ export function GraphCanvas({
57
63
  y: height / 2 + Math.sin(angle) * radius,
58
64
  };
59
65
  });
60
- const links = data.edges.map(d => ({ ...d }));
66
+ const links = data.edges.map((d) => ({ ...d }));
61
67
 
62
68
  // Create force simulation
63
- const simulation = d3.forceSimulation(nodes as d3.SimulationNodeDatum[])
69
+ const simulation = d3
70
+ .forceSimulation(nodes as d3.SimulationNodeDatum[])
64
71
  .force(
65
72
  'link',
66
- d3.forceLink(links)
73
+ d3
74
+ .forceLink(links)
67
75
  .id((d: unknown) => (d as { id: string }).id)
68
- .distance((d: unknown) => getEdgeDistance((d as { type: string }).type))
69
- .strength((d: unknown) => getEdgeStrength((d as { type: string }).type))
76
+ .distance((d: unknown) =>
77
+ getEdgeDistance((d as { type: string }).type)
78
+ )
79
+ .strength((d: unknown) =>
80
+ getEdgeStrength((d as { type: string }).type)
81
+ )
82
+ )
83
+ .force(
84
+ 'charge',
85
+ d3.forceManyBody().strength(GRAPH_CONFIG.simulation.chargeStrength)
70
86
  )
71
- .force('charge', d3.forceManyBody().strength(GRAPH_CONFIG.simulation.chargeStrength))
72
87
  .force('center', d3.forceCenter(width / 2, height / 2))
73
- .force('collision', d3.forceCollide().radius(GRAPH_CONFIG.collisionRadius))
74
- .force('x', d3.forceX(width / 2).strength(GRAPH_CONFIG.simulation.centerStrength))
75
- .force('y', d3.forceY(height / 2).strength(GRAPH_CONFIG.simulation.centerStrength));
88
+ .force(
89
+ 'collision',
90
+ d3.forceCollide().radius(GRAPH_CONFIG.collisionRadius)
91
+ )
92
+ .force(
93
+ 'x',
94
+ d3.forceX(width / 2).strength(GRAPH_CONFIG.simulation.centerStrength)
95
+ )
96
+ .force(
97
+ 'y',
98
+ d3.forceY(height / 2).strength(GRAPH_CONFIG.simulation.centerStrength)
99
+ );
76
100
 
77
101
  // Create link group
78
102
  const linkGroup = svgGroup.append('g').attr('class', 'links');
@@ -81,9 +105,17 @@ export function GraphCanvas({
81
105
  .data(links)
82
106
  .enter()
83
107
  .append('line')
84
- .attr('stroke', (d: unknown) => edgeColors[(d as { type: string }).type] || edgeColors.default)
85
- .attr('stroke-opacity', (d: unknown) => getEdgeOpacity((d as { type: string }).type))
86
- .attr('stroke-width', (d: unknown) => getEdgeStrokeWidth((d as { type: string }).type));
108
+ .attr(
109
+ 'stroke',
110
+ (d: unknown) =>
111
+ edgeColors[(d as { type: string }).type] || edgeColors.default
112
+ )
113
+ .attr('stroke-opacity', (d: unknown) =>
114
+ getEdgeOpacity((d as { type: string }).type)
115
+ )
116
+ .attr('stroke-width', (d: unknown) =>
117
+ getEdgeStrokeWidth((d as { type: string }).type)
118
+ );
87
119
 
88
120
  // Create node group
89
121
  const nodeGroup = svgGroup.append('g').attr('class', 'nodes');
@@ -95,7 +127,8 @@ export function GraphCanvas({
95
127
  .append('g')
96
128
  .attr('cursor', 'pointer')
97
129
  .call(
98
- d3.drag<SVGGElement, any>()
130
+ d3
131
+ .drag<SVGGElement, any>()
99
132
  .on('start', dragstarted)
100
133
  .on('drag', dragged)
101
134
  .on('end', dragended) as any
@@ -104,17 +137,32 @@ export function GraphCanvas({
104
137
  // Add circles to nodes
105
138
  node
106
139
  .append('circle')
107
- .attr('r', (d: unknown) => Math.sqrt((d as { value: number }).value || 10) + GRAPH_CONFIG.nodeBaseRadius)
108
- .attr('fill', (d: unknown) => (d as { color: string }).color || severityColors.default)
140
+ .attr(
141
+ 'r',
142
+ (d: unknown) =>
143
+ Math.sqrt((d as { value: number }).value || 10) +
144
+ GRAPH_CONFIG.nodeBaseRadius
145
+ )
146
+ .attr(
147
+ 'fill',
148
+ (d: unknown) => (d as { color: string }).color || severityColors.default
149
+ )
109
150
  .attr('stroke', effectiveTheme === 'dark' ? '#fff' : '#000')
110
151
  .attr('stroke-width', 1.5);
111
152
 
112
153
  // Add labels to nodes
113
154
  node
114
155
  .append('text')
115
- .text((d: unknown) => ((d as { label: string }).label.split('/').pop() || (d as { label: string }).label))
156
+ .text(
157
+ (d: unknown) =>
158
+ (d as { label: string }).label.split('/').pop() ||
159
+ (d as { label: string }).label
160
+ )
116
161
  .attr('x', 0)
117
- .attr('y', (d: unknown) => Math.sqrt((d as { value: number }).value || 10) + 12)
162
+ .attr(
163
+ 'y',
164
+ (d: unknown) => Math.sqrt((d as { value: number }).value || 10) + 12
165
+ )
118
166
  .attr('text-anchor', 'middle')
119
167
  .attr('fill', effectiveTheme === 'dark' ? '#e2e8f0' : '#1e293b')
120
168
  .attr('font-size', '9px')
@@ -139,12 +187,17 @@ export function GraphCanvas({
139
187
  .attr('x2', (d: unknown) => (d as { target: { x: number } }).target.x)
140
188
  .attr('y2', (d: unknown) => (d as { target: { y: number } }).target.y);
141
189
 
142
- node.attr('transform', (d: unknown) => `translate(${(d as { x: number }).x},${(d as { y: number }).y})`);
190
+ node.attr(
191
+ 'transform',
192
+ (d: unknown) =>
193
+ `translate(${(d as { x: number }).x},${(d as { y: number }).y})`
194
+ );
143
195
  });
144
196
 
145
197
  // Drag functions
146
198
  function dragstarted(event: unknown, d: unknown) {
147
- if (!(event as { active: boolean }).active) simulation.alphaTarget(0.3).restart();
199
+ if (!(event as { active: boolean }).active)
200
+ simulation.alphaTarget(0.3).restart();
148
201
  (d as { fx: number | null }).fx = (d as { x: number }).x;
149
202
  (d as { fy: number | null }).fy = (d as { y: number }).y;
150
203
  }
@@ -178,7 +231,14 @@ export function GraphCanvas({
178
231
  ref={svgRef}
179
232
  width="100%"
180
233
  height="100%"
181
- style={{ display: 'block', backgroundColor: 'transparent', zIndex: 10, position: 'absolute', top: 0, left: 0 }}
234
+ style={{
235
+ display: 'block',
236
+ backgroundColor: 'transparent',
237
+ zIndex: 10,
238
+ position: 'absolute',
239
+ top: 0,
240
+ left: 0,
241
+ }}
182
242
  />
183
243
  <div className="absolute bottom-6 left-6 z-20">
184
244
  <div
@@ -189,7 +249,13 @@ export function GraphCanvas({
189
249
  color: colors.textMuted,
190
250
  }}
191
251
  >
192
- <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor">
252
+ <svg
253
+ width="16"
254
+ height="16"
255
+ fill="none"
256
+ viewBox="0 0 24 24"
257
+ stroke="currentColor"
258
+ >
193
259
  <path
194
260
  strokeLinecap="round"
195
261
  strokeLinejoin="round"