@plumpslabs/kuma 2.3.31 → 2.3.33

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.
@@ -4,14 +4,15 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Kuma Studio</title>
7
- <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>♢</text></svg>">
7
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><circle cx='50' cy='50' r='48' fill='%23D97706'/><circle cx='50' cy='50' r='44' fill='%23F59E0B'/><circle cx='50' cy='45' r='30' fill='%2392400E'/><circle cx='35' cy='38' r='5' fill='%231F2937'/><circle cx='65' cy='38' r='5' fill='%231F2937'/><ellipse cx='50' cy='50' rx='10' ry='6' fill='%23B45309'/><path d='M35 58 Q50 72 65 58' stroke='%231F2937' stroke-width='3.5' fill='none' stroke-linecap='round'/></svg>">
8
8
  <script src="https://unpkg.com/vis-network@9.1.9/dist/vis-network.min.js"></script>
9
9
  <script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js"></script>
10
10
  <style>
11
11
  :root {
12
- --bg: #1a1a2e; --surface: #16213e; --border: #2a2a4a;
13
- --text: #e0e0e0; --muted: #888; --accent: #7c3aed; --accent2: #06b6d4;
14
- --green: #22c55e; --yellow: #eab308; --orange: #f97316; --red: #ef4444;
12
+ --bg: #0f172a; --surface: #1e293b; --border: #334155;
13
+ --text: #f1f5f9; --muted: #94a3b8; --accent: #a78bfa; --accent2: #22d3ee;
14
+ --green: #34d399; --yellow: #fbbf24; --orange: #fb923c; --red: #f87171;
15
+ --accent-bright: #c4b5fd; --accent2-bright: #67e8f9;
15
16
  }
16
17
  * { box-sizing: border-box; margin: 0; padding: 0; }
17
18
  html, body {
@@ -20,46 +21,44 @@ html, body {
20
21
  touch-action: none; -webkit-user-select: none; user-select: none;
21
22
  }
22
23
  header {
23
- display: flex; align-items: center; gap: 16px; padding: 12px 24px;
24
+ display: flex; align-items: center; gap: 12px; padding: 8px 16px;
24
25
  background: var(--surface); border-bottom: 1px solid var(--border);
25
26
  -webkit-app-region: drag;
26
27
  }
27
- header {
28
- display: flex; align-items: center; gap: 16px; padding: 12px 24px;
29
- background: var(--surface); border-bottom: 1px solid var(--border);
30
- -webkit-app-region: drag;
31
- }
32
- header h1 { font-size: 18px; font-weight: 700; color: var(--accent); letter-spacing: -0.5px; }
28
+ header h1 { font-size: 15px; font-weight: 700; color: var(--accent-bright); letter-spacing: -0.5px; text-shadow: 0 0 12px rgba(167,139,250,0.3); }
33
29
  header .status { font-size: 12px; color: var(--muted); }
34
30
  header .status .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; }
35
31
  header .status .dot.on { background: var(--green); }
36
32
  header .status .dot.off { background: var(--red); }
37
33
  .stats-bar {
38
- display: flex; gap: 8px; padding: 8px 24px; background: var(--surface);
39
- border-bottom: 1px solid var(--border); font-size: 12px;
40
- overflow-x: auto;
41
- }
42
- .stat { display: flex; align-items: center; gap: 4px; padding: 4px 10px; border-radius: 6px; background: var(--bg); white-space: nowrap; }
43
- .stat .num { font-weight: 700; color: var(--accent2); }
44
- .stat .lbl { color: var(--muted); }
45
- #main { display: flex; height: calc(100vh - 100px); }
34
+ display: flex; gap: 6px; padding: 6px 16px; background: var(--surface);
35
+ border-bottom: 1px solid var(--border); font-size: 11px;
36
+ overflow-x: auto; flex-wrap: nowrap;
37
+ }
38
+ .stat { display: flex; align-items: center; gap: 3px; padding: 3px 8px; border-radius: 5px; background: var(--bg); white-space: nowrap; }
39
+ .stat .num { font-weight: 700; color: var(--accent2); font-size: 12px; }
40
+ .stat .lbl { color: var(--muted); font-size: 10px; }
41
+ #main { display: flex; height: calc(100vh - 80px); }
46
42
  #graph-panel { flex: 1; position: relative; min-width: 0; overflow: hidden; }
47
43
  #graph-container { width: 100%; height: 100%; background: radial-gradient(ellipse at center, #1e1e3a 0%, var(--bg) 70%); }
48
- #sidebar { width: 360px; min-width: 360px; background: var(--surface); border-left: 1px solid var(--border); display: flex; flex-direction: column; }
49
- .tabs { display: flex; border-bottom: 1px solid var(--border); }
44
+ #sidebar { width: 320px; min-width: 320px; background: var(--surface); border-left: 1px solid var(--border); display: flex; flex-direction: column; }
45
+ .tabs { display: flex; gap: 3px; padding: 8px 12px 4px; background: var(--surface); border-bottom: 1px solid var(--border); overflow-x: auto; flex-wrap: nowrap; }
50
46
  .tab {
51
- flex: 1; padding: 10px; text-align: center; cursor: pointer; font-size: 12px;
52
- color: var(--muted); border-bottom: 2px solid transparent; transition: all 0.2s;
47
+ padding: 5px 10px; text-align: center; cursor: pointer; font-size: 11px;
48
+ color: var(--muted); border-radius: 6px; transition: all 0.15s; white-space: nowrap;
49
+ display: flex; align-items: center; gap: 4px; border: 1px solid transparent;
53
50
  }
54
- .tab:hover { color: var(--text); }
55
- .tab.active { color: var(--accent); border-bottom-color: var(--accent); }
56
- .tab-content { flex: 1; overflow-y: auto; padding: 12px; }
51
+ .tab:hover { color: var(--text); background: rgba(167,139,250,0.08); }
52
+ .tab.active { color: var(--accent-bright); background: rgba(167,139,250,0.12); border-color: rgba(167,139,250,0.25); }
53
+ .tab-content { flex: 1; overflow-y: auto; padding: 10px 12px; }
57
54
  .panel { display: none; }
58
- .panel.active { display: block; }
55
+ .panel.active { display: block; animation: fadeIn 0.15s; }
56
+ .tab-label { font-size: 11px; }
57
+ @media (max-width: 400px) { .tab-label { display: none; } .tab { padding: 5px 8px; } }
59
58
 
60
59
  /* Gotcha items */
61
60
  .gotcha-item {
62
- padding: 10px 12px; margin-bottom: 8px; border-radius: 8px;
61
+ padding: 8px 10px; margin-bottom: 6px; border-radius: 6px;
63
62
  background: var(--bg); border-left: 3px solid var(--muted); cursor: pointer;
64
63
  transition: background 0.15s;
65
64
  }
@@ -74,19 +73,19 @@ header .status .dot.off { background: var(--red); }
74
73
 
75
74
  /* Feature items */
76
75
  .feature-item {
77
- padding: 10px 12px; margin-bottom: 8px; border-radius: 8px;
76
+ padding: 8px 10px; margin-bottom: 6px; border-radius: 6px;
78
77
  background: var(--bg); border-left: 3px solid var(--accent); cursor: pointer;
79
78
  transition: background 0.15s;
80
79
  }
