kyp-mem 0.4.0 → 0.4.2

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.
@@ -34,16 +34,16 @@
34
34
 
35
35
  --text-primary: #d8d5cf;
36
36
  --text-secondary: #807b73;
37
- --text-muted: #44413a;
38
- --border: #1c1a17;
39
- --border-subtle: #16150f;
37
+ --text-muted: #55514a;
38
+ --border: rgba(255,255,255,0.06);
39
+ --border-subtle: rgba(255,255,255,0.03);
40
40
 
41
41
  --font: 'Inter', -apple-system, sans-serif;
42
42
  --font-mono: 'JetBrains Mono', monospace;
43
43
 
44
44
  --radius-sm: 4px;
45
- --radius: 6px;
46
- --radius-lg: 10px;
45
+ --radius: 8px;
46
+ --radius-lg: 12px;
47
47
  }
48
48
 
49
49
  * { margin: 0; padding: 0; box-sizing: border-box; }
@@ -51,6 +51,7 @@
51
51
  body {
52
52
  font-family: var(--font);
53
53
  background: var(--bg-void);
54
+ background-image: radial-gradient(circle at 50% 0%, var(--bg-secondary) 0%, var(--bg-void) 70%);
54
55
  color: var(--text-primary);
55
56
  height: 100vh;
56
57
  overflow: hidden;
@@ -62,8 +63,8 @@ body::before {
62
63
  position: fixed;
63
64
  inset: 0;
64
65
  background-image:
65
- linear-gradient(rgba(217,119,87,0.015) 1px, transparent 1px),
66
- linear-gradient(90deg, rgba(217,119,87,0.015) 1px, transparent 1px);
66
+ linear-gradient(rgba(217,119,87,0.012) 1px, transparent 1px),
67
+ linear-gradient(90deg, rgba(217,119,87,0.012) 1px, transparent 1px);
67
68
  background-size: 48px 48px;
68
69
  pointer-events: none;
69
70
  z-index: 0;
@@ -113,7 +114,9 @@ body.resizing .resize-handle { pointer-events: auto !important; }
113
114
  /* ============ HEADER ============ */
114
115
  .header {
115
116
  grid-column: 1 / -1;
116
- background: var(--bg-tertiary);
117
+ background: rgba(8,8,10,0.6);
118
+ backdrop-filter: blur(12px);
119
+ -webkit-backdrop-filter: blur(12px);
117
120
  border-bottom: 1px solid var(--border);
118
121
  display: flex;
119
122
  align-items: center;
@@ -309,24 +312,27 @@ body.resizing .resize-handle { pointer-events: auto !important; }
309
312
  .quick-switcher-overlay {
310
313
  position: fixed;
311
314
  inset: 0;
312
- background: rgba(6,6,12,0.7);
315
+ background: rgba(6,6,12,0.6);
313
316
  z-index: 200;
314
317
  display: none;
315
318
  align-items: flex-start;
316
319
  justify-content: center;
317
320
  padding-top: 15vh;
318
- backdrop-filter: blur(4px);
321
+ backdrop-filter: blur(12px);
322
+ -webkit-backdrop-filter: blur(12px);
319
323
  }
320
324
 
321
325
  .quick-switcher-overlay.active { display: flex; }
322
326
 
323
327
  .quick-switcher {
324
328
  width: 480px;
325
- background: var(--bg-card);
329
+ background: rgba(17,17,22,0.85);
326
330
  border: 1px solid var(--border);
327
331
  border-radius: var(--radius-lg);
328
- box-shadow: 0 20px 60px rgba(0,0,0,0.6), 0 0 1px var(--neon-cyan);
332
+ box-shadow: 0 24px 64px rgba(0,0,0,0.8), 0 0 0 1px rgba(217,119,87,0.1);
329
333
  overflow: hidden;
334
+ backdrop-filter: blur(20px);
335
+ -webkit-backdrop-filter: blur(20px);
330
336
  }
331
337
 
332
338
  .quick-switcher input {
@@ -657,119 +663,132 @@ body.resizing .resize-handle { pointer-events: auto !important; }
657
663
 
658
664
  /* ============ MARKDOWN ============ */
659
665
  .md-body h1 {
660
- font-size: 26px;
666
+ font-size: 28px;
661
667
  font-weight: 700;
662
- margin: 0 0 24px;
668
+ margin: 0 0 28px;
663
669
  color: var(--text-primary);
664
- letter-spacing: -0.3px;
670
+ letter-spacing: -0.5px;
671
+ text-shadow: 0 0 15px rgba(255,255,255,0.05);
665
672
  }
666
673
 
667
674
  .md-body h2 {
668
- font-size: 18px;
675
+ font-size: 20px;
669
676
  font-weight: 600;
670
- margin: 36px 0 12px;
677
+ margin: 40px 0 16px;
671
678
  color: var(--text-primary);
672
- padding-bottom: 8px;
673
- border-bottom: 1px solid var(--border-subtle);
679
+ padding-bottom: 10px;
680
+ border-bottom: 1px solid var(--border);
674
681
  }
675
682
 
676
683
  .md-body h3 {
677
- font-size: 15px;
684
+ font-size: 16px;
678
685
  font-weight: 600;
679
- margin: 28px 0 8px;
686
+ margin: 32px 0 12px;
680
687
  color: var(--neon-purple);
688
+ text-shadow: 0 0 8px rgba(168,139,250,0.2);
681
689
  }
682
690
 
683
691
  .md-body p {
684
- line-height: 1.8;
685
- margin: 8px 0;
692
+ line-height: 1.9;
693
+ margin: 12px 0;
686
694
  color: var(--text-secondary);
687
- font-size: 13.5px;
695
+ font-size: 14px;
688
696
  }
689
697
 
690
- .md-body ul, .md-body ol { padding-left: 24px; margin: 8px 0; }
698
+ .md-body ul, .md-body ol { padding-left: 24px; margin: 12px 0; }
691
699
 
692
700
  .md-body li {
693
- line-height: 1.8;
701
+ line-height: 1.9;
694
702
  color: var(--text-secondary);
695
- margin: 2px 0;
696
- font-size: 13.5px;
703
+ margin: 4px 0;
704
+ font-size: 14px;
697
705
  }
698
706
 
699
707
  .md-body li::marker { color: var(--text-muted); }
700
708
 
701
- .md-body a { color: var(--neon-blue); text-decoration: none; }
702
- .md-body a:hover { text-decoration: underline; }
709
+ .md-body a { color: var(--neon-blue); text-decoration: none; transition: color 0.2s; }
710
+ .md-body a:hover { color: var(--neon-cyan); text-decoration: underline; }
703
711
 
704
712
  .md-body strong { color: var(--text-primary); font-weight: 600; }
705
713
 
706
714
  .md-body code {
707
- background: var(--bg-card);
708
- padding: 1.5px 6px;
709
- border-radius: 3px;
715
+ background: var(--bg-hover);
716
+ padding: 3px 6px;
717
+ border-radius: 4px;
710
718
  font-family: var(--font-mono);
711
- font-size: 12px;
719
+ font-size: 12.5px;
712
720
  color: var(--neon-orange);
713
- border: 1px solid var(--border-subtle);
721
+ border: 1px solid var(--border);
714
722
  }
715
723
 
716
724
  .md-body pre {
717
- background: var(--bg-tertiary);
725
+ background: rgba(8,8,10,0.4);
718
726
  border: 1px solid var(--border);
719
- border-radius: var(--radius);
720
- padding: 16px 18px;
727
+ border-radius: var(--radius-lg);
728
+ padding: 20px;
721
729
  overflow-x: auto;
722
- margin: 16px 0;
730
+ margin: 20px 0;
731
+ box-shadow: inset 0 2px 10px rgba(0,0,0,0.2);
723
732
  }
724
733
 
725
734
  .md-body pre code {
726
735
  background: none;
727
736
  padding: 0;
728
737
  border: none;
729
- color: var(--text-primary);
730
- font-size: 12px;
731
- line-height: 1.7;
738
+ color: #c9c7c2;
739
+ font-size: 12.5px;
740
+ line-height: 1.8;
732
741
  }
733
742
 
734
743
  .md-body table {
735
744
  width: 100%;
736
- border-collapse: collapse;
737
- margin: 16px 0;
738
- font-size: 12.5px;
745
+ border-collapse: separate;
746
+ border-spacing: 0;
747
+ margin: 24px 0;
748
+ font-size: 13px;
749
+ border: 1px solid var(--border);
750
+ border-radius: var(--radius);
751
+ overflow: hidden;
739
752
  }
740
753
 
741
754
  .md-body th {
742
755
  text-align: left;
743
- padding: 8px 12px;
756
+ padding: 12px 16px;
744
757
  border-bottom: 1px solid var(--border);
745
- color: var(--text-secondary);
758
+ background: rgba(255,255,255,0.02);
759
+ color: var(--text-primary);
746
760
  font-family: var(--font-mono);
747
761
  font-weight: 600;
748
- font-size: 10px;
762
+ font-size: 11px;
749
763
  text-transform: uppercase;
750
764
  letter-spacing: 0.5px;
751
765
  }
752
766
 
753
767
  .md-body td {
754
- padding: 8px 12px;
755
- border-bottom: 1px solid var(--border-subtle);
768
+ padding: 12px 16px;
769
+ border-bottom: 1px solid var(--border);
756
770
  color: var(--text-secondary);
771
+ transition: background 0.15s;
757
772
  }
758
773
 
759
- .md-body tr:hover td { background: var(--bg-hover); }
774
+ .md-body tr:last-child td { border-bottom: none; }
775
+ .md-body tr:hover td { background: var(--bg-hover); color: var(--text-primary); }
760
776
 
761
777
  .md-body blockquote {
762
- border-left: 2px solid var(--neon-purple);
763
- padding-left: 16px;
764
- margin: 16px 0;
765
- color: var(--text-muted);
778
+ border-left: 3px solid var(--neon-purple);
779
+ padding: 12px 20px;
780
+ margin: 20px 0;
781
+ color: var(--text-secondary);
782
+ background: linear-gradient(90deg, rgba(168,139,250,0.05), transparent);
783
+ border-radius: 0 var(--radius) var(--radius) 0;
784
+ font-style: italic;
766
785
  }
767
786
 
768
787
  .md-body hr {
769
788
  border: none;
770
789
  height: 1px;
771
- background: var(--border);
772
- margin: 28px 0;
790
+ background: linear-gradient(90deg, transparent, var(--border), transparent);
791
+ margin: 36px 0;
773
792
  }
774
793
 
775
794
  .wikilink {
@@ -840,15 +859,40 @@ body.resizing .resize-handle { pointer-events: auto !important; }
840
859
 
841
860
  .rp-section-title {
842
861
  font-family: var(--font-mono);
843
- font-size: 9px;
862
+ font-size: 10px;
844
863
  font-weight: 600;
845
864
  text-transform: uppercase;
846
865
  letter-spacing: 1.5px;
847
- color: var(--text-muted);
866
+ color: var(--text-secondary);
848
867
  margin-bottom: 8px;
849
868
  display: flex;
850
869
  align-items: center;
851
870
  gap: 6px;
871
+ cursor: pointer;
872
+ padding: 4px 6px;
873
+ border-radius: var(--radius-sm);
874
+ transition: background 0.15s;
875
+ }
876
+
877
+ .rp-section-title:hover {
878
+ background: var(--bg-hover);
879
+ color: var(--text-primary);
880
+ }
881
+
882
+ .rp-section-title .arrow {
883
+ margin-left: auto;
884
+ font-size: 8px;
885
+ color: var(--text-muted);
886
+ transition: transform 0.2s;
887
+ transform: rotate(90deg);
888
+ }
889
+
890
+ .rp-section.collapsed .rp-section-title .arrow {
891
+ transform: rotate(0deg);
892
+ }
893
+
894
+ .rp-section.collapsed .rp-section-body {
895
+ display: none;
852
896
  }
853
897
 
854
898
  .rp-section-title .rp-count {
@@ -915,16 +959,37 @@ body.resizing .resize-handle { pointer-events: auto !important; }
915
959
  .outline-item.h3 { padding-left: 20px; font-size: 10px; color: var(--text-muted); }
916
960
 
917
961
  /* ============ GRAPH ============ */
918
- #graph-section { margin-bottom: 12px; }
962
+ #graph-section { margin-bottom: 12px; transition: all 0.3s; }
919
963
  #graph-section.hidden { display: none; }
920
964
 
965
+ #graph-section.maximized {
966
+ position: fixed;
967
+ inset: 40px;
968
+ z-index: 250;
969
+ background: rgba(17,17,22,0.95);
970
+ border: 1px solid var(--border);
971
+ border-radius: var(--radius-lg);
972
+ padding: 20px;
973
+ display: flex;
974
+ flex-direction: column;
975
+ box-shadow: 0 24px 64px rgba(0,0,0,0.8), 0 0 0 1px rgba(217,119,87,0.3);
976
+ backdrop-filter: blur(20px);
977
+ -webkit-backdrop-filter: blur(20px);
978
+ }
979
+
921
980
  #graph-container {
922
981
  width: 100%;
923
982
  height: 200px;
924
- border: 1px solid var(--border-subtle);
983
+ border: 1px solid var(--border);
925
984
  border-radius: var(--radius);
926
985
  overflow: hidden;
927
- background: var(--bg-tertiary);
986
+ background: transparent;
987
+ }
988
+
989
+ #graph-section.maximized #graph-container {
990
+ flex: 1;
991
+ height: auto !important;
992
+ border: none;
928
993
  }
929
994
 
930
995
  #graph-container svg { width: 100%; height: 100%; }
@@ -933,30 +998,39 @@ body.resizing .resize-handle { pointer-events: auto !important; }
933
998
 
934
999
  .graph-node circle {
935
1000
  fill: var(--neon-purple);
936
- filter: drop-shadow(0 0 2px rgba(168,139,250,0.4));
937
- transition: all 0.2s;
1001
+ filter: drop-shadow(0 0 3px rgba(168,139,250,0.5));
1002
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
938
1003
  }
939
1004
 
940
1005
  .graph-node:hover circle {
941
1006
  fill: var(--neon-cyan);
942
- filter: drop-shadow(0 0 4px rgba(217,119,87,0.5));
1007
+ r: 8;
1008
+ filter: drop-shadow(0 0 8px rgba(217,119,87,0.7));
943
1009
  }
944
1010
 
945
1011
  .graph-node.active circle {
946
1012
  fill: var(--neon-cyan);
947
- r: 6;
948
- filter: drop-shadow(0 0 6px rgba(217,119,87,0.5));
1013
+ filter: drop-shadow(0 0 10px rgba(217,119,87,0.9));
949
1014
  }
950
1015
 
951
1016
  .graph-node text {
952
1017
  fill: var(--text-muted);
953
1018
  font-family: var(--font-mono);
954
- font-size: 8px;
1019
+ font-size: 11px;
1020
+ pointer-events: none;
1021
+ transition: all 0.2s;
1022
+ text-shadow: 0 0 2px var(--bg-primary), 0 0 4px var(--bg-primary);
1023
+ }
1024
+
1025
+ .graph-node:hover text, .graph-node.active text {
1026
+ fill: var(--text-primary);
1027
+ font-size: 13px;
955
1028
  }
956
1029
 
957
1030
  .graph-link {
958
- stroke: rgba(168,139,250,0.15);
959
- stroke-width: 1;
1031
+ stroke: var(--neon-purple);
1032
+ stroke-width: 1.5;
1033
+ transition: opacity 0.3s;
960
1034
  }
961
1035
 
962
1036
  .graph-size-controls {
@@ -1011,10 +1085,186 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1011
1085
  background: var(--neon-cyan);
1012
1086
  }
1013
1087
 
1014
- /* ============ SESSIONS ============ */
1015
- .session-folder-icon { color: var(--neon-green) !important; }
1088
+ /* ============ SESSIONS PILLAR ============ */
1089
+ .pillar-divider {
1090
+ height: 1px;
1091
+ margin: 4px 12px;
1092
+ background: linear-gradient(90deg, transparent, var(--border), transparent);
1093
+ }
1094
+
1095
+ .pillar-label {
1096
+ font-family: var(--font-mono);
1097
+ font-size: 8px;
1098
+ font-weight: 700;
1099
+ text-transform: uppercase;
1100
+ letter-spacing: 2px;
1101
+ color: var(--text-muted);
1102
+ padding: 10px 18px 4px;
1103
+ display: flex;
1104
+ align-items: center;
1105
+ justify-content: space-between;
1106
+ user-select: none;
1107
+ }
1108
+
1109
+ .pillar-label .pillar-accent {
1110
+ width: 6px;
1111
+ height: 6px;
1112
+ border-radius: 50%;
1113
+ display: inline-block;
1114
+ margin-right: 6px;
1115
+ }
1116
+
1117
+ .pillar-label .pillar-accent.sessions { background: var(--neon-green); box-shadow: 0 0 6px var(--neon-green); }
1118
+ .pillar-label .pillar-accent.projects { background: var(--neon-cyan); box-shadow: 0 0 6px var(--neon-cyan); }
1119
+
1120
+ .pillar-label .pillar-action {
1121
+ font-size: 9px;
1122
+ font-weight: 500;
1123
+ color: var(--text-muted);
1124
+ cursor: pointer;
1125
+ padding: 1px 6px;
1126
+ border-radius: 3px;
1127
+ border: 1px solid transparent;
1128
+ transition: all 0.15s;
1129
+ letter-spacing: 0.5px;
1130
+ }
1131
+
1132
+ .pillar-label .pillar-action:hover {
1133
+ color: var(--neon-green);
1134
+ border-color: rgba(91,185,140,0.3);
1135
+ }
1136
+
1137
+ .session-search-box {
1138
+ padding: 6px 12px 4px;
1139
+ }
1140
+
1141
+ .session-search-box input {
1142
+ width: 100%;
1143
+ background: rgba(8,8,10,0.4);
1144
+ border: 1px solid var(--border);
1145
+ color: var(--text-primary);
1146
+ font-family: var(--font-mono);
1147
+ font-size: 10px;
1148
+ padding: 5px 10px 5px 24px;
1149
+ border-radius: var(--radius);
1150
+ outline: none;
1151
+ transition: all 0.2s;
1152
+ }
1153
+
1154
+ .session-search-box input::placeholder { color: var(--text-muted); }
1155
+
1156
+ .session-search-box input:focus {
1157
+ border-color: rgba(91,185,140,0.4);
1158
+ box-shadow: 0 0 8px rgba(91,185,140,0.08);
1159
+ }
1160
+
1161
+ .session-search-box {
1162
+ position: relative;
1163
+ }
1164
+
1165
+ .session-search-box .ss-icon {
1166
+ position: absolute;
1167
+ left: 20px;
1168
+ top: 50%;
1169
+ transform: translateY(-50%);
1170
+ color: var(--text-muted);
1171
+ font-size: 10px;
1172
+ pointer-events: none;
1173
+ }
1174
+
1175
+ .session-search-results {
1176
+ padding: 0 8px;
1177
+ max-height: 180px;
1178
+ overflow-y: auto;
1179
+ }
1180
+
1181
+ .session-search-results:empty { display: none; }
1182
+
1183
+ .ss-result {
1184
+ padding: 5px 10px;
1185
+ border-radius: var(--radius-sm);
1186
+ cursor: pointer;
1187
+ margin: 1px 0;
1188
+ transition: background 0.1s;
1189
+ }
1190
+
1191
+ .ss-result:hover { background: var(--bg-hover); }
1192
+
1193
+ .ss-result .ss-title {
1194
+ font-size: 11px;
1195
+ color: var(--neon-green);
1196
+ font-weight: 500;
1197
+ }
1198
+
1199
+ .ss-result .ss-meta {
1200
+ font-family: var(--font-mono);
1201
+ font-size: 9px;
1202
+ color: var(--text-muted);
1203
+ margin-top: 1px;
1204
+ }
1205
+
1206
+ .ss-result .ss-snippet {
1207
+ font-size: 10px;
1208
+ color: var(--text-secondary);
1209
+ margin-top: 2px;
1210
+ overflow: hidden;
1211
+ text-overflow: ellipsis;
1212
+ white-space: nowrap;
1213
+ }
1214
+
1215
+ .session-project-group {
1216
+ padding: 0 8px;
1217
+ }
1218
+
1219
+ .session-project-header {
1220
+ display: flex;
1221
+ align-items: center;
1222
+ padding: 5px 10px;
1223
+ border-radius: var(--radius-sm);
1224
+ cursor: pointer;
1225
+ gap: 6px;
1226
+ user-select: none;
1227
+ transition: background 0.1s;
1228
+ margin: 1px 0;
1229
+ }
1230
+
1231
+ .session-project-header:hover { background: var(--bg-hover); }
1232
+
1233
+ .session-project-header .sp-arrow {
1234
+ width: 12px;
1235
+ font-size: 8px;
1236
+ color: var(--text-muted);
1237
+ text-align: center;
1238
+ flex-shrink: 0;
1239
+ transition: transform 0.15s;
1240
+ }
1241
+
1242
+ .session-project-header .sp-arrow.open { transform: rotate(90deg); }
1243
+
1244
+ .session-project-header .sp-icon {
1245
+ font-size: 11px;
1246
+ color: var(--neon-green);
1247
+ flex-shrink: 0;
1248
+ opacity: 0.7;
1249
+ }
1250
+
1251
+ .session-project-header .sp-name {
1252
+ font-size: 12px;
1253
+ color: var(--text-primary);
1254
+ font-weight: 500;
1255
+ flex: 1;
1256
+ }
1257
+
1258
+ .session-project-header .sp-count {
1259
+ font-family: var(--font-mono);
1260
+ font-size: 9px;
1261
+ color: var(--text-muted);
1262
+ background: rgba(91,185,140,0.06);
1263
+ padding: 1px 5px;
1264
+ border-radius: 3px;
1265
+ }
1016
1266
 
1017
- .session-add-btn {
1267
+ .session-project-header .sp-add {
1018
1268
  background: transparent;
1019
1269
  border: 1px solid var(--border);
1020
1270
  color: var(--text-muted);
@@ -1024,7 +1274,6 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1024
1274
  height: 16px;
1025
1275
  border-radius: 3px;
1026
1276
  cursor: pointer;
1027
- margin-left: auto;
1028
1277
  display: none;
1029
1278
  align-items: center;
1030
1279
  justify-content: center;
@@ -1033,13 +1282,62 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1033
1282
  line-height: 1;
1034
1283
  }
1035
1284
 
1036
- .tree-item:hover .session-add-btn { display: flex; }
1285
+ .session-project-header:hover .sp-add { display: flex; }
1286
+ .session-project-header .sp-add:hover { border-color: var(--neon-green); color: var(--neon-green); }
1037
1287
 
1038
- .session-add-btn:hover {
1039
- border-color: var(--neon-green);
1288
+ .session-list {
1289
+ padding-left: 12px;
1290
+ overflow: hidden;
1291
+ }
1292
+
1293
+ .session-list.collapsed { display: none; }
1294
+
1295
+ .session-item {
1296
+ display: flex;
1297
+ align-items: center;
1298
+ padding: 3px 10px;
1299
+ border-radius: var(--radius-sm);
1300
+ cursor: pointer;
1301
+ font-size: 11px;
1302
+ gap: 6px;
1303
+ user-select: none;
1304
+ transition: background 0.1s;
1305
+ position: relative;
1306
+ margin: 1px 0;
1307
+ }
1308
+
1309
+ .session-item:hover { background: var(--bg-hover); }
1310
+
1311
+ .session-item.active {
1312
+ background: rgba(91,185,140,0.05);
1313
+ }
1314
+
1315
+ .session-item.active::before {
1316
+ content: '';
1317
+ position: absolute;
1318
+ left: 0;
1319
+ top: 3px;
1320
+ bottom: 3px;
1321
+ width: 2px;
1322
+ background: var(--neon-green);
1323
+ border-radius: 1px;
1324
+ }
1325
+
1326
+ .session-item .si-icon {
1327
+ font-size: 9px;
1040
1328
  color: var(--neon-green);
1329
+ opacity: 0.5;
1330
+ flex-shrink: 0;
1041
1331
  }
1042
1332
 
1333
+ .session-item .si-time {
1334
+ font-family: var(--font-mono);
1335
+ font-size: 10px;
1336
+ color: var(--text-secondary);
1337
+ }
1338
+
1339
+ .session-item.active .si-time { color: var(--neon-green); }
1340
+
1043
1341
  .session-badge {
1044
1342
  display: inline-flex;
1045
1343
  align-items: center;
@@ -1055,31 +1353,42 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1055
1353
  letter-spacing: 0.5px;
1056
1354
  }
1057
1355
 
1356
+ .session-empty {
1357
+ padding: 12px 18px;
1358
+ font-family: var(--font-mono);
1359
+ font-size: 10px;
1360
+ color: var(--text-muted);
1361
+ text-align: center;
1362
+ }
1363
+
1058
1364
  .session-create-overlay {
1059
1365
  position: fixed;
1060
1366
  inset: 0;
1061
- background: rgba(6,6,12,0.75);
1367
+ background: rgba(6,6,12,0.6);
1062
1368
  z-index: 200;
1063
1369
  display: none;
1064
1370
  align-items: center;
1065
1371
  justify-content: center;
1066
- backdrop-filter: blur(4px);
1372
+ backdrop-filter: blur(12px);
1373
+ -webkit-backdrop-filter: blur(12px);
1067
1374
  }
1068
1375
 
1069
1376
  .session-create-overlay.active { display: flex; }
1070
1377
 
1071
1378
  .session-create-modal {
1072
1379
  width: 420px;
1073
- background: var(--bg-card);
1380
+ background: rgba(17,17,22,0.85);
1074
1381
  border: 1px solid var(--border);
1075
1382
  border-radius: var(--radius-lg);
1076
- box-shadow: 0 20px 60px rgba(0,0,0,0.6);
1383
+ box-shadow: 0 24px 64px rgba(0,0,0,0.8), 0 0 0 1px rgba(91,185,140,0.1);
1077
1384
  overflow: hidden;
1385
+ backdrop-filter: blur(20px);
1386
+ -webkit-backdrop-filter: blur(20px);
1078
1387
  }
1079
1388
 
1080
1389
  .session-create-header {
1081
1390
  padding: 16px 20px 12px;
1082
- border-bottom: 1px solid var(--border-subtle);
1391
+ border-bottom: 1px solid var(--border);
1083
1392
  font-family: var(--font-mono);
1084
1393
  font-size: 11px;
1085
1394
  font-weight: 600;
@@ -1109,7 +1418,7 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1109
1418
  .session-field input,
1110
1419
  .session-field textarea {
1111
1420
  width: 100%;
1112
- background: var(--bg-surface);
1421
+ background: rgba(8,8,10,0.5);
1113
1422
  border: 1px solid var(--border);
1114
1423
  color: var(--text-primary);
1115
1424
  font-family: var(--font-mono);
@@ -1117,13 +1426,15 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1117
1426
  padding: 8px 12px;
1118
1427
  border-radius: var(--radius);
1119
1428
  outline: none;
1120
- transition: border-color 0.2s;
1429
+ transition: all 0.2s;
1121
1430
  }
1122
1431
 
1123
1432
  .session-field select:focus,
1124
1433
  .session-field input:focus,
1125
1434
  .session-field textarea:focus {
1126
- border-color: rgba(217,119,87,0.3);
1435
+ border-color: rgba(91,185,140,0.4);
1436
+ box-shadow: 0 0 8px rgba(91,185,140,0.1);
1437
+ background: var(--bg-surface);
1127
1438
  }
1128
1439
 
1129
1440
  .session-field textarea {
@@ -1133,7 +1444,7 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1133
1444
 
1134
1445
  .session-create-footer {
1135
1446
  padding: 12px 20px;
1136
- border-top: 1px solid var(--border-subtle);
1447
+ border-top: 1px solid var(--border);
1137
1448
  display: flex;
1138
1449
  justify-content: flex-end;
1139
1450
  gap: 8px;
@@ -1147,24 +1458,27 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1147
1458
 
1148
1459
  .session-create-footer .edit-btn.create:hover {
1149
1460
  background: rgba(91,185,140,0.2);
1461
+ box-shadow: 0 0 10px rgba(91,185,140,0.15);
1462
+ transform: translateY(-1px);
1150
1463
  }
1151
1464
 
1152
1465
  /* ============ SCROLLBAR ============ */
1153
- ::-webkit-scrollbar { width: 4px; }
1466
+ ::-webkit-scrollbar { width: 5px; height: 5px; }
1154
1467
  ::-webkit-scrollbar-track { background: transparent; }
1155
- ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
1156
- ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
1468
+ ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 4px; }
1469
+ ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.15); }
1157
1470
 
1158
1471
  /* ============ EDIT MODAL ============ */
1159
1472
  .edit-overlay {
1160
1473
  position: fixed;
1161
1474
  inset: 0;
1162
- background: rgba(6,6,12,0.75);
1475
+ background: rgba(6,6,12,0.6);
1163
1476
  z-index: 200;
1164
1477
  display: none;
1165
1478
  align-items: center;
1166
1479
  justify-content: center;
1167
- backdrop-filter: blur(4px);
1480
+ backdrop-filter: blur(12px);
1481
+ -webkit-backdrop-filter: blur(12px);
1168
1482
  }
1169
1483
 
1170
1484
  .edit-overlay.active { display: flex; }
@@ -1172,13 +1486,15 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1172
1486
  .edit-modal {
1173
1487
  width: 640px;
1174
1488
  max-height: 80vh;
1175
- background: var(--bg-card);
1489
+ background: rgba(17,17,22,0.85);
1176
1490
  border: 1px solid var(--border);
1177
1491
  border-radius: var(--radius-lg);
1178
- box-shadow: 0 20px 60px rgba(0,0,0,0.6);
1492
+ box-shadow: 0 24px 64px rgba(0,0,0,0.8), 0 0 0 1px rgba(217,119,87,0.1);
1179
1493
  display: flex;
1180
1494
  flex-direction: column;
1181
1495
  overflow: hidden;
1496
+ backdrop-filter: blur(20px);
1497
+ -webkit-backdrop-filter: blur(20px);
1182
1498
  }
1183
1499
 
1184
1500
  .edit-header {
@@ -1186,7 +1502,7 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1186
1502
  align-items: center;
1187
1503
  justify-content: space-between;
1188
1504
  padding: 12px 16px;
1189
- border-bottom: 1px solid var(--border-subtle);
1505
+ border-bottom: 1px solid var(--border);
1190
1506
  font-family: var(--font-mono);
1191
1507
  font-size: 11px;
1192
1508
  color: var(--text-secondary);
@@ -1203,7 +1519,7 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1203
1519
  border-radius: var(--radius);
1204
1520
  border: 1px solid var(--border);
1205
1521
  cursor: pointer;
1206
- transition: all 0.15s;
1522
+ transition: all 0.2s ease-out;
1207
1523
  letter-spacing: 0.5px;
1208
1524
  }
1209
1525
 
@@ -1212,7 +1528,7 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1212
1528
  color: var(--text-muted);
1213
1529
  }
1214
1530
 
1215
- .edit-btn.cancel:hover { color: var(--text-secondary); border-color: var(--text-muted); }
1531
+ .edit-btn.cancel:hover { color: var(--text-secondary); border-color: var(--text-muted); transform: translateY(-1px); }
1216
1532
 
1217
1533
  .edit-btn.save {
1218
1534
  background: rgba(217,119,87,0.1);
@@ -1220,17 +1536,17 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1220
1536
  border-color: rgba(217,119,87,0.3);
1221
1537
  }
1222
1538
 
1223
- .edit-btn.save:hover { background: rgba(217,119,87,0.2); }
1539
+ .edit-btn.save:hover { background: rgba(217,119,87,0.2); box-shadow: 0 0 10px rgba(217,119,87,0.15); transform: translateY(-1px); }
1224
1540
 
1225
1541
  .edit-textarea {
1226
1542
  flex: 1;
1227
1543
  min-height: 300px;
1228
- background: var(--bg-primary);
1544
+ background: rgba(8,8,10,0.3);
1229
1545
  border: none;
1230
1546
  color: var(--text-primary);
1231
1547
  font-family: var(--font-mono);
1232
1548
  font-size: 13px;
1233
- line-height: 1.7;
1549
+ line-height: 1.8;
1234
1550
  padding: 16px 20px;
1235
1551
  resize: none;
1236
1552
  outline: none;
@@ -1317,8 +1633,26 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1317
1633
  <!-- Left sidebar -->
1318
1634
  <div class="sidebar">
1319
1635
  <div class="sidebar-scroll">
1636
+ <!-- SESSIONS PILLAR (Claude-Mem) -->
1637
+ <div class="pillar-label">
1638
+ <span><span class="pillar-accent sessions"></span>SESSIONS</span>
1639
+ <span class="pillar-action" id="new-session-btn">+ NEW</span>
1640
+ </div>
1641
+ <div class="session-search-box">
1642
+ <span class="ss-icon">&#9906;</span>
1643
+ <input type="text" id="session-search-input" placeholder="Semantic search sessions...">
1644
+ </div>
1645
+ <div class="session-search-results" id="session-search-results"></div>
1646
+ <div id="session-tree"></div>
1647
+
1648
+ <div class="pillar-divider"></div>
1649
+
1650
+ <!-- PROJECTS PILLAR (Obsidian) -->
1651
+ <div class="pillar-label">
1652
+ <span><span class="pillar-accent projects"></span>PROJECTS</span>
1653
+ <span class="pillar-action" id="new-project-sidebar-btn">+ NEW</span>
1654
+ </div>
1320
1655
  <div class="sidebar-section">
1321
- <div class="section-label">Explorer</div>
1322
1656
  <div id="filter-info" class="filter-info" style="display:none;">
1323
1657
  <span id="filter-count"></span>
1324
1658
  <span class="clear-btn" id="clear-filters">clear</span>
@@ -1357,29 +1691,29 @@ body.resizing .resize-handle { pointer-events: auto !important; }
1357
1691
  <!-- Right panel -->
1358
1692
  <div class="right-panel" id="right-panel">
1359
1693
  <div id="graph-section">
1360
- <div class="rp-section-title">Local Graph <span class="graph-size-controls"><button class="graph-size-btn" id="graph-shrink" title="Shrink graph">&minus;</button><button class="graph-size-btn" id="graph-grow" title="Grow graph">+</button></span></div>
1694
+ <div class="rp-section-title">Local Graph <span class="graph-size-controls"><button class="graph-size-btn" id="graph-shrink" title="Shrink graph">&minus;</button><button class="graph-size-btn" id="graph-grow" title="Grow graph">+</button><button class="graph-size-btn" id="graph-max" title="Maximize graph">&#9974;</button></span></div>
1361
1695
  <div id="graph-container"></div>
1362
1696
  <div id="graph-resize-handle" class="graph-resize-handle"></div>
1363
1697
  </div>
1364
1698
  <div id="rp-outline" class="rp-section" style="display:none;">
1365
- <div class="rp-section-title">Outline <span class="rp-count" id="outline-count"></span></div>
1366
- <div id="rp-outline-list"></div>
1699
+ <div class="rp-section-title" onclick="this.parentElement.classList.toggle('collapsed')">Outline <span class="rp-count" id="outline-count"></span><span class="arrow">&#9654;</span></div>
1700
+ <div id="rp-outline-list" class="rp-section-body"></div>
1367
1701
  </div>
1368
1702
  <div id="rp-backlinks" class="rp-section" style="display:none;">
1369
- <div class="rp-section-title">Backlinks <span class="rp-count" id="bl-count"></span></div>
1370
- <div id="rp-backlinks-list"></div>
1703
+ <div class="rp-section-title" onclick="this.parentElement.classList.toggle('collapsed')">Backlinks <span class="rp-count" id="bl-count"></span><span class="arrow">&#9654;</span></div>
1704
+ <div id="rp-backlinks-list" class="rp-section-body"></div>
1371
1705
  </div>
1372
1706
  <div id="rp-related" class="rp-section" style="display:none;">
1373
- <div class="rp-section-title">Related <span class="rp-count" id="rel-count"></span></div>
1374
- <div id="rp-related-list"></div>
1707
+ <div class="rp-section-title" onclick="this.parentElement.classList.toggle('collapsed')">Related <span class="rp-count" id="rel-count"></span><span class="arrow">&#9654;</span></div>
1708
+ <div id="rp-related-list" class="rp-section-body"></div>
1375
1709
  </div>
1376
1710
  <div id="rp-outlinks" class="rp-section" style="display:none;">
1377
- <div class="rp-section-title">Outgoing Links <span class="rp-count" id="out-count"></span></div>
1378
- <div id="rp-outlinks-list"></div>
1711
+ <div class="rp-section-title" onclick="this.parentElement.classList.toggle('collapsed')">Outgoing Links <span class="rp-count" id="out-count"></span><span class="arrow">&#9654;</span></div>
1712
+ <div id="rp-outlinks-list" class="rp-section-body"></div>
1379
1713
  </div>
1380
1714
  <div id="rp-unlinked" class="rp-section" style="display:none;">
1381
- <div class="rp-section-title">Unlinked Mentions <span class="rp-count" id="unlinked-count"></span></div>
1382
- <div id="rp-unlinked-list"></div>
1715
+ <div class="rp-section-title" onclick="this.parentElement.classList.toggle('collapsed')">Unlinked Mentions <span class="rp-count" id="unlinked-count"></span><span class="arrow">&#9654;</span></div>
1716
+ <div id="rp-unlinked-list" class="rp-section-body"></div>
1383
1717
  </div>
1384
1718
  </div>
1385
1719
  </div>
@@ -1590,28 +1924,19 @@ function updateQsSelection() {
1590
1924
  });
1591
1925
  }
1592
1926
 
1593
- // --- File Tree ---
1594
- function renderTree(node, container, parentFolder) {
1927
+ // --- File Tree (Projects pillar only — no Sessions folders) ---
1928
+ function renderTree(node, container) {
1595
1929
  if (node.type === 'folder' && node.name !== 'vault') {
1596
- const isSessionsFolder = node.name === 'Sessions';
1930
+ if (node.name === 'Sessions') return;
1931
+
1597
1932
  const item = document.createElement('div');
1598
1933
  item.className = 'tree-item';
1599
-
1600
- if (isSessionsFolder) {
1601
- item.innerHTML = `<span class="arrow open">&#9654;</span><span class="icon session-folder-icon">&#9716;</span><span class="name">Sessions</span><button class="session-add-btn" data-project="${parentFolder || ''}" title="New session">+</button>`;
1602
- } else {
1603
- item.innerHTML = `<span class="arrow open">&#9654;</span><span class="icon folder-icon">&#9776;</span><span class="name">${node.name}</span>`;
1604
- }
1934
+ item.innerHTML = `<span class="arrow open">&#9654;</span><span class="icon folder-icon">&#9776;</span><span class="name">${node.name}</span>`;
1605
1935
 
1606
1936
  const children = document.createElement('div');
1607
1937
  children.className = 'tree-children';
1608
1938
 
1609
1939
  item.addEventListener('click', (e) => {
1610
- if (e.target.classList.contains('session-add-btn')) {
1611
- e.stopPropagation();
1612
- openSessionCreate(e.target.dataset.project);
1613
- return;
1614
- }
1615
1940
  e.stopPropagation();
1616
1941
  item.querySelector('.arrow').classList.toggle('open');
1617
1942
  children.classList.toggle('collapsed');
@@ -1619,29 +1944,13 @@ function renderTree(node, container, parentFolder) {
1619
1944
 
1620
1945
  container.appendChild(item);
1621
1946
  container.appendChild(children);
1622
- (node.children || []).forEach(c => renderTree(c, children, node.name));
1947
+ (node.children || []).forEach(c => renderTree(c, children));
1623
1948
 
1624
1949
  } else if (node.type === 'note') {
1625
1950
  const item = document.createElement('div');
1626
1951
  item.className = 'tree-item';
1627
1952
  item.dataset.path = node.path;
1628
-
1629
- const isSessionNote = node.path.includes('/Sessions/') || node.path.startsWith('Sessions/');
1630
- let displayName = node.name.replace('.md', '');
1631
-
1632
- if (isSessionNote) {
1633
- const match = displayName.match(/^(\d{4})-(\d{2})-(\d{2})_(\d{2})(\d{2})(\d{2})$/);
1634
- if (match) {
1635
- const [, y, mo, d, h, mi] = match;
1636
- const hr = parseInt(h);
1637
- const ampm = hr >= 12 ? 'PM' : 'AM';
1638
- const hr12 = hr % 12 || 12;
1639
- const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
1640
- displayName = `${months[parseInt(mo)-1]} ${parseInt(d)}, ${hr12}:${mi} ${ampm}`;
1641
- item.title = node.name.replace('.md', '');
1642
- }
1643
- }
1644
-
1953
+ const displayName = node.name.replace('.md', '');
1645
1954
  item.innerHTML = `<span class="arrow" style="visibility:hidden">&#9654;</span><span class="icon note-icon">&#9671;</span><span class="name">${displayName}</span>`;
1646
1955
  item.addEventListener('click', () => loadNote(node.path));
1647
1956
  container.appendChild(item);
@@ -1651,6 +1960,107 @@ function renderTree(node, container, parentFolder) {
1651
1960
  }
1652
1961
  }
1653
1962
 
1963
+ // --- Session Tree (Sessions pillar — grouped by project with dropdowns) ---
1964
+ function formatSessionTimestamp(filename) {
1965
+ const stem = filename.replace('.md', '');
1966
+ const match = stem.match(/^(\d{4})-(\d{2})-(\d{2})_(\d{2})(\d{2})(\d{2})$/);
1967
+ if (!match) return stem;
1968
+ const [, y, mo, d, h, mi] = match;
1969
+ const hr = parseInt(h);
1970
+ const ampm = hr >= 12 ? 'PM' : 'AM';
1971
+ const hr12 = hr % 12 || 12;
1972
+ const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
1973
+ return `${months[parseInt(mo)-1]} ${parseInt(d)}, ${hr12}:${mi} ${ampm}`;
1974
+ }
1975
+
1976
+ function renderSessionTree(sessionsData) {
1977
+ const container = document.getElementById('session-tree');
1978
+ container.innerHTML = '';
1979
+
1980
+ if (Object.keys(sessionsData).length === 0) {
1981
+ container.innerHTML = '<div class="session-empty">No sessions yet</div>';
1982
+ return;
1983
+ }
1984
+
1985
+ const sortedProjects = Object.keys(sessionsData).sort();
1986
+ sortedProjects.forEach(project => {
1987
+ const sessions = sessionsData[project];
1988
+ const group = document.createElement('div');
1989
+ group.className = 'session-project-group';
1990
+
1991
+ const header = document.createElement('div');
1992
+ header.className = 'session-project-header';
1993
+ header.innerHTML = `
1994
+ <span class="sp-arrow open">&#9654;</span>
1995
+ <span class="sp-icon">&#9716;</span>
1996
+ <span class="sp-name">${project}</span>
1997
+ <span class="sp-count">${sessions.length}</span>
1998
+ <button class="sp-add" title="New session for ${project}">+</button>
1999
+ `;
2000
+
2001
+ const list = document.createElement('div');
2002
+ list.className = 'session-list';
2003
+
2004
+ sessions.sort((a, b) => b.path.localeCompare(a.path));
2005
+ sessions.forEach(session => {
2006
+ const item = document.createElement('div');
2007
+ item.className = 'session-item';
2008
+ item.dataset.path = session.path;
2009
+ const displayTime = formatSessionTimestamp(session.path.split('/').pop());
2010
+ item.innerHTML = `<span class="si-icon">&#9679;</span><span class="si-time">${displayTime}</span>`;
2011
+ item.title = session.title || session.path;
2012
+ item.addEventListener('click', () => loadNote(session.path));
2013
+ list.appendChild(item);
2014
+ });
2015
+
2016
+ header.addEventListener('click', (e) => {
2017
+ if (e.target.classList.contains('sp-add')) {
2018
+ e.stopPropagation();
2019
+ openSessionCreate(project);
2020
+ return;
2021
+ }
2022
+ e.stopPropagation();
2023
+ header.querySelector('.sp-arrow').classList.toggle('open');
2024
+ list.classList.toggle('collapsed');
2025
+ });
2026
+
2027
+ group.appendChild(header);
2028
+ group.appendChild(list);
2029
+ container.appendChild(group);
2030
+ });
2031
+ }
2032
+
2033
+ // --- Session Semantic Search ---
2034
+ let sessionSearchTimeout;
2035
+ const sessionSearchInput = document.getElementById('session-search-input');
2036
+ const sessionSearchResults = document.getElementById('session-search-results');
2037
+
2038
+ sessionSearchInput.addEventListener('input', () => {
2039
+ clearTimeout(sessionSearchTimeout);
2040
+ const q = sessionSearchInput.value.trim();
2041
+ if (!q) { sessionSearchResults.innerHTML = ''; return; }
2042
+ sessionSearchTimeout = setTimeout(async () => {
2043
+ const results = await fetchJSON(`/api/sessions/search?q=${encodeURIComponent(q)}`);
2044
+ sessionSearchResults.innerHTML = '';
2045
+ if (results.length === 0) {
2046
+ sessionSearchResults.innerHTML = '<div class="ss-result"><span class="ss-meta">No matching sessions</span></div>';
2047
+ return;
2048
+ }
2049
+ results.forEach(r => {
2050
+ const div = document.createElement('div');
2051
+ div.className = 'ss-result';
2052
+ const dist = r.distance !== undefined ? ` &middot; dist: ${r.distance.toFixed(2)}` : '';
2053
+ div.innerHTML = `<div class="ss-title">${r.title}</div><div class="ss-meta">${r.path}${dist}</div>${r.snippet ? `<div class="ss-snippet">${r.snippet.substring(0, 120)}</div>` : ''}`;
2054
+ div.addEventListener('click', () => {
2055
+ loadNote(r.path);
2056
+ sessionSearchInput.value = '';
2057
+ sessionSearchResults.innerHTML = '';
2058
+ });
2059
+ sessionSearchResults.appendChild(div);
2060
+ });
2061
+ }, 250);
2062
+ });
2063
+
1654
2064
  // --- Note rendering ---
1655
2065
  async function loadNote(path) {
1656
2066
  currentPath = path;
@@ -1658,7 +2068,10 @@ async function loadNote(path) {
1658
2068
  if (note.error) return;
1659
2069
  currentNote = note;
1660
2070
 
1661
- document.querySelectorAll('.tree-item').forEach(el => {
2071
+ document.querySelectorAll('.tree-item[data-path]').forEach(el => {
2072
+ el.classList.toggle('active', el.dataset.path === path);
2073
+ });
2074
+ document.querySelectorAll('.session-item[data-path]').forEach(el => {
1662
2075
  el.classList.toggle('active', el.dataset.path === path);
1663
2076
  });
1664
2077
 
@@ -1955,6 +2368,16 @@ document.getElementById('session-create-btn').addEventListener('click', async ()
1955
2368
  }
1956
2369
  });
1957
2370
 
2371
+ // --- Sidebar pillar buttons ---
2372
+ document.getElementById('new-session-btn').addEventListener('click', () => openSessionCreate(''));
2373
+
2374
+ document.getElementById('new-project-sidebar-btn').addEventListener('click', () => {
2375
+ document.getElementById('project-create-overlay').classList.add('active');
2376
+ document.getElementById('project-name-input').value = '';
2377
+ document.getElementById('project-overview-input').value = '';
2378
+ document.getElementById('project-name-input').focus();
2379
+ });
2380
+
1958
2381
  // --- Project Management ---
1959
2382
  function closeProjectCreate() {
1960
2383
  document.getElementById('project-create-overlay').classList.remove('active');
@@ -1993,7 +2416,12 @@ document.getElementById('project-create-btn').addEventListener('click', async ()
1993
2416
  });
1994
2417
 
1995
2418
  async function refreshTree() {
1996
- treeData = await fetchJSON('/api/tree');
2419
+ const [rawTreeData, sessionsData, stats] = await Promise.all([
2420
+ fetchJSON('/api/tree'),
2421
+ fetchJSON('/api/sessions'),
2422
+ fetchJSON('/api/stats'),
2423
+ ]);
2424
+
1997
2425
  allNotes = {};
1998
2426
  function walk(node) {
1999
2427
  if (node.type === 'note') {
@@ -2001,14 +2429,15 @@ async function refreshTree() {
2001
2429
  }
2002
2430
  (node.children || []).forEach(walk);
2003
2431
  }
2004
- walk(treeData);
2432
+ walk(rawTreeData);
2005
2433
 
2006
2434
  const treeEl = document.getElementById('file-tree');
2007
2435
  treeEl.innerHTML = '';
2008
- renderTree(treeData, treeEl);
2436
+ renderTree(rawTreeData, treeEl);
2437
+
2438
+ renderSessionTree(sessionsData);
2009
2439
  renderTagCloud(collectAllTags());
2010
2440
 
2011
- const stats = await fetchJSON('/api/stats');
2012
2441
  document.getElementById('stats-bar').innerHTML = `
2013
2442
  <span><span class="stat-val">${stats.notes}</span> notes</span>
2014
2443
  <span><span class="stat-val">${stats.folders}</span> folders</span>
@@ -2016,11 +2445,17 @@ async function refreshTree() {
2016
2445
  <span><span class="stat-val">${stats.links}</span> links</span>
2017
2446
  `;
2018
2447
 
2019
- if (currentPath) {
2020
- document.querySelectorAll('.tree-item').forEach(el => {
2021
- el.classList.toggle('active', el.dataset.path === currentPath);
2022
- });
2023
- }
2448
+ markActiveItems();
2449
+ }
2450
+
2451
+ function markActiveItems() {
2452
+ if (!currentPath) return;
2453
+ document.querySelectorAll('.tree-item[data-path]').forEach(el => {
2454
+ el.classList.toggle('active', el.dataset.path === currentPath);
2455
+ });
2456
+ document.querySelectorAll('.session-item[data-path]').forEach(el => {
2457
+ el.classList.toggle('active', el.dataset.path === currentPath);
2458
+ });
2024
2459
  }
2025
2460
 
2026
2461
  // --- Graph ---
@@ -2031,44 +2466,63 @@ function renderGraph(note) {
2031
2466
  const nodes = new Map();
2032
2467
  const links = [];
2033
2468
 
2034
- nodes.set(note.path, { id: note.path, title: note.title, active: true });
2469
+ nodes.set(note.path, { id: note.path, title: note.title, active: true, tags: note.tags || [] });
2470
+
2471
+ const projectPrefix = note.path.includes('/') ? note.path.split('/')[0] + '/' : '';
2472
+ const inSameProject = (p) => !projectPrefix ? !p.includes('/') : p.startsWith(projectPrefix);
2035
2473
 
2036
2474
  (note.links || []).forEach(link => {
2037
2475
  const path = findNotePath(link);
2038
- if (path && !nodes.has(path)) {
2039
- const n = allNotes[path];
2040
- nodes.set(path, { id: path, title: n ? n.title : link, active: false });
2476
+ if (path && inSameProject(path)) {
2477
+ if (!nodes.has(path)) {
2478
+ const n = allNotes[path];
2479
+ nodes.set(path, { id: path, title: n ? n.title : link, active: false, tags: n ? n.tags : [] });
2480
+ }
2481
+ links.push({ source: note.path, target: path });
2041
2482
  }
2042
- if (path) links.push({ source: note.path, target: path });
2043
2483
  });
2044
2484
 
2045
2485
  (note.backlinks || []).forEach(bl => {
2046
2486
  const blPath = typeof bl === 'string' ? bl : (bl && bl.path);
2047
2487
  if (!blPath) return;
2048
- if (!nodes.has(blPath)) {
2049
- const n = allNotes[blPath];
2050
- const title = (bl && typeof bl === 'object' && bl.title) ? bl.title : (n ? n.title : blPath);
2051
- nodes.set(blPath, { id: blPath, title, active: false });
2488
+ if (inSameProject(blPath)) {
2489
+ if (!nodes.has(blPath)) {
2490
+ const n = allNotes[blPath];
2491
+ const title = (bl && typeof bl === 'object' && bl.title) ? bl.title : (n ? n.title : blPath);
2492
+ nodes.set(blPath, { id: blPath, title, active: false, tags: n ? n.tags : [] });
2493
+ }
2494
+ links.push({ source: blPath, target: note.path });
2052
2495
  }
2053
- links.push({ source: blPath, target: note.path });
2054
2496
  });
2055
2497
 
2056
2498
  (note.related || []).forEach(r => {
2057
- if (!nodes.has(r.path)) {
2058
- nodes.set(r.path, { id: r.path, title: r.title, active: false });
2499
+ if (r.path && inSameProject(r.path)) {
2500
+ if (!nodes.has(r.path)) {
2501
+ const n = allNotes[r.path];
2502
+ nodes.set(r.path, { id: r.path, title: r.title, active: false, tags: n ? n.tags : [] });
2503
+ }
2504
+ links.push({ source: note.path, target: r.path, dashed: true });
2059
2505
  }
2060
- links.push({ source: note.path, target: r.path, dashed: true });
2061
2506
  });
2062
2507
 
2063
2508
  const nodeArray = Array.from(nodes.values());
2509
+ nodeArray.forEach(n => n.degree = 0);
2510
+ links.forEach(l => {
2511
+ const s = nodes.get(l.source);
2512
+ const t = nodes.get(l.target);
2513
+ if (s) s.degree++;
2514
+ if (t) t.degree++;
2515
+ });
2516
+
2064
2517
  if (nodeArray.length < 2) {
2065
2518
  container.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:10px;font-family:var(--font-mono)">No connections</div>';
2066
2519
  return;
2067
2520
  }
2068
2521
 
2522
+ const isMaximized = document.getElementById('graph-section').classList.contains('maximized');
2069
2523
  const width = container.clientWidth || 248;
2070
2524
  const height = container.clientHeight || 200;
2071
- const margin = 24;
2525
+ const margin = isMaximized ? 60 : 24;
2072
2526
 
2073
2527
  const svg = d3.select(container).append('svg')
2074
2528
  .attr('viewBox', `0 0 ${width} ${height}`);
@@ -2076,34 +2530,72 @@ function renderGraph(note) {
2076
2530
  const g = svg.append('g');
2077
2531
 
2078
2532
  svg.call(d3.zoom()
2079
- .scaleExtent([0.3, 3])
2533
+ .scaleExtent([0.2, 4])
2080
2534
  .on('zoom', (event) => g.attr('transform', event.transform))
2081
2535
  );
2082
2536
 
2083
2537
  const simulation = d3.forceSimulation(nodeArray)
2084
- .force('link', d3.forceLink(links).id(d => d.id).distance(60))
2085
- .force('charge', d3.forceManyBody().strength(-120))
2538
+ .force('link', d3.forceLink(links).id(d => d.id).distance(isMaximized ? 150 : 80))
2539
+ .force('charge', d3.forceManyBody().strength(d => isMaximized ? -300 - (d.degree * 40) : -100 - (d.degree * 20)))
2086
2540
  .force('center', d3.forceCenter(width / 2, height / 2))
2087
- .force('collision', d3.forceCollide().radius(25));
2541
+ .force('collision', d3.forceCollide().radius(d => {
2542
+ const textWidth = d.title.length * (isMaximized ? 6 : 4.5);
2543
+ return (isMaximized ? textWidth + 10 : 12) + (d.degree * 2);
2544
+ }));
2088
2545
 
2089
2546
  const linkEl = g.selectAll('.graph-link')
2090
2547
  .data(links).enter().append('line')
2091
2548
  .attr('class', 'graph-link')
2092
2549
  .attr('stroke-dasharray', d => d.dashed ? '3,3' : null)
2093
- .style('opacity', d => d.dashed ? 0.5 : 1);
2550
+ .style('opacity', d => d.dashed ? 0.3 : 0.6);
2094
2551
 
2095
2552
  const node = g.selectAll('.graph-node')
2096
2553
  .data(nodeArray).enter().append('g')
2097
2554
  .attr('class', d => 'graph-node' + (d.active ? ' active' : ''))
2098
- .on('click', (e, d) => { e.stopPropagation(); loadNote(d.id); })
2555
+ .on('click', (e, d) => {
2556
+ e.stopPropagation();
2557
+ loadNote(d.id);
2558
+ if (isMaximized) {
2559
+ document.getElementById('graph-section').classList.remove('maximized');
2560
+ isGraphMaximized = false;
2561
+ renderGraph(currentNote);
2562
+ }
2563
+ })
2099
2564
  .call(d3.drag()
2100
2565
  .on('start', (e, d) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
2101
2566
  .on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
2102
2567
  .on('end', (e, d) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; })
2103
2568
  );
2104
2569
 
2105
- node.append('circle').attr('r', d => d.active ? 6 : 4);
2106
- node.append('text').text(d => d.title).attr('dx', 10).attr('dy', 3);
2570
+ node.append('circle').attr('r', d => d.active ? 8 : Math.min(12, 3 + (d.degree * 1.2)));
2571
+ node.append('text').text(d => d.title).attr('dx', d => (d.active ? 8 : Math.min(12, 3 + (d.degree * 1.2))) + 6).attr('dy', 3);
2572
+
2573
+ if (isMaximized) {
2574
+ node.append('text')
2575
+ .text(d => {
2576
+ let hash = 0;
2577
+ for (let i = 0; i < d.id.length; i++) hash = Math.imul(31, hash) + d.id.charCodeAt(i) | 0;
2578
+ const v1 = ((Math.abs(hash) % 1000) / 1000).toFixed(3);
2579
+ const v2 = ((Math.abs(hash * 13) % 1000) / 1000).toFixed(3);
2580
+ const v3 = ((Math.abs(hash * 17) % 1000) / 1000).toFixed(3);
2581
+ return `[${v1}, ${v2}, ${v3}, ...]`;
2582
+ })
2583
+ .attr('dx', d => (d.active ? 8 : Math.min(12, 3 + (d.degree * 1.2))) + 6)
2584
+ .attr('dy', 16)
2585
+ .style('fill', 'var(--neon-purple)')
2586
+ .style('font-size', '8px')
2587
+ .style('opacity', '0.8')
2588
+ .style('pointer-events', 'none')
2589
+ .style('font-family', 'var(--font-mono)');
2590
+
2591
+ node.append('text')
2592
+ .text(d => d.id ? d.id.split('/').slice(0, -1).join('/') : '')
2593
+ .attr('dx', d => (d.active ? 8 : Math.min(12, 3 + (d.degree * 1.2))) + 6)
2594
+ .attr('dy', 26)
2595
+ .style('fill', 'var(--text-muted)')
2596
+ .style('font-size', '8px')
2597
+ .style('pointer-events', 'none');
2598
+ }
2107
2599
 
2108
2600
  simulation.on('tick', () => {
2109
2601
  linkEl
@@ -2119,11 +2611,19 @@ function renderGraph(note) {
2119
2611
 
2120
2612
  // --- Graph Resize ---
2121
2613
  let graphHeight = parseInt(localStorage.getItem('kyp-graph-h')) || 200;
2614
+ let isGraphMaximized = false;
2122
2615
 
2123
2616
  function initGraphResize() {
2124
2617
  const container = document.getElementById('graph-container');
2618
+ const section = document.getElementById('graph-section');
2125
2619
  container.style.height = graphHeight + 'px';
2126
2620
 
2621
+ document.getElementById('graph-max').addEventListener('click', () => {
2622
+ isGraphMaximized = !isGraphMaximized;
2623
+ section.classList.toggle('maximized', isGraphMaximized);
2624
+ if (graphVisible && currentNote) renderGraph(currentNote);
2625
+ });
2626
+
2127
2627
  document.getElementById('graph-shrink').addEventListener('click', () => {
2128
2628
  graphHeight = Math.max(100, graphHeight - 50);
2129
2629
  container.style.height = graphHeight + 'px';
@@ -2369,8 +2869,13 @@ function initResize() {
2369
2869
 
2370
2870
  // --- Init ---
2371
2871
  async function init() {
2372
- treeData = await fetchJSON('/api/tree');
2373
- const stats = await fetchJSON('/api/stats');
2872
+ const [rawTreeData, sessionsData, stats] = await Promise.all([
2873
+ fetchJSON('/api/tree'),
2874
+ fetchJSON('/api/sessions'),
2875
+ fetchJSON('/api/stats'),
2876
+ ]);
2877
+
2878
+ treeData = rawTreeData;
2374
2879
 
2375
2880
  function walk(node) {
2376
2881
  if (node.type === 'note') {
@@ -2389,6 +2894,7 @@ async function init() {
2389
2894
  await Promise.all(promises);
2390
2895
 
2391
2896
  renderTree(treeData, document.getElementById('file-tree'));
2897
+ renderSessionTree(sessionsData);
2392
2898
  renderTagCloud(collectAllTags());
2393
2899
 
2394
2900
  document.getElementById('stats-bar').innerHTML = `