mcp-vector-search 0.12.2__py3-none-any.whl → 0.12.3__py3-none-any.whl

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.

Potentially problematic release.


This version of mcp-vector-search might be problematic. Click here for more details.

@@ -142,15 +142,15 @@
142
142
  }
143
143
 
144
144
  .link {
145
- stroke: #30363d;
146
- stroke-opacity: 0.6;
147
- stroke-width: 1.5px;
145
+ stroke: #58a6ff;
146
+ stroke-opacity: 0.8;
147
+ stroke-width: 3px;
148
148
  }
149
149
 
150
150
  .link.dependency {
151
151
  stroke: #d29922;
152
152
  stroke-opacity: 0.8;
153
- stroke-width: 2px;
153
+ stroke-width: 3px;
154
154
  stroke-dasharray: 5,5;
155
155
  }
156
156
 
@@ -314,6 +314,28 @@
314
314
  stroke-width: 3px;
315
315
  filter: drop-shadow(0 0 8px #f0e68c);
316
316
  }
317
+
318
+ .control-button {
319
+ padding: 8px 12px;
320
+ background: #21262d;
321
+ border: 1px solid #30363d;
322
+ border-radius: 6px;
323
+ color: #c9d1d9;
324
+ font-size: 12px;
325
+ cursor: pointer;
326
+ transition: background-color 0.2s;
327
+ margin-top: 8px;
328
+ width: 100%;
329
+ }
330
+
331
+ .control-button:hover {
332
+ background: #30363d;
333
+ }
334
+
335
+ .control-button:disabled {
336
+ opacity: 0.5;
337
+ cursor: not-allowed;
338
+ }
317
339
  </style>
318
340
  </head>
319
341
  <body>
@@ -324,6 +346,10 @@
324
346
  <label>⏳ Loading graph data...</label>
325
347
  </div>
326
348
 
349
+ <div class="control-group">
350
+ <button id="focus-button" class="control-button" disabled>📍 Focus on Selected</button>
351
+ </div>
352
+
327
353
  <h3>Legend</h3>
328
354
  <div class="legend">
329
355
  <div class="legend-item">
@@ -387,19 +413,46 @@
387
413
 
388
414
  const svg = d3.select("#graph")
389
415
  .attr("width", width)
390
- .attr("height", height)
391
- .call(d3.zoom().on("zoom", (event) => {
392
- g.attr("transform", event.transform);
393
- }));
416
+ .attr("height", height);
394
417
 
395
418
  const g = svg.append("g");
396
419
  const tooltip = d3.select("#tooltip");
420
+
421
+ const zoom = d3.zoom().on("zoom", (event) => {
422
+ g.attr("transform", event.transform);
423
+ });
424
+ svg.call(zoom);
425
+
397
426
  let simulation;
398
427
  let allNodes = [];
399
428
  let allLinks = [];
400
429
  let visibleNodes = new Set();
401
430
  let collapsedNodes = new Set();
402
431
  let highlightedNode = null;
432
+ let currentNode = null; // Track currently selected node
433
+
434
+ // Add arrow markers for different relationship types
435
+ const defs = svg.append("defs");
436
+
437
+ const markerTypes = [
438
+ {id: "arrow-directory", color: "#58a6ff"}, // Blue for folder→file
439
+ {id: "arrow-file", color: "#8b949e"}, // Gray for file→section
440
+ {id: "arrow-ast", color: "#a371f7"} // Purple for section→AST
441
+ ];
442
+
443
+ markerTypes.forEach(type => {
444
+ defs.append("marker")
445
+ .attr("id", type.id)
446
+ .attr("viewBox", "0 -5 10 10")
447
+ .attr("refX", 20)
448
+ .attr("refY", 0)
449
+ .attr("markerWidth", 6)
450
+ .attr("markerHeight", 6)
451
+ .attr("orient", "auto")
452
+ .append("path")
453
+ .attr("d", "M0,-5L10,0L0,5")
454
+ .attr("fill", type.color);
455
+ });
403
456
 
404
457
  function visualizeGraph(data) {
405
458
  g.selectAll("*").remove();
@@ -413,36 +466,36 @@
413
466
  // In monorepos, subproject nodes are roots
414
467
  rootNodes = allNodes.filter(n => n.type === 'subproject');
415
468
  } else {
416
- // Regular projects: Find root nodes by graph structure
417
- const dirNodes = allNodes.filter(n => n.type === 'directory');
418
- const fileNodes = allNodes.filter(n => n.type === 'file');
419
-
420
- // Find minimum depth for directories and files (for fallback)
421
- let minDirDepth = Infinity;
422
- let minFileDepth = Infinity;
423
-
424
- dirNodes.forEach(d => {
425
- if (d.depth < minDirDepth) minDirDepth = d.depth;
426
- });
427
-
428
- fileNodes.forEach(f => {
429
- if (f.depth < minFileDepth) minFileDepth = f.depth;
430
- });
431
-
432
- // CORRECT: Find root nodes by checking graph structure
469
+ // Regular projects: Find root nodes by checking graph structure
433
470
  // Root nodes are nodes with no incoming links (no parent)
434
471
  const targetIds = new Set(allLinks.map(link => link.target));
435
- rootNodes = allNodes.filter(node => !targetIds.has(node.id));
472
+ const allRootNodes = allNodes.filter(node => !targetIds.has(node.id));
473
+
474
+ console.log(`All root nodes found via links: ${allRootNodes.length}`, allRootNodes.map(n => `${n.name} (${n.type})`));
475
+
476
+ // Filter to show only directories and files at root
477
+ // Exclude orphaned files (files with no directory parent) from initial view
478
+ // These are typically config/doc files at project root
479
+ rootNodes = allRootNodes.filter(n => {
480
+ // Include all directories
481
+ if (n.type === 'directory') return true;
482
+
483
+ // Include files only if they're in a directory structure
484
+ // Exclude standalone files with no dir_path (project root files)
485
+ if (n.type === 'file' && n.dir_path === null) {
486
+ console.log(`Filtering out orphaned root file: ${n.name}`);
487
+ return false;
488
+ }
489
+
490
+ return false; // Exclude all other types (classes, functions, etc.)
491
+ });
436
492
 
437
- console.log(`Root nodes found via links: ${rootNodes.length}`, rootNodes.map(n => n.name));
493
+ console.log(`Filtered root nodes (directories only): ${rootNodes.length}`, rootNodes.map(n => n.name));
438
494
 
439
495
  // Fallback only if we got 0 root nodes (shouldn't happen with correct data)
440
496
  if (rootNodes.length === 0) {
441
- console.warn('No root nodes found via links, falling back to depth-based detection');
442
- rootNodes = [
443
- ...dirNodes.filter(n => n.depth === minDirDepth),
444
- ...fileNodes.filter(n => n.depth === minFileDepth)
445
- ];
497
+ console.warn('No root nodes after filtering, using all root nodes');
498
+ rootNodes = allRootNodes;
446
499
  }
447
500
  }
448
501
 
@@ -468,10 +521,11 @@
468
521
  const visibleNodesList = allNodes.filter(n => visibleNodes.has(n.id));
469
522
  console.log(`Rendering ${visibleNodesList.length} visible nodes`);
470
523
 
471
- const visibleLinks = allLinks.filter(l =>
472
- visibleNodes.has(l.source.id || l.source) &&
473
- visibleNodes.has(l.target.id || l.target)
474
- );
524
+ const visibleLinks = allLinks.filter(l => {
525
+ const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
526
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
527
+ return visibleNodes.has(sourceId) && visibleNodes.has(targetId);
528
+ });
475
529
  console.log(`Rendering ${visibleLinks.length} visible links`);
476
530
 
477
531
  simulation = d3.forceSimulation(visibleNodesList)
@@ -486,7 +540,33 @@
486
540
  .selectAll("line")