81
80
  .feature-item:hover { background: #1e293b; }
82
- .feature-item .name { font-size: 14px; font-weight: 600; color: var(--accent); margin-bottom: 4px; }
81
+ .feature-item .name { font-size: 13px; font-weight: 600; color: var(--accent); margin-bottom: 2px; }
83
82
  .feature-item .meta { font-size: 11px; color: var(--muted); }
84
83
  .feature-item .tags { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 4px; }
85
- .feature-item .tag { font-size: 10px; padding: 1px 6px; border-radius: 4px; background: rgba(124,58,237,0.15); color: var(--accent); }
84
+ .feature-item .tag { font-size: 10px; padding: 1px 6px; border-radius: 4px; background: rgba(167,139,250,0.15); color: var(--accent-bright); }
86
85
 
87
86
  /* Health items */
88
- .health-item { padding: 8px 12px; margin-bottom: 6px; background: var(--bg); border-radius: 6px; }
89
- .health-item .score { font-size: 20px; font-weight: 700; }
87
+ .health-item { padding: 6px 10px; margin-bottom: 5px; background: var(--bg); border-radius: 5px; }
88
+ .health-item .score { font-size: 18px; font-weight: 700; }
90
89
  .health-item .score.good { color: var(--green); }
91
90
  .health-item .score.ok { color: var(--yellow); }
92
91
  .health-item .score.bad { color: var(--red); }
@@ -96,7 +95,7 @@ header .status .dot.off { background: var(--red); }
96
95
 
97
96
  /* Legend */
98
97
  #legend {
99
- position: fixed; bottom: 16px; left: 16px; background: rgba(22,33,62,0.95);
98
+ position: fixed; bottom: 16px; left: 16px; background: rgba(15,23,42,0.95);
100
99
  border: 1px solid var(--border); border-radius: 8px; padding: 10px 14px;
101
100
  font-size: 11px; z-index: 10; max-height: 200px; overflow-y: auto;
102
101
  backdrop-filter: blur(8px); color: #f8fafc;
@@ -130,45 +129,45 @@ header .status .dot.off { background: var(--red); }
130
129
  border-radius: 6px; cursor: pointer; font-size: 12px; color: var(--text);
131
130
  transition: background 0.1s;
132
131
  }
133
- #ctx-menu .ctx-item:hover { background: rgba(124,58,237,0.15); color: var(--accent); }
132
+ #ctx-menu .ctx-item:hover { background: rgba(167,139,250,0.15); color: var(--accent-bright); }
134
133
  #ctx-menu .ctx-item i { width: 14px; height: 14px; flex-shrink: 0; }
135
134
  #ctx-menu .ctx-sep { height: 1px; background: var(--border); margin: 4px 8px; }
136
135
  #search-box input {
137
- background: rgba(22,33,62,0.92); border: 1px solid var(--border);
136
+ background: rgba(15,23,42,0.92); border: 1px solid var(--border);
138
137
  border-radius: 8px; padding: 8px 12px; color: var(--text); font-size: 12px;
139
138
  width: 200px; outline: none; backdrop-filter: blur(8px);
140
139
  transition: border-color 0.2s;
141
140
  }
142
- #search-box input:focus { border-color: var(--accent); }
141
+ #search-box input:focus { border-color: var(--accent-bright); box-shadow: 0 0 8px rgba(167,139,250,0.3); }
143
142
  #search-box input::placeholder { color: var(--muted); }
144
143
 
145
144
  /* Graph controls */
146
145
  #graph-controls {
147
- position: fixed; bottom: 16px; right: 376px; z-index: 10;
146
+ position: fixed; bottom: 16px; right: 336px; z-index: 10;
148
147
  display: flex; gap: 6px;
149
148
  }
150
149
  #graph-controls button {
151
- background: rgba(22,33,62,0.92); border: 1px solid var(--border);
150
+ background: rgba(15,23,42,0.92); border: 1px solid var(--border);
152
151
  border-radius: 6px; color: var(--text); padding: 6px 10px;
153
152
  font-size: 11px; cursor: pointer; backdrop-filter: blur(8px);
154
153
  transition: background 0.2s;
155
154
  }
156
- #graph-controls button:hover { background: var(--accent); border-color: var(--accent); }
155
+ #graph-controls button:hover { background: var(--accent); border-color: var(--accent-bright); }
157
156
  #graph-controls button.active {
158
- background: var(--accent) !important; border-color: var(--accent) !important;
159
- color: #ffffff !important; box-shadow: 0 0 10px rgba(124, 58, 237, 0.4);
157
+ background: var(--accent) !important; border-color: var(--accent-bright) !important;
158
+ color: #ffffff !important; box-shadow: 0 0 12px rgba(167,139,250,0.5);
160
159
  }
161
160
  #graph-controls .ctrl-sep { width: 1px; background: var(--border); margin: 0 2px; }
162
161
  .depth-btn { min-width: 28px !important; padding: 4px 6px !important; font-size: 11px !important; font-weight: 600; }
163
- .depth-btn.active { background: rgba(124,58,237,0.3) !important; border-color: var(--accent) !important; color: var(--accent) !important; }
162
+ .depth-btn.active { background: rgba(167,139,250,0.25) !important; border-color: var(--accent-bright) !important; color: var(--accent-bright) !important; }
164
163
 
165
164
  /* Loading */
166
165
  #loading {
167
166
  position: fixed; inset: 0; background: var(--bg); display: flex;
168
167
  align-items: center; justify-content: center; z-index: 100;
169
- font-size: 18px; color: var(--accent); flex-direction: column; gap: 16px;
168
+ font-size: 18px; color: var(--accent-bright); flex-direction: column; gap: 16px;
170
169
  }
171
- #loading .spinner { width: 32px; height: 32px; border: 3px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; }
170
+ #loading .spinner { width: 32px; height: 32px; border: 3px solid var(--border); border-top-color: var(--accent-bright); border-radius: 50%; animation: spin 0.8s linear infinite; }
172
171
  @keyframes spin { to { transform: rotate(360deg); } }
173
172
 
174
173
  /* Node Detail Modal */
@@ -179,31 +178,31 @@ header .status .dot.off { background: var(--red); }
179
178
  }
180
179
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
181
180
  .modal {
182
- background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
183
- width: 560px; max-width: 90vw; max-height: 85vh; overflow: hidden; box-shadow: 0 16px 64px rgba(0,0,0,0.5);
181
+ background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
182
+ width: 520px; max-width: 90vw; max-height: 85vh; overflow: hidden; box-shadow: 0 16px 64px rgba(0,0,0,0.5);
184
183
  display: flex; flex-direction: column; animation: slideUp 0.2s;
185
184
  }
186
185
  @keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
187
186
  .modal-header {
188
187
  display: flex; align-items: center; justify-content: space-between;
189
- padding: 14px 20px; border-bottom: 1px solid var(--border); flex-shrink: 0;
188
+ padding: 10px 16px; border-bottom: 1px solid var(--border); flex-shrink: 0;
190
189
  }
