@bakapiano/ccsm 0.9.0 → 0.10.1

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 (69) hide show
  1. package/CLAUDE.md +222 -195
  2. package/README.md +77 -79
  3. package/lib/cliSessionWatcher.js +249 -0
  4. package/lib/config.js +101 -24
  5. package/lib/folders.js +96 -0
  6. package/lib/localCliSessions.js +177 -0
  7. package/lib/persistedSessions.js +134 -0
  8. package/lib/webTerminal.js +31 -18
  9. package/lib/workspace.js +26 -4
  10. package/package.json +1 -1
  11. package/public/assets/claude-color.svg +1 -0
  12. package/public/assets/codex-color.svg +1 -0
  13. package/public/assets/copilot-color.svg +1 -0
  14. package/public/css/base.css +22 -5
  15. package/public/css/cards.css +37 -3
  16. package/public/css/feedback.css +127 -43
  17. package/public/css/forms.css +97 -25
  18. package/public/css/layout.css +74 -26
  19. package/public/css/modal.css +40 -26
  20. package/public/css/responsive.css +2 -2
  21. package/public/css/sidebar.css +424 -25
  22. package/public/css/terminals.css +138 -0
  23. package/public/css/tokens.css +28 -12
  24. package/public/css/wco.css +38 -39
  25. package/public/css/widgets.css +1177 -6
  26. package/public/index.html +35 -2
  27. package/public/js/api.js +194 -37
  28. package/public/js/components/AdoptModal.js +171 -0
  29. package/public/js/components/App.js +1 -11
  30. package/public/js/components/DirectoryPicker.js +203 -0
  31. package/public/js/components/EntityFormModal.js +105 -0
  32. package/public/js/components/Modal.js +51 -0
  33. package/public/js/components/OfflineBanner.js +29 -23
  34. package/public/js/components/PageTitleBar.js +13 -0
  35. package/public/js/components/Picker.js +179 -0
  36. package/public/js/components/Popover.js +55 -0
  37. package/public/js/components/Sidebar.js +219 -32
  38. package/public/js/components/TerminalView.js +27 -3
  39. package/public/js/components/useDragSort.js +67 -0
  40. package/public/js/dialog.js +10 -2
  41. package/public/js/icons.js +66 -3
  42. package/public/js/main.js +54 -3
  43. package/public/js/pages/AboutPage.js +80 -0
  44. package/public/js/pages/ConfigurePage.js +429 -207
  45. package/public/js/pages/LaunchPage.js +326 -86
  46. package/public/js/pages/SessionsPage.js +91 -41
  47. package/public/js/state.js +102 -73
  48. package/public/manifest.webmanifest +2 -2
  49. package/scripts/install.js +7 -2
  50. package/server.js +755 -441
  51. package/lib/favorites.js +0 -51
  52. package/lib/focus.js +0 -369
  53. package/lib/labels.js +0 -29
  54. package/lib/launcher.js +0 -219
  55. package/lib/sessions.js +0 -272
  56. package/lib/snapshot.js +0 -141
  57. package/public/js/actions.js +0 -107
  58. package/public/js/components/Fab.js +0 -11
  59. package/public/js/components/FavoritesTable.js +0 -81
  60. package/public/js/components/Footer.js +0 -12
  61. package/public/js/components/NewSessionModal.js +0 -153
  62. package/public/js/components/PageHead.js +0 -33
  63. package/public/js/components/Pagination.js +0 -27
  64. package/public/js/components/RecentTable.js +0 -68
  65. package/public/js/components/SessionsTable.js +0 -71
  66. package/public/js/components/SnapshotPanel.js +0 -77
  67. package/public/js/components/TitleCell.js +0 -40
  68. package/public/js/components/WorkspacesGrid.js +0 -41
  69. package/public/js/pages/TerminalsPage.js +0 -74
@@ -34,7 +34,9 @@
34
34
  position: fixed;
35
35
  inset: 0;
36
36
  z-index: 200;
37
- background: rgba(26, 24, 21, 0.48);
37
+ background: rgba(26, 24, 21, 0.42);
38
+ backdrop-filter: blur(2px);
39
+ -webkit-backdrop-filter: blur(2px);
38
40
  display: flex;
39
41
  align-items: center;
40
42
  justify-content: center;
