diff --git a/src/visualizer/public/index.html b/src/visualizer/public/index.html
index e07f820..bc736b7 100644
--- a/src/visualizer/public/index.html
+++ b/src/visualizer/public/index.html
@@ -42,7 +42,7 @@
--font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', Consolas, monospace;
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
--sidebar-width: 320px;
- --panel-width: 420px;
+ --panel-width: 460px;
--header-height: 52px;
--radius: 8px;
--radius-sm: 6px;
@@ -991,8 +991,8 @@
'height': 'label',
'padding': '12px',
'shape': 'data(shape)',
- 'text-wrap': 'none',
- 'text-max-width': '160px',
+ 'text-wrap': 'wrap',
+ 'text-max-width': '180px',
'transition-property': 'background-opacity, border-color, border-opacity, opacity, text-opacity',
'transition-duration': '0.2s',
}
@@ -1130,16 +1130,24 @@
// ====================================================================
// Graph Operations
// ====================================================================
+ const kindLabels = {
+ 'function': 'fn', 'method': 'method', 'class': 'class', 'interface': 'iface',
+ 'component': 'comp', 'route': 'route', 'enum': 'enum', 'type_alias': 'type',
+ 'struct': 'struct', 'trait': 'trait', 'variable': 'var', 'constant': 'const',
+ 'property': 'prop', 'field': 'field', 'file': 'file', 'module': 'mod',
+ };
+
function addNodeToGraph(node) {
if (cy.getElementById(node.id).length > 0) return;
const color = kindColors[node.kind] || '#8b949e';
const shape = kindShapes[node.kind] || 'round-rectangle';
+ const kindLabel = kindLabels[node.kind] || node.kind;
cy.add({
group: 'nodes',
data: {
id: node.id,
nodeId: node.id,
- label: node.name,
+ label: `${node.name}\n${kindLabel}`,
color: color,
shape: shape,
kind: node.kind,
@@ -1541,6 +1549,16 @@
let html = '';
+ // Quick actions
+ html += `
+
+
+
+
+
+
+
`;
+
// Meta info
html += `
Info
diff --git a/src/visualizer/server.ts b/src/visualizer/server.ts
index 033a45c..3e4582f 100644
--- a/src/visualizer/server.ts
+++ b/src/visualizer/server.ts
@@ -397,11 +397,55 @@ ${symbolIndex}`;
addEdge(item.edge);
}
- // Seeds with no relevant neighbors stay isolated — user can
- // right-click → expand to explore manually. No noise added.
+ // For seeds with no relevant neighbors, add top-3 callees
+ // so they're not completely floating
+ if (relevant.length === 0 && irrelevant.length > 0) {
+ for (const item of irrelevant.slice(0, 3)) {
+ if (nodeMap.size >= maxNodes) break;
+ // Only add if it connects to another node already in the graph
+ if (nodeMap.has(item.node.id)) {
+ addEdge(item.edge);
+ }
+ }
+ }
}
- // Step 3: Cross-connection pass — find edges between all result nodes
+ // Step 3: Bridge pass — find shared callees between isolated seeds
+ // If two seeds both call the same function, add it as a bridge node
+ const isolatedSeeds = Array.from(seedMap.keys()).filter(id => {
+ return !edgeList.some(e => e.source === id || e.target === id);
+ });
+
+ if (isolatedSeeds.length > 1) {
+ // Collect callees for each isolated seed
+ const seedCallees = new Map
();
+ for (const seedId of isolatedSeeds) {
+ const callees = this.cg.getCallees(seedId, 1);
+ for (const item of callees) {
+ const existing = seedCallees.get(item.node.id);
+ if (existing) {
+ existing.seeds.push(seedId);
+ } else {
+ seedCallees.set(item.node.id, { node: item.node, seeds: [seedId] });
+ }
+ }
+ }
+ // Add bridge nodes that connect 2+ isolated seeds
+ for (const [bridgeId, { node: bridgeNode, seeds }] of seedCallees) {
+ if (seeds.length >= 2 && nodeMap.size < maxNodes) {
+ nodeMap.set(bridgeId, bridgeNode);
+ // Add edges from each seed to the bridge
+ for (const seedId of seeds) {
+ const callees = this.cg.getCallees(seedId, 1);
+ for (const item of callees) {
+ if (item.node.id === bridgeId) addEdge(item.edge);
+ }
+ }
+ }
+ }
+ }
+
+ // Step 4: Cross-connection pass — find edges between all result nodes
for (const [nodeId] of nodeMap) {
const callers = this.cg.getCallers(nodeId, 1);
const callees = this.cg.getCallees(nodeId, 1);
@@ -412,7 +456,7 @@ ${symbolIndex}`;
}
}
- // Step 4: Filter edges and remove isolated non-root nodes
+ // Step 5: Filter edges and remove isolated non-root nodes
const finalEdges = edgeList.filter(e => nodeMap.has(e.source) && nodeMap.has(e.target));
const connectedIds = new Set();