487
541
  .data(visibleLinks)
488
542
  .join("line")
489
- .attr("class", d => d.type === "dependency" ? "link dependency" : "link");
543
+ .attr("class", d => d.type === "dependency" ? "link dependency" : "link")
544
+ .attr("marker-end", d => {
545
+ const sourceId = typeof d.source === 'object' ? d.source.id : d.source;
546
+ const targetId = typeof d.target === 'object' ? d.target.id : d.target;
547
+ const source = allNodes.find(n => n.id === sourceId);
548
+ const target = allNodes.find(n => n.id === targetId);
549
+
550
+ if (source && target) {
551
+ if (source.type === 'directory' && target.type === 'file') {
552
+ return "url(#arrow-directory)";
553
+ } else if (source.type === 'file' && ['class', 'function', 'method', 'imports'].includes(target.type)) {
554
+ return "url(#arrow-file)";
555
+ } else {
556
+ return "url(#arrow-ast)";
557
+ }
558
+ }
559
+ return "url(#arrow-ast)";
560
+ })
561
+ .attr("stroke", d => {
562
+ const sourceId = typeof d.source === 'object' ? d.source.id : d.source;
563
+ const source = allNodes.find(n => n.id === sourceId);
564
+ if (source) {
565
+ if (source.type === 'directory') return "#58a6ff";
566
+ if (source.type === 'file') return "#8b949e";
567
+ }
568
+ return "#a371f7";
569
+ });
490
570
 
491
571
  const node = g.append("g")
492
572
  .selectAll("g")
@@ -518,7 +598,8 @@
518
598
  })
519
599
  .attr("stroke", d => hasChildren(d) ? "#ffffff" : "none")
520
600
  .attr("stroke-width", d => hasChildren(d) ? 3 : 0)
521
- .style("fill", d => d.color || null); // Use custom color if available
601
+ .style("fill", d => d.color || null) // Use custom color if available
602
+ .style("cursor", "pointer"); // Ensure cursor shows pointer on all nodes
522
603
 
523
604
  // Add rectangles for document nodes
524
605
  node.filter(d => isDocNode(d))
@@ -543,17 +624,31 @@
543
624
  .attr("ry", 2)
544
625
  .attr("stroke", d => hasChildren(d) ? "#ffffff" : "none")
545
626
  .attr("stroke-width", d => hasChildren(d) ? 3 : 0)
546
- .style("fill", d => d.color || null);
627
+ .style("fill", d => d.color || null)
628
+ .style("cursor", "pointer"); // Ensure cursor shows pointer on all nodes
547
629
 
548
- // Add expand/collapse indicator
630
+ // Add expand/collapse indicator - circle background below node name
631
+ node.filter(d => hasChildren(d))
632
+ .append("circle")
633
+ .attr("class", "expand-circle")
634
+ .attr("r", 14)
635
+ .attr("cy", 35) // Position below the text
636
+ .attr("fill", "#2c2c2c")
637
+ .attr("stroke", "#ffeb3b")
638
+ .attr("stroke-width", 2.5)
639
+ .style("cursor", "pointer")
640
+ .style("pointer-events", "all");
641
+
642
+ // Add +/- text inside the circle
549
643
  node.filter(d => hasChildren(d))
550
644
  .append("text")
551
645
  .attr("class", "expand-indicator")
646
+ .attr("y", 35) // Same position as circle
647
+ .attr("dy", 6) // Center vertically within circle
552
648
  .attr("text-anchor", "middle")
553
- .attr("dy", 5)
554
- .style("font-size", "16px")
649
+ .style("font-size", "22px")
555
650
  .style("font-weight", "bold")
556
- .style("fill", "#ffffff")
651
+ .style("fill", "#ffeb3b")
557
652
  .style("pointer-events", "none")
558
653
  .text(d => collapsedNodes.has(d.id) ? "+" : "−");
559
654
 
@@ -575,7 +670,7 @@
575
670
  return '•';
576
671
  });
577
672
 
578
- // Add labels (show actual import statement for import nodes)
673
+ // Add labels (show actual import statement for import nodes, better names for text chunks)
579
674
  node.append("text")
580
675
  .text(d => {
581
676
  // Import nodes have type === 'imports'
@@ -588,6 +683,45 @@
588
683
  }
589
684
  return d.name; // Fallback to name if no content
590
685
  }
686
+
687
+ // Text chunk nodes - show content preview instead of just line numbers
688
+ if (d.type === 'text' && d.content) {
689
+ // Extract first line of content
690
+ const firstLine = d.content.split('\n')[0].trim();
691
+ if (firstLine.length > 0) {
692
+ // Truncate if too long (max 40 chars for graph labels)
693
+ const preview = firstLine.length > 40 ? firstLine.substring(0, 37) + '...' : firstLine;
694
+ return preview;
695
+ }
696
+ // Fallback to line numbers if content is empty
697
+ if (d.start_line) {
698
+ return `L${d.start_line}-${d.end_line}`;
699
+ }
700
+ return d.name;
701
+ }
702
+
703
+ // Code chunk nodes - show parent context if available
704
+ if (d.type === 'code' && d.start_line) {
705
+ // Try to find parent class/function by looking at incoming links
706
+ const parentLink = allLinks.find(l => {
707
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
708
+ return targetId === d.id;
709
+ });
710
+
711
+ if (parentLink) {
712
+ const sourceId = typeof parentLink.source === 'object' ? parentLink.source.id : parentLink.source;
713
+ const parentNode = allNodes.find(n => n.id === sourceId);
714
+
715
+ if (parentNode && ['class', 'function', 'method'].includes(parentNode.type)) {
716
+ // Show parent context with line numbers
717
+ return `${parentNode.name}:L${d.start_line}-${d.end_line}`;
718
+ }
719
+ }
720
+
721
+ // Fallback: just show line numbers if no parent found
722
+ return `L${d.start_line}-${d.end_line}`;
723
+ }
724
+
591
725
  return d.name;
592
726
  })
593
727
  .attr("dy", 30);
@@ -605,6 +739,102 @@
605
739
  updateStats({nodes: visibleNodesList, links: visibleLinks, metadata: {total_files: allNodes.length}});
606
740
  }
607
741
 