@@ -46,15 +48,16 @@
46
48
  .modal {
47
49
  background: var(--bg-elev);
48
50
  border: 1px solid var(--border);
49
- border-radius: var(--r-md);
51
+ border-radius: 14px;
50
52
  width: min(560px, 100%);
51
53
  max-height: 90vh;
52
54
  display: flex;
53
55
  flex-direction: column;
54
56
  overflow: hidden;
55
57
  box-shadow:
56
- 0 24px 64px -16px rgba(26, 24, 21, 0.35),
57
- 0 4px 12px -2px rgba(26, 24, 21, 0.15);
58
+ 0 32px 80px -20px rgba(26, 24, 21, 0.32),
59
+ 0 8px 20px -4px rgba(26, 24, 21, 0.10),
60
+ 0 0 0 1px rgba(26, 24, 21, 0.02);
58
61
  animation: modal-in .22s cubic-bezier(.4, 0, .2, 1);
59
62
  }
60
63
  @keyframes modal-in {
@@ -66,30 +69,44 @@
66
69
  display: flex;
67
70
  align-items: center;
68
71
  justify-content: space-between;
69
- padding: var(--s-4) var(--s-6);
72
+ gap: var(--s-3);
73
+ padding: 16px 20px;
70
74
  border-bottom: 1px solid var(--border-soft);
71
75
  }
72
76
  .modal-head h2 {
73
- font-size: 16px;
77
+ font-size: 15px;
74
78
  font-weight: 600;
75
79
  color: var(--ink);
80
+ letter-spacing: -0.005em;
81
+ margin: 0;
76
82
  }
83
+
84
+ /* Red close X · subtle by default, fills red on hover. Available on
85
+ <Modal> and on confirm/prompt dialogs via dialog.js. */
77
86
  .modal-close {
78
87
  appearance: none;
79
88
  background: transparent;
80
89
  border: 0;
81
- padding: 4px;
82
- margin: -4px;
90
+ padding: 0;
91
+ width: 26px;
92
+ height: 26px;
93
+ border-radius: 8px;
83
94
  cursor: pointer;
84
- color: var(--ink-muted);
85
- border-radius: 4px;
95
+ color: #c44a4a;
86
96
  display: inline-flex;
87
- transition: color .12s ease, background .12s ease;
97
+ align-items: center;
98
+ justify-content: center;
99
+ transition: color .12s ease, background .12s ease, transform .12s ease;
100
+ }
101
+ .modal-close:hover {
102
+ background: rgba(196, 74, 74, 0.10);
103
+ color: #b03737;
88
104
  }
89
- .modal-close:hover { color: var(--ink); background: var(--bg); }
105
+ .modal-close:active { transform: scale(0.94); }
106
+ .modal-close svg { display: block; }
90
107
 
91
108
  .modal-body {
92
- padding: var(--s-5) var(--s-6);
109
+ padding: 18px 20px;
93
110
  overflow-y: auto;
94
111
  flex: 1;
95
112
  }
@@ -110,8 +127,8 @@
110
127
  .modal-foot {
111
128
  display: flex;
112
129
  justify-content: flex-end;
113
- gap: var(--s-3);
114
- padding: var(--s-3) var(--s-6);
130
+ gap: 8px;
131
+ padding: 12px 20px;
115
132
  border-top: 1px solid var(--border-soft);
116
133
  background: var(--bg);
117
134
  }
@@ -146,22 +163,19 @@
146
163
  margin-top: var(--s-3);
147
164
  }
148
165
 
149
- /* Narrower variant for confirm/prompt — no border under head, tighter padding */
166
+ /* Narrower variant for confirm/prompt — keeps a head with the red close X */
150
167
  .modal-dialog { width: min(440px, 100%); }
151
- .modal-dialog .modal-head { padding: var(--s-4) var(--s-5) var(--s-3); border-bottom: 0; }
152
- .modal-dialog .modal-head h2 {
153
- font-size: 14.5px;
154
- font-weight: 600;
155
- color: var(--ink);
156
- line-height: 1.4;
157
- }
158
- .modal-dialog .modal-body { padding: 0 var(--s-5) var(--s-4); }
159
- .modal-dialog .modal-foot { padding: var(--s-3) var(--s-5); }
168
+ .modal-dialog .modal-body { padding: 14px 20px 16px; }
160
169
  .modal-dialog input[type="text"] {
161
170
  width: 100%;
162
171
  max-width: none;
163
- margin-top: var(--s-2);
172
+ margin-top: 8px;
164
173
  }
