@aiready/visualizer 0.1.22 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-cyan-400:oklch(78.9% .154 211.53);--color-purple-400:oklch(71.4% .203 305.504);--color-white:#fff;--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in:cubic-bezier(.4,0,1,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--blur-sm:8px;--blur-md:12px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.bottom-6{bottom:calc(var(--spacing)*6)}.left-6{left:calc(var(--spacing)*6)}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.ml-4{margin-left:calc(var(--spacing)*4)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.h-1{height:calc(var(--spacing)*1)}.h-2{height:calc(var(--spacing)*2)}.h-3{height:calc(var(--spacing)*3)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-7{height:calc(var(--spacing)*7)}.h-9{height:calc(var(--spacing)*9)}.h-12{height:calc(var(--spacing)*12)}.h-16{height:calc(var(--spacing)*16)}.h-full{height:100%}.h-screen{height:100vh}.w-2{width:calc(var(--spacing)*2)}.w-3{width:calc(var(--spacing)*3)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-7{width:calc(var(--spacing)*7)}.w-10{width:calc(var(--spacing)*10)}.w-12{width:calc(var(--spacing)*12)}.w-16{width:calc(var(--spacing)*16)}.w-80{width:calc(var(--spacing)*80)}.w-auto{width:auto}.w-full{width:100%}.w-px{width:1px}.max-w-md{max-width:var(--container-md)}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-5{gap:calc(var(--spacing)*5)}.gap-6{gap:calc(var(--spacing)*6)}:where(.-space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*-2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*-2)*calc(1 - var(--tw-space-x-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.bg-cyan-400{background-color:var(--color-cyan-400)}.bg-purple-400{background-color:var(--color-purple-400)}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-10{padding-block:calc(var(--spacing)*10)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-400{color:var(--color-amber-400)}.text-amber-500{color:var(--color-amber-500)}.text-cyan-400{color:var(--color-cyan-400)}.text-purple-400{color:var(--color-purple-400)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.uppercase{text-transform:uppercase}.opacity-40{opacity:.4}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}@media (hover:hover){.group-hover\:scale-125:is(:where(.group):hover *){--tw-scale-x:125%;--tw-scale-y:125%;--tw-scale-z:125%;scale:var(--tw-scale-x)var(--tw-scale-y)}.group-hover\:scale-y-150:is(:where(.group):hover *){--tw-scale-y:150%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:bg-white\/5:hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/5:hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.hover\:text-yellow-800:hover{color:var(--color-yellow-800)}}}*{box-sizing:border-box;margin:0;padding:0}html,body,#root{width:100%;height:100%;overflow:hidden}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}#root{flex-direction:column;width:100%;height:100vh;display:flex}@keyframes fadeIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
@@ -4,8 +4,8 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>AIReady Visualizer (Dev)</title>
7
- <script type="module" crossorigin src="/assets/index-R1Ga3mzd.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-cSvqzd3J.css">
7
+ <script type="module" crossorigin src="/assets/index-CmCfrPIo.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-Dizv739M.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/web/src/App.tsx CHANGED
@@ -24,6 +24,7 @@ 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
28
 
28
29
  // Filter state - start with all visible
29
30
  const [visibleSeverities, setVisibleSeverities] = useState<Set<SeverityLevel>>(
@@ -51,7 +52,19 @@ function App() {
51
52
  return;
52
53
  }
53
54
 
54
- setData(transformReportToGraph(reportData));
55
+ // Extract optional visualizer config from report (injected by CLI)
56
+ const visualizerConfig = (reportData as any).visualizerConfig;
57
+ const graphData = transformReportToGraph(reportData, visualizerConfig);
58
+ setData(graphData);
59
+
60
+ // Show warning if graph was truncated
61
+ if (graphData.truncated?.nodes || graphData.truncated?.edges) {
62
+ setTruncatedWarning({
63
+ nodes: graphData.truncated.nodes,
64
+ edges: graphData.truncated.edges,
65
+ });
66
+ }
67
+
55
68
  setLoading(false);
56
69
  };
57
70
 