742
+ function highlightNode(node) {
743
+ // Remove previous highlights - reset all to default stroke
744
+ d3.selectAll('.node circle, .node rect')
745
+ .attr('stroke-width', d => hasChildren(d) ? (d.type === 'file' || d.type === 'directory' ? 2 : 3) : 1.5)
746
+ .attr('stroke', d => {
747
+ if (hasChildren(d)) {
748
+ return '#ffffff';
749
+ }
750
+ return d.type === 'file' || d.type === 'directory' ? (d.type === 'file' ? '#58a6ff' : '#79c0ff') : '#30363d';
751
+ });
752
+
753
+ // Highlight the current node with blue stroke
754
+ const currentNodeElement = d3.selectAll('.node')
755
+ .filter(d => d.id === node.id);
756
+
757
+ currentNodeElement.select('circle, rect')
758
+ .attr('stroke', '#58a6ff')
759
+ .attr('stroke-width', 4);
760
+ }
761
+
762
+ function centerOnNode(node) {
763
+ const nodeElement = d3.selectAll('.node')
764
+ .filter(d => d.id === node.id);
765
+
766
+ if (nodeElement.empty()) return;
767
+
768
+ const nodeData = nodeElement.datum();
769
+ const transform = d3.zoomTransform(svg.node());
770
+
771
+ // Calculate center position
772
+ const x = -nodeData.x * transform.k + width / 2;
773
+ const y = -nodeData.y * transform.k + height / 2;
774
+
775
+ svg.transition()
776
+ .duration(750)
777
+ .call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(transform.k));
778
+ }
779
+
780
+ function updateGraphForNode(node) {
781
+ if (!node) {
782
+ // Show default root view
783
+ renderGraph();
784
+ return;
785
+ }
786
+
787
+ // Find parent
788
+ const parentLinks = allLinks.filter(l => {
789
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
790
+ return targetId === node.id;
791
+ });
792
+
793
+ if (parentLinks.length === 0) {
794
+ // This is a root node - show all roots
795
+ renderGraph();
796
+ highlightNode(node);
797
+ return;
798
+ }
799
+
800
+ // Get parent
801
+ const parentId = typeof parentLinks[0].source === 'object'
802
+ ? parentLinks[0].source.id
803
+ : parentLinks[0].source;
804
+ const parent = allNodes.find(n => n.id === parentId);
805
+
806
+ // Get all siblings (children of same parent)
807
+ const siblingLinks = allLinks.filter(l => {
808
+ const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
809
+ return sourceId === parentId;
810
+ });
811
+
812
+ const siblings = siblingLinks.map(l => {
813
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
814
+ return allNodes.find(n => n.id === targetId);
815
+ }).filter(n => n);
816
+
817
+ // Update visible nodes to show parent + siblings + current node's children
818
+ visibleNodes.clear();
819
+ visibleNodes.add(parentId); // Parent
820
+ siblings.forEach(s => visibleNodes.add(s.id)); // All siblings including current
821
+
822
+ // If current node is expanded, show its children too
823
+ if (!collapsedNodes.has(node.id)) {
824
+ const childLinks = allLinks.filter(l => {
825
+ const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
826
+ return sourceId === node.id;
827
+ });
828
+ childLinks.forEach(l => {
829
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
830
+ visibleNodes.add(targetId);
831
+ });
832
+ }
833
+
834
+ renderGraph();
835
+ highlightNode(node);
836
+ }
837
+
608
838
  function hasChildren(node) {
609
839
  // Handle both pre-mutation (string IDs) and post-mutation (object references)
610
840
  const result = allLinks.some(l => {
@@ -623,6 +853,9 @@
623
853
 
624
854
  event.stopPropagation();
625
855
 
856
+ // Track current node
857
+ currentNode = d;
858
+
626
859
  // Always show content pane when clicking any node
627
860
  showContentPane(d);
628
861
 
@@ -647,6 +880,9 @@
647
880
  } else {
648
881
  console.log('Node has no children, skipping expansion');
649
882
  }
883
+
884
+ // Highlight in graph
885
+ highlightNode(d);
650
886
  }
651
887
 
652
888
  function expandNode(node) {
@@ -654,12 +890,15 @@
654
890
  collapsedNodes.delete(node.id);
655
891
 
656
892
  // Find direct children
657
- const childLinks = allLinks.filter(l => (l.source.id || l.source) === node.id);
893
+ const childLinks = allLinks.filter(l => {
894
+ const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
895
+ return sourceId === node.id;
896
+ });
658
897
  console.log(` Found ${childLinks.length} child links for node ${node.id}`);
659
898
 
660
899
  const children = childLinks
661
900
  .map(l => {
662
- const targetId = l.target.id || l.target;
901
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
663
902
  const child = allNodes.find(n => n.id === targetId);
664
903
  if (!child) {
665
904
  console.warn(` Could not find child node with ID: ${targetId}`);
@@ -680,22 +919,55 @@
680
919
  }
681
920
 
682
921
  function collapseNode(node) {
922
+ console.log(`Collapsing node: ${node.name}`);
683
923
  collapsedNodes.add(node.id);
684
924
 
685
- // Hide all descendants recursively
686
- function hideDescendants(parentId) {
687
- const children = allLinks
688
- .filter(l => (l.source.id || l.source) === parentId)
689
- .map(l => l.target.id || l.target);
925
+ // Check if this is a root node
926
+ const isRootNode = !allLinks.some(l => {
927
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
928
+ return targetId === node.id;
929
+ });
690
930
 
691
- children.forEach(childId => {
692
- visibleNodes.delete(childId);
693
- collapsedNodes.delete(childId);
694
- hideDescendants(childId);
931
+ if (isRootNode) {
932
+ // Collapsing a root node - return to root-only view
933
+ console.log(' Collapsing root node - resetting to root view');
934
+
935
+ // Clear all visible nodes except root nodes
936
+ const rootNodes = allNodes.filter(n => {
937
+ const hasIncomingLinks = allLinks.some(l => {
938
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
939
+ return targetId === n.id;
940
+ });
941
+ return !hasIncomingLinks && n.type === 'directory';
695
942
  });
696
- }
697
943
 
698
- hideDescendants(node.id);
944
+ visibleNodes.clear();
945
+ rootNodes.forEach(n => visibleNodes.add(n.id));
946
+
947
+ // Clear all collapsed states
948
+ collapsedNodes.clear();
949
+ // Re-add collapsed state to all root nodes
950
+ rootNodes.forEach(n => collapsedNodes.add(n.id));
951
+ } else {
952
+ // Non-root node - existing recursive collapse logic
953
+ // Hide all descendants recursively
954
+ function hideDescendants(parentId) {
955
+ const children = allLinks
956
+ .filter(l => {
957
+ const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
958
+ return sourceId === parentId;
959
+ })
960
+ .map(l => typeof l.target === 'object' ? l.target.id : l.target);
961
+
962
+ children.forEach(childId => {
963
+ visibleNodes.delete(childId);
964
+ collapsedNodes.delete(childId);
965
+ hideDescendants(childId);
966
+ });
967
+ }
968
+
969
+ hideDescendants(node.id);
970
+ }
699
971
  }
700
972
 
701
973
  function showTooltip(event, d) {
@@ -769,10 +1041,47 @@
769
1041
  }
770
1042
  }
771
1043
 
1044
+ function findReferences(node) {
1045
+ // Find all nodes that link TO this node (reverse lookup)
1046
+ const incomingLinks = allLinks.filter(l => {
1047
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
1048
+ return targetId === node.id;
1049
+ });
1050
+
1051
+ const callers = incomingLinks.map(l => {
1052
+ const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
1053
+ return allNodes.find(n => n.id === sourceId);
1054
+ }).filter(n => n);
1055
+
1056
+ return callers;
1057
+ }
1058
+
1059
+ function findSemanticReferences(node) {
1060
+ // For semantic search, we'd ideally use the MCP vector search
1061
+ // For now, use simple heuristics based on name/content similarity
1062
+
1063
+ if (!node.content && !node.name) return [];
1064
+
1065
+ const searchTerms = node.name.toLowerCase().split(/[_\s]/);
1066
+
1067
+ const semanticMatches = allNodes.filter(other => {
1068
+ if (other.id === node.id) return false;
1069
+ if (!other.content && !other.name) return false;
1070
+
1071
+ const otherText = (other.content || other.name).toLowerCase();
1072
+ return searchTerms.some(term => term.length > 2 && otherText.includes(term));
1073
+ });
1074
+
1075
+ return semanticMatches.slice(0, 10); // Limit to top 10
1076
+ }
1077
+
772
1078
  function showContentPane(node) {
773
- // Highlight the node
1079
+ // Track and highlight the node
1080
+ currentNode = node;
774
1081
  highlightedNode = node;
775
- renderGraph();
1082
+
1083
+ // Enable focus button
1084
+ document.getElementById('focus-button').disabled = false;
776
1085
 
777
1086
  // Populate content pane
778
1087
  const pane = document.getElementById('content-pane');
@@ -819,99 +1128,711 @@
819
1128
  }
820
1129
 
821
1130
  function showDirectoryContents(node, container) {
1131
+ container.innerHTML = ''; // Clear first
1132
+
1133
+ // Add "Up" button if node has parent
1134
+ const parentLinks = allLinks.filter(l => {
1135
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
1136
+ return targetId === node.id;
1137
+ });
1138
+
1139
+ if (parentLinks.length > 0) {
1140
+ const parentId = typeof parentLinks[0].source === 'object'
1141
+ ? parentLinks[0].source.id
1142
+ : parentLinks[0].source;
1143
+ const parent = allNodes.find(n => n.id === parentId);
1144
+
1145
+ if (parent) {
1146
+ const upButton = document.createElement('div');
1147
+ upButton.style.cssText = 'margin-bottom: 15px; padding: 8px 12px; background-color: #21262d; border-radius: 6px; border: 1px solid #30363d; cursor: pointer; display: flex; align-items: center; gap: 8px; transition: background-color 0.2s;';
1148
+
1149
+ const upArrow = document.createElement('span');
1150
+ upArrow.textContent = '↑';
1151
+ upArrow.style.cssText = 'font-size: 20px; color: #58a6ff;';
1152
+
1153
+ const upText = document.createElement('span');
1154
+ upText.textContent = `Up to ${parent.name}`;
1155
+ upText.style.color = '#c9d1d9';
1156
+
1157
+ upButton.appendChild(upArrow);
1158
+ upButton.appendChild(upText);
1159
+
1160
+ upButton.addEventListener('mouseover', function() {
1161
+ this.style.backgroundColor = '#30363d';
1162
+ });
1163
+ upButton.addEventListener('mouseout', function() {
1164
+ this.style.backgroundColor = '#21262d';
1165
+ });
1166
+ upButton.addEventListener('click', () => {
1167
+ showContentPane(parent);
1168
+ updateGraphForNode(parent);
1169
+ });
1170
+
1171
+ container.appendChild(upButton);
1172
+ }
1173
+ }
1174
+
822
1175
  // Find all direct children of this directory
823
1176
  const children = allLinks
824
- .filter(l => (l.source.id || l.source) === node.id)
825
- .map(l => allNodes.find(n => n.id === (l.target.id || l.target)))
1177
+ .filter(l => {
1178
+ const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
1179
+ return sourceId === node.id;
1180
+ })
1181
+ .map(l => {
1182
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
1183
+ return allNodes.find(n => n.id === targetId);
1184
+ })
826
1185
  .filter(n => n);
827
1186
 
1187
+ // Re-find siblings (already found parentLinks above)
1188
+ // const parentLinks = allLinks.filter(l => {
1189
+ // const targetId = typeof l.target === 'object' ? l.target.id : l.target;
1190
+ // return targetId === node.id;
1191
+ // });
1192
+
1193
+ let siblings = [];
1194
+ if (parentLinks.length > 0) {
1195
+ const parentId = typeof parentLinks[0].source === 'object'
1196
+ ? parentLinks[0].source.id
1197
+ : parentLinks[0].source;
1198
+
1199
+ const siblingLinks = allLinks.filter(l => {
1200
+ const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
1201
+ return sourceId === parentId;
1202
+ });
1203
+
1204
+ siblings = siblingLinks
1205
+ .map(l => {
1206
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
1207
+ return allNodes.find(n => n.id === targetId);
1208
+ })
1209
+ .filter(n => n && n.id !== node.id); // Exclude self
1210
+ }
1211
+
1212
+ // Display siblings first if they exist
1213
+ if (siblings.length > 0) {
1214
+ const siblingsHeader = document.createElement('h4');
1215
+ siblingsHeader.textContent = 'Siblings';
1216
+ siblingsHeader.style.color = '#8b949e';
1217
+ siblingsHeader.style.marginTop = '0';
1218
+ container.appendChild(siblingsHeader);
1219
+
1220
+ const siblingsList = document.createElement('ul');
1221
+ siblingsList.className = 'directory-list';
1222
+
1223
+ siblings.forEach(sibling => {
1224
+ const listItem = document.createElement('li');
1225
+ listItem.style.cursor = 'pointer';
1226
+ listItem.style.transition = 'background-color 0.2s';
1227
+
1228
+ const icon = sibling.type === 'directory' ? '📁' : sibling.type === 'file' ? '📄' : '📝';
1229
+ listItem.innerHTML = `
1230
+ <span class="item-icon">${icon}</span>
1231
+ ${sibling.name}
1232
+ <span class="item-type">${sibling.type}</span>
1233
+ `;
1234
+
1235
+ listItem.addEventListener('mouseover', function() {
1236
+ this.style.backgroundColor = '#30363d';
1237
+ });
1238
+ listItem.addEventListener('mouseout', function() {
1239
+ this.style.backgroundColor = 'transparent';
1240
+ });
1241
+ listItem.addEventListener('click', function(event) {
1242
+ event.stopPropagation();
1243
+ if (hasChildren(sibling)) {
1244
+ if (collapsedNodes.has(sibling.id)) {
1245
+ expandNode(sibling);
1246
+ } else {
1247
+ collapseNode(sibling);
1248
+ }
1249
+ renderGraph();
1250
+ }
1251
+ showContentPane(sibling);
1252
+ updateGraphForNode(sibling);
1253
+ });
1254
+
1255
+ siblingsList.appendChild(listItem);
1256
+ });
1257
+
1258
+ container.appendChild(siblingsList);
1259
+ }
1260
+
1261
+ // Add references section for applicable node types
1262
+ if (node.type === 'function' || node.type === 'class' || node.type === 'method' || node.type === 'code') {
1263
+ const directCallers = findReferences(node);
1264
+ const semanticRefs = findSemanticReferences(node);
1265
+
1266
+ if (directCallers.length > 0 || semanticRefs.length > 0) {
1267
+ const refsHeader = document.createElement('h4');
1268
+ refsHeader.textContent = 'References';
1269
+ refsHeader.style.color = '#8b949e';
1270
+ refsHeader.style.marginTop = siblings.length > 0 ? '20px' : '0';
1271
+ container.appendChild(refsHeader);
1272
+
1273
+ if (directCallers.length > 0) {
1274
+ const callersSubheader = document.createElement('h5');
1275
+ callersSubheader.textContent = 'Direct Callers (AST)';
1276
+ callersSubheader.style.color = '#58a6ff';
1277
+ callersSubheader.style.fontSize = '14px';
1278
+ callersSubheader.style.marginTop = '10px';
1279
+ container.appendChild(callersSubheader);
1280
+
1281
+ const callersList = document.createElement('ul');
1282
+ callersList.className = 'directory-list';
1283
+
1284
+ directCallers.forEach(caller => {
1285
+ const listItem = document.createElement('li');
1286
+ listItem.style.cursor = 'pointer';
1287
+ listItem.style.transition = 'background-color 0.2s';
1288
+ listItem.innerHTML = `
1289
+ <span class="item-icon">🔗</span>
1290
+ ${caller.name}
1291
+ <span class="item-type">${caller.type}</span>
1292
+ `;
1293
+
1294
+ listItem.addEventListener('mouseover', function() {
1295
+ this.style.backgroundColor = '#30363d';
1296
+ });
1297
+ listItem.addEventListener('mouseout', function() {
1298
+ this.style.backgroundColor = 'transparent';
1299
+ });
1300
+ listItem.addEventListener('click', () => {
1301
+ showContentPane(caller);
1302
+ updateGraphForNode(caller);
1303
+ });
1304
+
1305
+ callersList.appendChild(listItem);
1306
+ });
1307
+
1308
+ container.appendChild(callersList);
1309
+ }
1310
+
1311
+ if (semanticRefs.length > 0) {
1312
+ const semanticSubheader = document.createElement('h5');
1313
+ semanticSubheader.textContent = 'Semantic References';
1314
+ semanticSubheader.style.color = '#a371f7';
1315
+ semanticSubheader.style.fontSize = '14px';
1316
+ semanticSubheader.style.marginTop = '10px';
1317
+ container.appendChild(semanticSubheader);
1318
+
1319
+ const semanticList = document.createElement('ul');
1320
+ semanticList.className = 'directory-list';
1321
+
1322
+ semanticRefs.forEach(ref => {
1323
+ const listItem = document.createElement('li');
1324
+ listItem.style.cursor = 'pointer';
1325
+ listItem.style.transition = 'background-color 0.2s';
1326
+ listItem.style.borderLeft = '2px dotted #a371f7';
1327
+ listItem.style.paddingLeft = '10px';
1328
+ listItem.innerHTML = `
1329
+ <span class="item-icon">🔗</span>
1330
+ ${ref.name}
1331
+ <span class="item-type">${ref.type}</span>
1332
+ `;
1333
+
1334
+ listItem.addEventListener('mouseover', function() {
1335
+ this.style.backgroundColor = '#30363d';
1336
+ });
1337
+ listItem.addEventListener('mouseout', function() {
1338
+ this.style.backgroundColor = 'transparent';
1339
+ });
1340
+ listItem.addEventListener('click', () => {
1341
+ showContentPane(ref);
1342
+ updateGraphForNode(ref);
1343
+ });
1344
+
1345
+ semanticList.appendChild(listItem);
1346
+ });
1347
+
1348
+ container.appendChild(semanticList);
1349
+ }
1350
+ }
1351
+ }
1352
+
1353
+ // Display children
828
1354
  if (children.length === 0) {
829
- container.innerHTML = '<p style="color: #8b949e;">Empty directory</p>';
1355
+ const emptyMsg = document.createElement('p');
1356
+ emptyMsg.textContent = 'Empty directory';
1357
+ emptyMsg.style.color = '#8b949e';
1358
+ container.appendChild(emptyMsg);
830
1359
  return;
831
1360
  }
832
1361
 
1362
+ const childrenHeader = document.createElement('h4');
1363
+ childrenHeader.textContent = 'Contents';
1364
+ childrenHeader.style.color = '#8b949e';
1365
+ if (siblings.length > 0) {
1366
+ childrenHeader.style.marginTop = '20px';
1367
+ } else {
1368
+ childrenHeader.style.marginTop = '0';
1369
+ }
1370
+ container.appendChild(childrenHeader);
1371
+
833
1372
  // Group by type
834
1373
  const files = children.filter(n => n.type === 'file');
835
1374
  const subdirs = children.filter(n => n.type === 'directory');
836
1375
  const chunks = children.filter(n => n.type !== 'file' && n.type !== 'directory');
837
1376
 
838
- let html = '<ul class="directory-list">';
1377
+ const childrenList = document.createElement('ul');
1378
+ childrenList.className = 'directory-list';
839
1379
 
840
1380
  // Show subdirectories first
841
1381
  subdirs.forEach(child => {
842
- html += `
843
- <li>
844
- <span class="item-icon">📁</span>
845
- ${child.name}
846
- <span class="item-type">directory</span>
847
- </li>
1382
+ const listItem = document.createElement('li');
1383
+ listItem.style.cursor = 'pointer';
1384
+ listItem.style.transition = 'background-color 0.2s';
1385
+ listItem.innerHTML = `
1386
+ <span class="item-icon">📁</span>
1387
+ ${child.name}
1388
+ <span class="item-type">directory</span>
848
1389
  `;
1390
+
1391
+ listItem.addEventListener('mouseover', function() {
1392
+ this.style.backgroundColor = '#30363d';
1393
+ });
1394
+ listItem.addEventListener('mouseout', function() {
1395
+ this.style.backgroundColor = 'transparent';
1396
+ });
1397
+ listItem.addEventListener('click', function(event) {
1398
+ event.stopPropagation();
1399
+ if (hasChildren(child)) {
1400
+ if (collapsedNodes.has(child.id)) {
1401
+ expandNode(child);
1402
+ } else {
1403
+ collapseNode(child);
1404
+ }
1405
+ renderGraph();
1406
+ }
1407
+ showContentPane(child);
1408
+ updateGraphForNode(child);
1409
+ });
1410
+
1411
+ childrenList.appendChild(listItem);
849
1412
  });
850
1413
 
851
1414
  // Then files
852
1415
  files.forEach(child => {
853
- html += `
854
- <li>
855
- <span class="item-icon">📄</span>
856
- ${child.name}
857
- <span class="item-type">file</span>
858
- </li>
1416
+ const listItem = document.createElement('li');
1417
+ listItem.style.cursor = 'pointer';
1418
+ listItem.style.transition = 'background-color 0.2s';
1419
+ listItem.innerHTML = `
1420
+ <span class="item-icon">📄</span>
1421
+ ${child.name}
1422
+ <span class="item-type">file</span>
859
1423
  `;
1424
+
1425
+ listItem.addEventListener('mouseover', function() {
1426
+ this.style.backgroundColor = '#30363d';
1427
+ });
1428
+ listItem.addEventListener('mouseout', function() {
1429
+ this.style.backgroundColor = 'transparent';
1430
+ });
1431
+ listItem.addEventListener('click', function(event) {
1432
+ event.stopPropagation();
1433
+ if (hasChildren(child)) {
1434
+ if (collapsedNodes.has(child.id)) {
1435
+ expandNode(child);
1436
+ } else {
1437
+ collapseNode(child);
1438
+ }
1439
+ renderGraph();
1440
+ }
1441
+ showContentPane(child);
1442
+ updateGraphForNode(child);
1443
+ });
1444
+
1445
+ childrenList.appendChild(listItem);
860
1446
  });
861
1447
 
862
1448
  // Then code chunks
863
1449
  chunks.forEach(child => {
864
1450
  const icon = child.type === 'class' ? '🔷' : child.type === 'function' ? '⚡' : '📝';
865
- html += `
866
- <li>
867
- <span class="item-icon">${icon}</span>
868
- ${child.name}
869
- <span class="item-type">${child.type}</span>
870
- </li>
1451
+ const listItem = document.createElement('li');
1452
+ listItem.style.cursor = 'pointer';
1453
+ listItem.style.transition = 'background-color 0.2s';
1454
+
1455
+ // Improve display name for text chunks
1456
+ let displayName = child.name;
1457
+ if (child.type === 'text' && child.content) {
1458
+ // For text chunks, show first line of content as preview
1459
+ const firstLine = child.content.split('\n')[0].trim();
1460
+ if (firstLine.length > 0) {
1461
+ displayName = firstLine.length > 50 ? firstLine.substring(0, 47) + '...' : firstLine;
1462
+ }
1463
+ }
1464
+
1465
+ listItem.innerHTML = `
1466
+ <span class="item-icon">${icon}</span>
1467
+ ${displayName}
1468
+ <span class="item-type">${child.type}</span>
871
1469
  `;
1470
+
1471
+ listItem.addEventListener('mouseover', function() {
1472
+ this.style.backgroundColor = '#30363d';
1473
+ });
1474
+ listItem.addEventListener('mouseout', function() {
1475
+ this.style.backgroundColor = 'transparent';
1476
+ });
1477
+ listItem.addEventListener('click', function(event) {
1478
+ event.stopPropagation();
1479
+ if (hasChildren(child)) {
1480
+ if (collapsedNodes.has(child.id)) {
1481
+ expandNode(child);
1482
+ } else {
1483
+ collapseNode(child);
1484
+ }
1485
+ renderGraph();
1486
+ }
1487
+ showContentPane(child);
1488
+ updateGraphForNode(child);
1489
+ });
1490
+
1491
+ childrenList.appendChild(listItem);
872
1492
  });
873
1493
 
874
- html += '</ul>';
1494
+ container.appendChild(childrenList);
875
1495
 
876
1496
  // Add summary
877
- const summary = `<p style="color: #8b949e; font-size: 11px; margin-top: 16px;">
878
- Total: ${children.length} items (${subdirs.length} directories, ${files.length} files, ${chunks.length} code chunks)
879
- </p>`;
880
-
881
- container.innerHTML = html + summary;
1497
+ const summary = document.createElement('p');
1498
+ summary.style.color = '#8b949e';
1499
+ summary.style.fontSize = '11px';
1500
+ summary.style.marginTop = '16px';
1501
+ summary.textContent = `Total: ${children.length} items (${subdirs.length} directories, ${files.length} files, ${chunks.length} code chunks)`;
1502
+ container.appendChild(summary);
882
1503
  }
883
1504
 
884
1505
  function showFileContents(node, container) {
1506
+ container.innerHTML = ''; // Clear first
1507
+
1508
+ // Add "Up" button if node has parent
1509
+ const parentLinks = allLinks.filter(l => {
1510
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
1511
+ return targetId === node.id;
1512
+ });
1513
+
1514
+ if (parentLinks.length > 0) {
1515
+ const parentId = typeof parentLinks[0].source === 'object'
1516
+ ? parentLinks[0].source.id
1517
+ : parentLinks[0].source;
1518
+ const parent = allNodes.find(n => n.id === parentId);
1519
+
1520
+ if (parent) {
1521
+ const upButton = document.createElement('div');
1522
+ upButton.style.cssText = 'margin-bottom: 15px; padding: 8px 12px; background-color: #21262d; border-radius: 6px; border: 1px solid #30363d; cursor: pointer; display: flex; align-items: center; gap: 8px; transition: background-color 0.2s;';
1523
+
1524
+ const upArrow = document.createElement('span');
1525
+ upArrow.textContent = '↑';
1526
+ upArrow.style.cssText = 'font-size: 20px; color: #58a6ff;';
1527
+
1528
+ const upText = document.createElement('span');
1529
+ upText.textContent = `Up to ${parent.name}`;
1530
+ upText.style.color = '#c9d1d9';
1531
+
1532
+ upButton.appendChild(upArrow);
1533
+ upButton.appendChild(upText);
1534
+
1535
+ upButton.addEventListener('mouseover', function() {
1536
+ this.style.backgroundColor = '#30363d';
1537
+ });
1538
+ upButton.addEventListener('mouseout', function() {
1539
+ this.style.backgroundColor = '#21262d';
1540
+ });
1541
+ upButton.addEventListener('click', () => {
1542
+ showContentPane(parent);
1543
+ updateGraphForNode(parent);
1544
+ });
1545
+
1546
+ container.appendChild(upButton);
1547
+ }
1548
+ }
1549
+
1550
+ // Re-find siblings (already found parentLinks above)
1551
+ // const parentLinks = allLinks.filter(l => {
1552
+ // const targetId = typeof l.target === 'object' ? l.target.id : l.target;
1553
+ // return targetId === node.id;
1554
+ // });
1555
+
1556
+ let siblings = [];
1557
+ if (parentLinks.length > 0) {
1558
+ const parentId = typeof parentLinks[0].source === 'object'
1559
+ ? parentLinks[0].source.id
1560
+ : parentLinks[0].source;
1561
+
1562
+ const siblingLinks = allLinks.filter(l => {
1563
+ const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
1564
+ return sourceId === parentId;
1565
+ });
1566
+
1567
+ siblings = siblingLinks
1568
+ .map(l => {
1569
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
1570
+ return allNodes.find(n => n.id === targetId);
1571
+ })
1572
+ .filter(n => n && n.id !== node.id); // Exclude self
1573
+ }
1574
+
1575
+ // Display siblings first if they exist
1576
+ if (siblings.length > 0) {
1577
+ const siblingsHeader = document.createElement('h4');
1578
+ siblingsHeader.textContent = 'Siblings';
1579
+ siblingsHeader.style.color = '#8b949e';
1580
+ siblingsHeader.style.marginTop = '0';
1581
+ container.appendChild(siblingsHeader);
1582
+
1583
+ const siblingsList = document.createElement('ul');
1584
+ siblingsList.className = 'directory-list';
1585
+
1586
+ siblings.forEach(sibling => {
1587
+ const listItem = document.createElement('li');
1588
+ listItem.style.cursor = 'pointer';
1589
+ listItem.style.transition = 'background-color 0.2s';
1590
+
1591
+ const icon = sibling.type === 'directory' ? '📁' : sibling.type === 'file' ? '📄' : '📝';
1592
+ listItem.innerHTML = `
1593
+ <span class="item-icon">${icon}</span>
1594
+ ${sibling.name}
1595
+ <span class="item-type">${sibling.type}</span>
1596
+ `;
1597
+
1598
+ listItem.addEventListener('mouseover', function() {
1599
+ this.style.backgroundColor = '#30363d';
1600
+ });
1601
+ listItem.addEventListener('mouseout', function() {
1602
+ this.style.backgroundColor = 'transparent';
1603
+ });
1604
+ listItem.addEventListener('click', function(event) {
1605
+ event.stopPropagation();
1606
+ if (hasChildren(sibling)) {
1607
+ if (collapsedNodes.has(sibling.id)) {
1608
+ expandNode(sibling);
1609
+ } else {
1610
+ collapseNode(sibling);
1611
+ }
1612
+ renderGraph();
1613
+ }
1614
+ showContentPane(sibling);
1615
+ updateGraphForNode(sibling);
1616
+ });
1617
+
1618
+ siblingsList.appendChild(listItem);
1619
+ });
1620
+
1621
+ container.appendChild(siblingsList);
1622
+ }
1623
+
1624
+ // Add references section for applicable node types
1625
+ if (node.type === 'function' || node.type === 'class' || node.type === 'method' || node.type === 'code') {
1626
+ const directCallers = findReferences(node);
1627
+ const semanticRefs = findSemanticReferences(node);
1628
+
1629
+ if (directCallers.length > 0 || semanticRefs.length > 0) {
1630
+ const refsHeader = document.createElement('h4');
1631
+ refsHeader.textContent = 'References';
1632
+ refsHeader.style.color = '#8b949e';
1633
+ refsHeader.style.marginTop = siblings.length > 0 ? '20px' : '0';
1634
+ container.appendChild(refsHeader);
1635
+
1636
+ if (directCallers.length > 0) {
1637
+ const callersSubheader = document.createElement('h5');
1638
+ callersSubheader.textContent = 'Direct Callers (AST)';
1639
+ callersSubheader.style.color = '#58a6ff';
1640
+ callersSubheader.style.fontSize = '14px';
1641
+ callersSubheader.style.marginTop = '10px';
1642
+ container.appendChild(callersSubheader);
1643
+
1644
+ const callersList = document.createElement('ul');
1645
+ callersList.className = 'directory-list';
1646
+
1647
+ directCallers.forEach(caller => {
1648
+ const listItem = document.createElement('li');
1649
+ listItem.style.cursor = 'pointer';
1650
+ listItem.style.transition = 'background-color 0.2s';
1651
+ listItem.innerHTML = `
1652
+ <span class="item-icon">🔗</span>
1653
+ ${caller.name}
1654
+ <span class="item-type">${caller.type}</span>
1655
+ `;
1656
+
1657
+ listItem.addEventListener('mouseover', function() {
1658
+ this.style.backgroundColor = '#30363d';
1659
+ });
1660
+ listItem.addEventListener('mouseout', function() {
1661
+ this.style.backgroundColor = 'transparent';
1662
+ });
1663
+ listItem.addEventListener('click', () => {
1664
+ showContentPane(caller);
1665
+ updateGraphForNode(caller);
1666
+ });
1667
+
1668
+ callersList.appendChild(listItem);
1669
+ });
1670
+
1671
+ container.appendChild(callersList);
1672
+ }
1673
+
1674
+ if (semanticRefs.length > 0) {
1675
+ const semanticSubheader = document.createElement('h5');
1676
+ semanticSubheader.textContent = 'Semantic References';
1677
+ semanticSubheader.style.color = '#a371f7';
1678
+ semanticSubheader.style.fontSize = '14px';
1679
+ semanticSubheader.style.marginTop = '10px';
1680
+ container.appendChild(semanticSubheader);
1681
+
1682
+ const semanticList = document.createElement('ul');
1683
+ semanticList.className = 'directory-list';
1684
+
1685
+ semanticRefs.forEach(ref => {
1686
+ const listItem = document.createElement('li');
1687
+ listItem.style.cursor = 'pointer';
1688
+ listItem.style.transition = 'background-color 0.2s';
1689
+ listItem.style.borderLeft = '2px dotted #a371f7';
1690
+ listItem.style.paddingLeft = '10px';
1691
+ listItem.innerHTML = `
1692
+ <span class="item-icon">🔗</span>
1693
+ ${ref.name}
1694
+ <span class="item-type">${ref.type}</span>
1695
+ `;
1696
+
1697
+ listItem.addEventListener('mouseover', function() {
1698
+ this.style.backgroundColor = '#30363d';
1699
+ });
1700
+ listItem.addEventListener('mouseout', function() {
1701
+ this.style.backgroundColor = 'transparent';
1702
+ });
1703
+ listItem.addEventListener('click', () => {
1704
+ showContentPane(ref);
1705
+ updateGraphForNode(ref);
1706
+ });
1707
+
1708
+ semanticList.appendChild(listItem);
1709
+ });
1710
+
1711
+ container.appendChild(semanticList);
1712
+ }
1713
+ }
1714
+ }
1715
+
885
1716
  // Find all chunks in this file
886
1717
  const fileChunks = allLinks
887
- .filter(l => (l.source.id || l.source) === node.id)
888
- .map(l => allNodes.find(n => n.id === (l.target.id || l.target)))
1718
+ .filter(l => {
1719
+ const sourceId = typeof l.source === 'object' ? l.source.id : l.source;
1720
+ return sourceId === node.id;
1721
+ })
1722
+ .map(l => {
1723
+ const targetId = typeof l.target === 'object' ? l.target.id : l.target;
1724
+ return allNodes.find(n => n.id === targetId);
1725
+ })
889
1726
  .filter(n => n);
890
1727
 
891
1728
  if (fileChunks.length === 0) {
892
- container.innerHTML = '<p style="color: #8b949e;">No code chunks found in this file</p>';
1729
+ const emptyMsg = document.createElement('p');
1730
+ emptyMsg.textContent = 'No code chunks found in this file';
1731
+ emptyMsg.style.color = '#8b949e';
1732
+ if (siblings.length > 0) {
1733
+ emptyMsg.style.marginTop = '20px';
1734
+ }
1735
+ container.appendChild(emptyMsg);
893
1736
  return;
894
1737
  }
895
1738
 
1739
+ // Add chunks header
1740
+ const chunksHeader = document.createElement('h4');
1741
+ chunksHeader.textContent = 'Code Chunks';
1742
+ chunksHeader.style.color = '#8b949e';
1743
+ if (siblings.length > 0) {
1744
+ chunksHeader.style.marginTop = '20px';
1745
+ } else {
1746
+ chunksHeader.style.marginTop = '0';
1747
+ }
1748
+ container.appendChild(chunksHeader);
1749
+
1750
+ // Create clickable list of chunks
1751
+ const chunksList = document.createElement('ul');
1752
+ chunksList.className = 'directory-list';
1753
+
1754
+ fileChunks.forEach(chunk => {
1755
+ const icon = chunk.type === 'class' ? '🔷' :
1756
+ chunk.type === 'function' ? '⚡' :
1757
+ chunk.type === 'imports' ? '⇄' : '📝';
1758
+ const listItem = document.createElement('li');
1759
+ listItem.style.cursor = 'pointer';
1760
+ listItem.style.transition = 'background-color 0.2s';
1761
+
1762
+ // Improve display name for text chunks
1763
+ let displayName = chunk.name;
1764
+ if (chunk.type === 'text' && chunk.content) {
1765
+ // For text chunks, show first line of content as preview
1766
+ const firstLine = chunk.content.split('\n')[0].trim();
1767
+ if (firstLine.length > 0) {
1768
+ displayName = firstLine.length > 50 ? firstLine.substring(0, 47) + '...' : firstLine;
1769
+ }
1770
+ if (chunk.start_line) {
1771
+ displayName += ` (L${chunk.start_line}-${chunk.end_line})`;
1772
+ }
1773
+ } else if (chunk.start_line) {
1774
+ displayName += ` (L${chunk.start_line}-${chunk.end_line})`;
1775
+ }
1776
+
1777
+ listItem.innerHTML = `
1778
+ <span class="item-icon">${icon}</span>
1779
+ ${displayName}
1780
+ <span class="item-type">${chunk.type}</span>
1781
+ `;
1782
+
1783
+ listItem.addEventListener('mouseover', function() {
1784
+ this.style.backgroundColor = '#30363d';
1785
+ });
1786
+ listItem.addEventListener('mouseout', function() {
1787
+ this.style.backgroundColor = 'transparent';
1788
+ });
1789
+ listItem.addEventListener('click', function(event) {
1790
+ event.stopPropagation();
1791
+ if (hasChildren(chunk)) {
1792
+ if (collapsedNodes.has(chunk.id)) {
1793
+ expandNode(chunk);
1794
+ } else {
1795
+ collapseNode(chunk);
1796
+ }
1797
+ renderGraph();
1798
+ }
1799
+ showContentPane(chunk);
1800
+ updateGraphForNode(chunk);
1801
+ });
1802
+
1803
+ chunksList.appendChild(listItem);
1804
+ });
1805
+
1806
+ container.appendChild(chunksList);
1807
+
896
1808
  // Collect all content from chunks and sort by line number
897
1809
  const sortedChunks = fileChunks
898
1810
  .filter(c => c.content)
899
1811
  .sort((a, b) => a.start_line - b.start_line);
900
1812
 
901
- if (sortedChunks.length === 0) {
902
- container.innerHTML = '<p style="color: #8b949e;">File content not available</p>';
903
- return;
1813
+ if (sortedChunks.length > 0) {
1814
+ // Add full content section
1815
+ const contentHeader = document.createElement('h4');
1816
+ contentHeader.textContent = 'Full File Content';
1817
+ contentHeader.style.color = '#8b949e';
1818
+ contentHeader.style.marginTop = '20px';
1819
+ container.appendChild(contentHeader);
1820
+
1821
+ const contentInfo = document.createElement('p');
1822
+ contentInfo.textContent = `Contains ${fileChunks.length} code chunks`;
1823
+ contentInfo.style.color = '#8b949e';
1824
+ contentInfo.style.fontSize = '11px';
1825
+ contentInfo.style.marginBottom = '12px';
1826
+ container.appendChild(contentInfo);
1827
+
1828
+ // Combine all chunks to show full file
1829
+ const fullContent = sortedChunks.map(c => c.content).join('\n\n');
1830
+ const pre = document.createElement('pre');
1831
+ const code = document.createElement('code');
1832
+ code.textContent = fullContent;
1833
+ pre.appendChild(code);
1834
+ container.appendChild(pre);
904
1835
  }
905
-
906
- // Combine all chunks to show full file
907
- const fullContent = sortedChunks.map(c => c.content).join('\n\n');
908
-
909
- container.innerHTML = `
910
- <p style="color: #8b949e; font-size: 11px; margin-bottom: 12px;">
911
- Contains ${fileChunks.length} code chunks
912
- </p>
913
- <pre><code>${escapeHtml(fullContent)}</code></pre>
914
- `;
915
1836
  }
916
1837
 
917
1838
  function showImportDetails(node, container) {
@@ -944,25 +1865,141 @@
944
1865
  }
945
1866
 
946
1867
  function showCodeContent(node, container) {
947
- // Show code for function, class, method, or code chunks
948
- let html = '';
1868
+ container.innerHTML = ''; // Clear first
1869
+
1870
+ // Add references section for applicable node types
1871
+ if (node.type === 'function' || node.type === 'class' || node.type === 'method' || node.type === 'code') {
1872
+ const directCallers = findReferences(node);
1873
+ const semanticRefs = findSemanticReferences(node);
1874
+
1875
+ if (directCallers.length > 0 || semanticRefs.length > 0) {
1876
+ const refsHeader = document.createElement('h4');
1877
+ refsHeader.textContent = 'References';
1878
+ refsHeader.style.color = '#8b949e';
1879
+ refsHeader.style.marginTop = '0';
1880
+ refsHeader.style.marginBottom = '10px';
1881
+ container.appendChild(refsHeader);
1882
+
1883
+ if (directCallers.length > 0) {
1884
+ const callersSubheader = document.createElement('h5');
1885
+ callersSubheader.textContent = 'Direct Callers (AST)';
1886
+ callersSubheader.style.color = '#58a6ff';
1887
+ callersSubheader.style.fontSize = '14px';
1888
+ callersSubheader.style.marginTop = '10px';
1889
+ container.appendChild(callersSubheader);
1890
+
1891
+ const callersList = document.createElement('ul');
1892
+ callersList.className = 'directory-list';
1893
+
1894
+ directCallers.forEach(caller => {
1895
+ const listItem = document.createElement('li');
1896
+ listItem.style.cursor = 'pointer';
1897
+ listItem.style.transition = 'background-color 0.2s';
1898
+ listItem.innerHTML = `
1899
+ <span class="item-icon">🔗</span>
1900
+ ${caller.name}
1901
+ <span class="item-type">${caller.type}</span>
1902
+ `;
1903
+
1904
+ listItem.addEventListener('mouseover', function() {
1905
+ this.style.backgroundColor = '#30363d';
1906
+ });
1907
+ listItem.addEventListener('mouseout', function() {
1908
+ this.style.backgroundColor = 'transparent';
1909
+ });
1910
+ listItem.addEventListener('click', () => {
1911
+ showContentPane(caller);
1912
+ updateGraphForNode(caller);
1913
+ });
1914
+
1915
+ callersList.appendChild(listItem);
1916
+ });
1917
+
1918
+ container.appendChild(callersList);
1919
+ }
949
1920
 
1921
+ if (semanticRefs.length > 0) {
1922
+ const semanticSubheader = document.createElement('h5');
1923
+ semanticSubheader.textContent = 'Semantic References';
1924
+ semanticSubheader.style.color = '#a371f7';
1925
+ semanticSubheader.style.fontSize = '14px';
1926
+ semanticSubheader.style.marginTop = '10px';
1927
+ container.appendChild(semanticSubheader);
1928
+
1929
+ const semanticList = document.createElement('ul');
1930
+ semanticList.className = 'directory-list';
1931
+
1932
+ semanticRefs.forEach(ref => {
1933
+ const listItem = document.createElement('li');
1934
+ listItem.style.cursor = 'pointer';
1935
+ listItem.style.transition = 'background-color 0.2s';
1936
+ listItem.style.borderLeft = '2px dotted #a371f7';
1937
+ listItem.style.paddingLeft = '10px';
1938
+ listItem.innerHTML = `
1939
+ <span class="item-icon">🔗</span>
1940
+ ${ref.name}
1941
+ <span class="item-type">${ref.type}</span>
1942
+ `;
1943
+
1944
+ listItem.addEventListener('mouseover', function() {
1945
+ this.style.backgroundColor = '#30363d';
1946
+ });
1947
+ listItem.addEventListener('mouseout', function() {
1948
+ this.style.backgroundColor = 'transparent';
1949
+ });
1950
+ listItem.addEventListener('click', () => {
1951
+ showContentPane(ref);
1952
+ updateGraphForNode(ref);
1953
+ });
1954
+
1955
+ semanticList.appendChild(listItem);
1956
+ });
1957
+
1958
+ container.appendChild(semanticList);
1959
+ }
1960
+
1961
+ // Add separator
1962
+ const separator = document.createElement('h4');
1963
+ separator.textContent = 'Code Content';
1964
+ separator.style.color = '#8b949e';
1965
+ separator.style.marginTop = '20px';
1966
+ separator.style.marginBottom = '10px';
1967
+ container.appendChild(separator);
1968
+ }
1969
+ }
1970
+
1971
+ // Show code for function, class, method, or code chunks
950
1972
  if (node.docstring) {
951
- html += `
952
- <div style="margin-bottom: 16px; padding: 12px; background: #161b22; border: 1px solid #30363d; border-radius: 6px;">
953
- <div style="font-size: 11px; color: #8b949e; margin-bottom: 8px; font-weight: 600;">DOCSTRING</div>
954
- <pre style="margin: 0; padding: 0; background: transparent; border: none;"><code>${escapeHtml(node.docstring)}</code></pre>
955
- </div>
956
- `;
1973
+ const docstringDiv = document.createElement('div');
1974
+ docstringDiv.style.cssText = 'margin-bottom: 16px; padding: 12px; background: #161b22; border: 1px solid #30363d; border-radius: 6px;';
1975
+
1976
+ const docLabel = document.createElement('div');
1977
+ docLabel.textContent = 'DOCSTRING';
1978
+ docLabel.style.cssText = 'font-size: 11px; color: #8b949e; margin-bottom: 8px; font-weight: 600;';
1979
+
1980
+ const docPre = document.createElement('pre');
1981
+ docPre.style.cssText = 'margin: 0; padding: 0; background: transparent; border: none;';
1982
+ const docCode = document.createElement('code');
1983
+ docCode.textContent = node.docstring;
1984
+ docPre.appendChild(docCode);
1985
+
1986
+ docstringDiv.appendChild(docLabel);
1987
+ docstringDiv.appendChild(docPre);
1988
+ container.appendChild(docstringDiv);
957
1989
  }
958
1990
 
959
1991
  if (node.content) {
960
- html += `<pre><code>${escapeHtml(node.content)}</code></pre>`;
1992
+ const pre = document.createElement('pre');
1993
+ const code = document.createElement('code');
1994
+ code.textContent = node.content;
1995
+ pre.appendChild(code);
1996
+ container.appendChild(pre);
961
1997
  } else {
962
- html += '<p style="color: #8b949e;">No content available</p>';
1998
+ const noContent = document.createElement('p');
1999
+ noContent.textContent = 'No content available';
2000
+ noContent.style.color = '#8b949e';
2001
+ container.appendChild(noContent);
963
2002
  }
964
-
965
- container.innerHTML = html;
966
2003
  }
967
2004
 
968
2005
  function escapeHtml(text) {
@@ -975,11 +2012,23 @@
975
2012
  const pane = document.getElementById('content-pane');
976
2013
  pane.classList.remove('visible');
977
2014
 
978
- // Remove highlight
2015
+ // Remove highlight and current node
2016
+ currentNode = null;
979
2017
  highlightedNode = null;
2018
+
2019
+ // Disable focus button
2020
+ document.getElementById('focus-button').disabled = true;
2021
+
980
2022
  renderGraph();
981
2023
  }
982
2024
 
2025
+ // Wire up Focus button
2026
+ document.getElementById('focus-button').addEventListener('click', () => {
2027
+ if (currentNode) {
2028
+ centerOnNode(currentNode);
2029
+ }
2030
+ });
2031
+
983
2032
  // Auto-load graph data on page load
984
2033
  window.addEventListener('DOMContentLoaded', () => {
985
2034
  const loadingEl = document.getElementById('loading');