174
+
175
+ /* Picker modals (CLI / Folder / Repos on launch page) — body is flush
176
+ so Picker.js controls inner padding. */
177
+ .modal-picker .modal-body { padding: 0; }
178
+
165
179
  .dialog-msg {
166
180
  font-size: 13.5px;
167
181
  color: var(--ink-mid);
@@ -2,9 +2,9 @@
2
2
 
3
3
  @media (max-width: 900px) {
4
4
  .app { grid-template-columns: var(--sidebar-w-collapsed) 1fr !important; }
5
- .sidebar { padding: var(--s-4) var(--s-2); }
5
+ .sidebar { padding: 0 var(--s-2); }
6
6
  .brand-name, .nav-label, .nav-badge { opacity: 0; pointer-events: none; }
7
- .main { padding: var(--s-6) var(--s-5) var(--s-5); }
7
+ .main { padding: 0 var(--s-4); }
8
8
  .page-head { flex-direction: column; gap: var(--s-3); }
9
9
  .config-grid { grid-template-columns: 1fr; }
10
10
  }
@@ -4,51 +4,78 @@
4
4
  position: sticky;
5
5
  top: 0;
6
6
  height: 100vh;
7
- background: var(--sidebar-bg);
8
- border-right: 1px solid var(--border);
7
+ background: var(--ui-bg);
8
+ border-right: 1px solid var(--ui-border);
9
9
  display: flex;
10
10
  flex-direction: column;
11
- padding: var(--s-4) var(--s-3);
12
- /* visible: let the resize handle (positioned at right: -3px) overflow the
13
- sidebar's right edge. We instead clip the things that USED to need
14
- this via the .sidebar-nav scroll region. */
11
+ padding: 0 var(--s-2);
15
12
  overflow: visible;
16
13
  transition: padding .25s cubic-bezier(.4, 0, .2, 1);
17
14
  }
18
15
  .sidebar[data-collapsed="true"] {
19
- padding: var(--s-4) var(--s-2);
16
+ padding: 0;
17
+ }
18
+ /* Collapsed: every clickable row becomes a 28x28 square, centered in
19
+ the narrow sidebar column. Width auto + margin auto so the square
20
+ sits centered regardless of what padding the parent has. */
21
+ .sidebar[data-collapsed="true"] .nav-item,
22
+ .sidebar[data-collapsed="true"] .util-item,
23
+ .sidebar[data-collapsed="true"] .sidebar-brand-button,
24
+ .sidebar[data-collapsed="true"] .tree-folder-head,
25
+ .sidebar[data-collapsed="true"] .tree-session {
26
+ width: 28px;
27
+ height: 28px;
28
+ min-height: 28px;
29
+ padding: 0;
30
+ margin: 0 auto;
31
+ justify-content: center;
32
+ gap: 0;
33
+ }
34
+ .sidebar[data-collapsed="true"] .sidebar-top {
35
+ padding: 0;
36
+ min-height: 40px;
37
+ justify-content: center;
38
+ }
39
+ .sidebar[data-collapsed="true"] .sidebar-top .collapse-toggle {
40
+ width: 40px;
41
+ height: 40px;
42
+ min-height: 40px;
43
+ margin: 0;
20
44
  }
21
45
 
22
46
  .sidebar-brand {
23
47
  display: flex;
24
48
  align-items: center;
25
49
  gap: var(--s-2);
26
- /* Top padding matches .main's so the brand mark sits on the same
27
- horizontal band as the page-title. */
28
- padding: var(--s-4) var(--s-2);
29
- min-height: 56px;
50
+ padding: 0 var(--s-2);
51
+ min-height: 28px;
52
+ height: 28px;
30
53
  }
31
54
  .brand-mark {
32
55
  display: inline-flex;
33
56
  align-items: center;
34
57
  justify-content: center;
35
- width: 32px;
36
- height: 32px;
37
- flex: 0 0 32px;
58
+ width: 20px;
59
+ height: 20px;
60
+ flex: 0 0 20px;
38
61
  background: transparent;
62
+ color: var(--accent);
39
63
  }