191
- .modal-header h2 { font-size: 14px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
190
+ .modal-header h2 { font-size: 13px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
192
191
  .modal-header .close {
193
192
  background: none; border: none; color: var(--muted); font-size: 18px;
194
193
  cursor: pointer; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
195
194
  border-radius: 6px; transition: all 0.15s; flex-shrink: 0;
196
195
  }
197
196
  .modal-header .close:hover { background: rgba(255,255,255,0.1); color: var(--text); }
198
- .modal-body { padding: 16px 20px; overflow-y: auto; flex: 1; min-height: 0; }
199
- .modal-section { margin-bottom: 14px; }
200
- .modal-section h3 { font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
201
- .modal-kv { display: flex; gap: 8px; margin-bottom: 5px; font-size: 12px; line-height: 1.4; }
202
- .modal-kv .key { color: var(--muted); min-width: 72px; flex-shrink: 0; }
197
+ .modal-body { padding: 12px 16px; overflow-y: auto; flex: 1; min-height: 0; }
198
+ .modal-section { margin-bottom: 10px; }
199
+ .modal-section h3 { font-size: 10px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
200
+ .modal-kv { display: flex; gap: 6px; margin-bottom: 3px; font-size: 11px; line-height: 1.3; }
201
+ .modal-kv .key { color: var(--muted); min-width: 64px; flex-shrink: 0; }
203
202
  .modal-kv .val { color: var(--text); word-break: break-word; overflow-wrap: anywhere; min-width: 0; }
204
203
  .relation-item {
205
- display: flex; align-items: center; gap: 6px; padding: 5px 8px;
206
- margin-bottom: 3px; border-radius: 6px; background: var(--bg);
204
+ display: flex; align-items: center; gap: 5px; padding: 4px 7px;
205
+ margin-bottom: 2px; border-radius: 5px; background: var(--bg);
207
206
  font-size: 11px; cursor: pointer; transition: background 0.15s;
208
207
  }
209
208
  .relation-item:hover { background: #1e293b; }
@@ -212,20 +211,20 @@ header .status .dot.off { background: var(--red); }
212
211
  .relation-item .type { color: var(--muted); font-size: 10px; flex-shrink: 0; max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
213
212
  .relation-item .weight { color: var(--accent2); font-size: 10px; flex-shrink: 0; }
214
213
 
215
- ::-webkit-scrollbar { width: 6px; }
214
+ ::-webkit-scrollbar { width: 4px; height: 4px; }
216
215
  ::-webkit-scrollbar-track { background: transparent; }
217
- ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
216
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
218
217
  </style>
219
218
  </head>
220
219
  <body>
221
220
  <div id="loading"><div class="spinner"></div>Connecting to Kuma...</div>
222
221
  <header>
223
- <h1><i data-lucide="brain" style="width:20px;height:20px;vertical-align:middle;margin-right:6px"></i> Kuma Studio</h1>
222
+ <h1><svg width="22" height="22" viewBox="0 0 100 100" style="vertical-align:middle;margin-right:6px"><circle cx="50" cy="50" r="48" fill="#D97706"/><circle cx="50" cy="50" r="44" fill="#F59E0B"/><circle cx="50" cy="45" r="30" fill="#92400E"/><circle cx="35" cy="38" r="5" fill="#1F2937"/><circle cx="65" cy="38" r="5" fill="#1F2937"/><ellipse cx="50" cy="50" rx="10" ry="6" fill="#B45309"/><path d="M35 58 Q50 72 65 58" stroke="#1F2937" stroke-width="3.5" fill="none" stroke-linecap="round"/></svg> Kuma Studio</h1>
224
223
  <div class="status"><span class="dot" id="status-dot"></span><span id="status-text">connecting...</span></div>
225
224
  <div style="margin-left:auto;display:flex;gap:8px;-webkit-app-region:no-drag">
226
- <button onclick="refetch()" id="refetch-btn" style="background:rgba(124,58,237,0.15);border:1px solid rgba(124,58,237,0.3);border-radius:6px;color:var(--accent);padding:5px 12px;font-size:11px;cursor:pointer;transition:all 0.2s"><i data-lucide="refresh-cw" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Refetch</button>
227
- <button onclick="resetKumaDb()" style="background:rgba(239,68,68,0.15);border:1px solid rgba(239,68,68,0.3);border-radius:6px;color:var(--red);padding:5px 12px;font-size:11px;cursor:pointer;transition:all 0.2s"><i data-lucide="trash-2" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Reset DB</button>
228
- <button onclick="stopKumaStudio()" style="background:rgba(148,163,184,0.15);border:1px solid rgba(148,163,184,0.3);border-radius:6px;color:#94a3b8;padding:5px 12px;font-size:11px;cursor:pointer;transition:all 0.2s"><i data-lucide="square" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Stop</button>
225
+ <button onclick="refetch()" id="refetch-btn" style="background:rgba(167,139,250,0.12);border:1px solid rgba(167,139,250,0.25);border-radius:5px;color:var(--accent-bright);padding:4px 10px;font-size:10px;cursor:pointer;transition:all 0.2s;display:flex;align-items:center;gap:3px"><i data-lucide="refresh-cw" style="width:11px;height:11px"></i> Refetch</button>
226
+ <button onclick="resetKumaDb()" style="background:rgba(239,68,68,0.12);border:1px solid rgba(239,68,68,0.25);border-radius:5px;color:var(--red);padding:4px 10px;font-size:10px;cursor:pointer;transition:all 0.2s;display:flex;align-items:center;gap:3px"><i data-lucide="trash-2" style="width:11px;height:11px"></i> Reset</button>
227
+ <button onclick="stopKumaStudio()" style="background:rgba(148,163,184,0.12);border:1px solid rgba(148,163,184,0.25);border-radius:5px;color:#94a3b8;padding:4px 10px;font-size:10px;cursor:pointer;transition:all 0.2s;display:flex;align-items:center;gap:3px"><i data-lucide="square" style="width:11px;height:11px"></i> Stop</button>
229
228
  </div>
230
229
  </header>
231
230
  <div class="stats-bar" id="stats-bar"></div>
@@ -249,9 +248,12 @@ header .status .dot.off { background: var(--red); }
249
248
  </div>
250
249
  <div id="sidebar">
251
250
  <div class="tabs">
252
- <div class="tab active" data-tab="features"><i data-lucide="star" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Features</div>
253
- <div class="tab" data-tab="gotchas"><i data-lucide="alert-triangle" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Gotchas</div>
254
- <div class="tab" data-tab="health"><i data-lucide="heart-pulse" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Health</div>
251
+ <div class="tab active" data-tab="features" title="Features"><i data-lucide="star" style="width:12px;height:12px"></i><span class="tab-label">Features</span></div>
252
+ <div class="tab" data-tab="gotchas" title="Gotchas"><i data-lucide="alert-triangle" style="width:12px;height:12px"></i><span class="tab-label">Gotchas</span></div>
253
+ <div class="tab" data-tab="health" title="Health"><i data-lucide="heart-pulse" style="width:12px;height:12px"></i><span class="tab-label">Health</span></div>
254
+ <div class="tab" data-tab="efficiency" title="Efficiency"><i data-lucide="zap" style="width:12px;height:12px"></i><span class="tab-label">Efficiency</span></div>
255
+ <div class="tab" data-tab="staleness" title="Staleness"><i data-lucide="activity" style="width:12px;height:12px"></i><span class="tab-label">Staleness</span></div>
256
+ <div class="tab" data-tab="activity" title="Activity"><i data-lucide="bar-chart-3" style="width:12px;height:12px"></i><span class="tab-label">Activity</span></div>
255
257
  </div>
256
258
  <div class="tab-content" id="tab-content"></div>
257
259
  </div>
@@ -273,7 +275,7 @@ async function resetKumaDb() {
273
275
  var confirmation = prompt("⚠️ TYPE 'DELETE' TO CONFIRM RESETTING KUMA DB\nThis will permanently wipe all knowledge:");
274
276
  if (confirmation !== "DELETE") { if (confirmation !== null) alert("Reset cancelled."); return; }
275
277
  try {
276
- var res = await fetch(API + "/reset-db", { method: "POST" }).then(function(r){return r.json()});
278
+ var res = await fetch(API + "/reset-db?confirm=1", { method: "POST" }).then(function(r){return r.json()});
277
279
  if (res.ok) { alert("Kuma DB reset."); refetch(); }
278
280
  } catch (e) { alert("Reset failed: " + e.message); }
279
281
  }
@@ -281,7 +283,7 @@ async function resetKumaDb() {
281
283
  async function stopKumaStudio() {
282
284
  if (!confirm("Stop Kuma Studio?")) return;
283
285
  try {
284
- await fetch(API + "/stop-server", { method: "POST" });
286
+ await fetch(API + "/stop-server?confirm=1", { method: "POST" });
285
287
  document.body.innerHTML = '<div style="display:flex;height:100vh;align-items:center;justify-content:center;color:#94a3b8;font-size:16px;">Server stopped. Close this tab.</div>';
286
288
  } catch (e) { alert("Stop failed: " + e.message); }
287
289
  }
@@ -336,11 +338,16 @@ async function init() {
336
338
  renderFeatures(data);
337
339
  renderGotchas(data);
338
340
  renderHealth(data);
341
+ renderEfficiency(data);
342
+ renderStaleness(data);
343
+ renderActivity(data);
339
344
  setupSearch();
340
345
  lucide.createIcons();
341
346
  }
342
347
  init();
343
348
 
349
+
350
+
344
351
  function showError(msg) {
345
352
  document.getElementById("graph-container").innerHTML = '<div class="empty"><i data-lucide="alert-circle" style="width:18px;height:18px;vertical-align:middle;margin-right:4px"></i> '+msg+'</div>';
346
353
  lucide.createIcons();
@@ -353,7 +360,7 @@ function renderStats(data) {
353
360
  document.getElementById("stats-bar").innerHTML =
354
361
  '<div class="stat"><span class="num">'+(s.node_count||0)+'</span><span class="lbl">nodes</span></div>'+
355
362
  '<div class="stat"><span class="num">'+(s.edge_count||0)+'</span><span class="lbl">edges</span></div>'+
356
- '<div class="stat"><span class="num" style="color:var(--accent)">'+(s.feature_count||0)+'</span><span class="lbl">features</span></div>'+
363
+ '<div class="stat"><span class="num" style="color:var(--accent-bright)">'+(s.feature_count||0)+'</span><span class="lbl">features</span></div>'+
357
364
  '<div class="stat"><span class="num">'+(s.gotcha_count||0)+'</span><span class="lbl">gotchas</span></div>'+
358
365
  '<div class="stat"><span class="num" style="color:var(--accent)">'+currentDbSize+'</span><span class="lbl">kuma.db</span></div>'+
359
366
  '<div class="stat"><span class="num" style="color:'+hcColor+'">'+(hc||0)+'</span><span class="lbl">health</span></div>';
@@ -435,7 +442,7 @@ function renderGraph(data) {
435
442
  var color = isGotcha ? '#f97316' : (colors[n.type] || '#64748b');
436
443
  return {
437
444
  id: n.id, label: label,
438
- color: { background: color, border: color, highlight: { background: color, border: '#38bdf8' }, hover: { background: color, border: '#a855f7' } },
445
+ color: { background: color, border: color, highlight: { background: color, border: '#67e8f9' }, hover: { background: color, border: '#c4b5fd' } },
439
446
  shape: shape,
440
447
  font: { color: '#ffffff', size: Math.min(13, Math.max(10, size * 0.55)), face: 'sans-serif', multi: false, bold: true },
441
448
  borderWidth: 0, size: size, mass: 1 + (size / 40),
@@ -465,7 +472,7 @@ function renderGraph(data) {
465
472
  id: 'e'+idx, from: e.source, to: e.target,
466
473
  label: e.relation || '',
467
474
  arrows: { to: { enabled: true, scaleFactor: 0.7, type: 'arrow' } },
468
- color: { color: edgeColor, highlight: '#a855f7', hover: '#38bdf8', opacity: 0.85 },
475
+ color: { color: edgeColor, highlight: '#c4b5fd', hover: '#67e8f9', opacity: 0.85 },
469
476
  font: { size: 10, color: '#94a3b8', strokeWidth: 0, background: 'transparent' },
470
477
  dashes: dashes,
471
478
  smooth: { type: 'continuous', roundness: 0.3 },
@@ -482,7 +489,7 @@ function renderGraph(data) {
482
489
  },
483
490
  interaction: { hover: true, tooltipDelay: 300, zoomView: true, dragView: true, dragNodes: true, keyboard: true, multiselect: true, selectable: true, selectConnectedEdges: false },
484
491
  nodes: { borderWidth: 0, font: { color: '#ffffff', size: 11, face: 'sans-serif' }, margin: { top: 6, bottom: 6, left: 10, right: 10 }, shapeProperties: { borderRadius: 6 } },
485
- edges: { smooth: { type: 'continuous', roundness: 0.2 }, width: 1, color: { color: '#4a4a6a', highlight: '#7c3aed' } },
492
+ edges: { smooth: { type: 'continuous', roundness: 0.2 }, width: 1, color: { color: '#4a4a6a', highlight: '#a78bfa' } },
486
493
  configure: { enabled: false },
487
494
  });
488
495
 
@@ -606,7 +613,7 @@ function resetHighlight() {
606
613
  network.body.data.nodes.update(nUp);
607
614
  network.body.data.edges.forEach(function(e) {
608
615
  var def = edgeDefaults[e.id];
609
- eUp.push({ id: e.id, color: { color: def ? def.color : '#64748b', highlight: '#a855f7', hover: '#38bdf8', opacity: 0.85 }, width: def ? def.width : 1 });
616
+ eUp.push({ id: e.id, color: { color: def ? def.color : '#64748b', highlight: '#c4b5fd', hover: '#67e8f9', opacity: 0.85 }, width: def ? def.width : 1 });
610
617
  });
611
618
  network.body.data.edges.update(eUp);
612
619
  document.getElementById("graph-tooltip").style.display = 'none';
@@ -784,7 +791,7 @@ async function refetch() {
784
791
  if (status.ok) { dot.className = 'dot on'; txt.textContent = status.project || 'connected'; } else { dot.className = 'dot off'; txt.textContent = 'no kuma.db found'; }
785
792
  var data = await fetch(API+'/dashboard').then(function(r){return r.json()});
786
793
  if (data.error) { showError(data.error); return; }
787
- renderStats(data); renderGraph(data); renderFeatures(data); renderGotchas(data); renderHealth(data);
794
+ renderStats(data); renderGraph(data); renderFeatures(data); renderGotchas(data); renderHealth(data); renderEfficiency(data); renderStaleness(data); renderActivity(data);
788
795
  lucide.createIcons();
789
796
  } catch(e) { showError('Refetch failed: ' + e.message); }
790
797
  btn.innerHTML = '<i data-lucide="refresh-cw" style="width:13px;height:13px;vertical-align:middle;margin-right:3px"></i> Refetch';
@@ -802,7 +809,10 @@ function setupTabs(){
802
809
  content.innerHTML =
803
810
  '<div class="panel active" id="panel-features"><div class="empty">Loading...</div></div>' +
804
811
  '<div class="panel" id="panel-gotchas"><div class="empty">Loading...</div></div>' +
805
- '<div class="panel" id="panel-health"><div class="empty">Loading...</div></div>';
812
+ '<div class="panel" id="panel-health"><div class="empty">Loading...</div></div>' +
813
+ '<div class="panel" id="panel-efficiency"><div class="empty">Loading...</div></div>' +
814
+ '<div class="panel" id="panel-staleness"><div class="empty">Loading...</div></div>' +
815
+ '<div class="panel" id="panel-activity"><div class="empty">Loading...</div></div>';
806
816
  document.querySelectorAll('.tab').forEach(function(tab){
807
817
  tab.onclick = function(){
808
818
  document.querySelectorAll('.tab').forEach(function(t){t.classList.remove('active')});
@@ -867,6 +877,137 @@ function renderHealth(data){
867
877
  }).join('');
868
878
  }
869
879
 
880
+ // ============ EFFICIENCY PANEL (GAP 1) ============
881
+ // Proves the philosophy: "the more you use Kuma, the more efficient it gets."
882
+ // Every saved decision / record is an asset — here is the evidence.
883
+
884
+ function fmtDuration(sec) {
885
+ if (!sec || sec <= 0) return "0s";
886
+ if (sec >= 3600) return (sec/3600).toFixed(1)+"h";
887
+ if (sec >= 60) return Math.round(sec/60)+"m "+(sec%60)+"s";
888
+ return sec+"s";
889
+ }
890
+
891
+ function renderEfficiency(data){
892
+ var el = document.getElementById("panel-efficiency");
893
+ if(!el) return;
894
+ var eff = data.efficiency || {};
895
+ var m = eff.metrics || {};
896
+ var r = eff.recordings || {};
897
+ var savedSec = m.researchTimeSaved ? Math.round(m.researchTimeSaved/1000) : 0;
898
+ var passRate = eff.verificationPassRate;
899
+ var passColor = passRate >= 80 ? 'var(--green)' : passRate >= 50 ? 'var(--yellow)' : 'var(--red)';
900
+
901
+ var stat = function(num, lbl, color){
902
+ return '<div style="flex:1;min-width:110px;padding:10px 12px;background:var(--bg);border-radius:8px;border:1px solid var(--border)"><div style="font-size:20px;font-weight:700;color:'+(color||'var(--accent2)')+'">'+num+'</div><div style="font-size:11px;color:var(--muted);margin-top:2px">'+lbl+'</div></div>';
903
+ };
904
+
905
+ var html = '';
906
+ html += '<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px">' +
907
+ stat(eff.sessions||0, 'sessions') +
908
+ stat(eff.toolCalls||0, 'tool calls') +
909
+ stat(eff.gotchas||0, 'gotchas', 'var(--orange)') +
910
+ stat(eff.researchCacheScopes||0, 'research cached', 'var(--green)') +
911
+ stat(eff.decisions||0, 'decisions', 'var(--accent)') +
912
+ stat(eff.verifications||0, 'verifications', passRate==null?'var(--muted)':'var(--green)') +
913
+ '</div>';
914
+
915
+ // The headline: time saved from cache — the compounding asset
916
+ html += '<div class="health-item" style="border-left:3px solid var(--green);margin-bottom:12px">' +
917
+ '<div style="font-size:13px;color:var(--muted)">⏱ Estimated time saved from research cache</div>' +
918
+ '<div class="score good" style="font-size:28px">'+fmtDuration(savedSec)+'</div>' +
919
+ '<div class="meta">'+ (m.filesRead||0) +' files read &middot; '+ (m.filesEdited||0) +' files edited (current session)</div>' +
920
+ '</div>';
921
+
922
+ // Recording assets — what we've banked
923
+ var recTotal = r.total||0;
924
+ html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">📦 Recorded Assets</div>';
925
+ html += '<div class="health-item" style="margin-bottom:12px">';
926
+ html += '<div style="display:flex;flex-wrap:wrap;gap:6px">';
927
+ html += '<span class="sev-tag" style="background:rgba(124,58,237,0.15);color:var(--accent)">arch_flow: '+(r.archFlows||0)+'</span>';
928
+ html += '<span class="sev-tag" style="background:#f9731620;color:var(--orange)">gotcha: '+(r.gotchas||0)+'</span>';
929
+ html += '<span class="sev-tag" style="background:#eab30820;color:var(--yellow)">decision: '+(r.decisions||0)+'</span>';
930
+ html += '<span class="sev-tag" style="background:#06b6d420;color:var(--accent2)">research_save: '+(r.researchSaves||0)+'</span>';
931
+ html += '</div>';
932
+ html += '<div class="meta" style="margin-top:6px">Total assets banked: <b style="color:var(--accent2)">'+recTotal+'</b> — each one saves re-research next session</div>';
933
+ html += '</div>';
934
+
935
+ // Verification pass rate
936
+ html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">🧪 Verification</div>';
937
+ html += '<div class="health-item" style="margin-bottom:12px">' +
938
+ '<div class="score" style="color:'+passColor+'">'+(passRate==null?'—':passRate+'%')+'</div>' +
939
+ '<div class="meta">'+(eff.verifications||0)+' runs &middot; pass rate</div>' +
940
+ '</div>';
941
+
942
+ // Recent sessions breakdown
943
+ var sess = eff.recentSessions || [];
944
+ if (sess.length) {
945
+ html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">🕐 Recent Sessions</div>';
946
+ html += sess.map(function(s){
947
+ var d = s.startedAt ? new Date(s.startedAt*1000) : null;
948
+ var dateStr = d ? d.toLocaleDateString()+' '+d.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}) : '-';
949
+ return '<div class="health-item">' +
950
+ '<div style="font-size:12px;color:var(--text)">'+escapeHtml(s.goal || 'untitled goal')+'</div>' +
951
+ '<div class="meta">'+dateStr+' &middot; '+s.toolCalls+' calls &middot; '+s.edits+' edits'+(s.failures?' &middot; <span style="color:var(--red)">'+s.failures+' failures</span>':'')+'</div>' +
952
+ '</div>';
953
+ }).join('');
954
+ }
955
+
956
+ el.innerHTML = html;
957
+ lucide.createIcons();
958
+ }
959
+
960
+ // ============ STALENESS PANEL (GAP 4) ============
961
+ // Assets can become liabilities when stale. Surface them before they bite.
962
+
963
+ function renderStaleness(data){
964
+ var el = document.getElementById("panel-staleness");
965
+ if(!el) return;
966
+ var st = data.staleness || { checked: 0, staleNodes: 0, missing: [] };
967
+ var html = '';
968
+
969
+ if (!st.staleNodes) {
970
+ html += '<div class="health-item" style="border-left:3px solid var(--green)">' +
971
+ '<div class="score good" style="font-size:22px">✅ All fresh</div>' +
972
+ '<div class="meta">'+st.checked+' file references checked — no stale assets found.</div>' +
973
+ '</div>';
974
+ } else {
975
+ html += '<div class="health-item" style="border-left:3px solid var(--red)">' +
976
+ '<div class="score bad" style="font-size:22px">'+st.staleNodes+' stale node(s)</div>' +
977
+ '<div class="meta">'+st.missing.length+' file(s) referenced but missing on disk ('+st.checked+' checked).</div>' +
978
+ '</div>';
979
+ html += '<div style="display:flex;gap:6px;margin:10px 0">' +
980
+ '<button onclick="copyHealCommand(this)" style="background:rgba(124,58,237,0.15);border:1px solid rgba(124,58,237,0.3);border-radius:6px;color:var(--accent);padding:6px 12px;font-size:11px;cursor:pointer">🛠 Copy heal command</button>' +
981
+ '</div>';
982
+ html += st.missing.map(function(m){
983
+ return '<div class="gotcha-item" style="border-color:var(--red)">' +
984
+ '<div class="file">'+escapeHtml(m.filePath)+'</div>' +
985
+ '<div class="desc">Referenced by <b style="color:var(--red)">'+m.nodeCount+'</b> node(s) but the file no longer exists.</div>' +
986
+ '</div>';
987
+ }).join('');
988
+ }
989
+
990
+ el.innerHTML = html;
991
+ lucide.createIcons();
992
+ }
993
+
994
+ function copyHealCommand(btn) {
995
+ var cmd = "kuma_memory({ action: 'heal' })";
996
+ if (navigator.clipboard && navigator.clipboard.writeText) {
997
+ navigator.clipboard.writeText(cmd).then(function() {
998
+ if (btn) { btn.textContent = "✅ Copied!"; setTimeout(function(){ btn.textContent = "🛠 Copy heal command"; }, 1500); }
999
+ }).catch(function() {
1000
+ if (btn) { btn.textContent = "⚠️ Copy failed"; setTimeout(function(){ btn.textContent = "🛠 Copy heal command"; }, 1500); }
1001
+ });
1002
+ } else {
1003
+ // Fallback for non-secure contexts
1004
+ var ta = document.createElement("textarea");
1005
+ ta.value = cmd; document.body.appendChild(ta); ta.select();
1006
+ try { document.execCommand("copy"); if (btn) { btn.textContent = "✅ Copied!"; setTimeout(function(){ btn.textContent = "🛠 Copy heal command"; }, 1500); } } catch(e) {}
1007
+ document.body.removeChild(ta);
1008
+ }
1009
+ }
1010
+
870
1011
  function setupSearch() {
871
1012
  var searchTimeout = null;
872
1013
  document.getElementById("node-search").oninput = function() {
@@ -920,6 +1061,277 @@ function setupSearch() {
920
1061
  }, 150);
921
1062
  };
922
1063
  }
1064
+
1065
+ // ============ ACTIVITY PANEL — Agent Usage Intensity ============
1066
+ // Shows how intensely agents use Kuma: tool calls per day, sessions frequency,
1067
+ // and activity heatmap to understand adoption patterns.
1068
+
1069
+ function renderActivity(data) {
1070
+ var el = document.getElementById("panel-activity");
1071
+ if (!el) return;
1072
+ var eff = data.efficiency || {};
1073
+ var sess = eff.recentSessions || [];
1074
+ var totalSessions = eff.sessions || 0;
1075
+ var totalToolCalls = eff.toolCalls || 0;
1076
+ var totalGotchas = eff.gotchas || 0;
1077
+ var totalArchFlows = eff.archFlows || 0;
1078
+ var totalDecisions = eff.decisions || 0;
1079
+ var totalEdits = eff.metrics ? (eff.metrics.filesEdited || 0) : 0;
1080
+
1081
+ var html = '';
1082
+
1083
+ // === COPY REPORT BUTTON ===
1084
+ html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">';
1085
+ html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px">🤖 Agent Usage Intensity</div>';
1086
+ html += '<button onclick="copyActivityReport()" id="copy-report-btn" style="background:rgba(34,211,238,0.12);border:1px solid rgba(34,211,238,0.25);border-radius:6px;color:var(--accent2);padding:4px 10px;font-size:10px;cursor:pointer;transition:all 0.2s;display:flex;align-items:center;gap:4px"><i data-lucide="copy" style="width:11px;height:11px"></i> Copy Report</button>';
1087
+ html += '</div>';
1088
+
1089
+ // Intensity score: tool_calls per session
1090
+ var avgCallsPerSession = totalSessions > 0 ? (totalToolCalls / totalSessions).toFixed(1) : '0';
1091
+ var intensityLevel = totalToolCalls > 100 ? 'High' : totalToolCalls > 30 ? 'Medium' : totalToolCalls > 0 ? 'Low' : 'None';
1092
+ var intensityColor = totalToolCalls > 100 ? 'var(--green)' : totalToolCalls > 30 ? 'var(--yellow)' : totalToolCalls > 0 ? 'var(--orange)' : 'var(--muted)';
1093
+ var intensityIcon = totalToolCalls > 100 ? '🔥' : totalToolCalls > 30 ? '⚡' : totalToolCalls > 0 ? '💤' : '⚫';
1094
+
1095
+ html += '<div class="health-item" style="margin-bottom:12px;border-left:3px solid ' + intensityColor + '">';
1096
+ html += '<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">';
1097
+ html += '<span style="font-size:24px">' + intensityIcon + '</span>';
1098
+ html += '<div><div style="font-size:16px;font-weight:700;color:' + intensityColor + '">' + intensityLevel + '</div>';
1099
+ html += '<div style="font-size:11px;color:var(--muted)">' + avgCallsPerSession + ' calls/session average</div></div>';
1100
+ html += '</div>';
1101
+ html += '<div style="display:flex;flex-wrap:wrap;gap:6px;margin-top:6px">';
1102
+ html += '<span style="font-size:10px;padding:2px 8px;border-radius:4px;background:rgba(34,211,238,0.15);color:var(--accent2)">' + totalToolCalls + ' total calls</span>';
1103
+ html += '<span style="font-size:10px;padding:2px 8px;border-radius:4px;background:rgba(52,211,153,0.15);color:var(--green)">' + totalSessions + ' sessions</span>';
1104
+ html += '<span style="font-size:10px;padding:2px 8px;border-radius:4px;background:rgba(251,191,36,0.15);color:var(--yellow)">' + totalEdits + ' edits</span>';
1105
+ html += '</div>';
1106
+ html += '</div>';
1107
+
1108
+ // === HEALTH SCORE TREND ===
1109
+ html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">📈 Activity Summary</div>';
1110
+ html += '<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px">';
1111
+
1112
+ var summaryCard = function(val, label, color) {
1113
+ return '<div style="flex:1;min-width:90px;padding:10px;background:var(--bg);border-radius:8px;border:1px solid var(--border);text-align:center">' +
1114
+ '<div style="font-size:20px;font-weight:700;color:' + (color || 'var(--accent2)') + '">' + val + '</div>' +
1115
+ '<div style="font-size:10px;color:var(--muted);margin-top:2px">' + label + '</div></div>';
1116
+ };
1117
+
1118
+ html += summaryCard(totalSessions, 'Sessions', 'var(--accent2)');
1119
+ html += summaryCard(totalToolCalls, 'Tool Calls', 'var(--accent)');
1120
+ html += summaryCard(totalGotchas, 'Gotchas', 'var(--orange)');
1121
+ html += summaryCard(totalArchFlows, 'Arch Flows', 'var(--accent)');
1122
+ html += summaryCard(totalDecisions, 'Decisions', 'var(--accent2)');
1123
+ html += '</div>';
1124
+
1125
+ // === RECENT SESSION ACTIVITY (last 8) ===
1126
+ if (sess.length) {
1127
+ html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">🕐 Recent Activity</div>';
1128
+ html += '<div style="display:flex;flex-direction:column;gap:4px;margin-bottom:12px">';
1129
+
1130
+ var maxCalls = Math.max.apply(null, sess.map(function(s) { return s.toolCalls || 0; }).concat([1]));
1131
+
1132
+ sess.forEach(function(s) {
1133
+ var calls = s.toolCalls || 0;
1134
+ var edits = s.edits || 0;
1135
+ var rollbacks = s.rollbacks || 0;
1136
+ var failures = s.failures || 0;
1137
+ var barWidth = Math.round((calls / maxCalls) * 100);
1138
+ var barColor = failures > 0 ? 'var(--red)' : rollbacks > 0 ? 'var(--yellow)' : 'var(--accent)';
1139
+ var goal = s.goal || 'No goal';
1140
+ var date = s.startedAt ? new Date(s.startedAt * 1000) : null;
1141
+ var dateStr = date ? date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'}) : '-';
1142
+
1143
+ html += '<div style="background:var(--bg);border-radius:6px;padding:8px 10px;border:1px solid var(--border)">';
1144
+ html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">';
1145
+ html += '<span style="font-size:11px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px" title="' + escapeHtml(goal) + '">' + escapeHtml(goal) + '</span>';
1146
+ html += '<span style="font-size:10px;color:var(--muted);flex-shrink:0;margin-left:8px">' + dateStr + '</span>';
1147
+ html += '</div>';
1148
+ // Activity bar
1149
+ html += '<div style="height:6px;background:var(--border);border-radius:3px;overflow:hidden;margin-bottom:4px">';
1150
+ html += '<div style="height:100%;width:' + barWidth + '%;background:' + barColor + ';border-radius:3px;transition:width 0.3s"></div>';
1151
+ html += '</div>';
1152
+ // Metrics row
1153
+ html += '<div style="display:flex;gap:8px;font-size:10px;color:var(--muted)">';
1154
+ html += '<span>🔧 ' + calls + ' calls</span>';
1155
+ if (edits > 0) html += '<span>📝 ' + edits + ' edits</span>';
1156
+ if (rollbacks > 0) html += '<span style="color:var(--yellow)">↩ ' + rollbacks + '</span>';
1157
+ if (failures > 0) html += '<span style="color:var(--red)">❌ ' + failures + '</span>';
1158
+ if (s.safetyScore != null) html += '<span style="color:var(--green)">🛡 ' + s.safetyScore + '</span>';
1159
+ html += '</div>';
1160
+ html += '</div>';
1161
+ });
1162
+ html += '</div>';
1163
+ }
1164
+
1165
+ // === EFFICIENCY TRENDS ===
1166
+ var successRate = eff.verificationPassRate || 100;
1167
+ var passColor = successRate >= 90 ? 'var(--green)' : successRate >= 70 ? 'var(--yellow)' : 'var(--red)';
1168
+ var recordingRate = totalToolCalls > 0 ? Math.round(((totalGotchas + totalArchFlows + totalDecisions) / totalToolCalls) * 100) : 0;
1169
+ // Rollback rate from recent sessions (rollbacks / calls, %)
1170
+ var totalRollbacks = sess.reduce(function(a, s) { return a + (s.rollbacks || 0); }, 0);
1171
+ var totalCalls = sess.reduce(function(a, s) { return a + (s.toolCalls || 0); }, 0);
1172
+ var rollbackRate = totalCalls > 0 ? Math.round((totalRollbacks / totalCalls) * 100) : 0;
1173
+
1174
+ html += '<div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;margin:10px 0 6px">📊 Efficiency Trends</div>';
1175
+ html += '<div class="health-item" style="margin-bottom:8px">';
1176
+ html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">';
1177
+ html += '<span style="font-size:12px;color:var(--text)">Verification Pass Rate</span>';
1178
+ html += '<span style="font-size:14px;font-weight:700;color:' + passColor + '">' + (successRate || '—') + '%</span>';
1179
+ html += '</div>';
1180
+ html += '<div style="height:8px;background:var(--border);border-radius:4px;overflow:hidden">';
1181
+ html += '<div style="height:100%;width:' + (successRate || 0) + '%;background:' + passColor + ';border-radius:4px"></div>';
1182
+ html += '</div>';
1183
+ html += '</div>';
1184
+
1185
+ html += '<div class="health-item" style="margin-bottom:8px">';
1186
+ html += '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">';
1187
+ html += '<span style="font-size:12px;color:var(--text)">Recording Rate</span>';
1188
+ html += '<span style="font-size:14px;font-weight:700;color:' + (recordingRate >= 10 ? 'var(--green)' : recordingRate >= 5 ? 'var(--yellow)' : 'var(--orange)') + '">' + recordingRate + '%</span>';
1189
+ html += '</div>';
1190
+ html += '<div style="height:8px;background:var(--border);border-radius:4px;overflow:hidden">';
1191
+ html += '<div style="height:100%;width:' + Math.min(recordingRate, 100) + '%;background:' + (recordingRate >= 10 ? 'var(--green)' : recordingRate >= 5 ? 'var(--yellow)' : 'var(--orange)') + ';border-radius:4px"></div>';
1192
+ html += '</div>';
1193
+ html += '</div>';
1194
+
1195
+ // === INSIGHT ===
1196
+ html += '<div style="margin-top:12px;padding:10px 12px;background:rgba(167,139,250,0.1);border:1px solid rgba(167,139,250,0.2);border-radius:8px;font-size:11px;color:var(--accent-bright)">';
1197
+ html += '💡 <b>Activity Insight:</b> ';
1198
+ if (totalSessions === 0) {
1199
+ html += 'No sessions recorded yet. Start using Kuma tools to track activity!';
1200
+ } else if (totalToolCalls < 10) {
1201
+ html += 'Getting started! Kuma learns more with each tool call.';
1202
+ } else if (successRate >= 90) {
1203
+ html += 'Excellent! High success rate shows Kuma is well-calibrated for your workflow.';
1204
+ } else if (rollbackRate > 10) {
1205
+ html += 'High rollback rate — Kuma gotchas may help avoid repeated mistakes.';
1206
+ } else {
1207
+ html += 'Healthy usage pattern. The more you use Kuma, the smarter it gets!';
1208
+ }
1209
+ html += '</div>';
1210
+
1211
+ el.innerHTML = html;
1212
+ lucide.createIcons();
1213
+ }
1214
+
1215
+ // ============ COPY ACTIVITY REPORT ============
1216
+ function copyActivityReport() {
1217
+ var btn = document.getElementById('copy-report-btn');
1218
+ if (!btn) return;
1219
+
1220
+ // Find the data from the last render
1221
+ var panel = document.getElementById('panel-activity');
1222
+ if (!panel) return;
1223
+
1224
+ // Build a clean text report
1225
+ var report = [];
1226
+ report.push('=== KUMA STUDIO — ACTIVITY REPORT ===');
1227
+ report.push('Generated: ' + new Date().toLocaleString());
1228
+ report.push('');
1229
+
1230
+ // Get all stat cards
1231
+ var stats = panel.querySelectorAll('[style*="font-size:20px"]');
1232
+ var labels = panel.querySelectorAll('[style*="font-size:10px;color:var(--muted)"]');
1233
+
1234
+ // Extract intensity info
1235
+ var intensityEl = panel.querySelector('[style*="font-size:16px;font-weight:700"]');
1236
+ var intensity = intensityEl ? intensityEl.textContent : 'Unknown';
1237
+ var avgCallsEl = panel.querySelector('[style*="font-size:11px;color:var(--muted)"]');
1238
+ var avgCalls = avgCallsEl ? avgCallsEl.textContent : '';
1239
+
1240
+ report.push('INTENSITY: ' + intensity);
1241
+ if (avgCalls) report.push('Average: ' + avgCalls);
1242
+ report.push('');
1243
+
1244
+ // Get all section headers and content
1245
+ var sections = panel.querySelectorAll('[style*="text-transform:uppercase"]');
1246
+ sections.forEach(function(section) {
1247
+ var title = section.textContent.trim();
1248
+ report.push('--- ' + title + ' ---');
1249
+
1250
+ // Get next sibling content
1251
+ var next = section.nextElementSibling;
1252
+ if (next) {
1253
+ // Extract text content, cleaning up HTML
1254
+ var text = next.textContent.trim()
1255
+ .replace(/\s+/g, ' ')
1256
+ .replace(/([0-9]+) sessions/g, '$1 sessions')
1257
+ .replace(/([0-9]+) tool calls/g, '$1 tool calls')
1258
+ .replace(/([0-9]+) file edits/g, '$1 file edits')
1259
+ .replace(/([0-9]+) rollbacks/g, '$1 rollbacks')
1260
+ .replace(/([0-9]+) failures/g, '$1 failures')
1261
+ .replace(/([0-9]+) total calls/g, '$1 total calls');
1262
+ report.push(text);
1263
+ }
1264
+ report.push('');
1265
+ });
1266
+
1267
+ // Get recent sessions
1268
+ var sessionItems = panel.querySelectorAll('[style*="background:var(--bg);border-radius:6px"]');
1269
+ if (sessionItems.length > 0) {
1270
+ report.push('--- RECENT SESSIONS ---');
1271
+ sessionItems.forEach(function(item) {
1272
+ var goal = item.querySelector('[style*="font-size:11px"]');
1273
+ var metrics = item.querySelector('[style*="font-size:10px"]');
1274
+ if (goal) report.push('• ' + goal.textContent.trim());
1275
+ if (metrics) report.push(' ' + metrics.textContent.trim());
1276
+ });
1277
+ report.push('');
1278
+ }
1279
+
1280
+ // Get efficiency trends
1281
+ var trends = panel.querySelectorAll('.health-item');
1282
+ if (trends.length > 0) {
1283
+ report.push('--- EFFICIENCY TRENDS ---');
1284
+ trends.forEach(function(trend) {
1285
+ var label = trend.querySelector('[style*="font-size:12px"]');
1286
+ var value = trend.querySelector('[style*="font-size:14px"]');
1287
+ if (label && value) {
1288
+ report.push(label.textContent.trim() + ': ' + value.textContent.trim());
1289
+ }
1290
+ });
1291
+ report.push('');
1292
+ }
1293
+
1294
+ // Get insight
1295
+ var insight = panel.querySelector('[style*="rgba(167,139,250,0.1)"]');
1296
+ if (insight) {
1297
+ report.push('--- INSIGHT ---');
1298
+ report.push(insight.textContent.trim());
1299
+ }
1300
+
1301
+ var reportText = report.join('\n');
1302
+
1303
+ // Copy to clipboard
1304
+ navigator.clipboard.writeText(reportText).then(function() {
1305
+ btn.innerHTML = '<i data-lucide="check" style="width:11px;height:11px"></i> Copied!';
1306
+ btn.style.background = 'rgba(52,211,153,0.15)';
1307
+ btn.style.borderColor = 'rgba(52,211,153,0.3)';
1308
+ btn.style.color = 'var(--green)';
1309
+ lucide.createIcons();
1310
+ setTimeout(function() {
1311
+ btn.innerHTML = '<i data-lucide="copy" style="width:11px;height:11px"></i> Copy Report';
1312
+ btn.style.background = 'rgba(34,211,238,0.12)';
1313
+ btn.style.borderColor = 'rgba(34,211,238,0.25)';
1314
+ btn.style.color = 'var(--accent2)';
1315
+ lucide.createIcons();
1316
+ }, 2000);
1317
+ }).catch(function() {
1318
+ // Fallback: select text for manual copy
1319
+ var textarea = document.createElement('textarea');
1320
+ textarea.value = reportText;
1321
+ textarea.style.position = 'fixed';
1322
+ textarea.style.left = '-9999px';
1323
+ document.body.appendChild(textarea);
1324
+ textarea.select();
1325
+ document.execCommand('copy');
1326
+ document.body.removeChild(textarea);
1327
+ btn.innerHTML = '<i data-lucide="check" style="width:11px;height:11px"></i> Copied!';
1328
+ lucide.createIcons();
1329
+ setTimeout(function() {
1330
+ btn.innerHTML = '<i data-lucide="copy" style="width:11px;height:11px"></i> Copy Report';
1331
+ lucide.createIcons();
1332
+ }, 2000);
1333
+ });
1334
+ }
923
1335
  </script>
924
1336
  </body>
925
1337
  </html>