@plumpslabs/kuma 2.4.10 → 2.4.12

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.
@@ -505,32 +505,280 @@ table.clean-table tr:hover td {
505
505
  #graph-canvas {
506
506
  width: 100%;
507
507
  height: 100%;
508
- background: radial-gradient(ellipse at center, #161e31 0%, var(--bg) 75%);
508
+ background-color: #060911;
509
+ background-image:
510
+ radial-gradient(circle at 50% 50%, rgba(30, 41, 59, 0.45) 0%, transparent 85%),
511
+ radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px);
512
+ background-size: 100% 100%, 28px 28px;
509
513
  }
510
514
  .graph-toolbar {
511
515
  position: absolute;
512
516
  top: 16px;
513
517
  left: 16px;
514
518
  display: flex;
519
+ align-items: center;
515
520
  gap: 8px;
516
- background: rgba(17,24,39,0.9);
517
- padding: 6px 10px;
518
- border-radius: 8px;
519
- border: 1px solid var(--border);
520
- backdrop-filter: blur(8px);
521
+ background: rgba(15, 23, 42, 0.88);
522
+ padding: 6px 12px;
523
+ border-radius: 10px;
524
+ border: 1px solid rgba(255, 255, 255, 0.08);
525
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
526
+ backdrop-filter: blur(12px);
521
527
  z-index: 10;
522
528
  }
523
529
  .graph-toolbar button {
524
- background: var(--surface-hover);
525
- border: 1px solid var(--border);
526
- color: #fff;
527
- padding: 4px 8px;
528
- border-radius: 5px;
530
+ background: rgba(255, 255, 255, 0.06);
531
+ border: 1px solid rgba(255, 255, 255, 0.08);
532
+ color: #f1f5f9;
533
+ padding: 5px 10px;
534
+ border-radius: 6px;
529
535
  font-size: 11px;
536
+ font-weight: 500;
530
537
  cursor: pointer;
538
+ transition: all 0.15s ease;
539
+ display: inline-flex;
540
+ align-items: center;
541
+ gap: 4px;
542
+ }
543
+ .graph-toolbar button:hover {
544
+ background: rgba(255, 255, 255, 0.12);
545
+ border-color: rgba(255, 255, 255, 0.2);
531
546
  }
532
547
  .graph-toolbar button.active {
533
- background: var(--amber-dark);
548
+ background: #d97706;
549
+ border-color: #f59e0b;
550
+ color: #fff;
551
+ }
552
+ .graph-toolbar select, .graph-toolbar input {
553
+ background: rgba(11, 15, 25, 0.85);
554
+ border: 1px solid rgba(255, 255, 255, 0.1);
555
+ color: #f8fafc;
556
+ padding: 5px 10px;
557
+ border-radius: 6px;
558
+ font-size: 11px;
559
+ }
560
+ .graph-toolbar input:focus, .graph-toolbar select:focus {
561
+ outline: none;
562
+ border-color: var(--amber);
563
+ box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.2);
564
+ }
565
+ .btn-primary {
566
+ background: var(--amber-dark) !important;
567
+ color: #fff !important;
568
+ font-weight: 600 !important;
569
+ }
570
+ .btn-primary:hover {
571
+ background: var(--amber) !important;
572
+ }
573
+
574
+ /* Slide-over Inspector Sidebar */
575
+ #graph-inspector {
576
+ position: absolute;
577
+ top: 0;
578
+ right: 0;
579
+ width: 420px;
580
+ max-width: 90vw;
581
+ height: 100%;
582
+ background: var(--surface);
583
+ border-left: 1px solid var(--border);
584
+ box-shadow: -8px 0 24px rgba(0,0,0,0.5);
585
+ transform: translateX(100%);
586
+ transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
587
+ z-index: 25;
588
+ display: flex;
589
+ flex-direction: column;
590
+ }
591
+ #graph-inspector.open {
592
+ transform: translateX(0);
593
+ }
594
+ .inspector-header {
595
+ padding: 14px 18px;
596
+ border-bottom: 1px solid var(--border);
597
+ display: flex;
598
+ align-items: center;
599
+ justify-content: space-between;
600
+ gap: 12px;
601
+ background: var(--card-bg);
602
+ }
603
+ .inspector-title {
604
+ font-size: 15px;
605
+ font-weight: 700;
606
+ color: #fff;
607
+ overflow: hidden;
608
+ text-overflow: ellipsis;
609
+ white-space: nowrap;
610
+ }
611
+ .inspector-close {
612
+ background: transparent;
613
+ border: none;
614
+ color: var(--muted);
615
+ font-size: 20px;
616
+ cursor: pointer;
617
+ line-height: 1;
618
+ padding: 4px;
619
+ border-radius: 4px;
620
+ }
621
+ .inspector-close:hover {
622
+ color: #fff;
623
+ background: var(--surface-hover);
624
+ }
625
+ .inspector-body {
626
+ flex: 1;
627
+ overflow-y: auto;
628
+ padding: 16px 18px;
629
+ display: flex;
630
+ flex-direction: column;
631
+ gap: 16px;
632
+ }
633
+ .inspector-footer {
634
+ padding: 12px 18px;
635
+ border-top: 1px solid var(--border);
636
+ background: var(--card-bg);
637
+ display: flex;
638
+ gap: 8px;
639
+ justify-content: flex-end;
640
+ }
641
+ .inspector-section {
642
+ display: flex;
643
+ flex-direction: column;
644
+ gap: 6px;
645
+ }
646
+ .inspector-label {
647
+ font-size: 11px;
648
+ font-weight: 700;
649
+ text-transform: uppercase;
650
+ letter-spacing: 0.05em;
651
+ color: var(--muted);
652
+ display: flex;
653
+ align-items: center;
654
+ gap: 6px;
655
+ }
656
+ .inspector-code {
657
+ font-family: var(--mono);
658
+ font-size: 12px;
659
+ background: #090d16;
660
+ border: 1px solid var(--border);
661
+ border-radius: 6px;
662
+ padding: 10px 12px;
663
+ color: #38bdf8;
664
+ white-space: pre-wrap;
665
+ word-break: break-all;
666
+ max-height: 160px;
667
+ overflow-y: auto;
668
+ }
669
+ .inspector-desc {
670
+ font-size: 13px;
671
+ line-height: 1.5;
672
+ color: #e2e8f0;
673
+ background: rgba(245, 158, 11, 0.05);
674
+ border-left: 3px solid var(--amber);
675
+ border-radius: 0 6px 6px 0;
676
+ padding: 10px 12px;
677
+ }
678
+ .inspector-chip {
679
+ display: inline-flex;
680
+ align-items: center;
681
+ gap: 6px;
682
+ background: var(--card-bg);
683
+ border: 1px solid var(--border);
684
+ padding: 6px 10px;
685
+ border-radius: 6px;
686
+ font-size: 12px;
687
+ color: #cbd5e1;
688
+ cursor: pointer;
689
+ transition: all 0.15s;
690
+ text-decoration: none;
691
+ }
692
+ .inspector-chip:hover {
693
+ background: var(--surface-hover);
694
+ border-color: var(--amber);
695
+ color: #fff;
696
+ }
697
+ .inspector-chip .chip-badge {
698
+ font-size: 10px;
699
+ font-family: var(--mono);
700
+ background: rgba(255,255,255,0.08);
701
+ padding: 1px 5px;
702
+ border-radius: 4px;
703
+ }
704
+ .chip-list {
705
+ display: flex;
706
+ flex-direction: column;
707
+ gap: 6px;
708
+ max-height: 160px;
709
+ overflow-y: auto;
710
+ }
711
+
712
+ /* Modals */
713
+ .kuma-modal-backdrop {
714
+ position: fixed;
715
+ inset: 0;
716
+ background: rgba(0, 0, 0, 0.75);
717
+ backdrop-filter: blur(4px);
718
+ z-index: 100;
719
+ display: none;
720
+ align-items: center;
721
+ justify-content: center;
722
+ padding: 20px;
723
+ }
724
+ .kuma-modal-backdrop.open {
725
+ display: flex;
726
+ }
727
+ .kuma-modal {
728
+ background: var(--surface);
729
+ border: 1px solid var(--border);
730
+ border-radius: 12px;
731
+ width: 100%;
732
+ max-width: 520px;
733
+ box-shadow: 0 20px 40px rgba(0,0,0,0.6);
734
+ display: flex;
735
+ flex-direction: column;
736
+ overflow: hidden;
737
+ }
738
+ .kuma-modal-header {
739
+ padding: 14px 18px;
740
+ background: var(--card-bg);
741
+ border-bottom: 1px solid var(--border);
742
+ display: flex;
743
+ align-items: center;
744
+ justify-content: space-between;
745
+ }
746
+ .kuma-modal-body {
747
+ padding: 18px;
748
+ display: flex;
749
+ flex-direction: column;
750
+ gap: 14px;
751
+ }
752
+ .kuma-modal-footer {
753
+ padding: 14px 18px;
754
+ background: var(--card-bg);
755
+ border-top: 1px solid var(--border);
756
+ display: flex;
757
+ justify-content: flex-end;
758
+ gap: 10px;
759
+ }
760
+ .form-group {
761
+ display: flex;
762
+ flex-direction: column;
763
+ gap: 6px;
764
+ }
765
+ .form-group label {
766
+ font-size: 12px;
767
+ font-weight: 600;
768
+ color: var(--muted);
769
+ }
770
+ .form-control {
771
+ background: var(--bg);
772
+ border: 1px solid var(--border);
773
+ border-radius: 6px;
774
+ padding: 8px 12px;
775
+ color: #fff;
776
+ font-size: 13px;
777
+ font-family: inherit;
778
+ }
779
+ .form-control:focus {
780
+ outline: none;
781
+ border-color: var(--amber);
534
782
  }