40
- .brand-mark svg { display: block; }
64
+ .brand-mark svg { display: block; width: 20px; height: 20px; }
41
65
  .brand-name {
42
- font-size: 19px;
66
+ font-size: 14px;
43
67
  font-weight: 600;
44
68
  letter-spacing: -0.02em;
45
69
  color: var(--ink);
46
70
  white-space: nowrap;
47
71
  opacity: 1;
72
+ line-height: 1;
48
73
  transition: opacity .15s ease;
49
74
  }
50
75
  .brand-dot { color: var(--accent); }
51
- .sidebar[data-collapsed="true"] .brand-name { opacity: 0; pointer-events: none; }
76
+ .sidebar[data-collapsed="true"] .brand-name { display: none; }
77
+ .sidebar[data-collapsed="true"] .brand-version { display: none; }
78
+ .sidebar[data-collapsed="true"] .sidebar-brand { justify-content: center; padding-left: 0; padding-right: 0; }
52
79
 
53
80
  .sidebar-nav {
54
81
  display: flex;
@@ -69,17 +96,16 @@
69
96
  min-height: 42px;
70
97
  border-radius: var(--r-sm);
71
98
  cursor: pointer;
72
- color: var(--ink-mid);
99
+ color: var(--ink);
73
100
  font-family: var(--body);
74
- font-size: 14.5px;
75
- font-weight: 500;
101
+ font-size: 13px;
102
+ font-weight: 400;
76
103
  text-align: left;
77
104
  transition: background .12s ease, color .12s ease;
78
105
  position: relative;
79
106
  }
80
107
  .nav-item:hover, .util-item:hover {
81
108
  background: var(--sidebar-hover);
82
- color: var(--ink);
83
109
  }
84
110
  .nav-item[aria-selected="true"] {
85
111
  background: var(--sidebar-active);
@@ -125,6 +151,13 @@
125
151
  flex: 1;
126
152
  }
127
153
  .sidebar[data-collapsed="true"] .nav-label { opacity: 0; pointer-events: none; }
154
+ /* Collapsed sidebar (60px wide): hide the label entirely so the icon
155
+ centers in the narrow column instead of being pushed off-screen by
156
+ the still-laid-out (but invisible) label text. Same for the badge. */
157
+ .sidebar[data-collapsed="true"] .nav-label,
158
+ .sidebar[data-collapsed="true"] .nav-badge {
159
+ display: none;
160
+ }
128
161
 
