somtum 1.3.0 → 1.3.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.
- package/README.md +236 -211
- package/dist/cli/hook.d.ts.map +1 -1
- package/dist/cli/hook.js +6 -0
- package/dist/cli/hook.js.map +1 -1
- package/dist/cli/index.js +4 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +11 -4
- package/dist/cli/serve.js.map +1 -1
- package/dist/core/extractor.d.ts.map +1 -1
- package/dist/core/extractor.js +8 -3
- package/dist/core/extractor.js.map +1 -1
- package/dist/dashboard/index.html +450 -936
- package/package.json +1 -1
|
@@ -7,160 +7,153 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link
|
|
10
|
-
href="https://fonts.googleapis.com/css2?family=
|
|
10
|
+
href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=Fira+Code:wght@300;400;500&display=swap"
|
|
11
11
|
rel="stylesheet"
|
|
12
12
|
/>
|
|
13
13
|
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
14
14
|
|
|
15
15
|
<style>
|
|
16
|
-
*,
|
|
17
|
-
*::before,
|
|
18
|
-
*::after {
|
|
19
|
-
box-sizing: border-box;
|
|
20
|
-
margin: 0;
|
|
21
|
-
padding: 0;
|
|
22
|
-
}
|
|
16
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
23
17
|
|
|
24
18
|
:root {
|
|
25
|
-
--bg:
|
|
26
|
-
--surface:
|
|
27
|
-
--surface2:
|
|
28
|
-
--surface3:
|
|
29
|
-
--border:
|
|
30
|
-
--border2:
|
|
31
|
-
--text:
|
|
32
|
-
--muted:
|
|
33
|
-
--muted2:
|
|
34
|
-
--accent:
|
|
35
|
-
--accent-g:
|
|
36
|
-
--accent-a:
|
|
37
|
-
--accent-r:
|
|
38
|
-
--accent-v:
|
|
39
|
-
--accent-c:
|
|
40
|
-
--radius:
|
|
41
|
-
--sidebar:
|
|
42
|
-
--header:
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
html,
|
|
46
|
-
|
|
47
|
-
height: 100%;
|
|
48
|
-
overflow: hidden;
|
|
19
|
+
--bg: #0c0a07;
|
|
20
|
+
--surface: #111009;
|
|
21
|
+
--surface2: #19160e;
|
|
22
|
+
--surface3: #201d13;
|
|
23
|
+
--border: #2d2918;
|
|
24
|
+
--border2: #3e3920;
|
|
25
|
+
--text: #ede8da;
|
|
26
|
+
--muted: #6b6350;
|
|
27
|
+
--muted2: #9e9278;
|
|
28
|
+
--accent: #f0a030;
|
|
29
|
+
--accent-g: #34d399;
|
|
30
|
+
--accent-a: #f0a030;
|
|
31
|
+
--accent-r: #e8685a;
|
|
32
|
+
--accent-v: #a78bfa;
|
|
33
|
+
--accent-c: #38bdf8;
|
|
34
|
+
--radius: 7px;
|
|
35
|
+
--sidebar: 304px;
|
|
36
|
+
--header: 52px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
html, body {
|
|
40
|
+
height: 100%; overflow: hidden;
|
|
49
41
|
background: var(--bg);
|
|
50
42
|
color: var(--text);
|
|
51
|
-
font-family: '
|
|
43
|
+
font-family: 'Syne', sans-serif;
|
|
52
44
|
font-size: 13px;
|
|
53
45
|
line-height: 1.5;
|
|
54
46
|
-webkit-font-smoothing: antialiased;
|
|
55
47
|
}
|
|
56
48
|
|
|
57
|
-
/*
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
background:
|
|
67
|
-
border-radius: 4px;
|
|
68
|
-
}
|
|
69
|
-
::-webkit-scrollbar-thumb:hover {
|
|
70
|
-
background: var(--muted);
|
|
49
|
+
/* Grain overlay */
|
|
50
|
+
body::after {
|
|
51
|
+
content: '';
|
|
52
|
+
position: fixed;
|
|
53
|
+
inset: 0;
|
|
54
|
+
pointer-events: none;
|
|
55
|
+
z-index: 9999;
|
|
56
|
+
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
|
57
|
+
opacity: 0.022;
|
|
58
|
+
background-size: 180px 180px;
|
|
71
59
|
}
|
|
72
60
|
|
|
73
|
-
/* ──
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
61
|
+
/* ── Scrollbars ── */
|
|
62
|
+
::-webkit-scrollbar { width: 3px; height: 3px; }
|
|
63
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
64
|
+
::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 3px; }
|
|
65
|
+
::-webkit-scrollbar-thumb:hover { background: var(--muted); }
|
|
77
66
|
|
|
78
|
-
|
|
67
|
+
.mono { font-family: 'Fira Code', monospace; }
|
|
68
|
+
|
|
69
|
+
/* ══ HEADER ══════════════════════════════════════════════════════ */
|
|
79
70
|
#header {
|
|
80
71
|
height: var(--header);
|
|
81
|
-
|
|
82
|
-
padding: 0 20px;
|
|
72
|
+
padding: 0 18px;
|
|
83
73
|
display: flex;
|
|
84
74
|
align-items: center;
|
|
85
|
-
gap:
|
|
75
|
+
gap: 12px;
|
|
86
76
|
flex-shrink: 0;
|
|
87
77
|
background: var(--surface);
|
|
78
|
+
border-bottom: 1px solid var(--border);
|
|
79
|
+
position: relative;
|
|
88
80
|
}
|
|
81
|
+
/* accent line under header */
|
|
82
|
+
#header::after {
|
|
83
|
+
content: '';
|
|
84
|
+
position: absolute;
|
|
85
|
+
bottom: -1px; left: 0;
|
|
86
|
+
width: 100%; height: 1px;
|
|
87
|
+
background: linear-gradient(90deg, var(--accent) 0%, transparent 40%);
|
|
88
|
+
opacity: 0.35;
|
|
89
|
+
}
|
|
90
|
+
|
|
89
91
|
#logo {
|
|
90
|
-
font-size:
|
|
92
|
+
font-size: 17px;
|
|
91
93
|
font-weight: 800;
|
|
92
94
|
color: var(--accent);
|
|
93
|
-
letter-spacing: -0.
|
|
95
|
+
letter-spacing: -0.02em;
|
|
94
96
|
line-height: 1;
|
|
95
97
|
display: flex;
|
|
96
98
|
align-items: center;
|
|
97
|
-
gap:
|
|
99
|
+
gap: 8px;
|
|
100
|
+
flex-shrink: 0;
|
|
98
101
|
}
|
|
99
102
|
#logo-mark {
|
|
100
|
-
width:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
display: flex;
|
|
105
|
-
align-items: center;
|
|
106
|
-
justify-content: center;
|
|
103
|
+
width: 28px; height: 28px;
|
|
104
|
+
border-radius: 8px;
|
|
105
|
+
background: linear-gradient(135deg, var(--accent) 0%, #e07820 100%);
|
|
106
|
+
display: flex; align-items: center; justify-content: center;
|
|
107
107
|
flex-shrink: 0;
|
|
108
|
+
box-shadow: 0 0 16px rgba(240,160,48,0.3);
|
|
108
109
|
}
|
|
109
|
-
#logo-mark svg {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
#header-sep {
|
|
115
|
-
width: 1px;
|
|
116
|
-
height: 20px;
|
|
117
|
-
background: var(--border);
|
|
118
|
-
}
|
|
110
|
+
#logo-mark svg { width: 14px; height: 14px; color: #0c0a07; }
|
|
111
|
+
|
|
112
|
+
#header-sep { width: 1px; height: 18px; background: var(--border2); }
|
|
113
|
+
|
|
119
114
|
#project-label {
|
|
120
115
|
font-size: 11px;
|
|
121
116
|
color: var(--muted);
|
|
122
|
-
font-family: '
|
|
117
|
+
font-family: 'Fira Code', monospace;
|
|
123
118
|
white-space: nowrap;
|
|
124
119
|
overflow: hidden;
|
|
125
120
|
text-overflow: ellipsis;
|
|
126
|
-
max-width:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
margin-left: auto;
|
|
121
|
+
max-width: 200px;
|
|
122
|
+
background: var(--surface2);
|
|
123
|
+
border: 1px solid var(--border);
|
|
124
|
+
padding: 3px 8px;
|
|
125
|
+
border-radius: 4px;
|
|
132
126
|
}
|
|
127
|
+
|
|
128
|
+
#stat-bar { display: flex; gap: 4px; margin-left: auto; }
|
|
133
129
|
.stat-pill {
|
|
134
|
-
display: flex;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
padding: 5px 11px;
|
|
138
|
-
border-radius: 6px;
|
|
130
|
+
display: flex; align-items: center; gap: 6px;
|
|
131
|
+
padding: 4px 10px;
|
|
132
|
+
border-radius: 5px;
|
|
139
133
|
background: var(--surface2);
|
|
140
134
|
border: 1px solid var(--border);
|
|
141
135
|
cursor: default;
|
|
136
|
+
transition: border-color 0.15s;
|
|
142
137
|
}
|
|
138
|
+
.stat-pill:hover { border-color: var(--border2); }
|
|
143
139
|
.stat-pill .s-lbl {
|
|
144
|
-
font-size:
|
|
145
|
-
|
|
146
|
-
text-transform: uppercase;
|
|
147
|
-
letter-spacing: 0.08em;
|
|
140
|
+
font-size: 9px; font-weight: 700;
|
|
141
|
+
text-transform: uppercase; letter-spacing: 0.1em;
|
|
148
142
|
color: var(--muted);
|
|
149
143
|
}
|
|
150
144
|
.stat-pill .s-val {
|
|
151
|
-
font-family: '
|
|
152
|
-
font-size:
|
|
153
|
-
font-weight: 600;
|
|
145
|
+
font-family: 'Fira Code', monospace;
|
|
146
|
+
font-size: 12.5px; font-weight: 500;
|
|
154
147
|
}
|
|
155
148
|
|
|
156
|
-
/*
|
|
149
|
+
/* ══ LAYOUT ══════════════════════════════════════════════════════ */
|
|
157
150
|
#layout {
|
|
158
151
|
display: flex;
|
|
159
152
|
height: calc(100vh - var(--header));
|
|
160
153
|
overflow: hidden;
|
|
161
154
|
}
|
|
162
155
|
|
|
163
|
-
/*
|
|
156
|
+
/* ══ SIDEBAR ══════════════════════════════════════════════════════ */
|
|
164
157
|
#sidebar {
|
|
165
158
|
width: var(--sidebar);
|
|
166
159
|
flex-shrink: 0;
|
|
@@ -175,319 +168,231 @@
|
|
|
175
168
|
#search-wrap {
|
|
176
169
|
padding: 10px;
|
|
177
170
|
border-bottom: 1px solid var(--border);
|
|
178
|
-
display: flex;
|
|
179
|
-
|
|
180
|
-
gap: 8px;
|
|
181
|
-
}
|
|
182
|
-
.search-box {
|
|
183
|
-
position: relative;
|
|
171
|
+
display: flex; flex-direction: column; gap: 8px;
|
|
172
|
+
background: var(--bg);
|
|
184
173
|
}
|
|
174
|
+
.search-box { position: relative; }
|
|
185
175
|
.search-box svg {
|
|
186
|
-
position: absolute;
|
|
187
|
-
|
|
188
|
-
top: 50%;
|
|
189
|
-
transform: translateY(-50%);
|
|
190
|
-
width: 14px;
|
|
191
|
-
height: 14px;
|
|
192
|
-
color: var(--muted);
|
|
193
|
-
pointer-events: none;
|
|
176
|
+
position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
|
|
177
|
+
width: 13px; height: 13px; color: var(--muted); pointer-events: none;
|
|
194
178
|
}
|
|
195
179
|
#search-input {
|
|
196
180
|
width: 100%;
|
|
197
|
-
background: var(--
|
|
198
|
-
border: 1px solid var(--
|
|
181
|
+
background: var(--surface);
|
|
182
|
+
border: 1px solid var(--border2);
|
|
199
183
|
border-radius: var(--radius);
|
|
200
|
-
padding: 7px 9px 7px
|
|
184
|
+
padding: 7px 9px 7px 28px;
|
|
201
185
|
font-size: 12.5px;
|
|
202
|
-
font-family: '
|
|
186
|
+
font-family: 'Fira Code', monospace;
|
|
203
187
|
color: var(--text);
|
|
204
188
|
outline: none;
|
|
205
|
-
transition:
|
|
206
|
-
border-color 0.15s,
|
|
207
|
-
box-shadow 0.15s;
|
|
208
|
-
}
|
|
209
|
-
#search-input::placeholder {
|
|
210
|
-
color: var(--muted);
|
|
189
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
211
190
|
}
|
|
191
|
+
#search-input::placeholder { color: var(--muted); }
|
|
212
192
|
#search-input:focus {
|
|
213
193
|
border-color: var(--accent);
|
|
214
|
-
box-shadow: 0 0 0 2px rgba(
|
|
194
|
+
box-shadow: 0 0 0 2px rgba(240,160,48,0.1);
|
|
215
195
|
}
|
|
216
196
|
#search-meta {
|
|
217
|
-
display: flex;
|
|
218
|
-
align-items: center;
|
|
219
|
-
justify-content: space-between;
|
|
220
|
-
}
|
|
221
|
-
.strat-row {
|
|
222
|
-
display: flex;
|
|
223
|
-
gap: 4px;
|
|
197
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
224
198
|
}
|
|
199
|
+
.strat-row { display: flex; gap: 3px; }
|
|
225
200
|
.strat-btn {
|
|
226
201
|
padding: 3px 8px;
|
|
227
|
-
font-size: 10px;
|
|
228
|
-
|
|
229
|
-
border: 1px solid var(--border);
|
|
230
|
-
border-radius: 4px;
|
|
202
|
+
font-size: 10px; font-weight: 600;
|
|
203
|
+
border: 1px solid var(--border2); border-radius: 4px;
|
|
231
204
|
cursor: pointer;
|
|
232
|
-
background: var(--surface2);
|
|
233
|
-
color: var(--muted2);
|
|
205
|
+
background: var(--surface2); color: var(--muted2);
|
|
234
206
|
transition: all 0.12s;
|
|
235
|
-
font-family: '
|
|
236
|
-
|
|
237
|
-
.strat-btn:hover {
|
|
238
|
-
color: var(--text);
|
|
239
|
-
border-color: var(--border2);
|
|
207
|
+
font-family: 'Syne', sans-serif;
|
|
208
|
+
letter-spacing: 0.03em;
|
|
240
209
|
}
|
|
210
|
+
.strat-btn:hover { color: var(--text); border-color: var(--muted); }
|
|
241
211
|
.strat-btn.active {
|
|
242
|
-
background: rgba(
|
|
243
|
-
border-color: rgba(
|
|
212
|
+
background: rgba(240,160,48,0.1);
|
|
213
|
+
border-color: rgba(240,160,48,0.4);
|
|
244
214
|
color: var(--accent);
|
|
245
215
|
}
|
|
246
|
-
#search-spinner {
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
#search-spinner svg {
|
|
250
|
-
animation: spin 0.7s linear infinite;
|
|
251
|
-
display: block;
|
|
252
|
-
}
|
|
216
|
+
#search-spinner { display: none; }
|
|
217
|
+
#search-spinner svg { animation: spin 0.7s linear infinite; display: block; }
|
|
253
218
|
|
|
254
219
|
/* Kind filters */
|
|
255
220
|
#kind-filters {
|
|
256
221
|
padding: 7px 10px;
|
|
257
222
|
border-bottom: 1px solid var(--border);
|
|
258
|
-
display: flex;
|
|
259
|
-
flex-wrap: wrap;
|
|
260
|
-
gap: 4px;
|
|
223
|
+
display: flex; flex-wrap: wrap; gap: 3px;
|
|
261
224
|
flex-shrink: 0;
|
|
262
225
|
}
|
|
263
226
|
.kind-tag {
|
|
264
|
-
padding: 2px 8px;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
cursor: pointer;
|
|
270
|
-
transition: all 0.12s;
|
|
271
|
-
color: var(--muted2);
|
|
272
|
-
background: var(--surface2);
|
|
273
|
-
}
|
|
274
|
-
.kind-tag:hover {
|
|
275
|
-
color: var(--text);
|
|
276
|
-
border-color: var(--border2);
|
|
277
|
-
}
|
|
278
|
-
.kind-tag.active {
|
|
279
|
-
color: #fff;
|
|
227
|
+
padding: 2px 8px; font-size: 10px; font-weight: 700;
|
|
228
|
+
border-radius: 4px; border: 1px solid transparent;
|
|
229
|
+
cursor: pointer; transition: all 0.12s;
|
|
230
|
+
color: var(--muted2); background: var(--surface2);
|
|
231
|
+
letter-spacing: 0.04em;
|
|
280
232
|
}
|
|
233
|
+
.kind-tag:hover { color: var(--text); border-color: var(--border2); }
|
|
234
|
+
.kind-tag.active { color: #fff; }
|
|
281
235
|
|
|
282
236
|
/* List header */
|
|
283
237
|
#list-header {
|
|
284
|
-
padding:
|
|
285
|
-
display: flex;
|
|
286
|
-
align-items: center;
|
|
287
|
-
justify-content: space-between;
|
|
238
|
+
padding: 5px 10px 5px;
|
|
239
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
288
240
|
border-bottom: 1px solid var(--border);
|
|
289
241
|
}
|
|
290
242
|
#list-header .h-label {
|
|
291
|
-
font-size:
|
|
292
|
-
|
|
293
|
-
text-transform: uppercase;
|
|
294
|
-
letter-spacing: 0.08em;
|
|
243
|
+
font-size: 9px; font-weight: 700;
|
|
244
|
+
text-transform: uppercase; letter-spacing: 0.12em;
|
|
295
245
|
color: var(--muted);
|
|
296
246
|
}
|
|
297
247
|
#list-count {
|
|
298
|
-
font-size: 10px;
|
|
299
|
-
font-family: 'JetBrains Mono', monospace;
|
|
300
|
-
color: var(--muted);
|
|
248
|
+
font-size: 10px; font-family: 'Fira Code', monospace; color: var(--muted);
|
|
301
249
|
}
|
|
302
250
|
|
|
303
251
|
/* Memory list */
|
|
304
252
|
#memory-list {
|
|
305
|
-
flex: 1;
|
|
306
|
-
overflow-y: auto;
|
|
253
|
+
flex: 1; overflow-y: auto;
|
|
307
254
|
padding: 6px;
|
|
308
|
-
display: flex;
|
|
309
|
-
flex-direction: column;
|
|
310
|
-
gap: 3px;
|
|
255
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
311
256
|
}
|
|
312
257
|
|
|
313
258
|
/* Memory card */
|
|
314
259
|
.mem-card {
|
|
315
|
-
padding:
|
|
260
|
+
padding: 10px 10px 8px;
|
|
316
261
|
border-radius: 6px;
|
|
317
262
|
background: var(--surface2);
|
|
318
263
|
border: 1px solid var(--border);
|
|
319
264
|
cursor: pointer;
|
|
320
|
-
transition:
|
|
321
|
-
border-color 0.12s,
|
|
322
|
-
background 0.12s;
|
|
265
|
+
transition: border-color 0.12s, background 0.12s, transform 0.1s;
|
|
323
266
|
position: relative;
|
|
324
267
|
overflow: hidden;
|
|
325
268
|
}
|
|
269
|
+
/* kind color bar on left */
|
|
326
270
|
.mem-card::before {
|
|
327
271
|
content: '';
|
|
328
|
-
position: absolute;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
272
|
+
position: absolute; left: 0; top: 0; bottom: 0;
|
|
273
|
+
width: 2.5px;
|
|
274
|
+
border-radius: 6px 0 0 6px;
|
|
275
|
+
background: var(--card-accent, var(--muted));
|
|
276
|
+
opacity: 0.6;
|
|
277
|
+
transition: opacity 0.12s;
|
|
334
278
|
}
|
|
335
279
|
.mem-card:hover {
|
|
336
280
|
border-color: var(--border2);
|
|
337
281
|
background: var(--surface3);
|
|
338
282
|
}
|
|
283
|
+
.mem-card:hover::before { opacity: 1; }
|
|
339
284
|
.mem-card.selected {
|
|
340
285
|
border-color: var(--accent);
|
|
286
|
+
background: rgba(240,160,48,0.04);
|
|
341
287
|
}
|
|
342
|
-
.mem-card.selected::before {
|
|
343
|
-
background: var(--accent);
|
|
344
|
-
}
|
|
288
|
+
.mem-card.selected::before { background: var(--accent); opacity: 1; }
|
|
345
289
|
|
|
346
290
|
.mc-head {
|
|
347
|
-
display: flex;
|
|
348
|
-
|
|
349
|
-
gap: 7px;
|
|
350
|
-
margin-bottom: 3px;
|
|
291
|
+
display: flex; align-items: flex-start; gap: 6px;
|
|
292
|
+
margin-bottom: 4px;
|
|
351
293
|
}
|
|
352
294
|
.mc-title {
|
|
353
|
-
font-size: 12.5px;
|
|
354
|
-
|
|
355
|
-
color: var(--text);
|
|
356
|
-
line-height: 1.35;
|
|
357
|
-
flex: 1;
|
|
358
|
-
min-width: 0;
|
|
295
|
+
font-size: 12.5px; font-weight: 600; color: var(--text);
|
|
296
|
+
line-height: 1.35; flex: 1; min-width: 0;
|
|
359
297
|
}
|
|
360
298
|
.mc-kind {
|
|
361
299
|
flex-shrink: 0;
|
|
362
|
-
font-size:
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
letter-spacing: 0.08em;
|
|
366
|
-
padding: 2px 6px;
|
|
367
|
-
border-radius: 3px;
|
|
300
|
+
font-size: 8.5px; font-weight: 700;
|
|
301
|
+
text-transform: uppercase; letter-spacing: 0.1em;
|
|
302
|
+
padding: 2px 6px; border-radius: 3px;
|
|
368
303
|
}
|
|
369
304
|
.mc-body {
|
|
370
|
-
font-size: 11px;
|
|
371
|
-
|
|
372
|
-
line-height: 1.4;
|
|
305
|
+
font-size: 11px; color: var(--muted2);
|
|
306
|
+
line-height: 1.45;
|
|
373
307
|
overflow: hidden;
|
|
374
308
|
display: -webkit-box;
|
|
375
309
|
-webkit-line-clamp: 2;
|
|
376
310
|
-webkit-box-orient: vertical;
|
|
377
311
|
}
|
|
378
312
|
.mc-footer {
|
|
379
|
-
display: flex;
|
|
380
|
-
align-items: center;
|
|
381
|
-
gap: 5px;
|
|
382
|
-
margin-top: 5px;
|
|
313
|
+
display: flex; align-items: center; gap: 5px; margin-top: 6px;
|
|
383
314
|
}
|
|
384
315
|
.mc-time {
|
|
385
|
-
font-size:
|
|
386
|
-
|
|
387
|
-
font-family: 'JetBrains Mono', monospace;
|
|
388
|
-
}
|
|
389
|
-
.mc-tags {
|
|
390
|
-
display: flex;
|
|
391
|
-
gap: 3px;
|
|
392
|
-
flex-wrap: wrap;
|
|
393
|
-
flex: 1;
|
|
316
|
+
font-size: 9.5px; color: var(--muted);
|
|
317
|
+
font-family: 'Fira Code', monospace;
|
|
394
318
|
}
|
|
319
|
+
.mc-tags { display: flex; gap: 3px; flex-wrap: wrap; flex: 1; }
|
|
395
320
|
.mc-tag {
|
|
396
|
-
font-size: 9px;
|
|
397
|
-
font-weight: 600;
|
|
321
|
+
font-size: 9px; font-weight: 600;
|
|
398
322
|
padding: 1px 5px;
|
|
399
323
|
background: var(--surface3);
|
|
400
324
|
border: 1px solid var(--border2);
|
|
401
|
-
border-radius: 3px;
|
|
402
|
-
|
|
325
|
+
border-radius: 3px; color: var(--muted);
|
|
326
|
+
font-family: 'Fira Code', monospace;
|
|
403
327
|
}
|
|
404
328
|
.mc-forget {
|
|
405
329
|
display: none;
|
|
406
|
-
width: 20px;
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
border
|
|
411
|
-
cursor: pointer;
|
|
412
|
-
background: rgba(239, 88, 88, 0.08);
|
|
413
|
-
border: 1px solid rgba(239, 88, 88, 0.2);
|
|
330
|
+
width: 20px; height: 20px;
|
|
331
|
+
align-items: center; justify-content: center;
|
|
332
|
+
border-radius: 4px; cursor: pointer;
|
|
333
|
+
background: rgba(232,104,90,0.08);
|
|
334
|
+
border: 1px solid rgba(232,104,90,0.2);
|
|
414
335
|
color: var(--accent-r);
|
|
415
|
-
font-size:
|
|
336
|
+
font-size: 13px; font-weight: 300;
|
|
416
337
|
transition: background 0.12s;
|
|
417
338
|
flex-shrink: 0;
|
|
418
|
-
position: absolute;
|
|
419
|
-
|
|
420
|
-
right: 7px;
|
|
421
|
-
}
|
|
422
|
-
.mem-card:hover .mc-forget {
|
|
423
|
-
display: flex;
|
|
424
|
-
}
|
|
425
|
-
.mc-forget:hover {
|
|
426
|
-
background: rgba(239, 88, 88, 0.18);
|
|
339
|
+
position: absolute; top: 7px; right: 7px;
|
|
340
|
+
line-height: 1;
|
|
427
341
|
}
|
|
342
|
+
.mem-card:hover .mc-forget { display: flex; }
|
|
343
|
+
.mc-forget:hover { background: rgba(232,104,90,0.2); }
|
|
428
344
|
|
|
429
|
-
/*
|
|
345
|
+
/* ══ MAIN ══════════════════════════════════════════════════════════ */
|
|
430
346
|
#main {
|
|
431
|
-
flex: 1;
|
|
432
|
-
|
|
433
|
-
flex-direction: column;
|
|
434
|
-
overflow: hidden;
|
|
435
|
-
min-width: 0;
|
|
347
|
+
flex: 1; display: flex; flex-direction: column;
|
|
348
|
+
overflow: hidden; min-width: 0;
|
|
436
349
|
}
|
|
437
350
|
|
|
438
351
|
/* Tab bar */
|
|
439
352
|
#tab-bar {
|
|
440
|
-
height:
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
display: flex;
|
|
444
|
-
align-items: center;
|
|
445
|
-
flex-shrink: 0;
|
|
353
|
+
height: 40px;
|
|
354
|
+
padding: 0 14px;
|
|
355
|
+
display: flex; align-items: center; flex-shrink: 0;
|
|
446
356
|
background: var(--surface);
|
|
447
|
-
|
|
357
|
+
border-bottom: 1px solid var(--border);
|
|
358
|
+
gap: 0;
|
|
448
359
|
}
|
|
449
360
|
.tab-btn {
|
|
450
|
-
padding:
|
|
451
|
-
|
|
452
|
-
font-weight: 600;
|
|
361
|
+
padding: 0 14px;
|
|
362
|
+
height: 100%;
|
|
363
|
+
font-size: 11.5px; font-weight: 600;
|
|
453
364
|
color: var(--muted2);
|
|
454
365
|
border: none;
|
|
455
|
-
border-radius: 5px;
|
|
456
366
|
background: none;
|
|
457
367
|
cursor: pointer;
|
|
458
|
-
transition:
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
.tab-btn:hover {
|
|
463
|
-
color: var(--text);
|
|
464
|
-
background: var(--surface2);
|
|
368
|
+
transition: color 0.12s;
|
|
369
|
+
position: relative;
|
|
370
|
+
font-family: 'Syne', sans-serif;
|
|
371
|
+
letter-spacing: 0.04em;
|
|
465
372
|
}
|
|
466
|
-
.tab-btn
|
|
467
|
-
|
|
468
|
-
|
|
373
|
+
.tab-btn::after {
|
|
374
|
+
content: '';
|
|
375
|
+
position: absolute;
|
|
376
|
+
bottom: 0; left: 14px; right: 14px;
|
|
377
|
+
height: 2px;
|
|
378
|
+
background: var(--accent);
|
|
379
|
+
border-radius: 2px 2px 0 0;
|
|
380
|
+
opacity: 0;
|
|
381
|
+
transition: opacity 0.15s;
|
|
469
382
|
}
|
|
383
|
+
.tab-btn:hover { color: var(--text); }
|
|
384
|
+
.tab-btn.active { color: var(--accent); }
|
|
385
|
+
.tab-btn.active::after { opacity: 1; }
|
|
470
386
|
#tab-hint {
|
|
471
387
|
margin-left: auto;
|
|
472
|
-
font-size: 10px;
|
|
473
|
-
|
|
474
|
-
padding-right:
|
|
475
|
-
font-family: 'JetBrains Mono', monospace;
|
|
388
|
+
font-size: 10px; color: var(--muted);
|
|
389
|
+
font-family: 'Fira Code', monospace;
|
|
390
|
+
padding-right: 4px;
|
|
476
391
|
}
|
|
477
392
|
|
|
478
393
|
/* Tab panes */
|
|
479
|
-
#tab-graph {
|
|
480
|
-
|
|
481
|
-
display: flex;
|
|
482
|
-
flex-direction: column;
|
|
483
|
-
overflow: hidden;
|
|
484
|
-
}
|
|
485
|
-
#tab-analytics {
|
|
486
|
-
display: none;
|
|
487
|
-
flex: 1;
|
|
488
|
-
overflow-y: auto;
|
|
489
|
-
padding: 14px;
|
|
490
|
-
}
|
|
394
|
+
#tab-graph { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
|
395
|
+
#tab-analytics { display: none; flex: 1; overflow-y: auto; padding: 14px; }
|
|
491
396
|
|
|
492
397
|
/* Graph */
|
|
493
398
|
#graph-container {
|
|
@@ -495,8 +400,20 @@
|
|
|
495
400
|
margin: 10px;
|
|
496
401
|
border: 1px solid var(--border);
|
|
497
402
|
border-radius: var(--radius);
|
|
498
|
-
background: #
|
|
403
|
+
background: #09080500;
|
|
499
404
|
overflow: hidden;
|
|
405
|
+
position: relative;
|
|
406
|
+
}
|
|
407
|
+
/* ambient glow behind graph */
|
|
408
|
+
#graph-container::before {
|
|
409
|
+
content: '';
|
|
410
|
+
position: absolute;
|
|
411
|
+
top: 50%; left: 50%;
|
|
412
|
+
width: 400px; height: 300px;
|
|
413
|
+
transform: translate(-50%, -50%);
|
|
414
|
+
background: radial-gradient(ellipse, rgba(240,160,48,0.04) 0%, transparent 70%);
|
|
415
|
+
pointer-events: none;
|
|
416
|
+
z-index: 0;
|
|
500
417
|
}
|
|
501
418
|
|
|
502
419
|
/* Detail panel */
|
|
@@ -504,90 +421,59 @@
|
|
|
504
421
|
display: none;
|
|
505
422
|
border-top: 1px solid var(--border);
|
|
506
423
|
background: var(--surface);
|
|
507
|
-
overflow-y: auto;
|
|
508
|
-
|
|
509
|
-
|
|
424
|
+
overflow-y: auto; flex-shrink: 0;
|
|
425
|
+
max-height: 36vh;
|
|
426
|
+
animation: slideUp 0.18s ease forwards;
|
|
510
427
|
}
|
|
511
|
-
|
|
512
|
-
|
|
428
|
+
@keyframes slideUp {
|
|
429
|
+
from { opacity: 0; transform: translateY(6px); }
|
|
430
|
+
to { opacity: 1; transform: translateY(0); }
|
|
513
431
|
}
|
|
432
|
+
#detail-inner { padding: 16px 20px; }
|
|
514
433
|
.detail-head {
|
|
515
|
-
display: flex;
|
|
516
|
-
align-items: flex-start;
|
|
517
|
-
gap: 10px;
|
|
518
|
-
margin-bottom: 12px;
|
|
434
|
+
display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px;
|
|
519
435
|
}
|
|
520
436
|
.detail-title {
|
|
521
|
-
font-size: 15px;
|
|
522
|
-
|
|
523
|
-
color: var(--text);
|
|
524
|
-
flex: 1;
|
|
525
|
-
line-height: 1.3;
|
|
437
|
+
font-size: 15px; font-weight: 700; color: var(--text);
|
|
438
|
+
flex: 1; line-height: 1.3;
|
|
526
439
|
}
|
|
527
440
|
.detail-meta {
|
|
528
|
-
display: flex;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
font-size: 11px;
|
|
532
|
-
color: var(--muted);
|
|
533
|
-
font-family: 'JetBrains Mono', monospace;
|
|
441
|
+
display: flex; gap: 16px; margin-bottom: 12px;
|
|
442
|
+
font-size: 10.5px; color: var(--muted);
|
|
443
|
+
font-family: 'Fira Code', monospace;
|
|
534
444
|
}
|
|
535
445
|
.detail-body {
|
|
536
|
-
font-size: 12.5px;
|
|
537
|
-
|
|
538
|
-
line-height: 1.65;
|
|
539
|
-
white-space: pre-wrap;
|
|
540
|
-
word-break: break-word;
|
|
446
|
+
font-size: 12.5px; color: var(--muted2);
|
|
447
|
+
line-height: 1.7; white-space: pre-wrap; word-break: break-word;
|
|
541
448
|
margin-bottom: 12px;
|
|
542
449
|
}
|
|
543
|
-
.detail-chips {
|
|
544
|
-
display: flex;
|
|
545
|
-
flex-wrap: wrap;
|
|
546
|
-
gap: 5px;
|
|
547
|
-
}
|
|
450
|
+
.detail-chips { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
548
451
|
.detail-chip {
|
|
549
|
-
font-size: 10px;
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
border-radius: 4px;
|
|
553
|
-
background: var(--surface2);
|
|
554
|
-
border: 1px solid var(--border2);
|
|
452
|
+
font-size: 10px; font-weight: 500;
|
|
453
|
+
padding: 2px 8px; border-radius: 4px;
|
|
454
|
+
background: var(--surface2); border: 1px solid var(--border2);
|
|
555
455
|
color: var(--muted2);
|
|
456
|
+
font-family: 'Fira Code', monospace;
|
|
556
457
|
}
|
|
557
458
|
.detail-chip.file {
|
|
558
|
-
color: var(--accent-c);
|
|
559
|
-
|
|
560
|
-
background: rgba(34, 208, 200, 0.07);
|
|
459
|
+
color: var(--accent-c); border-color: rgba(56,189,248,0.3);
|
|
460
|
+
background: rgba(56,189,248,0.06);
|
|
561
461
|
}
|
|
562
462
|
.detail-close {
|
|
563
|
-
width: 24px;
|
|
564
|
-
height: 24px;
|
|
565
|
-
border-radius: 5px;
|
|
463
|
+
width: 24px; height: 24px; border-radius: 5px;
|
|
566
464
|
flex-shrink: 0;
|
|
567
|
-
display: flex;
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
border: 1px solid var(--border);
|
|
572
|
-
cursor: pointer;
|
|
573
|
-
color: var(--muted2);
|
|
574
|
-
transition: color 0.12s;
|
|
575
|
-
}
|
|
576
|
-
.detail-close:hover {
|
|
577
|
-
color: var(--text);
|
|
465
|
+
display: flex; align-items: center; justify-content: center;
|
|
466
|
+
background: var(--surface2); border: 1px solid var(--border);
|
|
467
|
+
cursor: pointer; color: var(--muted2);
|
|
468
|
+
transition: color 0.12s, border-color 0.12s;
|
|
578
469
|
}
|
|
470
|
+
.detail-close:hover { color: var(--text); border-color: var(--border2); }
|
|
579
471
|
|
|
580
472
|
/* Analytics */
|
|
581
473
|
.a-grid {
|
|
582
|
-
display: grid;
|
|
583
|
-
grid-template-columns: 1fr 1fr;
|
|
584
|
-
gap: 10px;
|
|
585
|
-
}
|
|
586
|
-
@media (max-width: 900px) {
|
|
587
|
-
.a-grid {
|
|
588
|
-
grid-template-columns: 1fr;
|
|
589
|
-
}
|
|
474
|
+
display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
|
|
590
475
|
}
|
|
476
|
+
@media (max-width: 900px) { .a-grid { grid-template-columns: 1fr; } }
|
|
591
477
|
.a-card {
|
|
592
478
|
background: var(--surface);
|
|
593
479
|
border: 1px solid var(--border);
|
|
@@ -595,215 +481,97 @@
|
|
|
595
481
|
padding: 16px;
|
|
596
482
|
}
|
|
597
483
|
.a-title {
|
|
598
|
-
font-size:
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
letter-spacing: 0.1em;
|
|
602
|
-
color: var(--muted);
|
|
603
|
-
margin-bottom: 14px;
|
|
484
|
+
font-size: 9px; font-weight: 700;
|
|
485
|
+
text-transform: uppercase; letter-spacing: 0.14em;
|
|
486
|
+
color: var(--muted); margin-bottom: 14px;
|
|
604
487
|
}
|
|
605
488
|
.bar-row {
|
|
606
|
-
display: flex;
|
|
607
|
-
align-items: center;
|
|
608
|
-
gap: 8px;
|
|
609
|
-
margin-bottom: 8px;
|
|
489
|
+
display: flex; align-items: center; gap: 8px; margin-bottom: 9px;
|
|
610
490
|
}
|
|
611
491
|
.bar-label {
|
|
612
|
-
font-size: 11.5px;
|
|
613
|
-
|
|
614
|
-
width: 100px;
|
|
615
|
-
flex-shrink: 0;
|
|
492
|
+
font-size: 11.5px; font-weight: 600;
|
|
493
|
+
width: 100px; flex-shrink: 0;
|
|
616
494
|
}
|
|
617
495
|
.bar-track {
|
|
618
|
-
flex: 1;
|
|
619
|
-
|
|
620
|
-
background: var(--surface3);
|
|
621
|
-
border-radius: 2px;
|
|
622
|
-
overflow: hidden;
|
|
496
|
+
flex: 1; height: 3px;
|
|
497
|
+
background: var(--surface3); border-radius: 2px; overflow: hidden;
|
|
623
498
|
}
|
|
624
499
|
.bar-fill {
|
|
625
|
-
height: 100%;
|
|
626
|
-
|
|
627
|
-
width: 0;
|
|
628
|
-
transition: width 0.65s cubic-bezier(0.16, 1, 0.3, 1);
|
|
500
|
+
height: 100%; border-radius: 2px;
|
|
501
|
+
width: 0; transition: width 0.7s cubic-bezier(0.16,1,0.3,1);
|
|
629
502
|
}
|
|
630
503
|
.bar-count {
|
|
631
|
-
font-size: 11px;
|
|
632
|
-
|
|
633
|
-
color: var(--muted2);
|
|
634
|
-
width: 36px;
|
|
635
|
-
text-align: right;
|
|
636
|
-
flex-shrink: 0;
|
|
504
|
+
font-size: 11px; font-family: 'Fira Code', monospace;
|
|
505
|
+
color: var(--muted2); width: 36px; text-align: right; flex-shrink: 0;
|
|
637
506
|
}
|
|
638
507
|
.cache-stat {
|
|
639
|
-
display: flex;
|
|
640
|
-
|
|
641
|
-
align-items: center;
|
|
642
|
-
padding: 7px 0;
|
|
643
|
-
border-bottom: 1px solid var(--border);
|
|
644
|
-
}
|
|
645
|
-
.cache-stat:last-child {
|
|
646
|
-
border-bottom: none;
|
|
647
|
-
}
|
|
648
|
-
.cache-stat-lbl {
|
|
649
|
-
font-size: 12px;
|
|
650
|
-
color: var(--muted2);
|
|
651
|
-
}
|
|
652
|
-
.cache-stat-val {
|
|
653
|
-
font-family: 'JetBrains Mono', monospace;
|
|
654
|
-
font-size: 13px;
|
|
655
|
-
font-weight: 600;
|
|
508
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
509
|
+
padding: 8px 0; border-bottom: 1px solid var(--border);
|
|
656
510
|
}
|
|
511
|
+
.cache-stat:last-child { border-bottom: none; }
|
|
512
|
+
.cache-stat-lbl { font-size: 12px; color: var(--muted2); }
|
|
513
|
+
.cache-stat-val { font-family: 'Fira Code', monospace; font-size: 13px; font-weight: 500; }
|
|
657
514
|
.file-row {
|
|
658
|
-
display: flex;
|
|
659
|
-
|
|
660
|
-
gap: 8px;
|
|
661
|
-
padding: 6px 0;
|
|
662
|
-
border-bottom: 1px solid var(--border);
|
|
663
|
-
}
|
|
664
|
-
.file-row:last-child {
|
|
665
|
-
border-bottom: none;
|
|
515
|
+
display: flex; align-items: center; gap: 8px;
|
|
516
|
+
padding: 6px 0; border-bottom: 1px solid var(--border);
|
|
666
517
|
}
|
|
518
|
+
.file-row:last-child { border-bottom: none; }
|
|
667
519
|
.file-name {
|
|
668
|
-
font-size: 11px;
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
flex: 1;
|
|
672
|
-
overflow: hidden;
|
|
673
|
-
text-overflow: ellipsis;
|
|
674
|
-
white-space: nowrap;
|
|
675
|
-
min-width: 0;
|
|
520
|
+
font-size: 11px; font-family: 'Fira Code', monospace;
|
|
521
|
+
color: var(--text); flex: 1;
|
|
522
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
|
|
676
523
|
}
|
|
677
524
|
.file-count {
|
|
678
|
-
font-size: 11px;
|
|
679
|
-
|
|
680
|
-
color: var(--muted2);
|
|
681
|
-
flex-shrink: 0;
|
|
525
|
+
font-size: 11px; font-family: 'Fira Code', monospace;
|
|
526
|
+
color: var(--muted2); flex-shrink: 0;
|
|
682
527
|
}
|
|
683
528
|
|
|
684
529
|
/* Empty state */
|
|
685
530
|
.empty {
|
|
686
|
-
display: flex;
|
|
687
|
-
|
|
688
|
-
align
|
|
689
|
-
justify-content: center;
|
|
690
|
-
gap: 10px;
|
|
691
|
-
padding: 48px 24px;
|
|
692
|
-
color: var(--muted);
|
|
693
|
-
text-align: center;
|
|
694
|
-
}
|
|
695
|
-
.empty-icon {
|
|
696
|
-
opacity: 0.25;
|
|
697
|
-
margin-bottom: 2px;
|
|
698
|
-
}
|
|
699
|
-
.empty-title {
|
|
700
|
-
font-size: 13px;
|
|
701
|
-
font-weight: 600;
|
|
702
|
-
color: var(--muted2);
|
|
531
|
+
display: flex; flex-direction: column; align-items: center;
|
|
532
|
+
justify-content: center; gap: 8px;
|
|
533
|
+
padding: 48px 24px; color: var(--muted); text-align: center;
|
|
703
534
|
}
|
|
535
|
+
.empty-icon { opacity: 0.2; margin-bottom: 4px; }
|
|
536
|
+
.empty-title { font-size: 13px; font-weight: 600; color: var(--muted2); }
|
|
704
537
|
.empty-sub {
|
|
705
|
-
font-size: 11.5px;
|
|
706
|
-
|
|
707
|
-
max-width: 220px;
|
|
708
|
-
line-height: 1.5;
|
|
538
|
+
font-size: 11.5px; color: var(--muted);
|
|
539
|
+
max-width: 220px; line-height: 1.55;
|
|
709
540
|
}
|
|
710
541
|
|
|
711
542
|
/* Skeleton */
|
|
712
543
|
.skel {
|
|
713
|
-
background: var(--surface2);
|
|
714
|
-
|
|
715
|
-
animation: shimmer 1.5s ease-in-out infinite;
|
|
544
|
+
background: var(--surface2); border-radius: 5px;
|
|
545
|
+
animation: shimmer 1.8s ease-in-out infinite;
|
|
716
546
|
}
|
|
717
547
|
|
|
718
|
-
/* Animations */
|
|
719
|
-
@keyframes spin {
|
|
720
|
-
to {
|
|
721
|
-
transform: rotate(360deg);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
548
|
+
/* ── Animations ── */
|
|
549
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
724
550
|
@keyframes shimmer {
|
|
725
|
-
0%,
|
|
726
|
-
|
|
727
|
-
opacity: 0.4;
|
|
728
|
-
}
|
|
729
|
-
50% {
|
|
730
|
-
opacity: 0.7;
|
|
731
|
-
}
|
|
551
|
+
0%, 100% { opacity: 0.35; }
|
|
552
|
+
50% { opacity: 0.6; }
|
|
732
553
|
}
|
|
733
554
|
@keyframes fadeIn {
|
|
734
|
-
from {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
}
|
|
743
|
-
.
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
.
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
}
|
|
752
|
-
.
|
|
753
|
-
|
|
754
|
-
color: #9b72f5;
|
|
755
|
-
}
|
|
756
|
-
.kind-bugfix {
|
|
757
|
-
background: rgba(239, 88, 88, 0.12);
|
|
758
|
-
color: #ef5858;
|
|
759
|
-
}
|
|
760
|
-
.kind-command {
|
|
761
|
-
background: rgba(240, 166, 56, 0.12);
|
|
762
|
-
color: #f0a638;
|
|
763
|
-
}
|
|
764
|
-
.kind-file_summary {
|
|
765
|
-
background: rgba(34, 208, 200, 0.1);
|
|
766
|
-
color: #22d0c8;
|
|
767
|
-
}
|
|
768
|
-
.kind-other {
|
|
769
|
-
background: rgba(107, 122, 150, 0.12);
|
|
770
|
-
color: #6b7a96;
|
|
771
|
-
}
|
|
772
|
-
.kf-decision {
|
|
773
|
-
border-color: rgba(79, 141, 255, 0.3);
|
|
774
|
-
background: rgba(79, 141, 255, 0.08);
|
|
775
|
-
color: #4f8dff;
|
|
776
|
-
}
|
|
777
|
-
.kf-learning {
|
|
778
|
-
border-color: rgba(155, 114, 245, 0.3);
|
|
779
|
-
background: rgba(155, 114, 245, 0.08);
|
|
780
|
-
color: #9b72f5;
|
|
781
|
-
}
|
|
782
|
-
.kf-bugfix {
|
|
783
|
-
border-color: rgba(239, 88, 88, 0.3);
|
|
784
|
-
background: rgba(239, 88, 88, 0.08);
|
|
785
|
-
color: #ef5858;
|
|
786
|
-
}
|
|
787
|
-
.kf-command {
|
|
788
|
-
border-color: rgba(240, 166, 56, 0.3);
|
|
789
|
-
background: rgba(240, 166, 56, 0.08);
|
|
790
|
-
color: #f0a638;
|
|
791
|
-
}
|
|
792
|
-
.kf-file_summary {
|
|
793
|
-
border-color: rgba(34, 208, 200, 0.3);
|
|
794
|
-
background: rgba(34, 208, 200, 0.07);
|
|
795
|
-
color: #22d0c8;
|
|
796
|
-
}
|
|
797
|
-
.kf-other {
|
|
798
|
-
border-color: rgba(107, 122, 150, 0.3);
|
|
799
|
-
background: rgba(107, 122, 150, 0.08);
|
|
800
|
-
color: #6b7a96;
|
|
801
|
-
}
|
|
802
|
-
.kf-all {
|
|
803
|
-
border-color: rgba(79, 141, 255, 0.3);
|
|
804
|
-
background: rgba(79, 141, 255, 0.08);
|
|
805
|
-
color: #4f8dff;
|
|
806
|
-
}
|
|
555
|
+
from { opacity: 0; transform: translateY(3px); }
|
|
556
|
+
to { opacity: 1; transform: translateY(0); }
|
|
557
|
+
}
|
|
558
|
+
.fade-in { animation: fadeIn 0.14s ease forwards; }
|
|
559
|
+
|
|
560
|
+
/* ── Kind colour map ── */
|
|
561
|
+
.kind-decision { background: rgba(79,141,255,0.12); color: #6ba3ff; }
|
|
562
|
+
.kind-learning { background: rgba(167,139,250,0.12); color: #a78bfa; }
|
|
563
|
+
.kind-bugfix { background: rgba(232,104,90,0.12); color: #e8685a; }
|
|
564
|
+
.kind-command { background: rgba(240,160,48,0.12); color: #f0a030; }
|
|
565
|
+
.kind-file_summary { background: rgba(56,189,248,0.1); color: #38bdf8; }
|
|
566
|
+
.kind-other { background: rgba(107,99,80,0.15); color: #9e9278; }
|
|
567
|
+
|
|
568
|
+
.kf-decision { border-color: rgba(79,141,255,0.3); background: rgba(79,141,255,0.07); color: #6ba3ff; }
|
|
569
|
+
.kf-learning { border-color: rgba(167,139,250,0.3); background: rgba(167,139,250,0.07); color: #a78bfa; }
|
|
570
|
+
.kf-bugfix { border-color: rgba(232,104,90,0.3); background: rgba(232,104,90,0.07); color: #e8685a; }
|
|
571
|
+
.kf-command { border-color: rgba(240,160,48,0.3); background: rgba(240,160,48,0.07); color: #f0a030; }
|
|
572
|
+
.kf-file_summary { border-color: rgba(56,189,248,0.3); background: rgba(56,189,248,0.06); color: #38bdf8; }
|
|
573
|
+
.kf-other { border-color: rgba(107,99,80,0.3); background: rgba(107,99,80,0.07); color: #9e9278; }
|
|
574
|
+
.kf-all { border-color: rgba(240,160,48,0.35); background: rgba(240,160,48,0.08); color: #f0a030; }
|
|
807
575
|
</style>
|
|
808
576
|
</head>
|
|
809
577
|
<body>
|
|
@@ -811,17 +579,8 @@
|
|
|
811
579
|
<header id="header">
|
|
812
580
|
<div id="logo">
|
|
813
581
|
<div id="logo-mark">
|
|
814
|
-
<svg
|
|
815
|
-
|
|
816
|
-
fill="none"
|
|
817
|
-
stroke="currentColor"
|
|
818
|
-
stroke-width="2.2"
|
|
819
|
-
stroke-linecap="round"
|
|
820
|
-
stroke-linejoin="round"
|
|
821
|
-
>
|
|
822
|
-
<path
|
|
823
|
-
d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96-.46L7 19M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96-.46l.04-.54"
|
|
824
|
-
/>
|
|
582
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
|
583
|
+
<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96-.46L7 19M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96-.46l.04-.54" />
|
|
825
584
|
</svg>
|
|
826
585
|
</div>
|
|
827
586
|
somtum
|
|
@@ -831,112 +590,84 @@
|
|
|
831
590
|
<div id="stat-bar">
|
|
832
591
|
<div class="stat-pill">
|
|
833
592
|
<span class="s-lbl">Memories</span>
|
|
834
|
-
<span id="s-mem" class="s-val" style="color:
|
|
593
|
+
<span id="s-mem" class="s-val" style="color:var(--accent-g)">—</span>
|
|
835
594
|
</div>
|
|
836
595
|
<div class="stat-pill">
|
|
837
596
|
<span class="s-lbl">Saved</span>
|
|
838
|
-
<span id="s-saved" class="s-val" style="color:
|
|
597
|
+
<span id="s-saved" class="s-val" style="color:var(--accent)">—</span>
|
|
839
598
|
</div>
|
|
840
599
|
<div class="stat-pill">
|
|
841
600
|
<span class="s-lbl">Spent</span>
|
|
842
|
-
<span id="s-spent" class="s-val" style="color:
|
|
601
|
+
<span id="s-spent" class="s-val" style="color:var(--muted2)">—</span>
|
|
843
602
|
</div>
|
|
844
603
|
<div class="stat-pill">
|
|
845
604
|
<span class="s-lbl">ROI</span>
|
|
846
|
-
<span id="s-roi" class="s-val" style="color:
|
|
605
|
+
<span id="s-roi" class="s-val" style="color:var(--accent-v)">—</span>
|
|
847
606
|
</div>
|
|
848
607
|
<div class="stat-pill">
|
|
849
608
|
<span class="s-lbl">Cache</span>
|
|
850
|
-
<span id="s-cache" class="s-val" style="color:
|
|
609
|
+
<span id="s-cache" class="s-val" style="color:var(--accent-c)">—</span>
|
|
851
610
|
</div>
|
|
852
611
|
</div>
|
|
853
612
|
</header>
|
|
854
613
|
|
|
855
614
|
<!-- ═══ LAYOUT ══════════════════════════════════════════════════════ -->
|
|
856
615
|
<div id="layout">
|
|
857
|
-
<!-- ─── SIDEBAR
|
|
616
|
+
<!-- ─── SIDEBAR ─────────────────────────────────────────────────── -->
|
|
858
617
|
<aside id="sidebar">
|
|
859
|
-
<!-- Search -->
|
|
860
618
|
<div id="search-wrap">
|
|
861
619
|
<div class="search-box">
|
|
862
620
|
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
863
|
-
<circle cx="11" cy="11" r="7" />
|
|
864
|
-
<path d="M21 21l-5-5" stroke-linecap="round" />
|
|
621
|
+
<circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5" stroke-linecap="round"/>
|
|
865
622
|
</svg>
|
|
866
|
-
<input
|
|
867
|
-
id="search-input"
|
|
868
|
-
type="text"
|
|
869
|
-
placeholder="Search memories…"
|
|
870
|
-
autocomplete="off"
|
|
871
|
-
spellcheck="false"
|
|
872
|
-
/>
|
|
623
|
+
<input id="search-input" type="text" placeholder="Search memories…" autocomplete="off" spellcheck="false" />
|
|
873
624
|
</div>
|
|
874
625
|
<div id="search-meta">
|
|
875
626
|
<div class="strat-row" id="strategy-btns"></div>
|
|
876
627
|
<div id="search-spinner">
|
|
877
|
-
<svg
|
|
878
|
-
|
|
879
|
-
height="13"
|
|
880
|
-
viewBox="0 0 24 24"
|
|
881
|
-
fill="none"
|
|
882
|
-
stroke="var(--accent)"
|
|
883
|
-
stroke-width="2.5"
|
|
884
|
-
>
|
|
885
|
-
<path
|
|
886
|
-
d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"
|
|
887
|
-
/>
|
|
628
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2.5">
|
|
629
|
+
<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/>
|
|
888
630
|
</svg>
|
|
889
631
|
</div>
|
|
890
632
|
</div>
|
|
891
633
|
</div>
|
|
892
634
|
|
|
893
|
-
<!-- Kind filters -->
|
|
894
635
|
<div id="kind-filters"></div>
|
|
895
636
|
|
|
896
|
-
<!-- List header -->
|
|
897
637
|
<div id="list-header">
|
|
898
638
|
<span class="h-label">Memories</span>
|
|
899
639
|
<span id="list-count"></span>
|
|
900
640
|
</div>
|
|
901
641
|
|
|
902
|
-
<!-- Memory list -->
|
|
903
642
|
<div id="memory-list">
|
|
904
|
-
<div class="skel" style="height:
|
|
905
|
-
<div class="skel" style="height:
|
|
906
|
-
<div class="skel" style="height:
|
|
643
|
+
<div class="skel" style="height:66px;border-radius:6px"></div>
|
|
644
|
+
<div class="skel" style="height:54px;border-radius:6px;margin-top:2px"></div>
|
|
645
|
+
<div class="skel" style="height:70px;border-radius:6px;margin-top:2px"></div>
|
|
907
646
|
</div>
|
|
908
647
|
</aside>
|
|
909
648
|
|
|
910
|
-
<!-- ─── MAIN
|
|
649
|
+
<!-- ─── MAIN ─────────────────────────────────────────────────────── -->
|
|
911
650
|
<main id="main">
|
|
912
|
-
<!-- Tab bar -->
|
|
913
651
|
<div id="tab-bar">
|
|
914
652
|
<button class="tab-btn active" data-tab="graph">Graph</button>
|
|
915
653
|
<button class="tab-btn" data-tab="analytics">Analytics</button>
|
|
916
654
|
<span id="tab-hint"></span>
|
|
917
655
|
</div>
|
|
918
656
|
|
|
919
|
-
|
|
920
|
-
<div
|
|
921
|
-
style="flex: 1; display: flex; flex-direction: column; overflow: hidden; min-height: 0"
|
|
922
|
-
>
|
|
923
|
-
<!-- GRAPH TAB -->
|
|
657
|
+
<div style="flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0">
|
|
924
658
|
<div id="tab-graph">
|
|
925
659
|
<div id="graph-container"></div>
|
|
926
660
|
</div>
|
|
927
|
-
|
|
928
|
-
<!-- ANALYTICS TAB -->
|
|
929
661
|
<div id="tab-analytics">
|
|
930
662
|
<div class="a-grid" id="analytics-grid">
|
|
931
|
-
<div class="skel" style="height:
|
|
932
|
-
<div class="skel" style="height:
|
|
933
|
-
<div class="skel" style="height:
|
|
934
|
-
<div class="skel" style="height:
|
|
663
|
+
<div class="skel" style="height:180px"></div>
|
|
664
|
+
<div class="skel" style="height:180px"></div>
|
|
665
|
+
<div class="skel" style="height:200px"></div>
|
|
666
|
+
<div class="skel" style="height:200px"></div>
|
|
935
667
|
</div>
|
|
936
668
|
</div>
|
|
937
669
|
</div>
|
|
938
670
|
|
|
939
|
-
<!-- Detail panel -->
|
|
940
671
|
<div id="detail-panel">
|
|
941
672
|
<div id="detail-inner"></div>
|
|
942
673
|
</div>
|
|
@@ -946,27 +677,20 @@
|
|
|
946
677
|
<script>
|
|
947
678
|
// ─── State ───────────────────────────────────────────────────────────────────
|
|
948
679
|
const S = {
|
|
949
|
-
data: null,
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
selectedId: null,
|
|
954
|
-
searchQuery: '',
|
|
955
|
-
searchResults: null,
|
|
956
|
-
strategy: 'bm25',
|
|
957
|
-
debounce: null,
|
|
958
|
-
network: null,
|
|
959
|
-
analyticsRendered: false,
|
|
680
|
+
data: null, fullStats: null, activeTab: 'graph',
|
|
681
|
+
activeKind: 'all', selectedId: null, searchQuery: '',
|
|
682
|
+
searchResults: null, strategy: 'bm25', debounce: null,
|
|
683
|
+
network: null, analyticsRendered: false,
|
|
960
684
|
};
|
|
961
685
|
|
|
962
|
-
// ─── Kind colours (
|
|
686
|
+
// ─── Kind colours (vis-network nodes) ────────────────────────────────────────
|
|
963
687
|
const KIND_COLOR = {
|
|
964
|
-
decision:
|
|
965
|
-
learning:
|
|
966
|
-
bugfix:
|
|
967
|
-
command:
|
|
968
|
-
file_summary: { bg: '#
|
|
969
|
-
other:
|
|
688
|
+
decision: { bg: '#6ba3ff', border: '#4f8dff' },
|
|
689
|
+
learning: { bg: '#a78bfa', border: '#8b6fe8' },
|
|
690
|
+
bugfix: { bg: '#e8685a', border: '#d04838' },
|
|
691
|
+
command: { bg: '#f0a030', border: '#d48020' },
|
|
692
|
+
file_summary: { bg: '#38bdf8', border: '#1aa8e8' },
|
|
693
|
+
other: { bg: '#6b6350', border: '#504c3c' },
|
|
970
694
|
};
|
|
971
695
|
|
|
972
696
|
// ─── Utilities ────────────────────────────────────────────────────────────────
|
|
@@ -988,9 +712,7 @@
|
|
|
988
712
|
return `${Math.floor(hr / 24)}d ago`;
|
|
989
713
|
}
|
|
990
714
|
|
|
991
|
-
function kindClass(k) {
|
|
992
|
-
return 'kind-' + (k || 'other').replace(/[^a-z_]/g, '');
|
|
993
|
-
}
|
|
715
|
+
function kindClass(k) { return 'kind-' + (k || 'other').replace(/[^a-z_]/g, ''); }
|
|
994
716
|
|
|
995
717
|
function h(tag, attrs, ...children) {
|
|
996
718
|
const el = document.createElement(tag);
|
|
@@ -1018,9 +740,7 @@
|
|
|
1018
740
|
} catch (e) {
|
|
1019
741
|
document.getElementById('memory-list').innerHTML =
|
|
1020
742
|
'<div class="empty"><div class="empty-title">Could not load data</div>' +
|
|
1021
|
-
'<div class="empty-sub">' +
|
|
1022
|
-
e.message +
|
|
1023
|
-
'</div></div>';
|
|
743
|
+
'<div class="empty-sub">' + e.message + '</div></div>';
|
|
1024
744
|
}
|
|
1025
745
|
}
|
|
1026
746
|
|
|
@@ -1034,13 +754,10 @@
|
|
|
1034
754
|
document.getElementById('s-saved').textContent = fmtK(saved);
|
|
1035
755
|
document.getElementById('s-spent').textContent = fmtK(spent);
|
|
1036
756
|
document.getElementById('s-roi').textContent = roi;
|
|
1037
|
-
// cache from full stats, placeholder for now
|
|
1038
757
|
document.getElementById('s-cache').textContent = '—';
|
|
1039
|
-
|
|
1040
758
|
buildKindFilters(memories);
|
|
1041
759
|
renderList(memories);
|
|
1042
760
|
buildGraph(graph);
|
|
1043
|
-
// load full stats in background for cache pill
|
|
1044
761
|
loadFullStats();
|
|
1045
762
|
}
|
|
1046
763
|
|
|
@@ -1048,66 +765,41 @@
|
|
|
1048
765
|
function buildStrategyBtns() {
|
|
1049
766
|
const wrap = document.getElementById('strategy-btns');
|
|
1050
767
|
['bm25', 'hybrid', 'embeddings'].forEach((s) => {
|
|
1051
|
-
const btn = h(
|
|
1052
|
-
'
|
|
1053
|
-
{
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
btn.classList.add('active');
|
|
1059
|
-
if (S.searchQuery) runSearch(S.searchQuery);
|
|
1060
|
-
},
|
|
768
|
+
const btn = h('button', {
|
|
769
|
+
class: 'strat-btn' + (s === S.strategy ? ' active' : ''),
|
|
770
|
+
onclick: () => {
|
|
771
|
+
S.strategy = s;
|
|
772
|
+
wrap.querySelectorAll('.strat-btn').forEach((b) => b.classList.remove('active'));
|
|
773
|
+
btn.classList.add('active');
|
|
774
|
+
if (S.searchQuery) runSearch(S.searchQuery);
|
|
1061
775
|
},
|
|
1062
|
-
|
|
1063
|
-
);
|
|
776
|
+
}, s);
|
|
1064
777
|
wrap.append(btn);
|
|
1065
778
|
});
|
|
1066
779
|
}
|
|
1067
780
|
|
|
1068
781
|
// ─── Kind filter chips ────────────────────────────────────────────────────────
|
|
1069
|
-
const KIND_ORDER = [
|
|
1070
|
-
'all',
|
|
1071
|
-
'decision',
|
|
1072
|
-
'learning',
|
|
1073
|
-
'bugfix',
|
|
1074
|
-
'command',
|
|
1075
|
-
'file_summary',
|
|
1076
|
-
'other',
|
|
1077
|
-
];
|
|
782
|
+
const KIND_ORDER = ['all','decision','learning','bugfix','command','file_summary','other'];
|
|
1078
783
|
|
|
1079
784
|
function buildKindFilters(memories) {
|
|
1080
785
|
const wrap = document.getElementById('kind-filters');
|
|
1081
786
|
wrap.innerHTML = '';
|
|
1082
787
|
const counts = {};
|
|
1083
|
-
memories.forEach((m) => {
|
|
1084
|
-
|
|
1085
|
-
});
|
|
1086
|
-
|
|
1087
|
-
const kinds = [
|
|
1088
|
-
'all',
|
|
1089
|
-
...Object.keys(counts).sort((a, b) => KIND_ORDER.indexOf(a) - KIND_ORDER.indexOf(b)),
|
|
1090
|
-
];
|
|
1091
|
-
|
|
788
|
+
memories.forEach((m) => { counts[m.kind] = (counts[m.kind] || 0) + 1; });
|
|
789
|
+
const kinds = ['all', ...Object.keys(counts).sort((a, b) => KIND_ORDER.indexOf(a) - KIND_ORDER.indexOf(b))];
|
|
1092
790
|
kinds.forEach((k) => {
|
|
1093
791
|
const isAll = k === 'all';
|
|
1094
792
|
const label = isAll ? 'All' : k.replace('_', ' ');
|
|
1095
793
|
const count = isAll ? memories.length : counts[k] || 0;
|
|
1096
|
-
const btn = h(
|
|
1097
|
-
'
|
|
1098
|
-
{
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
b.className = 'kind-tag';
|
|
1104
|
-
});
|
|
1105
|
-
btn.className = 'kind-tag active kf-' + k;
|
|
1106
|
-
renderList(S.searchResults ?? S.data?.memories ?? []);
|
|
1107
|
-
},
|
|
794
|
+
const btn = h('div', {
|
|
795
|
+
class: 'kind-tag' + (S.activeKind === k ? ' active kf-' + k : ''),
|
|
796
|
+
onclick: () => {
|
|
797
|
+
S.activeKind = k;
|
|
798
|
+
wrap.querySelectorAll('.kind-tag').forEach((b) => { b.className = 'kind-tag'; });
|
|
799
|
+
btn.className = 'kind-tag active kf-' + k;
|
|
800
|
+
renderList(S.searchResults ?? S.data?.memories ?? []);
|
|
1108
801
|
},
|
|
1109
|
-
|
|
1110
|
-
);
|
|
802
|
+
}, label + (count > 0 ? ' ' + count : ''));
|
|
1111
803
|
wrap.append(btn);
|
|
1112
804
|
});
|
|
1113
805
|
}
|
|
@@ -1115,9 +807,7 @@
|
|
|
1115
807
|
// ─── Memory list ──────────────────────────────────────────────────────────────
|
|
1116
808
|
function renderList(memories) {
|
|
1117
809
|
const list = document.getElementById('memory-list');
|
|
1118
|
-
const filtered =
|
|
1119
|
-
S.activeKind === 'all' ? memories : memories.filter((m) => m.kind === S.activeKind);
|
|
1120
|
-
|
|
810
|
+
const filtered = S.activeKind === 'all' ? memories : memories.filter((m) => m.kind === S.activeKind);
|
|
1121
811
|
document.getElementById('list-count').textContent =
|
|
1122
812
|
filtered.length !== memories.length
|
|
1123
813
|
? filtered.length + ' / ' + memories.length
|
|
@@ -1125,24 +815,16 @@
|
|
|
1125
815
|
|
|
1126
816
|
if (filtered.length === 0) {
|
|
1127
817
|
list.innerHTML = '';
|
|
1128
|
-
list.append(
|
|
1129
|
-
h(
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
{ class: 'empty-sub' },
|
|
1137
|
-
S.searchQuery
|
|
1138
|
-
? 'Try a different query or strategy.'
|
|
1139
|
-
: 'End a Claude session — the hook will extract observations automatically.',
|
|
1140
|
-
),
|
|
1141
|
-
),
|
|
1142
|
-
);
|
|
818
|
+
list.append(h('div', { class: 'empty' },
|
|
819
|
+
h('div', { class: 'empty-icon' }, svgIcon('search')),
|
|
820
|
+
h('div', { class: 'empty-title' }, S.searchQuery ? 'No results' : 'No memories yet'),
|
|
821
|
+
h('div', { class: 'empty-sub' },
|
|
822
|
+
S.searchQuery
|
|
823
|
+
? 'Try a different query or strategy.'
|
|
824
|
+
: 'End a Claude session — observations are extracted automatically.'),
|
|
825
|
+
));
|
|
1143
826
|
return;
|
|
1144
827
|
}
|
|
1145
|
-
|
|
1146
828
|
list.innerHTML = '';
|
|
1147
829
|
filtered.forEach((m) => list.append(memCard(m)));
|
|
1148
830
|
}
|
|
@@ -1153,62 +835,35 @@
|
|
|
1153
835
|
onclick: () => selectMemory(m),
|
|
1154
836
|
});
|
|
1155
837
|
|
|
1156
|
-
// Left accent color
|
|
1157
838
|
const kc = KIND_COLOR[m.kind] || KIND_COLOR.other;
|
|
1158
839
|
card.style.setProperty('--card-accent', kc.bg);
|
|
1159
|
-
if (m.id === S.selectedId)
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
S.data.memories = S.data.memories.filter((x) => x.id !== m.id);
|
|
1174
|
-
if (S.selectedId === m.id) {
|
|
1175
|
-
S.selectedId = null;
|
|
1176
|
-
hideDetail();
|
|
1177
|
-
}
|
|
1178
|
-
buildKindFilters(S.data.memories);
|
|
1179
|
-
renderList(S.searchResults ?? S.data.memories);
|
|
1180
|
-
if (S.network) {
|
|
1181
|
-
try {
|
|
1182
|
-
S.network.getBody().data.nodes.remove(m.id);
|
|
1183
|
-
} catch {}
|
|
1184
|
-
}
|
|
1185
|
-
document.getElementById('s-mem').textContent = fmtK(S.data.memories.length);
|
|
1186
|
-
},
|
|
840
|
+
if (m.id === S.selectedId) card.style.borderLeftColor = kc.bg;
|
|
841
|
+
|
|
842
|
+
const forget = h('div', {
|
|
843
|
+
class: 'mc-forget', title: 'Forget this memory',
|
|
844
|
+
onclick: async (e) => {
|
|
845
|
+
e.stopPropagation();
|
|
846
|
+
if (!confirm('Remove this memory?')) return;
|
|
847
|
+
await fetch('/api/memories/' + m.id, { method: 'DELETE' });
|
|
848
|
+
S.data.memories = S.data.memories.filter((x) => x.id !== m.id);
|
|
849
|
+
if (S.selectedId === m.id) { S.selectedId = null; hideDetail(); }
|
|
850
|
+
buildKindFilters(S.data.memories);
|
|
851
|
+
renderList(S.searchResults ?? S.data.memories);
|
|
852
|
+
if (S.network) { try { S.network.getBody().data.nodes.remove(m.id); } catch {} }
|
|
853
|
+
document.getElementById('s-mem').textContent = fmtK(S.data.memories.length);
|
|
1187
854
|
},
|
|
1188
|
-
|
|
1189
|
-
);
|
|
855
|
+
}, '×');
|
|
1190
856
|
|
|
1191
|
-
const head = h(
|
|
1192
|
-
'div',
|
|
1193
|
-
{ class: 'mc-head' },
|
|
857
|
+
const head = h('div', { class: 'mc-head' },
|
|
1194
858
|
h('span', { class: 'mc-title' }, m.title),
|
|
1195
|
-
h(
|
|
1196
|
-
'span',
|
|
1197
|
-
{ class: 'mc-kind ' + kindClass(m.kind) },
|
|
1198
|
-
(m.kind || 'other').replace('_', ' '),
|
|
1199
|
-
),
|
|
859
|
+
h('span', { class: 'mc-kind ' + kindClass(m.kind) }, (m.kind || 'other').replace('_', ' ')),
|
|
1200
860
|
);
|
|
1201
|
-
|
|
1202
861
|
const preview = m.body ? h('div', { class: 'mc-body' }, m.body) : null;
|
|
1203
|
-
|
|
1204
862
|
const tags = (m.tags || []).slice(0, 3).map((t) => h('span', { class: 'mc-tag' }, t));
|
|
1205
|
-
const footer = h(
|
|
1206
|
-
'div',
|
|
1207
|
-
{ class: 'mc-footer' },
|
|
863
|
+
const footer = h('div', { class: 'mc-footer' },
|
|
1208
864
|
h('span', { class: 'mc-time' }, relTime(m.created_at)),
|
|
1209
865
|
h('div', { class: 'mc-tags' }, ...tags),
|
|
1210
866
|
);
|
|
1211
|
-
|
|
1212
867
|
card.append(forget, head);
|
|
1213
868
|
if (preview) card.append(preview);
|
|
1214
869
|
card.append(footer);
|
|
@@ -1218,18 +873,12 @@
|
|
|
1218
873
|
// ─── Detail panel ─────────────────────────────────────────────────────────────
|
|
1219
874
|
function selectMemory(m) {
|
|
1220
875
|
S.selectedId = m.id;
|
|
1221
|
-
document.querySelectorAll('.mem-card').forEach((c) => c.classList.remove('selected'));
|
|
1222
|
-
const cards = document.querySelectorAll('.mem-card');
|
|
1223
|
-
// find by checking onclick would match but instead we just re-render is expensive
|
|
1224
|
-
// so find by matching title as a quick approach
|
|
1225
876
|
renderList(S.searchResults ?? S.data?.memories ?? []);
|
|
1226
877
|
showDetail(m);
|
|
1227
878
|
if (S.network) {
|
|
1228
879
|
try {
|
|
1229
880
|
S.network.selectNodes([m.id]);
|
|
1230
|
-
S.network.focus(m.id, {
|
|
1231
|
-
animation: { duration: 400, easingFunction: 'easeInOutQuad' },
|
|
1232
|
-
});
|
|
881
|
+
S.network.focus(m.id, { animation: { duration: 400, easingFunction: 'easeInOutQuad' } });
|
|
1233
882
|
} catch {}
|
|
1234
883
|
}
|
|
1235
884
|
}
|
|
@@ -1238,57 +887,29 @@
|
|
|
1238
887
|
const panel = document.getElementById('detail-panel');
|
|
1239
888
|
const inner = document.getElementById('detail-inner');
|
|
1240
889
|
panel.style.display = 'block';
|
|
890
|
+
panel.style.animation = 'none';
|
|
891
|
+
requestAnimationFrame(() => { panel.style.animation = ''; });
|
|
1241
892
|
|
|
1242
|
-
const kc = KIND_COLOR[m.kind] || KIND_COLOR.other;
|
|
1243
893
|
const saved = m.tokens_saved ? fmtK(m.tokens_saved) + ' tk saved' : '';
|
|
1244
894
|
const spent = m.tokens_spent ? fmtK(m.tokens_spent) + ' tk spent' : '';
|
|
1245
|
-
|
|
1246
895
|
const fileChips = (m.files || []).map((f) => {
|
|
1247
896
|
const short = f.split('/').slice(-2).join('/');
|
|
1248
897
|
return h('span', { class: 'detail-chip file', title: f }, short);
|
|
1249
898
|
});
|
|
1250
899
|
const tagChips = (m.tags || []).map((t) => h('span', { class: 'detail-chip' }, t));
|
|
1251
|
-
|
|
1252
900
|
inner.innerHTML = '';
|
|
1253
901
|
inner.append(
|
|
1254
|
-
h(
|
|
1255
|
-
'div',
|
|
1256
|
-
{ class: 'detail-head' },
|
|
902
|
+
h('div', { class: 'detail-head' },
|
|
1257
903
|
h('span', { class: 'detail-title' }, m.title),
|
|
1258
|
-
h(
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
'div',
|
|
1265
|
-
{
|
|
1266
|
-
class: 'detail-close',
|
|
1267
|
-
title: 'Close',
|
|
1268
|
-
onclick: () => {
|
|
1269
|
-
S.selectedId = null;
|
|
1270
|
-
hideDetail();
|
|
1271
|
-
renderList(S.searchResults ?? S.data?.memories ?? []);
|
|
1272
|
-
},
|
|
1273
|
-
},
|
|
1274
|
-
h(
|
|
1275
|
-
'svg',
|
|
1276
|
-
{
|
|
1277
|
-
width: '10',
|
|
1278
|
-
height: '10',
|
|
1279
|
-
viewBox: '0 0 10 10',
|
|
1280
|
-
fill: 'none',
|
|
1281
|
-
stroke: 'currentColor',
|
|
1282
|
-
'stroke-width': '1.6',
|
|
1283
|
-
'stroke-linecap': 'round',
|
|
1284
|
-
},
|
|
1285
|
-
h('path', { d: 'M1 1l8 8M9 1L1 9' }),
|
|
1286
|
-
),
|
|
904
|
+
h('span', { class: 'mc-kind ' + kindClass(m.kind) }, (m.kind || 'other').replace('_', ' ')),
|
|
905
|
+
h('div', { class: 'detail-close', title: 'Close', onclick: () => {
|
|
906
|
+
S.selectedId = null; hideDetail(); renderList(S.searchResults ?? S.data?.memories ?? []);
|
|
907
|
+
}},
|
|
908
|
+
h('svg', { width: '10', height: '10', viewBox: '0 0 10 10', fill: 'none', stroke: 'currentColor', 'stroke-width': '1.6', 'stroke-linecap': 'round' },
|
|
909
|
+
h('path', { d: 'M1 1l8 8M9 1L1 9' }))
|
|
1287
910
|
),
|
|
1288
911
|
),
|
|
1289
|
-
h(
|
|
1290
|
-
'div',
|
|
1291
|
-
{ class: 'detail-meta' },
|
|
912
|
+
h('div', { class: 'detail-meta' },
|
|
1292
913
|
h('span', {}, relTime(m.created_at)),
|
|
1293
914
|
saved ? h('span', { style: 'color:var(--accent-g)' }, saved) : null,
|
|
1294
915
|
spent ? h('span', {}, spent) : null,
|
|
@@ -1302,11 +923,7 @@
|
|
|
1302
923
|
|
|
1303
924
|
function hideDetail() {
|
|
1304
925
|
document.getElementById('detail-panel').style.display = 'none';
|
|
1305
|
-
if (S.network) {
|
|
1306
|
-
try {
|
|
1307
|
-
S.network.unselectAll();
|
|
1308
|
-
} catch {}
|
|
1309
|
-
}
|
|
926
|
+
if (S.network) { try { S.network.unselectAll(); } catch {} }
|
|
1310
927
|
}
|
|
1311
928
|
|
|
1312
929
|
// ─── Search ────────────────────────────────────────────────────────────────────
|
|
@@ -1314,11 +931,7 @@
|
|
|
1314
931
|
const q = e.target.value.trim();
|
|
1315
932
|
S.searchQuery = q;
|
|
1316
933
|
clearTimeout(S.debounce);
|
|
1317
|
-
if (!q) {
|
|
1318
|
-
S.searchResults = null;
|
|
1319
|
-
renderList(S.data?.memories ?? []);
|
|
1320
|
-
return;
|
|
1321
|
-
}
|
|
934
|
+
if (!q) { S.searchResults = null; renderList(S.data?.memories ?? []); return; }
|
|
1322
935
|
S.debounce = setTimeout(() => runSearch(q), 320);
|
|
1323
936
|
});
|
|
1324
937
|
|
|
@@ -1326,13 +939,10 @@
|
|
|
1326
939
|
const spin = document.getElementById('search-spinner');
|
|
1327
940
|
spin.style.display = 'block';
|
|
1328
941
|
try {
|
|
1329
|
-
const res = await fetch(
|
|
1330
|
-
`/api/search?q=${encodeURIComponent(q)}&strategy=${S.strategy}&k=20`,
|
|
1331
|
-
);
|
|
942
|
+
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}&strategy=${S.strategy}&k=20`);
|
|
1332
943
|
const json = await res.json();
|
|
1333
944
|
S.searchResults = json.results;
|
|
1334
|
-
document.getElementById('tab-hint').textContent =
|
|
1335
|
-
`${json.strategy} · ${json.results.length} results`;
|
|
945
|
+
document.getElementById('tab-hint').textContent = `${json.strategy} · ${json.results.length} results`;
|
|
1336
946
|
renderList(json.results);
|
|
1337
947
|
} catch {
|
|
1338
948
|
S.searchResults = null;
|
|
@@ -1348,16 +958,11 @@
|
|
|
1348
958
|
btn.addEventListener('click', () => {
|
|
1349
959
|
const t = btn.dataset.tab;
|
|
1350
960
|
S.activeTab = t;
|
|
1351
|
-
document
|
|
1352
|
-
.querySelectorAll('.tab-btn[data-tab]')
|
|
1353
|
-
.forEach((b) => b.classList.remove('active'));
|
|
961
|
+
document.querySelectorAll('.tab-btn[data-tab]').forEach((b) => b.classList.remove('active'));
|
|
1354
962
|
btn.classList.add('active');
|
|
1355
963
|
document.getElementById('tab-graph').style.display = t === 'graph' ? 'flex' : 'none';
|
|
1356
|
-
document.getElementById('tab-analytics').style.display =
|
|
1357
|
-
|
|
1358
|
-
if (t === 'analytics' && !S.analyticsRendered && S.fullStats) {
|
|
1359
|
-
renderAnalytics(S.fullStats);
|
|
1360
|
-
}
|
|
964
|
+
document.getElementById('tab-analytics').style.display = t === 'analytics' ? 'flex' : 'none';
|
|
965
|
+
if (t === 'analytics' && !S.analyticsRendered && S.fullStats) renderAnalytics(S.fullStats);
|
|
1361
966
|
});
|
|
1362
967
|
});
|
|
1363
968
|
}
|
|
@@ -1367,70 +972,41 @@
|
|
|
1367
972
|
const container = document.getElementById('graph-container');
|
|
1368
973
|
if (!nodes?.length) {
|
|
1369
974
|
container.innerHTML =
|
|
1370
|
-
'<div class="empty" style="height:100%">' +
|
|
1371
|
-
svgIcon('graph') +
|
|
975
|
+
'<div class="empty" style="height:100%">' + svgIcon('graph').outerHTML +
|
|
1372
976
|
'<div class="empty-title">No graph yet</div>' +
|
|
1373
|
-
'<div class="empty-sub">Memories with shared files or tags
|
|
977
|
+
'<div class="empty-sub">Memories with shared files or tags appear here.</div></div>';
|
|
1374
978
|
return;
|
|
1375
979
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
shape: 'dot',
|
|
1391
|
-
size: 10,
|
|
1392
|
-
borderWidth: 1.5,
|
|
1393
|
-
};
|
|
1394
|
-
}),
|
|
1395
|
-
);
|
|
1396
|
-
|
|
1397
|
-
const vEdges = new vis.DataSet(
|
|
1398
|
-
edges.map((e, i) => ({
|
|
1399
|
-
id: i,
|
|
1400
|
-
from: e.from,
|
|
1401
|
-
to: e.to,
|
|
1402
|
-
color: { color: '#1f2535', highlight: '#4f8dff', opacity: 0.8 },
|
|
1403
|
-
width: 1,
|
|
1404
|
-
smooth: { type: 'dynamic' },
|
|
1405
|
-
})),
|
|
1406
|
-
);
|
|
1407
|
-
|
|
980
|
+
const vNodes = new vis.DataSet(nodes.map((n) => {
|
|
981
|
+
const kc = KIND_COLOR[n.kind] || KIND_COLOR.other;
|
|
982
|
+
return {
|
|
983
|
+
id: n.id, label: n.label, title: n.title,
|
|
984
|
+
color: { background: kc.bg, border: kc.border, highlight: { background: kc.bg, border: '#fff' } },
|
|
985
|
+
font: { color: '#ede8da', size: 11, face: 'Syne' },
|
|
986
|
+
shape: 'dot', size: 10, borderWidth: 1.5,
|
|
987
|
+
};
|
|
988
|
+
}));
|
|
989
|
+
const vEdges = new vis.DataSet(edges.map((e, i) => ({
|
|
990
|
+
id: i, from: e.from, to: e.to,
|
|
991
|
+
color: { color: '#2d2918', highlight: '#f0a030', opacity: 0.9 },
|
|
992
|
+
width: 1, smooth: { type: 'dynamic' },
|
|
993
|
+
})));
|
|
1408
994
|
S.network = new vis.Network(
|
|
1409
995
|
container,
|
|
1410
996
|
{ nodes: vNodes, edges: vEdges },
|
|
1411
997
|
{
|
|
1412
|
-
physics: {
|
|
1413
|
-
stabilization: { iterations: 100 },
|
|
1414
|
-
barnesHut: { gravitationalConstant: -2000, springLength: 120 },
|
|
1415
|
-
},
|
|
998
|
+
physics: { stabilization: { iterations: 100 }, barnesHut: { gravitationalConstant: -2000, springLength: 120 } },
|
|
1416
999
|
interaction: { hover: true, tooltipDelay: 200 },
|
|
1417
1000
|
nodes: { borderWidthSelected: 2 },
|
|
1418
1001
|
},
|
|
1419
1002
|
);
|
|
1420
|
-
|
|
1421
1003
|
S.network.on('click', ({ nodes: selected }) => {
|
|
1422
1004
|
if (!selected.length) return;
|
|
1423
1005
|
const id = selected[0];
|
|
1424
1006
|
const mem = S.data?.memories?.find((m) => m.id === id);
|
|
1425
|
-
if (mem) {
|
|
1426
|
-
S.selectedId = id;
|
|
1427
|
-
renderList(S.searchResults ?? S.data?.memories ?? []);
|
|
1428
|
-
showDetail(mem);
|
|
1429
|
-
}
|
|
1007
|
+
if (mem) { S.selectedId = id; renderList(S.searchResults ?? S.data?.memories ?? []); showDetail(mem); }
|
|
1430
1008
|
});
|
|
1431
|
-
|
|
1432
|
-
document.getElementById('tab-hint').textContent =
|
|
1433
|
-
`${nodes.length} nodes · ${edges.length} edges`;
|
|
1009
|
+
document.getElementById('tab-hint').textContent = `${nodes.length} nodes · ${edges.length} edges`;
|
|
1434
1010
|
}
|
|
1435
1011
|
|
|
1436
1012
|
// ─── Full stats ────────────────────────────────────────────────────────────────
|
|
@@ -1438,10 +1014,8 @@
|
|
|
1438
1014
|
try {
|
|
1439
1015
|
const res = await fetch('/api/stats/full');
|
|
1440
1016
|
S.fullStats = await res.json();
|
|
1441
|
-
// Update cache pill
|
|
1442
1017
|
const hit = S.fullStats.cache?.hit_rate;
|
|
1443
|
-
document.getElementById('s-cache').textContent =
|
|
1444
|
-
hit != null ? (hit * 100).toFixed(0) + '%' : '—';
|
|
1018
|
+
document.getElementById('s-cache').textContent = hit != null ? (hit * 100).toFixed(0) + '%' : '—';
|
|
1445
1019
|
if (S.activeTab === 'analytics') renderAnalytics(S.fullStats);
|
|
1446
1020
|
} catch {}
|
|
1447
1021
|
}
|
|
@@ -1455,57 +1029,32 @@
|
|
|
1455
1029
|
const kb = fs.kindBreakdown || {};
|
|
1456
1030
|
const kbMax = Math.max(...Object.values(kb).map(Number), 1);
|
|
1457
1031
|
const kbCard = card('Memory kinds');
|
|
1458
|
-
Object.entries(kb)
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
{ class: 'bar-row' },
|
|
1465
|
-
h(
|
|
1466
|
-
'span',
|
|
1467
|
-
{ class: 'bar-label', style: 'font-size:11.5px;font-weight:600' },
|
|
1468
|
-
kind.replace('_', ' '),
|
|
1469
|
-
),
|
|
1470
|
-
h(
|
|
1471
|
-
'div',
|
|
1472
|
-
{ class: 'bar-track' },
|
|
1473
|
-
h('div', { class: 'bar-fill', style: `background:${kc.bg}` }),
|
|
1474
|
-
),
|
|
1475
|
-
h('span', { class: 'bar-count' }, String(count)),
|
|
1476
|
-
);
|
|
1477
|
-
kbCard.append(row);
|
|
1478
|
-
requestAnimationFrame(() => {
|
|
1479
|
-
row.querySelector('.bar-fill').style.width = (count / kbMax) * 100 + '%';
|
|
1480
|
-
});
|
|
1481
|
-
});
|
|
1482
|
-
if (!Object.keys(kb).length)
|
|
1483
|
-
kbCard.append(
|
|
1484
|
-
h('div', { class: 'empty' }, h('div', { class: 'empty-sub' }, 'No data yet')),
|
|
1032
|
+
Object.entries(kb).sort((a, b) => b[1] - a[1]).forEach(([kind, count]) => {
|
|
1033
|
+
const kc = KIND_COLOR[kind] || KIND_COLOR.other;
|
|
1034
|
+
const row = h('div', { class: 'bar-row' },
|
|
1035
|
+
h('span', { class: 'bar-label' }, kind.replace('_', ' ')),
|
|
1036
|
+
h('div', { class: 'bar-track' }, h('div', { class: 'bar-fill', style: `background:${kc.bg}` })),
|
|
1037
|
+
h('span', { class: 'bar-count' }, String(count)),
|
|
1485
1038
|
);
|
|
1039
|
+
kbCard.append(row);
|
|
1040
|
+
requestAnimationFrame(() => { row.querySelector('.bar-fill').style.width = (count / kbMax) * 100 + '%'; });
|
|
1041
|
+
});
|
|
1042
|
+
if (!Object.keys(kb).length) kbCard.append(h('div', { class: 'empty' }, h('div', { class: 'empty-sub' }, 'No data yet')));
|
|
1486
1043
|
grid.append(wrap(kbCard));
|
|
1487
1044
|
|
|
1488
1045
|
// 2. Cache performance
|
|
1489
1046
|
const cc = fs.cache || {};
|
|
1490
1047
|
const cacheCard = card('Cache performance');
|
|
1491
1048
|
[
|
|
1492
|
-
[
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
],
|
|
1497
|
-
['Hits', cc.hit_count ?? 0, 'var(--accent-g)'],
|
|
1498
|
-
['Misses', cc.miss_count ?? 0, 'var(--muted2)'],
|
|
1499
|
-
['Entries', cc.entry_count ?? 0, 'var(--accent)'],
|
|
1049
|
+
['Hit rate', cc.hit_rate != null ? (cc.hit_rate * 100).toFixed(1) + '%' : '—', 'var(--accent-g)'],
|
|
1050
|
+
['Hits', cc.hit_count ?? 0, 'var(--accent-g)'],
|
|
1051
|
+
['Misses', cc.miss_count ?? 0, 'var(--muted2)'],
|
|
1052
|
+
['Entries', cc.entry_count ?? 0, 'var(--accent-c)'],
|
|
1500
1053
|
].forEach(([lbl, val, color]) => {
|
|
1501
|
-
cacheCard.append(
|
|
1502
|
-
h(
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
h('span', { class: 'cache-stat-lbl' }, lbl),
|
|
1506
|
-
h('span', { class: 'cache-stat-val', style: `color:${color}` }, String(val)),
|
|
1507
|
-
),
|
|
1508
|
-
);
|
|
1054
|
+
cacheCard.append(h('div', { class: 'cache-stat' },
|
|
1055
|
+
h('span', { class: 'cache-stat-lbl' }, lbl),
|
|
1056
|
+
h('span', { class: 'cache-stat-val', style: `color:${color}` }, String(val)),
|
|
1057
|
+
));
|
|
1509
1058
|
});
|
|
1510
1059
|
grid.append(wrap(cacheCard));
|
|
1511
1060
|
|
|
@@ -1514,53 +1063,27 @@
|
|
|
1514
1063
|
const ruMax = Math.max(...ru.map((r) => r.call_count), 1);
|
|
1515
1064
|
const ruCard = card('Retrieval strategy');
|
|
1516
1065
|
ru.sort((a, b) => b.call_count - a.call_count).forEach((r) => {
|
|
1517
|
-
const row = h(
|
|
1518
|
-
'div',
|
|
1519
|
-
{ class: 'bar-row' },
|
|
1066
|
+
const row = h('div', { class: 'bar-row' },
|
|
1520
1067
|
h('span', { class: 'bar-label' }, r.strategy),
|
|
1521
|
-
h(
|
|
1522
|
-
'div',
|
|
1523
|
-
{ class: 'bar-track' },
|
|
1524
|
-
h('div', { class: 'bar-fill', style: 'background:var(--accent-v)' }),
|
|
1525
|
-
),
|
|
1068
|
+
h('div', { class: 'bar-track' }, h('div', { class: 'bar-fill', style: 'background:var(--accent-v)' })),
|
|
1526
1069
|
h('span', { class: 'bar-count' }, String(r.call_count)),
|
|
1527
1070
|
);
|
|
1528
1071
|
ruCard.append(row);
|
|
1529
|
-
requestAnimationFrame(() => {
|
|
1530
|
-
row.querySelector('.bar-fill').style.width = (r.call_count / ruMax) * 100 + '%';
|
|
1531
|
-
});
|
|
1072
|
+
requestAnimationFrame(() => { row.querySelector('.bar-fill').style.width = (r.call_count / ruMax) * 100 + '%'; });
|
|
1532
1073
|
});
|
|
1533
|
-
if (!ru.length)
|
|
1534
|
-
ruCard.append(
|
|
1535
|
-
h('div', { class: 'empty' }, h('div', { class: 'empty-sub' }, 'No retrieval data yet')),
|
|
1536
|
-
);
|
|
1074
|
+
if (!ru.length) ruCard.append(h('div', { class: 'empty' }, h('div', { class: 'empty-sub' }, 'No retrieval data yet')));
|
|
1537
1075
|
grid.append(wrap(ruCard));
|
|
1538
1076
|
|
|
1539
1077
|
// 4. Top files
|
|
1540
1078
|
const tf = fs.topFiles || [];
|
|
1541
1079
|
const tfCard = card('Top files referenced');
|
|
1542
1080
|
tf.slice(0, 12).forEach((f) => {
|
|
1543
|
-
tfCard.append(
|
|
1544
|
-
h(
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
h(
|
|
1548
|
-
'span',
|
|
1549
|
-
{ class: 'file-name', title: f.file_path },
|
|
1550
|
-
f.file_path.split('/').slice(-2).join('/'),
|
|
1551
|
-
),
|
|
1552
|
-
h('span', { class: 'file-count' }, String(f.reference_count) + 'x'),
|
|
1553
|
-
),
|
|
1554
|
-
);
|
|
1081
|
+
tfCard.append(h('div', { class: 'file-row' },
|
|
1082
|
+
h('span', { class: 'file-name', title: f.file_path }, f.file_path.split('/').slice(-2).join('/')),
|
|
1083
|
+
h('span', { class: 'file-count' }, String(f.reference_count) + 'x'),
|
|
1084
|
+
));
|
|
1555
1085
|
});
|
|
1556
|
-
if (!tf.length)
|
|
1557
|
-
tfCard.append(
|
|
1558
|
-
h(
|
|
1559
|
-
'div',
|
|
1560
|
-
{ class: 'empty' },
|
|
1561
|
-
h('div', { class: 'empty-sub' }, 'No file references yet'),
|
|
1562
|
-
),
|
|
1563
|
-
);
|
|
1086
|
+
if (!tf.length) tfCard.append(h('div', { class: 'empty' }, h('div', { class: 'empty-sub' }, 'No file references yet')));
|
|
1564
1087
|
grid.append(wrap(tfCard));
|
|
1565
1088
|
}
|
|
1566
1089
|
|
|
@@ -1569,27 +1092,18 @@
|
|
|
1569
1092
|
el.append(h('div', { class: 'a-title' }, title));
|
|
1570
1093
|
return el;
|
|
1571
1094
|
}
|
|
1572
|
-
function wrap(el) {
|
|
1573
|
-
const w = h('div', {});
|
|
1574
|
-
w.append(el);
|
|
1575
|
-
return w;
|
|
1576
|
-
}
|
|
1095
|
+
function wrap(el) { const w = h('div', {}); w.append(el); return w; }
|
|
1577
1096
|
|
|
1578
1097
|
// ─── SVG icons ────────────────────────────────────────────────────────────────
|
|
1579
1098
|
function svgIcon(name) {
|
|
1580
1099
|
const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1581
|
-
el.setAttribute('width', '32');
|
|
1582
|
-
el.setAttribute('
|
|
1583
|
-
el.setAttribute('
|
|
1584
|
-
el.setAttribute('
|
|
1585
|
-
el.setAttribute('stroke', 'currentColor');
|
|
1586
|
-
el.setAttribute('stroke-width', '1.5');
|
|
1587
|
-
el.setAttribute('stroke-linecap', 'round');
|
|
1588
|
-
el.classList.add('empty-icon');
|
|
1100
|
+
el.setAttribute('width', '32'); el.setAttribute('height', '32');
|
|
1101
|
+
el.setAttribute('viewBox', '0 0 24 24'); el.setAttribute('fill', 'none');
|
|
1102
|
+
el.setAttribute('stroke', 'currentColor'); el.setAttribute('stroke-width', '1.5');
|
|
1103
|
+
el.setAttribute('stroke-linecap', 'round'); el.classList.add('empty-icon');
|
|
1589
1104
|
const paths = {
|
|
1590
1105
|
search: '<circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5"/>',
|
|
1591
|
-
graph:
|
|
1592
|
-
'<circle cx="5" cy="12" r="2"/><circle cx="19" cy="5" r="2"/><circle cx="19" cy="19" r="2"/><path d="M7 11.5L17 6M7 12.5L17 18"/>',
|
|
1106
|
+
graph: '<circle cx="5" cy="12" r="2"/><circle cx="19" cy="5" r="2"/><circle cx="19" cy="19" r="2"/><path d="M7 11.5L17 6M7 12.5L17 18"/>',
|
|
1593
1107
|
};
|
|
1594
1108
|
el.innerHTML = paths[name] || '';
|
|
1595
1109
|
return el;
|