@@ -148,6 +161,34 @@ function App() {
148
161
  data={filteredData}
149
162
  />
150
163
 
164
+ {/* Truncation warning banner */}
165
+ {truncatedWarning && (truncatedWarning.nodes || truncatedWarning.edges) && (
166
+ <div
167
+ className="px-4 py-3 bg-yellow-50 border-b flex items-center justify-between"
168
+ style={{ borderColor: colors.panelBorder }}
169
+ >
170
+ <div className="flex-1">
171
+ <p className="text-sm text-yellow-800 font-medium">
172
+ ⚠️ Graph visualization truncated at display limits.
173
+ {truncatedWarning.nodes && ' Too many nodes.'}
174
+ {truncatedWarning.edges && ' Too many edges.'}
175
+ </p>
176
+ <p className="text-xs text-yellow-700 mt-1">
177
+ To show more data, increase graph limits in aiready.json:
178
+ <code className="bg-yellow-100 px-1 rounded" style={{ marginLeft: '4px' }}>
179
+ {'"visualizer": { "graph": { "maxNodes": 2000, "maxEdges": 5000 } }'}
180
+ </code>
181
+ </p>
182
+ </div>
183
+ <button
184
+ onClick={() => setTruncatedWarning(null)}
185
+ className="text-yellow-600 hover:text-yellow-800 ml-4 flex-shrink-0"
186
+ >
187
+
188
+ </button>
189
+ </div>
190
+ )}
191
+
151
192
  <div className="flex flex-1 overflow-hidden">
152
193
  <div ref={containerRef} className="flex-1 relative">
153
194
  {filteredData && (
@@ -65,8 +65,8 @@ export const themeConfig: Record<'dark' | 'light', ThemeColors> = {
65
65
  };
66
66
 
67
67
  export const GRAPH_CONFIG = {
68
- maxNodes: 200,
69
- maxEdges: 300,
68
+ maxNodes: 400,
69
+ maxEdges: 600,
70
70
  nodeBaseRadius: 3,
71
71
  collisionRadius: 25,
72
72
  zoomMin: 0.1,
package/web/src/types.ts CHANGED
@@ -20,6 +20,14 @@ export interface GraphEdge {
20
20
  export interface GraphData {
21
21
  nodes: FileNode[];
22
22
  edges: GraphEdge[];
23
+ truncated?: {
24
+ nodes: boolean;
25
+ edges: boolean;
26
+ nodeCount: number;
27
+ edgeCount: number;
28
+ nodeLimit: number;
29
+ edgeLimit: number;
30
+ };
23
31
  }
24
32
 
25
33
  // Filter types
package/web/src/utils.ts CHANGED
@@ -11,7 +11,12 @@ export function getSeverityColor(severity: string | undefined): string {
11
11
  return severityColors.default;
12
12
  }
13
13
 
14
- export function transformReportToGraph(report: ReportData): GraphData {
14
+ export function transformReportToGraph(report: ReportData, runtimeGraphConfig?: { maxNodes?: number; maxEdges?: number }): GraphData {
15
+ // Use runtime config if available (from aiready.json), else use defaults from constants
16
+ const graphConfig = {
17
+ maxNodes: runtimeGraphConfig?.maxNodes ?? GRAPH_CONFIG.maxNodes,
18
+ maxEdges: runtimeGraphConfig?.maxEdges ?? GRAPH_CONFIG.maxEdges,
19
+ };
15
20
  const nodes: FileNode[] = [];
16
21
  const edges: GraphEdge[] = [];
17
22
  const nodeMap = new Map<string, FileNode>();
@@ -33,7 +38,19 @@ export function transformReportToGraph(report: ReportData): GraphData {
33
38
  }
34
39
 
35
40
  for (const ctx of report.context) {
36
- const issues = fileIssues.get(ctx.file);
41
+ // Try direct match first. If not found, try basename fallback so that
42
+ // pattern entries with different path styles still associate their
43
+ // severity with the context file in consumer reports.
44
+ let issues = fileIssues.get(ctx.file);
45
+ if (!issues) {
46
+ const ctxBase = (ctx.file || '').split('/').pop();
47
+ for (const [k, v] of fileIssues.entries()) {
48
+ if ((k || '').split('/').pop() === ctxBase) {
49
+ issues = v;
50
+ break;
51
+ }
52
+ }
53
+ }
37
54
  const severity = issues?.maxSeverity || ctx.severity || 'default';
38
55
  const tokenCost = ctx.tokenCost || 0;
39
56
 
@@ -109,13 +126,45 @@ export function transformReportToGraph(report: ReportData): GraphData {
109
126
  }
110
127
 
111
128
  for (const related of ctx.relatedFiles || []) {
112
- if (nodeMap.has(related) && related !== ctx.file) {
129
+ let relatedId: string | undefined = related;
130
+
131
+ // If exact match exists, use it. Otherwise try resolving relative paths
132
+ // from the source file's directory and try common extensions. As a
133
+ // final fallback, match by basename (endsWith) similar to dependency
134
+ // heuristics.
135
+ if (!nodeMap.has(relatedId)) {
136
+ const sourceDir = ctx.file.substring(0, ctx.file.lastIndexOf('/'));
137
+ const normalizedRel = related.replace(/^\.\/?/, '');
138
+ const tryPaths = [
139
+ `${sourceDir}/${normalizedRel}.ts`,
140
+ `${sourceDir}/${normalizedRel}.tsx`,
141
+ `${sourceDir}/${normalizedRel}/index.ts`,
142
+ `${sourceDir}/${normalizedRel}/index.tsx`,
143
+ `${sourceDir}/${normalizedRel}`,
144
+ ];
145
+ for (const p of tryPaths) {
146
+ if (nodeMap.has(p)) {
147
+ relatedId = p;
148
+ break;
149
+ }
150
+ }
151
+ }
152
+
153
+ // Fallback: loose basename matching
154
+ if (!nodeMap.has(relatedId)) {
155
+ const relBase = (related.split('/').pop() || related).replace(/\.(ts|tsx|js|jsx)$/, '');
156
+ relatedId = [...nodeMap.keys()].find(k =>
157
+ k.endsWith(`/${relBase}.ts`) || k.endsWith(`/${relBase}.tsx`) || k.endsWith(`/${relBase}/index.ts`) || k.endsWith(`/${relBase}/index.tsx`) || k.endsWith(`/${relBase}`)
158
+ );
159
+ }
160
+
161
+ if (relatedId && nodeMap.has(relatedId) && relatedId !== ctx.file) {
113
162
  const exists = edges.some(
114
163
  e =>
115
- (e.source === ctx.file && e.target === related) ||
116
- (e.source === related && e.target === ctx.file)
164
+ (e.source === ctx.file && e.target === relatedId) ||
165
+ (e.source === relatedId && e.target === ctx.file)
117
166
  );
118
- if (!exists) edges.push({ source: ctx.file, target: related, type: 'related' });
167
+ if (!exists) edges.push({ source: ctx.file, target: relatedId, type: 'related' });
119
168
  }
120
169
  }
121
170
  }
@@ -135,8 +184,8 @@ export function transformReportToGraph(report: ReportData): GraphData {
135
184
  const edgePriority: Record<string, number> = {
136
185
  similarity: 1,
137
186
  dependency: 2,
138
- reference: 3,
139
- related: 4,
187
+ reference: 4,
188
+ related: 3,
140
189
  };
141
190
  const sortedEdges = [...edges].sort((a, b) => {
142
191
  const priorityA = edgePriority[a.type] || 99;
@@ -145,8 +194,16 @@ export function transformReportToGraph(report: ReportData): GraphData {
145
194
  });
146
195
 
147
196
  return {
148
- nodes: nodes.slice(0, GRAPH_CONFIG.maxNodes),
149
- edges: sortedEdges.slice(0, GRAPH_CONFIG.maxEdges),
197
+ nodes: nodes.slice(0, graphConfig.maxNodes),
198
+ edges: sortedEdges.slice(0, graphConfig.maxEdges),
199
+ truncated: {
200
+ nodes: nodes.length > graphConfig.maxNodes,
201
+ edges: sortedEdges.length > graphConfig.maxEdges,
202
+ nodeCount: nodes.length,
203
+ edgeCount: sortedEdges.length,
204
+ nodeLimit: graphConfig.maxNodes,
205
+ edgeLimit: graphConfig.maxEdges,
206
+ },
150
207
  };
151
208
  }
152
209
 
@@ -28,6 +28,7 @@ export default defineConfig(async ({ command }) => {
28
28
  name: 'aiready-report-proxy',
29
29
  configureServer(server: any) {
30
30
  const reportPath = process.env.AIREADY_REPORT_PATH;
31
+ const visualizerConfigStr = process.env.AIREADY_VISUALIZER_CONFIG;
31
32
  if (!reportPath) return;
32
33
  server.middlewares.use(async (req: any, res: any, next: any) => {
33
34
  try {
@@ -40,10 +41,22 @@ export default defineConfig(async ({ command }) => {
40
41
  res.end('Report not found');
41
42
  return;
42
43
  }
43
- const data = await fsp.readFile(reportPath, 'utf8');
44
+ let data = await fsp.readFile(reportPath, 'utf8');
45
+ const report = JSON.parse(data);
46
+
47
+ // Inject visualizer config from env if available
48
+ if (visualizerConfigStr) {
49
+ try {
50
+ const visualizerConfig = JSON.parse(visualizerConfigStr);
51
+ report.visualizerConfig = visualizerConfig;
52
+ } catch (e) {
53
+ // Silently ignore parse errors
54
+ }
55
+ }
56
+
44
57
  res.statusCode = 200;
45
58
  res.setHeader('Content-Type', 'application/json; charset=utf-8');
46
- res.end(data);
59
+ res.end(JSON.stringify(report));
47
60
  return;
48
61
  }
49
62
  } catch (e) {