129
162
  .nav-badge {
130
163
  font-family: var(--mono);
@@ -156,7 +189,106 @@
156
189
 
157
190
  .sidebar-foot {
158
191
  margin-top: auto;
159
- padding-top: var(--s-2);
192
+ padding-top: 2px;
193
+ display: flex;
194
+ flex-direction: row;
195
+ justify-content: flex-end;
196
+ align-items: center;
197
+ gap: 2px;
198
+ }
199
+ /* Brand block at the bottom of the sidebar. Clickable: navigates to
200
+ About. Matches nav-item height so collapsed sidebar reads as a
201
+ uniform column of equally-sized icons. */
202
+ .sidebar-brand-button {
203
+ appearance: none;
204
+ background: transparent;
205
+ border: 0;
206
+ display: flex;
207
+ align-items: center;
208
+ gap: 8px;
209
+ width: 100%;
210
+ padding: 0 var(--s-2);
211
+ min-height: 28px;
212
+ height: 28px;
213
+ cursor: pointer;
214
+ border-radius: 4px;
215
+ text-align: left;
216
+ font: inherit;
217
+ color: var(--ink);
218
+ transition: background .12s;
219
+ }
220
+ .sidebar-brand-button[aria-selected="true"],
221
+ .sidebar-brand-button[aria-selected="true"]:hover {
222
+ background: var(--sidebar-active);
223
+ font-weight: 500;
224
+ }
225
+ .sidebar-brand-button:hover { background: var(--sidebar-hover); }
226
+ .sidebar-foot .brand-mark {
227
+ width: 14px;
228
+ height: 14px;
229
+ flex: 0 0 14px;
230
+ display: inline-flex;
231
+ align-items: center;
232
+ justify-content: center;
233
+ /* Lock the SVG box exactly to 14×14 so flex centering doesn't fight
234
+ the SVG's intrinsic dimensions; otherwise the mark drifts a couple
235
+ pixels low because <svg width=32 height=32> wants more vertical
236
+ space than the 14px row gives it. */
237
+ line-height: 0;
238
+ }
239
+ .sidebar-foot .brand-mark svg {
240
+ width: 14px;
241
+ height: 14px;
242
+ display: block;
243
+ }
244
+ .sidebar-foot .brand-name {
245
+ font-size: 13px;
246
+ font-weight: 400;
247
+ line-height: 1;
248
+ }
249
+ .brand-version {
250
+ margin-left: auto;
251
+ font-family: var(--mono);
252
+ font-size: 10px;
253
+ color: var(--ink-muted);
254
+ font-variant-numeric: tabular-nums;
255
+ }
256
+
257
+ /* Top strip · single row, currently hosts only the collapse toggle on
258
+ the right. Matches the page-title-bar height so the topmost row of
259
+ the window reads as one unified band. */
260
+ .sidebar-top {
261
+ display: flex;
262
+ align-items: center;
263
+ padding: 0;
264
+ min-height: 40px;
265
+ margin-bottom: var(--s-2);
266
+ }
267
+ .collapse-toggle {
268
+ appearance: none;
269
+ background: transparent;
270
+ border: 0;
271
+ display: inline-flex;
272
+ align-items: center;
273
+ justify-content: center;
274
+ width: 40px;
275
+ height: 40px;
276
+ padding: 0;
277
+ border-radius: 4px;
278
+ cursor: pointer;
279
+ color: var(--ink);
280
+ transition: background .12s;
281
+ min-height: 0;
282
+ flex: 0 0 40px;
283
+ }
284
+ .collapse-toggle:hover { background: var(--sidebar-hover); }
285
+ .collapse-toggle .nav-icon {
286
+ width: 14px;
287
+ height: 14px;
288
+ flex: 0 0 14px;
289
+ }
290
+ .sidebar[data-collapsed="true"] .sidebar-top {
291
+ justify-content: center;
160
292
  }
161
293
 
162
294
  /* chevron flips when the sidebar is collapsed; the rest is util-item styling */
@@ -187,9 +319,9 @@
187
319
  }
188
320
  .sidebar-resize-handle:hover,
189
321
  body.is-resizing-sidebar .sidebar-resize-handle {
190
- background: linear-gradient(to right, transparent 0, transparent 2px,
191
- var(--ink-mid) 2px, var(--ink-mid) 3px,
192
- transparent 3px);
322
+ /* Wash the hit area in the accent color so the user knows the edge is
323
+ live and so the theme follows their accent choice. */
324
+ background: var(--accent-soft);
193
325
  }
194
326
  /* While dragging, freeze global cursor + suppress text selection so the
195
327
  whole page tracks resize cleanly even if pointer leaves the handle. */
