@hugobatist/smartcode 0.1.0

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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +292 -0
  3. package/dist/cli.js +4324 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/index.d.ts +374 -0
  6. package/dist/index.js +1167 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/static/annotations-panel.js +133 -0
  9. package/dist/static/annotations-svg.js +108 -0
  10. package/dist/static/annotations.css +367 -0
  11. package/dist/static/annotations.js +367 -0
  12. package/dist/static/app-init.js +497 -0
  13. package/dist/static/breakpoints.css +69 -0
  14. package/dist/static/breakpoints.js +197 -0
  15. package/dist/static/clipboard.js +94 -0
  16. package/dist/static/collapse-ui.js +325 -0
  17. package/dist/static/command-history.js +89 -0
  18. package/dist/static/context-menu.js +334 -0
  19. package/dist/static/custom-renderer.js +201 -0
  20. package/dist/static/dagre-layout.js +291 -0
  21. package/dist/static/diagram-dom.js +241 -0
  22. package/dist/static/diagram-editor.js +368 -0
  23. package/dist/static/editor-panel.js +107 -0
  24. package/dist/static/editor-popovers.js +187 -0
  25. package/dist/static/event-bus.js +57 -0
  26. package/dist/static/export.js +181 -0
  27. package/dist/static/file-tree.js +470 -0
  28. package/dist/static/ghost-paths.js +397 -0
  29. package/dist/static/heatmap.css +116 -0
  30. package/dist/static/heatmap.js +308 -0
  31. package/dist/static/icons.js +66 -0
  32. package/dist/static/inline-edit.js +294 -0
  33. package/dist/static/interaction-state.js +155 -0
  34. package/dist/static/interaction-tracker.js +93 -0
  35. package/dist/static/live.html +239 -0
  36. package/dist/static/main-layout.css +220 -0
  37. package/dist/static/main.css +334 -0
  38. package/dist/static/mcp-sessions.js +202 -0
  39. package/dist/static/modal.css +109 -0
  40. package/dist/static/modal.js +171 -0
  41. package/dist/static/node-drag.js +293 -0
  42. package/dist/static/pan-zoom.js +199 -0
  43. package/dist/static/renderer.js +280 -0
  44. package/dist/static/search.css +103 -0
  45. package/dist/static/search.js +304 -0
  46. package/dist/static/selection.js +353 -0
  47. package/dist/static/session-player.css +137 -0
  48. package/dist/static/session-player.js +411 -0
  49. package/dist/static/sidebar.css +248 -0
  50. package/dist/static/svg-renderer.js +313 -0
  51. package/dist/static/svg-shapes.js +218 -0
  52. package/dist/static/tokens.css +76 -0
  53. package/dist/static/vendor/dagre-bundle.js +43 -0
  54. package/dist/static/vendor/dagre.min.js +3 -0
  55. package/dist/static/vendor/graphlib.min.js +2 -0
  56. package/dist/static/viewport-transform.js +107 -0
  57. package/dist/static/workspace-switcher.js +202 -0
  58. package/dist/static/ws-client.js +71 -0
  59. package/dist/static/ws-handler.js +125 -0
  60. package/package.json +74 -0