535
783
 
536
784
  .empty-placeholder {
@@ -562,27 +810,24 @@ table.clean-table tr:hover td {
562
810
  </div>
563
811
 
564
812
  <nav class="nav-tabs">
565
- <button class="nav-tab active" data-view="gotchas">
566
- <i data-lucide="shield-alert" style="width:14px;height:14px"></i>
567
- Gotcha Shield
568
- <span class="tab-badge" id="badge-gotcha-count">0</span>
813
+ <button class="nav-tab active" data-view="graph">
814
+ <i data-lucide="network" style="width:14px;height:14px"></i>
815
+ Knowledge Graph
816
+ <span class="tab-badge" id="badge-graph-count">0</span>
569
817
  </button>
570
818
  <button class="nav-tab" data-view="flows">
571
819
  <i data-lucide="git-merge" style="width:14px;height:14px"></i>
572
- Sequence Flows
820
+ Architecture & Flows
573
821
  <span class="tab-badge" id="badge-flow-count">0</span>
574
822
  </button>
575
- <button class="nav-tab" data-view="workspace">
576
- <i data-lucide="box" style="width:14px;height:14px"></i>
577
- Workspace & Impact
823
+ <button class="nav-tab" data-view="gotchas">
824
+ <i data-lucide="shield-alert" style="width:14px;height:14px"></i>
825
+ Known Gotchas
826
+ <span class="tab-badge" id="badge-gotcha-count">0</span>
578
827
  </button>
579
828
  <button class="nav-tab" data-view="health">
580
829
  <i data-lucide="activity" style="width:14px;height:14px"></i>
581
- Efficiency & Health
582
- </button>
583
- <button class="nav-tab" data-view="graph">
584
- <i data-lucide="network" style="width:14px;height:14px"></i>
585
- Knowledge Graph
830
+ Health & Audit
586
831
  </button>
587
832
  </nav>
588
833
 
@@ -593,29 +838,51 @@ table.clean-table tr:hover td {
593
838
  </header>
594
839
 
595
840
  <main id="viewport">
596
- <!-- VIEW 1: GOTCHA SHIELD -->
597
- <section class="view-panel active" id="view-gotchas">
598
- <div class="view-header">
599
- <div>
600
- <div class="view-title"><i data-lucide="shield-alert" style="color:var(--amber)"></i> Known Code Gotchas</div>
601
- <div class="view-desc">Catalog of fragile legacy contracts, edge cases, and unexpected quirks injected right before edits.</div>
841
+ <!-- VIEW 1: KNOWLEDGE GRAPH (PRIMARY HERO) -->
842
+ <section class="view-panel active" id="view-graph" style="padding:0">
843
+ <div id="graph-viewport">
844
+ <div class="graph-toolbar">
845
+ <button onclick="fitGraph()"><i data-lucide="maximize" style="width:11px;height:11px"></i> Fit</button>
846
+ <button id="btn-toggle-physics" onclick="togglePhysics()">Physics: Off</button>
847
+ <select id="graph-filter-type" onchange="onGraphFilterChange(this.value)">
848
+ <option value="all">All Types</option>
849
+ <option value="file">Files</option>
850
+ <option value="function">Functions</option>
851
+ <option value="class">Classes</option>
852
+ <option value="interface">Interfaces</option>
853
+ <option value="feature_domain">Domains & Architecture</option>
854
+ <option value="gotcha">Gotchas</option>
855
+ <option value="decision">Decisions</option>
856
+ </select>
857
+ <input id="graph-search-input" type="text" placeholder="Jump to node..." oninput="onGraphSearch(this.value)" />
858
+ <button class="btn-primary" onclick="openCreateNodeModal()">+ New Node</button>
859
+ <button onclick="openConnectEdgeModal()">+ Connect Edge</button>
860
+ <span style="color:var(--muted);font-size:11px;align-self:center;margin-left:4px" id="graph-stats-label">0 nodes</span>
602
861
  </div>
603
- <input type="text" id="gotcha-search" class="search-input" placeholder="Search file, issue, or workaround..." oninput="filterGotchas()" />
604
- </div>
862
+ <div id="graph-canvas"></div>
605
863
 
606
- <div class="filter-pills" id="status-pills">
607
- <div class="pill active" data-status="all" onclick="setStatusFilter('all')">All Statuses</div>
608
- <div class="pill" data-status="active" onclick="setStatusFilter('active')">Active</div>
609
- <div class="pill" data-status="verified" onclick="setStatusFilter('verified')">Verified</div>
610
- <div class="pill" data-status="candidate" onclick="setStatusFilter('candidate')">Candidate</div>
611
- <div class="pill" data-status="resolved" onclick="setStatusFilter('resolved')">Resolved</div>
612
- <div class="pill" data-status="deprecated" onclick="setStatusFilter('deprecated')">Deprecated</div>
864
+ <!-- Slide-over Inspector Sidebar -->
865
+ <aside id="graph-inspector">
866
+ <div class="inspector-header">
867
+ <div style="display:flex;align-items:center;gap:8px;overflow:hidden">
868
+ <span id="inspector-badge" class="badge-project">NODE</span>
869
+ <span id="inspector-name" class="inspector-title">Node Name</span>
870
+ </div>
871
+ <button class="inspector-close" onclick="closeNodeInspector()">&times;</button>
872
+ </div>
873
+ <div class="inspector-body" id="inspector-body">
874
+ <div style="color:var(--muted);font-size:12px;text-align:center;padding:20px">Select a node in the graph to inspect details.</div>
875
+ </div>
876
+ <div class="inspector-footer" id="inspector-footer" style="display:none">
877
+ <button class="btn-action" id="inspector-connect-btn">+ Connect</button>
878
+ <button class="btn-action" id="inspector-edit-btn">Edit</button>
879
+ <button class="btn-action" style="color:var(--rose);border-color:rgba(244,63,94,0.3)" id="inspector-delete-btn">Delete</button>
880
+ </div>
881
+ </aside>
613
882
  </div>
614
-
615
- <div class="gotcha-grid" id="gotcha-cards-container"></div>
616
883
  </section>
617
884
 
618
- <!-- VIEW 2: SEQUENCE FLOWS -->
885
+ <!-- VIEW 2: ARCHITECTURE & FLOWS -->
619
886
  <section class="view-panel" id="view-flows">
620
887
  <div class="view-header">
621
888
  <div>
@@ -626,37 +893,34 @@ table.clean-table tr:hover td {
626
893
  <div class="flow-list" id="flow-cards-container"></div>
627
894
  </section>
628
895
 
629
- <!-- VIEW 3: WORKSPACE & IMPACT -->
630
- <section class="view-panel" id="view-workspace">
896
+ <!-- VIEW 3: GOTCHA SHIELD -->
897
+ <section class="view-panel" id="view-gotchas">
631
898
  <div class="view-header">
632
899
  <div>
633
- <div class="view-title"><i data-lucide="box" style="color:var(--cyan)"></i> Workspace & Blast Radius Topology</div>
634
- <div class="view-desc">Monorepo packages, package graph, and blast radius simulation.</div>
900
+ <div class="view-title"><i data-lucide="shield-alert" style="color:var(--amber)"></i> Known Code Gotchas</div>
901
+ <div class="view-desc">Catalog of fragile legacy contracts, edge cases, and unexpected quirks injected right before edits.</div>
635
902
  </div>
903
+ <input type="text" id="gotcha-search" class="search-input" placeholder="Search file, issue, or workaround..." oninput="filterGotchas()" />
636
904
  </div>
637
- <div class="workspace-grid">
638
- <div class="ws-card">
639
- <h3><i data-lucide="layers" style="width:16px;height:16px"></i> Repository Topology</h3>
640
- <div id="ws-topology-info" style="font-size:13px;color:#cbd5e1;line-height:1.6">Loading workspace information...</div>
641
- </div>
642
- <div class="ws-card">
643
- <h3><i data-lucide="target" style="width:16px;height:16px"></i> Blast Radius Checker</h3>
644
- <p style="font-size:12px;color:var(--muted);margin-bottom:12px">Check downstream impact and consumer packages for a target file:</p>
645
- <div style="display:flex;gap:8px;margin-bottom:12px">
646
- <input type="text" id="blast-input" class="search-input" placeholder="e.g. src/auth.ts or package/core" style="flex:1" />
647
- <button class="btn-action" onclick="simulateBlastRadius()">Check</button>
648
- </div>
649
- <div id="blast-result" style="font-size:12px;color:#cbd5e1;background:var(--bg);padding:12px;border-radius:6px;border:1px solid var(--border)">Enter a target path to inspect blast radius.</div>
650
- </div>
905
+
906
+ <div class="filter-pills" id="status-pills">
907
+ <div class="pill active" data-status="all" onclick="setStatusFilter('all')">All Statuses</div>
908
+ <div class="pill" data-status="active" onclick="setStatusFilter('active')">Active</div>
909
+ <div class="pill" data-status="verified" onclick="setStatusFilter('verified')">Verified</div>
910
+ <div class="pill" data-status="candidate" onclick="setStatusFilter('candidate')">Candidate</div>
911
+ <div class="pill" data-status="resolved" onclick="setStatusFilter('resolved')">Resolved</div>
912
+ <div class="pill" data-status="deprecated" onclick="setStatusFilter('deprecated')">Deprecated</div>
651
913
  </div>
914
+
915
+ <div class="gotcha-grid" id="gotcha-cards-container"></div>
652
916
  </section>
653
917
 
654
- <!-- VIEW 4: HEALTH & EFFICIENCY -->
918
+ <!-- VIEW 4: HEALTH & AUDIT -->
655
919
  <section class="view-panel" id="view-health">
656
920
  <div class="view-header">
657
921
  <div>
658
922
  <div class="view-title"><i data-lucide="activity" style="color:var(--emerald)"></i> Efficiency & Health Metrics</div>
659
- <div class="view-desc">Proving that the more coding agents use Kuma, the more efficient and regression-free development becomes.</div>
923
+ <div class="view-desc">Project metrics and stale reference detection ensuring memory reliability.</div>
660
924
  </div>
661
925
  </div>
662
926
 
@@ -684,19 +948,94 @@ table.clean-table tr:hover td {
684
948
  <div id="staleness-container" style="padding:16px;font-size:12px;color:var(--muted)">Checking staleness...</div>
685
949
  </div>
686
950
  </section>
951
+ </main>
687
952
 
688
- <!-- VIEW 5: KNOWLEDGE GRAPH -->
689
- <section class="view-panel" id="view-graph" style="padding:0">
690
- <div id="graph-viewport">
691
- <div class="graph-toolbar">
692
- <button onclick="fitGraph()"><i data-lucide="maximize" style="width:11px;height:11px"></i> Fit</button>
693
- <button id="btn-toggle-physics" onclick="togglePhysics()">Physics: Off</button>
694
- <span style="color:var(--muted);font-size:11px;align-self:center;margin-left:4px" id="graph-stats-label">0 nodes</span>
953
+ <!-- Modal: Create / Edit Node -->
954
+ <div class="kuma-modal-backdrop" id="modal-node">
955
+ <div class="kuma-modal">
956
+ <div class="kuma-modal-header">
957
+ <span style="font-weight:700;font-size:14px" id="modal-node-title">Create Knowledge Node</span>
958
+ <button class="inspector-close" onclick="closeModal('modal-node')">&times;</button>
959
+ </div>
960
+ <div class="kuma-modal-body">
961
+ <input type="hidden" id="node-form-id" />
962
+ <div class="form-group">
963
+ <label>Type</label>
964
+ <select id="node-form-type" class="form-control">
965
+ <option value="feature_domain">feature_domain (Domain / Architecture Boundary)</option>
966
+ <option value="arch_flow">arch_flow (Architecture Flow / Sequence)</option>
967
+ <option value="decision">decision (Architecture Decision)</option>
968
+ <option value="workflow">workflow (Business / Code Workflow)</option>
969
+ <option value="function">function</option>
970
+ <option value="class">class</option>
971
+ <option value="interface">interface</option>
972
+ <option value="file">file</option>
973
+ <option value="api_route">api_route</option>
974
+ <option value="gotcha">gotcha</option>
975
+ </select>
976
+ </div>
977
+ <div class="form-group">
978
+ <label>Name</label>
979
+ <input type="text" id="node-form-name" class="form-control" placeholder="e.g. AuthEngine, calculateMetrics, UserStore" />
980
+ </div>
981
+ <div class="form-group">
982
+ <label>File Path (optional)</label>
983
+ <input type="text" id="node-form-filepath" class="form-control" placeholder="e.g. src/engine/kumaDb.ts" />
984
+ </div>
985
+ <div class="form-group">
986
+ <label>Purpose / Description (for AI Agents and Developers)</label>
987
+ <textarea id="node-form-desc" class="form-control" rows="3" placeholder="Explain what this component or function does..."></textarea>
695
988
  </div>
696
- <div id="graph-canvas"></div>
697
989
  </div>
698
- </section>
699
- </main>
990
+ <div class="kuma-modal-footer">
991
+ <button class="btn-action" onclick="closeModal('modal-node')">Cancel</button>
992
+ <button class="btn-action btn-primary" onclick="submitNodeForm()">Save Node</button>
993
+ </div>
994
+ </div>
995
+ </div>
996
+
997
+ <!-- Modal: Connect Edge -->
998
+ <div class="kuma-modal-backdrop" id="modal-edge">
999
+ <div class="kuma-modal">
1000
+ <div class="kuma-modal-header">
1001
+ <span style="font-weight:700;font-size:14px">Connect Nodes (Create Edge)</span>
1002
+ <button class="inspector-close" onclick="closeModal('modal-edge')">&times;</button>
1003
+ </div>
1004
+ <div class="kuma-modal-body">
1005
+ <div class="form-group">
1006
+ <label>Source Node</label>
1007
+ <select id="edge-form-source" class="form-control"></select>
1008
+ </div>
1009
+ <div class="form-group">
1010
+ <label>Relationship Type</label>
1011
+ <select id="edge-form-type" class="form-control">
1012
+ <option value="calls">calls (Function A calls Function B)</option>
1013
+ <option value="imports">imports (Module A imports Module B)</option>
1014
+ <option value="defines">defines (File defines Function/Class)</option>
1015
+ <option value="tests">tests (Test verifies Code)</option>
1016
+ <option value="depends_on">depends_on (Component depends on Target)</option>
1017
+ <option value="affects">affects (Change here impacts Target)</option>
1018
+ <option value="implements">implements (Class implements Interface)</option>
1019
+ <option value="extends">extends (Class extends Superclass)</option>
1020
+ <option value="flows_through">flows_through (Architecture Flow)</option>
1021
+ <option value="uses">uses (Subsystem uses Component/Service)</option>
1022
+ <option value="produces">produces (Subsystem produces Artifact/Event)</option>
1023
+ <option value="validates">validates (Subsystem validates Contract)</option>
1024
+ <option value="routes_to">routes_to (Router routes to Subsystem)</option>
1025
+ <option value="configures">configures (Subsystem configures Target)</option>
1026
+ </select>
1027
+ </div>
1028
+ <div class="form-group">
1029
+ <label>Target Node</label>
1030
+ <select id="edge-form-target" class="form-control"></select>
1031
+ </div>
1032
+ </div>
1033
+ <div class="kuma-modal-footer">
1034
+ <button class="btn-action" onclick="closeModal('modal-edge')">Cancel</button>
1035
+ <button class="btn-action btn-primary" onclick="submitEdgeForm()">Create Connection</button>
1036
+ </div>
1037
+ </div>
1038
+ </div>
700
1039
 
701
1040
  <script>
702
1041
  var API = window.location.origin + "/api";
@@ -737,10 +1076,17 @@ async function fetchDashboard() {
737
1076
 
738
1077
  function renderAll(data) {
739
1078
  if (!data) return;
1079
+ if (document.getElementById("badge-graph-count")) {
1080
+ document.getElementById("badge-graph-count").textContent = data.nodes ? data.nodes.length : 0;
1081
+ }
740
1082
  try { renderGotchas(data.gotchas || []); } catch (e) { console.error("renderGotchas error:", e); }
741
1083
  try { renderFlows(data.flows || []); } catch (e) { console.error("renderFlows error:", e); }
742
- try { renderWorkspace(data.workspace, data.nodes || []); } catch (e) { console.error("renderWorkspace error:", e); }
743
1084
  try { renderHealth(data); } catch (e) { console.error("renderHealth error:", e); }
1085
+
1086
+ var graphTab = document.querySelector('.nav-tab[data-view="graph"]');
1087
+ if (graphTab && graphTab.classList.contains("active")) {
1088
+ setTimeout(initOrRefreshGraph, 20);
1089
+ }
744
1090
  if (window.lucide && window.lucide.createIcons) {
745
1091
  try { window.lucide.createIcons(); } catch {}
746
1092
  }
@@ -864,53 +1210,6 @@ function renderFlows(flows) {
864
1210
  if (window.lucide && window.lucide.createIcons) window.lucide.createIcons();
865
1211
  }
866
1212
 
867
- function renderWorkspace(ws, nodes) {
868
- var container = document.getElementById("ws-topology-info");
869
- if (!container) return;
870
- if (!ws) {
871
- container.innerHTML = "<div>Single package workspace.</div>";
872
- return;
873
- }
874
- var nodesLen = (nodes && nodes.length) ? nodes.length : 0;
875
- var pkgHtml = '';
876
- if (ws.packages && Array.isArray(ws.packages) && ws.packages.length > 0) {
877
- pkgHtml = `
878
- <div style="margin-top:10px"><strong>Monorepo Packages (${ws.packages.length}):</strong></div>
879
- <div style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px">
880
- ${ws.packages.map(function(p) {
881
- var name = typeof p === 'string' ? p : (p.name || p.path || '');
882
- return `<span class="badge-pkg" style="background:#1e293b;border:1px solid #334155;padding:2px 8px;border-radius:4px;font-size:11px">📦 ${escapeHtml(name)}</span>`;
883
- }).join("")}
884
- </div>
885
- `;
886
- }
887
- container.innerHTML = `
888
- <div style="margin-bottom:8px"><strong>Workspace Type:</strong> <span class="badge-project">${escapeHtml(ws.type || "standard")}</span></div>
889
- <div style="margin-bottom:8px"><strong>Project Root:</strong> <code>${escapeHtml(ws.name || "root")}</code> (v${escapeHtml(ws.version || "1.0.0")})</div>
890
- ${pkgHtml}
891
- <div style="margin-top:12px;color:var(--muted)">Total indexed knowledge entities: <strong>${nodesLen}</strong></div>
892
- `;
893
- }
894
-
895
- function simulateBlastRadius() {
896
- var val = (document.getElementById("blast-input").value || "").trim();
897
- var out = document.getElementById("blast-result");
898
- if (!val) {
899
- out.innerHTML = "⚠️ Please enter a target path or symbol.";
900
- return;
901
- }
902
- if (!currentData || !currentData.edges) {
903
- out.innerHTML = "No graph data available.";
904
- return;
905
- }
906
- var matchingEdges = currentData.edges.filter(e => String(e.source).includes(val) || String(e.target).includes(val));
907
- out.innerHTML = `
908
- <div style="font-weight:700;color:var(--amber-light);margin-bottom:6px">Blast Radius for "${escapeHtml(val)}":</div>
909
- <div>Found <strong>${matchingEdges.length}</strong> directly connected relationship(s).</div>
910
- <div style="margin-top:8px">${matchingEdges.slice(0, 5).map(e => `• <code>${escapeHtml(e.source)}</code> <strong>${escapeHtml(e.relation)}</strong> <code>${escapeHtml(e.target)}</code>`).join("<br>")}</div>
911
- `;
912
- }
913
-
914
1213
  function renderHealth(data) {
915
1214
  var eff = data.efficiency || {};
916
1215
  var kpiContainer = document.getElementById("kpi-container");
@@ -923,8 +1222,8 @@ function renderHealth(data) {
923
1222
  <div class="kpi-label">Known Gotchas Tracked</div>
924
1223
  </div>
925
1224
  <div class="kpi-card">
926
- <div class="kpi-num" style="color:var(--emerald)">${passedRate}</div>
927
- <div class="kpi-label">Auto-Verification Pass Rate</div>
1225
+ <div class="kpi-num" style="color:var(--emerald)">${data.nodes ? data.nodes.length : 0}</div>
1226
+ <div class="kpi-label">Topology Nodes Mapped</div>
928
1227
  </div>
929
1228
  <div class="kpi-card">
930
1229
  <div class="kpi-num" style="color:var(--amber)">~${savedMinutes}</div>
@@ -970,50 +1269,534 @@ function renderHealth(data) {
970
1269
  }
971
1270
  }
972
1271
 
1272
+ var selectedNodeId = null;
1273
+ var visNodesDataSet = null;
1274
+ var visEdgesDataSet = null;
1275
+
1276
+ var NODE_COLORS = {
1277
+ file: "#06b6d4",
1278
+ function: "#3b82f6",
1279
+ class: "#ec4899",
1280
+ interface: "#a855f7",
1281
+ type: "#8b5cf6",
1282
+ component: "#0ea5e9",
1283
+ api_route: "#14b8a6",
1284
+ test: "#f59e0b",
1285
+ arch_flow: "#6366f1",
1286
+ feature_domain: "#6366f1",
1287
+ feature: "#4f46e5",
1288
+ decision: "#10b981",
1289
+ gotcha: "#f97316",
1290
+ workflow: "#818cf8",
1291
+ cross_service_link: "#818cf8",
1292
+ research: "#22c55e",
1293
+ };
1294
+
1295
+ function getNodeColor(type) {
1296
+ return NODE_COLORS[type] || "#64748b";
1297
+ }
1298
+
973
1299
  function initOrRefreshGraph() {
974
1300
  if (!currentData || !currentData.nodes) return;
975
1301
  var container = document.getElementById("graph-canvas");
976
1302
  document.getElementById("graph-stats-label").textContent = `${currentData.nodes.length} nodes, ${currentData.edges ? currentData.edges.length : 0} edges`;
977
1303
 
978
- var visNodes = new vis.DataSet(currentData.nodes.slice(0, 300).map(function(n) {
979
- var color = "#64748b";
980
- if (n.type === "file") color = "#06b6d4";
981
- else if (n.type === "gotcha") color = "#f59e0b";
982
- else if (n.type === "arch_flow" || n.type === "feature_domain") color = "#6366f1";
983
- else if (n.type === "cross_service_link" || n.type === "workflow") color = "#818cf8";
984
- else if (n.type === "decision") color = "#10b981";
1304
+ var filterType = document.getElementById("graph-filter-type") ? document.getElementById("graph-filter-type").value : "all";
1305
+ var displayNodes = currentData.nodes;
1306
+ if (filterType !== "all") {
1307
+ if (filterType === "feature_domain") {
1308
+ displayNodes = displayNodes.filter(n => n.type === "feature_domain" || n.type === "arch_flow" || n.type === "decision");
1309
+ } else {
1310
+ displayNodes = displayNodes.filter(n => n.type === filterType);
1311
+ }
1312
+ }
1313
+
1314
+ visNodesDataSet = new vis.DataSet(displayNodes.slice(0, 400).map(function(n) {
1315
+ var color = getNodeColor(n.type);
1316
+ var label = n.name || n.id.split("::").pop();
1317
+
1318
+ let meta = {};
1319
+ try { meta = typeof n.metadata === "string" ? JSON.parse(n.metadata) : (n.metadata || {}); } catch {}
1320
+
1321
+ var title = `${label} (${n.type})${n.file_path ? '\nFile: ' + n.file_path : ''}`;
1322
+ if (meta.description) title += `\nPurpose: ${meta.description.split('\n')[0]}`;
1323
+ if (meta.signature) title += `\nSignature: ${meta.signature}`;
1324
+
1325
+ // Size hierarchy for clear visual scanning
1326
+ var size = 11;
1327
+ if (n.type === "file") size = 17;
1328
+ else if (n.type === "feature_domain" || n.type === "arch_flow") size = 15;
1329
+ else if (n.type === "class" || n.type === "interface") size = 13;
1330
+ else if (n.type === "function" || n.type === "component") size = 11;
1331
+ else if (n.type === "decision") size = 12;
1332
+ else if (n.type === "gotcha") size = 12;
1333
+
985
1334
  return {
986
1335
  id: n.id,
987
- label: n.name || n.id,
988
- shape: "box",
989
- color: { background: color, border: "#243049", highlight: "#fde68a" },
990
- font: { color: "#fff", size: 11, face: "ui-monospace, monospace" },
991
- margin: 8,
1336
+ label: label,
1337
+ title: title,
1338
+ shape: "dot",
1339
+ size: size,
1340
+ color: {
1341
+ background: color,
1342
+ border: "#0f172a",
1343
+ highlight: {
1344
+ background: color,
1345
+ border: "#ffffff",
1346
+ },
1347
+ hover: {
1348
+ background: color,
1349
+ border: "#e2e8f0",
1350
+ },
1351
+ },
1352
+ borderWidth: 2,
1353
+ borderWidthSelected: 3,
1354
+ font: {
1355
+ color: "#cbd5e1",
1356
+ size: 11,
1357
+ face: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
1358
+ strokeWidth: 2,
1359
+ strokeColor: "#060911",
1360
+ vadjust: 3, // Pushes title directly underneath the circular node!
1361
+ },
992
1362
  };
993
1363
  }));
994
1364
 
995
- var visEdges = new vis.DataSet((currentData.edges || []).slice(0, 500).map(function(e) {
1365
+ var displayedNodeIds = new Set(visNodesDataSet.getIds());
1366
+ var validEdges = (currentData.edges || []).filter(e => displayedNodeIds.has(e.source) && displayedNodeIds.has(e.target));
1367
+
1368
+ visEdgesDataSet = new vis.DataSet(validEdges.slice(0, 700).map(function(e) {
1369
+ const isConceptVerb = ["uses", "produces", "validates", "routes_to", "configures", "flows_through"].includes(e.type);
996
1370
  return {
997
1371
  from: e.source,
998
1372
  to: e.target,
999
- arrows: "to",
1000
- color: { color: "#334155", highlight: "#f59e0b" },
1001
- width: 1,
1373
+ arrows: {
1374
+ to: { enabled: true, scaleFactor: 0.5 }
1375
+ },
1376
+ title: e.relation || e.type,
1377
+ label: isConceptVerb ? e.type : undefined,
1378
+ font: isConceptVerb ? { size: 9, color: "#94a3b8", strokeWidth: 2, strokeColor: "#060911", align: "middle" } : undefined,
1379
+ color: {
1380
+ color: isConceptVerb ? "rgba(99, 102, 241, 0.45)" : "rgba(148, 163, 184, 0.22)",
1381
+ highlight: "#f59e0b",
1382
+ hover: "#38bdf8",
1383
+ },
1384
+ smooth: {
1385
+ type: "continuous",
1386
+ roundness: 0.15,
1387
+ },
1388
+ width: isConceptVerb ? 1.8 : 1.2,
1002
1389
  };
1003
1390
  }));
1004
1391
 
1005
1392
  var options = {
1006
1393
  physics: {
1007
1394
  enabled: physicsEnabled,
1008
- barnesHut: { gravitationalConstant: -3000, springLength: 80 },
1395
+ barnesHut: {
1396
+ gravitationalConstant: -2800,
1397
+ centralGravity: 0.2,
1398
+ springLength: 90,
1399
+ springConstant: 0.04,
1400
+ damping: 0.09,
1401
+ },
1402
+ stabilization: {
1403
+ iterations: 150,
1404
+ updateInterval: 25,
1405
+ },
1406
+ },
1407
+ interaction: {
1408
+ hover: true,
1409
+ tooltipDelay: 120,
1410
+ zoomView: true,
1411
+ dragView: true,
1412
+ selectConnectedEdges: true,
1009
1413
  },
1010
- interaction: { hover: true, tooltipDelay: 100 },
1011
1414
  };
1012
1415
 
1013
1416
  if (graphNetwork) {
1014
1417
  graphNetwork.destroy();
1015
1418
  }
1016
- graphNetwork = new vis.Network(container, { nodes: visNodes, edges: visEdges }, options);
1419
+ graphNetwork = new vis.Network(container, { nodes: visNodesDataSet, edges: visEdgesDataSet }, options);
1420
+
1421
+ graphNetwork.on("selectNode", function(params) {
1422
+ if (params.nodes && params.nodes.length > 0) {
1423
+ openNodeInspector(params.nodes[0]);
1424
+ }
1425
+ });
1426
+
1427
+ graphNetwork.on("click", function(params) {
1428
+ if (!params.nodes || params.nodes.length === 0) {
1429
+ // Clicked on empty space
1430
+ }
1431
+ });
1432
+ }
1433
+
1434
+ function onGraphFilterChange(val) {
1435
+ initOrRefreshGraph();
1436
+ }
1437
+
1438
+ function onGraphSearch(query) {
1439
+ if (!query || !graphNetwork || !visNodesDataSet) return;
1440
+ var q = query.trim().toLowerCase();
1441
+ var foundId = null;
1442
+ var allItems = visNodesDataSet.get();
1443
+ for (var i = 0; i < allItems.length; i++) {
1444
+ var item = allItems[i];
1445
+ if (item.label.toLowerCase().includes(q) || item.id.toLowerCase().includes(q)) {
1446
+ foundId = item.id;
1447
+ break;
1448
+ }
1449
+ }
1450
+ if (foundId) {
1451
+ selectAndFocusNode(foundId);
1452
+ }
1453
+ }
1454
+
1455
+ async function openNodeInspector(nodeId) {
1456
+ selectedNodeId = nodeId;
1457
+ var inspector = document.getElementById("graph-inspector");
1458
+ var badge = document.getElementById("inspector-badge");
1459
+ var nameEl = document.getElementById("inspector-name");
1460
+ var body = document.getElementById("inspector-body");
1461
+ var footer = document.getElementById("inspector-footer");
1462
+
1463
+ inspector.classList.add("open");
1464
+ badge.textContent = "...";
1465
+ badge.style.background = "rgba(255,255,255,0.1)";
1466
+ nameEl.textContent = "Loading node details...";
1467
+ body.innerHTML = '<div style="color:var(--muted);text-align:center;padding:30px">Fetching metadata & topology...</div>';
1468
+ footer.style.display = "none";
1469
+
1470
+ try {
1471
+ var res = await fetch(API + "/node/" + encodeURIComponent(nodeId)).then(r => r.json());
1472
+ if (!res || !res.node) {
1473
+ body.innerHTML = '<div style="color:var(--rose);padding:20px">Node not found.</div>';
1474
+ return;
1475
+ }
1476
+
1477
+ var node = res.node;
1478
+ var outgoing = res.outgoing || [];
1479
+ var incoming = res.incoming || [];
1480
+ var gotchas = res.gotchas || [];
1481
+
1482
+ var meta = {};
1483
+ try { meta = typeof node.metadata === "string" ? JSON.parse(node.metadata) : (node.metadata || {}); } catch {}
1484
+
1485
+ var typeColor = getNodeColor(node.type);
1486
+ badge.textContent = node.type;
1487
+ badge.style.background = typeColor;
1488
+ badge.style.color = "#fff";
1489
+ nameEl.textContent = node.name || node.id;
1490
+ nameEl.title = node.name || node.id;
1491
+
1492
+ var html = [];
1493
+
1494
+ // 1. Identity & File Path
1495
+ html.push(`
1496
+ <div class="inspector-section">
1497
+ <div class="inspector-label">Location</div>
1498
+ <div style="display:flex;align-items:center;gap:6px;background:var(--card-bg);padding:8px 10px;border-radius:6px;border:1px solid var(--border)">
1499
+ <span style="font-family:var(--mono);font-size:12px;color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escapeHtml(node.file_path || 'Virtual / In-memory entity')}</span>
1500
+ ${node.file_path ? `<button class="btn-action" style="padding:2px 6px;font-size:10px" onclick="copyText('${escapeAttr(node.file_path)}')">Copy</button>` : ''}
1501
+ </div>
1502
+ </div>
1503
+ `);
1504
+
1505
+ // 1b. Centrality & Blast Radius
1506
+ var centrality = res.centrality || { inDegree: incoming.length, outDegree: outgoing.length, isHub: incoming.length >= 4 };
1507
+ var hubTag = centrality.isHub
1508
+ ? '<span style="background:rgba(239,68,68,0.2);color:#f87171;padding:2px 8px;border-radius:4px;font-weight:700;font-size:11px">🔥 ARCHITECTURAL HUB</span>'
1509
+ : '<span style="background:rgba(59,130,246,0.15);color:#60a5fa;padding:2px 8px;border-radius:4px;font-size:11px">Modular Node</span>';
1510
+ html.push(`
1511
+ <div class="inspector-section">
1512
+ <div class="inspector-label">Centrality & Impact Rank</div>
1513
+ <div style="display:flex;align-items:center;justify-content:space-between;background:var(--card-bg);padding:8px 10px;border-radius:6px;border:1px solid var(--border)">
1514
+ <div>${hubTag}</div>
1515
+ <div style="font-size:11px;color:var(--muted)"><b>${centrality.inDegree}</b> incoming · <b>${centrality.outDegree}</b> dependencies</div>
1516
+ </div>
1517
+ </div>
1518
+ `);
1519
+
1520
+ // 2. Description / Purpose
1521
+ if (meta.description) {
1522
+ html.push(`
1523
+ <div class="inspector-section">
1524
+ <div class="inspector-label"><i data-lucide="book-open" style="width:12px;height:12px"></i> Purpose / Explanation</div>
1525
+ <div class="inspector-desc">${escapeHtml(meta.description)}</div>
1526
+ </div>
1527
+ `);
1528
+ } else {
1529
+ html.push(`
1530
+ <div class="inspector-section">
1531
+ <div class="inspector-label"><i data-lucide="book-open" style="width:12px;height:12px"></i> Purpose / Explanation</div>
1532
+ <div style="font-size:12px;color:var(--muted);font-style:italic">No description recorded. Click 'Edit' below to add one.</div>
1533
+ </div>
1534
+ `);
1535
+ }
1536
+
1537
+ // 3. Signature & Parameters
1538
+ if (meta.signature) {
1539
+ html.push(`
1540
+ <div class="inspector-section">
1541
+ <div class="inspector-label"><i data-lucide="code" style="width:12px;height:12px"></i> Type Signature</div>
1542
+ <pre class="inspector-code"><code>${escapeHtml(meta.signature)}</code></pre>
1543
+ </div>
1544
+ `);
1545
+ }
1546
+
1547
+ // 4. Methods / Members
1548
+ if (meta.methods && Array.isArray(meta.methods) && meta.methods.length > 0) {
1549
+ html.push(`
1550
+ <div class="inspector-section">
1551
+ <div class="inspector-label">Public Methods (${meta.methods.length})</div>
1552
+ <div class="chip-list">
1553
+ ${meta.methods.map(m => `<div class="inspector-chip" style="cursor:default"><span class="chip-badge">method</span> ${escapeHtml(m)}</div>`).join("")}
1554
+ </div>
1555
+ </div>
1556
+ `);
1557
+ } else if (meta.members && Array.isArray(meta.members) && meta.members.length > 0) {
1558
+ html.push(`
1559
+ <div class="inspector-section">
1560
+ <div class="inspector-label">Members (${meta.members.length})</div>
1561
+ <div class="chip-list">
1562
+ ${meta.members.map(m => `<div class="inspector-chip" style="cursor:default"><span class="chip-badge">member</span> ${escapeHtml(m)}</div>`).join("")}
1563
+ </div>
1564
+ </div>
1565
+ `);
1566
+ }
1567
+
1568
+ // 5. Incoming Connections (who references/calls/imports this)
1569
+ html.push(`
1570
+ <div class="inspector-section">
1571
+ <div class="inspector-label">Incoming Connections (${incoming.length})</div>
1572
+ <div class="chip-list">
1573
+ ${incoming.length === 0 ? '<div style="color:var(--muted);font-size:12px">None</div>' : ''}
1574
+ ${incoming.map(e => `
1575
+ <div class="inspector-chip" onclick="selectAndFocusNode('${escapeAttr(e.source)}')">
1576
+ <span class="chip-badge" style="color:var(--amber-light)">${escapeHtml(e.relation)}</span>
1577
+ <span>${escapeHtml(e.source_name || e.source)}</span>
1578
+ </div>
1579
+ `).join("")}
1580
+ </div>
1581
+ </div>
1582
+ `);
1583
+
1584
+ // 6. Outgoing Connections (what this calls/imports/defines)
1585
+ html.push(`
1586
+ <div class="inspector-section">
1587
+ <div class="inspector-label">Outgoing Connections (${outgoing.length})</div>
1588
+ <div class="chip-list">
1589
+ ${outgoing.length === 0 ? '<div style="color:var(--muted);font-size:12px">None</div>' : ''}
1590
+ ${outgoing.map(e => `
1591
+ <div class="inspector-chip" onclick="selectAndFocusNode('${escapeAttr(e.target)}')">
1592
+ <span class="chip-badge" style="color:var(--cyan)">${escapeHtml(e.relation)}</span>
1593
+ <span>${escapeHtml(e.target_name || e.target)}</span>
1594
+ </div>
1595
+ `).join("")}
1596
+ </div>
1597
+ </div>
1598
+ `);
1599
+
1600
+ // 7. Associated Gotchas
1601
+ if (gotchas.length > 0) {
1602
+ html.push(`
1603
+ <div class="inspector-section">
1604
+ <div class="inspector-label" style="color:var(--rose)">Associated Gotchas (${gotchas.length})</div>
1605
+ <div style="display:flex;flex-direction:column;gap:8px">
1606
+ ${gotchas.map(g => `
1607
+ <div style="background:rgba(244,63,94,0.08);border:1px solid rgba(244,63,94,0.25);border-radius:6px;padding:10px;font-size:12px">
1608
+ <div style="font-weight:700;color:var(--rose);margin-bottom:4px">⚠️ ${escapeHtml(g.description)}</div>
1609
+ ${g.workaround ? `<div style="color:var(--muted);font-size:11px">Workaround: ${escapeHtml(g.workaround)}</div>` : ''}
1610
+ </div>
1611
+ `).join("")}
1612
+ </div>
1613
+ </div>
1614
+ `);
1615
+ }
1616
+
1617
+ body.innerHTML = html.join("");
1618
+ footer.style.display = "flex";
1619
+
1620
+ // Setup action buttons
1621
+ document.getElementById("inspector-connect-btn").onclick = function() {
1622
+ openConnectEdgeModal(node.id);
1623
+ };
1624
+ document.getElementById("inspector-edit-btn").onclick = function() {
1625
+ openEditNodeModal(node);
1626
+ };
1627
+ document.getElementById("inspector-delete-btn").onclick = function() {
1628
+ deleteNode(node.id);
1629
+ };
1630
+
1631
+ if (window.lucide && window.lucide.createIcons) {
1632
+ try { window.lucide.createIcons(); } catch {}
1633
+ }
1634
+ } catch (err) {
1635
+ body.innerHTML = `<div style="color:var(--rose);padding:20px">Failed to load node: ${escapeHtml(String(err))}</div>`;
1636
+ }
1637
+ }
1638
+
1639
+ function closeNodeInspector() {
1640
+ document.getElementById("graph-inspector").classList.remove("open");
1641
+ selectedNodeId = null;
1642
+ }
1643
+
1644
+ function selectAndFocusNode(nodeId) {
1645
+ if (graphNetwork) {
1646
+ graphNetwork.selectNodes([nodeId]);
1647
+ graphNetwork.focus(nodeId, {
1648
+ scale: 1.2,
1649
+ animation: { duration: 400, easingFunction: "easeInOutQuad" },
1650
+ });
1651
+ }
1652
+ openNodeInspector(nodeId);
1653
+ }
1654
+
1655
+ function closeModal(modalId) {
1656
+ var el = document.getElementById(modalId);
1657
+ if (el) el.classList.remove("open");
1658
+ }
1659
+
1660
+ function openCreateNodeModal() {
1661
+ document.getElementById("modal-node-title").textContent = "Create Knowledge Node";
1662
+ document.getElementById("node-form-id").value = "";
1663
+ document.getElementById("node-form-type").value = "feature_domain";
1664
+ document.getElementById("node-form-name").value = "";
1665
+ document.getElementById("node-form-filepath").value = "";
1666
+ document.getElementById("node-form-desc").value = "";
1667
+ document.getElementById("modal-node").classList.add("open");
1668
+ }
1669
+
1670
+ function openEditNodeModal(node) {
1671
+ document.getElementById("modal-node-title").textContent = "Edit Knowledge Node";
1672
+ document.getElementById("node-form-id").value = node.id;
1673
+ document.getElementById("node-form-type").value = node.type;
1674
+ document.getElementById("node-form-name").value = node.name || "";
1675
+ document.getElementById("node-form-filepath").value = node.file_path || "";
1676
+ var meta = {};
1677
+ try { meta = typeof node.metadata === "string" ? JSON.parse(node.metadata) : (node.metadata || {}); } catch {}
1678
+ document.getElementById("node-form-desc").value = meta.description || "";
1679
+ document.getElementById("modal-node").classList.add("open");
1680
+ }
1681
+
1682
+ async function submitNodeForm() {
1683
+ var id = document.getElementById("node-form-id").value.trim();
1684
+ var type = document.getElementById("node-form-type").value;
1685
+ var name = document.getElementById("node-form-name").value.trim();
1686
+ var filePath = document.getElementById("node-form-filepath").value.trim();
1687
+ var desc = document.getElementById("node-form-desc").value.trim();
1688
+
1689
+ if (!name) {
1690
+ alert("Node name is required.");
1691
+ return;
1692
+ }
1693
+
1694
+ try {
1695
+ var res = await fetch(API + "/node", {
1696
+ method: "POST",
1697
+ headers: { "Content-Type": "application/json" },
1698
+ body: JSON.stringify({
1699
+ id: id || undefined,
1700
+ type: type,
1701
+ name: name,
1702
+ file_path: filePath || undefined,
1703
+ description: desc || undefined,
1704
+ }),
1705
+ }).then(r => r.json());
1706
+
1707
+ if (res.ok) {
1708
+ showToast("Knowledge node saved successfully!");
1709
+ closeModal("modal-node");
1710
+ await fetchDashboard();
1711
+ if (res.node && res.node.id) {
1712
+ selectAndFocusNode(res.node.id);
1713
+ }
1714
+ } else {
1715
+ alert("Failed to save node: " + (res.error || "Unknown error"));
1716
+ }
1717
+ } catch (err) {
1718
+ alert("Error saving node: " + err);
1719
+ }
1720
+ }
1721
+
1722
+ function openConnectEdgeModal(prefillSource) {
1723
+ var sourceSelect = document.getElementById("edge-form-source");
1724
+ var targetSelect = document.getElementById("edge-form-target");
1725
+ sourceSelect.innerHTML = "";
1726
+ targetSelect.innerHTML = "";
1727
+
1728
+ if (currentData && currentData.nodes) {
1729
+ currentData.nodes.forEach(function(n) {
1730
+ var opt1 = document.createElement("option");
1731
+ opt1.value = n.id;
1732
+ opt1.textContent = `[${n.type}] ${n.name || n.id}`;
1733
+ sourceSelect.appendChild(opt1);
1734
+
1735
+ var opt2 = document.createElement("option");
1736
+ opt2.value = n.id;
1737
+ opt2.textContent = `[${n.type}] ${n.name || n.id}`;
1738
+ targetSelect.appendChild(opt2);
1739
+ });
1740
+ }
1741
+
1742
+ if (prefillSource) {
1743
+ sourceSelect.value = prefillSource;
1744
+ }
1745
+ document.getElementById("modal-edge").classList.add("open");
1746
+ }
1747
+
1748
+ async function submitEdgeForm() {
1749
+ var source = document.getElementById("edge-form-source").value;
1750
+ var target = document.getElementById("edge-form-target").value;
1751
+ var type = document.getElementById("edge-form-type").value;
1752
+
1753
+ if (!source || !target) {
1754
+ alert("Please select both source and target nodes.");
1755
+ return;
1756
+ }
1757
+ if (source === target) {
1758
+ alert("Source and target cannot be the same node.");
1759
+ return;
1760
+ }
1761
+
1762
+ try {
1763
+ var res = await fetch(API + "/edge", {
1764
+ method: "POST",
1765
+ headers: { "Content-Type": "application/json" },
1766
+ body: JSON.stringify({ source: source, target: target, type: type }),
1767
+ }).then(r => r.json());
1768
+
1769
+ if (res.ok) {
1770
+ showToast("Connection created successfully!");
1771
+ closeModal("modal-edge");
1772
+ await fetchDashboard();
1773
+ selectAndFocusNode(source);
1774
+ } else {
1775
+ alert("Failed to create connection: " + (res.error || "Unknown error"));
1776
+ }
1777
+ } catch (err) {
1778
+ alert("Error creating connection: " + err);
1779
+ }
1780
+ }
1781
+
1782
+ async function deleteNode(nodeId) {
1783
+ if (!confirm(`Are you sure you want to delete node "${nodeId}" and its connections?`)) return;
1784
+
1785
+ try {
1786
+ var res = await fetch(API + "/node/" + encodeURIComponent(nodeId), {
1787
+ method: "DELETE",
1788
+ }).then(r => r.json());
1789
+
1790
+ if (res.ok) {
1791
+ showToast("Node deleted successfully!");
1792
+ closeNodeInspector();
1793
+ await fetchDashboard();
1794
+ } else {
1795
+ alert("Failed to delete node: " + (res.error || "Unknown error"));
1796
+ }
1797
+ } catch (err) {
1798
+ alert("Error deleting node: " + err);
1799
+ }
1017
1800
  }
1018
1801
 
1019
1802
  function togglePhysics() {