@@ -200,3 +332,270 @@ body.is-resizing-sidebar {
200
332
  body.is-resizing-sidebar * {
201
333
  cursor: col-resize !important;
202
334
  }
335
+
336
+ /* === v1.0 codex-style sidebar tree === */
337
+
338
+ /* Compact top nav: smaller height + smaller font, so the folder tree
339
+ below dominates. */
340
+ .sidebar-nav.compact .nav-item {
341
+ font-size: 13px;
342
+ padding: 4px 10px;
343
+ min-height: 30px;
344
+ gap: 10px;
345
+ border-radius: 6px;
346
+ position: relative;
347
+ letter-spacing: -0.005em;
348
+ transition: background .14s ease, color .14s ease;
349
+ }
350
+ .sidebar-nav.compact .nav-item:hover { background: var(--sidebar-hover); }
351
+ .sidebar-nav.compact .nav-icon {
352
+ width: 14px;
353
+ height: 14px;
354
+ flex: 0 0 14px;
355
+ }
356
+ .sidebar[data-collapsed="true"] .sidebar-nav.compact .nav-item {
357
+ justify-content: center;
358
+ gap: 0;
359
+ padding: 8px 0;
360
+ }
361
+ .sidebar-nav.compact .nav-item.is-active {
362
+ background: var(--sidebar-active);
363
+ color: var(--ink);
364
+ font-weight: 500;
365
+ }
366
+
367
+ /* Tree section header. Looks like codex: uppercase label, small +
368
+ button on hover. */
369
+ .tree {
370
+ margin-top: var(--s-3);
371
+ display: flex;
372
+ flex-direction: column;
373
+ gap: 2px;
374
+ /* Only vertical scroll when the tree is taller than the rail.
375
+ overflow-y alone leaves overflow-x at the default `auto`, which
376
+ gives us an unwanted horizontal scrollbar whenever a session
377
+ title (or its hover row padding) brushes the viewport edge.
378
+ overflow-x:hidden truncates with ellipsis instead — labels
379
+ already do `text-overflow: ellipsis`. */
380
+ overflow-y: auto;
381
+ overflow-x: hidden;
382
+ flex: 1;
383
+ min-height: 0;
384
+ padding-bottom: var(--s-3);
385
+ }
386
+ .tree-head {
387
+ display: flex;
388
+ justify-content: space-between;
389
+ align-items: center;
390
+ padding: 0 10px;
391
+ min-height: 24px;
392
+ height: 24px;
393
+ font-size: 12px;
394
+ font-weight: 500;
395
+ letter-spacing: 0;
396
+ color: var(--ink-mid);
397
+ }
398
+ .tree-head-action {
399
+ appearance: none;
400
+ background: transparent;
401
+ border: 0;
402
+ padding: 2px 4px;
403
+ border-radius: 4px;
404
+ cursor: pointer;
405
+ color: var(--ink);
406
+ display: inline-flex;
407
+ align-items: center;
408
+ opacity: 0.6;
409
+ transition: opacity .12s, background .12s;
410
+ }
411
+ .tree-head-action:hover { opacity: 1; background: var(--sidebar-hover); }
412
+ .tree-head-action svg { width: 14px; height: 14px; }
413
+
414
+ /* Folder grouping. Chevron rotates on expand. */
415
+ .tree-folder {
416
+ display: flex;
417
+ flex-direction: column;
418
+ }
419
+ .tree-folder[data-dnd-over="true"] > .tree-folder-head {
420
+ box-shadow: 0 -2px 0 var(--accent) inset;
421
+ }
422
+ .tree-folder-head[draggable="true"] {
423
+ cursor: grab;
424
+ }
425
+ .tree-folder-head[draggable="true"]:active {
426
+ cursor: grabbing;
427
+ }
428
+ .tree-folder-head {
429
+ appearance: none;
430
+ background: transparent;
431
+ border: 0;
432
+ width: 100%;
433
+ text-align: left;
434
+ padding: 4px 8px;
435
+ display: flex;
436
+ align-items: center;
437
+ gap: 8px;
438
+ font-size: 13px;
439
+ font-weight: 400;
440
+ color: var(--ink);
441
+ cursor: pointer;
442
+ border-radius: 4px;
443
+ font-family: var(--body);
444
+ min-height: 28px;
445
+ }
446
+ .tree-folder-head:hover { background: var(--sidebar-hover); }
447
+ .tree-folder-icon {
448
+ display: inline-flex;
449
+ width: 14px;
450
+ height: 14px;
451
+ flex: 0 0 14px;
452
+ color: var(--ink);
453
+ }
454
+ .tree-folder-icon svg { width: 100%; height: 100%; }
455
+ .tree-folder-head.is-open .tree-folder-icon { color: var(--ink); }
456
+ .tree-folder-name {
457
+ flex: 1;
458
+ white-space: nowrap;
459
+ overflow: hidden;
460
+ text-overflow: ellipsis;
461
+ }
462
+ .tree-folder-count {
463
+ font-size: 13px;
464
+ color: var(--ink);
465
+ font-variant-numeric: tabular-nums;
466
+ padding: 0 5px;
467
+ background: transparent;
468
+ border-radius: 999px;
469
+ opacity: 0.6;
470
+ }
471
+ .tree-folder-actions {
472
+ display: none;
473
+ gap: 2px;
474
+ }
475
+ .tree-folder-head:hover .tree-folder-actions { display: inline-flex; }
476
+ .tree-folder-action {
477
+ appearance: none;
478
+ background: transparent;
479
+ border: 0;
480
+ display: inline-flex;
481
+ align-items: center;
482
+ justify-content: center;
483
+ width: 18px;
484
+ height: 18px;
485
+ padding: 0;
486
+ font-size: 13px;
487
+ color: var(--ink);
488
+ cursor: pointer;
489
+ opacity: 0.6;
490
+ border-radius: 3px;
491
+ }
492
+ .tree-folder-action:hover { opacity: 1; background: var(--sidebar-hover); }
493
+ .tree-folder-action svg { width: 12px; height: 12px; }
494
+
495
+ .tree-folder-body {
496
+ display: flex;
497
+ flex-direction: column;
498
+ /* No left padding here — the session rows themselves get padding-left
499
+ that lines their label up under the folder name. Keeping the bg
500
+ extend across the full sidebar width when selected/hovered. */
501
+ padding-left: 0;
502
+ }
503
+ .tree-empty {
504
+ font-size: 13px;
505
+ color: var(--ink);
506
+ padding: 3px 8px;
507
+ font-style: italic;
508
+ opacity: 0.5;
509
+ }
510
+
511
+ /* Session rows. Codex uses a colored dot + truncated label + tiny
512
+ timestamp on the right. */
513
+ .tree-session {
514
+ display: flex;
515
+ align-items: center;
516
+ gap: 8px;
517
+ /* Match the folder head: 8px left padding, then a 14px icon column
518
+ (the colored dot lives here, sized 8px and centered), then 8px gap
519
+ before the label — lines the label up exactly under the folder name. */
520
+ padding: 4px 8px;
521
+ border-radius: 4px;
522
+ cursor: pointer;
523
+ font-size: 13px;
524
+ color: var(--ink);
525
+ font-family: var(--body);
526
+ user-select: none;
527
+ transition: background .1s;
528
+ min-height: 28px;
529
+ }
530
+ .tree-session:hover { background: var(--sidebar-hover); }
531
+ .tree-session.is-active {
532
+ background: var(--sidebar-active);
533
+ font-weight: 500;
534
+ }
535
+ .tree-dot {
536
+ width: 14px;
537
+ height: 14px;
538
+ flex: 0 0 14px;
539
+ display: inline-flex;
540
+ align-items: center;
541
+ justify-content: center;
542
+ position: relative;
543
+ }
544
+ .tree-dot::after {
545
+ content: "";
546
+ width: 7px;
547
+ height: 7px;
548
+ border-radius: 50%;
549
+ background: var(--ink-faint);
550
+ transition: background .15s ease;
551
+ }
552
+ .tree-session.is-running .tree-dot::after {
553
+ background: var(--green);
554
+ box-shadow: 0 0 0 3px rgba(74, 138, 74, 0.18);
555
+ animation: tree-dot-pulse 1.8s ease-in-out infinite;
556
+ }
557
+ .tree-session.is-stopped .tree-dot::after {
558
+ background: var(--ink-faint);
559
+ box-shadow: none;
560
+ }
561
+ @keyframes tree-dot-pulse {
562
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(74, 138, 74, 0.45); }
563
+ 50% { box-shadow: 0 0 0 5px rgba(74, 138, 74, 0); }
564
+ }
565
+ .tree-label {
566
+ flex: 1;
567
+ white-space: nowrap;
568
+ overflow: hidden;
569
+ text-overflow: ellipsis;
570
+ }
571
+ .tree-session-actions {
572
+ display: none;
573
+ gap: 2px;
574
+ flex-shrink: 0;
575
+ }
576
+ .tree-session:hover .tree-session-actions { display: inline-flex; }
577
+ .tree-session:hover .tree-meta { display: none; }
578
+ .tree-session-action {
579
+ appearance: none;
580
+ background: transparent;
581
+ border: 0;
582
+ display: inline-flex;
583
+ align-items: center;
584
+ justify-content: center;
585
+ width: 18px;
586
+ height: 18px;
587
+ padding: 0;
588
+ color: var(--ink);
589
+ cursor: pointer;
590
+ opacity: 0.6;
591
+ border-radius: 3px;
592
+ }
593
+ .tree-session-action:hover { opacity: 1; background: var(--sidebar-hover); }
594
+ .tree-session-action svg { width: 12px; height: 12px; }
595
+ .tree-meta {
596
+ font-size: 13px;
597
+ color: var(--ink);
598
+ font-variant-numeric: tabular-nums;
599
+ flex-shrink: 0;
600
+ opacity: 0.5;
601
+ }