@@ -0,0 +1,411 @@
1
+ /**
2
+ * SmartCodeSessionPlayer -- timeline scrubber, play/pause/speed controls,
3
+ * diff highlighting for session replay.
4
+ * Precomputes cumulative diagram states for O(1) seeking.
5
+ */
6
+ var SmartCodeSessionPlayer = (function() {
7
+ 'use strict';
8
+
9
+ var state = {
10
+ events: [],
11
+ currentIndex: 0,
12
+ playing: false,
13
+ speed: 1,
14
+ animFrameId: null,
15
+ lastFrameTime: 0,
16
+ diagramStates: [],
17
+ visible: false,
18
+ bboxCache: {},
19
+ };
20
+
21
+ // DOM references (set in init)
22
+ var dom = {
23
+ player: null,
24
+ playPause: null,
25
+ scrubber: null,
26
+ speed: null,
27
+ time: null,
28
+ close: null,
29
+ };
30
+
31
+ // ── Cumulative State Model ──
32
+
33
+ function emptyState() {
34
+ return { nodeIds: new Set(), edgeIds: new Set(), statuses: new Map(), labels: new Map() };
35
+ }
36
+
37
+ function cloneState(s) {
38
+ return {
39
+ nodeIds: new Set(s.nodeIds),
40
+ edgeIds: new Set(s.edgeIds),
41
+ statuses: new Map(s.statuses),
42
+ labels: new Map(s.labels),
43
+ };
44
+ }
45
+
46
+ function applyEvent(s, ev) {
47
+ var p = ev.payload || {};
48
+ switch (ev.type) {
49
+ case 'node:visited':
50
+ case 'node:added':
51
+ if (p.nodeId) s.nodeIds.add(p.nodeId);
52
+ if (p.nodeId && p.label) s.labels.set(p.nodeId, p.label);
53
+ break;
54
+ case 'node:removed':
55
+ if (p.nodeId) s.nodeIds.delete(p.nodeId);
56
+ break;
57
+ case 'edge:added':
58
+ case 'edge:traversed':
59
+ if (p.edgeId) s.edgeIds.add(p.edgeId);
60
+ break;
61
+ case 'edge:removed':
62
+ if (p.edgeId) s.edgeIds.delete(p.edgeId);
63
+ break;
64
+ case 'status:changed':
65
+ if (p.nodeId && p.status) s.statuses.set(p.nodeId, p.status);
66
+ break;
67
+ default:
68
+ break;
69
+ }
70
+ return s;
71
+ }
72
+
73
+ function precomputeStates(events) {
74
+ var states = [];
75
+ var current = emptyState();
76
+ for (var i = 0; i < events.length; i++) {
77
+ current = applyEvent(cloneState(current), events[i]);
78
+ states.push(current);
79
+ }
80
+ return states;
81
+ }
82
+
83
+ // ── Diff Computation ──
84
+
85
+ function computeDiff(prevState, currState) {
86
+ var added = [];
87
+ var removed = [];
88
+ var modified = [];
89
+
90
+ currState.nodeIds.forEach(function(id) {
91
+ if (!prevState.nodeIds.has(id)) {
92
+ added.push(id);
93
+ } else {
94
+ var prevStatus = prevState.statuses.get(id);
95
+ var currStatus = currState.statuses.get(id);
96
+ var prevLabel = prevState.labels.get(id);
97
+ var currLabel = currState.labels.get(id);
98
+ if (prevStatus !== currStatus || prevLabel !== currLabel) {
99
+ modified.push(id);
100
+ }
101
+ }
102
+ });
103
+
104
+ prevState.nodeIds.forEach(function(id) {
105
+ if (!currState.nodeIds.has(id)) {
106
+ removed.push(id);
107
+ }
108
+ });
109
+
110
+ return { added: added, removed: removed, modified: modified };
111
+ }
112
+
113
+ // ── Diff Highlighting ──
114
+
115
+ function clearDiffHighlights() {
116
+ var svg = DiagramDOM.getSVG();
117
+ if (!svg) return;
118
+ var highlighted = svg.querySelectorAll('.diff-added, .diff-removed, .diff-modified');
119
+ for (var i = 0; i < highlighted.length; i++) {
120
+ highlighted[i].classList.remove('diff-added', 'diff-removed', 'diff-modified');
121
+ }
122
+ var ghosts = svg.querySelectorAll('.diff-removed-ghost');
123
+ for (var j = 0; j < ghosts.length; j++) {
124
+ ghosts[j].parentNode.removeChild(ghosts[j]);
125
+ }
126
+ }
127
+
128
+ function applyDiffHighlights(diff) {
129
+ clearDiffHighlights();
130
+ var svg = DiagramDOM.getSVG();
131
+ if (!svg) return;
132
+
133
+ diff.added.forEach(function(id) {
134
+ var el = DiagramDOM.findNodeElement(id);
135
+ if (el) el.classList.add('diff-added');
136
+ });
137
+
138
+ diff.modified.forEach(function(id) {
139
+ var el = DiagramDOM.findNodeElement(id);
140
+ if (el) el.classList.add('diff-modified');
141
+ });
142
+
143
+ diff.removed.forEach(function(id) {
144
+ var bbox = state.bboxCache[id];
145
+ if (!bbox) return;
146
+ var ghost = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
147
+ ghost.setAttribute('x', bbox.x);
148
+ ghost.setAttribute('y', bbox.y);
149
+ ghost.setAttribute('width', bbox.width);
150
+ ghost.setAttribute('height', bbox.height);
151
+ ghost.classList.add('diff-removed-ghost');
152
+ var g = svg.querySelector('g');
153
+ if (g) g.appendChild(ghost);
154
+ });
155
+ }
156
+
157
+ // ── BBox Caching (for removed node ghosts) ──
158
+
159
+ function cacheBBoxes() {
160
+ state.bboxCache = {};
161
+ var nodes = DiagramDOM.getAllNodeElements();
162
+ for (var i = 0; i < nodes.length; i++) {
163
+ var el = nodes[i];
164
+ var id = el.getAttribute('data-node-id');
165
+ if (!id) {
166
+ var elId = el.getAttribute('id');
167
+ if (elId) {
168
+ var m = elId.match(/^flowchart-(.+)-\d+$/);
169
+ if (m) id = m[1];
170
+ }
171
+ }
172
+ if (id && el.getBBox) {
173
+ try { state.bboxCache[id] = el.getBBox(); } catch (e) { /* ignore */ }
174
+ }
175
+ }
176
+ }
177
+
178
+ // ── Frame Application ──
179
+
180
+ function applyFrame(index) {
181
+ if (index < 0 || index >= state.diagramStates.length) return;
182
+ state.currentIndex = index;
183
+ cacheBBoxes();
184
+
185
+ if (index > 0) {
186
+ var diff = computeDiff(state.diagramStates[index - 1], state.diagramStates[index]);
187
+ applyDiffHighlights(diff);
188
+ } else {
189
+ clearDiffHighlights();
190
+ }
191
+ updateScrubber();
192
+ }
193
+
194
+ // ── Playback Controls ──
195
+
196
+ function play() {
197
+ if (state.events.length === 0) return;
198
+ if (state.currentIndex >= state.events.length - 1) {
199
+ state.currentIndex = 0;
200
+ applyFrame(0);
201
+ }
202
+ state.playing = true;
203
+ state.lastFrameTime = performance.now();
204
+ dom.playPause.textContent = 'Pause';
205
+ state.animFrameId = requestAnimationFrame(tick);
206
+ }
207
+
208
+ function pause() {
209
+ state.playing = false;
210
+ if (state.animFrameId) cancelAnimationFrame(state.animFrameId);
211
+ state.animFrameId = null;
212
+ if (dom.playPause) dom.playPause.textContent = 'Play';
213
+ }
214
+
215
+ function tick(now) {
216
+ if (!state.playing) return;
217
+ if (state.currentIndex >= state.events.length - 1) { pause(); return; }
218
+
219
+ var elapsed = (now - state.lastFrameTime) * state.speed;
220
+ var nextEvent = state.events[state.currentIndex + 1];
221
+ var currEvent = state.events[state.currentIndex];
222
+ var timeDelta = (nextEvent.ts - currEvent.ts) || 500;
223
+
224
+ if (elapsed >= timeDelta) {
225
+ state.currentIndex++;
226
+ state.lastFrameTime = now;
227
+ applyFrame(state.currentIndex);
228
+ }
229
+ state.animFrameId = requestAnimationFrame(tick);
230
+ }
231
+
232
+ function seekTo(index) {
233
+ var clamped = Math.max(0, Math.min(index, state.events.length - 1));
234
+ applyFrame(clamped);
235
+ }
236
+
237
+ // ── Session Loading ──
238
+
239
+ function loadSession(sessionId) {
240
+ return fetch((window.SmartCodeBaseUrl || '') + '/api/session/' + encodeURIComponent(sessionId))
241
+ .then(function(r) { return r.json(); })
242
+ .then(function(data) {
243
+ if (!data.events || data.events.length === 0) return;
244
+ state.events = data.events;
245
+ state.diagramStates = precomputeStates(state.events);
246
+ state.currentIndex = 0;
247
+ dom.scrubber.max = state.events.length - 1;
248
+ show();
249
+ applyFrame(0);
250
+ })
251
+ .catch(function(err) {
252
+ console.warn('Failed to load session:', err.message);
253
+ });
254
+ }
255
+
256
+ function handleSessionEvent(sessionId, event) {
257
+ if (!state.visible) return;
258
+ state.events.push(event);
259
+ var prev = state.diagramStates.length > 0
260
+ ? state.diagramStates[state.diagramStates.length - 1]
261
+ : emptyState();
262
+ state.diagramStates.push(applyEvent(cloneState(prev), event));
263
+ dom.scrubber.max = state.events.length - 1;
264
+ if (state.currentIndex === state.events.length - 2) {
265
+ state.currentIndex = state.events.length - 1;
266
+ applyFrame(state.currentIndex);
267
+ }
268
+ updateScrubber();
269
+ }
270
+
271
+ // ── Session List ──
272
+
273
+ function fetchSessionList(file) {
274
+ if (!file) return Promise.resolve([]);
275
+ return fetch((window.SmartCodeBaseUrl || '') + '/api/sessions/' + encodeURIComponent(file))
276
+ .then(function(r) { return r.ok ? r.json() : { sessions: [] }; })
277
+ .then(function(data) {
278
+ renderSessionList(data.sessions || []);
279
+ return data.sessions || [];
280
+ })
281
+ .catch(function() { renderSessionList([]); return []; });
282
+ }
283
+
284
+ function renderSessionList(sessions) {
285
+ var list = document.getElementById('sessionList');
286
+ if (!list) return;
287
+ // Clear existing items safely
288
+ while (list.firstChild) list.removeChild(list.firstChild);
289
+ if (sessions.length === 0) {
290
+ var empty = document.createElement('div');
291
+ empty.style.cssText = 'padding:8px;color:#71717a;font-size:12px;';
292
+ empty.textContent = 'No sessions recorded';
293
+ list.appendChild(empty);
294
+ return;
295
+ }
296
+ sessions.forEach(function(s) {
297
+ var item = document.createElement('div');
298
+ item.className = 'session-list-item';
299
+ var dur = s.duration ? Math.round(s.duration / 1000) + 's' : '?';
300
+ item.textContent = s.sessionId.substring(0, 8) + ' (' + s.totalEvents + ' events, ' + dur + ')';
301
+ item.addEventListener('click', function() {
302
+ loadSession(s.sessionId);
303
+ toggleSessionDropdown(false);
304
+ });
305
+ list.appendChild(item);
306
+ });
307
+ }
308
+
309
+ function toggleSessionDropdown(forceState) {
310
+ var dd = document.getElementById('sessionDropdown');
311
+ if (!dd) return;
312
+ var shouldShow = typeof forceState === 'boolean' ? forceState : dd.classList.contains('hidden');
313
+ if (shouldShow) {
314
+ dd.classList.remove('hidden');
315
+ var file = window.SmartCodeFileTree ? SmartCodeFileTree.getCurrentFile() : null;
316
+ fetchSessionList(file);
317
+ } else {
318
+ dd.classList.add('hidden');
319
+ }
320
+ }
321
+
322
+ // ── Visibility ──
323
+
324
+ function show() {
325
+ state.visible = true;
326
+ if (dom.player) dom.player.classList.remove('hidden');
327
+ }
328
+
329
+ function close() {
330
+ pause();
331
+ clearDiffHighlights();
332
+ state.visible = false;
333
+ state.events = [];
334
+ state.diagramStates = [];
335
+ state.currentIndex = 0;
336
+ state.bboxCache = {};
337
+ if (dom.player) dom.player.classList.add('hidden');
338
+ if (dom.scrubber) { dom.scrubber.value = 0; dom.scrubber.max = 0; }
339
+ if (dom.time) dom.time.textContent = '0 / 0';
340
+ }
341
+
342
+ // ── Scrubber ──
343
+
344
+ function updateScrubber() {
345
+ if (dom.scrubber) dom.scrubber.value = state.currentIndex;
346
+ if (dom.time) {
347
+ dom.time.textContent = (state.currentIndex + 1) + ' / ' + state.events.length;
348
+ }
349
+ }
350
+
351
+ // ── Initialization ──
352
+
353
+ function init() {
354
+ dom.player = document.getElementById('sessionPlayer');
355
+ dom.playPause = document.getElementById('spPlayPause');
356
+ dom.scrubber = document.getElementById('spScrubber');
357
+ dom.speed = document.getElementById('spSpeed');
358
+ dom.time = document.getElementById('spTime');
359
+ dom.close = document.getElementById('spClose');
360
+
361
+ if (dom.playPause) {
362
+ dom.playPause.addEventListener('click', function() {
363
+ state.playing ? pause() : play();
364
+ });
365
+ }
366
+ if (dom.scrubber) {
367
+ dom.scrubber.addEventListener('input', function() {
368
+ seekTo(parseInt(dom.scrubber.value, 10));
369
+ });
370
+ }
371
+ if (dom.speed) {
372
+ dom.speed.addEventListener('change', function() {
373
+ state.speed = parseFloat(dom.speed.value);
374
+ });
375
+ }
376
+ if (dom.close) {
377
+ dom.close.addEventListener('click', close);
378
+ }
379
+
380
+ // Sessions button dropdown toggle
381
+ var btn = document.getElementById('btnSessions');
382
+ if (btn) {
383
+ btn.addEventListener('click', function() { toggleSessionDropdown(); });
384
+ }
385
+
386
+ // Close dropdown on outside click
387
+ document.addEventListener('click', function(e) {
388
+ var dd = document.getElementById('sessionDropdown');
389
+ var btn2 = document.getElementById('btnSessions');
390
+ if (dd && !dd.contains(e.target) && e.target !== btn2) {
391
+ dd.classList.add('hidden');
392
+ }
393
+ });
394
+ }
395
+
396
+ return {
397
+ init: init,
398
+ loadSession: loadSession,
399
+ handleSessionEvent: handleSessionEvent,
400
+ fetchSessionList: fetchSessionList,
401
+ toggleSessionDropdown: toggleSessionDropdown,
402
+ show: show,
403
+ close: close,
404
+ isVisible: function() { return state.visible; },
405
+ isPlaying: function() { return state.playing; },
406
+ getIndex: function() { return state.currentIndex; },
407
+ play: play,
408
+ pause: pause,
409
+ seekTo: seekTo,
410
+ };
411
+ })();
@@ -0,0 +1,248 @@
1
+ /* sidebar.css -- Sidebar, breadcrumb, context menu, MCP session styles
2
+ * Extracted from main.css (Phase 20 QUAL-01) to stay under 500-line limit.
3
+ */
4
+
5
+ /* -- Breadcrumb bar -- */
6
+ .breadcrumb-bar {
7
+ display: flex;
8
+ align-items: center;
9
+ gap: 4px;
10
+ padding: 8px 12px;
11
+ background: var(--surface-1);
12
+ border-bottom: 1px solid var(--border-subtle);
13
+ font-size: 13px;
14
+ color: var(--text-secondary);
15
+ z-index: 5;
16
+ position: relative;
17
+ }
18
+ .breadcrumb-item {
19
+ cursor: pointer;
20
+ padding: 2px 6px;
21
+ border-radius: 3px;
22
+ transition: background 0.15s;
23
+ }
24
+ .breadcrumb-item:hover:not(.current) {
25
+ background: var(--surface-3);
26
+ color: var(--text-primary);
27
+ }
28
+ .breadcrumb-item.current {
29
+ color: var(--text-primary);
30
+ font-weight: 600;
31
+ cursor: default;
32
+ }
33
+ .breadcrumb-separator {
34
+ color: var(--text-tertiary);
35
+ user-select: none;
36
+ }
37
+ .breadcrumb-exit {
38
+ margin-left: auto;
39
+ padding: 4px 10px;
40
+ background: var(--surface-3);
41
+ border: 1px solid var(--border-subtle);
42
+ border-radius: 6px;
43
+ color: var(--text-primary);
44
+ cursor: pointer;
45
+ font-size: 12px;
46
+ font-weight: 500;
47
+ font-family: inherit;
48
+ transition: background 0.15s;
49
+ }
50
+ .breadcrumb-exit:hover {
51
+ background: var(--surface-4);
52
+ }
53
+
54
+ /* -- Auto-collapse notice -- */
55
+ .auto-collapse-notice {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 8px;
59
+ padding: 8px 12px;
60
+ background: var(--accent-muted);
61
+ border-bottom: 1px solid var(--accent-muted);
62
+ font-size: 12px;
63
+ color: var(--text-secondary);
64
+ z-index: 5;
65
+ position: relative;
66
+ }
67
+ .notice-icon { font-size: 14px; flex-shrink: 0; }
68
+ .notice-text { flex: 1; }
69
+ .notice-expand-all {
70
+ padding: 3px 10px;
71
+ background: var(--surface-3);
72
+ border: 1px solid var(--border-subtle);
73
+ border-radius: 5px;
74
+ color: var(--text-primary);
75
+ cursor: pointer;
76
+ font-size: 11px;
77
+ font-weight: 500;
78
+ font-family: inherit;
79
+ transition: background 0.15s;
80
+ }
81
+ .notice-expand-all:hover { background: var(--surface-4); }
82
+ .notice-dismiss {
83
+ background: none;
84
+ border: none;
85
+ color: var(--text-secondary);
86
+ cursor: pointer;
87
+ font-size: 14px;
88
+ padding: 2px 4px;
89
+ border-radius: 3px;
90
+ transition: background 0.15s;
91
+ }
92
+ .notice-dismiss:hover { background: var(--surface-3); color: var(--text-primary); }
93
+
94
+ /* -- Focus mode: dim non-focused clusters -- */
95
+ .diagram-focus-mode .cluster:not(.focused) {
96
+ opacity: 0.6;
97
+ transition: opacity 0.2s;
98
+ }
99
+ .diagram-focus-mode .cluster.focused {
100
+ opacity: 1;
101
+ }
102
+
103
+ /* -- Context Menu -- */
104
+ .context-menu {
105
+ position: fixed;
106
+ z-index: 10000;
107
+ background: var(--surface-1);
108
+ border: 1px solid var(--border-default);
109
+ border-radius: 8px;
110
+ box-shadow: 0 8px 24px rgba(0,0,0,0.4);
111
+ padding: 4px 0;
112
+ min-width: 180px;
113
+ font-family: 'Inter', sans-serif;
114
+ font-size: 13px;
115
+ }
116
+ .context-menu-item {
117
+ padding: 8px 16px;
118
+ cursor: pointer;
119
+ color: var(--text-primary);
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 8px;
123
+ transition: background 0.1s;
124
+ }
125
+ .context-menu-item:hover { background: var(--surface-3); }
126
+ .context-menu-item.danger { color: var(--status-problem); }
127
+ .context-menu-item.danger:hover { background: rgba(239,68,68,0.15); }
128
+ .context-menu-separator { height: 1px; background: var(--border-default); margin: 4px 0; }
129
+ .context-menu-submenu { font-size: 12px; padding-left: 24px; }
130
+ .context-menu-item.risk-high { color: var(--status-problem); }
131
+ .context-menu-item.risk-medium { color: var(--status-progress); }
132
+ .context-menu-item.risk-low { color: var(--status-ok); }
133
+
134
+ /* -- Selection -- */
135
+ .selected-edge path { stroke: var(--accent) !important; stroke-width: 3 !important; }
136
+ .selected-edge .smartcode-edge-path { stroke: var(--accent) !important; stroke-width: 3 !important; }
137
+ .selection-indicator { pointer-events: none; }
138
+
139
+ /* -- Sidebar Tabs (Files / Sessions) -- */
140
+ .sidebar-tabs {
141
+ display: flex;
142
+ gap: 2px;
143
+ }
144
+ .sidebar-tab {
145
+ padding: 4px 10px;
146
+ border: none;
147
+ background: transparent;
148
+ color: var(--text-tertiary);
149
+ font-size: 12px;
150
+ font-weight: 600;
151
+ font-family: inherit;
152
+ cursor: pointer;
153
+ border-radius: 4px;
154
+ transition: all 0.15s;
155
+ }
156
+ .sidebar-tab:hover { color: var(--text-secondary); background: var(--surface-3); }
157
+ .sidebar-tab.active { color: var(--text-primary); background: var(--surface-3); }
158
+
159
+ /* -- MCP Session Cards -- */
160
+ .mcp-session-card {
161
+ margin: 4px 6px;
162
+ border: 1px solid var(--border-subtle);
163
+ border-radius: 6px;
164
+ overflow: hidden;
165
+ }
166
+ .mcp-session-header {
167
+ display: flex;
168
+ align-items: center;
169
+ gap: 6px;
170
+ padding: 8px 10px;
171
+ background: var(--surface-2);
172
+ font-size: 12px;
173
+ font-weight: 600;
174
+ color: var(--text-primary);
175
+ }
176
+ .mcp-session-dot {
177
+ width: 8px;
178
+ height: 8px;
179
+ border-radius: 50%;
180
+ flex-shrink: 0;
181
+ }
182
+ .mcp-session-dot.active { background: var(--status-ok); box-shadow: 0 0 6px rgba(34,197,94,0.5); }
183
+ .mcp-session-dot.inactive { background: var(--text-tertiary); }
184
+ .mcp-session-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
185
+ .mcp-session-time { font-size: 10px; color: var(--text-tertiary); font-weight: 400; white-space: nowrap; }
186
+ .mcp-session-rename-btn {
187
+ display: flex;
188
+ align-items: center;
189
+ justify-content: center;
190
+ width: 20px;
191
+ height: 20px;
192
+ border: none;
193
+ background: transparent;
194
+ color: var(--text-tertiary);
195
+ cursor: pointer;
196
+ border-radius: 4px;
197
+ padding: 0;
198
+ flex-shrink: 0;
199
+ opacity: 0;
200
+ transition: opacity 0.15s, background 0.15s, color 0.15s;
201
+ }
202
+ .mcp-session-header:hover .mcp-session-rename-btn { opacity: 1; }
203
+ .mcp-session-rename-btn:hover { background: var(--surface-3); color: var(--text-primary); }
204
+ .mcp-session-nofiles { padding: 6px 10px; font-size: 11px; color: var(--text-tertiary); }
205
+ .mcp-session-file {
206
+ display: flex;
207
+ align-items: center;
208
+ gap: 6px;
209
+ padding: 5px 10px 5px 18px;
210
+ cursor: pointer;
211
+ font-size: 12px;
212
+ color: var(--text-secondary);
213
+ transition: background 0.1s;
214
+ }
215
+ .mcp-session-file:hover { background: var(--surface-3); color: var(--text-primary); }
216
+ .mcp-session-file.active { background: var(--accent-muted); color: var(--accent); }
217
+ .mcp-session-file-icon { display: flex; align-items: center; flex-shrink: 0; }
218
+ .mcp-session-file-icon svg { width: 13px; height: 13px; }
219
+ .mcp-session-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
220
+ .mcp-session-divider {
221
+ display: flex;
222
+ align-items: center;
223
+ gap: 8px;
224
+ padding: 10px 10px 4px;
225
+ font-size: 10px;
226
+ font-weight: 600;
227
+ color: var(--text-tertiary);
228
+ text-transform: uppercase;
229
+ letter-spacing: 0.5px;
230
+ }
231
+ .mcp-session-divider::after {
232
+ content: '';
233
+ flex: 1;
234
+ height: 1px;
235
+ background: var(--border-subtle);
236
+ }
237
+ .mcp-sessions-empty {
238
+ display: flex;
239
+ flex-direction: column;
240
+ align-items: center;
241
+ justify-content: center;
242
+ padding: 32px 16px;
243
+ text-align: center;
244
+ font-size: 12px;
245
+ color: var(--text-secondary);
246
+ }
247
+ .mcp-sessions-empty-icon { margin-bottom: 8px; opacity: 0.5; }
248
+ .mcp-sessions-empty-icon svg { width: 24px; height: 24px; }